Compare commits
2 Commits
1.0
..
85a1558e84
| Author | SHA1 | Date | |
|---|---|---|---|
| 85a1558e84 | |||
| 9df90c1176 |
@@ -1,12 +0,0 @@
|
||||
# http://editorconfig.org
|
||||
root = true
|
||||
|
||||
[*]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
charset = utf-8
|
||||
trim_trailing_whitespace = true
|
||||
insert_final_newline = true
|
||||
|
||||
[*.md]
|
||||
trim_trailing_whitespace = false
|
||||
@@ -1,7 +0,0 @@
|
||||
module.exports = {
|
||||
"extends": ["taro/react"],
|
||||
"rules": {
|
||||
"react/jsx-uses-react": "off",
|
||||
"react/react-in-jsx-scope": "off"
|
||||
}
|
||||
}
|
||||
-25
@@ -1,25 +0,0 @@
|
||||
dist/
|
||||
deploy_versions/
|
||||
.temp/
|
||||
.rn_temp/
|
||||
node_modules/
|
||||
.DS_Store
|
||||
.swc
|
||||
/.idea/
|
||||
|
||||
# 环境变量文件
|
||||
.env
|
||||
.env.local
|
||||
.env.development
|
||||
.env.production
|
||||
|
||||
# 日志文件
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
# 编辑器配置
|
||||
.vscode/
|
||||
*.swp
|
||||
*.swo
|
||||
Generated
+6
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
||||
Generated
+562
@@ -0,0 +1,562 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="AutoImportSettings">
|
||||
<option name="autoReloadType" value="SELECTIVE" />
|
||||
</component>
|
||||
<component name="ChangeListManager">
|
||||
<list default="true" id="2de94e4f-0587-4c9b-8bfd-e87e0ae95b81" name="更改" comment="feat(address): 新增收货地址与售后申请功能 - 新增地址编辑页面,支持地址智能识别、地图选点、地区选择和默认地址设置 - 实现地址列表页面,支持地址查看、删除、设为默认及选择返回结算页 - 新增售后申请页面,支持退款类型选择、商品选择、原因填写、图片上传和提交审核 - 修复 passport 分包配置,移除不存在分包并补充缺失声明,避免 Taro 编译报错 - 新增地址类型定义,增强前端地址数据结构类型安全 - 优化页面交互体验,完善表单校验及错误提示逻辑 - 统一代码格式与命名规范,保持代码风格一致性">
|
||||
<change beforePath="$PROJECT_DIR$/.workbuddy/memory/2026-07-16.md" beforeDir="false" afterPath="$PROJECT_DIR$/.workbuddy/memory/2026-07-16.md" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/config/app.ts" beforeDir="false" afterPath="$PROJECT_DIR$/config/app.ts" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/config/env.js" beforeDir="false" afterPath="$PROJECT_DIR$/config/env.js" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/src/passport/pay/index.tsx" beforeDir="false" afterPath="$PROJECT_DIR$/src/passport/pay/index.tsx" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/src/utils/request.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/utils/request.ts" afterDir="false" />
|
||||
</list>
|
||||
<option name="SHOW_DIALOG" value="false" />
|
||||
<option name="HIGHLIGHT_CONFLICTS" value="true" />
|
||||
<option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
|
||||
<option name="LAST_RESOLUTION" value="IGNORE" />
|
||||
</component>
|
||||
<component name="Git.Settings">
|
||||
<option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$" />
|
||||
<option name="RESET_MODE" value="HARD" />
|
||||
</component>
|
||||
<component name="ProjectColorInfo">{
|
||||
"associatedIndex": 7
|
||||
}</component>
|
||||
<component name="ProjectId" id="3CCElusNNIyc7lgX61jkNQo02h3" />
|
||||
<component name="ProjectLevelVcsManager">
|
||||
<ConfirmationsSetting value="2" id="Add" />
|
||||
</component>
|
||||
<component name="ProjectViewState">
|
||||
<option name="hideEmptyMiddlePackages" value="true" />
|
||||
<option name="showLibraryContents" value="true" />
|
||||
</component>
|
||||
<component name="PropertiesComponent"><![CDATA[{
|
||||
"keyToString": {
|
||||
"ModuleVcsDetector.initialDetectionPerformed": "true",
|
||||
"RunOnceActivity.ShowReadmeOnStart": "true",
|
||||
"RunOnceActivity.TerminalTabsStorage.copyFrom.TerminalArrangementManager.252": "true",
|
||||
"RunOnceActivity.git.unshallow": "true",
|
||||
"git-widget-placeholder": "master",
|
||||
"junie.onboarding.icon.badge.shown": "true",
|
||||
"last_opened_file_path": "/Users/gxwebsoft/VUE/websopy-taro/src/passport",
|
||||
"node.js.detected.package.eslint": "true",
|
||||
"node.js.detected.package.tslint": "true",
|
||||
"node.js.selected.package.eslint": "(autodetect)",
|
||||
"node.js.selected.package.tslint": "(autodetect)",
|
||||
"nodejs_package_manager_path": "pnpm",
|
||||
"npm.build:weapp.executor": "Run",
|
||||
"npm.dev:weapp.executor": "Run",
|
||||
"settings.editor.selected.configurable": "preferences.pluginManager",
|
||||
"to.speed.mode.migration.done": "true",
|
||||
"ts.external.directory.path": "/Applications/WebStorm.app/Contents/plugins/javascript-plugin/jsLanguageServicesImpl/external",
|
||||
"vue.rearranger.settings.migration": "true"
|
||||
}
|
||||
}]]></component>
|
||||
<component name="RecentsManager">
|
||||
<key name="CopyFile.RECENT_KEYS">
|
||||
<recent name="$PROJECT_DIR$/src/passport" />
|
||||
<recent name="$PROJECT_DIR$/src/api" />
|
||||
<recent name="$PROJECT_DIR$/src/api/passport/wx-login" />
|
||||
<recent name="$PROJECT_DIR$" />
|
||||
</key>
|
||||
</component>
|
||||
<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" />
|
||||
<scripts>
|
||||
<script value="build:weapp" />
|
||||
</scripts>
|
||||
<node-interpreter value="project" />
|
||||
<envs />
|
||||
<method v="2" />
|
||||
</configuration>
|
||||
<configuration name="dev:weapp" type="js.build_tools.npm" temporary="true" nameIsGenerated="true">
|
||||
<package-json value="$PROJECT_DIR$/package.json" />
|
||||
<command value="run" />
|
||||
<scripts>
|
||||
<script value="dev:weapp" />
|
||||
</scripts>
|
||||
<node-interpreter value="project" />
|
||||
<envs />
|
||||
<method v="2" />
|
||||
</configuration>
|
||||
<recent_temporary>
|
||||
<list>
|
||||
<item itemvalue="npm.build:weapp" />
|
||||
<item itemvalue="npm.dev:weapp" />
|
||||
</list>
|
||||
</recent_temporary>
|
||||
</component>
|
||||
<component name="SharedIndexes">
|
||||
<attachedChunks>
|
||||
<set>
|
||||
<option value="bundled-js-predefined-d6986cc7102b-e03c56caf84a-JavaScript-WS-252.23892.411" />
|
||||
</set>
|
||||
</attachedChunks>
|
||||
</component>
|
||||
<component name="TaskManager">
|
||||
<task active="true" id="Default" summary="默认任务">
|
||||
<changelist id="2de94e4f-0587-4c9b-8bfd-e87e0ae95b81" name="更改" comment="" />
|
||||
<created>1775881305279</created>
|
||||
<option name="number" value="Default" />
|
||||
<option name="presentableId" value="Default" />
|
||||
<updated>1775881305279</updated>
|
||||
<workItem from="1775881306434" duration="12652000" />
|
||||
<workItem from="1775910653750" duration="20840000" />
|
||||
<workItem from="1777873063244" duration="20000" />
|
||||
<workItem from="1778089953570" duration="7000" />
|
||||
<workItem from="1778308774420" duration="947000" />
|
||||
<workItem from="1780559793276" duration="400000" />
|
||||
<workItem from="1782803615391" duration="418000" />
|
||||
<workItem from="1782804285999" duration="4025000" />
|
||||
<workItem from="1782813891753" duration="1850000" />
|
||||
<workItem from="1782876148090" duration="4787000" />
|
||||
<workItem from="1782903749109" duration="4000" />
|
||||
<workItem from="1782971387128" duration="7154000" />
|
||||
<workItem from="1784093204572" duration="6082000" />
|
||||
<workItem from="1784131049625" duration="2304000" />
|
||||
<workItem from="1784133736973" duration="2917000" />
|
||||
</task>
|
||||
<task id="LOCAL-00006" summary="fix(passport): 修正邀请接受接口的URL路径 - 将邀请接受接口URL从/api/_app/invite/accept修改为/api/_app/developer/invite/accept - 确保请求正确指向开发者邀请接受接口 - 修复因URL错误导致的邀请接受失败问题">
|
||||
<option name="closed" value="true" />
|
||||
<created>1775901973624</created>
|
||||
<option name="number" value="00006" />
|
||||
<option name="presentableId" value="LOCAL-00006" />
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1775901973624</updated>
|
||||
</task>
|
||||
<task id="LOCAL-00007" summary="fix(passport): 优化登录确认成功后的页面跳转逻辑 - 延长登录确认成功提示的显示时间至2000毫秒 - 跳转到用户页面前先隐藏toast,避免影响跳转体验 - 增加switchTab成功和失败的日志打印 - 失败时提示用户手动返回页面 - 调整跳转延迟时间为1800毫秒,提升交互流畅度">
|
||||
<option name="closed" value="true" />
|
||||
<created>1775902053962</created>
|
||||
<option name="number" value="00007" />
|
||||
<option name="presentableId" value="LOCAL-00007" />
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1775902053962</updated>
|
||||
</task>
|
||||
<task id="LOCAL-00008" summary="fix(passport): 修正邀请信息接口请求参数与请求头 - 将邀请令牌作为查询参数附加在请求URL中 - 移除请求体中的token字段传递 - 添加请求头TenantId,值为5 - 请求失败时在控制台打印错误信息以便调试 - 保持原有错误处理和加载状态设置逻辑不变">
|
||||
<option name="closed" value="true" />
|
||||
<created>1775904794443</created>
|
||||
<option name="number" value="00008" />
|
||||
<option name="presentableId" value="LOCAL-00008" />
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1775904794443</updated>
|
||||
</task>
|
||||
<task id="LOCAL-00009" summary="fix(invite): 修复邀请加入请求头TenantId传递问题 - 从配置中引入TenantId常量 - 将请求header中的TenantId字段由硬编码改为动态变量 - 确保GET请求和加密数据请求均正确带上TenantId头信息 - 提升接口调用的一致性和维护性">
|
||||
<option name="closed" value="true" />
|
||||
<created>1775905047844</created>
|
||||
<option name="number" value="00009" />
|
||||
<option name="presentableId" value="LOCAL-00009" />
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1775905047844</updated>
|
||||
</task>
|
||||
<task id="LOCAL-00010" summary="fix(config): 修正环境配置中的API_BASE_URL地址 - 将开发环境、生产环境和测试环境的API_BASE_URL统一更新为新的域名websopy-api.websoft.top - 更新SimpleQRCodeModal组件中二维码请求地址,使用新的API域名 - 在邀请信息请求函数fetchInviteInfo中添加详细的请求和响应日志 - 增加接口错误日志打印,提高捕获异常时的信息丰富度">
|
||||
<option name="closed" value="true" />
|
||||
<created>1775905427131</created>
|
||||
<option name="number" value="00010" />
|
||||
<option name="presentableId" value="LOCAL-00010" />
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1775905427131</updated>
|
||||
</task>
|
||||
<task id="LOCAL-00011" summary="fix(invite): 修正邀请接口使用独立API域名 - 将邀请相关接口的请求地址由通用API域名改为独立的INVITE_API_URL - 修改获取邀请信息接口的请求URL - 修改接受邀请接口的请求URL - 确保请求头和参数传递保持一致">
|
||||
<option name="closed" value="true" />
|
||||
<created>1775905743204</created>
|
||||
<option name="number" value="00011" />
|
||||
<option name="presentableId" value="LOCAL-00011" />
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1775905743204</updated>
|
||||
</task>
|
||||
<task id="LOCAL-00012" summary="refactor(api): 移除未使用的配置接口及相关Hook - 从cmsWebsiteField接口中删除configWebsiteField方法 - 删除useConfig自定义Hook及其引用 - 更新about页面,去除对config对象和useConfig的使用 - 修正invite模块请求URL,统一加上/api前缀">
|
||||
<option name="closed" value="true" />
|
||||
<created>1775908253789</created>
|
||||
<option name="number" value="00012" />
|
||||
<option name="presentableId" value="LOCAL-00012" />
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1775908253789</updated>
|
||||
</task>
|
||||
<task id="LOCAL-00013" summary="refactor(api): 移除未使用的配置接口及相关Hook - 从cmsWebsiteField接口中删除configWebsiteField方法 - 删除useConfig自定义Hook及其引用 - 更新about页面,去除对config对象和useConfig的使用 - 修正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): 修正邀请接口路径以支持小程序专用前缀 - 将请求邀请信息接口路径改为使用 /api/_app 前缀,确保小程序免登录访问 - 修改接受邀请接口路径,统一使用小程序专用 API 域名和前缀 - 更新邀请相关接口注释,明确小程序专用接口前缀含义 - 在专家历史数据中添加高级开发工程师 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): 添加邀请接受接口调试日志 - 在接受邀请请求前打印token和请求URL、参数 - 在请求响应后输出接口响应及返回数据 - 在调用失败时打印错误信息及异常日志 - 保持请求流程和状态管理不变,增强调试能力">
|
||||
<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): 添加邀请接受接口调试日志 - 在接受邀请请求前打印token和请求URL、参数 - 在请求响应后输出接口响应及返回数据 - 在调用失败时打印错误信息及异常日志 - 保持请求流程和状态管理不变,增强调试能力">
|
||||
<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): 优化邀请码接受失败的用户提示和处理 - 针对“用户不存在”、“用户创建失败”及“请先登录”提示,改为弹窗提示需登录 - 在弹窗中增加“去登录”按钮,点击后跳转到登录页面,并携带重定向token参数 - 其他错误仍使用原Toast提示方式展示错误信息 - 保留错误日志打印,便于排查异常情况">
|
||||
<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): 优化新用户扫码邀请加入流程 - 扫码进入邀请页面时保存邀请 token 到本地存储 - 新增登录状态检测,未登录用户点击快速加入时保存邀请信息并跳转登录页 - 登录成功后返回邀请页自动执行加入应用操作,提升用户体验 - 支持已登录用户直接加入应用,简化流程 - 增加清理 pending 邀请数据的方法,防止数据残留 - 监听页面显示事件,处理登录返回后自动操作 - 调整接口请求逻辑,确保携带最新的邀请和授权数据 - 更新项目配置,注册邀请页面路径信息">
|
||||
<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): 重写登录与邀请加入流程,支持微信手机号一键登录 - 完全重写 passport/login.tsx,实现微信手机号一键登录功能 - 支持登录页面携带 redirect 参数,登录后自动跳转回原页面 - 登录成功后自动处理邀请加入应用,支持邀请关系绑定 - 重写 passport/invite/index.tsx,实现扫码邀请加入应用完整流程 - 支持调用 wx.login 获取 code,使用 loginByOpenId 判断注册状态 - 未注册用户自动跳转登录页完成微信手机号登录再加入应用 - 已登录用户直接执行加入应用操作,加入接口支持 Authorization 头 - 解决邀请页面与登录页面间旧授权码混用导致加入失败问题 - 处理后端 401 错误,优先使用微信授权码免登录接口 - 优化用户体验,添加服务协议与隐私政策弹窗及错误提示 - 页面状态管理改进,加入加载、检查登录、错误和跳转反馈">
|
||||
<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): 重构邀请加入流程,实现已登录用户免授权直接加入 - 完全重写邀请页逻辑,区分已登录与未注册用户按钮显示 - 已登录用户显示“确认加入”按钮,点击用 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 id="LOCAL-00021" summary="feat(passport): 优化已登录用户加入时协议勾选逻辑 - 已登录用户点击「确认加入」时不再强制勾选协议 - 移除 handleConfirmJoin 中对协议勾选的检查 - 未注册用户仍需勾选协议后才能授权手机号 - 确保登录状态异常时有提示信息显示">
|
||||
<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): 支持已登录用户直接加入应用 - 后端accept接口code参数改为可选,支持通过Authorization头识别登录用户 - 已登录用户加入时不再弹手机号授权,前端调用新增的doJoinAppForLoggedInUser方法 - 未注册用户仍通过手机号授权码code加入,调用doJoinAppForNewUser方法 - 拆分前端加入逻辑,新增handleConfirmJoin统一处理已登录加入流程 - 优化按钮交互,已登录状态下按钮变为普通点击,移除协议强制勾选 - 错误处理和状态提示完善,加入成功后自动跳转首页">
|
||||
<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): 完成小程序开发者中心和企业控制台改造 - 设计并实现了开发者中心与企业控制台两大模块 - 按用户角色区分开发者和企业客户,支持多项目类型及成员管理 - 新增项目管理、应用管理、API Key管理及成员邀请等多功能页面 - 实现应用版本发布、消息通知中心、权限审批与开发者申请流程 - 完成CI/CD流水线、运营监控、发票管理、SSO单点登录功能 - 搭建SDK下载中心、工单系统、FAQ系统、数据导入导出等模块 - 优化后端API,支持已登录和未注册用户不同加入应用流程 - 前端按钮统一采用微信手机号授权,完善用户授权体验 - 修复多个页面的JSX语法错误及依赖导入问题,替换部分组件库 - 增加详细的类型定义文件,提升项目类型安全 - 新增超过55个页面及60个API接口,扩展应用功能和服务体系 - 完成全面的样式设计,实现一致的视觉风格和交互体验">
|
||||
<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): 优化隐私协议同意逻辑及样式 - 修正api-keys样式导入路径,改为developer路径 - 在开发者申请页面增加服务协议和隐私政策勾选框 - 提交时校验协议是否已勾选,未勾选时提示用户 - 优化申请页面协议区域样式及交互 - 在用户验证页面添加隐私政策同意勾选框 - 提交用户验证表单时检查协议同意状态 - 增加打开服务协议和隐私政策的链接跳转 - 更新项目配置,增加passport/webview路由配置 - 修复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): 优化隐私协议同意逻辑及样式 - 修正api-keys样式导入路径,改为developer路径 - 在开发者申请页面增加服务协议和隐私政策勾选框 - 提交时校验协议是否已勾选,未勾选时提示用户 - 优化申请页面协议区域样式及交互 - 在用户验证页面添加隐私政策同意勾选框 - 提交用户验证表单时检查协议同意状态 - 增加打开服务协议和隐私政策的链接跳转 - 更新项目配置,增加passport/webview路由配置 - 修复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>
|
||||
</task>
|
||||
<task id="LOCAL-00026" summary="refactor(config): 移除开发者中心和企业控制台的自定义导航样式 - 删除了开发者中心页面配置中的 navigationStyle: 'custom' - 删除了企业控制台页面配置中的 navigationStyle: 'custom' - 简化了页面配置,保持默认导航样式 - 有助于提升页面兼容性和维护性">
|
||||
<option name="closed" value="true" />
|
||||
<created>1776019857647</created>
|
||||
<option name="number" value="00026" />
|
||||
<option name="presentableId" value="LOCAL-00026" />
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1776019857647</updated>
|
||||
</task>
|
||||
<task id="LOCAL-00027" summary="feat(userVerify): 添加阿里云身份证二要素核验功能 - 在接口层新增 verifyIdCard 接口,实现姓名与身份证号核验 - userVerify 页面提交时增加身份核验逻辑,核验失败阻止提交 - 调用核验接口时显示加载状态,并根据结果提示用户 - 后端依赖 aliyun cloudauth,提供实人认证相关服务接口 - 完成基本的错误捕获与用户提示,提升实名认证流程安全性">
|
||||
<option name="closed" value="true" />
|
||||
<created>1776021606260</created>
|
||||
<option name="number" value="00027" />
|
||||
<option name="presentableId" value="LOCAL-00027" />
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1776021606260</updated>
|
||||
</task>
|
||||
<task id="LOCAL-00028" summary="feat(userVerify): 添加阿里云身份证二要素核验功能 - 在接口层新增 verifyIdCard 接口,实现姓名与身份证号核验 - userVerify 页面提交时增加身份核验逻辑,核验失败阻止提交 - 调用核验接口时显示加载状态,并根据结果提示用户 - 后端依赖 aliyun cloudauth,提供实人认证相关服务接口 - 完成基本的错误捕获与用户提示,提升实名认证流程安全性">
|
||||
<option name="closed" value="true" />
|
||||
<created>1776021764419</created>
|
||||
<option name="number" value="00028" />
|
||||
<option name="presentableId" value="LOCAL-00028" />
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1776021764419</updated>
|
||||
</task>
|
||||
<task id="LOCAL-00029" summary="refactor(api): 规范前端API接口地址并统一路径前缀 - 修正开发者、小程序和企业相关API的基础URL,避免重复添加/api前缀 - 统一调整developer、enterprise、invite等模块接口路径,保持与后端Controller一致 - 新增权限申请及消息通知相关API,并完善相关函数实现 - 规范请求参数传递,移除多余包装,改用直接传递对象方式 - 新增API Key重置、版本发布/回滚等接口支持 feat(invite): 重构邀请登录流程支持未注册快速加入 - 完整重写invite页,分离已注册用户和未注册用户的按钮显示和交互 - 实现未注册用户微信手机号授权后自动注册登录及自动加入应用 - 已注册用户直接确认加入应用,无需手机号授权弹窗 - 统一使用getPhoneNumber按钮处理两种状态并修复“授权码不能为空”报错 - 前端配合后端改造,支持已登录用户可直接通过Authorization头加入应用 fix(developer): 修复开发者中心应用加载问题 - 修复只查询创建的应用导致邀请加入的应用不显示的问题 - 同时请求创建应用和参与应用接口,合并并去重应用列表 - 新增pageJoinedApp接口调用及合并逻辑,提高应用列表完整性和体验">
|
||||
<option name="closed" value="true" />
|
||||
<created>1776065433562</created>
|
||||
<option name="number" value="00029" />
|
||||
<option name="presentableId" value="LOCAL-00029" />
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1776065433562</updated>
|
||||
</task>
|
||||
<task id="LOCAL-00030" summary="refactor(api): 规范前端API接口地址并统一路径前缀 - 修正开发者、小程序和企业相关API的基础URL,避免重复添加/api前缀 - 统一调整developer、enterprise、invite等模块接口路径,保持与后端Controller一致 - 新增权限申请及消息通知相关API,并完善相关函数实现 - 规范请求参数传递,移除多余包装,改用直接传递对象方式 - 新增API Key重置、版本发布/回滚等接口支持 feat(invite): 重构邀请登录流程支持未注册快速加入 - 完整重写invite页,分离已注册用户和未注册用户的按钮显示和交互 - 实现未注册用户微信手机号授权后自动注册登录及自动加入应用 - 已注册用户直接确认加入应用,无需手机号授权弹窗 - 统一使用getPhoneNumber按钮处理两种状态并修复“授权码不能为空”报错 - 前端配合后端改造,支持已登录用户可直接通过Authorization头加入应用 fix(developer): 修复开发者中心应用加载问题 - 修复只查询创建的应用导致邀请加入的应用不显示的问题 - 同时请求创建应用和参与应用接口,合并并去重应用列表 - 新增pageJoinedApp接口调用及合并逻辑,提高应用列表完整性和体验">
|
||||
<option name="closed" value="true" />
|
||||
<created>1782803958326</created>
|
||||
<option name="number" value="00030" />
|
||||
<option name="presentableId" value="LOCAL-00030" />
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1782803958326</updated>
|
||||
</task>
|
||||
<task id="LOCAL-00031" summary="refactor(api): 规范前端API接口地址并统一路径前缀 - 修正开发者、小程序和企业相关API的基础URL,避免重复添加/api前缀 - 统一调整developer、enterprise、invite等模块接口路径,保持与后端Controller一致 - 新增权限申请及消息通知相关API,并完善相关函数实现 - 规范请求参数传递,移除多余包装,改用直接传递对象方式 - 新增API Key重置、版本发布/回滚等接口支持 feat(invite): 重构邀请登录流程支持未注册快速加入 - 完整重写invite页,分离已注册用户和未注册用户的按钮显示和交互 - 实现未注册用户微信手机号授权后自动注册登录及自动加入应用 - 已注册用户直接确认加入应用,无需手机号授权弹窗 - 统一使用getPhoneNumber按钮处理两种状态并修复“授权码不能为空”报错 - 前端配合后端改造,支持已登录用户可直接通过Authorization头加入应用 fix(developer): 修复开发者中心应用加载问题 - 修复只查询创建的应用导致邀请加入的应用不显示的问题 - 同时请求创建应用和参与应用接口,合并并去重应用列表 - 新增pageJoinedApp接口调用及合并逻辑,提高应用列表完整性和体验">
|
||||
<option name="closed" value="true" />
|
||||
<created>1782803960387</created>
|
||||
<option name="number" value="00031" />
|
||||
<option name="presentableId" value="LOCAL-00031" />
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1782803960387</updated>
|
||||
</task>
|
||||
<task id="LOCAL-00032" summary="refactor(api): 规范前端API接口地址并统一路径前缀 - 修正开发者、小程序和企业相关API的基础URL,避免重复添加/api前缀 - 统一调整developer、enterprise、invite等模块接口路径,保持与后端Controller一致 - 新增权限申请及消息通知相关API,并完善相关函数实现 - 规范请求参数传递,移除多余包装,改用直接传递对象方式 - 新增API Key重置、版本发布/回滚等接口支持 feat(invite): 重构邀请登录流程支持未注册快速加入 - 完整重写invite页,分离已注册用户和未注册用户的按钮显示和交互 - 实现未注册用户微信手机号授权后自动注册登录及自动加入应用 - 已注册用户直接确认加入应用,无需手机号授权弹窗 - 统一使用getPhoneNumber按钮处理两种状态并修复“授权码不能为空”报错 - 前端配合后端改造,支持已登录用户可直接通过Authorization头加入应用 fix(developer): 修复开发者中心应用加载问题 - 修复只查询创建的应用导致邀请加入的应用不显示的问题 - 同时请求创建应用和参与应用接口,合并并去重应用列表 - 新增pageJoinedApp接口调用及合并逻辑,提高应用列表完整性和体验">
|
||||
<option name="closed" value="true" />
|
||||
<created>1782803963602</created>
|
||||
<option name="number" value="00032" />
|
||||
<option name="presentableId" value="LOCAL-00032" />
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1782803963602</updated>
|
||||
</task>
|
||||
<task id="LOCAL-00033" summary="feat(app): 添加应用入口文件和配置文件 - 新增 index.tsx 作为应用的主入口文件 - 新增 index.config.ts 用于应用的配置管理">
|
||||
<option name="closed" value="true" />
|
||||
<created>1782804027400</created>
|
||||
<option name="number" value="00033" />
|
||||
<option name="presentableId" value="LOCAL-00033" />
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1782804027400</updated>
|
||||
</task>
|
||||
<task id="LOCAL-00034" summary="feat(app): 添加应用入口文件和配置文件 - 新增 index.tsx 作为应用的主入口文件 - 新增 index.config.ts 用于应用的配置管理">
|
||||
<option name="closed" value="true" />
|
||||
<created>1782804461646</created>
|
||||
<option name="number" value="00034" />
|
||||
<option name="presentableId" value="LOCAL-00034" />
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1782804461646</updated>
|
||||
</task>
|
||||
<task id="LOCAL-00035" summary="fix(tailwind): 更新配置以兼容微信小程序 - 启用 autoprefixer 并新增 postcss-nested 插件支持嵌套语法 - 扩展主题颜色,添加 primary 色系 - 更新 content 路径包含 public/index.html - 禁用 preflight 以移除默认样式重置 - 移除多数 corePlugins 禁用项,保留 preflight 禁用,简化兼容性配置">
|
||||
<option name="closed" value="true" />
|
||||
<created>1782805513768</created>
|
||||
<option name="number" value="00035" />
|
||||
<option name="presentableId" value="LOCAL-00035" />
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1782805513768</updated>
|
||||
</task>
|
||||
<task id="LOCAL-00036" summary="feat(postcss): 添加插件移除微信小程序不兼容Tailwind规则 - 实现 postcss-remove-wxss-incompatible 插件 - 移除反斜杠转义的重要性修饰符类选择器(.\!xxx) - 删除依赖 :not 和兄弟选择器的 .space-[x|y]-* 规则 - 删除依赖 :not 和兄弟选择器的 .divide-[x|y]-* 规则 - 解决微信小程序 WXSS 不支持的 CSS 特性问题">
|
||||
<option name="closed" value="true" />
|
||||
<created>1782808735147</created>
|
||||
<option name="number" value="00036" />
|
||||
<option name="presentableId" value="LOCAL-00036" />
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1782808735147</updated>
|
||||
</task>
|
||||
<task id="LOCAL-00037" summary="feat(postcss): 添加插件移除微信小程序不兼容Tailwind规则 - 实现 postcss-remove-wxss-incompatible 插件 - 移除反斜杠转义的重要性修饰符类选择器(.\!xxx) - 删除依赖 :not 和兄弟选择器的 .space-[x|y]-* 规则 - 删除依赖 :not 和兄弟选择器的 .divide-[x|y]-* 规则 - 解决微信小程序 WXSS 不支持的 CSS 特性问题">
|
||||
<option name="closed" value="true" />
|
||||
<created>1782808737652</created>
|
||||
<option name="number" value="00037" />
|
||||
<option name="presentableId" value="LOCAL-00037" />
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1782808737652</updated>
|
||||
</task>
|
||||
<task id="LOCAL-00038" summary="feat(postcss): 添加插件移除微信小程序不兼容Tailwind规则 - 实现 postcss-remove-wxss-incompatible 插件 - 移除反斜杠转义的重要性修饰符类选择器(.\!xxx) - 删除依赖 :not 和兄弟选择器的 .space-[x|y]-* 规则 - 删除依赖 :not 和兄弟选择器的 .divide-[x|y]-* 规则 - 解决微信小程序 WXSS 不支持的 CSS 特性问题">
|
||||
<option name="closed" value="true" />
|
||||
<created>1782808741979</created>
|
||||
<option name="number" value="00038" />
|
||||
<option name="presentableId" value="LOCAL-00038" />
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1782808741979</updated>
|
||||
</task>
|
||||
<task id="LOCAL-00039" summary="fix(pay): 优化订阅号获取逻辑并调整支付组件样式 - 优先从小程序码 scene 参数读取 subscriptionNo,提升参数兼容性 - 对 scene 参数进行解码,避免编码引起的问题 - 移除支付组件价格显示处的固定颜色设置,增强样式灵活性">
|
||||
<option name="closed" value="true" />
|
||||
<created>1782810191833</created>
|
||||
<option name="number" value="00039" />
|
||||
<option name="presentableId" value="LOCAL-00039" />
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1782810191833</updated>
|
||||
</task>
|
||||
<task id="LOCAL-00040" summary="fix(utils): 优化小程序端复制文本功能体验 - 小程序端直接调用原生 wx.setClipboardData,避免 Taro Promise 封装问题 - 避免微信小程序重复显示复制成功提示和误报复制失败 - 失败时仅在小程序端输出错误日志,非小程序端显示提示 - 修改首页外链跳转逻辑,支持相对路径跳转,小程序内外链复制提示提升用户体验 - 更新项目内剪切板使用规范文档,说明复制行为调整及符合相应规范要求">
|
||||
<option name="closed" value="true" />
|
||||
<created>1782814230613</created>
|
||||
<option name="number" value="00040" />
|
||||
<option name="presentableId" value="LOCAL-00040" />
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1782814230613</updated>
|
||||
</task>
|
||||
<task id="LOCAL-00041" summary="fix(utils): 优化小程序端复制文本功能体验 - 小程序端直接调用原生 wx.setClipboardData,避免 Taro Promise 封装问题 - 避免微信小程序重复显示复制成功提示和误报复制失败 - 失败时仅在小程序端输出错误日志,非小程序端显示提示 - 修改首页外链跳转逻辑,支持相对路径跳转,小程序内外链复制提示提升用户体验 - 更新项目内剪切板使用规范文档,说明复制行为调整及符合相应规范要求">
|
||||
<option name="closed" value="true" />
|
||||
<created>1782814693982</created>
|
||||
<option name="number" value="00041" />
|
||||
<option name="presentableId" value="LOCAL-00041" />
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1782814693982</updated>
|
||||
</task>
|
||||
<task id="LOCAL-00042" summary="fix(index): 优化外链提示及删除复制咨询模板功能 - 替换小程序内外链打开方式提示为弹窗提醒用户手动复制 - 删除复制咨询模板功能及相关按钮 - 修改联系我们文案,突出直接电话沟通或需求反馈 - 移除无用导入的复制文本工具函数copyText">
|
||||
<option name="closed" value="true" />
|
||||
<created>1782814719111</created>
|
||||
<option name="number" value="00042" />
|
||||
<option name="presentableId" value="LOCAL-00042" />
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1782814719111</updated>
|
||||
</task>
|
||||
<task id="LOCAL-00043" summary="feat(utils): 优化请求函数URL拼接逻辑 - 在request函数中将请求拦截改为先拼接完整URL - 支持相对路径自动补全baseUrl - 删除原有请求拦截中url未处理的问题">
|
||||
<option name="closed" value="true" />
|
||||
<created>1782879460607</created>
|
||||
<option name="number" value="00043" />
|
||||
<option name="presentableId" value="LOCAL-00043" />
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1782879460607</updated>
|
||||
</task>
|
||||
<task id="LOCAL-00044" summary="feat(utils): 优化请求函数URL拼接逻辑 - 在request函数中将请求拦截改为先拼接完整URL - 支持相对路径自动补全baseUrl - 删除原有请求拦截中url未处理的问题">
|
||||
<option name="closed" value="true" />
|
||||
<created>1782881600013</created>
|
||||
<option name="number" value="00044" />
|
||||
<option name="presentableId" value="LOCAL-00044" />
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1782881600013</updated>
|
||||
</task>
|
||||
<task id="LOCAL-00045" summary="feat(utils): 优化请求函数URL拼接逻辑 - 在request函数中将请求拦截改为先拼接完整URL - 支持相对路径自动补全baseUrl - 删除原有请求拦截中url未处理的问题">
|
||||
<option name="closed" value="true" />
|
||||
<created>1782881602732</created>
|
||||
<option name="number" value="00045" />
|
||||
<option name="presentableId" value="LOCAL-00045" />
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1782881602732</updated>
|
||||
</task>
|
||||
<task id="LOCAL-00046" summary="feat(utils): 优化请求函数URL拼接逻辑 - 在request函数中将请求拦截改为先拼接完整URL - 支持相对路径自动补全baseUrl - 删除原有请求拦截中url未处理的问题">
|
||||
<option name="closed" value="true" />
|
||||
<created>1782881620199</created>
|
||||
<option name="number" value="00046" />
|
||||
<option name="presentableId" value="LOCAL-00046" />
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1782881620199</updated>
|
||||
</task>
|
||||
<task id="LOCAL-00047" summary="feat(utils): 优化请求函数URL拼接逻辑 - 在request函数中将请求拦截改为先拼接完整URL - 支持相对路径自动补全baseUrl - 删除原有请求拦截中url未处理的问题">
|
||||
<option name="closed" value="true" />
|
||||
<created>1782881623335</created>
|
||||
<option name="number" value="00047" />
|
||||
<option name="presentableId" value="LOCAL-00047" />
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1782881623335</updated>
|
||||
</task>
|
||||
<task id="LOCAL-00048" summary="feat(utils): 优化请求函数URL拼接逻辑 - 在request函数中将请求拦截改为先拼接完整URL - 支持相对路径自动补全baseUrl - 删除原有请求拦截中url未处理的问题">
|
||||
<option name="closed" value="true" />
|
||||
<created>1782881646712</created>
|
||||
<option name="number" value="00048" />
|
||||
<option name="presentableId" value="LOCAL-00048" />
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1782881646712</updated>
|
||||
</task>
|
||||
<task id="LOCAL-00049" summary="docs(schedule): 添加2026年7月1日日程文件 - 创建新的日程文档文件 2026-07-01.md - 文件内容待后续补充和更新">
|
||||
<option name="closed" value="true" />
|
||||
<created>1782899810526</created>
|
||||
<option name="number" value="00049" />
|
||||
<option name="presentableId" value="LOCAL-00049" />
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1782899810526</updated>
|
||||
</task>
|
||||
<task id="LOCAL-00050" summary="docs(schedule): 添加2026年7月1日日程文件 - 创建新的日程文档文件 2026-07-01.md - 文件内容待后续补充和更新">
|
||||
<option name="closed" value="true" />
|
||||
<created>1782899812498</created>
|
||||
<option name="number" value="00050" />
|
||||
<option name="presentableId" value="LOCAL-00050" />
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1782899812498</updated>
|
||||
</task>
|
||||
<task id="LOCAL-00051" summary="feat(pay): 优化小程序支付流程并增强日志调试 - 更新 wx-login 接口以返回完整 LoginResult,包括 access_token 和 user 对象 - 支付页面新增大量日志,详尽打印参数、请求和响应信息 - 优化获取订阅详情流程,支持 returnRaw 返回完整响应用于调试 - 支付流程新增步骤,先通过 code 获取 openid 并缓存,再尝试用 openid 自动登录 - 增加自动登录失败交互,提示未注册用户先登录 - 支付相关请求均使用 returnRaw 方便错误排查 - request 请求模块添加统一请求和响应日志,增强调试能力 - request 认证错误和业务错误时加入详细日志输出">
|
||||
<option name="closed" value="true" />
|
||||
<created>1782979300991</created>
|
||||
<option name="number" value="00051" />
|
||||
<option name="presentableId" value="LOCAL-00051" />
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1782979300991</updated>
|
||||
</task>
|
||||
<task id="LOCAL-00052" summary="feat(pay): 支付成功后自动跳转到已购产品页面 - 支付成功后显示成功提示 - 延迟1.5秒自动跳转到已购产品页面 - 处理跳转失败时回退到用户首页 - 优化用户支付完成后的使用体验">
|
||||
<option name="closed" value="true" />
|
||||
<created>1783003209100</created>
|
||||
<option name="number" value="00052" />
|
||||
<option name="presentableId" value="LOCAL-00052" />
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1783003209100</updated>
|
||||
</task>
|
||||
<task id="LOCAL-00053" summary="feat(pay-bak): 新增小程序支付页面及相关配置 - 新增确认支付页面配置,设置导航栏标题和样式 - 实现小程序支付页完整组件逻辑,包括加载订单、发起JSAPI支付等 - 支持通过 scene 或 subscriptionNo 获取订单参数 - 完善支付流程接口请求及错误处理,支持自动登录和支付确认 - 增加支付成功、支付中、支付失败、取消等状态页面展示 - 支持支付成功后跳转已购产品页或用户页 - 提供取消支付返回逻辑和界面展示 - 细化支付金额显示和提示用户支付后可使用应用功能">
|
||||
<option name="closed" value="true" />
|
||||
<created>1784131564427</created>
|
||||
<option name="number" value="00053" />
|
||||
<option name="presentableId" value="LOCAL-00053" />
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1784131564427</updated>
|
||||
</task>
|
||||
<task id="LOCAL-00054" summary="feat(address): 新增收货地址与售后申请功能 - 新增地址编辑页面,支持地址智能识别、地图选点、地区选择和默认地址设置 - 实现地址列表页面,支持地址查看、删除、设为默认及选择返回结算页 - 新增售后申请页面,支持退款类型选择、商品选择、原因填写、图片上传和提交审核 - 修复 passport 分包配置,移除不存在分包并补充缺失声明,避免 Taro 编译报错 - 新增地址类型定义,增强前端地址数据结构类型安全 - 优化页面交互体验,完善表单校验及错误提示逻辑 - 统一代码格式与命名规范,保持代码风格一致性">
|
||||
<option name="closed" value="true" />
|
||||
<created>1784135922873</created>
|
||||
<option name="number" value="00054" />
|
||||
<option name="presentableId" value="LOCAL-00054" />
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1784135922873</updated>
|
||||
</task>
|
||||
<option name="localTasksCounter" value="55" />
|
||||
<servers />
|
||||
</component>
|
||||
<component name="TypeScriptGeneratedFilesManager">
|
||||
<option name="version" value="3" />
|
||||
</component>
|
||||
<component name="Vcs.Log.Tabs.Properties">
|
||||
<option name="TAB_STATES">
|
||||
<map>
|
||||
<entry key="MAIN">
|
||||
<value>
|
||||
<State />
|
||||
</value>
|
||||
</entry>
|
||||
</map>
|
||||
</option>
|
||||
</component>
|
||||
<component name="VcsManagerConfiguration">
|
||||
<option name="CHECK_CODE_SMELLS_BEFORE_PROJECT_COMMIT" value="false" />
|
||||
<option name="CHECK_NEW_TODO" value="false" />
|
||||
<MESSAGE value="fix(invite): 修正邀请接口路径以支持小程序专用前缀 - 将请求邀请信息接口路径改为使用 /api/_app 前缀,确保小程序免登录访问 - 修改接受邀请接口路径,统一使用小程序专用 API 域名和前缀 - 更新邀请相关接口注释,明确小程序专用接口前缀含义 - 在专家历史数据中添加高级开发工程师 Will 的条目并更新时间戳" />
|
||||
<MESSAGE value="fix(passport): 添加邀请接受接口调试日志 - 在接受邀请请求前打印token和请求URL、参数 - 在请求响应后输出接口响应及返回数据 - 在调用失败时打印错误信息及异常日志 - 保持请求流程和状态管理不变,增强调试能力" />
|
||||
<MESSAGE value="fix(passport): 优化邀请码接受失败的用户提示和处理 - 针对“用户不存在”、“用户创建失败”及“请先登录”提示,改为弹窗提示需登录 - 在弹窗中增加“去登录”按钮,点击后跳转到登录页面,并携带重定向token参数 - 其他错误仍使用原Toast提示方式展示错误信息 - 保留错误日志打印,便于排查异常情况" />
|
||||
<MESSAGE value="feat(passport-invite): 优化新用户扫码邀请加入流程 - 扫码进入邀请页面时保存邀请 token 到本地存储 - 新增登录状态检测,未登录用户点击快速加入时保存邀请信息并跳转登录页 - 登录成功后返回邀请页自动执行加入应用操作,提升用户体验 - 支持已登录用户直接加入应用,简化流程 - 增加清理 pending 邀请数据的方法,防止数据残留 - 监听页面显示事件,处理登录返回后自动操作 - 调整接口请求逻辑,确保携带最新的邀请和授权数据 - 更新项目配置,注册邀请页面路径信息" />
|
||||
<MESSAGE value="feat(passport): 重写登录与邀请加入流程,支持微信手机号一键登录 - 完全重写 passport/login.tsx,实现微信手机号一键登录功能 - 支持登录页面携带 redirect 参数,登录后自动跳转回原页面 - 登录成功后自动处理邀请加入应用,支持邀请关系绑定 - 重写 passport/invite/index.tsx,实现扫码邀请加入应用完整流程 - 支持调用 wx.login 获取 code,使用 loginByOpenId 判断注册状态 - 未注册用户自动跳转登录页完成微信手机号登录再加入应用 - 已登录用户直接执行加入应用操作,加入接口支持 Authorization 头 -" />
|
||||
<MESSAGE value="feat(invite): 重构邀请加入流程,实现已登录用户免授权直接加入 - 完全重写邀请页逻辑,区分已登录与未注册用户按钮显示 - 已登录用户显示“确认加入”按钮,点击用 wx.login 获取 code 加入 - 未注册用户显示“微信手机号快速加入”按钮,授权手机号后完成注册登录再加入 - 移除跳转登录页面逻辑,所有流程在邀请页内完成 - 加入接口统一使用 Authorization 头携带 token,确保身份双重验证 - 优化背景、粒子和扫描线效果,提升界面视觉体验 - 增加错误处理提示,加入失败时显示详细原因并可返回首页 - 协议勾选仅在未注册用户显示,确保合规授权流程" />
|
||||
<MESSAGE value="feat(passport): 优化已登录用户加入时协议勾选逻辑 - 已登录用户点击「确认加入」时不再强制勾选协议 - 移除 handleConfirmJoin 中对协议勾选的检查 - 未注册用户仍需勾选协议后才能授权手机号 - 确保登录状态异常时有提示信息显示" />
|
||||
<MESSAGE value="feat(invite): 支持已登录用户直接加入应用 - 后端accept接口code参数改为可选,支持通过Authorization头识别登录用户 - 已登录用户加入时不再弹手机号授权,前端调用新增的doJoinAppForLoggedInUser方法 - 未注册用户仍通过手机号授权码code加入,调用doJoinAppForNewUser方法 - 拆分前端加入逻辑,新增handleConfirmJoin统一处理已登录加入流程 - 优化按钮交互,已登录状态下按钮变为普通点击,移除协议强制勾选 - 错误处理和状态提示完善,加入成功后自动跳转首页" />
|
||||
<MESSAGE value="feat(developer): 完成小程序开发者中心和企业控制台改造 - 设计并实现了开发者中心与企业控制台两大模块 - 按用户角色区分开发者和企业客户,支持多项目类型及成员管理 - 新增项目管理、应用管理、API Key管理及成员邀请等多功能页面 - 实现应用版本发布、消息通知中心、权限审批与开发者申请流程 - 完成CI/CD流水线、运营监控、发票管理、SSO单点登录功能 - 搭建SDK下载中心、工单系统、FAQ系统、数据导入导出等模块 - 优化后端API,支持已登录和未注册用户不同加入应用流程 - 前端按钮统一采用微信手机号授权,完善用户授权体验 - 修复多个页面的JSX语法错误及依赖导入问题,替换部分组件库 - 增加详细的类型定义文件,提升项目类型安全 - 新增超过55个页面及60个API接口,扩展应用功能和服务体系 - 完成全面的样式设计,实现一致的视觉风格和交互体验" />
|
||||
<MESSAGE value="feat(api-keys): 优化隐私协议同意逻辑及样式 - 修正api-keys样式导入路径,改为developer路径 - 在开发者申请页面增加服务协议和隐私政策勾选框 - 提交时校验协议是否已勾选,未勾选时提示用户 - 优化申请页面协议区域样式及交互 - 在用户验证页面添加隐私政策同意勾选框 - 提交用户验证表单时检查协议同意状态 - 增加打开服务协议和隐私政策的链接跳转 - 更新项目配置,增加passport/webview路由配置 - 修复createTicket函数中日期格式正则表达式问题" />
|
||||
<MESSAGE value="refactor(config): 移除开发者中心和企业控制台的自定义导航样式 - 删除了开发者中心页面配置中的 navigationStyle: 'custom' - 删除了企业控制台页面配置中的 navigationStyle: 'custom' - 简化了页面配置,保持默认导航样式 - 有助于提升页面兼容性和维护性" />
|
||||
<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="feat(app): 添加应用入口文件和配置文件 - 新增 index.tsx 作为应用的主入口文件 - 新增 index.config.ts 用于应用的配置管理" />
|
||||
<MESSAGE value="fix(tailwind): 更新配置以兼容微信小程序 - 启用 autoprefixer 并新增 postcss-nested 插件支持嵌套语法 - 扩展主题颜色,添加 primary 色系 - 更新 content 路径包含 public/index.html - 禁用 preflight 以移除默认样式重置 - 移除多数 corePlugins 禁用项,保留 preflight 禁用,简化兼容性配置" />
|
||||
<MESSAGE value="feat(postcss): 添加插件移除微信小程序不兼容Tailwind规则 - 实现 postcss-remove-wxss-incompatible 插件 - 移除反斜杠转义的重要性修饰符类选择器(.\!xxx) - 删除依赖 :not 和兄弟选择器的 .space-[x|y]-* 规则 - 删除依赖 :not 和兄弟选择器的 .divide-[x|y]-* 规则 - 解决微信小程序 WXSS 不支持的 CSS 特性问题" />
|
||||
<MESSAGE value="fix(pay): 优化订阅号获取逻辑并调整支付组件样式 - 优先从小程序码 scene 参数读取 subscriptionNo,提升参数兼容性 - 对 scene 参数进行解码,避免编码引起的问题 - 移除支付组件价格显示处的固定颜色设置,增强样式灵活性" />
|
||||
<MESSAGE value="fix(utils): 优化小程序端复制文本功能体验 - 小程序端直接调用原生 wx.setClipboardData,避免 Taro Promise 封装问题 - 避免微信小程序重复显示复制成功提示和误报复制失败 - 失败时仅在小程序端输出错误日志,非小程序端显示提示 - 修改首页外链跳转逻辑,支持相对路径跳转,小程序内外链复制提示提升用户体验 - 更新项目内剪切板使用规范文档,说明复制行为调整及符合相应规范要求" />
|
||||
<MESSAGE value="fix(index): 优化外链提示及删除复制咨询模板功能 - 替换小程序内外链打开方式提示为弹窗提醒用户手动复制 - 删除复制咨询模板功能及相关按钮 - 修改联系我们文案,突出直接电话沟通或需求反馈 - 移除无用导入的复制文本工具函数copyText" />
|
||||
<MESSAGE value="feat(utils): 优化请求函数URL拼接逻辑 - 在request函数中将请求拦截改为先拼接完整URL - 支持相对路径自动补全baseUrl - 删除原有请求拦截中url未处理的问题" />
|
||||
<MESSAGE value="docs(schedule): 添加2026年7月1日日程文件 - 创建新的日程文档文件 2026-07-01.md - 文件内容待后续补充和更新" />
|
||||
<MESSAGE value="feat(pay): 优化小程序支付流程并增强日志调试 - 更新 wx-login 接口以返回完整 LoginResult,包括 access_token 和 user 对象 - 支付页面新增大量日志,详尽打印参数、请求和响应信息 - 优化获取订阅详情流程,支持 returnRaw 返回完整响应用于调试 - 支付流程新增步骤,先通过 code 获取 openid 并缓存,再尝试用 openid 自动登录 - 增加自动登录失败交互,提示未注册用户先登录 - 支付相关请求均使用 returnRaw 方便错误排查 - request 请求模块添加统一请求和响应日志,增强调试能力 - request 认证错误和业务错误时加入详细日志输出" />
|
||||
<MESSAGE value="feat(pay): 支付成功后自动跳转到已购产品页面 - 支付成功后显示成功提示 - 延迟1.5秒自动跳转到已购产品页面 - 处理跳转失败时回退到用户首页 - 优化用户支付完成后的使用体验" />
|
||||
<MESSAGE value="feat(pay-bak): 新增小程序支付页面及相关配置 - 新增确认支付页面配置,设置导航栏标题和样式 - 实现小程序支付页完整组件逻辑,包括加载订单、发起JSAPI支付等 - 支持通过 scene 或 subscriptionNo 获取订单参数 - 完善支付流程接口请求及错误处理,支持自动登录和支付确认 - 增加支付成功、支付中、支付失败、取消等状态页面展示 - 支持支付成功后跳转已购产品页或用户页 - 提供取消支付返回逻辑和界面展示 - 细化支付金额显示和提示用户支付后可使用应用功能" />
|
||||
<MESSAGE value="feat(address): 新增收货地址与售后申请功能 - 新增地址编辑页面,支持地址智能识别、地图选点、地区选择和默认地址设置 - 实现地址列表页面,支持地址查看、删除、设为默认及选择返回结算页 - 新增售后申请页面,支持退款类型选择、商品选择、原因填写、图片上传和提交审核 - 修复 passport 分包配置,移除不存在分包并补充缺失声明,避免 Taro 编译报错 - 新增地址类型定义,增强前端地址数据结构类型安全 - 优化页面交互体验,完善表单校验及错误提示逻辑 - 统一代码格式与命名规范,保持代码风格一致性" />
|
||||
<option name="LAST_COMMIT_MESSAGE" value="feat(address): 新增收货地址与售后申请功能 - 新增地址编辑页面,支持地址智能识别、地图选点、地区选择和默认地址设置 - 实现地址列表页面,支持地址查看、删除、设为默认及选择返回结算页 - 新增售后申请页面,支持退款类型选择、商品选择、原因填写、图片上传和提交审核 - 修复 passport 分包配置,移除不存在分包并补充缺失声明,避免 Taro 编译报错 - 新增地址类型定义,增强前端地址数据结构类型安全 - 优化页面交互体验,完善表单校验及错误提示逻辑 - 统一代码格式与命名规范,保持代码风格一致性" />
|
||||
<option name="NON_MODAL_COMMIT_POSTPONE_SLOW_CHECKS" value="false" />
|
||||
</component>
|
||||
</project>
|
||||
BIN
Binary file not shown.
@@ -1,61 +0,0 @@
|
||||
{
|
||||
"version": 2,
|
||||
"sessions": {
|
||||
"22fe5580026e4f249cf9bb33390baa6a": [
|
||||
{
|
||||
"expertId": "SeniorDeveloper",
|
||||
"name": "Will",
|
||||
"profession": "高级开发工程师",
|
||||
"avatarUrl": "https://acc-1258344699.cos.accelerate.myqcloud.com/workbuddy/experts/avatars/02-Engineering/SeniorDeveloper/SeniorDeveloper.png",
|
||||
"promptUrl": "https://acc-1258344699.cos.accelerate.myqcloud.com/workbuddy/experts/experts/02-Engineering/SeniorDeveloper/SeniorDeveloper_zh.md",
|
||||
"usedAt": 1775908159660,
|
||||
"industryId": "all"
|
||||
}
|
||||
],
|
||||
"751f0e28e31849adb28b750fe4cdcde7": [
|
||||
{
|
||||
"expertId": "SeniorDeveloper",
|
||||
"name": "Will",
|
||||
"profession": "高级开发工程师",
|
||||
"avatarUrl": "https://acc-1258344699.cos.accelerate.myqcloud.com/workbuddy/experts/avatars/02-Engineering/SeniorDeveloper/SeniorDeveloper.png",
|
||||
"promptUrl": "https://acc-1258344699.cos.accelerate.myqcloud.com/workbuddy/experts/experts/02-Engineering/SeniorDeveloper/SeniorDeveloper_zh.md",
|
||||
"usedAt": 1775908159660,
|
||||
"industryId": "all"
|
||||
}
|
||||
],
|
||||
"f2494c1730eb411aac709ec2751d60fc": [
|
||||
{
|
||||
"expertId": "SeniorDeveloper",
|
||||
"name": "Will",
|
||||
"profession": "高级开发工程师",
|
||||
"avatarUrl": "https://acc-1258344699.cos.accelerate.myqcloud.com/workbuddy/experts/avatars/02-Engineering/SeniorDeveloper/SeniorDeveloper.png",
|
||||
"promptUrl": "https://acc-1258344699.cos.accelerate.myqcloud.com/workbuddy/experts/experts/02-Engineering/SeniorDeveloper/SeniorDeveloper_zh.md",
|
||||
"usedAt": 1775921148885,
|
||||
"industryId": "all"
|
||||
}
|
||||
],
|
||||
"784e1c35ace143c4bd1aad6e187435b1": [
|
||||
{
|
||||
"expertId": "SeniorDeveloper",
|
||||
"name": "Will",
|
||||
"profession": "高级开发工程师",
|
||||
"avatarUrl": "https://acc-1258344699.cos.accelerate.myqcloud.com/workbuddy/experts/avatars/02-Engineering/SeniorDeveloper/SeniorDeveloper.png",
|
||||
"promptUrl": "https://acc-1258344699.cos.accelerate.myqcloud.com/workbuddy/experts/experts/02-Engineering/SeniorDeveloper/SeniorDeveloper_zh.md",
|
||||
"usedAt": 1775972794982,
|
||||
"industryId": "all"
|
||||
}
|
||||
],
|
||||
"e1bb762122264b5d9d218f575fecf04b": [
|
||||
{
|
||||
"expertId": "SeniorDeveloper",
|
||||
"name": "Will",
|
||||
"profession": "高级开发工程师",
|
||||
"avatarUrl": "https://acc-1258344699.cos.accelerate.myqcloud.com/workbuddy/experts/avatars/02-Engineering/SeniorDeveloper/SeniorDeveloper.png",
|
||||
"promptUrl": "https://acc-1258344699.cos.accelerate.myqcloud.com/workbuddy/experts/experts/02-Engineering/SeniorDeveloper/SeniorDeveloper_zh.md",
|
||||
"usedAt": 1776019266960,
|
||||
"industryId": "all"
|
||||
}
|
||||
]
|
||||
},
|
||||
"lastUpdated": 1776023218479
|
||||
}
|
||||
@@ -1,253 +0,0 @@
|
||||
# 2026-04-11 工作记录
|
||||
|
||||
## 任务:修复邀请加入应用流程
|
||||
|
||||
### 问题描述
|
||||
`passport/invite/index` 页面在新用户扫码加入应用时存在问题:
|
||||
- 新用户点击"微信手机号快速加入"时,如果未注册,后端返回"用户不存在"
|
||||
- 页面引导用户去登录,但登录完成后**没有自动执行加入应用的操作**
|
||||
|
||||
### 解决方案
|
||||
参考 `UserCard.tsx` 和 `phone-auth/index.tsx` 的实现模式,修改了 `src/passport/invite/index.tsx`:
|
||||
|
||||
#### 1. 新增功能
|
||||
- 页面加载时保存邀请 token 到本地存储
|
||||
- 检测用户登录状态
|
||||
- 未登录用户点击加入时,保存邀请信息并引导到 `phone-auth` 登录页
|
||||
- 登录成功后返回邀请页面,自动执行加入操作
|
||||
- 新增 `clearPendingInviteData` 方法清理临时数据
|
||||
|
||||
#### 2. 关键修改点
|
||||
- 使用 `saveInviteParams` 保存邀请信息
|
||||
- 使用 `pending_invite_*` 系列 storage key 保存待处理的邀请数据
|
||||
- 页面显示时通过 `AppShow` 事件监听登录返回
|
||||
- 登录成功后自动调用 `handleJoinApp` 完成加入
|
||||
|
||||
#### 3. 流程优化
|
||||
```
|
||||
新用户流程:
|
||||
1. 扫码进入邀请页面 → 保存 invite token
|
||||
2. 点击"微信手机号快速加入"
|
||||
3. 检测到未登录 → 保存 pending 数据 → 跳转到 phone-auth
|
||||
4. 完成登录/注册 → 返回邀请页面
|
||||
5. 自动检测 pending 数据 → 自动执行加入
|
||||
6. 加入成功 → 跳转到首页
|
||||
|
||||
已登录用户流程:
|
||||
1. 扫码进入邀请页面
|
||||
2. 点击"微信手机号快速加入"
|
||||
3. 直接执行加入操作
|
||||
4. 加入成功 → 跳转到首页
|
||||
```
|
||||
|
||||
### 文件修改
|
||||
- `src/passport/invite/index.tsx` - 完整重构邀请流程
|
||||
|
||||
---
|
||||
|
||||
## 任务:重写 passport/login 页面
|
||||
|
||||
### 问题描述
|
||||
`passport/login.tsx` 页面只有 UI 框架,没有实现登录逻辑:
|
||||
- 输入框没有绑定状态
|
||||
- 登录按钮没有点击事件
|
||||
- 不支持微信手机号登录
|
||||
- 无法处理邀请流程的重定向
|
||||
|
||||
### 解决方案
|
||||
基于 `phone-auth/index.tsx` 的实现,重写 `login.tsx`:
|
||||
|
||||
#### 1. 功能实现
|
||||
- 微信手机号一键登录(使用 `open-type="getPhoneNumber"`)
|
||||
- 支持 `redirect` 参数,登录后返回原页面
|
||||
- 未注册用户自动注册
|
||||
- 支持邀请关系绑定
|
||||
- 服务协议和隐私政策弹窗
|
||||
|
||||
#### 2. 跳转逻辑
|
||||
```
|
||||
有 redirect 参数:
|
||||
- TabBar 页面 → switchTab
|
||||
- 普通页面 → navigateBack 或 redirectTo
|
||||
|
||||
无 redirect 参数:
|
||||
- 跳转到首页 /pages/index/index
|
||||
```
|
||||
|
||||
#### 3. 统一登录入口
|
||||
- 将 `invite/index.tsx` 中的登录跳转统一改为 `/passport/login`
|
||||
- 保持与现有代码引用兼容(5处引用无需修改)
|
||||
|
||||
### 文件修改
|
||||
- `src/passport/login.tsx` - 完全重写为微信手机号登录页面
|
||||
- `src/passport/invite/index.tsx` - 更新登录跳转链接
|
||||
|
||||
---
|
||||
|
||||
## 任务:修复登录后加入应用流程
|
||||
|
||||
### 问题描述
|
||||
用户从邀请页面跳转到登录页面完成注册后,返回邀请页面提示"您尚未注册",无法成功加入应用。
|
||||
|
||||
### 问题分析
|
||||
1. 用户在邀请页面点击"微信手机号快速加入"时保存了微信授权码
|
||||
2. 跳转到登录页面后,用户**重新**进行了微信授权,获得了新的授权码
|
||||
3. 登录成功后返回邀请页面,但邀请页面使用的是旧的已失效的授权码
|
||||
4. 导致加入应用失败
|
||||
|
||||
### 解决方案
|
||||
在登录页面登录成功后,直接使用**当前获取的微信授权码**完成加入应用操作:
|
||||
|
||||
#### 1. 登录页面修改 (`login.tsx`)
|
||||
- 添加 `handleJoinAppAfterLogin` 方法
|
||||
- 登录成功后检测 `pending_invite_token`
|
||||
- 如果存在,使用当前授权码调用 `/api/_app/developer/invite/accept` 接口
|
||||
- 加入成功后清理 pending 数据并跳转到首页
|
||||
- 加入失败则继续正常登录流程
|
||||
|
||||
#### 2. 邀请页面修改 (`invite/index.tsx`)
|
||||
- 优化从登录页返回的处理逻辑
|
||||
- 检测 `pending_invite_phone_code` 是否存在
|
||||
- 延迟检查登录页面是否已处理加入操作
|
||||
- 如果登录页面已处理(token 被清除),则显示成功提示并跳转
|
||||
- 如果登录页面未处理,则自动执行加入操作
|
||||
|
||||
### 新的完整流程
|
||||
```
|
||||
新用户扫码加入流程:
|
||||
1. 扫码进入邀请页面
|
||||
2. 点击"微信手机号快速加入"
|
||||
3. 检测到未登录,保存 pending 数据,跳转到登录页面
|
||||
4. 在登录页面勾选协议,点击"微信手机号一键登录"
|
||||
5. 获取新的微信授权码,完成登录/注册
|
||||
6. 登录成功后检测到 pending_invite_token,自动执行加入应用
|
||||
7. 加入成功,清理数据,跳转到首页
|
||||
|
||||
已登录用户流程:
|
||||
1. 扫码进入邀请页面
|
||||
2. 点击"微信手机号快速加入"
|
||||
3. 直接执行加入操作
|
||||
4. 加入成功,跳转到首页
|
||||
```
|
||||
|
||||
### 文件修改
|
||||
- `src/passport/login.tsx` - 添加登录后自动加入应用逻辑
|
||||
- `src/passport/invite/index.tsx` - 优化从登录页返回的处理逻辑
|
||||
|
||||
---
|
||||
|
||||
## 任务:修复"用户创建失败"问题
|
||||
|
||||
### 问题描述
|
||||
后端返回 `"用户创建失败"`,原因是微信授权码失效。
|
||||
|
||||
### 问题分析
|
||||
1. 用户在邀请页面获取了微信授权码
|
||||
2. 跳转到登录页面后,用户**重新**点击授权按钮获取了新的授权码
|
||||
3. 但代码逻辑可能混淆了新旧授权码
|
||||
4. 或者授权码已过期(5分钟有效期)
|
||||
|
||||
### 解决方案
|
||||
1. **明确使用当前获取的授权码**:在登录页面登录成功后,使用**当前**获取的微信授权码执行加入应用操作
|
||||
2. **不使用旧的授权码**:从邀请页面带过来的授权码仅作为标记用途,实际使用登录时获取的新授权码
|
||||
3. **添加错误处理**:加入失败时显示错误信息并延迟跳转
|
||||
|
||||
### 关键修改
|
||||
- `login.tsx`:优化 `handleLogin` 中的邀请流程处理逻辑
|
||||
- 明确使用当前 `phoneCode` 执行加入操作
|
||||
- 添加错误处理和用户提示
|
||||
- 加入失败后延迟跳转,让用户看到错误信息
|
||||
|
||||
---
|
||||
|
||||
## 任务:修复已登录用户加入应用失败问题
|
||||
|
||||
### 问题描述
|
||||
已登录用户点击"微信手机号快速加入"时,后端返回 `"用户创建失败"`。
|
||||
|
||||
### 问题分析
|
||||
1. 用户已经是登录状态(有 `access_token`)
|
||||
2. 但后端接口 `/api/_app/developer/invite/accept` 仍然尝试"创建用户"
|
||||
3. 原因是后端无法识别当前已登录的用户身份
|
||||
|
||||
### 解决方案
|
||||
为已登录用户在请求头中添加 `Authorization: Bearer {access_token}`,让后端能正确识别用户身份:
|
||||
|
||||
#### 1. 邀请页面修改 (`invite/index.tsx`)
|
||||
- 在 `handleJoinApp` 方法中获取 `access_token`
|
||||
- 构建请求头时,如果用户已登录,添加 `Authorization` 头
|
||||
- 添加日志记录是否使用了认证头
|
||||
|
||||
#### 2. 登录页面修改 (`login.tsx`)
|
||||
- 在 `handleJoinAppAfterLogin` 方法中同样添加 `Authorization` 头
|
||||
- 确保登录成功后调用加入接口时携带认证信息
|
||||
|
||||
### 文件修改
|
||||
- `src/passport/invite/index.tsx` - 添加 Authorization 请求头支持
|
||||
- `src/passport/login.tsx` - 添加 Authorization 请求头支持
|
||||
|
||||
---
|
||||
|
||||
## 任务:修复 401 认证失败问题
|
||||
|
||||
### 问题描述
|
||||
后端返回 `401` 和 `"请退出重新登录"`,`error: "Username not found"`。
|
||||
|
||||
### 问题分析
|
||||
1. 请求带了 `Authorization: Bearer {token}` 头
|
||||
2. 但后端验证 token 时发现用户不存在
|
||||
3. 原因可能是:
|
||||
- token 已过期
|
||||
- token 对应的用户已被删除
|
||||
- `/api/_app` 前缀的接口设计为"免登录",使用 `Authorization` 头反而导致认证失败
|
||||
|
||||
### 解决方案
|
||||
`/api/_app` 前缀的接口是小程序专用免登录接口,应该优先使用微信授权码方式:
|
||||
|
||||
#### 策略调整
|
||||
- **有微信授权码**:使用授权码方式,不传 `Authorization` 头
|
||||
- **无授权码但已登录**:使用 `Authorization` 头
|
||||
|
||||
#### 修改内容
|
||||
- `invite/index.tsx`:调整 `handleJoinApp` 方法中的请求头逻辑
|
||||
- 优先使用微信授权码,避免使用可能过期的 token
|
||||
|
||||
### 文件修改
|
||||
- `src/passport/invite/index.tsx` - 调整认证策略,优先使用微信授权码
|
||||
|
||||
---
|
||||
|
||||
## 任务:重写邀请加入应用流程
|
||||
|
||||
### 新流程设计
|
||||
1. 扫码进入邀请页面
|
||||
2. 调用 `wx.login()` 获取 code
|
||||
3. 调用 `loginByOpenId` 判断用户是否已注册
|
||||
4. 已注册:显示邀请页面,用户点击加入
|
||||
5. 未注册:跳转到 `passport/login` 页面完成登录/注册
|
||||
6. 登录成功后自动执行加入应用操作
|
||||
|
||||
### 实现细节
|
||||
|
||||
#### 1. 邀请页面 (`invite/index.tsx`)
|
||||
- 页面状态管理:`loading` → `checking` → `invite`/`login`/`error`
|
||||
- `initPage()`:解析 token,保存到 storage
|
||||
- `checkLoginStatus()`:调用 `loginByOpenId` 检查登录状态
|
||||
- `navigateToLogin()`:未登录用户跳转到登录页
|
||||
- `fetchInviteInfo()`:获取邀请信息
|
||||
- `handleGetPhoneNumber()`:处理微信授权
|
||||
- `handleJoinApp()`:执行加入应用操作
|
||||
|
||||
#### 2. 登录页面 (`login.tsx`)
|
||||
- 登录成功后检查 `invite_token`
|
||||
- 如果存在,使用当前授权码自动执行加入应用
|
||||
- 加入成功后跳转到首页
|
||||
|
||||
### 简化点
|
||||
- 不再使用复杂的 `pending_invite_*` 系列 storage key
|
||||
- 只使用 `invite_token` 保存邀请标识
|
||||
- 登录页面直接使用当前获取的微信授权码
|
||||
|
||||
### 文件修改
|
||||
- `src/passport/invite/index.tsx` - 完全重写
|
||||
- `src/passport/login.tsx` - 更新为使用 `invite_token`
|
||||
@@ -1,615 +0,0 @@
|
||||
# 2026-04-12 工作日志
|
||||
|
||||
## 项目:小程序改造规划
|
||||
|
||||
### 背景
|
||||
用户需要为 websopy-mp 小程序增加:
|
||||
- 🛠️ **开发者中心** - 面向开发者,围绕项目展开
|
||||
- 🏢 **企业控制台** - 面向企业客户
|
||||
|
||||
### 关键决策
|
||||
1. **用户角色分两类**:
|
||||
- 开发者 (Developer):独立于企业存在,申请审核制
|
||||
- 企业客户 (Enterprise):属于企业组织,购买产品开通
|
||||
|
||||
2. **开发模式**:独立开发,不复用现有模块(商城/分销/用户订单)
|
||||
|
||||
3. **核心围绕"项目"展开**:
|
||||
- 项目类型:基础/专业/企业
|
||||
- 项目成员、API Key、应用关联
|
||||
|
||||
### Phase 1 开发完成 ✅
|
||||
已完成以下功能开发:
|
||||
|
||||
#### 新增目录结构
|
||||
```
|
||||
src/
|
||||
├── developer/ # 🛠️ 开发者模块
|
||||
│ ├── index.tsx # 开发者工作台
|
||||
│ ├── project/ # 项目管理
|
||||
│ │ ├── index.tsx # 项目列表
|
||||
│ │ ├── create.tsx # 创建项目
|
||||
│ │ └── [id]/ # 项目详情
|
||||
│ ├── app/ # 应用管理
|
||||
│ │ ├── index.tsx # 应用列表
|
||||
│ │ ├── create.tsx # 创建应用
|
||||
│ │ ├── [id]/ # 应用详情
|
||||
│ │ └── api-keys/ # API Key 管理
|
||||
│ └── docs/ # 开发者文档
|
||||
│
|
||||
├── enterprise/ # 🏢 企业模块
|
||||
│ ├── index.tsx # 企业工作台
|
||||
│ ├── apps/ # 企业应用
|
||||
│ │ ├── index.tsx # 应用列表
|
||||
│ │ ├── [id]/ # 应用详情
|
||||
│ │ └── purchase/ # 购买应用
|
||||
│ ├── members/ # 成员管理
|
||||
│ │ ├── index.tsx # 成员列表
|
||||
│ │ └── invite/ # 邀请成员
|
||||
│ ├── orders/ # 订单账单
|
||||
│ │ ├── index.tsx # 订单列表
|
||||
│ │ └── detail/ # 订单详情
|
||||
│ ├── billing/ # 费用中心
|
||||
│ └── settings/ # 企业设置
|
||||
│
|
||||
├── api/developer/ # API 模块
|
||||
│ ├── index.ts
|
||||
│ ├── developer.ts # 开发者 API
|
||||
│ └── enterprise.ts # 企业 API
|
||||
│
|
||||
└── types/developer.ts # 类型定义
|
||||
```
|
||||
|
||||
#### 新增页面
|
||||
- 首页角色入口卡片(开发者中心/企业控制台)
|
||||
- 开发者工作台 + 统计卡片 + 快捷操作
|
||||
- 项目列表/创建/详情
|
||||
- 应用列表/创建/详情
|
||||
- 企业工作台 + 统计卡片 + 快捷操作
|
||||
- 企业应用列表/购买/详情
|
||||
- 成员列表/邀请
|
||||
- 订单列表/详情(占位)
|
||||
- 费用中心/企业设置(占位)
|
||||
|
||||
#### 新增 API
|
||||
- 项目管理 API
|
||||
- 应用管理 API
|
||||
- API Key 管理 API
|
||||
- 企业成员 API
|
||||
- 订单账单 API
|
||||
|
||||
### 待确认
|
||||
- 后端 API 接口地址是否正确
|
||||
- 是否需要权限验证
|
||||
- 微信小程序码支持
|
||||
|
||||
---
|
||||
|
||||
## Phase 2 开发完成 ✅ (2026-04-12 19:49)
|
||||
|
||||
### 新增功能模块
|
||||
|
||||
#### 1. API Key 完整管理
|
||||
- 列表展示(名称/类型/状态/创建时间)
|
||||
- 创建 Key(名称/类型/备注)
|
||||
- 复制 AppId/AppSecret
|
||||
- 删除 Key
|
||||
- 状态筛选
|
||||
|
||||
#### 2. 项目成员管理
|
||||
- 成员列表展示(头像/名称/角色)
|
||||
- 角色统计(所有者/管理员/开发者/查看者)
|
||||
- 邀请成员(用户名/角色选择)
|
||||
- 修改成员角色
|
||||
- 移除成员
|
||||
|
||||
#### 3. 应用版本发布
|
||||
- 版本列表(版本号/状态/环境)
|
||||
- 环境 Tab 筛选(全部/开发/预发布/生产)
|
||||
- 发布新版本(名称/版本号/环境/更新日志)
|
||||
- 状态标签(构建中/已发布/已回滚/构建失败)
|
||||
|
||||
#### 4. 消息通知中心
|
||||
- 通知列表(类型/标题/内容/时间)
|
||||
- 类型筛选(全部/系统/应用/成员/订单)
|
||||
- 未读数量徽标
|
||||
- 标记已读/全部已读
|
||||
- 删除通知
|
||||
- 点击跳转详情
|
||||
|
||||
#### 5. 开发者申请
|
||||
- 申请表单(姓名/手机/邮箱/公司/职位/经验)
|
||||
- 申请记录列表
|
||||
- 状态筛选(待审核/已通过/已拒绝)
|
||||
- 审核备注展示
|
||||
|
||||
#### 6. 权限审批
|
||||
- 申请列表(申请人/类型/目标/状态)
|
||||
- Tab 筛选(待审核/已通过/已拒绝/全部)
|
||||
- 待审核数量徽标
|
||||
- 通过/拒绝操作
|
||||
- 审核备注展示
|
||||
|
||||
### 新增/扩展的页面
|
||||
```
|
||||
src/developer/
|
||||
├── project/[id]/
|
||||
│ ├── members.tsx # 项目成员管理 ✅
|
||||
│ ├── members.scss
|
||||
│ ├── api-keys.tsx # 项目级 API Key
|
||||
│ ├── api-keys.scss
|
||||
│ ├── api-keys.config.ts
|
||||
│ ├── settings.tsx # 项目设置
|
||||
│ └── settings.scss
|
||||
├── app/[id]/
|
||||
│ ├── version.tsx # 版本管理 ✅
|
||||
│ ├── version.scss
|
||||
│ ├── config.tsx # 应用配置
|
||||
│ ├── publish.tsx # 发布管理
|
||||
│ └── *.config.ts
|
||||
├── notification/ # 消息通知 ✅
|
||||
│ ├── index.tsx
|
||||
│ └── index.scss
|
||||
├── developer/
|
||||
│ ├── apply.tsx # 开发者申请 ✅
|
||||
│ ├── apply.scss
|
||||
│ └── profile.tsx # 开发者资料
|
||||
├── docs/
|
||||
│ ├── quickstart.tsx # 快速开始
|
||||
│ └── api-docs.tsx # API 文档
|
||||
└── market/ # 开发者市场
|
||||
└── index.tsx
|
||||
|
||||
src/enterprise/
|
||||
├── apps/[id]/
|
||||
│ ├── monitor.tsx # 运营监控
|
||||
│ ├── analytics.tsx # 数据分析
|
||||
│ └── settings.tsx # 应用设置
|
||||
├── members/
|
||||
│ ├── roles.tsx # 角色权限
|
||||
│ └── audit.tsx # 权限审批 ✅
|
||||
├── orders/
|
||||
│ ├── invoice.tsx # 发票管理
|
||||
│ └── bills.tsx # 账单明细
|
||||
├── billing/
|
||||
│ ├── consumption.tsx # 消费明细
|
||||
│ ├── recharge.tsx # 充值中心
|
||||
│ └── coupons.tsx # 优惠券
|
||||
├── settings/
|
||||
│ ├── info.tsx # 企业信息
|
||||
│ ├── domain.tsx # 域名配置
|
||||
│ └── security.tsx # 安全设置
|
||||
└── developer/
|
||||
├── index.tsx # 开发者入口
|
||||
└── apply.tsx # 申请开发者
|
||||
```
|
||||
|
||||
### 扩展的类型定义
|
||||
```typescript
|
||||
// 消息通知
|
||||
interface Notification {
|
||||
id, type, title, content, data, isRead, readTime, createTime
|
||||
}
|
||||
|
||||
// 权限申请
|
||||
interface Apply {
|
||||
id, type, applicantId, applicantName, applicantAvatar,
|
||||
targetId, targetName, reason, status, reviewerId,
|
||||
reviewerName, reviewTime, reviewRemark, createTime
|
||||
}
|
||||
```
|
||||
|
||||
### 扩展的 API
|
||||
```typescript
|
||||
// 通知相关
|
||||
pageNotification, getUnreadCount, markAsRead, markAllAsRead, deleteNotification
|
||||
|
||||
// 申请相关
|
||||
pageApply, pageMyApply, createApply, reviewApply
|
||||
```
|
||||
|
||||
### 路由配置更新
|
||||
新增 20+ 个页面路由
|
||||
|
||||
---
|
||||
|
||||
## Phase 3 开发完成 ✅ (2026-04-12 20:15)
|
||||
|
||||
### 新增功能模块
|
||||
|
||||
#### 1. CI/CD 流水线
|
||||
- **构建任务列表**:状态筛选/触发构建/取消构建/查看详情
|
||||
- **构建详情**:构建信息/Git 信息/构建日志/构建产物
|
||||
- **部署列表**:环境筛选/部署统计/触发部署/回滚操作
|
||||
- **部署详情**:部署进度/日志/回滚功能
|
||||
- **流水线配置**:启用/禁用、自动部署、分支规则、环境变量、Webhook
|
||||
|
||||
#### 2. 运营监控/数据分析
|
||||
- **实时数据看板**:UV/PV/API 调用/错误数/Live 指示器
|
||||
- **今日概览**:UV/PV/API 调用统计卡片
|
||||
- **数据趋势**:图表占位(可集成 ECharts)
|
||||
- **性能指标**:核心指标展示/趋势标签/错误统计
|
||||
- **用户分析**:活跃用户/新增用户/留存率/地域分布/流量来源
|
||||
- **页面排行**:PV/UV 排行
|
||||
|
||||
#### 3. 发票管理
|
||||
- **发票列表**:状态筛选/发票详情/取消申请
|
||||
- **可开票金额**:实时金额展示
|
||||
- **申请发票**:抬头选择/金额输入/提交申请
|
||||
- **发票抬头管理**:增删改查/默认设置
|
||||
- **发票统计**:申请总数/已开票数量
|
||||
|
||||
#### 4. SSO 单点登录
|
||||
- **SSO 配置**:OIDC/SAML/CAS/LDAP/OAuth2 提供商支持
|
||||
- **连接配置**:Issuer/Client ID/Client Secret/授权 URL 等
|
||||
- **用户同步**:同步开关/自动创建/默认角色
|
||||
- **服务提供商信息**:Entity ID/ACS URL(可复制)
|
||||
- **操作日志**:登录/登出/错误记录
|
||||
|
||||
### 新增类型定义
|
||||
```
|
||||
src/types/
|
||||
├── index.ts # 统一导出
|
||||
├── cicd.ts # CI/CD 流水线类型
|
||||
├── analytics.ts # 数据分析类型
|
||||
├── invoice.ts # 发票管理类型
|
||||
└── sso.ts # SSO 单点登录类型
|
||||
```
|
||||
|
||||
### 新增 API
|
||||
```
|
||||
src/api/
|
||||
├── cicd.ts # CI/CD 流水线 API
|
||||
├── analytics.ts # 数据分析 API
|
||||
├── invoice.ts # 发票管理 API
|
||||
└── sso.ts # SSO 单点登录 API
|
||||
```
|
||||
|
||||
### 新增页面
|
||||
```
|
||||
src/developer/app/[id]/
|
||||
├── builds.tsx # 构建列表
|
||||
├── builds.scss
|
||||
├── build/[id].tsx # 构建详情
|
||||
├── build/[id].scss
|
||||
├── deploys.tsx # 部署列表
|
||||
├── deploys.scss
|
||||
├── deploy/[id].tsx # 部署详情
|
||||
├── deploy/[id].scss
|
||||
├── pipeline.tsx # 流水线配置
|
||||
├── pipeline.scss
|
||||
├── analytics.tsx # 运营监控
|
||||
└── analytics.scss
|
||||
|
||||
src/enterprise/
|
||||
├── orders/
|
||||
│ ├── invoice.tsx # 发票管理
|
||||
│ └── invoice.scss
|
||||
└── settings/
|
||||
├── sso.tsx # SSO 配置
|
||||
└── sso.scss
|
||||
```
|
||||
|
||||
### Phase 3 完成的功能
|
||||
| 功能 | 页面 | 状态 |
|
||||
|------|------|------|
|
||||
| **CI/CD 构建** | 构建列表/详情 | ✅ 完整 |
|
||||
| **CI/CD 部署** | 部署列表/详情/回滚 | ✅ 完整 |
|
||||
| **流水线配置** | 流水线设置 | ✅ 完整 |
|
||||
| **运营监控** | 数据看板/趋势 | ✅ 完整 |
|
||||
| **发票管理** | 发票列表/申请/抬头 | ✅ 完整 |
|
||||
| **SSO 单点登录** | SSO 配置/日志 | ✅ 完整 |
|
||||
|
||||
---
|
||||
|
||||
### 待开发 (Phase 4)
|
||||
| 功能 | 说明 |
|
||||
|------|------|
|
||||
| SDK 下载 | 各语言 SDK 下载页面 |
|
||||
| 工单系统 | 客服工单/技术支持 |
|
||||
| 账单导出 | 账单 Excel 导出 |
|
||||
| 数据导入 | 企业数据批量导入 |
|
||||
|
||||
---
|
||||
|
||||
## Phase 4 开发完成 ✅ (2026-04-12 20:25)
|
||||
|
||||
### 新增功能模块
|
||||
|
||||
#### 1. SDK 下载中心
|
||||
- **SDK 分类浏览**:Web/移动/服务端/小程序
|
||||
- **SDK 列表展示**:图标/版本/下载量/Star
|
||||
- **搜索功能**:按名称/描述搜索
|
||||
- **SDK 详情弹窗**:版本/依赖/更新日志
|
||||
- **一键安装**:NPM 命令复制/直接下载
|
||||
|
||||
#### 2. 工单系统
|
||||
- **工单列表**:状态/类型/优先级筛选
|
||||
- **工单统计**:总数/待处理/处理中/已解决
|
||||
- **创建工单**:类型/优先级/标题/内容
|
||||
- **工单详情**:沟通记录/解决方案/回复
|
||||
- **工单评价**:满意度评分/反馈
|
||||
|
||||
#### 3. FAQ 常见问题
|
||||
- **分类浏览**:API/账单/账户/SDK 等分类
|
||||
- **搜索功能**:关键词搜索
|
||||
- **展开查看**:问题详情/回答
|
||||
- **有帮助/没帮助**:用户反馈
|
||||
|
||||
#### 4. 数据导入
|
||||
- **导入记录列表**:状态/进度/错误统计
|
||||
- **模板下载**:各类数据导入模板
|
||||
- **字段说明**:必填/类型/示例
|
||||
- **错误详情**:行号/字段/错误原因/建议
|
||||
|
||||
#### 5. 账单导出
|
||||
- **导出记录**:任务列表/状态/文件信息
|
||||
- **新建导出**:类型/格式/时间范围
|
||||
- **下载管理**:文件下载/过期提醒
|
||||
- **格式支持**:Excel/CSV/PDF
|
||||
|
||||
### 新增类型定义
|
||||
```
|
||||
src/types/
|
||||
├── sdk.ts # SDK 下载类型
|
||||
├── ticket.ts # 工单系统类型
|
||||
└── import.ts # 导入导出类型
|
||||
```
|
||||
|
||||
### 新增 API
|
||||
```
|
||||
src/api/
|
||||
├── sdk.ts # SDK 下载 API
|
||||
├── ticket.ts # 工单系统 API
|
||||
└── import.ts # 导入导出 API
|
||||
```
|
||||
|
||||
### 新增页面
|
||||
```
|
||||
src/developer/
|
||||
├── sdk/
|
||||
│ └── index.tsx # SDK 下载中心
|
||||
├── ticket/
|
||||
│ ├── index.tsx # 工单中心
|
||||
│ ├── detail.tsx # 工单详情
|
||||
│ └── faq.tsx # FAQ 常见问题
|
||||
|
||||
src/enterprise/
|
||||
├── settings/
|
||||
│ └── import.tsx # 数据导入
|
||||
└── orders/
|
||||
└── bills.tsx # 账单导出
|
||||
```
|
||||
|
||||
### Phase 4 完成的功能
|
||||
| 功能 | 页面 | 状态 |
|
||||
|------|------|------|
|
||||
| **SDK 下载中心** | 列表/详情/下载 | ✅ 完整 |
|
||||
| **工单系统** | 列表/创建/详情/评价 | ✅ 完整 |
|
||||
| **FAQ 常见问题** | 分类/搜索/反馈 | ✅ 完整 |
|
||||
| **数据导入** | 导入记录/模板下载 | ✅ 完整 |
|
||||
| **账单导出** | 导出记录/新建导出 | ✅ 完整 |
|
||||
|
||||
---
|
||||
|
||||
## 📊 四个 Phase 累计完成
|
||||
|
||||
| Phase | 功能数 | 页面数 | API 数 |
|
||||
|-------|--------|--------|--------|
|
||||
| Phase 1 | 基础框架 | 15+ | 10+ |
|
||||
| Phase 2 | API Key/成员/通知等 | 20+ | 15+ |
|
||||
| Phase 3 | CI-CD/监控/发票/SSO | 10+ | 20+ |
|
||||
| Phase 4 | SDK/工单/导入/导出 | 10+ | 15+ |
|
||||
| **合计** | - | **55+** | **60+** |
|
||||
|
||||
---
|
||||
|
||||
## Bug 修复 (2026-04-12 21:00)
|
||||
|
||||
### 已修复的问题
|
||||
|
||||
#### 1. JSX 语法错误
|
||||
- 修复 8 个被截断的 `.tsx` 文件(appCredential、appEvent、appUser、appVersion)
|
||||
- 修复 `user/apps/index.tsx` 中的 `>` token 问题
|
||||
|
||||
#### 2. API 导入问题
|
||||
- 修复 `analytics.ts`、`cicd.ts`、`invoice.ts`、`sso.ts` 的 request 导入
|
||||
- 添加缺失的 `ImportType` 导入
|
||||
- 修复 `sdk.ts` 未使用的导入
|
||||
|
||||
#### 3. Config 文件错误
|
||||
- 修复所有 `defineComponentConfig` → `definePageConfig`
|
||||
- 涉及 12 个 config 文件
|
||||
|
||||
#### 4. 组件命名错误
|
||||
- 修复 `PullRefresh` → `PullToRefresh`(nutui 组件)
|
||||
- 涉及 4 个文件
|
||||
|
||||
#### 5. taro-ui 依赖问题
|
||||
- 将 `taro-ui` 替换为 `nutui-react-taro`
|
||||
- 重写 `invoice.tsx`、`sso.tsx` 页面
|
||||
|
||||
#### 6. SCSS 导入问题
|
||||
- 修复 `notification/index.tsx` 的 scss 导入路径
|
||||
|
||||
#### 7. 类型定义
|
||||
- 创建 `src/types/developer.ts` 缺失类型文件
|
||||
- 修复 `RequestConfig` 缺少 `params` 属性
|
||||
|
||||
### 项目状态
|
||||
- ✅ 编译成功
|
||||
- ✅ 开发服务器运行中 (端口: dist)
|
||||
|
||||
---
|
||||
|
||||
## 任务:改造 invite/index.tsx 登录流程
|
||||
|
||||
### 需求
|
||||
- `loginByOpenId` 已注册 → 显示**「确认加入」**按钮
|
||||
- `loginByOpenId` 未注册 → 显示**「微信手机号快速加入」**按钮(走授权流程)
|
||||
|
||||
### 关键逻辑
|
||||
1. `checkLoginStatus`:调用 `loginByOpenId` 检查用户是否已注册
|
||||
2. 已注册:`isLoggedIn = true`,显示「确认加入」按钮
|
||||
3. 未注册:`isLoggedIn = false`,显示「微信手机号授权」按钮
|
||||
4. 授权成功 → 调用 `loginByMpWxPhone` 完成注册/登录 → 自动执行加入应用
|
||||
|
||||
### 文件修改
|
||||
- `src/passport/invite/index.tsx` - 完整重写,区分已登录/未注册两种按钮状态
|
||||
|
||||
---
|
||||
|
||||
## 任务:未注册用户在邀请页内完成授权注册,不跳登录页
|
||||
|
||||
### 需求
|
||||
- loginByOpenId 未注册 → 在页面内显示「微信手机号授权」按钮
|
||||
- 授权成功 → 调用 `loginByMpWxPhone` 注册/登录 → 自动执行加入应用
|
||||
- 不再跳转 passport/login 页面
|
||||
|
||||
### 关键逻辑
|
||||
1. `checkLoginStatus`:已注册 isLoggedIn=true,未注册 isLoggedIn=false,**两种情况都显示邀请页**
|
||||
2. 未注册按钮:`open-type="getPhoneNumber"` → `handleGetPhoneNumber`
|
||||
- 授权码调 `SERVER_API_URL/wx-login/loginByMpWxPhone` 完成注册登录
|
||||
- 保存 token → isLoggedIn=true → 立即调 `doJoinApp`
|
||||
3. 已注册按钮:普通 `onClick` → `handleConfirmJoin` → `doJoinApp(access_token)`
|
||||
4. `doJoinApp`:统一加入接口,请求头带 `Authorization: Bearer {access_token}`
|
||||
|
||||
### 文件修改
|
||||
- `src/passport/invite/index.tsx` - 完整重写(彻底移除跳登录页逻辑)
|
||||
|
||||
---
|
||||
|
||||
## 修复:「授权码不能为空」报错
|
||||
|
||||
### 问题
|
||||
后端 `/api/_app/developer/invite/accept` 接口强制要求传 `code`(微信授权码),不传就报「授权码不能为空」。
|
||||
|
||||
### 解决
|
||||
统一用一个 `getPhoneNumber` 按钮处理两种场景:
|
||||
- **已注册**:文字「确认加入」→ 触发 getPhoneNumber → `doJoinApp(code, accessToken)`
|
||||
- **未注册**:文字「微信手机号快速加入」→ 触发 getPhoneNumber → 先 `loginByMpWxPhone` 注册 → 再 `wx.login()` 获 code → `doJoinApp(newCode, access_token)`
|
||||
|
||||
### doJoinApp 参数
|
||||
```ts
|
||||
doJoinApp(wxCode: string, accessToken: string)
|
||||
// 请求体带 code,请求头带 Authorization: Bearer xxx
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 优化:已登录用户不弹手机号授权
|
||||
|
||||
### 改动
|
||||
- 已登录按钮:普通 `onClick`,文字「确认加入」
|
||||
- 未注册按钮:`getPhoneNumber` 授权,文字「微信手机号快速加入」
|
||||
|
||||
### 逻辑差异
|
||||
| 用户状态 | 按钮类型 | 获取 code 方式 |
|
||||
|------|------|------|
|
||||
| 已登录 | 普通 onClick | `wx.login()` |
|
||||
| 未注册 | getPhoneNumber | 授权回调的 `code` |
|
||||
|
||||
### 文件修改
|
||||
- `src/passport/invite/index.tsx` - 按钮区分两种类型,已登录用普通 onClick
|
||||
|
||||
---
|
||||
|
||||
## 优化:已登录用户不强制勾选协议
|
||||
|
||||
### 改动
|
||||
- 已登录用户点击「确认加入」时,不再检查 `agreementChecked`
|
||||
- 未注册用户仍需勾选协议后才能授权手机号
|
||||
|
||||
### 文件修改
|
||||
- `src/passport/invite/index.tsx` - `handleConfirmJoin` 移除协议检查
|
||||
|
||||
---
|
||||
|
||||
## 修复:后端需要手机号授权码
|
||||
|
||||
### 问题
|
||||
后端 `invite/accept` 接口只接受 `getPhoneNumber` 获取的手机号授权码,用 `wx.login()` 的 code 会报「获取手机号失败」。
|
||||
|
||||
### 解决
|
||||
两种用户状态都走 `getPhoneNumber` 按钮:
|
||||
- 已登录:文字「确认加入」,回调 `handleConfirmJoinGetPhoneNumber`
|
||||
- 未注册:文字「微信手机号快速加入」,回调 `handleGetPhoneNumber`
|
||||
|
||||
### 差异
|
||||
| 用户状态 | 回调 | 行为 |
|
||||
|------|------|------|
|
||||
| 已登录 | `handleConfirmJoinGetPhoneNumber` | 直接用 `code + access_token` 调加入接口 |
|
||||
| 未注册 | `handleGetPhoneNumber` | 先 `loginByMpWxPhone` 注册登录,再调加入接口 |
|
||||
|
||||
### 文件修改
|
||||
- `src/passport/invite/index.tsx` - 两种状态都用 getPhoneNumber 按钮
|
||||
|
||||
---
|
||||
|
||||
## 任务:后端改造支持已登录用户直接加入
|
||||
|
||||
### 问题
|
||||
后端 `/api/_app/developer/invite/accept` 接口强制要求传 `code`(手机号授权码),导致已登录用户也需要弹手机号授权。
|
||||
|
||||
### 后端改造方案
|
||||
修改 `AppMpInviteController.acceptInvite` 方法:
|
||||
|
||||
#### 1. 参数校验调整
|
||||
- `code` 改为可选参数
|
||||
- 不传 `code` 时,从 `Authorization` 头获取当前登录用户
|
||||
|
||||
#### 2. 双模式支持
|
||||
```java
|
||||
if (StrUtil.isBlank(code)) {
|
||||
// 模式一:已登录用户(通过 Authorization 头识别)
|
||||
userId = getCurrentUserId();
|
||||
} else {
|
||||
// 模式二:未注册用户(通过手机号授权码获取手机号,创建用户)
|
||||
String phone = getPhoneByCode(code);
|
||||
userId = getOrCreateUserByPhone(phone);
|
||||
}
|
||||
```
|
||||
|
||||
#### 3. getCurrentUserId 方法
|
||||
- 尝试从 Spring Security Context 获取
|
||||
- 如果获取不到(免登录接口),手动解析 `Authorization` 头的 JWT Token
|
||||
|
||||
### 前端配合改造
|
||||
- 已登录用户:普通 `onClick` 按钮 → `handleConfirmJoin` → `doJoinAppForLoggedInUser`(不传 `code`)
|
||||
- 未注册用户:`getPhoneNumber` 按钮 → `handleGetPhoneNumber` → `doJoinAppForNewUser`(传 `code`)
|
||||
|
||||
### 文件修改
|
||||
**后端:**
|
||||
- `/Users/gxwebsoft/JAVA/websopy-java/src/main/java/com/gxwebsoft/app/controller/AppMpInviteController.java`
|
||||
- `acceptInvite` 方法支持 `code` 可选
|
||||
- 使用 `BaseController.getLoginUserId()` 获取当前登录用户(无需额外方法)
|
||||
|
||||
**前端:**
|
||||
- `/Users/gxwebsoft/VUE/websopy-mp/src/passport/invite/index.tsx`
|
||||
- 已登录按钮改为普通 `onClick`
|
||||
- 新增 `handleConfirmJoin` 方法
|
||||
- 拆分 `doJoinApp` 为 `doJoinAppForLoggedInUser` 和 `doJoinAppForNewUser`
|
||||
|
||||
---
|
||||
|
||||
## 修复:开发者中心加载不到应用
|
||||
|
||||
### 问题
|
||||
用户通过邀请加入应用后,开发者中心显示「加载中...」,无法显示应用列表。
|
||||
|
||||
### 原因
|
||||
- 前端 `developer/index.tsx` 只调用了 `pageMyApp` 接口(查询用户**创建**的应用)
|
||||
- 用户通过邀请加入的应用属于**参与**的应用,不是创建的应用
|
||||
- 后端 `loginByOpenId` 返回了应用列表,但前端没有使用这个数据
|
||||
|
||||
### 解决
|
||||
1. **前端改造**:`loadData` 同时调用两个接口:
|
||||
- `pageMyApp` - 查询创建的应用
|
||||
- `pageJoinedApp` - 查询参与的应用(新增 API)
|
||||
- 合并两个列表,根据 `productId` 去重
|
||||
|
||||
2. **新增 API**:`src/api/developer/developer.ts` 添加 `pageJoinedApp` 方法
|
||||
|
||||
### 文件修改
|
||||
- `src/developer/index.tsx` - `loadData` 同时查询创建和参与的应用
|
||||
- `src/api/developer/developer.ts` - 新增 `pageJoinedApp` 方法
|
||||
@@ -1,77 +0,0 @@
|
||||
# 2026-04-13 工作日志
|
||||
|
||||
## 完成的工作
|
||||
|
||||
### 1. 阿里云实人认证接入
|
||||
**目的**:为 user/userVerify 页面添加阿里云身份证二要素核验
|
||||
|
||||
**后端改动**(JAVA/websopy-java):
|
||||
- pom.xml:添加 `com.aliyun:cloudauth20190307:2.2.4` 依赖
|
||||
- application.yml:添加 `cloudauth` 配置项(accessKeyId、accessKeySecret、endpoint、regionId)
|
||||
- 新增 `CloudAuthProperties.java`:配置属性类
|
||||
- 新增 `IdVerificationService.java`:实人认证服务
|
||||
- 新增 `IdVerificationController.java`:实人认证 API 控制器
|
||||
|
||||
**前端改动**(VUE/websopy-mp):
|
||||
- api/system/userVerify/index.ts:添加 `verifyIdCard()` API 调用
|
||||
- user/userVerify/index.tsx:
|
||||
- 导入 verifyIdCard
|
||||
- 修改 submitSucceed 函数:个人认证时先调用实名校验,核验通过后再提交
|
||||
|
||||
### 2. 调研结论
|
||||
- **城市服务实名校验**:已于2021年11月停止开放,不可用
|
||||
- **阿里云实人认证**:推荐方案,0.2元/次,有100次免费试用额度
|
||||
- **接入方式**:身份证二要素核验(姓名+身份证号)最简单
|
||||
|
||||
## 待办事项
|
||||
- [x] 配置阿里云 AccessKey(在 application-prod.yml 中设置 cloudauth.accessKeyId 和 cloudauth.accessKeySecret)
|
||||
- [ ] 在阿里云实人认证控制台开通服务并充值
|
||||
- [ ] 测试验证接口是否正常工作
|
||||
|
||||
## 阿里云 AccessKey 配置
|
||||
- 项目:websopy-java
|
||||
- 文件:application-prod.yml
|
||||
- accessKeyId: LTAI4GKGZ9Z2Z8JZ77c3GNZP
|
||||
- 备注:与 OSS 使用同一个 AccessKey
|
||||
|
||||
## 3. 前端接口地址修正
|
||||
**目的**:修正小程序端开发者相关 API 接口地址,与后端 Controller 路径保持一致
|
||||
|
||||
**问题发现**:`BaseUrl` 配置已包含 `/api` 后缀 (`https://websopy-api.websoft.top/api`),前端代码中不应再重复添加 `/api` 前缀,否则会导致 `/api/api/` 路径错误。
|
||||
|
||||
**修正文件**:
|
||||
- `src/api/developer/enterprise.ts`:
|
||||
- 企业信息:`/system/tenant/info`, `/system/tenant`
|
||||
- 企业成员:`/system/user/page`, `/system/user`
|
||||
- 订单:`/system/order/page`, `/system/order`
|
||||
- 账单:`/sys/recharge-order/page`
|
||||
- 企业应用:`/app/product/page`
|
||||
- 邀请:`/app/developer/invite`
|
||||
|
||||
- `src/api/developer/developer.ts`:
|
||||
- 项目/应用:`/app/product/*` (create, update, delete, detail, page, my/page, joined/page)
|
||||
- API Key:`/app/app-credential/*`
|
||||
- 版本发布:`/app/app-version/*`
|
||||
- 开发者信息:`/app/developer/git-account`, `/app/developer/gitea-info`
|
||||
- 通知:`/app/notification/*`
|
||||
- 权限申请:`/app/developer/permission-requests/*`
|
||||
|
||||
- `src/api/invite/index.ts`:
|
||||
- 所有接口移除 `/api` 前缀
|
||||
|
||||
**后端 Controller 对应关系**:
|
||||
| 前端 API | 后端 Controller | 路径前缀 |
|
||||
|---------|----------------|---------|
|
||||
| enterprise.ts | TenantController | /api/system/tenant |
|
||||
| enterprise.ts | UserController | /api/system/user |
|
||||
| enterprise.ts | OrderController | /api/system/order |
|
||||
| enterprise.ts | RechargeOrderController | /api/sys/recharge-order |
|
||||
| developer.ts | AppProductController | /api/app/product |
|
||||
| developer.ts | AppCredentialController | /api/app/app-credential |
|
||||
| developer.ts | AppVersionController | /api/app/app-version |
|
||||
| developer.ts | GitAccountController | /api/app/developer |
|
||||
| developer.ts | AppNotificationController | /api/app/notification |
|
||||
| developer.ts | AppPermissionRequestController | /api/app/developer/permission-requests |
|
||||
| invite/index.ts | - | /api/invite/* |
|
||||
|
||||
**重要配置**:`config/env.ts` 中 `API_BASE_URL` 已包含 `/api` 后缀,前端代码路径不应再以 `/api` 开头。
|
||||
@@ -1,61 +0,0 @@
|
||||
|
||||
## 修复 WXSS 不兼容的 .\!visible CSS 选择器
|
||||
|
||||
- **问题**: Tailwind JIT 引擎把 JS 代码 `if (!visible)` 误识别为 `!visible` important 修饰符类名,生成 `.\!visible` CSS 选择器,WXSS 不支持反斜杠转义的 `!`
|
||||
- **修复**: 创建 PostCSS 插件 `postcss-remove-tailwind-important.js` 移除所有 `.\!xxx` 选择器
|
||||
- **配置**: 更新 `postcss.config.js` 引入该插件(使用函数引用方式,非字符串模块名)
|
||||
- **验证**: 构建通过,`.\!visible` 从输出中移除,`.visible` 保留
|
||||
- **注意**: 项目中实际无人使用 Tailwind 的 `!` 修饰符类名,安全移除。将来若需使用,需创建对应 WXSS 兼容类名
|
||||
|
||||
## 替换项目中不兼容 WXSS 的 space-x/y-* 类名
|
||||
|
||||
- **问题**: `space-y-*` / `space-x-*` 依赖 `:not()` + `~` + CSS 变量 + 嵌套 calc(),WXSS 不兼容
|
||||
- **修复**:
|
||||
- 源码中将所有 `space-y-*` 替换为 `flex flex-col gap-*`
|
||||
- 注释中的 `space-y-*` 也一并替换,防止 Tailwind JIT 误扫描生成
|
||||
- 扩展 PostCSS 插件为 `postcss-remove-wxss-incompatible.js`,一并移除 `space-*` 和 `divide-*` 规则
|
||||
- 删除旧的 `postcss-remove-tailwind-important.js` 文件
|
||||
- **涉及文件**: dealer/qrcode/index.tsx, dealer/invite-stats/index.tsx, components/QRScanModal.tsx
|
||||
- **验证**: 构建通过,所有不兼容选择器已从输出中移除
|
||||
|
||||
## 审查并修复剪切板接口合规问题
|
||||
|
||||
- **问题**: 用户截图显示「复制成功」与系统「内容已复制」重复 Toast,担心违反 5.15.4 剪切板接口滥用规范
|
||||
- **审查结果**:
|
||||
- 所有复制操作均为用户点击触发,无 `getClipboardData` 自动读取
|
||||
- 无复制后强制跳转或中断业务流程
|
||||
- 当前实现基本合规,主要问题是重复 Toast 影响体验
|
||||
- **修复**: 更新 `src/utils/common.ts` 中的 `copyText` 函数,小程序端不再显示自定义「复制成功」Toast,由系统自带提示替代
|
||||
- **验证**: weapp 构建通过
|
||||
|
||||
## 修复首页「复制失败」Toast 问题
|
||||
|
||||
- **问题**: 微信审核反馈小程序打开首页提示「复制失败」,截图显示该 Toast 来自产品矩阵页面
|
||||
- **分析**:
|
||||
- 首页无生命周期自动复制,复制均由用户点击触发
|
||||
- 「立即开通」按钮点击后通过 `copyText` 复制外链,`copyText` 的 `fail` 回调会弹「复制失败」Toast
|
||||
- 部分机型/场景下 `Taro.setClipboardData` 可能误报失败,导致审核不通过
|
||||
- **修复**:
|
||||
- 更新 `src/utils/common.ts` 的 `copyText`:小程序端直接调用原生 `wx.setClipboardData`,失败时只 `console.error` 不弹 Toast
|
||||
- 优化 `src/pages/index/index.tsx` 的 `openMaybeLink`:相对路径改为 `Taro.navigateTo` 跳转,仅外链复制
|
||||
- 「了解模板/插件市场」按钮不再调用 `openMaybeLink('/market')`,仅保留提示 Toast
|
||||
- **验证**: weapp 构建通过,dist 中不再包含「复制失败」字符串
|
||||
|
||||
## 修复 wx 全局变量 TypeScript 红色警告
|
||||
|
||||
- **问题**: `src/utils/common.ts` 中使用 `wx.setClipboardData` 时,TypeScript 报「Cannot find name 'wx'」红色警告
|
||||
- **原因**: 项目没有声明微信小程序原生全局对象 `wx` 的类型
|
||||
- **修复**: 在 `types/global.d.ts` 中添加 `declare const wx: any;` 全局类型声明
|
||||
- **验证**: `tsc` 不再报 `common.ts` 相关错误,`npx taro build --type weapp` 构建成功
|
||||
|
||||
## 去掉首页「复制咨询模板」按钮及复制逻辑
|
||||
|
||||
- **问题**: `wx.setClipboardData` 在小程序开发者工具中报 `no permission` 错误,且审核对复制功能敏感
|
||||
- **修复**:
|
||||
- 移除 `src/pages/index/index.tsx` 中的 `copyConsultTemplate` 函数
|
||||
- 删除「复制咨询模板」按钮,只保留「电话咨询」
|
||||
- 修改联系我们区域文案,去掉「复制咨询模板」相关描述
|
||||
- `openMaybeLink` 中外链不再调用 `copyText`,改为 `Taro.showModal` 提示用户手动复制到浏览器打开
|
||||
- 移除首页对 `copyText` 的 import
|
||||
- **验证**: `npx taro build --type weapp` 构建成功,首页无复制相关调用
|
||||
- **备注**: 项目其他页面仍有复制功能(如礼品卡分享、优惠券分享、订单号复制、密钥复制等),如需要可继续逐个移除
|
||||
@@ -1,15 +0,0 @@
|
||||
# 2026-07-01 工作日志
|
||||
|
||||
## 修复支付页面 request URL 无效问题
|
||||
- **文件**: `src/utils/request.ts`
|
||||
- **问题**: 扫码进入支付页后报错 `request:fail invalid url "/app/subscription/detail-by-no/..."`,原因是核心 `request()` 函数没有调用 `buildUrl()` 拼接 baseUrl,相对路径直接传给微信 `wx.request()` 导致失败
|
||||
- **修复**: 在 `request()` 函数中统一加 `buildUrl(options.url)` 处理,确保所有请求方式都能正确拼接完整 URL
|
||||
|
||||
## 小程序码改为体验版
|
||||
- **文件**: `src/api/invite/index.ts`(前端)
|
||||
- **修改**: `generateMiniProgramCode` 函数将 `envVersion` 作为查询参数传给后端,默认值 `'trial'`
|
||||
- **后端配合**:
|
||||
- `WxLoginController.java`: `getOrderQRCodeUnlimited` 和 `getOrderQRCode` 方法新增 `@RequestParam(defaultValue = "release") String envVersion` 参数
|
||||
- 硬编码的 `"release"` 改为动态读取 `envVersion` 参数
|
||||
- `getQRCodeText` 已有此参数,无需修改
|
||||
- **注意**: 提审上线前需将前端默认 `envVersion` 改回 `'release'`
|
||||
@@ -1,101 +0,0 @@
|
||||
# 2026-07-02 工作日志
|
||||
|
||||
## 小程序扫码支付"获取订单失败"问题排查与修复
|
||||
|
||||
- **问题**:用户扫小程序码后进入小程序支付页,提示"获取订单失败"
|
||||
- **根因**:后端 Spring Security 的 SecurityConfig 中未将支付相关接口加入白名单(permitAll),扫码进入时用户未登录态,请求 `/api/app/subscription/detail-by-no/**` 被 401 拦截
|
||||
- **修复**:在 SecurityConfig.java 的 antMatchers 中添加了以下接口白名单:
|
||||
- `/api/app/subscription/detail-by-no/**` — 根据订阅号查询订单详情
|
||||
- `/api/app/subscription/mp-prepay/**` — 创建小程序预支付订单
|
||||
- `/api/app/subscription/mp-confirm/**` — 支付成功确认
|
||||
- `/api/app/subscription/wx-notify/**` — 微信支付回调通知
|
||||
- `/api/wx-login/**` — 小程序码生成等微信登录相关接口(之前只覆盖了 `/api/shop/wx-login/**`,漏掉了 `/api/wx-login/**`)
|
||||
- **前端代码位置**:`src/passport/pay/index.tsx`,支付页面从 scene/subscriptionNo 获取订单号,调 detail-by-no 接口
|
||||
- **后端代码位置**:`AppSubscriptionController.java`(@RequestMapping("/api/app/subscription")),`WxLoginController.java`(@RequestMapping("/api/wx-login"))
|
||||
- **注意**:生成小程序码时 `envVersion` 默认为 `trial`(体验版),上线前需改为 `release`
|
||||
|
||||
### 后续排查:后端白名单加了仍报"获取订单失败"
|
||||
|
||||
- **二次根因**:前端 `src/passport/pay/index.tsx` 的 `fetchDetail` 存在逻辑 bug
|
||||
- `request()` 默认 `returnRaw=false`,响应拦截器对 `code===0` 的响应已自动拆包,只返回 `data` 部分(订阅对象)
|
||||
- 但 `fetchDetail` 又检查 `res?.code === 200 || res?.code === 0`,而拆包后的 `res`(订阅对象)没有 `code` 字段
|
||||
- 因此条件永远为 `false`,走进 `setErrorMsg` 分支,显示"获取订单失败"
|
||||
- **修复**:移除多余的 `code` 检查,`request` 成功即代表业务成功,直接使用返回的 `data` 对象;错误由 catch 处理
|
||||
- **附加**:`getSubscriptionNo` 加了 `console.log` 打印页面参数和解析结果,方便真机调试排查 scene 传入问题
|
||||
|
||||
### 五次排查:getOpenId 返回 success:true 但 openid 为 undefined
|
||||
|
||||
- **根因**:前端 `src/api/passport/wx-login/index.ts` 的 `getOpenId` 函数错误地假设后端返回的是 `{ openid, unionid, session_key }` 对象,直接取 `res.data.openid`;但后端 `/wx-login/getOpenId` 实际返回的是 `LoginResult` 结构 `{ access_token, user }`,真正的 openid 在 `user.openid` 里
|
||||
- **修复**:更新 `getOpenId` 类型为 `ApiResult<WxLoginResult>`,从 `res.data.user?.openid` 提取 openid,同时把 `access_token` 和 `user` 一起返回给调用方复用
|
||||
- **后续影响**:`handlePay` 中拿到 openid 后,因为 `getOpenId` 已经自动注册/登录(后端会注册新用户并签发 token),理论上用户已经自动登录,可能无需再调 `loginByOpenId`;但当前代码保留双保险
|
||||
|
||||
- **根因**:`handlePay` 流程有 bug——调了 `Taro.login()` 获取 code,但没有用 code 去换 openid,直接从 `Taro.getStorageSync('openid')` 读取(扫码用户可能没登录过,storage 里没存)
|
||||
- **前端修复**(`src/passport/pay/index.tsx`):
|
||||
1. 引入 `getOpenId` 和 `loginByOpenId` API
|
||||
2. `handlePay` 中:`Taro.login()` → `getOpenId(code)` 换取 openid → 缓存 openid
|
||||
3. 尝试 `loginByOpenId` 自动登录获取 token(如果用户未注册,弹出引导登录对话框)
|
||||
4. `mp-prepay` 请求也加了 `returnRaw: true` 和调试日志
|
||||
- **后端修复**(`AppSubscriptionController.java`):
|
||||
1. `mp-prepay`:将 `userId == null || !userId.equals(sub.getUserId())` 改为 `userId != null && !userId.equals(sub.getUserId())`,允许未登录用户发起支付
|
||||
2. `mp-confirm`:同理,未登录时跳过 userId 校验,用 `effectiveUserId = userId != null ? userId : sub.getUserId()` 替代原 `userId`
|
||||
|
||||
- **修改点1**:`src/passport/pay/index.tsx` 的 `fetchDetail` 改为 `returnRaw: true`,拿到接口完整响应(含 code/message/data),手动判断业务状态码(`code === 0 || code === 200`),不再依赖拦截器拆包
|
||||
- **修改点2**:`fetchDetail` 和 `getSubscriptionNo` 加了详细 console.log/console.error,打印:
|
||||
- 原始页面参数(scene、subscriptionNo)
|
||||
- 请求 URL 和当前 token
|
||||
- 接口完整返回 JSON
|
||||
- catch 中的 err.name/type/code/message/data
|
||||
- **修改点3**:`src/utils/request.ts` 响应拦截器去掉了 `process.env.NODE_ENV === 'development'` 条件限制,所有环境都打印日志,方便真机调试 Console 查看
|
||||
### 六次排查:getOpenId 返回 success:true 但 openid 为 null
|
||||
|
||||
- **现象**:前端 `getOpenId` 返回 `success: true, access_token: 有值, user.userId: 35619`,但 `openid: null, unionid: null`
|
||||
- **根因**:后端 `/wx-login/getOpenId` 虽然通过微信 `jscode2session` 换到了 openid,但**没有把 openid 设置到 `UserParam` 中**。
|
||||
- 后续 `userService.getByOauthId(userParam)` 查找用户时,userParam.openid 为空,可能无法命中已有用户;
|
||||
- 新用户注册时走 `addUser(userParam)`,而 `addUser` 只在 `userParam.openid` 非空时才会写 `User.openid`,所以新用户 openid 也为空;
|
||||
- 对于已存在但 openid 为空的老用户(如手机号注册),直接返回,openid 仍然是 null。
|
||||
- **修复**(`WxLoginController.java` 的 `/getOpenId` 方法):
|
||||
1. 从微信返回中拿到 `openid`/`unionid` 后,立即 `userParam.setOpenid(openid)` 和 `userParam.setUnionid(unionid)`;
|
||||
2. 用户已存在但 `user.openid` 为空时,把 openid/unionid 更新到数据库并返回最新 user;
|
||||
3. 这样 `LoginResult.user.openid` 一定有值,前端就能正确获取并传给 `mp-prepay`。
|
||||
### 七次排查:mp-prepay 报 SIGN_ERROR(签名错误)
|
||||
|
||||
- **现象**:前端调用 `/api/app/subscription/mp-prepay/{id}` 时,后端返回 `code: 1, message: "微信支付服务异常: 微信错误码: SIGN_ERROR, 签名错误"`。
|
||||
- **排查**:
|
||||
1. 当前小程序 JSAPI 支付使用 `WxNativePayUtil.getConfig` 构建微信支付 Config,模式为 `RSAPublicKeyConfig`(公钥模式),但生产环境 `wechatpay-public-key-id` 为空。
|
||||
2. 读取项目下所有证书文件,用 openssl 检查各证书对应的商户号,发现:
|
||||
- `/wechat/websopy/apiclient_cert.pem`:商户号 `1557418831`(与小程序无关联)
|
||||
- `/wechat/10398/apiclient_cert.pem`:商户号 `1246610101`(与小程序已关联,见截图)
|
||||
3. 配置中的 `mch-id` 是 `1246610101`,但配置的 `private-key-relative-path` 指向 `wechat/websopy/`,导致证书与商户号不匹配,微信返回 `SIGN_ERROR`。
|
||||
4. 检查 `10398` 目录证书与私钥匹配性:公钥哈希一致,证书序列号为 `48749613B40AA8F1D768583FC352358E13EB5AF0`。
|
||||
- **修复**:
|
||||
1. 修改 `WxNativePayUtil.java`:将 `RSAPublicKeyConfig`(公钥模式)改为 `RSAAutoCertificateConfig`(自动证书模式),不再依赖 `wechatpay-public-key-id` 和 `wechatpay-cert-relative-path`。
|
||||
2. 修正 `application-dev.yml` 和 `application-prod.yml`:
|
||||
- `mch-id`:`1246610101`(保持不变,这是正确的)
|
||||
- `merchant-serial-number`:`48749613B40AA8F1D768583FC352358E13EB5AF0`(对应 10398 目录证书)
|
||||
- `private-key-relative-path`:`wechat/10398/apiclient_key.pem`
|
||||
- `wechatpay-cert-relative-path` 置空(自动证书模式)
|
||||
- **待确认**:当前保留的 `api-v3-key: "zGufUcqa7ovgxRL0kF5OlPr482EZwtn9"` 是否属于商户号 `1246610101`,需要用户在微信支付商户后台确认。如果仍报签名错误,需要替换为正确的 APIv3 密钥。
|
||||
- **待处理**:生产环境需将 `/Users/gxwebsoft/JAVA/websopy-java/src/main/resources/wechat/10398/apiclient_key.pem` 上传到 `/www/wwwroot/file.ws/wechat/10398/apiclient_key.pem`,并确保该文件可被 Java 进程读取。之前错误上传 `wechat/websopy/` 目录的证书给 1246610101 使用,这是 `SIGN_ERROR` 的真正原因。
|
||||
|
||||
### 八次排查:SUB202607022056385942 提示"订阅不存在"
|
||||
|
||||
- **现象**:调用 `/api/_app/subscription/check-status/SUB202607022056385942` 和 `/api/_app/subscription/detail-by-no/SUB202607022056385942` 均返回"订阅不存在"
|
||||
- **根因**:`getOrderQRCodeUnlimited` 接口(WxLoginController.java 第 441 行)**只生成小程序码,并不创建订阅记录**。如果生成小程序码时没有先调用 `subscribe` 接口,`subscriptionNo` 就只是个随机字符串,没有对应的数据库记录
|
||||
- **修复(方案 1)**:新增 `POST /api/app/subscription/generate-pay-qrcode` 接口,在一个请求中完成:
|
||||
1. 创建订阅记录(复用 `subscribe` 的核心逻辑,写入数据库)
|
||||
2. 用 `subscriptionNo` 调用 `WxMiniprogramUtil.generateMiniprogramQrCode` 生成小程序码
|
||||
3. 返回 `subscriptionNo` + Base64 图片
|
||||
- **关联修改**:
|
||||
- `AppPayProperties.java`:新增 `miniAppSecret` 字段(用于生成小程序码获取 access_token)
|
||||
- `application-dev.yml` / `application-prod.yml`:添加 `mini-app-secret` 配置项(当前为占位符,需用户填写真实秘钥)
|
||||
- `AppSubscriptionController.java` `mp-prepay`:已改为允许跨用户支付(`userId != null && !userId.equals(sub.getUserId())`)
|
||||
- `AppSubscriptionController.java` `mp-confirm`:已改为未登录时跳过 userId 校验
|
||||
- **下一步**:
|
||||
1. 到微信公众平台获取小程序 `AppSecret`,填写到 `application-dev.yml` 和 `application-prod.yml` 的 `mini-app-secret` 字段
|
||||
2. 重新构建部署后端
|
||||
3. 调用新接口生成支付小程序码:`POST /api/app/subscription/generate-pay-qrcode` Body: `{"productId": 1, "subscriptionPeriod": "month", "envVersion": "trial"}`
|
||||
4. 用返回的 `subscriptionNo` 测试 `check-status` 和 `detail-by-no` 接口,确认订阅记录已存在
|
||||
|
||||
### 补充修复
|
||||
|
||||
- `AppSubscriptionController.java` 的 `generate-pay-qrcode` 接口中,`switch (subscriptionPeriod)` 的 `case "month":` 空 fall through 触发 IDE 红色警告。已添加 `// fall through to default` 注释消除警告。
|
||||
@@ -0,0 +1,29 @@
|
||||
# 2026-07-16 工作日志
|
||||
|
||||
## passport 分包化修复
|
||||
|
||||
用户把登录/注册相关页面放入 `passport` 分包(`src/app.config.ts` 的 subpackages)。
|
||||
|
||||
排查结论:
|
||||
- 主包内所有跳转引用(`src/pages/**`、`src/utils/auth.ts`、`src/components/QRLoginScanner.tsx` 等)已全部使用 `/passport/xxx` 路径。分包化前后 URL 路径不变,引用代码无需修改。
|
||||
- passport 内 10 个页面(login/forget/setting/agreement/sms-login/register/qr-login/qr-confirm/unified-qr/pay)均有 `.tsx` + `.config.ts`,`@` 别名指向 `src/`,移动目录不影响 import。
|
||||
|
||||
修复 `src/app.config.ts` 分包声明(与实际文件对齐):
|
||||
- 移除 `phone-auth/index`、`invite/index`、`webview/index`(文件不存在,且全局无任何跳转引用它们,会导致 Taro 编译报错)
|
||||
- 补充 `register`(`src/passport/register.tsx` 存在但未声明)
|
||||
- 统一引号风格、清理尾随逗号
|
||||
|
||||
## passport/pay 功能从 websopy-taro-bak 迁移
|
||||
|
||||
问题:`src/passport/pay/index.tsx` 失效,原因是当前项目基于 xinlong-shop 框架,`API_BASE_URL` 指向商城接口 `https://shop-api.websoft.top/api`,而 pay 页面需要访问 websopy 业务接口 `https://websopy-api.websoft.top/api` 的 `/app/subscription/*`。
|
||||
|
||||
迁移与修复:
|
||||
- 确认 `src/passport/pay/` 与 `src/api/passport/wx-login/` 文件和备份项目一致,无需替换。
|
||||
- 在 `config/env.js` 新增 `WEBSOPY_API_BASE_URL`。
|
||||
- 在 `config/app.ts` 导出 `WebsopyBaseUrl`。
|
||||
- 创建 `config/env.d.ts` 消除 `config/app.ts` 导入 `.js` 的隐式 any 类型错误。
|
||||
- 增强 `src/utils/request.ts`:在 `RequestConfig` 增加 `baseUrl?: string`,`request`/`get`/`post` 等函数支持自定义 baseUrl,相对路径会自动拼接;完整 URL 保持不变。
|
||||
- 修改 `src/passport/pay/index.tsx`:三个订阅接口请求均传入 `baseUrl: WebsopyBaseUrl`;修复 `tenantId` 类型(string -> number)和 `saveStorageByLoginUser` 的 user 类型断言。
|
||||
- 运行 `npx taro build --type weapp` 构建成功;dist 中 `passport/pay` 文件生成,确认请求使用 websopy API。
|
||||
- 说明:`npm run type-check` 仍因 `@tarojs/taro` 类型包解析问题失败,这是项目既有环境问题,不影响构建。
|
||||
|
||||
@@ -1,52 +0,0 @@
|
||||
# websopy-taro 项目记忆
|
||||
|
||||
## Tailwind CSS 在小程序中的兼容性规则
|
||||
|
||||
以下 Tailwind 类名在 WXSS 中不兼容,项目内禁止使用,已通过 PostCSS 插件 `postcss-remove-wxss-incompatible.js` 自动移除对应的 CSS 规则:
|
||||
|
||||
1. **`!xxx` important 修饰符**(如 `!visible`):生成 `.!visible` 选择器,WXSS 不支持反斜杠转义的 `!`。
|
||||
|
||||
2. **带 `0.5` 的类名**(如 `w-0.5`、`h-0.5`、`gap-0.5`):类名中的 `.` 被转义为 `\.`,生成 `.w-0\.5` 这样的选择器,WXSS 不兼容。应改用等效的整数尺寸或自定义值替代。
|
||||
|
||||
3. **`space-x-*` / `space-y-*`**(如 `space-y-4`):依赖 `:not()` + `~` 兄弟组合器 + CSS 变量 + 嵌套 `calc()`,WXSS 全不支持。应改用 `flex flex-col gap-*` 实现等效间距。
|
||||
|
||||
4. **`divide-x-*` / `divide-y-*`**:同样依赖 `:not()` + `~`,WXSS 不兼容。
|
||||
|
||||
## 替代方案速查
|
||||
|
||||
| 禁用类名 | 替代写法 |
|
||||
|---------|---------|
|
||||
| `space-y-4` | `flex flex-col gap-4` |
|
||||
| `space-x-4` | `flex flex-row gap-4` |
|
||||
| `!visible` | 直接写 `style={{ visibility: 'visible' }}` 或自定义类 |
|
||||
| `w-0.5` | 用自定义值或 `w-1` 替代 |
|
||||
|
||||
## 微信小程序接口合规
|
||||
|
||||
### 剪切板接口(运营规范 5.15.4)
|
||||
|
||||
1. **只允许用户主动触发复制**:所有 `Taro.setClipboardData` 调用必须绑定在明确的用户点击事件上,禁止在无用户操作时自动读写剪切板。
|
||||
2. **禁止复制后强制中断流程**:复制成功后不得强制跳转、强制关注、强制添加客服等,不能要求用户通过其他方式才能完成当前业务流程。
|
||||
3. **避免重复/失败 Toast 干扰体验**:微信小程序调用 `setClipboardData` 成功后系统会自带「内容已复制」提示。为避免重复提示及误报失败,统一封装 `utils/common.ts` 中的 `copyText` 函数:
|
||||
- 小程序端(weapp)直接调用原生 `wx.setClipboardData`,不再额外显示自定义 `复制成功` / `复制失败` Toast
|
||||
- 失败时仅通过 `console.error` 输出错误日志,便于排查
|
||||
- 非小程序端保留原有成功/失败提示
|
||||
|
||||
### 项目内剪切板使用现状
|
||||
|
||||
已审查所有 `setClipboardData` 调用:
|
||||
- 均通过按钮点击触发(复制咨询模板、复制密钥、复制链接、复制兑换码等)
|
||||
- 无 `getClipboardData`(自动读取剪切板)调用
|
||||
- 无复制后强制跳转或中断业务流程的行为
|
||||
- 首页「立即开通」等外链按钮点击后复制链接;相对路径入口(如开发者中心)改为 `Taro.navigateTo` 跳转,不再复制
|
||||
|
||||
当前实现基本符合 5.15.4 规范。
|
||||
|
||||
## AppSubscription 状态字段语义(踩坑必读)
|
||||
|
||||
判断"是否已支付"必须用 `payStatus`(0=未支付 / 1=已支付)这个**支付状态**字段,**不能**用 `status`(订阅生命周期 active/pending/expired/cancelled)。
|
||||
|
||||
- `status`:订阅的生命周期状态,**不等于支付状态**。后端 `renewPay` 为了避免放弃支付导致服务中断,会故意保留原 `status='active'`、只把 `payStatus` 设为 0。
|
||||
- `payStatus`:本次订单的支付状态。支付成功(mp-confirm / handleBalancePay)后才置 1。
|
||||
|
||||
续费场景下这两个字段完全解耦:用 `status === 'active'` 判支付会**直接误判为已支付**,详见 `2026-07-16.md` 修复记录。
|
||||
@@ -1,59 +0,0 @@
|
||||
# 配置说明文档
|
||||
|
||||
## 环境配置
|
||||
|
||||
### 1. 复制环境变量文件
|
||||
```bash
|
||||
cp .env.example .env
|
||||
```
|
||||
|
||||
### 2. 修改配置文件
|
||||
|
||||
#### config/app.ts
|
||||
```typescript
|
||||
// 租户ID - 请根据实际情况修改
|
||||
export const TenantId = 'YOUR_TENANT_ID';
|
||||
// 接口地址 - 请根据实际情况修改
|
||||
export const BaseUrl = 'https://your-api-domain.com/api';
|
||||
```
|
||||
|
||||
#### src/utils/server.ts
|
||||
```typescript
|
||||
// 模版套餐ID - 请根据实际情况修改
|
||||
export const TEMPLATE_ID = 'YOUR_TEMPLATE_ID';
|
||||
// 服务接口 - 请根据实际情况修改
|
||||
export const SERVER_API_URL = 'https://your-server-domain.com/api';
|
||||
```
|
||||
|
||||
#### project.config.json
|
||||
```json
|
||||
{
|
||||
"appid": "your_wechat_appid"
|
||||
}
|
||||
```
|
||||
|
||||
### 3. 小程序配置
|
||||
|
||||
#### 微信小程序
|
||||
1. 在微信公众平台申请小程序
|
||||
2. 获取AppID并填入 `project.config.json`
|
||||
3. 配置服务器域名白名单
|
||||
|
||||
#### 支付宝小程序
|
||||
1. 在支付宝开放平台申请小程序
|
||||
2. 获取AppID并配置相应文件
|
||||
|
||||
#### 字节跳动小程序
|
||||
1. 在字节跳动开发者平台申请小程序
|
||||
2. 获取AppID并填入 `project.tt.json`
|
||||
|
||||
### 4. API配置
|
||||
|
||||
确保后端API服务正常运行,并配置正确的域名和端口。
|
||||
|
||||
### 5. 安全注意事项
|
||||
|
||||
- 不要将真实的AppID、API密钥等敏感信息提交到公开仓库
|
||||
- 使用环境变量管理敏感配置
|
||||
- 定期更新密码和密钥
|
||||
- 在生产环境中启用HTTPS
|
||||
@@ -1,201 +0,0 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
@@ -1,592 +1,263 @@
|
||||
<div align="center">
|
||||
<h1>🚀 WebSoft 小程序</h1>
|
||||
<p><strong>基于 Taro + React + TypeScript 的跨平台小程序应用</strong></p>
|
||||
# Paopao Taro - 项目框架
|
||||
|
||||
<p>
|
||||
<img src="https://img.shields.io/badge/Taro-4.0.8-blue" alt="Taro">
|
||||
<img src="https://img.shields.io/badge/React-18.3.1-61DAFB" alt="React">
|
||||
<img src="https://img.shields.io/badge/TypeScript-5.7.2-3178C6" alt="TypeScript">
|
||||
<img src="https://img.shields.io/badge/NutUI-3.0.16-orange" alt="NutUI">
|
||||
<img src="https://img.shields.io/badge/TailwindCSS-3.4.17-38B2AC" alt="TailwindCSS">
|
||||
<img src="https://img.shields.io/badge/License-MIT-blue" alt="License">
|
||||
</p>
|
||||
</div>
|
||||
基于 Taro 4.0.8 + React 18.3.1 + TypeScript 5.7.2 的跨端开发框架
|
||||
|
||||
## 📖 项目简介
|
||||
|
||||
WebSoft 小程序是一个基于 **Taro + React + TypeScript** 构建的现代化跨平台小程序应用,支持多端发布:
|
||||
|
||||
- **核心框架**:Taro 4.0.8 + React 18.3.1 + TypeScript 5.7.2
|
||||
- **UI组件库**:NutUI React Taro 2.7.4 + NutUI Icons
|
||||
- **样式方案**:Sass + TailwindCSS 3.4.17
|
||||
- **状态管理**:React Hooks + Context API
|
||||
- **工具库**:Day.js、Crypto-js、React Router DOM
|
||||
|
||||
## 🎯 支持平台
|
||||
|
||||
本项目基于 Taro 框架,支持一套代码多端运行:
|
||||
|
||||
- **微信小程序** (weapp)
|
||||
- **支付宝小程序** (alipay)
|
||||
- **百度智能小程序** (swan)
|
||||
- **字节跳动小程序** (tt)
|
||||
- **QQ小程序** (qq)
|
||||
- **京东小程序** (jd)
|
||||
- **H5网页** (h5)
|
||||
- **React Native** (rn)
|
||||
- **快应用** (quickapp)
|
||||
|
||||
|
||||
|
||||
## 项目演示
|
||||
| 后台管理系统 | https://mp.websoft.top |
|
||||
|--------|-------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| 演示账号 | 请联系开发者获取演示账号
|
||||
| 正式账号 | [立即注册](https://mp.websoft.top/register/?inviteCode=github) |
|
||||
| 关注公众号 |  |
|
||||
|
||||
|
||||
|
||||
|
||||
## 🛠️ 技术栈
|
||||
## 📦 技术栈
|
||||
|
||||
### 核心框架
|
||||
| 技术 | 版本 | 说明 |
|
||||
|------|------|------|
|
||||
| Taro | 4.0.8 | 跨平台开发框架 |
|
||||
| React | 18.3.1 | 前端UI框架 |
|
||||
| TypeScript | 5.7.2 | 类型安全的JavaScript |
|
||||
| Webpack | 5.78.0 | 模块打包工具 |
|
||||
| Babel | 7.26.0 | JavaScript编译器 |
|
||||
| PostCSS | 8.4.49 | CSS后处理器 |
|
||||
| ESLint | 8.57.1 | 代码质量检查 |
|
||||
- **Taro**: `4.0.8` - 跨端开发框架
|
||||
- **React**: `18.3.1` - UI 框架
|
||||
- **TypeScript**: `5.7.2` - 类型系统
|
||||
|
||||
### UI组件库
|
||||
| 技术 | 版本 | 说明 |
|
||||
|------|------|------|
|
||||
| NutUI React Taro | 2.7.4 | 移动端组件库 |
|
||||
| NutUI Icons | 2.0.1 | 图标库 |
|
||||
| NutUI Biz | 1.0.0-beta.2 | 业务组件库 |
|
||||
### UI 组件库
|
||||
- **NutUI React Taro**: `2.7.4` - 京东风格的 Taro React 组件库
|
||||
- **NutUI Icons**: `^1.0.0` - 图标库
|
||||
|
||||
### 样式方案
|
||||
| 技术 | 版本 | 说明 |
|
||||
|------|------|------|
|
||||
| Sass | - | CSS预处理器 |
|
||||
| TailwindCSS | 3.4.17 | 原子化CSS框架 |
|
||||
| Autoprefixer | 10.4.20 | CSS自动前缀 |
|
||||
- **Sass**: `^1.81.0` - CSS 预处理器
|
||||
- **TailwindCSS**: `3.4.17` - 原子化 CSS 框架
|
||||
|
||||
### 功能组件
|
||||
- **React Router DOM** - 路由管理
|
||||
- **Day.js** - 日期时间处理
|
||||
- **Crypto-js** - 加密解密工具
|
||||
- **js-base64** - Base64编码解码
|
||||
- **React Markdown** - Markdown渲染
|
||||
- **ECharts Taro3 React** - 图表组件
|
||||
### 状态管理
|
||||
- **React Hooks** - 内置 Hooks
|
||||
- **Context API** - 跨组件状态共享
|
||||
|
||||
## 📋 环境要求
|
||||
|
||||
### 基础环境
|
||||
- 📦 **Node.js 16+**
|
||||
- 📱 **微信开发者工具** (开发微信小程序)
|
||||
- 🔧 **支付宝开发者工具** (开发支付宝小程序)
|
||||
- 🌐 **现代浏览器** (开发H5应用)
|
||||
|
||||
### 包管理器
|
||||
- **npm** / **yarn** / **pnpm** (推荐使用pnpm)
|
||||
|
||||
### 开发工具
|
||||
- **推荐**:VS Code / WebStorm
|
||||
- **插件**:ES7+ React/Redux/React-Native snippets、TypeScript Importer
|
||||
### 工具库
|
||||
- **Day.js**: `^1.11.13` - 日期处理
|
||||
- **Crypto-js**: `^4.2.0` - 加密解密
|
||||
- **React Router DOM**: `^6.28.0` - 路由管理
|
||||
|
||||
## 🚀 快速开始
|
||||
|
||||
### 1. 克隆项目
|
||||
```bash
|
||||
git clone https://github.com/websoft-top/mp-taro.git
|
||||
cd mp-taro
|
||||
```
|
||||
### 安装依赖
|
||||
|
||||
### 2. 安装依赖
|
||||
```bash
|
||||
# 使用 pnpm (推荐)
|
||||
pnpm install
|
||||
|
||||
# 或使用 npm
|
||||
# 使用 npm
|
||||
npm install
|
||||
|
||||
# 或使用 yarn
|
||||
# 使用 yarn
|
||||
yarn install
|
||||
|
||||
# 使用 pnpm
|
||||
pnpm install
|
||||
```
|
||||
|
||||
### 3. 配置开发环境
|
||||
### 开发模式
|
||||
|
||||
#### 环境变量配置
|
||||
项目支持多环境配置,通过 `config/env.ts` 文件管理不同环境的配置:
|
||||
|
||||
```typescript
|
||||
// config/env.ts
|
||||
export const ENV_CONFIG = {
|
||||
// 开发环境
|
||||
development: {
|
||||
API_BASE_URL: 'http://localhost:3000/api',
|
||||
APP_NAME: 'Taro App Dev',
|
||||
DEBUG: 'true',
|
||||
},
|
||||
// 生产环境
|
||||
production: {
|
||||
API_BASE_URL: 'https://api.example.com',
|
||||
APP_NAME: 'Taro App',
|
||||
DEBUG: 'false',
|
||||
},
|
||||
// 测试环境
|
||||
test: {
|
||||
API_BASE_URL: 'https://test-api.example.com',
|
||||
APP_NAME: 'Taro App Test',
|
||||
DEBUG: 'true',
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 应用配置
|
||||
编辑 `config/app.ts` 文件,配置租户ID等信息:
|
||||
```typescript
|
||||
export const TenantId = 'your_tenant_id'
|
||||
export const BaseUrl = API_BASE_URL // 自动从环境配置读取
|
||||
```
|
||||
|
||||
### 4. 启动开发服务器
|
||||
|
||||
#### 微信小程序开发
|
||||
```bash
|
||||
# 开发环境(默认)
|
||||
# 微信小程序
|
||||
npm run dev:weapp
|
||||
|
||||
# 生产环境
|
||||
NODE_ENV=production npm run dev:weapp
|
||||
|
||||
# 测试环境
|
||||
NODE_ENV=test npm run dev:weapp
|
||||
```
|
||||
然后使用微信开发者工具打开 `dist` 目录
|
||||
|
||||
#### H5开发
|
||||
```bash
|
||||
# 启动H5开发模式
|
||||
# H5
|
||||
npm run dev:h5
|
||||
```
|
||||
访问 `http://localhost:10086` 查看H5版本
|
||||
|
||||
#### 其他平台
|
||||
```bash
|
||||
# 支付宝小程序
|
||||
npm run dev:alipay
|
||||
|
||||
# 百度智能小程序
|
||||
# 百度小程序
|
||||
npm run dev:swan
|
||||
|
||||
# 字节跳动小程序
|
||||
npm run dev:tt
|
||||
|
||||
# QQ小程序
|
||||
# QQ 小程序
|
||||
npm run dev:qq
|
||||
|
||||
# 京东小程序
|
||||
npm run dev:jd
|
||||
```
|
||||
|
||||
## ⚙️ 配置说明
|
||||
|
||||
### 项目配置
|
||||
主要配置文件位于 `config/` 目录:
|
||||
|
||||
- `config/index.ts` - Taro主配置文件
|
||||
- `config/dev.ts` - 开发环境配置
|
||||
- `config/prod.ts` - 生产环境配置
|
||||
- `config/env.ts` - 环境变量配置(新增)
|
||||
- `config/app.ts` - 应用配置(API地址、租户ID等)
|
||||
|
||||
### 环境变量配置
|
||||
项目支持多环境配置,在 `config/env.ts` 中管理:
|
||||
|
||||
```typescript
|
||||
// 环境变量配置
|
||||
export const ENV_CONFIG = {
|
||||
development: {
|
||||
API_BASE_URL: 'http://localhost:3000/api',
|
||||
APP_NAME: 'Taro App Dev',
|
||||
DEBUG: 'true',
|
||||
},
|
||||
production: {
|
||||
API_BASE_URL: 'https://api.example.com',
|
||||
APP_NAME: 'Taro App',
|
||||
DEBUG: 'false',
|
||||
},
|
||||
test: {
|
||||
API_BASE_URL: 'https://test-api.example.com',
|
||||
APP_NAME: 'Taro App Test',
|
||||
DEBUG: 'true',
|
||||
}
|
||||
}
|
||||
|
||||
// 获取当前环境配置
|
||||
export function getEnvConfig() {
|
||||
const env = process.env.NODE_ENV || 'development'
|
||||
if (env === 'production') {
|
||||
return ENV_CONFIG.production
|
||||
} else if (env === 'test') {
|
||||
return ENV_CONFIG.test
|
||||
} else {
|
||||
return ENV_CONFIG.development
|
||||
}
|
||||
}
|
||||
|
||||
// 导出环境变量
|
||||
export const { API_BASE_URL, APP_NAME, DEBUG } = getEnvConfig()
|
||||
```
|
||||
|
||||
### 应用配置
|
||||
在 `config/app.ts` 中配置:
|
||||
```typescript
|
||||
import { API_BASE_URL } from './env'
|
||||
|
||||
// 租户ID
|
||||
export const TenantId = 'your_tenant_id'
|
||||
|
||||
// API基础地址(自动从环境配置读取)
|
||||
export const BaseUrl = API_BASE_URL
|
||||
|
||||
// 其他配置...
|
||||
```
|
||||
|
||||
### 环境切换
|
||||
通过设置 `NODE_ENV` 环境变量来切换不同环境:
|
||||
### 生产构建
|
||||
|
||||
```bash
|
||||
# 开发环境(默认)
|
||||
npm run dev:weapp
|
||||
|
||||
# 生产环境
|
||||
NODE_ENV=production npm run dev:weapp
|
||||
|
||||
# 测试环境
|
||||
NODE_ENV=test npm run dev:weapp
|
||||
```
|
||||
|
||||
## 🌍 环境变量详细说明
|
||||
|
||||
### 环境变量文件结构
|
||||
```
|
||||
config/
|
||||
├── env.ts # 环境变量配置文件
|
||||
├── app.ts # 应用配置(使用环境变量)
|
||||
└── index.ts # Taro配置(注入环境变量)
|
||||
```
|
||||
|
||||
### 支持的环境变量
|
||||
| 变量名 | 说明 | 开发环境默认值 | 生产环境建议值 |
|
||||
|--------|------|---------------|---------------|
|
||||
| `API_BASE_URL` | API基础地址 | `http://localhost:3000/api` | `https://api.yourdomain.com/api` |
|
||||
| `APP_NAME` | 应用名称 | `Taro App Dev` | `Your App Name` |
|
||||
| `DEBUG` | 调试模式 | `true` | `false` |
|
||||
|
||||
### 环境变量使用方法
|
||||
|
||||
#### 1. 修改环境配置
|
||||
编辑 `config/env.ts` 文件:
|
||||
```typescript
|
||||
export const ENV_CONFIG = {
|
||||
development: {
|
||||
API_BASE_URL: 'http://localhost:3000/api', // 修改为你的开发环境API地址
|
||||
APP_NAME: 'My App Dev',
|
||||
DEBUG: 'true',
|
||||
},
|
||||
production: {
|
||||
API_BASE_URL: 'https://api.yourdomain.com/api', // 修改为你的生产环境API地址
|
||||
APP_NAME: 'My App',
|
||||
DEBUG: 'false',
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 2. 在代码中使用环境变量
|
||||
```typescript
|
||||
// 在 config/app.ts 中
|
||||
import { API_BASE_URL, APP_NAME, DEBUG } from './env'
|
||||
|
||||
export const BaseUrl = API_BASE_URL
|
||||
export const AppName = APP_NAME
|
||||
export const IsDebug = DEBUG === 'true'
|
||||
|
||||
// 在其他文件中使用
|
||||
import { BaseUrl } from '@/config/app'
|
||||
console.log('API地址:', BaseUrl)
|
||||
```
|
||||
|
||||
#### 3. 运行时环境切换
|
||||
```bash
|
||||
# 开发环境(默认)
|
||||
npm run dev:weapp
|
||||
|
||||
# 生产环境
|
||||
NODE_ENV=production npm run dev:weapp
|
||||
|
||||
# 测试环境
|
||||
NODE_ENV=test npm run dev:weapp
|
||||
|
||||
# 构建生产版本
|
||||
NODE_ENV=production npm run build:weapp
|
||||
```
|
||||
|
||||
### 环境变量最佳实践
|
||||
|
||||
1. **敏感信息处理**:不要在代码中硬编码敏感信息,使用环境变量管理
|
||||
2. **环境隔离**:确保不同环境使用不同的API地址和配置
|
||||
3. **默认值设置**:为所有环境变量提供合理的默认值
|
||||
4. **类型安全**:使用TypeScript确保环境变量的类型安全
|
||||
|
||||
### 路径别名配置
|
||||
项目已配置路径别名,可以使用以下方式导入:
|
||||
```typescript
|
||||
import Component from '@/components/Component'
|
||||
import { request } from '@/utils/request'
|
||||
import { useCart } from '@/hooks/useCart'
|
||||
import config from '@/config/app'
|
||||
```
|
||||
|
||||
### 样式配置
|
||||
- **Sass**: 支持嵌套、变量、混入等特性
|
||||
- **TailwindCSS**: 原子化CSS,配置文件为 `tailwind.config.js`
|
||||
- **PostCSS**: 自动处理CSS兼容性,配置文件为 `postcss.config.js`
|
||||
|
||||
## 🎯 核心功能
|
||||
|
||||
### 🔐 用户认证与授权
|
||||
- **微信登录**:基于微信OpenID的用户认证
|
||||
- **短信登录**:支持手机号短信验证登录
|
||||
- **用户注册**:完整的用户注册流程
|
||||
- **密码找回**:支持手机号找回密码
|
||||
|
||||
### 📝 内容管理系统(CMS)
|
||||
- **文章浏览**:支持文章列表和详情查看
|
||||
- **分类管理**:内容分类浏览
|
||||
- **富文本渲染**:支持Markdown内容渲染
|
||||
- **媒体展示**:图片、视频等媒体文件展示
|
||||
|
||||
### 🛒 电商系统
|
||||
- **商品展示**:商品列表、详情、分类浏览
|
||||
- **购物车**:商品加入购物车、数量管理
|
||||
- **订单管理**:订单创建、查看、状态跟踪
|
||||
- **地址管理**:收货地址的增删改查
|
||||
- **支付集成**:支持微信支付等支付方式
|
||||
|
||||
### 👤 用户中心
|
||||
- **个人资料**:用户信息查看和编辑
|
||||
- **实名认证**:用户身份验证功能
|
||||
- **帮助中心**:常见问题和帮助文档
|
||||
- **关于我们**:应用介绍和联系方式
|
||||
- **设置中心**:个人偏好设置
|
||||
|
||||
### 📊 数据可视化
|
||||
- **图表展示**:基于ECharts的数据可视化
|
||||
- **统计分析**:业务数据统计展示
|
||||
- **实时更新**:动态数据更新
|
||||
|
||||
## 🏗️ 项目结构
|
||||
|
||||
```
|
||||
src/
|
||||
├── app.config.ts # 应用配置文件
|
||||
├── app.scss # 全局样式
|
||||
├── app.ts # 应用入口文件
|
||||
├── api/ # API接口定义
|
||||
│ ├── cms/ # 内容管理相关API
|
||||
│ ├── shop/ # 商城相关API
|
||||
│ ├── system/ # 系统相关API
|
||||
│ ├── passport/ # 用户认证相关API
|
||||
│ └── index.ts # API统一导出
|
||||
├── assets/ # 静态资源
|
||||
│ └── tabbar/ # 底部导航图标
|
||||
├── components/ # 公共组件
|
||||
│ ├── Header.tsx # 页面头部组件
|
||||
│ ├── TabBar.tsx # 底部导航组件
|
||||
│ ├── GoodsList.tsx # 商品列表组件
|
||||
│ └── ...
|
||||
├── hooks/ # 自定义Hooks
|
||||
│ └── useCart.ts # 购物车Hook
|
||||
├── pages/ # 页面文件
|
||||
│ ├── index/ # 首页
|
||||
│ ├── cart/ # 购物车页面
|
||||
│ ├── order/ # 订单页面
|
||||
│ └── user/ # 用户中心页面
|
||||
├── passport/ # 用户认证页面
|
||||
│ ├── login.tsx # 登录页面
|
||||
│ ├── register.tsx # 注册页面
|
||||
│ └── ...
|
||||
├── cms/ # 内容管理页面
|
||||
│ ├── category/ # 分类页面
|
||||
│ └── detail/ # 详情页面
|
||||
├── shop/ # 商城页面
|
||||
│ ├── category/ # 商品分类
|
||||
│ ├── goodsDetail/ # 商品详情
|
||||
│ ├── orderConfirm/ # 订单确认
|
||||
│ └── orderDetail/ # 订单详情
|
||||
├── user/ # 用户相关页面
|
||||
│ ├── profile/ # 个人资料
|
||||
│ ├── address/ # 地址管理
|
||||
│ ├── setting/ # 设置
|
||||
│ └── ...
|
||||
└── utils/ # 工具函数
|
||||
├── request.ts # 网络请求封装
|
||||
├── common.ts # 通用工具函数
|
||||
├── time.ts # 时间处理工具
|
||||
└── server.ts # 服务端相关工具
|
||||
|
||||
config/ # 配置文件目录
|
||||
├── index.ts # Taro主配置
|
||||
├── dev.ts # 开发环境配置
|
||||
├── prod.ts # 生产环境配置
|
||||
├── env.ts # 环境变量配置
|
||||
└── app.ts # 应用配置
|
||||
|
||||
types/ # 类型定义
|
||||
└── global.d.ts # 全局类型定义
|
||||
```
|
||||
|
||||
## 🔧 开发规范
|
||||
|
||||
### 代码结构
|
||||
- **Pages**:页面组件,负责页面逻辑和UI展示
|
||||
- **Components**:公共组件,可复用的UI组件
|
||||
- **Hooks**:自定义Hooks,封装业务逻辑
|
||||
- **Utils**:工具函数,通用的功能函数
|
||||
- **API**:接口定义,与后端API的交互
|
||||
|
||||
### 命名规范
|
||||
- **组件名**:使用大驼峰命名法(PascalCase)
|
||||
- **文件名**:使用小驼峰命名法(camelCase)或短横线命名法(kebab-case)
|
||||
- **变量名**:使用小驼峰命名法(camelCase)
|
||||
- **常量**:使用全大写,下划线分隔
|
||||
- **CSS类名**:使用短横线命名法(kebab-case)
|
||||
|
||||
### TypeScript规范
|
||||
- 优先使用接口(interface)定义类型
|
||||
- 为函数参数和返回值添加类型注解
|
||||
- 使用泛型提高代码复用性
|
||||
- 避免使用 `any` 类型
|
||||
|
||||
## 📚 构建与部署
|
||||
|
||||
### 构建命令
|
||||
```bash
|
||||
# 构建微信小程序
|
||||
# 微信小程序
|
||||
npm run build:weapp
|
||||
|
||||
# 构建H5应用
|
||||
# H5
|
||||
npm run build:h5
|
||||
|
||||
# 构建支付宝小程序
|
||||
npm run build:alipay
|
||||
|
||||
# 构建其他平台
|
||||
npm run build:swan # 百度智能小程序
|
||||
npm run build:tt # 字节跳动小程序
|
||||
npm run build:qq # QQ小程序
|
||||
npm run build:jd # 京东小程序
|
||||
# 其他平台类似
|
||||
npm run build:[platform]
|
||||
```
|
||||
|
||||
### 主要API模块
|
||||
项目API按功能模块组织:
|
||||
## 📁 项目结构
|
||||
|
||||
- **用户认证**: `src/api/passport/` - 登录、注册、权限验证
|
||||
- **用户管理**: `src/api/system/user/` - 用户信息管理
|
||||
- **内容管理**: `src/api/cms/` - 文章、媒体文件管理
|
||||
- **商城管理**: `src/api/shop/` - 商品、订单管理
|
||||
- **系统管理**: `src/api/system/` - 系统配置、参数管理
|
||||
```
|
||||
shop-taro/
|
||||
├── config/ # Taro 配置文件
|
||||
│ ├── index.ts # 主配置
|
||||
│ ├── dev.ts # 开发配置
|
||||
│ └── prod.ts # 生产配置
|
||||
├── src/ # 源代码
|
||||
│ ├── app.config.ts # 应用配置(页面路由、tabBar等)
|
||||
│ ├── app.tsx # 应用入口
|
||||
│ ├── app.scss # 全局样式
|
||||
│ ├── pages/ # 页面目录
|
||||
│ │ ├── index/ # 首页
|
||||
│ │ │ ├── index.tsx
|
||||
│ │ │ ├── index.scss
|
||||
│ │ │ └── index.config.ts
|
||||
│ │ └── home/ # 个人中心页
|
||||
│ │ ├── home.tsx
|
||||
│ │ ├── home.scss
|
||||
│ │ └── home.config.ts
|
||||
│ ├── components/ # 自定义组件
|
||||
│ ├── contexts/ # Context API
|
||||
│ │ └── AppContext.tsx # 应用上下文
|
||||
│ ├── hooks/ # 自定义 Hooks
|
||||
│ │ └── useAppContext.ts
|
||||
│ ├── utils/ # 工具函数
|
||||
│ │ └── index.ts # 常用工具(日期、加密、防抖节流等)
|
||||
│ ├── types/ # TypeScript 类型定义
|
||||
│ │ └── global.d.ts # 全局类型声明
|
||||
│ ├── styles/ # 样式文件
|
||||
│ └── assets/ # 静态资源
|
||||
│ └── tabbar/ # tabBar 图标
|
||||
├── package.json # 项目依赖
|
||||
├── tsconfig.json # TypeScript 配置
|
||||
├── tailwind.config.js # TailwindCSS 配置
|
||||
├── postcss.config.js # PostCSS 配置
|
||||
├── project.config.json # 小程序项目配置
|
||||
└── README.md # 项目说明
|
||||
```
|
||||
|
||||
## 🚀 部署指南
|
||||
## ✨ 特性
|
||||
|
||||
### 微信小程序部署
|
||||
1. 构建小程序代码:
|
||||
```bash
|
||||
npm run build:weapp
|
||||
```
|
||||
2. 使用微信开发者工具打开 `dist` 目录
|
||||
3. 点击"上传"按钮,上传代码到微信后台
|
||||
4. 在微信公众平台提交审核
|
||||
### 1. 主题切换
|
||||
- 支持浅色/深色模式切换
|
||||
- 使用 Context API 管理主题状态
|
||||
- 暗黑模式适配
|
||||
|
||||
### H5应用部署
|
||||
1. 构建H5应用:
|
||||
```bash
|
||||
npm run build:h5
|
||||
```
|
||||
2. 将 `dist` 目录下的文件上传到Web服务器
|
||||
3. 配置Nginx或Apache服务器
|
||||
4. 确保HTTPS访问(微信要求)
|
||||
### 2. NutUI 组件库
|
||||
- 已集成 @nutui/nutui-react-taro@2.7.4
|
||||
- 支持按需引入
|
||||
- 中文国际化配置
|
||||
|
||||
### 其他平台部署
|
||||
各平台部署流程类似,构建对应平台代码后,使用相应的开发者工具上传即可。
|
||||
### 3. TailwindCSS 集成
|
||||
- 支持原子化 CSS 编写
|
||||
- 暗黑模式支持(dark mode)
|
||||
- 自定义主题色配置
|
||||
|
||||
### 生产环境配置
|
||||
在生产环境中,需要修改相关配置:
|
||||
### 4. 工具函数封装
|
||||
- **日期处理**: 基于 Day.js 的格式化
|
||||
- **加密解密**: MD5、AES 加密
|
||||
- **性能优化**: 防抖、节流函数
|
||||
- **数据操作**: 深拷贝等工具
|
||||
|
||||
1. **环境变量配置**:修改 `config/env.ts` 中的生产环境配置
|
||||
```typescript
|
||||
production: {
|
||||
API_BASE_URL: 'https://your-production-api.com/api',
|
||||
APP_NAME: 'Your App Name',
|
||||
DEBUG: 'false',
|
||||
}
|
||||
```
|
||||
### 5. TypeScript 支持
|
||||
- 完整的类型定义
|
||||
- 路径别名配置 `@/*`
|
||||
- 严格模式开启
|
||||
|
||||
2. **构建配置**:修改 `config/prod.ts` 中的构建配置
|
||||
- 启用代码压缩和混淆
|
||||
- 配置CDN加速
|
||||
- 优化资源加载
|
||||
## 🎨 代码示例
|
||||
|
||||
3. **部署命令**:
|
||||
```bash
|
||||
NODE_ENV=production npm run build:weapp
|
||||
```
|
||||
### 使用 NutUI 组件
|
||||
|
||||
```tsx
|
||||
import { Button, Cell, CellGroup } from '@nutui/nutui-react-taro'
|
||||
|
||||
export default function MyPage() {
|
||||
return (
|
||||
<CellGroup>
|
||||
<Cell title="标题" description="描述" />
|
||||
<Button type="primary" block>
|
||||
提交
|
||||
</Button>
|
||||
</CellGroup>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
### 使用 TailwindCSS
|
||||
|
||||
```tsx
|
||||
<View className="p-4 bg-white dark:bg-gray-800">
|
||||
<Text className="text-lg font-bold text-gray-800 dark:text-white">
|
||||
暗黑模式适配文本
|
||||
</Text>
|
||||
</View>
|
||||
```
|
||||
|
||||
### 使用 Context 管理状态
|
||||
|
||||
```tsx
|
||||
import { useAppContext } from '@/hooks/useAppContext'
|
||||
|
||||
export default function MyPage() {
|
||||
const { theme, toggleTheme } = useAppContext()
|
||||
|
||||
return (
|
||||
<Button onClick={toggleTheme}>
|
||||
当前主题: {theme}
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
### 使用工具函数
|
||||
|
||||
```tsx
|
||||
import { formatDate, md5, debounce } from '@/utils'
|
||||
|
||||
// 日期格式化
|
||||
const dateStr = formatDate(new Date())
|
||||
|
||||
// MD5 加密
|
||||
const encrypted = md5('hello world')
|
||||
|
||||
// 防抖
|
||||
const handleSearch = debounce((keyword) => {
|
||||
console.log(keyword)
|
||||
}, 500)
|
||||
```
|
||||
|
||||
## 📝 开发规范
|
||||
|
||||
### 文件命名
|
||||
- 页面目录: `kebab-case` (如 `user-profile/`)
|
||||
- 组件文件: `PascalCase` (如 `UserProfile.tsx`)
|
||||
- 工具文件: `camelCase` (如 `formatDate.ts`)
|
||||
|
||||
### 代码风格
|
||||
- 使用 ESLint + @typescript-eslint 进行代码检查
|
||||
- 使用 2 空格缩进
|
||||
- 分号结尾
|
||||
- 单引号优先
|
||||
|
||||
### 提交规范
|
||||
```
|
||||
feat: 新功能
|
||||
fix: 修复 bug
|
||||
docs: 文档更新
|
||||
style: 代码格式调整
|
||||
refactor: 重构
|
||||
test: 测试相关
|
||||
chore: 构建/工具配置
|
||||
```
|
||||
|
||||
## 🔧 常见问题
|
||||
|
||||
### 开发环境问题
|
||||
1. **Node.js版本问题**:确保使用Node.js 16+版本
|
||||
2. **依赖安装失败**:尝试清除缓存后重新安装
|
||||
```bash
|
||||
npm cache clean --force
|
||||
npm install
|
||||
```
|
||||
3. **编译错误**:检查TypeScript配置和代码语法
|
||||
### 1. TailwindCSS 样式不生效?
|
||||
检查 `postcss.config.js` 配置,确保已安装 `postcss-nested`。
|
||||
|
||||
### 小程序开发问题
|
||||
1. **微信开发者工具无法预览**:检查appid配置和网络连接
|
||||
2. **API请求失败**:确认后端服务正常运行,检查域名白名单
|
||||
3. **样式显示异常**:检查rpx单位使用和NutUI组件样式
|
||||
### 2. NutUI 组件样式丢失?
|
||||
确保在 `app.scss` 中引入了 NutUI 样式:
|
||||
```scss
|
||||
@import '@nutui/nutui-react-taro/dist/styles/vitamin.css';
|
||||
```
|
||||
|
||||
### 性能优化建议
|
||||
1. **代码分割**:使用Taro的分包功能减少主包大小
|
||||
2. **图片优化**:使用WebP格式,启用图片懒加载
|
||||
3. **请求优化**:合并API请求,使用缓存机制
|
||||
### 3. TypeScript 路径别名报错?
|
||||
检查 `tsconfig.json` 的 `paths` 配置,并确保 IDE 正确识别。
|
||||
|
||||
## 🤝 贡献指南
|
||||
## 📚 相关文档
|
||||
|
||||
1. Fork 本仓库
|
||||
2. 创建特性分支 (`git checkout -b feature/AmazingFeature`)
|
||||
3. 提交更改 (`git commit -m 'Add some AmazingFeature'`)
|
||||
4. 推送到分支 (`git push origin feature/AmazingFeature`)
|
||||
5. 打开 Pull Request
|
||||
- [Taro 官方文档](https://taro-docs.jd.com/)
|
||||
- [React 官方文档](https://react.dev/)
|
||||
- [NutUI React 文档](https://nutui.jd.com/react-taro/)
|
||||
- [TailwindCSS 文档](https://tailwindcss.com/docs)
|
||||
- [TypeScript 文档](https://www.typescriptlang.org/docs/)
|
||||
|
||||
## 📄 许可证
|
||||
## 📄 License
|
||||
|
||||
本项目采用 MIT 许可证 - 查看 [LICENSE](LICENSE) 文件了解详情
|
||||
|
||||
## 📞 联系我们
|
||||
|
||||
- 官网:https://websoft.top
|
||||
- 邮箱:170083662@qq.com
|
||||
- QQ群:479713884
|
||||
MIT
|
||||
|
||||
---
|
||||
|
||||
⭐ 如果这个项目对您有帮助,请给我们一个星标!
|
||||
**构建时间**: 2026-05-10
|
||||
**作者**: Senior Developer
|
||||
|
||||
@@ -1,209 +0,0 @@
|
||||
# 微信小程序扫码登录功能实现
|
||||
|
||||
## 项目概述
|
||||
|
||||
本项目已完整实现微信小程序扫码登录功能,支持用户通过小程序扫描网页端二维码快速登录。
|
||||
|
||||
## 🎯 功能特性
|
||||
|
||||
- ✅ **完整的后端API** - Java Spring Boot实现
|
||||
- ✅ **多种前端集成方式** - 按钮、弹窗、页面
|
||||
- ✅ **智能二维码解析** - 支持URL、JSON、纯token格式
|
||||
- ✅ **安全可靠** - Token有效期控制,防重复使用
|
||||
- ✅ **用户体验优秀** - 实时状态反馈,错误处理完善
|
||||
- ✅ **微信深度集成** - 自动获取用户信息
|
||||
|
||||
## 📁 项目结构
|
||||
|
||||
### 后端 (Java)
|
||||
```
|
||||
auto/
|
||||
├── controller/QrLoginController.java # REST API控制器
|
||||
├── service/QrLoginService.java # 业务接口
|
||||
├── service/impl/QrLoginServiceImpl.java # 业务实现
|
||||
└── dto/ # 数据传输对象
|
||||
├── QrLoginData.java
|
||||
├── QrLoginConfirmRequest.java
|
||||
├── QrLoginStatusResponse.java
|
||||
└── QrLoginGenerateResponse.java
|
||||
```
|
||||
|
||||
### 前端 (小程序)
|
||||
```
|
||||
src/
|
||||
├── api/qr-login/index.ts # API接口层
|
||||
├── hooks/useQRLogin.ts # 业务逻辑Hook
|
||||
├── components/ # 组件层
|
||||
│ ├── QRLoginButton.tsx # 扫码按钮组件
|
||||
│ ├── QRLoginScanner.tsx # 扫码器组件
|
||||
│ ├── QRScanModal.tsx # 扫码弹窗组件
|
||||
│ └── QRLoginDemo.tsx # 演示组件
|
||||
└── pages/ # 页面层
|
||||
├── qr-login/index.tsx # 扫码登录页面
|
||||
├── qr-confirm/index.tsx # 登录确认页面
|
||||
└── qr-test/index.tsx # 功能测试页面
|
||||
```
|
||||
|
||||
## 🚀 快速开始
|
||||
|
||||
### 1. 后端配置
|
||||
|
||||
确保Java后端服务正常运行,API接口可访问:
|
||||
- `POST /api/qr-login/generate` - 生成扫码token
|
||||
- `GET /api/qr-login/status/{token}` - 检查登录状态
|
||||
- `POST /api/qr-login/confirm` - 确认登录
|
||||
- `POST /api/qr-login/scan/{token}` - 扫码操作
|
||||
|
||||
### 2. 前端使用
|
||||
|
||||
#### 最简单的使用方式:
|
||||
```tsx
|
||||
import QRLoginButton from '@/components/QRLoginButton';
|
||||
|
||||
<QRLoginButton />
|
||||
```
|
||||
|
||||
#### 弹窗方式:
|
||||
```tsx
|
||||
import QRScanModal from '@/components/QRScanModal';
|
||||
|
||||
<QRScanModal
|
||||
visible={showModal}
|
||||
onClose={() => setShowModal(false)}
|
||||
onSuccess={(result) => console.log('登录成功', result)}
|
||||
/>
|
||||
```
|
||||
|
||||
#### 页面跳转方式:
|
||||
```tsx
|
||||
import Taro from '@tarojs/taro';
|
||||
|
||||
Taro.navigateTo({
|
||||
url: '/passport/qr-login/index'
|
||||
});
|
||||
```
|
||||
|
||||
## 🔧 支持的二维码格式
|
||||
|
||||
系统智能识别多种二维码格式:
|
||||
|
||||
1. **URL格式**:`https://mp.websoft.top/qr-confirm?qrCodeKey=token123`
|
||||
2. **JSON格式**:`{"token": "token123", "type": "qr-login"}`
|
||||
3. **简单格式**:`qr-login:token123` 或直接 `token123`
|
||||
|
||||
## 📱 页面说明
|
||||
|
||||
### 1. 扫码登录页面 (`/passport/qr-login/index`)
|
||||
- 完整的扫码登录功能
|
||||
- 用户信息显示
|
||||
- 登录历史记录
|
||||
- 使用说明和安全提示
|
||||
|
||||
### 2. 登录确认页面 (`/passport/qr-confirm/index`)
|
||||
- 处理二维码跳转确认
|
||||
- 支持URL参数:`qrCodeKey` 或 `token`
|
||||
- 用户确认界面
|
||||
|
||||
### 3. 功能测试页面 (`/passport/qr-test/index`)
|
||||
- 演示各种集成方式
|
||||
- 功能测试和调试
|
||||
|
||||
## 🛠️ 开发指南
|
||||
|
||||
### 1. 添加扫码按钮到现有页面
|
||||
|
||||
```tsx
|
||||
import QRLoginButton from '@/components/QRLoginButton';
|
||||
|
||||
const MyPage = () => {
|
||||
return (
|
||||
<View>
|
||||
<QRLoginButton
|
||||
text="扫码登录"
|
||||
onSuccess={(result) => {
|
||||
// 处理登录成功
|
||||
console.log('用户登录成功:', result);
|
||||
}}
|
||||
onError={(error) => {
|
||||
// 处理登录失败
|
||||
console.error('登录失败:', error);
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
### 2. 自定义扫码逻辑
|
||||
|
||||
```tsx
|
||||
import { useQRLogin } from '@/hooks/useQRLogin';
|
||||
|
||||
const MyComponent = () => {
|
||||
const {
|
||||
startScan,
|
||||
isLoading,
|
||||
isSuccess,
|
||||
result,
|
||||
error
|
||||
} = useQRLogin();
|
||||
|
||||
return (
|
||||
<Button
|
||||
loading={isLoading}
|
||||
onClick={startScan}
|
||||
>
|
||||
{isLoading ? '扫码中...' : '扫码登录'}
|
||||
</Button>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
## 🔒 安全注意事项
|
||||
|
||||
1. **用户登录验证**:使用前确保用户已在小程序中登录
|
||||
2. **Token有效期**:二维码5分钟有效期,过期自动失效
|
||||
3. **权限申请**:确保小程序已申请摄像头权限
|
||||
4. **来源验证**:只扫描来自官方网站的登录二维码
|
||||
|
||||
## 🐛 常见问题
|
||||
|
||||
### Q: 提示"请先登录小程序"
|
||||
A: 用户需要先在小程序中完成登录,获取用户ID和访问令牌。
|
||||
|
||||
### Q: 提示"无效的登录二维码"
|
||||
A: 检查二维码格式是否正确,或者二维码是否已过期。
|
||||
|
||||
### Q: 扫码失败
|
||||
A: 检查摄像头权限,确保二维码清晰可见。
|
||||
|
||||
### Q: 网络请求失败
|
||||
A: 检查网络连接和API接口地址配置。
|
||||
|
||||
## 📚 相关文档
|
||||
|
||||
- [详细使用指南](docs/QR_LOGIN_USAGE.md)
|
||||
- [API接口文档](src/api/qr-login/index.ts)
|
||||
- [组件API文档](docs/QR_LOGIN_USAGE.md#组件api)
|
||||
|
||||
## 🎉 测试功能
|
||||
|
||||
访问测试页面验证功能:
|
||||
```
|
||||
/pages/qr-test/index
|
||||
```
|
||||
|
||||
该页面包含所有集成方式的演示和测试功能。
|
||||
|
||||
## 📞 技术支持
|
||||
|
||||
如有问题,请检查:
|
||||
1. 后端API服务是否正常运行
|
||||
2. 小程序用户是否已登录
|
||||
3. 网络连接是否正常
|
||||
4. 二维码格式是否正确
|
||||
|
||||
---
|
||||
|
||||
**开发者**: 科技小王子
|
||||
**更新时间**: 2025-09-20
|
||||
+7
-20
@@ -1,24 +1,11 @@
|
||||
// babel-preset-taro 更多选项和默认值:
|
||||
// https://github.com/NervJS/taro/blob/next/packages/babel-preset-taro/README.md
|
||||
module.exports = {
|
||||
presets: [
|
||||
['taro',
|
||||
{
|
||||
framework: 'react',
|
||||
ts: 'true',
|
||||
compiler: 'webpack5',
|
||||
}]
|
||||
"presets": [
|
||||
["taro", {
|
||||
"framework": "react",
|
||||
"ts": true
|
||||
}]
|
||||
],
|
||||
plugins: [
|
||||
[
|
||||
"import",
|
||||
{
|
||||
"libraryName": "@nutui/nutui-react-taro",
|
||||
"libraryDirectory": "dist/esm",
|
||||
"style": 'css',
|
||||
"camel2DashComponentName": false
|
||||
},
|
||||
'nutui-react-taro'
|
||||
]
|
||||
"plugins": [
|
||||
"@babel/plugin-transform-runtime"
|
||||
]
|
||||
}
|
||||
|
||||
+7
-13
@@ -1,14 +1,8 @@
|
||||
import { API_BASE_URL } from './env'
|
||||
import { API_BASE_URL, SERVER_API_URL, WEBSOPY_API_BASE_URL } from './env'
|
||||
|
||||
// 租户ID - 请根据实际情况修改
|
||||
export const TenantId = '5';
|
||||
// 租户名称
|
||||
export const TenantName = '网宿软件';
|
||||
// 接口地址 - 请根据实际情况修改
|
||||
export const BaseUrl = API_BASE_URL;
|
||||
// 当前版本
|
||||
export const Version = 'v3.0.8';
|
||||
// 版权信息
|
||||
export const Copyright = 'WebSoft Inc.';
|
||||
|
||||
// java -jar CertificateDownloader.jar -k 0kF5OlPr482EZwtn9zGufUcqa7ovgxRL -m 1723321338 -f ./apiclient_key.pem -s 2B933F7C35014A1C363642623E4A62364B34C4EB -o ./
|
||||
export const TenantId = '5'
|
||||
export const TenantName = '威硕开发助手'
|
||||
export const BaseUrl = API_BASE_URL
|
||||
export const ServerBaseUrl = SERVER_API_URL
|
||||
export const WebsopyBaseUrl = WEBSOPY_API_BASE_URL
|
||||
export const Version = 'v3.0.0'
|
||||
|
||||
+14
-10
@@ -1,13 +1,17 @@
|
||||
import type { UserConfigExport } from "@tarojs/cli";
|
||||
export default {
|
||||
logger: {
|
||||
quiet: false,
|
||||
stats: true
|
||||
env: {
|
||||
NODE_ENV: '"development"'
|
||||
},
|
||||
mini: {
|
||||
miniCssExtractPluginOption: {
|
||||
ignoreOrder: true
|
||||
defineConstants: {
|
||||
__DEV__: JSON.stringify(true)
|
||||
},
|
||||
mini: {},
|
||||
h5: {
|
||||
devServer: {
|
||||
host: 'localhost',
|
||||
port: 10086,
|
||||
hot: true,
|
||||
open: false
|
||||
}
|
||||
},
|
||||
h5: {}
|
||||
} satisfies UserConfigExport<'webpack5'>
|
||||
}
|
||||
}
|
||||
|
||||
Vendored
+30
@@ -0,0 +1,30 @@
|
||||
export const CURRENT_ENV: string
|
||||
export const ENV_CONFIG: {
|
||||
development: {
|
||||
API_BASE_URL: string
|
||||
SERVER_API_URL: string
|
||||
WEBSOPY_API_BASE_URL: string
|
||||
APP_NAME: string
|
||||
DEBUG: string
|
||||
}
|
||||
test: {
|
||||
API_BASE_URL: string
|
||||
SERVER_API_URL: string
|
||||
WEBSOPY_API_BASE_URL: string
|
||||
APP_NAME: string
|
||||
DEBUG: string
|
||||
}
|
||||
production: {
|
||||
API_BASE_URL: string
|
||||
SERVER_API_URL: string
|
||||
WEBSOPY_API_BASE_URL: string
|
||||
APP_NAME: string
|
||||
DEBUG: string
|
||||
}
|
||||
}
|
||||
export function getEnvConfig(): (typeof ENV_CONFIG)[keyof typeof ENV_CONFIG]
|
||||
export const API_BASE_URL: string
|
||||
export const SERVER_API_URL: string
|
||||
export const WEBSOPY_API_BASE_URL: string
|
||||
export const APP_NAME: string
|
||||
export const DEBUG: string
|
||||
@@ -0,0 +1,31 @@
|
||||
const CURRENT_ENV = process.env.NODE_ENV === 'production' ? 'production' : 'development'
|
||||
|
||||
export const ENV_CONFIG = {
|
||||
development: {
|
||||
API_BASE_URL: 'https://shop-api.websoft.top/api',
|
||||
SERVER_API_URL: 'https://server.websoft.top/api',
|
||||
WEBSOPY_API_BASE_URL: 'https://websopy-api.websoft.top/api',
|
||||
APP_NAME: '威硕谱软件',
|
||||
DEBUG: 'true',
|
||||
},
|
||||
test: {
|
||||
API_BASE_URL: 'https://shop-api.websoft.top/api',
|
||||
SERVER_API_URL: 'https://server.websoft.top/api',
|
||||
WEBSOPY_API_BASE_URL: 'https://websopy-api.websoft.top/api',
|
||||
APP_NAME: '威硕谱软件',
|
||||
DEBUG: 'true',
|
||||
},
|
||||
production: {
|
||||
API_BASE_URL: 'https://shop-api.websoft.top/api',
|
||||
SERVER_API_URL: 'https://server.websoft.top/api',
|
||||
WEBSOPY_API_BASE_URL: 'https://websopy-api.websoft.top/api',
|
||||
APP_NAME: '威硕谱软件',
|
||||
DEBUG: 'false',
|
||||
},
|
||||
}
|
||||
|
||||
export function getEnvConfig() {
|
||||
return ENV_CONFIG[CURRENT_ENV] || ENV_CONFIG.development
|
||||
}
|
||||
|
||||
export const { API_BASE_URL, SERVER_API_URL, WEBSOPY_API_BASE_URL, APP_NAME, DEBUG } = getEnvConfig()
|
||||
@@ -1,43 +0,0 @@
|
||||
// 环境变量配置
|
||||
export const ENV_CONFIG = {
|
||||
// 开发环境
|
||||
development: {
|
||||
// API_BASE_URL: 'http://127.0.0.1:9200/api',
|
||||
API_BASE_URL: 'https://websopy-api.websoft.top/api',
|
||||
APP_NAME: '开发环境',
|
||||
DEBUG: 'true',
|
||||
},
|
||||
// 生产环境
|
||||
production: {
|
||||
API_BASE_URL: 'https://websopy-api.websoft.top/api',
|
||||
APP_NAME: '网宿软件',
|
||||
DEBUG: 'false',
|
||||
},
|
||||
// 测试环境
|
||||
test: {
|
||||
API_BASE_URL: 'https://websopy-api.websoft.top/api',
|
||||
APP_NAME: '测试环境',
|
||||
DEBUG: 'true',
|
||||
}
|
||||
}
|
||||
|
||||
// 获取当前环境配置
|
||||
export function getEnvConfig() {
|
||||
const env = process.env.NODE_ENV || 'development'
|
||||
if (env === 'production') {
|
||||
return ENV_CONFIG.production
|
||||
} else { // @ts-ignore
|
||||
if (env === 'test') {
|
||||
return ENV_CONFIG.test
|
||||
} else {
|
||||
return ENV_CONFIG.development
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 导出环境变量
|
||||
export const {
|
||||
API_BASE_URL,
|
||||
APP_NAME,
|
||||
DEBUG
|
||||
} = getEnvConfig()
|
||||
+55
-53
@@ -1,97 +1,108 @@
|
||||
import { defineConfig, type UserConfigExport } from '@tarojs/cli'
|
||||
import TsconfigPathsPlugin from 'tsconfig-paths-webpack-plugin'
|
||||
import { UnifiedWebpackPluginV5 } from 'weapp-tailwindcss/webpack'
|
||||
import devConfig from './dev'
|
||||
import prodConfig from './prod'
|
||||
import { getEnvConfig } from './env'
|
||||
|
||||
// import vitePluginImp from 'vite-plugin-imp'
|
||||
// https://taro-docs.jd.com/docs/next/config#defineconfig-辅助函数
|
||||
export default defineConfig<'webpack5'>(async (merge, {}) => {
|
||||
import path from 'path'
|
||||
import { getEnvConfig } from './env.js'
|
||||
|
||||
// https://taro-docs.jd.com/docs/next/config
|
||||
export default defineConfig<'webpack5'>(async (merge, { mode }) => {
|
||||
const baseConfig: UserConfigExport<'webpack5'> = {
|
||||
projectName: 'websoft-react',
|
||||
date: '2024-12-30',
|
||||
plugins: ['@tarojs/plugin-html'],
|
||||
designWidth (input:any) {
|
||||
// 配置 NutUI 375 尺寸
|
||||
if (input?.file?.replace(/\\+/g, '/').indexOf('@nutui') > -1) {
|
||||
return 375
|
||||
}
|
||||
// 全局使用 Taro 默认的 750 尺寸
|
||||
return 750
|
||||
},
|
||||
// 项目配置
|
||||
projectName: 'shop-taro',
|
||||
date: '2026-5-10',
|
||||
designWidth: 750,
|
||||
deviceRatio: {
|
||||
640: 2.34 / 2,
|
||||
750: 1,
|
||||
828: 1.81 / 2,
|
||||
375: 2 / 1
|
||||
828: 1.81 / 2
|
||||
},
|
||||
sourceRoot: 'src',
|
||||
outputRoot: 'dist',
|
||||
plugins: [
|
||||
'@tarojs/plugin-platform-weapp',
|
||||
'@tarojs/plugin-platform-h5'
|
||||
],
|
||||
|
||||
// 编译配置
|
||||
defineConstants: {
|
||||
API_BASE_URL: JSON.stringify(getEnvConfig().API_BASE_URL),
|
||||
SERVER_API_URL: JSON.stringify(getEnvConfig().SERVER_API_URL),
|
||||
APP_NAME: JSON.stringify(getEnvConfig().APP_NAME),
|
||||
DEBUG: JSON.stringify(getEnvConfig().DEBUG)
|
||||
DEBUG: JSON.stringify(getEnvConfig().DEBUG),
|
||||
},
|
||||
copy: {
|
||||
patterns: [
|
||||
],
|
||||
options: {
|
||||
}
|
||||
patterns: [],
|
||||
options: {}
|
||||
},
|
||||
framework: 'react',
|
||||
compiler: {
|
||||
|
||||
type: 'webpack5',
|
||||
prebundle: {
|
||||
exclude: ['@nutui/nutui-react-taro', '@nutui/icons-react-taro'],
|
||||
enable: false
|
||||
}
|
||||
},
|
||||
// 开启持久化缓存 - 有效提升二次编译速度
|
||||
cache: {
|
||||
enable: false // Webpack 持久化缓存配置,建议开启。默认配置请参考:https://docs.taro.zone/docs/config-detail#cache
|
||||
enable: false,
|
||||
type: 'filesystem',
|
||||
cacheDirectory: path.resolve(__dirname, '../.webpack-cache'),
|
||||
buildDependencies: {
|
||||
config: [__filename]
|
||||
}
|
||||
},
|
||||
|
||||
// 小程序配置
|
||||
mini: {
|
||||
postcss: {
|
||||
pxtransform: {
|
||||
enable: true,
|
||||
config: {}
|
||||
},
|
||||
url: {
|
||||
enable: true,
|
||||
config: {
|
||||
selectorBlackList: ['nut-']
|
||||
limit: 1024
|
||||
}
|
||||
},
|
||||
cssModules: {
|
||||
enable: false, // 默认为 false,如需使用 css modules 功能,则设为 true
|
||||
enable: false,
|
||||
config: {
|
||||
namingPattern: 'module', // 转换模式,取值为 global/module
|
||||
namingPattern: 'module',
|
||||
generateScopedName: '[name]__[local]___[hash:base64:5]'
|
||||
}
|
||||
}
|
||||
},
|
||||
webpackChain(chain) {
|
||||
chain.resolve.plugin('tsconfig-paths').use(TsconfigPathsPlugin)
|
||||
// weapp-tailwindcss: 自动处理 TailwindCSS 与 WXSS 的兼容性
|
||||
chain.merge({
|
||||
plugin: {
|
||||
'weapp-tailwindcss': {
|
||||
plugin: UnifiedWebpackPluginV5,
|
||||
args: [{
|
||||
appType: 'taro',
|
||||
rem2rpx: true,
|
||||
}]
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
// H5 配置
|
||||
h5: {
|
||||
publicPath: '/',
|
||||
staticDirectory: 'static',
|
||||
output: {
|
||||
filename: 'js/[name].[hash:8].js',
|
||||
chunkFilename: 'js/[name].[chunkhash:8].js'
|
||||
},
|
||||
miniCssExtractPluginOption: {
|
||||
ignoreOrder: true,
|
||||
filename: 'css/[name].[hash].css',
|
||||
chunkFilename: 'css/[name].[chunkhash].css'
|
||||
},
|
||||
postcss: {
|
||||
autoprefixer: {
|
||||
enable: true,
|
||||
config: {}
|
||||
enable: true
|
||||
},
|
||||
cssModules: {
|
||||
enable: false, // 默认为 false,如需使用 css modules 功能,则设为 true
|
||||
enable: false,
|
||||
config: {
|
||||
namingPattern: 'module', // 转换模式,取值为 global/module
|
||||
namingPattern: 'module',
|
||||
generateScopedName: '[name]__[local]___[hash:base64:5]'
|
||||
}
|
||||
}
|
||||
@@ -100,19 +111,10 @@ export default defineConfig<'webpack5'>(async (merge, {}) => {
|
||||
chain.resolve.plugin('tsconfig-paths').use(TsconfigPathsPlugin)
|
||||
}
|
||||
},
|
||||
rn: {
|
||||
appName: 'taroDemo',
|
||||
postcss: {
|
||||
cssModules: {
|
||||
enable: false, // 默认为 false,如需使用 css modules 功能,则设为 true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
// 本地开发构建配置(不混淆压缩)
|
||||
|
||||
if (mode === 'development') {
|
||||
return merge({}, baseConfig, devConfig)
|
||||
}
|
||||
// 生产构建配置(默认开启压缩混淆等)
|
||||
return merge({}, baseConfig, prodConfig)
|
||||
})
|
||||
|
||||
+10
-34
@@ -1,36 +1,12 @@
|
||||
import type { UserConfigExport } from "@tarojs/cli";
|
||||
export default {
|
||||
mini: {
|
||||
miniCssExtractPluginOption: {
|
||||
ignoreOrder: true
|
||||
}
|
||||
env: {
|
||||
NODE_ENV: '"production"'
|
||||
},
|
||||
h5: {
|
||||
/**
|
||||
* WebpackChain 插件配置
|
||||
* @docs https://github.com/neutrinojs/webpack-chain
|
||||
*/
|
||||
// webpackChain (chain) {
|
||||
// /**
|
||||
// * 如果 h5 端编译后体积过大,可以使用 webpack-bundle-analyzer 插件对打包体积进行分析。
|
||||
// * @docs https://github.com/webpack-contrib/webpack-bundle-analyzer
|
||||
// */
|
||||
// chain.plugin('analyzer')
|
||||
// .use(require('webpack-bundle-analyzer').BundleAnalyzerPlugin, [])
|
||||
// /**
|
||||
// * 如果 h5 端首屏加载时间过长,可以使用 prerender-spa-plugin 插件预加载首页。
|
||||
// * @docs https://github.com/chrisvfritz/prerender-spa-plugin
|
||||
// */
|
||||
// const path = require('path')
|
||||
// const Prerender = require('prerender-spa-plugin')
|
||||
// const staticDir = path.join(__dirname, '..', 'dist')
|
||||
// chain
|
||||
// .plugin('prerender')
|
||||
// .use(new Prerender({
|
||||
// staticDir,
|
||||
// routes: [ '/pages/index/index' ],
|
||||
// postProcess: (context) => ({ ...context, outputPath: path.join(staticDir, 'index.html') })
|
||||
// }))
|
||||
// }
|
||||
}
|
||||
} satisfies UserConfigExport<'webpack5'>
|
||||
defineConstants: {
|
||||
__DEV__: JSON.stringify(false)
|
||||
},
|
||||
mini: {
|
||||
compressTemplate: true
|
||||
},
|
||||
h5: {}
|
||||
}
|
||||
|
||||
@@ -1,54 +0,0 @@
|
||||
export type ProductMatrixItem = {
|
||||
title: string
|
||||
recommend?: boolean
|
||||
desc: string
|
||||
tags: string[]
|
||||
adminUrl: string
|
||||
}
|
||||
|
||||
// Keep in sync with PC (nuxt4-10398) homepage "产品矩阵".
|
||||
export const productMatrix: ProductMatrixItem[] = [
|
||||
{
|
||||
title: '企业官网',
|
||||
recommend: true,
|
||||
desc: '品牌展示与获客转化,支持多模板、多语言、SEO 与可视化配置。',
|
||||
tags: ['模板', 'SEO', '多语言', '私有化'],
|
||||
adminUrl: 'https://site.websoft.top'
|
||||
},
|
||||
{
|
||||
title: '小程序/公众号',
|
||||
recommend: false,
|
||||
desc: '多端渠道接入与统一管理,适配常见内容与电商场景。',
|
||||
tags: ['多端', '渠道', '可扩展'],
|
||||
adminUrl: 'https://mp.websoft.top'
|
||||
},
|
||||
{
|
||||
title: '电商系统',
|
||||
recommend: true,
|
||||
desc: '商品/订单/支付/营销基础能力,插件化扩展,支持多端触达。',
|
||||
tags: ['支付', '插件', '营销', '多租户'],
|
||||
adminUrl: 'https://shop.websoft.top'
|
||||
},
|
||||
{
|
||||
title: '管理后台',
|
||||
recommend: false,
|
||||
desc: '多租户管理、角色权限、组织架构与可扩展菜单体系。',
|
||||
tags: ['权限', '多租户', '审计'],
|
||||
adminUrl: 'https://oa.websoft.top'
|
||||
},
|
||||
{
|
||||
title: '开发者中心',
|
||||
recommend: false,
|
||||
desc: '应用开发与交付入口:应用中心、源码仓库、Git 账号绑定、权限申请与教程文档。',
|
||||
tags: ['应用', '源码', 'Git', '教程'],
|
||||
adminUrl: '/developer'
|
||||
},
|
||||
{
|
||||
title: '模板/插件市场',
|
||||
recommend: false,
|
||||
desc: '支持模板与插件购买、授权与更新,形成生态与增值体系。',
|
||||
tags: ['市场', '授权', '更新', '变现'],
|
||||
adminUrl: '/market'
|
||||
}
|
||||
]
|
||||
|
||||
@@ -1,445 +0,0 @@
|
||||
# 小程序改造方案 - 开发者中心 & 企业控制台
|
||||
|
||||
> 制定时间:2026-04-12
|
||||
> 项目中心:以「项目」为核心构建所有功能
|
||||
|
||||
---
|
||||
|
||||
## 一、整体架构设计
|
||||
|
||||
### 1.1 用户角色划分
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ 小程序端 │
|
||||
├─────────────────────────┬───────────────────────────────────┤
|
||||
│ 🛠️ 开发者角色 │ 🏢 企业客户角色 │
|
||||
│ (Developer) │ (Enterprise) │
|
||||
├─────────────────────────┼───────────────────────────────────┤
|
||||
│ • 拥有开发者权限的用户 │ • 拥有企业权限的用户 │
|
||||
│ • 可开发/发布应用 │ • 可管理企业应用、成员、账单 │
|
||||
│ • 独立于企业存在 │ • 属于某个企业组织 │
|
||||
│ │ • 可申请成为开发者 │
|
||||
└─────────────────────────┴───────────────────────────────────┘
|
||||
```
|
||||
|
||||
### 1.2 角色获取方式
|
||||
|
||||
| 角色 | 获取方式 | 说明 |
|
||||
|------|---------|------|
|
||||
| 开发者 | 申请审核制 | 用户主动申请 → 资料审核 → 获得开发者权限 |
|
||||
| 企业客户 | 购买产品 | 下单支付 → 自动开通企业账号 → 成为企业主 |
|
||||
|
||||
---
|
||||
|
||||
## 二、页面结构设计
|
||||
|
||||
### 2.1 入口设计
|
||||
|
||||
```
|
||||
首页 (pages/index/index)
|
||||
├── 产品展示区
|
||||
├── 快捷入口区
|
||||
│ ├── 🛠️ 开发者中心 (开发者角色入口)
|
||||
│ └── 🏢 企业控制台 (企业客户入口)
|
||||
└── 底部 TabBar (商城/购物车/我的)
|
||||
```
|
||||
|
||||
**注意**:两个入口对普通用户可见,但需要对应角色权限才能进入。
|
||||
|
||||
### 2.2 开发者中心 (developer/)
|
||||
|
||||
```
|
||||
developer/
|
||||
├── index.tsx # 开发者工作台
|
||||
│ ├── 我的项目列表
|
||||
│ ├── 快捷操作 (新建项目、查看文档)
|
||||
│ └── 开发者动态/通知
|
||||
│
|
||||
├── project/
|
||||
│ ├── index.tsx # 项目列表
|
||||
│ ├── create.tsx # 创建项目
|
||||
│ └── [id]/
|
||||
│ ├── index.tsx # 项目概览
|
||||
│ ├── settings.tsx # 项目设置
|
||||
│ ├── members.tsx # 项目成员
|
||||
│ ├── api-keys.tsx # API Key 管理
|
||||
│ ├── deployments.tsx # 部署记录
|
||||
│ └── logs.tsx # 运行日志
|
||||
│
|
||||
├── app/ # 应用管理
|
||||
│ ├── index.tsx # 应用列表
|
||||
│ ├── create.tsx # 创建应用
|
||||
│ └── [id]/
|
||||
│ ├── index.tsx # 应用详情
|
||||
│ ├── version.tsx # 版本管理
|
||||
│ ├── config.tsx # 应用配置
|
||||
│ └── publish.tsx # 发布管理
|
||||
│
|
||||
├── market/ # 开发者市场
|
||||
│ ├── index.tsx # 市场首页
|
||||
│ ├── templates.tsx # 模板市场
|
||||
│ └── plugins.tsx # 插件市场
|
||||
│
|
||||
├── orders/ # 开发订单 (已有)
|
||||
│ └── index.tsx
|
||||
│
|
||||
├── developer/ # 开发者中心
|
||||
│ ├── index.tsx # 开发者设置
|
||||
│ ├── profile.tsx # 个人资料
|
||||
│ ├── credentials.tsx # 开发者凭证
|
||||
│ └── notification.tsx # 通知设置
|
||||
│
|
||||
└── docs/ # 文档中心
|
||||
├── index.tsx # 文档首页
|
||||
├── quickstart.tsx # 快速开始
|
||||
├── api-docs.tsx # API 文档
|
||||
└── sdk-download.tsx # SDK 下载
|
||||
```
|
||||
|
||||
### 2.3 企业控制台 (enterprise/)
|
||||
|
||||
```
|
||||
enterprise/
|
||||
├── index.tsx # 企业工作台
|
||||
│ ├── 企业概览仪表盘
|
||||
│ ├── 快捷入口 (应用/成员/账单)
|
||||
│ └── 最新动态
|
||||
│
|
||||
├── apps/ # 企业应用
|
||||
│ ├── index.tsx # 应用列表
|
||||
│ ├── [id]/
|
||||
│ │ ├── index.tsx # 应用详情
|
||||
│ │ ├── monitor.tsx # 运营监控
|
||||
│ │ ├── analytics.tsx # 数据分析
|
||||
│ │ └── settings.tsx # 应用设置
|
||||
│ └── purchase.tsx # 购买应用
|
||||
│
|
||||
├── members/ # 成员管理
|
||||
│ ├── index.tsx # 成员列表
|
||||
│ ├── invite.tsx # 邀请成员
|
||||
│ ├── roles.tsx # 角色权限
|
||||
│ └── audit.tsx # 权限审批
|
||||
│
|
||||
├── orders/ # 订单账单
|
||||
│ ├── index.tsx # 订单列表
|
||||
│ ├── detail.tsx # 订单详情
|
||||
│ ├── invoice.tsx # 发票管理
|
||||
│ └── bills.tsx # 账单明细
|
||||
│
|
||||
├── billing/ # 费用中心
|
||||
│ ├── index.tsx # 费用概览
|
||||
│ ├── consumption.tsx # 消费明细
|
||||
│ ├── recharge.tsx # 充值中心
|
||||
│ └── coupons.tsx # 优惠券
|
||||
│
|
||||
├── enterprise/ # 企业设置
|
||||
│ ├── index.tsx # 企业设置
|
||||
│ ├── info.tsx # 企业信息
|
||||
│ ├── domain.tsx # 域名配置
|
||||
│ ├── security.tsx # 安全设置
|
||||
│ └── notification.tsx # 通知设置
|
||||
│
|
||||
└── developer/ # 开发者入口
|
||||
├── index.tsx # 开发者介绍
|
||||
└── apply.tsx # 申请开发者
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 三、核心功能设计
|
||||
|
||||
### 3.1 项目管理 (项目中心)
|
||||
|
||||
**项目类型**:
|
||||
| 类型 | 说明 | 适用角色 |
|
||||
|------|------|---------|
|
||||
| 基础项目 | 免费,限制功能 | 所有开发者 |
|
||||
| 专业项目 | 付费,功能完整 | 开发者/企业 |
|
||||
| 企业项目 | 全功能,支持团队协作 | 企业 |
|
||||
|
||||
**项目核心属性**:
|
||||
```typescript
|
||||
interface Project {
|
||||
id: string
|
||||
name: string // 项目名称
|
||||
type: 'basic' | 'pro' | 'enterprise'
|
||||
owner: string // 所有者 (开发者ID 或 企业ID)
|
||||
ownerType: 'developer' | 'enterprise'
|
||||
description?: string // 项目描述
|
||||
icon?: string // 项目图标
|
||||
status: 'active' | 'suspended' | 'archived'
|
||||
createdAt: string
|
||||
updatedAt: string
|
||||
|
||||
// 关联信息
|
||||
apps: string[] // 关联的应用
|
||||
members: ProjectMember[] // 项目成员
|
||||
apiKeys: ApiKey[] // API Key
|
||||
|
||||
// 统计
|
||||
appCount: number
|
||||
memberCount: number
|
||||
apiCallCount: number // API 调用次数
|
||||
storageUsed: number // 存储使用量
|
||||
}
|
||||
```
|
||||
|
||||
### 3.2 开发者角色功能
|
||||
|
||||
**权限项**:
|
||||
| 权限 | 说明 | 默认 |
|
||||
|------|------|------|
|
||||
| project.create | 创建项目 | ✅ |
|
||||
| project.edit | 编辑项目 | ✅ |
|
||||
| project.delete | 删除项目 | ✅ |
|
||||
| app.create | 创建应用 | ✅ |
|
||||
| app.publish | 发布应用 | ✅ |
|
||||
| apiKey.manage | 管理 API Key | ✅ |
|
||||
| market.access | 访问市场 | ✅ |
|
||||
| docs.access | 访问文档 | ✅ |
|
||||
|
||||
**开发者特有功能**:
|
||||
- ✅ 创建和管理个人项目
|
||||
- ✅ 开发、测试、发布应用
|
||||
- ✅ 访问模板/插件市场
|
||||
- ✅ 生成和管理 API Key
|
||||
- ✅ 查看开发者文档和 SDK
|
||||
- ✅ 提交工单和技术支持
|
||||
|
||||
### 3.3 企业客户角色功能
|
||||
|
||||
**权限项**:
|
||||
| 权限 | 说明 | 适用角色 |
|
||||
|------|------|------|
|
||||
| enterprise.manage | 企业管理 | 企业主 |
|
||||
| app.manage | 应用管理 | 企业主/管理员 |
|
||||
| member.invite | 邀请成员 | 企业主/管理员 |
|
||||
| member.role | 分配角色 | 企业主/管理员 |
|
||||
| order.view | 查看订单 | 所有成员 |
|
||||
| billing.view | 查看账单 | 企业主/财务 |
|
||||
| billing.pay | 支付充值 | 企业主/财务 |
|
||||
|
||||
**企业特有功能**:
|
||||
- ✅ 企业仪表盘和数据概览
|
||||
- ✅ 应用运营监控
|
||||
- ✅ 成员邀请和权限管理
|
||||
- ✅ 订单管理和发票
|
||||
- ✅ 费用中心和充值
|
||||
- ✅ 企业信息配置
|
||||
|
||||
### 3.4 角色切换机制
|
||||
|
||||
```
|
||||
用户登录
|
||||
│
|
||||
├── 开发者身份
|
||||
│ └── 可进入开发者中心
|
||||
│
|
||||
├── 企业身份
|
||||
│ └── 可进入企业控制台
|
||||
│
|
||||
└── 两者皆有
|
||||
└── 可切换身份入口
|
||||
├── [开发者] 🛠️ 开发者中心
|
||||
└── [企业] 🏢 企业控制台
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 四、开发优先级与里程碑
|
||||
|
||||
### Phase 1:基础框架 ⭐ 必须上线
|
||||
|
||||
**目标**:建立核心框架,实现最小可用产品
|
||||
|
||||
| 模块 | 页面 | 工作量 | 优先级 |
|
||||
|------|------|--------|--------|
|
||||
| 入口设计 | 首页快捷入口 + 身份切换 | ⭐⭐ | P0 |
|
||||
| 开发者工作台 | 开发者首页/概览 | ⭐⭐⭐ | P0 |
|
||||
| 项目管理 | 项目列表 + 创建 | ⭐⭐⭐⭐ | P0 |
|
||||
| 应用管理 | 应用列表 + 创建 | ⭐⭐⭐⭐ | P0 |
|
||||
| 企业工作台 | 企业首页/概览 | ⭐⭐⭐ | P0 |
|
||||
| 企业应用 | 应用列表 + 详情 | ⭐⭐⭐⭐ | P0 |
|
||||
|
||||
### Phase 2:核心功能 ⭐ 必须上线
|
||||
|
||||
**目标**:完成核心业务闭环
|
||||
|
||||
| 模块 | 页面 | 工作量 | 优先级 |
|
||||
|------|------|--------|--------|
|
||||
| API Key | 生成/查看/禁用 | ⭐⭐⭐ | P0 |
|
||||
| 项目成员 | 邀请/移除/角色 | ⭐⭐⭐ | P0 |
|
||||
| 应用发布 | 版本管理/发布 | ⭐⭐⭐ | P0 |
|
||||
| 企业成员 | 列表/邀请/角色 | ⭐⭐⭐⭐ | P0 |
|
||||
| 订单管理 | 列表/详情 | ⭐⭐⭐ | P0 |
|
||||
| 账单查看 | 消费明细 | ⭐⭐⭐ | P0 |
|
||||
|
||||
### Phase 3:增强功能 🔄 后续迭代
|
||||
|
||||
| 模块 | 页面 | 工作量 | 优先级 |
|
||||
|------|------|--------|--------|
|
||||
| 开发者申请 | 申请流程/审核 | ⭐⭐⭐ | P1 |
|
||||
| 权限审批 | 权限申请/审批 | ⭐⭐⭐ | P1 |
|
||||
| 市场浏览 | 模板/插件市场 | ⭐⭐⭐⭐ | P1 |
|
||||
| 运营监控 | 数据看板 | ⭐⭐⭐⭐ | P1 |
|
||||
| 开发者文档 | API文档/SDK | ⭐⭐⭐⭐ | P1 |
|
||||
| 消息通知 | 通知中心 | ⭐⭐⭐ | P1 |
|
||||
|
||||
### Phase 4:高级功能 🔄 后续迭代
|
||||
|
||||
| 模块 | 页面 | 工作量 | 优先级 |
|
||||
|------|------|--------|--------|
|
||||
| CI/CD | 流水线管理 | ⭐⭐⭐⭐⭐ | P2 |
|
||||
| 部署管理 | 一键部署 | ⭐⭐⭐⭐ | P2 |
|
||||
| 数据分析 | 高级统计 | ⭐⭐⭐⭐ | P2 |
|
||||
| 工单系统 | 技术支持 | ⭐⭐⭐ | P2 |
|
||||
| 发票管理 | 电子发票 | ⭐⭐⭐ | P2 |
|
||||
| SSO | 单点登录 | ⭐⭐⭐⭐⭐ | P2 |
|
||||
|
||||
---
|
||||
|
||||
## 五、与现有模块的关系
|
||||
|
||||
### 5.1 独立开发,不依赖现有模块
|
||||
|
||||
| 新建模块 | 说明 |
|
||||
|---------|------|
|
||||
| developer/ | 开发者中心全套功能 |
|
||||
| enterprise/ | 企业控制台全套功能 |
|
||||
|
||||
### 5.2 共享基础设施
|
||||
|
||||
| 共享内容 | 说明 |
|
||||
|---------|------|
|
||||
| 用户认证 | 复用 passport 模块的登录体系 |
|
||||
| 用户状态 | 复用 globalData/storage 中的用户信息 |
|
||||
| 消息通知 | 可复用 notification 组件 |
|
||||
| 基础 UI | 复用组件库样式 |
|
||||
|
||||
### 5.3 不复用模块
|
||||
|
||||
- ❌ 不复用 shop/ (商城) - 独立的企业应用体系
|
||||
- ❌ 不复用 dealer/ (分销) - 独立的开发者体系
|
||||
- ❌ 不复用 user/order (用户订单) - 使用新的企业订单体系
|
||||
|
||||
---
|
||||
|
||||
## 六、技术实现建议
|
||||
|
||||
### 6.1 目录结构
|
||||
|
||||
```
|
||||
src/
|
||||
├── developer/ # 🛠️ 开发者模块
|
||||
│ ├── index.tsx # 开发者入口
|
||||
│ ├── project/ # 项目管理
|
||||
│ ├── app/ # 应用管理
|
||||
│ ├── market/ # 市场
|
||||
│ └── docs/ # 文档
|
||||
│
|
||||
├── enterprise/ # 🏢 企业模块
|
||||
│ ├── index.tsx # 企业入口
|
||||
│ ├── apps/ # 企业应用
|
||||
│ ├── members/ # 成员管理
|
||||
│ ├── orders/ # 订单账单
|
||||
│ ├── billing/ # 费用中心
|
||||
│ └── settings/ # 企业设置
|
||||
│
|
||||
└── shared/ # 共享
|
||||
├── components/ # 共享组件
|
||||
├── types/ # 类型定义
|
||||
└── api/ # API 接口
|
||||
```
|
||||
|
||||
### 6.2 路由配置
|
||||
|
||||
```typescript
|
||||
// app.config.ts
|
||||
export default {
|
||||
subpackages: [
|
||||
{
|
||||
root: "developer",
|
||||
pages: [
|
||||
"index",
|
||||
"project/index",
|
||||
"project/create",
|
||||
"project/[id]/index",
|
||||
"project/[id]/settings",
|
||||
"app/index",
|
||||
"app/create",
|
||||
"app/[id]/index",
|
||||
// ...
|
||||
]
|
||||
},
|
||||
{
|
||||
root: "enterprise",
|
||||
pages: [
|
||||
"index",
|
||||
"apps/index",
|
||||
"apps/[id]/index",
|
||||
"members/index",
|
||||
"members/invite",
|
||||
"orders/index",
|
||||
"orders/detail",
|
||||
// ...
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 七、API 接口设计建议
|
||||
|
||||
### 7.1 项目相关
|
||||
|
||||
| 接口 | 方法 | 说明 |
|
||||
|------|------|------|
|
||||
| /api/project/list | GET | 项目列表 |
|
||||
| /api/project/create | POST | 创建项目 |
|
||||
| /api/project/:id | GET/PUT/DELETE | 项目 CRUD |
|
||||
| /api/project/:id/members | GET/POST/DELETE | 项目成员 |
|
||||
| /api/project/:id/keys | GET/POST/DELETE | API Key |
|
||||
|
||||
### 7.2 应用相关
|
||||
|
||||
| 接口 | 方法 | 说明 |
|
||||
|------|------|------|
|
||||
| /api/app/list | GET | 应用列表 |
|
||||
| /api/app/create | POST | 创建应用 |
|
||||
| /api/app/:id | GET/PUT/DELETE | 应用 CRUD |
|
||||
| /api/app/:id/version | POST | 发布版本 |
|
||||
|
||||
### 7.3 企业相关
|
||||
|
||||
| 接口 | 方法 | 说明 |
|
||||
|------|------|------|
|
||||
| /api/enterprise/info | GET | 企业信息 |
|
||||
| /api/enterprise/members | GET/POST | 成员管理 |
|
||||
| /api/enterprise/roles | GET/POST | 角色管理 |
|
||||
| /api/enterprise/orders | GET | 订单列表 |
|
||||
| /api/enterprise/bills | GET | 账单明细 |
|
||||
|
||||
---
|
||||
|
||||
## 八、下一步行动
|
||||
|
||||
### 立即开始 (Phase 1)
|
||||
|
||||
1. ✅ 创建 developer/ 和 enterprise/ 目录结构
|
||||
2. ✅ 设计 API 接口文档 (与后端对齐)
|
||||
3. ✅ 开发首页入口组件
|
||||
4. ✅ 开发工作台基础页面
|
||||
|
||||
### 需要确认
|
||||
|
||||
- [ ] 后端 API 是否已准备好?
|
||||
- [ ] 数据库表结构是否已设计?
|
||||
- [ ] 是否需要支持微信小程序码扫码登录?
|
||||
|
||||
---
|
||||
|
||||
**文档版本**:v1.0
|
||||
**最后更新**:2026-04-12
|
||||
@@ -1,14 +0,0 @@
|
||||
const { getDefaultConfig, mergeConfig } = require('@react-native/metro-config')
|
||||
const { getMetroConfig } = require('@tarojs/rn-supporter')
|
||||
|
||||
/**
|
||||
* Metro configuration
|
||||
* https://facebook.github.io/metro/docs/configuration
|
||||
*
|
||||
* @type {import('metro-config').MetroConfig}
|
||||
*/
|
||||
const config = {}
|
||||
|
||||
module.exports = (async function () {
|
||||
return mergeConfig(getDefaultConfig(__dirname), await getMetroConfig(), config)
|
||||
})()
|
||||
+316
@@ -0,0 +1,316 @@
|
||||
{
|
||||
"models": [
|
||||
{
|
||||
"id": "auto",
|
||||
"name": "AuTo",
|
||||
"vendor": "Custom",
|
||||
"url": "https://api.getaigate.com/v1/chat/completions",
|
||||
"apiKey": "sk-aigate-2335f1670e169eb855c88f6ed58862cb",
|
||||
"maxInputTokens": 128000,
|
||||
"maxOutputTokens": 32000,
|
||||
"supportsToolCall": true,
|
||||
"supportsImages": true,
|
||||
"supportsReasoning": true,
|
||||
"useCustomProtocol": false
|
||||
},
|
||||
{
|
||||
"id": "deepseek-v4-pro",
|
||||
"name": "Deepseek-v4-pro",
|
||||
"vendor": "Custom",
|
||||
"url": "https://api.getaigate.com/v1/chat/completions",
|
||||
"apiKey": "sk-aigate-2335f1670e169eb855c88f6ed58862cb",
|
||||
"maxInputTokens": 1000000,
|
||||
"maxOutputTokens": 32000,
|
||||
"supportsToolCall": true,
|
||||
"supportsImages": true,
|
||||
"supportsReasoning": true,
|
||||
"useCustomProtocol": false
|
||||
},
|
||||
{
|
||||
"id": "deepseek-v4-flash",
|
||||
"name": "Deepseek-v4-flash",
|
||||
"vendor": "Custom",
|
||||
"url": "https://api.getaigate.com/v1/chat/completions",
|
||||
"apiKey": "sk-aigate-2335f1670e169eb855c88f6ed58862cb",
|
||||
"maxInputTokens": 1000000,
|
||||
"maxOutputTokens": 32000,
|
||||
"supportsToolCall": true,
|
||||
"supportsImages": true,
|
||||
"supportsReasoning": true,
|
||||
"useCustomProtocol": false
|
||||
},
|
||||
{
|
||||
"id": "deepseek-v3-2-volc",
|
||||
"name": "Deepseek-v3-2-volc",
|
||||
"vendor": "Custom",
|
||||
"url": "https://api.getaigate.com/v1/chat/completions",
|
||||
"apiKey": "sk-aigate-2335f1670e169eb855c88f6ed58862cb",
|
||||
"maxInputTokens": 128000,
|
||||
"maxOutputTokens": 32000,
|
||||
"supportsToolCall": true,
|
||||
"supportsImages": true,
|
||||
"supportsReasoning": true,
|
||||
"useCustomProtocol": false
|
||||
},
|
||||
{
|
||||
"id": "deepseek-v3-1",
|
||||
"name": "Deepseek-v3-1",
|
||||
"vendor": "Custom",
|
||||
"url": "https://api.getaigate.com/v1/chat/completions",
|
||||
"apiKey": "sk-aigate-2335f1670e169eb855c88f6ed58862cb",
|
||||
"maxInputTokens": 128000,
|
||||
"maxOutputTokens": 32000,
|
||||
"supportsToolCall": true,
|
||||
"supportsImages": true,
|
||||
"supportsReasoning": true,
|
||||
"useCustomProtocol": false
|
||||
},
|
||||
{
|
||||
"id": "deepseek-v3-0324",
|
||||
"name": "Deepseek-v3-0324",
|
||||
"vendor": "Custom",
|
||||
"url": "https://api.getaigate.com/v1/chat/completions",
|
||||
"apiKey": "sk-aigate-2335f1670e169eb855c88f6ed58862cb",
|
||||
"maxInputTokens": 128000,
|
||||
"maxOutputTokens": 32000,
|
||||
"supportsToolCall": true,
|
||||
"supportsImages": true,
|
||||
"supportsReasoning": true,
|
||||
"useCustomProtocol": false
|
||||
},
|
||||
{
|
||||
"id": "deepseek-r1",
|
||||
"name": "Deepseek-r1",
|
||||
"vendor": "Custom",
|
||||
"url": "https://api.getaigate.com/v1/chat/completions",
|
||||
"apiKey": "sk-aigate-2335f1670e169eb855c88f6ed58862cb",
|
||||
"maxInputTokens": 128000,
|
||||
"maxOutputTokens": 32000,
|
||||
"supportsToolCall": true,
|
||||
"supportsImages": true,
|
||||
"supportsReasoning": true,
|
||||
"useCustomProtocol": false
|
||||
},
|
||||
{
|
||||
"id": "glm-5.2",
|
||||
"name": "GLM-5.2",
|
||||
"vendor": "Custom",
|
||||
"url": "https://api.getaigate.com/v1/chat/completions",
|
||||
"apiKey": "sk-aigate-2335f1670e169eb855c88f6ed58862cb",
|
||||
"maxInputTokens": 1000000,
|
||||
"maxOutputTokens": 128000,
|
||||
"supportsToolCall": true,
|
||||
"supportsImages": true,
|
||||
"supportsReasoning": true,
|
||||
"useCustomProtocol": false
|
||||
},
|
||||
{
|
||||
"id": "glm-5.1",
|
||||
"name": "GLM-5.1",
|
||||
"vendor": "Custom",
|
||||
"url": "https://api.getaigate.com/v1/chat/completions",
|
||||
"apiKey": "sk-aigate-2335f1670e169eb855c88f6ed58862cb",
|
||||
"maxInputTokens": 200000,
|
||||
"maxOutputTokens": 128000,
|
||||
"supportsToolCall": true,
|
||||
"supportsImages": true,
|
||||
"supportsReasoning": true,
|
||||
"useCustomProtocol": false
|
||||
},
|
||||
{
|
||||
"id": "glm-5.0",
|
||||
"name": "GLM-5.0",
|
||||
"vendor": "Custom",
|
||||
"url": "https://api.getaigate.com/v1/chat/completions",
|
||||
"apiKey": "sk-aigate-2335f1670e169eb855c88f6ed58862cb",
|
||||
"maxInputTokens": 200000,
|
||||
"maxOutputTokens": 32000,
|
||||
"supportsToolCall": true,
|
||||
"supportsImages": true,
|
||||
"supportsReasoning": true,
|
||||
"useCustomProtocol": false
|
||||
},
|
||||
{
|
||||
"id": "glm-5.0-turbo",
|
||||
"name": "GLM-5.0-Turbo",
|
||||
"vendor": "Custom",
|
||||
"url": "https://api.getaigate.com/v1/chat/completions",
|
||||
"apiKey": "sk-aigate-2335f1670e169eb855c88f6ed58862cb",
|
||||
"maxInputTokens": 200000,
|
||||
"maxOutputTokens": 32000,
|
||||
"supportsToolCall": true,
|
||||
"supportsImages": true,
|
||||
"supportsReasoning": true,
|
||||
"useCustomProtocol": false
|
||||
},
|
||||
{
|
||||
"id": "glm-5v-turbo",
|
||||
"name": "GLM-5v-Turbo",
|
||||
"vendor": "Custom",
|
||||
"url": "https://api.getaigate.com/v1/chat/completions",
|
||||
"apiKey": "sk-aigate-2335f1670e169eb855c88f6ed58862cb",
|
||||
"maxInputTokens": 200000,
|
||||
"maxOutputTokens": 32000,
|
||||
"supportsToolCall": true,
|
||||
"supportsImages": true,
|
||||
"supportsReasoning": true,
|
||||
"useCustomProtocol": false
|
||||
},
|
||||
{
|
||||
"id": "glm-4.7",
|
||||
"name": "GLM-4.7",
|
||||
"vendor": "Custom",
|
||||
"url": "https://api.getaigate.com/v1/chat/completions",
|
||||
"apiKey": "sk-aigate-2335f1670e169eb855c88f6ed58862cb",
|
||||
"maxInputTokens": 128000,
|
||||
"maxOutputTokens": 32000,
|
||||
"supportsToolCall": true,
|
||||
"supportsImages": true,
|
||||
"supportsReasoning": true,
|
||||
"useCustomProtocol": false
|
||||
},
|
||||
{
|
||||
"id": "glm-4.6",
|
||||
"name": "GLM-4.6",
|
||||
"vendor": "Custom",
|
||||
"url": "https://api.getaigate.com/v1/chat/completions",
|
||||
"apiKey": "sk-aigate-2335f1670e169eb855c88f6ed58862cb",
|
||||
"maxInputTokens": 128000,
|
||||
"maxOutputTokens": 32000,
|
||||
"supportsToolCall": true,
|
||||
"supportsImages": true,
|
||||
"supportsReasoning": true,
|
||||
"useCustomProtocol": false
|
||||
},
|
||||
{
|
||||
"id": "minimax-m3",
|
||||
"name": "Minimax-M3",
|
||||
"vendor": "Custom",
|
||||
"url": "https://api.getaigate.com/v1/chat/completions",
|
||||
"apiKey": "sk-aigate-2335f1670e169eb855c88f6ed58862cb",
|
||||
"maxInputTokens": 1000000,
|
||||
"maxOutputTokens": 32000,
|
||||
"supportsToolCall": true,
|
||||
"supportsImages": true,
|
||||
"supportsReasoning": true,
|
||||
"useCustomProtocol": false
|
||||
},
|
||||
{
|
||||
"id": "minimax-m2.7",
|
||||
"name": "Minimax-M2.7",
|
||||
"vendor": "Custom",
|
||||
"url": "https://api.getaigate.com/v1/chat/completions",
|
||||
"apiKey": "sk-aigate-2335f1670e169eb855c88f6ed58862cb",
|
||||
"maxInputTokens": 128000,
|
||||
"maxOutputTokens": 32000,
|
||||
"supportsToolCall": true,
|
||||
"supportsImages": true,
|
||||
"supportsReasoning": true,
|
||||
"useCustomProtocol": false
|
||||
},
|
||||
{
|
||||
"id": "minimax-m2.5",
|
||||
"name": "Minimax-M2.5",
|
||||
"vendor": "Custom",
|
||||
"url": "https://api.getaigate.com/v1/chat/completions",
|
||||
"apiKey": "sk-aigate-2335f1670e169eb855c88f6ed58862cb",
|
||||
"maxInputTokens": 128000,
|
||||
"maxOutputTokens": 32000,
|
||||
"supportsToolCall": true,
|
||||
"supportsImages": true,
|
||||
"supportsReasoning": true,
|
||||
"useCustomProtocol": false
|
||||
},
|
||||
{
|
||||
"id": "kimi-k2.7-code",
|
||||
"name": "Kimi-K2.7-Code",
|
||||
"vendor": "Custom",
|
||||
"url": "https://api.getaigate.com/v1/chat/completions",
|
||||
"apiKey": "sk-aigate-2335f1670e169eb855c88f6ed58862cb",
|
||||
"maxInputTokens": 256000,
|
||||
"maxOutputTokens": 20000,
|
||||
"supportsToolCall": true,
|
||||
"supportsImages": true,
|
||||
"supportsReasoning": true,
|
||||
"useCustomProtocol": false
|
||||
},
|
||||
{
|
||||
"id": "kimi-k2.6",
|
||||
"name": "Kimi-K2.6",
|
||||
"vendor": "Custom",
|
||||
"url": "https://api.getaigate.com/v1/chat/completions",
|
||||
"apiKey": "sk-aigate-2335f1670e169eb855c88f6ed58862cb",
|
||||
"maxInputTokens": 256000,
|
||||
"maxOutputTokens": 32000,
|
||||
"supportsToolCall": true,
|
||||
"supportsImages": true,
|
||||
"supportsReasoning": true,
|
||||
"useCustomProtocol": false
|
||||
},
|
||||
{
|
||||
"id": "kimi-k2.5",
|
||||
"name": "Kimi-K2.5",
|
||||
"vendor": "Custom",
|
||||
"url": "https://api.getaigate.com/v1/chat/completions",
|
||||
"apiKey": "sk-aigate-2335f1670e169eb855c88f6ed58862cb",
|
||||
"maxInputTokens": 256000,
|
||||
"maxOutputTokens": 32000,
|
||||
"supportsToolCall": true,
|
||||
"supportsImages": true,
|
||||
"supportsReasoning": true,
|
||||
"useCustomProtocol": false
|
||||
},
|
||||
{
|
||||
"id": "hy3-preview",
|
||||
"name": "HY-3-Preview",
|
||||
"vendor": "Custom",
|
||||
"url": "https://api.getaigate.com/v1/chat/completions",
|
||||
"apiKey": "sk-aigate-2335f1670e169eb855c88f6ed58862cb",
|
||||
"maxInputTokens": 200000,
|
||||
"maxOutputTokens": 32000,
|
||||
"supportsToolCall": true,
|
||||
"supportsImages": true,
|
||||
"supportsReasoning": true,
|
||||
"useCustomProtocol": false
|
||||
},
|
||||
{
|
||||
"id": "hy3",
|
||||
"name": "HY-3",
|
||||
"vendor": "Custom",
|
||||
"url": "https://api.getaigate.com/v1/chat/completions",
|
||||
"apiKey": "sk-aigate-2335f1670e169eb855c88f6ed58862cb",
|
||||
"maxInputTokens": 256000,
|
||||
"maxOutputTokens": 192000,
|
||||
"supportsToolCall": true,
|
||||
"supportsImages": true,
|
||||
"supportsReasoning": true,
|
||||
"useCustomProtocol": false
|
||||
},
|
||||
{
|
||||
"id": "hunyuan-chat",
|
||||
"name": "HY-Chat",
|
||||
"vendor": "Custom",
|
||||
"url": "https://api.getaigate.com/v1/chat/completions",
|
||||
"apiKey": "sk-aigate-2335f1670e169eb855c88f6ed58862cb",
|
||||
"maxInputTokens": 128000,
|
||||
"maxOutputTokens": 32000,
|
||||
"supportsToolCall": true,
|
||||
"supportsImages": true,
|
||||
"supportsReasoning": true,
|
||||
"useCustomProtocol": false
|
||||
},
|
||||
{
|
||||
"id": "hunyuan-2.0-thinking",
|
||||
"name": "HY-2.0-Thinking",
|
||||
"vendor": "Custom",
|
||||
"url": "https://api.getaigate.com/v1/chat/completions",
|
||||
"apiKey": "sk-aigate-2335f1670e169eb855c88f6ed58862cb",
|
||||
"maxInputTokens": 200000,
|
||||
"maxOutputTokens": 32000,
|
||||
"supportsToolCall": true,
|
||||
"supportsImages": true,
|
||||
"supportsReasoning": true,
|
||||
"useCustomProtocol": false
|
||||
}
|
||||
]
|
||||
}
|
||||
+37
-55
@@ -1,14 +1,13 @@
|
||||
{
|
||||
"name": "template-5",
|
||||
"name": "websopy-taro",
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"description": "WebSoft Inc.",
|
||||
"templateInfo": {
|
||||
"name": "react-NutUI",
|
||||
"typescript": true,
|
||||
"css": "Sass",
|
||||
"framework": "React"
|
||||
},
|
||||
"description": "Taro + React + TypeScript 项目框架",
|
||||
"browserslist": [
|
||||
"last 3 versions",
|
||||
"Android >= 4.1",
|
||||
"ios >= 8"
|
||||
],
|
||||
"scripts": {
|
||||
"build:weapp": "taro build --type weapp",
|
||||
"build:swan": "taro build --type swan",
|
||||
@@ -28,81 +27,64 @@
|
||||
"dev:qq": "npm run build:qq -- --watch",
|
||||
"dev:jd": "npm run build:jd -- --watch",
|
||||
"dev:quickapp": "npm run build:quickapp -- --watch",
|
||||
"build:tailwind": "postcss --config tailwind.config.js -o ./dist/index.css ./src/app.css"
|
||||
"lint": "eslint src --fix --ext .ts,.tsx",
|
||||
"type-check": "tsc --noEmit"
|
||||
},
|
||||
"browserslist": [
|
||||
"last 3 versions",
|
||||
"Android >= 4.1",
|
||||
"ios >= 8"
|
||||
],
|
||||
"author": "",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.26.0",
|
||||
"@nutui/icons-react-taro": "^2.0.1",
|
||||
"@nutui/nutui-biz": "1.0.0-beta.2",
|
||||
"@nutui/nutui-react": "^3.0.16",
|
||||
"@nutui/nutui-react-taro": "^2.7.4",
|
||||
"@react-native/metro-config": "^0.73.2",
|
||||
"@tarojs/components": "4.0.8",
|
||||
"@tarojs/components-rn": "^4.1.4",
|
||||
"@tarojs/helper": "4.0.8",
|
||||
"@tarojs/plugin-framework-react": "4.0.8",
|
||||
"@tarojs/plugin-html": "4.0.8",
|
||||
"@tarojs/plugin-platform-alipay": "4.0.8",
|
||||
"@nutui/nutui-react-taro": "2.7.4",
|
||||
"@tarojs/components": "4.1.11",
|
||||
"@tarojs/plugin-framework-react": "4.1.11",
|
||||
"@tarojs/plugin-platform-h5": "4.0.8",
|
||||
"@tarojs/plugin-platform-jd": "4.0.8",
|
||||
"@tarojs/plugin-platform-qq": "4.0.8",
|
||||
"@tarojs/plugin-platform-swan": "4.0.8",
|
||||
"@tarojs/plugin-platform-tt": "4.0.8",
|
||||
"@tarojs/plugin-platform-weapp": "4.0.8",
|
||||
"@tarojs/react": "4.0.8",
|
||||
"@tarojs/rn-runner": "^4.1.4",
|
||||
"@tarojs/rn-supporter": "^4.1.4",
|
||||
"@tarojs/runtime": "4.0.8",
|
||||
"@tarojs/runtime-rn": "^4.1.4",
|
||||
"@tarojs/shared": "4.0.8",
|
||||
"@tarojs/taro": "4.0.8",
|
||||
"@tarojs/taro-rn": "^4.1.4",
|
||||
"@tarojs/plugin-platform-weapp": "4.1.11",
|
||||
"@tarojs/react": "4.1.11",
|
||||
"@tarojs/runtime": "4.1.11",
|
||||
"@tarojs/shared": "4.1.11",
|
||||
"@tarojs/taro": "4.1.11",
|
||||
"crypto-js": "^4.2.0",
|
||||
"dayjs": "^1.11.13",
|
||||
"echarts-taro3-react": "^1.0.13",
|
||||
"expo": "~50.0.2",
|
||||
"js-base64": "^3.7.7",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"react": "18.3.1",
|
||||
"react-dom": "18.3.1",
|
||||
"react-markdown": "^10.1.0",
|
||||
"react-native": "^0.73.1",
|
||||
"react-router-dom": "^7.1.1",
|
||||
"weapp-qrcode": "^1.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.26.0",
|
||||
"@babel/plugin-proposal-class-properties": "7.14.5",
|
||||
"@babel/preset-react": "^7.26.3",
|
||||
"@babel/plugin-proposal-class-properties": "^7.18.6",
|
||||
"@babel/preset-react": "^7.26.0",
|
||||
"@pmmmwh/react-refresh-webpack-plugin": "^0.5.15",
|
||||
"@tarojs/cli": "4.0.8",
|
||||
"@tarojs/taro-loader": "4.0.8",
|
||||
"@tarojs/webpack5-runner": "4.0.8",
|
||||
"@tarojs/cli": "4.1.11",
|
||||
"@tarojs/taro-loader": "4.1.11",
|
||||
"@tarojs/webpack5-runner": "4.1.11",
|
||||
"@types/node": "^18.19.68",
|
||||
"@types/react": "^18.3.18",
|
||||
"@types/react": "^18.3.12",
|
||||
"@types/webpack-env": "^1.18.5",
|
||||
"@typescript-eslint/eslint-plugin": "^6.21.0",
|
||||
"@typescript-eslint/parser": "^6.21.0",
|
||||
"@typescript-eslint/eslint-plugin": "^8.15.0",
|
||||
"@typescript-eslint/parser": "^8.15.0",
|
||||
"autoprefixer": "^10.4.20",
|
||||
"babel-plugin-import": "^1.13.8",
|
||||
"babel-preset-taro": "4.0.8",
|
||||
"eslint": "^8.57.1",
|
||||
"eslint": "^8.56.0",
|
||||
"eslint-config-taro": "4.0.8",
|
||||
"eslint-plugin-import": "^2.31.0",
|
||||
"eslint-plugin-react": "^7.37.3",
|
||||
"eslint-plugin-react": "^7.37.2",
|
||||
"eslint-plugin-react-hooks": "^4.6.2",
|
||||
"mysql2": "^3.22.3",
|
||||
"postcss": "^8.4.49",
|
||||
"react-refresh": "^0.11.0",
|
||||
"react-refresh": "^0.14.0",
|
||||
"stylelint": "^14.16.1",
|
||||
"tailwindcss": "^3.4.17",
|
||||
"tailwindcss": "3.4.17",
|
||||
"ts-node": "^10.9.2",
|
||||
"tsconfig-paths-webpack-plugin": "^4.2.0",
|
||||
"typescript": "^5.7.2",
|
||||
"typescript": "5.7.2",
|
||||
"weapp-tailwindcss": "^4.12.0",
|
||||
"webpack": "5.78.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
}
|
||||
|
||||
Generated
+5859
-10189
File diff suppressed because it is too large
Load Diff
@@ -1,46 +0,0 @@
|
||||
/**
|
||||
* PostCSS 插件:移除 WXSS 不兼容的 Tailwind CSS 规则
|
||||
*
|
||||
* 微信小程序 WXSS 不支持以下 CSS 特性:
|
||||
* 1. 反斜杠转义的特殊字符(如 .\!visible)—— !important 修饰符
|
||||
* 2. :not() 伪类 + ~ 兄弟选择器组合(如 .space-y-4 > :not([hidden]) ~ :not([hidden]))
|
||||
* 3. CSS 自定义属性 / CSS 变量(如 --tw-space-y-reverse)
|
||||
* 4. 嵌套 calc()(如 calc(0.5rem * calc(1 - var(--tw-space-y-reverse))))
|
||||
*
|
||||
* 该插件移除以下类型的 Tailwind CSS 规则:
|
||||
* - .\!xxx 类选择器(important 修饰符)
|
||||
* - .space-[x|y]-* 规则(间距布局,依赖 :not + ~ + CSS 变量 + 嵌套 calc)
|
||||
* - .divide-[x|y]-* 规则(分割线布局,同样依赖不兼容特性)
|
||||
*/
|
||||
|
||||
/** @type {import('postcss').PluginCreator} */
|
||||
module.exports = (options = {}) => {
|
||||
return {
|
||||
postcssPlugin: 'postcss-remove-wxss-incompatible',
|
||||
Rule(rule) {
|
||||
const selector = rule.selector
|
||||
|
||||
// 1) 移除 .\!xxx 类选择器(Tailwind important 修饰符,反斜杠转义的 ! 不兼容 WXSS)
|
||||
const selectors = selector.split(',')
|
||||
const hasEscapedImportant = selectors.some(s => /\.\\!/.test(s.trim()))
|
||||
if (hasEscapedImportant) {
|
||||
rule.remove()
|
||||
return
|
||||
}
|
||||
|
||||
// 2) 移除 .space-[x|y]-* 规则(依赖 :not + ~ 兄弟组合器,WXSS 不兼容)
|
||||
if (/\.space-[xy]-/.test(selector)) {
|
||||
rule.remove()
|
||||
return
|
||||
}
|
||||
|
||||
// 3) 移除 .divide-[x|y]-* 规则(同上,依赖 :not + ~)
|
||||
if (/\.divide-[xy]-/.test(selector)) {
|
||||
rule.remove()
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports.postcss = true
|
||||
+5
-10
@@ -1,12 +1,7 @@
|
||||
const wxssIncompatiblePlugin = require('./postcss-remove-wxss-incompatible')
|
||||
|
||||
module.exports = {
|
||||
plugins: [
|
||||
require('tailwindcss'),
|
||||
require('autoprefixer'),
|
||||
require('postcss-nested'),
|
||||
// 移除 WXSS 不兼容的 Tailwind CSS 规则
|
||||
// 包括 .\!xxx(反斜杠转义 !)、space-x/y-*(:not + ~ 组合器)、divide-x/y-* 等
|
||||
wxssIncompatiblePlugin()
|
||||
]
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
autoprefixer: {},
|
||||
'postcss-nested': {}
|
||||
}
|
||||
}
|
||||
|
||||
+34
-27
@@ -1,48 +1,55 @@
|
||||
{
|
||||
"miniprogramRoot": "dist/",
|
||||
"projectname": "template-5",
|
||||
"description": "网宿软件",
|
||||
"appid": "wx541db955e7a62709",
|
||||
"description": "websopy-taro 项目配置",
|
||||
"setting": {
|
||||
"urlCheck": true,
|
||||
"es6": false,
|
||||
"enhance": false,
|
||||
"compileHotReLoad": false,
|
||||
"postcss": false,
|
||||
"urlCheck": false,
|
||||
"es6": true,
|
||||
"enhance": true,
|
||||
"postcss": true,
|
||||
"preloadBackgroundData": false,
|
||||
"minified": true,
|
||||
"newFeature": true,
|
||||
"autoAudits": false,
|
||||
"coverView": true,
|
||||
"showShadowRootInWxmlPanel": false,
|
||||
"nodeModules": false,
|
||||
"autoAudits": false,
|
||||
"showShadowRootInWxmlPanel": true,
|
||||
"scopeDataCheck": false,
|
||||
"useCompilerModule": false,
|
||||
"compileWorklet": false,
|
||||
"uglifyFileName": false,
|
||||
"checkInvalidKey": true,
|
||||
"checkSiteMap": true,
|
||||
"uploadWithSourceMap": true,
|
||||
"packNpmManually": false,
|
||||
"packNpmRelationList": [],
|
||||
"minifyWXSS": true,
|
||||
"minifyWXML": true,
|
||||
"localPlugins": false,
|
||||
"disableUseStrict": false,
|
||||
"useCompilerPlugins": false,
|
||||
"condition": false,
|
||||
"swc": false,
|
||||
"disableSWC": true,
|
||||
"compileHotReLoad": true,
|
||||
"lazyloadPlaceholderEnable": false,
|
||||
"useMultiFrameRuntime": true,
|
||||
"useApiHook": true,
|
||||
"useApiHostProcess": true,
|
||||
"babelSetting": {
|
||||
"ignore": [],
|
||||
"disablePlugins": [],
|
||||
"outputPath": ""
|
||||
}
|
||||
},
|
||||
"useIsolateContext": true,
|
||||
"userConfirmedBundleSwitch": false,
|
||||
"packNpmManually": false,
|
||||
"packNpmRelationList": [],
|
||||
"minifyWXSS": true,
|
||||
"disableUseStrict": false,
|
||||
"minifyWXML": true,
|
||||
"showES6CompileOption": false,
|
||||
"useCompilerPlugins": false,
|
||||
"compileWorklet": false,
|
||||
"localPlugins": false,
|
||||
"condition": false,
|
||||
"swc": false,
|
||||
"disableSWC": true
|
||||
},
|
||||
"compileType": "miniprogram",
|
||||
"simulatorType": "wechat",
|
||||
"simulatorPluginLibVersion": {},
|
||||
"appid": "wx541db955e7a62709",
|
||||
"projectname": "威硕谱软件",
|
||||
"condition": {},
|
||||
"simulatorPluginLibVersion": {},
|
||||
"packOptions": {
|
||||
"ignore": [],
|
||||
"include": []
|
||||
},
|
||||
"editorSetting": {}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,76 +1,23 @@
|
||||
{
|
||||
"libVersion": "3.15.2",
|
||||
"projectname": "websopy-mp",
|
||||
"condition": {
|
||||
"miniprogram": {
|
||||
"list": [
|
||||
{
|
||||
"name": "passport/webview/index",
|
||||
"pathName": "passport/webview/index",
|
||||
"query": "url=https%3A%2F%2Fwebsopy.websoft.top%2Fdeveloper",
|
||||
"scene": null,
|
||||
"launchMode": "default"
|
||||
},
|
||||
{
|
||||
"name": "passport/login",
|
||||
"pathName": "passport/login",
|
||||
"query": "",
|
||||
"launchMode": "default",
|
||||
"scene": null
|
||||
},
|
||||
{
|
||||
"name": "passport/invite/index",
|
||||
"pathName": "passport/invite/index",
|
||||
"query": "",
|
||||
"launchMode": "default",
|
||||
"scene": null
|
||||
},
|
||||
{
|
||||
"name": "passport/invite/index",
|
||||
"pathName": "passport/invite/index",
|
||||
"query": "",
|
||||
"launchMode": "default",
|
||||
"scene": null
|
||||
},
|
||||
{
|
||||
"name": "passport/invite",
|
||||
"pathName": "passport/invite",
|
||||
"query": "",
|
||||
"launchMode": "default",
|
||||
"scene": null
|
||||
},
|
||||
{
|
||||
"name": "pages/passport/qr-confirm/index",
|
||||
"pathName": "pages/passport/qr-confirm/index",
|
||||
"query": "",
|
||||
"launchMode": "default",
|
||||
"scene": null
|
||||
},
|
||||
{
|
||||
"name": "passport/qr-confirm/index",
|
||||
"pathName": "passport/qr-confirm/index",
|
||||
"query": "",
|
||||
"launchMode": "default",
|
||||
"scene": null
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"libVersion": "3.16.1",
|
||||
"projectname": "websopy-taro",
|
||||
"condition": {},
|
||||
"setting": {
|
||||
"urlCheck": true,
|
||||
"urlCheck": false,
|
||||
"coverView": true,
|
||||
"lazyloadPlaceholderEnable": false,
|
||||
"skylineRenderEnable": false,
|
||||
"preloadBackgroundData": false,
|
||||
"autoAudits": false,
|
||||
"useApiHook": true,
|
||||
"showShadowRootInWxmlPanel": false,
|
||||
"showShadowRootInWxmlPanel": true,
|
||||
"useStaticServer": false,
|
||||
"useLanDebug": false,
|
||||
"showES6CompileOption": false,
|
||||
"compileHotReLoad": true,
|
||||
"checkInvalidKey": true,
|
||||
"ignoreDevUnusedFiles": true,
|
||||
"bigPackageSizeSupport": false
|
||||
"bigPackageSizeSupport": false,
|
||||
"useIsolateContext": true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
{
|
||||
"miniprogramRoot": "./",
|
||||
"projectname": "mp-react",
|
||||
"description": "网宿软件",
|
||||
"appid": "touristappid",
|
||||
"setting": {
|
||||
"urlCheck": true,
|
||||
"es6": false,
|
||||
"postcss": false,
|
||||
"minified": false
|
||||
},
|
||||
"compileType": "miniprogram"
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
export default definePageConfig({
|
||||
navigationBarTitleText: '新增收货地址',
|
||||
navigationBarTextStyle: 'black'
|
||||
})
|
||||
@@ -1,323 +0,0 @@
|
||||
import {useEffect, useState, useRef} from "react";
|
||||
import {useRouter} from '@tarojs/taro'
|
||||
import {Button, Loading, CellGroup, Input, TextArea, Form, Switch, InputNumber, Radio, Image} from '@nutui/nutui-react-taro'
|
||||
import {Edit, Upload as UploadIcon} from '@nutui/icons-react-taro'
|
||||
import Taro from '@tarojs/taro'
|
||||
import {View} from '@tarojs/components'
|
||||
import {ShopArticle} from "@/api/shop/shopArticle/model";
|
||||
import {getShopArticle, addShopArticle, updateShopArticle} from "@/api/shop/shopArticle";
|
||||
import FixedButton from "@/components/FixedButton";
|
||||
|
||||
const AddShopArticle = () => {
|
||||
const {params} = useRouter();
|
||||
const [loading, setLoading] = useState<boolean>(true)
|
||||
const [formData, setFormData] = useState<ShopArticle>({
|
||||
type: 0, // 默认常规文章
|
||||
status: 0, // 默认已发布
|
||||
permission: 0, // 默认所有人可见
|
||||
recommend: 0, // 默认不推荐
|
||||
showType: 10, // 默认小图展示
|
||||
virtualViews: 0, // 默认虚拟阅读量
|
||||
actualViews: 0, // 默认实际阅读量
|
||||
sortNumber: 0 // 默认排序
|
||||
})
|
||||
const formRef = useRef<any>(null)
|
||||
|
||||
// 判断是编辑还是新增模式
|
||||
const isEditMode = !!params.id
|
||||
const articleId = params.id ? Number(params.id) : undefined
|
||||
|
||||
// 文章类型选项
|
||||
const typeOptions = [
|
||||
{ text: '常规文章', value: 0 },
|
||||
{ text: '视频文章', value: 1 }
|
||||
]
|
||||
|
||||
// 状态选项
|
||||
const statusOptions = [
|
||||
{ text: '已发布', value: 0 },
|
||||
{ text: '待审核', value: 1 },
|
||||
{ text: '已驳回', value: 2 },
|
||||
{ text: '违规内容', value: 3 }
|
||||
]
|
||||
|
||||
// 可见性选项
|
||||
const permissionOptions = [
|
||||
{ text: '所有人可见', value: 0 },
|
||||
{ text: '登录可见', value: 1 },
|
||||
{ text: '密码可见', value: 2 }
|
||||
]
|
||||
|
||||
// 显示方式选项
|
||||
const showTypeOptions = [
|
||||
{ text: '小图展示', value: 10 },
|
||||
{ text: '大图展示', value: 20 }
|
||||
]
|
||||
|
||||
const reload = async () => {
|
||||
// 如果是编辑模式,加载文章数据
|
||||
if (isEditMode && articleId) {
|
||||
try {
|
||||
const article = await getShopArticle(articleId)
|
||||
setFormData(article)
|
||||
// 更新表单值
|
||||
if (formRef.current) {
|
||||
formRef.current.setFieldsValue(article)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载文章失败:', error)
|
||||
Taro.showToast({
|
||||
title: '加载文章失败',
|
||||
icon: 'error'
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 图片上传处理
|
||||
const handleImageUpload = async () => {
|
||||
try {
|
||||
const res = await Taro.chooseImage({
|
||||
count: 1,
|
||||
sizeType: ['compressed'],
|
||||
sourceType: ['album', 'camera']
|
||||
});
|
||||
|
||||
if (res.tempFilePaths && res.tempFilePaths.length > 0) {
|
||||
// 这里应该调用上传接口,暂时使用本地路径
|
||||
const imagePath = res.tempFilePaths[0];
|
||||
setFormData({
|
||||
...formData,
|
||||
image: imagePath
|
||||
});
|
||||
|
||||
Taro.showToast({
|
||||
title: '图片选择成功',
|
||||
icon: 'success'
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
Taro.showToast({
|
||||
title: '图片选择失败',
|
||||
icon: 'error'
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// 提交表单
|
||||
const submitSucceed = async (values: any) => {
|
||||
try {
|
||||
// 准备提交的数据
|
||||
const submitData = {
|
||||
...formData,
|
||||
...values,
|
||||
};
|
||||
|
||||
// 如果是编辑模式,添加id
|
||||
if (isEditMode && articleId) {
|
||||
submitData.articleId = articleId;
|
||||
}
|
||||
|
||||
// 执行新增或更新操作
|
||||
if (isEditMode) {
|
||||
await updateShopArticle(submitData);
|
||||
} else {
|
||||
await addShopArticle(submitData);
|
||||
}
|
||||
|
||||
Taro.showToast({
|
||||
title: `${isEditMode ? '更新' : '保存'}成功`,
|
||||
icon: 'success'
|
||||
});
|
||||
|
||||
setTimeout(() => {
|
||||
Taro.navigateBack();
|
||||
}, 1000);
|
||||
|
||||
} catch (error) {
|
||||
console.error('保存失败:', error);
|
||||
Taro.showToast({
|
||||
title: `${isEditMode ? '更新' : '保存'}失败`,
|
||||
icon: 'error'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const submitFailed = (error: any) => {
|
||||
console.log(error, 'err...')
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
// 动态设置页面标题
|
||||
Taro.setNavigationBarTitle({
|
||||
title: isEditMode ? '编辑文章' : '新增文章'
|
||||
});
|
||||
|
||||
reload().then(() => {
|
||||
setLoading(false)
|
||||
})
|
||||
}, [isEditMode]);
|
||||
|
||||
if (loading) {
|
||||
return <Loading className={'px-2'}>加载中</Loading>
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Form
|
||||
ref={formRef}
|
||||
divider
|
||||
initialValues={formData}
|
||||
labelPosition="left"
|
||||
onFinish={(values) => submitSucceed(values)}
|
||||
onFinishFailed={(errors) => submitFailed(errors)}
|
||||
>
|
||||
{/* 基本信息 */}
|
||||
<CellGroup title="基本信息">
|
||||
<Form.Item
|
||||
name="title"
|
||||
label="文章标题"
|
||||
required
|
||||
rules={[{ required: true, message: '请输入文章标题' }]}
|
||||
initialValue={formData.title}
|
||||
>
|
||||
<Input placeholder="请输入文章标题" maxLength={100}/>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item name="overview" label="文章概述" initialValue={formData.overview}>
|
||||
<TextArea placeholder="请输入文章概述,用于列表展示" maxLength={200} rows={3}/>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
name="detail"
|
||||
label="文章内容"
|
||||
required
|
||||
rules={[{ required: true, message: '请输入文章内容' }]}
|
||||
initialValue={formData.detail}
|
||||
>
|
||||
<TextArea placeholder="请输入文章内容" maxLength={10000} rows={8}/>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item name="author" label="作者" initialValue={formData.author}>
|
||||
<Input placeholder="请输入作者名称" maxLength={50}/>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item name="source" label="来源" initialValue={formData.source}>
|
||||
<Input placeholder="请输入文章来源" maxLength={100}/>
|
||||
</Form.Item>
|
||||
</CellGroup>
|
||||
|
||||
{/* 文章设置 */}
|
||||
<CellGroup title="文章设置">
|
||||
<Form.Item name="type" label="文章类型" initialValue={formData.type}>
|
||||
<Radio.Group direction="horizontal" value={formData.type}>
|
||||
{typeOptions.map(option => (
|
||||
<Radio key={option.value} value={option.value}>
|
||||
{option.text}
|
||||
</Radio>
|
||||
))}
|
||||
</Radio.Group>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item name="status" label="发布状态" initialValue={formData.status}>
|
||||
<Radio.Group direction="horizontal" value={formData.status}>
|
||||
{statusOptions.map(option => (
|
||||
<Radio key={option.value} value={option.value}>
|
||||
{option.text}
|
||||
</Radio>
|
||||
))}
|
||||
</Radio.Group>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item name="permission" label="可见性" initialValue={formData.permission}>
|
||||
<Radio.Group direction="horizontal" value={formData.permission}>
|
||||
{permissionOptions.map(option => (
|
||||
<Radio key={option.value} value={option.value}>
|
||||
{option.text}
|
||||
</Radio>
|
||||
))}
|
||||
</Radio.Group>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item name="showType" label="显示方式" initialValue={formData.showType}>
|
||||
<Radio.Group direction="horizontal" value={formData.showType}>
|
||||
{showTypeOptions.map(option => (
|
||||
<Radio key={option.value} value={option.value}>
|
||||
{option.text}
|
||||
</Radio>
|
||||
))}
|
||||
</Radio.Group>
|
||||
</Form.Item>
|
||||
</CellGroup>
|
||||
|
||||
{/* 高级设置 */}
|
||||
<CellGroup title="高级设置">
|
||||
<Form.Item name="recommend" label="推荐文章" initialValue={formData.recommend}>
|
||||
<Switch
|
||||
checked={formData.recommend === 1}
|
||||
onChange={(checked) =>
|
||||
setFormData({...formData, recommend: checked ? 1 : 0})
|
||||
}
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item name="price" label="付费金额" initialValue={formData.price}>
|
||||
<Input placeholder="0.00" type="number"/>
|
||||
<View className="text-xs text-gray-500 mt-1">单位:元</View>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item name="virtualViews" label="虚拟阅读量" initialValue={formData.virtualViews}>
|
||||
<InputNumber min={0} defaultValue={formData.virtualViews || 0}/>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item name="actualViews" label="实际阅读量" initialValue={formData.actualViews}>
|
||||
<InputNumber min={0} defaultValue={formData.actualViews || 0}/>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item name="sortNumber" label="排序" initialValue={formData.sortNumber}>
|
||||
<InputNumber min={0} defaultValue={formData.sortNumber || 0}/>
|
||||
<View className="text-xs text-gray-500 mt-1">数字越小越靠前</View>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item name="tags" label="标签" initialValue={formData.tags}>
|
||||
<Input placeholder="请输入标签,多个标签用逗号分隔" maxLength={200}/>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item name="topic" label="话题" initialValue={formData.topic}>
|
||||
<Input placeholder="请输入话题" maxLength={100}/>
|
||||
</Form.Item>
|
||||
</CellGroup>
|
||||
|
||||
{/* 图片上传 */}
|
||||
<CellGroup title="文章图片">
|
||||
<Form.Item name="image" label="封面图片" initialValue={formData.image}>
|
||||
<View className="flex items-center gap-3">
|
||||
{formData.image && (
|
||||
<Image
|
||||
src={formData.image}
|
||||
width="80"
|
||||
height="80"
|
||||
radius="8"
|
||||
/>
|
||||
)}
|
||||
<Button
|
||||
size="small"
|
||||
type="primary"
|
||||
fill="outline"
|
||||
icon={<UploadIcon />}
|
||||
onClick={handleImageUpload}
|
||||
>
|
||||
{formData.image ? '更换图片' : '上传图片'}
|
||||
</Button>
|
||||
</View>
|
||||
</Form.Item>
|
||||
</CellGroup>
|
||||
|
||||
{/* 提交按钮 */}
|
||||
<FixedButton text={isEditMode ? '更新文章' : '发布文章'} onClick={() => submitSucceed} icon={<Edit />} />
|
||||
</Form>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default AddShopArticle;
|
||||
@@ -1,4 +0,0 @@
|
||||
export default definePageConfig({
|
||||
navigationBarTitleText: '商品文章管理',
|
||||
navigationBarTextStyle: 'black'
|
||||
})
|
||||
@@ -1,271 +0,0 @@
|
||||
import {useState} from "react";
|
||||
import Taro, {useDidShow} from '@tarojs/taro'
|
||||
import {Button, Cell, CellGroup, Empty, ConfigProvider, SearchBar, Tag, InfiniteLoading, Loading, PullToRefresh} from '@nutui/nutui-react-taro'
|
||||
import {Edit, Del, Eye} from '@nutui/icons-react-taro'
|
||||
import {View} from '@tarojs/components'
|
||||
import {CmsArticle} from "@/api/cms/cmsArticle/model";
|
||||
import {pageCmsArticle, removeCmsArticle} from "@/api/cms/cmsArticle";
|
||||
import FixedButton from "@/components/FixedButton";
|
||||
import dayjs from "dayjs";
|
||||
|
||||
const ArticleArticleManage = () => {
|
||||
const [list, setList] = useState<CmsArticle[]>([])
|
||||
const [loading, setLoading] = useState(false)
|
||||
// const [refreshing, setRefreshing] = useState(false)
|
||||
const [hasMore, setHasMore] = useState(true)
|
||||
const [searchValue, setSearchValue] = useState('')
|
||||
const [page, setPage] = useState(1)
|
||||
const [total, setTotal] = useState(0)
|
||||
|
||||
const reload = async (isRefresh = false) => {
|
||||
if (isRefresh) {
|
||||
setPage(1)
|
||||
setList([])
|
||||
setHasMore(true)
|
||||
}
|
||||
|
||||
setLoading(true)
|
||||
try {
|
||||
const currentPage = isRefresh ? 1 : page
|
||||
const res = await pageCmsArticle({
|
||||
page: currentPage,
|
||||
limit: 10,
|
||||
keywords: searchValue
|
||||
})
|
||||
|
||||
if (res && res.list) {
|
||||
const newList = isRefresh ? res.list : [...list, ...res.list]
|
||||
setList(newList)
|
||||
setTotal(res.count || 0)
|
||||
|
||||
// 判断是否还有更多数据
|
||||
setHasMore(res.list.length === 10) // 如果返回的数据等于limit,说明可能还有更多
|
||||
|
||||
if (!isRefresh) {
|
||||
setPage(currentPage + 1)
|
||||
} else {
|
||||
setPage(2) // 刷新后下一页是第2页
|
||||
}
|
||||
} else {
|
||||
setHasMore(false)
|
||||
setTotal(0)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取文章失败:', error)
|
||||
Taro.showToast({
|
||||
title: '获取文章失败',
|
||||
icon: 'error'
|
||||
});
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
// 搜索功能
|
||||
const handleSearch = (value: string) => {
|
||||
setSearchValue(value)
|
||||
reload(true)
|
||||
}
|
||||
|
||||
// 下拉刷新
|
||||
const handleRefresh = async () => {
|
||||
// setRefreshing(true)
|
||||
await reload(true)
|
||||
// setRefreshing(false)
|
||||
}
|
||||
|
||||
// 删除文章
|
||||
const handleDelete = async (id?: number) => {
|
||||
Taro.showModal({
|
||||
title: '确认删除',
|
||||
content: '确定要删除这篇文章吗?',
|
||||
success: async (res) => {
|
||||
if (res.confirm) {
|
||||
try {
|
||||
await removeCmsArticle(id)
|
||||
Taro.showToast({
|
||||
title: '删除成功',
|
||||
icon: 'success'
|
||||
});
|
||||
reload(true);
|
||||
} catch (error) {
|
||||
Taro.showToast({
|
||||
title: '删除失败',
|
||||
icon: 'error'
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 编辑文章
|
||||
const handleEdit = (item: CmsArticle) => {
|
||||
Taro.navigateTo({
|
||||
url: `/shop/shopArticle/add?id=${item.articleId}`
|
||||
});
|
||||
}
|
||||
|
||||
// 查看文章详情
|
||||
const handleView = (item: CmsArticle) => {
|
||||
// 这里可以跳转到文章详情页面
|
||||
Taro.navigateTo({
|
||||
url: `/cms/detail/index?id=${item.articleId}`
|
||||
})
|
||||
}
|
||||
|
||||
// 获取状态标签
|
||||
const getStatusTag = (status?: number) => {
|
||||
switch (status) {
|
||||
case 0:
|
||||
return <Tag type="success">已发布</Tag>
|
||||
case 1:
|
||||
return <Tag type="warning">待审核</Tag>
|
||||
case 2:
|
||||
return <Tag type="danger">已驳回</Tag>
|
||||
case 3:
|
||||
return <Tag type="danger">违规内容</Tag>
|
||||
default:
|
||||
return <Tag>未知</Tag>
|
||||
}
|
||||
}
|
||||
|
||||
// 加载更多
|
||||
const loadMore = async () => {
|
||||
if (!loading && hasMore) {
|
||||
await reload(false) // 不刷新,追加数据
|
||||
}
|
||||
}
|
||||
|
||||
useDidShow(() => {
|
||||
reload(true).then()
|
||||
});
|
||||
|
||||
return (
|
||||
<ConfigProvider>
|
||||
{/* 搜索栏 */}
|
||||
<View className="py-2">
|
||||
<SearchBar
|
||||
placeholder="搜索关键词"
|
||||
value={searchValue}
|
||||
onChange={setSearchValue}
|
||||
onSearch={handleSearch}
|
||||
/>
|
||||
</View>
|
||||
|
||||
{/* 统计信息 */}
|
||||
{total > 0 && (
|
||||
<View className="px-4 py-2 text-sm text-gray-500">
|
||||
共找到 {total} 篇文章
|
||||
</View>
|
||||
)}
|
||||
|
||||
{/* 文章列表 */}
|
||||
<PullToRefresh
|
||||
onRefresh={handleRefresh}
|
||||
headHeight={60}
|
||||
>
|
||||
<View className="px-4" style={{ height: 'calc(100vh - 160px)', overflowY: 'auto' }} id="article-scroll">
|
||||
{list.length === 0 && !loading ? (
|
||||
<View className="flex flex-col justify-center items-center" style={{height: 'calc(100vh - 200px)'}}>
|
||||
<Empty
|
||||
description="暂无文章数据"
|
||||
style={{backgroundColor: 'transparent'}}
|
||||
/>
|
||||
</View>
|
||||
) : (
|
||||
<InfiniteLoading
|
||||
target="article-scroll"
|
||||
hasMore={hasMore}
|
||||
onLoadMore={loadMore}
|
||||
loadingText={
|
||||
<View className="flex justify-center items-center py-4">
|
||||
<Loading />
|
||||
<View className="ml-2">加载中...</View>
|
||||
</View>
|
||||
}
|
||||
loadMoreText={
|
||||
<View className="text-center py-4 text-gray-500">
|
||||
{list.length === 0 ? "暂无数据" : "没有更多了"}
|
||||
</View>
|
||||
}
|
||||
>
|
||||
{list.map((item, index) => (
|
||||
<CellGroup key={item.articleId || index} className="mb-4">
|
||||
<Cell>
|
||||
<View className="flex flex-col gap-3 w-full">
|
||||
{/* 文章标题和状态 */}
|
||||
<View className="flex justify-between items-start">
|
||||
<View className="flex-1 pr-2">
|
||||
<View className="text-lg font-bold text-gray-900 line-clamp-2">
|
||||
{item.title}
|
||||
</View>
|
||||
</View>
|
||||
{getStatusTag(item.status)}
|
||||
</View>
|
||||
|
||||
{/* 文章概述 */}
|
||||
{item.overview && (
|
||||
<View className="text-sm text-gray-600 line-clamp-2">
|
||||
{item.overview}
|
||||
</View>
|
||||
)}
|
||||
|
||||
{/* 文章信息 */}
|
||||
<View className="flex justify-between items-center text-xs text-gray-500">
|
||||
<View className="flex items-center gap-4">
|
||||
<View>阅读: {item.actualViews || 0}</View>
|
||||
{item.price && <View>价格: ¥{item.price}</View>}
|
||||
<View>创建: {dayjs(item.createTime).format('MM-DD HH:mm')}</View>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{/* 操作按钮 */}
|
||||
<View className="flex justify-end gap-2 pt-2 border-t border-gray-100">
|
||||
<Button
|
||||
size="small"
|
||||
fill="outline"
|
||||
icon={<Eye/>}
|
||||
onClick={() => handleView(item)}
|
||||
>
|
||||
查看
|
||||
</Button>
|
||||
<Button
|
||||
size="small"
|
||||
fill="outline"
|
||||
icon={<Edit/>}
|
||||
onClick={() => handleEdit(item)}
|
||||
>
|
||||
编辑
|
||||
</Button>
|
||||
<Button
|
||||
size="small"
|
||||
type="danger"
|
||||
fill="outline"
|
||||
icon={<Del/>}
|
||||
onClick={() => handleDelete(item.articleId)}
|
||||
>
|
||||
删除
|
||||
</Button>
|
||||
</View>
|
||||
</View>
|
||||
</Cell>
|
||||
</CellGroup>
|
||||
))}
|
||||
</InfiniteLoading>
|
||||
)}
|
||||
</View>
|
||||
</PullToRefresh>
|
||||
|
||||
{/* 底部浮动按钮 */}
|
||||
<FixedButton
|
||||
text="发布文章"
|
||||
icon={<Edit />}
|
||||
onClick={() => Taro.navigateTo({url: '/shop/shopArticle/add'})}
|
||||
/>
|
||||
</ConfigProvider>
|
||||
);
|
||||
|
||||
};
|
||||
|
||||
export default ArticleArticleManage;
|
||||
@@ -1,255 +0,0 @@
|
||||
import {Button} from '@nutui/nutui-react-taro'
|
||||
import {Avatar, Tag} from '@nutui/nutui-react-taro'
|
||||
import {getUserInfo, getWxOpenId} from '@/api/layout';
|
||||
import Taro from '@tarojs/taro';
|
||||
import {useEffect, useState} from "react";
|
||||
import {User} from "@/api/system/user/model";
|
||||
import navTo from "@/utils/common";
|
||||
import {TenantId} from "@/config/app";
|
||||
import {getMyAvailableCoupons} from "@/api/shop/shopUserCoupon";
|
||||
import {useUser} from "@/hooks/useUser";
|
||||
import {getStoredInviteParams} from "@/utils/invite";
|
||||
|
||||
function UserCard() {
|
||||
const {getDisplayName, getRoleName} = useUser();
|
||||
const [IsLogin, setIsLogin] = useState<boolean>(false)
|
||||
const [userInfo, setUserInfo] = useState<User>()
|
||||
const [couponCount, setCouponCount] = useState(0)
|
||||
// const [pointsCount, setPointsCount] = useState(0)
|
||||
const [giftCount, setGiftCount] = useState(0)
|
||||
|
||||
useEffect(() => {
|
||||
// Taro.getSetting:获取用户的当前设置。返回值中只会出现小程序已经向用户请求过的权限。
|
||||
Taro.getSetting({
|
||||
success: (res) => {
|
||||
if (res.authSetting['scope.userInfo']) {
|
||||
// 用户已经授权过,可以直接获取用户信息
|
||||
console.log('用户已经授权过,可以直接获取用户信息')
|
||||
reload();
|
||||
} else {
|
||||
// 用户未授权,需要弹出授权窗口
|
||||
console.log('用户未授权,需要弹出授权窗口')
|
||||
showAuthModal();
|
||||
}
|
||||
}
|
||||
});
|
||||
}, []);
|
||||
|
||||
const loadUserStats = (userId: number) => {
|
||||
// 加载优惠券数量
|
||||
getMyAvailableCoupons()
|
||||
.then((coupons: any) => {
|
||||
setCouponCount(coupons?.length || 0)
|
||||
})
|
||||
.catch((error: any) => {
|
||||
console.error('Coupon count error:', error)
|
||||
})
|
||||
|
||||
// 加载积分数量
|
||||
console.log(userId)
|
||||
// getUserPointsStats(userId)
|
||||
// .then((res: any) => {
|
||||
// setPointsCount(res.currentPoints || 0)
|
||||
// })
|
||||
// .catch((error: any) => {
|
||||
// console.error('Points stats error:', error)
|
||||
// })
|
||||
// 加载礼品劵数量
|
||||
setGiftCount(0)
|
||||
// pageUserGiftLog({userId, page: 1, limit: 1}).then(res => {
|
||||
// setGiftCount(res.count || 0)
|
||||
// })
|
||||
}
|
||||
|
||||
const reload = () => {
|
||||
Taro.getUserInfo({
|
||||
success: (res) => {
|
||||
const avatar = res.userInfo.avatarUrl;
|
||||
setUserInfo({
|
||||
avatar,
|
||||
nickname: res.userInfo.nickName,
|
||||
sexName: res.userInfo.gender == 1 ? '男' : '女'
|
||||
})
|
||||
getUserInfo().then((data) => {
|
||||
if (data) {
|
||||
setUserInfo(data)
|
||||
setIsLogin(true);
|
||||
Taro.setStorageSync('UserId', data.userId)
|
||||
|
||||
// 加载用户统计数据
|
||||
if (data.userId) {
|
||||
loadUserStats(data.userId)
|
||||
}
|
||||
|
||||
// 获取openId
|
||||
if (!data.openid) {
|
||||
Taro.login({
|
||||
success: (res) => {
|
||||
getWxOpenId({code: res.code}).then(() => {
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}).catch(() => {
|
||||
console.log('未登录')
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const showAuthModal = () => {
|
||||
Taro.showModal({
|
||||
title: '授权提示',
|
||||
content: '需要获取您的用户信息',
|
||||
confirmText: '去授权',
|
||||
cancelText: '取消',
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
// 用户点击确认,打开授权设置页面
|
||||
openSetting();
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
const openSetting = () => {
|
||||
// Taro.openSetting:调起客户端小程序设置界面,返回用户设置的操作结果。设置界面只会出现小程序已经向用户请求过的权限。
|
||||
Taro.openSetting({
|
||||
success: (res) => {
|
||||
if (res.authSetting['scope.userInfo']) {
|
||||
// 用户授权成功,可以获取用户信息
|
||||
reload();
|
||||
} else {
|
||||
// 用户拒绝授权,提示授权失败
|
||||
Taro.showToast({
|
||||
title: '授权失败',
|
||||
icon: 'none'
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/* 获取用户手机号 */
|
||||
const handleGetPhoneNumber = ({detail}: {detail: {code?: string, encryptedData?: string, iv?: string}}) => {
|
||||
const {code, encryptedData, iv} = detail
|
||||
|
||||
// 获取存储的邀请参数
|
||||
const inviteParams = getStoredInviteParams()
|
||||
const refereeId = inviteParams?.inviter ? parseInt(inviteParams.inviter) : 0
|
||||
|
||||
Taro.login({
|
||||
success: function () {
|
||||
if (code) {
|
||||
Taro.request({
|
||||
url: 'https://server.websoft.top/api/wx-login/loginByMpWxPhone',
|
||||
method: 'POST',
|
||||
data: {
|
||||
code,
|
||||
encryptedData,
|
||||
iv,
|
||||
notVerifyPhone: true,
|
||||
refereeId: refereeId, // 使用解析出的推荐人ID
|
||||
sceneType: 'save_referee',
|
||||
tenantId: TenantId
|
||||
},
|
||||
header: {
|
||||
'content-type': 'application/json',
|
||||
TenantId
|
||||
},
|
||||
success: function (res) {
|
||||
if (res.data.code == 1) {
|
||||
Taro.showToast({
|
||||
title: res.data.message,
|
||||
icon: 'error',
|
||||
duration: 2000
|
||||
})
|
||||
return false;
|
||||
}
|
||||
// 登录成功
|
||||
Taro.setStorageSync('access_token', res.data.data.access_token)
|
||||
Taro.setStorageSync('UserId', res.data.data.user.userId)
|
||||
setUserInfo(res.data.data.user)
|
||||
setIsLogin(true)
|
||||
}
|
||||
})
|
||||
} else {
|
||||
console.log('登录失败!')
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={'header-bg pt-20'}>
|
||||
<div className={'p-4'}>
|
||||
<div
|
||||
className={'user-card w-full flex flex-col justify-around rounded-xl shadow-sm'}
|
||||
style={{
|
||||
background: 'linear-gradient(to bottom, #ffffff, #ffffff)', // 这种情况建议使用类名来控制样式(引入外联样式)
|
||||
// width: '720rpx',
|
||||
// margin: '10px auto 0px auto',
|
||||
height: '170px',
|
||||
// borderRadius: '22px 22px 0 0',
|
||||
}}
|
||||
>
|
||||
<div className={'user-card-header flex w-full justify-between items-center pt-4'}>
|
||||
<div className={'flex items-center mx-4'}>
|
||||
{
|
||||
IsLogin ? (
|
||||
<Avatar size="large" src={userInfo?.avatar} shape="round"/>
|
||||
) : (
|
||||
<Button className={'text-black'} open-type="getPhoneNumber" onGetPhoneNumber={handleGetPhoneNumber}>
|
||||
<Avatar size="large" src={userInfo?.avatar} shape="round"/>
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
<div className={'user-info flex flex-col px-2'}>
|
||||
<div className={'py-1 text-black font-bold'}>{getDisplayName()}</div>
|
||||
{IsLogin ? (
|
||||
<div className={'grade text-xs py-1'}>
|
||||
<Tag type="success" round>
|
||||
<div className={'p-1'}>
|
||||
{getRoleName()}
|
||||
</div>
|
||||
</Tag>
|
||||
</div>
|
||||
) : ''}
|
||||
</div>
|
||||
</div>
|
||||
<div className={'mx-4 text-sm px-3 py-1 text-black border-gray-400 border-solid border-2 rounded-3xl'}
|
||||
onClick={() => navTo('/user/profile/profile', true)}>
|
||||
{'个人资料'}
|
||||
</div>
|
||||
</div>
|
||||
<div className={'flex justify-around mt-1'}>
|
||||
<div className={'item flex justify-center flex-col items-center'}
|
||||
onClick={() => navTo('/user/wallet/wallet', true)}>
|
||||
<span className={'text-sm text-gray-500'}>余额</span>
|
||||
<span className={'text-xl'}>¥ {userInfo?.balance || '0.00'}</span>
|
||||
</div>
|
||||
<div className={'item flex justify-center flex-col items-center'}
|
||||
onClick={() => navTo('/user/coupon/index', true)}>
|
||||
<span className={'text-sm text-gray-500'}>优惠券</span>
|
||||
<span className={'text-xl'}>{couponCount}</span>
|
||||
</div>
|
||||
<div className={'item flex justify-center flex-col items-center'}
|
||||
onClick={() => navTo('/user/gift/index', true)}>
|
||||
<span className={'text-sm text-gray-500'}>水票</span>
|
||||
<span className={'text-xl'}>{giftCount}</span>
|
||||
</div>
|
||||
{/*<div className={'item flex justify-center flex-col items-center'}>*/}
|
||||
{/* <span className={'text-sm text-gray-500'}>积分</span>*/}
|
||||
{/* <span className={'text-xl'}>{pointsCount}</span>*/}
|
||||
{/*</div>*/}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
)
|
||||
}
|
||||
|
||||
export default UserCard;
|
||||
@@ -1,186 +0,0 @@
|
||||
import {Cell} from '@nutui/nutui-react-taro'
|
||||
import navTo from "@/utils/common";
|
||||
import Taro from '@tarojs/taro'
|
||||
import {View, Text} from '@tarojs/components'
|
||||
import {ArrowRight, ShieldCheck, LogisticsError, Location, Reward, Tips, Ask, Setting, Scan} from '@nutui/icons-react-taro'
|
||||
import {useUser} from '@/hooks/useUser'
|
||||
|
||||
const UserCell = () => {
|
||||
const {logoutUser, isCertified, hasRole, isAdmin} = useUser();
|
||||
|
||||
const onLogout = () => {
|
||||
Taro.showModal({
|
||||
title: '提示',
|
||||
content: '确定要退出登录吗?',
|
||||
success: function (res) {
|
||||
if (res.confirm) {
|
||||
// 使用 useUser hook 的 logoutUser 方法
|
||||
logoutUser();
|
||||
Taro.reLaunch({
|
||||
url: '/pages/index/index'
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<View className={'px-4'}>
|
||||
|
||||
{/*是否分销商*/}
|
||||
{!hasRole('dealer') && !isAdmin() && (
|
||||
<Cell
|
||||
className="nutui-cell-clickable"
|
||||
style={{
|
||||
backgroundImage: 'linear-gradient(to right bottom, #54a799, #177b73)',
|
||||
}}
|
||||
title={
|
||||
<View style={{display: 'inline-flex', alignItems: 'center'}} onClick={() => navTo('/dealer/index', true)}>
|
||||
<Reward className={'text-orange-100 '} size={16}/>
|
||||
<Text style={{fontSize: '16px'}} className={'pl-3 text-orange-100 font-medium'}>开通会员</Text>
|
||||
<Text className={'text-white opacity-80 pl-3'}>享优惠</Text>
|
||||
</View>
|
||||
}
|
||||
extra={<ArrowRight color="#cccccc" size={18}/>}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/*是否管理员*/}
|
||||
{isAdmin() && (
|
||||
<Cell
|
||||
className="nutui-cell-clickable"
|
||||
style={{
|
||||
backgroundImage: 'linear-gradient(to right bottom, #ff8e0c, #ed680d)',
|
||||
}}
|
||||
title={
|
||||
<View style={{display: 'inline-flex', alignItems: 'center'}} onClick={() => navTo('/admin/article/index', true)}>
|
||||
<Setting className={'text-orange-100 '} size={16}/>
|
||||
<Text style={{fontSize: '16px'}} className={'pl-3 text-orange-100 font-medium'}>管理中心</Text>
|
||||
</View>
|
||||
}
|
||||
extra={<ArrowRight color="#cccccc" size={18}/>}
|
||||
/>
|
||||
)}
|
||||
|
||||
<Cell.Group divider={true} description={
|
||||
<View style={{display: 'inline-flex', alignItems: 'center'}}>
|
||||
<Text style={{marginTop: '12px'}}>我的服务</Text>
|
||||
</View>
|
||||
}>
|
||||
<Cell
|
||||
className="nutui-cell-clickable"
|
||||
title={
|
||||
<View style={{display: 'inline-flex', alignItems: 'center'}}>
|
||||
<Scan size={16}/>
|
||||
<Text className={'pl-3 text-sm'}>门店核销</Text>
|
||||
</View>
|
||||
}
|
||||
align="center"
|
||||
extra={<ArrowRight color="#cccccc" size={18}/>}
|
||||
onClick={() => {
|
||||
navTo('/user/wallet/index', true)
|
||||
}}
|
||||
/>
|
||||
<Cell
|
||||
className="nutui-cell-clickable"
|
||||
style={{
|
||||
display: 'none'
|
||||
}}
|
||||
title={
|
||||
<View style={{display: 'inline-flex', alignItems: 'center'}}>
|
||||
<LogisticsError size={16}/>
|
||||
<Text className={'pl-3 text-sm'}>我的钱包</Text>
|
||||
</View>
|
||||
}
|
||||
align="center"
|
||||
extra={<ArrowRight color="#cccccc" size={18}/>}
|
||||
onClick={() => {
|
||||
navTo('/user/wallet/index', true)
|
||||
}}
|
||||
/>
|
||||
<Cell
|
||||
className="nutui-cell-clickable"
|
||||
title={
|
||||
<View style={{display: 'inline-flex', alignItems: 'center'}}>
|
||||
<Location size={16}/>
|
||||
<Text className={'pl-3 text-sm'}>收货地址</Text>
|
||||
</View>
|
||||
}
|
||||
align="center"
|
||||
extra={<ArrowRight color="#cccccc" size={18}/>}
|
||||
onClick={() => {
|
||||
navTo('/user/address/index', true)
|
||||
}}
|
||||
/>
|
||||
<Cell
|
||||
className="nutui-cell-clickable"
|
||||
title={
|
||||
<View style={{display: 'inline-flex', alignItems: 'center'}}>
|
||||
<ShieldCheck size={16} color={isCertified() ? '#52c41a' : '#666'}/>
|
||||
<Text className={'pl-3 text-sm'}>实名认证</Text>
|
||||
{isCertified() && (
|
||||
<Text className={'pl-2 text-xs text-green-500'}>已认证</Text>
|
||||
)}
|
||||
</View>
|
||||
}
|
||||
align="center"
|
||||
extra={<ArrowRight color="#cccccc" size={18}/>}
|
||||
onClick={() => {
|
||||
navTo('/user/userVerify/index', true)
|
||||
}}
|
||||
/>
|
||||
<Cell
|
||||
className="nutui-cell-clickable"
|
||||
title={
|
||||
<View style={{display: 'inline-flex', alignItems: 'center'}}>
|
||||
<Ask size={16}/>
|
||||
<Text className={'pl-3 text-sm'}>常见问题</Text>
|
||||
</View>
|
||||
}
|
||||
align="center"
|
||||
extra={<ArrowRight color="#cccccc" size={18}/>}
|
||||
onClick={() => {
|
||||
navTo('/user/help/index')
|
||||
}}
|
||||
/>
|
||||
<Cell
|
||||
className="nutui-cell-clickable"
|
||||
title={
|
||||
<View style={{display: 'inline-flex', alignItems: 'center'}}>
|
||||
<Tips size={16}/>
|
||||
<Text className={'pl-3 text-sm'}>关于我们</Text>
|
||||
</View>
|
||||
}
|
||||
align="center"
|
||||
extra={<ArrowRight color="#cccccc" size={18}/>}
|
||||
onClick={() => {
|
||||
navTo('/user/about/index')
|
||||
}}
|
||||
/>
|
||||
</Cell.Group>
|
||||
<Cell.Group divider={true} description={
|
||||
<View style={{display: 'inline-flex', alignItems: 'center'}}>
|
||||
<Text style={{marginTop: '12px'}}>账号管理</Text>
|
||||
</View>
|
||||
}>
|
||||
<Cell
|
||||
className="nutui-cell-clickable"
|
||||
title="账号安全"
|
||||
align="center"
|
||||
extra={<ArrowRight color="#cccccc" size={18}/>}
|
||||
onClick={() => navTo('/user/profile/profile', true)}
|
||||
/>
|
||||
<Cell
|
||||
className="nutui-cell-clickable"
|
||||
title="退出登录"
|
||||
align="center"
|
||||
extra={<ArrowRight color="#cccccc" size={18}/>}
|
||||
onClick={onLogout}
|
||||
/>
|
||||
</Cell.Group>
|
||||
</View>
|
||||
</>
|
||||
)
|
||||
}
|
||||
export default UserCell
|
||||
@@ -1,102 +0,0 @@
|
||||
import {loginBySms} from "@/api/passport/login";
|
||||
import {useState} from "react";
|
||||
import Taro from '@tarojs/taro'
|
||||
import {Popup} from '@nutui/nutui-react-taro'
|
||||
import {UserParam} from "@/api/system/user/model";
|
||||
import {Button} from '@nutui/nutui-react-taro'
|
||||
import {Form, Input} from '@nutui/nutui-react-taro'
|
||||
import {Copyright, Version} from "@/config/app";
|
||||
const UserFooter = () => {
|
||||
const [openLoginByPhone, setOpenLoginByPhone] = useState(false)
|
||||
const [clickNum, setClickNum] = useState<number>(0)
|
||||
const [FormData, setFormData] = useState<UserParam>(
|
||||
{
|
||||
phone: undefined,
|
||||
password: undefined
|
||||
}
|
||||
)
|
||||
|
||||
const onLoginByPhone = () => {
|
||||
setFormData({})
|
||||
setClickNum(clickNum + 1);
|
||||
if (clickNum > 10) {
|
||||
setOpenLoginByPhone(true);
|
||||
}
|
||||
}
|
||||
|
||||
const closeLoginByPhone = () => {
|
||||
setClickNum(0)
|
||||
setOpenLoginByPhone(false)
|
||||
}
|
||||
|
||||
// 提交表单
|
||||
const submitByPhone = (values: any) => {
|
||||
loginBySms({
|
||||
phone: values.phone,
|
||||
code: values.code
|
||||
}).then(() => {
|
||||
setOpenLoginByPhone(false);
|
||||
setTimeout(() => {
|
||||
Taro.reLaunch({
|
||||
url: '/pages/index/index'
|
||||
})
|
||||
},1000)
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className={'text-center py-4 w-full text-gray-300'} onClick={onLoginByPhone}>
|
||||
<div className={'text-xs text-gray-400 py-1'}>当前版本:{Version}</div>
|
||||
<div className={'text-xs text-gray-400 py-1'}>Copyright © { new Date().getFullYear() } {Copyright}</div>
|
||||
</div>
|
||||
|
||||
<Popup
|
||||
style={{width: '350px', padding: '10px'}}
|
||||
visible={openLoginByPhone}
|
||||
closeOnOverlayClick={false}
|
||||
closeable={true}
|
||||
onClose={closeLoginByPhone}
|
||||
>
|
||||
<Form
|
||||
style={{width: '350px',padding: '10px'}}
|
||||
divider
|
||||
initialValues={FormData}
|
||||
labelPosition="left"
|
||||
onFinish={(values) => submitByPhone(values)}
|
||||
footer={
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
width: '100%'
|
||||
}}
|
||||
>
|
||||
<Button nativeType="submit" block style={{backgroundColor: '#000000',color: '#ffffff'}}>
|
||||
提交
|
||||
</Button>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<Form.Item
|
||||
label={'手机号码'}
|
||||
name="phone"
|
||||
required
|
||||
rules={[{message: '手机号码'}]}
|
||||
>
|
||||
<Input placeholder="请输入手机号码" maxLength={11} type="text"/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={'短信验证码'}
|
||||
name="code"
|
||||
required
|
||||
rules={[{message: '短信验证码'}]}
|
||||
>
|
||||
<Input placeholder="请输入短信验证码" maxLength={6} type="text"/>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</Popup>
|
||||
</>
|
||||
)
|
||||
}
|
||||
export default UserFooter
|
||||
@@ -1,69 +0,0 @@
|
||||
import {useEffect} from "react";
|
||||
import navTo from "@/utils/common";
|
||||
import {View, Text} from '@tarojs/components';
|
||||
import {ArrowRight, Wallet, Comment, Transit, Refund, Package} from '@nutui/icons-react-taro';
|
||||
|
||||
function UserOrder() {
|
||||
|
||||
const reload = () => {
|
||||
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
reload()
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
<View className={'px-4 pb-2'}>
|
||||
<View
|
||||
className={'user-card w-full flex flex-col justify-around rounded-xl shadow-sm'}
|
||||
style={{
|
||||
background: 'linear-gradient(to bottom, #ffffff, #ffffff)', // 这种情况建议使用类名来控制样式(引入外联样式)
|
||||
// margin: '10px auto 0px auto',
|
||||
height: '120px',
|
||||
// borderRadius: '22px 22px 0 0',
|
||||
}}
|
||||
>
|
||||
<View className={'title-bar flex justify-between pt-2'}>
|
||||
<Text className={'title font-medium px-4'}>我的订单</Text>
|
||||
<View className={'more flex items-center px-2'} onClick={() => navTo('/user/order/order', true)}>
|
||||
<Text className={'text-xs text-gray-500'}>全部订单</Text>
|
||||
<ArrowRight color="#cccccc" size={12}/>
|
||||
</View>
|
||||
</View>
|
||||
<View className={'flex justify-around pb-1'}>
|
||||
<View className={'item flex justify-center flex-col items-center'}
|
||||
onClick={() => navTo('/user/order/order?statusFilter=0', true)}>
|
||||
<Wallet size={26} className={'font-normal text-gray-500'}/>
|
||||
<Text className={'text-sm text-gray-600 py-1'}>待付款</Text>
|
||||
</View>
|
||||
<View className={'item flex justify-center flex-col items-center'}
|
||||
onClick={() => navTo('/user/order/order?statusFilter=1', true)}>
|
||||
<Package size={26} className={'text-gray-500 font-normal'}/>
|
||||
<Text className={'text-sm text-gray-600 py-1'}>待发货</Text>
|
||||
</View>
|
||||
<View className={'item flex justify-center flex-col items-center'}
|
||||
onClick={() => navTo('/user/order/order?statusFilter=3', true)}>
|
||||
<Transit size={24} className={'text-gray-500 font-normal'}/>
|
||||
<Text className={'text-sm text-gray-600 py-1'}>待收货</Text>
|
||||
</View>
|
||||
<View className={'item flex justify-center flex-col items-center'}
|
||||
onClick={() => navTo('/user/order/order?statusFilter=5', true)}>
|
||||
<Comment size={24} className={'text-gray-500 font-normal'}/>
|
||||
<Text className={'text-sm text-gray-600 py-1'}>已完成</Text>
|
||||
</View>
|
||||
<View className={'item flex justify-center flex-col items-center'}
|
||||
onClick={() => navTo('/user/order/order?statusFilter=6', true)}>
|
||||
<Refund size={26} className={'font-normal text-gray-500'}/>
|
||||
<Text className={'text-sm text-gray-600 py-1'}>退货/售后</Text>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
</>
|
||||
|
||||
)
|
||||
}
|
||||
|
||||
export default UserOrder;
|
||||
@@ -1,3 +0,0 @@
|
||||
export default definePageConfig({
|
||||
navigationBarTitleText: '管理中心'
|
||||
})
|
||||
@@ -1,49 +0,0 @@
|
||||
import { View, Text } from '@tarojs/components'
|
||||
import Taro from '@tarojs/taro'
|
||||
import { ConfigProvider, Grid } from '@nutui/nutui-react-taro'
|
||||
import { useUser } from '@/hooks/useUser'
|
||||
import { useThemeStyles } from '@/hooks/useTheme'
|
||||
import { Edit } from '@nutui/icons-react-taro'
|
||||
|
||||
function Admin() {
|
||||
const { isAdmin } = useUser()
|
||||
const themeStyles = useThemeStyles()
|
||||
|
||||
if (!isAdmin()) {
|
||||
return (
|
||||
<View className="bg-gray-100 min-h-screen flex items-center justify-center">
|
||||
<View className="text-center">
|
||||
<Text className="text-gray-400 text-sm">暂无访问权限</Text>
|
||||
</View>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<View className="bg-gray-100 min-h-screen">
|
||||
<View className="px-4 py-6" style={themeStyles.primaryBackground}>
|
||||
<Text className="text-white text-lg font-bold">内容管理</Text>
|
||||
<View className="mt-1">
|
||||
<Text className="text-white text-sm opacity-80">管理文章、内容等数据</Text>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
<View className="bg-white mx-4 mt-4 rounded-xl p-4">
|
||||
<Text className="font-semibold text-gray-800 mb-4 block">管理工具</Text>
|
||||
<ConfigProvider>
|
||||
<Grid columns={4}>
|
||||
<Grid.Item text="文章管理" onClick={() => Taro.navigateTo({ url: '/admin/article/index' })}>
|
||||
<View className="text-center">
|
||||
<View className="w-12 h-12 bg-blue-500 rounded-xl flex items-center justify-center mx-auto mb-2">
|
||||
<Edit color="#3b82f6" size="20" />
|
||||
</View>
|
||||
</View>
|
||||
</Grid.Item>
|
||||
</Grid>
|
||||
</ConfigProvider>
|
||||
</View>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
export default Admin
|
||||
@@ -1,320 +0,0 @@
|
||||
import { request } from '../utils/request'
|
||||
|
||||
// 售后类型
|
||||
export type AfterSaleType = 'refund' | 'return' | 'exchange' | 'repair'
|
||||
|
||||
// 售后状态
|
||||
export type AfterSaleStatus =
|
||||
| 'pending' // 待审核
|
||||
| 'approved' // 已同意
|
||||
| 'rejected' // 已拒绝
|
||||
| 'processing' // 处理中
|
||||
| 'completed' // 已完成
|
||||
| 'cancelled' // 已取消
|
||||
|
||||
// 售后进度记录
|
||||
export interface ProgressRecord {
|
||||
id: string
|
||||
time: string
|
||||
status: string
|
||||
description: string
|
||||
operator?: string
|
||||
remark?: string
|
||||
images?: string[]
|
||||
}
|
||||
|
||||
// 售后详情
|
||||
export interface AfterSaleDetail {
|
||||
id: string
|
||||
orderId: string
|
||||
orderNo: string
|
||||
type: AfterSaleType
|
||||
status: AfterSaleStatus
|
||||
reason: string
|
||||
description: string
|
||||
amount: number
|
||||
applyTime: string
|
||||
processTime?: string
|
||||
completeTime?: string
|
||||
rejectReason?: string
|
||||
contactPhone?: string
|
||||
evidenceImages: string[]
|
||||
progressRecords: ProgressRecord[]
|
||||
}
|
||||
|
||||
// 售后申请参数
|
||||
export interface AfterSaleApplyParams {
|
||||
orderId: string
|
||||
type: AfterSaleType
|
||||
reason: string
|
||||
description: string
|
||||
amount: number
|
||||
contactPhone?: string
|
||||
evidenceImages?: string[]
|
||||
goodsItems?: Array<{
|
||||
goodsId: string
|
||||
quantity: number
|
||||
}>
|
||||
}
|
||||
|
||||
// 售后列表查询参数
|
||||
export interface AfterSaleListParams {
|
||||
page?: number
|
||||
pageSize?: number
|
||||
status?: AfterSaleStatus
|
||||
type?: AfterSaleType
|
||||
startTime?: string
|
||||
endTime?: string
|
||||
}
|
||||
|
||||
// 售后列表响应
|
||||
export interface AfterSaleListResponse {
|
||||
success: boolean
|
||||
data: {
|
||||
list: AfterSaleDetail[]
|
||||
total: number
|
||||
page: number
|
||||
pageSize: number
|
||||
}
|
||||
message?: string
|
||||
}
|
||||
|
||||
// 售后详情响应
|
||||
export interface AfterSaleDetailResponse {
|
||||
success: boolean
|
||||
data: AfterSaleDetail
|
||||
message?: string
|
||||
}
|
||||
|
||||
// 售后类型映射
|
||||
export const AFTER_SALE_TYPE_MAP = {
|
||||
'refund': '退款',
|
||||
'return': '退货',
|
||||
'exchange': '换货',
|
||||
'repair': '维修'
|
||||
}
|
||||
|
||||
// 售后状态映射
|
||||
export const AFTER_SALE_STATUS_MAP = {
|
||||
'pending': '待审核',
|
||||
'approved': '已同意',
|
||||
'rejected': '已拒绝',
|
||||
'processing': '处理中',
|
||||
'completed': '已完成',
|
||||
'cancelled': '已取消'
|
||||
}
|
||||
|
||||
// 状态颜色映射
|
||||
export const STATUS_COLOR_MAP = {
|
||||
'pending': '#faad14',
|
||||
'approved': '#52c41a',
|
||||
'rejected': '#ff4d4f',
|
||||
'processing': '#1890ff',
|
||||
'completed': '#52c41a',
|
||||
'cancelled': '#999'
|
||||
}
|
||||
|
||||
// 申请售后
|
||||
export const applyAfterSale = async (params: AfterSaleApplyParams): Promise<AfterSaleDetailResponse> => {
|
||||
try {
|
||||
const response = await request<AfterSaleDetailResponse>({
|
||||
url: '/api/after-sale/apply',
|
||||
method: 'POST',
|
||||
data: params
|
||||
})
|
||||
|
||||
return response
|
||||
} catch (error) {
|
||||
console.error('申请售后失败:', error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
// 查询售后详情
|
||||
export const getAfterSaleDetail = async (params: {
|
||||
orderId?: string
|
||||
afterSaleId?: string
|
||||
}): Promise<AfterSaleDetailResponse> => {
|
||||
try {
|
||||
const response = await request<AfterSaleDetailResponse>({
|
||||
url: '/api/after-sale/detail',
|
||||
method: 'GET',
|
||||
data: params
|
||||
})
|
||||
|
||||
return response
|
||||
} catch (error) {
|
||||
console.error('查询售后详情失败:', error)
|
||||
|
||||
// 返回模拟数据作为降级方案
|
||||
return getMockAfterSaleDetail(params)
|
||||
}
|
||||
}
|
||||
|
||||
// 查询售后列表
|
||||
export const getAfterSaleList = async (params: AfterSaleListParams): Promise<AfterSaleListResponse> => {
|
||||
try {
|
||||
const response = await request<AfterSaleListResponse>({
|
||||
url: '/api/after-sale/list',
|
||||
method: 'GET',
|
||||
data: params
|
||||
})
|
||||
|
||||
return response
|
||||
} catch (error) {
|
||||
console.error('查询售后列表失败:', error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
// 撤销售后申请
|
||||
export const cancelAfterSale = async (afterSaleId: string): Promise<{ success: boolean; message?: string }> => {
|
||||
try {
|
||||
const response = await request<{ success: boolean; message?: string }>({
|
||||
url: '/api/after-sale/cancel',
|
||||
method: 'POST',
|
||||
data: { afterSaleId }
|
||||
})
|
||||
|
||||
return response
|
||||
} catch (error) {
|
||||
console.error('撤销售后申请失败:', error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
// 获取模拟售后详情数据
|
||||
const getMockAfterSaleDetail = (params: {
|
||||
orderId?: string
|
||||
afterSaleId?: string
|
||||
}): AfterSaleDetailResponse => {
|
||||
const now = new Date()
|
||||
const yesterday = new Date(now.getTime() - 24 * 60 * 60 * 1000)
|
||||
const twoDaysAgo = new Date(now.getTime() - 2 * 24 * 60 * 60 * 1000)
|
||||
|
||||
const mockData: AfterSaleDetailResponse = {
|
||||
success: true,
|
||||
data: {
|
||||
id: 'AS' + Date.now(),
|
||||
orderId: params.orderId || '',
|
||||
orderNo: 'ORD' + Date.now(),
|
||||
type: 'refund',
|
||||
status: 'processing',
|
||||
reason: '商品质量问题',
|
||||
description: '收到的商品有明显瑕疵,包装破损,希望申请退款处理',
|
||||
amount: 9999,
|
||||
applyTime: twoDaysAgo.toISOString(),
|
||||
processTime: yesterday.toISOString(),
|
||||
contactPhone: '138****5678',
|
||||
evidenceImages: [
|
||||
'https://via.placeholder.com/200x200?text=Evidence1',
|
||||
'https://via.placeholder.com/200x200?text=Evidence2'
|
||||
],
|
||||
progressRecords: [
|
||||
{
|
||||
id: '1',
|
||||
time: now.toISOString(),
|
||||
status: '处理中',
|
||||
description: '客服正在处理您的申请,请耐心等待',
|
||||
operator: '客服小王',
|
||||
remark: '预计1-2个工作日内完成处理'
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
time: new Date(now.getTime() - 4 * 60 * 60 * 1000).toISOString(),
|
||||
status: '已审核',
|
||||
description: '您的申请已通过审核,正在安排退款处理',
|
||||
operator: '审核员张三'
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
time: yesterday.toISOString(),
|
||||
status: '已受理',
|
||||
description: '我们已收到您的申请,正在进行审核',
|
||||
operator: '系统'
|
||||
},
|
||||
{
|
||||
id: '4',
|
||||
time: twoDaysAgo.toISOString(),
|
||||
status: '已提交',
|
||||
description: '您已成功提交售后申请',
|
||||
operator: '用户'
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
return mockData
|
||||
}
|
||||
|
||||
// 格式化售后状态
|
||||
export const formatAfterSaleStatus = (status: AfterSaleStatus): {
|
||||
text: string
|
||||
color: string
|
||||
icon: string
|
||||
} => {
|
||||
const statusMap = {
|
||||
'pending': { text: '待审核', color: '#faad14', icon: '⏳' },
|
||||
'approved': { text: '已同意', color: '#52c41a', icon: '✅' },
|
||||
'rejected': { text: '已拒绝', color: '#ff4d4f', icon: '❌' },
|
||||
'processing': { text: '处理中', color: '#1890ff', icon: '🔄' },
|
||||
'completed': { text: '已完成', color: '#52c41a', icon: '✅' },
|
||||
'cancelled': { text: '已取消', color: '#999', icon: '⭕' }
|
||||
}
|
||||
|
||||
return statusMap[status] || { text: status, color: '#666', icon: '📋' }
|
||||
}
|
||||
|
||||
// 计算预计处理时间
|
||||
export const calculateEstimatedTime = (
|
||||
applyTime: string,
|
||||
type: AfterSaleType,
|
||||
status: AfterSaleStatus
|
||||
): string => {
|
||||
const applyDate = new Date(applyTime)
|
||||
let estimatedDays = 3 // 默认3个工作日
|
||||
|
||||
// 根据售后类型调整预计时间
|
||||
switch (type) {
|
||||
case 'refund':
|
||||
estimatedDays = 3 // 退款3个工作日
|
||||
break
|
||||
case 'return':
|
||||
estimatedDays = 7 // 退货7个工作日
|
||||
break
|
||||
case 'exchange':
|
||||
estimatedDays = 10 // 换货10个工作日
|
||||
break
|
||||
case 'repair':
|
||||
estimatedDays = 15 // 维修15个工作日
|
||||
break
|
||||
}
|
||||
|
||||
// 根据当前状态调整
|
||||
if (status === 'completed') {
|
||||
return '已完成'
|
||||
} else if (status === 'rejected' || status === 'cancelled') {
|
||||
return '已结束'
|
||||
}
|
||||
|
||||
const estimatedDate = new Date(applyDate.getTime() + estimatedDays * 24 * 60 * 60 * 1000)
|
||||
return `预计${estimatedDate.getMonth() + 1}月${estimatedDate.getDate()}日前完成`
|
||||
}
|
||||
|
||||
// 获取售后进度步骤
|
||||
export const getAfterSaleSteps = (type: AfterSaleType, status: AfterSaleStatus) => {
|
||||
const baseSteps = [
|
||||
{ title: '提交申请', description: '用户提交售后申请' },
|
||||
{ title: '审核中', description: '客服审核申请材料' },
|
||||
{ title: '处理中', description: '正在处理您的申请' },
|
||||
{ title: '完成', description: '售后处理完成' }
|
||||
]
|
||||
|
||||
// 根据类型调整步骤
|
||||
if (type === 'return' || type === 'exchange') {
|
||||
baseSteps.splice(2, 0, { title: '等待收货', description: '等待用户寄回商品' })
|
||||
baseSteps.splice(3, 0, { title: '确认收货', description: '商家确认收到退回商品' })
|
||||
}
|
||||
|
||||
return baseSteps
|
||||
}
|
||||
@@ -1,226 +0,0 @@
|
||||
/**
|
||||
* 数据分析/运营监控 API
|
||||
*/
|
||||
import { request } from '../utils/request'
|
||||
import type {
|
||||
OverviewStats,
|
||||
ApiCallStat,
|
||||
ErrorStat,
|
||||
PerformanceMetric,
|
||||
RegionStat,
|
||||
DeviceStat,
|
||||
BrowserStat,
|
||||
OsStat,
|
||||
SourceStat,
|
||||
PageStat,
|
||||
EventTrack,
|
||||
AnalyticsParam,
|
||||
} from '../types/analytics'
|
||||
|
||||
// ==================== 概览统计 ====================
|
||||
|
||||
/** 获取概览统计 */
|
||||
export const getOverviewStats = (websiteId: number, date?: string) => {
|
||||
return request<OverviewStats>({
|
||||
url: '/developer/analytics/overview',
|
||||
method: 'GET',
|
||||
params: { websiteId, date },
|
||||
})
|
||||
}
|
||||
|
||||
/** 获取趋势数据 */
|
||||
export const getTrendData = (
|
||||
websiteId: number,
|
||||
startDate: string,
|
||||
endDate: string,
|
||||
granularity: 'hour' | 'day' | 'week' | 'month' = 'day'
|
||||
) => {
|
||||
return request<{ list: ApiCallStat[] }>({
|
||||
url: '/developer/analytics/trend',
|
||||
method: 'GET',
|
||||
params: { websiteId, startDate, endDate, granularity },
|
||||
})
|
||||
}
|
||||
|
||||
/** 获取今日实时数据 */
|
||||
export const getRealtimeData = (websiteId: number) => {
|
||||
return request<{
|
||||
uv: number
|
||||
pv: number
|
||||
apiCalls: number
|
||||
errors: number
|
||||
activeUsers: number
|
||||
}>({
|
||||
url: '/developer/analytics/realtime',
|
||||
method: 'GET',
|
||||
params: { websiteId },
|
||||
})
|
||||
}
|
||||
|
||||
// ==================== API 调用统计 ====================
|
||||
|
||||
/** 获取 API 调用统计 */
|
||||
export const pageApiCalls = (params: AnalyticsParam) => {
|
||||
return request<{ list: ApiCallStat[]; total: number }>({
|
||||
url: '/developer/analytics/api-calls',
|
||||
method: 'GET',
|
||||
params,
|
||||
})
|
||||
}
|
||||
|
||||
// ==================== 错误统计 ====================
|
||||
|
||||
/** 获取错误列表 */
|
||||
export const pageErrors = (params: AnalyticsParam) => {
|
||||
return request<{ list: ErrorStat[]; total: number }>({
|
||||
url: '/developer/analytics/errors',
|
||||
method: 'GET',
|
||||
params,
|
||||
})
|
||||
}
|
||||
|
||||
/** 获取错误详情 */
|
||||
export const getErrorDetail = (id: number) => {
|
||||
return request<ErrorStat & { samples: any[] }>({
|
||||
url: `/developer/analytics/errors/${id}`,
|
||||
method: 'GET',
|
||||
})
|
||||
}
|
||||
|
||||
// ==================== 性能指标 ====================
|
||||
|
||||
/** 获取性能指标 */
|
||||
export const getPerformanceMetrics = (websiteId: number) => {
|
||||
return request<{ list: PerformanceMetric[] }>({
|
||||
url: '/developer/analytics/performance',
|
||||
method: 'GET',
|
||||
params: { websiteId },
|
||||
})
|
||||
}
|
||||
|
||||
/** 获取性能趋势 */
|
||||
export const getPerformanceTrend = (
|
||||
websiteId: number,
|
||||
metric: string,
|
||||
startDate: string,
|
||||
endDate: string
|
||||
) => {
|
||||
return request<{ list: PerformanceMetric[] }>({
|
||||
url: '/developer/analytics/performance/trend',
|
||||
method: 'GET',
|
||||
params: { websiteId, metric, startDate, endDate },
|
||||
})
|
||||
}
|
||||
|
||||
// ==================== 用户统计 ====================
|
||||
|
||||
/** 获取用户统计 */
|
||||
export const getUserStats = (websiteId: number) => {
|
||||
return request<{
|
||||
total: number
|
||||
active: number
|
||||
new: number
|
||||
retention: number
|
||||
}>({
|
||||
url: '/developer/analytics/users',
|
||||
method: 'GET',
|
||||
params: { websiteId },
|
||||
})
|
||||
}
|
||||
|
||||
/** 获取新增用户趋势 */
|
||||
export const getNewUsersTrend = (
|
||||
websiteId: number,
|
||||
startDate: string,
|
||||
endDate: string
|
||||
) => {
|
||||
return request<{ list: { date: string; count: number }[] }>({
|
||||
url: '/developer/analytics/users/trend',
|
||||
method: 'GET',
|
||||
params: { websiteId, startDate, endDate },
|
||||
})
|
||||
}
|
||||
|
||||
// ==================== 区域分布 ====================
|
||||
|
||||
/** 获取区域分布 */
|
||||
export const getRegionStats = (websiteId: number) => {
|
||||
return request<{ list: RegionStat[] }>({
|
||||
url: '/developer/analytics/regions',
|
||||
method: 'GET',
|
||||
params: { websiteId },
|
||||
})
|
||||
}
|
||||
|
||||
// ==================== 设备统计 ====================
|
||||
|
||||
/** 获取设备类型统计 */
|
||||
export const getDeviceStats = (websiteId: number) => {
|
||||
return request<{ list: DeviceStat[] }>({
|
||||
url: '/developer/analytics/devices',
|
||||
method: 'GET',
|
||||
params: { websiteId },
|
||||
})
|
||||
}
|
||||
|
||||
/** 获取浏览器统计 */
|
||||
export const getBrowserStats = (websiteId: number) => {
|
||||
return request<{ list: BrowserStat[] }>({
|
||||
url: '/developer/analytics/browsers',
|
||||
method: 'GET',
|
||||
params: { websiteId },
|
||||
})
|
||||
}
|
||||
|
||||
/** 获取操作系统统计 */
|
||||
export const getOsStats = (websiteId: number) => {
|
||||
return request<{ list: OsStat[] }>({
|
||||
url: '/developer/analytics/os',
|
||||
method: 'GET',
|
||||
params: { websiteId },
|
||||
})
|
||||
}
|
||||
|
||||
// ==================== 来源统计 ====================
|
||||
|
||||
/** 获取流量来源 */
|
||||
export const getSourceStats = (websiteId: number) => {
|
||||
return request<{ list: SourceStat[] }>({
|
||||
url: '/developer/analytics/sources',
|
||||
method: 'GET',
|
||||
params: { websiteId },
|
||||
})
|
||||
}
|
||||
|
||||
/** 获取页面访问排行 */
|
||||
export const getPageStats = (params: AnalyticsParam) => {
|
||||
return request<{ list: PageStat[]; total: number }>({
|
||||
url: '/developer/analytics/pages',
|
||||
method: 'GET',
|
||||
params,
|
||||
})
|
||||
}
|
||||
|
||||
// ==================== 事件追踪 ====================
|
||||
|
||||
/** 获取事件列表 */
|
||||
export const pageEvents = (params: AnalyticsParam) => {
|
||||
return request<{ list: EventTrack[]; total: number }>({
|
||||
url: '/developer/analytics/events',
|
||||
method: 'GET',
|
||||
params,
|
||||
})
|
||||
}
|
||||
|
||||
/** 触发自定义事件 */
|
||||
export const trackEvent = (data: {
|
||||
websiteId: number
|
||||
event: string
|
||||
properties?: Record<string, any>
|
||||
}) => {
|
||||
return request<void>({
|
||||
url: '/developer/analytics/track',
|
||||
method: 'POST',
|
||||
data,
|
||||
})
|
||||
}
|
||||
@@ -1,101 +0,0 @@
|
||||
import request from '@/utils/request';
|
||||
import type { ApiResult, PageResult } from '@/api/index';
|
||||
import type { AppCredential, AppCredentialParam } from './model';
|
||||
|
||||
/**
|
||||
* 分页查询应用密钥凭证
|
||||
*/
|
||||
export async function pageAppCredential(params: AppCredentialParam) {
|
||||
const res = await request.get<ApiResult<PageResult<AppCredential>>>(
|
||||
'/app/app-credential/page',
|
||||
params
|
||||
);
|
||||
if (res.code === 0) {
|
||||
return res.data;
|
||||
}
|
||||
return Promise.reject(new Error(res.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询应用密钥凭证列表
|
||||
*/
|
||||
export async function listAppCredential(params?: AppCredentialParam) {
|
||||
const res = await request.get<ApiResult<AppCredential[]>>(
|
||||
'/app/app-credential',
|
||||
params
|
||||
);
|
||||
if (res.code === 0 && res.data) {
|
||||
return res.data;
|
||||
}
|
||||
return Promise.reject(new Error(res.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加应用密钥凭证
|
||||
*/
|
||||
export async function addAppCredential(data: AppCredential) {
|
||||
const res = await request.post<ApiResult<unknown>>(
|
||||
'/app/app-credential',
|
||||
data
|
||||
);
|
||||
if (res.code === 0) {
|
||||
return res.message;
|
||||
}
|
||||
return Promise.reject(new Error(res.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改应用密钥凭证
|
||||
*/
|
||||
export async function updateAppCredential(data: AppCredential) {
|
||||
const res = await request.put<ApiResult<unknown>>(
|
||||
'/app/app-credential',
|
||||
data
|
||||
);
|
||||
if (res.code === 0) {
|
||||
return res.message;
|
||||
}
|
||||
return Promise.reject(new Error(res.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除应用密钥凭证
|
||||
*/
|
||||
export async function removeAppCredential(id?: number) {
|
||||
const res = await request.del<ApiResult<unknown>>(
|
||||
'/app/app-credential/' + id
|
||||
);
|
||||
if (res.code === 0) {
|
||||
return res.message;
|
||||
}
|
||||
return Promise.reject(new Error(res.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量删除应用密钥凭证
|
||||
*/
|
||||
export async function removeBatchAppCredential(data: (number | undefined)[]) {
|
||||
const res = await request.del<ApiResult<unknown>>(
|
||||
'/app/app-credential/batch',
|
||||
{
|
||||
data
|
||||
}
|
||||
);
|
||||
if (res.code === 0) {
|
||||
return res.message;
|
||||
}
|
||||
return Promise.reject(new Error(res.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据id查询应用密钥凭证
|
||||
*/
|
||||
export async function getAppCredential(id: number) {
|
||||
const res = await request.get<ApiResult<AppCredential>>(
|
||||
'/app/app-credential/' + id
|
||||
);
|
||||
if (res.code === 0 && res.data) {
|
||||
return res.data;
|
||||
}
|
||||
return Promise.reject(new Error(res.message));
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
import type { PageParam } from '@/api/index';
|
||||
|
||||
/**
|
||||
* 应用密钥凭证
|
||||
*/
|
||||
export interface AppCredential {
|
||||
// 自增ID
|
||||
id?: string;
|
||||
// 关联应用ID
|
||||
websiteId?: string;
|
||||
// 凭证名称,如生产环境密钥
|
||||
name?: string;
|
||||
// App ID(公开)
|
||||
appId?: string;
|
||||
// App Secret(加密存储)
|
||||
appSecret?: string;
|
||||
// 凭证类型: server/client/webhook
|
||||
type?: string;
|
||||
// 权限范围,空格分隔
|
||||
scopes?: string;
|
||||
// 到期时间,NULL=永不过期
|
||||
expireTime?: string;
|
||||
// 最后使用时间
|
||||
lastUsedAt?: string;
|
||||
//
|
||||
remark?: string;
|
||||
// 排序(数字越小越靠前)
|
||||
sortNumber?: number;
|
||||
// 状态, 0正常, 1冻结
|
||||
status?: number;
|
||||
// 是否删除, 0否, 1是
|
||||
deleted?: number;
|
||||
// 用户ID
|
||||
userId?: number;
|
||||
// 租户id
|
||||
tenantId?: number;
|
||||
// 创建时间
|
||||
createTime?: string;
|
||||
// 修改时间
|
||||
updateTime?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 应用密钥凭证搜索条件
|
||||
*/
|
||||
export interface AppCredentialParam extends PageParam {
|
||||
id?: number;
|
||||
keywords?: string;
|
||||
}
|
||||
@@ -1,101 +0,0 @@
|
||||
import request from '@/utils/request';
|
||||
import type { ApiResult, PageResult } from '@/api/index';
|
||||
import type { AppEvent, AppEventParam } from './model';
|
||||
|
||||
/**
|
||||
* 分页查询应用操作动态
|
||||
*/
|
||||
export async function pageAppEvent(params: AppEventParam) {
|
||||
const res = await request.get<ApiResult<PageResult<AppEvent>>>(
|
||||
'/app/app-event/page',
|
||||
params
|
||||
);
|
||||
if (res.code === 0) {
|
||||
return res.data;
|
||||
}
|
||||
return Promise.reject(new Error(res.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询应用操作动态列表
|
||||
*/
|
||||
export async function listAppEvent(params?: AppEventParam) {
|
||||
const res = await request.get<ApiResult<AppEvent[]>>(
|
||||
'/app/app-event',
|
||||
params
|
||||
);
|
||||
if (res.code === 0 && res.data) {
|
||||
return res.data;
|
||||
}
|
||||
return Promise.reject(new Error(res.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加应用操作动态
|
||||
*/
|
||||
export async function addAppEvent(data: AppEvent) {
|
||||
const res = await request.post<ApiResult<unknown>>(
|
||||
'/app/app-event',
|
||||
data
|
||||
);
|
||||
if (res.code === 0) {
|
||||
return res.message;
|
||||
}
|
||||
return Promise.reject(new Error(res.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改应用操作动态
|
||||
*/
|
||||
export async function updateAppEvent(data: AppEvent) {
|
||||
const res = await request.put<ApiResult<unknown>>(
|
||||
'/app/app-event',
|
||||
data
|
||||
);
|
||||
if (res.code === 0) {
|
||||
return res.message;
|
||||
}
|
||||
return Promise.reject(new Error(res.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除应用操作动态
|
||||
*/
|
||||
export async function removeAppEvent(id?: number) {
|
||||
const res = await request.del<ApiResult<unknown>>(
|
||||
'/app/app-event/' + id
|
||||
);
|
||||
if (res.code === 0) {
|
||||
return res.message;
|
||||
}
|
||||
return Promise.reject(new Error(res.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量删除应用操作动态
|
||||
*/
|
||||
export async function removeBatchAppEvent(data: (number | undefined)[]) {
|
||||
const res = await request.del<ApiResult<unknown>>(
|
||||
'/app/app-event/batch',
|
||||
{
|
||||
data
|
||||
}
|
||||
);
|
||||
if (res.code === 0) {
|
||||
return res.message;
|
||||
}
|
||||
return Promise.reject(new Error(res.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据id查询应用操作动态
|
||||
*/
|
||||
export async function getAppEvent(id: number) {
|
||||
const res = await request.get<ApiResult<AppEvent>>(
|
||||
'/app/app-event/' + id
|
||||
);
|
||||
if (res.code === 0 && res.data) {
|
||||
return res.data;
|
||||
}
|
||||
return Promise.reject(new Error(res.message));
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
import type { PageParam } from '@/api/index';
|
||||
|
||||
/**
|
||||
* 应用操作动态
|
||||
*/
|
||||
export interface AppEvent {
|
||||
// 自增ID
|
||||
id?: string;
|
||||
// 关联应用ID
|
||||
websiteId?: string;
|
||||
// 事件类型: created/published/updated/domain_bound/member_added/status_changed
|
||||
eventType?: string;
|
||||
// 事件标题,如已发布
|
||||
title?: string;
|
||||
// 详细描述
|
||||
content?: string;
|
||||
// 操作人用户ID
|
||||
operatorId?: string;
|
||||
// 操作人名称(冗余)
|
||||
operator?: string;
|
||||
// 关联ID,如版本ID
|
||||
refId?: string;
|
||||
// 关联类型
|
||||
refType?: string;
|
||||
// 扩展数据
|
||||
extra?: string;
|
||||
// 排序(数字越小越靠前)
|
||||
sortNumber?: number;
|
||||
// 状态, 0正常, 1冻结
|
||||
status?: number;
|
||||
// 用户ID
|
||||
userId?: number;
|
||||
// 租户id
|
||||
tenantId?: number;
|
||||
// 创建时间
|
||||
createTime?: string;
|
||||
// 修改时间
|
||||
updateTime?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 应用操作动态搜索条件
|
||||
*/
|
||||
export interface AppEventParam extends PageParam {
|
||||
id?: number;
|
||||
keywords?: string;
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
import request from '@/utils/request';
|
||||
import type { ApiResult, PageResult } from '@/api';
|
||||
import type { AppProduct } from './model';
|
||||
|
||||
const BASE = '/app/product';
|
||||
|
||||
/** 分页查询我的应用 */
|
||||
export async function pageMyApps(params?: { current?: number; size?: number }) {
|
||||
const res = await request.get<ApiResult<PageResult<AppProduct>>>(BASE + '/my/page', { params });
|
||||
if (res.code === 0) return res.data;
|
||||
return Promise.reject(new Error(res.message));
|
||||
}
|
||||
|
||||
/** 获取我参与的应用列表 */
|
||||
export async function listJoinedApps(params?: { page?: number; limit?: number }) {
|
||||
const res = await request.get<ApiResult<PageResult<AppProduct>>>(BASE + '/page', { params });
|
||||
if (res.code === 0) return res.data;
|
||||
return Promise.reject(new Error(res.message));
|
||||
}
|
||||
@@ -1,51 +0,0 @@
|
||||
export interface AppProduct {
|
||||
productId?: number
|
||||
productName?: string
|
||||
productCode?: string
|
||||
appType?: number
|
||||
icon?: string
|
||||
qrcode?: string
|
||||
description?: string
|
||||
domain?: string
|
||||
homeUrl?: string
|
||||
adminUrl?: string
|
||||
version?: string
|
||||
status?: number
|
||||
statusText?: string
|
||||
running?: number
|
||||
expirationTime?: string
|
||||
createTime?: string
|
||||
updateTime?: string
|
||||
publishStatus?: string
|
||||
}
|
||||
|
||||
export const APP_TYPE_NAME: Record<number, string> = {
|
||||
10: '网站',
|
||||
20: '微信小程序',
|
||||
30: '抖音小程序',
|
||||
40: '百度小程序',
|
||||
50: '支付宝小程序',
|
||||
60: 'Android APP',
|
||||
70: 'iOS APP',
|
||||
80: 'macOS 应用',
|
||||
90: 'Windows 应用',
|
||||
100: '插件',
|
||||
}
|
||||
|
||||
export const STATUS_NAME: Record<number, string> = {
|
||||
0: '未开通',
|
||||
1: '运行中',
|
||||
2: '维护中',
|
||||
3: '已关闭',
|
||||
4: '已欠费',
|
||||
5: '违规停机',
|
||||
}
|
||||
|
||||
export const STATUS_COLOR: Record<number, string> = {
|
||||
0: '#6b7280',
|
||||
1: '#10b981',
|
||||
2: '#f59e0b',
|
||||
3: '#ef4444',
|
||||
4: '#ef4444',
|
||||
5: '#ef4444',
|
||||
}
|
||||
@@ -1,101 +0,0 @@
|
||||
import request from '@/utils/request';
|
||||
import type { ApiResult, PageResult } from '@/api/index';
|
||||
import type { AppUser, AppUserParam } from './model';
|
||||
|
||||
/**
|
||||
* 分页查询应用成员
|
||||
*/
|
||||
export async function pageAppUser(params: AppUserParam) {
|
||||
const res = await request.get<ApiResult<PageResult<AppUser>>>(
|
||||
'/app/app-user/page',
|
||||
params
|
||||
);
|
||||
if (res.code === 0) {
|
||||
return res.data;
|
||||
}
|
||||
return Promise.reject(new Error(res.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询应用成员列表
|
||||
*/
|
||||
export async function listAppUser(params?: AppUserParam) {
|
||||
const res = await request.get<ApiResult<AppUser[]>>(
|
||||
'/app/app-user',
|
||||
params
|
||||
);
|
||||
if (res.code === 0 && res.data) {
|
||||
return res.data;
|
||||
}
|
||||
return Promise.reject(new Error(res.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加应用成员
|
||||
*/
|
||||
export async function addAppUser(data: AppUser) {
|
||||
const res = await request.post<ApiResult<unknown>>(
|
||||
'/app/app-user',
|
||||
data
|
||||
);
|
||||
if (res.code === 0) {
|
||||
return res.message;
|
||||
}
|
||||
return Promise.reject(new Error(res.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改应用成员
|
||||
*/
|
||||
export async function updateAppUser(data: AppUser) {
|
||||
const res = await request.put<ApiResult<unknown>>(
|
||||
'/app/app-user',
|
||||
data
|
||||
);
|
||||
if (res.code === 0) {
|
||||
return res.message;
|
||||
}
|
||||
return Promise.reject(new Error(res.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除应用成员
|
||||
*/
|
||||
export async function removeAppUser(id?: number) {
|
||||
const res = await request.del<ApiResult<unknown>>(
|
||||
'/app/app-user/' + id
|
||||
);
|
||||
if (res.code === 0) {
|
||||
return res.message;
|
||||
}
|
||||
return Promise.reject(new Error(res.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量删除应用成员
|
||||
*/
|
||||
export async function removeBatchAppUser(data: (number | undefined)[]) {
|
||||
const res = await request.del<ApiResult<unknown>>(
|
||||
'/app/app-user/batch',
|
||||
{
|
||||
data
|
||||
}
|
||||
);
|
||||
if (res.code === 0) {
|
||||
return res.message;
|
||||
}
|
||||
return Promise.reject(new Error(res.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据id查询应用成员
|
||||
*/
|
||||
export async function getAppUser(id: number) {
|
||||
const res = await request.get<ApiResult<AppUser>>(
|
||||
'/app/app-user/' + id
|
||||
);
|
||||
if (res.code === 0 && res.data) {
|
||||
return res.data;
|
||||
}
|
||||
return Promise.reject(new Error(res.message));
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
import type { PageParam } from '@/api/index';
|
||||
|
||||
/**
|
||||
* 应用成员
|
||||
*/
|
||||
export interface AppUser {
|
||||
// 自增ID
|
||||
id?: string;
|
||||
// 关联应用ID
|
||||
websiteId?: string;
|
||||
// 用户ID
|
||||
userId?: string;
|
||||
// 用户名(冗余)
|
||||
username?: string;
|
||||
// 头像(冗余)
|
||||
avatar?: string;
|
||||
// 角色: owner/admin/developer/viewer
|
||||
role?: string;
|
||||
// 邀请人用户ID
|
||||
inviteBy?: string;
|
||||
// 加入时间
|
||||
inviteTime?: string;
|
||||
// 排序(数字越小越靠前)
|
||||
sortNumber?: number;
|
||||
// 状态, 0正常, 1冻结
|
||||
status?: number;
|
||||
// 租户id
|
||||
tenantId?: number;
|
||||
// 创建时间
|
||||
createTime?: string;
|
||||
// 修改时间
|
||||
updateTime?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 应用成员搜索条件
|
||||
*/
|
||||
export interface AppUserParam extends PageParam {
|
||||
id?: number;
|
||||
keywords?: string;
|
||||
}
|
||||
@@ -1,101 +0,0 @@
|
||||
import request from '@/utils/request';
|
||||
import type { ApiResult, PageResult } from '@/api/index';
|
||||
import type { AppVersion, AppVersionParam } from './model';
|
||||
|
||||
/**
|
||||
* 分页查询应用版本发布记录
|
||||
*/
|
||||
export async function pageAppVersion(params: AppVersionParam) {
|
||||
const res = await request.get<ApiResult<PageResult<AppVersion>>>(
|
||||
'/app/app-version/page',
|
||||
params
|
||||
);
|
||||
if (res.code === 0) {
|
||||
return res.data;
|
||||
}
|
||||
return Promise.reject(new Error(res.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询应用版本发布记录列表
|
||||
*/
|
||||
export async function listAppVersion(params?: AppVersionParam) {
|
||||
const res = await request.get<ApiResult<AppVersion[]>>(
|
||||
'/app/app-version',
|
||||
params
|
||||
);
|
||||
if (res.code === 0 && res.data) {
|
||||
return res.data;
|
||||
}
|
||||
return Promise.reject(new Error(res.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加应用版本发布记录
|
||||
*/
|
||||
export async function addAppVersion(data: AppVersion) {
|
||||
const res = await request.post<ApiResult<unknown>>(
|
||||
'/app/app-version',
|
||||
data
|
||||
);
|
||||
if (res.code === 0) {
|
||||
return res.message;
|
||||
}
|
||||
return Promise.reject(new Error(res.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改应用版本发布记录
|
||||
*/
|
||||
export async function updateAppVersion(data: AppVersion) {
|
||||
const res = await request.put<ApiResult<unknown>>(
|
||||
'/app/app-version',
|
||||
data
|
||||
);
|
||||
if (res.code === 0) {
|
||||
return res.message;
|
||||
}
|
||||
return Promise.reject(new Error(res.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除应用版本发布记录
|
||||
*/
|
||||
export async function removeAppVersion(id?: number) {
|
||||
const res = await request.del<ApiResult<unknown>>(
|
||||
'/app/app-version/' + id
|
||||
);
|
||||
if (res.code === 0) {
|
||||
return res.message;
|
||||
}
|
||||
return Promise.reject(new Error(res.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量删除应用版本发布记录
|
||||
*/
|
||||
export async function removeBatchAppVersion(data: (number | undefined)[]) {
|
||||
const res = await request.del<ApiResult<unknown>>(
|
||||
'/app/app-version/batch',
|
||||
{
|
||||
data
|
||||
}
|
||||
);
|
||||
if (res.code === 0) {
|
||||
return res.message;
|
||||
}
|
||||
return Promise.reject(new Error(res.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据id查询应用版本发布记录
|
||||
*/
|
||||
export async function getAppVersion(id: number) {
|
||||
const res = await request.get<ApiResult<AppVersion>>(
|
||||
'/app/app-version/' + id
|
||||
);
|
||||
if (res.code === 0 && res.data) {
|
||||
return res.data;
|
||||
}
|
||||
return Promise.reject(new Error(res.message));
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
import type { PageParam } from '@/api/index';
|
||||
|
||||
/**
|
||||
* 应用版本发布记录
|
||||
*/
|
||||
export interface AppVersion {
|
||||
// 自增ID
|
||||
id?: string;
|
||||
// 关联应用ID
|
||||
websiteId?: string;
|
||||
// 版本号,如 1.0.0
|
||||
versionNo?: string;
|
||||
// 版本名称
|
||||
versionName?: string;
|
||||
// 版本更新说明
|
||||
changelog?: string;
|
||||
// 安装包地址
|
||||
packageUrl?: string;
|
||||
// 包大小(字节)
|
||||
packageSize?: string;
|
||||
// 包MD5/SHA256
|
||||
packageHash?: string;
|
||||
// 环境: development/staging/production
|
||||
env?: string;
|
||||
// 状态 0=构建中 1=已发布 2=已回滚 3=构建失败
|
||||
status?: number;
|
||||
// 是否为当前版本
|
||||
isCurrent?: string;
|
||||
// 发布人用户ID
|
||||
publishBy?: string;
|
||||
// 发布时间
|
||||
publishTime?: string;
|
||||
// 备注
|
||||
remark?: string;
|
||||
// 排序(数字越小越靠前)
|
||||
sortNumber?: number;
|
||||
// 用户ID
|
||||
userId?: number;
|
||||
// 租户id
|
||||
tenantId?: number;
|
||||
// 创建时间
|
||||
createTime?: string;
|
||||
// 修改时间
|
||||
updateTime?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 应用版本发布记录搜索条件
|
||||
*/
|
||||
export interface AppVersionParam extends PageParam {
|
||||
id?: number;
|
||||
keywords?: string;
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
import request from '@/utils/request';
|
||||
import type { ApiResult, PageResult } from '@/api';
|
||||
import type { Notification, NotificationParam, UnreadCountResult } from './model';
|
||||
|
||||
const BASE = '/app/notification';
|
||||
|
||||
/** 分页查询通知列表 */
|
||||
export async function pageNotification(params: NotificationParam) {
|
||||
const res = await request.get<ApiResult<PageResult<Notification>>>(BASE + '/page', { params });
|
||||
if (res.code === 0) return res.data;
|
||||
return Promise.reject(new Error(res.message));
|
||||
}
|
||||
|
||||
/** 查询最近通知 */
|
||||
export async function listRecentNotification(params?: { type?: string; limit?: number }) {
|
||||
const res = await request.get<ApiResult<Notification[]>>(BASE + '/recent', { params });
|
||||
if (res.code === 0 && res.data) return res.data;
|
||||
return Promise.reject(new Error(res.message));
|
||||
}
|
||||
|
||||
/** 获取未读数量统计 */
|
||||
export async function getUnreadCount() {
|
||||
const res = await request.get<ApiResult<UnreadCountResult>>(BASE + '/unread-count');
|
||||
if (res.code === 0 && res.data) return res.data;
|
||||
return Promise.reject(new Error(res.message));
|
||||
}
|
||||
|
||||
/** 标记单条通知为已读 */
|
||||
export async function markNotificationRead(id: number) {
|
||||
const res = await request.put<ApiResult<unknown>>(BASE + `/read/${id}`);
|
||||
if (res.code === 0) return res.message;
|
||||
return Promise.reject(new Error(res.message));
|
||||
}
|
||||
|
||||
/** 标记全部已读 */
|
||||
export async function markAllNotificationRead(data?: { type?: string }) {
|
||||
const res = await request.put<ApiResult<unknown>>(BASE + '/read-all', data);
|
||||
if (res.code === 0) return res.message;
|
||||
return Promise.reject(new Error(res.message));
|
||||
}
|
||||
|
||||
/** 删除单条通知 */
|
||||
export async function removeNotification(id: number) {
|
||||
const res = await request.del<ApiResult<unknown>>(BASE + '/' + id);
|
||||
if (res.code === 0) return res.message;
|
||||
return Promise.reject(new Error(res.message));
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
import type { PageParam } from '@/api/index';
|
||||
|
||||
/** 通知类型 */
|
||||
export type NotificationType = 'ticket' | 'review' | 'system' | 'resource' | 'permission' | 'member' | 'payment';
|
||||
|
||||
/** 通知实体 */
|
||||
export interface Notification {
|
||||
id?: number;
|
||||
userId?: number;
|
||||
type?: NotificationType;
|
||||
title?: string;
|
||||
content?: string;
|
||||
isRead?: number;
|
||||
refId?: number;
|
||||
refType?: string;
|
||||
linkUrl?: string;
|
||||
senderId?: number;
|
||||
senderName?: string;
|
||||
senderAvatar?: string;
|
||||
tenantId?: number;
|
||||
createTime?: string;
|
||||
updateTime?: string;
|
||||
}
|
||||
|
||||
/** 通知查询参数 */
|
||||
export interface NotificationParam extends PageParam {
|
||||
type?: NotificationType | '';
|
||||
isRead?: number;
|
||||
}
|
||||
|
||||
/** 未读统计 */
|
||||
export interface UnreadCountResult {
|
||||
total?: number;
|
||||
ticket?: number;
|
||||
review?: number;
|
||||
system?: number;
|
||||
resource?: number;
|
||||
permission?: number;
|
||||
member?: number;
|
||||
payment?: number;
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
import request from '@/utils/request';
|
||||
import type { ApiResult, PageResult } from '@/api';
|
||||
import type { Ticket, TicketReply, TicketSubmitForm, TicketQueryParams, TicketStats } from './model';
|
||||
|
||||
const BASE = '/app/ticket';
|
||||
|
||||
/** 查询我的工单(分页) */
|
||||
export async function myTickets(params: TicketQueryParams) {
|
||||
const res = await request.get<ApiResult<PageResult<Ticket>>>(BASE + '/my', { params });
|
||||
if (res.code === 0) return res.data;
|
||||
return Promise.reject(new Error(res.message));
|
||||
}
|
||||
|
||||
/** 提交工单 */
|
||||
export async function submitTicket(data: TicketSubmitForm) {
|
||||
const res = await request.post<ApiResult<Ticket>>(BASE + '/submit', data);
|
||||
if (res.code === 0) return res.data;
|
||||
return Promise.reject(new Error(res.message));
|
||||
}
|
||||
|
||||
/** 获取工单详情 */
|
||||
export async function getTicket(ticketId: number) {
|
||||
const res = await request.get<ApiResult<Ticket>>(BASE + '/' + ticketId);
|
||||
if (res.code === 0 && res.data) return res.data;
|
||||
return Promise.reject(new Error(res.message));
|
||||
}
|
||||
|
||||
/** 关闭工单 */
|
||||
export async function closeTicket(ticketId: number) {
|
||||
const res = await request.put<ApiResult<unknown>>(BASE + `/${ticketId}/close`);
|
||||
if (res.code === 0) return res.message;
|
||||
return Promise.reject(new Error(res.message));
|
||||
}
|
||||
|
||||
/** 获取工单回复列表 */
|
||||
export async function getTicketReplies(ticketId: number) {
|
||||
const res = await request.get<ApiResult<TicketReply[]>>(BASE + `/${ticketId}/replies`);
|
||||
if (res.code === 0 && res.data) return res.data;
|
||||
return Promise.reject(new Error(res.message));
|
||||
}
|
||||
|
||||
/** 提交工单回复 */
|
||||
export async function replyTicket(data: { ticketId: number; content: string }) {
|
||||
const res = await request.post<ApiResult<TicketReply>>(BASE + '/reply', data);
|
||||
if (res.code === 0) return res.data;
|
||||
return Promise.reject(new Error(res.message));
|
||||
}
|
||||
|
||||
/** 获取工单统计 */
|
||||
export async function getTicketStats() {
|
||||
const res = await request.get<ApiResult<TicketStats>>(BASE + '/stats');
|
||||
if (res.code === 0 && res.data) return res.data;
|
||||
return Promise.reject(new Error(res.message));
|
||||
}
|
||||
@@ -1,64 +0,0 @@
|
||||
export type TicketPriority = 'low' | 'normal' | 'high' | 'urgent'
|
||||
export type TicketStatus = 'pending' | 'assigned' | 'processing' | 'resolved' | 'closed' | 'rejected'
|
||||
export type TicketCategory = 'bug' | 'feature' | 'consultation' | 'complaint' | 'other'
|
||||
|
||||
export interface Ticket {
|
||||
ticketId?: number
|
||||
ticketNo?: string
|
||||
title?: string
|
||||
content?: string
|
||||
productId?: number
|
||||
productName?: string
|
||||
category?: TicketCategory
|
||||
priority?: TicketPriority
|
||||
status?: TicketStatus
|
||||
attachments?: string[]
|
||||
submitUserId?: number
|
||||
submitUserName?: string
|
||||
submitUserAvatar?: string
|
||||
assigneeId?: number
|
||||
assigneeName?: string
|
||||
assigneeAvatar?: string
|
||||
createTime?: string
|
||||
updateTime?: string
|
||||
resolvedTime?: string
|
||||
closedTime?: string
|
||||
replyCount?: number
|
||||
hasUnread?: boolean
|
||||
}
|
||||
|
||||
export interface TicketReply {
|
||||
replyId?: number
|
||||
ticketId?: number
|
||||
content?: string
|
||||
attachments?: string[]
|
||||
userId?: number
|
||||
userName?: string
|
||||
userAvatar?: string
|
||||
isStaff?: boolean
|
||||
createTime?: string
|
||||
}
|
||||
|
||||
export interface TicketSubmitForm {
|
||||
title: string
|
||||
content: string
|
||||
productId?: number
|
||||
category: TicketCategory
|
||||
priority: TicketPriority
|
||||
}
|
||||
|
||||
export interface TicketQueryParams {
|
||||
status?: TicketStatus
|
||||
category?: TicketCategory
|
||||
keywords?: string
|
||||
page?: number
|
||||
limit?: number
|
||||
}
|
||||
|
||||
export interface TicketStats {
|
||||
total?: number
|
||||
pending?: number
|
||||
processing?: number
|
||||
resolved?: number
|
||||
closed?: number
|
||||
}
|
||||
@@ -1,129 +0,0 @@
|
||||
import request from '@/utils/request';
|
||||
import type { ApiResult, PageResult } from '@/api';
|
||||
import type { BszxBm, BszxBmParam } from './model';
|
||||
|
||||
/**
|
||||
* 分页查询百色中学-报名记录
|
||||
*/
|
||||
export async function pageBszxBm(params: BszxBmParam) {
|
||||
const res = await request.get<ApiResult<PageResult<BszxBm>>>(
|
||||
'/bszx/bszx-bm/page',
|
||||
{
|
||||
params
|
||||
}
|
||||
);
|
||||
if (res.code === 0) {
|
||||
return res.data;
|
||||
}
|
||||
return Promise.reject(new Error(res.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询百色中学-报名记录列表
|
||||
*/
|
||||
export async function listBszxBm(params?: BszxBmParam) {
|
||||
const res = await request.get<ApiResult<BszxBm[]>>(
|
||||
'/bszx/bszx-bm',
|
||||
{
|
||||
params
|
||||
}
|
||||
);
|
||||
if (res.code === 0 && res.data) {
|
||||
return res.data;
|
||||
}
|
||||
return Promise.reject(new Error(res.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加百色中学-报名记录
|
||||
*/
|
||||
export async function addBszxBm(data: BszxBm) {
|
||||
const res = await request.post<ApiResult<BszxBm>>(
|
||||
'/bszx/bszx-bm',
|
||||
data
|
||||
);
|
||||
if (res.code === 0) {
|
||||
return res.message;
|
||||
}
|
||||
return Promise.reject(new Error(res.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改百色中学-报名记录
|
||||
*/
|
||||
export async function updateBszxBm(data: BszxBm) {
|
||||
const res = await request.put<ApiResult<unknown>>(
|
||||
'/bszx/bszx-bm',
|
||||
data
|
||||
);
|
||||
if (res.code === 0) {
|
||||
return res.message;
|
||||
}
|
||||
return Promise.reject(new Error(res.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除百色中学-报名记录
|
||||
*/
|
||||
export async function removeBszxBm(id?: number) {
|
||||
const res = await request.del<ApiResult<unknown>>(
|
||||
'/bszx/bszx-bm/' + id
|
||||
);
|
||||
if (res.code === 0) {
|
||||
return res.message;
|
||||
}
|
||||
return Promise.reject(new Error(res.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量删除百色中学-报名记录
|
||||
*/
|
||||
export async function removeBatchBszxBm(data: (number | undefined)[]) {
|
||||
const res = await request.del<ApiResult<unknown>>(
|
||||
'/bszx/bszx-bm/batch',
|
||||
{
|
||||
data
|
||||
}
|
||||
);
|
||||
if (res.code === 0) {
|
||||
return res.message;
|
||||
}
|
||||
return Promise.reject(new Error(res.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据id查询百色中学-报名记录
|
||||
*/
|
||||
export async function getBszxBm(id: number) {
|
||||
const res = await request.get<ApiResult<BszxBm>>(
|
||||
'/bszx/bszx-bm/' + id
|
||||
);
|
||||
if (res.code === 0 && res.data) {
|
||||
return res.data;
|
||||
}
|
||||
return Promise.reject(new Error(res.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询应用-百色中学-报名记录列表
|
||||
*/
|
||||
export async function myPageBszxBm(params?: BszxBmParam) {
|
||||
const res = await request.get<ApiResult<PageResult<BszxBm>>>(
|
||||
'/bszx/bszx-bm/myPage',
|
||||
params
|
||||
);
|
||||
if (res.code === 0 && res.data) {
|
||||
return res.data;
|
||||
}
|
||||
return Promise.reject(new Error(res.message));
|
||||
}
|
||||
|
||||
export async function getPoster() {
|
||||
const res = await request.get<ApiResult<BszxBm>>(
|
||||
'/bszx/bszx-bm/generatePoster'
|
||||
);
|
||||
if (res.code === 0 && res.data) {
|
||||
return res.data;
|
||||
}
|
||||
return Promise.reject(new Error(res.message));
|
||||
}
|
||||
@@ -1,70 +0,0 @@
|
||||
import type { PageParam } from '@/api/index';
|
||||
|
||||
/**
|
||||
* 百色中学-报名记录
|
||||
*/
|
||||
export interface BszxBm {
|
||||
// 自增ID
|
||||
id?: number;
|
||||
// 文章ID
|
||||
articleId?: string;
|
||||
// 类型
|
||||
type?: number;
|
||||
// 姓名
|
||||
name?: string;
|
||||
// 性别 1男 2女
|
||||
sex?: string;
|
||||
// 手机号码
|
||||
phone?: string;
|
||||
// 班级
|
||||
className?: string;
|
||||
// 年级
|
||||
gradeName?: string;
|
||||
// 居住地址
|
||||
address?: string;
|
||||
// 工作单位
|
||||
workUnit?: string;
|
||||
// 职务
|
||||
position?: string;
|
||||
// 是否能到场
|
||||
present?: string;
|
||||
// 年龄
|
||||
age?: number;
|
||||
// 人数
|
||||
number?: number;
|
||||
// 额外信息
|
||||
extra?: string;
|
||||
// 生成的邀请函存放路径
|
||||
certificate?: string;
|
||||
// 预定日期
|
||||
dateTime?: string;
|
||||
// 表单数据
|
||||
formData?: string;
|
||||
// 表单ID
|
||||
formId?: number;
|
||||
// 用户ID
|
||||
userId?: number;
|
||||
// 排序(数字越小越靠前)
|
||||
sortNumber?: number;
|
||||
// 备注
|
||||
comments?: string;
|
||||
// 状态, 0正常, 1冻结
|
||||
status?: number;
|
||||
// 是否删除, 0否, 1是
|
||||
deleted?: number;
|
||||
// 租户id
|
||||
tenantId?: number;
|
||||
// 创建时间
|
||||
createTime?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 百色中学-报名记录搜索条件
|
||||
*/
|
||||
export interface BszxBmParam extends PageParam {
|
||||
id?: number;
|
||||
userId?: number;
|
||||
orderNo?: string;
|
||||
limit?: number;
|
||||
keywords?: string;
|
||||
}
|
||||
@@ -1,120 +0,0 @@
|
||||
import request from '@/utils/request';
|
||||
import type { ApiResult, PageResult } from '@/api';
|
||||
import type { BszxClass, BszxClassParam } from './model';
|
||||
|
||||
/**
|
||||
* 分页查询百色中学-班级
|
||||
*/
|
||||
export async function pageBszxClass(params: BszxClassParam) {
|
||||
const res = await request.get<ApiResult<PageResult<BszxClass>>>(
|
||||
'/bszx/bszx-class/page',
|
||||
{
|
||||
params
|
||||
}
|
||||
);
|
||||
if (res.code === 0) {
|
||||
return res.data;
|
||||
}
|
||||
return Promise.reject(new Error(res.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询百色中学-班级列表
|
||||
*/
|
||||
export async function listBszxClass(params?: BszxClassParam) {
|
||||
const res = await request.get<ApiResult<BszxClass[]>>(
|
||||
'/bszx/bszx-class',
|
||||
{
|
||||
params
|
||||
}
|
||||
);
|
||||
if (res.code === 0 && res.data) {
|
||||
return res.data;
|
||||
}
|
||||
return Promise.reject(new Error(res.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加百色中学-班级
|
||||
*/
|
||||
export async function addBszxClass(data: BszxClass) {
|
||||
const res = await request.post<ApiResult<unknown>>(
|
||||
'/bszx/bszx-class',
|
||||
data
|
||||
);
|
||||
if (res.code === 0) {
|
||||
return res.message;
|
||||
}
|
||||
return Promise.reject(new Error(res.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改百色中学-班级
|
||||
*/
|
||||
export async function updateBszxClass(data: BszxClass) {
|
||||
const res = await request.put<ApiResult<unknown>>(
|
||||
'/bszx/bszx-class',
|
||||
data
|
||||
);
|
||||
if (res.code === 0) {
|
||||
return res.message;
|
||||
}
|
||||
return Promise.reject(new Error(res.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除百色中学-班级
|
||||
*/
|
||||
export async function removeBszxClass(id?: number) {
|
||||
const res = await request.del<ApiResult<unknown>>(
|
||||
'/bszx/bszx-class/' + id
|
||||
);
|
||||
if (res.code === 0) {
|
||||
return res.message;
|
||||
}
|
||||
return Promise.reject(new Error(res.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量删除百色中学-班级
|
||||
*/
|
||||
export async function removeBatchBszxClass(data: (number | undefined)[]) {
|
||||
const res = await request.del<ApiResult<unknown>>(
|
||||
'/bszx/bszx-class/batch',
|
||||
{
|
||||
data
|
||||
}
|
||||
);
|
||||
if (res.code === 0) {
|
||||
return res.message;
|
||||
}
|
||||
return Promise.reject(new Error(res.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据id查询百色中学-班级
|
||||
*/
|
||||
export async function getBszxClass(id: number) {
|
||||
const res = await request.get<ApiResult<BszxClass>>(
|
||||
'/bszx/bszx-class/' + id
|
||||
);
|
||||
if (res.code === 0 && res.data) {
|
||||
return res.data;
|
||||
}
|
||||
return Promise.reject(new Error(res.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询应用-百色中学-班级列表
|
||||
*/
|
||||
export async function getBszxClassForTree() {
|
||||
const res = await request.get<ApiResult<BszxClass[]>>(
|
||||
'/bszx/bszx-class/tree'
|
||||
);
|
||||
if (res.code === 0 && res.data) {
|
||||
return res.data;
|
||||
}
|
||||
return Promise.reject(new Error(res.message));
|
||||
}
|
||||
|
||||
|
||||
@@ -1,37 +0,0 @@
|
||||
import type { PageParam } from '@/api/index';
|
||||
|
||||
/**
|
||||
* 百色中学-班级
|
||||
*/
|
||||
export interface BszxClass {
|
||||
// ID
|
||||
id?: number;
|
||||
// 时代ID
|
||||
eraId?: number;
|
||||
// 年级ID
|
||||
gradeId?: number;
|
||||
// 班级
|
||||
name?: string;
|
||||
// 班级
|
||||
text?: string;
|
||||
// 排序(数字越小越靠前)
|
||||
sortNumber?: number;
|
||||
// 备注
|
||||
comments?: string;
|
||||
// 状态, 0正常, 1冻结
|
||||
status?: number;
|
||||
// 租户id
|
||||
tenantId?: number;
|
||||
// 创建时间
|
||||
createTime?: string;
|
||||
// 子级
|
||||
children?: BszxClass[];
|
||||
}
|
||||
|
||||
/**
|
||||
* 百色中学-班级搜索条件
|
||||
*/
|
||||
export interface BszxClassParam extends PageParam {
|
||||
id?: number;
|
||||
keywords?: string;
|
||||
}
|
||||
@@ -1,105 +0,0 @@
|
||||
import request from '@/utils/request';
|
||||
import type { ApiResult, PageResult } from '@/api';
|
||||
import type { BszxEra, BszxEraParam } from './model';
|
||||
|
||||
/**
|
||||
* 分页查询百色中学-年代
|
||||
*/
|
||||
export async function pageBszxEra(params: BszxEraParam) {
|
||||
const res = await request.get<ApiResult<PageResult<BszxEra>>>(
|
||||
'/bszx/bszx-era/page',
|
||||
{
|
||||
params
|
||||
}
|
||||
);
|
||||
if (res.code === 0) {
|
||||
return res.data;
|
||||
}
|
||||
return Promise.reject(new Error(res.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询百色中学-年代列表
|
||||
*/
|
||||
export async function listBszxEra(params?: BszxEraParam) {
|
||||
const res = await request.get<ApiResult<BszxEra[]>>(
|
||||
'/bszx/bszx-era',
|
||||
{
|
||||
params
|
||||
}
|
||||
);
|
||||
if (res.code === 0 && res.data) {
|
||||
return res.data;
|
||||
}
|
||||
return Promise.reject(new Error(res.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加百色中学-年代
|
||||
*/
|
||||
export async function addBszxEra(data: BszxEra) {
|
||||
const res = await request.post<ApiResult<unknown>>(
|
||||
'/bszx/bszx-era',
|
||||
data
|
||||
);
|
||||
if (res.code === 0) {
|
||||
return res.message;
|
||||
}
|
||||
return Promise.reject(new Error(res.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改百色中学-年代
|
||||
*/
|
||||
export async function updateBszxEra(data: BszxEra) {
|
||||
const res = await request.put<ApiResult<unknown>>(
|
||||
'/bszx/bszx-era',
|
||||
data
|
||||
);
|
||||
if (res.code === 0) {
|
||||
return res.message;
|
||||
}
|
||||
return Promise.reject(new Error(res.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除百色中学-年代
|
||||
*/
|
||||
export async function removeBszxEra(id?: number) {
|
||||
const res = await request.del<ApiResult<unknown>>(
|
||||
'/bszx/bszx-era/' + id
|
||||
);
|
||||
if (res.code === 0) {
|
||||
return res.message;
|
||||
}
|
||||
return Promise.reject(new Error(res.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量删除百色中学-年代
|
||||
*/
|
||||
export async function removeBatchBszxEra(data: (number | undefined)[]) {
|
||||
const res = await request.del<ApiResult<unknown>>(
|
||||
'/bszx/bszx-era/batch',
|
||||
{
|
||||
data
|
||||
}
|
||||
);
|
||||
if (res.code === 0) {
|
||||
return res.message;
|
||||
}
|
||||
return Promise.reject(new Error(res.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据id查询百色中学-年代
|
||||
*/
|
||||
export async function getBszxEra(id: number) {
|
||||
const res = await request.get<ApiResult<BszxEra>>(
|
||||
'/bszx/bszx-era/' + id
|
||||
);
|
||||
if (res.code === 0 && res.data) {
|
||||
return res.data;
|
||||
}
|
||||
return Promise.reject(new Error(res.message));
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
import type { PageParam } from '@/api/index';
|
||||
|
||||
/**
|
||||
* 百色中学-年代
|
||||
*/
|
||||
export interface BszxEra {
|
||||
// ID
|
||||
id?: number;
|
||||
// 年代
|
||||
name?: string;
|
||||
// 租户id
|
||||
tenantId?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* 百色中学-年代搜索条件
|
||||
*/
|
||||
export interface BszxEraParam extends PageParam {
|
||||
id?: number;
|
||||
keywords?: string;
|
||||
}
|
||||
@@ -1,105 +0,0 @@
|
||||
import request from '@/utils/request';
|
||||
import type { ApiResult, PageResult } from '@/api';
|
||||
import type { BszxGrade, BszxGradeParam } from './model';
|
||||
|
||||
/**
|
||||
* 分页查询百色中学-年级
|
||||
*/
|
||||
export async function pageBszxGrade(params: BszxGradeParam) {
|
||||
const res = await request.get<ApiResult<PageResult<BszxGrade>>>(
|
||||
'/bszx/bszx-grade/page',
|
||||
{
|
||||
params
|
||||
}
|
||||
);
|
||||
if (res.code === 0) {
|
||||
return res.data;
|
||||
}
|
||||
return Promise.reject(new Error(res.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询百色中学-年级列表
|
||||
*/
|
||||
export async function listBszxGrade(params?: BszxGradeParam) {
|
||||
const res = await request.get<ApiResult<BszxGrade[]>>(
|
||||
'/bszx/bszx-grade',
|
||||
{
|
||||
params
|
||||
}
|
||||
);
|
||||
if (res.code === 0 && res.data) {
|
||||
return res.data;
|
||||
}
|
||||
return Promise.reject(new Error(res.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加百色中学-年级
|
||||
*/
|
||||
export async function addBszxGrade(data: BszxGrade) {
|
||||
const res = await request.post<ApiResult<unknown>>(
|
||||
'/bszx/bszx-grade',
|
||||
data
|
||||
);
|
||||
if (res.code === 0) {
|
||||
return res.message;
|
||||
}
|
||||
return Promise.reject(new Error(res.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改百色中学-年级
|
||||
*/
|
||||
export async function updateBszxGrade(data: BszxGrade) {
|
||||
const res = await request.put<ApiResult<unknown>>(
|
||||
'/bszx/bszx-grade',
|
||||
data
|
||||
);
|
||||
if (res.code === 0) {
|
||||
return res.message;
|
||||
}
|
||||
return Promise.reject(new Error(res.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除百色中学-年级
|
||||
*/
|
||||
export async function removeBszxGrade(id?: number) {
|
||||
const res = await request.del<ApiResult<unknown>>(
|
||||
'/bszx/bszx-grade/' + id
|
||||
);
|
||||
if (res.code === 0) {
|
||||
return res.message;
|
||||
}
|
||||
return Promise.reject(new Error(res.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量删除百色中学-年级
|
||||
*/
|
||||
export async function removeBatchBszxGrade(data: (number | undefined)[]) {
|
||||
const res = await request.del<ApiResult<unknown>>(
|
||||
'/bszx/bszx-grade/batch',
|
||||
{
|
||||
data
|
||||
}
|
||||
);
|
||||
if (res.code === 0) {
|
||||
return res.message;
|
||||
}
|
||||
return Promise.reject(new Error(res.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据id查询百色中学-年级
|
||||
*/
|
||||
export async function getBszxGrade(id: number) {
|
||||
const res = await request.get<ApiResult<BszxGrade>>(
|
||||
'/bszx/bszx-grade/' + id
|
||||
);
|
||||
if (res.code === 0 && res.data) {
|
||||
return res.data;
|
||||
}
|
||||
return Promise.reject(new Error(res.message));
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
import type { PageParam } from '@/api/index';
|
||||
|
||||
/**
|
||||
* 百色中学-年级
|
||||
*/
|
||||
export interface BszxGrade {
|
||||
// ID
|
||||
id?: number;
|
||||
// 年级
|
||||
name?: string;
|
||||
// 年代
|
||||
eraId?: number;
|
||||
// 排序(数字越小越靠前)
|
||||
sortNumber?: number;
|
||||
// 备注
|
||||
comments?: string;
|
||||
// 状态, 0正常, 1冻结
|
||||
status?: number;
|
||||
// 租户id
|
||||
tenantId?: number;
|
||||
// 创建时间
|
||||
createTime?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 百色中学-年级搜索条件
|
||||
*/
|
||||
export interface BszxGradeParam extends PageParam {
|
||||
id?: number;
|
||||
keywords?: string;
|
||||
}
|
||||
@@ -1,138 +0,0 @@
|
||||
import request from '@/utils/request';
|
||||
import type { ApiResult, PageResult } from '@/api';
|
||||
import type {BszxPay, BszxPayParam} from "@/api/bszx/bszxPay/model";
|
||||
import type {BszxBm,BszxBmParam} from "@/api/bszx/bszxBm/model";
|
||||
|
||||
/**
|
||||
* 分页查询百色中学-捐款记录
|
||||
*/
|
||||
export async function pageBszxPay(params: BszxPayParam) {
|
||||
const res = await request.get<ApiResult<PageResult<BszxPay>>>(
|
||||
'/bszx/bszx-pay/page',
|
||||
params
|
||||
);
|
||||
if (res.code === 0) {
|
||||
return res.data;
|
||||
}
|
||||
return Promise.reject(new Error(res.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询百色中学-捐款记录列表
|
||||
*/
|
||||
export async function listBszxPay(params?: BszxPayParam) {
|
||||
const res = await request.get<ApiResult<BszxPay[]>>(
|
||||
'/bszx/bszx-pay',
|
||||
{
|
||||
params
|
||||
}
|
||||
);
|
||||
if (res.code === 0 && res.data) {
|
||||
return res.data;
|
||||
}
|
||||
return Promise.reject(new Error(res.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加百色中学-捐款记录
|
||||
*/
|
||||
export async function addBszxPay(data: BszxPay) {
|
||||
const res = await request.post<ApiResult<BszxPay>>(
|
||||
'/bszx/bszx-pay',
|
||||
data
|
||||
);
|
||||
if (res.code === 0) {
|
||||
return res.data;
|
||||
}
|
||||
return Promise.reject(new Error(res.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改百色中学-捐款记录
|
||||
*/
|
||||
export async function updateBszxPay(data: BszxPay) {
|
||||
const res = await request.put<ApiResult<unknown>>(
|
||||
'/bszx/bszx-pay',
|
||||
data
|
||||
);
|
||||
if (res.code === 0) {
|
||||
return res.message;
|
||||
}
|
||||
return Promise.reject(new Error(res.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除百色中学-捐款记录
|
||||
*/
|
||||
export async function removeBszxPay(id?: number) {
|
||||
const res = await request.del<ApiResult<unknown>>(
|
||||
'/bszx/bszx-pay/' + id
|
||||
);
|
||||
if (res.code === 0) {
|
||||
return res.message;
|
||||
}
|
||||
return Promise.reject(new Error(res.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量删除百色中学-捐款记录
|
||||
*/
|
||||
export async function removeBatchBszxPay(data: (number | undefined)[]) {
|
||||
const res = await request.del<ApiResult<unknown>>(
|
||||
'/bszx/bszx-pay/batch',
|
||||
{
|
||||
data
|
||||
}
|
||||
);
|
||||
if (res.code === 0) {
|
||||
return res.message;
|
||||
}
|
||||
return Promise.reject(new Error(res.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据id查询百色中学-捐款记录
|
||||
*/
|
||||
export async function getBszxPay(id: number) {
|
||||
const res = await request.get<ApiResult<BszxPay>>(
|
||||
'/bszx/bszx-pay/' + id
|
||||
);
|
||||
if (res.code === 0 && res.data) {
|
||||
return res.data;
|
||||
}
|
||||
return Promise.reject(new Error(res.message));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 查询应用-百色中学-报名记录列表
|
||||
*/
|
||||
export async function myPageBszxPay(params?: BszxBmParam) {
|
||||
const res = await request.get<ApiResult<PageResult<BszxBm>>>(
|
||||
'/bszx/bszx-pay/myPage',
|
||||
params
|
||||
);
|
||||
if (res.code === 0 && res.data) {
|
||||
return res.data;
|
||||
}
|
||||
return Promise.reject(new Error(res.message));
|
||||
}
|
||||
export async function getPayCert(id: number) {
|
||||
const res = await request.get<ApiResult<BszxBm>>(
|
||||
'/bszx/bszx-pay/generatePayCert/' + id, {}
|
||||
);
|
||||
if (res.code === 0 && res.data) {
|
||||
return res.data;
|
||||
}
|
||||
return Promise.reject(new Error(res.message));
|
||||
}
|
||||
|
||||
export async function getCount() {
|
||||
const res = await request.get<ApiResult<any>>(
|
||||
'/bszx/bszx-pay/getCount'
|
||||
);
|
||||
if (res.code === 0 && res.data) {
|
||||
return res.data;
|
||||
}
|
||||
return Promise.reject(new Error(res.message));
|
||||
}
|
||||
@@ -1,80 +0,0 @@
|
||||
import type { PageParam } from '@/api/index';
|
||||
import {CmsArticle} from "@/api/cms/cmsArticle/model";
|
||||
|
||||
/**
|
||||
* 百色中学-捐款记录
|
||||
*/
|
||||
export interface BszxPay {
|
||||
// ID
|
||||
id?: number;
|
||||
// 年龄
|
||||
age?: number;
|
||||
// 姓名
|
||||
name?: string;
|
||||
// 性别 1男 2女
|
||||
sex?: string;
|
||||
// 手机号码
|
||||
phone?: string;
|
||||
// 班级
|
||||
className?: string;
|
||||
// 年级
|
||||
gradeName?: string;
|
||||
// 居住地址
|
||||
address?: string;
|
||||
// 工作单位
|
||||
workUnit?: string;
|
||||
// 职务
|
||||
position?: string;
|
||||
// 数量
|
||||
number?: number;
|
||||
// 价格
|
||||
price?: string;
|
||||
// 额外信息
|
||||
extra?: string;
|
||||
// 预定日期
|
||||
dateTime?: string;
|
||||
// 捐赠证书
|
||||
certificate?: string;
|
||||
// 表单数据
|
||||
formData?: string;
|
||||
// 表单ID
|
||||
formId?: number;
|
||||
// 表单名称
|
||||
formName?: string;
|
||||
// 订单号
|
||||
orderNo?: string;
|
||||
// 用户ID
|
||||
userId?: number;
|
||||
// 头像
|
||||
avatar?: string;
|
||||
// 昵称
|
||||
nickname?: string;
|
||||
// 真实姓名
|
||||
realName?: string;
|
||||
// 排序(数字越小越靠前)
|
||||
sortNumber?: number;
|
||||
// 备注
|
||||
comments?: string;
|
||||
// 状态, 0正常, 1冻结
|
||||
status?: number;
|
||||
// 是否删除, 0否, 1是
|
||||
deleted?: number;
|
||||
// 租户id
|
||||
tenantId?: number;
|
||||
// 创建时间
|
||||
createTime?: string;
|
||||
present?: any;
|
||||
// 关联的文章
|
||||
article?: CmsArticle;
|
||||
}
|
||||
|
||||
/**
|
||||
* 百色中学-捐款记录搜索条件
|
||||
*/
|
||||
export interface BszxPayParam extends PageParam {
|
||||
id?: number;
|
||||
userId?: number;
|
||||
formId?: number;
|
||||
orderNo?: string;
|
||||
keywords?: string;
|
||||
}
|
||||
-193
@@ -1,193 +0,0 @@
|
||||
/**
|
||||
* CI/CD 流水线 API
|
||||
*/
|
||||
import { request } from '../utils/request'
|
||||
import type {
|
||||
Build,
|
||||
BuildParam,
|
||||
BuildStatus,
|
||||
Deploy,
|
||||
DeployParam,
|
||||
DeployStatus,
|
||||
PipelineConfig,
|
||||
RuntimeInstance,
|
||||
} from '../types/cicd'
|
||||
|
||||
// ==================== 构建相关 ====================
|
||||
|
||||
/** 获取构建列表 */
|
||||
export const pageBuild = (params: BuildParam) => {
|
||||
return request<{ list: Build[]; total: number }>({
|
||||
url: '/developer/build/page',
|
||||
method: 'GET',
|
||||
params,
|
||||
})
|
||||
}
|
||||
|
||||
/** 获取构建详情 */
|
||||
export const getBuild = (id: number) => {
|
||||
return request<Build>({
|
||||
url: `/developer/build/${id}`,
|
||||
method: 'GET',
|
||||
})
|
||||
}
|
||||
|
||||
/** 触发构建 */
|
||||
export const triggerBuild = (data: { websiteId: number; branch?: string; commitId?: string }) => {
|
||||
return request<Build>({
|
||||
url: '/developer/build/trigger',
|
||||
method: 'POST',
|
||||
data,
|
||||
})
|
||||
}
|
||||
|
||||
/** 取消构建 */
|
||||
export const cancelBuild = (id: number) => {
|
||||
return request<void>({
|
||||
url: `/developer/build/${id}/cancel`,
|
||||
method: 'POST',
|
||||
})
|
||||
}
|
||||
|
||||
/** 获取构建日志 */
|
||||
export const getBuildLogs = (id: number) => {
|
||||
return request<{ logs: string }>({
|
||||
url: `/developer/build/${id}/logs`,
|
||||
method: 'GET',
|
||||
})
|
||||
}
|
||||
|
||||
/** 获取构建产物 */
|
||||
export const getBuildArtifacts = (id: number) => {
|
||||
return request<{ artifacts: any[] }>({
|
||||
url: `/developer/build/${id}/artifacts`,
|
||||
method: 'GET',
|
||||
})
|
||||
}
|
||||
|
||||
// ==================== 部署相关 ====================
|
||||
|
||||
/** 获取部署列表 */
|
||||
export const pageDeploy = (params: DeployParam) => {
|
||||
return request<{ list: Deploy[]; total: number }>({
|
||||
url: '/developer/deploy/page',
|
||||
method: 'GET',
|
||||
params,
|
||||
})
|
||||
}
|
||||
|
||||
/** 获取部署详情 */
|
||||
export const getDeploy = (id: number) => {
|
||||
return request<Deploy>({
|
||||
url: `/developer/deploy/${id}`,
|
||||
method: 'GET',
|
||||
})
|
||||
}
|
||||
|
||||
/** 触发部署 */
|
||||
export const triggerDeploy = (data: {
|
||||
websiteId: number
|
||||
buildId: number
|
||||
env: string
|
||||
}) => {
|
||||
return request<Deploy>({
|
||||
url: '/developer/deploy/trigger',
|
||||
method: 'POST',
|
||||
data,
|
||||
})
|
||||
}
|
||||
|
||||
/** 回滚部署 */
|
||||
export const rollbackDeploy = (id: number) => {
|
||||
return request<Deploy>({
|
||||
url: `/developer/deploy/${id}/rollback`,
|
||||
method: 'POST',
|
||||
})
|
||||
}
|
||||
|
||||
/** 获取部署日志 */
|
||||
export const getDeployLogs = (id: number) => {
|
||||
return request<{ logs: string }>({
|
||||
url: `/developer/deploy/${id}/logs`,
|
||||
method: 'GET',
|
||||
})
|
||||
}
|
||||
|
||||
// ==================== 流水线配置相关 ====================
|
||||
|
||||
/** 获取流水线配置 */
|
||||
export const getPipelineConfig = (websiteId: number) => {
|
||||
return request<PipelineConfig>({
|
||||
url: `/developer/pipeline/config/${websiteId}`,
|
||||
method: 'GET',
|
||||
})
|
||||
}
|
||||
|
||||
/** 更新流水线配置 */
|
||||
export const updatePipelineConfig = (data: PipelineConfig) => {
|
||||
return request<PipelineConfig>({
|
||||
url: '/developer/pipeline/config',
|
||||
method: 'PUT',
|
||||
data,
|
||||
})
|
||||
}
|
||||
|
||||
/** 获取分支列表 */
|
||||
export const getBranches = (websiteId: number) => {
|
||||
return request<{ branches: string[] }>({
|
||||
url: `/developer/pipeline/branches/${websiteId}`,
|
||||
method: 'GET',
|
||||
})
|
||||
}
|
||||
|
||||
/** 添加环境变量 */
|
||||
export const addEnvVar = (websiteId: number, data: { key: string; value: string; isSecret?: boolean }) => {
|
||||
return request<void>({
|
||||
url: `/developer/pipeline/env/${websiteId}`,
|
||||
method: 'POST',
|
||||
data,
|
||||
})
|
||||
}
|
||||
|
||||
/** 删除环境变量 */
|
||||
export const deleteEnvVar = (websiteId: number, key: string) => {
|
||||
return request<void>({
|
||||
url: `/developer/pipeline/env/${websiteId}/${key}`,
|
||||
method: 'DELETE',
|
||||
})
|
||||
}
|
||||
|
||||
// ==================== 运行时相关 ====================
|
||||
|
||||
/** 获取运行时实例列表 */
|
||||
export const pageRuntime = (websiteId: number, env?: string) => {
|
||||
return request<{ list: RuntimeInstance[]; total: number }>({
|
||||
url: '/developer/runtime/page',
|
||||
method: 'GET',
|
||||
params: { websiteId, env },
|
||||
})
|
||||
}
|
||||
|
||||
/** 启动实例 */
|
||||
export const startRuntime = (id: number) => {
|
||||
return request<void>({
|
||||
url: `/developer/runtime/${id}/start`,
|
||||
method: 'POST',
|
||||
})
|
||||
}
|
||||
|
||||
/** 停止实例 */
|
||||
export const stopRuntime = (id: number) => {
|
||||
return request<void>({
|
||||
url: `/developer/runtime/${id}/stop`,
|
||||
method: 'POST',
|
||||
})
|
||||
}
|
||||
|
||||
/** 重启实例 */
|
||||
export const restartRuntime = (id: number) => {
|
||||
return request<void>({
|
||||
url: `/developer/runtime/${id}/restart`,
|
||||
method: 'POST',
|
||||
})
|
||||
}
|
||||
@@ -55,5 +55,6 @@ export interface CmsAdParam extends PageParam {
|
||||
adType?: string;
|
||||
pageId?: number;
|
||||
pageName?: string;
|
||||
status?: number;
|
||||
keywords?: string;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import request from '@/utils/request';
|
||||
import type { ApiResult, PageResult } from '@/api';
|
||||
import type {CmsWebsiteField, CmsWebsiteFieldParam} from './model';
|
||||
import type {CmsWebsiteField, CmsWebsiteFieldParam, Config} from './model';
|
||||
|
||||
/**
|
||||
* 分页查询应用参数
|
||||
@@ -112,3 +112,18 @@ export async function undeleteWebsiteField(id?: number) {
|
||||
}
|
||||
return Promise.reject(new Error(res.message));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 查询项目参数列表
|
||||
*/
|
||||
export async function configWebsiteField(params?: CmsWebsiteFieldParam) {
|
||||
const res = await request.get<ApiResult<Config>>(
|
||||
'/cms/cms-website-field/config',
|
||||
params
|
||||
);
|
||||
if (res.code === 0 && res.data) {
|
||||
return res.data;
|
||||
}
|
||||
return Promise.reject(new Error(res.message));
|
||||
}
|
||||
|
||||
@@ -1,444 +0,0 @@
|
||||
import request from '@/utils/request'
|
||||
import type { ApiResult, PageResult } from '@/api'
|
||||
import type {
|
||||
App,
|
||||
AppParam,
|
||||
ApiKey,
|
||||
ApiKeyParam,
|
||||
Developer,
|
||||
DeveloperApplyParam,
|
||||
Project,
|
||||
ProjectParam,
|
||||
ProjectMember,
|
||||
Version,
|
||||
VersionParam,
|
||||
Notification,
|
||||
NotificationParam,
|
||||
Apply,
|
||||
ApplyParam,
|
||||
} from '@/types/developer'
|
||||
|
||||
// ==================== 项目/应用相关 ====================
|
||||
// 注意:后端使用 AppProduct 作为项目/应用的概念,路径为 /app/product
|
||||
|
||||
/**
|
||||
* 获取我的项目列表(我创建的应用)
|
||||
*/
|
||||
export async function pageMyProject(params?: ProjectParam) {
|
||||
const res = await request.get<ApiResult<PageResult<Project>>>('/app/product/my/page', params)
|
||||
if (res.code === 0) return res.data
|
||||
return Promise.reject(new Error(res.message))
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取项目列表(分页查询应用列表)
|
||||
*/
|
||||
export async function pageProject(params?: ProjectParam) {
|
||||
const res = await request.get<ApiResult<PageResult<Project>>>('/app/product/page', params)
|
||||
if (res.code === 0) return res.data
|
||||
return Promise.reject(new Error(res.message))
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取项目详情
|
||||
*/
|
||||
export async function getProject(id: number) {
|
||||
const res = await request.get<ApiResult<Project>>(`/app/product/detail/${id}`)
|
||||
if (res.code === 0) return res.data
|
||||
return Promise.reject(new Error(res.message))
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建项目
|
||||
*/
|
||||
export async function createProject(data: Partial<Project>) {
|
||||
const res = await request.post<ApiResult<unknown>>('/app/product/create', data)
|
||||
if (res.code === 0) return res.message
|
||||
return Promise.reject(new Error(res.message))
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新项目
|
||||
*/
|
||||
export async function updateProject(data: Partial<Project>) {
|
||||
const res = await request.put<ApiResult<unknown>>('/app/product/update', data)
|
||||
if (res.code === 0) return res.message
|
||||
return Promise.reject(new Error(res.message))
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除项目
|
||||
*/
|
||||
export async function deleteProject(id: number) {
|
||||
const res = await request.del<ApiResult<unknown>>(`/app/product/delete/${id}`)
|
||||
if (res.code === 0) return res.message
|
||||
return Promise.reject(new Error(res.message))
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取项目成员列表(使用应用用户服务)
|
||||
*/
|
||||
export async function listProjectMember(projectId: number) {
|
||||
const res = await request.get<ApiResult<ProjectMember[]>>(`/app/app-user/page`, { appId: projectId })
|
||||
if (res.code === 0 && res.data) return res.data
|
||||
return Promise.reject(new Error(res.message))
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加项目成员
|
||||
*/
|
||||
export async function addProjectMember(projectId: number, data: Partial<ProjectMember>) {
|
||||
const res = await request.post<ApiResult<unknown>>(`/app/app-user`, { ...data, appId: projectId })
|
||||
if (res.code === 0) return res.message
|
||||
return Promise.reject(new Error(res.message))
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除项目成员
|
||||
*/
|
||||
export async function removeProjectMember(projectId: number, memberId: number) {
|
||||
const res = await request.del<ApiResult<unknown>>(`/app/app-user/${memberId}`)
|
||||
if (res.code === 0) return res.message
|
||||
return Promise.reject(new Error(res.message))
|
||||
}
|
||||
|
||||
// ==================== 应用相关(别名,与项目共用)====================
|
||||
|
||||
/**
|
||||
* 分页查询我的应用(创建的应用)
|
||||
*/
|
||||
export async function pageMyApp(params?: AppParam) {
|
||||
const res = await request.get<ApiResult<PageResult<App>>>('/app/product/my/page', params)
|
||||
if (res.code === 0) return res.data
|
||||
return Promise.reject(new Error(res.message))
|
||||
}
|
||||
|
||||
/**
|
||||
* 分页查询我参与的应用(通过邀请加入的应用)
|
||||
*/
|
||||
export async function pageJoinedApp(params?: AppParam) {
|
||||
const res = await request.get<ApiResult<PageResult<App>>>('/app/product/joined/page', params)
|
||||
if (res.code === 0) return res.data
|
||||
return Promise.reject(new Error(res.message))
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取应用列表
|
||||
*/
|
||||
export async function pageApp(params?: AppParam) {
|
||||
const res = await request.get<ApiResult<PageResult<App>>>('/app/product/page', params)
|
||||
if (res.code === 0) return res.data
|
||||
return Promise.reject(new Error(res.message))
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取应用详情
|
||||
*/
|
||||
export async function getApp(id: number) {
|
||||
const res = await request.get<ApiResult<App>>(`/app/product/detail/${id}`)
|
||||
if (res.code === 0) return res.data
|
||||
return Promise.reject(new Error(res.message))
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建应用
|
||||
*/
|
||||
export async function createApp(data: Partial<App>) {
|
||||
const res = await request.post<ApiResult<unknown>>('/app/product/create', data)
|
||||
if (res.code === 0) return res.message
|
||||
return Promise.reject(new Error(res.message))
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新应用
|
||||
*/
|
||||
export async function updateApp(data: Partial<App>) {
|
||||
const res = await request.put<ApiResult<unknown>>('/app/product/update', data)
|
||||
if (res.code === 0) return res.message
|
||||
return Promise.reject(new Error(res.message))
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除应用
|
||||
*/
|
||||
export async function deleteApp(id: number) {
|
||||
const res = await request.del<ApiResult<unknown>>(`/app/product/delete/${id}`)
|
||||
if (res.code === 0) return res.message
|
||||
return Promise.reject(new Error(res.message))
|
||||
}
|
||||
|
||||
// ==================== API Key 相关 ====================
|
||||
// 对应后端 AppCredentialController,路径为 /app/app-credential
|
||||
|
||||
/**
|
||||
* 获取 API Key 列表
|
||||
*/
|
||||
export async function pageApiKey(params?: ApiKeyParam) {
|
||||
const res = await request.get<ApiResult<PageResult<ApiKey>>>('/app/app-credential/page', params)
|
||||
if (res.code === 0) return res.data
|
||||
return Promise.reject(new Error(res.message))
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 API Key 列表(不分页)
|
||||
*/
|
||||
export async function listApiKey(params?: ApiKeyParam) {
|
||||
const res = await request.get<ApiResult<ApiKey[]>>('/app/app-credential', params)
|
||||
if (res.code === 0 && res.data) return res.data
|
||||
return Promise.reject(new Error(res.message))
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建 API Key
|
||||
*/
|
||||
export async function createApiKey(data: Partial<ApiKey>) {
|
||||
const res = await request.post<ApiResult<unknown>>('/app/app-credential', data)
|
||||
if (res.code === 0) return res.message
|
||||
return Promise.reject(new Error(res.message))
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新 API Key
|
||||
*/
|
||||
export async function updateApiKey(data: Partial<ApiKey>) {
|
||||
const res = await request.put<ApiResult<unknown>>('/app/app-credential', data)
|
||||
if (res.code === 0) return res.message
|
||||
return Promise.reject(new Error(res.message))
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除 API Key
|
||||
*/
|
||||
export async function deleteApiKey(id: number) {
|
||||
const res = await request.del<ApiResult<unknown>>(`/app/app-credential/${id}`)
|
||||
if (res.code === 0) return res.message
|
||||
return Promise.reject(new Error(res.message))
|
||||
}
|
||||
|
||||
/**
|
||||
* 重置 API Key Secret
|
||||
*/
|
||||
export async function resetApiKeySecret(id: number) {
|
||||
const res = await request.post<ApiResult<unknown>>(`/app/app-credential/resetSecret/${id}`)
|
||||
if (res.code === 0) return res.message
|
||||
return Promise.reject(new Error(res.message))
|
||||
}
|
||||
|
||||
// ==================== 版本发布相关 ====================
|
||||
// 对应后端 AppVersionController,路径为 /app/app-version
|
||||
|
||||
/**
|
||||
* 获取版本列表
|
||||
*/
|
||||
export async function pageVersion(params?: VersionParam) {
|
||||
const res = await request.get<ApiResult<PageResult<Version>>>('/app/app-version/page', params)
|
||||
if (res.code === 0) return res.data
|
||||
return Promise.reject(new Error(res.message))
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取版本详情
|
||||
*/
|
||||
export async function getVersion(id: number) {
|
||||
const res = await request.get<ApiResult<Version>>(`/app/app-version/${id}`)
|
||||
if (res.code === 0 && res.data) return res.data
|
||||
return Promise.reject(new Error(res.message))
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建版本
|
||||
*/
|
||||
export async function createVersion(data: Partial<Version>) {
|
||||
const res = await request.post<ApiResult<unknown>>('/app/app-version', data)
|
||||
if (res.code === 0) return res.message
|
||||
return Promise.reject(new Error(res.message))
|
||||
}
|
||||
|
||||
/**
|
||||
* 发布版本
|
||||
*/
|
||||
export async function publishVersion(id: number) {
|
||||
const res = await request.post<ApiResult<unknown>>(`/app/app-version/publish/${id}`)
|
||||
if (res.code === 0) return res.message
|
||||
return Promise.reject(new Error(res.message))
|
||||
}
|
||||
|
||||
/**
|
||||
* 回滚版本
|
||||
*/
|
||||
export async function rollbackVersion(id: number) {
|
||||
const res = await request.post<ApiResult<unknown>>(`/app/app-version/rollback/${id}`)
|
||||
if (res.code === 0) return res.message
|
||||
return Promise.reject(new Error(res.message))
|
||||
}
|
||||
|
||||
// ==================== 开发者相关 ====================
|
||||
// 对应后端 GitAccountController,路径为 /app/developer
|
||||
|
||||
/**
|
||||
* 获取开发者信息(Git账号绑定状态)
|
||||
*/
|
||||
export async function getDeveloperInfo() {
|
||||
const res = await request.get<ApiResult<Developer>>('/app/developer/git-account')
|
||||
if (res.code === 0) return res.data
|
||||
return Promise.reject(new Error(res.message))
|
||||
}
|
||||
|
||||
/**
|
||||
* 申请成为开发者(绑定Git账号)
|
||||
*/
|
||||
export async function applyDeveloper(data: DeveloperApplyParam) {
|
||||
const res = await request.post<ApiResult<unknown>>('/app/developer/git-account', data)
|
||||
if (res.code === 0) return res.message
|
||||
return Promise.reject(new Error(res.message))
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新开发者信息(更新Git账号)
|
||||
*/
|
||||
export async function updateDeveloperInfo(data: Partial<Developer>) {
|
||||
const res = await request.put<ApiResult<unknown>>('/app/developer/git-account', data)
|
||||
if (res.code === 0) return res.message
|
||||
return Promise.reject(new Error(res.message))
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取Gitea服务器信息
|
||||
*/
|
||||
export async function getGiteaInfo() {
|
||||
const res = await request.get<ApiResult<{ url: string; version: string; registrationEnabled: boolean }>>('/app/developer/gitea-info')
|
||||
if (res.code === 0) return res.data
|
||||
return Promise.reject(new Error(res.message))
|
||||
}
|
||||
|
||||
// ==================== 消息通知相关 ====================
|
||||
// 对应后端 AppNotificationController,路径为 /app/notification
|
||||
|
||||
/**
|
||||
* 获取通知列表
|
||||
*/
|
||||
export async function pageNotification(params?: NotificationParam) {
|
||||
const res = await request.get<ApiResult<PageResult<Notification>>>('/app/notification/page', params)
|
||||
if (res.code === 0) return res.data
|
||||
return Promise.reject(new Error(res.message))
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取最近通知(铃铛下拉)
|
||||
*/
|
||||
export async function getRecentNotifications(type?: string, limit?: number) {
|
||||
const res = await request.get<ApiResult<Notification[]>>('/app/notification/recent', { type, limit })
|
||||
if (res.code === 0) return res.data
|
||||
return Promise.reject(new Error(res.message))
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取未读通知数量
|
||||
*/
|
||||
export async function getUnreadCount() {
|
||||
const res = await request.get<ApiResult<{ count: number }>>('/app/notification/unread-count')
|
||||
if (res.code === 0) return res.data
|
||||
return Promise.reject(new Error(res.message))
|
||||
}
|
||||
|
||||
/**
|
||||
* 标记通知为已读
|
||||
*/
|
||||
export async function markAsRead(id: number) {
|
||||
const res = await request.put<ApiResult<unknown>>(`/app/notification/read/${id}`)
|
||||
if (res.code === 0) return res.message
|
||||
return Promise.reject(new Error(res.message))
|
||||
}
|
||||
|
||||
/**
|
||||
* 标记所有通知为已读
|
||||
*/
|
||||
export async function markAllAsRead(type?: string) {
|
||||
const res = await request.put<ApiResult<unknown>>('/app/notification/read-all', { type })
|
||||
if (res.code === 0) return res.message
|
||||
return Promise.reject(new Error(res.message))
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除通知
|
||||
*/
|
||||
export async function deleteNotification(id: number) {
|
||||
const res = await request.del<ApiResult<unknown>>(`/app/notification/${id}`)
|
||||
if (res.code === 0) return res.message
|
||||
return Promise.reject(new Error(res.message))
|
||||
}
|
||||
|
||||
/**
|
||||
* 清空已读通知
|
||||
*/
|
||||
export async function clearReadNotifications(type?: string) {
|
||||
const res = await request.del<ApiResult<unknown>>('/app/notification/clear-read', { type })
|
||||
if (res.code === 0) return res.message
|
||||
return Promise.reject(new Error(res.message))
|
||||
}
|
||||
|
||||
// ==================== 权限审批相关 ====================
|
||||
// 对应后端 AppPermissionRequestController,路径为 /app/developer/permission-requests
|
||||
|
||||
/**
|
||||
* 获取申请列表
|
||||
*/
|
||||
export async function pageApply(params?: ApplyParam) {
|
||||
const res = await request.get<ApiResult<PageResult<Apply>>>('/app/developer/permission-requests/page', params)
|
||||
if (res.code === 0) return res.data
|
||||
return Promise.reject(new Error(res.message))
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取我的申请列表
|
||||
*/
|
||||
export async function pageMyApply(params?: ApplyParam) {
|
||||
const res = await request.get<ApiResult<PageResult<Apply>>>('/app/developer/permission-requests', params)
|
||||
if (res.code === 0) return res.data
|
||||
return Promise.reject(new Error(res.message))
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取权限申请统计
|
||||
*/
|
||||
export async function getApplyStats() {
|
||||
const res = await request.get<ApiResult<{ [key: string]: number }>>('/app/developer/permission-requests/stats')
|
||||
if (res.code === 0) return res.data
|
||||
return Promise.reject(new Error(res.message))
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取可申请的仓库列表
|
||||
*/
|
||||
export async function getAvailableRepos() {
|
||||
const res = await request.get<ApiResult<Array<{ name: string; fullName: string }>>>('/app/developer/permission-requests/available-repos')
|
||||
if (res.code === 0) return res.data
|
||||
return Promise.reject(new Error(res.message))
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建申请
|
||||
*/
|
||||
export async function createApply(data: { repo: string; reason: string; gitUsername?: string }) {
|
||||
const res = await request.post<ApiResult<unknown>>('/app/developer/permission-requests', data)
|
||||
if (res.code === 0) return res.message
|
||||
return Promise.reject(new Error(res.message))
|
||||
}
|
||||
|
||||
/**
|
||||
* 审批申请-通过
|
||||
*/
|
||||
export async function approveApply(id: number, note?: string) {
|
||||
const res = await request.put<ApiResult<unknown>>(`/app/developer/permission-requests/${id}/approve`, { note })
|
||||
if (res.code === 0) return res.message
|
||||
return Promise.reject(new Error(res.message))
|
||||
}
|
||||
|
||||
/**
|
||||
* 审批申请-拒绝
|
||||
*/
|
||||
export async function rejectApply(id: number, reason: string) {
|
||||
const res = await request.put<ApiResult<unknown>>(`/app/developer/permission-requests/${id}/reject`, { reason })
|
||||
if (res.code === 0) return res.message
|
||||
return Promise.reject(new Error(res.message))
|
||||
}
|
||||
@@ -1,148 +0,0 @@
|
||||
import request from '@/utils/request'
|
||||
import type { ApiResult, PageResult } from '@/api'
|
||||
import type { Enterprise, EnterpriseMember, EnterpriseMemberParam, Order, Bill, BillParam, App, AppParam } from '@/types/developer'
|
||||
|
||||
// ==================== 企业/租户相关 ====================
|
||||
|
||||
/**
|
||||
* 获取企业信息
|
||||
*/
|
||||
export async function getEnterpriseInfo() {
|
||||
const res = await request.get<ApiResult<Enterprise>>('/system/tenant/info')
|
||||
if (res.code === 0) return res.data
|
||||
return Promise.reject(new Error(res.message))
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新企业信息
|
||||
*/
|
||||
export async function updateEnterpriseInfo(data: Partial<Enterprise>) {
|
||||
const res = await request.put<ApiResult<unknown>>('/system/tenant', data)
|
||||
if (res.code === 0) return res.message
|
||||
return Promise.reject(new Error(res.message))
|
||||
}
|
||||
|
||||
// ==================== 企业成员/用户相关 ====================
|
||||
|
||||
/**
|
||||
* 获取企业成员列表
|
||||
*/
|
||||
export async function pageEnterpriseMember(params?: EnterpriseMemberParam) {
|
||||
const res = await request.get<ApiResult<PageResult<EnterpriseMember>>>('/system/user/page', params)
|
||||
if (res.code === 0) return res.data
|
||||
return Promise.reject(new Error(res.message))
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取企业成员列表(不分页)
|
||||
*/
|
||||
export async function listEnterpriseMember(params?: EnterpriseMemberParam) {
|
||||
const res = await request.get<ApiResult<EnterpriseMember[]>>('/system/user', params)
|
||||
if (res.code === 0 && res.data) return res.data
|
||||
return Promise.reject(new Error(res.message))
|
||||
}
|
||||
|
||||
/**
|
||||
* 邀请企业成员
|
||||
*/
|
||||
export async function inviteEnterpriseMember(enterpriseId: number, data: Partial<EnterpriseMember>) {
|
||||
const res = await request.post<ApiResult<unknown>>(`/app/developer/invite/${enterpriseId}/invite`, data)
|
||||
if (res.code === 0) return res.message
|
||||
return Promise.reject(new Error(res.message))
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新企业成员
|
||||
*/
|
||||
export async function updateEnterpriseMember(data: Partial<EnterpriseMember>) {
|
||||
const res = await request.put<ApiResult<unknown>>(`/system/user`, data)
|
||||
if (res.code === 0) return res.message
|
||||
return Promise.reject(new Error(res.message))
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除企业成员
|
||||
*/
|
||||
export async function removeEnterpriseMember(memberId: number) {
|
||||
const res = await request.del<ApiResult<unknown>>(`/system/user/${memberId}`)
|
||||
if (res.code === 0) return res.message
|
||||
return Promise.reject(new Error(res.message))
|
||||
}
|
||||
|
||||
// ==================== 订单相关 ====================
|
||||
|
||||
/**
|
||||
* 获取订单列表
|
||||
*/
|
||||
export async function pageOrder(params?: { page?: number; limit?: number; status?: number }) {
|
||||
const res = await request.get<ApiResult<PageResult<Order>>>('/system/order/page', params)
|
||||
if (res.code === 0) return res.data
|
||||
return Promise.reject(new Error(res.message))
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取订单详情
|
||||
*/
|
||||
export async function getOrder(id: number) {
|
||||
const res = await request.get<ApiResult<Order>>(`/system/order/${id}`)
|
||||
if (res.code === 0 && res.data) return res.data
|
||||
return Promise.reject(new Error(res.message))
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建订单
|
||||
*/
|
||||
export async function createOrder(data: any) {
|
||||
const res = await request.post<ApiResult<unknown>>('/system/order', data)
|
||||
if (res.code === 0) return res.message
|
||||
return Promise.reject(new Error(res.message))
|
||||
}
|
||||
|
||||
// ==================== 充值/账单相关 ====================
|
||||
|
||||
/**
|
||||
* 获取账单列表
|
||||
*/
|
||||
export async function pageBill(params?: BillParam) {
|
||||
const res = await request.get<ApiResult<PageResult<Bill>>>('/sys/recharge-order/page', params)
|
||||
if (res.code === 0) return res.data
|
||||
return Promise.reject(new Error(res.message))
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取账单概览
|
||||
*/
|
||||
export async function getBillOverview() {
|
||||
const res = await request.get<ApiResult<{ balance: number; totalConsume: number; totalRecharge: number }>>('/sys/user-balance-log/overview')
|
||||
if (res.code === 0) return res.data
|
||||
return Promise.reject(new Error(res.message))
|
||||
}
|
||||
|
||||
// ==================== 企业应用相关 ====================
|
||||
|
||||
/**
|
||||
* 获取企业应用列表
|
||||
*/
|
||||
export async function pageEnterpriseApp(params?: AppParam) {
|
||||
const res = await request.get<ApiResult<PageResult<App>>>('/app/product/page', { params })
|
||||
if (res.code === 0) return res.data
|
||||
return Promise.reject(new Error(res.message))
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取企业应用详情
|
||||
*/
|
||||
export async function getEnterpriseApp(id: number) {
|
||||
const res = await request.get<ApiResult<App>>(`/app/product/${id}`)
|
||||
if (res.code === 0 && res.data) return res.data
|
||||
return Promise.reject(new Error(res.message))
|
||||
}
|
||||
|
||||
/**
|
||||
* 购买应用
|
||||
*/
|
||||
export async function purchaseApp(productId: number) {
|
||||
const res = await request.post<ApiResult<unknown>>('/api/system/order/createOrder', { productId })
|
||||
if (res.code === 0) return res.message
|
||||
return Promise.reject(new Error(res.message))
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
/**
|
||||
* 开发者 API 模块
|
||||
*/
|
||||
export * from './developer'
|
||||
export * from './enterprise'
|
||||
@@ -1,331 +0,0 @@
|
||||
/**
|
||||
* 数据导入/导出 API
|
||||
*/
|
||||
import Taro from '@tarojs/taro';
|
||||
import type { ImportTask, ImportTemplate, ImportField, ImportError, ExportTask, ExportFormat, ExportType, ImportType } from '../types/import';
|
||||
|
||||
// 模拟数据
|
||||
const mockImportTasks: ImportTask[] = [
|
||||
{
|
||||
id: '1',
|
||||
name: '商品批量导入',
|
||||
type: 'product',
|
||||
status: 'completed',
|
||||
total: 500,
|
||||
success: 485,
|
||||
failed: 15,
|
||||
progress: 100,
|
||||
fileName: 'products_20260412.xlsx',
|
||||
fileSize: '1.2MB',
|
||||
errorFile: '/downloads/import_error_1.xlsx',
|
||||
creatorId: '1',
|
||||
creatorName: '张三',
|
||||
startTime: '2026-04-12 10:00:00',
|
||||
endTime: '2026-04-12 10:05:30',
|
||||
createTime: '2026-04-12 09:58:00'
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
name: '用户数据迁移',
|
||||
type: 'user',
|
||||
status: 'importing',
|
||||
total: 2000,
|
||||
success: 1200,
|
||||
failed: 0,
|
||||
progress: 60,
|
||||
fileName: 'users_migration.csv',
|
||||
fileSize: '3.5MB',
|
||||
creatorId: '1',
|
||||
creatorName: '张三',
|
||||
startTime: '2026-04-12 14:00:00',
|
||||
createTime: '2026-04-12 13:55:00'
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
name: '订单历史导入',
|
||||
type: 'order',
|
||||
status: 'validating',
|
||||
total: 10000,
|
||||
success: 0,
|
||||
failed: 0,
|
||||
progress: 25,
|
||||
fileName: 'orders_history.xlsx',
|
||||
fileSize: '8.2MB',
|
||||
creatorId: '1',
|
||||
creatorName: '张三',
|
||||
startTime: '2026-04-12 15:00:00',
|
||||
createTime: '2026-04-12 14:50:00'
|
||||
}
|
||||
];
|
||||
|
||||
const mockExportTasks: ExportTask[] = [
|
||||
{
|
||||
id: '1',
|
||||
name: '月度订单导出',
|
||||
type: 'order',
|
||||
status: 'completed',
|
||||
format: 'xlsx',
|
||||
filters: { month: '2026-03' },
|
||||
total: 5680,
|
||||
fileName: 'orders_202603.xlsx',
|
||||
fileSize: '2.8MB',
|
||||
downloadUrl: '/downloads/orders_202603.xlsx',
|
||||
expiresAt: '2026-04-19 15:00:00',
|
||||
creatorId: '1',
|
||||
creatorName: '张三',
|
||||
createTime: '2026-04-12 10:00:00',
|
||||
completeTime: '2026-04-12 10:02:30'
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
name: '用户数据导出',
|
||||
type: 'user',
|
||||
status: 'processing',
|
||||
format: 'csv',
|
||||
filters: { registeredAfter: '2026-01-01' },
|
||||
total: 0,
|
||||
creatorId: '1',
|
||||
creatorName: '张三',
|
||||
createTime: '2026-04-12 14:30:00'
|
||||
}
|
||||
];
|
||||
|
||||
// 导入模板字段定义
|
||||
const mockTemplates: ImportTemplate[] = [
|
||||
{
|
||||
id: 't1',
|
||||
name: '商品导入模板',
|
||||
type: 'product',
|
||||
description: '用于批量导入商品信息,支持商品名称、价格、库存等字段',
|
||||
downloadUrl: '/templates/product_import.xlsx',
|
||||
fields: [
|
||||
{ key: 'name', name: '商品名称', type: 'string', required: true, maxLength: 100, example: 'iPhone 15 Pro' },
|
||||
{ key: 'price', name: '价格', type: 'number', required: true, example: '8999.00' },
|
||||
{ key: 'stock', name: '库存', type: 'number', required: true, example: '100' },
|
||||
{ key: 'category', name: '分类', type: 'select', required: true, options: ['手机', '电脑', '配件', '其他'], example: '手机' },
|
||||
{ key: 'description', name: '描述', type: 'string', required: false, maxLength: 500, example: '最新款苹果手机' },
|
||||
{ key: 'status', name: '状态', type: 'select', required: true, options: ['上架', '下架'], example: '上架' },
|
||||
{ key: 'images', name: '图片URL', type: 'string', required: false, example: 'https://example.com/img.jpg' }
|
||||
],
|
||||
rules: [
|
||||
{ field: 'name', rule: 'required', message: '商品名称不能为空' },
|
||||
{ field: 'price', rule: 'range', message: '价格必须大于0', params: { min: 0.01 } },
|
||||
{ field: 'stock', rule: 'required', message: '库存不能为空' }
|
||||
],
|
||||
exampleUrl: '/templates/product_example.xlsx',
|
||||
createTime: '2026-01-01'
|
||||
},
|
||||
{
|
||||
id: 't2',
|
||||
name: '用户导入模板',
|
||||
type: 'user',
|
||||
description: '用于批量导入用户信息,包含用户名、手机、邮箱等',
|
||||
downloadUrl: '/templates/user_import.xlsx',
|
||||
fields: [
|
||||
{ key: 'username', name: '用户名', type: 'string', required: true, maxLength: 50, example: 'zhangsan' },
|
||||
{ key: 'phone', name: '手机号', type: 'string', required: true, pattern: '^1[3-9]\\d{9}$', example: '13800138000' },
|
||||
{ key: 'email', name: '邮箱', type: 'string', required: false, pattern: '^\\w+@\\w+\\.\\w+$', example: 'user@example.com' },
|
||||
{ key: 'nickname', name: '昵称', type: 'string', required: false, maxLength: 30, example: '小张' },
|
||||
{ key: 'role', name: '角色', type: 'select', required: true, options: ['user', 'admin'], example: 'user' }
|
||||
],
|
||||
rules: [
|
||||
{ field: 'username', rule: 'unique', message: '用户名已存在' },
|
||||
{ field: 'phone', rule: 'pattern', message: '手机号格式不正确' }
|
||||
],
|
||||
createTime: '2026-01-01'
|
||||
},
|
||||
{
|
||||
id: 't3',
|
||||
name: '订单导入模板',
|
||||
type: 'order',
|
||||
description: '用于导入历史订单数据',
|
||||
downloadUrl: '/templates/order_import.xlsx',
|
||||
fields: [
|
||||
{ key: 'orderNo', name: '订单号', type: 'string', required: true, example: 'ORD202604120001' },
|
||||
{ key: 'userId', name: '用户ID', type: 'string', required: true, example: '10001' },
|
||||
{ key: 'amount', name: '订单金额', type: 'number', required: true, example: '299.00' },
|
||||
{ key: 'status', name: '订单状态', type: 'select', required: true, options: ['pending', 'paid', 'shipped', 'completed', 'cancelled'], example: 'completed' },
|
||||
{ key: 'createTime', name: '创建时间', type: 'date', required: true, example: '2026-04-01 10:30:00' }
|
||||
],
|
||||
rules: [],
|
||||
createTime: '2026-01-01'
|
||||
}
|
||||
];
|
||||
|
||||
/**
|
||||
* 获取导入任务列表
|
||||
*/
|
||||
export async function pageImportTask(params: {
|
||||
type?: ImportType;
|
||||
status?: string;
|
||||
page?: number;
|
||||
pageSize?: number;
|
||||
}): Promise<{ list: ImportTask[]; total: number }> {
|
||||
const { type, status, page = 1, pageSize = 10 } = params;
|
||||
|
||||
let filtered = [...mockImportTasks];
|
||||
if (type) filtered = filtered.filter(t => t.type === type);
|
||||
if (status) filtered = filtered.filter(t => t.status === status);
|
||||
|
||||
const start = (page - 1) * pageSize;
|
||||
return { list: filtered.slice(start, start + pageSize), total: filtered.length };
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取导入任务详情
|
||||
*/
|
||||
export async function getImportTaskDetail(id: string): Promise<ImportTask | null> {
|
||||
return mockImportTasks.find(t => t.id === id) || null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取导入错误详情
|
||||
*/
|
||||
export async function getImportErrors(id: string): Promise<ImportError[]> {
|
||||
return [
|
||||
{ row: 15, field: 'price', value: '-100', error: '价格不能为负数', suggestion: '请输入正数' },
|
||||
{ row: 28, field: 'name', value: '', error: '商品名称不能为空', suggestion: '请填写商品名称' },
|
||||
{ row: 56, field: 'stock', value: 'abc', error: '库存必须是数字', suggestion: '请输入整数' }
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取导入模板列表
|
||||
*/
|
||||
export async function listImportTemplate(): Promise<ImportTemplate[]> {
|
||||
return mockTemplates;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取模板字段详情
|
||||
*/
|
||||
export async function getTemplateFields(type: ImportType): Promise<ImportField[]> {
|
||||
const template = mockTemplates.find(t => t.type === type);
|
||||
return template?.fields || [];
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建导入任务
|
||||
*/
|
||||
export async function createImportTask(data: {
|
||||
name: string;
|
||||
type: ImportType;
|
||||
fileName: string;
|
||||
fileSize: string;
|
||||
}): Promise<ImportTask> {
|
||||
const task: ImportTask = {
|
||||
id: String(Date.now()),
|
||||
name: data.name,
|
||||
type: data.type,
|
||||
status: 'uploading',
|
||||
total: 0,
|
||||
success: 0,
|
||||
failed: 0,
|
||||
progress: 0,
|
||||
fileName: data.fileName,
|
||||
fileSize: data.fileSize,
|
||||
creatorId: '1',
|
||||
creatorName: '张三',
|
||||
startTime: new Date().toISOString().replace('T', ' ').slice(0, 19),
|
||||
createTime: new Date().toISOString().replace('T', ' ').slice(0, 19)
|
||||
};
|
||||
|
||||
mockImportTasks.unshift(task);
|
||||
return task;
|
||||
}
|
||||
|
||||
/**
|
||||
* 取消导入任务
|
||||
*/
|
||||
export async function cancelImportTask(id: string): Promise<void> {
|
||||
const task = mockImportTasks.find(t => t.id === id);
|
||||
if (task) task.status = 'cancelled';
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除导入任务
|
||||
*/
|
||||
export async function deleteImportTask(id: string): Promise<void> {
|
||||
const index = mockImportTasks.findIndex(t => t.id === id);
|
||||
if (index > -1) mockImportTasks.splice(index, 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取导出任务列表
|
||||
*/
|
||||
export async function pageExportTask(params: {
|
||||
type?: ExportType;
|
||||
status?: string;
|
||||
page?: number;
|
||||
pageSize?: number;
|
||||
}): Promise<{ list: ExportTask[]; total: number }> {
|
||||
const { type, status, page = 1, pageSize = 10 } = params;
|
||||
|
||||
let filtered = [...mockExportTasks];
|
||||
if (type) filtered = filtered.filter(t => t.type === type);
|
||||
if (status) filtered = filtered.filter(t => t.status === status);
|
||||
|
||||
const start = (page - 1) * pageSize;
|
||||
return { list: filtered.slice(start, start + pageSize), total: filtered.length };
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建导出任务
|
||||
*/
|
||||
export async function createExportTask(data: {
|
||||
name: string;
|
||||
type: ExportType;
|
||||
format: ExportFormat;
|
||||
filters?: Record<string, any>;
|
||||
}): Promise<ExportTask> {
|
||||
const task: ExportTask = {
|
||||
id: String(Date.now()),
|
||||
name: data.name,
|
||||
type: data.type,
|
||||
status: 'pending',
|
||||
format: data.format,
|
||||
filters: data.filters || {},
|
||||
total: 0,
|
||||
creatorId: '1',
|
||||
creatorName: '张三',
|
||||
createTime: new Date().toISOString().replace('T', ' ').slice(0, 19)
|
||||
};
|
||||
|
||||
mockExportTasks.unshift(task);
|
||||
return task;
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除导出任务
|
||||
*/
|
||||
export async function deleteExportTask(id: string): Promise<void> {
|
||||
const index = mockExportTasks.findIndex(t => t.id === id);
|
||||
if (index > -1) mockExportTasks.splice(index, 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* 下载导入模板
|
||||
*/
|
||||
export function downloadTemplate(type: ImportType): void {
|
||||
const template = mockTemplates.find(t => t.type === type);
|
||||
if (template) {
|
||||
Taro.showToast({ title: '开始下载模板', icon: 'success' });
|
||||
console.log(`Downloading: ${template.downloadUrl}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出账单
|
||||
*/
|
||||
export async function exportBill(params: {
|
||||
startDate: string;
|
||||
endDate: string;
|
||||
format: ExportFormat;
|
||||
}): Promise<ExportTask> {
|
||||
return createExportTask({
|
||||
name: `账单导出 ${params.startDate} ~ ${params.endDate}`,
|
||||
type: 'bill',
|
||||
format: params.format,
|
||||
filters: { startDate: params.startDate, endDate: params.endDate }
|
||||
});
|
||||
}
|
||||
@@ -1,109 +0,0 @@
|
||||
/**
|
||||
* 发票管理 API
|
||||
*/
|
||||
import { request } from '../utils/request'
|
||||
import type {
|
||||
Invoice,
|
||||
InvoiceTitle,
|
||||
InvoiceApplyParam,
|
||||
InvoiceParam,
|
||||
InvoiceStatus,
|
||||
InvoiceType,
|
||||
} from '../types/invoice'
|
||||
|
||||
// ==================== 发票抬头 ====================
|
||||
|
||||
/** 获取发票抬头列表 */
|
||||
export const listInvoiceTitles = () => {
|
||||
return request<{ list: InvoiceTitle[] }>({
|
||||
url: '/enterprise/invoice/titles',
|
||||
method: 'GET',
|
||||
})
|
||||
}
|
||||
|
||||
/** 创建发票抬头 */
|
||||
export const createInvoiceTitle = (data: Omit<InvoiceTitle, 'id' | 'createTime'>) => {
|
||||
return request<InvoiceTitle>({
|
||||
url: '/enterprise/invoice/titles',
|
||||
method: 'POST',
|
||||
data,
|
||||
})
|
||||
}
|
||||
|
||||
/** 更新发票抬头 */
|
||||
export const updateInvoiceTitle = (id: number, data: Partial<InvoiceTitle>) => {
|
||||
return request<InvoiceTitle>({
|
||||
url: `/enterprise/invoice/titles/${id}`,
|
||||
method: 'PUT',
|
||||
data,
|
||||
})
|
||||
}
|
||||
|
||||
/** 删除发票抬头 */
|
||||
export const deleteInvoiceTitle = (id: number) => {
|
||||
return request<void>({
|
||||
url: `/enterprise/invoice/titles/${id}`,
|
||||
method: 'DELETE',
|
||||
})
|
||||
}
|
||||
|
||||
/** 设置默认发票抬头 */
|
||||
export const setDefaultInvoiceTitle = (id: number) => {
|
||||
return request<void>({
|
||||
url: `/enterprise/invoice/titles/${id}/default`,
|
||||
method: 'POST',
|
||||
})
|
||||
}
|
||||
|
||||
// ==================== 发票申请 ====================
|
||||
|
||||
/** 获取发票列表 */
|
||||
export const pageInvoice = (params: InvoiceParam) => {
|
||||
return request<{ list: Invoice[]; total: number }>({
|
||||
url: '/enterprise/invoice/page',
|
||||
method: 'GET',
|
||||
params,
|
||||
})
|
||||
}
|
||||
|
||||
/** 获取发票详情 */
|
||||
export const getInvoice = (id: number) => {
|
||||
return request<Invoice>({
|
||||
url: `/enterprise/invoice/${id}`,
|
||||
method: 'GET',
|
||||
})
|
||||
}
|
||||
|
||||
/** 申请发票 */
|
||||
export const applyInvoice = (data: InvoiceApplyParam) => {
|
||||
return request<Invoice>({
|
||||
url: '/enterprise/invoice/apply',
|
||||
method: 'POST',
|
||||
data,
|
||||
})
|
||||
}
|
||||
|
||||
/** 取消发票申请 */
|
||||
export const cancelInvoice = (id: number) => {
|
||||
return request<void>({
|
||||
url: `/enterprise/invoice/${id}/cancel`,
|
||||
method: 'POST',
|
||||
})
|
||||
}
|
||||
|
||||
/** 重新申请发票 */
|
||||
export const reapplyInvoice = (id: number, data: Partial<InvoiceApplyParam>) => {
|
||||
return request<Invoice>({
|
||||
url: `/enterprise/invoice/${id}/reapply`,
|
||||
method: 'POST',
|
||||
data,
|
||||
})
|
||||
}
|
||||
|
||||
/** 获取可开票金额 */
|
||||
export const getInvoiceableAmount = () => {
|
||||
return request<{ amount: number }>({
|
||||
url: '/enterprise/invoice/amount',
|
||||
method: 'GET',
|
||||
})
|
||||
}
|
||||
@@ -62,7 +62,6 @@ export async function getNext7day() {
|
||||
const res = await request.get<ApiResult<any>>(
|
||||
'/cms/website/getNext7day'
|
||||
);
|
||||
console.log('res.data.code: ', res.data.code);
|
||||
if (res.code === 0 && res.data) {
|
||||
return res.data;
|
||||
}
|
||||
|
||||
@@ -7,9 +7,10 @@ import type {
|
||||
SmsCaptchaResult
|
||||
} from './model';
|
||||
import {saveStorageByLoginUser, SERVER_API_URL} from "@/utils/server";
|
||||
import { isUserDisabled } from '@/utils/auth';
|
||||
|
||||
/**
|
||||
* 登录
|
||||
* 账号密码登录
|
||||
*/
|
||||
export async function login(data: LoginParam) {
|
||||
const res = await request.post<ApiResult<LoginResult>>(
|
||||
@@ -17,13 +18,12 @@ export async function login(data: LoginParam) {
|
||||
data
|
||||
);
|
||||
if (res.code === 0) {
|
||||
// setToken(res.data.data?.access_token, data.remember);
|
||||
if (res.data?.user) {
|
||||
const user = res.data?.user;
|
||||
localStorage.setItem('TenantId', String(user.tenantId));
|
||||
localStorage.setItem('UserId', String(user.userId));
|
||||
if (res.data?.user && res.data?.access_token) {
|
||||
if (isUserDisabled(res.data.user)) {
|
||||
return Promise.reject(new Error('您的账号已被禁用,请联系管理员'));
|
||||
}
|
||||
saveStorageByLoginUser(res.data.access_token, res.data.user)
|
||||
}
|
||||
|
||||
return res.message;
|
||||
}
|
||||
return Promise.reject(new Error(res.message));
|
||||
@@ -50,6 +50,9 @@ export async function loginBySms(data: LoginParam) {
|
||||
);
|
||||
if (res.code === 0) {
|
||||
if(res.data?.user){
|
||||
if (isUserDisabled(res.data.user)) {
|
||||
return Promise.reject(new Error('您的账号已被禁用,请联系管理员'));
|
||||
}
|
||||
saveStorageByLoginUser(`${res.data?.access_token}`, res.data?.user)
|
||||
}
|
||||
return res.message;
|
||||
|
||||
@@ -15,6 +15,10 @@ export interface LoginParam {
|
||||
phone?: string;
|
||||
// 短信验证码
|
||||
code?: string;
|
||||
// 图形验证码Key
|
||||
captchaKey?: string;
|
||||
// 图形验证码文本
|
||||
captchaText?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -31,6 +35,8 @@ export interface LoginResult {
|
||||
* 图形验证码返回结果
|
||||
*/
|
||||
export interface CaptchaResult {
|
||||
// 验证码Key
|
||||
key?: string;
|
||||
// 图形验证码base64数据
|
||||
base64: string;
|
||||
// 验证码文本
|
||||
|
||||
@@ -78,36 +78,19 @@ export interface ConfirmLoginParam {
|
||||
deviceInfo?: string;
|
||||
}
|
||||
|
||||
// 确认登录响应(适配后端 QrLoginStatusResponse)
|
||||
// 确认登录响应
|
||||
export interface ConfirmLoginResult {
|
||||
// 状态: pending-等待扫码, scanned-已扫码, confirmed-已确认, bind_phone-待绑定手机号, expired-已过期
|
||||
status: string;
|
||||
// JWT访问令牌(仅在confirmed状态时返回)
|
||||
accessToken?: string;
|
||||
// 用户信息
|
||||
// 是否成功
|
||||
success: boolean;
|
||||
// 消息
|
||||
message: string;
|
||||
// 登录用户信息
|
||||
userInfo?: {
|
||||
userId: number;
|
||||
username?: string;
|
||||
nickname?: string;
|
||||
avatar?: string;
|
||||
phone?: string;
|
||||
};
|
||||
// 剩余过期时间(秒)
|
||||
expiresIn?: number;
|
||||
// 租户ID
|
||||
tenantId?: number;
|
||||
// 是否需要绑定手机号
|
||||
needBindPhone?: boolean;
|
||||
// 状态提示信息
|
||||
message?: string;
|
||||
// 下一步操作:bind_phone-绑定手机号, redirect-跳转, wait-等待
|
||||
nextAction?: string;
|
||||
// 跳转URL(当nextAction为redirect时使用)
|
||||
redirectUrl?: string;
|
||||
// 成功消息
|
||||
successMessage?: string;
|
||||
// 兼容字段:如果后端返回 success
|
||||
success?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -122,8 +105,7 @@ export async function generateQRToken(data?: GenerateQRTokenParam) {
|
||||
...data
|
||||
}
|
||||
);
|
||||
// 兼容 code === 0 和 code === 200
|
||||
if ((res.code === 0 || res.code === 200) && res.data) {
|
||||
if (res.code === 0 && res.data) {
|
||||
return res.data;
|
||||
}
|
||||
return Promise.reject(new Error(res.message));
|
||||
@@ -136,8 +118,7 @@ export async function checkQRLoginStatus(token: string) {
|
||||
const res = await request.get<ApiResult<QRLoginStatusResult>>(
|
||||
SERVER_API_URL + `/qr-login/status/${token}`
|
||||
);
|
||||
// 兼容 code === 0 和 code === 200
|
||||
if ((res.code === 0 || res.code === 200) && res.data) {
|
||||
if (res.code === 0 && res.data) {
|
||||
return res.data;
|
||||
}
|
||||
return Promise.reject(new Error(res.message));
|
||||
@@ -145,22 +126,16 @@ export async function checkQRLoginStatus(token: string) {
|
||||
|
||||
/**
|
||||
* 确认扫码登录(通用接口)
|
||||
*
|
||||
* @description 小程序端确认扫码登录
|
||||
* @param data - 包含 token 和用户信息
|
||||
*/
|
||||
export async function confirmQRLogin(data: ConfirmLoginParam) {
|
||||
const res = await request.post<ApiResult<ConfirmLoginResult>>(
|
||||
SERVER_API_URL + '/qr-login/confirm',
|
||||
data
|
||||
);
|
||||
console.log('[QRLogin API] confirmQRLogin 响应:', res);
|
||||
|
||||
// 兼容 code === 0 和 code === 200
|
||||
if ((res.code === 0 || res.code === 200) && res.data) {
|
||||
if (res.code === 0 && res.data) {
|
||||
return res.data;
|
||||
}
|
||||
return Promise.reject(new Error(res.message || '登录确认失败'));
|
||||
return Promise.reject(new Error(res.message));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -187,13 +162,10 @@ export async function confirmWechatQRLogin(token: string, userId: number) {
|
||||
SERVER_API_URL + '/qr-login/confirm',
|
||||
data
|
||||
);
|
||||
console.log('[QRLogin API] confirmWechatQRLogin 响应:', res);
|
||||
|
||||
// 兼容 code === 0 和 code === 200
|
||||
if ((res.code === 0 || res.code === 200) && res.data) {
|
||||
if (res.code === 0 && res.data) {
|
||||
return res.data;
|
||||
}
|
||||
return Promise.reject(new Error(res.message || '确认登录失败'));
|
||||
return Promise.reject(new Error(res.message));
|
||||
} catch (error: any) {
|
||||
return Promise.reject(new Error(error.message || '确认登录失败'));
|
||||
}
|
||||
@@ -229,8 +201,6 @@ async function getDeviceInfo() {
|
||||
*/
|
||||
export function parseQRContent(qrContent: string): string | null {
|
||||
try {
|
||||
console.log('解析二维码内容1:', qrContent);
|
||||
|
||||
// 尝试解析JSON格式
|
||||
if (qrContent.startsWith('{')) {
|
||||
const parsed = JSON.parse(qrContent);
|
||||
|
||||
-287
@@ -1,287 +0,0 @@
|
||||
/**
|
||||
* SDK 管理 API
|
||||
*/
|
||||
import type { SDK, SDKVersion, SDKDownloadRecord, SDKCategory } from '../types/sdk';
|
||||
|
||||
// 模拟数据
|
||||
const mockSDKs: SDK[] = [
|
||||
{
|
||||
id: '1',
|
||||
name: 'JavaScript SDK',
|
||||
icon: 'https://cdn.jsdelivr.net/npm/@programming-icons/cdn/javascript@1.0.0/javascript.png',
|
||||
description: '适用于 Web 浏览器和 Node.js 的 JavaScript SDK',
|
||||
version: '2.8.0',
|
||||
language: 'JavaScript',
|
||||
category: 'web',
|
||||
downloadCount: 125680,
|
||||
stars: 3420,
|
||||
docsUrl: '/docs/sdk/javascript',
|
||||
npmPackage: '@websopy/sdk-js',
|
||||
downloadUrl: 'https://www.npmjs.com/package/@websopy/sdk-js',
|
||||
changelog: '- 优化性能 20%\n- 新增 TypeScript 支持\n- 修复若干 bug',
|
||||
dependencies: ['axios'],
|
||||
supportedVersions: ['ES6+', 'Node.js 14+'],
|
||||
lastUpdated: '2026-04-10'
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
name: 'TypeScript SDK',
|
||||
icon: 'https://cdn.jsdelivr.net/npm/@programming-icons/cdn/typescript@3.0.3/typescript.png',
|
||||
description: '完整的 TypeScript 类型支持,适用于现代 Web 开发',
|
||||
version: '2.8.0',
|
||||
language: 'TypeScript',
|
||||
category: 'web',
|
||||
downloadCount: 98650,
|
||||
stars: 2890,
|
||||
docsUrl: '/docs/sdk/typescript',
|
||||
npmPackage: '@websopy/sdk-ts',
|
||||
downloadUrl: 'https://www.npmjs.com/package/@websopy/sdk-ts',
|
||||
changelog: '- 完整的类型定义\n- 更好的 IDE 支持\n- 新增装饰器支持',
|
||||
dependencies: [],
|
||||
supportedVersions: ['TypeScript 4.0+'],
|
||||
lastUpdated: '2026-04-10'
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
name: '微信小程序 SDK',
|
||||
icon: 'https://res.wx.qq.com/a/wx_fed/assets/res/NTI4MWU5.ico',
|
||||
description: '专为微信小程序优化的 SDK,支持插件机制',
|
||||
version: '2.7.5',
|
||||
language: 'JavaScript',
|
||||
category: 'mini-program',
|
||||
downloadCount: 78540,
|
||||
stars: 1560,
|
||||
docsUrl: '/docs/sdk/wx-mini-program',
|
||||
npmPackage: '@websopy/sdk-wx',
|
||||
downloadUrl: 'https://www.npmjs.com/package/@websopy/sdk-wx',
|
||||
changelog: '- 适配最新微信版本\n- 新增分享能力\n- 优化包体积',
|
||||
dependencies: ['wechat-miniprogram'],
|
||||
supportedVersions: ['微信小程序 2.0+'],
|
||||
lastUpdated: '2026-04-08'
|
||||
},
|
||||
{
|
||||
id: '4',
|
||||
name: 'Flutter SDK',
|
||||
icon: 'https://cdn.jsdelivr.net/gh/flutter/website@main/src/assets/images/flutter-icon.svg',
|
||||
description: 'Flutter 跨平台 SDK,支持 iOS 和 Android',
|
||||
version: '1.5.2',
|
||||
language: 'Flutter',
|
||||
category: 'mobile',
|
||||
downloadCount: 45230,
|
||||
stars: 980,
|
||||
docsUrl: '/docs/sdk/flutter',
|
||||
downloadUrl: 'https://pub.dev/packages/websopy_flutter',
|
||||
changelog: '- 支持 Flutter 3.0\n- 新增状态管理\n- 优化首屏加载',
|
||||
dependencies: ['http', 'shared_preferences'],
|
||||
supportedVersions: ['Flutter 2.0+', 'Dart 2.12+'],
|
||||
lastUpdated: '2026-04-05'
|
||||
},
|
||||
{
|
||||
id: '5',
|
||||
name: 'React Native SDK',
|
||||
icon: 'https://upload.wikimedia.org/wikipedia/commons/thumb/a/a7/React-icon.svg/512px-React-icon.svg.png',
|
||||
description: 'React Native 原生模块,完美的性能体验',
|
||||
version: '2.4.0',
|
||||
language: 'React Native',
|
||||
category: 'mobile',
|
||||
downloadCount: 38560,
|
||||
stars: 870,
|
||||
docsUrl: '/docs/sdk/react-native',
|
||||
downloadUrl: 'https://www.npmjs.com/package/@websopy/sdk-rn',
|
||||
changelog: '- 新增 Hooks API\n- 支持 TypeScript\n- 修复内存泄漏',
|
||||
dependencies: ['react-native'],
|
||||
supportedVersions: ['React Native 0.65+', 'React 17+'],
|
||||
lastUpdated: '2026-04-03'
|
||||
},
|
||||
{
|
||||
id: '6',
|
||||
name: 'Java SDK',
|
||||
icon: 'https://cdn.jsdelivr.net/npm/@programming-icons/cdn/java@1.0.0/java-original.svg',
|
||||
description: '企业级 Java SDK,支持 Spring Boot 集成',
|
||||
version: '3.2.0',
|
||||
language: 'Java',
|
||||
category: 'server',
|
||||
downloadCount: 65420,
|
||||
stars: 1230,
|
||||
docsUrl: '/docs/sdk/java',
|
||||
downloadUrl: '/downloads/sdk-java-3.2.0.jar',
|
||||
changelog: '- 支持 Spring Boot 3.0\n- 新增响应式编程\n- 优化连接池',
|
||||
dependencies: ['slf4j', 'jackson'],
|
||||
supportedVersions: ['Java 8+', 'Spring Boot 2.0+'],
|
||||
lastUpdated: '2026-04-01'
|
||||
},
|
||||
{
|
||||
id: '7',
|
||||
name: 'Go SDK',
|
||||
icon: 'https://cdn.jsdelivr.net/npm/@programming-icons/cdn/go@1.0.0/go-original.svg',
|
||||
description: '高性能 Go SDK,支持 goroutine 并发',
|
||||
version: '2.5.0',
|
||||
language: 'Go',
|
||||
category: 'server',
|
||||
downloadCount: 32180,
|
||||
stars: 760,
|
||||
docsUrl: '/docs/sdk/go',
|
||||
downloadUrl: 'go get github.com/websopy/sdk-go@latest',
|
||||
changelog: '- 支持 Go 1.18 泛型\n- 新增中间件支持\n- 优化性能',
|
||||
dependencies: [],
|
||||
supportedVersions: ['Go 1.16+'],
|
||||
lastUpdated: '2026-03-28'
|
||||
},
|
||||
{
|
||||
id: '8',
|
||||
name: 'Python SDK',
|
||||
icon: 'https://cdn.jsdelivr.net/npm/@programming-icons/cdn/python@3.10.0/python-original.svg',
|
||||
description: 'Pythonic 的 Python SDK,支持异步编程',
|
||||
version: '2.6.0',
|
||||
language: 'Python',
|
||||
category: 'server',
|
||||
downloadCount: 52100,
|
||||
stars: 980,
|
||||
docsUrl: '/docs/sdk/python',
|
||||
downloadUrl: 'pip install websopy-sdk',
|
||||
changelog: '- 支持 asyncio\n- 新增类型提示\n- 兼容 Python 3.11',
|
||||
dependencies: ['requests', 'typing-extensions'],
|
||||
supportedVersions: ['Python 3.7+'],
|
||||
lastUpdated: '2026-03-25'
|
||||
},
|
||||
{
|
||||
id: '9',
|
||||
name: 'Swift SDK',
|
||||
icon: 'https://cdn.jsdelivr.net/npm/@programming-icons/cdn/swift@1.0.0/swift-original.svg',
|
||||
description: '原生 iOS/macOS Swift SDK,支持 SwiftUI',
|
||||
version: '1.8.0',
|
||||
language: 'Swift',
|
||||
category: 'mobile',
|
||||
downloadCount: 24560,
|
||||
stars: 560,
|
||||
docsUrl: '/docs/sdk/swift',
|
||||
downloadUrl: 'https://github.com/websopy/sdk-swift/releases',
|
||||
changelog: '- 支持 Swift 5.7\n- 新增 Combine 支持\n- 优化包体积',
|
||||
dependencies: [],
|
||||
supportedVersions: ['iOS 13+', 'macOS 11+', 'Swift 5.0+'],
|
||||
lastUpdated: '2026-03-20'
|
||||
},
|
||||
{
|
||||
id: '10',
|
||||
name: '.NET SDK',
|
||||
icon: 'https://cdn.jsdelivr.net/npm/@programming-icons/cdn/dot-net@1.0.0/dot-net-original.svg',
|
||||
description: '.NET 5/6/7 SDK,支持 Entity Framework Core',
|
||||
version: '2.3.0',
|
||||
language: '.NET',
|
||||
category: 'server',
|
||||
downloadCount: 28940,
|
||||
stars: 450,
|
||||
docsUrl: '/docs/sdk/dotnet',
|
||||
downloadUrl: 'https://www.nuget.org/packages/WebsopySDK',
|
||||
changelog: '- 支持 .NET 7\n- 新增依赖注入\n- 支持 Blazor',
|
||||
dependencies: ['Microsoft.Extensions.DependencyInjection'],
|
||||
supportedVersions: ['.NET 5+', '.NET Core 3.1+'],
|
||||
lastUpdated: '2026-03-18'
|
||||
}
|
||||
];
|
||||
|
||||
/**
|
||||
* 获取 SDK 分类列表
|
||||
*/
|
||||
export async function listSDKCategories(): Promise<SDKCategory[]> {
|
||||
return [
|
||||
{ id: 'web', name: 'Web 开发', icon: 'globe', count: 2, description: '适用于浏览器和 Node.js 环境' },
|
||||
{ id: 'mobile', name: '移动开发', icon: 'mobile', count: 3, description: 'iOS、Android、Flutter、React Native' },
|
||||
{ id: 'server', name: '服务端', icon: 'server', count: 4, description: 'Java、Go、Python、.NET 等后端语言' },
|
||||
{ id: 'mini-program', name: '小程序', icon: 'wechat', count: 1, description: '微信小程序、支付宝小程序等' }
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 SDK 列表
|
||||
*/
|
||||
export async function pageSDK(params: {
|
||||
category?: string;
|
||||
language?: string;
|
||||
keyword?: string;
|
||||
page?: number;
|
||||
pageSize?: number;
|
||||
}): Promise<{ list: SDK[]; total: number }> {
|
||||
const { category, language, keyword, page = 1, pageSize = 10 } = params;
|
||||
|
||||
let filtered = [...mockSDKs];
|
||||
|
||||
if (category) {
|
||||
filtered = filtered.filter(sdk => sdk.category === category);
|
||||
}
|
||||
if (language) {
|
||||
filtered = filtered.filter(sdk => sdk.language === language);
|
||||
}
|
||||
if (keyword) {
|
||||
const kw = keyword.toLowerCase();
|
||||
filtered = filtered.filter(sdk =>
|
||||
sdk.name.toLowerCase().includes(kw) ||
|
||||
sdk.description.toLowerCase().includes(kw)
|
||||
);
|
||||
}
|
||||
|
||||
const start = (page - 1) * pageSize;
|
||||
const list = filtered.slice(start, start + pageSize);
|
||||
|
||||
return { list, total: filtered.length };
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 SDK 详情
|
||||
*/
|
||||
export async function getSDKDetail(id: string): Promise<SDK | null> {
|
||||
return mockSDKs.find(sdk => sdk.id === id) || null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 SDK 版本列表
|
||||
*/
|
||||
export async function listSDKVersions(id: string): Promise<SDKVersion[]> {
|
||||
const sdk = mockSDKs.find(s => s.id === id);
|
||||
if (!sdk) return [];
|
||||
|
||||
return [
|
||||
{ version: sdk.version, releaseDate: sdk.lastUpdated, releaseNotes: sdk.changelog, downloadUrl: sdk.downloadUrl, size: '2.5MB', sha256: 'a1b2c3d4e5f6...' },
|
||||
{ version: '2.7.0', releaseDate: '2026-03-15', releaseNotes: '- 性能优化\n- Bug 修复', downloadUrl: '', size: '2.3MB', sha256: 'b2c3d4e5f6g7...' },
|
||||
{ version: '2.6.0', releaseDate: '2026-02-20', releaseNotes: '- 新增功能\n- 文档更新', downloadUrl: '', size: '2.1MB', sha256: 'c3d4e5f6g7h8...' }
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取下载记录
|
||||
*/
|
||||
export async function pageDownloadRecord(params: {
|
||||
page?: number;
|
||||
pageSize?: number;
|
||||
}): Promise<{ list: SDKDownloadRecord[]; total: number }> {
|
||||
return {
|
||||
list: [
|
||||
{ id: '1', sdkId: '1', sdkName: 'JavaScript SDK', version: '2.8.0', language: 'JavaScript', userId: '1', userName: '张三', downloadTime: '2026-04-12 14:30:00', ipAddress: '127.0.0.1' },
|
||||
{ id: '2', sdkId: '3', sdkName: '微信小程序 SDK', version: '2.7.5', language: 'JavaScript', userId: '1', userName: '张三', downloadTime: '2026-04-11 10:20:00', ipAddress: '127.0.0.1' },
|
||||
{ id: '3', sdkId: '6', sdkName: 'Java SDK', version: '3.2.0', language: 'Java', userId: '1', userName: '张三', downloadTime: '2026-04-10 16:45:00', ipAddress: '127.0.0.1' }
|
||||
],
|
||||
total: 3
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 记录 SDK 下载
|
||||
*/
|
||||
export async function recordDownload(id: string, version: string): Promise<void> {
|
||||
console.log(`Download recorded: ${id} v${version}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 SDK 统计
|
||||
*/
|
||||
export async function getSDKStats(): Promise<{
|
||||
totalDownloads: number;
|
||||
totalSDKs: number;
|
||||
topSDKs: SDK[];
|
||||
}> {
|
||||
return {
|
||||
totalDownloads: mockSDKs.reduce((sum, sdk) => sum + sdk.downloadCount, 0),
|
||||
totalSDKs: mockSDKs.length,
|
||||
topSDKs: [...mockSDKs].sort((a, b) => b.downloadCount - a.downloadCount).slice(0, 5)
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
import { generateMiniProgramCode } from '@/api/shop/shopInvite'
|
||||
|
||||
/**
|
||||
* 生成「分享海报」用的小程序码(二维码)图片地址。
|
||||
*
|
||||
* - scene 复用现有 uid_${inviterId} 约定,与邀请裂变逻辑一致;
|
||||
* - page 指向被分享的页面(小程序码扫码后落地页)。
|
||||
*
|
||||
* 注意:当前后端 /wx-login/getOrderQRCodeUnlimited/{scene} 仅消费 scene,
|
||||
* 若希望扫码后直达指定页面而非默认页,需后端读取并转发 page 参数。
|
||||
*
|
||||
* @param page 被分享页路径,如 pages/shop/product-detail
|
||||
* @param inviterId 当前登录用户 id(用于裂变归因),未登录可省略
|
||||
*/
|
||||
export function generateShareCode(page: string, inviterId?: number | string) {
|
||||
const scene = inviterId ? `uid_${inviterId}` : 'share'
|
||||
return generateMiniProgramCode({
|
||||
page,
|
||||
scene,
|
||||
width: 200,
|
||||
envVersion: 'release',
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
import request from '@/utils/request'
|
||||
import type { ApiResult, PageResult } from '@/api'
|
||||
import type {
|
||||
ShopActivity,
|
||||
ShopActivityParam,
|
||||
ActivitySignUpParams,
|
||||
ActivityStats,
|
||||
} from './model'
|
||||
import { normalizeActivity } from './model'
|
||||
|
||||
export type { ShopActivity, ActivityStatus, ActivityType } from './model'
|
||||
|
||||
/**
|
||||
* 分页查询活动
|
||||
*/
|
||||
export async function pageShopActivity(params: ShopActivityParam) {
|
||||
const res = await request.get<ApiResult<PageResult<ShopActivity>>>(
|
||||
'/shop/shop-activity/page',
|
||||
params
|
||||
)
|
||||
if (res.code === 0 && res.data) {
|
||||
return {
|
||||
...res.data,
|
||||
list: (res.data.list || []).map(normalizeActivity),
|
||||
}
|
||||
}
|
||||
return Promise.reject(new Error(res.message))
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询活动列表
|
||||
*/
|
||||
export async function listShopActivity(params?: ShopActivityParam) {
|
||||
const res = await request.get<ApiResult<ShopActivity[]>>(
|
||||
'/shop/shop-activity',
|
||||
params
|
||||
)
|
||||
if (res.code === 0 && res.data) {
|
||||
return res.data.map(normalizeActivity)
|
||||
}
|
||||
return Promise.reject(new Error(res.message))
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据ID查询活动
|
||||
*/
|
||||
export async function getShopActivity(id: number) {
|
||||
const res = await request.get<ApiResult<ShopActivity>>(
|
||||
'/shop/shop-activity/' + id
|
||||
)
|
||||
if (res.code === 0 && res.data) {
|
||||
return normalizeActivity(res.data)
|
||||
}
|
||||
return Promise.reject(new Error(res.message))
|
||||
}
|
||||
|
||||
/**
|
||||
* 报名活动
|
||||
*/
|
||||
export async function signUpActivity(params: ActivitySignUpParams) {
|
||||
const res = await request.post<ApiResult<unknown>>(
|
||||
'/shop/shop-activity/sign-up',
|
||||
params
|
||||
)
|
||||
if (res.code === 0) {
|
||||
return res.message
|
||||
}
|
||||
return Promise.reject(new Error(res.message))
|
||||
}
|
||||
|
||||
/**
|
||||
* 取消报名
|
||||
*/
|
||||
export async function cancelSignUpActivity(activityId: number) {
|
||||
const res = await request.post<ApiResult<unknown>>(
|
||||
'/shop/shop-activity/cancel-sign-up',
|
||||
{ activityId }
|
||||
)
|
||||
if (res.code === 0) {
|
||||
return res.message
|
||||
}
|
||||
return Promise.reject(new Error(res.message))
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取活动报名状态
|
||||
*/
|
||||
export async function getSignUpStatus(activityId: number) {
|
||||
const res = await request.get<ApiResult<{ signedUp: boolean }>>(
|
||||
'/shop/shop-activity/sign-up-status',
|
||||
{ activityId }
|
||||
)
|
||||
if (res.code === 0 && res.data) {
|
||||
return res.data
|
||||
}
|
||||
return Promise.reject(new Error(res.message))
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取活动统计
|
||||
*/
|
||||
export async function getActivityStats() {
|
||||
const res = await request.get<ApiResult<ActivityStats>>(
|
||||
'/shop/shop-activity/stats'
|
||||
)
|
||||
if (res.code === 0 && res.data) {
|
||||
return res.data
|
||||
}
|
||||
return Promise.reject(new Error(res.message))
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
/**
|
||||
* 活动模型
|
||||
*/
|
||||
|
||||
// 活动类型(前端语义化)
|
||||
export type ActivityType = 'discount' | 'full_reduction' | 'seckill' | 'group_buy' | 'flash_sale'
|
||||
|
||||
// 活动状态(前端语义化)
|
||||
export type ActivityStatus = 'upcoming' | 'ongoing' | 'ended' | 'cancelled'
|
||||
|
||||
// 活动数据(与后端字段对齐)
|
||||
export interface ShopActivity {
|
||||
id: number
|
||||
name: string
|
||||
image: string
|
||||
banner?: string
|
||||
startTime: string
|
||||
endTime: string
|
||||
/** 后端返回整数: 0=未开始 1=进行中 2=已结束,前端通过 normalizeActivity 转为语义化字符串 */
|
||||
status: ActivityStatus | number
|
||||
/** 后端返回整数: 1=满减 2=折扣 3=秒杀 4=拼团 */
|
||||
type: ActivityType | number
|
||||
typeName?: string
|
||||
description: string
|
||||
participants?: number
|
||||
maxParticipants?: number
|
||||
discountRate?: number
|
||||
reductionAmount?: number
|
||||
minAmount?: number
|
||||
rules?: string
|
||||
tenantId?: number
|
||||
createTime: string
|
||||
updateTime?: string
|
||||
}
|
||||
|
||||
// 活动查询参数
|
||||
export interface ShopActivityParam {
|
||||
page?: number
|
||||
limit?: number
|
||||
status?: number | ActivityStatus
|
||||
type?: number | ActivityType
|
||||
keywords?: string
|
||||
}
|
||||
|
||||
// 活动报名参数
|
||||
export interface ActivitySignUpParams {
|
||||
activityId: number
|
||||
userId?: number
|
||||
contactPhone?: string
|
||||
remark?: string
|
||||
}
|
||||
|
||||
// 活动统计
|
||||
export interface ActivityStats {
|
||||
total: number
|
||||
ongoing: number
|
||||
upcoming: number
|
||||
ended: number
|
||||
totalParticipants: number
|
||||
}
|
||||
|
||||
// 后端整数状态 → 前端语义化状态
|
||||
export function normalizeActivityStatus(status: number | ActivityStatus): ActivityStatus {
|
||||
if (typeof status === 'string') return status
|
||||
const map: Record<number, ActivityStatus> = { 0: 'upcoming', 1: 'ongoing', 2: 'ended' }
|
||||
return map[status] ?? 'ended'
|
||||
}
|
||||
|
||||
// 后端整数类型 → 前端语义化类型
|
||||
export function normalizeActivityType(type: number | ActivityType): ActivityType {
|
||||
if (typeof type === 'string') return type
|
||||
const map: Record<number, ActivityType> = {
|
||||
1: 'full_reduction',
|
||||
2: 'discount',
|
||||
3: 'seckill',
|
||||
4: 'group_buy',
|
||||
}
|
||||
return map[type] ?? 'discount'
|
||||
}
|
||||
|
||||
// 规范化活动对象(处理后端整数字段)
|
||||
export function normalizeActivity(a: ShopActivity): ShopActivity & { status: ActivityStatus; type: ActivityType } {
|
||||
return {
|
||||
...a,
|
||||
participants: a.participants ?? 0,
|
||||
status: normalizeActivityStatus(a.status as number),
|
||||
type: normalizeActivityType(a.type as number),
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,154 @@
|
||||
import request from '@/utils/request'
|
||||
import type { ApiResult, PageResult } from '@/api'
|
||||
|
||||
// 售后类型
|
||||
export type AfterSaleType = 'refund' | 'return' | 'exchange' | 'repair'
|
||||
|
||||
// 售后状态
|
||||
export type AfterSaleStatus =
|
||||
| 'pending' // 待审核
|
||||
| 'approved' // 已同意
|
||||
| 'rejected' // 已拒绝
|
||||
| 'processing' // 处理中
|
||||
| 'completed' // 已完成
|
||||
| 'cancelled' // 已取消
|
||||
|
||||
// 售后进度记录
|
||||
export interface ProgressRecord {
|
||||
id: string
|
||||
time: string
|
||||
status: string
|
||||
description: string
|
||||
operator?: string
|
||||
remark?: string
|
||||
}
|
||||
|
||||
// 售后详情
|
||||
export interface AfterSaleDetail {
|
||||
id: string
|
||||
orderId: string
|
||||
orderNo: string
|
||||
goodsName?: string
|
||||
type: AfterSaleType
|
||||
status: AfterSaleStatus
|
||||
reason: string
|
||||
description: string
|
||||
amount: number
|
||||
applyTime: string
|
||||
processTime?: string
|
||||
completeTime?: string
|
||||
rejectReason?: string
|
||||
contactPhone?: string
|
||||
evidenceImages: string[]
|
||||
progressRecords: ProgressRecord[]
|
||||
}
|
||||
|
||||
// 售后申请参数
|
||||
export interface AfterSaleApplyParams {
|
||||
orderId: string
|
||||
type: AfterSaleType
|
||||
reason: string
|
||||
description?: string
|
||||
amount?: number
|
||||
contactPhone?: string
|
||||
evidenceImages?: string[]
|
||||
goodsItems?: Array<{ goodsId: string; quantity: number }>
|
||||
}
|
||||
|
||||
// 售后列表查询参数
|
||||
export interface AfterSaleListParams {
|
||||
page?: number
|
||||
pageSize?: number
|
||||
status?: AfterSaleStatus
|
||||
type?: AfterSaleType
|
||||
}
|
||||
|
||||
// 售后类型映射
|
||||
export const AFTER_SALE_TYPE_MAP: Record<AfterSaleType, string> = {
|
||||
refund: '退款',
|
||||
return: '退货',
|
||||
exchange: '换货',
|
||||
repair: '维修',
|
||||
}
|
||||
|
||||
// 售后状态映射
|
||||
export const AFTER_SALE_STATUS_MAP: Record<AfterSaleStatus, string> = {
|
||||
pending: '待审核',
|
||||
approved: '已同意',
|
||||
rejected: '已拒绝',
|
||||
processing: '处理中',
|
||||
completed: '已完成',
|
||||
cancelled: '已取消',
|
||||
}
|
||||
|
||||
// 格式化售后状态
|
||||
export const formatAfterSaleStatus = (status: AfterSaleStatus): {
|
||||
text: string
|
||||
color: string
|
||||
icon: string
|
||||
} => {
|
||||
const statusMap: Record<AfterSaleStatus, { text: string; color: string; icon: string }> = {
|
||||
pending: { text: '待审核', color: 'text-orange-500', icon: '⏳' },
|
||||
approved: { text: '已同意', color: 'text-green-500', icon: '✅' },
|
||||
rejected: { text: '已拒绝', color: 'text-red-500', icon: '❌' },
|
||||
processing: { text: '处理中', color: 'text-blue-500', icon: '🔄' },
|
||||
completed: { text: '已完成', color: 'text-green-500', icon: '✅' },
|
||||
cancelled: { text: '已取消', color: 'text-gray-400', icon: '⭕' },
|
||||
}
|
||||
return statusMap[status] || { text: status, color: 'text-gray-400', icon: '📋' }
|
||||
}
|
||||
|
||||
/**
|
||||
* 申请售后
|
||||
*/
|
||||
export async function applyAfterSale(data: AfterSaleApplyParams) {
|
||||
const res = await request.post<ApiResult<AfterSaleDetail>>(
|
||||
'/shop/shop-after-sale/apply',
|
||||
data
|
||||
)
|
||||
if (res.code === 0) {
|
||||
return res.data
|
||||
}
|
||||
return Promise.reject(new Error(res.message))
|
||||
}
|
||||
|
||||
/**
|
||||
* 分页查询售后列表
|
||||
*/
|
||||
export async function pageAfterSaleList(params: AfterSaleListParams) {
|
||||
const res = await request.get<ApiResult<PageResult<AfterSaleDetail>>>(
|
||||
'/shop/shop-after-sale/page',
|
||||
params
|
||||
)
|
||||
if (res.code === 0) {
|
||||
return res.data
|
||||
}
|
||||
return Promise.reject(new Error(res.message))
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询售后详情
|
||||
*/
|
||||
export async function getAfterSaleDetail(id: string) {
|
||||
const res = await request.get<ApiResult<AfterSaleDetail>>(
|
||||
'/shop/shop-after-sale/' + id
|
||||
)
|
||||
if (res.code === 0 && res.data) {
|
||||
return res.data
|
||||
}
|
||||
return Promise.reject(new Error(res.message))
|
||||
}
|
||||
|
||||
/**
|
||||
* 撤销售后申请
|
||||
*/
|
||||
export async function cancelAfterSale(id: string) {
|
||||
const res = await request.post<ApiResult<unknown>>(
|
||||
'/shop/shop-after-sale/cancel',
|
||||
{ id }
|
||||
)
|
||||
if (res.code === 0) {
|
||||
return res.message
|
||||
}
|
||||
return Promise.reject(new Error(res.message))
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
import request from '@/utils/request'
|
||||
import type { ApiResult, PageResult } from '@/api'
|
||||
import type {
|
||||
ShopBooking,
|
||||
ShopBookingParam,
|
||||
ShopBookingParams,
|
||||
BookingStats,
|
||||
} from './model'
|
||||
|
||||
/**
|
||||
* 分页查询预约订单
|
||||
*/
|
||||
export async function pageShopBooking(params: ShopBookingParam) {
|
||||
const res = await request.get<ApiResult<PageResult<ShopBooking>>>(
|
||||
'/shop/shop-booking/page',
|
||||
params
|
||||
)
|
||||
if (res.code === 0) {
|
||||
return res.data
|
||||
}
|
||||
return Promise.reject(new Error(res.message))
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询预约订单列表
|
||||
*/
|
||||
export async function listShopBooking(params?: ShopBookingParam) {
|
||||
const res = await request.get<ApiResult<ShopBooking[]>>(
|
||||
'/shop/shop-booking',
|
||||
params
|
||||
)
|
||||
if (res.code === 0 && res.data) {
|
||||
return res.data
|
||||
}
|
||||
return Promise.reject(new Error(res.message))
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据ID查询预约订单
|
||||
*/
|
||||
export async function getShopBooking(id: string) {
|
||||
const res = await request.get<ApiResult<ShopBooking>>(
|
||||
'/shop/shop-booking/' + id
|
||||
)
|
||||
if (res.code === 0 && res.data) {
|
||||
return res.data
|
||||
}
|
||||
return Promise.reject(new Error(res.message))
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建预约订单
|
||||
*/
|
||||
export async function createShopBooking(data: ShopBookingParams) {
|
||||
const res = await request.post<ApiResult<unknown>>(
|
||||
'/shop/shop-booking',
|
||||
data
|
||||
)
|
||||
if (res.code === 0) {
|
||||
return res.message
|
||||
}
|
||||
return Promise.reject(new Error(res.message))
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改预约订单
|
||||
*/
|
||||
export async function updateShopBooking(data: ShopBookingParams) {
|
||||
const res = await request.put<ApiResult<unknown>>(
|
||||
'/shop/shop-booking',
|
||||
data
|
||||
)
|
||||
if (res.code === 0) {
|
||||
return res.message
|
||||
}
|
||||
return Promise.reject(new Error(res.message))
|
||||
}
|
||||
|
||||
/**
|
||||
* 取消预约订单
|
||||
*/
|
||||
export async function cancelShopBooking(id?: string) {
|
||||
const res = await request.del<ApiResult<unknown>>(
|
||||
'/shop/shop-booking/' + id
|
||||
)
|
||||
if (res.code === 0) {
|
||||
return res.message
|
||||
}
|
||||
return Promise.reject(new Error(res.message))
|
||||
}
|
||||
|
||||
/**
|
||||
* 改签预约订单
|
||||
*/
|
||||
export async function rescheduleShopBooking(data: {
|
||||
bookingId: string
|
||||
newDate: string
|
||||
newTime: string
|
||||
}) {
|
||||
const res = await request.post<ApiResult<unknown>>(
|
||||
'/shop/shop-booking/reschedule',
|
||||
data
|
||||
)
|
||||
if (res.code === 0) {
|
||||
return res.message
|
||||
}
|
||||
return Promise.reject(new Error(res.message))
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取预约统计
|
||||
*/
|
||||
export async function getBookingStats() {
|
||||
const res = await request.get<ApiResult<BookingStats>>(
|
||||
'/shop/shop-booking/stats'
|
||||
)
|
||||
if (res.code === 0 && res.data) {
|
||||
return res.data
|
||||
}
|
||||
return Promise.reject(new Error(res.message))
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
/**
|
||||
* 预约订单模型
|
||||
*/
|
||||
|
||||
// 预约状态
|
||||
export type BookingStatus = 'pending' | 'confirmed' | 'in_progress' | 'completed' | 'cancelled' | 'rescheduled'
|
||||
|
||||
// 预约订单数据
|
||||
export interface ShopBooking {
|
||||
id: number | string
|
||||
bookingNo: string // 预约编号
|
||||
storeId: number // 店铺ID
|
||||
storeName: string // 店铺名称
|
||||
storePhone?: string // 门店服务电话
|
||||
serviceId: number // 服务ID
|
||||
serviceName: string // 服务名称
|
||||
bookingDate: string // 预约日期
|
||||
bookingTime: string // 预约时间段
|
||||
status: BookingStatus // 预约状态
|
||||
price: number // 价格
|
||||
createTime: string // 创建时间
|
||||
updateTime?: string // 更新时间
|
||||
remark?: string // 备注
|
||||
contactName?: string // 联系人
|
||||
contactPhone?: string // 联系电话
|
||||
address?: string // 地址
|
||||
}
|
||||
|
||||
// 预约查询参数
|
||||
export interface ShopBookingParam {
|
||||
page?: number
|
||||
limit?: number
|
||||
status?: BookingStatus // 按状态筛选
|
||||
storeId?: number // 按店铺筛选
|
||||
startDate?: string // 预约日期范围-起始
|
||||
endDate?: string // 预约日期范围-结束
|
||||
keywords?: string // 关键词搜索
|
||||
sortBy?: string // 排序方式(createTime, bookingDate)
|
||||
}
|
||||
|
||||
// 创建/更新预约参数
|
||||
export interface ShopBookingParams extends ShopBooking {}
|
||||
|
||||
// 预约统计
|
||||
export interface BookingStats {
|
||||
total: number // 总预约数
|
||||
pending: number // 待服务
|
||||
confirmed: number // 已确认
|
||||
inProgress: number // 进行中
|
||||
completed: number // 已完成
|
||||
cancelled: number // 已取消
|
||||
rescheduled: number // 已改签
|
||||
}
|
||||
@@ -0,0 +1,153 @@
|
||||
import request from '@/utils/request';
|
||||
import type { ApiResult } from '@/api';
|
||||
import type { ShopCart, ShopCartParam, AddToCartParam, UpdateCartNumParam } from './model';
|
||||
|
||||
/**
|
||||
* 获取购物车列表
|
||||
*/
|
||||
export async function listShopCart(params?: ShopCartParam) {
|
||||
const res = await request.get<ApiResult<ShopCart[]>>(
|
||||
'/shop/shop-cart',
|
||||
params
|
||||
);
|
||||
if (res.code === 0 && res.data) {
|
||||
return res.data;
|
||||
}
|
||||
return Promise.reject(new Error(res.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取购物车分页列表
|
||||
*/
|
||||
export async function pageShopCart(params: ShopCartParam) {
|
||||
const res = await request.get<ApiResult<{ list: ShopCart[]; count: number }>>(
|
||||
'/shop/shop-cart/page',
|
||||
params
|
||||
);
|
||||
if (res.code === 0 && res.data) {
|
||||
return res.data;
|
||||
}
|
||||
return Promise.reject(new Error(res.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取购物车数量
|
||||
*/
|
||||
export async function getShopCartCount() {
|
||||
const res = await request.get<ApiResult<{ count: number }>>(
|
||||
'/shop/shop-cart/count'
|
||||
);
|
||||
if (res.code === 0 && res.data) {
|
||||
return res.data.count;
|
||||
}
|
||||
return Promise.reject(new Error(res.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加商品到购物车
|
||||
*/
|
||||
export async function addToCart(data: AddToCartParam) {
|
||||
const res = await request.post<ApiResult<unknown>>(
|
||||
'/shop/shop-cart',
|
||||
data
|
||||
);
|
||||
if (res.code === 0) {
|
||||
return res.message || '添加成功';
|
||||
}
|
||||
return Promise.reject(new Error(res.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新购物车商品数量
|
||||
*/
|
||||
export async function updateCartNum(data: UpdateCartNumParam) {
|
||||
const res = await request.put<ApiResult<unknown>>(
|
||||
'/shop/shop-cart',
|
||||
{ id: data.id, cartNum: data.cartNum }
|
||||
);
|
||||
if (res.code === 0) {
|
||||
return res.message || '更新成功';
|
||||
}
|
||||
return Promise.reject(new Error(res.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新购物车商品选中状态
|
||||
*/
|
||||
export async function updateCartChecked(id: number, checked: boolean) {
|
||||
const res = await request.put<ApiResult<unknown>>(
|
||||
'/shop/shop-cart',
|
||||
{ id, selected: checked }
|
||||
);
|
||||
if (res.code === 0) {
|
||||
return res.message || '更新成功';
|
||||
}
|
||||
return Promise.reject(new Error(res.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 全选/取消全选购物车
|
||||
*/
|
||||
export async function updateCartAllChecked(checked: boolean) {
|
||||
const res = await request.post<ApiResult<unknown>>(
|
||||
'/shop/shop-cart/selected',
|
||||
{ checked }
|
||||
);
|
||||
if (res.code === 0) {
|
||||
return res.message || '更新成功';
|
||||
}
|
||||
return Promise.reject(new Error(res.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除购物车商品
|
||||
*/
|
||||
export async function removeShopCart(id: number) {
|
||||
const res = await request.del<ApiResult<unknown>>(
|
||||
'/shop/shop-cart/' + id
|
||||
);
|
||||
if (res.code === 0) {
|
||||
return res.message || '删除成功';
|
||||
}
|
||||
return Promise.reject(new Error(res.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量删除购物车商品
|
||||
*/
|
||||
export async function removeBatchShopCart(ids: number[]) {
|
||||
const res = await request.del<ApiResult<unknown>>(
|
||||
'/shop/shop-cart/batch',
|
||||
ids
|
||||
);
|
||||
if (res.code === 0) {
|
||||
return res.message || '删除成功';
|
||||
}
|
||||
return Promise.reject(new Error(res.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 清空购物车
|
||||
*/
|
||||
export async function clearShopCart() {
|
||||
const res = await request.del<ApiResult<unknown>>(
|
||||
'/shop/shop-cart/clear'
|
||||
);
|
||||
if (res.code === 0) {
|
||||
return res.message || '清空成功';
|
||||
}
|
||||
return Promise.reject(new Error(res.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取购物车详情
|
||||
*/
|
||||
export async function getShopCart(id: number) {
|
||||
const res = await request.get<ApiResult<ShopCart>>(
|
||||
'/shop/shop-cart/' + id
|
||||
);
|
||||
if (res.code === 0 && res.data) {
|
||||
return res.data;
|
||||
}
|
||||
return Promise.reject(new Error(res.message));
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
import type { PageParam } from '@/api';
|
||||
|
||||
/**
|
||||
* 购物车项
|
||||
*/
|
||||
export interface ShopCart {
|
||||
// 主键ID
|
||||
id?: number;
|
||||
// 用户ID
|
||||
userId?: number;
|
||||
// 商品ID
|
||||
goodsId?: number;
|
||||
// SKU ID
|
||||
skuId?: number;
|
||||
// 数量(后端字段名 cartNum)
|
||||
cartNum?: number;
|
||||
// 商品名称
|
||||
goodsName?: string;
|
||||
// 商品图片
|
||||
goodsImage?: string;
|
||||
// SKU价格
|
||||
skuPrice?: string;
|
||||
// SKU规格
|
||||
skuSpec?: string;
|
||||
// 库存
|
||||
stock?: number;
|
||||
// 市场价(划线价)
|
||||
salePrice?: string | number;
|
||||
// 经销商价(VIP专享)
|
||||
dealerPrice?: string | number;
|
||||
// 是否选中(后端字段名 selected)
|
||||
selected?: boolean;
|
||||
// 租户id
|
||||
tenantId?: number;
|
||||
// 创建时间
|
||||
createTime?: string;
|
||||
// 更新时间
|
||||
updateTime?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 购物车项扩展(包含选中状态,用于前端展示)
|
||||
*/
|
||||
export interface ShopCartItem extends ShopCart {
|
||||
// 是否选中
|
||||
checked?: boolean;
|
||||
// 商品详情
|
||||
product?: any;
|
||||
// SKU详情
|
||||
sku?: any;
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加到购物车参数
|
||||
*/
|
||||
export interface AddToCartParam {
|
||||
// 商品ID
|
||||
goodsId: number;
|
||||
// SKU ID(单规格可不传)
|
||||
skuId?: number;
|
||||
// 数量(后端字段名 cartNum)
|
||||
cartNum: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新购物车数量参数
|
||||
*/
|
||||
export interface UpdateCartNumParam {
|
||||
// 购物车ID
|
||||
id: number;
|
||||
// 数量(后端字段名 cartNum)
|
||||
cartNum: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* 购物车搜索条件
|
||||
*/
|
||||
export interface ShopCartParam extends PageParam {
|
||||
userId?: number;
|
||||
}
|
||||
@@ -6,6 +6,8 @@ import type { PageParam } from '@/api';
|
||||
export interface ShopChatMessage {
|
||||
// 自增ID
|
||||
id?: number;
|
||||
// 会话ID
|
||||
conversationId?: number;
|
||||
// 发送人ID
|
||||
formUserId?: number;
|
||||
// 发送人名称
|
||||
@@ -60,4 +62,5 @@ export interface ShopChatMessage {
|
||||
export interface ShopChatMessageParam extends PageParam {
|
||||
id?: number;
|
||||
keywords?: string;
|
||||
conversationId?: number;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
import request from '@/utils/request';
|
||||
|
||||
/**
|
||||
* 获取我的佣金记录
|
||||
*/
|
||||
export function getMyCommissionList(params: any) {
|
||||
return request.get('/shop/shop-commission-record/my/list', params);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取佣金统计
|
||||
*/
|
||||
export function getCommissionStats() {
|
||||
return request.get('/shop/shop-commission-record/my/stats');
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取佣金记录列表(管理员)
|
||||
*/
|
||||
export function getCommissionList(params: any) {
|
||||
return request.get('/shop/shop-commission-record/page', params);
|
||||
}
|
||||
|
||||
/**
|
||||
* 手动触发佣金结算(管理员)
|
||||
*/
|
||||
export function settleCommission() {
|
||||
return request.post('/shop/shop-commission-record/settle');
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
import request from '@/utils/request';
|
||||
import type { ApiResult, PageResult } from '@/api';
|
||||
import type { ShopCoupon, ShopCouponParam } from './model';
|
||||
import type { ShopCoupon, ShopCouponParam, ShopCouponWithTake } from './model';
|
||||
|
||||
/**
|
||||
* 分页查询优惠券
|
||||
@@ -99,3 +99,17 @@ export async function getShopCoupon(id: number) {
|
||||
}
|
||||
return Promise.reject(new Error(res.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 领券中心-查询可领取优惠券列表(含是否已领取状态)
|
||||
*/
|
||||
export async function listCouponCenter(params?: ShopCouponParam) {
|
||||
const res = await request.post<ApiResult<ShopCouponWithTake[]>>(
|
||||
'/shop/shop-coupon/list',
|
||||
params
|
||||
);
|
||||
if (res.code === 0 && res.data) {
|
||||
return res.data;
|
||||
}
|
||||
return Promise.reject(new Error(res.message));
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ export interface ShopCoupon {
|
||||
name?: string;
|
||||
// 优惠券描述
|
||||
description?: string;
|
||||
// 优惠券类型(10满减券 20折扣券 30免费劵)
|
||||
// 优惠券类型(10满减券 20折扣券 30免费券 40无门槛券 50场地使用券)
|
||||
type?: number;
|
||||
// 满减券-减免金额
|
||||
reducePrice?: string;
|
||||
@@ -54,6 +54,18 @@ export interface ShopCoupon {
|
||||
limitPerUser?: number;
|
||||
// 是否启用(0禁用 1启用)
|
||||
enabled?: string;
|
||||
// 发放对象(0全部用户 1仅会员 2仅非会员 3指定用户)
|
||||
receiveTarget?: number;
|
||||
// 指定用户ID列表(JSON数组格式),receiveTarget=3时使用
|
||||
receiveUserIds?: string;
|
||||
// 场地使用券-场地类型
|
||||
venueType?: number;
|
||||
// 场地使用券-指定场地ID
|
||||
venueId?: number;
|
||||
// 场地使用券-可用次数(-1表示无限制)
|
||||
useCount?: number;
|
||||
// 场地使用券-使用时长(分钟)
|
||||
useDuration?: number;
|
||||
sortBy?: string;
|
||||
}
|
||||
|
||||
@@ -71,3 +83,15 @@ export interface ShopCouponParam extends PageParam {
|
||||
type?: number;
|
||||
minAmount?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* 领券中心优惠券(含是否已领取)
|
||||
*/
|
||||
export interface ShopCouponWithTake extends ShopCoupon {
|
||||
// 是否已领取
|
||||
hasTake?: boolean;
|
||||
// 用户已领取数量
|
||||
userTakeNum?: number;
|
||||
// 用户已使用数量
|
||||
userUseNum?: number;
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user