Browse Source

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

master
DU 2 months ago
parent
commit
20565daf53
  1. 40
      src/api/customer.ts
  2. 62
      src/pageScan/closeTable/components/FooterButtons.tsx
  3. 28
      src/pages/scan/index.tsx
  4. 15
      src/pages/user/index.tsx
  5. 239
      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 @@
/**
* 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),
})
})
}

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

@ -5,6 +5,7 @@ import { View, Text } from '@tarojs/components'
import Taro from '@tarojs/taro' import Taro from '@tarojs/taro'
import { useState } from 'react' import { useState } from 'react'
import { immediateClose, invokeWechatPay } from '../../../api/startDevice' import { immediateClose, invokeWechatPay } from '../../../api/startDevice'
import { closeOrder, pollTablePaymentStatus } from '../../../api/order'
import { userStore } from '../../../store' import { userStore } from '../../../store'
import './FooterButtons.scss' import './FooterButtons.scss'
@ -63,55 +64,44 @@ const FooterButtons = ({ type, grpSn, operationId }: FooterButtonsProps) => {
console.log('关台接口返回:', result) console.log('关台接口返回:', result)
const consumptionId = result.data.consumptionId
const outTradeNo = result.data.paymentResponseDto?.outTradeNo
// 判断是否需要支付(可能需要补差价) // 判断是否需要支付(可能需要补差价)
if (result.data.paymentResponseDto?.payParameters) { if (result.data.paymentResponseDto?.payParameters) {
// 调起微信支付 // 调起微信支付
try { try {
await invokeWechatPay(result.data.paymentResponseDto.payParameters) await invokeWechatPay(result.data.paymentResponseDto.payParameters)
// 支付成功 // 轮询服务端确认支付结果
Taro.showToast({ Taro.showLoading({ title: '确认支付中...', mask: true })
title: '关台成功', const paid = await pollTablePaymentStatus(consumptionId)
icon: 'success', Taro.hideLoading()
duration: 2000
}) if (paid) {
Taro.showToast({ title: '关台成功', icon: 'success', duration: 2000 })
// 跳转到订单详情或首页 setTimeout(() => Taro.switchTab({ url: '/pages/index/index' }), 2000)
setTimeout(() => { } else {
Taro.switchTab({ Taro.showModal({
url: '/pages/index/index' title: '支付确认中',
content: '支付结果尚未到账,请稍后查看订单,如有疑问请联系店长。',
showCancel: false,
confirmText: '知道了',
}) })
}, 2000) }
} catch (payError: any) { } catch (payError: any) {
console.log('支付错误:', payError) console.log('支付错误:', payError)
// 用户取消支付或支付失败 if (outTradeNo) closeOrder(outTradeNo).catch(() => {})
if (payError.errMsg?.includes('cancel')) { if (payError.errMsg?.includes('cancel')) {
Taro.showToast({ Taro.showToast({ title: '已取消支付', icon: 'none', duration: 2000 })
title: '已取消支付',
icon: 'none',
duration: 2000
})
} else { } else {
Taro.showToast({ Taro.showToast({ title: payError.errMsg || '支付失败', icon: 'none', duration: 2000 })
title: payError.errMsg || '支付失败',
icon: 'none',
duration: 2000
})
} }
} }
} else { } else {
// 无需支付 // 无需支付(押金关台场景)
Taro.showToast({ Taro.showToast({ title: '关台成功', icon: 'success', duration: 2000 })
title: '关台成功', setTimeout(() => Taro.switchTab({ url: '/pages/index/index' }), 2000)
icon: 'success',
duration: 2000
})
setTimeout(() => {
Taro.switchTab({
url: '/pages/index/index'
})
}, 2000)
} }
} catch (error: any) { } catch (error: any) {
console.error('关台失败:', error) console.error('关台失败:', error)

28
src/pages/scan/index.tsx

@ -6,7 +6,6 @@ import Taro, { useLoad } from '@tarojs/taro'
import { observer } from 'mobx-react' import { observer } from 'mobx-react'
import { useState } from 'react' import { useState } from 'react'
import { Dialog } from '@taroify/core' import { Dialog } from '@taroify/core'
import { scanAndHandle } from '@/utils/qrCodeHandler'
import './index.scss' import './index.scss'
const ScanIndex = observer(() => { const ScanIndex = observer(() => {
@ -16,17 +15,22 @@ const ScanIndex = observer(() => {
console.log('扫码页面加载') console.log('扫码页面加载')
}) })
// 扫码 // 扫码 - 扫码结果统一交由 qrHandler 页面处理(负责调用 getTableInfo 等初始化逻辑)
const handleScan = async () => { const handleScan = () => {
try { Taro.scanCode({
await scanAndHandle({ onlyFromCamera: true,
onlyFromCamera: true, scanType: ['qrCode', 'barCode'],
scanType: ['qrCode', 'barCode'] success: (res) => {
}) Taro.navigateTo({
} catch (error) { url: `/pages/qrHandler/index?qrContent=${encodeURIComponent(res.result)}`
// 错误处理已在 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'
import { Image as TaroifyImage, Button } from '@taroify/core' import { Image as TaroifyImage, Button } from '@taroify/core'
import { userStore } from '../../store' import { userStore } from '../../store'
import { wechatLogin } from '../../api/login' import { wechatLogin } from '../../api/login'
import { getCustomerBySn } from '../../api/customer'
import { getBigImage } from '@/utils/imageHelper' import { getBigImage } from '@/utils/imageHelper'
import './index.scss' import './index.scss'
@ -26,8 +27,20 @@ import agreementIcon from '../../assets/icon/agreement.png';
import myAvatar from '../../assets/icon/my_avatar.png'; import myAvatar from '../../assets/icon/my_avatar.png';
const UserIndex = observer(() => { const UserIndex = observer(() => {
useLoad(() => { useLoad(async () => {
console.log('个人中心页面加载') 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)
}
}
}) })
// 成长值图标映射(根据实际业务逻辑调整) // 成长值图标映射(根据实际业务逻辑调整)

239
src/pagesUser/editProfile/index.tsx

@ -4,43 +4,40 @@
import { View, Text, Image, Input, Button as WxButton } from '@tarojs/components' import { View, Text, Image, Input, Button as WxButton } from '@tarojs/components'
import Taro, { useLoad } from '@tarojs/taro' import Taro, { useLoad } from '@tarojs/taro'
import { useState } from 'react' import { useState } from 'react'
import { Image as TaroifyImage, DatetimePicker, Popup, Button, ActionSheet } from '@taroify/core' import { observer } from 'mobx-react'
import { ArrowLeft } from '@taroify/icons' 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 myAvatar from '../../assets/icon/my_avatar.png'
import './index.scss' import './index.scss'
const EditProfile = () => { const EditProfile = observer(() => {
const [nickname, setNickname] = useState('旋转跳跃的球圣') const info = userStore.userInfo
const [birthday, setBirthday] = useState(new Date('2025-01-01'))
const [datePickerOpen, setDatePickerOpen] = useState(false) 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 [avatarActionOpen, setAvatarActionOpen] = useState(false)
const [avatarUrl, setAvatarUrl] = useState(myAvatar)
useLoad(() => { useLoad(() => {
console.log('编辑个人信息页面加载') console.log('编辑个人信息页面加载')
}) })
// 返回上一页 // 点击头像区域
const handleBack = () => {
Taro.navigateBack()
}
// 点击头像,打开选择菜单
const handleAvatarClick = () => { const handleAvatarClick = () => {
setAvatarActionOpen(true) setAvatarActionOpen(true)
} }
// 使用微信头像 // 微信头像回调
const handleUseWechatAvatar = () => { const handleChooseAvatar = (e: any) => {
// 注意:button open-type="chooseAvatar" 需要在 button 组件上使用 const url = e.detail.avatarUrl
// 这里先关闭 ActionSheet,让用户点击特殊的 button setAvatarUrl(url)
setNewAvatarPath(url)
setAvatarActionOpen(false) setAvatarActionOpen(false)
// 实际使用时需要通过 button open-type="chooseAvatar" 触发
Taro.showToast({
title: '请使用下方按钮选择微信头像',
icon: 'none',
duration: 2000
})
} }
// 从相册选择 // 从相册选择
@ -51,10 +48,10 @@ const EditProfile = () => {
sizeType: ['compressed'], sizeType: ['compressed'],
sourceType: ['album'], sourceType: ['album'],
success: (res) => { success: (res) => {
console.log('选择头像', res.tempFilePaths[0]) const path = res.tempFilePaths[0]
setAvatarUrl(res.tempFilePaths[0]) setAvatarUrl(path)
// TODO: 上传头像到服务器 setNewAvatarPath(path)
} },
}) })
} }
@ -66,74 +63,83 @@ const EditProfile = () => {
sizeType: ['compressed'], sizeType: ['compressed'],
sourceType: ['camera'], sourceType: ['camera'],
success: (res) => { success: (res) => {
console.log('拍照', res.tempFilePaths[0]) const path = res.tempFilePaths[0]
setAvatarUrl(res.tempFilePaths[0]) setAvatarUrl(path)
// TODO: 上传头像到服务器 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 = () => { const handleSave = async () => {
Taro.showToast({ if (loading) return
title: '保存成功',
icon: 'success', const userId = info?.id
duration: 2000 if (!userId) {
}) Taro.showToast({ title: '用户信息异常,请重新登录', icon: 'none', duration: 2000 })
return
setTimeout(() => { }
Taro.navigateBack()
}, 2000) 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 ( return (
<View className="edit-profile-page"> <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="content-area">
{/* 头像区域 */} {/* 头像区域 */}
<View className="avatar-section" onClick={handleAvatarClick}> <View className="avatar-section" onClick={handleAvatarClick}>
<View className="avatar-wrapper"> <View className="avatar-wrapper">
<TaroifyImage <TaroifyImage
className="avatar" className="avatar"
src={avatarUrl} src={avatarUrl || myAvatar}
round round
mode="aspectFill" mode="aspectFill"
/> />
{/* 相机图标 */}
<View className="camera-icon"> <View className="camera-icon">
<Image <Image
className="camera-img" className="camera-img"
src={require('../../assets/icon/phototake.png')} src={require('../../assets/icon/phototake.png')}
mode="widthFix" mode="widthFix"
@ -153,51 +159,55 @@ const EditProfile = () => {
onInput={(e) => setNickname(e.detail.value)} onInput={(e) => setNickname(e.detail.value)}
placeholder="请输入昵称" placeholder="请输入昵称"
placeholderClass="form-placeholder" placeholderClass="form-placeholder"
maxlength={20}
/> />
</View> </View>
{/* 生日 */} {/* 手机号 */}
<View className="form-item" onClick={() => setDatePickerOpen(true)}> <View className="form-item">
<Text className="form-label"></Text> <Text className="form-label"></Text>
<View className="form-value"> <Input
<Text>{formatDate(birthday)}</Text> className="form-input"
<ArrowLeft className="arrow-icon" size={16} color="#999" /> value={phone}
</View> 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>
</View> </View>
{/* 保存按钮 */} {/* 保存按钮 */}
<Button className="save-button" onClick={handleSave}> <Button
className={`save-button ${loading ? 'disabled' : ''}`}
loading={loading}
onClick={handleSave}
>
{loading ? '保存中...' : '保存'}
</Button> </Button>
</View> </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 <ActionSheet
open={avatarActionOpen} open={avatarActionOpen}
onSelect={() => {}} onSelect={() => {}}
onClose={() => setAvatarActionOpen(false)} onClose={() => setAvatarActionOpen(false)}
> >
{/* 使用微信头像 - 需要特殊处理 */} <WxButton
<WxButton
className="avatar-action-item wechat-avatar-btn" className="avatar-action-item wechat-avatar-btn"
openType="chooseAvatar" openType="chooseAvatar"
onChooseAvatar={handleChooseAvatar} onChooseAvatar={handleChooseAvatar}
@ -205,29 +215,30 @@ const EditProfile = () => {
<Text className="avatar-emoji">🤵</Text> <Text className="avatar-emoji">🤵</Text>
<Text></Text> <Text></Text>
</WxButton> </WxButton>
<ActionSheet.Action <ActionSheet.Action
className="avatar-action-item" className="avatar-action-item"
onClick={handleChooseFromAlbum} onClick={handleChooseFromAlbum}
> >
</ActionSheet.Action> </ActionSheet.Action>
<ActionSheet.Action <ActionSheet.Action
className="avatar-action-item" className="avatar-action-item"
onClick={handleTakePhoto} onClick={handleTakePhoto}
> >
</ActionSheet.Action> </ActionSheet.Action>
<ActionSheet.Action <ActionSheet.Action
className="avatar-action-item cancel-action" className="avatar-action-item cancel-action"
onClick={() => setAvatarActionOpen(false)}
> >
</ActionSheet.Action> </ActionSheet.Action>
</ActionSheet> </ActionSheet>
</View> </View>
) )
} })
export default EditProfile export default EditProfile

16
src/store/user.ts

@ -7,6 +7,8 @@ import { CustomerDto } from '../types/login'
export interface UserInfo { export interface UserInfo {
id: number id: number
/** 客户编码(用于 getCustomerBySn 刷新信息) */
sn?: string
nickname: string nickname: string
avatar?: string avatar?: string
phone?: string phone?: string
@ -42,6 +44,10 @@ class UserStore {
if (userInfo) { if (userInfo) {
this.userInfo = userInfo this.userInfo = userInfo
this.isLoggedIn = true this.isLoggedIn = true
// 同步恢复余额,避免重启后显示 0
if (userInfo.amount !== undefined) {
this.balance = userInfo.amount
}
} }
} catch (error) { } catch (error) {
console.error('读取用户信息失败:', error) console.error('读取用户信息失败:', error)
@ -54,15 +60,21 @@ class UserStore {
if (userInfo) { if (userInfo) {
Taro.setStorageSync('userInfo', userInfo) Taro.setStorageSync('userInfo', userInfo)
// 单独保存 openId,供 request.ts 请求头注入使用
if (userInfo.wxOpenId) {
Taro.setStorageSync('openId', userInfo.wxOpenId)
}
} else { } else {
Taro.removeStorageSync('userInfo') Taro.removeStorageSync('userInfo')
Taro.removeStorageSync('openId')
} }
} }
// 从微信登录结果设置用户信息 // 从微信登录结果(或刷新接口)设置用户信息
setUserInfoFromWechat = (customer: CustomerDto) => { setUserInfoFromWechat = (customer: CustomerDto) => {
const userInfo: UserInfo = { const userInfo: UserInfo = {
id: customer.id, id: customer.id,
sn: customer.sn, // 修复:保存 sn,用于刷新接口
nickname: customer.wxNickName || customer.name || '微信用户', nickname: customer.wxNickName || customer.name || '微信用户',
avatar: customer.wxAvatarUrl, avatar: customer.wxAvatarUrl,
phone: customer.phone, phone: customer.phone,
@ -77,7 +89,7 @@ class UserStore {
instSn: customer.instSn, instSn: customer.instSn,
ownerName: customer.ownerName, ownerName: customer.ownerName,
createTime: customer.createTime, createTime: customer.createTime,
lastLoginTime: customer.lastLoginTime lastLoginTime: customer.lastLoginTime,
} }
this.setUserInfo(userInfo) this.setUserInfo(userInfo)

15
src/types/login.ts

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