Files
bodyWeight/miniprogram/pages/addMember/addMember.ts
T
17792275749andClaude Sonnet 4.6 439f560218 fix(lefu): 完善蓝牙连接状态处理,修复断开后 UI 不同步问题
- connectState 回调增加 UNAVAILABLE 清理和 READY 自动重连
- disconnect() 增加 _intentionalConnect 标记避免误重连
- deviceWillDisconnect 增加 _activeProtocol 防重入判断
- connectedDevice/equipment/connectedWifi 增加断开状态响应
- connectedWifi 设备断开后自动退出配网页

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-21 09:43:49 +08:00

519 lines
14 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/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',
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,
/** 部门展示文案 */
unitDisplay: '',
/** 员工编号查询返回的完整数据,提交时作为 base 合并 */
employeeQueryResult: null as Record<string, any> | null,
},
/** tabs 切换:清空查询结果、重置表单 */
onTabTap(e: WechatMiniprogram.TouchEvent) {
const tab = e.currentTarget.dataset.value as string
this.setData({
userType: tab as 'employee' | 'family',
employeeQueried: false,
employeeNotFound: false,
unitDisplay: '',
employeeQueryResult: null,
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,
unitDisplay: '',
employeeQueryResult: null,
'form.name': '',
'form.gender': '',
'form.age': '',
'form.height': '',
'form.weight': '',
})
},
/** 员工编号查询 */
onQueryEmployee() {
const workNo = this.data.form.workNo.trim()
if (!workNo) {
wx.showToast({ title: '请填写员工编号', icon: 'none' })
return
}
if (this.data.querying) return
this.setData({ querying: true, employeeQueried: false, employeeNotFound: false })
get<any>('weighingScale/v5/getUserInfoByWkno', {
sn: lefuService.deviceInfo?.serialNumber ?? '',
workNo
}, { loading: true })
.then(res => {
const result = res.result
if (!result) {
this.setData({
employeeQueried: true,
employeeNotFound: true,
unitDisplay: '',
employeeQueryResult: null,
'form.name': '',
'form.gender': '',
'form.age': '',
'form.height': '',
'form.weight': '',
})
return
}
const idCard = result.idCard || ''
const parsed = this.parseIdCard(idCard)
const sex = result.sex || parsed.sex
this.setData({
employeeQueried: true,
employeeNotFound: false,
employeeQueryResult: result,
unitDisplay: [result.secondDepart, result.thirdDepart].filter(Boolean).join(' / '),
'form.name': result.realname || '',
'form.idCard': idCard,
'form.gender': sex === 1 ? '男' : (sex === 2 ? '女' : parsed.gender),
'form.age': parsed.age,
'form.sex': sex,
'form.height': result.height ? String(result.height) : '',
'form.weight': result.weight ? String(result.weight) : '',
birthday: result.birthday || '',
})
})
.catch(() => {
this.setData({
employeeQueried: true,
employeeNotFound: true,
unitDisplay: '',
employeeQueryResult: null,
})
})
.finally(() => {
this.setData({ querying: false })
})
},
onNameInput(e: WechatMiniprogram.Input) {
const name = e.detail.value
this.setData({
'form.name': name,
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()
if (this.isIdCardValid(idCard)) {
const parsed = this.parseIdCard(idCard)
this.setData({
'form.idCard': idCard,
'form.gender': parsed.gender,
'form.age': parsed.age,
'form.sex': parsed.sex,
birthday: parsed.birthday,
})
} else {
this.setData({
'form.idCard': idCard,
'form.gender': '',
'form.age': '',
'form.sex': 0,
birthday: '',
})
}
},
/** 家庭用户性别切换 */
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)
},
isIdCardValid(idCard: string): boolean {
return /^\d{17}[\dX]$/.test(idCard)
},
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: idCard || null,
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,
'form.sex': parsed.sex,
birthday: parsed.birthday,
})
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.sex': parsed.sex,
'form.height': serverResult.height ? String(serverResult.height) : '',
'form.weight': serverResult.weight ? String(serverResult.weight) : '',
birthday: serverResult.birthday || parsed.birthday
})
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,
'form.sex': parsed.sex,
birthday: serverResult.birthday || parsed.birthday
}
if (serverResult.height) {
data['form.height'] = String(serverResult.height)
}
if (serverResult.weight) {
data['form.weight'] = String(serverResult.weight)
}
this.setData(data)
}
})
.catch(() => {})
.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 { 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')
// 员工:以查询接口返回为 base;家庭:以选中的已有用户 + 身份证查询结果为 base
const baseSpread = userType === 'employee'
? (this.data.employeeQueryResult ?? {})
: { ...(this.data.selectedMember ?? {}), ...(this.data.serverResult ?? {}) }
const birthday = this.data.birthday
const finalSex = userType === 'family' ? sex : this.data.form.sex
const payload: Record<string, any> = {
...(baseSpread),
dataType: userType === 'family' ? 1 : 0,
parentUserId: userInfo?.userId ?? '',
sn: lefuService.deviceInfo?.serialNumber ?? '',
scaleDeviceId: lefuService.deviceInfo?.serialNumber ?? '',
realname: name.trim(),
idCard: idCard || null,
sex: finalSex,
birthday,
height: heightNum,
weight: isNaN(weightNum) ? 0 : weightNum,
connectDeviceInfo: connectDeviceInfoRaw ? JSON.stringify(connectDeviceInfoRaw) : '',
}
put('weighingScale/v5/edit/user', payload)
.then(() => {
wx.showToast({ title: '添加成功', 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: '添加失败,请重试', icon: 'none' })
})
}
})