forked from gxwebsoft/mp-10550
feat(admin): 完成门店核销功能
- 新增管理员页面和相关组件 - 实现用户认证和权限控制 - 添加用户订单、积分等功能 - 优化用户卡片和用户页面布局 - 实现礼品卡核销功能
This commit is contained in:
@@ -2,7 +2,7 @@
|
||||
export const ENV_CONFIG = {
|
||||
// 开发环境
|
||||
development: {
|
||||
API_BASE_URL: 'http://127.0.0.1:9200/api',
|
||||
API_BASE_URL: 'https://cms-api.websoft.top/api',
|
||||
APP_NAME: '开发环境',
|
||||
DEBUG: 'true',
|
||||
},
|
||||
|
||||
150
src/admin/components/StoreCell.tsx
Normal file
150
src/admin/components/StoreCell.tsx
Normal file
@@ -0,0 +1,150 @@
|
||||
import {Cell} from '@nutui/nutui-react-taro'
|
||||
import navTo from "@/utils/common";
|
||||
import Taro from '@tarojs/taro'
|
||||
import {View, Text} from '@tarojs/components'
|
||||
import {ArrowRight, ShieldCheck, LogisticsError, Location, Reward, Tips, Ask, Setting, Scan} from '@nutui/icons-react-taro'
|
||||
import {useUser} from '@/hooks/useUser'
|
||||
|
||||
const UserCell = () => {
|
||||
const {logoutUser, isCertified, hasRole, isAdmin} = useUser();
|
||||
|
||||
const onLogout = () => {
|
||||
Taro.showModal({
|
||||
title: '提示',
|
||||
content: '确定要退出登录吗?',
|
||||
success: function (res) {
|
||||
if (res.confirm) {
|
||||
// 使用 useUser hook 的 logoutUser 方法
|
||||
logoutUser();
|
||||
Taro.reLaunch({
|
||||
url: '/pages/index/index'
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<View className={'px-4'}>
|
||||
<Cell.Group divider={true} description={
|
||||
<View style={{display: 'inline-flex', alignItems: 'center'}}>
|
||||
<Text style={{marginTop: '12px'}}>我的服务</Text>
|
||||
</View>
|
||||
}>
|
||||
<Cell
|
||||
className="nutui-cell-clickable"
|
||||
title={
|
||||
<View style={{display: 'inline-flex', alignItems: 'center'}}>
|
||||
<Scan size={16}/>
|
||||
<Text className={'pl-3 text-sm'}>门店核销</Text>
|
||||
</View>
|
||||
}
|
||||
align="center"
|
||||
extra={<ArrowRight color="#cccccc" size={18}/>}
|
||||
onClick={() => {
|
||||
navTo('/user/wallet/index', true)
|
||||
}}
|
||||
/>
|
||||
<Cell
|
||||
className="nutui-cell-clickable"
|
||||
style={{
|
||||
display: 'none'
|
||||
}}
|
||||
title={
|
||||
<View style={{display: 'inline-flex', alignItems: 'center'}}>
|
||||
<LogisticsError size={16}/>
|
||||
<Text className={'pl-3 text-sm'}>我的钱包</Text>
|
||||
</View>
|
||||
}
|
||||
align="center"
|
||||
extra={<ArrowRight color="#cccccc" size={18}/>}
|
||||
onClick={() => {
|
||||
navTo('/user/wallet/index', true)
|
||||
}}
|
||||
/>
|
||||
<Cell
|
||||
className="nutui-cell-clickable"
|
||||
title={
|
||||
<View style={{display: 'inline-flex', alignItems: 'center'}}>
|
||||
<Location size={16}/>
|
||||
<Text className={'pl-3 text-sm'}>收货地址</Text>
|
||||
</View>
|
||||
}
|
||||
align="center"
|
||||
extra={<ArrowRight color="#cccccc" size={18}/>}
|
||||
onClick={() => {
|
||||
navTo('/user/address/index', true)
|
||||
}}
|
||||
/>
|
||||
<Cell
|
||||
className="nutui-cell-clickable"
|
||||
title={
|
||||
<View style={{display: 'inline-flex', alignItems: 'center'}}>
|
||||
<ShieldCheck size={16} color={isCertified() ? '#52c41a' : '#666'}/>
|
||||
<Text className={'pl-3 text-sm'}>实名认证</Text>
|
||||
{isCertified() && (
|
||||
<Text className={'pl-2 text-xs text-green-500'}>已认证</Text>
|
||||
)}
|
||||
</View>
|
||||
}
|
||||
align="center"
|
||||
extra={<ArrowRight color="#cccccc" size={18}/>}
|
||||
onClick={() => {
|
||||
navTo('/user/userVerify/index', true)
|
||||
}}
|
||||
/>
|
||||
<Cell
|
||||
className="nutui-cell-clickable"
|
||||
title={
|
||||
<View style={{display: 'inline-flex', alignItems: 'center'}}>
|
||||
<Ask size={16}/>
|
||||
<Text className={'pl-3 text-sm'}>常见问题</Text>
|
||||
</View>
|
||||
}
|
||||
align="center"
|
||||
extra={<ArrowRight color="#cccccc" size={18}/>}
|
||||
onClick={() => {
|
||||
navTo('/user/help/index')
|
||||
}}
|
||||
/>
|
||||
<Cell
|
||||
className="nutui-cell-clickable"
|
||||
title={
|
||||
<View style={{display: 'inline-flex', alignItems: 'center'}}>
|
||||
<Tips size={16}/>
|
||||
<Text className={'pl-3 text-sm'}>关于我们</Text>
|
||||
</View>
|
||||
}
|
||||
align="center"
|
||||
extra={<ArrowRight color="#cccccc" size={18}/>}
|
||||
onClick={() => {
|
||||
navTo('/user/about/index')
|
||||
}}
|
||||
/>
|
||||
</Cell.Group>
|
||||
<Cell.Group divider={true} description={
|
||||
<View style={{display: 'inline-flex', alignItems: 'center'}}>
|
||||
<Text style={{marginTop: '12px'}}>账号管理</Text>
|
||||
</View>
|
||||
}>
|
||||
<Cell
|
||||
className="nutui-cell-clickable"
|
||||
title="账号安全"
|
||||
align="center"
|
||||
extra={<ArrowRight color="#cccccc" size={18}/>}
|
||||
onClick={() => navTo('/user/profile/profile', true)}
|
||||
/>
|
||||
<Cell
|
||||
className="nutui-cell-clickable"
|
||||
title="退出登录"
|
||||
align="center"
|
||||
extra={<ArrowRight color="#cccccc" size={18}/>}
|
||||
onClick={onLogout}
|
||||
/>
|
||||
</Cell.Group>
|
||||
</View>
|
||||
</>
|
||||
)
|
||||
}
|
||||
export default UserCell
|
||||
249
src/admin/components/UserCard.tsx
Normal file
249
src/admin/components/UserCard.tsx
Normal file
@@ -0,0 +1,249 @@
|
||||
import {Button} from '@nutui/nutui-react-taro'
|
||||
import {Avatar, Tag} from '@nutui/nutui-react-taro'
|
||||
import {getUserInfo, getWxOpenId} from '@/api/layout';
|
||||
import Taro from '@tarojs/taro';
|
||||
import {useEffect, useState} from "react";
|
||||
import {User} from "@/api/system/user/model";
|
||||
import navTo from "@/utils/common";
|
||||
import {TenantId} from "@/config/app";
|
||||
import {getMyAvailableCoupons} from "@/api/shop/shopUserCoupon";
|
||||
import {useUser} from "@/hooks/useUser";
|
||||
|
||||
function UserCard() {
|
||||
const {getDisplayName, getRoleName} = useUser();
|
||||
const [IsLogin, setIsLogin] = useState<boolean>(false)
|
||||
const [userInfo, setUserInfo] = useState<User>()
|
||||
const [couponCount, setCouponCount] = useState(0)
|
||||
// const [pointsCount, setPointsCount] = useState(0)
|
||||
const [giftCount, setGiftCount] = useState(0)
|
||||
|
||||
useEffect(() => {
|
||||
// Taro.getSetting:获取用户的当前设置。返回值中只会出现小程序已经向用户请求过的权限。
|
||||
Taro.getSetting({
|
||||
success: (res) => {
|
||||
if (res.authSetting['scope.userInfo']) {
|
||||
// 用户已经授权过,可以直接获取用户信息
|
||||
console.log('用户已经授权过,可以直接获取用户信息')
|
||||
reload();
|
||||
} else {
|
||||
// 用户未授权,需要弹出授权窗口
|
||||
console.log('用户未授权,需要弹出授权窗口')
|
||||
showAuthModal();
|
||||
}
|
||||
}
|
||||
});
|
||||
}, []);
|
||||
|
||||
const loadUserStats = (userId: number) => {
|
||||
// 加载优惠券数量
|
||||
getMyAvailableCoupons()
|
||||
.then((coupons: any) => {
|
||||
setCouponCount(coupons?.length || 0)
|
||||
})
|
||||
.catch((error: any) => {
|
||||
console.error('Coupon count error:', error)
|
||||
})
|
||||
|
||||
// 加载积分数量
|
||||
console.log(userId)
|
||||
// getUserPointsStats(userId)
|
||||
// .then((res: any) => {
|
||||
// setPointsCount(res.currentPoints || 0)
|
||||
// })
|
||||
// .catch((error: any) => {
|
||||
// console.error('Points stats error:', error)
|
||||
// })
|
||||
// 加载礼品劵数量
|
||||
setGiftCount(0)
|
||||
// pageUserGiftLog({userId, page: 1, limit: 1}).then(res => {
|
||||
// setGiftCount(res.count || 0)
|
||||
// })
|
||||
}
|
||||
|
||||
const reload = () => {
|
||||
Taro.getUserInfo({
|
||||
success: (res) => {
|
||||
const avatar = res.userInfo.avatarUrl;
|
||||
setUserInfo({
|
||||
avatar,
|
||||
nickname: res.userInfo.nickName,
|
||||
sexName: res.userInfo.gender == 1 ? '男' : '女'
|
||||
})
|
||||
getUserInfo().then((data) => {
|
||||
if (data) {
|
||||
setUserInfo(data)
|
||||
setIsLogin(true);
|
||||
Taro.setStorageSync('UserId', data.userId)
|
||||
|
||||
// 加载用户统计数据
|
||||
if (data.userId) {
|
||||
loadUserStats(data.userId)
|
||||
}
|
||||
|
||||
// 获取openId
|
||||
if (!data.openid) {
|
||||
Taro.login({
|
||||
success: (res) => {
|
||||
getWxOpenId({code: res.code}).then(() => {
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}).catch(() => {
|
||||
console.log('未登录')
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const showAuthModal = () => {
|
||||
Taro.showModal({
|
||||
title: '授权提示',
|
||||
content: '需要获取您的用户信息',
|
||||
confirmText: '去授权',
|
||||
cancelText: '取消',
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
// 用户点击确认,打开授权设置页面
|
||||
openSetting();
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
const openSetting = () => {
|
||||
// Taro.openSetting:调起客户端小程序设置界面,返回用户设置的操作结果。设置界面只会出现小程序已经向用户请求过的权限。
|
||||
Taro.openSetting({
|
||||
success: (res) => {
|
||||
if (res.authSetting['scope.userInfo']) {
|
||||
// 用户授权成功,可以获取用户信息
|
||||
reload();
|
||||
} else {
|
||||
// 用户拒绝授权,提示授权失败
|
||||
Taro.showToast({
|
||||
title: '授权失败',
|
||||
icon: 'none'
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/* 获取用户手机号 */
|
||||
const handleGetPhoneNumber = ({detail}: {detail: {code?: string, encryptedData?: string, iv?: string}}) => {
|
||||
const {code, encryptedData, iv} = detail
|
||||
Taro.login({
|
||||
success: function () {
|
||||
if (code) {
|
||||
Taro.request({
|
||||
url: 'https://server.websoft.top/api/wx-login/loginByMpWxPhone',
|
||||
method: 'POST',
|
||||
data: {
|
||||
code,
|
||||
encryptedData,
|
||||
iv,
|
||||
notVerifyPhone: true,
|
||||
refereeId: 0,
|
||||
sceneType: 'save_referee',
|
||||
tenantId: TenantId
|
||||
},
|
||||
header: {
|
||||
'content-type': 'application/json',
|
||||
TenantId
|
||||
},
|
||||
success: function (res) {
|
||||
if (res.data.code == 1) {
|
||||
Taro.showToast({
|
||||
title: res.data.message,
|
||||
icon: 'error',
|
||||
duration: 2000
|
||||
})
|
||||
return false;
|
||||
}
|
||||
// 登录成功
|
||||
Taro.setStorageSync('access_token', res.data.data.access_token)
|
||||
Taro.setStorageSync('UserId', res.data.data.user.userId)
|
||||
setUserInfo(res.data.data.user)
|
||||
setIsLogin(true)
|
||||
}
|
||||
})
|
||||
} else {
|
||||
console.log('登录失败!')
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={'header-bg pt-20'}>
|
||||
<div className={'p-4'}>
|
||||
<div
|
||||
className={'user-card w-full flex flex-col justify-around rounded-xl shadow-sm'}
|
||||
style={{
|
||||
background: 'linear-gradient(to bottom, #ffffff, #ffffff)', // 这种情况建议使用类名来控制样式(引入外联样式)
|
||||
// width: '720rpx',
|
||||
// margin: '10px auto 0px auto',
|
||||
height: '170px',
|
||||
// borderRadius: '22px 22px 0 0',
|
||||
}}
|
||||
>
|
||||
<div className={'user-card-header flex w-full justify-between items-center pt-4'}>
|
||||
<div className={'flex items-center mx-4'}>
|
||||
{
|
||||
IsLogin ? (
|
||||
<Avatar size="large" src={userInfo?.avatar} shape="round"/>
|
||||
) : (
|
||||
<Button className={'text-black'} open-type="getPhoneNumber" onGetPhoneNumber={handleGetPhoneNumber}>
|
||||
<Avatar size="large" src={userInfo?.avatar} shape="round"/>
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
<div className={'user-info flex flex-col px-2'}>
|
||||
<div className={'py-1 text-black font-bold'}>{getDisplayName()}</div>
|
||||
{IsLogin ? (
|
||||
<div className={'grade text-xs py-1'}>
|
||||
<Tag type="success" round>
|
||||
<div className={'p-1'}>
|
||||
{getRoleName()}
|
||||
</div>
|
||||
</Tag>
|
||||
</div>
|
||||
) : ''}
|
||||
</div>
|
||||
</div>
|
||||
<div className={'mx-4 text-sm px-3 py-1 text-black border-gray-400 border-solid border-2 rounded-3xl'}
|
||||
onClick={() => navTo('/user/profile/profile', true)}>
|
||||
{'个人资料'}
|
||||
</div>
|
||||
</div>
|
||||
<div className={'flex justify-around mt-1'}>
|
||||
<div className={'item flex justify-center flex-col items-center'}
|
||||
onClick={() => navTo('/user/wallet/wallet', true)}>
|
||||
<span className={'text-sm text-gray-500'}>余额</span>
|
||||
<span className={'text-xl'}>¥ {userInfo?.balance || '0.00'}</span>
|
||||
</div>
|
||||
<div className={'item flex justify-center flex-col items-center'}
|
||||
onClick={() => navTo('/user/coupon/index', true)}>
|
||||
<span className={'text-sm text-gray-500'}>优惠券</span>
|
||||
<span className={'text-xl'}>{couponCount}</span>
|
||||
</div>
|
||||
<div className={'item flex justify-center flex-col items-center'}
|
||||
onClick={() => navTo('/user/gift/index', true)}>
|
||||
<span className={'text-sm text-gray-500'}>礼品卡</span>
|
||||
<span className={'text-xl'}>{giftCount}</span>
|
||||
</div>
|
||||
{/*<div className={'item flex justify-center flex-col items-center'}>*/}
|
||||
{/* <span className={'text-sm text-gray-500'}>积分</span>*/}
|
||||
{/* <span className={'text-xl'}>{pointsCount}</span>*/}
|
||||
{/*</div>*/}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
)
|
||||
}
|
||||
|
||||
export default UserCard;
|
||||
186
src/admin/components/UserCell.tsx
Normal file
186
src/admin/components/UserCell.tsx
Normal file
@@ -0,0 +1,186 @@
|
||||
import {Cell} from '@nutui/nutui-react-taro'
|
||||
import navTo from "@/utils/common";
|
||||
import Taro from '@tarojs/taro'
|
||||
import {View, Text} from '@tarojs/components'
|
||||
import {ArrowRight, ShieldCheck, LogisticsError, Location, Reward, Tips, Ask, Setting, Scan} from '@nutui/icons-react-taro'
|
||||
import {useUser} from '@/hooks/useUser'
|
||||
|
||||
const UserCell = () => {
|
||||
const {logoutUser, isCertified, hasRole, isAdmin} = useUser();
|
||||
|
||||
const onLogout = () => {
|
||||
Taro.showModal({
|
||||
title: '提示',
|
||||
content: '确定要退出登录吗?',
|
||||
success: function (res) {
|
||||
if (res.confirm) {
|
||||
// 使用 useUser hook 的 logoutUser 方法
|
||||
logoutUser();
|
||||
Taro.reLaunch({
|
||||
url: '/pages/index/index'
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<View className={'px-4'}>
|
||||
|
||||
{/*是否分销商*/}
|
||||
{!hasRole('dealer') && !isAdmin() && (
|
||||
<Cell
|
||||
className="nutui-cell-clickable"
|
||||
style={{
|
||||
backgroundImage: 'linear-gradient(to right bottom, #54a799, #177b73)',
|
||||
}}
|
||||
title={
|
||||
<View style={{display: 'inline-flex', alignItems: 'center'}} onClick={() => navTo('/dealer/index', true)}>
|
||||
<Reward className={'text-orange-100 '} size={16}/>
|
||||
<Text style={{fontSize: '16px'}} className={'pl-3 text-orange-100 font-medium'}>开通会员</Text>
|
||||
<Text className={'text-white opacity-80 pl-3'}>享优惠</Text>
|
||||
</View>
|
||||
}
|
||||
extra={<ArrowRight color="#cccccc" size={18}/>}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/*是否管理员*/}
|
||||
{isAdmin() && (
|
||||
<Cell
|
||||
className="nutui-cell-clickable"
|
||||
style={{
|
||||
backgroundImage: 'linear-gradient(to right bottom, #ff8e0c, #ed680d)',
|
||||
}}
|
||||
title={
|
||||
<View style={{display: 'inline-flex', alignItems: 'center'}} onClick={() => navTo('/admin/article/index', true)}>
|
||||
<Setting className={'text-orange-100 '} size={16}/>
|
||||
<Text style={{fontSize: '16px'}} className={'pl-3 text-orange-100 font-medium'}>管理中心</Text>
|
||||
</View>
|
||||
}
|
||||
extra={<ArrowRight color="#cccccc" size={18}/>}
|
||||
/>
|
||||
)}
|
||||
|
||||
<Cell.Group divider={true} description={
|
||||
<View style={{display: 'inline-flex', alignItems: 'center'}}>
|
||||
<Text style={{marginTop: '12px'}}>我的服务</Text>
|
||||
</View>
|
||||
}>
|
||||
<Cell
|
||||
className="nutui-cell-clickable"
|
||||
title={
|
||||
<View style={{display: 'inline-flex', alignItems: 'center'}}>
|
||||
<Scan size={16}/>
|
||||
<Text className={'pl-3 text-sm'}>门店核销</Text>
|
||||
</View>
|
||||
}
|
||||
align="center"
|
||||
extra={<ArrowRight color="#cccccc" size={18}/>}
|
||||
onClick={() => {
|
||||
navTo('/user/wallet/index', true)
|
||||
}}
|
||||
/>
|
||||
<Cell
|
||||
className="nutui-cell-clickable"
|
||||
style={{
|
||||
display: 'none'
|
||||
}}
|
||||
title={
|
||||
<View style={{display: 'inline-flex', alignItems: 'center'}}>
|
||||
<LogisticsError size={16}/>
|
||||
<Text className={'pl-3 text-sm'}>我的钱包</Text>
|
||||
</View>
|
||||
}
|
||||
align="center"
|
||||
extra={<ArrowRight color="#cccccc" size={18}/>}
|
||||
onClick={() => {
|
||||
navTo('/user/wallet/index', true)
|
||||
}}
|
||||
/>
|
||||
<Cell
|
||||
className="nutui-cell-clickable"
|
||||
title={
|
||||
<View style={{display: 'inline-flex', alignItems: 'center'}}>
|
||||
<Location size={16}/>
|
||||
<Text className={'pl-3 text-sm'}>收货地址</Text>
|
||||
</View>
|
||||
}
|
||||
align="center"
|
||||
extra={<ArrowRight color="#cccccc" size={18}/>}
|
||||
onClick={() => {
|
||||
navTo('/user/address/index', true)
|
||||
}}
|
||||
/>
|
||||
<Cell
|
||||
className="nutui-cell-clickable"
|
||||
title={
|
||||
<View style={{display: 'inline-flex', alignItems: 'center'}}>
|
||||
<ShieldCheck size={16} color={isCertified() ? '#52c41a' : '#666'}/>
|
||||
<Text className={'pl-3 text-sm'}>实名认证</Text>
|
||||
{isCertified() && (
|
||||
<Text className={'pl-2 text-xs text-green-500'}>已认证</Text>
|
||||
)}
|
||||
</View>
|
||||
}
|
||||
align="center"
|
||||
extra={<ArrowRight color="#cccccc" size={18}/>}
|
||||
onClick={() => {
|
||||
navTo('/user/userVerify/index', true)
|
||||
}}
|
||||
/>
|
||||
<Cell
|
||||
className="nutui-cell-clickable"
|
||||
title={
|
||||
<View style={{display: 'inline-flex', alignItems: 'center'}}>
|
||||
<Ask size={16}/>
|
||||
<Text className={'pl-3 text-sm'}>常见问题</Text>
|
||||
</View>
|
||||
}
|
||||
align="center"
|
||||
extra={<ArrowRight color="#cccccc" size={18}/>}
|
||||
onClick={() => {
|
||||
navTo('/user/help/index')
|
||||
}}
|
||||
/>
|
||||
<Cell
|
||||
className="nutui-cell-clickable"
|
||||
title={
|
||||
<View style={{display: 'inline-flex', alignItems: 'center'}}>
|
||||
<Tips size={16}/>
|
||||
<Text className={'pl-3 text-sm'}>关于我们</Text>
|
||||
</View>
|
||||
}
|
||||
align="center"
|
||||
extra={<ArrowRight color="#cccccc" size={18}/>}
|
||||
onClick={() => {
|
||||
navTo('/user/about/index')
|
||||
}}
|
||||
/>
|
||||
</Cell.Group>
|
||||
<Cell.Group divider={true} description={
|
||||
<View style={{display: 'inline-flex', alignItems: 'center'}}>
|
||||
<Text style={{marginTop: '12px'}}>账号管理</Text>
|
||||
</View>
|
||||
}>
|
||||
<Cell
|
||||
className="nutui-cell-clickable"
|
||||
title="账号安全"
|
||||
align="center"
|
||||
extra={<ArrowRight color="#cccccc" size={18}/>}
|
||||
onClick={() => navTo('/user/profile/profile', true)}
|
||||
/>
|
||||
<Cell
|
||||
className="nutui-cell-clickable"
|
||||
title="退出登录"
|
||||
align="center"
|
||||
extra={<ArrowRight color="#cccccc" size={18}/>}
|
||||
onClick={onLogout}
|
||||
/>
|
||||
</Cell.Group>
|
||||
</View>
|
||||
</>
|
||||
)
|
||||
}
|
||||
export default UserCell
|
||||
102
src/admin/components/UserFooter.tsx
Normal file
102
src/admin/components/UserFooter.tsx
Normal file
@@ -0,0 +1,102 @@
|
||||
import {loginBySms} from "@/api/passport/login";
|
||||
import {useState} from "react";
|
||||
import Taro from '@tarojs/taro'
|
||||
import {Popup} from '@nutui/nutui-react-taro'
|
||||
import {UserParam} from "@/api/system/user/model";
|
||||
import {Button} from '@nutui/nutui-react-taro'
|
||||
import {Form, Input} from '@nutui/nutui-react-taro'
|
||||
import {Copyright, Version} from "@/config/app";
|
||||
const UserFooter = () => {
|
||||
const [openLoginByPhone, setOpenLoginByPhone] = useState(false)
|
||||
const [clickNum, setClickNum] = useState<number>(0)
|
||||
const [FormData, setFormData] = useState<UserParam>(
|
||||
{
|
||||
phone: undefined,
|
||||
password: undefined
|
||||
}
|
||||
)
|
||||
|
||||
const onLoginByPhone = () => {
|
||||
setFormData({})
|
||||
setClickNum(clickNum + 1);
|
||||
if (clickNum > 10) {
|
||||
setOpenLoginByPhone(true);
|
||||
}
|
||||
}
|
||||
|
||||
const closeLoginByPhone = () => {
|
||||
setClickNum(0)
|
||||
setOpenLoginByPhone(false)
|
||||
}
|
||||
|
||||
// 提交表单
|
||||
const submitByPhone = (values: any) => {
|
||||
loginBySms({
|
||||
phone: values.phone,
|
||||
code: values.code
|
||||
}).then(() => {
|
||||
setOpenLoginByPhone(false);
|
||||
setTimeout(() => {
|
||||
Taro.reLaunch({
|
||||
url: '/pages/index/index'
|
||||
})
|
||||
},1000)
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className={'text-center py-4 w-full text-gray-300'} onClick={onLoginByPhone}>
|
||||
<div className={'text-xs text-gray-400 py-1'}>当前版本:{Version}</div>
|
||||
<div className={'text-xs text-gray-400 py-1'}>Copyright © { new Date().getFullYear() } {Copyright}</div>
|
||||
</div>
|
||||
|
||||
<Popup
|
||||
style={{width: '350px', padding: '10px'}}
|
||||
visible={openLoginByPhone}
|
||||
closeOnOverlayClick={false}
|
||||
closeable={true}
|
||||
onClose={closeLoginByPhone}
|
||||
>
|
||||
<Form
|
||||
style={{width: '350px',padding: '10px'}}
|
||||
divider
|
||||
initialValues={FormData}
|
||||
labelPosition="left"
|
||||
onFinish={(values) => submitByPhone(values)}
|
||||
footer={
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
width: '100%'
|
||||
}}
|
||||
>
|
||||
<Button nativeType="submit" block style={{backgroundColor: '#000000',color: '#ffffff'}}>
|
||||
提交
|
||||
</Button>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<Form.Item
|
||||
label={'手机号码'}
|
||||
name="phone"
|
||||
required
|
||||
rules={[{message: '手机号码'}]}
|
||||
>
|
||||
<Input placeholder="请输入手机号码" maxLength={11} type="text"/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={'短信验证码'}
|
||||
name="code"
|
||||
required
|
||||
rules={[{message: '短信验证码'}]}
|
||||
>
|
||||
<Input placeholder="请输入短信验证码" maxLength={6} type="text"/>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</Popup>
|
||||
</>
|
||||
)
|
||||
}
|
||||
export default UserFooter
|
||||
69
src/admin/components/UserOrder.tsx
Normal file
69
src/admin/components/UserOrder.tsx
Normal file
@@ -0,0 +1,69 @@
|
||||
import {useEffect} from "react";
|
||||
import navTo from "@/utils/common";
|
||||
import {View, Text} from '@tarojs/components';
|
||||
import {ArrowRight, Wallet, Comment, Transit, Refund, Package} from '@nutui/icons-react-taro';
|
||||
|
||||
function UserOrder() {
|
||||
|
||||
const reload = () => {
|
||||
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
reload()
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
<View className={'px-4 pb-2'}>
|
||||
<View
|
||||
className={'user-card w-full flex flex-col justify-around rounded-xl shadow-sm'}
|
||||
style={{
|
||||
background: 'linear-gradient(to bottom, #ffffff, #ffffff)', // 这种情况建议使用类名来控制样式(引入外联样式)
|
||||
// margin: '10px auto 0px auto',
|
||||
height: '120px',
|
||||
// borderRadius: '22px 22px 0 0',
|
||||
}}
|
||||
>
|
||||
<View className={'title-bar flex justify-between pt-2'}>
|
||||
<Text className={'title font-medium px-4'}>我的订单</Text>
|
||||
<View className={'more flex items-center px-2'} onClick={() => navTo('/user/order/order', true)}>
|
||||
<Text className={'text-xs text-gray-500'}>全部订单</Text>
|
||||
<ArrowRight color="#cccccc" size={12}/>
|
||||
</View>
|
||||
</View>
|
||||
<View className={'flex justify-around pb-1'}>
|
||||
<View className={'item flex justify-center flex-col items-center'}
|
||||
onClick={() => navTo('/user/order/order?statusFilter=0', true)}>
|
||||
<Wallet size={26} className={'font-normal text-gray-500'}/>
|
||||
<Text className={'text-sm text-gray-600 py-1'}>待付款</Text>
|
||||
</View>
|
||||
<View className={'item flex justify-center flex-col items-center'}
|
||||
onClick={() => navTo('/user/order/order?statusFilter=1', true)}>
|
||||
<Package size={26} className={'text-gray-500 font-normal'}/>
|
||||
<Text className={'text-sm text-gray-600 py-1'}>待发货</Text>
|
||||
</View>
|
||||
<View className={'item flex justify-center flex-col items-center'}
|
||||
onClick={() => navTo('/user/order/order?statusFilter=3', true)}>
|
||||
<Transit size={24} className={'text-gray-500 font-normal'}/>
|
||||
<Text className={'text-sm text-gray-600 py-1'}>待收货</Text>
|
||||
</View>
|
||||
<View className={'item flex justify-center flex-col items-center'}
|
||||
onClick={() => navTo('/user/order/order?statusFilter=5', true)}>
|
||||
<Comment size={24} className={'text-gray-500 font-normal'}/>
|
||||
<Text className={'text-sm text-gray-600 py-1'}>已完成</Text>
|
||||
</View>
|
||||
<View className={'item flex justify-center flex-col items-center'}
|
||||
onClick={() => navTo('/user/order/order?statusFilter=6', true)}>
|
||||
<Refund size={26} className={'font-normal text-gray-500'}/>
|
||||
<Text className={'text-sm text-gray-600 py-1'}>退货/售后</Text>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
</>
|
||||
|
||||
)
|
||||
}
|
||||
|
||||
export default UserOrder;
|
||||
3
src/admin/index.config.ts
Normal file
3
src/admin/index.config.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export default definePageConfig({
|
||||
navigationBarTitleText: '管理中心'
|
||||
})
|
||||
25
src/admin/index.tsx
Normal file
25
src/admin/index.tsx
Normal file
@@ -0,0 +1,25 @@
|
||||
import {useEffect} from 'react'
|
||||
import {useUser} from "@/hooks/useUser";
|
||||
import {Text} from '@tarojs/components';
|
||||
|
||||
function Admin() {
|
||||
const {
|
||||
isAdmin
|
||||
} = useUser();
|
||||
|
||||
useEffect(() => {
|
||||
}, []);
|
||||
|
||||
if (!isAdmin()) {
|
||||
return (
|
||||
<Text>您不是管理员</Text>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<>
|
||||
<Text>待开发...</Text>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default Admin
|
||||
@@ -68,6 +68,7 @@ export default defineAppConfig({
|
||||
{
|
||||
"root": "admin",
|
||||
"pages": [
|
||||
"index",
|
||||
"article/index",
|
||||
]
|
||||
}
|
||||
@@ -90,12 +91,12 @@ export default defineAppConfig({
|
||||
selectedIconPath: "assets/tabbar/home-active.png",
|
||||
text: "首页",
|
||||
},
|
||||
{
|
||||
pagePath: "pages/find/find",
|
||||
iconPath: "assets/tabbar/find.png",
|
||||
selectedIconPath: "assets/tabbar/find-active.png",
|
||||
text: "发现",
|
||||
},
|
||||
// {
|
||||
// pagePath: "pages/find/find",
|
||||
// iconPath: "assets/tabbar/find.png",
|
||||
// selectedIconPath: "assets/tabbar/find-active.png",
|
||||
// text: "发现",
|
||||
// },
|
||||
{
|
||||
pagePath: "pages/cart/cart",
|
||||
iconPath: "assets/tabbar/cart.png",
|
||||
|
||||
@@ -24,6 +24,20 @@ const SimpleQRCodeModal: React.FC<SimpleQRCodeModalProps> = ({
|
||||
faceValue
|
||||
}) => {
|
||||
|
||||
// const copyToClipboard = () => {
|
||||
// if (qrContent) {
|
||||
// Taro.setClipboardData({
|
||||
// data: qrContent,
|
||||
// success: () => {
|
||||
// Taro.showToast({
|
||||
// title: '兑换码已复制',
|
||||
// icon: 'success'
|
||||
// })
|
||||
// }
|
||||
// })
|
||||
// }
|
||||
// }
|
||||
|
||||
|
||||
return (
|
||||
<Popup
|
||||
@@ -38,9 +52,9 @@ const SimpleQRCodeModal: React.FC<SimpleQRCodeModalProps> = ({
|
||||
>
|
||||
<View className="p-6">
|
||||
{/* 标题 */}
|
||||
<View className="text-center mb-4">
|
||||
<View className="mb-4">
|
||||
<Text className="text-lg font-bold">礼品卡二维码</Text>
|
||||
<Text className="text-sm text-gray-500 mt-1">
|
||||
<Text className="text-sm text-gray-400 mt-1 px-2">
|
||||
请向商家出示此二维码
|
||||
</Text>
|
||||
</View>
|
||||
@@ -64,13 +78,22 @@ const SimpleQRCodeModal: React.FC<SimpleQRCodeModalProps> = ({
|
||||
{/* 二维码区域 */}
|
||||
<View className="text-center mb-4">
|
||||
<View className="p-4 bg-white border border-gray-200 rounded-lg">
|
||||
{qrContent ? (
|
||||
<img
|
||||
src={`https://api.qrserver.com/v1/create-qr-code/?size=200x200&data=${encodeURIComponent(qrContent)}`}
|
||||
alt="二维码"
|
||||
style={{width: '200px', height: '200px'}}
|
||||
className="mx-auto"
|
||||
/>
|
||||
) : (
|
||||
<View className="bg-gray-100 rounded flex items-center justify-center mx-auto"
|
||||
style={{width: '200px', height: '200px'}}>
|
||||
<View className="text-center">
|
||||
<Text className="text-gray-500 text-sm">二维码</Text>
|
||||
<Text className="text-xs text-gray-400 mt-1">ID: {qrContent ? qrContent.slice(-6) : '------'}</Text>
|
||||
<QrCode size="48" className="text-gray-400 mb-2"/>
|
||||
<Text className="text-gray-500 text-sm">生成中...</Text>
|
||||
</View>
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
</View>
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import {Button} from '@nutui/nutui-react-taro'
|
||||
import {Avatar, Tag} from '@nutui/nutui-react-taro'
|
||||
import {Scan} from '@nutui/icons-react-taro';
|
||||
import {getUserInfo, getWxOpenId} from '@/api/layout';
|
||||
import Taro from '@tarojs/taro';
|
||||
import {useEffect, useState} from "react";
|
||||
@@ -10,6 +11,9 @@ import {getMyAvailableCoupons} from "@/api/shop/shopUserCoupon";
|
||||
import {useUser} from "@/hooks/useUser";
|
||||
|
||||
function UserCard() {
|
||||
const {
|
||||
isAdmin
|
||||
} = useUser();
|
||||
const {getDisplayName, getRoleName} = useUser();
|
||||
const [IsLogin, setIsLogin] = useState<boolean>(false)
|
||||
const [userInfo, setUserInfo] = useState<User>()
|
||||
@@ -46,6 +50,7 @@ function UserCard() {
|
||||
|
||||
// 加载积分数量
|
||||
console.log(userId)
|
||||
setPointsCount(0)
|
||||
// getUserPointsStats(userId)
|
||||
// .then((res: any) => {
|
||||
// setPointsCount(res.currentPoints || 0)
|
||||
@@ -61,7 +66,6 @@ function UserCard() {
|
||||
}
|
||||
|
||||
const reload = () => {
|
||||
setPointsCount(0)
|
||||
Taro.getUserInfo({
|
||||
success: (res) => {
|
||||
const avatar = res.userInfo.avatarUrl;
|
||||
@@ -214,7 +218,8 @@ function UserCard() {
|
||||
) : ''}
|
||||
</div>
|
||||
</div>
|
||||
<div className={'mx-4 text-sm px-3 py-1 text-black border-gray-400 border-solid border-2 rounded-3xl'}
|
||||
{isAdmin() && <Scan onClick={() => navTo('/user/store/verification', true)} />}
|
||||
<div className={'mr-4 text-sm px-3 py-1 text-black border-gray-400 border-solid border-2 rounded-3xl'}
|
||||
onClick={() => navTo('/user/profile/profile', true)}>
|
||||
{'个人资料'}
|
||||
</div>
|
||||
@@ -223,7 +228,11 @@ function UserCard() {
|
||||
<div className={'item flex justify-center flex-col items-center'}
|
||||
onClick={() => navTo('/user/wallet/wallet', true)}>
|
||||
<span className={'text-sm text-gray-500'}>余额</span>
|
||||
<span className={'text-xl'}>¥ {userInfo?.balance || '0.00'}</span>
|
||||
<span className={'text-xl'}>{userInfo?.balance || '0.00'}</span>
|
||||
</div>
|
||||
<div className={'item flex justify-center flex-col items-center'}>
|
||||
<span className={'text-sm text-gray-500'}>积分</span>
|
||||
<span className={'text-xl'}>{pointsCount}</span>
|
||||
</div>
|
||||
<div className={'item flex justify-center flex-col items-center'}
|
||||
onClick={() => navTo('/user/coupon/index', true)}>
|
||||
@@ -235,10 +244,6 @@ function UserCard() {
|
||||
<span className={'text-sm text-gray-500'}>礼品卡</span>
|
||||
<span className={'text-xl'}>{giftCount}</span>
|
||||
</div>
|
||||
<div className={'item flex justify-center flex-col items-center'}>
|
||||
<span className={'text-sm text-gray-500'}>积分</span>
|
||||
<span className={'text-xl'}>{pointsCount}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -2,12 +2,34 @@ import {useEffect} from 'react'
|
||||
import UserCard from "./components/UserCard";
|
||||
import UserOrder from "./components/UserOrder";
|
||||
import UserCell from "./components/UserCell";
|
||||
import './user.scss'
|
||||
import UserFooter from "./components/UserFooter";
|
||||
import {useUser} from "@/hooks/useUser";
|
||||
import './user.scss'
|
||||
|
||||
function User() {
|
||||
const {
|
||||
isAdmin
|
||||
} = useUser();
|
||||
|
||||
useEffect(() => {
|
||||
}, []);
|
||||
|
||||
/**
|
||||
* 门店核销管理
|
||||
*/
|
||||
if (isAdmin()) {
|
||||
return <>
|
||||
<div className={'w-full'} style={{
|
||||
background: 'linear-gradient(to bottom, #e9fff2, #f9fafb)'
|
||||
}}>
|
||||
<UserCard/>
|
||||
<UserOrder/>
|
||||
<UserCell/>
|
||||
<UserFooter/>
|
||||
</div>
|
||||
</>
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className={'w-full'} style={{
|
||||
|
||||
@@ -6,15 +6,15 @@ import Taro from '@tarojs/taro'
|
||||
import dayjs from 'dayjs'
|
||||
import {getShopGiftByCode, updateShopGift} from "@/api/shop/shopGift";
|
||||
|
||||
interface VerificationData {
|
||||
type: string
|
||||
giftId: number
|
||||
giftCode: string
|
||||
verificationCode: string
|
||||
faceValue: string
|
||||
timestamp: number
|
||||
expireTime?: string
|
||||
}
|
||||
// interface VerificationData {
|
||||
// type: string
|
||||
// giftId: number
|
||||
// giftCode: string
|
||||
// verificationCode: string
|
||||
// faceValue: string
|
||||
// timestamp: number
|
||||
// expireTime?: string
|
||||
// }
|
||||
|
||||
interface GiftCardInfo {
|
||||
id: number
|
||||
@@ -40,7 +40,8 @@ const StoreVerification: React.FC = () => {
|
||||
success: (res) => {
|
||||
console.log('扫码结果:', res.result)
|
||||
setScanResult(res.result)
|
||||
parseQRCode(res.result)
|
||||
verificationQRCode(res.result)
|
||||
// parseQRCode(res.result)
|
||||
},
|
||||
fail: (err) => {
|
||||
console.error('扫码失败:', err)
|
||||
@@ -53,41 +54,57 @@ const StoreVerification: React.FC = () => {
|
||||
}
|
||||
|
||||
// 解析二维码数据
|
||||
const parseQRCode = (qrData: string) => {
|
||||
try {
|
||||
const data: VerificationData = JSON.parse(qrData)
|
||||
// const parseQRCode = (qrData: string) => {
|
||||
// try {
|
||||
// const data: VerificationData = JSON.parse(qrData)
|
||||
//
|
||||
// if (data.type === 'gift_card_verification') {
|
||||
// setVerificationCode(data.verificationCode)
|
||||
// console.log(data.verificationCode,'...vaerrr')
|
||||
// } else {
|
||||
// throw new Error('无效的二维码格式')
|
||||
// }
|
||||
// } catch (error) {
|
||||
// console.error('解析二维码失败:', error)
|
||||
// Taro.showToast({
|
||||
// title: '无效的二维码',
|
||||
// icon: 'error'
|
||||
// })
|
||||
// }
|
||||
// }
|
||||
|
||||
if (data.type === 'gift_card_verification') {
|
||||
setVerificationCode(data.verificationCode)
|
||||
// 模拟获取礼品卡信息
|
||||
mockGetGiftInfo(data)
|
||||
} else {
|
||||
throw new Error('无效的二维码格式')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('解析二维码失败:', error)
|
||||
Taro.showToast({
|
||||
title: '无效的二维码',
|
||||
// 扫码核销操作
|
||||
const verificationQRCode = async (code:string) => {
|
||||
const gift = await getShopGiftByCode(code)
|
||||
|
||||
if(gift.status == 1){
|
||||
return Taro.showToast({
|
||||
title: '此礼品码已使用',
|
||||
icon: 'error'
|
||||
})
|
||||
}
|
||||
if(gift.status == 2){
|
||||
return Taro.showToast({
|
||||
title: '此礼品码已失效',
|
||||
icon: 'error'
|
||||
})
|
||||
}
|
||||
|
||||
// 模拟获取礼品卡信息(实际应该调用API)
|
||||
const mockGetGiftInfo = (data: VerificationData) => {
|
||||
// 这里应该调用后端API验证礼品卡信息
|
||||
const mockGiftInfo: GiftCardInfo = {
|
||||
id: data.giftId,
|
||||
name: '礼品卡',
|
||||
goodsName: '星巴克咖啡券',
|
||||
faceValue: data.faceValue,
|
||||
type: 20,
|
||||
status: 0,
|
||||
expireTime: data.expireTime,
|
||||
code: data.giftCode
|
||||
if(gift.userId == 0){
|
||||
return Taro.showToast({
|
||||
title: '此礼品码未认领',
|
||||
icon: 'error'
|
||||
})
|
||||
}
|
||||
|
||||
setGiftInfo(mockGiftInfo)
|
||||
updateShopGift({
|
||||
...gift,
|
||||
status: 1,
|
||||
takeTime: dayjs.unix(Date.now() / 1000).format('YYYY-MM-DD HH:mm:ss')
|
||||
}).then(() => {
|
||||
Taro.showToast({
|
||||
title: '核销成功',
|
||||
icon: 'success'
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// 手动输入核销码验证
|
||||
@@ -106,7 +123,8 @@ const StoreVerification: React.FC = () => {
|
||||
const giftCard = await getShopGiftByCode(verificationCode.trim())
|
||||
await updateShopGift({
|
||||
...giftCard,
|
||||
status: 1
|
||||
status: 1,
|
||||
takeTime: dayjs.unix(Date.now() / 1000).format('YYYY-MM-DD HH:mm:ss')
|
||||
})
|
||||
Taro.showToast({
|
||||
title: '核销成功',
|
||||
|
||||
Reference in New Issue
Block a user