13 Commits

Author SHA1 Message Date
efcbaa5b60 feat(user): 合并切换开发者菜单并优化首页轮播图显示
- 用户菜单根据 isDeveloper 字段切换显示申请或进入开发者中心
- 已审核用户菜单高亮显示“进入开发者中心”
- 去除冗余开发者中心菜单条目,简化结构
- 重新设计开发者中心页面UI,统一门店中心风格
- 顶部展示开发者头像及昵称手机号信息
- 功能卡片展示“我的应用”、“API Key”和“我的工单”,异步加载数量角标
- 底部添加“开发者帮助与支持”入口,替代门店订阅消息卡片
- 未登录或非开发者状态显示相应空态及操作按钮
- 首页轮播图为空时不显示容器,功能入口自动上移,优化空状态体验
- 优化登录状态与权限校验逻辑,防止页面闪烁和无权限展示
2026-07-24 14:19:25 +08:00
0713ad4cf7 feat(user): 合并切换开发者菜单并优化首页轮播图显示
- 用户菜单根据 isDeveloper 字段切换显示申请或进入开发者中心
- 已审核用户菜单高亮显示“进入开发者中心”
- 去除冗余开发者中心菜单条目,简化结构
- 重新设计开发者中心页面UI,统一门店中心风格
- 顶部展示开发者头像及昵称手机号信息
- 功能卡片展示“我的应用”、“API Key”和“我的工单”,异步加载数量角标
- 底部添加“开发者帮助与支持”入口,替代门店订阅消息卡片
- 未登录或非开发者状态显示相应空态及操作按钮
- 首页轮播图为空时不显示容器,功能入口自动上移,优化空状态体验
- 优化登录状态与权限校验逻辑,防止页面闪烁和无权限展示
2026-07-23 18:39:49 +08:00
226e223d96 feat(user): 合并切换开发者菜单项状态并更新开发者中心UI
- 用户菜单中根据 isDeveloper 字段条件展示“申请成为开发者”或“进入开发者中心”
- 已审核通过用户菜单显示“进入开发者中心”,并设置高亮样式
- 去除冗余的独立“开发者中心”菜单条目,简化菜单结构
- 重新设计开发者中心页面UI,统一为门店中心风格
- 顶部展示开发者头像及昵称手机号信息
- 功能卡片展示“我的应用”、“API Key”和“我的工单”,异步加载数量角标
- 底部添加“开发者帮助与支持”入口,替代门店订阅消息卡片
- 未登录或非开发者状态显示相应空态提示及操作按钮
- 优化登录状态与权限校验逻辑,防止页面闪烁和无权限内容显示
2026-07-23 18:35:53 +08:00
dc72e530c7 fix(auth): 统一超级管理员标识为布尔值true
- 将所有页面和菜单中超级管理员校验由数字1改为布尔true
- 平台管理、门店中心菜单项仅在isSuperAdmin===true时显示
- 门店中心页面移除店员门槛,改为超级管理员访问限制
- 非超级管理员访问门店中心弹窗提示并返回
- 首页工作台入口及相关访问权限判定切换为布尔判断
- 优化首屏加载状态,避免权限校验闪烁和误判
2026-07-23 18:29:48 +08:00
5da7d17c55 feat(user): 合并并切换“申请开发者”菜单项状态
- 在用户菜单中根据 isDeveloper 字段切换显示“申请成为开发者”或“进入开发者中心”
- 已通过审核用户直接显示“进入开发者中心”且高亮显示
- 去除冗余的独立“开发者中心”菜单条目,简化菜单结构
- 调整平台管理页和门店中心入口权限为仅超级管理员可访问
- 优化登录与权限校验流程,防止权限不足时页面闪烁或无访问权限空白
- 更新相关文档,完善审核通过后店员记录添加流程
2026-07-23 18:27:13 +08:00
9fba545d34 feat(user): 补充开发者审核通过时添加店员记录功能
- 引入 addShopStoreUser 接口用于新增门店店员记录
- 新增 clerkStoreId 和 clerkTenantId 状态,保存当前审核店员的门店和租户信息
- 在审核通过后调用 addShopStoreUser,添加申请人为当前门店店员
- 保证只有当 clerkStoreId 存在时才执行新增操作
- 调整平台管理页 UI,使其与门店中心保持一致风格
- 门店中心及平台管理入口权限调整,仅超级管理员可访问
- 用户菜单中平台管理和门店中心入口现基于 isSuperAdmin 字段控制
- 优化登录及权限校验流程,避免权限不足时页面闪烁显示
- 更新工作记录文档,补充审核通过流程中新添加的店员记录步骤
2026-07-23 18:25:35 +08:00
babcf36439 feat(user): 补充开发者审核通过时添加店员记录功能
- 引入 addShopStoreUser 接口用于新增门店店员记录
- 新增 clerkStoreId 和 clerkTenantId 状态,保存当前审核店员的门店和租户信息
- 在审核通过后调用 addShopStoreUser,添加申请人为当前门店店员
- 保证只有当 clerkStoreId 存在时才执行新增操作
- 更新工作记录文档,补充审核通过流程中新添加的店员记录步骤
- 保持原有升级开发者逻辑和接口调用不变
2026-07-23 17:59:22 +08:00
a703dcad90 fix(weapp): 修复支付页nutui组件导致内容空白问题
- 替换支付页中nutui的Button、Cell、Price、Divider组件为Taro原生组件或项目自定义组件
- 修正商品名称、价格类型和支付金额显示,使用flex布局及Text组件自绘内容
- 调整按钮样式,统一为带蓝色背景和圆角的自定义样式
- 移除nutui Price组件中的thousands属性,使用项目自定义Price组件
- 解决微信小程序中nutui组件因div渲染缺失导致UI元素不显示的问题
- 保持支付流程及状态逻辑无改动,仅优化展示兼容性和稳定性
2026-07-23 17:48:30 +08:00
b673e2ebac fix(weapp): 修复支付页nutui组件导致内容空白问题
- 替换支付页中nutui的Button、Cell、Price、Divider组件为Taro原生组件或项目自定义组件
- 修正商品名称、价格类型和支付金额显示,使用flex布局及Text组件自绘内容
- 调整按钮样式,统一为带蓝色背景和圆角的自定义样式
- 移除nutui Price组件中的thousands属性,使用项目自定义Price组件
- 解决微信小程序中nutui组件因div渲染缺失导致UI元素不显示的问题
- 保持支付流程及状态逻辑无改动,仅优化展示兼容性和稳定性
2026-07-23 17:45:50 +08:00
e986222ca7 fix(weapp): 修复支付页nutui组件导致内容空白问题
- 替换支付页中nutui的Button、Cell、Price、Divider组件为Taro原生组件或项目自定义组件
- 修正商品名称、价格类型和支付金额显示,使用flex布局及Text组件自绘内容
- 调整按钮样式,统一为带蓝色背景和圆角的自定义样式
- 移除nutui Price组件中的thousands属性,使用项目自定义Price组件
- 解决微信小程序中nutui组件因div渲染缺失导致UI元素不显示的问题
- 保持支付流程及状态逻辑无改动,仅优化展示兼容性和稳定性
2026-07-23 17:23:21 +08:00
2ec3383fd0 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
2026-07-22 16:42:15 +08:00
ab1b32af68 feat(weapp): 新增开发者中心与平台管理入口及相关功能
- 小程序端新增开发者中心和平台管理页面模块,完善功能入口
- 新增开发者中心页面(概览、我的应用、API Key和工单)
- 新增平台管理页面(管理首页、应用审核、Git权限审核、域名绑定审核和工单处理)
- 复刻PC端/api/app/*相关API接口,基地址使用WEBSOPY_API_BASE_URL
- 增加公共组件StatCard、EntryGrid、CellRow以支持新页面UI
- 修改首页增加开发者中心和平台管理角色入口,仅对对应用户显示
- 用户页面菜单新增开发者中心和平台管理入口条件展示
- 约定开发者身份使用sys_user.is_developer布尔字段,不再依赖角色表
- 更新登录态业务,菜单是否显示基于登录接口返回
2026-07-22 16:02:21 +08:00
5b5b7c9a90 refactor(user): 将VIP身份统一改为开发者标记
- 审核通过时不再新增角色,而是更新用户isDeveloper布尔标记
- 后端UserService新增markAsDeveloper方法,通过updateById设置isDeveloper
- UserController新增PUT接口更新开发者标记,权限使用sys:userRole:save
- 前端api新增setUserDeveloper调用新的审核接口
- vip-review页面移除角色相关代码,改为调用setUserDeveloper方法
- 约定开发者身份即sys_user.is_developer字段,不再依赖角色表记录
- app.config.ts追加开发者中心与平台管理相关页面路由
- 新增开发者中心、平台管理的多个API模块(apikey、appProduct、appDomain、developer请求等)
- 添加公共工具方法和复用组件用于开发者中心及平台管理功能
- 统一品牌名称为“威数谱软件”,更新文案及界面展现一致性
2026-07-22 15:10:20 +08:00
14 changed files with 1076 additions and 356 deletions

245
.idea/workspace.xml generated
View File

@@ -4,12 +4,8 @@
<option name="autoReloadType" value="SELECTIVE" />
</component>
<component name="ChangeListManager">
<list default="true" id="2de94e4f-0587-4c9b-8bfd-e87e0ae95b81" name="更改" comment="refactor(user): 统一品牌名称为“威数谱软件”&#10;&#10;- 将项目中所有“威硕谱软件”名称修改为“威数谱软件”&#10;- 更新协议页面、欢迎消息、礼品卡等多处文本内容&#10;- 修改小程序配置文件中的项目名称和显示标题&#10;- 优化开发者申请页部分文案,明确开发者身份描述&#10;- 保证所有界面及配置中的品牌名称统一一致">
<list default="true" id="2de94e4f-0587-4c9b-8bfd-e87e0ae95b81" name="更改" comment="feat(user): 合并切换开发者菜单并优化首页轮播图显示&#10;&#10;- 用户菜单根据 isDeveloper 字段切换显示申请或进入开发者中心&#10;- 已审核用户菜单高亮显示“进入开发者中心”&#10;- 去除冗余开发者中心菜单条目,简化结构&#10;- 重新设计开发者中心页面UI统一门店中心风格&#10;- 顶部展示开发者头像及昵称手机号信息&#10;- 功能卡片展示“我的应用”、“API Key”和“我的工单”异步加载数量角标&#10;- 底部添加“开发者帮助与支持”入口,替代门店订阅消息卡片&#10;- 未登录或非开发者状态显示相应空态及操作按钮&#10;- 首页轮播图为空时不显示容器,功能入口自动上移,优化空状态体验&#10;- 优化登录状态与权限校验逻辑,防止页面闪烁和无权限展示">
<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-22.md" beforeDir="false" afterPath="$PROJECT_DIR$/.workbuddy/memory/2026-07-22.md" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/api/system/user/index.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/api/system/user/index.ts" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/app.config.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/app.config.ts" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/pages/user/vip-review/index.tsx" beforeDir="false" afterPath="$PROJECT_DIR$/src/pages/user/vip-review/index.tsx" afterDir="false" />
</list>
<option name="SHOW_DIALOG" value="false" />
<option name="HIGHLIGHT_CONFLICTS" value="true" />
@@ -63,7 +59,7 @@
<recent name="$PROJECT_DIR$" />
</key>
</component>
<component name="RunManager" selected="npm.dev:weapp">
<component name="RunManager" selected="npm.build:weapp">
<configuration name="build:weapp" type="js.build_tools.npm" temporary="true" nameIsGenerated="true">
<package-json value="$PROJECT_DIR$/package.json" />
<command value="run" />
@@ -86,8 +82,8 @@
</configuration>
<recent_temporary>
<list>
<item itemvalue="npm.dev:weapp" />
<item itemvalue="npm.build:weapp" />
<item itemvalue="npm.dev:weapp" />
</list>
</recent_temporary>
</component>
@@ -121,111 +117,8 @@
<workItem from="1784131049625" duration="2304000" />
<workItem from="1784133736973" duration="4614000" />
<workItem from="1784585070286" duration="3047000" />
<workItem from="1784638429525" duration="10410000" />
</task>
<task id="LOCAL-00013" summary="refactor(api): 移除未使用的配置接口及相关Hook&#10;&#10;- 从cmsWebsiteField接口中删除configWebsiteField方法&#10;- 删除useConfig自定义Hook及其引用&#10;- 更新about页面去除对config对象和useConfig的使用&#10;- 修正invite模块请求URL统一加上/api前缀">
<option name="closed" value="true" />
<created>1775908391880</created>
<option name="number" value="00013" />
<option name="presentableId" value="LOCAL-00013" />
<option name="project" value="LOCAL" />
<updated>1775908391880</updated>
</task>
<task id="LOCAL-00014" summary="fix(invite): 修正邀请接口路径以支持小程序专用前缀&#10;&#10;- 将请求邀请信息接口路径改为使用 /api/_app 前缀,确保小程序免登录访问&#10;- 修改接受邀请接口路径,统一使用小程序专用 API 域名和前缀&#10;- 更新邀请相关接口注释,明确小程序专用接口前缀含义&#10;- 在专家历史数据中添加高级开发工程师 Will 的条目并更新时间戳">
<option name="closed" value="true" />
<created>1775909013060</created>
<option name="number" value="00014" />
<option name="presentableId" value="LOCAL-00014" />
<option name="project" value="LOCAL" />
<updated>1775909013060</updated>
</task>
<task id="LOCAL-00015" summary="fix(passport): 添加邀请接受接口调试日志&#10;&#10;- 在接受邀请请求前打印token和请求URL、参数&#10;- 在请求响应后输出接口响应及返回数据&#10;- 在调用失败时打印错误信息及异常日志&#10;- 保持请求流程和状态管理不变,增强调试能力">
<option name="closed" value="true" />
<created>1775909542310</created>
<option name="number" value="00015" />
<option name="presentableId" value="LOCAL-00015" />
<option name="project" value="LOCAL" />
<updated>1775909542310</updated>
</task>
<task id="LOCAL-00016" summary="fix(passport): 添加邀请接受接口调试日志&#10;&#10;- 在接受邀请请求前打印token和请求URL、参数&#10;- 在请求响应后输出接口响应及返回数据&#10;- 在调用失败时打印错误信息及异常日志&#10;- 保持请求流程和状态管理不变,增强调试能力">
<option name="closed" value="true" />
<created>1775909811705</created>
<option name="number" value="00016" />
<option name="presentableId" value="LOCAL-00016" />
<option name="project" value="LOCAL" />
<updated>1775909811705</updated>
</task>
<task id="LOCAL-00017" summary="fix(passport): 优化邀请码接受失败的用户提示和处理&#10;&#10;- 针对“用户不存在”、“用户创建失败”及“请先登录”提示,改为弹窗提示需登录&#10;- 在弹窗中增加“去登录”按钮点击后跳转到登录页面并携带重定向token参数&#10;- 其他错误仍使用原Toast提示方式展示错误信息&#10;- 保留错误日志打印,便于排查异常情况">
<option name="closed" value="true" />
<created>1775920899787</created>
<option name="number" value="00017" />
<option name="presentableId" value="LOCAL-00017" />
<option name="project" value="LOCAL" />
<updated>1775920899787</updated>
</task>
<task id="LOCAL-00018" summary="feat(passport-invite): 优化新用户扫码邀请加入流程&#10;&#10;- 扫码进入邀请页面时保存邀请 token 到本地存储&#10;- 新增登录状态检测,未登录用户点击快速加入时保存邀请信息并跳转登录页&#10;- 登录成功后返回邀请页自动执行加入应用操作,提升用户体验&#10;- 支持已登录用户直接加入应用,简化流程&#10;- 增加清理 pending 邀请数据的方法,防止数据残留&#10;- 监听页面显示事件,处理登录返回后自动操作&#10;- 调整接口请求逻辑,确保携带最新的邀请和授权数据&#10;- 更新项目配置,注册邀请页面路径信息">
<option name="closed" value="true" />
<created>1775921636640</created>
<option name="number" value="00018" />
<option name="presentableId" value="LOCAL-00018" />
<option name="project" value="LOCAL" />
<updated>1775921636640</updated>
</task>
<task id="LOCAL-00019" summary="feat(passport): 重写登录与邀请加入流程,支持微信手机号一键登录&#10;&#10;- 完全重写 passport/login.tsx实现微信手机号一键登录功能&#10;- 支持登录页面携带 redirect 参数,登录后自动跳转回原页面&#10;- 登录成功后自动处理邀请加入应用,支持邀请关系绑定&#10;- 重写 passport/invite/index.tsx实现扫码邀请加入应用完整流程&#10;- 支持调用 wx.login 获取 code使用 loginByOpenId 判断注册状态&#10;- 未注册用户自动跳转登录页完成微信手机号登录再加入应用&#10;- 已登录用户直接执行加入应用操作,加入接口支持 Authorization 头&#10;- 解决邀请页面与登录页面间旧授权码混用导致加入失败问题&#10;- 处理后端 401 错误,优先使用微信授权码免登录接口&#10;- 优化用户体验,添加服务协议与隐私政策弹窗及错误提示&#10;- 页面状态管理改进,加入加载、检查登录、错误和跳转反馈">
<option name="closed" value="true" />
<created>1775962292970</created>
<option name="number" value="00019" />
<option name="presentableId" value="LOCAL-00019" />
<option name="project" value="LOCAL" />
<updated>1775962292970</updated>
</task>
<task id="LOCAL-00020" summary="feat(invite): 重构邀请加入流程,实现已登录用户免授权直接加入&#10;&#10;- 完全重写邀请页逻辑,区分已登录与未注册用户按钮显示&#10;- 已登录用户显示“确认加入”按钮,点击用 wx.login 获取 code 加入&#10;- 未注册用户显示“微信手机号快速加入”按钮,授权手机号后完成注册登录再加入&#10;- 移除跳转登录页面逻辑,所有流程在邀请页内完成&#10;- 加入接口统一使用 Authorization 头携带 token确保身份双重验证&#10;- 优化背景、粒子和扫描线效果,提升界面视觉体验&#10;- 增加错误处理提示,加入失败时显示详细原因并可返回首页&#10;- 协议勾选仅在未注册用户显示,确保合规授权流程">
<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 id="LOCAL-00021" summary="feat(passport): 优化已登录用户加入时协议勾选逻辑&#10;&#10;- 已登录用户点击「确认加入」时不再强制勾选协议&#10;- 移除 handleConfirmJoin 中对协议勾选的检查&#10;- 未注册用户仍需勾选协议后才能授权手机号&#10;- 确保登录状态异常时有提示信息显示">
<option name="closed" value="true" />
<created>1775965478226</created>
<option name="number" value="00021" />
<option name="presentableId" value="LOCAL-00021" />
<option name="project" value="LOCAL" />
<updated>1775965478226</updated>
</task>
<task id="LOCAL-00022" summary="feat(invite): 支持已登录用户直接加入应用&#10;&#10;- 后端accept接口code参数改为可选支持通过Authorization头识别登录用户&#10;- 已登录用户加入时不再弹手机号授权前端调用新增的doJoinAppForLoggedInUser方法&#10;- 未注册用户仍通过手机号授权码code加入调用doJoinAppForNewUser方法&#10;- 拆分前端加入逻辑新增handleConfirmJoin统一处理已登录加入流程&#10;- 优化按钮交互,已登录状态下按钮变为普通点击,移除协议强制勾选&#10;- 错误处理和状态提示完善,加入成功后自动跳转首页">
<option name="closed" value="true" />
<created>1775966172496</created>
<option name="number" value="00022" />
<option name="presentableId" value="LOCAL-00022" />
<option name="project" value="LOCAL" />
<updated>1775966172496</updated>
</task>
<task id="LOCAL-00023" summary="feat(developer): 完成小程序开发者中心和企业控制台改造&#10;&#10;- 设计并实现了开发者中心与企业控制台两大模块&#10;- 按用户角色区分开发者和企业客户,支持多项目类型及成员管理&#10;- 新增项目管理、应用管理、API Key管理及成员邀请等多功能页面&#10;- 实现应用版本发布、消息通知中心、权限审批与开发者申请流程&#10;- 完成CI/CD流水线、运营监控、发票管理、SSO单点登录功能&#10;- 搭建SDK下载中心、工单系统、FAQ系统、数据导入导出等模块&#10;- 优化后端API支持已登录和未注册用户不同加入应用流程&#10;- 前端按钮统一采用微信手机号授权,完善用户授权体验&#10;- 修复多个页面的JSX语法错误及依赖导入问题替换部分组件库&#10;- 增加详细的类型定义文件,提升项目类型安全&#10;- 新增超过55个页面及60个API接口扩展应用功能和服务体系&#10;- 完成全面的样式设计,实现一致的视觉风格和交互体验">
<option name="closed" value="true" />
<created>1776018406658</created>
<option name="number" value="00023" />
<option name="presentableId" value="LOCAL-00023" />
<option name="project" value="LOCAL" />
<updated>1776018406658</updated>
</task>
<task id="LOCAL-00024" summary="feat(api-keys): 优化隐私协议同意逻辑及样式&#10;&#10;- 修正api-keys样式导入路径改为developer路径&#10;- 在开发者申请页面增加服务协议和隐私政策勾选框&#10;- 提交时校验协议是否已勾选,未勾选时提示用户&#10;- 优化申请页面协议区域样式及交互&#10;- 在用户验证页面添加隐私政策同意勾选框&#10;- 提交用户验证表单时检查协议同意状态&#10;- 增加打开服务协议和隐私政策的链接跳转&#10;- 更新项目配置增加passport/webview路由配置&#10;- 修复createTicket函数中日期格式正则表达式问题">
<option name="closed" value="true" />
<created>1776019228297</created>
<option name="number" value="00024" />
<option name="presentableId" value="LOCAL-00024" />
<option name="project" value="LOCAL" />
<updated>1776019228297</updated>
</task>
<task id="LOCAL-00025" summary="feat(api-keys): 优化隐私协议同意逻辑及样式&#10;&#10;- 修正api-keys样式导入路径改为developer路径&#10;- 在开发者申请页面增加服务协议和隐私政策勾选框&#10;- 提交时校验协议是否已勾选,未勾选时提示用户&#10;- 优化申请页面协议区域样式及交互&#10;- 在用户验证页面添加隐私政策同意勾选框&#10;- 提交用户验证表单时检查协议同意状态&#10;- 增加打开服务协议和隐私政策的链接跳转&#10;- 更新项目配置增加passport/webview路由配置&#10;- 修复createTicket函数中日期格式正则表达式问题">
<option name="closed" value="true" />
<created>1776019711439</created>
<option name="number" value="00025" />
<option name="presentableId" value="LOCAL-00025" />
<option name="project" value="LOCAL" />
<updated>1776019711439</updated>
<workItem from="1784638429525" duration="16147000" />
<workItem from="1784798575885" duration="4896000" />
</task>
<task id="LOCAL-00026" summary="refactor(config): 移除开发者中心和企业控制台的自定义导航样式&#10;&#10;- 删除了开发者中心页面配置中的 navigationStyle: 'custom'&#10;- 删除了企业控制台页面配置中的 navigationStyle: 'custom'&#10;- 简化了页面配置,保持默认导航样式&#10;- 有助于提升页面兼容性和维护性">
<option name="closed" value="true" />
@@ -515,7 +408,111 @@
<option name="project" value="LOCAL" />
<updated>1784699171525</updated>
</task>
<option name="localTasksCounter" value="62" />
<task id="LOCAL-00062" summary="refactor(user): 将VIP身份统一改为开发者标记&#10;&#10;- 审核通过时不再新增角色而是更新用户isDeveloper布尔标记&#10;- 后端UserService新增markAsDeveloper方法通过updateById设置isDeveloper&#10;- UserController新增PUT接口更新开发者标记权限使用sys:userRole:save&#10;- 前端api新增setUserDeveloper调用新的审核接口&#10;- vip-review页面移除角色相关代码改为调用setUserDeveloper方法&#10;- 约定开发者身份即sys_user.is_developer字段不再依赖角色表记录&#10;- app.config.ts追加开发者中心与平台管理相关页面路由&#10;- 新增开发者中心、平台管理的多个API模块apikey、appProduct、appDomain、developer请求等&#10;- 添加公共工具方法和复用组件用于开发者中心及平台管理功能&#10;- 统一品牌名称为“威数谱软件”,更新文案及界面展现一致性">
<option name="closed" value="true" />
<created>1784703425108</created>
<option name="number" value="00062" />
<option name="presentableId" value="LOCAL-00062" />
<option name="project" value="LOCAL" />
<updated>1784703425108</updated>
</task>
<task id="LOCAL-00063" summary="refactor(user): 将VIP身份统一改为开发者标记&#10;&#10;- 审核通过时不再新增角色而是更新用户isDeveloper布尔标记&#10;- 后端UserService新增markAsDeveloper方法通过updateById设置isDeveloper&#10;- UserController新增PUT接口更新开发者标记权限使用sys:userRole:save&#10;- 前端api新增setUserDeveloper调用新的审核接口&#10;- vip-review页面移除角色相关代码改为调用setUserDeveloper方法&#10;- 约定开发者身份即sys_user.is_developer字段不再依赖角色表记录&#10;- app.config.ts追加开发者中心与平台管理相关页面路由&#10;- 新增开发者中心、平台管理的多个API模块apikey、appProduct、appDomain、developer请求等&#10;- 添加公共工具方法和复用组件用于开发者中心及平台管理功能&#10;- 统一品牌名称为“威数谱软件”,更新文案及界面展现一致性">
<option name="closed" value="true" />
<created>1784704220175</created>
<option name="number" value="00063" />
<option name="presentableId" value="LOCAL-00063" />
<option name="project" value="LOCAL" />
<updated>1784704220175</updated>
</task>
<task id="LOCAL-00064" summary="feat(weapp): 新增开发者中心与平台管理入口及相关功能&#10;&#10;- 小程序端新增开发者中心和平台管理页面模块,完善功能入口&#10;- 新增开发者中心页面概览、我的应用、API Key和工单&#10;- 新增平台管理页面管理首页、应用审核、Git权限审核、域名绑定审核和工单处理&#10;- 复刻PC端/api/app/*相关API接口基地址使用WEBSOPY_API_BASE_URL&#10;- 增加公共组件StatCard、EntryGrid、CellRow以支持新页面UI&#10;- 修改首页增加开发者中心和平台管理角色入口,仅对对应用户显示&#10;- 用户页面菜单新增开发者中心和平台管理入口条件展示&#10;- 约定开发者身份使用sys_user.is_developer布尔字段不再依赖角色表&#10;- 更新登录态业务菜单是否显示基于登录接口返回的isDeveloper/isAdmin字段&#10;- 确保相关页面已在app.config.ts注册并添加身份认证需求&#10;- 调整运行配置为npm build:weapp确保编译无误">
<option name="closed" value="true" />
<created>1784707342939</created>
<option name="number" value="00064" />
<option name="presentableId" value="LOCAL-00064" />
<option name="project" value="LOCAL" />
<updated>1784707342940</updated>
</task>
<task id="LOCAL-00065" summary="feat(weapp): 新增开发者中心与平台管理入口及功能&#10;&#10;- 小程序端新增开发者中心和平台管理页面模块,完善功能入口&#10;- 新增开发者中心页面概览、我的应用、API Key和工单&#10;- 新增平台管理页面管理首页、应用审核、Git权限审核、域名绑定审核和工单处理&#10;- 复刻PC端/api/app/*相关API接口基地址使用WEBSOPY_API_BASE_URL&#10;- 增加公共组件StatCard、EntryGrid、CellRow以支持新页面UI&#10;- 修改首页增加开发者中心和平台管理角色入口,仅对对应用户显示&#10;- 用户页面菜单新增开发者中心和平台管理入口条件展示&#10;- 约定开发者身份使用sys_user.is_developer布尔字段不再依赖角色表&#10;- 更新登录态业务菜单是否显示基于登录接口返回的isDeveloper/isAdmin字段&#10;- vip-review审核通过流程调整为只调用setUserDeveloper后台替换角色为developer并置isDeveloper=1">
<option name="closed" value="true" />
<created>1784709736683</created>
<option name="number" value="00065" />
<option name="presentableId" value="LOCAL-00065" />
<option name="project" value="LOCAL" />
<updated>1784709736683</updated>
</task>
<task id="LOCAL-00066" summary="fix(weapp): 修复支付页nutui组件导致内容空白问题&#10;&#10;- 替换支付页中nutui的Button、Cell、Price、Divider组件为Taro原生组件或项目自定义组件&#10;- 修正商品名称、价格类型和支付金额显示使用flex布局及Text组件自绘内容&#10;- 调整按钮样式,统一为带蓝色背景和圆角的自定义样式&#10;- 移除nutui Price组件中的thousands属性使用项目自定义Price组件&#10;- 解决微信小程序中nutui组件因div渲染缺失导致UI元素不显示的问题&#10;- 保持支付流程及状态逻辑无改动,仅优化展示兼容性和稳定性">
<option name="closed" value="true" />
<created>1784798603228</created>
<option name="number" value="00066" />
<option name="presentableId" value="LOCAL-00066" />
<option name="project" value="LOCAL" />
<updated>1784798603228</updated>
</task>
<task id="LOCAL-00067" summary="fix(weapp): 修复支付页nutui组件导致内容空白问题&#10;&#10;- 替换支付页中nutui的Button、Cell、Price、Divider组件为Taro原生组件或项目自定义组件&#10;- 修正商品名称、价格类型和支付金额显示使用flex布局及Text组件自绘内容&#10;- 调整按钮样式,统一为带蓝色背景和圆角的自定义样式&#10;- 移除nutui Price组件中的thousands属性使用项目自定义Price组件&#10;- 解决微信小程序中nutui组件因div渲染缺失导致UI元素不显示的问题&#10;- 保持支付流程及状态逻辑无改动,仅优化展示兼容性和稳定性">
<option name="closed" value="true" />
<created>1784799951929</created>
<option name="number" value="00067" />
<option name="presentableId" value="LOCAL-00067" />
<option name="project" value="LOCAL" />
<updated>1784799951929</updated>
</task>
<task id="LOCAL-00068" summary="fix(weapp): 修复支付页nutui组件导致内容空白问题&#10;&#10;- 替换支付页中nutui的Button、Cell、Price、Divider组件为Taro原生组件或项目自定义组件&#10;- 修正商品名称、价格类型和支付金额显示使用flex布局及Text组件自绘内容&#10;- 调整按钮样式,统一为带蓝色背景和圆角的自定义样式&#10;- 移除nutui Price组件中的thousands属性使用项目自定义Price组件&#10;- 解决微信小程序中nutui组件因div渲染缺失导致UI元素不显示的问题&#10;- 保持支付流程及状态逻辑无改动,仅优化展示兼容性和稳定性">
<option name="closed" value="true" />
<created>1784800111045</created>
<option name="number" value="00068" />
<option name="presentableId" value="LOCAL-00068" />
<option name="project" value="LOCAL" />
<updated>1784800111045</updated>
</task>
<task id="LOCAL-00069" summary="feat(user): 补充开发者审核通过时添加店员记录功能&#10;&#10;- 引入 addShopStoreUser 接口用于新增门店店员记录&#10;- 新增 clerkStoreId 和 clerkTenantId 状态,保存当前审核店员的门店和租户信息&#10;- 在审核通过后调用 addShopStoreUser添加申请人为当前门店店员&#10;- 保证只有当 clerkStoreId 存在时才执行新增操作&#10;- 更新工作记录文档,补充审核通过流程中新添加的店员记录步骤&#10;- 保持原有升级开发者逻辑和接口调用不变">
<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>
<task id="LOCAL-00070" summary="feat(user): 补充开发者审核通过时添加店员记录功能&#10;&#10;- 引入 addShopStoreUser 接口用于新增门店店员记录&#10;- 新增 clerkStoreId 和 clerkTenantId 状态,保存当前审核店员的门店和租户信息&#10;- 在审核通过后调用 addShopStoreUser添加申请人为当前门店店员&#10;- 保证只有当 clerkStoreId 存在时才执行新增操作&#10;- 调整平台管理页 UI使其与门店中心保持一致风格&#10;- 门店中心及平台管理入口权限调整,仅超级管理员可访问&#10;- 用户菜单中平台管理和门店中心入口现基于 isSuperAdmin 字段控制&#10;- 优化登录及权限校验流程,避免权限不足时页面闪烁显示&#10;- 更新工作记录文档,补充审核通过流程中新添加的店员记录步骤">
<option name="closed" value="true" />
<created>1784802336157</created>
<option name="number" value="00070" />
<option name="presentableId" value="LOCAL-00070" />
<option name="project" value="LOCAL" />
<updated>1784802336157</updated>
</task>
<task id="LOCAL-00071" summary="feat(user): 合并并切换“申请开发者”菜单项状态&#10;&#10;- 在用户菜单中根据 isDeveloper 字段切换显示“申请成为开发者”或“进入开发者中心”&#10;- 已通过审核用户直接显示“进入开发者中心”且高亮显示&#10;- 去除冗余的独立“开发者中心”菜单条目,简化菜单结构&#10;- 调整平台管理页和门店中心入口权限为仅超级管理员可访问&#10;- 优化登录与权限校验流程,防止权限不足时页面闪烁或无访问权限空白&#10;- 更新相关文档,完善审核通过后店员记录添加流程">
<option name="closed" value="true" />
<created>1784802434477</created>
<option name="number" value="00071" />
<option name="presentableId" value="LOCAL-00071" />
<option name="project" value="LOCAL" />
<updated>1784802434477</updated>
</task>
<task id="LOCAL-00072" summary="fix(auth): 统一超级管理员标识为布尔值true&#10;&#10;- 将所有页面和菜单中超级管理员校验由数字1改为布尔true&#10;- 平台管理、门店中心菜单项仅在isSuperAdmin===true时显示&#10;- 门店中心页面移除店员门槛,改为超级管理员访问限制&#10;- 非超级管理员访问门店中心弹窗提示并返回&#10;- 首页工作台入口及相关访问权限判定切换为布尔判断&#10;- 优化首屏加载状态,避免权限校验闪烁和误判">
<option name="closed" value="true" />
<created>1784802589434</created>
<option name="number" value="00072" />
<option name="presentableId" value="LOCAL-00072" />
<option name="project" value="LOCAL" />
<updated>1784802589434</updated>
</task>
<task id="LOCAL-00073" summary="feat(user): 合并切换开发者菜单项状态并更新开发者中心UI&#10;&#10;- 用户菜单中根据 isDeveloper 字段条件展示“申请成为开发者”或“进入开发者中心”&#10;- 已审核通过用户菜单显示“进入开发者中心”,并设置高亮样式&#10;- 去除冗余的独立“开发者中心”菜单条目,简化菜单结构&#10;- 重新设计开发者中心页面UI统一为门店中心风格&#10;- 顶部展示开发者头像及昵称手机号信息&#10;- 功能卡片展示“我的应用”、“API Key”和“我的工单”异步加载数量角标&#10;- 底部添加“开发者帮助与支持”入口,替代门店订阅消息卡片&#10;- 未登录或非开发者状态显示相应空态提示及操作按钮&#10;- 优化登录状态与权限校验逻辑,防止页面闪烁和无权限内容显示">
<option name="closed" value="true" />
<created>1784802954848</created>
<option name="number" value="00073" />
<option name="presentableId" value="LOCAL-00073" />
<option name="project" value="LOCAL" />
<updated>1784802954848</updated>
</task>
<task id="LOCAL-00074" summary="feat(user): 合并切换开发者菜单并优化首页轮播图显示&#10;&#10;- 用户菜单根据 isDeveloper 字段切换显示申请或进入开发者中心&#10;- 已审核用户菜单高亮显示“进入开发者中心”&#10;- 去除冗余开发者中心菜单条目,简化结构&#10;- 重新设计开发者中心页面UI统一门店中心风格&#10;- 顶部展示开发者头像及昵称手机号信息&#10;- 功能卡片展示“我的应用”、“API Key”和“我的工单”异步加载数量角标&#10;- 底部添加“开发者帮助与支持”入口,替代门店订阅消息卡片&#10;- 未登录或非开发者状态显示相应空态及操作按钮&#10;- 首页轮播图为空时不显示容器,功能入口自动上移,优化空状态体验&#10;- 优化登录状态与权限校验逻辑,防止页面闪烁和无权限展示">
<option name="closed" value="true" />
<created>1784803190504</created>
<option name="number" value="00074" />
<option name="presentableId" value="LOCAL-00074" />
<option name="project" value="LOCAL" />
<updated>1784803190504</updated>
</task>
<option name="localTasksCounter" value="75" />
<servers />
</component>
<component name="TypeScriptGeneratedFilesManager">
@@ -535,16 +532,6 @@
<component name="VcsManagerConfiguration">
<option name="CHECK_CODE_SMELLS_BEFORE_PROJECT_COMMIT" value="false" />
<option name="CHECK_NEW_TODO" value="false" />
<MESSAGE value="feat(invite): 支持已登录用户直接加入应用&#10;&#10;- 后端accept接口code参数改为可选支持通过Authorization头识别登录用户&#10;- 已登录用户加入时不再弹手机号授权前端调用新增的doJoinAppForLoggedInUser方法&#10;- 未注册用户仍通过手机号授权码code加入调用doJoinAppForNewUser方法&#10;- 拆分前端加入逻辑新增handleConfirmJoin统一处理已登录加入流程&#10;- 优化按钮交互,已登录状态下按钮变为普通点击,移除协议强制勾选&#10;- 错误处理和状态提示完善,加入成功后自动跳转首页" />
<MESSAGE value="feat(developer): 完成小程序开发者中心和企业控制台改造&#10;&#10;- 设计并实现了开发者中心与企业控制台两大模块&#10;- 按用户角色区分开发者和企业客户,支持多项目类型及成员管理&#10;- 新增项目管理、应用管理、API Key管理及成员邀请等多功能页面&#10;- 实现应用版本发布、消息通知中心、权限审批与开发者申请流程&#10;- 完成CI/CD流水线、运营监控、发票管理、SSO单点登录功能&#10;- 搭建SDK下载中心、工单系统、FAQ系统、数据导入导出等模块&#10;- 优化后端API支持已登录和未注册用户不同加入应用流程&#10;- 前端按钮统一采用微信手机号授权,完善用户授权体验&#10;- 修复多个页面的JSX语法错误及依赖导入问题替换部分组件库&#10;- 增加详细的类型定义文件,提升项目类型安全&#10;- 新增超过55个页面及60个API接口扩展应用功能和服务体系&#10;- 完成全面的样式设计,实现一致的视觉风格和交互体验" />
<MESSAGE value="feat(api-keys): 优化隐私协议同意逻辑及样式&#10;&#10;- 修正api-keys样式导入路径改为developer路径&#10;- 在开发者申请页面增加服务协议和隐私政策勾选框&#10;- 提交时校验协议是否已勾选,未勾选时提示用户&#10;- 优化申请页面协议区域样式及交互&#10;- 在用户验证页面添加隐私政策同意勾选框&#10;- 提交用户验证表单时检查协议同意状态&#10;- 增加打开服务协议和隐私政策的链接跳转&#10;- 更新项目配置增加passport/webview路由配置&#10;- 修复createTicket函数中日期格式正则表达式问题" />
<MESSAGE value="refactor(config): 移除开发者中心和企业控制台的自定义导航样式&#10;&#10;- 删除了开发者中心页面配置中的 navigationStyle: 'custom'&#10;- 删除了企业控制台页面配置中的 navigationStyle: 'custom'&#10;- 简化了页面配置,保持默认导航样式&#10;- 有助于提升页面兼容性和维护性" />
<MESSAGE value="feat(userVerify): 添加阿里云身份证二要素核验功能&#10;&#10;- 在接口层新增 verifyIdCard 接口,实现姓名与身份证号核验&#10;- userVerify 页面提交时增加身份核验逻辑,核验失败阻止提交&#10;- 调用核验接口时显示加载状态,并根据结果提示用户&#10;- 后端依赖 aliyun cloudauth提供实人认证相关服务接口&#10;- 完成基本的错误捕获与用户提示,提升实名认证流程安全性" />
<MESSAGE value="refactor(api): 规范前端API接口地址并统一路径前缀&#10;&#10;- 修正开发者、小程序和企业相关API的基础URL避免重复添加/api前缀&#10;- 统一调整developer、enterprise、invite等模块接口路径保持与后端Controller一致&#10;- 新增权限申请及消息通知相关API并完善相关函数实现&#10;- 规范请求参数传递,移除多余包装,改用直接传递对象方式&#10;- 新增API Key重置、版本发布/回滚等接口支持&#10;&#10;feat(invite): 重构邀请登录流程支持未注册快速加入&#10;&#10;- 完整重写invite页分离已注册用户和未注册用户的按钮显示和交互&#10;- 实现未注册用户微信手机号授权后自动注册登录及自动加入应用&#10;- 已注册用户直接确认加入应用,无需手机号授权弹窗&#10;- 统一使用getPhoneNumber按钮处理两种状态并修复“授权码不能为空”报错&#10;- 前端配合后端改造支持已登录用户可直接通过Authorization头加入应用&#10;&#10;fix(developer): 修复开发者中心应用加载问题&#10;&#10;- 修复只查询创建的应用导致邀请加入的应用不显示的问题&#10;- 同时请求创建应用和参与应用接口,合并并去重应用列表&#10;- 新增pageJoinedApp接口调用及合并逻辑提高应用列表完整性和体验" />
<MESSAGE value="feat(app): 添加应用入口文件和配置文件&#10;&#10;- 新增 index.tsx 作为应用的主入口文件&#10;- 新增 index.config.ts 用于应用的配置管理" />
<MESSAGE value="fix(tailwind): 更新配置以兼容微信小程序&#10;&#10;- 启用 autoprefixer 并新增 postcss-nested 插件支持嵌套语法&#10;- 扩展主题颜色,添加 primary 色系&#10;- 更新 content 路径包含 public/index.html&#10;- 禁用 preflight 以移除默认样式重置&#10;- 移除多数 corePlugins 禁用项,保留 preflight 禁用,简化兼容性配置" />
<MESSAGE value="feat(postcss): 添加插件移除微信小程序不兼容Tailwind规则&#10;&#10;- 实现 postcss-remove-wxss-incompatible 插件&#10;- 移除反斜杠转义的重要性修饰符类选择器(.\!xxx&#10;- 删除依赖 :not 和兄弟选择器的 .space-[x|y]-* 规则&#10;- 删除依赖 :not 和兄弟选择器的 .divide-[x|y]-* 规则&#10;- 解决微信小程序 WXSS 不支持的 CSS 特性问题" />
<MESSAGE value="fix(pay): 优化订阅号获取逻辑并调整支付组件样式&#10;&#10;- 优先从小程序码 scene 参数读取 subscriptionNo提升参数兼容性&#10;- 对 scene 参数进行解码,避免编码引起的问题&#10;- 移除支付组件价格显示处的固定颜色设置,增强样式灵活性" />
<MESSAGE value="fix(utils): 优化小程序端复制文本功能体验&#10;&#10;- 小程序端直接调用原生 wx.setClipboardData避免 Taro Promise 封装问题&#10;- 避免微信小程序重复显示复制成功提示和误报复制失败&#10;- 失败时仅在小程序端输出错误日志,非小程序端显示提示&#10;- 修改首页外链跳转逻辑,支持相对路径跳转,小程序内外链复制提示提升用户体验&#10;- 更新项目内剪切板使用规范文档,说明复制行为调整及符合相应规范要求" />
<MESSAGE value="fix(index): 优化外链提示及删除复制咨询模板功能&#10;&#10;- 替换小程序内外链打开方式提示为弹窗提醒用户手动复制&#10;- 删除复制咨询模板功能及相关按钮&#10;- 修改联系我们文案,突出直接电话沟通或需求反馈&#10;- 移除无用导入的复制文本工具函数copyText" />
<MESSAGE value="feat(utils): 优化请求函数URL拼接逻辑&#10;&#10;- 在request函数中将请求拦截改为先拼接完整URL&#10;- 支持相对路径自动补全baseUrl&#10;- 删除原有请求拦截中url未处理的问题" />
@@ -560,7 +547,17 @@
<MESSAGE value="feat(user): 统一VIP概念为开发者并优化开发者登录及申请&#10;&#10;- 将VIP相关文案、图标改为开发者涉及用户中心、门店中心、审核页等&#10;- 优化登录弹窗UI改为红色系提升视觉统一性和交互体验&#10;- 关于我们页面品牌和公司信息内容更新,展示最新企业介绍和业务范围&#10;- VIP升级页新增“所在城市”选择使用微信原生地区选择器完善地址信息&#10;- 用户申请表单中新增城市字段及校验,提交时携带城市信息&#10;- 优化审核页面文案,统称开发者身份,明确申请审核内容&#10;- 修复UserRoleController.save接口bug避免userId被无条件覆盖导致角色绑错&#10;- 动态查询VIP角色ID避免硬编码支持多租户环境正确绑定角色" />
<MESSAGE value="refactor(user): 删除门店信息输入框&#10;&#10;- 移除用户资料页面中的门店名称输入框&#10;- 移除用户资料页面中的门店地址输入框&#10;- 简化用户页面界面布局,提高界面整洁度" />
<MESSAGE value="refactor(user): 统一品牌名称为“威数谱软件”&#10;&#10;- 将项目中所有“威硕谱软件”名称修改为“威数谱软件”&#10;- 更新协议页面、欢迎消息、礼品卡等多处文本内容&#10;- 修改小程序配置文件中的项目名称和显示标题&#10;- 优化开发者申请页部分文案,明确开发者身份描述&#10;- 保证所有界面及配置中的品牌名称统一一致" />
<option name="LAST_COMMIT_MESSAGE" value="refactor(user): 统一品牌名称为“威数谱软件”&#10;&#10;- 将项目中所有“威硕谱软件”名称修改为“威数谱软件”&#10;- 更新协议页面、欢迎消息、礼品卡等多处文本内容&#10;- 修改小程序配置文件中的项目名称和显示标题&#10;- 优化开发者申请页部分文案,明确开发者身份描述&#10;- 保证所有界面及配置中的品牌名称统一一致" />
<MESSAGE value="refactor(user): 将VIP身份统一改为开发者标记&#10;&#10;- 审核通过时不再新增角色而是更新用户isDeveloper布尔标记&#10;- 后端UserService新增markAsDeveloper方法通过updateById设置isDeveloper&#10;- UserController新增PUT接口更新开发者标记权限使用sys:userRole:save&#10;- 前端api新增setUserDeveloper调用新的审核接口&#10;- vip-review页面移除角色相关代码改为调用setUserDeveloper方法&#10;- 约定开发者身份即sys_user.is_developer字段不再依赖角色表记录&#10;- app.config.ts追加开发者中心与平台管理相关页面路由&#10;- 新增开发者中心、平台管理的多个API模块apikey、appProduct、appDomain、developer请求等&#10;- 添加公共工具方法和复用组件用于开发者中心及平台管理功能&#10;- 统一品牌名称为“威数谱软件”,更新文案及界面展现一致" />
<MESSAGE value="feat(weapp): 新增开发者中心与平台管理入口及相关功能&#10;&#10;- 小程序端新增开发者中心和平台管理页面模块,完善功能入口&#10;- 新增开发者中心页面概览、我的应用、API Key和工单&#10;- 新增平台管理页面管理首页、应用审核、Git权限审核、域名绑定审核和工单处理&#10;- 复刻PC端/api/app/*相关API接口基地址使用WEBSOPY_API_BASE_URL&#10;- 增加公共组件StatCard、EntryGrid、CellRow以支持新页面UI&#10;- 修改首页增加开发者中心和平台管理角色入口,仅对对应用户显示&#10;- 用户页面菜单新增开发者中心和平台管理入口条件展示&#10;- 约定开发者身份使用sys_user.is_developer布尔字段不再依赖角色表&#10;- 更新登录态业务,菜单是否显示基于登录接口返回" />
<MESSAGE value="feat(weapp): 新增开发者中心与平台管理入口及功能&#10;&#10;- 小程序端新增开发者中心和平台管理页面模块,完善功能入口&#10;- 新增开发者中心页面概览、我的应用、API Key和工单&#10;- 新增平台管理页面管理首页、应用审核、Git权限审核、域名绑定审核和工单处理&#10;- 复刻PC端/api/app/*相关API接口基地址使用WEBSOPY_API_BASE_URL&#10;- 增加公共组件StatCard、EntryGrid、CellRow以支持新页面UI&#10;- 修改首页增加开发者中心和平台管理角色入口,仅对对应用户显示&#10;- 用户页面菜单新增开发者中心和平台管理入口条件展示&#10;- 约定开发者身份使用sys_user.is_developer布尔字段不再依赖角色表&#10;- 更新登录态业务菜单是否显示基于登录接口返回的isDeveloper/isAdmin字段&#10;- vip-review审核通过流程调整为只调用setUserDeveloper后台替换角色为developer并置isDeveloper=1" />
<MESSAGE value="fix(weapp): 修复支付页nutui组件导致内容空白问题&#10;&#10;- 替换支付页中nutui的Button、Cell、Price、Divider组件为Taro原生组件或项目自定义组件&#10;- 修正商品名称、价格类型和支付金额显示使用flex布局及Text组件自绘内容&#10;- 调整按钮样式,统一为带蓝色背景和圆角的自定义样式&#10;- 移除nutui Price组件中的thousands属性使用项目自定义Price组件&#10;- 解决微信小程序中nutui组件因div渲染缺失导致UI元素不显示的问题&#10;- 保持支付流程及状态逻辑无改动,仅优化展示兼容性和稳定性" />
<MESSAGE value="feat(user): 补充开发者审核通过时添加店员记录功能&#10;&#10;- 引入 addShopStoreUser 接口用于新增门店店员记录&#10;- 新增 clerkStoreId 和 clerkTenantId 状态,保存当前审核店员的门店和租户信息&#10;- 在审核通过后调用 addShopStoreUser添加申请人为当前门店店员&#10;- 保证只有当 clerkStoreId 存在时才执行新增操作&#10;- 更新工作记录文档,补充审核通过流程中新添加的店员记录步骤&#10;- 保持原有升级开发者逻辑和接口调用不变" />
<MESSAGE value="feat(user): 补充开发者审核通过时添加店员记录功能&#10;&#10;- 引入 addShopStoreUser 接口用于新增门店店员记录&#10;- 新增 clerkStoreId 和 clerkTenantId 状态,保存当前审核店员的门店和租户信息&#10;- 在审核通过后调用 addShopStoreUser添加申请人为当前门店店员&#10;- 保证只有当 clerkStoreId 存在时才执行新增操作&#10;- 调整平台管理页 UI使其与门店中心保持一致风格&#10;- 门店中心及平台管理入口权限调整,仅超级管理员可访问&#10;- 用户菜单中平台管理和门店中心入口现基于 isSuperAdmin 字段控制&#10;- 优化登录及权限校验流程,避免权限不足时页面闪烁显示&#10;- 更新工作记录文档,补充审核通过流程中新添加的店员记录步骤" />
<MESSAGE value="feat(user): 合并并切换“申请开发者”菜单项状态&#10;&#10;- 在用户菜单中根据 isDeveloper 字段切换显示“申请成为开发者”或“进入开发者中心”&#10;- 已通过审核用户直接显示“进入开发者中心”且高亮显示&#10;- 去除冗余的独立“开发者中心”菜单条目,简化菜单结构&#10;- 调整平台管理页和门店中心入口权限为仅超级管理员可访问&#10;- 优化登录与权限校验流程,防止权限不足时页面闪烁或无访问权限空白&#10;- 更新相关文档,完善审核通过后店员记录添加流程" />
<MESSAGE value="fix(auth): 统一超级管理员标识为布尔值true&#10;&#10;- 将所有页面和菜单中超级管理员校验由数字1改为布尔true&#10;- 平台管理、门店中心菜单项仅在isSuperAdmin===true时显示&#10;- 门店中心页面移除店员门槛,改为超级管理员访问限制&#10;- 非超级管理员访问门店中心弹窗提示并返回&#10;- 首页工作台入口及相关访问权限判定切换为布尔判断&#10;- 优化首屏加载状态,避免权限校验闪烁和误判" />
<MESSAGE value="feat(user): 合并切换开发者菜单项状态并更新开发者中心UI&#10;&#10;- 用户菜单中根据 isDeveloper 字段条件展示“申请成为开发者”或“进入开发者中心”&#10;- 已审核通过用户菜单显示“进入开发者中心”,并设置高亮样式&#10;- 去除冗余的独立“开发者中心”菜单条目,简化菜单结构&#10;- 重新设计开发者中心页面UI统一为门店中心风格&#10;- 顶部展示开发者头像及昵称手机号信息&#10;- 功能卡片展示“我的应用”、“API Key”和“我的工单”异步加载数量角标&#10;- 底部添加“开发者帮助与支持”入口,替代门店订阅消息卡片&#10;- 未登录或非开发者状态显示相应空态提示及操作按钮&#10;- 优化登录状态与权限校验逻辑,防止页面闪烁和无权限内容显示" />
<MESSAGE value="feat(user): 合并切换开发者菜单并优化首页轮播图显示&#10;&#10;- 用户菜单根据 isDeveloper 字段切换显示申请或进入开发者中心&#10;- 已审核用户菜单高亮显示“进入开发者中心”&#10;- 去除冗余开发者中心菜单条目,简化结构&#10;- 重新设计开发者中心页面UI统一门店中心风格&#10;- 顶部展示开发者头像及昵称手机号信息&#10;- 功能卡片展示“我的应用”、“API Key”和“我的工单”异步加载数量角标&#10;- 底部添加“开发者帮助与支持”入口,替代门店订阅消息卡片&#10;- 未登录或非开发者状态显示相应空态及操作按钮&#10;- 首页轮播图为空时不显示容器,功能入口自动上移,优化空状态体验&#10;- 优化登录状态与权限校验逻辑,防止页面闪烁和无权限展示" />
<option name="LAST_COMMIT_MESSAGE" value="feat(user): 合并切换开发者菜单并优化首页轮播图显示&#10;&#10;- 用户菜单根据 isDeveloper 字段切换显示申请或进入开发者中心&#10;- 已审核用户菜单高亮显示“进入开发者中心”&#10;- 去除冗余开发者中心菜单条目,简化结构&#10;- 重新设计开发者中心页面UI统一门店中心风格&#10;- 顶部展示开发者头像及昵称手机号信息&#10;- 功能卡片展示“我的应用”、“API Key”和“我的工单”异步加载数量角标&#10;- 底部添加“开发者帮助与支持”入口,替代门店订阅消息卡片&#10;- 未登录或非开发者状态显示相应空态及操作按钮&#10;- 首页轮播图为空时不显示容器,功能入口自动上移,优化空状态体验&#10;- 优化登录状态与权限校验逻辑,防止页面闪烁和无权限展示" />
<option name="NON_MODAL_COMMIT_POSTPONE_SLOW_CHECKS" value="false" />
</component>
</project>

View File

@@ -45,3 +45,30 @@
- `@/api/system/user/index.ts` 新增 `setUserDeveloper(userId)``PUT /system/user/developer/{userId}`
- `vip-review/index.tsx`:移除 `addUserRole/listUserRole/listRoles/TenantId` 等角色相关 import 与 `VIP_ROLE_*``getCurrentTenantId``getVipRoleId``assignVipRole``formatDateTime``handleApprove``await assignVipRole(item.userId)` 替换为 `await setUserDeveloper(item.userId!)`
- 约定:**开发者身份 = `sys_user.is_developer` 布尔位,不是角色表记录**(已记项目 MEMORY.md
## vip-review 审核通过:补回「开发者角色绑定」+ 保留 `isDeveloper=1`
- 起因:上一轮把 `addUserRole` 整段删了(按"不用新增角色")。本轮用户要求"除了更新角色之外,还要更新 isDeveloper=1",即两者都要。
- 改动:仅前端 `src/pages/user/vip-review/index.tsx`
- 恢复 import`addUserRole, listUserRole`(@/api/system/userRole)、`listRoles`(@/api/system/role)、`TenantId`(@/config/app)。
- 恢复常量与函数:`VIP_ROLE_CODE='developer'``VIP_ROLE_NAME='开发者'``getCurrentTenantId()``getVipRoleId()`(按 roleCode+租户查 roleId`assignVipRole(userId)`(查已有角色、无则 `addUserRole`)。
- `handleApprove``await setUserDeveloper(item.userId!)` 之前新增 `await assignVipRole(item.userId)`:先绑 developer 角色,再置 `is_developer=1`
- 后端 `markAsDeveloper``PUT /system/user/developer/{userId}`)上一轮已落地并编译通过,本次未改;其 `updateById``isDeveloper=true` 即"更新数据库"的落库动作。
- 结论:审核通过 = 绑定 developer 角色 + 置 isDeveloper=1两项都做。已更新项目 MEMORY.md 的"开发者身份表示方式"条目。
## vip-review 审核通过:更正为「替换角色为 developer」+ `is_developer=1`(最终方案)
- 用户纠正上一轮:不是"新增/叠加角色"(那样会留下原角色 + developer 两个角色),而是**把用户角色改成 developer替换不并存**,同时 `sys_user.is_developer=1`
- 前端 `vip-review/index.tsx`:撤掉上一轮刚加回的 `assignVipRole``addUserRole/listUserRole/listRoles/TenantId/VIP_ROLE_*`/`getCurrentTenantId`/`getVipRoleId``handleApprove` 恢复为只 `await setUserDeveloper(item.userId!)`。前端完全不碰 `sys_user_role`
- 后端 `UserServiceImpl.markAsDeveloper` 本次新增"替换角色"逻辑:置 `is_developer=1` 后,按 `user.getTenantId()``roleService.getByRoleCode(roleCode="developer", tenantId)` 找到 developer 角色 → `userRoleService.remove(eq userId)` 删现有角色 → `saveBatch(userId, [developerRoleId])` 只绑 developer单角色。整段包 try/catch角色缺失/失败不影响 is_developer 落库。新增 import`RoleParam``java.util.Collections`
- 编译:`./mvnw -o compile` → BUILD SUCCESS。前端无残留角色符号。
- 结论(已同步 MEMORY.md升级为开发者 = 单角色 developer + isDeveloper 标记位;前端一次 `setUserDeveloper` 调用、后端一处搞定。
## 小程序端新增开发者中心 & 平台管理(入口改造)
- 起因:用户要求小程序端对齐 PC 端 `/developer``/admin` 功能。方案确认:入口挂「我的」页,分阶段实现 MVP 核心。
- 页面(新增于 `src/pages/developer/*``src/pages/admin/*`,已注册到 `app.config.ts`
- 开发者中心:`index`(概览) / `apps`(我的应用) / `apikeys`(API Key) / `tickets`(我的工单)
- 平台管理:`index`(管理首页) / `app-review`(应用审核) / `git-review`(Git/权限审核) / `domain-review`(域名绑定审核) / `tickets`(工单处理)
- API 模块(新增 `src/api/app/`apikey、appProduct、appDomain、ticket、developer复刻 PC 端 `/api/app/*` 契约,基址走 `WEBSOPY_API_BASE_URL`
- 公共组件:新增 `StatCard` / `EntryGrid` / `CellRow``src/components/common/`)。
- 入口方式(**本次按用户最新要求改**):不做工作台卡片,改为「我的」页功能菜单里的链接项,与「门店中心」同款——`开发者中心` 仅当 `(user as any)?.isDeveloper` 时显示、`平台管理` 仅当 `(user as any)?.isAdmin` 时显示,均 `requireAuth`。首页不额外加卡。
- 关键依赖:菜单显示与否取决于登录接口返回的 user 对象是否携带 `isDeveloper/isAdmin` 字段(后端 `setDeveloper` 写的是 `sys_user.is_developer`,但登录态是否回填该字段需联调确认)。
- 编译验证:已 `npm run build:weapp`(后台 IzjQLY 进行中),待确认无报错。

View File

@@ -0,0 +1,86 @@
# 2026-07-23
## 扫码进入小程序后支付页空白 bug
**现象**:用户从 web 端订单详情生成 QR 码 → 微信扫码进入小程序 → `passport/pay` 页面只显示卡片标题"确认支付"、"支付金额"标签、底部提示文案,**商品名称/价格类型/价格/分隔线/取消按钮/立即支付按钮全部缺失**,无法继续支付流程。
**根因**`@nutui/nutui-react-taro@2.7.4` 的 Cell / Price / Divider / Button 内部用 `React.createElement("div", ...)`,而 `dist/base.wxml` 里只有 `<view>` / `<text>` / `<button>` 模板没有 `<div>`。这些组件在 weapp 中"空壳化"。截图里只显示纯 Text/View 和图标(来自 `@nutui/icons-react-taro` 的 SVG没有显示的内容全部是 nutui-react-taro 组件。
**修复**:只改 `src/passport/pay/index.tsx` 一个文件,把 4 个 nutui 组件换成 Taro 原生 / 项目自定义组件:
- `import { Button, Cell, Price, Divider } from '@nutui/nutui-react-taro'``import { Button } from '@tarojs/components'` + `import Price from '@/components/common/Price'`
- `<Cell>``<View>` + `<Text>` 自绘(用 `flex justify-between px-4 py-3 border-b border-gray-100`
- `<Price size="large" thousands>``<Price size="large">`(项目自定义组件)
- `<Divider>``border-b border-gray-100` 替代
- `<Button type="primary" block>``<Button className="bg-blue-500 text-white rounded-full">`className 控制颜色)
- error/paid/loading 三个分支里的 `<Button type="primary">` 也一并替换
**验证**
- `npm run build:weapp``Compiled successfully in 14.45s`
- `dist/passport/pay/index.js``nut-cell|nut-button|nut-divider|nut-price` 全部为 0按钮编译成 `l.zx`Taro Button 局部变量名)
- 文件最终 413 行diff 集中在 import + 渲染区逻辑数据获取、JSAPI 支付、状态判断 payStatus vs status没动
**遗留**项目其它页面passport/{login,forget,register,setting,unified-qr,qr-login}、pages/order/*、pages/booking/*、components/common/{FixedButton,EmptyState}、components/QRLoginScanner也用了 nutui Button/Checkbox/Input/Card/Tabs/Tag 等。如果以后用户反馈"按钮/输入框看不到或不可点",按同样模式替换即可。这次没改,避免一次改动面太大。
## vip-review 审核通过补充:添加 shop_store_user 记录
**背景**:之前实现的 `src/pages/user/vip-review/index.tsx` 审核通过只调了 `updateShopDealerApply` + `setUserDeveloper`,漏了"给申请人加一条门店店员记录"。用户 2026-07-23 指出"审核通过的操作,还是要添加一条记录到 shopStoreUser可能之前理解错了",要求补上。
**修改**(仅前端一个文件):
- import 增加 `addShopStoreUser`(来自 `@/api/shop/shopStoreUser`)。
- 新增 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 复用审核店员的租户,避免前端另取。
## 平台管理页 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=true 才显示
**需求**:平台管理与门店中心两个入口都需要 `isSuperAdmin=true`(布尔)才显示(原先平台管理=isAdmin、门店中心=店员 storeInfo。门店中心页面本身按用户确认改为「仅超级管理员可进」。
**改动文件**
- `src/pages/user/user.tsx`:菜单项 平台管理、门店中心 的显示条件均由 `isAdmin` / `storeInfo` 改为 `(user as any)?.isSuperAdmin === 1`
- `src/pages/index/index.tsx`:首页角色工作台外层容器补 `|| isSuperAdmin===true`,平台管理入口 `(user as any)?.isAdmin``isSuperAdmin === true`
- `src/pages/admin/index/index.tsx`:页面访问门槛 `isAdmin``isSuperAdmin===true`(含空态文案);`loading` 拆为 `userLoading`(来自 useUser防闪烁) + `dataLoading`(角标数据);首屏 `if (userLoading || dataLoading) spinner`
- `src/pages/store/center/index.tsx`:移除 `getMyClerk()` 店员门槛,改为 `isSuperAdmin===true` 才进;非超管弹「仅超级管理员可访问」并 navigateBack头部加 `header` 兜底(超管非店员时显示「门店中心」+ 管理员昵称/手机号);渲染守卫 `userLoading → 请先登录 → 无访问权限`,去掉原 `if(!storeInfo) return null`(否则非店员超管整页空白)。
**验证**`npm run build:weapp` 在沙箱被 OOM/锁文件杀掉exit 137无报错属环境问题非代码错误改动均为项目既有写法useUser / Badge / 渐变头部)。
## 「申请成为开发者」菜单项随 isDeveloper 切换
**需求**:我的页面菜单里「申请成为开发者(推荐)」条目,若用户已审核通过成为开发者,该处文案/链接改为「进入开发者中心」(跳 `/pages/developer/index/index`)。
**改动**`src/pages/user/user.tsx` 菜单 `menuItems`
- 把原第 105 行固定「申请成为开发者」+ 第 107 行独立「开发者中心」(仅 isDeveloper 显示) 合并为条件式:`(user as any)?.isDeveloper ? { 进入开发者中心 → /pages/developer/index/index } : { 申请成为开发者 → /pages/user/vip-upgrade/index }`,两者均 `highlight: true`(醒目推荐样式)。去掉了冗余的独立「开发者中心」条目。
## 开发者中心 UI 对齐门店中心
**需求**`pages/developer/index/index` 的 UI 改成和门店中心(`pages/store/center/index`)一模一样。
**改动**(仅 `src/pages/developer/index/index.tsx`,整体重写):
- 删除原 NavBar + StatCard 3列网格 + EntryGrid 老结构,改写为门店中心同款「渐变绿头部 + 白卡片列表(图标/标题/描述/角标/箭头)+ 底部卡片」。
- 头部:开发者 avatar + 「开发者中心」标题 + 昵称·手机号(取 `(user as any)`)。
- FEATURE_CARDS 三张:我的应用(📦/蓝)、API Key(🔑/绿)、我的工单(🎫/橙),各自 desc + url角标沿用 `Badge`展示各功能的计数stats.apps/keys/tickets由 getMyApps/getAppApiKeyStats/getMyTickets 的 total 取),卡片渲染后异步填充。
- 底部卡片改为「开发者帮助与支持」(💡 蓝圈)→ `/pages/user/help-center/index`(替代门店中心的订阅消息卡,订阅模板是门店专属,开发者无对应模板)。
- 保留两个空态:!isLoggedIn「请先登录」、!isDeveloper「尚未加入开发者中心」+ 申请按钮(保留原 apply 入口store center 无此场景故无按钮);首屏用 `userLoading`(来自 useUser) 转圈防闪烁。
- 不再引入 NavBar/StatCard/EntryGrid/Loading可能内部用 nutui踩 weapp 空白坑),只用 `@tarojs/components` + 自定义 `Badge`,与门店中心一致。
## 首页轮播图空时不显示
**需求**`pages/index/index` 的轮播图banner若为空则不显示。
**改动**`src/pages/index/index.tsx` 轮播图渲染段):把外层 `.index-banner-wrap` 容器从「`bannerSlides.length>0` 渲染 Swiper否则渲染『暂无轮播图』占位框」改为「`bannerSlides.length > 0 &&` 整个容器才渲染」;空时整块(含占位框)不显示,下方功能入口自动上移。

View File

@@ -0,0 +1,12 @@
# 2026-07-24 工作日志
## 应用列表页改版pages/developer/apps/index
将「我的应用」列表从 CellRow 纯文本行改为卡片式布局:
- **卡片结构**:左侧 Logo/Emoji 图标 + 应用名 + 类型标识;中部双状态标签(发布状态 + 运行状态,带颜色);底部两行截断描述;底部操作栏(管理后台 / 首页 / 编辑 / 删除)。
- **Logo**`AppProduct.logo` 有值时用 `Image` 展示,否则按 `appType` 显示 emoji 占位(🌐💚🤖🍎⚙️ 等)。
- **操作行为**:管理后台/首页按钮 → `setClipboardData` 复制链接并 toast编辑 → 弹窗预填数据调 `updateAppProduct`;删除 → `Taro.showModal` 确认后调 `deleteAppProduct`
- **编辑模式**:复用创建弹窗,`productCode` 编辑时 disabled按钮文案区分「创建」/「保存」。
- **API 新增**`appProduct.ts` 加了 `updateAppProduct`PUT /app/product/update`deleteAppProduct`DELETE /app/product/delete/{id}),引入 `put`/`del` from request utils。
- **空状态**:带 emoji 图标 + 引导文案。
- 后端 Controller 在 websopy-java不是 com.gxwebsoft.core路径前缀 `/api/app/product`

View File

@@ -3,15 +3,42 @@
## 核心约定
### 开发者身份的表示方式
- **开发者身份 = `sys_user.is_developer` 布尔位**(数据库列 `is_developer`不是 `sys_user_role` 里的角色记录。
- **开发者身份主校验位 = `sys_user.is_developer` 布尔位**(数据库列 `is_developer`登录态 `MainController.loginByDeveloperSms` 校验它才允许开发者登录。
- 后端仓库:`/Users/gxwebsoft/JAVA/com.gxwebsoft.core`(单 Maven 模块,包名 `com.gxwebsoft` 分包)。
- 标记接口:`PUT /api/system/user/developer/{userId}``UserController.setDeveloper``UserService.markAsDeveloper`),成功后经 `syncMessageProducer` 同步 websopy。
- 读取点:`MainController.loginByDeveloperSms` 校验 `isDeveloper` 才允许开发者登录。
- 前端调用:`@/api/system/user``setUserDeveloper(userId)`
- **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=true访问门槛2026-07-23 确认)
- 用户对象字段 `isSuperAdmin``(user as any)?.isSuperAdmin === true`,布尔 true 才算),前端此前未引用,由后端 `sys_user` 返回。
- **平台管理**`pages/admin/index/index`)与**门店中心**`pages/store/center/index`)两个入口,**菜单与首页工作台均只在 `isSuperAdmin===true` 时显示**(原先平台管理是 `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`)。
- 用户变更后如需同步 websopy`syncMessageProducer.sendUserSyncMessage("websopy","UPDATE",user)`(在 `UserServiceImpl` 内,`@Autowired(required=false)` 注入)。
- 用户相关代码都在 `com.gxwebsoft.common.system`entity/service/controller业务应用数据在 `com.gxwebsoft.websopy`(无 Controller
## ⚠️ Taro 小程序 nutui-react-taro 兼容性陷阱2026-07-23 确认)
**问题**`@nutui/nutui-react-taro@2.7.4`**Cell / Price / Divider / Button** 等展示类组件,内部用 `React.createElement("div", ...)` 构建布局。Taro 4 的 babel/swc 没有把这些 `div` 转成 `view`,而 `dist/base.wxml` 里完全没有 `<div>` 模板,导致这些组件在 weapp 中"空壳化"——外层元素不存在,内层内容(`<div>`)被丢弃。
**症状**:组件位置完全空白 / 按钮不可见 / 价格显示为空。Text/View 与 `@nutui/icons-react-taro` 的图标Tips/Check/Close/Clock基于 SVG渲染正常。
**解决办法**(最小改动):在该页面用 Taro 原生组件替代:
| nutui不可用 | Taro 原生 / 项目自定义 |
|---|---|
| `import { Button, Cell, Price, Divider } from '@nutui/nutui-react-taro'` | `import { Button } from '@tarojs/components'` + `import Price from '@/components/common/Price'` |
| `<Cell title="X" description={Y} />` | `<View className="flex justify-between px-4 py-3 border-b border-gray-100"><Text className="text-gray-600">X</Text><Text className="text-gray-800">{Y}</Text></View>` |
| `<Price price={n} size="large" thousands />` | `<Price price={n} size="large" />`**用项目自定义 Price**,不用 nutui 的) |
| `<Divider />` | 用 `border-b border-gray-100` 替代 |
| `<Button type="primary" block loading={x} onClick={y}>...</Button>` | `<Button loading={x} onClick={y} className="flex-1 bg-blue-500 text-white rounded-full border-0">...</Button>`className 控制颜色,`loading` prop 仍然支持) |
**适用范围判断**项目里其它页面qr-login、register、passport/forget 等)也用了 nutui 的 Cell/Card/Divider/Button遇到同类"组件消失"现象时按同样模式替换即可。已修:`src/passport/pay/index.tsx`2026-07-23扫码进入小程序后支付页空白的 bug
**验证手段**:跑 `npm run build:weapp` 后在 `dist/<page>/index.js` 里 grep `nut-cell|nut-button|nut-divider|nut-price`,应为 0grep `l.zx`Taro Button 在 weapp 编译后的局部变量名)应出现 2 次(取消 + 立即支付)。
**已知的 nutui 组件清单**(按 2026-07-23 grep 结果):`Button``passport/pay, passport/login/setting/forget/register, pages/order/*, pages/booking/*, components/common/{FixedButton,EmptyState}` 等大量页面;`Cell``passport/pay, pages/order/order.tsx``Divider``passport/pay, passport/qr-login``Price``passport/pay`。修复顺序建议pay已修→ 视觉影响最大的页面 → 批量。

View File

@@ -1,5 +1,5 @@
// 应用产品(对齐 PC /developer/apps 与 /admin 应用管理,基址走 websopy-api
import { get, post } from '@/utils/request'
import { get, post, put, del } from '@/utils/request'
import { WebsopyBaseUrl } from '@/config/app'
const BASE = '/app/product'
@@ -57,6 +57,20 @@ export async function pageAllApps(params: any) {
return Promise.reject(new Error(res?.message || '加载失败'))
}
/** 更新应用 */
export async function updateAppProduct(data: any) {
const res = await put<any>(`${BASE}/update`, data, { baseUrl: WebsopyBaseUrl })
if (res?.code === 0) return res.data
return Promise.reject(new Error(res?.message || '更新失败'))
}
/** 删除应用 */
export async function deleteAppProduct(id: number) {
const res = await del<any>(`${BASE}/delete/${id}`, {}, { baseUrl: WebsopyBaseUrl })
if (res?.code === 0) return res.data
return Promise.reject(new Error(res?.message || '删除失败'))
}
/** 审核通过 */
export async function approvePublishReview(id: number) {
const res = await post<any>(`${BASE}/approve/${id}`, {}, { baseUrl: WebsopyBaseUrl })

View File

@@ -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 === true
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 (
<View className='min-h-full bg-gray-50 flex items-center justify-center'>
<Text className='text-gray-400'>...</Text>
</View>
)
}
if (!isLoggedIn) {
return (
<View className='min-h-screen bg-gray-100'>
<NavBar title='平台管理' />
<View className='flex flex-col items-center justify-center py-24'>
<Text className='text-gray-400 text-sm'></Text>
</View>
<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 (!isAdmin) {
if (!isSuperAdmin) {
return (
<View className='min-h-screen bg-gray-100'>
<NavBar title='平台管理' />
<View className='flex flex-col items-center justify-center py-20 px-10'>
<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>
<Text className='text-xs text-gray-400 text-center'></Text>
</View>
)
}
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 (
<View className='min-h-screen bg-gray-100'>
<NavBar title='平台管理' />
<ScrollView scrollY>
{loading ? (
<Loading />
<View className='min-h-full bg-gray-50'>
{/* 顶部信息区:管理员头像 + 平台名称(与门店中心同款渐变头部) */}
<View
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='grid grid-cols-2 gap-3 mb-3'>
<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 className='w-full h-full flex items-center justify-center'>
<Text className='text-2xl'>👤</Text>
</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>
)
}
export default AdminCenterPage

View File

@@ -1,12 +1,17 @@
import React, { useState, useEffect, useCallback } from 'react'
import { View, Text, ScrollView, Input, Textarea } from '@tarojs/components'
import { View, Text, ScrollView, Input, Textarea, Image } from '@tarojs/components'
import Taro from '@tarojs/taro'
import NavBar from '@/components/NavBar'
import CellRow from '@/components/common/CellRow'
import Loading from '@/components/common/Loading'
import { useUser } from '@/hooks/useUser'
import { getMyApps, addAppProduct } from '@/api/app/appProduct'
import { getPageList, APP_TYPE_NAME } from '@/utils/devcenter'
import { getMyApps, addAppProduct, updateAppProduct, deleteAppProduct } from '@/api/app/appProduct'
import {
getPageList,
APP_TYPE_NAME,
PUBLISH_STATUS_NAME,
STATUS_NAME,
publishStatusColor,
} from '@/utils/devcenter'
definePageConfig({ navigationBarTitleText: '我的应用' })
@@ -18,20 +23,62 @@ const APP_TYPE_OPTIONS = [
{ type: 110, name: '平台应用' },
]
/** 应用类型 emoji 映射(无 logo 时用) */
const APP_TYPE_EMOJI: Record<number, string> = {
10: '🌐',
20: '💚',
30: '🎵',
40: '🔍',
50: '💙',
60: '🤖',
70: '🍎',
80: '💻',
90: '🖥️',
100: '🔌',
110: '⚙️',
}
/** 运行状态颜色 */
function statusColor(status?: number): string {
switch (status) {
case 1:
return '#16a34a'
case 2:
case 4:
return '#d97706'
case 3:
case 5:
return '#dc2626'
default:
return '#6b7280'
}
}
interface AppForm {
productId?: number
productName: string
productCode: string
appType: number
description: string
}
const EMPTY_FORM: AppForm = { productName: '', productCode: '', appType: 20, description: '' }
const MyAppsPage: React.FC = () => {
const { isLoggedIn } = useUser()
const [loading, setLoading] = useState(true)
const [list, setList] = useState<any[]>([])
const [showModal, setShowModal] = useState(false)
const [form, setForm] = useState({ productName: '', productCode: '', appType: 20, description: '' })
const [form, setForm] = useState<AppForm>(EMPTY_FORM)
const [submitting, setSubmitting] = useState(false)
const [editing, setEditing] = useState(false)
const load = useCallback(async () => {
setLoading(true)
try {
const data = await getMyApps({ page: 1, limit: 20 })
setList(getPageList(data))
} catch (e) {
} catch {
// ignore
} finally {
setLoading(false)
@@ -43,7 +90,20 @@ const MyAppsPage: React.FC = () => {
}, [isLoggedIn, load])
const openCreate = () => {
setForm({ productName: '', productCode: '', appType: 20, description: '' })
setForm(EMPTY_FORM)
setEditing(false)
setShowModal(true)
}
const openEdit = (app: any) => {
setForm({
productId: app.productId,
productName: app.productName || '',
productCode: app.productCode || '',
appType: app.appType || 20,
description: app.description || '',
})
setEditing(true)
setShowModal(true)
}
@@ -52,53 +112,199 @@ const MyAppsPage: React.FC = () => {
Taro.showToast({ title: '请填写应用名称', icon: 'none' })
return
}
if (!/^[a-z][a-z0-9]*$/.test(form.productCode)) {
if (!editing && !/^[a-z][a-z0-9]*$/.test(form.productCode)) {
Taro.showToast({ title: '标识须为小写字母/数字', icon: 'none' })
return
}
setSubmitting(true)
try {
if (editing) {
await updateAppProduct(form)
Taro.showToast({ title: '更新成功', icon: 'success' })
} else {
await addAppProduct(form)
Taro.showToast({ title: '创建成功', icon: 'success' })
}
setShowModal(false)
load()
} catch (e: any) {
Taro.showToast({ title: e?.message || '创建失败', icon: 'none' })
Taro.showToast({ title: e?.message || '操作失败', icon: 'none' })
} finally {
setSubmitting(false)
}
}
const handleDelete = (app: any) => {
Taro.showModal({
title: '删除应用',
content: `确定删除「${app.productName || '未命名应用'}」吗?此操作不可恢复。`,
confirmText: '删除',
confirmColor: '#dc2626',
success: async (res) => {
if (!res.confirm) return
Taro.showLoading({ title: '删除中...' })
try {
await deleteAppProduct(app.productId)
Taro.hideLoading()
Taro.showToast({ title: '删除成功', icon: 'success' })
load()
} catch (e: any) {
Taro.hideLoading()
Taro.showToast({ title: e?.message || '删除失败', icon: 'none' })
}
},
})
}
const copyLink = (url: string, label: string) => {
if (!url) {
Taro.showToast({ title: `${label}未配置`, icon: 'none' })
return
}
Taro.setClipboardData({
data: url,
success: () => {
Taro.showToast({ title: `${label}链接已复制`, icon: 'none' })
},
})
}
return (
<View className='min-h-screen bg-gray-100'>
<NavBar title='我的应用' rightText='创建' onRightClick={openCreate} />
<ScrollView scrollY>
<ScrollView scrollY className='pb-4'>
{loading ? (
<Loading />
) : list.length === 0 ? (
<View className='flex flex-col items-center justify-center py-24'>
<Text className='text-gray-400 text-sm'></Text>
<View className='flex flex-col items-center justify-center py-32'>
<Text className='text-5xl mb-4'>📦</Text>
<Text className='text-gray-400 text-sm mb-1'></Text>
<Text className='text-gray-300 text-xs'></Text>
</View>
) : (
<View className='mt-3'>
{list.map((app) => (
<CellRow
<View className='px-3 pt-3'>
{list.map((app) => {
const pubColor = publishStatusColor(app.publishStatus)
const runColor = statusColor(app.status)
const emoji = APP_TYPE_EMOJI[app.appType as number] || '📦'
const pubName = PUBLISH_STATUS_NAME[app.publishStatus as string] || '开发中'
const runName = STATUS_NAME[app.status as number] || '未开通'
return (
<View
key={app.productId}
title={app.productName || '未命名应用'}
subtitle={`${APP_TYPE_NAME[app.appType as number] || '未知类型'} · ${app.productCode || ''}`}
onClick={() => {
if (app.adminUrl) Taro.setClipboardData({ data: app.adminUrl })
}}
className='bg-white rounded-xl mb-3 overflow-hidden'
style={{ boxShadow: '0 1px 4px rgba(0,0,0,0.06)' }}
>
{/* 卡片头部:图标 + 名称 + 类型 */}
<View className='flex items-start p-4'>
{/* Logo / Emoji */}
<View
className='w-12 h-12 rounded-xl flex items-center justify-center flex-shrink-0 mr-3'
style={{ background: '#f3f4f6' }}
>
{app.logo ? (
<Image
src={app.logo}
className='w-12 h-12 rounded-xl'
mode='aspectFill'
/>
))}
) : (
<Text className='text-2xl'>{emoji}</Text>
)}
</View>
{/* 名称 + 标识 */}
<View className='flex-1 min-w-0'>
<Text className='text-base font-medium text-gray-800 block truncate'>
{app.productName || '未命名应用'}
</Text>
<Text className='text-xs text-gray-400 mt-0.5 block'>
{APP_TYPE_NAME[app.appType as number] || '未知类型'} · {app.productCode || ''}
</Text>
</View>
</View>
{/* 状态标签 */}
<View className='flex items-center gap-2 px-4 mb-3'>
<Text
className='text-xs px-2 py-0.5 rounded-full'
style={{ color: pubColor, background: pubColor + '1a' }}
>
{pubName}
</Text>
<Text
className='text-xs px-2 py-0.5 rounded-full'
style={{ color: runColor, background: runColor + '1a' }}
>
{runName}
</Text>
{app.createTime && (
<Text className='text-xs text-gray-300 ml-auto'>
{app.createTime.slice(0, 10)}
</Text>
)}
</View>
{/* 描述 */}
{app.description && (
<View className='px-4 pb-3'>
<Text className='text-xs text-gray-500 leading-relaxed' style={{ display: '-webkit-box', WebkitLineClamp: 2, WebkitBoxOrient: 'vertical', overflow: 'hidden' }}>
{app.description}
</Text>
</View>
)}
{/* 底部操作区 */}
<View className='flex items-center border-t border-gray-50'>
<View
className='flex-1 flex items-center justify-center py-2.5'
onClick={() => copyLink(app.adminUrl, '管理后台')}
>
<Text className='text-xs text-gray-500'></Text>
</View>
<View className='w-px h-4 bg-gray-100' />
<View
className='flex-1 flex items-center justify-center py-2.5'
onClick={() => copyLink(app.homeUrl, '首页')}
>
<Text className='text-xs text-gray-500'></Text>
</View>
<View className='w-px h-4 bg-gray-100' />
<View
className='flex-1 flex items-center justify-center py-2.5'
onClick={() => openEdit(app)}
>
<Text className='text-xs text-blue-500'></Text>
</View>
<View className='w-px h-4 bg-gray-100' />
<View
className='flex-1 flex items-center justify-center py-2.5'
onClick={() => handleDelete(app)}
>
<Text className='text-xs text-red-500'></Text>
</View>
</View>
</View>
)
})}
</View>
)}
</ScrollView>
{showModal && (
<View style={{ position: 'fixed', left: 0, top: 0, right: 0, bottom: 0, background: 'rgba(0,0,0,0.4)', zIndex: 999 }}>
<View className='absolute bottom-0 left-0 right-0 bg-white rounded-t-2xl p-4'>
<Text className='text-base font-medium block mb-3'></Text>
<View
style={{ position: 'fixed', left: 0, top: 0, right: 0, bottom: 0, background: 'rgba(0,0,0,0.4)', zIndex: 999 }}
onClick={() => setShowModal(false)}
>
<View
className='absolute bottom-0 left-0 right-0 bg-white rounded-t-2xl p-4'
onClick={(e) => e.stopPropagation()}
style={{ paddingBottom: 'env(safe-area-inset-bottom)' }}
>
<View className='flex items-center justify-between mb-4'>
<Text className='text-base font-medium'>{editing ? '编辑应用' : '创建应用'}</Text>
<Text className='text-gray-300 text-lg' onClick={() => setShowModal(false)}>×</Text>
</View>
<View className='mb-3'>
<Text className='text-xs text-gray-500 block mb-1'></Text>
<Input
@@ -108,15 +314,21 @@ const MyAppsPage: React.FC = () => {
onInput={(e) => setForm({ ...form, productName: e.detail.value })}
/>
</View>
<View className='mb-3'>
<Text className='text-xs text-gray-500 block mb-1'></Text>
<Text className='text-xs text-gray-500 block mb-1'>
{editing && '(不可修改)'}
</Text>
<Input
className='bg-gray-100 rounded-lg px-3 py-2 text-sm'
placeholder='如weishop'
value={form.productCode}
disabled={editing}
style={editing ? { opacity: 0.5 } : {}}
onInput={(e) => setForm({ ...form, productCode: e.detail.value })}
/>
</View>
<View className='mb-3'>
<Text className='text-xs text-gray-500 block mb-1'></Text>
<View className='flex flex-wrap gap-2'>
@@ -132,6 +344,7 @@ const MyAppsPage: React.FC = () => {
))}
</View>
</View>
<View className='mb-4'>
<Text className='text-xs text-gray-500 block mb-1'></Text>
<Textarea
@@ -142,12 +355,22 @@ const MyAppsPage: React.FC = () => {
style={{ height: '80px' }}
/>
</View>
<View className='flex gap-3'>
<View className='flex-1 py-2.5 rounded-lg bg-gray-100 text-center' onClick={() => setShowModal(false)}>
<View
className='flex-1 py-2.5 rounded-lg bg-gray-100 text-center'
onClick={() => setShowModal(false)}
>
<Text className='text-sm text-gray-600'></Text>
</View>
<View className='flex-1 py-2.5 rounded-lg text-center' style={{ background: '#2563eb' }} onClick={submit}>
<Text className='text-sm text-white'>{submitting ? '提交中...' : '创建'}</Text>
<View
className='flex-1 py-2.5 rounded-lg text-center'
style={{ background: '#2563eb' }}
onClick={submit}
>
<Text className='text-sm text-white'>
{submitting ? '提交中...' : editing ? '保存' : '创建'}
</Text>
</View>
</View>
</View>

View File

@@ -1,26 +1,65 @@
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 Badge from '@/components/common/Badge'
import { useUser } from '@/hooks/useUser'
import { getMyApps } from '@/api/app/appProduct'
import { getAppApiKeyStats } from '@/api/app/apikey'
import { getMyTickets } from '@/api/app/ticket'
import { getPageTotal } from '@/utils/devcenter'
definePageConfig({ navigationBarTitleText: '开发者中心' })
definePageConfig({
navigationBarTitleText: '开发者中心',
})
const DeveloperCenterPage: React.FC = () => {
const { user, isLoggedIn } = useUser()
// 功能卡片定义(未来新增功能只需在这里加一项)
const FEATURE_CARDS = [
{
key: 'apps',
icon: '📦',
title: '我的应用',
desc: '管理你开发并上架的应用',
url: '/pages/developer/apps/index',
color: '#3b82f6',
bgColor: '#dbeafe',
showBadge: true,
},
{
key: 'apikeys',
icon: '🔑',
title: 'API Key',
desc: '查看与管理你的接口密钥',
url: '/pages/developer/apikeys/index',
color: '#10b981',
bgColor: '#dcfce7',
showBadge: true,
},
{
key: 'tickets',
icon: '🎫',
title: '我的工单',
desc: '提交与跟踪你的工单',
url: '/pages/developer/tickets/index',
color: '#f59e0b',
bgColor: '#fef3c7',
showBadge: true,
},
]
export default function DeveloperCenterPage() {
const { user, isLoggedIn, loading: userLoading } = useUser()
const isDeveloper = !!(user as any)?.isDeveloper
const [loading, setLoading] = useState(true)
const [stats, setStats] = useState({ apps: 0, keys: 0, tickets: 0 })
useEffect(() => {
if (isLoggedIn && isDeveloper) {
load()
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [isLoggedIn, isDeveloper])
/** 加载我的应用 / API Key / 工单数量 */
const load = useCallback(async () => {
setLoading(true)
try {
const [appsR, keysR, ticketsR] = await Promise.allSettled([
getMyApps({ page: 1, limit: 1 }),
@@ -31,40 +70,40 @@ const DeveloperCenterPage: React.FC = () => {
const keys = keysR.status === 'fulfilled' ? getPageTotal(keysR.value) : 0
const tickets = ticketsR.status === 'fulfilled' ? getPageTotal(ticketsR.value) : 0
setStats({ apps, keys, tickets })
} catch (e) {
} catch {
// ignore
} finally {
setLoading(false)
}
}, [])
useEffect(() => {
if (isLoggedIn) load()
}, [isLoggedIn, load])
/** 获取卡片角标数字 */
const getBadgeCount = (key: string) => {
if (key === 'apps') return stats.apps
if (key === 'apikeys') return stats.keys
if (key === 'tickets') return stats.tickets
return 0
}
if (userLoading) {
return (
<View className='min-h-full bg-gray-50 flex items-center justify-center'>
<Text className='text-gray-400'>...</Text>
</View>
)
}
if (!isLoggedIn) {
return (
<View className='min-h-screen bg-gray-100'>
<NavBar title='开发者中心' />
<View className='flex flex-col items-center justify-center py-24'>
<Text className='text-gray-400 text-sm'></Text>
<View
className='mt-4 px-6 py-2 rounded-full'
style={{ background: '#0e932e' }}
onClick={() => Taro.navigateTo({ url: '/passport/login' })}
>
<Text className='text-white text-sm'></Text>
</View>
</View>
<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 (!isDeveloper) {
return (
<View className='min-h-screen bg-gray-100'>
<NavBar title='开发者中心' />
<View className='flex flex-col items-center justify-center py-20 px-10'>
<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 mb-5 leading-relaxed'>
@@ -78,37 +117,112 @@ const DeveloperCenterPage: React.FC = () => {
<Text className='text-white text-sm'></Text>
</View>
</View>
</View>
)
}
const entries = [
{ icon: '📦', label: '我的应用', url: '/pages/developer/apps/index' },
{ icon: '🔑', label: 'API Key', url: '/pages/developer/apikeys/index' },
{ icon: '🎫', label: '我的工单', url: '/pages/developer/tickets/index' },
]
// 头部展示信息
const header = {
avatar: (user as any)?.avatar,
title: '开发者中心',
subtitle: `${((user as any)?.nickname || (user as any)?.username || '')}${((user as any)?.phone) ? ` · ${(user as any).phone}` : ''}`,
}
return (
<View className='min-h-screen bg-gray-100'>
<NavBar title='开发者中心' />
<ScrollView scrollY>
{loading ? (
<Loading />
<View className='min-h-full bg-gray-50'>
{/* 顶部信息区:头像 + 名称 */}
<View
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'>
{header.avatar ? (
<Image
className='w-full h-full'
src={header.avatar}
mode='aspectFill'
/>
) : (
<View className='p-4'>
<View className='grid grid-cols-3 gap-3 mb-3'>
<StatCard label='我的应用' value={stats.apps} color='#3b82f6' />
<StatCard label='API Key' value={stats.keys} color='#10b981' />
<StatCard label='我的工单' value={stats.tickets} color='#f59e0b' />
</View>
<Text className='text-base font-medium mb-2 block'></Text>
<EntryGrid items={entries} columns={3} />
<View className='h-6' />
<View className='w-full h-full flex items-center justify-center'>
<Text className='text-2xl'>👤</Text>
</View>
)}
</ScrollView>
</View>
{/* 信息 */}
<View className='flex-1 min-w-0'>
<Text className='text-white text-lg font-bold block truncate'>
{header.title}
</Text>
<Text className='text-white text-opacity-80 text-sm mt-1 block truncate'>
{header.subtitle}
</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 active:opacity-80'
onClick={() => Taro.navigateTo({ url: '/pages/user/help-center/index' })}
>
<View className='w-10 h-10 rounded-full flex items-center justify-center flex-shrink-0'
style={{ background: '#dbeafe' }}>
<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'>
SDK
</Text>
</View>
<Text className='text-blue-500 text-sm flex-shrink-0'></Text>
</View>
</View>
</View>
)
}
export default DeveloperCenterPage

View File

@@ -255,9 +255,34 @@ const IndexPage: React.FC = () => {
</View>
<ScrollView scrollY style={{ height: scrollHeight }}>
{/* 轮播图 */}
{/* 角色工作台入口:仅开发者 / 平台管理员 / 超级管理员可见 */}
{(isLoggedIn && ((user as any)?.isDeveloper || (user as any)?.isAdmin || (user as any)?.isSuperAdmin === true)) && (
<View className='mx-3 mt-2 grid grid-cols-2 gap-2'>
{(user as any)?.isDeveloper && (
<View
className='flex items-center justify-center py-3 rounded-lg border border-blue-100'
style={{ background: 'linear-gradient(135deg, #eff6ff 0%, #dbeafe 100%)' }}
onClick={() => Taro.navigateTo({ url: '/pages/developer/index/index' })}
>
<Text className='text-lg mr-2'>🛠</Text>
<Text className='text-sm font-medium text-blue-700'></Text>
</View>
)}
{(user as any)?.isSuperAdmin === true && (
<View
className='flex items-center justify-center py-3 rounded-lg border border-purple-100'
style={{ background: 'linear-gradient(135deg, #f5f3ff 0%, #ede9fe 100%)' }}
onClick={() => Taro.navigateTo({ url: '/pages/admin/index/index' })}
>
<Text className='text-lg mr-2'>🛡</Text>
<Text className='text-sm font-medium text-purple-700'></Text>
</View>
)}
</View>
)}
{/* 轮播图:有数据才显示,为空则不渲染 */}
{bannerSlides.length > 0 && (
<View className='mx-3 mt-2 rounded-lg overflow-hidden index-banner-wrap'>
{bannerSlides.length > 0 ? (
<Swiper
autoplay
interval={3000}
@@ -277,12 +302,8 @@ const IndexPage: React.FC = () => {
</SwiperItem>
))}
</Swiper>
) : (
<View className='w-full bg-green-50 flex items-center justify-center' style={{ height: `${bannerHeight}px` }}>
<Text className='text-gray-400 text-sm'></Text>
</View>
)}
</View>
{/* 功能入口网格 */}
<View className='mx-3 mt-3 p-3 bg-white rounded-lg'>

View File

@@ -5,6 +5,7 @@ import { getMyClerk } from '@/api/shop/shopStoreUser'
import { listShopDealerApply } from '@/api/shop/shopDealerApply'
import { pageShopOrder } from '@/api/shop/shopOrder'
import Badge from '@/components/common/Badge'
import { useUser } from '@/hooks/useUser'
definePageConfig({
navigationBarTitleText: '门店中心',
@@ -61,30 +62,28 @@ const SUBSCRIBE_TMPL_IDS = [
]
export default function StoreCenterPage() {
const { user, isLoggedIn, loading: userLoading } = useUser()
const isSuperAdmin = (user as any)?.isSuperAdmin === true
const [storeInfo, setStoreInfo] = useState<any>(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' })
// 仅超级管理员可访问
if (!isLoggedIn || !isSuperAdmin) {
if (isLoggedIn && !isSuperAdmin) {
Taro.showToast({ title: '仅超级管理员可访问', icon: 'none' })
setTimeout(() => Taro.navigateBack(), 1500)
}
return
}
setStoreInfo(data)
// 超管若同时具备店员身份,加载门店信息用于头部展示(非必须)
getMyClerk()
.then(data => setStoreInfo(data || null))
.catch(() => setStoreInfo(null))
// 加载待审核数量
loadBadgeCounts()
})
.catch(() => {
Taro.showToast({ title: '仅门店店员可访问', icon: 'none' })
setTimeout(() => Taro.navigateBack(), 1500)
})
.finally(() => setLoading(false))
}, [])
}, [isLoggedIn, isSuperAdmin])
/** 加载各类待处理角标数量 */
const loadBadgeCounts = async () => {
@@ -140,7 +139,7 @@ export default function StoreCenterPage() {
})
}, [])
if (loading) {
if (userLoading) {
return (
<View className='min-h-full bg-gray-50 flex items-center justify-center'>
<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 (
<View className='min-h-full bg-gray-50'>
{/* 顶部信息区:店员头像 + 门店名称 */}
{/* 顶部信息区:头像 + 名称 */}
<View
className='pt-8 pb-12 px-5 rounded-b-3xl relative overflow-hidden'
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'
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'>
{storeInfo.avatar ? (
{header.avatar ? (
<Image
className='w-full h-full'
src={storeInfo.avatar}
src={header.avatar}
mode='aspectFill'
/>
) : (
@@ -178,13 +208,13 @@ export default function StoreCenterPage() {
)}
</View>
{/* 门店信息 */}
{/* 信息 */}
<View className='flex-1 min-w-0'>
<Text className='text-white text-lg font-bold block truncate'>
{storeInfo.storeName || '门店中心'}
{header.title}
</Text>
<Text className='text-white text-opacity-80 text-sm mt-1 block truncate'>
{storeInfo.name} {storeInfo.phone ? `· ${storeInfo.phone}` : ''}
{header.subtitle}
</Text>
</View>
</View>

View File

@@ -101,10 +101,14 @@ const UserPage: React.FC = () => {
})
const menuItems = [
// 申请成为开发者 - 醒目样式
{ icon: '🔰', label: '申请成为开发者', url: '/pages/user/vip-upgrade/index', highlight: true, requireAuth: true },
// 门店中心:仅门店店员/店长显示(通过 /shop/shop-store-user/my 判断)
...(storeInfo ? [{ icon: '🏪', label: '门店中心', url: '/pages/store/center/index', requireAuth: true }] : []),
// 申请成为开发者 / 进入开发者中心:已通过审核则直接进开发者中心
...((user as any)?.isDeveloper
? [{ icon: '🛠️', label: '进入开发者中心', url: '/pages/developer/index/index', highlight: true, requireAuth: true }]
: [{ icon: '🔰', label: '申请成为开发者', url: '/pages/user/vip-upgrade/index', highlight: true, requireAuth: true }]),
// 平台管理:仅 isSuperAdmin=true 用户显示
...((user as any)?.isSuperAdmin === true ? [{ icon: '🛡️', label: '平台管理', url: '/pages/admin/index/index', requireAuth: true }] : []),
// 门店中心:仅 isSuperAdmin=true 用户显示
...((user as any)?.isSuperAdmin === true ? [{ 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' },

View File

@@ -4,7 +4,7 @@ import Taro, { useDidShow } from '@tarojs/taro'
import { listShopDealerApply, updateShopDealerApply } from '@/api/shop/shopDealerApply'
import type { ShopDealerApply } from '@/api/shop/shopDealerApply/model'
import { setUserDeveloper } from '@/api/system/user'
import { getMyClerk } from '@/api/shop/shopStoreUser'
import { getMyClerk, addShopStoreUser } from '@/api/shop/shopStoreUser'
definePageConfig({
navigationBarTitleText: '开发者审核',
@@ -44,6 +44,8 @@ const VipReviewPage: React.FC = () => {
const [loading, setLoading] = useState(false)
const [submitting, setSubmitting] = useState(false)
const [isClerk, setIsClerk] = useState(false)
const [clerkStoreId, setClerkStoreId] = useState<number>()
const [clerkTenantId, setClerkTenantId] = useState<number>()
const loadingRef = useRef(false)
// 验证当前用户是否为店员
@@ -52,6 +54,9 @@ const VipReviewPage: React.FC = () => {
.then(data => {
if (data) {
setIsClerk(true)
// 记录当前店员所属门店与租户,审核通过时用于给申请人添加店员记录
setClerkStoreId(data.storeId)
setClerkTenantId(data.tenantId)
} else {
setIsClerk(false)
Taro.showToast({ title: '仅门店店员可访问', icon: 'none' })
@@ -129,9 +134,20 @@ const VipReviewPage: React.FC = () => {
auditTime: Date.now(),
} as ShopDealerApply)
// 审核通过后,将用户标记为开发者(直接更新用户标记位,不新增角色
// 审核通过:升级为开发者(后端 markAsDeveloper 会置 is_developer=1 并把角色替换为 developer
await setUserDeveloper(item.userId!)
// 审核通过在当前门店下给申请人添加一条店员shop_store_user记录
if (clerkStoreId) {
await addShopStoreUser({
userId: item.userId!,
storeId: clerkStoreId,
tenantId: clerkTenantId,
isDelete: 0,
comments: '开发者审核通过自动添加',
})
}
Taro.showToast({ title: '已通过', icon: 'success' })
loadList(activeTab)
} catch (e: any) {

View File

@@ -1,13 +1,13 @@
import React, { useState, useEffect, useCallback } from 'react'
import { View, Text } from '@tarojs/components'
import { Button, Cell, Price, Divider } from '@nutui/nutui-react-taro'
import { Check, Close, Tips, Clock } from '@nutui/icons-react-taro'
import { View, Text, Button } from '@tarojs/components'
import { Tips, Check, Close, Clock } from '@nutui/icons-react-taro'
import Taro from '@tarojs/taro'
import { request } from '@/utils/request'
import { getOpenId, loginByOpenId } from '@/api/passport/wx-login'
import type { User } from '@/api/system/user/model'
import { saveStorageByLoginUser } from '@/utils/server'
import { TenantId, WebsopyBaseUrl } from '@/config/app'
import Price from '@/components/common/Price'
/**
* 小程序支付页面
@@ -312,7 +312,12 @@ const PayPage: React.FC = () => {
<Close className="text-red-500 mb-4" size="48" />
<Text className="block text-gray-800 text-lg mb-2"></Text>
<Text className="block text-gray-500 mb-6">{errorMsg}</Text>
<Button type="primary" onClick={handleBack}></Button>
<Button
onClick={handleBack}
className="bg-blue-500 text-white rounded-full px-6"
>
</Button>
</View>
</View>
)
@@ -333,7 +338,12 @@ const PayPage: React.FC = () => {
¥{Number(detail.payPrice).toFixed(2)}
</Text>
) : null}
<Button type="primary" onClick={handleBack}></Button>
<Button
onClick={handleBack}
className="bg-blue-500 text-white rounded-full px-6"
>
</Button>
</View>
</View>
)
@@ -347,21 +357,26 @@ const PayPage: React.FC = () => {
<View className="bg-white rounded-lg shadow-sm p-4 mb-4">
<Text className="block text-lg font-bold text-gray-800 mb-3"></Text>
<Cell title="商品名称" description={detail?.productName || '-'} />
<Cell title="价格类型" description={getPriceDesc()} />
<View className="flex items-center justify-between px-4 py-3">
<Text className="text-gray-600"></Text>
<Price
price={detail?.payPrice}
size="large"
thousands
/>
{/* 商品名称 */}
<View className="flex items-center justify-between px-4 py-3 border-b border-gray-100">
<Text className="text-gray-600"></Text>
<Text className="text-gray-800">{detail?.productName || '-'}</Text>
</View>
<Divider />
{/* 价格类型 */}
<View className="flex items-center justify-between px-4 py-3 border-b border-gray-100">
<Text className="text-gray-600"></Text>
<Text className="text-gray-800">{getPriceDesc() || '-'}</Text>
</View>
<View className="flex items-start px-4 py-2">
{/* 支付金额 */}
<View className="flex items-center justify-between px-4 py-3">
<Text className="text-gray-600"></Text>
<Price price={detail?.payPrice} size="large" />
</View>
{/* 提示 */}
<View className="flex items-start px-4 py-2 mt-2 border-t border-gray-100">
<Tips className="text-orange-500 mr-2 mt-1" size="16" />
<Text className="text-sm text-gray-500">
使
@@ -373,18 +388,15 @@ const PayPage: React.FC = () => {
<View className="fixed bottom-0 left-0 right-0 p-4 bg-white border-t border-gray-100">
<View className="flex gap-3">
<Button
block
onClick={handleBack}
className="flex-1"
className="flex-1 bg-gray-100 text-gray-700 rounded-full border-0"
>
</Button>
<Button
type="primary"
block
loading={paying}
onClick={handlePay}
className="flex-1"
className="flex-1 bg-blue-500 text-white rounded-full border-0"
>
{paying ? '支付中...' : '立即支付'}
</Button>