feat(user): 补充开发者审核通过时添加店员记录功能
- 引入 addShopStoreUser 接口用于新增门店店员记录 - 新增 clerkStoreId 和 clerkTenantId 状态,保存当前审核店员的门店和租户信息 - 在审核通过后调用 addShopStoreUser,添加申请人为当前门店店员 - 保证只有当 clerkStoreId 存在时才执行新增操作 - 调整平台管理页 UI,使其与门店中心保持一致风格 - 门店中心及平台管理入口权限调整,仅超级管理员可访问 - 用户菜单中平台管理和门店中心入口现基于 isSuperAdmin 字段控制 - 优化登录及权限校验流程,避免权限不足时页面闪烁显示 - 更新工作记录文档,补充审核通过流程中新添加的店员记录步骤
This commit is contained in:
30
.idea/workspace.xml
generated
30
.idea/workspace.xml
generated
@@ -4,7 +4,11 @@
|
|||||||
<option name="autoReloadType" value="SELECTIVE" />
|
<option name="autoReloadType" value="SELECTIVE" />
|
||||||
</component>
|
</component>
|
||||||
<component name="ChangeListManager">
|
<component name="ChangeListManager">
|
||||||
<list default="true" id="2de94e4f-0587-4c9b-8bfd-e87e0ae95b81" name="更改" comment="fix(weapp): 修复支付页nutui组件导致内容空白问题 - 替换支付页中nutui的Button、Cell、Price、Divider组件为Taro原生组件或项目自定义组件 - 修正商品名称、价格类型和支付金额显示,使用flex布局及Text组件自绘内容 - 调整按钮样式,统一为带蓝色背景和圆角的自定义样式 - 移除nutui Price组件中的thousands属性,使用项目自定义Price组件 - 解决微信小程序中nutui组件因div渲染缺失导致UI元素不显示的问题 - 保持支付流程及状态逻辑无改动,仅优化展示兼容性和稳定性" />
|
<list default="true" id="2de94e4f-0587-4c9b-8bfd-e87e0ae95b81" name="更改" comment="feat(user): 补充开发者审核通过时添加店员记录功能 - 引入 addShopStoreUser 接口用于新增门店店员记录 - 新增 clerkStoreId 和 clerkTenantId 状态,保存当前审核店员的门店和租户信息 - 在审核通过后调用 addShopStoreUser,添加申请人为当前门店店员 - 保证只有当 clerkStoreId 存在时才执行新增操作 - 更新工作记录文档,补充审核通过流程中新添加的店员记录步骤 - 保持原有升级开发者逻辑和接口调用不变">
|
||||||
|
<change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/.workbuddy/memory/2026-07-23.md" beforeDir="false" afterPath="$PROJECT_DIR$/.workbuddy/memory/2026-07-23.md" afterDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/src/pages/admin/index/index.tsx" beforeDir="false" afterPath="$PROJECT_DIR$/src/pages/admin/index/index.tsx" afterDir="false" />
|
||||||
|
</list>
|
||||||
<option name="SHOW_DIALOG" value="false" />
|
<option name="SHOW_DIALOG" value="false" />
|
||||||
<option name="HIGHLIGHT_CONFLICTS" value="true" />
|
<option name="HIGHLIGHT_CONFLICTS" value="true" />
|
||||||
<option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
|
<option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
|
||||||
@@ -116,15 +120,7 @@
|
|||||||
<workItem from="1784133736973" duration="4614000" />
|
<workItem from="1784133736973" duration="4614000" />
|
||||||
<workItem from="1784585070286" duration="3047000" />
|
<workItem from="1784585070286" duration="3047000" />
|
||||||
<workItem from="1784638429525" duration="16147000" />
|
<workItem from="1784638429525" duration="16147000" />
|
||||||
<workItem from="1784798575885" duration="986000" />
|
<workItem from="1784798575885" duration="2188000" />
|
||||||
</task>
|
|
||||||
<task id="LOCAL-00020" summary="feat(invite): 重构邀请加入流程,实现已登录用户免授权直接加入 - 完全重写邀请页逻辑,区分已登录与未注册用户按钮显示 - 已登录用户显示“确认加入”按钮,点击用 wx.login 获取 code 加入 - 未注册用户显示“微信手机号快速加入”按钮,授权手机号后完成注册登录再加入 - 移除跳转登录页面逻辑,所有流程在邀请页内完成 - 加入接口统一使用 Authorization 头携带 token,确保身份双重验证 - 优化背景、粒子和扫描线效果,提升界面视觉体验 - 增加错误处理提示,加入失败时显示详细原因并可返回首页 - 协议勾选仅在未注册用户显示,确保合规授权流程">
|
|
||||||
<option name="closed" value="true" />
|
|
||||||
<created>1775965322794</created>
|
|
||||||
<option name="number" value="00020" />
|
|
||||||
<option name="presentableId" value="LOCAL-00020" />
|
|
||||||
<option name="project" value="LOCAL" />
|
|
||||||
<updated>1775965322794</updated>
|
|
||||||
</task>
|
</task>
|
||||||
<task id="LOCAL-00021" summary="feat(passport): 优化已登录用户加入时协议勾选逻辑 - 已登录用户点击「确认加入」时不再强制勾选协议 - 移除 handleConfirmJoin 中对协议勾选的检查 - 未注册用户仍需勾选协议后才能授权手机号 - 确保登录状态异常时有提示信息显示">
|
<task id="LOCAL-00021" summary="feat(passport): 优化已登录用户加入时协议勾选逻辑 - 已登录用户点击「确认加入」时不再强制勾选协议 - 移除 handleConfirmJoin 中对协议勾选的检查 - 未注册用户仍需勾选协议后才能授权手机号 - 确保登录状态异常时有提示信息显示">
|
||||||
<option name="closed" value="true" />
|
<option name="closed" value="true" />
|
||||||
@@ -510,7 +506,15 @@
|
|||||||
<option name="project" value="LOCAL" />
|
<option name="project" value="LOCAL" />
|
||||||
<updated>1784800111045</updated>
|
<updated>1784800111045</updated>
|
||||||
</task>
|
</task>
|
||||||
<option name="localTasksCounter" value="69" />
|
<task id="LOCAL-00069" summary="feat(user): 补充开发者审核通过时添加店员记录功能 - 引入 addShopStoreUser 接口用于新增门店店员记录 - 新增 clerkStoreId 和 clerkTenantId 状态,保存当前审核店员的门店和租户信息 - 在审核通过后调用 addShopStoreUser,添加申请人为当前门店店员 - 保证只有当 clerkStoreId 存在时才执行新增操作 - 更新工作记录文档,补充审核通过流程中新添加的店员记录步骤 - 保持原有升级开发者逻辑和接口调用不变">
|
||||||
|
<option name="closed" value="true" />
|
||||||
|
<created>1784800763746</created>
|
||||||
|
<option name="number" value="00069" />
|
||||||
|
<option name="presentableId" value="LOCAL-00069" />
|
||||||
|
<option name="project" value="LOCAL" />
|
||||||
|
<updated>1784800763746</updated>
|
||||||
|
</task>
|
||||||
|
<option name="localTasksCounter" value="70" />
|
||||||
<servers />
|
<servers />
|
||||||
</component>
|
</component>
|
||||||
<component name="TypeScriptGeneratedFilesManager">
|
<component name="TypeScriptGeneratedFilesManager">
|
||||||
@@ -530,7 +534,6 @@
|
|||||||
<component name="VcsManagerConfiguration">
|
<component name="VcsManagerConfiguration">
|
||||||
<option name="CHECK_CODE_SMELLS_BEFORE_PROJECT_COMMIT" value="false" />
|
<option name="CHECK_CODE_SMELLS_BEFORE_PROJECT_COMMIT" value="false" />
|
||||||
<option name="CHECK_NEW_TODO" value="false" />
|
<option name="CHECK_NEW_TODO" value="false" />
|
||||||
<MESSAGE value="feat(userVerify): 添加阿里云身份证二要素核验功能 - 在接口层新增 verifyIdCard 接口,实现姓名与身份证号核验 - userVerify 页面提交时增加身份核验逻辑,核验失败阻止提交 - 调用核验接口时显示加载状态,并根据结果提示用户 - 后端依赖 aliyun cloudauth,提供实人认证相关服务接口 - 完成基本的错误捕获与用户提示,提升实名认证流程安全性" />
|
|
||||||
<MESSAGE value="refactor(api): 规范前端API接口地址并统一路径前缀 - 修正开发者、小程序和企业相关API的基础URL,避免重复添加/api前缀 - 统一调整developer、enterprise、invite等模块接口路径,保持与后端Controller一致 - 新增权限申请及消息通知相关API,并完善相关函数实现 - 规范请求参数传递,移除多余包装,改用直接传递对象方式 - 新增API Key重置、版本发布/回滚等接口支持 feat(invite): 重构邀请登录流程支持未注册快速加入 - 完整重写invite页,分离已注册用户和未注册用户的按钮显示和交互 - 实现未注册用户微信手机号授权后自动注册登录及自动加入应用 - 已注册用户直接确认加入应用,无需手机号授权弹窗 - 统一使用getPhoneNumber按钮处理两种状态并修复“授权码不能为空”报错 - 前端配合后端改造,支持已登录用户可直接通过Authorization头加入应用 fix(developer): 修复开发者中心应用加载问题 - 修复只查询创建的应用导致邀请加入的应用不显示的问题 - 同时请求创建应用和参与应用接口,合并并去重应用列表 - 新增pageJoinedApp接口调用及合并逻辑,提高应用列表完整性和体验" />
|
<MESSAGE value="refactor(api): 规范前端API接口地址并统一路径前缀 - 修正开发者、小程序和企业相关API的基础URL,避免重复添加/api前缀 - 统一调整developer、enterprise、invite等模块接口路径,保持与后端Controller一致 - 新增权限申请及消息通知相关API,并完善相关函数实现 - 规范请求参数传递,移除多余包装,改用直接传递对象方式 - 新增API Key重置、版本发布/回滚等接口支持 feat(invite): 重构邀请登录流程支持未注册快速加入 - 完整重写invite页,分离已注册用户和未注册用户的按钮显示和交互 - 实现未注册用户微信手机号授权后自动注册登录及自动加入应用 - 已注册用户直接确认加入应用,无需手机号授权弹窗 - 统一使用getPhoneNumber按钮处理两种状态并修复“授权码不能为空”报错 - 前端配合后端改造,支持已登录用户可直接通过Authorization头加入应用 fix(developer): 修复开发者中心应用加载问题 - 修复只查询创建的应用导致邀请加入的应用不显示的问题 - 同时请求创建应用和参与应用接口,合并并去重应用列表 - 新增pageJoinedApp接口调用及合并逻辑,提高应用列表完整性和体验" />
|
||||||
<MESSAGE value="feat(app): 添加应用入口文件和配置文件 - 新增 index.tsx 作为应用的主入口文件 - 新增 index.config.ts 用于应用的配置管理" />
|
<MESSAGE value="feat(app): 添加应用入口文件和配置文件 - 新增 index.tsx 作为应用的主入口文件 - 新增 index.config.ts 用于应用的配置管理" />
|
||||||
<MESSAGE value="fix(tailwind): 更新配置以兼容微信小程序 - 启用 autoprefixer 并新增 postcss-nested 插件支持嵌套语法 - 扩展主题颜色,添加 primary 色系 - 更新 content 路径包含 public/index.html - 禁用 preflight 以移除默认样式重置 - 移除多数 corePlugins 禁用项,保留 preflight 禁用,简化兼容性配置" />
|
<MESSAGE value="fix(tailwind): 更新配置以兼容微信小程序 - 启用 autoprefixer 并新增 postcss-nested 插件支持嵌套语法 - 扩展主题颜色,添加 primary 色系 - 更新 content 路径包含 public/index.html - 禁用 preflight 以移除默认样式重置 - 移除多数 corePlugins 禁用项,保留 preflight 禁用,简化兼容性配置" />
|
||||||
@@ -555,7 +558,8 @@
|
|||||||
<MESSAGE value="feat(weapp): 新增开发者中心与平台管理入口及相关功能 - 小程序端新增开发者中心和平台管理页面模块,完善功能入口 - 新增开发者中心页面(概览、我的应用、API Key和工单) - 新增平台管理页面(管理首页、应用审核、Git权限审核、域名绑定审核和工单处理) - 复刻PC端/api/app/*相关API接口,基地址使用WEBSOPY_API_BASE_URL - 增加公共组件StatCard、EntryGrid、CellRow以支持新页面UI - 修改首页增加开发者中心和平台管理角色入口,仅对对应用户显示 - 用户页面菜单新增开发者中心和平台管理入口条件展示 - 约定开发者身份使用sys_user.is_developer布尔字段,不再依赖角色表 - 更新登录态业务,菜单是否显示基于登录接口返回" />
|
<MESSAGE value="feat(weapp): 新增开发者中心与平台管理入口及相关功能 - 小程序端新增开发者中心和平台管理页面模块,完善功能入口 - 新增开发者中心页面(概览、我的应用、API Key和工单) - 新增平台管理页面(管理首页、应用审核、Git权限审核、域名绑定审核和工单处理) - 复刻PC端/api/app/*相关API接口,基地址使用WEBSOPY_API_BASE_URL - 增加公共组件StatCard、EntryGrid、CellRow以支持新页面UI - 修改首页增加开发者中心和平台管理角色入口,仅对对应用户显示 - 用户页面菜单新增开发者中心和平台管理入口条件展示 - 约定开发者身份使用sys_user.is_developer布尔字段,不再依赖角色表 - 更新登录态业务,菜单是否显示基于登录接口返回" />
|
||||||
<MESSAGE value="feat(weapp): 新增开发者中心与平台管理入口及功能 - 小程序端新增开发者中心和平台管理页面模块,完善功能入口 - 新增开发者中心页面(概览、我的应用、API Key和工单) - 新增平台管理页面(管理首页、应用审核、Git权限审核、域名绑定审核和工单处理) - 复刻PC端/api/app/*相关API接口,基地址使用WEBSOPY_API_BASE_URL - 增加公共组件StatCard、EntryGrid、CellRow以支持新页面UI - 修改首页增加开发者中心和平台管理角色入口,仅对对应用户显示 - 用户页面菜单新增开发者中心和平台管理入口条件展示 - 约定开发者身份使用sys_user.is_developer布尔字段,不再依赖角色表 - 更新登录态业务,菜单是否显示基于登录接口返回的isDeveloper/isAdmin字段 - vip-review审核通过流程调整为只调用setUserDeveloper,后台替换角色为developer并置isDeveloper=1" />
|
<MESSAGE value="feat(weapp): 新增开发者中心与平台管理入口及功能 - 小程序端新增开发者中心和平台管理页面模块,完善功能入口 - 新增开发者中心页面(概览、我的应用、API Key和工单) - 新增平台管理页面(管理首页、应用审核、Git权限审核、域名绑定审核和工单处理) - 复刻PC端/api/app/*相关API接口,基地址使用WEBSOPY_API_BASE_URL - 增加公共组件StatCard、EntryGrid、CellRow以支持新页面UI - 修改首页增加开发者中心和平台管理角色入口,仅对对应用户显示 - 用户页面菜单新增开发者中心和平台管理入口条件展示 - 约定开发者身份使用sys_user.is_developer布尔字段,不再依赖角色表 - 更新登录态业务,菜单是否显示基于登录接口返回的isDeveloper/isAdmin字段 - vip-review审核通过流程调整为只调用setUserDeveloper,后台替换角色为developer并置isDeveloper=1" />
|
||||||
<MESSAGE value="fix(weapp): 修复支付页nutui组件导致内容空白问题 - 替换支付页中nutui的Button、Cell、Price、Divider组件为Taro原生组件或项目自定义组件 - 修正商品名称、价格类型和支付金额显示,使用flex布局及Text组件自绘内容 - 调整按钮样式,统一为带蓝色背景和圆角的自定义样式 - 移除nutui Price组件中的thousands属性,使用项目自定义Price组件 - 解决微信小程序中nutui组件因div渲染缺失导致UI元素不显示的问题 - 保持支付流程及状态逻辑无改动,仅优化展示兼容性和稳定性" />
|
<MESSAGE value="fix(weapp): 修复支付页nutui组件导致内容空白问题 - 替换支付页中nutui的Button、Cell、Price、Divider组件为Taro原生组件或项目自定义组件 - 修正商品名称、价格类型和支付金额显示,使用flex布局及Text组件自绘内容 - 调整按钮样式,统一为带蓝色背景和圆角的自定义样式 - 移除nutui Price组件中的thousands属性,使用项目自定义Price组件 - 解决微信小程序中nutui组件因div渲染缺失导致UI元素不显示的问题 - 保持支付流程及状态逻辑无改动,仅优化展示兼容性和稳定性" />
|
||||||
<option name="LAST_COMMIT_MESSAGE" value="fix(weapp): 修复支付页nutui组件导致内容空白问题 - 替换支付页中nutui的Button、Cell、Price、Divider组件为Taro原生组件或项目自定义组件 - 修正商品名称、价格类型和支付金额显示,使用flex布局及Text组件自绘内容 - 调整按钮样式,统一为带蓝色背景和圆角的自定义样式 - 移除nutui Price组件中的thousands属性,使用项目自定义Price组件 - 解决微信小程序中nutui组件因div渲染缺失导致UI元素不显示的问题 - 保持支付流程及状态逻辑无改动,仅优化展示兼容性和稳定性" />
|
<MESSAGE value="feat(user): 补充开发者审核通过时添加店员记录功能 - 引入 addShopStoreUser 接口用于新增门店店员记录 - 新增 clerkStoreId 和 clerkTenantId 状态,保存当前审核店员的门店和租户信息 - 在审核通过后调用 addShopStoreUser,添加申请人为当前门店店员 - 保证只有当 clerkStoreId 存在时才执行新增操作 - 更新工作记录文档,补充审核通过流程中新添加的店员记录步骤 - 保持原有升级开发者逻辑和接口调用不变" />
|
||||||
|
<option name="LAST_COMMIT_MESSAGE" value="feat(user): 补充开发者审核通过时添加店员记录功能 - 引入 addShopStoreUser 接口用于新增门店店员记录 - 新增 clerkStoreId 和 clerkTenantId 状态,保存当前审核店员的门店和租户信息 - 在审核通过后调用 addShopStoreUser,添加申请人为当前门店店员 - 保证只有当 clerkStoreId 存在时才执行新增操作 - 更新工作记录文档,补充审核通过流程中新添加的店员记录步骤 - 保持原有升级开发者逻辑和接口调用不变" />
|
||||||
<option name="NON_MODAL_COMMIT_POSTPONE_SLOW_CHECKS" value="false" />
|
<option name="NON_MODAL_COMMIT_POSTPONE_SLOW_CHECKS" value="false" />
|
||||||
</component>
|
</component>
|
||||||
</project>
|
</project>
|
||||||
@@ -32,3 +32,30 @@
|
|||||||
- `handleApprove` 在 `setUserDeveloper` 之后调 `addShopStoreUser({ userId, storeId: clerkStoreId, tenantId: clerkTenantId, isDelete: 0, comments: '开发者审核通过自动添加' })`,仅当 `clerkStoreId` 存在时执行。
|
- `handleApprove` 在 `setUserDeveloper` 之后调 `addShopStoreUser({ userId, storeId: clerkStoreId, tenantId: clerkTenantId, isDelete: 0, comments: '开发者审核通过自动添加' })`,仅当 `clerkStoreId` 存在时执行。
|
||||||
|
|
||||||
**依赖**:后端 `POST /shop/shop-store-user`(ShopStoreUserController.save,需 `shop:shopStoreUser:save` 权限,店员已有);tenantId 复用审核店员的租户,避免前端另取。
|
**依赖**:后端 `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 / 渐变头部)。
|
||||||
@@ -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 审核通过流程(用户 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 仍对应这些旧名,重命名需谨慎。
|
- 注意:前端 `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)
|
### 后端代码风格(com.gxwebsoft.core)
|
||||||
- Controller 继承 `BaseController`,统一返回 `ApiResult`(成功 `code=0`,失败 `code=1`),用 `success()/fail()`。
|
- Controller 继承 `BaseController`,统一返回 `ApiResult`(成功 `code=0`,失败 `code=1`),用 `success()/fail()`。
|
||||||
- 「按 userId 置一个标志位」的范式:`new User(){{setUserId(); setXxx();}}` → `userService.updateById(u)`(参考 `updateStatus`/`updateRecommend`/`auditUser`)。
|
- 「按 userId 置一个标志位」的范式:`new User(){{setUserId(); setXxx();}}` → `userService.updateById(u)`(参考 `updateStatus`/`updateRecommend`/`auditUser`)。
|
||||||
|
|||||||
@@ -1,104 +1,241 @@
|
|||||||
import React, { useState, useEffect, useCallback } from 'react'
|
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 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 { useUser } from '@/hooks/useUser'
|
||||||
import { pageAllApps, pagePublishReviews } from '@/api/app/appProduct'
|
import { pageAllApps, pagePublishReviews } from '@/api/app/appProduct'
|
||||||
import { getAllTickets } from '@/api/app/ticket'
|
import { getAllTickets } from '@/api/app/ticket'
|
||||||
import { pageUsers } from '@/api/system/user'
|
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 FEATURE_CARDS = [
|
||||||
const isAdmin = !!(user as any)?.isAdmin
|
{
|
||||||
const [loading, setLoading] = useState(true)
|
key: 'app-review',
|
||||||
const [stats, setStats] = useState({ apps: 0, pending: 0, tickets: 0, users: 0 })
|
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 () => {
|
const load = useCallback(async () => {
|
||||||
setLoading(true)
|
setDataLoading(true)
|
||||||
try {
|
try {
|
||||||
const [appsR, pendR, tickR, usersR] = await Promise.allSettled([
|
const [appsR, pendR, allR, usersR] = await Promise.allSettled([
|
||||||
pageAllApps({ page: 1, limit: 1 }),
|
pageAllApps({ page: 1, limit: 1 }),
|
||||||
pagePublishReviews({ 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),
|
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
|
// 待审核应用(pagePublishReviews 已按 pending_review 过滤)
|
||||||
const tickets = tickR.status === 'fulfilled' ? getPageTotal(tickR.value) : 0
|
const pendingAppsCount = pendR.status === 'fulfilled' ? getPageTotal(pendR.value) : 0
|
||||||
const users = usersR.status === 'fulfilled' ? getPageTotal(usersR.value) : 0
|
setPendingApps(pendingAppsCount)
|
||||||
setStats({ apps, pending, tickets, users })
|
|
||||||
} catch (e) {
|
// 工单:总数 + 待处理(未解决/未关闭)
|
||||||
|
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
|
// ignore
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false)
|
setDataLoading(false)
|
||||||
}
|
}
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
useEffect(() => {
|
/** 获取卡片角标数字 */
|
||||||
if (isLoggedIn) load()
|
const getBadgeCount = (key: string) => {
|
||||||
}, [isLoggedIn, load])
|
if (key === 'app-review') return pendingApps
|
||||||
|
if (key === 'tickets') return pendingTickets
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
if (userLoading || dataLoading) {
|
||||||
|
return (
|
||||||
|
<View className='min-h-full bg-gray-50 flex items-center justify-center'>
|
||||||
|
<Text className='text-gray-400'>加载中...</Text>
|
||||||
|
</View>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
if (!isLoggedIn) {
|
if (!isLoggedIn) {
|
||||||
return (
|
return (
|
||||||
<View className='min-h-screen bg-gray-100'>
|
<View className='min-h-full bg-gray-50 flex flex-col items-center justify-center py-24'>
|
||||||
<NavBar title='平台管理' />
|
<Text className='text-5xl mb-3'>🔐</Text>
|
||||||
<View className='flex flex-col items-center justify-center py-24'>
|
<Text className='text-base text-gray-700 mb-2'>请先登录</Text>
|
||||||
<Text className='text-gray-400 text-sm'>请先登录</Text>
|
<Text className='text-xs text-gray-400 text-center'>登录后即可进入平台管理</Text>
|
||||||
</View>
|
|
||||||
</View>
|
</View>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isAdmin) {
|
if (!isSuperAdmin) {
|
||||||
return (
|
return (
|
||||||
<View className='min-h-screen bg-gray-100'>
|
<View className='min-h-full bg-gray-50 flex flex-col items-center justify-center py-20 px-10'>
|
||||||
<NavBar title='平台管理' />
|
|
||||||
<View className='flex flex-col items-center justify-center py-20 px-10'>
|
|
||||||
<Text className='text-5xl mb-3'>🛡️</Text>
|
<Text className='text-5xl mb-3'>🛡️</Text>
|
||||||
<Text className='text-base text-gray-700 mb-2'>无访问权限</Text>
|
<Text className='text-base text-gray-700 mb-2'>无访问权限</Text>
|
||||||
<Text className='text-xs text-gray-400 text-center'>平台管理仅对管理员开放</Text>
|
<Text className='text-xs text-gray-400 text-center'>平台管理仅对超级管理员开放</Text>
|
||||||
</View>
|
|
||||||
</View>
|
</View>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const entries = [
|
const adminName = (user as any)?.nickname || (user as any)?.username || '平台管理员'
|
||||||
{ icon: '📝', label: '应用审核', url: '/pages/admin/app-review/index' },
|
const adminPhone = (user as any)?.phone
|
||||||
{ icon: '🐙', label: 'Git 审核', url: '/pages/admin/git-review/index' },
|
|
||||||
{ icon: '🌐', label: '域名审核', url: '/pages/admin/domain-review/index' },
|
|
||||||
{ icon: '🎫', label: '工单处理', url: '/pages/admin/tickets/index' },
|
|
||||||
]
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View className='min-h-screen bg-gray-100'>
|
<View className='min-h-full bg-gray-50'>
|
||||||
<NavBar title='平台管理' />
|
|
||||||
<ScrollView scrollY>
|
{/* 顶部信息区:管理员头像 + 平台名称(与门店中心同款渐变头部) */}
|
||||||
{loading ? (
|
<View
|
||||||
<Loading />
|
className='pt-8 pb-12 px-5 rounded-b-3xl relative overflow-hidden'
|
||||||
|
style={{ background: 'linear-gradient(135deg, #15803d 0%, #22c55e 60%, #4ade80 100%)' }}
|
||||||
|
>
|
||||||
|
<View className='absolute -top-10 -right-10 w-40 h-40 rounded-full opacity-10'
|
||||||
|
style={{ background: 'radial-gradient(circle, #ffffff, transparent)' }} />
|
||||||
|
<View className='absolute -bottom-6 -left-6 w-24 h-24 rounded-full opacity-15'
|
||||||
|
style={{ background: 'radial-gradient(circle, #ffffff, transparent)' }} />
|
||||||
|
<View className='relative z-10 flex items-center gap-4'>
|
||||||
|
{/* 管理员头像 */}
|
||||||
|
<View className='w-16 h-16 rounded-full bg-white/20 border-2 border-white/40 overflow-hidden flex-shrink-0'>
|
||||||
|
{((user as any)?.avatar) ? (
|
||||||
|
<Image
|
||||||
|
className='w-full h-full'
|
||||||
|
src={(user as any).avatar}
|
||||||
|
mode='aspectFill'
|
||||||
|
/>
|
||||||
) : (
|
) : (
|
||||||
<View className='p-4'>
|
<View className='w-full h-full flex items-center justify-center'>
|
||||||
<View className='grid grid-cols-2 gap-3 mb-3'>
|
<Text className='text-2xl'>👤</Text>
|
||||||
<StatCard label='应用总数' value={stats.apps} color='#3b82f6' />
|
|
||||||
<StatCard label='待审核应用' value={stats.pending} color='#f59e0b' sub={stats.pending > 0 ? '需处理' : undefined} subColor='#f59e0b' />
|
|
||||||
</View>
|
|
||||||
<View className='grid grid-cols-2 gap-3 mb-3'>
|
|
||||||
<StatCard label='工单总数' value={stats.tickets} color='#10b981' />
|
|
||||||
<StatCard label='用户总数' value={stats.users} color='#8b5cf6' />
|
|
||||||
</View>
|
|
||||||
<Text className='text-base font-medium mb-2 block'>审核与处理</Text>
|
|
||||||
<EntryGrid items={entries} columns={4} />
|
|
||||||
<View className='h-6' />
|
|
||||||
</View>
|
</View>
|
||||||
)}
|
)}
|
||||||
</ScrollView>
|
</View>
|
||||||
|
|
||||||
|
{/* 平台信息 */}
|
||||||
|
<View className='flex-1 min-w-0'>
|
||||||
|
<Text className='text-white text-lg font-bold block truncate'>
|
||||||
|
平台管理
|
||||||
|
</Text>
|
||||||
|
<Text className='text-white text-opacity-80 text-sm mt-1 block truncate'>
|
||||||
|
{adminName}{adminPhone ? ` · ${adminPhone}` : ''}
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{/* 功能卡片区(与门店中心同款) */}
|
||||||
|
<View className='mx-3 -mt-6 relative z-20'>
|
||||||
|
|
||||||
|
<View className='flex flex-col gap-3'>
|
||||||
|
{FEATURE_CARDS.map(card => (
|
||||||
|
<View
|
||||||
|
key={card.key}
|
||||||
|
className='bg-white rounded-xl p-4 flex items-center gap-4 active:opacity-80 relative'
|
||||||
|
onClick={() => Taro.navigateTo({ url: card.url })}
|
||||||
|
>
|
||||||
|
{/* 图标 */}
|
||||||
|
<View
|
||||||
|
className='w-12 h-12 rounded-xl flex items-center justify-center flex-shrink-0'
|
||||||
|
style={{ background: card.bgColor }}
|
||||||
|
>
|
||||||
|
<Text className='text-2xl'>{card.icon}</Text>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{/* 文字 */}
|
||||||
|
<View className='flex-1 min-w-0'>
|
||||||
|
<Text className='text-gray-800 text-base font-medium block'>{card.title}</Text>
|
||||||
|
<Text className='text-gray-400 text-xs mt-0.5 block'>{card.desc}</Text>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{/* 待处理角标 */}
|
||||||
|
{card.showBadge && (
|
||||||
|
<Badge count={getBadgeCount(card.key)} className='absolute -top-1 -right-1' size={20} fontSize={11} fontWeight='bold' />
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* 箭头 */}
|
||||||
|
<Text className='text-gray-300 text-lg flex-shrink-0'>›</Text>
|
||||||
|
</View>
|
||||||
|
))}
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{/* 底部:平台概况(保留原页面统计信息,沿用同款卡片样式) */}
|
||||||
|
<View className='mx-3 mt-6 mb-6'>
|
||||||
|
<View className='bg-white rounded-xl p-4 flex items-center gap-3'>
|
||||||
|
<View className='w-10 h-10 rounded-full bg-green-50 flex items-center justify-center flex-shrink-0'>
|
||||||
|
<Text className='text-xl'>📊</Text>
|
||||||
|
</View>
|
||||||
|
<View className='flex-1 min-w-0'>
|
||||||
|
<Text className='text-gray-800 text-sm font-medium block'>平台概况</Text>
|
||||||
|
<Text className='text-gray-400 text-xs mt-0.5 block'>
|
||||||
|
应用 {overview.apps} · 用户 {overview.users} · 工单 {overview.tickets}
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
</View>
|
</View>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default AdminCenterPage
|
|
||||||
|
|||||||
@@ -255,8 +255,8 @@ const IndexPage: React.FC = () => {
|
|||||||
</View>
|
</View>
|
||||||
|
|
||||||
<ScrollView scrollY style={{ height: scrollHeight }}>
|
<ScrollView scrollY style={{ height: scrollHeight }}>
|
||||||
{/* 角色工作台入口:仅开发者 / 平台管理员可见 */}
|
{/* 角色工作台入口:仅开发者 / 平台管理员 / 超级管理员可见 */}
|
||||||
{(isLoggedIn && ((user as any)?.isDeveloper || (user as any)?.isAdmin)) && (
|
{(isLoggedIn && ((user as any)?.isDeveloper || (user as any)?.isAdmin || (user as any)?.isSuperAdmin === 1)) && (
|
||||||
<View className='mx-3 mt-2 grid grid-cols-2 gap-2'>
|
<View className='mx-3 mt-2 grid grid-cols-2 gap-2'>
|
||||||
{(user as any)?.isDeveloper && (
|
{(user as any)?.isDeveloper && (
|
||||||
<View
|
<View
|
||||||
@@ -268,7 +268,7 @@ const IndexPage: React.FC = () => {
|
|||||||
<Text className='text-sm font-medium text-blue-700'>开发者中心</Text>
|
<Text className='text-sm font-medium text-blue-700'>开发者中心</Text>
|
||||||
</View>
|
</View>
|
||||||
)}
|
)}
|
||||||
{(user as any)?.isAdmin && (
|
{(user as any)?.isSuperAdmin === 1 && (
|
||||||
<View
|
<View
|
||||||
className='flex items-center justify-center py-3 rounded-lg border border-purple-100'
|
className='flex items-center justify-center py-3 rounded-lg border border-purple-100'
|
||||||
style={{ background: 'linear-gradient(135deg, #f5f3ff 0%, #ede9fe 100%)' }}
|
style={{ background: 'linear-gradient(135deg, #f5f3ff 0%, #ede9fe 100%)' }}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { getMyClerk } from '@/api/shop/shopStoreUser'
|
|||||||
import { listShopDealerApply } from '@/api/shop/shopDealerApply'
|
import { listShopDealerApply } from '@/api/shop/shopDealerApply'
|
||||||
import { pageShopOrder } from '@/api/shop/shopOrder'
|
import { pageShopOrder } from '@/api/shop/shopOrder'
|
||||||
import Badge from '@/components/common/Badge'
|
import Badge from '@/components/common/Badge'
|
||||||
|
import { useUser } from '@/hooks/useUser'
|
||||||
|
|
||||||
definePageConfig({
|
definePageConfig({
|
||||||
navigationBarTitleText: '门店中心',
|
navigationBarTitleText: '门店中心',
|
||||||
@@ -61,30 +62,28 @@ const SUBSCRIBE_TMPL_IDS = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
export default function StoreCenterPage() {
|
export default function StoreCenterPage() {
|
||||||
|
const { user, isLoggedIn, loading: userLoading } = useUser()
|
||||||
|
const isSuperAdmin = (user as any)?.isSuperAdmin === 1
|
||||||
const [storeInfo, setStoreInfo] = useState<any>(null)
|
const [storeInfo, setStoreInfo] = useState<any>(null)
|
||||||
const [pendingVipCount, setPendingVipCount] = useState(0)
|
const [pendingVipCount, setPendingVipCount] = useState(0)
|
||||||
const [pendingOrderCount, setPendingOrderCount] = useState(0)
|
const [pendingOrderCount, setPendingOrderCount] = useState(0)
|
||||||
const [loading, setLoading] = useState(true)
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// 验证店员身份
|
// 仅超级管理员可访问
|
||||||
getMyClerk()
|
if (!isLoggedIn || !isSuperAdmin) {
|
||||||
.then(data => {
|
if (isLoggedIn && !isSuperAdmin) {
|
||||||
if (!data) {
|
Taro.showToast({ title: '仅超级管理员可访问', icon: 'none' })
|
||||||
Taro.showToast({ title: '仅门店店员可访问', icon: 'none' })
|
|
||||||
setTimeout(() => Taro.navigateBack(), 1500)
|
setTimeout(() => Taro.navigateBack(), 1500)
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
setStoreInfo(data)
|
// 超管若同时具备店员身份,加载门店信息用于头部展示(非必须)
|
||||||
|
getMyClerk()
|
||||||
|
.then(data => setStoreInfo(data || null))
|
||||||
|
.catch(() => setStoreInfo(null))
|
||||||
// 加载待审核数量
|
// 加载待审核数量
|
||||||
loadBadgeCounts()
|
loadBadgeCounts()
|
||||||
})
|
}, [isLoggedIn, isSuperAdmin])
|
||||||
.catch(() => {
|
|
||||||
Taro.showToast({ title: '仅门店店员可访问', icon: 'none' })
|
|
||||||
setTimeout(() => Taro.navigateBack(), 1500)
|
|
||||||
})
|
|
||||||
.finally(() => setLoading(false))
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
/** 加载各类待处理角标数量 */
|
/** 加载各类待处理角标数量 */
|
||||||
const loadBadgeCounts = async () => {
|
const loadBadgeCounts = async () => {
|
||||||
@@ -140,7 +139,7 @@ export default function StoreCenterPage() {
|
|||||||
})
|
})
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
if (loading) {
|
if (userLoading) {
|
||||||
return (
|
return (
|
||||||
<View className='min-h-full bg-gray-50 flex items-center justify-center'>
|
<View className='min-h-full bg-gray-50 flex items-center justify-center'>
|
||||||
<Text className='text-gray-400'>加载中...</Text>
|
<Text className='text-gray-400'>加载中...</Text>
|
||||||
@@ -148,12 +147,43 @@ export default function StoreCenterPage() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!storeInfo) return null
|
if (!isLoggedIn) {
|
||||||
|
return (
|
||||||
|
<View className='min-h-full bg-gray-50 flex flex-col items-center justify-center py-24'>
|
||||||
|
<Text className='text-5xl mb-3'>🔐</Text>
|
||||||
|
<Text className='text-base text-gray-700 mb-2'>请先登录</Text>
|
||||||
|
<Text className='text-xs text-gray-400 text-center'>登录后方可进入门店中心</Text>
|
||||||
|
</View>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isSuperAdmin) {
|
||||||
|
return (
|
||||||
|
<View className='min-h-full bg-gray-50 flex flex-col items-center justify-center py-20 px-10'>
|
||||||
|
<Text className='text-5xl mb-3'>🛡️</Text>
|
||||||
|
<Text className='text-base text-gray-700 mb-2'>无访问权限</Text>
|
||||||
|
<Text className='text-xs text-gray-400 text-center'>门店中心仅对超级管理员开放</Text>
|
||||||
|
</View>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 头部展示信息:超管同时是店员则展示门店,否则展示管理员身份兜底
|
||||||
|
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 (
|
return (
|
||||||
<View className='min-h-full bg-gray-50'>
|
<View className='min-h-full bg-gray-50'>
|
||||||
|
|
||||||
{/* 顶部信息区:店员头像 + 门店名称 */}
|
{/* 顶部信息区:头像 + 名称 */}
|
||||||
<View
|
<View
|
||||||
className='pt-8 pb-12 px-5 rounded-b-3xl relative overflow-hidden'
|
className='pt-8 pb-12 px-5 rounded-b-3xl relative overflow-hidden'
|
||||||
style={{ background: 'linear-gradient(135deg, #15803d 0%, #22c55e 60%, #4ade80 100%)' }}
|
style={{ background: 'linear-gradient(135deg, #15803d 0%, #22c55e 60%, #4ade80 100%)' }}
|
||||||
@@ -163,12 +193,12 @@ export default function StoreCenterPage() {
|
|||||||
<View className='absolute -bottom-6 -left-6 w-24 h-24 rounded-full opacity-15'
|
<View className='absolute -bottom-6 -left-6 w-24 h-24 rounded-full opacity-15'
|
||||||
style={{ background: 'radial-gradient(circle, #ffffff, transparent)' }} />
|
style={{ background: 'radial-gradient(circle, #ffffff, transparent)' }} />
|
||||||
<View className='relative z-10 flex items-center gap-4'>
|
<View className='relative z-10 flex items-center gap-4'>
|
||||||
{/* 店员头像 */}
|
{/* 头像 */}
|
||||||
<View className='w-16 h-16 rounded-full bg-white/20 border-2 border-white/40 overflow-hidden flex-shrink-0'>
|
<View className='w-16 h-16 rounded-full bg-white/20 border-2 border-white/40 overflow-hidden flex-shrink-0'>
|
||||||
{storeInfo.avatar ? (
|
{header.avatar ? (
|
||||||
<Image
|
<Image
|
||||||
className='w-full h-full'
|
className='w-full h-full'
|
||||||
src={storeInfo.avatar}
|
src={header.avatar}
|
||||||
mode='aspectFill'
|
mode='aspectFill'
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
@@ -178,13 +208,13 @@ export default function StoreCenterPage() {
|
|||||||
)}
|
)}
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
{/* 门店信息 */}
|
{/* 信息 */}
|
||||||
<View className='flex-1 min-w-0'>
|
<View className='flex-1 min-w-0'>
|
||||||
<Text className='text-white text-lg font-bold block truncate'>
|
<Text className='text-white text-lg font-bold block truncate'>
|
||||||
{storeInfo.storeName || '门店中心'}
|
{header.title}
|
||||||
</Text>
|
</Text>
|
||||||
<Text className='text-white text-opacity-80 text-sm mt-1 block truncate'>
|
<Text className='text-white text-opacity-80 text-sm mt-1 block truncate'>
|
||||||
{storeInfo.name} {storeInfo.phone ? `· ${storeInfo.phone}` : ''}
|
{header.subtitle}
|
||||||
</Text>
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
|
|||||||
@@ -105,10 +105,10 @@ const UserPage: React.FC = () => {
|
|||||||
{ icon: '🔰', label: '申请成为开发者', url: '/pages/user/vip-upgrade/index', highlight: true, requireAuth: true },
|
{ icon: '🔰', label: '申请成为开发者', url: '/pages/user/vip-upgrade/index', highlight: true, requireAuth: true },
|
||||||
// 开发者中心:仅 isDeveloper 用户显示
|
// 开发者中心:仅 isDeveloper 用户显示
|
||||||
...((user as any)?.isDeveloper ? [{ icon: '🛠️', label: '开发者中心', url: '/pages/developer/index/index', requireAuth: true }] : []),
|
...((user as any)?.isDeveloper ? [{ icon: '🛠️', label: '开发者中心', url: '/pages/developer/index/index', requireAuth: true }] : []),
|
||||||
// 平台管理:仅 isAdmin 用户显示
|
// 平台管理:仅 isSuperAdmin=1 用户显示
|
||||||
...((user as any)?.isAdmin ? [{ icon: '🛡️', label: '平台管理', url: '/pages/admin/index/index', requireAuth: true }] : []),
|
...((user as any)?.isSuperAdmin === 1 ? [{ icon: '🛡️', label: '平台管理', url: '/pages/admin/index/index', requireAuth: true }] : []),
|
||||||
// 门店中心:仅门店店员/店长显示(通过 /shop/shop-store-user/my 判断)
|
// 门店中心:仅 isSuperAdmin=1 用户显示
|
||||||
...(storeInfo ? [{ icon: '🏪', label: '门店中心', url: '/pages/store/center/index', requireAuth: true }] : []),
|
...((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/wallet', requireAuth: true },
|
||||||
{ icon: '📍', label: '收货地址', url: '/pages/user/address-list', requireAuth: true },
|
{ icon: '📍', label: '收货地址', url: '/pages/user/address-list', requireAuth: true },
|
||||||
// { icon: '⭐', label: '积分明细', url: '/pages/user/points-record' },
|
// { icon: '⭐', label: '积分明细', url: '/pages/user/points-record' },
|
||||||
|
|||||||
Reference in New Issue
Block a user