```
feat(registration): 优化经销商注册流程并增加地址定位功能 - 修改导航栏标题从“邀请注册”为“注册成为会员” - 修复重复提交问题并移除不必要的submitting状态 - 增加昵称和头像的必填验证提示 - 添加用户角色缺失时的默认角色写入机制 - 集成地图选点功能,支持经纬度获取和地址解析 - 实现微信地址导入功能,自动填充基本信息 - 增加定位权限检查和错误处理机制 - 添加.gitignore规则忽略备份文件夹src__bak - 移除已废弃的银行卡和客户管理页面代码 - 优化表单验证规则和错误提示信息 - 实现经销商注册成功后自动跳转到“我的”页面 - 添加用户信息缓存刷新机制确保角色信息同步 ```
This commit is contained in:
255
src/admin/components/UserCard.tsx
Normal file
255
src/admin/components/UserCard.tsx
Normal file
@@ -0,0 +1,255 @@
|
||||
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";
|
||||
import {getStoredInviteParams} from "@/utils/invite";
|
||||
|
||||
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
|
||||
|
||||
// 获取存储的邀请参数
|
||||
const inviteParams = getStoredInviteParams()
|
||||
const refereeId = inviteParams?.inviter ? parseInt(inviteParams.inviter) : 0
|
||||
|
||||
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: refereeId, // 使用解析出的推荐人ID
|
||||
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;
|
||||
@@ -1,295 +1,35 @@
|
||||
import React from 'react'
|
||||
import {View, Text} from '@tarojs/components'
|
||||
import {ConfigProvider, Button, Grid, Avatar} from '@nutui/nutui-react-taro'
|
||||
import {
|
||||
User,
|
||||
Shopping,
|
||||
Dongdong,
|
||||
ArrowRight,
|
||||
Purse,
|
||||
People
|
||||
} from '@nutui/icons-react-taro'
|
||||
import {useDealerUser} from '@/hooks/useDealerUser'
|
||||
import { useThemeStyles } from '@/hooks/useTheme'
|
||||
import {businessGradients, cardGradients, gradientUtils} from '@/styles/gradients'
|
||||
import Taro from '@tarojs/taro'
|
||||
import {useEffect} from 'react'
|
||||
import {useUser} from "@/hooks/useUser";
|
||||
import {Empty} from '@nutui/nutui-react-taro';
|
||||
import {Text} from '@tarojs/components';
|
||||
|
||||
const DealerIndex: React.FC = () => {
|
||||
function Admin() {
|
||||
const {
|
||||
dealerUser,
|
||||
error,
|
||||
refresh,
|
||||
} = useDealerUser()
|
||||
isAdmin
|
||||
} = useUser();
|
||||
|
||||
// 使用主题样式
|
||||
const themeStyles = useThemeStyles()
|
||||
useEffect(() => {
|
||||
}, []);
|
||||
|
||||
// 导航到各个功能页面
|
||||
const navigateToPage = (url: string) => {
|
||||
Taro.navigateTo({url})
|
||||
}
|
||||
|
||||
// 格式化金额
|
||||
const formatMoney = (money?: string) => {
|
||||
if (!money) return '0.00'
|
||||
return parseFloat(money).toFixed(2)
|
||||
}
|
||||
|
||||
// 格式化时间
|
||||
const formatTime = (time?: string) => {
|
||||
if (!time) return '-'
|
||||
return new Date(time).toLocaleDateString()
|
||||
}
|
||||
|
||||
// 获取用户主题
|
||||
const userTheme = gradientUtils.getThemeByUserId(dealerUser?.userId)
|
||||
|
||||
// 获取渐变背景
|
||||
const getGradientBackground = (themeColor?: string) => {
|
||||
if (themeColor) {
|
||||
const darkerColor = gradientUtils.adjustColorBrightness(themeColor, -30)
|
||||
return gradientUtils.createGradient(themeColor, darkerColor)
|
||||
}
|
||||
return userTheme.background
|
||||
}
|
||||
|
||||
console.log(getGradientBackground(),'getGradientBackground()')
|
||||
|
||||
if (error) {
|
||||
if (!isAdmin()) {
|
||||
return (
|
||||
<View className="p-4">
|
||||
<View className="bg-red-50 border border-red-200 rounded-lg p-4 mb-4">
|
||||
<Text className="text-red-600">{error}</Text>
|
||||
</View>
|
||||
<Button type="primary" onClick={refresh}>
|
||||
重试
|
||||
</Button>
|
||||
</View>
|
||||
)
|
||||
<Empty
|
||||
description="您不是管理员"
|
||||
imageSize={80}
|
||||
style={{
|
||||
backgroundColor: 'transparent',
|
||||
height: 'calc(100vh - 200px)'
|
||||
}}
|
||||
>
|
||||
|
||||
</Empty>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<View className="bg-gray-100 min-h-screen">
|
||||
<View>
|
||||
{/*头部信息*/}
|
||||
{dealerUser && (
|
||||
<View className="px-4 py-6 relative overflow-hidden" style={themeStyles.primaryBackground}>
|
||||
{/* 装饰性背景元素 - 小程序兼容版本 */}
|
||||
<View className="absolute w-32 h-32 rounded-full" style={{
|
||||
backgroundColor: 'rgba(255, 255, 255, 0.1)',
|
||||
top: '-16px',
|
||||
right: '-16px'
|
||||
}}></View>
|
||||
<View className="absolute w-24 h-24 rounded-full" style={{
|
||||
backgroundColor: 'rgba(255, 255, 255, 0.08)',
|
||||
bottom: '-12px',
|
||||
left: '-12px'
|
||||
}}></View>
|
||||
<View className="absolute w-16 h-16 rounded-full" style={{
|
||||
backgroundColor: 'rgba(255, 255, 255, 0.05)',
|
||||
top: '60px',
|
||||
left: '120px'
|
||||
}}></View>
|
||||
<View className="flex items-center justify-between relative z-10 mb-4">
|
||||
<Avatar
|
||||
size="50"
|
||||
src={dealerUser?.qrcode}
|
||||
icon={<User/>}
|
||||
className="mr-4"
|
||||
style={{
|
||||
border: '2px solid rgba(255, 255, 255, 0.3)'
|
||||
}}
|
||||
/>
|
||||
<View className="flex-1 flex-col">
|
||||
<View className="text-white text-lg font-bold mb-1" style={{
|
||||
}}>
|
||||
{dealerUser?.realName || '分销商'}
|
||||
</View>
|
||||
<View className="text-sm" style={{
|
||||
color: 'rgba(255, 255, 255, 0.8)'
|
||||
}}>
|
||||
ID: {dealerUser.userId} | 推荐人: {dealerUser.refereeId || '无'}
|
||||
</View>
|
||||
</View>
|
||||
<View className="text-right hidden">
|
||||
<Text className="text-xs" style={{
|
||||
color: 'rgba(255, 255, 255, 0.9)'
|
||||
}}>加入时间</Text>
|
||||
<Text className="text-xs" style={{
|
||||
color: 'rgba(255, 255, 255, 0.7)'
|
||||
}}>
|
||||
{formatTime(dealerUser.createTime)}
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
)}
|
||||
|
||||
{/* 佣金统计卡片 */}
|
||||
{dealerUser && (
|
||||
<View className="mx-4 -mt-6 rounded-xl p-4 relative z-10" style={cardGradients.elevated}>
|
||||
<View className="mb-4">
|
||||
<Text className="font-semibold text-gray-800">佣金统计</Text>
|
||||
</View>
|
||||
<View className="grid grid-cols-3 gap-3">
|
||||
<View className="text-center p-3 rounded-lg flex flex-col" style={{
|
||||
background: businessGradients.money.available
|
||||
}}>
|
||||
<Text className="text-lg font-bold mb-1 text-white">
|
||||
{formatMoney(dealerUser.money)}
|
||||
</Text>
|
||||
<Text className="text-xs" style={{ color: 'rgba(255, 255, 255, 0.9)' }}>可提现</Text>
|
||||
</View>
|
||||
<View className="text-center p-3 rounded-lg flex flex-col" style={{
|
||||
background: businessGradients.money.frozen
|
||||
}}>
|
||||
<Text className="text-lg font-bold mb-1 text-white">
|
||||
{formatMoney(dealerUser.freezeMoney)}
|
||||
</Text>
|
||||
<Text className="text-xs" style={{ color: 'rgba(255, 255, 255, 0.9)' }}>冻结中</Text>
|
||||
</View>
|
||||
<View className="text-center p-3 rounded-lg flex flex-col" style={{
|
||||
background: businessGradients.money.total
|
||||
}}>
|
||||
<Text className="text-lg font-bold mb-1 text-white">
|
||||
{formatMoney(dealerUser.totalMoney)}
|
||||
</Text>
|
||||
<Text className="text-xs" style={{ color: 'rgba(255, 255, 255, 0.9)' }}>累计收益</Text>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
)}
|
||||
|
||||
{/* 团队统计 */}
|
||||
{dealerUser && (
|
||||
<View className="bg-white mx-4 mt-4 rounded-xl p-4 hidden">
|
||||
<View className="flex items-center justify-between mb-4">
|
||||
<Text className="font-semibold text-gray-800">我的邀请</Text>
|
||||
<View
|
||||
className="text-gray-400 text-sm flex items-center"
|
||||
onClick={() => navigateToPage('/dealer/team/index')}
|
||||
>
|
||||
<Text>查看详情</Text>
|
||||
<ArrowRight size="12"/>
|
||||
</View>
|
||||
</View>
|
||||
<View className="grid grid-cols-3 gap-4">
|
||||
<View className="text-center grid">
|
||||
<Text className="text-xl font-bold text-purple-500 mb-1">
|
||||
{dealerUser.firstNum || 0}
|
||||
</Text>
|
||||
<Text className="text-xs text-gray-500">一级成员</Text>
|
||||
</View>
|
||||
<View className="text-center grid">
|
||||
<Text className="text-xl font-bold text-indigo-500 mb-1">
|
||||
{dealerUser.secondNum || 0}
|
||||
</Text>
|
||||
<Text className="text-xs text-gray-500">二级成员</Text>
|
||||
</View>
|
||||
<View className="text-center grid">
|
||||
<Text className="text-xl font-bold text-pink-500 mb-1">
|
||||
{dealerUser.thirdNum || 0}
|
||||
</Text>
|
||||
<Text className="text-xs text-gray-500">三级成员</Text>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
)}
|
||||
|
||||
{/* 功能导航 */}
|
||||
<View className="bg-white mx-4 mt-4 rounded-xl p-4">
|
||||
<View className="font-semibold mb-4 text-gray-800">分销工具</View>
|
||||
<ConfigProvider>
|
||||
<Grid
|
||||
columns={4}
|
||||
className="no-border-grid"
|
||||
style={{
|
||||
'--nutui-grid-border-color': 'transparent',
|
||||
'--nutui-grid-item-border-width': '0px',
|
||||
border: 'none'
|
||||
} as React.CSSProperties}
|
||||
>
|
||||
<Grid.Item text="分销订单" onClick={() => navigateToPage('/dealer/orders/index')}>
|
||||
<View className="text-center">
|
||||
<View className="w-12 h-12 bg-blue-50 rounded-xl flex items-center justify-center mx-auto mb-2">
|
||||
<Shopping color="#3b82f6" size="20"/>
|
||||
</View>
|
||||
</View>
|
||||
</Grid.Item>
|
||||
|
||||
<Grid.Item text={'提现申请'} onClick={() => navigateToPage('/dealer/withdraw/index')}>
|
||||
<View className="text-center">
|
||||
<View className="w-12 h-12 bg-green-50 rounded-xl flex items-center justify-center mx-auto mb-2">
|
||||
<Purse color="#10b981" size="20"/>
|
||||
</View>
|
||||
</View>
|
||||
</Grid.Item>
|
||||
|
||||
<Grid.Item text={'我的邀请'} onClick={() => navigateToPage('/dealer/team/index')}>
|
||||
<View className="text-center">
|
||||
<View className="w-12 h-12 bg-purple-50 rounded-xl flex items-center justify-center mx-auto mb-2">
|
||||
<People color="#8b5cf6" size="20"/>
|
||||
</View>
|
||||
</View>
|
||||
</Grid.Item>
|
||||
|
||||
<Grid.Item text={'我的邀请码'} onClick={() => navigateToPage('/dealer/qrcode/index')}>
|
||||
<View className="text-center">
|
||||
<View className="w-12 h-12 bg-orange-50 rounded-xl flex items-center justify-center mx-auto mb-2">
|
||||
<Dongdong color="#f59e0b" size="20"/>
|
||||
</View>
|
||||
</View>
|
||||
</Grid.Item>
|
||||
</Grid>
|
||||
|
||||
{/* 第二行功能 */}
|
||||
{/*<Grid*/}
|
||||
{/* columns={4}*/}
|
||||
{/* className="no-border-grid mt-4"*/}
|
||||
{/* style={{*/}
|
||||
{/* '--nutui-grid-border-color': 'transparent',*/}
|
||||
{/* '--nutui-grid-item-border-width': '0px',*/}
|
||||
{/* border: 'none'*/}
|
||||
{/* } as React.CSSProperties}*/}
|
||||
{/*>*/}
|
||||
{/* <Grid.Item text={'邀请统计'} onClick={() => navigateToPage('/dealer/invite-stats/index')}>*/}
|
||||
{/* <View className="text-center">*/}
|
||||
{/* <View className="w-12 h-12 bg-indigo-50 rounded-xl flex items-center justify-center mx-auto mb-2">*/}
|
||||
{/* <Presentation color="#6366f1" size="20"/>*/}
|
||||
{/* </View>*/}
|
||||
{/* </View>*/}
|
||||
{/* </Grid.Item>*/}
|
||||
|
||||
{/* /!* 预留其他功能位置 *!/*/}
|
||||
{/* <Grid.Item text={''}>*/}
|
||||
{/* <View className="text-center">*/}
|
||||
{/* <View className="w-12 h-12 bg-gray-50 rounded-xl flex items-center justify-center mx-auto mb-2">*/}
|
||||
{/* </View>*/}
|
||||
{/* </View>*/}
|
||||
{/* </Grid.Item>*/}
|
||||
|
||||
{/* <Grid.Item text={''}>*/}
|
||||
{/* <View className="text-center">*/}
|
||||
{/* <View className="w-12 h-12 bg-gray-50 rounded-xl flex items-center justify-center mx-auto mb-2">*/}
|
||||
{/* </View>*/}
|
||||
{/* </View>*/}
|
||||
{/* </Grid.Item>*/}
|
||||
|
||||
{/* <Grid.Item text={''}>*/}
|
||||
{/* <View className="text-center">*/}
|
||||
{/* <View className="w-12 h-12 bg-gray-50 rounded-xl flex items-center justify-center mx-auto mb-2">*/}
|
||||
{/* </View>*/}
|
||||
{/* </View>*/}
|
||||
{/* </Grid.Item>*/}
|
||||
{/*</Grid>*/}
|
||||
</ConfigProvider>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{/* 底部安全区域 */}
|
||||
<View className="h-20"></View>
|
||||
</View>
|
||||
<>
|
||||
<Text>待开发...</Text>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default DealerIndex
|
||||
export default Admin
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
export default definePageConfig({
|
||||
navigationBarTitleText: '实名审核'
|
||||
})
|
||||
@@ -1,319 +0,0 @@
|
||||
import React, {useState, useEffect, useCallback} from 'react'
|
||||
import {View, Text} from '@tarojs/components'
|
||||
import {
|
||||
Space,
|
||||
Tabs,
|
||||
Tag,
|
||||
Empty,
|
||||
Loading,
|
||||
PullToRefresh,
|
||||
Button,
|
||||
Dialog,
|
||||
Image,
|
||||
ImagePreview,
|
||||
TextArea
|
||||
} from '@nutui/nutui-react-taro'
|
||||
import Taro from '@tarojs/taro'
|
||||
import {useDealerUser} from '@/hooks/useDealerUser'
|
||||
import {pageUserVerify, updateUserVerify} from '@/api/system/userVerify'
|
||||
import type {ShopDealerWithdraw} from '@/api/shop/shopDealerWithdraw/model'
|
||||
import {UserVerify} from "@/api/system/userVerify/model";
|
||||
|
||||
const UserVeirfyAdmin: React.FC = () => {
|
||||
const [activeTab, setActiveTab] = useState<string | number>(0)
|
||||
const [loading, setLoading] = useState<boolean>(false)
|
||||
const [refreshing, setRefreshing] = useState<boolean>(false)
|
||||
const [list, setList] = useState<UserVerify[]>([])
|
||||
const [rejectDialogVisible, setRejectDialogVisible] = useState<boolean>(false)
|
||||
const [rejectReason, setRejectReason] = useState<string>('')
|
||||
const [currentRecord, setCurrentRecord] = useState<ShopDealerWithdraw | null>(null)
|
||||
const [showPreview, setShowPreview] = useState(false)
|
||||
const [showPreview2, setShowPreview2] = useState(false)
|
||||
|
||||
const {dealerUser} = useDealerUser()
|
||||
|
||||
// Tab 切换处理函数
|
||||
const handleTabChange = (value: string | number) => {
|
||||
console.log('Tab切换到:', value)
|
||||
setActiveTab(value)
|
||||
// activeTab变化会自动触发useEffect重新获取数据,无需手动调用
|
||||
}
|
||||
|
||||
// 获取审核记录
|
||||
const fetchWithdrawRecords = useCallback(async () => {
|
||||
if (!dealerUser?.userId) return
|
||||
|
||||
try {
|
||||
setLoading(true)
|
||||
const currentStatus = Number(activeTab)
|
||||
const result = await pageUserVerify({
|
||||
page: 1,
|
||||
limit: 100,
|
||||
status: currentStatus // 后端筛选,提高性能
|
||||
})
|
||||
|
||||
if (result?.list) {
|
||||
const processedRecords = result.list.map(record => ({
|
||||
...record
|
||||
}))
|
||||
setList(processedRecords)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取审核记录失败:', error)
|
||||
Taro.showToast({
|
||||
title: '获取审核记录失败',
|
||||
icon: 'none'
|
||||
})
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}, [dealerUser?.userId, activeTab])
|
||||
|
||||
|
||||
// 刷新数据
|
||||
const handleRefresh = async () => {
|
||||
setRefreshing(true)
|
||||
await Promise.all([fetchWithdrawRecords()])
|
||||
setRefreshing(false)
|
||||
}
|
||||
|
||||
// 审核通过
|
||||
const handleApprove = async (record: ShopDealerWithdraw) => {
|
||||
try {
|
||||
await updateUserVerify({
|
||||
...record,
|
||||
status: 1, // 审核通过
|
||||
})
|
||||
|
||||
Taro.showToast({
|
||||
title: '审核通过',
|
||||
icon: 'success'
|
||||
})
|
||||
|
||||
await fetchWithdrawRecords()
|
||||
} catch (error: any) {
|
||||
if (error !== 'cancel') {
|
||||
console.error('审核通过失败:', error)
|
||||
Taro.showToast({
|
||||
title: error.message || '操作失败',
|
||||
icon: 'none'
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 驳回申请
|
||||
const handleReject = (record: ShopDealerWithdraw) => {
|
||||
setCurrentRecord(record)
|
||||
setRejectReason('')
|
||||
setRejectDialogVisible(true)
|
||||
}
|
||||
|
||||
// 确认驳回
|
||||
const confirmReject = async () => {
|
||||
if (!rejectReason.trim()) {
|
||||
Taro.showToast({
|
||||
title: '请输入驳回原因',
|
||||
icon: 'none'
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
await updateUserVerify({
|
||||
...currentRecord!,
|
||||
status: 2, // 驳回
|
||||
comments: rejectReason.trim()
|
||||
})
|
||||
|
||||
Taro.showToast({
|
||||
title: '已驳回',
|
||||
icon: 'success'
|
||||
})
|
||||
|
||||
setRejectDialogVisible(false)
|
||||
setCurrentRecord(null)
|
||||
setRejectReason('')
|
||||
await fetchWithdrawRecords()
|
||||
} catch (error: any) {
|
||||
console.error('驳回失败:', error)
|
||||
Taro.showToast({
|
||||
title: error.message || '操作失败',
|
||||
icon: 'none'
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 初始化加载数据
|
||||
useEffect(() => {
|
||||
if (dealerUser?.userId) {
|
||||
fetchWithdrawRecords().then()
|
||||
}
|
||||
}, [fetchWithdrawRecords])
|
||||
|
||||
const getStatusText = (status?: number) => {
|
||||
switch (status) {
|
||||
case 0:
|
||||
return '待审核'
|
||||
case 1:
|
||||
return '审核通过'
|
||||
case 2:
|
||||
return '已驳回'
|
||||
default:
|
||||
return '未知'
|
||||
}
|
||||
}
|
||||
|
||||
const getStatusColor = (status?: number) => {
|
||||
switch (status) {
|
||||
case 0:
|
||||
return 'warning'
|
||||
case 1:
|
||||
return 'success'
|
||||
case 2:
|
||||
return 'danger'
|
||||
default:
|
||||
return 'default'
|
||||
}
|
||||
}
|
||||
|
||||
const renderWithdrawRecords = () => {
|
||||
console.log('渲染审核记录:', {loading, recordsCount: list.length, dealerUserId: dealerUser?.userId})
|
||||
|
||||
return (
|
||||
<PullToRefresh
|
||||
disabled={refreshing}
|
||||
onRefresh={handleRefresh}
|
||||
>
|
||||
<View>
|
||||
{loading ? (
|
||||
<View className="text-center py-8">
|
||||
<Loading/>
|
||||
<Text className="text-gray-500 mt-2">加载中...</Text>
|
||||
</View>
|
||||
) : list.length > 0 ? (
|
||||
list.map(record => (
|
||||
<View key={record.id} className="rounded-lg bg-gray-50 p-4 mb-3 shadow-sm">
|
||||
<View className="flex justify-between items-start mb-3">
|
||||
<Space direction={'vertical'}>
|
||||
<Text className="font-semibold text-gray-800">
|
||||
{record.realName}
|
||||
</Text>
|
||||
<Text className="font-normal text-sm text-gray-500">
|
||||
{record.phone}
|
||||
</Text>
|
||||
<Text className="font-normal text-sm text-gray-500">
|
||||
身份证号码:{record.idCard}
|
||||
</Text>
|
||||
</Space>
|
||||
<Tag type={getStatusColor(record.status)}>
|
||||
{getStatusText(record.status)}
|
||||
</Tag>
|
||||
</View>
|
||||
|
||||
<View className="flex gap-2 mb-2">
|
||||
<Image src={record.sfz1} height={100} onClick={() => setShowPreview(true)}/>
|
||||
<Image src={record.sfz2} height={100} onClick={() => setShowPreview2(true)}/>
|
||||
</View>
|
||||
<ImagePreview
|
||||
autoPlay
|
||||
images={[{src: `${record.sfz1}`}]}
|
||||
visible={showPreview}
|
||||
onClose={() => setShowPreview(false)}
|
||||
/>
|
||||
<ImagePreview
|
||||
autoPlay
|
||||
images={[{src: `${record.sfz1}`}]}
|
||||
visible={showPreview2}
|
||||
onClose={() => setShowPreview2(false)}
|
||||
/>
|
||||
|
||||
<View className="text-xs text-gray-400">
|
||||
<Text>申请时间:{record.createTime}</Text>
|
||||
{record.status == 1 && (
|
||||
<Text className="block mt-1">
|
||||
审核时间:{record.updateTime}
|
||||
</Text>
|
||||
)}
|
||||
{record.status == 2 && (
|
||||
<Text className="block mt-1 text-red-500">
|
||||
驳回原因:{record.comments}
|
||||
</Text>
|
||||
)}
|
||||
</View>
|
||||
|
||||
{/* 操作按钮 */}
|
||||
{record.status === 0 && (
|
||||
<View className="flex gap-2 mt-3">
|
||||
<Button
|
||||
type="success"
|
||||
size="small"
|
||||
className="flex-1"
|
||||
onClick={() => handleApprove(record)}
|
||||
>
|
||||
审核通过
|
||||
</Button>
|
||||
<Button
|
||||
type="danger"
|
||||
size="small"
|
||||
className="flex-1"
|
||||
onClick={() => handleReject(record)}
|
||||
>
|
||||
驳回
|
||||
</Button>
|
||||
</View>
|
||||
)}
|
||||
|
||||
</View>
|
||||
))
|
||||
) : (
|
||||
<Empty description="暂无申请记录"/>
|
||||
)}
|
||||
</View>
|
||||
</PullToRefresh>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<View className="bg-gray-50 min-h-screen">
|
||||
<Tabs value={activeTab} onChange={handleTabChange}>
|
||||
<Tabs.TabPane title="待审核" value="0">
|
||||
{renderWithdrawRecords()}
|
||||
</Tabs.TabPane>
|
||||
|
||||
<Tabs.TabPane title="已通过" value="1">
|
||||
{renderWithdrawRecords()}
|
||||
</Tabs.TabPane>
|
||||
|
||||
<Tabs.TabPane title="已驳回" value="2">
|
||||
{renderWithdrawRecords()}
|
||||
</Tabs.TabPane>
|
||||
</Tabs>
|
||||
|
||||
{/* 驳回原因对话框 */}
|
||||
<Dialog
|
||||
visible={rejectDialogVisible}
|
||||
title="驳回原因"
|
||||
onCancel={() => {
|
||||
setRejectDialogVisible(false)
|
||||
setCurrentRecord(null)
|
||||
setRejectReason('')
|
||||
}}
|
||||
onConfirm={confirmReject}
|
||||
>
|
||||
<View className="p-4">
|
||||
<TextArea
|
||||
placeholder="请输入驳回原因"
|
||||
value={rejectReason}
|
||||
onChange={(value) => setRejectReason(value)}
|
||||
maxLength={200}
|
||||
rows={4}
|
||||
/>
|
||||
</View>
|
||||
</Dialog>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
export default UserVeirfyAdmin
|
||||
320
src/api/afterSale.ts
Normal file
320
src/api/afterSale.ts
Normal file
@@ -0,0 +1,320 @@
|
||||
import { request } from '../utils/request'
|
||||
|
||||
// 售后类型
|
||||
export type AfterSaleType = 'refund' | 'return' | 'exchange' | 'repair'
|
||||
|
||||
// 售后状态
|
||||
export type AfterSaleStatus =
|
||||
| 'pending' // 待审核
|
||||
| 'approved' // 已同意
|
||||
| 'rejected' // 已拒绝
|
||||
| 'processing' // 处理中
|
||||
| 'completed' // 已完成
|
||||
| 'cancelled' // 已取消
|
||||
|
||||
// 售后进度记录
|
||||
export interface ProgressRecord {
|
||||
id: string
|
||||
time: string
|
||||
status: string
|
||||
description: string
|
||||
operator?: string
|
||||
remark?: string
|
||||
images?: string[]
|
||||
}
|
||||
|
||||
// 售后详情
|
||||
export interface AfterSaleDetail {
|
||||
id: string
|
||||
orderId: string
|
||||
orderNo: string
|
||||
type: AfterSaleType
|
||||
status: AfterSaleStatus
|
||||
reason: string
|
||||
description: string
|
||||
amount: number
|
||||
applyTime: string
|
||||
processTime?: string
|
||||
completeTime?: string
|
||||
rejectReason?: string
|
||||
contactPhone?: string
|
||||
evidenceImages: string[]
|
||||
progressRecords: ProgressRecord[]
|
||||
}
|
||||
|
||||
// 售后申请参数
|
||||
export interface AfterSaleApplyParams {
|
||||
orderId: string
|
||||
type: AfterSaleType
|
||||
reason: string
|
||||
description: string
|
||||
amount: number
|
||||
contactPhone?: string
|
||||
evidenceImages?: string[]
|
||||
goodsItems?: Array<{
|
||||
goodsId: string
|
||||
quantity: number
|
||||
}>
|
||||
}
|
||||
|
||||
// 售后列表查询参数
|
||||
export interface AfterSaleListParams {
|
||||
page?: number
|
||||
pageSize?: number
|
||||
status?: AfterSaleStatus
|
||||
type?: AfterSaleType
|
||||
startTime?: string
|
||||
endTime?: string
|
||||
}
|
||||
|
||||
// 售后列表响应
|
||||
export interface AfterSaleListResponse {
|
||||
success: boolean
|
||||
data: {
|
||||
list: AfterSaleDetail[]
|
||||
total: number
|
||||
page: number
|
||||
pageSize: number
|
||||
}
|
||||
message?: string
|
||||
}
|
||||
|
||||
// 售后详情响应
|
||||
export interface AfterSaleDetailResponse {
|
||||
success: boolean
|
||||
data: AfterSaleDetail
|
||||
message?: string
|
||||
}
|
||||
|
||||
// 售后类型映射
|
||||
export const AFTER_SALE_TYPE_MAP = {
|
||||
'refund': '退款',
|
||||
'return': '退货',
|
||||
'exchange': '换货',
|
||||
'repair': '维修'
|
||||
}
|
||||
|
||||
// 售后状态映射
|
||||
export const AFTER_SALE_STATUS_MAP = {
|
||||
'pending': '待审核',
|
||||
'approved': '已同意',
|
||||
'rejected': '已拒绝',
|
||||
'processing': '处理中',
|
||||
'completed': '已完成',
|
||||
'cancelled': '已取消'
|
||||
}
|
||||
|
||||
// 状态颜色映射
|
||||
export const STATUS_COLOR_MAP = {
|
||||
'pending': '#faad14',
|
||||
'approved': '#52c41a',
|
||||
'rejected': '#ff4d4f',
|
||||
'processing': '#1890ff',
|
||||
'completed': '#52c41a',
|
||||
'cancelled': '#999'
|
||||
}
|
||||
|
||||
// 申请售后
|
||||
export const applyAfterSale = async (params: AfterSaleApplyParams): Promise<AfterSaleDetailResponse> => {
|
||||
try {
|
||||
const response = await request<AfterSaleDetailResponse>({
|
||||
url: '/api/after-sale/apply',
|
||||
method: 'POST',
|
||||
data: params
|
||||
})
|
||||
|
||||
return response
|
||||
} catch (error) {
|
||||
console.error('申请售后失败:', error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
// 查询售后详情
|
||||
export const getAfterSaleDetail = async (params: {
|
||||
orderId?: string
|
||||
afterSaleId?: string
|
||||
}): Promise<AfterSaleDetailResponse> => {
|
||||
try {
|
||||
const response = await request<AfterSaleDetailResponse>({
|
||||
url: '/api/after-sale/detail',
|
||||
method: 'GET',
|
||||
data: params
|
||||
})
|
||||
|
||||
return response
|
||||
} catch (error) {
|
||||
console.error('查询售后详情失败:', error)
|
||||
|
||||
// 返回模拟数据作为降级方案
|
||||
return getMockAfterSaleDetail(params)
|
||||
}
|
||||
}
|
||||
|
||||
// 查询售后列表
|
||||
export const getAfterSaleList = async (params: AfterSaleListParams): Promise<AfterSaleListResponse> => {
|
||||
try {
|
||||
const response = await request<AfterSaleListResponse>({
|
||||
url: '/api/after-sale/list',
|
||||
method: 'GET',
|
||||
data: params
|
||||
})
|
||||
|
||||
return response
|
||||
} catch (error) {
|
||||
console.error('查询售后列表失败:', error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
// 撤销售后申请
|
||||
export const cancelAfterSale = async (afterSaleId: string): Promise<{ success: boolean; message?: string }> => {
|
||||
try {
|
||||
const response = await request<{ success: boolean; message?: string }>({
|
||||
url: '/api/after-sale/cancel',
|
||||
method: 'POST',
|
||||
data: { afterSaleId }
|
||||
})
|
||||
|
||||
return response
|
||||
} catch (error) {
|
||||
console.error('撤销售后申请失败:', error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
// 获取模拟售后详情数据
|
||||
const getMockAfterSaleDetail = (params: {
|
||||
orderId?: string
|
||||
afterSaleId?: string
|
||||
}): AfterSaleDetailResponse => {
|
||||
const now = new Date()
|
||||
const yesterday = new Date(now.getTime() - 24 * 60 * 60 * 1000)
|
||||
const twoDaysAgo = new Date(now.getTime() - 2 * 24 * 60 * 60 * 1000)
|
||||
|
||||
const mockData: AfterSaleDetailResponse = {
|
||||
success: true,
|
||||
data: {
|
||||
id: 'AS' + Date.now(),
|
||||
orderId: params.orderId || '',
|
||||
orderNo: 'ORD' + Date.now(),
|
||||
type: 'refund',
|
||||
status: 'processing',
|
||||
reason: '商品质量问题',
|
||||
description: '收到的商品有明显瑕疵,包装破损,希望申请退款处理',
|
||||
amount: 9999,
|
||||
applyTime: twoDaysAgo.toISOString(),
|
||||
processTime: yesterday.toISOString(),
|
||||
contactPhone: '138****5678',
|
||||
evidenceImages: [
|
||||
'https://via.placeholder.com/200x200?text=Evidence1',
|
||||
'https://via.placeholder.com/200x200?text=Evidence2'
|
||||
],
|
||||
progressRecords: [
|
||||
{
|
||||
id: '1',
|
||||
time: now.toISOString(),
|
||||
status: '处理中',
|
||||
description: '客服正在处理您的申请,请耐心等待',
|
||||
operator: '客服小王',
|
||||
remark: '预计1-2个工作日内完成处理'
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
time: new Date(now.getTime() - 4 * 60 * 60 * 1000).toISOString(),
|
||||
status: '已审核',
|
||||
description: '您的申请已通过审核,正在安排退款处理',
|
||||
operator: '审核员张三'
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
time: yesterday.toISOString(),
|
||||
status: '已受理',
|
||||
description: '我们已收到您的申请,正在进行审核',
|
||||
operator: '系统'
|
||||
},
|
||||
{
|
||||
id: '4',
|
||||
time: twoDaysAgo.toISOString(),
|
||||
status: '已提交',
|
||||
description: '您已成功提交售后申请',
|
||||
operator: '用户'
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
return mockData
|
||||
}
|
||||
|
||||
// 格式化售后状态
|
||||
export const formatAfterSaleStatus = (status: AfterSaleStatus): {
|
||||
text: string
|
||||
color: string
|
||||
icon: string
|
||||
} => {
|
||||
const statusMap = {
|
||||
'pending': { text: '待审核', color: '#faad14', icon: '⏳' },
|
||||
'approved': { text: '已同意', color: '#52c41a', icon: '✅' },
|
||||
'rejected': { text: '已拒绝', color: '#ff4d4f', icon: '❌' },
|
||||
'processing': { text: '处理中', color: '#1890ff', icon: '🔄' },
|
||||
'completed': { text: '已完成', color: '#52c41a', icon: '✅' },
|
||||
'cancelled': { text: '已取消', color: '#999', icon: '⭕' }
|
||||
}
|
||||
|
||||
return statusMap[status] || { text: status, color: '#666', icon: '📋' }
|
||||
}
|
||||
|
||||
// 计算预计处理时间
|
||||
export const calculateEstimatedTime = (
|
||||
applyTime: string,
|
||||
type: AfterSaleType,
|
||||
status: AfterSaleStatus
|
||||
): string => {
|
||||
const applyDate = new Date(applyTime)
|
||||
let estimatedDays = 3 // 默认3个工作日
|
||||
|
||||
// 根据售后类型调整预计时间
|
||||
switch (type) {
|
||||
case 'refund':
|
||||
estimatedDays = 3 // 退款3个工作日
|
||||
break
|
||||
case 'return':
|
||||
estimatedDays = 7 // 退货7个工作日
|
||||
break
|
||||
case 'exchange':
|
||||
estimatedDays = 10 // 换货10个工作日
|
||||
break
|
||||
case 'repair':
|
||||
estimatedDays = 15 // 维修15个工作日
|
||||
break
|
||||
}
|
||||
|
||||
// 根据当前状态调整
|
||||
if (status === 'completed') {
|
||||
return '已完成'
|
||||
} else if (status === 'rejected' || status === 'cancelled') {
|
||||
return '已结束'
|
||||
}
|
||||
|
||||
const estimatedDate = new Date(applyDate.getTime() + estimatedDays * 24 * 60 * 60 * 1000)
|
||||
return `预计${estimatedDate.getMonth() + 1}月${estimatedDate.getDate()}日前完成`
|
||||
}
|
||||
|
||||
// 获取售后进度步骤
|
||||
export const getAfterSaleSteps = (type: AfterSaleType, status: AfterSaleStatus) => {
|
||||
const baseSteps = [
|
||||
{ title: '提交申请', description: '用户提交售后申请' },
|
||||
{ title: '审核中', description: '客服审核申请材料' },
|
||||
{ title: '处理中', description: '正在处理您的申请' },
|
||||
{ title: '完成', description: '售后处理完成' }
|
||||
]
|
||||
|
||||
// 根据类型调整步骤
|
||||
if (type === 'return' || type === 'exchange') {
|
||||
baseSteps.splice(2, 0, { title: '等待收货', description: '等待用户寄回商品' })
|
||||
baseSteps.splice(3, 0, { title: '确认收货', description: '商家确认收到退回商品' })
|
||||
}
|
||||
|
||||
return baseSteps
|
||||
}
|
||||
@@ -102,7 +102,7 @@ export async function getCmsAd(id: number) {
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据id查询广告位
|
||||
* 根据code查询广告位
|
||||
*/
|
||||
export async function getCmsAdByCode(code: string) {
|
||||
const res = await request.get<ApiResult<CmsAd>>(
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { PageParam } from '@/api/index';
|
||||
import type { PageParam } from '@/api';
|
||||
|
||||
/**
|
||||
* 广告位
|
||||
@@ -52,7 +52,6 @@ export interface CmsAd {
|
||||
*/
|
||||
export interface CmsAdParam extends PageParam {
|
||||
adId?: number;
|
||||
type?: number;
|
||||
adType?: string;
|
||||
pageId?: number;
|
||||
pageName?: string;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import request from '@/utils/request';
|
||||
import type {ApiResult, PageResult} from '@/api/index';
|
||||
import type {ApiResult, PageResult} from '@/api';
|
||||
import type {CmsArticle, CmsArticleParam} from './model';
|
||||
|
||||
/**
|
||||
@@ -204,3 +204,15 @@ export async function getByIds(params?: CmsArticleParam) {
|
||||
return Promise.reject(new Error(res.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据code查询文章
|
||||
*/
|
||||
export async function getByCode(code: string) {
|
||||
const res = await request.get<ApiResult<CmsArticle>>(
|
||||
'/cms/cms-article/getByCode/' + code
|
||||
);
|
||||
if (res.code === 0 && res.data) {
|
||||
return res.data;
|
||||
}
|
||||
return Promise.reject(new Error(res.message));
|
||||
}
|
||||
|
||||
@@ -123,3 +123,16 @@ export async function getNavigationByPath(params: CmsNavigationParam) {
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 根据code查询导航
|
||||
*/
|
||||
export async function getByCode(code: string) {
|
||||
const res = await request.get<ApiResult<CmsNavigation>>(
|
||||
'/cms/cms-navigation/getByCode/' + code
|
||||
);
|
||||
if (res.code === 0 && res.data) {
|
||||
return res.data;
|
||||
}
|
||||
return Promise.reject(new Error(res.message));
|
||||
}
|
||||
|
||||
|
||||
@@ -55,8 +55,8 @@ export interface CmsNavigation {
|
||||
parentName?: string;
|
||||
// 模型名称
|
||||
modelName?: string;
|
||||
// 类型(已废弃)
|
||||
type?: number;
|
||||
// 类型(模型)
|
||||
type?: string;
|
||||
// 绑定的页面(已废弃)
|
||||
pageId?: number;
|
||||
// 项目ID
|
||||
@@ -113,6 +113,5 @@ export interface CmsNavigationParam extends PageParam {
|
||||
parentId?: number;
|
||||
hide?: number;
|
||||
model?: string;
|
||||
home?: number;
|
||||
keywords?: string;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { PageParam } from '@/api';
|
||||
import type { PageParam } from '@/api/index';
|
||||
|
||||
/**
|
||||
* 应用参数
|
||||
@@ -55,6 +55,9 @@ export interface Config {
|
||||
email?: string;
|
||||
loginTitle?: string;
|
||||
sysLogo?: string;
|
||||
NoticeBar?: string;
|
||||
apiUrl?: string;
|
||||
vipText?: string;
|
||||
vipComments?: string;
|
||||
deliveryText?: string;
|
||||
guaranteeText?: string;
|
||||
openComments?: string;
|
||||
}
|
||||
|
||||
101
src/api/glt/gltTicketOrder/index.ts
Normal file
101
src/api/glt/gltTicketOrder/index.ts
Normal file
@@ -0,0 +1,101 @@
|
||||
import request from '@/utils/request';
|
||||
import type { ApiResult, PageResult } from '@/api/index';
|
||||
import type { GltTicketOrder, GltTicketOrderParam } from './model';
|
||||
|
||||
/**
|
||||
* 分页查询送水订单
|
||||
*/
|
||||
export async function pageGltTicketOrder(params: GltTicketOrderParam) {
|
||||
const res = await request.get<ApiResult<PageResult<GltTicketOrder>>>(
|
||||
'/glt/glt-ticket-order/page',
|
||||
params
|
||||
);
|
||||
if (res.code === 0) {
|
||||
return res.data;
|
||||
}
|
||||
return Promise.reject(new Error(res.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询送水订单列表
|
||||
*/
|
||||
export async function listGltTicketOrder(params?: GltTicketOrderParam) {
|
||||
const res = await request.get<ApiResult<GltTicketOrder[]>>(
|
||||
'/glt/glt-ticket-order',
|
||||
params
|
||||
);
|
||||
if (res.code === 0 && res.data) {
|
||||
return res.data;
|
||||
}
|
||||
return Promise.reject(new Error(res.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加送水订单
|
||||
*/
|
||||
export async function addGltTicketOrder(data: GltTicketOrder) {
|
||||
const res = await request.post<ApiResult<unknown>>(
|
||||
'/glt/glt-ticket-order',
|
||||
data
|
||||
);
|
||||
if (res.code === 0) {
|
||||
return res.message;
|
||||
}
|
||||
return Promise.reject(new Error(res.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改送水订单
|
||||
*/
|
||||
export async function updateGltTicketOrder(data: GltTicketOrder) {
|
||||
const res = await request.put<ApiResult<unknown>>(
|
||||
'/glt/glt-ticket-order',
|
||||
data
|
||||
);
|
||||
if (res.code === 0) {
|
||||
return res.message;
|
||||
}
|
||||
return Promise.reject(new Error(res.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除送水订单
|
||||
*/
|
||||
export async function removeGltTicketOrder(id?: number) {
|
||||
const res = await request.del<ApiResult<unknown>>(
|
||||
'/glt/glt-ticket-order/' + id
|
||||
);
|
||||
if (res.code === 0) {
|
||||
return res.message;
|
||||
}
|
||||
return Promise.reject(new Error(res.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量删除送水订单
|
||||
*/
|
||||
export async function removeBatchGltTicketOrder(data: (number | undefined)[]) {
|
||||
const res = await request.del<ApiResult<unknown>>(
|
||||
'/glt/glt-ticket-order/batch',
|
||||
{
|
||||
data
|
||||
}
|
||||
);
|
||||
if (res.code === 0) {
|
||||
return res.message;
|
||||
}
|
||||
return Promise.reject(new Error(res.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据id查询送水订单
|
||||
*/
|
||||
export async function getGltTicketOrder(id: number) {
|
||||
const res = await request.get<ApiResult<GltTicketOrder>>(
|
||||
'/glt/glt-ticket-order/' + id
|
||||
);
|
||||
if (res.code === 0 && res.data) {
|
||||
return res.data;
|
||||
}
|
||||
return Promise.reject(new Error(res.message));
|
||||
}
|
||||
94
src/api/glt/gltTicketOrder/model/index.ts
Normal file
94
src/api/glt/gltTicketOrder/model/index.ts
Normal file
@@ -0,0 +1,94 @@
|
||||
import type { PageParam } from '@/api';
|
||||
|
||||
/**
|
||||
* 送水订单
|
||||
*/
|
||||
export interface GltTicketOrder {
|
||||
//
|
||||
id?: number;
|
||||
// 用户水票ID
|
||||
userTicketId?: number;
|
||||
// 订单编号
|
||||
orderNo?: string;
|
||||
// 门店ID
|
||||
storeId?: number;
|
||||
// 门店名称
|
||||
storeName?: string;
|
||||
// 门店地址
|
||||
storeAddress?: string;
|
||||
// 门店电话
|
||||
storePhone?: string;
|
||||
// 配送员
|
||||
riderId?: number;
|
||||
// 配送员名称
|
||||
riderName?: string;
|
||||
// 配送员电话
|
||||
riderPhone?: string;
|
||||
// 仓库ID
|
||||
warehouseId?: number;
|
||||
// 仓库名称
|
||||
warehouseName?: string;
|
||||
// 仓库地址
|
||||
warehouseAddress?: string;
|
||||
// 关联收货地址
|
||||
addressId?: number;
|
||||
// 收货地址
|
||||
address?: string;
|
||||
// 配送时间
|
||||
sendTime?: string;
|
||||
// 配送开始时间(配送员点击“开始配送”)
|
||||
sendStartTime?: string;
|
||||
// 配送结束时间(配送员确认送达)
|
||||
sendEndTime?: string;
|
||||
// 配送员送达拍照(选填/必填由后端策略决定)
|
||||
sendEndImg?: string;
|
||||
// 发货/配送状态(建议:10待配送 20配送中 30待客户确认 40已完成)
|
||||
deliveryStatus?: number;
|
||||
// 客户确认收货时间(客户点击确认收货)
|
||||
receiveConfirmTime?: string;
|
||||
// 客户确认方式(建议:10客户手动确认 20配送照片自动确认 30后台超时自动确认)
|
||||
receiveConfirmType?: number;
|
||||
// 买家留言
|
||||
buyerRemarks?: string;
|
||||
// 用于统计
|
||||
price?: string;
|
||||
// 购买数量
|
||||
totalNum?: number;
|
||||
// 用户ID
|
||||
userId?: number;
|
||||
// 昵称
|
||||
nickname?: string;
|
||||
// 头像
|
||||
avatar?: string;
|
||||
// 手机号码
|
||||
phone?: string;
|
||||
// 排序(数字越小越靠前)
|
||||
sortNumber?: number;
|
||||
// 备注
|
||||
comments?: string;
|
||||
// 状态, 0正常, 1冻结
|
||||
status?: number;
|
||||
// 是否删除, 0否, 1是
|
||||
deleted?: number;
|
||||
// 租户id
|
||||
tenantId?: number;
|
||||
// 创建时间
|
||||
createTime?: string;
|
||||
// 修改时间
|
||||
updateTime?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 送水订单搜索条件
|
||||
*/
|
||||
export interface GltTicketOrderParam extends PageParam {
|
||||
id?: number;
|
||||
keywords?: string;
|
||||
userId?: number;
|
||||
// 配送员用户ID(用于配送员端查询)
|
||||
riderId?: number;
|
||||
// 发货/配送状态(建议与 GltTicketOrder.deliveryStatus 对齐)
|
||||
deliveryStatus?: number;
|
||||
// 兼容 ShopOrderParam 的筛选字段(如后端已实现可直接复用)
|
||||
statusFilter?: number;
|
||||
}
|
||||
118
src/api/glt/gltTicketTemplate/index.ts
Normal file
118
src/api/glt/gltTicketTemplate/index.ts
Normal file
@@ -0,0 +1,118 @@
|
||||
import request from '@/utils/request';
|
||||
import type { ApiResult, PageResult } from '@/api';
|
||||
import type { GltTicketTemplate, GltTicketTemplateParam } from './model';
|
||||
|
||||
/**
|
||||
* 分页查询水票
|
||||
*/
|
||||
export async function pageGltTicketTemplate(params: GltTicketTemplateParam) {
|
||||
const res = await request.get<ApiResult<PageResult<GltTicketTemplate>>>(
|
||||
'/glt/glt-ticket-template/page',
|
||||
{
|
||||
params
|
||||
}
|
||||
);
|
||||
if (res.code === 0) {
|
||||
return res.data;
|
||||
}
|
||||
return Promise.reject(new Error(res.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询水票列表
|
||||
*/
|
||||
export async function listGltTicketTemplate(params?: GltTicketTemplateParam) {
|
||||
const res = await request.get<ApiResult<GltTicketTemplate[]>>(
|
||||
'/glt/glt-ticket-template',
|
||||
{
|
||||
params
|
||||
}
|
||||
);
|
||||
if (res.code === 0 && res.data) {
|
||||
return res.data;
|
||||
}
|
||||
return Promise.reject(new Error(res.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加水票
|
||||
*/
|
||||
export async function addGltTicketTemplate(data: GltTicketTemplate) {
|
||||
const res = await request.post<ApiResult<unknown>>(
|
||||
'/glt/glt-ticket-template',
|
||||
data
|
||||
);
|
||||
if (res.code === 0) {
|
||||
return res.message;
|
||||
}
|
||||
return Promise.reject(new Error(res.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改水票
|
||||
*/
|
||||
export async function updateGltTicketTemplate(data: GltTicketTemplate) {
|
||||
const res = await request.put<ApiResult<unknown>>(
|
||||
'/glt/glt-ticket-template',
|
||||
data
|
||||
);
|
||||
if (res.code === 0) {
|
||||
return res.message;
|
||||
}
|
||||
return Promise.reject(new Error(res.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除水票
|
||||
*/
|
||||
export async function removeGltTicketTemplate(id?: number) {
|
||||
const res = await request.del<ApiResult<unknown>>(
|
||||
'/glt/glt-ticket-template/' + id
|
||||
);
|
||||
if (res.code === 0) {
|
||||
return res.message;
|
||||
}
|
||||
return Promise.reject(new Error(res.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量删除水票
|
||||
*/
|
||||
export async function removeBatchGltTicketTemplate(data: (number | undefined)[]) {
|
||||
const res = await request.del<ApiResult<unknown>>(
|
||||
'/glt/glt-ticket-template/batch',
|
||||
{
|
||||
data
|
||||
}
|
||||
);
|
||||
if (res.code === 0) {
|
||||
return res.message;
|
||||
}
|
||||
return Promise.reject(new Error(res.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据id查询水票
|
||||
*/
|
||||
export async function getGltTicketTemplate(id: number) {
|
||||
const res = await request.get<ApiResult<GltTicketTemplate>>(
|
||||
'/glt/glt-ticket-template/' + id
|
||||
);
|
||||
if (res.code === 0 && res.data) {
|
||||
return res.data;
|
||||
}
|
||||
return Promise.reject(new Error(res.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据商品ID查询水票模板
|
||||
*/
|
||||
export async function getGltTicketTemplateByGoodsId(id: number) {
|
||||
const res = await request.get<ApiResult<GltTicketTemplate>>(
|
||||
'/glt/glt-ticket-template/getByGoodsId/' + id
|
||||
);
|
||||
if (res.code === 0 && res.data) {
|
||||
return res.data;
|
||||
}
|
||||
return Promise.reject(new Error(res.message));
|
||||
}
|
||||
55
src/api/glt/gltTicketTemplate/model/index.ts
Normal file
55
src/api/glt/gltTicketTemplate/model/index.ts
Normal file
@@ -0,0 +1,55 @@
|
||||
import type { PageParam } from '@/api';
|
||||
|
||||
/**
|
||||
* 水票
|
||||
*/
|
||||
export interface GltTicketTemplate {
|
||||
//
|
||||
id?: number;
|
||||
// 关联商品ID
|
||||
goodsId?: number;
|
||||
// 名称
|
||||
name?: string;
|
||||
// 启用
|
||||
enabled?: boolean;
|
||||
// 单位名称
|
||||
unitName?: string;
|
||||
// 最小购买数量
|
||||
minBuyQty?: number;
|
||||
// 起始发送数量
|
||||
startSendQty?: number;
|
||||
// 买赠:买1送4 => gift_multiplier=4
|
||||
giftMultiplier?: number;
|
||||
// 是否把购买量也计入套票总量(默认仅计入赠送量)
|
||||
includeBuyQty?: boolean;
|
||||
// 每期释放数量(默认每月释放10)
|
||||
monthlyReleaseQty?: number;
|
||||
// 总共释放多少期(若配置>0,则按期数平均分摊)
|
||||
releasePeriods?: number;
|
||||
// 首期释放时机:0=支付成功当刻;1=下个月同日
|
||||
firstReleaseMode?: number;
|
||||
// 用户ID
|
||||
userId?: number;
|
||||
// 排序(数字越小越靠前)
|
||||
sortNumber?: number;
|
||||
// 备注
|
||||
comments?: string;
|
||||
// 状态, 0正常, 1冻结
|
||||
status?: number;
|
||||
// 是否删除, 0否, 1是
|
||||
deleted?: number;
|
||||
// 租户id
|
||||
tenantId?: number;
|
||||
// 创建时间
|
||||
createTime?: string;
|
||||
// 修改时间
|
||||
updateTime?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 水票搜索条件
|
||||
*/
|
||||
export interface GltTicketTemplateParam extends PageParam {
|
||||
id?: number;
|
||||
keywords?: string;
|
||||
}
|
||||
170
src/api/glt/gltUserTicket/index.ts
Normal file
170
src/api/glt/gltUserTicket/index.ts
Normal file
@@ -0,0 +1,170 @@
|
||||
import request from '@/utils/request';
|
||||
import type { ApiResult, PageResult } from '@/api';
|
||||
import type { GltUserTicket, GltUserTicketParam } from './model';
|
||||
|
||||
function normalizeTotal(input: unknown): number {
|
||||
if (typeof input === 'number' && Number.isFinite(input)) return input;
|
||||
if (typeof input === 'string') {
|
||||
const n = Number(input);
|
||||
if (Number.isFinite(n)) return n;
|
||||
}
|
||||
if (input && typeof input === 'object') {
|
||||
const obj: any = input;
|
||||
// Common shapes from different backends.
|
||||
for (const key of ['total', 'count', 'value', 'num', 'ticketTotal', 'totalQty']) {
|
||||
const v = obj?.[key];
|
||||
const n = normalizeTotal(v);
|
||||
if (n) return n;
|
||||
}
|
||||
// Sometimes nested: { data: { total: ... } } / { data: 12 }
|
||||
if ('data' in obj) {
|
||||
const n = normalizeTotal(obj.data);
|
||||
if (n) return n;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 分页查询我的水票
|
||||
*/
|
||||
export async function pageGltUserTicket(params: GltUserTicketParam) {
|
||||
const res = await request.get<ApiResult<PageResult<GltUserTicket>>>(
|
||||
'/glt/glt-user-ticket/page',
|
||||
params
|
||||
);
|
||||
if (res.code === 0 && res.data) {
|
||||
return res.data;
|
||||
}
|
||||
return Promise.reject(new Error(res.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询我的水票列表
|
||||
*/
|
||||
export async function listGltUserTicket(params?: GltUserTicketParam) {
|
||||
const res = await request.get<ApiResult<GltUserTicket[]>>(
|
||||
'/glt/glt-user-ticket',
|
||||
params
|
||||
);
|
||||
if (res.code === 0 && res.data) {
|
||||
return res.data;
|
||||
}
|
||||
return Promise.reject(new Error(res.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加我的水票
|
||||
*/
|
||||
export async function addGltUserTicket(data: GltUserTicket) {
|
||||
const res = await request.post<ApiResult<unknown>>(
|
||||
'/glt/glt-user-ticket',
|
||||
data
|
||||
);
|
||||
if (res.code === 0) {
|
||||
return res.message;
|
||||
}
|
||||
return Promise.reject(new Error(res.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改我的水票
|
||||
*/
|
||||
export async function updateGltUserTicket(data: GltUserTicket) {
|
||||
const res = await request.put<ApiResult<unknown>>(
|
||||
'/glt/glt-user-ticket',
|
||||
data
|
||||
);
|
||||
if (res.code === 0) {
|
||||
return res.message;
|
||||
}
|
||||
return Promise.reject(new Error(res.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除我的水票
|
||||
*/
|
||||
export async function removeGltUserTicket(id?: number) {
|
||||
const res = await request.del<ApiResult<unknown>>(
|
||||
'/glt/glt-user-ticket/' + id
|
||||
);
|
||||
if (res.code === 0) {
|
||||
return res.message;
|
||||
}
|
||||
return Promise.reject(new Error(res.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量删除我的水票
|
||||
*/
|
||||
export async function removeBatchGltUserTicket(data: (number | undefined)[]) {
|
||||
const res = await request.del<ApiResult<unknown>>(
|
||||
'/glt/glt-user-ticket/batch',
|
||||
{
|
||||
data
|
||||
}
|
||||
);
|
||||
if (res.code === 0) {
|
||||
return res.message;
|
||||
}
|
||||
return Promise.reject(new Error(res.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据id查询我的水票
|
||||
*/
|
||||
export async function getGltUserTicket(id: number) {
|
||||
const res = await request.get<ApiResult<GltUserTicket>>(
|
||||
'/glt/glt-user-ticket/' + id
|
||||
);
|
||||
if (res.code === 0 && res.data) {
|
||||
return res.data;
|
||||
}
|
||||
return Promise.reject(new Error(res.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取我的水票总数
|
||||
*/
|
||||
export async function getMyGltUserTicketTotal(userId?: number) {
|
||||
const params = userId ? { userId } : undefined
|
||||
|
||||
const extract = (res: any) => {
|
||||
// Some backends may return a raw number instead of ApiResult.
|
||||
if (typeof res === 'number' || typeof res === 'string') return normalizeTotal(res)
|
||||
if (res && typeof res === 'object' && 'code' in res) {
|
||||
const apiRes = res as ApiResult<unknown>
|
||||
if (apiRes.code === 0) return normalizeTotal(apiRes.data)
|
||||
throw new Error(apiRes.message)
|
||||
}
|
||||
return normalizeTotal(res)
|
||||
}
|
||||
|
||||
// Try both the configured BaseUrl host and the auth-server host.
|
||||
// If the first one returns 0, keep trying; some tenants deploy GLT on a different host.
|
||||
const urls = [
|
||||
'/glt/glt-user-ticket/my-total'
|
||||
]
|
||||
|
||||
let lastError: unknown
|
||||
let firstTotal: number | undefined
|
||||
for (const url of urls) {
|
||||
try {
|
||||
const res = await request.get<any>(url, params)
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
console.log('[getMyGltUserTicketTotal] response:', { url, res })
|
||||
}
|
||||
const total = extract(res)
|
||||
if (firstTotal === undefined) firstTotal = total
|
||||
if (total) return total
|
||||
} catch (e) {
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
console.warn('[getMyGltUserTicketTotal] failed:', { url, error: e })
|
||||
}
|
||||
lastError = e
|
||||
}
|
||||
}
|
||||
|
||||
if (firstTotal !== undefined) return firstTotal
|
||||
return Promise.reject(lastError instanceof Error ? lastError : new Error('获取水票总数失败'))
|
||||
}
|
||||
66
src/api/glt/gltUserTicket/model/index.ts
Normal file
66
src/api/glt/gltUserTicket/model/index.ts
Normal file
@@ -0,0 +1,66 @@
|
||||
import type { PageParam } from '@/api';
|
||||
|
||||
/**
|
||||
* 我的水票
|
||||
*/
|
||||
export interface GltUserTicket {
|
||||
//
|
||||
id?: number;
|
||||
// 模板ID
|
||||
templateId?: number;
|
||||
// 模板名称
|
||||
templateName?: string;
|
||||
// 商品ID
|
||||
goodsId?: number;
|
||||
// 订单ID
|
||||
orderId?: number;
|
||||
// 订单编号
|
||||
orderNo?: string;
|
||||
// 订单商品ID
|
||||
orderGoodsId?: number;
|
||||
// 总数量
|
||||
totalQty?: number;
|
||||
// 可用数量
|
||||
availableQty?: number;
|
||||
// 冻结数量
|
||||
frozenQty?: number;
|
||||
// 已使用数量
|
||||
usedQty?: number;
|
||||
// 已释放数量
|
||||
releasedQty?: number;
|
||||
// 用户ID
|
||||
userId?: number;
|
||||
// 用户昵称
|
||||
nickname?: string;
|
||||
// 用户头像
|
||||
avatar?: string;
|
||||
// 用户手机号
|
||||
phone?: string;
|
||||
// 排序(数字越小越靠前)
|
||||
sortNumber?: number;
|
||||
// 备注
|
||||
comments?: string;
|
||||
// 状态, 0正常, 1冻结
|
||||
status?: number;
|
||||
// 是否删除, 0否, 1是
|
||||
deleted?: number;
|
||||
// 租户id
|
||||
tenantId?: number;
|
||||
// 创建时间
|
||||
createTime?: string;
|
||||
// 修改时间
|
||||
updateTime?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 我的水票搜索条件
|
||||
*/
|
||||
export interface GltUserTicketParam extends PageParam {
|
||||
id?: number;
|
||||
templateId?: number;
|
||||
userId?: number;
|
||||
phone?: string;
|
||||
keywords?: string;
|
||||
// 状态过滤:0正常,1冻结
|
||||
status?: number;
|
||||
}
|
||||
101
src/api/glt/gltUserTicketLog/index.ts
Normal file
101
src/api/glt/gltUserTicketLog/index.ts
Normal file
@@ -0,0 +1,101 @@
|
||||
import request from '@/utils/request';
|
||||
import type { ApiResult, PageResult } from '@/api';
|
||||
import type { GltUserTicketLog, GltUserTicketLogParam } from './model';
|
||||
|
||||
/**
|
||||
* 分页查询消费日志
|
||||
*/
|
||||
export async function pageGltUserTicketLog(params: GltUserTicketLogParam) {
|
||||
const res = await request.get<ApiResult<PageResult<GltUserTicketLog>>>(
|
||||
'/glt/glt-user-ticket-log/page',
|
||||
params
|
||||
);
|
||||
if (res.code === 0) {
|
||||
return res.data;
|
||||
}
|
||||
return Promise.reject(new Error(res.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询消费日志列表
|
||||
*/
|
||||
export async function listGltUserTicketLog(params?: GltUserTicketLogParam) {
|
||||
const res = await request.get<ApiResult<GltUserTicketLog[]>>(
|
||||
'/glt/glt-user-ticket-log',
|
||||
params
|
||||
);
|
||||
if (res.code === 0 && res.data) {
|
||||
return res.data;
|
||||
}
|
||||
return Promise.reject(new Error(res.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加消费日志
|
||||
*/
|
||||
export async function addGltUserTicketLog(data: GltUserTicketLog) {
|
||||
const res = await request.post<ApiResult<unknown>>(
|
||||
'/glt/glt-user-ticket-log',
|
||||
data
|
||||
);
|
||||
if (res.code === 0) {
|
||||
return res.message;
|
||||
}
|
||||
return Promise.reject(new Error(res.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改消费日志
|
||||
*/
|
||||
export async function updateGltUserTicketLog(data: GltUserTicketLog) {
|
||||
const res = await request.put<ApiResult<unknown>>(
|
||||
'/glt/glt-user-ticket-log',
|
||||
data
|
||||
);
|
||||
if (res.code === 0) {
|
||||
return res.message;
|
||||
}
|
||||
return Promise.reject(new Error(res.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除消费日志
|
||||
*/
|
||||
export async function removeGltUserTicketLog(id?: number) {
|
||||
const res = await request.del<ApiResult<unknown>>(
|
||||
'/glt/glt-user-ticket-log/' + id
|
||||
);
|
||||
if (res.code === 0) {
|
||||
return res.message;
|
||||
}
|
||||
return Promise.reject(new Error(res.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量删除消费日志
|
||||
*/
|
||||
export async function removeBatchGltUserTicketLog(data: (number | undefined)[]) {
|
||||
const res = await request.del<ApiResult<unknown>>(
|
||||
'/glt/glt-user-ticket-log/batch',
|
||||
{
|
||||
data
|
||||
}
|
||||
);
|
||||
if (res.code === 0) {
|
||||
return res.message;
|
||||
}
|
||||
return Promise.reject(new Error(res.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据id查询消费日志
|
||||
*/
|
||||
export async function getGltUserTicketLog(id: number) {
|
||||
const res = await request.get<ApiResult<GltUserTicketLog>>(
|
||||
'/glt/glt-user-ticket-log/' + id
|
||||
);
|
||||
if (res.code === 0 && res.data) {
|
||||
return res.data;
|
||||
}
|
||||
return Promise.reject(new Error(res.message));
|
||||
}
|
||||
54
src/api/glt/gltUserTicketLog/model/index.ts
Normal file
54
src/api/glt/gltUserTicketLog/model/index.ts
Normal file
@@ -0,0 +1,54 @@
|
||||
import type { PageParam } from '@/api';
|
||||
|
||||
/**
|
||||
* 消费日志
|
||||
*/
|
||||
export interface GltUserTicketLog {
|
||||
//
|
||||
id?: number;
|
||||
// 用户水票ID
|
||||
userTicketId?: number;
|
||||
// 变更类型
|
||||
changeType?: number;
|
||||
// 可更改
|
||||
changeAvailable?: number;
|
||||
// 更改冻结状态
|
||||
changeFrozen?: number;
|
||||
// 已使用更改
|
||||
changeUsed?: number;
|
||||
// 可用后
|
||||
availableAfter?: number;
|
||||
// 冻结后
|
||||
frozenAfter?: number;
|
||||
// 使用后
|
||||
usedAfter?: number;
|
||||
// 订单ID
|
||||
orderId?: number;
|
||||
// 订单编号
|
||||
orderNo?: string;
|
||||
// 用户ID
|
||||
userId?: number;
|
||||
// 排序(数字越小越靠前)
|
||||
sortNumber?: number;
|
||||
// 备注
|
||||
comments?: string;
|
||||
// 状态, 0正常, 1冻结
|
||||
status?: number;
|
||||
// 是否删除, 0否, 1是
|
||||
deleted?: number;
|
||||
// 租户id
|
||||
tenantId?: number;
|
||||
// 创建时间
|
||||
createTime?: string;
|
||||
// 修改时间
|
||||
updateTime?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 消费日志搜索条件
|
||||
*/
|
||||
export interface GltUserTicketLogParam extends PageParam {
|
||||
id?: number;
|
||||
keywords?: string;
|
||||
userId?: number;
|
||||
}
|
||||
105
src/api/glt/gltUserTicketRelease/index.ts
Normal file
105
src/api/glt/gltUserTicketRelease/index.ts
Normal file
@@ -0,0 +1,105 @@
|
||||
import request from '@/utils/request';
|
||||
import type { ApiResult, PageResult } from '@/api';
|
||||
import type { GltUserTicketRelease, GltUserTicketReleaseParam } from './model';
|
||||
|
||||
/**
|
||||
* 分页查询水票释放
|
||||
*/
|
||||
export async function pageGltUserTicketRelease(params: GltUserTicketReleaseParam) {
|
||||
const res = await request.get<ApiResult<PageResult<GltUserTicketRelease>>>(
|
||||
'/glt/glt-user-ticket-release/page',
|
||||
{
|
||||
params
|
||||
}
|
||||
);
|
||||
if (res.code === 0) {
|
||||
return res.data;
|
||||
}
|
||||
return Promise.reject(new Error(res.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询水票释放列表
|
||||
*/
|
||||
export async function listGltUserTicketRelease(params?: GltUserTicketReleaseParam) {
|
||||
const res = await request.get<ApiResult<GltUserTicketRelease[]>>(
|
||||
'/glt/glt-user-ticket-release',
|
||||
{
|
||||
params
|
||||
}
|
||||
);
|
||||
if (res.code === 0 && res.data) {
|
||||
return res.data;
|
||||
}
|
||||
return Promise.reject(new Error(res.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加水票释放
|
||||
*/
|
||||
export async function addGltUserTicketRelease(data: GltUserTicketRelease) {
|
||||
const res = await request.post<ApiResult<unknown>>(
|
||||
'/glt/glt-user-ticket-release',
|
||||
data
|
||||
);
|
||||
if (res.code === 0) {
|
||||
return res.message;
|
||||
}
|
||||
return Promise.reject(new Error(res.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改水票释放
|
||||
*/
|
||||
export async function updateGltUserTicketRelease(data: GltUserTicketRelease) {
|
||||
const res = await request.put<ApiResult<unknown>>(
|
||||
'/glt/glt-user-ticket-release',
|
||||
data
|
||||
);
|
||||
if (res.code === 0) {
|
||||
return res.message;
|
||||
}
|
||||
return Promise.reject(new Error(res.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除水票释放
|
||||
*/
|
||||
export async function removeGltUserTicketRelease(id?: number) {
|
||||
const res = await request.del<ApiResult<unknown>>(
|
||||
'/glt/glt-user-ticket-release/' + id
|
||||
);
|
||||
if (res.code === 0) {
|
||||
return res.message;
|
||||
}
|
||||
return Promise.reject(new Error(res.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量删除水票释放
|
||||
*/
|
||||
export async function removeBatchGltUserTicketRelease(data: (number | undefined)[]) {
|
||||
const res = await request.del<ApiResult<unknown>>(
|
||||
'/glt/glt-user-ticket-release/batch',
|
||||
{
|
||||
data
|
||||
}
|
||||
);
|
||||
if (res.code === 0) {
|
||||
return res.message;
|
||||
}
|
||||
return Promise.reject(new Error(res.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据id查询水票释放
|
||||
*/
|
||||
export async function getGltUserTicketRelease(id: number) {
|
||||
const res = await request.get<ApiResult<GltUserTicketRelease>>(
|
||||
'/glt/glt-user-ticket-release/' + id
|
||||
);
|
||||
if (res.code === 0 && res.data) {
|
||||
return res.data;
|
||||
}
|
||||
return Promise.reject(new Error(res.message));
|
||||
}
|
||||
38
src/api/glt/gltUserTicketRelease/model/index.ts
Normal file
38
src/api/glt/gltUserTicketRelease/model/index.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
import type { PageParam } from '@/api';
|
||||
|
||||
/**
|
||||
* 水票释放
|
||||
*/
|
||||
export interface GltUserTicketRelease {
|
||||
//
|
||||
id?: string;
|
||||
// 水票ID
|
||||
userTicketId?: string;
|
||||
// 用户ID
|
||||
userId?: number;
|
||||
// 周期编号
|
||||
periodNo?: number;
|
||||
// 释放数量
|
||||
releaseQty?: number;
|
||||
// 释放时间
|
||||
releaseTime?: string;
|
||||
// 状态
|
||||
status?: number;
|
||||
// 是否删除, 0否, 1是
|
||||
deleted?: number;
|
||||
// 租户id
|
||||
tenantId?: number;
|
||||
// 创建时间
|
||||
createTime?: string;
|
||||
// 修改时间
|
||||
updateTime?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 水票释放搜索条件
|
||||
*/
|
||||
export interface GltUserTicketReleaseParam extends PageParam {
|
||||
id?: number;
|
||||
userId?: number;
|
||||
keywords?: string;
|
||||
}
|
||||
@@ -58,5 +58,4 @@ export interface PageParam {
|
||||
lang?: string;
|
||||
model?: string;
|
||||
BaseUrl?: string;
|
||||
sceneType?: string;
|
||||
}
|
||||
|
||||
259
src/api/logistics.ts
Normal file
259
src/api/logistics.ts
Normal file
@@ -0,0 +1,259 @@
|
||||
import { request } from '../utils/request'
|
||||
|
||||
// 物流信息接口
|
||||
export interface LogisticsInfo {
|
||||
expressCompany: string // 快递公司代码
|
||||
expressCompanyName: string // 快递公司名称
|
||||
expressNo: string // 快递单号
|
||||
status: string // 物流状态
|
||||
updateTime: string // 更新时间
|
||||
estimatedTime?: string // 预计送达时间
|
||||
currentLocation?: string // 当前位置
|
||||
senderInfo?: {
|
||||
name: string
|
||||
phone: string
|
||||
address: string
|
||||
}
|
||||
receiverInfo?: {
|
||||
name: string
|
||||
phone: string
|
||||
address: string
|
||||
}
|
||||
}
|
||||
|
||||
// 物流跟踪记录
|
||||
export interface LogisticsTrack {
|
||||
time: string
|
||||
location: string
|
||||
status: string
|
||||
description: string
|
||||
isCompleted: boolean
|
||||
}
|
||||
|
||||
// 物流查询响应
|
||||
export interface LogisticsResponse {
|
||||
success: boolean
|
||||
data: {
|
||||
logisticsInfo: LogisticsInfo
|
||||
trackList: LogisticsTrack[]
|
||||
}
|
||||
message?: string
|
||||
}
|
||||
|
||||
// 支持的快递公司
|
||||
export const EXPRESS_COMPANIES = {
|
||||
'SF': '顺丰速运',
|
||||
'YTO': '圆通速递',
|
||||
'ZTO': '中通快递',
|
||||
'STO': '申通快递',
|
||||
'YD': '韵达速递',
|
||||
'HTKY': '百世快递',
|
||||
'JD': '京东物流',
|
||||
'EMS': '中国邮政',
|
||||
'YUNDA': '韵达快递',
|
||||
'JTSD': '极兔速递',
|
||||
'DBKD': '德邦快递',
|
||||
'UC': '优速快递'
|
||||
}
|
||||
|
||||
// 查询物流信息
|
||||
export const queryLogistics = async (params: {
|
||||
orderId?: string
|
||||
expressNo: string
|
||||
expressCompany: string
|
||||
}): Promise<LogisticsResponse> => {
|
||||
try {
|
||||
// 实际项目中这里应该调用真实的物流查询API
|
||||
// 例如:快递100、快递鸟、菜鸟裹裹等第三方物流查询服务
|
||||
|
||||
// 模拟API调用
|
||||
const response = await request({
|
||||
url: '/api/logistics/query',
|
||||
method: 'POST',
|
||||
data: params
|
||||
})
|
||||
|
||||
return response
|
||||
} catch (error) {
|
||||
console.error('查询物流信息失败:', error)
|
||||
|
||||
// 返回模拟数据作为降级方案
|
||||
return getMockLogisticsData(params)
|
||||
}
|
||||
}
|
||||
|
||||
// 获取模拟物流数据
|
||||
const getMockLogisticsData = (params: {
|
||||
orderId?: string
|
||||
expressNo: string
|
||||
expressCompany: string
|
||||
}): LogisticsResponse => {
|
||||
const now = new Date()
|
||||
const yesterday = new Date(now.getTime() - 24 * 60 * 60 * 1000)
|
||||
const twoDaysAgo = new Date(now.getTime() - 2 * 24 * 60 * 60 * 1000)
|
||||
const threeDaysAgo = new Date(now.getTime() - 3 * 24 * 60 * 60 * 1000)
|
||||
|
||||
const mockData: LogisticsResponse = {
|
||||
success: true,
|
||||
data: {
|
||||
logisticsInfo: {
|
||||
expressCompany: params.expressCompany,
|
||||
expressCompanyName: EXPRESS_COMPANIES[params.expressCompany] || params.expressCompany,
|
||||
expressNo: params.expressNo,
|
||||
status: '运输中',
|
||||
updateTime: now.toISOString(),
|
||||
estimatedTime: new Date(now.getTime() + 24 * 60 * 60 * 1000).toISOString(),
|
||||
currentLocation: '北京市朝阳区',
|
||||
senderInfo: {
|
||||
name: '商家仓库',
|
||||
phone: '400-123-4567',
|
||||
address: '上海市浦东新区张江高科技园区'
|
||||
},
|
||||
receiverInfo: {
|
||||
name: '张三',
|
||||
phone: '138****5678',
|
||||
address: '北京市朝阳区三里屯街道'
|
||||
}
|
||||
},
|
||||
trackList: [
|
||||
{
|
||||
time: now.toISOString(),
|
||||
location: '北京市朝阳区',
|
||||
status: '运输中',
|
||||
description: '快件正在运输途中,预计今日送达,请保持手机畅通',
|
||||
isCompleted: false
|
||||
},
|
||||
{
|
||||
time: new Date(now.getTime() - 2 * 60 * 60 * 1000).toISOString(),
|
||||
location: '北京转运中心',
|
||||
status: '已发出',
|
||||
description: '快件已从北京转运中心发出,正在派送途中',
|
||||
isCompleted: true
|
||||
},
|
||||
{
|
||||
time: new Date(now.getTime() - 6 * 60 * 60 * 1000).toISOString(),
|
||||
location: '北京转运中心',
|
||||
status: '已到达',
|
||||
description: '快件已到达北京转运中心,正在进行分拣',
|
||||
isCompleted: true
|
||||
},
|
||||
{
|
||||
time: yesterday.toISOString(),
|
||||
location: '天津转运中心',
|
||||
status: '已发出',
|
||||
description: '快件已从天津转运中心发出',
|
||||
isCompleted: true
|
||||
},
|
||||
{
|
||||
time: new Date(yesterday.getTime() - 4 * 60 * 60 * 1000).toISOString(),
|
||||
location: '天津转运中心',
|
||||
status: '已到达',
|
||||
description: '快件已到达天津转运中心',
|
||||
isCompleted: true
|
||||
},
|
||||
{
|
||||
time: twoDaysAgo.toISOString(),
|
||||
location: '上海转运中心',
|
||||
status: '已发出',
|
||||
description: '快件已从上海转运中心发出',
|
||||
isCompleted: true
|
||||
},
|
||||
{
|
||||
time: new Date(twoDaysAgo.getTime() - 2 * 60 * 60 * 1000).toISOString(),
|
||||
location: '上海转运中心',
|
||||
status: '已到达',
|
||||
description: '快件已到达上海转运中心,正在进行分拣',
|
||||
isCompleted: true
|
||||
},
|
||||
{
|
||||
time: threeDaysAgo.toISOString(),
|
||||
location: '上海市浦东新区',
|
||||
status: '已发货',
|
||||
description: '商家已发货,快件已交给快递公司',
|
||||
isCompleted: true
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
return mockData
|
||||
}
|
||||
|
||||
// 获取快递公司列表
|
||||
export const getExpressCompanies = () => {
|
||||
return Object.entries(EXPRESS_COMPANIES).map(([code, name]) => ({
|
||||
code,
|
||||
name
|
||||
}))
|
||||
}
|
||||
|
||||
// 根据快递单号自动识别快递公司
|
||||
export const detectExpressCompany = (expressNo: string): string => {
|
||||
// 这里可以根据快递单号的规则来自动识别快递公司
|
||||
// 实际项目中可以使用第三方服务的自动识别API
|
||||
|
||||
if (expressNo.startsWith('SF')) return 'SF'
|
||||
if (expressNo.startsWith('YT')) return 'YTO'
|
||||
if (expressNo.startsWith('ZT')) return 'ZTO'
|
||||
if (expressNo.startsWith('ST')) return 'STO'
|
||||
if (expressNo.startsWith('YD')) return 'YD'
|
||||
if (expressNo.startsWith('JD')) return 'JD'
|
||||
if (expressNo.startsWith('EMS')) return 'EMS'
|
||||
|
||||
// 默认返回顺丰
|
||||
return 'SF'
|
||||
}
|
||||
|
||||
// 格式化物流状态
|
||||
export const formatLogisticsStatus = (status: string): {
|
||||
text: string
|
||||
color: string
|
||||
icon: string
|
||||
} => {
|
||||
const statusMap = {
|
||||
'已发货': { text: '已发货', color: '#1890ff', icon: '📦' },
|
||||
'运输中': { text: '运输中', color: '#52c41a', icon: '🚚' },
|
||||
'派送中': { text: '派送中', color: '#faad14', icon: '🏃' },
|
||||
'已签收': { text: '已签收', color: '#52c41a', icon: '✅' },
|
||||
'异常': { text: '异常', color: '#ff4d4f', icon: '⚠️' },
|
||||
'退回': { text: '退回', color: '#ff4d4f', icon: '↩️' }
|
||||
}
|
||||
|
||||
return statusMap[status] || { text: status, color: '#666', icon: '📋' }
|
||||
}
|
||||
|
||||
// 计算预计送达时间
|
||||
export const calculateEstimatedTime = (
|
||||
sendTime: string,
|
||||
expressCompany: string,
|
||||
distance?: number
|
||||
): string => {
|
||||
const sendDate = new Date(sendTime)
|
||||
let estimatedDays = 3 // 默认3天
|
||||
|
||||
// 根据快递公司调整预计时间
|
||||
switch (expressCompany) {
|
||||
case 'SF':
|
||||
estimatedDays = 1 // 顺丰次日达
|
||||
break
|
||||
case 'JD':
|
||||
estimatedDays = 1 // 京东次日达
|
||||
break
|
||||
case 'YTO':
|
||||
case 'ZTO':
|
||||
case 'STO':
|
||||
estimatedDays = 2 // 三通一达2天
|
||||
break
|
||||
default:
|
||||
estimatedDays = 3
|
||||
}
|
||||
|
||||
// 根据距离调整(如果有距离信息)
|
||||
if (distance) {
|
||||
if (distance > 2000) estimatedDays += 1 // 超过2000公里加1天
|
||||
if (distance > 3000) estimatedDays += 1 // 超过3000公里再加1天
|
||||
}
|
||||
|
||||
const estimatedDate = new Date(sendDate.getTime() + estimatedDays * 24 * 60 * 60 * 1000)
|
||||
return estimatedDate.toISOString()
|
||||
}
|
||||
@@ -1,61 +0,0 @@
|
||||
import request from '@/utils/request';
|
||||
import type { ApiResult } from '@/api';
|
||||
import {UserVerify} from "@/api/system/userVerify/model";
|
||||
import {ShopDealerWithdraw} from "@/api/shop/shopDealerWithdraw/model";
|
||||
|
||||
/**
|
||||
* 升级为管理员
|
||||
* 推送模版消息
|
||||
*/
|
||||
export async function pushByUpdateAdmin(userId: number) {
|
||||
const res = await request.get<ApiResult<unknown>>(
|
||||
'/sdy/sdy-template-message/' + userId
|
||||
);
|
||||
if (res.code === 0) {
|
||||
return res.message;
|
||||
}
|
||||
return Promise.reject(new Error(res.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 通知管理员审核操作提醒
|
||||
*/
|
||||
export async function pushReviewReminder(data: UserVerify) {
|
||||
const res = await request.post<ApiResult<unknown>>(
|
||||
'/sdy/sdy-template-message/pushReviewReminder',
|
||||
data
|
||||
);
|
||||
if (res.code === 0) {
|
||||
return res.message;
|
||||
}
|
||||
return Promise.reject(new Error(res.message));
|
||||
}
|
||||
/**
|
||||
* 通知管理员去提现审核操作提醒
|
||||
*/
|
||||
export async function pushWithdrawalReviewReminder(data: ShopDealerWithdraw) {
|
||||
const res = await request.post<ApiResult<unknown>>(
|
||||
'/sdy/sdy-template-message/pushWithdrawalReviewReminder',
|
||||
data
|
||||
);
|
||||
if (res.code === 0) {
|
||||
return res.message;
|
||||
}
|
||||
return Promise.reject(new Error(res.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 提现成功通知
|
||||
*/
|
||||
export async function pushNoticeOfWithdrawalToAccount(data: ShopDealerWithdraw) {
|
||||
const res = await request.post<ApiResult<unknown>>(
|
||||
'/sdy/sdy-template-message/pushNoticeOfWithdrawalToAccount',
|
||||
data
|
||||
);
|
||||
if (res.code === 0) {
|
||||
return res.message;
|
||||
}
|
||||
return Promise.reject(new Error(res.message));
|
||||
}
|
||||
|
||||
|
||||
@@ -1,123 +0,0 @@
|
||||
import type { PageParam } from '@/api/index';
|
||||
|
||||
/**
|
||||
* 商品文章
|
||||
*/
|
||||
export interface ShopArticle {
|
||||
// 文章ID
|
||||
articleId?: number;
|
||||
// 文章标题
|
||||
title?: string;
|
||||
// 文章类型 0常规 1视频
|
||||
type?: number;
|
||||
// 模型
|
||||
model?: string;
|
||||
// 详情页模板
|
||||
detail?: string;
|
||||
// 文章分类ID
|
||||
categoryId?: number;
|
||||
// 上级id, 0是顶级
|
||||
parentId?: number;
|
||||
// 话题
|
||||
topic?: string;
|
||||
// 标签
|
||||
tags?: string;
|
||||
// 封面图
|
||||
image?: string;
|
||||
// 封面图宽
|
||||
imageWidth?: number;
|
||||
// 封面图高
|
||||
imageHeight?: number;
|
||||
// 付费金额
|
||||
price?: string;
|
||||
// 开始时间
|
||||
startTime?: string;
|
||||
// 结束时间
|
||||
endTime?: string;
|
||||
// 来源
|
||||
source?: string;
|
||||
// 产品概述
|
||||
overview?: string;
|
||||
// 虚拟阅读量(仅用作展示)
|
||||
virtualViews?: number;
|
||||
// 实际阅读量
|
||||
actualViews?: number;
|
||||
// 评分
|
||||
rate?: string;
|
||||
// 列表显示方式(10小图展示 20大图展示)
|
||||
showType?: number;
|
||||
// 访问密码
|
||||
password?: string;
|
||||
// 可见类型 0所有人 1登录可见 2密码可见
|
||||
permission?: number;
|
||||
// 发布来源客户端 (APP、H5、小程序等)
|
||||
platform?: string;
|
||||
// 文章附件
|
||||
files?: string;
|
||||
// 视频地址
|
||||
video?: string;
|
||||
// 接受的文件类型
|
||||
accept?: string;
|
||||
// 经度
|
||||
longitude?: string;
|
||||
// 纬度
|
||||
latitude?: string;
|
||||
// 所在省份
|
||||
province?: string;
|
||||
// 所在城市
|
||||
city?: string;
|
||||
// 所在辖区
|
||||
region?: string;
|
||||
// 街道地址
|
||||
address?: string;
|
||||
// 点赞数
|
||||
likes?: number;
|
||||
// 评论数
|
||||
commentNumbers?: number;
|
||||
// 提醒谁看
|
||||
toUsers?: string;
|
||||
// 作者
|
||||
author?: string;
|
||||
// 推荐
|
||||
recommend?: number;
|
||||
// 报名人数
|
||||
bmUsers?: number;
|
||||
// 用户ID
|
||||
userId?: number;
|
||||
// 项目ID
|
||||
projectId?: number;
|
||||
// 语言
|
||||
lang?: string;
|
||||
// 关联默认语言的文章ID
|
||||
langArticleId?: number;
|
||||
// 是否自动翻译
|
||||
translation?: string;
|
||||
// 编辑器类型 0 Markdown编辑器 1 富文本编辑器
|
||||
editor?: string;
|
||||
// pdf文件地址
|
||||
pdfUrl?: string;
|
||||
// 版本号
|
||||
version?: number;
|
||||
// 排序(数字越小越靠前)
|
||||
sortNumber?: number;
|
||||
// 备注
|
||||
comments?: string;
|
||||
// 状态, 0已发布, 1待审核 2已驳回 3违规内容
|
||||
status?: number;
|
||||
// 是否删除, 0否, 1是
|
||||
deleted?: number;
|
||||
// 租户id
|
||||
tenantId?: number;
|
||||
// 创建时间
|
||||
createTime?: string;
|
||||
// 修改时间
|
||||
updateTime?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 商品文章搜索条件
|
||||
*/
|
||||
export interface ShopArticleParam extends PageParam {
|
||||
articleId?: number;
|
||||
keywords?: string;
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { PageParam } from '@/api';
|
||||
import type { PageParam } from '@/api/index';
|
||||
|
||||
/**
|
||||
* 优惠券
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { PageParam } from '@/api';
|
||||
import type { PageParam } from '@/api/index';
|
||||
|
||||
/**
|
||||
* 分销商申请记录表
|
||||
@@ -10,14 +10,6 @@ export interface ShopDealerApply {
|
||||
userId?: number;
|
||||
// 姓名
|
||||
realName?: string;
|
||||
// 分销商名称
|
||||
dealerName?: string;
|
||||
// 分销商编号
|
||||
dealerCode?: string;
|
||||
// 详细地址
|
||||
address?: string;
|
||||
// 金额
|
||||
money?: number;
|
||||
// 手机号
|
||||
mobile?: string;
|
||||
// 推荐人用户ID
|
||||
@@ -25,9 +17,7 @@ export interface ShopDealerApply {
|
||||
// 申请方式(10需后台审核 20无需审核)
|
||||
applyType?: number;
|
||||
// 申请时间
|
||||
applyTime?: string;
|
||||
// 签单时间
|
||||
contractTime?: string;
|
||||
applyTime?: number;
|
||||
// 审核状态 (10待审核 20审核通过 30驳回)
|
||||
applyStatus?: number;
|
||||
// 审核时间
|
||||
@@ -40,14 +30,6 @@ export interface ShopDealerApply {
|
||||
createTime?: string;
|
||||
// 修改时间
|
||||
updateTime?: string;
|
||||
// 过期时间
|
||||
expirationTime?: string;
|
||||
// 备注
|
||||
comments?: string;
|
||||
// 昵称
|
||||
nickName?: string;
|
||||
// 推荐人名称
|
||||
refereeName?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -55,10 +37,7 @@ export interface ShopDealerApply {
|
||||
*/
|
||||
export interface ShopDealerApplyParam extends PageParam {
|
||||
applyId?: number;
|
||||
type?: number;
|
||||
dealerName?: string;
|
||||
mobile?: string;
|
||||
userId?: number;
|
||||
keywords?: string;
|
||||
applyStatus?: number; // 申请状态筛选 (10待审核 20审核通过 30驳回)
|
||||
}
|
||||
|
||||
@@ -1,45 +0,0 @@
|
||||
import type { PageParam } from '@/api';
|
||||
|
||||
/**
|
||||
* 分销商提现银行卡
|
||||
*/
|
||||
export interface ShopDealerBank {
|
||||
// 主键ID
|
||||
id?: number;
|
||||
// 分销商用户ID
|
||||
userId?: number;
|
||||
// 开户行名称
|
||||
bankName?: string;
|
||||
// 银行开户名
|
||||
bankAccount?: string;
|
||||
// 银行卡号
|
||||
bankCard?: string;
|
||||
// 申请状态 (10待审核 20审核通过 30驳回)
|
||||
applyStatus?: number;
|
||||
// 审核时间
|
||||
auditTime?: number;
|
||||
// 驳回原因
|
||||
rejectReason?: string;
|
||||
// 是否默认
|
||||
isDefault?: boolean;
|
||||
// 租户id
|
||||
tenantId?: number;
|
||||
// 创建时间
|
||||
createTime?: string;
|
||||
// 修改时间
|
||||
updateTime?: string;
|
||||
// 类型
|
||||
type?: string;
|
||||
// 名称
|
||||
name?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 分销商提现银行卡搜索条件
|
||||
*/
|
||||
export interface ShopDealerBankParam extends PageParam {
|
||||
id?: number;
|
||||
userId?: number;
|
||||
isDefault?: boolean;
|
||||
keywords?: string;
|
||||
}
|
||||
@@ -99,17 +99,3 @@ export async function getShopDealerCapital(id: number) {
|
||||
}
|
||||
return Promise.reject(new Error(res.message));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 根据id查询分销商资金明细表
|
||||
*/
|
||||
export async function getShopDealerCapitalByOrderNo(orderNo: string) {
|
||||
const res = await request.get<ApiResult<ShopDealerCapital>>(
|
||||
'/shop/shop-dealer-capital/getByCode/' + orderNo
|
||||
);
|
||||
if (res.code === 0 && res.data) {
|
||||
return res.data;
|
||||
}
|
||||
return Promise.reject(new Error(res.message));
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { PageParam } from '@/api';
|
||||
import type { PageParam } from '@/api/index';
|
||||
|
||||
/**
|
||||
* 分销商资金明细表
|
||||
@@ -8,14 +8,14 @@ export interface ShopDealerCapital {
|
||||
id?: number;
|
||||
// 分销商用户ID
|
||||
userId?: number;
|
||||
// 订单编号
|
||||
orderNo?: string;
|
||||
// 订单ID
|
||||
orderId?: number;
|
||||
// 资金流动类型 (10佣金收入 20提现支出 30转账支出 40转账收入)
|
||||
flowType?: number;
|
||||
// 金额
|
||||
money?: string;
|
||||
// 描述
|
||||
comments?: string;
|
||||
describe?: string;
|
||||
// 对方用户ID
|
||||
toUserId?: number;
|
||||
// 商城ID
|
||||
@@ -31,8 +31,11 @@ export interface ShopDealerCapital {
|
||||
*/
|
||||
export interface ShopDealerCapitalParam extends PageParam {
|
||||
id?: number;
|
||||
orderNo?: string;
|
||||
// 仅查询当前分销商的收益/资金明细
|
||||
userId?: number;
|
||||
month?: string,
|
||||
// 可选:按订单过滤
|
||||
orderId?: number;
|
||||
// 可选:资金流动类型过滤
|
||||
flowType?: number;
|
||||
keywords?: string;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { PageParam } from '@/api';
|
||||
import type { PageParam } from '@/api/index';
|
||||
|
||||
/**
|
||||
* 分销商订单记录表
|
||||
@@ -6,14 +6,13 @@ import type { PageParam } from '@/api';
|
||||
export interface ShopDealerOrder {
|
||||
// 主键ID
|
||||
id?: number;
|
||||
// 商品名称
|
||||
title?: string;
|
||||
// 买家用户ID
|
||||
userId?: number;
|
||||
// 昵称
|
||||
nickname?: string;
|
||||
// 订单编号
|
||||
// 订单编号(部分接口会直接返回订单号字符串)
|
||||
orderNo?: string;
|
||||
// 订单ID
|
||||
orderId?: number;
|
||||
// 订单总金额(不含运费)
|
||||
orderPrice?: string;
|
||||
// 分销商用户id(一级)
|
||||
@@ -28,32 +27,16 @@ export interface ShopDealerOrder {
|
||||
secondMoney?: string;
|
||||
// 分销佣金(三级)
|
||||
thirdMoney?: string;
|
||||
// 分销商昵称(一级)
|
||||
firstNickname: undefined,
|
||||
// 分销商昵称(二级)
|
||||
secondNickname: undefined,
|
||||
// 分销商昵称(三级)
|
||||
thirdNickname: undefined,
|
||||
// 订单结算金额
|
||||
settledPrice?: string;
|
||||
// 换算成度
|
||||
degreePrice?: string;
|
||||
// 单价
|
||||
price?: string;
|
||||
// 订单支付金额
|
||||
payPrice?: string;
|
||||
// 订单是否失效(0未失效 1已失效)
|
||||
isInvalid?: number;
|
||||
// 佣金结算(0未结算 1已结算)
|
||||
isSettled?: number;
|
||||
// 分销佣金比例
|
||||
rate?: number;
|
||||
// 订单月份
|
||||
month?: string;
|
||||
// 佣金解冻(0未解冻 1已解冻)
|
||||
isUnfreeze?: number;
|
||||
// 订单状态
|
||||
orderStatus?: number;
|
||||
// 结算时间
|
||||
settleTime?: number;
|
||||
// 订单备注
|
||||
comments?: string;
|
||||
// 商城ID
|
||||
tenantId?: number;
|
||||
// 创建时间
|
||||
@@ -71,9 +54,7 @@ export interface ShopDealerOrderParam extends PageParam {
|
||||
secondUserId?: number;
|
||||
thirdUserId?: number;
|
||||
userId?: number;
|
||||
// 数据权限/资源ID(通常传当前登录用户ID)
|
||||
resourceId?: number;
|
||||
isInvalid?: number;
|
||||
isSettled?: number;
|
||||
month?: string;
|
||||
keywords?: string;
|
||||
}
|
||||
|
||||
@@ -1,105 +0,0 @@
|
||||
import request from '@/utils/request';
|
||||
import type { ApiResult, PageResult } from '@/api';
|
||||
import type { ShopDealerRecord, ShopDealerRecordParam } from './model';
|
||||
|
||||
/**
|
||||
* 分页查询客户跟进情况
|
||||
*/
|
||||
export async function pageShopDealerRecord(params: ShopDealerRecordParam) {
|
||||
const res = await request.get<ApiResult<PageResult<ShopDealerRecord>>>(
|
||||
'/shop/shop-dealer-record/page',
|
||||
{
|
||||
params
|
||||
}
|
||||
);
|
||||
if (res.code === 0) {
|
||||
return res.data;
|
||||
}
|
||||
return Promise.reject(new Error(res.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询客户跟进情况列表
|
||||
*/
|
||||
export async function listShopDealerRecord(params?: ShopDealerRecordParam) {
|
||||
const res = await request.get<ApiResult<ShopDealerRecord[]>>(
|
||||
'/shop/shop-dealer-record',
|
||||
{
|
||||
params
|
||||
}
|
||||
);
|
||||
if (res.code === 0 && res.data) {
|
||||
return res.data;
|
||||
}
|
||||
return Promise.reject(new Error(res.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加客户跟进情况
|
||||
*/
|
||||
export async function addShopDealerRecord(data: ShopDealerRecord) {
|
||||
const res = await request.post<ApiResult<unknown>>(
|
||||
'/shop/shop-dealer-record',
|
||||
data
|
||||
);
|
||||
if (res.code === 0) {
|
||||
return res.message;
|
||||
}
|
||||
return Promise.reject(new Error(res.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改客户跟进情况
|
||||
*/
|
||||
export async function updateShopDealerRecord(data: ShopDealerRecord) {
|
||||
const res = await request.put<ApiResult<unknown>>(
|
||||
'/shop/shop-dealer-record',
|
||||
data
|
||||
);
|
||||
if (res.code === 0) {
|
||||
return res.message;
|
||||
}
|
||||
return Promise.reject(new Error(res.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除客户跟进情况
|
||||
*/
|
||||
export async function removeShopDealerRecord(id?: number) {
|
||||
const res = await request.del<ApiResult<unknown>>(
|
||||
'/shop/shop-dealer-record/' + id
|
||||
);
|
||||
if (res.code === 0) {
|
||||
return res.message;
|
||||
}
|
||||
return Promise.reject(new Error(res.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量删除客户跟进情况
|
||||
*/
|
||||
export async function removeBatchShopDealerRecord(data: (number | undefined)[]) {
|
||||
const res = await request.del<ApiResult<unknown>>(
|
||||
'/shop/shop-dealer-record/batch',
|
||||
{
|
||||
data
|
||||
}
|
||||
);
|
||||
if (res.code === 0) {
|
||||
return res.message;
|
||||
}
|
||||
return Promise.reject(new Error(res.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据id查询客户跟进情况
|
||||
*/
|
||||
export async function getShopDealerRecord(id: number) {
|
||||
const res = await request.get<ApiResult<ShopDealerRecord>>(
|
||||
'/shop/shop-dealer-record/' + id
|
||||
);
|
||||
if (res.code === 0 && res.data) {
|
||||
return res.data;
|
||||
}
|
||||
return Promise.reject(new Error(res.message));
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
import type { PageParam } from '@/api';
|
||||
|
||||
/**
|
||||
* 客户跟进情况
|
||||
*/
|
||||
export interface ShopDealerRecord {
|
||||
// ID
|
||||
id?: number;
|
||||
// 上级id, 0是顶级
|
||||
parentId?: number;
|
||||
// 客户ID
|
||||
dealerId?: number;
|
||||
// 内容
|
||||
content?: string;
|
||||
// 用户ID
|
||||
userId?: number;
|
||||
// 排序(数字越小越靠前)
|
||||
sortNumber?: number;
|
||||
// 备注
|
||||
comments?: string;
|
||||
// 状态, 0待处理, 1已完成
|
||||
status?: number;
|
||||
// 是否删除, 0否, 1是
|
||||
deleted?: number;
|
||||
// 租户id
|
||||
tenantId?: number;
|
||||
// 创建时间
|
||||
createTime?: string;
|
||||
// 修改时间
|
||||
updateTime?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 客户跟进情况搜索条件
|
||||
*/
|
||||
export interface ShopDealerRecordParam extends PageParam {
|
||||
id?: number;
|
||||
keywords?: string;
|
||||
}
|
||||
@@ -99,16 +99,3 @@ export async function getShopDealerReferee(id: number) {
|
||||
}
|
||||
return Promise.reject(new Error(res.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据userId查询推荐关系
|
||||
*/
|
||||
export async function getShopDealerRefereeByUserId(userId: number) {
|
||||
const res = await request.get<ApiResult<ShopDealerReferee>>(
|
||||
'/shop/shop-dealer-referee/getByUserId/' + userId
|
||||
);
|
||||
if (res.code === 0 && res.data) {
|
||||
return res.data;
|
||||
}
|
||||
return Promise.reject(new Error(res.message));
|
||||
}
|
||||
|
||||
@@ -8,20 +8,8 @@ export interface ShopDealerReferee {
|
||||
id?: number;
|
||||
// 分销商用户ID
|
||||
dealerId?: number;
|
||||
// 分销商名称
|
||||
dealerName?: string;
|
||||
// 分销商手机号
|
||||
dealerPhone?: string;
|
||||
// 用户id(被推荐人)
|
||||
userId?: number;
|
||||
// 用户头像
|
||||
avatar?: string;
|
||||
// 用户昵称
|
||||
nickname?: string;
|
||||
// 用户别名
|
||||
alias?: string;
|
||||
// 用户手机号
|
||||
phone?: string;
|
||||
// 推荐关系层级(1,2,3)
|
||||
level?: number;
|
||||
// 商城ID
|
||||
@@ -38,8 +26,6 @@ export interface ShopDealerReferee {
|
||||
export interface ShopDealerRefereeParam extends PageParam {
|
||||
id?: number;
|
||||
dealerId?: number;
|
||||
deleted?: number;
|
||||
roleId?: number;
|
||||
isAdmin?: boolean;
|
||||
keywords?: string;
|
||||
deleted?: number;
|
||||
}
|
||||
|
||||
@@ -59,21 +59,6 @@ export async function updateShopDealerUser(data: ShopDealerUser) {
|
||||
return Promise.reject(new Error(res.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改分销商用户记录表
|
||||
* @param data
|
||||
*/
|
||||
export async function updateShopDealerUserByUserId(data: ShopDealerUser) {
|
||||
const res = await request.put<ApiResult<unknown>>(
|
||||
'/shop/shop-dealer-user/updateByUserId',
|
||||
data
|
||||
);
|
||||
if (res.code === 0) {
|
||||
return res.message;
|
||||
}
|
||||
return Promise.reject(new Error(res.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除分销商用户记录表
|
||||
*/
|
||||
@@ -110,8 +95,9 @@ export async function getShopDealerUser(userId: number) {
|
||||
const res = await request.get<ApiResult<ShopDealerUser>>(
|
||||
'/shop/shop-dealer-user/' + userId
|
||||
);
|
||||
if (res.code === 0 && res.data) {
|
||||
return res.data;
|
||||
if (res.code === 0) {
|
||||
// 未注册为分销商时,后端可能返回 data=null,这里用 null 表示“没有分销商信息”
|
||||
return res.data || null;
|
||||
}
|
||||
return Promise.reject(new Error(res.message));
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { PageParam } from '@/api';
|
||||
import type { PageParam } from '@/api/index';
|
||||
|
||||
/**
|
||||
* 分销商用户记录表
|
||||
@@ -22,9 +22,6 @@ export interface ShopDealerUser {
|
||||
totalMoney?: string;
|
||||
// 推荐人用户ID
|
||||
refereeId?: number;
|
||||
dealerName?: string;
|
||||
dealerPhone?: string;
|
||||
dealerAvatar?: string;
|
||||
// 成员数量(一级)
|
||||
firstNum?: number;
|
||||
// 成员数量(二级)
|
||||
|
||||
@@ -2,6 +2,21 @@ import request from '@/utils/request';
|
||||
import type { ApiResult, PageResult } from '@/api';
|
||||
import type { ShopDealerWithdraw, ShopDealerWithdrawParam } from './model';
|
||||
|
||||
// WeChat transfer v3: backend may return `package_info` for MiniProgram to open the
|
||||
// "confirm receipt" page via `wx.requestMerchantTransfer`.
|
||||
export type ShopDealerWithdrawCreateResult =
|
||||
| string
|
||||
| {
|
||||
package_info?: string;
|
||||
packageInfo?: string;
|
||||
[k: string]: any;
|
||||
}
|
||||
| null
|
||||
| undefined;
|
||||
|
||||
// When applyStatus=20, user can "receive" (WeChat confirm receipt flow).
|
||||
export type ShopDealerWithdrawReceiveResult = ShopDealerWithdrawCreateResult;
|
||||
|
||||
/**
|
||||
* 分页查询分销商提现明细表
|
||||
*/
|
||||
@@ -33,11 +48,40 @@ export async function listShopDealerWithdraw(params?: ShopDealerWithdrawParam) {
|
||||
/**
|
||||
* 添加分销商提现明细表
|
||||
*/
|
||||
export async function addShopDealerWithdraw(data: ShopDealerWithdraw) {
|
||||
const res = await request.post<ApiResult<unknown>>(
|
||||
export async function addShopDealerWithdraw(data: ShopDealerWithdraw): Promise<ShopDealerWithdrawCreateResult> {
|
||||
const res = await request.post<ApiResult<any>>(
|
||||
'/shop/shop-dealer-withdraw',
|
||||
data
|
||||
);
|
||||
if (res.code === 0) {
|
||||
// Some backends return `message`, while WeChat transfer flow returns `data.package_info`.
|
||||
return res.data ?? res.message;
|
||||
}
|
||||
return Promise.reject(new Error(res.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 用户领取(仅当 applyStatus=20 时)- 后台返回 package_info 供小程序调起确认收款页
|
||||
*/
|
||||
export async function receiveShopDealerWithdraw(id: number): Promise<ShopDealerWithdrawReceiveResult> {
|
||||
const res = await request.post<ApiResult<any>>(
|
||||
'/shop/shop-dealer-withdraw/receive/' + id,
|
||||
{}
|
||||
);
|
||||
if (res.code === 0) {
|
||||
return res.data ?? res.message;
|
||||
}
|
||||
return Promise.reject(new Error(res.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 领取成功回调:前端确认收款后通知后台把状态置为 applyStatus=40
|
||||
*/
|
||||
export async function receiveSuccessShopDealerWithdraw(id: number) {
|
||||
const res = await request.post<ApiResult<unknown>>(
|
||||
'/shop/shop-dealer-withdraw/receive-success/' + id,
|
||||
{}
|
||||
);
|
||||
if (res.code === 0) {
|
||||
return res.message;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { PageParam } from '@/api';
|
||||
import type { PageParam } from '@/api/index';
|
||||
|
||||
/**
|
||||
* 分销商提现明细表
|
||||
@@ -30,18 +30,12 @@ export interface ShopDealerWithdraw {
|
||||
rejectReason?: string;
|
||||
// 来源客户端(APP、H5、小程序等)
|
||||
platform?: string;
|
||||
// 手机号
|
||||
phone?: string;
|
||||
// 备注
|
||||
comments?: string;
|
||||
// 租户id
|
||||
tenantId?: number;
|
||||
// 创建时间
|
||||
createTime?: string;
|
||||
// 修改时间
|
||||
updateTime?: string;
|
||||
// 附件
|
||||
image?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -51,5 +45,4 @@ export interface ShopDealerWithdrawParam extends PageParam {
|
||||
id?: number;
|
||||
userId?: number;
|
||||
keywords?: string;
|
||||
applyStatus?: number; // 申请状态筛选
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { PageParam } from '@/api';
|
||||
|
||||
/**
|
||||
* 礼品卡
|
||||
* 水票
|
||||
*/
|
||||
export interface ShopGift {
|
||||
// 礼品卡ID
|
||||
|
||||
@@ -146,4 +146,7 @@ export interface ShopGoodsParam extends PageParam {
|
||||
isShow?: number;
|
||||
stock?: number;
|
||||
keywords?: string;
|
||||
recommend?: number;
|
||||
// 0上架 1下架(以实际后端约定为准)
|
||||
status?: number;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import request from '@/utils/request';
|
||||
import request, { ErrorType, RequestError } from '@/utils/request';
|
||||
import type { ApiResult, PageResult } from '@/api';
|
||||
import type { ShopOrder, ShopOrderParam, OrderCreateRequest } from './model';
|
||||
|
||||
@@ -113,6 +113,44 @@ export interface WxPayResult {
|
||||
paySign: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 订单重新发起支付(对“已创建但未支付”的订单生成新的预支付参数,不应重复创建订单)
|
||||
*
|
||||
* 说明:不同后端版本可能暴露不同路径,这里做兼容探测;若全部失败,调用方可自行降级处理。
|
||||
*/
|
||||
export interface OrderPrepayRequest {
|
||||
orderId: number;
|
||||
payType: number;
|
||||
}
|
||||
|
||||
export async function prepayShopOrder(data: OrderPrepayRequest) {
|
||||
const urls = [
|
||||
'/shop/shop-order/pay',
|
||||
'/shop/shop-order/prepay',
|
||||
'/shop/shop-order/repay'
|
||||
];
|
||||
|
||||
let lastError: unknown;
|
||||
let businessError: unknown;
|
||||
for (const url of urls) {
|
||||
try {
|
||||
const res = await request.post<ApiResult<WxPayResult>>(url, data, { showError: false });
|
||||
// request.ts 在 code!=0 时会直接 throw;走到这里通常都是 code===0
|
||||
if (res.code === 0) return res.data;
|
||||
} catch (e) {
|
||||
// 若已命中“业务错误”(例如订单已取消/已支付),优先保留该错误用于向上提示;
|
||||
// 不要被后续的 404/网络错误覆盖掉,避免调用方误判为“不支持该接口”而降级走创建订单。
|
||||
if (!businessError && e instanceof RequestError && e.type === ErrorType.BUSINESS_ERROR) {
|
||||
businessError = e;
|
||||
} else {
|
||||
lastError = e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Promise.reject(businessError || lastError || new Error('发起支付失败'));
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建订单
|
||||
*/
|
||||
@@ -140,3 +178,18 @@ export async function repairOrder(data: ShopOrder) {
|
||||
}
|
||||
return Promise.reject(new Error(res.message));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 申请|同意退款
|
||||
*/
|
||||
export async function refundShopOrder(data: ShopOrder) {
|
||||
const res = await request.put<ApiResult<unknown>>(
|
||||
'/shop/shop-order/refund',
|
||||
data
|
||||
);
|
||||
if (res.code === 0) {
|
||||
return res.message;
|
||||
}
|
||||
return Promise.reject(new Error(res.message));
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { PageParam } from '@/api/index';
|
||||
import type { ShopOrderGoods } from '@/api/shop/shopOrderGoods/model';
|
||||
|
||||
/**
|
||||
* 订单
|
||||
@@ -26,6 +27,14 @@ export interface ShopOrder {
|
||||
merchantName?: string;
|
||||
// 商户编号
|
||||
merchantCode?: string;
|
||||
// 归属门店ID(shop_store.id)
|
||||
storeId?: number;
|
||||
// 归属门店名称
|
||||
storeName?: string;
|
||||
// 配送员用户ID(优先级派单)
|
||||
riderId?: number;
|
||||
// 发货仓库ID
|
||||
warehouseId?: number;
|
||||
// 使用的优惠券id
|
||||
couponId?: number;
|
||||
// 使用的会员卡id
|
||||
@@ -60,6 +69,8 @@ export interface ShopOrder {
|
||||
sendStartTime?: string;
|
||||
// 配送结束时间
|
||||
sendEndTime?: string;
|
||||
// 配送员送达拍照(选填)
|
||||
sendEndImg?: string;
|
||||
// 发货店铺id
|
||||
expressMerchantId?: number;
|
||||
// 发货店铺
|
||||
@@ -82,6 +93,8 @@ export interface ShopOrder {
|
||||
totalNum?: number;
|
||||
// 教练id
|
||||
coachId?: number;
|
||||
// 商品ID
|
||||
formId?: number;
|
||||
// 支付的用户id
|
||||
payUserId?: number;
|
||||
// 0余额支付, 1微信支付,102微信Native,2会员卡支付,3支付宝,4现金,5POS机,6VIP月卡,7VIP年卡,8VIP次卡,9IC月卡,10IC年卡,11IC次卡,12免费,13VIP充值卡,14IC充值卡,15积分支付,16VIP季卡,17IC季卡,18代付
|
||||
@@ -144,6 +157,8 @@ export interface ShopOrder {
|
||||
selfTakeCode?: string;
|
||||
// 是否已收到赠品
|
||||
hasTakeGift?: string;
|
||||
// 订单商品项
|
||||
orderGoods?: ShopOrderGoods[];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -162,6 +177,14 @@ export interface OrderGoodsItem {
|
||||
export interface OrderCreateRequest {
|
||||
// 商品信息列表
|
||||
goodsItems: OrderGoodsItem[];
|
||||
// 归属门店ID(shop_store.id)
|
||||
storeId?: number;
|
||||
// 归属门店名称(可选)
|
||||
storeName?: string;
|
||||
// 配送员用户ID(优先级派单)
|
||||
riderId?: number;
|
||||
// 发货仓库ID
|
||||
warehouseId?: number;
|
||||
// 收货地址ID
|
||||
addressId?: number;
|
||||
// 支付方式
|
||||
@@ -170,6 +193,8 @@ export interface OrderCreateRequest {
|
||||
couponId?: number;
|
||||
// 备注
|
||||
comments?: string;
|
||||
// 配送开始时间(用于预约/配送时间)
|
||||
sendStartTime?: string;
|
||||
// 配送方式 0快递 1自提
|
||||
deliveryType?: number;
|
||||
// 自提店铺ID
|
||||
@@ -194,6 +219,14 @@ export interface OrderGoodsItem {
|
||||
export interface OrderCreateRequest {
|
||||
// 商品信息列表
|
||||
goodsItems: OrderGoodsItem[];
|
||||
// 归属门店ID(shop_store.id)
|
||||
storeId?: number;
|
||||
// 归属门店名称(可选)
|
||||
storeName?: string;
|
||||
// 配送员用户ID(优先级派单)
|
||||
riderId?: number;
|
||||
// 发货仓库ID
|
||||
warehouseId?: number;
|
||||
// 收货地址ID
|
||||
addressId?: number;
|
||||
// 支付方式
|
||||
@@ -202,6 +235,8 @@ export interface OrderCreateRequest {
|
||||
couponId?: number;
|
||||
// 备注
|
||||
comments?: string;
|
||||
// 配送开始时间(用于预约/配送时间)
|
||||
sendStartTime?: string;
|
||||
// 配送方式 0快递 1自提
|
||||
deliveryType?: number;
|
||||
// 自提店铺ID
|
||||
@@ -220,6 +255,12 @@ export interface ShopOrderParam extends PageParam {
|
||||
payType?: number;
|
||||
isInvoice?: boolean;
|
||||
userId?: number;
|
||||
// 归属门店ID(shop_store.id)
|
||||
storeId?: number;
|
||||
// 配送员用户ID
|
||||
riderId?: number;
|
||||
// 发货仓库ID
|
||||
warehouseId?: number;
|
||||
keywords?: string;
|
||||
deliveryStatus?: number;
|
||||
statusFilter?: number;
|
||||
|
||||
101
src/api/shop/shopStore/index.ts
Normal file
101
src/api/shop/shopStore/index.ts
Normal file
@@ -0,0 +1,101 @@
|
||||
import request from '@/utils/request';
|
||||
import type { ApiResult, PageResult } from '@/api';
|
||||
import type { ShopStore, ShopStoreParam } from './model';
|
||||
|
||||
/**
|
||||
* 分页查询门店
|
||||
*/
|
||||
export async function pageShopStore(params: ShopStoreParam) {
|
||||
const res = await request.get<ApiResult<PageResult<ShopStore>>>(
|
||||
'/shop/shop-store/page',
|
||||
params
|
||||
);
|
||||
if (res.code === 0) {
|
||||
return res.data;
|
||||
}
|
||||
return Promise.reject(new Error(res.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询门店列表
|
||||
*/
|
||||
export async function listShopStore(params?: ShopStoreParam) {
|
||||
const res = await request.get<ApiResult<ShopStore[]>>(
|
||||
'/shop/shop-store',
|
||||
params
|
||||
);
|
||||
if (res.code === 0 && res.data) {
|
||||
return res.data;
|
||||
}
|
||||
return Promise.reject(new Error(res.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加门店
|
||||
*/
|
||||
export async function addShopStore(data: ShopStore) {
|
||||
const res = await request.post<ApiResult<unknown>>(
|
||||
'/shop/shop-store',
|
||||
data
|
||||
);
|
||||
if (res.code === 0) {
|
||||
return res.message;
|
||||
}
|
||||
return Promise.reject(new Error(res.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改门店
|
||||
*/
|
||||
export async function updateShopStore(data: ShopStore) {
|
||||
const res = await request.put<ApiResult<unknown>>(
|
||||
'/shop/shop-store',
|
||||
data
|
||||
);
|
||||
if (res.code === 0) {
|
||||
return res.message;
|
||||
}
|
||||
return Promise.reject(new Error(res.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除门店
|
||||
*/
|
||||
export async function removeShopStore(id?: number) {
|
||||
const res = await request.del<ApiResult<unknown>>(
|
||||
'/shop/shop-store/' + id
|
||||
);
|
||||
if (res.code === 0) {
|
||||
return res.message;
|
||||
}
|
||||
return Promise.reject(new Error(res.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量删除门店
|
||||
*/
|
||||
export async function removeBatchShopStore(data: (number | undefined)[]) {
|
||||
const res = await request.del<ApiResult<unknown>>(
|
||||
'/shop/shop-store/batch',
|
||||
{
|
||||
data
|
||||
}
|
||||
);
|
||||
if (res.code === 0) {
|
||||
return res.message;
|
||||
}
|
||||
return Promise.reject(new Error(res.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据id查询门店
|
||||
*/
|
||||
export async function getShopStore(id: number) {
|
||||
const res = await request.get<ApiResult<ShopStore>>(
|
||||
'/shop/shop-store/' + id
|
||||
);
|
||||
if (res.code === 0 && res.data) {
|
||||
return res.data;
|
||||
}
|
||||
return Promise.reject(new Error(res.message));
|
||||
}
|
||||
63
src/api/shop/shopStore/model/index.ts
Normal file
63
src/api/shop/shopStore/model/index.ts
Normal file
@@ -0,0 +1,63 @@
|
||||
import type { PageParam } from '@/api';
|
||||
|
||||
/**
|
||||
* 门店
|
||||
*/
|
||||
export interface ShopStore {
|
||||
// 自增ID
|
||||
id?: number;
|
||||
// 店铺名称
|
||||
name?: string;
|
||||
// 门店地址
|
||||
address?: string;
|
||||
// 手机号码
|
||||
phone?: string;
|
||||
// 邮箱
|
||||
email?: string;
|
||||
// 门店经理
|
||||
managerName?: string;
|
||||
// 门店banner
|
||||
shopBanner?: string;
|
||||
// 所在省份
|
||||
province?: string;
|
||||
// 所在城市
|
||||
city?: string;
|
||||
// 所在辖区
|
||||
region?: string;
|
||||
// 经度和纬度
|
||||
lngAndLat?: string;
|
||||
// 位置
|
||||
location?:string;
|
||||
// 区域
|
||||
district?: string;
|
||||
// 轮廓
|
||||
points?: string;
|
||||
// 用户ID
|
||||
userId?: number;
|
||||
// 默认仓库ID(shop_warehouse.id)
|
||||
warehouseId?: number;
|
||||
// 默认仓库名称(可选)
|
||||
warehouseName?: string;
|
||||
// 状态
|
||||
status?: number;
|
||||
// 备注
|
||||
comments?: string;
|
||||
// 排序号
|
||||
sortNumber?: number;
|
||||
// 是否删除
|
||||
isDelete?: number;
|
||||
// 租户id
|
||||
tenantId?: number;
|
||||
// 创建时间
|
||||
createTime?: string;
|
||||
// 修改时间
|
||||
updateTime?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 门店搜索条件
|
||||
*/
|
||||
export interface ShopStoreParam extends PageParam {
|
||||
id?: number;
|
||||
keywords?: string;
|
||||
}
|
||||
101
src/api/shop/shopStoreFence/index.ts
Normal file
101
src/api/shop/shopStoreFence/index.ts
Normal file
@@ -0,0 +1,101 @@
|
||||
import request from '@/utils/request';
|
||||
import type { ApiResult, PageResult } from '@/api/index';
|
||||
import type { ShopStoreFence, ShopStoreFenceParam } from './model';
|
||||
|
||||
/**
|
||||
* 分页查询黄家明_电子围栏
|
||||
*/
|
||||
export async function pageShopStoreFence(params: ShopStoreFenceParam) {
|
||||
const res = await request.get<ApiResult<PageResult<ShopStoreFence>>>(
|
||||
'/shop/shop-store-fence/page',
|
||||
params
|
||||
);
|
||||
if (res.code === 0) {
|
||||
return res.data;
|
||||
}
|
||||
return Promise.reject(new Error(res.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询黄家明_电子围栏列表
|
||||
*/
|
||||
export async function listShopStoreFence(params?: ShopStoreFenceParam) {
|
||||
const res = await request.get<ApiResult<ShopStoreFence[]>>(
|
||||
'/shop/shop-store-fence',
|
||||
params
|
||||
);
|
||||
if (res.code === 0 && res.data) {
|
||||
return res.data;
|
||||
}
|
||||
return Promise.reject(new Error(res.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加黄家明_电子围栏
|
||||
*/
|
||||
export async function addShopStoreFence(data: ShopStoreFence) {
|
||||
const res = await request.post<ApiResult<unknown>>(
|
||||
'/shop/shop-store-fence',
|
||||
data
|
||||
);
|
||||
if (res.code === 0) {
|
||||
return res.message;
|
||||
}
|
||||
return Promise.reject(new Error(res.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改黄家明_电子围栏
|
||||
*/
|
||||
export async function updateShopStoreFence(data: ShopStoreFence) {
|
||||
const res = await request.put<ApiResult<unknown>>(
|
||||
'/shop/shop-store-fence',
|
||||
data
|
||||
);
|
||||
if (res.code === 0) {
|
||||
return res.message;
|
||||
}
|
||||
return Promise.reject(new Error(res.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除黄家明_电子围栏
|
||||
*/
|
||||
export async function removeShopStoreFence(id?: number) {
|
||||
const res = await request.del<ApiResult<unknown>>(
|
||||
'/shop/shop-store-fence/' + id
|
||||
);
|
||||
if (res.code === 0) {
|
||||
return res.message;
|
||||
}
|
||||
return Promise.reject(new Error(res.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量删除黄家明_电子围栏
|
||||
*/
|
||||
export async function removeBatchShopStoreFence(data: (number | undefined)[]) {
|
||||
const res = await request.del<ApiResult<unknown>>(
|
||||
'/shop/shop-store-fence/batch',
|
||||
{
|
||||
data
|
||||
}
|
||||
);
|
||||
if (res.code === 0) {
|
||||
return res.message;
|
||||
}
|
||||
return Promise.reject(new Error(res.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据id查询黄家明_电子围栏
|
||||
*/
|
||||
export async function getShopStoreFence(id: number) {
|
||||
const res = await request.get<ApiResult<ShopStoreFence>>(
|
||||
'/shop/shop-store-fence/' + id
|
||||
);
|
||||
if (res.code === 0 && res.data) {
|
||||
return res.data;
|
||||
}
|
||||
return Promise.reject(new Error(res.message));
|
||||
}
|
||||
43
src/api/shop/shopStoreFence/model/index.ts
Normal file
43
src/api/shop/shopStoreFence/model/index.ts
Normal file
@@ -0,0 +1,43 @@
|
||||
import type { PageParam } from '@/api/index';
|
||||
|
||||
/**
|
||||
* 黄家明_电子围栏
|
||||
*/
|
||||
export interface ShopStoreFence {
|
||||
// 自增ID
|
||||
id?: number;
|
||||
// 围栏名称
|
||||
name?: string;
|
||||
// 类型 0圆形 1方形
|
||||
type?: number;
|
||||
// 定位
|
||||
location?: string;
|
||||
// 经度
|
||||
longitude?: string;
|
||||
// 纬度
|
||||
latitude?: string;
|
||||
// 区域
|
||||
district?: string;
|
||||
// 电子围栏轮廓
|
||||
points?: string;
|
||||
// 排序(数字越小越靠前)
|
||||
sortNumber?: number;
|
||||
// 备注
|
||||
comments?: string;
|
||||
// 状态, 0正常, 1冻结
|
||||
status?: number;
|
||||
// 租户id
|
||||
tenantId?: number;
|
||||
// 创建时间
|
||||
createTime?: string;
|
||||
// 修改时间
|
||||
updateTime?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 黄家明_电子围栏搜索条件
|
||||
*/
|
||||
export interface ShopStoreFenceParam extends PageParam {
|
||||
id?: number;
|
||||
keywords?: string;
|
||||
}
|
||||
@@ -1,13 +1,13 @@
|
||||
import request from '@/utils/request';
|
||||
import type { ApiResult, PageResult } from '@/api';
|
||||
import type { ShopDealerBank, ShopDealerBankParam } from './model';
|
||||
import type { ShopStoreRider, ShopStoreRiderParam } from './model';
|
||||
|
||||
/**
|
||||
* 分页查询分销商银行卡
|
||||
* 分页查询配送员
|
||||
*/
|
||||
export async function pageShopDealerBank(params: ShopDealerBankParam) {
|
||||
const res = await request.get<ApiResult<PageResult<ShopDealerBank>>>(
|
||||
'/shop/shop-dealer-bank/page',
|
||||
export async function pageShopStoreRider(params: ShopStoreRiderParam) {
|
||||
const res = await request.get<ApiResult<PageResult<ShopStoreRider>>>(
|
||||
'/shop/shop-store-rider/page',
|
||||
params
|
||||
);
|
||||
if (res.code === 0) {
|
||||
@@ -17,11 +17,11 @@ export async function pageShopDealerBank(params: ShopDealerBankParam) {
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询分销商银行卡列表
|
||||
* 查询配送员列表
|
||||
*/
|
||||
export async function listShopDealerBank(params?: ShopDealerBankParam) {
|
||||
const res = await request.get<ApiResult<ShopDealerBank[]>>(
|
||||
'/shop/shop-dealer-bank',
|
||||
export async function listShopStoreRider(params?: ShopStoreRiderParam) {
|
||||
const res = await request.get<ApiResult<ShopStoreRider[]>>(
|
||||
'/shop/shop-store-rider',
|
||||
params
|
||||
);
|
||||
if (res.code === 0 && res.data) {
|
||||
@@ -31,11 +31,11 @@ export async function listShopDealerBank(params?: ShopDealerBankParam) {
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加分销商银行卡
|
||||
* 添加配送员
|
||||
*/
|
||||
export async function addShopDealerBank(data: ShopDealerBank) {
|
||||
export async function addShopStoreRider(data: ShopStoreRider) {
|
||||
const res = await request.post<ApiResult<unknown>>(
|
||||
'/shop/shop-dealer-bank',
|
||||
'/shop/shop-store-rider',
|
||||
data
|
||||
);
|
||||
if (res.code === 0) {
|
||||
@@ -45,11 +45,11 @@ export async function addShopDealerBank(data: ShopDealerBank) {
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改分销商银行卡
|
||||
* 修改配送员
|
||||
*/
|
||||
export async function updateShopDealerBank(data: ShopDealerBank) {
|
||||
export async function updateShopStoreRider(data: ShopStoreRider) {
|
||||
const res = await request.put<ApiResult<unknown>>(
|
||||
'/shop/shop-dealer-bank',
|
||||
'/shop/shop-store-rider',
|
||||
data
|
||||
);
|
||||
if (res.code === 0) {
|
||||
@@ -59,11 +59,11 @@ export async function updateShopDealerBank(data: ShopDealerBank) {
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除分销商银行卡
|
||||
* 删除配送员
|
||||
*/
|
||||
export async function removeShopDealerBank(id?: number) {
|
||||
export async function removeShopStoreRider(id?: number) {
|
||||
const res = await request.del<ApiResult<unknown>>(
|
||||
'/shop/shop-dealer-bank/' + id
|
||||
'/shop/shop-store-rider/' + id
|
||||
);
|
||||
if (res.code === 0) {
|
||||
return res.message;
|
||||
@@ -72,11 +72,11 @@ export async function removeShopDealerBank(id?: number) {
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量删除分销商银行卡
|
||||
* 批量删除配送员
|
||||
*/
|
||||
export async function removeBatchShopDealerBank(data: (number | undefined)[]) {
|
||||
export async function removeBatchShopStoreRider(data: (number | undefined)[]) {
|
||||
const res = await request.del<ApiResult<unknown>>(
|
||||
'/shop/shop-dealer-bank/batch',
|
||||
'/shop/shop-store-rider/batch',
|
||||
{
|
||||
data
|
||||
}
|
||||
@@ -88,11 +88,11 @@ export async function removeBatchShopDealerBank(data: (number | undefined)[]) {
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据id查询分销商银行卡
|
||||
* 根据id查询配送员
|
||||
*/
|
||||
export async function getShopDealerBank(id: number) {
|
||||
const res = await request.get<ApiResult<ShopDealerBank>>(
|
||||
'/shop/shop-dealer-bank/' + id
|
||||
export async function getShopStoreRider(id: number) {
|
||||
const res = await request.get<ApiResult<ShopStoreRider>>(
|
||||
'/shop/shop-store-rider/' + id
|
||||
);
|
||||
if (res.code === 0 && res.data) {
|
||||
return res.data;
|
||||
71
src/api/shop/shopStoreRider/model/index.ts
Normal file
71
src/api/shop/shopStoreRider/model/index.ts
Normal file
@@ -0,0 +1,71 @@
|
||||
import type { PageParam } from '@/api';
|
||||
|
||||
/**
|
||||
* 配送员
|
||||
*/
|
||||
export interface ShopStoreRider {
|
||||
// 主键ID
|
||||
id?: string;
|
||||
// 配送点ID(shop_dealer.id)
|
||||
dealerId?: number;
|
||||
// 骑手编号(可选)
|
||||
riderNo?: string;
|
||||
// 姓名
|
||||
realName?: string;
|
||||
// 手机号
|
||||
mobile?: string;
|
||||
// 头像
|
||||
avatar?: string;
|
||||
// 身份证号(可选)
|
||||
idCardNo?: string;
|
||||
// 状态:1启用;0禁用
|
||||
status?: number;
|
||||
// 接单状态:0休息/下线;1在线;2忙碌
|
||||
workStatus?: number;
|
||||
// 是否开启自动派单:1是;0否
|
||||
autoDispatchEnabled?: number;
|
||||
// 派单优先级(同小区多骑手时可用,值越大越优先)
|
||||
dispatchPriority?: number;
|
||||
// 最大同时配送单数(0表示不限制)
|
||||
maxOnhandOrders?: number;
|
||||
// 是否计算工资(提成):1计算;0不计算(如三方配送点可设0)
|
||||
commissionCalcEnabled?: number;
|
||||
// 水每桶提成金额(元/桶)
|
||||
waterBucketUnitFee?: string;
|
||||
// 其他商品提成方式:1按订单固定金额;2按订单金额比例;3按商品规则(另表)
|
||||
otherGoodsCommissionType?: number;
|
||||
// 其他商品提成值:固定金额(元)或比例(%)
|
||||
otherGoodsCommissionValue?: string;
|
||||
// 用户ID
|
||||
userId?: number;
|
||||
// 经度(配送员当前位置)
|
||||
longitude?: string;
|
||||
// 纬度(配送员当前位置)
|
||||
latitude?: string;
|
||||
// 备注
|
||||
comments?: string;
|
||||
// 排序号
|
||||
sortNumber?: number;
|
||||
// 是否删除
|
||||
isDelete?: number;
|
||||
// 租户id
|
||||
tenantId?: number;
|
||||
// 创建时间
|
||||
createTime?: string;
|
||||
// 修改时间
|
||||
updateTime?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 配送员搜索条件
|
||||
*/
|
||||
export interface ShopStoreRiderParam extends PageParam {
|
||||
id?: number;
|
||||
keywords?: string;
|
||||
// 配送点/门店ID(后端可能用 dealerId 或 storeId)
|
||||
dealerId?: number;
|
||||
storeId?: number;
|
||||
status?: number;
|
||||
workStatus?: number;
|
||||
autoDispatchEnabled?: number;
|
||||
}
|
||||
101
src/api/shop/shopStoreUser/index.ts
Normal file
101
src/api/shop/shopStoreUser/index.ts
Normal file
@@ -0,0 +1,101 @@
|
||||
import request from '@/utils/request';
|
||||
import type { ApiResult, PageResult } from '@/api';
|
||||
import type { ShopStoreUser, ShopStoreUserParam } from './model';
|
||||
|
||||
/**
|
||||
* 分页查询店员
|
||||
*/
|
||||
export async function pageShopStoreUser(params: ShopStoreUserParam) {
|
||||
const res = await request.get<ApiResult<PageResult<ShopStoreUser>>>(
|
||||
'/shop/shop-store-user/page',
|
||||
params
|
||||
);
|
||||
if (res.code === 0) {
|
||||
return res.data;
|
||||
}
|
||||
return Promise.reject(new Error(res.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询店员列表
|
||||
*/
|
||||
export async function listShopStoreUser(params?: ShopStoreUserParam) {
|
||||
const res = await request.get<ApiResult<ShopStoreUser[]>>(
|
||||
'/shop/shop-store-user',
|
||||
params
|
||||
);
|
||||
if (res.code === 0 && res.data) {
|
||||
return res.data;
|
||||
}
|
||||
return Promise.reject(new Error(res.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加店员
|
||||
*/
|
||||
export async function addShopStoreUser(data: ShopStoreUser) {
|
||||
const res = await request.post<ApiResult<unknown>>(
|
||||
'/shop/shop-store-user',
|
||||
data
|
||||
);
|
||||
if (res.code === 0) {
|
||||
return res.message;
|
||||
}
|
||||
return Promise.reject(new Error(res.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改店员
|
||||
*/
|
||||
export async function updateShopStoreUser(data: ShopStoreUser) {
|
||||
const res = await request.put<ApiResult<unknown>>(
|
||||
'/shop/shop-store-user',
|
||||
data
|
||||
);
|
||||
if (res.code === 0) {
|
||||
return res.message;
|
||||
}
|
||||
return Promise.reject(new Error(res.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除店员
|
||||
*/
|
||||
export async function removeShopStoreUser(id?: number) {
|
||||
const res = await request.del<ApiResult<unknown>>(
|
||||
'/shop/shop-store-user/' + id
|
||||
);
|
||||
if (res.code === 0) {
|
||||
return res.message;
|
||||
}
|
||||
return Promise.reject(new Error(res.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量删除店员
|
||||
*/
|
||||
export async function removeBatchShopStoreUser(data: (number | undefined)[]) {
|
||||
const res = await request.del<ApiResult<unknown>>(
|
||||
'/shop/shop-store-user/batch',
|
||||
{
|
||||
data
|
||||
}
|
||||
);
|
||||
if (res.code === 0) {
|
||||
return res.message;
|
||||
}
|
||||
return Promise.reject(new Error(res.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据id查询店员
|
||||
*/
|
||||
export async function getShopStoreUser(id: number) {
|
||||
const res = await request.get<ApiResult<ShopStoreUser>>(
|
||||
'/shop/shop-store-user/' + id
|
||||
);
|
||||
if (res.code === 0 && res.data) {
|
||||
return res.data;
|
||||
}
|
||||
return Promise.reject(new Error(res.message));
|
||||
}
|
||||
36
src/api/shop/shopStoreUser/model/index.ts
Normal file
36
src/api/shop/shopStoreUser/model/index.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
import type { PageParam } from '@/api';
|
||||
|
||||
/**
|
||||
* 店员
|
||||
*/
|
||||
export interface ShopStoreUser {
|
||||
// 主键ID
|
||||
id?: number;
|
||||
// 配送点ID(shop_dealer.id)
|
||||
storeId?: number;
|
||||
// 用户ID
|
||||
userId?: number;
|
||||
// 备注
|
||||
comments?: string;
|
||||
// 排序号
|
||||
sortNumber?: number;
|
||||
// 是否删除
|
||||
isDelete?: number;
|
||||
// 租户id
|
||||
tenantId?: number;
|
||||
// 创建时间
|
||||
createTime?: string;
|
||||
// 修改时间
|
||||
updateTime?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 店员搜索条件
|
||||
*/
|
||||
export interface ShopStoreUserParam extends PageParam {
|
||||
id?: number;
|
||||
keywords?: string;
|
||||
storeId?: number;
|
||||
userId?: number;
|
||||
isDelete?: number;
|
||||
}
|
||||
101
src/api/shop/shopStoreWarehouse/index.ts
Normal file
101
src/api/shop/shopStoreWarehouse/index.ts
Normal file
@@ -0,0 +1,101 @@
|
||||
import request from '@/utils/request';
|
||||
import type { ApiResult, PageResult } from '@/api/index';
|
||||
import type { ShopStoreWarehouse, ShopStoreWarehouseParam } from './model';
|
||||
|
||||
/**
|
||||
* 分页查询仓库
|
||||
*/
|
||||
export async function pageShopStoreWarehouse(params: ShopStoreWarehouseParam) {
|
||||
const res = await request.get<ApiResult<PageResult<ShopStoreWarehouse>>>(
|
||||
'/shop/shop-store-warehouse/page',
|
||||
params
|
||||
);
|
||||
if (res.code === 0) {
|
||||
return res.data;
|
||||
}
|
||||
return Promise.reject(new Error(res.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询仓库列表
|
||||
*/
|
||||
export async function listShopStoreWarehouse(params?: ShopStoreWarehouseParam) {
|
||||
const res = await request.get<ApiResult<ShopStoreWarehouse[]>>(
|
||||
'/shop/shop-store-warehouse',
|
||||
params
|
||||
);
|
||||
if (res.code === 0 && res.data) {
|
||||
return res.data;
|
||||
}
|
||||
return Promise.reject(new Error(res.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加仓库
|
||||
*/
|
||||
export async function addShopStoreWarehouse(data: ShopStoreWarehouse) {
|
||||
const res = await request.post<ApiResult<unknown>>(
|
||||
'/shop/shop-store-warehouse',
|
||||
data
|
||||
);
|
||||
if (res.code === 0) {
|
||||
return res.message;
|
||||
}
|
||||
return Promise.reject(new Error(res.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改仓库
|
||||
*/
|
||||
export async function updateShopStoreWarehouse(data: ShopStoreWarehouse) {
|
||||
const res = await request.put<ApiResult<unknown>>(
|
||||
'/shop/shop-store-warehouse',
|
||||
data
|
||||
);
|
||||
if (res.code === 0) {
|
||||
return res.message;
|
||||
}
|
||||
return Promise.reject(new Error(res.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除仓库
|
||||
*/
|
||||
export async function removeShopStoreWarehouse(id?: number) {
|
||||
const res = await request.del<ApiResult<unknown>>(
|
||||
'/shop/shop-store-warehouse/' + id
|
||||
);
|
||||
if (res.code === 0) {
|
||||
return res.message;
|
||||
}
|
||||
return Promise.reject(new Error(res.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量删除仓库
|
||||
*/
|
||||
export async function removeBatchShopStoreWarehouse(data: (number | undefined)[]) {
|
||||
const res = await request.del<ApiResult<unknown>>(
|
||||
'/shop/shop-store-warehouse/batch',
|
||||
{
|
||||
data
|
||||
}
|
||||
);
|
||||
if (res.code === 0) {
|
||||
return res.message;
|
||||
}
|
||||
return Promise.reject(new Error(res.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据id查询仓库
|
||||
*/
|
||||
export async function getShopStoreWarehouse(id: number) {
|
||||
const res = await request.get<ApiResult<ShopStoreWarehouse>>(
|
||||
'/shop/shop-store-warehouse/' + id
|
||||
);
|
||||
if (res.code === 0 && res.data) {
|
||||
return res.data;
|
||||
}
|
||||
return Promise.reject(new Error(res.message));
|
||||
}
|
||||
53
src/api/shop/shopStoreWarehouse/model/index.ts
Normal file
53
src/api/shop/shopStoreWarehouse/model/index.ts
Normal file
@@ -0,0 +1,53 @@
|
||||
import type { PageParam } from '@/api/index';
|
||||
|
||||
/**
|
||||
* 仓库
|
||||
*/
|
||||
export interface ShopStoreWarehouse {
|
||||
// 自增ID
|
||||
id?: number;
|
||||
// 仓库名称
|
||||
name?: string;
|
||||
// 唯一标识
|
||||
code?: string;
|
||||
// 类型 中心仓,区域仓,门店仓
|
||||
type?: string;
|
||||
// 仓库地址
|
||||
address?: string;
|
||||
// 真实姓名
|
||||
realName?: string;
|
||||
// 联系电话
|
||||
phone?: string;
|
||||
// 所在省份
|
||||
province?: string;
|
||||
// 所在城市
|
||||
city?: string;
|
||||
// 所在辖区
|
||||
region?: string;
|
||||
// 经纬度
|
||||
lngAndLat?: string;
|
||||
// 用户ID
|
||||
userId?: number;
|
||||
// 状态
|
||||
status?: number;
|
||||
// 备注
|
||||
comments?: string;
|
||||
// 排序号
|
||||
sortNumber?: number;
|
||||
// 是否删除
|
||||
isDelete?: number;
|
||||
// 租户id
|
||||
tenantId?: number;
|
||||
// 创建时间
|
||||
createTime?: string;
|
||||
// 修改时间
|
||||
updateTime?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 仓库搜索条件
|
||||
*/
|
||||
export interface ShopStoreWarehouseParam extends PageParam {
|
||||
id?: number;
|
||||
keywords?: string;
|
||||
}
|
||||
@@ -38,6 +38,8 @@ export interface ShopUserAddress {
|
||||
tenantId?: number;
|
||||
// 注册时间
|
||||
createTime?: string;
|
||||
// 更新时间
|
||||
updateTime?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -138,3 +138,18 @@ export async function getMyExpiredCoupons() {
|
||||
}
|
||||
return Promise.reject(new Error(res.message));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 领取优惠券
|
||||
*/
|
||||
export async function takeCoupon(params: { couponId: number; userId: number }) {
|
||||
const res = await request.post<ApiResult<unknown>>(
|
||||
'/shop/shop-user-coupon/take',
|
||||
params
|
||||
);
|
||||
if (res.code === 0) {
|
||||
return res.message;
|
||||
}
|
||||
return Promise.reject(new Error(res.message));
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import request from '@/utils/request';
|
||||
import type { ApiResult, PageResult } from '@/api';
|
||||
import type { ShopUserReferee, ShopUserRefereeParam } from './model';
|
||||
import type {ShopDealerReferee} from "@/api/shop/shopDealerReferee/model";
|
||||
|
||||
/**
|
||||
* 分页查询用户推荐关系表
|
||||
@@ -100,17 +99,3 @@ export async function getShopUserReferee(id: number) {
|
||||
}
|
||||
return Promise.reject(new Error(res.message));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 根据userId查询推荐关系
|
||||
*/
|
||||
export async function getShopUserRefereeByUserId(userId: number) {
|
||||
const res = await request.get<ApiResult<ShopDealerReferee>>(
|
||||
'/shop/shop-user-referee/getByUserId/' + userId
|
||||
);
|
||||
if (res.code === 0 && res.data) {
|
||||
return res.data;
|
||||
}
|
||||
return Promise.reject(new Error(res.message));
|
||||
}
|
||||
|
||||
@@ -22,8 +22,7 @@ export interface ChatMessage {
|
||||
withdraw?: number;
|
||||
// 文件信息
|
||||
fileInfo?: string;
|
||||
//
|
||||
toUserName?: string;
|
||||
toUserName?: any;
|
||||
formUserName?: string;
|
||||
// 批量发送
|
||||
toUserIds?: any[];
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import request from '@/utils/request';
|
||||
import Taro from '@tarojs/taro'
|
||||
import dayjs from 'dayjs';
|
||||
// @ts-ignore
|
||||
import crypto from 'crypto-js';
|
||||
import {Base64} from 'js-base64';
|
||||
import {FileRecord} from "@/api/system/file/model";
|
||||
@@ -22,7 +21,7 @@ export async function uploadOssByPath(filePath: string) {
|
||||
let stsExpired = Taro.getStorageSync('stsExpiredAt');
|
||||
if (!sts || (stsExpired && dayjs().isBefore(dayjs(stsExpired)))) {
|
||||
// @ts-ignore
|
||||
const {data: {data: {credentials}}} = await request.get(`https://server.websoft.top/api/oss/getSTSToken`)
|
||||
const {data: {data: {credentials}}} = await request.get(`https://gle-server.websoft.top/api/oss/getSTSToken`)
|
||||
Taro.setStorageSync('sts', credentials)
|
||||
Taro.setStorageSync('stsExpiredAt', credentials.expiration)
|
||||
sts = credentials
|
||||
@@ -50,7 +49,7 @@ export async function uploadOssByPath(filePath: string) {
|
||||
})
|
||||
}
|
||||
|
||||
const computeSignature = (accessKeySecret: string, canonicalString: string) => {
|
||||
const computeSignature = (accessKeySecret: string, canonicalString: string): string => {
|
||||
return crypto.enc.Base64.stringify(crypto.HmacSHA1(canonicalString, accessKeySecret));
|
||||
}
|
||||
|
||||
|
||||
@@ -30,3 +30,18 @@ export async function updateUserRole(data: UserRole) {
|
||||
}
|
||||
return Promise.reject(new Error(res.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增用户角色
|
||||
* 说明:部分后端实现为 POST 新增、PUT 修改;这里补齐 API 以便新用户无角色时可以创建默认角色。
|
||||
*/
|
||||
export async function addUserRole(data: UserRole) {
|
||||
const res = await request.post<ApiResult<unknown>>(
|
||||
SERVER_API_URL + '/system/user-role',
|
||||
data
|
||||
);
|
||||
if (res.code === 0) {
|
||||
return res.message;
|
||||
}
|
||||
return Promise.reject(new Error(res.message));
|
||||
}
|
||||
|
||||
@@ -53,7 +53,6 @@ export interface UserVerify {
|
||||
*/
|
||||
export interface UserVerifyParam extends PageParam {
|
||||
id?: number;
|
||||
userId?: number;
|
||||
status?: number;
|
||||
keywords?: string;
|
||||
}
|
||||
|
||||
@@ -43,6 +43,15 @@ export interface UserOrderStats {
|
||||
total: number
|
||||
}
|
||||
|
||||
// 用户卡片统计(个人中心头部:余额/积分/优惠券/水票)
|
||||
export interface UserCardStats {
|
||||
balance: string
|
||||
points: number
|
||||
coupons: number
|
||||
giftCards: number
|
||||
lastUpdateTime?: string
|
||||
}
|
||||
|
||||
// 用户完整数据
|
||||
export interface UserDashboard {
|
||||
balance: UserBalance
|
||||
@@ -108,6 +117,17 @@ export async function getUserOrderStats() {
|
||||
return Promise.reject(new Error(res.message))
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户卡片统计(一次性返回余额/积分/可用优惠券/未使用礼品卡数量)
|
||||
*/
|
||||
export async function getUserCardStats() {
|
||||
const res = await request.get<ApiResult<UserCardStats>>('/user/card/stats')
|
||||
if (res.code === 0 && res.data) {
|
||||
return res.data
|
||||
}
|
||||
return Promise.reject(new Error(res.message))
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户完整仪表板数据(一次性获取所有数据)
|
||||
*/
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
export default defineAppConfig({
|
||||
export default {
|
||||
pages: [
|
||||
'pages/index/index',
|
||||
'pages/cart/cart',
|
||||
@@ -27,10 +27,20 @@ export default defineAppConfig({
|
||||
"detail/index"
|
||||
]
|
||||
},
|
||||
{
|
||||
"root": "coupon",
|
||||
"pages": [
|
||||
"index"
|
||||
]
|
||||
},
|
||||
{
|
||||
"root": "user",
|
||||
"pages": [
|
||||
"order/order",
|
||||
"order/logistics/index",
|
||||
"order/evaluate/index",
|
||||
"order/refund/index",
|
||||
"order/progress/index",
|
||||
"company/company",
|
||||
"profile/profile",
|
||||
"setting/setting",
|
||||
@@ -43,11 +53,17 @@ export default defineAppConfig({
|
||||
"wallet/wallet",
|
||||
"coupon/index",
|
||||
"points/points",
|
||||
"gift/index",
|
||||
"gift/redeem",
|
||||
"gift/detail",
|
||||
"ticket/index",
|
||||
"ticket/use",
|
||||
"ticket/orders/index",
|
||||
// "gift/index",
|
||||
// "gift/redeem",
|
||||
// "gift/detail",
|
||||
// "gift/add",
|
||||
"store/verification",
|
||||
"store/orders/index",
|
||||
"theme/index",
|
||||
"poster/poster",
|
||||
"chat/conversation/index",
|
||||
"chat/message/index",
|
||||
"chat/message/add",
|
||||
@@ -60,38 +76,45 @@ export default defineAppConfig({
|
||||
"index",
|
||||
"apply/add",
|
||||
"withdraw/index",
|
||||
"withdraw/admin",
|
||||
"orders/index",
|
||||
"capital/index",
|
||||
"capital/detail",
|
||||
"capital/record",
|
||||
"team/index",
|
||||
"qrcode/index",
|
||||
"invite-stats/index",
|
||||
"info",
|
||||
"customer/index",
|
||||
"customer/add",
|
||||
"customer/trading",
|
||||
"wechat/index",
|
||||
"bank/index",
|
||||
"bank/add"
|
||||
"info"
|
||||
]
|
||||
},
|
||||
{
|
||||
"root": "shop",
|
||||
"pages": [
|
||||
'category/index',
|
||||
'orderDetail/index',
|
||||
'goodsDetail/index',
|
||||
'orderConfirm/index',
|
||||
'orderConfirmCart/index',
|
||||
'comments/index',
|
||||
'search/index']
|
||||
},
|
||||
{
|
||||
"root": "store",
|
||||
"pages": [
|
||||
"index",
|
||||
"orders/index"
|
||||
]
|
||||
},
|
||||
{
|
||||
"root": "rider",
|
||||
"pages": [
|
||||
"index",
|
||||
"orders/index",
|
||||
"ticket/verification/index"
|
||||
]
|
||||
},
|
||||
// {
|
||||
// "root": "shop",
|
||||
// "pages": ['category/index',
|
||||
// 'orderDetail/index',
|
||||
// 'goodsDetail/index',
|
||||
// 'orderConfirm/index',
|
||||
// 'orderConfirmCart/index',
|
||||
// 'search/index']
|
||||
// },
|
||||
{
|
||||
"root": "admin",
|
||||
"pages": [
|
||||
"index",
|
||||
"article/index",
|
||||
"userVerify/index"
|
||||
]
|
||||
}
|
||||
],
|
||||
@@ -135,6 +158,9 @@ export default defineAppConfig({
|
||||
permission: {
|
||||
"scope.userLocation": {
|
||||
"desc": "你的位置信息将用于小程序位置接口的效果展示"
|
||||
},
|
||||
"scope.writePhotosAlbum": {
|
||||
"desc": "用于保存小程序码到相册,方便分享给好友"
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
19
src/app.scss
19
src/app.scss
@@ -10,14 +10,14 @@ page{
|
||||
background-position: bottom;
|
||||
}
|
||||
|
||||
// 在全局样式文件中添加
|
||||
/* 在全局样式文件中添加 */
|
||||
button {
|
||||
&::after {
|
||||
border: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
// 去掉 Grid 组件的边框
|
||||
/* 去掉 Grid 组件的边框 */
|
||||
.no-border-grid {
|
||||
.nut-grid-item {
|
||||
border: none !important;
|
||||
@@ -38,7 +38,7 @@ button {
|
||||
}
|
||||
}
|
||||
|
||||
// 微信授权按钮的特殊样式
|
||||
/* 微信授权按钮的特殊样式 */
|
||||
button[open-type="getPhoneNumber"] {
|
||||
background: none !important;
|
||||
padding: 0 !important;
|
||||
@@ -87,8 +87,21 @@ button[open-type="chooseAvatar"] {
|
||||
justify-content: center;
|
||||
height: 80px;
|
||||
}
|
||||
.cart-buy-only{
|
||||
border-radius: 20px;
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
|
||||
image {
|
||||
margin: 0; /* 全局设置图片的 margin */
|
||||
}
|
||||
|
||||
/* 管理员面板功能项交互效果 */
|
||||
.admin-feature-item {
|
||||
transition: transform 0.15s ease-in-out;
|
||||
}
|
||||
|
||||
.admin-feature-item:active {
|
||||
transform: scale(0.95);
|
||||
}
|
||||
|
||||
28
src/app.ts
28
src/app.ts
@@ -7,9 +7,11 @@ import {loginByOpenId} from "@/api/layout";
|
||||
import {TenantId} from "@/config/app";
|
||||
import {saveStorageByLoginUser} from "@/utils/server";
|
||||
import {parseInviteParams, saveInviteParams, trackInviteSource, handleInviteRelation} from "@/utils/invite";
|
||||
import {configWebsiteField} from "@/api/cms/cmsWebsiteField";
|
||||
import { useConfig } from "@/hooks/useConfig"; // 引入新的自定义Hook
|
||||
|
||||
function App(props: { children: any; }) {
|
||||
const { refetch: handleTheme } = useConfig(); // 使用新的Hook
|
||||
|
||||
const reload = () => {
|
||||
Taro.login({
|
||||
success: (res) => {
|
||||
@@ -38,6 +40,8 @@ function App(props: { children: any; }) {
|
||||
};
|
||||
// 可以使用所有的 React Hooks
|
||||
useEffect(() => {
|
||||
// 设置主题 (现在由useConfig Hook处理)
|
||||
handleTheme()
|
||||
// Taro.getSetting:获取用户的当前设置。返回值中只会出现小程序已经向用户请求过的权限。
|
||||
Taro.getSetting({
|
||||
success: (res) => {
|
||||
@@ -53,13 +57,12 @@ function App(props: { children: any; }) {
|
||||
// 处理小程序启动参数中的邀请信息
|
||||
const options = Taro.getLaunchOptionsSync()
|
||||
handleLaunchOptions(options)
|
||||
handleTheme()
|
||||
})
|
||||
|
||||
// 处理启动参数
|
||||
const handleLaunchOptions = (options: any) => {
|
||||
try {
|
||||
console.log('=== 小程序启动参数处理开始 ===')
|
||||
console.log('=== 小程 序启动参数处理开始 ===')
|
||||
console.log('完整启动参数:', JSON.stringify(options, null, 2))
|
||||
|
||||
// 解析邀请参数
|
||||
@@ -75,11 +78,7 @@ function App(props: { children: any; }) {
|
||||
|
||||
// 显示邀请提示
|
||||
setTimeout(() => {
|
||||
Taro.showToast({
|
||||
title: `检测到邀请信息 ID:${inviteParams.inviter}`,
|
||||
icon: 'success',
|
||||
duration: 3000
|
||||
})
|
||||
console.log(`检测到邀请信息 ID:${inviteParams.inviter}`)
|
||||
}, 1000)
|
||||
|
||||
} else {
|
||||
@@ -92,19 +91,6 @@ function App(props: { children: any; }) {
|
||||
}
|
||||
}
|
||||
|
||||
const handleTheme = () => {
|
||||
configWebsiteField().then(data => {
|
||||
// 设置主题
|
||||
if(data.theme && !Taro.getStorageSync('user_theme')){
|
||||
Taro.setStorageSync('user_theme', data.theme)
|
||||
}
|
||||
// 自定义接口
|
||||
if(data.apiUrl && process.env.NODE_ENV !== 'development'){
|
||||
Taro.setStorageSync('ApiUrl', data.apiUrl)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 对应 onHide
|
||||
useDidHide(() => {
|
||||
})
|
||||
|
||||
BIN
src/assets/tabbar/category-active.png
Normal file
BIN
src/assets/tabbar/category-active.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 5.3 KiB |
BIN
src/assets/tabbar/category.png
Normal file
BIN
src/assets/tabbar/category.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.9 KiB |
BIN
src/assets/tabbar/logo.png
Normal file
BIN
src/assets/tabbar/logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 7.7 KiB |
@@ -1,25 +1,16 @@
|
||||
import {Image, Cell} from '@nutui/nutui-react-taro'
|
||||
import {View, Text} from '@tarojs/components'
|
||||
import Taro from '@tarojs/taro'
|
||||
import {CmsArticle} from "@/api/cms/cmsArticle/model";
|
||||
|
||||
const ArticleList = (props: any) => {
|
||||
|
||||
return (
|
||||
<>
|
||||
<View className={'px-3'}>
|
||||
{props.data.map((item: any, index: number) => {
|
||||
<div className={'px-3'}>
|
||||
{props.data.map((item: CmsArticle, index: number) => {
|
||||
return (
|
||||
<Cell
|
||||
title={
|
||||
<View>
|
||||
<View className="text-base font-medium mb-1">{item.title}</View>
|
||||
{item.comments && (
|
||||
<Text className="text-sm text-gray-500 leading-relaxed">
|
||||
{item.comments}
|
||||
</Text>
|
||||
)}
|
||||
</View>
|
||||
}
|
||||
title={item.title}
|
||||
extra={
|
||||
<Image src={item.image} mode={'aspectFit'} lazyLoad={false} width={100} height="100"/>
|
||||
}
|
||||
@@ -28,7 +19,7 @@ const ArticleList = (props: any) => {
|
||||
/>
|
||||
)
|
||||
})}
|
||||
</View>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import Taro from '@tarojs/taro'
|
||||
import {useShareAppMessage, useShareTimeline} from "@tarojs/taro"
|
||||
import {useShareAppMessage} from "@tarojs/taro"
|
||||
import {Loading} from '@nutui/nutui-react-taro'
|
||||
import {useEffect, useState} from "react"
|
||||
import {useRouter} from '@tarojs/taro'
|
||||
@@ -42,13 +42,6 @@ function Category() {
|
||||
})
|
||||
}, []);
|
||||
|
||||
useShareTimeline(() => {
|
||||
return {
|
||||
title: `${nav?.categoryName}_易赊宝`,
|
||||
path: `/shop/category/index?id=${categoryId}`
|
||||
};
|
||||
});
|
||||
|
||||
useShareAppMessage(() => {
|
||||
return {
|
||||
title: `${nav?.categoryName}_易赊宝`,
|
||||
|
||||
@@ -17,8 +17,8 @@ function Detail() {
|
||||
const reload = async () => {
|
||||
const item = await getCmsArticle(Number(params.id))
|
||||
|
||||
if (item) {
|
||||
item.content = wxParse(`${item.content}`)
|
||||
if (item && item.content) {
|
||||
item.content = wxParse(item.content)
|
||||
setItem(item)
|
||||
Taro.setNavigationBarTitle({
|
||||
title: `${item?.categoryName}`
|
||||
@@ -43,6 +43,10 @@ function Detail() {
|
||||
<div className={'p-4 font-bold text-lg'}>{item?.title}</div>
|
||||
<div className={'text-gray-400 text-sm px-4 '}>{item?.createTime}</div>
|
||||
<View className={'content p-4'}>
|
||||
{/*如果有视频就显示视频 视频沾满宽度*/}
|
||||
{item?.video && <View className={'w-full'}>
|
||||
<video src={item?.video} controls={true} width={'100%'}></video>
|
||||
</View>}
|
||||
<RichText nodes={item?.content}/>
|
||||
</View>
|
||||
<Line height={44}/>
|
||||
|
||||
@@ -5,6 +5,7 @@ import {getUserInfo} from "@/api/layout";
|
||||
import {useEffect, useState} from "react";
|
||||
import {getCmsArticle} from "@/api/cms/cmsArticle";
|
||||
import {CmsArticle} from "@/api/cms/cmsArticle/model";
|
||||
import { goToRegister } from '@/utils/auth'
|
||||
|
||||
function AddCartBar() {
|
||||
const { router } = getCurrentInstance();
|
||||
@@ -13,13 +14,8 @@ function AddCartBar() {
|
||||
const [IsLogin, setIsLogin] = useState<boolean>(false)
|
||||
const onPay = () => {
|
||||
if (!IsLogin) {
|
||||
Taro.showToast({title: `请先登录`, icon: 'error'})
|
||||
setTimeout(() => {
|
||||
Taro.switchTab(
|
||||
{
|
||||
url: '/pages/user/user',
|
||||
},
|
||||
)
|
||||
goToRegister({ redirect: '/pages/user/user' })
|
||||
}, 1000)
|
||||
return false;
|
||||
}
|
||||
@@ -30,7 +26,7 @@ function AddCartBar() {
|
||||
navTo('/bszx/pay/pay?id=' + id)
|
||||
}
|
||||
}
|
||||
const reload = (id) => {
|
||||
const reload = (id: number) => {
|
||||
getCmsArticle(id).then(data => {
|
||||
setArticle(data)
|
||||
})
|
||||
@@ -47,7 +43,7 @@ function AddCartBar() {
|
||||
useEffect(() => {
|
||||
const id = router?.params.id as number | undefined;
|
||||
setId(id)
|
||||
reload(id);
|
||||
reload(Number(id));
|
||||
}, []);
|
||||
|
||||
return (
|
||||
|
||||
139
src/components/AdminPanel.tsx
Normal file
139
src/components/AdminPanel.tsx
Normal file
@@ -0,0 +1,139 @@
|
||||
import React from 'react';
|
||||
import { View, Text } from '@tarojs/components';
|
||||
import { Button } from '@nutui/nutui-react-taro';
|
||||
import { Scan, Setting, User, Shop } from '@nutui/icons-react-taro';
|
||||
import navTo from '@/utils/common';
|
||||
|
||||
export interface AdminPanelProps {
|
||||
/** 是否显示面板 */
|
||||
visible: boolean;
|
||||
/** 关闭面板回调 */
|
||||
onClose?: () => void;
|
||||
/** 自定义样式类名 */
|
||||
className?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 管理员功能面板组件
|
||||
*/
|
||||
const AdminPanel: React.FC<AdminPanelProps> = ({
|
||||
visible,
|
||||
onClose,
|
||||
className = ''
|
||||
}) => {
|
||||
if (!visible) return null;
|
||||
|
||||
// 管理员功能列表
|
||||
const adminFeatures = [
|
||||
{
|
||||
id: 'unified-qr',
|
||||
title: '统一扫码',
|
||||
description: '扫码登录和核销一体化功能',
|
||||
icon: <Scan className="text-blue-500" size="24" />,
|
||||
color: 'bg-blue-50 border-blue-200',
|
||||
onClick: () => {
|
||||
navTo('/passport/unified-qr/index', true);
|
||||
onClose?.();
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'user-management',
|
||||
title: '用户管理',
|
||||
description: '管理系统用户信息',
|
||||
icon: <User className="text-purple-500" size="24" />,
|
||||
color: 'bg-purple-50 border-purple-200',
|
||||
onClick: () => {
|
||||
// TODO: 跳转到用户管理页面
|
||||
console.log('跳转到用户管理');
|
||||
onClose?.();
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'store-management',
|
||||
title: '门店管理',
|
||||
description: '管理门店信息和设置',
|
||||
icon: <Shop className="text-orange-500" size="24" />,
|
||||
color: 'bg-orange-50 border-orange-200',
|
||||
onClick: () => {
|
||||
// TODO: 跳转到门店管理页面
|
||||
console.log('跳转到门店管理');
|
||||
onClose?.();
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'system-settings',
|
||||
title: '系统设置',
|
||||
description: '系统配置和参数管理',
|
||||
icon: <Setting className="text-gray-500" size="24" />,
|
||||
color: 'bg-gray-50 border-gray-200',
|
||||
onClick: () => {
|
||||
// TODO: 跳转到系统设置页面
|
||||
console.log('跳转到系统设置');
|
||||
onClose?.();
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
return (
|
||||
<View className={`admin-panel ${className}`}>
|
||||
{/* 遮罩层 */}
|
||||
<View
|
||||
className="fixed inset-0 bg-black bg-opacity-50 z-40"
|
||||
onClick={onClose}
|
||||
/>
|
||||
|
||||
{/* 面板内容 */}
|
||||
<View className="fixed bottom-0 left-0 right-0 bg-white rounded-t-3xl z-50 overflow-hidden">
|
||||
{/* 面板头部 */}
|
||||
<View className="flex items-center justify-between p-4 border-b border-gray-100">
|
||||
<View className="flex items-center">
|
||||
<Setting className="text-blue-500 mr-2" size="20" />
|
||||
<Text className="text-lg font-bold text-gray-800">管理员面板</Text>
|
||||
</View>
|
||||
<Button
|
||||
size="small"
|
||||
type="default"
|
||||
onClick={onClose}
|
||||
className="text-gray-500"
|
||||
>
|
||||
关闭
|
||||
</Button>
|
||||
</View>
|
||||
|
||||
{/* 功能网格 */}
|
||||
<View className="p-4 pb-8">
|
||||
<View className="grid grid-cols-2 gap-3">
|
||||
{adminFeatures.map((feature) => (
|
||||
<View
|
||||
key={feature.id}
|
||||
className={`${feature.color} border rounded-xl p-4 admin-feature-item`}
|
||||
onClick={feature.onClick}
|
||||
>
|
||||
<View className="flex items-center mb-2">
|
||||
{feature.icon}
|
||||
<Text className="ml-2 font-medium text-gray-800">
|
||||
{feature.title}
|
||||
</Text>
|
||||
</View>
|
||||
<Text className="text-xs text-gray-600 leading-relaxed">
|
||||
{feature.description}
|
||||
</Text>
|
||||
</View>
|
||||
))}
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{/* 底部提示 */}
|
||||
<View className="px-4 pb-4">
|
||||
<View className="bg-yellow-50 border border-yellow-200 rounded-lg p-3">
|
||||
<Text className="text-xs text-yellow-700 text-center">
|
||||
💡 管理员功能仅对具有管理权限的用户开放
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
export default AdminPanel;
|
||||
@@ -1,8 +1,8 @@
|
||||
import React from 'react';
|
||||
import { Badge } from "@nutui/nutui-react-taro";
|
||||
import { Cart } from "@nutui/icons-react-taro";
|
||||
import Taro from '@tarojs/taro';
|
||||
import { useCart } from "@/hooks/useCart";
|
||||
import { switchTab } from '@/utils/navigation';
|
||||
|
||||
interface CartIconProps {
|
||||
style?: React.CSSProperties;
|
||||
@@ -26,13 +26,13 @@ const CartIcon: React.FC<CartIconProps> = ({
|
||||
onClick();
|
||||
} else {
|
||||
// 默认跳转到购物车页面
|
||||
Taro.switchTab({ url: '/pages/cart/cart' });
|
||||
switchTab('cart/cart');
|
||||
}
|
||||
};
|
||||
|
||||
if (showBadge) {
|
||||
return (
|
||||
<div
|
||||
<div
|
||||
className={className}
|
||||
style={style}
|
||||
onClick={handleClick}
|
||||
@@ -47,7 +47,7 @@ const CartIcon: React.FC<CartIconProps> = ({
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
<div
|
||||
className={className}
|
||||
style={style}
|
||||
onClick={handleClick}
|
||||
|
||||
@@ -2,115 +2,177 @@
|
||||
position: relative;
|
||||
display: flex;
|
||||
width: 100%;
|
||||
height: 120px;
|
||||
margin-bottom: 16px;
|
||||
border-radius: 12px;
|
||||
height: 160px;
|
||||
border-radius: 16px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
|
||||
background: #fff;
|
||||
transition: all 0.3s ease;
|
||||
border: 2px solid #f0f0f0;
|
||||
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
|
||||
/* 更精美的阴影效果 */
|
||||
/*box-shadow:
|
||||
0 4px 20px rgba(0, 0, 0, 0.08),
|
||||
0 1px 3px rgba(0, 0, 0, 0.1);*/
|
||||
|
||||
/* 边框光晕效果 */
|
||||
/*&::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
border-radius: 16px;
|
||||
padding: 1px;
|
||||
background: linear-gradient(135deg, rgba(255, 255, 255, 0.3), rgba(255, 255, 255, 0.1));
|
||||
mask-composite: exclude;
|
||||
pointer-events: none;
|
||||
}*/
|
||||
|
||||
&:active {
|
||||
transform: scale(0.98);
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.12);
|
||||
transform: scale(0.98) translateY(1px);
|
||||
box-shadow:
|
||||
0 2px 12px rgba(0, 0, 0, 0.12),
|
||||
0 1px 2px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
&.disabled {
|
||||
opacity: 0.6;
|
||||
/* filter: grayscale(0.3); 小程序不支持filter属性 */
|
||||
}
|
||||
|
||||
.coupon-left {
|
||||
flex-shrink: 0;
|
||||
width: 110px;
|
||||
width: 140px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #fff;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
|
||||
/* 添加光泽效果 */
|
||||
&::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: -50%;
|
||||
left: -50%;
|
||||
width: 200%;
|
||||
height: 200%;
|
||||
background: linear-gradient(45deg, transparent 30%, rgba(255, 255, 255, 0.1) 50%, transparent 70%);
|
||||
transform: rotate(45deg);
|
||||
animation: shimmer 3s infinite;
|
||||
}
|
||||
|
||||
&.theme-red {
|
||||
background: linear-gradient(135deg, #ff6b6b 0%, #ee5a52 100%);
|
||||
background: linear-gradient(135deg, #ff6b6b 0%, #ee5a52 50%, #e53e3e 100%);
|
||||
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
|
||||
&.theme-orange {
|
||||
background: linear-gradient(135deg, #ffa726 0%, #ff9800 100%);
|
||||
background: linear-gradient(135deg, #ffa726 0%, #ff9800 50%, #f57c00 100%);
|
||||
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
|
||||
&.theme-blue {
|
||||
background: linear-gradient(135deg, #42a5f5 0%, #2196f3 100%);
|
||||
background: linear-gradient(135deg, #42a5f5 0%, #2196f3 50%, #1976d2 100%);
|
||||
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
|
||||
&.theme-purple {
|
||||
background: linear-gradient(135deg, #ab47bc 0%, #9c27b0 100%);
|
||||
background: linear-gradient(135deg, #ab47bc 0%, #9c27b0 50%, #7b1fa2 100%);
|
||||
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
|
||||
&.theme-green {
|
||||
background: linear-gradient(135deg, #66bb6a 0%, #4caf50 100%);
|
||||
background: linear-gradient(135deg, #66bb6a 0%, #4caf50 50%, #388e3c 100%);
|
||||
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
|
||||
.amount-wrapper {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
margin-bottom: 8px;
|
||||
margin-bottom: 12px;
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
|
||||
.currency {
|
||||
font-size: 28px;
|
||||
font-weight: 600;
|
||||
margin-right: 2px;
|
||||
font-size: 30px;
|
||||
font-weight: 700;
|
||||
margin-right: 3px;
|
||||
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.amount {
|
||||
font-size: 36px;
|
||||
font-weight: bold;
|
||||
font-size: 42px;
|
||||
font-weight: 800;
|
||||
line-height: 1;
|
||||
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
letter-spacing: -1px;
|
||||
}
|
||||
}
|
||||
|
||||
.condition {
|
||||
font-size: 22px;
|
||||
opacity: 0.9;
|
||||
font-size: 24px;
|
||||
opacity: 0.95;
|
||||
text-align: center;
|
||||
line-height: 1.2;
|
||||
font-weight: 500;
|
||||
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
}
|
||||
}
|
||||
|
||||
.coupon-divider {
|
||||
flex-shrink: 0;
|
||||
width: 2px;
|
||||
width: 3px;
|
||||
position: relative;
|
||||
background: #f5f5f5;
|
||||
background: linear-gradient(180deg, #f8fafc 0%, #e2e8f0 50%, #f8fafc 100%);
|
||||
|
||||
.divider-line {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
top: 24px;
|
||||
bottom: 24px;
|
||||
left: 50%;
|
||||
width: 1px;
|
||||
transform: translateX(-50%);
|
||||
background: repeating-linear-gradient(
|
||||
to bottom,
|
||||
transparent 0px,
|
||||
transparent 4px,
|
||||
#ddd 4px,
|
||||
#ddd 8px
|
||||
#cbd5e1 0px,
|
||||
#cbd5e1 6px,
|
||||
transparent 6px,
|
||||
transparent 12px
|
||||
);
|
||||
}
|
||||
|
||||
.divider-circle-top {
|
||||
position: absolute;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
background: #f5f5f5;
|
||||
border-radius: 50%;
|
||||
top: -8px;
|
||||
left: -7px;
|
||||
top: -10px;
|
||||
left: -8.5px;
|
||||
border: 2px solid #e2e8f0;
|
||||
box-shadow:
|
||||
0 2px 4px rgba(0, 0, 0, 0.05),
|
||||
inset 0 1px 0 rgba(255, 255, 255, 0.8);
|
||||
}
|
||||
|
||||
.divider-circle-bottom {
|
||||
position: absolute;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
background: #f5f5f5;
|
||||
border-radius: 50%;
|
||||
bottom: -8px;
|
||||
left: -7px;
|
||||
bottom: -10px;
|
||||
left: -8.5px;
|
||||
border: 2px solid #e2e8f0;
|
||||
box-shadow:
|
||||
0 2px 4px rgba(0, 0, 0, 0.05),
|
||||
inset 0 1px 0 rgba(255, 255, 255, 0.8);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -120,7 +182,8 @@
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 16px;
|
||||
padding: 20px 18px;
|
||||
background: linear-gradient(135deg, #ffffff 0%, #fafbfc 100%);
|
||||
|
||||
.coupon-info {
|
||||
flex: 1;
|
||||
@@ -129,17 +192,28 @@
|
||||
justify-content: center;
|
||||
|
||||
.coupon-title {
|
||||
font-size: 32px;
|
||||
font-weight: 600;
|
||||
color: #1f2937;
|
||||
margin-bottom: 6px;
|
||||
font-size: 34px;
|
||||
font-weight: 700;
|
||||
color: #1a202c;
|
||||
margin-bottom: 12px;
|
||||
line-height: 1.3;
|
||||
letter-spacing: -0.5px;
|
||||
|
||||
/* 文字渐变效果在小程序中不支持,使用纯色替代 */
|
||||
}
|
||||
|
||||
.coupon-validity {
|
||||
font-size: 26px;
|
||||
color: #6b7280;
|
||||
color: #718096;
|
||||
line-height: 1.2;
|
||||
font-weight: 500;
|
||||
|
||||
/* 添加图标前缀 */
|
||||
&::before {
|
||||
content: '⏰';
|
||||
margin-right: 6px;
|
||||
font-size: 24px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -150,45 +224,71 @@
|
||||
flex-shrink: 0;
|
||||
|
||||
.coupon-btn {
|
||||
min-width: 120px;
|
||||
height: 60px;
|
||||
border-radius: 30px;
|
||||
font-size: 26px;
|
||||
min-width: 140px;
|
||||
height: 72px;
|
||||
border-radius: 34px;
|
||||
font-size: 28px;
|
||||
border: none;
|
||||
color: #fff;
|
||||
font-weight: 600;
|
||||
transition: all 0.2s ease;
|
||||
font-weight: 700;
|
||||
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
|
||||
/* 添加按钮光泽效果 */
|
||||
&::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: -100%;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent);
|
||||
transition: left 0.5s;
|
||||
}
|
||||
|
||||
&:hover::before {
|
||||
left: 100%;
|
||||
}
|
||||
|
||||
&:active {
|
||||
transform: scale(0.95);
|
||||
transform: scale(0.96) translateY(1px);
|
||||
}
|
||||
|
||||
&.theme-red {
|
||||
background: linear-gradient(135deg, #ff6b6b 0%, #ee5a52 100%);
|
||||
background: linear-gradient(135deg, #ff6b6b 0%, #ee5a52 50%, #e53e3e 100%);
|
||||
box-shadow: 0 4px 12px rgba(255, 107, 107, 0.3);
|
||||
}
|
||||
|
||||
&.theme-orange {
|
||||
background: linear-gradient(135deg, #ffa726 0%, #ff9800 100%);
|
||||
background: linear-gradient(135deg, #ffa726 0%, #ff9800 50%, #f57c00 100%);
|
||||
box-shadow: 0 4px 12px rgba(255, 167, 38, 0.3);
|
||||
}
|
||||
|
||||
&.theme-blue {
|
||||
background: linear-gradient(135deg, #42a5f5 0%, #2196f3 100%);
|
||||
background: linear-gradient(135deg, #42a5f5 0%, #2196f3 50%, #1976d2 100%);
|
||||
box-shadow: 0 4px 12px rgba(66, 165, 245, 0.3);
|
||||
}
|
||||
|
||||
&.theme-purple {
|
||||
background: linear-gradient(135deg, #ab47bc 0%, #9c27b0 100%);
|
||||
background: linear-gradient(135deg, #ab47bc 0%, #9c27b0 50%, #7b1fa2 100%);
|
||||
box-shadow: 0 4px 12px rgba(171, 71, 188, 0.3);
|
||||
}
|
||||
|
||||
&.theme-green {
|
||||
background: linear-gradient(135deg, #66bb6a 0%, #4caf50 100%);
|
||||
background: linear-gradient(135deg, #66bb6a 0%, #4caf50 50%, #388e3c 100%);
|
||||
box-shadow: 0 4px 12px rgba(102, 187, 106, 0.3);
|
||||
}
|
||||
}
|
||||
|
||||
.status-text {
|
||||
font-size: 26px;
|
||||
color: #9ca3af;
|
||||
padding: 8px 12px;
|
||||
font-weight: 500;
|
||||
font-size: 28px;
|
||||
color: #a0aec0;
|
||||
padding: 12px 16px;
|
||||
font-weight: 600;
|
||||
background: linear-gradient(135deg, #f7fafc 0%, #edf2f7 100%);
|
||||
border-radius: 20px;
|
||||
border: 1px solid #e2e8f0;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -199,19 +299,84 @@
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: rgba(0, 0, 0, 0.1);
|
||||
background: rgba(0, 0, 0, 0.15);
|
||||
border-radius: 16px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 10;
|
||||
/* backdrop-filter: blur(2px); 小程序不支持backdrop-filter属性 */
|
||||
|
||||
.status-badge {
|
||||
background: rgba(0, 0, 0, 0.6);
|
||||
background: rgba(0, 0, 0, 0.7);
|
||||
color: #fff;
|
||||
padding: 4px 12px;
|
||||
border-radius: 12px;
|
||||
font-size: 28px;
|
||||
font-weight: 500;
|
||||
padding: 8px 16px;
|
||||
border-radius: 16px;
|
||||
font-size: 30px;
|
||||
font-weight: 700;
|
||||
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* 动画效果 */
|
||||
@keyframes shimmer {
|
||||
0% {
|
||||
transform: translateX(-100%) translateY(-100%) rotate(45deg);
|
||||
}
|
||||
100% {
|
||||
transform: translateX(100%) translateY(100%) rotate(45deg);
|
||||
}
|
||||
}
|
||||
|
||||
/* 响应式优化 */
|
||||
@media (max-width: 768px) {
|
||||
.coupon-card {
|
||||
height: 150px;
|
||||
|
||||
.coupon-left {
|
||||
width: 130px;
|
||||
|
||||
.amount-wrapper {
|
||||
.currency {
|
||||
font-size: 26px;
|
||||
}
|
||||
|
||||
.amount {
|
||||
font-size: 38px;
|
||||
}
|
||||
}
|
||||
|
||||
.condition {
|
||||
font-size: 22px;
|
||||
}
|
||||
}
|
||||
|
||||
.coupon-right {
|
||||
padding: 24px 20px;
|
||||
|
||||
.coupon-info {
|
||||
.coupon-title {
|
||||
font-size: 30px;
|
||||
}
|
||||
|
||||
.coupon-validity {
|
||||
font-size: 24px;
|
||||
}
|
||||
}
|
||||
|
||||
.coupon-actions {
|
||||
.coupon-btn {
|
||||
min-width: 130px;
|
||||
height: 64px;
|
||||
font-size: 26px;
|
||||
}
|
||||
|
||||
.status-text {
|
||||
font-size: 26px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
67
src/components/CouponList.scss
Normal file
67
src/components/CouponList.scss
Normal file
@@ -0,0 +1,67 @@
|
||||
.coupon-list-container {
|
||||
padding: 0 20px;
|
||||
background: #f5f5f5;
|
||||
min-height: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.coupon-list-title {
|
||||
font-size: 32px;
|
||||
font-weight: 600;
|
||||
color: #1f2937;
|
||||
margin-bottom: 32px;
|
||||
padding-top: 24px;
|
||||
}
|
||||
|
||||
.coupon-list-empty {
|
||||
text-align: center;
|
||||
padding: 120px 20px;
|
||||
color: #9ca3af;
|
||||
font-size: 28px;
|
||||
}
|
||||
|
||||
.coupon-list-content {
|
||||
padding-bottom: 40px;
|
||||
}
|
||||
|
||||
.coupon-list-item {
|
||||
margin-bottom: 32px;
|
||||
transition: all 0.3s ease;
|
||||
|
||||
&:hover {
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
&:active {
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
}
|
||||
|
||||
/* 水平滚动布局样式 */
|
||||
.coupon-horizontal-container {
|
||||
.coupon-horizontal-title {
|
||||
font-size: 32px;
|
||||
font-weight: 600;
|
||||
color: #1f2937;
|
||||
margin-bottom: 24px;
|
||||
padding-left: 16px;
|
||||
}
|
||||
|
||||
.coupon-horizontal-scroll {
|
||||
padding: 0 16px;
|
||||
|
||||
.coupon-horizontal-item {
|
||||
flex-shrink: 0;
|
||||
width: 240px;
|
||||
margin-right: 16px;
|
||||
|
||||
&:last-child {
|
||||
margin-right: 16px; // 保持右边距
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
import React from 'react'
|
||||
import { View, ScrollView } from '@tarojs/components'
|
||||
import CouponCard, { CouponCardProps } from './CouponCard'
|
||||
import './CouponList.scss'
|
||||
|
||||
export interface CouponListProps {
|
||||
/** 优惠券列表数据 */
|
||||
@@ -32,26 +33,29 @@ const CouponList: React.FC<CouponListProps> = ({
|
||||
// 垂直布局
|
||||
if (layout === 'vertical') {
|
||||
return (
|
||||
<View className="p-4">
|
||||
<View className="coupon-list-container">
|
||||
{title && (
|
||||
<View className="font-semibold text-gray-800 mb-4">{title}</View>
|
||||
<View className="coupon-list-title">{title}</View>
|
||||
)}
|
||||
|
||||
{coupons.length === 0 ? (
|
||||
showEmpty && (
|
||||
<View className="text-center py-10 px-5 text-gray-500">
|
||||
<View className="coupon-list-empty">
|
||||
{emptyText}
|
||||
</View>
|
||||
)
|
||||
) : (
|
||||
coupons.map((coupon, index) => (
|
||||
<View
|
||||
key={index}
|
||||
onClick={() => handleCouponClick(coupon, index)}
|
||||
>
|
||||
<CouponCard {...coupon} />
|
||||
</View>
|
||||
))
|
||||
<View className="coupon-list-content">
|
||||
{coupons.map((coupon, index) => (
|
||||
<View
|
||||
key={index}
|
||||
className="coupon-list-item"
|
||||
onClick={() => handleCouponClick(coupon, index)}
|
||||
>
|
||||
<CouponCard {...coupon} />
|
||||
</View>
|
||||
))}
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
)
|
||||
@@ -59,29 +63,29 @@ const CouponList: React.FC<CouponListProps> = ({
|
||||
|
||||
// 水平滚动布局
|
||||
return (
|
||||
<View>
|
||||
<View className="coupon-horizontal-container">
|
||||
{title && (
|
||||
<View className="font-semibold text-gray-800 mb-4 pl-4">
|
||||
<View className="coupon-horizontal-title">
|
||||
{title}
|
||||
</View>
|
||||
)}
|
||||
|
||||
{coupons.length === 0 ? (
|
||||
showEmpty && (
|
||||
<View className="text-center py-10 px-5 text-gray-500">
|
||||
<View className="coupon-list-empty">
|
||||
{emptyText}
|
||||
</View>
|
||||
)
|
||||
) : (
|
||||
<ScrollView
|
||||
scrollX
|
||||
className="flex p-4 gap-2 overflow-x-auto"
|
||||
className="coupon-horizontal-scroll flex overflow-x-auto"
|
||||
showScrollbar={false}
|
||||
>
|
||||
{coupons.map((coupon, index) => (
|
||||
<View
|
||||
key={index}
|
||||
className="flex-shrink-0 w-60 mb-0"
|
||||
className="coupon-horizontal-item"
|
||||
onClick={() => handleCouponClick(coupon, index)}
|
||||
>
|
||||
<CouponCard {...coupon} />
|
||||
|
||||
@@ -24,7 +24,7 @@ export interface GiftCardProps {
|
||||
faceValue?: string
|
||||
/** 商品原价 */
|
||||
originalPrice?: string
|
||||
/** 礼品卡类型:10-实物礼品卡 20-虚拟礼品卡 30-服务礼品卡 */
|
||||
/** 礼品卡类型:10-礼品劵 20-虚拟礼品卡 30-服务礼品卡 */
|
||||
type?: number
|
||||
/** 状态:0-未使用 1-已使用 2-失效 */
|
||||
status?: number
|
||||
@@ -112,10 +112,10 @@ const GiftCard: React.FC<GiftCardProps> = ({
|
||||
// 获取礼品卡类型文本
|
||||
const getTypeText = () => {
|
||||
switch (type) {
|
||||
case 10: return '实物礼品卡'
|
||||
case 10: return '礼品劵'
|
||||
case 20: return '虚拟礼品卡'
|
||||
case 30: return '服务礼品卡'
|
||||
default: return '礼品卡'
|
||||
default: return '水票'
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -51,7 +51,7 @@ const GiftCardGuide: React.FC<GiftCardGuideProps> = ({
|
||||
title: '礼品卡类型说明',
|
||||
icon: <Gift size="24" className="text-purple-500" />,
|
||||
content: [
|
||||
'🎁 实物礼品卡:需到指定地址领取商品',
|
||||
'🎁 礼品劵:需到指定地址领取商品',
|
||||
'💻 虚拟礼品卡:自动发放到账户余额',
|
||||
'🛎️ 服务礼品卡:联系客服预约服务',
|
||||
'⏰ 注意查看有效期,过期无法使用'
|
||||
|
||||
@@ -28,10 +28,10 @@ const GiftCardShare: React.FC<GiftCardShareProps> = ({
|
||||
// 获取礼品卡类型文本
|
||||
const getTypeText = () => {
|
||||
switch (giftCard.type) {
|
||||
case 10: return '实物礼品卡'
|
||||
case 10: return '礼品劵'
|
||||
case 20: return '虚拟礼品卡'
|
||||
case 30: return '服务礼品卡'
|
||||
default: return '礼品卡'
|
||||
default: return '水票'
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ import {NavBar} from '@nutui/nutui-react-taro'
|
||||
import {ArrowLeft} from '@nutui/icons-react-taro'
|
||||
import Taro from '@tarojs/taro'
|
||||
|
||||
function Header(props) {
|
||||
function Header(props: any) {
|
||||
return (
|
||||
<>
|
||||
<NavBar
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# PaymentCountdown 支付倒计时组件
|
||||
|
||||
基于订单创建时间的支付倒计时组件,支持静态显示和实时更新两种模式。
|
||||
基于订单过期时间(`expirationTime`)的支付倒计时组件,支持静态显示和实时更新两种模式。
|
||||
|
||||
## 功能特性
|
||||
|
||||
@@ -19,7 +19,7 @@ import PaymentCountdown from '@/components/PaymentCountdown';
|
||||
|
||||
// 订单列表页 - 静态显示
|
||||
<PaymentCountdown
|
||||
createTime={order.createTime}
|
||||
expirationTime={order.expirationTime}
|
||||
payStatus={order.payStatus}
|
||||
realTime={false}
|
||||
mode="badge"
|
||||
@@ -27,7 +27,7 @@ import PaymentCountdown from '@/components/PaymentCountdown';
|
||||
|
||||
// 订单详情页 - 实时更新
|
||||
<PaymentCountdown
|
||||
createTime={order.createTime}
|
||||
expirationTime={order.expirationTime}
|
||||
payStatus={order.payStatus}
|
||||
realTime={true}
|
||||
showSeconds={true}
|
||||
@@ -43,7 +43,7 @@ import PaymentCountdown from '@/components/PaymentCountdown';
|
||||
```tsx
|
||||
// 自定义超时时间(12小时)
|
||||
<PaymentCountdown
|
||||
createTime={order.createTime}
|
||||
expirationTime={order.expirationTime}
|
||||
payStatus={order.payStatus}
|
||||
realTime={true}
|
||||
timeoutHours={12}
|
||||
@@ -55,7 +55,7 @@ import PaymentCountdown from '@/components/PaymentCountdown';
|
||||
|
||||
// 纯文本模式
|
||||
<PaymentCountdown
|
||||
createTime={order.createTime}
|
||||
expirationTime={order.expirationTime}
|
||||
payStatus={order.payStatus}
|
||||
realTime={false}
|
||||
mode="text"
|
||||
@@ -67,6 +67,7 @@ import PaymentCountdown from '@/components/PaymentCountdown';
|
||||
| 参数 | 类型 | 默认值 | 说明 |
|
||||
|------|------|--------|------|
|
||||
| createTime | string | - | 订单创建时间 |
|
||||
| expirationTime | string | - | 订单过期时间(推荐) |
|
||||
| payStatus | boolean | false | 支付状态 |
|
||||
| realTime | boolean | false | 是否实时更新 |
|
||||
| timeoutHours | number | 24 | 超时小时数 |
|
||||
@@ -102,12 +103,13 @@ import PaymentCountdown from '@/components/PaymentCountdown';
|
||||
import { usePaymentCountdown, formatCountdownText } from '@/hooks/usePaymentCountdown';
|
||||
|
||||
const MyComponent = ({ order }) => {
|
||||
const timeLeft = usePaymentCountdown(
|
||||
order.createTime,
|
||||
order.payStatus,
|
||||
true, // 实时更新
|
||||
24 // 24小时超时
|
||||
);
|
||||
const timeLeft = usePaymentCountdown({
|
||||
expirationTime: order.expirationTime,
|
||||
createTime: order.createTime, // expirationTime 缺失时回退
|
||||
payStatus: order.payStatus,
|
||||
realTime: true,
|
||||
timeoutHours: 24
|
||||
});
|
||||
|
||||
const countdownText = formatCountdownText(timeLeft, true);
|
||||
|
||||
|
||||
@@ -133,7 +133,6 @@
|
||||
margin: 8px 0;
|
||||
|
||||
.countdown-text {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,8 @@ import './PaymentCountdown.scss';
|
||||
export interface PaymentCountdownProps {
|
||||
/** 订单创建时间 */
|
||||
createTime?: string;
|
||||
/** 订单过期时间(推荐直接传后端返回的 expirationTime) */
|
||||
expirationTime?: string;
|
||||
/** 支付状态 */
|
||||
payStatus?: boolean;
|
||||
/** 是否实时更新(详情页用true,列表页用false) */
|
||||
@@ -29,18 +31,25 @@ export interface PaymentCountdownProps {
|
||||
|
||||
const PaymentCountdown: React.FC<PaymentCountdownProps> = ({
|
||||
createTime,
|
||||
expirationTime,
|
||||
payStatus = false,
|
||||
realTime = false,
|
||||
timeoutHours = 1,
|
||||
timeoutHours = 24,
|
||||
showSeconds = false,
|
||||
className = '',
|
||||
onExpired,
|
||||
mode = 'badge'
|
||||
}) => {
|
||||
const timeLeft = usePaymentCountdown(createTime, payStatus, realTime, timeoutHours);
|
||||
const timeLeft = usePaymentCountdown({
|
||||
createTime,
|
||||
expirationTime,
|
||||
payStatus,
|
||||
realTime,
|
||||
timeoutHours
|
||||
});
|
||||
|
||||
// 如果已支付或没有创建时间,不显示倒计时
|
||||
if (payStatus || !createTime) {
|
||||
// 如果已支付或没有可计算的截止时间,不显示倒计时
|
||||
if (payStatus || (!expirationTime && !createTime)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
89
src/components/QRLoginButton.tsx
Normal file
89
src/components/QRLoginButton.tsx
Normal file
@@ -0,0 +1,89 @@
|
||||
import React from 'react';
|
||||
import { Button } from '@nutui/nutui-react-taro';
|
||||
import {View} from '@tarojs/components'
|
||||
import { Scan } from '@nutui/icons-react-taro';
|
||||
import Taro from '@tarojs/taro';
|
||||
import { useQRLogin } from '@/hooks/useQRLogin';
|
||||
|
||||
export interface QRLoginButtonProps {
|
||||
/** 按钮类型 */
|
||||
type?: 'primary' | 'success' | 'warning' | 'danger' | 'default';
|
||||
/** 按钮大小 */
|
||||
size?: 'large' | 'normal' | 'small';
|
||||
/** 按钮文本 */
|
||||
text?: string;
|
||||
/** 是否显示图标 */
|
||||
showIcon?: boolean;
|
||||
/** 自定义样式类名 */
|
||||
className?: string;
|
||||
/** 点击成功回调 */
|
||||
onSuccess?: (result: any) => void;
|
||||
/** 点击失败回调 */
|
||||
onError?: (error: string) => void;
|
||||
/** 是否使用页面模式(跳转到专门页面) */
|
||||
usePageMode?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* 扫码登录按钮组件
|
||||
*/
|
||||
const QRLoginButton: React.FC<QRLoginButtonProps> = ({
|
||||
type = 'default',
|
||||
size = 'small',
|
||||
text = '扫码登录',
|
||||
showIcon = true,
|
||||
onSuccess,
|
||||
onError,
|
||||
usePageMode = false
|
||||
}) => {
|
||||
const { startScan, isLoading, canScan } = useQRLogin();
|
||||
|
||||
// 处理点击事件
|
||||
const handleClick = async () => {
|
||||
console.log('处理点击事件handleClick', usePageMode)
|
||||
if (usePageMode) {
|
||||
// 跳转到专门的扫码登录页面
|
||||
if (canScan()) {
|
||||
Taro.navigateTo({
|
||||
url: '/passport/qr-login/index'
|
||||
});
|
||||
} else {
|
||||
Taro.showToast({
|
||||
title: '请先登录小程序',
|
||||
icon: 'error'
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// 直接执行扫码登录
|
||||
try {
|
||||
await startScan();
|
||||
// 成功回调会在Hook内部处理
|
||||
} catch (error: any) {
|
||||
onError?.(error.message || '扫码登录失败');
|
||||
}
|
||||
};
|
||||
|
||||
console.log(onSuccess,'onSuccess')
|
||||
const disabled = !canScan() || isLoading;
|
||||
|
||||
return (
|
||||
<Button
|
||||
type={type}
|
||||
size={size}
|
||||
loading={isLoading}
|
||||
disabled={disabled}
|
||||
onClick={handleClick}
|
||||
>
|
||||
<View className="flex items-center justify-center">
|
||||
{showIcon && !isLoading && (
|
||||
<Scan className="mr-1" />
|
||||
)}
|
||||
{isLoading ? '扫码中...' : (disabled && !canScan() ? '请先登录' : text)}
|
||||
</View>
|
||||
</Button>
|
||||
);
|
||||
};
|
||||
|
||||
export default QRLoginButton;
|
||||
182
src/components/QRLoginScanner.tsx
Normal file
182
src/components/QRLoginScanner.tsx
Normal file
@@ -0,0 +1,182 @@
|
||||
import React from 'react';
|
||||
import { View, Text } from '@tarojs/components';
|
||||
import { Button, Loading } from '@nutui/nutui-react-taro';
|
||||
import { Scan, Success, Failure } from '@nutui/icons-react-taro';
|
||||
import { useQRLogin, ScanLoginState } from '@/hooks/useQRLogin';
|
||||
|
||||
export interface QRLoginScannerProps {
|
||||
/** 扫码成功回调 */
|
||||
onSuccess?: (result: any) => void;
|
||||
/** 扫码失败回调 */
|
||||
onError?: (error: string) => void;
|
||||
/** 自定义样式类名 */
|
||||
className?: string;
|
||||
/** 按钮文本 */
|
||||
buttonText?: string;
|
||||
/** 是否显示状态信息 */
|
||||
showStatus?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* 扫码登录组件
|
||||
*/
|
||||
const QRLoginScanner: React.FC<QRLoginScannerProps> = ({
|
||||
onSuccess,
|
||||
onError,
|
||||
className = '',
|
||||
buttonText = '扫码登录',
|
||||
showStatus = true
|
||||
}) => {
|
||||
const {
|
||||
state,
|
||||
error,
|
||||
result,
|
||||
isLoading,
|
||||
startScan,
|
||||
cancel,
|
||||
reset,
|
||||
canScan
|
||||
} = useQRLogin();
|
||||
|
||||
// 处理扫码成功
|
||||
React.useEffect(() => {
|
||||
if (state === ScanLoginState.SUCCESS && result) {
|
||||
onSuccess?.(result);
|
||||
}
|
||||
}, [state, result, onSuccess]);
|
||||
|
||||
// 处理扫码失败
|
||||
React.useEffect(() => {
|
||||
if (state === ScanLoginState.ERROR && error) {
|
||||
onError?.(error);
|
||||
}
|
||||
}, [state, error, onError]);
|
||||
|
||||
// 获取状态显示内容
|
||||
const getStatusContent = () => {
|
||||
switch (state) {
|
||||
case ScanLoginState.SCANNING:
|
||||
return (
|
||||
<View className="flex items-center justify-center text-blue-500">
|
||||
<Loading className="mr-2" />
|
||||
<Text>请扫描登录二维码...</Text>
|
||||
</View>
|
||||
);
|
||||
|
||||
case ScanLoginState.CONFIRMING:
|
||||
return (
|
||||
<View className="flex items-center justify-center text-orange-500">
|
||||
<Loading className="mr-2" />
|
||||
<Text>正在确认登录...</Text>
|
||||
</View>
|
||||
);
|
||||
|
||||
case ScanLoginState.SUCCESS:
|
||||
return (
|
||||
<View className="flex items-center justify-center text-green-500">
|
||||
<Success className="mr-2" />
|
||||
<Text>登录确认成功!</Text>
|
||||
</View>
|
||||
);
|
||||
|
||||
case ScanLoginState.ERROR:
|
||||
return (
|
||||
<View className="flex items-center justify-center text-red-500">
|
||||
<Failure className="mr-2" />
|
||||
<Text>{error || '扫码登录失败'}</Text>
|
||||
</View>
|
||||
);
|
||||
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
// 获取按钮状态
|
||||
const getButtonProps = () => {
|
||||
const disabled = !canScan() || isLoading;
|
||||
|
||||
switch (state) {
|
||||
case ScanLoginState.SCANNING:
|
||||
case ScanLoginState.CONFIRMING:
|
||||
return {
|
||||
loading: true,
|
||||
disabled: true,
|
||||
text: state === ScanLoginState.SCANNING ? '扫码中...' : '确认中...',
|
||||
onClick: cancel
|
||||
};
|
||||
|
||||
case ScanLoginState.SUCCESS:
|
||||
return {
|
||||
loading: false,
|
||||
disabled: false,
|
||||
text: '重新扫码',
|
||||
onClick: reset
|
||||
};
|
||||
|
||||
case ScanLoginState.ERROR:
|
||||
return {
|
||||
loading: false,
|
||||
disabled: false,
|
||||
text: '重试',
|
||||
onClick: startScan
|
||||
};
|
||||
|
||||
default:
|
||||
return {
|
||||
loading: false,
|
||||
disabled,
|
||||
text: disabled ? '请先登录' : buttonText,
|
||||
onClick: startScan
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
const buttonProps = getButtonProps();
|
||||
|
||||
return (
|
||||
<View className={`qr-login-scanner ${className}`}>
|
||||
{/* 扫码按钮 */}
|
||||
<Button
|
||||
type="primary"
|
||||
size="large"
|
||||
loading={buttonProps.loading}
|
||||
disabled={buttonProps.disabled}
|
||||
onClick={buttonProps.onClick}
|
||||
className="w-full"
|
||||
>
|
||||
{!buttonProps.loading && (
|
||||
<Scan className="mr-2" />
|
||||
)}
|
||||
{buttonProps.text}
|
||||
</Button>
|
||||
|
||||
{/* 状态显示 */}
|
||||
{showStatus && (
|
||||
<View className="mt-4 text-center">
|
||||
{getStatusContent()}
|
||||
</View>
|
||||
)}
|
||||
|
||||
{/* 成功结果显示 */}
|
||||
{state === ScanLoginState.SUCCESS && result && (
|
||||
<View className="mt-4 p-4 bg-green-50 rounded-lg">
|
||||
<Text className="text-sm text-green-700">
|
||||
已为用户 {result.userInfo?.nickname || result.userInfo?.userId} 确认登录
|
||||
</Text>
|
||||
</View>
|
||||
)}
|
||||
|
||||
{/* 使用说明 */}
|
||||
{state === ScanLoginState.IDLE && (
|
||||
<View className="mt-4 text-center">
|
||||
<Text className="text-xs text-gray-500">
|
||||
扫描网页端显示的登录二维码即可快速登录
|
||||
</Text>
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
export default QRLoginScanner;
|
||||
272
src/components/QRScanModal.tsx
Normal file
272
src/components/QRScanModal.tsx
Normal file
@@ -0,0 +1,272 @@
|
||||
import React, { useState } from 'react';
|
||||
import { View, Text } from '@tarojs/components';
|
||||
import { Button, Popup, Loading } from '@nutui/nutui-react-taro';
|
||||
import { Scan, Close, Success, Failure } from '@nutui/icons-react-taro';
|
||||
import Taro from '@tarojs/taro';
|
||||
import { parseQRContent, confirmQRLogin } from '@/api/passport/qr-login';
|
||||
import { useUser } from '@/hooks/useUser';
|
||||
|
||||
export interface QRScanModalProps {
|
||||
/** 是否显示弹窗 */
|
||||
visible: boolean;
|
||||
/** 关闭弹窗回调 */
|
||||
onClose: () => void;
|
||||
/** 扫码成功回调 */
|
||||
onSuccess?: (result: any) => void;
|
||||
/** 扫码失败回调 */
|
||||
onError?: (error: string) => void;
|
||||
/** 弹窗标题 */
|
||||
title?: string;
|
||||
/** 描述文本 */
|
||||
description?: string;
|
||||
/** 是否自动确认登录 */
|
||||
autoConfirm?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* 二维码扫描弹窗组件(用于扫码登录)
|
||||
*/
|
||||
const QRScanModal: React.FC<QRScanModalProps> = ({
|
||||
visible,
|
||||
onClose,
|
||||
onSuccess,
|
||||
onError,
|
||||
title = '扫描登录二维码',
|
||||
description = '扫描网页端显示的登录二维码',
|
||||
autoConfirm = true
|
||||
}) => {
|
||||
const { user } = useUser();
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [status, setStatus] = useState<'idle' | 'scanning' | 'confirming' | 'success' | 'error'>('idle');
|
||||
const [errorMsg, setErrorMsg] = useState('');
|
||||
|
||||
// 开始扫码
|
||||
const handleScan = async () => {
|
||||
if (!user?.userId) {
|
||||
onError?.('请先登录小程序');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
setLoading(true);
|
||||
setStatus('scanning');
|
||||
setErrorMsg('');
|
||||
|
||||
// 扫码
|
||||
const scanResult = await new Promise<string>((resolve, reject) => {
|
||||
Taro.scanCode({
|
||||
onlyFromCamera: true,
|
||||
scanType: ['qrCode'],
|
||||
success: (res) => {
|
||||
if (res.result) {
|
||||
resolve(res.result);
|
||||
} else {
|
||||
reject(new Error('扫码结果为空'));
|
||||
}
|
||||
},
|
||||
fail: (err) => {
|
||||
reject(new Error(err.errMsg || '扫码失败'));
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// 解析二维码内容
|
||||
const token = parseQRContent(scanResult);
|
||||
if (!token) {
|
||||
throw new Error('无效的登录二维码');
|
||||
}
|
||||
|
||||
if (autoConfirm) {
|
||||
// 自动确认登录
|
||||
setStatus('confirming');
|
||||
const result = await confirmQRLogin({
|
||||
token,
|
||||
userId: user.userId,
|
||||
platform: 'wechat',
|
||||
wechatInfo: {
|
||||
nickname: user.nickname,
|
||||
avatar: user.avatar
|
||||
}
|
||||
});
|
||||
|
||||
if (result.success) {
|
||||
setStatus('success');
|
||||
onSuccess?.(result);
|
||||
|
||||
// 显示成功提示
|
||||
Taro.showToast({
|
||||
title: '登录确认成功',
|
||||
icon: 'success'
|
||||
});
|
||||
|
||||
// 延迟关闭
|
||||
setTimeout(() => {
|
||||
onClose();
|
||||
setStatus('idle');
|
||||
}, 1500);
|
||||
} else {
|
||||
throw new Error(result.message || '登录确认失败');
|
||||
}
|
||||
} else {
|
||||
// 只返回扫码结果
|
||||
onSuccess?.(scanResult);
|
||||
onClose();
|
||||
setStatus('idle');
|
||||
}
|
||||
} catch (error: any) {
|
||||
setStatus('error');
|
||||
const errorMessage = error.message || '操作失败';
|
||||
setErrorMsg(errorMessage);
|
||||
onError?.(errorMessage);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
// 重试
|
||||
const handleRetry = () => {
|
||||
setStatus('idle');
|
||||
setErrorMsg('');
|
||||
handleScan();
|
||||
};
|
||||
|
||||
// 关闭弹窗
|
||||
const handleClose = () => {
|
||||
setStatus('idle');
|
||||
setErrorMsg('');
|
||||
setLoading(false);
|
||||
onClose();
|
||||
};
|
||||
|
||||
// 获取状态显示内容
|
||||
const getStatusContent = () => {
|
||||
switch (status) {
|
||||
case 'scanning':
|
||||
return {
|
||||
icon: <Loading className="text-blue-500" />,
|
||||
title: '正在扫码...',
|
||||
description: '请将二维码对准摄像头'
|
||||
};
|
||||
|
||||
case 'confirming':
|
||||
return {
|
||||
icon: <Loading className="text-orange-500" />,
|
||||
title: '正在确认登录...',
|
||||
description: '请稍候,正在为您确认登录'
|
||||
};
|
||||
|
||||
case 'success':
|
||||
return {
|
||||
icon: <Success size="32" className="text-green-500" />,
|
||||
title: '登录确认成功',
|
||||
description: '网页端将自动完成登录'
|
||||
};
|
||||
|
||||
case 'error':
|
||||
return {
|
||||
icon: <Failure size="32" className="text-red-500" />,
|
||||
title: '操作失败',
|
||||
description: errorMsg || '请重试'
|
||||
};
|
||||
|
||||
default:
|
||||
return {
|
||||
icon: <Scan size="32" className="text-blue-500" />,
|
||||
title,
|
||||
description
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
const statusContent = getStatusContent();
|
||||
|
||||
return (
|
||||
<Popup
|
||||
visible={visible}
|
||||
position="center"
|
||||
closeable={false}
|
||||
onClose={handleClose}
|
||||
style={{ width: '85%', borderRadius: '12px' }}
|
||||
>
|
||||
<View className="p-6 text-center relative">
|
||||
{/* 关闭按钮 */}
|
||||
{status !== 'scanning' && status !== 'confirming' && (
|
||||
<View className="absolute top-4 right-4">
|
||||
<Button
|
||||
size="small"
|
||||
type="default"
|
||||
onClick={handleClose}
|
||||
className="w-8 h-8 p-0"
|
||||
>
|
||||
<Close size="16" />
|
||||
</Button>
|
||||
</View>
|
||||
)}
|
||||
|
||||
{/* 图标 */}
|
||||
<View className="w-16 h-16 mx-auto bg-gray-100 rounded-full flex items-center justify-center mb-4">
|
||||
{statusContent.icon}
|
||||
</View>
|
||||
|
||||
{/* 标题 */}
|
||||
<Text className="text-lg font-bold text-gray-800 mb-2 block">
|
||||
{statusContent.title}
|
||||
</Text>
|
||||
|
||||
{/* 描述 */}
|
||||
<Text className="text-gray-600 mb-6 block">
|
||||
{statusContent.description}
|
||||
</Text>
|
||||
|
||||
{/* 操作按钮 */}
|
||||
{status === 'idle' && (
|
||||
<Button
|
||||
type="primary"
|
||||
size="large"
|
||||
onClick={handleScan}
|
||||
className="w-full"
|
||||
disabled={!user?.userId}
|
||||
>
|
||||
<Scan className="mr-2" />
|
||||
{user?.userId ? '开始扫码' : '请先登录'}
|
||||
</Button>
|
||||
)}
|
||||
|
||||
{status === 'error' && (
|
||||
<View className="space-y-2">
|
||||
<Button
|
||||
type="primary"
|
||||
size="large"
|
||||
onClick={handleRetry}
|
||||
className="w-full"
|
||||
>
|
||||
重试
|
||||
</Button>
|
||||
<Button
|
||||
type="default"
|
||||
size="normal"
|
||||
onClick={handleClose}
|
||||
className="w-full"
|
||||
>
|
||||
取消
|
||||
</Button>
|
||||
</View>
|
||||
)}
|
||||
|
||||
{(status === 'scanning' || status === 'confirming') && (
|
||||
<Button
|
||||
type="default"
|
||||
size="large"
|
||||
onClick={handleClose}
|
||||
className="w-full"
|
||||
>
|
||||
取消
|
||||
</Button>
|
||||
)}
|
||||
</View>
|
||||
{loading}
|
||||
</Popup>
|
||||
);
|
||||
};
|
||||
|
||||
export default QRScanModal;
|
||||
@@ -81,7 +81,7 @@ const SimpleQRCodeModal: React.FC<SimpleQRCodeModalProps> = ({
|
||||
{qrContent ? (
|
||||
<View className={'flex flex-col justify-center'}>
|
||||
<img
|
||||
src={`https://cms-api.websoft.top/api/qr-code/create-encrypted-qr-image?size=300x300&expireMinutes=60&businessType=gift&data=${encodeURIComponent(qrContent)}`}
|
||||
src={`https://ysb-api.websoft.top/api/qr-code/create-encrypted-qr-image?size=300x300&expireMinutes=60&businessType=gift&data=${encodeURIComponent(qrContent)}`}
|
||||
alt="二维码"
|
||||
style={{width: '200px', height: '200px'}}
|
||||
className="mx-auto"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Tabbar } from '@nutui/nutui-react-taro'
|
||||
import { Home, User } from '@nutui/icons-react-taro'
|
||||
import Taro from '@tarojs/taro'
|
||||
import { switchTab } from '@/utils/navigation'
|
||||
|
||||
function TabBar(){
|
||||
return (
|
||||
@@ -9,13 +9,13 @@ function TabBar(){
|
||||
onSwitch={(index) => {
|
||||
console.log(index)
|
||||
if(index == 0){
|
||||
Taro.switchTab({ url: '/pages/index/index' })
|
||||
switchTab('index/index')
|
||||
}
|
||||
// if(index == 1){
|
||||
// Taro.navigateTo({ url: '/pages/detail/detail' })
|
||||
// goTo('detail/detail')
|
||||
// }
|
||||
if(index == 1){
|
||||
Taro.switchTab({ url: '/pages/user/user' })
|
||||
switchTab('user/user')
|
||||
}
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import React from 'react';
|
||||
import { Button } from '@nutui/nutui-react-taro';
|
||||
import { View } from '@tarojs/components';
|
||||
import { Scan } from '@nutui/icons-react-taro';
|
||||
import Taro from '@tarojs/taro';
|
||||
import { useUnifiedQRScan, ScanType, type UnifiedScanResult } from '@/hooks/useUnifiedQRScan';
|
||||
@@ -27,11 +29,15 @@ export interface UnifiedQRButtonProps {
|
||||
* 支持登录和核销两种类型的二维码扫描
|
||||
*/
|
||||
const UnifiedQRButton: React.FC<UnifiedQRButtonProps> = ({
|
||||
type = 'danger',
|
||||
size = 'small',
|
||||
text = '扫码',
|
||||
showIcon = true,
|
||||
onSuccess,
|
||||
onError,
|
||||
usePageMode = false
|
||||
}) => {
|
||||
const { startScan, canScan, result } = useUnifiedQRScan();
|
||||
const { startScan, isLoading, canScan, state, result } = useUnifiedQRScan();
|
||||
console.log(result,'useUnifiedQRScan>>result')
|
||||
// 处理点击事件
|
||||
const handleClick = async () => {
|
||||
@@ -62,7 +68,7 @@ const UnifiedQRButton: React.FC<UnifiedQRButtonProps> = ({
|
||||
setTimeout(() => {
|
||||
Taro.showModal({
|
||||
title: '核销成功',
|
||||
content: '是否继续扫码核销其他礼品卡?',
|
||||
content: '是否继续扫码核销其他水票/礼品卡?',
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
handleClick(); // 递归调用继续扫码
|
||||
@@ -77,8 +83,43 @@ const UnifiedQRButton: React.FC<UnifiedQRButtonProps> = ({
|
||||
}
|
||||
};
|
||||
|
||||
const disabled = !canScan() || isLoading;
|
||||
|
||||
// 根据当前状态动态显示文本
|
||||
const getButtonText = () => {
|
||||
if (isLoading) {
|
||||
switch (state) {
|
||||
case 'scanning':
|
||||
return '扫码中...';
|
||||
case 'processing':
|
||||
return '处理中...';
|
||||
default:
|
||||
return '扫码中...';
|
||||
}
|
||||
}
|
||||
|
||||
if (disabled && !canScan()) {
|
||||
return '请先登录';
|
||||
}
|
||||
|
||||
return text;
|
||||
};
|
||||
|
||||
return (
|
||||
<Scan className={'text-white'} onClick={handleClick} />
|
||||
<Button
|
||||
type={type}
|
||||
size={size}
|
||||
loading={isLoading}
|
||||
disabled={disabled}
|
||||
onClick={handleClick}
|
||||
>
|
||||
<View className="flex items-center justify-center">
|
||||
{showIcon && !isLoading && (
|
||||
<Scan className="mr-1" />
|
||||
)}
|
||||
{getButtonText()}
|
||||
</View>
|
||||
</Button>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React from 'react';
|
||||
import { View, Text } from '@tarojs/components';
|
||||
import { View, Text, Image } from '@tarojs/components';
|
||||
import { Button, Avatar } from '@nutui/nutui-react-taro';
|
||||
import { useUser } from '@/hooks/useUser';
|
||||
import navTo from '@/utils/common';
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
export default definePageConfig({
|
||||
navigationBarTitleText: '客户报备',
|
||||
navigationBarTitleText: '领劵中心',
|
||||
navigationBarTextStyle: 'black'
|
||||
})
|
||||
368
src/coupon/index.tsx
Normal file
368
src/coupon/index.tsx
Normal file
@@ -0,0 +1,368 @@
|
||||
import {useState} from "react";
|
||||
import Taro, {useDidShow} from '@tarojs/taro'
|
||||
import {
|
||||
Empty,
|
||||
ConfigProvider,
|
||||
InfiniteLoading,
|
||||
Loading,
|
||||
PullToRefresh,
|
||||
Tabs,
|
||||
TabPane
|
||||
} from '@nutui/nutui-react-taro'
|
||||
import {View} from '@tarojs/components'
|
||||
import {ShopCoupon} from "@/api/shop/shopCoupon/model";
|
||||
import {pageShopCoupon} from "@/api/shop/shopCoupon";
|
||||
import CouponList from "@/components/CouponList";
|
||||
import CouponGuide from "@/components/CouponGuide";
|
||||
import CouponFilter from "@/components/CouponFilter";
|
||||
import {CouponCardProps} from "@/components/CouponCard";
|
||||
import {takeCoupon} from "@/api/shop/shopUserCoupon";
|
||||
|
||||
const CouponReceiveCenter = () => {
|
||||
const [list, setList] = useState<ShopCoupon[]>([])
|
||||
const [loading, setLoading] = useState(false)
|
||||
const [hasMore, setHasMore] = useState(true)
|
||||
const [page, setPage] = useState(1)
|
||||
const [activeTab, setActiveTab] = useState('0') // 0-全部 1-满减券 2-折扣券 3-免费券
|
||||
const [showGuide, setShowGuide] = useState(false)
|
||||
const [showFilter, setShowFilter] = useState(false)
|
||||
const [filters, setFilters] = useState({
|
||||
type: [] as number[],
|
||||
minAmount: undefined as number | undefined,
|
||||
sortBy: 'createTime' as 'createTime' | 'amount' | 'expireTime',
|
||||
sortOrder: 'desc' as 'asc' | 'desc'
|
||||
})
|
||||
|
||||
// 获取优惠券类型过滤条件
|
||||
const getTypeFilter = () => {
|
||||
switch (String(activeTab)) {
|
||||
case '0': // 全部
|
||||
return {}
|
||||
case '1': // 满减券
|
||||
return { type: 10 }
|
||||
case '2': // 折扣券
|
||||
return { type: 20 }
|
||||
case '3': // 免费券
|
||||
return { type: 30 }
|
||||
default:
|
||||
return {}
|
||||
}
|
||||
}
|
||||
|
||||
// 根据传入的值获取类型过滤条件
|
||||
const getTypeFilterByValue = (value: string | number) => {
|
||||
switch (String(value)) {
|
||||
case '0': // 全部
|
||||
return {}
|
||||
case '1': // 满减券
|
||||
return { type: 10 }
|
||||
case '2': // 折扣券
|
||||
return { type: 20 }
|
||||
case '3': // 免费券
|
||||
return { type: 30 }
|
||||
default:
|
||||
return {}
|
||||
}
|
||||
}
|
||||
|
||||
// 根据类型过滤条件加载优惠券
|
||||
const loadCouponsByType = async (typeFilter: any) => {
|
||||
setLoading(true)
|
||||
try {
|
||||
const currentPage = 1
|
||||
// 获取可领取的优惠券(启用状态且未过期)
|
||||
const res = await pageShopCoupon({
|
||||
page: currentPage,
|
||||
limit: 10,
|
||||
keywords: '',
|
||||
enabled: 1, // 启用状态
|
||||
isExpire: 0, // 未过期
|
||||
...typeFilter
|
||||
})
|
||||
|
||||
console.log('API返回数据:', res)
|
||||
if (res && res.list) {
|
||||
setList(res.list)
|
||||
setHasMore(res.list.length === 10)
|
||||
setPage(2)
|
||||
} else {
|
||||
setList([])
|
||||
setHasMore(false)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取优惠券失败:', error)
|
||||
Taro.showToast({
|
||||
title: '获取优惠券失败',
|
||||
icon: 'error'
|
||||
})
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
const reload = async (isRefresh = false) => {
|
||||
if (isRefresh) {
|
||||
setPage(1)
|
||||
setList([])
|
||||
setHasMore(true)
|
||||
}
|
||||
|
||||
setLoading(true)
|
||||
try {
|
||||
const currentPage = isRefresh ? 1 : page
|
||||
const typeFilter = getTypeFilter()
|
||||
console.log('reload - 当前activeTab:', activeTab, '类型过滤:', typeFilter)
|
||||
|
||||
// 获取可领取的优惠券(启用状态且未过期)
|
||||
const res = await pageShopCoupon({
|
||||
page: currentPage,
|
||||
limit: 10,
|
||||
keywords: '',
|
||||
enabled: 1, // 启用状态
|
||||
isExpire: 0, // 未过期
|
||||
...typeFilter,
|
||||
// 应用筛选条件
|
||||
...(filters.type.length > 0 && { type: filters.type[0] }),
|
||||
sortBy: filters.sortBy,
|
||||
sortOrder: filters.sortOrder
|
||||
})
|
||||
|
||||
console.log('reload - API返回数据:', res)
|
||||
if (res && res.list) {
|
||||
const newList = isRefresh ? res.list : [...list, ...res.list]
|
||||
setList(newList)
|
||||
|
||||
// 判断是否还有更多数据
|
||||
setHasMore(res.list.length === 10)
|
||||
|
||||
if (!isRefresh) {
|
||||
setPage(currentPage + 1)
|
||||
} else {
|
||||
setPage(2)
|
||||
}
|
||||
} else {
|
||||
setHasMore(false)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取优惠券失败:', error)
|
||||
Taro.showToast({
|
||||
title: '获取优惠券失败',
|
||||
icon: 'error'
|
||||
});
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
// 下拉刷新
|
||||
const handleRefresh = async () => {
|
||||
await reload(true)
|
||||
}
|
||||
|
||||
// Tab切换
|
||||
const handleTabChange = (value: string | number) => {
|
||||
console.log('Tab切换到:', value)
|
||||
setActiveTab(String(value))
|
||||
setPage(1)
|
||||
setList([])
|
||||
setHasMore(true)
|
||||
|
||||
// 直接传递类型值,避免异步状态更新问题
|
||||
const typeFilter = getTypeFilterByValue(value)
|
||||
console.log('类型过滤条件:', typeFilter)
|
||||
|
||||
// 立即加载数据
|
||||
loadCouponsByType(typeFilter)
|
||||
}
|
||||
|
||||
// 转换优惠券数据为CouponCard组件所需格式
|
||||
const transformCouponData = (coupon: ShopCoupon): CouponCardProps => {
|
||||
let amount = 0
|
||||
let type: 10 | 20 | 30 = 10
|
||||
|
||||
if (coupon.type === 10) { // 满减券
|
||||
type = 10
|
||||
amount = parseFloat(coupon.reducePrice || '0')
|
||||
} else if (coupon.type === 20) { // 折扣券
|
||||
type = 20
|
||||
amount = coupon.discount || 0
|
||||
} else if (coupon.type === 30) { // 免费券
|
||||
type = 30
|
||||
amount = 0
|
||||
}
|
||||
|
||||
return {
|
||||
id: coupon.id?.toString(),
|
||||
amount,
|
||||
type,
|
||||
status: 0, // 可领取状态
|
||||
minAmount: parseFloat(coupon.minPrice || '0'),
|
||||
title: coupon.name || '优惠券',
|
||||
description: coupon.description,
|
||||
startTime: coupon.startTime,
|
||||
endTime: coupon.endTime,
|
||||
showReceiveBtn: true, // 显示领取按钮
|
||||
onReceive: () => handleReceiveCoupon(coupon),
|
||||
theme: getThemeByType(coupon.type)
|
||||
}
|
||||
}
|
||||
|
||||
// 根据优惠券类型获取主题色
|
||||
const getThemeByType = (type?: number): 'red' | 'orange' | 'blue' | 'purple' | 'green' => {
|
||||
switch (type) {
|
||||
case 10: return 'red' // 满减券-红色
|
||||
case 20: return 'orange' // 折扣券-橙色
|
||||
case 30: return 'green' // 免费券-绿色
|
||||
default: return 'blue'
|
||||
}
|
||||
}
|
||||
|
||||
// 领取优惠券
|
||||
const handleReceiveCoupon = async (coupon: ShopCoupon) => {
|
||||
try {
|
||||
// 检查是否已登录
|
||||
const userId = Taro.getStorageSync('UserId')
|
||||
if (!userId) {
|
||||
Taro.showToast({
|
||||
title: '请先登录',
|
||||
icon: 'error'
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 调用领取接口
|
||||
await takeCoupon({
|
||||
couponId: coupon.id!,
|
||||
userId: userId
|
||||
})
|
||||
|
||||
Taro.showToast({
|
||||
title: '领取成功',
|
||||
icon: 'success'
|
||||
})
|
||||
|
||||
// 刷新列表
|
||||
reload(true)
|
||||
} catch (error: any) {
|
||||
console.error('领取优惠券失败:', error)
|
||||
Taro.showToast({
|
||||
title: error.message || '领取失败',
|
||||
icon: 'none'
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 筛选条件变更
|
||||
const handleFiltersChange = (newFilters: any) => {
|
||||
setFilters(newFilters)
|
||||
reload(true)
|
||||
}
|
||||
|
||||
// 查看我的优惠券
|
||||
const handleViewMyCoupons = () => {
|
||||
Taro.navigateTo({
|
||||
url: '/user/coupon/index'
|
||||
})
|
||||
}
|
||||
|
||||
// 加载更多
|
||||
const loadMore = async () => {
|
||||
if (!loading && hasMore) {
|
||||
await reload(false) // 不刷新,追加数据
|
||||
}
|
||||
}
|
||||
|
||||
useDidShow(() => {
|
||||
reload(true).then()
|
||||
});
|
||||
|
||||
return (
|
||||
<ConfigProvider className="h-screen flex flex-col">
|
||||
{/* Tab切换 */}
|
||||
<View className="bg-white hidden">
|
||||
<Tabs value={activeTab} onChange={handleTabChange}>
|
||||
<TabPane title="全部" value="0">
|
||||
</TabPane>
|
||||
<TabPane title="满减券" value="1">
|
||||
</TabPane>
|
||||
<TabPane title="折扣券" value="2">
|
||||
</TabPane>
|
||||
<TabPane title="免费券" value="3">
|
||||
</TabPane>
|
||||
</Tabs>
|
||||
</View>
|
||||
|
||||
{/* 优惠券列表 - 占满剩余空间 */}
|
||||
<View className="flex-1 overflow-hidden">
|
||||
<PullToRefresh
|
||||
onRefresh={handleRefresh}
|
||||
headHeight={60}
|
||||
>
|
||||
<View
|
||||
style={{
|
||||
height: 'calc(100vh - 60px)',
|
||||
overflowY: 'auto',
|
||||
paddingTop: '24px',
|
||||
paddingBottom: '32px'
|
||||
}}
|
||||
id="coupon-scroll"
|
||||
>
|
||||
{list.length === 0 && !loading ? (
|
||||
<View className="flex flex-col justify-center items-center h-full">
|
||||
<Empty
|
||||
description="暂无可领取的优惠券"
|
||||
style={{backgroundColor: 'transparent'}}
|
||||
actions={[
|
||||
{
|
||||
text: '查看我的优惠券',
|
||||
onClick: handleViewMyCoupons
|
||||
}
|
||||
]}
|
||||
/>
|
||||
</View>
|
||||
) : (
|
||||
<InfiniteLoading
|
||||
target="coupon-scroll"
|
||||
hasMore={hasMore}
|
||||
onLoadMore={loadMore}
|
||||
loadingText={
|
||||
<View className="flex justify-center items-center py-4">
|
||||
<Loading />
|
||||
<View className="ml-2">加载中...</View>
|
||||
</View>
|
||||
}
|
||||
loadMoreText={
|
||||
<View className="text-center py-4 text-gray-500">
|
||||
{list.length === 0 ? "暂无数据" : "没有更多了"}
|
||||
</View>
|
||||
}
|
||||
>
|
||||
<CouponList
|
||||
coupons={list.map(transformCouponData)}
|
||||
showEmpty={false}
|
||||
/>
|
||||
</InfiniteLoading>
|
||||
)}
|
||||
</View>
|
||||
</PullToRefresh>
|
||||
</View>
|
||||
|
||||
{/* 使用指南弹窗 */}
|
||||
<CouponGuide
|
||||
visible={showGuide}
|
||||
onClose={() => setShowGuide(false)}
|
||||
/>
|
||||
|
||||
{/* 筛选弹窗 */}
|
||||
<CouponFilter
|
||||
visible={showFilter}
|
||||
filters={filters}
|
||||
onFiltersChange={handleFiltersChange}
|
||||
onClose={() => setShowFilter(false)}
|
||||
/>
|
||||
</ConfigProvider>
|
||||
);
|
||||
|
||||
};
|
||||
|
||||
export default CouponReceiveCenter;
|
||||
@@ -1,4 +1,4 @@
|
||||
export default definePageConfig({
|
||||
navigationBarTitleText: '邀请注册',
|
||||
navigationBarTitleText: '注册成为会员',
|
||||
navigationBarTextStyle: 'black'
|
||||
})
|
||||
|
||||
@@ -9,9 +9,10 @@ import {TenantId} from "@/config/app";
|
||||
import {updateUser} from "@/api/system/user";
|
||||
import {User} from "@/api/system/user/model";
|
||||
import {getStoredInviteParams, handleInviteRelation} from "@/utils/invite";
|
||||
import {addShopDealerUser, updateShopDealerUserByUserId} from "@/api/shop/shopDealerUser";
|
||||
import {listUserRole, updateUserRole} from "@/api/system/userRole";
|
||||
import {addShopDealerCapital} from "@/api/shop/shopDealerCapital";
|
||||
import {addShopDealerUser} from "@/api/shop/shopDealerUser";
|
||||
import {addUserRole, listUserRole, updateUserRole} from "@/api/system/userRole";
|
||||
import { listRoles } from "@/api/system/role";
|
||||
import type { UserRole } from "@/api/system/userRole/model";
|
||||
|
||||
// 类型定义
|
||||
interface ChooseAvatarEvent {
|
||||
@@ -27,9 +28,8 @@ interface InputEvent {
|
||||
}
|
||||
|
||||
const AddUserAddress = () => {
|
||||
const {user, loginUser} = useUser()
|
||||
const {user, loginUser, fetchUserInfo} = useUser()
|
||||
const [loading, setLoading] = useState<boolean>(true)
|
||||
const [submitting, setSubmitting] = useState<boolean>(false)
|
||||
const [FormData, setFormData] = useState<User>()
|
||||
const formRef = useRef<any>(null)
|
||||
|
||||
@@ -129,14 +129,7 @@ const AddUserAddress = () => {
|
||||
}
|
||||
|
||||
// 提交表单
|
||||
const submitSucceed = async (values: any) => {
|
||||
// 防止重复提交
|
||||
if (submitting) {
|
||||
console.log('正在提交中,请勿重复点击')
|
||||
return
|
||||
}
|
||||
|
||||
setSubmitting(true)
|
||||
const submitSucceed = async (values: User) => {
|
||||
try {
|
||||
// 验证必填字段
|
||||
if (!values.phone && !FormData?.phone) {
|
||||
@@ -151,8 +144,8 @@ const AddUserAddress = () => {
|
||||
const nickname = values.realName || FormData?.nickname || '';
|
||||
if (!nickname || nickname.trim() === '') {
|
||||
Taro.showToast({
|
||||
title: '请填写昵称',
|
||||
icon: 'error'
|
||||
title: '请上传头像和填写昵称',
|
||||
icon: 'none'
|
||||
});
|
||||
return;
|
||||
}
|
||||
@@ -185,12 +178,27 @@ const AddUserAddress = () => {
|
||||
}
|
||||
console.log(values,FormData)
|
||||
|
||||
const roles = await listUserRole({userId: user?.userId})
|
||||
console.log(roles, 'roles...')
|
||||
if (!user?.userId) {
|
||||
Taro.showToast({
|
||||
title: '用户信息缺失,请先登录',
|
||||
icon: 'error'
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
let roles: UserRole[] = [];
|
||||
try {
|
||||
roles = await listUserRole({userId: user.userId})
|
||||
console.log(roles, 'roles...')
|
||||
} catch (e) {
|
||||
// 新用户/权限限制时可能查不到角色列表,不影响基础注册流程
|
||||
console.warn('查询用户角色失败,将尝试直接写入默认角色:', e)
|
||||
roles = []
|
||||
}
|
||||
|
||||
// 准备提交的数据
|
||||
await updateUser({
|
||||
userId: user?.userId,
|
||||
userId: user.userId,
|
||||
nickname: values.realName || FormData?.nickname,
|
||||
phone: values.phone || FormData?.phone,
|
||||
avatar: values.avatar || FormData?.avatar,
|
||||
@@ -198,33 +206,57 @@ const AddUserAddress = () => {
|
||||
});
|
||||
|
||||
await addShopDealerUser({
|
||||
userId: user?.userId,
|
||||
userId: user.userId,
|
||||
realName: values.realName || FormData?.nickname,
|
||||
mobile: values.phone || FormData?.phone,
|
||||
refereeId: values.refereeId || FormData?.refereeId
|
||||
refereeId: Number(values.refereeId) || Number(FormData?.refereeId)
|
||||
})
|
||||
|
||||
if (roles.length > 0) {
|
||||
await updateUserRole({
|
||||
...roles[0],
|
||||
roleId: 1848
|
||||
})
|
||||
// 通知其他页面(如“我的”页、分销中心页)刷新经销商信息
|
||||
Taro.eventCenter.trigger('dealerUser:changed')
|
||||
|
||||
// 角色为空时这里会导致“注册成功但没有角色”,这里做一次兜底写入默认 user 角色
|
||||
try {
|
||||
// 1) 先尝试通过 roleCode=user 查询角色ID(避免硬编码)
|
||||
// 2) 取不到就回退到旧的默认ID(1848)
|
||||
let userRoleId: number | undefined;
|
||||
try {
|
||||
// 注意:当前 request.get 的封装不支持 axios 风格的 { params: ... },
|
||||
// 某些自动生成的 API 可能无法按参数过滤;这里直接取全量再本地查找更稳。
|
||||
const roleList = await listRoles();
|
||||
userRoleId = roleList?.find(r => r.roleCode === 'user')?.roleId;
|
||||
} catch (_) {
|
||||
// ignore
|
||||
}
|
||||
if (!userRoleId) userRoleId = 1848;
|
||||
|
||||
const baseRolePayload = {
|
||||
userId: user.userId,
|
||||
tenantId: Number(TenantId),
|
||||
roleId: userRoleId
|
||||
};
|
||||
|
||||
// 后端若已创建 user-role 记录则更新;否则尝试“无id更新”触发创建(多数实现会 upsert)
|
||||
if (roles.length > 0) {
|
||||
await updateUserRole({
|
||||
...roles[0],
|
||||
roleId: userRoleId
|
||||
});
|
||||
} else {
|
||||
try {
|
||||
await addUserRole(baseRolePayload);
|
||||
} catch (_) {
|
||||
// 兼容后端仅支持 PUT upsert 的情况
|
||||
await updateUserRole(baseRolePayload);
|
||||
}
|
||||
}
|
||||
|
||||
// 刷新一次用户信息,确保 roles 写回本地缓存,避免“我的”页显示为空/不一致
|
||||
await fetchUserInfo();
|
||||
} catch (e) {
|
||||
console.warn('写入默认角色失败(不影响注册成功):', e)
|
||||
}
|
||||
|
||||
// 获得50元奖励
|
||||
await updateShopDealerUserByUserId({
|
||||
userId: user?.userId,
|
||||
money: '50',
|
||||
})
|
||||
|
||||
// 保存明细
|
||||
await addShopDealerCapital({
|
||||
userId: user?.userId,
|
||||
flowType: 50,
|
||||
money: '50',
|
||||
toUserId: user?.refereeId,
|
||||
comments: '新人注册奖励'
|
||||
})
|
||||
|
||||
Taro.showToast({
|
||||
title: `注册成功`,
|
||||
@@ -232,17 +264,12 @@ const AddUserAddress = () => {
|
||||
});
|
||||
|
||||
setTimeout(() => {
|
||||
Taro.navigateBack();
|
||||
// “我的”是 tabBar 页面,注册完成后直接切到“我的”
|
||||
Taro.switchTab({ url: '/pages/user/user' });
|
||||
}, 1000);
|
||||
|
||||
} catch (error) {
|
||||
console.error('验证邀请人失败:', error);
|
||||
Taro.showToast({
|
||||
title: '注册失败,请重试',
|
||||
icon: 'error'
|
||||
})
|
||||
} finally {
|
||||
setSubmitting(false)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -411,9 +438,9 @@ const AddUserAddress = () => {
|
||||
>
|
||||
<View className={'bg-gray-100 h-3'}></View>
|
||||
<CellGroup style={{padding: '4px 0'}}>
|
||||
<Form.Item name="refereeId" label="邀请人ID" initialValue={FormData?.refereeId} required>
|
||||
<Input placeholder="邀请人ID" disabled={true}/>
|
||||
</Form.Item>
|
||||
{/*<Form.Item name="refereeId" label="邀请人ID" initialValue={FormData?.refereeId} required>*/}
|
||||
{/* <Input placeholder="邀请人ID" disabled={false}/>*/}
|
||||
{/*</Form.Item>*/}
|
||||
<Form.Item name="phone" label="手机号" initialValue={FormData?.phone} required>
|
||||
<View className="flex items-center justify-between">
|
||||
<Input
|
||||
@@ -451,9 +478,8 @@ const AddUserAddress = () => {
|
||||
{/* 底部浮动按钮 */}
|
||||
<FixedButton
|
||||
icon={<Edit/>}
|
||||
text={submitting ? '注册中...' : '立即注册'}
|
||||
text={'立即注册'}
|
||||
onClick={handleFixedButtonClick}
|
||||
disabled={submitting}
|
||||
/>
|
||||
|
||||
</>
|
||||
|
||||
@@ -1,156 +0,0 @@
|
||||
import {useEffect, useState, useRef} from "react";
|
||||
import {useRouter} from '@tarojs/taro'
|
||||
import {Loading, CellGroup, Input, Form} from '@nutui/nutui-react-taro'
|
||||
import Taro from '@tarojs/taro'
|
||||
import {
|
||||
getShopDealerBank,
|
||||
listShopDealerBank,
|
||||
updateShopDealerBank,
|
||||
addShopDealerBank
|
||||
} from "@/api/shop/shopDealerBank";
|
||||
import FixedButton from "@/components/FixedButton";
|
||||
import {ShopDealerBank} from "@/api/shop/shopDealerBank/model";
|
||||
import {myUserVerify} from "@/api/system/userVerify";
|
||||
|
||||
const AddUserAddress = () => {
|
||||
const {params} = useRouter();
|
||||
const [loading, setLoading] = useState<boolean>(true)
|
||||
const [FormData, setFormData] = useState<ShopDealerBank>()
|
||||
const formRef = useRef<any>(null)
|
||||
|
||||
// 判断是编辑还是新增模式
|
||||
const isEditMode = !!params.id
|
||||
const bankId = params.id ? Number(params.id) : undefined
|
||||
|
||||
|
||||
const reload = async () => {
|
||||
// 如果是编辑模式,加载地址数据
|
||||
if (isEditMode && bankId) {
|
||||
try {
|
||||
const bank = await getShopDealerBank(bankId)
|
||||
setFormData(bank)
|
||||
} catch (error) {
|
||||
console.error('加载地址失败:', error)
|
||||
Taro.showToast({
|
||||
title: '加载地址失败',
|
||||
icon: 'error'
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 提交表单
|
||||
const submitSucceed = async (values: any) => {
|
||||
console.log('.>>>>>>,....',values)
|
||||
|
||||
|
||||
const verify = await myUserVerify({userId: Taro.getStorageSync('UserId')})
|
||||
if(verify?.realName !== values.bankAccount){
|
||||
Taro.showToast({
|
||||
title: '收款人姓名与实名认证信息不一致!',
|
||||
icon: 'none'
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
try {
|
||||
// 准备提交的数据
|
||||
const submitData = {
|
||||
...values,
|
||||
isDefault: true // 新增或编辑的地址都设为默认地址
|
||||
};
|
||||
|
||||
console.log('提交数据:', submitData)
|
||||
|
||||
// 如果是编辑模式,添加id
|
||||
if (isEditMode && bankId) {
|
||||
submitData.id = bankId;
|
||||
}
|
||||
|
||||
// 先处理默认地址逻辑
|
||||
const defaultAddress = await listShopDealerBank({isDefault: true});
|
||||
if (defaultAddress && defaultAddress.length > 0) {
|
||||
// 如果当前编辑的不是默认地址,或者是新增地址,需要取消其他默认地址
|
||||
if (!isEditMode || (isEditMode && defaultAddress[0].id !== bankId)) {
|
||||
await updateShopDealerBank({
|
||||
...defaultAddress[0],
|
||||
isDefault: false
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 执行新增或更新操作
|
||||
if (isEditMode) {
|
||||
await updateShopDealerBank(submitData);
|
||||
} else {
|
||||
await addShopDealerBank(submitData);
|
||||
}
|
||||
|
||||
Taro.showToast({
|
||||
title: `${isEditMode ? '更新' : '保存'}成功`,
|
||||
icon: 'success'
|
||||
});
|
||||
|
||||
setTimeout(() => {
|
||||
Taro.navigateBack();
|
||||
}, 1000);
|
||||
|
||||
} catch (error) {
|
||||
console.error('保存失败:', error);
|
||||
Taro.showToast({
|
||||
title: `${isEditMode ? '更新' : '保存'}失败`,
|
||||
icon: 'error'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const submitFailed = (error: any) => {
|
||||
console.log(error, 'err...')
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
// 动态设置页面标题
|
||||
Taro.setNavigationBarTitle({
|
||||
title: isEditMode ? '编辑银行卡' : '添加银行卡'
|
||||
});
|
||||
|
||||
reload().then(() => {
|
||||
setLoading(false)
|
||||
})
|
||||
}, [isEditMode]);
|
||||
|
||||
if (loading) {
|
||||
return <Loading className={'px-2'}>加载中</Loading>
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Form
|
||||
ref={formRef}
|
||||
divider
|
||||
initialValues={FormData}
|
||||
labelPosition="left"
|
||||
onFinish={(values) => submitSucceed(values)}
|
||||
onFinishFailed={(errors) => submitFailed(errors)}
|
||||
>
|
||||
<CellGroup style={{padding: '4px 0'}}>
|
||||
<Form.Item name="bankName" label="开户行名称" initialValue={FormData?.bankName} required>
|
||||
<Input placeholder="开户行名称"/>
|
||||
</Form.Item>
|
||||
<Form.Item name="bankAccount" label="银行开户名" initialValue={FormData?.bankAccount} required>
|
||||
<Input placeholder="银行开户名"/>
|
||||
</Form.Item>
|
||||
<Form.Item name="bankCard" label="银行卡号" initialValue={FormData?.bankCard} required>
|
||||
<Input placeholder="银行卡号"/>
|
||||
</Form.Item>
|
||||
</CellGroup>
|
||||
</Form>
|
||||
|
||||
{/* 底部浮动按钮 */}
|
||||
<FixedButton text={isEditMode ? '更新地址' : '保存并使用'} onClick={() => formRef.current?.submit()}/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default AddUserAddress;
|
||||
@@ -1,132 +0,0 @@
|
||||
import {useState} from "react";
|
||||
import Taro, {useDidShow} from '@tarojs/taro'
|
||||
import {Button, Cell, Space, Empty, ConfigProvider} from '@nutui/nutui-react-taro'
|
||||
import {CheckNormal, Checked} from '@nutui/icons-react-taro'
|
||||
import {View} from '@tarojs/components'
|
||||
import {ShopDealerBank} from "@/api/shop/shopDealerBank/model";
|
||||
import {listShopDealerBank, removeShopDealerBank, updateShopDealerBank} from "@/api/shop/shopDealerBank";
|
||||
import FixedButton from "@/components/FixedButton";
|
||||
|
||||
const DealerBank = () => {
|
||||
const [list, setList] = useState<ShopDealerBank[]>([])
|
||||
const [bank, setAddress] = useState<ShopDealerBank>()
|
||||
|
||||
const reload = () => {
|
||||
listShopDealerBank({})
|
||||
.then(data => {
|
||||
setList(data || [])
|
||||
// 默认地址
|
||||
setAddress(data.find(item => item.isDefault))
|
||||
})
|
||||
.catch(() => {
|
||||
Taro.showToast({
|
||||
title: '获取地址失败',
|
||||
icon: 'error'
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
const onDefault = async (item: ShopDealerBank) => {
|
||||
if (bank) {
|
||||
await updateShopDealerBank({
|
||||
...bank,
|
||||
isDefault: false
|
||||
})
|
||||
}
|
||||
await updateShopDealerBank({
|
||||
id: item.id,
|
||||
isDefault: true
|
||||
})
|
||||
Taro.showToast({
|
||||
title: '设置成功',
|
||||
icon: 'success'
|
||||
});
|
||||
reload();
|
||||
}
|
||||
|
||||
const onDel = async (id?: number) => {
|
||||
await removeShopDealerBank(id)
|
||||
Taro.showToast({
|
||||
title: '删除成功',
|
||||
icon: 'success'
|
||||
});
|
||||
reload();
|
||||
}
|
||||
|
||||
const selectAddress = async (item: ShopDealerBank) => {
|
||||
if (bank) {
|
||||
await updateShopDealerBank({
|
||||
...bank,
|
||||
isDefault: false
|
||||
})
|
||||
}
|
||||
await updateShopDealerBank({
|
||||
id: item.id,
|
||||
isDefault: true
|
||||
})
|
||||
setTimeout(() => {
|
||||
Taro.navigateBack()
|
||||
}, 500)
|
||||
}
|
||||
|
||||
useDidShow(() => {
|
||||
reload()
|
||||
});
|
||||
|
||||
if (list.length == 0) {
|
||||
return (
|
||||
<ConfigProvider>
|
||||
<div className={'h-full flex flex-col justify-center items-center'} style={{
|
||||
height: 'calc(100vh - 300px)',
|
||||
}}>
|
||||
<Empty
|
||||
style={{
|
||||
backgroundColor: 'transparent'
|
||||
}}
|
||||
description="您还没有银行卡哦"
|
||||
/>
|
||||
<Space>
|
||||
<Button onClick={() => Taro.navigateTo({url: '/dealer/bank/add'})}>新增银行卡</Button>
|
||||
</Space>
|
||||
</div>
|
||||
</ConfigProvider>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<View className={'p-3'}>
|
||||
{list.map((item, _) => (
|
||||
<Cell.Group>
|
||||
<Cell className={'flex flex-col gap-1'} extra={item.bankAccount} onClick={() => selectAddress(item)}>
|
||||
<View>
|
||||
<View className={'font-medium text-sm'}>{item.bankName}</View>
|
||||
</View>
|
||||
<View className={'text-xs'}>
|
||||
{item.bankCard} {item.bankAccount}
|
||||
</View>
|
||||
</Cell>
|
||||
<Cell
|
||||
align="center"
|
||||
title={
|
||||
<View className={'flex items-center gap-1'} onClick={() => onDefault(item)}>
|
||||
{item.isDefault ? <Checked className={'text-green-600'} size={16}/> : <CheckNormal size={16}/>}
|
||||
<View className={'text-gray-400'}>选择</View>
|
||||
</View>
|
||||
}
|
||||
extra={
|
||||
<>
|
||||
<View className={'text-gray-400'} onClick={() => onDel(item.id)}>
|
||||
删除
|
||||
</View>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
</Cell.Group>
|
||||
))}
|
||||
{/* 底部浮动按钮 */}
|
||||
<FixedButton text={'新增银行卡'} onClick={() => Taro.navigateTo({url: '/dealer/bank/add'})} />
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
export default DealerBank;
|
||||
@@ -1,79 +0,0 @@
|
||||
import {useState, useEffect} from 'react'
|
||||
import {View, Text} from '@tarojs/components'
|
||||
import {Empty, Loading} from '@nutui/nutui-react-taro'
|
||||
import {useRouter} from '@tarojs/taro'
|
||||
import {getShopDealerCapital} from '@/api/shop/shopDealerCapital'
|
||||
import type {ShopDealerCapital} from '@/api/shop/shopDealerCapital/model'
|
||||
|
||||
const DealerCapitalDetail = () => {
|
||||
const {params} = useRouter();
|
||||
const [loading, setLoading] = useState<boolean>(false)
|
||||
const [item, setItem] = useState<ShopDealerCapital>()
|
||||
|
||||
// 获取订单数据
|
||||
const reload = async () => {
|
||||
const data = await getShopDealerCapital(Number(params.id))
|
||||
setItem(data)
|
||||
}
|
||||
|
||||
const getFlowType = (index?: number) => {
|
||||
if (index === 10) return '电费收益'
|
||||
if (index === 20) return '提现支出'
|
||||
if (index === 30) return '转账支出'
|
||||
if (index === 40) return '转账收入'
|
||||
if (index === 50) return '新注册奖励'
|
||||
return 'warning'
|
||||
}
|
||||
|
||||
// 初始化加载数据
|
||||
useEffect(() => {
|
||||
reload().then(() => {
|
||||
setLoading(true)
|
||||
})
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<View className="min-h-screen bg-gray-50">
|
||||
<View className="p-4">
|
||||
{loading && !item ? (
|
||||
<View className="text-center py-8">
|
||||
<Loading/>
|
||||
<Text className="text-gray-500 mt-2">加载中...</Text>
|
||||
</View>
|
||||
) : item ? (
|
||||
<View key={item.id} className="bg-white rounded-lg p-4 mb-3 shadow-sm">
|
||||
<View className="flex flex-col justify-center items-center py-8">
|
||||
<Text className="text-lg text-gray-300">
|
||||
{getFlowType(item.flowType)}
|
||||
</Text>
|
||||
<View className="text-4xl mt-1 font-semibold flex justify-start">
|
||||
<Text className={'subscript text-xl mt-1'}>¥</Text>
|
||||
<Text className={'text-4xl'}>{Number(item.money).toFixed(2)}</Text>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
<View className="flex flex-col justify-between mb-1">
|
||||
<Text className="text-sm my-1 text-gray-500">
|
||||
收益描述:{item.comments}
|
||||
</Text>
|
||||
{item.orderNo && (
|
||||
<Text className="text-sm my-1 text-gray-500">
|
||||
订单编号:{item.orderNo}
|
||||
</Text>
|
||||
)}
|
||||
<Text className="text-sm my-1 text-gray-500">
|
||||
创建时间:{item.createTime}
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
) : (
|
||||
<Empty description="账单不存在" style={{
|
||||
backgroundColor: 'transparent'
|
||||
}}/>
|
||||
)}
|
||||
</View>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
export default DealerCapitalDetail
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user