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.
- package/README.md +36 -2
- package/package.json +25 -4
- package/src/generator.mjs +103 -0
- package/src/index.mjs +42 -0
- package/template/.editorconfig +12 -0
- package/template/.env +6 -0
- package/template/.env.development +5 -0
- package/template/.env.production +5 -0
- package/template/.env.staging +5 -0
- package/template/.husky/commit-msg +1 -0
- package/template/.husky/pre-commit +1 -0
- package/template/.nvmrc +1 -0
- package/template/.prettierignore +4 -0
- package/template/.prettierrc.json +5 -0
- package/template/.vscode/extensions.json +8 -0
- package/template/README.md +18 -0
- package/template/_gitignore +9 -0
- package/template/_npmrc +2 -0
- package/template/build/cdn.ts +25 -0
- package/template/build/compression.ts +25 -0
- package/template/build/env.ts +34 -0
- package/template/commitlint.config.js +3 -0
- package/template/eslint.config.js +44 -0
- package/template/index.html +13 -0
- package/template/mock/health.ts +10 -0
- package/template/package.json +90 -0
- package/template/src/App.vue +9 -0
- package/template/src/api/http.ts +7 -0
- package/template/src/directives/auth/index.ts +15 -0
- package/template/src/directives/copy/index.ts +34 -0
- package/template/src/directives/index.ts +19 -0
- package/template/src/directives/longpress/index.ts +50 -0
- package/template/src/directives/optimize/index.ts +62 -0
- package/template/src/directives/perms/index.ts +15 -0
- package/template/src/directives/ripple/index.scss +27 -0
- package/template/src/directives/ripple/index.ts +49 -0
- package/template/src/main.ts +22 -0
- package/template/src/plugins/echarts.ts +30 -0
- package/template/src/plugins/index.ts +1 -0
- package/template/src/router/index.ts +34 -0
- package/template/src/router/modules/static.ts +25 -0
- package/template/src/router/routes.ts +4 -0
- package/template/src/stores/auth.ts +57 -0
- package/template/src/stores/index.ts +4 -0
- package/template/src/stores/theme.ts +60 -0
- package/template/src/styles/index.scss +23 -0
- package/template/src/styles/reset.scss +34 -0
- package/template/src/styles/tailwind.css +3 -0
- package/template/src/styles/tokens.scss +9 -0
- package/template/src/theme/index.ts +88 -0
- package/template/src/theme/types.ts +1 -0
- package/template/src/utils/auth.ts +36 -0
- package/template/src/utils/event-bus.ts +9 -0
- package/template/src/utils/http/index.ts +121 -0
- package/template/src/utils/http/types.ts +32 -0
- package/template/src/utils/index.ts +7 -0
- package/template/src/utils/localforage/index.ts +1 -0
- package/template/src/utils/message.ts +24 -0
- package/template/src/utils/mitt.ts +1 -0
- package/template/src/utils/print.ts +60 -0
- package/template/src/utils/progress.ts +13 -0
- package/template/src/utils/route-auth.ts +11 -0
- package/template/src/utils/storage.ts +50 -0
- package/template/src/utils/tree.ts +87 -0
- package/template/src/views/Home.vue +244 -0
- package/template/src/views/Login.vue +280 -0
- package/template/src/views/NotFound.vue +33 -0
- package/template/stylelint.config.js +40 -0
- package/template/tsconfig.json +34 -0
- package/template/types/env.d.ts +19 -0
- package/template/types/global.d.ts +4 -0
- package/template/vite.config.ts +71 -0
package/README.md
CHANGED
|
@@ -1,3 +1,37 @@
|
|
|
1
|
-
#
|
|
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.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
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
|
+
}
|
package/template/.env
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
pnpm exec commitlint --edit "$1"
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
pnpm lint
|
package/template/.nvmrc
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
20.19.0
|
package/template/_npmrc
ADDED
|
@@ -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,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,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,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
|
+
}
|