15 Commits

Author SHA1 Message Date
gxwebsoft 4fb2a94b5b fix(passport): 添加邀请接受接口调试日志
- 在接受邀请请求前打印token和请求URL、参数
- 在请求响应后输出接口响应及返回数据
- 在调用失败时打印错误信息及异常日志
- 保持请求流程和状态管理不变,增强调试能力
2026-04-11 20:12:21 +08:00
gxwebsoft 4d471aa45d fix(invite): 修正邀请接口路径以支持小程序专用前缀
- 将请求邀请信息接口路径改为使用 /api/_app 前缀,确保小程序免登录访问
- 修改接受邀请接口路径,统一使用小程序专用 API 域名和前缀
- 更新邀请相关接口注释,明确小程序专用接口前缀含义
- 在专家历史数据中添加高级开发工程师 Will 的条目并更新时间戳
2026-04-11 20:03:31 +08:00
gxwebsoft a6b187c9c9 refactor(api): 移除未使用的配置接口及相关Hook
- 从cmsWebsiteField接口中删除configWebsiteField方法
- 删除useConfig自定义Hook及其引用
- 更新about页面,去除对config对象和useConfig的使用
- 修正invite模块请求URL,统一加上/api前缀
2026-04-11 19:53:11 +08:00
gxwebsoft a568b9d9df refactor(api): 移除未使用的配置接口及相关Hook
- 从cmsWebsiteField接口中删除configWebsiteField方法
- 删除useConfig自定义Hook及其引用
- 更新about页面,去除对config对象和useConfig的使用
- 修正invite模块请求URL,统一加上/api前缀
2026-04-11 19:50:52 +08:00
gxwebsoft b06552eaf1 fix(invite): 修正邀请接口使用独立API域名
- 将邀请相关接口的请求地址由通用API域名改为独立的INVITE_API_URL
- 修改获取邀请信息接口的请求URL
- 修改接受邀请接口的请求URL
- 确保请求头和参数传递保持一致
2026-04-11 19:09:02 +08:00
gxwebsoft b6105d65e9 fix(config): 修正环境配置中的API_BASE_URL地址
- 将开发环境、生产环境和测试环境的API_BASE_URL统一更新为新的域名websopy-api.websoft.top
- 更新SimpleQRCodeModal组件中二维码请求地址,使用新的API域名
- 在邀请信息请求函数fetchInviteInfo中添加详细的请求和响应日志
- 增加接口错误日志打印,提高捕获异常时的信息丰富度
2026-04-11 19:03:45 +08:00
gxwebsoft 699ff9c01e fix(invite): 修复邀请加入请求头TenantId传递问题
- 从配置中引入TenantId常量
- 将请求header中的TenantId字段由硬编码改为动态变量
- 确保GET请求和加密数据请求均正确带上TenantId头信息
- 提升接口调用的一致性和维护性
2026-04-11 18:57:26 +08:00
gxwebsoft d939fe3f31 fix(passport): 修正邀请信息接口请求参数与请求头
- 将邀请令牌作为查询参数附加在请求URL中
- 移除请求体中的token字段传递
- 添加请求头TenantId,值为5
- 请求失败时在控制台打印错误信息以便调试
- 保持原有错误处理和加载状态设置逻辑不变
2026-04-11 18:53:13 +08:00
gxwebsoft 8bc7f76d93 fix(passport): 优化登录确认成功后的页面跳转逻辑
- 延长登录确认成功提示的显示时间至2000毫秒
- 跳转到用户页面前先隐藏toast,避免影响跳转体验
- 增加switchTab成功和失败的日志打印
- 失败时提示用户手动返回页面
- 调整跳转延迟时间为1800毫秒,提升交互流畅度
2026-04-11 18:07:32 +08:00
gxwebsoft e180d06780 fix(passport): 修正邀请接受接口的URL路径
- 将邀请接受接口URL从/api/_app/invite/accept修改为/api/_app/developer/invite/accept
- 确保请求正确指向开发者邀请接受接口
- 修复因URL错误导致的邀请接受失败问题
2026-04-11 18:06:12 +08:00
gxwebsoft 9f974e38b4 fix(passport): 修正邀请接受接口的URL路径
- 将邀请接受接口URL从/api/_app/invite/accept修改为/api/_app/developer/invite/accept
- 确保请求正确指向开发者邀请接受接口
- 修复因URL错误导致的邀请接受失败问题
2026-04-11 16:29:08 +08:00
gxwebsoft 890673f757 fix(passport): 修正邀请接受接口的URL路径
- 将邀请接受接口URL从/api/_app/invite/accept修改为/api/_app/developer/invite/accept
- 确保请求正确指向开发者邀请接受接口
- 修复因URL错误导致的邀请接受失败问题
2026-04-11 16:26:30 +08:00
gxwebsoft 86f972d694 feat(invite): 增加邀请加入确认页面功能
- 在 app.config.ts 中添加 invite 模块路径
- 在 project.private.config.json 中配置小程序的邀请确认页面入口
- 新增邀请加入页面配置文件,设置页面标题和样式
- 实现邀请加入页面核心逻辑,支持获取邀请信息和微信手机号授权
- 支持用户同意协议后快速加入应用,加入成功后跳转首页
- 实现拒绝加入功能,支持返回上一页或首页
- 页面UI设计带渐变背景、邀请信息展示和协议勾选交互
- 提供服务协议和隐私政策跳转查看链接
- 添加加载和错误状态的用户友好提示界面
2026-04-11 16:01:53 +08:00
gxwebsoft f1c61c071a feat(add): 新增多页面新增和编辑表单功能
- 添加编辑和新增收货地址页面,支持表单数据加载和提交
- 新增应用密钥凭证、新增应用操作动态、新增应用成员、新增应用版本页面配置
- 实现文章新增及编辑页面,包含图片上传及多种文章属性配置
- 增加注册会员页面,支持头像上传、手机号获取和邀请人关系处理
- 引入统一表单提交成功和失败处理,支持编辑模式数据回显
- 配置统一eslint和editorconfig规则,增强代码规范和编辑体验
- 新增.gitignore规则,屏蔽无关文件和目录,优化版本管理
2026-04-11 12:24:27 +08:00
gxwebsoft 07f5c92f4b feat(add): 新增多页面新增和编辑表单功能
- 添加编辑和新增收货地址页面,支持表单数据加载和提交
- 新增应用密钥凭证、新增应用操作动态、新增应用成员、新增应用版本页面配置
- 实现文章新增及编辑页面,包含图片上传及多种文章属性配置
- 增加注册会员页面,支持头像上传、手机号获取和邀请人关系处理
- 引入统一表单提交成功和失败处理,支持编辑模式数据回显
- 配置统一eslint和editorconfig规则,增强代码规范和编辑体验
- 新增.gitignore规则,屏蔽无关文件和目录,优化版本管理
2026-04-11 12:22:29 +08:00
875 changed files with 52664 additions and 56863 deletions
+12
View File
@@ -0,0 +1,12 @@
# 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
+7
View File
@@ -0,0 +1,7 @@
module.exports = {
"extends": ["taro/react"],
"rules": {
"react/jsx-uses-react": "off",
"react/react-in-jsx-scope": "off"
}
}
+25
View File
@@ -0,0 +1,25 @@
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
View File
@@ -1,7 +0,0 @@
<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>
-8
View File
@@ -1,8 +0,0 @@
<?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
View File
@@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>
-12
View File
@@ -1,12 +0,0 @@
<?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>
-573
View File
@@ -1,573 +0,0 @@
<?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(official): 使用演示数据兜底官网内容展示&#10;&#10;- 在关于我们页补充企业优势和联系方式演示数据展示&#10;- 资讯列表页增加分类标签,实现按分类筛选资讯&#10;- 文章详情页新增相关推荐文章展示逻辑&#10;- 主页使用演示数据兜底导航、Banner、核心业务、公司简介和文章列表&#10;- 各页面均优化站点信息使用,优先展示真实数据,缺失时使用演示数据&#10;- 留言页增加公司联系信息展示,提升体验&#10;- 个人中心页支持分享官网,展示演示数据防止空白&#10;- 主页Banner支持无图时使用渐变背景和文字占位&#10;- 统一使用演示数据,保证页面无空白,提升整体稳定性和用户体验">
<change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
<change beforePath="$PROJECT_DIR$/.workbuddy/memory/MEMORY.md" beforeDir="false" afterPath="$PROJECT_DIR$/.workbuddy/memory/MEMORY.md" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/official/about/index.tsx" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/src/official/article-detail/index.tsx" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/src/official/article/index.tsx" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/src/official/index/index.tsx" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/src/official/message/index.tsx" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/src/official/mine/index.tsx" beforeDir="false" />
</list>
<option name="SHOW_DIALOG" value="false" />
<option name="HIGHLIGHT_CONFLICTS" value="true" />
<option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
<option name="LAST_RESOLUTION" value="IGNORE" />
</component>
<component name="Git.Settings">
<option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$" />
<option name="RESET_MODE" value="HARD" />
</component>
<component name="ProjectColorInfo">{
&quot;associatedIndex&quot;: 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">{
&quot;keyToString&quot;: {
&quot;ModuleVcsDetector.initialDetectionPerformed&quot;: &quot;true&quot;,
&quot;RunOnceActivity.ShowReadmeOnStart&quot;: &quot;true&quot;,
&quot;RunOnceActivity.TerminalTabsStorage.copyFrom.TerminalArrangementManager.252&quot;: &quot;true&quot;,
&quot;RunOnceActivity.git.unshallow&quot;: &quot;true&quot;,
&quot;git-widget-placeholder&quot;: &quot;master&quot;,
&quot;junie.onboarding.icon.badge.shown&quot;: &quot;true&quot;,
&quot;last_opened_file_path&quot;: &quot;/Users/gxwebsoft/VUE/websopy-taro/src/passport&quot;,
&quot;node.js.detected.package.eslint&quot;: &quot;true&quot;,
&quot;node.js.detected.package.stylelint&quot;: &quot;true&quot;,
&quot;node.js.detected.package.tslint&quot;: &quot;true&quot;,
&quot;node.js.selected.package.eslint&quot;: &quot;(autodetect)&quot;,
&quot;node.js.selected.package.stylelint&quot;: &quot;/Users/gxwebsoft/VUE/websopy-taro/node_modules/stylelint&quot;,
&quot;node.js.selected.package.tslint&quot;: &quot;(autodetect)&quot;,
&quot;nodejs_package_manager_path&quot;: &quot;pnpm&quot;,
&quot;npm.build:weapp.executor&quot;: &quot;Run&quot;,
&quot;npm.dev:weapp.executor&quot;: &quot;Run&quot;,
&quot;settings.editor.selected.configurable&quot;: &quot;preferences.pluginManager&quot;,
&quot;to.speed.mode.migration.done&quot;: &quot;true&quot;,
&quot;ts.external.directory.path&quot;: &quot;/Users/gxwebsoft/VUE/websopy-taro/node_modules/typescript/lib&quot;,
&quot;vue.rearranger.settings.migration&quot;: &quot;true&quot;
}
}</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="13654000" />
<workItem from="1786532924218" duration="826000" />
</task>
<task id="LOCAL-00036" summary="feat(postcss): 添加插件移除微信小程序不兼容Tailwind规则&#10;&#10;- 实现 postcss-remove-wxss-incompatible 插件&#10;- 移除反斜杠转义的重要性修饰符类选择器(.\!xxx)&#10;- 删除依赖 :not 和兄弟选择器的 .space-[x|y]-* 规则&#10;- 删除依赖 :not 和兄弟选择器的 .divide-[x|y]-* 规则&#10;- 解决微信小程序 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规则&#10;&#10;- 实现 postcss-remove-wxss-incompatible 插件&#10;- 移除反斜杠转义的重要性修饰符类选择器(.\!xxx)&#10;- 删除依赖 :not 和兄弟选择器的 .space-[x|y]-* 规则&#10;- 删除依赖 :not 和兄弟选择器的 .divide-[x|y]-* 规则&#10;- 解决微信小程序 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规则&#10;&#10;- 实现 postcss-remove-wxss-incompatible 插件&#10;- 移除反斜杠转义的重要性修饰符类选择器(.\!xxx)&#10;- 删除依赖 :not 和兄弟选择器的 .space-[x|y]-* 规则&#10;- 删除依赖 :not 和兄弟选择器的 .divide-[x|y]-* 规则&#10;- 解决微信小程序 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): 优化订阅号获取逻辑并调整支付组件样式&#10;&#10;- 优先从小程序码 scene 参数读取 subscriptionNo,提升参数兼容性&#10;- 对 scene 参数进行解码,避免编码引起的问题&#10;- 移除支付组件价格显示处的固定颜色设置,增强样式灵活性">
<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): 优化小程序端复制文本功能体验&#10;&#10;- 小程序端直接调用原生 wx.setClipboardData,避免 Taro Promise 封装问题&#10;- 避免微信小程序重复显示复制成功提示和误报复制失败&#10;- 失败时仅在小程序端输出错误日志,非小程序端显示提示&#10;- 修改首页外链跳转逻辑,支持相对路径跳转,小程序内外链复制提示提升用户体验&#10;- 更新项目内剪切板使用规范文档,说明复制行为调整及符合相应规范要求">
<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): 优化小程序端复制文本功能体验&#10;&#10;- 小程序端直接调用原生 wx.setClipboardData,避免 Taro Promise 封装问题&#10;- 避免微信小程序重复显示复制成功提示和误报复制失败&#10;- 失败时仅在小程序端输出错误日志,非小程序端显示提示&#10;- 修改首页外链跳转逻辑,支持相对路径跳转,小程序内外链复制提示提升用户体验&#10;- 更新项目内剪切板使用规范文档,说明复制行为调整及符合相应规范要求">
<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): 优化外链提示及删除复制咨询模板功能&#10;&#10;- 替换小程序内外链打开方式提示为弹窗提醒用户手动复制&#10;- 删除复制咨询模板功能及相关按钮&#10;- 修改联系我们文案,突出直接电话沟通或需求反馈&#10;- 移除无用导入的复制文本工具函数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拼接逻辑&#10;&#10;- 在request函数中将请求拦截改为先拼接完整URL&#10;- 支持相对路径自动补全baseUrl&#10;- 删除原有请求拦截中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拼接逻辑&#10;&#10;- 在request函数中将请求拦截改为先拼接完整URL&#10;- 支持相对路径自动补全baseUrl&#10;- 删除原有请求拦截中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拼接逻辑&#10;&#10;- 在request函数中将请求拦截改为先拼接完整URL&#10;- 支持相对路径自动补全baseUrl&#10;- 删除原有请求拦截中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拼接逻辑&#10;&#10;- 在request函数中将请求拦截改为先拼接完整URL&#10;- 支持相对路径自动补全baseUrl&#10;- 删除原有请求拦截中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拼接逻辑&#10;&#10;- 在request函数中将请求拦截改为先拼接完整URL&#10;- 支持相对路径自动补全baseUrl&#10;- 删除原有请求拦截中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拼接逻辑&#10;&#10;- 在request函数中将请求拦截改为先拼接完整URL&#10;- 支持相对路径自动补全baseUrl&#10;- 删除原有请求拦截中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日日程文件&#10;&#10;- 创建新的日程文档文件 2026-07-01.md&#10;- 文件内容待后续补充和更新">
<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日日程文件&#10;&#10;- 创建新的日程文档文件 2026-07-01.md&#10;- 文件内容待后续补充和更新">
<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): 优化小程序支付流程并增强日志调试&#10;&#10;- 更新 wx-login 接口以返回完整 LoginResult,包括 access_token 和 user 对象&#10;- 支付页面新增大量日志,详尽打印参数、请求和响应信息&#10;- 优化获取订阅详情流程,支持 returnRaw 返回完整响应用于调试&#10;- 支付流程新增步骤,先通过 code 获取 openid 并缓存,再尝试用 openid 自动登录&#10;- 增加自动登录失败交互,提示未注册用户先登录&#10;- 支付相关请求均使用 returnRaw 方便错误排查&#10;- request 请求模块添加统一请求和响应日志,增强调试能力&#10;- 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): 支付成功后自动跳转到已购产品页面&#10;&#10;- 支付成功后显示成功提示&#10;- 延迟1.5秒自动跳转到已购产品页面&#10;- 处理跳转失败时回退到用户首页&#10;- 优化用户支付完成后的使用体验">
<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): 新增小程序支付页面及相关配置&#10;&#10;- 新增确认支付页面配置,设置导航栏标题和样式&#10;- 实现小程序支付页完整组件逻辑,包括加载订单、发起JSAPI支付等&#10;- 支持通过 scene 或 subscriptionNo 获取订单参数&#10;- 完善支付流程接口请求及错误处理,支持自动登录和支付确认&#10;- 增加支付成功、支付中、支付失败、取消等状态页面展示&#10;- 支持支付成功后跳转已购产品页或用户页&#10;- 提供取消支付返回逻辑和界面展示&#10;- 细化支付金额显示和提示用户支付后可使用应用功能">
<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): 新增收货地址与售后申请功能&#10;&#10;- 新增地址编辑页面,支持地址智能识别、地图选点、地区选择和默认地址设置&#10;- 实现地址列表页面,支持地址查看、删除、设为默认及选择返回结算页&#10;- 新增售后申请页面,支持退款类型选择、商品选择、原因填写、图片上传和提交审核&#10;- 修复 passport 分包配置,移除不存在分包并补充缺失声明,避免 Taro 编译报错&#10;- 新增地址类型定义,增强前端地址数据结构类型安全&#10;- 优化页面交互体验,完善表单校验及错误提示逻辑&#10;- 统一代码格式与命名规范,保持代码风格一致性">
<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 问题&#10;&#10;- 迁移 src/passport/pay 相关代码至当前项目,无需替换文件&#10;- 新增环境变量 WEBSOPY_API_BASE_URL,提供 websopy API 基础地址&#10;- 在 config/app.ts 导出 WebsopyBaseUrl,供代码引用&#10;- 配置 env.d.ts 类型定义,消除 TypeScript 隐式 any 错误&#10;- request 请求方法支持自定义 baseUrl 以调用不同接口地址&#10;- passport/pay 页面调用订阅接口统一传入 WebsopyBaseUrl&#10;- 修正 tenantId 类型为 number,调整登录用户类型断言&#10;- 确认编译成功,dist 目录生成对应文件及正确请求地址&#10;- 说明 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): 优化支付成功后页面跳转逻辑&#10;&#10;- 修改支付成功后的自动跳转目标,从已购产品页面改为我的页面&#10;- 使用 switchTab 替代 navigateTo 保证在 tabBar 页顺利跳转&#10;- 移除多余的跳转失败回退逻辑,简化跳转流程&#10;- 调整跳转延迟时间为 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&#10;&#10;- RoleParam 类型新增 tenantId 字段,支持按租户过滤角色&#10;- 新增 getCurrentTenantId 获取当前租户ID,支持登录态和默认租户回退&#10;- 实现 getVipRoleId 函数,按 roleCode 与 tenantId 查询 VIP 角色ID&#10;- assignVipRole 中动态获取 VIP 角色ID,避免硬编码&#10;- 添加用户角色时带上 tenantId,确保多租户角色绑定正确&#10;- 移除写死的 VIP_ROLE_ID,改用动态查询结果,提高跨租户兼容性&#10;- 补充引入 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,避免硬编码绑定问题&#10;&#10;- RoleParam新增tenantId字段,支持按租户过滤角色&#10;- 新增getCurrentTenantId函数,兼容登录态及默认租户回退&#10;- 实现getVipRoleId函数,按roleCode与tenantId查询VIP角色ID&#10;- assignVipRole动态获取VIP角色ID,移除写死的角色ID&#10;- 添加用户角色时传入tenantId,确保多租户环境中角色绑定正确&#10;- 修复后端UserRoleController.save接口bug,避免userId被无条件覆盖&#10;- 用户角色绑定时正确使用申请人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概念为开发者并优化开发者登录及申请&#10;&#10;- 将VIP相关文案、图标改为开发者,涉及用户中心、门店中心、审核页等&#10;- 优化登录弹窗UI,改为红色系,提升视觉统一性和交互体验&#10;- 关于我们页面品牌和公司信息内容更新,展示最新企业介绍和业务范围&#10;- VIP升级页新增“所在城市”选择,使用微信原生地区选择器,完善地址信息&#10;- 用户申请表单中新增城市字段及校验,提交时携带城市信息&#10;- 优化审核页面文案,统称开发者身份,明确申请审核内容&#10;- 修复UserRoleController.save接口bug,避免userId被无条件覆盖导致角色绑错&#10;- 动态查询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): 删除门店信息输入框&#10;&#10;- 移除用户资料页面中的门店名称输入框&#10;- 移除用户资料页面中的门店地址输入框&#10;- 简化用户页面界面布局,提高界面整洁度">
<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): 统一品牌名称为“威数谱软件”&#10;&#10;- 将项目中所有“威硕谱软件”名称修改为“威数谱软件”&#10;- 更新协议页面、欢迎消息、礼品卡等多处文本内容&#10;- 修改小程序配置文件中的项目名称和显示标题&#10;- 优化开发者申请页部分文案,明确开发者身份描述&#10;- 保证所有界面及配置中的品牌名称统一一致">
<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身份统一改为开发者标记&#10;&#10;- 审核通过时不再新增角色,而是更新用户isDeveloper布尔标记&#10;- 后端UserService新增markAsDeveloper方法,通过updateById设置isDeveloper&#10;- UserController新增PUT接口更新开发者标记,权限使用sys:userRole:save&#10;- 前端api新增setUserDeveloper调用新的审核接口&#10;- vip-review页面移除角色相关代码,改为调用setUserDeveloper方法&#10;- 约定开发者身份即sys_user.is_developer字段,不再依赖角色表记录&#10;- app.config.ts追加开发者中心与平台管理相关页面路由&#10;- 新增开发者中心、平台管理的多个API模块(apikey、appProduct、appDomain、developer请求等)&#10;- 添加公共工具方法和复用组件用于开发者中心及平台管理功能&#10;- 统一品牌名称为“威数谱软件”,更新文案及界面展现一致性">
<option name="closed" value="true" />
<created>1784703425108</created>
<option name="number" value="00062" />
<option name="presentableId" value="LOCAL-00062" />
<option name="project" value="LOCAL" />
<updated>1784703425108</updated>
</task>
<task id="LOCAL-00063" summary="refactor(user): 将VIP身份统一改为开发者标记&#10;&#10;- 审核通过时不再新增角色,而是更新用户isDeveloper布尔标记&#10;- 后端UserService新增markAsDeveloper方法,通过updateById设置isDeveloper&#10;- UserController新增PUT接口更新开发者标记,权限使用sys:userRole:save&#10;- 前端api新增setUserDeveloper调用新的审核接口&#10;- vip-review页面移除角色相关代码,改为调用setUserDeveloper方法&#10;- 约定开发者身份即sys_user.is_developer字段,不再依赖角色表记录&#10;- app.config.ts追加开发者中心与平台管理相关页面路由&#10;- 新增开发者中心、平台管理的多个API模块(apikey、appProduct、appDomain、developer请求等)&#10;- 添加公共工具方法和复用组件用于开发者中心及平台管理功能&#10;- 统一品牌名称为“威数谱软件”,更新文案及界面展现一致性">
<option name="closed" value="true" />
<created>1784704220175</created>
<option name="number" value="00063" />
<option name="presentableId" value="LOCAL-00063" />
<option name="project" value="LOCAL" />
<updated>1784704220175</updated>
</task>
<task id="LOCAL-00064" summary="feat(weapp): 新增开发者中心与平台管理入口及相关功能&#10;&#10;- 小程序端新增开发者中心和平台管理页面模块,完善功能入口&#10;- 新增开发者中心页面(概览、我的应用、API Key和工单)&#10;- 新增平台管理页面(管理首页、应用审核、Git权限审核、域名绑定审核和工单处理)&#10;- 复刻PC端/api/app/*相关API接口,基地址使用WEBSOPY_API_BASE_URL&#10;- 增加公共组件StatCard、EntryGrid、CellRow以支持新页面UI&#10;- 修改首页增加开发者中心和平台管理角色入口,仅对对应用户显示&#10;- 用户页面菜单新增开发者中心和平台管理入口条件展示&#10;- 约定开发者身份使用sys_user.is_developer布尔字段,不再依赖角色表&#10;- 更新登录态业务,菜单是否显示基于登录接口返回的isDeveloper/isAdmin字段&#10;- 确保相关页面已在app.config.ts注册并添加身份认证需求&#10;- 调整运行配置为npm build:weapp,确保编译无误">
<option name="closed" value="true" />
<created>1784707342939</created>
<option name="number" value="00064" />
<option name="presentableId" value="LOCAL-00064" />
<option name="project" value="LOCAL" />
<updated>1784707342940</updated>
</task>
<task id="LOCAL-00065" summary="feat(weapp): 新增开发者中心与平台管理入口及功能&#10;&#10;- 小程序端新增开发者中心和平台管理页面模块,完善功能入口&#10;- 新增开发者中心页面(概览、我的应用、API Key和工单)&#10;- 新增平台管理页面(管理首页、应用审核、Git权限审核、域名绑定审核和工单处理)&#10;- 复刻PC端/api/app/*相关API接口,基地址使用WEBSOPY_API_BASE_URL&#10;- 增加公共组件StatCard、EntryGrid、CellRow以支持新页面UI&#10;- 修改首页增加开发者中心和平台管理角色入口,仅对对应用户显示&#10;- 用户页面菜单新增开发者中心和平台管理入口条件展示&#10;- 约定开发者身份使用sys_user.is_developer布尔字段,不再依赖角色表&#10;- 更新登录态业务,菜单是否显示基于登录接口返回的isDeveloper/isAdmin字段&#10;- vip-review审核通过流程调整为只调用setUserDeveloper,后台替换角色为developer并置isDeveloper=1">
<option name="closed" value="true" />
<created>1784709736683</created>
<option name="number" value="00065" />
<option name="presentableId" value="LOCAL-00065" />
<option name="project" value="LOCAL" />
<updated>1784709736683</updated>
</task>
<task id="LOCAL-00066" summary="fix(weapp): 修复支付页nutui组件导致内容空白问题&#10;&#10;- 替换支付页中nutui的Button、Cell、Price、Divider组件为Taro原生组件或项目自定义组件&#10;- 修正商品名称、价格类型和支付金额显示,使用flex布局及Text组件自绘内容&#10;- 调整按钮样式,统一为带蓝色背景和圆角的自定义样式&#10;- 移除nutui Price组件中的thousands属性,使用项目自定义Price组件&#10;- 解决微信小程序中nutui组件因div渲染缺失导致UI元素不显示的问题&#10;- 保持支付流程及状态逻辑无改动,仅优化展示兼容性和稳定性">
<option name="closed" value="true" />
<created>1784798603228</created>
<option name="number" value="00066" />
<option name="presentableId" value="LOCAL-00066" />
<option name="project" value="LOCAL" />
<updated>1784798603228</updated>
</task>
<task id="LOCAL-00067" summary="fix(weapp): 修复支付页nutui组件导致内容空白问题&#10;&#10;- 替换支付页中nutui的Button、Cell、Price、Divider组件为Taro原生组件或项目自定义组件&#10;- 修正商品名称、价格类型和支付金额显示,使用flex布局及Text组件自绘内容&#10;- 调整按钮样式,统一为带蓝色背景和圆角的自定义样式&#10;- 移除nutui Price组件中的thousands属性,使用项目自定义Price组件&#10;- 解决微信小程序中nutui组件因div渲染缺失导致UI元素不显示的问题&#10;- 保持支付流程及状态逻辑无改动,仅优化展示兼容性和稳定性">
<option name="closed" value="true" />
<created>1784799951929</created>
<option name="number" value="00067" />
<option name="presentableId" value="LOCAL-00067" />
<option name="project" value="LOCAL" />
<updated>1784799951929</updated>
</task>
<task id="LOCAL-00068" summary="fix(weapp): 修复支付页nutui组件导致内容空白问题&#10;&#10;- 替换支付页中nutui的Button、Cell、Price、Divider组件为Taro原生组件或项目自定义组件&#10;- 修正商品名称、价格类型和支付金额显示,使用flex布局及Text组件自绘内容&#10;- 调整按钮样式,统一为带蓝色背景和圆角的自定义样式&#10;- 移除nutui Price组件中的thousands属性,使用项目自定义Price组件&#10;- 解决微信小程序中nutui组件因div渲染缺失导致UI元素不显示的问题&#10;- 保持支付流程及状态逻辑无改动,仅优化展示兼容性和稳定性">
<option name="closed" value="true" />
<created>1784800111045</created>
<option name="number" value="00068" />
<option name="presentableId" value="LOCAL-00068" />
<option name="project" value="LOCAL" />
<updated>1784800111045</updated>
</task>
<task id="LOCAL-00069" summary="feat(user): 补充开发者审核通过时添加店员记录功能&#10;&#10;- 引入 addShopStoreUser 接口用于新增门店店员记录&#10;- 新增 clerkStoreId 和 clerkTenantId 状态,保存当前审核店员的门店和租户信息&#10;- 在审核通过后调用 addShopStoreUser,添加申请人为当前门店店员&#10;- 保证只有当 clerkStoreId 存在时才执行新增操作&#10;- 更新工作记录文档,补充审核通过流程中新添加的店员记录步骤&#10;- 保持原有升级开发者逻辑和接口调用不变">
<option name="closed" value="true" />
<created>1784800763746</created>
<option name="number" value="00069" />
<option name="presentableId" value="LOCAL-00069" />
<option name="project" value="LOCAL" />
<updated>1784800763746</updated>
</task>
<task id="LOCAL-00070" summary="feat(user): 补充开发者审核通过时添加店员记录功能&#10;&#10;- 引入 addShopStoreUser 接口用于新增门店店员记录&#10;- 新增 clerkStoreId 和 clerkTenantId 状态,保存当前审核店员的门店和租户信息&#10;- 在审核通过后调用 addShopStoreUser,添加申请人为当前门店店员&#10;- 保证只有当 clerkStoreId 存在时才执行新增操作&#10;- 调整平台管理页 UI,使其与门店中心保持一致风格&#10;- 门店中心及平台管理入口权限调整,仅超级管理员可访问&#10;- 用户菜单中平台管理和门店中心入口现基于 isSuperAdmin 字段控制&#10;- 优化登录及权限校验流程,避免权限不足时页面闪烁显示&#10;- 更新工作记录文档,补充审核通过流程中新添加的店员记录步骤">
<option name="closed" value="true" />
<created>1784802336157</created>
<option name="number" value="00070" />
<option name="presentableId" value="LOCAL-00070" />
<option name="project" value="LOCAL" />
<updated>1784802336157</updated>
</task>
<task id="LOCAL-00071" summary="feat(user): 合并并切换“申请开发者”菜单项状态&#10;&#10;- 在用户菜单中根据 isDeveloper 字段切换显示“申请成为开发者”或“进入开发者中心”&#10;- 已通过审核用户直接显示“进入开发者中心”且高亮显示&#10;- 去除冗余的独立“开发者中心”菜单条目,简化菜单结构&#10;- 调整平台管理页和门店中心入口权限为仅超级管理员可访问&#10;- 优化登录与权限校验流程,防止权限不足时页面闪烁或无访问权限空白&#10;- 更新相关文档,完善审核通过后店员记录添加流程">
<option name="closed" value="true" />
<created>1784802434477</created>
<option name="number" value="00071" />
<option name="presentableId" value="LOCAL-00071" />
<option name="project" value="LOCAL" />
<updated>1784802434477</updated>
</task>
<task id="LOCAL-00072" summary="fix(auth): 统一超级管理员标识为布尔值true&#10;&#10;- 将所有页面和菜单中超级管理员校验由数字1改为布尔true&#10;- 平台管理、门店中心菜单项仅在isSuperAdmin===true时显示&#10;- 门店中心页面移除店员门槛,改为超级管理员访问限制&#10;- 非超级管理员访问门店中心弹窗提示并返回&#10;- 首页工作台入口及相关访问权限判定切换为布尔判断&#10;- 优化首屏加载状态,避免权限校验闪烁和误判">
<option name="closed" value="true" />
<created>1784802589434</created>
<option name="number" value="00072" />
<option name="presentableId" value="LOCAL-00072" />
<option name="project" value="LOCAL" />
<updated>1784802589434</updated>
</task>
<task id="LOCAL-00073" summary="feat(user): 合并切换开发者菜单项状态并更新开发者中心UI&#10;&#10;- 用户菜单中根据 isDeveloper 字段条件展示“申请成为开发者”或“进入开发者中心”&#10;- 已审核通过用户菜单显示“进入开发者中心”,并设置高亮样式&#10;- 去除冗余的独立“开发者中心”菜单条目,简化菜单结构&#10;- 重新设计开发者中心页面UI,统一为门店中心风格&#10;- 顶部展示开发者头像及昵称手机号信息&#10;- 功能卡片展示“我的应用”、“API Key”和“我的工单”,异步加载数量角标&#10;- 底部添加“开发者帮助与支持”入口,替代门店订阅消息卡片&#10;- 未登录或非开发者状态显示相应空态提示及操作按钮&#10;- 优化登录状态与权限校验逻辑,防止页面闪烁和无权限内容显示">
<option name="closed" value="true" />
<created>1784802954848</created>
<option name="number" value="00073" />
<option name="presentableId" value="LOCAL-00073" />
<option name="project" value="LOCAL" />
<updated>1784802954848</updated>
</task>
<task id="LOCAL-00074" summary="feat(user): 合并切换开发者菜单并优化首页轮播图显示&#10;&#10;- 用户菜单根据 isDeveloper 字段切换显示申请或进入开发者中心&#10;- 已审核用户菜单高亮显示“进入开发者中心”&#10;- 去除冗余开发者中心菜单条目,简化结构&#10;- 重新设计开发者中心页面UI,统一门店中心风格&#10;- 顶部展示开发者头像及昵称手机号信息&#10;- 功能卡片展示“我的应用”、“API Key”和“我的工单”,异步加载数量角标&#10;- 底部添加“开发者帮助与支持”入口,替代门店订阅消息卡片&#10;- 未登录或非开发者状态显示相应空态及操作按钮&#10;- 首页轮播图为空时不显示容器,功能入口自动上移,优化空状态体验&#10;- 优化登录状态与权限校验逻辑,防止页面闪烁和无权限展示">
<option name="closed" value="true" />
<created>1784803190504</created>
<option name="number" value="00074" />
<option name="presentableId" value="LOCAL-00074" />
<option name="project" value="LOCAL" />
<updated>1784803190504</updated>
</task>
<task id="LOCAL-00075" summary="feat(user): 合并切换开发者菜单并优化首页轮播图显示&#10;&#10;- 用户菜单根据 isDeveloper 字段切换显示申请或进入开发者中心&#10;- 已审核用户菜单高亮显示“进入开发者中心”&#10;- 去除冗余开发者中心菜单条目,简化结构&#10;- 重新设计开发者中心页面UI,统一门店中心风格&#10;- 顶部展示开发者头像及昵称手机号信息&#10;- 功能卡片展示“我的应用”、“API Key”和“我的工单”,异步加载数量角标&#10;- 底部添加“开发者帮助与支持”入口,替代门店订阅消息卡片&#10;- 未登录或非开发者状态显示相应空态及操作按钮&#10;- 首页轮播图为空时不显示容器,功能入口自动上移,优化空状态体验&#10;- 优化登录状态与权限校验逻辑,防止页面闪烁和无权限展示">
<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): 优化微信支付和线下付款流程&#10;&#10;- 将默认支付方式调整为微信支付(payType=1),增加支付方式选择项&#10;- 微信支付成功后直接调用微信支付接口并处理支付结果反馈&#10;- 支付取消或失败时显示对应提示信息&#10;- 线下付款(payType=9)保留并显示相应提示,不触发在线支付&#10;- 提交订单时根据支付方式显示不同的按钮文本 (‘提交并支付’ 或 ‘提交订单’)&#10;- 针对微信支付配置缺失的错误,统一显示详细提示,便于定位问题&#10;- 优化支付方式选中时的提示样式与说明,提升用户体验">
<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): 优化微信支付和线下付款流程&#10;&#10;- 支付方式新增微信支付(payType=1),并设为默认,保留线下付款(payType=9)&#10;- 微信支付时调用 Taro.requestPayment 发起支付流程,取消或失败给出明确提示&#10;- 支付选项选中状态下微信支付显示绿色提示,线下付款显示橙色提示&#10;- 提交按钮文案区分微信支付(提交并支付)与线下付款(提交订单)&#10;- 后端支付配置缺失时提示“微信支付暂不可用:支付配置缺失”&#10;- 确保编译构建通过,未引入新类型错误">
<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): 实现企业官网分包及内容展示&#10;&#10;- 新增官方分包目录及页面(首页、资讯列表、详情、关于我们、留言、我的)&#10;- 自定义实现底部导航组件 OfficialTabBar,解决微信 tabBar 不支持分包问题&#10;- 统一接口调用走 shop-api 代理,无新增 CMS 域名,减少合法域名负担&#10;- 新增 CMS 站点信息获取接口及留言咨询提交与查询接口&#10;- 设计并完善官网富文本样式,品牌色统一为 #185FA5,并更新 tailwind 配置&#10;- 首页新增企业官网入口卡片,方便用户访问官网内容&#10;- 完成官网分包构建验证,解决 Taro 构建批量删除保护问题&#10;- 规范 CMS 字段使用,适配字段名称及状态含义&#10;- 后续计划对历史页面 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服务&#10;&#10;- 新增cmsRequest工具,基于request封装,默认baseURL为cms-api&#10;- 改用cmsRequest替换所有cms相关请求,确保请求走独立域名&#10;- 配置三环境新增CMS_API_BASE_URL及导出CmsBaseUrl常量&#10;- 解决CMS接口走shop-api时返回404的问题,避免小程序域名白名单冲突&#10;- 统一修改34个cms API模块的261处调用,保持接口行为一致&#10;- 修复部分拼接全URL方式改为相对路径调用cmsRequest&#10;- 更新注释说明并调整相关接口调用代码&#10;- 兼容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>
<task id="LOCAL-00080" summary="refactor(cms): 迁移CMS模块接口到独立cms-api服务&#10;&#10;- 新增cmsRequest工具,基于request封装,默认baseURL为cms-api&#10;- 改用cmsRequest替换所有cms相关请求,确保请求走独立域名&#10;- 配置三环境新增CMS_API_BASE_URL及导出CmsBaseUrl常量&#10;- 解决CMS接口走shop-api时返回404的问题,避免小程序域名白名单冲突&#10;- 统一修改34个cms API模块的261处调用,保持接口行为一致&#10;- 修复部分拼接全URL方式改为相对路径调用cmsRequest&#10;- 更新注释说明并调整相关接口调用代码&#10;- 兼容macOS sed替换问题,采用perl命令完成批量替换">
<option name="closed" value="true" />
<created>1786417523542</created>
<option name="number" value="00080" />
<option name="presentableId" value="LOCAL-00080" />
<option name="project" value="LOCAL" />
<updated>1786417523542</updated>
</task>
<task id="LOCAL-00081" summary="refactor(cms): 迁移CMS模块接口到独立cms-api服务&#10;&#10;- 新增cmsRequest工具,基于request封装,默认baseURL为cms-api&#10;- 改用cmsRequest替换所有cms相关请求,确保请求走独立域名&#10;- 配置三环境新增CMS_API_BASE_URL及导出CmsBaseUrl常量&#10;- 解决CMS接口走shop-api时返回404的问题,避免小程序域名白名单冲突&#10;- 统一修改34个cms API模块的261处调用,保持接口行为一致&#10;- 修复部分拼接全URL方式改为相对路径调用cmsRequest&#10;- 更新注释说明并调整相关接口调用代码&#10;- 兼容macOS sed替换问题,采用perl命令完成批量替换">
<option name="closed" value="true" />
<created>1786417577350</created>
<option name="number" value="00081" />
<option name="presentableId" value="LOCAL-00081" />
<option name="project" value="LOCAL" />
<updated>1786417577350</updated>
</task>
<task id="LOCAL-00082" summary="refactor(common): 移除公共组件中的 NutUI Button,改用 Taro 原生 Button&#10;&#10;- `EmptyState` 和 `FixedButton` 两个组件全部替换 NutUI Button 为 Taro 原生 Button&#10;- 解决空状态按钮在微信小程序端不可见的问题,确保按钮正常显示&#10;- `FixedButton` 组件虽然未被调用,仍保留但调整实现,icon 由 prop 改为 children 渲染&#10;- 统一主色使用 tailwind 的 `bg-red-500`,弃用原 NutUI primary 颜色&#10;- 全局修复微信小程序原生 button 的 `::after` 灰色描边问题,在全局样式中设置 `button::after { border: none; }`&#10;- NutUI Button 的引用文件数由 27 减至 25,`src/components/common/` 完全无 NutUI 依赖&#10;- CMS 请求链路调整,所有 CMS 接口改用独立 `cmsRequest` 实现,确保请求正确&#10;- 修正 `src/api/official` 的数据层,取消错误的 shop-api 代理说明,强调独立 CMS API 基础地址和合法域名配置事项">
<option name="closed" value="true" />
<created>1786417944432</created>
<option name="number" value="00082" />
<option name="presentableId" value="LOCAL-00082" />
<option name="project" value="LOCAL" />
<updated>1786417944432</updated>
</task>
<task id="LOCAL-00083" summary="refactor(common): 移除 NutUI Button 并改用 Taro 原生 Button&#10;&#10;- `EmptyState` 和 `FixedButton` 组件替换 NutUI Button 为 Taro 原生 Button&#10;- 解决空状态按钮在微信小程序端不可见问题,确保按钮正常显示&#10;- `FixedButton` 组件调整实现,icon 由 prop 改为 children 渲染&#10;- 统一主色改用 tailwind 的 `bg-red-500`,弃用 NutUI primary 颜色&#10;- 全局样式修复微信小程序原生 button 的 `::after` 灰色描边问题&#10;- NutUI Button 引用文件数由 27 减至 25,`src/components/common/` 无 NutUI 依赖&#10;- CMS 请求链路调整,所有 CMS 接口改用独立 `cmsRequest` 实现">
<option name="closed" value="true" />
<created>1786434456515</created>
<option name="number" value="00083" />
<option name="presentableId" value="LOCAL-00083" />
<option name="project" value="LOCAL" />
<updated>1786434456515</updated>
</task>
<task id="LOCAL-00084" summary="refactor(official): 使用演示数据兜底官网内容展示&#10;&#10;- 在关于我们页补充企业优势和联系方式演示数据展示&#10;- 资讯列表页增加分类标签,实现按分类筛选资讯&#10;- 文章详情页新增相关推荐文章展示逻辑&#10;- 主页使用演示数据兜底导航、Banner、核心业务、公司简介和文章列表&#10;- 各页面均优化站点信息使用,优先展示真实数据,缺失时使用演示数据&#10;- 留言页增加公司联系信息展示,提升体验&#10;- 个人中心页支持分享官网,展示演示数据防止空白&#10;- 主页Banner支持无图时使用渐变背景和文字占位&#10;- 统一使用演示数据,保证页面无空白,提升整体稳定性和用户体验">
<option name="closed" value="true" />
<created>1786533468126</created>
<option name="number" value="00084" />
<option name="presentableId" value="LOCAL-00084" />
<option name="project" value="LOCAL" />
<updated>1786533468126</updated>
</task>
<option name="localTasksCounter" value="85" />
<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(address): 新增收货地址与售后申请功能&#10;&#10;- 新增地址编辑页面,支持地址智能识别、地图选点、地区选择和默认地址设置&#10;- 实现地址列表页面,支持地址查看、删除、设为默认及选择返回结算页&#10;- 新增售后申请页面,支持退款类型选择、商品选择、原因填写、图片上传和提交审核&#10;- 修复 passport 分包配置,移除不存在分包并补充缺失声明,避免 Taro 编译报错&#10;- 新增地址类型定义,增强前端地址数据结构类型安全&#10;- 优化页面交互体验,完善表单校验及错误提示逻辑&#10;- 统一代码格式与命名规范,保持代码风格一致性" />
<MESSAGE value="feat(passport): 迁移并修复小程序支付页面访问 websopy API 问题&#10;&#10;- 迁移 src/passport/pay 相关代码至当前项目,无需替换文件&#10;- 新增环境变量 WEBSOPY_API_BASE_URL,提供 websopy API 基础地址&#10;- 在 config/app.ts 导出 WebsopyBaseUrl,供代码引用&#10;- 配置 env.d.ts 类型定义,消除 TypeScript 隐式 any 错误&#10;- request 请求方法支持自定义 baseUrl 以调用不同接口地址&#10;- passport/pay 页面调用订阅接口统一传入 WebsopyBaseUrl&#10;- 修正 tenantId 类型为 number,调整登录用户类型断言&#10;- 确认编译成功,dist 目录生成对应文件及正确请求地址&#10;- 说明 type-check 依赖库问题不影响正常构建和运行" />
<MESSAGE value="feat(passport): 优化支付成功后页面跳转逻辑&#10;&#10;- 修改支付成功后的自动跳转目标,从已购产品页面改为我的页面&#10;- 使用 switchTab 替代 navigateTo 保证在 tabBar 页顺利跳转&#10;- 移除多余的跳转失败回退逻辑,简化跳转流程&#10;- 调整跳转延迟时间为 1500 毫秒,提升用户体验" />
<MESSAGE value="feat(user): 改为动态查询 VIP 角色ID,避免写死角色ID&#10;&#10;- RoleParam 类型新增 tenantId 字段,支持按租户过滤角色&#10;- 新增 getCurrentTenantId 获取当前租户ID,支持登录态和默认租户回退&#10;- 实现 getVipRoleId 函数,按 roleCode 与 tenantId 查询 VIP 角色ID&#10;- assignVipRole 中动态获取 VIP 角色ID,避免硬编码&#10;- 添加用户角色时带上 tenantId,确保多租户角色绑定正确&#10;- 移除写" />
<MESSAGE value="feat(user): 动态查询VIP角色ID,避免硬编码绑定问题&#10;&#10;- RoleParam新增tenantId字段,支持按租户过滤角色&#10;- 新增getCurrentTenantId函数,兼容登录态及默认租户回退&#10;- 实现getVipRoleId函数,按roleCode与tenantId查询VIP角色ID&#10;- assignVipRole动态获取VIP角色ID,移除写死的角色ID&#10;- 添加用户角色时传" />
<MESSAGE value="feat(user): 统一VIP概念为开发者并优化开发者登录及申请&#10;&#10;- 将VIP相关文案、图标改为开发者,涉及用户中心、门店中心、审核页等&#10;- 优化登录弹窗UI,改为红色系,提升视觉统一性和交互体验&#10;- 关于我们页面品牌和公司信息内容更新,展示最新企业介绍和业务范围&#10;- VIP升级页新增“所在城市”选择,使用微信原生地区选择器,完善地址信息&#10;- 用户申请表单中新增城市字段及校验,提交时携带城市信息&#10;- 优化审核页面文案,统称开发者身份,明确申请审核内容&#10;- 修复UserRoleController.save接口bug,避免userId被无条件覆盖导致角色绑错&#10;- 动态查询VIP角色ID,避免硬编码,支持多租户环境正确绑定角色" />
<MESSAGE value="refactor(user): 删除门店信息输入框&#10;&#10;- 移除用户资料页面中的门店名称输入框&#10;- 移除用户资料页面中的门店地址输入框&#10;- 简化用户页面界面布局,提高界面整洁度" />
<MESSAGE value="refactor(user): 统一品牌名称为“威数谱软件”&#10;&#10;- 将项目中所有“威硕谱软件”名称修改为“威数谱软件”&#10;- 更新协议页面、欢迎消息、礼品卡等多处文本内容&#10;- 修改小程序配置文件中的项目名称和显示标题&#10;- 优化开发者申请页部分文案,明确开发者身份描述&#10;- 保证所有界面及配置中的品牌名称统一一致" />
<MESSAGE value="refactor(user): 将VIP身份统一改为开发者标记&#10;&#10;- 审核通过时不再新增角色,而是更新用户isDeveloper布尔标记&#10;- 后端UserService新增markAsDeveloper方法,通过updateById设置isDeveloper&#10;- UserController新增PUT接口更新开发者标记,权限使用sys:userRole:save&#10;- 前端api新增setUserDeveloper调用新的审核接口&#10;- vip-review页面移除角色相关代码,改为调用setUserDeveloper方法&#10;- 约定开发者身份即sys_user.is_developer字段,不再依赖角色表记录&#10;- app.config.ts追加开发者中心与平台管理相关页面路由&#10;- 新增开发者中心、平台管理的多个API模块(apikey、appProduct、appDomain、developer请求等)&#10;- 添加公共工具方法和复用组件用于开发者中心及平台管理功能&#10;- 统一品牌名称为“威数谱软件”,更新文案及界面展现一致性" />
<MESSAGE value="feat(weapp): 新增开发者中心与平台管理入口及相关功能&#10;&#10;- 小程序端新增开发者中心和平台管理页面模块,完善功能入口&#10;- 新增开发者中心页面(概览、我的应用、API Key和工单)&#10;- 新增平台管理页面(管理首页、应用审核、Git权限审核、域名绑定审核和工单处理)&#10;- 复刻PC端/api/app/*相关API接口,基地址使用WEBSOPY_API_BASE_URL&#10;- 增加公共组件StatCard、EntryGrid、CellRow以支持新页面UI&#10;- 修改首页增加开发者中心和平台管理角色入口,仅对对应用户显示&#10;- 用户页面菜单新增开发者中心和平台管理入口条件展示&#10;- 约定开发者身份使用sys_user.is_developer布尔字段,不再依赖角色表&#10;- 更新登录态业务,菜单是否显示基于登录接口返回" />
<MESSAGE value="feat(weapp): 新增开发者中心与平台管理入口及功能&#10;&#10;- 小程序端新增开发者中心和平台管理页面模块,完善功能入口&#10;- 新增开发者中心页面(概览、我的应用、API Key和工单)&#10;- 新增平台管理页面(管理首页、应用审核、Git权限审核、域名绑定审核和工单处理)&#10;- 复刻PC端/api/app/*相关API接口,基地址使用WEBSOPY_API_BASE_URL&#10;- 增加公共组件StatCard、EntryGrid、CellRow以支持新页面UI&#10;- 修改首页增加开发者中心和平台管理角色入口,仅对对应用户显示&#10;- 用户页面菜单新增开发者中心和平台管理入口条件展示&#10;- 约定开发者身份使用sys_user.is_developer布尔字段,不再依赖角色表&#10;- 更新登录态业务,菜单是否显示基于登录接口返回的isDeveloper/isAdmin字段&#10;- vip-review审核通过流程调整为只调用setUserDeveloper,后台替换角色为developer并置isDeveloper=1" />
<MESSAGE value="fix(weapp): 修复支付页nutui组件导致内容空白问题&#10;&#10;- 替换支付页中nutui的Button、Cell、Price、Divider组件为Taro原生组件或项目自定义组件&#10;- 修正商品名称、价格类型和支付金额显示,使用flex布局及Text组件自绘内容&#10;- 调整按钮样式,统一为带蓝色背景和圆角的自定义样式&#10;- 移除nutui Price组件中的thousands属性,使用项目自定义Price组件&#10;- 解决微信小程序中nutui组件因div渲染缺失导致UI元素不显示的问题&#10;- 保持支付流程及状态逻辑无改动,仅优化展示兼容性和稳定性" />
<MESSAGE value="feat(user): 补充开发者审核通过时添加店员记录功能&#10;&#10;- 引入 addShopStoreUser 接口用于新增门店店员记录&#10;- 新增 clerkStoreId 和 clerkTenantId 状态,保存当前审核店员的门店和租户信息&#10;- 在审核通过后调用 addShopStoreUser,添加申请人为当前门店店员&#10;- 保证只有当 clerkStoreId 存在时才执行新增操作&#10;- 更新工作记录文档,补充审核通过流程中新添加的店员记录步骤&#10;- 保持原有升级开发者逻辑和接口调用不变" />
<MESSAGE value="feat(user): 补充开发者审核通过时添加店员记录功能&#10;&#10;- 引入 addShopStoreUser 接口用于新增门店店员记录&#10;- 新增 clerkStoreId 和 clerkTenantId 状态,保存当前审核店员的门店和租户信息&#10;- 在审核通过后调用 addShopStoreUser,添加申请人为当前门店店员&#10;- 保证只有当 clerkStoreId 存在时才执行新增操作&#10;- 调整平台管理页 UI,使其与门店中心保持一致风格&#10;- 门店中心及平台管理入口权限调整,仅超级管理员可访问&#10;- 用户菜单中平台管理和门店中心入口现基于 isSuperAdmin 字段控制&#10;- 优化登录及权限校验流程,避免权限不足时页面闪烁显示&#10;- 更新工作记录文档,补充审核通过流程中新添加的店员记录步骤" />
<MESSAGE value="feat(user): 合并并切换“申请开发者”菜单项状态&#10;&#10;- 在用户菜单中根据 isDeveloper 字段切换显示“申请成为开发者”或“进入开发者中心”&#10;- 已通过审核用户直接显示“进入开发者中心”且高亮显示&#10;- 去除冗余的独立“开发者中心”菜单条目,简化菜单结构&#10;- 调整平台管理页和门店中心入口权限为仅超级管理员可访问&#10;- 优化登录与权限校验流程,防止权限不足时页面闪烁或无访问权限空白&#10;- 更新相关文档,完善审核通过后店员记录添加流程" />
<MESSAGE value="fix(auth): 统一超级管理员标识为布尔值true&#10;&#10;- 将所有页面和菜单中超级管理员校验由数字1改为布尔true&#10;- 平台管理、门店中心菜单项仅在isSuperAdmin===true时显示&#10;- 门店中心页面移除店员门槛,改为超级管理员访问限制&#10;- 非超级管理员访问门店中心弹窗提示并返回&#10;- 首页工作台入口及相关访问权限判定切换为布尔判断&#10;- 优化首屏加载状态,避免权限校验闪烁和误判" />
<MESSAGE value="feat(user): 合并切换开发者菜单项状态并更新开发者中心UI&#10;&#10;- 用户菜单中根据 isDeveloper 字段条件展示“申请成为开发者”或“进入开发者中心”&#10;- 已审核通过用户菜单显示“进入开发者中心”,并设置高亮样式&#10;- 去除冗余的独立“开发者中心”菜单条目,简化菜单结构&#10;- 重新设计开发者中心页面UI,统一为门店中心风格&#10;- 顶部展示开发者头像及昵称手机号信息&#10;- 功能卡片展示“我的应用”、“API Key”和“我的工单”,异步加载数量角标&#10;- 底部添加“开发者帮助与支持”入口,替代门店订阅消息卡片&#10;- 未登录或非开发者状态显示相应空态提示及操作按钮&#10;- 优化登录状态与权限校验逻辑,防止页面闪烁和无权限内容显示" />
<MESSAGE value="feat(user): 合并切换开发者菜单并优化首页轮播图显示&#10;&#10;- 用户菜单根据 isDeveloper 字段切换显示申请或进入开发者中心&#10;- 已审核用户菜单高亮显示“进入开发者中心”&#10;- 去除冗余开发者中心菜单条目,简化结构&#10;- 重新设计开发者中心页面UI,统一门店中心风格&#10;- 顶部展示开发者头像及昵称手机号信息&#10;- 功能卡片展示“我的应用”、“API Key”和“我的工单”,异步加载数量角标&#10;- 底部添加“开发者帮助与支持”入口,替代门店订阅消息卡片&#10;- 未登录或非开发者状态显示相应空态及操作按钮&#10;- 首页轮播图为空时不显示容器,功能入口自动上移,优化空状态体验&#10;- 优化登录状态与权限校验逻辑,防止页面闪烁和无权限展示" />
<MESSAGE value="feat(checkout): 优化微信支付和线下付款流程&#10;&#10;- 将默认支付方式调整为微信支付(payType=1),增加支付方式选择项&#10;- 微信支付成功后直接调用微信支付接口并处理支付结果反馈&#10;- 支付取消或失败时显示对应提示信息&#10;- 线下付款(payType=9)保留并显示相应提示,不触发在线支付&#10;- 提交订单时根据支付方式显示不同的按钮文本 (‘提交并支付’ 或 ‘提交订单’)&#10;- 针对微信支付配置缺失的错误,统一显示详细提示,便于定位问题&#10;- 优化支付方式选中时的提示样式与说明,提升用户体验" />
<MESSAGE value="feat(checkout): 优化微信支付和线下付款流程&#10;&#10;- 支付方式新增微信支付(payType=1),并设为默认,保留线下付款(payType=9)&#10;- 微信支付时调用 Taro.requestPayment 发起支付流程,取消或失败给出明确提示&#10;- 支付选项选中状态下微信支付显示绿色提示,线下付款显示橙色提示&#10;- 提交按钮文案区分微信支付(提交并支付)与线下付款(提交订单)&#10;- 后端支付配置缺失时提示“微信支付暂不可用:支付配置缺失”&#10;- 确保编译构建通过,未引入新类型错误" />
<MESSAGE value="feat(official): 实现企业官网分包及内容展示&#10;&#10;- 新增官方分包目录及页面(首页、资讯列表、详情、关于我们、留言、我的)&#10;- 自定义实现底部导航组件 OfficialTabBar,解决微信 tabBar 不支持分包问题&#10;- 统一接口调用走 shop-api 代理,无新增 CMS 域名,减少合法域名负担&#10;- 新增 CMS 站点信息获取接口及留言咨询提交与查询接口&#10;- 设计并完善官网富文本样式,品牌色统一为 #185FA5,并更新 tailwind 配置&#10;- 首页新增企业官网入口卡片,方便用户访问官网内容&#10;- 完成官网分包构建验证,解决 Taro 构建批量删除保护问题&#10;- 规范 CMS 字段使用,适配字段名称及状态含义&#10;- 后续计划对历史页面 NutUI 组件进行空壳化整改,及更新 UI 设计文档" />
<MESSAGE value="refactor(cms): 迁移CMS模块接口到独立cms-api服务&#10;&#10;- 新增cmsRequest工具,基于request封装,默认baseURL为cms-api&#10;- 改用cmsRequest替换所有cms相关请求,确保请求走独立域名&#10;- 配置三环境新增CMS_API_BASE_URL及导出CmsBaseUrl常量&#10;- 解决CMS接口走shop-api时返回404的问题,避免小程序域名白名单冲突&#10;- 统一修改34个cms API模块的261处调用,保持接口行为一致&#10;- 修复部分拼接全URL方式改为相对路径调用cmsRequest&#10;- 更新注释说明并调整相关接口调用代码&#10;- 兼容macOS sed替换问题,采用perl命令完成批量替换" />
<MESSAGE value="refactor(common): 移除公共组件中的 NutUI Button,改用 Taro 原生 Button&#10;&#10;- `EmptyState` 和 `FixedButton` 两个组件全部替换 NutUI Button 为 Taro 原生 Button&#10;- 解决空状态按钮在微信小程序端不可见的问题,确保按钮正常显示&#10;- `FixedButton` 组件虽然未被调用,仍保留但调整实现,icon 由 prop 改为 children 渲染&#10;- 统一主色使用 tailwind 的 `bg-red-500`,弃用原 NutUI primary 颜色&#10;- 全局修复微信小程序原生 button 的 `::after` 灰色描边问题,在全局样式中设置 `button::after { border: none; }`&#10;- NutUI Button 的引用文件数由 27 减至 25,`src/components/common/` 完全无 NutUI 依赖&#10;- CMS 请求链路调整,所有 CMS 接口改用独立 `cmsRequest` 实现" />
<MESSAGE value="refactor(common): 移除 NutUI Button 并改用 Taro 原生 Button&#10;&#10;- `EmptyState` 和 `FixedButton` 组件替换 NutUI Button 为 Taro 原生 Button&#10;- 解决空状态按钮在微信小程序端不可见问题,确保按钮正常显示&#10;- `FixedButton` 组件调整实现,icon 由 prop 改为 children 渲染&#10;- 统一主色改用 tailwind 的 `bg-red-500`,弃用 NutUI primary 颜色&#10;- 全局样式修复微信小程序原生 button 的 `::after` 灰色描边问题&#10;- NutUI Button 引用文件数由 27 减至 25,`src/components/common/` 无 NutUI 依赖&#10;- CMS 请求链路调整,所有 CMS 接口改用独立 `cmsRequest` 实现" />
<MESSAGE value="refactor(official): 使用演示数据兜底官网内容展示&#10;&#10;- 在关于我们页补充企业优势和联系方式演示数据展示&#10;- 资讯列表页增加分类标签,实现按分类筛选资讯&#10;- 文章详情页新增相关推荐文章展示逻辑&#10;- 主页使用演示数据兜底导航、Banner、核心业务、公司简介和文章列表&#10;- 各页面均优化站点信息使用,优先展示真实数据,缺失时使用演示数据&#10;- 留言页增加公司联系信息展示,提升体验&#10;- 个人中心页支持分享官网,展示演示数据防止空白&#10;- 主页Banner支持无图时使用渐变背景和文字占位&#10;- 统一使用演示数据,保证页面无空白,提升整体稳定性和用户体验" />
<option name="LAST_COMMIT_MESSAGE" value="refactor(official): 使用演示数据兜底官网内容展示&#10;&#10;- 在关于我们页补充企业优势和联系方式演示数据展示&#10;- 资讯列表页增加分类标签,实现按分类筛选资讯&#10;- 文章详情页新增相关推荐文章展示逻辑&#10;- 主页使用演示数据兜底导航、Banner、核心业务、公司简介和文章列表&#10;- 各页面均优化站点信息使用,优先展示真实数据,缺失时使用演示数据&#10;- 留言页增加公司联系信息展示,提升体验&#10;- 个人中心页支持分享官网,展示演示数据防止空白&#10;- 主页Banner支持无图时使用渐变背景和文字占位&#10;- 统一使用演示数据,保证页面无空白,提升整体稳定性和用户体验" />
<option name="NON_MODAL_COMMIT_POSTPONE_SLOW_CHECKS" value="false" />
</component>
</project>
+28
View File
@@ -0,0 +1,28 @@
{
"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"
}
]
},
"lastUpdated": 1775909408190
}
-29
View File
@@ -1,29 +0,0 @@
# 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` 类型包解析问题失败,这是项目既有环境问题,不影响构建。
-15
View File
@@ -1,15 +0,0 @@
# 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`,一致。
-74
View File
@@ -1,74 +0,0 @@
# 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 进行中),待确认无报错。
-86
View File
@@ -1,86 +0,0 @@
# 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 &&` 整个容器才渲染」;空时整块(含占位框)不显示,下方功能入口自动上移。
-12
View File
@@ -1,12 +0,0 @@
# 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`。
-16
View File
@@ -1,16 +0,0 @@
# 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` 重新支付)一致。
-34
View File
@@ -1,34 +0,0 @@
# 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,属长尾待办。
-25
View File
@@ -1,25 +0,0 @@
# 2026-08-12 工作记录
## 企业官网 official 分包白屏根因与修复
### 现象
进入「企业官网」(official/index) 整页白屏;其余 official 页(article/about 等)也有同样产物结构风险。
### 根因(两层)
1. **构建产物错配**:official 各页原先是 `src/official/<page>/index.tsx`(文件夹+index)写法,Taro 编译时把 `.js` 输出到扁平层 `dist/official/index.js`,却把 `.json/.wxml` 输出到嵌套层 `dist/official/index/index.*`,二者不同目录。微信按 `official/index` 找 `dist/official/index.json` 时找不到 → 白屏。对比 passport 分包:`login.tsx` 用扁平文件写法,产物全扁平共址、正常。
2. **构建被 SAFE_DELETE 守卫打断**:`build:weapp` 的 `emptyOutputDir` 触发批量删除守卫,导致某次构建流产,index 页 `.js` 缺失(白屏直接表现)。
### 修复
- 将 official 6 个页面由「文件夹+index.tsx」改为**扁平文件** `src/official/<page>.tsx`(同 passport/login.tsx),并修正相对引用 `../components`→`./components`、`../data`→`./data`。app.config 分包 pages 不变(仍为 index/article/article-detail/about/message/mine)。
- 新增 `scripts/safe-build-weapp.sh`:构建前先 `mv dist /tmp/taro-dist-bak/dist-$(date +%s)` 规避 SAFE_DELETE 守卫,再用本地 `./node_modules/.bin/taro` 构建。package.json 加 `build:weapp:safe` 脚本。
- 重建后 6 页产物均为扁平共址(`dist/official/<page>.{js,json,wxml,wxss}`),nutui 残留 0,白屏消失。
### 内容增强(前端内置演示兜底)
新增 `src/official/data/demo.ts`:站点信息/导航宫格/Banner/核心业务/资讯/企业优势 演示数据(用 CSS 渐变占位、无外链裂图);CMS 有数据时自动覆盖。
- index 重写为:品牌头+Banner轮播+导航宫格+公司简介+核心业务横向卡+资讯动态+留言CTA+底部联系方式。
- article 加分类 tab(treeNavigation);article-detail 加相关推荐;about 加企业优势;mine 加分享官网(Button openType=share)+useShareAppMessage;message 接站点联系信息。
- 风格统一品牌色 `brand-600=#185FA5`(tailwind),富文本用 `.official-rich`。
### 注意事项
- 构建官方分包务必用 `npm run build:weapp:safe`,勿用 `npm run build:weapp`(会再触发 SAFE_DELETE 守卫打断)。
- official 页面必须保持**扁平文件**写法,不要再改回 `<page>/index.tsx` 文件夹结构,否则会重现 js/json 错配白屏。
-63
View File
@@ -1,63 +0,0 @@
# 项目长期记忆(websopy-taro)
## 核心约定
### 开发者身份的表示方式
- **开发者身份主校验位 = `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 仍对应这些旧名,重命名需谨慎。
### 超级管理员(isSuperAdmin=true)访问门槛(2026-07-23 确认)
- 用户对象字段 `isSuperAdmin`(`(user as any)?.isSuperAdmin === true`,布尔 true 才算),前端此前未引用,由后端 `sys_user` 返回。
- **平台管理**(`pages/admin/index/index`)与**门店中心**(`pages/store/center/index`)两个入口,**菜单与首页工作台均只在 `isSuperAdmin===true` 时显示**(原先平台管理是 `isAdmin`、门店中心是店员 `storeInfo`)。
- 门店中心页面已改为「仅超级管理员可进」:移除原 `getMyClerk()` 店员门槛;非超管访问弹「仅超级管理员可访问」并 `navigateBack`;超管若同时是店员仍会加载门店信息用于头部,否则头部兜底显示「门店中心」+ 管理员昵称/手机号。
- 两页都用 `useUser()` 的 `loading`(userLoading)做首屏门卫,避免超管用户未从 storage 解析完时先闪「无访问权限」。注意:`pages/store/center` 的入口曾依赖 `user.tsx` 里的 `storeInfo`(getMyClerk),现已不再用 `storeInfo` 决定菜单可见性。
### 后端代码风格(com.gxwebsoft.core)
- Controller 继承 `BaseController`,统一返回 `ApiResult`(成功 `code=0`,失败 `code=1`),用 `success()/fail()`。
- 「按 userId 置一个标志位」的范式:`new User(){{setUserId(); setXxx();}}` → `userService.updateById(u)`(参考 `updateStatus`/`updateRecommend`/`auditUser`)。
- 用户变更后如需同步 websopy,经 `syncMessageProducer.sendUserSyncMessage("websopy","UPDATE",user)`(在 `UserServiceImpl` 内,`@Autowired(required=false)` 注入)。
- 用户相关代码都在 `com.gxwebsoft.common.system`(entity/service/controller),业务应用数据在 `com.gxwebsoft.websopy`(无 Controller)。
## 官网分包 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` 请求头。
- **⚠️ official 页面必须用扁平文件写法**(`src/official/<page>.tsx`,同 passport/login.tsx),**不要用 `<page>/index.tsx` 文件夹结构**。文件夹写法会让 Taro 把 `.js` 输出到扁平层、`index.json/index.wxml` 输出到嵌套层,二者不同目录 → 微信按 `official/<page>` 找不到 `index.json` → 整包白屏。2026-08-12 已把 6 个页全部改扁平并修复。
- **⚠️ 构建官方分包必须用 `npm run build:weapp:safe`**(脚本 `scripts/safe-build-weapp.sh` 先 `mv dist` 再构建,规避 SAFE_DELETE 守卫打断)。勿用 `npm run build:weapp`,否则守卫会中断构建导致产物缺失/白屏。演示兜底数据在 `src/official/data/demo.ts`(CMS 有数据自动覆盖)。
- **⚠️ 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)。
## ⚠️ 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>`)被丢弃。
**症状**:组件位置完全空白 / 按钮不可见 / 价格显示为空。Text/View 与 `@nutui/icons-react-taro` 的图标(Tips/Check/Close/Clock,基于 SVG)渲染正常。
**解决办法**(最小改动):在该页面用 Taro 原生组件替代:
| nutui(不可用) | Taro 原生 / 项目自定义 |
|---|---|
| `import { Button, Cell, Price, Divider } from '@nutui/nutui-react-taro'` | `import { Button } from '@tarojs/components'` + `import Price from '@/components/common/Price'` |
| `<Cell title="X" description={Y} />` | `<View className="flex justify-between px-4 py-3 border-b border-gray-100"><Text className="text-gray-600">X</Text><Text className="text-gray-800">{Y}</Text></View>` |
| `<Price price={n} size="large" thousands />` | `<Price price={n} size="large" />`(**用项目自定义 Price**,不用 nutui 的) |
| `<Divider />` | 用 `border-b border-gray-100` 替代 |
| `<Button type="primary" block loading={x} onClick={y}>...</Button>` | `<Button loading={x} onClick={y} className="flex-1 bg-blue-500 text-white rounded-full border-0">...</Button>`(className 控制颜色,`loading` prop 仍然支持) |
**适用范围判断**:项目里其它页面(qr-login、register、passport/forget 等)也用了 nutui 的 Cell/Card/Divider/Button,遇到同类"组件消失"现象时按同样模式替换即可。已修:`src/passport/pay/index.tsx`(2026-07-23,扫码进入小程序后支付页空白的 bug)。
**验证手段**:跑 `npm run build:weapp` 后在 `dist/<page>/index.js` 里 grep `nut-cell|nut-button|nut-divider|nut-price`,应为 0;grep `l.zx`(Taro Button 在 weapp 编译后的局部变量名)应出现 2 次(取消 + 立即支付)。
**已知的 nutui 组件清单**(按 2026-07-23 grep 结果):`Button` 在 `passport/pay, passport/login/setting/forget/register, pages/order/*, pages/booking/*, components/common/{FixedButton,EmptyState}` 等大量页面;`Cell` 在 `passport/pay, pages/order/order.tsx`;`Divider` 在 `passport/pay, passport/qr-login`;`Price` 在 `passport/pay`。修复顺序建议:pay(已修)→ 视觉影响最大的页面 → 批量。
-62
View File
@@ -1,62 +0,0 @@
# 小程序端 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、按租户适配模板
+59
View File
@@ -0,0 +1,59 @@
# 配置说明文档
## 环境配置
### 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
+201
View File
@@ -0,0 +1,201 @@
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.
+533 -204
View File
@@ -1,263 +1,592 @@
# Paopao Taro - 项目框架
<div align="center">
<h1>🚀 WebSoft 小程序</h1>
<p><strong>基于 Taro + React + TypeScript 的跨平台小程序应用</strong></p>
基于 Taro 4.0.8 + React 18.3.1 + TypeScript 5.7.2 的跨端开发框架
<p>
<img src="https://img.shields.io/badge/Taro-4.0.8-blue" alt="Taro">
<img src="https://img.shields.io/badge/React-18.3.1-61DAFB" alt="React">
<img src="https://img.shields.io/badge/TypeScript-5.7.2-3178C6" alt="TypeScript">
<img src="https://img.shields.io/badge/NutUI-3.0.16-orange" alt="NutUI">
<img src="https://img.shields.io/badge/TailwindCSS-3.4.17-38B2AC" alt="TailwindCSS">
<img src="https://img.shields.io/badge/License-MIT-blue" alt="License">
</p>
</div>
## 📦 技术栈
## 📖 项目简介
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) |
| 关注公众号 | ![输入图片说明](https://oss.wsdns.cn/20240327/f1175cc5aae741d3af05484747270bd5.jpeg?x-oss-process=image/resize,m_fixed,w_150/quality,Q_90) |
## 🛠️ 技术栈
### 核心框架
- **Taro**: `4.0.8` - 跨端开发框架
- **React**: `18.3.1` - UI 框架
- **TypeScript**: `5.7.2` - 类型系统
| 技术 | 版本 | 说明 |
|------|------|------|
| Taro | 4.0.8 | 跨平台开发框架 |
| React | 18.3.1 | 前端UI框架 |
| TypeScript | 5.7.2 | 类型安全的JavaScript |
| Webpack | 5.78.0 | 模块打包工具 |
| Babel | 7.26.0 | JavaScript编译器 |
| PostCSS | 8.4.49 | CSS后处理器 |
| ESLint | 8.57.1 | 代码质量检查 |
### UI 组件库
- **NutUI React Taro**: `2.7.4` - 京东风格的 Taro React 组件库
- **NutUI Icons**: `^1.0.0` - 图标库
### UI组件库
| 技术 | 版本 | 说明 |
|------|------|------|
| 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 Hooks** - 内置 Hooks
- **Context API** - 跨组件状态共享
### 功能组件
- **React Router DOM** - 路由管理
- **Day.js** - 日期时间处理
- **Crypto-js** - 加密解密工具
- **js-base64** - Base64编码解码
- **React Markdown** - Markdown渲染
- **ECharts Taro3 React** - 图表组件
### 工具库
- **Day.js**: `^1.11.13` - 日期处理
- **Crypto-js**: `^4.2.0` - 加密解密
- **React Router DOM**: `^6.28.0` - 路由管理
## 📋 环境要求
### 基础环境
- 📦 **Node.js 16+**
- 📱 **微信开发者工具** (开发微信小程序)
- 🔧 **支付宝开发者工具** (开发支付宝小程序)
- 🌐 **现代浏览器** (开发H5应用)
### 包管理器
- **npm** / **yarn** / **pnpm** (推荐使用pnpm)
### 开发工具
- **推荐**:VS Code / WebStorm
- **插件**:ES7+ React/Redux/React-Native snippets、TypeScript Importer
## 🚀 快速开始
### 安装依赖
### 1. 克隆项目
```bash
# 使用 npm
npm install
# 使用 yarn
yarn install
# 使用 pnpm
pnpm install
git clone https://github.com/websoft-top/mp-taro.git
cd mp-taro
```
### 开发模式
### 2. 安装依赖
```bash
# 微信小程序
# 使用 pnpm (推荐)
pnpm install
# 或使用 npm
npm install
# 或使用 yarn
yarn install
```
### 3. 配置开发环境
#### 环境变量配置
项目支持多环境配置,通过 `config/env.ts` 文件管理不同环境的配置:
```typescript
// config/env.ts
export const ENV_CONFIG = {
// 开发环境
development: {
API_BASE_URL: 'http://localhost:3000/api',
APP_NAME: 'Taro App Dev',
DEBUG: 'true',
},
// 生产环境
production: {
API_BASE_URL: 'https://api.example.com',
APP_NAME: 'Taro App',
DEBUG: 'false',
},
// 测试环境
test: {
API_BASE_URL: 'https://test-api.example.com',
APP_NAME: 'Taro App Test',
DEBUG: 'true',
}
}
```
#### 应用配置
编辑 `config/app.ts` 文件,配置租户ID等信息:
```typescript
export const TenantId = 'your_tenant_id'
export const BaseUrl = API_BASE_URL // 自动从环境配置读取
```
### 4. 启动开发服务器
#### 微信小程序开发
```bash
# 开发环境(默认)
npm run dev:weapp
# H5
npm run dev:h5
# 生产环境
NODE_ENV=production npm run dev:weapp
# 测试环境
NODE_ENV=test npm run dev:weapp
```
然后使用微信开发者工具打开 `dist` 目录
#### H5开发
```bash
# 启动H5开发模式
npm run dev:h5
```
访问 `http://localhost:10086` 查看H5版本
#### 其他平台
```bash
# 支付宝小程序
npm run dev:alipay
# 百度小程序
# 百度智能小程序
npm run dev:swan
# 字节跳动小程序
npm run dev:tt
# QQ 小程序
# QQ小程序
npm run dev:qq
# 京东小程序
npm run dev:jd
```
### 生产构建
## ⚙️ 配置说明
### 项目配置
主要配置文件位于 `config/` 目录:
- `config/index.ts` - Taro主配置文件
- `config/dev.ts` - 开发环境配置
- `config/prod.ts` - 生产环境配置
- `config/env.ts` - 环境变量配置(新增)
- `config/app.ts` - 应用配置(API地址、租户ID等)
### 环境变量配置
项目支持多环境配置,在 `config/env.ts` 中管理:
```typescript
// 环境变量配置
export const ENV_CONFIG = {
development: {
API_BASE_URL: 'http://localhost:3000/api',
APP_NAME: 'Taro App Dev',
DEBUG: 'true',
},
production: {
API_BASE_URL: 'https://api.example.com',
APP_NAME: 'Taro App',
DEBUG: 'false',
},
test: {
API_BASE_URL: 'https://test-api.example.com',
APP_NAME: 'Taro App Test',
DEBUG: 'true',
}
}
// 获取当前环境配置
export function getEnvConfig() {
const env = process.env.NODE_ENV || 'development'
if (env === 'production') {
return ENV_CONFIG.production
} else if (env === 'test') {
return ENV_CONFIG.test
} else {
return ENV_CONFIG.development
}
}
// 导出环境变量
export const { API_BASE_URL, APP_NAME, DEBUG } = getEnvConfig()
```
### 应用配置
在 `config/app.ts` 中配置:
```typescript
import { API_BASE_URL } from './env'
// 租户ID
export const TenantId = 'your_tenant_id'
// API基础地址(自动从环境配置读取)
export const BaseUrl = API_BASE_URL
// 其他配置...
```
### 环境切换
通过设置 `NODE_ENV` 环境变量来切换不同环境:
```bash
# 微信小程序
# 开发环境(默认)
npm run dev:weapp
# 生产环境
NODE_ENV=production npm run dev:weapp
# 测试环境
NODE_ENV=test npm run dev:weapp
```
## 🌍 环境变量详细说明
### 环境变量文件结构
```
config/
├── env.ts # 环境变量配置文件
├── app.ts # 应用配置(使用环境变量)
└── index.ts # Taro配置(注入环境变量)
```
### 支持的环境变量
| 变量名 | 说明 | 开发环境默认值 | 生产环境建议值 |
|--------|------|---------------|---------------|
| `API_BASE_URL` | API基础地址 | `http://localhost:3000/api` | `https://api.yourdomain.com/api` |
| `APP_NAME` | 应用名称 | `Taro App Dev` | `Your App Name` |
| `DEBUG` | 调试模式 | `true` | `false` |
### 环境变量使用方法
#### 1. 修改环境配置
编辑 `config/env.ts` 文件:
```typescript
export const ENV_CONFIG = {
development: {
API_BASE_URL: 'http://localhost:3000/api', // 修改为你的开发环境API地址
APP_NAME: 'My App Dev',
DEBUG: 'true',
},
production: {
API_BASE_URL: 'https://api.yourdomain.com/api', // 修改为你的生产环境API地址
APP_NAME: 'My App',
DEBUG: 'false',
}
}
```
#### 2. 在代码中使用环境变量
```typescript
// 在 config/app.ts 中
import { API_BASE_URL, APP_NAME, DEBUG } from './env'
export const BaseUrl = API_BASE_URL
export const AppName = APP_NAME
export const IsDebug = DEBUG === 'true'
// 在其他文件中使用
import { BaseUrl } from '@/config/app'
console.log('API地址:', BaseUrl)
```
#### 3. 运行时环境切换
```bash
# 开发环境(默认)
npm run dev:weapp
# 生产环境
NODE_ENV=production npm run dev:weapp
# 测试环境
NODE_ENV=test npm run dev:weapp
# 构建生产版本
NODE_ENV=production npm run build:weapp
```
### 环境变量最佳实践
1. **敏感信息处理**:不要在代码中硬编码敏感信息,使用环境变量管理
2. **环境隔离**:确保不同环境使用不同的API地址和配置
3. **默认值设置**:为所有环境变量提供合理的默认值
4. **类型安全**:使用TypeScript确保环境变量的类型安全
### 路径别名配置
项目已配置路径别名,可以使用以下方式导入:
```typescript
import Component from '@/components/Component'
import { request } from '@/utils/request'
import { useCart } from '@/hooks/useCart'
import config from '@/config/app'
```
### 样式配置
- **Sass**: 支持嵌套、变量、混入等特性
- **TailwindCSS**: 原子化CSS,配置文件为 `tailwind.config.js`
- **PostCSS**: 自动处理CSS兼容性,配置文件为 `postcss.config.js`
## 🎯 核心功能
### 🔐 用户认证与授权
- **微信登录**:基于微信OpenID的用户认证
- **短信登录**:支持手机号短信验证登录
- **用户注册**:完整的用户注册流程
- **密码找回**:支持手机号找回密码
### 📝 内容管理系统(CMS)
- **文章浏览**:支持文章列表和详情查看
- **分类管理**:内容分类浏览
- **富文本渲染**:支持Markdown内容渲染
- **媒体展示**:图片、视频等媒体文件展示
### 🛒 电商系统
- **商品展示**:商品列表、详情、分类浏览
- **购物车**:商品加入购物车、数量管理
- **订单管理**:订单创建、查看、状态跟踪
- **地址管理**:收货地址的增删改查
- **支付集成**:支持微信支付等支付方式
### 👤 用户中心
- **个人资料**:用户信息查看和编辑
- **实名认证**:用户身份验证功能
- **帮助中心**:常见问题和帮助文档
- **关于我们**:应用介绍和联系方式
- **设置中心**:个人偏好设置
### 📊 数据可视化
- **图表展示**:基于ECharts的数据可视化
- **统计分析**:业务数据统计展示
- **实时更新**:动态数据更新
## 🏗️ 项目结构
```
src/
├── app.config.ts # 应用配置文件
├── app.scss # 全局样式
├── app.ts # 应用入口文件
├── api/ # API接口定义
│ ├── cms/ # 内容管理相关API
│ ├── shop/ # 商城相关API
│ ├── system/ # 系统相关API
│ ├── passport/ # 用户认证相关API
│ └── index.ts # API统一导出
├── assets/ # 静态资源
│ └── tabbar/ # 底部导航图标
├── components/ # 公共组件
│ ├── Header.tsx # 页面头部组件
│ ├── TabBar.tsx # 底部导航组件
│ ├── GoodsList.tsx # 商品列表组件
│ └── ...
├── hooks/ # 自定义Hooks
│ └── useCart.ts # 购物车Hook
├── pages/ # 页面文件
│ ├── index/ # 首页
│ ├── cart/ # 购物车页面
│ ├── order/ # 订单页面
│ └── user/ # 用户中心页面
├── passport/ # 用户认证页面
│ ├── login.tsx # 登录页面
│ ├── register.tsx # 注册页面
│ └── ...
├── cms/ # 内容管理页面
│ ├── category/ # 分类页面
│ └── detail/ # 详情页面
├── shop/ # 商城页面
│ ├── category/ # 商品分类
│ ├── goodsDetail/ # 商品详情
│ ├── orderConfirm/ # 订单确认
│ └── orderDetail/ # 订单详情
├── user/ # 用户相关页面
│ ├── profile/ # 个人资料
│ ├── address/ # 地址管理
│ ├── setting/ # 设置
│ └── ...
└── utils/ # 工具函数
├── request.ts # 网络请求封装
├── common.ts # 通用工具函数
├── time.ts # 时间处理工具
└── server.ts # 服务端相关工具
config/ # 配置文件目录
├── index.ts # Taro主配置
├── dev.ts # 开发环境配置
├── prod.ts # 生产环境配置
├── env.ts # 环境变量配置
└── app.ts # 应用配置
types/ # 类型定义
└── global.d.ts # 全局类型定义
```
## 🔧 开发规范
### 代码结构
- **Pages**:页面组件,负责页面逻辑和UI展示
- **Components**:公共组件,可复用的UI组件
- **Hooks**:自定义Hooks,封装业务逻辑
- **Utils**:工具函数,通用的功能函数
- **API**:接口定义,与后端API的交互
### 命名规范
- **组件名**:使用大驼峰命名法(PascalCase)
- **文件名**:使用小驼峰命名法(camelCase)或短横线命名法(kebab-case)
- **变量名**:使用小驼峰命名法(camelCase)
- **常量**:使用全大写,下划线分隔
- **CSS类名**:使用短横线命名法(kebab-case)
### TypeScript规范
- 优先使用接口(interface)定义类型
- 为函数参数和返回值添加类型注解
- 使用泛型提高代码复用性
- 避免使用 `any` 类型
## 📚 构建与部署
### 构建命令
```bash
# 构建微信小程序
npm run build:weapp
# H5
# 构建H5应用
npm run build:h5
# 其他平台类似
npm run build:[platform]
# 构建支付宝小程序
npm run build:alipay
# 构建其他平台
npm run build:swan # 百度智能小程序
npm run build:tt # 字节跳动小程序
npm run build:qq # QQ小程序
npm run build:jd # 京东小程序
```
## 📁 项目结构
### 主要API模块
项目API按功能模块组织:
```
shop-taro/
├── config/ # Taro 配置文件
│ ├── index.ts # 主配置
│ ├── dev.ts # 开发配置
│ └── prod.ts # 生产配置
├── src/ # 源代码
│ ├── app.config.ts # 应用配置(页面路由、tabBar等)
│ ├── app.tsx # 应用入口
│ ├── app.scss # 全局样式
│ ├── pages/ # 页面目录
│ │ ├── index/ # 首页
│ │ │ ├── index.tsx
│ │ │ ├── index.scss
│ │ │ └── index.config.ts
│ │ └── home/ # 个人中心页
│ │ ├── home.tsx
│ │ ├── home.scss
│ │ └── home.config.ts
│ ├── components/ # 自定义组件
│ ├── contexts/ # Context API
│ │ └── AppContext.tsx # 应用上下文
│ ├── hooks/ # 自定义 Hooks
│ │ └── useAppContext.ts
│ ├── utils/ # 工具函数
│ │ └── index.ts # 常用工具(日期、加密、防抖节流等)
│ ├── types/ # TypeScript 类型定义
│ │ └── global.d.ts # 全局类型声明
│ ├── styles/ # 样式文件
│ └── assets/ # 静态资源
│ └── tabbar/ # tabBar 图标
├── package.json # 项目依赖
├── tsconfig.json # TypeScript 配置
├── tailwind.config.js # TailwindCSS 配置
├── postcss.config.js # PostCSS 配置
├── project.config.json # 小程序项目配置
└── README.md # 项目说明
```
- **用户认证**: `src/api/passport/` - 登录、注册、权限验证
- **用户管理**: `src/api/system/user/` - 用户信息管理
- **内容管理**: `src/api/cms/` - 文章、媒体文件管理
- **商城管理**: `src/api/shop/` - 商品、订单管理
- **系统管理**: `src/api/system/` - 系统配置、参数管理
## ✨ 特性
## 🚀 部署指南
### 1. 主题切换
- 支持浅色/深色模式切换
- 使用 Context API 管理主题状态
- 暗黑模式适配
### 微信小程序部署
1. 构建小程序代码:
```bash
npm run build:weapp
```
2. 使用微信开发者工具打开 `dist` 目录
3. 点击"上传"按钮,上传代码到微信后台
4. 在微信公众平台提交审核
### 2. NutUI 组件库
- 已集成 @nutui/nutui-react-taro@2.7.4
- 支持按需引入
- 中文国际化配置
### H5应用部署
1. 构建H5应用:
```bash
npm run build:h5
```
2. 将 `dist` 目录下的文件上传到Web服务器
3. 配置Nginx或Apache服务器
4. 确保HTTPS访问(微信要求)
### 3. TailwindCSS 集成
- 支持原子化 CSS 编写
- 暗黑模式支持(dark mode)
- 自定义主题色配置
### 其他平台部署
各平台部署流程类似,构建对应平台代码后,使用相应的开发者工具上传即可。
### 4. 工具函数封装
- **日期处理**: 基于 Day.js 的格式化
- **加密解密**: MD5、AES 加密
- **性能优化**: 防抖、节流函数
- **数据操作**: 深拷贝等工具
### 生产环境配置
在生产环境中,需要修改相关配置:
### 5. TypeScript 支持
- 完整的类型定义
- 路径别名配置 `@/*`
- 严格模式开启
1. **环境变量配置**:修改 `config/env.ts` 中的生产环境配置
```typescript
production: {
API_BASE_URL: 'https://your-production-api.com/api',
APP_NAME: 'Your App Name',
DEBUG: 'false',
}
```
## 🎨 代码示例
2. **构建配置**:修改 `config/prod.ts` 中的构建配置
- 启用代码压缩和混淆
- 配置CDN加速
- 优化资源加载
### 使用 NutUI 组件
```tsx
import { Button, Cell, CellGroup } from '@nutui/nutui-react-taro'
export default function MyPage() {
return (
<CellGroup>
<Cell title="标题" description="描述" />
<Button type="primary" block>
提交
</Button>
</CellGroup>
)
}
```
### 使用 TailwindCSS
```tsx
<View className="p-4 bg-white dark:bg-gray-800">
<Text className="text-lg font-bold text-gray-800 dark:text-white">
暗黑模式适配文本
</Text>
</View>
```
### 使用 Context 管理状态
```tsx
import { useAppContext } from '@/hooks/useAppContext'
export default function MyPage() {
const { theme, toggleTheme } = useAppContext()
return (
<Button onClick={toggleTheme}>
当前主题: {theme}
</Button>
)
}
```
### 使用工具函数
```tsx
import { formatDate, md5, debounce } from '@/utils'
// 日期格式化
const dateStr = formatDate(new Date())
// MD5 加密
const encrypted = md5('hello world')
// 防抖
const handleSearch = debounce((keyword) => {
console.log(keyword)
}, 500)
```
## 📝 开发规范
### 文件命名
- 页面目录: `kebab-case` (如 `user-profile/`)
- 组件文件: `PascalCase` (如 `UserProfile.tsx`)
- 工具文件: `camelCase` (如 `formatDate.ts`)
### 代码风格
- 使用 ESLint + @typescript-eslint 进行代码检查
- 使用 2 空格缩进
- 分号结尾
- 单引号优先
### 提交规范
```
feat: 新功能
fix: 修复 bug
docs: 文档更新
style: 代码格式调整
refactor: 重构
test: 测试相关
chore: 构建/工具配置
```
3. **部署命令**:
```bash
NODE_ENV=production npm run build:weapp
```
## 🔧 常见问题
### 1. TailwindCSS 样式不生效?
检查 `postcss.config.js` 配置,确保已安装 `postcss-nested`。
### 开发环境问题
1. **Node.js版本问题**:确保使用Node.js 16+版本
2. **依赖安装失败**:尝试清除缓存后重新安装
```bash
npm cache clean --force
npm install
```
3. **编译错误**:检查TypeScript配置和代码语法
### 2. NutUI 组件样式丢失?
确保在 `app.scss` 中引入了 NutUI 样式:
```scss
@import '@nutui/nutui-react-taro/dist/styles/vitamin.css';
```
### 小程序开发问题
1. **微信开发者工具无法预览**:检查appid配置和网络连接
2. **API请求失败**:确认后端服务正常运行,检查域名白名单
3. **样式显示异常**:检查rpx单位使用和NutUI组件样式
### 3. TypeScript 路径别名报错?
检查 `tsconfig.json` 的 `paths` 配置,并确保 IDE 正确识别。
### 性能优化建议
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 本仓库
2. 创建特性分支 (`git checkout -b feature/AmazingFeature`)
3. 提交更改 (`git commit -m 'Add some AmazingFeature'`)
4. 推送到分支 (`git push origin feature/AmazingFeature`)
5. 打开 Pull Request
## 📄 License
## 📄 许可证
MIT
本项目采用 MIT 许可证 - 查看 [LICENSE](LICENSE) 文件了解详情
## 📞 联系我们
- 官网:https://websoft.top
- 邮箱:170083662@qq.com
- QQ群:479713884
---
**构建时间**: 2026-05-10
**作者**: Senior Developer
⭐ 如果这个项目对您有帮助,请给我们一个星标!
+209
View File
@@ -0,0 +1,209 @@
# 微信小程序扫码登录功能实现
## 项目概述
本项目已完整实现微信小程序扫码登录功能,支持用户通过小程序扫描网页端二维码快速登录。
## 🎯 功能特性
- ✅ **完整的后端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
+20 -7
View File
@@ -1,11 +1,24 @@
// babel-preset-taro 更多选项和默认值:
// https://github.com/NervJS/taro/blob/next/packages/babel-preset-taro/README.md
module.exports = {
"presets": [
["taro", {
"framework": "react",
"ts": true
}]
presets: [
['taro',
{
framework: 'react',
ts: 'true',
compiler: 'webpack5',
}]
],
"plugins": [
"@babel/plugin-transform-runtime"
plugins: [
[
"import",
{
"libraryName": "@nutui/nutui-react-taro",
"libraryDirectory": "dist/esm",
"style": 'css',
"camel2DashComponentName": false
},
'nutui-react-taro'
]
]
}
+13 -8
View File
@@ -1,9 +1,14 @@
import { API_BASE_URL, SERVER_API_URL, WEBSOPY_API_BASE_URL, CMS_API_BASE_URL } from './env'
import { API_BASE_URL } from './env'
export const TenantId = '5'
export const TenantName = '赛联小助手'
export const BaseUrl = API_BASE_URL
export const ServerBaseUrl = SERVER_API_URL
export const WebsopyBaseUrl = WEBSOPY_API_BASE_URL
export const CmsBaseUrl = CMS_API_BASE_URL
export const Version = 'v3.0.0'
// 租户ID - 请根据实际情况修改
export const TenantId = '5';
// 租户名称
export const TenantName = '网宿软件';
// 接口地址 - 请根据实际情况修改
export const BaseUrl = API_BASE_URL;
// 当前版本
export const Version = 'v3.0.8';
// 版权信息
export const Copyright = 'WebSoft Inc.';
// java -jar CertificateDownloader.jar -k 0kF5OlPr482EZwtn9zGufUcqa7ovgxRL -m 1723321338 -f ./apiclient_key.pem -s 2B933F7C35014A1C363642623E4A62364B34C4EB -o ./
+10 -14
View File
@@ -1,17 +1,13 @@
import type { UserConfigExport } from "@tarojs/cli";
export default {
env: {
NODE_ENV: '"development"'
logger: {
quiet: false,
stats: true
},
defineConstants: {
__DEV__: JSON.stringify(true)
},
mini: {},
h5: {
devServer: {
host: 'localhost',
port: 10086,
hot: true,
open: false
mini: {
miniCssExtractPluginOption: {
ignoreOrder: true
}
}
}
},
h5: {}
} satisfies UserConfigExport<'webpack5'>
-30
View File
@@ -1,30 +0,0 @@
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
-34
View File
@@ -1,34 +0,0 @@
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()
+43
View File
@@ -0,0 +1,43 @@
// 环境变量配置
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()
+53 -55
View File
@@ -1,108 +1,97 @@
import { defineConfig, type UserConfigExport } from '@tarojs/cli'
import TsconfigPathsPlugin from 'tsconfig-paths-webpack-plugin'
import { UnifiedWebpackPluginV5 } from 'weapp-tailwindcss/webpack'
import devConfig from './dev'
import prodConfig from './prod'
import path from 'path'
import { getEnvConfig } from './env.js'
import { getEnvConfig } from './env'
// import vitePluginImp from 'vite-plugin-imp'
// https://taro-docs.jd.com/docs/next/config#defineconfig-辅助函数
export default defineConfig<'webpack5'>(async (merge, {}) => {
// https://taro-docs.jd.com/docs/next/config
export default defineConfig<'webpack5'>(async (merge, { mode }) => {
const baseConfig: UserConfigExport<'webpack5'> = {
// 项目配置
projectName: 'shop-taro',
date: '2026-5-10',
designWidth: 750,
projectName: 'websoft-react',
date: '2024-12-30',
plugins: ['@tarojs/plugin-html'],
designWidth (input:any) {
// 配置 NutUI 375 尺寸
if (input?.file?.replace(/\\+/g, '/').indexOf('@nutui') > -1) {
return 375
}
// 全局使用 Taro 默认的 750 尺寸
return 750
},
deviceRatio: {
640: 2.34 / 2,
750: 1,
828: 1.81 / 2
828: 1.81 / 2,
375: 2 / 1
},
sourceRoot: 'src',
outputRoot: 'dist',
plugins: [
'@tarojs/plugin-platform-weapp',
'@tarojs/plugin-platform-h5'
],
// 编译配置
defineConstants: {
API_BASE_URL: JSON.stringify(getEnvConfig().API_BASE_URL),
SERVER_API_URL: JSON.stringify(getEnvConfig().SERVER_API_URL),
APP_NAME: JSON.stringify(getEnvConfig().APP_NAME),
DEBUG: JSON.stringify(getEnvConfig().DEBUG),
DEBUG: JSON.stringify(getEnvConfig().DEBUG)
},
copy: {
patterns: [],
options: {}
patterns: [
],
options: {
}
},
framework: 'react',
compiler: {
type: 'webpack5',
prebundle: {
exclude: ['@nutui/nutui-react-taro', '@nutui/icons-react-taro'],
enable: false
}
},
// 开启持久化缓存 - 有效提升二次编译速度
cache: {
enable: false,
type: 'filesystem',
cacheDirectory: path.resolve(__dirname, '../.webpack-cache'),
buildDependencies: {
config: [__filename]
}
enable: false // Webpack 持久化缓存配置,建议开启。默认配置请参考:https://docs.taro.zone/docs/config-detail#cache
},
// 小程序配置
mini: {
postcss: {
pxtransform: {
enable: true,
config: {}
},
url: {
enable: true,
config: {
limit: 1024
selectorBlackList: ['nut-']
}
},
cssModules: {
enable: false,
enable: false, // 默认为 false,如需使用 css modules 功能,则设为 true
config: {
namingPattern: 'module',
namingPattern: 'module', // 转换模式,取值为 global/module
generateScopedName: '[name]__[local]___[hash:base64:5]'
}
}
},
webpackChain(chain) {
chain.resolve.plugin('tsconfig-paths').use(TsconfigPathsPlugin)
// weapp-tailwindcss: 自动处理 TailwindCSS 与 WXSS 的兼容性
chain.merge({
plugin: {
'weapp-tailwindcss': {
plugin: UnifiedWebpackPluginV5,
args: [{
appType: 'taro',
rem2rpx: true,
}]
}
}
})
}
},
// H5 配置
h5: {
publicPath: '/',
staticDirectory: 'static',
output: {
filename: 'js/[name].[hash:8].js',
chunkFilename: 'js/[name].[chunkhash:8].js'
},
miniCssExtractPluginOption: {
ignoreOrder: true,
filename: 'css/[name].[hash].css',
chunkFilename: 'css/[name].[chunkhash].css'
},
postcss: {
autoprefixer: {
enable: true
enable: true,
config: {}
},
cssModules: {
enable: false,
enable: false, // 默认为 false,如需使用 css modules 功能,则设为 true
config: {
namingPattern: 'module',
namingPattern: 'module', // 转换模式,取值为 global/module
generateScopedName: '[name]__[local]___[hash:base64:5]'
}
}
@@ -111,10 +100,19 @@ export default defineConfig<'webpack5'>(async (merge, { mode }) => {
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, prodConfig)
})
+33 -9
View File
@@ -1,12 +1,36 @@
import type { UserConfigExport } from "@tarojs/cli";
export default {
env: {
NODE_ENV: '"production"'
},
defineConstants: {
__DEV__: JSON.stringify(false)
},
mini: {
compressTemplate: true
miniCssExtractPluginOption: {
ignoreOrder: true
}
},
h5: {}
}
h5: {
/**
* WebpackChain 插件配置
* @docs https://github.com/neutrinojs/webpack-chain
*/
// webpackChain (chain) {
// /**
// * 如果 h5 端编译后体积过大,可以使用 webpack-bundle-analyzer 插件对打包体积进行分析。
// * @docs https://github.com/webpack-contrib/webpack-bundle-analyzer
// */
// chain.plugin('analyzer')
// .use(require('webpack-bundle-analyzer').BundleAnalyzerPlugin, [])
// /**
// * 如果 h5 端首屏加载时间过长,可以使用 prerender-spa-plugin 插件预加载首页。
// * @docs https://github.com/chrisvfritz/prerender-spa-plugin
// */
// const path = require('path')
// const Prerender = require('prerender-spa-plugin')
// const staticDir = path.join(__dirname, '..', 'dist')
// chain
// .plugin('prerender')
// .use(new Prerender({
// staticDir,
// routes: [ '/pages/index/index' ],
// postProcess: (context) => ({ ...context, outputPath: path.join(staticDir, 'index.html') })
// }))
// }
}
} satisfies UserConfigExport<'webpack5'>
+54
View File
@@ -0,0 +1,54 @@
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
View File
@@ -0,0 +1 @@
import '@tarojs/rn-supporter/entry-file.js'
+14
View File
@@ -0,0 +1,14 @@
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
View File
@@ -1,316 +0,0 @@
{
"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
}
]
}
+55 -38
View File
@@ -1,16 +1,16 @@
{
"name": "websopy-taro",
"name": "template-5",
"version": "1.0.0",
"private": true,
"description": "Taro + React + TypeScript 项目框架",
"browserslist": [
"last 3 versions",
"Android >= 4.1",
"ios >= 8"
],
"description": "WebSoft Inc.",
"templateInfo": {
"name": "react-NutUI",
"typescript": true,
"css": "Sass",
"framework": "React"
},
"scripts": {
"build:weapp": "taro build --type weapp",
"build:weapp:safe": "bash scripts/safe-build-weapp.sh",
"build:swan": "taro build --type swan",
"build:alipay": "taro build --type alipay",
"build:tt": "taro build --type tt",
@@ -28,64 +28,81 @@
"dev:qq": "npm run build:qq -- --watch",
"dev:jd": "npm run build:jd -- --watch",
"dev:quickapp": "npm run build:quickapp -- --watch",
"lint": "eslint src --fix --ext .ts,.tsx",
"type-check": "tsc --noEmit"
"build:tailwind": "postcss --config tailwind.config.js -o ./dist/index.css ./src/app.css"
},
"browserslist": [
"last 3 versions",
"Android >= 4.1",
"ios >= 8"
],
"author": "",
"dependencies": {
"@babel/runtime": "^7.26.0",
"@nutui/icons-react-taro": "^2.0.1",
"@nutui/nutui-biz": "1.0.0-beta.2",
"@nutui/nutui-react": "^3.0.16",
"@nutui/nutui-react-taro": "2.7.4",
"@tarojs/components": "4.1.11",
"@tarojs/plugin-framework-react": "4.1.11",
"@nutui/nutui-react-taro": "^2.7.4",
"@react-native/metro-config": "^0.73.2",
"@tarojs/components": "4.0.8",
"@tarojs/components-rn": "^4.1.4",
"@tarojs/helper": "4.0.8",
"@tarojs/plugin-framework-react": "4.0.8",
"@tarojs/plugin-html": "4.0.8",
"@tarojs/plugin-platform-alipay": "4.0.8",
"@tarojs/plugin-platform-h5": "4.0.8",
"@tarojs/plugin-platform-weapp": "4.1.11",
"@tarojs/react": "4.1.11",
"@tarojs/runtime": "4.1.11",
"@tarojs/shared": "4.1.11",
"@tarojs/taro": "4.1.11",
"@tarojs/plugin-platform-jd": "4.0.8",
"@tarojs/plugin-platform-qq": "4.0.8",
"@tarojs/plugin-platform-swan": "4.0.8",
"@tarojs/plugin-platform-tt": "4.0.8",
"@tarojs/plugin-platform-weapp": "4.0.8",
"@tarojs/react": "4.0.8",
"@tarojs/rn-runner": "^4.1.4",
"@tarojs/rn-supporter": "^4.1.4",
"@tarojs/runtime": "4.0.8",
"@tarojs/runtime-rn": "^4.1.4",
"@tarojs/shared": "4.0.8",
"@tarojs/taro": "4.0.8",
"@tarojs/taro-rn": "^4.1.4",
"crypto-js": "^4.2.0",
"dayjs": "^1.11.13",
"echarts-taro3-react": "^1.0.13",
"expo": "~50.0.2",
"js-base64": "^3.7.7",
"react": "18.3.1",
"react-dom": "18.3.1",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-markdown": "^10.1.0",
"react-native": "^0.73.1",
"react-router-dom": "^7.1.1",
"weapp-qrcode": "^1.0.0"
},
"devDependencies": {
"@babel/core": "^7.26.0",
"@babel/plugin-proposal-class-properties": "^7.18.6",
"@babel/preset-react": "^7.26.0",
"@babel/plugin-proposal-class-properties": "7.14.5",
"@babel/preset-react": "^7.26.3",
"@pmmmwh/react-refresh-webpack-plugin": "^0.5.15",
"@tarojs/cli": "4.1.11",
"@tarojs/taro-loader": "4.1.11",
"@tarojs/webpack5-runner": "4.1.11",
"@tarojs/cli": "4.0.8",
"@tarojs/taro-loader": "4.0.8",
"@tarojs/webpack5-runner": "4.0.8",
"@types/node": "^18.19.68",
"@types/react": "^18.3.12",
"@types/react": "^18.3.18",
"@types/webpack-env": "^1.18.5",
"@typescript-eslint/eslint-plugin": "^8.15.0",
"@typescript-eslint/parser": "^8.15.0",
"@typescript-eslint/eslint-plugin": "^6.21.0",
"@typescript-eslint/parser": "^6.21.0",
"autoprefixer": "^10.4.20",
"babel-plugin-import": "^1.13.8",
"babel-preset-taro": "4.0.8",
"eslint": "^8.56.0",
"eslint": "^8.57.1",
"eslint-config-taro": "4.0.8",
"eslint-plugin-import": "^2.31.0",
"eslint-plugin-react": "^7.37.2",
"eslint-plugin-react": "^7.37.3",
"eslint-plugin-react-hooks": "^4.6.2",
"mysql2": "^3.22.3",
"postcss": "^8.4.49",
"react-refresh": "^0.14.0",
"react-refresh": "^0.11.0",
"stylelint": "^14.16.1",
"tailwindcss": "3.4.17",
"tailwindcss": "^3.4.17",
"ts-node": "^10.9.2",
"tsconfig-paths-webpack-plugin": "^4.2.0",
"typescript": "5.7.2",
"weapp-tailwindcss": "^4.12.0",
"typescript": "^5.7.2",
"webpack": "5.78.0"
},
"engines": {
"node": ">=18"
}
}
+10209 -5879
View File
File diff suppressed because it is too large Load Diff
+4 -3
View File
@@ -1,7 +1,8 @@
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
'postcss-nested': {}
}
autoprefixer: {
remove: true // 禁用 autoprefixer,避免添加浏览器前缀
},
},
}
+26 -33
View File
@@ -1,52 +1,45 @@
{
"description": "websopy-taro 项目配置",
"miniprogramRoot": "dist/",
"projectname": "template-5",
"description": "网宿软件",
"appid": "wx541db955e7a62709",
"setting": {
"urlCheck": false,
"es6": true,
"enhance": true,
"postcss": true,
"urlCheck": true,
"es6": false,
"enhance": false,
"compileHotReLoad": false,
"postcss": false,
"preloadBackgroundData": false,
"minified": true,
"newFeature": true,
"coverView": true,
"nodeModules": false,
"autoAudits": false,
"showShadowRootInWxmlPanel": true,
"coverView": true,
"showShadowRootInWxmlPanel": false,
"scopeDataCheck": false,
"useCompilerModule": false,
"compileWorklet": false,
"uglifyFileName": false,
"checkInvalidKey": true,
"checkSiteMap": true,
"uploadWithSourceMap": true,
"compileHotReLoad": true,
"lazyloadPlaceholderEnable": false,
"useMultiFrameRuntime": true,
"useApiHook": true,
"useApiHostProcess": true,
"packNpmManually": false,
"packNpmRelationList": [],
"minifyWXSS": true,
"minifyWXML": true,
"localPlugins": false,
"disableUseStrict": false,
"useCompilerPlugins": false,
"condition": false,
"swc": false,
"disableSWC": true,
"babelSetting": {
"ignore": [],
"disablePlugins": [],
"outputPath": ""
},
"useIsolateContext": true,
"userConfirmedBundleSwitch": false,
"packNpmManually": false,
"packNpmRelationList": [],
"minifyWXSS": true,
"disableUseStrict": false,
"minifyWXML": true,
"showES6CompileOption": false,
"useCompilerPlugins": false,
"compileWorklet": false,
"localPlugins": false,
"condition": false,
"swc": false,
"disableSWC": true
}
},
"compileType": "miniprogram",
"appid": "wx541db955e7a62709",
"projectname": "赛联小助手",
"condition": {},
"simulatorType": "wechat",
"simulatorPluginLibVersion": {},
"condition": {},
"packOptions": {
"ignore": [],
"include": []
+39 -7
View File
@@ -1,23 +1,55 @@
{
"libVersion": "3.16.1",
"projectname": "websopy-taro",
"condition": {},
"libVersion": "3.15.2",
"projectname": "websopy-mp",
"condition": {
"miniprogram": {
"list": [
{
"name": "passport/invite/index",
"pathName": "passport/invite/index",
"query": "",
"scene": null,
"launchMode": "default"
},
{
"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": {
"urlCheck": false,
"urlCheck": true,
"coverView": true,
"lazyloadPlaceholderEnable": false,
"skylineRenderEnable": false,
"preloadBackgroundData": false,
"autoAudits": false,
"useApiHook": true,
"showShadowRootInWxmlPanel": true,
"showShadowRootInWxmlPanel": false,
"useStaticServer": false,
"useLanDebug": false,
"showES6CompileOption": false,
"compileHotReLoad": true,
"checkInvalidKey": true,
"ignoreDevUnusedFiles": true,
"bigPackageSizeSupport": false,
"useIsolateContext": true
"bigPackageSizeSupport": false
}
}
+13
View File
@@ -0,0 +1,13 @@
{
"miniprogramRoot": "./",
"projectname": "mp-react",
"description": "网宿软件",
"appid": "touristappid",
"setting": {
"urlCheck": true,
"es6": false,
"postcss": false,
"minified": false
},
"compileType": "miniprogram"
}
-16
View File
@@ -1,16 +0,0 @@
#!/bin/bash
# 安全构建小程序:规避 Taro emptyOutputDir 触发的 SAFE_DELETE 批量删除守卫
# (dist 文件数 > 50 时守卫会中断构建,导致 official/index 等页面 .js 缺失而白屏)
set -e
BAK_ROOT="/tmp/taro-dist-bak"
if [ -d dist ]; then
mkdir -p "$BAK_ROOT"
BAK="$BAK_ROOT/dist-$(date +%s)"
mv dist "$BAK"
echo "[safe-build] 已备份旧 dist -> $BAK"
fi
echo "[safe-build] 开始构建 weapp ..."
./node_modules/.bin/taro build --type weapp
echo "[safe-build] 构建完成"
+4
View File
@@ -0,0 +1,4 @@
export default definePageConfig({
navigationBarTitleText: '新增收货地址',
navigationBarTextStyle: 'black'
})
+323
View File
@@ -0,0 +1,323 @@
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;
+4
View File
@@ -0,0 +1,4 @@
export default definePageConfig({
navigationBarTitleText: '商品文章管理',
navigationBarTextStyle: 'black'
})
+271
View File
@@ -0,0 +1,271 @@
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;
+255
View File
@@ -0,0 +1,255 @@
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;
+186
View File
@@ -0,0 +1,186 @@
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
+102
View File
@@ -0,0 +1,102 @@
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
+69
View File
@@ -0,0 +1,69 @@
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;
+3
View File
@@ -0,0 +1,3 @@
export default definePageConfig({
navigationBarTitleText: '管理中心'
})
+49
View File
@@ -0,0 +1,49 @@
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
+320
View File
@@ -0,0 +1,320 @@
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
}
-49
View File
@@ -1,49 +0,0 @@
// 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 || '加载失败'))
}
+101
View File
@@ -0,0 +1,101 @@
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));
}
+49
View File
@@ -0,0 +1,49 @@
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;
}
-58
View File
@@ -1,58 +0,0 @@
// 个性域名绑定(对齐 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 || '操作失败'))
}
+101
View File
@@ -0,0 +1,101 @@
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));
}
+47
View File
@@ -0,0 +1,47 @@
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;
}
-90
View File
@@ -1,90 +0,0 @@
// 应用产品(对齐 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 || '操作失败'))
}
+19
View File
@@ -0,0 +1,19 @@
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));
}
+51
View File
@@ -0,0 +1,51 @@
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',
}
+101
View File
@@ -0,0 +1,101 @@
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));
}
+41
View File
@@ -0,0 +1,41 @@
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;
}
+101
View File
@@ -0,0 +1,101 @@
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));
}
+53
View File
@@ -0,0 +1,53 @@
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;
}
-106
View File
@@ -1,106 +0,0 @@
// 开发者权限申请 & 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 || '操作失败'))
}
+47
View File
@@ -0,0 +1,47 @@
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));
}
+41
View File
@@ -0,0 +1,41 @@
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;
}
-82
View File
@@ -1,82 +0,0 @@
// 工单(对齐 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 || '加载失败'))
}
+54
View File
@@ -0,0 +1,54 @@
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));
}
+64
View File
@@ -0,0 +1,64 @@
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
}
+129
View File
@@ -0,0 +1,129 @@
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));
}
+70
View File
@@ -0,0 +1,70 @@
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;
}
+120
View File
@@ -0,0 +1,120 @@
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));
}
+37
View File
@@ -0,0 +1,37 @@
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;
}
+105
View File
@@ -0,0 +1,105 @@
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));
}
+21
View File
@@ -0,0 +1,21 @@
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;
}
+105
View File
@@ -0,0 +1,105 @@
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));
}
+31
View File
@@ -0,0 +1,31 @@
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;
}
+138
View File
@@ -0,0 +1,138 @@
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));
}
+80
View File
@@ -0,0 +1,80 @@
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;
}
+9 -9
View File
@@ -1,4 +1,4 @@
import cmsRequest from '@/utils/cmsRequest';
import request from '@/utils/request';
import type { ApiResult, PageResult } from '@/api';
import type { CmsAd, CmsAdParam } from './model';
@@ -7,7 +7,7 @@ import type { CmsAd, CmsAdParam } from './model';
* 分页查询广告位
*/
export async function pageCmsAd(params: CmsAdParam) {
const res = await cmsRequest.get<ApiResult<PageResult<CmsAd>>>(
const res = await request.get<ApiResult<PageResult<CmsAd>>>(
'/cms/cms-ad/page',
params
);
@@ -21,7 +21,7 @@ export async function pageCmsAd(params: CmsAdParam) {
* 查询广告位列表
*/
export async function listCmsAd(params?: CmsAdParam) {
const res = await cmsRequest.get<ApiResult<CmsAd[]>>(
const res = await request.get<ApiResult<CmsAd[]>>(
'/cms/cms-ad',
params
);
@@ -35,7 +35,7 @@ export async function listCmsAd(params?: CmsAdParam) {
* 添加广告位
*/
export async function addCmsAd(data: CmsAd) {
const res = await cmsRequest.post<ApiResult<unknown>>(
const res = await request.post<ApiResult<unknown>>(
'/cms/cms-ad',
data
);
@@ -49,7 +49,7 @@ export async function addCmsAd(data: CmsAd) {
* 修改广告位
*/
export async function updateCmsAd(data: CmsAd) {
const res = await cmsRequest.put<ApiResult<unknown>>(
const res = await request.put<ApiResult<unknown>>(
'/cms/cms-ad',
data
);
@@ -63,7 +63,7 @@ export async function updateCmsAd(data: CmsAd) {
* 删除广告位
*/
export async function removeCmsAd(id?: number) {
const res = await cmsRequest.del<ApiResult<unknown>>(
const res = await request.del<ApiResult<unknown>>(
'/cms/cms-ad/' + id
);
if (res.code === 0) {
@@ -76,7 +76,7 @@ export async function removeCmsAd(id?: number) {
* 批量删除广告位
*/
export async function removeBatchCmsAd(data: (number | undefined)[]) {
const res = await cmsRequest.del<ApiResult<unknown>>(
const res = await request.del<ApiResult<unknown>>(
'/cms/cms-ad/batch',
{
data
@@ -92,7 +92,7 @@ export async function removeBatchCmsAd(data: (number | undefined)[]) {
* 根据id查询广告位
*/
export async function getCmsAd(id: number) {
const res = await cmsRequest.get<ApiResult<CmsAd>>(
const res = await request.get<ApiResult<CmsAd>>(
'/cms/cms-ad/' + id
);
if (res.code === 0 && res.data) {
@@ -105,7 +105,7 @@ export async function getCmsAd(id: number) {
* 根据code查询广告位
*/
export async function getCmsAdByCode(code: string) {
const res = await cmsRequest.get<ApiResult<CmsAd>>(
const res = await request.get<ApiResult<CmsAd>>(
'/cms/cms-ad/getByCode/' + code
);
if (res.code === 0 && res.data) {
-1
View File
@@ -55,6 +55,5 @@ export interface CmsAdParam extends PageParam {
adType?: string;
pageId?: number;
pageName?: string;
status?: number;
keywords?: string;
}
+8 -8
View File
@@ -1,4 +1,4 @@
import cmsRequest from '@/utils/cmsRequest';
import request from '@/utils/request';
import type { ApiResult, PageResult } from '@/api';
import type { CmsAdRecord, CmsAdRecordParam } from './model';
@@ -7,7 +7,7 @@ import type { CmsAdRecord, CmsAdRecordParam } from './model';
* 分页查询广告图片
*/
export async function pageCmsAdRecord(params: CmsAdRecordParam) {
const res = await cmsRequest.get<ApiResult<PageResult<CmsAdRecord>>>(
const res = await request.get<ApiResult<PageResult<CmsAdRecord>>>(
'/cms/cms-ad-record/page',
{
params
@@ -23,7 +23,7 @@ export async function pageCmsAdRecord(params: CmsAdRecordParam) {
* 查询广告图片列表
*/
export async function listCmsAdRecord(params?: CmsAdRecordParam) {
const res = await cmsRequest.get<ApiResult<CmsAdRecord[]>>(
const res = await request.get<ApiResult<CmsAdRecord[]>>(
'/cms/cms-ad-record',
{
params
@@ -39,7 +39,7 @@ export async function listCmsAdRecord(params?: CmsAdRecordParam) {
* 添加广告图片
*/
export async function addCmsAdRecord(data: CmsAdRecord) {
const res = await cmsRequest.post<ApiResult<unknown>>(
const res = await request.post<ApiResult<unknown>>(
'/cms/cms-ad-record',
data
);
@@ -53,7 +53,7 @@ export async function addCmsAdRecord(data: CmsAdRecord) {
* 修改广告图片
*/
export async function updateCmsAdRecord(data: CmsAdRecord) {
const res = await cmsRequest.put<ApiResult<unknown>>(
const res = await request.put<ApiResult<unknown>>(
'/cms/cms-ad-record',
data
);
@@ -67,7 +67,7 @@ export async function updateCmsAdRecord(data: CmsAdRecord) {
* 删除广告图片
*/
export async function removeCmsAdRecord(id?: number) {
const res = await cmsRequest.del<ApiResult<unknown>>(
const res = await request.del<ApiResult<unknown>>(
'/cms/cms-ad-record/' + id
);
if (res.code === 0) {
@@ -80,7 +80,7 @@ export async function removeCmsAdRecord(id?: number) {
* 批量删除广告图片
*/
export async function removeBatchCmsAdRecord(data: (number | undefined)[]) {
const res = await cmsRequest.del<ApiResult<unknown>>(
const res = await request.del<ApiResult<unknown>>(
'/cms/cms-ad-record/batch',
{
data
@@ -96,7 +96,7 @@ export async function removeBatchCmsAdRecord(data: (number | undefined)[]) {
* 根据id查询广告图片
*/
export async function getCmsAdRecord(id: number) {
const res = await cmsRequest.get<ApiResult<CmsAdRecord>>(
const res = await request.get<ApiResult<CmsAdRecord>>(
'/cms/cms-ad-record/' + id
);
if (res.code === 0 && res.data) {
+16 -16
View File
@@ -1,4 +1,4 @@
import cmsRequest from '@/utils/cmsRequest';
import request from '@/utils/request';
import type {ApiResult, PageResult} from '@/api';
import type {CmsArticle, CmsArticleParam} from './model';
@@ -6,7 +6,7 @@ import type {CmsArticle, CmsArticleParam} from './model';
* 分页查询文章
*/
export async function pageCmsArticle(params: CmsArticleParam) {
const res = await cmsRequest.get<ApiResult<PageResult<CmsArticle>>>(
const res = await request.get<ApiResult<PageResult<CmsArticle>>>(
'/cms/cms-article/page',
params
);
@@ -20,7 +20,7 @@ export async function pageCmsArticle(params: CmsArticleParam) {
* 查询文章列表
*/
export async function listCmsArticle(params?: CmsArticleParam) {
const res = await cmsRequest.get<ApiResult<CmsArticle[]>>(
const res = await request.get<ApiResult<CmsArticle[]>>(
'/cms/cms-article',
params
);
@@ -34,7 +34,7 @@ export async function listCmsArticle(params?: CmsArticleParam) {
* 添加文章
*/
export async function addCmsArticle(data: CmsArticle) {
const res = await cmsRequest.post<ApiResult<unknown>>(
const res = await request.post<ApiResult<unknown>>(
'/cms/cms-article',
data
);
@@ -48,7 +48,7 @@ export async function addCmsArticle(data: CmsArticle) {
* 修改文章
*/
export async function updateCmsArticle(data: CmsArticle) {
const res = await cmsRequest.put<ApiResult<unknown>>(
const res = await request.put<ApiResult<unknown>>(
'/cms/cms-article',
data
);
@@ -62,7 +62,7 @@ export async function updateCmsArticle(data: CmsArticle) {
* 删除文章
*/
export async function removeCmsArticle(id?: number) {
const res = await cmsRequest.del<ApiResult<unknown>>(
const res = await request.del<ApiResult<unknown>>(
'/cms/cms-article/' + id
);
if (res.code === 0) {
@@ -75,7 +75,7 @@ export async function removeCmsArticle(id?: number) {
* 批量删除文章
*/
export async function removeBatchCmsArticle(data: (number | undefined)[]) {
const res = await cmsRequest.del<ApiResult<unknown>>(
const res = await request.del<ApiResult<unknown>>(
'/cms/cms-article/batch',
{
data
@@ -91,7 +91,7 @@ export async function removeBatchCmsArticle(data: (number | undefined)[]) {
* 根据id查询文章
*/
export async function getCmsArticle(id: number) {
const res = await cmsRequest.get<ApiResult<CmsArticle>>(
const res = await request.get<ApiResult<CmsArticle>>(
'/cms/cms-article/' + id
);
if (res.code === 0 && res.data) {
@@ -101,7 +101,7 @@ export async function getCmsArticle(id: number) {
}
export async function getCount(params?: CmsArticleParam) {
const res = await cmsRequest.get<ApiResult<unknown>>('/cms/cms-article/data', {
const res = await request.get<ApiResult<unknown>>('/cms/cms-article/data', {
params
});
if (res.code === 0) {
@@ -115,7 +115,7 @@ export async function getCount(params?: CmsArticleParam) {
* @param params
*/
export async function getPrevious(params?: CmsArticleParam) {
const res = await cmsRequest.get<ApiResult<CmsArticle>>(
const res = await request.get<ApiResult<CmsArticle>>(
'/cms/cms-article/getPrevious/' + params?.articleId,
{
params
@@ -132,7 +132,7 @@ export async function getPrevious(params?: CmsArticleParam) {
* @param params
*/
export async function getNext(params?: CmsArticleParam) {
const res = await cmsRequest.get<ApiResult<CmsArticle>>(
const res = await request.get<ApiResult<CmsArticle>>(
'/cms/cms-article/getNext/' + params?.articleId,
{
params
@@ -149,7 +149,7 @@ export async function getNext(params?: CmsArticleParam) {
* @param params
*/
export async function checkArticlePassword(params?: CmsArticleParam) {
const res = await cmsRequest.get<ApiResult<unknown>>(
const res = await request.get<ApiResult<unknown>>(
'/cms/cms-article/checkArticlePassword',
{
params
@@ -162,7 +162,7 @@ export async function checkArticlePassword(params?: CmsArticleParam) {
}
export async function findTags(params?: CmsArticleParam) {
const res = await cmsRequest.get<ApiResult<CmsArticle[]>>(
const res = await request.get<ApiResult<CmsArticle[]>>(
'/cms/cms-article/findTags',
{
params
@@ -175,7 +175,7 @@ export async function findTags(params?: CmsArticleParam) {
}
export async function pageTags(params?: CmsArticleParam) {
const res = await cmsRequest.get<ApiResult<CmsArticle[]>>(
const res = await request.get<ApiResult<CmsArticle[]>>(
'/cms/cms-article/pageTags',
{
params
@@ -192,7 +192,7 @@ export async function pageTags(params?: CmsArticleParam) {
* @param params
*/
export async function getByIds(params?: CmsArticleParam) {
const res = await cmsRequest.get<ApiResult<CmsArticle[]>>(
const res = await request.get<ApiResult<CmsArticle[]>>(
'/cms/cms-article/getByIds',
{
params
@@ -208,7 +208,7 @@ export async function getByIds(params?: CmsArticleParam) {
* 根据code查询文章
*/
export async function getByCode(code: string) {
const res = await cmsRequest.get<ApiResult<CmsArticle>>(
const res = await request.get<ApiResult<CmsArticle>>(
'/cms/cms-article/getByCode/' + code
);
if (res.code === 0 && res.data) {
+8 -8
View File
@@ -1,4 +1,4 @@
import cmsRequest from '@/utils/cmsRequest';
import request from '@/utils/request';
import type { ApiResult, PageResult } from '@/api';
import type { CmsArticleCategory, CmsArticleCategoryParam } from './model';
@@ -7,7 +7,7 @@ import type { CmsArticleCategory, CmsArticleCategoryParam } from './model';
* 分页查询文章分类表
*/
export async function pageCmsArticleCategory(params: CmsArticleCategoryParam) {
const res = await cmsRequest.get<ApiResult<PageResult<CmsArticleCategory>>>(
const res = await request.get<ApiResult<PageResult<CmsArticleCategory>>>(
'/cms/cms-article-category/page',
{
params
@@ -23,7 +23,7 @@ export async function pageCmsArticleCategory(params: CmsArticleCategoryParam) {
* 查询文章分类表列表
*/
export async function listCmsArticleCategory(params?: CmsArticleCategoryParam) {
const res = await cmsRequest.get<ApiResult<CmsArticleCategory[]>>(
const res = await request.get<ApiResult<CmsArticleCategory[]>>(
'/cms/cms-article-category',
{
params
@@ -39,7 +39,7 @@ export async function listCmsArticleCategory(params?: CmsArticleCategoryParam) {
* 添加文章分类表
*/
export async function addCmsArticleCategory(data: CmsArticleCategory) {
const res = await cmsRequest.post<ApiResult<unknown>>(
const res = await request.post<ApiResult<unknown>>(
'/cms/cms-article-category',
data
);
@@ -53,7 +53,7 @@ export async function addCmsArticleCategory(data: CmsArticleCategory) {
* 修改文章分类表
*/
export async function updateCmsArticleCategory(data: CmsArticleCategory) {
const res = await cmsRequest.put<ApiResult<unknown>>(
const res = await request.put<ApiResult<unknown>>(
'/cms/cms-article-category',
data
);
@@ -67,7 +67,7 @@ export async function updateCmsArticleCategory(data: CmsArticleCategory) {
* 删除文章分类表
*/
export async function removeCmsArticleCategory(id?: number) {
const res = await cmsRequest.del<ApiResult<unknown>>(
const res = await request.del<ApiResult<unknown>>(
'/cms/cms-article-category/' + id
);
if (res.code === 0) {
@@ -82,7 +82,7 @@ export async function removeCmsArticleCategory(id?: number) {
export async function removeBatchCmsArticleCategory(
data: (number | undefined)[]
) {
const res = await cmsRequest.del<ApiResult<unknown>>(
const res = await request.del<ApiResult<unknown>>(
'/cms/cms-article-category/batch',
{
data
@@ -98,7 +98,7 @@ export async function removeBatchCmsArticleCategory(
* 根据id查询文章分类表
*/
export async function getCmsArticleCategory(id: number) {
const res = await cmsRequest.get<ApiResult<CmsArticleCategory>>(
const res = await request.get<ApiResult<CmsArticleCategory>>(
'/cms/cms-article-category/' + id
);
if (res.code === 0 && res.data) {
+8 -8
View File
@@ -1,4 +1,4 @@
import cmsRequest from '@/utils/cmsRequest';
import request from '@/utils/request';
import type { ApiResult, PageResult } from '@/api';
import type { CmsArticleComment, CmsArticleCommentParam } from './model';
@@ -7,7 +7,7 @@ import type { CmsArticleComment, CmsArticleCommentParam } from './model';
* 分页查询文章评论表
*/
export async function pageCmsArticleComment(params: CmsArticleCommentParam) {
const res = await cmsRequest.get<ApiResult<PageResult<CmsArticleComment>>>(
const res = await request.get<ApiResult<PageResult<CmsArticleComment>>>(
'/cms/cms-article-comment/page',
{
params
@@ -23,7 +23,7 @@ export async function pageCmsArticleComment(params: CmsArticleCommentParam) {
* 查询文章评论表列表
*/
export async function listCmsArticleComment(params?: CmsArticleCommentParam) {
const res = await cmsRequest.get<ApiResult<CmsArticleComment[]>>(
const res = await request.get<ApiResult<CmsArticleComment[]>>(
'/cms/cms-article-comment',
{
params
@@ -39,7 +39,7 @@ export async function listCmsArticleComment(params?: CmsArticleCommentParam) {
* 添加文章评论表
*/
export async function addCmsArticleComment(data: CmsArticleComment) {
const res = await cmsRequest.post<ApiResult<unknown>>(
const res = await request.post<ApiResult<unknown>>(
'/cms/cms-article-comment',
data
);
@@ -53,7 +53,7 @@ export async function addCmsArticleComment(data: CmsArticleComment) {
* 修改文章评论表
*/
export async function updateCmsArticleComment(data: CmsArticleComment) {
const res = await cmsRequest.put<ApiResult<unknown>>(
const res = await request.put<ApiResult<unknown>>(
'/cms/cms-article-comment',
data
);
@@ -67,7 +67,7 @@ export async function updateCmsArticleComment(data: CmsArticleComment) {
* 删除文章评论表
*/
export async function removeCmsArticleComment(id?: number) {
const res = await cmsRequest.del<ApiResult<unknown>>(
const res = await request.del<ApiResult<unknown>>(
'/cms/cms-article-comment/' + id
);
if (res.code === 0) {
@@ -80,7 +80,7 @@ export async function removeCmsArticleComment(id?: number) {
* 批量删除文章评论表
*/
export async function removeBatchCmsArticleComment(data: (number | undefined)[]) {
const res = await cmsRequest.del<ApiResult<unknown>>(
const res = await request.del<ApiResult<unknown>>(
'/cms/cms-article-comment/batch',
{
data
@@ -96,7 +96,7 @@ export async function removeBatchCmsArticleComment(data: (number | undefined)[])
* 根据id查询文章评论表
*/
export async function getCmsArticleComment(id: number) {
const res = await cmsRequest.get<ApiResult<CmsArticleComment>>(
const res = await request.get<ApiResult<CmsArticleComment>>(
'/cms/cms-article-comment/' + id
);
if (res.code === 0 && res.data) {
+8 -8
View File
@@ -1,4 +1,4 @@
import cmsRequest from '@/utils/cmsRequest';
import request from '@/utils/request';
import type { ApiResult, PageResult } from '@/api';
import type { CmsArticleContent, CmsArticleContentParam } from './model';
@@ -7,7 +7,7 @@ import type { CmsArticleContent, CmsArticleContentParam } from './model';
* 分页查询文章记录表
*/
export async function pageCmsArticleContent(params: CmsArticleContentParam) {
const res = await cmsRequest.get<ApiResult<PageResult<CmsArticleContent>>>(
const res = await request.get<ApiResult<PageResult<CmsArticleContent>>>(
'/cms/cms-article-content/page',
{
params
@@ -23,7 +23,7 @@ export async function pageCmsArticleContent(params: CmsArticleContentParam) {
* 查询文章记录表列表
*/
export async function listCmsArticleContent(params?: CmsArticleContentParam) {
const res = await cmsRequest.get<ApiResult<CmsArticleContent[]>>(
const res = await request.get<ApiResult<CmsArticleContent[]>>(
'/cms/cms-article-content',
{
params
@@ -39,7 +39,7 @@ export async function listCmsArticleContent(params?: CmsArticleContentParam) {
* 添加文章记录表
*/
export async function addCmsArticleContent(data: CmsArticleContent) {
const res = await cmsRequest.post<ApiResult<unknown>>(
const res = await request.post<ApiResult<unknown>>(
'/cms/cms-article-content',
data
);
@@ -53,7 +53,7 @@ export async function addCmsArticleContent(data: CmsArticleContent) {
* 修改文章记录表
*/
export async function updateCmsArticleContent(data: CmsArticleContent) {
const res = await cmsRequest.put<ApiResult<unknown>>(
const res = await request.put<ApiResult<unknown>>(
'/cms/cms-article-content',
data
);
@@ -67,7 +67,7 @@ export async function updateCmsArticleContent(data: CmsArticleContent) {
* 删除文章记录表
*/
export async function removeCmsArticleContent(id?: number) {
const res = await cmsRequest.del<ApiResult<unknown>>(
const res = await request.del<ApiResult<unknown>>(
'/cms/cms-article-content/' + id
);
if (res.code === 0) {
@@ -80,7 +80,7 @@ export async function removeCmsArticleContent(id?: number) {
* 批量删除文章记录表
*/
export async function removeBatchCmsArticleContent(data: (number | undefined)[]) {
const res = await cmsRequest.del<ApiResult<unknown>>(
const res = await request.del<ApiResult<unknown>>(
'/cms/cms-article-content/batch',
{
data
@@ -96,7 +96,7 @@ export async function removeBatchCmsArticleContent(data: (number | undefined)[])
* 根据id查询文章记录表
*/
export async function getCmsArticleContent(id: number) {
const res = await cmsRequest.get<ApiResult<CmsArticleContent>>(
const res = await request.get<ApiResult<CmsArticleContent>>(
'/cms/cms-article-content/' + id
);
if (res.code === 0 && res.data) {
+8 -8
View File
@@ -1,4 +1,4 @@
import cmsRequest from '@/utils/cmsRequest';
import request from '@/utils/request';
import type { ApiResult, PageResult } from '@/api';
import type { CmsArticleCount, CmsArticleCountParam } from './model';
@@ -7,7 +7,7 @@ import type { CmsArticleCount, CmsArticleCountParam } from './model';
* 分页查询点赞文章
*/
export async function pageCmsArticleCount(params: CmsArticleCountParam) {
const res = await cmsRequest.get<ApiResult<PageResult<CmsArticleCount>>>(
const res = await request.get<ApiResult<PageResult<CmsArticleCount>>>(
'/cms/cms-article-count/page',
{
params
@@ -23,7 +23,7 @@ export async function pageCmsArticleCount(params: CmsArticleCountParam) {
* 查询点赞文章列表
*/
export async function listCmsArticleCount(params?: CmsArticleCountParam) {
const res = await cmsRequest.get<ApiResult<CmsArticleCount[]>>(
const res = await request.get<ApiResult<CmsArticleCount[]>>(
'/cms/cms-article-count',
{
params
@@ -39,7 +39,7 @@ export async function listCmsArticleCount(params?: CmsArticleCountParam) {
* 添加点赞文章
*/
export async function addCmsArticleCount(data: CmsArticleCount) {
const res = await cmsRequest.post<ApiResult<unknown>>(
const res = await request.post<ApiResult<unknown>>(
'/cms/cms-article-count',
data
);
@@ -53,7 +53,7 @@ export async function addCmsArticleCount(data: CmsArticleCount) {
* 修改点赞文章
*/
export async function updateCmsArticleCount(data: CmsArticleCount) {
const res = await cmsRequest.put<ApiResult<unknown>>(
const res = await request.put<ApiResult<unknown>>(
'/cms/cms-article-count',
data
);
@@ -67,7 +67,7 @@ export async function updateCmsArticleCount(data: CmsArticleCount) {
* 删除点赞文章
*/
export async function removeCmsArticleCount(id?: number) {
const res = await cmsRequest.del<ApiResult<unknown>>(
const res = await request.del<ApiResult<unknown>>(
'/cms/cms-article-count/' + id
);
if (res.code === 0) {
@@ -80,7 +80,7 @@ export async function removeCmsArticleCount(id?: number) {
* 批量删除点赞文章
*/
export async function removeBatchCmsArticleCount(data: (number | undefined)[]) {
const res = await cmsRequest.del<ApiResult<unknown>>(
const res = await request.del<ApiResult<unknown>>(
'/cms/cms-article-count/batch',
{
data
@@ -96,7 +96,7 @@ export async function removeBatchCmsArticleCount(data: (number | undefined)[]) {
* 根据id查询点赞文章
*/
export async function getCmsArticleCount(id: number) {
const res = await cmsRequest.get<ApiResult<CmsArticleCount>>(
const res = await request.get<ApiResult<CmsArticleCount>>(
'/cms/cms-article-count/' + id
);
if (res.code === 0 && res.data) {
+8 -8
View File
@@ -1,4 +1,4 @@
import cmsRequest from '@/utils/cmsRequest';
import request from '@/utils/request';
import type { ApiResult, PageResult } from '@/api';
import type { CmsArticleLike, CmsArticleLikeParam } from './model';
@@ -7,7 +7,7 @@ import type { CmsArticleLike, CmsArticleLikeParam } from './model';
* 分页查询点赞文章
*/
export async function pageCmsArticleLike(params: CmsArticleLikeParam) {
const res = await cmsRequest.get<ApiResult<PageResult<CmsArticleLike>>>(
const res = await request.get<ApiResult<PageResult<CmsArticleLike>>>(
'/cms/cms-article-like/page',
{
params
@@ -23,7 +23,7 @@ export async function pageCmsArticleLike(params: CmsArticleLikeParam) {
* 查询点赞文章列表
*/
export async function listCmsArticleLike(params?: CmsArticleLikeParam) {
const res = await cmsRequest.get<ApiResult<CmsArticleLike[]>>(
const res = await request.get<ApiResult<CmsArticleLike[]>>(
'/cms/cms-article-like',
{
params
@@ -39,7 +39,7 @@ export async function listCmsArticleLike(params?: CmsArticleLikeParam) {
* 添加点赞文章
*/
export async function addCmsArticleLike(data: CmsArticleLike) {
const res = await cmsRequest.post<ApiResult<unknown>>(
const res = await request.post<ApiResult<unknown>>(
'/cms/cms-article-like',
data
);
@@ -53,7 +53,7 @@ export async function addCmsArticleLike(data: CmsArticleLike) {
* 修改点赞文章
*/
export async function updateCmsArticleLike(data: CmsArticleLike) {
const res = await cmsRequest.put<ApiResult<unknown>>(
const res = await request.put<ApiResult<unknown>>(
'/cms/cms-article-like',
data
);
@@ -67,7 +67,7 @@ export async function updateCmsArticleLike(data: CmsArticleLike) {
* 删除点赞文章
*/
export async function removeCmsArticleLike(id?: number) {
const res = await cmsRequest.del<ApiResult<unknown>>(
const res = await request.del<ApiResult<unknown>>(
'/cms/cms-article-like/' + id
);
if (res.code === 0) {
@@ -80,7 +80,7 @@ export async function removeCmsArticleLike(id?: number) {
* 批量删除点赞文章
*/
export async function removeBatchCmsArticleLike(data: (number | undefined)[]) {
const res = await cmsRequest.del<ApiResult<unknown>>(
const res = await request.del<ApiResult<unknown>>(
'/cms/cms-article-like/batch',
{
data
@@ -96,7 +96,7 @@ export async function removeBatchCmsArticleLike(data: (number | undefined)[]) {
* 根据id查询点赞文章
*/
export async function getCmsArticleLike(id: number) {
const res = await cmsRequest.get<ApiResult<CmsArticleLike>>(
const res = await request.get<ApiResult<CmsArticleLike>>(
'/cms/cms-article-like/' + id
);
if (res.code === 0 && res.data) {
+8 -8
View File
@@ -1,4 +1,4 @@
import cmsRequest from '@/utils/cmsRequest';
import request from '@/utils/request';
import type { ApiResult, PageResult } from '@/api';
import type { CmsComponents, CmsComponentsParam } from './model';
@@ -7,7 +7,7 @@ import type { CmsComponents, CmsComponentsParam } from './model';
* 分页查询组件
*/
export async function pageCmsComponents(params: CmsComponentsParam) {
const res = await cmsRequest.get<ApiResult<PageResult<CmsComponents>>>(
const res = await request.get<ApiResult<PageResult<CmsComponents>>>(
'/cms/cms-components/page',
{
params
@@ -23,7 +23,7 @@ export async function pageCmsComponents(params: CmsComponentsParam) {
* 查询组件列表
*/
export async function listCmsComponents(params?: CmsComponentsParam) {
const res = await cmsRequest.get<ApiResult<CmsComponents[]>>(
const res = await request.get<ApiResult<CmsComponents[]>>(
'/cms/cms-components',
{
params
@@ -39,7 +39,7 @@ export async function listCmsComponents(params?: CmsComponentsParam) {
* 添加组件
*/
export async function addCmsComponents(data: CmsComponents) {
const res = await cmsRequest.post<ApiResult<unknown>>(
const res = await request.post<ApiResult<unknown>>(
'/cms/cms-components',
data
);
@@ -53,7 +53,7 @@ export async function addCmsComponents(data: CmsComponents) {
* 修改组件
*/
export async function updateCmsComponents(data: CmsComponents) {
const res = await cmsRequest.put<ApiResult<unknown>>(
const res = await request.put<ApiResult<unknown>>(
'/cms/cms-components',
data
);
@@ -67,7 +67,7 @@ export async function updateCmsComponents(data: CmsComponents) {
* 删除组件
*/
export async function removeCmsComponents(id?: number) {
const res = await cmsRequest.del<ApiResult<unknown>>(
const res = await request.del<ApiResult<unknown>>(
'/cms/cms-components/' + id
);
if (res.code === 0) {
@@ -80,7 +80,7 @@ export async function removeCmsComponents(id?: number) {
* 批量删除组件
*/
export async function removeBatchCmsComponents(data: (number | undefined)[]) {
const res = await cmsRequest.del<ApiResult<unknown>>(
const res = await request.del<ApiResult<unknown>>(
'/cms/cms-components/batch',
{
data
@@ -96,7 +96,7 @@ export async function removeBatchCmsComponents(data: (number | undefined)[]) {
* 根据id查询组件
*/
export async function getCmsComponents(id: number) {
const res = await cmsRequest.get<ApiResult<CmsComponents>>(
const res = await request.get<ApiResult<CmsComponents>>(
'/cms/cms-components/' + id
);
if (res.code === 0 && res.data) {
-31
View File
@@ -1,31 +0,0 @@
import cmsRequest from '@/utils/cmsRequest'
import type { ApiResult, PageResult } from '@/api'
import type { CmsContactLead, CmsContactLeadParam } from './model'
/**
* 提交联系线索(留言咨询),免登录
*/
export async function submitCmsContactLead(data: Partial<CmsContactLead>) {
const res = await cmsRequest.post<ApiResult<unknown>>(
'/cms/cms-contact-lead/submit',
data
)
if (res.code === 0) {
return res.message
}
return Promise.reject(new Error(res.message))
}
/**
* 分页查询我的联系线索
*/
export async function pageCmsContactLead(params: CmsContactLeadParam) {
const res = await cmsRequest.get<ApiResult<PageResult<CmsContactLead>>>(
'/cms/cms-contact-lead/page',
params
)
if (res.code === 0) {
return res.data
}
return Promise.reject(new Error(res.message))
}
-36
View File
@@ -1,36 +0,0 @@
import type { PageParam } from '@/api'
/**
* 联系线索(留言咨询)
*/
export interface CmsContactLead {
// 线索ID
leadId?: number
// 称呼
name?: string
// 手机号
phone?: string
// 公司
company?: string
// 邮箱
email?: string
// 需求描述
need?: string
// 来源 web|miniapp|app
source?: string
// 状态 0待跟进 1已回复 2已完成 3已归档
status?: number
// 租户id
tenantId?: number
// 创建时间
createTime?: string
}
/**
* 联系线索搜索条件
*/
export interface CmsContactLeadParam extends PageParam {
name?: string
phone?: string
source?: string
}
+8 -8
View File
@@ -1,4 +1,4 @@
import cmsRequest from '@/utils/cmsRequest';
import request from '@/utils/request';
import type { ApiResult, PageResult } from '@/api';
import type { CmsDesign, CmsDesignParam } from './model';
@@ -7,7 +7,7 @@ import type { CmsDesign, CmsDesignParam } from './model';
* 分页查询页面管理记录表
*/
export async function pageCmsDesign(params: CmsDesignParam) {
const res = await cmsRequest.get<ApiResult<PageResult<CmsDesign>>>(
const res = await request.get<ApiResult<PageResult<CmsDesign>>>(
'/cms/cms-design/page',
params
);
@@ -21,7 +21,7 @@ export async function pageCmsDesign(params: CmsDesignParam) {
* 查询页面管理记录表列表
*/
export async function listCmsDesign(params?: CmsDesignParam) {
const res = await cmsRequest.get<ApiResult<CmsDesign[]>>(
const res = await request.get<ApiResult<CmsDesign[]>>(
'/cms/cms-design',
params
);
@@ -35,7 +35,7 @@ export async function listCmsDesign(params?: CmsDesignParam) {
* 添加页面管理记录表
*/
export async function addCmsDesign(data: CmsDesign) {
const res = await cmsRequest.post<ApiResult<unknown>>(
const res = await request.post<ApiResult<unknown>>(
'/cms/cms-design',
data
);
@@ -49,7 +49,7 @@ export async function addCmsDesign(data: CmsDesign) {
* 修改页面管理记录表
*/
export async function updateCmsDesign(data: CmsDesign) {
const res = await cmsRequest.put<ApiResult<unknown>>(
const res = await request.put<ApiResult<unknown>>(
'/cms/cms-design',
data
);
@@ -63,7 +63,7 @@ export async function updateCmsDesign(data: CmsDesign) {
* 删除页面管理记录表
*/
export async function removeCmsDesign(id?: number) {
const res = await cmsRequest.del<ApiResult<unknown>>(
const res = await request.del<ApiResult<unknown>>(
'/cms/cms-design/' + id
);
if (res.code === 0) {
@@ -76,7 +76,7 @@ export async function removeCmsDesign(id?: number) {
* 批量删除页面管理记录表
*/
export async function removeBatchCmsDesign(data: (number | undefined)[]) {
const res = await cmsRequest.del<ApiResult<unknown>>(
const res = await request.del<ApiResult<unknown>>(
'/cms/cms-design/batch',
{
data
@@ -92,7 +92,7 @@ export async function removeBatchCmsDesign(data: (number | undefined)[]) {
* 根据id查询页面管理记录表
*/
export async function getCmsDesign(id: number) {
const res = await cmsRequest.get<ApiResult<CmsDesign>>(
const res = await request.get<ApiResult<CmsDesign>>(
'/cms/cms-design/' + id
);
if (res.code === 0 && res.data) {
+8 -8
View File
@@ -1,4 +1,4 @@
import cmsRequest from '@/utils/cmsRequest';
import request from '@/utils/request';
import type { ApiResult, PageResult } from '@/api';
import type { CmsDesignRecord, CmsDesignRecordParam } from './model';
@@ -7,7 +7,7 @@ import type { CmsDesignRecord, CmsDesignRecordParam } from './model';
* 分页查询页面组件表
*/
export async function pageCmsDesignRecord(params: CmsDesignRecordParam) {
const res = await cmsRequest.get<ApiResult<PageResult<CmsDesignRecord>>>(
const res = await request.get<ApiResult<PageResult<CmsDesignRecord>>>(
'/cms/cms-design-record/page',
{
params
@@ -23,7 +23,7 @@ export async function pageCmsDesignRecord(params: CmsDesignRecordParam) {
* 查询页面组件表列表
*/
export async function listCmsDesignRecord(params?: CmsDesignRecordParam) {
const res = await cmsRequest.get<ApiResult<CmsDesignRecord[]>>(
const res = await request.get<ApiResult<CmsDesignRecord[]>>(
'/cms/cms-design-record',
{
params
@@ -39,7 +39,7 @@ export async function listCmsDesignRecord(params?: CmsDesignRecordParam) {
* 添加页面组件表
*/
export async function addCmsDesignRecord(data: CmsDesignRecord) {
const res = await cmsRequest.post<ApiResult<unknown>>(
const res = await request.post<ApiResult<unknown>>(
'/cms/cms-design-record',
data
);
@@ -53,7 +53,7 @@ export async function addCmsDesignRecord(data: CmsDesignRecord) {
* 修改页面组件表
*/
export async function updateCmsDesignRecord(data: CmsDesignRecord) {
const res = await cmsRequest.put<ApiResult<unknown>>(
const res = await request.put<ApiResult<unknown>>(
'/cms/cms-design-record',
data
);
@@ -67,7 +67,7 @@ export async function updateCmsDesignRecord(data: CmsDesignRecord) {
* 删除页面组件表
*/
export async function removeCmsDesignRecord(id?: number) {
const res = await cmsRequest.del<ApiResult<unknown>>(
const res = await request.del<ApiResult<unknown>>(
'/cms/cms-design-record/' + id
);
if (res.code === 0) {
@@ -80,7 +80,7 @@ export async function removeCmsDesignRecord(id?: number) {
* 批量删除页面组件表
*/
export async function removeBatchCmsDesignRecord(data: (number | undefined)[]) {
const res = await cmsRequest.del<ApiResult<unknown>>(
const res = await request.del<ApiResult<unknown>>(
'/cms/cms-design-record/batch',
{
data
@@ -96,7 +96,7 @@ export async function removeBatchCmsDesignRecord(data: (number | undefined)[]) {
* 根据id查询页面组件表
*/
export async function getCmsDesignRecord(id: number) {
const res = await cmsRequest.get<ApiResult<CmsDesignRecord>>(
const res = await request.get<ApiResult<CmsDesignRecord>>(
'/cms/cms-design-record/' + id
);
if (res.code === 0 && res.data) {
+8 -8
View File
@@ -1,4 +1,4 @@
import cmsRequest from '@/utils/cmsRequest';
import request from '@/utils/request';
import type { ApiResult, PageResult } from '@/api';
import type { CmsDocs, CmsDocsParam } from './model';
@@ -7,7 +7,7 @@ import type { CmsDocs, CmsDocsParam } from './model';
* 分页查询文档管理记录表
*/
export async function pageCmsDocs(params: CmsDocsParam) {
const res = await cmsRequest.get<ApiResult<PageResult<CmsDocs>>>(
const res = await request.get<ApiResult<PageResult<CmsDocs>>>(
'/cms/cms-docs/page',
{
params
@@ -23,7 +23,7 @@ export async function pageCmsDocs(params: CmsDocsParam) {
* 查询文档管理记录表列表
*/
export async function listCmsDocs(params?: CmsDocsParam) {
const res = await cmsRequest.get<ApiResult<CmsDocs[]>>(
const res = await request.get<ApiResult<CmsDocs[]>>(
'/cms/cms-docs',
{
params
@@ -39,7 +39,7 @@ export async function listCmsDocs(params?: CmsDocsParam) {
* 添加文档管理记录表
*/
export async function addCmsDocs(data: CmsDocs) {
const res = await cmsRequest.post<ApiResult<unknown>>(
const res = await request.post<ApiResult<unknown>>(
'/cms/cms-docs',
data
);
@@ -53,7 +53,7 @@ export async function addCmsDocs(data: CmsDocs) {
* 修改文档管理记录表
*/
export async function updateCmsDocs(data: CmsDocs) {
const res = await cmsRequest.put<ApiResult<unknown>>(
const res = await request.put<ApiResult<unknown>>(
'/cms/cms-docs',
data
);
@@ -67,7 +67,7 @@ export async function updateCmsDocs(data: CmsDocs) {
* 删除文档管理记录表
*/
export async function removeCmsDocs(id?: number) {
const res = await cmsRequest.del<ApiResult<unknown>>(
const res = await request.del<ApiResult<unknown>>(
'/cms/cms-docs/' + id
);
if (res.code === 0) {
@@ -80,7 +80,7 @@ export async function removeCmsDocs(id?: number) {
* 批量删除文档管理记录表
*/
export async function removeBatchCmsDocs(data: (number | undefined)[]) {
const res = await cmsRequest.del<ApiResult<unknown>>(
const res = await request.del<ApiResult<unknown>>(
'/cms/cms-docs/batch',
{
data
@@ -96,7 +96,7 @@ export async function removeBatchCmsDocs(data: (number | undefined)[]) {
* 根据id查询文档管理记录表
*/
export async function getCmsDocs(id: number) {
const res = await cmsRequest.get<ApiResult<CmsDocs>>(
const res = await request.get<ApiResult<CmsDocs>>(
'/cms/cms-docs/' + id
);
if (res.code === 0 && res.data) {
+8 -8
View File
@@ -1,4 +1,4 @@
import cmsRequest from '@/utils/cmsRequest';
import request from '@/utils/request';
import type { ApiResult, PageResult } from '@/api';
import type { CmsDocsBook, CmsDocsBookParam } from './model';
@@ -7,7 +7,7 @@ import type { CmsDocsBook, CmsDocsBookParam } from './model';
* 分页查询书籍记录表
*/
export async function pageCmsDocsBook(params: CmsDocsBookParam) {
const res = await cmsRequest.get<ApiResult<PageResult<CmsDocsBook>>>(
const res = await request.get<ApiResult<PageResult<CmsDocsBook>>>(
'/cms/cms-docs-book/page',
{
params
@@ -23,7 +23,7 @@ export async function pageCmsDocsBook(params: CmsDocsBookParam) {
* 查询书籍记录表列表
*/
export async function listCmsDocsBook(params?: CmsDocsBookParam) {
const res = await cmsRequest.get<ApiResult<CmsDocsBook[]>>(
const res = await request.get<ApiResult<CmsDocsBook[]>>(
'/cms/cms-docs-book',
{
params
@@ -39,7 +39,7 @@ export async function listCmsDocsBook(params?: CmsDocsBookParam) {
* 添加书籍记录表
*/
export async function addCmsDocsBook(data: CmsDocsBook) {
const res = await cmsRequest.post<ApiResult<unknown>>(
const res = await request.post<ApiResult<unknown>>(
'/cms/cms-docs-book',
data
);
@@ -53,7 +53,7 @@ export async function addCmsDocsBook(data: CmsDocsBook) {
* 修改书籍记录表
*/
export async function updateCmsDocsBook(data: CmsDocsBook) {
const res = await cmsRequest.put<ApiResult<unknown>>(
const res = await request.put<ApiResult<unknown>>(
'/cms/cms-docs-book',
data
);
@@ -67,7 +67,7 @@ export async function updateCmsDocsBook(data: CmsDocsBook) {
* 删除书籍记录表
*/
export async function removeCmsDocsBook(id?: number) {
const res = await cmsRequest.del<ApiResult<unknown>>(
const res = await request.del<ApiResult<unknown>>(
'/cms/cms-docs-book/' + id
);
if (res.code === 0) {
@@ -80,7 +80,7 @@ export async function removeCmsDocsBook(id?: number) {
* 批量删除书籍记录表
*/
export async function removeBatchCmsDocsBook(data: (number | undefined)[]) {
const res = await cmsRequest.del<ApiResult<unknown>>(
const res = await request.del<ApiResult<unknown>>(
'/cms/cms-docs-book/batch',
{
data
@@ -96,7 +96,7 @@ export async function removeBatchCmsDocsBook(data: (number | undefined)[]) {
* 根据id查询书籍记录表
*/
export async function getCmsDocsBook(id: number) {
const res = await cmsRequest.get<ApiResult<CmsDocsBook>>(
const res = await request.get<ApiResult<CmsDocsBook>>(
'/cms/cms-docs-book/' + id
);
if (res.code === 0 && res.data) {
+8 -8
View File
@@ -1,4 +1,4 @@
import cmsRequest from '@/utils/cmsRequest';
import request from '@/utils/request';
import type { ApiResult, PageResult } from '@/api';
import type { CmsDocsContent, CmsDocsContentParam } from './model';
@@ -7,7 +7,7 @@ import type { CmsDocsContent, CmsDocsContentParam } from './model';
* 分页查询文档内容记录表
*/
export async function pageCmsDocsContent(params: CmsDocsContentParam) {
const res = await cmsRequest.get<ApiResult<PageResult<CmsDocsContent>>>(
const res = await request.get<ApiResult<PageResult<CmsDocsContent>>>(
'/cms/cms-docs-content/page',
{
params
@@ -23,7 +23,7 @@ export async function pageCmsDocsContent(params: CmsDocsContentParam) {
* 查询文档内容记录表列表
*/
export async function listCmsDocsContent(params?: CmsDocsContentParam) {
const res = await cmsRequest.get<ApiResult<CmsDocsContent[]>>(
const res = await request.get<ApiResult<CmsDocsContent[]>>(
'/cms/cms-docs-content',
{
params
@@ -39,7 +39,7 @@ export async function listCmsDocsContent(params?: CmsDocsContentParam) {
* 添加文档内容记录表
*/
export async function addCmsDocsContent(data: CmsDocsContent) {
const res = await cmsRequest.post<ApiResult<unknown>>(
const res = await request.post<ApiResult<unknown>>(
'/cms/cms-docs-content',
data
);
@@ -53,7 +53,7 @@ export async function addCmsDocsContent(data: CmsDocsContent) {
* 修改文档内容记录表
*/
export async function updateCmsDocsContent(data: CmsDocsContent) {
const res = await cmsRequest.put<ApiResult<unknown>>(
const res = await request.put<ApiResult<unknown>>(
'/cms/cms-docs-content',
data
);
@@ -67,7 +67,7 @@ export async function updateCmsDocsContent(data: CmsDocsContent) {
* 删除文档内容记录表
*/
export async function removeCmsDocsContent(id?: number) {
const res = await cmsRequest.del<ApiResult<unknown>>(
const res = await request.del<ApiResult<unknown>>(
'/cms/cms-docs-content/' + id
);
if (res.code === 0) {
@@ -80,7 +80,7 @@ export async function removeCmsDocsContent(id?: number) {
* 批量删除文档内容记录表
*/
export async function removeBatchCmsDocsContent(data: (number | undefined)[]) {
const res = await cmsRequest.del<ApiResult<unknown>>(
const res = await request.del<ApiResult<unknown>>(
'/cms/cms-docs-content/batch',
{
data
@@ -96,7 +96,7 @@ export async function removeBatchCmsDocsContent(data: (number | undefined)[]) {
* 根据id查询文档内容记录表
*/
export async function getCmsDocsContent(id: number) {
const res = await cmsRequest.get<ApiResult<CmsDocsContent>>(
const res = await request.get<ApiResult<CmsDocsContent>>(
'/cms/cms-docs-content/' + id
);
if (res.code === 0 && res.data) {

Some files were not shown because too many files have changed in this diff Show More