vue2server7 7.0.15 → 7.0.16

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,6 +1,6 @@
1
1
  {
2
2
  "name": "vue2server7",
3
- "version": "7.0.15",
3
+ "version": "7.0.16",
4
4
  "description": "",
5
5
  "scripts": {
6
6
  "dev": "nodemon --watch src --ext ts --exec \"ts-node src/app.ts\"",
package/test/1/main.ts ADDED
@@ -0,0 +1,52 @@
1
+ /**
2
+ * 应用入口文件
3
+ *
4
+ * 职责:
5
+ * 1. 创建 Vue 应用实例
6
+ * 2. 注册全局插件(UI 库、状态管理、路由、国际化)
7
+ * 3. 加载全局样式
8
+ * 4. 挂载到 DOM
9
+ * 5. 启动版本热更新检测
10
+ *
11
+ * 导入顺序说明(eslint simple-import-sort 已禁用):
12
+ * - 框架核心 → 第三方库 → 业务模块 → 样式 → 副作用模块
13
+ * 此处手动控制顺序以确保 permission 等副作用模块在路由注册之后加载
14
+ */
15
+
16
+ /* eslint-disable simple-import-sort/imports */
17
+ import { createApp } from 'vue';
18
+ import TDesign from 'tdesign-vue-next';
19
+
20
+ import App from './App.vue';
21
+ import router from './router';
22
+ import { store } from './store';
23
+ import i18n from './locales';
24
+
25
+ /** TDesign 组件库全局样式 */
26
+ import 'tdesign-vue-next/es/style/index.css';
27
+ /** 项目自定义全局样式(变量覆盖、通用布局等) */
28
+ import '@/style/index.less';
29
+ /**
30
+ * 路由权限守卫(副作用模块)
31
+ * 导入即执行 router.beforeEach,必须在 router 创建之后引入
32
+ */
33
+ import './permission';
34
+
35
+ const app = createApp(App);
36
+
37
+ app.use(TDesign); // TDesign 组件全局注册(Dialog、Message 等可通过插件式 API 调用)
38
+ app.use(store); // Pinia 状态管理
39
+ app.use(router); // Vue Router
40
+ app.use(i18n); // Vue I18n 国际化
41
+
42
+ /** 挂载到 public/index.html 中的 #app 节点 */
43
+ app.mount('#app');
44
+
45
+ /**
46
+ * 版本热更新检测
47
+ * 放在 mount 之后执行,确保 DOM 中的 <meta name="app-version"> 已就绪
48
+ * 详见 @/utils/version-update.ts
49
+ */
50
+ import { initVersionCheck } from './utils/version-update';
51
+
52
+ initVersionCheck();
@@ -0,0 +1,38 @@
1
+ import dayjs from 'dayjs';
2
+
3
+ /**
4
+ * 获取表头数据
5
+ *
6
+ * @export
7
+ * @param {string[]} dateTime
8
+ * @param {number} divideNum
9
+ * @returns {string[]} timeArray
10
+ */
11
+ export function getDateArray(dateTime: string[] = [], divideNum = 10): string[] {
12
+ const timeArray: string[] = [];
13
+ if (dateTime.length > 0) {
14
+ for (let i = 0; i < divideNum; i++) {
15
+ const dateAbsTime: number = (new Date(dateTime[1]).getTime() - new Date(dateTime[0]).getTime()) / divideNum;
16
+ const enhandTime: number = new Date(dateTime[0]).getTime() + dateAbsTime * i;
17
+ timeArray.push(dayjs(enhandTime).format('YYYY-MM-DD'));
18
+ }
19
+ }
20
+
21
+ return timeArray;
22
+ }
23
+
24
+ /**
25
+ * 获取随机数
26
+ *
27
+ * @param {number} [num]
28
+ * @returns {number} resultNum
29
+ */
30
+ export function getRandomArray(num = 100): number {
31
+ let resultNum = Number((Math.random() * num).toFixed(0));
32
+
33
+ if (resultNum <= 1) {
34
+ resultNum = 1;
35
+ }
36
+
37
+ return resultNum;
38
+ }
@@ -0,0 +1,110 @@
1
+ import type * as echarts from 'echarts/core';
2
+ import trim from 'lodash/trim';
3
+ import { Color } from 'tvision-color';
4
+
5
+ import type { TColorToken } from '@/config/color';
6
+ import type { ModeType } from '@/types/interface';
7
+
8
+ /**
9
+ * 依据主题类型获取颜色
10
+ *
11
+ * @export
12
+ * @returns {Array<string>} themeColorList
13
+ */
14
+ export function getColorFromTheme(): Array<string> {
15
+ const theme = trim(getComputedStyle(document.documentElement).getPropertyValue('--td-brand-color'));
16
+ const themeColorList = Color.getRandomPalette({
17
+ color: theme,
18
+ colorGamut: 'bright',
19
+ number: 8,
20
+ });
21
+
22
+ return themeColorList;
23
+ }
24
+
25
+ /** 图表颜色 */
26
+ export function getChartListColor(): Array<string> {
27
+ return getColorFromTheme();
28
+ }
29
+
30
+ /**
31
+ * 更改图表主题颜色
32
+ *
33
+ * @export
34
+ * @param {Array<string>} chartsList
35
+ */
36
+ export function changeChartsTheme(chartsList: echarts.EChartsType[]): void {
37
+ if (chartsList && chartsList.length) {
38
+ const chartChangeColor = getChartListColor();
39
+
40
+ for (let index = 0; index < chartsList.length; index++) {
41
+ const elementChart = chartsList[index];
42
+
43
+ if (elementChart) {
44
+ const optionVal = elementChart.getOption();
45
+
46
+ // 更改主题颜色
47
+ optionVal.color = chartChangeColor;
48
+
49
+ elementChart.setOption(optionVal, true);
50
+ }
51
+ }
52
+ }
53
+ }
54
+
55
+ /**
56
+ * 根据当前主题色、模式等情景 计算最后生成的色阶
57
+ */
58
+ export function generateColorMap(theme: string, colorPalette: Array<string>, mode: ModeType, brandColorIdx: number) {
59
+ const isDarkMode = mode === 'dark';
60
+
61
+ if (isDarkMode) {
62
+ colorPalette.reverse().map((color) => {
63
+ const [h, s, l] = Color.colorTransform(color, 'hex', 'hsl');
64
+ return Color.colorTransform([h, Number(s) - 4, l], 'hsl', 'hex');
65
+ });
66
+ brandColorIdx = 5;
67
+ colorPalette[0] = `${colorPalette[brandColorIdx]}20`;
68
+ }
69
+
70
+ const colorMap: TColorToken = {
71
+ '--td-brand-color': colorPalette[brandColorIdx], // 主题色
72
+ '--td-brand-color-1': colorPalette[0], // light
73
+ '--td-brand-color-2': colorPalette[1], // focus
74
+ '--td-brand-color-3': colorPalette[2], // disabled
75
+ '--td-brand-color-4': colorPalette[3],
76
+ '--td-brand-color-5': colorPalette[4],
77
+ '--td-brand-color-6': colorPalette[5],
78
+ '--td-brand-color-7': brandColorIdx > 0 ? colorPalette[brandColorIdx - 1] : theme, // hover
79
+ '--td-brand-color-8': colorPalette[brandColorIdx], // 主题色
80
+ '--td-brand-color-9': brandColorIdx > 8 ? theme : colorPalette[brandColorIdx + 1], // click
81
+ '--td-brand-color-10': colorPalette[9],
82
+ };
83
+ return colorMap;
84
+ }
85
+
86
+ /**
87
+ * 将生成的样式嵌入头部
88
+ */
89
+ export function insertThemeStylesheet(theme: string, colorMap: TColorToken, mode: ModeType) {
90
+ const isDarkMode = mode === 'dark';
91
+ const root = !isDarkMode ? `:root[theme-color='${theme}']` : `:root[theme-color='${theme}'][theme-mode='dark']`;
92
+
93
+ const styleSheet = document.createElement('style');
94
+ styleSheet.type = 'text/css';
95
+ styleSheet.textContent = `${root}{
96
+ --td-brand-color: ${colorMap['--td-brand-color']};
97
+ --td-brand-color-1: ${colorMap['--td-brand-color-1']};
98
+ --td-brand-color-2: ${colorMap['--td-brand-color-2']};
99
+ --td-brand-color-3: ${colorMap['--td-brand-color-3']};
100
+ --td-brand-color-4: ${colorMap['--td-brand-color-4']};
101
+ --td-brand-color-5: ${colorMap['--td-brand-color-5']};
102
+ --td-brand-color-6: ${colorMap['--td-brand-color-6']};
103
+ --td-brand-color-7: ${colorMap['--td-brand-color-7']};
104
+ --td-brand-color-8: ${colorMap['--td-brand-color-8']};
105
+ --td-brand-color-9: ${colorMap['--td-brand-color-9']};
106
+ --td-brand-color-10: ${colorMap['--td-brand-color-10']};
107
+ }`;
108
+
109
+ document.head.appendChild(styleSheet);
110
+ }
@@ -0,0 +1,9 @@
1
+ // 获取常用时间
2
+ import dayjs from 'dayjs';
3
+
4
+ export const LAST_7_DAYS = [dayjs().subtract(6, 'day').format('YYYY-MM-DD'), dayjs().format('YYYY-MM-DD')];
5
+
6
+ export const LAST_30_DAYS = [
7
+ dayjs().subtract(30, 'day').format('YYYY-MM-DD'),
8
+ dayjs().subtract(1, 'day').format('YYYY-MM-DD'),
9
+ ];
@@ -0,0 +1,294 @@
1
+ import type {
2
+ AxiosError,
3
+ AxiosInstance,
4
+ AxiosRequestConfig,
5
+ AxiosRequestHeaders,
6
+ AxiosResponse,
7
+ InternalAxiosRequestConfig,
8
+ } from 'axios';
9
+ import axios from 'axios';
10
+ import cloneDeep from 'lodash/cloneDeep';
11
+ import debounce from 'lodash/debounce';
12
+ import isFunction from 'lodash/isFunction';
13
+ import throttle from 'lodash/throttle';
14
+ import { stringify } from 'qs';
15
+
16
+ import { ContentTypeEnum } from '@/constants';
17
+ import type { AxiosRequestConfigRetry, RequestOptions, Result } from '@/types/axios';
18
+
19
+ import { AxiosCanceler } from './AxiosCancel';
20
+ import type { CreateAxiosOptions } from './AxiosTransform';
21
+
22
+ /**
23
+ * Axios 模块
24
+ */
25
+ export class VAxios {
26
+ /**
27
+ * Axios实例句柄
28
+ * @private
29
+ */
30
+ private instance: AxiosInstance;
31
+
32
+ /**
33
+ * Axios配置
34
+ * @private
35
+ */
36
+ private readonly options: CreateAxiosOptions;
37
+
38
+ constructor(options: CreateAxiosOptions) {
39
+ this.options = options;
40
+ this.instance = axios.create(options);
41
+ this.setupInterceptors();
42
+ }
43
+
44
+ /**
45
+ * 创建Axios实例
46
+ * @param config
47
+ * @private
48
+ */
49
+ private createAxios(config: CreateAxiosOptions): void {
50
+ this.instance = axios.create(config);
51
+ }
52
+
53
+ /**
54
+ * 获取数据处理类
55
+ * @private
56
+ */
57
+ private getTransform() {
58
+ const { transform } = this.options;
59
+ return transform;
60
+ }
61
+
62
+ /**
63
+ * 获取Axios实例
64
+ */
65
+ getAxios(): AxiosInstance {
66
+ return this.instance;
67
+ }
68
+
69
+ /**
70
+ * 配置Axios
71
+ * @param config
72
+ */
73
+ configAxios(config: CreateAxiosOptions) {
74
+ if (!this.instance) return;
75
+ this.createAxios(config);
76
+ }
77
+
78
+ /**
79
+ * 设置公共头部信息
80
+ * @param headers
81
+ */
82
+ setHeader(headers: Record<string, string>): void {
83
+ if (!this.instance) return;
84
+ Object.assign(this.instance.defaults.headers, headers);
85
+ }
86
+
87
+ /**
88
+ * 设置拦截器
89
+ * @private
90
+ */
91
+ private setupInterceptors() {
92
+ const transform = this.getTransform();
93
+ if (!transform) return;
94
+
95
+ const { requestInterceptors, requestInterceptorsCatch, responseInterceptors, responseInterceptorsCatch } =
96
+ transform;
97
+ const axiosCanceler = new AxiosCanceler();
98
+
99
+ // 请求拦截器
100
+ this.instance.interceptors.request.use((config: InternalAxiosRequestConfig) => {
101
+ // 如果忽略取消令牌,则不会取消重复的请求
102
+ // @ts-expect-error 请求参数通过嵌套赋值
103
+ const { ignoreCancelToken } = config.requestOptions;
104
+ const ignoreCancel = ignoreCancelToken ?? this.options.requestOptions?.ignoreCancelToken;
105
+ if (!ignoreCancel) axiosCanceler.addPending(config);
106
+
107
+ if (requestInterceptors && isFunction(requestInterceptors)) {
108
+ config = requestInterceptors(config, this.options) as InternalAxiosRequestConfig;
109
+ }
110
+
111
+ return config;
112
+ }, undefined);
113
+
114
+ // 请求错误处理
115
+ if (requestInterceptorsCatch && isFunction(requestInterceptorsCatch)) {
116
+ this.instance.interceptors.request.use(undefined, requestInterceptorsCatch);
117
+ }
118
+
119
+ // 响应结果处理
120
+ this.instance.interceptors.response.use((res: AxiosResponse) => {
121
+ if (res) axiosCanceler.removePending(res.config);
122
+ if (responseInterceptors && isFunction(responseInterceptors)) {
123
+ res = responseInterceptors(res);
124
+ }
125
+ return res;
126
+ }, undefined);
127
+
128
+ // 响应错误处理
129
+ if (responseInterceptorsCatch && isFunction(responseInterceptorsCatch)) {
130
+ this.instance.interceptors.response.use(undefined, (error) => responseInterceptorsCatch(error, this.instance));
131
+ }
132
+ }
133
+
134
+ /**
135
+ * 支持 FormData 请求格式
136
+ * @param config
137
+ */
138
+ supportFormData(config: AxiosRequestConfig) {
139
+ const headers = config.headers || (this.options.headers as AxiosRequestHeaders);
140
+ const contentType = headers?.['Content-Type'] || headers?.['content-type'];
141
+
142
+ if (
143
+ contentType !== ContentTypeEnum.FormURLEncoded ||
144
+ !Reflect.has(config, 'data') ||
145
+ config.method?.toUpperCase() === 'GET'
146
+ ) {
147
+ return config;
148
+ }
149
+
150
+ return {
151
+ ...config,
152
+ data: stringify(config.data, { arrayFormat: 'brackets' }),
153
+ };
154
+ }
155
+
156
+ /**
157
+ * 支持 params 序列化
158
+ * @param config
159
+ */
160
+ supportParamsStringify(config: AxiosRequestConfig) {
161
+ const headers = config.headers || this.options.headers;
162
+ const contentType = headers?.['Content-Type'] || headers?.['content-type'];
163
+
164
+ if (contentType === ContentTypeEnum.FormURLEncoded || !Reflect.has(config, 'params')) {
165
+ return config;
166
+ }
167
+
168
+ return {
169
+ ...config,
170
+ paramsSerializer: (params: any) => stringify(params, { arrayFormat: 'brackets' }),
171
+ };
172
+ }
173
+
174
+ get<T = any>(config: AxiosRequestConfig, options?: RequestOptions): Promise<T> {
175
+ return this.request({ ...config, method: 'GET' }, options);
176
+ }
177
+
178
+ post<T = any>(config: AxiosRequestConfig, options?: RequestOptions): Promise<T> {
179
+ return this.request({ ...config, method: 'POST' }, options);
180
+ }
181
+
182
+ put<T = any>(config: AxiosRequestConfig, options?: RequestOptions): Promise<T> {
183
+ return this.request({ ...config, method: 'PUT' }, options);
184
+ }
185
+
186
+ delete<T = any>(config: AxiosRequestConfig, options?: RequestOptions): Promise<T> {
187
+ return this.request({ ...config, method: 'DELETE' }, options);
188
+ }
189
+
190
+ patch<T = any>(config: AxiosRequestConfig, options?: RequestOptions): Promise<T> {
191
+ return this.request({ ...config, method: 'PATCH' }, options);
192
+ }
193
+
194
+ /**
195
+ * 上传文件封装
196
+ * @param key 文件所属的key
197
+ * @param file 文件
198
+ * @param config 请求配置
199
+ * @param options
200
+ */
201
+ upload<T = any>(key: string, file: File, config: AxiosRequestConfig, options?: RequestOptions): Promise<T> {
202
+ const params: FormData = config.params ?? new FormData();
203
+ params.append(key, file);
204
+
205
+ return this.request(
206
+ {
207
+ ...config,
208
+ method: 'POST',
209
+ headers: {
210
+ 'Content-Type': ContentTypeEnum.FormData,
211
+ },
212
+ params,
213
+ },
214
+ options,
215
+ );
216
+ }
217
+
218
+ /**
219
+ * 请求封装
220
+ * @param config
221
+ * @param options
222
+ */
223
+ request<T = any>(config: AxiosRequestConfigRetry, options?: RequestOptions): Promise<T> {
224
+ const { requestOptions } = this.options;
225
+
226
+ if (requestOptions.throttle !== undefined && requestOptions.debounce !== undefined) {
227
+ throw new Error('throttle and debounce cannot be set at the same time');
228
+ }
229
+
230
+ if (requestOptions.throttle && requestOptions.throttle.delay !== 0) {
231
+ return new Promise((resolve) => {
232
+ throttle(() => resolve(this.synthesisRequest(config, options)), requestOptions.throttle.delay);
233
+ });
234
+ }
235
+
236
+ if (requestOptions.debounce && requestOptions.debounce.delay !== 0) {
237
+ return new Promise((resolve) => {
238
+ debounce(() => resolve(this.synthesisRequest(config, options)), requestOptions.debounce.delay);
239
+ });
240
+ }
241
+
242
+ return this.synthesisRequest(config, options);
243
+ }
244
+
245
+ /**
246
+ * 请求方法
247
+ * @private
248
+ */
249
+ private async synthesisRequest<T = any>(config: AxiosRequestConfigRetry, options?: RequestOptions): Promise<T> {
250
+ let conf: CreateAxiosOptions = cloneDeep(config);
251
+ const transform = this.getTransform();
252
+
253
+ const { requestOptions } = this.options;
254
+
255
+ const opt: RequestOptions = { ...requestOptions, ...options };
256
+
257
+ const { beforeRequestHook, requestCatchHook, transformRequestHook } = transform || {};
258
+ if (beforeRequestHook && isFunction(beforeRequestHook)) {
259
+ conf = beforeRequestHook(conf, opt);
260
+ }
261
+ conf.requestOptions = opt;
262
+
263
+ conf = this.supportFormData(conf);
264
+ // 支持params数组参数格式化,因axios默认的toFormData即为brackets方式,无需配置paramsSerializer为qs,有需要可解除注释,参数参考qs文档
265
+ // conf = this.supportParamsStringify(conf);
266
+
267
+ return new Promise((resolve, reject) => {
268
+ this.instance
269
+ .request<any, AxiosResponse<Result>>(!config.retryCount ? conf : config)
270
+ .then((res: AxiosResponse<Result>) => {
271
+ if (transformRequestHook && isFunction(transformRequestHook)) {
272
+ try {
273
+ const ret = transformRequestHook(res, opt);
274
+ resolve(ret);
275
+ } catch (err) {
276
+ reject(err || new Error('请求错误!'));
277
+ }
278
+ return;
279
+ }
280
+ resolve(res as unknown as Promise<T>);
281
+ })
282
+ .catch((e: Error | AxiosError) => {
283
+ if (requestCatchHook && isFunction(requestCatchHook)) {
284
+ reject(requestCatchHook(e, opt));
285
+ return;
286
+ }
287
+ if (axios.isAxiosError(e)) {
288
+ // 在这里重写Axios的错误信息
289
+ }
290
+ reject(e);
291
+ });
292
+ });
293
+ }
294
+ }
@@ -0,0 +1,67 @@
1
+ import type { AxiosRequestConfig, Canceler } from 'axios';
2
+ import axios from 'axios';
3
+ import isFunction from 'lodash/isFunction';
4
+
5
+ // 存储请求与取消令牌的键值对列表
6
+ let pendingMap = new Map<string, Canceler>();
7
+
8
+ /**
9
+ * 获取请求Url
10
+ * @param config
11
+ */
12
+ export const getPendingUrl = (config: AxiosRequestConfig) => [config.method, config.url].join('&');
13
+
14
+ /**
15
+ * @description 请求管理器
16
+ */
17
+ export class AxiosCanceler {
18
+ /**
19
+ * 添加请求到列表中
20
+ * @param config
21
+ */
22
+ addPending(config: AxiosRequestConfig) {
23
+ this.removePending(config);
24
+ const url = getPendingUrl(config);
25
+ config.cancelToken =
26
+ config.cancelToken ||
27
+ new axios.CancelToken((cancel) => {
28
+ if (!pendingMap.has(url)) {
29
+ // 如果当前没有相同请求就添加
30
+ pendingMap.set(url, cancel);
31
+ }
32
+ });
33
+ }
34
+
35
+ /**
36
+ * 移除现有的所有请求
37
+ */
38
+ removeAllPending() {
39
+ pendingMap.forEach((cancel) => {
40
+ if (cancel && isFunction(cancel)) cancel();
41
+ });
42
+ pendingMap.clear();
43
+ }
44
+
45
+ /**
46
+ * 移除指定请求
47
+ * @param config
48
+ */
49
+ removePending(config: AxiosRequestConfig) {
50
+ const url = getPendingUrl(config);
51
+
52
+ if (pendingMap.has(url)) {
53
+ // If there is a current request identifier in pending,
54
+ // the current request needs to be cancelled and removed
55
+ const cancel = pendingMap.get(url);
56
+ if (cancel) cancel(url);
57
+ pendingMap.delete(url);
58
+ }
59
+ }
60
+
61
+ /**
62
+ * 重置
63
+ */
64
+ reset() {
65
+ pendingMap = new Map<string, Canceler>();
66
+ }
67
+ }
@@ -0,0 +1,63 @@
1
+ import type { AxiosError, AxiosInstance, AxiosRequestConfig, AxiosResponse } from 'axios';
2
+
3
+ import type { RequestOptions, Result } from '@/types/axios';
4
+
5
+ /**
6
+ * @description 创建Axios实例配置
7
+ */
8
+ export interface CreateAxiosOptions extends AxiosRequestConfig {
9
+ /**
10
+ * 请求验证方案
11
+ *
12
+ * https://developer.mozilla.org/en-US/docs/Web/HTTP/Authentication#authentication_schemes
13
+ */
14
+ authenticationScheme?: string;
15
+ /**
16
+ * 请求数据处理
17
+ */
18
+ transform?: AxiosTransform;
19
+ /**
20
+ * 请求配置
21
+ */
22
+ requestOptions?: RequestOptions;
23
+ }
24
+
25
+ /**
26
+ * Axios请求数据处理 抽象类
27
+ */
28
+ export abstract class AxiosTransform {
29
+ /**
30
+ * 请求前钩子
31
+ */
32
+ beforeRequestHook?: (_config: AxiosRequestConfig, _options: RequestOptions) => AxiosRequestConfig;
33
+
34
+ /**
35
+ * 数据处理前钩子
36
+ */
37
+ transformRequestHook?: <T = any>(_res: AxiosResponse<Result>, _options: RequestOptions) => T;
38
+
39
+ /**
40
+ * 请求失败钩子
41
+ */
42
+ requestCatchHook?: <T = any>(_e: Error | AxiosError, _options: RequestOptions) => Promise<T>;
43
+
44
+ /**
45
+ * 请求拦截器
46
+ */
47
+ requestInterceptors?: (_config: AxiosRequestConfig, _options: CreateAxiosOptions) => AxiosRequestConfig;
48
+
49
+ /**
50
+ * 响应拦截器
51
+ */
52
+ responseInterceptors?: (_res: AxiosResponse) => AxiosResponse;
53
+
54
+ /**
55
+ * 请求拦截器错误处理
56
+ */
57
+ requestInterceptorsCatch?: (_error: AxiosError) => void;
58
+
59
+ /**
60
+ * 响应拦截器错误处理
61
+ */
62
+ responseInterceptorsCatch?: (_error: AxiosError, _instance: AxiosInstance) => void;
63
+ }