befly-admin 3.13.1 → 3.13.3

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.1",
4
- "gitHead": "acf16235e533550fa7a54478dafb3497208d2e9f",
3
+ "version": "3.13.3",
4
+ "gitHead": "c71ecec269032dcc0aa8ca995da4918f99f5ee0e",
5
5
  "private": false,
6
6
  "description": "Befly Admin - 基于 Vue3 + TDesign Vue Next 的后台管理系统",
7
7
  "files": [
@@ -30,7 +30,7 @@
30
30
  "dependencies": {
31
31
  "axios": "^1.13.5",
32
32
  "befly-admin-ui": "1.8.14",
33
- "befly-vite": "^1.5.0",
33
+ "befly-vite": "^1.5.1",
34
34
  "pinia": "^3.0.4",
35
35
  "tdesign-icons-vue-next": "^0.4.0",
36
36
  "tdesign-vue-next": "^1.18.2",
@@ -77,7 +77,6 @@
77
77
  <script setup>
78
78
  import { arrayToTree } from "befly-admin-ui/utils/arrayToTree";
79
79
  import { isString } from "../utils/is.js";
80
- import { CloseCircleIcon, CloudIcon, CodeIcon, LinkIcon, MenuIcon, SettingIcon, UserIcon } from "tdesign-icons-vue-next";
81
80
 
82
81
  import { reactive } from "vue";
83
82
 
@@ -1,140 +1,138 @@
1
- import axios, { AxiosHeaders } from "axios";
2
1
  import { cleanParams } from "befly-admin-ui/utils/cleanParams";
3
2
 
4
3
  import { $Storage } from "./storage";
5
4
 
6
- function toAxiosRequestConfig(options) {
7
- if (!options) {
8
- return undefined;
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;
9
12
  }
10
13
 
11
- const out = Object.assign({}, options);
12
- delete out["dropValues"];
13
- delete out["dropKeyValue"];
14
+ const params = new URLSearchParams();
15
+ for (const key of Object.keys(queryData)) {
16
+ const value = queryData[key];
14
17
 
15
- if (Object.keys(out).length === 0) {
16
- return undefined;
17
- }
18
+ if (value === null || value === undefined) {
19
+ continue;
20
+ }
18
21
 
19
- return out;
20
- }
22
+ if (Array.isArray(value)) {
23
+ for (const item of value) {
24
+ if (item === null || item === undefined) {
25
+ continue;
26
+ }
27
+ params.append(key, String(item));
28
+ }
29
+ continue;
30
+ }
21
31
 
22
- function isPlainRecord(value) {
23
- if (typeof value !== "object" || value === null) return false;
24
- if (Array.isArray(value)) return false;
25
- if (value instanceof FormData) return false;
26
- return true;
27
- }
32
+ params.append(key, String(value));
33
+ }
28
34
 
29
- function maybeCleanRequestData(data, cleanOptions) {
30
- if (!isPlainRecord(data)) {
31
- return data;
35
+ const query = params.toString();
36
+ if (query.length === 0) {
37
+ return finalUrl;
32
38
  }
33
39
 
34
- const dropValues = cleanOptions?.dropValues;
35
- const dropKeyValue = cleanOptions?.dropKeyValue;
36
- return cleanParams(data, dropValues ?? [], dropKeyValue);
40
+ return `${finalUrl}${finalUrl.includes("?") ? "&" : "?"}${query}`;
37
41
  }
38
42
 
39
- class HttpError extends Error {
40
- constructor(code, msg, data, rawError) {
41
- super(msg);
42
- this.name = "HttpError";
43
- this.code = code;
44
- this.data = data;
45
- this.rawError = rawError;
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}`);
46
52
  }
47
- }
48
53
 
49
- function isNormalizedHttpError(value) {
50
- return value instanceof HttpError;
51
- }
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 = {};
65
+ }
52
66
 
53
- async function showNetworkErrorToast() {
54
- try {
55
- // 在测试/非浏览器环境下,提示组件可能不可用;仅在需要展示提示时再加载。
56
- MessagePlugin.error("网络连接失败");
57
- } catch {
58
- // ignore
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;
70
+ headers.set("Content-Type", "application/json");
71
+ requestConfig.body = JSON.stringify(finalBody);
72
+ } else {
73
+ requestConfig.body = body;
74
+ }
59
75
  }
60
- }
61
76
 
62
- async function unwrapApiResponse(promise) {
77
+ requestConfig.method = method;
78
+ requestConfig.headers = headers;
79
+
63
80
  try {
64
- const response = await promise;
65
- const res = response.data;
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
+ });
89
+ }
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
+ });
99
+ }
66
100
 
101
+ const res = await response.json();
67
102
  if (res.code !== 0) {
68
- throw new HttpError(res.code, res.msg || "请求失败", res.data);
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"
112
+ });
69
113
  }
70
114
 
71
115
  return res;
72
116
  } catch (error) {
73
- // 业务错误:不显示提示,由业务层处理
74
- if (isNormalizedHttpError(error)) {
117
+ if (error instanceof Error && error.cause && typeof error.cause === "object" && error.cause.type === "api") {
75
118
  throw error;
76
119
  }
77
120
 
78
- await showNetworkErrorToast();
79
- throw new HttpError(-1, "网络连接失败", undefined, error);
121
+ throw new Error("网络连接失败", {
122
+ cause: error,
123
+ code: "runtime",
124
+ subsystem: "adminHttp",
125
+ operation: "request"
126
+ });
80
127
  }
81
128
  }
82
129
 
83
- // 创建 axios 实例
84
- const request = axios.create({
85
- baseURL: import.meta.env["VITE_API_BASE_URL"] || "",
86
- timeout: 10000,
87
- headers: {
88
- "Content-Type": "application/json"
89
- }
90
- });
91
-
92
- // 请求拦截器
93
- request.interceptors.request.use(
94
- (config) => {
95
- const token = $Storage.local.get("token");
96
- if (token) {
97
- const headers = new AxiosHeaders(config.headers);
98
- headers.set("Authorization", `Bearer ${token}`);
99
- config.headers = headers;
100
- }
101
- return config;
102
- },
103
- (error) => {
104
- return Promise.reject(error);
105
- }
106
- );
107
-
108
130
  async function httpGet(url, data, options) {
109
- const axiosConfig = toAxiosRequestConfig(options);
110
- const inputData = data ?? {};
111
- const cleanedData = maybeCleanRequestData(inputData, options);
112
-
113
- // 规则:GET 必须传 params;为空也传空对象
114
- const finalConfig = Object.assign({}, axiosConfig);
115
- finalConfig.params = cleanedData;
116
-
117
- return unwrapApiResponse(request.get(url, finalConfig));
131
+ return request("GET", url, data, options);
118
132
  }
119
133
 
120
134
  async function httpPost(url, data, options) {
121
- const axiosConfig = toAxiosRequestConfig(options);
122
- if (data === undefined) {
123
- // 规则:POST 必须传 body;为空也传空对象
124
- return unwrapApiResponse(request.post(url, {}, axiosConfig));
125
- }
126
-
127
- if (data instanceof FormData) {
128
- return unwrapApiResponse(request.post(url, data, axiosConfig));
129
- }
130
-
131
- const cleanedData = maybeCleanRequestData(data, options);
132
- if (Object.keys(cleanedData).length === 0) {
133
- // 规则:POST 必须传 body;清洗为空则传空对象
134
- return unwrapApiResponse(request.post(url, {}, axiosConfig));
135
- }
136
-
137
- return unwrapApiResponse(request.post(url, cleanedData, axiosConfig));
135
+ return request("POST", url, data, options);
138
136
  }
139
137
 
140
138
  /**