Browse Source

对接 根据SN 获取用户信息, 更新客户信息接口等

master
DU 2 months ago
parent
commit
20565daf53
  1. 40
      src/api/customer.ts
  2. 58
      src/pageScan/closeTable/components/FooterButtons.tsx
  3. 22
      src/pages/scan/index.tsx
  4. 15
      src/pages/user/index.tsx
  5. 205
      src/pagesUser/editProfile/index.tsx
  6. 16
      src/store/user.ts
  7. 15
      src/types/login.ts

40
src/api/customer.ts

@ -0,0 +1,40 @@ @@ -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<CustomerDto>(`/Customer/sn/${sn}`)
}
/**
*
* @param customerId IDCustomerDto.id
* @param dto
*/
export const updateCustomer = (customerId: number, dto: UpdateCustomerDto) => {
return put<void>(`/Customer/${customerId}`, dto)
}
/**
* Base64
*
* @param filePath wx.chooseImage
* @returns Base64 data:image/xxx;base64,
*/
export const readImageAsBase64 = (filePath: string): Promise<string> => {
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),
})
})
}

58
src/pageScan/closeTable/components/FooterButtons.tsx

@ -5,6 +5,7 @@ import { View, Text } from '@tarojs/components' @@ -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) => { @@ -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
})
// 轮询服务端确认支付结果
Taro.showLoading({ title: '确认支付中...', mask: true })
const paid = await pollTablePaymentStatus(consumptionId)
Taro.hideLoading()
// 跳转到订单详情或首页
setTimeout(() => {
Taro.switchTab({
url: '/pages/index/index'
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)

22
src/pages/scan/index.tsx

@ -6,7 +6,6 @@ import Taro, { useLoad } from '@tarojs/taro' @@ -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(() => { @@ -16,17 +15,22 @@ const ScanIndex = observer(() => {
console.log('扫码页面加载')
})
// 扫码
const handleScan = async () => {
try {
await scanAndHandle({
// 扫码 - 扫码结果统一交由 qrHandler 页面处理(负责调用 getTableInfo 等初始化逻辑)
const handleScan = () => {
Taro.scanCode({
onlyFromCamera: true,
scanType: ['qrCode', 'barCode']
scanType: ['qrCode', 'barCode'],
success: (res) => {
Taro.navigateTo({
url: `/pages/qrHandler/index?qrContent=${encodeURIComponent(res.result)}`
})
} catch (error) {
// 错误处理已在 scanAndHandle 中完成
console.log('扫码取消或失败')
},
fail: (err) => {
if (!err.errMsg?.includes('cancel')) {
Taro.showToast({ title: '扫码失败,请重试', icon: 'none' })
}
}
})
}
// 去开台

15
src/pages/user/index.tsx

@ -8,6 +8,7 @@ import { observer } from 'mobx-react' @@ -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'; @@ -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)
}
}
})
// 成长值图标映射(根据实际业务逻辑调整)

205
src/pagesUser/editProfile/index.tsx

@ -4,43 +4,40 @@ @@ -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<string>('')
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 = () => { @@ -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,72 +63,81 @@ const EditProfile = () => { @@ -66,72 +63,81 @@ 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 handleSave = async () => {
if (loading) return
const userId = info?.id
if (!userId) {
Taro.showToast({ title: '用户信息异常,请重新登录', icon: 'none', duration: 2000 })
return
}
// 格式化日期显示
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}`
if (!nickname.trim()) {
Taro.showToast({ title: '昵称不能为空', icon: 'none', duration: 2000 })
return
}
// 确认选择日期
const handleDateConfirm = (newValue: any) => {
setBirthday(newValue)
setDatePickerOpen(false)
setLoading(true)
try {
// 如果选了新头像,先转成 Base64
let avatarImage: string | undefined
if (newAvatarPath) {
try {
avatarImage = await readImageAsBase64(newAvatarPath)
} catch (e) {
console.warn('头像转 Base64 失败,跳过头像更新:', e)
}
}
// 保存
const handleSave = () => {
Taro.showToast({
title: '保存成功',
icon: 'success',
duration: 2000
await updateCustomer(userId, {
id: userId,
name: nickname.trim(),
phone: phone.trim() || undefined,
wchart: wchart.trim() || undefined,
avatarImage,
})
setTimeout(() => {
Taro.navigateBack()
}, 2000)
// 同步更新本地 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 (
<View className="edit-profile-page">
{/* 自定义导航栏 */}
{/* <View className="custom-navbar">
<View className="navbar-left" onClick={handleBack}>
<ArrowLeft size={20} color="#FFFFFF" />
</View>
<View className="navbar-title">
<Text></Text>
</View>
<View className="navbar-right" />
</View> */}
{/* 内容区域 */}
<View className="content-area">
{/* 头像区域 */}
<View className="avatar-section" onClick={handleAvatarClick}>
<View className="avatar-wrapper">
<TaroifyImage
className="avatar"
src={avatarUrl}
src={avatarUrl || myAvatar}
round
mode="aspectFill"
/>
{/* 相机图标 */}
<View className="camera-icon">
<Image
className="camera-img"
@ -153,50 +159,54 @@ const EditProfile = () => { @@ -153,50 +159,54 @@ const EditProfile = () => {
onInput={(e) => setNickname(e.detail.value)}
placeholder="请输入昵称"
placeholderClass="form-placeholder"
maxlength={20}
/>
</View>
{/* 生日 */}
<View className="form-item" onClick={() => setDatePickerOpen(true)}>
<Text className="form-label"></Text>
<View className="form-value">
<Text>{formatDate(birthday)}</Text>
<ArrowLeft className="arrow-icon" size={16} color="#999" />
{/* 手机号 */}
<View className="form-item">
<Text className="form-label"></Text>
<Input
className="form-input"
value={phone}
onInput={(e) => setPhone(e.detail.value)}
placeholder="请输入手机号"
placeholderClass="form-placeholder"
type="number"
maxlength={11}
/>
</View>
{/* 微信号 */}
<View className="form-item">
<Text className="form-label"></Text>
<Input
className="form-input"
value={wchart}
onInput={(e) => setWchart(e.detail.value)}
placeholder="请输入微信号"
placeholderClass="form-placeholder"
maxlength={30}
/>
</View>
</View>
{/* 保存按钮 */}
<Button className="save-button" onClick={handleSave}>
<Button
className={`save-button ${loading ? 'disabled' : ''}`}
loading={loading}
onClick={handleSave}
>
{loading ? '保存中...' : '保存'}
</Button>
</View>
{/* 日期选择器 */}
<Popup
open={datePickerOpen}
placement="bottom"
onClose={() => setDatePickerOpen(false)}
>
<DatetimePicker
type="date"
value={birthday}
onChange={handleDateConfirm}
onCancel={() => setDatePickerOpen(false)}
onConfirm={handleDateConfirm}
>
{/* <Button></Button>
<Button></Button> */}
</DatetimePicker>
</Popup>
{/* 头像选择菜单 */}
<ActionSheet
open={avatarActionOpen}
onSelect={() => {}}
onClose={() => setAvatarActionOpen(false)}
>
{/* 使用微信头像 - 需要特殊处理 */}
<WxButton
className="avatar-action-item wechat-avatar-btn"
openType="chooseAvatar"
@ -222,12 +232,13 @@ const EditProfile = () => { @@ -222,12 +232,13 @@ const EditProfile = () => {
<ActionSheet.Action
className="avatar-action-item cancel-action"
onClick={() => setAvatarActionOpen(false)}
>
</ActionSheet.Action>
</ActionSheet>
</View>
)
}
})
export default EditProfile

16
src/store/user.ts

@ -7,6 +7,8 @@ import { CustomerDto } from '../types/login' @@ -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 { @@ -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 { @@ -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 { @@ -77,7 +89,7 @@ class UserStore {
instSn: customer.instSn,
ownerName: customer.ownerName,
createTime: customer.createTime,
lastLoginTime: customer.lastLoginTime
lastLoginTime: customer.lastLoginTime,
}
this.setUserInfo(userInfo)

15
src/types/login.ts

@ -42,3 +42,18 @@ export interface WechatAuthResponse { @@ -42,3 +42,18 @@ export interface WechatAuthResponse {
success: boolean
data: WechatAuthResultDto
}
// 更新客户信息请求参数
export interface UpdateCustomerDto {
id: number
/** 头像(Base64 编码字符串) */
avatarImage?: string
/** 名称 */
name?: string
/** 客户类型 */
type?: number
/** 微信号 */
wchart?: string
/** 手机号 */
phone?: string
}
Loading…
Cancel
Save