fe-cli-starter 0.0.0-stage → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,3 +1,115 @@
1
- # Temporary Holding Version
1
+ # fe-cli-starter
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ 一套开箱即用的前端工程化 CLI 脚手架,帮助中小团队解决**新项目初始化繁琐、代码规范不统一、存量代码整改费时**的问题。
4
+
5
+ ## 解决什么痛点
6
+ - 新项目每次都要重新配 ESLint / Prettier / Stylelint / Commitlint,费时且容易漏
7
+ - 多个项目代码风格不一致,Review 成本高
8
+ - 存量老项目没有规范,想整改却无从下手
9
+
10
+ ## 功能
11
+ 1. `create`:交互式初始化 Vue3 + Vite 项目,可选启用 Stylelint
12
+ 2. 内置统一规范集:ESLint + Prettier + Stylelint + Commitlint(Git 提交信息校验)
13
+ 3. `scan`:扫描存量项目,输出规范问题清单
14
+ 4. `fix`:一键批量修复存量代码格式问题
15
+
16
+ ## 安装使用
17
+ ```bash
18
+ # 全局安装
19
+ npm install -g fe-cli-starter
20
+
21
+ # 交互式初始化新项目
22
+ fe create my-project
23
+
24
+ # 或跳过交互,直接指定选项(适合CI)
25
+ fe create my-project --no-stylelint
26
+
27
+ # 扫描存量项目规范问题
28
+ fe scan ./my-old-project
29
+
30
+ # 一键修复
31
+ fe fix ./my-old-project
32
+ ```
33
+
34
+ ## 实测输出
35
+ ```
36
+ $ fe scan ./demo-app
37
+ ▶ 扫描规范问题:./demo-app
38
+ [格式] 需要格式化: src/messy.js
39
+
40
+ src/messy.js
41
+ 1:8 warning 'a' is assigned a value but never used no-unused-vars
42
+ 4:1 warning Unexpected console statement no-console
43
+
44
+ ✘ 共发现 5 个规范问题,可执行 fe fix ./demo-app 尝试自动修复
45
+
46
+ $ fe fix ./demo-app
47
+ ▶ 一键修复:./demo-app
48
+ [格式] 已重新格式化 1 个文件
49
+ ✘ 仍有 4 个问题无法自动修复,需手动处理
50
+ ```
51
+
52
+ ## 内置规范
53
+ | 类别 | 工具 | 说明 |
54
+ | --- | --- | --- |
55
+ | JS 代码 | ESLint | 基于 Vue3 推荐规则扩展(no-console、no-unused-vars 等) |
56
+ | 格式化 | Prettier | 统一缩进、引号、分号等 |
57
+ | 样式 | Stylelint | CSS/SCSS/Less 规则 |
58
+ | 提交 | Commitlint | Git 提交信息格式校验 |
59
+
60
+ ## 生成项目的目录结构
61
+ ```
62
+ my-project/
63
+ ├── AGENTS.md # AI 编码助手 / 团队开发约定
64
+ ├── README.md # 项目说明(含项目名)
65
+ ├── docs/ # 项目文档目录(需求/接口/部署)
66
+ ├── public/ # 静态资源目录(构建时原样拷贝)
67
+ ├── eslint.config.mjs # ESLint 配置(ESLint 9 flat config)
68
+ ├── stylelint.config.mjs # 样式规范配置(可选启用)
69
+ ├── prettier.config.mjs # 格式化规则(含注释说明)
70
+ ├── .prettierignore
71
+ ├── vite.config.js # 别名 / 代理 / 按需导入 / 分包
72
+ ├── index.html
73
+ └── src/
74
+ ├── main.js # 挂载 pinia + router
75
+ ├── App.vue
76
+ ├── assets/README.md # 静态资源目录说明(图片/全局样式/字体)
77
+ ├── components/README.md # 公共组件目录说明(跨页面复用组件)
78
+ ├── router/index.js # 路由骨架
79
+ ├── stores/counter.js # Pinia store 示例
80
+ ├── utils/request.js # axios 封装(token 拦截 / 错误处理)
81
+ └── views/HomeView.vue # 示例页面
82
+ ```
83
+
84
+ ## 为什么配置文件是 `.mjs` 后缀?
85
+ 核心原因:**强制该文件按 ESM(`import/export`)解析,不受目标项目 package.json 的 `"type"` 字段影响。**
86
+
87
+ | 后缀 | Node 解析方式 |
88
+ | --- | --- |
89
+ | `.js` | 看 package.json 的 `"type"`:有 `"module"` 是 ESM,否则是 CommonJS |
90
+ | `.mjs` | 强制按 ESM 解析,任何项目通用 |
91
+ | `.cjs` | 强制按 CommonJS 解析 |
92
+
93
+ 具体到本脚手架:
94
+ 1. **ESLint 9 已废弃 `.eslintrc.*` 旧格式**,新版 flat config 官方推荐文件名就是 `eslint.config.mjs`;旧 JSON 格式不支持 `import` 插件、不能写注释,表达不了 flat config 的写法
95
+ 2. `.mjs` 保证配置在**任何项目里都能正确运行**:新项目(`"type": "module"`)没问题;老项目没有 `"type": "module"` 时,`.js` 会被按 CommonJS 解析,`import` 语法直接报错 `Cannot use import statement outside a module`——用 `.mjs` 就规避了这个坑。这对 `fe fix` 整改存量项目尤其重要
96
+ 3. `vite.config.js` 因为生成项目固定带 `"type": "module"`,用 `.js` 即可;Vite 同样支持 `.mjs`
97
+
98
+ ## 与 `npm create vue` 官方脚手架的区别
99
+ 官方模板解决"项目怎么跑起来";本工具解决"团队项目怎么统一规范、老项目怎么低成本整改"。核心价值在**规范集统一 + 存量项目扫描修复**,并预置团队全家桶:vue-router、pinia、element-plus(按需自动导入)、axios 封装、dayjs、echarts(独立分包)、@vueuse/core、lodash-es、sass。
100
+
101
+ ## 适合谁用
102
+ - 2~20人的前端团队,想统一项目规范
103
+ - 经常新起项目,希望初始化即规范
104
+ - 手上有老项目需要做规范整改
105
+
106
+ ## 技术实现
107
+ - Node.js + Commander:命令注册
108
+ - Inquirer:交互式问答
109
+ - EJS:项目模板渲染
110
+ - 产物支持对存量项目的扫描与批量修复
111
+
112
+ > 注:本项目为个人开源演示实现,可按团队需求定制扩展。
113
+
114
+ ## License
115
+ MIT
package/bin/fe.js ADDED
@@ -0,0 +1,24 @@
1
+ #!/usr/bin/env node
2
+ import { program } from 'commander';
3
+ import { create } from '../commands/create.js';
4
+ import { scan, fix } from '../commands/index.js';
5
+
6
+ program.name('fe').description('前端工程化脚手架:初始化项目 / 扫描规范问题 / 一键修复').version('1.0.0');
7
+
8
+ program
9
+ .command('create <name>')
10
+ .description('交互式初始化一个 Vue3 + Vite 项目')
11
+ .option('--no-stylelint', '不启用 Stylelint')
12
+ .action((name, opts) => create(name, { useStylelint: opts.stylelint }));
13
+
14
+ program
15
+ .command('scan [dir]')
16
+ .description('扫描存量项目的规范问题(默认当前目录)')
17
+ .action(scan);
18
+
19
+ program
20
+ .command('fix [dir]')
21
+ .description('一键修复存量代码格式问题(默认当前目录)')
22
+ .action(fix);
23
+
24
+ program.parse(process.argv);
@@ -0,0 +1,62 @@
1
+ import { confirm } from '@inquirer/prompts';
2
+ import fs from 'node:fs';
3
+ import path from 'node:path';
4
+ import { fileURLToPath } from 'node:url';
5
+ import ejs from 'ejs';
6
+
7
+ const TEMPLATE_DIR = fileURLToPath(new URL('../templates/vue3-vite/', import.meta.url));
8
+ const CONFIG_DIR = fileURLToPath(new URL('../config/', import.meta.url));
9
+
10
+ export async function create(name, preset = {}) {
11
+ const targetDir = path.resolve(process.cwd(), name);
12
+ if (fs.existsSync(targetDir)) {
13
+ console.error(`✘ 目录已存在:${targetDir}`);
14
+ process.exit(1);
15
+ }
16
+
17
+ const useStylelint =
18
+ preset.useStylelint ?? (await confirm({ message: '是否启用 Stylelint(样式规范)?', default: true }));
19
+
20
+ console.log(`\n▶ 正在生成项目 ${name} ...`);
21
+
22
+ // 1. 渲染并拷贝模板文件(递归)
23
+ renderDir(TEMPLATE_DIR, targetDir, { projectName: name, useStylelint });
24
+
25
+ // 2. 拷贝规范配置(按选项)
26
+ copyIf(path.join(CONFIG_DIR, 'eslint.config.mjs'), targetDir);
27
+ copyIf(path.join(CONFIG_DIR, 'prettier.config.mjs'), targetDir);
28
+ copyIf(path.join(CONFIG_DIR, '.prettierignore'), targetDir);
29
+ if (useStylelint) {
30
+ copyIf(path.join(CONFIG_DIR, 'stylelint.config.mjs'), targetDir);
31
+ copyIf(path.join(CONFIG_DIR, '.stylelintignore'), targetDir);
32
+ }
33
+
34
+ console.log(`✔ 项目已生成:${targetDir}\n`);
35
+ console.log('下一步:');
36
+ console.log(` cd ${name}`);
37
+ console.log(' npm install');
38
+ console.log(' npm run dev');
39
+ }
40
+
41
+ function renderDir(srcDir, destDir, data) {
42
+ fs.mkdirSync(destDir, { recursive: true });
43
+ for (const entry of fs.readdirSync(srcDir, { withFileTypes: true })) {
44
+ const src = path.join(srcDir, entry.name);
45
+ const dest = path.join(destDir, entry.name);
46
+ if (entry.isDirectory()) {
47
+ renderDir(src, dest, data);
48
+ } else if (entry.name.endsWith('.ejs')) {
49
+ const outName = entry.name.replace(/\.ejs$/, '');
50
+ const dest = path.join(destDir, outName);
51
+ const tpl = fs.readFileSync(src, 'utf8');
52
+ fs.writeFileSync(dest, ejs.render(tpl, data));
53
+ } else {
54
+ const outName = entry.name.startsWith('_') ? `.${entry.name.slice(1)}` : entry.name;
55
+ fs.copyFileSync(src, path.join(destDir, outName));
56
+ }
57
+ }
58
+ }
59
+
60
+ function copyIf(src, destDir) {
61
+ if (fs.existsSync(src)) fs.copyFileSync(src, path.join(destDir, path.basename(src)));
62
+ }
@@ -0,0 +1,29 @@
1
+ import { scanDir } from '../lib/lint.js';
2
+
3
+ export async function scan(dir = '.') {
4
+ await runLint(dir, false);
5
+ }
6
+
7
+ export async function fix(dir = '.') {
8
+ await runLint(dir, true);
9
+ }
10
+
11
+ async function runLint(dir, fixMode) {
12
+ const target = dir ? dir : '.';
13
+ if (fixMode) {
14
+ console.log(`▶ 一键修复:${target}`);
15
+ } else {
16
+ console.log(`▶ 扫描规范问题:${target}`);
17
+ }
18
+ const results = await scanDir(target, fixMode);
19
+ let total = 0;
20
+ for (const r of results) {
21
+ if (r.output) console.log(r.output.trimEnd());
22
+ total += r.count;
23
+ }
24
+ if (fixMode) {
25
+ console.log(total > 0 ? `\n✘ 仍有 ${total} 个问题无法自动修复,需手动处理` : '\n✔ 修复完成,全部通过');
26
+ } else {
27
+ console.log(total > 0 ? `\n✘ 共发现 ${total} 个规范问题,可执行 fe fix ${target} 尝试自动修复` : '\n✔ 未发现规范问题');
28
+ }
29
+ }
@@ -0,0 +1,3 @@
1
+ node_modules
2
+ dist
3
+ *.min.*
@@ -0,0 +1,2 @@
1
+ node_modules
2
+ dist
@@ -0,0 +1,21 @@
1
+ import js from '@eslint/js';
2
+ import pluginVue from 'eslint-plugin-vue';
3
+
4
+ export default [
5
+ js.configs.recommended,
6
+ ...pluginVue.configs['flat/recommended'],
7
+ {
8
+ languageOptions: {
9
+ ecmaVersion: 'latest',
10
+ sourceType: 'module',
11
+ },
12
+ rules: {
13
+ 'no-console': 'warn',
14
+ 'no-unused-vars': ['warn', { argsIgnorePattern: '^_' }],
15
+ 'vue/multi-word-component-names': 'off',
16
+ },
17
+ },
18
+ {
19
+ ignores: ['node_modules/**', 'dist/**'],
20
+ },
21
+ ];
@@ -0,0 +1,14 @@
1
+ export default {
2
+ // 行尾加分号
3
+ semi: true,
4
+ // 使用单引号(JS/TS/Vue)
5
+ singleQuote: true,
6
+ // 每行最大宽度 100,超出自动换行
7
+ printWidth: 100,
8
+ // 对象/数组最后一项也加逗号,便于 git diff 干净
9
+ trailingComma: 'all',
10
+ // 缩进 2 空格
11
+ tabWidth: 2,
12
+ // 统一换行符为 LF,避免 Windows/Mac 协作时文件被整体改动
13
+ endOfLine: 'lf',
14
+ };
@@ -0,0 +1,10 @@
1
+ import stylelintConfigStandard from 'stylelint-config-standard';
2
+
3
+ export default {
4
+ extends: [stylelintConfigStandard],
5
+ rules: {
6
+ 'selector-class-pattern': null,
7
+ 'custom-property-empty-line-before': null,
8
+ },
9
+ ignoreFiles: ['node_modules/**', 'dist/**'],
10
+ };
package/lib/lint.js ADDED
@@ -0,0 +1,72 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { pathToFileURL, fileURLToPath } from 'node:url';
4
+ import { globSync } from 'glob';
5
+ import * as prettier from 'prettier';
6
+
7
+ const CONFIG_DIR = path.dirname(fileURLToPath(new URL('../config/eslint.config.mjs', import.meta.url)));
8
+ const IGNORE_PATTERNS = ['**/node_modules/**', '**/dist/**', '**/.git/**', '**/*.min.*'];
9
+
10
+ export async function scanDir(target, fixMode) {
11
+ const absTarget = path.resolve(target);
12
+ const patterns = ['**/*.{js,mjs,ts,vue,jsx,tsx}'];
13
+
14
+ const eslint = await import('eslint');
15
+ const { ESLint } = eslint;
16
+
17
+ const results = [];
18
+
19
+ // 1. ESLint(格式之外的问题)
20
+ try {
21
+ const eslintrc = new ESLint({
22
+ cwd: absTarget,
23
+ fix: fixMode,
24
+ overrideConfigFile: path.join(CONFIG_DIR, 'eslint.config.mjs'),
25
+ ignore: true,
26
+ });
27
+ const lintResults = await eslintrc.lintFiles(patterns);
28
+ if (fixMode) await ESLint.outputFixes(lintResults);
29
+ const formatter = await eslintrc.loadFormatter('stylish');
30
+ const count = lintResults.reduce(
31
+ (sum, r) => sum + r.errorCount + r.warningCount,
32
+ 0
33
+ );
34
+ const formatted = formatter.format(lintResults.filter((r) => r.messages.length));
35
+ results.push({ count, output: formatted });
36
+ } catch (e) {
37
+ results.push({ count: 0, output: `ESLint 扫描跳过:${e.message.split('\n')[0]}\n` });
38
+ }
39
+
40
+ // 2. Prettier 格式检查/修复
41
+ try {
42
+ const files = globSync(patterns.map((p) => path.join(absTarget, p)), {
43
+ ignore: IGNORE_PATTERNS,
44
+ nodir: true,
45
+ });
46
+ let changed = 0;
47
+ for (const file of files) {
48
+ const content = fs.readFileSync(file, 'utf8');
49
+ const options = await prettier.resolveConfig(file);
50
+ const formatted = await prettier.format(content, {
51
+ ...options,
52
+ filepath: file,
53
+ });
54
+ if (formatted !== content) {
55
+ if (fixMode) {
56
+ fs.writeFileSync(file, formatted);
57
+ } else {
58
+ console.log(` [格式] 需要格式化: ${path.relative(absTarget, file)}`);
59
+ }
60
+ changed++;
61
+ }
62
+ }
63
+ results.push({
64
+ count: fixMode ? 0 : changed,
65
+ output: fixMode ? ` [格式] 已重新格式化 ${changed} 个文件\n` : '',
66
+ });
67
+ } catch (e) {
68
+ results.push({ count: 0, output: `Prettier 扫描跳过:${e.message}\n` });
69
+ }
70
+
71
+ return results;
72
+ }
package/package.json CHANGED
@@ -1,6 +1,39 @@
1
1
  {
2
2
  "name": "fe-cli-starter",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "1.0.0",
4
+ "description": "开箱即用的前端工程化 CLI 脚手架:初始化 Vue3+Vite 项目、扫描与一键修复存量代码规范问题",
5
+ "bin": {
6
+ "fe": "./bin/fe.js"
7
+ },
8
+ "type": "module",
9
+ "files": [
10
+ "bin",
11
+ "commands",
12
+ "lib",
13
+ "templates",
14
+ "config"
15
+ ],
16
+ "scripts": {
17
+ "test": "node bin/fe.js --help"
18
+ },
19
+ "keywords": [
20
+ "cli",
21
+ "scaffold",
22
+ "eslint",
23
+ "prettier",
24
+ "vue3"
25
+ ],
26
+ "license": "MIT",
27
+ "dependencies": {
28
+ "@inquirer/prompts": "^7.0.0",
29
+ "commander": "^12.0.0",
30
+ "ejs": "^3.1.10",
31
+ "prettier": "^3.3.0",
32
+ "eslint": "^9.0.0",
33
+ "stylelint": "^16.0.0",
34
+ "semver": "^7.6.0",
35
+ "glob": "^11.0.0",
36
+ "eslint-plugin-vue": "^9.0.0",
37
+ "@eslint/js": "^9.0.0"
38
+ }
39
+ }
@@ -0,0 +1,32 @@
1
+ # AGENTS.md — AI 编码助手工作约定
2
+
3
+ 本文件供 AI 编码助手(如 Trae、Cursor、Copilot 等)在本项目工作时遵循,也作为团队开发约定的一部分。
4
+
5
+ ## 项目技术栈
6
+ - Vue 3(组合式 API `<script setup>`)+ Vite
7
+ - 状态管理:Pinia(`src/stores/`)
8
+ - 路由:Vue Router(`src/router/`)
9
+ - UI 库:Element Plus(unplugin-vue-components 按需自动注册,无需手动 import 组件)
10
+ - 请求:axios(统一走 `src/utils/request.js`,不要在组件里直接 new axios 实例)
11
+ - 日期:dayjs;图表:echarts;工具:@vueuse/core、lodash-es
12
+
13
+ ## 常用命令
14
+ ```bash
15
+ npm run dev # 本地开发
16
+ npm run build # 生产构建
17
+ npm run lint # 代码规范检查并自动修复
18
+ ```
19
+
20
+ ## 编码约定
21
+ 1. 页面放 `src/views/`,公共组件放 `src/components/`,组合式函数放 `src/composables/`
22
+ 2. 路由懒加载:`component: () => import('@/views/Xxx.vue')`
23
+ 3. 路径别名统一用 `@/`(指向 `src/`)
24
+ 4. 接口请求统一走 `src/utils/request.js`,baseURL 与代理在 `vite.config.js` 中配置
25
+ 5. 样式使用 SCSS,组件内样式加 `scoped`
26
+ 6. 全局状态放 Pinia store,不要用全局变量传数据
27
+ 7. 提交前执行 `npm run lint`,保证代码通过规范检查
28
+
29
+ ## 禁止事项
30
+ - 不要引入与现有依赖功能重复的库(如同时引入 moment 和 dayjs)
31
+ - 不要修改 `eslint.config.mjs` / `prettier.config.mjs` 的规则来迁就不规范代码
32
+ - 不要在代码中硬编码后端地址,统一走环境变量与代理
@@ -0,0 +1,31 @@
1
+ # <%= projectName %>
2
+
3
+ 基于 fe-cli-starter 生成的 Vue3 + Vite 项目,内置团队规范与常用全家桶。
4
+
5
+ ## 技术栈
6
+ Vue3 · Vite · Pinia · Vue Router · Element Plus · axios · dayjs · echarts
7
+
8
+ ## 快速开始
9
+ ```bash
10
+ npm install
11
+ npm run dev # 开发:http://localhost:5173
12
+ ```
13
+
14
+ ## 常用命令
15
+ | 命令 | 说明 |
16
+ | --- | --- |
17
+ | `npm run dev` | 本地开发 |
18
+ | `npm run build` | 生产构建 |
19
+ | `npm run preview` | 预览构建产物 |
20
+ | `npm run lint` | 代码规范检查并自动修复 |
21
+
22
+ ## 目录结构
23
+ 见 [AGENTS.md](./AGENTS.md) 与 [docs/](./docs/)。
24
+
25
+ ## 需要按项目修改的配置
26
+ 1. `vite.config.js`:`/api` 代理目标地址
27
+ 2. `src/utils/request.js`:请求 baseURL、token 取值方式
28
+ 3. `index.html`:页面标题
29
+
30
+ ## 相关文档
31
+ - [docs/开发规范.md](./docs/开发规范.md)
@@ -0,0 +1,4 @@
1
+ node_modules
2
+ dist
3
+ *.local
4
+ .DS_Store
@@ -0,0 +1,10 @@
1
+ # docs — 项目文档目录
2
+
3
+ 存放本项目的开发文档,建议包含:
4
+
5
+ - `开发规范.md`:团队编码规范与目录约定(可从 AGENTS.md 补充细化)
6
+ - `需求文档/`:业务需求说明
7
+ - `接口文档/`:后端接口约定
8
+ - `部署文档.md`:构建、发布流程
9
+
10
+ > 文档与代码同仓库维护,改代码的同时更新对应文档。
@@ -0,0 +1,12 @@
1
+ <!DOCTYPE html>
2
+ <html lang="zh-CN">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title><%= projectName %></title>
7
+ </head>
8
+ <body>
9
+ <div id="app"></div>
10
+ <script type="module" src="/src/main.js"></script>
11
+ </body>
12
+ </html>
@@ -0,0 +1,36 @@
1
+ {
2
+ "name": "<%= projectName %>",
3
+ "private": true,
4
+ "version": "0.0.0",
5
+ "type": "module",
6
+ "scripts": {
7
+ "dev": "vite",
8
+ "build": "vite build",
9
+ "preview": "vite preview",
10
+ "lint": "eslint . --fix && prettier --write .<% if (useStylelint) { %> && stylelint \"**/*.{css,scss,vue}\" --fix<% } %>"
11
+ },
12
+ "dependencies": {
13
+ "@vueuse/core": "^11.0.0",
14
+ "axios": "^1.7.0",
15
+ "dayjs": "^1.11.0",
16
+ "echarts": "^5.5.0",
17
+ "element-plus": "^2.8.0",
18
+ "lodash-es": "^4.17.21",
19
+ "pinia": "^2.2.0",
20
+ "vue": "^3.5.0",
21
+ "vue-router": "^4.4.0"
22
+ },
23
+ "devDependencies": {
24
+ "@eslint/js": "^9.0.0",
25
+ "@vitejs/plugin-vue": "^5.0.0",
26
+ "eslint": "^9.0.0",
27
+ "eslint-plugin-vue": "^9.0.0",
28
+ "prettier": "^3.3.0",
29
+ "sass": "^1.77.0",
30
+ "stylelint": "^16.0.0",
31
+ "stylelint-config-standard": "^36.0.0",
32
+ "unplugin-auto-import": "^0.18.0",
33
+ "unplugin-vue-components": "^0.27.0",
34
+ "vite": "^5.0.0"
35
+ }
36
+ }
@@ -0,0 +1,7 @@
1
+ # public — 静态资源目录
2
+
3
+ 此目录下的文件构建时会原样拷贝到 `dist/` 根路径,引用方式为绝对路径(如 `/favicon.ico`)。
4
+
5
+ 建议存放:favicon、不需要构建处理的静态文件、robots.txt 等。
6
+
7
+ > 代码中通过 `@/src/assets/` 引入的图片走构建管线(可压缩、带 hash);需要固定 URL 的文件才放这里。
@@ -0,0 +1,3 @@
1
+ <template>
2
+ <router-view />
3
+ </template>
@@ -0,0 +1,9 @@
1
+ # assets — 项目资源目录
2
+
3
+ 存放需要走构建管线的静态资源:
4
+
5
+ - `images/`:图片(会被压缩、加 hash,代码中用 `import img from '@/assets/images/xxx.png'` 引入)
6
+ - `styles/`:全局样式(如 `variables.scss` 变量、`mixins.scss` 公共混入、`reset.scss`)
7
+ - `fonts/`:字体文件
8
+
9
+ > 与 `public/` 的区别:public 里的文件构建时原样拷贝、URL 固定;assets 里的文件经过 Vite 处理(压缩、hash、按需打包),优先用 assets。
@@ -0,0 +1,8 @@
1
+ # components — 公共组件目录
2
+
3
+ 存放跨页面复用的公共组件,命名使用大驼峰(如 `DataTable.vue`、`SearchBar.vue`)。
4
+
5
+ 约定:
6
+ 1. 页面私有的组件放对应 `src/views/` 页面目录里,不要全部堆到这里
7
+ 2. 复杂组件建议配套 `components/xxx/` 目录形式(组件 + `index.vue` + 子组件)
8
+ 3. 组件 props 用 `defineProps` 显式声明类型,不透传未定义属性
@@ -0,0 +1,10 @@
1
+ import { createApp } from 'vue';
2
+ import { createPinia } from 'pinia';
3
+ import App from './App.vue';
4
+ import router from './router';
5
+
6
+ // element-plus 组件由 unplugin-vue-components 按需自动注册,无需全量引入
7
+ const app = createApp(App);
8
+ app.use(createPinia());
9
+ app.use(router);
10
+ app.mount('#app');
@@ -0,0 +1,16 @@
1
+ import { createRouter, createWebHistory } from 'vue-router';
2
+
3
+ const routes = [
4
+ {
5
+ path: '/',
6
+ name: 'home',
7
+ component: () => import('../views/HomeView.vue'),
8
+ },
9
+ ];
10
+
11
+ const router = createRouter({
12
+ history: createWebHistory(),
13
+ routes,
14
+ });
15
+
16
+ export default router;
@@ -0,0 +1,12 @@
1
+ import { defineStore } from 'pinia';
2
+
3
+ export const useCounterStore = defineStore('counter', {
4
+ state: () => ({
5
+ count: 0,
6
+ }),
7
+ actions: {
8
+ increment() {
9
+ this.count++;
10
+ },
11
+ },
12
+ });
@@ -0,0 +1,25 @@
1
+ import axios from 'axios';
2
+
3
+ // 按项目实际后端地址修改,开发环境代理配置见 vite.config.js
4
+ const request = axios.create({
5
+ baseURL: '/api',
6
+ timeout: 10000,
7
+ });
8
+
9
+ // 请求拦截器:统一带上 token
10
+ request.interceptors.request.use((config) => {
11
+ const token = localStorage.getItem('token');
12
+ if (token) config.headers.Authorization = `Bearer ${token}`;
13
+ return config;
14
+ });
15
+
16
+ // 响应拦截器:统一处理业务错误
17
+ request.interceptors.response.use(
18
+ (response) => response.data,
19
+ (error) => {
20
+ console.error('请求异常:', error.message);
21
+ return Promise.reject(error);
22
+ }
23
+ );
24
+
25
+ export default request;
@@ -0,0 +1,31 @@
1
+ <script setup>
2
+ import { ref } from 'vue';
3
+ import { useCounterStore } from '../stores/counter';
4
+
5
+ const counter = useCounterStore();
6
+ const now = ref('');
7
+
8
+ const handleClick = () => {
9
+ counter.increment();
10
+ now.value = new Date().toLocaleTimeString();
11
+ };
12
+
13
+ // axios 请求已封装在 src/utils/request.js,用法:
14
+ // import request from '@/utils/request';
15
+ // request.get('/xxx').then((data) => {})
16
+ </script>
17
+
18
+ <template>
19
+ <div class="home">
20
+ <h1>Vue3 + Vite + Element Plus 项目已就绪</h1>
21
+ <el-button type="primary" @click="handleClick">点击了 {{ counter.count }} 次</el-button>
22
+ <p v-if="now">当前时间:{{ now }}(dayjs 可用于日期格式化)</p>
23
+ </div>
24
+ </template>
25
+
26
+ <style scoped lang="scss">
27
+ .home {
28
+ padding: 40px;
29
+ text-align: center;
30
+ }
31
+ </style>
@@ -0,0 +1,47 @@
1
+ import { defineConfig } from 'vite';
2
+ import vue from '@vitejs/plugin-vue';
3
+ import AutoImport from 'unplugin-auto-import/vite';
4
+ import Components from 'unplugin-vue-components/vite';
5
+ import { ElementPlusResolver } from 'unplugin-vue-components/resolvers';
6
+ import { fileURLToPath, URL } from 'node:url';
7
+
8
+ export default defineConfig({
9
+ plugins: [
10
+ vue(),
11
+ // 自动导入 vue / vue-router / pinia 的 API(ref、computed、useRouter 等无需手动 import)
12
+ AutoImport({
13
+ imports: ['vue', 'vue-router', 'pinia'],
14
+ resolvers: [ElementPlusResolver()],
15
+ }),
16
+ // element-plus 组件按需自动注册
17
+ Components({
18
+ resolvers: [ElementPlusResolver()],
19
+ }),
20
+ ],
21
+ resolve: {
22
+ alias: {
23
+ '@': fileURLToPath(new URL('./src', import.meta.url)),
24
+ },
25
+ },
26
+ server: {
27
+ host: '0.0.0.0',
28
+ proxy: {
29
+ // 按项目实际后端修改
30
+ '/api': {
31
+ target: 'http://localhost:8080',
32
+ changeOrigin: true,
33
+ },
34
+ },
35
+ },
36
+ build: {
37
+ rollupOptions: {
38
+ output: {
39
+ manualChunks: {
40
+ vue: ['vue', 'vue-router', 'pinia'],
41
+ 'element-plus': ['element-plus'],
42
+ echarts: ['echarts'],
43
+ },
44
+ },
45
+ },
46
+ },
47
+ });