移动端环境准备与启动
适用端:移动端 | 类型:操作
本页把移动端从零跑起来:装什么、依赖什么、三条启动路径(H5 / 微信小程序 / App)各自怎么走、env/.env* 的叠加顺序,以及怎么确认真的跑通了(不是「dev server 起来了」,是「登录进去能看到工作台」)。
前置阅读:环境准备与启动(后端与数据库那半边,本页不重复)。
它解决什么问题
移动端与 Web 端共用同一个后端,所以「跑起来」的前提是后端已经在跑。差别在于移动端有三个目标平台、一套代码,而只有 H5 能在浏览器里直接验证——小程序要开发者工具、App 要 HBuilderX,这两条路本机都没实测过(见「未证实」)。
代码在哪
| 要看什么 | 文件 |
|---|---|
| 脚本与引擎约束 | package.json:24-98(scripts)、:3(engines: node>=20, pnpm>=9)、:4(packageManager: pnpm@10.10.0) |
| Vite 配置与插件链 | vite.config.ts(18 个插件,顺序敏感,见工程结构) |
| 环境变量 | env/.env、env/.env.development、env/.env.test、env/.env.production |
| 基础文件生成器 | scripts/create-base-files.js |
| 页面 / 应用清单的全局配置 | pages.config.ts、manifest.config.ts |
| dev server 端口 | env/.env:2(VITE_APP_PORT = 9000)→ vite.config.ts:229 |
核心机制
1. 只能用 pnpm,且 install 不能跳
"preinstall": "npx only-allow pnpm",
"prepare": "pnpm init-husky & pnpm init-baseFiles",
"init-baseFiles": "node ./scripts/create-base-files.js"用 npm / yarn 会在 preinstall 直接报错。prepare 用的是 &(并行)不是 &&——init-husky 与 init-baseFiles 互不阻塞,一个失败不影响另一个。
init-husky 里藏着一条 git init
package.json 的 init-husky = git init && husky。在本仓(已是 git 仓库)里 git init 是幂等空操作,但在别的目录跑 pnpm install 会凭空造出一个仓库。这是 unibest 骨架带过来的,不要照着改。
init-baseFiles 生成两个被 gitignore 的必需文件(.gitignore:42-43):
| 生成物 | 内容 | 为什么必需 |
|---|---|---|
src/manifest.json | 空对象占位 | uni 编译期强依赖,缺了直接起不来 |
src/pages.json | 只含 index / me 两页的最小占位 | 同上;真实内容由 UniPages 插件在 dev/build 时覆写 |
所以新克隆的正确顺序是 pnpm install(prepare 自动生成)→ pnpm dev。跳过 install 直接 dev 会失败。
🔴 只有三个脚本挂了 init-baseFiles 前置钩子
实测 package.json 里 pre* 钩子只有五个:preinstall、predev、predev:app、predev:mp、prepare。
也就是说 pnpm dev:h5 / dev:mp-weixin / dev:test / dev:prod 以及所有 build:* 都不会自动生成基础文件。新克隆后直接跑 pnpm dev:mp-weixin 会失败,而报错信息不会告诉你是 pages.json 缺失。
对策:新克隆先跑一次 pnpm init-baseFiles(或 pnpm dev,它有 predev 钩子),再切到别的脚本。
2. 环境变量的叠加顺序
Vite 的 loadEnv(mode, 'env')(vite.config.ts:70)按 .env → .env.${mode} 合并,后者覆盖前者。env/ 下只有 4 个文件:
| 文件 | 什么时候参与 |
|---|---|
.env | 总是(所有 mode 的基线) |
.env.development | pnpm dev(mode = development) |
.env.test | pnpm dev:test / build:test |
.env.production | pnpm build:h5 等生产构建 |
关键变量(值取自本机,2026-10-25):
| 变量 | env/.env | .env.production | 谁读它 |
|---|---|---|---|
VITE_APP_TITLE | 启凌智搭 | — | manifest.config.ts + H5 的 transformIndexHtml(vite.config.ts:170) |
VITE_APP_PORT | 9000 | — | vite.config.ts:229 |
VITE_SERVER_BASEURL | http://localhost:48080/admin-api | https://demo.enlev.cn/admin-api | src/utils/index.ts:141-160 |
VITE_STATIC_BASEURL | ''(空) | '' | src/utils/download.ts:335-343 的 staticUrl() |
VITE_APP_PUBLIC_BASE | / | — | Vite base + manifest.config.ts:33-34 |
VITE_AUTH_MODE | 'double' | — | src/utils/index.ts:179 → 双 token 模式 |
VITE_APP_PROXY_ENABLE | false | — | vite.config.ts:231-240;false 时 proxy: undefined |
VITE_APP_CAPTCHA_ENABLE | false | — | 登录页滑块验证码 |
VITE_APP_TENANT_ENABLE | true | — | src/http/interceptor.ts:10 |
VITE_APP_DEFAULT_LOGIN_TENANT_ID | 1 | — | 登录页租户选择器默认值 |
VITE_COPY_NATIVE_RES_ENABLE | false | — | App 端原生插件资源复制 |
VITE_APP_API_ENCRYPT_ENABLE | true | — | 见下 ⚠️ |
VITE_UPLOAD_BASEURL 根本不存在
env/.env:23-25 自己写着「没有任何代码读取」。上传地址是拼出来的:${getEnvBaseUrl()}/infra/file/upload(src/api/infra/file/index.ts:78)。别去配它。
VITE_APP_API_ENCRYPT_ENABLE=true 但请求加密是休眠的
拦截器两侧都接了线:请求侧 src/http/interceptor.ts:83-95、响应侧 src/http/http.ts:84-94。但请求侧只在 options.isEncrypt 为真时才加密,而全仓没有任何 API 调用方传过 isEncrypt(只有类型声明 src/http/types.ts:13)。
所以真实状态是:响应解密是活的(由后端 X-Api-Encrypt 响应头驱动),请求加密是死代码。别把这个开关当成「已启用接口加密」去对外声明。
3. 三条启动路径
| 目标 | 命令 | 产物 / 打开方式 | 本机实测 |
|---|---|---|---|
| H5 | pnpm dev(= uni) | http://localhost:9000,host 0.0.0.0(vite.config.ts:227) | ✅ |
| 微信小程序 | pnpm dev:mp-weixin | dist/dev/mp-weixin,用微信开发者工具「导入项目」 | ❌ 未实测 |
| App | pnpm dev:app | dist/dev/app,用 HBuilderX 打开 | ❌ 未实测 |
非 H5 端的 dev 也是 build command
vite.config.ts:59-64 的注释写得很清楚:pnpm dev:mp-weixin 得到的是 command='build' + mode='development',不是 serve。所有按 command === 'serve' 分支的插件逻辑(包括 eruda 注入)在非 H5 端的 dev 下都不会生效。
CI 或不想被自动弹起开发者工具打断时,设 SKIP_OPEN_DEVTOOLS=true(vite.config.ts:193-194)。
怎么验证跑通了
四步,每步都有确定的判据。只看 dev server 打印 ready 是不够的。
第 1 步:后端在跑
curl -s -X POST http://localhost:48080/admin-api/system/auth/login \
-H 'Content-Type: application/json' -H 'tenant-id: 1' \
-d '{"username":"admin","password":"admin123"}'
# 期望:{"code":0,...,"data":{"userId":1,"accessToken":"...","refreshToken":"..."}}返回 code:0 且 data 里同时有 accessToken 与 refreshToken —— 后者是 VITE_AUTH_MODE='double' 的前提。
这条 curl 返回 code:0 不代表 mock token 没开
Bearer test1 那条免登录后门见坑位索引 A1。这里用的是真实账号口令,走的是正常登录。
第 2 步:导航树接口通
TOKEN=$(curl -s -X POST http://localhost:48080/admin-api/system/auth/login \
-H 'Content-Type: application/json' -H 'tenant-id: 1' \
-d '{"username":"admin","password":"admin123"}' \
| node -pe 'JSON.parse(require("fs").readFileSync(0)).data.accessToken')
curl -s "http://localhost:48080/admin-api/system/nav-node/tree?terminal=app" \
-H "Authorization: Bearer $TOKEN" -H 'tenant-id: 1'期望 code:0 且 data 是非空数组。本机实测返回 3 个顶层分组 / 19 个节点(库里 20 行存活,AI 大模型 组因零个存活子节点被剪枝)——这条剪枝规则见服务端驱动导航。
用 admin 测出来的是「不过滤」的树
admin 是超级管理员,权限判定走短路(GrantDecisionService#isSuperAdmin)。要验证 permissions 过滤真的生效,必须换一个非超管账号——这是坑位索引 D3 在移动端的同一件事。
第 3 步:H5 页面起来了
浏览器打开 http://localhost:9000,应当自动跳到登录页(/pages-core/auth/login),因为路由拦截器默认「需要登录」(src/router/config.ts:8-10)。
🔴 页面标题可能还是旧的——env 改了必须重启 dev server
loadEnv 只在 vite.config.ts:70 求值一次,VITE_APP_TITLE 被闭包捕获后交给 transformIndexHtml(:170)。Vite 不会因为 .env 变化重新求值 config。
本机 2026-10-25 实测:env/.env:1 已是 启凌智搭(提交 d434864f,2026-10-24 改的),但正在跑的 :9000 进程仍返回 <title>X评云</title>——那是改名前的值。进程比源文件旧,标题、base、端口都是启动那一刻定死的。
所以「标题不对」不是 bug,是该重启了。同理适用于任何 env/.env* 的改动。
第 4 步:登录进去,工作台有卡片
用 admin / admin123 登录(租户默认 id=1,其名称实测为「启凌智搭」)。登录后应当看到工作台,上面有 系统管理 / 基础设施 / 工作流程 三个分组的卡片。
浏览器 Network 面板里应当看到 /system/nav-node/tree?terminal=app 返回 200,请求打的是 localhost:48080(直连,没有 proxy 前缀)。
如果工作台是空的但登录成功:先查第 2 步的接口返回是不是空数组,再查当前账号是不是非超管且没有对应 permissions(见服务端驱动导航的过滤链)。
怎么扩展
- 加一个页面:移动端页面开发范式
- 加一个环境变量:写进
env/.env(基线)或对应 mode 文件,然后在src/env.d.ts:11-36的ImportMetaEnv里补类型声明——否则import.meta.env.XXX在type-check下报错 - 改端口 / 后端地址:只改
env/,改完重启 dev server
坑与注意
env/.env.local不存在,与 Web 仓不同(Web 仓的.env.local是纳入 git 跟踪的,见坑位索引 B2)。移动端只有 4 个 env 文件,别按 Web 的习惯去找.env.local。pnpm dev会占住终端并 watch,改pages.config.ts/src/tabbar/config.ts这类生成pages.json的配置必须重启——pages.json是产物,HMR 覆盖不到它。- eruda(移动端调试面板)只在 H5 + development 注入(
vite.config.ts:189-191)。dev:test/dev:prod的 mode 不是 development,所以没有 eruda,别以为是坏了。 - 小程序端要在开发者工具里勾「不校验合法域名」(详情 → 本地设置),否则
localhost:48080会被拦。 - 后端库名是
ainb-professional-init,不是上游的ruoyi-vue-pro,见坑位索引与环境准备与启动。
→ 本节是贴着上下文的提醒;按症状查的移动端全域清单见移动端坑位,跨端清单见坑位索引。
未证实
- 微信小程序与 App 两条路径本机从未实测:
dist/dev/mp-weixin的导入流程、miniprogram-ci上传、HBuilderX 打包、原生插件资源复制都只有源码依据。 pnpm install在全新克隆上的完整行为未实测(本机node_modules已存在)。prepare用&并行,init-husky失败时init-baseFiles是否仍会执行,取决于 shell 对&的处理,没有验证过。- 「新克隆不跑
init-baseFiles则type-check失败」是静态推断:src/utils/index.ts:3直接import { pages, subPackages } from '@/pages.json',而该文件不入库。没有真的在干净目录上复现。
