From b288502e0ec62288671e5df42e2ca8d59209f6a5 Mon Sep 17 00:00:00 2001 From: liu <2302563948@qq.com> Date: Mon, 10 Aug 2026 10:09:30 +0800 Subject: [PATCH] =?UTF-8?q?=E9=97=A8=E5=BA=97=E4=BF=A1=E6=81=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app.config.ts | 2 + src/pages/cart/index.less | 89 ++++- src/pages/cart/index.tsx | 68 +++- src/pages/login/index.less | 19 + src/pages/login/index.tsx | 112 ++---- src/pages/profile/index.tsx | 18 +- src/pages/register/index.config.ts | 4 + src/pages/register/index.less | 196 ++++++++++ src/pages/register/index.tsx | 222 +++++++++++ src/pages/settings/index.tsx | 90 +---- src/pages/store-info/index.config.ts | 3 + src/pages/store-info/index.less | 87 +++++ src/pages/store-info/index.tsx | 174 +++++++++ src/services/auth.ts | 18 +- src/services/store.ts | 13 +- src/stores/auth/useAuthStore.ts | 40 +- src/types/store.ts | 23 ++ src/types/user.ts | 26 +- 小程序端API文档.md | 545 --------------------------- 19 files changed, 1001 insertions(+), 748 deletions(-) create mode 100644 src/pages/register/index.config.ts create mode 100644 src/pages/register/index.less create mode 100644 src/pages/register/index.tsx create mode 100644 src/pages/store-info/index.config.ts create mode 100644 src/pages/store-info/index.less create mode 100644 src/pages/store-info/index.tsx create mode 100644 src/types/store.ts delete mode 100644 小程序端API文档.md diff --git a/src/app.config.ts b/src/app.config.ts index 18d4310..1b0503d 100644 --- a/src/app.config.ts +++ b/src/app.config.ts @@ -9,6 +9,8 @@ export default defineAppConfig({ 'pages/statement/index', 'pages/settings/index', 'pages/login/index', + 'pages/register/index', + 'pages/store-info/index', ], window: { backgroundTextStyle: 'light', diff --git a/src/pages/cart/index.less b/src/pages/cart/index.less index 236d68f..f26b6ec 100644 --- a/src/pages/cart/index.less +++ b/src/pages/cart/index.less @@ -184,7 +184,7 @@ // ===== 下单确认弹层 ===== .order-popup { - padding: 32rpx 32rpx 24rpx; + padding: 32rpx 32rpx 130rpx; &__title { font-size: 34rpx; @@ -192,8 +192,78 @@ display: block; } + // --- 配送信息 --- + &__delivery { + margin-top: 24rpx; + display: flex; + align-items: center; + background: #f7f8fa; + border-radius: 12rpx; + padding: 20rpx 24rpx; + } + + &__delivery-icon { + flex-shrink: 0; + margin-right: 16rpx; + } + + &__delivery-info { + flex: 1; + min-width: 0; + } + + &__delivery-head { + display: flex; + align-items: baseline; + } + + &__delivery-name { + font-size: 28rpx; + color: #323233; + font-weight: 600; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + + &__delivery-contact { + margin-left: 16rpx; + font-size: 24rpx; + color: #646566; + flex-shrink: 0; + } + + &__delivery-address { + margin-top: 8rpx; + font-size: 24rpx; + color: #969799; + display: -webkit-box; + -webkit-box-orient: vertical; + -webkit-line-clamp: 2; + overflow: hidden; + } + + &__delivery-warn { + margin-top: 8rpx; + font-size: 24rpx; + color: #ee0a24; + } + + &__delivery-tip { + flex: 1; + font-size: 26rpx; + color: #969799; + } + + &__delivery-arrow { + margin-left: 16rpx; + font-size: 32rpx; + color: #c8c9cc; + flex-shrink: 0; + } + &__list { - max-height: 480rpx; + max-height: 400rpx; margin-top: 20rpx; } @@ -208,6 +278,16 @@ flex: 1; min-width: 0; display: flex; + } + + &-image { + width: 80rpx; + height: 80rpx; + margin-right: 10rpx; + } + + &-title { + display: flex; flex-direction: column; } @@ -256,7 +336,7 @@ &__remark-input { margin-top: 12rpx; width: 100%; - min-height: 120rpx; + height: 80rpx; background: #f7f8fa; border-radius: 12rpx; padding: 16rpx; @@ -274,13 +354,12 @@ &__total { font-size: 28rpx; color: #323233; - - // 金额颜色由内部元素控制 } &__submit { width: 280rpx; border-radius: 999rpx; + margin: 0; } } } diff --git a/src/pages/cart/index.tsx b/src/pages/cart/index.tsx index 02be2e0..0db53b3 100644 --- a/src/pages/cart/index.tsx +++ b/src/pages/cart/index.tsx @@ -1,10 +1,12 @@ import { useCallback, useEffect, useRef, useState } from 'react' import Taro, { useDidShow } from '@tarojs/taro' import { View, Text, Image, Textarea, ScrollView } from '@tarojs/components' -import { Button, Empty, Popup, Stepper } from '@antmjs/vantui' +import { Button, Empty, Icon, Popup, Stepper } from '@antmjs/vantui' import useCartStore from '@/stores/cart/useCartStore' import { createOrderApi } from '@/services/order' +import { getStoreInfoApi } from '@/services/store' import type { CartItem } from '@/types/cart' +import type { StoreDetail } from '@/types/store' import './index.less' export default function CartPage() { @@ -27,12 +29,27 @@ export default function CartPage() { const [remark, setRemark] = useState('') const [submitting, setSubmitting] = useState(false) + /** 配送信息(下单弹层展示,打开时拉取) */ + const [storeInfo, setStoreInfo] = useState(null) + const [storeLoading, setStoreLoading] = useState(false) + /** 可购项(status=1) */ const purchasable = items.filter(item => item.status === 1) const hasInvalid = items.length > 0 && purchasable.length < items.length + /** 拉取门店配送信息 */ + const fetchStoreInfo = useCallback(() => { + setStoreLoading(true) + getStoreInfoApi() + .then(res => setStoreInfo(res.data)) + .catch(() => setStoreInfo(null)) + .finally(() => setStoreLoading(false)) + }, []) + useDidShow(() => { fetchCart().catch(() => {}) + // 从门店信息编辑页返回且弹层仍打开时 → 刷新配送信息 + if (showOrder) fetchStoreInfo() }) /** 同步本地编辑数量:删除不存在的项,保留在编数量 */ @@ -106,14 +123,24 @@ export default function CartPage() { }) }, [clearCart]) - /** 打开下单弹层 */ + /** 打开下单弹层(同时拉取配送信息) */ const handleOrderTap = useCallback(() => { if (!purchasable.length) { Taro.showToast({ title: '没有可购买的商品', icon: 'none' }) return } setShowOrder(true) - }, [purchasable.length]) + fetchStoreInfo() + }, [purchasable.length, fetchStoreInfo]) + + /** 配送信息点击:拉取失败时重试,否则前往门店信息编辑页 */ + const handleDeliveryTap = useCallback(() => { + if (!storeLoading && !storeInfo) { + fetchStoreInfo() + return + } + Taro.navigateTo({ url: '/pages/store-info/index' }) + }, [storeLoading, storeInfo, fetchStoreInfo]) /** 提交订单(金额一律服务端重算) */ const handleSubmitOrder = useCallback(async () => { @@ -240,12 +267,43 @@ export default function CartPage() { > 确认下单 + + {/* 配送信息(点击前往修改门店信息) */} + + + {storeLoading ? ( + 配送信息加载中... + ) : storeInfo ? ( + + + {storeInfo.name} + {(storeInfo.contact || storeInfo.phone) && ( + + {storeInfo.contact} {storeInfo.phone} + + )} + + {storeInfo.address ? ( + {storeInfo.address} + ) : ( + 收货地址未填写,点击完善配送信息 + )} + + ) : ( + 未获取到门店信息,点击重试 + )} + + + {purchasable.map(item => ( - {item.name} - {item.spec} / {item.unit} + + + {item.name} + {item.spec} / {item.unit} + ×{displayQty(item)} diff --git a/src/pages/login/index.less b/src/pages/login/index.less index a85b587..d040dd0 100644 --- a/src/pages/login/index.less +++ b/src/pages/login/index.less @@ -144,6 +144,25 @@ opacity: 0.75; } +/* ========== 去注册入口 ========== */ +.login-switch { + display: flex; + align-items: center; + justify-content: center; + margin-top: 32px; + + .switch-text { + font-size: 28px; + color: #969799; + } + + .switch-link { + font-size: 28px; + color: #1989fa; + margin-left: 8px; + } +} + /* ========== 协议文字 ========== */ .login-agreement { display: flex; diff --git a/src/pages/login/index.tsx b/src/pages/login/index.tsx index 37e88a4..617b3e8 100644 --- a/src/pages/login/index.tsx +++ b/src/pages/login/index.tsx @@ -6,15 +6,10 @@ import useAuthStore from '@/stores/auth/useAuthStore' import './index.less' export default function LoginPage() { - const user = useAuthStore(s => s.user) const login = useAuthStore(s => s.login) - const bindPhone = useAuthStore(s => s.bindPhone) const isLoggedIn = useAuthStore(s => !!s.token && !!s.user) const [submitting, setSubmitting] = useState(false) - const [binding, setBinding] = useState(false) - /** 登录成功但身份待绑定(type=0)时,引导绑定手机号 */ - const [needsBind, setNeedsBind] = useState(false) const isWeb = Taro.getEnv() === Taro.ENV_TYPE.WEB @@ -28,17 +23,17 @@ export default function LoginPage() { } }, []) - /** 已登录且已绑定身份 → 返回;已登录未绑定 → 引导绑定手机号 */ - useEffect(() => { - if (!isLoggedIn) return - if (user && user.type === 0) { - setNeedsBind(true) - } else { - goBack() - } - }, [isLoggedIn, user, goBack]) + /** 前往注册页 */ + const goRegister = useCallback(() => { + Taro.navigateTo({ url: '/pages/register/index' }) + }, []) - /** 微信一键登录(wx.login code 换 openid,新用户自动注册) */ + /** 已登录 → 自动返回 */ + useEffect(() => { + if (isLoggedIn) goBack() + }, [isLoggedIn, goBack]) + + /** 微信一键登录(wx.login code 换 openid,仅已注册用户可登录) */ const handleLogin = useCallback(async () => { if (submitting) return // H5 环境无法获取微信登录凭证 @@ -54,45 +49,24 @@ export default function LoginPage() { return } await login({ code: res.code }) - // 登录结果(type=0 → needsBind,否则自动返回)由 effect 处理 - } catch { - // 业务/网络错误已由 request 层提示 + // 登录成功后由 effect 自动返回 + } catch (e: any) { + // 未注册用户:引导前往注册页(其余错误已由 request 层提示) + if (typeof e?.msg === 'string' && e.msg.includes('用户不存在')) { + Taro.showModal({ + title: '未注册', + content: '该微信账号尚未注册,需授权手机号并填写门店编码完成注册', + confirmText: '去注册', + cancelText: '取消', + success: res => { + if (res.confirm) goRegister() + }, + }) + } } finally { setSubmitting(false) } - }, [login, submitting, isWeb]) - - /** 微信手机号授权绑定(自动匹配门店/供应商) */ - const handleGetPhoneNumber = useCallback( - async (e: any) => { - if (binding) return - - const detail = e.detail || {} - - // 用户拒绝授权 - if (detail.errMsg && !detail.errMsg.includes(':ok')) { - Taro.showToast({ title: '需要授权手机号才能绑定', icon: 'none' }) - return - } - - if (!detail.code) { - Taro.showToast({ title: '未获取到手机号授权凭证', icon: 'none' }) - return - } - - setBinding(true) - try { - await bindPhone(detail.code) - Taro.showToast({ title: '绑定成功', icon: 'success' }) - // 绑定成功后 type 更新,由 effect 自动返回 - } catch { - // 业务/网络错误已由 request 层提示 - } finally { - setBinding(false) - } - }, - [binding, bindPhone], - ) + }, [login, submitting, isWeb, goRegister]) /** 查看用户协议 */ const handleShowAgreement = useCallback(() => { @@ -107,7 +81,7 @@ export default function LoginPage() { return ( {/* ========== 导航栏 ========== */} - + {/* ========== 内容区域 ========== */} @@ -127,26 +101,20 @@ export default function LoginPage() { {/* 登录操作 */} - {needsBind ? ( - - ) : ( - - )} + + + {/* 未注册用户入口 */} + + 还没有账号? + 立即注册 + 登录即代表同意 diff --git a/src/pages/profile/index.tsx b/src/pages/profile/index.tsx index 1541a7d..1b634b1 100644 --- a/src/pages/profile/index.tsx +++ b/src/pages/profile/index.tsx @@ -1,4 +1,4 @@ -import { useCallback } from 'react' +import { useCallback, useMemo } from 'react' import Taro, { useDidShow } from '@tarojs/taro' import { View, Text, Image } from '@tarojs/components' import { Icon } from '@antmjs/vantui' @@ -45,6 +45,20 @@ export default function ProfilePage() { const loggedIn = !!token && !!user + /** 功能菜单(门店账号追加「门店信息」入口) */ + const menuItems = useMemo(() => { + if (!user?.store) return MENU_ITEMS + return [ + { + key: 'store-info', + label: '门店信息', + icon: 'shop-o', + onClick: () => Taro.navigateTo({ url: '/pages/store-info/index' }), + }, + ...MENU_ITEMS, + ] + }, [user?.store]) + useDidShow(() => { if (!loggedIn) return // 刷新用户信息(门店/客户等级可能变化) @@ -157,7 +171,7 @@ export default function ProfilePage() { {/* ========== 功能菜单 ========== */} - {MENU_ITEMS.map(item => ( + {menuItems.map(item => ( diff --git a/src/pages/register/index.config.ts b/src/pages/register/index.config.ts new file mode 100644 index 0000000..0a728e8 --- /dev/null +++ b/src/pages/register/index.config.ts @@ -0,0 +1,4 @@ +export default definePageConfig({ + navigationStyle: 'custom', + navigationBarTitleText: '注册', +}) diff --git a/src/pages/register/index.less b/src/pages/register/index.less new file mode 100644 index 0000000..ff9765c --- /dev/null +++ b/src/pages/register/index.less @@ -0,0 +1,196 @@ +/* ======================================== + 注册页面 + ======================================== */ + +.register-page { + min-height: 100vh; + background: #fff; +} + +/* ========== 内容区域 ========== */ +.register-content { + display: flex; + flex-direction: column; + align-items: center; + padding: 80px 60px 0; +} + +/* ========== 品牌区域 ========== */ +.register-brand { + display: flex; + flex-direction: column; + align-items: center; + margin-bottom: 60px; + + .logo-wrapper { + width: 160px; + height: 160px; + border-radius: 50%; + background: linear-gradient(160deg, #1989fa 0%, #07c160 100%); + display: flex; + align-items: center; + justify-content: center; + margin-bottom: 24px; + box-shadow: 0 8px 32px rgba(25, 137, 250, 0.3); + } + + .logo-text { + font-size: 80px; + color: #fff; + font-weight: 700; + } + + .app-name { + font-size: 44px; + font-weight: 600; + color: #323233; + margin-bottom: 12px; + } + + .app-slogan { + font-size: 28px; + color: #969799; + } +} + +/* ========== 注册表单 ========== */ +.register-form { + width: 100%; + background: #f7f8fa; + border-radius: 24px; + padding: 0 32px; + margin-bottom: 80px; +} + +.form-item { + display: flex; + align-items: center; + height: 112px; + border-bottom: 1px solid #ebedf0; + + &:last-child { + border-bottom: none; + } +} + +.form-label { + width: 160px; + font-size: 30px; + color: #323233; + flex-shrink: 0; +} + +.form-input { + flex: 1; + height: 100%; + font-size: 30px; + color: #323233; +} + +.form-input-placeholder { + color: #c8c9cc; +} + +/* 手机号已授权状态 */ +.form-phone-ok { + flex: 1; + display: flex; + align-items: center; + + .form-phone-ok__text { + font-size: 30px; + color: #07c160; + } +} + +/* 手机号授权按钮(微信原生 Button 需重置样式) */ +.phone-auth-btn { + flex: 1; + height: 64px; + line-height: 64px; + background: #e8f7ef; + color: #07c160; + font-size: 28px; + font-weight: 500; + border: none; + border-radius: 32px; + text-align: center; + padding: 0 32px; + + /* 重置微信 Button 默认样式 */ + &::after { + border: none; + } +} + +/* ========== 注册操作区 ========== */ +.register-actions { + width: 100%; + display: flex; + flex-direction: column; + align-items: center; +} + +.register-btn { + width: 100%; + height: 96px; + line-height: 96px; + background: linear-gradient(160deg, #1989fa 0%, #07c160 100%); + color: #fff; + font-size: 34px; + font-weight: 500; + border: none; + border-radius: 48px; + text-align: center; + padding: 0; + box-shadow: 0 6px 24px rgba(25, 137, 250, 0.35); + transition: opacity 0.2s; + + /* 重置微信 Button 默认样式 */ + &::after { + border: none; + } +} + +.register-btn--loading { + opacity: 0.75; +} + +/* ========== 去登录入口 ========== */ +.register-switch { + display: flex; + align-items: center; + justify-content: center; + margin-top: 32px; + + .switch-text { + font-size: 28px; + color: #969799; + } + + .switch-link { + font-size: 28px; + color: #1989fa; + margin-left: 8px; + } +} + +/* ========== 协议文字 ========== */ +.register-agreement { + display: flex; + align-items: center; + justify-content: center; + flex-wrap: wrap; + margin-top: 32px; + line-height: 1.6; + + .agree-text { + font-size: 24px; + color: #c8c9cc; + } + + .agree-link { + font-size: 24px; + color: #1989fa; + } +} diff --git a/src/pages/register/index.tsx b/src/pages/register/index.tsx new file mode 100644 index 0000000..08d4b79 --- /dev/null +++ b/src/pages/register/index.tsx @@ -0,0 +1,222 @@ +import { useCallback, useEffect, useState } from 'react' +import Taro from '@tarojs/taro' +import { View, Text, Button, Input } from '@tarojs/components' +import CustomNavBar from '@/components/NavBar' +import useAuthStore from '@/stores/auth/useAuthStore' +import './index.less' + +export default function RegisterPage() { + const register = useAuthStore(s => s.register) + const isLoggedIn = useAuthStore(s => !!s.token && !!s.user) + + /** 门店编码(后台门店管理维护) */ + const [storeCode, setStoreCode] = useState('') + /** 微信手机号授权得到的 code */ + const [phoneCode, setPhoneCode] = useState('') + const [submitting, setSubmitting] = useState(false) + + const isWeb = Taro.getEnv() === Taro.ENV_TYPE.WEB + + /** 返回上一页(无页面栈时回首页) */ + const goBack = useCallback(() => { + const pages = Taro.getCurrentPages() + if (pages.length > 1) { + Taro.navigateBack() + } else { + Taro.switchTab({ url: '/pages/index/index' }) + } + }, []) + + /** 前往登录页 */ + const goLogin = useCallback(() => { + const pages = Taro.getCurrentPages() + if (pages.length > 1) { + Taro.navigateBack() + } else { + Taro.navigateTo({ url: '/pages/login/index' }) + } + }, []) + + /** 已注册成功(登录态就绪)→ 自动返回 */ + useEffect(() => { + if (isLoggedIn) goBack() + }, [isLoggedIn, goBack]) + + /** 发起注册:wx.login 换 code → POST /mini/auth/register */ + const doRegister = useCallback( + async (phoneCodeValue: string) => { + if (submitting) return + if (isWeb) { + Taro.showToast({ title: '请在微信小程序中注册', icon: 'none' }) + return + } + const code = storeCode.trim() + if (!code) { + Taro.showToast({ title: '请填写门店编码', icon: 'none' }) + return + } + setSubmitting(true) + try { + const res = await Taro.login() + if (!res.code) { + Taro.showToast({ title: '获取登录凭证失败', icon: 'none' }) + return + } + await register({ code: res.code, phoneCode: phoneCodeValue, storeCode: code }) + // 注册成功后由 effect 自动返回 + } catch (e: any) { + // 该微信已注册:引导前往登录(其余错误已由 request 层提示) + if (typeof e?.msg === 'string' && e.msg.includes('已经注册')) { + Taro.showModal({ + title: '提示', + content: '该微信已经注册,请直接登录', + confirmText: '去登录', + cancelText: '取消', + success: res => { + if (res.confirm) goLogin() + }, + }) + } + } finally { + setSubmitting(false) + } + }, + [register, submitting, isWeb, storeCode, goLogin], + ) + + /** 微信手机号授权(openType getPhoneNumber) */ + const handleGetPhoneNumber = useCallback( + (e: any) => { + if (isWeb) { + Taro.showToast({ title: '请在微信小程序中授权手机号', icon: 'none' }) + return + } + const detail = e.detail || {} + + // 用户拒绝授权 + if (detail.errMsg && !detail.errMsg.includes(':ok')) { + Taro.showToast({ title: '需要授权手机号才能注册', icon: 'none' }) + return + } + + if (!detail.code) { + Taro.showToast({ title: '未获取到手机号授权凭证', icon: 'none' }) + return + } + + setPhoneCode(detail.code) + if (storeCode.trim()) { + // 门店编码已填 → 直接发起注册 + doRegister(detail.code) + } else { + Taro.showToast({ title: '手机号已授权,请填写门店编码', icon: 'none' }) + } + }, + [isWeb, storeCode, doRegister], + ) + + /** 点击注册按钮(门店编码已填 + 手机号已授权) */ + const handleSubmit = useCallback(() => { + if (!phoneCode) { + Taro.showToast({ title: '请先授权手机号', icon: 'none' }) + return + } + if (!storeCode.trim()) { + Taro.showToast({ title: '请填写门店编码', icon: 'none' }) + return + } + doRegister(phoneCode) + }, [phoneCode, storeCode, doRegister]) + + /** 查看用户协议 */ + const handleShowAgreement = useCallback(() => { + Taro.showToast({ title: '用户协议即将上线', icon: 'none' }) + }, []) + + /** 查看隐私政策 */ + const handleShowPrivacy = useCallback(() => { + Taro.showToast({ title: '隐私政策即将上线', icon: 'none' }) + }, []) + + return ( + + {/* ========== 导航栏 ========== */} + + + {/* ========== 内容区域 ========== */} + + {/* 品牌区域 */} + + + + + 订货采购 + 注册即绑定门店,开启订货之旅 + + + {/* 注册表单 */} + + {/* 门店编码 */} + + 门店编码 + setStoreCode(e.detail.value)} + /> + + + {/* 手机号授权 */} + + 手机号 + {phoneCode ? ( + + 已授权 + + ) : ( + + )} + + + + {/* 注册操作 */} + + + + {/* 已注册用户入口 */} + + 已有账号? + 去登录 + + + + 注册即代表同意 + + 《用户协议》 + + + + 《隐私政策》 + + + + + + ) +} diff --git a/src/pages/settings/index.tsx b/src/pages/settings/index.tsx index 05855bf..0164d74 100644 --- a/src/pages/settings/index.tsx +++ b/src/pages/settings/index.tsx @@ -1,9 +1,6 @@ -import { useCallback, useState } from 'react' -import Taro, { useDidShow } from '@tarojs/taro' -import { View, Text, Input } from '@tarojs/components' -import { Button, Popup } from '@antmjs/vantui' -import useAuthStore from '@/stores/auth/useAuthStore' -import { updatePaymentCycleApi } from '@/services/store' +import { useCallback } from 'react' +import Taro from '@tarojs/taro' +import { View, Text } from '@tarojs/components' import './index.less' /** @@ -11,38 +8,6 @@ import './index.less' * 门店设置:回款周期;后续在此追加更多设置项 */ export default function SettingsPage() { - const token = useAuthStore(s => s.token) - - /** 回款周期弹层 */ - const [showCycle, setShowCycle] = useState(false) - const [cycleInput, setCycleInput] = useState('1') - const [cycleLoading, setCycleLoading] = useState(false) - - const loggedIn = !!token - - useDidShow(() => { - // 预留:进入设置页时刷新门店设置 - }) - - /** 保存回款周期 */ - const handleSaveCycle = useCallback(async () => { - const days = Number(cycleInput) - if (!Number.isInteger(days) || days < 0) { - Taro.showToast({ title: '请输入不小于 0 的整数', icon: 'none' }) - return - } - if (cycleLoading) return - setCycleLoading(true) - try { - await updatePaymentCycleApi(days) - Taro.showToast({ title: '回款周期已更新', icon: 'success' }) - setShowCycle(false) - } catch { - // 错误已由 request 层 toast - } finally { - setCycleLoading(false) - } - }, [cycleInput, cycleLoading]) /** 占位菜单:后续单独页面开发 */ const handlePlaceholder = useCallback((label: string) => { @@ -51,22 +16,6 @@ export default function SettingsPage() { return ( - {/* ========== 门店设置 ========== */} - - - 门店设置 - - setShowCycle(true)}> - 回款周期(天) - 设置 › - - handlePlaceholder('收货信息')}> - 收货信息 - - - - - {/* ========== 通用 ========== */} handlePlaceholder('清除缓存')}> 清除缓存 @@ -85,39 +34,6 @@ export default function SettingsPage() { - - {/* ========== 回款周期弹层 ========== */} - setShowCycle(false)} - > - - 修改回款周期 - 0 = 当天结算;该值影响后续生成对账单的应结算日期 - setCycleInput(e.detail.value)} - /> - - - ) } diff --git a/src/pages/store-info/index.config.ts b/src/pages/store-info/index.config.ts new file mode 100644 index 0000000..6d13f72 --- /dev/null +++ b/src/pages/store-info/index.config.ts @@ -0,0 +1,3 @@ +export default definePageConfig({ + navigationBarTitleText: '门店信息', +}) diff --git a/src/pages/store-info/index.less b/src/pages/store-info/index.less new file mode 100644 index 0000000..f82381c --- /dev/null +++ b/src/pages/store-info/index.less @@ -0,0 +1,87 @@ +.store-info-page { + min-height: 100vh; + background: #f7f8fa; + padding: 20rpx 24rpx 60rpx; + box-sizing: border-box; + + // ===== 区块 ===== + .store-section { + background: #fff; + border-radius: 20rpx; + padding: 8rpx 28rpx; + margin-bottom: 20rpx; + + &__header { + padding: 24rpx 0 8rpx; + border-bottom: 1rpx solid #f2f3f5; + } + + &__title { + font-size: 30rpx; + font-weight: 600; + } + } + + // ===== 表单行 ===== + .store-field { + display: flex; + align-items: center; + padding: 28rpx 0; + border-bottom: 1rpx solid #f2f3f5; + + &:last-child { + border-bottom: none; + } + + &--textarea { + align-items: flex-start; + } + + &__label { + width: 160rpx; + flex-shrink: 0; + font-size: 28rpx; + color: #323233; + } + + &__value { + flex: 1; + font-size: 28rpx; + color: #646566; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + + &__input { + flex: 1; + font-size: 28rpx; + color: #323233; + } + + &__textarea { + flex: 1; + min-height: 120rpx; + font-size: 28rpx; + color: #323233; + line-height: 1.5; + } + + &__placeholder { + color: #c8c9cc; + } + } + + // ===== 保存按钮 ===== + .store-submit { + margin-top: 40rpx; + } + + // ===== 加载 / 异常占位 ===== + .store-placeholder { + padding: 160rpx 0; + text-align: center; + font-size: 28rpx; + color: #969799; + } +} diff --git a/src/pages/store-info/index.tsx b/src/pages/store-info/index.tsx new file mode 100644 index 0000000..f110885 --- /dev/null +++ b/src/pages/store-info/index.tsx @@ -0,0 +1,174 @@ +import { useCallback, useState } from 'react' +import Taro, { useDidShow } from '@tarojs/taro' +import { View, Text, Input, Textarea } from '@tarojs/components' +import { Button } from '@antmjs/vantui' +import useAuthStore from '@/stores/auth/useAuthStore' +import { getStoreInfoApi, updateStoreInfoApi } from '@/services/store' +import type { StoreDetail } from '@/types/store' +import './index.less' + +/** 字段长度限制(与后端一致) */ +const LIMITS = { + contact: 50, + phone: 20, + address: 255, +} as const + +/** + * 门店信息编辑页 + * 名称 / 编码 / 回款周期为只读(后台维护),仅支持修改联系人 / 电话 / 地址 + */ +export default function StoreInfoPage() { + const token = useAuthStore(s => s.token) + + /** 门店详情(含只读字段) */ + const [detail, setDetail] = useState(null) + const [loading, setLoading] = useState(true) + + /** 可编辑字段 */ + const [contact, setContact] = useState('') + const [phone, setPhone] = useState('') + const [address, setAddress] = useState('') + const [saving, setSaving] = useState(false) + + const loggedIn = !!token + + /** 拉取门店详情并回显 */ + const fetchDetail = useCallback(() => { + setLoading(true) + getStoreInfoApi() + .then(res => { + setDetail(res.data) + setContact(res.data.contact || '') + setPhone(res.data.phone || '') + setAddress(res.data.address || '') + }) + .catch(() => { + setDetail(null) + }) + .finally(() => setLoading(false)) + }, []) + + useDidShow(() => { + if (!loggedIn) return + fetchDetail() + }) + + /** 保存:仅提交白名单字段(联系人 / 电话 / 地址) */ + const handleSave = useCallback(async () => { + if (saving) return + if (contact.length > LIMITS.contact) { + Taro.showToast({ title: `联系人不能超过 ${LIMITS.contact} 个字符`, icon: 'none' }) + return + } + if (phone.length > LIMITS.phone) { + Taro.showToast({ title: `联系电话不能超过 ${LIMITS.phone} 个字符`, icon: 'none' }) + return + } + if (address.length > LIMITS.address) { + Taro.showToast({ title: `地址不能超过 ${LIMITS.address} 个字符`, icon: 'none' }) + return + } + setSaving(true) + try { + await updateStoreInfoApi({ + contact: contact.trim(), + phone: phone.trim(), + address: address.trim(), + }) + Taro.showToast({ title: '门店信息已更新', icon: 'success' }) + } catch { + // 错误已由 request 层 toast + } finally { + setSaving(false) + } + }, [contact, phone, address, saving]) + + return ( + + {!loggedIn ? ( + 请先登录后查看门店信息 + ) : loading ? ( + 加载中... + ) : !detail ? ( + 未获取到门店信息 + ) : ( + <> + {/* ========== 基础信息(只读,后台维护) ========== */} + + + 基础信息 + + + 门店名称 + {detail.name || '-'} + + + 门店编码 + {detail.code || '-'} + + + 回款周期 + {detail.payment_cycle_days} 天 + + + + {/* ========== 联系信息(可编辑) ========== */} + + + 联系信息 + + + 联系人 + setContact(e.detail.value)} + /> + + + 联系电话 + setPhone(e.detail.value)} + /> + + + 地址 +