befly-admin 3.10.4 → 3.11.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/index.html CHANGED
@@ -8,6 +8,6 @@
8
8
  </head>
9
9
  <body>
10
10
  <div id="app"></div>
11
- <script type="module" src="/src/main.js"></script>
11
+ <script type="module" src="/src/main.ts"></script>
12
12
  </body>
13
13
  </html>
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "befly-admin",
3
- "version": "3.10.4",
4
- "gitHead": "5a041666725189792d66b994d4d35a28822637ad",
3
+ "version": "3.11.0",
4
+ "gitHead": "3a9dc64d2eb69e646f4fd6eebd5d8dbe6516cb69",
5
5
  "private": false,
6
6
  "description": "Befly Admin - 基于 Vue3 + TDesign Vue Next 的后台管理系统",
7
7
  "files": [
@@ -28,7 +28,7 @@
28
28
  "preview": "vite preview"
29
29
  },
30
30
  "dependencies": {
31
- "@befly-addon/admin": "^1.6.3",
31
+ "@befly-addon/admin": "^1.7.0",
32
32
  "@iconify-json/lucide": "^1.2.83",
33
33
  "axios": "^1.13.2",
34
34
  "befly-vite": "^1.4.4",
@@ -135,7 +135,7 @@ const $Method = {
135
135
  // 获取用户菜单权限
136
136
  async fetchUserMenus() {
137
137
  try {
138
- const { data } = await $Http("/addon/admin/menu/all");
138
+ const { data } = await $Http.post("/addon/admin/menu/all");
139
139
  const lists = Array.isArray(data?.lists) ? data.lists : [];
140
140
 
141
141
  const bizMenus = [];
package/src/main.ts ADDED
@@ -0,0 +1,17 @@
1
+ // 引入 TDesign 样式
2
+ import "tdesign-vue-next/es/style/index.css";
3
+ // 引入 addonAdmin 的 CSS 变量
4
+ import "@befly-addon/admin/styles/variables.scss";
5
+ // 引入全局基础样式(reset、通用类、滚动条等)
6
+ import "./styles/global.scss";
7
+ import App from "./App.vue";
8
+
9
+ const app = createApp(App);
10
+
11
+ // 安装基础插件
12
+ app.use(createPinia());
13
+
14
+ // 使用路由
15
+ app.use($Router);
16
+
17
+ app.mount("#app");
@@ -3,27 +3,29 @@
3
3
  * 存放框架内置的配置变量,不建议修改
4
4
  */
5
5
 
6
+ export type BeflyAdminConfig = {
7
+ appTitle: string;
8
+ apiBaseUrl: string;
9
+ uploadUrl: string;
10
+ storageNamespace: string;
11
+ loginPath: string;
12
+ homePath: string;
13
+ isDev: boolean;
14
+ isProd: boolean;
15
+ };
16
+
6
17
  /**
7
18
  * 内置配置对象
8
19
  */
9
- export const $Config = {
10
- /** 应用标题 */
20
+ export const $Config: BeflyAdminConfig = {
11
21
  appTitle: import.meta.env.VITE_APP_TITLE || "野蜂飞舞",
12
- /** API 基础地址 */
13
22
  apiBaseUrl: import.meta.env.VITE_API_BASE_URL || "",
14
23
 
15
- /** 上传地址(优先使用 VITE_UPLOAD_URL;否则基于 VITE_API_BASE_URL 拼接 /upload) */
16
24
  uploadUrl: import.meta.env.VITE_UPLOAD_URL || ((import.meta.env.VITE_API_BASE_URL || "").length > 0 ? `${import.meta.env.VITE_API_BASE_URL}/upload` : "/upload"),
17
- /** 存储命名空间 */
18
25
  storageNamespace: import.meta.env.VITE_STORAGE_NAMESPACE || "befly_admin",
19
26
 
20
- /** 登录页路径(可通过 VITE_LOGIN_PATH 覆盖) */
21
27
  loginPath: import.meta.env.VITE_LOGIN_PATH || "/addon/admin/login",
22
-
23
- /** 首页路径(可通过 VITE_HOME_PATH 覆盖) */
24
28
  homePath: import.meta.env.VITE_HOME_PATH || "/addon/admin",
25
- /** 是否开发环境 */
26
29
  isDev: import.meta.env.DEV,
27
- /** 是否生产环境 */
28
30
  isProd: import.meta.env.PROD
29
31
  };
@@ -2,16 +2,17 @@
2
2
  * 全局状态管理
3
3
  * 集中管理所有全局数据,避免分散到多个 store 文件
4
4
  */
5
+
5
6
  export const useGlobal = defineStore("global", () => {
6
7
  // ==================== 全局数据 ====================
7
- const data = $ref({});
8
+ const data = reactive<Record<string, unknown>>({});
8
9
 
9
10
  // ==================== 全局方法 ====================
10
- const method = {};
11
+ const method: Record<string, unknown> = {};
11
12
 
12
13
  // ==================== 返回 ====================
13
14
  return {
14
- data,
15
- method
15
+ data: data,
16
+ method: method
16
17
  };
17
18
  });
@@ -0,0 +1,181 @@
1
+ import axios, { AxiosHeaders, type AxiosRequestConfig, type AxiosResponse, type InternalAxiosRequestConfig } from "axios";
2
+
3
+ import { cleanParams } from "../utils/cleanParams";
4
+ import { getViteEnvString } from "../utils/getViteEnvString";
5
+ import { $Storage } from "./storage";
6
+
7
+ export type HttpApiResponse<TData> = {
8
+ code: number;
9
+ msg?: string;
10
+ data?: TData;
11
+ };
12
+
13
+ export type HttpCleanParamsOptions = {
14
+ dropValues?: readonly unknown[];
15
+ dropKeyValue?: Record<string, readonly unknown[]>;
16
+ };
17
+
18
+ export type HttpClientOptions = AxiosRequestConfig & HttpCleanParamsOptions;
19
+
20
+ export type HttpGetData = Record<string, unknown>;
21
+ export type HttpPostData = Record<string, unknown> | FormData;
22
+
23
+ function toAxiosRequestConfig(options: HttpClientOptions | undefined): AxiosRequestConfig | undefined {
24
+ if (!options) {
25
+ return undefined;
26
+ }
27
+
28
+ const out = Object.assign({}, options) as Record<string, unknown>;
29
+ delete out["dropValues"];
30
+ delete out["dropKeyValue"];
31
+
32
+ if (Object.keys(out).length === 0) {
33
+ return undefined;
34
+ }
35
+
36
+ return out as AxiosRequestConfig;
37
+ }
38
+
39
+ function isPlainRecord(value: unknown): value is Record<string, unknown> {
40
+ if (typeof value !== "object" || value === null) return false;
41
+ if (Array.isArray(value)) return false;
42
+ if (value instanceof FormData) return false;
43
+ return true;
44
+ }
45
+
46
+ function maybeCleanRequestData(data: Record<string, unknown>, cleanOptions: HttpCleanParamsOptions | undefined): Record<string, unknown> {
47
+ if (!isPlainRecord(data)) {
48
+ return data;
49
+ }
50
+
51
+ const dropValues = cleanOptions?.dropValues;
52
+ const dropKeyValue = cleanOptions?.dropKeyValue;
53
+ return cleanParams(data, dropValues ?? [], dropKeyValue);
54
+ }
55
+
56
+ class HttpError extends Error {
57
+ public code: number;
58
+ public data?: unknown;
59
+ public rawError?: unknown;
60
+
61
+ public constructor(code: number, msg: string, data?: unknown, rawError?: unknown) {
62
+ super(msg);
63
+ this.name = "HttpError";
64
+ this.code = code;
65
+ this.data = data;
66
+ this.rawError = rawError;
67
+ }
68
+ }
69
+
70
+ function isNormalizedHttpError(value: unknown): value is HttpError {
71
+ return value instanceof HttpError;
72
+ }
73
+
74
+ async function showNetworkErrorToast(): Promise<void> {
75
+ // 在测试/非浏览器环境下,tdesign-vue-next 可能不可用;仅在需要展示提示时再加载。
76
+ try {
77
+ const mod = await import("tdesign-vue-next");
78
+ mod.MessagePlugin.error("网络连接失败");
79
+ } catch {
80
+ // ignore
81
+ }
82
+ }
83
+
84
+ async function unwrapApiResponse<TData>(promise: Promise<AxiosResponse<HttpApiResponse<TData>>>): Promise<HttpApiResponse<TData>> {
85
+ try {
86
+ const response = await promise;
87
+ const res = response.data;
88
+
89
+ if (res.code !== 0) {
90
+ throw new HttpError(res.code, res.msg || "请求失败", res.data);
91
+ }
92
+
93
+ return res;
94
+ } catch (error) {
95
+ // 业务错误:不显示提示,由业务层处理
96
+ if (isNormalizedHttpError(error)) {
97
+ throw error;
98
+ }
99
+
100
+ await showNetworkErrorToast();
101
+ throw new HttpError(-1, "网络连接失败", undefined, error);
102
+ }
103
+ }
104
+
105
+ // 创建 axios 实例
106
+ const request = axios.create({
107
+ baseURL: getViteEnvString("VITE_API_BASE_URL") || "",
108
+ timeout: 10000,
109
+ headers: {
110
+ "Content-Type": "application/json"
111
+ }
112
+ });
113
+
114
+ // 请求拦截器
115
+ request.interceptors.request.use(
116
+ (config: InternalAxiosRequestConfig) => {
117
+ const token = $Storage.local.get("token");
118
+ if (token) {
119
+ const headers = new AxiosHeaders(config.headers);
120
+ headers.set("Authorization", `Bearer ${token}`);
121
+ config.headers = headers;
122
+ }
123
+ return config;
124
+ },
125
+ (error: unknown) => {
126
+ return Promise.reject(error);
127
+ }
128
+ );
129
+
130
+ async function httpGet<TData>(url: string, data?: HttpGetData, options?: HttpClientOptions): Promise<HttpApiResponse<TData>> {
131
+ const axiosConfig = toAxiosRequestConfig(options);
132
+ const inputData = data ?? {};
133
+ const cleanedData = maybeCleanRequestData(inputData, options);
134
+
135
+ // 规则:GET 必须传 params;为空也传空对象
136
+ const finalConfig = Object.assign({}, axiosConfig);
137
+ (finalConfig as { params?: unknown }).params = cleanedData;
138
+
139
+ return unwrapApiResponse<TData>(request.get<HttpApiResponse<TData>>(url, finalConfig));
140
+ }
141
+
142
+ async function httpPost<TData>(url: string, data?: HttpPostData, options?: HttpClientOptions): Promise<HttpApiResponse<TData>> {
143
+ const axiosConfig = toAxiosRequestConfig(options);
144
+ if (data === undefined) {
145
+ // 规则:POST 必须传 body;为空也传空对象
146
+ return unwrapApiResponse<TData>(request.post<HttpApiResponse<TData>>(url, {}, axiosConfig));
147
+ }
148
+
149
+ if (data instanceof FormData) {
150
+ return unwrapApiResponse<TData>(request.post<HttpApiResponse<TData>>(url, data, axiosConfig));
151
+ }
152
+
153
+ const cleanedData = maybeCleanRequestData(data, options);
154
+ if (Object.keys(cleanedData).length === 0) {
155
+ // 规则:POST 必须传 body;清洗为空则传空对象
156
+ return unwrapApiResponse<TData>(request.post<HttpApiResponse<TData>>(url, {}, axiosConfig));
157
+ }
158
+
159
+ return unwrapApiResponse<TData>(request.post<HttpApiResponse<TData>>(url, cleanedData, axiosConfig));
160
+ }
161
+
162
+ /**
163
+ * 统一的 HTTP 请求对象(仅支持 GET 和 POST)
164
+ * - 调用方式:$Http.get(url, data?, options?) / $Http.post(url, data?, options?)
165
+ * - 重要行为:
166
+ * - 未传 data / 清洗为空时:仍会发送空对象(GET: params={}, POST: body={})
167
+ * - 原因:部分后端接口会基于“参数结构存在”触发默认逻辑/签名校验/中间件约束;
168
+ * 因此这里不做“省略空对象”的优化。
169
+ * - 传入 plain object 时:默认强制移除 null / undefined
170
+ * - 可选参数清洗(第三参,且可与 axios config 混用):
171
+ * - dropValues:全局丢弃值列表(仅对未配置 dropKeyValue 的 key 生效)
172
+ * - dropKeyValue:按 key 精确配置丢弃值列表(覆盖全局 dropValues)
173
+ *
174
+ * 例子:保留 page=0,但丢弃 keyword="",并且其它字段应用全局 dropValues
175
+ * - dropValues: [0, ""]
176
+ * - dropKeyValue: { page: [], keyword: [""] }
177
+ */
178
+ export const $Http = {
179
+ get: httpGet,
180
+ post: httpPost
181
+ };
@@ -1,20 +1,21 @@
1
- import { $Storage } from "@/plugins/storage";
2
1
  import { Layouts } from "befly-vite";
3
2
  import { createRouter, createWebHashHistory } from "vue-router";
4
3
  import { routes } from "vue-router/auto-routes";
5
4
 
5
+ import { $Storage } from "./storage";
6
+
6
7
  // 应用自定义布局系统(同时可选注入根路径重定向)
7
- const finalRoutes = Layouts(routes, $Config.homePath, (layoutName) => {
8
+ const finalRoutes = Layouts(routes, $Config.homePath, (layoutName: string) => {
8
9
  if (layoutName === "default") {
9
- return () => import("@/layouts/default.vue");
10
+ return () => import("../layouts/default.vue");
10
11
  }
11
12
 
12
- return () => import(`@/layouts/${layoutName}.vue`);
13
+ return () => import(`../layouts/${layoutName}.vue`);
13
14
  });
14
15
 
15
16
  /**
16
17
  * 创建并导出路由实例
17
- * 可直接在 main.js 中使用 app.use($Router)
18
+ * 可直接在 main.ts 中使用 app.use($Router)
18
19
  */
19
20
  export const $Router = createRouter({
20
21
  history: createWebHashHistory(import.meta.env.BASE_URL),
@@ -32,7 +33,7 @@ $Router.beforeEach((to, _from, next) => {
32
33
  }
33
34
 
34
35
  // 公开路由放行
35
- if (to.meta?.public === true) {
36
+ if (to.meta?.["public"] === true) {
36
37
  return next();
37
38
  }
38
39
 
@@ -0,0 +1,160 @@
1
+ /**
2
+ * Storage 封装
3
+ * 支持命名空间隔离,统一管理 localStorage 和 sessionStorage
4
+ */
5
+
6
+ import { getViteEnvString } from "../utils/getViteEnvString";
7
+
8
+ // 获取命名空间
9
+ const NAMESPACE = getViteEnvString("VITE_STORAGE_NAMESPACE") || "befly";
10
+
11
+ class MemoryStorage implements Storage {
12
+ private map: Map<string, string>;
13
+
14
+ public constructor() {
15
+ this.map = new Map<string, string>();
16
+ }
17
+
18
+ public get length(): number {
19
+ return this.map.size;
20
+ }
21
+
22
+ public clear(): void {
23
+ for (const key of this.map.keys()) {
24
+ delete (this as unknown as Record<string, unknown>)[key];
25
+ }
26
+ this.map.clear();
27
+ }
28
+
29
+ public getItem(key: string): string | null {
30
+ return this.map.get(key) ?? null;
31
+ }
32
+
33
+ public key(index: number): string | null {
34
+ const keys = Array.from(this.map.keys());
35
+ return keys[index] ?? null;
36
+ }
37
+
38
+ public removeItem(key: string): void {
39
+ this.map.delete(key);
40
+ delete (this as unknown as Record<string, unknown>)[key];
41
+ }
42
+
43
+ public setItem(key: string, value: string): void {
44
+ this.map.set(key, value);
45
+ (this as unknown as Record<string, unknown>)[key] = value;
46
+ }
47
+ }
48
+
49
+ function getBrowserStorage(kind: "localStorage" | "sessionStorage"): Storage {
50
+ const win = globalThis as unknown as { window?: { localStorage?: Storage; sessionStorage?: Storage } };
51
+ const storage = win.window?.[kind];
52
+ if (storage) {
53
+ return storage;
54
+ }
55
+ return new MemoryStorage();
56
+ }
57
+
58
+ type StorageOps = {
59
+ set: (key: string, value: unknown) => void;
60
+ get: {
61
+ (key: string): string | null;
62
+ (key: string, defaultValue: string | null): string | null;
63
+ <T>(key: string, defaultValue: T): T;
64
+ };
65
+ remove: (key: string) => void;
66
+ clear: () => void;
67
+ has: (key: string) => boolean;
68
+ keys: () => string[];
69
+ };
70
+
71
+ /**
72
+ * 存储操作类
73
+ */
74
+ class StorageManager {
75
+ private localStorage: Storage;
76
+ private sessionStorage: Storage;
77
+ private namespace: string;
78
+
79
+ public constructor(namespace: string = NAMESPACE) {
80
+ this.localStorage = getBrowserStorage("localStorage");
81
+ this.sessionStorage = getBrowserStorage("sessionStorage");
82
+ this.namespace = namespace;
83
+ }
84
+
85
+ private getKey(key: string): string {
86
+ return `${this.namespace}:${key}`;
87
+ }
88
+
89
+ private createStorageOps(storage: Storage): StorageOps {
90
+ return {
91
+ set: (key: string, value: unknown) => {
92
+ try {
93
+ const fullKey = this.getKey(key);
94
+ const serializedValue = JSON.stringify(value);
95
+ storage.setItem(fullKey, serializedValue);
96
+ } catch {
97
+ // ignore
98
+ }
99
+ },
100
+
101
+ get: ((key: string, defaultValue?: unknown) => {
102
+ try {
103
+ const fullKey = this.getKey(key);
104
+ const value = storage.getItem(fullKey);
105
+ if (value === null) {
106
+ return defaultValue ?? null;
107
+ }
108
+ return JSON.parse(value) as unknown;
109
+ } catch {
110
+ return defaultValue ?? null;
111
+ }
112
+ }) as StorageOps["get"],
113
+
114
+ remove: (key: string) => {
115
+ try {
116
+ const fullKey = this.getKey(key);
117
+ storage.removeItem(fullKey);
118
+ } catch {
119
+ // ignore
120
+ }
121
+ },
122
+
123
+ clear: () => {
124
+ try {
125
+ const keys = Object.keys(storage);
126
+ const prefix = `${this.namespace}:`;
127
+ keys.forEach((key) => {
128
+ if (key.startsWith(prefix)) {
129
+ storage.removeItem(key);
130
+ }
131
+ });
132
+ } catch {
133
+ // ignore
134
+ }
135
+ },
136
+
137
+ has: (key: string) => {
138
+ const fullKey = this.getKey(key);
139
+ return storage.getItem(fullKey) !== null;
140
+ },
141
+
142
+ keys: () => {
143
+ const keys = Object.keys(storage);
144
+ const prefix = `${this.namespace}:`;
145
+ return keys.filter((key) => key.startsWith(prefix)).map((key) => key.substring(prefix.length));
146
+ }
147
+ };
148
+ }
149
+
150
+ public get local(): StorageOps {
151
+ return this.createStorageOps(this.localStorage);
152
+ }
153
+
154
+ public get session(): StorageOps {
155
+ return this.createStorageOps(this.sessionStorage);
156
+ }
157
+ }
158
+
159
+ // 导出单例
160
+ export const $Storage = new StorageManager();
@@ -6,15 +6,16 @@
6
6
  // biome-ignore lint: disable
7
7
  export {}
8
8
  declare global {
9
- const $Config: typeof import('../plugins/config.js').$Config
10
- const $Http: typeof import('../plugins/http.js').$Http
11
- const $Router: typeof import('../plugins/router.js').$Router
12
- const $Storage: typeof import('../plugins/storage.js').$Storage
9
+ const $Config: typeof import('../plugins/config').$Config
10
+ const $Http: typeof import('../plugins/http').$Http
11
+ const $Router: typeof import('../plugins/router').$Router
12
+ const $Storage: typeof import('../plugins/storage').$Storage
13
13
  const DialogPlugin: typeof import("tdesign-vue-next").DialogPlugin
14
14
  const EffectScope: typeof import('vue').EffectScope
15
15
  const MessagePlugin: typeof import('tdesign-vue-next').MessagePlugin
16
16
  const acceptHMRUpdate: typeof import('pinia').acceptHMRUpdate
17
17
  const arrayToTree: typeof import('../utils/arrayToTree').arrayToTree
18
+ const cleanParams: typeof import('../utils/cleanParams').cleanParams
18
19
  const computed: typeof import('vue').computed
19
20
  const createApp: typeof import('vue').createApp
20
21
  const createPinia: typeof import('pinia').createPinia
@@ -27,6 +28,7 @@ declare global {
27
28
  const getCurrentInstance: typeof import('vue').getCurrentInstance
28
29
  const getCurrentScope: typeof import('vue').getCurrentScope
29
30
  const getCurrentWatcher: typeof import('vue').getCurrentWatcher
31
+ const getViteEnvString: typeof import('../utils/getViteEnvString').getViteEnvString
30
32
  const h: typeof import('vue').h
31
33
  const inject: typeof import('vue').inject
32
34
  const isProxy: typeof import('vue').isProxy
@@ -62,7 +64,7 @@ declare global {
62
64
  const readonly: typeof import('vue').readonly
63
65
  const ref: typeof import('vue').ref
64
66
  const resolveComponent: typeof import('vue').resolveComponent
65
- const router: typeof import('../plugins/router.js').router
67
+ const router: typeof import('../plugins/router').router
66
68
  const setActivePinia: typeof import('pinia').setActivePinia
67
69
  const setMapStoreSuffix: typeof import('pinia').setMapStoreSuffix
68
70
  const shallowReactive: typeof import('vue').shallowReactive
@@ -78,7 +80,7 @@ declare global {
78
80
  const useAttrs: typeof import('vue').useAttrs
79
81
  const useCssModule: typeof import('vue').useCssModule
80
82
  const useCssVars: typeof import('vue').useCssVars
81
- const useGlobal: typeof import('../plugins/global.js').useGlobal
83
+ const useGlobal: typeof import('../plugins/global').useGlobal
82
84
  const useId: typeof import('vue').useId
83
85
  const useModel: typeof import('vue').useModel
84
86
  const useRoute: typeof import('vue-router').useRoute
@@ -98,6 +100,12 @@ declare global {
98
100
  // @ts-ignore
99
101
  export type { ArrayToTreeResult } from '../utils/arrayToTree'
100
102
  import('../utils/arrayToTree')
103
+ // @ts-ignore
104
+ export type { BeflyAdminConfig } from '../plugins/config'
105
+ import('../plugins/config')
106
+ // @ts-ignore
107
+ export type { HttpApiResponse, HttpCleanParamsOptions, HttpClientOptions, HttpGetData, HttpPostData } from '../plugins/http'
108
+ import('../plugins/http')
101
109
  }
102
110
 
103
111
  // for vue template auto import
@@ -105,14 +113,15 @@ import { UnwrapRef } from 'vue'
105
113
  declare module 'vue' {
106
114
  interface GlobalComponents {}
107
115
  interface ComponentCustomProperties {
108
- readonly $Config: UnwrapRef<typeof import('../plugins/config.js')['$Config']>
109
- readonly $Http: UnwrapRef<typeof import('../plugins/http.js')['$Http']>
110
- readonly $Router: UnwrapRef<typeof import('../plugins/router.js')['$Router']>
111
- readonly $Storage: UnwrapRef<typeof import('../plugins/storage.js')['$Storage']>
116
+ readonly $Config: UnwrapRef<typeof import('../plugins/config')['$Config']>
117
+ readonly $Http: UnwrapRef<typeof import('../plugins/http')['$Http']>
118
+ readonly $Router: UnwrapRef<typeof import('../plugins/router')['$Router']>
119
+ readonly $Storage: UnwrapRef<typeof import('../plugins/storage')['$Storage']>
112
120
  readonly EffectScope: UnwrapRef<typeof import('vue')['EffectScope']>
113
121
  readonly MessagePlugin: UnwrapRef<typeof import('tdesign-vue-next')['MessagePlugin']>
114
122
  readonly acceptHMRUpdate: UnwrapRef<typeof import('pinia')['acceptHMRUpdate']>
115
123
  readonly arrayToTree: UnwrapRef<typeof import('../utils/arrayToTree')['arrayToTree']>
124
+ readonly cleanParams: UnwrapRef<typeof import('../utils/cleanParams')['cleanParams']>
116
125
  readonly computed: UnwrapRef<typeof import('vue')['computed']>
117
126
  readonly createApp: UnwrapRef<typeof import('vue')['createApp']>
118
127
  readonly createPinia: UnwrapRef<typeof import('pinia')['createPinia']>
@@ -125,6 +134,7 @@ declare module 'vue' {
125
134
  readonly getCurrentInstance: UnwrapRef<typeof import('vue')['getCurrentInstance']>
126
135
  readonly getCurrentScope: UnwrapRef<typeof import('vue')['getCurrentScope']>
127
136
  readonly getCurrentWatcher: UnwrapRef<typeof import('vue')['getCurrentWatcher']>
137
+ readonly getViteEnvString: UnwrapRef<typeof import('../utils/getViteEnvString')['getViteEnvString']>
128
138
  readonly h: UnwrapRef<typeof import('vue')['h']>
129
139
  readonly inject: UnwrapRef<typeof import('vue')['inject']>
130
140
  readonly isProxy: UnwrapRef<typeof import('vue')['isProxy']>
@@ -175,7 +185,7 @@ declare module 'vue' {
175
185
  readonly useAttrs: UnwrapRef<typeof import('vue')['useAttrs']>
176
186
  readonly useCssModule: UnwrapRef<typeof import('vue')['useCssModule']>
177
187
  readonly useCssVars: UnwrapRef<typeof import('vue')['useCssVars']>
178
- readonly useGlobal: UnwrapRef<typeof import('../plugins/global.js')['useGlobal']>
188
+ readonly useGlobal: UnwrapRef<typeof import('../plugins/global')['useGlobal']>
179
189
  readonly useId: UnwrapRef<typeof import('vue')['useId']>
180
190
  readonly useModel: UnwrapRef<typeof import('vue')['useModel']>
181
191
  readonly useRoute: UnwrapRef<typeof import('vue-router')['useRoute']>
@@ -0,0 +1,9 @@
1
+ declare module "befly-vite" {
2
+ import type { RouteRecordRaw } from "vue-router";
3
+
4
+ export function Layouts(
5
+ routes: readonly RouteRecordRaw[],
6
+ homePath?: string,
7
+ resolver?: (layoutName: string) => unknown
8
+ ): readonly RouteRecordRaw[];
9
+ }
@@ -0,0 +1,6 @@
1
+ declare module "*.vue" {
2
+ import type { DefineComponent } from "vue";
3
+
4
+ const component: DefineComponent<Record<string, unknown>, Record<string, unknown>, unknown>;
5
+ export default component;
6
+ }
@@ -0,0 +1 @@
1
+ /// <reference types="vite/client" />
@@ -0,0 +1,39 @@
1
+ export function cleanParams<TData extends Record<string, unknown>>(data: TData, dropValues?: readonly unknown[], dropKeyValue?: Record<string, readonly unknown[]>): Partial<TData> {
2
+ const globalDropValues = dropValues ?? [];
3
+ const perKeyDropValues = dropKeyValue ?? {};
4
+
5
+ const globalDropSet = new Set<unknown>(globalDropValues);
6
+
7
+ const out: Record<string, unknown> = {};
8
+
9
+ for (const key of Object.keys(data)) {
10
+ const value = data[key];
11
+
12
+ // 默认强制移除 null / undefined
13
+ if (value === null || value === undefined) {
14
+ continue;
15
+ }
16
+
17
+ // 如果该 key 配了规则:以 key 规则为准,不再应用全局 dropValues
18
+ if (Object.hasOwn(perKeyDropValues, key)) {
19
+ const keyDropValues = perKeyDropValues[key] ?? [];
20
+ const keyDropSet = new Set<unknown>(keyDropValues);
21
+
22
+ if (keyDropSet.has(value)) {
23
+ continue;
24
+ }
25
+
26
+ out[key] = value;
27
+ continue;
28
+ }
29
+
30
+ // 未配置 key 规则:应用全局 dropValues
31
+ if (globalDropSet.has(value)) {
32
+ continue;
33
+ }
34
+
35
+ out[key] = value;
36
+ }
37
+
38
+ return out as Partial<TData>;
39
+ }
@@ -0,0 +1,4 @@
1
+ export function getViteEnvString(key: string): string | undefined {
2
+ const env = (import.meta as unknown as { env?: Record<string, string> }).env;
3
+ return env?.[key];
4
+ }
package/src/main.js DELETED
@@ -1,47 +0,0 @@
1
- // 引入 TDesign 组件
2
- import { Table as TTable } from "tdesign-vue-next";
3
- // 引入 TDesign 样式
4
- import "tdesign-vue-next/es/style/index.css";
5
- // 引入 addonAdmin 的 CSS 变量
6
- import "@befly-addon/admin/styles/variables.scss";
7
- // 引入全局基础样式(reset、通用类、滚动条等)
8
- import "@/styles/global.scss";
9
- import App from "./App.vue";
10
-
11
- const app = createApp(App);
12
-
13
- // 安装基础插件
14
- app.use(createPinia());
15
-
16
- // 使用路由
17
- app.use($Router);
18
-
19
- // 全局配置 TTable 默认属性
20
- app.component("TTable", {
21
- ...TTable,
22
- props: {
23
- ...TTable.props,
24
- bordered: {
25
- type: [Boolean, Object],
26
- default: () => ({ cell: "horizontal" })
27
- },
28
- size: {
29
- type: String,
30
- default: "small"
31
- },
32
- height: {
33
- type: [String, Number],
34
- default: "100%"
35
- },
36
- selectOnRowClick: {
37
- type: Boolean,
38
- default: true
39
- },
40
- activeRowType: {
41
- type: String,
42
- default: "single"
43
- }
44
- }
45
- });
46
-
47
- app.mount("#app");
@@ -1,86 +0,0 @@
1
- import axios from "axios";
2
- import { MessagePlugin } from "tdesign-vue-next";
3
-
4
- import { $Storage } from "./storage";
5
-
6
- /**
7
- * @typedef {Object} ApiResponse
8
- * @property {0 | 1} code
9
- * @property {string} msg
10
- * @property {any} data
11
- */
12
-
13
- // 创建 axios 实例
14
- const request = axios.create({
15
- baseURL: import.meta.env.VITE_API_BASE_URL,
16
- timeout: 10000,
17
- headers: {
18
- "Content-Type": "application/json"
19
- }
20
- });
21
-
22
- // 请求拦截器
23
- request.interceptors.request.use(
24
- (config) => {
25
- // 添加 token
26
- const token = $Storage.local.get("token");
27
- if (token) {
28
- config.headers.Authorization = `Bearer ${token}`;
29
- }
30
- return config;
31
- },
32
- (error) => {
33
- return Promise.reject(error);
34
- }
35
- );
36
-
37
- // 响应拦截器
38
- request.interceptors.response.use(
39
- (response) => {
40
- const res = response.data;
41
-
42
- // 如果code不是0,说明业务失败(不显示提示,由业务层处理)
43
- if (res.code !== 0) {
44
- return Promise.reject({
45
- code: res.code,
46
- msg: res.msg || "请求失败",
47
- data: res.data
48
- });
49
- }
50
-
51
- // 成功时返回 data
52
- return res;
53
- },
54
- (error) => {
55
- MessagePlugin.error("网络连接失败");
56
- return Promise.reject({
57
- code: -1,
58
- msg: "网络连接失败",
59
- error: error
60
- });
61
- }
62
- );
63
-
64
- /**
65
- * 统一的 HTTP 请求方法(仅支持 GET 和 POST)
66
- * @template T
67
- * @param {string} url - 请求路径
68
- * @param {any} [data={}] - 请求数据,默认为空对象
69
- * @param {'get' | 'post'} [method='post'] - 请求方法,默认为 'post',可选 'get' | 'post'
70
- * @param {import('axios').AxiosRequestConfig} [config] - axios 请求配置
71
- * @returns {Promise<any>} 成功返回 data,失败抛出 {code, msg, data} 对象
72
- */
73
- export function $Http(url, data = {}, method = "post", config) {
74
- const methodLower = method.toLowerCase();
75
-
76
- // GET 请求将 data 作为 params
77
- if (methodLower === "get") {
78
- return request.get(url, {
79
- ...config,
80
- params: data
81
- });
82
- }
83
-
84
- // POST 请求将 data 作为 body
85
- return request.post(url, data, config);
86
- }
@@ -1,137 +0,0 @@
1
- /**
2
- * Storage 封装
3
- * 支持命名空间隔离,统一管理 localStorage 和 sessionStorage
4
- */
5
-
6
- // 获取命名空间
7
- const NAMESPACE = import.meta.env.VITE_STORAGE_NAMESPACE || "befly";
8
-
9
- /**
10
- * 存储操作类
11
- */
12
- class StorageManager {
13
- /**
14
- * @param {string} namespace
15
- */
16
- constructor(namespace = NAMESPACE) {
17
- this.localStorage = window.localStorage;
18
- this.sessionStorage = window.sessionStorage;
19
- this.namespace = namespace;
20
- }
21
-
22
- /**
23
- * 生成带命名空间的key
24
- * @param {string} key
25
- * @returns {string}
26
- */
27
- getKey(key) {
28
- return `${this.namespace}:${key}`;
29
- }
30
-
31
- /**
32
- * 创建存储操作方法
33
- * @param {Storage} storage
34
- */
35
- createStorageOps(storage) {
36
- return {
37
- /**
38
- * 设置存储
39
- * @param {string} key - 键名
40
- * @param {any} value - 值(自动序列化)
41
- * @returns {void}
42
- */
43
- set: (key, value) => {
44
- try {
45
- const fullKey = this.getKey(key);
46
- const serializedValue = JSON.stringify(value);
47
- storage.setItem(fullKey, serializedValue);
48
- } catch {}
49
- },
50
-
51
- /**
52
- * 获取存储
53
- * @template T
54
- * @param {string} key - 键名
55
- * @param {T | null} [defaultValue=null] - 默认值
56
- * @returns {T | null} 解析后的值
57
- */
58
- get: (key, defaultValue = null) => {
59
- try {
60
- const fullKey = this.getKey(key);
61
- const value = storage.getItem(fullKey);
62
- if (value === null) {
63
- return defaultValue;
64
- }
65
- return JSON.parse(value);
66
- } catch {
67
- return defaultValue;
68
- }
69
- },
70
-
71
- /**
72
- * 删除存储
73
- * @param {string} key - 键名
74
- * @returns {void}
75
- */
76
- remove: (key) => {
77
- try {
78
- const fullKey = this.getKey(key);
79
- storage.removeItem(fullKey);
80
- } catch {}
81
- },
82
-
83
- /**
84
- * 清空当前命名空间下的所有存储
85
- * @returns {void}
86
- */
87
- clear: () => {
88
- try {
89
- const keys = Object.keys(storage);
90
- const prefix = `${this.namespace}:`;
91
- keys.forEach((key) => {
92
- if (key.startsWith(prefix)) {
93
- storage.removeItem(key);
94
- }
95
- });
96
- } catch {}
97
- },
98
-
99
- /**
100
- * 判断是否存在某个键
101
- * @param {string} key - 键名
102
- * @returns {boolean}
103
- */
104
- has: (key) => {
105
- const fullKey = this.getKey(key);
106
- return storage.getItem(fullKey) !== null;
107
- },
108
-
109
- /**
110
- * 获取所有键名
111
- * @returns {string[]} 去除命名空间前缀的键名列表
112
- */
113
- keys: () => {
114
- const keys = Object.keys(storage);
115
- const prefix = `${this.namespace}:`;
116
- return keys.filter((key) => key.startsWith(prefix)).map((key) => key.substring(prefix.length));
117
- }
118
- };
119
- }
120
-
121
- /**
122
- * localStorage 操作方法
123
- */
124
- get local() {
125
- return this.createStorageOps(this.localStorage);
126
- }
127
-
128
- /**
129
- * sessionStorage 操作方法
130
- */
131
- get session() {
132
- return this.createStorageOps(this.sessionStorage);
133
- }
134
- }
135
-
136
- // 导出单例
137
- export const $Storage = new StorageManager();