diff --git a/config/env.ts b/config/env.ts index 44ddb26..c92883b 100644 --- a/config/env.ts +++ b/config/env.ts @@ -2,7 +2,7 @@ export const ENV_CONFIG = { // 开发环境 development: { - API_BASE_URL: 'http://127.0.0.1:9200/api', + API_BASE_URL: 'https://cms-api.websoft.top/api', APP_NAME: '开发环境', DEBUG: 'true', }, diff --git a/src/admin/components/StoreCell.tsx b/src/admin/components/StoreCell.tsx new file mode 100644 index 0000000..9fd837e --- /dev/null +++ b/src/admin/components/StoreCell.tsx @@ -0,0 +1,150 @@ +import {Cell} from '@nutui/nutui-react-taro' +import navTo from "@/utils/common"; +import Taro from '@tarojs/taro' +import {View, Text} from '@tarojs/components' +import {ArrowRight, ShieldCheck, LogisticsError, Location, Reward, Tips, Ask, Setting, Scan} from '@nutui/icons-react-taro' +import {useUser} from '@/hooks/useUser' + +const UserCell = () => { + const {logoutUser, isCertified, hasRole, isAdmin} = useUser(); + + const onLogout = () => { + Taro.showModal({ + title: '提示', + content: '确定要退出登录吗?', + success: function (res) { + if (res.confirm) { + // 使用 useUser hook 的 logoutUser 方法 + logoutUser(); + Taro.reLaunch({ + url: '/pages/index/index' + }) + } + } + }) + } + + return ( + <> + + + 我的服务 + + }> + + + 门店核销 + + } + align="center" + extra={} + onClick={() => { + navTo('/user/wallet/index', true) + }} + /> + + + 我的钱包 + + } + align="center" + extra={} + onClick={() => { + navTo('/user/wallet/index', true) + }} + /> + + + 收货地址 + + } + align="center" + extra={} + onClick={() => { + navTo('/user/address/index', true) + }} + /> + + + 实名认证 + {isCertified() && ( + 已认证 + )} + + } + align="center" + extra={} + onClick={() => { + navTo('/user/userVerify/index', true) + }} + /> + + + 常见问题 + + } + align="center" + extra={} + onClick={() => { + navTo('/user/help/index') + }} + /> + + + 关于我们 + + } + align="center" + extra={} + onClick={() => { + navTo('/user/about/index') + }} + /> + + + 账号管理 + + }> + } + onClick={() => navTo('/user/profile/profile', true)} + /> + } + onClick={onLogout} + /> + + + + ) +} +export default UserCell diff --git a/src/admin/components/UserCard.tsx b/src/admin/components/UserCard.tsx new file mode 100644 index 0000000..132dc49 --- /dev/null +++ b/src/admin/components/UserCard.tsx @@ -0,0 +1,249 @@ +import {Button} from '@nutui/nutui-react-taro' +import {Avatar, Tag} from '@nutui/nutui-react-taro' +import {getUserInfo, getWxOpenId} from '@/api/layout'; +import Taro from '@tarojs/taro'; +import {useEffect, useState} from "react"; +import {User} from "@/api/system/user/model"; +import navTo from "@/utils/common"; +import {TenantId} from "@/config/app"; +import {getMyAvailableCoupons} from "@/api/shop/shopUserCoupon"; +import {useUser} from "@/hooks/useUser"; + +function UserCard() { + const {getDisplayName, getRoleName} = useUser(); + const [IsLogin, setIsLogin] = useState(false) + const [userInfo, setUserInfo] = useState() + const [couponCount, setCouponCount] = useState(0) + // const [pointsCount, setPointsCount] = useState(0) + const [giftCount, setGiftCount] = useState(0) + + useEffect(() => { + // Taro.getSetting:获取用户的当前设置。返回值中只会出现小程序已经向用户请求过的权限。 + Taro.getSetting({ + success: (res) => { + if (res.authSetting['scope.userInfo']) { + // 用户已经授权过,可以直接获取用户信息 + console.log('用户已经授权过,可以直接获取用户信息') + reload(); + } else { + // 用户未授权,需要弹出授权窗口 + console.log('用户未授权,需要弹出授权窗口') + showAuthModal(); + } + } + }); + }, []); + + const loadUserStats = (userId: number) => { + // 加载优惠券数量 + getMyAvailableCoupons() + .then((coupons: any) => { + setCouponCount(coupons?.length || 0) + }) + .catch((error: any) => { + console.error('Coupon count error:', error) + }) + + // 加载积分数量 + console.log(userId) + // getUserPointsStats(userId) + // .then((res: any) => { + // setPointsCount(res.currentPoints || 0) + // }) + // .catch((error: any) => { + // console.error('Points stats error:', error) + // }) + // 加载礼品劵数量 + setGiftCount(0) + // pageUserGiftLog({userId, page: 1, limit: 1}).then(res => { + // setGiftCount(res.count || 0) + // }) + } + + const reload = () => { + Taro.getUserInfo({ + success: (res) => { + const avatar = res.userInfo.avatarUrl; + setUserInfo({ + avatar, + nickname: res.userInfo.nickName, + sexName: res.userInfo.gender == 1 ? '男' : '女' + }) + getUserInfo().then((data) => { + if (data) { + setUserInfo(data) + setIsLogin(true); + Taro.setStorageSync('UserId', data.userId) + + // 加载用户统计数据 + if (data.userId) { + loadUserStats(data.userId) + } + + // 获取openId + if (!data.openid) { + Taro.login({ + success: (res) => { + getWxOpenId({code: res.code}).then(() => { + }) + } + }) + } + } + }).catch(() => { + console.log('未登录') + }); + } + }); + }; + + const showAuthModal = () => { + Taro.showModal({ + title: '授权提示', + content: '需要获取您的用户信息', + confirmText: '去授权', + cancelText: '取消', + success: (res) => { + if (res.confirm) { + // 用户点击确认,打开授权设置页面 + openSetting(); + } + } + }); + }; + + + const openSetting = () => { + // Taro.openSetting:调起客户端小程序设置界面,返回用户设置的操作结果。设置界面只会出现小程序已经向用户请求过的权限。 + Taro.openSetting({ + success: (res) => { + if (res.authSetting['scope.userInfo']) { + // 用户授权成功,可以获取用户信息 + reload(); + } else { + // 用户拒绝授权,提示授权失败 + Taro.showToast({ + title: '授权失败', + icon: 'none' + }); + } + } + }); + }; + + /* 获取用户手机号 */ + const handleGetPhoneNumber = ({detail}: {detail: {code?: string, encryptedData?: string, iv?: string}}) => { + const {code, encryptedData, iv} = detail + Taro.login({ + success: function () { + if (code) { + Taro.request({ + url: 'https://server.websoft.top/api/wx-login/loginByMpWxPhone', + method: 'POST', + data: { + code, + encryptedData, + iv, + notVerifyPhone: true, + refereeId: 0, + sceneType: 'save_referee', + tenantId: TenantId + }, + header: { + 'content-type': 'application/json', + TenantId + }, + success: function (res) { + if (res.data.code == 1) { + Taro.showToast({ + title: res.data.message, + icon: 'error', + duration: 2000 + }) + return false; + } + // 登录成功 + Taro.setStorageSync('access_token', res.data.data.access_token) + Taro.setStorageSync('UserId', res.data.data.user.userId) + setUserInfo(res.data.data.user) + setIsLogin(true) + } + }) + } else { + console.log('登录失败!') + } + } + }) + } + + return ( +
+
+
+
+
+ { + IsLogin ? ( + + ) : ( + + ) + } +
+
{getDisplayName()}
+ {IsLogin ? ( +
+ +
+ {getRoleName()} +
+
+
+ ) : ''} +
+
+
navTo('/user/profile/profile', true)}> + {'个人资料'} +
+
+
+
navTo('/user/wallet/wallet', true)}> + 余额 + ¥ {userInfo?.balance || '0.00'} +
+
navTo('/user/coupon/index', true)}> + 优惠券 + {couponCount} +
+
navTo('/user/gift/index', true)}> + 礼品卡 + {giftCount} +
+ {/*
*/} + {/* 积分*/} + {/* {pointsCount}*/} + {/*
*/} +
+
+
+
+ + ) +} + +export default UserCard; diff --git a/src/admin/components/UserCell.tsx b/src/admin/components/UserCell.tsx new file mode 100644 index 0000000..0928980 --- /dev/null +++ b/src/admin/components/UserCell.tsx @@ -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 ( + <> + + + {/*是否分销商*/} + {!hasRole('dealer') && !isAdmin() && ( + navTo('/dealer/index', true)}> + + 开通会员 + 享优惠 + + } + extra={} + /> + )} + + {/*是否管理员*/} + {isAdmin() && ( + navTo('/admin/article/index', true)}> + + 管理中心 + + } + extra={} + /> + )} + + + 我的服务 + + }> + + + 门店核销 + + } + align="center" + extra={} + onClick={() => { + navTo('/user/wallet/index', true) + }} + /> + + + 我的钱包 + + } + align="center" + extra={} + onClick={() => { + navTo('/user/wallet/index', true) + }} + /> + + + 收货地址 + + } + align="center" + extra={} + onClick={() => { + navTo('/user/address/index', true) + }} + /> + + + 实名认证 + {isCertified() && ( + 已认证 + )} + + } + align="center" + extra={} + onClick={() => { + navTo('/user/userVerify/index', true) + }} + /> + + + 常见问题 + + } + align="center" + extra={} + onClick={() => { + navTo('/user/help/index') + }} + /> + + + 关于我们 + + } + align="center" + extra={} + onClick={() => { + navTo('/user/about/index') + }} + /> + + + 账号管理 + + }> + } + onClick={() => navTo('/user/profile/profile', true)} + /> + } + onClick={onLogout} + /> + + + + ) +} +export default UserCell diff --git a/src/admin/components/UserFooter.tsx b/src/admin/components/UserFooter.tsx new file mode 100644 index 0000000..fb74b71 --- /dev/null +++ b/src/admin/components/UserFooter.tsx @@ -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(0) + const [FormData, setFormData] = useState( + { + 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 ( + <> +
+
当前版本:{Version}
+
Copyright © { new Date().getFullYear() } {Copyright}
+
+ + +
submitByPhone(values)} + footer={ +
+ +
+ } + > + + + + + + +
+
+ + ) +} +export default UserFooter diff --git a/src/admin/components/UserOrder.tsx b/src/admin/components/UserOrder.tsx new file mode 100644 index 0000000..92e94a4 --- /dev/null +++ b/src/admin/components/UserOrder.tsx @@ -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 ( + <> + + + + 我的订单 + navTo('/user/order/order', true)}> + 全部订单 + + + + + navTo('/user/order/order?statusFilter=0', true)}> + + 待付款 + + navTo('/user/order/order?statusFilter=1', true)}> + + 待发货 + + navTo('/user/order/order?statusFilter=3', true)}> + + 待收货 + + navTo('/user/order/order?statusFilter=5', true)}> + + 已完成 + + navTo('/user/order/order?statusFilter=6', true)}> + + 退货/售后 + + + + + + + ) +} + +export default UserOrder; diff --git a/src/admin/index.config.ts b/src/admin/index.config.ts new file mode 100644 index 0000000..3c73cba --- /dev/null +++ b/src/admin/index.config.ts @@ -0,0 +1,3 @@ +export default definePageConfig({ + navigationBarTitleText: '管理中心' +}) diff --git a/src/admin/index.tsx b/src/admin/index.tsx new file mode 100644 index 0000000..8fd127e --- /dev/null +++ b/src/admin/index.tsx @@ -0,0 +1,25 @@ +import {useEffect} from 'react' +import {useUser} from "@/hooks/useUser"; +import {Text} from '@tarojs/components'; + +function Admin() { + const { + isAdmin + } = useUser(); + + useEffect(() => { + }, []); + + if (!isAdmin()) { + return ( + 您不是管理员 + ); + } + return ( + <> + 待开发... + + ) +} + +export default Admin diff --git a/src/app.config.ts b/src/app.config.ts index a7641e1..8a884d7 100644 --- a/src/app.config.ts +++ b/src/app.config.ts @@ -68,6 +68,7 @@ export default defineAppConfig({ { "root": "admin", "pages": [ + "index", "article/index", ] } @@ -90,12 +91,12 @@ export default defineAppConfig({ selectedIconPath: "assets/tabbar/home-active.png", text: "首页", }, - { - pagePath: "pages/find/find", - iconPath: "assets/tabbar/find.png", - selectedIconPath: "assets/tabbar/find-active.png", - text: "发现", - }, + // { + // pagePath: "pages/find/find", + // iconPath: "assets/tabbar/find.png", + // selectedIconPath: "assets/tabbar/find-active.png", + // text: "发现", + // }, { pagePath: "pages/cart/cart", iconPath: "assets/tabbar/cart.png", diff --git a/src/components/SimpleQRCodeModal.tsx b/src/components/SimpleQRCodeModal.tsx index ccebbcd..b8fc29e 100644 --- a/src/components/SimpleQRCodeModal.tsx +++ b/src/components/SimpleQRCodeModal.tsx @@ -24,6 +24,20 @@ const SimpleQRCodeModal: React.FC = ({ faceValue }) => { + // const copyToClipboard = () => { + // if (qrContent) { + // Taro.setClipboardData({ + // data: qrContent, + // success: () => { + // Taro.showToast({ + // title: '兑换码已复制', + // icon: 'success' + // }) + // } + // }) + // } + // } + return ( = ({ > {/* 标题 */} - + 礼品卡二维码 - + 请向商家出示此二维码 @@ -64,13 +78,22 @@ const SimpleQRCodeModal: React.FC = ({ {/* 二维码区域 */} - - - 二维码 - ID: {qrContent ? qrContent.slice(-6) : '------'} + {qrContent ? ( + 二维码 + ) : ( + + + + 生成中... + - + )} diff --git a/src/pages/user/components/UserCard.tsx b/src/pages/user/components/UserCard.tsx index 0edd01d..7c1ae70 100644 --- a/src/pages/user/components/UserCard.tsx +++ b/src/pages/user/components/UserCard.tsx @@ -1,5 +1,6 @@ import {Button} from '@nutui/nutui-react-taro' import {Avatar, Tag} from '@nutui/nutui-react-taro' +import {Scan} from '@nutui/icons-react-taro'; import {getUserInfo, getWxOpenId} from '@/api/layout'; import Taro from '@tarojs/taro'; import {useEffect, useState} from "react"; @@ -10,6 +11,9 @@ import {getMyAvailableCoupons} from "@/api/shop/shopUserCoupon"; import {useUser} from "@/hooks/useUser"; function UserCard() { + const { + isAdmin + } = useUser(); const {getDisplayName, getRoleName} = useUser(); const [IsLogin, setIsLogin] = useState(false) const [userInfo, setUserInfo] = useState() @@ -46,6 +50,7 @@ function UserCard() { // 加载积分数量 console.log(userId) + setPointsCount(0) // getUserPointsStats(userId) // .then((res: any) => { // setPointsCount(res.currentPoints || 0) @@ -61,7 +66,6 @@ function UserCard() { } const reload = () => { - setPointsCount(0) Taro.getUserInfo({ success: (res) => { const avatar = res.userInfo.avatarUrl; @@ -214,7 +218,8 @@ function UserCard() { ) : ''} -
navTo('/user/store/verification', true)} />} +
navTo('/user/profile/profile', true)}> {'个人资料'}
@@ -223,7 +228,11 @@ function UserCard() {
navTo('/user/wallet/wallet', true)}> 余额 - ¥ {userInfo?.balance || '0.00'} + {userInfo?.balance || '0.00'} +
+
+ 积分 + {pointsCount}
navTo('/user/coupon/index', true)}> @@ -235,10 +244,6 @@ function UserCard() { 礼品卡 {giftCount}
-
- 积分 - {pointsCount} -
diff --git a/src/pages/user/user.tsx b/src/pages/user/user.tsx index a18bad0..f67404d 100644 --- a/src/pages/user/user.tsx +++ b/src/pages/user/user.tsx @@ -2,20 +2,42 @@ import {useEffect} from 'react' import UserCard from "./components/UserCard"; import UserOrder from "./components/UserOrder"; import UserCell from "./components/UserCell"; -import './user.scss' import UserFooter from "./components/UserFooter"; +import {useUser} from "@/hooks/useUser"; +import './user.scss' + function User() { + const { + isAdmin + } = useUser(); useEffect(() => { }, []); + + /** + * 门店核销管理 + */ + if (isAdmin()) { + return <> +
+ + + + +
+ + } + return ( <>
- - - + + +
diff --git a/src/user/store/verification.tsx b/src/user/store/verification.tsx index d144158..dc96d0b 100644 --- a/src/user/store/verification.tsx +++ b/src/user/store/verification.tsx @@ -6,15 +6,15 @@ import Taro from '@tarojs/taro' import dayjs from 'dayjs' import {getShopGiftByCode, updateShopGift} from "@/api/shop/shopGift"; -interface VerificationData { - type: string - giftId: number - giftCode: string - verificationCode: string - faceValue: string - timestamp: number - expireTime?: string -} +// interface VerificationData { +// type: string +// giftId: number +// giftCode: string +// verificationCode: string +// faceValue: string +// timestamp: number +// expireTime?: string +// } interface GiftCardInfo { id: number @@ -40,7 +40,8 @@ const StoreVerification: React.FC = () => { success: (res) => { console.log('扫码结果:', res.result) setScanResult(res.result) - parseQRCode(res.result) + verificationQRCode(res.result) + // parseQRCode(res.result) }, fail: (err) => { console.error('扫码失败:', err) @@ -53,41 +54,57 @@ const StoreVerification: React.FC = () => { } // 解析二维码数据 - const parseQRCode = (qrData: string) => { - try { - const data: VerificationData = JSON.parse(qrData) + // const parseQRCode = (qrData: string) => { + // try { + // const data: VerificationData = JSON.parse(qrData) + // + // if (data.type === 'gift_card_verification') { + // setVerificationCode(data.verificationCode) + // console.log(data.verificationCode,'...vaerrr') + // } else { + // throw new Error('无效的二维码格式') + // } + // } catch (error) { + // console.error('解析二维码失败:', error) + // Taro.showToast({ + // title: '无效的二维码', + // icon: 'error' + // }) + // } + // } - if (data.type === 'gift_card_verification') { - setVerificationCode(data.verificationCode) - // 模拟获取礼品卡信息 - mockGetGiftInfo(data) - } else { - throw new Error('无效的二维码格式') - } - } catch (error) { - console.error('解析二维码失败:', error) - Taro.showToast({ - title: '无效的二维码', + // 扫码核销操作 + const verificationQRCode = async (code:string) => { + const gift = await getShopGiftByCode(code) + + if(gift.status == 1){ + return Taro.showToast({ + title: '此礼品码已使用', icon: 'error' }) } - } - - // 模拟获取礼品卡信息(实际应该调用API) - const mockGetGiftInfo = (data: VerificationData) => { - // 这里应该调用后端API验证礼品卡信息 - const mockGiftInfo: GiftCardInfo = { - id: data.giftId, - name: '礼品卡', - goodsName: '星巴克咖啡券', - faceValue: data.faceValue, - type: 20, - status: 0, - expireTime: data.expireTime, - code: data.giftCode + if(gift.status == 2){ + return Taro.showToast({ + title: '此礼品码已失效', + icon: 'error' + }) } - - setGiftInfo(mockGiftInfo) + if(gift.userId == 0){ + return Taro.showToast({ + title: '此礼品码未认领', + icon: 'error' + }) + } + updateShopGift({ + ...gift, + status: 1, + takeTime: dayjs.unix(Date.now() / 1000).format('YYYY-MM-DD HH:mm:ss') + }).then(() => { + Taro.showToast({ + title: '核销成功', + icon: 'success' + }) + }) } // 手动输入核销码验证 @@ -106,7 +123,8 @@ const StoreVerification: React.FC = () => { const giftCard = await getShopGiftByCode(verificationCode.trim()) await updateShopGift({ ...giftCard, - status: 1 + status: 1, + takeTime: dayjs.unix(Date.now() / 1000).format('YYYY-MM-DD HH:mm:ss') }) Taro.showToast({ title: '核销成功',