Files
bodyWeight/miniprogram/pages/addMember/addMember.ts
T

341 lines
9.3 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 {
/** 姓名 */
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,
},
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.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 })
if (!name.trim()) {
this.setData({ serverResult: null, hasQueried: false })
return
}
if (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()
const parsed = this.parseIdCard(idCard)
this.setData({
'form.idCard': idCard,
'form.gender': parsed.gender,
'form.age': parsed.age
})
if (!parsed.gender) {
this.setData({ serverResult: null, hasQueried: false })
return
}
if (this.data.form.name.trim()) {
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 允许新增提交
*/
fetchUserInfoByIdCard(name: string, idCard: string) {
if (this.data.querying) return
this.setData({ querying: true, serverResult: null, hasQueried: false })
get<UserInfoFromServer>('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
}
const parsed = this.parseIdCard(serverResult.idCard)
this.setData({
serverResult,
hasQueried: true,
'form.gender': parsed.gender,
'form.age': parsed.age
})
})
.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.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),
})
},
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<string, any> = {
...(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' })
})
}
})