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 { /** 姓名 */ name: string /** 身份证号 */ idCard: string /** 性别(由身份证解析,只读) */ gender: string /** 年龄(由身份证解析,只读) */ age: string /** 身高(cm) */ height: string /** 体重(kg) */ weight: string } /** 身份证解析结果 */ 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 } /** * 添加/编辑成员页 * URL 参数含 userId 时为编辑模式,隐藏「从已有成员中选择」按钮并预填表单 */ Page({ data: { /** 编辑模式标记 */ isEdit: false, /** 编辑时的用户 ID */ userId: '', form: { name: '', idCard: '', gender: '', age: '', height: '', weight: '' } as MemberForm, /** 从抽屉选中的完整成员数据,提交时作为 base spread */ selectedMember: null as ExistingMember | null, showSelectDrawer: false, /** 正在请求身份证查询接口 */ querying: false, /** 是否已完成一次身份证查询(成功 200 即视为完成,无论 result 是否为 null) */ hasQueried: false, /** 接口返回的用户信息,姓名 + 身份证联动校正性别年龄 */ serverResult: null as UserInfoFromServer | null, /** 表单只读状态(code=101 时置为 true) */ readonly: false, /** 是否显示确认弹框 */ showConfirmModal: false, }, onLoad(options: Record) { const { userId } = options if (!userId) return this.setData({ isEdit: true, userId }) get('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.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) } }) }, 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) } }, onHeightInput(e: WechatMiniprogram.Input) { this.setData({ 'form.height': e.detail.value }) }, onWeightInput(e: WechatMiniprogram.Input) { this.setData({ 'form.weight': e.detail.value }) }, /** * 校验身份证格式: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}` } }, /** * 调用服务端接口校验身份证与姓名,成功后用接口返回的身份证重新计算性别年龄 * result 为 null 视为「查无此人」,标记 hasQueried 允许新增提交 * result.code 101 视为「信息已存在」,弹框提示并锁定表单 * result.code 102 视为正常,走原有逻辑 */ fetchUserInfoByIdCard(name: string, idCard: string) { if (this.data.querying) return this.setData({ querying: true, serverResult: null, hasQueried: false }) get('weighingScale/v3/getUserInfoByIdcard', { sn: lefuService.deviceInfo?.serialNumber ?? '', idCard, realname: name }, { loading: false }) .then(res => { const serverResult = res.result if (!serverResult) { this.setData({ hasQueried: true }) 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 = { 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) { const member = e.detail const parsed = this.parseIdCard(member.idCard) this.setData({ showSelectDrawer: false, selectedMember: member, '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), }) }, /** 确认弹框 - 确认 */ onConfirmModalConfirm() { this.setData({ showConfirmModal: false }) this.onSubmit() }, /** 确认弹框 - 取消 */ onConfirmModalCancel() { this.setData({ showConfirmModal: false, readonly: false, form: { name: '', idCard: '', gender: '', age: '', height: '', weight: '' } }) }, onCancel() { wx.navigateBack() }, onSubmit() { const { name, idCard, gender, age, height, weight } = this.data.form const heightNum = parseFloat(height) const weightNum = parseFloat(weight) if (!name.trim()) { wx.showToast({ title: '请填写姓名', icon: 'none' }) return } if (idCard.length !== 18) { wx.showToast({ title: '请填写正确的身份证号', icon: 'none' }) return } if (!gender || !age) { wx.showToast({ title: '身份证号有误,请检查', icon: 'none' }) return } if (!height || isNaN(heightNum) || heightNum <= 0) { wx.showToast({ title: '请填写身高', icon: 'none' }) return } if (!weight || isNaN(weightNum) || weightNum <= 0) { wx.showToast({ title: '请填写体重', icon: 'none' }) return } if (!this.data.hasQueried) { wx.showToast({ title: this.data.querying ? '身份信息验证中,请稍候' : '请检查姓名与身份证号', icon: 'none' }) return } const parsed = this.parseIdCard(idCard) const userInfo = wx.getStorageSync('userInfo') const connectDeviceInfoRaw = wx.getStorageSync('connectDeviceInfo') const serverResult = this.data.serverResult // selectedMember(抽屉选中) → serverResult(接口校验,可能为 null) → 表单/本地解析(用户输入) 依次覆盖 const payload: Record = { ...(this.data.selectedMember ?? {}), ...(serverResult ?? {}), parentUserId: userInfo?.userId ?? '', sn: lefuService.deviceInfo?.serialNumber ?? '', scaleDeviceId: lefuService.deviceInfo?.serialNumber ?? '', realname: name.trim(), idCard, sex: parsed.sex, birthday: parsed.birthday, height: heightNum, weight: weightNum, connectDeviceInfo: connectDeviceInfoRaw ? JSON.stringify(connectDeviceInfoRaw) : '', } if (this.data.isEdit) { payload.id = this.data.userId } const successMsg = this.data.isEdit ? '保存成功' : '添加成功' const failMsg = this.data.isEdit ? '保存失败,请重试' : '添加失败,请重试' put('weighingScale/v3/edit/user', payload) .then(() => { wx.showToast({ title: successMsg, icon: 'success' }) setTimeout(() => { // 通知上一页(equipmentMember)刷新成员列表 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' }) }) } })