diff --git a/.workbuddy/memory/2026-07-14.md b/.workbuddy/memory/2026-07-14.md index 6b59d41..dcbec2c 100644 --- a/.workbuddy/memory/2026-07-14.md +++ b/.workbuddy/memory/2026-07-14.md @@ -5,3 +5,11 @@ - 问题:订单商品图片不显示,字段名与模型不匹配 - 修复:`coverImage` → `image`、`specInfo` → `spec`、`quantity` → `totalNum`(按 `ShopOrderGoods` 模型定义) - 图片压缩:`getCompressedImageUrl(goods.image, { width: 80 })`,展示尺寸 w-16 h-16(64px),压缩宽度 80 够用 + +## 门店中心新增用户管理功能 +- 新页面:`src/pages/store/users/index.tsx` +- 功能:分页查询用户列表、搜索(昵称/手机号)、Tab 筛选(全部/正常/已禁用)、查看用户详情弹窗、禁用/启用用户 +- 使用的 API:`pageUsers`(`/system/user/page`)、`updateUserStatus`(`/system/user/status`,参数 userId + status,0=正常 1=禁用) +- 入口:`src/pages/store/center/index.tsx` FEATURE_CARDS 中,在 VIP会员审核上方加了"用户管理"卡片(蓝色主题 👥 图标) +- 路由注册:`src/app.config.ts` 新增 `pages/store/users/index` + diff --git a/src/app.config.ts b/src/app.config.ts index 43b0811..c95e399 100644 --- a/src/app.config.ts +++ b/src/app.config.ts @@ -91,6 +91,7 @@ export default { 'pages/store/center/index', 'pages/store/orders/index', 'pages/store/goods/index', + 'pages/store/users/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 a522fbd..aefbe92 100644 --- a/src/pages/store/center/index.tsx +++ b/src/pages/store/center/index.tsx @@ -32,6 +32,16 @@ const FEATURE_CARDS = [ // 显示待处理订单数量角标 showBadge: true, }, + { + key: 'users', + icon: '👥', + title: '用户管理', + desc: '查看用户信息、禁用/启用用户', + url: '/pages/store/users/index', + color: '#2563eb', + bgColor: '#dbeafe', + showBadge: false, + }, { key: 'vip-review', icon: '👑', diff --git a/src/pages/store/users/index.tsx b/src/pages/store/users/index.tsx new file mode 100644 index 0000000..7b00c3e --- /dev/null +++ b/src/pages/store/users/index.tsx @@ -0,0 +1,445 @@ +import React, { useState, useEffect, useCallback, useRef } from 'react' +import { View, Text, Image, ScrollView, Input } from '@tarojs/components' +import Taro, { useDidShow } from '@tarojs/taro' +import { pageUsers, updateUserStatus } from '@/api/system/user' +import type { User } from '@/api/system/user/model' +import { getCompressedImageUrl } from '@/utils/image' + +definePageConfig({ + navigationBarTitleText: '用户管理', +}) + +// ─── Tab 配置 ────────────────────────────────────────────────── +type TabKey = 'all' | 'normal' | 'disabled' + +const TABS: { key: TabKey; label: string; status?: number }[] = [ + { key: 'all', label: '全部' }, + { key: 'normal', label: '正常', status: 0 }, + { key: 'disabled', label: '已禁用', status: 1 }, +] + +// ─── 页面组件 ────────────────────────────────────────────────────── +export default function StoreUsersPage() { + const [activeTab, setActiveTab] = useState('all') + + // 用户列表与分页 + const [userList, setUserList] = 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 [showDetail, setShowDetail] = useState(false) + const [detailUser, setDetailUser] = useState(null) + + const pageSize = 20 + const loadingRef = useRef(false) + + /** 加载用户列表 */ + const loadUsers = useCallback(async (tab: TabKey, pageNo: number = 1, append = false, kw?: string) => { + if (loadingRef.current) return + loadingRef.current = true + setLoading(true) + + try { + const tabConfig = TABS.find(t => t.key === tab) + const params: any = { + page: pageNo, + limit: pageSize, + } + if (tabConfig?.status !== undefined) { + params.status = tabConfig.status + } + const effectiveKw = kw !== undefined ? kw : keyword + if (effectiveKw) params.keywords = effectiveKw + + const res = await pageUsers(params) + const list = res?.list || [] + + setUserList(prev => append ? [...prev, ...list] : list) + setPage(pageNo) + setHasMore(list.length >= pageSize) + } catch (e: any) { + Taro.showToast({ title: e.message || '加载失败', icon: 'none' }) + if (!append) setUserList([]) + } finally { + loadingRef.current = false + setLoading(false) + } + }, [keyword]) + + // 切换 tab 时重新加载 + useEffect(() => { + loadUsers(activeTab, 1) + }, [activeTab, loadUsers]) + + // 页面重新显示时刷新 + useDidShow(() => { + loadUsers(activeTab, 1) + }) + + /** 加载更多 */ + const handleLoadMore = () => { + if (hasMore && !loadingRef.current) { + loadUsers(activeTab, page + 1, true) + } + } + + /** 执行搜索 */ + const handleSearch = () => { + setKeyword(searchText) + loadUsers(activeTab, 1, false, searchText) + } + + /** 清除搜索 */ + const handleClearSearch = () => { + setSearchText('') + setKeyword('') + loadUsers(activeTab, 1, false, '') + } + + /** 切换用户状态(禁用/启用) */ + const handleToggleStatus = (user: User) => { + const currentStatus = user.status ?? 0 + const newStatus = currentStatus === 0 ? 1 : 0 + const actionText = newStatus === 1 ? '禁用' : '启用' + + Taro.showModal({ + title: '确认操作', + content: `确定要${actionText}用户「${user.nickname || user.realName || user.phone || user.userId}」吗?${newStatus === 1 ? '禁用后该用户将无法登录。' : ''}`, + confirmColor: newStatus === 1 ? '#ee0a24' : '#0e932e', + success: async (res) => { + if (!res.confirm) return + try { + Taro.showLoading({ title: `${actionText}中...` }) + await updateUserStatus(user.userId, newStatus) + Taro.hideLoading() + Taro.showToast({ title: `${actionText}成功`, icon: 'success' }) + + // 更新列表和详情中的用户状态 + setUserList(prev => prev.map(u => + u.userId === user.userId ? { ...u, status: newStatus } : u + )) + if (detailUser?.userId === user.userId) { + setDetailUser(prev => prev ? { ...prev, status: newStatus } : null) + } + } catch (e: any) { + Taro.hideLoading() + Taro.showToast({ title: e.message || `${actionText}失败`, icon: 'none' }) + } + }, + }) + } + + /** 打开用户详情 */ + const openDetail = (user: User) => { + setDetailUser(user) + setShowDetail(true) + } + + /** 格式化时间 */ + const formatTime = (time?: string) => { + if (!time) return '-' + // 兼容各种时间格式 + return time.replace('T', ' ').substring(0, 19) + } + + /** 渲染单个用户卡片 */ + const renderUserCard = (user: User) => { + const isDisabled = user.status === 1 + const displayName = user.nickname || user.realName || '未设置昵称' + + return ( + openDetail(user)} + > + {/* 用户头部 */} + + {/* 头像 */} + + {user.avatar ? ( + + ) : ( + + 👤 + + )} + + + {/* 信息 */} + + + + {displayName} + + + {isDisabled ? '已禁用' : '正常'} + + + + {user.phone || user.mobile || '未绑定手机'} + + + + {/* 禁用/启用按钮 */} + { + e.stopPropagation() + handleToggleStatus(user) + }} + > + + {isDisabled ? '启用' : '禁用'} + + + + + {/* 附加信息 */} + + {user.memberLevelName && ( + + 会员: + {user.memberLevelName} + + )} + + 注册: + {formatTime(user.createTime)} + + {user.balance !== undefined && Number(user.balance) > 0 && ( + + 余额: + ¥{user.balance} + + )} + + + ) + } + + return ( + + {/* 搜索栏 */} + + + setSearchText(e.detail.value)} + onConfirm={handleSearch} + confirmType='search' + /> + {searchText ? ( + × + ) : null} + + + 搜索 + + + + {/* Tab 栏 */} + + {TABS.map(tab => ( + setActiveTab(tab.key)} + > + + {tab.label} + + + ))} + + + {/* 用户列表 */} + + {loading && userList.length === 0 ? ( + + 加载中... + + ) : userList.length === 0 ? ( + + 暂无用户 + + ) : ( + userList.map(renderUserCard) + )} + {loading && userList.length > 0 && ( + + 加载中... + + )} + {!hasMore && userList.length > 0 && ( + + 没有更多了 + + )} + + + + {/* 用户详情弹窗 */} + {showDetail && detailUser && ( + + {/* 遮罩 */} + setShowDetail(false)} /> + {/* 弹窗内容 */} + + {/* 标题 */} + + 用户详情 + setShowDetail(false)}>× + + + {/* 用户基本信息 */} + + + {detailUser.avatar ? ( + + ) : ( + + 👤 + + )} + + + + + {detailUser.nickname || detailUser.realName || '未设置昵称'} + + + {detailUser.status === 1 ? '已禁用' : '正常'} + + + + ID: {detailUser.userId || detailUser.id} + + + + + {/* 详细信息列表 */} + + {/* 手机号 */} + + 手机号 + {detailUser.phone || detailUser.mobile || '-'} + + + {/* 真实姓名 */} + {detailUser.realName && ( + + 真实姓名 + {detailUser.realName} + + )} + + {/* 性别 */} + + 性别 + + {detailUser.gender === 1 ? '男' : detailUser.gender === 2 ? '女' : '未知'} + + + + {/* 会员等级 */} + {detailUser.memberLevelName && ( + + 会员等级 + {detailUser.memberLevelName} + + )} + + {/* 余额 */} + {detailUser.balance !== undefined && ( + + 余额 + ¥{detailUser.balance || '0.00'} + + )} + + {/* 积分 */} + {detailUser.points !== undefined && ( + + 积分 + {detailUser.points || 0} + + )} + + {/* 邮箱 */} + {detailUser.email && ( + + 邮箱 + {detailUser.email} + + )} + + {/* 注册时间 */} + + 注册时间 + {formatTime(detailUser.createTime)} + + + + {/* 操作按钮 */} + + setShowDetail(false)} + > + 关闭 + + handleToggleStatus(detailUser)} + > + + {detailUser.status === 1 ? '启用用户' : '禁用用户'} + + + + + + )} + + ) +}