Compare commits
29 Commits
main
..
db1bc728f6
| Author | SHA1 | Date | |
|---|---|---|---|
| db1bc728f6 | |||
| 6dd4ca9785 | |||
| d085e02ecf | |||
| b4cd54e102 | |||
| 2e80a49ca1 | |||
| d611a76ebd | |||
| 9fcadd7725 | |||
| efcbaa5b60 | |||
| 0713ad4cf7 | |||
| 226e223d96 | |||
| dc72e530c7 | |||
| 5da7d17c55 | |||
| 9fba545d34 | |||
| babcf36439 | |||
| a703dcad90 | |||
| b673e2ebac | |||
| e986222ca7 | |||
| 2ec3383fd0 | |||
| ab1b32af68 | |||
| 5b5b7c9a90 | |||
| 39d3b59434 | |||
| 2ea16e22cf | |||
| 50c98cdcf7 | |||
| 8d4b55098f | |||
| 12d6bdfb06 | |||
| eeb63efe9f | |||
| 43093b505b | |||
| 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
|
|
||||||
+7
@@ -0,0 +1,7 @@
|
|||||||
|
<component name="InspectionProjectProfileManager">
|
||||||
|
<profile version="1.0">
|
||||||
|
<option name="myName" value="Project Default" />
|
||||||
|
<inspection_tool class="Eslint" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="Stylelint" enabled="true" level="ERROR" enabled_by_default="true" />
|
||||||
|
</profile>
|
||||||
|
</component>
|
||||||
Generated
+8
@@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="ProjectModuleManager">
|
||||||
|
<modules>
|
||||||
|
<module fileurl="file://$PROJECT_DIR$/.idea/websopy-taro.iml" filepath="$PROJECT_DIR$/.idea/websopy-taro.iml" />
|
||||||
|
</modules>
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
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
+12
@@ -0,0 +1,12 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<module type="WEB_MODULE" version="4">
|
||||||
|
<component name="NewModuleRootManager">
|
||||||
|
<content url="file://$MODULE_DIR$">
|
||||||
|
<excludeFolder url="file://$MODULE_DIR$/.tmp" />
|
||||||
|
<excludeFolder url="file://$MODULE_DIR$/temp" />
|
||||||
|
<excludeFolder url="file://$MODULE_DIR$/tmp" />
|
||||||
|
</content>
|
||||||
|
<orderEntry type="inheritedJdk" />
|
||||||
|
<orderEntry type="sourceFolder" forTests="false" />
|
||||||
|
</component>
|
||||||
|
</module>
|
||||||
Generated
+563
@@ -0,0 +1,563 @@
|
|||||||
|
<?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="refactor(cms): 迁移CMS模块接口到独立cms-api服务 - 新增cmsRequest工具,基于request封装,默认baseURL为cms-api - 改用cmsRequest替换所有cms相关请求,确保请求走独立域名 - 配置三环境新增CMS_API_BASE_URL及导出CmsBaseUrl常量 - 解决CMS接口走shop-api时返回404的问题,避免小程序域名白名单冲突 - 统一修改34个cms API模块的261处调用,保持接口行为一致 - 修复部分拼接全URL方式改为相对路径调用cmsRequest - 更新注释说明并调整相关接口调用代码 - 兼容macOS sed替换问题,采用perl命令完成批量替换" />
|
||||||
|
<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">{
|
||||||
|
"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.stylelint": "true",
|
||||||
|
"node.js.detected.package.tslint": "true",
|
||||||
|
"node.js.selected.package.eslint": "(autodetect)",
|
||||||
|
"node.js.selected.package.stylelint": "/Users/gxwebsoft/VUE/websopy-taro/node_modules/stylelint",
|
||||||
|
"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": "/Users/gxwebsoft/VUE/websopy-taro/node_modules/typescript/lib",
|
||||||
|
"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.dev: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.dev:weapp" />
|
||||||
|
<item itemvalue="npm.build: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="4614000" />
|
||||||
|
<workItem from="1784585070286" duration="3047000" />
|
||||||
|
<workItem from="1784638429525" duration="16147000" />
|
||||||
|
<workItem from="1784798575885" duration="4896000" />
|
||||||
|
<workItem from="1784873955217" duration="3822000" />
|
||||||
|
<workItem from="1786371937331" duration="10089000" />
|
||||||
|
</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>
|
||||||
|
<task id="LOCAL-00055" summary="feat(passport): 迁移并修复小程序支付页面访问 websopy API 问题 - 迁移 src/passport/pay 相关代码至当前项目,无需替换文件 - 新增环境变量 WEBSOPY_API_BASE_URL,提供 websopy API 基础地址 - 在 config/app.ts 导出 WebsopyBaseUrl,供代码引用 - 配置 env.d.ts 类型定义,消除 TypeScript 隐式 any 错误 - request 请求方法支持自定义 baseUrl 以调用不同接口地址 - passport/pay 页面调用订阅接口统一传入 WebsopyBaseUrl - 修正 tenantId 类型为 number,调整登录用户类型断言 - 确认编译成功,dist 目录生成对应文件及正确请求地址 - 说明 type-check 依赖库问题不影响正常构建和运行">
|
||||||
|
<option name="closed" value="true" />
|
||||||
|
<created>1784136882898</created>
|
||||||
|
<option name="number" value="00055" />
|
||||||
|
<option name="presentableId" value="LOCAL-00055" />
|
||||||
|
<option name="project" value="LOCAL" />
|
||||||
|
<updated>1784136882898</updated>
|
||||||
|
</task>
|
||||||
|
<task id="LOCAL-00056" summary="feat(passport): 优化支付成功后页面跳转逻辑 - 修改支付成功后的自动跳转目标,从已购产品页面改为我的页面 - 使用 switchTab 替代 navigateTo 保证在 tabBar 页顺利跳转 - 移除多余的跳转失败回退逻辑,简化跳转流程 - 调整跳转延迟时间为 1500 毫秒,提升用户体验">
|
||||||
|
<option name="closed" value="true" />
|
||||||
|
<created>1784586922535</created>
|
||||||
|
<option name="number" value="00056" />
|
||||||
|
<option name="presentableId" value="LOCAL-00056" />
|
||||||
|
<option name="project" value="LOCAL" />
|
||||||
|
<updated>1784586922535</updated>
|
||||||
|
</task>
|
||||||
|
<task id="LOCAL-00057" summary="feat(user): 改为动态查询 VIP 角色ID,避免写死角色ID - RoleParam 类型新增 tenantId 字段,支持按租户过滤角色 - 新增 getCurrentTenantId 获取当前租户ID,支持登录态和默认租户回退 - 实现 getVipRoleId 函数,按 roleCode 与 tenantId 查询 VIP 角色ID - assignVipRole 中动态获取 VIP 角色ID,避免硬编码 - 添加用户角色时带上 tenantId,确保多租户角色绑定正确 - 移除写死的 VIP_ROLE_ID,改用动态查询结果,提高跨租户兼容性 - 补充引入 listRoles 和 TenantId,完善相关依赖引用">
|
||||||
|
<option name="closed" value="true" />
|
||||||
|
<created>1784639229566</created>
|
||||||
|
<option name="number" value="00057" />
|
||||||
|
<option name="presentableId" value="LOCAL-00057" />
|
||||||
|
<option name="project" value="LOCAL" />
|
||||||
|
<updated>1784639229566</updated>
|
||||||
|
</task>
|
||||||
|
<task id="LOCAL-00058" summary="feat(user): 动态查询VIP角色ID,避免硬编码绑定问题 - RoleParam新增tenantId字段,支持按租户过滤角色 - 新增getCurrentTenantId函数,兼容登录态及默认租户回退 - 实现getVipRoleId函数,按roleCode与tenantId查询VIP角色ID - assignVipRole动态获取VIP角色ID,移除写死的角色ID - 添加用户角色时传入tenantId,确保多租户环境中角色绑定正确 - 修复后端UserRoleController.save接口bug,避免userId被无条件覆盖 - 用户角色绑定时正确使用申请人userId,解决VIP审核角色误绑定问题">
|
||||||
|
<option name="closed" value="true" />
|
||||||
|
<created>1784653464775</created>
|
||||||
|
<option name="number" value="00058" />
|
||||||
|
<option name="presentableId" value="LOCAL-00058" />
|
||||||
|
<option name="project" value="LOCAL" />
|
||||||
|
<updated>1784653464775</updated>
|
||||||
|
</task>
|
||||||
|
<task id="LOCAL-00059" summary="feat(user): 统一VIP概念为开发者并优化开发者登录及申请 - 将VIP相关文案、图标改为开发者,涉及用户中心、门店中心、审核页等 - 优化登录弹窗UI,改为红色系,提升视觉统一性和交互体验 - 关于我们页面品牌和公司信息内容更新,展示最新企业介绍和业务范围 - VIP升级页新增“所在城市”选择,使用微信原生地区选择器,完善地址信息 - 用户申请表单中新增城市字段及校验,提交时携带城市信息 - 优化审核页面文案,统称开发者身份,明确申请审核内容 - 修复UserRoleController.save接口bug,避免userId被无条件覆盖导致角色绑错 - 动态查询VIP角色ID,避免硬编码,支持多租户环境正确绑定角色">
|
||||||
|
<option name="closed" value="true" />
|
||||||
|
<created>1784697551142</created>
|
||||||
|
<option name="number" value="00059" />
|
||||||
|
<option name="presentableId" value="LOCAL-00059" />
|
||||||
|
<option name="project" value="LOCAL" />
|
||||||
|
<updated>1784697551142</updated>
|
||||||
|
</task>
|
||||||
|
<task id="LOCAL-00060" summary="refactor(user): 删除门店信息输入框 - 移除用户资料页面中的门店名称输入框 - 移除用户资料页面中的门店地址输入框 - 简化用户页面界面布局,提高界面整洁度">
|
||||||
|
<option name="closed" value="true" />
|
||||||
|
<created>1784697730666</created>
|
||||||
|
<option name="number" value="00060" />
|
||||||
|
<option name="presentableId" value="LOCAL-00060" />
|
||||||
|
<option name="project" value="LOCAL" />
|
||||||
|
<updated>1784697730666</updated>
|
||||||
|
</task>
|
||||||
|
<task id="LOCAL-00061" summary="refactor(user): 统一品牌名称为“威数谱软件” - 将项目中所有“威硕谱软件”名称修改为“威数谱软件” - 更新协议页面、欢迎消息、礼品卡等多处文本内容 - 修改小程序配置文件中的项目名称和显示标题 - 优化开发者申请页部分文案,明确开发者身份描述 - 保证所有界面及配置中的品牌名称统一一致">
|
||||||
|
<option name="closed" value="true" />
|
||||||
|
<created>1784699171525</created>
|
||||||
|
<option name="number" value="00061" />
|
||||||
|
<option name="presentableId" value="LOCAL-00061" />
|
||||||
|
<option name="project" value="LOCAL" />
|
||||||
|
<updated>1784699171525</updated>
|
||||||
|
</task>
|
||||||
|
<task id="LOCAL-00062" summary="refactor(user): 将VIP身份统一改为开发者标记 - 审核通过时不再新增角色,而是更新用户isDeveloper布尔标记 - 后端UserService新增markAsDeveloper方法,通过updateById设置isDeveloper - UserController新增PUT接口更新开发者标记,权限使用sys:userRole:save - 前端api新增setUserDeveloper调用新的审核接口 - vip-review页面移除角色相关代码,改为调用setUserDeveloper方法 - 约定开发者身份即sys_user.is_developer字段,不再依赖角色表记录 - app.config.ts追加开发者中心与平台管理相关页面路由 - 新增开发者中心、平台管理的多个API模块(apikey、appProduct、appDomain、developer请求等) - 添加公共工具方法和复用组件用于开发者中心及平台管理功能 - 统一品牌名称为“威数谱软件”,更新文案及界面展现一致性">
|
||||||
|
<option name="closed" value="true" />
|
||||||
|
<created>1784703425108</created>
|
||||||
|
<option name="number" value="00062" />
|
||||||
|
<option name="presentableId" value="LOCAL-00062" />
|
||||||
|
<option name="project" value="LOCAL" />
|
||||||
|
<updated>1784703425108</updated>
|
||||||
|
</task>
|
||||||
|
<task id="LOCAL-00063" summary="refactor(user): 将VIP身份统一改为开发者标记 - 审核通过时不再新增角色,而是更新用户isDeveloper布尔标记 - 后端UserService新增markAsDeveloper方法,通过updateById设置isDeveloper - UserController新增PUT接口更新开发者标记,权限使用sys:userRole:save - 前端api新增setUserDeveloper调用新的审核接口 - vip-review页面移除角色相关代码,改为调用setUserDeveloper方法 - 约定开发者身份即sys_user.is_developer字段,不再依赖角色表记录 - app.config.ts追加开发者中心与平台管理相关页面路由 - 新增开发者中心、平台管理的多个API模块(apikey、appProduct、appDomain、developer请求等) - 添加公共工具方法和复用组件用于开发者中心及平台管理功能 - 统一品牌名称为“威数谱软件”,更新文案及界面展现一致性">
|
||||||
|
<option name="closed" value="true" />
|
||||||
|
<created>1784704220175</created>
|
||||||
|
<option name="number" value="00063" />
|
||||||
|
<option name="presentableId" value="LOCAL-00063" />
|
||||||
|
<option name="project" value="LOCAL" />
|
||||||
|
<updated>1784704220175</updated>
|
||||||
|
</task>
|
||||||
|
<task id="LOCAL-00064" summary="feat(weapp): 新增开发者中心与平台管理入口及相关功能 - 小程序端新增开发者中心和平台管理页面模块,完善功能入口 - 新增开发者中心页面(概览、我的应用、API Key和工单) - 新增平台管理页面(管理首页、应用审核、Git权限审核、域名绑定审核和工单处理) - 复刻PC端/api/app/*相关API接口,基地址使用WEBSOPY_API_BASE_URL - 增加公共组件StatCard、EntryGrid、CellRow以支持新页面UI - 修改首页增加开发者中心和平台管理角色入口,仅对对应用户显示 - 用户页面菜单新增开发者中心和平台管理入口条件展示 - 约定开发者身份使用sys_user.is_developer布尔字段,不再依赖角色表 - 更新登录态业务,菜单是否显示基于登录接口返回的isDeveloper/isAdmin字段 - 确保相关页面已在app.config.ts注册并添加身份认证需求 - 调整运行配置为npm build:weapp,确保编译无误">
|
||||||
|
<option name="closed" value="true" />
|
||||||
|
<created>1784707342939</created>
|
||||||
|
<option name="number" value="00064" />
|
||||||
|
<option name="presentableId" value="LOCAL-00064" />
|
||||||
|
<option name="project" value="LOCAL" />
|
||||||
|
<updated>1784707342940</updated>
|
||||||
|
</task>
|
||||||
|
<task id="LOCAL-00065" summary="feat(weapp): 新增开发者中心与平台管理入口及功能 - 小程序端新增开发者中心和平台管理页面模块,完善功能入口 - 新增开发者中心页面(概览、我的应用、API Key和工单) - 新增平台管理页面(管理首页、应用审核、Git权限审核、域名绑定审核和工单处理) - 复刻PC端/api/app/*相关API接口,基地址使用WEBSOPY_API_BASE_URL - 增加公共组件StatCard、EntryGrid、CellRow以支持新页面UI - 修改首页增加开发者中心和平台管理角色入口,仅对对应用户显示 - 用户页面菜单新增开发者中心和平台管理入口条件展示 - 约定开发者身份使用sys_user.is_developer布尔字段,不再依赖角色表 - 更新登录态业务,菜单是否显示基于登录接口返回的isDeveloper/isAdmin字段 - vip-review审核通过流程调整为只调用setUserDeveloper,后台替换角色为developer并置isDeveloper=1">
|
||||||
|
<option name="closed" value="true" />
|
||||||
|
<created>1784709736683</created>
|
||||||
|
<option name="number" value="00065" />
|
||||||
|
<option name="presentableId" value="LOCAL-00065" />
|
||||||
|
<option name="project" value="LOCAL" />
|
||||||
|
<updated>1784709736683</updated>
|
||||||
|
</task>
|
||||||
|
<task id="LOCAL-00066" summary="fix(weapp): 修复支付页nutui组件导致内容空白问题 - 替换支付页中nutui的Button、Cell、Price、Divider组件为Taro原生组件或项目自定义组件 - 修正商品名称、价格类型和支付金额显示,使用flex布局及Text组件自绘内容 - 调整按钮样式,统一为带蓝色背景和圆角的自定义样式 - 移除nutui Price组件中的thousands属性,使用项目自定义Price组件 - 解决微信小程序中nutui组件因div渲染缺失导致UI元素不显示的问题 - 保持支付流程及状态逻辑无改动,仅优化展示兼容性和稳定性">
|
||||||
|
<option name="closed" value="true" />
|
||||||
|
<created>1784798603228</created>
|
||||||
|
<option name="number" value="00066" />
|
||||||
|
<option name="presentableId" value="LOCAL-00066" />
|
||||||
|
<option name="project" value="LOCAL" />
|
||||||
|
<updated>1784798603228</updated>
|
||||||
|
</task>
|
||||||
|
<task id="LOCAL-00067" summary="fix(weapp): 修复支付页nutui组件导致内容空白问题 - 替换支付页中nutui的Button、Cell、Price、Divider组件为Taro原生组件或项目自定义组件 - 修正商品名称、价格类型和支付金额显示,使用flex布局及Text组件自绘内容 - 调整按钮样式,统一为带蓝色背景和圆角的自定义样式 - 移除nutui Price组件中的thousands属性,使用项目自定义Price组件 - 解决微信小程序中nutui组件因div渲染缺失导致UI元素不显示的问题 - 保持支付流程及状态逻辑无改动,仅优化展示兼容性和稳定性">
|
||||||
|
<option name="closed" value="true" />
|
||||||
|
<created>1784799951929</created>
|
||||||
|
<option name="number" value="00067" />
|
||||||
|
<option name="presentableId" value="LOCAL-00067" />
|
||||||
|
<option name="project" value="LOCAL" />
|
||||||
|
<updated>1784799951929</updated>
|
||||||
|
</task>
|
||||||
|
<task id="LOCAL-00068" summary="fix(weapp): 修复支付页nutui组件导致内容空白问题 - 替换支付页中nutui的Button、Cell、Price、Divider组件为Taro原生组件或项目自定义组件 - 修正商品名称、价格类型和支付金额显示,使用flex布局及Text组件自绘内容 - 调整按钮样式,统一为带蓝色背景和圆角的自定义样式 - 移除nutui Price组件中的thousands属性,使用项目自定义Price组件 - 解决微信小程序中nutui组件因div渲染缺失导致UI元素不显示的问题 - 保持支付流程及状态逻辑无改动,仅优化展示兼容性和稳定性">
|
||||||
|
<option name="closed" value="true" />
|
||||||
|
<created>1784800111045</created>
|
||||||
|
<option name="number" value="00068" />
|
||||||
|
<option name="presentableId" value="LOCAL-00068" />
|
||||||
|
<option name="project" value="LOCAL" />
|
||||||
|
<updated>1784800111045</updated>
|
||||||
|
</task>
|
||||||
|
<task id="LOCAL-00069" summary="feat(user): 补充开发者审核通过时添加店员记录功能 - 引入 addShopStoreUser 接口用于新增门店店员记录 - 新增 clerkStoreId 和 clerkTenantId 状态,保存当前审核店员的门店和租户信息 - 在审核通过后调用 addShopStoreUser,添加申请人为当前门店店员 - 保证只有当 clerkStoreId 存在时才执行新增操作 - 更新工作记录文档,补充审核通过流程中新添加的店员记录步骤 - 保持原有升级开发者逻辑和接口调用不变">
|
||||||
|
<option name="closed" value="true" />
|
||||||
|
<created>1784800763746</created>
|
||||||
|
<option name="number" value="00069" />
|
||||||
|
<option name="presentableId" value="LOCAL-00069" />
|
||||||
|
<option name="project" value="LOCAL" />
|
||||||
|
<updated>1784800763746</updated>
|
||||||
|
</task>
|
||||||
|
<task id="LOCAL-00070" summary="feat(user): 补充开发者审核通过时添加店员记录功能 - 引入 addShopStoreUser 接口用于新增门店店员记录 - 新增 clerkStoreId 和 clerkTenantId 状态,保存当前审核店员的门店和租户信息 - 在审核通过后调用 addShopStoreUser,添加申请人为当前门店店员 - 保证只有当 clerkStoreId 存在时才执行新增操作 - 调整平台管理页 UI,使其与门店中心保持一致风格 - 门店中心及平台管理入口权限调整,仅超级管理员可访问 - 用户菜单中平台管理和门店中心入口现基于 isSuperAdmin 字段控制 - 优化登录及权限校验流程,避免权限不足时页面闪烁显示 - 更新工作记录文档,补充审核通过流程中新添加的店员记录步骤">
|
||||||
|
<option name="closed" value="true" />
|
||||||
|
<created>1784802336157</created>
|
||||||
|
<option name="number" value="00070" />
|
||||||
|
<option name="presentableId" value="LOCAL-00070" />
|
||||||
|
<option name="project" value="LOCAL" />
|
||||||
|
<updated>1784802336157</updated>
|
||||||
|
</task>
|
||||||
|
<task id="LOCAL-00071" summary="feat(user): 合并并切换“申请开发者”菜单项状态 - 在用户菜单中根据 isDeveloper 字段切换显示“申请成为开发者”或“进入开发者中心” - 已通过审核用户直接显示“进入开发者中心”且高亮显示 - 去除冗余的独立“开发者中心”菜单条目,简化菜单结构 - 调整平台管理页和门店中心入口权限为仅超级管理员可访问 - 优化登录与权限校验流程,防止权限不足时页面闪烁或无访问权限空白 - 更新相关文档,完善审核通过后店员记录添加流程">
|
||||||
|
<option name="closed" value="true" />
|
||||||
|
<created>1784802434477</created>
|
||||||
|
<option name="number" value="00071" />
|
||||||
|
<option name="presentableId" value="LOCAL-00071" />
|
||||||
|
<option name="project" value="LOCAL" />
|
||||||
|
<updated>1784802434477</updated>
|
||||||
|
</task>
|
||||||
|
<task id="LOCAL-00072" summary="fix(auth): 统一超级管理员标识为布尔值true - 将所有页面和菜单中超级管理员校验由数字1改为布尔true - 平台管理、门店中心菜单项仅在isSuperAdmin===true时显示 - 门店中心页面移除店员门槛,改为超级管理员访问限制 - 非超级管理员访问门店中心弹窗提示并返回 - 首页工作台入口及相关访问权限判定切换为布尔判断 - 优化首屏加载状态,避免权限校验闪烁和误判">
|
||||||
|
<option name="closed" value="true" />
|
||||||
|
<created>1784802589434</created>
|
||||||
|
<option name="number" value="00072" />
|
||||||
|
<option name="presentableId" value="LOCAL-00072" />
|
||||||
|
<option name="project" value="LOCAL" />
|
||||||
|
<updated>1784802589434</updated>
|
||||||
|
</task>
|
||||||
|
<task id="LOCAL-00073" summary="feat(user): 合并切换开发者菜单项状态并更新开发者中心UI - 用户菜单中根据 isDeveloper 字段条件展示“申请成为开发者”或“进入开发者中心” - 已审核通过用户菜单显示“进入开发者中心”,并设置高亮样式 - 去除冗余的独立“开发者中心”菜单条目,简化菜单结构 - 重新设计开发者中心页面UI,统一为门店中心风格 - 顶部展示开发者头像及昵称手机号信息 - 功能卡片展示“我的应用”、“API Key”和“我的工单”,异步加载数量角标 - 底部添加“开发者帮助与支持”入口,替代门店订阅消息卡片 - 未登录或非开发者状态显示相应空态提示及操作按钮 - 优化登录状态与权限校验逻辑,防止页面闪烁和无权限内容显示">
|
||||||
|
<option name="closed" value="true" />
|
||||||
|
<created>1784802954848</created>
|
||||||
|
<option name="number" value="00073" />
|
||||||
|
<option name="presentableId" value="LOCAL-00073" />
|
||||||
|
<option name="project" value="LOCAL" />
|
||||||
|
<updated>1784802954848</updated>
|
||||||
|
</task>
|
||||||
|
<task id="LOCAL-00074" summary="feat(user): 合并切换开发者菜单并优化首页轮播图显示 - 用户菜单根据 isDeveloper 字段切换显示申请或进入开发者中心 - 已审核用户菜单高亮显示“进入开发者中心” - 去除冗余开发者中心菜单条目,简化结构 - 重新设计开发者中心页面UI,统一门店中心风格 - 顶部展示开发者头像及昵称手机号信息 - 功能卡片展示“我的应用”、“API Key”和“我的工单”,异步加载数量角标 - 底部添加“开发者帮助与支持”入口,替代门店订阅消息卡片 - 未登录或非开发者状态显示相应空态及操作按钮 - 首页轮播图为空时不显示容器,功能入口自动上移,优化空状态体验 - 优化登录状态与权限校验逻辑,防止页面闪烁和无权限展示">
|
||||||
|
<option name="closed" value="true" />
|
||||||
|
<created>1784803190504</created>
|
||||||
|
<option name="number" value="00074" />
|
||||||
|
<option name="presentableId" value="LOCAL-00074" />
|
||||||
|
<option name="project" value="LOCAL" />
|
||||||
|
<updated>1784803190504</updated>
|
||||||
|
</task>
|
||||||
|
<task id="LOCAL-00075" summary="feat(user): 合并切换开发者菜单并优化首页轮播图显示 - 用户菜单根据 isDeveloper 字段切换显示申请或进入开发者中心 - 已审核用户菜单高亮显示“进入开发者中心” - 去除冗余开发者中心菜单条目,简化结构 - 重新设计开发者中心页面UI,统一门店中心风格 - 顶部展示开发者头像及昵称手机号信息 - 功能卡片展示“我的应用”、“API Key”和“我的工单”,异步加载数量角标 - 底部添加“开发者帮助与支持”入口,替代门店订阅消息卡片 - 未登录或非开发者状态显示相应空态及操作按钮 - 首页轮播图为空时不显示容器,功能入口自动上移,优化空状态体验 - 优化登录状态与权限校验逻辑,防止页面闪烁和无权限展示">
|
||||||
|
<option name="closed" value="true" />
|
||||||
|
<created>1784873966827</created>
|
||||||
|
<option name="number" value="00075" />
|
||||||
|
<option name="presentableId" value="LOCAL-00075" />
|
||||||
|
<option name="project" value="LOCAL" />
|
||||||
|
<updated>1784873966827</updated>
|
||||||
|
</task>
|
||||||
|
<task id="LOCAL-00076" summary="feat(checkout): 优化微信支付和线下付款流程 - 将默认支付方式调整为微信支付(payType=1),增加支付方式选择项 - 微信支付成功后直接调用微信支付接口并处理支付结果反馈 - 支付取消或失败时显示对应提示信息 - 线下付款(payType=9)保留并显示相应提示,不触发在线支付 - 提交订单时根据支付方式显示不同的按钮文本 (‘提交并支付’ 或 ‘提交订单’) - 针对微信支付配置缺失的错误,统一显示详细提示,便于定位问题 - 优化支付方式选中时的提示样式与说明,提升用户体验">
|
||||||
|
<option name="closed" value="true" />
|
||||||
|
<created>1786372231925</created>
|
||||||
|
<option name="number" value="00076" />
|
||||||
|
<option name="presentableId" value="LOCAL-00076" />
|
||||||
|
<option name="project" value="LOCAL" />
|
||||||
|
<updated>1786372231925</updated>
|
||||||
|
</task>
|
||||||
|
<task id="LOCAL-00077" summary="feat(checkout): 优化微信支付和线下付款流程 - 支付方式新增微信支付(payType=1),并设为默认,保留线下付款(payType=9) - 微信支付时调用 Taro.requestPayment 发起支付流程,取消或失败给出明确提示 - 支付选项选中状态下微信支付显示绿色提示,线下付款显示橙色提示 - 提交按钮文案区分微信支付(提交并支付)与线下付款(提交订单) - 后端支付配置缺失时提示“微信支付暂不可用:支付配置缺失” - 确保编译构建通过,未引入新类型错误">
|
||||||
|
<option name="closed" value="true" />
|
||||||
|
<created>1786407521622</created>
|
||||||
|
<option name="number" value="00077" />
|
||||||
|
<option name="presentableId" value="LOCAL-00077" />
|
||||||
|
<option name="project" value="LOCAL" />
|
||||||
|
<updated>1786407521622</updated>
|
||||||
|
</task>
|
||||||
|
<task id="LOCAL-00078" summary="feat(official): 实现企业官网分包及内容展示 - 新增官方分包目录及页面(首页、资讯列表、详情、关于我们、留言、我的) - 自定义实现底部导航组件 OfficialTabBar,解决微信 tabBar 不支持分包问题 - 统一接口调用走 shop-api 代理,无新增 CMS 域名,减少合法域名负担 - 新增 CMS 站点信息获取接口及留言咨询提交与查询接口 - 设计并完善官网富文本样式,品牌色统一为 #185FA5,并更新 tailwind 配置 - 首页新增企业官网入口卡片,方便用户访问官网内容 - 完成官网分包构建验证,解决 Taro 构建批量删除保护问题 - 规范 CMS 字段使用,适配字段名称及状态含义 - 后续计划对历史页面 NutUI 组件进行空壳化整改,及更新 UI 设计文档">
|
||||||
|
<option name="closed" value="true" />
|
||||||
|
<created>1786409297653</created>
|
||||||
|
<option name="number" value="00078" />
|
||||||
|
<option name="presentableId" value="LOCAL-00078" />
|
||||||
|
<option name="project" value="LOCAL" />
|
||||||
|
<updated>1786409297653</updated>
|
||||||
|
</task>
|
||||||
|
<task id="LOCAL-00079" summary="refactor(cms): 迁移CMS模块接口到独立cms-api服务 - 新增cmsRequest工具,基于request封装,默认baseURL为cms-api - 改用cmsRequest替换所有cms相关请求,确保请求走独立域名 - 配置三环境新增CMS_API_BASE_URL及导出CmsBaseUrl常量 - 解决CMS接口走shop-api时返回404的问题,避免小程序域名白名单冲突 - 统一修改34个cms API模块的261处调用,保持接口行为一致 - 修复部分拼接全URL方式改为相对路径调用cmsRequest - 更新注释说明并调整相关接口调用代码 - 兼容macOS sed替换问题,采用perl命令完成批量替换">
|
||||||
|
<option name="closed" value="true" />
|
||||||
|
<created>1786417481138</created>
|
||||||
|
<option name="number" value="00079" />
|
||||||
|
<option name="presentableId" value="LOCAL-00079" />
|
||||||
|
<option name="project" value="LOCAL" />
|
||||||
|
<updated>1786417481138</updated>
|
||||||
|
</task>
|
||||||
|
<option name="localTasksCounter" value="80" />
|
||||||
|
<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="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 编译报错 - 新增地址类型定义,增强前端地址数据结构类型安全 - 优化页面交互体验,完善表单校验及错误提示逻辑 - 统一代码格式与命名规范,保持代码风格一致性" />
|
||||||
|
<MESSAGE value="feat(passport): 迁移并修复小程序支付页面访问 websopy API 问题 - 迁移 src/passport/pay 相关代码至当前项目,无需替换文件 - 新增环境变量 WEBSOPY_API_BASE_URL,提供 websopy API 基础地址 - 在 config/app.ts 导出 WebsopyBaseUrl,供代码引用 - 配置 env.d.ts 类型定义,消除 TypeScript 隐式 any 错误 - request 请求方法支持自定义 baseUrl 以调用不同接口地址 - passport/pay 页面调用订阅接口统一传入 WebsopyBaseUrl - 修正 tenantId 类型为 number,调整登录用户类型断言 - 确认编译成功,dist 目录生成对应文件及正确请求地址 - 说明 type-check 依赖库问题不影响正常构建和运行" />
|
||||||
|
<MESSAGE value="feat(passport): 优化支付成功后页面跳转逻辑 - 修改支付成功后的自动跳转目标,从已购产品页面改为我的页面 - 使用 switchTab 替代 navigateTo 保证在 tabBar 页顺利跳转 - 移除多余的跳转失败回退逻辑,简化跳转流程 - 调整跳转延迟时间为 1500 毫秒,提升用户体验" />
|
||||||
|
<MESSAGE value="feat(user): 改为动态查询 VIP 角色ID,避免写死角色ID - RoleParam 类型新增 tenantId 字段,支持按租户过滤角色 - 新增 getCurrentTenantId 获取当前租户ID,支持登录态和默认租户回退 - 实现 getVipRoleId 函数,按 roleCode 与 tenantId 查询 VIP 角色ID - assignVipRole 中动态获取 VIP 角色ID,避免硬编码 - 添加用户角色时带上 tenantId,确保多租户角色绑定正确 - 移除写" />
|
||||||
|
<MESSAGE value="feat(user): 动态查询VIP角色ID,避免硬编码绑定问题 - RoleParam新增tenantId字段,支持按租户过滤角色 - 新增getCurrentTenantId函数,兼容登录态及默认租户回退 - 实现getVipRoleId函数,按roleCode与tenantId查询VIP角色ID - assignVipRole动态获取VIP角色ID,移除写死的角色ID - 添加用户角色时传" />
|
||||||
|
<MESSAGE value="feat(user): 统一VIP概念为开发者并优化开发者登录及申请 - 将VIP相关文案、图标改为开发者,涉及用户中心、门店中心、审核页等 - 优化登录弹窗UI,改为红色系,提升视觉统一性和交互体验 - 关于我们页面品牌和公司信息内容更新,展示最新企业介绍和业务范围 - VIP升级页新增“所在城市”选择,使用微信原生地区选择器,完善地址信息 - 用户申请表单中新增城市字段及校验,提交时携带城市信息 - 优化审核页面文案,统称开发者身份,明确申请审核内容 - 修复UserRoleController.save接口bug,避免userId被无条件覆盖导致角色绑错 - 动态查询VIP角色ID,避免硬编码,支持多租户环境正确绑定角色" />
|
||||||
|
<MESSAGE value="refactor(user): 删除门店信息输入框 - 移除用户资料页面中的门店名称输入框 - 移除用户资料页面中的门店地址输入框 - 简化用户页面界面布局,提高界面整洁度" />
|
||||||
|
<MESSAGE value="refactor(user): 统一品牌名称为“威数谱软件” - 将项目中所有“威硕谱软件”名称修改为“威数谱软件” - 更新协议页面、欢迎消息、礼品卡等多处文本内容 - 修改小程序配置文件中的项目名称和显示标题 - 优化开发者申请页部分文案,明确开发者身份描述 - 保证所有界面及配置中的品牌名称统一一致" />
|
||||||
|
<MESSAGE value="refactor(user): 将VIP身份统一改为开发者标记 - 审核通过时不再新增角色,而是更新用户isDeveloper布尔标记 - 后端UserService新增markAsDeveloper方法,通过updateById设置isDeveloper - UserController新增PUT接口更新开发者标记,权限使用sys:userRole:save - 前端api新增setUserDeveloper调用新的审核接口 - vip-review页面移除角色相关代码,改为调用setUserDeveloper方法 - 约定开发者身份即sys_user.is_developer字段,不再依赖角色表记录 - app.config.ts追加开发者中心与平台管理相关页面路由 - 新增开发者中心、平台管理的多个API模块(apikey、appProduct、appDomain、developer请求等) - 添加公共工具方法和复用组件用于开发者中心及平台管理功能 - 统一品牌名称为“威数谱软件”,更新文案及界面展现一致性" />
|
||||||
|
<MESSAGE value="feat(weapp): 新增开发者中心与平台管理入口及相关功能 - 小程序端新增开发者中心和平台管理页面模块,完善功能入口 - 新增开发者中心页面(概览、我的应用、API Key和工单) - 新增平台管理页面(管理首页、应用审核、Git权限审核、域名绑定审核和工单处理) - 复刻PC端/api/app/*相关API接口,基地址使用WEBSOPY_API_BASE_URL - 增加公共组件StatCard、EntryGrid、CellRow以支持新页面UI - 修改首页增加开发者中心和平台管理角色入口,仅对对应用户显示 - 用户页面菜单新增开发者中心和平台管理入口条件展示 - 约定开发者身份使用sys_user.is_developer布尔字段,不再依赖角色表 - 更新登录态业务,菜单是否显示基于登录接口返回" />
|
||||||
|
<MESSAGE value="feat(weapp): 新增开发者中心与平台管理入口及功能 - 小程序端新增开发者中心和平台管理页面模块,完善功能入口 - 新增开发者中心页面(概览、我的应用、API Key和工单) - 新增平台管理页面(管理首页、应用审核、Git权限审核、域名绑定审核和工单处理) - 复刻PC端/api/app/*相关API接口,基地址使用WEBSOPY_API_BASE_URL - 增加公共组件StatCard、EntryGrid、CellRow以支持新页面UI - 修改首页增加开发者中心和平台管理角色入口,仅对对应用户显示 - 用户页面菜单新增开发者中心和平台管理入口条件展示 - 约定开发者身份使用sys_user.is_developer布尔字段,不再依赖角色表 - 更新登录态业务,菜单是否显示基于登录接口返回的isDeveloper/isAdmin字段 - vip-review审核通过流程调整为只调用setUserDeveloper,后台替换角色为developer并置isDeveloper=1" />
|
||||||
|
<MESSAGE value="fix(weapp): 修复支付页nutui组件导致内容空白问题 - 替换支付页中nutui的Button、Cell、Price、Divider组件为Taro原生组件或项目自定义组件 - 修正商品名称、价格类型和支付金额显示,使用flex布局及Text组件自绘内容 - 调整按钮样式,统一为带蓝色背景和圆角的自定义样式 - 移除nutui Price组件中的thousands属性,使用项目自定义Price组件 - 解决微信小程序中nutui组件因div渲染缺失导致UI元素不显示的问题 - 保持支付流程及状态逻辑无改动,仅优化展示兼容性和稳定性" />
|
||||||
|
<MESSAGE value="feat(user): 补充开发者审核通过时添加店员记录功能 - 引入 addShopStoreUser 接口用于新增门店店员记录 - 新增 clerkStoreId 和 clerkTenantId 状态,保存当前审核店员的门店和租户信息 - 在审核通过后调用 addShopStoreUser,添加申请人为当前门店店员 - 保证只有当 clerkStoreId 存在时才执行新增操作 - 更新工作记录文档,补充审核通过流程中新添加的店员记录步骤 - 保持原有升级开发者逻辑和接口调用不变" />
|
||||||
|
<MESSAGE value="feat(user): 补充开发者审核通过时添加店员记录功能 - 引入 addShopStoreUser 接口用于新增门店店员记录 - 新增 clerkStoreId 和 clerkTenantId 状态,保存当前审核店员的门店和租户信息 - 在审核通过后调用 addShopStoreUser,添加申请人为当前门店店员 - 保证只有当 clerkStoreId 存在时才执行新增操作 - 调整平台管理页 UI,使其与门店中心保持一致风格 - 门店中心及平台管理入口权限调整,仅超级管理员可访问 - 用户菜单中平台管理和门店中心入口现基于 isSuperAdmin 字段控制 - 优化登录及权限校验流程,避免权限不足时页面闪烁显示 - 更新工作记录文档,补充审核通过流程中新添加的店员记录步骤" />
|
||||||
|
<MESSAGE value="feat(user): 合并并切换“申请开发者”菜单项状态 - 在用户菜单中根据 isDeveloper 字段切换显示“申请成为开发者”或“进入开发者中心” - 已通过审核用户直接显示“进入开发者中心”且高亮显示 - 去除冗余的独立“开发者中心”菜单条目,简化菜单结构 - 调整平台管理页和门店中心入口权限为仅超级管理员可访问 - 优化登录与权限校验流程,防止权限不足时页面闪烁或无访问权限空白 - 更新相关文档,完善审核通过后店员记录添加流程" />
|
||||||
|
<MESSAGE value="fix(auth): 统一超级管理员标识为布尔值true - 将所有页面和菜单中超级管理员校验由数字1改为布尔true - 平台管理、门店中心菜单项仅在isSuperAdmin===true时显示 - 门店中心页面移除店员门槛,改为超级管理员访问限制 - 非超级管理员访问门店中心弹窗提示并返回 - 首页工作台入口及相关访问权限判定切换为布尔判断 - 优化首屏加载状态,避免权限校验闪烁和误判" />
|
||||||
|
<MESSAGE value="feat(user): 合并切换开发者菜单项状态并更新开发者中心UI - 用户菜单中根据 isDeveloper 字段条件展示“申请成为开发者”或“进入开发者中心” - 已审核通过用户菜单显示“进入开发者中心”,并设置高亮样式 - 去除冗余的独立“开发者中心”菜单条目,简化菜单结构 - 重新设计开发者中心页面UI,统一为门店中心风格 - 顶部展示开发者头像及昵称手机号信息 - 功能卡片展示“我的应用”、“API Key”和“我的工单”,异步加载数量角标 - 底部添加“开发者帮助与支持”入口,替代门店订阅消息卡片 - 未登录或非开发者状态显示相应空态提示及操作按钮 - 优化登录状态与权限校验逻辑,防止页面闪烁和无权限内容显示" />
|
||||||
|
<MESSAGE value="feat(user): 合并切换开发者菜单并优化首页轮播图显示 - 用户菜单根据 isDeveloper 字段切换显示申请或进入开发者中心 - 已审核用户菜单高亮显示“进入开发者中心” - 去除冗余开发者中心菜单条目,简化结构 - 重新设计开发者中心页面UI,统一门店中心风格 - 顶部展示开发者头像及昵称手机号信息 - 功能卡片展示“我的应用”、“API Key”和“我的工单”,异步加载数量角标 - 底部添加“开发者帮助与支持”入口,替代门店订阅消息卡片 - 未登录或非开发者状态显示相应空态及操作按钮 - 首页轮播图为空时不显示容器,功能入口自动上移,优化空状态体验 - 优化登录状态与权限校验逻辑,防止页面闪烁和无权限展示" />
|
||||||
|
<MESSAGE value="feat(checkout): 优化微信支付和线下付款流程 - 将默认支付方式调整为微信支付(payType=1),增加支付方式选择项 - 微信支付成功后直接调用微信支付接口并处理支付结果反馈 - 支付取消或失败时显示对应提示信息 - 线下付款(payType=9)保留并显示相应提示,不触发在线支付 - 提交订单时根据支付方式显示不同的按钮文本 (‘提交并支付’ 或 ‘提交订单’) - 针对微信支付配置缺失的错误,统一显示详细提示,便于定位问题 - 优化支付方式选中时的提示样式与说明,提升用户体验" />
|
||||||
|
<MESSAGE value="feat(checkout): 优化微信支付和线下付款流程 - 支付方式新增微信支付(payType=1),并设为默认,保留线下付款(payType=9) - 微信支付时调用 Taro.requestPayment 发起支付流程,取消或失败给出明确提示 - 支付选项选中状态下微信支付显示绿色提示,线下付款显示橙色提示 - 提交按钮文案区分微信支付(提交并支付)与线下付款(提交订单) - 后端支付配置缺失时提示“微信支付暂不可用:支付配置缺失” - 确保编译构建通过,未引入新类型错误" />
|
||||||
|
<MESSAGE value="feat(official): 实现企业官网分包及内容展示 - 新增官方分包目录及页面(首页、资讯列表、详情、关于我们、留言、我的) - 自定义实现底部导航组件 OfficialTabBar,解决微信 tabBar 不支持分包问题 - 统一接口调用走 shop-api 代理,无新增 CMS 域名,减少合法域名负担 - 新增 CMS 站点信息获取接口及留言咨询提交与查询接口 - 设计并完善官网富文本样式,品牌色统一为 #185FA5,并更新 tailwind 配置 - 首页新增企业官网入口卡片,方便用户访问官网内容 - 完成官网分包构建验证,解决 Taro 构建批量删除保护问题 - 规范 CMS 字段使用,适配字段名称及状态含义 - 后续计划对历史页面 NutUI 组件进行空壳化整改,及更新 UI 设计文档" />
|
||||||
|
<MESSAGE value="refactor(cms): 迁移CMS模块接口到独立cms-api服务 - 新增cmsRequest工具,基于request封装,默认baseURL为cms-api - 改用cmsRequest替换所有cms相关请求,确保请求走独立域名 - 配置三环境新增CMS_API_BASE_URL及导出CmsBaseUrl常量 - 解决CMS接口走shop-api时返回404的问题,避免小程序域名白名单冲突 - 统一修改34个cms API模块的261处调用,保持接口行为一致 - 修复部分拼接全URL方式改为相对路径调用cmsRequest - 更新注释说明并调整相关接口调用代码 - 兼容macOS sed替换问题,采用perl命令完成批量替换" />
|
||||||
|
<option name="LAST_COMMIT_MESSAGE" value="refactor(cms): 迁移CMS模块接口到独立cms-api服务 - 新增cmsRequest工具,基于request封装,默认baseURL为cms-api - 改用cmsRequest替换所有cms相关请求,确保请求走独立域名 - 配置三环境新增CMS_API_BASE_URL及导出CmsBaseUrl常量 - 解决CMS接口走shop-api时返回404的问题,避免小程序域名白名单冲突 - 统一修改34个cms API模块的261处调用,保持接口行为一致 - 修复部分拼接全URL方式改为相对路径调用cmsRequest - 更新注释说明并调整相关接口调用代码 - 兼容macOS sed替换问题,采用perl命令完成批量替换" />
|
||||||
|
<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` 类型包解析问题失败,这是项目既有环境问题,不影响构建。
|
||||||
|
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
# 2026-07-21
|
||||||
|
|
||||||
|
## VIP 角色绑定改为动态查询(去硬编码)
|
||||||
|
- 问题:`src/pages/user/vip-review/index.tsx` 中 `VIP_ROLE_ID = 2032` 写死,跨租户不适用。
|
||||||
|
- 改动:
|
||||||
|
- `src/api/system/role/model/index.ts`:给 `RoleParam` 增加 `tenantId?: number | string` 字段,支持按租户过滤角色。
|
||||||
|
- `vip-review/index.tsx`:新增 `getCurrentTenantId()`(取自 `Taro.getStorageSync('TenantId') || TenantId`)与 `getVipRoleId()`(调用 `listRoles({ roleCode:'vip', tenantId })` 动态获取 roleId);`assignVipRole` 改用动态 roleId,并在 `addUserRole` 时带上 `tenantId`。
|
||||||
|
- 导入补充:`listRoles` (from `@/api/system/role`)、`TenantId` (from `@/config/app`)。
|
||||||
|
- 约定:VIP 角色以 `roleCode='vip'` 标识,不再依赖固定 roleId。
|
||||||
|
|
||||||
|
## 后端确认(com.gxwebsoft.core)
|
||||||
|
- `GET /api/system/role`:支持 `roleCode` 过滤(`RoleParam.roleCode` 带 `@QueryField(EQ)`)。✅
|
||||||
|
- 租户隔离由 MyBatis-Plus `TenantLineInnerInterceptor` 自动完成(`sys_role` 不在忽略表清单),租户取自请求头 `tenantId`。`PageParam.buildWrapper` 还会**显式跳过** `tenantId` 字段不拼 WHERE,所以前端的 `tenantId` 查询参数不参与过滤——真正隔离靠请求头。`RoleParam.tenantId` 无 `@QueryField`,传了也被忽略,但功能正确(按当前租户返回 VIP 角色)。
|
||||||
|
- ⚠️ 关键 bug:`POST /api/system/user-role`(save) 用 `getLoginUser().getUserId()` **无条件覆盖**请求体里的 `userId`。VIP 审核通过时,角色会被绑到当前登录的店员,而非申请人 `item.userId`。原硬编码版本也存在此问题。需与用户确认是否修复(建议:仅当请求体 userId 为空时才回退到登录用户)。
|
||||||
|
- 路径核对:前端 `SERVER_API_URL='.../api'`,`addUserRole` 命中 `/api/system/user-role`,一致。
|
||||||
@@ -0,0 +1,74 @@
|
|||||||
|
# 2026-07-22 工作日志
|
||||||
|
|
||||||
|
## 开发者登录弹窗 UI 美化
|
||||||
|
- 文件:`src/pages/user/about/index.tsx`
|
||||||
|
- 美化了 Input(圆角12px、高度48px、柔和背景色)、Button(蓝色渐变、阴影、圆角14px)、验证码按钮(与输入框等高、倒计时优化)
|
||||||
|
- 弹窗整体:圆角加大、内边距增加、宽度收窄
|
||||||
|
|
||||||
|
## 开发者登录弹窗样式改为红色系
|
||||||
|
- 文件:`src/pages/user/about/index.tsx`
|
||||||
|
- Input 边框:`transparent` → `#e53e3e`(红色),背景 `#f7f8fa` → `#fff5f5`(淡红)
|
||||||
|
- 获取验证码按钮:激活态添加 `backgroundColor: #e53e3e`、`borderColor: #e53e3e`
|
||||||
|
- 登录按钮:渐变从蓝色 `#4f8cff→#3b6fd4` 改为红色 `#fc5c5c→#e53e3e`,阴影同步改为红色
|
||||||
|
|
||||||
|
## 关于我们页面介绍内容改写
|
||||||
|
- 文件:`src/pages/user/about/index.tsx`
|
||||||
|
- 公司:南宁市网宿信息科技有限公司,品牌:威数谱软件(websopy)
|
||||||
|
- 重写品牌故事(三个段落)、核心业务(6项软件相关)、联系我们(公司名称/总部/电话/邮箱/官网)、版权信息
|
||||||
|
- 头部 Logo 文字「鑫」→「威」,版本号显示 websopy v1.0.0
|
||||||
|
|
||||||
|
## VIP 概念替换为「开发者」(文案统一)
|
||||||
|
- 起因:用户要求把 `pages/user/vip-upgrade/index` 的 VIP 概念换成「开发者」。后台审核页 `vip-review` 已定义 `VIP_ROLE_NAME='开发者'`,概念本就一致。
|
||||||
|
- 改动文件与要点:
|
||||||
|
- `vip-upgrade/index.tsx` + `index.config.ts`:导航栏/分享标题/主副标题/状态说明/温馨提示/权益区块标题及权益项描述,全部「VIP会员」→「开发者」;顶部图标 👑→🔰;表单标签「门店名称/门店地址」→「开发团队/公司地址」(placeholder、toast 同步),底层字段 realName/address 不变。
|
||||||
|
- `user.tsx`:用户中心入口「升级VIP会员」👑→「申请开发者」🔰。
|
||||||
|
- `store/center/index.tsx`:门店中心入口「VIP会员审核/审核客户VIP会员申请」👑→「开发者审核/审核客户开发者申请」🔰。
|
||||||
|
- `vip-review/index.tsx`:导航栏标题、通过/驳回弹窗文案、审核说明、列表字段标签(门店名称/门店地址→开发团队/公司地址)统一为「开发者」。
|
||||||
|
- 注意:仅改可见文案与图标,**未重命名**页面路由/文件夹(vip-upgrade、vip-review)、变量名(VIP_ROLE_CODE、pendingVipCount 等)及定价逻辑(dealerPrice/VIP 状态判断仍保留原标识符,因对应后端角色与缓存 key)。
|
||||||
|
- 用户确认方式:先给文案对照表,经 AskUserQuestion 确认表单标签与图标及范围后执行。
|
||||||
|
|
||||||
|
## VIP 升级页新增「所在城市」选择
|
||||||
|
- 文件:`src/pages/user/vip-upgrade/index.tsx`、`src/api/shop/shopDealerApply/model/index.ts`
|
||||||
|
- 在「办公地址」上方新增「所在城市 *」字段:复用微信原生 `Picker mode='region'`(省市区三级),与 `pages/user/address-edit.tsx` 风格一致。
|
||||||
|
- 状态:`cityValue: string[]` + 派生 `cityText`(以空格连接);`Picker` 的 `disabled` 跟随 `isReadonly`(审核中/已通过只读)。
|
||||||
|
- `ShopDealerApply` 模型新增 `city?: string`(注释:省 市 区,空格分隔)。
|
||||||
|
- 提交时 `addShopDealerApply` 增加 `city: cityText`;读取历史申请时 `setCityValue(latest.city.split(' '))`;保存校验增加「请选择所在城市」。
|
||||||
|
- 注意:仅前端扩展字段,后端是否落库 `city` 存疑(模型此前只有 address),需后端 schema 支持才能真正持久化。
|
||||||
|
|
||||||
|
## vip-review 审核通过:改为「更新 user 为 developer」(不新增角色)
|
||||||
|
- 起因:用户要求 `pages/user/vip-review/index` 审核通过时直接把 user 标记为开发者,而不是往 `sys_user_role` 加 developer 角色。
|
||||||
|
- 后端(仓库 `/Users/gxwebsoft/JAVA/com.gxwebsoft.core`,已 `mvn -o compile` BUILD SUCCESS):
|
||||||
|
- `User.isDeveloper`(`sys_user.is_developer`,Boolean)此前从未被写入,正是开发者标记位。
|
||||||
|
- `UserService` 新增 `boolean markAsDeveloper(Integer userId)`;`UserServiceImpl` 用最小实体 `updateById` 置 `isDeveloper=true`,成功后再经 `syncMessageProducer` 同步 websopy(复用 updateUser 的同步范式)。
|
||||||
|
- `UserController` 新增 `PUT /api/system/user/developer/{userId}`(`setDeveloper`),权限沿用 `sys:userRole:save`(与原 `addUserRole` 一致,避免店员 403 回归)。
|
||||||
|
- 前端:
|
||||||
|
- `@/api/system/user/index.ts` 新增 `setUserDeveloper(userId)` → `PUT /system/user/developer/{userId}`。
|
||||||
|
- `vip-review/index.tsx`:移除 `addUserRole/listUserRole/listRoles/TenantId` 等角色相关 import 与 `VIP_ROLE_*`、`getCurrentTenantId`、`getVipRoleId`、`assignVipRole`、`formatDateTime`;`handleApprove` 中 `await assignVipRole(item.userId)` 替换为 `await setUserDeveloper(item.userId!)`。
|
||||||
|
- 约定:**开发者身份 = `sys_user.is_developer` 布尔位,不是角色表记录**(已记项目 MEMORY.md)。
|
||||||
|
|
||||||
|
## vip-review 审核通过:补回「开发者角色绑定」+ 保留 `isDeveloper=1`
|
||||||
|
- 起因:上一轮把 `addUserRole` 整段删了(按"不用新增角色")。本轮用户要求"除了更新角色之外,还要更新 isDeveloper=1",即两者都要。
|
||||||
|
- 改动:仅前端 `src/pages/user/vip-review/index.tsx`:
|
||||||
|
- 恢复 import:`addUserRole, listUserRole`(@/api/system/userRole)、`listRoles`(@/api/system/role)、`TenantId`(@/config/app)。
|
||||||
|
- 恢复常量与函数:`VIP_ROLE_CODE='developer'`、`VIP_ROLE_NAME='开发者'`、`getCurrentTenantId()`、`getVipRoleId()`(按 roleCode+租户查 roleId)、`assignVipRole(userId)`(查已有角色、无则 `addUserRole`)。
|
||||||
|
- `handleApprove` 中 `await setUserDeveloper(item.userId!)` 之前新增 `await assignVipRole(item.userId)`:先绑 developer 角色,再置 `is_developer=1`。
|
||||||
|
- 后端 `markAsDeveloper`(`PUT /system/user/developer/{userId}`)上一轮已落地并编译通过,本次未改;其 `updateById` 置 `isDeveloper=true` 即"更新数据库"的落库动作。
|
||||||
|
- 结论:审核通过 = 绑定 developer 角色 + 置 isDeveloper=1(两项都做)。已更新项目 MEMORY.md 的"开发者身份表示方式"条目。
|
||||||
|
|
||||||
|
## vip-review 审核通过:更正为「替换角色为 developer」+ `is_developer=1`(最终方案)
|
||||||
|
- 用户纠正上一轮:不是"新增/叠加角色"(那样会留下原角色 + developer 两个角色),而是**把用户角色改成 developer(替换,不并存)**,同时 `sys_user.is_developer=1`。
|
||||||
|
- 前端 `vip-review/index.tsx`:撤掉上一轮刚加回的 `assignVipRole` 及 `addUserRole/listUserRole/listRoles/TenantId/VIP_ROLE_*`/`getCurrentTenantId`/`getVipRoleId`;`handleApprove` 恢复为只 `await setUserDeveloper(item.userId!)`。前端完全不碰 `sys_user_role`。
|
||||||
|
- 后端 `UserServiceImpl.markAsDeveloper` 本次新增"替换角色"逻辑:置 `is_developer=1` 后,按 `user.getTenantId()` 调 `roleService.getByRoleCode(roleCode="developer", tenantId)` 找到 developer 角色 → `userRoleService.remove(eq userId)` 删现有角色 → `saveBatch(userId, [developerRoleId])` 只绑 developer(单角色)。整段包 try/catch,角色缺失/失败不影响 is_developer 落库。新增 import:`RoleParam`、`java.util.Collections`。
|
||||||
|
- 编译:`./mvnw -o compile` → BUILD SUCCESS。前端无残留角色符号。
|
||||||
|
- 结论(已同步 MEMORY.md):升级为开发者 = 单角色 developer + isDeveloper 标记位;前端一次 `setUserDeveloper` 调用、后端一处搞定。
|
||||||
|
|
||||||
|
## 小程序端新增开发者中心 & 平台管理(入口改造)
|
||||||
|
- 起因:用户要求小程序端对齐 PC 端 `/developer` 与 `/admin` 功能。方案确认:入口挂「我的」页,分阶段实现 MVP 核心。
|
||||||
|
- 页面(新增于 `src/pages/developer/*` 与 `src/pages/admin/*`,已注册到 `app.config.ts`):
|
||||||
|
- 开发者中心:`index`(概览) / `apps`(我的应用) / `apikeys`(API Key) / `tickets`(我的工单)
|
||||||
|
- 平台管理:`index`(管理首页) / `app-review`(应用审核) / `git-review`(Git/权限审核) / `domain-review`(域名绑定审核) / `tickets`(工单处理)
|
||||||
|
- API 模块(新增 `src/api/app/`:apikey、appProduct、appDomain、ticket、developer),复刻 PC 端 `/api/app/*` 契约,基址走 `WEBSOPY_API_BASE_URL`。
|
||||||
|
- 公共组件:新增 `StatCard` / `EntryGrid` / `CellRow`(`src/components/common/`)。
|
||||||
|
- 入口方式(**本次按用户最新要求改**):不做工作台卡片,改为「我的」页功能菜单里的链接项,与「门店中心」同款——`开发者中心` 仅当 `(user as any)?.isDeveloper` 时显示、`平台管理` 仅当 `(user as any)?.isAdmin` 时显示,均 `requireAuth`。首页不额外加卡。
|
||||||
|
- 关键依赖:菜单显示与否取决于登录接口返回的 user 对象是否携带 `isDeveloper/isAdmin` 字段(后端 `setDeveloper` 写的是 `sys_user.is_developer`,但登录态是否回填该字段需联调确认)。
|
||||||
|
- 编译验证:已 `npm run build:weapp`(后台 IzjQLY 进行中),待确认无报错。
|
||||||
@@ -0,0 +1,86 @@
|
|||||||
|
# 2026-07-23
|
||||||
|
|
||||||
|
## 扫码进入小程序后支付页空白 bug
|
||||||
|
|
||||||
|
**现象**:用户从 web 端订单详情生成 QR 码 → 微信扫码进入小程序 → `passport/pay` 页面只显示卡片标题"确认支付"、"支付金额"标签、底部提示文案,**商品名称/价格类型/价格/分隔线/取消按钮/立即支付按钮全部缺失**,无法继续支付流程。
|
||||||
|
|
||||||
|
**根因**:`@nutui/nutui-react-taro@2.7.4` 的 Cell / Price / Divider / Button 内部用 `React.createElement("div", ...)`,而 `dist/base.wxml` 里只有 `<view>` / `<text>` / `<button>` 模板没有 `<div>`。这些组件在 weapp 中"空壳化"。截图里只显示纯 Text/View 和图标(来自 `@nutui/icons-react-taro` 的 SVG),没有显示的内容全部是 nutui-react-taro 组件。
|
||||||
|
|
||||||
|
**修复**:只改 `src/passport/pay/index.tsx` 一个文件,把 4 个 nutui 组件换成 Taro 原生 / 项目自定义组件:
|
||||||
|
|
||||||
|
- `import { Button, Cell, Price, Divider } from '@nutui/nutui-react-taro'` → `import { Button } from '@tarojs/components'` + `import Price from '@/components/common/Price'`
|
||||||
|
- `<Cell>` → `<View>` + `<Text>` 自绘(用 `flex justify-between px-4 py-3 border-b border-gray-100`)
|
||||||
|
- `<Price size="large" thousands>` → `<Price size="large">`(项目自定义组件)
|
||||||
|
- `<Divider>` → `border-b border-gray-100` 替代
|
||||||
|
- `<Button type="primary" block>` → `<Button className="bg-blue-500 text-white rounded-full">`(className 控制颜色)
|
||||||
|
- error/paid/loading 三个分支里的 `<Button type="primary">` 也一并替换
|
||||||
|
|
||||||
|
**验证**:
|
||||||
|
- `npm run build:weapp` → `Compiled successfully in 14.45s`
|
||||||
|
- `dist/passport/pay/index.js` 里 `nut-cell|nut-button|nut-divider|nut-price` 全部为 0;按钮编译成 `l.zx`(Taro Button 局部变量名)
|
||||||
|
- 文件最终 413 行,diff 集中在 import + 渲染区,逻辑(数据获取、JSAPI 支付、状态判断 payStatus vs status)没动
|
||||||
|
|
||||||
|
**遗留**:项目其它页面(passport/{login,forget,register,setting,unified-qr,qr-login}、pages/order/*、pages/booking/*、components/common/{FixedButton,EmptyState}、components/QRLoginScanner)也用了 nutui Button/Checkbox/Input/Card/Tabs/Tag 等。如果以后用户反馈"按钮/输入框看不到或不可点",按同样模式替换即可。这次没改,避免一次改动面太大。
|
||||||
|
|
||||||
|
## vip-review 审核通过补充:添加 shop_store_user 记录
|
||||||
|
|
||||||
|
**背景**:之前实现的 `src/pages/user/vip-review/index.tsx` 审核通过只调了 `updateShopDealerApply` + `setUserDeveloper`,漏了"给申请人加一条门店店员记录"。用户 2026-07-23 指出"审核通过的操作,还是要添加一条记录到 shopStoreUser,可能之前理解错了",要求补上。
|
||||||
|
|
||||||
|
**修改**(仅前端一个文件):
|
||||||
|
- import 增加 `addShopStoreUser`(来自 `@/api/shop/shopStoreUser`)。
|
||||||
|
- 新增 state `clerkStoreId` / `clerkTenantId`,在 `getMyClerk()` 成功后写入(店员记录自带 `storeId`/`tenantId`)。
|
||||||
|
- `handleApprove` 在 `setUserDeveloper` 之后调 `addShopStoreUser({ userId, storeId: clerkStoreId, tenantId: clerkTenantId, isDelete: 0, comments: '开发者审核通过自动添加' })`,仅当 `clerkStoreId` 存在时执行。
|
||||||
|
|
||||||
|
**依赖**:后端 `POST /shop/shop-store-user`(ShopStoreUserController.save,需 `shop:shopStoreUser:save` 权限,店员已有);tenantId 复用审核店员的租户,避免前端另取。
|
||||||
|
|
||||||
|
## 平台管理页 UI 对齐门店中心
|
||||||
|
|
||||||
|
**需求**:用户要求 `pages/admin/index/index` 与 `pages/store/center/index` 一模一样的 UI。
|
||||||
|
|
||||||
|
**改动**(仅 `src/pages/admin/index/index.tsx`):
|
||||||
|
- 删除原先 NavBar + StatCard 网格 + EntryGrid 的旧结构,整体改写为门店中心同款「渐变绿色头部 + 白卡片列表(图标/标题/描述/角标/箭头)+ 底部信息卡」。
|
||||||
|
- 头部展示管理员 avatar + 「平台管理」标题 + 昵称·手机号(取 `useUser().user`)。
|
||||||
|
- FEATURE_CARDS 保留 4 个入口:应用审核(📝)、Git 审核(🐙)、域名审核(🌐)、工单处理(🎫),各自带 desc、color/bgColor、url。
|
||||||
|
- 角标沿用项目 `Badge` 组件:应用审核=待审核应用数(pagePublishReviews 已按 pending_review 过滤),工单处理=未解决/未关闭工单数(getAllTickets 取前100条 client 端过滤)。
|
||||||
|
- 底部卡片「平台概况」保留原页面统计:应用总数、用户总数、工单总数(pageAllApps / pageUsers / getAllTickets 的 total)。
|
||||||
|
- 保留登录校验(!isLoggedIn)与管理员校验(!isAdmin)两个空态,改为居中图标文案,去掉 NavBar 依赖(标题走 definePageConfig)。
|
||||||
|
- 新页面不再引入 nutui 组件,只用 `@tarojs/components` + 自定义 `Badge`,规避 weapp 空白坑。
|
||||||
|
|
||||||
|
**待验证**:`npm run build:weapp` 未实跑(环境 tsc 报 @tarojs/taro 类型库缺失属全局问题,与本次无关)。
|
||||||
|
|
||||||
|
## 平台管理 / 门店中心 改为 isSuperAdmin=true 才显示
|
||||||
|
|
||||||
|
**需求**:平台管理与门店中心两个入口都需要 `isSuperAdmin=true`(布尔)才显示(原先平台管理=isAdmin、门店中心=店员 storeInfo)。门店中心页面本身按用户确认改为「仅超级管理员可进」。
|
||||||
|
|
||||||
|
**改动文件**:
|
||||||
|
- `src/pages/user/user.tsx`:菜单项 平台管理、门店中心 的显示条件均由 `isAdmin` / `storeInfo` 改为 `(user as any)?.isSuperAdmin === 1`。
|
||||||
|
- `src/pages/index/index.tsx`:首页角色工作台外层容器补 `|| isSuperAdmin===true`,平台管理入口 `(user as any)?.isAdmin` → `isSuperAdmin === true`。
|
||||||
|
- `src/pages/admin/index/index.tsx`:页面访问门槛 `isAdmin` → `isSuperAdmin===true`(含空态文案);`loading` 拆为 `userLoading`(来自 useUser,防闪烁) + `dataLoading`(角标数据);首屏 `if (userLoading || dataLoading) spinner`。
|
||||||
|
- `src/pages/store/center/index.tsx`:移除 `getMyClerk()` 店员门槛,改为 `isSuperAdmin===true` 才进;非超管弹「仅超级管理员可访问」并 navigateBack;头部加 `header` 兜底(超管非店员时显示「门店中心」+ 管理员昵称/手机号);渲染守卫 `userLoading → 请先登录 → 无访问权限`,去掉原 `if(!storeInfo) return null`(否则非店员超管整页空白)。
|
||||||
|
|
||||||
|
**验证**:`npm run build:weapp` 在沙箱被 OOM/锁文件杀掉(exit 137,无报错),属环境问题,非代码错误;改动均为项目既有写法(useUser / Badge / 渐变头部)。
|
||||||
|
|
||||||
|
## 「申请成为开发者」菜单项随 isDeveloper 切换
|
||||||
|
|
||||||
|
**需求**:我的页面菜单里「申请成为开发者(推荐)」条目,若用户已审核通过成为开发者,该处文案/链接改为「进入开发者中心」(跳 `/pages/developer/index/index`)。
|
||||||
|
|
||||||
|
**改动**(`src/pages/user/user.tsx` 菜单 `menuItems`):
|
||||||
|
- 把原第 105 行固定「申请成为开发者」+ 第 107 行独立「开发者中心」(仅 isDeveloper 显示) 合并为条件式:`(user as any)?.isDeveloper ? { 进入开发者中心 → /pages/developer/index/index } : { 申请成为开发者 → /pages/user/vip-upgrade/index }`,两者均 `highlight: true`(醒目推荐样式)。去掉了冗余的独立「开发者中心」条目。
|
||||||
|
|
||||||
|
## 开发者中心 UI 对齐门店中心
|
||||||
|
|
||||||
|
**需求**:`pages/developer/index/index` 的 UI 改成和门店中心(`pages/store/center/index`)一模一样。
|
||||||
|
|
||||||
|
**改动**(仅 `src/pages/developer/index/index.tsx`,整体重写):
|
||||||
|
- 删除原 NavBar + StatCard 3列网格 + EntryGrid 老结构,改写为门店中心同款「渐变绿头部 + 白卡片列表(图标/标题/描述/角标/箭头)+ 底部卡片」。
|
||||||
|
- 头部:开发者 avatar + 「开发者中心」标题 + 昵称·手机号(取 `(user as any)`)。
|
||||||
|
- FEATURE_CARDS 三张:我的应用(📦/蓝)、API Key(🔑/绿)、我的工单(🎫/橙),各自 desc + url;角标沿用 `Badge`,展示各功能的计数(stats.apps/keys/tickets,由 getMyApps/getAppApiKeyStats/getMyTickets 的 total 取),卡片渲染后异步填充。
|
||||||
|
- 底部卡片改为「开发者帮助与支持」(💡 蓝圈)→ `/pages/user/help-center/index`(替代门店中心的订阅消息卡,订阅模板是门店专属,开发者无对应模板)。
|
||||||
|
- 保留两个空态:!isLoggedIn「请先登录」、!isDeveloper「尚未加入开发者中心」+ 申请按钮(保留原 apply 入口,store center 无此场景故无按钮);首屏用 `userLoading`(来自 useUser) 转圈防闪烁。
|
||||||
|
- 不再引入 NavBar/StatCard/EntryGrid/Loading(可能内部用 nutui,踩 weapp 空白坑),只用 `@tarojs/components` + 自定义 `Badge`,与门店中心一致。
|
||||||
|
|
||||||
|
## 首页轮播图空时不显示
|
||||||
|
|
||||||
|
**需求**:`pages/index/index` 的轮播图(banner)若为空则不显示。
|
||||||
|
|
||||||
|
**改动**(`src/pages/index/index.tsx` 轮播图渲染段):把外层 `.index-banner-wrap` 容器从「`bannerSlides.length>0` 渲染 Swiper,否则渲染『暂无轮播图』占位框」改为「`bannerSlides.length > 0 &&` 整个容器才渲染」;空时整块(含占位框)不显示,下方功能入口自动上移。
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
# 2026-07-24 工作日志
|
||||||
|
|
||||||
|
## 应用列表页改版(pages/developer/apps/index)
|
||||||
|
|
||||||
|
将「我的应用」列表从 CellRow 纯文本行改为卡片式布局:
|
||||||
|
- **卡片结构**:左侧 Logo/Emoji 图标 + 应用名 + 类型标识;中部双状态标签(发布状态 + 运行状态,带颜色);底部两行截断描述;底部操作栏(管理后台 / 首页 / 编辑 / 删除)。
|
||||||
|
- **Logo**:`AppProduct.logo` 有值时用 `Image` 展示,否则按 `appType` 显示 emoji 占位(🌐💚🤖🍎⚙️ 等)。
|
||||||
|
- **操作行为**:管理后台/首页按钮 → `setClipboardData` 复制链接并 toast;编辑 → 弹窗预填数据调 `updateAppProduct`;删除 → `Taro.showModal` 确认后调 `deleteAppProduct`。
|
||||||
|
- **编辑模式**:复用创建弹窗,`productCode` 编辑时 disabled,按钮文案区分「创建」/「保存」。
|
||||||
|
- **API 新增**:`appProduct.ts` 加了 `updateAppProduct`(PUT /app/product/update)和 `deleteAppProduct`(DELETE /app/product/delete/{id}),引入 `put`/`del` from request utils。
|
||||||
|
- **空状态**:带 emoji 图标 + 引导文案。
|
||||||
|
- 后端 Controller 在 websopy-java(不是 com.gxwebsoft.core),路径前缀 `/api/app/product`。
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
# 2026-08-10
|
||||||
|
|
||||||
|
## checkout 页面接入微信支付
|
||||||
|
|
||||||
|
文件:`src/pages/shop/checkout.tsx`
|
||||||
|
|
||||||
|
改动:
|
||||||
|
- 支付方式选项新增「微信支付」(`payType=1`),并设为默认;原「线下付款」(`payType=9`) 保留。
|
||||||
|
- `handleSubmit`:`createOrder(orderParams)` 后,若 `payType===1 && res`(`res` 为后端返回的 `WxPayResult`),调用 `Taro.requestPayment({timeStamp,nonceStr,package,signType,paySign})` 调起微信支付;取消/失败给明确提示,订单已创建故统一跳订单列表可重新支付。线下付款保持原「订单已提交」提示。
|
||||||
|
- 支付选项选中提示:微信支付显示绿色提示,线下付款橙色提示(原仅有线下提示)。
|
||||||
|
- 提交按钮文案:微信支付显示「提交并支付」,线下显示「提交订单」。
|
||||||
|
- 异常提示:微信/线下下单时若后端报「应用ID/appid 缺失」等支付配置错误,统一提示「微信支付暂不可用:支付配置缺失」。
|
||||||
|
|
||||||
|
验证:`npm run build:weapp` 编译通过(17.45s,Compiled successfully)。`tsc --noEmit` 因 tsconfig 把 `@tarojs/taro` 列入 types(缺 @types 声明)预存报错,与本次改动无关。
|
||||||
|
|
||||||
|
模式说明:本项目微信支付标准流程为 `createOrder(payType=1)` 后端直接返回 `WxPayResult` → `Taro.requestPayment(...)`,与 `pages/order/detail.tsx` 的 `handlePay`(用 `prepayShopOrder` 重新支付)一致。
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
# 2026-08-11 工作日志
|
||||||
|
|
||||||
|
## CMS 模块接口迁移到 cms-api
|
||||||
|
- **问题**:CMS 接口(/cms/cms-article, /cms/cms-ad 等)走 `shop-api.websoft.top/api/cms/...` 返回 404
|
||||||
|
- **方案**:CMS 模块全部改用独立 baseURL `https://cms-api.websoft.top/api`
|
||||||
|
- **改动清单**:
|
||||||
|
1. `config/env.js` — 三环境(dev/test/prod)新增 `CMS_API_BASE_URL: 'https://cms-api.websoft.top/api'`
|
||||||
|
2. `config/app.ts` — 新增导出 `CmsBaseUrl = CMS_API_BASE_URL`
|
||||||
|
3. 新建 `src/utils/cmsRequest.ts` — 基于 request 封装,默认 baseUrl 为 CmsBaseUrl,API 完全一致
|
||||||
|
4. `src/api/cms/` 下全部 34 个 index.ts — `import request` → `import cmsRequest`,`request.get/post/put/del` → `cmsRequest.get/post/put/del`(共 261 处调用)
|
||||||
|
5. `src/api/cms/cmsDomain/index.ts` — 特殊处理:原用 `SERVER_API_URL + '/cms/...'` 拼全 URL,改为相对路径走 cmsRequest
|
||||||
|
6. `src/api/layout/index.ts` — `getServerTime()` 和 `getNext7day()` 两个 CMS 接口改用 cmsRequest
|
||||||
|
7. `src/api/official/index.ts` — 更新注释说明
|
||||||
|
- **注意**:macOS sed 对 `request\.(get|post)\(` 模式替换无效(原因不明),最终用 `perl -pi -e 's/request\.get/cmsRequest.get/g'` 成功批量替换
|
||||||
|
|
||||||
|
## 公共组件去 NutUI 空壳化(高杠杆修复)
|
||||||
|
**背景**:排查 NutUI 空壳化残留时发现 `src/components/common/` 下两个公共组件内部引用 NutUI `Button`,属于最高杠杆修复点。
|
||||||
|
|
||||||
|
- **`EmptyState/index.tsx`**:被 53 个文件 import,其中 **3 个页面 4 处真正传了 `actionText`** —— 这些空状态按钮在 weapp 里此前完全不可见:
|
||||||
|
- `pages/shop/cart.tsx`:「去逛逛」「去登录后查看购物车」(空购物车唯一出口,之前是死路)
|
||||||
|
- `pages/user/address-list.tsx`:「添加地址」
|
||||||
|
- `pages/user/coupon-list.tsx`:动态 action
|
||||||
|
- 改法:`@nutui` Button → `@tarojs/components` Button,`size='mini'` + `bg-red-500 text-white rounded-full border-0`,用 style 强制 padding/lineHeight。
|
||||||
|
- **`FixedButton/index.tsx`**:同样改为 Taro 原生 Button;`icon` prop 由原生不支持改为渲染成 children。**注意:该组件全项目 0 处调用,是死代码**,保留但可考虑清理。
|
||||||
|
- **主色选择**:NutUI 默认 primary 是红色系(`--nutui-brand-*`),项目 `bg-red-500` 用了 32 次是事实主色,故统一用 red-500,不用 tailwind 里那套没人用的 `primary`(`bg-primary-*` 全项目 0 引用)。
|
||||||
|
|
||||||
|
### 附带修复:小程序 button::after 描边
|
||||||
|
- 发现项目从未处理微信原生 `button` 自带的 `::after` 灰色描边伪元素,**tailwind `border-0` 覆盖不到伪元素**,导致所有 Taro 原生 Button 多一圈边框(包括 7-23 修的 `passport/pay`)。
|
||||||
|
- 在 `src/app.scss` 加全局 `button::after { border: none; }`,已验证打进 `app-origin.wxss`。NutUI Button 编译产物不是 button 标签,不受影响。
|
||||||
|
|
||||||
|
### 状态
|
||||||
|
- 构建通过:`✔ Compiled successfully in 21.13s`。
|
||||||
|
- NutUI 引用文件 27 → **25**,`src/components/common/` 已完全无 NutUI。
|
||||||
|
- 剩余 25 个页面级散引用(Button 19 / Card 2 / Cell 1 / Divider 1),集中在 order、passport、booking,属长尾待办。
|
||||||
+48
-39
@@ -1,52 +1,61 @@
|
|||||||
# websopy-taro 项目记忆
|
# 项目长期记忆(websopy-taro)
|
||||||
|
|
||||||
## Tailwind CSS 在小程序中的兼容性规则
|
## 核心约定
|
||||||
|
|
||||||
以下 Tailwind 类名在 WXSS 中不兼容,项目内禁止使用,已通过 PostCSS 插件 `postcss-remove-wxss-incompatible.js` 自动移除对应的 CSS 规则:
|
### 开发者身份的表示方式
|
||||||
|
- **开发者身份主校验位 = `sys_user.is_developer` 布尔位**(数据库列 `is_developer`),登录态 `MainController.loginByDeveloperSms` 校验它才允许开发者登录。
|
||||||
|
- 后端仓库:`/Users/gxwebsoft/JAVA/com.gxwebsoft.core`(单 Maven 模块,包名 `com.gxwebsoft` 分包)。
|
||||||
|
- 标记接口:`PUT /api/system/user/developer/{userId}`(`UserController.setDeveloper` → `UserService.markAsDeveloper`),成功后经 `syncMessageProducer` 同步 websopy。
|
||||||
|
- 前端调用:`@/api/system/user` 的 `setUserDeveloper(userId)`。
|
||||||
|
- **vip-review 审核通过流程(用户 2026-07-22 最终确认,2026-07-23 补充)**:前端 `handleApprove` 依次调 ① `updateShopDealerApply(applyStatus=20)`;② `setUserDeveloper(userId)`(不碰 `sys_user_role`);③ **`addShopStoreUser`**——在当前门店下给申请人补一条 `shop_store_user`(店员)记录(`userId=申请人, storeId=审核店员所属门店, tenantId=同店员, isDelete=0`)。后端 `markAsDeveloper` 做两件事:① 把 `sys_user.is_developer` 置 `1`;② **替换**用户角色为 `developer`——`roleService.getByRoleCode(roleCode=developer, tenantId=用户租户)` 找到角色 → 删掉该用户现有 `sys_user_role` → 只绑定 developer 这一个角色,**不与原角色并存**(角色替换包 try/catch,失败不影响 isDeveloper 落库)。即"升级为开发者 = 单角色 developer + isDeveloper 标记位 + 一条门店店员记录",不是新增第二个角色。审核店员的 `storeId/tenantId` 来自 `getMyClerk()` 返回值(存于 `clerkStoreId/clerkTenantId` state)。
|
||||||
|
- 注意:前端 `vip-review` / `vip-upgrade` 等页面的变量名仍残留 `VIP_ROLE_*`、`pendingVipCount`、`dealerPrice` 等旧标识符(仅文案改成了「开发者」),后端角色与缓存 key 仍对应这些旧名,重命名需谨慎。
|
||||||
|
|
||||||
1. **`!xxx` important 修饰符**(如 `!visible`):生成 `.!visible` 选择器,WXSS 不支持反斜杠转义的 `!`。
|
### 超级管理员(isSuperAdmin=true)访问门槛(2026-07-23 确认)
|
||||||
|
- 用户对象字段 `isSuperAdmin`(`(user as any)?.isSuperAdmin === true`,布尔 true 才算),前端此前未引用,由后端 `sys_user` 返回。
|
||||||
|
- **平台管理**(`pages/admin/index/index`)与**门店中心**(`pages/store/center/index`)两个入口,**菜单与首页工作台均只在 `isSuperAdmin===true` 时显示**(原先平台管理是 `isAdmin`、门店中心是店员 `storeInfo`)。
|
||||||
|
- 门店中心页面已改为「仅超级管理员可进」:移除原 `getMyClerk()` 店员门槛;非超管访问弹「仅超级管理员可访问」并 `navigateBack`;超管若同时是店员仍会加载门店信息用于头部,否则头部兜底显示「门店中心」+ 管理员昵称/手机号。
|
||||||
|
- 两页都用 `useUser()` 的 `loading`(userLoading)做首屏门卫,避免超管用户未从 storage 解析完时先闪「无访问权限」。注意:`pages/store/center` 的入口曾依赖 `user.tsx` 里的 `storeInfo`(getMyClerk),现已不再用 `storeInfo` 决定菜单可见性。
|
||||||
|
|
||||||
2. **带 `0.5` 的类名**(如 `w-0.5`、`h-0.5`、`gap-0.5`):类名中的 `.` 被转义为 `\.`,生成 `.w-0\.5` 这样的选择器,WXSS 不兼容。应改用等效的整数尺寸或自定义值替代。
|
### 后端代码风格(com.gxwebsoft.core)
|
||||||
|
- Controller 继承 `BaseController`,统一返回 `ApiResult`(成功 `code=0`,失败 `code=1`),用 `success()/fail()`。
|
||||||
|
- 「按 userId 置一个标志位」的范式:`new User(){{setUserId(); setXxx();}}` → `userService.updateById(u)`(参考 `updateStatus`/`updateRecommend`/`auditUser`)。
|
||||||
|
- 用户变更后如需同步 websopy,经 `syncMessageProducer.sendUserSyncMessage("websopy","UPDATE",user)`(在 `UserServiceImpl` 内,`@Autowired(required=false)` 注入)。
|
||||||
|
- 用户相关代码都在 `com.gxwebsoft.common.system`(entity/service/controller),业务应用数据在 `com.gxwebsoft.websopy`(无 Controller)。
|
||||||
|
|
||||||
3. **`space-x-*` / `space-y-*`**(如 `space-y-4`):依赖 `:not()` + `~` 兄弟组合器 + CSS 变量 + 嵌套 `calc()`,WXSS 全不支持。应改用 `flex flex-col gap-*` 实现等效间距。
|
## 官网分包 official(2026-08-11 落地)
|
||||||
|
- 位置 `src/official/`(6 页:index/article/article-detail/about/message/mine)+ `src/official/components/`(OfficialTabBar/ArticleCard/SectionTitle),在 `src/app.config.ts` 的 `subpackages` 中 root=`official`。入口在 `src/pages/index/index.tsx` 搜索栏下的「企业官网」卡片。
|
||||||
|
- 数据层统一从 `@/api/official` 引入(re-export `src/api/cms/*`)。租户靠 `TenantId` 请求头。
|
||||||
|
- **⚠️ CMS 请求链路(2026-08-11 修正,勿回退)**:CMS 接口**不能**走 shop-api——`shop-api.websoft.top/api/cms/...` 返回 **404**,shop-api 并未代理 `/cms`(早前"已代理"的判断是错的)。现全部走独立 `https://cms-api.websoft.top/api`:
|
||||||
|
- `config/env.js` 三环境均有 `CMS_API_BASE_URL`,`config/app.ts` 导出 `CmsBaseUrl`。
|
||||||
|
- `src/utils/cmsRequest.ts` 基于 request 封装、默认 baseUrl=CmsBaseUrl,API 与 request 一致。
|
||||||
|
- `src/api/cms/` 下 34 个 `index.ts` 一律 `import cmsRequest`;**新增 CMS 接口必须用 cmsRequest,不要用 request**。
|
||||||
|
- 需在小程序后台把 `cms-api.websoft.top` 加入 request 合法域名白名单。
|
||||||
|
- 批量替换技巧:macOS `sed` 对 `request\.(get|post)\(` 替换无效,改用 `perl -pi -e 's/request\.get/cmsRequest.get/g'`。
|
||||||
|
- CMS 字段陷阱:文章封面 `image`(非 cover)、摘要 `overview`(非 summary)、`ARTICLE_STATUS.PUBLISHED = 0`(0 才是已发布);留言内容字段是 `need`(非 content),小程序提交带 `source:'miniapp'`。
|
||||||
|
- 微信原生 tabBar **不支持分包页面**,官网底部导航是自实现的 `OfficialTabBar`(用 `redirectTo`)。
|
||||||
|
- 品牌色统一 token:tailwind `brand` 色阶(600=`#185FA5`)+ `app.scss` 的 `--brand-color/--brand-light`。富文本用 `.official-rich`(定义在 `app.scss`,px 自动转 rpx)。
|
||||||
|
|
||||||
4. **`divide-x-*` / `divide-y-*`**:同样依赖 `:not()` + `~`,WXSS 不兼容。
|
## ⚠️ Taro 构建陷阱:dist 清空触发批量删除保护(2026-08-11)
|
||||||
|
`npm run build:weapp` 启动时 Taro 会 `emptyOutputDir` 清空 `dist/`,文件数 >50 会被安全删除守卫中断并抛 `SAFE_DELETE_BULK_CONFIRM_REQUIRED`。
|
||||||
|
**解决**:构建前先 `mv dist /tmp/taro-dist-bak/dist-$(date +%s)`,再执行构建,完成后 `rm -rf /tmp/taro-dist-bak`。
|
||||||
|
|
||||||
## 替代方案速查
|
## ⚠️ Taro 小程序 nutui-react-taro 兼容性陷阱(2026-07-23 确认)
|
||||||
|
|
||||||
| 禁用类名 | 替代写法 |
|
**问题**:`@nutui/nutui-react-taro@2.7.4` 的 **Cell / Price / Divider / Button** 等展示类组件,内部用 `React.createElement("div", ...)` 构建布局。Taro 4 的 babel/swc 没有把这些 `div` 转成 `view`,而 `dist/base.wxml` 里完全没有 `<div>` 模板,导致这些组件在 weapp 中"空壳化"——外层元素不存在,内层内容(`<div>`)被丢弃。
|
||||||
|---------|---------|
|
|
||||||
| `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` 替代 |
|
|
||||||
|
|
||||||
## 微信小程序接口合规
|
**症状**:组件位置完全空白 / 按钮不可见 / 价格显示为空。Text/View 与 `@nutui/icons-react-taro` 的图标(Tips/Check/Close/Clock,基于 SVG)渲染正常。
|
||||||
|
|
||||||
### 剪切板接口(运营规范 5.15.4)
|
**解决办法**(最小改动):在该页面用 Taro 原生组件替代:
|
||||||
|
| nutui(不可用) | Taro 原生 / 项目自定义 |
|
||||||
|
|---|---|
|
||||||
|
| `import { Button, Cell, Price, Divider } from '@nutui/nutui-react-taro'` | `import { Button } from '@tarojs/components'` + `import Price from '@/components/common/Price'` |
|
||||||
|
| `<Cell title="X" description={Y} />` | `<View className="flex justify-between px-4 py-3 border-b border-gray-100"><Text className="text-gray-600">X</Text><Text className="text-gray-800">{Y}</Text></View>` |
|
||||||
|
| `<Price price={n} size="large" thousands />` | `<Price price={n} size="large" />`(**用项目自定义 Price**,不用 nutui 的) |
|
||||||
|
| `<Divider />` | 用 `border-b border-gray-100` 替代 |
|
||||||
|
| `<Button type="primary" block loading={x} onClick={y}>...</Button>` | `<Button loading={x} onClick={y} className="flex-1 bg-blue-500 text-white rounded-full border-0">...</Button>`(className 控制颜色,`loading` prop 仍然支持) |
|
||||||
|
|
||||||
1. **只允许用户主动触发复制**:所有 `Taro.setClipboardData` 调用必须绑定在明确的用户点击事件上,禁止在无用户操作时自动读写剪切板。
|
**适用范围判断**:项目里其它页面(qr-login、register、passport/forget 等)也用了 nutui 的 Cell/Card/Divider/Button,遇到同类"组件消失"现象时按同样模式替换即可。已修:`src/passport/pay/index.tsx`(2026-07-23,扫码进入小程序后支付页空白的 bug)。
|
||||||
2. **禁止复制后强制中断流程**:复制成功后不得强制跳转、强制关注、强制添加客服等,不能要求用户通过其他方式才能完成当前业务流程。
|
|
||||||
3. **避免重复/失败 Toast 干扰体验**:微信小程序调用 `setClipboardData` 成功后系统会自带「内容已复制」提示。为避免重复提示及误报失败,统一封装 `utils/common.ts` 中的 `copyText` 函数:
|
|
||||||
- 小程序端(weapp)直接调用原生 `wx.setClipboardData`,不再额外显示自定义 `复制成功` / `复制失败` Toast
|
|
||||||
- 失败时仅通过 `console.error` 输出错误日志,便于排查
|
|
||||||
- 非小程序端保留原有成功/失败提示
|
|
||||||
|
|
||||||
### 项目内剪切板使用现状
|
**验证手段**:跑 `npm run build:weapp` 后在 `dist/<page>/index.js` 里 grep `nut-cell|nut-button|nut-divider|nut-price`,应为 0;grep `l.zx`(Taro Button 在 weapp 编译后的局部变量名)应出现 2 次(取消 + 立即支付)。
|
||||||
|
|
||||||
已审查所有 `setClipboardData` 调用:
|
**已知的 nutui 组件清单**(按 2026-07-23 grep 结果):`Button` 在 `passport/pay, passport/login/setting/forget/register, pages/order/*, pages/booking/*, components/common/{FixedButton,EmptyState}` 等大量页面;`Cell` 在 `passport/pay, pages/order/order.tsx`;`Divider` 在 `passport/pay, passport/qr-login`;`Price` 在 `passport/pay`。修复顺序建议:pay(已修)→ 视觉影响最大的页面 → 批量。
|
||||||
- 均通过按钮点击触发(复制咨询模板、复制密钥、复制链接、复制兑换码等)
|
|
||||||
- 无 `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` 修复记录。
|
|
||||||
|
|||||||
@@ -0,0 +1,62 @@
|
|||||||
|
# 小程序端 UI 布局与设计方案(基于 website-admin 后台)
|
||||||
|
|
||||||
|
## 一、结论先行
|
||||||
|
`website-admin`(Nuxt 4 + Vue3 + ant-design-vue)是「云·企业官网」SaaS 建站后台,管理的核心是可对外发布的**官网内容**(栏目导航、文章、产品、案例、单页、广告、留言线索),以及订阅/模板/域名等租户配置。
|
||||||
|
|
||||||
|
因此小程序端的自然定位是:**官网内容的移动端展示 + 获客工具**(即后台所建官网的小程序版),而非现有 `websopy-taro` 里的电商/商城体系。
|
||||||
|
|
||||||
|
> ⚠️ 关键提醒:现有 `websopy-taro` 实际是「电商/会员/门店/开发者」小程序,与 `website-admin` 的「官网建站」业务域不一致。下文按"官网展示型小程序"规划,并在第六节给出两种落地路径供选择。
|
||||||
|
|
||||||
|
## 二、信息架构(IA):后台模块 → 小程序页面映射
|
||||||
|
|
||||||
|
| website-admin 模块 | 小程序页面 | 说明 |
|
||||||
|
|---|---|---|
|
||||||
|
| 控制台(统计/订阅) | 我的 / 首页概览 | 小程序仅展示轻量订阅状态 |
|
||||||
|
| 栏目导航 menus | 首页导航宫格 | 由后台栏目自动生成入口 |
|
||||||
|
| 广告 banners | 首页轮播 Banner | 后台配置 |
|
||||||
|
| 文章 articles | 资讯列表 / 资讯详情 | 含分类筛选 |
|
||||||
|
| 产品 products | 产品列表 / 产品详情 | 含分类 |
|
||||||
|
| 案例 cases | 案例列表 / 案例详情 | — |
|
||||||
|
| 单页 pages | 关于我们 / 联系我们 | 富文本渲染 |
|
||||||
|
| 留言咨询 messages | 留言咨询表单 / 我的留言 | 提交线索 |
|
||||||
|
| 模板 / 域名 / 设置 | 不暴露(仅后台) | 小程序按当前租户自动套用 |
|
||||||
|
|
||||||
|
## 三、导航与 tabBar 方案
|
||||||
|
推荐 4 个底部 Tab(沿用微信小程序原生 tabBar,`custom` 可选):
|
||||||
|
1. **首页 Home** — Banner + 栏目宫格 + 资讯/产品/案例推荐
|
||||||
|
2. **产品 Products** — 分类 + 产品网格
|
||||||
|
3. **案例 Cases**(或 资讯)— 案例列表
|
||||||
|
4. **我的 Mine** — 账号、我的留言、订阅状态
|
||||||
|
|
||||||
|
资讯 / 关于 / 留言作为首页二级入口或非 Tab 页。
|
||||||
|
|
||||||
|
## 四、页面布局草案(关键屏)
|
||||||
|
- **首页**:状态栏+标题栏 → Banner 轮播 → 栏目导航宫格 → "资讯动态"横滑卡片 → "推荐产品/案例"区块 → 底部留言 CTA。
|
||||||
|
- **产品列表**:左侧分类栏 + 右侧产品网格(ProductCard)。
|
||||||
|
- **资讯详情**:标题/时间/来源 → 富文本正文(rich-text)→ 相关推荐 → 底部"咨询"按钮。
|
||||||
|
- **我的**:头像/企业名 → 我的留言 → 订阅状态 → 设置。
|
||||||
|
|
||||||
|
## 五、UI 设计规范建议
|
||||||
|
1. **建立 Design Token**(CSS 变量):主色、中性色、语义色、间距、圆角、字号。主色默认取自 `website-admin`「网站设置」的 brand 色,支持按租户覆盖。
|
||||||
|
2. **统一主题**:消除当前 4 套主色并存的混乱(tabBar `#0e932e` / CSS 变量 `#1890ff` / Tailwind `#0ea5e9` / 微信 `#07c160`),收敛为 1 套 token。
|
||||||
|
3. **图标体系**:弃用 emoji 当图标;改用 `@nutui/icons-react-taro`(SVG,已验证可渲染)或自定义图标组件。
|
||||||
|
4. **统一布局容器**:新增 `Layout` 组件(NavBar + 内容区 + 可选自定义 TabBar),避免每页各自实现。
|
||||||
|
5. **NutUI 去"空壳化"**:继续替换 `Cell/Price/Divider/Button`(weapp 端整块空白的已知 bug),改用 Taro 原生 + 项目自定义组件。
|
||||||
|
6. **富文本**:后台 wangeditor 的 HTML 经 Taro `rich-text` 渲染,需做外联 style 注入与样式隔离。
|
||||||
|
7. **内容卡片组件集**:Banner、ArticleCard、ProductCard、CaseCard、NavGrid、SectionTitle、LeadForm、EmptyState。
|
||||||
|
|
||||||
|
## 六、与现有 websopy-taro 的关系 & 落地路径
|
||||||
|
- **路径 A(推荐)**:新建独立小程序工程(或 `websopy-taro` 新分支),专门做官网展示端,复用 Taro + React + Tailwind 技术栈与工具链,不混入电商逻辑。
|
||||||
|
- **路径 B**:在现有 `websopy-taro` 内新增"官网"分包,与电商并存;适合想用一个小程序承载多业务。
|
||||||
|
- **路径 C**:直接复用 `website-admin` 的 CMS API(`app/api/cms`、`app/api/websopy`),小程序端只做展示层。
|
||||||
|
|
||||||
|
建议先确认路径,再进入详细设计 / 实现。
|
||||||
|
|
||||||
|
## 七、实施路线图(建议)
|
||||||
|
1. 定主色 / Design Token / 图标规范
|
||||||
|
2. 搭 Layout + NavBar + TabBar 骨架
|
||||||
|
3. 首页(Banner + 宫格 + 资讯/产品/案例区块)
|
||||||
|
4. 资讯 / 产品 / 案例 列表 + 详情(rich-text)
|
||||||
|
5. 留言咨询表单 + 我的留言
|
||||||
|
6. 我的页 + 订阅状态
|
||||||
|
7. 联调 `website-admin` CMS API、按租户适配模板
|
||||||
@@ -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,187 +1,59 @@
|
|||||||
<div align="center">
|
# Paopao Taro - 项目框架
|
||||||
<h1>🚀 WebSoft 小程序</h1>
|
|
||||||
<p><strong>基于 Taro + React + TypeScript 的跨平台小程序应用</strong></p>
|
|
||||||
|
|
||||||
<p>
|
基于 Taro 4.0.8 + React 18.3.1 + TypeScript 5.7.2 的跨端开发框架
|
||||||
<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>
|
|
||||||
|
|
||||||
## 📖 项目简介
|
## 📦 技术栈
|
||||||
|
|
||||||
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 框架
|
||||||
| Taro | 4.0.8 | 跨平台开发框架 |
|
- **TypeScript**: `5.7.2` - 类型系统
|
||||||
| 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 | 代码质量检查 |
|
|
||||||
|
|
||||||
### UI 组件库
|
### UI 组件库
|
||||||
| 技术 | 版本 | 说明 |
|
- **NutUI React Taro**: `2.7.4` - 京东风格的 Taro React 组件库
|
||||||
|------|------|------|
|
- **NutUI Icons**: `^1.0.0` - 图标库
|
||||||
| NutUI React Taro | 2.7.4 | 移动端组件库 |
|
|
||||||
| NutUI Icons | 2.0.1 | 图标库 |
|
|
||||||
| NutUI Biz | 1.0.0-beta.2 | 业务组件库 |
|
|
||||||
|
|
||||||
### 样式方案
|
### 样式方案
|
||||||
| 技术 | 版本 | 说明 |
|
- **Sass**: `^1.81.0` - CSS 预处理器
|
||||||
|------|------|------|
|
- **TailwindCSS**: `3.4.17` - 原子化 CSS 框架
|
||||||
| Sass | - | CSS预处理器 |
|
|
||||||
| TailwindCSS | 3.4.17 | 原子化CSS框架 |
|
|
||||||
| Autoprefixer | 10.4.20 | CSS自动前缀 |
|
|
||||||
|
|
||||||
### 功能组件
|
### 状态管理
|
||||||
- **React Router DOM** - 路由管理
|
- **React Hooks** - 内置 Hooks
|
||||||
- **Day.js** - 日期时间处理
|
- **Context API** - 跨组件状态共享
|
||||||
- **Crypto-js** - 加密解密工具
|
|
||||||
- **js-base64** - Base64编码解码
|
|
||||||
- **React Markdown** - Markdown渲染
|
|
||||||
- **ECharts Taro3 React** - 图表组件
|
|
||||||
|
|
||||||
## 📋 环境要求
|
### 工具库
|
||||||
|
- **Day.js**: `^1.11.13` - 日期处理
|
||||||
### 基础环境
|
- **Crypto-js**: `^4.2.0` - 加密解密
|
||||||
- 📦 **Node.js 16+**
|
- **React Router DOM**: `^6.28.0` - 路由管理
|
||||||
- 📱 **微信开发者工具** (开发微信小程序)
|
|
||||||
- 🔧 **支付宝开发者工具** (开发支付宝小程序)
|
|
||||||
- 🌐 **现代浏览器** (开发H5应用)
|
|
||||||
|
|
||||||
### 包管理器
|
|
||||||
- **npm** / **yarn** / **pnpm** (推荐使用pnpm)
|
|
||||||
|
|
||||||
### 开发工具
|
|
||||||
- **推荐**:VS Code / WebStorm
|
|
||||||
- **插件**:ES7+ React/Redux/React-Native snippets、TypeScript Importer
|
|
||||||
|
|
||||||
## 🚀 快速开始
|
## 🚀 快速开始
|
||||||
|
|
||||||
### 1. 克隆项目
|
### 安装依赖
|
||||||
```bash
|
|
||||||
git clone https://github.com/websoft-top/mp-taro.git
|
|
||||||
cd mp-taro
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. 安装依赖
|
|
||||||
```bash
|
```bash
|
||||||
# 使用 pnpm (推荐)
|
# 使用 npm
|
||||||
pnpm install
|
|
||||||
|
|
||||||
# 或使用 npm
|
|
||||||
npm install
|
npm install
|
||||||
|
|
||||||
# 或使用 yarn
|
# 使用 yarn
|
||||||
yarn install
|
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
|
```bash
|
||||||
# 开发环境(默认)
|
# 微信小程序
|
||||||
npm run dev:weapp
|
npm run dev:weapp
|
||||||
|
|
||||||
# 生产环境
|
# H5
|
||||||
NODE_ENV=production npm run dev:weapp
|
|
||||||
|
|
||||||
# 测试环境
|
|
||||||
NODE_ENV=test npm run dev:weapp
|
|
||||||
```
|
|
||||||
然后使用微信开发者工具打开 `dist` 目录
|
|
||||||
|
|
||||||
#### H5开发
|
|
||||||
```bash
|
|
||||||
# 启动H5开发模式
|
|
||||||
npm run dev:h5
|
npm run dev:h5
|
||||||
```
|
|
||||||
访问 `http://localhost:10086` 查看H5版本
|
|
||||||
|
|
||||||
#### 其他平台
|
|
||||||
```bash
|
|
||||||
# 支付宝小程序
|
# 支付宝小程序
|
||||||
npm run dev:alipay
|
npm run dev:alipay
|
||||||
|
|
||||||
# 百度智能小程序
|
# 百度小程序
|
||||||
npm run dev:swan
|
npm run dev:swan
|
||||||
|
|
||||||
# 字节跳动小程序
|
# 字节跳动小程序
|
||||||
@@ -189,404 +61,203 @@ npm run dev:tt
|
|||||||
|
|
||||||
# QQ 小程序
|
# QQ 小程序
|
||||||
npm run dev: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
|
```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
|
npm run build:weapp
|
||||||
|
|
||||||
# 构建H5应用
|
# H5
|
||||||
npm run build:h5
|
npm run build:h5
|
||||||
|
|
||||||
# 构建支付宝小程序
|
# 其他平台类似
|
||||||
npm run build:alipay
|
npm run build:[platform]
|
||||||
|
|
||||||
# 构建其他平台
|
|
||||||
npm run build:swan # 百度智能小程序
|
|
||||||
npm run build:tt # 字节跳动小程序
|
|
||||||
npm run build:qq # QQ小程序
|
|
||||||
npm run build:jd # 京东小程序
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### 主要API模块
|
## 📁 项目结构
|
||||||
项目API按功能模块组织:
|
|
||||||
|
|
||||||
- **用户认证**: `src/api/passport/` - 登录、注册、权限验证
|
|
||||||
- **用户管理**: `src/api/system/user/` - 用户信息管理
|
|
||||||
- **内容管理**: `src/api/cms/` - 文章、媒体文件管理
|
|
||||||
- **商城管理**: `src/api/shop/` - 商品、订单管理
|
|
||||||
- **系统管理**: `src/api/system/` - 系统配置、参数管理
|
|
||||||
|
|
||||||
## 🚀 部署指南
|
|
||||||
|
|
||||||
### 微信小程序部署
|
|
||||||
1. 构建小程序代码:
|
|
||||||
```bash
|
|
||||||
npm run build:weapp
|
|
||||||
```
|
```
|
||||||
2. 使用微信开发者工具打开 `dist` 目录
|
shop-taro/
|
||||||
3. 点击"上传"按钮,上传代码到微信后台
|
├── config/ # Taro 配置文件
|
||||||
4. 在微信公众平台提交审核
|
│ ├── index.ts # 主配置
|
||||||
|
│ ├── dev.ts # 开发配置
|
||||||
### H5应用部署
|
│ └── prod.ts # 生产配置
|
||||||
1. 构建H5应用:
|
├── src/ # 源代码
|
||||||
```bash
|
│ ├── app.config.ts # 应用配置(页面路由、tabBar等)
|
||||||
npm run build:h5
|
│ ├── 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 # 项目说明
|
||||||
```
|
```
|
||||||
2. 将 `dist` 目录下的文件上传到Web服务器
|
|
||||||
3. 配置Nginx或Apache服务器
|
|
||||||
4. 确保HTTPS访问(微信要求)
|
|
||||||
|
|
||||||
### 其他平台部署
|
## ✨ 特性
|
||||||
各平台部署流程类似,构建对应平台代码后,使用相应的开发者工具上传即可。
|
|
||||||
|
|
||||||
### 生产环境配置
|
### 1. 主题切换
|
||||||
在生产环境中,需要修改相关配置:
|
- 支持浅色/深色模式切换
|
||||||
|
- 使用 Context API 管理主题状态
|
||||||
|
- 暗黑模式适配
|
||||||
|
|
||||||
1. **环境变量配置**:修改 `config/env.ts` 中的生产环境配置
|
### 2. NutUI 组件库
|
||||||
```typescript
|
- 已集成 @nutui/nutui-react-taro@2.7.4
|
||||||
production: {
|
- 支持按需引入
|
||||||
API_BASE_URL: 'https://your-production-api.com/api',
|
- 中文国际化配置
|
||||||
APP_NAME: 'Your App Name',
|
|
||||||
DEBUG: 'false',
|
### 3. TailwindCSS 集成
|
||||||
|
- 支持原子化 CSS 编写
|
||||||
|
- 暗黑模式支持(dark mode)
|
||||||
|
- 自定义主题色配置
|
||||||
|
|
||||||
|
### 4. 工具函数封装
|
||||||
|
- **日期处理**: 基于 Day.js 的格式化
|
||||||
|
- **加密解密**: MD5、AES 加密
|
||||||
|
- **性能优化**: 防抖、节流函数
|
||||||
|
- **数据操作**: 深拷贝等工具
|
||||||
|
|
||||||
|
### 5. TypeScript 支持
|
||||||
|
- 完整的类型定义
|
||||||
|
- 路径别名配置 `@/*`
|
||||||
|
- 严格模式开启
|
||||||
|
|
||||||
|
## 🎨 代码示例
|
||||||
|
|
||||||
|
### 使用 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>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
2. **构建配置**:修改 `config/prod.ts` 中的构建配置
|
### 使用 TailwindCSS
|
||||||
- 启用代码压缩和混淆
|
|
||||||
- 配置CDN加速
|
|
||||||
- 优化资源加载
|
|
||||||
|
|
||||||
3. **部署命令**:
|
```tsx
|
||||||
```bash
|
<View className="p-4 bg-white dark:bg-gray-800">
|
||||||
NODE_ENV=production npm run build:weapp
|
<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. TailwindCSS 样式不生效?
|
||||||
1. **Node.js版本问题**:确保使用Node.js 16+版本
|
检查 `postcss.config.js` 配置,确保已安装 `postcss-nested`。
|
||||||
2. **依赖安装失败**:尝试清除缓存后重新安装
|
|
||||||
```bash
|
### 2. NutUI 组件样式丢失?
|
||||||
npm cache clean --force
|
确保在 `app.scss` 中引入了 NutUI 样式:
|
||||||
npm install
|
```scss
|
||||||
|
@import '@nutui/nutui-react-taro/dist/styles/vitamin.css';
|
||||||
```
|
```
|
||||||
3. **编译错误**:检查TypeScript配置和代码语法
|
|
||||||
|
|
||||||
### 小程序开发问题
|
### 3. TypeScript 路径别名报错?
|
||||||
1. **微信开发者工具无法预览**:检查appid配置和网络连接
|
检查 `tsconfig.json` 的 `paths` 配置,并确保 IDE 正确识别。
|
||||||
2. **API请求失败**:确认后端服务正常运行,检查域名白名单
|
|
||||||
3. **样式显示异常**:检查rpx单位使用和NutUI组件样式
|
|
||||||
|
|
||||||
### 性能优化建议
|
## 📚 相关文档
|
||||||
1. **代码分割**:使用Taro的分包功能减少主包大小
|
|
||||||
2. **图片优化**:使用WebP格式,启用图片懒加载
|
|
||||||
3. **请求优化**:合并API请求,使用缓存机制
|
|
||||||
|
|
||||||
## 🤝 贡献指南
|
- [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/)
|
||||||
|
|
||||||
1. Fork 本仓库
|
## 📄 License
|
||||||
2. 创建特性分支 (`git checkout -b feature/AmazingFeature`)
|
|
||||||
3. 提交更改 (`git commit -m 'Add some AmazingFeature'`)
|
|
||||||
4. 推送到分支 (`git push origin feature/AmazingFeature`)
|
|
||||||
5. 打开 Pull Request
|
|
||||||
|
|
||||||
## 📄 许可证
|
MIT
|
||||||
|
|
||||||
本项目采用 MIT 许可证 - 查看 [LICENSE](LICENSE) 文件了解详情
|
|
||||||
|
|
||||||
## 📞 联系我们
|
|
||||||
|
|
||||||
- 官网:https://websoft.top
|
|
||||||
- 邮箱:170083662@qq.com
|
|
||||||
- QQ群:479713884
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
⭐ 如果这个项目对您有帮助,请给我们一个星标!
|
**构建时间**: 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
|
|
||||||
+6
-19
@@ -1,24 +1,11 @@
|
|||||||
// babel-preset-taro 更多选项和默认值:
|
|
||||||
// https://github.com/NervJS/taro/blob/next/packages/babel-preset-taro/README.md
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
presets: [
|
"presets": [
|
||||||
['taro',
|
["taro", {
|
||||||
{
|
"framework": "react",
|
||||||
framework: 'react',
|
"ts": true
|
||||||
ts: 'true',
|
|
||||||
compiler: 'webpack5',
|
|
||||||
}]
|
}]
|
||||||
],
|
],
|
||||||
plugins: [
|
"plugins": [
|
||||||
[
|
"@babel/plugin-transform-runtime"
|
||||||
"import",
|
|
||||||
{
|
|
||||||
"libraryName": "@nutui/nutui-react-taro",
|
|
||||||
"libraryDirectory": "dist/esm",
|
|
||||||
"style": 'css',
|
|
||||||
"camel2DashComponentName": false
|
|
||||||
},
|
|
||||||
'nutui-react-taro'
|
|
||||||
]
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
+8
-13
@@ -1,14 +1,9 @@
|
|||||||
import { API_BASE_URL } from './env'
|
import { API_BASE_URL, SERVER_API_URL, WEBSOPY_API_BASE_URL, CMS_API_BASE_URL } from './env'
|
||||||
|
|
||||||
// 租户ID - 请根据实际情况修改
|
export const TenantId = '5'
|
||||||
export const TenantId = '5';
|
export const TenantName = '赛联小助手'
|
||||||
// 租户名称
|
export const BaseUrl = API_BASE_URL
|
||||||
export const TenantName = '网宿软件';
|
export const ServerBaseUrl = SERVER_API_URL
|
||||||
// 接口地址 - 请根据实际情况修改
|
export const WebsopyBaseUrl = WEBSOPY_API_BASE_URL
|
||||||
export const BaseUrl = API_BASE_URL;
|
export const CmsBaseUrl = CMS_API_BASE_URL
|
||||||
// 当前版本
|
export const Version = 'v3.0.0'
|
||||||
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 ./
|
|
||||||
|
|||||||
+14
-10
@@ -1,13 +1,17 @@
|
|||||||
import type { UserConfigExport } from "@tarojs/cli";
|
|
||||||
export default {
|
export default {
|
||||||
logger: {
|
env: {
|
||||||
quiet: false,
|
NODE_ENV: '"development"'
|
||||||
stats: true
|
|
||||||
},
|
},
|
||||||
mini: {
|
defineConstants: {
|
||||||
miniCssExtractPluginOption: {
|
__DEV__: JSON.stringify(true)
|
||||||
ignoreOrder: 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,34 @@
|
|||||||
|
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',
|
||||||
|
CMS_API_BASE_URL: 'https://cms-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',
|
||||||
|
CMS_API_BASE_URL: 'https://cms-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',
|
||||||
|
CMS_API_BASE_URL: 'https://cms-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, CMS_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 { defineConfig, type UserConfigExport } from '@tarojs/cli'
|
||||||
import TsconfigPathsPlugin from 'tsconfig-paths-webpack-plugin'
|
import TsconfigPathsPlugin from 'tsconfig-paths-webpack-plugin'
|
||||||
|
import { UnifiedWebpackPluginV5 } from 'weapp-tailwindcss/webpack'
|
||||||
import devConfig from './dev'
|
import devConfig from './dev'
|
||||||
import prodConfig from './prod'
|
import prodConfig from './prod'
|
||||||
import { getEnvConfig } from './env'
|
import path from 'path'
|
||||||
|
import { getEnvConfig } from './env.js'
|
||||||
// import vitePluginImp from 'vite-plugin-imp'
|
|
||||||
// https://taro-docs.jd.com/docs/next/config#defineconfig-辅助函数
|
|
||||||
export default defineConfig<'webpack5'>(async (merge, {}) => {
|
|
||||||
|
|
||||||
|
// https://taro-docs.jd.com/docs/next/config
|
||||||
|
export default defineConfig<'webpack5'>(async (merge, { mode }) => {
|
||||||
const baseConfig: UserConfigExport<'webpack5'> = {
|
const baseConfig: UserConfigExport<'webpack5'> = {
|
||||||
projectName: 'websoft-react',
|
// 项目配置
|
||||||
date: '2024-12-30',
|
projectName: 'shop-taro',
|
||||||
plugins: ['@tarojs/plugin-html'],
|
date: '2026-5-10',
|
||||||
designWidth (input:any) {
|
designWidth: 750,
|
||||||
// 配置 NutUI 375 尺寸
|
|
||||||
if (input?.file?.replace(/\\+/g, '/').indexOf('@nutui') > -1) {
|
|
||||||
return 375
|
|
||||||
}
|
|
||||||
// 全局使用 Taro 默认的 750 尺寸
|
|
||||||
return 750
|
|
||||||
},
|
|
||||||
deviceRatio: {
|
deviceRatio: {
|
||||||
640: 2.34 / 2,
|
640: 2.34 / 2,
|
||||||
750: 1,
|
750: 1,
|
||||||
828: 1.81 / 2,
|
828: 1.81 / 2
|
||||||
375: 2 / 1
|
|
||||||
},
|
},
|
||||||
sourceRoot: 'src',
|
sourceRoot: 'src',
|
||||||
outputRoot: 'dist',
|
outputRoot: 'dist',
|
||||||
|
plugins: [
|
||||||
|
'@tarojs/plugin-platform-weapp',
|
||||||
|
'@tarojs/plugin-platform-h5'
|
||||||
|
],
|
||||||
|
|
||||||
|
// 编译配置
|
||||||
defineConstants: {
|
defineConstants: {
|
||||||
API_BASE_URL: JSON.stringify(getEnvConfig().API_BASE_URL),
|
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),
|
APP_NAME: JSON.stringify(getEnvConfig().APP_NAME),
|
||||||
DEBUG: JSON.stringify(getEnvConfig().DEBUG)
|
DEBUG: JSON.stringify(getEnvConfig().DEBUG),
|
||||||
},
|
},
|
||||||
copy: {
|
copy: {
|
||||||
patterns: [
|
patterns: [],
|
||||||
],
|
options: {}
|
||||||
options: {
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
framework: 'react',
|
framework: 'react',
|
||||||
compiler: {
|
compiler: {
|
||||||
|
|
||||||
type: 'webpack5',
|
type: 'webpack5',
|
||||||
prebundle: {
|
prebundle: {
|
||||||
exclude: ['@nutui/nutui-react-taro', '@nutui/icons-react-taro'],
|
|
||||||
enable: false
|
enable: false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
// 开启持久化缓存 - 有效提升二次编译速度
|
||||||
cache: {
|
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: {
|
mini: {
|
||||||
postcss: {
|
postcss: {
|
||||||
pxtransform: {
|
pxtransform: {
|
||||||
|
enable: true,
|
||||||
|
config: {}
|
||||||
|
},
|
||||||
|
url: {
|
||||||
enable: true,
|
enable: true,
|
||||||
config: {
|
config: {
|
||||||
selectorBlackList: ['nut-']
|
limit: 1024
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
cssModules: {
|
cssModules: {
|
||||||
enable: false, // 默认为 false,如需使用 css modules 功能,则设为 true
|
enable: false,
|
||||||
config: {
|
config: {
|
||||||
namingPattern: 'module', // 转换模式,取值为 global/module
|
namingPattern: 'module',
|
||||||
generateScopedName: '[name]__[local]___[hash:base64:5]'
|
generateScopedName: '[name]__[local]___[hash:base64:5]'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
webpackChain(chain) {
|
webpackChain(chain) {
|
||||||
chain.resolve.plugin('tsconfig-paths').use(TsconfigPathsPlugin)
|
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: {
|
h5: {
|
||||||
publicPath: '/',
|
publicPath: '/',
|
||||||
staticDirectory: 'static',
|
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: {
|
postcss: {
|
||||||
autoprefixer: {
|
autoprefixer: {
|
||||||
enable: true,
|
enable: true
|
||||||
config: {}
|
|
||||||
},
|
},
|
||||||
cssModules: {
|
cssModules: {
|
||||||
enable: false, // 默认为 false,如需使用 css modules 功能,则设为 true
|
enable: false,
|
||||||
config: {
|
config: {
|
||||||
namingPattern: 'module', // 转换模式,取值为 global/module
|
namingPattern: 'module',
|
||||||
generateScopedName: '[name]__[local]___[hash:base64:5]'
|
generateScopedName: '[name]__[local]___[hash:base64:5]'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -100,19 +111,10 @@ export default defineConfig<'webpack5'>(async (merge, {}) => {
|
|||||||
chain.resolve.plugin('tsconfig-paths').use(TsconfigPathsPlugin)
|
chain.resolve.plugin('tsconfig-paths').use(TsconfigPathsPlugin)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
rn: {
|
|
||||||
appName: 'taroDemo',
|
|
||||||
postcss: {
|
|
||||||
cssModules: {
|
|
||||||
enable: false, // 默认为 false,如需使用 css modules 功能,则设为 true
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
if (mode === 'development') {
|
||||||
}
|
|
||||||
if (process.env.NODE_ENV === 'development') {
|
|
||||||
// 本地开发构建配置(不混淆压缩)
|
|
||||||
return merge({}, baseConfig, devConfig)
|
return merge({}, baseConfig, devConfig)
|
||||||
}
|
}
|
||||||
// 生产构建配置(默认开启压缩混淆等)
|
|
||||||
return merge({}, baseConfig, prodConfig)
|
return merge({}, baseConfig, prodConfig)
|
||||||
})
|
})
|
||||||
|
|||||||
+9
-33
@@ -1,36 +1,12 @@
|
|||||||
import type { UserConfigExport } from "@tarojs/cli";
|
|
||||||
export default {
|
export default {
|
||||||
mini: {
|
env: {
|
||||||
miniCssExtractPluginOption: {
|
NODE_ENV: '"production"'
|
||||||
ignoreOrder: true
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
h5: {
|
defineConstants: {
|
||||||
/**
|
__DEV__: JSON.stringify(false)
|
||||||
* WebpackChain 插件配置
|
},
|
||||||
* @docs https://github.com/neutrinojs/webpack-chain
|
mini: {
|
||||||
*/
|
compressTemplate: true
|
||||||
// webpackChain (chain) {
|
},
|
||||||
// /**
|
h5: {}
|
||||||
// * 如果 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'>
|
|
||||||
|
|||||||
@@ -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",
|
"version": "1.0.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"description": "WebSoft Inc.",
|
"description": "Taro + React + TypeScript 项目框架",
|
||||||
"templateInfo": {
|
"browserslist": [
|
||||||
"name": "react-NutUI",
|
"last 3 versions",
|
||||||
"typescript": true,
|
"Android >= 4.1",
|
||||||
"css": "Sass",
|
"ios >= 8"
|
||||||
"framework": "React"
|
],
|
||||||
},
|
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build:weapp": "taro build --type weapp",
|
"build:weapp": "taro build --type weapp",
|
||||||
"build:swan": "taro build --type swan",
|
"build:swan": "taro build --type swan",
|
||||||
@@ -28,81 +27,64 @@
|
|||||||
"dev:qq": "npm run build:qq -- --watch",
|
"dev:qq": "npm run build:qq -- --watch",
|
||||||
"dev:jd": "npm run build:jd -- --watch",
|
"dev:jd": "npm run build:jd -- --watch",
|
||||||
"dev:quickapp": "npm run build:quickapp -- --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": {
|
"dependencies": {
|
||||||
"@babel/runtime": "^7.26.0",
|
|
||||||
"@nutui/icons-react-taro": "^2.0.1",
|
"@nutui/icons-react-taro": "^2.0.1",
|
||||||
"@nutui/nutui-biz": "1.0.0-beta.2",
|
"@nutui/nutui-biz": "1.0.0-beta.2",
|
||||||
"@nutui/nutui-react": "^3.0.16",
|
"@nutui/nutui-react": "^3.0.16",
|
||||||
"@nutui/nutui-react-taro": "^2.7.4",
|
"@nutui/nutui-react-taro": "2.7.4",
|
||||||
"@react-native/metro-config": "^0.73.2",
|
"@tarojs/components": "4.1.11",
|
||||||
"@tarojs/components": "4.0.8",
|
"@tarojs/plugin-framework-react": "4.1.11",
|
||||||
"@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",
|
|
||||||
"@tarojs/plugin-platform-h5": "4.0.8",
|
"@tarojs/plugin-platform-h5": "4.0.8",
|
||||||
"@tarojs/plugin-platform-jd": "4.0.8",
|
"@tarojs/plugin-platform-weapp": "4.1.11",
|
||||||
"@tarojs/plugin-platform-qq": "4.0.8",
|
"@tarojs/react": "4.1.11",
|
||||||
"@tarojs/plugin-platform-swan": "4.0.8",
|
"@tarojs/runtime": "4.1.11",
|
||||||
"@tarojs/plugin-platform-tt": "4.0.8",
|
"@tarojs/shared": "4.1.11",
|
||||||
"@tarojs/plugin-platform-weapp": "4.0.8",
|
"@tarojs/taro": "4.1.11",
|
||||||
"@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",
|
|
||||||
"crypto-js": "^4.2.0",
|
"crypto-js": "^4.2.0",
|
||||||
"dayjs": "^1.11.13",
|
"dayjs": "^1.11.13",
|
||||||
"echarts-taro3-react": "^1.0.13",
|
"echarts-taro3-react": "^1.0.13",
|
||||||
"expo": "~50.0.2",
|
|
||||||
"js-base64": "^3.7.7",
|
"js-base64": "^3.7.7",
|
||||||
"react": "^18.3.1",
|
"react": "18.3.1",
|
||||||
"react-dom": "^18.3.1",
|
"react-dom": "18.3.1",
|
||||||
"react-markdown": "^10.1.0",
|
"react-markdown": "^10.1.0",
|
||||||
"react-native": "^0.73.1",
|
|
||||||
"react-router-dom": "^7.1.1",
|
"react-router-dom": "^7.1.1",
|
||||||
"weapp-qrcode": "^1.0.0"
|
"weapp-qrcode": "^1.0.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/core": "^7.26.0",
|
"@babel/core": "^7.26.0",
|
||||||
"@babel/plugin-proposal-class-properties": "7.14.5",
|
"@babel/plugin-proposal-class-properties": "^7.18.6",
|
||||||
"@babel/preset-react": "^7.26.3",
|
"@babel/preset-react": "^7.26.0",
|
||||||
"@pmmmwh/react-refresh-webpack-plugin": "^0.5.15",
|
"@pmmmwh/react-refresh-webpack-plugin": "^0.5.15",
|
||||||
"@tarojs/cli": "4.0.8",
|
"@tarojs/cli": "4.1.11",
|
||||||
"@tarojs/taro-loader": "4.0.8",
|
"@tarojs/taro-loader": "4.1.11",
|
||||||
"@tarojs/webpack5-runner": "4.0.8",
|
"@tarojs/webpack5-runner": "4.1.11",
|
||||||
"@types/node": "^18.19.68",
|
"@types/node": "^18.19.68",
|
||||||
"@types/react": "^18.3.18",
|
"@types/react": "^18.3.12",
|
||||||
"@types/webpack-env": "^1.18.5",
|
"@types/webpack-env": "^1.18.5",
|
||||||
"@typescript-eslint/eslint-plugin": "^6.21.0",
|
"@typescript-eslint/eslint-plugin": "^8.15.0",
|
||||||
"@typescript-eslint/parser": "^6.21.0",
|
"@typescript-eslint/parser": "^8.15.0",
|
||||||
"autoprefixer": "^10.4.20",
|
"autoprefixer": "^10.4.20",
|
||||||
"babel-plugin-import": "^1.13.8",
|
|
||||||
"babel-preset-taro": "4.0.8",
|
"babel-preset-taro": "4.0.8",
|
||||||
"eslint": "^8.57.1",
|
"eslint": "^8.56.0",
|
||||||
"eslint-config-taro": "4.0.8",
|
"eslint-config-taro": "4.0.8",
|
||||||
"eslint-plugin-import": "^2.31.0",
|
"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",
|
"eslint-plugin-react-hooks": "^4.6.2",
|
||||||
|
"mysql2": "^3.22.3",
|
||||||
"postcss": "^8.4.49",
|
"postcss": "^8.4.49",
|
||||||
"react-refresh": "^0.11.0",
|
"react-refresh": "^0.14.0",
|
||||||
"stylelint": "^14.16.1",
|
"stylelint": "^14.16.1",
|
||||||
"tailwindcss": "^3.4.17",
|
"tailwindcss": "3.4.17",
|
||||||
"ts-node": "^10.9.2",
|
"ts-node": "^10.9.2",
|
||||||
"tsconfig-paths-webpack-plugin": "^4.2.0",
|
"tsconfig-paths-webpack-plugin": "^4.2.0",
|
||||||
"typescript": "^5.7.2",
|
"typescript": "5.7.2",
|
||||||
|
"weapp-tailwindcss": "^4.12.0",
|
||||||
"webpack": "5.78.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 = {
|
module.exports = {
|
||||||
plugins: [
|
plugins: {
|
||||||
require('tailwindcss'),
|
tailwindcss: {},
|
||||||
require('autoprefixer'),
|
autoprefixer: {},
|
||||||
require('postcss-nested'),
|
'postcss-nested': {}
|
||||||
// 移除 WXSS 不兼容的 Tailwind CSS 规则
|
}
|
||||||
// 包括 .\!xxx(反斜杠转义 !)、space-x/y-*(:not + ~ 组合器)、divide-x/y-* 等
|
|
||||||
wxssIncompatiblePlugin()
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
|
|||||||
+33
-26
@@ -1,45 +1,52 @@
|
|||||||
{
|
{
|
||||||
"miniprogramRoot": "dist/",
|
"description": "websopy-taro 项目配置",
|
||||||
"projectname": "template-5",
|
|
||||||
"description": "网宿软件",
|
|
||||||
"appid": "wx541db955e7a62709",
|
|
||||||
"setting": {
|
"setting": {
|
||||||
"urlCheck": true,
|
"urlCheck": false,
|
||||||
"es6": false,
|
"es6": true,
|
||||||
"enhance": false,
|
"enhance": true,
|
||||||
"compileHotReLoad": false,
|
"postcss": true,
|
||||||
"postcss": false,
|
|
||||||
"preloadBackgroundData": false,
|
"preloadBackgroundData": false,
|
||||||
"minified": true,
|
"minified": true,
|
||||||
"newFeature": true,
|
"newFeature": true,
|
||||||
"autoAudits": false,
|
|
||||||
"coverView": true,
|
"coverView": true,
|
||||||
"showShadowRootInWxmlPanel": false,
|
"nodeModules": false,
|
||||||
|
"autoAudits": false,
|
||||||
|
"showShadowRootInWxmlPanel": true,
|
||||||
"scopeDataCheck": false,
|
"scopeDataCheck": false,
|
||||||
"useCompilerModule": false,
|
|
||||||
"compileWorklet": false,
|
|
||||||
"uglifyFileName": false,
|
"uglifyFileName": false,
|
||||||
|
"checkInvalidKey": true,
|
||||||
|
"checkSiteMap": true,
|
||||||
"uploadWithSourceMap": true,
|
"uploadWithSourceMap": true,
|
||||||
"packNpmManually": false,
|
"compileHotReLoad": true,
|
||||||
"packNpmRelationList": [],
|
"lazyloadPlaceholderEnable": false,
|
||||||
"minifyWXSS": true,
|
"useMultiFrameRuntime": true,
|
||||||
"minifyWXML": true,
|
"useApiHook": true,
|
||||||
"localPlugins": false,
|
"useApiHostProcess": true,
|
||||||
"disableUseStrict": false,
|
|
||||||
"useCompilerPlugins": false,
|
|
||||||
"condition": false,
|
|
||||||
"swc": false,
|
|
||||||
"disableSWC": true,
|
|
||||||
"babelSetting": {
|
"babelSetting": {
|
||||||
"ignore": [],
|
"ignore": [],
|
||||||
"disablePlugins": [],
|
"disablePlugins": [],
|
||||||
"outputPath": ""
|
"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",
|
"compileType": "miniprogram",
|
||||||
"simulatorType": "wechat",
|
"appid": "wx541db955e7a62709",
|
||||||
"simulatorPluginLibVersion": {},
|
"projectname": "赛联小助手",
|
||||||
"condition": {},
|
"condition": {},
|
||||||
|
"simulatorPluginLibVersion": {},
|
||||||
"packOptions": {
|
"packOptions": {
|
||||||
"ignore": [],
|
"ignore": [],
|
||||||
"include": []
|
"include": []
|
||||||
|
|||||||
@@ -1,76 +1,23 @@
|
|||||||
{
|
{
|
||||||
"libVersion": "3.15.2",
|
"libVersion": "3.16.1",
|
||||||
"projectname": "websopy-mp",
|
"projectname": "websopy-taro",
|
||||||
"condition": {
|
"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
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"setting": {
|
"setting": {
|
||||||
"urlCheck": true,
|
"urlCheck": false,
|
||||||
"coverView": true,
|
"coverView": true,
|
||||||
"lazyloadPlaceholderEnable": false,
|
"lazyloadPlaceholderEnable": false,
|
||||||
"skylineRenderEnable": false,
|
"skylineRenderEnable": false,
|
||||||
"preloadBackgroundData": false,
|
"preloadBackgroundData": false,
|
||||||
"autoAudits": false,
|
"autoAudits": false,
|
||||||
"useApiHook": true,
|
"useApiHook": true,
|
||||||
"showShadowRootInWxmlPanel": false,
|
"showShadowRootInWxmlPanel": true,
|
||||||
"useStaticServer": false,
|
"useStaticServer": false,
|
||||||
"useLanDebug": false,
|
"useLanDebug": false,
|
||||||
"showES6CompileOption": false,
|
"showES6CompileOption": false,
|
||||||
"compileHotReLoad": true,
|
"compileHotReLoad": true,
|
||||||
"checkInvalidKey": true,
|
"checkInvalidKey": true,
|
||||||
"ignoreDevUnusedFiles": 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,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
// API Key 管理(对齐 PC /developer/apikeys,基址走 websopy-api)
|
||||||
|
import { get, post, put, del } from '@/utils/request'
|
||||||
|
import { WebsopyBaseUrl } from '@/config/app'
|
||||||
|
|
||||||
|
const BASE = '/app/apikey'
|
||||||
|
|
||||||
|
export interface AppApiKey {
|
||||||
|
id: number
|
||||||
|
name: string
|
||||||
|
apiKey: string
|
||||||
|
keyPrefix?: string
|
||||||
|
status: number // 0正常 1禁用
|
||||||
|
scopes?: string
|
||||||
|
expireTime?: string
|
||||||
|
lastUsedAt?: string
|
||||||
|
usageCount?: number
|
||||||
|
remark?: string
|
||||||
|
createTime?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function pageAppApiKey(params: any) {
|
||||||
|
const res = await get<any>(BASE, params, { baseUrl: WebsopyBaseUrl })
|
||||||
|
if (res?.code === 0) return res.data
|
||||||
|
return Promise.reject(new Error(res?.message || '加载失败'))
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function createAppApiKey(data: any) {
|
||||||
|
const res = await post<any>(BASE, data, { baseUrl: WebsopyBaseUrl })
|
||||||
|
if (res?.code === 0) return res.data
|
||||||
|
return Promise.reject(new Error(res?.message || '创建失败'))
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function updateAppApiKeyStatus(id: number, status: number) {
|
||||||
|
const res = await put<any>(`${BASE}/${id}/status?status=${status}`, {}, { baseUrl: WebsopyBaseUrl })
|
||||||
|
if (res?.code === 0) return res.message
|
||||||
|
return Promise.reject(new Error(res?.message || '操作失败'))
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function removeAppApiKey(id: number) {
|
||||||
|
const res = await del<any>(`${BASE}/${id}`, {}, { baseUrl: WebsopyBaseUrl })
|
||||||
|
if (res?.code === 0) return res.message
|
||||||
|
return Promise.reject(new Error(res?.message || '删除失败'))
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getAppApiKeyStats() {
|
||||||
|
const res = await get<any>(`${BASE}/stats`, undefined, { baseUrl: WebsopyBaseUrl })
|
||||||
|
if (res?.code === 0) return res.data
|
||||||
|
return Promise.reject(new Error(res?.message || '加载失败'))
|
||||||
|
}
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
// 个性域名绑定(对齐 PC /admin/domain-bindings,基址走 websopy-api)
|
||||||
|
import { get, post } from '@/utils/request'
|
||||||
|
import { WebsopyBaseUrl } from '@/config/app'
|
||||||
|
|
||||||
|
const BASE = '/app/domain'
|
||||||
|
|
||||||
|
export interface AppDomain {
|
||||||
|
id?: number
|
||||||
|
tenantId?: number
|
||||||
|
productId?: number
|
||||||
|
domain?: string
|
||||||
|
targetIp?: string
|
||||||
|
recordType?: string
|
||||||
|
status?: number // 0待解析 1校验中 2已生效 3已拒绝 4已停用
|
||||||
|
dnsStatus?: number
|
||||||
|
dnsIp?: string
|
||||||
|
proxyConfigured?: number
|
||||||
|
rejectReason?: string
|
||||||
|
tenantName?: string
|
||||||
|
createTime?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 管理端列表 */
|
||||||
|
export async function pageAppDomain(params: any) {
|
||||||
|
const res = await get<any>(`${BASE}/admin/list`, params, { baseUrl: WebsopyBaseUrl })
|
||||||
|
if (res?.code === 0) return res.data
|
||||||
|
return Promise.reject(new Error(res?.message || '加载失败'))
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function verifyDomain(id: number) {
|
||||||
|
const res = await post<any>(`${BASE}/verify/${id}`, {}, { baseUrl: WebsopyBaseUrl })
|
||||||
|
if (res?.code === 0) return res.data
|
||||||
|
return Promise.reject(new Error(res?.message || '校验失败'))
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function setProxy(id: number) {
|
||||||
|
const res = await post<any>(`${BASE}/proxy/${id}`, {}, { baseUrl: WebsopyBaseUrl })
|
||||||
|
if (res?.code === 0) return res.data
|
||||||
|
return Promise.reject(new Error(res?.message || '操作失败'))
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function activeDomain(id: number) {
|
||||||
|
const res = await post<any>(`${BASE}/active/${id}`, {}, { baseUrl: WebsopyBaseUrl })
|
||||||
|
if (res?.code === 0) return res.data
|
||||||
|
return Promise.reject(new Error(res?.message || '操作失败'))
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function rejectDomain(id: number, reason: string) {
|
||||||
|
const res = await post<any>(`${BASE}/reject/${id}`, { reason }, { baseUrl: WebsopyBaseUrl })
|
||||||
|
if (res?.code === 0) return res.message
|
||||||
|
return Promise.reject(new Error(res?.message || '操作失败'))
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function disableDomain(id: number) {
|
||||||
|
const res = await post<any>(`${BASE}/disable/${id}`, {}, { baseUrl: WebsopyBaseUrl })
|
||||||
|
if (res?.code === 0) return res.message
|
||||||
|
return Promise.reject(new Error(res?.message || '操作失败'))
|
||||||
|
}
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,90 @@
|
|||||||
|
// 应用产品(对齐 PC /developer/apps 与 /admin 应用管理,基址走 websopy-api)
|
||||||
|
import { get, post, put, del } from '@/utils/request'
|
||||||
|
import { WebsopyBaseUrl } from '@/config/app'
|
||||||
|
|
||||||
|
const BASE = '/app/product'
|
||||||
|
|
||||||
|
export interface AppProduct {
|
||||||
|
productId?: number
|
||||||
|
productName?: string
|
||||||
|
productCode?: string
|
||||||
|
appType?: number
|
||||||
|
description?: string
|
||||||
|
logo?: string
|
||||||
|
publishStatus?: string
|
||||||
|
status?: number
|
||||||
|
adminUrl?: string
|
||||||
|
homeUrl?: string
|
||||||
|
createTime?: string
|
||||||
|
userId?: number
|
||||||
|
tenantId?: number
|
||||||
|
rejectReason?: string
|
||||||
|
[key: string]: any
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 我的应用(创建者) */
|
||||||
|
export async function getMyApps(params: any) {
|
||||||
|
const res = await get<any>(`${BASE}/my/page`, params, { baseUrl: WebsopyBaseUrl })
|
||||||
|
if (res?.code === 0) return res.data
|
||||||
|
return Promise.reject(new Error(res?.message || '加载失败'))
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 开发者应用列表 */
|
||||||
|
export async function getDeveloperApps(params?: any) {
|
||||||
|
const res = await get<any>(`${BASE}/list`, params, { baseUrl: WebsopyBaseUrl })
|
||||||
|
if (res?.code === 0) return res.data
|
||||||
|
return Promise.reject(new Error(res?.message || '加载失败'))
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 创建应用 */
|
||||||
|
export async function addAppProduct(data: any) {
|
||||||
|
const res = await post<any>(`${BASE}/create`, data, { baseUrl: WebsopyBaseUrl })
|
||||||
|
if (res?.code === 0) return res.data
|
||||||
|
return Promise.reject(new Error(res?.message || '创建失败'))
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 发布审核列表(管理端,publishStatus=pending_review) */
|
||||||
|
export async function pagePublishReviews(params: any) {
|
||||||
|
const res = await get<any>(`${BASE}/page`, { ...params, publishStatus: 'pending_review' }, { baseUrl: WebsopyBaseUrl })
|
||||||
|
if (res?.code === 0) return res.data
|
||||||
|
return Promise.reject(new Error(res?.message || '加载失败'))
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 全部应用(管理端) */
|
||||||
|
export async function pageAllApps(params: any) {
|
||||||
|
const res = await get<any>(`${BASE}/page`, params, { baseUrl: WebsopyBaseUrl })
|
||||||
|
if (res?.code === 0) return res.data
|
||||||
|
return Promise.reject(new Error(res?.message || '加载失败'))
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 更新应用 */
|
||||||
|
export async function updateAppProduct(data: any) {
|
||||||
|
const res = await put<any>(`${BASE}/update`, data, { baseUrl: WebsopyBaseUrl })
|
||||||
|
if (res?.code === 0) return res.data
|
||||||
|
return Promise.reject(new Error(res?.message || '更新失败'))
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 删除应用 */
|
||||||
|
export async function deleteAppProduct(id: number) {
|
||||||
|
const res = await del<any>(`${BASE}/delete/${id}`, {}, { baseUrl: WebsopyBaseUrl })
|
||||||
|
if (res?.code === 0) return res.data
|
||||||
|
return Promise.reject(new Error(res?.message || '删除失败'))
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 审核通过 */
|
||||||
|
export async function approvePublishReview(id: number) {
|
||||||
|
const res = await post<any>(`${BASE}/approve/${id}`, {}, { baseUrl: WebsopyBaseUrl })
|
||||||
|
if (res?.code === 0) return res.message
|
||||||
|
return Promise.reject(new Error(res?.message || '操作失败'))
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 审核拒绝 */
|
||||||
|
export async function rejectPublishReview(params: { productId: number; rejectReason: string }) {
|
||||||
|
const res = await post<any>(
|
||||||
|
`${BASE}/reject/${params.productId}?reason=${encodeURIComponent(params.rejectReason)}`,
|
||||||
|
{},
|
||||||
|
{ baseUrl: WebsopyBaseUrl }
|
||||||
|
)
|
||||||
|
if (res?.code === 0) return res.message
|
||||||
|
return Promise.reject(new Error(res?.message || '操作失败'))
|
||||||
|
}
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,106 @@
|
|||||||
|
// 开发者权限申请 & Git 账号(对齐 PC /developer/requests、/git 与 /admin/git-review,基址走 websopy-api)
|
||||||
|
import { get, post, put } from '@/utils/request'
|
||||||
|
import { WebsopyBaseUrl } from '@/config/app'
|
||||||
|
|
||||||
|
const BASE = '/app/developer'
|
||||||
|
|
||||||
|
export interface PermissionRequest {
|
||||||
|
id?: number
|
||||||
|
userId?: number
|
||||||
|
gitUsername?: string
|
||||||
|
repo?: string
|
||||||
|
repoName?: string
|
||||||
|
reason?: string
|
||||||
|
status?: string // pending / approved / rejected
|
||||||
|
createTime?: string
|
||||||
|
reviewedAt?: string
|
||||||
|
reviewerName?: string
|
||||||
|
rejectReason?: string
|
||||||
|
[key: string]: any
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface GitAccount {
|
||||||
|
id?: number
|
||||||
|
userId?: number
|
||||||
|
username?: string
|
||||||
|
email?: string
|
||||||
|
status?: string // pending / verified / rejected
|
||||||
|
verificationNote?: string
|
||||||
|
tenantId?: number
|
||||||
|
[key: string]: any
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============ 权限申请 ============
|
||||||
|
export async function pagePermissionRequests(params: any) {
|
||||||
|
const res = await get<any>(`${BASE}/permission-requests`, params, { baseUrl: WebsopyBaseUrl })
|
||||||
|
if (res?.code === 0) return res.data
|
||||||
|
return Promise.reject(new Error(res?.message || '加载失败'))
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getPermissionRequestStats() {
|
||||||
|
const res = await get<any>(`${BASE}/permission-requests/stats`, undefined, { baseUrl: WebsopyBaseUrl })
|
||||||
|
if (res?.code === 0) return res.data
|
||||||
|
return Promise.reject(new Error(res?.message || '加载失败'))
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getAvailableRepos() {
|
||||||
|
const res = await get<any>(`${BASE}/available-repos`, undefined, { baseUrl: WebsopyBaseUrl })
|
||||||
|
if (res?.code === 0) return res.data
|
||||||
|
return Promise.reject(new Error(res?.message || '加载失败'))
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function submitPermissionRequest(data: any) {
|
||||||
|
const res = await post<any>(`${BASE}/permission-requests`, data, { baseUrl: WebsopyBaseUrl })
|
||||||
|
if (res?.code === 0) return res.data
|
||||||
|
return Promise.reject(new Error(res?.message || '提交失败'))
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function approvePermissionRequest(id: number) {
|
||||||
|
const res = await put<any>(`${BASE}/permission-requests/${id}/approve`, {}, { baseUrl: WebsopyBaseUrl })
|
||||||
|
if (res?.code === 0) return res.message
|
||||||
|
return Promise.reject(new Error(res?.message || '操作失败'))
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function rejectPermissionRequest(id: number, reason: string) {
|
||||||
|
const res = await put<any>(`${BASE}/permission-requests/${id}/reject`, { reason }, { baseUrl: WebsopyBaseUrl })
|
||||||
|
if (res?.code === 0) return res.message
|
||||||
|
return Promise.reject(new Error(res?.message || '操作失败'))
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============ Git 账号 ============
|
||||||
|
export async function saveGitAccount(data: any) {
|
||||||
|
const res = await post<any>(`${BASE}/git-account`, data, { baseUrl: WebsopyBaseUrl })
|
||||||
|
if (res?.code === 0) return res.data
|
||||||
|
return Promise.reject(new Error(res?.message || '提交失败'))
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getGitAccountStatus() {
|
||||||
|
const res = await get<any>(`${BASE}/git-account/status`, undefined, { baseUrl: WebsopyBaseUrl })
|
||||||
|
if (res?.code === 0) return res.data
|
||||||
|
return Promise.reject(new Error(res?.message || '加载失败'))
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getGiteaInfo() {
|
||||||
|
const res = await get<any>(`${BASE}/gitea-info`, undefined, { baseUrl: WebsopyBaseUrl })
|
||||||
|
if (res?.code === 0) return res.data
|
||||||
|
return Promise.reject(new Error(res?.message || '加载失败'))
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============ 管理端 ============
|
||||||
|
export async function pageGitAccounts(params: any) {
|
||||||
|
const res = await get<any>(`${BASE}/git-accounts`, params, { baseUrl: WebsopyBaseUrl })
|
||||||
|
if (res?.code === 0) return res.data
|
||||||
|
return Promise.reject(new Error(res?.message || '加载失败'))
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function approveGitAccount(id: number) {
|
||||||
|
const res = await put<any>(`${BASE}/git-accounts/${id}/approve`, {}, { baseUrl: WebsopyBaseUrl })
|
||||||
|
if (res?.code === 0) return res.message
|
||||||
|
return Promise.reject(new Error(res?.message || '操作失败'))
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function rejectGitAccount(id: number, reason: string) {
|
||||||
|
const res = await put<any>(`${BASE}/git-accounts/${id}/reject`, { reason }, { baseUrl: WebsopyBaseUrl })
|
||||||
|
if (res?.code === 0) return res.message
|
||||||
|
return Promise.reject(new Error(res?.message || '操作失败'))
|
||||||
|
}
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,82 @@
|
|||||||
|
// 工单(对齐 PC /developer/tickets 与 /admin/tickets,基址走 websopy-api)
|
||||||
|
import { get, post } from '@/utils/request'
|
||||||
|
import { WebsopyBaseUrl } from '@/config/app'
|
||||||
|
|
||||||
|
const BASE = '/app/ticket'
|
||||||
|
|
||||||
|
export interface Ticket {
|
||||||
|
ticketId?: number
|
||||||
|
ticketNo?: string
|
||||||
|
title?: string
|
||||||
|
productId?: number
|
||||||
|
productName?: string
|
||||||
|
status?: string
|
||||||
|
priority?: string
|
||||||
|
category?: string
|
||||||
|
assigneeName?: string
|
||||||
|
submitUserName?: string
|
||||||
|
replyCount?: number
|
||||||
|
hasUnread?: boolean
|
||||||
|
createTime?: string
|
||||||
|
[key: string]: any
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getMyTickets(params: any) {
|
||||||
|
const res = await get<any>(`${BASE}/my`, params, { baseUrl: WebsopyBaseUrl })
|
||||||
|
if (res?.code === 0) return res.data
|
||||||
|
return Promise.reject(new Error(res?.message || '加载失败'))
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getAllTickets(params: any) {
|
||||||
|
const res = await get<any>(BASE, params, { baseUrl: WebsopyBaseUrl })
|
||||||
|
if (res?.code === 0) return res.data
|
||||||
|
return Promise.reject(new Error(res?.message || '加载失败'))
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getTicketDetail(id: number) {
|
||||||
|
const res = await get<any>(`${BASE}/${id}`, undefined, { baseUrl: WebsopyBaseUrl })
|
||||||
|
if (res?.code === 0) return res.data
|
||||||
|
return Promise.reject(new Error(res?.message || '加载失败'))
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getTicketReplies(id: number) {
|
||||||
|
const res = await get<any>(`${BASE}/${id}/replies`, undefined, { baseUrl: WebsopyBaseUrl })
|
||||||
|
if (res?.code === 0) return res.data
|
||||||
|
return Promise.reject(new Error(res?.message || '加载失败'))
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function submitTicket(data: any) {
|
||||||
|
const res = await post<any>(BASE, data, { baseUrl: WebsopyBaseUrl })
|
||||||
|
if (res?.code === 0) return res.data
|
||||||
|
return Promise.reject(new Error(res?.message || '提交失败'))
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function replyTicket(id: number, content: string) {
|
||||||
|
const res = await post<any>(`${BASE}/${id}/reply`, { content }, { baseUrl: WebsopyBaseUrl })
|
||||||
|
if (res?.code === 0) return res.data
|
||||||
|
return Promise.reject(new Error(res?.message || '回复失败'))
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function updateTicketStatus(id: number, status: string) {
|
||||||
|
const res = await post<any>(`${BASE}/${id}/status`, { status }, { baseUrl: WebsopyBaseUrl })
|
||||||
|
if (res?.code === 0) return res.message
|
||||||
|
return Promise.reject(new Error(res?.message || '操作失败'))
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function assignTicket(id: number, assigneeId: number) {
|
||||||
|
const res = await post<any>(`${BASE}/${id}/assign`, { assigneeId }, { baseUrl: WebsopyBaseUrl })
|
||||||
|
if (res?.code === 0) return res.message
|
||||||
|
return Promise.reject(new Error(res?.message || '操作失败'))
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getTicketStats() {
|
||||||
|
const res = await get<any>(`${BASE}/stats`, undefined, { baseUrl: WebsopyBaseUrl })
|
||||||
|
if (res?.code === 0) return res.data
|
||||||
|
return Promise.reject(new Error(res?.message || '加载失败'))
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getTechStaffList() {
|
||||||
|
const res = await get<any>(`${BASE}/staffs`, undefined, { baseUrl: WebsopyBaseUrl })
|
||||||
|
if (res?.code === 0) return res.data
|
||||||
|
return Promise.reject(new Error(res?.message || '加载失败'))
|
||||||
|
}
|
||||||
@@ -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',
|
|
||||||
})
|
|
||||||
}
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import request from '@/utils/request';
|
import cmsRequest from '@/utils/cmsRequest';
|
||||||
import type { ApiResult, PageResult } from '@/api';
|
import type { ApiResult, PageResult } from '@/api';
|
||||||
import type { CmsAd, CmsAdParam } from './model';
|
import type { CmsAd, CmsAdParam } from './model';
|
||||||
|
|
||||||
@@ -7,7 +7,7 @@ import type { CmsAd, CmsAdParam } from './model';
|
|||||||
* 分页查询广告位
|
* 分页查询广告位
|
||||||
*/
|
*/
|
||||||
export async function pageCmsAd(params: CmsAdParam) {
|
export async function pageCmsAd(params: CmsAdParam) {
|
||||||
const res = await request.get<ApiResult<PageResult<CmsAd>>>(
|
const res = await cmsRequest.get<ApiResult<PageResult<CmsAd>>>(
|
||||||
'/cms/cms-ad/page',
|
'/cms/cms-ad/page',
|
||||||
params
|
params
|
||||||
);
|
);
|
||||||
@@ -21,7 +21,7 @@ export async function pageCmsAd(params: CmsAdParam) {
|
|||||||
* 查询广告位列表
|
* 查询广告位列表
|
||||||
*/
|
*/
|
||||||
export async function listCmsAd(params?: CmsAdParam) {
|
export async function listCmsAd(params?: CmsAdParam) {
|
||||||
const res = await request.get<ApiResult<CmsAd[]>>(
|
const res = await cmsRequest.get<ApiResult<CmsAd[]>>(
|
||||||
'/cms/cms-ad',
|
'/cms/cms-ad',
|
||||||
params
|
params
|
||||||
);
|
);
|
||||||
@@ -35,7 +35,7 @@ export async function listCmsAd(params?: CmsAdParam) {
|
|||||||
* 添加广告位
|
* 添加广告位
|
||||||
*/
|
*/
|
||||||
export async function addCmsAd(data: CmsAd) {
|
export async function addCmsAd(data: CmsAd) {
|
||||||
const res = await request.post<ApiResult<unknown>>(
|
const res = await cmsRequest.post<ApiResult<unknown>>(
|
||||||
'/cms/cms-ad',
|
'/cms/cms-ad',
|
||||||
data
|
data
|
||||||
);
|
);
|
||||||
@@ -49,7 +49,7 @@ export async function addCmsAd(data: CmsAd) {
|
|||||||
* 修改广告位
|
* 修改广告位
|
||||||
*/
|
*/
|
||||||
export async function updateCmsAd(data: CmsAd) {
|
export async function updateCmsAd(data: CmsAd) {
|
||||||
const res = await request.put<ApiResult<unknown>>(
|
const res = await cmsRequest.put<ApiResult<unknown>>(
|
||||||
'/cms/cms-ad',
|
'/cms/cms-ad',
|
||||||
data
|
data
|
||||||
);
|
);
|
||||||
@@ -63,7 +63,7 @@ export async function updateCmsAd(data: CmsAd) {
|
|||||||
* 删除广告位
|
* 删除广告位
|
||||||
*/
|
*/
|
||||||
export async function removeCmsAd(id?: number) {
|
export async function removeCmsAd(id?: number) {
|
||||||
const res = await request.del<ApiResult<unknown>>(
|
const res = await cmsRequest.del<ApiResult<unknown>>(
|
||||||
'/cms/cms-ad/' + id
|
'/cms/cms-ad/' + id
|
||||||
);
|
);
|
||||||
if (res.code === 0) {
|
if (res.code === 0) {
|
||||||
@@ -76,7 +76,7 @@ export async function removeCmsAd(id?: number) {
|
|||||||
* 批量删除广告位
|
* 批量删除广告位
|
||||||
*/
|
*/
|
||||||
export async function removeBatchCmsAd(data: (number | undefined)[]) {
|
export async function removeBatchCmsAd(data: (number | undefined)[]) {
|
||||||
const res = await request.del<ApiResult<unknown>>(
|
const res = await cmsRequest.del<ApiResult<unknown>>(
|
||||||
'/cms/cms-ad/batch',
|
'/cms/cms-ad/batch',
|
||||||
{
|
{
|
||||||
data
|
data
|
||||||
@@ -92,7 +92,7 @@ export async function removeBatchCmsAd(data: (number | undefined)[]) {
|
|||||||
* 根据id查询广告位
|
* 根据id查询广告位
|
||||||
*/
|
*/
|
||||||
export async function getCmsAd(id: number) {
|
export async function getCmsAd(id: number) {
|
||||||
const res = await request.get<ApiResult<CmsAd>>(
|
const res = await cmsRequest.get<ApiResult<CmsAd>>(
|
||||||
'/cms/cms-ad/' + id
|
'/cms/cms-ad/' + id
|
||||||
);
|
);
|
||||||
if (res.code === 0 && res.data) {
|
if (res.code === 0 && res.data) {
|
||||||
@@ -105,7 +105,7 @@ export async function getCmsAd(id: number) {
|
|||||||
* 根据code查询广告位
|
* 根据code查询广告位
|
||||||
*/
|
*/
|
||||||
export async function getCmsAdByCode(code: string) {
|
export async function getCmsAdByCode(code: string) {
|
||||||
const res = await request.get<ApiResult<CmsAd>>(
|
const res = await cmsRequest.get<ApiResult<CmsAd>>(
|
||||||
'/cms/cms-ad/getByCode/' + code
|
'/cms/cms-ad/getByCode/' + code
|
||||||
);
|
);
|
||||||
if (res.code === 0 && res.data) {
|
if (res.code === 0 && res.data) {
|
||||||
|
|||||||
@@ -55,5 +55,6 @@ export interface CmsAdParam extends PageParam {
|
|||||||
adType?: string;
|
adType?: string;
|
||||||
pageId?: number;
|
pageId?: number;
|
||||||
pageName?: string;
|
pageName?: string;
|
||||||
|
status?: number;
|
||||||
keywords?: string;
|
keywords?: string;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import request from '@/utils/request';
|
import cmsRequest from '@/utils/cmsRequest';
|
||||||
import type { ApiResult, PageResult } from '@/api';
|
import type { ApiResult, PageResult } from '@/api';
|
||||||
import type { CmsAdRecord, CmsAdRecordParam } from './model';
|
import type { CmsAdRecord, CmsAdRecordParam } from './model';
|
||||||
|
|
||||||
@@ -7,7 +7,7 @@ import type { CmsAdRecord, CmsAdRecordParam } from './model';
|
|||||||
* 分页查询广告图片
|
* 分页查询广告图片
|
||||||
*/
|
*/
|
||||||
export async function pageCmsAdRecord(params: CmsAdRecordParam) {
|
export async function pageCmsAdRecord(params: CmsAdRecordParam) {
|
||||||
const res = await request.get<ApiResult<PageResult<CmsAdRecord>>>(
|
const res = await cmsRequest.get<ApiResult<PageResult<CmsAdRecord>>>(
|
||||||
'/cms/cms-ad-record/page',
|
'/cms/cms-ad-record/page',
|
||||||
{
|
{
|
||||||
params
|
params
|
||||||
@@ -23,7 +23,7 @@ export async function pageCmsAdRecord(params: CmsAdRecordParam) {
|
|||||||
* 查询广告图片列表
|
* 查询广告图片列表
|
||||||
*/
|
*/
|
||||||
export async function listCmsAdRecord(params?: CmsAdRecordParam) {
|
export async function listCmsAdRecord(params?: CmsAdRecordParam) {
|
||||||
const res = await request.get<ApiResult<CmsAdRecord[]>>(
|
const res = await cmsRequest.get<ApiResult<CmsAdRecord[]>>(
|
||||||
'/cms/cms-ad-record',
|
'/cms/cms-ad-record',
|
||||||
{
|
{
|
||||||
params
|
params
|
||||||
@@ -39,7 +39,7 @@ export async function listCmsAdRecord(params?: CmsAdRecordParam) {
|
|||||||
* 添加广告图片
|
* 添加广告图片
|
||||||
*/
|
*/
|
||||||
export async function addCmsAdRecord(data: CmsAdRecord) {
|
export async function addCmsAdRecord(data: CmsAdRecord) {
|
||||||
const res = await request.post<ApiResult<unknown>>(
|
const res = await cmsRequest.post<ApiResult<unknown>>(
|
||||||
'/cms/cms-ad-record',
|
'/cms/cms-ad-record',
|
||||||
data
|
data
|
||||||
);
|
);
|
||||||
@@ -53,7 +53,7 @@ export async function addCmsAdRecord(data: CmsAdRecord) {
|
|||||||
* 修改广告图片
|
* 修改广告图片
|
||||||
*/
|
*/
|
||||||
export async function updateCmsAdRecord(data: CmsAdRecord) {
|
export async function updateCmsAdRecord(data: CmsAdRecord) {
|
||||||
const res = await request.put<ApiResult<unknown>>(
|
const res = await cmsRequest.put<ApiResult<unknown>>(
|
||||||
'/cms/cms-ad-record',
|
'/cms/cms-ad-record',
|
||||||
data
|
data
|
||||||
);
|
);
|
||||||
@@ -67,7 +67,7 @@ export async function updateCmsAdRecord(data: CmsAdRecord) {
|
|||||||
* 删除广告图片
|
* 删除广告图片
|
||||||
*/
|
*/
|
||||||
export async function removeCmsAdRecord(id?: number) {
|
export async function removeCmsAdRecord(id?: number) {
|
||||||
const res = await request.del<ApiResult<unknown>>(
|
const res = await cmsRequest.del<ApiResult<unknown>>(
|
||||||
'/cms/cms-ad-record/' + id
|
'/cms/cms-ad-record/' + id
|
||||||
);
|
);
|
||||||
if (res.code === 0) {
|
if (res.code === 0) {
|
||||||
@@ -80,7 +80,7 @@ export async function removeCmsAdRecord(id?: number) {
|
|||||||
* 批量删除广告图片
|
* 批量删除广告图片
|
||||||
*/
|
*/
|
||||||
export async function removeBatchCmsAdRecord(data: (number | undefined)[]) {
|
export async function removeBatchCmsAdRecord(data: (number | undefined)[]) {
|
||||||
const res = await request.del<ApiResult<unknown>>(
|
const res = await cmsRequest.del<ApiResult<unknown>>(
|
||||||
'/cms/cms-ad-record/batch',
|
'/cms/cms-ad-record/batch',
|
||||||
{
|
{
|
||||||
data
|
data
|
||||||
@@ -96,7 +96,7 @@ export async function removeBatchCmsAdRecord(data: (number | undefined)[]) {
|
|||||||
* 根据id查询广告图片
|
* 根据id查询广告图片
|
||||||
*/
|
*/
|
||||||
export async function getCmsAdRecord(id: number) {
|
export async function getCmsAdRecord(id: number) {
|
||||||
const res = await request.get<ApiResult<CmsAdRecord>>(
|
const res = await cmsRequest.get<ApiResult<CmsAdRecord>>(
|
||||||
'/cms/cms-ad-record/' + id
|
'/cms/cms-ad-record/' + id
|
||||||
);
|
);
|
||||||
if (res.code === 0 && res.data) {
|
if (res.code === 0 && res.data) {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import request from '@/utils/request';
|
import cmsRequest from '@/utils/cmsRequest';
|
||||||
import type {ApiResult, PageResult} from '@/api';
|
import type {ApiResult, PageResult} from '@/api';
|
||||||
import type {CmsArticle, CmsArticleParam} from './model';
|
import type {CmsArticle, CmsArticleParam} from './model';
|
||||||
|
|
||||||
@@ -6,7 +6,7 @@ import type {CmsArticle, CmsArticleParam} from './model';
|
|||||||
* 分页查询文章
|
* 分页查询文章
|
||||||
*/
|
*/
|
||||||
export async function pageCmsArticle(params: CmsArticleParam) {
|
export async function pageCmsArticle(params: CmsArticleParam) {
|
||||||
const res = await request.get<ApiResult<PageResult<CmsArticle>>>(
|
const res = await cmsRequest.get<ApiResult<PageResult<CmsArticle>>>(
|
||||||
'/cms/cms-article/page',
|
'/cms/cms-article/page',
|
||||||
params
|
params
|
||||||
);
|
);
|
||||||
@@ -20,7 +20,7 @@ export async function pageCmsArticle(params: CmsArticleParam) {
|
|||||||
* 查询文章列表
|
* 查询文章列表
|
||||||
*/
|
*/
|
||||||
export async function listCmsArticle(params?: CmsArticleParam) {
|
export async function listCmsArticle(params?: CmsArticleParam) {
|
||||||
const res = await request.get<ApiResult<CmsArticle[]>>(
|
const res = await cmsRequest.get<ApiResult<CmsArticle[]>>(
|
||||||
'/cms/cms-article',
|
'/cms/cms-article',
|
||||||
params
|
params
|
||||||
);
|
);
|
||||||
@@ -34,7 +34,7 @@ export async function listCmsArticle(params?: CmsArticleParam) {
|
|||||||
* 添加文章
|
* 添加文章
|
||||||
*/
|
*/
|
||||||
export async function addCmsArticle(data: CmsArticle) {
|
export async function addCmsArticle(data: CmsArticle) {
|
||||||
const res = await request.post<ApiResult<unknown>>(
|
const res = await cmsRequest.post<ApiResult<unknown>>(
|
||||||
'/cms/cms-article',
|
'/cms/cms-article',
|
||||||
data
|
data
|
||||||
);
|
);
|
||||||
@@ -48,7 +48,7 @@ export async function addCmsArticle(data: CmsArticle) {
|
|||||||
* 修改文章
|
* 修改文章
|
||||||
*/
|
*/
|
||||||
export async function updateCmsArticle(data: CmsArticle) {
|
export async function updateCmsArticle(data: CmsArticle) {
|
||||||
const res = await request.put<ApiResult<unknown>>(
|
const res = await cmsRequest.put<ApiResult<unknown>>(
|
||||||
'/cms/cms-article',
|
'/cms/cms-article',
|
||||||
data
|
data
|
||||||
);
|
);
|
||||||
@@ -62,7 +62,7 @@ export async function updateCmsArticle(data: CmsArticle) {
|
|||||||
* 删除文章
|
* 删除文章
|
||||||
*/
|
*/
|
||||||
export async function removeCmsArticle(id?: number) {
|
export async function removeCmsArticle(id?: number) {
|
||||||
const res = await request.del<ApiResult<unknown>>(
|
const res = await cmsRequest.del<ApiResult<unknown>>(
|
||||||
'/cms/cms-article/' + id
|
'/cms/cms-article/' + id
|
||||||
);
|
);
|
||||||
if (res.code === 0) {
|
if (res.code === 0) {
|
||||||
@@ -75,7 +75,7 @@ export async function removeCmsArticle(id?: number) {
|
|||||||
* 批量删除文章
|
* 批量删除文章
|
||||||
*/
|
*/
|
||||||
export async function removeBatchCmsArticle(data: (number | undefined)[]) {
|
export async function removeBatchCmsArticle(data: (number | undefined)[]) {
|
||||||
const res = await request.del<ApiResult<unknown>>(
|
const res = await cmsRequest.del<ApiResult<unknown>>(
|
||||||
'/cms/cms-article/batch',
|
'/cms/cms-article/batch',
|
||||||
{
|
{
|
||||||
data
|
data
|
||||||
@@ -91,7 +91,7 @@ export async function removeBatchCmsArticle(data: (number | undefined)[]) {
|
|||||||
* 根据id查询文章
|
* 根据id查询文章
|
||||||
*/
|
*/
|
||||||
export async function getCmsArticle(id: number) {
|
export async function getCmsArticle(id: number) {
|
||||||
const res = await request.get<ApiResult<CmsArticle>>(
|
const res = await cmsRequest.get<ApiResult<CmsArticle>>(
|
||||||
'/cms/cms-article/' + id
|
'/cms/cms-article/' + id
|
||||||
);
|
);
|
||||||
if (res.code === 0 && res.data) {
|
if (res.code === 0 && res.data) {
|
||||||
@@ -101,7 +101,7 @@ export async function getCmsArticle(id: number) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function getCount(params?: CmsArticleParam) {
|
export async function getCount(params?: CmsArticleParam) {
|
||||||
const res = await request.get<ApiResult<unknown>>('/cms/cms-article/data', {
|
const res = await cmsRequest.get<ApiResult<unknown>>('/cms/cms-article/data', {
|
||||||
params
|
params
|
||||||
});
|
});
|
||||||
if (res.code === 0) {
|
if (res.code === 0) {
|
||||||
@@ -115,7 +115,7 @@ export async function getCount(params?: CmsArticleParam) {
|
|||||||
* @param params
|
* @param params
|
||||||
*/
|
*/
|
||||||
export async function getPrevious(params?: CmsArticleParam) {
|
export async function getPrevious(params?: CmsArticleParam) {
|
||||||
const res = await request.get<ApiResult<CmsArticle>>(
|
const res = await cmsRequest.get<ApiResult<CmsArticle>>(
|
||||||
'/cms/cms-article/getPrevious/' + params?.articleId,
|
'/cms/cms-article/getPrevious/' + params?.articleId,
|
||||||
{
|
{
|
||||||
params
|
params
|
||||||
@@ -132,7 +132,7 @@ export async function getPrevious(params?: CmsArticleParam) {
|
|||||||
* @param params
|
* @param params
|
||||||
*/
|
*/
|
||||||
export async function getNext(params?: CmsArticleParam) {
|
export async function getNext(params?: CmsArticleParam) {
|
||||||
const res = await request.get<ApiResult<CmsArticle>>(
|
const res = await cmsRequest.get<ApiResult<CmsArticle>>(
|
||||||
'/cms/cms-article/getNext/' + params?.articleId,
|
'/cms/cms-article/getNext/' + params?.articleId,
|
||||||
{
|
{
|
||||||
params
|
params
|
||||||
@@ -149,7 +149,7 @@ export async function getNext(params?: CmsArticleParam) {
|
|||||||
* @param params
|
* @param params
|
||||||
*/
|
*/
|
||||||
export async function checkArticlePassword(params?: CmsArticleParam) {
|
export async function checkArticlePassword(params?: CmsArticleParam) {
|
||||||
const res = await request.get<ApiResult<unknown>>(
|
const res = await cmsRequest.get<ApiResult<unknown>>(
|
||||||
'/cms/cms-article/checkArticlePassword',
|
'/cms/cms-article/checkArticlePassword',
|
||||||
{
|
{
|
||||||
params
|
params
|
||||||
@@ -162,7 +162,7 @@ export async function checkArticlePassword(params?: CmsArticleParam) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function findTags(params?: CmsArticleParam) {
|
export async function findTags(params?: CmsArticleParam) {
|
||||||
const res = await request.get<ApiResult<CmsArticle[]>>(
|
const res = await cmsRequest.get<ApiResult<CmsArticle[]>>(
|
||||||
'/cms/cms-article/findTags',
|
'/cms/cms-article/findTags',
|
||||||
{
|
{
|
||||||
params
|
params
|
||||||
@@ -175,7 +175,7 @@ export async function findTags(params?: CmsArticleParam) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function pageTags(params?: CmsArticleParam) {
|
export async function pageTags(params?: CmsArticleParam) {
|
||||||
const res = await request.get<ApiResult<CmsArticle[]>>(
|
const res = await cmsRequest.get<ApiResult<CmsArticle[]>>(
|
||||||
'/cms/cms-article/pageTags',
|
'/cms/cms-article/pageTags',
|
||||||
{
|
{
|
||||||
params
|
params
|
||||||
@@ -192,7 +192,7 @@ export async function pageTags(params?: CmsArticleParam) {
|
|||||||
* @param params
|
* @param params
|
||||||
*/
|
*/
|
||||||
export async function getByIds(params?: CmsArticleParam) {
|
export async function getByIds(params?: CmsArticleParam) {
|
||||||
const res = await request.get<ApiResult<CmsArticle[]>>(
|
const res = await cmsRequest.get<ApiResult<CmsArticle[]>>(
|
||||||
'/cms/cms-article/getByIds',
|
'/cms/cms-article/getByIds',
|
||||||
{
|
{
|
||||||
params
|
params
|
||||||
@@ -208,7 +208,7 @@ export async function getByIds(params?: CmsArticleParam) {
|
|||||||
* 根据code查询文章
|
* 根据code查询文章
|
||||||
*/
|
*/
|
||||||
export async function getByCode(code: string) {
|
export async function getByCode(code: string) {
|
||||||
const res = await request.get<ApiResult<CmsArticle>>(
|
const res = await cmsRequest.get<ApiResult<CmsArticle>>(
|
||||||
'/cms/cms-article/getByCode/' + code
|
'/cms/cms-article/getByCode/' + code
|
||||||
);
|
);
|
||||||
if (res.code === 0 && res.data) {
|
if (res.code === 0 && res.data) {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import request from '@/utils/request';
|
import cmsRequest from '@/utils/cmsRequest';
|
||||||
import type { ApiResult, PageResult } from '@/api';
|
import type { ApiResult, PageResult } from '@/api';
|
||||||
import type { CmsArticleCategory, CmsArticleCategoryParam } from './model';
|
import type { CmsArticleCategory, CmsArticleCategoryParam } from './model';
|
||||||
|
|
||||||
@@ -7,7 +7,7 @@ import type { CmsArticleCategory, CmsArticleCategoryParam } from './model';
|
|||||||
* 分页查询文章分类表
|
* 分页查询文章分类表
|
||||||
*/
|
*/
|
||||||
export async function pageCmsArticleCategory(params: CmsArticleCategoryParam) {
|
export async function pageCmsArticleCategory(params: CmsArticleCategoryParam) {
|
||||||
const res = await request.get<ApiResult<PageResult<CmsArticleCategory>>>(
|
const res = await cmsRequest.get<ApiResult<PageResult<CmsArticleCategory>>>(
|
||||||
'/cms/cms-article-category/page',
|
'/cms/cms-article-category/page',
|
||||||
{
|
{
|
||||||
params
|
params
|
||||||
@@ -23,7 +23,7 @@ export async function pageCmsArticleCategory(params: CmsArticleCategoryParam) {
|
|||||||
* 查询文章分类表列表
|
* 查询文章分类表列表
|
||||||
*/
|
*/
|
||||||
export async function listCmsArticleCategory(params?: CmsArticleCategoryParam) {
|
export async function listCmsArticleCategory(params?: CmsArticleCategoryParam) {
|
||||||
const res = await request.get<ApiResult<CmsArticleCategory[]>>(
|
const res = await cmsRequest.get<ApiResult<CmsArticleCategory[]>>(
|
||||||
'/cms/cms-article-category',
|
'/cms/cms-article-category',
|
||||||
{
|
{
|
||||||
params
|
params
|
||||||
@@ -39,7 +39,7 @@ export async function listCmsArticleCategory(params?: CmsArticleCategoryParam) {
|
|||||||
* 添加文章分类表
|
* 添加文章分类表
|
||||||
*/
|
*/
|
||||||
export async function addCmsArticleCategory(data: CmsArticleCategory) {
|
export async function addCmsArticleCategory(data: CmsArticleCategory) {
|
||||||
const res = await request.post<ApiResult<unknown>>(
|
const res = await cmsRequest.post<ApiResult<unknown>>(
|
||||||
'/cms/cms-article-category',
|
'/cms/cms-article-category',
|
||||||
data
|
data
|
||||||
);
|
);
|
||||||
@@ -53,7 +53,7 @@ export async function addCmsArticleCategory(data: CmsArticleCategory) {
|
|||||||
* 修改文章分类表
|
* 修改文章分类表
|
||||||
*/
|
*/
|
||||||
export async function updateCmsArticleCategory(data: CmsArticleCategory) {
|
export async function updateCmsArticleCategory(data: CmsArticleCategory) {
|
||||||
const res = await request.put<ApiResult<unknown>>(
|
const res = await cmsRequest.put<ApiResult<unknown>>(
|
||||||
'/cms/cms-article-category',
|
'/cms/cms-article-category',
|
||||||
data
|
data
|
||||||
);
|
);
|
||||||
@@ -67,7 +67,7 @@ export async function updateCmsArticleCategory(data: CmsArticleCategory) {
|
|||||||
* 删除文章分类表
|
* 删除文章分类表
|
||||||
*/
|
*/
|
||||||
export async function removeCmsArticleCategory(id?: number) {
|
export async function removeCmsArticleCategory(id?: number) {
|
||||||
const res = await request.del<ApiResult<unknown>>(
|
const res = await cmsRequest.del<ApiResult<unknown>>(
|
||||||
'/cms/cms-article-category/' + id
|
'/cms/cms-article-category/' + id
|
||||||
);
|
);
|
||||||
if (res.code === 0) {
|
if (res.code === 0) {
|
||||||
@@ -82,7 +82,7 @@ export async function removeCmsArticleCategory(id?: number) {
|
|||||||
export async function removeBatchCmsArticleCategory(
|
export async function removeBatchCmsArticleCategory(
|
||||||
data: (number | undefined)[]
|
data: (number | undefined)[]
|
||||||
) {
|
) {
|
||||||
const res = await request.del<ApiResult<unknown>>(
|
const res = await cmsRequest.del<ApiResult<unknown>>(
|
||||||
'/cms/cms-article-category/batch',
|
'/cms/cms-article-category/batch',
|
||||||
{
|
{
|
||||||
data
|
data
|
||||||
@@ -98,7 +98,7 @@ export async function removeBatchCmsArticleCategory(
|
|||||||
* 根据id查询文章分类表
|
* 根据id查询文章分类表
|
||||||
*/
|
*/
|
||||||
export async function getCmsArticleCategory(id: number) {
|
export async function getCmsArticleCategory(id: number) {
|
||||||
const res = await request.get<ApiResult<CmsArticleCategory>>(
|
const res = await cmsRequest.get<ApiResult<CmsArticleCategory>>(
|
||||||
'/cms/cms-article-category/' + id
|
'/cms/cms-article-category/' + id
|
||||||
);
|
);
|
||||||
if (res.code === 0 && res.data) {
|
if (res.code === 0 && res.data) {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import request from '@/utils/request';
|
import cmsRequest from '@/utils/cmsRequest';
|
||||||
import type { ApiResult, PageResult } from '@/api';
|
import type { ApiResult, PageResult } from '@/api';
|
||||||
import type { CmsArticleComment, CmsArticleCommentParam } from './model';
|
import type { CmsArticleComment, CmsArticleCommentParam } from './model';
|
||||||
|
|
||||||
@@ -7,7 +7,7 @@ import type { CmsArticleComment, CmsArticleCommentParam } from './model';
|
|||||||
* 分页查询文章评论表
|
* 分页查询文章评论表
|
||||||
*/
|
*/
|
||||||
export async function pageCmsArticleComment(params: CmsArticleCommentParam) {
|
export async function pageCmsArticleComment(params: CmsArticleCommentParam) {
|
||||||
const res = await request.get<ApiResult<PageResult<CmsArticleComment>>>(
|
const res = await cmsRequest.get<ApiResult<PageResult<CmsArticleComment>>>(
|
||||||
'/cms/cms-article-comment/page',
|
'/cms/cms-article-comment/page',
|
||||||
{
|
{
|
||||||
params
|
params
|
||||||
@@ -23,7 +23,7 @@ export async function pageCmsArticleComment(params: CmsArticleCommentParam) {
|
|||||||
* 查询文章评论表列表
|
* 查询文章评论表列表
|
||||||
*/
|
*/
|
||||||
export async function listCmsArticleComment(params?: CmsArticleCommentParam) {
|
export async function listCmsArticleComment(params?: CmsArticleCommentParam) {
|
||||||
const res = await request.get<ApiResult<CmsArticleComment[]>>(
|
const res = await cmsRequest.get<ApiResult<CmsArticleComment[]>>(
|
||||||
'/cms/cms-article-comment',
|
'/cms/cms-article-comment',
|
||||||
{
|
{
|
||||||
params
|
params
|
||||||
@@ -39,7 +39,7 @@ export async function listCmsArticleComment(params?: CmsArticleCommentParam) {
|
|||||||
* 添加文章评论表
|
* 添加文章评论表
|
||||||
*/
|
*/
|
||||||
export async function addCmsArticleComment(data: CmsArticleComment) {
|
export async function addCmsArticleComment(data: CmsArticleComment) {
|
||||||
const res = await request.post<ApiResult<unknown>>(
|
const res = await cmsRequest.post<ApiResult<unknown>>(
|
||||||
'/cms/cms-article-comment',
|
'/cms/cms-article-comment',
|
||||||
data
|
data
|
||||||
);
|
);
|
||||||
@@ -53,7 +53,7 @@ export async function addCmsArticleComment(data: CmsArticleComment) {
|
|||||||
* 修改文章评论表
|
* 修改文章评论表
|
||||||
*/
|
*/
|
||||||
export async function updateCmsArticleComment(data: CmsArticleComment) {
|
export async function updateCmsArticleComment(data: CmsArticleComment) {
|
||||||
const res = await request.put<ApiResult<unknown>>(
|
const res = await cmsRequest.put<ApiResult<unknown>>(
|
||||||
'/cms/cms-article-comment',
|
'/cms/cms-article-comment',
|
||||||
data
|
data
|
||||||
);
|
);
|
||||||
@@ -67,7 +67,7 @@ export async function updateCmsArticleComment(data: CmsArticleComment) {
|
|||||||
* 删除文章评论表
|
* 删除文章评论表
|
||||||
*/
|
*/
|
||||||
export async function removeCmsArticleComment(id?: number) {
|
export async function removeCmsArticleComment(id?: number) {
|
||||||
const res = await request.del<ApiResult<unknown>>(
|
const res = await cmsRequest.del<ApiResult<unknown>>(
|
||||||
'/cms/cms-article-comment/' + id
|
'/cms/cms-article-comment/' + id
|
||||||
);
|
);
|
||||||
if (res.code === 0) {
|
if (res.code === 0) {
|
||||||
@@ -80,7 +80,7 @@ export async function removeCmsArticleComment(id?: number) {
|
|||||||
* 批量删除文章评论表
|
* 批量删除文章评论表
|
||||||
*/
|
*/
|
||||||
export async function removeBatchCmsArticleComment(data: (number | undefined)[]) {
|
export async function removeBatchCmsArticleComment(data: (number | undefined)[]) {
|
||||||
const res = await request.del<ApiResult<unknown>>(
|
const res = await cmsRequest.del<ApiResult<unknown>>(
|
||||||
'/cms/cms-article-comment/batch',
|
'/cms/cms-article-comment/batch',
|
||||||
{
|
{
|
||||||
data
|
data
|
||||||
@@ -96,7 +96,7 @@ export async function removeBatchCmsArticleComment(data: (number | undefined)[])
|
|||||||
* 根据id查询文章评论表
|
* 根据id查询文章评论表
|
||||||
*/
|
*/
|
||||||
export async function getCmsArticleComment(id: number) {
|
export async function getCmsArticleComment(id: number) {
|
||||||
const res = await request.get<ApiResult<CmsArticleComment>>(
|
const res = await cmsRequest.get<ApiResult<CmsArticleComment>>(
|
||||||
'/cms/cms-article-comment/' + id
|
'/cms/cms-article-comment/' + id
|
||||||
);
|
);
|
||||||
if (res.code === 0 && res.data) {
|
if (res.code === 0 && res.data) {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import request from '@/utils/request';
|
import cmsRequest from '@/utils/cmsRequest';
|
||||||
import type { ApiResult, PageResult } from '@/api';
|
import type { ApiResult, PageResult } from '@/api';
|
||||||
import type { CmsArticleContent, CmsArticleContentParam } from './model';
|
import type { CmsArticleContent, CmsArticleContentParam } from './model';
|
||||||
|
|
||||||
@@ -7,7 +7,7 @@ import type { CmsArticleContent, CmsArticleContentParam } from './model';
|
|||||||
* 分页查询文章记录表
|
* 分页查询文章记录表
|
||||||
*/
|
*/
|
||||||
export async function pageCmsArticleContent(params: CmsArticleContentParam) {
|
export async function pageCmsArticleContent(params: CmsArticleContentParam) {
|
||||||
const res = await request.get<ApiResult<PageResult<CmsArticleContent>>>(
|
const res = await cmsRequest.get<ApiResult<PageResult<CmsArticleContent>>>(
|
||||||
'/cms/cms-article-content/page',
|
'/cms/cms-article-content/page',
|
||||||
{
|
{
|
||||||
params
|
params
|
||||||
@@ -23,7 +23,7 @@ export async function pageCmsArticleContent(params: CmsArticleContentParam) {
|
|||||||
* 查询文章记录表列表
|
* 查询文章记录表列表
|
||||||
*/
|
*/
|
||||||
export async function listCmsArticleContent(params?: CmsArticleContentParam) {
|
export async function listCmsArticleContent(params?: CmsArticleContentParam) {
|
||||||
const res = await request.get<ApiResult<CmsArticleContent[]>>(
|
const res = await cmsRequest.get<ApiResult<CmsArticleContent[]>>(
|
||||||
'/cms/cms-article-content',
|
'/cms/cms-article-content',
|
||||||
{
|
{
|
||||||
params
|
params
|
||||||
@@ -39,7 +39,7 @@ export async function listCmsArticleContent(params?: CmsArticleContentParam) {
|
|||||||
* 添加文章记录表
|
* 添加文章记录表
|
||||||
*/
|
*/
|
||||||
export async function addCmsArticleContent(data: CmsArticleContent) {
|
export async function addCmsArticleContent(data: CmsArticleContent) {
|
||||||
const res = await request.post<ApiResult<unknown>>(
|
const res = await cmsRequest.post<ApiResult<unknown>>(
|
||||||
'/cms/cms-article-content',
|
'/cms/cms-article-content',
|
||||||
data
|
data
|
||||||
);
|
);
|
||||||
@@ -53,7 +53,7 @@ export async function addCmsArticleContent(data: CmsArticleContent) {
|
|||||||
* 修改文章记录表
|
* 修改文章记录表
|
||||||
*/
|
*/
|
||||||
export async function updateCmsArticleContent(data: CmsArticleContent) {
|
export async function updateCmsArticleContent(data: CmsArticleContent) {
|
||||||
const res = await request.put<ApiResult<unknown>>(
|
const res = await cmsRequest.put<ApiResult<unknown>>(
|
||||||
'/cms/cms-article-content',
|
'/cms/cms-article-content',
|
||||||
data
|
data
|
||||||
);
|
);
|
||||||
@@ -67,7 +67,7 @@ export async function updateCmsArticleContent(data: CmsArticleContent) {
|
|||||||
* 删除文章记录表
|
* 删除文章记录表
|
||||||
*/
|
*/
|
||||||
export async function removeCmsArticleContent(id?: number) {
|
export async function removeCmsArticleContent(id?: number) {
|
||||||
const res = await request.del<ApiResult<unknown>>(
|
const res = await cmsRequest.del<ApiResult<unknown>>(
|
||||||
'/cms/cms-article-content/' + id
|
'/cms/cms-article-content/' + id
|
||||||
);
|
);
|
||||||
if (res.code === 0) {
|
if (res.code === 0) {
|
||||||
@@ -80,7 +80,7 @@ export async function removeCmsArticleContent(id?: number) {
|
|||||||
* 批量删除文章记录表
|
* 批量删除文章记录表
|
||||||
*/
|
*/
|
||||||
export async function removeBatchCmsArticleContent(data: (number | undefined)[]) {
|
export async function removeBatchCmsArticleContent(data: (number | undefined)[]) {
|
||||||
const res = await request.del<ApiResult<unknown>>(
|
const res = await cmsRequest.del<ApiResult<unknown>>(
|
||||||
'/cms/cms-article-content/batch',
|
'/cms/cms-article-content/batch',
|
||||||
{
|
{
|
||||||
data
|
data
|
||||||
@@ -96,7 +96,7 @@ export async function removeBatchCmsArticleContent(data: (number | undefined)[])
|
|||||||
* 根据id查询文章记录表
|
* 根据id查询文章记录表
|
||||||
*/
|
*/
|
||||||
export async function getCmsArticleContent(id: number) {
|
export async function getCmsArticleContent(id: number) {
|
||||||
const res = await request.get<ApiResult<CmsArticleContent>>(
|
const res = await cmsRequest.get<ApiResult<CmsArticleContent>>(
|
||||||
'/cms/cms-article-content/' + id
|
'/cms/cms-article-content/' + id
|
||||||
);
|
);
|
||||||
if (res.code === 0 && res.data) {
|
if (res.code === 0 && res.data) {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import request from '@/utils/request';
|
import cmsRequest from '@/utils/cmsRequest';
|
||||||
import type { ApiResult, PageResult } from '@/api';
|
import type { ApiResult, PageResult } from '@/api';
|
||||||
import type { CmsArticleCount, CmsArticleCountParam } from './model';
|
import type { CmsArticleCount, CmsArticleCountParam } from './model';
|
||||||
|
|
||||||
@@ -7,7 +7,7 @@ import type { CmsArticleCount, CmsArticleCountParam } from './model';
|
|||||||
* 分页查询点赞文章
|
* 分页查询点赞文章
|
||||||
*/
|
*/
|
||||||
export async function pageCmsArticleCount(params: CmsArticleCountParam) {
|
export async function pageCmsArticleCount(params: CmsArticleCountParam) {
|
||||||
const res = await request.get<ApiResult<PageResult<CmsArticleCount>>>(
|
const res = await cmsRequest.get<ApiResult<PageResult<CmsArticleCount>>>(
|
||||||
'/cms/cms-article-count/page',
|
'/cms/cms-article-count/page',
|
||||||
{
|
{
|
||||||
params
|
params
|
||||||
@@ -23,7 +23,7 @@ export async function pageCmsArticleCount(params: CmsArticleCountParam) {
|
|||||||
* 查询点赞文章列表
|
* 查询点赞文章列表
|
||||||
*/
|
*/
|
||||||
export async function listCmsArticleCount(params?: CmsArticleCountParam) {
|
export async function listCmsArticleCount(params?: CmsArticleCountParam) {
|
||||||
const res = await request.get<ApiResult<CmsArticleCount[]>>(
|
const res = await cmsRequest.get<ApiResult<CmsArticleCount[]>>(
|
||||||
'/cms/cms-article-count',
|
'/cms/cms-article-count',
|
||||||
{
|
{
|
||||||
params
|
params
|
||||||
@@ -39,7 +39,7 @@ export async function listCmsArticleCount(params?: CmsArticleCountParam) {
|
|||||||
* 添加点赞文章
|
* 添加点赞文章
|
||||||
*/
|
*/
|
||||||
export async function addCmsArticleCount(data: CmsArticleCount) {
|
export async function addCmsArticleCount(data: CmsArticleCount) {
|
||||||
const res = await request.post<ApiResult<unknown>>(
|
const res = await cmsRequest.post<ApiResult<unknown>>(
|
||||||
'/cms/cms-article-count',
|
'/cms/cms-article-count',
|
||||||
data
|
data
|
||||||
);
|
);
|
||||||
@@ -53,7 +53,7 @@ export async function addCmsArticleCount(data: CmsArticleCount) {
|
|||||||
* 修改点赞文章
|
* 修改点赞文章
|
||||||
*/
|
*/
|
||||||
export async function updateCmsArticleCount(data: CmsArticleCount) {
|
export async function updateCmsArticleCount(data: CmsArticleCount) {
|
||||||
const res = await request.put<ApiResult<unknown>>(
|
const res = await cmsRequest.put<ApiResult<unknown>>(
|
||||||
'/cms/cms-article-count',
|
'/cms/cms-article-count',
|
||||||
data
|
data
|
||||||
);
|
);
|
||||||
@@ -67,7 +67,7 @@ export async function updateCmsArticleCount(data: CmsArticleCount) {
|
|||||||
* 删除点赞文章
|
* 删除点赞文章
|
||||||
*/
|
*/
|
||||||
export async function removeCmsArticleCount(id?: number) {
|
export async function removeCmsArticleCount(id?: number) {
|
||||||
const res = await request.del<ApiResult<unknown>>(
|
const res = await cmsRequest.del<ApiResult<unknown>>(
|
||||||
'/cms/cms-article-count/' + id
|
'/cms/cms-article-count/' + id
|
||||||
);
|
);
|
||||||
if (res.code === 0) {
|
if (res.code === 0) {
|
||||||
@@ -80,7 +80,7 @@ export async function removeCmsArticleCount(id?: number) {
|
|||||||
* 批量删除点赞文章
|
* 批量删除点赞文章
|
||||||
*/
|
*/
|
||||||
export async function removeBatchCmsArticleCount(data: (number | undefined)[]) {
|
export async function removeBatchCmsArticleCount(data: (number | undefined)[]) {
|
||||||
const res = await request.del<ApiResult<unknown>>(
|
const res = await cmsRequest.del<ApiResult<unknown>>(
|
||||||
'/cms/cms-article-count/batch',
|
'/cms/cms-article-count/batch',
|
||||||
{
|
{
|
||||||
data
|
data
|
||||||
@@ -96,7 +96,7 @@ export async function removeBatchCmsArticleCount(data: (number | undefined)[]) {
|
|||||||
* 根据id查询点赞文章
|
* 根据id查询点赞文章
|
||||||
*/
|
*/
|
||||||
export async function getCmsArticleCount(id: number) {
|
export async function getCmsArticleCount(id: number) {
|
||||||
const res = await request.get<ApiResult<CmsArticleCount>>(
|
const res = await cmsRequest.get<ApiResult<CmsArticleCount>>(
|
||||||
'/cms/cms-article-count/' + id
|
'/cms/cms-article-count/' + id
|
||||||
);
|
);
|
||||||
if (res.code === 0 && res.data) {
|
if (res.code === 0 && res.data) {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import request from '@/utils/request';
|
import cmsRequest from '@/utils/cmsRequest';
|
||||||
import type { ApiResult, PageResult } from '@/api';
|
import type { ApiResult, PageResult } from '@/api';
|
||||||
import type { CmsArticleLike, CmsArticleLikeParam } from './model';
|
import type { CmsArticleLike, CmsArticleLikeParam } from './model';
|
||||||
|
|
||||||
@@ -7,7 +7,7 @@ import type { CmsArticleLike, CmsArticleLikeParam } from './model';
|
|||||||
* 分页查询点赞文章
|
* 分页查询点赞文章
|
||||||
*/
|
*/
|
||||||
export async function pageCmsArticleLike(params: CmsArticleLikeParam) {
|
export async function pageCmsArticleLike(params: CmsArticleLikeParam) {
|
||||||
const res = await request.get<ApiResult<PageResult<CmsArticleLike>>>(
|
const res = await cmsRequest.get<ApiResult<PageResult<CmsArticleLike>>>(
|
||||||
'/cms/cms-article-like/page',
|
'/cms/cms-article-like/page',
|
||||||
{
|
{
|
||||||
params
|
params
|
||||||
@@ -23,7 +23,7 @@ export async function pageCmsArticleLike(params: CmsArticleLikeParam) {
|
|||||||
* 查询点赞文章列表
|
* 查询点赞文章列表
|
||||||
*/
|
*/
|
||||||
export async function listCmsArticleLike(params?: CmsArticleLikeParam) {
|
export async function listCmsArticleLike(params?: CmsArticleLikeParam) {
|
||||||
const res = await request.get<ApiResult<CmsArticleLike[]>>(
|
const res = await cmsRequest.get<ApiResult<CmsArticleLike[]>>(
|
||||||
'/cms/cms-article-like',
|
'/cms/cms-article-like',
|
||||||
{
|
{
|
||||||
params
|
params
|
||||||
@@ -39,7 +39,7 @@ export async function listCmsArticleLike(params?: CmsArticleLikeParam) {
|
|||||||
* 添加点赞文章
|
* 添加点赞文章
|
||||||
*/
|
*/
|
||||||
export async function addCmsArticleLike(data: CmsArticleLike) {
|
export async function addCmsArticleLike(data: CmsArticleLike) {
|
||||||
const res = await request.post<ApiResult<unknown>>(
|
const res = await cmsRequest.post<ApiResult<unknown>>(
|
||||||
'/cms/cms-article-like',
|
'/cms/cms-article-like',
|
||||||
data
|
data
|
||||||
);
|
);
|
||||||
@@ -53,7 +53,7 @@ export async function addCmsArticleLike(data: CmsArticleLike) {
|
|||||||
* 修改点赞文章
|
* 修改点赞文章
|
||||||
*/
|
*/
|
||||||
export async function updateCmsArticleLike(data: CmsArticleLike) {
|
export async function updateCmsArticleLike(data: CmsArticleLike) {
|
||||||
const res = await request.put<ApiResult<unknown>>(
|
const res = await cmsRequest.put<ApiResult<unknown>>(
|
||||||
'/cms/cms-article-like',
|
'/cms/cms-article-like',
|
||||||
data
|
data
|
||||||
);
|
);
|
||||||
@@ -67,7 +67,7 @@ export async function updateCmsArticleLike(data: CmsArticleLike) {
|
|||||||
* 删除点赞文章
|
* 删除点赞文章
|
||||||
*/
|
*/
|
||||||
export async function removeCmsArticleLike(id?: number) {
|
export async function removeCmsArticleLike(id?: number) {
|
||||||
const res = await request.del<ApiResult<unknown>>(
|
const res = await cmsRequest.del<ApiResult<unknown>>(
|
||||||
'/cms/cms-article-like/' + id
|
'/cms/cms-article-like/' + id
|
||||||
);
|
);
|
||||||
if (res.code === 0) {
|
if (res.code === 0) {
|
||||||
@@ -80,7 +80,7 @@ export async function removeCmsArticleLike(id?: number) {
|
|||||||
* 批量删除点赞文章
|
* 批量删除点赞文章
|
||||||
*/
|
*/
|
||||||
export async function removeBatchCmsArticleLike(data: (number | undefined)[]) {
|
export async function removeBatchCmsArticleLike(data: (number | undefined)[]) {
|
||||||
const res = await request.del<ApiResult<unknown>>(
|
const res = await cmsRequest.del<ApiResult<unknown>>(
|
||||||
'/cms/cms-article-like/batch',
|
'/cms/cms-article-like/batch',
|
||||||
{
|
{
|
||||||
data
|
data
|
||||||
@@ -96,7 +96,7 @@ export async function removeBatchCmsArticleLike(data: (number | undefined)[]) {
|
|||||||
* 根据id查询点赞文章
|
* 根据id查询点赞文章
|
||||||
*/
|
*/
|
||||||
export async function getCmsArticleLike(id: number) {
|
export async function getCmsArticleLike(id: number) {
|
||||||
const res = await request.get<ApiResult<CmsArticleLike>>(
|
const res = await cmsRequest.get<ApiResult<CmsArticleLike>>(
|
||||||
'/cms/cms-article-like/' + id
|
'/cms/cms-article-like/' + id
|
||||||
);
|
);
|
||||||
if (res.code === 0 && res.data) {
|
if (res.code === 0 && res.data) {
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user