cmpt-huitu-cli 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/README.md +169 -0
- package/bin/huitu.js +30 -0
- package/package.json +38 -0
- package/src/create.js +127 -0
- package/src/versions.js +23 -0
- package/templates/default/README.md +127 -0
- package/templates/default/eslint.config.js +187 -0
- package/templates/default/index.html +38 -0
- package/templates/default/jsconfig.json +29 -0
- package/templates/default/package.json +56 -0
- package/templates/default/public/.DS_Store +0 -0
- package/templates/default/public/config/envCfg.js +68 -0
- package/templates/default/public/images/favicon.ico +0 -0
- package/templates/default/src/.DS_Store +0 -0
- package/templates/default/src/App.vue +49 -0
- package/templates/default/src/assets/.DS_Store +0 -0
- package/templates/default/src/assets/empty.png +0 -0
- package/templates/default/src/assets/images/.DS_Store +0 -0
- package/templates/default/src/assets/images/404/403_1.png +0 -0
- package/templates/default/src/assets/images/404/404.png +0 -0
- package/templates/default/src/assets/images/404/404_2.png +0 -0
- package/templates/default/src/assets/images/404/404_cloud.png +0 -0
- package/templates/default/src/assets/images/login/img.png +0 -0
- package/templates/default/src/assets/images/login/logo.png +0 -0
- package/templates/default/src/assets/images/login/person.png +0 -0
- package/templates/default/src/components/.DS_Store +0 -0
- package/templates/default/src/components/error/Error.vue +259 -0
- package/templates/default/src/components.d.ts +18 -0
- package/templates/default/src/imports.d.ts +90 -0
- package/templates/default/src/main.js +27 -0
- package/templates/default/src/routers/base.js +41 -0
- package/templates/default/src/routers/guard.js +43 -0
- package/templates/default/src/routers/index.js +36 -0
- package/templates/default/src/routers/modules/example.js +17 -0
- package/templates/default/src/services/.DS_Store +0 -0
- package/templates/default/src/services/login.js +32 -0
- package/templates/default/src/stores/README.md +317 -0
- package/templates/default/src/stores/index/global.js +49 -0
- package/templates/default/src/stores/index/template.js +31 -0
- package/templates/default/src/stores/index.js +14 -0
- package/templates/default/src/stores//344/275/277/347/224/250/347/244/272/344/276/213.vue +94 -0
- package/templates/default/src/styles/index.scss +23 -0
- package/templates/default/src/styles/theme/README.md +52 -0
- package/templates/default/src/styles/theme/variables.scss +62 -0
- package/templates/default/src/utils/RequestCache.js +198 -0
- package/templates/default/src/utils/auth.js +51 -0
- package/templates/default/src/utils/errorCode.js +16 -0
- package/templates/default/src/utils/index.js +519 -0
- package/templates/default/src/utils/requestAxios.js +148 -0
- package/templates/default/src/utils/theme.js +20 -0
- package/templates/default/src/views/About.vue +6 -0
- package/templates/default/src/views/Home.vue +111 -0
- package/templates/default/src/views/login/index.vue +285 -0
- package/templates/default/vite/plugins/autoComponents.js +18 -0
- package/templates/default/vite/plugins/autoImport.js +13 -0
- package/templates/default/vite/plugins/compression.js +24 -0
- package/templates/default/vite/plugins/externals.js +8 -0
- package/templates/default/vite/plugins/index.js +18 -0
- package/templates/default/vite/plugins/visualizer.js +11 -0
- package/templates/default/vite.config.js +185 -0
|
@@ -0,0 +1,519 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @FileDescription: 系统通用工具类
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export function isProd() {
|
|
6
|
+
// 推荐:使用import.meta.env.DEV 和 import.meta.env.PROD进行环境判断,而非直接检查MODE,因为MODE可能被环境变量文件或者命令改变
|
|
7
|
+
if (import.meta.env.PROD) {
|
|
8
|
+
return true
|
|
9
|
+
}
|
|
10
|
+
return false
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function isDev() {
|
|
14
|
+
if (import.meta.env.DEV) {
|
|
15
|
+
return true
|
|
16
|
+
}
|
|
17
|
+
return false
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// 获取上传后图片的地址
|
|
21
|
+
export function getFileFullPathNoBucket(fileName, filePath) {
|
|
22
|
+
const uploadUrl = window.HT_EnvConfig?.uploadUrl || '/upload'
|
|
23
|
+
return uploadUrl + '/api/oss/minio/objects/file?filename=' + fileName + '&filepath=' + filePath
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// 获取上传后图片的地址(带 bucket)
|
|
27
|
+
export function getFileFullPathByBucket(fileName, filePath, bucketname = 'default') {
|
|
28
|
+
const uploadUrl = window.HT_EnvConfig?.uploadUrl || '/upload'
|
|
29
|
+
return uploadUrl + '/api/oss/minio/objects/file-by-bucketname?bucketname=' + bucketname + '&filename=' + fileName + '&filepath=' + filePath
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Parse the time to string
|
|
34
|
+
* @param {(Object|string|number)} time
|
|
35
|
+
* @param {string} cFormat
|
|
36
|
+
* @returns {string}
|
|
37
|
+
*/
|
|
38
|
+
export function parseTimeNative(time, cFormat) {
|
|
39
|
+
if (arguments.length === 0) {
|
|
40
|
+
return null
|
|
41
|
+
}
|
|
42
|
+
const format = cFormat || '{y}-{m}-{d} {h}:{i}:{s}'
|
|
43
|
+
let date
|
|
44
|
+
|
|
45
|
+
if (typeof time === 'object') {
|
|
46
|
+
date = time
|
|
47
|
+
} else {
|
|
48
|
+
if (typeof time === 'string' && /^[0-9]+$/.test(time)) {
|
|
49
|
+
time = Number.parseInt(time)
|
|
50
|
+
}
|
|
51
|
+
if (typeof time === 'number' && time.toString().length === 10) {
|
|
52
|
+
time = time * 1000
|
|
53
|
+
}
|
|
54
|
+
date = new Date(time)
|
|
55
|
+
}
|
|
56
|
+
const formatObj = {
|
|
57
|
+
y: date.getFullYear(),
|
|
58
|
+
m: date.getMonth() + 1,
|
|
59
|
+
d: date.getDate(),
|
|
60
|
+
h: date.getHours(),
|
|
61
|
+
i: date.getMinutes(),
|
|
62
|
+
s: date.getSeconds(),
|
|
63
|
+
a: date.getDay(),
|
|
64
|
+
}
|
|
65
|
+
const timeStr = format.replace(/{(y|m|d|h|i|s|a)+}/g, (result, key) => {
|
|
66
|
+
let value = formatObj[key]
|
|
67
|
+
// Note: getDay() returns 0 on Sunday
|
|
68
|
+
if (key === 'a') {
|
|
69
|
+
return ['日', '一', '二', '三', '四', '五', '六'][value]
|
|
70
|
+
}
|
|
71
|
+
if (result.length > 0 && value < 10) {
|
|
72
|
+
value = '0' + value
|
|
73
|
+
}
|
|
74
|
+
return value || 0
|
|
75
|
+
})
|
|
76
|
+
return timeStr
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* @param {number} time
|
|
81
|
+
* @param {string} option
|
|
82
|
+
* @returns {string}
|
|
83
|
+
*/
|
|
84
|
+
export function formatTime(time, option) {
|
|
85
|
+
if (('' + time).length === 10) {
|
|
86
|
+
time = Number.parseInt(time) * 1000
|
|
87
|
+
} else {
|
|
88
|
+
time = +time
|
|
89
|
+
}
|
|
90
|
+
const d = new Date(time)
|
|
91
|
+
const now = Date.now()
|
|
92
|
+
|
|
93
|
+
const diff = (now - d) / 1000
|
|
94
|
+
|
|
95
|
+
if (diff < 30) {
|
|
96
|
+
return '刚刚'
|
|
97
|
+
} else if (diff < 3600) {
|
|
98
|
+
// less 1 hour
|
|
99
|
+
return Math.ceil(diff / 60) + '分钟前'
|
|
100
|
+
} else if (diff < 3600 * 24) {
|
|
101
|
+
return Math.ceil(diff / 3600) + '小时前'
|
|
102
|
+
} else if (diff < 3600 * 24 * 2) {
|
|
103
|
+
return '1天前'
|
|
104
|
+
}
|
|
105
|
+
if (option) {
|
|
106
|
+
return parseTimeNative(time, option)
|
|
107
|
+
} else {
|
|
108
|
+
return d.getMonth() + 1 + '月' + d.getDate() + '日' + d.getHours() + '时' + d.getMinutes() + '分'
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* @param {string} url
|
|
114
|
+
* @returns {Object}
|
|
115
|
+
*/
|
|
116
|
+
export function getQueryParameters(url) {
|
|
117
|
+
const search = url.split('?')[1]
|
|
118
|
+
if (!search) {
|
|
119
|
+
return {}
|
|
120
|
+
}
|
|
121
|
+
return JSON.parse('{"' + decodeURIComponent(search).replace(/"/g, '\\"').replace(/&/g, '","').replace(/=/g, '":"').replace(/\+/g, ' ') + '"}')
|
|
122
|
+
}
|
|
123
|
+
// 移除地址栏?参数
|
|
124
|
+
export function removeQueryParameters(str, name) {
|
|
125
|
+
var reg = new RegExp(name + '=([^&]*)(&?)', 'i')
|
|
126
|
+
var r = str.match(reg)
|
|
127
|
+
if (r != null) {
|
|
128
|
+
str = str.replace(reg, '')
|
|
129
|
+
// console.log(str)
|
|
130
|
+
// alert()
|
|
131
|
+
}
|
|
132
|
+
return str
|
|
133
|
+
}
|
|
134
|
+
export function hasClass(elem, cls) {
|
|
135
|
+
cls = cls || ''
|
|
136
|
+
if (cls.replace(/\s/g, '').length === 0) return false // 当cls没有参数时,返回false
|
|
137
|
+
return new RegExp(' ' + cls + ' ').test(' ' + elem.className + ' ')
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
export function addClass(elem, cls) {
|
|
141
|
+
if (!hasClass(elem, cls)) {
|
|
142
|
+
elem.className = elem.className === '' ? cls : elem.className + ' ' + cls
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
export function removeClass(elem, cls) {
|
|
147
|
+
if (hasClass(elem, cls)) {
|
|
148
|
+
let newClass = ' ' + elem.className.replace(/[\t\r\n]/g, '') + ' '
|
|
149
|
+
while (newClass.indexOf(' ' + cls + ' ') >= 0) {
|
|
150
|
+
newClass = newClass.replace(' ' + cls + ' ', ' ')
|
|
151
|
+
}
|
|
152
|
+
elem.className = newClass.replace(/^\s+|\s+$/g, '')
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
export function tansParamsString(obj) {
|
|
157
|
+
if (!obj) return ''
|
|
158
|
+
const pairs = []
|
|
159
|
+
for (const key in obj) {
|
|
160
|
+
const value = obj[key]
|
|
161
|
+
|
|
162
|
+
if (value instanceof Array) {
|
|
163
|
+
for (let i = 0; i < value.length; ++i) {
|
|
164
|
+
pairs.push(encodeURIComponent(key + '[' + i + ']') + '=' + encodeURIComponent(value[i]))
|
|
165
|
+
}
|
|
166
|
+
continue
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
pairs.push(encodeURIComponent(key) + '=' + encodeURIComponent(obj[key]))
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
return pairs.join('&')
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* [strToNull 处理提交JSON后台不接受""空字符串]
|
|
177
|
+
* @author: cm
|
|
178
|
+
* @param {对象} data
|
|
179
|
+
*/
|
|
180
|
+
export const strToNull = (data) => {
|
|
181
|
+
for (const x in data) {
|
|
182
|
+
if (data[x] === '') {
|
|
183
|
+
// 如果是空字符串 把直接内容转为 null
|
|
184
|
+
data[x] = ''
|
|
185
|
+
} else {
|
|
186
|
+
if (Array.isArray(data[x])) {
|
|
187
|
+
// 是数组遍历数组 递归继续处理
|
|
188
|
+
data[x] = data[x].map((z) => {
|
|
189
|
+
return strToNull(z)
|
|
190
|
+
})
|
|
191
|
+
}
|
|
192
|
+
if (typeof data[x] === 'object') {
|
|
193
|
+
// 是json 递归继续处理
|
|
194
|
+
data[x] = strToNull(data[x])
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
return data
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// 防抖函数
|
|
202
|
+
// 用法示例: methods:{ testFun: debounce(function(params){}, 500)}
|
|
203
|
+
export const debounce = (fn, t) => {
|
|
204
|
+
const delay = t || 500
|
|
205
|
+
let timer
|
|
206
|
+
return function () {
|
|
207
|
+
const args = arguments
|
|
208
|
+
if (timer) {
|
|
209
|
+
clearTimeout(timer)
|
|
210
|
+
}
|
|
211
|
+
timer = setTimeout(() => {
|
|
212
|
+
timer = null
|
|
213
|
+
fn.apply(this, args)
|
|
214
|
+
}, delay)
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* 函数节流
|
|
220
|
+
* @param fn
|
|
221
|
+
* @param interval
|
|
222
|
+
* @returns {Function}
|
|
223
|
+
* @constructor
|
|
224
|
+
*/
|
|
225
|
+
export const throttle = (fn, t) => {
|
|
226
|
+
let last
|
|
227
|
+
let timer
|
|
228
|
+
const interval = t || 500
|
|
229
|
+
return function () {
|
|
230
|
+
const args = arguments
|
|
231
|
+
const now = +new Date()
|
|
232
|
+
if (last && now - last < interval) {
|
|
233
|
+
clearTimeout(timer)
|
|
234
|
+
timer = setTimeout(() => {
|
|
235
|
+
last = now
|
|
236
|
+
fn.apply(this, args)
|
|
237
|
+
}, interval)
|
|
238
|
+
} else {
|
|
239
|
+
last = now
|
|
240
|
+
fn.apply(this, args)
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* 获取数值指定小数点位数
|
|
247
|
+
* @param num 数值
|
|
248
|
+
* @param digit 小数点位数
|
|
249
|
+
* @returns {null}
|
|
250
|
+
*/
|
|
251
|
+
export function getNumberByDigit(num, digit) {
|
|
252
|
+
if (!isNum(num)) return ''
|
|
253
|
+
if (digit === 3 && num % 1 === 0) return num
|
|
254
|
+
const retNum = Number.parseFloat(num).toFixed(digit)
|
|
255
|
+
return retNum == 0 ? 0 : Number.parseFloat(num).toFixed(digit) * 1 // 此方法会触发四舍五入 - 此处万不能使用绝对等于号
|
|
256
|
+
// return Math.ceil(0.007 * 100) / 100
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* 对比对象差异并返回差异属性
|
|
261
|
+
* @param obj1
|
|
262
|
+
* @param obj2
|
|
263
|
+
* @return {*}
|
|
264
|
+
*/
|
|
265
|
+
export function diffObjProps(obj1, obj2) {
|
|
266
|
+
return Object.fromEntries([...Object.entries(obj1), ...Object.entries(obj2)].filter(([key]) => obj1[key] !== obj2[key]))
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* 判断字符是否为中文汉字
|
|
271
|
+
* @param str
|
|
272
|
+
* @return {boolean}
|
|
273
|
+
*/
|
|
274
|
+
export function isChinese(str) {
|
|
275
|
+
const reg = /^[\u4E00-\u9FA5]+$/
|
|
276
|
+
return reg.test(str)
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/**
|
|
280
|
+
* 经度验证,最长2位,小数点后最长7位数
|
|
281
|
+
* @param val
|
|
282
|
+
* @return {*}
|
|
283
|
+
* @constructor
|
|
284
|
+
*/
|
|
285
|
+
export function lgtdIsValid(val) {
|
|
286
|
+
const regex = /^[-+]?[0-9]{1,3}(\.[0-9]{1,7})?$/
|
|
287
|
+
return regex.test(val)
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* 纬度验证,最长2位,小数点后最长7位数
|
|
292
|
+
* @param val
|
|
293
|
+
* @return {*}
|
|
294
|
+
* @constructor
|
|
295
|
+
*/
|
|
296
|
+
export function lttdIsValid(val) {
|
|
297
|
+
const regex = /^[-+]?[0-9]{1,2}(\.[0-9]{1,7})?$/
|
|
298
|
+
return regex.test(val)
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
* 深拷贝
|
|
303
|
+
*
|
|
304
|
+
*/
|
|
305
|
+
export function deepCopy(obj) {
|
|
306
|
+
if (typeof obj !== 'object' || obj === null) {
|
|
307
|
+
return obj // 如果不是对象类型或者是null,直接返回
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
let copy
|
|
311
|
+
if (Array.isArray(obj)) {
|
|
312
|
+
copy = [] // 如果是数组,创建一个空数组
|
|
313
|
+
for (let i = 0; i < obj.length; i++) {
|
|
314
|
+
copy[i] = deepCopy(obj[i]) // 递归拷贝数组中的每个元素
|
|
315
|
+
}
|
|
316
|
+
} else {
|
|
317
|
+
copy = {} // 如果是对象,创建一个空对象
|
|
318
|
+
for (const key in obj) {
|
|
319
|
+
if (obj.hasOwnProperty(key)) {
|
|
320
|
+
copy[key] = deepCopy(obj[key]) // 递归拷贝对象中的每个属性值
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
return copy // 返回深拷贝后的对象或数组
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
/**
|
|
328
|
+
* 导出前的HTML元素图片转换
|
|
329
|
+
* @param dataurl
|
|
330
|
+
* @return {Blob}
|
|
331
|
+
*/
|
|
332
|
+
export function dataURLToBlob(dataurl) {
|
|
333
|
+
const arr = dataurl.split(',')
|
|
334
|
+
const mime = arr[0].match(/:(.*?);/)[1]
|
|
335
|
+
const bstr = atob(arr[1])
|
|
336
|
+
let n = bstr.length
|
|
337
|
+
const u8arr = new Uint8Array(n)
|
|
338
|
+
while (n--) {
|
|
339
|
+
u8arr[n] = bstr.charCodeAt(n)
|
|
340
|
+
}
|
|
341
|
+
return new Blob([u8arr], { type: mime })
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
/**
|
|
345
|
+
* 将相对路径转换为绝对URL地址
|
|
346
|
+
* @param {string} url - 需要转换的相对路径
|
|
347
|
+
* @returns {string|null} 返回转换后的绝对URL地址,如果转换失败则返回null
|
|
348
|
+
*/
|
|
349
|
+
export const requireFile = (url) => {
|
|
350
|
+
// 方式一:容易错误
|
|
351
|
+
// try {
|
|
352
|
+
// // 如果是以 / 开头的绝对路径,直接返回
|
|
353
|
+
// if (url.startsWith('/')) {
|
|
354
|
+
// return url
|
|
355
|
+
// }
|
|
356
|
+
// // 使用new URL时路径需基于当前文件位置计算相对路径,@别名可能失效需改用../
|
|
357
|
+
// return new URL(url, import.meta.url).href
|
|
358
|
+
// } catch (error) {
|
|
359
|
+
// console.error(error)
|
|
360
|
+
// return null
|
|
361
|
+
// }
|
|
362
|
+
|
|
363
|
+
// 方式二:推荐
|
|
364
|
+
// 静态扫描 src/assets 下所有图片资源,生成路径映射(构建时执行)
|
|
365
|
+
// 匹配规则:递归扫描所有子目录,支持 png/jpg/jpeg/gif/svg 格式
|
|
366
|
+
const imageResourceMap = import.meta.glob(
|
|
367
|
+
'/src/assets/**/*.{png,jpg,jpeg,gif,svg}',
|
|
368
|
+
{ eager: true, import: 'default' } // 立即加载并获取资源路径
|
|
369
|
+
)
|
|
370
|
+
try {
|
|
371
|
+
// 拼接完整的资源路径(基于 src 目录)
|
|
372
|
+
const fullResourcePath = `/src${url}`
|
|
373
|
+
// 从映射表中获取对应资源的处理后路径(开发/生产环境自适应)
|
|
374
|
+
return imageResourceMap[fullResourcePath] || null
|
|
375
|
+
} catch (error) {
|
|
376
|
+
console.error('图片资源路径解析失败:', error)
|
|
377
|
+
return null
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
// 判断是否为数值 true 数值类型 false 其他
|
|
382
|
+
function isNum(val) {
|
|
383
|
+
// 先判定是否为number
|
|
384
|
+
if (typeof val !== 'number') {
|
|
385
|
+
return false
|
|
386
|
+
}
|
|
387
|
+
if (!isNaN(val)) {
|
|
388
|
+
return true
|
|
389
|
+
} else {
|
|
390
|
+
return false
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
// 截断到指定位数(不四舍五入)
|
|
394
|
+
const truncateToDecimal = (num, digit) => {
|
|
395
|
+
const multiplier = Math.pow(10, digit)
|
|
396
|
+
// 正数用Math.floor,负数用Math.ceil(避免-0.25截断为-0.3)
|
|
397
|
+
return num >= 0 ? Math.floor(num * multiplier) / multiplier : Math.ceil(num * multiplier) / multiplier
|
|
398
|
+
}
|
|
399
|
+
// 判断是否为数值 true 数值类型 false 其他
|
|
400
|
+
export const formatNum = (val, digit) => {
|
|
401
|
+
// 异常值判断:无效输入返回 '-'
|
|
402
|
+
if (val === null || isNaN(Number(val)) || val === undefined || val === '' || val === ' ') return '-'
|
|
403
|
+
|
|
404
|
+
// 1. 转换为数值并截断到指定位数(不四舍五入)
|
|
405
|
+
const num = Number(val)
|
|
406
|
+
const truncatedNum = truncateToDecimal(num, digit) // 核心:截断逻辑
|
|
407
|
+
|
|
408
|
+
// 2. 转为带指定位数的字符串(确保至少有digit位小数,补0)
|
|
409
|
+
// 用toFixed临时转换,再处理截断后的结果(避免浮点数精度问题)
|
|
410
|
+
const fixedStr = truncatedNum.toFixed(digit)
|
|
411
|
+
|
|
412
|
+
// 3. 分割整数和小数部分
|
|
413
|
+
const [integerPart, decimalPart] = fixedStr.split('.')
|
|
414
|
+
|
|
415
|
+
// 4. 判断是否为整数(小数部分全为0)
|
|
416
|
+
const isInteger = decimalPart.split('').every((char) => char === '0')
|
|
417
|
+
|
|
418
|
+
if (isInteger) {
|
|
419
|
+
// 整数:强制保留digit位小数(补0)
|
|
420
|
+
return `${integerPart}.${decimalPart}`
|
|
421
|
+
} else {
|
|
422
|
+
// 非整数:去掉小数部分尾部的0
|
|
423
|
+
const trimmedDecimal = decimalPart.replace(/0+$/, '')
|
|
424
|
+
return `${integerPart}.${trimmedDecimal}`
|
|
425
|
+
}
|
|
426
|
+
// // 异常值判断
|
|
427
|
+
// if (val === null || isNaN(val) || val === undefined || val === '' || val === ' ') return '--'
|
|
428
|
+
// // 数值 或 字符串数值
|
|
429
|
+
// val = Number.parseFloat(val).toFixed(digit)
|
|
430
|
+
|
|
431
|
+
// // 删除尾部的‘0’
|
|
432
|
+
// if (val.endsWith('0')) {
|
|
433
|
+
// val = val.substring(0, val.lastIndexOf('0'))
|
|
434
|
+
// }
|
|
435
|
+
|
|
436
|
+
// // 删除尾部的‘.’
|
|
437
|
+
// if (val.endsWith('.')) {
|
|
438
|
+
// val = val.substring(0, val.lastIndexOf('.'))
|
|
439
|
+
// }
|
|
440
|
+
// return val
|
|
441
|
+
}
|
|
442
|
+
/**
|
|
443
|
+
* @description: 数据深拷贝
|
|
444
|
+
* @param {*} obj 对象
|
|
445
|
+
* @return {*}
|
|
446
|
+
*/
|
|
447
|
+
export const deepClone = (obj) => {
|
|
448
|
+
const isObj = (v) => v && typeof v === 'object'
|
|
449
|
+
if (!isObj(obj)) return obj
|
|
450
|
+
const newObj = Array.isArray(obj) ? [] : {}
|
|
451
|
+
for (const key in obj) {
|
|
452
|
+
const item = obj[key]
|
|
453
|
+
newObj[key] = isObj(item) ? deepClone(item) : item
|
|
454
|
+
}
|
|
455
|
+
return newObj
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
/**
|
|
459
|
+
* @description: 文件下载
|
|
460
|
+
* @param {*} data 文件流
|
|
461
|
+
* @return {String} fileName 下载显示的文件名称
|
|
462
|
+
*/
|
|
463
|
+
export function downloadFileXls(data, fileName) {
|
|
464
|
+
// application/vnd.ms-excel,采用后台响应的content-type值
|
|
465
|
+
downloadFile(data, fileName, 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet')
|
|
466
|
+
}
|
|
467
|
+
export function downloadFileDoc(data, fileName) {
|
|
468
|
+
downloadFile(data, fileName, 'application/msword')
|
|
469
|
+
}
|
|
470
|
+
export function downloadFilePpt(data, fileName) {
|
|
471
|
+
downloadFile(data, fileName, 'application/vnd.ms-powerpoint')
|
|
472
|
+
}
|
|
473
|
+
export function downloadFileZip(data, fileName) {
|
|
474
|
+
downloadFile(data, fileName, 'application/zip')
|
|
475
|
+
}
|
|
476
|
+
export function downloadFileTxt(data, fileName) {
|
|
477
|
+
downloadFile(data, fileName, 'application/plain')
|
|
478
|
+
}
|
|
479
|
+
export function downloadFile(data, fileName, type) {
|
|
480
|
+
// 生成blob对象
|
|
481
|
+
//const blob = new Blob([data], { type: `${type}` })
|
|
482
|
+
const blob = new Blob([data], { type: `${type};charset=utf-8` })
|
|
483
|
+
|
|
484
|
+
if (window.navigator.msSaveOrOpenBlob) {
|
|
485
|
+
window.navigator.msSaveBlob(blob, fileName)
|
|
486
|
+
return
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
// 生成下载链接
|
|
490
|
+
const downElement = document.createElement('a')
|
|
491
|
+
downElement.style.display = 'none'
|
|
492
|
+
const href = window.URL.createObjectURL(blob)
|
|
493
|
+
downElement.href = href
|
|
494
|
+
downElement.download = fileName
|
|
495
|
+
document.body.appendChild(downElement)
|
|
496
|
+
downElement.click()
|
|
497
|
+
window.URL.revokeObjectURL(href)
|
|
498
|
+
downElement.remove()
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
/**
|
|
502
|
+
* 预加载图片资源
|
|
503
|
+
*
|
|
504
|
+
* @param {string[]} arrUrl - 图片URL数组
|
|
505
|
+
* @returns {Promise} 返回一个Promise,当所有图片加载完成时resolve,任意一张图片加载失败时reject
|
|
506
|
+
*/
|
|
507
|
+
export function preloadImages(arrUrl) {
|
|
508
|
+
return Promise.all(
|
|
509
|
+
arrUrl.map(
|
|
510
|
+
(url) =>
|
|
511
|
+
new Promise((resolve, reject) => {
|
|
512
|
+
const img = new Image()
|
|
513
|
+
img.onload = () => resolve(img)
|
|
514
|
+
img.onerror = reject
|
|
515
|
+
img.src = url
|
|
516
|
+
})
|
|
517
|
+
)
|
|
518
|
+
)
|
|
519
|
+
}
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @FileDescription: 系统ajax请求封装库
|
|
3
|
+
* @Author: yans
|
|
4
|
+
* @Date: 2025-12-26
|
|
5
|
+
* @LastEditors: yans
|
|
6
|
+
* @LastEditTime: 2025-12-30
|
|
7
|
+
*/
|
|
8
|
+
import axios from 'axios'
|
|
9
|
+
import { getToken, clearAuth } from '@utils/auth'
|
|
10
|
+
import { tansParamsString } from '@huitu/utils'
|
|
11
|
+
import errorCode from '@utils/errorCode'
|
|
12
|
+
|
|
13
|
+
// 全局默认基础配置
|
|
14
|
+
const _BaseURL = window.HT_EnvConfig.apiUrlNew
|
|
15
|
+
// 请求超时设置
|
|
16
|
+
const _TimeOut = 30000
|
|
17
|
+
// 请求数据大小限制
|
|
18
|
+
const _LimitSize = 5 * 1024 * 1024
|
|
19
|
+
|
|
20
|
+
// 默认请求头参数设置
|
|
21
|
+
axios.defaults.headers['Content-Type'] = 'application/json;charset=utf-8'
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* 创建Axios实例的函数
|
|
25
|
+
* @param {Object} config - 配置对象,包含baseURL、timeOut、limitSize等配置项
|
|
26
|
+
* @param {string} [config.baseURL] - 请求的基础URL
|
|
27
|
+
* @param {number} [config.timeOut] - 请求超时时间
|
|
28
|
+
* @param {number} [config.limitSize] - 请求数据大小限制
|
|
29
|
+
* @returns {Object} 返回配置好的Axios实例
|
|
30
|
+
*/
|
|
31
|
+
export function createAxiosInstance(config) {
|
|
32
|
+
// 0、基础配置
|
|
33
|
+
const baseURL = config.baseURL || _BaseURL
|
|
34
|
+
// 请求超时设置
|
|
35
|
+
const timeOut = config.timeOut || _TimeOut
|
|
36
|
+
// 请求数据大小限制
|
|
37
|
+
const limitSize = config.limitSize || _LimitSize
|
|
38
|
+
|
|
39
|
+
// 1、创建axios实例
|
|
40
|
+
const service = axios.create({
|
|
41
|
+
// axios中请求配置有baseURL选项,表示请求URL公共部分
|
|
42
|
+
baseURL: baseURL,
|
|
43
|
+
// 超时
|
|
44
|
+
timeout: timeOut,
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
// 2、请求拦截器
|
|
48
|
+
service.interceptors.request.use(
|
|
49
|
+
(config) => {
|
|
50
|
+
console.log('请求参数:', config)
|
|
51
|
+
// 是否需要设置 token
|
|
52
|
+
const isToken = (config.headers || {}).isToken === false
|
|
53
|
+
if (getToken() && !isToken) {
|
|
54
|
+
const token = getToken()
|
|
55
|
+
// 自动添加 Token 到请求头
|
|
56
|
+
// 根据后端要求选择以下方式之一:
|
|
57
|
+
config.headers['Authorization'] = `Bearer ${token}`
|
|
58
|
+
|
|
59
|
+
// 如果需要添加其他认证信息,请在此处添加
|
|
60
|
+
// 例如:config.headers['tenantId'] = 'your-tenant-id'
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// 处理特殊代理路径(如果需要)
|
|
64
|
+
// 如果请求路径匹配特殊代理规则,则不使用 baseURL
|
|
65
|
+
// 示例:config.url?.startsWith('/special-api') && (config.baseURL = null)
|
|
66
|
+
// 默认使用 baseURL
|
|
67
|
+
if (!config.baseURL) {
|
|
68
|
+
config.baseURL = baseURL
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// get请求映射params参数
|
|
72
|
+
if (config.method === 'get' && config.params) {
|
|
73
|
+
const url = config.url + '?' + tansParamsString(config.params)
|
|
74
|
+
config.params = {}
|
|
75
|
+
config.url = url
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// post等请求处理
|
|
79
|
+
if (config.method === 'post' || config.method === 'put') {
|
|
80
|
+
const requestObj = typeof config.data === 'object' ? JSON.stringify(config.data) : config.data // 请求数据
|
|
81
|
+
const requestSize = (requestObj || '').length // 请求数据大小
|
|
82
|
+
if (requestSize >= limitSize) {
|
|
83
|
+
return Promise.reject(new Error('请求数据大小超出允许的5M限制。'))
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return config
|
|
88
|
+
},
|
|
89
|
+
(error) => {
|
|
90
|
+
console.log('request error:', error)
|
|
91
|
+
return Promise.reject(error)
|
|
92
|
+
}
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
// 3、响应拦截器
|
|
96
|
+
service.interceptors.response.use(
|
|
97
|
+
(res) => {
|
|
98
|
+
// 未设置状态码则默认成功状态
|
|
99
|
+
const code = res.data?.code || 200
|
|
100
|
+
// 获取错误信息
|
|
101
|
+
const message = errorCode[code] || res.data?.msg || errorCode['default']
|
|
102
|
+
// 二进制数据则直接返回
|
|
103
|
+
if (res.request.responseType === 'blob' || res.request.responseType === 'arraybuffer') {
|
|
104
|
+
// 访问成功
|
|
105
|
+
return res.data
|
|
106
|
+
}
|
|
107
|
+
// debugger
|
|
108
|
+
if (code === 401) {
|
|
109
|
+
// 非法访问
|
|
110
|
+
clearAuth()
|
|
111
|
+
window.location.href = import.meta.env.BASE_URL + '/login'
|
|
112
|
+
return Promise.reject('无效的会话或会话已过期,请重新登录。')
|
|
113
|
+
} else if (code !== 200 && code !== 0 && code !== '0' && code !== 1000) {
|
|
114
|
+
// 后台报错
|
|
115
|
+
console.log('response error:', message)
|
|
116
|
+
return Promise.reject(new Error(message))
|
|
117
|
+
} else {
|
|
118
|
+
// 访问成功
|
|
119
|
+
return res.data
|
|
120
|
+
}
|
|
121
|
+
},
|
|
122
|
+
(error) => {
|
|
123
|
+
console.log('response error:', error)
|
|
124
|
+
let { message } = error
|
|
125
|
+
if (message == 'Network Error') {
|
|
126
|
+
message = '后端接口连接异常'
|
|
127
|
+
} else if (message.includes('timeout')) {
|
|
128
|
+
message = '系统接口请求超时'
|
|
129
|
+
} else if (message.includes('Request failed with status code')) {
|
|
130
|
+
message = '系统接口' + message.substr(message.length - 3) + '异常'
|
|
131
|
+
} else if (message == 'canceled') {
|
|
132
|
+
message = '系统接口canceled异常'
|
|
133
|
+
}
|
|
134
|
+
console.log('response error message:', message)
|
|
135
|
+
return Promise.reject(error)
|
|
136
|
+
}
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
// 返回ajax实例
|
|
140
|
+
return service
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// ***********创建ajax请求实例*********** //
|
|
144
|
+
export const axiosInstance = createAxiosInstance({
|
|
145
|
+
baseURL: _BaseURL,
|
|
146
|
+
timeOut: _TimeOut,
|
|
147
|
+
limitSize: _LimitSize,
|
|
148
|
+
})
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* @Description: '主题切换函数 data-theme属性: light | dark'
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
// 设置主题
|
|
6
|
+
import { cache } from '@huitu/utils'
|
|
7
|
+
const { local } = cache
|
|
8
|
+
export const setTheme = (theme) => {
|
|
9
|
+
local.set('data-theme', theme)
|
|
10
|
+
document.documentElement.setAttribute('data-theme', theme)
|
|
11
|
+
}
|
|
12
|
+
// 获取当前主题
|
|
13
|
+
export const getTheme = () => {
|
|
14
|
+
return local.get('data-theme') || 'light'
|
|
15
|
+
}
|
|
16
|
+
// 初始化主题 默认浅色系
|
|
17
|
+
export const initTheme = () => {
|
|
18
|
+
const theme = getTheme()
|
|
19
|
+
setTheme(theme)
|
|
20
|
+
}
|