velmora-starter 0.0.0-stage → 0.1.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.
Files changed (72) hide show
  1. package/README.md +36 -2
  2. package/package.json +25 -4
  3. package/src/generator.mjs +103 -0
  4. package/src/index.mjs +42 -0
  5. package/template/.editorconfig +12 -0
  6. package/template/.env +6 -0
  7. package/template/.env.development +5 -0
  8. package/template/.env.production +5 -0
  9. package/template/.env.staging +5 -0
  10. package/template/.husky/commit-msg +1 -0
  11. package/template/.husky/pre-commit +1 -0
  12. package/template/.nvmrc +1 -0
  13. package/template/.prettierignore +4 -0
  14. package/template/.prettierrc.json +5 -0
  15. package/template/.vscode/extensions.json +8 -0
  16. package/template/README.md +18 -0
  17. package/template/_gitignore +9 -0
  18. package/template/_npmrc +2 -0
  19. package/template/build/cdn.ts +25 -0
  20. package/template/build/compression.ts +25 -0
  21. package/template/build/env.ts +34 -0
  22. package/template/commitlint.config.js +3 -0
  23. package/template/eslint.config.js +44 -0
  24. package/template/index.html +13 -0
  25. package/template/mock/health.ts +10 -0
  26. package/template/package.json +90 -0
  27. package/template/src/App.vue +9 -0
  28. package/template/src/api/http.ts +7 -0
  29. package/template/src/directives/auth/index.ts +15 -0
  30. package/template/src/directives/copy/index.ts +34 -0
  31. package/template/src/directives/index.ts +19 -0
  32. package/template/src/directives/longpress/index.ts +50 -0
  33. package/template/src/directives/optimize/index.ts +62 -0
  34. package/template/src/directives/perms/index.ts +15 -0
  35. package/template/src/directives/ripple/index.scss +27 -0
  36. package/template/src/directives/ripple/index.ts +49 -0
  37. package/template/src/main.ts +22 -0
  38. package/template/src/plugins/echarts.ts +30 -0
  39. package/template/src/plugins/index.ts +1 -0
  40. package/template/src/router/index.ts +34 -0
  41. package/template/src/router/modules/static.ts +25 -0
  42. package/template/src/router/routes.ts +4 -0
  43. package/template/src/stores/auth.ts +57 -0
  44. package/template/src/stores/index.ts +4 -0
  45. package/template/src/stores/theme.ts +60 -0
  46. package/template/src/styles/index.scss +23 -0
  47. package/template/src/styles/reset.scss +34 -0
  48. package/template/src/styles/tailwind.css +3 -0
  49. package/template/src/styles/tokens.scss +9 -0
  50. package/template/src/theme/index.ts +88 -0
  51. package/template/src/theme/types.ts +1 -0
  52. package/template/src/utils/auth.ts +36 -0
  53. package/template/src/utils/event-bus.ts +9 -0
  54. package/template/src/utils/http/index.ts +121 -0
  55. package/template/src/utils/http/types.ts +32 -0
  56. package/template/src/utils/index.ts +7 -0
  57. package/template/src/utils/localforage/index.ts +1 -0
  58. package/template/src/utils/message.ts +24 -0
  59. package/template/src/utils/mitt.ts +1 -0
  60. package/template/src/utils/print.ts +60 -0
  61. package/template/src/utils/progress.ts +13 -0
  62. package/template/src/utils/route-auth.ts +11 -0
  63. package/template/src/utils/storage.ts +50 -0
  64. package/template/src/utils/tree.ts +87 -0
  65. package/template/src/views/Home.vue +244 -0
  66. package/template/src/views/Login.vue +280 -0
  67. package/template/src/views/NotFound.vue +33 -0
  68. package/template/stylelint.config.js +40 -0
  69. package/template/tsconfig.json +34 -0
  70. package/template/types/env.d.ts +19 -0
  71. package/template/types/global.d.ts +4 -0
  72. package/template/vite.config.ts +71 -0
package/README.md CHANGED
@@ -1,3 +1,37 @@
1
- # Temporary Holding Version
1
+ # velmora-starter
2
+
3
+ Velmora Starter 的项目创建工具,用于快速生成 Vue 3、TypeScript、Vite 和 Element Plus 工程。
4
+
5
+ ## 环境要求
6
+
7
+ - Node.js `20.19+` 或 `22.12+`
8
+
9
+ ## 创建项目
10
+
11
+ ```bash
12
+ npm create velmora-starter@latest my-app
13
+ ```
14
+
15
+ CLI 默认只创建项目文件,不会自动下载依赖。创建完成后执行:
16
+
17
+ ```bash
18
+ cd my-app
19
+ npm install
20
+ npm run dev
21
+ ```
22
+
23
+ 如果需要在创建后自动安装依赖:
24
+
25
+ ```bash
26
+ npm create velmora-starter@latest my-app --install
27
+ ```
28
+
29
+ ## 常用命令
30
+
31
+ ```bash
32
+ npm run dev
33
+ npm run typecheck
34
+ npm run lint
35
+ npm run build
36
+ ```
2
37
 
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.
package/package.json CHANGED
@@ -1,6 +1,27 @@
1
1
  {
2
2
  "name": "velmora-starter",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.1.0",
4
+ "description": "Create a project from Velmora Starter",
5
+ "type": "module",
6
+ "bin": {
7
+ "velmora-starter": "./src/index.mjs"
8
+ },
9
+ "files": [
10
+ "src",
11
+ "template",
12
+ "README.md"
13
+ ],
14
+ "scripts": {
15
+ "test": "node --test test/*.test.mjs"
16
+ },
17
+ "engines": {
18
+ "node": "^20.19.0 || >=22.12.0"
19
+ },
20
+ "keywords": [
21
+ "vue",
22
+ "vite",
23
+ "starter",
24
+ "scaffold"
25
+ ],
26
+ "license": "MIT"
27
+ }
@@ -0,0 +1,103 @@
1
+ import { spawn } from "node:child_process";
2
+ import {
3
+ cp,
4
+ copyFile,
5
+ mkdir,
6
+ readFile,
7
+ readdir,
8
+ rm,
9
+ writeFile
10
+ } from "node:fs/promises";
11
+ import { basename, resolve } from "node:path";
12
+ import process from "node:process";
13
+ import { fileURLToPath, URL } from "node:url";
14
+
15
+ const templateRoot = fileURLToPath(new URL("../template", import.meta.url));
16
+ const packageNamePattern = /^[a-z0-9][a-z0-9._-]*$/;
17
+
18
+ export function validateProjectName(projectName) {
19
+ if (!packageNamePattern.test(projectName)) {
20
+ throw new Error(
21
+ "项目名称只能包含小写字母、数字、点、下划线和连字符,并且必须以字母或数字开头。"
22
+ );
23
+ }
24
+ }
25
+
26
+ export function formatProjectTitle(projectName) {
27
+ return projectName
28
+ .split(/[-_.]+/)
29
+ .filter(Boolean)
30
+ .map((part) => `${part[0].toUpperCase()}${part.slice(1)}`)
31
+ .join(" ");
32
+ }
33
+
34
+ async function ensureEmptyTarget(targetDirectory) {
35
+ try {
36
+ const entries = await readdir(targetDirectory);
37
+ if (entries.length > 0) {
38
+ throw new Error(`目标目录不为空:${targetDirectory}`);
39
+ }
40
+ } catch (error) {
41
+ if (error?.code !== "ENOENT") throw error;
42
+ await mkdir(targetDirectory, { recursive: true });
43
+ }
44
+ }
45
+
46
+ async function replaceTemplateTokens(targetDirectory, projectName) {
47
+ const replacements = [
48
+ ["package.json", "velmora-project-name", projectName],
49
+ [".env", "VELMORA_PROJECT_TITLE", formatProjectTitle(projectName)],
50
+ [".env", "VELMORA_PROJECT_NAMESPACE", projectName],
51
+ ["README.md", "VELMORA_PROJECT_TITLE", formatProjectTitle(projectName)]
52
+ ];
53
+
54
+ for (const [file, token, value] of replacements) {
55
+ const filePath = resolve(targetDirectory, file);
56
+ const source = await readFile(filePath, "utf8");
57
+ await writeFile(filePath, source.replaceAll(token, value));
58
+ }
59
+ }
60
+
61
+ async function restoreDotfiles(targetDirectory) {
62
+ for (const file of ["gitignore", "npmrc"]) {
63
+ const sourcePath = resolve(targetDirectory, `_${file}`);
64
+ await copyFile(sourcePath, resolve(targetDirectory, `.${file}`));
65
+ await rm(sourcePath);
66
+ }
67
+ }
68
+
69
+ function installDependencies(targetDirectory) {
70
+ const command = process.platform === "win32" ? "npm.cmd" : "npm";
71
+
72
+ return new Promise((resolvePromise, rejectPromise) => {
73
+ const child = spawn(command, ["install"], {
74
+ cwd: targetDirectory,
75
+ stdio: "inherit"
76
+ });
77
+
78
+ child.on("error", rejectPromise);
79
+ child.on("exit", (code) => {
80
+ if (code === 0) resolvePromise();
81
+ else rejectPromise(new Error(`npm install 执行失败,退出码:${code}`));
82
+ });
83
+ });
84
+ }
85
+
86
+ export async function createProject({
87
+ directory,
88
+ install = false,
89
+ cwd = process.cwd()
90
+ }) {
91
+ const targetDirectory = resolve(cwd, directory);
92
+ const projectName = basename(targetDirectory);
93
+
94
+ validateProjectName(projectName);
95
+ await ensureEmptyTarget(targetDirectory);
96
+ await cp(templateRoot, targetDirectory, { recursive: true });
97
+ await restoreDotfiles(targetDirectory);
98
+ await replaceTemplateTokens(targetDirectory, projectName);
99
+
100
+ if (install) await installDependencies(targetDirectory);
101
+
102
+ return { projectName, targetDirectory };
103
+ }
package/src/index.mjs ADDED
@@ -0,0 +1,42 @@
1
+ #!/usr/bin/env node
2
+
3
+ import console from "node:console";
4
+ import { createInterface } from "node:readline/promises";
5
+ import process, { stdin, stdout } from "node:process";
6
+ import { createProject } from "./generator.mjs";
7
+
8
+ const args = process.argv.slice(2);
9
+
10
+ if (args.includes("--help") || args.includes("-h")) {
11
+ console.log(`
12
+ create-velmora-starter <project-name> [--install]
13
+
14
+ 创建一个 Velmora Starter 项目。
15
+ `);
16
+ process.exit(0);
17
+ }
18
+
19
+ const install = args.includes("--install");
20
+ let directory = args.find((arg) => !arg.startsWith("-"));
21
+
22
+ if (!directory) {
23
+ const prompt = createInterface({ input: stdin, output: stdout });
24
+ directory = (await prompt.question("项目名称:")).trim();
25
+ prompt.close();
26
+ }
27
+
28
+ if (!directory) {
29
+ console.error("项目名称不能为空。");
30
+ process.exit(1);
31
+ }
32
+
33
+ try {
34
+ const result = await createProject({ directory, install });
35
+ console.log(`\n项目 ${result.projectName} 创建完成。`);
36
+ console.log(`cd ${directory}`);
37
+ if (!install) console.log("npm install");
38
+ console.log("npm run dev\n");
39
+ } catch (error) {
40
+ console.error(`\n创建失败:${error.message}`);
41
+ process.exitCode = 1;
42
+ }
@@ -0,0 +1,12 @@
1
+ root = true
2
+
3
+ [*]
4
+ charset = utf-8
5
+ end_of_line = lf
6
+ indent_style = space
7
+ indent_size = 2
8
+ insert_final_newline = true
9
+ trim_trailing_whitespace = true
10
+
11
+ [*.md]
12
+ trim_trailing_whitespace = false
package/template/.env ADDED
@@ -0,0 +1,6 @@
1
+ VITE_APP_TITLE=VELMORA_PROJECT_TITLE
2
+ VITE_APP_NAMESPACE=VELMORA_PROJECT_NAMESPACE
3
+ VITE_PORT=8848
4
+ VITE_PUBLIC_PATH=/
5
+ VITE_ROUTER_HISTORY=hash
6
+ VITE_API_BASE_URL=/api
@@ -0,0 +1,5 @@
1
+ VITE_ENABLE_MOCK=true
2
+ VITE_SOURCE_MAP=true
3
+ VITE_COMPRESSION=none
4
+ VITE_CDN=false
5
+ VITE_REMOVE_CONSOLE=false
@@ -0,0 +1,5 @@
1
+ VITE_ENABLE_MOCK=false
2
+ VITE_SOURCE_MAP=false
3
+ VITE_COMPRESSION=brotli
4
+ VITE_CDN=false
5
+ VITE_REMOVE_CONSOLE=false
@@ -0,0 +1,5 @@
1
+ VITE_ENABLE_MOCK=false
2
+ VITE_SOURCE_MAP=true
3
+ VITE_COMPRESSION=gzip
4
+ VITE_CDN=false
5
+ VITE_REMOVE_CONSOLE=false
@@ -0,0 +1 @@
1
+ pnpm exec commitlint --edit "$1"
@@ -0,0 +1 @@
1
+ pnpm lint
@@ -0,0 +1 @@
1
+ 20.19.0
@@ -0,0 +1,4 @@
1
+ dist
2
+ node_modules
3
+ report.html
4
+ pnpm-lock.yaml
@@ -0,0 +1,5 @@
1
+ {
2
+ "semi": true,
3
+ "singleQuote": false,
4
+ "trailingComma": "none"
5
+ }
@@ -0,0 +1,8 @@
1
+ {
2
+ "recommendations": [
3
+ "Vue.volar",
4
+ "dbaeumer.vscode-eslint",
5
+ "esbenp.prettier-vscode",
6
+ "stylelint.vscode-stylelint"
7
+ ]
8
+ }
@@ -0,0 +1,18 @@
1
+ # VELMORA_PROJECT_TITLE
2
+
3
+ 基于 Vue 3、TypeScript、Vite 和 Element Plus 创建的项目。
4
+
5
+ ## 开始使用
6
+
7
+ ```bash
8
+ npm install
9
+ npm run dev
10
+ ```
11
+
12
+ ## 校验与构建
13
+
14
+ ```bash
15
+ npm run typecheck
16
+ npm run lint
17
+ npm run build
18
+ ```
@@ -0,0 +1,9 @@
1
+ node_modules
2
+ dist
3
+ report.html
4
+ *.local
5
+ *.log
6
+ .DS_Store
7
+ .idea
8
+ .vscode/*
9
+ !.vscode/extensions.json
@@ -0,0 +1,2 @@
1
+ auto-install-peers=true
2
+ strict-peer-dependencies=false
@@ -0,0 +1,25 @@
1
+ import { Plugin as importToCdn } from "vite-plugin-cdn-import";
2
+
3
+ /** 将稳定的第三方运行时依赖切换为 jsDelivr CDN 资源。 */
4
+ export function createCdnPlugin() {
5
+ return importToCdn({
6
+ prodUrl: "https://cdn.jsdelivr.net/npm/{name}@{version}/{path}",
7
+ modules: [
8
+ { name: "vue", var: "Vue", path: "dist/vue.global.prod.js" },
9
+ {
10
+ name: "vue-router",
11
+ var: "VueRouter",
12
+ path: "dist/vue-router.global.prod.js"
13
+ },
14
+ { name: "pinia", var: "Pinia", path: "dist/pinia.iife.prod.js" },
15
+ { name: "axios", var: "axios", path: "dist/axios.min.js" },
16
+ {
17
+ name: "element-plus",
18
+ var: "ElementPlus",
19
+ path: "dist/index.full.min.js",
20
+ css: "dist/index.css"
21
+ },
22
+ { name: "echarts", var: "echarts", path: "dist/echarts.min.js" }
23
+ ]
24
+ });
25
+ }
@@ -0,0 +1,25 @@
1
+ import type { PluginOption } from "vite";
2
+ import compression from "vite-plugin-compression";
3
+ import type { BuildEnv } from "./env";
4
+
5
+ /** 根据环境配置按需创建 gzip、brotli 压缩插件。 */
6
+ export function createCompressionPlugins(
7
+ mode: BuildEnv["compression"]
8
+ ): PluginOption[] {
9
+ if (mode === "none") return [];
10
+
11
+ const plugins: PluginOption[] = [];
12
+ const common = { threshold: 10 * 1024, deleteOriginFile: false };
13
+
14
+ if (mode === "gzip" || mode === "both") {
15
+ plugins.push(compression({ ...common, algorithm: "gzip", ext: ".gz" }));
16
+ }
17
+
18
+ if (mode === "brotli" || mode === "both") {
19
+ plugins.push(
20
+ compression({ ...common, algorithm: "brotliCompress", ext: ".br" })
21
+ );
22
+ }
23
+
24
+ return plugins;
25
+ }
@@ -0,0 +1,34 @@
1
+ /** 构建阶段统一使用的环境配置,避免插件各自解析字符串环境变量。 */
2
+ export type BuildEnv = {
3
+ appTitle: string;
4
+ appNamespace: string;
5
+ port: number;
6
+ publicPath: string;
7
+ sourceMap: boolean;
8
+ enableMock: boolean;
9
+ enableCdn: boolean;
10
+ removeConsole: boolean;
11
+ compression: "none" | "gzip" | "brotli" | "both";
12
+ };
13
+
14
+ const toBoolean = (value: string | undefined) => value === "true";
15
+
16
+ export function resolveBuildEnv(env: Record<string, string>): BuildEnv {
17
+ const compression = env.VITE_COMPRESSION || "none";
18
+
19
+ if (!["none", "gzip", "brotli", "both"].includes(compression)) {
20
+ throw new Error(`Unsupported VITE_COMPRESSION: ${compression}`);
21
+ }
22
+
23
+ return {
24
+ appTitle: env.VITE_APP_TITLE || "Vue App",
25
+ appNamespace: env.VITE_APP_NAMESPACE || "vue-app",
26
+ port: Number(env.VITE_PORT || 8848),
27
+ publicPath: env.VITE_PUBLIC_PATH || "/",
28
+ sourceMap: toBoolean(env.VITE_SOURCE_MAP),
29
+ enableMock: toBoolean(env.VITE_ENABLE_MOCK),
30
+ enableCdn: toBoolean(env.VITE_CDN),
31
+ removeConsole: toBoolean(env.VITE_REMOVE_CONSOLE),
32
+ compression: compression as BuildEnv["compression"]
33
+ };
34
+ }
@@ -0,0 +1,3 @@
1
+ export default {
2
+ extends: ["@commitlint/config-conventional"]
3
+ };
@@ -0,0 +1,44 @@
1
+ import js from "@eslint/js";
2
+ import vue from "eslint-plugin-vue";
3
+ import tseslint from "typescript-eslint";
4
+ import vueParser from "vue-eslint-parser";
5
+ import prettier from "eslint-config-prettier";
6
+
7
+ export default tseslint.config(
8
+ {
9
+ ignores: [
10
+ "dist",
11
+ "node_modules",
12
+ "report.html",
13
+ "docs/node_modules",
14
+ "docs/docs/.vitepress/cache",
15
+ "docs/docs/.vitepress/dist"
16
+ ]
17
+ },
18
+ js.configs.recommended,
19
+ ...tseslint.configs.recommended,
20
+ ...vue.configs["flat/recommended"],
21
+ {
22
+ files: ["**/*.vue"],
23
+ languageOptions: {
24
+ parser: vueParser,
25
+ parserOptions: {
26
+ parser: tseslint.parser,
27
+ sourceType: "module"
28
+ }
29
+ },
30
+ rules: {
31
+ "vue/multi-word-component-names": "off"
32
+ }
33
+ },
34
+ {
35
+ rules: {
36
+ "@typescript-eslint/no-explicit-any": "off",
37
+ "@typescript-eslint/no-unused-vars": [
38
+ "error",
39
+ { argsIgnorePattern: "^_", varsIgnorePattern: "^_" }
40
+ ]
41
+ }
42
+ },
43
+ prettier
44
+ );
@@ -0,0 +1,13 @@
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
+ <meta name="theme-color" content="#ffffff" />
7
+ <title>%VITE_APP_TITLE%</title>
8
+ </head>
9
+ <body>
10
+ <div id="app"></div>
11
+ <script type="module" src="/src/main.ts"></script>
12
+ </body>
13
+ </html>
@@ -0,0 +1,10 @@
1
+ import { defineFakeRoute } from "vite-plugin-fake-server/client";
2
+
3
+ // 用于开发环境确认 Mock 服务与 API 前缀是否正常工作。
4
+ export default defineFakeRoute([
5
+ {
6
+ url: "/api/health",
7
+ method: "get",
8
+ response: () => ({ status: "ok" })
9
+ }
10
+ ]);
@@ -0,0 +1,90 @@
1
+ {
2
+ "name": "velmora-project-name",
3
+ "version": "0.1.0",
4
+ "private": true,
5
+ "type": "module",
6
+ "scripts": {
7
+ "dev": "cross-env NODE_OPTIONS=--max-old-space-size=4096 vite",
8
+ "build": "rimraf dist && cross-env NODE_OPTIONS=--max-old-space-size=8192 vite build",
9
+ "build:staging": "rimraf dist && vite build --mode staging",
10
+ "preview": "vite preview",
11
+ "report": "cross-env ANALYZE=true npm run build",
12
+ "typecheck": "vue-tsc --noEmit",
13
+ "lint:eslint": "eslint . --max-warnings 0",
14
+ "lint:prettier": "prettier --check .",
15
+ "lint:stylelint": "stylelint \"src/**/*.{vue,css,scss}\"",
16
+ "lint": "npm run lint:eslint && npm run lint:prettier && npm run lint:stylelint",
17
+ "format": "prettier --write .",
18
+ "prepare": "husky"
19
+ },
20
+ "engines": {
21
+ "node": "^20.19.0 || >=22.12.0"
22
+ },
23
+ "dependencies": {
24
+ "@vueuse/core": "^14.0.0",
25
+ "@vueuse/motion": "^3.0.3",
26
+ "animate.css": "^4.1.1",
27
+ "axios": "^1.12.2",
28
+ "echarts": "^6.0.0",
29
+ "element-plus": "^2.11.5",
30
+ "localforage": "^1.10.0",
31
+ "mitt": "^3.0.1",
32
+ "nprogress": "^0.2.0",
33
+ "pinia": "^3.0.3",
34
+ "qs": "^6.14.0",
35
+ "vue": "^3.5.22",
36
+ "vue-router": "^4.6.3"
37
+ },
38
+ "devDependencies": {
39
+ "@commitlint/cli": "^20.1.0",
40
+ "@commitlint/config-conventional": "^20.0.0",
41
+ "@eslint/js": "^9.38.0",
42
+ "@iconify/json": "^2.2.400",
43
+ "@tailwindcss/vite": "^4.1.16",
44
+ "@types/node": "^20.19.23",
45
+ "@types/nprogress": "^0.2.3",
46
+ "@types/qs": "^6.14.0",
47
+ "@vitejs/plugin-vue": "^6.0.1",
48
+ "cross-env": "^10.1.0",
49
+ "eslint": "^9.38.0",
50
+ "eslint-config-prettier": "^10.1.8",
51
+ "eslint-plugin-vue": "^10.5.1",
52
+ "husky": "^9.1.7",
53
+ "lint-staged": "^16.2.6",
54
+ "postcss": "^8.5.6",
55
+ "postcss-html": "^1.8.0",
56
+ "postcss-scss": "^4.0.9",
57
+ "prettier": "^3.6.2",
58
+ "rimraf": "^6.0.1",
59
+ "rollup-plugin-visualizer": "^6.0.5",
60
+ "sass": "^1.93.2",
61
+ "stylelint": "^16.25.0",
62
+ "stylelint-config-recess-order": "^7.4.0",
63
+ "stylelint-config-recommended-vue": "^1.6.1",
64
+ "stylelint-config-standard": "^39.0.0",
65
+ "stylelint-config-standard-scss": "^16.0.0",
66
+ "stylelint-order": "^7.0.0",
67
+ "stylelint-prettier": "^5.0.3",
68
+ "stylelint-scss": "^6.12.1",
69
+ "tailwindcss": "^4.1.16",
70
+ "typescript": "^5.9.3",
71
+ "typescript-eslint": "^8.46.2",
72
+ "unplugin-icons": "^22.5.0",
73
+ "vite": "^7.1.12",
74
+ "vite-plugin-cdn-import": "^1.0.1",
75
+ "vite-plugin-compression": "^0.5.1",
76
+ "vite-plugin-fake-server": "^2.2.0",
77
+ "vite-plugin-remove-console": "^2.2.0",
78
+ "vite-svg-loader": "^5.1.0",
79
+ "vue-eslint-parser": "^10.2.0",
80
+ "vue-tsc": "^3.1.2"
81
+ },
82
+ "lint-staged": {
83
+ "*.{js,mjs,cjs,ts,vue}": [
84
+ "eslint --fix",
85
+ "prettier --write"
86
+ ],
87
+ "*.{css,scss,vue}": "stylelint --fix",
88
+ "*.{json,md,yml,yaml}": "prettier --write"
89
+ }
90
+ }
@@ -0,0 +1,9 @@
1
+ <script setup lang="ts">
2
+ import { ElConfigProvider } from "element-plus";
3
+ </script>
4
+
5
+ <template>
6
+ <el-config-provider>
7
+ <router-view />
8
+ </el-config-provider>
9
+ </template>
@@ -0,0 +1,7 @@
1
+ export { http } from "@/utils/http";
2
+ export type {
3
+ HttpError,
4
+ HttpRequestConfig,
5
+ HttpResponse,
6
+ RequestMethod
7
+ } from "@/utils/http/types";
@@ -0,0 +1,15 @@
1
+ import type { Directive, DirectiveBinding } from "vue";
2
+ import { hasAuth } from "@/utils/route-auth";
3
+
4
+ /** 根据当前路由 meta.auths 控制元素是否保留。 */
5
+ export const auth: Directive<HTMLElement, string | string[]> = {
6
+ mounted(element, binding: DirectiveBinding<string | string[]>) {
7
+ if (!binding.value) {
8
+ throw new Error(
9
+ "[Directive: auth]: need auths! Like v-auth=\"['btn.add', 'btn.edit']\""
10
+ );
11
+ }
12
+
13
+ if (!hasAuth(binding.value)) element.remove();
14
+ }
15
+ };
@@ -0,0 +1,34 @@
1
+ import type { Directive } from "vue";
2
+ import { message } from "@/utils/message";
3
+
4
+ type CopyElement = HTMLElement & {
5
+ copyValue?: string;
6
+ copyHandler?: () => void;
7
+ };
8
+
9
+ /** 在指定事件(默认双击)发生时复制绑定文本。 */
10
+ export const copy: Directive<CopyElement, string> = {
11
+ mounted(element, binding) {
12
+ element.copyValue = binding.value;
13
+ element.copyHandler = async () => {
14
+ try {
15
+ await navigator.clipboard.writeText(element.copyValue ?? "");
16
+ message("复制成功", { type: "success" });
17
+ } catch {
18
+ message("复制失败", { type: "error" });
19
+ }
20
+ };
21
+ element.addEventListener(binding.arg ?? "dblclick", element.copyHandler);
22
+ },
23
+ updated(element, binding) {
24
+ element.copyValue = binding.value;
25
+ },
26
+ unmounted(element, binding) {
27
+ if (element.copyHandler) {
28
+ element.removeEventListener(
29
+ binding.arg ?? "dblclick",
30
+ element.copyHandler
31
+ );
32
+ }
33
+ }
34
+ };
@@ -0,0 +1,19 @@
1
+ import type { App } from "vue";
2
+ import { auth } from "./auth";
3
+ import { copy } from "./copy";
4
+ import { longpress } from "./longpress";
5
+ import { optimize } from "./optimize";
6
+ import { perms } from "./perms";
7
+ import { ripple } from "./ripple";
8
+
9
+ export { auth, copy, longpress, optimize, perms, ripple };
10
+
11
+ /** 一次性注册工程提供的全部全局指令。 */
12
+ export function setupDirectives(app: App) {
13
+ app.directive("auth", auth);
14
+ app.directive("copy", copy);
15
+ app.directive("longpress", longpress);
16
+ app.directive("optimize", optimize);
17
+ app.directive("perms", perms);
18
+ app.directive("ripple", ripple);
19
+ }