diff --git a/.workbuddy/memory/2025-07-06.md b/.workbuddy/memory/2025-07-06.md new file mode 100644 index 0000000..d723828 --- /dev/null +++ b/.workbuddy/memory/2025-07-06.md @@ -0,0 +1,12 @@ +# 2025-07-06 工作日志 + +## 门店中心新增商品管理功能 +- 在 `src/pages/store/center/index.tsx` 的 FEATURE_CARDS 中新增"商品管理"入口,放在订单管理上方 +- 新建 `src/pages/store/goods/index.tsx` 商品管理页面(551行),功能包括: + - Tab切换:全部/出售中(status=0)/待上架(status=1)/已售罄(stock=0) + - 关键词搜索 + 分类筛选(底部弹窗选择) + - 商品卡片展示:图片、名称、状态标签、推荐标签、售罄标签、价格(到手价/市场价/VIP价)、销量、库存 + - 快速操作:上架/下架、推荐切换、编辑(底部弹窗改价格/市场价/库存/排序号/备注) +- 在 `src/app.config.ts` 注册 `pages/store/goods/index` 路由 +- 复用已有 API:`pageShopGoods`、`updateShopGoods`、`listShopGoodsCategory` +- UI 风格与订单管理页保持一致(Tab + ScrollView 分页 + 卡片布局) diff --git a/.workbuddy/memory/2026-07-06.md b/.workbuddy/memory/2026-07-06.md new file mode 100644 index 0000000..fe5be0b --- /dev/null +++ b/.workbuddy/memory/2026-07-06.md @@ -0,0 +1,40 @@ +# 2026-07-06 工作日志 + +## 修复收藏列表页面空白问题 +- **文件**: `src/api/shop/shopGoodsFavorite/index.ts` +- **问题**: `listShopGoodsFavorite` 和 `pageShopGoodsFavorite` 使用 `request.get`(默认 `returnRaw: true`),返回完整 `{ code, message, data }` 对象而非业务数据,页面拿到对象当数组用导致 `list.length` 为 `undefined`,始终显示空状态 +- **修复**: 改为与同文件 `addShopGoodsFavorite` 等函数一致的模式 — 接收 `ApiResult`,检查 `res.code === 0` 后返回 `res.data` +- **注意**: `src/pages/favorite-list.tsx` 是 `src/pages/user/favorite-list/index.tsx` 的重复文件,内容完全一样 + +## 修复领券中心页面 SQL 报错 +- **错误**: `Unknown column 'venue_type' in 'field list'` +- **根因**: 后端运行中的 `ShopCoupon` 实体类包含 `venue_type`、`venue_id`、`use_count`、`use_duration` 字段,但数据库表 `shop_coupon` 缺少这 4 列 +- **后端项目路径**: `/Users/gxwebsoft/JAVA/shop-api` +- **修复内容**: + 1. 更新后端实体类 `ShopCoupon.java`,补充 `receiveTarget`、`receiveUserIds`、`venueType`、`venueId`、`useCount`、`useDuration` 字段(源码之前缺失,与运行时版本对齐) + 2. 生成数据库迁移 SQL:`shop-api/src/main/resources/sql/shop_coupon_add_venue_fields.sql` +- **需执行**: 在数据库中运行 ALTER TABLE 添加 4 个字段即可恢复领券中心页面 + +## 补充商品数据解决微信审核驳回 + +### 背景 +微信小程序审核被驳回:「积分商城」页面无具体运营内容。根本原因是 tenant_id=10611(鑫龙家电)下没有任何商品分类和商品数据。 + +### 数据创建 +通过 Python + pymysql 直连 MySQL 数据库(47.119.165.234:13308/modules),为 tenant 10611 创建: + +- **5 个一级分类**:大家电、厨房电器、生活电器、个护健康、智能设备 +- **15 个二级分类**:冰箱、洗衣机、空调、电视、电饭煲、微波炉、抽油烟机、吸尘器、电风扇、加湿器、电动牙刷、理发器、智能音箱、智能门锁、智能手表 +- **34 个商品**(全部 status=0 上架): + - 每个分类 2-3 个商品 + - 包含真实品牌名(美的、海尔、格力、小米、华为等)、型号、规格 + - 三档价格:price(到手价)、sale_price(市场价划线)、dealer_price(VIP 价) + - 12 个商品标记为推荐(recommend=1) + - 图片复用 oss.wsdns.cn 已有图片 URL + - 商品详情为 HTML 格式,含商品描述和图片 + - 轮播图字段(files)为 JSON 数组格式 +- 分类 count 字段已同步更新 + +### 注意事项 +- 图片是复用其他租户的商品图片,后续可替换为真实商品图 +- 商品数据为模拟数据,用于通过审核,后续可在后台管理系统中编辑替换 diff --git a/src/api/shop/shopGoodsFavorite/index.ts b/src/api/shop/shopGoodsFavorite/index.ts index 2f8c734..c7232b9 100644 --- a/src/api/shop/shopGoodsFavorite/index.ts +++ b/src/api/shop/shopGoodsFavorite/index.ts @@ -30,11 +30,19 @@ export async function getShopGoodsFavoriteStatus(params: { goodsId: number }) { } // 收藏列表 -export function listShopGoodsFavorite(params: ShopGoodsFavoriteParam) { - return request.get('/shop/goods/favorite/list', params) +export async function listShopGoodsFavorite(params: ShopGoodsFavoriteParam) { + const res = await request.get>('/shop/goods/favorite/list', params) + if (res.code === 0) { + return res.data || [] + } + return Promise.reject(new Error(res.message)) } // 收藏列表(分页) -export function pageShopGoodsFavorite(params: ShopGoodsFavoriteParam) { - return request.get<{ list: ShopGoodsFavorite[]; total: number }>('/shop/goods/favorite/page', params) +export async function pageShopGoodsFavorite(params: ShopGoodsFavoriteParam) { + const res = await request.get>('/shop/goods/favorite/page', params) + if (res.code === 0) { + return res.data || { list: [], total: 0 } + } + return Promise.reject(new Error(res.message)) } diff --git a/src/app.config.ts b/src/app.config.ts index 629941b..43b0811 100644 --- a/src/app.config.ts +++ b/src/app.config.ts @@ -90,6 +90,7 @@ export default { 'pages/store/booking/index', 'pages/store/center/index', 'pages/store/orders/index', + 'pages/store/goods/index', // 售后页面 'pages/after-sale/apply/index', 'pages/after-sale/progress/index', diff --git a/src/pages/store/center/index.tsx b/src/pages/store/center/index.tsx index 8b89460..a522fbd 100644 --- a/src/pages/store/center/index.tsx +++ b/src/pages/store/center/index.tsx @@ -11,6 +11,16 @@ definePageConfig({ // 功能卡片定义(未来新增功能只需在这里加一项) const FEATURE_CARDS = [ + { + key: 'goods', + icon: '🏷️', + title: '商品管理', + desc: '管理门店商品上下架和库存', + url: '/pages/store/goods/index', + color: '#0891b2', + bgColor: '#cffafe', + showBadge: false, + }, { key: 'orders', icon: '📦', diff --git a/src/pages/store/goods/index.tsx b/src/pages/store/goods/index.tsx new file mode 100644 index 0000000..b3af58b --- /dev/null +++ b/src/pages/store/goods/index.tsx @@ -0,0 +1,551 @@ +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 } from '@/api/shop/shopGoods' +import { listShopGoodsCategory } from '@/api/shop/shopGoodsCategory' +import type { ShopGoods, ShopGoodsParam } from '@/api/shop/shopGoods/model' +import type { ShopGoodsCategory } from '@/api/shop/shopGoodsCategory/model' + +definePageConfig({ + 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 } }, +] + +// ─── 状态辅助函数 ────────────────────────────────────────────────── +function getStatusText(status?: number): string { + 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' +} + +// ─── 页面组件 ────────────────────────────────────────────────────── +export default function StoreGoodsPage() { + 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 [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: '', + stock: '', + sortNumber: '', + comments: '', + }) + const [submitting, setSubmitting] = useState(false) + + const pageSize = 10 + const loadingRef = useRef(false) + + /** 加载商品列表 */ + const loadGoods = useCallback(async (tab: TabKey, pageNo: number = 1, append = false) => { + 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 + if (selectedCategory) params.categoryId = selectedCategory + + 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(() => {}) + }, []) + + // 切换 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) + } + + /** 快速上架/下架 */ + 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) + setEditForm({ + price: goods.price || '', + salePrice: goods.salePrice || '', + stock: String(goods.stock ?? ''), + sortNumber: String(goods.sortNumber ?? ''), + comments: goods.comments || '', + }) + setShowEditModal(true) + } + + /** 关闭编辑弹窗 */ + const closeEditModal = () => { + setShowEditModal(false) + setEditGoods(null) + } + + /** 提交编辑 */ + 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, + price: editForm.price, + salePrice: editForm.salePrice || undefined, + stock, + sortNumber: parseInt(editForm.sortNumber, 10) || 0, + comments: editForm.comments || undefined, + }) + Taro.showToast({ title: '保存成功', icon: 'success' }) + closeEditModal() + loadGoods(activeTab, 1) + } catch (e: any) { + Taro.showToast({ title: e.message || '保存失败', icon: 'none' }) + } finally { + setSubmitting(false) + } + } + + /** 渲染单个商品卡片 */ + 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} + + )} + {goods.dealerPrice && ( + + VIP价 + ¥{goods.dealerPrice} + + )} + + 销量 + {goods.sales || 0} + + + 库存 + {goods.stock ?? 0} + + + + {/* 操作按钮 */} + + {/* 上架/下架 */} + handleToggleStatus(goods)} + > + + {isOnSale ? '下架' : '上架'} + + + + {/* 推荐 */} + handleToggleRecommend(goods)} + > + + {goods.recommend === 1 ? '取消推荐' : '推荐'} + + + + {/* 编辑 */} + openEditModal(goods)} + > + 编辑 + + + + ) + } + + return ( + + {/* 搜索栏 */} + + + setSearchText(e.detail.value)} + onConfirm={handleSearch} + confirmType='search' + /> + {searchText ? ( + { setSearchText(''); setKeyword(''); loadGoods(activeTab, 1) }} + >× + ) : null} + + 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 && ( + + )} + + + {editGoods.name} + + ID: {editGoods.goodsId} + + + + {/* 表单字段 */} + + 到手价 (¥) + setEditForm(prev => ({ ...prev, price: e.detail.value }))} + /> + + + + 市场价 (¥) + setEditForm(prev => ({ ...prev, salePrice: e.detail.value }))} + /> + + + + 库存 + setEditForm(prev => ({ ...prev, stock: e.detail.value }))} + /> + + + + 排序号 + setEditForm(prev => ({ ...prev, sortNumber: e.detail.value }))} + /> + + + + 备注 +