3个文件已删除
11个文件已添加
18个文件已修改
1 文件已重命名
| .. | .. |
|---|
| 2 | 2 | node_modules |
|---|
| 3 | 3 | /dist |
|---|
| 4 | 4 | |
|---|
| 5 | | -# local env files |
|---|
| 5 | +# 本地环境文件 |
|---|
| 6 | 6 | .env.local |
|---|
| 7 | 7 | .env.*.local |
|---|
| 8 | 8 | |
|---|
| .. | .. |
|---|
| 21 | 21 | *.sw? |
|---|
| 22 | 22 | build.sh |
|---|
| 23 | 23 | .idea |
|---|
| 24 | | -unpackage |
|---|
| 24 | +unpackage |
|---|
| 25 | +/.hbuilderx |
|---|
| 26 | +# 根目录 project.config.json 不参与 mp-weixin 编译(小程序 appid 由 scripts/publish.js 写入产物) |
|---|
| 27 | +project.config.json |
|---|
| 28 | +/manifest.json |
|---|
deleted file mode 100644| .. | .. |
|---|
| 1 | | -{ // launch.json 配置了启动调试时相关设置,configurations下节点名称可为 app-plus/h5/mp-weixin/mp-baidu/mp-alipay/mp-qq/mp-toutiao/mp-360/ |
|---|
| 2 | | - // launchtype项可配置值为local或remote, local代表前端连本地云函数,remote代表前端连云端云函数 |
|---|
| 3 | | - "version": "0.0", |
|---|
| 4 | | - "configurations": [{ |
|---|
| 5 | | - "app-plus" : |
|---|
| 6 | | - { |
|---|
| 7 | | - "launchtype" : "remote" |
|---|
| 8 | | - }, |
|---|
| 9 | | - "default" : |
|---|
| 10 | | - { |
|---|
| 11 | | - "launchtype" : "remote" |
|---|
| 12 | | - }, |
|---|
| 13 | | - "h5" : |
|---|
| 14 | | - { |
|---|
| 15 | | - "launchtype" : "remote" |
|---|
| 16 | | - }, |
|---|
| 17 | | - "mp-weixin" : |
|---|
| 18 | | - { |
|---|
| 19 | | - "launchtype" : "remote" |
|---|
| 20 | | - }, |
|---|
| 21 | | - "type" : "uniCloud" |
|---|
| 22 | | - } |
|---|
| 23 | | - ] |
|---|
| 24 | | -} |
|---|
| .. | .. |
|---|
| 1 | +# 4 个 bat 使用说明 |
|---|
| 2 | + |
|---|
| 3 | +项目根目录下 4 个双击即用的批处理,按用途分两类: |
|---|
| 4 | + |
|---|
| 5 | +| 我想做的事 | 用哪个 bat | |
|---|
| 6 | +| --- | --- | |
|---|
| 7 | +| 出小程序发行版,并自动在微信开发者工具里打开 | `publish-sit.bat`(测试)/ `publish-prod.bat`(生产) | |
|---|
| 8 | +| 在 HBuilderX 里「运行 → 运行到小程序模拟器」调试 | 先切环境:`switch-sit.bat` / `switch-prod.bat` | |
|---|
| 9 | + |
|---|
| 10 | +> 一句话区分:`publish-*` 产出 `unpackage/dist/build/mp-weixin` 发行产物,编译期 appid 由脚本注入,**不改仓库里的任何文件**;`switch-*` 改的是 HBuilderX 的编译输入 `manifest.json`,供 GUI 编译/调试用。 |
|---|
| 11 | + |
|---|
| 12 | +--- |
|---|
| 13 | + |
|---|
| 14 | +## 1. 一次性前提 |
|---|
| 15 | + |
|---|
| 16 | +1. Node.js(命令行 `node -v` 可用) |
|---|
| 17 | +2. HBuilderX(提供命令行 `cli.exe`) |
|---|
| 18 | +3. 微信开发者工具,且 **设置 → 安全设置 → 服务端口 = 开启**(不开时 `cli.bat open` 会失败,脚本会降级提示你手动导入) |
|---|
| 19 | + |
|---|
| 20 | +**工具路径不需要配置**,脚本按顺序自动查找,命中后把所在目录加入本次进程的 `PATH`(便于按名字直接调用): |
|---|
| 21 | + |
|---|
| 22 | +1. 环境变量 `HBUILDERX_CLI` / `WX_DEVTOOLS_CLI` |
|---|
| 23 | +2. 系统 `PATH` |
|---|
| 24 | +3. 常见安装目录(`Program Files`、`Program Files (x86)`、用户目录、桌面、下载、各磁盘常见软件目录…) |
|---|
| 25 | +4. 磁盘浅层扫描(≤2 层,只扫本地固定磁盘,跳过系统目录) |
|---|
| 26 | + |
|---|
| 27 | +找不到时直接报错并给出办法: |
|---|
| 28 | + |
|---|
| 29 | +```bat |
|---|
| 30 | +set PATH=%PATH%;<HBuilderX 或 微信开发者工具 的安装目录> |
|---|
| 31 | +set HBUILDERX_CLI=<HBuilderX cli.exe 完整路径> |
|---|
| 32 | +set WX_DEVTOOLS_CLI=<微信开发者工具 cli.bat 完整路径> |
|---|
| 33 | +``` |
|---|
| 34 | + |
|---|
| 35 | +--- |
|---|
| 36 | + |
|---|
| 37 | +## 2. publish-sit.bat / publish-prod.bat |
|---|
| 38 | + |
|---|
| 39 | +双击 = 发行并自动打开。实际执行 4 步: |
|---|
| 40 | + |
|---|
| 41 | +| 步骤 | 做什么 | 具体命令 | |
|---|
| 42 | +| --- | --- | --- | |
|---|
| 43 | +| 0 | 生成 `manifest.json`(模板 `manifest.json.demo` + 该环境 appid) | 内置逻辑 | |
|---|
| 44 | +| 1 | HBuilderX CLI 发行编译 | `cli.exe publish --platform mp-weixin --project <项目根>` | |
|---|
| 45 | +| 2 | 回写产物 appid(兜底校正) | 写入 `unpackage/dist/build/mp-weixin/project.config.json` | |
|---|
| 46 | +| 3 | 微信开发者工具 CLI 打开产物 | `cli.bat open --project <产物目录>` | |
|---|
| 47 | + |
|---|
| 48 | +- 发行产物:`unpackage/dist/build/mp-weixin` |
|---|
| 49 | +- 编译日志在 **HBuilderX 控制台**里看 |
|---|
| 50 | +- 任一步失败,窗口会打印 `[FAILED] ...`(退出码非 0) |
|---|
| 51 | + |
|---|
| 52 | +### 可加的参数(`%*` 原样传给 `scripts/publish.js`) |
|---|
| 53 | + |
|---|
| 54 | +| 参数 | 作用 | |
|---|
| 55 | +| --- | --- | |
|---|
| 56 | +| `--dry-run` | 只打印将要执行的命令,不做任何写操作 | |
|---|
| 57 | +| `--no-open` | 只编译,不打开微信开发者工具(此种情况不要求安装开发者工具) | |
|---|
| 58 | +| `--no-gen` | 跳过生成 `manifest.json`(要求它已存在;适合你手工维护 manifest 的场景) | |
|---|
| 59 | +| 其他参数 | 原样透传给 HBuilderX cli | |
|---|
| 60 | + |
|---|
| 61 | +用法示例(在命令行窗口里): |
|---|
| 62 | + |
|---|
| 63 | +```bat |
|---|
| 64 | +publish-sit.bat --dry-run |
|---|
| 65 | +publish-prod.bat --no-open |
|---|
| 66 | +``` |
|---|
| 67 | + |
|---|
| 68 | +等价命令:`npm run release:sit` / `npm run release:prod` / `node scripts/publish.js sit` |
|---|
| 69 | + |
|---|
| 70 | +--- |
|---|
| 71 | + |
|---|
| 72 | +## 3. switch-sit.bat / switch-prod.bat |
|---|
| 73 | + |
|---|
| 74 | +双击 = 按该环境重新生成 `manifest.json`(`manifest.json.demo` + 该环境 appid)。 |
|---|
| 75 | + |
|---|
| 76 | +- 等价命令:`npm run env:sit` / `npm run env:prod` / `node scripts/set-env.js sit` |
|---|
| 77 | +- **切完必须做两件事**:① 在 HBuilderX 里重新编译一次;② 在微信开发者工具里重新打开该项目(appid 变更需重开) |
|---|
| 78 | +- 幂等:内容已一致时提示「无需重写」,可放心重复双击 |
|---|
| 79 | +- 适用场景:在 HBuilderX GUI 里直接运行/调试(编译期 appid 取自 `manifest.json`),或需要出 App 包(顶层 DCloud appid) |
|---|
| 80 | + |
|---|
| 81 | +### 两个环境的取值 |
|---|
| 82 | + |
|---|
| 83 | +| 环境 | 小程序 appid(`mp-weixin.appid`) | DCloud appid(顶层) | 接口地址 | |
|---|
| 84 | +| --- | --- | --- | --- | |
|---|
| 85 | +| sit | `wx7e443c60d08449de` | `__UNI__3D0C5FE` | `https://sit.tohours.com/crmeb/front` | |
|---|
| 86 | +| prod | `wx15d029cdf2e92a9f` | `__UNI__CE0C605` | `https://acmall.bluedeer.com.cn/crmeb/front` | |
|---|
| 87 | + |
|---|
| 88 | +数据源:`config/env/sit.js`、`config/env/prod.js`(唯一数据源,改这两处即可,两个文件键名保持一致)。 |
|---|
| 89 | + |
|---|
| 90 | +--- |
|---|
| 91 | + |
|---|
| 92 | +## 4. 为什么跑完 git 是干净的 |
|---|
| 93 | + |
|---|
| 94 | +`manifest.json` 是**生成物**,`project.config.json` 与 `unpackage/` 已忽略: |
|---|
| 95 | + |
|---|
| 96 | +``` |
|---|
| 97 | +manifest.json.demo(入库,模板) ──注入环境 appid──▶ manifest.json(已忽略,不入库) |
|---|
| 98 | +``` |
|---|
| 99 | + |
|---|
| 100 | +所以发行、切环境都不会产生 git 改动,`git status` 只会有你自己改的源码。 |
|---|
| 101 | + |
|---|
| 102 | +两点注意: |
|---|
| 103 | + |
|---|
| 104 | +1. 新 clone 的机器**没有 `manifest.json`,HBuilderX 打不开项目** —— 先跑一次 `switch-sit.bat`(或任意 `publish-*.bat`)把它生成出来。 |
|---|
| 105 | +2. 在 HBuilderX GUI 里直接改的 manifest 配置落在**生成物**上,下次生成会被模板内容覆盖(脚本会打印警告)。需要长期保留的改动,请写进 `manifest.json.demo`。 |
|---|
| 106 | + |
|---|
| 107 | +--- |
|---|
| 108 | + |
|---|
| 109 | +## 5. 接口地址为什么自动对得上 |
|---|
| 110 | + |
|---|
| 111 | +`config/env/index.js` 的识别顺序:手动 `OVERRIDE_ENV` > 小程序 appid 自动识别 > H5 域名识别 > 默认 prod,对外导出 `ENV_NAME` / `IS_SIT` / `IS_PROD`。 |
|---|
| 112 | + |
|---|
| 113 | +也就是「用哪个 appid 编译」就等于「用哪套接口」,不会出现接口与环境错配 —— 你只需要选对 bat。 |
|---|
| 114 | + |
|---|
| 115 | +--- |
|---|
| 116 | + |
|---|
| 117 | +## 6. 常见问题 |
|---|
| 118 | + |
|---|
| 119 | +| 现象 | 处理 | |
|---|
| 120 | +| --- | --- | |
|---|
| 121 | +| 窗口报 `'…' is not recognized as an internal or external command` 且带中文乱码 | bat 内混用 UTF-8 中文时,cmd 按代码页解析会错位。现在 4 个 bat **全部是 ASCII**,中文提示由 Node 打印。自己改 bat 时不要加中文,要加就用 Node 输出 | |
|---|
| 122 | +| `未找到 HBuilderX 命令行 cli.exe` / `未找到微信开发者工具命令行 cli.bat` | 按报错里的两种办法处理:加 `PATH` 或设对应环境变量(见第 1 节) | |
|---|
| 123 | +| 自动打开失败,提示未开启服务端口 | 开发者工具 → 设置 → 安全设置 → 打开服务端口;或手动「导入项目」选 `unpackage\dist\build\mp-weixin` | |
|---|
| 124 | +| 开发者工具里 appid 还是旧的 | appid 变更后需要在工具里**重新打开**该项目 | |
|---|
| 125 | +| `--no-gen 需要 manifest.json 已存在` | 先跑一次 `switch-*.bat`,或去掉 `--no-gen` | |
|---|
| 126 | +| `cli.exe 退出码 …` | 编译失败,看 HBuilderX 控制台里的具体报错 | |
|---|
| 127 | + |
|---|
| 128 | +--- |
|---|
| 129 | + |
|---|
| 130 | +## 7. 附注 |
|---|
| 131 | + |
|---|
| 132 | +- 4 个 bat 都会先 `cd /d "%~dp0"`,所以在任意目录双击或命令行调用都不会跑错位置;末尾 `pause` 方便双击后查看输出。 |
|---|
| 133 | +- 微信开发者工具生成的 `project.private.config.json` 目前仍入库(未被忽略)。若想连它一起做到零噪音,可自行加入 `.gitignore` 并执行 `git rm --cached project.private.config.json`。 |
|---|
| .. | .. |
|---|
| 409 | 409 | .box { |
|---|
| 410 | 410 | width: 600rpx; |
|---|
| 411 | 411 | padding: 90rpx 0 70rpx 0; |
|---|
| 412 | | - background: url("https://ossacmall.bluedeer.com.cn/app/static/images/confirm_bg.png") center 0rpx/100% auto no-repeat; |
|---|
| 412 | + background: url("https://ossacmall.bluedeer.com.cn/app/static/images/confirm_bg.png") center 0rpx / 100% auto no-repeat; |
|---|
| 413 | 413 | background-color: #f5f5f5; |
|---|
| 414 | 414 | position: absolute; |
|---|
| 415 | 415 | left: 50%; |
|---|
| .. | .. |
|---|
| 55 | 55 | .box { |
|---|
| 56 | 56 | width: 600rpx; |
|---|
| 57 | 57 | padding: 90rpx 0 70rpx 0; |
|---|
| 58 | | - background: url("https://ossacmall.bluedeer.com.cn/app/static/images/confirm_bg.png") center -50rpx/100% auto no-repeat; |
|---|
| 58 | + background: url("https://ossacmall.bluedeer.com.cn/app/static/images/confirm_bg.png") center -50rpx / 100% auto no-repeat; |
|---|
| 59 | 59 | background-color: #f5f5f5; |
|---|
| 60 | 60 | position: absolute; |
|---|
| 61 | 61 | left: 50%; |
|---|
| .. | .. |
|---|
| 138 | 138 | .box { |
|---|
| 139 | 139 | width: 600rpx; |
|---|
| 140 | 140 | padding: 90rpx 0 70rpx 0; |
|---|
| 141 | | - background: url("https://ossacmall.bluedeer.com.cn/app/static/images/confirm_bg.png") center 0rpx/100% auto no-repeat; |
|---|
| 141 | + background: url("https://ossacmall.bluedeer.com.cn/app/static/images/confirm_bg.png") center 0rpx / 100% auto no-repeat; |
|---|
| 142 | 142 | background-color: #f5f5f5; |
|---|
| 143 | 143 | position: absolute; |
|---|
| 144 | 144 | left: 50%; |
|---|
| .. | .. |
|---|
| 322 | 322 | .box { |
|---|
| 323 | 323 | width: 600rpx; |
|---|
| 324 | 324 | padding: 90rpx 0 70rpx 0; |
|---|
| 325 | | - background: url("https://ossacmall.bluedeer.com.cn/app/static/images/confirm_bg.png") center -120rpx/100% auto no-repeat; |
|---|
| 325 | + background: url("https://ossacmall.bluedeer.com.cn/app/static/images/confirm_bg.png") center -120rpx / 100% auto no-repeat; |
|---|
| 326 | 326 | background-color: #f5f5f5; |
|---|
| 327 | 327 | position: absolute; |
|---|
| 328 | 328 | left: 50%; |
|---|
| .. | .. |
|---|
| 131 | 131 | |
|---|
| 132 | 132 | .box { |
|---|
| 133 | 133 | padding: 90rpx 0 70rpx 0; |
|---|
| 134 | | - background: url("https://ossacmall.bluedeer.com.cn/app/static/images/confirm_bg.png") center 320rpx/100% auto no-repeat; |
|---|
| 134 | + background: url("https://ossacmall.bluedeer.com.cn/app/static/images/confirm_bg.png") center 320rpx / 100% auto no-repeat; |
|---|
| 135 | 135 | background-color: #f5f5f5; |
|---|
| 136 | 136 | position: relative; |
|---|
| 137 | 137 | border-radius: 14rpx; |
|---|
| .. | .. |
|---|
| 186 | 186 | path: url, |
|---|
| 187 | 187 | envVersion: miniProgramInfo.envVersion == 'release' ? 'release' : 'trial', |
|---|
| 188 | 188 | }) |
|---|
| 189 | | - |
|---|
| 190 | | - // let params = { |
|---|
| 191 | | - // custAgentNo: that.$store.getters.belong?.agentNo || '', |
|---|
| 192 | | - // custAgentChnl: that.$store.getters.belong?.channel || '', |
|---|
| 193 | | - // custAgentCoy: that.$store.getters.belong?.companyCode || '', |
|---|
| 194 | | - // custAgentBranch: that.$store.getters.belong?.branchCode || '', |
|---|
| 195 | | - // custAgentOrgNo: that.$store.getters.belong?.subCode || '' |
|---|
| 196 | | - // }; |
|---|
| 197 | | - // let webUrl = 'https://ossacmall.bluedeer.com.cn/qr/index.html?isMall=true' + (miniProgramInfo.envVersion == 'release' ? '' : '&isTrial=true') + '¶ms=' + encodeURIComponent(JSON.stringify(params)) + '&pageUrl=' + encodeURIComponent(url); |
|---|
| 198 | | - // if (HTTP_REQUEST_URL.indexOf('tohours') > 0) { |
|---|
| 199 | | - // webUrl = webUrl.replace('https://ossacmall.bluedeer.com.cn/qr', 'https://sit.tohours.com/crmeb_web/qr/') |
|---|
| 200 | | - // } |
|---|
| 201 | | - // uni.navigateTo({ url: '/pages/users/web_page/index?webUel=' + encodeURIComponent(webUrl) }) |
|---|
| 202 | 189 | } |
|---|
| 203 | 190 | if (!that.$store.getters.isLogin) { |
|---|
| 204 | 191 | toLogin(that); |
|---|
| .. | .. |
|---|
| 9 | 9 | </view> |
|---|
| 10 | 10 | </view> |
|---|
| 11 | 11 | <view class="points-right"> |
|---|
| 12 | | - <image src="https://ossacmall.bluedeer.com.cn/app/static/images/yueyang_jifen.png" class="lottery-btn" mode="widthFix" |
|---|
| 13 | | - @click="goLottery"></image> |
|---|
| 14 | | - </view> |
|---|
| 15 | | - <view class="detail-btn" @click="goDetail"> |
|---|
| 16 | | - <text>消耗明细</text> |
|---|
| 17 | | - </view> |
|---|
| 18 | | - </view> |
|---|
| 12 | + <image src="https://ossacmall.bluedeer.com.cn/app/static/images/yueyang_jifen.png" class="lottery-btn" mode="widthFix" |
|---|
| 13 | + @click="goLottery"></image> |
|---|
| 14 | + </view> |
|---|
| 15 | + </view> |
|---|
| 19 | 16 | |
|---|
| 20 | 17 | <!-- 优惠券兑换区 --> |
|---|
| 21 | 18 | <view class="coupon-area"> |
|---|
| .. | .. |
|---|
| 133 | 130 | }, |
|---|
| 134 | 131 | onShow() {}, |
|---|
| 135 | 132 | methods: { |
|---|
| 136 | | - goDetail() { |
|---|
| 137 | | - uni.navigateTo({ |
|---|
| 138 | | - url: '/pages/consumption/index?zoneId=' + this.zoneInfo.id |
|---|
| 139 | | - }) |
|---|
| 140 | | - }, |
|---|
| 141 | 133 | goLottery() { |
|---|
| 142 | 134 | this.$emit('goLottery') |
|---|
| 143 | 135 | }, |
|---|
| .. | .. |
|---|
| 322 | 314 | height: 57rpx; |
|---|
| 323 | 315 | } |
|---|
| 324 | 316 | |
|---|
| 325 | | - .detail-btn { |
|---|
| 326 | | - border: 2rpx solid #fff; |
|---|
| 327 | | - /* border-radius: 10rpx; */ |
|---|
| 328 | | - border-top-left-radius: 20rpx; |
|---|
| 329 | | - border-bottom-left-radius: 20rpx; |
|---|
| 330 | | - padding: 10rpx; |
|---|
| 331 | | - background-color: #daf2b2; |
|---|
| 332 | | - color: #1e984f; |
|---|
| 333 | | - width: 50rpx; |
|---|
| 334 | | - position: fixed; |
|---|
| 335 | | - top: 200rpx; |
|---|
| 336 | | - right: 0rpx; |
|---|
| 337 | | - line-height: 1rem; |
|---|
| 338 | | - } |
|---|
| 317 | + |
|---|
| 339 | 318 | |
|---|
| 340 | 319 | .coupon-area { |
|---|
| 341 | 320 | display: flex; |
|---|
| .. | .. |
|---|
| 7 | 7 | // +---------------------------------------------------------------------- |
|---|
| 8 | 8 | // | Author: CRMEB Team <admin@crmeb.com> |
|---|
| 9 | 9 | // +---------------------------------------------------------------------- |
|---|
| 10 | | -//移动端商城API |
|---|
| 11 | | -let domain = 'https://acmall.bluedeer.com.cn/crmeb/front' |
|---|
| 12 | 10 | |
|---|
| 13 | | -module.exports = { |
|---|
| 14 | | - // 请求域名 格式: https://您的域名 |
|---|
| 15 | | - HTTP_REQUEST_URL:domain, |
|---|
| 16 | | - |
|---|
| 17 | | - // #ifdef MP |
|---|
| 18 | | - HTTP_REQUEST_URL: domain, |
|---|
| 19 | | - // #endif |
|---|
| 20 | | - HTTP_REQUEST_URL:'https://acmall.bluedeer.com.cn/crmeb/front', |
|---|
| 21 | | - HTTP_ADMIN_URL:'https://acmall.bluedeer.com.cn/crmeb/admin', |
|---|
| 22 | | - APPID:'wx15d029cdf2e92a9f', |
|---|
| 23 | | - GDP_ACCOUNT_ID:'8672f93377575ace', |
|---|
| 24 | | - GDP_DATASOURCE_ID:'b01182b2d4f73e45', |
|---|
| 25 | | - GDP_SERVER_URL:'https://giocollect.aviva-cofco.com.cn', |
|---|
| 26 | | - GDP_DEBUG:true, |
|---|
| 27 | | - THIRD_APPID:'wxa83daa550324ead8', |
|---|
| 28 | | - // #ifdef H5 |
|---|
| 29 | | - //H5接口是浏览器地址 |
|---|
| 30 | | - // HTTP_REQUEST_URL: window.location.protocol+"//"+window.location.host, |
|---|
| 31 | | - // http://api.java.crmeb.net:20001 |
|---|
| 32 | | - HTTP_REQUEST_URL:domain, |
|---|
| 33 | | - // #endif |
|---|
| 34 | | - |
|---|
| 11 | +// 环境相关参数(HTTP_REQUEST_URL/HTTP_ADMIN_URL/APPID/THIRD_APPID/GDP_* 等) |
|---|
| 12 | +// 统一维护在 config/env/ 下,本文件只做合并,对外导出名保持不变 |
|---|
| 13 | +const env = require('./env'); |
|---|
| 14 | + |
|---|
| 15 | +module.exports = Object.assign({}, env, { |
|---|
| 35 | 16 | HEADER:{ |
|---|
| 36 | 17 | 'content-type': 'application/json' |
|---|
| 37 | 18 | }, |
|---|
| .. | .. |
|---|
| 44 | 25 | EXPIRE:0, |
|---|
| 45 | 26 | //分页最多显示条数 |
|---|
| 46 | 27 | LIMIT: 10 |
|---|
| 47 | | -}; |
|---|
| 28 | +}); |
|---|
| .. | .. |
|---|
| 1 | +// +---------------------------------------------------------------------- |
|---|
| 2 | +// | 环境选择入口:一套代码跑多环境 |
|---|
| 3 | +// | 识别优先级:手动指定 > 小程序 appid 自动识别 > H5 域名识别 > 默认 prod |
|---|
| 4 | +// | 说明:小程序 appid 由 manifest.json -> mp-weixin.appid 决定, |
|---|
| 5 | +// | 所以“用哪个 appid 跑”就等于“用哪套接口”,不会出现接口与环境错配 |
|---|
| 6 | +// +---------------------------------------------------------------------- |
|---|
| 7 | +const envs = { |
|---|
| 8 | + prod: require('./prod.js'), |
|---|
| 9 | + sit: require('./sit.js') |
|---|
| 10 | +}; |
|---|
| 11 | + |
|---|
| 12 | +// 手动强制指定环境:'' = 自动识别(推荐);也可填 'prod' 或 'sit' |
|---|
| 13 | +const OVERRIDE_ENV = ''; |
|---|
| 14 | + |
|---|
| 15 | +// appid -> 环境名 映射 |
|---|
| 16 | +const APPID_ENV_MAP = {}; |
|---|
| 17 | +Object.keys(envs).forEach(function(key) { |
|---|
| 18 | + APPID_ENV_MAP[envs[key].APPID] = key; |
|---|
| 19 | +}); |
|---|
| 20 | + |
|---|
| 21 | +function detectEnv() { |
|---|
| 22 | + if (OVERRIDE_ENV && envs[OVERRIDE_ENV]) return OVERRIDE_ENV; |
|---|
| 23 | + |
|---|
| 24 | + // #ifdef MP |
|---|
| 25 | + try { |
|---|
| 26 | + const info = uni.getAccountInfoSync(); |
|---|
| 27 | + const appId = info && info.miniProgram && info.miniProgram.appId; |
|---|
| 28 | + if (appId && APPID_ENV_MAP[appId]) return APPID_ENV_MAP[appId]; |
|---|
| 29 | + } catch (e) {} |
|---|
| 30 | + // #endif |
|---|
| 31 | + |
|---|
| 32 | + // #ifdef H5 |
|---|
| 33 | + if (typeof location !== 'undefined' && location.hostname) { |
|---|
| 34 | + const host = location.hostname; |
|---|
| 35 | + if (/sit|tohours|localhost|127\.0\.0\.1/.test(host)) return 'sit'; |
|---|
| 36 | + } |
|---|
| 37 | + // #endif |
|---|
| 38 | + |
|---|
| 39 | + return 'prod'; |
|---|
| 40 | +} |
|---|
| 41 | + |
|---|
| 42 | +const ENV_NAME = detectEnv(); |
|---|
| 43 | +const current = envs[ENV_NAME] || envs.prod; |
|---|
| 44 | + |
|---|
| 45 | +module.exports = Object.assign({}, current, { |
|---|
| 46 | + ENV_NAME: ENV_NAME, |
|---|
| 47 | + IS_PROD: ENV_NAME === 'prod', |
|---|
| 48 | + IS_SIT: ENV_NAME === 'sit' |
|---|
| 49 | +}); |
|---|
| .. | .. |
|---|
| 1 | +// +---------------------------------------------------------------------- |
|---|
| 2 | +// | 生产环境(PROD)参数 |
|---|
| 3 | +// | 注意:新增环境参数时,请同步维护 prod.js 与 sit.js,保持键名完全一致 |
|---|
| 4 | +// +---------------------------------------------------------------------- |
|---|
| 5 | +module.exports = { |
|---|
| 6 | + ENV_NAME: 'prod', |
|---|
| 7 | + // 请求域名 |
|---|
| 8 | + HTTP_REQUEST_URL: 'https://acmall.bluedeer.com.cn/crmeb/front', |
|---|
| 9 | + HTTP_ADMIN_URL: 'https://acmall.bluedeer.com.cn/crmeb/admin', |
|---|
| 10 | + // 微信小程序 appid(需与 manifest.json -> mp-weixin.appid 保持一致) |
|---|
| 11 | + APPID: 'wx15d029cdf2e92a9f', |
|---|
| 12 | + // 跳转关联小程序 appid |
|---|
| 13 | + THIRD_APPID: 'wxa83daa550324ead8', |
|---|
| 14 | + // GrowingIO 埋点 |
|---|
| 15 | + GDP_ACCOUNT_ID: '8672f93377575ace', |
|---|
| 16 | + GDP_DATASOURCE_ID: 'b01182b2d4f73e45', |
|---|
| 17 | + GDP_SERVER_URL: 'https://giocollect.aviva-cofco.com.cn', |
|---|
| 18 | + GDP_DEBUG: true, |
|---|
| 19 | + // 活动页/内嵌 web 资源地址(替代代码里按域名判断环境) |
|---|
| 20 | + WEB_ACTIVITY_BASE: 'https://ossacmall.bluedeer.com.cn', |
|---|
| 21 | + // DCloud 应用标识(需与 manifest.json -> appid 保持一致) |
|---|
| 22 | + UNI_APPID: '__UNI__CE0C605' |
|---|
| 23 | +}; |
|---|
| .. | .. |
|---|
| 1 | +// +---------------------------------------------------------------------- |
|---|
| 2 | +// | 测试环境(SIT)参数 |
|---|
| 3 | +// | 注意:新增环境参数时,请同步维护 prod.js 与 sit.js,保持键名完全一致 |
|---|
| 4 | +// +---------------------------------------------------------------------- |
|---|
| 5 | +module.exports = { |
|---|
| 6 | + ENV_NAME: 'sit', |
|---|
| 7 | + // 请求域名 |
|---|
| 8 | + HTTP_REQUEST_URL: 'https://sit.tohours.com/crmeb/front', |
|---|
| 9 | + HTTP_ADMIN_URL: 'https://sit.tohours.com/crmeb/admin', |
|---|
| 10 | + // 微信小程序 appid(需与 manifest.json -> mp-weixin.appid 保持一致) |
|---|
| 11 | + APPID: 'wx7e443c60d08449de', |
|---|
| 12 | + // 跳转关联小程序 appid |
|---|
| 13 | + THIRD_APPID: 'wx64d2ecbbeb3eae79', |
|---|
| 14 | + // GrowingIO 埋点 |
|---|
| 15 | + GDP_ACCOUNT_ID: '83b2522b4d9c972a', |
|---|
| 16 | + GDP_DATASOURCE_ID: 'a1616a9a748d2c7f', |
|---|
| 17 | + GDP_SERVER_URL: 'https://collecttest.aviva-cofco.com.cn', |
|---|
| 18 | + GDP_DEBUG: true, |
|---|
| 19 | + // 活动页/内嵌 web 资源地址(替代代码里按域名判断环境) |
|---|
| 20 | + WEB_ACTIVITY_BASE: 'https://sit.tohours.com/crmeb_web', |
|---|
| 21 | + // DCloud 应用标识(需与 manifest.json -> appid 保持一致) |
|---|
| 22 | + UNI_APPID: '__UNI__3D0C5FE' |
|---|
| 23 | +}; |
|---|
| .. | .. |
|---|
| 87 | 87 | uni.login({ |
|---|
| 88 | 88 | provider: provider, |
|---|
| 89 | 89 | success(res) { |
|---|
| 90 | + console.log(uni.login,res); |
|---|
| 90 | 91 | if (res.code) Cache.set(STATE_R_KEY, res.code, 10800); |
|---|
| 91 | 92 | return resolve(res.code); |
|---|
| 92 | 93 | }, |
|---|
similarity index 98%rename from manifest.jsonrename to manifest.json.demo| .. | .. |
|---|
| 1 | 1 | { |
|---|
| 2 | 2 | "name" : "CRMEB Java 商城", |
|---|
| 3 | | - "appid" : "__UNI__CE0C605", |
|---|
| 3 | + "appid" : "__UNI__3D0C5FE", |
|---|
| 4 | 4 | "description" : "CRMEB Java 商城", |
|---|
| 5 | 5 | "versionName" : "1.0.0", |
|---|
| 6 | 6 | "versionCode" : 1001, |
|---|
| .. | .. |
|---|
| 123 | 123 | "quickapp" : {}, |
|---|
| 124 | 124 | /* 小程序特有相关 */ |
|---|
| 125 | 125 | "mp-weixin" : { |
|---|
| 126 | | - "appid" : "wx15d029cdf2e92a9f", |
|---|
| 126 | + "appid" : null, |
|---|
| 127 | 127 | "setting" : { |
|---|
| 128 | 128 | "urlCheck" : true, |
|---|
| 129 | 129 | "minified" : true, |
|---|
| .. | .. |
|---|
| 1 | 1 | { |
|---|
| 2 | + "scripts": { |
|---|
| 3 | + "env:prod": "node scripts/set-env.js prod", |
|---|
| 4 | + "env:sit": "node scripts/set-env.js sit", |
|---|
| 5 | + "release:sit": "node scripts/publish.js sit", |
|---|
| 6 | + "release:prod": "node scripts/publish.js prod" |
|---|
| 7 | + }, |
|---|
| 2 | 8 | "dependencies": { |
|---|
| 3 | 9 | "bignumber.js": "^9.3.1", |
|---|
| 4 | 10 | "vue-inset-loader": "^1.2.6" |
|---|
| .. | .. |
|---|
| 13 | 13 | <scroll-view scroll-y="true" scroll-with-animation='true' |
|---|
| 14 | 14 | :style="{height: (height-280/750*windowWidth) + 'px'}" :scroll-into-view="'menu_item'+navActive"> |
|---|
| 15 | 15 | <view class='item acea-row row-center-wrapper menu_item' :id="'menu_item'+index" |
|---|
| 16 | | - :class='index==navActive?"on":""' v-for="(item,index) in categoryList" :key="item._key" |
|---|
| 16 | + :class='index==navActive?"on":""' v-for="(item,index) in categoryList" :key="index" |
|---|
| 17 | 17 | @click='tap(index,"b"+index)'> |
|---|
| 18 | 18 | <image v-if="item.extra" class="img" :src="item.extra" mode="aspectFill"></image> |
|---|
| 19 | 19 | <view class="text">{{item.name}}</view> |
|---|
| .. | .. |
|---|
| 25 | 25 | <scroll-view :scroll-into-view="'subMenu_item'+subActive" scroll-x="true" scroll-with-animation='true' |
|---|
| 26 | 26 | style="width: 100%;"> |
|---|
| 27 | 27 | <view class="subMenu_wrapper"> |
|---|
| 28 | | - <block v-for="(itemn,indexn) in categoryList[navActive].child" :key="itemn._key"><text |
|---|
| 28 | + <block v-for="(itemn,indexn) in categoryList[navActive].child" :key="indexn"><text |
|---|
| 29 | 29 | class="subMenu_item" :id="'subMenu_item'+indexn" :class='indexn==subActive?"on":""' |
|---|
| 30 | 30 | @click='tapSub(indexn)'>{{itemn.name}}</text></block> |
|---|
| 31 | 31 | </view> |
|---|
| .. | .. |
|---|
| 35 | 35 | :style="{top: (categoryList[navActive].show2Category==0?0:118) + 'rpx',height: (height-(categoryList[navActive].show2Category==0?0:(122/750*windowWidth))) + 'px'}"> |
|---|
| 36 | 36 | <scroll-view :scroll-top="scrollTop" class="goodlistScroll" scroll-y="true" style="height:100%;" |
|---|
| 37 | 37 | :scroll-into-view="scrollIntoView" @scroll="handleScroll" :throttle="false"> |
|---|
| 38 | | - <block v-for="(aitem,aindex) in categoryList" :key="aitem._key"> |
|---|
| 39 | | - <view class="list" v-if="aitem.child.length>0" v-for="(sitem,sindex) in aitem.child" :key="sitem._key" |
|---|
| 38 | + <block v-for="(aitem,aindex) in categoryList" :key="aindex"> |
|---|
| 39 | + <view class="list" v-if="aitem.child.length>0" v-for="sitem,sindex in aitem.child" :key="sindex" |
|---|
| 40 | 40 | :style="(aindex==categoryList.length-1&&sindex==aitem.child.length-1)?('min-height:'+(height-370/750*windowWidth)+'px'):''" |
|---|
| 41 | 41 | :id="'list'+aindex+'_'+sindex"> |
|---|
| 42 | 42 | <view class="list_title">{{sitem.name}}</view> |
|---|
| 43 | 43 | <view class="list_wrapper cf"> |
|---|
| 44 | 44 | <view class="list_column cfl"> |
|---|
| 45 | 45 | <block style="float:left" v-if="sitem.productArr.length>0" |
|---|
| 46 | | - v-for="(gitem,gindex) in sitem.productArr" :key="gitem._key"> |
|---|
| 46 | + v-for="(gitem,gindex) in sitem.productArr" :key="gindex"> |
|---|
| 47 | 47 | <GoodBlock v-if="gindex%2==0" :size="'small'" :pageName="'分类'" |
|---|
| 48 | 48 | :moduleName="'分类商品列表'" :careBeanRate="careBeanRate" :goodsInfo="gitem"> |
|---|
| 49 | 49 | </GoodBlock> |
|---|
| .. | .. |
|---|
| 51 | 51 | </view> |
|---|
| 52 | 52 | <view class="list_column cfr"> |
|---|
| 53 | 53 | <block style="float:left" v-if="sitem.productArr.length>0" |
|---|
| 54 | | - v-for="(gitem,gindex) in sitem.productArr" :key="gitem._key"> |
|---|
| 54 | + v-for="(gitem,gindex) in sitem.productArr" :key="gindex"> |
|---|
| 55 | 55 | <GoodBlock v-if="gindex%2==1" :size="'small'" :pageName="'分类'" |
|---|
| 56 | 56 | :moduleName="'分类商品列表'" :careBeanRate="careBeanRate" :goodsInfo="gitem"> |
|---|
| 57 | 57 | </GoodBlock> |
|---|
| .. | .. |
|---|
| 66 | 66 | <view class="list_wrapper cf"> |
|---|
| 67 | 67 | <view class="list_column cfl"> |
|---|
| 68 | 68 | <block v-if="aitem.productArr.length>0" v-for="(gitem,gindex) in aitem.productArr" |
|---|
| 69 | | - :key="gitem._key"> |
|---|
| 69 | + :key="gindex"> |
|---|
| 70 | 70 | <GoodBlock v-if="gindex%2==0" :pageName="'分类'" :moduleName="'分类商品列表'" |
|---|
| 71 | 71 | :careBeanRate="careBeanRate" :goodsInfo="gitem"></GoodBlock> |
|---|
| 72 | 72 | </block> |
|---|
| 73 | 73 | </view> |
|---|
| 74 | 74 | <view class="list_column cfr"> |
|---|
| 75 | 75 | <block v-if="aitem.productArr.length>0" v-for="(gitem,gindex) in aitem.productArr" |
|---|
| 76 | | - :key="gitem._key"> |
|---|
| 76 | + :key="gindex"> |
|---|
| 77 | 77 | <GoodBlock v-if="gindex%2==1" :pageName="'分类'" :moduleName="'分类商品列表'" |
|---|
| 78 | 78 | :careBeanRate="careBeanRate" :goodsInfo="gitem"></GoodBlock> |
|---|
| 79 | 79 | </block> |
|---|
| .. | .. |
|---|
| 144 | 144 | totalGetNum: 0, |
|---|
| 145 | 145 | // 首屏优化:当前分类的待完成请求数 |
|---|
| 146 | 146 | catePending: [], |
|---|
| 147 | + // 标记各分类是否已派发过请求,避免切换分类时重复请求 |
|---|
| 148 | + cateStarted: [], |
|---|
| 147 | 149 | firstScreenReady: true, |
|---|
| 150 | + // 后台加载标记:首屏渲染并关闭loading后,才开始后台加载其余分类 |
|---|
| 151 | + bgLoaded: false, |
|---|
| 148 | 152 | // 渲染优化:非首屏分类数据缓冲队列,批量渲染减少setData次数 |
|---|
| 149 | 153 | deferredLists: [], |
|---|
| 150 | 154 | deferTimer: null, |
|---|
| .. | .. |
|---|
| 162 | 166 | let that = this; |
|---|
| 163 | 167 | that.menuTop = uni.getMenuButtonBoundingClientRect().top; |
|---|
| 164 | 168 | that.menuHeight = uni.getMenuButtonBoundingClientRect().height; |
|---|
| 169 | + // 尽早计算页面布局高度,避免首屏数据就绪后因 height 为 0 而白屏 |
|---|
| 170 | + that.infoScroll(); |
|---|
| 165 | 171 | if (that.$store.getters.careBeanRate) { |
|---|
| 166 | 172 | that.$nextTick(() => { |
|---|
| 167 | 173 | that.$set(that, 'careBeanRate', that.$store.getters.careBeanRate) |
|---|
| .. | .. |
|---|
| 176 | 182 | }); |
|---|
| 177 | 183 | }, |
|---|
| 178 | 184 | mounted() {}, |
|---|
| 185 | + onReady() { |
|---|
| 186 | + // 页面渲染完成后复核一次布局尺寸(含滚动区域 scrollHeight) |
|---|
| 187 | + this.infoScroll(); |
|---|
| 188 | + }, |
|---|
| 179 | 189 | onHide() { |
|---|
| 180 | 190 | let that = this; |
|---|
| 181 | 191 | if (that.$store.getters.isSystemHide) { |
|---|
| .. | .. |
|---|
| 339 | 349 | that.isClicking = true; |
|---|
| 340 | 350 | that.navActive = index; |
|---|
| 341 | 351 | that.subActive = 0; |
|---|
| 352 | + // loading期间切到尚未发起请求的分类时,立即补发请求,避免loading卡住 |
|---|
| 353 | + if (!that.cateStarted[index]) { |
|---|
| 354 | + that.loadCateByIndex(index); |
|---|
| 355 | + } |
|---|
| 342 | 356 | // loading期间切换分类,若目标分类已加载完成则立即关闭loading |
|---|
| 343 | 357 | that.checkFirstScreen(index); |
|---|
| 344 | 358 | that.$nextTick(() => { |
|---|
| .. | .. |
|---|
| 352 | 366 | let that = this; |
|---|
| 353 | 367 | that.isClicking = true; |
|---|
| 354 | 368 | that.subActive = index; |
|---|
| 369 | + // 当前分类尚未发起请求时补发,避免loading卡住 |
|---|
| 370 | + if (!that.cateStarted[that.navActive]) { |
|---|
| 371 | + that.loadCateByIndex(that.navActive); |
|---|
| 372 | + } |
|---|
| 355 | 373 | that.checkFirstScreen(that.navActive); |
|---|
| 356 | 374 | that.$nextTick(() => { |
|---|
| 357 | 375 | that.scrollIntoView = 'list' + that.navActive + '_' + that.subActive; |
|---|
| .. | .. |
|---|
| 391 | 409 | let that = this; |
|---|
| 392 | 410 | that.firstScreenReady = false; |
|---|
| 393 | 411 | that.allRequestsDone = false; |
|---|
| 412 | + that.bgLoaded = false; |
|---|
| 394 | 413 | that.deferredLists = []; |
|---|
| 395 | 414 | if (that.deferTimer) { |
|---|
| 396 | 415 | clearTimeout(that.deferTimer); |
|---|
| 397 | 416 | that.deferTimer = null; |
|---|
| 398 | 417 | } |
|---|
| 418 | + // 暂不考虑传入cid的情况:一律从第一个分类开始加载首屏 |
|---|
| 419 | + that.navActive = 0; |
|---|
| 420 | + that.subActive = 0; |
|---|
| 399 | 421 | uni.showLoading(); |
|---|
| 400 | 422 | getCategoryList().then(res => { |
|---|
| 401 | | - that.categoryList = res.data; |
|---|
| 402 | | - // [P0-优化] 装配阶段为所有分类节点打 _key,供模板 :key 使用 |
|---|
| 403 | | - that.ensureCategoryKey(that.categoryList); |
|---|
| 423 | + that.categoryList = res.data || []; |
|---|
| 404 | 424 | // 重置计数,避免重复进入页面时累积 |
|---|
| 405 | 425 | that.totalGetNum = 0; |
|---|
| 406 | 426 | that.finishNum = 0; |
|---|
| 407 | 427 | that.catePending = []; |
|---|
| 428 | + that.cateStarted = []; |
|---|
| 408 | 429 | that.categoryList.forEach((item, index) => { |
|---|
| 409 | | - |
|---|
| 410 | 430 | if (item.child && item.child.length > 0) { |
|---|
| 431 | + item.child.sort((a, b) => { |
|---|
| 432 | + return a.sort - b.sort; |
|---|
| 433 | + }); |
|---|
| 411 | 434 | that.totalGetNum += item.child.length; |
|---|
| 412 | 435 | that.catePending[index] = item.child.length; |
|---|
| 413 | 436 | } else { |
|---|
| .. | .. |
|---|
| 415 | 438 | that.catePending[index] = 1; |
|---|
| 416 | 439 | } |
|---|
| 417 | 440 | }) |
|---|
| 418 | | - |
|---|
| 419 | | - // 先标记当前选中分类,再发起请求 |
|---|
| 420 | | - that.categoryList.forEach((item, index) => { |
|---|
| 421 | | - if (that.optionId && that.optionId == item.id) { |
|---|
| 422 | | - that.navActive = index; |
|---|
| 423 | | - that.subActive = 0; |
|---|
| 424 | | - } |
|---|
| 425 | | - if (item.child && item.child.length > 0) { |
|---|
| 426 | | - item.child.sort((a, b) => { |
|---|
| 427 | | - return a.sort - b.sort; |
|---|
| 428 | | - }); |
|---|
| 429 | | - item.child.forEach((item2, index2) => { |
|---|
| 430 | | - if (that.optionId && that.optionId == item2.id) { |
|---|
| 431 | | - that.navActive = index; |
|---|
| 432 | | - that.subActive = index2; |
|---|
| 433 | | - } |
|---|
| 434 | | - }) |
|---|
| 435 | | - } |
|---|
| 441 | + // 分类为空时直接关闭loading,避免一直停留在loading态 |
|---|
| 442 | + if (!that.categoryList.length) { |
|---|
| 443 | + that.firstScreenReady = true; |
|---|
| 444 | + that.isLoading = false; |
|---|
| 445 | + uni.hideLoading(); |
|---|
| 446 | + return; |
|---|
| 447 | + } |
|---|
| 448 | + // 首屏优化:先只请求第一个分类,保证首屏数据最快返回并直接渲染 |
|---|
| 449 | + that.loadCateByIndex(that.navActive); |
|---|
| 450 | + // 其余分类待首屏渲染、loading关闭后,由 loadRestCategory 后台错峰加载 |
|---|
| 451 | + }) |
|---|
| 452 | + }, |
|---|
| 453 | + // 按索引加载某个分类下的商品数据(同一分类只派发一次) |
|---|
| 454 | + loadCateByIndex(index) { |
|---|
| 455 | + let that = this; |
|---|
| 456 | + let item = that.categoryList[index]; |
|---|
| 457 | + if (!item) return; |
|---|
| 458 | + if (that.cateStarted[index]) return; |
|---|
| 459 | + that.cateStarted[index] = true; |
|---|
| 460 | + if (item.child && item.child.length > 0) { |
|---|
| 461 | + item.child.forEach((item2, index2) => { |
|---|
| 462 | + that.getAllProductList('isSub', index, index2); |
|---|
| 436 | 463 | }) |
|---|
| 437 | | - |
|---|
| 438 | | - const loadCate = (index) => { |
|---|
| 439 | | - let item = that.categoryList[index]; |
|---|
| 440 | | - if (item.child && item.child.length > 0) { |
|---|
| 441 | | - item.child.forEach((item2, index2) => { |
|---|
| 442 | | - that.getAllProductList('isSub', index, index2); |
|---|
| 443 | | - }) |
|---|
| 444 | | - } else { |
|---|
| 445 | | - that.getAllProductList(false, index); |
|---|
| 446 | | - } |
|---|
| 447 | | - }; |
|---|
| 448 | | - // [P0-优化] 首屏分类(当前navActive):立即并发发起,不走任何队列,最大限度缩短首屏 loading 时长 |
|---|
| 449 | | - loadCate(that.navActive); |
|---|
| 450 | | - // [P0-优化] 其余分类:构建任务列表,走下方并发限流池,避免一次性占满 wx.request queue 拖慢首屏 |
|---|
| 451 | | - let otherTasks = []; |
|---|
| 452 | | - that.categoryList.forEach((it, idx) => { |
|---|
| 453 | | - if (idx !== that.navActive) otherTasks.push({ index: idx }); |
|---|
| 454 | | - }); |
|---|
| 455 | | - that.runCategoryTasksWithLimit(otherTasks, 4); |
|---|
| 456 | | - |
|---|
| 457 | | - |
|---|
| 458 | | - }) |
|---|
| 459 | | - }, |
|---|
| 460 | | - // [P0-优化] 并发限流工具:除首屏分类外,其余分类的请求经由此处发起 |
|---|
| 461 | | - runCategoryTasksWithLimit(tasks, limit) { |
|---|
| 462 | | - let that = this; |
|---|
| 463 | | - if (!tasks || !tasks.length) return Promise.resolve(); |
|---|
| 464 | | - const queue = tasks.slice(); |
|---|
| 465 | | - const executing = []; |
|---|
| 466 | | - const launch = () => { |
|---|
| 467 | | - while (queue.length && executing.length < limit) { |
|---|
| 468 | | - const task = queue.shift(); |
|---|
| 469 | | - const item = that.categoryList[task.index]; |
|---|
| 470 | | - if (!item) continue; |
|---|
| 471 | | - let promise; |
|---|
| 472 | | - if (item.child && item.child.length > 0) { |
|---|
| 473 | | - const subPromises = []; |
|---|
| 474 | | - item.child.forEach((sub, subIdx) => { |
|---|
| 475 | | - subPromises.push(that.getAllProductList('isSub', task.index, subIdx)); |
|---|
| 476 | | - }); |
|---|
| 477 | | - promise = Promise.all(subPromises); |
|---|
| 478 | | - } else { |
|---|
| 479 | | - promise = that.getAllProductList(false, task.index); |
|---|
| 480 | | - } |
|---|
| 481 | | - // 用 .catch 包一层避免单个失败拖垮整池 |
|---|
| 482 | | - const tracked = promise.catch(() => {}); |
|---|
| 483 | | - tracked.finally(() => { |
|---|
| 484 | | - const idx = executing.indexOf(tracked); |
|---|
| 485 | | - if (idx >= 0) executing.splice(idx, 1); |
|---|
| 486 | | - }).catch(() => {}); |
|---|
| 487 | | - executing.push(tracked); |
|---|
| 488 | | - } |
|---|
| 489 | | - }; |
|---|
| 490 | | - const next = () => { |
|---|
| 491 | | - launch(); |
|---|
| 492 | | - if (executing.length === 0) return Promise.resolve(); |
|---|
| 493 | | - return Promise.race(executing).then(next, next); |
|---|
| 494 | | - }; |
|---|
| 495 | | - return next(); |
|---|
| 496 | | - }, |
|---|
| 497 | | - // [P0-优化] 为分类节点打 _key:非 H5 平台的 v-for :key 必须为简单变量, |
|---|
| 498 | | - // 故在数据装配阶段预先在 item 上加 _key,模板用 :key="item._key" 引用 |
|---|
| 499 | | - ensureCategoryKey: function(list) { |
|---|
| 500 | | - if (!Array.isArray(list)) return; |
|---|
| 501 | | - list.forEach((item, i) => { |
|---|
| 502 | | - if (!item) return; |
|---|
| 503 | | - if (item.id != null) item._key = 'c-' + item.id; |
|---|
| 504 | | - else if (!item._key) item._key = 'c0-' + i; |
|---|
| 505 | | - if (item.child && item.child.length) { |
|---|
| 506 | | - item.child.forEach((sub, j) => { |
|---|
| 507 | | - if (!sub) return; |
|---|
| 508 | | - if (sub.id != null) sub._key = 's-' + sub.id; |
|---|
| 509 | | - else if (!sub._key) sub._key = 's0-' + i + '-' + j; |
|---|
| 510 | | - }); |
|---|
| 511 | | - } |
|---|
| 512 | | - }); |
|---|
| 513 | | - }, |
|---|
| 514 | | - // [P0-优化] 为商品节点打 _key |
|---|
| 515 | | - ensureProductKey: function(list) { |
|---|
| 516 | | - if (!Array.isArray(list)) return; |
|---|
| 517 | | - list.forEach((g, k) => { |
|---|
| 518 | | - if (!g) return; |
|---|
| 519 | | - if (g.id != null && !g._key) g._key = 'p-' + g.id; |
|---|
| 520 | | - else if (!g._key) g._key = 'pk-' + k; |
|---|
| 521 | | - }); |
|---|
| 522 | | - }, |
|---|
| 523 | | - // 当前分类数据全部就绪后立即关闭全屏loading,不必等待所有分类 |
|---|
| 524 | | - checkFirstScreen(index1) { |
|---|
| 464 | + } else { |
|---|
| 465 | + that.getAllProductList(false, index); |
|---|
| 466 | + } |
|---|
| 467 | + }, |
|---|
| 468 | + // 首屏渲染并关闭loading后,后台错峰加载其余分类,避免与首屏抢请求/抢渲染 |
|---|
| 469 | + loadRestCategory() { |
|---|
| 470 | + let that = this; |
|---|
| 471 | + if (that.bgLoaded) return; |
|---|
| 472 | + that.bgLoaded = true; |
|---|
| 473 | + let delay = 0; |
|---|
| 474 | + that.categoryList.forEach((item, index) => { |
|---|
| 475 | + if (index == that.navActive) return; |
|---|
| 476 | + // 每个分类错开触发,逐步在后台铺开,避免一次性铺满请求队列 |
|---|
| 477 | + delay += 50; |
|---|
| 478 | + setTimeout(() => { |
|---|
| 479 | + that.loadCateByIndex(index); |
|---|
| 480 | + }, delay); |
|---|
| 481 | + }) |
|---|
| 482 | + }, |
|---|
| 483 | + // 当前分类数据全部就绪后立即关闭全屏loading,不必等待所有分类 |
|---|
| 484 | + checkFirstScreen(index1) { |
|---|
| 525 | 485 | let that = this; |
|---|
| 526 | 486 | if (that.firstScreenReady || index1 != that.navActive) return; |
|---|
| 527 | 487 | if ((that.catePending[index1] || 0) <= 0) { |
|---|
| .. | .. |
|---|
| 531 | 491 | that.$nextTick(() => { |
|---|
| 532 | 492 | setTimeout(() => { |
|---|
| 533 | 493 | uni.hideLoading(); |
|---|
| 494 | + // 首屏已渲染且loading已关闭,此时才开始后台加载其余分类 |
|---|
| 495 | + that.loadRestCategory(); |
|---|
| 534 | 496 | }, 50) |
|---|
| 535 | 497 | }) |
|---|
| 536 | 498 | } |
|---|
| .. | .. |
|---|
| 600 | 562 | updateDimensions() { |
|---|
| 601 | 563 | const query = uni.createSelectorQuery().in(this); |
|---|
| 602 | 564 | query.select('.goodlistScroll').boundingClientRect(data => { |
|---|
| 603 | | - this.scrollHeight = data.height; |
|---|
| 565 | + // 首屏渲染前该节点可能还不存在,避免报错 |
|---|
| 566 | + if (data) this.scrollHeight = data.height; |
|---|
| 604 | 567 | }).exec(); |
|---|
| 605 | 568 | }, |
|---|
| 606 | 569 | handleScroll(e) { |
|---|
| .. | .. |
|---|
| 631 | 594 | cid: isSub ? that.categoryList[index1].child[index2].id : that.categoryList[index1].id, |
|---|
| 632 | 595 | }).then(res => { |
|---|
| 633 | 596 | let list = res.data.list; |
|---|
| 634 | | - // [P0-优化] 装配阶段为商品节点打 _key,供模板 :key 使用 |
|---|
| 635 | | - that.ensureProductKey(list); |
|---|
| 636 | 597 | // 当前浏览分类即时渲染;其余分类进入缓冲队列批量渲染, |
|---|
| 637 | 598 | // 避免几十次$set各自触发一次全量列表渲染 |
|---|
| 638 | 599 | if (index1 == that.navActive && !that.firstScreenReady) { |
|---|
| .. | .. |
|---|
| 735 | 735 | path: url, |
|---|
| 736 | 736 | envVersion: miniProgramInfo.envVersion == 'release' ? 'release' : 'trial', |
|---|
| 737 | 737 | }) |
|---|
| 738 | | - |
|---|
| 739 | | - // let params={}; |
|---|
| 740 | | - // params['custAgentNo']=((this.$store.getters.belong && this.$store.getters.belong.agentNo) ? this.$store |
|---|
| 741 | | - // .getters.belong.agentNo : ''); |
|---|
| 742 | | - // params['custAgentChnl']=((this.$store.getters.belong && this.$store.getters.belong.channel) ? this.$store |
|---|
| 743 | | - // .getters.belong.channel : ''); |
|---|
| 744 | | - // params['custAgentCoy']=((this.$store.getters.belong && this.$store.getters.belong.companyCode) ? this |
|---|
| 745 | | - // .$store.getters.belong.companyCode : ''); |
|---|
| 746 | | - // params['custAgentBranch']=((this.$store.getters.belong && this.$store.getters.belong.branchCode) ? this |
|---|
| 747 | | - // .$store.getters.belong.branchCode : ''); |
|---|
| 748 | | - // params['custAgentOrgNo']=((this.$store.getters.belong && this.$store.getters.belong.subCode) ? this |
|---|
| 749 | | - // .$store.getters.belong.subCode : ''); |
|---|
| 750 | | - // let webUrl='https://ossacmall.bluedeer.com.cn/qr/index.html?isMall=true'+(miniProgramInfo.envVersion == 'release' ? '' : '&isTrial=true')+'¶ms='+encodeURIComponent(JSON.stringify(params))+'&pageUrl='+encodeURIComponent(url); |
|---|
| 751 | | - // if(HTTP_REQUEST_URL.indexOf('tohours')>0){ |
|---|
| 752 | | - // webUrl=webUrl.replace('https://ossacmall.bluedeer.com.cn/qr','https://sit.tohours.com/crmeb_web/qr/') |
|---|
| 753 | | - // } |
|---|
| 754 | | - // uni.navigateTo({ |
|---|
| 755 | | - // url: '/pages/users/web_page/index?webUel='+encodeURIComponent(webUrl) |
|---|
| 756 | | - // }) |
|---|
| 757 | | - |
|---|
| 738 | + |
|---|
| 758 | 739 | this.clickTrack('third') |
|---|
| 759 | 740 | }, |
|---|
| 760 | 741 | goToLink(item, type) { |
|---|
| .. | .. |
|---|
| 231 | 231 | path: url, |
|---|
| 232 | 232 | envVersion: miniProgramInfo.envVersion == 'release' ? 'release' : 'trial', |
|---|
| 233 | 233 | }) |
|---|
| 234 | | - |
|---|
| 235 | | - // let params = {}; |
|---|
| 236 | | - // params['custAgentNo'] = ((this.$store.getters.belong && this.$store.getters.belong.agentNo) ? this.$store |
|---|
| 237 | | - // .getters.belong.agentNo : ''); |
|---|
| 238 | | - // params['custAgentChnl'] = ((this.$store.getters.belong && this.$store.getters.belong.channel) ? this.$store |
|---|
| 239 | | - // .getters.belong.channel : ''); |
|---|
| 240 | | - // params['custAgentCoy'] = ((this.$store.getters.belong && this.$store.getters.belong.companyCode) ? this |
|---|
| 241 | | - // .$store.getters.belong.companyCode : ''); |
|---|
| 242 | | - // params['custAgentBranch'] = ((this.$store.getters.belong && this.$store.getters.belong.branchCode) ? this |
|---|
| 243 | | - // .$store.getters.belong.branchCode : ''); |
|---|
| 244 | | - // params['custAgentOrgNo'] = ((this.$store.getters.belong && this.$store.getters.belong.subCode) ? this |
|---|
| 245 | | - // .$store.getters.belong.subCode : ''); |
|---|
| 246 | | - // let webUrl = 'https://ossacmall.bluedeer.com.cn/qr/index.html?isMall=true' + (miniProgramInfo.envVersion == |
|---|
| 247 | | - // 'release' ? '' : '&isTrial=true') + '¶ms=' + encodeURIComponent(JSON.stringify(params)) + |
|---|
| 248 | | - // '&pageUrl=' + encodeURIComponent(url); |
|---|
| 249 | | - // if (HTTP_REQUEST_URL.indexOf('tohours') > 0) { |
|---|
| 250 | | - // webUrl = webUrl.replace('https://ossacmall.bluedeer.com.cn/qr', |
|---|
| 251 | | - // 'https://sit.tohours.com/crmeb_web/qr/') |
|---|
| 252 | | - // } |
|---|
| 253 | | - // uni.navigateTo({ |
|---|
| 254 | | - // url: '/pages/users/web_page/index?webUel=' + encodeURIComponent(webUrl) |
|---|
| 255 | | - // }) |
|---|
| 256 | 234 | }, |
|---|
| 257 | 235 | goToLink(item, type) { |
|---|
| 258 | 236 | let that = this; |
|---|
| .. | .. |
|---|
| 508 | 508 | path: url, |
|---|
| 509 | 509 | envVersion: envVersion != 'release' ? 'trial' : 'release' |
|---|
| 510 | 510 | }) |
|---|
| 511 | | - |
|---|
| 512 | | - // let webUrl='https://ossacmall.bluedeer.com.cn/qr/index.html?isMall=true'+(envVersion == 'release' ? '' : '&isTrial=true')+'¶ms='+encodeURIComponent(JSON.stringify(params))+'&pageUrl='+encodeURIComponent(url); |
|---|
| 513 | | - // if(HTTP_REQUEST_URL.indexOf('tohours')>0){ |
|---|
| 514 | | - // webUrl=webUrl.replace('https://ossacmall.bluedeer.com.cn/qr','https://sit.tohours.com/crmeb_web/qr/') |
|---|
| 515 | | - // } |
|---|
| 516 | | - // uni.navigateTo({ |
|---|
| 517 | | - // url: '/pages/users/web_page/index?webUel='+encodeURIComponent(webUrl) |
|---|
| 518 | | - // }) |
|---|
| 519 | 511 | } |
|---|
| 520 | 512 | if (!that.$store.getters.isLogin) { |
|---|
| 521 | 513 | toLogin(that); |
|---|
| .. | .. |
|---|
| 579 | 579 | path: url, |
|---|
| 580 | 580 | envVersion: miniProgramInfo.envVersion == 'release' ? 'release' : 'trial', |
|---|
| 581 | 581 | }) |
|---|
| 582 | | - |
|---|
| 583 | | - // let params = {}; |
|---|
| 584 | | - // params['custAgentNo'] = ((that.$store.getters.belong && that.$store.getters.belong.agentNo) ? that |
|---|
| 585 | | - // .$store |
|---|
| 586 | | - // .getters.belong.agentNo : ''); |
|---|
| 587 | | - // params['custAgentChnl'] = ((that.$store.getters.belong && that.$store.getters.belong.channel) ? that |
|---|
| 588 | | - // .$store |
|---|
| 589 | | - // .getters.belong.channel : ''); |
|---|
| 590 | | - // params['custAgentCoy'] = ((that.$store.getters.belong && that.$store.getters.belong.companyCode) ? that |
|---|
| 591 | | - // .$store.getters.belong.companyCode : ''); |
|---|
| 592 | | - // params['custAgentBranch'] = ((that.$store.getters.belong && that.$store.getters.belong.branchCode) ? |
|---|
| 593 | | - // that |
|---|
| 594 | | - // .$store.getters.belong.branchCode : ''); |
|---|
| 595 | | - // params['custAgentOrgNo'] = ((that.$store.getters.belong && that.$store.getters.belong.subCode) ? that |
|---|
| 596 | | - // .$store.getters.belong.subCode : ''); |
|---|
| 597 | | - // let webUrl = 'https://ossacmall.bluedeer.com.cn/qr/index.html?isMall=true' + (miniProgramInfo.envVersion == 'release' ? |
|---|
| 598 | | - // '' : '&isTrial=true') + '¶ms=' + encodeURIComponent(JSON.stringify(params)) + '&pageUrl=' + |
|---|
| 599 | | - // encodeURIComponent(url); |
|---|
| 600 | | - // if (HTTP_REQUEST_URL.indexOf('tohours') > 0) { |
|---|
| 601 | | - // webUrl = webUrl.replace('https://ossacmall.bluedeer.com.cn/qr', |
|---|
| 602 | | - // 'https://sit.tohours.com/crmeb_web/qr/') |
|---|
| 603 | | - // } |
|---|
| 604 | | - // uni.navigateTo({ |
|---|
| 605 | | - // url: '/pages/users/web_page/index?webUel=' + encodeURIComponent(webUrl) |
|---|
| 606 | | - // }) |
|---|
| 607 | 582 | } |
|---|
| 608 | 583 | if (!that.$store.getters.isLogin) { |
|---|
| 609 | 584 | toLogin(that); |
|---|
| .. | .. |
|---|
| 1730 | 1705 | box-sizing: border-box; |
|---|
| 1731 | 1706 | padding: 25rpx 25rpx 25rpx 55rpx; |
|---|
| 1732 | 1707 | border-radius: 14rpx; |
|---|
| 1733 | | - background: url("https://ossacmall.bluedeer.com.cn/app/static/images/icon_expressTips.png") 15rpx 25rpx/32rpx auto no-repeat; |
|---|
| 1708 | + background: url("https://ossacmall.bluedeer.com.cn/app/static/images/icon_expressTips.png") 15rpx 25rpx / 32rpx auto no-repeat; |
|---|
| 1734 | 1709 | background-color: #fef7ee; |
|---|
| 1735 | 1710 | color: #8e3b1f; |
|---|
| 1736 | 1711 | font-size: 27rpx; |
|---|
| .. | .. |
|---|
| 289 | 289 | path: url, |
|---|
| 290 | 290 | envVersion: miniProgramInfo.envVersion == 'release' ? 'release' : 'trial', |
|---|
| 291 | 291 | }) |
|---|
| 292 | | - |
|---|
| 293 | | - // let params = {}; |
|---|
| 294 | | - // params['custAgentNo'] = ((this.$store.getters.belong && this.$store.getters.belong.agentNo) ? this.$store |
|---|
| 295 | | - // .getters.belong.agentNo : ''); |
|---|
| 296 | | - // params['custAgentChnl'] = ((this.$store.getters.belong && this.$store.getters.belong.channel) ? this.$store |
|---|
| 297 | | - // .getters.belong.channel : ''); |
|---|
| 298 | | - // params['custAgentCoy'] = ((this.$store.getters.belong && this.$store.getters.belong.companyCode) ? this |
|---|
| 299 | | - // .$store.getters.belong.companyCode : ''); |
|---|
| 300 | | - // params['custAgentBranch'] = ((this.$store.getters.belong && this.$store.getters.belong.branchCode) ? this |
|---|
| 301 | | - // .$store.getters.belong.branchCode : ''); |
|---|
| 302 | | - // params['custAgentOrgNo'] = ((this.$store.getters.belong && this.$store.getters.belong.subCode) ? this |
|---|
| 303 | | - // .$store.getters.belong.subCode : ''); |
|---|
| 304 | | - // let webUrl = 'https://ossacmall.bluedeer.com.cn/qr/index.html?isMall=true' + (miniProgramInfo.envVersion == |
|---|
| 305 | | - // 'release' ? '' : '&isTrial=true') + '¶ms=' + encodeURIComponent(JSON.stringify(params)) + |
|---|
| 306 | | - // '&pageUrl=' + encodeURIComponent(url); |
|---|
| 307 | | - // if (HTTP_REQUEST_URL.indexOf('tohours') > 0) { |
|---|
| 308 | | - // webUrl = webUrl.replace('https://ossacmall.bluedeer.com.cn/qr', |
|---|
| 309 | | - // 'https://sit.tohours.com/crmeb_web/qr/') |
|---|
| 310 | | - // } |
|---|
| 311 | | - // uni.navigateTo({ |
|---|
| 312 | | - // url: '/pages/users/web_page/index?webUel=' + encodeURIComponent(webUrl) |
|---|
| 313 | | - // }) |
|---|
| 314 | 292 | }, |
|---|
| 315 | 293 | isTabLink(link) { |
|---|
| 316 | 294 | let isTab = false; |
|---|
| .. | .. |
|---|
| 14 | 14 | <view :style="{ height: marTop + 'px' }"></view> |
|---|
| 15 | 15 | <yueyangZone ref=yueyangZoneRef v-if="isYueyangZone" :zoneInfo="info" :careBeanRate="careBeanRate" |
|---|
| 16 | 16 | style="position: relative;z-index: 2;" @goLottery="goLottery" :relationKey="relationKey"></yueyangZone> |
|---|
| 17 | + <view v-if="isYueyangZone" class="detail-btn" @click="goDetail"> |
|---|
| 18 | + <text>消耗明细</text> |
|---|
| 19 | + </view> |
|---|
| 17 | 20 | <view v-else> |
|---|
| 18 | 21 | <view class="expireTime" v-if="expireTime && expireTime.length"> |
|---|
| 19 | 22 | <text>专区将在</text> |
|---|
| .. | .. |
|---|
| 495 | 498 | |
|---|
| 496 | 499 | <script> |
|---|
| 497 | 500 | import { |
|---|
| 498 | | - HTTP_REQUEST_URL |
|---|
| 501 | + WEB_ACTIVITY_BASE |
|---|
| 499 | 502 | } from '@/config/app'; |
|---|
| 500 | 503 | import { |
|---|
| 501 | 504 | mapGetters |
|---|
| .. | .. |
|---|
| 1195 | 1198 | // that.myCareBean = res2.data.totalCareBeans; |
|---|
| 1196 | 1199 | }); |
|---|
| 1197 | 1200 | }, |
|---|
| 1201 | + goDetail() { |
|---|
| 1202 | + uni.navigateTo({ |
|---|
| 1203 | + url: '/pages/consumption/index?zoneId=' + this.info.id |
|---|
| 1204 | + }) |
|---|
| 1205 | + }, |
|---|
| 1198 | 1206 | goLottery() { |
|---|
| 1199 | 1207 | let that = this; |
|---|
| 1200 | 1208 | var data = { |
|---|
| .. | .. |
|---|
| 1206 | 1214 | this.addTrack('yy_marketPageClick', data); |
|---|
| 1207 | 1215 | |
|---|
| 1208 | 1216 | function go() { |
|---|
| 1209 | | - let webUrl = 'https://ossacmall.bluedeer.com.cn/acmall_lottery/index.html' |
|---|
| 1210 | | - if (HTTP_REQUEST_URL.indexOf('tohours') > 0) { |
|---|
| 1211 | | - webUrl = webUrl.replace('https://ossacmall.bluedeer.com.cn', |
|---|
| 1212 | | - 'https://sit.tohours.com/crmeb_web') |
|---|
| 1213 | | - } |
|---|
| 1217 | + let webUrl = `${WEB_ACTIVITY_BASE}/acmall_lottery/index.html` |
|---|
| 1214 | 1218 | webUrl += '?token=' + |
|---|
| 1215 | 1219 | that.$store.getters.token + '&unionId=' + that.$store.getters.unionId + '&mdayId=' + that.info |
|---|
| 1216 | 1220 | .mdayId |
|---|
| .. | .. |
|---|
| 1840 | 1844 | transform: translate(0, -50%); |
|---|
| 1841 | 1845 | } |
|---|
| 1842 | 1846 | |
|---|
| 1847 | +.detail-btn { |
|---|
| 1848 | + border: 2rpx solid #fff; |
|---|
| 1849 | + border-top-left-radius: 20rpx; |
|---|
| 1850 | + border-bottom-left-radius: 20rpx; |
|---|
| 1851 | + padding: 10rpx; |
|---|
| 1852 | + background-color: #daf2b2; |
|---|
| 1853 | + color: #1e984f; |
|---|
| 1854 | + width: 50rpx; |
|---|
| 1855 | + position: fixed; |
|---|
| 1856 | + top: 200rpx; |
|---|
| 1857 | + right: 0rpx; |
|---|
| 1858 | + line-height: 1rem; |
|---|
| 1859 | + z-index: 99; |
|---|
| 1860 | +} |
|---|
| 1861 | + |
|---|
| 1843 | 1862 | .head_img { |
|---|
| 1844 | 1863 | width: 750rpx; |
|---|
| 1845 | 1864 | height: 422rpx; |
|---|
deleted file mode 100644| .. | .. |
|---|
| 1 | | -{ |
|---|
| 2 | | - "appid": "wx15d029cdf2e92a9f", |
|---|
| 3 | | - "compileType": "miniprogram", |
|---|
| 4 | | - "libVersion": "3.1.4", |
|---|
| 5 | | - "packOptions": { |
|---|
| 6 | | - "ignore": [], |
|---|
| 7 | | - "include": [] |
|---|
| 8 | | - }, |
|---|
| 9 | | - "setting": { |
|---|
| 10 | | - "coverView": true, |
|---|
| 11 | | - "es6": true, |
|---|
| 12 | | - "postcss": true, |
|---|
| 13 | | - "minified": true, |
|---|
| 14 | | - "enhance": true, |
|---|
| 15 | | - "showShadowRootInWxmlPanel": true, |
|---|
| 16 | | - "packNpmRelationList": [], |
|---|
| 17 | | - "babelSetting": { |
|---|
| 18 | | - "ignore": [], |
|---|
| 19 | | - "disablePlugins": [], |
|---|
| 20 | | - "outputPath": "" |
|---|
| 21 | | - }, |
|---|
| 22 | | - "condition": false |
|---|
| 23 | | - }, |
|---|
| 24 | | - "condition": {}, |
|---|
| 25 | | - "editorSetting": { |
|---|
| 26 | | - "tabIndent": "insertSpaces", |
|---|
| 27 | | - "tabSize": 2 |
|---|
| 28 | | - } |
|---|
| 29 | | -} |
|---|
| .. | .. |
|---|
| 1 | +@echo off |
|---|
| 2 | +chcp 65001 >nul |
|---|
| 3 | +cd /d "%~dp0" |
|---|
| 4 | +node "scripts\publish.js" prod %* |
|---|
| 5 | +if errorlevel 1 ( |
|---|
| 6 | + echo. |
|---|
| 7 | + echo [FAILED] publish PROD failed - see output above |
|---|
| 8 | +) else ( |
|---|
| 9 | + echo. |
|---|
| 10 | + echo [OK] PROD finished - see output above. Release dir: unpackage\dist\build\mp-weixin |
|---|
| 11 | +) |
|---|
| 12 | +pause |
|---|
| .. | .. |
|---|
| 1 | +@echo off |
|---|
| 2 | +chcp 65001 >nul |
|---|
| 3 | +cd /d "%~dp0" |
|---|
| 4 | +node "scripts\publish.js" sit %* |
|---|
| 5 | +if errorlevel 1 ( |
|---|
| 6 | + echo. |
|---|
| 7 | + echo [FAILED] publish SIT failed - see output above |
|---|
| 8 | +) else ( |
|---|
| 9 | + echo. |
|---|
| 10 | + echo [OK] SIT finished - see output above. Release dir: unpackage\dist\build\mp-weixin |
|---|
| 11 | +) |
|---|
| 12 | +pause |
|---|
| .. | .. |
|---|
| 1 | +/** |
|---|
| 2 | + * manifest.json 生成器 |
|---|
| 3 | + * |
|---|
| 4 | + * manifest.json 是 HBuilderX 的编译输入,但内含环境相关 appid,不适合直接提交; |
|---|
| 5 | + * 因此模板入库、生成物忽略: |
|---|
| 6 | + * |
|---|
| 7 | + * manifest.json.demo(入库,模板) ──注入环境 appid──▶ manifest.json(.gitignore) |
|---|
| 8 | + * |
|---|
| 9 | + * appid 数据源唯一:config/env/<env>.js 的 UNI_APPID(DCloud 标识)、APPID(微信小程序 appid) |
|---|
| 10 | + * 说明:模板里 mp-weixin 的 appid 允许写成 null 或不存在,生成时会自动写入/补齐。 |
|---|
| 11 | + */ |
|---|
| 12 | +const fs = require('fs'); |
|---|
| 13 | +const path = require('path'); |
|---|
| 14 | + |
|---|
| 15 | +const ROOT = path.resolve(__dirname, '..'); |
|---|
| 16 | +const TEMPLATE = path.join(ROOT, 'manifest.json.demo'); |
|---|
| 17 | +const TARGET = path.join(ROOT, 'manifest.json'); |
|---|
| 18 | + |
|---|
| 19 | +function fail(msg) { |
|---|
| 20 | + console.error('[manifest] ' + msg); |
|---|
| 21 | + process.exit(1); |
|---|
| 22 | +} |
|---|
| 23 | + |
|---|
| 24 | +/** 只替换第一处 "appid" : <字符串或 null>,保留原有的空格/缩进风格 */ |
|---|
| 25 | +function replaceAppId(text, value) { |
|---|
| 26 | + let done = false; |
|---|
| 27 | + const result = text.replace(/"appid"(\s*:\s*)(?:"[^"]*"|null)/, function(match, sep) { |
|---|
| 28 | + done = true; |
|---|
| 29 | + return '"appid"' + sep + '"' + value + '"'; |
|---|
| 30 | + }); |
|---|
| 31 | + return { text: result, done: done }; |
|---|
| 32 | +} |
|---|
| 33 | + |
|---|
| 34 | +/** 注入两个 appid:顶层(DCloud 标识)+ mp-weixin 块内(微信小程序 appid) */ |
|---|
| 35 | +function injectAppIds(text, env) { |
|---|
| 36 | + // 1) 顶层 appid(第一处出现即为顶层) |
|---|
| 37 | + const top = replaceAppId(text, env.UNI_APPID); |
|---|
| 38 | + if (!top.done) fail('模板中未找到顶层 "appid" 字段:' + path.relative(ROOT, TEMPLATE)); |
|---|
| 39 | + text = top.text; |
|---|
| 40 | + |
|---|
| 41 | + // 2) mp-weixin 下的 appid,只在该区块内替换第一处 |
|---|
| 42 | + const idx = text.indexOf('"mp-weixin"'); |
|---|
| 43 | + if (idx < 0) fail('模板中未找到 "mp-weixin" 配置块:' + path.relative(ROOT, TEMPLATE)); |
|---|
| 44 | + const head = text.slice(0, idx); |
|---|
| 45 | + const body = text.slice(idx); |
|---|
| 46 | + const scoped = replaceAppId(body, env.APPID); |
|---|
| 47 | + if (scoped.done) return head + scoped.text; |
|---|
| 48 | + |
|---|
| 49 | + // 3) 字段被删掉时,按同级子项缩进补一行 |
|---|
| 50 | + const m = body.match(/^("mp-weixin"\s*:\s*\{)(\r?\n)([ \t]*)/); |
|---|
| 51 | + if (!m) fail('模板的 "mp-weixin" 块格式异常,无法写入 appid'); |
|---|
| 52 | + const nl = m[2]; |
|---|
| 53 | + const indent = m[3]; |
|---|
| 54 | + return head + m[1] + nl + indent + '"appid" : "' + env.APPID + '",' + nl + indent + body.slice(m[0].length); |
|---|
| 55 | +} |
|---|
| 56 | + |
|---|
| 57 | +/** 统计「除 appid 行以外」的差异(按行内容比较,不受行号位移影响) */ |
|---|
| 58 | +function otherDiff(a, b) { |
|---|
| 59 | + function count(text) { |
|---|
| 60 | + const m = new Map(); |
|---|
| 61 | + text.split(/\r?\n/).forEach(function(line) { |
|---|
| 62 | + const l = line.replace(/\s+$/, ''); |
|---|
| 63 | + if (/^\s*"appid"\s*:/.test(l.trim())) return; |
|---|
| 64 | + m.set(l, (m.get(l) || 0) + 1); |
|---|
| 65 | + }); |
|---|
| 66 | + return m; |
|---|
| 67 | + } |
|---|
| 68 | + const ma = count(a); |
|---|
| 69 | + const mb = count(b); |
|---|
| 70 | + const keys = new Set(Array.from(ma.keys()).concat(Array.from(mb.keys()))); |
|---|
| 71 | + let diff = 0; |
|---|
| 72 | + const sample = []; |
|---|
| 73 | + keys.forEach(function(k) { |
|---|
| 74 | + const d = Math.abs((ma.get(k) || 0) - (mb.get(k) || 0)); |
|---|
| 75 | + if (!d) return; |
|---|
| 76 | + diff += d; |
|---|
| 77 | + if (sample.length < 3 && k.trim()) sample.push(k.trim().slice(0, 40)); |
|---|
| 78 | + }); |
|---|
| 79 | + return { count: diff, sample: sample }; |
|---|
| 80 | +} |
|---|
| 81 | + |
|---|
| 82 | +/** |
|---|
| 83 | + * 按环境生成 manifest.json |
|---|
| 84 | + * @param {object} env config/env/<env>.js 导出的配置 |
|---|
| 85 | + * @returns {{ file: string, changed: boolean }} |
|---|
| 86 | + */ |
|---|
| 87 | +function generate(env) { |
|---|
| 88 | + if (!fs.existsSync(TEMPLATE)) { |
|---|
| 89 | + fail('未找到模板 ' + path.relative(ROOT, TEMPLATE) + |
|---|
| 90 | + '\n 请把 manifest.json 的模板保存为 manifest.json.demo(需入库)后再生成'); |
|---|
| 91 | + } |
|---|
| 92 | + |
|---|
| 93 | + const rendered = injectAppIds(fs.readFileSync(TEMPLATE, 'utf8'), env); |
|---|
| 94 | + const exists = fs.existsSync(TARGET); |
|---|
| 95 | + |
|---|
| 96 | + if (exists) { |
|---|
| 97 | + const prev = fs.readFileSync(TARGET, 'utf8'); |
|---|
| 98 | + // 行尾差异(CRLF/LF)不算变化,避免无意义重写 |
|---|
| 99 | + if (prev.replace(/\r\n/g, '\n') === rendered.replace(/\r\n/g, '\n')) { |
|---|
| 100 | + console.log('[manifest] manifest.json 已是目标内容,无需重写'); |
|---|
| 101 | + return { file: TARGET, changed: false }; |
|---|
| 102 | + } |
|---|
| 103 | + const diffs = otherDiff(prev, rendered); |
|---|
| 104 | + if (diffs.count) { |
|---|
| 105 | + console.log('[manifest] 注意:现有 manifest.json 有 ' + diffs.count + ' 行内容与模板不同,本次会被模板覆盖' + |
|---|
| 106 | + (diffs.sample.length ? '(如 ' + diffs.sample.join(' | ') + ')' : '')); |
|---|
| 107 | + console.log('[manifest] 需要长期保留的改动请写进 manifest.json.demo'); |
|---|
| 108 | + } |
|---|
| 109 | + } |
|---|
| 110 | + |
|---|
| 111 | + fs.writeFileSync(TARGET, rendered, 'utf8'); |
|---|
| 112 | + console.log('[manifest] 已由 manifest.json.demo 生成 manifest.json' + |
|---|
| 113 | + '(DCloud ' + env.UNI_APPID + ' / 小程序 ' + env.APPID + ')'); |
|---|
| 114 | + return { file: TARGET, changed: true }; |
|---|
| 115 | +} |
|---|
| 116 | + |
|---|
| 117 | +module.exports = { |
|---|
| 118 | + TEMPLATE: TEMPLATE, |
|---|
| 119 | + TARGET: TARGET, |
|---|
| 120 | + generate: generate, |
|---|
| 121 | + injectAppIds: injectAppIds |
|---|
| 122 | +}; |
|---|
| .. | .. |
|---|
| 1 | +/** |
|---|
| 2 | + * 命令行发行(不上传)+ 自动用微信开发者工具打开产物 |
|---|
| 3 | + * |
|---|
| 4 | + * 用法: |
|---|
| 5 | + * node scripts/publish.js sit # 发行 SIT 版本并打开微信开发者工具 |
|---|
| 6 | + * node scripts/publish.js prod # 发行 PROD 版本并打开微信开发者工具 |
|---|
| 7 | + * node scripts/publish.js sit --no-gen # 不改 manifest.json(要求它已存在) |
|---|
| 8 | + * node scripts/publish.js sit --no-open # 只编译,不打开微信开发者工具 |
|---|
| 9 | + * node scripts/publish.js sit --dry-run # 只打印将要执行的命令 |
|---|
| 10 | + * |
|---|
| 11 | + * 流程:从 manifest.json.demo 生成 manifest.json(按目标环境注入 appid)→ 发行编译 → |
|---|
| 12 | + * 校正产物 appid → 用微信开发者工具打开产物 |
|---|
| 13 | + * |
|---|
| 14 | + * 环境值统一取自 config/env/<env>.js(唯一数据源),接口地址等运行时参数由包内按 appid 自动识别; |
|---|
| 15 | + * manifest.json 是生成物(已 .gitignore),因此 git 永远没有环境噪音。 |
|---|
| 16 | + * 微信开发者工具打开的产物 appid = unpackage/dist/build/mp-weixin/project.config.json 的 appid |
|---|
| 17 | + * (构建时由 manifest 生成,这里再兜底校正一次)。 |
|---|
| 18 | + * |
|---|
| 19 | + * 外部工具(HBuilderX cli.exe、微信开发者工具 cli.bat)不写死绝对路径,按顺序自动查找: |
|---|
| 20 | + * 1) 环境变量 HBUILDERX_CLI / WX_DEVTOOLS_CLI |
|---|
| 21 | + * 2) 系统 PATH |
|---|
| 22 | + * 3) 常见安装目录(Program Files、用户目录、各磁盘常见软件目录) |
|---|
| 23 | + * 4) 磁盘根目录浅层扫描(≤2 层) |
|---|
| 24 | + * 找到后会把其所在目录加入本次进程 PATH(便于按名字直接调用),找不到则报错并给出解决办法。 |
|---|
| 25 | + */ |
|---|
| 26 | +const { spawnSync } = require('child_process'); |
|---|
| 27 | +const fs = require('fs'); |
|---|
| 28 | +const path = require('path'); |
|---|
| 29 | +const manifest = require('./manifest'); |
|---|
| 30 | + |
|---|
| 31 | +const ROOT = path.resolve(__dirname, '..'); |
|---|
| 32 | +const ENV_DIR = path.join(ROOT, 'config', 'env'); |
|---|
| 33 | +const DIST_DIR = path.join(ROOT, 'unpackage', 'dist', 'build', 'mp-weixin'); |
|---|
| 34 | +const DIST_CONFIG = path.join(DIST_DIR, 'project.config.json'); |
|---|
| 35 | + |
|---|
| 36 | +/* ------------------------- 外部工具定位 ------------------------- */ |
|---|
| 37 | + |
|---|
| 38 | +// 扫描时要跳过的系统/无关目录 |
|---|
| 39 | +const SKIP_DIRS = /^(\$recycle\.bin|system volume information|windows|winsxs|node_modules|\.git|\.cache|programdata)$/i; |
|---|
| 40 | + |
|---|
| 41 | +const HX_TOOL = { |
|---|
| 42 | + envKey: 'HBUILDERX_CLI', |
|---|
| 43 | + label: 'HBuilderX 命令行 cli.exe', |
|---|
| 44 | + names: ['cli.exe'], |
|---|
| 45 | + subDirs: ['HBuilderX', 'HBuilderX\\HBuilderX'], |
|---|
| 46 | + dirPattern: /^hbuilderx/i, |
|---|
| 47 | + ok: function(file) { return /hbuilderx/i.test(file); }, |
|---|
| 48 | + hint: 'HBuilderX 安装/解压目录下的 cli.exe(如 X:\\HBuilderX\\cli.exe)', |
|---|
| 49 | + extra: '' |
|---|
| 50 | +}; |
|---|
| 51 | + |
|---|
| 52 | +const WX_TOOL = { |
|---|
| 53 | + envKey: 'WX_DEVTOOLS_CLI', |
|---|
| 54 | + label: '微信开发者工具命令行 cli.bat', |
|---|
| 55 | + names: ['cli.bat'], |
|---|
| 56 | + subDirs: ['微信web开发者工具', '微信开发者工具', 'Tencent\\微信web开发者工具', 'Tencent\\微信开发者工具'], |
|---|
| 57 | + dirPattern: /微信(web)?开发者工具/, |
|---|
| 58 | + hint: '微信开发者工具安装目录下的 cli.bat(如 X:\\Program Files (x86)\\Tencent\\微信web开发者工具\\cli.bat)', |
|---|
| 59 | + extra: ' 3) 或加 --no-open,只编译不自动打开产物\n' |
|---|
| 60 | +}; |
|---|
| 61 | + |
|---|
| 62 | +function fail(msg) { |
|---|
| 63 | + console.error('[release] ' + msg); |
|---|
| 64 | + process.exit(1); |
|---|
| 65 | +} |
|---|
| 66 | + |
|---|
| 67 | +function uniq(list) { |
|---|
| 68 | + return list.filter(function(v, i) { return v && list.indexOf(v) === i; }); |
|---|
| 69 | +} |
|---|
| 70 | + |
|---|
| 71 | +function isFile(p) { |
|---|
| 72 | + try { return fs.statSync(p).isFile(); } catch (e) { return false; } |
|---|
| 73 | +} |
|---|
| 74 | + |
|---|
| 75 | +function isDir(p) { |
|---|
| 76 | + try { return fs.statSync(p).isDirectory(); } catch (e) { return false; } |
|---|
| 77 | +} |
|---|
| 78 | + |
|---|
| 79 | +/** 本地固定磁盘(C:、D: …);拿不到时退回环境变量里的盘符(结果缓存,只探测一次) */ |
|---|
| 80 | +let DRIVES = null; |
|---|
| 81 | +function fixedDrives() { |
|---|
| 82 | + if (DRIVES) return DRIVES; |
|---|
| 83 | + const ps = spawnSync('powershell', ['-NoProfile', '-NonInteractive', '-Command', |
|---|
| 84 | + "[System.IO.DriveInfo]::GetDrives() | Where-Object { $_.DriveType -eq 'Fixed' } | ForEach-Object { $_.Name }" |
|---|
| 85 | + ], { encoding: 'utf8', windowsHide: true }); |
|---|
| 86 | + const fromPs = (ps.stdout || '').split(/\r?\n/) |
|---|
| 87 | + .map(function(s) { return s.trim().replace(/\\$/, ''); }) |
|---|
| 88 | + .filter(function(s) { return /^[A-Za-z]:$/.test(s); }); |
|---|
| 89 | + const fromEnv = [process.env.SystemDrive, process.env.HOMEDRIVE]; |
|---|
| 90 | + DRIVES = uniq(fromPs.concat(fromEnv).map(function(d) { return (d || '').replace(/\\$/, ''); })); |
|---|
| 91 | + return DRIVES; |
|---|
| 92 | +} |
|---|
| 93 | + |
|---|
| 94 | +/** 在系统 PATH 中按文件名查找,ok() 用于排除同名无关程序 */ |
|---|
| 95 | +function findOnPath(names, ok) { |
|---|
| 96 | + const dirs = (process.env.PATH || '').split(path.delimiter); |
|---|
| 97 | + for (let i = 0; i < dirs.length; i++) { |
|---|
| 98 | + const dir = dirs[i].replace(/^"|"$/g, ''); |
|---|
| 99 | + if (!dir) continue; |
|---|
| 100 | + for (let j = 0; j < names.length; j++) { |
|---|
| 101 | + const file = path.join(dir, names[j]); |
|---|
| 102 | + if (isFile(file) && (!ok || ok(file))) return file; |
|---|
| 103 | + } |
|---|
| 104 | + } |
|---|
| 105 | + return ''; |
|---|
| 106 | +} |
|---|
| 107 | + |
|---|
| 108 | +/** 常见安装根目录:Program Files、用户目录、各磁盘根与常见软件目录 */ |
|---|
| 109 | +function baseDirs() { |
|---|
| 110 | + const bases = [ |
|---|
| 111 | + process.env.ProgramFiles, |
|---|
| 112 | + process.env['ProgramFiles(x86)'], |
|---|
| 113 | + process.env.LOCALAPPDATA, |
|---|
| 114 | + process.env.APPDATA, |
|---|
| 115 | + process.env.USERPROFILE, |
|---|
| 116 | + process.env.LOCALAPPDATA && path.join(process.env.LOCALAPPDATA, 'Programs'), |
|---|
| 117 | + process.env.USERPROFILE && path.join(process.env.USERPROFILE, 'Desktop'), |
|---|
| 118 | + process.env.USERPROFILE && path.join(process.env.USERPROFILE, 'Downloads') |
|---|
| 119 | + ]; |
|---|
| 120 | + fixedDrives().forEach(function(d) { |
|---|
| 121 | + bases.push(d + '\\', d + '\\Program Files', d + '\\Program Files (x86)', |
|---|
| 122 | + d + '\\soft', d + '\\software', d + '\\Programs', d + '\\develop'); |
|---|
| 123 | + }); |
|---|
| 124 | + return uniq(bases).filter(isDir); |
|---|
| 125 | +} |
|---|
| 126 | + |
|---|
| 127 | +/** 磁盘浅层扫描:找名字符合特征的目录(兜底手段) */ |
|---|
| 128 | +function scanPlausibleDirs(pattern) { |
|---|
| 129 | + const found = []; |
|---|
| 130 | + fixedDrives().forEach(function(root) { |
|---|
| 131 | + let level1 = []; |
|---|
| 132 | + try { level1 = fs.readdirSync(root + '\\', { withFileTypes: true }); } catch (e) { return; } |
|---|
| 133 | + level1.forEach(function(e1) { |
|---|
| 134 | + if (!e1.isDirectory() || SKIP_DIRS.test(e1.name)) return; |
|---|
| 135 | + const p1 = path.join(root + '\\', e1.name); |
|---|
| 136 | + if (pattern.test(e1.name)) { found.push(p1); return; } |
|---|
| 137 | + let level2 = []; |
|---|
| 138 | + try { level2 = fs.readdirSync(p1, { withFileTypes: true }); } catch (e) { return; } |
|---|
| 139 | + level2.forEach(function(e2) { |
|---|
| 140 | + if (e2.isDirectory() && pattern.test(e2.name)) found.push(path.join(p1, e2.name)); |
|---|
| 141 | + }); |
|---|
| 142 | + }); |
|---|
| 143 | + }); |
|---|
| 144 | + return found; |
|---|
| 145 | +} |
|---|
| 146 | + |
|---|
| 147 | +/** 定位工具:返回 { file, source },找不到返回 null */ |
|---|
| 148 | +function resolveTool(cfg) { |
|---|
| 149 | + // 1) 环境变量显式指定 |
|---|
| 150 | + const fromEnv = (process.env[cfg.envKey] || '').trim(); |
|---|
| 151 | + if (fromEnv) { |
|---|
| 152 | + if (isFile(fromEnv)) return { file: fromEnv, source: '环境变量 ' + cfg.envKey }; |
|---|
| 153 | + fail('环境变量 ' + cfg.envKey + ' 指向的文件不存在:' + fromEnv); |
|---|
| 154 | + } |
|---|
| 155 | + |
|---|
| 156 | + // 2) 系统 PATH |
|---|
| 157 | + const onPath = findOnPath(cfg.names, cfg.ok); |
|---|
| 158 | + if (onPath) return { file: onPath, source: '系统 PATH' }; |
|---|
| 159 | + |
|---|
| 160 | + // 3) 常见安装目录 + 磁盘浅层扫描 |
|---|
| 161 | + const dirs = baseDirs().concat(scanPlausibleDirs(cfg.dirPattern)); |
|---|
| 162 | + for (let i = 0; i < dirs.length; i++) { |
|---|
| 163 | + for (let j = 0; j < cfg.subDirs.length; j++) { |
|---|
| 164 | + for (let k = 0; k < cfg.names.length; k++) { |
|---|
| 165 | + const file = path.join(dirs[i], cfg.subDirs[j], cfg.names[k]); |
|---|
| 166 | + if (isFile(file)) return { file: file, source: '常见安装目录' }; |
|---|
| 167 | + } |
|---|
| 168 | + } |
|---|
| 169 | + } |
|---|
| 170 | + return null; |
|---|
| 171 | +} |
|---|
| 172 | + |
|---|
| 173 | +/** 找不到工具时的报错提示 */ |
|---|
| 174 | +function toolFail(cfg) { |
|---|
| 175 | + fail('未找到 ' + cfg.label + '\n' + |
|---|
| 176 | + ' 已查找:环境变量 ' + cfg.envKey + ' → 系统 PATH → 常见安装目录 → 磁盘浅层扫描\n' + |
|---|
| 177 | + ' 典型位置:' + cfg.hint + '\n' + |
|---|
| 178 | + ' 解决办法(任选其一):\n' + |
|---|
| 179 | + ' 1) 把安装目录加入 PATH:set PATH=%PATH%;<安装目录>\n' + |
|---|
| 180 | + ' 2) 用环境变量指定:set ' + cfg.envKey + '=<cli 完整路径>\n' + |
|---|
| 181 | + cfg.extra); |
|---|
| 182 | +} |
|---|
| 183 | + |
|---|
| 184 | +/** 把工具所在目录加入本次进程 PATH,便于按名字直接调用 */ |
|---|
| 185 | +function putOnPath(file) { |
|---|
| 186 | + const dir = path.dirname(file); |
|---|
| 187 | + const cur = process.env.PATH || ''; |
|---|
| 188 | + if (cur.split(path.delimiter).indexOf(dir) < 0) process.env.PATH = dir + path.delimiter + cur; |
|---|
| 189 | +} |
|---|
| 190 | + |
|---|
| 191 | +/* ------------------------- 参数解析 ------------------------- */ |
|---|
| 192 | + |
|---|
| 193 | +const argv = process.argv.slice(2); |
|---|
| 194 | +const envName = (argv.find(function(a) { return a.charAt(0) !== '-'; }) || '').trim(); |
|---|
| 195 | +const noGen = argv.indexOf('--no-gen') > -1; |
|---|
| 196 | +const noOpen = argv.indexOf('--no-open') > -1; |
|---|
| 197 | +const dryRun = argv.indexOf('--dry-run') > -1; |
|---|
| 198 | + |
|---|
| 199 | +const OWN_ARGS = ['--no-gen', '--no-open', '--dry-run']; |
|---|
| 200 | +// 透传给 HBuilderX cli 的额外参数(保留 "参数 值" 成对关系) |
|---|
| 201 | +const passthrough = []; |
|---|
| 202 | +let envNameSkipped = false; |
|---|
| 203 | +argv.forEach(function(a) { |
|---|
| 204 | + if (OWN_ARGS.indexOf(a) > -1) return; |
|---|
| 205 | + if (!envNameSkipped && a === envName) { |
|---|
| 206 | + envNameSkipped = true; |
|---|
| 207 | + return; |
|---|
| 208 | + } |
|---|
| 209 | + passthrough.push(a); |
|---|
| 210 | +}); |
|---|
| 211 | + |
|---|
| 212 | +function readAppId(file) { |
|---|
| 213 | + try { |
|---|
| 214 | + return JSON.parse(fs.readFileSync(file, 'utf8')).appid || ''; |
|---|
| 215 | + } catch (e) { |
|---|
| 216 | + return ''; |
|---|
| 217 | + } |
|---|
| 218 | +} |
|---|
| 219 | + |
|---|
| 220 | +/** 回写 appid(正则替换,保留原格式) */ |
|---|
| 221 | +function writeAppId(file, appid) { |
|---|
| 222 | + const prev = fs.readFileSync(file, 'utf8'); |
|---|
| 223 | + const next = prev.replace(/"appid"(\s*:\s*)"[^"]*"/, function(m, sep) { |
|---|
| 224 | + return '"appid"' + sep + '"' + appid + '"'; |
|---|
| 225 | + }); |
|---|
| 226 | + if (next !== prev) fs.writeFileSync(file, next, 'utf8'); |
|---|
| 227 | + return readAppId(file); |
|---|
| 228 | +} |
|---|
| 229 | + |
|---|
| 230 | +function main() { |
|---|
| 231 | + const available = fs.existsSync(ENV_DIR) |
|---|
| 232 | + ? fs.readdirSync(ENV_DIR).filter(f => f.endsWith('.js') && f !== 'index.js').map(f => f.replace(/\.js$/, '')) |
|---|
| 233 | + : []; |
|---|
| 234 | + |
|---|
| 235 | + if (!envName) fail('请指定环境名,可选:' + available.join(' / ') + '\n 例:node scripts/publish.js sit'); |
|---|
| 236 | + if (available.indexOf(envName) < 0) fail('未找到 config/env/' + envName + '.js,可选:' + available.join(' / ')); |
|---|
| 237 | + |
|---|
| 238 | + const env = require(path.join(ENV_DIR, envName + '.js')); |
|---|
| 239 | + |
|---|
| 240 | + // 定位外部工具(不写死路径) |
|---|
| 241 | + console.log('[release] 查找 HBuilderX / 微信开发者工具…'); |
|---|
| 242 | + const hx = resolveTool(HX_TOOL); |
|---|
| 243 | + if (!hx) toolFail(HX_TOOL); |
|---|
| 244 | + const wx = noOpen ? null : resolveTool(WX_TOOL); |
|---|
| 245 | + if (!noOpen && !wx) toolFail(WX_TOOL); |
|---|
| 246 | + putOnPath(hx.file); |
|---|
| 247 | + if (wx) putOnPath(wx.file); |
|---|
| 248 | + |
|---|
| 249 | + // 目标编译产物由 manifest.json 决定,而 manifest.json 是生成物 |
|---|
| 250 | + if (noGen && !fs.existsSync(manifest.TARGET)) { |
|---|
| 251 | + fail('--no-gen 需要 manifest.json 已存在,请先运行:node scripts/set-env.js ' + envName); |
|---|
| 252 | + } |
|---|
| 253 | + |
|---|
| 254 | + console.log('[release] 环境 : ' + envName + '(' + env.ENV_NAME + ')'); |
|---|
| 255 | + console.log('[release] 小程序appid : ' + env.APPID); |
|---|
| 256 | + console.log('[release] 接口地址 : ' + env.HTTP_REQUEST_URL); |
|---|
| 257 | + console.log('[release] 产物目录 : ' + DIST_DIR); |
|---|
| 258 | + console.log('[release] HBuilderX : ' + hx.file + '(' + hx.source + ')'); |
|---|
| 259 | + if (wx) console.log('[release] 开发者工具 : ' + wx.file + '(' + wx.source + ')'); |
|---|
| 260 | + console.log('[release] manifest.json: ' + (noGen |
|---|
| 261 | + ? '不改动(--no-gen)' |
|---|
| 262 | + : '由 manifest.json.demo 生成(appid ' + env.APPID + ',生成物不入库)')); |
|---|
| 263 | + |
|---|
| 264 | + const cliArgs = ['publish', '--platform', 'mp-weixin', '--project', ROOT].concat(passthrough); |
|---|
| 265 | + // 路径含空格/中文,交给 cmd 解析更稳(--no-open 时没有微信开发者工具,不拼这条命令) |
|---|
| 266 | + const openCmd = wx ? '"' + wx.file + '" open --project "' + DIST_DIR + '"' : ''; |
|---|
| 267 | + |
|---|
| 268 | + if (dryRun) { |
|---|
| 269 | + console.log('[release] 0) 生成: manifest.json ← manifest.json.demo' + (noGen ? '(已指定 --no-gen,跳过)' : '')); |
|---|
| 270 | + console.log('[release] 1) 编译: "' + hx.file + '" ' + cliArgs.join(' ')); |
|---|
| 271 | + console.log('[release] 2) 产物 appid 回写为 ' + env.APPID + ' → ' + DIST_CONFIG); |
|---|
| 272 | + if (!noOpen) console.log('[release] 3) 打开: ' + openCmd); |
|---|
| 273 | + console.log('[release] --dry-run,未执行'); |
|---|
| 274 | + return; |
|---|
| 275 | + } |
|---|
| 276 | + |
|---|
| 277 | + // 1) 生成 manifest.json(HBuilderX 的编译输入,模板:manifest.json.demo) |
|---|
| 278 | + if (noGen) { |
|---|
| 279 | + console.log('[release] 跳过 manifest.json 生成(--no-gen)'); |
|---|
| 280 | + } else { |
|---|
| 281 | + manifest.generate(env); |
|---|
| 282 | + } |
|---|
| 283 | + |
|---|
| 284 | + // 2) 发行编译(产物:unpackage/dist/build/mp-weixin) |
|---|
| 285 | + console.log('[release] 开始发行编译,日志见 HBuilderX 控制台…'); |
|---|
| 286 | + const build = spawnSync(hx.file, cliArgs, { cwd: ROOT, stdio: 'inherit' }); |
|---|
| 287 | + if (build.error) fail('调用 cli.exe 失败:' + build.error.message); |
|---|
| 288 | + if (build.status !== 0) fail('cli.exe 退出码 ' + build.status + ',请查看 HBuilderX 控制台输出'); |
|---|
| 289 | + |
|---|
| 290 | + // 3) 校正产物 appid(正常应与 manifest 一致,这里兜底保证) |
|---|
| 291 | + if (!fs.existsSync(DIST_CONFIG)) { |
|---|
| 292 | + fail('发行产物缺少 project.config.json:' + DIST_CONFIG + '\n 请确认 HBuilderX 编译是否成功'); |
|---|
| 293 | + } |
|---|
| 294 | + const before = readAppId(DIST_CONFIG); |
|---|
| 295 | + const after = writeAppId(DIST_CONFIG, env.APPID); |
|---|
| 296 | + if (!after) fail('回写产物 appid 失败:' + DIST_CONFIG); |
|---|
| 297 | + console.log('[release] 产物 appid : ' + before + (before === after ? '(无需调整)' : ' → ' + after)); |
|---|
| 298 | + |
|---|
| 299 | + // 4) 用微信开发者工具打开产物(首次需在「设置-安全设置」中开启服务端口) |
|---|
| 300 | + if (!noOpen) { |
|---|
| 301 | + console.log('[release] 打开微信开发者工具…'); |
|---|
| 302 | + const open = spawnSync(openCmd, { cwd: ROOT, stdio: 'inherit', shell: true }); |
|---|
| 303 | + if (open.error || open.status !== 0) { |
|---|
| 304 | + console.log('[release] 自动打开失败(常见原因:未开启服务端口、微信开发者工具版本较旧)'); |
|---|
| 305 | + console.log('[release] 请手动打开微信开发者工具 → 导入项目 → 选择目录:' + DIST_DIR); |
|---|
| 306 | + } |
|---|
| 307 | + } |
|---|
| 308 | + |
|---|
| 309 | + console.log('[release] 完成。微信开发者工具中的项目 appid = ' + after + ',包内会自动使用 ' + env.HTTP_REQUEST_URL); |
|---|
| 310 | +} |
|---|
| 311 | + |
|---|
| 312 | +main(); |
|---|
| .. | .. |
|---|
| 1 | +/** |
|---|
| 2 | + * 环境切换:按 config/env/<env>.js 从模板生成 manifest.json |
|---|
| 3 | + * |
|---|
| 4 | + * 用法: |
|---|
| 5 | + * npm run env:sit 等价于 node scripts/set-env.js sit |
|---|
| 6 | + * npm run env:prod 等价于 node scripts/set-env.js prod |
|---|
| 7 | + * |
|---|
| 8 | + * 说明: |
|---|
| 9 | + * manifest.json 是 HBuilderX 的编译输入(appid 属于编译期值,无法像接口地址那样运行时切换), |
|---|
| 10 | + * 但它含环境相关 appid,不适合直接提交,所以: |
|---|
| 11 | + * manifest.json.demo(入库模板) --生成--> manifest.json(已 .gitignore) |
|---|
| 12 | + * 生成后必须重新编译一次,并在微信开发者工具中重新打开项目(appid 变更需重开)。 |
|---|
| 13 | + * |
|---|
| 14 | + * 如需在 HBuilderX 里直接编译/运行调试,先跑一次本脚本; |
|---|
| 15 | + * 走命令行发行则直接用 scripts/publish.js(内部会自动生成)。 |
|---|
| 16 | + * |
|---|
| 17 | + * appid 数据源唯一:config/env/<env>.js 中的 UNI_APPID(DCloud 标识)、APPID(小程序 appid) |
|---|
| 18 | + */ |
|---|
| 19 | +const fs = require('fs'); |
|---|
| 20 | +const path = require('path'); |
|---|
| 21 | +const manifest = require('./manifest'); |
|---|
| 22 | + |
|---|
| 23 | +const ROOT = path.resolve(__dirname, '..'); |
|---|
| 24 | +const ENV_DIR = path.join(ROOT, 'config', 'env'); |
|---|
| 25 | + |
|---|
| 26 | +function fail(msg) { |
|---|
| 27 | + console.error('[set-env] ' + msg); |
|---|
| 28 | + process.exit(1); |
|---|
| 29 | +} |
|---|
| 30 | + |
|---|
| 31 | +function main() { |
|---|
| 32 | + const envName = (process.argv[2] || '').trim(); |
|---|
| 33 | + const available = fs.existsSync(ENV_DIR) |
|---|
| 34 | + ? fs.readdirSync(ENV_DIR).filter(f => f.endsWith('.js') && f !== 'index.js').map(f => f.replace(/\.js$/, '')) |
|---|
| 35 | + : []; |
|---|
| 36 | + |
|---|
| 37 | + if (!envName) { |
|---|
| 38 | + fail('请指定环境名,可选:' + available.join(' / ') + '\n 例:npm run env:sit'); |
|---|
| 39 | + } |
|---|
| 40 | + if (available.indexOf(envName) < 0) { |
|---|
| 41 | + fail('未找到 config/env/' + envName + '.js,可选:' + available.join(' / ')); |
|---|
| 42 | + } |
|---|
| 43 | + |
|---|
| 44 | + const env = require(path.join(ENV_DIR, envName + '.js')); |
|---|
| 45 | + if (!env.UNI_APPID || !env.APPID) { |
|---|
| 46 | + fail('config/env/' + envName + '.js 缺少 UNI_APPID 或 APPID'); |
|---|
| 47 | + } |
|---|
| 48 | + |
|---|
| 49 | + console.log('[set-env] 切换到 ' + envName + '(' + env.ENV_NAME + ')'); |
|---|
| 50 | + console.log(' DCloud appid : ' + env.UNI_APPID); |
|---|
| 51 | + console.log(' 小程序 appid : ' + env.APPID); |
|---|
| 52 | + console.log(' 接口地址 : ' + env.HTTP_REQUEST_URL); |
|---|
| 53 | + |
|---|
| 54 | + const res = manifest.generate(env); |
|---|
| 55 | + |
|---|
| 56 | + console.log(res.changed |
|---|
| 57 | + ? '[set-env] 完成,请在 HBuilderX 中重新编译一次,并在微信开发者工具中重新打开项目' |
|---|
| 58 | + : '[set-env] 无需改动,当前 manifest.json 已是 ' + envName + ' 环境'); |
|---|
| 59 | +} |
|---|
| 60 | + |
|---|
| 61 | +main(); |
|---|
| .. | .. |
|---|
| 1 | +@echo off |
|---|
| 2 | +chcp 65001 >nul |
|---|
| 3 | +cd /d "%~dp0" |
|---|
| 4 | +node "scripts\set-env.js" prod |
|---|
| 5 | +if errorlevel 1 ( |
|---|
| 6 | + echo. |
|---|
| 7 | + echo [FAILED] switch to PROD failed - see output above |
|---|
| 8 | +) else ( |
|---|
| 9 | + echo. |
|---|
| 10 | + echo [OK] PROD manifest generated. Recompile in HBuilderX, then reopen the project in WeChat DevTools. |
|---|
| 11 | +) |
|---|
| 12 | +pause |
|---|
| .. | .. |
|---|
| 1 | +@echo off |
|---|
| 2 | +chcp 65001 >nul |
|---|
| 3 | +cd /d "%~dp0" |
|---|
| 4 | +node "scripts\set-env.js" sit |
|---|
| 5 | +if errorlevel 1 ( |
|---|
| 6 | + echo. |
|---|
| 7 | + echo [FAILED] switch to SIT failed - see output above |
|---|
| 8 | +) else ( |
|---|
| 9 | + echo. |
|---|
| 10 | + echo [OK] SIT manifest generated. Recompile in HBuilderX, then reopen the project in WeChat DevTools. |
|---|
| 11 | +) |
|---|
| 12 | +pause |
|---|
deleted file mode 100644| .. | .. |
|---|
| 1 | | -/*** |
|---|
| 2 | | -需要修改的配置文件 |
|---|
| 3 | | -***/ |
|---|
| 4 | | - |
|---|
| 5 | | ------app/manifest.json-------- |
|---|
| 6 | | - appid: wx15d029cdf2e92a9f |
|---|
| 7 | | - |
|---|
| 8 | | - |
|---|
| 9 | | ------app/config/app.js------- |
|---|
| 10 | | - |
|---|
| 11 | | - HTTP_REQUEST_URL:'https://acmall.bluedeer.com.cn/crmeb/front', |
|---|
| 12 | | - HTTP_ADMIN_URL:'https://acmall.bluedeer.com.cn/crmeb/admin', |
|---|
| 13 | | - APPID:'wx15d029cdf2e92a9f', |
|---|
| 14 | | - GDP_ACCOUNT_ID:'8672f93377575ace', |
|---|
| 15 | | - GDP_DATASOURCE_ID:'b01182b2d4f73e45', |
|---|
| 16 | | - GDP_SERVER_URL:'https://giocollect.aviva-cofco.com.cn', |
|---|
| 17 | | - GDP_DEBUG:true, |
|---|
| 18 | | - THIRD_APPID:'wxa83daa550324ead8', |
|---|
| 19 | | - |
|---|