diff --git a/miniprogram/api/index.ts b/miniprogram/api/index.ts new file mode 100644 index 0000000..39ee9d9 --- /dev/null +++ b/miniprogram/api/index.ts @@ -0,0 +1,2 @@ +export { checkOpenIdLogin, getUserInfoByIdCard, updateUser } from './user' +export type { UserInfo, LoginResult } from './user' diff --git a/miniprogram/api/user.ts b/miniprogram/api/user.ts new file mode 100644 index 0000000..c9e3056 --- /dev/null +++ b/miniprogram/api/user.ts @@ -0,0 +1,50 @@ +import { get, post, put } from '../utils/request/index' + +export interface UserInfo { + id: string + 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 | null + thirdId: string + realname: string + bmi: number + createBy: string + createTime: string + updateBy: string | null + updateTime: string | null + delFlag: number + sn: string | null + workNo: string | null + remark: string | null + relationType: string | null + sex_dictText: string + connectDeviceInfo: string +} + +export interface LoginResult { + user: UserInfo | null + token: string +} + +/** OpenID 登录 */ +export const checkOpenIdLogin = (code: string) => + post('weighingScale/v2/checkOpenIdLogin', { code }) + +/** 根据身份证号查询用户信息 */ +export const getUserInfoByIdCard = (params: { sn: string; idCard: string; realname: string }) => + get('weighingScale/v3/getUserInfoByIdcard', params, { loading: false }) + +/** 更新用户信息 */ +export const updateUser = (data: Record) => + put('weighingScale/v3/update/user', data) diff --git a/miniprogram/app.json b/miniprogram/app.json index daa4897..56e765a 100644 --- a/miniprogram/app.json +++ b/miniprogram/app.json @@ -1,23 +1,9 @@ { "pages": [ - "pages/login/login", - "pages/home/home", - "pages/connectedDevice/connectedDevice", - "pages/connectedWifi/connectedWifi", - "pages/equipmentMember/equipmentMember", "pages/supplementPersonal/supplementPersonal", - "pages/addMember/addMember", - "pages/editMember/editMember", - "pages/equipment/equipment", - "pages/measurementReport/measurementReport", - "pages/data/data", - "pages/my/my" + "pages/login/login" ], - "window": { - "navigationBarBackgroundColor": "#ffffff", - "navigationBarTextStyle": "black", - "navigationBarTitleText": "智能体重秤" - }, + "window": {}, "style": "v2", "rendererOptions": { "skyline": { @@ -32,7 +18,7 @@ "lazyCodeLoading": "requiredComponents", "plugins": { "ppScale-plugin": { - "version": "0.0.29", + "version": "0.0.31", "provider": "wx0826af4e4908f524" } }, @@ -43,37 +29,5 @@ }, "requiredPrivateInfos": [ "getLocation" - ], - "tabBar": { - "color": "#999999", - "selectedColor": "#000000", - "backgroundColor": "#ffffff", - "borderStyle": "black", - "list": [ - { - "pagePath": "pages/home/home", - "text": "首页", - "iconPath": "images/tabBar/home_default.png", - "selectedIconPath": "images/tabBar/home.png" - }, - { - "pagePath": "pages/equipment/equipment", - "text": "设备", - "iconPath": "images/tabBar/equipment_default.png", - "selectedIconPath": "images/tabBar/equipment.png" - }, - { - "pagePath": "pages/data/data", - "text": "数据", - "iconPath": "images/tabBar/data_default.png", - "selectedIconPath": "images/tabBar/data.png" - }, - { - "pagePath": "pages/my/my", - "text": "我的", - "iconPath": "images/tabBar/my_default.png", - "selectedIconPath": "images/tabBar/my.png" - } - ] - } + ] } \ No newline at end of file diff --git a/miniprogram/app.less b/miniprogram/app.less index bcf564c..a864113 100644 --- a/miniprogram/app.less +++ b/miniprogram/app.less @@ -1,19 +1,15 @@ -/**app.wxss**/ -@import "./styles/index.less"; - page { - width: 100%; - height: 100vh; - overflow: hidden; - box-sizing: border-box; - background: var(--color-bg); - /* 左右边距不加全局,避免全屏元素被挤 */ + width: 100%; + height: 100vh; + overflow: hidden; + box-sizing: border-box; + background-color: #F5F5F5; } /* 需要页面标准边距的业务容器,在 WXML 根节点使用 */ .page-container { - padding-left: var(--page-padding-x); - padding-right: var(--page-padding-x); + padding-left: 30rpx; + padding-right: 30rpx; } image { diff --git a/miniprogram/app.ts b/miniprogram/app.ts index 7a5194a..da96bab 100644 --- a/miniprogram/app.ts +++ b/miniprogram/app.ts @@ -1,18 +1,22 @@ App({ - globalData: { - /** 当前已连接设备的蓝牙状态,页面可读取 */ - connectState: '' as string, - }, + globalData: {}, - onLaunch() { - // 已登录则跳过登录页 - const userInfo = wx.getStorageSync('userInfo') - if (userInfo) { - const connectDeviceInfo = wx.getStorageSync('connectDeviceInfo') - const hasDevice = connectDeviceInfo && Object.keys(connectDeviceInfo).length > 0 - wx.reLaunch({ - url: hasDevice ? '/pages/home/home' : '/pages/connectedDevice/connectedDevice' + onLaunch() {}, + + getWxLoginCode(): Promise { + return new Promise((resolve, reject) => { + wx.login({ + success: (res) => { + if (res.code) { + resolve(res.code) + } else { + reject(new Error('获取登录凭证失败')) + } + }, + fail: (err) => { + reject(new Error(err.errMsg || '获取登录凭证失败')) + }, }) - } + }) }, }) diff --git a/miniprogram/components/selectMemberDrawer/selectMemberDrawer.json b/miniprogram/components/selectMemberDrawer/selectMemberDrawer.json deleted file mode 100644 index a89ef4d..0000000 --- a/miniprogram/components/selectMemberDrawer/selectMemberDrawer.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "component": true, - "usingComponents": {} -} diff --git a/miniprogram/components/selectMemberDrawer/selectMemberDrawer.less b/miniprogram/components/selectMemberDrawer/selectMemberDrawer.less deleted file mode 100644 index 976f2cd..0000000 --- a/miniprogram/components/selectMemberDrawer/selectMemberDrawer.less +++ /dev/null @@ -1,263 +0,0 @@ -/* 遮罩层 */ -.drawer-mask { - position: fixed; - top: 0; - left: 0; - width: 100%; - height: 100%; - background-color: rgba(37, 37, 53, 0.6); - z-index: 100; - opacity: 0; - transition: opacity 0.3s ease; - - &--visible { - opacity: 1; - } -} - -/* 抽屉面板 */ -.drawer-panel { - position: fixed; - bottom: 0; - left: 0; - width: 100%; - /* 最大高度约 75% 屏幕 */ - height: 85vh; - background-color: #FFFFFF; - border-radius: 24rpx 24rpx 0 0; - z-index: 101; - display: flex; - flex-direction: column; - transform: translateY(100%); - transition: transform 0.3s ease; - - &--visible { - transform: translateY(0); - } -} - -/* 拖拽把手 */ -.drawer-handle { - width: 72rpx; - height: 6rpx; - background-color: #77849E; - border-radius: 3rpx; - margin: 32rpx auto 40rpx; -} - -/* 标题栏 */ -.drawer-header { - width: 100%; - height: 32rpx; - padding: 0 30rpx; - box-sizing: border-box; - display: flex; - align-items: center; - justify-content: space-between; - - .drawer-title { - color: #252535; - height: 32rpx; - font-size: 32rpx; - font-weight: 700; - line-height: 32rpx; - } - - /* 关闭按钮 */ - - .drawer-close { - width: 32rpx; - height: 32rpx; - display: flex; - align-items: center; - justify-content: center; - - .drawer-close-icon { - width: 28rpx; - height: 28rpx; - position: relative; - - &::before, - &::after { - content: ''; - position: absolute; - top: 50%; - left: 50%; - width: 100%; - height: 3rpx; - background-color: #252535; - border-radius: 2rpx; - } - - &::before { - transform: translate(-50%, -50%) rotate(45deg); - } - - &::after { - transform: translate(-50%, -50%) rotate(-45deg); - } - } - } -} - -/* 顶部分隔线 */ -.drawer-divider { - width: 690rpx; - height: 2rpx; - background-color: #EAEDF1; - margin: 39rpx 30rpx 0; - flex-shrink: 0; -} - -/* 用户滚动列表 */ -.drawer-list { - flex: 1; - overflow: hidden; - padding-bottom: env(safe-area-inset-bottom); -} - -/* 用户行 */ -.member-item { - width: 100%; - height: 90rpx; - padding: 0 30rpx; - box-sizing: border-box; - display: flex; - flex-direction: row; - align-items: center; - margin-top: 24rpx; - - /* 有图片头像 */ - - .member-avatar { - width: 90rpx; - height: 90rpx; - border-radius: 24rpx; - flex-shrink: 0; - - &--selected { - border: 3rpx solid #50A6FF; - } - } - - /* 纯色占位头像 */ - - .member-avatar-placeholder { - width: 90rpx; - height: 90rpx; - border-radius: 24rpx; - flex-shrink: 0; - - /* 男:蓝色 */ - - &--male { - background-color: #C6E6FF; - } - - /* 女:粉色 */ - - &--female { - background-color: #FFD5E3; - } - - /* 选中男 */ - - &--selected-male { - border: 3rpx solid #50A6FF; - width: 96rpx; - height: 96rpx; - margin: -3rpx 0 0 -3rpx; - } - - /* 选中女 */ - - &--selected-female { - border: 3rpx solid #FF8FB5; - width: 96rpx; - height: 96rpx; - margin: -3rpx 0 0 -3rpx; - } - } - - /* 姓名 + 副标题 */ - - .member-info { - flex: 1; - height: 78rpx; - display: flex; - flex-direction: column; - justify-content: space-between; - margin-left: 23rpx; - - .member-name { - color: #333333; - font-size: 32rpx; - font-weight: 500; - line-height: 32rpx; - white-space: nowrap; - } - - .member-sub { - color: #77849E; - font-size: 26rpx; - line-height: 34rpx; - white-space: nowrap; - } - } - - .member-type { - height: 40rpx; - flex-shrink: 0; - - .member-type-tag { - height: 40rpx; - padding: 0 14rpx; - font-size: 22rpx; - line-height: 40rpx; - border-radius: 6rpx; - } - - .member-type-tag--self { - color: #1385FA; - background-color: rgba(19, 133, 250, 0.12); - } - - .member-type-tag--employee { - color: #10B981; - background-color: rgba(16, 185, 129, 0.12); - } - - .member-type-tag--family { - color: #F59E0B; - background-color: rgba(245, 158, 11, 0.12); - } - } -} - -/* 行间水平分隔线 */ -.member-separator { - width: 690rpx; - height: 2rpx; - background-color: #EAEDF1; - margin: 24rpx 30rpx 0; -} - -/* 空态占位 */ -.drawer-empty { - display: flex; - flex-direction: column; - align-items: center; - padding-top: 80rpx; - - .drawer-empty-icon { - width: 193rpx; - height: 138rpx; - margin-bottom: 30rpx; - } - - .drawer-empty-text { - color: #808080; - font-size: 28rpx; - line-height: 28rpx; - } -} diff --git a/miniprogram/components/selectMemberDrawer/selectMemberDrawer.ts b/miniprogram/components/selectMemberDrawer/selectMemberDrawer.ts deleted file mode 100644 index 4ea9d18..0000000 --- a/miniprogram/components/selectMemberDrawer/selectMemberDrawer.ts +++ /dev/null @@ -1,194 +0,0 @@ -import { get } from '../../utils/request/index' -import { lefuService } from '../../lefu/index' - -/** GET /weighingScale/v3/select/addMembers/user 返回的单条结构 */ -interface AddMemberApiItem { - id: string - realname: string - sex_dictText: '男' | '女' - birthday: string - height: number - weight: number - idCard: string | null - avatar: string | null - dataType?: number - relationType?: number | string | null -} - -/** GET /weighingScale/v3/select/history/user 返回的单条结构 */ -interface HistoryApiItem { - id: string - userId: string - realname: string - sex: number - height: number - birthday: string - avatar: string | null - relationType: number | string | null - dataType?: number -} - -/** 抽屉内部用户项(含回填所需全量字段) */ -interface ExistingMember { - id: string - name: string - gender: '男' | '女' - age: number - height: number - weight: number - idCard: string - avatar?: string - selected?: boolean - /** history 模式额外携带,供页面构建 userInfo */ - userId?: string - sex?: number - birthday?: string - relationType?: number | string | null - /** 0=员工 1=家庭成员 */ - dataType?: number -} - -/** - * 选择已有用户抽屉组件 - * Properties: show(控制显示) - * Events: close(关闭), select(选中用户,携带 ExistingMember) - */ -Component({ - properties: { - /** 是否显示抽屉 */ - show: { - type: Boolean, - value: false - }, - /** 数据来源模式:addMember=新增用户选择 / history=历史数据切换用户 */ - source: { - type: String, - value: 'addMember' - }, - /** 需要排除的用户 ID(history 模式下排除当前查看的用户) */ - excludeUserId: { - type: String, - value: '' - } - }, - - data: { - /** 控制 wx:if,DOM 是否存在 */ - innerShow: false, - /** 控制 --visible 类,触发 CSS transition */ - animVisible: false, - /** 用户列表(组件内部拉取) */ - members: [] as ExistingMember[] - }, - - observers: { - /** - * 监听外部 show 变化: - * 打开时先拉数据再展示抽屉;关闭时移除动画类后移除 DOM - */ - show(val: boolean) { - if (val) { - this._fetchMembers() - this.setData({ innerShow: true }) - setTimeout(() => { - this.setData({ animVisible: true }) - }, 20) - } else { - this.setData({ animVisible: false }) - setTimeout(() => { - this.setData({ innerShow: false }) - }, 300) - } - } - }, - - methods: { - /** 从生日字符串(YYYY-MM-DD)计算周岁 */ - _calcAge(birthday: string): number { - if (!birthday) return 0 - const [year, month, day] = birthday.split('-').map(Number) - if (!year || !month || !day) return 0 - const now = new Date() - let age = now.getFullYear() - year - if (now.getMonth() + 1 < month || (now.getMonth() + 1 === month && now.getDate() < day)) age -= 1 - return Math.max(0, age) - }, - - /** 调接口获取可选用户列表,根据 source 调不同接口 */ - _fetchMembers() { - const userInfo = wx.getStorageSync('userInfo') as { userId?: string } | null - const userId = userInfo?.userId ?? '' - - wx.showLoading({ title: '加载中...', mask: true }) - - if (this.properties.source === 'history') { - const params: Record = { userId } - if (this.properties.excludeUserId) { - params.userIdExclude = this.properties.excludeUserId - } - get('weighingScale/v3/select/history/user', params, { loading: false }) - .then((res: any) => { - const members: ExistingMember[] = (res.result ?? []).map((item: HistoryApiItem) => ({ - id: item.id, - name: item.realname, - gender: item.sex === 1 ? '男' : '女', - age: this._calcAge(item.birthday), - height: item.height, - weight: 0, - idCard: '', - avatar: item.avatar ?? '', - userId: item.userId, - sex: item.sex, - birthday: item.birthday, - relationType: item.relationType, - dataType: item.dataType, - })) - this.setData({ members }) - }) - .finally(() => { - wx.hideLoading() - }) - } else { - const sn = lefuService.deviceInfo?.serialNumber ?? '' - get('weighingScale/v5/select/addMembers/user', { userId, sn }, { loading: false }) - .then((res: any) => { - const members: ExistingMember[] = (res.result ?? []).map((item: AddMemberApiItem) => ({ - id: item.id, - name: item.realname, - gender: item.sex_dictText, - age: this._calcAge(item.birthday), - height: item.height, - weight: item.weight, - idCard: item.idCard ?? '', - avatar: item.avatar ?? '', - relationType: item.relationType, - dataType: item.dataType, - })) - this.setData({ members }) - }) - .finally(() => { - wx.hideLoading() - }) - } - }, - - /** 点击遮罩关闭 */ - onTapMask() { - this.triggerEvent('close') - }, - - /** 点击关闭按钮 */ - onTapClose() { - this.triggerEvent('close') - }, - - /** - * 点击用户行,触发 select 事件 - * @param e 携带 data-member 的点击事件 - */ - onTapMember(e: WechatMiniprogram.TouchEvent) { - const member = e.currentTarget.dataset.member as ExistingMember - this.triggerEvent('select', member) - } - } -}) diff --git a/miniprogram/components/selectMemberDrawer/selectMemberDrawer.wxml b/miniprogram/components/selectMemberDrawer/selectMemberDrawer.wxml deleted file mode 100644 index cfe1365..0000000 --- a/miniprogram/components/selectMemberDrawer/selectMemberDrawer.wxml +++ /dev/null @@ -1,70 +0,0 @@ - - - - - - - - - - - - - - 选择已有用户 - - - - - - - - - - - - - - - - - - - - - - - - {{ item.name }} - {{ item.gender }}·{{ item.age }}岁 丨 {{ item.height }}cm - - - - - 本人 - - - 员工 - - - 家庭用户 - - - - - - - - - - - - - - - 暂无可选用户 - - - - diff --git a/miniprogram/components/userInfoConfirmModal/userInfoConfirmModal.json b/miniprogram/components/userInfoConfirmModal/userInfoConfirmModal.json deleted file mode 100644 index ac28323..0000000 --- a/miniprogram/components/userInfoConfirmModal/userInfoConfirmModal.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "component": true, - "usingComponents": {} -} diff --git a/miniprogram/components/userInfoConfirmModal/userInfoConfirmModal.less b/miniprogram/components/userInfoConfirmModal/userInfoConfirmModal.less deleted file mode 100644 index 53f8f43..0000000 --- a/miniprogram/components/userInfoConfirmModal/userInfoConfirmModal.less +++ /dev/null @@ -1,139 +0,0 @@ -.confirm-modal { - position: fixed; - top: 0; - left: 0; - right: 0; - bottom: 0; - z-index: 1000; - display: flex; - align-items: center; - justify-content: center; -} - -.modal-mask { - position: absolute; - top: 0; - left: 0; - right: 0; - bottom: 0; - background: rgba(0, 0, 0, 0.5); - opacity: 0; - transition: opacity 0.3s ease; - - &.anim-visible { - opacity: 1; - } -} - -.modal-content { - position: relative; - width: 660rpx; - background-color: #ffffff; - border-radius: 24rpx; - overflow: hidden; - transform: scale(0.9); - opacity: 0; - transition: all 0.3s ease; - - &.anim-visible { - transform: scale(1); - opacity: 1; - } -} - -.modal-header { - padding-top: 50rpx; - width: 100%; - margin-bottom: 30rpx; - - .modal-title { - width: 100%; - height: 36rpx; - font-weight: bolder; - font-size: 36rpx; - color: #252535; - line-height: 36rpx; - text-align: center; - margin-bottom: 24rpx; - } - - .modal-sub-title { - width: 100%; - height: 32rpx; - font-weight: 400; - font-size: 32rpx; - color: #1385FA; - line-height: 32rpx; - text-align: center; - } -} - -.modal-body { - width: 100%; - box-sizing: border-box; - padding: 0 30rpx; - margin-bottom: 30rpx; - - .modal-body-box { - width: 100%; - padding: 0 30rpx; - box-sizing: border-box; - border-radius: 16rpx; - background-color: #F6F6F6; - - .info-row { - width: 100%; - height: 80rpx; - display: flex; - padding: 10rpx 0; - align-items: center; - justify-content: space-between; - box-sizing: border-box; - border-bottom: 1px solid #EAECF1; - - .info-label { - color: #77849E; - height: 80rpx; - font-size: 28rpx; - line-height: 80rpx; - } - - .info-value { - color: #252535; - height: 80rpx; - font-weight: 500; - font-size: 28rpx; - line-height: 80rpx; - } - - &:last-of-type { - border-bottom: none; - } - } - } -} - -.modal-footer { - width: 100%; - height: 110rpx; - display: flex; - border-top: 1rpx solid #EAECF1; - - .modal-btn { - flex: 1; - height: 110rpx; - line-height: 110rpx; - font-size: 36rpx; - text-align: center; - } - - .modal-btn-cancel { - color: #77849E; - border-right: 1rpx solid #EAECF1; - } - - .modal-btn-confirm { - color: #1385FA; - font-weight: 500; - } -} diff --git a/miniprogram/components/userInfoConfirmModal/userInfoConfirmModal.ts b/miniprogram/components/userInfoConfirmModal/userInfoConfirmModal.ts deleted file mode 100644 index 850a43e..0000000 --- a/miniprogram/components/userInfoConfirmModal/userInfoConfirmModal.ts +++ /dev/null @@ -1,63 +0,0 @@ -/** - * 用户信息确认弹框组件 - * Properties: show(控制显示), userInfo(用户信息) - * Events: confirm(确认), cancel(取消) - */ -Component({ - properties: { - /** 是否显示弹框 */ - show: { - type: Boolean, - value: false - }, - /** 用户信息数据 */ - userInfo: { - type: Object, - value: {} - } - }, - - data: { - /** 控制 wx:if,DOM 是否存在 */ - innerShow: false, - /** 控制 --visible 类,触发 CSS transition */ - animVisible: false - }, - - observers: { - /** - * 监听外部 show 变化: - * 打开时先展示弹框再执行动画;关闭时移除动画类后移除 DOM - */ - show(val: boolean) { - if (val) { - this.setData({ innerShow: true }) - setTimeout(() => { - this.setData({ animVisible: true }) - }, 20) - } else { - this.setData({ animVisible: false }) - setTimeout(() => { - this.setData({ innerShow: false }) - }, 300) - } - } - }, - - methods: { - /** 点击遮罩关闭 */ - onTapMask() { - this.triggerEvent('cancel') - }, - - /** 点击取消按钮 */ - onTapCancel() { - this.triggerEvent('cancel') - }, - - /** 点击确认按钮 */ - onTapConfirm() { - this.triggerEvent('confirm') - } - } -}) diff --git a/miniprogram/components/userInfoConfirmModal/userInfoConfirmModal.wxml b/miniprogram/components/userInfoConfirmModal/userInfoConfirmModal.wxml deleted file mode 100644 index b24c0fa..0000000 --- a/miniprogram/components/userInfoConfirmModal/userInfoConfirmModal.wxml +++ /dev/null @@ -1,45 +0,0 @@ - - - - - - - - - 当前身份证号已存在 - 是否直接复用信息? - - - - - - - 姓名: - {{ userInfo.realname }} - - - 身份证号: - {{ userInfo.idCard }} - - - 性别: - {{ userInfo.sex_dictText }} - - - 身高: - {{ userInfo.height }}cm - - - 体重: - {{ userInfo.weight }}kg - - - - - - - 取消 - 确认 - - - diff --git a/miniprogram/images/connectedDevice/icon.png b/miniprogram/images/connectedDevice/icon.png deleted file mode 100644 index 1df92b3..0000000 Binary files a/miniprogram/images/connectedDevice/icon.png and /dev/null differ diff --git a/miniprogram/images/connectedWifi/close.png b/miniprogram/images/connectedWifi/close.png deleted file mode 100644 index 6e32387..0000000 Binary files a/miniprogram/images/connectedWifi/close.png and /dev/null differ diff --git a/miniprogram/images/connectedWifi/noData.png b/miniprogram/images/connectedWifi/noData.png deleted file mode 100644 index 37783f3..0000000 Binary files a/miniprogram/images/connectedWifi/noData.png and /dev/null differ diff --git a/miniprogram/images/connectedWifi/open.png b/miniprogram/images/connectedWifi/open.png deleted file mode 100644 index 2b2c650..0000000 Binary files a/miniprogram/images/connectedWifi/open.png and /dev/null differ diff --git a/miniprogram/images/connectedWifi/wifi.png b/miniprogram/images/connectedWifi/wifi.png deleted file mode 100644 index a0882a8..0000000 Binary files a/miniprogram/images/connectedWifi/wifi.png and /dev/null differ diff --git a/miniprogram/images/connectedWifi/wifiPwd.png b/miniprogram/images/connectedWifi/wifiPwd.png deleted file mode 100644 index 2999896..0000000 Binary files a/miniprogram/images/connectedWifi/wifiPwd.png and /dev/null differ diff --git a/miniprogram/images/data/noData.png b/miniprogram/images/data/noData.png deleted file mode 100644 index 9a7fab2..0000000 Binary files a/miniprogram/images/data/noData.png and /dev/null differ diff --git a/miniprogram/images/device/CF568_G.png b/miniprogram/images/device/CF568_G.png deleted file mode 100644 index 0af20bb..0000000 Binary files a/miniprogram/images/device/CF568_G.png and /dev/null differ diff --git a/miniprogram/images/device/CF577.png b/miniprogram/images/device/CF577.png deleted file mode 100644 index 128435b..0000000 Binary files a/miniprogram/images/device/CF577.png and /dev/null differ diff --git a/miniprogram/images/device/CF636_G.png b/miniprogram/images/device/CF636_G.png deleted file mode 100644 index f77d79f..0000000 Binary files a/miniprogram/images/device/CF636_G.png and /dev/null differ diff --git a/miniprogram/images/device/YX-B4-568-BW.png b/miniprogram/images/device/YX-B4-568-BW.png deleted file mode 100644 index 0af20bb..0000000 Binary files a/miniprogram/images/device/YX-B4-568-BW.png and /dev/null differ diff --git a/miniprogram/images/device/YX-B8-636-BW.png b/miniprogram/images/device/YX-B8-636-BW.png deleted file mode 100644 index f77d79f..0000000 Binary files a/miniprogram/images/device/YX-B8-636-BW.png and /dev/null differ diff --git a/miniprogram/images/home/header.jpg b/miniprogram/images/home/header.jpg deleted file mode 100644 index a6138b3..0000000 Binary files a/miniprogram/images/home/header.jpg and /dev/null differ diff --git a/miniprogram/images/home/lianjie.png b/miniprogram/images/home/lianjie.png deleted file mode 100644 index 7f68b2e..0000000 Binary files a/miniprogram/images/home/lianjie.png and /dev/null differ diff --git a/miniprogram/images/home/noData.png b/miniprogram/images/home/noData.png deleted file mode 100644 index 9a7fab2..0000000 Binary files a/miniprogram/images/home/noData.png and /dev/null differ diff --git a/miniprogram/images/my/about.png b/miniprogram/images/my/about.png deleted file mode 100644 index 3dcc2fd..0000000 Binary files a/miniprogram/images/my/about.png and /dev/null differ diff --git a/miniprogram/images/my/header.png b/miniprogram/images/my/header.png deleted file mode 100644 index c9889a8..0000000 Binary files a/miniprogram/images/my/header.png and /dev/null differ diff --git a/miniprogram/images/selectMemberDrawer/noData.png b/miniprogram/images/selectMemberDrawer/noData.png deleted file mode 100644 index 9a7fab2..0000000 Binary files a/miniprogram/images/selectMemberDrawer/noData.png and /dev/null differ diff --git a/miniprogram/images/tabBar/.gitkeep b/miniprogram/images/tabBar/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/miniprogram/images/tabBar/data.png b/miniprogram/images/tabBar/data.png deleted file mode 100644 index c203668..0000000 Binary files a/miniprogram/images/tabBar/data.png and /dev/null differ diff --git a/miniprogram/images/tabBar/data_default.png b/miniprogram/images/tabBar/data_default.png deleted file mode 100644 index a837ba7..0000000 Binary files a/miniprogram/images/tabBar/data_default.png and /dev/null differ diff --git a/miniprogram/images/tabBar/equipment.png b/miniprogram/images/tabBar/equipment.png deleted file mode 100644 index d0002ff..0000000 Binary files a/miniprogram/images/tabBar/equipment.png and /dev/null differ diff --git a/miniprogram/images/tabBar/equipment_default.png b/miniprogram/images/tabBar/equipment_default.png deleted file mode 100644 index 779f3b6..0000000 Binary files a/miniprogram/images/tabBar/equipment_default.png and /dev/null differ diff --git a/miniprogram/images/tabBar/home.png b/miniprogram/images/tabBar/home.png deleted file mode 100644 index 70d8fb1..0000000 Binary files a/miniprogram/images/tabBar/home.png and /dev/null differ diff --git a/miniprogram/images/tabBar/home_default.png b/miniprogram/images/tabBar/home_default.png deleted file mode 100644 index 5f52e32..0000000 Binary files a/miniprogram/images/tabBar/home_default.png and /dev/null differ diff --git a/miniprogram/images/tabBar/my.png b/miniprogram/images/tabBar/my.png deleted file mode 100644 index 54b2ec9..0000000 Binary files a/miniprogram/images/tabBar/my.png and /dev/null differ diff --git a/miniprogram/images/tabBar/my_default.png b/miniprogram/images/tabBar/my_default.png deleted file mode 100644 index 8825219..0000000 Binary files a/miniprogram/images/tabBar/my_default.png and /dev/null differ diff --git a/miniprogram/lefu/index.ts b/miniprogram/lefu/index.ts deleted file mode 100644 index 36397eb..0000000 --- a/miniprogram/lefu/index.ts +++ /dev/null @@ -1,12 +0,0 @@ -export { lefuService } from './service' -export type { - ScannedDevice, - LeFuWifiItem, - ProgressData, - LockData, - LeFuConfig, - DeviceSetting, - DeviceMember, - DeviceInfo, - FirmwareVersion, -} from './types' diff --git a/miniprogram/lefu/service.ts b/miniprogram/lefu/service.ts deleted file mode 100644 index f7f6532..0000000 --- a/miniprogram/lefu/service.ts +++ /dev/null @@ -1,488 +0,0 @@ -import { get } from '../utils/request/index' -import type { - RawDevice, - LeFuPlugin, - ScannedDevice, - LeFuWifiItem, - ProgressData, - LockData, - LeFuConfig, - DeviceSetting, - DeviceMember, - DeviceInfo, - FirmwareVersion, -} from './types' - -/** 顶层加载插件(对齐 demo,requirePlugin 必须在文件顶层调用) */ -const plugin = requirePlugin('ppScale-plugin') as LeFuPlugin - -/** 乐福 SDK 配置 */ -const LEFU_CONFIG: LeFuConfig = { - key: 'lefudc91611833e18d94', - secret: 'eQ50JYimMp0X7uhNLj6D3lTEk4TUUvEG7dvaqKM9t1U=', - // domain1: 'http://10.10.10.10:8889', - // domain2: 'http://10.10.10.10:8889/weight', - domain1: 'http://device.shuziweidao.com:80/gateway', - domain2: 'http://device.shuziweidao.com:80/weight', -} - -/** 支持的设备型号配置列表(5款) */ -const DEVICE_SETTINGS: DeviceSetting[] = [ - { - advLength: 999, calorieStatus: 0, createBy: null, - deviceAccuracyType: 2, deviceCalcuteType: 3, deviceConnectType: 2, - deviceFuncType: 223, deviceName: 'CF568_G', devicePowerType: 3, - deviceProtocolType: 3, deviceType: 1, deviceUnitType: '0,1,11', - id: 65, imgUrl: null, macAddressStart: 6, remark: null, - sign: 'FF', status: 0, uhStatus: 1, updateBy: null, - }, - { - advLength: 999, calorieStatus: 0, createBy: null, - deviceAccuracyType: 2, deviceCalcuteType: 3, deviceConnectType: 2, - deviceFuncType: 223, deviceName: 'YX-B4-568-BW', devicePowerType: 3, - deviceProtocolType: 3, deviceType: 1, deviceUnitType: '0,1,11', - id: 65, imgUrl: null, macAddressStart: 6, remark: null, - sign: 'FF', status: 0, uhStatus: 1, updateBy: null, - }, - { - advLength: 999, calorieStatus: 0, createBy: null, - deviceAccuracyType: 2, deviceCalcuteType: 4, deviceConnectType: 2, - deviceFuncType: 65759, deviceName: 'CF636_G', devicePowerType: 3, - deviceProtocolType: 3, deviceType: 1, deviceUnitType: '0,1,2', - id: 216, imgUrl: null, macAddressStart: 6, remark: null, - sign: 'FF', status: 0, uhStatus: 1, updateBy: null, - }, - { - advLength: 999, calorieStatus: 0, createBy: null, - deviceAccuracyType: 2, deviceCalcuteType: 4, deviceConnectType: 2, - deviceFuncType: 65759, deviceName: 'YX-B8-636-BW', devicePowerType: 3, - deviceProtocolType: 3, deviceType: 1, deviceUnitType: '0,1,2', - id: 216, imgUrl: null, macAddressStart: 6, remark: null, - sign: 'FF', status: 0, uhStatus: 1, updateBy: null, - }, - { - advLength: 999, calorieStatus: 0, createBy: null, - deviceAccuracyType: 2, deviceCalcuteType: 4, deviceConnectType: 2, - deviceFuncType: 223, deviceName: 'CF577', devicePowerType: 3, - deviceProtocolType: 3, deviceType: 1, deviceUnitType: '0,1,11', - id: 51, imgUrl: null, macAddressStart: 6, remark: null, - sign: 'FF', status: 0, uhStatus: 1, updateBy: null, - }, -] - -const TAG = '[LeFuService]' - -class LeFuService { - private _activeProtocol: any = null - private _connectState = '' - private _deviceInfo: DeviceInfo | null = null - private _busSubscribed = false - private _intentionalConnect = false - - private _reconnectCount = 0 - private readonly _maxReconnect = 3 - private _reconnectTimer: number | null = null - private _keepAliveTimer: number | null = null - private _lastRawDevice: RawDevice | null = null - - private _onDevicesListCb: ((devices: ScannedDevice[]) => void) | null = null - private _onConnectStateCb: ((state: string) => void) | null = null - private _onDeviceConnectCb: (() => void) | null = null - private _onProgressCb: ((data: ProgressData) => void) | null = null - private _onLockedCb: ((data: LockData) => void) | null = null - private _onDisconnectedCb: (() => void) | null = null - private _onDeviceInfoCb: ((info: DeviceInfo) => void) | null = null - - get config(): LeFuConfig { return LEFU_CONFIG } - get isConnected(): boolean { return !!this._activeProtocol } - get connectState(): string { return this._connectState } - get BLUE_STATE(): Record { return plugin.BLUE_STATE ?? {} } - get deviceInfo(): DeviceInfo | null { return this._deviceInfo } - - // ─── 回调注册 ───────────────────────────────── - onDevicesList(cb: (devices: ScannedDevice[]) => void): void { this._onDevicesListCb = cb } - onConnectState(cb: (state: string) => void): void { this._onConnectStateCb = cb } - onDeviceConnect(cb: () => void): void { this._onDeviceConnectCb = cb } - onMeasuring(cb: (data: ProgressData) => void): void { this._onProgressCb = cb } - onLocked(cb: (data: LockData) => void): void { this._onLockedCb = cb } - onDisconnected(cb: () => void): void { this._onDisconnectedCb = cb } - onDeviceInfo(cb: (info: DeviceInfo) => void): void { this._onDeviceInfoCb = cb } - - // ─── 扫描与连接 ─────────────────────────────── - startScan(): void { - console.log(TAG, 'startScan()') - plugin.Blue.setDeviceSetting(DEVICE_SETTINGS) - this._subscribeBus() - plugin.Blue.start(DEVICE_SETTINGS.map(s => s.deviceName), false) - } - - private _subscribeBus(): void { - if (this._busSubscribed) { - console.log(TAG, 'bus 已订阅,跳过') - return - } - this._busSubscribed = true - - plugin.bus.subscribe('devicesList', (devList: RawDevice[]) => { - console.log(TAG, 'bus[devicesList],设备数:', devList?.length, devList) - if (!devList?.length) { - this._onDevicesListCb?.([]) - return - } - const devices: ScannedDevice[] = devList.map((item: RawDevice) => ({ - name: item.name || item.deviceName || '未知设备', - raw: item, - model: {}, - })) - this._onDevicesListCb?.(devices) - }) - - plugin.bus.subscribe('devicesModel', (res: any) => { - console.log(TAG, 'bus[devicesModel] 完整数据:', JSON.stringify(res)) - }) - - plugin.bus.subscribe('deviceInfo', (res: DeviceInfo) => { - console.log(TAG, 'bus[deviceInfo]:', res) - this._deviceInfo = res - this._onDeviceInfoCb?.(res) - }) - - plugin.bus.subscribe('deviceConnect', () => { - console.log(TAG, 'bus[deviceConnect]') - this._intentionalConnect = false - this._reconnectCount = 0 - plugin.ScaleAction.startDataProgress(true) - this._activeProtocol = plugin.ScaleAction.getActiveProtocol() - this._activeProtocol.codeUpdateMTU((mtuRes: any) => { - console.log(TAG, 'codeUpdateMTU:', mtuRes) - this._activeProtocol.codeSyncTime((syncRes: any) => { - console.log(TAG, 'codeSyncTime:', syncRes) - this._onDeviceConnectCb?.() - }) - }) - }) - - plugin.bus.subscribe('connectState', (res: string) => { - console.log(TAG, 'bus[connectState]:', res) - this._connectState = res - this._onConnectStateCb?.(res) - if (res === plugin.BLUE_STATE.CONNECTSUCCESS) { - this._startKeepAlive() - return - } - if (res === plugin.BLUE_STATE.CONNECTFAILED) { - this._stopKeepAlive() - if (this._intentionalConnect) { - this._intentionalConnect = false - } else { - this._doReconnect() - } - return - } - // 蓝牙不可用:做完整清理 - if (res === plugin.BLUE_STATE.UNAVAILABLE) { - this._stopKeepAlive() - this._stopReconnectTimer() - if (this._activeProtocol) { - this._activeProtocol = null - this._onDisconnectedCb?.() - } - return - } - // 蓝牙就绪:如有缓存设备且非主动断开,尝试连接 - if (res === plugin.BLUE_STATE.READY) { - if (this._intentionalConnect) { - this._intentionalConnect = false - } else if (this._lastRawDevice && !this._activeProtocol) { - this._stopReconnectTimer() - wx.openBluetoothAdapter({ - success: () => this.connect(this._lastRawDevice!), - }) - } - } - }) - - plugin.bus.subscribe('progressData', (res: ProgressData) => { - this._onProgressCb?.(res) - }) - - plugin.bus.subscribe('lockData', (res: LockData) => { - this._onLockedCb?.(res) - }) - - plugin.bus.subscribe('deviceWillDisconnect', () => { - console.warn(TAG, 'bus[deviceWillDisconnect],intentionalConnect:', this._intentionalConnect) - this._stopKeepAlive() - if (this._activeProtocol) { - this._activeProtocol = null - this._onDisconnectedCb?.() - } - if (!this._intentionalConnect) { - this._doReconnect() - } - }) - } - - connect(rawDevice: RawDevice): void { - console.log(TAG, 'connect():', rawDevice.name) - this._lastRawDevice = rawDevice - this._intentionalConnect = true - this._stopKeepAlive() - this._stopReconnectTimer() - // 先断开旧连接(此时状态仍在),再清空内部状态 - if (this.isConnected) { - plugin.Blue.disconnect() - } - this._activeProtocol = null - this._connectState = '' - this._deviceInfo = null - // 直连缓存设备时不走 startScan,需手动初始化插件设备配置和总线订阅 - plugin.Blue.setDeviceSetting(DEVICE_SETTINGS) - this._subscribeBus() - plugin.Blue.stopBluetoothDevicesDiscovery() - plugin.Blue.clearProtocol() - plugin.Blue.createBLEConnection(rawDevice) - } - - disconnect(): void { - console.log(TAG, 'disconnect()') - this._intentionalConnect = true - this._stopKeepAlive() - plugin.Blue.disconnect() - } - - /** 读缓存设备信息,自动扫描匹配后连接,无缓存时直接返回 */ - autoConnect(): void { - - const cached = wx.getStorageSync('connectDeviceInfo') as RawDevice | null - const targetDeviceId = cached?.deviceId as string | undefined - if (!targetDeviceId) return - - let scanTimer: number | null = null - - wx.openBluetoothAdapter({ - success: () => { - scanTimer = setTimeout(() => { - this.stopScan() - }, 15000) - - this.onDevicesList((devices) => { - const matched = devices.find(d => d.raw.deviceId === targetDeviceId) - if (!matched) return - if (scanTimer !== null) { clearTimeout(scanTimer); scanTimer = null } - this.stopScan() - wx.setStorageSync('connectDeviceInfo', matched.raw) - this.connect(matched.raw) - }) - this.startScan() - }, - fail: () => { - if (scanTimer !== null) { clearTimeout(scanTimer); scanTimer = null } - }, - }) - } - - stopScan(): void { - console.log(TAG, 'stopScan()') - this._stopReconnectTimer() - wx.stopBluetoothDevicesDiscovery() - this._onDevicesListCb = null - } - - // ─── WiFi 配网 ──────────────────────────────── - getWifiList(): Promise { - console.log(TAG, 'getWifiList()') - return new Promise((resolve, reject) => { - if (!this._activeProtocol) { - reject(new Error('设备未连接,无法获取 WiFi 列表')) - return - } - const timer = setTimeout(() => reject(new Error('获取 WiFi 列表超时')), 15000) - setTimeout(() => { - this._activeProtocol.dataFindSurroundDevice((res: any[]) => { - clearTimeout(timer) - console.log(TAG, 'getWifiList 结果:', res) - if (!res?.length) { - this._activeProtocol.dataExitWifiConfig(() => { }) - resolve([]) - return - } - resolve(res.map((item: any) => ({ ssid: item.ssid || item.name || '', signal: item.signal }))) - }) - }, 2000) - }) - } - - configWifi(ssid: string, password: string): Promise { - const version = (this._deviceInfo?.modelNumber ?? '').split('-').length === 5 ? 'domain2' : 'domain1' - const startTs = Date.now() - console.log(TAG, 'configWifi():', ssid, version, 'ts=', startTs, 'activeProtocol=', !!this._activeProtocol) - return new Promise((resolve, reject) => { - if (!this._activeProtocol) { - console.warn(TAG, 'configWifi 早期 reject:_activeProtocol 为空') - reject(new Error('设备未连接,无法配网')) - return - } - let settled = false - const timer = setTimeout(() => { - console.warn(TAG, 'configWifi 超时触发 elapsed=', Date.now() - startTs, 'settled=', settled) - if (settled) return - settled = true - reject(new Error('配网超时,请重试')) - }, 20000) - console.log(TAG, 'configWifi 已注册 20s 定时器 timerId=', timer) - const callback = (res: number, ...extra: any[]) => { - console.log(TAG, 'configWifi 回调 res=', res, 'extra=', extra, 'elapsed=', Date.now() - startTs, 'settled=', settled) - if (settled) return - settled = true - clearTimeout(timer) - res === 23 ? resolve() : reject(new Error(`配网失败,错误码:${res}`)) - } - if (version === 'domain1') { - console.log(TAG, 'domain1 dataConfigNetWork') - this._activeProtocol.dataConfigNetWork({ domain: LEFU_CONFIG.domain1, ssid, password }, callback) - } else { - console.log(TAG, 'domain2 dataConfigUserNetWork') - this._activeProtocol.dataConfigUserNetWork( - { domain: LEFU_CONFIG.domain2, ssid, password, userName: 'apiUser', userPassword: '3acebb95eb49577e9c2a2082589b9bd6' }, - callback, - ) - } - }) - } - - checkWifiConfig(): Promise { - return new Promise((resolve, reject) => { - if (!this._activeProtocol) { reject(new Error('设备未连接')); return } - this._activeProtocol.codeFetchWifiConfig((res: number | undefined | null) => { - resolve(res === 0x01) - }) - }) - } - - // ─── OTA 升级 ───────────────────────────────── - checkAndUpgrade(): Promise { - return new Promise((resolve, reject) => { - if (!this._activeProtocol) { reject(new Error('设备未连接')); return } - if (!this._deviceInfo) { reject(new Error('固件信息未就绪')); return } - - const { firmwareRevision, modelNumber } = this._deviceInfo - const [mcuVer, bleVer, wifiVer, resVer] = (firmwareRevision || '').split('.') - const isServerNewer = (dv: string, sv: string) => { - const dn = parseInt(dv, 10) - const sn = (sv || '').split('.').map(v => parseInt(v, 10)).reduce((a, v) => a * 1000 + v, 0) - return sn > dn - } - - get('weighingScale/v2/firmware/version', { type: modelNumber }, { loading: false }) - .then(res => { - const { mcuVersion, bleVersion, wifiVersion, resVersion } = res.result - const needUpdate = isServerNewer(mcuVer, mcuVersion) || isServerNewer(bleVer, bleVersion) - || isServerNewer(wifiVer, wifiVersion) || isServerNewer(resVer, resVersion) - if (!needUpdate) { resolve(false); return } - this._activeProtocol.codeOtaUpdate((status: number) => { - console.log(`codeOtaUpdatecodeOtaUpdatecodeOtaUpdate ${status}`) - status != 0 ? reject(new Error('请求失败')) : resolve(true) - }) - }) - .catch(reject) - }) - } - - // ─── 私有:保活与重连 ───────────────────────── - private _startKeepAlive(): void { - this._stopKeepAlive() - this._keepAliveTimer = setInterval(() => { this._activeProtocol?.sendKeepAliveCode?.() }, 15000) - } - - private _stopKeepAlive(): void { - if (this._keepAliveTimer !== null) { - clearInterval(this._keepAliveTimer) - this._keepAliveTimer = null - } - } - - private _stopReconnectTimer(): void { - if (this._reconnectTimer !== null) { - clearTimeout(this._reconnectTimer) - this._reconnectTimer = null - } - this._reconnectCount = 0 - } - - private _doReconnect(): void { - if (this._reconnectCount >= this._maxReconnect) { - console.warn(TAG, '重连次数已达上限') - this._onDisconnectedCb?.() - return - } - if (!this._lastRawDevice) { - console.warn(TAG, '无缓存设备,放弃重连') - return - } - this._reconnectCount++ - console.log(TAG, `_doReconnect 第 ${this._reconnectCount} 次`) - this._stopReconnectTimer() - this._reconnectTimer = setTimeout(() => { - plugin.Blue.clearProtocol() - plugin.Blue.createBLEConnection(this._lastRawDevice!) - }, 2000) - } - - // ─── 请求用户(不下发) ───────────────────── - clearDeviceMembers(): Promise { - return new Promise((resolve, reject) => { - if (!this._activeProtocol) { reject(new Error('设备未连接')); return } - const timer = setTimeout(() => reject(new Error('请求超时')), 15000) - this._activeProtocol.codeClearDeviceData('01', (res: number) => { - clearTimeout(timer) - res === 0 ? resolve() : reject(new Error('请求失败')) - }) - }) - } - - // ─── 用户同步 ───────────────────────────────── - syncMembersToDevice(members: DeviceMember[], onProgress?: (current: number, total: number) => void): Promise { - return new Promise((resolve, reject) => { - if (!this._activeProtocol) { reject(new Error('设备未连接')); return } - const mainUser = members.find(m => m.isSelf) - if (!mainUser) { reject(new Error('缺少主用户')); return } - - const clearTimer = setTimeout(() => reject(new Error('请求超时')), 15000) - this._activeProtocol.codeClearDeviceData('01', (clearRes: number) => { - clearTimeout(clearTimer) - if (clearRes != 0) { reject(new Error('请求失败')); return } - - let index = 0 - let syncTimer: number | null = null - const resetTimer = () => { - if (syncTimer !== null) clearTimeout(syncTimer) - syncTimer = setTimeout(() => reject(new Error(`请求超时`)), 15000) - } - const syncNext = () => { - if (index >= members.length) { - if (syncTimer !== null) clearTimeout(syncTimer) - resolve() - return - } - const member = members[index] - onProgress?.(index + 1, members.length) - resetTimer() - this._activeProtocol.dataSyncUserInfo( - { - userID: mainUser.id, userName: member.name, - memberID: member.isSelf ? '' : member.id, - age: member.age, gender: member.gender, height: member.height, - isAthleteMode: 0, deviceHeaderIndex: index, - currentWeight: '', targetWeight: '', idealWeight: '', recentData: [], - }, - (res: number) => { if (res === 0) index++; syncNext() }, - ) - } - syncNext() - }) - }) - } -} - -export const lefuService = new LeFuService() diff --git a/miniprogram/lefu/types.ts b/miniprogram/lefu/types.ts deleted file mode 100644 index 6b32a28..0000000 --- a/miniprogram/lefu/types.ts +++ /dev/null @@ -1,112 +0,0 @@ -/** ppScale-plugin 原始设备对象 */ -export type RawDevice = Record - -/** 乐福 SDK 插件对象(运行时由 requirePlugin 注入) */ -export type LeFuPlugin = Record - - -/** 设备配置(来自 master device.setting 列表) */ -export interface DeviceSetting { - advLength: number - calorieStatus: number - deviceAccuracyType: number - deviceCalcuteType: number - deviceConnectType: number - deviceFuncType: number - deviceName: string - devicePowerType: number - deviceProtocolType: number - deviceType: number - deviceUnitType: string - id: number - macAddressStart: number - sign: string - status: number - uhStatus: number - createBy: null - imgUrl: null - remark: null - updateBy: null -} - -/** 扫描到的设备(展示层使用) */ -export interface ScannedDevice { - /** 设备名称 */ - name: string - /** plugin 返回的原始设备对象,连接时传回给 SDK */ - raw: RawDevice - /** 设备模型(含 deviceConnectType 等信息) */ - model: Record -} - -/** WiFi 项 */ -export interface LeFuWifiItem { - /** WiFi SSID */ - ssid: string - /** 信号强度(部分设备上报) */ - signal?: number -} - -/** 测量进度数据(progressData 事件,测量过程中多次回调) */ -export interface ProgressData { - /** 体重,单位:克 */ - weight: number - /** 单位枚举值 */ - unit: number - /** 是否正在心率测量 */ - isHeartRating: boolean -} - -/** 测量锁定数据(lockData 事件,测量完成时回调一次) */ -export interface LockData { - /** 体重,单位:克 */ - weight: number - /** 阻抗值(用于体成分分析) */ - resistance: number - /** 心率(部分设备上报) */ - heartRate?: number - [key: string]: any -} - -/** 乐福服务配置 */ -export interface LeFuConfig { - key: string - secret: string - /** 老协议域名(无鉴权) */ - domain1: string - /** 新协议域名(有鉴权) */ - domain2: string -} - -/** 设备固件信息(deviceInfo 事件返回) */ -export interface DeviceInfo { - /** 固件版本号,格式:mcu.ble.wifi.res,如 "006.005.004.305" */ - firmwareRevision: string - /** 设备型号,用于请求服务端版本接口 */ - modelNumber: string - [key: string]: any -} - -/** 服务端固件版本(firmware/version 接口 result 字段) */ -export interface FirmwareVersion { - mcuVersion: string - bleVersion: string - wifiVersion: string - resVersion: string -} - -/** 下发给设备的用户信息(lefu 层使用,业务层转换后传入) */ -export interface DeviceMember { - /** 用户唯一 ID */ - id: string - /** 姓名 */ - name: string - /** 性别:1=男,0=女 */ - gender: 0 | 1 - /** 年龄 */ - age: number - /** 身高(cm) */ - height: number - /** 是否本人(主用户) */ - isSelf: boolean -} diff --git a/miniprogram/pages/addMember/addMember.json b/miniprogram/pages/addMember/addMember.json deleted file mode 100644 index 15f1a8f..0000000 --- a/miniprogram/pages/addMember/addMember.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "navigationBarTitleText": "添加用户", - "usingComponents": { - "select-member-drawer": "/components/selectMemberDrawer/selectMemberDrawer", - "user-info-confirm-modal": "/components/userInfoConfirmModal/userInfoConfirmModal" - } -} diff --git a/miniprogram/pages/addMember/addMember.less b/miniprogram/pages/addMember/addMember.less deleted file mode 100644 index 77f6407..0000000 --- a/miniprogram/pages/addMember/addMember.less +++ /dev/null @@ -1,402 +0,0 @@ -.add-member { - width: 100%; - height: 100%; - display: flex; - flex-flow: column; - - /* 顶部浅蓝提示条 */ - .tips { - width: 100%; - height: 56rpx; - display: flex; - margin-bottom: 24rpx; - padding-left: 30rpx; - align-items: center; - box-sizing: border-box; - background-color: rgba(19, 133, 250, 0.2); - - .tips-icon { - color: #1385FA; - width: 28rpx; - height: 28rpx; - font-size: 20rpx; - font-weight: bold; - margin-right: 10rpx; - line-height: 24rpx; - text-align: center; - border-radius: 50%; - box-sizing: border-box; - border: 1rpx solid #1385FA; - } - - .tips-text { - color: #1385FA; - height: 24rpx; - font-size: 24rpx; - line-height: 24rpx; - } - } - - .form { - width: 100%; - flex: 1; - overflow: hidden; - - .scrollViewContent { - width: 100%; - padding: 0 30rpx; - box-sizing: border-box; - - /* 顶部 tabs - 圆角卡片样式 */ - .tabsDom { - width: 100%; - box-sizing: border-box; - padding: 30rpx 30rpx 40rpx; - border-radius: 16rpx 16rpx 0 0; - background-color: #ffffff; - - .tabs { - width: 100%; - height: 80rpx; - padding: 4rpx; - display: flex; - flex-wrap: nowrap; - box-sizing: border-box; - border-radius: 16rpx; - background-color: #F3F5F8; - - .tab-item { - color: #808080; - flex: 1; - height: 72rpx; - font-size: 32rpx; - text-align: center; - line-height: 72rpx; - box-sizing: border-box; - border-radius: 16rpx; - - &.active { - color: #1385FA; - font-weight: bold; - background-color: #FFFFFF; - } - } - } - } - - .selectWorkNo { - width: 100%; - margin-bottom: 30rpx; - box-sizing: border-box; - padding: 0 30rpx 40rpx; - border-radius: 0 0 16rpx 16rpx; - background-color: #ffffff; - - .selectWorkNoBtn { - color: #FFFFFF; - width: 580rpx; - height: 88rpx; - font-size: 32rpx; - font-weight: bold; - line-height: 88rpx; - border-radius: 44rpx; - text-align: center; - margin: 0 auto; - background-color: #1385FA; - } - } - - .workNoDesc { - color: #B6B6B6; - width: 100%; - height: 24rpx; - font-size: 24rpx; - text-align: center; - line-height: 24rpx; - } - - .workNoNull { - color: #F24439; - margin: 56rpx auto 0; - width: 640rpx; - height: 88rpx; - font-size: 28rpx; - line-height: 88rpx; - border-radius: 16rpx; - text-align: center; - background: rgba(242,68,57,0.12); - } - - .workNoInfo { - width: 100%; - padding-bottom: 10rpx; - - .workNoInfo_ { - width: 100%; - margin-bottom: 20rpx; - - .workNoInfoTitle { - color: #808080; - width: 100%; - height: 64rpx; - font-size: 24rpx; - line-height: 64rpx; - } - - .workNoInfoContent { - width: 100%; - border-radius: 16rpx; - box-sizing: border-box; - padding: 40rpx 30rpx 30rpx; - background-color: #FFFFFF; - - > view { - width: 100%; - display: flex; - flex-wrap: nowrap; - justify-content: space-between; - - .form-row { - width: 300rpx; - } - } - } - } - } - - .familyInfo { - width: 100%; - padding-bottom: 10rpx; - - .familyInfo_ { - width: 100%; - border-radius: 16rpx; - box-sizing: border-box; - padding: 0 30rpx 10rpx; - margin-bottom: 40rpx; - background-color: #FFFFFF; - } - - .form-row { - width: 100%; - margin-bottom: 40rpx; - } - - /* 身高体重同行 */ - .form-row-inline { - width: 100%; - display: flex; - gap: 24rpx; - margin-bottom: 40rpx; - - .form-row-half { - flex: 1; - } - } - } - - .form-row { - width: 100%; - margin-bottom: 40rpx; - - &:last-child { - margin-bottom: 0; - } - } - - .form-label { - width: 100%; - height: 40rpx; - display: flex; - flex-wrap: nowrap; - margin-bottom: 18rpx; - - .label-text { - color: #252535; - height: 40rpx; - font-size: 28rpx; - line-height: 40rpx; - font-weight: bold; - margin-right: 6rpx; - } - - .label-star { - color: #FF4D4F; - height: 40rpx; - font-weight: 500; - font-size: 28rpx; - line-height: 40rpx; - } - } - - .form-control { - width: 100%; - } - - /* 可输入区域 */ - .form-input { - width: 100%; - height: 88rpx; - padding: 0 30rpx; - color: #252535; - font-size: 26rpx; - border-radius: 16rpx; - box-sizing: border-box; - background-color: #FFFFFF; - border: 2rpx solid #E6E6EA; - } - - .form-placeholder { - color: #B6B6B6; - font-size: 26rpx; - } - - /* 日期选择器 */ - .form-picker { - width: 100%; - height: 88rpx; - padding: 0 30rpx; - color: #252535; - font-size: 26rpx; - line-height: 88rpx; - border-radius: 16rpx; - box-sizing: border-box; - background-color: #FFFFFF; - border: 2rpx solid #E6E6EA; - } - - .form-picker-placeholder { - color: #B6B6B6; - } - - /* 输入框 + 后缀容器(身高 cm / 体重 kg) */ - .form-input-wrap { - position: relative; - width: 100%; - - .form-input-with-suffix { - padding-right: 80rpx; - } - - .form-suffix-inside { - position: absolute; - right: 30rpx; - top: 0; - height: 88rpx; - color: #77849E; - font-size: 26rpx; - line-height: 88rpx; - } - } - - /* 只读区域 */ - .form-readonly { - width: 100%; - height: 88rpx; - box-sizing: border-box; - padding: 0 24rpx; - background: #F6F6F6; - border-radius: 16rpx; - display: flex; - align-items: center; - border: 2rpx solid #E6E6EA; - - .readonly-text { - color: #252535; - font-size: 26rpx; - } - } - - .form-readonly-with-suffix { - justify-content: space-between; - - .form-suffix { - color: #77849E; - height: 88rpx; - font-size: 26rpx; - line-height: 88rpx; - } - } - - /* 性别切换按钮(家庭用户) */ - .gender-toggle { - display: flex; - gap: 24rpx; - - .gender-option { - width: 300rpx; - height: 88rpx; - font-size: 28rpx; - text-align: center; - line-height: 88rpx; - border-radius: 16rpx; - box-sizing: border-box; - color: #77849E; - background-color: #FFFFFF; - border: 2rpx solid #E6E6EA; - } - - .gender-option-active { - color: #FFFFFF; - font-weight: bold; - background-color: #1385FA; - border-color: #1385FA; - } - } - } - - /* 从已有用户中选择(蓝色描边幽灵按钮) */ - .ghost-btn { - color: #1385FA; - width: 580rpx; - height: 88rpx; - font-size: 32rpx; - font-weight: bold; - text-align: center; - line-height: 84rpx; - border-radius: 44rpx; - box-sizing: border-box; - border: 2rpx solid #1385FA; - margin: 0 auto; - } - } - - /* 底部双按钮 */ - .actions { - width: 100%; - height: 120rpx; - padding: 0 55rpx; - display: flex; - flex-wrap: nowrap; - align-items: center; - justify-content: space-between; - box-sizing: border-box; - background-color: #FFFFFF; - box-shadow: 0 2rpx 8rpx 0 rgba(19, 133, 250, 0.06); - - .btn-cancel { - color: #1385FA; - width: 300rpx; - height: 88rpx; - font-size: 32rpx; - text-align: center; - line-height: 84rpx; - border-radius: 44rpx; - box-sizing: border-box; - border: 2rpx solid #1385FA; - background-color: #FFFFFF; - } - - .btn-primary { - color: #FFFFFF; - width: 300rpx; - height: 88rpx; - font-size: 32rpx; - font-weight: bold; - text-align: center; - line-height: 88rpx; - border-radius: 44rpx; - background-color: #1385FA; - } - } -} diff --git a/miniprogram/pages/addMember/addMember.ts b/miniprogram/pages/addMember/addMember.ts deleted file mode 100644 index 8e314c1..0000000 --- a/miniprogram/pages/addMember/addMember.ts +++ /dev/null @@ -1,518 +0,0 @@ -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 | 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('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('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 = { - 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) { - 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 = { - ...(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' }) - }) - } -}) diff --git a/miniprogram/pages/addMember/addMember.wxml b/miniprogram/pages/addMember/addMember.wxml deleted file mode 100644 index c8fa57e..0000000 --- a/miniprogram/pages/addMember/addMember.wxml +++ /dev/null @@ -1,207 +0,0 @@ - - - - - ! - 最多支持 10 位用户 - - - - - - - - - 员工 - 家庭用户 - - - - - - - - - - 员工编号 - * - - - - - - 确认查询 - - - - 输入员工编号后自动查询员工信息 - - - 未查询到员工信息,请确认员工编号是否正确 - - - - - 用户信息 - - - - 姓名 - - {{ form.name }} - - - - 单位 - - {{ unitDisplay }} - - - - - - 性别 - - {{ form.gender }} - - - - 年龄 - - {{ form.age }} - - - - - - - 补充信息 - - - - - 身高 - * - - - - - cm - - - - - 体重 - - - - kg - - - - - - - - - - - - - - - - - 昵称 - * - - - - - - - - - 身份证号 - - - - - - - - - 性别 - * - - - - - - - - - - - - - 出生年月 - * - - - - {{ birthday || '请选择出生年月' }} - - - - - - - - - 身高 - * - - - - - cm - - - - - 体重 - - - - kg - - - - - - - - 从已有用户中选择 - - - - - - - - - - 取消 - 保存用户信息 - - - - - - - - - - - diff --git a/miniprogram/pages/connectedDevice/connectedDevice.json b/miniprogram/pages/connectedDevice/connectedDevice.json deleted file mode 100644 index 002cd9b..0000000 --- a/miniprogram/pages/connectedDevice/connectedDevice.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "navigationBarTitleText": "搜索设备", - "usingComponents": {} -} diff --git a/miniprogram/pages/connectedDevice/connectedDevice.less b/miniprogram/pages/connectedDevice/connectedDevice.less deleted file mode 100644 index 682e481..0000000 --- a/miniprogram/pages/connectedDevice/connectedDevice.less +++ /dev/null @@ -1,285 +0,0 @@ -/* 连接设备页 —— 三态(idle / searching / found / empty)共用 */ -.connected-device { - width: 100%; - height: 100%; - padding: 48rpx 30rpx 0; - box-sizing: border-box; - background-color: #ffffff; - - /* 三层同心圆波纹动画 */ - .ripple { - position: relative; - width: 300rpx; - height: 300rpx; - margin: 0 auto 48rpx; - display: flex; - align-items: center; - justify-content: center; - - /* 波纹圆环:默认隐藏,仅在 .ripple--active 时播放动画 */ - .ripple-ring { - position: absolute; - width: 300rpx; - height: 300rpx; - border-radius: 50%; - background-color: #1385FA; - opacity: 0; - } - - &.ripple--active { - .ripple-ring { - animation: ripple-expand 2.4s ease-out infinite; - } - - .ripple-ring--delay1 { - animation-delay: 0.8s; - } - - .ripple-ring--delay2 { - animation-delay: 1.6s; - } - } - - /* 中心实心圆 + 图标 */ - .ripple-core { - position: relative; - z-index: 1; - width: 140rpx; - height: 140rpx; - padding: 30rpx; - box-sizing: border-box; - border-radius: 50%; - background-color: #1385FA; - display: flex; - align-items: center; - justify-content: center; - - image { - width: 80rpx; - height: 80rpx; - } - } - } - - @keyframes ripple-expand { - 0% { - transform: scale(0.46); - opacity: 1; - } - 100% { - transform: scale(1); - opacity: 0; - } - } - - .title { - color: #252535; - width: 100%; - height: 36rpx; - font-size: 36rpx; - font-weight: bold; - line-height: 36rpx; - text-align: center; - margin-bottom: 22rpx; - } - - .sub-title { - color: #808080; - width: 100%; - height: 30rpx; - font-size: 30rpx; - text-align: center; - line-height: 30rpx; - margin-bottom: 40rpx; - } - - .search-btn { - color: #FFFFFF; - width: 580rpx; - height: 88rpx; - font-size: 32rpx; - font-weight: bold; - text-align: center; - line-height: 88rpx; - border-radius: 44rpx; - margin: 0 auto 32rpx; - background-color: #1385FA; - } - - // .help { - // width: 100%; - // height: 30rpx; - // text-align: center; - // line-height: 30rpx; - // margin-bottom: 60rpx; - - // .help-text { - // color: #808080; - // font-size: 30rpx; - // line-height: 30rpx; - // margin-right: 8rpx; - // } - - // .help-link { - // color: #1385FA; - // font-size: 30rpx; - // line-height: 30rpx; - // } - // } - - .search-device { - width: 100%; - - .search-device-header { - width: 100%; - height: 32rpx; - display: flex; - flex-wrap: nowrap; - justify-content: space-between; - margin-bottom: 38rpx; - - .search-device-title { - color: #252535; - height: 32rpx; - font-size: 32rpx; - font-weight: bold; - line-height: 32rpx; - } - - .search-device-count { - height: 32rpx; - display: flex; - flex-wrap: nowrap; - - .count-text { - color: #808080; - height: 32rpx; - font-size: 28rpx; - line-height: 32rpx; - } - - .count-num { - color: #1385FA; - height: 32rpx; - font-size: 28rpx; - font-weight: bold; - line-height: 32rpx; - padding: 0 6rpx; - } - } - } - - .empty { - width: 100%; - display: flex; - flex-direction: column; - align-items: center; - margin-top: 160rpx; - - .empty-image { - width: 193rpx; - height: 138rpx; - margin-bottom: 40rpx; - background-color: #F5F5F5; - } - - .empty-text { - color: #808080; - width: 100%; - height: 30rpx; - font-size: 30rpx; - line-height: 30rpx; - text-align: center; - } - } - - .device-list { - width: 100%; - display: flex; - flex-direction: column; - - .device-card { - width: 100%; - height: 150rpx; - display: flex; - align-items: center; - padding: 0 30rpx; - background-color: #FFFFFF; - border: 2rpx solid #E6E6EA; - border-radius: 16rpx; - box-sizing: border-box; - margin-bottom: 24rpx; - - .card-icon { - width: 90rpx; - height: 90rpx; - overflow: hidden; - border-radius: 24rpx; - margin-right: 24rpx; - } - - .card-info { - flex: 1; - display: flex; - flex-direction: column; - justify-content: center; - - .card-name { - color: #252535; - font-size: 32rpx; - font-weight: bold; - height: 32rpx; - line-height: 32rpx; - } - - .card-status { - margin-top: 18rpx; - display: flex; - align-items: center; - - .card-dot { - width: 12rpx; - height: 12rpx; - border-radius: 50%; - background-color: #2AC79F; - margin-right: 15rpx; - } - - .card-status-text { - color: #2AC79F; - height: 28rpx; - font-size: 28rpx; - font-weight: 500; - line-height: 28rpx; - } - } - } - - .card-btn-primary { - color: #FFFFFF; - font-size: 24rpx; - font-weight: 500; - width: 88rpx; - height: 48rpx; - line-height: 48rpx; - text-align: center; - border-radius: 8rpx; - background-color: #2AC79F; - } - - .card-btn-danger { - color: #F24439; - font-size: 24rpx; - font-weight: 500; - width: 120rpx; - height: 48rpx; - line-height: 44rpx; - text-align: center; - border: 2rpx solid #F24439; - border-radius: 8rpx; - box-sizing: border-box; - } - } - } - } -} diff --git a/miniprogram/pages/connectedDevice/connectedDevice.ts b/miniprogram/pages/connectedDevice/connectedDevice.ts deleted file mode 100644 index 9cad0ff..0000000 --- a/miniprogram/pages/connectedDevice/connectedDevice.ts +++ /dev/null @@ -1,264 +0,0 @@ -import { lefuService } from '../../lefu/index' -import type { ScannedDevice } from '../../lefu/index' -import type { RawDevice } from '../../lefu/types' - -/** 展示用设备项(仅存入 setData,不含复杂对象) */ -interface DeviceItem { - name: string - connected: boolean -} - -/** 页面状态 */ -type DeviceStatus = 'idle' | 'searching' | 'found' | 'empty' - -/** 页面级原始设备缓存,不进入 setData 避免序列化异常 */ -let _rawDevices: ScannedDevice[] = [] - -const TAG = '[connectedDevice]' - -Page({ - data: { - status: 'idle' as DeviceStatus, - deviceList: [] as DeviceItem[], - /** 防止权限弹框重入 */ - _modalLock: false, - /** 自动连接缓存设备的一次性标记,防止扫描回调反复触发 */ - _autoConnectTried: false, - }, - - onShow() { - console.log(TAG, 'onShow') - // 回到页面时同步实际连接状态,避免回调被其他页面覆盖导致状态不同步 - if (!lefuService.isConnected) { - const list = this.data.deviceList.map(item => ({ ...item, connected: false })) - this.setData({ deviceList: list }) - } - }, - - onHide() { - console.log(TAG, 'onHide, 停止扫描') - // _rawDevices = [] - // this.setData({ status: 'idle', deviceList: [] }) - lefuService.stopScan() - }, - - onUnload() { - console.log(TAG, 'onUnload, 停止扫描') - lefuService.stopScan() - }, - - /** 点击「开始搜索 / 重新搜索」按钮:先验权限,再扫描 */ - async onStartSearch() { - console.log(TAG, 'onStartSearch, _modalLock:', this.data._modalLock) - if (this.data._modalLock) return - _rawDevices = [] - this.setData({ status: 'searching', deviceList: [], _autoConnectTried: false }) - await this._checkPermissionAndScan() - }, - - /** 检查蓝牙 + 位置权限,通过后开始扫描 */ - async _checkPermissionAndScan() { - console.log(TAG, '_checkPermissionAndScan 开始') - try { - const { authSetting } = await wx.getSetting() - const setting = authSetting as Record - const hasBle = setting['scope.bluetooth'] - const hasLoc = setting['scope.userLocation'] - console.log(TAG, '权限状态 → 蓝牙:', hasBle, '位置:', hasLoc) - - if (hasBle && hasLoc) { - await this._openAdapterAndScan() - return - } - - // 逐个申请缺失权限 - const missing: Array<{ scope: string; label: string }> = [] - if (!hasBle) missing.push({ scope: 'scope.bluetooth', label: '蓝牙' }) - if (!hasLoc) missing.push({ scope: 'scope.userLocation', label: '位置' }) - console.log(TAG, '缺失权限:', missing.map(m => m.label)) - - try { - for (const item of missing) { - console.log(TAG, '申请权限:', item.scope) - await wx.authorize({ scope: item.scope }) - console.log(TAG, '权限申请成功:', item.scope) - } - await this._openAdapterAndScan() - } catch (err) { - console.warn(TAG, '权限申请被拒绝:', err) - this._showPermissionModal(missing.map(m => m.label).join('和')) - } - } catch (err) { - console.error(TAG, 'getSetting 失败:', err) - wx.showToast({ title: '获取权限设置失败', icon: 'none' }) - this.setData({ status: 'idle' }) - } - }, - - /** 引导用户去设置页开启权限 */ - _showPermissionModal(permissionNames: string) { - console.log(TAG, '弹出权限引导框, 缺失:', permissionNames) - this.setData({ _modalLock: true }) - - wx.showModal({ - title: '需要授权', - content: `小程序需要您的${permissionNames}权限才能搜索蓝牙设备,请前往手机设置开启。`, - confirmText: '去开启', - cancelText: '取消', - success: (res) => { - console.log(TAG, '权限弹框结果:', res.confirm ? '去开启' : '取消') - this.setData({ _modalLock: false, status: 'idle' }) - if (res.confirm) { - wx.openSetting({ - success: () => this.onStartSearch(), - }) - } - }, - fail: () => { - this.setData({ _modalLock: false, status: 'idle' }) - }, - }) - }, - - /** 打开蓝牙适配器,成功后启动扫描 */ - async _openAdapterAndScan() { - console.log(TAG, 'wx.openBluetoothAdapter() 开始') - try { - await wx.openBluetoothAdapter() - console.log(TAG, 'wx.openBluetoothAdapter() 成功') - this._startScan() - } catch (err: any) { - console.error(TAG, 'wx.openBluetoothAdapter() 失败, errCode:', err?.errCode, err?.errMsg) - if (err?.errCode === 10001) { - wx.showModal({ - title: '蓝牙未开启', - content: '请先开启手机蓝牙后重试', - showCancel: false, - confirmText: '知道了', - success: () => this.setData({ status: 'idle' }), - }) - } else { - wx.showToast({ title: '蓝牙初始化失败', icon: 'none' }) - this.setData({ status: 'idle' }) - } - } - }, - - /** 权限已就绪,注册设备列表回调并启动扫描 */ - _startScan() { - console.log(TAG, '_startScan 启动') - lefuService.onDevicesList((devices) => { - console.log(TAG, 'onDevicesList 回调, 设备数:', devices.length, devices.map(d => d.name)) - if (!devices.length) { - _rawDevices = [] - this.setData({ status: 'empty', deviceList: [] }) - return - } - _rawDevices = devices - // 保留已连接设备的状态,避免扫描刷新时把 connected 重置为 false - const connectedName = this.data.deviceList.find(d => d.connected)?.name - const list: DeviceItem[] = devices.map(d => ({ - name: d.name, - connected: d.name === connectedName, - })) - this.setData({ status: 'found', deviceList: list }) - - // 缓存设备命中:自动连接一次 - if (this.data._autoConnectTried) return - const cached = wx.getStorageSync('connectDeviceInfo') as RawDevice | null - const cachedDeviceId = cached?.deviceId - if (!cachedDeviceId) return - const matchedIndex = devices.findIndex(d => d.raw.deviceId === cachedDeviceId) - if (matchedIndex < 0) return - console.log(TAG, '命中缓存设备, deviceId:', cachedDeviceId, 'index:', matchedIndex) - this.setData({ _autoConnectTried: true }) - this._autoConnectByIndex(matchedIndex) - }) - lefuService.onDisconnected(() => { - const list = this.data.deviceList.map(item => ({ ...item, connected: false })) - this.setData({ deviceList: list }) - }) - lefuService.startScan() - console.log(TAG, 'lefuService.startScan() 已调用') - }, - - /** 缓存设备自动连接(与 onConnect 共享连接流程) */ - _autoConnectByIndex(index: number) { - const raw = _rawDevices[index] - if (!raw) return - wx.showLoading({ title: '连接中...', mask: true }) - lefuService.stopScan() - lefuService.connect(raw.raw) - lefuService.onConnectState((state) => { - if (state === lefuService.BLUE_STATE.CONNECTFAILED) { - wx.hideLoading() - wx.showToast({ title: '连接失败,请重试', icon: 'none' }) - } - }) - lefuService.onDeviceConnect(() => { - wx.setStorageSync('connectDeviceInfo', { ...raw.raw, scaleDeviceName: raw.raw.name }) - const list = this.data.deviceList.map((item, i) => ({ - ...item, - connected: i === index, - })) - this.setData({ deviceList: list }) - wx.hideLoading() - console.log(TAG, '缓存设备已自动连接(不跳转)') - }) - }, - - /** 点击整张卡片:已连接 → 跳转配网页;未连接 → 走手动连接流程 */ - onTapCard(e: WechatMiniprogram.TouchEvent) { - const connected = e.currentTarget.dataset.connected as boolean - console.log(TAG, 'onTapCard, connected:', connected) - if (connected) { - wx.navigateTo({ url: '/pages/connectedWifi/connectedWifi' }) - return - } - this.onConnect(e) - }, - - /** 连接指定设备 */ - onConnect(e: WechatMiniprogram.TouchEvent) { - const index = e.currentTarget.dataset.index as number - const raw = _rawDevices[index] - console.log(TAG, 'onConnect, index:', index, 'device:', raw) - if (!raw) { - console.warn(TAG, 'onConnect: 找不到对应 raw device, index:', index) - return - } - - wx.showLoading({ title: '连接中...', mask: true }) - lefuService.connect(raw.raw) - lefuService.onConnectState((state) => { - if (state === lefuService.BLUE_STATE.CONNECTFAILED) { - wx.hideLoading() - wx.showToast({ title: '连接失败,请重试', icon: 'none' }) - } - }) - lefuService.onDeviceConnect(() => { - wx.setStorageSync('connectDeviceInfo', { ...raw.raw, scaleDeviceName: raw.raw.name }) - const list = this.data.deviceList.map((item, i) => ({ - ...item, - connected: i === index, - })) - this.setData({ deviceList: list }) - wx.hideLoading() - console.log(TAG, '设备连接就绪,跳转配网页') - wx.navigateTo({ url: '/pages/connectedWifi/connectedWifi' }) - }) - }, - - /** 断开当前连接 */ - onDisconnect() { - console.log(TAG, 'onDisconnect') - const list = this.data.deviceList.map(item => ({ ...item, connected: false })) - this.setData({ deviceList: list }) - lefuService.disconnect() - }, - - /** 查看帮助 */ - onOpenHelp() { - wx.showToast({ title: '请确保设备已开机且在附近', icon: 'none' }) - }, -}) diff --git a/miniprogram/pages/connectedDevice/connectedDevice.wxml b/miniprogram/pages/connectedDevice/connectedDevice.wxml deleted file mode 100644 index c8be7f0..0000000 --- a/miniprogram/pages/connectedDevice/connectedDevice.wxml +++ /dev/null @@ -1,83 +0,0 @@ - - - - - - - - - - - - - - - {{ status === 'idle' ? '搜索附近设备' : '搜索附近设备中...' }} - - - - 请确保蓝牙已开启,设备处于待机状态 - - - - {{ status === 'idle' ? '开始搜索' : '重新搜索' }} - - - - - - - - - - - - 搜索结果 - - 发现 - {{ deviceList.length }} - 台设备 - - - - - - - - 暂未搜索到可用设备,请重新搜索 - - - - - - - - - - - - - {{ item.name }} - - - - 已连接 - - - - - 断开连接 - - - 连接 - - - - - - - - diff --git a/miniprogram/pages/connectedWifi/connectedWifi.json b/miniprogram/pages/connectedWifi/connectedWifi.json deleted file mode 100644 index e98f212..0000000 --- a/miniprogram/pages/connectedWifi/connectedWifi.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "navigationBarTitleText": "WiFi连接", - "usingComponents": {} -} diff --git a/miniprogram/pages/connectedWifi/connectedWifi.less b/miniprogram/pages/connectedWifi/connectedWifi.less deleted file mode 100644 index 8b4adfa..0000000 --- a/miniprogram/pages/connectedWifi/connectedWifi.less +++ /dev/null @@ -1,389 +0,0 @@ -.connectedWifi { - width: 100%; - height: 100%; - display: flex; - flex-flow: column; - padding: 30rpx 30rpx 0; - box-sizing: border-box; - background-color: #FFFFFF; -} - -.setWifi { - width: 100%; - height: 100%; - padding: 30rpx 10rpx 0; - box-sizing: border-box; -} - -.setWifi-title { - color: #252535; - width: 100%; - height: 48rpx; - font-size: 48rpx; - font-weight: bold; - line-height: 48rpx; - margin-bottom: 48rpx; -} - -.setWifi-form { - width: 100%; - margin-bottom: 64rpx; -} - -.setWifi-form>view { - width: 100%; - height: 96rpx; - display: flex; - flex-wrap: nowrap; - align-items: center; - padding: 0 24rpx; - box-sizing: border-box; - border-radius: 16rpx; - background-color: #F6F6F6; -} - -.setWifi-form>view:first-of-type { - margin-bottom: 32rpx; -} - -.setWifi-form>view>view:nth-of-type(1) { - color: #252535; - width: 100rpx; - height: 96rpx; - line-height: 96rpx; - font-size: 32rpx; - font-weight: bold; -} - -.setWifi-form>view:first-of-type>view:nth-of-type(1) { - padding: 24rpx 45rpx 24rpx 7rpx; - box-sizing: border-box; - display: flex; - align-items: center; -} - -.setWifi-form>view>view:nth-of-type(2) { - color: #252535; - flex: 1; - width: 0; - height: 96rpx; - font-size: 32rpx; - line-height: 96rpx; -} - -.setWifi-form>view>view:nth-of-type(2) input { - color: #252535; - width: 100%; - height: 96rpx; - font-size: 32rpx; - line-height: 96rpx; -} - -.placeholderClass { - color: #B6B6B6; - height: 96rpx; - font-size: 32rpx; - line-height: 96rpx; -} - -.setWifi-form>view>view:nth-of-type(3) { - width: 56rpx; - height: 56rpx; -} - -.setWifi-btn { - width: 100%; - height: 88rpx; - padding: 0 15rpx; - display: flex; - flex-wrap: nowrap; - justify-content: space-between; - box-sizing: border-box; -} - -.setWifi-btn>view { - color: #FFFFFF; - width: 300rpx; - height: 88rpx; - font-size: 32rpx; - font-weight: bold; - text-align: center; - border-radius: 44rpx; - line-height: 84rpx; - box-sizing: border-box; - background-color: #1385FA; - border: 2rpx solid #1385FA; - - &:first-of-type { - color: #1385FA; - background-color: #FFFFFF; - } -} - -/* 顶部已连接设备卡 */ -.deviceCard { - width: 100%; - height: 150rpx; - display: flex; - flex-wrap: nowrap; - align-items: center; - padding: 30rpx; - box-sizing: border-box; - border: 2rpx solid #E6E6EA; - border-radius: 16rpx; - background-color: #FFFFFF; - margin-bottom: 46rpx; - - /* 设备图标占位 */ - >view:nth-of-type(1) { - width: 90rpx; - height: 90rpx; - overflow: hidden; - margin-right: 24rpx; - border-radius: 24rpx; - flex-shrink: 0; - } - - /* 右侧信息 */ - >view:nth-of-type(2) { - flex: 1; - display: flex; - flex-direction: column; - justify-content: center; - - /* 设备名 */ - >view:nth-of-type(1) { - color: #252535; - height: 32rpx; - font-size: 32rpx; - font-weight: bold; - line-height: 32rpx; - margin-bottom: 16rpx; - } - - /* 状态行 */ - >view:nth-of-type(2) { - display: flex; - flex-wrap: nowrap; - align-items: center; - } - } -} - -.status-dot { - width: 12rpx; - height: 12rpx; - border-radius: 50%; - margin-right: 14rpx; - flex-shrink: 0; - - &--green { background-color: #2AC79F; } - &--red { background-color: #F24439; } - &--gray { background-color: #999999; } -} - -.status-text { - height: 28rpx; - font-size: 28rpx; - font-weight: 500; - line-height: 28rpx; - - &--green { color: #2AC79F; } - &--red { color: #F24439; } - &--gray { color: #999999; } -} - -/* idle / searching 两态:居中文案 */ -.wifiIdle, -.wifiSearching { - flex: 1; - display: flex; - justify-content: center; - align-items: center; - color: #B6B6B6; - font-size: 32rpx; - line-height: 32rpx; -} - -/* 附近WiFi 标题区:title 行 + 副标题 */ -.wifiHeader { - width: 100%; - padding: 0 12rpx; - box-sizing: border-box; - - /* 标题行:附近WiFi + 可选「刷新」 */ - >view:nth-of-type(1) { - width: 100%; - display: flex; - flex-wrap: nowrap; - justify-content: space-between; - align-items: center; - margin-bottom: 24rpx; - - >view:nth-of-type(1) { - color: #252535; - height: 36rpx; - font-size: 36rpx; - font-weight: bold; - line-height: 36rpx; - } - - >view:nth-of-type(2) { - color: #1385FA; - height: 36rpx; - font-size: 28rpx; - font-weight: 500; - line-height: 36rpx; - } - } - - /* 副标题 */ - >view:nth-of-type(2) { - color: #B6B6B6; - height: 30rpx; - font-size: 28rpx; - line-height: 30rpx; - margin-bottom: 32rpx; - } -} - -/* WiFi 列表:scroll-view Y 轴滚动,撑满剩余高度 */ -.wifiList { - width: 100%; - flex: 1; - min-height: 0; - padding: 0 12rpx; - box-sizing: border-box; -} - -.arrow { - &::after { - right: 4rpx; - } -} - -/* 列表项:普通态 */ -.wifiItem { - width: 100%; - height: 80rpx; - display: flex; - flex-wrap: nowrap; - align-items: center; - margin-bottom: 32rpx; - - /* 左侧 wifi 图标容器 */ - >view:nth-of-type(1) { - width: 80rpx; - height: 80rpx; - margin-right: 24rpx; - flex-shrink: 0; - display: flex; - align-items: center; - justify-content: center; - - image { - width: 100%; - height: 100%; - } - } - - /* WiFi 名称 */ - >text { - flex: 1; - color: #252535; - height: 32rpx; - font-size: 32rpx; - font-weight: 500; - line-height: 32rpx; - } - - /* 右箭头 */ - >view:nth-of-type(2) { - width: 20rpx; - height: 18rpx; - } -} - -/* 列表项:已连接态 */ -.wifiItem-connected { - - >text { - color: #1385FA; - } - - /* 右侧「已连接」+ 勾 */ - >view:nth-of-type(2) { - width: auto; - height: auto; - display: flex; - flex-wrap: nowrap; - align-items: center; - background-color: transparent; - - >text { - color: #2AC79F; - font-size: 24rpx; - font-weight: 500; - line-height: 24rpx; - margin-right: 5rpx; - } - - /* 右箭头 */ - >view { - width: 20rpx; - height: 18rpx; - } - } -} - -/* 列表态底部「确认」按钮:贴底 */ -.wifiBtn { - width: 100%; - height: 120rpx; - display: flex; - align-items: center; - justify-content: center; - - >view { - color: #FFFFFF; - width: 580rpx; - height: 88rpx; - font-size: 32rpx; - font-weight: bold; - text-align: center; - line-height: 88rpx; - border-radius: 44rpx; - background-color: #1385FA; - } - - .wifiBtn-disabled { - background-color: #A8C9F0; - } -} - -/* 空态 */ -.wifiEmpty { - flex: 1; - min-height: 0; - display: flex; - flex-direction: column; - align-items: center; - padding: 39rpx 42rpx 0; - box-sizing: border-box; - - >view { - width: 566rpx; - height: 499rpx; - margin-bottom: 40rpx; - border-radius: 16rpx; - background-color: #F5F8FF; - } - - >text { - color: #1385FA; - font-size: 36rpx; - font-weight: bold; - line-height: 36rpx; - margin-top: 8rpx; - } -} diff --git a/miniprogram/pages/connectedWifi/connectedWifi.ts b/miniprogram/pages/connectedWifi/connectedWifi.ts deleted file mode 100644 index 5026355..0000000 --- a/miniprogram/pages/connectedWifi/connectedWifi.ts +++ /dev/null @@ -1,134 +0,0 @@ -import { lefuService } from '../../lefu/index' -import type { LeFuWifiItem } from '../../lefu/index' - -interface WifiItem { - ssid: string - connected: boolean -} - -type WifiStatus = 'idle' | 'searching' | 'list' | 'empty' | 'password' | 'configuring' -type BleStatus = 'connected' | 'failed' | 'connecting' - -Page({ - - data: { - status: 'idle' as WifiStatus, - deviceName: '智能体重秤', - bleStatus: 'connecting' as BleStatus, - wifiList: [] as WifiItem[], - hasConnected: false, - selectedSsid: '', - selectWifiPWD: '', - type: true, - }, - - _toBleStatus(state: string): BleStatus { - const bs = lefuService.BLUE_STATE - if (state === bs.CONNECTSUCCESS || state === bs.WIFISUCCESS) return 'connected' - if (state === bs.CONNECTFAILED || state === bs.UNAVAILABLE) return 'failed' - return 'connecting' - }, - - onLoad() { - // 尽早注册 deviceInfo 回调,防止 onShow 前已触发而错过 - lefuService.onDeviceInfo((info) => { - this.setData({ deviceName: info.modelNumber }) - }) - // 设备断开即退出配网页 - lefuService.onDisconnected(() => { - wx.showToast({ title: '设备已断开连接', icon: 'none', duration: 1500 }) - setTimeout(() => wx.navigateBack(), 1500) - }) - this.setData({ - bleStatus: this._toBleStatus(lefuService.connectState), - deviceName: lefuService.deviceInfo?.modelNumber ?? '智能体重秤', - }) - this.onStartSearch() - }, - - onShow() { - lefuService.onConnectState((state) => { - this.setData({ bleStatus: this._toBleStatus(state) }) - }) - }, - - onUnload() { - wx.hideLoading() - lefuService.onDisconnected(() => {}) - if (this.data.status === 'configuring') { - lefuService.disconnect() - } - }, - - onStartSearch() { - this.setData({ status: 'searching', wifiList: [] }) - wx.showLoading({ title: '正在获取 WiFi 列表...', mask: true }) - lefuService.getWifiList() - .then((items: LeFuWifiItem[]) => { - wx.hideLoading() - if (!items.length) { - this.setData({ status: 'empty' }) - return - } - this.setData({ - status: 'list', - wifiList: items.map(item => ({ ssid: item.ssid, connected: false })), - }) - }) - .catch(() => { - wx.hideLoading() - this.setData({ status: 'empty' }) - }) - }, - - onRefresh() { - this.onStartSearch() - }, - - onTapWifi(e: WechatMiniprogram.TouchEvent) { - const ssid = e.currentTarget.dataset.ssid as string - const target = this.data.wifiList.find(item => item.ssid === ssid) - if (!target || target.connected) return - this.setData({ status: 'password', selectedSsid: ssid, selectWifiPWD: '', type: true }) - }, - - passwordInput(e: WechatMiniprogram.Input) { - this.setData({ selectWifiPWD: e.detail.value }) - }, - - inputTypeChange() { - this.setData({ type: !this.data.type }) - }, - - onCancelPwd() { - this.setData({ status: 'list', selectedSsid: '', selectWifiPWD: '' }) - }, - - onConfirmPwd() { - const { selectedSsid, selectWifiPWD } = this.data - if (!selectedSsid) return - this.setData({ status: 'configuring' }) - wx.showLoading({ title: '正在配网...', mask: true }) - lefuService.configWifi(selectedSsid, selectWifiPWD) - .then(() => { - wx.hideLoading() - this.setData({ - status: 'list', - wifiList: this.data.wifiList.map(item => ({ ...item, connected: item.ssid === selectedSsid })), - hasConnected: true, - selectedSsid: '', - selectWifiPWD: '', - }) - }) - .catch((err: Error) => { - wx.hideLoading() - wx.showToast({ title: err.message || '配网失败,请重试', icon: 'none' }) - this.setData({ status: 'password' }) - }) - }, - - onConfirm() { - if (!this.data.hasConnected) return - wx.navigateTo({ url: '/pages/supplementPersonal/supplementPersonal' }) - }, -}) diff --git a/miniprogram/pages/connectedWifi/connectedWifi.wxml b/miniprogram/pages/connectedWifi/connectedWifi.wxml deleted file mode 100644 index 4e2798b..0000000 --- a/miniprogram/pages/connectedWifi/connectedWifi.wxml +++ /dev/null @@ -1,134 +0,0 @@ - - - - 输入Wi-Fi密码 - - - - - - {{selectedSsid}} - - - 密码 - - - - - - - - - - - - - - - 取消 - 连接 - - - - - - - - - - - - {{deviceName}} - - - - 已连接 - - - - 连接断开 - - - - 连接中... - - - - - - - - 查找周边WiFi - - - - - - 搜索中... - - - - - - - 附近WiFi - - 网络列表(请选择一个网络进行连接) - - - - - - - - - {{item.ssid}} - - 已连接 - - - - - - - - - - {{item.ssid}} - - - - - - - 确认 - - - - - - - 附近WiFi - 刷新 - - 网络列表(请选择一个网络进行连接) - - - - - - 未查找到可用wifi,请重试 - - - - diff --git a/miniprogram/pages/data/data.json b/miniprogram/pages/data/data.json deleted file mode 100644 index cf12031..0000000 --- a/miniprogram/pages/data/data.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "usingComponents": { - "select-member-drawer": "/components/selectMemberDrawer/selectMemberDrawer" - } -} diff --git a/miniprogram/pages/data/data.less b/miniprogram/pages/data/data.less deleted file mode 100644 index bb35273..0000000 --- a/miniprogram/pages/data/data.less +++ /dev/null @@ -1,304 +0,0 @@ -.scrollViewContent { - width: 100%; - padding: 24rpx 30rpx; - box-sizing: border-box; - - .data-card { - width: 100%; - overflow: hidden; - zoom: 1; - position: relative; - padding: 40rpx 30rpx 30rpx; - box-sizing: border-box; - border-radius: 24rpx; - margin-bottom: 30rpx; - background-color: #FFFFFF; - - &::before { - content: ""; - position: absolute; - top: 2rpx; - left: 2rpx; - right: 2rpx; - height: 88rpx; - background: linear-gradient(180deg, #D3E9FF 0%, #FCFBFD 100%); - } - - - /* 用户信息行 */ - .userInfo { - position: relative; - z-index: 2; - width: 100%; - display: flex; - align-items: center; - margin-bottom: 30rpx; - - /* 头像色块(展示姓名首字) */ - .avatar { - width: 90rpx; - height: 90rpx; - margin-right: 24rpx; - border-radius: 18rpx; - color: #FFFFFF; - font-size: 36rpx; - font-weight: bold; - line-height: 90rpx; - text-align: center; - background-color: #1385FA; - } - - /* 姓名 + meta */ - .info { - flex: 1; - - .info-name-row { - height: 40rpx; - display: flex; - align-items: center; - margin-bottom: 10rpx; - - .info-name { - color: #252535; - height: 40rpx; - font-size: 30rpx; - font-weight: bold; - line-height: 40rpx; - margin-right: 20rpx; - } - - /* 本人 badge */ - .badge-self { - color: #1385FA; - height: 40rpx; - padding: 0 18rpx; - font-size: 24rpx; - line-height: 40rpx; - border-radius: 8rpx; - background-color: rgba(19, 133, 250, 0.2); - } - } - - .info-meta { - height: 26rpx; - display: flex; - align-items: center; - - .meta-text { - color: #808080; - height: 26rpx; - font-size: 24rpx; - line-height: 26rpx; - } - - .meta-divider { - color: #CCCCCC; - font-size: 24rpx; - line-height: 26rpx; - margin: 0 12rpx; - } - } - } - - /* 切换用户按钮 */ - .btn { - color: #1385FA; - width: 120rpx; - height: 48rpx; - font-size: 24rpx; - font-weight: 500; - line-height: 48rpx; - border-radius: 8rpx; - text-align: center; - border: 2rpx solid #1385FA; - } - } - - .card-divider { - position: relative; - z-index: 2; - width: calc(100% + 60rpx); - left: -30rpx; - height: 1rpx; - margin-bottom: 30rpx; - border-bottom: 2rpx dashed #EAECF1; - - &::before { - content: ""; - position: absolute; - left: -15rpx; - top: 0; - bottom: 0; - margin: auto; - width: 30rpx; - height: 30rpx; - background-color: #F5F7FB; - border-radius: 50%; - } - - &::after { - content: ""; - position: absolute; - right: -15rpx; - top: 0; - bottom: 0; - margin: auto; - width: 30rpx; - height: 30rpx; - background-color: #F5F7FB; - border-radius: 50%; - } - } - - /* 时间范围 Tab */ - .nav { - width: 100%; - height: 72rpx; - padding: 4rpx; - box-sizing: border-box; - display: flex; - flex-wrap: nowrap; - background-color: #E8EBF2; - border-radius: 16rpx; - margin-bottom: 24rpx; - - >view { - flex: 1; - height: 64rpx; - border-radius: 16rpx; - font-size: 28rpx; - color: #808080; - line-height: 64rpx; - text-align: center; - - &.active { - color: #FFFFFF; - font-weight: bold; - background-color: #1385FA; - } - } - } - - /* 统计摘要 4 格 */ - .summary { - width: 100%; - display: flex; - flex-wrap: nowrap; - justify-content: space-between; - padding: 24rpx 0; - background-color: #F3F4F6; - border-radius: 16rpx; - - .summary-item { - flex: 1; - display: flex; - flex-direction: column; - align-items: center; - - .summary-label { - color: #808080; - height: 24rpx; - font-size: 24rpx; - line-height: 24rpx; - margin-bottom: 20rpx; - } - - .summary-value { - color: #252535; - height: 32rpx; - font-size: 32rpx; - font-weight: bold; - line-height: 32rpx; - - text { - color: #808080; - font-size: 24rpx; - font-weight: normal; - margin-left: 4rpx; - } - } - - /* 体重下降(利好) */ - .value-down { - color: #2AC79F; - } - - /* 体重上升(警示) */ - .value-up { - color: #FF4D4F; - } - } - } - } - - /* 历史记录列表 */ - .data-list { - width: 100%; - - .list { - width: 100%; - margin-bottom: 30rpx; - - >view:first-of-type { - color: #808080; - width: 100%; - height: 24rpx; - font-size: 24rpx; - line-height: 24rpx; - margin-bottom: 20rpx; - } - - >view:last-of-type { - width: 100%; - height: 96rpx; - display: flex; - justify-content: space-between; - padding-left: 30rpx; - padding-right: 50rpx; - box-sizing: border-box; - flex-wrap: nowrap; - border-radius: 16rpx; - background-color: #FFFFFF; - - >view { - color: #808080; - font-size: 26rpx; - height: 96rpx; - line-height: 96rpx; - - text { - color: #252535; - font-weight: bold; - } - } - } - } - } - - /* ===== 空态 ===== */ - .empty-state { - width: 100%; - display: flex; - flex-direction: column; - align-items: center; - padding-top: 160rpx; - - .empty-icon { - width: 193rpx; - height: 138rpx; - margin-bottom: 40rpx; - - image { - width: 100%; - height: 100%; - } - } - - .empty-text { - color: #808080; - height: 30rpx; - font-size: 30rpx; - line-height: 30rpx; - } - } -} diff --git a/miniprogram/pages/data/data.ts b/miniprogram/pages/data/data.ts deleted file mode 100644 index e6ae6ee..0000000 --- a/miniprogram/pages/data/data.ts +++ /dev/null @@ -1,252 +0,0 @@ -import { get } from '../../utils/request/index' - -/** 接口 type 参数:0=全部 1=近7天 2=近30天 3=近3月 */ -type RangeType = 0 | 1 | 2 | 3 - -/** 时间范围 Tab 项 */ -interface RangeTab { - key: RangeType - label: string -} - -/** 缓存中的用户信息 */ -interface StorageUserInfo { - userId: string - realname: string - sex: number - height: number - birthday: string - avatar: string | null - relationType: number | string | null - [key: string]: any -} - -/** 页面展示用用户信息 */ -interface PageUserInfo extends StorageUserInfo { - sex_dictText: '男' | '女' | '' - age: number - isSelf: boolean -} - -/** GET /weighingScale/v3/getMeasureStatistics 返回结构 */ -interface MeasureStatistics { - maxWeight: number - minWeight: number - avgWeight: number - weightChange: number -} - -/** GET /weighingScale/v3/getMeasureHistoryPage records 单条 */ -interface MeasureRecord { - recordId: string - examTime: string - weight: number - bmi: number - fatRate: number -} - -/** 统计空态默认值 */ -const EMPTY_STATS: MeasureStatistics = { - maxWeight: 0, - minWeight: 0, - avgWeight: 0, - weightChange: 0, -} - -/** 从生日字符串(YYYY-MM-DD)计算周岁 */ -const calcAge = (birthday: string): number => { - if (!birthday) return 0 - const [year, month, day] = birthday.split('-').map(Number) - if (!year || !month || !day) return 0 - const now = new Date() - let age = now.getFullYear() - year - if (now.getMonth() + 1 < month || (now.getMonth() + 1 === month && now.getDate() < day)) age -= 1 - return Math.max(0, age) -} - -Page({ - data: { - activeType: 1 as RangeType, - rangeTabs: [ - { key: 1, label: '近7天' }, - { key: 2, label: '近30天' }, - { key: 3, label: '近3月' }, - ] as RangeTab[], - - userInfo: null as PageUserInfo | null, - - stats: EMPTY_STATS as MeasureStatistics, - - records: [] as MeasureRecord[], - pageNo: 1, - total: 0, - hasMore: false, - refresherTriggered: false, - showSelectDrawer: false, - }, - - onLoad() { - const raw = wx.getStorageSync('userInfo') as StorageUserInfo | null - if (!raw?.userId) return - const userInfo: PageUserInfo = { - ...raw, - sex_dictText: raw.sex === 1 ? '男' : raw.sex === 2 ? '女' : '', - age: calcAge(raw.birthday), - isSelf: true, - } - this.setData({ userInfo }) - this.loadData() - }, - - /** 重置分页,并行拉统计 + 第一页列表 */ - loadData() { - const userInfo = this.data.userInfo - if (!userInfo?.userId) return - this.setData({ status: 'loading', pageNo: 1, records: [] }) - - const statsReq = get('weighingScale/v3/getMeasureStatistics', { - userId: userInfo.userId, - type: this.data.activeType, - }, { loading: false }) - - const pageReq = get('weighingScale/v3/getMeasureHistoryPage', { - userId: userInfo.userId, - type: this.data.activeType, - pageNo: 1, - pageSize: 10, - }, { loading: false }) - - wx.showLoading({ title: '加载中...', mask: true }) - Promise.all([statsReq, pageReq]) - .then(([statsRes, pageRes]: any[]) => { - const rawStats = statsRes.result - const stats: MeasureStatistics = rawStats ? { - maxWeight: rawStats.maxWeight ?? 0, - minWeight: rawStats.minWeight ?? 0, - avgWeight: rawStats.avgWeight ?? 0, - weightChange: rawStats.weightChange ?? 0, - } : EMPTY_STATS - - const page = pageRes.result ?? {} - const records: MeasureRecord[] = (page.records ?? []).map((r: any) => ({ - recordId: r.recordId ?? '', - examTime: r.examTime ?? '', - weight: r.weight ?? 0, - bmi: r.bmi ?? 0, - fatRate: r.fatRate ?? 0, - })) - const total = page.total ?? 0 - - this.setData({ - stats, - records, - total, - hasMore: records.length < total, - }) - }) - .finally(() => { - wx.hideLoading() - this.setData({ refresherTriggered: false }) - }) - }, - - /** scroll-view 触底,加载下一页 */ - onScrollToLower() { - if (!this.data.hasMore) return - const userInfo = this.data.userInfo - if (!userInfo?.userId) return - const nextPage = this.data.pageNo + 1 - - wx.showLoading({ title: '加载中...', mask: true }) - get('weighingScale/v3/getMeasureHistoryPage', { - userId: userInfo.userId, - type: this.data.activeType, - pageNo: nextPage, - pageSize: 10, - }, { loading: false }) - .then((res: any) => { - const page = res.result ?? {} - const newRecords: MeasureRecord[] = (page.records ?? []).map((r: any) => ({ - recordId: r.recordId ?? '', - examTime: r.examTime ?? '', - weight: r.weight ?? 0, - bmi: r.bmi ?? 0, - fatRate: r.fatRate ?? 0, - })) - const allRecords = [...this.data.records, ...newRecords] - this.setData({ - records: allRecords, - pageNo: nextPage, - hasMore: allRecords.length < this.data.total, - }) - }) - .finally(() => { - wx.hideLoading() - }) - }, - - /** scroll-view 下拉刷新 */ - onRefresh() { - this.setData({ refresherTriggered: true }) - this.loadData() - }, - - /** 切换时间范围 Tab */ - onTapRange(e: WechatMiniprogram.TouchEvent) { - const key = Number(e.currentTarget.dataset.key) as RangeType - if (key === this.data.activeType) return - this.setData({ activeType: key }) - this.loadData() - }, - - /** 点击记录行,携带 recordId + userId 跳转测量报告 */ - onTapRecord(e: WechatMiniprogram.TouchEvent) { - const recordId = e.currentTarget.dataset.recordId as string - const userId = this.data.userInfo?.userId ?? '' - wx.navigateTo({ - url: `/pages/measurementReport/measurementReport?recordId=${recordId}&userId=${userId}`, - }) - }, - - /** 打开切换用户抽屉 */ - onTapSwitchMember() { - this.setData({ showSelectDrawer: true }) - }, - - /** 抽屉关闭 */ - onCloseDrawer() { - this.setData({ showSelectDrawer: false }) - }, - - /** 选中用户后更新 userInfo 并重新请求数据 */ - onSelectMember(e: WechatMiniprogram.CustomEvent) { - const m = e.detail as { - userId?: string - id: string - name: string - gender: '男' | '女' - age: number - height: number - sex?: number - birthday?: string - relationType?: number | string | null - avatar?: string - } - const storedUserId = (wx.getStorageSync('userInfo') as StorageUserInfo | null)?.userId ?? '' - const userInfo: PageUserInfo = { - id: m.id, - userId: m.userId ?? '', - realname: m.name, - sex: m.sex ?? (m.gender === '男' ? 1 : 2), - height: m.height, - birthday: m.birthday ?? '', - avatar: m.avatar ?? null, - relationType: m.relationType ?? null, - sex_dictText: m.gender, - age: m.age, - isSelf: m.userId === storedUserId, - } - this.setData({ showSelectDrawer: false, userInfo }) - this.loadData() - }, -}) diff --git a/miniprogram/pages/data/data.wxml b/miniprogram/pages/data/data.wxml deleted file mode 100644 index f1972a8..0000000 --- a/miniprogram/pages/data/data.wxml +++ /dev/null @@ -1,106 +0,0 @@ - - - - - - - - - {{ userInfo.realname[0] }} - - - {{ userInfo.realname }} - - 本人 - - - - {{ userInfo.sex_dictText }} - · - {{ userInfo.age }}岁 - | - {{ userInfo.height }}cm - - - 切换用户 - - - - - - - - {{ item.label }} - - - - - - - 最高体重 - {{ stats.maxWeight }}kg - - - 最低体重 - {{ stats.minWeight }}kg - - - 平均体重 - {{ stats.avgWeight }}kg - - - 体重变化 - - {{ stats.weightChange > 0 ? '+' : '' }}{{ stats.weightChange }}kg - - - - - - - - - - - {{ item.examTime }} - - 体重:{{ item.weight }}kg - BMI:{{ item.bmi }} - 体脂率:{{ item.fatRate }}% - - - - - - - - - - - - - 暂无称重数据 - - - - - - - diff --git a/miniprogram/pages/editMember/editMember.json b/miniprogram/pages/editMember/editMember.json deleted file mode 100644 index 00a9a93..0000000 --- a/miniprogram/pages/editMember/editMember.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "navigationBarTitleText": "编辑用户", - "usingComponents": { - "user-info-confirm-modal": "/components/userInfoConfirmModal/userInfoConfirmModal" - } -} diff --git a/miniprogram/pages/editMember/editMember.less b/miniprogram/pages/editMember/editMember.less deleted file mode 100644 index 86c84f5..0000000 --- a/miniprogram/pages/editMember/editMember.less +++ /dev/null @@ -1,249 +0,0 @@ -.edit-member { - width: 100%; - height: 100%; - display: flex; - flex-flow: column; - - /* 顶部浅蓝提示条 */ - .tips { - width: 100%; - height: 56rpx; - display: flex; - margin-bottom: 24rpx; - padding-left: 30rpx; - align-items: center; - box-sizing: border-box; - background-color: rgba(19, 133, 250, 0.2); - - .tips-icon { - color: #1385FA; - width: 28rpx; - height: 28rpx; - font-size: 20rpx; - font-weight: bold; - margin-right: 10rpx; - line-height: 24rpx; - text-align: center; - border-radius: 50%; - box-sizing: border-box; - border: 1rpx solid #1385FA; - } - - .tips-text { - color: #1385FA; - height: 24rpx; - font-size: 24rpx; - line-height: 24rpx; - } - } - - .form { - width: 100%; - flex: 1; - overflow: hidden; - - .scrollViewContent { - width: 100%; - padding: 0 30rpx; - box-sizing: border-box; - } - - /* 表单卡片 */ - .form-card { - width: 100%; - padding: 40rpx 30rpx; - border-radius: 16rpx; - box-sizing: border-box; - margin-bottom: 40rpx; - background-color: #FFFFFF; - - .form-row { - width: 100%; - margin-bottom: 40rpx; - - &:last-child { - margin-bottom: 0; - } - } - - .form-label { - width: 100%; - height: 40rpx; - display: flex; - flex-wrap: nowrap; - margin-bottom: 18rpx; - - .label-text { - color: #252535; - height: 40rpx; - font-size: 28rpx; - line-height: 40rpx; - font-weight: bold; - margin-right: 6rpx; - } - - .label-star { - color: #FF4D4F; - height: 40rpx; - font-weight: 500; - font-size: 28rpx; - line-height: 40rpx; - } - } - - .form-control { - width: 100%; - } - - /* 可输入区域 */ - .form-input { - width: 100%; - height: 88rpx; - padding: 0 30rpx; - color: #252535; - font-size: 26rpx; - border-radius: 16rpx; - box-sizing: border-box; - background-color: #FFFFFF; - border: 2rpx solid #E6E6EA; - } - - .form-placeholder { - color: #B6B6B6; - font-size: 26rpx; - } - - /* 日期选择器 */ - .form-picker { - width: 100%; - height: 88rpx; - padding: 0 30rpx; - color: #252535; - font-size: 26rpx; - line-height: 88rpx; - border-radius: 16rpx; - box-sizing: border-box; - background-color: #FFFFFF; - border: 2rpx solid #E6E6EA; - } - - .form-picker-placeholder { - color: #B6B6B6; - } - - /* 输入框 + 后缀容器(身高 cm / 体重 kg) */ - .form-input-wrap { - position: relative; - width: 100%; - - .form-input-with-suffix { - padding-right: 80rpx; - } - - .form-suffix-inside { - position: absolute; - right: 30rpx; - top: 0; - height: 88rpx; - color: #77849E; - font-size: 26rpx; - line-height: 88rpx; - } - } - - /* 只读区域(单位 / 性别 / 年龄 / 出生年月) */ - .form-readonly { - width: 100%; - height: 88rpx; - box-sizing: border-box; - padding: 0 24rpx; - background: #F6F6F6; - border-radius: 16rpx; - display: flex; - align-items: center; - border: 2rpx solid #E6E6EA; - - .readonly-text { - color: #252535; - font-size: 26rpx; - } - } - - .form-readonly-with-suffix { - justify-content: space-between; - - .form-suffix { - color: #77849E; - height: 88rpx; - font-size: 26rpx; - line-height: 88rpx; - } - } - - /* 性别切换按钮(家庭用户) */ - .gender-toggle { - display: flex; - gap: 24rpx; - - .gender-option { - width: 300rpx; - height: 88rpx; - font-size: 28rpx; - text-align: center; - line-height: 88rpx; - border-radius: 16rpx; - box-sizing: border-box; - color: #77849E; - background-color: #FFFFFF; - border: 2rpx solid #E6E6EA; - } - - .gender-option-active { - color: #FFFFFF; - font-weight: bold; - background-color: #1385FA; - border-color: #1385FA; - } - } - } - } - - /* 底部双按钮 */ - .actions { - width: 100%; - height: 120rpx; - padding: 0 55rpx; - display: flex; - flex-wrap: nowrap; - align-items: center; - justify-content: space-between; - box-sizing: border-box; - background-color: #FFFFFF; - box-shadow: 0 2rpx 8rpx 0 rgba(19, 133, 250, 0.06); - - .btn-cancel { - color: #1385FA; - width: 300rpx; - height: 88rpx; - font-size: 32rpx; - text-align: center; - line-height: 84rpx; - border-radius: 44rpx; - box-sizing: border-box; - border: 2rpx solid #1385FA; - background-color: #FFFFFF; - } - - .btn-primary { - color: #FFFFFF; - width: 300rpx; - height: 88rpx; - font-size: 32rpx; - font-weight: bold; - text-align: center; - line-height: 88rpx; - border-radius: 44rpx; - background-color: #1385FA; - } - } -} diff --git a/miniprogram/pages/editMember/editMember.ts b/miniprogram/pages/editMember/editMember.ts deleted file mode 100644 index d19a065..0000000 --- a/miniprogram/pages/editMember/editMember.ts +++ /dev/null @@ -1,382 +0,0 @@ -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 { - name: string - idCard: string - gender: string - age: string - height: string - weight: string - /** 1=男 2=女 */ - sex: number -} - -/** 身份证解析结果 */ -interface IdCardParsed { - gender: string - age: string - sex: number - birthday: string -} - -/** - * 编辑用户页 - * 根据用户 dataType 区分非家庭/家庭两种编辑模式 - */ -Page({ - data: { - userId: '', - /** dataType === 1 为家庭用户 */ - isFamily: false, - /** 单位(非家庭用户) */ - unitDisplay: '', - /** 出生年月 YYYY-MM-DD(家庭用户) */ - birthday: '', - /** 接口返回的原始用户数据,提交时以此为 base 覆盖修改字段 */ - rawUser: null as Record | null, - /** 正在请求身份证查询接口 */ - querying: false, - /** 是否已完成一次身份证查询 */ - hasQueried: false, - /** 接口返回的用户信息 */ - serverResult: null as UserInfoFromServer | null, - /** 表单只读状态(code=101 时置为 true) */ - readonly: false, - /** 是否显示确认弹框 */ - showConfirmModal: false, - - form: { - name: '', - idCard: '', - gender: '', - age: '', - height: '', - weight: '', - sex: 0, - } as MemberForm, - }, - - onLoad(options: Record) { - const { userId } = options - if (!userId) { - wx.showToast({ title: '缺少用户信息', icon: 'none' }) - setTimeout(() => wx.navigateBack(), 1500) - return - } - this.setData({ userId }) - get>('weighingScale/v3/select/userinfo/byId', { id: userId }, { loading: true }) - .then((res: any) => { - const u = res.result - if (!u) return - const isFamily = u.dataType === 1 - const sex = u.sex ?? 0 - const gender = sex === 1 ? '男' : sex === 2 ? '女' : '' - const age = this.calcAge(u.birthday ?? '') - - this.setData({ - rawUser: u, - isFamily, - unitDisplay: [u.secondDepart, u.thirdDepart].filter(Boolean).join(' / '), - birthday: u.birthday ?? '', - 'form.name': u.realname ?? '', - 'form.idCard': u.idCard ?? '', - 'form.gender': gender, - 'form.age': String(age), - 'form.height': String(u.height ?? ''), - 'form.weight': String(u.weight ?? ''), - 'form.sex': sex, - }) - }) - .catch(() => { - wx.showToast({ title: '加载用户信息失败', icon: 'none' }) - }) - }, - - /** 从生日字符串计算周岁 */ - 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) - }, - - onNameInput(e: WechatMiniprogram.Input) { - const name = e.detail.value - this.setData({ - 'form.name': name, - serverResult: null, - hasQueried: false, - readonly: false - }) - if (this.data.isFamily && 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.data.isFamily) { - this.setData({ 'form.idCard': idCard }) - return - } - 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, - }) - }, - - onHeightInput(e: WechatMiniprogram.Input) { - this.setData({ 'form.height': e.detail.value }) - }, - - onWeightInput(e: WechatMiniprogram.Input) { - this.setData({ 'form.weight': e.detail.value }) - }, - - 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('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, - '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 = { - 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 }) - }) - }, - - onConfirmModalConfirm() { - this.setData({ showConfirmModal: false }) - this.onSubmit() - }, - - onConfirmModalCancel() { - this.setData({ - showConfirmModal: false, - readonly: false, - 'form.name': '', - 'form.idCard': '', - 'form.gender': '', - 'form.age': '', - 'form.height': '', - 'form.weight': '', - serverResult: null, - hasQueried: false, - }) - }, - - /** 出生年月变更,同步更新年龄 */ - onBirthdayChange(e: WechatMiniprogram.PickerChange) { - const birthday = e.detail.value as string - const age = this.calcAge(birthday) - this.setData({ - birthday, - 'form.age': String(age), - }) - }, - - onCancel() { - wx.navigateBack() - }, - - onSubmit() { - const { name, idCard, gender, height, weight, sex } = this.data.form - const { isFamily, rawUser } = this.data - const heightNum = parseFloat(height) - const weightNum = parseFloat(weight) - - // 昵称(仅家庭用户必填) - if (isFamily && !name.trim()) { - wx.showToast({ title: '请填写昵称', icon: 'none' }) - return - } - // 家庭用户性别必选 - if (isFamily && !gender) { - wx.showToast({ title: '请选择性别', icon: 'none' }) - return - } - // 家庭用户出生年月必填 - if (isFamily && !this.data.birthday.trim()) { - wx.showToast({ title: '请填写出生年月', icon: 'none' }) - return - } - if (!height || isNaN(heightNum) || heightNum <= 0) { - wx.showToast({ title: '请填写身高', icon: 'none' }) - return - } - - const connectDeviceInfoRaw = wx.getStorageSync('connectDeviceInfo') - - // 以接口原始返回 + 身份证查询结果为 base,覆盖编辑后的字段 - const serverResult = this.data.serverResult - const payload: Record = { - ...(rawUser ?? {}), - ...(serverResult ?? {}), - id: this.data.userId, - realname: name.trim(), - idCard, - sex, - birthday: this.data.birthday, - height: heightNum, - weight: isNaN(weightNum) ? 0 : weightNum, - connectDeviceInfo: connectDeviceInfoRaw ? JSON.stringify(connectDeviceInfoRaw) : (rawUser?.connectDeviceInfo ?? ''), - } - - 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' }) - }) - } -}) diff --git a/miniprogram/pages/editMember/editMember.wxml b/miniprogram/pages/editMember/editMember.wxml deleted file mode 100644 index b999132..0000000 --- a/miniprogram/pages/editMember/editMember.wxml +++ /dev/null @@ -1,190 +0,0 @@ - - - - - ! - 最多支持 10 位用户 - - - - - - - - - - - - - - 姓名 - - - - {{ form.name }} - - - - - - - - 单位 - - - - {{ unitDisplay }} - - - - - - - - 性别 - - - - {{ form.gender }} - - - - - - - - 年龄 - - - - {{ form.age }} - - - - - - - - - 身高 - * - - - - - cm - - - - - - - - 体重 - - - - - kg - - - - - - - - - - - - - 昵称 - * - - - - - - - - - - 身份证号码 - - - - - - - - - - 性别 - * - - - - - - - - - - - - - 出生年月 - * - - - - - {{ birthday || '请选择出生年月' }} - - - - - - - - - 身高 - * - - - - - cm - - - - - - - - 体重 - - - - - kg - - - - - - - - - - - - - 取消 - 保存用户信息 - - - - - - diff --git a/miniprogram/pages/equipment/equipment.json b/miniprogram/pages/equipment/equipment.json deleted file mode 100644 index 1d7d59d..0000000 --- a/miniprogram/pages/equipment/equipment.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "navigationBarTitleText": "设备管理", - "usingComponents": {} -} diff --git a/miniprogram/pages/equipment/equipment.less b/miniprogram/pages/equipment/equipment.less deleted file mode 100644 index 61b9b20..0000000 --- a/miniprogram/pages/equipment/equipment.less +++ /dev/null @@ -1,196 +0,0 @@ -/* 设备管理页 —— 已配对设备列表 + 添加设备主按钮 */ -.equipment { - width: 100%; - height: 100%; - box-sizing: border-box; - background-color: #F7F8FA; - display: flex; - flex-direction: column; - - /* 副标题:已配对设备 */ - .section-title { - color: #808080; - height: 80rpx; - font-size: 26rpx; - padding: 0 30rpx; - line-height: 80rpx; - flex-shrink: 0; - } - - /* 卡片列表(scroll-view) */ - .device-list { - flex: 1; - overflow: hidden; - width: 100%; - - .scroll-view-content { - width: 100%; - padding: 0 30rpx; - box-sizing: border-box; - - .device-card { - background-color: #FFFFFF; - border-radius: 16rpx; - padding: 30rpx; - box-sizing: border-box; - margin-bottom: 24rpx; - // margin: 0 30rpx 24rpx; - - &:last-child { - margin-bottom: 30rpx; - } - - /* 上半:图标 + 设备名 + 状态 */ - .card-top { - width: 100%; - display: flex; - align-items: center; - - .card-icon { - width: 90rpx; - height: 90rpx; - border-radius: 24rpx; - margin-right: 24rpx; - } - - .card-info { - flex: 1; - display: flex; - flex-direction: column; - justify-content: center; - - .card-name { - color: #252535; - height: 32rpx; - font-size: 32rpx; - font-weight: bold; - line-height: 32rpx; - margin-bottom: 16rpx; - } - - .card-status { - display: flex; - align-items: center; - - .card-dot { - width: 12rpx; - height: 12rpx; - border-radius: 50%; - margin-right: 14rpx; - } - - .card-dot-on { - background-color: #2AC79F; - } - - .card-dot-off { - background-color: #F24439; - } - - .card-status-text { - height: 28rpx; - font-size: 28rpx; - font-weight: 500; - line-height: 28rpx; - } - - .card-status-on { - color: #2AC79F; - } - - .card-status-off { - color: #F24439; - } - } - } - } - - /* 分隔线 */ - .card-divider { - width: 100%; - height: 2rpx; - background-color: #EAECF1; - border-radius: 1rpx; - margin: 30rpx 0 30rpx; - } - - /* 操作按钮区:单/双形态共用 */ - .card-actions { - width: 100%; - padding: 0 24rpx; - box-sizing: border-box; - display: flex; - align-items: center; - - /* 单按钮:已连接态 */ - &.card-actions-single { - - .btn-ghost-full { - width: 100%; - height: 82rpx; - line-height: 78rpx; - text-align: center; - color: #77849E; - font-size: 28rpx; - background-color: #FFFFFF; - border: 2rpx solid #E6E6EA; - border-radius: 39rpx; - box-sizing: border-box; - } - } - - /* 双按钮:未连接态 */ - &.card-actions-double { - justify-content: space-between; - - .btn-ghost-half { - width: 280rpx; - height: 82rpx; - line-height: 78rpx; - text-align: center; - color: #77849E; - font-size: 28rpx; - background-color: #FFFFFF; - border: 2rpx solid #E6E6EA; - border-radius: 39rpx; - box-sizing: border-box; - } - } - } - } - } - } - - /* 底部「添加设备」主按钮 */ - .add-btn { - width: 580rpx; - height: 88rpx; - display: flex; - align-items: center; - justify-content: center; - background-color: #1385FA; - border-radius: 44rpx; - margin: 20rpx auto; - flex-shrink: 0; - - .add-btn-icon { - color: #FFFFFF; - width: 32rpx; - font-size: 28rpx; - font-weight: bold; - height: 32rpx; - line-height: 30rpx; - border-radius: 50%; - text-align: center; - margin-right: 15rpx; - border: 2rpx solid #ffffff; - } - - .add-btn-text { - color: #FFFFFF; - font-size: 32rpx; - font-weight: bold; - line-height: 32rpx; - } - } -} diff --git a/miniprogram/pages/equipment/equipment.ts b/miniprogram/pages/equipment/equipment.ts deleted file mode 100644 index 4bdea6a..0000000 --- a/miniprogram/pages/equipment/equipment.ts +++ /dev/null @@ -1,199 +0,0 @@ -import { get } from '../../utils/request/index' -import { lefuService } from '../../lefu/index' -import type { RawDevice } from '../../lefu/types' - -/** API 返回的设备记录 */ -interface DeviceRecord { - id: string - userId: string - parentUserId: string | null - scaleDeviceId: string - deviceId: string | null - deviceType: string - dataType: number - userType: number - idCard: string - height: number - weight: number - birthday: string - sex: number - avatar: string | null - phone: string | null - thirdId: string - realname: string - bmi: number - createBy: string - createTime: string - updateBy: string - updateTime: string - delFlag: number - sn: string | null - workNo: string - remark: string | null - relationType: string | null - sex_dictText: string - /** 设备连接信息 JSON 字符串,与登录接口返回格式一致 */ - connectDeviceInfo: string -} - -/** 展示用设备项 */ -interface DeviceItem { - id: string - scaleDeviceId: string - scaleDeviceName: string - connectDeviceInfo: string - realname: string - /** 蓝牙连接状态,接口不返回,由业务层更新 */ - connected: boolean - userId: string - sex: number - height: number - birthday: string - avatar: string | null - relationType: string | null - sex_dictText: string -} - -Page({ - data: { - loading: false, - deviceList: [] as DeviceItem[], - }, - - onShow() { - this.loadData() - }, - - onUnload() { - lefuService.stopScan() - lefuService.onDeviceConnect(() => {}) - lefuService.onDeviceInfo(() => {}) - lefuService.onDisconnected(() => {}) - }, - - /** 拉取已绑定设备列表,加载完成后检查缓存自动连接 */ - loadData() { - this.setData({ loading: true }) - const userInfo = wx.getStorageSync('userInfo') - get('weighingScale/v3/list/device', { userId: userInfo?.userId }) - .then(res => { - const list: DeviceItem[] = res.result.map(item => { - let scaleDeviceName = '' - try { - const info = JSON.parse(item.connectDeviceInfo || '{}') - scaleDeviceName = info.name ?? '' - } catch {} - return { - id: item.id, - scaleDeviceId: item.scaleDeviceId, - scaleDeviceName, - connectDeviceInfo: item.connectDeviceInfo ?? '', - realname: item.realname, - connected: false, - userId: item.userId, - sex: item.sex, - height: item.height, - birthday: item.birthday, - avatar: item.avatar, - relationType: item.relationType, - sex_dictText: item.sex_dictText, - } - }) - this.setData({ deviceList: list }, () => { - this._checkAndAutoConnect() - }) - }) - .finally(() => { - this.setData({ loading: false }) - }) - }, - - /** 每次进入页面都走真实扫描连接,确保状态最新 */ - _checkAndAutoConnect() { - lefuService.onDeviceInfo((info) => { - this._markConnected(info.serialNumber as string) - }) - lefuService.onDisconnected(() => { - this._markAllDisconnected() - }) - lefuService.autoConnect() - }, - - /** 将 serialNumber 对应的列表项标记为已连接,其余置为未连接 */ - _markConnected(serialNumber: string) { - const list = this.data.deviceList.map(item => ({ - ...item, - connected: item.scaleDeviceId === serialNumber, - })) - this.setData({ deviceList: list }) - }, - - /** 将所有设备标记为未连接 */ - _markAllDisconnected() { - const list = this.data.deviceList.map(item => ({ ...item, connected: false })) - this.setData({ deviceList: list }) - }, - - /** 点击「连接」:扫描周边设备,找到 deviceId 匹配的设备后连接 */ - onTapConnect(e: WechatMiniprogram.TouchEvent) { - const userInfo = e.currentTarget.dataset.userInfo as DeviceItem - const connectDeviceInfoStr = e.currentTarget.dataset.connectDeviceInfo as string - let cachedDevice: RawDevice | null = null - try { - cachedDevice = JSON.parse(connectDeviceInfoStr || '{}') - } catch {} - const targetDeviceId = cachedDevice?.deviceId as string | undefined - if (!targetDeviceId) return - - if (lefuService.isConnected) lefuService.disconnect() - this.setData({ deviceList: this.data.deviceList.map(item => ({ ...item, connected: false })) }) - - let scanTimer: number | null = null - - wx.showLoading({ title: '扫描中...', mask: true }) - wx.openBluetoothAdapter({ - success: () => { - scanTimer = setTimeout(() => { - lefuService.stopScan() - wx.hideLoading() - wx.showToast({ title: '未找到设备', icon: 'none' }) - }, 15000) - - lefuService.onDevicesList((devices) => { - const matched = devices.find(d => d.raw.deviceId === targetDeviceId) - if (!matched) return - if (scanTimer !== null) { clearTimeout(scanTimer); scanTimer = null } - lefuService.stopScan() - wx.showLoading({ title: '连接中...', mask: true }) - lefuService.connect(matched.raw) - lefuService.onDeviceConnect(() => { - wx.hideLoading() - wx.setStorageSync('connectDeviceInfo', matched.raw) - // 连接成功后缓存当前设备关联的用户信息 - if (userInfo) { - wx.setStorageSync('userInfo', userInfo) - } - }) - lefuService.onDeviceInfo((info) => { - this._markConnected(info.serialNumber as string) - }) - }) - lefuService.startScan() - }, - fail: () => { - wx.hideLoading() - wx.showToast({ title: '蓝牙未开启', icon: 'none' }) - }, - }) - }, - - /* 点击「用户管理」→ 跳转用户管理页,sn 由目标页从缓存读取 */ - onTapMember() { - wx.navigateTo({ url: '/pages/equipmentMember/equipmentMember' }) - }, - - /* 点击「添加设备」→ 跳转设备搜索页 */ - onTapAdd() { - wx.navigateTo({ url: '/pages/connectedDevice/connectedDevice' }) - }, -}) diff --git a/miniprogram/pages/equipment/equipment.wxml b/miniprogram/pages/equipment/equipment.wxml deleted file mode 100644 index cba9004..0000000 --- a/miniprogram/pages/equipment/equipment.wxml +++ /dev/null @@ -1,61 +0,0 @@ - - - - 已配对设备 - - - - - - - - - - - - - {{ item.scaleDeviceName }} - - - - - 已连接 - - - - - 未连接 - - - - - - - - - - - - - 用户管理 - - - - - - 连接 - - - - - - - - - - - + - 添加设备 - - - diff --git a/miniprogram/pages/equipmentMember/equipmentMember.json b/miniprogram/pages/equipmentMember/equipmentMember.json deleted file mode 100644 index db436ed..0000000 --- a/miniprogram/pages/equipmentMember/equipmentMember.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "navigationBarTitleText": "设备用户", - "usingComponents": {} -} diff --git a/miniprogram/pages/equipmentMember/equipmentMember.less b/miniprogram/pages/equipmentMember/equipmentMember.less deleted file mode 100644 index 960955f..0000000 --- a/miniprogram/pages/equipmentMember/equipmentMember.less +++ /dev/null @@ -1,405 +0,0 @@ -.equipmentMember { - width: 100%; - height: 100%; - display: flex; - flex-direction: column; - - /* 顶部浅红提示条(未连接态) */ - .tips { - width: 100%; - height: 56rpx; - display: flex; - padding-left: 30rpx; - align-items: center; - flex-shrink: 0; - margin-bottom: 24rpx; - box-sizing: border-box; - background-color: rgba(255, 77, 79, 0.12); - - .tips-icon { - color: #FF4D4F; - width: 28rpx; - height: 28rpx; - font-size: 20rpx; - font-weight: bold; - margin-right: 10rpx; - line-height: 24rpx; - text-align: center; - border-radius: 50%; - box-sizing: border-box; - border: 1rpx solid #FF4D4F; - } - - .tips-text { - color: #FF4D4F; - height: 24rpx; - font-size: 24rpx; - line-height: 24rpx; - } - } - - .seaction { - width: 100%; - flex: 1; - overflow: hidden; - display: flex; - padding-top: 24rpx; - flex-direction: column; - margin-bottom: 24rpx; - - /* 设备卡(大卡:左插画 + 右信息) */ - .device-card { - width: 100%; - display: flex; - align-items: center; - padding: 30rpx; - border-radius: 20rpx; - box-sizing: border-box; - margin-bottom: 24rpx; - background-color: #FFFFFF; - - .device-illustration { - width: 90rpx; - height: 90rpx; - border-radius: 24rpx; - margin-right: 24rpx; - background-color: rgba(19, 133, 250, 0.12); - } - - .device-info { - flex: 1; - height: auto; - - .device-name { - color: #252535; - height: 32rpx; - font-size: 32rpx; - font-weight: bold; - line-height: 32rpx; - margin-bottom: 10rpx; - } - - .device-status { - height: 28rpx; - display: flex; - align-items: center; - - .status-dot { - width: 12rpx; - height: 12rpx; - border-radius: 50%; - margin-right: 10rpx; - } - - .status-dot-on { - background-color: #2AC79F; - } - - .status-dot-off { - background-color: #B6B6B6; - } - - .status-text { - color: #77849E; - height: 28rpx; - font-size: 28rpx; - line-height: 28rpx; - } - } - } - } - - .scrollViewContent { - box-sizing: border-box; - - /* 用户卡(容量小标题 + 列表整体白卡) */ - .member-card { - width: 100%; - flex: 1; - box-sizing: border-box; - overflow: hidden; - margin-bottom: 20rpx; - - /* 容量小标题 */ - .member-card-header { - width: 100%; - height: 88rpx; - display: flex; - padding: 0 30rpx; - box-sizing: border-box; - border-radius: 20rpx 20rpx 0 0; - background-color: #FFFFFF; - border-bottom: 1rpx solid #EAECF1; - justify-content: space-between; - - .capacity-label { - color: #252535; - height: 88rpx; - font-size: 30rpx; - font-weight: bold; - line-height: 88rpx; - } - - .capacity-value { - color: #1385FA; - height: 88rpx; - font-size: 28rpx; - font-weight: bold; - line-height: 88rpx; - } - } - - .member-card-title { - height: 60rpx; - font-size: 24rpx; - line-height: 60rpx; - padding-left: 30rpx; - box-sizing: border-box; - } - - .member-item { - width: 100%; - display: flex; - align-items: center; - padding: 20rpx 30rpx; - box-sizing: border-box; - margin-bottom: 24rpx; - border-radius: 24rpx; - background-color: #FFFFFF; - - &:last-of-type { - margin-bottom: 0; - } - - .member-avatar { - width: 90rpx; - height: 90rpx; - border-radius: 20rpx; - margin-right: 24rpx; - background-color: #1385FA; - color: #ffffff; - font-size: 36rpx; - font-weight: bold; - line-height: 90rpx; - text-align: center; - } - - .member-info { - flex: 1; - display: flex; - flex-flow: column; - justify-content: center; - overflow: hidden; - - .member-name-row { - height: 40rpx; - display: flex; - align-items: center; - margin-bottom: 16rpx; - - .member-name { - color: #252535; - height: 40rpx; - font-size: 32rpx; - font-weight: bold; - line-height: 40rpx; - margin-right: 20rpx; - } - } - - .member-meta { - height: 26rpx; - display: flex; - align-items: center; - - .meta-text { - color: #77849E; - height: 26rpx; - font-size: 26rpx; - line-height: 26rpx; - } - - .meta-divider { - color: #B6B6B6; - height: 26rpx; - font-size: 26rpx; - line-height: 26rpx; - margin: 0 12rpx; - } - } - } - - /* 右侧:badge 或纯文字编辑/删除 */ - .member-actions { - height: 48rpx; - display: flex; - align-items: center; - flex-shrink: 0; - - .action-text { - color: #808080; - height: 48rpx; - font-size: 24rpx; - line-height: 46rpx; - margin-left: 20rpx; - border-radius: 8rpx; - box-sizing: border-box; - padding: 0 20rpx; - border: 1rpx solid #E6E6EA; - } - - .action-text-danger { - color: #FF4D4F; - border: 1rpx solid #FF4D4F; - } - } - } - - .my { - border-radius: 0 0 24rpx 24rpx; - } - } - } - - /* 通用 badge(本人 / 他人添加) */ - .badge { - height: 40rpx; - font-size: 24rpx; - line-height: 40rpx; - border-radius: 8rpx; - } - - .badge-self { - color: #1385FA; - padding: 0 18rpx; - background-color: rgba(19, 133, 250, 0.12); - } - - .badge-my { - color: #1385FA; - padding: 0 18rpx; - background-color: rgba(19, 133, 250, 0.08); - } - - .badge-other { - color: #808080; - } - } - - .btn { - width: 100%; - height: 120rpx; - display: flex; - align-items: center; - background-color: #FFFFFF; - - /* 底部添加用户按钮 */ - .add-member { - width: 580rpx; - height: 88rpx; - display: flex; - align-items: center; - justify-content: center; - margin: 0 auto; - border-radius: 44rpx; - background-color: #1385FA; - - .add-member-icon { - color: #FFFFFF; - width: 32rpx; - font-size: 28rpx; - font-weight: bold; - height: 32rpx; - line-height: 30rpx; - border-radius: 50%; - text-align: center; - margin-right: 15rpx; - border: 2rpx solid #ffffff; - } - - .add-member-text { - color: #FFFFFF; - height: 88rpx; - font-size: 32rpx; - font-weight: bold; - line-height: 88rpx; - - text { - font-weight: 500; - } - } - } - - .add-member-disabled { - opacity: 0.4; - } - } - - /* 删除确认弹窗 — 仿原生 wx.showModal 风格 */ - .delete-modal-mask { - position: fixed; - top: -10vh; - left: 0; - right: 0; - bottom: 0; - margin: auto; - z-index: 999; - display: flex; - align-items: center; - justify-content: center; - background-color: rgba(0, 0, 0, 0.5); - } - - .delete-modal { - width: 650rpx; - padding-top: 50rpx; - border-radius: 24rpx; - background-color: #FFFFFF; - } - - .delete-modal-title { - width: 100%; - height: 40rpx; - line-height: 40rpx; - color: #000000; - text-align: center; - font-size: 36rpx; - font-weight: bolder; - } - - .delete-modal-content { - padding: 40rpx 0 60rpx; - font-size: 28rpx; - color: #353535; - line-height: 44rpx; - text-align: center; - } - - .delete-modal-warning { - color: #F24439; - } - - .delete-modal-footer { - width: 100%; - height: 110rpx; - display: flex; - border-top: 2rpx solid #EAECF1; - } - - .delete-modal-btn { - flex: 1; - height: 110rpx; - line-height: 110rpx; - text-align: center; - font-size: 36rpx; - } - - .delete-modal-btn-cancel { - color: #77849E; - border-right: 2rpx solid #EAECF1; - } - - .delete-modal-btn-confirm { - color: #1385FA; - } -} diff --git a/miniprogram/pages/equipmentMember/equipmentMember.ts b/miniprogram/pages/equipmentMember/equipmentMember.ts deleted file mode 100644 index aebe14a..0000000 --- a/miniprogram/pages/equipmentMember/equipmentMember.ts +++ /dev/null @@ -1,337 +0,0 @@ -import { get, del } from '../../utils/request/index' -import { lefuService } from '../../lefu/index' -import type { DeviceMember } from '../../lefu/index' - -/** GET /weighingScale/v3/selectUserBySn/user 返回的 result 单条结构 */ -interface SysUserDevice { - id: string - userId: string - parentUserId: string | null - scaleDeviceId: string - deviceId: string | null - deviceType: string - dataType: number - /** 用户类型:1=主用户 0=子用户 */ - userType: number - idCard: string - height: number - weight: number - birthday: string - /** 性别:1=男 2=女 */ - sex: number - avatar: string | null - phone: string | null - thirdId: string | null - realname: string - bmi: number - createBy: string | null - createTime: string | null - updateBy: string | null - updateTime: string | null - delFlag: number - sn: string | null - workNo: string | null - remark: string | null - connectDeviceInfo: string | null - /** 关系标记:1=本人 2=自己添加 3=他人添加 */ - relationType: number | null -} - -/** 页面展示用用户模型 */ -interface MemberDisplay { - id: string - /** 用户 ID */ - userId: string - realname: string - /** '男' | '女' */ - sex_dictText: string - /** 周岁,由 birthday 计算而来 */ - ageDisplay: string - height: number - /** 1=本人 2=自己添加 3=他人添加 */ - relationType: number - userType: number - avatar: string | null - /** 原始 sex 数值,供 lefu 层转换使用 */ - sex: number - /** 周岁数值,供 lefu 层使用 */ - age: number - /** 0=员工 1=家庭成员 */ - dataType: number -} - -/** 设备卡展示模型 */ -interface DeviceCard { - /** 设备 SN,来自接口返回的 scaleDeviceId */ - name: string - capacityUsed: number - capacityTotal: number -} - -/** - * 从生日字符串(YYYY-MM-DD)计算周岁 - * @param birthday 生日字符串 - */ -const 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) -} - -const toDeviceMember = (m: MemberDisplay): DeviceMember => ({ - id: m.userId, - name: m.realname, - gender: m.sex === 1 ? 1 : 0, - age: m.age, - height: m.height, - isSelf: m.relationType === 1, -}) - -const syncToDevice = (members: MemberDisplay[]): void => { - const deviceMembers = members.map(toDeviceMember) - const total = deviceMembers.length - wx.showLoading({ title: `加载中...`, mask: true }) - console.log(`正在下发第1/${total}个用户...`) - lefuService.syncMembersToDevice(deviceMembers, (current, t) => { - wx.showLoading({ title: `加载中...`, mask: true }) - console.log(`正在下发第${current}/${t}个用户...`) - }) - .then(() => { - wx.hideLoading() - wx.showToast({ title: '用户同步成功', icon: 'success' }) - }) - .catch((err: Error) => { - wx.hideLoading() - wx.showToast({ title: err.message || '同步失败,请重试', icon: 'none' }) - }) -} - -/** - * 设备用户管理页 - * sn 与设备名直接读 lefuService.deviceInfo,连接状态实时同步 - */ -Page({ - /** 非响应式标记:首次加载不同步设备,数据变更后的刷新才同步 */ - _firstLoad: true as boolean, - - data: { - connected: false, - /** 当前设备 SN,来自 lefuService.deviceInfo.serialNumber */ - sn: '', - loading: false, - deviceCard: { - name: '', - capacityUsed: 0, - capacityTotal: 10, - } as DeviceCard, - /** 本人(单个) */ - selfMember: null as MemberDisplay | null, - /** 员工列表 */ - employeeList: [] as MemberDisplay[], - /** 家庭成员列表 */ - familyList: [] as MemberDisplay[], - /** 自定义删除确认弹窗 */ - showDeleteModal: false, - deleteTarget: null as MemberDisplay | null, - }, - - // 当前子页 addMember 未注册任何 SDK 回调,不会覆盖本页回调槽位,故无需 onShow 兜底。 - // 若未来子页新增 onDeviceConnect / onDisconnected 注册,需启用以下 onShow 同步连接状态。 - // onShow() { - // this.setData({ connected: lefuService.isConnected }) - // }, - - onLoad() { - const deviceInfo = lefuService.deviceInfo - this.setData({ - sn: deviceInfo?.serialNumber ?? '', - connected: lefuService.isConnected, - 'deviceCard.name': deviceInfo?.modelNumber ?? '智能体重秤', - }) - if (deviceInfo?.serialNumber) { - this._loadData(deviceInfo.serialNumber) - } - // deviceInfo 实时同步:进页面时可能还未到达 - lefuService.onDeviceInfo((info) => { - this.setData({ - sn: info.serialNumber ?? '', - 'deviceCard.name': info.modelNumber ?? '智能体重秤', - }) - if (!this.data.selfMember && !this.data.employeeList.length && !this.data.familyList.length && info.serialNumber) { - this._loadData(info.serialNumber) - } - }) - lefuService.onDeviceConnect(() => { - this.setData({ connected: true }) - }) - lefuService.onDisconnected(() => { - this.setData({ connected: false }) - }) - }, - - onUnload() { - lefuService.onDeviceConnect(() => {}) - lefuService.onDisconnected(() => {}) - lefuService.onDeviceInfo(() => {}) - }, - - /** - * 调 v3 接口拉取用户列表,成功后更新设备名与用户数据 - * @param sn 设备 SN - */ - _loadData(sn: string) { - const userInfo = wx.getStorageSync('userInfo') as { userId?: string } | null - const userId = userInfo?.userId ?? '' - this.setData({ loading: true }) - - get('weighingScale/v3/selectUserBySn/user', { sn, userId }) - .then(res => { - const list = res.result.map((item): MemberDisplay => { - const age = calcAge(item.birthday) - return { - id: item.id, - userId: item.userId, - realname: item.realname, - sex_dictText: item.sex === 1 ? '男' : '女', - ageDisplay: String(age), - height: item.height, - relationType: item.relationType ?? 2, - userType: item.userType, - avatar: item.avatar, - sex: item.sex, - age, - dataType: item.dataType, - } - }) - const selfMember = list.find(m => m.relationType === 1) || null - const employeeList = list.filter(m => m.relationType !== 1 && m.dataType === 0) - const familyList = list.filter(m => m.relationType !== 1 && m.dataType === 1) - this.setData({ - selfMember, - employeeList, - familyList, - 'deviceCard.capacityUsed': list.length, - }) - syncToDevice(list) - }) - .finally(() => { - this.setData({ loading: false }) - }) - }, - - /** 编辑用户:仅携带 userId 跳转,详情由目标页自行查询 */ - onTapEdit(e: WechatMiniprogram.TouchEvent) { - const id = e.currentTarget.dataset.id as string - wx.navigateTo({ url: `/pages/editMember/editMember?userId=${id}` }) - }, - - /** 删除用户:弹出自定义确认弹窗 */ - onTapDelete(e: WechatMiniprogram.TouchEvent) { - const id = e.currentTarget.dataset.id as string - const target = this.data.employeeList.find(m => m.id === id) - || this.data.familyList.find(m => m.id === id) - if (!target) return - - if (!this.data.connected) { - wx.showToast({ title: '请先连接设备', icon: 'none' }) - return - } - - // if (target.userType === 1) { - // const hasSubUser = this.data.employeeList.some(m => m.userType !== 1) - // || this.data.familyList.some(m => m.userType !== 1) - // if (hasSubUser) { - // wx.showToast({ title: '请先删除所有普通用户', icon: 'none' }) - // return - // } - // } - - this.setData({ - showDeleteModal: true, - deleteTarget: target, - }) - }, - - /** 关闭删除确认弹窗 */ - onHideDeleteModal() { - this.setData({ - showDeleteModal: false, - deleteTarget: null, - }) - }, - - /** 确认删除 */ - onConfirmDelete() { - const target = this.data.deleteTarget - if (!target) return - - const id = target.id - - const newEmployeeList = this.data.employeeList.filter(m => m.id !== id) - const newFamilyList = this.data.familyList.filter(m => m.id !== id) - const merged: MemberDisplay[] = [ - ...(this.data.selfMember ? [this.data.selfMember] : []), - ...newEmployeeList, - ...newFamilyList, - ] - - this.setData({ - employeeList: newEmployeeList, - familyList: newFamilyList, - 'deviceCard.capacityUsed': merged.length, - showDeleteModal: false, - deleteTarget: null, - }) - - wx.showLoading({ title: '加载中...', mask: true }) - console.log('正在同步设备数据...') - - let devicePromise: Promise - if (merged.length) { - devicePromise = lefuService.syncMembersToDevice( - merged.map(toDeviceMember), - (current, total) => { - wx.showLoading({ title: `加载中...`, mask: true }) - console.log(`正在同步设备数据 ${current}/${total}...`) - }, - ) - } else { - devicePromise = lefuService.clearDeviceMembers() - } - - devicePromise - .then(() => { - return del('weighingScale/v2/del/user', { id }, { loading: false }) - }) - .then(() => { - wx.hideLoading() - wx.showToast({ title: '删除成功', icon: 'success' }) - }) - .catch((err: Error) => { - wx.hideLoading() - this._loadData(this.data.sn) - wx.showToast({ title: err.message || '删除失败,请重试', icon: 'none' }) - }) - }, - - /** 空方法,阻止弹窗事件冒泡 */ - noop() {}, - - /** 添加用户:未连接态禁用 */ - onTapAddMember() { - if (!this.data.connected) return - const total = (this.data.selfMember ? 1 : 0) + this.data.employeeList.length + this.data.familyList.length - if (total >= 10) { - wx.showToast({ title: '超过最大用户限制', icon: 'none' }) - return - } - wx.navigateTo({ url: '/pages/addMember/addMember' }) - }, -}) diff --git a/miniprogram/pages/equipmentMember/equipmentMember.wxml b/miniprogram/pages/equipmentMember/equipmentMember.wxml deleted file mode 100644 index 28a0da6..0000000 --- a/miniprogram/pages/equipmentMember/equipmentMember.wxml +++ /dev/null @@ -1,148 +0,0 @@ - - - - - - ! - 设备未连接,请前往连接 - - - - - - - - - - {{ deviceCard.name }} - - - {{ connected ? '设备已连接' : '未连接' }} - - - - - - - - - - - - - 设备容量 - - {{ deviceCard.capacityUsed }}/{{ deviceCard.capacityTotal }} - - - - - - {{ selfMember.realname[0] }} - - - {{ selfMember.realname }} - 本人 - - - {{ selfMember.sex_dictText }} - · - {{ selfMember.ageDisplay }}岁 - | - {{ selfMember.height }}cm - - - - 编辑 - - - - - - - 员工 - - - {{ item.realname[0] }} - - - {{ item.realname }} - - 他人添加 - - - - {{ item.sex_dictText }} - · - {{ item.ageDisplay }}岁 - | - {{ item.height }}cm - - - - 编辑 - 删除 - - - - - - - - 家庭成员 - - - {{ item.realname[0] }} - - - {{ item.realname }} - - 他人添加 - - - - {{ item.sex_dictText }} - · - {{ item.ageDisplay }}岁 - | - {{ item.height }}cm - - - - 编辑 - 删除 - - - - - - - - - - - - + - 添加用户(最多10位) - - - - - - - 确认删除 - - - "{{ deleteTarget.realname }}"用户为他人添加,请确认是否删除 - - - 确定删除"{{ deleteTarget.realname }}"吗?删除后将不再显示 - - - - 取消 - 删除 - - - - diff --git a/miniprogram/pages/home/home.json b/miniprogram/pages/home/home.json deleted file mode 100644 index e90e996..0000000 --- a/miniprogram/pages/home/home.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "usingComponents": {}, - "navigationStyle": "custom" -} diff --git a/miniprogram/pages/home/home.less b/miniprogram/pages/home/home.less deleted file mode 100644 index 4466927..0000000 --- a/miniprogram/pages/home/home.less +++ /dev/null @@ -1,496 +0,0 @@ -.header { - width: 100%; - position: relative; - height: 450rpx; - margin-bottom: 25rpx; - - .bg { - width: 100%; - height: 450rpx; - } - - .device { - position: absolute; - z-index: 2; - top: 285rpx; - left: 60rpx; - right: 60rpx; - bottom: 0; - display: flex; - align-items: center; - - .device-icon { - width: 90rpx; - height: 90rpx; - overflow: hidden; - border-radius: 24rpx; - margin-right: 24rpx; - } - - .device-info { - flex: 1; - - .device-name { - color: #FFFFFF; - font-size: 32rpx; - font-weight: 700; - line-height: 36rpx; - margin-bottom: 14rpx; - } - - .device-status { - display: flex; - align-items: center; - - .status-dot { - width: 12rpx; - height: 12rpx; - border-radius: 50%; - margin-right: 10rpx; - } - - .dot-on { - background-color: #FFFFFF; - } - - .dot-off { - background-color: rgba(255, 255, 255, 0.5); - } - - .status-text { - color: #FFFFFF; - font-size: 28rpx; - line-height: 28rpx; - } - } - } - - .device-switch { - color: #FFFFFF; - height: 48rpx; - padding: 0 20rpx; - font-size: 24rpx; - font-weight: 500; - line-height: 44rpx; - border-radius: 8rpx; - box-sizing: border-box; - border: 2rpx solid #FFFFFF; - } - } -} - -.scrollViewContent { - width: 100%; - padding: 0 30rpx; - box-sizing: border-box; - - /* ===== 最近测量卡 ===== */ - .weight-card { - width: 100%; - position: relative; - margin-bottom: 24rpx; - padding: 2rpx 2rpx 50rpx; - border-radius: 24rpx; - box-sizing: border-box; - background-color: #FFFFFF; - - /* 卡头(带渐变背景) */ - .card-header { - width: 100%; - height: 88rpx; - padding-left: 30rpx; - padding-right: 10rpx; - box-sizing: border-box; - display: flex; - align-items: center; - margin-bottom: 18rpx; - justify-content: space-between; - background: linear-gradient(180deg, rgba(211, 233, 255, 0.6) 0%, rgba(252, 251, 253, 0.6) 100%); - - .card-header-left { - display: flex; - align-items: center; - - .card-title-bar { - width: 6rpx; - height: 24rpx; - border-radius: 3rpx; - background-color: #1385FA; - margin-right: 14rpx; - } - - .card-title { - color: #252535; - font-size: 30rpx; - font-weight: 700; - line-height: 30rpx; - } - } - - .card-header-right { - display: flex; - align-items: center; - - .link-text { - color: #77849E; - font-size: 24rpx; - line-height: 24rpx; - } - - .arrow { - padding-right: 40rpx; - box-sizing: border-box; - } - } - } - - .card-content { - width: 100%; - padding: 0 30rpx; - box-sizing: border-box; - - /* ===== 最近测量:体重主数据行 ===== */ - .weight-row { - display: flex; - align-items: flex-end; - margin-bottom: 30rpx; - - .weight-main { - flex: 1; - display: flex; - flex-direction: column; - - .weight-num-wrap { - height: 96rpx; - display: flex; - margin-bottom: 20rpx; - align-items: baseline; - - .weight-int { - color: #252535; - height: 96rpx; - font-size: 96rpx; - font-weight: bold; - line-height: 96rpx; - } - - .weight-dec { - color: #252535; - font-size: 56rpx; - font-weight: bold; - line-height: 56rpx; - margin-right: 6rpx; - } - - .weight-unit { - color: #808080; - font-size: 28rpx; - font-weight: bold; - line-height: 28rpx; - } - } - - .weight-date { - color: #B6B6B6; - height: 24rpx; - font-size: 24rpx; - line-height: 24rpx; - } - } - - /* 右侧变化量 */ - .weight-diff { - width: 250rpx; - display: flex; - flex-direction: column; - align-items: flex-start; - - .diff-num-wrap { - display: flex; - align-items: flex-end; - margin-bottom: 14rpx; - - .diff-num { - height: 48rpx; - font-size: 48rpx; - font-weight: bold; - line-height: 48rpx; - margin-right: 6rpx; - } - - .diff-unit { - position: relative; - color: #808080; - height: 28rpx; - font-weight: bold; - font-size: 28rpx; - line-height: 28rpx; - - text { - position: absolute; - left: 0; - bottom: 42rpx; - } - } - - /* 向下箭头(减轻) */ - .arrow-down { - border-left: 10rpx solid transparent; - border-right: 10rpx solid transparent; - border-top: 16rpx solid #2AC79F; - } - - /* 向上箭头(增重) */ - .arrow-up { - border-left: 10rpx solid transparent; - border-right: 10rpx solid transparent; - border-bottom: 16rpx solid #FF4D4F; - } - - .diff-down { - color: #2AC79F; - } - - .diff-up { - color: #FF4D4F; - } - - .diff-neutral { - color: #000000; - } - } - - .diff-label { - color: #B6B6B6; - font-size: 24rpx; - line-height: 28rpx; - margin-top: 8rpx; - } - } - } - - /* 分割线 */ - .card-divider { - width: 100%; - height: 2rpx; - background-color: #EAECF1; - margin-bottom: 35rpx; - } - - /* 身高 / BMI / 标准体重 指标行 */ - .metrics-row { - display: flex; - padding: 0 10rpx; - justify-content: space-between; - - .metric-col { - display: flex; - flex-direction: column; - - .metric-label { - color: #808080; - height: 30rpx; - display: flex; - font-size: 26rpx; - line-height: 30rpx; - margin-bottom: 20rpx; - - .metric-label-text { - color: #808080; - height: 30rpx; - font-size: 26rpx; - line-height: 30rpx; - margin-right: 6rpx; - } - - .metric-label-value { - color: #FFFFFF; - height: 30rpx; - padding: 0 6rpx; - font-size: 18rpx; - line-height: 30rpx; - background-color: #2AC79F; - border-radius: 8rpx; - } - } - - /* BMI 行(标签 + badge 并排) */ - .metric-label-row { - height: 30rpx; - display: flex; - align-items: center; - margin-bottom: 20rpx; - - .metric-label { - margin-bottom: 0; - } - - /* BMI 标准/偏高 badge */ - .bmi-badge { - height: 30rpx; - padding: 0 10rpx; - margin-left: 10rpx; - font-size: 18rpx; - line-height: 30rpx; - border-radius: 8rpx; - color: #FFFFFF; - background-color: #2AC79F; - } - } - - .metric-value-row { - display: flex; - align-items: baseline; - - .metric-value { - color: #252535; - height: 44rpx; - font-size: 44rpx; - font-weight: 700; - line-height: 44rpx; - margin-right: 6rpx; - } - - .metric-unit { - color: #252535; - height: 26rpx; - font-size: 26rpx; - font-weight: bold; - line-height: 26rpx; - } - } - } - } - } - - .card-lianjie { - position: absolute; - right: 24rpx; - width: 32rpx; - height: 84rpx; - bottom: -55rpx; - } - } - - /* ===== 详细数据卡 ===== */ - .detail-card { - width: 100%; - margin-bottom: 24rpx; - padding: 2rpx 2rpx 30rpx; - border-radius: 24rpx; - box-sizing: border-box; - background-color: #FFFFFF; - - /* 卡头(带渐变背景) */ - .card-header { - width: 100%; - height: 88rpx; - padding-left: 30rpx; - padding-right: 50rpx; - box-sizing: border-box; - display: flex; - align-items: center; - justify-content: space-between; - - .card-header-left { - display: flex; - align-items: center; - - .card-title-bar { - width: 6rpx; - height: 24rpx; - border-radius: 3rpx; - background-color: #1385FA; - margin-right: 14rpx; - } - - .card-title { - color: #252535; - font-size: 30rpx; - font-weight: 700; - line-height: 30rpx; - } - } - - .card-header-right { - display: flex; - align-items: center; - - .link-text { - color: #77849E; - height: 24rpx; - font-size: 24rpx; - line-height: 24rpx; - } - } - } - - /* ===== 详细数据:三格 ===== */ - .detail-grid { - display: flex; - padding: 0 30rpx; - justify-content: space-between; - - .detail-item { - width: 195rpx; - padding: 24rpx; - box-sizing: border-box; - border-radius: 16rpx; - background-color: #F3F4F6; - display: flex; - flex-direction: column; - - .detail-label { - color: #808080; - font-size: 24rpx; - line-height: 24rpx; - margin-bottom: 30rpx; - } - - .detail-value-row { - display: flex; - align-items: baseline; - - .detail-value { - color: #252535; - height: 44rpx; - font-size: 44rpx; - font-weight: bold; - line-height: 44rpx; - margin-right: 2rpx; - } - - .detail-unit { - color: #252535; - height: 26rpx; - font-size: 26rpx; - line-height: 26rpx; - } - } - } - } - } - - /* ===== 空态 ===== */ - .empty-state { - width: 100%; - display: flex; - flex-direction: column; - align-items: center; - padding-top: 280rpx; - - .empty-icon { - width: 193rpx; - height: 138rpx; - margin-bottom: 40rpx; - border-radius: 20rpx; - } - - .empty-text { - color: #808080; - height: 30rpx; - font-size: 30rpx; - line-height: 30rpx; - } - } -} diff --git a/miniprogram/pages/home/home.ts b/miniprogram/pages/home/home.ts deleted file mode 100644 index 2173b18..0000000 --- a/miniprogram/pages/home/home.ts +++ /dev/null @@ -1,174 +0,0 @@ -import { get } from '../../utils/request/index' -import { lefuService } from '../../lefu/index' - -/** 设备信息 */ -interface DeviceInfo { - name: string - connected: boolean -} - -/** GET /weighingScale/v3/getMeasureHistory result 结构 */ -interface MeasureHistoryVO { - recordId: string - height: number - weight: number - weightChange: number - bmi: number - bmiType: string - standardWeight: number - fatRate: number - fatValue: number - basalMetabolism: number - examTime: string - sn: string - userInfo: { - realname: string - sex: number - [key: string]: any - } | null -} - -/** 页面渲染用测量记录 */ -interface WeightRecord { - weight: number - weightDecimal: string - date: string - diff: number - height: number - bmi: number - bmiLabel: string - stdWeight: number - bodyFat: number - bodyFatMass: number - bmr: number -} - -/** 缓存中的用户信息 */ -interface StorageUserInfo { - userId: string - [key: string]: any -} - -/** 将体重数字拆成整数 + 小数字符串 */ -const splitWeight = (weight: number): { int: number; decimal: string } => { - const str = weight.toFixed(2) - const [intPart, decPart] = str.split('.') - return { int: Number(intPart), decimal: `.${decPart}` } -} - -Page({ - _otaChecked: false as boolean, - - data: { - hasData: false, - device: { - name: '智能体重秤', - connected: false, - } as DeviceInfo, - record: null as WeightRecord | null, - }, - - onLoad() { - lefuService.onDeviceInfo((info) => { - this.setData({ 'device.name': info.modelNumber ?? '智能体重秤' }) - }) - lefuService.onDeviceConnect(() => { - this.setData({ 'device.connected': true }) - this._checkOta() - }) - lefuService.onDisconnected(() => { - this.setData({ 'device.connected': false }) - }) - }, - - onShow() { - // 每次进入 tab 同步最新设备状态 - const connected = lefuService.isConnected - this.setData({ - 'device.connected': connected, - 'device.name': lefuService.deviceInfo?.modelNumber ?? '智能体重秤', - }) - // 进来时设备已连接(如热启动/切 tab 回来),onDeviceConnect 不会再触发,需在此补一次检测 - if (connected && !this._otaChecked) { - this._checkOta() - } - // 断连时自动扫描重连,重连成功后 onDeviceConnect 回调会触发 OTA 检测 - if (!connected) { - lefuService.autoConnect() - } - this.loadData() - }, - - onUnload() { - lefuService.onDeviceConnect(() => {}) - lefuService.onDisconnected(() => {}) - lefuService.onDeviceInfo(() => {}) - }, - - onShareAppMessage(): WechatMiniprogram.Page.ICustomShareContent { - return { - title: '智能蓝牙秤', - path: '/pages/home/home', - imageUrl: '/images/share/share.jpg', - } - }, - - /** 拉取最新一次测量记录 */ - loadData() { - const raw = wx.getStorageSync('userInfo') as StorageUserInfo | null - if (!raw?.userId) return - - get('weighingScale/v3/getMeasureHistory', { - userId: raw.userId, - }, { loading: false }) - .then((res: any) => { - const d: MeasureHistoryVO = res.result - if (!d) { - this.setData({ hasData: false, record: null }) - return - } - const { int, decimal } = splitWeight(d.weight ?? 0) - const record: WeightRecord = { - weight: int, - weightDecimal: decimal, - date: d.examTime ?? '', - diff: d.weightChange ?? 0, - height: d.height ?? 0, - bmi: d.bmi ?? 0, - bmiLabel: d.bmiType ?? '', - stdWeight: d.standardWeight ?? 0, - bodyFat: d.fatRate ?? 0, - bodyFatMass: d.fatValue ?? 0, - bmr: d.basalMetabolism ?? 0, - } - this.setData({ hasData: true, record }) - }) - }, - - /** OTA 固件检测,连接成功后自动调用,有新版本直接升级,整个页面生命周期只触发一次 */ - _checkOta() { - if (this._otaChecked) return - this._otaChecked = true - lefuService.checkAndUpgrade() - .then(() => { - wx.showLoading({ title: '加载中...', mask: true }) - setTimeout(() => { - wx.hideLoading() - // 升级完成后设备重启,旧连接与订阅失效,重新扫描连接 - this.setData({ 'device.connected': false }) - lefuService.autoConnect() - }, 15000) - }) - .catch(() => {}) - }, - - /** 跳转历史数据页 */ - onTapHistory() { - wx.switchTab({ url: '/pages/data/data' }) - }, - - /** 跳转设备列表页 */ - onTapSwitchDevice() { - wx.switchTab({ url: '/pages/equipment/equipment' }) - }, -}) diff --git a/miniprogram/pages/home/home.wxml b/miniprogram/pages/home/home.wxml deleted file mode 100644 index 31b69b6..0000000 --- a/miniprogram/pages/home/home.wxml +++ /dev/null @@ -1,148 +0,0 @@ - - - - - - - - - - {{ device.name }} - - - {{ device.connected ? '设备已连接' : '未连接' }} - - - 切换设备 - - - - - - - - - - - - - - 最近测量 - - - 查看历史 - - - - - - - - - - {{ record.weight }} - {{ record.weightDecimal }} - kg - - {{ record.date }} - - - - {{ record.diff < 0 ? -record.diff : record.diff }} - - kg - - - - {{ record.diff < 0 ? '体重较上次减轻' : (record.diff > 0 ? '体重较上次增加' : '与上次持平') }} - - - - - - - - - - - 身高 - - - {{ record.height }} - cm - - - - - BMI - {{ record.bmiLabel }} - - - {{ record.bmi }} - - - - - 标准体重 - - - {{ record.stdWeight }} - kg - - - - - - - - - - - - - - - - - 详细数据 - - - - - - - 体脂率 - - {{ record.bodyFat }} - % - - - - 体脂肪 - - {{ record.bodyFatMass }} - kg - - - - 基础代谢 - - {{ record.bmr }} - kcal - - - - - - - - - - - - - 暂无称重数据 - - - - diff --git a/miniprogram/pages/login/login.ts b/miniprogram/pages/login/login.ts index 32e141b..6a1fe09 100644 --- a/miniprogram/pages/login/login.ts +++ b/miniprogram/pages/login/login.ts @@ -1,43 +1,6 @@ -import { post } from '../../utils/request/index' +import { checkOpenIdLogin } from '../../api/index' -/** 通过 OpenID 登录的用户信息 */ -interface UserInfo { - id: string - 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 | null - thirdId: string - realname: string - bmi: number - createBy: string - createTime: string - updateBy: string | null - updateTime: string | null - delFlag: number - sn: string | null - workNo: string | null - remark: string | null - relationType: string | null - sex_dictText: string - connectDeviceInfo: string -} - -/** 登录接口 result 结构 */ -interface CheckOpenIdLoginResult { - user: UserInfo | null - token: string -} +const app = getApp() Page({ data: { @@ -47,7 +10,7 @@ Page({ onShareAppMessage(): WechatMiniprogram.Page.ICustomShareContent { return { title: '智能蓝牙秤', - path: '/pages/home/home', + path: '/pages/login/login', imageUrl: '/images/share/share.jpg', } }, @@ -57,30 +20,25 @@ Page({ if (this.data.loading) return this.setData({ loading: true }) - wx.login({ - success: ({ code }) => { - post('weighingScale/v2/checkOpenIdLogin', { code }) - .then(({ result }) => { - wx.setStorageSync('token', result.token) - wx.setStorageSync('userInfo', result.user) - - /* 上次连接设备不为空对象则直接进首页 */ - const connectDeviceInfo = result.user?.connectDeviceInfo - if (connectDeviceInfo && connectDeviceInfo !== '{}') { - wx.setStorageSync('connectDeviceInfo', JSON.parse(connectDeviceInfo)) - wx.reLaunch({ url: '/pages/home/home' }) - } else { - wx.reLaunch({ url: '/pages/connectedDevice/connectedDevice' }) - } - }) - .catch(() => { - this.setData({ loading: false }) - }) - }, - fail: () => { - wx.showToast({ title: '获取登录凭证失败', icon: 'none' }) + // 获取微信临时 code → 调用 OpenID 登录接口 + app.getWxLoginCode() + .then((code) => checkOpenIdLogin(code)) + .then((res) => { + const { token, user } = res.data + // 持久化登录凭证和用户信息 + wx.setStorageSync('token', token) + if (user) { + wx.setStorageSync('userInfo', user) + wx.reLaunch({ url: '/pages/home/home' }) + } else { + wx.reLaunch({ url: '/pages/supplementPersonal/supplementPersonal' }) + } + }) + .catch((err) => { + wx.showToast({ title: err.msg || '登录失败,请重试', icon: 'none' }) + }) + .finally(() => { this.setData({ loading: false }) - }, - }) + }) }, }) diff --git a/miniprogram/pages/measurementReport/measurementReport.json b/miniprogram/pages/measurementReport/measurementReport.json deleted file mode 100644 index 8344175..0000000 --- a/miniprogram/pages/measurementReport/measurementReport.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "navigationBarTitleText": "测量报告", - "usingComponents": {} -} diff --git a/miniprogram/pages/measurementReport/measurementReport.less b/miniprogram/pages/measurementReport/measurementReport.less deleted file mode 100644 index ef310bb..0000000 --- a/miniprogram/pages/measurementReport/measurementReport.less +++ /dev/null @@ -1,421 +0,0 @@ -.scrollViewContent { - width: 100%; - padding: 24rpx 30rpx; - box-sizing: border-box; - - /* ===== 测量信息卡片 ===== */ - .info-card { - width: 100%; - display: flex; - align-items: center; - padding: 36rpx 30rpx; - margin-bottom: 24rpx; - border-radius: 24rpx; - box-sizing: border-box; - //background-color: #FFFFFF; - background: linear-gradient(180deg, #D3E9FF 0%, #FCFBFD 100%); - - .info-avatar { - width: 90rpx; - height: 90rpx; - margin-right: 24rpx; - border-radius: 18rpx; - color: #FFFFFF; - font-size: 36rpx; - font-weight: bold; - line-height: 90rpx; - text-align: center; - background-color: #1385FA; - } - - .info-meta { - flex: 1; - - .info-name { - color: #333333; - height: 32rpx; - font-size: 32rpx; - font-weight: bold; - line-height: 32rpx; - margin-bottom: 20rpx; - } - - .info-time { - color: #77849E; - height: 26rpx; - font-size: 26rpx; - line-height: 26rpx; - } - } - } - - /* ===== 最近测量卡 ===== */ - .weight-card { - width: 100%; - position: relative; - margin-bottom: 24rpx; - padding: 2rpx 2rpx 50rpx; - border-radius: 24rpx; - box-sizing: border-box; - background-color: #FFFFFF; - - /* 卡头(带渐变背景) */ - .card-header { - width: 100%; - height: 88rpx; - padding-left: 30rpx; - padding-right: 10rpx; - box-sizing: border-box; - display: flex; - align-items: center; - margin-bottom: 18rpx; - justify-content: space-between; - - .card-header-left { - display: flex; - align-items: center; - - .card-title-bar { - width: 6rpx; - height: 24rpx; - border-radius: 3rpx; - background-color: #1385FA; - margin-right: 14rpx; - } - - .card-title { - color: #252535; - font-size: 30rpx; - font-weight: 700; - line-height: 30rpx; - } - } - } - - .card-content { - width: 100%; - padding: 0 30rpx; - box-sizing: border-box; - - /* ===== 最近测量:体重主数据行 ===== */ - .weight-row { - display: flex; - align-items: flex-end; - margin-bottom: 30rpx; - - .weight-main { - flex: 1; - display: flex; - flex-direction: column; - - .weight-num-wrap { - height: 96rpx; - display: flex; - margin-bottom: 20rpx; - align-items: baseline; - - .weight-int { - color: #252535; - height: 96rpx; - font-size: 96rpx; - font-weight: bold; - line-height: 96rpx; - } - - .weight-dec { - color: #252535; - font-size: 56rpx; - font-weight: bold; - line-height: 56rpx; - margin-right: 6rpx; - } - - .weight-unit { - color: #808080; - font-size: 28rpx; - font-weight: bold; - line-height: 28rpx; - } - } - - .weight-date { - color: #B6B6B6; - height: 24rpx; - font-size: 24rpx; - line-height: 24rpx; - } - } - - /* 右侧变化量 */ - .weight-diff { - width: 250rpx; - display: flex; - flex-direction: column; - align-items: flex-start; - - .diff-num-wrap { - display: flex; - align-items: flex-end; - margin-bottom: 14rpx; - - .diff-num { - height: 48rpx; - font-size: 48rpx; - font-weight: bold; - line-height: 48rpx; - margin-right: 6rpx; - } - - .diff-unit { - position: relative; - color: #808080; - height: 28rpx; - font-weight: bold; - font-size: 28rpx; - line-height: 28rpx; - - text { - position: absolute; - left: 0; - bottom: 42rpx; - } - } - - /* 向下箭头(减轻) */ - .arrow-down { - border-left: 10rpx solid transparent; - border-right: 10rpx solid transparent; - border-top: 16rpx solid #2AC79F; - } - - /* 向上箭头(增重) */ - .arrow-up { - border-left: 10rpx solid transparent; - border-right: 10rpx solid transparent; - border-bottom: 16rpx solid #FF4D4F; - } - - .diff-down { - color: #2AC79F; - } - - .diff-up { - color: #FF4D4F; - } - - .diff-neutral { - color: #000000; - } - } - - .diff-label { - color: #B6B6B6; - font-size: 24rpx; - line-height: 28rpx; - margin-top: 8rpx; - } - } - } - - /* 分割线 */ - .card-divider { - width: 100%; - height: 2rpx; - background-color: #EAECF1; - margin-bottom: 35rpx; - } - - /* 身高 / BMI / 标准体重 指标行 */ - .metrics-row { - display: flex; - padding: 0 10rpx; - justify-content: space-between; - - .metric-col { - display: flex; - flex-direction: column; - - .metric-label { - color: #808080; - height: 30rpx; - display: flex; - font-size: 26rpx; - line-height: 30rpx; - margin-bottom: 20rpx; - - .metric-label-text { - color: #808080; - height: 30rpx; - font-size: 26rpx; - line-height: 30rpx; - margin-right: 6rpx; - } - - .metric-label-value { - color: #FFFFFF; - height: 30rpx; - padding: 0 6rpx; - font-size: 18rpx; - line-height: 30rpx; - background-color: #2AC79F; - border-radius: 8rpx; - } - } - - /* BMI 行(标签 + badge 并排) */ - .metric-label-row { - height: 30rpx; - display: flex; - align-items: center; - margin-bottom: 20rpx; - - .metric-label { - margin-bottom: 0; - } - - /* BMI 标准/偏高 badge */ - .bmi-badge { - height: 30rpx; - padding: 0 10rpx; - margin-left: 10rpx; - font-size: 18rpx; - line-height: 30rpx; - border-radius: 8rpx; - color: #FFFFFF; - background-color: #2AC79F; - } - } - - .metric-value-row { - display: flex; - align-items: baseline; - - .metric-value { - color: #252535; - height: 44rpx; - font-size: 44rpx; - font-weight: 700; - line-height: 44rpx; - margin-right: 6rpx; - } - - .metric-unit { - color: #252535; - height: 26rpx; - font-size: 26rpx; - font-weight: bold; - line-height: 26rpx; - } - } - } - } - } - - .card-lianjie { - position: absolute; - right: 24rpx; - width: 32rpx; - height: 84rpx; - bottom: -55rpx; - } - } - - /* ===== 详细数据卡 ===== */ - .detail-card { - width: 100%; - margin-bottom: 24rpx; - padding: 2rpx 2rpx 30rpx; - border-radius: 24rpx; - box-sizing: border-box; - background-color: #FFFFFF; - - /* 卡头(带渐变背景) */ - .card-header { - width: 100%; - height: 88rpx; - padding-left: 30rpx; - padding-right: 50rpx; - box-sizing: border-box; - display: flex; - align-items: center; - justify-content: space-between; - - .card-header-left { - display: flex; - align-items: center; - - .card-title-bar { - width: 6rpx; - height: 24rpx; - border-radius: 3rpx; - background-color: #1385FA; - margin-right: 14rpx; - } - - .card-title { - color: #252535; - font-size: 30rpx; - font-weight: 700; - line-height: 30rpx; - } - } - - .card-header-right { - display: flex; - align-items: center; - - .link-text { - color: #77849E; - height: 24rpx; - font-size: 24rpx; - line-height: 24rpx; - } - } - } - - /* ===== 详细数据:三格 ===== */ - .detail-grid { - display: flex; - padding: 0 30rpx; - justify-content: space-between; - - .detail-item { - width: 195rpx; - padding: 24rpx; - box-sizing: border-box; - border-radius: 16rpx; - background-color: #F3F4F6; - display: flex; - flex-direction: column; - - .detail-label { - color: #808080; - font-size: 24rpx; - line-height: 24rpx; - margin-bottom: 30rpx; - } - - .detail-value-row { - display: flex; - align-items: baseline; - - .detail-value { - color: #252535; - height: 44rpx; - font-size: 44rpx; - font-weight: bold; - line-height: 44rpx; - margin-right: 2rpx; - } - - .detail-unit { - color: #252535; - height: 26rpx; - font-size: 26rpx; - line-height: 26rpx; - } - } - } - } - } -} diff --git a/miniprogram/pages/measurementReport/measurementReport.ts b/miniprogram/pages/measurementReport/measurementReport.ts deleted file mode 100644 index 4518094..0000000 --- a/miniprogram/pages/measurementReport/measurementReport.ts +++ /dev/null @@ -1,122 +0,0 @@ -import { get } from '../../utils/request/index' - -/** GET /weighingScale/v3/getMeasureHistory result 结构 */ -interface MeasureHistoryVO { - recordId: string - height: number - weight: number - weightChange: number - bmi: number - bmiType: string - standardWeight: number - fatRate: number - fatValue: number - basalMetabolism: number - examTime: string - sn: string - userInfo: { - realname: string - sex: number - [key: string]: any - } | null -} - -/** 页面渲染用测量记录 */ -interface WeightRecord { - /** 体重整数部分 */ - weight: number - /** 体重小数部分,含小数点,如 ".20" */ - weightDecimal: string - /** 测量时间 */ - date: string - /** 与上次体重差值(负=减轻,正=增重) */ - diff: number - /** 身高(cm) */ - height: number - /** BMI 值 */ - bmi: number - /** BMI 评级文本(来自接口 bmiType) */ - bmiLabel: string - /** 标准体重(kg) */ - stdWeight: number - /** 体脂率(%) */ - bodyFat: number - /** 体脂肪量(kg) */ - bodyFatMass: number - /** 基础代谢(kcal) */ - bmr: number -} - -/** 空态默认值,避免页面展示 null */ -const EMPTY_RECORD: WeightRecord = { - weight: 0, - weightDecimal: '', - date: '', - diff: 0, - height: 0, - bmi: 0, - bmiLabel: '', - stdWeight: 0, - bodyFat: 0, - bodyFatMass: 0, - bmr: 0, -} - -/** 将体重数字拆成整数 + 小数字符串 */ -const splitWeight = (weight: number): { int: number; decimal: string } => { - const str = weight.toFixed(2) - const [intPart, decPart] = str.split('.') - return { int: Number(intPart), decimal: `.${decPart}` } -} - -/** - * 测量报告页 - * URL 参数:recordId(记录 ID)、userId(用户 ID) - */ -Page({ - data: { - record: EMPTY_RECORD as WeightRecord, - /** 被查看用户的姓名(来自接口 userInfo.realname) */ - realname: '', - }, - - onLoad(options: Record) { - const { recordId, userId } = options - if (!recordId || !userId) return - - get('weighingScale/v3/getMeasureHistory', { - userId, - recordId, - }, { loading: true }) - .then((res: any) => { - const d: MeasureHistoryVO = res.result - if (!d) return - - const { int, decimal } = splitWeight(d.weight ?? 0) - const record: WeightRecord = { - weight: int, - weightDecimal: decimal, - date: d.examTime ?? '', - diff: d.weightChange ?? 0, - height: d.height ?? 0, - bmi: d.bmi ?? 0, - bmiLabel: d.bmiType ?? '', - stdWeight: d.standardWeight ?? 0, - bodyFat: d.fatRate ?? 0, - bodyFatMass: d.fatValue ?? 0, - bmr: d.basalMetabolism ?? 0, - } - this.setData({ - record, - realname: d.userInfo?.realname ?? '', - }) - }) - }, - - onShareAppMessage(): WechatMiniprogram.Page.ICustomShareContent { - return { - title: `${this.data.realname}的测量报告`, - path: '/pages/measurementReport/measurementReport', - } - }, -}) diff --git a/miniprogram/pages/measurementReport/measurementReport.wxml b/miniprogram/pages/measurementReport/measurementReport.wxml deleted file mode 100644 index d0e20db..0000000 --- a/miniprogram/pages/measurementReport/measurementReport.wxml +++ /dev/null @@ -1,121 +0,0 @@ - - - - - {{ realname[0] }} - - {{ realname }} - {{ record.date }} - - - - - - - - - - 体重 - - - - - - - - - {{ record.weight }} - {{ record.weightDecimal }} - kg - - {{ record.date }} - - - - {{ record.diff < 0 ? -record.diff : record.diff }} - - kg - - - - {{ record.diff < 0 ? '体重较上次减轻' : (record.diff > 0 ? '体重较上次增加' : '与上次持平') }} - - - - - - - - - - - 身高 - - - {{ record.height }} - cm - - - - - BMI - {{ record.bmiLabel }} - - - {{ record.bmi }} - - - - - 标准体重 - - - {{ record.stdWeight }} - kg - - - - - - - - - - - - - - - - - 详细数据 - - - - - - - 体脂率 - - {{ record.bodyFat }} - % - - - - 体脂肪 - - {{ record.bodyFatMass }} - kg - - - - 基础代谢 - - {{ record.bmr }} - kcal - - - - - - diff --git a/miniprogram/pages/my/my.json b/miniprogram/pages/my/my.json deleted file mode 100644 index e90e996..0000000 --- a/miniprogram/pages/my/my.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "usingComponents": {}, - "navigationStyle": "custom" -} diff --git a/miniprogram/pages/my/my.less b/miniprogram/pages/my/my.less deleted file mode 100644 index 3e83bb6..0000000 --- a/miniprogram/pages/my/my.less +++ /dev/null @@ -1,107 +0,0 @@ -.header { - width: 100%; - height: 320rpx; - margin-bottom: 26rpx; - - .bg { - width: 100%; - height: 100%; - } - - .user { - position: absolute; - top: 185rpx; - left: 40rpx; - right: 40rpx; - width: 100%; - display: flex; - box-sizing: border-box; - - .user-avatar { - width: 120rpx; - height: 120rpx; - margin-right: 30rpx; - border-radius: 24rpx; - color: #FFFFFF; - font-size: 48rpx; - font-weight: bold; - line-height: 110rpx; - text-align: center; - box-sizing: border-box; - border: 5rpx solid #ffffff; - background-color: #1385FA; - } - - .user-info { - flex: 1; - display: flex; - flex-flow: column; - justify-content: center; - overflow: hidden; - - .user-name { - color: #252536; - height: 40rpx; - font-weight: bold; - font-size: 40rpx; - line-height: 40rpx; - margin-bottom: 22rpx; - } - - .user-meta { - height: 28rpx; - display: flex; - align-items: center; - - .meta-text { - color: #77849E; - height: 28rpx; - font-size: 28rpx; - line-height: 28rpx; - } - - .meta-divider { - color: #B6B6B6; - height: 28rpx; - font-size: 28rpx; - line-height: 28rpx; - margin: 0 12rpx; - } - } - } - } -} - -.list { - width: 100%; - padding: 0 30rpx; - box-sizing: border-box; - - .list-item { - width: 100%; - height: 110rpx; - display: flex; - padding-left: 30rpx; - padding-right: 20rpx; - box-sizing: border-box; - align-items: center; - flex-wrap: nowrap; - border-radius: 16rpx; - background-color: #ffffff; - - >view:nth-of-type(1) { - width: 38rpx; - height: 38rpx; - margin-right: 22rpx; - } - - >view:nth-of-type(2) { - flex: 1; - height: 30rpx; - color: #394356; - font-weight: bold; - font-size: 30rpx; - line-height: 30rpx; - } - } -} \ No newline at end of file diff --git a/miniprogram/pages/my/my.ts b/miniprogram/pages/my/my.ts deleted file mode 100644 index ba424a9..0000000 --- a/miniprogram/pages/my/my.ts +++ /dev/null @@ -1,54 +0,0 @@ -/** 缓存中的用户信息 */ -interface StorageUserInfo { - userId: string - realname: string - sex: number - height: number - birthday: string - [key: string]: any -} - -/** 页面展示用用户信息 */ -interface PageUserInfo { - realname: string - sex_dictText: '男' | '女' | '' - age: number - height: number -} - -/** 从生日字符串(YYYY-MM-DD)计算周岁 */ -const calcAge = (birthday: string): number => { - if (!birthday) return 0 - const [year, month, day] = birthday.split('-').map(Number) - if (!year || !month || !day) return 0 - const now = new Date() - let age = now.getFullYear() - year - if (now.getMonth() + 1 < month || (now.getMonth() + 1 === month && now.getDate() < day)) age -= 1 - return Math.max(0, age) -} - -Page({ - data: { - userInfo: null as PageUserInfo | null, - }, - - onShow() { - const raw = wx.getStorageSync('userInfo') as StorageUserInfo | null - if (!raw?.userId) return - const userInfo: PageUserInfo = { - realname: raw.realname ?? '', - sex_dictText: raw.sex === 1 ? '男' : raw.sex === 2 ? '女' : '', - age: calcAge(raw.birthday), - height: raw.height ?? 0, - } - this.setData({ userInfo }) - }, - - onShareAppMessage(): WechatMiniprogram.Page.ICustomShareContent { - return { - title: '智能蓝牙秤', - path: '/pages/home/home', - imageUrl: '/images/share/share.jpg', - } - }, -}) diff --git a/miniprogram/pages/my/my.wxml b/miniprogram/pages/my/my.wxml deleted file mode 100644 index 49d5962..0000000 --- a/miniprogram/pages/my/my.wxml +++ /dev/null @@ -1,29 +0,0 @@ - - - - - - - {{ userInfo.realname[0] }} - - - - - - - - - - 关于我们 - - - \ No newline at end of file diff --git a/miniprogram/pages/supplementPersonal/supplementPersonal.json b/miniprogram/pages/supplementPersonal/supplementPersonal.json index 1e48fa5..cbb6109 100644 --- a/miniprogram/pages/supplementPersonal/supplementPersonal.json +++ b/miniprogram/pages/supplementPersonal/supplementPersonal.json @@ -1,4 +1,4 @@ { - "usingComponents": {}, - "navigationBarTitleText": "完善个人信息" + "usingComponents": {}, + "navigationBarTitleText": "完善个人信息" } diff --git a/miniprogram/pages/supplementPersonal/supplementPersonal.ts b/miniprogram/pages/supplementPersonal/supplementPersonal.ts index 810de53..856ff1e 100644 --- a/miniprogram/pages/supplementPersonal/supplementPersonal.ts +++ b/miniprogram/pages/supplementPersonal/supplementPersonal.ts @@ -1,328 +1,88 @@ -import { get, put } from '../../utils/request/index' -import { lefuService } from '../../lefu/index' -import type { DeviceMember } from '../../lefu/index' +import { getUserInfoByIdCard, updateUser, type UserInfo } from '../../api/index' +import { parseIdCard } from '../../utils/common' -/** 表单数据模型 */ -interface PersonalForm { - /** 姓名 */ - name: string - /** 身份证号 */ - idCard: string - /** 性别(由身份证解析,只读) */ - gender: string - /** 年龄(由身份证解析,只读) */ - age: string - /** 身高(cm) */ - height: string - /** 体重(kg) */ - weight: string -} - -/** - * 身份证解析结果 - * sex / birthday 用于接口提交,gender / age 用于页面展示 - */ -interface IdCardParsed { - gender: string // '男' | '女' | '' - age: string // 周岁字符串,解析失败为 '' - sex: number // 1=男 2=女,0=解析失败 - birthday: string // YYYY-MM-DD,解析失败为 '' -} - -/** 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 -} - -/** PUT /weighingScale/v3/update/user 请求体 */ -type UpdateUserPayload = UserInfoFromServer & { - /** storage 中取出的 connectDeviceInfo 对象,JSON.stringify 后传入 */ - connectDeviceInfo: string -} - -/** - * 完善个人信息页 - * 收集姓名、身份证号、身高、体重,身份证满 18 位且姓名非空时自动调接口校验并回填性别年龄 - */ Page({ data: { - form: { - name: '', - idCard: '', - gender: '', - age: '', - height: '', - weight: '' - } as PersonalForm, - /** 正在请求身份证查询接口 */ - querying: false, - /** 接口返回的用户信息,提交时透传给更新接口 */ - serverResult: null as UserInfoFromServer | null, - /** 表单是否只读(从添加设备流程过来时为 true) */ - readonly: false + name: '', + idCard: '', + gender: '', + age: '', + height: '', + weight: '', + serverResult: null as UserInfo | null, }, - onLoad() { - // 已有连接设备 → 从缓存 userInfo 回填表单并禁用编辑 - const connectDeviceInfo = wx.getStorageSync('connectDeviceInfo') - if (!connectDeviceInfo || JSON.stringify(connectDeviceInfo) === '{}') return - - const userInfo = wx.getStorageSync('userInfo') as Record | null - if (!userInfo?.userId) return - - const parsed = this.parseIdCard(userInfo.idCard ?? '') - this.setData({ - readonly: true, - 'form.name': userInfo.realname ?? '', - 'form.idCard': userInfo.idCard ?? '', - 'form.gender': parsed.gender || userInfo.sex_dictText || '', - 'form.age': parsed.age || '', - 'form.height': userInfo.height != null ? String(userInfo.height) : '', - 'form.weight': userInfo.weight != null ? String(userInfo.weight) : '', - }) - }, - - /** - * 姓名输入 - * 如果身份证已合法则联动触发服务端查询 - */ onNameInput(e: WechatMiniprogram.Input) { - const name = e.detail.value - this.setData({ 'form.name': name }) - if (!name.trim()) { - this.setData({ serverResult: null }) - return - } - if (this.isIdCardValid(this.data.form.idCard)) { - this.fetchUserInfoByIdCard(name.trim(), this.data.form.idCard) - } + this.setData({ name: e.detail.value.trim() }) + }, + + /* 失焦时:清空查询结果 → 重新校验身份证,变更即失效 */ + onNameBlur() { + if (!this.data.name) return + this.setData({ serverResult: null }) + this.validateIdCard() }, - /** - * 身份证号输入 - * 本地解析性别年龄;满足合法格式且姓名非空时触发服务端查询 - */ 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 }) - return - } - if (this.data.form.name.trim()) { - this.fetchUserInfoByIdCard(this.data.form.name.trim(), idCard) - } + this.setData({ idCard, serverResult: null }) + if (idCard.length === 18) this.validateIdCard() + }, + + /* 独立的身份证合法性校验,合法且姓名已填则请求接口 */ + validateIdCard() { + const { idCard, name } = this.data + const { gender, age } = parseIdCard(idCard) + this.setData({ gender, age }) + if (gender && name) this.fetchUser() + }, + + fetchUser() { + getUserInfoByIdCard({ + sn: '', + idCard: this.data.idCard, + realname: this.data.name, + }).then((res) => { + if (res.data) { + const { gender, age } = parseIdCard(res.data.idCard) + this.setData({ serverResult: res.data, gender, age }) + } + }).catch(() => {}) }, - /** 身高输入 */ onHeightInput(e: WechatMiniprogram.Input) { - this.setData({ 'form.height': e.detail.value }) + this.setData({ height: e.detail.value }) }, - /** 体重输入 */ onWeightInput(e: WechatMiniprogram.Input) { - this.setData({ 'form.weight': e.detail.value }) + this.setData({ weight: e.detail.value }) }, - /** - * 校验身份证格式:18 位,前 17 位数字,末位数字或 X - * @param idCard 待校验的身份证号 - */ - isIdCardValid(idCard: string): boolean { - return /^\d{17}[\dX]$/.test(idCard) - }, - - /** - * 解析身份证,返回性别、年龄、sex 数值、出生日期 - * @param idCard 18 位身份证号 - */ - parseIdCard(idCard: string): IdCardParsed { - const empty: IdCardParsed = { gender: '', age: '', sex: 0, birthday: '' } - if (!this.isIdCardValid(idCard)) return empty - - // 性别:第 17 位奇数为男,偶数为女 - const genderCode = parseInt(idCard.charAt(16), 10) - const sex = genderCode % 2 === 1 ? 1 : 2 - const gender = sex === 1 ? '男' : '女' - - // 出生日期:第 7-14 位 - 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 - const nowMonth = now.getMonth() + 1 - const nowDay = now.getDate() - if (nowMonth < month || (nowMonth === month && nowDay < day)) age -= 1 - if (age < 0 || age > 150) return empty - - const mm = String(month).padStart(2, '0') - const dd = String(day).padStart(2, '0') - const birthday = `${year}-${mm}-${dd}` - - return { gender, age: String(age), sex, birthday } - }, - - /** - * 调用服务端接口校验身份证与姓名,成功后用接口返回的身份证重新计算性别年龄 - * @param name 用户填写的姓名 - * @param idCard 用户填写的身份证号 - */ - fetchUserInfoByIdCard(name: string, idCard: string) { - if (this.data.querying) return - this.setData({ querying: true, serverResult: null }) - - get('weighingScale/v3/getUserInfoByIdcard', { - sn: lefuService.deviceInfo?.serialNumber ?? '', - idCard, - realname: name - }, { loading: false }) - .then(res => { - const serverResult = res.result - // 用接口返回的身份证重新计算性别和年龄(以服务端为准) - const parsed = this.parseIdCard(serverResult.idCard) - this.setData({ - serverResult, - 'form.gender': parsed.gender, - 'form.age': parsed.age - }) - }) - .catch(() => { - // 查询失败静默处理,保留本地解析结果 - }) - .finally(() => { - this.setData({ querying: false }) - }) - }, - - /** - * 提交表单 - * 校验全部必填项 → 构建 payload(透传 serverResult + 用户手动输入)→ PUT 更新接口 - */ onSubmit() { - const { name, idCard, gender, age, height, weight } = this.data.form + const { name, idCard, gender, height, weight, serverResult } = this.data 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 (!name) return wx.showToast({ title: '请填写姓名', icon: 'none' }) + if (!gender) return wx.showToast({ title: '请填写正确的身份证号', icon: 'none' }) + if (!height || isNaN(heightNum) || heightNum <= 0) return wx.showToast({ title: '请填写身高', icon: 'none' }) + if (!serverResult) return wx.showToast({ title: '请先验证姓名与身份证号', icon: 'none' }) - const parsed = this.parseIdCard(idCard) + const parsed = parseIdCard(idCard) - // connectDeviceInfo 存储时已 JSON.parse 为对象,提交时还原为 JSON 字符串 - const connectDeviceInfoRaw = wx.getStorageSync('connectDeviceInfo') - const connectDeviceInfo: string = connectDeviceInfoRaw ? JSON.stringify(connectDeviceInfoRaw) : '' - - let payload: UpdateUserPayload - - if (this.data.readonly) { - // readonly 模式:从缓存 userInfo 构建 payload - const userInfo = wx.getStorageSync('userInfo') as Record - const { connectDeviceInfo: _c, ...rest } = userInfo - payload = { - ...rest, - idCard, - realname: name.trim(), - height: heightNum, - weight: weightNum, - sex: parsed.sex, - birthday: parsed.birthday, - sn: lefuService.deviceInfo?.serialNumber ?? '', - scaleDeviceId: lefuService.deviceInfo?.serialNumber ?? '', - connectDeviceInfo, - } as UpdateUserPayload - } else { - // 正常模式:需要 serverResult - if (!this.data.serverResult) { - wx.showToast({ title: '身份信息未验证,请检查姓名与身份证号', icon: 'none' }) - return - } - const serverResult = this.data.serverResult as UserInfoFromServer - payload = { - ...serverResult, - idCard, - realname: name.trim(), - height: heightNum, - weight: weightNum, - sex: parsed.sex, - birthday: parsed.birthday, - sn: lefuService.deviceInfo?.serialNumber ?? '', - scaleDeviceId: lefuService.deviceInfo?.serialNumber ?? '', - connectDeviceInfo, - } - } - - put('weighingScale/v3/update/user', payload) - .then(res => { - const { connectDeviceInfo: _, ...userInfo } = res.result - wx.setStorageSync('userInfo', userInfo) - - const mainUser: DeviceMember = { - id: payload.userId, - name: payload.realname, - gender: payload.sex === 1 ? 1 : 0, - age: parseInt(parsed.age, 10) || 0, - height: payload.height, - isSelf: true, - } - - wx.showLoading({ title: '加载中...', mask: true }) - lefuService.syncMembersToDevice([mainUser]) - .then(() => { - wx.hideLoading() - wx.switchTab({ url: '/pages/home/home' }) - }) - .catch((err: Error) => { - wx.hideLoading() - wx.showToast({ title: err.message || '主用户下发失败', icon: 'none' }) - }) - }) - } + updateUser({ + ...serverResult, + idCard, + realname: name, + height: heightNum, + weight: weightNum || 0, + sex: parsed.sex, + birthday: parsed.birthday, + }).then((res) => { + wx.setStorageSync('userInfo', res.data) + wx.showToast({ title: '保存成功', icon: 'success' }) + }).catch(() => { + wx.showToast({ title: '保存失败,请重试', icon: 'none' }) + }) + }, }) diff --git a/miniprogram/pages/supplementPersonal/supplementPersonal.wxml b/miniprogram/pages/supplementPersonal/supplementPersonal.wxml index aa2ff4c..b8fb934 100644 --- a/miniprogram/pages/supplementPersonal/supplementPersonal.wxml +++ b/miniprogram/pages/supplementPersonal/supplementPersonal.wxml @@ -1,129 +1,106 @@ + + ! + 请完整填写信息,用于精准计算体脂数据 + - - - ! - 请完整填写信息,用于精准计算体脂数据 - + + + + + 姓名 + * + + + + + - - - + + + 身份证号 + * + + + + + - - - - 姓名 - * - - - - - + + + 性别 + + + + {{ gender }} + 自动生成,无需填写 + + + - - - - 身份证号 - * - - - - - + + + 年龄 + + + + {{ age }} + 自动生成,无需填写 + + + + - - - - 性别 - - - - - {{ form.gender }} - - - 自动生成,无需填写 - - - - + + + 身高 + + + + + cm + + + - - - - 年龄 - - - - - {{ form.age }} - - - 自动生成,无需填写 - - - - - + + + 体重 + + + + + kg + + + + + - - - - * - 身高 - - - - - cm - - - - - - - - 体重 - - - - - kg - - - - - - - - 完成并进入首页 + 完成 diff --git a/miniprogram/styles/index.less b/miniprogram/styles/index.less deleted file mode 100644 index a997abc..0000000 --- a/miniprogram/styles/index.less +++ /dev/null @@ -1,7 +0,0 @@ -/* ================================================================ - * 全局主题变量聚合入口 - * ---------------------------------------------------------------- - * 由 app.less 引入,Less 编译期合并到 app.wxss,无运行时开销。 - * ================================================================ */ -@import "./theme.less"; -@import "./spacing.less"; diff --git a/miniprogram/styles/spacing.less b/miniprogram/styles/spacing.less deleted file mode 100644 index 2726329..0000000 --- a/miniprogram/styles/spacing.less +++ /dev/null @@ -1,9 +0,0 @@ -/* ================================================================ - * 全局主题变量(Design Token)—— 间距 - * ---------------------------------------------------------------- - * 规则:业务代码只允许引用本文件中的语义变量,严禁直接写硬编码值。 - * ================================================================ */ -page { - /* 页面边距 —— 仅用于 page 级容器横向 padding */ - --page-padding-x: 30rpx; /* 页面左右统一边距 */ -} diff --git a/miniprogram/styles/theme.less b/miniprogram/styles/theme.less deleted file mode 100644 index 9358be9..0000000 --- a/miniprogram/styles/theme.less +++ /dev/null @@ -1,52 +0,0 @@ -/* ================================================================ - * 全局主题变量(Design Token)—— 颜色 - * ---------------------------------------------------------------- - * 规则:样式类型禁止混用。即使颜色值相同,也必须按语义独立定义, - * 业务代码只允许引用本文件中的语义变量,严禁直接写色值。 - * 分组:品牌色 / 文字色 / 背景色 / 按钮色 / 线条色 / 状态色 / 阴影色 - * ================================================================ */ -page { - /* 品牌色 —— 主色调,用于链接、强调、聚焦等品牌表达 */ - --color-primary: #000000; /* 主色 */ - --color-primary-disabled: #cccccc; /* 主色禁用态 */ - - /* 文字色 —— 仅用于 color 属性,禁止用于按钮/背景 */ - --color-text: #333333; /* 一级正文 */ - --color-text-secondary: #999999; /* 二级辅助文字 */ - --color-text-placeholder: #cccccc; /* 占位文字 */ - --color-text-inverse: #ffffff; /* 反白文字(深底上的白字) */ - --color-text-disabled: #cccccc; /* 禁用文字 */ - - /* 背景色 —— 仅用于容器 background,禁止用于按钮 */ - --color-bg: #f5f5f5; /* 页面底色 */ - --color-bg-card: #ffffff; /* 卡片/容器底色 */ - --color-bg-elevated: #ffffff; /* 浮层/弹框底色 */ - --color-bg-mask: rgba(0, 0, 0, 0.5); /* 弹框蒙层(action-sheet/confirm) */ - - /* 按钮色 —— 仅用于按钮组件,与文字色/背景色完全解耦 */ - --color-btn-primary: #000000; /* 主按钮底色 */ - --color-btn-primary-text: #ffffff; /* 主按钮文字 */ - --color-btn-default: #ffffff; /* 默认按钮底色 */ - --color-btn-default-text: #333333; /* 默认按钮文字 */ - --color-btn-default-border: #e5e5e5; /* 默认按钮边框 */ - --color-btn-disabled: #cccccc; /* 禁用按钮底色 */ - --color-btn-disabled-text: #ffffff; /* 禁用按钮文字 */ - --color-btn-danger: #fa5151; /* 危险按钮底色(删除等) */ - --color-btn-danger-text: #ffffff; /* 危险按钮文字 */ - --color-btn-link: #000000; /* 链接按钮文字色 */ - - /* 线条色 —— 仅用于 border,禁止用于文字/背景 */ - --color-border: #e5e5e5; /* 常规边框 */ - --color-border-light: #f0f0f0; /* 浅边框 */ - --color-divider: #eeeeee; /* 分割线 */ - - /* 状态色 —— 成功/警告/危险/信息提示 */ - --color-success: #07c160; /* 成功(微信绿) */ - --color-warning: #ff9500; /* 警告(橙) */ - --color-danger: #fa5151; /* 危险/错误(红) */ - --color-info: #1677ff; /* 信息(蓝) */ - - /* 阴影色 —— 仅用于 box-shadow */ - --color-shadow: rgba(0, 0, 0, 0.08); /* 常规阴影 */ - --color-shadow-card: rgba(0, 0, 0, 0.04); /* 卡片浅阴影 */ -} diff --git a/miniprogram/utils/common.ts b/miniprogram/utils/common.ts new file mode 100644 index 0000000..81a650b --- /dev/null +++ b/miniprogram/utils/common.ts @@ -0,0 +1,84 @@ +/** 身份证解析结果 */ +export interface IdCardParsed { + /** 性别文本(男/女),解析失败返回空字符串 */ + gender: string + /** 年龄文本,解析失败返回空字符串 */ + age: string + /** 生日 YYYY-MM-DD */ + birthday: string + /** 性别 1=男 2=女 */ + sex: number +} + +/** storage 中 userInfo 的类型 */ +export interface StorageUserInfo { + id: string + 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 | null + thirdId: string + realname: string + bmi: number + createBy: string + createTime: string + updateBy: string | null + updateTime: string | null + delFlag: number + sn: string | null + workNo: string | null + remark: string | null + relationType: string | null + sex_dictText: string + connectDeviceInfo: string +} + +/** 校验身份证号格式是否合法 */ +export const isIdCardValid = (idCard: string): boolean => { + if (idCard.length !== 18) return false + // 前 17 位必须为数字 + if (!/^\d{17}$/.test(idCard.slice(0, 17))) return false + // 第 18 位为数字或 X + if (!/^[\dXx]$/.test(idCard[17])) return false + return true +} + +/** 解析身份证号,提取性别、年龄、生日 */ +export const parseIdCard = (idCard: string): IdCardParsed => { + const empty: IdCardParsed = { gender: '', age: '', birthday: '', sex: 0 } + if (!isIdCardValid(idCard)) return empty + + // 第 17 位奇数为男,偶数为女 + const sexCode = parseInt(idCard[16], 10) + const sex = sexCode % 2 === 1 ? 1 : 2 + + // 第 7-14 位为生日 YYYYMMDD + const year = parseInt(idCard.slice(6, 10), 10) + const month = parseInt(idCard.slice(10, 12), 10) + const day = parseInt(idCard.slice(12, 14), 10) + + // 计算年龄 + const now = new Date() + const birth = new Date(year, month - 1, day) + let age = now.getFullYear() - year + if (now.getMonth() < month - 1 || (now.getMonth() === month - 1 && now.getDate() < day)) { + age-- + } + + return { + gender: sex === 1 ? '男' : '女', + age: String(age), + birthday: `${year}-${String(month).padStart(2, '0')}-${String(day).padStart(2, '0')}`, + sex, + } +} diff --git a/miniprogram/utils/request/config.ts b/miniprogram/utils/request/config.ts new file mode 100644 index 0000000..fd73f89 --- /dev/null +++ b/miniprogram/utils/request/config.ts @@ -0,0 +1,8 @@ +// TODO: 替换为实际接口地址 +const IS_DEV = false + +export const BASE_URL = IS_DEV + ? '' + : '' + +export const TIMEOUT = 30000 diff --git a/miniprogram/utils/request/index.ts b/miniprogram/utils/request/index.ts index 7921630..299fc59 100644 --- a/miniprogram/utils/request/index.ts +++ b/miniprogram/utils/request/index.ts @@ -1,10 +1,8 @@ import type { ApiResponse, HttpMethod, RequestOptions } from './types' +import { BASE_URL, TIMEOUT } from './config' export type { ApiResponse, RequestOptions } -// const BASE_URL = 'http://10.10.10.10:8889/' // 测试环境 -const BASE_URL = 'https://device.shuziweidao.com/gateway/' // 正式环境 - let _loadingCount = 0 const _showLoading = (title: string): void => { @@ -29,7 +27,7 @@ const request = ( const header: Record = { 'content-type': contentType } const token = wx.getStorageSync('token') as string - if (token) header['X-Access-Token'] = token + if (token) header['abtoken'] = token return new Promise((resolve, reject) => { wx.request({ @@ -37,17 +35,14 @@ const request = ( data, method, header, + timeout: TIMEOUT, success: (res) => { if (loading) _hideLoading() const body = res.data as ApiResponse - if (body.code === 200) { + if (body.code === '00000') { resolve(body) - } else if (body.code === 401) { - wx.clearStorageSync() - wx.reLaunch({ url: '/pages/login/login' }) - reject(body) } else { - wx.showToast({ title: body.message || '请求失败', icon: 'none' }) + wx.showToast({ title: body.msg || '请求失败', icon: 'none' }) reject(body) } }, @@ -77,12 +72,3 @@ export const put = ( data?: Record, options?: RequestOptions, ): Promise> => request(url, data, 'PUT', options) - -export const del = ( - url: string, - data?: Record, - options?: RequestOptions, -): Promise> => request(url, data, 'DELETE', { - contentType: 'application/x-www-form-urlencoded', - ...options, -}) diff --git a/miniprogram/utils/request/types.ts b/miniprogram/utils/request/types.ts index 6c8e588..b645020 100644 --- a/miniprogram/utils/request/types.ts +++ b/miniprogram/utils/request/types.ts @@ -1,16 +1,13 @@ export interface ApiResponse { - code: number - result: T - message?: string + code: string + msg: string + data: T } -export type HttpMethod = 'GET' | 'POST' | 'PUT' | 'DELETE' +export type HttpMethod = 'GET' | 'POST' | 'PUT' export interface RequestOptions { - /** 是否显示 loading,默认 true */ loading?: boolean - /** loading 文案,默认"加载中..." */ loadingTitle?: string - /** Content-Type,默认 application/json */ contentType?: 'application/json' | 'application/x-www-form-urlencoded' } diff --git a/miniprogram/utils/util.ts b/miniprogram/utils/util.ts deleted file mode 100644 index 69a2e19..0000000 --- a/miniprogram/utils/util.ts +++ /dev/null @@ -1,19 +0,0 @@ -export const formatTime = (date: Date) => { - const year = date.getFullYear() - const month = date.getMonth() + 1 - const day = date.getDate() - const hour = date.getHours() - const minute = date.getMinutes() - const second = date.getSeconds() - - return ( - [year, month, day].map(formatNumber).join('/') + - ' ' + - [hour, minute, second].map(formatNumber).join(':') - ) -} - -const formatNumber = (n: number) => { - const s = n.toString() - return s[1] ? s : '0' + s -} diff --git a/typings/index.d.ts b/typings/index.d.ts index b4002d8..5094c09 100644 --- a/typings/index.d.ts +++ b/typings/index.d.ts @@ -1,10 +1,8 @@ /// interface IAppOption { - globalData: { - userInfo?: WechatMiniprogram.UserInfo, - /** 当前已连接设备的蓝牙状态 */ - connectState: string, - } - userInfoReadyCallback?: WechatMiniprogram.GetUserInfoSuccessCallback, -} \ No newline at end of file + globalData: {} + userInfoReadyCallback?: WechatMiniprogram.GetUserInfoSuccessCallback + /** 获取微信登录临时 code */ + getWxLoginCode(): Promise +}