Files
bodyWeight/miniprogram/pages/addMember/addMember.ts
T
17792275749andClaude Opus 4.7 4e4afd6958 feat(addMember): 员工/家庭双tab完整改造,状态机控制查询展示
- 员工tab:编号输入+查询→三种状态(未查询/查不到/查到展示)
- 家庭tab:familyInfo_卡片包裹,昵称/身份证/性别切换/出生年月/身高体重同行
- 切换tab或改编号即时清空查询结果
- 提交区分员工/家庭双模式校验,dataType写入payload

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-11 13:32:55 +08:00

510 lines
13 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { get, put } from '../../utils/request/index'
import { lefuService } from '../../lefu/index'
/** GET /weighingScale/v3/selectUserBySn/user 返回的单条结构(编辑回填用) */
interface SysUserDevice {
id: string
realname: string
idCard: string
height: number
weight: number
sex: number
birthday: string
}
/** GET /weighingScale/v3/getUserInfoByIdcard 返回的 result 结构 */
interface UserInfoFromServer {
id: string | null
userId: string
parentUserId: string | null
scaleDeviceId: string
deviceId: string | null
deviceType: string
dataType: number
userType: string | null
idCard: string
height: number
weight: number
birthday: string
sex: number
avatar: string | null
phone: string
thirdId: string | null
realname: string
bmi: number
workNo: string
remark: string | null
relationType: string | null
sn: string
delFlag: number
sex_dictText: string
}
/** 表单数据模型 */
interface MemberForm {
/** 员工编号 */
workNo: string
/** 姓名 / 昵称 */
name: string
/** 身份证号 */
idCard: string
/** 性别 */
gender: string
/** 年龄 */
age: string
/** 身高(cm) */
height: string
/** 体重(kg) */
weight: string
/** 1=男 2=女 */
sex: number
}
/** 身份证解析结果 */
interface IdCardParsed {
gender: string
age: string
sex: number
birthday: string
}
/** 从抽屉组件 select 事件接收的用户数据(完整 item) */
interface ExistingMember {
id: string
name: string
gender: '男' | '女'
age: number
height: number
weight: number
idCard: string
avatar?: string
[key: string]: any
}
/**
* 添加用户页
* 员工 tab:员工编号查询 → 回填信息 → 补充身高体重
* 家庭 tab:昵称/身份证/性别/出生年月/身高体重
*/
Page({
data: {
/** tabs 当前选中:employee | family */
userType: 'employee' as 'employee' | 'family',
/** 编辑模式标记 */
isEdit: false,
/** 编辑时的用户 ID */
userId: '',
form: {
workNo: '',
name: '',
idCard: '',
gender: '',
age: '',
height: '',
weight: '',
sex: 0,
} as MemberForm,
/** 出生年月(家庭用户 date picker */
birthday: '',
/** 从抽屉选中的完整用户数据,提交时作为 base spread */
selectedMember: null as ExistingMember | null,
showSelectDrawer: false,
/** 正在请求身份证查询接口 */
querying: false,
/** 是否已完成一次身份证查询 */
hasQueried: false,
/** 接口返回的用户信息 */
serverResult: null as UserInfoFromServer | null,
/** 表单只读状态(code=101 时置为 true */
readonly: false,
/** 是否显示确认弹框 */
showConfirmModal: false,
/** 员工编号是否已查询 */
employeeQueried: false,
/** 员工编号查询是否未找到 */
employeeNotFound: false,
},
onLoad(options: Record<string, string>) {
const { userId } = options
if (!userId) return
this.setData({ isEdit: true, userId })
get<SysUserDevice>('weighingScale/v3/select/userinfo/byId', { id: userId }, { loading: false })
.then((res: any) => {
const u: SysUserDevice = res.result
if (!u) return
const parsed = this.parseIdCard(u.idCard ?? '')
this.setData({
'form.workNo': u.workNo ?? '',
'form.name': u.realname ?? '',
'form.idCard': u.idCard ?? '',
'form.gender': parsed.gender,
'form.age': parsed.age,
'form.height': String(u.height ?? ''),
'form.weight': String(u.weight ?? ''),
})
if ((u.realname ?? '').trim() && this.isIdCardValid(u.idCard ?? '')) {
this.fetchUserInfoByIdCard(u.realname.trim(), u.idCard)
}
})
},
/** tabs 切换:清空查询结果、重置表单只读状态 */
onTabTap(e: WechatMiniprogram.TouchEvent) {
const tab = e.currentTarget.dataset.value as string
this.setData({
userType: tab as 'employee' | 'family',
employeeQueried: false,
employeeNotFound: false,
readonly: false,
hasQueried: false,
serverResult: null,
'form.workNo': '',
'form.name': '',
'form.idCard': '',
'form.gender': '',
'form.age': '',
'form.height': '',
'form.weight': '',
'form.sex': 0,
birthday: '',
selectedMember: null,
})
},
/** 员工编号变更即清空查询状态 */
onWorkNoInput(e: WechatMiniprogram.Input) {
this.setData({
'form.workNo': e.detail.value,
employeeQueried: false,
employeeNotFound: false,
'form.name': '',
'form.gender': '',
'form.age': '',
'form.height': '',
'form.weight': '',
})
},
/** 员工编号查询(API 预留) */
onQueryEmployee() {
const workNo = this.data.form.workNo.trim()
if (!workNo) {
wx.showToast({ title: '请填写员工编号', icon: 'none' })
return
}
// TODO: 接入员工编号查询接口
wx.showToast({ title: '查询功能暂未开放', icon: 'none' })
},
onNameInput(e: WechatMiniprogram.Input) {
const name = e.detail.value
this.setData({
'form.name': name,
'form.gender': '',
'form.age': '',
serverResult: null,
hasQueried: false,
readonly: false
})
if (name.trim() && this.isIdCardValid(this.data.form.idCard)) {
this.fetchUserInfoByIdCard(name.trim(), this.data.form.idCard)
}
},
onIdCardInput(e: WechatMiniprogram.Input) {
const idCard = (e.detail.value || '').trim().toUpperCase()
this.setData({
'form.idCard': idCard,
'form.gender': '',
'form.age': '',
serverResult: null,
hasQueried: false,
readonly: false
})
if (this.data.form.name.trim() && this.isIdCardValid(idCard)) {
this.fetchUserInfoByIdCard(this.data.form.name.trim(), idCard)
}
},
/** 家庭用户性别切换 */
onGenderTap(e: WechatMiniprogram.TouchEvent) {
const value = e.currentTarget.dataset.value as string
const sex = value === '男' ? 1 : 2
this.setData({
'form.gender': value,
'form.sex': sex,
})
},
/** 出生年月变更 */
onBirthdayChange(e: WechatMiniprogram.PickerChange) {
this.setData({ birthday: e.detail.value as string })
},
onHeightInput(e: WechatMiniprogram.Input) {
this.setData({ 'form.height': e.detail.value })
},
onWeightInput(e: WechatMiniprogram.Input) {
this.setData({ 'form.weight': e.detail.value })
},
/** 从生日字符串计算周岁 */
calcAge(birthday: string): number {
if (!birthday) return 0
const parts = birthday.split('-').map(Number)
const [year, month, day] = parts
if (!year || !month || !day) return 0
const now = new Date()
let age = now.getFullYear() - year
const nowMonth = now.getMonth() + 1
const nowDay = now.getDate()
if (nowMonth < month || (nowMonth === month && nowDay < day)) age -= 1
return Math.max(0, age)
},
/**
* 校验身份证格式:18 位,前 17 位数字,末位数字或 X
*/
isIdCardValid(idCard: string): boolean {
return /^\d{17}[\dX]$/.test(idCard)
},
/**
* 解析身份证,返回性别、年龄、sex 数值、出生日期
*/
parseIdCard(idCard: string): IdCardParsed {
const empty: IdCardParsed = { gender: '', age: '', sex: 0, birthday: '' }
if (!this.isIdCardValid(idCard)) return empty
const genderCode = parseInt(idCard.charAt(16), 10)
const sex = genderCode % 2 === 1 ? 1 : 2
const gender = sex === 1 ? '男' : '女'
const year = parseInt(idCard.substr(6, 4), 10)
const month = parseInt(idCard.substr(10, 2), 10)
const day = parseInt(idCard.substr(12, 2), 10)
if (!year || month < 1 || month > 12 || day < 1 || day > 31) return empty
const now = new Date()
let age = now.getFullYear() - year
if (now.getMonth() + 1 < month || (now.getMonth() + 1 === month && now.getDate() < day)) age -= 1
if (age < 0 || age > 150) return empty
const mm = String(month).padStart(2, '0')
const dd = String(day).padStart(2, '0')
return { gender, age: String(age), sex, birthday: `${year}-${mm}-${dd}` }
},
/**
* 调用服务端接口校验身份证与姓名
*/
fetchUserInfoByIdCard(name: string, idCard: string) {
if (this.data.querying) return
this.setData({ querying: true, serverResult: null, hasQueried: false })
get<any>('weighingScale/v3/getUserInfoByIdcard', {
sn: lefuService.deviceInfo?.serialNumber ?? '',
idCard,
realname: name
}, { loading: true })
.then(res => {
const serverResult = res.result
if (!serverResult) {
wx.showToast({
title: res.message || '未查询到用户信息',
icon: 'none'
})
const parsed = this.parseIdCard(idCard)
this.setData({
hasQueried: true,
'form.gender': parsed.gender,
'form.age': parsed.age,
})
return
}
if (serverResult.flag == 101) {
const parsed = this.parseIdCard(serverResult.idCard)
this.setData({
serverResult,
hasQueried: true,
readonly: true,
showConfirmModal: true,
'form.name': serverResult.realname ?? '',
'form.idCard': serverResult.idCard ?? '',
'form.gender': parsed.gender,
'form.age': parsed.age,
'form.height': serverResult.height ? String(serverResult.height) : '',
'form.weight': serverResult.weight ? String(serverResult.weight) : ''
})
return
}
if (serverResult.flag === null) {
const parsed = this.parseIdCard(serverResult.idCard)
const data: Record<string, any> = {
serverResult,
hasQueried: true,
'form.gender': parsed.gender,
'form.age': parsed.age
}
if (serverResult.height) {
data['form.height'] = String(serverResult.height)
}
if (serverResult.weight) {
data['form.weight'] = String(serverResult.weight)
}
this.setData(data)
}
})
.catch(() => {
// 查询失败保留本地解析结果,hasQueried 维持 false
})
.finally(() => {
this.setData({ querying: false })
})
},
onTapSelectExisting() {
this.setData({ showSelectDrawer: true })
},
onSelectMemberClose() {
this.setData({ showSelectDrawer: false })
},
onSelectMember(e: WechatMiniprogram.CustomEvent<ExistingMember>) {
const member = e.detail
const parsed = this.parseIdCard(member.idCard)
this.setData({
showSelectDrawer: false,
selectedMember: member,
'form.workNo': member.workNo || '',
'form.name': member.name,
'form.idCard': member.idCard,
'form.gender': parsed.gender,
'form.age': parsed.age,
'form.height': String(member.height),
'form.weight': String(member.weight),
})
if (member.name.trim() && this.isIdCardValid(member.idCard)) {
this.fetchUserInfoByIdCard(member.name.trim(), member.idCard)
}
},
/** 确认弹框 - 确认 */
onConfirmModalConfirm() {
this.setData({ showConfirmModal: false })
this.onSubmit()
},
/** 确认弹框 - 取消 */
onConfirmModalCancel() {
this.setData({
showConfirmModal: false,
readonly: false,
form: {
workNo: '',
name: '',
idCard: '',
gender: '',
age: '',
height: '',
weight: '',
sex: 0,
}
})
},
onCancel() {
wx.navigateBack()
},
onSubmit() {
const { name, idCard, gender, height, weight, sex } = this.data.form
const { isEdit, userType } = this.data
const heightNum = parseFloat(height)
const weightNum = parseFloat(weight)
if (userType === 'employee') {
// 员工:必须查询成功
if (!this.data.employeeQueried || this.data.employeeNotFound) {
wx.showToast({ title: '请先查询员工信息', icon: 'none' })
return
}
if (!height || isNaN(heightNum) || heightNum <= 0) {
wx.showToast({ title: '请填写身高', icon: 'none' })
return
}
} else {
// 家庭用户
if (!name.trim()) {
wx.showToast({ title: '请填写昵称', icon: 'none' })
return
}
if (!gender) {
wx.showToast({ title: '请选择性别', icon: 'none' })
return
}
if (!this.data.birthday.trim()) {
wx.showToast({ title: '请填写出生年月', icon: 'none' })
return
}
if (!height || isNaN(heightNum) || heightNum <= 0) {
wx.showToast({ title: '请填写身高', icon: 'none' })
return
}
}
const userInfo = wx.getStorageSync('userInfo')
const connectDeviceInfoRaw = wx.getStorageSync('connectDeviceInfo')
const serverResult = this.data.serverResult
const baseSpread = userType === 'family' ? (this.data.selectedMember ?? {}) : {}
const birthday = userType === 'family' ? this.data.birthday : ''
const finalSex = userType === 'family' ? sex : this.data.form.sex
const payload: Record<string, any> = {
...(baseSpread),
...(serverResult ?? {}),
dataType: userType === 'family' ? 1 : 0,
parentUserId: userInfo?.userId ?? '',
sn: lefuService.deviceInfo?.serialNumber ?? '',
scaleDeviceId: lefuService.deviceInfo?.serialNumber ?? '',
realname: name.trim(),
idCard,
sex: finalSex,
birthday,
height: heightNum,
weight: isNaN(weightNum) ? 0 : weightNum,
connectDeviceInfo: connectDeviceInfoRaw ? JSON.stringify(connectDeviceInfoRaw) : '',
}
if (isEdit) {
payload.id = this.data.userId
}
const successMsg = isEdit ? '保存成功' : '添加成功'
const failMsg = isEdit ? '保存失败,请重试' : '添加失败,请重试'
put('weighingScale/v3/edit/user', payload)
.then(() => {
wx.showToast({ title: successMsg, icon: 'success' })
setTimeout(() => {
const pages = getCurrentPages()
const prevPage = pages[pages.length - 2] as any
if (prevPage && prevPage.data?.sn) {
prevPage._loadData(prevPage.data.sn)
}
wx.navigateBack()
}, 1500)
})
.catch(() => {
wx.showToast({ title: failMsg, icon: 'none' })
})
}
})