From 20565daf53ef177d3e8884d2288e65538a43d23a Mon Sep 17 00:00:00 2001 From: DU Date: Tue, 5 May 2026 14:32:11 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AF=B9=E6=8E=A5=20=E6=A0=B9=E6=8D=AESN=20?= =?UTF-8?q?=E8=8E=B7=E5=8F=96=E7=94=A8=E6=88=B7=E4=BF=A1=E6=81=AF=EF=BC=8C?= =?UTF-8?q?=20=E6=9B=B4=E6=96=B0=E5=AE=A2=E6=88=B7=E4=BF=A1=E6=81=AF?= =?UTF-8?q?=E6=8E=A5=E5=8F=A3=E7=AD=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/customer.ts | 40 +++ .../closeTable/components/FooterButtons.tsx | 62 ++--- src/pages/scan/index.tsx | 28 +- src/pages/user/index.tsx | 15 +- src/pagesUser/editProfile/index.tsx | 239 +++++++++--------- src/store/user.ts | 16 +- src/types/login.ts | 15 ++ 7 files changed, 250 insertions(+), 165 deletions(-) create mode 100644 src/api/customer.ts diff --git a/src/api/customer.ts b/src/api/customer.ts new file mode 100644 index 0000000..9e87683 --- /dev/null +++ b/src/api/customer.ts @@ -0,0 +1,40 @@ +/** + * 客户信息相关 API + */ +import { get, put } from '../services/request' +import { CustomerDto, UpdateCustomerDto } from '../types/login' + +/** + * 根据客户 SN 获取客户详情(含最新余额、会员类型等) + * @param sn 客户编码(CustomerDto.sn) + */ +export const getCustomerBySn = (sn: string) => { + return get(`/Customer/sn/${sn}`) +} + +/** + * 更新客户信息(昵称、头像、手机号、微信号等) + * @param customerId 客户 ID(CustomerDto.id) + * @param dto 要更新的字段 + */ +export const updateCustomer = (customerId: number, dto: UpdateCustomerDto) => { + return put(`/Customer/${customerId}`, dto) +} + +/** + * 读取本地图片并转为 Base64 字符串 + * 用于将选择的头像图片编码后传给更新接口 + * @param filePath 本地临时文件路径(如 wx.chooseImage 返回的路径) + * @returns Base64 字符串(不含 data:image/xxx;base64, 前缀) + */ +export const readImageAsBase64 = (filePath: string): Promise => { + return new Promise((resolve, reject) => { + const fs = wx.getFileSystemManager() + fs.readFile({ + filePath, + encoding: 'base64', + success: (res) => resolve(res.data as string), + fail: (err) => reject(err), + }) + }) +} diff --git a/src/pageScan/closeTable/components/FooterButtons.tsx b/src/pageScan/closeTable/components/FooterButtons.tsx index 33d6275..0f55b19 100644 --- a/src/pageScan/closeTable/components/FooterButtons.tsx +++ b/src/pageScan/closeTable/components/FooterButtons.tsx @@ -5,6 +5,7 @@ import { View, Text } from '@tarojs/components' import Taro from '@tarojs/taro' import { useState } from 'react' import { immediateClose, invokeWechatPay } from '../../../api/startDevice' +import { closeOrder, pollTablePaymentStatus } from '../../../api/order' import { userStore } from '../../../store' import './FooterButtons.scss' @@ -63,55 +64,44 @@ const FooterButtons = ({ type, grpSn, operationId }: FooterButtonsProps) => { console.log('关台接口返回:', result) + const consumptionId = result.data.consumptionId + const outTradeNo = result.data.paymentResponseDto?.outTradeNo + // 判断是否需要支付(可能需要补差价) if (result.data.paymentResponseDto?.payParameters) { // 调起微信支付 try { await invokeWechatPay(result.data.paymentResponseDto.payParameters) - - // 支付成功 - Taro.showToast({ - title: '关台成功', - icon: 'success', - duration: 2000 - }) - - // 跳转到订单详情或首页 - setTimeout(() => { - Taro.switchTab({ - url: '/pages/index/index' + + // 轮询服务端确认支付结果 + Taro.showLoading({ title: '确认支付中...', mask: true }) + const paid = await pollTablePaymentStatus(consumptionId) + Taro.hideLoading() + + if (paid) { + Taro.showToast({ title: '关台成功', icon: 'success', duration: 2000 }) + setTimeout(() => Taro.switchTab({ url: '/pages/index/index' }), 2000) + } else { + Taro.showModal({ + title: '支付确认中', + content: '支付结果尚未到账,请稍后查看订单,如有疑问请联系店长。', + showCancel: false, + confirmText: '知道了', }) - }, 2000) + } } catch (payError: any) { console.log('支付错误:', payError) - // 用户取消支付或支付失败 + if (outTradeNo) closeOrder(outTradeNo).catch(() => {}) if (payError.errMsg?.includes('cancel')) { - Taro.showToast({ - title: '已取消支付', - icon: 'none', - duration: 2000 - }) + Taro.showToast({ title: '已取消支付', icon: 'none', duration: 2000 }) } else { - Taro.showToast({ - title: payError.errMsg || '支付失败', - icon: 'none', - duration: 2000 - }) + Taro.showToast({ title: payError.errMsg || '支付失败', icon: 'none', duration: 2000 }) } } } else { - // 无需支付 - Taro.showToast({ - title: '关台成功', - icon: 'success', - duration: 2000 - }) - - setTimeout(() => { - Taro.switchTab({ - url: '/pages/index/index' - }) - }, 2000) + // 无需支付(押金关台场景) + Taro.showToast({ title: '关台成功', icon: 'success', duration: 2000 }) + setTimeout(() => Taro.switchTab({ url: '/pages/index/index' }), 2000) } } catch (error: any) { console.error('关台失败:', error) diff --git a/src/pages/scan/index.tsx b/src/pages/scan/index.tsx index 42940c7..bea90e4 100644 --- a/src/pages/scan/index.tsx +++ b/src/pages/scan/index.tsx @@ -6,7 +6,6 @@ import Taro, { useLoad } from '@tarojs/taro' import { observer } from 'mobx-react' import { useState } from 'react' import { Dialog } from '@taroify/core' -import { scanAndHandle } from '@/utils/qrCodeHandler' import './index.scss' const ScanIndex = observer(() => { @@ -16,17 +15,22 @@ const ScanIndex = observer(() => { console.log('扫码页面加载') }) - // 扫码 - const handleScan = async () => { - try { - await scanAndHandle({ - onlyFromCamera: true, - scanType: ['qrCode', 'barCode'] - }) - } catch (error) { - // 错误处理已在 scanAndHandle 中完成 - console.log('扫码取消或失败') - } + // 扫码 - 扫码结果统一交由 qrHandler 页面处理(负责调用 getTableInfo 等初始化逻辑) + const handleScan = () => { + Taro.scanCode({ + onlyFromCamera: true, + scanType: ['qrCode', 'barCode'], + success: (res) => { + Taro.navigateTo({ + url: `/pages/qrHandler/index?qrContent=${encodeURIComponent(res.result)}` + }) + }, + fail: (err) => { + if (!err.errMsg?.includes('cancel')) { + Taro.showToast({ title: '扫码失败,请重试', icon: 'none' }) + } + } + }) } // 去开台 diff --git a/src/pages/user/index.tsx b/src/pages/user/index.tsx index ee4769f..d6a6c06 100644 --- a/src/pages/user/index.tsx +++ b/src/pages/user/index.tsx @@ -8,6 +8,7 @@ import { observer } from 'mobx-react' import { Image as TaroifyImage, Button } from '@taroify/core' import { userStore } from '../../store' import { wechatLogin } from '../../api/login' +import { getCustomerBySn } from '../../api/customer' import { getBigImage } from '@/utils/imageHelper' import './index.scss' @@ -26,8 +27,20 @@ import agreementIcon from '../../assets/icon/agreement.png'; import myAvatar from '../../assets/icon/my_avatar.png'; const UserIndex = observer(() => { - useLoad(() => { + useLoad(async () => { console.log('个人中心页面加载') + // 已登录时,刷新最新用户信息(余额、会员类型等) + const sn = userStore.userInfo?.sn + if (userStore.isLoggedIn && sn) { + try { + const res = await getCustomerBySn(sn) + if (res.data) { + userStore.setUserInfoFromWechat(res.data) + } + } catch (e) { + console.warn('刷新用户信息失败:', e) + } + } }) // 成长值图标映射(根据实际业务逻辑调整) diff --git a/src/pagesUser/editProfile/index.tsx b/src/pagesUser/editProfile/index.tsx index 5268dd7..97302f9 100644 --- a/src/pagesUser/editProfile/index.tsx +++ b/src/pagesUser/editProfile/index.tsx @@ -4,43 +4,40 @@ import { View, Text, Image, Input, Button as WxButton } from '@tarojs/components' import Taro, { useLoad } from '@tarojs/taro' import { useState } from 'react' -import { Image as TaroifyImage, DatetimePicker, Popup, Button, ActionSheet } from '@taroify/core' -import { ArrowLeft } from '@taroify/icons' +import { observer } from 'mobx-react' +import { Image as TaroifyImage, Button, ActionSheet } from '@taroify/core' +import { updateCustomer, readImageAsBase64 } from '../../api/customer' +import { userStore } from '../../store' import myAvatar from '../../assets/icon/my_avatar.png' import './index.scss' -const EditProfile = () => { - const [nickname, setNickname] = useState('旋转跳跃的球圣') - const [birthday, setBirthday] = useState(new Date('2025-01-01')) - const [datePickerOpen, setDatePickerOpen] = useState(false) +const EditProfile = observer(() => { + const info = userStore.userInfo + + const [nickname, setNickname] = useState(info?.name || info?.nickname || '') + const [phone, setPhone] = useState(info?.phone || '') + const [wchart, setWchart] = useState(info?.wchart || '') + const [avatarUrl, setAvatarUrl] = useState(info?.avatar || myAvatar) + // 是否选了新的本地头像(需要上传) + const [newAvatarPath, setNewAvatarPath] = useState('') + const [loading, setLoading] = useState(false) const [avatarActionOpen, setAvatarActionOpen] = useState(false) - const [avatarUrl, setAvatarUrl] = useState(myAvatar) useLoad(() => { console.log('编辑个人信息页面加载') }) - // 返回上一页 - const handleBack = () => { - Taro.navigateBack() - } - - // 点击头像,打开选择菜单 + // 点击头像区域 const handleAvatarClick = () => { setAvatarActionOpen(true) } - // 使用微信头像 - const handleUseWechatAvatar = () => { - // 注意:button open-type="chooseAvatar" 需要在 button 组件上使用 - // 这里先关闭 ActionSheet,让用户点击特殊的 button + // 微信头像回调 + const handleChooseAvatar = (e: any) => { + const url = e.detail.avatarUrl + setAvatarUrl(url) + setNewAvatarPath(url) setAvatarActionOpen(false) - // 实际使用时需要通过 button open-type="chooseAvatar" 触发 - Taro.showToast({ - title: '请使用下方按钮选择微信头像', - icon: 'none', - duration: 2000 - }) } // 从相册选择 @@ -51,10 +48,10 @@ const EditProfile = () => { sizeType: ['compressed'], sourceType: ['album'], success: (res) => { - console.log('选择头像', res.tempFilePaths[0]) - setAvatarUrl(res.tempFilePaths[0]) - // TODO: 上传头像到服务器 - } + const path = res.tempFilePaths[0] + setAvatarUrl(path) + setNewAvatarPath(path) + }, }) } @@ -66,74 +63,83 @@ const EditProfile = () => { sizeType: ['compressed'], sourceType: ['camera'], success: (res) => { - console.log('拍照', res.tempFilePaths[0]) - setAvatarUrl(res.tempFilePaths[0]) - // TODO: 上传头像到服务器 - } + const path = res.tempFilePaths[0] + setAvatarUrl(path) + setNewAvatarPath(path) + }, }) } - // 获取微信头像回调 - const handleChooseAvatar = (e: any) => { - console.log('微信头像', e.detail.avatarUrl) - setAvatarUrl(e.detail.avatarUrl) - // TODO: 上传头像到服务器 - } - - // 格式化日期显示 - const formatDate = (date: Date) => { - const year = date.getFullYear() - const month = String(date.getMonth() + 1).padStart(2, '0') - const day = String(date.getDate()).padStart(2, '0') - return `${year}-${month}-${day}` - } - - // 确认选择日期 - const handleDateConfirm = (newValue: any) => { - setBirthday(newValue) - setDatePickerOpen(false) - } - // 保存 - const handleSave = () => { - Taro.showToast({ - title: '保存成功', - icon: 'success', - duration: 2000 - }) - - setTimeout(() => { - Taro.navigateBack() - }, 2000) + const handleSave = async () => { + if (loading) return + + const userId = info?.id + if (!userId) { + Taro.showToast({ title: '用户信息异常,请重新登录', icon: 'none', duration: 2000 }) + return + } + + if (!nickname.trim()) { + Taro.showToast({ title: '昵称不能为空', icon: 'none', duration: 2000 }) + return + } + + setLoading(true) + + try { + // 如果选了新头像,先转成 Base64 + let avatarImage: string | undefined + if (newAvatarPath) { + try { + avatarImage = await readImageAsBase64(newAvatarPath) + } catch (e) { + console.warn('头像转 Base64 失败,跳过头像更新:', e) + } + } + + await updateCustomer(userId, { + id: userId, + name: nickname.trim(), + phone: phone.trim() || undefined, + wchart: wchart.trim() || undefined, + avatarImage, + }) + + // 同步更新本地 store + userStore.setUserInfo({ + ...userStore.userInfo!, + name: nickname.trim(), + nickname: nickname.trim(), + phone: phone.trim() || userStore.userInfo?.phone, + wchart: wchart.trim() || userStore.userInfo?.wchart, + avatar: newAvatarPath || userStore.userInfo?.avatar, + }) + + Taro.showToast({ title: '保存成功', icon: 'success', duration: 2000 }) + setTimeout(() => Taro.navigateBack(), 1500) + } catch (error: any) { + console.error('保存失败:', error) + // 错误提示由 request 拦截器统一处理 + } finally { + setLoading(false) + } } return ( - {/* 自定义导航栏 */} - {/* - - - - - 个人信息 - - - */} - - {/* 内容区域 */} {/* 头像区域 */} - - {/* 相机图标 */} - { onInput={(e) => setNickname(e.detail.value)} placeholder="请输入昵称" placeholderClass="form-placeholder" + maxlength={20} /> - {/* 生日 */} - setDatePickerOpen(true)}> - 生日 - - {formatDate(birthday)} - - + {/* 手机号 */} + + 手机号 + setPhone(e.detail.value)} + placeholder="请输入手机号" + placeholderClass="form-placeholder" + type="number" + maxlength={11} + /> + + + {/* 微信号 */} + + 微信号 + setWchart(e.detail.value)} + placeholder="请输入微信号" + placeholderClass="form-placeholder" + maxlength={30} + /> {/* 保存按钮 */} - - {/* 日期选择器 */} - setDatePickerOpen(false)} - > - setDatePickerOpen(false)} - onConfirm={handleDateConfirm} - > - {/* - */} - - - {/* 头像选择菜单 */} {}} onClose={() => setAvatarActionOpen(false)} > - {/* 使用微信头像 - 需要特殊处理 */} - { 🤵 用微信头像 - - 从相册选择 - - 拍照 - - setAvatarActionOpen(false)} > 取消 ) -} +}) export default EditProfile diff --git a/src/store/user.ts b/src/store/user.ts index ebdbc5f..b48ec8c 100644 --- a/src/store/user.ts +++ b/src/store/user.ts @@ -7,6 +7,8 @@ import { CustomerDto } from '../types/login' export interface UserInfo { id: number + /** 客户编码(用于 getCustomerBySn 刷新信息) */ + sn?: string nickname: string avatar?: string phone?: string @@ -42,6 +44,10 @@ class UserStore { if (userInfo) { this.userInfo = userInfo this.isLoggedIn = true + // 同步恢复余额,避免重启后显示 0 + if (userInfo.amount !== undefined) { + this.balance = userInfo.amount + } } } catch (error) { console.error('读取用户信息失败:', error) @@ -54,15 +60,21 @@ class UserStore { if (userInfo) { Taro.setStorageSync('userInfo', userInfo) + // 单独保存 openId,供 request.ts 请求头注入使用 + if (userInfo.wxOpenId) { + Taro.setStorageSync('openId', userInfo.wxOpenId) + } } else { Taro.removeStorageSync('userInfo') + Taro.removeStorageSync('openId') } } - // 从微信登录结果设置用户信息 + // 从微信登录结果(或刷新接口)设置用户信息 setUserInfoFromWechat = (customer: CustomerDto) => { const userInfo: UserInfo = { id: customer.id, + sn: customer.sn, // 修复:保存 sn,用于刷新接口 nickname: customer.wxNickName || customer.name || '微信用户', avatar: customer.wxAvatarUrl, phone: customer.phone, @@ -77,7 +89,7 @@ class UserStore { instSn: customer.instSn, ownerName: customer.ownerName, createTime: customer.createTime, - lastLoginTime: customer.lastLoginTime + lastLoginTime: customer.lastLoginTime, } this.setUserInfo(userInfo) diff --git a/src/types/login.ts b/src/types/login.ts index d68fe96..39ca48d 100644 --- a/src/types/login.ts +++ b/src/types/login.ts @@ -41,4 +41,19 @@ export interface WechatAuthResponse { code: number success: boolean data: WechatAuthResultDto +} + +// 更新客户信息请求参数 +export interface UpdateCustomerDto { + id: number + /** 头像(Base64 编码字符串) */ + avatarImage?: string + /** 名称 */ + name?: string + /** 客户类型 */ + type?: number + /** 微信号 */ + wchart?: string + /** 手机号 */ + phone?: string } \ No newline at end of file