befly-admin 3.13.3 → 3.13.5

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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "befly-admin",
3
- "version": "3.13.3",
4
- "gitHead": "c71ecec269032dcc0aa8ca995da4918f99f5ee0e",
3
+ "version": "3.13.5",
4
+ "gitHead": "9c4e8e90df43d744fb03b2ae473a2a566a19eee2",
5
5
  "private": false,
6
6
  "description": "Befly Admin - 基于 Vue3 + TDesign Vue Next 的后台管理系统",
7
7
  "files": [
@@ -29,8 +29,8 @@
29
29
  },
30
30
  "dependencies": {
31
31
  "axios": "^1.13.5",
32
- "befly-admin-ui": "1.8.14",
33
- "befly-vite": "^1.5.1",
32
+ "befly-admin-ui": "1.8.15",
33
+ "befly-vite": "^1.5.2",
34
34
  "pinia": "^3.0.4",
35
35
  "tdesign-icons-vue-next": "^0.4.0",
36
36
  "tdesign-vue-next": "^1.18.2",
@@ -296,10 +296,7 @@ async function loadList(options) {
296
296
  }
297
297
  }
298
298
 
299
- const res = await $Http.post(listEndpoint.path, data, {
300
- dropValues: listEndpoint.dropValues,
301
- dropKeyValue: listEndpoint.dropKeyValue
302
- });
299
+ const res = await $Http.json(listEndpoint.path, data, listEndpoint.dropValues, listEndpoint.dropKeyValue);
303
300
 
304
301
  // 并发保护:旧请求返回后不应覆盖新请求的状态
305
302
  if (seq !== requestSeq) {
@@ -332,7 +329,7 @@ async function loadList(options) {
332
329
  if (seq !== requestSeq) {
333
330
  return;
334
331
  }
335
- void MessagePlugin.error("加载数据失败");
332
+ MessagePlugin.error("加载数据失败");
336
333
  } finally {
337
334
  if (seq === requestSeq) {
338
335
  $Data.loading = false;
@@ -352,13 +349,13 @@ async function reload(options) {
352
349
 
353
350
  function onPageChange(info) {
354
351
  $Data.pager.currentPage = info.currentPage;
355
- void reload({ keepSelection: true });
352
+ reload({ keepSelection: true });
356
353
  }
357
354
 
358
355
  function onPageSizeChange(info) {
359
356
  $Data.pager.limit = info.pageSize;
360
357
  $Data.pager.currentPage = 1;
361
- void reload({ keepSelection: false });
358
+ reload({ keepSelection: false });
362
359
  }
363
360
 
364
361
  function onActiveChange(value, context) {
@@ -463,10 +460,7 @@ async function deleteRow(row) {
463
460
  }
464
461
  }
465
462
 
466
- await $Http.post(ep.path, data, {
467
- dropValues: ep.dropValues,
468
- dropKeyValue: ep.dropKeyValue
469
- });
463
+ await $Http.json(ep.path, data, ep.dropValues, ep.dropKeyValue);
470
464
 
471
465
  MessagePlugin.success("删除成功");
472
466
  destroy();
@@ -509,7 +503,7 @@ defineExpose({
509
503
 
510
504
  onMounted(() => {
511
505
  if (!props.autoLoad) return;
512
- void reload({ keepSelection: false });
506
+ reload({ keepSelection: false });
513
507
  });
514
508
  </script>
515
509
 
@@ -45,7 +45,7 @@
45
45
  <span>系统设置</span>
46
46
  </div>
47
47
  <div class="footer-user">
48
- <t-upload :action="$Config.uploadUrl" :headers="{ Authorization: $Storage.local.get('token') }" :show-upload-list="false" accept="image/*" @success="onAvatarUploadSuccess">
48
+ <t-upload :action="$Config.uploadUrl" :headers="uploadHeaders" :show-upload-list="false" accept="image/*" @success="onAvatarUploadSuccess">
49
49
  <div class="user-avatar" :class="{ 'has-avatar': $Data.userInfo.avatar }">
50
50
  <img v-if="$Data.userInfo.avatar" :src="$Data.userInfo.avatar" alt="avatar" />
51
51
  <UserIcon v-else style="width: 16px; height: 16px; color: #fff" />
@@ -82,6 +82,7 @@ import { reactive } from "vue";
82
82
 
83
83
  const router = useRouter();
84
84
  const route = useRoute();
85
+ const uploadHeaders = { Authorization: localStorage.getItem("yicode-token") || "" };
85
86
 
86
87
  const loginPath = "/core/login";
87
88
 
@@ -129,7 +130,7 @@ const $Data = reactive({
129
130
 
130
131
  async function fetchUserMenus() {
131
132
  try {
132
- const { data } = await $Http.post("/core/menu/all");
133
+ const { data } = await $Http.json("/core/menu/all");
133
134
  const lists = Array.isArray(data?.lists) ? data.lists : [];
134
135
 
135
136
  const normalizedLists = lists.map((menu) => {
@@ -144,7 +145,7 @@ async function fetchUserMenus() {
144
145
  $Data.userMenus = treeResult.tree;
145
146
  setActiveMenu();
146
147
  } catch (error) {
147
- void MessagePlugin.error("获取用户菜单失败");
148
+ MessagePlugin.error(error.msg || error.message || "获取用户菜单失败");
148
149
  }
149
150
  }
150
151
 
@@ -213,7 +214,7 @@ async function handleLogout() {
213
214
  }
214
215
 
215
216
  try {
216
- $Storage.local.remove("token");
217
+ localStorage.removeItem("yicode-token");
217
218
  await router.push(loginPath);
218
219
  MessagePlugin.success("退出成功");
219
220
  destroy();
@@ -239,7 +240,7 @@ function handleSettings() {
239
240
  function onAvatarUploadSuccess(res) {
240
241
  if (res.response?.code === 0 && res.response?.data?.url) {
241
242
  $Data.userInfo.avatar = res.response.data.url;
242
- void MessagePlugin.success("头像上传成功");
243
+ MessagePlugin.success("头像上传成功");
243
244
  }
244
245
  }
245
246
 
@@ -1,157 +1,92 @@
1
- import { cleanParams } from "befly-admin-ui/utils/cleanParams";
2
-
3
- import { $Storage } from "./storage";
4
-
5
- function buildUrl(url, queryData) {
6
- const baseUrl = import.meta.env["VITE_API_BASE_URL"] || "";
7
- const absoluteInputUrl = /^https?:\/\//i.test(url);
8
- let finalUrl = absoluteInputUrl ? String(url) : `${String(baseUrl).replace(/\/+$/, "")}/${String(url).replace(/^\/+/, "")}`;
9
-
10
- if (!queryData || typeof queryData !== "object") {
11
- return finalUrl;
12
- }
13
-
14
- const params = new URLSearchParams();
15
- for (const key of Object.keys(queryData)) {
16
- const value = queryData[key];
17
-
18
- if (value === null || value === undefined) {
19
- continue;
20
- }
21
-
22
- if (Array.isArray(value)) {
23
- for (const item of value) {
24
- if (item === null || item === undefined) {
25
- continue;
1
+ const API_BASE_URL = import.meta.env.VITE_API_BASE_URL;
2
+ async function requestPost(tokenGetter, url, data, dropValues, dropKeyValue) {
3
+ try {
4
+ const fullUrl = `${API_BASE_URL}${url}`;
5
+ const isForm = data instanceof FormData;
6
+ const dropList = Array.isArray(dropValues) ? dropValues : [null, undefined];
7
+ const dropKeyMap = dropKeyValue && typeof dropKeyValue === "object" ? dropKeyValue : {};
8
+ const shouldDrop = (key, value) => {
9
+ for (const dropValue of dropList) {
10
+ if (Object.is(value, dropValue)) {
11
+ return true;
26
12
  }
27
- params.append(key, String(item));
28
13
  }
29
- continue;
30
- }
31
-
32
- params.append(key, String(value));
33
- }
34
-
35
- const query = params.toString();
36
- if (query.length === 0) {
37
- return finalUrl;
38
- }
39
-
40
- return `${finalUrl}${finalUrl.includes("?") ? "&" : "?"}${query}`;
41
- }
42
-
43
- async function request(method, url, data, options) {
44
- const requestConfig = options ? Object.assign({}, options) : {};
45
- delete requestConfig["dropValues"];
46
- delete requestConfig["dropKeyValue"];
47
-
48
- const headers = new Headers(requestConfig.headers || {});
49
- const token = $Storage.local.get("token");
50
- if (token) {
51
- headers.set("Authorization", `Bearer ${token}`);
52
- }
53
-
54
- let finalUrl = String(url);
55
- if (method === "GET") {
56
- const inputData = data ?? {};
57
- const queryData = typeof inputData === "object" && inputData !== null && !Array.isArray(inputData) && !(inputData instanceof FormData) ? cleanParams(inputData, options?.dropValues ?? [], options?.dropKeyValue) : inputData;
58
- finalUrl = buildUrl(finalUrl, queryData);
59
- } else {
60
- finalUrl = buildUrl(finalUrl);
61
-
62
- let body = data;
63
- if (data === undefined) {
64
- body = {};
14
+ if (Array.isArray(dropKeyMap[key])) {
15
+ for (const dropValue of dropKeyMap[key]) {
16
+ if (Object.is(value, dropValue)) {
17
+ return true;
18
+ }
19
+ }
20
+ }
21
+ return false;
22
+ };
23
+ let payloadData = data ?? {};
24
+ if (isForm) {
25
+ const nextForm = new FormData();
26
+ for (const entry of payloadData.entries()) {
27
+ const key = entry[0];
28
+ const value = entry[1];
29
+ if (!shouldDrop(key, value)) {
30
+ nextForm.append(key, value);
31
+ }
32
+ }
33
+ payloadData = nextForm;
34
+ } else if (payloadData && typeof payloadData === "object") {
35
+ const nextData = {};
36
+ for (const key of Object.keys(payloadData)) {
37
+ const value = payloadData[key];
38
+ if (!shouldDrop(key, value)) {
39
+ nextData[key] = value;
40
+ }
41
+ }
42
+ payloadData = nextData;
65
43
  }
66
-
67
- if (!(body instanceof FormData)) {
68
- const cleaned = typeof body === "object" && body !== null && !Array.isArray(body) ? cleanParams(body, options?.dropValues ?? [], options?.dropKeyValue) : body;
69
- const finalBody = typeof cleaned === "object" && cleaned !== null && !Array.isArray(cleaned) && Object.keys(cleaned).length === 0 ? {} : cleaned;
44
+ const headers = new Headers();
45
+ if (!isForm) {
70
46
  headers.set("Content-Type", "application/json");
71
- requestConfig.body = JSON.stringify(finalBody);
72
- } else {
73
- requestConfig.body = body;
74
47
  }
75
- }
76
-
77
- requestConfig.method = method;
78
- requestConfig.headers = headers;
79
-
80
- try {
81
- const response = await fetch(finalUrl, requestConfig);
82
- if (!response.ok) {
83
- throw new Error(`HTTP ${response.status}`, {
84
- cause: null,
85
- code: "runtime",
86
- subsystem: "adminHttp",
87
- operation: "request"
88
- });
48
+ const tokenValue = typeof tokenGetter === "function" ? tokenGetter() : "";
49
+ if (tokenValue) {
50
+ headers.set("Authorization", "Bearer " + tokenValue);
89
51
  }
90
-
91
- const contentType = response.headers.get("content-type") || "";
92
- if (!contentType.toLowerCase().includes("application/json")) {
93
- throw new Error("响应不是 JSON", {
94
- cause: null,
95
- code: "runtime",
96
- subsystem: "adminHttp",
97
- operation: "request"
98
- });
52
+ const init = {
53
+ method: "POST",
54
+ headers: headers,
55
+ body: isForm ? payloadData : JSON.stringify(payloadData ?? {})
56
+ };
57
+ const res = await fetch(fullUrl, init);
58
+ let payload;
59
+ try {
60
+ payload = await res.json();
61
+ } catch {
62
+ if (res.ok) {
63
+ return Promise.reject({ msg: "响应解析失败" });
64
+ }
65
+ payload = {
66
+ code: res.status,
67
+ msg: `请求失败:HTTP ${res.status}`
68
+ };
99
69
  }
100
-
101
- const res = await response.json();
102
- if (res.code !== 0) {
103
- throw new Error(res.msg || "请求失败", {
104
- cause: {
105
- type: "api",
106
- apiCode: res.code,
107
- apiData: res.data
108
- },
109
- code: "runtime",
110
- subsystem: "adminHttp",
111
- operation: "request"
70
+ if (payload.code !== 0) {
71
+ return Promise.reject({
72
+ msg: payload.msg ?? "请求失败",
73
+ code: payload.code,
74
+ detail: payload.detail
112
75
  });
113
76
  }
114
-
115
- return res;
77
+ return Promise.resolve(payload);
116
78
  } catch (error) {
117
- if (error instanceof Error && error.cause && typeof error.cause === "object" && error.cause.type === "api") {
118
- throw error;
79
+ if (error && typeof error === "object") {
80
+ return Promise.reject(error);
119
81
  }
120
-
121
- throw new Error("网络连接失败", {
122
- cause: error,
123
- code: "runtime",
124
- subsystem: "adminHttp",
125
- operation: "request"
126
- });
82
+ return Promise.reject({ msg: "网络连接失败" });
127
83
  }
128
84
  }
129
-
130
- async function httpGet(url, data, options) {
131
- return request("GET", url, data, options);
132
- }
133
-
134
- async function httpPost(url, data, options) {
135
- return request("POST", url, data, options);
136
- }
137
-
138
- /**
139
- * 统一的 HTTP 请求对象(仅支持 GET 和 POST)
140
- * - 调用方式:$Http.get(url, data?, options?) / $Http.post(url, data?, options?)
141
- * - 重要行为:
142
- * - 未传 data / 清洗为空时:仍会发送空对象(GET: params={}, POST: body={})
143
- * - 原因:部分后端接口会基于“参数结构存在”触发默认逻辑/签名校验/中间件约束;
144
- * 因此这里不做“省略空对象”的优化。
145
- * - 传入 plain object 时:默认强制移除 null / undefined
146
- * - 可选参数清洗(第三参,且可与 axios config 混用):
147
- * - dropValues:全局丢弃值列表(仅对未配置 dropKeyValue 的 key 生效)
148
- * - dropKeyValue:按 key 精确配置丢弃值列表(覆盖全局 dropValues)
149
- *
150
- * 例子:保留 page=0,但丢弃 keyword="",并且其它字段应用全局 dropValues
151
- * - dropValues: [0, ""]
152
- * - dropKeyValue: { page: [], keyword: [""] }
153
- */
154
85
  export const $Http = {
155
- get: httpGet,
156
- post: httpPost
86
+ json: async (url, data, dropValues, dropKeyValue) => {
87
+ return requestPost(() => localStorage.getItem("yicode-token") ?? "", url, data ?? {}, dropValues, dropKeyValue);
88
+ },
89
+ form: async (url, formData, dropValues, dropKeyValue) => {
90
+ return requestPost(() => localStorage.getItem("yicode-token") ?? "", url, formData, dropValues, dropKeyValue);
91
+ }
157
92
  };
@@ -2,8 +2,6 @@ import { Layouts } from "befly-vite";
2
2
  import { createRouter, createWebHashHistory } from "vue-router";
3
3
  import { routes } from "vue-router/auto-routes";
4
4
 
5
- import { $Storage } from "./storage";
6
-
7
5
  // 应用自定义布局系统(同时可选注入根路径重定向)
8
6
  const finalRoutes = Layouts(routes, $Config.homePath, (layoutName) => {
9
7
  if (layoutName === "default") {
@@ -23,31 +21,31 @@ export const $Router = createRouter({
23
21
  });
24
22
 
25
23
  // 路由守卫 - 基础鉴权(最小实现:public 放行;未登录跳登录;已登录访问登录页跳首页)
26
- $Router.beforeEach((to, _from, next) => {
27
- const token = $Storage.local.get("token");
24
+ $Router.beforeEach((to, _from) => {
25
+ const token = localStorage.getItem("yicode-token");
28
26
  const toPath = to.path;
29
27
 
30
28
  // 根路径:按是否登录分流(兜底,避免 / 永远重定向到首页)
31
29
  if (toPath === "/") {
32
- return next(token ? $Config.homePath : $Config.loginPath);
30
+ return token ? $Config.homePath : $Config.loginPath;
33
31
  }
34
32
 
35
33
  // 公开路由放行
36
34
  if (to.meta?.["public"] === true) {
37
- return next();
35
+ return true;
38
36
  }
39
37
 
40
38
  // 未登录访问非公开路由 → 登录页
41
39
  if (!token && toPath !== $Config.loginPath) {
42
- return next($Config.loginPath);
40
+ return $Config.loginPath;
43
41
  }
44
42
 
45
43
  // 已登录访问登录页 → 首页
46
44
  if (token && toPath === $Config.loginPath) {
47
- return next($Config.homePath);
45
+ return $Config.homePath;
48
46
  }
49
47
 
50
- next();
48
+ return true;
51
49
  });
52
50
 
53
51
  // 路由就绪后处理
@@ -1,139 +0,0 @@
1
- /**
2
- * Storage 封装
3
- * 支持命名空间隔离,统一管理 localStorage 和 sessionStorage
4
- */
5
-
6
- // 获取命名空间
7
- const NAMESPACE = import.meta.env["VITE_STORAGE_NAMESPACE"] || "befly";
8
-
9
- class MemoryStorage {
10
- constructor() {
11
- this.map = new Map();
12
- }
13
-
14
- get length() {
15
- return this.map.size;
16
- }
17
-
18
- clear() {
19
- for (const key of this.map.keys()) {
20
- delete this[key];
21
- }
22
- this.map.clear();
23
- }
24
-
25
- getItem(key) {
26
- return this.map.get(key) ?? null;
27
- }
28
-
29
- key(index) {
30
- const keys = Array.from(this.map.keys());
31
- return keys[index] ?? null;
32
- }
33
-
34
- removeItem(key) {
35
- this.map.delete(key);
36
- delete this[key];
37
- }
38
-
39
- setItem(key, value) {
40
- this.map.set(key, value);
41
- this[key] = value;
42
- }
43
- }
44
-
45
- function getBrowserStorage(kind) {
46
- const win = globalThis;
47
- const storage = win.window?.[kind];
48
- if (storage) {
49
- return storage;
50
- }
51
- return new MemoryStorage();
52
- }
53
-
54
- /**
55
- * 存储操作类
56
- */
57
- class StorageManager {
58
- constructor(namespace = NAMESPACE) {
59
- this.localStorage = getBrowserStorage("localStorage");
60
- this.sessionStorage = getBrowserStorage("sessionStorage");
61
- this.namespace = namespace;
62
- }
63
-
64
- getKey(key) {
65
- return `${this.namespace}:${key}`;
66
- }
67
-
68
- createStorageOps(storage) {
69
- return {
70
- set: (key, value) => {
71
- try {
72
- const fullKey = this.getKey(key);
73
- const serializedValue = JSON.stringify(value);
74
- storage.setItem(fullKey, serializedValue);
75
- } catch {
76
- // ignore
77
- }
78
- },
79
-
80
- get: (key, defaultValue) => {
81
- try {
82
- const fullKey = this.getKey(key);
83
- const value = storage.getItem(fullKey);
84
- if (value === null) {
85
- return defaultValue ?? null;
86
- }
87
- return JSON.parse(value);
88
- } catch {
89
- return defaultValue ?? null;
90
- }
91
- },
92
-
93
- remove: (key) => {
94
- try {
95
- const fullKey = this.getKey(key);
96
- storage.removeItem(fullKey);
97
- } catch {
98
- // ignore
99
- }
100
- },
101
-
102
- clear: () => {
103
- try {
104
- const keys = Object.keys(storage);
105
- const prefix = `${this.namespace}:`;
106
- keys.forEach((key) => {
107
- if (key.startsWith(prefix)) {
108
- storage.removeItem(key);
109
- }
110
- });
111
- } catch {
112
- // ignore
113
- }
114
- },
115
-
116
- has: (key) => {
117
- const fullKey = this.getKey(key);
118
- return storage.getItem(fullKey) !== null;
119
- },
120
-
121
- keys: () => {
122
- const keys = Object.keys(storage);
123
- const prefix = `${this.namespace}:`;
124
- return keys.filter((key) => key.startsWith(prefix)).map((key) => key.substring(prefix.length));
125
- }
126
- };
127
- }
128
-
129
- get local() {
130
- return this.createStorageOps(this.localStorage);
131
- }
132
-
133
- get session() {
134
- return this.createStorageOps(this.sessionStorage);
135
- }
136
- }
137
-
138
- // 导出单例
139
- export const $Storage = new StorageManager();