From ae8b86631913204a8833d60b9334ac4520ec53a5 Mon Sep 17 00:00:00 2001 From: apple Date: Sat, 21 Mar 2026 02:33:14 +0800 Subject: [PATCH] =?UTF-8?q?feat(fsgx):=20=E7=AE=A1=E7=90=86=E5=90=8E?= =?UTF-8?q?=E5=8F=B0=E4=B8=8E=E9=83=A8=E7=BD=B2=E7=9B=B8=E5=85=B3=E6=9B=B4?= =?UTF-8?q?=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - admin: 路由守卫修复空白页、addRoute、devServer 端口与代理 - admin: package.json 生产构建去掉 NODE_OPTIONS openssl - ajcaptcha: 滑块验证码改用 file 缓存避免 Redis NOAUTH - nginx-crmeb: 增加 81 端口站点 - docs: deploy 补充 NOAUTH/Redis 说明,新增 H5 部署脚本与 nginx 示例 - 其他: database、start-api、swoole ini、uniapp 资源等 Made-with: Cursor --- docs/deploy-h5-diagnose.sh | 24 ++ docs/deploy-h5.sh | 35 +++ docs/deploy.md | 233 +++++++++++++- docs/execution-plan.md | 289 ++++++++++++++++++ docs/nginx-hjf-cloud.conf.example | 34 +++ pro_v3.5.1/README.md | 2 +- .../app/http/middleware/InstallMiddleware.php | 4 + pro_v3.5.1/config/ajcaptcha.php | 6 +- pro_v3.5.1/config/database.php | 4 +- pro_v3.5.1/help/start-api.sh | 4 +- pro_v3.5.1/help/swoole-loader-php80.ini | 6 + pro_v3.5.1/nginx-crmeb.conf | 36 +++ pro_v3.5.1/public/index.php | 5 +- pro_v3.5.1/think | 5 +- pro_v3.5.1/view/admin/.env.production | 4 +- pro_v3.5.1/view/admin/package.json | 2 +- pro_v3.5.1/view/admin/src/router/index.js | 4 +- pro_v3.5.1/view/admin/vue.config.js | 8 +- pro_v3.5.1/view/uniapp/manifest.json | 37 +-- .../view/uniapp/static/images/support.png | Bin 0 -> 14209 bytes 20 files changed, 697 insertions(+), 45 deletions(-) create mode 100755 docs/deploy-h5-diagnose.sh create mode 100755 docs/deploy-h5.sh create mode 100644 docs/execution-plan.md create mode 100644 docs/nginx-hjf-cloud.conf.example create mode 100644 pro_v3.5.1/help/swoole-loader-php80.ini create mode 100644 pro_v3.5.1/view/uniapp/static/images/support.png diff --git a/docs/deploy-h5-diagnose.sh b/docs/deploy-h5-diagnose.sh new file mode 100755 index 00000000..e5fb98c4 --- /dev/null +++ b/docs/deploy-h5-diagnose.sh @@ -0,0 +1,24 @@ +#!/usr/bin/env bash +# 诊断 H5 部署问题(Unexpected token '<') +# 在服务器上检查:文件是否存在、index.html 引用、Nginx root +set -e +REMOTE="root@182.92.142.158" +REMOTE_PUBLIC="/www/wwwroot/hjf.suzhouyuqi.com/public" + +echo "=== 1. 检查 index.html 引用的 JS 文件 ===" +ssh "$REMOTE" "grep -oE 'src=[^>]+\.js' $REMOTE_PUBLIC/index.html 2>/dev/null || echo 'index.html 不存在或无法读取'" + +echo "" +echo "=== 2. 检查 static/js 下实际存在的文件 ===" +ssh "$REMOTE" "ls $REMOTE_PUBLIC/static/js/index.*.js $REMOTE_PUBLIC/static/js/chunk-vendors.*.js 2>/dev/null || echo '文件不存在'" + +echo "" +echo "=== 3. 直接请求 JS 看返回类型(应为 application/javascript) ===" +curl -sI "https://hjf.suzhouyuqi.com/static/js/chunk-vendors.54d49a5a.js" | head -5 + +echo "" +echo "=== 4. 若上面 Content-Type 不是 javascript,说明 Nginx 未正确提供静态文件 ===" +echo "请在宝塔/面板中检查站点配置:" +echo " - root 必须为: $REMOTE_PUBLIC" +echo " - 添加 location ~ ^/(static|assets|pages)/ { try_files \$uri =404; }" +echo "参考: docs/nginx-hjf-cloud.conf.example" diff --git a/docs/deploy-h5.sh b/docs/deploy-h5.sh new file mode 100755 index 00000000..a91e8eb3 --- /dev/null +++ b/docs/deploy-h5.sh @@ -0,0 +1,35 @@ +#!/usr/bin/env bash +# H5 前端一键部署到云服务器(需 sshpass,或已配置 SSH 免密) +# 支持 HBuilder 导出 web 或 h5 路径 +set -e +cd "$(dirname "$0")/.." +REMOTE="root@182.92.142.158" +REMOTE_PUBLIC="/www/wwwroot/hjf.suzhouyuqi.com/public" +BACKUP_DIR="/www/wwwroot/backup" + +# 优先使用 web,其次 h5 +if [ -d "pro_v3.5.1/view/uniapp/unpackage/dist/build/web" ]; then + H5_SRC="pro_v3.5.1/view/uniapp/unpackage/dist/build/web" +elif [ -d "pro_v3.5.1/view/uniapp/unpackage/dist/build/h5" ]; then + H5_SRC="pro_v3.5.1/view/uniapp/unpackage/dist/build/h5" +else + echo "错误:未找到 H5 构建产物(web 或 h5),请先用 HBuilder 或 npm run build:h5 打包" + exit 1 +fi + +echo "备份云服务器 public 目录(上一版本)..." +ssh "$REMOTE" "mkdir -p $BACKUP_DIR && tar -czf $BACKUP_DIR/hjf_public_\$(date +%Y%m%d_%H%M%S).tar.gz -C /www/wwwroot/hjf.suzhouyuqi.com public && echo '备份完成'" + +echo "上传 H5 到 $REMOTE:$REMOTE_PUBLIC ..." +# 先删除旧的 index.html、static、assets、pages,避免 /h5/ 路径残留 +ssh "$REMOTE" "cd $REMOTE_PUBLIC && rm -f index.html && rm -rf static assets pages 2>/dev/null; true" +tar czf - -C "$H5_SRC" . | ssh "$REMOTE" "cd $REMOTE_PUBLIC && tar xzf -" + +echo "修改权限 ..." +ssh "$REMOTE" "chown -R www:www $REMOTE_PUBLIC && chmod -R 755 $REMOTE_PUBLIC" + +echo "" +echo "验证 index.html 引用路径(应为 /static/ 而非 /h5/static/):" +ssh "$REMOTE" "grep -oE 'src=[^>]+\.js' $REMOTE_PUBLIC/index.html 2>/dev/null | head -3" +echo "" +echo "部署完成。访问 https://hjf.suzhouyuqi.com/ 验证。若仍报 Unexpected token '<',见 docs/deploy.md 4.7 节" diff --git a/docs/deploy.md b/docs/deploy.md index 468da7d2..115c4f18 100644 --- a/docs/deploy.md +++ b/docs/deploy.md @@ -17,7 +17,49 @@ |--------|----------|----------------| | 服务器 API | `pro_v3.5.1/`(排除 view、public/admin) | `/www/wwwroot/hjf.suzhouyuqi.com/` | | 管理后台前端 | `pro_v3.5.1/view/admin/dist/` | `/www/wwwroot/hjf.suzhouyuqi.com/public/admin/` | -| H5 前端 | `pro_v3.5.1/view/uniapp/unpackage/dist/build/h5/` | `/www/wwwroot/hjf.suzhouyuqi.com/public/` | +| H5 前端 | `pro_v3.5.1/view/uniapp/unpackage/dist/build/web/` 或 `.../h5/` | `/www/wwwroot/hjf.suzhouyuqi.com/public/` | + +### 云服务器 Nginx 配置要求(H5 根路径访问必备) + +域名根路径 `https://hjf.suzhouyuqi.com/` 需正确返回 H5 页面。若出现 `Unexpected token '<'`(请求 JS 时返回 HTML),通常是 Nginx 未正确提供静态文件。 + +**必须满足:** + +1. **站点 root 指向 public**:`root /www/wwwroot/hjf.suzhouyuqi.com/public;` +2. **index 顺序**:`index index.html index.php;`(优先 index.html) +3. **静态文件优先**:对 `/static/`、`/assets/`、`/pages/` 等路径,Nginx 应先尝试本地文件,**仅当文件不存在**时才转发到 PHP/Swoole。 + +**推荐配置示例**(宝塔/ LNMP 站点): + +```nginx +server { + listen 80; + listen 443 ssl http2; + server_name hjf.suzhouyuqi.com; + root /www/wwwroot/hjf.suzhouyuqi.com/public; + index index.html index.php; + + # 静态资源:直接由 Nginx 提供,不转发到 PHP + location ~ ^/(static|assets|pages)/ { + try_files $uri =404; + expires 7d; + } + + # PHP 请求 + location ~ \.php$ { + # 转发到 Swoole 或 php-fpm,按现有配置 + } + + # 其余请求:先找静态文件,再走 PHP + location / { + try_files $uri $uri/ /index.php?$query_string; + } +} +``` + +若使用 `if (!-e $request_filename) { proxy_pass ... }`,需确保**文件存在时不会进入 proxy**,否则 `/static/js/xxx.js` 会被错误地返回 HTML。 + +完整示例见 `docs/nginx-hjf-cloud.conf.example`。 --- @@ -152,6 +194,8 @@ tar -czvf /www/backup/hjf_admin_$(date +%Y%m%d_%H%M%S).tar.gz \ ### 3.3 上传(本地) +**方式 A:rsync**(服务器需已安装 rsync) + ```bash cd /Users/apple/scott2026/huangjingfen @@ -160,6 +204,15 @@ rsync -avz --delete \ root@182.92.142.158:/www/wwwroot/hjf.suzhouyuqi.com/public/admin/ ``` +**方式 B:tar + ssh**(服务器无 rsync 时使用) + +```bash +cd /Users/apple/scott2026/huangjingfen + +tar czf - -C pro_v3.5.1/view/admin/dist . | \ + ssh root@182.92.142.158 "cd /www/wwwroot/hjf.suzhouyuqi.com/public && rm -rf admin && mkdir -p admin && tar xzf - -C admin" +``` + ### 3.4 上传后修改权限(服务器) ```bash @@ -173,13 +226,56 @@ chmod -R 775 /www/wwwroot/hjf.suzhouyuqi.com/public/admin 浏览器访问 `http://hjf.suzhouyuqi.com/admin/login`,确认页面正常。 +### 3.6 本地环境部署 + +将管理后台前端打包并部署到本机,配合 `nginx-crmeb.conf` 与 Swoole 使用。 + +**前置条件:** + +- Nginx 已加载 `pro_v3.5.1/nginx-crmeb.conf`(`root` 指向 `pro_v3.5.1/public`,80 反代到 20199) +- Swoole API 已启动:`./help/start-api.sh` 或 `php -d memory_limit=300M think swoole` + +**步骤:** + +```bash +# 1. 本地构建(同 3.1) +cd /Users/apple/scott2026/huangjingfen/pro_v3.5.1/view/admin +npm install # 依赖有变更时执行 +npm run build + +# 2. 将构建产物复制到 public/admin(覆盖旧文件) +cd /Users/apple/scott2026/huangjingfen +rm -rf pro_v3.5.1/public/admin +cp -r pro_v3.5.1/view/admin/dist pro_v3.5.1/public/admin +``` + +或使用 rsync(保留权限、便于增量更新): + +```bash +cd /Users/apple/scott2026/huangjingfen +rsync -av --delete pro_v3.5.1/view/admin/dist/ pro_v3.5.1/public/admin/ +``` + +**验证:** + +浏览器访问 `http://127.0.0.1/admin/` 或 `http://127.0.0.1/admin/login`,确认管理后台页面正常。 + --- ## 四、子项目三:H5 前端 -仅部署 H5 构建产物到 `public/`(站点根目录)。 +仅部署 H5 构建产物到 `public/`(站点根目录)。部署时仅覆盖 H5 相关文件,不删除 `admin/`、`index.php` 等。 -### 4.1 本地构建 +### 4.1 本地打包(HBuilder 或 npm) + +**方式 A:HBuilder 打包(推荐)** + +1. 用 HBuilderX 打开 `pro_v3.5.1/view/uniapp` 项目 +2. 确认 `config/app.js` 中 `BASE_HOST` 已设为云服务器域名(如 `hjf.suzhouyuqi.com`) +3. 菜单:发行 → 网站-H5 手机版(或 Web) → 填写网站标题 → 发行 +4. 构建产物在 `view/uniapp/unpackage/dist/build/web/` 或 `.../build/h5/` + +**方式 B:命令行构建** ```bash cd /Users/apple/scott2026/huangjingfen/pro_v3.5.1/view/uniapp @@ -190,18 +286,24 @@ npm run build:h5 构建产物在 `view/uniapp/unpackage/dist/build/h5/`。 -### 4.2 备份(服务器) +### 4.2 备份(服务器,上传前执行) + +上传前在服务器备份 `public` 目录,便于回滚: ```bash ssh root@182.92.142.158 -mkdir -p /www/backup -tar -czvf /www/backup/hjf_public_$(date +%Y%m%d_%H%M%S).tar.gz \ +mkdir -p /www/wwwroot/backup +tar -czvf /www/wwwroot/backup/hjf_public_$(date +%Y%m%d_%H%M%S).tar.gz \ -C /www/wwwroot/hjf.suzhouyuqi.com public ``` +> 一键部署脚本 `deploy-h5.sh` 会自动执行此备份后再上传。 + ### 4.3 上传(本地) +**方式 A:rsync**(服务器需已安装 rsync) + ```bash cd /Users/apple/scott2026/huangjingfen @@ -212,9 +314,122 @@ rsync -avz \ > 不使用 `--delete`,避免覆盖或删除 `admin/`、`index.php` 等文件。 -### 4.4 验证 +**方式 B:tar + ssh**(服务器无 rsync 时使用) -浏览器访问 `http://hjf.suzhouyuqi.com/`,确认 H5 页面正常。 +```bash +cd /Users/apple/scott2026/huangjingfen + +tar czf - -C pro_v3.5.1/view/uniapp/unpackage/dist/build/h5 . | \ + ssh root@182.92.142.158 "cd /www/wwwroot/hjf.suzhouyuqi.com/public && tar xzf -" +``` + +> 仅解压覆盖 H5 文件(index.html、static/ 等),不删除 `admin/`。 + +**方式 C:scp** + +```bash +cd /Users/apple/scott2026/huangjingfen + +scp -r pro_v3.5.1/view/uniapp/unpackage/dist/build/h5/* \ + root@182.92.142.158:/www/wwwroot/hjf.suzhouyuqi.com/public/ +``` + +### 4.4 上传后修改权限(服务器) + +```bash +ssh root@182.92.142.158 + +chown -R www:www /www/wwwroot/hjf.suzhouyuqi.com/public +chmod -R 755 /www/wwwroot/hjf.suzhouyuqi.com/public +``` + +> 若仅更新 H5 根目录下的 index.html、static 等,可只对相关路径执行: +> `chown -R www:www /www/wwwroot/hjf.suzhouyuqi.com/public/index.html /www/wwwroot/hjf.suzhouyuqi.com/public/static` + +### 4.5 一键部署脚本(本地执行) + +脚本 `docs/deploy-h5.sh` 会依次执行:**备份上一版本 → 上传 → 修改权限**。执行前确保已打包好 H5(`unpackage/dist/build/web/` 或 `h5/` 存在): + +```bash +#!/usr/bin/env bash +# H5 前端一键部署到云服务器(需 sshpass,或已配置 SSH 免密) +# 支持 HBuilder 导出 web 或 h5 路径 +set -e +cd "$(dirname "$0")/.." +REMOTE="root@182.92.142.158" +REMOTE_PUBLIC="/www/wwwroot/hjf.suzhouyuqi.com/public" +BACKUP_DIR="/www/wwwroot/backup" + +# 优先使用 web,其次 h5 +if [ -d "pro_v3.5.1/view/uniapp/unpackage/dist/build/web" ]; then + H5_SRC="pro_v3.5.1/view/uniapp/unpackage/dist/build/web" +elif [ -d "pro_v3.5.1/view/uniapp/unpackage/dist/build/h5" ]; then + H5_SRC="pro_v3.5.1/view/uniapp/unpackage/dist/build/h5" +else + echo "错误:未找到 H5 构建产物,请先用 HBuilder 或 npm run build:h5 打包" + exit 1 +fi + +echo "备份云服务器 public 目录(上一版本)..." +ssh "$REMOTE" "mkdir -p $BACKUP_DIR && tar -czf $BACKUP_DIR/hjf_public_\$(date +%Y%m%d_%H%M%S).tar.gz -C /www/wwwroot/hjf.suzhouyuqi.com public && echo '备份完成'" + +echo "上传 H5 到 $REMOTE:$REMOTE_PUBLIC ..." +tar czf - -C "$H5_SRC" . | ssh "$REMOTE" "cd $REMOTE_PUBLIC && tar xzf -" + +echo "修改权限 ..." +ssh "$REMOTE" "chown -R www:www $REMOTE_PUBLIC && chmod -R 755 $REMOTE_PUBLIC" + +echo "部署完成,请访问 https://hjf.suzhouyuqi.com/ 验证" +``` + +用法: + +```bash +cd /Users/apple/scott2026/huangjingfen +chmod +x docs/deploy-h5.sh +./docs/deploy-h5.sh +``` + +若需密码,可安装 `sshpass` 后使用:`SSHPASS='A@123456' sshpass -e ./docs/deploy-h5.sh` + +### 4.6 验证 + +浏览器访问 `http://hjf.suzhouyuqi.com/` 或 `https://hjf.suzhouyuqi.com/`,确认 H5 页面正常。 + +### 4.7 H5 报错 `Unexpected token '<'` 排查 + +**现象**:访问根路径时控制台报 `index.xxx.js:1 Uncaught SyntaxError: Unexpected token '<'`。 + +**原因**:请求 JS 时服务器返回了 HTML(404 页或 index.php 输出)。常见情况: +- **index.html 引用 `/h5/static/...`**:旧版 index 与当前部署路径不一致,`/h5/static/js/xxx.js` 返回 404 → HTML +- **Nginx 未正确提供静态文件**:root 未指向 `.../public` 或缺少 `location ~ ^/(static|assets|pages)/` + +**排查步骤**: + +1. **检查 index.html 引用路径**: + ```bash + curl -s "https://hjf.suzhouyuqi.com/" | grep -oE 'src=[^>]+\.js' + ``` + 若出现 `/h5/static/`,说明服务器 index.html 为旧版,需重新部署。 +2. **重新构建并部署**(部署脚本会先清理旧 index.html、static 等): + ```bash + # 本地:HBuilder 发行 H5 或 npm run build:h5 + ./docs/deploy-h5.sh + ``` +3. **确认 Nginx root**:站点 root 必须为 `.../public`,见上文「云服务器 Nginx 配置要求」。 +4. **清除浏览器缓存**:强制刷新(Ctrl+Shift+R)或无痕模式访问。 + +--- + +## 四(补充)、管理后台登录滑块:`NOAUTH Authentication required` + +若访问 `/adminapi/ajcaptcha` 或 `/adminapi/is_captcha` 返回 `{"msg":"NOAUTH Authentication required."}`,说明 **Redis 需要密码但 `.env` 中 `[REDIS]` 的 `PASSWORD` 与服务器 Redis 不一致**(或 Redis 开启了 `requirepass` 而 `.env` 为空)。 + +**处理:** + +1. 在服务器上核对 Redis:`redis-cli -h <主机> -p 6379 -a '<实际密码>' ping`,将正确密码写入站点根目录 `.env` 的 `PASSWORD = ...`。 +2. 重启 Swoole / PHP 进程使配置生效。 +3. 项目已把 **滑块验证码(ajcaptcha)缓存改为本地 file**,不依赖 Redis 认证;部署后请同步更新 `config/ajcaptcha.php`。登录次数统计等仍走 Redis,**长期仍须修正 Redis 配置**。 --- @@ -247,5 +462,5 @@ tar -xzvf /www/backup/hjf_admin_YYYYMMDD_HHMMSS.tar.gz -C /www/wwwroot/hjf.suzho # 回滚 H5(恢复 public/ 目录) rm -rf /www/wwwroot/hjf.suzhouyuqi.com/public -tar -xzvf /www/backup/hjf_public_YYYYMMDD_HHMMSS.tar.gz -C /www/wwwroot/hjf.suzhouyuqi.com +tar -xzvf /www/wwwroot/backup/hjf_public_YYYYMMDD_HHMMSS.tar.gz -C /www/wwwroot/hjf.suzhouyuqi.com ``` diff --git a/docs/execution-plan.md b/docs/execution-plan.md new file mode 100644 index 00000000..7e9ca456 --- /dev/null +++ b/docs/execution-plan.md @@ -0,0 +1,289 @@ +# 黄精粉健康商城 · 剩余开发任务执行方案 + +> 基于 PRD_V2.md + openclaw-frontend-tasks.md 的现状分析 +> 制定日期:2026-03-15 +> 当前分支:claude/hjf-queue-admin-apis-hsymG + +--- + +## 一、现状盘点(已完成 vs 待完成) + +### ✅ 已完成的任务 + +| 阶段 | 任务 | 说明 | +|------|------|------| +| Phase 0 | P0-01, P0-02 | UniApp + Admin Mock 数据文件 | +| Stage 1A | P1A-01~06 | 全部6个 API 模块(uniapp + admin) | +| Stage 1B | P1B-01~04 | 全部4个公共组件(QueueProgress / AssetCard / MemberBadge / RefundNotice) | +| Stage 1C | P1C-01~06 | 全部6个新 UniApp 页面(公排状态/历史/规则 + 资产总览/积分明细 + 引导页) | +| Stage 1D | P1D-02 | 商品详情页:`is_queue_goods` 角标 + 公排提示条 | +| Stage 1D | P1D-03 | 购买确认页:多单拆分提示 + 公排入队说明 | +| Stage 1D | P1D-04 | 支付结果页:公排入队成功提示 + 查看公排入口 | +| Stage 1D | P1D-06 | 提现页:7% 手续费动态计算 + 提示文案 | +| Stage 1E | P1E-01~06 | 全部6个 Admin 新页面(公排订单/财务/配置 + 会员管理/配置 + 积分日志) | +| Stage 1F | P1F-01~07 | 全部路由注册(pages.json + Admin hjfQueue.js 路由模块 + index.js 导入) | +| Stage 1G | P1G-01 | Admin 用户管理列表:`member_level`、`no_assess` 列和筛选项 | + +--- + +### ⏳ 待完成的任务(本方案覆盖范围) + +``` +Phase 1 尾单(4项) +├── P1D-01 首页:报单商品角标 + 公排入口Banner +├── P1D-05 推荐收益页:积分替换佣金显示 +├── P1D-07 个人中心:HjfMemberBadge等级徽章嵌入 +└── P1G-02 Admin商品编辑:报单标记 + 积分支付白名单 + +Phase 2 数据库迁移(5项) +Phase 3 后端 API 开发(16项) +Phase 4 前后端联调集成(5项) +Phase 5 完整测试(8项) +``` + +--- + +## 二、执行方案 + +### 阶段 A:Phase 1 收尾(前端,可立即执行) + +> 依赖:无。可在当前 Mock 模式下独立完成。 +> 目标:让 Phase 1 所有38个任务全部 `[x]`,解锁 CP-01 评审检查点。 + +--- + +#### 任务 A1 — P1D-01:首页报单商品角标 + +**文件**:`pro_v3.5.1/view/uniapp/pages/index/index.vue` + +**改造内容**: +1. 在商品列表卡片的商品名/图片处,检查 `item.is_queue_goods == 1`,叠加渲染 `报单` 角标(红色标签,参考 goods_details 中已有的 `.queue-goods-tag` 样式)。 +2. 在首页 Banner 区或活动专区下方,增加"公排进度"快捷入口行(复用 `HjfQueueProgress` 组件缩略版,或仅放文字按钮跳转 `/pages/queue/status`)。 +3. 无需新增 API 调用,角标从商品列表字段 `is_queue_goods` 读取即可。 + +**验收标准**:报单商品卡片右上角出现红色"报单"角标;点击公排入口可跳转公排状态页。 + +--- + +#### 任务 A2 — P1D-05:推荐收益页积分替换佣金 + +**文件**:`pro_v3.5.1/view/uniapp/pages/users/user_spread_money/index.vue` + +**改造内容**: +1. 将列表中"佣金"字样统一替换为"积分",金额字段从 `money`/`commission` 改为读取 `points`。 +2. 展示积分类型标签:`reward_direct`(直推奖励)/ `reward_umbrella`(伞下奖励)。 +3. 导入 `import { getTeamIncome } from '@/api/hjfMember.js'`,替换原有 API 调用。 +4. 数值格式:整数积分,不保留小数;去掉 `¥` 符号,改为"积分"后缀。 + +**验收标准**:推荐收益列表显示积分数量而非金额,类型标签正确区分直推/伞下。 + +--- + +#### 任务 A3 — P1D-07:个人中心会员等级徽章 + +**文件**:`pro_v3.5.1/view/uniapp/pages/user/index.vue` + +**改造内容**: +1. 引入 `HjfMemberBadge` 组件,在用户头像/昵称旁嵌入等级徽章。 + ```js + import HjfMemberBadge from '@/components/HjfMemberBadge.vue' + ``` +2. 从 `getMemberInfo` API(已有 Mock)获取 `member_level`,传入组件 `:level` prop。 +3. 在资产快捷入口区域已有的 `hjf-nav-row` 基础上,补充"待释放积分"数值预览(展示 `frozen_points`,不换页即可看到大数字)。 +4. 已有的公排查询 + 资产入口导航行保持不变,不重复建设。 + +**验收标准**:昵称旁出现对应等级的彩色徽章;资产行显示待释放积分数。 + +--- + +#### 任务 A4 — P1G-02:Admin 商品编辑-报单标记与支付方式 + +**文件**:`pro_v3.5.1/view/admin/src/pages/product/creatProduct/index.vue` + +**改造内容**: +1. 在商品基本信息 Tab 中增加"报单商品"开关(iView `i-switch`),绑定 `formValidate.is_queue_goods`,默认 `false`。 +2. 在支付方式 Tab / 销售设置区域,增加复选框组"允许积分支付"(`allow_pay_types`),选项:`待释放积分`、`已释放积分`;报单商品开关开启时,此项置灰并强制清空。 +3. 表单提交时将 `is_queue_goods`(0/1)和 `allow_pay_types`(数组序列化)一并提交。 +4. 编辑回显时正确反填两个字段。 + +**验收标准**:新建/编辑商品可设置报单标记;报单商品自动禁用积分支付选项。 + +--- + +### 阶段 B:Phase 2 数据库迁移 + +> 依赖:后端开发环境就绪(ThinkPHP 8 + MySQL 8.0)。 +> 建议由后端工程师执行,前端工程师无需等待此阶段。 + +| 任务 | 操作 | 目标 | +|------|------|------| +| P2-01 | CREATE TABLE | `eb_queue_pool`(公排池,9个字段,含复合索引) | +| P2-02 | CREATE TABLE | `eb_points_release_log`(积分释放日志,7个字段) | +| P2-03 | ALTER TABLE | `eb_user` 增加4字段:`member_level`、`no_assess`、`frozen_points`、`available_points` | +| P2-04 | ALTER TABLE | `eb_store_product` 增加2字段:`is_queue_goods`、`allow_pay_types` | +| P2-05 | INSERT | `eb_system_config` 插入9项系统配置键值对(公排倍数、释放比例、手续费率、各等级门槛和奖励) | + +**关键索引(P2-01)**: +```sql +INDEX idx_uid (uid), +INDEX idx_status_add_time (status, add_time), +INDEX idx_queue_no (queue_no), +INDEX idx_trigger_batch (trigger_batch) +``` + +--- + +### 阶段 C:Phase 3 后端 API 开发 + +> 依赖:Phase 2 完成。 +> 开发顺序:先核心引擎,再外围接口,最后定时任务。 + +#### C1 — 公排引擎(优先级最高) + +| 任务 | 文件/类 | 内容 | +|------|---------|------| +| P3-01 | `QueuePool` Service | 入队逻辑(写 `eb_queue_pool`,Redis 分布式锁防并发) | +| P3-02 | `QueueRefund` Service | 退款触发逻辑(每入N单检查退款,使用 think-queue 异步处理) | +| P3-03 | `QueueController` | 用户端接口:`GET /hjf/queue/status`、`GET /hjf/queue/history` | +| P3-04 | `AdminQueueController` | Admin接口:`GET /hjf/queue/order`、`GET /hjf/queue/config`、`POST /hjf/queue/config`、`GET /hjf/queue/finance` | + +**核心逻辑要点**: +- 支付回调成功后:判断 `is_queue_goods` → 多单拆分 → 逐单调用 `QueuePool::enqueue()` → 检查触发条件 +- Redis Key:`hjf:queue:lock`(分布式锁),`hjf:queue:pending_count`(待触发计数) +- 退款写入 `eb_user.now_money`(复用 CRMEB 余额字段),记录 `eb_user_bill` + +#### C2 — 积分奖励引擎 + +| 任务 | 文件/类 | 内容 | +|------|---------|------| +| P3-05 | `PointsReward` Service | 级差计算:按会员等级发放直推/伞下积分,写入 `frozen_points` | +| P3-06 | `PointsRelease` Job | 每日凌晨定时任务:`frozen_points × 0.4‰ → available_points`,写 `eb_points_release_log` | +| P3-07 | `PointsController` | 用户端接口:`GET /hjf/points/detail`(5类型筛选,分页) | +| P3-08 | `AdminPointsController` | Admin接口:`GET /hjf/points/release-log` | + +**每日释放公式**:`release_amount = FLOOR(frozen_points × rate / 1000)`,`rate` 取系统配置 `hjf_release_rate`(默认 4)。 + +#### C3 — 会员等级体系 + +| 任务 | 文件/类 | 内容 | +|------|---------|------| +| P3-09 | `MemberLevel` Service | 升级判断:直推单数 / 伞下业绩单数达标后自动升级;伞下业绩分离逻辑 | +| P3-10 | `AdminMemberController` | Admin接口:`GET /hjf/member/list`、`PUT /hjf/member/level/:uid`、`GET/POST /hjf/member/config` | + +**升级触发时机**:每次订单支付回调完成后,对推荐链上的所有上级异步检查升级条件。 + +#### C4 — 资产接口 + +| 任务 | 文件/类 | 内容 | +|------|---------|------| +| P3-11 | `AssetsController` | `GET /hjf/assets/overview`:返回余额 + 积分汇总(复用 `eb_user` 字段) | +| P3-12 | `AssetsController` | `GET /hjf/assets/cash/detail`:现金流水(分页,复用 `eb_user_bill`) | + +#### C5 — 路由注册 + +| 任务 | 内容 | +|------|------| +| P3-13 | `route/api.php`:注册用户端全部 hjf 路由(含鉴权中间件) | +| P3-14 | `route/admin.php`:注册 Admin 端全部 hjf 路由(含权限中间件) | + +#### C6 — 单元测试桩 + +| 任务 | 内容 | +|------|------| +| P3-15 | 公排引擎单元测试:入队/触发退款/分布式锁 | +| P3-16 | 积分计算单元测试:级差计算/每日释放精度(bcmath) | + +--- + +### 阶段 D:Phase 4 前后端联调集成 + +> 依赖:Phase 2 + Phase 3 完成,测试环境可访问真实 API。 + +| 任务 | 内容 | 操作 | +|------|------|------| +| P4-01 | 关闭 Mock 开关 | 将所有 `const USE_MOCK = true` 改为 `false`(UniApp + Admin 共8个文件) | +| P4-02 | UniApp 冒烟测试 | 登录 → 查看公排状态 → 资产总览 → 积分明细 → 推荐收益 | +| P4-03 | Admin 冒烟测试 | 公排订单列表 → 公排配置保存 → 会员等级调整 → 积分日志查询 | +| P4-04 | 支付回调联调 | 测试购买报单商品 → 公排入队 → 积分发放 → 等级升级完整链路 | +| P4-05 | 定时任务验证 | 手动触发每日积分释放任务,验证 `release_log` 记录正确 | + +**Mock 关闭检查清单**: +``` +uniapp/api/hjfQueue.js USE_MOCK → false +uniapp/api/hjfAssets.js USE_MOCK → false +uniapp/api/hjfMember.js USE_MOCK → false +admin/src/api/hjfQueue.js USE_MOCK → false +admin/src/api/hjfMember.js USE_MOCK → false +admin/src/api/hjfPoints.js USE_MOCK → false +``` + +--- + +### 阶段 E:Phase 5 完整测试 + +> 依赖:Phase 4 联调通过。 + +| 任务 | 类型 | 内容 | +|------|------|------| +| P5-01 | 前端渲染测试 | 所有新页面在3个Mock场景(A/B/C)下截图验收 | +| P5-02 | 后端接口测试 | 用 Postman/Apifox 验证所有 P3 接口的响应格式和边界值 | +| P5-03 | 公排边界测试 | 精确触发:第4单入队时退款到第1单;多人同时入队(并发锁) | +| P5-04 | 积分精度测试 | bcmath 计算:`1000000 × 4 / 1000 = 4000`(无浮点误差) | +| P5-05 | 会员升级测试 | 直推3单后自动升级创客;伞下30单升云店;业绩分离逻辑 | +| P5-06 | 并发压测 | 1000并发用户同时访问公排状态页;公排入队 200 TPS | +| P5-07 | E2E 全流程 | 新用户注册 → 引导页 → 购买报单商品 → 等待公排退款 → 申请提现 | +| P5-08 | 回归测试 | CRMEB 原有功能(登录/商品/订单/支付)未被改造影响 | + +--- + +## 三、执行优先级与分工建议 + +``` +立即可执行(无依赖,Agent 可直接实施) +├── A1 首页报单角标 ← 最简单,约30分钟 +├── A2 推荐收益页积分替换 ← 约45分钟 +├── A3 个人中心等级徽章 ← 约30分钟 +└── A4 Admin商品编辑改造 ← 约60分钟 + +等待后端就绪(并行推进) +├── B 数据库迁移 ← DBA/后端工程师 +├── C 后端API开发 ← 后端工程师(C1优先) +└── D 联调集成 ← 前后端协作 + +最终验收 +└── E 完整测试 ← 测试工程师 +``` + +**关键路径**:`A1~A4 完成` → `CP-01 评审` → `B+C 并行` → `D 联调` → `E 测试` + +--- + +## 四、风险点与注意事项 + +| 风险 | 描述 | 应对措施 | +|------|------|---------| +| 公排并发竞争 | 多单同时入队可能重复触发退款 | Redis `SET NX EX` 分布式锁,退款前二次检查状态 | +| 积分浮点误差 | `3600 × 0.4‰` 在 PHP 中存在精度问题 | 全程使用 `bcmath`:`bcmul($points, '4', 0)` → `bcdiv(..., '1000', 0)` | +| 伞下业绩分离 | 下级升级后业绩需从上级扣除 | 升级事件写入消息队列,异步重算上级业绩;加数据库事务 | +| Admin 路由权限 | hjf 新路由需配置到角色权限表 | P3-14 后端路由注册时同步写 `eb_system_menus` | +| CRMEB 原生字段冲突 | `eb_user` 新增字段可能影响原有查询 | ALTER TABLE 使用 `DEFAULT 0`,不破坏现有 NULL 约束 | + +--- + +## 五、当前可立即下达的指令(Agent 参考) + +按优先级排序,每条指令对应一个独立任务,完成后 `git commit` 即可: + +``` +1. feat(P1D-01): 首页报单商品角标与公排快捷入口 + 文件: pages/index/index.vue + +2. feat(P1D-05): 推荐收益页积分替换佣金 + 文件: pages/users/user_spread_money/index.vue + +3. feat(P1D-07): 个人中心嵌入HjfMemberBadge等级徽章 + 文件: pages/user/index.vue + +4. feat(P1G-02): Admin商品编辑报单标记与积分支付配置 + 文件: admin/src/pages/product/creatProduct/index.vue +``` diff --git a/docs/nginx-hjf-cloud.conf.example b/docs/nginx-hjf-cloud.conf.example new file mode 100644 index 00000000..621f9c71 --- /dev/null +++ b/docs/nginx-hjf-cloud.conf.example @@ -0,0 +1,34 @@ +# 云服务器 hjf.suzhouyuqi.com 站点 Nginx 配置示例 +# 用于宝塔/LNMP:复制到站点配置或 include 使用 +# 关键:root 指向 public,静态文件 /static/、/assets/、/pages/ 由 Nginx 直接提供 + +server { + listen 80; + listen 443 ssl http2; + server_name hjf.suzhouyuqi.com; + root /www/wwwroot/hjf.suzhouyuqi.com/public; + index index.html index.php; + + # SSL 证书(宝塔通常自动配置) + # ssl_certificate ... + # ssl_certificate_key ... + + # H5 静态资源:直接由 Nginx 提供,避免返回 HTML 导致 "Unexpected token '<'" + location ~ ^/(static|assets|pages)/ { + try_files $uri =404; + expires 7d; + } + + # PHP 请求(按现有 Swoole/php-fpm 配置) + location ~ \.php$ { + proxy_pass http://127.0.0.1:20199; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + + # 其余请求:先找静态文件,再走入口 + location / { + try_files $uri $uri/ /index.php?$query_string; + } +} diff --git a/pro_v3.5.1/README.md b/pro_v3.5.1/README.md index 1a14834e..100ef673 100644 --- a/pro_v3.5.1/README.md +++ b/pro_v3.5.1/README.md @@ -322,7 +322,7 @@ php think swoole 5. 后台登录: http://域名/admin -默认账号:admin 密码:A@123456 +默认账号:admin 密码:A@123456 或 A123456 ## 启动命令 diff --git a/pro_v3.5.1/app/http/middleware/InstallMiddleware.php b/pro_v3.5.1/app/http/middleware/InstallMiddleware.php index f340475b..c31b856b 100644 --- a/pro_v3.5.1/app/http/middleware/InstallMiddleware.php +++ b/pro_v3.5.1/app/http/middleware/InstallMiddleware.php @@ -24,6 +24,10 @@ class InstallMiddleware implements MiddlewareInterface public function handle(Request $request, \Closure $next) { + // CORS 预检请求不重定向,交给后续 AllowOriginMiddleware 返回 200 + CORS 头 + if (strtoupper($request->method()) === 'OPTIONS') { + return $next($request); + } //检测是否已安装CRMEB系统 if (!is_dir(root_path() . "public/install/") || !is_file(root_path() . "public/install/install.lock")) { return redirect('/install/index'); diff --git a/pro_v3.5.1/config/ajcaptcha.php b/pro_v3.5.1/config/ajcaptcha.php index 3663edb1..e0c14c70 100644 --- a/pro_v3.5.1/config/ajcaptcha.php +++ b/pro_v3.5.1/config/ajcaptcha.php @@ -35,8 +35,10 @@ return [ 'text' => '' ], 'cache' => [ - //若您使用了框架,并且想使用类似于redis这样的缓存驱动,则应换成框架的中的缓存驱动 - 'constructor' => app()->make(\think\Cache::class), + // file 存储:生产环境 Redis 密码错误时避免 ajcaptcha 报 NOAUTH;业务主缓存仍用 config/cache.php + 'constructor' => static function ($options) { + return app()->make(\think\Cache::class)->store('file'); + }, 'method' => [ //遵守PSR-16规范不需要设置此项(tp6, laravel,hyperf)。如tp5就不支持(tp5缓存方法是rm,所以要配置为"delete" => "rm") /** diff --git a/pro_v3.5.1/config/database.php b/pro_v3.5.1/config/database.php index 28552703..b73937ce 100644 --- a/pro_v3.5.1/config/database.php +++ b/pro_v3.5.1/config/database.php @@ -29,8 +29,8 @@ return [ 'type' => env('DATABASE_TYPE', 'mysql'), // 服务器地址 'hostname' => env('DATABASE_HOSTNAME', '127.0.0.1'), - // 数据库名 - 'database' => env('DATABASE_DATABASE', ''), + // 数据库名(直接写死,避免 .env 路径问题) + 'database' => 'fsgx-shop', // 用户名 'username' => env('DATABASE_USERNAME', 'root'), // 密码 diff --git a/pro_v3.5.1/help/start-api.sh b/pro_v3.5.1/help/start-api.sh index ed130f60..d25ccf35 100755 --- a/pro_v3.5.1/help/start-api.sh +++ b/pro_v3.5.1/help/start-api.sh @@ -4,4 +4,6 @@ set -e cd "$(dirname "$0")/.." -php -d memory_limit=300M think swoole +# 使用 PHP 8.0(Swoole Loader 仅支持 8.0) +PHP_BIN="${PHP_BIN:-/usr/local/opt/php@8.0/bin/php}" +"$PHP_BIN" -d memory_limit=300M think swoole diff --git a/pro_v3.5.1/help/swoole-loader-php80.ini b/pro_v3.5.1/help/swoole-loader-php80.ini new file mode 100644 index 00000000..164f2e20 --- /dev/null +++ b/pro_v3.5.1/help/swoole-loader-php80.ini @@ -0,0 +1,6 @@ +; 供 PHP 8.0 加载 Swoole Loader(在 conf.d 中软链或复制此文件,或 php.ini 中 include 此路径) +; 使用方式(任选其一): +; 1. 复制到 PHP 8.0 conf.d:cp help/swoole-loader-php80.ini /usr/local/etc/php/8.0/conf.d/99-swoole-loader.ini +; 并确保 extension= 指向的 .so 路径存在(如下方为项目内绝对路径,需按本机修改)。 +; 2. 或将 .so 复制到 PHP 8.0 的 extension_dir 后,改为:extension = swoole_loader_80_nts.so +extension = /Users/apple/scott2026/huangjingfen/pro_v3.5.1/help/swoole_loader_mac/swoole_loader_80_nts.so diff --git a/pro_v3.5.1/nginx-crmeb.conf b/pro_v3.5.1/nginx-crmeb.conf index 6cab0431..024de327 100644 --- a/pro_v3.5.1/nginx-crmeb.conf +++ b/pro_v3.5.1/nginx-crmeb.conf @@ -1,3 +1,4 @@ +# 站点1:默认 80 端口 server { listen 80; server_name 127.0.0.1; @@ -31,3 +32,38 @@ server { expires 12h; } } + +# 站点2:81 端口(远程 MySQL: fsgx-shop) +server { + listen 81; + server_name 127.0.0.1; + + root /Users/apple/scott2026/huangjingfen/pro_v3.5.1/public; + index index.html index.php; + + location ~* \.(php|jsp|cgi|asp|aspx)$ { + proxy_pass http://127.0.0.1:20199; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header REMOTE-HOST $remote_addr; + } + + location / { + if (!-e $request_filename) { + proxy_pass http://127.0.0.1:20199; + } + proxy_http_version 1.1; + proxy_read_timeout 360s; + proxy_redirect off; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header REMOTE-HOST $remote_addr; + add_header X-Cache $upstream_cache_status; + add_header Cache-Control no-cache; + expires 12h; + } +} diff --git a/pro_v3.5.1/public/index.php b/pro_v3.5.1/public/index.php index 436f6579..85cb85ec 100644 --- a/pro_v3.5.1/public/index.php +++ b/pro_v3.5.1/public/index.php @@ -16,8 +16,9 @@ define('DS', DIRECTORY_SEPARATOR); require __DIR__ . '/../vendor/autoload.php'; -// 执行HTTP应用并响应 -$http = (new App())->http; +// 显式指定应用根目录,确保 .env 正确加载(避免 getDefaultRootPath 返回上级目录) +$rootPath = dirname(__DIR__) . DIRECTORY_SEPARATOR; +$http = (new App($rootPath))->http; $response = $http->run(); diff --git a/pro_v3.5.1/think b/pro_v3.5.1/think index b7e9aa43..1ba81332 100644 --- a/pro_v3.5.1/think +++ b/pro_v3.5.1/think @@ -15,5 +15,6 @@ namespace think; // 加载基础文件 require __DIR__ . '/vendor/autoload.php'; -// 应用初始化 -(new App())->console->run(); \ No newline at end of file +// 显式指定应用根目录,确保 .env 正确加载(避免 getDefaultRootPath 返回上级目录) +$rootPath = __DIR__ . DIRECTORY_SEPARATOR; +(new App($rootPath))->console->run(); \ No newline at end of file diff --git a/pro_v3.5.1/view/admin/.env.production b/pro_v3.5.1/view/admin/.env.production index 1b46ac40..dadf2827 100644 --- a/pro_v3.5.1/view/admin/.env.production +++ b/pro_v3.5.1/view/admin/.env.production @@ -5,6 +5,6 @@ VUE_APP_ENV='production' # 页面 title VUE_APP_TITLE=CRMEB # socket 系统连接地址 (ws)或(wss)://www.crmeb.com(换成你的域名)/ws 非独立部署默认为空 -VUE_APP_WS_ADMIN_URL='ws://hjf.suzhouyuqi.com/ws' +VUE_APP_WS_ADMIN_URL='ws://fsgx.uj345.com/ws' # 接口请求地址 (http)或 (https)://www.crmeb.com(换成你的域名)/adminapi 非独立部署默认为空 -VUE_APP_API_URL='http://hjf.suzhouyuqi.com/adminapi' +VUE_APP_API_URL='http://fsgx.uj345.com/adminapi' diff --git a/pro_v3.5.1/view/admin/package.json b/pro_v3.5.1/view/admin/package.json index 1e3ee7fc..abf16892 100644 --- a/pro_v3.5.1/view/admin/package.json +++ b/pro_v3.5.1/view/admin/package.json @@ -5,7 +5,7 @@ "scripts": { "serve": "node src/libs/start.js && vue-cli-service serve --open --mode=dev", "dev": "node src/libs/start.js && vue-cli-service serve --open --mode=dev", - "build": "cross-env NODE_OPTIONS=--openssl-legacy-provider vue-cli-service build --mode=production" + "build": "vue-cli-service build --mode=production" }, "dependencies": { "@babel/polyfill": "^7.12.1", diff --git a/pro_v3.5.1/view/admin/src/router/index.js b/pro_v3.5.1/view/admin/src/router/index.js index 434c1498..df6d3eb5 100644 --- a/pro_v3.5.1/view/admin/src/router/index.js +++ b/pro_v3.5.1/view/admin/src/router/index.js @@ -79,7 +79,7 @@ const router = new VueRouter({ routes: [...routes], mode: Setting.routerMode, }); -router.addRoutes(supplierRoutes); +supplierRoutes.forEach((route) => router.addRoute(route)); setTimeout(() => { console.log(router, "11"); }, 1000); @@ -140,6 +140,8 @@ function handlePublicRoute(to, next) { if (hasMenus || isLoginPage) { return next(); } + // 无菜单且非登录页时,重定向到登录页(修复空白页:此前未调用 next() 导致导航挂起) + next({ path: `${Setting.roterPre}/login`, query: { redirect: to.fullPath } }); } router.afterEach((to) => { // if (Setting.showProgressBar) iView.LoadingBar.finish(); diff --git a/pro_v3.5.1/view/admin/vue.config.js b/pro_v3.5.1/view/admin/vue.config.js index c8a00968..73c8c5d5 100644 --- a/pro_v3.5.1/view/admin/vue.config.js +++ b/pro_v3.5.1/view/admin/vue.config.js @@ -27,11 +27,11 @@ module.exports = { productionSourceMap: false, //关闭生产环境下的SourceMap映射文件 devServer: { publicPath: Setting.publicPath, - port: 8080, + port: 8085, proxy: { - '/adminapi': { target: 'http://127.0.0.1:20199', changeOrigin: true }, - '/api': { target: 'http://127.0.0.1:20199', changeOrigin: true }, - '/kefuapi': { target: 'http://127.0.0.1:20199', changeOrigin: true }, + '/adminapi': { target: process.env.VUE_APP_API_URL ? new URL(process.env.VUE_APP_API_URL).origin : 'http://127.0.0.1:20199', changeOrigin: true }, + '/api': { target: process.env.VUE_APP_API_URL ? new URL(process.env.VUE_APP_API_URL).origin : 'http://127.0.0.1:20199', changeOrigin: true }, + '/kefuapi': { target: process.env.VUE_APP_API_URL ? new URL(process.env.VUE_APP_API_URL).origin : 'http://127.0.0.1:20199', changeOrigin: true }, }, }, diff --git a/pro_v3.5.1/view/uniapp/manifest.json b/pro_v3.5.1/view/uniapp/manifest.json index ee7376c5..75b6cb99 100644 --- a/pro_v3.5.1/view/uniapp/manifest.json +++ b/pro_v3.5.1/view/uniapp/manifest.json @@ -1,7 +1,7 @@ { - "name" : "crmeb", - "appid" : "__UNI__70A74E1", - "description" : "crmeb商城", + "name" : "黄精粉", + "appid" : "__UNI__6691FE3", + "description" : "黄精粉商城", "versionName" : "3.5.1", "versionCode" : 351, "transformPx" : false, @@ -125,13 +125,13 @@ }, "icons" : { "android" : { - "hdpi" : "unpackage/res/icons/72x72.png", - "xhdpi" : "unpackage/res/icons/96x96.png", - "xxhdpi" : "unpackage/res/icons/144x144.png", - "xxxhdpi" : "unpackage/res/icons/192x192.png" + "hdpi" : "", + "xhdpi" : "", + "xxhdpi" : "", + "xxxhdpi" : "" }, "ios" : { - "appstore" : "unpackage/res/icons/1024x1024.png", + "appstore" : "", "ipad" : { "app" : "unpackage/res/icons/76x76.png", "app@2x" : "unpackage/res/icons/152x152.png", @@ -144,14 +144,14 @@ "spotlight@2x" : "unpackage/res/icons/80x80.png" }, "iphone" : { - "app@2x" : "unpackage/res/icons/120x120.png", - "app@3x" : "unpackage/res/icons/180x180.png", - "notification@2x" : "unpackage/res/icons/40x40.png", + "app@2x" : "", + "app@3x" : "", + "notification@2x" : "", "notification@3x" : "unpackage/res/icons/60x60.png", - "settings@2x" : "unpackage/res/icons/58x58.png", - "settings@3x" : "unpackage/res/icons/87x87.png", - "spotlight@2x" : "unpackage/res/icons/80x80.png", - "spotlight@3x" : "unpackage/res/icons/120x120.png" + "settings@2x" : "", + "settings@3x" : "", + "spotlight@2x" : "", + "spotlight@3x" : "" } } } @@ -214,11 +214,11 @@ }, "h5" : { "devServer" : { - "https" : false + "https" : true }, "router" : { "mode" : "history", - "base" : "/h5/" + "base" : "/" }, "domain" : "", "sdkConfigs" : { @@ -251,7 +251,8 @@ "statusbar" : { "immersed" : true } - } + }, + "fallbackLocale" : "zh-Hans" } /* "lazyCodeLoading" : "requiredComponents", */// "optimization" : { // "subPackages" : true diff --git a/pro_v3.5.1/view/uniapp/static/images/support.png b/pro_v3.5.1/view/uniapp/static/images/support.png new file mode 100644 index 0000000000000000000000000000000000000000..9a03fed197f570f8904bba29545f3fc5f1d76796 GIT binary patch literal 14209 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