@newlifex/cube-vue 项目初始化
什么时候用
当用户需要创建一个新的前端项目,或在现有项目中引入 @newlifex/cube-vue 框架时使用。
架构说明
@newlifex/cube-vue 包发布名为 @newlifex/cube-vue,代码中 import 使用 @newlifex/cube-vue/... 路径(通过 pnpm 别名机制)。
支持两种项目结构:
| 场景 | 说明 | 参考 |
|---|---|---|
| Monorepo 工作空间 | 项目放在 @newlifex/cube-vue 所在仓库内,通过 workspace 协议引用 | |
| 独立应用 | @newlifex/cube-vue 作为 npm 包安装 | 后续 npm 发布后 |
本技能以 monorepo 场景为主,独立应用场景仅调整依赖声明方式(npm install @newlifex/cube-vue + 别名)。
初始化步骤
1. 检查环境
- Node.js >= 24
- pnpm >= 9
- 根目录需配置
pnpm-workspace.yaml(monorepo 场景)
2. 注册 workspace(monorepo 场景)
在根目录 pnpm-workspace.yaml 中添加新项目:
packages:
- '新项目目录'
- 'Cube/NewLife.Cube.Vue/web' # 如已有则跳过
3. 创建项目入口文件
在项目 src/ 目录下创建 main.ts:
// src/main.ts
import { initApp } from '@newlifex/cube-vue/core/initApp';
import '@newlifex/cube-vue/core/global.css';
initApp();
4. 创建 index.html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>应用名称</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.ts"></script>
</body>
</html>
5. 创建配置文件
在项目根目录创建 configs/ 目录:
configs/config.ts - 通用配置:
import type { EnvConfig } from '@newlifex/cube-vue/core/configure/types';
export const config: EnvConfig = {
base: {
title: '应用名称',
footer: '版权所有 © 2026',
},
ui: {
theme: {
primaryColor: '#1890ff',
},
},
auth: {
oauthUrl: '/login',
getUserInfoAxiosConfig: {
url: '/Admin/User/Info',
method: 'GET',
},
},
};
configs/config.development.ts - 开发环境配置:
import type { EnvConfig } from '@newlifex/cube-vue/core/configure/types';
export const config: EnvConfig = {
request: {
baseUrl: import.meta.env.VITE_APP_BASE_API || 'http://localhost:5000',
timeout: 30000,
},
};
configs/config.test.ts - 测试环境配置:
import type { EnvConfig } from '@newlifex/cube-vue/core/configure/types';
export const config: EnvConfig = {
request: {
baseUrl: import.meta.env.VITE_APP_BASE_API || '/api',
timeout: 30000,
},
};
configs/config.production.ts - 生产环境配置(支持容器部署替换):
import type { EnvConfig } from '@newlifex/cube-vue/core/configure/types';
export const config: EnvConfig = {
request: {
baseUrl: '${BUILD_REQUEST_BASE_URL}',
timeout: 30000,
},
};
configs/microAppConfig.json - 微应用配置(空数组即可):
[]
6. 创建环境变量文件
.env - 通用环境变量:
VITE_APP_TITLE=应用名称
.env.development - 开发环境变量:
VITE_APP_ENV=development
VITE_APP_TITLE=应用名称 - 开发环境
VITE_APP_BASE_API=http://localhost:5000
.env.test - 测试环境变量(需 vite --mode test):
VITE_APP_ENV=test
VITE_APP_TITLE=应用名称 - 测试环境
VITE_APP_BASE_API=http://test-api.example.com
.env.production - 生产环境变量:
VITE_APP_ENV=production
VITE_APP_TITLE=应用名称
# 生产环境 API URL 由容器部署时通过 BUILD_REQUEST_BASE_URL 注入
VITE_APP_BASE_API=/api
7. 配置 Vite
vite.config.ts(monorepo 场景——不需要 alias):
import { fileURLToPath, URL } from 'node:url';
import { defineConfig, loadEnv } from 'vite';
import vue from '@vitejs/plugin-vue';
import vueJsx from '@vitejs/plugin-vue-jsx';
import cubeFront from '@newlifex/cube-vue/core/plugin';
export default defineConfig(({ mode }) => {
const env = loadEnv(mode, process.cwd(), '');
return {
plugins: [
vue(),
vueJsx(),
cubeFront(),
],
resolve: {
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url)),
// ⚠️ 必须加 '@newlifex/cube-vue' 别名指向 Cube web 源码目录:
// 包内代码用包名自引用(@newlifex/cube-vue/core/...)时,pnpm workspace 链接
// 只存在于应用侧 node_modules,包内部解析不到,需别名兜底。
// 例:'@newlifex/cube-vue': fileURLToPath(new URL('../Cube/NewLife.Cube/NewLife.Cube.Vue/web', import.meta.url))
},
},
server: {
port: 5188,
host: '0.0.0.0',
proxy: {
'/api': {
target: env.VITE_APP_BASE_API || 'http://localhost:5000',
changeOrigin: true,
},
},
},
build: {
outDir: 'dist',
sourcemap: true,
target: 'es2022',
},
};
});
为什么不需要
@newlifex/cube-vue别名? 因为 pnpm workspace 协议(workspace:*)+ 别名("@newlifex/cube-vue": "workspace:@newlifex/cube-vue@*")会自动创建node_modules/@newlifex/cube-vue的 symlink,Node.js 模块解析就能直接找到。
8. 配置 TypeScript
⚠️ 重要:必须添加
@newlifex/cube-vue/core/client类型声明,否则initApp()等 API 会报类型错误。
tsconfig.json:
{
"compilerOptions": {
"target": "ES2020",
"useDefineForClassFields": true,
"module": "ESNext",
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"skipLibCheck": true,
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "preserve",
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true,
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"]
},
"types": ["@newlifex/cube-vue/core/client"]
},
"include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"],
"references": [{ "path": "./tsconfig.node.json" }]
}
说明:
"types": ["@newlifex/cube-vue/core/client"]会自动加载框架的类型声明文件,确保initApp()、useUserStore()等 API 的类型提示正常工作。
tsconfig.node.json:
{
"compilerOptions": {
"composite": true,
"skipLibCheck": true,
"module": "ESNext",
"moduleResolution": "bundler",
"allowSyntheticDefaultImports": true,
"strict": true
},
"include": ["vite.config.ts"]
}
9. 配置 package.json
Monorepo 场景 — 依赖声明:
使用 一条 workspace 依赖:
{
"dependencies": {
"@newlifex/cube-vue": "workspace:*",
"dayjs": "^1.11.0"
},
"devDependencies": {
"@vitejs/plugin-vue": "^5.2.0",
"@vitejs/plugin-vue-jsx": "^4.1.0",
"cross-env": "^7.0.3",
"typescript": "~5.8.0",
"vite": "^6.2.0",
"vue-tsc": "^2.2.0"
}
}
条目解释:
| 依赖 | 作用 |
|---|---|
"@newlifex/cube-vue": "workspace:*" |
正式包名,monorepo 内通过 workspace 协议引用本地库 |
| vue/pinia/element-plus/vue-router | 不必须声明(pnpm auto-install-peers 自动装),但建议显式锁定版本 |
为什么不需要
@newlifex/cube-vue: "link:../../@newlifex/cube-vue"?因为 monorepo workspace 模式下,workspace:*等价且语义更清晰,link:路径已被淘汰。
10. package.json 完整示例
{
"name": "your-app",
"version": "0.1.0",
"private": true,
"type": "module",
"scripts": {
"dev": "cross-env NODE_ENV=development vite",
"dev:test": "cross-env NODE_ENV=test vite --mode test",
"build": "vue-tsc -b && vite build",
"build:test": "vue-tsc -b && vite build --mode test",
"preview": "vite preview",
"typecheck": "vue-tsc --noEmit"
},
"dependencies": {
"@newlifex/cube-vue": "workspace:*",
"element-plus": "^2.9.0",
"pinia": "^3.0.0",
"vue": "^3.5.0",
"vue-router": "^4.5.0",
"dayjs": "^1.11.0"
},
"devDependencies": {
"@vitejs/plugin-vue": "^5.2.0",
"@vitejs/plugin-vue-jsx": "^4.1.0",
"cross-env": "^7.0.3",
"typescript": "~5.8.0",
"vite": "^6.2.0",
"vue-tsc": "^2.2.0"
}
}
11. 创建 Docker 配置
在项目根目录创建 docker/ 目录:
docker/Dockerfile:
# 构建阶段
FROM node:20-alpine AS builder
WORKDIR /app
# 安装 pnpm
RUN npm install -g pnpm
# 安装依赖(monorepo 场景需复制根目录 lock)
COPY package.json pnpm-lock.yaml ./
RUN pnpm install --frozen-lockfile
# 复制源代码
COPY . .
# 构建
RUN pnpm run build
# 生产阶段
FROM nginx:alpine
COPY --from=builder /app/dist /usr/share/nginx/html
COPY docker/enterpoint.sh /docker-entrypoint.d/enterpoint.sh
RUN chmod +x /docker-entrypoint.d/enterpoint.sh
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
docker/enterpoint.sh:
#!/bin/bash
set +e
set -x
echo "🚀 Frontend Container Starting..."
# 替换 BUILD_ 开头的占位符
sed -i "s|BUILD_REQUEST_BASE_URL|${BUILD_REQUEST_BASE_URL:-/}|g" /usr/share/nginx/html/index.html
echo "✅ 配置完成"
echo "🎉 启动 Nginx..."
exec "$@"
配置优先级(从低到高)
defaultConfig → configs/config.ts → configs/config.{env}.ts → window._CUBE_CONFIG_ → BUILD_ 占位符
BUILD_ 占位符机制
用于生产环境容器部署时动态注入配置:
- 在
configs/config.production.ts中使用baseUrl: '${BUILD_REQUEST_BASE_URL}' - Vite 构建时会自动将
BUILD_XXX占位符注入到dist/index.html - 容器启动时,
docker/enterpoint.sh通过 sed 替换占位符
生成的 html 内联脚本格式:
<script>
let cubeConfig = window._CUBE_CONFIG_ || (window._CUBE_CONFIG_={});
let request = cubeConfig["request"] || (cubeConfig["request"]={});
request["baseUrl"] = "BUILD_REQUEST_BASE_URL";
</script>
框架提供的能力
| 功能 | 说明 | 如何使用 |
|---|---|---|
| 布局系统 | RootLayout 统一接管菜单/登录/标签页,布局组件仅提供结构外壳 | 通过 registerLayout(option, setAsCurrent) 登记(见 cube-layout 技能),禁止旧版 provide(LayoutKey) |
| 状态管理 | UserStore 用户状态,MenuStore 菜单状态 | useUserStore(), useMenuStore() |
| 路由系统 | 动态路由,由后端菜单自动生成 | 无需手写 vue-router 配置,页面文件按目录约定放置即可 |
| API请求 | 带 Token、401处理、错误提示的 Axios 封装 | import request from '@newlifex/cube-vue/core/utils/request' |
| 国际化 | Vue I18n,支持动态切换 | intl.get('key').d('默认值') |
| 页面覆盖 | Section 机制,可覆盖框架组件 | 在视图目录下创建 PascalCase(首字母大写)Vue 文件,见下方「页面视图目录约定」 |
| BUILD_ 配置注入 | 生产构建时自动注入到 html | 在 config.production.ts 使用 ${BUILD_XXX} |
验证初始化成功
开发环境验证:
pnpm dev启动- 访问
http://localhost:3000 - 检查登录页面正常渲染
生产构建验证:
pnpm build成功- 检查
dist/index.html是否包含 BUILD_ 占位符脚本
红线 / 禁止自行发挥
以下为历史踩坑固化的强制约束,落实时严格照办,禁止凭记忆或"想当然"自行发挥:
- 框架源码路径有层级,定位时别少写一层:本仓库中框架源码位于
Cube/NewLife.Cube/NewLife.Cube.Vue/web(注意是两层:NewLife.Cube目录内还有一层NewLife.Cube.Vue)。不同仓库层级可能不同,关键是先确认框架源码真实目录再引用——任何指向框架源码的路径、别名、@newlifex/cube-vue兜底 alias 都要与实际目录对齐,少写一层会导致文件找不到(MISSING)。 package.json不要重复声明@newlifex/cube-vue:monorepo 场景只用workspace:*一条依赖(见步骤 9/10),禁止再写第二处link:或重复键——重复键会让 pnpm 解析失败或产生不可预期的幽灵依赖。- dev 脚本统一用
cross-env:"dev": "cross-env NODE_ENV=development vite",不要裸写vite或直接NODE_ENV=development vite(跨平台兼容)。 - 视图目录必须含
apps/层级(独立宿主):若vite.config.ts在项目根目录(非apps/<name>/),页面必须放进apps/<app-name>/src/views/...,否则 Vite 插件按apps/*/src/views扫描,扫不到src/views/下的页面。详见「页面视图目录约定」。 - 不要创建被框架接管的孤儿文件:
initApp()内部已挂载框架core/App.vue,禁止在项目里再写一份src/App.vue(那是孤儿文件,不会生效且造成困惑)。src/main.ts只需initApp()+registerLayout(),不要手写createApp().mount()或new Router()。 - Docker 章节按需执行:仅在需要容器部署时才创建
docker/,不部署则跳过,不要无脑生成(用户明确暂不启用 docker)。
多语言配置(可选)
默认中文,如需多语言在 src/i18n/ 下配置:
src/i18n/index.ts- I18n 实例src/i18n/locales/- 语言文件目录
页面视图目录约定(重要)
框架通过 Vite 插件在构建期自动扫描视图目录、生成路由与 Section 覆盖。视图目录位置取决于项目结构:
| 项目结构 | vite.config.ts 位置 | 页面应放置目录 | 说明 |
|---|---|---|---|
| 子项目模式(monorepo 子应用) | <root>/apps/<name>/vite.config.ts |
apps/<name>/src/views/<area>/<controller>/index.vue |
插件按 apps/<name>/src/views 精确扫描 |
| 单体模式(根目录即应用) | <root>/vite.config.ts |
<root>/apps/<name>/src/views/...(必须含 apps/ 层级) |
插件遍历 root/apps/*/src/views,不会扫描 src/views/ |
| 框架内置应用 | 框架源码内 | @newlifex/cube-vue/core/apps/*/src/views |
仅框架自身使用 |
独立宿主常见坑:若项目是独立 Vue 应用(vite.config 在根目录、没有
apps/子目录),不要把页面放进src/views/—— 框架扫描不到。必须在根目录新建apps/<your-app-name>/src/views/...,让插件按单体模式扫描到。一个根目录下可以有多个apps/*,每个各自一份src/views。
页面文件命名(用于 Section 覆盖):
- 路由页:
views/<area>/<controller>/index.vue(小写目录 +index.vue) - Section 覆盖:
views/<area>/<controller>/<PascalCase>.vue(首字母大写的 Vue 文件名会被识别为 Section 覆盖组件)
详见 cube-add-page 技能。