init Project

This commit is contained in:
2025-04-09 12:10:46 +08:00
parent 505d08443c
commit 75a1447d66
207 changed files with 26387 additions and 13 deletions

29
src/utils/asyncRouter.js Normal file
View File

@@ -0,0 +1,29 @@
const viewModules = import.meta.glob('../view/**/*.vue')
const pluginModules = import.meta.glob('../plugin/**/*.vue')
export const asyncRouterHandle = (asyncRouter) => {
asyncRouter.forEach((item) => {
if (item.component && typeof item.component === 'string') {
item.meta.path = '/src/' + item.component
if (item.component.split('/')[0] === 'view') {
item.component = dynamicImport(viewModules, item.component)
} else if (item.component.split('/')[0] === 'plugin') {
item.component = dynamicImport(pluginModules, item.component)
}
}
if (item.children) {
asyncRouterHandle(item.children)
}
})
}
function dynamicImport(dynamicViewsModules, component) {
const keys = Object.keys(dynamicViewsModules)
const matchKeys = keys.filter((key) => {
const k = key.replace('../', '')
return k === component
})
const matchKey = matchKeys[0]
return dynamicViewsModules[matchKey]
}

6
src/utils/btnAuth.js Normal file
View File

@@ -0,0 +1,6 @@
import { useRoute } from 'vue-router'
import { reactive } from 'vue'
export const useBtnAuth = () => {
const route = useRoute()
return route.meta.btns || reactive({})
}

4
src/utils/bus.js Normal file
View File

@@ -0,0 +1,4 @@
// using ES6 modules
import mitt from 'mitt'
export const emitter = mitt()

View File

@@ -0,0 +1,5 @@
import { emitter } from '@/utils/bus.js'
export const closeThisPage = () => {
emitter.emit('closeThisPage')
}

44
src/utils/date.js Normal file
View File

@@ -0,0 +1,44 @@
// 对Date的扩展将 Date 转化为指定格式的String
// 月(M)、日(d)、小时(h)、分(m)、秒(s)、季度(q) 可以用 1-2 个占位符,
// 年(y)可以用 1-4 个占位符,毫秒(S)只能用 1 个占位符(是 1-3 位的数字)
// (new Date()).Format("yyyy-MM-dd hh:mm:ss.S") ==> 2006-07-02 08:09:04.423
// (new Date()).Format("yyyy-M-d h:m:s.S") ==> 2006-7-2 8:9:4.18
// eslint-disable-next-line no-extend-native
Date.prototype.Format = function(fmt) {
const o = {
'M+': this.getMonth() + 1, // 月份
'd+': this.getDate(), // 日
'h+': this.getHours(), // 小时
'm+': this.getMinutes(), // 分
's+': this.getSeconds(), // 秒
'q+': Math.floor((this.getMonth() + 3) / 3), // 季度
'S': this.getMilliseconds() // 毫秒
}
const reg = /(y+)/
if (reg.test(fmt)) {
const t = reg.exec(fmt)[1]
fmt = fmt.replace(
t,
(this.getFullYear() + '').substring(4 - t.length)
)
}
for (let k in o) {
const regx = new RegExp('(' + k + ')')
if (regx.test(fmt)) {
const t = regx.exec(fmt)[1]
fmt = fmt.replace(
t,
t.length === 1 ? o[k] : ('00' + o[k]).substring(('' + o[k]).length)
)
}
}
return fmt
}
export function formatTimeToStr(times, pattern) {
let d = new Date(times).Format('yyyy-MM-dd hh:mm:ss')
if (pattern) {
d = new Date(times).Format(pattern)
}
return d.toLocaleString()
}

26
src/utils/dictionary.js Normal file
View File

@@ -0,0 +1,26 @@
import { useDictionaryStore } from '@/pinia/modules/dictionary'
// 获取字典方法 使用示例 getDict('sex').then(res) 或者 async函数下 const res = await getDict('sex')
export const getDict = async (type) => {
const dictionaryStore = useDictionaryStore()
await dictionaryStore.getDictionary(type)
return dictionaryStore.dictionaryMap[type]
}
// 字典文字展示方法
export const showDictLabel = (
dict,
code,
keyCode = 'value',
valueCode = 'label'
) => {
if (!dict) {
return ''
}
const dictMap = {}
dict.forEach((item) => {
if (Reflect.has(item, keyCode) && Reflect.has(item, valueCode)) {
dictMap[item[keyCode]] = item[valueCode]
}
})
return Reflect.has(dictMap, code) ? dictMap[code] : ''
}

3
src/utils/doc.js Normal file
View File

@@ -0,0 +1,3 @@
export const toDoc = (url) => {
window.open(url, '_blank')
}

20
src/utils/downloadImg.js Normal file
View File

@@ -0,0 +1,20 @@
export const downloadImage = (imgsrc, name) => {
// 下载图片地址和图片名
var image = new Image()
image.setAttribute('crossOrigin', 'anonymous')
image.onload = function () {
var canvas = document.createElement('canvas')
canvas.width = image.width
canvas.height = image.height
var context = canvas.getContext('2d')
context.drawImage(image, 0, 0, image.width, image.height)
var url = canvas.toDataURL('image/png') // 得到图片的base64编码数据
var a = document.createElement('a') // 生成一个a元素
var event = new MouseEvent('click') // 创建一个单击事件
a.download = name || 'photo' // 设置图片名称
a.href = url // 将生成的URL设置为a.href属性
a.dispatchEvent(event) // 触发a的单击事件
}
image.src = imgsrc
}

17
src/utils/event.js Normal file
View File

@@ -0,0 +1,17 @@
export function addEventListen(target, event, handler, capture = false) {
if (
target.addEventListener &&
typeof target.addEventListener === 'function'
) {
target.addEventListener(event, handler, capture)
}
}
export function removeEventListen(target, event, handler, capture = false) {
if (
target.removeEventListener &&
typeof target.removeEventListener === 'function'
) {
target.removeEventListener(event, handler, capture)
}
}

View File

@@ -0,0 +1,13 @@
export const fmtTitle = (title, now) => {
const reg = /\$\{(.+?)\}/
const reg_g = /\$\{(.+?)\}/g
const result = title.match(reg_g)
if (result) {
result.forEach((item) => {
const key = item.match(reg)[1]
const value = now.params[key] || now.query[key]
title = title.replace(item, value)
})
}
return title
}

146
src/utils/format.js Normal file
View File

@@ -0,0 +1,146 @@
import { formatTimeToStr } from '@/utils/date'
import { getDict } from '@/utils/dictionary'
import { ref } from 'vue'
export const formatBoolean = (bool) => {
if (bool !== null) {
return bool ? '是' : '否'
} else {
return ''
}
}
export const formatDate = (time) => {
if (time !== null && time !== '') {
var date = new Date(time)
return formatTimeToStr(date, 'yyyy-MM-dd hh:mm:ss')
} else {
return ''
}
}
export const filterDict = (value, options) => {
const rowLabel = options && options.filter((item) => item.value === value)
return rowLabel && rowLabel[0] && rowLabel[0].label
}
export const filterDataSource = (dataSource, value) => {
if (Array.isArray(value)) {
return value.map((item) => {
const rowLabel = dataSource && dataSource.find((i) => i.value === item)
return rowLabel?.label
})
}
const rowLabel = dataSource && dataSource.find((item) => item.value === value)
return rowLabel?.label
}
export const getDictFunc = async (type) => {
const dicts = await getDict(type)
return dicts
}
const path =
import.meta.env.VITE_BASE_PATH + ':' + import.meta.env.VITE_SERVER_PORT + '/'
export const ReturnArrImg = (arr) => {
const imgArr = []
if (arr instanceof Array) {
// 如果是数组类型
for (const arrKey in arr) {
if (arr[arrKey].slice(0, 4) !== 'http') {
imgArr.push(path + arr[arrKey])
} else {
imgArr.push(arr[arrKey])
}
}
} else {
// 如果不是数组类型
if (arr?.slice(0, 4) !== 'http') {
imgArr.push(path + arr)
} else {
imgArr.push(arr)
}
}
return imgArr
}
export const returnArrImg = ReturnArrImg
export const onDownloadFile = (url) => {
window.open(path + url)
}
const colorToHex = (u) => {
let e = u.replace('#', '').match(/../g)
for (let t = 0; t < 3; t++) e[t] = parseInt(e[t], 16)
return e
}
const hexToColor = (u, e, t) => {
let a = [u.toString(16), e.toString(16), t.toString(16)]
for (let n = 0; n < 3; n++) a[n].length === 1 && (a[n] = `0${a[n]}`)
return `#${a.join('')}`
}
const generateAllColors = (u, e) => {
let t = colorToHex(u)
const target = [10, 10, 30]
for (let a = 0; a < 3; a++) t[a] = Math.floor(t[a] * (1 - e) + target[a] * e)
return hexToColor(t[0], t[1], t[2])
}
const generateAllLightColors = (u, e) => {
let t = colorToHex(u)
const target = [240, 248, 255] // RGB for blue white color
for (let a = 0; a < 3; a++) t[a] = Math.floor(t[a] * (1 - e) + target[a] * e)
return hexToColor(t[0], t[1], t[2])
}
function addOpacityToColor(u, opacity) {
let t = colorToHex(u)
return `rgba(${t[0]}, ${t[1]}, ${t[2]}, ${opacity})`
}
export const setBodyPrimaryColor = (primaryColor, darkMode) => {
let fmtColorFunc = generateAllColors
if (darkMode === 'light') {
fmtColorFunc = generateAllLightColors
}
document.documentElement.style.setProperty('--el-color-primary', primaryColor)
document.documentElement.style.setProperty(
'--el-color-primary-bg',
addOpacityToColor(primaryColor, 0.4)
)
for (let times = 1; times <= 2; times++) {
document.documentElement.style.setProperty(
`--el-color-primary-dark-${times}`,
fmtColorFunc(primaryColor, times / 10)
)
}
for (let times = 1; times <= 10; times++) {
document.documentElement.style.setProperty(
`--el-color-primary-light-${times}`,
fmtColorFunc(primaryColor, times / 10)
)
}
document.documentElement.style.setProperty(
`--el-menu-hover-bg-color`,
addOpacityToColor(primaryColor, 0.2)
)
}
const baseUrl = ref(import.meta.env.VITE_BASE_API)
export const getBaseUrl = () => {
return baseUrl.value === '/' ? '' : baseUrl.value
}
export const CreateUUID = () => {
let d = new Date().getTime()
if (window.performance && typeof window.performance.now === 'function') {
d += performance.now()
}
return '00000000-0000-0000-0000-000000000000'.replace(/0/g, (c) => {
const r = (d + Math.random() * 16) % 16 | 0 // d是随机种子
d = Math.floor(d / 16)
return (c === '0' ? r : (r & 0x3) | 0x8).toString(16)
})
}

126
src/utils/image.js Normal file
View File

@@ -0,0 +1,126 @@
export default class ImageCompress {
constructor(file, fileSize, maxWH = 1920) {
this.file = file
this.fileSize = fileSize
this.maxWH = maxWH // 最大长宽
}
compress() {
// 压缩
const fileType = this.file.type
const fileSize = this.file.size / 1024
return new Promise((resolve) => {
const reader = new FileReader()
reader.readAsDataURL(this.file)
reader.onload = () => {
const canvas = document.createElement('canvas')
const img = document.createElement('img')
img.src = reader.result
img.onload = () => {
const ctx = canvas.getContext('2d')
const _dWH = this.dWH(img.width, img.height, this.maxWH)
canvas.width = _dWH.width
canvas.height = _dWH.height
// 清空后, 重写画布
ctx.clearRect(0, 0, canvas.width, canvas.height)
ctx.drawImage(img, 0, 0, canvas.width, canvas.height)
const newImgData = canvas.toDataURL(fileType, 0.9)
// 压缩宽高后的图像大小
const newImgSize = this.fileSizeKB(newImgData)
if (newImgSize > this.fileSize) {
console.log('图片尺寸太大!' + fileSize + ' >> ' + newImgSize)
}
const blob = this.dataURLtoBlob(newImgData, fileType)
const nfile = new File([blob], this.file.name)
resolve(nfile)
}
}
})
}
/**
* 长宽等比缩小
* 图像的一边(长或宽)为最大目标值
*/
dWH(srcW, srcH, dMax) {
const defaults = {
width: srcW,
height: srcH
}
if (Math.max(srcW, srcH) > dMax) {
if (srcW > srcH) {
defaults.width = dMax
defaults.height = Math.round(srcH * (dMax / srcW))
return defaults
} else {
defaults.height = dMax
defaults.width = Math.round(srcW * (dMax / srcH))
return defaults
}
} else {
return defaults
}
}
fileSizeKB(dataURL) {
let sizeKB = 0
sizeKB = Math.round((dataURL.split(',')[1].length * 3) / 4 / 1024)
return sizeKB
}
/**
* 转为Blob
*/
dataURLtoBlob(dataURL, fileType) {
const byteString = atob(dataURL.split(',')[1])
let mimeString = dataURL.split(',')[0].split(':')[1].split(';')[0]
const ab = new ArrayBuffer(byteString.length)
const ia = new Uint8Array(ab)
for (let i = 0; i < byteString.length; i++) {
ia[i] = byteString.charCodeAt(i)
}
if (fileType) {
mimeString = fileType
}
return new Blob([ab], { type: mimeString, lastModifiedDate: new Date() })
}
}
const path = import.meta.env.VITE_FILE_API
export const getUrl = (url) => {
if (url && url.slice(0, 4) !== 'http') {
if (path === '/') {
return url
}
if (url.slice(0, 1) === '/') {
return path + url
}
return path + '/' + url
} else {
return url
}
}
const VIDEO_EXTENSIONS = ['.mp4', '.mov', '.webm', '.ogg']
const VIDEO_MIME_TYPES = ['video/mp4', 'video/webm', 'video/ogg']
const IMAGE_MIME_TYPES = ['image/jpeg', 'image/png', 'image/webp', 'image/svg+xml']
export const isVideoExt = (url) => {
const urlLower = url?.toLowerCase() || ''
return urlLower !== '' && VIDEO_EXTENSIONS.some(ext => urlLower.endsWith(ext))
}
export const isVideoMime = (type) => {
const typeLower = type?.toLowerCase() || ''
return typeLower !== '' && VIDEO_MIME_TYPES.includes(typeLower)
}
export const isImageMime = (type) => {
const typeLower = type?.toLowerCase() || ''
return typeLower !== '' && IMAGE_MIME_TYPES.includes(typeLower)
}

9
src/utils/page.js Normal file
View File

@@ -0,0 +1,9 @@
import { fmtTitle } from '@/utils/fmtRouterTitle'
import config from '@/core/config'
export default function getPageTitle(pageTitle, route) {
if (pageTitle) {
const title = fmtTitle(pageTitle, route)
return `${title} - ${config.appName}`
}
return `${config.appName}`
}

14
src/utils/params.js Normal file
View File

@@ -0,0 +1,14 @@
import { useParamsStore } from '@/pinia/modules/params'
/*
* 获取参数方法 使用示例 getParams('key').then(res) 或者 async函数下 const res = await getParams('key')
* const res = ref('')
* const fun = async () => {
* res.value = await getParams('test')
* }
* fun()
*/
export const getParams = async(key) => {
const paramsStore = useParamsStore()
await paramsStore.getParams(key)
return paramsStore.paramsMap[key]
}

172
src/utils/request.js Normal file
View File

@@ -0,0 +1,172 @@
import axios from 'axios' // 引入axios
import { ElMessage, ElMessageBox } from 'element-plus'
import { useUserStore } from '@/pinia/modules/user'
import router from '@/router/index'
import { ElLoading } from 'element-plus'
const service = axios.create({
baseURL: import.meta.env.VITE_BASE_API,
timeout: 99999
})
let activeAxios = 0
let timer
let loadingInstance
const showLoading = (
option = {
target: null
}
) => {
const loadDom = document.getElementById('gva-base-load-dom')
activeAxios++
if (timer) {
clearTimeout(timer)
}
timer = setTimeout(() => {
if (activeAxios > 0) {
if (!option.target) option.target = loadDom
loadingInstance = ElLoading.service(option)
}
}, 400)
}
const closeLoading = () => {
activeAxios--
if (activeAxios <= 0) {
clearTimeout(timer)
loadingInstance && loadingInstance.close()
}
}
// http request 拦截器
service.interceptors.request.use(
(config) => {
if (!config.donNotShowLoading) {
showLoading(config.loadingOption)
}
const userStore = useUserStore()
config.headers = {
'Content-Type': 'application/json',
'x-token': userStore.token,
'x-user-id': userStore.userInfo.ID,
...config.headers
}
return config
},
(error) => {
if (!error.config.donNotShowLoading) {
closeLoading()
}
ElMessage({
showClose: true,
message: error,
type: 'error'
})
return error
}
)
// http response 拦截器
service.interceptors.response.use(
(response) => {
const userStore = useUserStore()
if (!response.config.donNotShowLoading) {
closeLoading()
}
if (response.headers['new-token']) {
userStore.setToken(response.headers['new-token'])
}
if (response.data.code === 0 || response.headers.success === 'true') {
if (response.headers.msg) {
response.data.msg = decodeURI(response.headers.msg)
}
return response.data
} else {
ElMessage({
showClose: true,
message: response.data.msg || decodeURI(response.headers.msg),
type: 'error'
})
return response.data.msg ? response.data : response
}
},
(error) => {
if (!error.config.donNotShowLoading) {
closeLoading()
}
if (!error.response) {
ElMessageBox.confirm(
`
<p>检测到请求错误</p>
<p>${error}</p>
`,
'请求报错',
{
dangerouslyUseHTMLString: true,
distinguishCancelAndClose: true,
confirmButtonText: '稍后重试',
cancelButtonText: '取消'
}
)
return
}
switch (error.response.status) {
case 500:
ElMessageBox.confirm(
`
<p>检测到接口错误${error}</p>
<p>错误码<span style="color:red"> 500 </span>此类错误内容常见于后台panic请先查看后台日志如果影响您正常使用可强制登出清理缓存</p>
`,
'接口报错',
{
dangerouslyUseHTMLString: true,
distinguishCancelAndClose: true,
confirmButtonText: '清理缓存',
cancelButtonText: '取消'
}
).then(() => {
const userStore = useUserStore()
userStore.ClearStorage()
router.push({ name: 'Login', replace: true })
})
break
case 404:
ElMessageBox.confirm(
`
<p>检测到接口错误${error}</p>
<p>错误码<span style="color:red"> 404 </span>此类错误多为接口未注册或未重启或者请求路径方法与api路径方法不符--如果为自动化代码请检查是否存在空格</p>
`,
'接口报错',
{
dangerouslyUseHTMLString: true,
distinguishCancelAndClose: true,
confirmButtonText: '我知道了',
cancelButtonText: '取消'
}
)
break
case 401:
ElMessageBox.confirm(
`
<p>无效的令牌</p>
<p>错误码:<span style="color:red"> 401 </span>错误信息:${error}</p>
`,
'身份信息',
{
dangerouslyUseHTMLString: true,
distinguishCancelAndClose: true,
confirmButtonText: '重新登录',
cancelButtonText: '取消'
}
).then(() => {
const userStore = useUserStore()
userStore.ClearStorage()
router.push({ name: 'Login', replace: true })
})
break
}
return error
}
)
export default service

29
src/utils/stringFun.js Normal file
View File

@@ -0,0 +1,29 @@
/* eslint-disable */
export const toUpperCase = (str) => {
if (str[0]) {
return str.replace(str[0], str[0].toUpperCase())
} else {
return ''
}
}
export const toLowerCase = (str) => {
if (str[0]) {
return str.replace(str[0], str[0].toLowerCase())
} else {
return ''
}
}
// 驼峰转换下划线
export const toSQLLine = (str) => {
if (str === 'ID') return 'ID'
return str.replace(/([A-Z])/g, '_$1').toLowerCase()
}
// 下划线转换驼峰
export const toHump = (name) => {
return name.replace(/\_(\w)/g, function (all, letter) {
return letter.toUpperCase()
})
}