befly-vite 1.2.9 → 1.2.11

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
@@ -5,7 +5,7 @@ Befly Vite 配置预设和插件集合,专为 Vue 3 项目优化。
5
5
  ## 特性
6
6
 
7
7
  - ✅ 开箱即用的 Vite + Vue 3 配置
8
- - ✅ 集成常用插件(路由、自动导入、图标、UnoCSS 等)
8
+ - ✅ 集成常用插件(路由、自动导入、图标等)
9
9
  - ✅ 优化的构建配置(分包、压缩、分析)
10
10
  - ✅ 支持自定义扩展
11
11
 
@@ -35,8 +35,11 @@ import { fileURLToPath } from "node:url";
35
35
  export default createBeflyViteConfig({
36
36
  root: fileURLToPath(new URL(".", import.meta.url)),
37
37
 
38
+ // 说明:项目 views 固定扫描 "src/views";如需扫描 addon 内的视图目录,可配置 addonView(单级目录名)
39
+ // addonView: "adminViews",
40
+
38
41
  // 自定义配置
39
- userConfig: {
42
+ viteConfig: {
40
43
  server: {
41
44
  port: 5600
42
45
  }
@@ -1,9 +1,5 @@
1
1
  /**
2
- * 自定义布局处理函数
3
- * 根据文件名后缀 _数字 判断使用哪个布局
4
- */
5
-
6
- /**
2
+ * auto-routes 的 route 结构(我们只依赖以下字段)。
7
3
  * @typedef {Object} RouteConfig
8
4
  * @property {string=} path
9
5
  * @property {any=} component
@@ -12,6 +8,7 @@
12
8
  */
13
9
 
14
10
  /**
11
+ * 内部扁平结构:一条“最终路由 path + 选用布局 + 页面组件”。
15
12
  * @typedef {Object} LayoutConfig
16
13
  * @property {string} path
17
14
  * @property {string} layoutName
@@ -20,11 +17,15 @@
20
17
  */
21
18
 
22
19
  /**
20
+ * 内部实现:根据文件名后缀 _数字 判断使用哪个布局,输出扁平布局配置。
21
+ *
22
+ * 注意:该函数仅供 befly-vite 包内部使用,不作为对外 API。
23
+ *
23
24
  * @param {RouteConfig[]} routes
24
25
  * @param {string=} inheritLayout
25
26
  * @returns {LayoutConfig[]}
26
27
  */
27
- export function Layouts(routes, inheritLayout = "") {
28
+ function buildLayoutConfigs(routes, inheritLayout = "") {
28
29
  /** @type {LayoutConfig[]} */
29
30
  const result = [];
30
31
 
@@ -37,7 +38,7 @@ export function Layouts(routes, inheritLayout = "") {
37
38
  // 中间节点:递归处理子路由,不包裹布局
38
39
  if (route.children && route.children.length > 0) {
39
40
  const cleanPath = pathMatch ? currentPath.replace(/_\d+$/, "") : currentPath;
40
- const childConfigs = Layouts(route.children, currentLayout);
41
+ const childConfigs = buildLayoutConfigs(route.children, currentLayout);
41
42
 
42
43
  for (const child of childConfigs) {
43
44
  const mergedPath = cleanPath ? `${cleanPath}/${child.path}`.replace(/\/+/, "/") : child.path;
@@ -77,14 +78,23 @@ export function Layouts(routes, inheritLayout = "") {
77
78
  }
78
79
 
79
80
  /**
80
- * 将 Layouts 输出的扁平配置转换为 Vue Router 的 RouteRecordRaw
81
- * 说明:resolveLayoutComponent 由业务方提供,以避免 utils 强耦合具体项目的布局路径。
81
+ * 将 auto-routes routes 按 `_数字` 规则套用布局组件,并输出 Vue Router 的 RouteRecordRaw[]。
82
82
  *
83
- * @param {LayoutConfig[]} configs
83
+ * @param {any[]} routes
84
84
  * @param {(layoutName: string) => any} resolveLayoutComponent
85
85
  * @returns {import('vue-router').RouteRecordRaw[]}
86
86
  */
87
- export function applyLayouts(configs, resolveLayoutComponent) {
87
+ export function Layouts(routes, resolveLayoutComponent) {
88
+ if (!Array.isArray(routes)) {
89
+ throw new Error("Layouts(routes, resolveLayoutComponent) 中 routes 必须是数组。");
90
+ }
91
+
92
+ if (typeof resolveLayoutComponent !== "function") {
93
+ throw new Error("Layouts(routes, resolveLayoutComponent) 中 resolveLayoutComponent 必须是函数。");
94
+ }
95
+
96
+ const configs = buildLayoutConfigs(routes);
97
+
88
98
  return configs.map((config) => {
89
99
  const layoutComponent = resolveLayoutComponent(config.layoutName);
90
100
 
package/index.js CHANGED
@@ -1,3 +1,5 @@
1
+ import { existsSync, readdirSync, realpathSync } from "node:fs";
2
+ import { join } from "node:path";
1
3
  import { fileURLToPath } from "node:url";
2
4
 
3
5
  import { defineConfig, mergeConfig } from "vite";
@@ -5,12 +7,10 @@ import { defineConfig, mergeConfig } from "vite";
5
7
  import { createAnalyzerPlugin } from "./plugins/analyzer.js";
6
8
  import { createAutoImportPlugin } from "./plugins/auto-import.js";
7
9
  import { createComponentsPlugin } from "./plugins/components.js";
8
- import { createCompressionPlugin } from "./plugins/compression.js";
9
10
  import { createDevToolsPlugin } from "./plugins/devtools.js";
10
11
  import { createIconsPlugin } from "./plugins/icons.js";
11
12
  import { createReactivityTransformPlugin } from "./plugins/reactivity-transform.js";
12
13
  import { createRouterPlugin } from "./plugins/router.js";
13
- import { createUnoCSSPlugin } from "./plugins/unocss.js";
14
14
  import { createVuePlugin } from "./plugins/vue.js";
15
15
 
16
16
  /**
@@ -66,48 +66,60 @@ function defaultManualChunks(id) {
66
66
  return "vue-macros";
67
67
  }
68
68
 
69
- // befly-addon(必须在 Vue 判断之后)
70
- if (id.includes("@befly-addon/") || id.includes("packages/addonAdmin/") || id.includes("packages\\addonAdmin\\")) {
71
- return "befly-addon";
72
- }
73
-
74
- // 其他 node_modules 依赖
75
- if (id.includes("node_modules/")) {
76
- return "vendor";
77
- }
69
+ // 注意:不要把所有 addon node_modules 强制合并到单个 chunk。
70
+ // - addon 路由(importMode: async)应按页面懒加载自然拆分。
71
+ // - node_modules 让 Rollup 按共享与动态导入边界自动拆分即可。
72
+ // 如需进一步细分,可通过 createBeflyViteConfig({ manualChunks }) 注入自定义策略。
78
73
  }
79
74
 
80
75
  /**
81
76
  * 创建 Befly Vite 配置
82
77
  * @param {Object} options - 配置选项
83
78
  * @param {string} options.root - 项目根目录(可选)
84
- * @param {Function} options.scanViews - 扫描视图函数(可选)
79
+ * @param {string} options.addonView - addon 内要扫描的视图目录名(可选,默认 "adminViews")
85
80
  * @param {Object} options.resolvers - 自定义 resolvers(可选)
86
81
  * @param {Function} options.manualChunks - 自定义分包配置(可选)
87
- * @param {Object} options.userConfig - 用户自定义配置(可选)
82
+ * @param {Object} options.viteConfig - 用户自定义配置(可选)
88
83
  * @returns {Object} Vite 配置对象
89
84
  */
90
85
  export function createBeflyViteConfig(options = {}) {
91
- const { root, scanViews, resolvers = {}, manualChunks, userConfig = {} } = options;
86
+ const { root, addonView = "adminViews", resolvers = {}, manualChunks, viteConfig = {} } = options;
92
87
 
93
88
  // 计算根目录(如果未提供)
94
89
  const appRoot = root || process.cwd();
95
90
 
91
+ if (typeof addonView !== "string") {
92
+ throw new Error('createBeflyViteConfig({ addonView }) 中 addonView 必须是字符串目录名。\n例如:addonView: "adminViews"');
93
+ }
94
+
95
+ if (addonView.trim() !== addonView) {
96
+ throw new Error('createBeflyViteConfig({ addonView }) 中 addonView 不能包含首尾空格。\n例如:addonView: "adminViews"');
97
+ }
98
+
99
+ if (!addonView) {
100
+ throw new Error('createBeflyViteConfig({ addonView }) 中 addonView 不能为空。\n例如:addonView: "adminViews"');
101
+ }
102
+
103
+ // 只能是单级目录名:禁止多级路径与路径穿越
104
+ if (addonView === "." || addonView === ".." || addonView.includes("/") || addonView.includes("\\") || addonView.includes("..") || addonView.includes("\0")) {
105
+ throw new Error('createBeflyViteConfig({ addonView }) 中 addonView 必须是单级目录名(不能是多级路径)。\n例如:addonView: "adminViews"');
106
+ }
107
+
108
+ const routesFolders = scanViewsInternal(appRoot, addonView);
109
+
96
110
  const baseConfig = defineConfig({
97
111
  base: "./",
98
112
 
99
113
  plugins: [
100
114
  //
101
- createUnoCSSPlugin(),
102
- createRouterPlugin({ scanViews: scanViews }),
115
+ createRouterPlugin({ routesFolders: routesFolders }),
103
116
  createVuePlugin(),
104
117
  createReactivityTransformPlugin(),
105
118
  createDevToolsPlugin(),
106
119
  createAutoImportPlugin({ resolvers: resolvers }),
107
120
  createComponentsPlugin({ resolvers: resolvers }),
108
121
  createIconsPlugin(),
109
- createAnalyzerPlugin(),
110
- createCompressionPlugin()
122
+ createAnalyzerPlugin()
111
123
  ],
112
124
 
113
125
  resolve: {
@@ -153,5 +165,57 @@ export function createBeflyViteConfig(options = {}) {
153
165
  }
154
166
  });
155
167
 
156
- return mergeConfig(baseConfig, userConfig);
168
+ return mergeConfig(baseConfig, viteConfig);
169
+ }
170
+
171
+ /**
172
+ * 内部实现:扫描项目和所有 @befly-addon 包的视图目录
173
+ * @param {string} appRoot
174
+ * @param {string} addonView
175
+ * @returns {Array<{ src: string, path: string, exclude: string[] }>}
176
+ */
177
+ function scanViewsInternal(appRoot, addonView = "adminViews") {
178
+ const addonBasePath = join(appRoot, "node_modules", "@befly-addon");
179
+
180
+ /** @type {Array<{ src: string, path: string, exclude: string[] }>} */
181
+ const routesFolders = [];
182
+
183
+ // 1. 项目自身 views
184
+ const appViewsPath = join(appRoot, "src", "views");
185
+ if (existsSync(appViewsPath)) {
186
+ routesFolders.push({
187
+ src: realpathSync(appViewsPath),
188
+ path: "",
189
+ exclude: ["**/components/**"]
190
+ });
191
+ }
192
+
193
+ // 2. 扫描 @befly-addon/*/<addonView>(仅此目录允许生成 addon 路由)
194
+ if (!existsSync(addonBasePath)) {
195
+ return routesFolders;
196
+ }
197
+
198
+ try {
199
+ const addonDirs = readdirSync(addonBasePath);
200
+
201
+ for (const addonName of addonDirs) {
202
+ const addonPath = join(addonBasePath, addonName);
203
+ if (!existsSync(addonPath)) {
204
+ continue;
205
+ }
206
+
207
+ const addonViewPath = join(addonPath, addonView);
208
+ if (existsSync(addonViewPath)) {
209
+ routesFolders.push({
210
+ src: realpathSync(addonViewPath),
211
+ path: `addon/${addonName}/`,
212
+ exclude: ["**/components/**"]
213
+ });
214
+ }
215
+ }
216
+ } catch {
217
+ // 扫描失败保持静默,避免影响 Vite 启动
218
+ }
219
+
220
+ return routesFolders;
157
221
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "befly-vite",
3
- "version": "1.2.9",
4
- "gitHead": "b2a7116ecbf488ef3e7e74c5cb1da1ff33a64f2c",
3
+ "version": "1.2.11",
4
+ "gitHead": "f5d17c019a58e82cfacec5ce3a1f04e3225ba838",
5
5
  "private": false,
6
6
  "description": "Befly Vite 配置预设和插件集合",
7
7
  "keywords": [
@@ -15,41 +15,34 @@
15
15
  "license": "Apache-2.0",
16
16
  "author": "chensuiyi <bimostyle@qq.com>",
17
17
  "files": [
18
+ "index.browser.js",
18
19
  "index.js",
19
20
  "package.json",
20
21
  "README.md",
21
- "bin/",
22
- "configs/",
23
- "plugins/",
24
- "utils/"
22
+ "plugins/"
25
23
  ],
26
24
  "type": "module",
27
- "main": "index.js",
28
25
  "exports": {
29
- ".": "./index.js",
30
- "./utils/*": "./utils/*.js"
26
+ ".": {
27
+ "browser": "./index.browser.js",
28
+ "import": "./index.js",
29
+ "default": "./index.js"
30
+ }
31
31
  },
32
32
  "publishConfig": {
33
33
  "access": "public",
34
34
  "registry": "https://registry.npmjs.org"
35
35
  },
36
- "scripts": {
37
- "scanViewsDir": "bun ./bin/scanViewsDir.ts"
38
- },
39
36
  "dependencies": {
40
- "@unocss/preset-attributify": "^66.5.11",
41
- "@unocss/preset-uno": "^66.5.11",
42
37
  "@vitejs/plugin-vue": "^6.0.3",
43
38
  "@vue-macros/reactivity-transform": "^3.1.1",
44
- "befly-shared": "^1.3.8",
39
+ "befly-shared": "^1.3.9",
45
40
  "sass": "^1.97.1",
46
- "unocss": "^66.5.11",
47
41
  "unplugin-auto-import": "^20.3.0",
48
42
  "unplugin-icons": "^22.5.0",
49
43
  "unplugin-vue-components": "^30.0.0",
50
- "unplugin-vue-router": "^0.19.0",
44
+ "unplugin-vue-router": "^0.19.2",
51
45
  "vite-bundle-analyzer": "^1.3.1",
52
- "vite-plugin-compression2": "^2.4.0",
53
46
  "vite-plugin-vue-devtools": "^8.0.5"
54
47
  },
55
48
  "peerDependencies": {
package/plugins/router.js CHANGED
@@ -4,10 +4,10 @@ import VueRouter from "unplugin-vue-router/vite";
4
4
  * 创建路由插件配置
5
5
  */
6
6
  export function createRouterPlugin(options = {}) {
7
- const { scanViews } = options;
7
+ const { routesFolders } = options;
8
8
 
9
9
  return VueRouter({
10
- routesFolder: scanViews(),
10
+ routesFolder: routesFolders,
11
11
  dts: "./src/types/typed-router.d.ts",
12
12
  extensions: [".vue"],
13
13
  importMode: "async",
@@ -1,114 +0,0 @@
1
- import type { ViewDirMeta } from "befly-shared/utils/scanViewsDir";
2
-
3
- import { existsSync } from "node:fs";
4
- import { readFile, readdir, writeFile } from "node:fs/promises";
5
- import { join, resolve } from "node:path";
6
- import { fileURLToPath } from "node:url";
7
-
8
- import { cleanDirName, extractDefinePageMetaFromScriptSetup, extractScriptSetupBlock, normalizeMenuTree } from "befly-shared/utils/scanViewsDir";
9
-
10
- type MenuConfig = {
11
- name: string;
12
- path: string;
13
- icon?: string;
14
- sort?: number;
15
- children?: MenuConfig[];
16
- };
17
-
18
- /**
19
- * 扫描 views 目录,构建菜单树(与 core/sync/syncMenu.ts 中的 scanViewsDir 逻辑一致)
20
- */
21
- export async function scanViewsDir(viewsDir: string, prefix: string, parentPath: string = ""): Promise<MenuConfig[]> {
22
- if (!existsSync(viewsDir)) {
23
- return [];
24
- }
25
-
26
- const menus: MenuConfig[] = [];
27
- const entries = await readdir(viewsDir, { withFileTypes: true });
28
-
29
- for (const entry of entries) {
30
- if (!entry.isDirectory() || entry.name === "components") {
31
- continue;
32
- }
33
-
34
- const dirPath = join(viewsDir, entry.name);
35
- const indexVuePath = join(dirPath, "index.vue");
36
-
37
- if (!existsSync(indexVuePath)) {
38
- continue;
39
- }
40
-
41
- let meta: ViewDirMeta | null = null;
42
- try {
43
- const content = await readFile(indexVuePath, "utf-8");
44
-
45
- const scriptSetup = extractScriptSetupBlock(content);
46
- if (!scriptSetup) {
47
- continue;
48
- }
49
-
50
- meta = extractDefinePageMetaFromScriptSetup(scriptSetup);
51
- if (!meta?.title) {
52
- continue;
53
- }
54
- } catch {
55
- continue;
56
- }
57
-
58
- if (!meta?.title) {
59
- continue;
60
- }
61
-
62
- const cleanName = cleanDirName(entry.name);
63
- let menuPath: string;
64
- if (cleanName === "index") {
65
- menuPath = parentPath;
66
- } else {
67
- menuPath = parentPath ? `${parentPath}/${cleanName}` : `/${cleanName}`;
68
- }
69
-
70
- const fullPath = prefix ? (menuPath ? `${prefix}${menuPath}` : prefix) : menuPath || "/";
71
-
72
- const menu: MenuConfig = {
73
- name: meta.title,
74
- path: fullPath,
75
- sort: meta.order ?? 999999
76
- };
77
-
78
- const children = await scanViewsDir(dirPath, prefix, menuPath);
79
- if (children.length > 0) {
80
- menu.children = children;
81
- }
82
-
83
- menus.push(menu);
84
- }
85
-
86
- menus.sort((a, b) => (a.sort ?? 999999) - (b.sort ?? 999999));
87
- return menus;
88
- }
89
-
90
- async function main(): Promise<void> {
91
- // 固定扫描目录:仓库 packages/admin/src/views
92
- // 固定输出:同目录下的 menu.json
93
- const fileDir = resolve(fileURLToPath(new URL(".", import.meta.url)));
94
- const repoRoot = resolve(fileDir, "..", "..", "..");
95
-
96
- const absDir = resolve(repoRoot, "packages", "admin", "src", "views");
97
- const outPath = join(absDir, "menu.json");
98
-
99
- if (!existsSync(absDir)) {
100
- process.stderr.write(`Missing views dir: ${absDir}\n`);
101
- process.exitCode = 1;
102
- return;
103
- }
104
-
105
- const menus = await scanViewsDir(absDir, "");
106
- const normalized = normalizeMenuTree(menus);
107
-
108
- const content = `${JSON.stringify(normalized, null, 4)}\n`;
109
- await writeFile(outPath, content, { encoding: "utf-8" });
110
-
111
- process.stdout.write(`Wrote ${normalized.length} root menus to ${outPath}\n`);
112
- }
113
-
114
- await main();
@@ -1,37 +0,0 @@
1
- import { defineConfig, presetAttributify, presetUno } from "unocss";
2
-
3
- /**
4
- * 创建 UnoCSS 配置
5
- */
6
- export function createUnoConfig(userConfig = {}) {
7
- const defaultConfig = {
8
- presets: [presetUno(), presetAttributify()],
9
- shortcuts: {
10
- "flex-center": "flex items-center justify-center",
11
- "flex-between": "flex items-center justify-between",
12
- "flex-col-center": "flex flex-col items-center justify-center"
13
- },
14
- theme: {
15
- colors: {
16
- primary: "#1890ff",
17
- success: "#52c41a",
18
- warning: "#faad14",
19
- danger: "#ff4d4f",
20
- info: "#1890ff"
21
- }
22
- }
23
- };
24
-
25
- return defineConfig({
26
- ...defaultConfig,
27
- ...userConfig,
28
- theme: {
29
- ...defaultConfig.theme,
30
- ...userConfig.theme
31
- },
32
- shortcuts: {
33
- ...defaultConfig.shortcuts,
34
- ...userConfig.shortcuts
35
- }
36
- });
37
- }
@@ -1,15 +0,0 @@
1
- import { compression } from "vite-plugin-compression2";
2
-
3
- /**
4
- * 创建文件压缩插件配置
5
- */
6
- export function createCompressionPlugin(options = {}) {
7
- const { threshold = 10240, algorithms = ["gzip", "brotliCompress"] } = options;
8
-
9
- return compression({
10
- include: /\.(html|xml|css|json|js|mjs|svg)$/i,
11
- threshold: threshold,
12
- algorithms: algorithms,
13
- deleteOriginalAssets: false
14
- });
15
- }
package/plugins/unocss.js DELETED
@@ -1,8 +0,0 @@
1
- import UnoCSS from "unocss/vite";
2
-
3
- /**
4
- * 创建 UnoCSS 插件配置
5
- */
6
- export function createUnoCSSPlugin() {
7
- return UnoCSS();
8
- }
@@ -1,100 +0,0 @@
1
- /**
2
- * @typedef {Object} ArrayToTreeOptions
3
- * @property {string=} idField
4
- * @property {string=} pidField
5
- * @property {string=} childrenField
6
- * @property {any=} rootPid
7
- * @property {string=} sortField
8
- * @property {(node: any) => any=} mapFn
9
- */
10
-
11
- /**
12
- * @template T
13
- * @param {T[]} items
14
- * @param {ArrayToTreeOptions=} options
15
- * @returns {T[]}
16
- */
17
- export function arrayToTree(items, options = {}) {
18
- const idField = typeof options.idField === "string" ? options.idField : "id";
19
- const pidField = typeof options.pidField === "string" ? options.pidField : "pid";
20
- const childrenField = typeof options.childrenField === "string" ? options.childrenField : "children";
21
- const rootPid = "rootPid" in options ? options.rootPid : 0;
22
- const sortField = "sortField" in options ? (typeof options.sortField === "string" && options.sortField.length > 0 ? options.sortField : null) : "sort";
23
- const mapFn = typeof options.mapFn === "function" ? options.mapFn : null;
24
-
25
- /**
26
- * pid -> items[]
27
- * @type {Map<any, T[]>}
28
- */
29
- const pidMap = new Map();
30
-
31
- for (const item of items) {
32
- // @ts-ignore
33
- const pid = item ? item[pidField] : undefined;
34
- const list = pidMap.get(pid);
35
- if (list) {
36
- list.push(item);
37
- } else {
38
- pidMap.set(pid, [item]);
39
- }
40
- }
41
-
42
- /**
43
- * @param {any} pid
44
- * @param {Set<any>} stack
45
- * @returns {T[]}
46
- */
47
- const build = (pid, stack) => {
48
- /** @type {T[]} */
49
- const tree = [];
50
- const list = pidMap.get(pid) || [];
51
-
52
- for (const item of list) {
53
- const node = Object.assign({}, item);
54
- const mappedNode = mapFn ? mapFn(node) : node;
55
-
56
- // 子节点 rootPid = node[id]
57
- // @ts-ignore
58
- const nextRootPid = mappedNode ? mappedNode[idField] : undefined;
59
-
60
- let children = [];
61
- if (!stack.has(nextRootPid)) {
62
- stack.add(nextRootPid);
63
- children = build(nextRootPid, stack);
64
- stack.delete(nextRootPid);
65
- }
66
-
67
- if (children.length > 0) {
68
- // @ts-ignore
69
- mappedNode[childrenField] = children;
70
- }
71
-
72
- tree.push(mappedNode);
73
- }
74
-
75
- if (sortField) {
76
- tree.sort((a, b) => {
77
- // @ts-ignore
78
- const av = a ? a[sortField] : undefined;
79
- // @ts-ignore
80
- const bv = b ? b[sortField] : undefined;
81
-
82
- const aMissing = av === undefined || av === null;
83
- const bMissing = bv === undefined || bv === null;
84
- if (aMissing && bMissing) return 0;
85
- if (aMissing) return 1;
86
- if (bMissing) return -1;
87
-
88
- if (typeof av === "number" && typeof bv === "number") {
89
- return av - bv;
90
- }
91
-
92
- return String(av).localeCompare(String(bv), undefined, { numeric: true, sensitivity: "base" });
93
- });
94
- }
95
-
96
- return tree;
97
- };
98
-
99
- return build(rootPid, new Set());
100
- }
@@ -1,7 +0,0 @@
1
- /**
2
- * UnoCSS 配置创建函数(对外稳定导出)
3
- *
4
- * 注意:befly-vite 不再从主入口导出 configs/plugins,统一通过 utils 子路径导入。
5
- */
6
-
7
- export { createUnoConfig } from "../configs/uno.config.js";
@@ -1,94 +0,0 @@
1
- /**
2
- * @typedef {Object} FieldClearOptions
3
- * @property {string[]=} pickKeys
4
- * @property {string[]=} omitKeys
5
- * @property {any[]=} keepValues
6
- * @property {any[]=} excludeValues
7
- * @property {Record<string, any>=} keepMap
8
- */
9
-
10
- function isObject(val) {
11
- return val !== null && typeof val === "object" && !Array.isArray(val);
12
- }
13
-
14
- function isArray(val) {
15
- return Array.isArray(val);
16
- }
17
-
18
- /**
19
- * 清理对象/数组字段
20
- * - 支持 pick/omit/keepValues/excludeValues
21
- * - 支持 keepMap 强制保留
22
- * @template T
23
- * @param {T|T[]} data
24
- * @param {FieldClearOptions=} options
25
- * @returns {any}
26
- */
27
- export function fieldClear(data, options = {}) {
28
- const pickKeys = options.pickKeys;
29
- const omitKeys = options.omitKeys;
30
- const keepValues = options.keepValues;
31
- const excludeValues = options.excludeValues;
32
- const keepMap = options.keepMap;
33
-
34
- const filterObj = (obj) => {
35
- /** @type {Record<string, any>} */
36
- const result = {};
37
-
38
- let keys = Object.keys(obj);
39
- if (pickKeys && pickKeys.length) {
40
- keys = keys.filter((k) => pickKeys.includes(k));
41
- }
42
- if (omitKeys && omitKeys.length) {
43
- keys = keys.filter((k) => !omitKeys.includes(k));
44
- }
45
-
46
- for (const key of keys) {
47
- const value = obj[key];
48
-
49
- // 1. keepMap 优先
50
- if (keepMap && Object.prototype.hasOwnProperty.call(keepMap, key)) {
51
- if (Object.is(keepMap[key], value)) {
52
- result[key] = value;
53
- continue;
54
- }
55
- }
56
-
57
- // 2. keepValues
58
- if (keepValues && keepValues.length && !keepValues.includes(value)) {
59
- continue;
60
- }
61
-
62
- // 3. excludeValues
63
- if (excludeValues && excludeValues.length && excludeValues.includes(value)) {
64
- continue;
65
- }
66
-
67
- result[key] = value;
68
- }
69
-
70
- return result;
71
- };
72
-
73
- if (isArray(data)) {
74
- return data
75
- .map((item) => {
76
- if (isObject(item)) {
77
- return filterObj(item);
78
- }
79
- return item;
80
- })
81
- .filter((item) => {
82
- if (isObject(item)) {
83
- return Object.keys(item).length > 0;
84
- }
85
- return true;
86
- });
87
- }
88
-
89
- if (isObject(data)) {
90
- return filterObj(data);
91
- }
92
-
93
- return data;
94
- }
@@ -1,21 +0,0 @@
1
- /**
2
- * 密码哈希工具(浏览器侧)
3
- * 使用 SHA-256 + 盐值对密码进行单向哈希
4
- * @param {string} password
5
- * @param {string=} salt
6
- * @returns {Promise<string>} 十六进制哈希
7
- */
8
- export async function hashPassword(password, salt = "befly") {
9
- const data = String(password) + String(salt);
10
-
11
- const encoder = new TextEncoder();
12
- const dataBuffer = encoder.encode(data);
13
-
14
- // Web Crypto API
15
- const hashBuffer = await crypto.subtle.digest("SHA-256", dataBuffer);
16
-
17
- const hashArray = Array.from(new Uint8Array(hashBuffer));
18
- const hashHex = hashArray.map((b) => b.toString(16).padStart(2, "0")).join("");
19
-
20
- return hashHex;
21
- }
package/utils/router.js DELETED
@@ -1,109 +0,0 @@
1
- /**
2
- * 路由相关工具函数(守卫 / 布局装配 / resolver 等)
3
- */
4
-
5
- import { Layouts, applyLayouts } from "./layouts.js";
6
-
7
- /**
8
- * 规范化路由 path:去尾随 "/"(根路径 "/" 例外)。
9
- *
10
- * @param {any} path
11
- * @returns {any}
12
- */
13
- export function normalizeRoutePath(path) {
14
- if (typeof path !== "string") {
15
- return path;
16
- }
17
-
18
- const normalized = path.replace(/\/+$/, "");
19
- return normalized.length === 0 ? "/" : normalized;
20
- }
21
-
22
- /**
23
- * 应用一个最小可用的 token 鉴权守卫(业务方提供 token 获取方式与路径)。
24
- *
25
- * 约定:当路由 meta.public === true 时认为是公开路由。
26
- *
27
- * @param {import('vue-router').Router} router
28
- * @param {{
29
- * getToken: () => any,
30
- * loginPath: string,
31
- * homePath: string
32
- * }} options
33
- */
34
- export function applyTokenAuthGuard(router, options) {
35
- const normalizedLoginPath = normalizeRoutePath(options.loginPath);
36
- const normalizedHomePath = normalizeRoutePath(options.homePath);
37
-
38
- router.beforeEach(async (to, _from, next) => {
39
- const token = options.getToken();
40
- const toPath = normalizeRoutePath(to.path);
41
-
42
- // 0. 根路径重定向
43
- if (toPath === "/") {
44
- return next(token ? normalizedHomePath : normalizedLoginPath);
45
- }
46
-
47
- // 1. 未登录且访问非公开路由 → 跳转登录
48
- if (!token && to.meta?.public !== true && toPath !== normalizedLoginPath) {
49
- return next(normalizedLoginPath);
50
- }
51
-
52
- // 2. 已登录访问登录页 → 跳转首页
53
- if (token && toPath === normalizedLoginPath) {
54
- return next(normalizedHomePath);
55
- }
56
-
57
- next();
58
- });
59
- }
60
-
61
- /**
62
- * 将“组件/懒加载函数/Promise”统一转换为 Vue Router 可接受的懒加载 component 函数。
63
- *
64
- * - 如果已经是函数(通常是 `() => import(...)`),直接返回。
65
- * - 否则包一层函数(使其变成 lazy component)。
66
- *
67
- * @param {any} value
68
- * @returns {any}
69
- */
70
- function toLazyComponent(value) {
71
- if (typeof value === "function") {
72
- return value;
73
- }
74
-
75
- return () => value;
76
- }
77
-
78
- /**
79
- * 创建布局组件解析器(resolver)。
80
- *
81
- * @param {{
82
- * resolveDefaultLayout: () => any,
83
- * resolveNamedLayout: (layoutName: string) => any,
84
- * defaultLayoutName?: string
85
- * }} options
86
- * @returns {(layoutName: string) => any}
87
- */
88
- export function createLayoutComponentResolver(options) {
89
- const defaultLayoutName = options.defaultLayoutName || "default";
90
-
91
- return (layoutName) => {
92
- if (layoutName === defaultLayoutName) {
93
- return toLazyComponent(options.resolveDefaultLayout());
94
- }
95
-
96
- return toLazyComponent(options.resolveNamedLayout(layoutName));
97
- };
98
- }
99
-
100
- /**
101
- * 将 auto-routes 的 routes 按 `_数字` 规则套用布局组件,并输出 Vue Router 的 RouteRecordRaw[]。
102
- *
103
- * @param {any[]} routes
104
- * @param {(layoutName: string) => any} resolveLayoutComponent
105
- * @returns {import('vue-router').RouteRecordRaw[]}
106
- */
107
- export function buildLayoutRoutes(routes, resolveLayoutComponent) {
108
- return applyLayouts(Layouts(routes), resolveLayoutComponent);
109
- }
@@ -1,59 +0,0 @@
1
- import { existsSync, readdirSync, realpathSync } from "node:fs";
2
- import { join } from "node:path";
3
-
4
- /**
5
- * 扫描项目和所有 @befly-addon 包的视图目录
6
- * 用于 unplugin-vue-router 的 routesFolder 配置
7
- *
8
- * 约定:addon 只允许从 adminViews 扫描路由:
9
- * - <addonRoot>/adminViews
10
- *
11
- * 注意:此函数只能在 vite.config.js 中使用(Node.js 环境),不能在浏览器中使用
12
- * @returns {Array<{ src: string, path: string, exclude: string[] }>} 路由文件夹配置数组
13
- */
14
- export function scanViews() {
15
- const appRoot = process.cwd();
16
- const addonBasePath = join(appRoot, "node_modules", "@befly-addon");
17
-
18
- /** @type {Array<{ src: string, path: string, exclude: string[] }>} */
19
- const routesFolders = [];
20
-
21
- // 1. 项目自身 views
22
- const appViewsPath = join(appRoot, "src", "views");
23
- if (existsSync(appViewsPath)) {
24
- routesFolders.push({
25
- src: realpathSync(appViewsPath),
26
- path: "",
27
- exclude: ["**/components/**"]
28
- });
29
- }
30
-
31
- // 2. 扫描 @befly-addon/*/adminViews(仅此目录允许生成 addon 路由)
32
- if (!existsSync(addonBasePath)) {
33
- return routesFolders;
34
- }
35
-
36
- try {
37
- const addonDirs = readdirSync(addonBasePath);
38
-
39
- for (const addonName of addonDirs) {
40
- const addonPath = join(addonBasePath, addonName);
41
- if (!existsSync(addonPath)) {
42
- continue;
43
- }
44
-
45
- const adminViewsPath = join(addonPath, "adminViews");
46
- if (existsSync(adminViewsPath)) {
47
- routesFolders.push({
48
- src: realpathSync(adminViewsPath),
49
- path: `addon/${addonName}/`,
50
- exclude: ["**/components/**"]
51
- });
52
- }
53
- }
54
- } catch {
55
- // 扫描失败保持静默,避免影响 Vite 启动
56
- }
57
-
58
- return routesFolders;
59
- }
@@ -1,41 +0,0 @@
1
- /**
2
- * 为表格列添加默认配置
3
- * @param {any[]} columns
4
- * @param {Record<string, any>=} customConfig
5
- * @returns {any[]}
6
- */
7
- export function withDefaultColumns(columns, customConfig = {}) {
8
- /** @type {Record<string, any>} */
9
- const specialColumnConfig = Object.assign(
10
- {
11
- operation: { width: 100, align: "center", fixed: "right" },
12
- state: { width: 100, align: "center" },
13
- sort: { width: 100, align: "center" },
14
- id: { width: 200, align: "center" }
15
- },
16
- customConfig
17
- );
18
-
19
- return columns.map((col) => {
20
- const colKey = col && col.colKey;
21
-
22
- let specialConfig = colKey ? specialColumnConfig[colKey] : undefined;
23
-
24
- if (!specialConfig && colKey && (colKey.endsWith("At") || colKey.endsWith("At2"))) {
25
- specialConfig = { align: "center" };
26
- }
27
-
28
- const base = {
29
- width: 150,
30
- ellipsis: true
31
- };
32
-
33
- const merged = Object.assign({}, base);
34
- if (specialConfig) {
35
- Object.assign(merged, specialConfig);
36
- }
37
- Object.assign(merged, col);
38
-
39
- return merged;
40
- });
41
- }