vue3-admin-gpt 1.0.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/.env.development +14 -0
- package/.env.production +14 -0
- package/LICENSE +21 -0
- package/README.en.md +106 -0
- package/README.md +104 -0
- package/build-zip.cjs +53 -0
- package/cli.js +110 -0
- package/jsconfig.json +9 -0
- package/package.json +92 -0
- package/public/index.html +20 -0
- package/public/robots.txt +2 -0
- package/rspack.config.js +282 -0
- package/rspack.js +162 -0
- package/src/App.vue +9 -0
- package/src/api/icon.js +9 -0
- package/src/api/router.js +9 -0
- package/src/api/table.js +25 -0
- package/src/api/tree.js +9 -0
- package/src/api/user.js +34 -0
- package/src/assets/error_images/401.png +0 -0
- package/src/assets/error_images/404.png +0 -0
- package/src/assets/error_images/cloud.png +0 -0
- package/src/assets/login_images/background.jpg +0 -0
- package/src/assets/logo.png +0 -0
- package/src/assets/qr_logo/lqr_logo.png +0 -0
- package/src/assets/vuejs-fill.svg +4 -0
- package/src/components/VabPageHeader/index.vue +133 -0
- package/src/config/index.js +7 -0
- package/src/config/net.config.js +20 -0
- package/src/config/permission.js +136 -0
- package/src/config/setting.config.js +62 -0
- package/src/config/settings.js +6 -0
- package/src/config/theme.config.js +14 -0
- package/src/layouts/EmptyLayout.vue +3 -0
- package/src/layouts/components/VabAppMain/index.vue +109 -0
- package/src/layouts/components/VabAvatar/index.vue +255 -0
- package/src/layouts/components/VabBreadcrumb/index.vue +61 -0
- package/src/layouts/components/VabFullScreen/index.vue +61 -0
- package/src/layouts/components/VabLogo/index.vue +94 -0
- package/src/layouts/components/VabNav/index.vue +176 -0
- package/src/layouts/components/VabSide/components/VabMenuItem.vue +80 -0
- package/src/layouts/components/VabSide/components/VabSideItem.vue +100 -0
- package/src/layouts/components/VabSide/components/VabSubmenu.vue +56 -0
- package/src/layouts/components/VabSide/index.vue +123 -0
- package/src/layouts/components/VabTabs/index.vue +500 -0
- package/src/layouts/components/VabTheme/index.vue +603 -0
- package/src/layouts/components/VabTop/index.vue +286 -0
- package/src/layouts/export.js +29 -0
- package/src/layouts/index.vue +339 -0
- package/src/main.js +40 -0
- package/src/plugins/echarts.js +4 -0
- package/src/plugins/index.js +44 -0
- package/src/plugins/support.js +16 -0
- package/src/router/index.js +400 -0
- package/src/store/index.js +26 -0
- package/src/store/modules/errorLog.js +27 -0
- package/src/store/modules/routes.js +60 -0
- package/src/store/modules/settings.js +73 -0
- package/src/store/modules/table.js +22 -0
- package/src/store/modules/tabsBar.js +109 -0
- package/src/store/modules/user.js +131 -0
- package/src/styles/element-variables.scss +13 -0
- package/src/styles/loading.scss +345 -0
- package/src/styles/nav-icons.scss +52 -0
- package/src/styles/normalize.scss +353 -0
- package/src/styles/spinner/dots.css +68 -0
- package/src/styles/spinner/gauge.css +104 -0
- package/src/styles/spinner/inner-circles.css +51 -0
- package/src/styles/spinner/plus.css +341 -0
- package/src/styles/themes/default.scss +1 -0
- package/src/styles/transition.scss +18 -0
- package/src/styles/vab.scss +476 -0
- package/src/styles/variables.scss +69 -0
- package/src/utils/accessToken.js +56 -0
- package/src/utils/eventBus.js +8 -0
- package/src/utils/handleRoutes.js +100 -0
- package/src/utils/index.js +231 -0
- package/src/utils/message.js +67 -0
- package/src/utils/pageTitle.js +11 -0
- package/src/utils/password.js +43 -0
- package/src/utils/permission.js +19 -0
- package/src/utils/request.js +187 -0
- package/src/utils/static.js +81 -0
- package/src/utils/vab.js +218 -0
- package/src/utils/validate.js +48 -0
- package/src/views/401.vue +302 -0
- package/src/views/404.vue +302 -0
- package/src/views/demo/index.vue +591 -0
- package/src/views/index/index.vue +1489 -0
- package/src/views/login/index.vue +456 -0
- package/src/views/register/index.vue +524 -0
- package/src/views/vab/calendar.vue +488 -0
- package/src/views/vab/campaign.vue +1006 -0
- package/src/views/vab/chart.vue +189 -0
- package/src/views/vab/customer.vue +666 -0
- package/src/views/vab/editor.vue +84 -0
- package/src/views/vab/form.vue +151 -0
- package/src/views/vab/help.vue +390 -0
- package/src/views/vab/icon.vue +113 -0
- package/src/views/vab/knowledge.vue +820 -0
- package/src/views/vab/nested/menu1/menu2/menu3.vue +29 -0
- package/src/views/vab/nested/menu1/menu2.vue +33 -0
- package/src/views/vab/nested/menu1.vue +33 -0
- package/src/views/vab/nested.vue +97 -0
- package/src/views/vab/notification.vue +416 -0
- package/src/views/vab/order.vue +507 -0
- package/src/views/vab/permissions.vue +214 -0
- package/src/views/vab/product.vue +724 -0
- package/src/views/vab/project.vue +559 -0
- package/src/views/vab/settings.vue +319 -0
- package/src/views/vab/statistics.vue +431 -0
- package/src/views/vab/table.vue +110 -0
- package/src/views/vab/task.vue +613 -0
- package/src/views/vab/team.vue +662 -0
- package/src/views/vab/tree.vue +44 -0
- package/src/views/vab/upload.vue +180 -0
- package/src/views/vab/vue3Demo/index.vue +103 -0
- package/src/views/vab/workflow.vue +863 -0
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @description 格式化时间
|
|
3
|
+
* @param time
|
|
4
|
+
* @param cFormat
|
|
5
|
+
* @returns {string|null}
|
|
6
|
+
*/
|
|
7
|
+
export function parseTime(time, cFormat) {
|
|
8
|
+
if (arguments.length === 0) {
|
|
9
|
+
return null
|
|
10
|
+
}
|
|
11
|
+
const format = cFormat || '{y}-{m}-{d} {h}:{i}:{s}'
|
|
12
|
+
let date
|
|
13
|
+
if (typeof time === 'object') {
|
|
14
|
+
date = time
|
|
15
|
+
} else {
|
|
16
|
+
if (typeof time === 'string' && /^[0-9]+$/.test(time)) {
|
|
17
|
+
time = parseInt(time)
|
|
18
|
+
}
|
|
19
|
+
if (typeof time === 'number' && time.toString().length === 10) {
|
|
20
|
+
time = time * 1000
|
|
21
|
+
}
|
|
22
|
+
date = new Date(time)
|
|
23
|
+
}
|
|
24
|
+
const formatObj = {
|
|
25
|
+
y: date.getFullYear(),
|
|
26
|
+
m: date.getMonth() + 1,
|
|
27
|
+
d: date.getDate(),
|
|
28
|
+
h: date.getHours(),
|
|
29
|
+
i: date.getMinutes(),
|
|
30
|
+
s: date.getSeconds(),
|
|
31
|
+
a: date.getDay(),
|
|
32
|
+
}
|
|
33
|
+
const time_str = format.replace(/{(y|m|d|h|i|s|a)+}/g, (result, key) => {
|
|
34
|
+
let value = formatObj[key]
|
|
35
|
+
if (key === 'a') {
|
|
36
|
+
return ['日', '一', '二', '三', '四', '五', '六'][value]
|
|
37
|
+
}
|
|
38
|
+
if (result.length > 0 && value < 10) {
|
|
39
|
+
value = `0${value}`
|
|
40
|
+
}
|
|
41
|
+
return value || 0
|
|
42
|
+
})
|
|
43
|
+
return time_str
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* @description 格式化时间
|
|
48
|
+
* @param time
|
|
49
|
+
* @param option
|
|
50
|
+
* @returns {string}
|
|
51
|
+
*/
|
|
52
|
+
export function formatTime(time, option) {
|
|
53
|
+
if (`${time}`.length === 10) {
|
|
54
|
+
time = parseInt(time) * 1000
|
|
55
|
+
} else {
|
|
56
|
+
time = +time
|
|
57
|
+
}
|
|
58
|
+
const d = new Date(time)
|
|
59
|
+
const now = Date.now()
|
|
60
|
+
|
|
61
|
+
const diff = (now - d) / 1000
|
|
62
|
+
|
|
63
|
+
if (diff < 30) {
|
|
64
|
+
return '刚刚'
|
|
65
|
+
} else if (diff < 3600) {
|
|
66
|
+
// less 1 hour
|
|
67
|
+
return `${Math.ceil(diff / 60)}分钟前`
|
|
68
|
+
} else if (diff < 3600 * 24) {
|
|
69
|
+
return `${Math.ceil(diff / 3600)}小时前`
|
|
70
|
+
} else if (diff < 3600 * 24 * 2) {
|
|
71
|
+
return '1天前'
|
|
72
|
+
}
|
|
73
|
+
if (option) {
|
|
74
|
+
return parseTime(time, option)
|
|
75
|
+
} else {
|
|
76
|
+
return `${d.getMonth() + 1}月${d.getDate()}日${d.getHours()}时${d.getMinutes()}分`
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* @description 将url请求参数转为json格式
|
|
82
|
+
* @param url
|
|
83
|
+
* @returns {{}|any}
|
|
84
|
+
*/
|
|
85
|
+
export function paramObj(url) {
|
|
86
|
+
const search = url.split('?')[1]
|
|
87
|
+
if (!search) {
|
|
88
|
+
return {}
|
|
89
|
+
}
|
|
90
|
+
return JSON.parse(`{"${decodeURIComponent(search).replace(/"/g, '\\"').replace(/&/g, '","').replace(/=/g, '":"').replace(/\+/g, ' ')}"}`)
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* @description 父子关系的数组转换成树形结构数据
|
|
95
|
+
* @param data
|
|
96
|
+
* @returns {*}
|
|
97
|
+
*/
|
|
98
|
+
export function translateDataToTree(data) {
|
|
99
|
+
const parent = data.filter((value) => value.parentId === 'undefined' || value.parentId == null)
|
|
100
|
+
const children = data.filter((value) => value.parentId !== 'undefined' && value.parentId != null)
|
|
101
|
+
const translator = (parent, children) => {
|
|
102
|
+
parent.forEach((parent) => {
|
|
103
|
+
children.forEach((current, index) => {
|
|
104
|
+
if (current.parentId === parent.id) {
|
|
105
|
+
const temp = JSON.parse(JSON.stringify(children))
|
|
106
|
+
temp.splice(index, 1)
|
|
107
|
+
translator([current], temp)
|
|
108
|
+
typeof parent.children !== 'undefined' ? parent.children.push(current) : (parent.children = [current])
|
|
109
|
+
}
|
|
110
|
+
})
|
|
111
|
+
})
|
|
112
|
+
}
|
|
113
|
+
translator(parent, children)
|
|
114
|
+
return parent
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* @description 树形结构数据转换成父子关系的数组
|
|
119
|
+
* @param data
|
|
120
|
+
* @returns {[]}
|
|
121
|
+
*/
|
|
122
|
+
export function translateTreeToData(data) {
|
|
123
|
+
const result = []
|
|
124
|
+
data.forEach((item) => {
|
|
125
|
+
const loop = (data) => {
|
|
126
|
+
result.push({
|
|
127
|
+
id: data.id,
|
|
128
|
+
name: data.name,
|
|
129
|
+
parentId: data.parentId,
|
|
130
|
+
})
|
|
131
|
+
const child = data.children
|
|
132
|
+
if (child) {
|
|
133
|
+
for (let i = 0; i < child.length; i++) {
|
|
134
|
+
loop(child[i])
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
loop(item)
|
|
139
|
+
})
|
|
140
|
+
return result
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* @description 10位时间戳转换
|
|
145
|
+
* @param time
|
|
146
|
+
* @returns {string}
|
|
147
|
+
*/
|
|
148
|
+
export function tenBitTimestamp(time) {
|
|
149
|
+
const date = new Date(time * 1000)
|
|
150
|
+
const y = date.getFullYear()
|
|
151
|
+
let m = date.getMonth() + 1
|
|
152
|
+
m = m < 10 ? `${m}` : m
|
|
153
|
+
let d = date.getDate()
|
|
154
|
+
d = d < 10 ? `${d}` : d
|
|
155
|
+
let h = date.getHours()
|
|
156
|
+
h = h < 10 ? `0${h}` : h
|
|
157
|
+
let minute = date.getMinutes()
|
|
158
|
+
let second = date.getSeconds()
|
|
159
|
+
minute = minute < 10 ? `0${minute}` : minute
|
|
160
|
+
second = second < 10 ? `0${second}` : second
|
|
161
|
+
return `${y}年${m}月${d}日 ${h}:${minute}:${second}` //组合
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* @description 13位时间戳转换
|
|
166
|
+
* @param time
|
|
167
|
+
* @returns {string}
|
|
168
|
+
*/
|
|
169
|
+
export function thirteenBitTimestamp(time) {
|
|
170
|
+
const date = new Date(time / 1)
|
|
171
|
+
const y = date.getFullYear()
|
|
172
|
+
let m = date.getMonth() + 1
|
|
173
|
+
m = m < 10 ? `${m}` : m
|
|
174
|
+
let d = date.getDate()
|
|
175
|
+
d = d < 10 ? `${d}` : d
|
|
176
|
+
let h = date.getHours()
|
|
177
|
+
h = h < 10 ? `0${h}` : h
|
|
178
|
+
let minute = date.getMinutes()
|
|
179
|
+
let second = date.getSeconds()
|
|
180
|
+
minute = minute < 10 ? `0${minute}` : minute
|
|
181
|
+
second = second < 10 ? `0${second}` : second
|
|
182
|
+
return `${y}年${m}月${d}日 ${h}:${minute}:${second}` //组合
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* @description 获取随机id
|
|
187
|
+
* @param length
|
|
188
|
+
* @returns {string}
|
|
189
|
+
*/
|
|
190
|
+
export function uuid(length = 32) {
|
|
191
|
+
const num = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890'
|
|
192
|
+
let str = ''
|
|
193
|
+
for (let i = 0; i < length; i++) {
|
|
194
|
+
str += num.charAt(Math.floor(Math.random() * num.length))
|
|
195
|
+
}
|
|
196
|
+
return str
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* @description m到n的随机数
|
|
201
|
+
* @param m
|
|
202
|
+
* @param n
|
|
203
|
+
* @returns {number}
|
|
204
|
+
*/
|
|
205
|
+
export function random(m, n) {
|
|
206
|
+
return Math.floor(Math.random() * (m - n) + n)
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* @description addEventListener
|
|
211
|
+
* @type {function(...[*]=)}
|
|
212
|
+
*/
|
|
213
|
+
export const on = (function () {
|
|
214
|
+
return function (element, event, handler, useCapture = false) {
|
|
215
|
+
if (element && event && handler) {
|
|
216
|
+
element.addEventListener(event, handler, useCapture)
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
})()
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* @description removeEventListener
|
|
223
|
+
* @type {function(...[*]=)}
|
|
224
|
+
*/
|
|
225
|
+
export const off = (function () {
|
|
226
|
+
return function (element, event, handler, useCapture = false) {
|
|
227
|
+
if (element && event) {
|
|
228
|
+
element.removeEventListener(event, handler, useCapture)
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
})()
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @description 消息提示工具函数
|
|
3
|
+
* 提供统一的消息提示方法,兼容Vue 3
|
|
4
|
+
*/
|
|
5
|
+
import { ElMessage, ElNotification, ElMessageBox } from "element-plus";
|
|
6
|
+
import { messageDuration } from "@/config";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* 消息提示
|
|
10
|
+
* @param {string} message 提示信息
|
|
11
|
+
* @param {string} type 消息类型
|
|
12
|
+
* @param {object} options 配置项
|
|
13
|
+
*/
|
|
14
|
+
export function baseMessage(message, type = "info", options = {}) {
|
|
15
|
+
if (message) {
|
|
16
|
+
const defaultOptions = {
|
|
17
|
+
message,
|
|
18
|
+
type,
|
|
19
|
+
duration: messageDuration,
|
|
20
|
+
};
|
|
21
|
+
return ElMessage({ ...defaultOptions, ...options });
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* 通知提示
|
|
27
|
+
* @param {string} title 标题
|
|
28
|
+
* @param {string} message 提示信息
|
|
29
|
+
* @param {object} options 配置项
|
|
30
|
+
*/
|
|
31
|
+
export function baseNotify(title, message, options = {}) {
|
|
32
|
+
ElNotification({
|
|
33
|
+
title,
|
|
34
|
+
message,
|
|
35
|
+
type: "success",
|
|
36
|
+
...options,
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* 确认提示
|
|
42
|
+
* @param {string} content 内容
|
|
43
|
+
* @param {object} options 配置项
|
|
44
|
+
*/
|
|
45
|
+
export function baseConfirm(content, options = {}) {
|
|
46
|
+
const defaultOptions = {
|
|
47
|
+
closeOnClickModal: false,
|
|
48
|
+
closeOnPressEscape: false,
|
|
49
|
+
...options,
|
|
50
|
+
};
|
|
51
|
+
return ElMessageBox.confirm(content, "温馨提示", defaultOptions);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* 警告提示
|
|
56
|
+
* @param {string} content 内容
|
|
57
|
+
* @param {object} options 配置项
|
|
58
|
+
*/
|
|
59
|
+
export function baseAlert(content, options = {}) {
|
|
60
|
+
const defaultOptions = {
|
|
61
|
+
closeOnClickModal: false,
|
|
62
|
+
closeOnPressEscape: false,
|
|
63
|
+
showClose: true,
|
|
64
|
+
...options,
|
|
65
|
+
};
|
|
66
|
+
return ElMessageBox.alert(content, "温馨提示", defaultOptions);
|
|
67
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import JSEncrypt from "jsencrypt";
|
|
2
|
+
|
|
3
|
+
export function formatTest(pass) {
|
|
4
|
+
if (pass.length < 8 || pass.length > 16) {
|
|
5
|
+
return {err:true,msg:'密码长度为8~16位'};
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
var patternNum = /\d+/; //数字
|
|
9
|
+
var patternLetterUp = /[a-zA-Z]+/; // 字母
|
|
10
|
+
var patternLetterLow = /[a-z]+/; // 字母
|
|
11
|
+
var patternFuhao = /[!@%^*、‘+=-\\.]+/; // 特殊符号
|
|
12
|
+
var patternhanzi = /[\u4e00-\u9fa5]+/; // 汉字
|
|
13
|
+
var patternFuhaoNo = /[';<>$&#()\\s]+/; // 不含字符
|
|
14
|
+
var patternFour = /([\s\S])(\1){3,}/; //不可连续四位相同
|
|
15
|
+
if (
|
|
16
|
+
!patternNum.test(pass) ||
|
|
17
|
+
!patternLetterUp.test(pass)
|
|
18
|
+
// !patternLetterLow.test(pass) ||
|
|
19
|
+
// !patternFuhao.test(pass)
|
|
20
|
+
) {
|
|
21
|
+
return { err: true, msg: "密码需包含数字、字母" };
|
|
22
|
+
}
|
|
23
|
+
// if (patternFour.test(pass)) {
|
|
24
|
+
// return { err: true, msg: "密码含有过多连续字符" };
|
|
25
|
+
// }
|
|
26
|
+
// if (patternFuhaoNo.test(pass)) {
|
|
27
|
+
// return { err: true, msg: "密码含有非法字符" };
|
|
28
|
+
// }
|
|
29
|
+
if (patternhanzi.test(pass)) {
|
|
30
|
+
return { err: true, msg: "密码不可使用汉字" };
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return { err: false, msg: "" };
|
|
34
|
+
}
|
|
35
|
+
const config={
|
|
36
|
+
RsaPublicKey:'-----BEGIN PUBLIC KEY-----MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAoY8e9XE+wLDEA0ps24us2TZ3o/MTQ1WgcsADkNWYd8YucX6VW3ykol9gTMfV3WC3Ol6kH49Kq6naY7syxqBkHsjyQp8V25w5kiJrno3NlIfkwdd/xFJ2AxRQBz/BYHpIPtkBGvL/oV949Fed/k4in3iGgiWcshK4SWJ/Q8dm2C6H2PI8piF0kLLXPCM7h3wePuMUHb6A2hzuENhp+Kf5qmbrMz8q8PO3BvCsxKVX2273vFYX1+vFPS1r6rTMMayt6N3j+1tUSobAZ49Saew7rWjzYEy/s36hmCWqDAb++tLQtcgF1R+k0ZB+BdYMg5BLdXuF1Eyqcd5hiOH904uAZQIDAQAB-----END PUBLIC KEY-----'
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
export function RSA(pass) {
|
|
40
|
+
var encrypt = new JSEncrypt();
|
|
41
|
+
encrypt.setPublicKey(config.RsaPublicKey);
|
|
42
|
+
return encrypt.encrypt(pass);
|
|
43
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import store from '@/store'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @description 检查权限
|
|
5
|
+
* @param value
|
|
6
|
+
* @returns {boolean}
|
|
7
|
+
*/
|
|
8
|
+
export default function checkPermission(value) {
|
|
9
|
+
if (value && value instanceof Array && value.length > 0) {
|
|
10
|
+
const permissions = store.getters['user/permissions']
|
|
11
|
+
const permissionPermissions = value
|
|
12
|
+
|
|
13
|
+
return permissions.some((role) => {
|
|
14
|
+
return permissionPermissions.includes(role)
|
|
15
|
+
})
|
|
16
|
+
} else {
|
|
17
|
+
return false
|
|
18
|
+
}
|
|
19
|
+
}
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
import axios from "axios";
|
|
2
|
+
import {
|
|
3
|
+
baseURL,
|
|
4
|
+
contentType,
|
|
5
|
+
debounce,
|
|
6
|
+
invalidCode,
|
|
7
|
+
loginInterception,
|
|
8
|
+
noPermissionCode,
|
|
9
|
+
requestTimeout,
|
|
10
|
+
successCode,
|
|
11
|
+
tokenName,
|
|
12
|
+
} from "@/config";
|
|
13
|
+
import store from "@/store";
|
|
14
|
+
import qs from "qs";
|
|
15
|
+
import router from "@/router";
|
|
16
|
+
import { isArray } from "@/utils/validate";
|
|
17
|
+
import { ElLoading, ElMessage } from "element-plus";
|
|
18
|
+
import { pickBy, identity } from "lodash-es";
|
|
19
|
+
import { mock } from "mockjs";
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
let loadingInstance;
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* @description 处理code异常
|
|
26
|
+
* @param {*} code
|
|
27
|
+
* @param {*} msg
|
|
28
|
+
*/
|
|
29
|
+
const handleCode = (code, msg) => {
|
|
30
|
+
switch (code) {
|
|
31
|
+
case invalidCode:
|
|
32
|
+
ElMessage.error(msg || `后端接口${code}异常`);
|
|
33
|
+
store.dispatch("user/resetAccessToken");
|
|
34
|
+
if (loginInterception) {
|
|
35
|
+
location.reload();
|
|
36
|
+
}
|
|
37
|
+
break;
|
|
38
|
+
case noPermissionCode:
|
|
39
|
+
router.push({ path: "/401" }).catch(() => {});
|
|
40
|
+
break;
|
|
41
|
+
default:
|
|
42
|
+
ElMessage.error(msg || `后端接口${code}异常`);
|
|
43
|
+
break;
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
// 请求重试配置
|
|
48
|
+
const retryConfig = {
|
|
49
|
+
retry: 3, // 重试次数
|
|
50
|
+
retryDelay: 1000, // 重试间隔时间
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
// 创建axios实例
|
|
54
|
+
const instance = axios.create({
|
|
55
|
+
baseURL,
|
|
56
|
+
timeout: requestTimeout,
|
|
57
|
+
headers: {
|
|
58
|
+
"Content-Type": contentType,
|
|
59
|
+
},
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
// 请求重试方法
|
|
63
|
+
instance.defaults.retry = retryConfig.retry;
|
|
64
|
+
instance.defaults.retryDelay = retryConfig.retryDelay;
|
|
65
|
+
|
|
66
|
+
// 请求拦截器
|
|
67
|
+
instance.interceptors.request.use(
|
|
68
|
+
(config) => {
|
|
69
|
+
if (store.state.user.accessToken) {
|
|
70
|
+
config.headers[tokenName] = store.state.user.accessToken;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
//这里会过滤所有为空、0、false的key,如果不需要请自行注释
|
|
74
|
+
if (config.data) config.data = pickBy(config.data, identity);
|
|
75
|
+
if (
|
|
76
|
+
config.data &&
|
|
77
|
+
config.headers["Content-Type"] ===
|
|
78
|
+
"application/x-www-form-urlencoded;charset=UTF-8"
|
|
79
|
+
)
|
|
80
|
+
config.data = qs.stringify(config.data);
|
|
81
|
+
if (debounce.some((item) => config.url.includes(item)))
|
|
82
|
+
loadingInstance = ElLoading.service();
|
|
83
|
+
|
|
84
|
+
return config;
|
|
85
|
+
},
|
|
86
|
+
(error) => {
|
|
87
|
+
return Promise.reject(error);
|
|
88
|
+
}
|
|
89
|
+
);
|
|
90
|
+
|
|
91
|
+
// 响应拦截器
|
|
92
|
+
instance.interceptors.response.use(
|
|
93
|
+
(response) => {
|
|
94
|
+
if (loadingInstance) loadingInstance.close();
|
|
95
|
+
|
|
96
|
+
const { data, config } = response;
|
|
97
|
+
|
|
98
|
+
// 判断data是否为undefined或null
|
|
99
|
+
if (data === undefined || data === null) {
|
|
100
|
+
ElMessage.error("后端接口返回数据为空");
|
|
101
|
+
return Promise.reject("后端接口返回数据为空");
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// 安全地解构code和msg,避免undefined异常
|
|
105
|
+
const code = data.code !== undefined ? data.code : null;
|
|
106
|
+
const msg = data.msg !== undefined ? data.msg : "未知错误";
|
|
107
|
+
|
|
108
|
+
// 操作正常Code数组
|
|
109
|
+
const codeVerificationArray = isArray(successCode)
|
|
110
|
+
? [...successCode]
|
|
111
|
+
: [...[successCode]];
|
|
112
|
+
|
|
113
|
+
// 是否操作正常
|
|
114
|
+
if (code !== null && codeVerificationArray.includes(code)) {
|
|
115
|
+
return data;
|
|
116
|
+
} else {
|
|
117
|
+
handleCode(code, msg);
|
|
118
|
+
return Promise.reject(
|
|
119
|
+
`请求异常拦截:${JSON.stringify({
|
|
120
|
+
url: config.url,
|
|
121
|
+
code,
|
|
122
|
+
msg,
|
|
123
|
+
})}` || "Error"
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
},
|
|
127
|
+
(error) => {
|
|
128
|
+
if (loadingInstance) loadingInstance.close();
|
|
129
|
+
|
|
130
|
+
// 处理请求重试
|
|
131
|
+
const { config } = error;
|
|
132
|
+
if (config && config.retry) {
|
|
133
|
+
// 设置当前重试次数
|
|
134
|
+
config.__retryCount = config.__retryCount || 0;
|
|
135
|
+
|
|
136
|
+
// 检查是否可以重试
|
|
137
|
+
if (config.__retryCount < config.retry) {
|
|
138
|
+
// 增加重试次数
|
|
139
|
+
config.__retryCount += 1;
|
|
140
|
+
|
|
141
|
+
// 创建新的Promise进行重试
|
|
142
|
+
const backoff = new Promise((resolve) => {
|
|
143
|
+
setTimeout(() => {
|
|
144
|
+
console.log(
|
|
145
|
+
`重试请求: ${config.url}, 尝试次数: ${config.__retryCount}`
|
|
146
|
+
);
|
|
147
|
+
resolve();
|
|
148
|
+
}, config.retryDelay || 1000);
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
// 重新发起请求
|
|
152
|
+
return backoff.then(() => instance(config));
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// 处理undefined或无法解析的错误情况
|
|
157
|
+
if (!error) {
|
|
158
|
+
ElMessage.error("发生未知错误");
|
|
159
|
+
return Promise.reject("发生未知错误");
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const { response, message } = error;
|
|
163
|
+
if (response && response.data) {
|
|
164
|
+
const { status, data } = response;
|
|
165
|
+
handleCode(status, data.msg || message || "未知错误");
|
|
166
|
+
return Promise.reject(error);
|
|
167
|
+
} else {
|
|
168
|
+
let errorMsg = "后端接口未知异常";
|
|
169
|
+
|
|
170
|
+
if (message) {
|
|
171
|
+
if (message === "Network Error") {
|
|
172
|
+
errorMsg = "后端接口连接异常";
|
|
173
|
+
} else if (message.includes("timeout")) {
|
|
174
|
+
errorMsg = "后端接口请求超时";
|
|
175
|
+
} else if (message.includes("Request failed with status code")) {
|
|
176
|
+
const code = message.substr(message.length - 3);
|
|
177
|
+
errorMsg = `后端接口${code}异常`;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
ElMessage.error(errorMsg);
|
|
182
|
+
return Promise.reject(error);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
);
|
|
186
|
+
|
|
187
|
+
export default instance;
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @author https://vuejs-core.cn
|
|
3
|
+
* @description 导入所有 controller 模块,浏览器环境中自动输出controller文件夹下Mock接口,请勿修改。
|
|
4
|
+
*/
|
|
5
|
+
import Mock from 'mockjs'
|
|
6
|
+
import { paramObj } from '@/utils'
|
|
7
|
+
|
|
8
|
+
const mocks = []
|
|
9
|
+
// 使用兼容 rspack 的方式导入 mock 控制器
|
|
10
|
+
const files = require.context('../../mock/controller', false, /\.js$/)
|
|
11
|
+
|
|
12
|
+
files.keys().forEach((key) => {
|
|
13
|
+
mocks.push(...files(key))
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
export function mockXHR() {
|
|
17
|
+
// 设置Mock响应延迟
|
|
18
|
+
Mock.setup({
|
|
19
|
+
timeout: '200-600',
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
// 保存原始的XHR send方法
|
|
23
|
+
Mock.XHR.prototype.proxy_send = Mock.XHR.prototype.send
|
|
24
|
+
Mock.XHR.prototype.send = function () {
|
|
25
|
+
if (this.custom.xhr) {
|
|
26
|
+
this.custom.xhr.withCredentials = this.withCredentials || false
|
|
27
|
+
|
|
28
|
+
if (this.responseType) {
|
|
29
|
+
this.custom.xhr.responseType = this.responseType
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
this.proxy_send(...arguments)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function XHRHttpRequst(respond) {
|
|
36
|
+
return function (options) {
|
|
37
|
+
let result
|
|
38
|
+
try {
|
|
39
|
+
if (respond instanceof Function) {
|
|
40
|
+
const { body, type, url } = options
|
|
41
|
+
// 安全解析body
|
|
42
|
+
let parsedBody = {}
|
|
43
|
+
if (body) {
|
|
44
|
+
try {
|
|
45
|
+
parsedBody = JSON.parse(body)
|
|
46
|
+
} catch (e) {
|
|
47
|
+
console.warn('无法解析请求体:', e)
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
result = respond({
|
|
52
|
+
method: type,
|
|
53
|
+
body: parsedBody,
|
|
54
|
+
query: paramObj(url),
|
|
55
|
+
})
|
|
56
|
+
} else {
|
|
57
|
+
result = respond
|
|
58
|
+
}
|
|
59
|
+
} catch (error) {
|
|
60
|
+
console.error('Mock处理请求时发生错误:', error)
|
|
61
|
+
// 返回默认错误响应
|
|
62
|
+
result = {
|
|
63
|
+
code: 500,
|
|
64
|
+
message: '服务器内部错误',
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return Mock.mock(result)
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// 注册所有的mock服务
|
|
72
|
+
mocks.forEach((item) => {
|
|
73
|
+
Mock.mock(new RegExp(item.url), item.type || 'get', XHRHttpRequst(item.response))
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
// 记录mock设置完成
|
|
77
|
+
console.info(`[Mock] 成功设置 ${mocks.length} 个模拟接口`)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// 导出mocks列表,便于调试
|
|
81
|
+
export const mockList = mocks
|