From bd3589af61200c5298c3f5b45d9a0f3a20ef4425 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=B5=B5=E5=BF=A0=E6=9E=97?= <170083662@qq.com> Date: Fri, 17 Jul 2026 12:38:27 +0800 Subject: [PATCH] =?UTF-8?q?feat(store-center):=20=E5=9C=A8=E9=97=A8?= =?UTF-8?q?=E5=BA=97=E4=B8=AD=E5=BF=83=E6=B7=BB=E5=8A=A0=E6=89=AB=E7=A0=81?= =?UTF-8?q?=E7=99=BB=E5=BD=95PC=E5=90=8E=E5=8F=B0=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 门店中心右上角新增扫码登录按钮,使用base64 SVG图标兼容小程序Image组件渲染 - 扫码后通过解析二维码内容调用confirmWechatQRLogin接口完成PC端登录确认 - 支持扫码取消静默处理,扫码失败则弹窗提示错误信息 - 优化门店中心页面布局,调整扫码按钮绝对定位及样式 - 优化了用户管理页面的小型样式,包括按钮和文本间距调整 - 调整用户页面部分Badge显示逻辑,排除tabIndex为4的项不显示角标 --- .workbuddy/memory/2026-07-17.md | 10 + src/pages/store/center/index.tsx | 508 ++++---- src/pages/store/goods/index.tsx | 2014 +++++++++++++++--------------- src/pages/store/users/index.tsx | 18 +- src/pages/user/user.tsx | 2 +- 5 files changed, 1322 insertions(+), 1230 deletions(-) diff --git a/.workbuddy/memory/2026-07-17.md b/.workbuddy/memory/2026-07-17.md index 998ad1a..78e2540 100644 --- a/.workbuddy/memory/2026-07-17.md +++ b/.workbuddy/memory/2026-07-17.md @@ -94,3 +94,13 @@ ### 验证 - `npx taro build --type weapp` 构建成功 + +## 门店中心右上角扫码登录 PC 后台(src/pages/store/center/index.tsx) +- **需求**:门店中心顶部绿色渐变区右上角加一个二维码扫码图标,点击后调起微信扫码,扫码结果解析出 token 后调 `confirmWechatQRLogin` 确认 PC 端登录 +- **参考**:`/Users/gxwebsoft/VUE/template-10584/src/pages/user/components/UserCard.tsx` 中的 `UnifiedQRButton` 组件 +- **实现**: + - 复用已有 API:`parseQRContent` + `confirmWechatQRLogin`(`@/api/passport/qr-login`),与 `src/components/QRLoginScanner.tsx` 同款 + - 新增 `handleScanLogin` callback:`Taro.scanCode` → `parseQRContent` → 校验 token/userId → `confirmWechatQRLogin` → 成功弹窗 + - 右上角按钮用 `absolute top-3 right-4 z-20` 定位,半透明白色圆形背景 + base64 SVG 图标 + - 小程序不支持 `` 标签,用 `Image` 组件 + `data:image/svg+xml;base64,...` data URI 渲染图标 + - 用户取消扫码(errMsg 含 'cancel')静默处理 diff --git a/src/pages/store/center/index.tsx b/src/pages/store/center/index.tsx index 80ae31c..0cce16c 100644 --- a/src/pages/store/center/index.tsx +++ b/src/pages/store/center/index.tsx @@ -1,254 +1,306 @@ -import React, { useState, useEffect, useCallback } from 'react' -import { View, Text, Image } from '@tarojs/components' +import React, {useState, useEffect, useCallback} from 'react' +import {View, Text, Image} from '@tarojs/components' import Taro from '@tarojs/taro' -import { getMyClerk } from '@/api/shop/shopStoreUser' -import { listShopDealerApply } from '@/api/shop/shopDealerApply' -import { pageShopOrder } from '@/api/shop/shopOrder' +import {getMyClerk} from '@/api/shop/shopStoreUser' +import {listShopDealerApply} from '@/api/shop/shopDealerApply' +import {pageShopOrder} from '@/api/shop/shopOrder' +import {confirmWechatQRLogin, parseQRContent} from '@/api/passport/qr-login' import Badge from '@/components/common/Badge' +// 二维码扫码图标(base64 SVG data URI,兼容小程序 Image 组件) +const QR_SCAN_ICON = "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjQiIGhlaWdodD0iMjQiIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cGF0aCBkPSJNMyA3VjVhMiAyIDAgMCAxIDItMmgyTTE3IDNoMmEyIDIgMCAwIDEgMiAydjJNMjEgMTd2MmEyIDIgMCAwIDEtMiAyaC0yTTcgMjFINWEyIDIgMCAwIDEtMi0ydi0yIiBzdHJva2U9IiNmZmZmZmYiIHN0cm9rZS13aWR0aD0iMiIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIiAvPjxyZWN0IHg9IjciIHk9IjciIHdpZHRoPSI0IiBoZWlnaHQ9IjQiIHN0cm9rZT0iI2ZmZmZmZiIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2UtbGluZWpvaW49InJvdW5kIiAvPjxyZWN0IHg9IjEzIiB5PSI3IiB3aWR0aD0iNCIgaGVpZ2h0PSI0IiBzdHJva2U9IiNmZmZmZmYiIHN0cm9rZS13aWR0aD0iMiIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIgLz48cmVjdCB4PSI3IiB5PSIxMyIgd2lkdGg9IjQiIGhlaWdodD0iNCIgc3Ryb2tlPSIjZmZmZmZmIiBzdHJva2Utd2lkdGg9IjIiIHN0cm9rZS1saW5lam9pbj0icm91bmQiIC8+PHBhdGggZD0iTTEzIDEzaDJ2Mk0xNyAxM3YyTTE3IDE3djJNMTMgMTd2Mk0xMyAxOWgyIiBzdHJva2U9IiNmZmZmZmYiIHN0cm9rZS13aWR0aD0iMiIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIiAvPjwvc3ZnPg==" + definePageConfig({ - navigationBarTitleText: '门店中心', + navigationBarTitleText: '门店中心', }) // 功能卡片定义(未来新增功能只需在这里加一项) const FEATURE_CARDS = [ - { - key: 'goods', - icon: '🏷️', - title: '商品管理', - desc: '管理门店商品上下架和库存', - url: '/pages/store/goods/index', - color: '#0891b2', - bgColor: '#cffafe', - showBadge: false, - }, - { - key: 'orders', - icon: '📦', - title: '订单管理', - desc: '查看和处理门店订单', - url: '/pages/store/orders/index', - color: '#15803d', - bgColor: '#dcfce7', - // 显示待处理订单数量角标 - showBadge: true, - }, - { - key: 'users', - icon: '👥', - title: '用户管理', - desc: '查看用户信息、禁用/启用用户', - url: '/pages/store/users/index', - color: '#2563eb', - bgColor: '#dbeafe', - showBadge: false, - }, - { - key: 'vip-review', - icon: '👑', - title: 'VIP会员审核', - desc: '审核客户VIP会员申请', - url: '/pages/user/vip-review/index', - color: '#7c3aed', - bgColor: '#ede9fe', - showBadge: true, - }, + { + key: 'goods', + icon: '🏷️', + title: '商品管理', + desc: '管理门店商品上下架和库存', + url: '/pages/store/goods/index', + color: '#0891b2', + bgColor: '#cffafe', + showBadge: false, + }, + { + key: 'orders', + icon: '📦', + title: '订单管理', + desc: '查看和处理门店订单', + url: '/pages/store/orders/index', + color: '#15803d', + bgColor: '#dcfce7', + // 显示待处理订单数量角标 + showBadge: true, + }, + { + key: 'users', + icon: '👥', + title: '用户管理', + desc: '查看用户信息、禁用/启用用户', + url: '/pages/store/users/index', + color: '#2563eb', + bgColor: '#dbeafe', + showBadge: false, + }, + { + key: 'vip-review', + icon: '👑', + title: 'VIP会员审核', + desc: '审核客户VIP会员申请', + url: '/pages/user/vip-review/index', + color: '#7c3aed', + bgColor: '#ede9fe', + showBadge: true, + }, ] // 订阅消息模板 ID const SUBSCRIBE_TMPL_IDS = [ - 'RpZaVfxTuNxRmUxvinAwk6FHTpjV9yTznShdy8OHiTk', // 交易提醒:订单号、商品名称、联系人、联系电话、送货地址 + 'RpZaVfxTuNxRmUxvinAwk6FHTpjV9yTznShdy8OHiTk', // 交易提醒:订单号、商品名称、联系人、联系电话、送货地址 ] export default function StoreCenterPage() { - const [storeInfo, setStoreInfo] = useState(null) - const [pendingVipCount, setPendingVipCount] = useState(0) - const [pendingOrderCount, setPendingOrderCount] = useState(0) - const [loading, setLoading] = useState(true) + const [storeInfo, setStoreInfo] = useState(null) + const [pendingVipCount, setPendingVipCount] = useState(0) + const [pendingOrderCount, setPendingOrderCount] = useState(0) + const [loading, setLoading] = useState(true) - useEffect(() => { - // 验证店员身份 - getMyClerk() - .then(data => { - if (!data) { - Taro.showToast({ title: '仅门店店员可访问', icon: 'none' }) - setTimeout(() => Taro.navigateBack(), 1500) - return + useEffect(() => { + // 验证店员身份 + getMyClerk() + .then(data => { + if (!data) { + Taro.showToast({title: '仅门店店员可访问', icon: 'none'}) + setTimeout(() => Taro.navigateBack(), 1500) + return + } + setStoreInfo(data) + // 加载待审核数量 + loadBadgeCounts() + }) + .catch(() => { + Taro.showToast({title: '仅门店店员可访问', icon: 'none'}) + setTimeout(() => Taro.navigateBack(), 1500) + }) + .finally(() => setLoading(false)) + }, []) + + /** 加载各类待处理角标数量 */ + const loadBadgeCounts = async () => { + try { + // VIP 待审核 + const vipData = await listShopDealerApply({applyStatus: 10}) + setPendingVipCount((vipData || []).length) + } catch { /* ignore */ } - setStoreInfo(data) - // 加载待审核数量 - loadBadgeCounts() - }) - .catch(() => { - Taro.showToast({ title: '仅门店店员可访问', icon: 'none' }) - setTimeout(() => Taro.navigateBack(), 1500) - }) - .finally(() => setLoading(false)) - }, []) - /** 加载各类待处理角标数量 */ - const loadBadgeCounts = async () => { - try { - // VIP 待审核 - const vipData = await listShopDealerApply({ applyStatus: 10 }) - setPendingVipCount((vipData || []).length) - } catch { /* ignore */ } - - try { - // 待处理订单:未付款/待发货/待收货 - const orderData = await pageShopOrder({ statusFilter: 1, page: 1, limit: 1 }) - setPendingOrderCount(orderData?.total || 0) - } catch { /* ignore */ } - } - - /** 获取卡片角标数字 */ - const getBadgeCount = (key: string) => { - if (key === 'orders') return pendingOrderCount - if (key === 'vip-review') return pendingVipCount - return 0 - } - - /** 请求订阅消息授权 */ - const handleSubscribe = useCallback(() => { - if (SUBSCRIBE_TMPL_IDS.length === 0) { - Taro.showToast({ title: '暂无可订阅的消息模板', icon: 'none' }) - return + try { + // 待处理订单:未付款/待发货/待收货 + const orderData = await pageShopOrder({statusFilter: 1, page: 1, limit: 1}) + setPendingOrderCount(orderData?.total || 0) + } catch { /* ignore */ + } } - Taro.requestSubscribeMessage({ - tmplIds: SUBSCRIBE_TMPL_IDS, - success: (res) => { - // res[templateId] === 'accept' 表示用户同意订阅 - const accepted = SUBSCRIBE_TMPL_IDS.filter(id => res[id] === 'accept') - if (accepted.length > 0) { - Taro.showToast({ title: '订阅成功', icon: 'success' }) + + /** 获取卡片角标数字 */ + const getBadgeCount = (key: string) => { + if (key === 'orders') return pendingOrderCount + if (key === 'vip-review') return pendingVipCount + return 0 + } + + /** 扫码登录 PC 后台 */ + const handleScanLogin = useCallback(async () => { + try { + Taro.showLoading({title: '请扫码...', mask: true}) + const scanRes = await Taro.scanCode({onlyFromCamera: false, scanType: ['qrCode']}) + Taro.hideLoading() + + const rawContent = scanRes.result || '' + const token = parseQRContent(rawContent) + const userId = Number(Taro.getStorageSync('UserId')) + + if (!token) { + Taro.showModal({title: '提示', content: '未识别到有效的登录二维码', showCancel: false}) + return + } + if (!userId) { + Taro.showModal({title: '提示', content: '当前用户未登录', showCancel: false}) + return + } + + Taro.showLoading({title: '正在确认登录...', mask: true}) + await confirmWechatQRLogin(token, userId) + Taro.hideLoading() + Taro.showModal({title: '登录成功', content: 'PC 端后台登录已确认,请返回网页端查看', showCancel: false}) + } catch (err: any) { + Taro.hideLoading() + if (err?.errMsg?.includes('cancel')) { + return // 用户取消扫码,静默处理 + } + Taro.showModal({title: '登录失败', content: err?.message || '扫码登录失败,请重试', showCancel: false}) } - }, - fail: (err: any) => { - console.error('订阅失败:', err) - // errCode 20001: 模板ID不存在或未绑定到当前小程序 - if (err.errCode === 20001) { - Taro.showModal({ - title: '订阅模板未配置', - content: '请联系管理员在微信公众平台选用「新订单通知」订阅消息模板', - showCancel: false, - }) - } else if (err.errCode === 20004 || err.errCode === 20002) { - // 用户拒绝/关闭了主开关,静默处理 - Taro.showToast({ title: '已取消订阅', icon: 'none' }) + }, []) + + /** 请求订阅消息授权 */ + const handleSubscribe = useCallback(() => { + if (SUBSCRIBE_TMPL_IDS.length === 0) { + Taro.showToast({title: '暂无可订阅的消息模板', icon: 'none'}) + return } - }, - }) - }, []) + Taro.requestSubscribeMessage({ + tmplIds: SUBSCRIBE_TMPL_IDS, + success: (res) => { + // res[templateId] === 'accept' 表示用户同意订阅 + const accepted = SUBSCRIBE_TMPL_IDS.filter(id => res[id] === 'accept') + if (accepted.length > 0) { + Taro.showToast({title: '订阅成功', icon: 'success'}) + } + }, + fail: (err: any) => { + console.error('订阅失败:', err) + // errCode 20001: 模板ID不存在或未绑定到当前小程序 + if (err.errCode === 20001) { + Taro.showModal({ + title: '订阅模板未配置', + content: '请联系管理员在微信公众平台选用「新订单通知」订阅消息模板', + showCancel: false, + }) + } else if (err.errCode === 20004 || err.errCode === 20002) { + // 用户拒绝/关闭了主开关,静默处理 + Taro.showToast({title: '已取消订阅', icon: 'none'}) + } + }, + }) + }, []) - if (loading) { - return ( - - 加载中... - - ) - } - - if (!storeInfo) return null - - return ( - - - {/* 顶部信息区:店员头像 + 门店名称 */} - - - - - {/* 店员头像 */} - - {storeInfo.avatar ? ( - - ) : ( - - 👤 - - )} - - - {/* 门店信息 */} - - - {storeInfo.storeName || '门店中心'} - - - {storeInfo.name} {storeInfo.phone ? `· ${storeInfo.phone}` : ''} - - - - - - {/* 功能卡片区 */} - - - - {FEATURE_CARDS.map(card => ( - Taro.navigateTo({ url: card.url })} - > - {/* 图标 */} - - {card.icon} - - - {/* 文字 */} - - {card.title} - {card.desc} - - - {/* 待处理角标 */} - {card.showBadge && ( - - )} - - {/* 箭头 */} - + if (loading) { + return ( + + 加载中... - ))} - - + ) + } - {/* 底部提示 */} - - {/* 订阅消息提醒 */} - - - 🔔 - - - 接收新订单提醒 - - 开启后新订单将通过微信服务通知提醒您 - - - 去开启 - + if (!storeInfo) return null - {/**/} - {/* 未来更多功能将持续上线*/} - {/**/} - - - ) + return ( + + + {/* 顶部信息区:店员头像 + 门店名称 */} + + + + + {/* 右上角扫码登录按钮 */} + + + + + + {/* 店员头像 */} + + {storeInfo.avatar ? ( + + ) : ( + + 👤 + + )} + + + {/* 门店信息 */} + + + {storeInfo.storeName || '门店中心'} + + + {storeInfo.name} {storeInfo.phone ? `· ${storeInfo.phone}` : ''} + + + + + + {/* 功能卡片区 */} + + + + {FEATURE_CARDS.map(card => ( + Taro.navigateTo({url: card.url})} + > + {/* 图标 */} + + {card.icon} + + + {/* 文字 */} + + {card.title} + {card.desc} + + + {/* 待处理角标 */} + {card.showBadge && ( + + )} + + {/* 箭头 */} + + + ))} + + + + {/* 底部提示 */} + + {/* 订阅消息提醒 */} + + + 🔔 + + + 接收新订单提醒 + + 开启后新订单将通过微信服务通知提醒您 + + + 去开启 + + + + 未来更多功能将持续上线 + + + + + ) } diff --git a/src/pages/store/goods/index.tsx b/src/pages/store/goods/index.tsx index ea884b5..7e16803 100644 --- a/src/pages/store/goods/index.tsx +++ b/src/pages/store/goods/index.tsx @@ -1,1054 +1,1084 @@ -import React, { useState, useEffect, useCallback, useRef } from 'react' -import { View, Text, Image, ScrollView, Input, Textarea } from '@tarojs/components' -import Taro, { useDidShow } from '@tarojs/taro' -import { pageShopGoods, updateShopGoods, addShopGoods } from '@/api/shop/shopGoods' -import { listShopGoodsCategory } from '@/api/shop/shopGoodsCategory' -import { getMyClerk } from '@/api/shop/shopStoreUser' -import { uploadFile } from '@/api/system/file' -import type { ShopGoods, ShopGoodsParam } from '@/api/shop/shopGoods/model' -import type { ShopGoodsCategory } from '@/api/shop/shopGoodsCategory/model' -import { getCompressedImageUrl } from '@/utils/image' +import React, {useState, useEffect, useCallback, useRef} from 'react' +import {View, Text, Image, ScrollView, Input, Textarea} from '@tarojs/components' +import Taro, {useDidShow} from '@tarojs/taro' +import {pageShopGoods, updateShopGoods, addShopGoods} from '@/api/shop/shopGoods' +import {listShopGoodsCategory} from '@/api/shop/shopGoodsCategory' +import {getMyClerk} from '@/api/shop/shopStoreUser' +import {uploadFile} from '@/api/system/file' +import type {ShopGoods, ShopGoodsParam} from '@/api/shop/shopGoods/model' +import type {ShopGoodsCategory} from '@/api/shop/shopGoodsCategory/model' +import {getCompressedImageUrl} from '@/utils/image' definePageConfig({ - navigationBarTitleText: '商品管理', + navigationBarTitleText: '商品管理', }) // ─── Tab 配置 ────────────────────────────────────────────────── type TabKey = 'all' | 'on_sale' | 'pending' | 'sold_out' const TABS: { key: TabKey; label: string; param: Partial }[] = [ - { key: 'all', label: '全部', param: {} }, - { key: 'on_sale', label: '已上架', param: { status: 0 } }, - { key: 'pending', label: '待上架', param: { status: 1 } }, - { key: 'sold_out', label: '已售罄', param: { stock: 0 } }, + {key: 'all', label: '全部', param: {}}, + {key: 'on_sale', label: '已上架', param: {status: 0}}, + {key: 'pending', label: '待上架', param: {status: 1}}, + {key: 'sold_out', label: '已售罄', param: {stock: 0}}, ] // ─── 状态辅助函数 ────────────────────────────────────────────────── function getStatusText(status?: number): string { - const map: Record = { 0: '已上架', 1: '待上架', 2: '待审核', 3: '审核不通过' } - return map[status ?? 0] || '未知' + const map: Record = {0: '已上架', 1: '待上架', 2: '待审核', 3: '审核不通过'} + return map[status ?? 0] || '未知' } function getStatusColor(status?: number): string { - const map: Record = { 0: '#16a34a', 1: '#ea580c', 2: '#9333ea', 3: '#dc2626' } - return map[status ?? 0] || '#999' + const map: Record = {0: '#16a34a', 1: '#ea580c', 2: '#9333ea', 3: '#dc2626'} + return map[status ?? 0] || '#999' } // ─── 页面组件 ────────────────────────────────────────────────────── export default function StoreGoodsPage() { - const [activeTab, setActiveTab] = useState('all') + const [activeTab, setActiveTab] = useState('all') - // 商品列表与分页 - const [goodsList, setGoodsList] = useState([]) - const [page, setPage] = useState(1) - const [hasMore, setHasMore] = useState(true) - const [loading, setLoading] = useState(false) + // 商品列表与分页 + const [goodsList, setGoodsList] = useState([]) + const [page, setPage] = useState(1) + const [hasMore, setHasMore] = useState(true) + const [loading, setLoading] = useState(false) - // 搜索与筛选 - const [keyword, setKeyword] = useState('') - const [searchText, setSearchText] = useState('') - const [categories, setCategories] = useState([]) - const [selectedCategory, setSelectedCategory] = useState(undefined) - const [showCategoryPicker, setShowCategoryPicker] = useState(false) + // 搜索与筛选 + const [keyword, setKeyword] = useState('') + const [searchText, setSearchText] = useState('') + const [categories, setCategories] = useState([]) + const [selectedCategory, setSelectedCategory] = useState(undefined) + const [showCategoryPicker, setShowCategoryPicker] = useState(false) - // 编辑弹窗 - const [showEditModal, setShowEditModal] = useState(false) - const [editGoods, setEditGoods] = useState(null) - const [editForm, setEditForm] = useState({ - price: '', - salePrice: '', - dealerPrice: '', - stock: '', - sortNumber: '', - comments: '', - files: [] as Array<{ uid?: number; url: string; status?: string }>, - }) - const [submitting, setSubmitting] = useState(false) - const [uploadingImage, setUploadingImage] = useState(false) - const [uploadingBanner, setUploadingBanner] = useState(false) + // 编辑弹窗 + const [showEditModal, setShowEditModal] = useState(false) + const [editGoods, setEditGoods] = useState(null) + const [editForm, setEditForm] = useState({ + price: '', + salePrice: '', + dealerPrice: '', + stock: '', + sortNumber: '', + comments: '', + files: [] as Array<{ uid?: number; url: string; status?: string }>, + }) + const [submitting, setSubmitting] = useState(false) + const [uploadingImage, setUploadingImage] = useState(false) + const [uploadingBanner, setUploadingBanner] = useState(false) - // 当前登录店员的门店ID - const [storeId, setStoreId] = useState(undefined) + // 当前登录店员的门店ID + const [storeId, setStoreId] = useState(undefined) - // 新增商品弹窗 - const [showAddModal, setShowAddModal] = useState(false) - const [addForm, setAddForm] = useState({ - name: '', - categoryId: undefined as number | undefined, - image: '', - price: '', - salePrice: '', - dealerPrice: '', - stock: '', - sortNumber: '', - comments: '', - files: [] as Array<{ uid?: number; url: string; status?: string }>, - }) - const [addSubmitting, setAddSubmitting] = useState(false) - const [addUploadingImage, setAddUploadingImage] = useState(false) - const [addUploadingBanner, setAddUploadingBanner] = useState(false) - const [showAddCategoryPicker, setShowAddCategoryPicker] = useState(false) + // 新增商品弹窗 + const [showAddModal, setShowAddModal] = useState(false) + const [addForm, setAddForm] = useState({ + name: '', + categoryId: undefined as number | undefined, + image: '', + price: '', + salePrice: '', + dealerPrice: '', + stock: '', + sortNumber: '', + comments: '', + files: [] as Array<{ uid?: number; url: string; status?: string }>, + }) + const [addSubmitting, setAddSubmitting] = useState(false) + const [addUploadingImage, setAddUploadingImage] = useState(false) + const [addUploadingBanner, setAddUploadingBanner] = useState(false) + const [showAddCategoryPicker, setShowAddCategoryPicker] = useState(false) - const pageSize = 10 - const loadingRef = useRef(false) + const pageSize = 10 + const loadingRef = useRef(false) - /** 加载商品列表 */ - const loadGoods = useCallback(async (tab: TabKey, pageNo: number = 1, append = false, categoryId?: number) => { - if (loadingRef.current) return - loadingRef.current = true - setLoading(true) + /** 加载商品列表 */ + const loadGoods = useCallback(async (tab: TabKey, pageNo: number = 1, append = false, categoryId?: number) => { + if (loadingRef.current) return + loadingRef.current = true + setLoading(true) - try { - const tabConfig = TABS.find(t => t.key === tab) - if (!tabConfig) return - - const params: ShopGoodsParam = { - ...tabConfig.param, - page: pageNo, - limit: pageSize, - } - if (keyword) params.keywords = keyword - const effectiveCategoryId = categoryId !== undefined ? categoryId : selectedCategory - if (effectiveCategoryId) params.categoryId = effectiveCategoryId - - const res = await pageShopGoods(params) - const list = res?.list || [] - - // 已售罄 Tab 需要客户端二次过滤 stock <= 0 - const filtered = tab === 'sold_out' ? list.filter(g => (g.stock ?? 0) <= 0) : list - - setGoodsList(prev => append ? [...prev, ...filtered] : filtered) - setPage(pageNo) - setHasMore(list.length >= pageSize) - } catch (e: any) { - Taro.showToast({ title: e.message || '加载失败', icon: 'none' }) - if (!append) setGoodsList([]) - } finally { - loadingRef.current = false - setLoading(false) - } - }, [keyword, selectedCategory]) - - // 加载分类列表 - useEffect(() => { - listShopGoodsCategory({}) - .then(data => setCategories(data || [])) - .catch(() => {}) - }, []) - - // 获取当前登录店员的门店ID - useEffect(() => { - getMyClerk() - .then(data => { - if (data?.storeId) setStoreId(data.storeId) - }) - .catch(() => {}) - }, []) - - // 切换 tab 时重新加载 - useEffect(() => { - loadGoods(activeTab, 1) - }, [activeTab, loadGoods]) - - // 页面重新显示时刷新 - useDidShow(() => { - loadGoods(activeTab, 1) - }) - - /** 加载更多 */ - const handleLoadMore = () => { - if (hasMore && !loadingRef.current) { - loadGoods(activeTab, page + 1, true) - } - } - - /** 执行搜索 */ - const handleSearch = () => { - setKeyword(searchText) - loadGoods(activeTab, 1) - } - - /** 选择分类 */ - const handleSelectCategory = (cat: ShopGoodsCategory | undefined) => { - setSelectedCategory(cat?.categoryId) - setShowCategoryPicker(false) - loadGoods(activeTab, 1, false, cat?.categoryId) - } - - /** 快速上架/下架 */ - const handleToggleStatus = async (goods: ShopGoods) => { - const newStatus = goods.status === 0 ? 1 : 0 - const actionText = newStatus === 0 ? '上架' : '下架' - - Taro.showModal({ - title: '提示', - content: `确定要${actionText}商品「${goods.name}」吗?`, - success: async (res) => { - if (!res.confirm) return try { - Taro.showLoading({ title: `${actionText}中...` }) - await updateShopGoods({ ...goods, status: newStatus }) - Taro.hideLoading() - Taro.showToast({ title: `${actionText}成功`, icon: 'success' }) - loadGoods(activeTab, 1) + const tabConfig = TABS.find(t => t.key === tab) + if (!tabConfig) return + + const params: ShopGoodsParam = { + ...tabConfig.param, + page: pageNo, + limit: pageSize, + } + if (keyword) params.keywords = keyword + const effectiveCategoryId = categoryId !== undefined ? categoryId : selectedCategory + if (effectiveCategoryId) params.categoryId = effectiveCategoryId + + const res = await pageShopGoods(params) + const list = res?.list || [] + + // 已售罄 Tab 需要客户端二次过滤 stock <= 0 + const filtered = tab === 'sold_out' ? list.filter(g => (g.stock ?? 0) <= 0) : list + + setGoodsList(prev => append ? [...prev, ...filtered] : filtered) + setPage(pageNo) + setHasMore(list.length >= pageSize) } catch (e: any) { - Taro.hideLoading() - Taro.showToast({ title: e.message || `${actionText}失败`, icon: 'none' }) + Taro.showToast({title: e.message || '加载失败', icon: 'none'}) + if (!append) setGoodsList([]) + } finally { + loadingRef.current = false + setLoading(false) } - }, + }, [keyword, selectedCategory]) + + // 加载分类列表 + useEffect(() => { + listShopGoodsCategory({}) + .then(data => setCategories(data || [])) + .catch(() => { + }) + }, []) + + // 获取当前登录店员的门店ID + useEffect(() => { + getMyClerk() + .then(data => { + if (data?.storeId) setStoreId(data.storeId) + }) + .catch(() => { + }) + }, []) + + // 切换 tab 时重新加载 + useEffect(() => { + loadGoods(activeTab, 1) + }, [activeTab, loadGoods]) + + // 页面重新显示时刷新 + useDidShow(() => { + loadGoods(activeTab, 1) }) - } - /** 切换推荐 */ - const handleToggleRecommend = async (goods: ShopGoods) => { - const newRecommend = goods.recommend === 1 ? 0 : 1 - try { - await updateShopGoods({ ...goods, recommend: newRecommend }) - Taro.showToast({ title: newRecommend === 1 ? '已推荐' : '已取消推荐', icon: 'none' }) - loadGoods(activeTab, 1) - } catch (e: any) { - Taro.showToast({ title: e.message || '操作失败', icon: 'none' }) - } - } - - /** 打开编辑弹窗 */ - const openEditModal = (goods: ShopGoods) => { - setEditGoods(goods) - const bannerFiles: Array<{ uid?: number; url: string; status?: string }> = [] - if (goods.files) { - try { - const parsed = JSON.parse(goods.files) - if (Array.isArray(parsed)) { - bannerFiles.push( - ...parsed - .map((item: any) => - typeof item === 'string' - ? { url: item } - : { uid: item.uid, url: item.url || '', status: item.status } - ) - .filter((item: any) => item.url) - ) + /** 加载更多 */ + const handleLoadMore = () => { + if (hasMore && !loadingRef.current) { + loadGoods(activeTab, page + 1, true) } - } catch { - // 非 JSON 数组时按逗号分隔兜底 - bannerFiles.push(...goods.files.split(',').filter(Boolean).map(url => ({ url }))) - } } - setEditForm({ - price: goods.price || '', - salePrice: goods.salePrice || '', - dealerPrice: goods.dealerPrice || '', - stock: String(goods.stock ?? ''), - sortNumber: String(goods.sortNumber ?? ''), - comments: goods.comments || '', - files: bannerFiles, - }) - setShowEditModal(true) - } - /** 关闭编辑弹窗 */ - const closeEditModal = () => { - setShowEditModal(false) - setEditGoods(null) - } - - /** 上传/更换商品图片 */ - const handleUploadImage = async () => { - if (uploadingImage) return - setUploadingImage(true) - try { - const res = await uploadFile() - const imageUrl = res.path || '' - if (imageUrl) { - setEditGoods(prev => prev ? { ...prev, image: imageUrl } : null) - Taro.showToast({ title: '上传成功', icon: 'success' }) - } - } catch (e: any) { - Taro.showToast({ title: e.message || '上传失败', icon: 'none' }) - } finally { - setUploadingImage(false) + /** 执行搜索 */ + const handleSearch = () => { + setKeyword(searchText) + loadGoods(activeTab, 1) } - } - /** 上传轮播图 */ - const handleAddBanner = async () => { - if (uploadingBanner) return - setUploadingBanner(true) - try { - const res = await uploadFile() - const imageUrl = res.path || '' - if (imageUrl) { + /** 选择分类 */ + const handleSelectCategory = (cat: ShopGoodsCategory | undefined) => { + setSelectedCategory(cat?.categoryId) + setShowCategoryPicker(false) + loadGoods(activeTab, 1, false, cat?.categoryId) + } + + /** 快速上架/下架 */ + const handleToggleStatus = async (goods: ShopGoods) => { + const newStatus = goods.status === 0 ? 1 : 0 + const actionText = newStatus === 0 ? '上架' : '下架' + + Taro.showModal({ + title: '提示', + content: `确定要${actionText}商品「${goods.name}」吗?`, + success: async (res) => { + if (!res.confirm) return + try { + Taro.showLoading({title: `${actionText}中...`}) + await updateShopGoods({...goods, status: newStatus}) + Taro.hideLoading() + Taro.showToast({title: `${actionText}成功`, icon: 'success'}) + loadGoods(activeTab, 1) + } catch (e: any) { + Taro.hideLoading() + Taro.showToast({title: e.message || `${actionText}失败`, icon: 'none'}) + } + }, + }) + } + + /** 切换推荐 */ + const handleToggleRecommend = async (goods: ShopGoods) => { + const newRecommend = goods.recommend === 1 ? 0 : 1 + try { + await updateShopGoods({...goods, recommend: newRecommend}) + Taro.showToast({title: newRecommend === 1 ? '已推荐' : '已取消推荐', icon: 'none'}) + loadGoods(activeTab, 1) + } catch (e: any) { + Taro.showToast({title: e.message || '操作失败', icon: 'none'}) + } + } + + /** 打开编辑弹窗 */ + const openEditModal = (goods: ShopGoods) => { + setEditGoods(goods) + const bannerFiles: Array<{ uid?: number; url: string; status?: string }> = [] + if (goods.files) { + try { + const parsed = JSON.parse(goods.files) + if (Array.isArray(parsed)) { + bannerFiles.push( + ...parsed + .map((item: any) => + typeof item === 'string' + ? {url: item} + : {uid: item.uid, url: item.url || '', status: item.status} + ) + .filter((item: any) => item.url) + ) + } + } catch { + // 非 JSON 数组时按逗号分隔兜底 + bannerFiles.push(...goods.files.split(',').filter(Boolean).map(url => ({url}))) + } + } + setEditForm({ + price: goods.price || '', + salePrice: goods.salePrice || '', + dealerPrice: goods.dealerPrice || '', + stock: String(goods.stock ?? ''), + sortNumber: String(goods.sortNumber ?? ''), + comments: goods.comments || '', + files: bannerFiles, + }) + setShowEditModal(true) + } + + /** 关闭编辑弹窗 */ + const closeEditModal = () => { + setShowEditModal(false) + setEditGoods(null) + } + + /** 上传/更换商品图片 */ + const handleUploadImage = async () => { + if (uploadingImage) return + setUploadingImage(true) + try { + const res = await uploadFile() + const imageUrl = res.path || '' + if (imageUrl) { + setEditGoods(prev => prev ? {...prev, image: imageUrl} : null) + Taro.showToast({title: '上传成功', icon: 'success'}) + } + } catch (e: any) { + Taro.showToast({title: e.message || '上传失败', icon: 'none'}) + } finally { + setUploadingImage(false) + } + } + + /** 上传轮播图 */ + const handleAddBanner = async () => { + if (uploadingBanner) return + setUploadingBanner(true) + try { + const res = await uploadFile() + const imageUrl = res.path || '' + if (imageUrl) { + setEditForm(prev => ({ + ...prev, + files: [...prev.files, {uid: res.id, url: imageUrl, status: 'done'}], + })) + Taro.showToast({title: '上传成功', icon: 'success'}) + } + } catch (e: any) { + Taro.showToast({title: e.message || '上传失败', icon: 'none'}) + } finally { + setUploadingBanner(false) + } + } + + /** 删除轮播图 */ + const handleRemoveBanner = (index: number) => { setEditForm(prev => ({ - ...prev, - files: [...prev.files, { uid: res.id, url: imageUrl, status: 'done' }], + ...prev, + files: prev.files.filter((_, i) => i !== index), })) - Taro.showToast({ title: '上传成功', icon: 'success' }) - } - } catch (e: any) { - Taro.showToast({ title: e.message || '上传失败', icon: 'none' }) - } finally { - setUploadingBanner(false) - } - } - - /** 删除轮播图 */ - const handleRemoveBanner = (index: number) => { - setEditForm(prev => ({ - ...prev, - files: prev.files.filter((_, i) => i !== index), - })) - } - - /** 提交编辑 */ - const submitEdit = async () => { - if (!editGoods) return - - const price = parseFloat(editForm.price) - if (isNaN(price) || price < 0) { - Taro.showToast({ title: '请输入有效的价格', icon: 'none' }) - return } - const stock = parseInt(editForm.stock, 10) - if (isNaN(stock) || stock < 0) { - Taro.showToast({ title: '请输入有效的库存', icon: 'none' }) - return + /** 提交编辑 */ + const submitEdit = async () => { + if (!editGoods) return + + const price = parseFloat(editForm.price) + if (isNaN(price) || price < 0) { + Taro.showToast({title: '请输入有效的价格', icon: 'none'}) + return + } + + const stock = parseInt(editForm.stock, 10) + if (isNaN(stock) || stock < 0) { + Taro.showToast({title: '请输入有效的库存', icon: 'none'}) + return + } + + setSubmitting(true) + try { + await updateShopGoods({ + ...editGoods, + image: editGoods.image, + price: editForm.price, + salePrice: editForm.salePrice || undefined, + dealerPrice: editForm.dealerPrice || undefined, + stock, + sortNumber: parseInt(editForm.sortNumber, 10) || 0, + comments: editForm.comments || undefined, + files: JSON.stringify(editForm.files), + }) + Taro.showToast({title: '保存成功', icon: 'success'}) + closeEditModal() + loadGoods(activeTab, 1) + } catch (e: any) { + Taro.showToast({title: e.message || '保存失败', icon: 'none'}) + } finally { + setSubmitting(false) + } } - setSubmitting(true) - try { - await updateShopGoods({ - ...editGoods, - image: editGoods.image, - price: editForm.price, - salePrice: editForm.salePrice || undefined, - dealerPrice: editForm.dealerPrice || undefined, - stock, - sortNumber: parseInt(editForm.sortNumber, 10) || 0, - comments: editForm.comments || undefined, - files: JSON.stringify(editForm.files), - }) - Taro.showToast({ title: '保存成功', icon: 'success' }) - closeEditModal() - loadGoods(activeTab, 1) - } catch (e: any) { - Taro.showToast({ title: e.message || '保存失败', icon: 'none' }) - } finally { - setSubmitting(false) + // ─── 新增商品相关 ────────────────────────────────────────────── + + /** 打开新增弹窗 */ + const openAddModal = () => { + setAddForm({ + name: '', + categoryId: undefined, + image: '', + price: '', + salePrice: '', + dealerPrice: '', + stock: '', + sortNumber: '', + comments: '', + files: [], + }) + setShowAddModal(true) } - } - // ─── 新增商品相关 ────────────────────────────────────────────── - - /** 打开新增弹窗 */ - const openAddModal = () => { - setAddForm({ - name: '', - categoryId: undefined, - image: '', - price: '', - salePrice: '', - dealerPrice: '', - stock: '', - sortNumber: '', - comments: '', - files: [], - }) - setShowAddModal(true) - } - - /** 关闭新增弹窗 */ - const closeAddModal = () => { - setShowAddModal(false) - } - - /** 上传商品图片(新增) */ - const handleAddUploadImage = async () => { - if (addUploadingImage) return - setAddUploadingImage(true) - try { - const res = await uploadFile() - const imageUrl = res.path || '' - if (imageUrl) { - setAddForm(prev => ({ ...prev, image: imageUrl })) - Taro.showToast({ title: '上传成功', icon: 'success' }) - } - } catch (e: any) { - Taro.showToast({ title: e.message || '上传失败', icon: 'none' }) - } finally { - setAddUploadingImage(false) + /** 关闭新增弹窗 */ + const closeAddModal = () => { + setShowAddModal(false) } - } - /** 上传轮播图(新增) */ - const handleAddUploadBanner = async () => { - if (addUploadingBanner) return - setAddUploadingBanner(true) - try { - const res = await uploadFile() - const imageUrl = res.path || '' - if (imageUrl) { + /** 上传商品图片(新增) */ + const handleAddUploadImage = async () => { + if (addUploadingImage) return + setAddUploadingImage(true) + try { + const res = await uploadFile() + const imageUrl = res.path || '' + if (imageUrl) { + setAddForm(prev => ({...prev, image: imageUrl})) + Taro.showToast({title: '上传成功', icon: 'success'}) + } + } catch (e: any) { + Taro.showToast({title: e.message || '上传失败', icon: 'none'}) + } finally { + setAddUploadingImage(false) + } + } + + /** 上传轮播图(新增) */ + const handleAddUploadBanner = async () => { + if (addUploadingBanner) return + setAddUploadingBanner(true) + try { + const res = await uploadFile() + const imageUrl = res.path || '' + if (imageUrl) { + setAddForm(prev => ({ + ...prev, + files: [...prev.files, {uid: res.id, url: imageUrl, status: 'done'}], + })) + Taro.showToast({title: '上传成功', icon: 'success'}) + } + } catch (e: any) { + Taro.showToast({title: e.message || '上传失败', icon: 'none'}) + } finally { + setAddUploadingBanner(false) + } + } + + /** 删除轮播图(新增) */ + const handleAddRemoveBanner = (index: number) => { setAddForm(prev => ({ - ...prev, - files: [...prev.files, { uid: res.id, url: imageUrl, status: 'done' }], + ...prev, + files: prev.files.filter((_, i) => i !== index), })) - Taro.showToast({ title: '上传成功', icon: 'success' }) - } - } catch (e: any) { - Taro.showToast({ title: e.message || '上传失败', icon: 'none' }) - } finally { - setAddUploadingBanner(false) - } - } - - /** 删除轮播图(新增) */ - const handleAddRemoveBanner = (index: number) => { - setAddForm(prev => ({ - ...prev, - files: prev.files.filter((_, i) => i !== index), - })) - } - - /** 提交新增商品 */ - const submitAdd = async () => { - if (!addForm.name.trim()) { - Taro.showToast({ title: '请输入商品名称', icon: 'none' }) - return - } - if (!addForm.image) { - Taro.showToast({ title: '请上传商品图片', icon: 'none' }) - return - } - const price = parseFloat(addForm.price) - if (isNaN(price) || price < 0) { - Taro.showToast({ title: '请输入有效的到手价', icon: 'none' }) - return - } - const stock = parseInt(addForm.stock, 10) - if (isNaN(stock) || stock < 0) { - Taro.showToast({ title: '请输入有效的库存', icon: 'none' }) - return } - if (!storeId) { - Taro.showToast({ title: '未获取到门店信息,请重试', icon: 'none' }) - return + /** 提交新增商品 */ + const submitAdd = async () => { + if (!addForm.name.trim()) { + Taro.showToast({title: '请输入商品名称', icon: 'none'}) + return + } + if (!addForm.image) { + Taro.showToast({title: '请上传商品图片', icon: 'none'}) + return + } + const price = parseFloat(addForm.price) + if (isNaN(price) || price < 0) { + Taro.showToast({title: '请输入有效的到手价', icon: 'none'}) + return + } + const stock = parseInt(addForm.stock, 10) + if (isNaN(stock) || stock < 0) { + Taro.showToast({title: '请输入有效的库存', icon: 'none'}) + return + } + + if (!storeId) { + Taro.showToast({title: '未获取到门店信息,请重试', icon: 'none'}) + return + } + + setAddSubmitting(true) + try { + await addShopGoods({ + name: addForm.name.trim(), + categoryId: addForm.categoryId, + image: addForm.image, + price: addForm.price, + salePrice: addForm.salePrice || undefined, + dealerPrice: addForm.dealerPrice || undefined, + stock, + sortNumber: parseInt(addForm.sortNumber, 10) || 0, + comments: addForm.comments || undefined, + files: JSON.stringify(addForm.files), + storeId, + status: 1, // 新增默认待上架 + recommend: 0, + type: 1, // 实物商品 + }) + Taro.showToast({title: '添加成功', icon: 'success'}) + closeAddModal() + loadGoods(activeTab, 1) + } catch (e: any) { + Taro.showToast({title: e.message || '添加失败', icon: 'none'}) + } finally { + setAddSubmitting(false) + } } - setAddSubmitting(true) - try { - await addShopGoods({ - name: addForm.name.trim(), - categoryId: addForm.categoryId, - image: addForm.image, - price: addForm.price, - salePrice: addForm.salePrice || undefined, - dealerPrice: addForm.dealerPrice || undefined, - stock, - sortNumber: parseInt(addForm.sortNumber, 10) || 0, - comments: addForm.comments || undefined, - files: JSON.stringify(addForm.files), - storeId, - status: 1, // 新增默认待上架 - recommend: 0, - type: 1, // 实物商品 - }) - Taro.showToast({ title: '添加成功', icon: 'success' }) - closeAddModal() - loadGoods(activeTab, 1) - } catch (e: any) { - Taro.showToast({ title: e.message || '添加失败', icon: 'none' }) - } finally { - setAddSubmitting(false) - } - } + /** 渲染单个商品卡片 */ + const renderGoodsCard = (goods: ShopGoods) => { + const isSoldOut = (goods.stock ?? 0) <= 0 + const isOnSale = goods.status === 0 - /** 渲染单个商品卡片 */ - const renderGoodsCard = (goods: ShopGoods) => { - const isSoldOut = (goods.stock ?? 0) <= 0 - const isOnSale = goods.status === 0 + return ( + + {/* 商品头部:图片 + 名称 + 状态 */} + + {goods.image ? ( + Taro.previewImage({urls: [goods.image!]})} + /> + ) : ( + + 📦 + + )} + + + + {getStatusText(goods.status)} + + {isSoldOut && isOnSale && ( + 售罄 + )} + {goods.recommend === 1 && ( + 推荐 + )} + + + {goods.name} + + {goods.categoryName && ( + {goods.categoryName} + )} + + + + {/* 价格信息 */} + + + 到手价 + ¥{goods.price || '0'} + + {goods.salePrice && ( + + 市场价 + ¥{goods.salePrice} + + )} + + VIP价 + ¥{goods.dealerPrice} + + + 销量 + {goods.sales || 0} + + + 库存 + {goods.stock ?? 0} + + + + {/* 操作按钮 */} + + {/* 上架/下架 */} + handleToggleStatus(goods)} + > + + {isOnSale ? '下架' : '上架'} + + + + {/* 推荐 */} + handleToggleRecommend(goods)} + > + + {goods.recommend === 1 ? '取消推荐' : '推荐'} + + + + {/* 编辑 */} + openEditModal(goods)} + > + 编辑 + + + + ) + } return ( - - {/* 商品头部:图片 + 名称 + 状态 */} - - {goods.image ? ( - Taro.previewImage({ urls: [goods.image!] })} - /> - ) : ( - - 📦 + + {/* 搜索栏 */} + + + + setShowCategoryPicker(true)} + > + + {selectedCategory + ? categories.find(c => c.categoryId === selectedCategory)?.title || '分类' + : '分类'} + + + - )} - - - - {getStatusText(goods.status)} - - {isSoldOut && isOnSale && ( - 售罄 - )} - {goods.recommend === 1 && ( - 推荐 - )} + + {/* Tab 栏 */} + + {TABS.map(tab => ( + setActiveTab(tab.key)} + > + + {tab.label} + + + ))} - - {goods.name} - - {goods.categoryName && ( - {goods.categoryName} + + {/* 商品列表 */} + + {loading && goodsList.length === 0 ? ( + + 加载中... + + ) : goodsList.length === 0 ? ( + + 暂无商品 + + ) : ( + goodsList.map(renderGoodsCard) + )} + {loading && goodsList.length > 0 && ( + + 加载中... + + )} + {!hasMore && goodsList.length > 0 && ( + + 没有更多了 + + )} + + + + {/* 新增商品浮动按钮 */} + + + + + + {/* 分类选择弹窗 */} + {showCategoryPicker && ( + setShowCategoryPicker(false)}> + + e.stopPropagation()} + > + + 选择分类 + setShowCategoryPicker(false)}>× + + handleSelectCategory(undefined)} + > + 全部分类 + + {categories.map(cat => ( + handleSelectCategory(cat)} + > + {cat.title} + {selectedCategory === cat.categoryId && ( + + )} + + ))} + + + + )} + + {/* 编辑弹窗 */} + {showEditModal && editGoods && ( + + + + 编辑商品 + + {/* 商品信息 */} + + + {editGoods.image ? ( + + ) : ( + + 📷 + + )} + + + {uploadingImage ? '上传中...' : '更换'} + + + + + + {editGoods.name} + + ID: {editGoods.goodsId} + + + + {/* 轮播图 */} + + 轮播图 + + {editForm.files.map((file, index) => ( + + + handleRemoveBanner(index)} + > + × + + + ))} + + {uploadingBanner ? '...' : '+'} + {uploadingBanner ? '上传中' : '上传'} + + + + + {/* 表单字段 */} + + 到手价 (¥) + + + + 市场价 (¥) + + + + 会员价 (¥) + + + + 库存 + + + + 排序号 + + + + 备注 + + + {/* 操作按钮 */} + + + 取消 + + + + {submitting ? '保存中...' : '保存'} + + + + + + )} + + {/* 新增商品弹窗 */} + {showAddModal && ( + + + + 新增商品 + + {/* 商品图片 */} + + 商品图片 * + + {addForm.image ? ( + + ) : ( + + 📷 + + )} + + + {addUploadingImage ? '上传中...' : '上传'} + + + + + + {/* 商品名称 */} + + 商品名称 * + + + {/* 商品分类 */} + + 商品分类 + setShowAddCategoryPicker(true)} + > + + {addForm.categoryId + ? categories.find(c => c.categoryId === addForm.categoryId)?.title || '选择分类' + : '选择分类(选填)'} + + + + + + {/* 轮播图 */} + + 轮播图 + + {addForm.files.map((file, index) => ( + + + handleAddRemoveBanner(index)} + > + × + + + ))} + + {addUploadingBanner ? '...' : '+'} + {addUploadingBanner ? '上传中' : '上传'} + + + + + {/* 到手价 */} + + 到手价 (¥) * + + + {/* 市场价 */} + + 市场价 (¥) + + + {/* 会员价 */} + + 会员价 (¥) + + + {/* 库存 */} + + 库存 * + + + {/* 排序号 */} + + 排序号 + + + {/* 备注 */} + + 备注 + + + {/* 操作按钮 */} + + + 取消 + + + + {addSubmitting ? '添加中...' : '添加'} + + + + + + )} + + {/* 新增商品分类选择弹窗 */} + {showAddCategoryPicker && ( + setShowAddCategoryPicker(false)}> + + e.stopPropagation()} + > + + 选择分类 + setShowAddCategoryPicker(false)}>× + + { + setAddForm(prev => ({...prev, categoryId: undefined})) + setShowAddCategoryPicker(false) + }} + > + 不选分类 + + {categories.map(cat => ( + { + setAddForm(prev => ({...prev, categoryId: cat.categoryId})) + setShowAddCategoryPicker(false) + }} + > + {cat.title} + {addForm.categoryId === cat.categoryId && ( + + )} + + ))} + + + )} - - - {/* 价格信息 */} - - - 到手价 - ¥{goods.price || '0'} - - {goods.salePrice && ( - - 市场价 - ¥{goods.salePrice} - - )} - {goods.dealerPrice && ( - - VIP价 - ¥{goods.dealerPrice} - - )} - - 销量 - {goods.sales || 0} - - - 库存 - {goods.stock ?? 0} - - - - {/* 操作按钮 */} - - {/* 上架/下架 */} - handleToggleStatus(goods)} - > - - {isOnSale ? '下架' : '上架'} - - - - {/* 推荐 */} - handleToggleRecommend(goods)} - > - - {goods.recommend === 1 ? '取消推荐' : '推荐'} - - - - {/* 编辑 */} - openEditModal(goods)} - > - 编辑 - - - ) - } - - return ( - - {/* 搜索栏 */} - - - - setShowCategoryPicker(true)} - > - - {selectedCategory - ? categories.find(c => c.categoryId === selectedCategory)?.title || '分类' - : '分类'} - - - - - - {/* Tab 栏 */} - - {TABS.map(tab => ( - setActiveTab(tab.key)} - > - - {tab.label} - - - ))} - - - {/* 商品列表 */} - - {loading && goodsList.length === 0 ? ( - - 加载中... - - ) : goodsList.length === 0 ? ( - - 暂无商品 - - ) : ( - goodsList.map(renderGoodsCard) - )} - {loading && goodsList.length > 0 && ( - - 加载中... - - )} - {!hasMore && goodsList.length > 0 && ( - - 没有更多了 - - )} - - - - {/* 新增商品浮动按钮 */} - - + - - - {/* 分类选择弹窗 */} - {showCategoryPicker && ( - setShowCategoryPicker(false)}> - - e.stopPropagation()} - > - - 选择分类 - setShowCategoryPicker(false)}>× - - handleSelectCategory(undefined)} - > - 全部分类 - - {categories.map(cat => ( - handleSelectCategory(cat)} - > - {cat.title} - {selectedCategory === cat.categoryId && ( - - )} - - ))} - - - - )} - - {/* 编辑弹窗 */} - {showEditModal && editGoods && ( - - - - 编辑商品 - - {/* 商品信息 */} - - - {editGoods.image ? ( - - ) : ( - - 📷 - - )} - - - {uploadingImage ? '上传中...' : '更换'} - - - - - - {editGoods.name} - - ID: {editGoods.goodsId} - - - - {/* 轮播图 */} - - 轮播图 - - {editForm.files.map((file, index) => ( - - - handleRemoveBanner(index)} - > - × - - - ))} - - {uploadingBanner ? '...' : '+'} - {uploadingBanner ? '上传中' : '上传'} - - - - - {/* 表单字段 */} - - 到手价 (¥) - - - - 市场价 (¥) - - - - 会员价 (¥) - - - - 库存 - - - - 排序号 - - - - 备注 - - - {/* 操作按钮 */} - - - 取消 - - - - {submitting ? '保存中...' : '保存'} - - - - - - )} - - {/* 新增商品弹窗 */} - {showAddModal && ( - - - - 新增商品 - - {/* 商品图片 */} - - 商品图片 * - - {addForm.image ? ( - - ) : ( - - 📷 - - )} - - - {addUploadingImage ? '上传中...' : '上传'} - - - - - - {/* 商品名称 */} - - 商品名称 * - - - {/* 商品分类 */} - - 商品分类 - setShowAddCategoryPicker(true)} - > - - {addForm.categoryId - ? categories.find(c => c.categoryId === addForm.categoryId)?.title || '选择分类' - : '选择分类(选填)'} - - - - - - {/* 轮播图 */} - - 轮播图 - - {addForm.files.map((file, index) => ( - - - handleAddRemoveBanner(index)} - > - × - - - ))} - - {addUploadingBanner ? '...' : '+'} - {addUploadingBanner ? '上传中' : '上传'} - - - - - {/* 到手价 */} - - 到手价 (¥) * - - - {/* 市场价 */} - - 市场价 (¥) - - - {/* 会员价 */} - - 会员价 (¥) - - - {/* 库存 */} - - 库存 * - - - {/* 排序号 */} - - 排序号 - - - {/* 备注 */} - - 备注 - - - {/* 操作按钮 */} - - - 取消 - - - - {addSubmitting ? '添加中...' : '添加'} - - - - - - )} - - {/* 新增商品分类选择弹窗 */} - {showAddCategoryPicker && ( - setShowAddCategoryPicker(false)}> - - e.stopPropagation()} - > - - 选择分类 - setShowAddCategoryPicker(false)}>× - - { - setAddForm(prev => ({ ...prev, categoryId: undefined })) - setShowAddCategoryPicker(false) - }} - > - 不选分类 - - {categories.map(cat => ( - { - setAddForm(prev => ({ ...prev, categoryId: cat.categoryId })) - setShowAddCategoryPicker(false) - }} - > - {cat.title} - {addForm.categoryId === cat.categoryId && ( - - )} - - ))} - - - - )} - - ) } diff --git a/src/pages/store/users/index.tsx b/src/pages/store/users/index.tsx index 7b00c3e..f4e25bf 100644 --- a/src/pages/store/users/index.tsx +++ b/src/pages/store/users/index.tsx @@ -183,7 +183,7 @@ export default function StoreUsersPage() { {displayName} - + {user.phone || user.mobile || '未绑定手机'} {/* 禁用/启用按钮 */} { e.stopPropagation() handleToggleStatus(user) @@ -215,17 +215,17 @@ export default function StoreUsersPage() { {user.memberLevelName && ( - 会员: + 会员: {user.memberLevelName} )} - 注册: + 注册: {formatTime(user.createTime)} {user.balance !== undefined && Number(user.balance) > 0 && ( - 余额: + 余额: ¥{user.balance} )} @@ -238,7 +238,7 @@ export default function StoreUsersPage() { {/* 搜索栏 */} - +