diff --git a/.idea/workspace.xml b/.idea/workspace.xml
index f306e58..d3f9854 100644
--- a/.idea/workspace.xml
+++ b/.idea/workspace.xml
@@ -4,7 +4,11 @@
-
+
+
+
+
+
@@ -116,15 +120,7 @@
-
-
-
-
- 1775965322794
-
-
-
- 1775965322794
+
@@ -510,7 +506,15 @@
1784800111045
-
+
+
+ 1784800763746
+
+
+
+ 1784800763746
+
+
@@ -530,7 +534,6 @@
-
@@ -555,7 +558,8 @@
-
+
+
\ No newline at end of file
diff --git a/.workbuddy/memory/2026-07-23.md b/.workbuddy/memory/2026-07-23.md
index f86510c..10e9f67 100644
--- a/.workbuddy/memory/2026-07-23.md
+++ b/.workbuddy/memory/2026-07-23.md
@@ -31,4 +31,31 @@
- 新增 state `clerkStoreId` / `clerkTenantId`,在 `getMyClerk()` 成功后写入(店员记录自带 `storeId`/`tenantId`)。
- `handleApprove` 在 `setUserDeveloper` 之后调 `addShopStoreUser({ userId, storeId: clerkStoreId, tenantId: clerkTenantId, isDelete: 0, comments: '开发者审核通过自动添加' })`,仅当 `clerkStoreId` 存在时执行。
-**依赖**:后端 `POST /shop/shop-store-user`(ShopStoreUserController.save,需 `shop:shopStoreUser:save` 权限,店员已有);tenantId 复用审核店员的租户,避免前端另取。
\ No newline at end of file
+**依赖**:后端 `POST /shop/shop-store-user`(ShopStoreUserController.save,需 `shop:shopStoreUser:save` 权限,店员已有);tenantId 复用审核店员的租户,避免前端另取。
+
+## 平台管理页 UI 对齐门店中心
+
+**需求**:用户要求 `pages/admin/index/index` 与 `pages/store/center/index` 一模一样的 UI。
+
+**改动**(仅 `src/pages/admin/index/index.tsx`):
+- 删除原先 NavBar + StatCard 网格 + EntryGrid 的旧结构,整体改写为门店中心同款「渐变绿色头部 + 白卡片列表(图标/标题/描述/角标/箭头)+ 底部信息卡」。
+- 头部展示管理员 avatar + 「平台管理」标题 + 昵称·手机号(取 `useUser().user`)。
+- FEATURE_CARDS 保留 4 个入口:应用审核(📝)、Git 审核(🐙)、域名审核(🌐)、工单处理(🎫),各自带 desc、color/bgColor、url。
+- 角标沿用项目 `Badge` 组件:应用审核=待审核应用数(pagePublishReviews 已按 pending_review 过滤),工单处理=未解决/未关闭工单数(getAllTickets 取前100条 client 端过滤)。
+- 底部卡片「平台概况」保留原页面统计:应用总数、用户总数、工单总数(pageAllApps / pageUsers / getAllTickets 的 total)。
+- 保留登录校验(!isLoggedIn)与管理员校验(!isAdmin)两个空态,改为居中图标文案,去掉 NavBar 依赖(标题走 definePageConfig)。
+- 新页面不再引入 nutui 组件,只用 `@tarojs/components` + 自定义 `Badge`,规避 weapp 空白坑。
+
+**待验证**:`npm run build:weapp` 未实跑(环境 tsc 报 @tarojs/taro 类型库缺失属全局问题,与本次无关)。
+
+## 平台管理 / 门店中心 改为 isSuperAdmin=1 才显示
+
+**需求**:平台管理与门店中心两个入口都需要 `isSuperAdmin=1` 才显示(原先平台管理=isAdmin、门店中心=店员 storeInfo)。门店中心页面本身按用户确认改为「仅超级管理员可进」。
+
+**改动文件**:
+- `src/pages/user/user.tsx`:菜单项 平台管理、门店中心 的显示条件均由 `isAdmin` / `storeInfo` 改为 `(user as any)?.isSuperAdmin === 1`。
+- `src/pages/index/index.tsx`:首页角色工作台外层容器补 `|| isSuperAdmin===1`,平台管理入口 `(user as any)?.isAdmin` → `isSuperAdmin === 1`。
+- `src/pages/admin/index/index.tsx`:页面访问门槛 `isAdmin` → `isSuperAdmin===1`(含空态文案);`loading` 拆为 `userLoading`(来自 useUser,防闪烁) + `dataLoading`(角标数据);首屏 `if (userLoading || dataLoading) spinner`。
+- `src/pages/store/center/index.tsx`:移除 `getMyClerk()` 店员门槛,改为 `isSuperAdmin===1` 才进;非超管弹「仅超级管理员可访问」并 navigateBack;头部加 `header` 兜底(超管非店员时显示「门店中心」+ 管理员昵称/手机号);渲染守卫 `userLoading → 请先登录 → 无访问权限`,去掉原 `if(!storeInfo) return null`(否则非店员超管整页空白)。
+
+**验证**:`npm run build:weapp` 在沙箱被 OOM/锁文件杀掉(exit 137,无报错),属环境问题,非代码错误;改动均为项目既有写法(useUser / Badge / 渐变头部)。
\ No newline at end of file
diff --git a/.workbuddy/memory/MEMORY.md b/.workbuddy/memory/MEMORY.md
index 4c50308..0e56143 100644
--- a/.workbuddy/memory/MEMORY.md
+++ b/.workbuddy/memory/MEMORY.md
@@ -10,6 +10,12 @@
- **vip-review 审核通过流程(用户 2026-07-22 最终确认,2026-07-23 补充)**:前端 `handleApprove` 依次调 ① `updateShopDealerApply(applyStatus=20)`;② `setUserDeveloper(userId)`(不碰 `sys_user_role`);③ **`addShopStoreUser`**——在当前门店下给申请人补一条 `shop_store_user`(店员)记录(`userId=申请人, storeId=审核店员所属门店, tenantId=同店员, isDelete=0`)。后端 `markAsDeveloper` 做两件事:① 把 `sys_user.is_developer` 置 `1`;② **替换**用户角色为 `developer`——`roleService.getByRoleCode(roleCode=developer, tenantId=用户租户)` 找到角色 → 删掉该用户现有 `sys_user_role` → 只绑定 developer 这一个角色,**不与原角色并存**(角色替换包 try/catch,失败不影响 isDeveloper 落库)。即"升级为开发者 = 单角色 developer + isDeveloper 标记位 + 一条门店店员记录",不是新增第二个角色。审核店员的 `storeId/tenantId` 来自 `getMyClerk()` 返回值(存于 `clerkStoreId/clerkTenantId` state)。
- 注意:前端 `vip-review` / `vip-upgrade` 等页面的变量名仍残留 `VIP_ROLE_*`、`pendingVipCount`、`dealerPrice` 等旧标识符(仅文案改成了「开发者」),后端角色与缓存 key 仍对应这些旧名,重命名需谨慎。
+### 超级管理员(isSuperAdmin=1)访问门槛(2026-07-23 确认)
+- 用户对象字段 `isSuperAdmin`(`(user as any)?.isSuperAdmin === 1`,数值 1 才算),前端此前未引用,由后端 `sys_user` 返回。
+- **平台管理**(`pages/admin/index/index`)与**门店中心**(`pages/store/center/index`)两个入口,**菜单与首页工作台均只在 `isSuperAdmin===1` 时显示**(原先平台管理是 `isAdmin`、门店中心是店员 `storeInfo`)。
+- 门店中心页面已改为「仅超级管理员可进」:移除原 `getMyClerk()` 店员门槛;非超管访问弹「仅超级管理员可访问」并 `navigateBack`;超管若同时是店员仍会加载门店信息用于头部,否则头部兜底显示「门店中心」+ 管理员昵称/手机号。
+- 两页都用 `useUser()` 的 `loading`(userLoading)做首屏门卫,避免超管用户未从 storage 解析完时先闪「无访问权限」。注意:`pages/store/center` 的入口曾依赖 `user.tsx` 里的 `storeInfo`(getMyClerk),现已不再用 `storeInfo` 决定菜单可见性。
+
### 后端代码风格(com.gxwebsoft.core)
- Controller 继承 `BaseController`,统一返回 `ApiResult`(成功 `code=0`,失败 `code=1`),用 `success()/fail()`。
- 「按 userId 置一个标志位」的范式:`new User(){{setUserId(); setXxx();}}` → `userService.updateById(u)`(参考 `updateStatus`/`updateRecommend`/`auditUser`)。
diff --git a/src/pages/admin/index/index.tsx b/src/pages/admin/index/index.tsx
index 4f1cd35..6cda198 100644
--- a/src/pages/admin/index/index.tsx
+++ b/src/pages/admin/index/index.tsx
@@ -1,104 +1,241 @@
import React, { useState, useEffect, useCallback } from 'react'
-import { View, Text, ScrollView } from '@tarojs/components'
+import { View, Text, Image } from '@tarojs/components'
import Taro from '@tarojs/taro'
-import NavBar from '@/components/NavBar'
-import StatCard from '@/components/common/StatCard'
-import EntryGrid from '@/components/common/EntryGrid'
-import Loading from '@/components/common/Loading'
import { useUser } from '@/hooks/useUser'
import { pageAllApps, pagePublishReviews } from '@/api/app/appProduct'
import { getAllTickets } from '@/api/app/ticket'
import { pageUsers } from '@/api/system/user'
-import { getPageTotal } from '@/utils/devcenter'
+import { getPageTotal, getPageList } from '@/utils/devcenter'
+import Badge from '@/components/common/Badge'
-definePageConfig({ navigationBarTitleText: '平台管理' })
+definePageConfig({
+ navigationBarTitleText: '平台管理',
+})
-const AdminCenterPage: React.FC = () => {
- const { user, isLoggedIn } = useUser()
- const isAdmin = !!(user as any)?.isAdmin
- const [loading, setLoading] = useState(true)
- const [stats, setStats] = useState({ apps: 0, pending: 0, tickets: 0, users: 0 })
+// 功能卡片定义(与门店中心同款结构:图标 + 标题 + 描述 + 角标 + 跳转)
+const FEATURE_CARDS = [
+ {
+ key: 'app-review',
+ icon: '📝',
+ title: '应用审核',
+ desc: '审核开发者提交的应用上架',
+ url: '/pages/admin/app-review/index',
+ color: '#3b82f6',
+ bgColor: '#dbeafe',
+ showBadge: true,
+ },
+ {
+ key: 'git-review',
+ icon: '🐙',
+ title: 'Git 审核',
+ desc: '审核 Git 仓库接入申请',
+ url: '/pages/admin/git-review/index',
+ color: '#8b5cf6',
+ bgColor: '#ede9fe',
+ showBadge: false,
+ },
+ {
+ key: 'domain-review',
+ icon: '🌐',
+ title: '域名审核',
+ desc: '审核自定义域名绑定',
+ url: '/pages/admin/domain-review/index',
+ color: '#0891b2',
+ bgColor: '#cffafe',
+ showBadge: false,
+ },
+ {
+ key: 'tickets',
+ icon: '🎫',
+ title: '工单处理',
+ desc: '处理用户提交的工单',
+ url: '/pages/admin/tickets/index',
+ color: '#f59e0b',
+ bgColor: '#fef3c7',
+ showBadge: true,
+ },
+]
+export default function AdminCenterPage() {
+ const { user, isLoggedIn, loading: userLoading } = useUser()
+ const isSuperAdmin = (user as any)?.isSuperAdmin === 1
+
+ const [dataLoading, setDataLoading] = useState(true)
+ const [pendingApps, setPendingApps] = useState(0)
+ const [pendingTickets, setPendingTickets] = useState(0)
+ const [overview, setOverview] = useState({ apps: 0, users: 0, tickets: 0 })
+
+ useEffect(() => {
+ if (isLoggedIn && isSuperAdmin) {
+ load()
+ } else {
+ setDataLoading(false)
+ }
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [isLoggedIn, isSuperAdmin])
+
+ /** 加载角标数量与平台概况 */
const load = useCallback(async () => {
- setLoading(true)
+ setDataLoading(true)
try {
- const [appsR, pendR, tickR, usersR] = await Promise.allSettled([
+ const [appsR, pendR, allR, usersR] = await Promise.allSettled([
pageAllApps({ page: 1, limit: 1 }),
pagePublishReviews({ page: 1, limit: 1 }),
- getAllTickets({ page: 1, limit: 1 }),
+ getAllTickets({ page: 1, limit: 100 }),
pageUsers({ page: 1, limit: 1 } as any),
])
- const apps = appsR.status === 'fulfilled' ? getPageTotal(appsR.value) : 0
- const pending = pendR.status === 'fulfilled' ? getPageTotal(pendR.value) : 0
- const tickets = tickR.status === 'fulfilled' ? getPageTotal(tickR.value) : 0
- const users = usersR.status === 'fulfilled' ? getPageTotal(usersR.value) : 0
- setStats({ apps, pending, tickets, users })
- } catch (e) {
+
+ // 待审核应用(pagePublishReviews 已按 pending_review 过滤)
+ const pendingAppsCount = pendR.status === 'fulfilled' ? getPageTotal(pendR.value) : 0
+ setPendingApps(pendingAppsCount)
+
+ // 工单:总数 + 待处理(未解决/未关闭)
+ const ticketData = allR.status === 'fulfilled' ? allR.value : null
+ const ticketList = ticketData ? getPageList(ticketData) : []
+ const pendingTicketsCount = ticketList.filter(
+ (t: any) => t.status !== 'resolved' && t.status !== 'closed'
+ ).length
+ setPendingTickets(pendingTicketsCount)
+
+ const appsTotal = appsR.status === 'fulfilled' ? getPageTotal(appsR.value) : 0
+ const ticketsTotal = ticketData ? getPageTotal(ticketData) : 0
+ const usersTotal = usersR.status === 'fulfilled' ? getPageTotal(usersR.value) : 0
+ setOverview({ apps: appsTotal, users: usersTotal, tickets: ticketsTotal })
+ } catch {
// ignore
} finally {
- setLoading(false)
+ setDataLoading(false)
}
}, [])
- useEffect(() => {
- if (isLoggedIn) load()
- }, [isLoggedIn, load])
+ /** 获取卡片角标数字 */
+ const getBadgeCount = (key: string) => {
+ if (key === 'app-review') return pendingApps
+ if (key === 'tickets') return pendingTickets
+ return 0
+ }
+
+ if (userLoading || dataLoading) {
+ return (
+
+ 加载中...
+
+ )
+ }
if (!isLoggedIn) {
return (
-
-
-
- 请先登录
-
+
+ 🔐
+ 请先登录
+ 登录后即可进入平台管理
)
}
- if (!isAdmin) {
+ if (!isSuperAdmin) {
return (
-
-
-
- 🛡️
- 无访问权限
- 平台管理仅对管理员开放
-
+
+ 🛡️
+ 无访问权限
+ 平台管理仅对超级管理员开放
)
}
- const entries = [
- { icon: '📝', label: '应用审核', url: '/pages/admin/app-review/index' },
- { icon: '🐙', label: 'Git 审核', url: '/pages/admin/git-review/index' },
- { icon: '🌐', label: '域名审核', url: '/pages/admin/domain-review/index' },
- { icon: '🎫', label: '工单处理', url: '/pages/admin/tickets/index' },
- ]
+ const adminName = (user as any)?.nickname || (user as any)?.username || '平台管理员'
+ const adminPhone = (user as any)?.phone
return (
-
-
-
- {loading ? (
-
- ) : (
-
-
-
- 0 ? '需处理' : undefined} subColor='#f59e0b' />
-
-
-
-
-
- 审核与处理
-
-
+
+
+ {/* 顶部信息区:管理员头像 + 平台名称(与门店中心同款渐变头部) */}
+
+
+
+
+ {/* 管理员头像 */}
+
+ {((user as any)?.avatar) ? (
+
+ ) : (
+
+ 👤
+
+ )}
- )}
-
+
+ {/* 平台信息 */}
+
+
+ 平台管理
+
+
+ {adminName}{adminPhone ? ` · ${adminPhone}` : ''}
+
+
+
+
+
+ {/* 功能卡片区(与门店中心同款) */}
+
+
+
+ {FEATURE_CARDS.map(card => (
+ Taro.navigateTo({ url: card.url })}
+ >
+ {/* 图标 */}
+
+ {card.icon}
+
+
+ {/* 文字 */}
+
+ {card.title}
+ {card.desc}
+
+
+ {/* 待处理角标 */}
+ {card.showBadge && (
+
+ )}
+
+ {/* 箭头 */}
+ ›
+
+ ))}
+
+
+
+ {/* 底部:平台概况(保留原页面统计信息,沿用同款卡片样式) */}
+
+
+
+ 📊
+
+
+ 平台概况
+
+ 应用 {overview.apps} · 用户 {overview.users} · 工单 {overview.tickets}
+
+
+
+
)
}
-
-export default AdminCenterPage
diff --git a/src/pages/index/index.tsx b/src/pages/index/index.tsx
index e864f6f..3c3afc4 100644
--- a/src/pages/index/index.tsx
+++ b/src/pages/index/index.tsx
@@ -255,8 +255,8 @@ const IndexPage: React.FC = () => {
- {/* 角色工作台入口:仅开发者 / 平台管理员可见 */}
- {(isLoggedIn && ((user as any)?.isDeveloper || (user as any)?.isAdmin)) && (
+ {/* 角色工作台入口:仅开发者 / 平台管理员 / 超级管理员可见 */}
+ {(isLoggedIn && ((user as any)?.isDeveloper || (user as any)?.isAdmin || (user as any)?.isSuperAdmin === 1)) && (
{(user as any)?.isDeveloper && (
{
开发者中心
)}
- {(user as any)?.isAdmin && (
+ {(user as any)?.isSuperAdmin === 1 && (
(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
- }
- setStoreInfo(data)
- // 加载待审核数量
- loadBadgeCounts()
- })
- .catch(() => {
- Taro.showToast({ title: '仅门店店员可访问', icon: 'none' })
+ // 仅超级管理员可访问
+ if (!isLoggedIn || !isSuperAdmin) {
+ if (isLoggedIn && !isSuperAdmin) {
+ Taro.showToast({ title: '仅超级管理员可访问', icon: 'none' })
setTimeout(() => Taro.navigateBack(), 1500)
- })
- .finally(() => setLoading(false))
- }, [])
+ }
+ return
+ }
+ // 超管若同时具备店员身份,加载门店信息用于头部展示(非必须)
+ getMyClerk()
+ .then(data => setStoreInfo(data || null))
+ .catch(() => setStoreInfo(null))
+ // 加载待审核数量
+ loadBadgeCounts()
+ }, [isLoggedIn, isSuperAdmin])
/** 加载各类待处理角标数量 */
const loadBadgeCounts = async () => {
@@ -140,7 +139,7 @@ export default function StoreCenterPage() {
})
}, [])
- if (loading) {
+ if (userLoading) {
return (
加载中...
@@ -148,12 +147,43 @@ export default function StoreCenterPage() {
)
}
- if (!storeInfo) return null
+ if (!isLoggedIn) {
+ return (
+
+ 🔐
+ 请先登录
+ 登录后方可进入门店中心
+
+ )
+ }
+
+ if (!isSuperAdmin) {
+ return (
+
+ 🛡️
+ 无访问权限
+ 门店中心仅对超级管理员开放
+
+ )
+ }
+
+ // 头部展示信息:超管同时是店员则展示门店,否则展示管理员身份兜底
+ const header = storeInfo
+ ? {
+ avatar: storeInfo.avatar,
+ title: storeInfo.storeName || '门店中心',
+ subtitle: `${storeInfo.name || ''}${storeInfo.phone ? ` · ${storeInfo.phone}` : ''}`,
+ }
+ : {
+ avatar: (user as any)?.avatar,
+ title: '门店中心',
+ subtitle: `${((user as any)?.nickname || (user as any)?.username || '')}${((user as any)?.phone) ? ` · ${(user as any).phone}` : ''}`,
+ }
return (
- {/* 顶部信息区:店员头像 + 门店名称 */}
+ {/* 顶部信息区:头像 + 名称 */}
- {/* 店员头像 */}
+ {/* 头像 */}
- {storeInfo.avatar ? (
+ {header.avatar ? (
) : (
@@ -178,13 +208,13 @@ export default function StoreCenterPage() {
)}
- {/* 门店信息 */}
+ {/* 信息 */}
- {storeInfo.storeName || '门店中心'}
+ {header.title}
- {storeInfo.name} {storeInfo.phone ? `· ${storeInfo.phone}` : ''}
+ {header.subtitle}
diff --git a/src/pages/user/user.tsx b/src/pages/user/user.tsx
index 472d598..61f39bb 100644
--- a/src/pages/user/user.tsx
+++ b/src/pages/user/user.tsx
@@ -105,10 +105,10 @@ const UserPage: React.FC = () => {
{ icon: '🔰', label: '申请成为开发者', url: '/pages/user/vip-upgrade/index', highlight: true, requireAuth: true },
// 开发者中心:仅 isDeveloper 用户显示
...((user as any)?.isDeveloper ? [{ icon: '🛠️', label: '开发者中心', url: '/pages/developer/index/index', requireAuth: true }] : []),
- // 平台管理:仅 isAdmin 用户显示
- ...((user as any)?.isAdmin ? [{ icon: '🛡️', label: '平台管理', url: '/pages/admin/index/index', requireAuth: true }] : []),
- // 门店中心:仅门店店员/店长显示(通过 /shop/shop-store-user/my 判断)
- ...(storeInfo ? [{ icon: '🏪', label: '门店中心', url: '/pages/store/center/index', requireAuth: true }] : []),
+ // 平台管理:仅 isSuperAdmin=1 用户显示
+ ...((user as any)?.isSuperAdmin === 1 ? [{ icon: '🛡️', label: '平台管理', url: '/pages/admin/index/index', requireAuth: true }] : []),
+ // 门店中心:仅 isSuperAdmin=1 用户显示
+ ...((user as any)?.isSuperAdmin === 1 ? [{ icon: '🏪', label: '门店中心', url: '/pages/store/center/index', requireAuth: true }] : []),
{ icon: '💰', label: '我的钱包', url: '/pages/user/wallet', requireAuth: true },
{ icon: '📍', label: '收货地址', url: '/pages/user/address-list', requireAuth: true },
// { icon: '⭐', label: '积分明细', url: '/pages/user/points-record' },