feat: 使用prettierrc对代码进行格式化

This commit is contained in:
piexlMax(奇淼 2024-11-09 10:32:26 +08:00
parent 87a4ce17e1
commit 7d9af64f6d
139 changed files with 12685 additions and 12664 deletions

12
web/.prettierrc Normal file
View File

@ -0,0 +1,12 @@
{
"printWidth": 80,
"tabWidth": 2,
"useTabs": false,
"semi": false,
"singleQuote": true,
"trailingComma": "none",
"bracketSpacing": true,
"arrowParens": "always",
"vueIndentScriptAndStyle": true,
"endOfLine": "lf"
}

View File

@ -1,33 +1,38 @@
# gin-vue-admin web # gin-vue-admin web
## Project setup ## Project setup
``` ```
npm install npm install
``` ```
### Compiles and hot-reloads for development ### Compiles and hot-reloads for development
``` ```
npm run serve npm run serve
``` ```
### Compiles and minifies for production ### Compiles and minifies for production
``` ```
npm run build npm run build
``` ```
### Run your tests ### Run your tests
``` ```
npm run test npm run test
``` ```
### Lints and fixes files ### Lints and fixes files
``` ```
npm run lint npm run lint
``` ```
整理代码结构 整理代码结构
``` lua
```lua
web web
├── babel.config.js ├── babel.config.js
├── Dockerfile ├── Dockerfile
@ -67,7 +72,7 @@ web
│ │ ├── asyncRouter.js -- 动态路由相关 │ │ ├── asyncRouter.js -- 动态路由相关
│ │ ├── bus.js -- 全局mitt声明文件 │ │ ├── bus.js -- 全局mitt声明文件
│ │ ├── date.js -- 日期相关 │ │ ├── date.js -- 日期相关
│ │ ├── dictionary.js -- 获取字典方法 │ │ ├── dictionary.js -- 获取字典方法
│ │ ├── downloadImg.js -- 下载图片方法 │ │ ├── downloadImg.js -- 下载图片方法
│ │ ├── format.js -- 格式整理相关 │ │ ├── format.js -- 格式整理相关
│ │ ├── image.js -- 图片相关方法 │ │ ├── image.js -- 图片相关方法
@ -80,21 +85,21 @@ web
| | ├── error -- 错误 | | ├── error -- 错误
| | ├── example --上传案例 | | ├── example --上传案例
| | ├── iconList -- icon列表 | | ├── iconList -- icon列表
| | ├── init -- 初始化数据 | | ├── init -- 初始化数据
| | | ├── index -- 新版本 | | | ├── index -- 新版本
| | | ├── init -- 旧版本 | | | ├── init -- 旧版本
| | ├── layout -- layout约束页面 | | ├── layout -- layout约束页面
| | | ├── aside | | | ├── aside
| | | ├── bottomInfo -- bottomInfo | | | ├── bottomInfo -- bottomInfo
| | | ├── screenfull -- 全屏设置 | | | ├── screenfull -- 全屏设置
| | | ├── setting -- 系统设置 | | | ├── setting -- 系统设置
| | | └── index.vue -- base 约束 | | | └── index.vue -- base 约束
| | ├── login --登录 | | ├── login --登录
| | ├── person --个人中心 | | ├── person --个人中心
| | ├── superAdmin -- 超级管理员操作 | | ├── superAdmin -- 超级管理员操作
| | ├── system -- 系统检测页面 | | ├── system -- 系统检测页面
| | ├── systemTools -- 系统配置相关页面 | | ├── systemTools -- 系统配置相关页面
| | └── routerHolder.vue -- page 入口页面 | | └── routerHolder.vue -- page 入口页面
├── vite.config.js -- vite 配置文件 ├── vite.config.js -- vite 配置文件
└── yarn.lock └── yarn.lock

View File

@ -1,8 +1,4 @@
module.exports = { module.exports = {
presets: [ presets: [],
plugins: []
],
'plugins': [
]
} }

View File

@ -1,6 +1,6 @@
import js from '@eslint/js' import js from '@eslint/js'
import pluginVue from 'eslint-plugin-vue' import pluginVue from 'eslint-plugin-vue'
import globals from "globals" import globals from 'globals'
export default [ export default [
js.configs.recommended, js.configs.recommended,
@ -10,20 +10,20 @@ export default [
files: ['**/*.{js,mjs,jsx,vue}'], files: ['**/*.{js,mjs,jsx,vue}'],
languageOptions: { languageOptions: {
ecmaVersion: 'latest', ecmaVersion: 'latest',
sourceType: "module", sourceType: 'module',
globals: globals.node globals: globals.node
}, },
rules: { rules: {
"vue/max-attributes-per-line" : 0, 'vue/max-attributes-per-line': 0,
"vue/no-v-model-argument" : 0, 'vue/no-v-model-argument': 0,
"vue/multi-word-component-names": "off", 'vue/multi-word-component-names': 'off',
'no-lone-blocks': 'off', 'no-lone-blocks': 'off',
'no-extend-native': 'off', 'no-extend-native': 'off',
'no-unused-vars': ['error', { "argsIgnorePattern": '^_' }], 'no-unused-vars': ['error', { argsIgnorePattern: '^_' }]
}, }
}, },
{ {
name: 'app/files-to-ignore', name: 'app/files-to-ignore',
ignores: ['**/dist/**', '**/build/*.js', '**/src/assets/**', '**/public/**'], ignores: ['**/dist/**', '**/build/*.js', '**/src/assets/**', '**/public/**']
}, }
] ]

View File

@ -1,102 +1,115 @@
<!DOCTYPE html> <!doctype html>
<html lang="zh-cn" class="transition-colors"> <html lang="zh-cn" class="transition-colors">
<head>
<head> <meta charset="utf-8" />
<meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width,initial-scale=1.0" />
<meta name="viewport" content="width=device-width,initial-scale=1.0"> <meta
<meta content="Gin,Vue,Admin.Gin-Vue-Admin,GVA,gin-vue-admin,后台管理框架,vue后台管理框架,gin-vue-admin文档,gin-vue-admin首页,gin-vue-admin" name="keywords" /> content="Gin,Vue,Admin.Gin-Vue-Admin,GVA,gin-vue-admin,后台管理框架,vue后台管理框架,gin-vue-admin文档,gin-vue-admin首页,gin-vue-admin"
<link rel="icon" href="favicon.ico"> name="keywords"
/>
<link rel="icon" href="favicon.ico" />
<title></title> <title></title>
<style> <style>
.transition-colors{ .transition-colors {
transition-property: color, background-color, border-color, text-decoration-color, fill, stroke; transition-property: color, background-color, border-color,
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); text-decoration-color, fill, stroke;
transition-duration: 150ms; transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
transition-duration: 150ms;
} }
body{ body {
margin: 0; margin: 0;
--64f90c3645474bd5: #409eff; --64f90c3645474bd5: #409eff;
}
#gva-loading-box {
position: relative;
display: flex;
align-items: center;
justify-content: center;
height: 100vh;
width: 100vw;
}
#loading-text {
position: absolute;
bottom: calc(50% - 100px);
left: 0;
width: 100%;
text-align: center;
color: #666;
font-size: 14px;
} }
#gva-loading-box{
position: relative;
display: flex;
align-items: center;
justify-content: center;
height: 100vh;
width: 100vw;
}
#loading-text {
position: absolute;
bottom: calc(50% - 100px);
left: 0;
width: 100%;
text-align: center;
color: #666;
font-size: 14px;
}
#loading { #loading {
position: absolute; position: absolute;
top: calc(50% - 20px); top: calc(50% - 20px);
left: calc(50% - 20px); left: calc(50% - 20px);
} }
@keyframes loader { @keyframes loader {
0% { left: -100px } 0% {
100% { left: 110%; } left: -100px;
}
100% {
left: 110%;
}
} }
#box { #box {
width: 50px; width: 50px;
height: 50px; height: 50px;
background: var(--64f90c3645474bd5); background: var(--64f90c3645474bd5);
animation: animate .5s linear infinite; animation: animate 0.5s linear infinite;
position: absolute; position: absolute;
top: 0; top: 0;
left: 0; left: 0;
border-radius: 3px; border-radius: 3px;
} }
@keyframes animate { @keyframes animate {
17% { border-bottom-right-radius: 3px; } 17% {
25% { transform: translateY(9px) rotate(22.5deg); } border-bottom-right-radius: 3px;
50% { }
transform: translateY(18px) scale(1,.9) rotate(45deg) ; 25% {
border-bottom-right-radius: 40px; transform: translateY(9px) rotate(22.5deg);
} }
75% { transform: translateY(9px) rotate(67.5deg); } 50% {
100% { transform: translateY(0) rotate(90deg); } transform: translateY(18px) scale(1, 0.9) rotate(45deg);
border-bottom-right-radius: 40px;
}
75% {
transform: translateY(9px) rotate(67.5deg);
}
100% {
transform: translateY(0) rotate(90deg);
}
} }
#shadow { #shadow {
width: 50px; width: 50px;
height: 5px; height: 5px;
background: #000; background: #000;
opacity: 0.1; opacity: 0.1;
position: absolute; position: absolute;
top: 59px; top: 59px;
left: 0; left: 0;
border-radius: 50%; border-radius: 50%;
animation: shadow .5s linear infinite; animation: shadow 0.5s linear infinite;
} }
.dark #shadow{ .dark #shadow {
background: #fff; background: #fff;
} }
@keyframes shadow { @keyframes shadow {
50% { 50% {
transform: scale(1.2,1); transform: scale(1.2, 1);
} }
} }
</style> </style>
</head> </head>
<body> <body>
<div id="gva-loading-box"> <div id="gva-loading-box">
<div id="loading"> <div id="loading">
<div id="shadow"></div> <div id="shadow"></div>
<div id="box"></div> <div id="box"></div>
</div> </div>
<div id="loading-text">系统正在加载中请稍候...</div> <div id="loading-text">系统正在加载中请稍候...</div>
</div> </div>
<div id="app"></div> <div id="app"></div>
<script type="module" src="./src/main.js"></script> <script type="module" src="./src/main.js"></script>
</body> </body>
</html> </html>

View File

@ -1,10 +1,10 @@
{ {
"compilerOptions": { "compilerOptions": {
"baseUrl": "./", "baseUrl": "./",
"paths": { "paths": {
"@/*": ["src/*"] "@/*": ["src/*"]
} }
}, },
"exclude": ["node_modules", "dist"], "exclude": ["node_modules", "dist"],
"include": ["src/**/*"] "include": ["src/**/*"]
} }

View File

@ -1,73 +1,73 @@
{ {
"name": "gin-vue-admin", "name": "gin-vue-admin",
"version": "2.7.7", "version": "2.7.7",
"private": true, "private": true,
"scripts": { "scripts": {
"serve": "node openDocument.js && vite --host --mode development", "serve": "node openDocument.js && vite --host --mode development",
"build": "vite build --mode production", "build": "vite build --mode production",
"limit-build": "npm install increase-memory-limit-fixbug cross-env -g && npm run fix-memory-limit && node ./limit && npm run build", "limit-build": "npm install increase-memory-limit-fixbug cross-env -g && npm run fix-memory-limit && node ./limit && npm run build",
"preview": "vite preview", "preview": "vite preview",
"fix-memory-limit": "cross-env LIMIT=4096 increase-memory-limit" "fix-memory-limit": "cross-env LIMIT=4096 increase-memory-limit"
}, },
"dependencies": { "dependencies": {
"@element-plus/icons-vue": "^2.3.1", "@element-plus/icons-vue": "^2.3.1",
"@form-create/designer": "^3.2.6", "@form-create/designer": "^3.2.6",
"@form-create/element-ui": "^3.2.10", "@form-create/element-ui": "^3.2.10",
"@vue-office/docx": "^1.6.2", "@vue-office/docx": "^1.6.2",
"@vue-office/excel": "^1.7.11", "@vue-office/excel": "^1.7.11",
"@vue-office/pdf": "^2.0.2", "@vue-office/pdf": "^2.0.2",
"@vueuse/core": "^11.0.3", "@vueuse/core": "^11.0.3",
"@wangeditor/editor": "^5.1.23", "@wangeditor/editor": "^5.1.23",
"@wangeditor/editor-for-vue": "^5.1.12", "@wangeditor/editor-for-vue": "^5.1.12",
"ace-builds": "^1.36.4", "ace-builds": "^1.36.4",
"axios": "^1.7.7", "axios": "^1.7.7",
"chokidar": "^4.0.0", "chokidar": "^4.0.0",
"core-js": "^3.38.1", "core-js": "^3.38.1",
"default-passive-events": "^2.0.0", "default-passive-events": "^2.0.0",
"echarts": "5.5.1", "echarts": "5.5.1",
"element-plus": "^2.8.5", "element-plus": "^2.8.5",
"highlight.js": "^11.10.0", "highlight.js": "^11.10.0",
"js-cookie": "^3.0.5", "js-cookie": "^3.0.5",
"marked": "14.1.1", "marked": "14.1.1",
"marked-highlight": "^2.1.4", "marked-highlight": "^2.1.4",
"mitt": "^3.0.1", "mitt": "^3.0.1",
"nprogress": "^0.2.0", "nprogress": "^0.2.0",
"path": "^0.12.7", "path": "^0.12.7",
"pinia": "^2.2.2", "pinia": "^2.2.2",
"qs": "^6.13.0", "qs": "^6.13.0",
"screenfull": "^6.0.2", "screenfull": "^6.0.2",
"sortablejs": "^1.15.3", "sortablejs": "^1.15.3",
"spark-md5": "^3.0.2", "spark-md5": "^3.0.2",
"tailwindcss": "^3.4.10", "tailwindcss": "^3.4.10",
"vform3-builds": "^3.0.10", "vform3-builds": "^3.0.10",
"vite-auto-import-svg": "^1.1.0", "vite-auto-import-svg": "^1.1.0",
"vue": "^3.5.7", "vue": "^3.5.7",
"vue-echarts": "^7.0.3", "vue-echarts": "^7.0.3",
"vue-router": "^4.4.3", "vue-router": "^4.4.3",
"vue3-ace-editor": "^2.2.4", "vue3-ace-editor": "^2.2.4",
"vuedraggable": "^4.1.0" "vuedraggable": "^4.1.0"
}, },
"devDependencies": { "devDependencies": {
"@babel/eslint-parser": "^7.25.1", "@babel/eslint-parser": "^7.25.1",
"@eslint/js": "^9.14.0", "@eslint/js": "^9.14.0",
"@vitejs/plugin-legacy": "^5.4.2", "@vitejs/plugin-legacy": "^5.4.2",
"@vitejs/plugin-vue": "^5.1.4", "@vitejs/plugin-vue": "^5.1.4",
"@vue/cli-plugin-babel": "~5.0.8", "@vue/cli-plugin-babel": "~5.0.8",
"@vue/cli-plugin-eslint": "~5.0.8", "@vue/cli-plugin-eslint": "~5.0.8",
"@vue/cli-plugin-router": "~5.0.8", "@vue/cli-plugin-router": "~5.0.8",
"@vue/cli-plugin-vuex": "~5.0.8", "@vue/cli-plugin-vuex": "~5.0.8",
"@vue/cli-service": "~5.0.8", "@vue/cli-service": "~5.0.8",
"@vue/compiler-sfc": "^3.5.1", "@vue/compiler-sfc": "^3.5.1",
"babel-plugin-import": "^1.13.8", "babel-plugin-import": "^1.13.8",
"chalk": "^5.3.0", "chalk": "^5.3.0",
"dotenv": "^16.4.5", "dotenv": "^16.4.5",
"eslint": "^9.14.0", "eslint": "^9.14.0",
"eslint-plugin-vue": "^9.30.0", "eslint-plugin-vue": "^9.30.0",
"sass": "^1.78.0", "sass": "^1.78.0",
"terser": "^5.31.6", "terser": "^5.31.6",
"vite": "^5.4.3", "vite": "^5.4.3",
"vite-plugin-banner": "^0.8.0", "vite-plugin-banner": "^0.8.0",
"vite-plugin-importer": "^0.2.5", "vite-plugin-importer": "^0.2.5",
"vite-plugin-vue-devtools": "^7.4.4" "vite-plugin-vue-devtools": "^7.4.4"
} }
} }

View File

@ -1,6 +1,6 @@
module.exports = { module.exports = {
plugins: { plugins: {
tailwindcss: {}, tailwindcss: {},
autoprefixer: {}, autoprefixer: {}
}, }
} }

View File

@ -1,5 +1,8 @@
<template> <template>
<div id="app" class="bg-gray-50 text-slate-700 dark:text-slate-500 dark:bg-slate-800"> <div
id="app"
class="bg-gray-50 text-slate-700 dark:text-slate-500 dark:bg-slate-800"
>
<el-config-provider :locale="zhCn"> <el-config-provider :locale="zhCn">
<router-view /> <router-view />
</el-config-provider> </el-config-provider>
@ -7,35 +10,32 @@
</template> </template>
<script setup> <script setup>
import zhCn from 'element-plus/dist/locale/zh-cn.mjs' import zhCn from 'element-plus/dist/locale/zh-cn.mjs'
import {useAppStore} from "@/pinia"; import { useAppStore } from '@/pinia'
useAppStore() useAppStore()
defineOptions({ defineOptions({
name: 'App' name: 'App'
}) })
</script> </script>
<style lang="scss"> <style lang="scss">
//
#app {
height: 100vh;
overflow: hidden;
font-weight: 400 !important;
}
.el-button {
font-weight: 400 !important;
}
// .gva-body-h {
#app { min-height: calc(100% - 3rem);
height: 100vh; }
overflow: hidden;
font-weight: 400 !important;
}
.el-button{
font-weight: 400 !important;
}
.gva-body-h{
min-height: calc(100% - 3rem);
}
.gva-container{
height: calc(100% - 2.5rem);
}
.gva-container2{
height: calc(100% - 4.5rem);
}
.gva-container {
height: calc(100% - 2.5rem);
}
.gva-container2 {
height: calc(100% - 4.5rem);
}
</style> </style>

View File

@ -145,7 +145,6 @@ export const freshCasbin = () => {
}) })
} }
export const syncApi = () => { export const syncApi = () => {
return service({ return service({
url: '/api/syncApi', url: '/api/syncApi',
@ -153,7 +152,6 @@ export const syncApi = () => {
}) })
} }
export const getApiGroups = () => { export const getApiGroups = () => {
return service({ return service({
url: '/api/getApiGroups', url: '/api/getApiGroups',
@ -169,11 +167,10 @@ export const ignoreApi = (data) => {
}) })
} }
export const enterSyncApi = (data) => { export const enterSyncApi = (data) => {
return service({ return service({
url: '/api/enterSyncApi', url: '/api/enterSyncApi',
method: 'post', method: 'post',
data data
}) })
} }

View File

@ -19,7 +19,7 @@ export const deleteAuthority = (data) => {
return service({ return service({
url: '/authority/deleteAuthority', url: '/authority/deleteAuthority',
method: 'post', method: 'post',
data, data
}) })
} }

View File

@ -1,4 +1,3 @@
import service from '@/utils/request' import service from '@/utils/request'
export const getAuthorityBtnApi = (data) => { export const getAuthorityBtnApi = (data) => {
@ -24,4 +23,3 @@ export const canRemoveAuthorityBtnApi = (params) => {
params params
}) })
} }

View File

@ -139,28 +139,26 @@ export const pubPlug = (params) => {
}) })
} }
export const llmAuto = (data) => { export const llmAuto = (data) => {
return service({ return service({
url: '/autoCode/llmAuto', url: '/autoCode/llmAuto',
method: 'post', method: 'post',
data:{...data,mode:'ai'}, data: { ...data, mode: 'ai' },
timeout: 1000 * 60 * 10, timeout: 1000 * 60 * 10,
loadingOption:{ loadingOption: {
lock: true, lock: true,
fullscreen:true, fullscreen: true,
text: `小淼正在思考,请稍候...`, text: `小淼正在思考,请稍候...`
} }
}) })
} }
export const butler = (data) => { export const butler = (data) => {
return service({ return service({
url: '/autoCode/llmAuto', url: '/autoCode/llmAuto',
method: 'post', method: 'post',
data:{...data,mode:'butler'}, data: { ...data, mode: 'butler' },
timeout: 1000 * 60 * 10, timeout: 1000 * 60 * 10
}) })
} }

View File

@ -4,7 +4,9 @@ const service = axios.create()
export function Commits(page) { export function Commits(page) {
return service({ return service({
url: 'https://api.github.com/repos/flipped-aurora/gin-vue-admin/commits?page=' + page, url:
'https://api.github.com/repos/flipped-aurora/gin-vue-admin/commits?page=' +
page,
method: 'get' method: 'get'
}) })
} }

View File

@ -53,4 +53,3 @@ export const reloadSystem = (data) => {
data data
}) })
} }

View File

@ -27,42 +27,41 @@
</template> </template>
<script setup> <script setup>
defineOptions({
defineOptions({ name: 'ArrayCtrl'
name: 'ArrayCtrl',
})
import { nextTick, ref } from 'vue'
const inputValue = ref('')
const inputVisible = ref(false)
const InputRef = ref(null)
const modelValue = defineModel()
defineProps({
editable: {
type: Boolean,
default: () => false
}
})
const handleClose = (tag) => {
modelValue.value.splice(modelValue.value.indexOf(tag), 1)
}
const showInput = () => {
inputVisible.value = true
nextTick(() => {
InputRef.value?.input?.focus()
}) })
}
const handleInputConfirm = () => { import { nextTick, ref } from 'vue'
if (inputValue.value) {
modelValue.value.push(inputValue.value) const inputValue = ref('')
const inputVisible = ref(false)
const InputRef = ref(null)
const modelValue = defineModel()
defineProps({
editable: {
type: Boolean,
default: () => false
}
})
const handleClose = (tag) => {
modelValue.value.splice(modelValue.value.indexOf(tag), 1)
}
const showInput = () => {
inputVisible.value = true
nextTick(() => {
InputRef.value?.input?.focus()
})
}
const handleInputConfirm = () => {
if (inputValue.value) {
modelValue.value.push(inputValue.value)
}
inputVisible.value = false
inputValue.value = ''
} }
inputVisible.value = false
inputValue.value = ''
}
</script> </script>

View File

@ -3,14 +3,17 @@
未经授权的商用使用可能会被我们的资产搜索引擎爬取并可能导致后续索赔索赔金额将不低于高级授权费的十倍请您遵守版权法律法规尊重知识产权 未经授权的商用使用可能会被我们的资产搜索引擎爬取并可能导致后续索赔索赔金额将不低于高级授权费的十倍请您遵守版权法律法规尊重知识产权
--> -->
<template> <template>
<div class="flex flex-col md:flex-row gap-2 items-center text-sm text-slate-700 dark:text-slate-500 justify-center py-2"> <div
class="flex flex-col md:flex-row gap-2 items-center text-sm text-slate-700 dark:text-slate-500 justify-center py-2"
>
<div class="text-center"> <div class="text-center">
<span class="mr-1">Powered by</span> <span class="mr-1">Powered by</span>
<span> <span>
<a <a
class="font-bold text-active" class="font-bold text-active"
href="https://github.com/flipped-aurora/gin-vue-admin" href="https://github.com/flipped-aurora/gin-vue-admin"
>Gin-Vue-Admin</a> >Gin-Vue-Admin</a
>
</span> </span>
</div> </div>
<slot /> <slot />
@ -20,22 +23,22 @@
<a <a
class="font-bold text-active" class="font-bold text-active"
href="https://github.com/flipped-aurora" href="https://github.com/flipped-aurora"
>flipped-aurora团队</a> >flipped-aurora团队</a
>
</span> </span>
</div> </div>
</div> </div>
</template> </template>
<script setup> <script setup>
defineOptions({ defineOptions({
name: 'BottomInfo' name: 'BottomInfo'
}) })
console.log( console.log(
`%c powered by %c flipped-aurorae %c`, `%c powered by %c flipped-aurorae %c`,
'background:#0081ff; padding: 1px; border-radius: 3px 0 0 3px; color: #fff', 'background:#0081ff; padding: 1px; border-radius: 3px 0 0 3px; color: #fff',
'background:#354855; padding: 1px 5px; border-radius: 0 3px 3px 0; color: #fff; font-weight: bold;', 'background:#354855; padding: 1px 5px; border-radius: 0 3px 3px 0; color: #fff; font-weight: bold;',
'background:transparent' 'background:transparent'
) )
</script> </script>

View File

@ -14,43 +14,41 @@
/> />
</template> </template>
<script setup> <script setup>
import { ref, nextTick } from 'vue'; import { ref, nextTick } from 'vue'
import VCharts from 'vue-echarts'; import VCharts from 'vue-echarts'
import { useWindowResize } from '@/hooks/use-windows-resize' import { useWindowResize } from '@/hooks/use-windows-resize'
defineProps({ defineProps({
options: { options: {
type: Object, type: Object,
default() { default() {
return {}; return {}
}
}, },
}, autoResize: {
autoResize: { type: Boolean,
type: Boolean, default: true
default: true, },
}, width: {
width: { type: String,
type: String, default: '100%'
default: '100%', },
}, height: {
height: { type: String,
type: String, default: '100%'
default: '100%', }
}, })
}); const renderChart = ref(false)
const renderChart = ref(false);
nextTick(() => {
renderChart.value = true;
});
useWindowResize(()=>{
renderChart.value = false;
nextTick(() => { nextTick(() => {
renderChart.value = true; renderChart.value = true
}); })
useWindowResize(() => {
}) renderChart.value = false
nextTick(() => {
renderChart.value = true
})
})
</script> </script>
<style scoped lang="less"></style> <style scoped lang="less"></style>

View File

@ -10,20 +10,16 @@
v-model="searchInput" v-model="searchInput"
class="quick-input" class="quick-input"
placeholder="请输入你需要快捷到达的功能" placeholder="请输入你需要快捷到达的功能"
> />
</template> </template>
<div <div v-for="(option, index) in options" :key="index">
v-for="(option,index) in options" <div v-if="option.children.length" class="quick-title">
:key="index" {{ option.label }}
> </div>
<div <div
v-if="option.children.length" v-for="(item, key) in option.children"
class="quick-title" :key="index + '-' + key"
>{{ option.label }}</div>
<div
v-for="(item,key) in option.children"
:key="index+'-'+key"
class="quick-item" class="quick-item"
@click="item.func" @click="item.func"
> >
@ -40,158 +36,164 @@
</template> </template>
<script setup> <script setup>
import { reactive, ref, watch } from 'vue' import { reactive, ref, watch } from 'vue'
import { useRouter } from 'vue-router' import { useRouter } from 'vue-router'
import { useRouterStore } from '@/pinia/modules/router' import { useRouterStore } from '@/pinia/modules/router'
import { useAppStore,useUserStore } from '@/pinia' import { useAppStore, useUserStore } from '@/pinia'
defineOptions({ defineOptions({
name: 'CommandMenu', name: 'CommandMenu'
}) })
const appStore = useAppStore() const appStore = useAppStore()
const userStore = useUserStore() const userStore = useUserStore()
const router = useRouter() const router = useRouter()
const route = useRouter() const route = useRouter()
const routerStore = useRouterStore() const routerStore = useRouterStore()
const dialogVisible = ref(false) const dialogVisible = ref(false)
const searchInput = ref('') const searchInput = ref('')
const options = reactive([]) const options = reactive([])
const deepMenus = (menus) => { const deepMenus = (menus) => {
const arr = [] const arr = []
menus.forEach(menu => { menus.forEach((menu) => {
if (menu.children && menu.children.length > 0) { if (menu.children && menu.children.length > 0) {
arr.push(...deepMenus(menu.children)) arr.push(...deepMenus(menu.children))
} else { } else {
if (menu.meta.title && menu.meta.title.indexOf(searchInput.value) > -1) { if (
arr.push({ menu.meta.title &&
label: menu.meta.title, menu.meta.title.indexOf(searchInput.value) > -1
func: () => changeRouter(menu) ) {
}) arr.push({
label: menu.meta.title,
func: () => changeRouter(menu)
})
}
} }
})
return arr
}
const addQuickMenu = () => {
const option = {
label: '跳转',
children: []
} }
}) const menus = deepMenus(routerStore.asyncRouters[0].children)
return arr option.children.push(...menus)
} options.push(option)
const addQuickMenu = () => {
const option = {
label: '跳转',
children: []
} }
const menus = deepMenus(routerStore.asyncRouters[0].children)
option.children.push(...menus)
options.push(option)
}
const addQuickOption = () => { const addQuickOption = () => {
const option = { const option = {
label: '操作', label: '操作',
children: [] children: []
}
const quickArr = [
{
label: '亮色主题',
func: () => changeMode('light')
}, {
label: '暗色主题',
func: () => changeMode('dark')
}, {
label: '退出登录',
func: () => userStore.LoginOut()
} }
] const quickArr = [
option.children.push(...quickArr.filter(item => item.label.indexOf(searchInput.value) > -1)) {
options.push(option) label: '亮色主题',
} func: () => changeMode('light')
},
addQuickMenu() {
addQuickOption() label: '暗色主题',
func: () => changeMode('dark')
const open = () => { },
dialogVisible.value = true {
} label: '退出登录',
func: () => userStore.LoginOut()
const changeRouter = (e) => { }
const index = e.name ]
const query = {} option.children.push(
const params = {} ...quickArr.filter((item) => item.label.indexOf(searchInput.value) > -1)
routerStore.routeMap[index]?.parameters && )
routerStore.routeMap[index]?.parameters.forEach((item) => { options.push(option)
if (item.type === 'query') {
query[item.key] = item.value
} else {
params[item.key] = item.value
}
})
if (index === route.name) return
if (e.name.indexOf('http://') > -1 || e.name.indexOf('https://') > -1) {
window.open(e.name)
} else {
router.push({ name: index, query, params })
} }
dialogVisible.value = false
}
const changeMode = (e) => {
if (e === null) {
appStore.toggleTheme(false )
return
}
appStore.toggleTheme(true )
}
const close = () => {
dialogVisible.value = false
}
defineExpose({ open })
watch(searchInput, () => {
options.length = 0
addQuickMenu() addQuickMenu()
addQuickOption() addQuickOption()
})
const open = () => {
dialogVisible.value = true
}
const changeRouter = (e) => {
const index = e.name
const query = {}
const params = {}
routerStore.routeMap[index]?.parameters &&
routerStore.routeMap[index]?.parameters.forEach((item) => {
if (item.type === 'query') {
query[item.key] = item.value
} else {
params[item.key] = item.value
}
})
if (index === route.name) return
if (e.name.indexOf('http://') > -1 || e.name.indexOf('https://') > -1) {
window.open(e.name)
} else {
router.push({ name: index, query, params })
}
dialogVisible.value = false
}
const changeMode = (e) => {
if (e === null) {
appStore.toggleTheme(false)
return
}
appStore.toggleTheme(true)
}
const close = () => {
dialogVisible.value = false
}
defineExpose({ open })
watch(searchInput, () => {
options.length = 0
addQuickMenu()
addQuickOption()
})
</script> </script>
<style lang="scss"> <style lang="scss">
.overlay { .overlay {
border-radius: 4px; border-radius: 4px;
.el-dialog__header{ .el-dialog__header {
padding:0 !important; padding: 0 !important;
margin-right:0 !important; margin-right: 0 !important;
} }
.el-dialog__body{ .el-dialog__body {
padding: 12px !important; padding: 12px !important;
height: 50vh; height: 50vh;
overflow: auto !important; overflow: auto !important;
} }
.quick-title{ .quick-title {
margin-top: 8px; margin-top: 8px;
font-size: 12px; font-size: 12px;
font-weight: 600; font-weight: 600;
color: #666; color: #666;
} }
.quick-input{ .quick-input {
@apply bg-gray-50 dark:bg-gray-800; @apply bg-gray-50 dark:bg-gray-800;
color: #666; color: #666;
border-radius: 4px 4px 0 0; border-radius: 4px 4px 0 0;
border:none; border: none;
padding: 12px 16px; padding: 12px 16px;
box-sizing: border-box; box-sizing: border-box;
width: 100%; width: 100%;
font-size: 16px; font-size: 16px;
border-bottom: 1px solid #ddd; border-bottom: 1px solid #ddd;
} }
.quick-item{ .quick-item {
font-size: 14px; font-size: 14px;
padding: 8px; padding: 8px;
margin: 4px 0; margin: 4px 0;
&:hover{ &:hover {
@apply bg-gray-200 dark:bg-slate-500; @apply bg-gray-200 dark:bg-slate-500;
cursor: pointer; cursor: pointer;
border-radius: 4px; border-radius: 4px;
}
} }
} }
}
</style> </style>

View File

@ -1,28 +1,12 @@
<template> <template>
<span class="headerAvatar "> <span class="headerAvatar">
<template v-if="picType === 'avatar'"> <template v-if="picType === 'avatar'">
<el-avatar <el-avatar v-if="userStore.userInfo.headerImg" :size="30" :src="avatar" />
v-if="userStore.userInfo.headerImg" <el-avatar v-else :size="30" :src="noAvatar" />
:size="30"
:src="avatar"
/>
<el-avatar
v-else
:size="30"
:src="noAvatar"
/>
</template> </template>
<template v-if="picType === 'img'"> <template v-if="picType === 'img'">
<img <img v-if="userStore.userInfo.headerImg" :src="avatar" class="avatar" />
v-if="userStore.userInfo.headerImg" <img v-else :src="noAvatar" class="avatar" />
:src="avatar"
class="avatar"
>
<img
v-else
:src="noAvatar"
class="avatar"
>
</template> </template>
<template v-if="picType === 'file'"> <template v-if="picType === 'file'">
<el-image <el-image
@ -36,69 +20,71 @@
</template> </template>
<script setup> <script setup>
import noAvatarPng from '@/assets/noBody.png' import noAvatarPng from '@/assets/noBody.png'
import { useUserStore } from '@/pinia/modules/user' import { useUserStore } from '@/pinia/modules/user'
import { computed, ref } from 'vue' import { computed, ref } from 'vue'
defineOptions({ defineOptions({
name: 'CustomPic' name: 'CustomPic'
}) })
const props = defineProps({ const props = defineProps({
picType: { picType: {
type: String, type: String,
required: false, required: false,
default: 'avatar' default: 'avatar'
}, },
picSrc: { picSrc: {
type: String, type: String,
required: false, required: false,
default: '' default: ''
}, },
preview: { preview: {
type: Boolean, type: Boolean,
default: false default: false
}
})
const path = ref(import.meta.env.VITE_BASE_API + '/')
const noAvatar = ref(noAvatarPng)
const userStore = useUserStore()
const avatar = computed(() => {
if (props.picSrc === '') {
if (userStore.userInfo.headerImg !== '' && userStore.userInfo.headerImg.slice(0, 4) === 'http') {
return userStore.userInfo.headerImg
} }
return path.value + userStore.userInfo.headerImg })
} else {
if (props.picSrc !== '' && props.picSrc.slice(0, 4) === 'http') {
return props.picSrc
}
return path.value + props.picSrc
}
})
const file = computed(() => {
if (props.picSrc && props.picSrc.slice(0, 4) !== 'http') {
return path.value + props.picSrc
}
return props.picSrc
})
const previewSrcList = computed(() => props.preview ? [file.value] : [])
const path = ref(import.meta.env.VITE_BASE_API + '/')
const noAvatar = ref(noAvatarPng)
const userStore = useUserStore()
const avatar = computed(() => {
if (props.picSrc === '') {
if (
userStore.userInfo.headerImg !== '' &&
userStore.userInfo.headerImg.slice(0, 4) === 'http'
) {
return userStore.userInfo.headerImg
}
return path.value + userStore.userInfo.headerImg
} else {
if (props.picSrc !== '' && props.picSrc.slice(0, 4) === 'http') {
return props.picSrc
}
return path.value + props.picSrc
}
})
const file = computed(() => {
if (props.picSrc && props.picSrc.slice(0, 4) !== 'http') {
return path.value + props.picSrc
}
return props.picSrc
})
const previewSrcList = computed(() => (props.preview ? [file.value] : []))
</script> </script>
<style scoped> <style scoped>
.headerAvatar{ .headerAvatar {
display: flex; display: flex;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
margin-right: 8px; margin-right: 8px;
} }
.file{ .file {
width: 80px; width: 80px;
height: 80px; height: 80px;
position: relative; position: relative;
} }
</style> </style>

View File

@ -1,58 +1,59 @@
<template> <template>
<el-button <el-button type="primary" icon="download" @click="exportExcelFunc"
type="primary" >导出</el-button
icon="download" >
@click="exportExcelFunc"
>导出</el-button>
</template> </template>
<script setup> <script setup>
const props = defineProps({ const props = defineProps({
templateId: { templateId: {
type: String, type: String,
required: true required: true
}, },
condition: { condition: {
type: Object, type: Object,
default: () => ({}) default: () => ({})
}, },
limit: { limit: {
type: Number, type: Number,
default: 0 default: 0
}, },
offset: { offset: {
type: Number, type: Number,
default: 0 default: 0
}, },
order: { order: {
type: String, type: String,
default: '' default: ''
} }
}) })
import { ElMessage } from 'element-plus' import { ElMessage } from 'element-plus'
const exportExcelFunc = async() => { const exportExcelFunc = async () => {
if (props.templateId === '') { if (props.templateId === '') {
ElMessage.error('组件未设置模板ID') ElMessage.error('组件未设置模板ID')
return return
} }
const baseUrl = import.meta.env.VITE_BASE_API const baseUrl = import.meta.env.VITE_BASE_API
const paramsCopy = JSON.parse(JSON.stringify(props.condition)) const paramsCopy = JSON.parse(JSON.stringify(props.condition))
if (props.limit) { if (props.limit) {
paramsCopy.limit = props.limit paramsCopy.limit = props.limit
} }
if (props.offset) { if (props.offset) {
paramsCopy.offset = props.offset paramsCopy.offset = props.offset
} }
if (props.order) { if (props.order) {
paramsCopy.order = props.order paramsCopy.order = props.order
} }
const params = Object.entries(paramsCopy) const params = Object.entries(paramsCopy)
.map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(value)}`) .map(
.join('&') ([key, value]) =>
const url = `${baseUrl}/sysExportTemplate/exportExcel?templateID=${props.templateId}${params ? '&' + params : ''}` `${encodeURIComponent(key)}=${encodeURIComponent(value)}`
)
.join('&')
const url = `${baseUrl}/sysExportTemplate/exportExcel?templateID=${props.templateId}${params ? '&' + params : ''}`
window.open(url, '_blank') window.open(url, '_blank')
} }
</script> </script>

View File

@ -1,28 +1,26 @@
<template> <template>
<el-button <el-button type="primary" icon="download" @click="exportTemplate"
type="primary" >下载模板</el-button
icon="download" >
@click="exportTemplate"
>下载模板</el-button>
</template> </template>
<script setup> <script setup>
const props = defineProps({ const props = defineProps({
templateId: { templateId: {
type: String, type: String,
required: true required: true
} }
}) })
import { ElMessage } from 'element-plus' import { ElMessage } from 'element-plus'
const exportTemplate = async() => { const exportTemplate = async () => {
if (props.templateId === '') { if (props.templateId === '') {
ElMessage.error('组件未设置模板ID') ElMessage.error('组件未设置模板ID')
return return
}
const baseUrl = import.meta.env.VITE_BASE_API
const url = `${baseUrl}/sysExportTemplate/exportTemplate?templateID=${props.templateId}`
window.open(url, '_blank')
} }
const baseUrl = import.meta.env.VITE_BASE_API
const url = `${baseUrl}/sysExportTemplate/exportTemplate?templateID=${props.templateId}`
window.open(url, '_blank')
}
</script> </script>

View File

@ -5,38 +5,32 @@
:on-success="handleSuccess" :on-success="handleSuccess"
:multiple="false" :multiple="false"
> >
<el-button <el-button type="primary" icon="upload" class="ml-3"> 导入 </el-button>
type="primary"
icon="upload"
class="ml-3"
>
导入
</el-button>
</el-upload> </el-upload>
</template> </template>
<script setup> <script setup>
import { ElMessage } from 'element-plus' import { ElMessage } from 'element-plus'
const baseUrl = import.meta.env.VITE_BASE_API const baseUrl = import.meta.env.VITE_BASE_API
const props = defineProps({ const props = defineProps({
templateId: { templateId: {
type: String, type: String,
required: true required: true
}
})
const emit = defineEmits(['on-success'])
const url = `${baseUrl}/sysExportTemplate/importExcel?templateID=${props.templateId}`
const handleSuccess = (res) => {
if (res.code === 0) {
ElMessage.success('导入成功')
emit('on-success')
} else {
ElMessage.error(res.msg)
}
} }
})
const emit = defineEmits(['on-success'])
const url = `${baseUrl}/sysExportTemplate/importExcel?templateID=${props.templateId}`
const handleSuccess = (res) => {
if (res.code === 0) {
ElMessage.success('导入成功')
emit('on-success')
} else {
ElMessage.error(res.msg)
}
}
</script> </script>

View File

@ -1,35 +1,31 @@
<template> <template>
<vue-office-docx <vue-office-docx :src="docx" @rendered="rendered" />
:src="docx"
@rendered="rendered"
/>
</template> </template>
<script> <script>
export default { export default {
name: 'Docx' name: 'Docx'
} }
</script> </script>
<script setup> <script setup>
import { ref, watch } from 'vue' import { ref, watch } from 'vue'
// VueOfficeDocx // VueOfficeDocx
import VueOfficeDocx from '@vue-office/docx' import VueOfficeDocx from '@vue-office/docx'
// //
import '@vue-office/docx/lib/index.css' import '@vue-office/docx/lib/index.css'
const model = defineModel({ const model = defineModel({
type: String, type: String
}) })
const docx = ref(null) const docx = ref(null)
watch( watch(
() => model, () => model,
value => { docx.value = value }, (value) => {
{ immediate: true } docx.value = value
) },
const rendered = () => { { immediate: true }
} )
const rendered = () => {}
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped></style>
</style>

View File

@ -1,33 +1,36 @@
<template> <template>
<VueOfficeExcel :src="excel" @rendered="renderedHandler" @error="errorHandler" style="height: 100vh;width: 100vh"/> <VueOfficeExcel
:src="excel"
@rendered="renderedHandler"
@error="errorHandler"
style="height: 100vh; width: 100vh"
/>
</template> </template>
<script> <script>
export default { export default {
name: 'Excel' name: 'Excel'
} }
</script> </script>
<script setup> <script setup>
//VueOfficeExcel //VueOfficeExcel
import VueOfficeExcel from '@vue-office/excel' import VueOfficeExcel from '@vue-office/excel'
// //
import '@vue-office/excel/lib/index.css' import '@vue-office/excel/lib/index.css'
import {ref, watch} from 'vue' import { ref, watch } from 'vue'
const props = defineProps({ const props = defineProps({
modelValue: { modelValue: {
type: String, type: String,
default: () => "" default: () => ''
} }
}) })
const excel = ref('') const excel = ref('')
watch(() => props.modelValue, val => excel.value = val, {immediate: true}) watch(
const renderedHandler = () => { () => props.modelValue,
(val) => (excel.value = val),
} { immediate: true }
const errorHandler = () => { )
const renderedHandler = () => {}
} const errorHandler = () => {}
</script> </script>
<style> <style></style>
</style>

View File

@ -1,53 +1,49 @@
<template> <template>
<div class="border border-solid border-gray-100 h-full w-full"> <div class="border border-solid border-gray-100 h-full w-full">
<el-row> <el-row>
<div v-if="ext==='docx'"> <div v-if="ext === 'docx'">
<Docx v-model="fullFileURL" /> <Docx v-model="fullFileURL" />
</div> </div>
<div v-else-if="ext==='pdf'"> <div v-else-if="ext === 'pdf'">
<Pdf v-model="fullFileURL" /> <Pdf v-model="fullFileURL" />
</div> </div>
<div v-else-if="ext==='xlsx'"> <div v-else-if="ext === 'xlsx'">
<Excel v-model="fullFileURL" /> <Excel v-model="fullFileURL" />
</div> </div>
<div v-else-if="ext==='image'"> <div v-else-if="ext === 'image'">
<el-image <el-image :src="fullFileURL" lazy />
:src="fullFileURL"
lazy
/>
</div> </div>
</el-row> </el-row>
</div> </div>
</template> </template>
<script> <script>
export default { export default {
name: 'Office' name: 'Office'
} }
</script> </script>
<script setup> <script setup>
import { ref, watch, computed } from 'vue' import { ref, watch, computed } from 'vue'
import Docx from '@/components/office/docx.vue' import Docx from '@/components/office/docx.vue'
import Pdf from '@/components/office/pdf.vue' import Pdf from '@/components/office/pdf.vue'
import Excel from '@/components/office/excel.vue' import Excel from '@/components/office/excel.vue'
const path = ref(import.meta.env.VITE_BASE_API) const path = ref(import.meta.env.VITE_BASE_API)
const model = defineModel({ type: String}) const model = defineModel({ type: String })
const fileUrl = ref('') const fileUrl = ref('')
const ext = ref('') const ext = ref('')
watch( watch(
() => model, () => model,
val => { (val) => {
fileUrl.value = val fileUrl.value = val
const fileExt = val.split('.')[1] || '' const fileExt = val.split('.')[1] || ''
const image = ['png', 'jpg', 'jpeg', 'gif'] const image = ['png', 'jpg', 'jpeg', 'gif']
ext.value = image.includes(fileExt) ? 'image' : fileExt ext.value = image.includes(fileExt) ? 'image' : fileExt
}, },
{ immediate: true } { immediate: true }
) )
const fullFileURL = computed(() => { const fullFileURL = computed(() => {
return path.value + '/' + fileUrl.value return path.value + '/' + fileUrl.value
}) })
</script> </script>

View File

@ -1,36 +1,39 @@
<template> <template>
<vue-office-pdf <vue-office-pdf
:src="pdf"
:src="pdf" @rendered="renderedHandler"
@rendered="renderedHandler" @error="errorHandler"
@error="errorHandler"
/> />
</template> </template>
<script> <script>
export default { export default {
name: "Pdf" name: 'Pdf'
} }
</script> </script>
<script setup> <script setup>
import {ref, watch} from "vue" import { ref, watch } from 'vue'
//VueOfficeDocx //VueOfficeDocx
import VueOfficePdf from "@vue-office/pdf"; import VueOfficePdf from '@vue-office/pdf'
// //
import '@vue-office/docx/lib/index.css' import '@vue-office/docx/lib/index.css'
console.log("pdf===>") console.log('pdf===>')
const props = defineProps({ const props = defineProps({
modelValue: { modelValue: {
type: String, type: String,
default: () => "" default: () => ''
}
})
const pdf = ref(null)
watch(
() => props.modelValue,
(val) => (pdf.value = val),
{ immediate: true }
)
const renderedHandler = () => {
console.log('pdf 加载成功')
} }
}) const errorHandler = () => {
const pdf = ref(null) console.log('pdf 错误')
watch(() => props.modelValue, val => pdf.value = val, {immediate: true}) }
const renderedHandler = () => { </script>
console.log("pdf 加载成功")
}
const errorHandler = () => {
console.log("pdf 错误")
}
</script>

View File

@ -8,7 +8,7 @@
<Editor <Editor
v-model="valueHtml" v-model="valueHtml"
class="overflow-y-hidden mt-0.5" class="overflow-y-hidden mt-0.5"
style="height: 18rem;" style="height: 18rem"
:default-config="editorConfig" :default-config="editorConfig"
mode="default" mode="default"
@onCreated="handleCreated" @onCreated="handleCreated"
@ -18,70 +18,69 @@
</template> </template>
<script setup> <script setup>
import '@wangeditor/editor/dist/css/style.css' // css
import '@wangeditor/editor/dist/css/style.css' // css const basePath = import.meta.env.VITE_BASE_API
const basePath = import.meta.env.VITE_BASE_API import { onBeforeUnmount, ref, shallowRef, watch } from 'vue'
import { Editor, Toolbar } from '@wangeditor/editor-for-vue'
import { onBeforeUnmount, ref, shallowRef, watch } from 'vue' import { ElMessage } from 'element-plus'
import { Editor, Toolbar } from '@wangeditor/editor-for-vue' import { getUrl } from '@/utils/image'
import { ElMessage } from 'element-plus' const emits = defineEmits(['change', 'update:modelValue'])
import { getUrl } from '@/utils/image'
const emits = defineEmits(['change', 'update:modelValue']) const change = (editor) => {
emits('change', editor)
const change = (editor) => { emits('update:modelValue', valueHtml.value)
emits('change', editor)
emits('update:modelValue', valueHtml.value)
}
const props = defineProps({
modelValue: {
type: String,
default: ''
} }
})
const editorRef = shallowRef() const props = defineProps({
const valueHtml = ref('') modelValue: {
type: String,
const toolbarConfig = {} default: ''
const editorConfig = { }
placeholder: '请输入内容...', })
MENU_CONF: {}
} const editorRef = shallowRef()
editorConfig.MENU_CONF['uploadImage'] = { const valueHtml = ref('')
fieldName: 'file',
server: basePath + '/fileUploadAndDownload/upload?noSave=1', const toolbarConfig = {}
customInsert(res, insertFn) { const editorConfig = {
if (res.code === 0) { placeholder: '请输入内容...',
const urlPath = getUrl(res.data.file.url) MENU_CONF: {}
insertFn(urlPath, res.data.file.name) }
return editorConfig.MENU_CONF['uploadImage'] = {
fieldName: 'file',
server: basePath + '/fileUploadAndDownload/upload?noSave=1',
customInsert(res, insertFn) {
if (res.code === 0) {
const urlPath = getUrl(res.data.file.url)
insertFn(urlPath, res.data.file.name)
return
}
ElMessage.error(res.msg)
} }
ElMessage.error(res.msg)
} }
}
// //
onBeforeUnmount(() => { onBeforeUnmount(() => {
const editor = editorRef.value const editor = editorRef.value
if (editor == null) return if (editor == null) return
editor.destroy() editor.destroy()
}) })
const handleCreated = (editor) => { const handleCreated = (editor) => {
editorRef.value = editor editorRef.value = editor
valueHtml.value = props.modelValue valueHtml.value = props.modelValue
} }
watch(() => props.modelValue, () => {
valueHtml.value = props.modelValue
})
watch(
() => props.modelValue,
() => {
valueHtml.value = props.modelValue
}
)
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss"></style>
</style>

View File

@ -11,48 +11,48 @@
</div> </div>
</template> </template>
<script setup> <script setup>
import '@wangeditor/editor/dist/css/style.css' // css
import '@wangeditor/editor/dist/css/style.css' // css import { onBeforeUnmount, ref, shallowRef, watch } from 'vue'
import { Editor } from '@wangeditor/editor-for-vue'
import { onBeforeUnmount, ref, shallowRef, watch } from 'vue' const emits = defineEmits(['change', 'update:modelValue'])
import { Editor } from '@wangeditor/editor-for-vue' const editorConfig = ref({
readOnly: true
const emits = defineEmits(['change', 'update:modelValue']) })
const editorConfig = ref({ const change = (editor) => {
readOnly: true emits('change', editor)
}) emits('update:modelValue', valueHtml.value)
const change = (editor) => {
emits('change', editor)
emits('update:modelValue', valueHtml.value)
}
const props = defineProps({
modelValue: {
type: String,
default: ''
} }
})
const editorRef = shallowRef() const props = defineProps({
const valueHtml = ref('') modelValue: {
type: String,
default: ''
}
})
// const editorRef = shallowRef()
onBeforeUnmount(() => { const valueHtml = ref('')
const editor = editorRef.value
if (editor == null) return
editor.destroy()
})
const handleCreated = (editor) => { //
editorRef.value = editor onBeforeUnmount(() => {
valueHtml.value = props.modelValue const editor = editorRef.value
} if (editor == null) return
editor.destroy()
})
watch(() => props.modelValue, () => { const handleCreated = (editor) => {
valueHtml.value = props.modelValue editorRef.value = editor
}) valueHtml.value = props.modelValue
}
watch(
() => props.modelValue,
() => {
valueHtml.value = props.modelValue
}
)
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss"></style>
</style>

View File

@ -12,76 +12,70 @@
:accept="accept" :accept="accept"
class="upload-btn" class="upload-btn"
> >
<el-button type="primary"> <el-button type="primary"> 上传文件 </el-button>
上传文件
</el-button>
</el-upload> </el-upload>
</div> </div>
</template> </template>
<script setup> <script setup>
import { ref } from 'vue'
import { ElMessage } from 'element-plus'
import { getBaseUrl } from '@/utils/format'
import { ref } from 'vue' defineOptions({
import { ElMessage } from 'element-plus' name: 'UploadCommon'
import { getBaseUrl } from '@/utils/format' })
defineOptions({ defineProps({
name: 'UploadCommon', limit: {
}) type: Number,
default: 3
},
accept: {
type: String,
default: ''
}
})
defineProps({ const fullscreenLoading = ref(false)
limit: {
type: Number,
default: 3
},
accept: {
type: String,
default: ''
},
})
const model = defineModel({ type: Array })
const fullscreenLoading = ref(false) const fileList = ref(model.value)
const model = defineModel({ type: Array }) const emits = defineEmits(['on-success', 'on-error'])
const fileList = ref(model.value) const uploadSuccess = (res) => {
const { data, code } = res
if (code !== 0) {
ElMessage({
type: 'error',
message: '上传失败' + res.msg
})
fileList.value.pop()
return
}
model.value.push({
name: data.file.name,
url: data.file.url
})
emits('on-success', res)
}
const emits = defineEmits(['on-success', 'on-error']) const uploadRemove = (file) => {
const index = model.value.indexOf(file)
if (index > -1) {
model.value.splice(index, 1)
fileList.value = model.value
}
}
const uploadSuccess = (res) => { const uploadError = (err) => {
const { data,code } = res
if(code !== 0){
ElMessage({ ElMessage({
type: 'error', type: 'error',
message: '上传失败'+res.msg message: '上传失败'
}) })
fileList.value.pop() fullscreenLoading.value = false
return emits('on-error', err)
} }
model.value.push({
name: data.file.name,
url: data.file.url
})
emits('on-success', res)
}
const uploadRemove = (file) => {
const index = model.value.indexOf(file)
if (index > -1) {
model.value.splice(index, 1)
fileList.value = model.value
}
}
const uploadError = (err) => {
ElMessage({
type: 'error',
message: '上传失败'
})
fullscreenLoading.value = false
emits('on-error',err)
}
</script> </script>

View File

@ -15,10 +15,15 @@
muted muted
preload="metadata" preload="metadata"
> >
<source :src="getUrl(model) + '#t=1'"> <source :src="getUrl(model) + '#t=1'" />
</video> </video>
<img v-if="model&&!isVideoExt(model)" class="w-full h-full" :src="getUrl(model)" alt="图片"> <img
v-if="model && !isVideoExt(model)"
class="w-full h-full"
:src="getUrl(model)"
alt="图片"
/>
<div <div
v-if="model" v-if="model"
class="left-0 top-0 hidden text-gray-600 group-hover:bg-gray-600 group-hover:bg-opacity-30 w-full h-full group-hover:flex justify-center items-center absolute z-10" class="left-0 top-0 hidden text-gray-600 group-hover:bg-gray-600 group-hover:bg-opacity-30 w-full h-full group-hover:flex justify-center items-center absolute z-10"
@ -42,24 +47,23 @@
</div> </div>
</template> </template>
<script setup> <script setup>
import { getUrl, isVideoExt } from '@/utils/image' import { getUrl, isVideoExt } from '@/utils/image'
import { Delete, Plus } from '@element-plus/icons-vue' import { Delete, Plus } from '@element-plus/icons-vue'
defineProps({ defineProps({
model: { model: {
default: '', default: '',
type: String type: String
}
})
const emits = defineEmits(['chooseItem', 'deleteItem'])
const chooseItem = () => {
emits('chooseItem')
} }
})
const emits = defineEmits(['chooseItem', 'deleteItem'])
const chooseItem = () => {
emits('chooseItem')
}
const deleteItem = () => {
emits('deleteItem')
}
const deleteItem = () => {
emits('deleteItem')
}
</script> </script>

View File

@ -1,96 +1,80 @@
<template> <template>
<div> <div>
<selectComponent <selectComponent
v-if="!props.multiple" v-if="!props.multiple"
:model="model" :model="model"
@chooseItem="openChooseImg" @chooseItem="openChooseImg"
@deleteItem="openChooseImg" @deleteItem="openChooseImg"
/> />
<div <div v-else class="w-full gap-4 flex flex-wrap">
v-else
class="w-full gap-4 flex flex-wrap"
>
<selectComponent <selectComponent
v-for="(item, index) in model" v-for="(item, index) in model"
:key="index" :key="index"
:model="item" :model="item"
@chooseItem="openChooseImg" @chooseItem="openChooseImg"
@deleteItem="deleteImg(index)" @deleteItem="deleteImg(index)"
/> />
<selectComponent <selectComponent
v-if="model?.length < props.maxUpdateCount || props.maxUpdateCount === 0" v-if="
@chooseItem="openChooseImg" model?.length < props.maxUpdateCount || props.maxUpdateCount === 0
@deleteItem="openChooseImg" "
@chooseItem="openChooseImg"
@deleteItem="openChooseImg"
/> />
</div> </div>
<el-drawer <el-drawer v-model="drawer" title="媒体库" size="650px">
v-model="drawer" <warning-bar title="点击“文件名/备注”可以编辑文件名或者备注内容。" />
title="媒体库"
size="650px"
>
<warning-bar
title="点击“文件名/备注”可以编辑文件名或者备注内容。"
/>
<div class="gva-btn-list gap-2"> <div class="gva-btn-list gap-2">
<upload-common <upload-common :image-common="imageCommon" @on-success="getImageList" />
:image-common="imageCommon"
@on-success="getImageList"
/>
<upload-image <upload-image
:image-url="imageUrl" :image-url="imageUrl"
:file-size="512" :file-size="512"
:max-w-h="1080" :max-w-h="1080"
@on-success="getImageList" @on-success="getImageList"
/> />
<el-input <el-input
v-model="search.keyword" v-model="search.keyword"
class="keyword" class="keyword"
placeholder="请输入文件名或备注" placeholder="请输入文件名或备注"
/> />
<el-button <el-button type="primary" icon="search" @click="getImageList">
type="primary"
icon="search"
@click="getImageList"
>
查询 查询
</el-button> </el-button>
</div> </div>
<div class="flex flex-wrap gap-4"> <div class="flex flex-wrap gap-4">
<div <div v-for="(item, key) in picList" :key="key" class="w-40">
v-for="(item,key) in picList" <div
:key="key" class="w-40 h-40 border rounded overflow-hidden border-dashed border-gray-300 cursor-pointer relative group"
class="w-40" >
>
<div class="w-40 h-40 border rounded overflow-hidden border-dashed border-gray-300 cursor-pointer relative group">
<el-image <el-image
:key="key" :key="key"
:src="getUrl(item.url)" :src="getUrl(item.url)"
fit="cover" fit="cover"
class="w-full h-full relative" class="w-full h-full relative"
@click="chooseImg(item.url)" @click="chooseImg(item.url)"
> >
<template #error> <template #error>
<el-icon <el-icon
v-if="isVideoExt(item.url || '')" v-if="isVideoExt(item.url || '')"
:size="32" :size="32"
class="absolute top-[calc(50%-16px)] left-[calc(50%-16px)]" class="absolute top-[calc(50%-16px)] left-[calc(50%-16px)]"
> >
<VideoPlay /> <VideoPlay />
</el-icon> </el-icon>
<video <video
v-if="isVideoExt(item.url || '')" v-if="isVideoExt(item.url || '')"
class="w-full h-full object-cover" class="w-full h-full object-cover"
muted muted
preload="metadata" preload="metadata"
@click="chooseImg(item.url)" @click="chooseImg(item.url)"
> >
<source :src="getUrl(item.url) + '#t=1'"> <source :src="getUrl(item.url) + '#t=1'" />
您的浏览器不支持视频播放 您的浏览器不支持视频播放
</video> </video>
<div <div
v-else v-else
class="w-full h-full object-cover flex items-center justify-center" class="w-full h-full object-cover flex items-center justify-center"
> >
<el-icon :size="32"> <el-icon :size="32">
<icon-picture /> <icon-picture />
@ -98,179 +82,206 @@
</div> </div>
</template> </template>
</el-image> </el-image>
<div class="absolute -right-1 top-1 w-8 h-8 group-hover:inline-block hidden" @click="deleteCheck(item)"> <div
class="absolute -right-1 top-1 w-8 h-8 group-hover:inline-block hidden"
@click="deleteCheck(item)"
>
<el-icon :size="16"><CircleClose /></el-icon> <el-icon :size="16"><CircleClose /></el-icon>
</div> </div>
</div> </div>
<div <div
class="overflow-hidden text-nowrap overflow-ellipsis text-center w-full" class="overflow-hidden text-nowrap overflow-ellipsis text-center w-full"
@click="editFileNameFunc(item)" @click="editFileNameFunc(item)"
> >
{{ item.name }} {{ item.name }}
</div> </div>
</div> </div>
</div> </div>
<el-pagination <el-pagination
:current-page="page" :current-page="page"
:page-size="pageSize" :page-size="pageSize"
:total="total" :total="total"
:style="{'justify-content':'center'}" :style="{ 'justify-content': 'center' }"
layout="total, prev, pager, next, jumper" layout="total, prev, pager, next, jumper"
@current-change="handleCurrentChange" @current-change="handleCurrentChange"
@size-change="handleSizeChange" @size-change="handleSizeChange"
/> />
</el-drawer> </el-drawer>
</div> </div>
</template> </template>
<script setup> <script setup>
import { getUrl, isVideoExt } from '@/utils/image' import { getUrl, isVideoExt } from '@/utils/image'
import { ref } from 'vue' import { ref } from 'vue'
import { getFileList, editFileName, deleteFile } from '@/api/fileUploadAndDownload' import {
import UploadImage from '@/components/upload/image.vue' getFileList,
import UploadCommon from '@/components/upload/common.vue' editFileName,
import WarningBar from '@/components/warningBar/warningBar.vue' deleteFile
import { ElMessage, ElMessageBox } from 'element-plus' } from '@/api/fileUploadAndDownload'
import { Picture as IconPicture } from '@element-plus/icons-vue' import UploadImage from '@/components/upload/image.vue'
import selectComponent from '@/components/selectImage/selectComponent.vue' import UploadCommon from '@/components/upload/common.vue'
import WarningBar from '@/components/warningBar/warningBar.vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { Picture as IconPicture } from '@element-plus/icons-vue'
import selectComponent from '@/components/selectImage/selectComponent.vue'
const imageUrl = ref('') const imageUrl = ref('')
const imageCommon = ref('') const imageCommon = ref('')
const search = ref({}) const search = ref({})
const page = ref(1) const page = ref(1)
const total = ref(0) const total = ref(0)
const pageSize = ref(20) const pageSize = ref(20)
const model = defineModel({ type: [String, Array] }) const model = defineModel({ type: [String, Array] })
const props = defineProps({ const props = defineProps({
multiple: { multiple: {
type: Boolean, type: Boolean,
default: false default: false
}, },
fileType: { fileType: {
type: String, type: String,
default: '' default: ''
}, },
maxUpdateCount: { maxUpdateCount: {
type: Number, type: Number,
default: 0 default: 0
}
})
const deleteImg = (index) => {
model.value.splice(index, 1)
}
const handleSizeChange = (val) => {
pageSize.value = val
getImageList()
}
const handleCurrentChange = (val) => {
page.value = val
getImageList()
}
const editFileNameFunc = async(row) => {
ElMessageBox.prompt('请输入文件名或者备注', '编辑', {
confirmButtonText: '确定',
cancelButtonText: '取消',
inputPattern: /\S/,
inputErrorMessage: '不能为空',
inputValue: row.name
}).then(async({ value }) => {
row.name = value
const res = await editFileName(row)
if (res.code === 0) {
ElMessage({
type: 'success',
message: '编辑成功!',
})
getImageList()
} }
}).catch(() => {
ElMessage({
type: 'info',
message: '取消修改'
})
}) })
}
const drawer = ref(false) const deleteImg = (index) => {
const picList = ref([]) model.value.splice(index, 1)
}
const imageTypeList = ['png', 'jpg', 'jpeg', 'gif', 'bmp', 'webp'] const handleSizeChange = (val) => {
const videoTyteList = ['mp4', 'avi', 'rmvb', 'rm', 'asf', 'divx', 'mpg', 'mpeg', 'mpe', 'wmv', 'mkv', 'vob'] pageSize.value = val
getImageList()
}
const listObj = { const handleCurrentChange = (val) => {
image: imageTypeList, page.value = val
video: videoTyteList getImageList()
} }
const chooseImg = (url) => { const editFileNameFunc = async (row) => {
if (props.fileType) { ElMessageBox.prompt('请输入文件名或者备注', '编辑', {
const typeSuccess = listObj[props.fileType].some(item => { confirmButtonText: '确定',
if (url.includes(item)) { cancelButtonText: '取消',
return true inputPattern: /\S/,
} inputErrorMessage: '不能为空',
inputValue: row.name
}) })
if (!typeSuccess) { .then(async ({ value }) => {
ElMessage({ row.name = value
type: 'error', const res = await editFileName(row)
message: '当前类型不支持使用' if (res.code === 0) {
ElMessage({
type: 'success',
message: '编辑成功!'
})
getImageList()
}
}) })
.catch(() => {
ElMessage({
type: 'info',
message: '取消修改'
})
})
}
const drawer = ref(false)
const picList = ref([])
const imageTypeList = ['png', 'jpg', 'jpeg', 'gif', 'bmp', 'webp']
const videoTyteList = [
'mp4',
'avi',
'rmvb',
'rm',
'asf',
'divx',
'mpg',
'mpeg',
'mpe',
'wmv',
'mkv',
'vob'
]
const listObj = {
image: imageTypeList,
video: videoTyteList
}
const chooseImg = (url) => {
if (props.fileType) {
const typeSuccess = listObj[props.fileType].some((item) => {
if (url.includes(item)) {
return true
}
})
if (!typeSuccess) {
ElMessage({
type: 'error',
message: '当前类型不支持使用'
})
return
}
}
if (props.multiple) {
model.value.push(url)
} else {
model.value = url
}
drawer.value = false
}
const openChooseImg = async () => {
if (model.value && !props.multiple) {
model.value = ''
return return
} }
await getImageList()
drawer.value = true
} }
if (props.multiple) {
model.value.push(url)
} else {
model.value = url
}
drawer.value = false
}
const openChooseImg = async() => { const getImageList = async () => {
if (model.value && !props.multiple) { const res = await getFileList({
model.value = '' page: page.value,
return pageSize: pageSize.value,
} ...search.value
await getImageList()
drawer.value = true
}
const getImageList = async() => {
const res = await getFileList({ page: page.value, pageSize: pageSize.value, ...search.value })
if (res.code === 0) {
picList.value = res.data.list
total.value = res.data.total
page.value = res.data.page
pageSize.value = res.data.pageSize
}
}
const deleteCheck = (item) => {
ElMessageBox.confirm('是否删除该文件', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(async() => {
const res = await deleteFile(item)
if (res.code === 0) {
ElMessage({
type: 'success',
message: '删除成功!',
})
getImageList()
}
}).catch(() => {
ElMessage({
type: 'info',
message: '已取消删除'
}) })
}) if (res.code === 0) {
} picList.value = res.data.list
total.value = res.data.total
page.value = res.data.page
pageSize.value = res.data.pageSize
}
}
const deleteCheck = (item) => {
ElMessageBox.confirm('是否删除该文件', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
})
.then(async () => {
const res = await deleteFile(item)
if (res.code === 0) {
ElMessage({
type: 'success',
message: '删除成功!'
})
getImageList()
}
})
.catch(() => {
ElMessage({
type: 'info',
message: '已取消删除'
})
})
}
</script> </script>

View File

@ -1,39 +1,32 @@
<template> <template>
<svg <svg :class="svgClass" v-bind="$attrs" :color="color">
:class="svgClass" <use :xlink:href="'#' + name" rel="external nofollow" />
v-bind="$attrs"
:color="color"
>
<use
:xlink:href="'#'+name"
rel="external nofollow"
/>
</svg> </svg>
</template> </template>
<script setup> <script setup>
import { computed } from 'vue' import { computed } from 'vue'
const props = defineProps({ const props = defineProps({
name: { name: {
type: String, type: String,
required: true required: true
}, },
color: { color: {
type: String, type: String,
default: 'currentColor' default: 'currentColor'
} }
}) })
const svgClass = computed(() => { const svgClass = computed(() => {
if (props.name) { if (props.name) {
return `svg-icon ${props.name}` return `svg-icon ${props.name}`
} }
return 'svg-icon' return 'svg-icon'
}) })
</script> </script>
<style scoped> <style scoped>
.svg-icon { .svg-icon {
@apply w-4 h-4; @apply w-4 h-4;
fill: currentColor; fill: currentColor;
vertical-align: middle; vertical-align: middle;
} }
</style> </style>

View File

@ -15,62 +15,61 @@
</template> </template>
<script setup> <script setup>
import { ref } from 'vue'
import { ElMessage } from 'element-plus'
import { isVideoMime, isImageMime } from '@/utils/image'
import { getBaseUrl } from '@/utils/format'
import { ref } from 'vue' defineOptions({
import { ElMessage } from 'element-plus' name: 'UploadCommon'
import { isVideoMime, isImageMime } from '@/utils/image'
import { getBaseUrl } from '@/utils/format'
defineOptions({
name: 'UploadCommon',
})
const emit = defineEmits(['on-success'])
const fullscreenLoading = ref(false)
const checkFile = (file) => {
fullscreenLoading.value = true
const isLt500K = file.size / 1024 / 1024 < 0.5 // 500K, @todo
const isLt5M = file.size / 1024 / 1024 < 5 // 5MB, @todo
const isVideo = isVideoMime(file.type)
const isImage = isImageMime(file.type)
let pass = true
if (!isVideo && !isImage) {
ElMessage.error('上传图片只能是 jpg,png,svg,webp 格式, 上传视频只能是 mp4,webm 格式!')
fullscreenLoading.value = false
pass = false
}
if (!isLt5M && isVideo) {
ElMessage.error('上传视频大小不能超过 5MB')
fullscreenLoading.value = false
pass = false
}
if (!isLt500K && isImage) {
ElMessage.error('未压缩的上传图片大小不能超过 500KB请使用压缩上传')
fullscreenLoading.value = false
pass = false
}
console.log('upload file check result: ', pass)
return pass
}
const uploadSuccess = (res) => {
const { data } = res
if (data.file) {
emit('on-success', data.file.url)
}
}
const uploadError = () => {
ElMessage({
type: 'error',
message: '上传失败'
}) })
fullscreenLoading.value = false
}
const emit = defineEmits(['on-success'])
const fullscreenLoading = ref(false)
const checkFile = (file) => {
fullscreenLoading.value = true
const isLt500K = file.size / 1024 / 1024 < 0.5 // 500K, @todo
const isLt5M = file.size / 1024 / 1024 < 5 // 5MB, @todo
const isVideo = isVideoMime(file.type)
const isImage = isImageMime(file.type)
let pass = true
if (!isVideo && !isImage) {
ElMessage.error(
'上传图片只能是 jpg,png,svg,webp 格式, 上传视频只能是 mp4,webm 格式!'
)
fullscreenLoading.value = false
pass = false
}
if (!isLt5M && isVideo) {
ElMessage.error('上传视频大小不能超过 5MB')
fullscreenLoading.value = false
pass = false
}
if (!isLt500K && isImage) {
ElMessage.error('未压缩的上传图片大小不能超过 500KB请使用压缩上传')
fullscreenLoading.value = false
pass = false
}
console.log('upload file check result: ', pass)
return pass
}
const uploadSuccess = (res) => {
const { data } = res
if (data.file) {
emit('on-success', data.file.url)
}
}
const uploadError = () => {
ElMessage({
type: 'error',
message: '上传失败'
})
fullscreenLoading.value = false
}
</script> </script>

View File

@ -1,4 +1,3 @@
<template> <template>
<div> <div>
<el-upload <el-upload
@ -14,79 +13,78 @@
</template> </template>
<script setup> <script setup>
import ImageCompress from '@/utils/image' import ImageCompress from '@/utils/image'
import { ElMessage } from 'element-plus' import { ElMessage } from 'element-plus'
import { getBaseUrl } from '@/utils/format' import { getBaseUrl } from '@/utils/format'
defineOptions({ defineOptions({
name: 'UploadImage', name: 'UploadImage'
}) })
const emit = defineEmits(['on-success']) const emit = defineEmits(['on-success'])
const props = defineProps({ const props = defineProps({
imageUrl: { imageUrl: {
type: String, type: String,
default: '' default: ''
}, },
fileSize: { fileSize: {
type: Number, type: Number,
default: 2048 // 2M default: 2048 // 2M
}, },
maxWH: { maxWH: {
type: Number, type: Number,
default: 1920 // default: 1920 //
} }
}) })
const beforeImageUpload = (file) => { const beforeImageUpload = (file) => {
const isJPG = file.type === 'image/jpeg' const isJPG = file.type === 'image/jpeg'
const isPng = file.type === 'image/png' const isPng = file.type === 'image/png'
if (!isJPG && !isPng) { if (!isJPG && !isPng) {
ElMessage.error('上传头像图片只能是 jpg或png 格式!') ElMessage.error('上传头像图片只能是 jpg或png 格式!')
return false return false
}
const isRightSize = file.size / 1024 < props.fileSize
if (!isRightSize) {
//
const compress = new ImageCompress(file, props.fileSize, props.maxWH)
return compress.compress()
}
return isRightSize
} }
const isRightSize = file.size / 1024 < props.fileSize const handleImageSuccess = (res) => {
if (!isRightSize) { const { data } = res
// if (data.file) {
const compress = new ImageCompress(file, props.fileSize, props.maxWH) emit('on-success', data.file.url)
return compress.compress() }
} }
return isRightSize
}
const handleImageSuccess = (res) => {
const { data } = res
if (data.file) {
emit('on-success', data.file.url)
}
}
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.image-uploader { .image-uploader {
border: 1px dashed #d9d9d9; border: 1px dashed #d9d9d9;
width: 180px; width: 180px;
border-radius: 6px; border-radius: 6px;
cursor: pointer; cursor: pointer;
position: relative; position: relative;
overflow: hidden; overflow: hidden;
} }
.image-uploader { .image-uploader {
border-color: #409eff; border-color: #409eff;
} }
.image-uploader-icon { .image-uploader-icon {
font-size: 28px; font-size: 28px;
color: #8c939d; color: #8c939d;
width: 178px; width: 178px;
height: 178px; height: 178px;
line-height: 178px; line-height: 178px;
text-align: center; text-align: center;
} }
.image { .image {
width: 178px; width: 178px;
height: 178px; height: 178px;
display: block; display: block;
} }
</style> </style>

View File

@ -1,7 +1,7 @@
<template> <template>
<div <div
class="px-1.5 py-2 flex items-center rounded-sm mt-2 bg-amber-50 gap-2 mb-3 text-amber-500 dark:bg-amber-700 dark:text-gray-200" class="px-1.5 py-2 flex items-center rounded-sm mt-2 bg-amber-50 gap-2 mb-3 text-amber-500 dark:bg-amber-700 dark:text-gray-200"
:class="href&&'cursor-pointer'" :class="href && 'cursor-pointer'"
@click="open" @click="open"
> >
<el-icon class="text-xl"> <el-icon class="text-xl">
@ -13,21 +13,21 @@
</div> </div>
</template> </template>
<script setup> <script setup>
import { WarningFilled } from '@element-plus/icons-vue' import { WarningFilled } from '@element-plus/icons-vue'
const prop = defineProps({ const prop = defineProps({
title: { title: {
type: String, type: String,
default: '' default: ''
}, },
href: { href: {
type: String, type: String,
default: '' default: ''
} }
}) })
const open = () => { const open = () => {
if (prop.href) { if (prop.href) {
window.open(prop.href) window.open(prop.href)
}
} }
}
</script> </script>

View File

@ -1,30 +1,52 @@
/** /**
* 网站配置文件 * 网站配置文件
*/ */
const greenText = (text) => `\x1b[32m${text}\x1b[0m`; const greenText = (text) => `\x1b[32m${text}\x1b[0m`
const config = { const config = {
appName: 'Gin-Vue-Admin', appName: 'Gin-Vue-Admin',
appLogo: 'logo.png', appLogo: 'logo.png',
showViteLogo: true, showViteLogo: true,
logs: [], logs: []
} }
export const viteLogo = (env) => { export const viteLogo = (env) => {
if (config.showViteLogo) { if (config.showViteLogo) {
console.log(greenText(`> 欢迎使用Gin-Vue-Admin开源地址https://github.com/flipped-aurora/gin-vue-admin`)); console.log(
console.log(greenText(`> 当前版本:v2.7.7`)); greenText(
console.log(greenText(`> 加群方式:微信shouzi_1994 QQ群470239250`)); `> 欢迎使用Gin-Vue-Admin开源地址https://github.com/flipped-aurora/gin-vue-admin`
console.log(greenText(`> 项目地址https://github.com/flipped-aurora/gin-vue-admin`)); )
console.log(greenText(`> 插件市场:https://plugin.gin-vue-admin.com`)); )
console.log(greenText(`> GVA讨论社区:https://support.qq.com/products/371961`)); console.log(greenText(`> 当前版本:v2.7.7`))
console.log(greenText(`> 默认自动化文档地址:http://127.0.0.1:${env.VITE_SERVER_PORT}/swagger/index.html`)); console.log(greenText(`> 加群方式:微信shouzi_1994 QQ群470239250`))
console.log(greenText(`> 默认前端文件运行地址:http://127.0.0.1:${env.VITE_CLI_PORT}`)); console.log(
console.log(greenText(`--------------------------------------版权声明--------------------------------------`)); greenText(`> 项目地址https://github.com/flipped-aurora/gin-vue-admin`)
console.log(greenText(`** 版权所有方flipped-aurora开源团队 **`)); )
console.log(greenText(`** 版权持有公司:北京翻转极光科技有限责任公司 **`)); console.log(greenText(`> 插件市场:https://plugin.gin-vue-admin.com`))
console.log(greenText(`** 剔除授权标识需购买商用授权https://gin-vue-admin.com/empower/index.html **`)); console.log(
console.log('\n'); greenText(`> GVA讨论社区:https://support.qq.com/products/371961`)
)
console.log(
greenText(
`> 默认自动化文档地址:http://127.0.0.1:${env.VITE_SERVER_PORT}/swagger/index.html`
)
)
console.log(
greenText(`> 默认前端文件运行地址:http://127.0.0.1:${env.VITE_CLI_PORT}`)
)
console.log(
greenText(
`--------------------------------------版权声明--------------------------------------`
)
)
console.log(greenText(`** 版权所有方flipped-aurora开源团队 **`))
console.log(greenText(`** 版权持有公司:北京翻转极光科技有限责任公司 **`))
console.log(
greenText(
`** 剔除授权标识需购买商用授权https://gin-vue-admin.com/empower/index.html **`
)
)
console.log('\n')
} }
} }

View File

@ -10,21 +10,26 @@ const createIconComponent = (name) => ({
name: 'SvgIcon', name: 'SvgIcon',
render() { render() {
return h(svgIcon, { return h(svgIcon, {
name: name, name: name
}) })
}, }
}) })
const registerIcons = async(app) => { const registerIcons = async (app) => {
const iconModules = import.meta.glob('@/assets/icons/**/*.svg') // 系统目录 svg 图标 const iconModules = import.meta.glob('@/assets/icons/**/*.svg') // 系统目录 svg 图标
const pluginIconModules = import.meta.glob('@/plugin/**/assets/icons/**/*.svg') // 插件目录 svg 图标 const pluginIconModules = import.meta.glob(
const mergedIconModules = Object.assign({}, iconModules, pluginIconModules); // 合并所有 svg 图标 '@/plugin/**/assets/icons/**/*.svg'
) // 插件目录 svg 图标
const mergedIconModules = Object.assign({}, iconModules, pluginIconModules) // 合并所有 svg 图标
for (const path in mergedIconModules) { for (const path in mergedIconModules) {
let pluginName = "" let pluginName = ''
if (path.startsWith("/src/plugin/")) { if (path.startsWith('/src/plugin/')) {
pluginName = `${path.split('/')[3]}-` pluginName = `${path.split('/')[3]}-`
} }
const iconName = path.split('/').pop().replace(/\.svg$/, '') const iconName = path
.split('/')
.pop()
.replace(/\.svg$/, '')
// 如果iconName带空格则不加入到图标库中并且提示名称不合法 // 如果iconName带空格则不加入到图标库中并且提示名称不合法
if (iconName.indexOf(' ') !== -1) { if (iconName.indexOf(' ') !== -1) {
console.error(`icon ${iconName}.svg includes whitespace in ${path}`) console.error(`icon ${iconName}.svg includes whitespace in ${path}`)
@ -32,11 +37,12 @@ const registerIcons = async(app) => {
} }
const key = `${pluginName}${iconName}` const key = `${pluginName}${iconName}`
// 开发模式下列出所有 svg 图标,方便开发者直接查找复制使用 // 开发模式下列出所有 svg 图标,方便开发者直接查找复制使用
import.meta.env.MODE == 'development' && console.log(`svg-icon-component: <${key} />`) import.meta.env.MODE == 'development' &&
console.log(`svg-icon-component: <${key} />`)
const iconComponent = createIconComponent(key) const iconComponent = createIconComponent(key)
config.logs.push({ config.logs.push({
'key': key, key: key,
'label': key, label: key
}) })
app.component(key, iconComponent) app.component(key, iconComponent)
} }

View File

@ -5,7 +5,7 @@ export default {
const userStore = useUserStore() const userStore = useUserStore()
app.directive('auth', { app.directive('auth', {
// 当被绑定的元素插入到 DOM 中时…… // 当被绑定的元素插入到 DOM 中时……
mounted: function(el, binding) { mounted: function (el, binding) {
const userInfo = userStore.userInfo const userInfo = userStore.userInfo
let type = '' let type = ''
switch (Object.prototype.toString.call(binding.value)) { switch (Object.prototype.toString.call(binding.value)) {
@ -27,7 +27,7 @@ export default {
return return
} }
const waitUse = binding.value.toString().split(',') const waitUse = binding.value.toString().split(',')
let flag = waitUse.some(item => Number(item) === userInfo.authorityId) let flag = waitUse.some((item) => Number(item) === userInfo.authorityId)
if (binding.modifiers.not) { if (binding.modifiers.not) {
flag = !flag flag = !flag
} }
@ -38,4 +38,3 @@ export default {
}) })
} }
} }

View File

@ -1,19 +1,18 @@
// 本组件参考 arco-pro 的实现 // 本组件参考 arco-pro 的实现
// https://github.com/arco-design/arco-design-pro-vue/blob/main/arco-design-pro-vite/src/hooks/chart-option.ts // https://github.com/arco-design/arco-design-pro-vue/blob/main/arco-design-pro-vite/src/hooks/chart-option.ts
import { computed } from 'vue'; import { computed } from 'vue'
import { useAppStore } from '@/pinia'; import { useAppStore } from '@/pinia'
export default function useChartOption(sourceOption) { export default function useChartOption(sourceOption) {
const appStore = useAppStore(); const appStore = useAppStore()
const isDark = computed(() => { const isDark = computed(() => {
return appStore.theme === 'dark'; return appStore.theme === 'dark'
}); })
const chartOption = computed(() => { const chartOption = computed(() => {
return sourceOption(isDark.value); return sourceOption(isDark.value)
}); })
return { return {
chartOption, chartOption
}; }
} }

View File

@ -1,35 +1,35 @@
// 本组件参考 arco-pro 的实现 // 本组件参考 arco-pro 的实现
// https://github.com/arco-design/arco-design-pro-vue/blob/main/arco-design-pro-vite/src/hooks/responsive.ts // https://github.com/arco-design/arco-design-pro-vue/blob/main/arco-design-pro-vite/src/hooks/responsive.ts
import { onMounted, onBeforeMount, onBeforeUnmount } from 'vue'; import { onMounted, onBeforeMount, onBeforeUnmount } from 'vue'
import { useDebounceFn } from '@vueuse/core'; import { useDebounceFn } from '@vueuse/core'
import { useAppStore } from '@/pinia'; import { useAppStore } from '@/pinia'
import { addEventListen, removeEventListen } from '@/utils/event'; import { addEventListen, removeEventListen } from '@/utils/event'
const WIDTH = 992; const WIDTH = 992
function queryDevice() { function queryDevice() {
const rect = document.body.getBoundingClientRect(); const rect = document.body.getBoundingClientRect()
return rect.width - 1 < WIDTH; return rect.width - 1 < WIDTH
} }
export default function useResponsive(immediate) { export default function useResponsive(immediate) {
const appStore = useAppStore(); const appStore = useAppStore()
function resizeHandler() { function resizeHandler() {
if (!document.hidden) { if (!document.hidden) {
const isMobile = queryDevice(); const isMobile = queryDevice()
appStore.toggleDevice(isMobile ? 'mobile' : 'desktop'); appStore.toggleDevice(isMobile ? 'mobile' : 'desktop')
// appStore.toggleDevice(isMobile); // appStore.toggleDevice(isMobile);
}
} }
const debounceFn = useDebounceFn(resizeHandler, 100); }
onMounted(() => { const debounceFn = useDebounceFn(resizeHandler, 100)
if (immediate) debounceFn(); onMounted(() => {
}); if (immediate) debounceFn()
onBeforeMount(() => { })
addEventListen(window, 'resize', debounceFn); onBeforeMount(() => {
}); addEventListen(window, 'resize', debounceFn)
onBeforeUnmount(() => { })
removeEventListen(window, 'resize', debounceFn); onBeforeUnmount(() => {
}); removeEventListen(window, 'resize', debounceFn)
})
} }

View File

@ -18,6 +18,6 @@ export const useWindowResize = (cb) => {
useEventListener('resize', onResize, { passive: true }) useEventListener('resize', onResize, { passive: true })
return { return {
width, width,
height, height
} }
} }

View File

@ -19,11 +19,5 @@ import 'default-passive-events'
const app = createApp(App) const app = createApp(App)
app.config.productionTip = false app.config.productionTip = false
app app.use(run).use(ElementPlus).use(store).use(auth).use(router).mount('#app')
.use(run)
.use(ElementPlus)
.use(store)
.use(auth)
.use(router)
.mount('#app')
export default app export default app

View File

@ -64,4 +64,4 @@
"/src/plugin/announcement/form/info.vue": "InfoForm", "/src/plugin/announcement/form/info.vue": "InfoForm",
"/src/plugin/announcement/view/info.vue": "Info", "/src/plugin/announcement/view/info.vue": "Info",
"/src/plugin/email/view/index.vue": "Email" "/src/plugin/email/view/index.vue": "Email"
} }

View File

@ -8,26 +8,25 @@ Nprogress.configure({ showSpinner: false, ease: 'ease', speed: 500 })
const whiteList = ['Login', 'Init'] const whiteList = ['Login', 'Init']
const getRouter = async(userStore) => { const getRouter = async (userStore) => {
const routerStore = useRouterStore() const routerStore = useRouterStore()
await routerStore.SetAsyncRouter() await routerStore.SetAsyncRouter()
await userStore.GetUserInfo() await userStore.GetUserInfo()
const asyncRouters = routerStore.asyncRouters const asyncRouters = routerStore.asyncRouters
asyncRouters.forEach(asyncRouter => { asyncRouters.forEach((asyncRouter) => {
router.addRoute(asyncRouter) router.addRoute(asyncRouter)
}) })
} }
const removeLoading = () => { const removeLoading = () => {
const element = document.getElementById('gva-loading-box'); const element = document.getElementById('gva-loading-box')
if (element) { if (element) {
element.remove(); element.remove()
} }
} }
async function handleKeepAlive(to) { async function handleKeepAlive(to) {
if (to.matched.some(item => item.meta.keepAlive)) { if (to.matched.some((item) => item.meta.keepAlive)) {
if (to.matched && to.matched.length > 2) { if (to.matched && to.matched.length > 2) {
for (let i = 1; i < to.matched.length; i++) { for (let i = 1; i < to.matched.length; i++) {
const element = to.matched[i - 1] const element = to.matched[i - 1]
@ -45,7 +44,7 @@ async function handleKeepAlive(to) {
} }
} }
router.beforeEach(async(to, from) => { router.beforeEach(async (to, from) => {
const routerStore = useRouterStore() const routerStore = useRouterStore()
Nprogress.start() Nprogress.start()
const userStore = useUserStore() const userStore = useUserStore()
@ -54,7 +53,7 @@ router.beforeEach(async(to, from) => {
const token = userStore.token const token = userStore.token
// 在白名单中的判断情况 // 在白名单中的判断情况
document.title = getPageTitle(to.meta.title, to) document.title = getPageTitle(to.meta.title, to)
if(to.meta.client) { if (to.meta.client) {
return true return true
} }
if (whiteList.indexOf(to.name) > -1) { if (whiteList.indexOf(to.name) > -1) {
@ -85,9 +84,9 @@ router.beforeEach(async(to, from) => {
} else { } else {
// 不在白名单中并且已经登录的时候 // 不在白名单中并且已经登录的时候
if (token) { if (token) {
if(sessionStorage.getItem("needToHome") === 'true') { if (sessionStorage.getItem('needToHome') === 'true') {
sessionStorage.removeItem("needToHome") sessionStorage.removeItem('needToHome')
return { path: '/'} return { path: '/' }
} }
// 添加flag防止多次获取动态路由和栈溢出 // 添加flag防止多次获取动态路由和栈溢出
if (!routerStore.asyncRouterFlag && whiteList.indexOf(from.name) < 0) { if (!routerStore.asyncRouterFlag && whiteList.indexOf(from.name) < 0) {
@ -124,7 +123,6 @@ router.beforeEach(async(to, from) => {
} }
}) })
router.afterEach(() => { router.afterEach(() => {
// 路由加载完成后关闭进度条 // 路由加载完成后关闭进度条
document.getElementsByClassName('main-cont main-right')[0]?.scrollTo(0, 0) document.getElementsByClassName('main-cont main-right')[0]?.scrollTo(0, 0)

View File

@ -5,9 +5,4 @@ import { useDictionaryStore } from '@/pinia/modules/dictionary'
const store = createPinia() const store = createPinia()
export { export { store, useAppStore, useUserStore, useDictionaryStore }
store,
useAppStore,
useUserStore,
useDictionaryStore
}

View File

@ -1,58 +1,56 @@
import { defineStore } from 'pinia' import { defineStore } from 'pinia'
import { ref, watchEffect, reactive } from 'vue' import { ref, watchEffect, reactive } from 'vue'
import { setBodyPrimaryColor } from '@/utils/format' import { setBodyPrimaryColor } from '@/utils/format'
export const useAppStore = defineStore('app', () => { export const useAppStore = defineStore('app', () => {
const device = ref('')
const device = ref("")
const config = reactive({ const config = reactive({
weakness: false, weakness: false,
grey: false, grey: false,
primaryColor: '#3b82f6', primaryColor: '#3b82f6',
showTabs: true, showTabs: true,
darkMode: 'auto', darkMode: 'auto',
layout_side_width : 256, layout_side_width: 256,
layout_side_collapsed_width : 80, layout_side_collapsed_width: 80,
layout_side_item_height : 48, layout_side_item_height: 48,
show_watermark: true, show_watermark: true,
side_mode : 'normal' side_mode: 'normal'
}) })
const theme = ref( 'auto') const theme = ref('auto')
const toggleTheme = (dark) => { const toggleTheme = (dark) => {
if (dark) { if (dark) {
theme.value = 'dark'; theme.value = 'dark'
} else { } else {
theme.value = 'light'; theme.value = 'light'
} }
} }
const toggleWeakness = (e) => { const toggleWeakness = (e) => {
config.weakness = e; config.weakness = e
} }
const toggleGrey = (e) => { const toggleGrey = (e) => {
config.grey = e; config.grey = e
} }
const togglePrimaryColor = (e) => { const togglePrimaryColor = (e) => {
config.primaryColor = e; config.primaryColor = e
} }
const toggleTabs = (e) => { const toggleTabs = (e) => {
config.showTabs = e; config.showTabs = e
} }
const toggleDevice = (e) => { const toggleDevice = (e) => {
device.value = e; device.value = e
} }
const toggleDarkMode = (e) => { const toggleDarkMode = (e) => {
config.darkMode = e config.darkMode = e
} }
const toggleDarkModeAuto = () =>{ const toggleDarkModeAuto = () => {
// 处理浏览器主题 // 处理浏览器主题
const darkQuery = window.matchMedia('(prefers-color-scheme: dark)') const darkQuery = window.matchMedia('(prefers-color-scheme: dark)')
const dark = darkQuery.matches const dark = darkQuery.matches
@ -63,61 +61,59 @@ export const useAppStore = defineStore('app', () => {
} }
const toggleConfigSideWidth = (e) => { const toggleConfigSideWidth = (e) => {
config.layout_side_width = e; config.layout_side_width = e
} }
const toggleConfigSideCollapsedWidth = (e) => { const toggleConfigSideCollapsedWidth = (e) => {
config.layout_side_collapsed_width = e; config.layout_side_collapsed_width = e
} }
const toggleConfigSideItemHeight = (e) => { const toggleConfigSideItemHeight = (e) => {
config.layout_side_item_height = e; config.layout_side_item_height = e
} }
const toggleConfigWatermark = (e) => { const toggleConfigWatermark = (e) => {
config.show_watermark = e; config.show_watermark = e
} }
const toggleSideModel= (e) =>{ const toggleSideModel = (e) => {
config.side_mode = e config.side_mode = e
} }
watchEffect(() => { watchEffect(() => {
if (theme.value === 'dark') { if (theme.value === 'dark') {
document.documentElement.classList.add('dark'); document.documentElement.classList.add('dark')
document.documentElement.classList.remove('light'); document.documentElement.classList.remove('light')
} else { } else {
document.documentElement.classList.add('light'); document.documentElement.classList.add('light')
document.documentElement.classList.remove('dark'); document.documentElement.classList.remove('dark')
} }
}) })
watchEffect(() => { watchEffect(() => {
// 色弱模式监听处理 // 色弱模式监听处理
if (config.weakness) { if (config.weakness) {
document.documentElement.classList.add('html-weakenss'); document.documentElement.classList.add('html-weakenss')
} else { } else {
document.documentElement.classList.remove('html-weakenss'); document.documentElement.classList.remove('html-weakenss')
} }
}) })
watchEffect(() => { watchEffect(() => {
// 灰色模式监听处理 // 灰色模式监听处理
if (config.grey) { if (config.grey) {
document.documentElement.classList.add('html-grey'); document.documentElement.classList.add('html-grey')
} else { } else {
document.documentElement.classList.remove('html-grey'); document.documentElement.classList.remove('html-grey')
} }
}) })
watchEffect(() => { watchEffect(() => {
if(config.darkMode === 'auto'){ if (config.darkMode === 'auto') {
toggleDarkModeAuto() toggleDarkModeAuto()
} }
if(config.darkMode === 'dark'){ if (config.darkMode === 'dark') {
toggleTheme(true) toggleTheme(true)
}else{ } else {
toggleTheme(false) toggleTheme(false)
} }
}) })
@ -143,5 +139,4 @@ export const useAppStore = defineStore('app', () => {
toggleConfigWatermark, toggleConfigWatermark,
toggleSideModel toggleSideModel
} }
}) })

View File

@ -10,7 +10,7 @@ export const useDictionaryStore = defineStore('dictionary', () => {
dictionaryMap.value = { ...dictionaryMap.value, ...dictionaryRes } dictionaryMap.value = { ...dictionaryMap.value, ...dictionaryRes }
} }
const getDictionary = async(type) => { const getDictionary = async (type) => {
if (dictionaryMap.value[type] && dictionaryMap.value[type].length) { if (dictionaryMap.value[type] && dictionaryMap.value[type].length) {
return dictionaryMap.value[type] return dictionaryMap.value[type]
} else { } else {
@ -18,13 +18,14 @@ export const useDictionaryStore = defineStore('dictionary', () => {
if (res.code === 0) { if (res.code === 0) {
const dictionaryRes = {} const dictionaryRes = {}
const dict = [] const dict = []
res.data.resysDictionary.sysDictionaryDetails && res.data.resysDictionary.sysDictionaryDetails.forEach(item => { res.data.resysDictionary.sysDictionaryDetails &&
dict.push({ res.data.resysDictionary.sysDictionaryDetails.forEach((item) => {
label: item.label, dict.push({
value: item.value, label: item.label,
extend: item.extend value: item.value,
extend: item.extend
})
}) })
})
dictionaryRes[res.data.resysDictionary.type] = dict dictionaryRes[res.data.resysDictionary.type] = dict
setDictionaryMap(dictionaryRes) setDictionaryMap(dictionaryRes)
return dictionaryMap.value[type] return dictionaryMap.value[type]

View File

@ -2,43 +2,48 @@ import { asyncRouterHandle } from '@/utils/asyncRouter'
import { emitter } from '@/utils/bus.js' import { emitter } from '@/utils/bus.js'
import { asyncMenu } from '@/api/menu' import { asyncMenu } from '@/api/menu'
import { defineStore } from 'pinia' import { defineStore } from 'pinia'
import { ref,watchEffect } from 'vue' import { ref, watchEffect } from 'vue'
import pathInfo from "@/pathInfo.json"; import pathInfo from '@/pathInfo.json'
const notLayoutRouterArr = [] const notLayoutRouterArr = []
const keepAliveRoutersArr = [] const keepAliveRoutersArr = []
const nameMap = {} const nameMap = {}
const formatRouter = (routes, routeMap, parent) => { const formatRouter = (routes, routeMap, parent) => {
routes && routes.forEach(item => { routes &&
item.parent = parent routes.forEach((item) => {
item.meta.btns = item.btns item.parent = parent
item.meta.hidden = item.hidden item.meta.btns = item.btns
if (item.meta.defaultMenu === true) { item.meta.hidden = item.hidden
if (!parent) { if (item.meta.defaultMenu === true) {
item = { ...item, path: `/${item.path}` } if (!parent) {
notLayoutRouterArr.push(item) item = { ...item, path: `/${item.path}` }
notLayoutRouterArr.push(item)
}
} }
} routeMap[item.name] = item
routeMap[item.name] = item if (item.children && item.children.length > 0) {
if (item.children && item.children.length > 0) { formatRouter(item.children, routeMap, item)
formatRouter(item.children, routeMap, item) }
} })
})
} }
const KeepAliveFilter = (routes) => { const KeepAliveFilter = (routes) => {
routes && routes.forEach(item => { routes &&
// 子菜单中有 keep-alive 的,父菜单也必须 keep-alive否则无效。这里将子菜单中有 keep-alive 的父菜单也加入。 routes.forEach((item) => {
if ((item.children && item.children.some(ch => ch.meta.keepAlive) || item.meta.keepAlive)) { // 子菜单中有 keep-alive 的,父菜单也必须 keep-alive否则无效。这里将子菜单中有 keep-alive 的父菜单也加入。
const path = item.meta.path if (
keepAliveRoutersArr.push(pathInfo[path]) (item.children && item.children.some((ch) => ch.meta.keepAlive)) ||
nameMap[item.name] = pathInfo[path] item.meta.keepAlive
} ) {
if (item.children && item.children.length > 0) { const path = item.meta.path
KeepAliveFilter(item.children) keepAliveRoutersArr.push(pathInfo[path])
} nameMap[item.name] = pathInfo[path]
}) }
if (item.children && item.children.length > 0) {
KeepAliveFilter(item.children)
}
})
} }
export const useRouterStore = defineStore('router', () => { export const useRouterStore = defineStore('router', () => {
@ -46,7 +51,7 @@ export const useRouterStore = defineStore('router', () => {
const asyncRouterFlag = ref(0) const asyncRouterFlag = ref(0)
const setKeepAliveRouters = (history) => { const setKeepAliveRouters = (history) => {
const keepArrTemp = [] const keepArrTemp = []
history.forEach(item => { history.forEach((item) => {
if (nameMap[item.name]) { if (nameMap[item.name]) {
keepArrTemp.push(nameMap[item.name]) keepArrTemp.push(nameMap[item.name])
} }
@ -63,64 +68,63 @@ export const useRouterStore = defineStore('router', () => {
const menuMap = {} const menuMap = {}
const topActive = ref("") const topActive = ref('')
const setLeftMenu = (name) => { const setLeftMenu = (name) => {
sessionStorage.setItem('topActive', name) sessionStorage.setItem('topActive', name)
topActive.value = name topActive.value = name
if(menuMap[name]?.children){ if (menuMap[name]?.children) {
leftMenu.value = menuMap[name].children leftMenu.value = menuMap[name].children
} }
return menuMap[name]?.children return menuMap[name]?.children
} }
watchEffect(()=>{ watchEffect(() => {
let topActive = sessionStorage.getItem("topActive") let topActive = sessionStorage.getItem('topActive')
let firstHasChildren = '' let firstHasChildren = ''
asyncRouters.value[0]?.children.forEach((item) => { asyncRouters.value[0]?.children.forEach((item) => {
if (item.hidden) return; if (item.hidden) return
menuMap[item.name] = item; menuMap[item.name] = item
if (!firstHasChildren && item.children && item.children.length > 0) { if (!firstHasChildren && item.children && item.children.length > 0) {
firstHasChildren = item.name firstHasChildren = item.name
} }
topMenu.value.push({...item, children: []}) topMenu.value.push({ ...item, children: [] })
}); })
if(!menuMap[topActive]?.children && firstHasChildren){ if (!menuMap[topActive]?.children && firstHasChildren) {
topActive = firstHasChildren topActive = firstHasChildren
} }
setLeftMenu(topActive) setLeftMenu(topActive)
}) })
const routeMap = ({}) const routeMap = {}
// 从后台获取动态路由 // 从后台获取动态路由
const SetAsyncRouter = async() => { const SetAsyncRouter = async () => {
asyncRouterFlag.value++ asyncRouterFlag.value++
const baseRouter = [{ const baseRouter = [
path: '/layout', {
name: 'layout', path: '/layout',
component: 'view/layout/index.vue', name: 'layout',
meta: { component: 'view/layout/index.vue',
title: '底层layout' meta: {
}, title: '底层layout'
children: [] },
}] children: []
}
]
const asyncRouterRes = await asyncMenu() const asyncRouterRes = await asyncMenu()
const asyncRouter = asyncRouterRes.data.menus const asyncRouter = asyncRouterRes.data.menus
asyncRouter && asyncRouter.push({ asyncRouter &&
path: 'reload', asyncRouter.push({
name: 'Reload', path: 'reload',
hidden: true, name: 'Reload',
meta: { hidden: true,
title: '', meta: {
closeTab: true, title: '',
}, closeTab: true
component: 'view/error/reload.vue' },
}) component: 'view/error/reload.vue'
})
formatRouter(asyncRouter, routeMap) formatRouter(asyncRouter, routeMap)
baseRouter[0].children = asyncRouter baseRouter[0].children = asyncRouter
if (notLayoutRouterArr.length !== 0) { if (notLayoutRouterArr.length !== 0) {
@ -144,4 +148,3 @@ export const useRouterStore = defineStore('router', () => {
routeMap routeMap
} }
}) })

View File

@ -7,7 +7,7 @@ import { ref, watch } from 'vue'
import { useRouterStore } from './router' import { useRouterStore } from './router'
import cookie from 'js-cookie' import cookie from 'js-cookie'
import {useAppStore} from "@/pinia"; import { useAppStore } from '@/pinia'
export const useUserStore = defineStore('user', () => { export const useUserStore = defineStore('user', () => {
const appStore = useAppStore() const appStore = useAppStore()
@ -17,13 +17,15 @@ export const useUserStore = defineStore('user', () => {
uuid: '', uuid: '',
nickName: '', nickName: '',
headerImg: '', headerImg: '',
authority: {}, authority: {}
}) })
const token = ref(window.localStorage.getItem('token') || cookie.get('x-token') || '') const token = ref(
window.localStorage.getItem('token') || cookie.get('x-token') || ''
)
const setUserInfo = (val) => { const setUserInfo = (val) => {
userInfo.value = val userInfo.value = val
if(val.originSetting){ if (val.originSetting) {
Object.keys(appStore.config).forEach(key => { Object.keys(appStore.config).forEach((key) => {
appStore.config[key] = val.originSetting[key] appStore.config[key] = val.originSetting[key]
}) })
} }
@ -36,7 +38,7 @@ export const useUserStore = defineStore('user', () => {
const NeedInit = async () => { const NeedInit = async () => {
token.value = '' token.value = ''
window.localStorage.removeItem('token') window.localStorage.removeItem('token')
await router.push({name: 'Init', replace: true}) await router.push({ name: 'Init', replace: true })
} }
const ResetUserInfo = (value = {}) => { const ResetUserInfo = (value = {}) => {
@ -46,7 +48,7 @@ export const useUserStore = defineStore('user', () => {
} }
} }
/* 获取用户信息*/ /* 获取用户信息*/
const GetUserInfo = async() => { const GetUserInfo = async () => {
const res = await getUserInfo() const res = await getUserInfo()
if (res.code === 0) { if (res.code === 0) {
setUserInfo(res.data.userInfo) setUserInfo(res.data.userInfo)
@ -54,10 +56,10 @@ export const useUserStore = defineStore('user', () => {
return res return res
} }
/* 登录*/ /* 登录*/
const LoginIn = async(loginInfo) => { const LoginIn = async (loginInfo) => {
loadingInstance.value = ElLoading.service({ loadingInstance.value = ElLoading.service({
fullscreen: true, fullscreen: true,
text: '登录中,请稍候...', text: '登录中,请稍候...'
}) })
const res = await login(loginInfo) const res = await login(loginInfo)
@ -78,7 +80,7 @@ export const useUserStore = defineStore('user', () => {
const asyncRouters = routerStore.asyncRouters const asyncRouters = routerStore.asyncRouters
// 注册到路由表里 // 注册到路由表里
asyncRouters.forEach(asyncRouter => { asyncRouters.forEach((asyncRouter) => {
router.addRoute(asyncRouter) router.addRoute(asyncRouter)
}) })
@ -95,13 +97,12 @@ export const useUserStore = defineStore('user', () => {
window.localStorage.setItem('osType', 'MAC') window.localStorage.setItem('osType', 'MAC')
} }
// 全部操作均结束关闭loading并返回 // 全部操作均结束关闭loading并返回
loadingInstance.value.close() loadingInstance.value.close()
return true return true
} }
/* 登出*/ /* 登出*/
const LoginOut = async() => { const LoginOut = async () => {
const res = await jsonInBlacklist() const res = await jsonInBlacklist()
// 登出失败 // 登出失败
@ -116,7 +117,7 @@ export const useUserStore = defineStore('user', () => {
window.location.reload() window.location.reload()
} }
/* 清理数据 */ /* 清理数据 */
const ClearStorage = async() => { const ClearStorage = async () => {
token.value = '' token.value = ''
sessionStorage.clear() sessionStorage.clear()
window.localStorage.removeItem('token') window.localStorage.removeItem('token')
@ -124,9 +125,12 @@ export const useUserStore = defineStore('user', () => {
localStorage.removeItem('originSetting') localStorage.removeItem('originSetting')
} }
watch(() => token.value, () => { watch(
window.localStorage.setItem('token', token.value) () => token.value,
}) () => {
window.localStorage.setItem('token', token.value)
}
)
return { return {
userInfo, userInfo,

View File

@ -105,6 +105,6 @@ export const getInfoList = (params) => {
export const getInfoDataSource = () => { export const getInfoDataSource = () => {
return service({ return service({
url: '/info/getInfoDataSource', url: '/info/getInfoDataSource',
method: 'get', method: 'get'
}) })
} }

View File

@ -1,21 +1,41 @@
<template> <template>
<div> <div>
<div class="gva-form-box"> <div class="gva-form-box">
<el-form :model="formData" ref="elFormRef" label-position="right" :rules="rule" label-width="80px"> <el-form
:model="formData"
ref="elFormRef"
label-position="right"
:rules="rule"
label-width="80px"
>
<el-form-item label="标题:" prop="title"> <el-form-item label="标题:" prop="title">
<el-input v-model="formData.title" :clearable="true" placeholder="请输入标题" /> <el-input
</el-form-item> v-model="formData.title"
:clearable="true"
placeholder="请输入标题"
/>
</el-form-item>
<el-form-item label="内容:" prop="content"> <el-form-item label="内容:" prop="content">
<RichEdit v-model="formData.content"/> <RichEdit v-model="formData.content" />
</el-form-item> </el-form-item>
<el-form-item label="作者:" prop="userID"> <el-form-item label="作者:" prop="userID">
<el-select v-model="formData.userID" placeholder="请选择作者" style="width:100%" :clearable="true" > <el-select
<el-option v-for="(item,key) in dataSource.userID" :key="key" :label="item.label" :value="item.value" /> v-model="formData.userID"
</el-select> placeholder="请选择作者"
</el-form-item> style="width: 100%"
:clearable="true"
>
<el-option
v-for="(item, key) in dataSource.userID"
:key="key"
:label="item.label"
:value="item.value"
/>
</el-select>
</el-form-item>
<el-form-item label="附件:" prop="attachments"> <el-form-item label="附件:" prop="attachments">
<SelectFile v-model="formData.attachments" /> <SelectFile v-model="formData.attachments" />
</el-form-item> </el-form-item>
<el-form-item> <el-form-item>
<el-button type="primary" @click="save">保存</el-button> <el-button type="primary" @click="save">保存</el-button>
<el-button type="primary" @click="back">返回</el-button> <el-button type="primary" @click="back">返回</el-button>
@ -26,42 +46,41 @@
</template> </template>
<script setup> <script setup>
import { import {
getInfoDataSource, getInfoDataSource,
createInfo, createInfo,
updateInfo, updateInfo,
findInfo findInfo
} from '@/plugin/announcement/api/info' } from '@/plugin/announcement/api/info'
defineOptions({ defineOptions({
name: 'InfoForm' name: 'InfoForm'
}) })
// //
import { useRoute, useRouter } from "vue-router" import { useRoute, useRouter } from 'vue-router'
import { ElMessage } from 'element-plus' import { ElMessage } from 'element-plus'
import { ref, reactive } from 'vue' import { ref, reactive } from 'vue'
import SelectFile from '@/components/selectFile/selectFile.vue' import SelectFile from '@/components/selectFile/selectFile.vue'
// //
import RichEdit from '@/components/richtext/rich-edit.vue' import RichEdit from '@/components/richtext/rich-edit.vue'
const route = useRoute() const route = useRoute()
const router = useRouter() const router = useRouter()
const type = ref('') const type = ref('')
const formData = ref({ const formData = ref({
title: '', title: '',
content: '', content: '',
userID: undefined, userID: undefined,
attachments: [], attachments: []
}) })
// //
const rule = reactive({ const rule = reactive({})
})
const elFormRef = ref() const elFormRef = ref()
const dataSource = ref([]) const dataSource = ref([])
const getDataSourceFunc = async()=>{ const getDataSourceFunc = async () => {
const res = await getInfoDataSource() const res = await getInfoDataSource()
if (res.code === 0) { if (res.code === 0) {
dataSource.value = res.data dataSource.value = res.data
@ -69,9 +88,9 @@ const elFormRef = ref()
} }
getDataSourceFunc() getDataSourceFunc()
// //
const init = async () => { const init = async () => {
// urlID find createupdate idurl // urlID find createupdate idurl
if (route.query.id) { if (route.query.id) {
const res = await findInfo({ ID: route.query.id }) const res = await findInfo({ ID: route.query.id })
if (res.code === 0) { if (res.code === 0) {
@ -81,40 +100,38 @@ const init = async () => {
} else { } else {
type.value = 'create' type.value = 'create'
} }
} }
init() init()
// //
const save = async() => { const save = async () => {
elFormRef.value?.validate( async (valid) => { elFormRef.value?.validate(async (valid) => {
if (!valid) return if (!valid) return
let res let res
switch (type.value) { switch (type.value) {
case 'create': case 'create':
res = await createInfo(formData.value) res = await createInfo(formData.value)
break break
case 'update': case 'update':
res = await updateInfo(formData.value) res = await updateInfo(formData.value)
break break
default: default:
res = await createInfo(formData.value) res = await createInfo(formData.value)
break break
} }
if (res.code === 0) { if (res.code === 0) {
ElMessage({ ElMessage({
type: 'success', type: 'success',
message: '创建/更改成功' message: '创建/更改成功'
}) })
} }
}) })
} }
// //
const back = () => { const back = () => {
router.go(-1) router.go(-1)
} }
</script> </script>
<style> <style></style>
</style>

View File

@ -1,22 +1,50 @@
<template> <template>
<div> <div>
<div class="gva-search-box"> <div class="gva-search-box">
<el-form ref="elSearchFormRef" :inline="true" :model="searchInfo" class="demo-form-inline" :rules="searchRule" @keyup.enter="onSubmit"> <el-form
ref="elSearchFormRef"
:inline="true"
:model="searchInfo"
class="demo-form-inline"
:rules="searchRule"
@keyup.enter="onSubmit"
>
<el-form-item label="创建日期" prop="createdAt"> <el-form-item label="创建日期" prop="createdAt">
<template #label> <template #label>
<span> <span>
创建日期 创建日期
<el-tooltip content="搜索范围是开始日期(包含)至结束日期(不包含)"> <el-tooltip
content="搜索范围是开始日期(包含)至结束日期(不包含)"
>
<el-icon><QuestionFilled /></el-icon> <el-icon><QuestionFilled /></el-icon>
</el-tooltip> </el-tooltip>
</span> </span>
</template> </template>
<el-date-picker v-model="searchInfo.startCreatedAt" type="datetime" placeholder="开始日期" :disabled-date="time=> searchInfo.endCreatedAt ? time.getTime() > searchInfo.endCreatedAt.getTime() : false" /> <el-date-picker
v-model="searchInfo.startCreatedAt"
type="datetime"
placeholder="开始日期"
:disabled-date="
(time) =>
searchInfo.endCreatedAt
? time.getTime() > searchInfo.endCreatedAt.getTime()
: false
"
/>
<el-date-picker v-model="searchInfo.endCreatedAt" type="datetime" placeholder="结束日期" :disabled-date="time=> searchInfo.startCreatedAt ? time.getTime() < searchInfo.startCreatedAt.getTime() : false" /> <el-date-picker
v-model="searchInfo.endCreatedAt"
type="datetime"
placeholder="结束日期"
:disabled-date="
(time) =>
searchInfo.startCreatedAt
? time.getTime() < searchInfo.startCreatedAt.getTime()
: false
"
/>
</el-form-item> </el-form-item>
<template v-if="showAllQuery"> <template v-if="showAllQuery">
<!-- 将需要控制显示状态的查询条件添加到此范围内 --> <!-- 将需要控制显示状态的查询条件添加到此范围内 -->
</template> </template>
@ -25,13 +53,23 @@
<el-button type="primary" icon="search" @click="onSubmit"> <el-button type="primary" icon="search" @click="onSubmit">
查询 查询
</el-button> </el-button>
<el-button icon="refresh" @click="onReset"> <el-button icon="refresh" @click="onReset"> 重置 </el-button>
重置 <el-button
</el-button> v-if="!showAllQuery"
<el-button v-if="!showAllQuery" link type="primary" icon="arrow-down" @click="showAllQuery=true"> link
type="primary"
icon="arrow-down"
@click="showAllQuery = true"
>
展开 展开
</el-button> </el-button>
<el-button v-else link type="primary" icon="arrow-up" @click="showAllQuery=false"> <el-button
v-else
link
type="primary"
icon="arrow-up"
@click="showAllQuery = false"
>
收起 收起
</el-button> </el-button>
</el-form-item> </el-form-item>
@ -42,7 +80,12 @@
<el-button type="primary" icon="plus" @click="openDialog"> <el-button type="primary" icon="plus" @click="openDialog">
新增 新增
</el-button> </el-button>
<el-button icon="delete" style="margin-left: 10px;" :disabled="!multipleSelection.length" @click="onDelete"> <el-button
icon="delete"
style="margin-left: 10px"
:disabled="!multipleSelection.length"
@click="onDelete"
>
删除 删除
</el-button> </el-button>
</div> </div>
@ -65,24 +108,46 @@
<el-table-column align="left" label="标题" prop="title" width="120" /> <el-table-column align="left" label="标题" prop="title" width="120" />
<el-table-column align="left" label="作者" prop="userID" width="120"> <el-table-column align="left" label="作者" prop="userID" width="120">
<template #default="scope"> <template #default="scope">
<span>{{ filterDataSource(dataSource.userID,scope.row.userID) }}</span> <span>{{
filterDataSource(dataSource.userID, scope.row.userID)
}}</span>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="附件" prop="attachments" width="200"> <el-table-column label="附件" prop="attachments" width="200">
<template #default="scope"> <template #default="scope">
<div class="file-list"> <div class="file-list">
<el-tag v-for="file in scope.row.attachments" :key="file.uid" @click="downloadFile(file.url)"> <el-tag
v-for="file in scope.row.attachments"
:key="file.uid"
@click="downloadFile(file.url)"
>
{{ file.name }} {{ file.name }}
</el-tag> </el-tag>
</div> </div>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column align="left" label="操作" fixed="right" min-width="240"> <el-table-column
align="left"
label="操作"
fixed="right"
min-width="240"
>
<template #default="scope"> <template #default="scope">
<el-button type="primary" link icon="edit" class="table-button" @click="updateInfoFunc(scope.row)"> <el-button
type="primary"
link
icon="edit"
class="table-button"
@click="updateInfoFunc(scope.row)"
>
变更 变更
</el-button> </el-button>
<el-button type="primary" link icon="delete" @click="deleteRow(scope.row)"> <el-button
type="primary"
link
icon="delete"
@click="deleteRow(scope.row)"
>
删除 删除
</el-button> </el-button>
</template> </template>
@ -100,31 +165,53 @@
/> />
</div> </div>
</div> </div>
<el-drawer v-model="dialogFormVisible" destroy-on-close size="800" :show-close="false" :before-close="closeDialog"> <el-drawer
v-model="dialogFormVisible"
destroy-on-close
size="800"
:show-close="false"
:before-close="closeDialog"
>
<template #header> <template #header>
<div class="flex justify-between items-center"> <div class="flex justify-between items-center">
<span class="text-lg">{{ type==='create'?'添加':'修改' }}</span> <span class="text-lg">{{ type === 'create' ? '添加' : '修改' }}</span>
<div> <div>
<el-button type="primary" @click="enterDialog"> <el-button type="primary" @click="enterDialog"> </el-button>
<el-button @click="closeDialog"> </el-button>
</el-button>
<el-button @click="closeDialog">
</el-button>
</div> </div>
</div> </div>
</template> </template>
<el-form ref="elFormRef" :model="formData" label-position="top" :rules="rule" label-width="80px"> <el-form
ref="elFormRef"
:model="formData"
label-position="top"
:rules="rule"
label-width="80px"
>
<el-form-item label="标题:" prop="title"> <el-form-item label="标题:" prop="title">
<el-input v-model="formData.title" :clearable="true" placeholder="请输入标题" /> <el-input
v-model="formData.title"
:clearable="true"
placeholder="请输入标题"
/>
</el-form-item> </el-form-item>
<el-form-item label="内容:" prop="content"> <el-form-item label="内容:" prop="content">
<RichEdit v-model="formData.content" /> <RichEdit v-model="formData.content" />
</el-form-item> </el-form-item>
<el-form-item label="作者:" prop="userID"> <el-form-item label="作者:" prop="userID">
<el-select v-model="formData.userID" placeholder="请选择作者" style="width:100%" :clearable="true"> <el-select
<el-option v-for="(item,key) in dataSource.userID" :key="key" :label="item.label" :value="item.value" /> v-model="formData.userID"
placeholder="请选择作者"
style="width: 100%"
:clearable="true"
>
<el-option
v-for="(item, key) in dataSource.userID"
:key="key"
:label="item.label"
:value="item.value"
/>
</el-select> </el-select>
</el-form-item> </el-form-item>
<el-form-item label="附件:" prop="attachments"> <el-form-item label="附件:" prop="attachments">
@ -136,42 +223,42 @@
</template> </template>
<script setup> <script setup>
import { import {
getInfoDataSource, getInfoDataSource,
createInfo, createInfo,
deleteInfo, deleteInfo,
deleteInfoByIds, deleteInfoByIds,
updateInfo, updateInfo,
findInfo, findInfo,
getInfoList getInfoList
} from '@/plugin/announcement/api/info' } from '@/plugin/announcement/api/info'
import { getUrl } from '@/utils/image' import { getUrl } from '@/utils/image'
// //
import RichEdit from '@/components/richtext/rich-edit.vue' import RichEdit from '@/components/richtext/rich-edit.vue'
// //
import SelectFile from '@/components/selectFile/selectFile.vue' import SelectFile from '@/components/selectFile/selectFile.vue'
// //
import { formatDate, filterDataSource } from '@/utils/format' import { formatDate, filterDataSource } from '@/utils/format'
import { ElMessage, ElMessageBox } from 'element-plus' import { ElMessage, ElMessageBox } from 'element-plus'
import { ref, reactive } from 'vue' import { ref, reactive } from 'vue'
defineOptions({ defineOptions({
name: 'Info' name: 'Info'
}) })
// / // /
const showAllQuery = ref(false) const showAllQuery = ref(false)
// //
const formData = ref({ const formData = ref({
title: '', title: '',
content: '', content: '',
userID: undefined, userID: undefined,
attachments: [], attachments: []
}) })
const dataSource = ref([]) const dataSource = ref([])
const getDataSourceFunc = async()=>{ const getDataSourceFunc = async () => {
const res = await getInfoDataSource() const res = await getInfoDataSource()
if (res.code === 0) { if (res.code === 0) {
dataSource.value = res.data dataSource.value = res.data
@ -179,114 +266,129 @@ const formData = ref({
} }
getDataSourceFunc() getDataSourceFunc()
//
const rule = reactive({})
const searchRule = reactive({
// createdAt: [
const rule = reactive({ {
}) validator: (rule, value, callback) => {
if (
const searchRule = reactive({ searchInfo.value.startCreatedAt &&
createdAt: [ !searchInfo.value.endCreatedAt
{ validator: (rule, value, callback) => { ) {
if (searchInfo.value.startCreatedAt && !searchInfo.value.endCreatedAt) { callback(new Error('请填写结束日期'))
callback(new Error('请填写结束日期')) } else if (
} else if (!searchInfo.value.startCreatedAt && searchInfo.value.endCreatedAt) { !searchInfo.value.startCreatedAt &&
callback(new Error('请填写开始日期')) searchInfo.value.endCreatedAt
} else if (searchInfo.value.startCreatedAt && searchInfo.value.endCreatedAt && (searchInfo.value.startCreatedAt.getTime() === searchInfo.value.endCreatedAt.getTime() || searchInfo.value.startCreatedAt.getTime() > searchInfo.value.endCreatedAt.getTime())) { ) {
callback(new Error('开始日期应当早于结束日期')) callback(new Error('请填写开始日期'))
} else { } else if (
callback() searchInfo.value.startCreatedAt &&
searchInfo.value.endCreatedAt &&
(searchInfo.value.startCreatedAt.getTime() ===
searchInfo.value.endCreatedAt.getTime() ||
searchInfo.value.startCreatedAt.getTime() >
searchInfo.value.endCreatedAt.getTime())
) {
callback(new Error('开始日期应当早于结束日期'))
} else {
callback()
}
},
trigger: 'change'
} }
}, trigger: 'change' } ]
],
})
const elFormRef = ref()
const elSearchFormRef = ref()
// =========== ===========
const page = ref(1)
const total = ref(0)
const pageSize = ref(10)
const tableData = ref([])
const searchInfo = ref({})
//
const onReset = () => {
searchInfo.value = {}
getTableData()
}
//
const onSubmit = () => {
elSearchFormRef.value?.validate(async(valid) => {
if (!valid) return
page.value = 1
pageSize.value = 10
getTableData()
}) })
}
// const elFormRef = ref()
const handleSizeChange = (val) => { const elSearchFormRef = ref()
pageSize.value = val
getTableData()
}
// // =========== ===========
const handleCurrentChange = (val) => { const page = ref(1)
page.value = val const total = ref(0)
getTableData() const pageSize = ref(10)
} const tableData = ref([])
const searchInfo = ref({})
// //
const getTableData = async() => { const onReset = () => {
const table = await getInfoList({ page: page.value, pageSize: pageSize.value, ...searchInfo.value }) searchInfo.value = {}
if (table.code === 0) { getTableData()
tableData.value = table.data.list
total.value = table.data.total
page.value = table.data.page
pageSize.value = table.data.pageSize
} }
}
getTableData() //
const onSubmit = () => {
elSearchFormRef.value?.validate(async (valid) => {
if (!valid) return
page.value = 1
pageSize.value = 10
getTableData()
})
}
// ============== =============== //
const handleSizeChange = (val) => {
pageSize.value = val
getTableData()
}
// //
const setOptions = async () =>{ const handleCurrentChange = (val) => {
} page.value = val
getTableData()
}
// //
setOptions() const getTableData = async () => {
const table = await getInfoList({
page: page.value,
// pageSize: pageSize.value,
const multipleSelection = ref([]) ...searchInfo.value
// })
const handleSelectionChange = (val) => { if (table.code === 0) {
multipleSelection.value = val tableData.value = table.data.list
} total.value = table.data.total
page.value = table.data.page
// pageSize.value = table.data.pageSize
const deleteRow = (row) => {
ElMessageBox.confirm('确定要删除吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
deleteInfoFunc(row)
})
} }
}
// getTableData()
const onDelete = async() => {
ElMessageBox.confirm('确定要删除吗?', '提示', { // ============== ===============
confirmButtonText: '确定',
cancelButtonText: '取消', //
type: 'warning' const setOptions = async () => {}
}).then(async() => {
//
setOptions()
//
const multipleSelection = ref([])
//
const handleSelectionChange = (val) => {
multipleSelection.value = val
}
//
const deleteRow = (row) => {
ElMessageBox.confirm('确定要删除吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
deleteInfoFunc(row)
})
}
//
const onDelete = async () => {
ElMessageBox.confirm('确定要删除吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(async () => {
const IDs = [] const IDs = []
if (multipleSelection.value.length === 0) { if (multipleSelection.value.length === 0) {
ElMessage({ ElMessage({
@ -296,7 +398,7 @@ const onDelete = async() => {
return return
} }
multipleSelection.value && multipleSelection.value &&
multipleSelection.value.map(item => { multipleSelection.value.map((item) => {
IDs.push(item.ID) IDs.push(item.ID)
}) })
const res = await deleteInfoByIds({ IDs }) const res = await deleteInfoByIds({ IDs })
@ -310,104 +412,100 @@ const onDelete = async() => {
} }
getTableData() getTableData()
} }
}) })
} }
// //
const type = ref('') const type = ref('')
// //
const updateInfoFunc = async(row) => { const updateInfoFunc = async (row) => {
const res = await findInfo({ ID: row.ID }) const res = await findInfo({ ID: row.ID })
type.value = 'update' type.value = 'update'
if (res.code === 0) { if (res.code === 0) {
formData.value = res.data formData.value = res.data
dialogFormVisible.value = true dialogFormVisible.value = true
} }
} }
//
// const deleteInfoFunc = async (row) => {
const deleteInfoFunc = async (row) => {
const res = await deleteInfo({ ID: row.ID }) const res = await deleteInfo({ ID: row.ID })
if (res.code === 0) { if (res.code === 0) {
ElMessage({ ElMessage({
type: 'success', type: 'success',
message: '删除成功' message: '删除成功'
}) })
if (tableData.value.length === 1 && page.value > 1) { if (tableData.value.length === 1 && page.value > 1) {
page.value-- page.value--
} }
getTableData() getTableData()
} }
} }
// //
const dialogFormVisible = ref(false) const dialogFormVisible = ref(false)
// //
const openDialog = () => { const openDialog = () => {
type.value = 'create' type.value = 'create'
dialogFormVisible.value = true dialogFormVisible.value = true
} }
// //
const closeDialog = () => { const closeDialog = () => {
dialogFormVisible.value = false dialogFormVisible.value = false
formData.value = { formData.value = {
title: '', title: '',
content: '', content: '',
userID: undefined, userID: undefined,
attachments: [], attachments: []
} }
} }
// //
const enterDialog = async () => { const enterDialog = async () => {
elFormRef.value?.validate( async (valid) => { elFormRef.value?.validate(async (valid) => {
if (!valid) return if (!valid) return
let res let res
switch (type.value) { switch (type.value) {
case 'create': case 'create':
res = await createInfo(formData.value) res = await createInfo(formData.value)
break break
case 'update': case 'update':
res = await updateInfo(formData.value) res = await updateInfo(formData.value)
break break
default: default:
res = await createInfo(formData.value) res = await createInfo(formData.value)
break break
} }
if (res.code === 0) { if (res.code === 0) {
ElMessage({ ElMessage({
type: 'success', type: 'success',
message: '创建/更改成功' message: '创建/更改成功'
}) })
closeDialog() closeDialog()
getTableData() getTableData()
} }
}) })
} }
const downloadFile = (url) => { const downloadFile = (url) => {
window.open(getUrl(url), '_blank') window.open(getUrl(url), '_blank')
} }
</script> </script>
<style> <style>
.file-list {
display: flex;
flex-wrap: wrap;
gap: 4px;
}
.file-list{ .fileBtn {
display: flex; margin-bottom: 10px;
flex-wrap: wrap; }
gap: 4px;
}
.fileBtn{
margin-bottom: 10px;
}
.fileBtn:last-child{
margin-bottom: 0;
}
.fileBtn:last-child {
margin-bottom: 0;
}
</style> </style>

View File

@ -27,4 +27,3 @@ export const sendEmail = (data) => {
data data
}) })
} }

View File

@ -1,6 +1,8 @@
<template> <template>
<div> <div>
<warning-bar title="需要提前配置email配置文件为防止不必要的垃圾邮件在线体验功能不开放此功能体验。" /> <warning-bar
title="需要提前配置email配置文件为防止不必要的垃圾邮件在线体验功能不开放此功能体验。"
/>
<div class="gva-form-box"> <div class="gva-form-box">
<el-form <el-form
ref="emailForm" ref="emailForm"
@ -15,10 +17,7 @@
<el-input v-model="form.subject" /> <el-input v-model="form.subject" />
</el-form-item> </el-form-item>
<el-form-item label="邮件内容"> <el-form-item label="邮件内容">
<el-input <el-input v-model="form.body" type="textarea" />
v-model="form.body"
type="textarea"
/>
</el-form-item> </el-form-item>
<el-form-item> <el-form-item>
<el-button @click="sendTestEmail">发送测试邮件</el-button> <el-button @click="sendTestEmail">发送测试邮件</el-button>
@ -27,37 +26,35 @@
</el-form> </el-form>
</div> </div>
</div> </div>
</template> </template>
<script setup> <script setup>
import WarningBar from '@/components/warningBar/warningBar.vue' import WarningBar from '@/components/warningBar/warningBar.vue'
import { emailTest } from '@/plugin/email/api/email.js' import { emailTest } from '@/plugin/email/api/email.js'
import { ElMessage } from 'element-plus' import { ElMessage } from 'element-plus'
import { reactive, ref } from 'vue' import { reactive, ref } from 'vue'
defineOptions({ defineOptions({
name: 'Email', name: 'Email'
}) })
const emailForm = ref(null) const emailForm = ref(null)
const form = reactive({ const form = reactive({
to: '', to: '',
subject: '', subject: '',
body: '', body: ''
}) })
const sendTestEmail = async() => { const sendTestEmail = async () => {
const res = await emailTest() const res = await emailTest()
if (res.code === 0) { if (res.code === 0) {
ElMessage.success('发送成功') ElMessage.success('发送成功')
}
} }
}
const sendEmail = async() => { const sendEmail = async () => {
const res = await emailTest() const res = await emailTest()
if (res.code === 0) { if (res.code === 0) {
ElMessage.success('发送成功,请查收') ElMessage.success('发送成功,请查收')
}
} }
}
</script> </script>

View File

@ -1,31 +1,32 @@
import { createRouter, createWebHashHistory } from 'vue-router' import { createRouter, createWebHashHistory } from 'vue-router'
const routes = [{ const routes = [
path: '/', {
redirect: '/login' path: '/',
}, redirect: '/login'
{
path: '/init',
name: 'Init',
component: () => import('@/view/init/index.vue')
},
{
path: '/login',
name: 'Login',
component: () => import('@/view/login/index.vue')
},
{
path: '/:catchAll(.*)',
meta: {
closeTab: true,
}, },
component: () => import('@/view/error/index.vue') {
} path: '/init',
name: 'Init',
component: () => import('@/view/init/index.vue')
},
{
path: '/login',
name: 'Login',
component: () => import('@/view/login/index.vue')
},
{
path: '/:catchAll(.*)',
meta: {
closeTab: true
},
component: () => import('@/view/error/index.vue')
}
] ]
const router = createRouter({ const router = createRouter({
history: createWebHashHistory(), history: createWebHashHistory(),
routes, routes
}) })
export default router export default router

View File

@ -1,24 +1,24 @@
@forward 'element-plus/theme-chalk/src/common/var.scss' with ( @forward 'element-plus/theme-chalk/src/common/var.scss' with (
$colors: ( $colors: (
'white': #ffffff, 'white': #ffffff,
'black': #000000, 'black': #000000,
'primary': ( 'primary': (
'base': #4d70ff, 'base': #4d70ff
), ),
'success': ( 'success': (
'base': #67c23a, 'base': #67c23a
), ),
'warning': ( 'warning': (
'base': #e6a23c, 'base': #e6a23c
), ),
'danger': ( 'danger': (
'base': #f56c6c, 'base': #f56c6c
), ),
'error': ( 'error': (
'base': #f56c6c, 'base': #f56c6c
), ),
'info': ( 'info': (
'base': #909399, 'base': #909399
), )
) )
); );

View File

@ -1,138 +1,136 @@
@use '@/style/main.scss'; @use '@/style/main.scss';
@use "@/style/reset"; @use '@/style/reset';
@tailwind base; @tailwind base;
@tailwind components; @tailwind components;
@tailwind utilities; @tailwind utilities;
.el-button { .el-button {
font-weight: 400; font-weight: 400;
border-radius: 2px; border-radius: 2px;
} }
.gva-pagination { .gva-pagination {
@apply flex justify-end; @apply flex justify-end;
.el-pagination__editor { .el-pagination__editor {
.el-input__inner { .el-input__inner {
@apply h-8; @apply h-8;
}
} }
}
.is-active { .is-active {
@apply rounded text-white; @apply rounded text-white;
background: var(--el-color-primary); background: var(--el-color-primary);
color: #ffffff !important; color: #ffffff !important;
} }
} }
.el-drawer__header {
.el-drawer__header{ margin-bottom: 0 !important;
margin-bottom: 0 !important; padding-top: 16px !important;
padding-top: 16px !important; padding-bottom: 16px !important;
padding-bottom: 16px !important; @apply border-0 border-b border-solid border-gray-200;
@apply border-0 border-b border-solid border-gray-200;
} }
.el-form--inline { .el-form--inline {
.el-form-item { .el-form-item {
& > .el-input, .el-cascader, .el-select, .el-date-editor, .el-autocomplete { & > .el-input,
@apply w-52; .el-cascader,
} .el-select,
.el-date-editor,
.el-autocomplete {
@apply w-52;
} }
}
} }
.el-dropdown { .el-dropdown {
@apply overflow-hidden @apply overflow-hidden;
} }
.el-table { .el-table {
tr{ tr {
th { th {
@apply dark:bg-slate-900; @apply dark:bg-slate-900;
.cell { .cell {
@apply leading-[36px] text-gray-700 dark:text-gray-200; @apply leading-[36px] text-gray-700 dark:text-gray-200;
} }
}
} }
.el-table__row { }
td { .el-table__row {
@apply dark:bg-slate-900; td {
.cell { @apply dark:bg-slate-900;
@apply leading-[32px] text-gray-600 dark:text-gray-300; .cell {
} @apply leading-[32px] text-gray-600 dark:text-gray-300;
} }
} }
tr{ }
th{ tr {
&.is-leaf { th {
@apply dark:bg-slate-900; &.is-leaf {
} @apply dark:bg-slate-900;
} }
} }
}
} }
// layout // layout
// table // table
.el-pagination { .el-pagination {
@apply mt-8; @apply mt-8;
.btn-prev, .btn-prev,
.btn-next { .btn-next {
@apply border border-solid border-gray-300 dark:border-gray-700 rounded; @apply border border-solid border-gray-300 dark:border-gray-700 rounded;
} }
.el-pager { .el-pager {
li { li {
@apply border border-solid border-gray-300 dark:border-gray-600 rounded text-gray-600 text-sm mx-1; @apply border border-solid border-gray-300 dark:border-gray-600 rounded text-gray-600 text-sm mx-1;
}
} }
}
} }
.el-menu{ .el-menu {
li{ li {
@apply my-1; @apply my-1;
} }
} }
.el-menu--vertical{ .el-menu--vertical {
.el-menu-item{ .el-menu-item {
border-radius: 2px; border-radius: 2px;
&.is-active{ &.is-active {
background-color: var(--el-color-primary) !important; background-color: var(--el-color-primary) !important;
color: #fff !important; color: #fff !important;
}
} }
}
} }
.el-sub-menu.el-sub-menu__hide-arrow {
.el-sub-menu.el-sub-menu__hide-arrow{ height: 44px;
height: 44px;
} }
.el-tabs__header {
.el-tabs__header{ margin: 0 0 1px !important;
margin: 0 0 1px !important;
} }
.el-sub-menu.is-active{ .el-sub-menu.is-active {
> .el-sub-menu__title{ > .el-sub-menu__title {
color: var(--el-color-primary) !important; color: var(--el-color-primary) !important;
} }
} }
.el-sub-menu__title.el-tooltip__trigger, .el-sub-menu__title.el-tooltip__trigger,
.el-menu-item .el-menu-tooltip__trigger{ .el-menu-item .el-menu-tooltip__trigger {
justify-content: center; justify-content: center;
} }
.el-menu--horizontal .el-menu .el-sub-menu__title{ .el-menu--horizontal .el-menu .el-sub-menu__title {
justify-content: flex-start; justify-content: flex-start;
} }
html.dark { html.dark {
/* 自定义深色背景颜色 */ /* 自定义深色背景颜色 */
--el-bg-color: rgb(30 ,41 ,59); --el-bg-color: rgb(30, 41, 59);
--el-bg-color-overlay: rgb(40 ,51 ,69); --el-bg-color-overlay: rgb(40, 51, 69);
--el-fill-color-light: rgb(15 ,23 ,42); --el-fill-color-light: rgb(15, 23, 42);
--el-fill-color : rgb(15 ,23 ,42); --el-fill-color: rgb(15, 23, 42);
} }

View File

@ -1,47 +1,47 @@
@font-face { @font-face {
font-family: 'gvaIcon'; font-family: 'gvaIcon';
src: url('data:font/ttf;charset=utf-8;base64,AAEAAAANAIAAAwBQRkZUTZJUyU8AAA14AAAAHEdERUYAKQARAAANWAAAAB5PUy8yPJpJTAAAAVgAAABgY21hcM0T0L4AAAHYAAABWmdhc3D//wADAAANUAAAAAhnbHlmRk3UvwAAA0wAAAbYaGVhZB/a5jgAAADcAAAANmhoZWEHngOFAAABFAAAACRobXR4DaoBrAAAAbgAAAAebG9jYQbMCGgAAAM0AAAAGG1heHABGgB+AAABOAAAACBuYW1lXoIBAgAACiQAAAKCcG9zdN15OnUAAAyoAAAAqAABAAAAAQAA+a916l8PPPUACwQAAAAAAN5YUSMAAAAA3lhRIwBL/8ADwAM1AAAACAACAAAAAAAAAAEAAAOA/4AAXAQAAAAAAAPAAAEAAAAAAAAAAAAAAAAAAAAEAAEAAAALAHIABQAAAAAAAgAAAAoACgAAAP8AAAAAAAAABAQAAZAABQAAAokCzAAAAI8CiQLMAAAB6wAyAQgAAAIABQMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUGZFZADA5mXmfQOA/4AAAAPcAIAAAAABAAAAAAAAAAAAAAAgAAEEAAAAAAAAAAQAAAAEAACLAIoAYAB1AHYASwBLAGAAAAAAAAMAAAADAAAAHAABAAAAAABUAAMAAQAAABwABAA4AAAACgAIAAIAAuZm5mrmduZ9//8AAOZl5mrmdeZ7//8ZnhmbGZEZjQABAAAAAAAAAAAAAAAAAQYAAAEAAAAAAAAAAQIAAAACAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEYAigEcAbgCUAK6AxoDbAACAIsAIANsAswAEQAjAAAlIicBJjQ3ATYeAQYHCQEeAQYhIicBJjQ3ATYeAQYHCQEeAQYDSw0J/qsLCwFVChsSAgr+xAE8CgIV/qkNCP6qCgoBVgkbEgIK/sUBOwoCFCAJATULGQsBNQoCExwI/uL+4ggbFAkBNQsZCwE1CgITHAj+4v7iCRoUAAAAAAIAigAgA2sCzAARACIAAAE0JwEmDgEWFwkBDgEWMjcBNiUBJg4BFhcJAQ4BFjI3ATY0AiAL/qsJHBECCQE8/sQJAhQZCQFVCwFA/qsKGxICCgE8/sQKAhUZCQFVCwF1DQsBNQoCExwI/uL+4gkaFAkBNQskATUKAhMcCP7i/uIJGhQJATULGQADAGD/wAOgAzUATABcAGwAAAE1NCcmJyYiBwYHBh0BDgEdARQWOwEyNj0BNCYrATU0NzY3NjIXFhcWHQEjIgYdARQWOwEGBwYHLgEjIgYUFjMyNjc2NzY3PgE9ATQmBRUUBisBIiY9ATQ2OwEyFgUUBisBIiY9ATQ2OwEyFhUDYDAvT1O+U08vMBslLB9VHi0tHiAoJkFDnENBJiggHi0tHhUPJC5SChwRHCQkHBEeCHJAMxAfKiX9kAYFVQUGBgVVBQYCVQYFVQUGBgVVBQYByQxgUlAuMDAuUFJgDAQqG6seLCweqx4tCk5DQScnJydBQ04KLR6rHiwrGiAGDxElNiUSEAc1KkUBKx6rGyhFqwQGBgSrBQYGsAQGBgSrBQYGBQAABAB1//UDjQMLABsANwBSAHEAABMyNj0BFxYyNjQvATMyNjQmKwEiBwYHBh0BFBYFIgYdAScmIgYUHwEjIgYUFjsBMjc2NzY9ATYmJQc1NCYiBh0BFBcWFxY7ATI2NCYrATc2NCYGATQ1FSYnJisBIgYUFjsBBwYUFjI/ARUUFjI2PQEnJpUNE7wJHRMKvIcMFBQM1ggCDAgCFALiDRPJCRoTCcmJDBQUDNYIAg8CAwES/gbJExkUAggKBAbWDBQUDInJCRMXAgEHCwQG2AwUFAyJvAkSHgi8ExoTAgEB9RQMibwIEhkKvBMZFAIGDAQI1gwU6hQMickJExoJyRMZFAIICgQG2AwUIsmHDBQUDNYIAg8CAxQZE8kKGRMBAcABAQIOAwMUGRO8ChkTCbyHDBQUDNYFBAAABAB2//cDjgMMABoANQBRAG0AAAEjIgYUFjsBMjc2NzY9ATQmIgYdAScmIgYUFwEzMjY0JisBIgcGBwYdARQWMjY9ARcWMjY0JyUmJyYrASIGFBY7AQcGFBYyPwEVFBYyNj0BLgE3FhcWOwEyNjQmKwE3NjQmIg8BNTQmIgYdAR4BATqJDRMTDdUJAg8CAhMaE7cKGRQKAjeJDRMTDdUJAg8CAhMaE8gJHhIK/i8HCgQH1w0TEw2JyQoTHQnIFBkTAQKoBwoEBtYNExMNibwKFBkKvBMZFAICAhoUGRMCBwoEBtYNExMNib4KExoK/iAUGRMCBwoEB9UNExMNickIEhkK8w8CAhMZFMgKGRMJyYkNExMN1QIJzQ8CAhMZFLsKGhMKvIkNExMN1QMIAAAAAAUAS//LA7UDNQAUACkAKgA3AEQAAAEiBwYHBhQXFhcWMjc2NzY0JyYnJgMiJyYnJjQ3Njc2MhcWFxYUBwYHBgMjFB4BMj4BNC4BIg4BFyIGHQEUFjI2PQE0JgIAd2ZiOzs7O2Jm7mZiOzs7O2Jmd2VXVDIzMzJUV8pXVDIzMzJUV2UrDBQWFAwMFBYUDCsNExMaExMDNTs7YmbuZmI7Ozs7YmbuZmI7O/zWMzJUV8pXVDIzMzJUV8pXVDIzAjULFAwMFBYUDAwUgBQM6w0TEw3rDBQAAQBL/+ADwAMgAD0AAAEmBg8BLgEjIgcGBwYUFxYXFjMyPgE3Ni4BBgcOAiMiJyYnJjQ3Njc2MzIeARcnJg4BFh8BMj8BNj8BNCYDpgwXAxc5yXZyY184Ojo4X2NyWaB4HgULGhcFGWaJS2FUUTAwMTBRU2FIhGQbgA0WBw4NwgUIBAwDMQ0CsQMODFhmeDk3XmHiYV43OUV9UQ0XCQsMRWo6MC9PUr9TTy8wNmNBJQMOGhYDMwMBCAu6DRYAAAAAAgBg/8YDugMiAB4AMwAABSc+ATU0JyYnJiIHBgcGFBcWFxYzMjc2NxcWMjc2JiUiJyYnJjQ3Njc2MhcWFxYUBwYHBgOxviouNDFVV8lXVTIzMzJVV2RDPzwzvgkeCAcB/hxUSEYpKiopRkioSEYpKyspRkgCvjB9RGRYVDIzNDJVWMlXVTE0GBYqvgkJChuBKylGSKhIRikqKilGSKhIRikrAAAAABIA3gABAAAAAAAAABMAKAABAAAAAAABAAgATgABAAAAAAACAAcAZwABAAAAAAADAAgAgQABAAAAAAAEAAgAnAABAAAAAAAFAAsAvQABAAAAAAAGAAgA2wABAAAAAAAKACsBPAABAAAAAAALABMBkAADAAEECQAAACYAAAADAAEECQABABAAPAADAAEECQACAA4AVwADAAEECQADABAAbwADAAEECQAEABAAigADAAEECQAFABYApQADAAEECQAGABAAyQADAAEECQAKAFYA5AADAAEECQALACYBaABDAHIAZQBhAHQAZQBkACAAYgB5ACAAaQBjAG8AbgBmAG8AbgB0AABDcmVhdGVkIGJ5IGljb25mb250AABpAGMAbwBuAGYAbwBuAHQAAGljb25mb250AABSAGUAZwB1AGwAYQByAABSZWd1bGFyAABpAGMAbwBuAGYAbwBuAHQAAGljb25mb250AABpAGMAbwBuAGYAbwBuAHQAAGljb25mb250AABWAGUAcgBzAGkAbwBuACAAMQAuADAAAFZlcnNpb24gMS4wAABpAGMAbwBuAGYAbwBuAHQAAGljb25mb250AABHAGUAbgBlAHIAYQB0AGUAZAAgAGIAeQAgAHMAdgBnADIAdAB0AGYAIABmAHIAbwBtACAARgBvAG4AdABlAGwAbABvACAAcAByAG8AagBlAGMAdAAuAABHZW5lcmF0ZWQgYnkgc3ZnMnR0ZiBmcm9tIEZvbnRlbGxvIHByb2plY3QuAABoAHQAdABwADoALwAvAGYAbwBuAHQAZQBsAGwAbwAuAGMAbwBtAABodHRwOi8vZm9udGVsbG8uY29tAAAAAAIAAAAAAAAACgAAAAAAAQAAAAAAAAAAAAAAAAAAAAAACwAAAAEAAgECAQMBBAEFAQYBBwEIAQkRYXJyb3ctZG91YmxlLWxlZnQSYXJyb3ctZG91YmxlLXJpZ2h0EGN1c3RvbWVyLXNlcnZpY2URZnVsbHNjcmVlbi1leHBhbmQRZnVsbHNjcmVlbi1zaHJpbmsGcHJvbXB0B3JlZnJlc2gGc2VhcmNoAAAAAf//AAIAAQAAAAwAAAAWAAAAAgABAAMACgABAAQAAAACAAAAAAAAAAEAAAAA1aQnCAAAAADeWFEjAAAAAN5YUSM=') format('truetype'); src: url('data:font/ttf;charset=utf-8;base64,AAEAAAANAIAAAwBQRkZUTZJUyU8AAA14AAAAHEdERUYAKQARAAANWAAAAB5PUy8yPJpJTAAAAVgAAABgY21hcM0T0L4AAAHYAAABWmdhc3D//wADAAANUAAAAAhnbHlmRk3UvwAAA0wAAAbYaGVhZB/a5jgAAADcAAAANmhoZWEHngOFAAABFAAAACRobXR4DaoBrAAAAbgAAAAebG9jYQbMCGgAAAM0AAAAGG1heHABGgB+AAABOAAAACBuYW1lXoIBAgAACiQAAAKCcG9zdN15OnUAAAyoAAAAqAABAAAAAQAA+a916l8PPPUACwQAAAAAAN5YUSMAAAAA3lhRIwBL/8ADwAM1AAAACAACAAAAAAAAAAEAAAOA/4AAXAQAAAAAAAPAAAEAAAAAAAAAAAAAAAAAAAAEAAEAAAALAHIABQAAAAAAAgAAAAoACgAAAP8AAAAAAAAABAQAAZAABQAAAokCzAAAAI8CiQLMAAAB6wAyAQgAAAIABQMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUGZFZADA5mXmfQOA/4AAAAPcAIAAAAABAAAAAAAAAAAAAAAgAAEEAAAAAAAAAAQAAAAEAACLAIoAYAB1AHYASwBLAGAAAAAAAAMAAAADAAAAHAABAAAAAABUAAMAAQAAABwABAA4AAAACgAIAAIAAuZm5mrmduZ9//8AAOZl5mrmdeZ7//8ZnhmbGZEZjQABAAAAAAAAAAAAAAAAAQYAAAEAAAAAAAAAAQIAAAACAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEYAigEcAbgCUAK6AxoDbAACAIsAIANsAswAEQAjAAAlIicBJjQ3ATYeAQYHCQEeAQYhIicBJjQ3ATYeAQYHCQEeAQYDSw0J/qsLCwFVChsSAgr+xAE8CgIV/qkNCP6qCgoBVgkbEgIK/sUBOwoCFCAJATULGQsBNQoCExwI/uL+4ggbFAkBNQsZCwE1CgITHAj+4v7iCRoUAAAAAAIAigAgA2sCzAARACIAAAE0JwEmDgEWFwkBDgEWMjcBNiUBJg4BFhcJAQ4BFjI3ATY0AiAL/qsJHBECCQE8/sQJAhQZCQFVCwFA/qsKGxICCgE8/sQKAhUZCQFVCwF1DQsBNQoCExwI/uL+4gkaFAkBNQskATUKAhMcCP7i/uIJGhQJATULGQADAGD/wAOgAzUATABcAGwAAAE1NCcmJyYiBwYHBh0BDgEdARQWOwEyNj0BNCYrATU0NzY3NjIXFhcWHQEjIgYdARQWOwEGBwYHLgEjIgYUFjMyNjc2NzY3PgE9ATQmBRUUBisBIiY9ATQ2OwEyFgUUBisBIiY9ATQ2OwEyFhUDYDAvT1O+U08vMBslLB9VHi0tHiAoJkFDnENBJiggHi0tHhUPJC5SChwRHCQkHBEeCHJAMxAfKiX9kAYFVQUGBgVVBQYCVQYFVQUGBgVVBQYByQxgUlAuMDAuUFJgDAQqG6seLCweqx4tCk5DQScnJydBQ04KLR6rHiwrGiAGDxElNiUSEAc1KkUBKx6rGyhFqwQGBgSrBQYGsAQGBgSrBQYGBQAABAB1//UDjQMLABsANwBSAHEAABMyNj0BFxYyNjQvATMyNjQmKwEiBwYHBh0BFBYFIgYdAScmIgYUHwEjIgYUFjsBMjc2NzY9ATYmJQc1NCYiBh0BFBcWFxY7ATI2NCYrATc2NCYGATQ1FSYnJisBIgYUFjsBBwYUFjI/ARUUFjI2PQEnJpUNE7wJHRMKvIcMFBQM1ggCDAgCFALiDRPJCRoTCcmJDBQUDNYIAg8CAwES/gbJExkUAggKBAbWDBQUDInJCRMXAgEHCwQG2AwUFAyJvAkSHgi8ExoTAgEB9RQMibwIEhkKvBMZFAIGDAQI1gwU6hQMickJExoJyRMZFAIICgQG2AwUIsmHDBQUDNYIAg8CAxQZE8kKGRMBAcABAQIOAwMUGRO8ChkTCbyHDBQUDNYFBAAABAB2//cDjgMMABoANQBRAG0AAAEjIgYUFjsBMjc2NzY9ATQmIgYdAScmIgYUFwEzMjY0JisBIgcGBwYdARQWMjY9ARcWMjY0JyUmJyYrASIGFBY7AQcGFBYyPwEVFBYyNj0BLgE3FhcWOwEyNjQmKwE3NjQmIg8BNTQmIgYdAR4BATqJDRMTDdUJAg8CAhMaE7cKGRQKAjeJDRMTDdUJAg8CAhMaE8gJHhIK/i8HCgQH1w0TEw2JyQoTHQnIFBkTAQKoBwoEBtYNExMNibwKFBkKvBMZFAICAhoUGRMCBwoEBtYNExMNib4KExoK/iAUGRMCBwoEB9UNExMNickIEhkK8w8CAhMZFMgKGRMJyYkNExMN1QIJzQ8CAhMZFLsKGhMKvIkNExMN1QMIAAAAAAUAS//LA7UDNQAUACkAKgA3AEQAAAEiBwYHBhQXFhcWMjc2NzY0JyYnJgMiJyYnJjQ3Njc2MhcWFxYUBwYHBgMjFB4BMj4BNC4BIg4BFyIGHQEUFjI2PQE0JgIAd2ZiOzs7O2Jm7mZiOzs7O2Jmd2VXVDIzMzJUV8pXVDIzMzJUV2UrDBQWFAwMFBYUDCsNExMaExMDNTs7YmbuZmI7Ozs7YmbuZmI7O/zWMzJUV8pXVDIzMzJUV8pXVDIzAjULFAwMFBYUDAwUgBQM6w0TEw3rDBQAAQBL/+ADwAMgAD0AAAEmBg8BLgEjIgcGBwYUFxYXFjMyPgE3Ni4BBgcOAiMiJyYnJjQ3Njc2MzIeARcnJg4BFh8BMj8BNj8BNCYDpgwXAxc5yXZyY184Ojo4X2NyWaB4HgULGhcFGWaJS2FUUTAwMTBRU2FIhGQbgA0WBw4NwgUIBAwDMQ0CsQMODFhmeDk3XmHiYV43OUV9UQ0XCQsMRWo6MC9PUr9TTy8wNmNBJQMOGhYDMwMBCAu6DRYAAAAAAgBg/8YDugMiAB4AMwAABSc+ATU0JyYnJiIHBgcGFBcWFxYzMjc2NxcWMjc2JiUiJyYnJjQ3Njc2MhcWFxYUBwYHBgOxviouNDFVV8lXVTIzMzJVV2RDPzwzvgkeCAcB/hxUSEYpKiopRkioSEYpKyspRkgCvjB9RGRYVDIzNDJVWMlXVTE0GBYqvgkJChuBKylGSKhIRikqKilGSKhIRikrAAAAABIA3gABAAAAAAAAABMAKAABAAAAAAABAAgATgABAAAAAAACAAcAZwABAAAAAAADAAgAgQABAAAAAAAEAAgAnAABAAAAAAAFAAsAvQABAAAAAAAGAAgA2wABAAAAAAAKACsBPAABAAAAAAALABMBkAADAAEECQAAACYAAAADAAEECQABABAAPAADAAEECQACAA4AVwADAAEECQADABAAbwADAAEECQAEABAAigADAAEECQAFABYApQADAAEECQAGABAAyQADAAEECQAKAFYA5AADAAEECQALACYBaABDAHIAZQBhAHQAZQBkACAAYgB5ACAAaQBjAG8AbgBmAG8AbgB0AABDcmVhdGVkIGJ5IGljb25mb250AABpAGMAbwBuAGYAbwBuAHQAAGljb25mb250AABSAGUAZwB1AGwAYQByAABSZWd1bGFyAABpAGMAbwBuAGYAbwBuAHQAAGljb25mb250AABpAGMAbwBuAGYAbwBuAHQAAGljb25mb250AABWAGUAcgBzAGkAbwBuACAAMQAuADAAAFZlcnNpb24gMS4wAABpAGMAbwBuAGYAbwBuAHQAAGljb25mb250AABHAGUAbgBlAHIAYQB0AGUAZAAgAGIAeQAgAHMAdgBnADIAdAB0AGYAIABmAHIAbwBtACAARgBvAG4AdABlAGwAbABvACAAcAByAG8AagBlAGMAdAAuAABHZW5lcmF0ZWQgYnkgc3ZnMnR0ZiBmcm9tIEZvbnRlbGxvIHByb2plY3QuAABoAHQAdABwADoALwAvAGYAbwBuAHQAZQBsAGwAbwAuAGMAbwBtAABodHRwOi8vZm9udGVsbG8uY29tAAAAAAIAAAAAAAAACgAAAAAAAQAAAAAAAAAAAAAAAAAAAAAACwAAAAEAAgECAQMBBAEFAQYBBwEIAQkRYXJyb3ctZG91YmxlLWxlZnQSYXJyb3ctZG91YmxlLXJpZ2h0EGN1c3RvbWVyLXNlcnZpY2URZnVsbHNjcmVlbi1leHBhbmQRZnVsbHNjcmVlbi1zaHJpbmsGcHJvbXB0B3JlZnJlc2gGc2VhcmNoAAAAAf//AAIAAQAAAAwAAAAWAAAAAgABAAMACgABAAQAAAACAAAAAAAAAAEAAAAA1aQnCAAAAADeWFEjAAAAAN5YUSM=')
font-weight: 600; format('truetype');
font-style: normal; font-weight: 600;
font-display: swap; font-style: normal;
font-display: swap;
} }
.gvaIcon { .gvaIcon {
font-family: "gvaIcon" !important; font-family: 'gvaIcon' !important;
font-size: 16px; font-size: 16px;
font-style: normal; font-style: normal;
font-weight: 800; font-weight: 800;
-webkit-font-smoothing: antialiased; -webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale; -moz-osx-font-smoothing: grayscale;
} }
.gvaIcon-arrow-double-left:before { .gvaIcon-arrow-double-left:before {
content: "\e665"; content: '\e665';
} }
.gvaIcon-arrow-double-right:before { .gvaIcon-arrow-double-right:before {
content: "\e666"; content: '\e666';
} }
.gvaIcon-fullscreen-shrink:before { .gvaIcon-fullscreen-shrink:before {
content: "\e676"; content: '\e676';
} }
.gvaIcon-customer-service:before { .gvaIcon-customer-service:before {
content: "\e66a"; content: '\e66a';
} }
.gvaIcon-fullscreen-expand:before { .gvaIcon-fullscreen-expand:before {
content: "\e675"; content: '\e675';
} }
.gvaIcon-prompt:before { .gvaIcon-prompt:before {
content: "\e67b"; content: '\e67b';
} }
.gvaIcon-refresh:before { .gvaIcon-refresh:before {
content: "\e67c"; content: '\e67c';
} }
.gvaIcon-search:before { .gvaIcon-search:before {
content: "\e67d"; content: '\e67d';
} }

View File

@ -1,53 +1,50 @@
@use '@/style/iconfont.css'; @use '@/style/iconfont.css';
.html-grey{ .html-grey {
filter: grayscale(100%); filter: grayscale(100%);
} }
.html-weakenss{ .html-weakenss {
filter: invert(80%); filter: invert(80%);
} }
.gva-table-box { .gva-table-box {
@apply p-4 bg-white text-slate-700 dark:text-slate-400 dark:bg-slate-900 rounded my-2; @apply p-4 bg-white text-slate-700 dark:text-slate-400 dark:bg-slate-900 rounded my-2;
.el-table{ .el-table {
@apply border-x border-t border-b-0 rounded border-table-border border-solid -mx-[1px]; @apply border-x border-t border-b-0 rounded border-table-border border-solid -mx-[1px];
} }
} }
.gva-btn-list { .gva-btn-list {
@apply mb-3 flex items-center; @apply mb-3 flex items-center;
} }
#nprogress .bar { #nprogress .bar {
background: #29d !important; background: #29d !important;
} }
.gva-customer-icon{ .gva-customer-icon {
@apply w-4 h-4; @apply w-4 h-4;
} }
::-webkit-scrollbar { ::-webkit-scrollbar {
@apply hidden; @apply hidden;
} }
.gva-search-box { .gva-search-box {
@apply p-4 bg-white text-slate-700 dark:text-slate-400 dark:bg-slate-900 rounded my-2; @apply p-4 bg-white text-slate-700 dark:text-slate-400 dark:bg-slate-900 rounded my-2;
} }
.gva-form-box { .gva-form-box {
@apply p-4 bg-white text-slate-700 dark:text-slate-400 dark:bg-slate-900 rounded my-2; @apply p-4 bg-white text-slate-700 dark:text-slate-400 dark:bg-slate-900 rounded my-2;
} }
.el-tree--highlight-current .el-tree-node.is-current > .el-tree-node__content{ .el-tree--highlight-current .el-tree-node.is-current > .el-tree-node__content {
background: var(--el-color-primary-bg) !important; background: var(--el-color-primary-bg) !important;
} }
.el-dropdown{ .el-dropdown {
outline: none;
* {
outline: none; outline: none;
*{ }
outline: none;
}
} }

View File

@ -1,7 +1,6 @@
/* Document /* Document
========================================================================== */ ========================================================================== */
/** /**
* 1. Correct the line height in all browsers. * 1. Correct the line height in all browsers.
* 2. Prevent adjustments of font size after orientation changes in iOS. * 2. Prevent adjustments of font size after orientation changes in iOS.
@ -14,11 +13,9 @@ html {
/* 2 */ /* 2 */
} }
/* Sections /* Sections
========================================================================== */ ========================================================================== */
/** /**
* Remove the margin in all browsers. * Remove the margin in all browsers.
*/ */
@ -27,7 +24,6 @@ body {
margin: 0; margin: 0;
} }
/** /**
* Render the `main` element consistently in IE. * Render the `main` element consistently in IE.
*/ */
@ -36,7 +32,6 @@ main {
display: block; display: block;
} }
/** /**
* Correct the font size and margin on `h1` elements within `section` and * Correct the font size and margin on `h1` elements within `section` and
* `article` contexts in Chrome, Firefox, and Safari. * `article` contexts in Chrome, Firefox, and Safari.
@ -47,11 +42,9 @@ h1 {
margin: 0.67em 0; margin: 0.67em 0;
} }
/* Grouping content /* Grouping content
========================================================================== */ ========================================================================== */
/** /**
* 1. Add the correct box sizing in Firefox. * 1. Add the correct box sizing in Firefox.
* 2. Show the overflow in Edge and IE. * 2. Show the overflow in Edge and IE.
@ -66,7 +59,6 @@ hr {
/* 2 */ /* 2 */
} }
/** /**
* 1. Correct the inheritance and scaling of font size in all browsers. * 1. Correct the inheritance and scaling of font size in all browsers.
* 2. Correct the odd `em` font sizing in all browsers. * 2. Correct the odd `em` font sizing in all browsers.
@ -79,11 +71,9 @@ pre {
/* 2 */ /* 2 */
} }
/* Text-level semantics /* Text-level semantics
========================================================================== */ ========================================================================== */
/** /**
* Remove the gray background on active links in IE 10. * Remove the gray background on active links in IE 10.
*/ */
@ -92,7 +82,6 @@ a {
background-color: transparent; background-color: transparent;
} }
/** /**
* 1. Remove the bottom border in Chrome 57- * 1. Remove the bottom border in Chrome 57-
* 2. Add the correct text decoration in Chrome, Edge, IE, Opera, and Safari. * 2. Add the correct text decoration in Chrome, Edge, IE, Opera, and Safari.
@ -107,7 +96,6 @@ abbr[title] {
/* 2 */ /* 2 */
} }
/** /**
* Add the correct font weight in Chrome, Edge, and Safari. * Add the correct font weight in Chrome, Edge, and Safari.
*/ */
@ -117,7 +105,6 @@ strong {
font-weight: bolder; font-weight: bolder;
} }
/** /**
* 1. Correct the inheritance and scaling of font size in all browsers. * 1. Correct the inheritance and scaling of font size in all browsers.
* 2. Correct the odd `em` font sizing in all browsers. * 2. Correct the odd `em` font sizing in all browsers.
@ -132,7 +119,6 @@ samp {
/* 2 */ /* 2 */
} }
/** /**
* Add the correct font size in all browsers. * Add the correct font size in all browsers.
*/ */
@ -141,7 +127,6 @@ small {
font-size: 80%; font-size: 80%;
} }
/** /**
* Prevent `sub` and `sup` elements from affecting the line height in * Prevent `sub` and `sup` elements from affecting the line height in
* all browsers. * all browsers.
@ -163,11 +148,9 @@ sup {
top: -0.5em; top: -0.5em;
} }
/* Embedded content /* Embedded content
========================================================================== */ ========================================================================== */
/** /**
* Remove the border on images inside links in IE 10. * Remove the border on images inside links in IE 10.
*/ */
@ -176,11 +159,9 @@ img {
border-style: none; border-style: none;
} }
/* Forms /* Forms
========================================================================== */ ========================================================================== */
/** /**
* 1. Change the font styles in all browsers. * 1. Change the font styles in all browsers.
* 2. Remove the margin in Firefox and Safari. * 2. Remove the margin in Firefox and Safari.
@ -201,7 +182,6 @@ textarea {
/* 2 */ /* 2 */
} }
/** /**
* Show the overflow in IE. * Show the overflow in IE.
* 1. Show the overflow in Edge. * 1. Show the overflow in Edge.
@ -213,7 +193,6 @@ input {
overflow: visible; overflow: visible;
} }
/** /**
* Remove the inheritance of text transform in Edge, Firefox, and IE. * Remove the inheritance of text transform in Edge, Firefox, and IE.
* 1. Remove the inheritance of text transform in Firefox. * 1. Remove the inheritance of text transform in Firefox.
@ -225,44 +204,40 @@ select {
text-transform: none; text-transform: none;
} }
/** /**
* Correct the inability to style clickable types in iOS and Safari. * Correct the inability to style clickable types in iOS and Safari.
*/ */
button, button,
[type="button"], [type='button'],
[type="reset"], [type='reset'],
[type="submit"] { [type='submit'] {
-webkit-appearance: button; -webkit-appearance: button;
} }
/** /**
* Remove the inner border and padding in Firefox. * Remove the inner border and padding in Firefox.
*/ */
button::-moz-focus-inner, button::-moz-focus-inner,
[type="button"]::-moz-focus-inner, [type='button']::-moz-focus-inner,
[type="reset"]::-moz-focus-inner, [type='reset']::-moz-focus-inner,
[type="submit"]::-moz-focus-inner { [type='submit']::-moz-focus-inner {
border-style: none; border-style: none;
padding: 0; padding: 0;
} }
/** /**
* Restore the focus styles unset by the previous rule. * Restore the focus styles unset by the previous rule.
*/ */
button:-moz-focusring, button:-moz-focusring,
[type="button"]:-moz-focusring, [type='button']:-moz-focusring,
[type="reset"]:-moz-focusring, [type='reset']:-moz-focusring,
[type="submit"]:-moz-focusring { [type='submit']:-moz-focusring {
outline: 1px dotted ButtonText; outline: 1px dotted ButtonText;
} }
/** /**
* Correct the padding in Firefox. * Correct the padding in Firefox.
*/ */
@ -271,7 +246,6 @@ fieldset {
padding: 0.35em 0.75em 0.625em; padding: 0.35em 0.75em 0.625em;
} }
/** /**
* 1. Correct the text wrapping in Edge and IE. * 1. Correct the text wrapping in Edge and IE.
* 2. Correct the color inheritance from `fieldset` elements in IE. * 2. Correct the color inheritance from `fieldset` elements in IE.
@ -294,7 +268,6 @@ legend {
/* 1 */ /* 1 */
} }
/** /**
* Add the correct vertical alignment in Chrome, Firefox, and Opera. * Add the correct vertical alignment in Chrome, Firefox, and Opera.
*/ */
@ -303,7 +276,6 @@ progress {
vertical-align: baseline; vertical-align: baseline;
} }
/** /**
* Remove the default vertical scrollbar in IE 10+. * Remove the default vertical scrollbar in IE 10+.
*/ */
@ -312,53 +284,48 @@ textarea {
overflow: auto; overflow: auto;
} }
/** /**
* 1. Add the correct box sizing in IE 10. * 1. Add the correct box sizing in IE 10.
* 2. Remove the padding in IE 10. * 2. Remove the padding in IE 10.
*/ */
[type="checkbox"], [type='checkbox'],
[type="radio"] { [type='radio'] {
box-sizing: border-box; box-sizing: border-box;
/* 1 */ /* 1 */
padding: 0; padding: 0;
/* 2 */ /* 2 */
} }
/** /**
* Correct the cursor style of increment and decrement buttons in Chrome. * Correct the cursor style of increment and decrement buttons in Chrome.
*/ */
[type="number"]::-webkit-inner-spin-button, [type='number']::-webkit-inner-spin-button,
[type="number"]::-webkit-outer-spin-button { [type='number']::-webkit-outer-spin-button {
height: auto; height: auto;
} }
/** /**
* 1. Correct the odd appearance in Chrome and Safari. * 1. Correct the odd appearance in Chrome and Safari.
* 2. Correct the outline style in Safari. * 2. Correct the outline style in Safari.
*/ */
[type="search"] { [type='search'] {
-webkit-appearance: textfield; -webkit-appearance: textfield;
/* 1 */ /* 1 */
outline-offset: -2px; outline-offset: -2px;
/* 2 */ /* 2 */
} }
/** /**
* Remove the inner padding in Chrome and Safari on macOS. * Remove the inner padding in Chrome and Safari on macOS.
*/ */
[type="search"]::-webkit-search-decoration { [type='search']::-webkit-search-decoration {
-webkit-appearance: none; -webkit-appearance: none;
} }
/** /**
* 1. Correct the inability to style clickable types in iOS and Safari. * 1. Correct the inability to style clickable types in iOS and Safari.
* 2. Change font properties to `inherit` in Safari. * 2. Change font properties to `inherit` in Safari.
@ -371,11 +338,9 @@ textarea {
/* 2 */ /* 2 */
} }
/* Interactive /* Interactive
========================================================================== */ ========================================================================== */
/* /*
* Add the correct display in Edge, IE 10+, and Firefox. * Add the correct display in Edge, IE 10+, and Firefox.
*/ */
@ -384,7 +349,6 @@ details {
display: block; display: block;
} }
/* /*
* Add the correct display in all browsers. * Add the correct display in all browsers.
*/ */
@ -393,11 +357,9 @@ summary {
display: list-item; display: list-item;
} }
/* Misc /* Misc
========================================================================== */ ========================================================================== */
/** /**
* Add the correct display in IE 10+. * Add the correct display in IE 10+.
*/ */
@ -406,7 +368,6 @@ template {
display: none; display: none;
} }
/** /**
* Add the correct display in IE 10. * Add the correct display in IE 10.
*/ */
@ -433,7 +394,8 @@ table,
th, th,
td { td {
border: none; border: none;
font-family: "Helvetica Neue", Helvetica, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "微软雅黑", Arial, sans-serif; font-family: 'Helvetica Neue', Helvetica, 'PingFang SC', 'Hiragino Sans GB',
'Microsoft YaHei', '微软雅黑', Arial, sans-serif;
font-size: 14px; font-size: 14px;
margin: 0px; margin: 0px;
padding: 0px; padding: 0px;
@ -486,8 +448,8 @@ input::-moz-placeholder {
color: #ccc; color: #ccc;
} }
input[type=submit], input[type='submit'],
input[type=button] { input[type='button'] {
cursor: pointer; cursor: pointer;
} }

View File

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

View File

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

View File

@ -3,7 +3,7 @@
// 年(y)可以用 1-4 个占位符,毫秒(S)只能用 1 个占位符(是 1-3 位的数字) // 年(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-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 // (new Date()).Format("yyyy-M-d h:m:s.S") ==> 2006-7-2 8:9:4.18
Date.prototype.Format = function(fmt) { Date.prototype.Format = function (fmt) {
const o = { const o = {
'M+': this.getMonth() + 1, // 月份 'M+': this.getMonth() + 1, // 月份
'd+': this.getDate(), // 日 'd+': this.getDate(), // 日
@ -11,11 +11,21 @@ Date.prototype.Format = function(fmt) {
'm+': this.getMinutes(), // 分 'm+': this.getMinutes(), // 分
's+': this.getSeconds(), // 秒 's+': this.getSeconds(), // 秒
'q+': Math.floor((this.getMonth() + 3) / 3), // 季度 'q+': Math.floor((this.getMonth() + 3) / 3), // 季度
'S': this.getMilliseconds() // 毫秒 S: this.getMilliseconds() // 毫秒
}
if (/(y+)/.test(fmt)) {
fmt = fmt.replace(
RegExp.$1,
(this.getFullYear() + '').substr(4 - RegExp.$1.length)
)
} }
if (/(y+)/.test(fmt)) { fmt = fmt.replace(RegExp.$1, (this.getFullYear() + '').substr(4 - RegExp.$1.length)) }
for (const k in o) { for (const k in o) {
if (new RegExp('(' + k + ')').test(fmt)) { fmt = fmt.replace(RegExp.$1, (RegExp.$1.length === 1) ? (o[k]) : (('00' + o[k]).substr(('' + o[k]).length))) } if (new RegExp('(' + k + ')').test(fmt)) {
fmt = fmt.replace(
RegExp.$1,
RegExp.$1.length === 1 ? o[k] : ('00' + o[k]).substr(('' + o[k]).length)
)
}
} }
return fmt return fmt
} }

View File

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

View File

@ -1,7 +1,8 @@
export const downloadImage = (imgsrc, name) => { // 下载图片地址和图片名 export const downloadImage = (imgsrc, name) => {
// 下载图片地址和图片名
var image = new Image() var image = new Image()
image.setAttribute('crossOrigin', 'anonymous') image.setAttribute('crossOrigin', 'anonymous')
image.onload = function() { image.onload = function () {
var canvas = document.createElement('canvas') var canvas = document.createElement('canvas')
canvas.width = image.width canvas.width = image.width
canvas.height = image.height canvas.height = image.height

View File

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

View File

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

View File

@ -9,7 +9,7 @@ export default class ImageCompress {
// 压缩 // 压缩
const fileType = this.file.type const fileType = this.file.type
const fileSize = this.file.size / 1024 const fileSize = this.file.size / 1024
return new Promise(resolve => { return new Promise((resolve) => {
const reader = new FileReader() const reader = new FileReader()
reader.readAsDataURL(this.file) reader.readAsDataURL(this.file)
reader.onload = () => { reader.onload = () => {
@ -26,7 +26,7 @@ export default class ImageCompress {
ctx.clearRect(0, 0, canvas.width, canvas.height) ctx.clearRect(0, 0, canvas.width, canvas.height)
ctx.drawImage(img, 0, 0, canvas.width, canvas.height) ctx.drawImage(img, 0, 0, canvas.width, canvas.height)
const newImgData = canvas.toDataURL(fileType, 0.90) const newImgData = canvas.toDataURL(fileType, 0.9)
// 压缩宽高后的图像大小 // 压缩宽高后的图像大小
const newImgSize = this.fileSizeKB(newImgData) const newImgSize = this.fileSizeKB(newImgData)
@ -69,7 +69,7 @@ export default class ImageCompress {
fileSizeKB(dataURL) { fileSizeKB(dataURL) {
let sizeKB = 0 let sizeKB = 0
sizeKB = Math.round((dataURL.split(',')[1].length * 3 / 4) / 1024) sizeKB = Math.round((dataURL.split(',')[1].length * 3) / 4 / 1024)
return sizeKB return sizeKB
} }
@ -93,21 +93,30 @@ export default class ImageCompress {
const path = import.meta.env.VITE_FILE_API const path = import.meta.env.VITE_FILE_API
export const getUrl = (url) => { export const getUrl = (url) => {
if (url && url.slice(0, 4) !== 'http'){ if (url && url.slice(0, 4) !== 'http') {
if (path === "/"){ if (path === '/') {
return url return url
} }
if (url.slice(0, 1) === "/"){ if (url.slice(0, 1) === '/') {
return path + url return path + url
} }
return path + "/" + url return path + '/' + url
}else{ } else {
return url return url
} }
} }
export const isVideoExt = (url) => url.endsWith('.mp4') || url.endsWith('.mov') || url.endsWith('.webm') || url.endsWith('.ogg'); export const isVideoExt = (url) =>
url.endsWith('.mp4') ||
url.endsWith('.mov') ||
url.endsWith('.webm') ||
url.endsWith('.ogg')
export const isVideoMime = (type) => type == 'video/mp4' || type == 'video/webm' || type == 'video/ogg'; export const isVideoMime = (type) =>
type == 'video/mp4' || type == 'video/webm' || type == 'video/ogg'
export const isImageMime = (type) => type == 'image/jpeg' || type == 'image/png' || type == 'image/webp' || type == 'image/svg+xml'; export const isImageMime = (type) =>
type == 'image/jpeg' ||
type == 'image/png' ||
type == 'image/webp' ||
type == 'image/svg+xml'

View File

@ -11,9 +11,11 @@ const service = axios.create({
let activeAxios = 0 let activeAxios = 0
let timer let timer
let loadingInstance let loadingInstance
const showLoading = (option = { const showLoading = (
target: null, option = {
}) => { target: null
}
) => {
const loadDom = document.getElementById('gva-base-load-dom') const loadDom = document.getElementById('gva-base-load-dom')
activeAxios++ activeAxios++
if (timer) { if (timer) {
@ -36,7 +38,7 @@ const closeLoading = () => {
} }
// http request 拦截器 // http request 拦截器
service.interceptors.request.use( service.interceptors.request.use(
config => { (config) => {
if (!config.donNotShowLoading) { if (!config.donNotShowLoading) {
showLoading(config.loadingOption) showLoading(config.loadingOption)
} }
@ -49,7 +51,7 @@ service.interceptors.request.use(
} }
return config return config
}, },
error => { (error) => {
if (!error.config.donNotShowLoading) { if (!error.config.donNotShowLoading) {
closeLoading() closeLoading()
} }
@ -64,7 +66,7 @@ service.interceptors.request.use(
// http response 拦截器 // http response 拦截器
service.interceptors.response.use( service.interceptors.response.use(
response => { (response) => {
const userStore = useUserStore() const userStore = useUserStore()
if (!response.config.donNotShowLoading) { if (!response.config.donNotShowLoading) {
closeLoading() closeLoading()
@ -86,67 +88,81 @@ service.interceptors.response.use(
return response.data.msg ? response.data : response return response.data.msg ? response.data : response
} }
}, },
error => { (error) => {
if (!error.config.donNotShowLoading) { if (!error.config.donNotShowLoading) {
closeLoading() closeLoading()
} }
if (!error.response) { if (!error.response) {
ElMessageBox.confirm(` ElMessageBox.confirm(
`
<p>检测到请求错误</p> <p>检测到请求错误</p>
<p>${error}</p> <p>${error}</p>
`, '请求报错', { `,
dangerouslyUseHTMLString: true, '请求报错',
distinguishCancelAndClose: true, {
confirmButtonText: '稍后重试', dangerouslyUseHTMLString: true,
cancelButtonText: '取消' distinguishCancelAndClose: true,
}) confirmButtonText: '稍后重试',
cancelButtonText: '取消'
}
)
return return
} }
switch (error.response.status) { switch (error.response.status) {
case 500: case 500:
ElMessageBox.confirm(` ElMessageBox.confirm(
`
<p>检测到接口错误${error}</p> <p>检测到接口错误${error}</p>
<p>错误码<span style="color:red"> 500 </span>panic使</p> <p>错误码<span style="color:red"> 500 </span>panic使</p>
`, '接口报错', { `,
dangerouslyUseHTMLString: true, '接口报错',
distinguishCancelAndClose: true, {
confirmButtonText: '清理缓存', dangerouslyUseHTMLString: true,
cancelButtonText: '取消' distinguishCancelAndClose: true,
confirmButtonText: '清理缓存',
cancelButtonText: '取消'
}
).then(() => {
const userStore = useUserStore()
userStore.ClearStorage()
router.push({ name: 'Login', replace: true })
}) })
.then(() => {
const userStore = useUserStore()
userStore.ClearStorage()
router.push({ name: 'Login', replace: true })
})
break break
case 404: case 404:
ElMessageBox.confirm(` ElMessageBox.confirm(
`
<p>检测到接口错误${error}</p> <p>检测到接口错误${error}</p>
<p>错误码<span style="color:red"> 404 </span>api--</p> <p>错误码<span style="color:red"> 404 </span>api--</p>
`, '接口报错', { `,
dangerouslyUseHTMLString: true, '接口报错',
distinguishCancelAndClose: true, {
confirmButtonText: '我知道了', dangerouslyUseHTMLString: true,
cancelButtonText: '取消' distinguishCancelAndClose: true,
}) confirmButtonText: '我知道了',
cancelButtonText: '取消'
}
)
break break
case 401: case 401:
ElMessageBox.confirm(` ElMessageBox.confirm(
`
<p>无效的令牌</p> <p>无效的令牌</p>
<p>错误码:<span style="color:red"> 401 </span>:${error}</p> <p>错误码:<span style="color:red"> 401 </span>:${error}</p>
`, '身份信息', { `,
dangerouslyUseHTMLString: true, '身份信息',
distinguishCancelAndClose: true, {
confirmButtonText: '重新登录', dangerouslyUseHTMLString: true,
cancelButtonText: '取消' distinguishCancelAndClose: true,
confirmButtonText: '重新登录',
cancelButtonText: '取消'
}
).then(() => {
const userStore = useUserStore()
userStore.ClearStorage()
router.push({ name: 'Login', replace: true })
}) })
.then(() => {
const userStore = useUserStore()
userStore.ClearStorage()
router.push({ name: 'Login', replace: true })
})
break break
} }

View File

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

View File

@ -8,16 +8,13 @@
</template> </template>
<div> <div>
<el-row> <el-row>
<el-col <el-col :span="8" :offset="8">
:span="8"
:offset="8"
>
<a href="https://github.com/flipped-aurora/gin-vue-admin"> <a href="https://github.com/flipped-aurora/gin-vue-admin">
<img <img
class="org-img dom-center" class="org-img dom-center"
src="@/assets/logo.png" src="@/assets/logo.png"
alt="gin-vue-admin" alt="gin-vue-admin"
> />
</a> </a>
</el-col> </el-col>
</el-row> </el-row>
@ -28,7 +25,7 @@
class="dom-center" class="dom-center"
src="https://img.shields.io/github/watchers/flipped-aurora/gin-vue-admin.svg?label=Watch" src="https://img.shields.io/github/watchers/flipped-aurora/gin-vue-admin.svg?label=Watch"
alt="" alt=""
> />
</a> </a>
</el-col> </el-col>
<el-col :span="8"> <el-col :span="8">
@ -37,7 +34,7 @@
class="dom-center" class="dom-center"
src="https://img.shields.io/github/stars/flipped-aurora/gin-vue-admin.svg?style=social" src="https://img.shields.io/github/stars/flipped-aurora/gin-vue-admin.svg?style=social"
alt="" alt=""
> />
</a> </a>
</el-col> </el-col>
<el-col :span="8"> <el-col :span="8">
@ -46,7 +43,7 @@
class="dom-center" class="dom-center"
src="https://img.shields.io/github/forks/flipped-aurora/gin-vue-admin.svg?label=Fork" src="https://img.shields.io/github/forks/flipped-aurora/gin-vue-admin.svg?label=Fork"
alt="" alt=""
> />
</a> </a>
</el-col> </el-col>
</el-row> </el-row>
@ -58,34 +55,27 @@
</template> </template>
<div> <div>
<el-row> <el-row>
<el-col <el-col :span="8" :offset="8">
:span="8"
:offset="8"
>
<a href="https://github.com/flipped-aurora"> <a href="https://github.com/flipped-aurora">
<img <img
class="org-img dom-center" class="org-img dom-center"
src="@/assets/flipped-aurora.png" src="@/assets/flipped-aurora.png"
alt="flipped-aurora" alt="flipped-aurora"
> />
</a> </a>
</el-col> </el-col>
</el-row> </el-row>
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4 mt-4"> <div
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4 mt-4"
v-for="(item, index) in members" >
:key="index" <div v-for="(item, index) in members" :key="index" :span="8">
:span="8"
>
<a :href="item.html_url" class="flex items-center"> <a :href="item.html_url" class="flex items-center">
<img <img class="w-8 h-8 rounded-full" :src="item.avatar_url" />
class="w-8 h-8 rounded-full"
:src="item.avatar_url"
>
<el-link <el-link
class=" text-blue-700 ml-2 text-xl font-bold font-sans " class="text-blue-700 ml-2 text-xl font-bold font-sans"
style="" style=""
>{{ item.login }}</el-link> >{{ item.login }}</el-link
>
</a> </a>
</div> </div>
</div> </div>
@ -100,7 +90,7 @@
<div> <div>
<el-timeline> <el-timeline>
<el-timeline-item <el-timeline-item
v-for="(item,index) in dataTimeline" v-for="(item, index) in dataTimeline"
:key="index" :key="index"
:timestamp="item.from" :timestamp="item.from"
placement="top" placement="top"
@ -112,12 +102,7 @@
</el-timeline-item> </el-timeline-item>
</el-timeline> </el-timeline>
</div> </div>
<el-button <el-button class="load-more" type="primary" link @click="loadMore">
class="load-more"
type="primary"
link
@click="loadMore"
>
Load more Load more
</el-button> </el-button>
</el-card> </el-card>
@ -127,72 +112,70 @@
</template> </template>
<script setup> <script setup>
import { ref } from 'vue' import { ref } from 'vue'
import { Commits, Members } from '@/api/github' import { Commits, Members } from '@/api/github'
import { formatTimeToStr } from '@/utils/date' import { formatTimeToStr } from '@/utils/date'
const page = ref(0) const page = ref(0)
defineOptions({ defineOptions({
name: 'About' name: 'About'
}) })
const loadMore = () => { const loadMore = () => {
page.value++ page.value++
loadCommits() loadCommits()
} }
const dataTimeline = ref([]) const dataTimeline = ref([])
const loadCommits = () => { const loadCommits = () => {
Commits(page.value).then(({ data }) => { Commits(page.value).then(({ data }) => {
data.forEach((element) => { data.forEach((element) => {
if (element.commit.message) { if (element.commit.message) {
dataTimeline.value.push({ dataTimeline.value.push({
from: formatTimeToStr(element.commit.author.date, 'yyyy-MM-dd'), from: formatTimeToStr(element.commit.author.date, 'yyyy-MM-dd'),
title: element.commit.author.name, title: element.commit.author.name,
showDayAndMonth: true, showDayAndMonth: true,
message: element.commit.message, message: element.commit.message
}) })
} }
})
}) })
}) }
}
const members = ref([]) const members = ref([])
const loadMembers = () => { const loadMembers = () => {
Members().then(({ data }) => { Members().then(({ data }) => {
members.value = data members.value = data
members.value.sort() members.value.sort()
}) })
} }
loadCommits()
loadMembers()
loadCommits()
loadMembers()
</script> </script>
<style scoped> <style scoped>
.load-more { .load-more {
margin-left: 120px; margin-left: 120px;
} }
.avatar-img { .avatar-img {
float: left; float: left;
height: 40px; height: 40px;
width: 40px; width: 40px;
border-radius: 50%; border-radius: 50%;
-webkit-border-radius: 50%; -webkit-border-radius: 50%;
-moz-border-radius: 50%; -moz-border-radius: 50%;
margin-top: 15px; margin-top: 15px;
} }
.org-img { .org-img {
height: 150px; height: 150px;
width: 150px; width: 150px;
} }
.dom-center {
.dom-center { margin-left: 50%;
margin-left: 50%; transform: translateX(-50%);
transform: translateX(-50%); }
}
</style> </style>

View File

@ -5,36 +5,39 @@
<template> <template>
<el-carousel class="-mt-2"> <el-carousel class="-mt-2">
<el-carousel-item class="cursor-pointer lg:h-40" v-for="(item , index) in banners" :key="index" @click="openLink(item.link)"> <el-carousel-item
class="cursor-pointer lg:h-40"
v-for="(item, index) in banners"
:key="index"
@click="openLink(item.link)"
>
<el-image class="h-full w-full" :src="item.img" fit="fill"></el-image> <el-image class="h-full w-full" :src="item.img" fit="fill"></el-image>
</el-carousel-item> </el-carousel-item>
</el-carousel> </el-carousel>
</template> </template>
<script setup> <script setup>
import banner from "@/assets/banner.jpg" import banner from '@/assets/banner.jpg'
import banner2 from "@/assets/banner2.jpg" import banner2 from '@/assets/banner2.jpg'
const openLink = (link) => { const openLink = (link) => {
window.open(link, '_blank') window.open(link, '_blank')
}
const banners = [
{
img: banner,
link: "https://gin-vue-admin.com/empower/index.html"
},
{
img: banner2,
link: "https://plugin.gin-vue-admin.com"
},
{
img: "https://qmplusimg.henrongyi.top/gvaDemo/k8s.jpg",
link: "https://plugin.gin-vue-admin.com/#/layout/newPluginInfo?id=42"
} }
]
const banners = [
{
img: banner,
link: 'https://gin-vue-admin.com/empower/index.html'
},
{
img: banner2,
link: 'https://plugin.gin-vue-admin.com'
},
{
img: 'https://qmplusimg.henrongyi.top/gvaDemo/k8s.jpg',
link: 'https://plugin.gin-vue-admin.com/#/layout/newPluginInfo?id=42'
}
]
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss"></style>
</style>

View File

@ -5,10 +5,8 @@
<template> <template>
<div <div
class="bg-white dark:bg-slate-900 text-gray-800 dark:text-gray-400 rounded shadow" :class="[ class="bg-white dark:bg-slate-900 text-gray-800 dark:text-gray-400 rounded shadow"
customClass || '', :class="[customClass || '', withoutPadding ? 'p-0' : 'p-4']"
withoutPadding ? 'p-0' : 'p-4'
]"
> >
<div v-if="title" class="flex justify-between items-center"> <div v-if="title" class="flex justify-between items-center">
<div class="text-base font-bold"> <div class="text-base font-bold">
@ -25,27 +23,24 @@
</template> </template>
<script setup> <script setup>
defineProps({ defineProps({
title: { title: {
type: String, type: String,
default: '' default: ''
}, },
showAction: { showAction: {
type: Boolean, type: Boolean,
default: false default: false
}, },
customClass: { customClass: {
type: String, type: String,
default: '' default: ''
}, },
withoutPadding: { withoutPadding: {
type: Boolean, type: Boolean,
default: false default: false
} }
}) })
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss"></style>
</style>

View File

@ -10,174 +10,181 @@
</template> </template>
<script setup> <script setup>
import Chart from "@/components/charts/index.vue"; import Chart from '@/components/charts/index.vue'
import useChartOption from '@/hooks/charts'; import useChartOption from '@/hooks/charts'
import { graphic } from 'echarts' import { graphic } from 'echarts'
import { computed, ref } from 'vue' import { computed, ref } from 'vue'
import { useAppStore } from '@/pinia' import { useAppStore } from '@/pinia'
import { storeToRefs } from 'pinia' import { storeToRefs } from 'pinia'
const appStore = useAppStore() const appStore = useAppStore()
const { config } = storeToRefs(appStore) const { config } = storeToRefs(appStore)
defineProps({ defineProps({
height: { height: {
type: String, type: String,
default: '128px', default: '128px'
}, }
}) })
const dotColor = computed(() => { const dotColor = computed(() => {
console.log(appStore.theme) console.log(appStore.theme)
return appStore.theme === 'dark' ? '#333' : '#E5E8EF' return appStore.theme === 'dark' ? '#333' : '#E5E8EF'
}) })
const graphicFactory = (side) => { const graphicFactory = (side) => {
return { return {
type: 'text', type: 'text',
bottom: '8', bottom: '8',
...side, ...side,
style: { style: {
text: '', text: '',
textAlign: 'center', textAlign: 'center',
fill: '#4E5969', fill: '#4E5969',
fontSize: 12, fontSize: 12
}, }
}; }
} }
const xAxis = ref(["2024-1", "2024-2", "2024-3", "2024-4", "2024-5", "2024-6", "2024-7", "2024-8"]); const xAxis = ref([
const chartsData = ref([12,22,32,45,32,78,89,92]); '2024-1',
const graphicElements = ref([ '2024-2',
graphicFactory({ left: '5%' }), '2024-3',
graphicFactory({ right: 0 }), '2024-4',
]); '2024-5',
const { chartOption } = useChartOption(() => { '2024-6',
return { '2024-7',
grid: { '2024-8'
left: '40', ])
right: '0', const chartsData = ref([12, 22, 32, 45, 32, 78, 89, 92])
top: '10', const graphicElements = ref([
bottom: '30', graphicFactory({ left: '5%' }),
}, graphicFactory({ right: 0 })
xAxis: { ])
type: 'category', const { chartOption } = useChartOption(() => {
offset: 2, return {
data: xAxis.value, grid: {
boundaryGap: false, left: '40',
axisLabel: { right: '0',
color: '#4E5969', top: '10',
formatter(value, idx) { bottom: '30'
if (idx === 0) return ''; },
if (idx === xAxis.value.length - 1) return ''; xAxis: {
return `${value}`; type: 'category',
offset: 2,
data: xAxis.value,
boundaryGap: false,
axisLabel: {
color: '#4E5969',
formatter(value, idx) {
if (idx === 0) return ''
if (idx === xAxis.value.length - 1) return ''
return `${value}`
}
}, },
}, axisLine: {
axisLine: { show: false
show: false,
},
axisTick: {
show: false,
},
splitLine: {
show: true,
interval: (idx) => {
if (idx === 0) return false;
if (idx === xAxis.value.length - 1) return false;
return true;
}, },
lineStyle: { axisTick: {
color: dotColor.value, show: false
}, },
}, splitLine: {
axisPointer: { show: true,
show: true, interval: (idx) => {
lineStyle: { if (idx === 0) return false
color: `${config.value.primaryColor}FF`, if (idx === xAxis.value.length - 1) return false
width: 2, return true
},
lineStyle: {
color: dotColor.value
}
}, },
axisPointer: {
show: true,
lineStyle: {
color: `${config.value.primaryColor}FF`,
width: 2
}
}
}, },
}, yAxis: {
yAxis: { type: 'value',
type: 'value', axisLine: {
axisLine: { show: false
show: false,
},
axisLabel: {
formatter(value, idx) {
if (idx === 0) return value;
return `${value}k`;
}, },
}, axisLabel: {
splitLine: { formatter(value, idx) {
show: true, if (idx === 0) return value
lineStyle: { return `${value}k`
type: 'dashed', }
color: dotColor.value,
}, },
splitLine: {
show: true,
lineStyle: {
type: 'dashed',
color: dotColor.value
}
}
}, },
}, tooltip: {
tooltip: { trigger: 'axis',
trigger: 'axis', formatter(params) {
formatter(params) { const [firstElement] = params
const [firstElement] = params return `<div>
return `<div>
<p class="tooltip-title">${firstElement.axisValueLabel}</p> <p class="tooltip-title">${firstElement.axisValueLabel}</p>
<div class="content-panel"><span>总内容量</span><span class="tooltip-value">${( <div class="content-panel"><span>总内容量</span><span class="tooltip-value">${(
Number(firstElement.value) * 10000 Number(firstElement.value) * 10000
).toLocaleString()}</span></div> ).toLocaleString()}</span></div>
</div>`; </div>`
},
className: 'echarts-tooltip-diy'
}, },
className: 'echarts-tooltip-diy', graphic: {
}, elements: graphicElements.value
graphic: { },
elements: graphicElements.value, series: [
}, {
series: [ data: chartsData.value,
{ type: 'line',
data: chartsData.value, smooth: true,
type: 'line', // symbol: 'circle',
smooth: true, symbolSize: 12,
// symbol: 'circle', emphasis: {
symbolSize: 12, focus: 'series',
emphasis: { itemStyle: {
focus: 'series', borderWidth: 2
itemStyle: { }
borderWidth: 2,
}, },
}, lineStyle: {
lineStyle: { width: 3,
width: 3, color: new graphic.LinearGradient(0, 0, 1, 0, [
color: new graphic.LinearGradient(0, 0, 1, 0, [ {
{ offset: 0,
offset: 0, color: `${config.value.primaryColor}80`
color: `${config.value.primaryColor}80`, },
}, {
{ offset: 0.5,
offset: 0.5, color: `${config.value.primaryColor}92`
color: `${config.value.primaryColor}92`, },
}, {
{ offset: 1,
offset: 1, color: `${config.value.primaryColor}FF`
color: `${config.value.primaryColor}FF`, }
}, ])
]), },
}, showSymbol: false,
showSymbol: false, areaStyle: {
areaStyle: { opacity: 0.8,
opacity: 0.8, color: new graphic.LinearGradient(0, 0, 0, 1, [
color: new graphic.LinearGradient(0, 0, 0, 1, [ {
{ offset: 0,
offset: 0, color: `${config.value.primaryColor}20`
color: `${config.value.primaryColor}20`, },
}, {
{ offset: 1,
offset: 1, color: `${config.value.primaryColor}08`
color: `${config.value.primaryColor}08`, }
}, ])
]), }
}, }
}, ]
], }
}; })
});
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss"></style>
</style>

View File

@ -11,131 +11,128 @@
</template> </template>
<script setup> <script setup>
import Chart from "@/components/charts/index.vue"; import Chart from '@/components/charts/index.vue'
import useChartOption from '@/hooks/charts'; import useChartOption from '@/hooks/charts'
import { graphic } from 'echarts' import { graphic } from 'echarts'
import { ref } from 'vue'; import { ref } from 'vue'
import { storeToRefs } from "pinia" import { storeToRefs } from 'pinia'
import { useAppStore } from '@/pinia' import { useAppStore } from '@/pinia'
const appStore = useAppStore() const appStore = useAppStore()
const { config } = storeToRefs(appStore) const { config } = storeToRefs(appStore)
const prop = defineProps({ const prop = defineProps({
height: { height: {
type: String, type: String,
default: '128px', default: '128px'
}, },
data : { data: {
type : Array, type: Array,
default : () => [] default: () => []
}
})
const graphicFactory = (side) => {
return {
type: 'text',
bottom: '8',
...side,
style: {
text: '',
textAlign: 'center',
fill: '#4E5969',
fontSize: 12
}
}
} }
}) const graphicElements = ref([
const graphicFactory = (side) => { graphicFactory({ left: '5%' }),
return { graphicFactory({ right: 0 })
type: 'text', ])
bottom: '8', const { chartOption } = useChartOption(() => {
...side, return {
style: { grid: {
text: '', left: '40',
textAlign: 'center', right: '0',
fill: '#4E5969', top: '10',
fontSize: 12, bottom: '30'
}, },
}; xAxis: {
} type: 'category',
const graphicElements = ref([ offset: 2,
graphicFactory({ left: '5%' }),
graphicFactory({ right: 0 }),
]);
const { chartOption } = useChartOption(() => {
return {
grid: {
left: '40',
right: '0',
top: '10',
bottom: '30',
},
xAxis: {
type: 'category',
offset: 2,
show : false,
boundaryGap: false,
axisLine: {
show: false, show: false,
boundaryGap: false,
axisLine: {
show: false
},
axisTick: {
show: false
},
splitLine: {
show: false
}
}, },
axisTick: { yAxis: {
type: 'value',
show: false, show: false,
axisLine: {
show: false
},
axisLabel: {
show: false
},
splitLine: {
show: false
}
}, },
splitLine: { graphic: {
show: false, elements: graphicElements.value
}, },
series: [
}, {
yAxis: { data: prop.data,
type: 'value', type: 'line',
show: false, smooth: true,
axisLine: { symbolSize: 12,
show: false, emphasis: {
}, focus: 'series',
axisLabel: { itemStyle: {
show: false borderWidth: 2
}, }
splitLine: {
show: false,
},
},
graphic: {
elements: graphicElements.value,
},
series: [
{
data: prop.data,
type: 'line',
smooth: true,
symbolSize: 12,
emphasis: {
focus: 'series',
itemStyle: {
borderWidth: 2,
}, },
}, lineStyle: {
lineStyle: { width: 3,
width: 3, color: new graphic.LinearGradient(0, 0, 1, 0, [
color: new graphic.LinearGradient(0, 0, 1, 0, [ {
{ offset: 0,
offset: 0, color: `${config.value.primaryColor}32`
color: `${config.value.primaryColor}32`, },
}, {
{ offset: 0.5,
offset: 0.5, color: `${config.value.primaryColor}64`
color: `${config.value.primaryColor}64`, },
}, {
{ offset: 1,
offset: 1, color: `${config.value.primaryColor}FF`
color: `${config.value.primaryColor}FF`, }
}, ])
]), },
}, showSymbol: false,
showSymbol: false, areaStyle: {
areaStyle: { opacity: 0.8,
opacity: 0.8, color: new graphic.LinearGradient(0, 0, 0, 1, [
color: new graphic.LinearGradient(0, 0, 0, 1, [ {
{ offset: 0,
offset: 0, color: `${config.value.primaryColor}20`
color: `${config.value.primaryColor}20`, },
}, {
{ offset: 1,
offset: 1, color: `${config.value.primaryColor}08`
color: `${config.value.primaryColor}08`, }
}, ])
]), }
}, }
}, ]
], }
}; })
});
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss"></style>
</style>

View File

@ -11,7 +11,7 @@
</div> </div>
<slot v-else name="title" /> <slot v-else name="title" />
</div> </div>
<div class=" w-full relative"> <div class="w-full relative">
<div v-if="type !== 4"> <div v-if="type !== 4">
<div class="mt-4 text-gray-600 text-3xl font-mono"> <div class="mt-4 text-gray-600 text-3xl font-mono">
<el-statistic :value="268500" /> <el-statistic :value="268500" />
@ -20,7 +20,7 @@
+80% <el-icon><TopRight /></el-icon> +80% <el-icon><TopRight /></el-icon>
</div> </div>
</div> </div>
<div class=" absolute top-0 right-2 w-[50%] h-20"> <div class="absolute top-0 right-2 w-[50%] h-20">
<charts-people-number v-if="type === 1" :data="data[0]" height="100%" /> <charts-people-number v-if="type === 1" :data="data[0]" height="100%" />
<charts-people-number v-if="type === 2" :data="data[1]" height="100%" /> <charts-people-number v-if="type === 2" :data="data[1]" height="100%" />
<charts-people-number v-if="type === 3" :data="data[2]" height="100%" /> <charts-people-number v-if="type === 3" :data="data[2]" height="100%" />
@ -31,28 +31,24 @@
</template> </template>
<script setup> <script setup>
import chartsPeopleNumber from './charts-people-numbers.vue'
import chartsContentNumber from './charts-content-numbers.vue'
defineProps({
type: {
type: Number,
default: 1
},
title: {
type: String,
default: ''
}
})
const data = [
import chartsPeopleNumber from "./charts-people-numbers.vue"
import chartsContentNumber from "./charts-content-numbers.vue"
defineProps({
type :{
type : Number,
default : 1
},
title : {
type : String,
default : ""
}
})
const data = [
[12, 22, 32, 45, 32, 78, 89, 92], [12, 22, 32, 45, 32, 78, 89, 92],
[1, 2, 43, 5, 67, 78, 89, 12], [1, 2, 43, 5, 67, 78, 89, 12],
[12, 22, 32, 45, 32, 78, 89, 92], [12, 22, 32, 45, 32, 78, 89, 92]
] ]
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss"></style>
</style>

View File

@ -1,19 +1,19 @@
import GvaBanner from "./banner.vue" import GvaBanner from './banner.vue'
import GvaCard from "./card.vue" import GvaCard from './card.vue'
import GvaChart from "./charts.vue" import GvaChart from './charts.vue'
import GvaTable from "./table.vue" import GvaTable from './table.vue'
import GvaNotice from "./notice.vue" import GvaNotice from './notice.vue'
import GvaQuickLink from "./quickLinks.vue" import GvaQuickLink from './quickLinks.vue'
import GvaWiki from "./wiki.vue" import GvaWiki from './wiki.vue'
import GvaPluginTable from "./pluginTable.vue" import GvaPluginTable from './pluginTable.vue'
export { export {
GvaBanner, GvaBanner,
GvaCard, GvaCard,
GvaChart, GvaChart,
GvaTable, GvaTable,
GvaNotice, GvaNotice,
GvaQuickLink, GvaQuickLink,
GvaWiki, GvaWiki,
GvaPluginTable GvaPluginTable
} }

View File

@ -5,7 +5,11 @@
<template> <template>
<el-scrollbar> <el-scrollbar>
<div v-for="(item , index) in notices" :key="index" class="flex items-center mb-1.5 gap-3"> <div
v-for="(item, index) in notices"
:key="index"
class="flex items-center mb-1.5 gap-3"
>
<el-tag :type="item.type" size="small"> <el-tag :type="item.type" size="small">
{{ item.typeTitle }} {{ item.typeTitle }}
</el-tag> </el-tag>
@ -21,58 +25,56 @@
<script setup> <script setup>
const notices = [ const notices = [
{ {
type : 'primary', type: 'primary',
typeTitle : "公告", typeTitle: '公告',
title : "授权费将在从六月一日起结束第一价格梯度,进入第二价格梯度。", title: '授权费将在从六月一日起结束第一价格梯度,进入第二价格梯度。'
}, },
{ {
type : 'success', type: 'success',
typeTitle : "通知", typeTitle: '通知',
title : "授权后将进入专属飞书群,获取官方辅助。", title: '授权后将进入专属飞书群,获取官方辅助。'
}, },
{ {
type : 'warning', type: 'warning',
typeTitle : "警告", typeTitle: '警告',
title : "授权可获得插件市场极大优惠价格。", title: '授权可获得插件市场极大优惠价格。'
}, },
{ {
type : 'danger', type: 'danger',
typeTitle : "违规", typeTitle: '违规',
title : "未授权商用将有可能被资源采集工具爬取并追责。", title: '未授权商用将有可能被资源采集工具爬取并追责。'
}, },
{ {
type : 'info', type: 'info',
typeTitle : "信息", typeTitle: '信息',
title : "再次感谢您对开源事业的支持", title: '再次感谢您对开源事业的支持'
}, },
{ {
type : 'primary', type: 'primary',
typeTitle : "公告", typeTitle: '公告',
title : "让创意更有价值。", title: '让创意更有价值。'
}, },
{ {
type : 'success', type: 'success',
typeTitle : "通知", typeTitle: '通知',
title : "让劳动更有意义。", title: '让劳动更有意义。'
}, },
{ {
type : 'warning', type: 'warning',
typeTitle : "警告", typeTitle: '警告',
title : "让思维更有深度。", title: '让思维更有深度。'
}, },
{ {
type : 'danger', type: 'danger',
typeTitle : "错误", typeTitle: '错误',
title : "让生活更有趣味。", title: '让生活更有趣味。'
}, },
{ {
type : 'info', type: 'info',
typeTitle : "信息", typeTitle: '信息',
title : "让公司更有活力。", title: '让公司更有活力。'
} }
] ]
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss"></style>
</style>

View File

@ -9,7 +9,9 @@
<el-table-column prop="ranking" label="排名" width="80" align="center" /> <el-table-column prop="ranking" label="排名" width="80" align="center" />
<el-table-column prop="title" label="插件标题" show-overflow-tooltip> <el-table-column prop="title" label="插件标题" show-overflow-tooltip>
<template #default="{ row }"> <template #default="{ row }">
<a class="text-active" :href="row.link" target="_blank">{{ row.title }}</a> <a class="text-active" :href="row.link" target="_blank">{{
row.title
}}</a>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column prop="click_num" label="关注度" width="100" /> <el-table-column prop="click_num" label="关注度" width="100" />
@ -19,45 +21,46 @@
</template> </template>
<script setup> <script setup>
const tableData = [ const tableData = [
{ {
ranking: 1, ranking: 1,
title : "组织管理插件:更方便管理组织,分配资源权限。", title: '组织管理插件:更方便管理组织,分配资源权限。',
click_num : 523, click_num: 523,
hot : 263, hot: 263,
link : "https://plugin.gin-vue-admin.com/#/layout/newPluginInfo?id=36" link: 'https://plugin.gin-vue-admin.com/#/layout/newPluginInfo?id=36'
}, },
{ {
ranking: 2, ranking: 2,
title : "Kubernetes容器管理:Kubernetes 原生资源管理提供炫酷的YAML 编辑Pod 终端方便运维兄弟管理k8s资源", title:
click_num : 416, 'Kubernetes容器管理:Kubernetes 原生资源管理提供炫酷的YAML 编辑Pod 终端方便运维兄弟管理k8s资源',
hot : 223, click_num: 416,
link : "https://plugin.gin-vue-admin.com/#/layout/newPluginInfo?id=42" hot: 223,
}, link: 'https://plugin.gin-vue-admin.com/#/layout/newPluginInfo?id=42'
{ },
ranking: 3, {
title : "定时任务配置化管理:本插件用于对系统内部的定时任务进行配置化管理可以配置自定义的函数和HTTP可以配置cron和remark等等", ranking: 3,
click_num : 337, title:
hot : 176, '定时任务配置化管理:本插件用于对系统内部的定时任务进行配置化管理可以配置自定义的函数和HTTP可以配置cron和remark等等',
link : "https://plugin.gin-vue-admin.com/#/layout/newPluginInfo?id=67" click_num: 337,
}, hot: 176,
{ link: 'https://plugin.gin-vue-admin.com/#/layout/newPluginInfo?id=67'
ranking: 4, },
title : "官网CMS系统基于Gin-Vue-Admin 和 插件市场客户端开发基座开发的企业官网类cms系统", {
click_num : 292, ranking: 4,
hot : 145, title:
link : "https://plugin.gin-vue-admin.com/#/layout/newPluginInfo?id=69" '官网CMS系统基于Gin-Vue-Admin 和 插件市场客户端开发基座开发的企业官网类cms系统',
}, click_num: 292,
{ hot: 145,
ranking: 5, link: 'https://plugin.gin-vue-admin.com/#/layout/newPluginInfo?id=69'
title : "微信支付插件:提供扫码支付功能(需自行对接业务)", },
click_num : 173, {
hot : 110, ranking: 5,
link : "https://plugin.gin-vue-admin.com/#/layout/newPluginInfo?id=28" title: '微信支付插件:提供扫码支付功能(需自行对接业务)',
}, click_num: 173,
] hot: 110,
link: 'https://plugin.gin-vue-admin.com/#/layout/newPluginInfo?id=28'
}
]
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss"></style>
</style>

View File

@ -5,21 +5,35 @@
<template> <template>
<div class="mt-8 w-full"> <div class="mt-8 w-full">
<div class="grid grid-cols-2 md:grid-cols-3 3xl:grid-cols-4"> <div class="grid grid-cols-2 md:grid-cols-3 3xl:grid-cols-4">
<div v-for="(item, index ) in shortcuts" :key="index" class="flex flex-col items-center mb-3 group cursor-pointer" @click="toPath(item)"> <div
<div class="w-8 h-8 rounded bg-gray-200 dark:bg-slate-500 flex items-center justify-center group-hover:bg-blue-400 group-hover:text-white"> v-for="(item, index) in shortcuts"
:key="index"
class="flex flex-col items-center mb-3 group cursor-pointer"
@click="toPath(item)"
>
<div
class="w-8 h-8 rounded bg-gray-200 dark:bg-slate-500 flex items-center justify-center group-hover:bg-blue-400 group-hover:text-white"
>
<el-icon><component :is="item.icon" /></el-icon> <el-icon><component :is="item.icon" /></el-icon>
</div> </div>
<div class="text-xs mt-2 text-gray-700 dark:text-gray-300"> <div class="text-xs mt-2 text-gray-700 dark:text-gray-300">
{{ item.title }} {{ item.title }}
</div> </div>
</div> </div>
</div> </div>
<div class="grid grid-cols-2 md:grid-cols-3 3xl:grid-cols-4 mt-8"> <div class="grid grid-cols-2 md:grid-cols-3 3xl:grid-cols-4 mt-8">
<div v-for="(item, index ) in recentVisits" :key="index" class="flex flex-col items-center mb-3 group cursor-pointer" @click="openLink(item)"> <div
<div class="w-8 h-8 rounded bg-gray-200 dark:bg-slate-500 flex items-center justify-center group-hover:bg-blue-400 group-hover:text-white"> v-for="(item, index) in recentVisits"
:key="index"
class="flex flex-col items-center mb-3 group cursor-pointer"
@click="openLink(item)"
>
<div
class="w-8 h-8 rounded bg-gray-200 dark:bg-slate-500 flex items-center justify-center group-hover:bg-blue-400 group-hover:text-white"
>
<el-icon><component :is="item.icon" /></el-icon> <el-icon><component :is="item.icon" /></el-icon>
</div> </div>
<div class="text-xs mt-2 text-gray-700 dark:text-gray-300"> <div class="text-xs mt-2 text-gray-700 dark:text-gray-300">
{{ item.title }} {{ item.title }}
</div> </div>
</div> </div>
@ -27,65 +41,71 @@
</div> </div>
</template> </template>
<script setup> <script setup>
import { Menu,Link,User,Service,Document,Reading,Files,Memo } from '@element-plus/icons-vue' import {
import {useRouter} from "vue-router"; Menu,
const router = useRouter() Link,
User,
Service,
Document,
Reading,
Files,
Memo
} from '@element-plus/icons-vue'
import { useRouter } from 'vue-router'
const router = useRouter()
const toPath = (item) => { const toPath = (item) => {
router.push({name: item.path}) router.push({ name: item.path })
} }
const openLink = (item) => { const openLink = (item) => {
window.open(item.path, '_blank') window.open(item.path, '_blank')
} }
const shortcuts = [ const shortcuts = [
{ {
icon : Menu, icon: Menu,
title : "菜单管理", title: '菜单管理',
path : "menu", path: 'menu'
}, },
{ {
icon : Link, icon: Link,
title : "API管理", title: 'API管理',
path : "api", path: 'api'
}, },
{ {
icon : Service, icon: Service,
title : "角色管理", title: '角色管理',
path : "authority", path: 'authority'
}, },
{ {
icon : User, icon: User,
title : "用户管理", title: '用户管理',
path : "user", path: 'user'
}, },
{ {
icon : Files, icon: Files,
title : "自动化包", title: '自动化包',
path: "autoPkg", path: 'autoPkg'
}, },
{ {
icon : Memo, icon: Memo,
title : "自动代码", title: '自动代码',
path: "autoCode", path: 'autoCode'
} }
] ]
const recentVisits = [ const recentVisits = [
{ {
icon : Reading, icon: Reading,
title : "授权购买", title: '授权购买',
path: "https://gin-vue-admin.com/empower/index.html", path: 'https://gin-vue-admin.com/empower/index.html'
}, },
{ {
icon : Document, icon: Document,
title : "插件市场", title: '插件市场',
path: "https://plugin.gin-vue-admin.com/#/layout/home", path: 'https://plugin.gin-vue-admin.com/#/layout/home'
} }
] ]
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss"></style>
</style>

View File

@ -9,46 +9,44 @@
<el-table-column prop="ranking" label="排名" width="80" align="center" /> <el-table-column prop="ranking" label="排名" width="80" align="center" />
<el-table-column prop="title" label="内容标题" show-overflow-tooltip /> <el-table-column prop="title" label="内容标题" show-overflow-tooltip />
<el-table-column prop="click_num" label="关注度" width="100" /> <el-table-column prop="click_num" label="关注度" width="100" />
<el-table-column prop="hot" label="热度值" width="100" /> <el-table-column prop="hot" label="热度值" width="100" />
</el-table> </el-table>
</div> </div>
</template> </template>
<script setup> <script setup>
const tableData = [ const tableData = [
{ {
ranking: 1, ranking: 1,
title : "更简洁的使用界面,更快速的操作体验", title: '更简洁的使用界面,更快速的操作体验',
click_num : 523, click_num: 523,
hot : 263 hot: 263
}, },
{ {
ranking: 2, ranking: 2,
title : "更优质的服务,更便捷的使用体验", title: '更优质的服务,更便捷的使用体验',
click_num : 416, click_num: 416,
hot : 223 hot: 223
}, },
{ {
ranking: 3, ranking: 3,
title : "更快速的创意实现,更高效的工作效率", title: '更快速的创意实现,更高效的工作效率',
click_num : 337, click_num: 337,
hot : 176 hot: 176
}, },
{ {
ranking: 4, ranking: 4,
title : "更多的创意资源,更多的创意灵感", title: '更多的创意资源,更多的创意灵感',
click_num : 292, click_num: 292,
hot : 145 hot: 145
}, },
{ {
ranking: 5, ranking: 5,
title : "更合理的代码结构,更清晰的代码逻辑", title: '更合理的代码结构,更清晰的代码逻辑',
click_num : 173, click_num: 173,
hot : 110 hot: 110
}, }
] ]
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss"></style>
</style>

View File

@ -5,38 +5,41 @@
<template> <template>
<div class="grid grid-cols-2 gap-2"> <div class="grid grid-cols-2 gap-2">
<a v-for="item in wikis" :key="item.url" :href="item.url" class="text-sm text-gray-700 dark:text-gray-300 no-underline hover:text-active " target="_blank"> <a
v-for="item in wikis"
:key="item.url"
:href="item.url"
class="text-sm text-gray-700 dark:text-gray-300 no-underline hover:text-active"
target="_blank"
>
{{ item.title }} {{ item.title }}
</a> </a>
</div> </div>
</template> </template>
<script setup> <script setup>
const wikis = [ const wikis = [
{ {
title: 'Vue3', title: 'Vue3',
url : 'https://v3.cn.vuejs.org/guide/introduction.html' url: 'https://v3.cn.vuejs.org/guide/introduction.html'
}, },
{ {
title: 'GIN 文档', title: 'GIN 文档',
url : 'https://gin-gonic.com/' url: 'https://gin-gonic.com/'
}, },
{ {
title : "GVA 文档", title: 'GVA 文档',
url : 'https://www.gin-vue-admin.com/' url: 'https://www.gin-vue-admin.com/'
}, },
{ {
title : "插件市场", title: '插件市场',
url : 'https://plugin.gin-vue-admin.com/' url: 'https://plugin.gin-vue-admin.com/'
}, },
{ {
title : "github 仓库", title: 'github 仓库',
url : 'https://github.com/flipped-aurora/gin-vue-admin' url: 'https://github.com/flipped-aurora/gin-vue-admin'
} }
]
]
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss"></style>
</style>

View File

@ -1,5 +1,7 @@
<template> <template>
<div class="grid grid-cols-1 md:grid-cols-3 lg:grid-cols-7 py-2 gap-4 md:gap-2 gva-container2"> <div
class="grid grid-cols-1 md:grid-cols-3 lg:grid-cols-7 py-2 gap-4 md:gap-2 gva-container2"
>
<gva-card custom-class="col-span-1 lg:col-span-2 h-32"> <gva-card custom-class="col-span-1 lg:col-span-2 h-32">
<gva-chart :type="1" title="访问人数" /> <gva-chart :type="1" title="访问人数" />
</gva-card> </gva-card>
@ -9,20 +11,37 @@
<gva-card custom-class="col-span-1 lg:col-span-2 h-32"> <gva-card custom-class="col-span-1 lg:col-span-2 h-32">
<gva-chart :type="3" title="解决数量" /> <gva-chart :type="3" title="解决数量" />
</gva-card> </gva-card>
<gva-card title="快捷功能" show-action custom-class="col-start-1 md:col-start-3 lg:col-start-7 row-span-2 h-38"> <gva-card
title="快捷功能"
show-action
custom-class="col-start-1 md:col-start-3 lg:col-start-7 row-span-2 h-38"
>
<gva-quick-link /> <gva-quick-link />
</gva-card> </gva-card>
<gva-card title="内容数据" custom-class="col-span-1 md:col-span-2 md:row-start-2 lg:col-span-6 col-start-1 row-span-2"> <gva-card
title="内容数据"
custom-class="col-span-1 md:col-span-2 md:row-start-2 lg:col-span-6 col-start-1 row-span-2"
>
<gva-chart :type="4" /> <gva-chart :type="4" />
</gva-card> </gva-card>
<gva-card title="文档" show-action custom-class="md:row-start-8 md:col-start-3 lg:row-start-3 lg:col-start-7"> <gva-card
title="文档"
show-action
custom-class="md:row-start-8 md:col-start-3 lg:row-start-3 lg:col-start-7"
>
<gva-wiki /> <gva-wiki />
</gva-card> </gva-card>
<gva-card title="最新更新" custom-class="col-span-1 md:col-span-3 row-span-2"> <gva-card
title="最新更新"
custom-class="col-span-1 md:col-span-3 row-span-2"
>
<gva-table /> <gva-table />
</gva-card> </gva-card>
<gva-card title="最新插件" custom-class="col-span-1 md:col-span-3 row-span-2"> <gva-card
title="最新插件"
custom-class="col-span-1 md:col-span-3 row-span-2"
>
<gva-plugin-table /> <gva-plugin-table />
</gva-card> </gva-card>
@ -30,18 +49,29 @@
<gva-notice /> <gva-notice />
</gva-card> </gva-card>
<gva-card without-padding custom-class="overflow-hidden lg:h-40 col-span-1 md:col-start-2 md:col-span-1 lg:col-start-7"> <gva-card
without-padding
custom-class="overflow-hidden lg:h-40 col-span-1 md:col-start-2 md:col-span-1 lg:col-start-7"
>
<gva-banner /> <gva-banner />
</gva-card> </gva-card>
</div> </div>
</template> </template>
<script setup> <script setup>
import { GvaPluginTable,GvaTable, GvaChart, GvaWiki , GvaNotice , GvaQuickLink , GvaCard , GvaBanner } from "./components" import {
defineOptions({ GvaPluginTable,
name: 'Dashboard' GvaTable,
}) GvaChart,
GvaWiki,
GvaNotice,
GvaQuickLink,
GvaCard,
GvaBanner
} from './components'
defineOptions({
name: 'Dashboard'
})
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped></style>
</style>

View File

@ -2,10 +2,19 @@
<div> <div>
<div class="w-full h-screen bg-gray-50 flex items-center justify-center"> <div class="w-full h-screen bg-gray-50 flex items-center justify-center">
<div class="flex flex-col items-center text-2xl gap-4"> <div class="flex flex-col items-center text-2xl gap-4">
<img class="w-1/3" src="../../assets/404.png"> <img class="w-1/3" src="../../assets/404.png" />
<p class="text-lg">页面被神秘力量吸走了</p> <p class="text-lg">页面被神秘力量吸走了</p>
<p class="text-lg">常见问题为当前此角色无当前路由如果确定要使用本路由请到角色管理进行分配</p> <p class="text-lg">
<p>项目地址<a href="https://github.com/flipped-aurora/gin-vue-admin" target="_blank" class="text-blue-600 ">https://github.com/flipped-aurora/gin-vue-admin</a></p> 常见问题为当前此角色无当前路由如果确定要使用本路由请到角色管理进行分配
</p>
<p>
项目地址<a
href="https://github.com/flipped-aurora/gin-vue-admin"
target="_blank"
class="text-blue-600"
>https://github.com/flipped-aurora/gin-vue-admin</a
>
</p>
<el-button @click="toDashboard">返回首页</el-button> <el-button @click="toDashboard">返回首页</el-button>
</div> </div>
</div> </div>
@ -13,16 +22,16 @@
</template> </template>
<script setup> <script setup>
import { useUserStore } from '@/pinia/modules/user' import { useUserStore } from '@/pinia/modules/user'
import { useRouter } from 'vue-router' import { useRouter } from 'vue-router'
defineOptions({ defineOptions({
name: 'Error' name: 'Error'
}) })
const userStore = useUserStore() const userStore = useUserStore()
const router = useRouter() const router = useRouter()
const toDashboard = () => { const toDashboard = () => {
router.push({ name: userStore.userInfo.authority.defaultRouter }) router.push({ name: userStore.userInfo.authority.defaultRouter })
} }
</script> </script>

View File

@ -3,12 +3,12 @@
</template> </template>
<script setup> <script setup>
import { useRouter } from 'vue-router' import { useRouter } from 'vue-router'
defineOptions({ defineOptions({
name: 'Reload' name: 'Reload'
}) })
const router = useRouter() const router = useRouter()
router.go(-1) router.go(-1)
</script> </script>

View File

@ -2,14 +2,8 @@
<div class="break-point"> <div class="break-point">
<div class="gva-table-box"> <div class="gva-table-box">
<el-divider content-position="left">大文件上传</el-divider> <el-divider content-position="left">大文件上传</el-divider>
<form <form id="fromCont" method="post">
id="fromCont" <div class="fileUpload" @click="inputChange">
method="post"
>
<div
class="fileUpload"
@click="inputChange"
>
选择文件 选择文件
<input <input
v-show="false" v-show="false"
@ -18,7 +12,7 @@
multiple="multiple" multiple="multiple"
type="file" type="file"
@change="choseFile" @change="choseFile"
> />
</div> </div>
</form> </form>
<el-button <el-button
@ -26,17 +20,12 @@
type="primary" type="primary"
class="uploadBtn" class="uploadBtn"
@click="getFile" @click="getFile"
>上传文件</el-button> >上传文件</el-button
>
<div class="el-upload__tip">请上传不超过5MB的文件</div> <div class="el-upload__tip">请上传不超过5MB的文件</div>
<div class="list"> <div class="list">
<transition <transition name="list" tag="p">
name="list" <div v-if="file" class="list-item">
tag="p"
>
<div
v-if="file"
class="list-item"
>
<el-icon> <el-icon>
<document /> <document />
</el-icon> </el-icon>
@ -51,186 +40,195 @@
</div> </div>
</transition> </transition>
</div> </div>
<div class="tips">此版本为先行体验功能测试版样式美化和性能优化正在进行中上传切片文件和合成的完整文件分别再QMPlusserver目录的breakpointDir文件夹和fileDir文件夹</div> <div class="tips">
此版本为先行体验功能测试版样式美化和性能优化正在进行中上传切片文件和合成的完整文件分别再QMPlusserver目录的breakpointDir文件夹和fileDir文件夹
</div>
</div> </div>
</div> </div>
</template> </template>
<script setup> <script setup>
import SparkMD5 from 'spark-md5' import SparkMD5 from 'spark-md5'
import { import {
findFile, findFile,
breakpointContinueFinish, breakpointContinueFinish,
removeChunk, removeChunk,
breakpointContinue breakpointContinue
} from '@/api/breakpoint' } from '@/api/breakpoint'
import { ref, watch } from 'vue' import { ref, watch } from 'vue'
import { ElMessage } from 'element-plus' import { ElMessage } from 'element-plus'
defineOptions({ defineOptions({
name: 'BreakPoint' name: 'BreakPoint'
}) })
const file = ref(null) const file = ref(null)
const fileMd5 = ref('') const fileMd5 = ref('')
const formDataList = ref([]) const formDataList = ref([])
const waitUpLoad = ref([]) const waitUpLoad = ref([])
const waitNum = ref(NaN) const waitNum = ref(NaN)
const limitFileSize = ref(false) const limitFileSize = ref(false)
const percentage = ref(0) const percentage = ref(0)
const percentageFlage = ref(true) const percentageFlage = ref(true)
// //
const choseFile = async(e) => { const choseFile = async (e) => {
// return
// return if (!e.target.files.length) {
if (!e.target.files.length) { return
return }
} const fileR = new FileReader() // reader
const fileR = new FileReader() // reader const fileInput = e.target.files[0] //
const fileInput = e.target.files[0] // const maxSize = 5 * 1024 * 1024
const maxSize = 5 * 1024 * 1024 file.value = fileInput // file 便 func
file.value = fileInput // file 便 func percentage.value = 0
percentage.value = 0 if (file.value.size < maxSize) {
if (file.value.size < maxSize) { fileR.readAsArrayBuffer(file.value) // ArrayBuffer
fileR.readAsArrayBuffer(file.value) // ArrayBuffer fileR.onload = async (e) => {
fileR.onload = async e => { // arrayBuffer e dome e.target.result
// arrayBuffer e dome e.target.result const blob = e.target.result
const blob = e.target.result const spark = new SparkMD5.ArrayBuffer() // md5 md5
const spark = new SparkMD5.ArrayBuffer() // md5 md5 spark.append(blob) //
spark.append(blob) // fileMd5.value = spark.end() // a md5
fileMd5.value = spark.end() // a md5 const FileSliceCap = 1 * 1024 * 1024 //
const FileSliceCap = 1 * 1024 * 1024 // let start = 0 //
let start = 0 // let end = 0 // a
let end = 0 // a let i = 0 //
let i = 0 // formDataList.value = [] //
formDataList.value = [] // while (end < file.value.size) {
while (end < file.value.size) { // size
// size start = i * FileSliceCap //
start = i * FileSliceCap // end = (i + 1) * FileSliceCap //
end = (i + 1) * FileSliceCap // var fileSlice = file.value.slice(start, end) // file.slice h5
var fileSlice = file.value.slice(start, end) // file.slice h5 const formData = new window.FormData() // FormData
const formData = new window.FormData() // FormData formData.append('fileMd5', fileMd5.value) // Md5
formData.append('fileMd5', fileMd5.value) // Md5 formData.append('file', fileSlice) //
formData.append('file', fileSlice) // formData.append('chunkNumber', i) //
formData.append('chunkNumber', i) // formData.append('fileName', file.value.name) // formData.appen formData
formData.append('fileName', file.value.name) // formData.appen formData formDataList.value.push({ key: i, formData }) //
formDataList.value.push({ key: i, formData }) // i++
i++ }
const params = {
fileName: file.value.name,
fileMd5: fileMd5.value,
chunkTotal: formDataList.value.length
}
const res = await findFile(params)
//
const finishList = res.data.file.ExaFileChunk //
const IsFinish = res.data.file.IsFinish // md5
if (!IsFinish) {
//
waitUpLoad.value = formDataList.value.filter((all) => {
return !(
finishList &&
finishList.some((fi) => fi.FileChunkNumber === all.key)
) //
})
} else {
waitUpLoad.value = [] //
ElMessage.success('文件已秒传')
}
waitNum.value = waitUpLoad.value.length //
} }
} else {
limitFileSize.value = true
ElMessage('请上传小于5M文件')
}
}
const getFile = () => {
//
if (file.value === null) {
ElMessage('请先上传文件')
return
}
if (percentage.value === 100) {
percentageFlage.value = false
}
sliceFile() //
}
const sliceFile = () => {
waitUpLoad.value &&
waitUpLoad.value.forEach((item) => {
//
item.formData.append('chunkTotal', formDataList.value.length) //
const fileR = new FileReader() //
const fileF = item.formData.get('file')
fileR.readAsArrayBuffer(fileF)
fileR.onload = (e) => {
const spark = new SparkMD5.ArrayBuffer()
spark.append(e.target.result)
item.formData.append('chunkMd5', spark.end()) // md5
upLoadFileSlice(item)
}
})
}
watch(
() => waitNum.value,
() => {
percentage.value = Math.floor(
((formDataList.value.length - waitNum.value) /
formDataList.value.length) *
100
)
}
)
const upLoadFileSlice = async (item) => {
//
const fileRe = await breakpointContinue(item.formData)
if (fileRe.code !== 0) {
return
}
waitNum.value-- //
if (waitNum.value === 0) {
//
const params = { const params = {
fileName: file.value.name, fileName: file.value.name,
fileMd5: fileMd5.value, fileMd5: fileMd5.value
chunkTotal: formDataList.value.length
} }
const res = await findFile(params) const res = await breakpointContinueFinish(params)
// if (res.code === 0) {
const finishList = res.data.file.ExaFileChunk // //
const IsFinish = res.data.file.IsFinish // md5 const params = {
if (!IsFinish) { fileName: file.value.name,
// fileMd5: fileMd5.value,
waitUpLoad.value = formDataList.value.filter(all => { filePath: res.data.filePath
return !( }
finishList && ElMessage.success('上传成功')
finishList.some(fi => fi.FileChunkNumber === all.key) await removeChunk(params)
) //
})
} else {
waitUpLoad.value = [] //
ElMessage.success('文件已秒传')
} }
waitNum.value = waitUpLoad.value.length //
}
} else {
limitFileSize.value = true
ElMessage('请上传小于5M文件')
}
}
const getFile = () => {
//
if (file.value === null) {
ElMessage('请先上传文件')
return
}
if (percentage.value === 100) {
percentageFlage.value = false
}
sliceFile() //
}
const sliceFile = () => {
waitUpLoad.value &&
waitUpLoad.value.forEach(item => {
//
item.formData.append('chunkTotal', formDataList.value.length) //
const fileR = new FileReader() //
const fileF = item.formData.get('file')
fileR.readAsArrayBuffer(fileF)
fileR.onload = e => {
const spark = new SparkMD5.ArrayBuffer()
spark.append(e.target.result)
item.formData.append('chunkMd5', spark.end()) // md5
upLoadFileSlice(item)
}
})
}
watch(() => waitNum.value, () => { percentage.value = Math.floor(((formDataList.value.length - waitNum.value) / formDataList.value.length) * 100) })
const upLoadFileSlice = async(item) => {
//
const fileRe = await breakpointContinue(item.formData)
if (fileRe.code !== 0) {
return
}
waitNum.value-- //
if (waitNum.value === 0) {
//
const params = {
fileName: file.value.name,
fileMd5: fileMd5.value
}
const res = await breakpointContinueFinish(params)
if (res.code === 0) {
//
const params = {
fileName: file.value.name,
fileMd5: fileMd5.value,
filePath: res.data.filePath,
}
ElMessage.success('上传成功')
await removeChunk(params)
} }
} }
}
const FileInput = ref(null) const FileInput = ref(null)
const inputChange = () => { const inputChange = () => {
FileInput.value.dispatchEvent(new MouseEvent('click')) FileInput.value.dispatchEvent(new MouseEvent('click'))
} }
</script> </script>
<style lang='scss' scoped> <style lang="scss" scoped>
h3 { h3 {
margin: 40px 0 0; margin: 40px 0 0;
} }
ul { ul {
list-style-type: none; list-style-type: none;
padding: 0; padding: 0;
} }
li { li {
display: inline-block; display: inline-block;
margin: 0 10px; margin: 0 10px;
} }
a { a {
color: #42b983; color: #42b983;
} }
#fromCont{ #fromCont {
display: inline-block; display: inline-block;
} }
.fileUpload{ .fileUpload {
padding: 3px 10px; padding: 3px 10px;
font-size: 12px; font-size: 12px;
height: 20px; height: 20px;
@ -241,7 +239,7 @@ a {
border-radius: 4px; border-radius: 4px;
overflow: hidden; overflow: hidden;
display: inline-block; display: inline-block;
input{ input {
position: absolute; position: absolute;
font-size: 100px; font-size: 100px;
right: 0; right: 0;
@ -249,47 +247,48 @@ a {
opacity: 0; opacity: 0;
cursor: pointer; cursor: pointer;
} }
} }
.fileName{ .fileName {
display: inline-block; display: inline-block;
vertical-align: top; vertical-align: top;
margin: 6px 15px 0 15px; margin: 6px 15px 0 15px;
} }
.uploadBtn{ .uploadBtn {
position: relative; position: relative;
top: -10px; top: -10px;
margin-left: 15px; margin-left: 15px;
} }
.tips{ .tips {
margin-top: 30px; margin-top: 30px;
font-size: 14px; font-size: 14px;
font-weight: 400; font-weight: 400;
color: #606266; color: #606266;
} }
.el-divider{ .el-divider {
margin: 0 0 30px 0; margin: 0 0 30px 0;
} }
.list{ .list {
margin-top:15px; margin-top: 15px;
} }
.list-item { .list-item {
display: block; display: block;
margin-right: 10px; margin-right: 10px;
color: #606266; color: #606266;
line-height: 25px; line-height: 25px;
margin-bottom: 5px; margin-bottom: 5px;
width: 40%; width: 40%;
.percentage{ .percentage {
float: right; float: right;
} }
} }
.list-enter-active, .list-leave-active { .list-enter-active,
transition: all 1s; .list-leave-active {
} transition: all 1s;
.list-enter, .list-leave-to }
.list-enter, .list-leave-to
/* .list-leave-active for below version 2.1.8 */ { /* .list-leave-active for below version 2.1.8 */ {
opacity: 0; opacity: 0;
transform: translateY(-30px); transform: translateY(-30px);
} }
</style> </style>

View File

@ -1,13 +1,13 @@
<template> <template>
<div> <div>
<warning-bar title="在资源权限中将此角色的资源权限清空 或者不包含创建者的角色 即可屏蔽此客户资源的显示" /> <warning-bar
title="在资源权限中将此角色的资源权限清空 或者不包含创建者的角色 即可屏蔽此客户资源的显示"
/>
<div class="gva-table-box"> <div class="gva-table-box">
<div class="gva-btn-list"> <div class="gva-btn-list">
<el-button <el-button type="primary" icon="plus" @click="openDrawer"
type="primary" >新增</el-button
icon="plus" >
@click="openDrawer"
>新增</el-button>
</div> </div>
<el-table <el-table
ref="multipleTable" ref="multipleTable"
@ -16,15 +16,8 @@
tooltip-effect="dark" tooltip-effect="dark"
row-key="ID" row-key="ID"
> >
<el-table-column <el-table-column type="selection" width="55" />
type="selection" <el-table-column align="left" label="接入日期" width="180">
width="55"
/>
<el-table-column
align="left"
label="接入日期"
width="180"
>
<template #default="scope"> <template #default="scope">
<span>{{ formatDate(scope.row.CreatedAt) }}</span> <span>{{ formatDate(scope.row.CreatedAt) }}</span>
</template> </template>
@ -47,24 +40,22 @@
prop="sysUserId" prop="sysUserId"
width="120" width="120"
/> />
<el-table-column <el-table-column align="left" label="操作" min-width="160">
align="left"
label="操作"
min-width="160"
>
<template #default="scope"> <template #default="scope">
<el-button <el-button
type="primary" type="primary"
link link
icon="edit" icon="edit"
@click="updateCustomer(scope.row)" @click="updateCustomer(scope.row)"
>变更</el-button> >变更</el-button
>
<el-button <el-button
type="primary" type="primary"
link link
icon="delete" icon="delete"
@click="deleteCustomer(scope.row)" @click="deleteCustomer(scope.row)"
>删除</el-button> >删除</el-button
>
</template> </template>
</el-table-column> </el-table-column>
</el-table> </el-table>
@ -90,29 +81,16 @@
<span class="text-lg">客户</span> <span class="text-lg">客户</span>
<div> <div>
<el-button @click="closeDrawer"> </el-button> <el-button @click="closeDrawer"> </el-button>
<el-button <el-button type="primary" @click="enterDrawer"> </el-button>
type="primary"
@click="enterDrawer"
> </el-button>
</div> </div>
</div> </div>
</template> </template>
<el-form <el-form :inline="true" :model="form" label-width="80px">
:inline="true"
:model="form"
label-width="80px"
>
<el-form-item label="客户名"> <el-form-item label="客户名">
<el-input <el-input v-model="form.customerName" autocomplete="off" />
v-model="form.customerName"
autocomplete="off"
/>
</el-form-item> </el-form-item>
<el-form-item label="客户电话"> <el-form-item label="客户电话">
<el-input <el-input v-model="form.customerPhoneData" autocomplete="off" />
v-model="form.customerPhoneData"
autocomplete="off"
/>
</el-form-item> </el-form-item>
</el-form> </el-form>
</el-drawer> </el-drawer>
@ -120,116 +98,118 @@
</template> </template>
<script setup> <script setup>
import { import {
createExaCustomer, createExaCustomer,
updateExaCustomer, updateExaCustomer,
deleteExaCustomer, deleteExaCustomer,
getExaCustomer, getExaCustomer,
getExaCustomerList getExaCustomerList
} from '@/api/customer' } from '@/api/customer'
import WarningBar from '@/components/warningBar/warningBar.vue' import WarningBar from '@/components/warningBar/warningBar.vue'
import { ref } from 'vue' import { ref } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus' import { ElMessage, ElMessageBox } from 'element-plus'
import { formatDate } from '@/utils/format' import { formatDate } from '@/utils/format'
defineOptions({ defineOptions({
name: 'Customer' name: 'Customer'
}) })
const form = ref({ const form = ref({
customerName: '',
customerPhoneData: ''
})
const page = ref(1)
const total = ref(0)
const pageSize = ref(10)
const tableData = ref([])
//
const handleSizeChange = (val) => {
pageSize.value = val
getTableData()
}
const handleCurrentChange = (val) => {
page.value = val
getTableData()
}
//
const getTableData = async() => {
const table = await getExaCustomerList({ page: page.value, pageSize: pageSize.value })
if (table.code === 0) {
tableData.value = table.data.list
total.value = table.data.total
page.value = table.data.page
pageSize.value = table.data.pageSize
}
}
getTableData()
const drawerFormVisible = ref(false)
const type = ref('')
const updateCustomer = async(row) => {
const res = await getExaCustomer({ ID: row.ID })
type.value = 'update'
if (res.code === 0) {
form.value = res.data.customer
drawerFormVisible.value = true
}
}
const closeDrawer = () => {
drawerFormVisible.value = false
form.value = {
customerName: '', customerName: '',
customerPhoneData: '' customerPhoneData: ''
}
}
const deleteCustomer = async(row) => {
ElMessageBox.confirm('确定要删除吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(async() => {
const res = await deleteExaCustomer({ ID: row.ID })
if (res.code === 0) {
ElMessage({
type: 'success',
message: '删除成功'
})
if (tableData.value.length === 1 && page.value > 1) {
page.value--
}
getTableData()
}
}) })
}
const enterDrawer = async() => {
let res
switch (type.value) {
case 'create':
res = await createExaCustomer(form.value)
break
case 'update':
res = await updateExaCustomer(form.value)
break
default:
res = await createExaCustomer(form.value)
break
}
if (res.code === 0) { const page = ref(1)
closeDrawer() const total = ref(0)
const pageSize = ref(10)
const tableData = ref([])
//
const handleSizeChange = (val) => {
pageSize.value = val
getTableData() getTableData()
} }
}
const openDrawer = () => {
type.value = 'create'
drawerFormVisible.value = true
}
const handleCurrentChange = (val) => {
page.value = val
getTableData()
}
//
const getTableData = async () => {
const table = await getExaCustomerList({
page: page.value,
pageSize: pageSize.value
})
if (table.code === 0) {
tableData.value = table.data.list
total.value = table.data.total
page.value = table.data.page
pageSize.value = table.data.pageSize
}
}
getTableData()
const drawerFormVisible = ref(false)
const type = ref('')
const updateCustomer = async (row) => {
const res = await getExaCustomer({ ID: row.ID })
type.value = 'update'
if (res.code === 0) {
form.value = res.data.customer
drawerFormVisible.value = true
}
}
const closeDrawer = () => {
drawerFormVisible.value = false
form.value = {
customerName: '',
customerPhoneData: ''
}
}
const deleteCustomer = async (row) => {
ElMessageBox.confirm('确定要删除吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(async () => {
const res = await deleteExaCustomer({ ID: row.ID })
if (res.code === 0) {
ElMessage({
type: 'success',
message: '删除成功'
})
if (tableData.value.length === 1 && page.value > 1) {
page.value--
}
getTableData()
}
})
}
const enterDrawer = async () => {
let res
switch (type.value) {
case 'create':
res = await createExaCustomer(form.value)
break
case 'update':
res = await updateExaCustomer(form.value)
break
default:
res = await createExaCustomer(form.value)
break
}
if (res.code === 0) {
closeDrawer()
getTableData()
}
}
const openDrawer = () => {
type.value = 'create'
drawerFormVisible.value = true
}
</script> </script>
<style></style> <style></style>

View File

@ -1,10 +1,7 @@
<template> <template>
<div> <div>
<router-view v-slot="{ Component }"> <router-view v-slot="{ Component }">
<transition <transition mode="out-in" name="el-fade-in-linear">
mode="out-in"
name="el-fade-in-linear"
>
<keep-alive :include="routerStore.keepAliveRouters"> <keep-alive :include="routerStore.keepAliveRouters">
<component :is="Component" /> <component :is="Component" />
</keep-alive> </keep-alive>
@ -14,9 +11,9 @@
</template> </template>
<script setup> <script setup>
import { useRouterStore } from '@/pinia/modules/router' import { useRouterStore } from '@/pinia/modules/router'
const routerStore = useRouterStore() const routerStore = useRouterStore()
defineOptions({ defineOptions({
name: 'Example' name: 'Example'
}) })
</script> </script>

View File

@ -1,25 +1,16 @@
<template> <template>
<div v-loading.fullscreen.lock="fullscreenLoading"> <div v-loading.fullscreen.lock="fullscreenLoading">
<div class="gva-table-box"> <div class="gva-table-box">
<warning-bar <warning-bar title="点击“文件名/备注”可以编辑文件名或者备注内容。" />
title="点击“文件名/备注”可以编辑文件名或者备注内容。"
/>
<div class="gva-btn-list gap-3"> <div class="gva-btn-list gap-3">
<upload-common <upload-common :image-common="imageCommon" @on-success="getTableData" />
:image-common="imageCommon"
@on-success="getTableData"
/>
<upload-image <upload-image
:image-url="imageUrl" :image-url="imageUrl"
:file-size="512" :file-size="512"
:max-w-h="1080" :max-w-h="1080"
@on-success="getTableData" @on-success="getTableData"
/> />
<el-button <el-button type="primary" icon="upload" @click="importUrlFunc">
type="primary"
icon="upload"
@click="importUrlFunc"
>
导入URL 导入URL
</el-button> </el-button>
<el-input <el-input
@ -27,33 +18,18 @@
class="w-72" class="w-72"
placeholder="请输入文件名或备注" placeholder="请输入文件名或备注"
/> />
<el-button <el-button type="primary" icon="search" @click="getTableData"
type="primary" >查询</el-button
icon="search" >
@click="getTableData"
>查询</el-button>
</div> </div>
<el-table :data="tableData"> <el-table :data="tableData">
<el-table-column <el-table-column align="left" label="预览" width="100">
align="left"
label="预览"
width="100"
>
<template #default="scope"> <template #default="scope">
<CustomPic <CustomPic pic-type="file" :pic-src="scope.row.url" preview />
pic-type="file"
:pic-src="scope.row.url"
preview
/>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column <el-table-column align="left" label="日期" prop="UpdatedAt" width="180">
align="left"
label="日期"
prop="UpdatedAt"
width="180"
>
<template #default="scope"> <template #default="scope">
<div>{{ formatDate(scope.row.UpdatedAt) }}</div> <div>{{ formatDate(scope.row.UpdatedAt) }}</div>
</template> </template>
@ -65,50 +41,37 @@
width="180" width="180"
> >
<template #default="scope"> <template #default="scope">
<div <div class="name" @click="editFileNameFunc(scope.row)">
class="name" {{ scope.row.name }}
@click="editFileNameFunc(scope.row)" </div>
>{{ scope.row.name }}</div>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column <el-table-column align="left" label="链接" prop="url" min-width="300" />
align="left" <el-table-column align="left" label="标签" prop="tag" width="100">
label="链接"
prop="url"
min-width="300"
/>
<el-table-column
align="left"
label="标签"
prop="tag"
width="100"
>
<template #default="scope"> <template #default="scope">
<el-tag <el-tag
:type="scope.row.tag === 'jpg' ? 'info' : 'success'" :type="scope.row.tag === 'jpg' ? 'info' : 'success'"
disable-transitions disable-transitions
>{{ scope.row.tag }} >{{ scope.row.tag }}
</el-tag> </el-tag>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column <el-table-column align="left" label="操作" width="160">
align="left"
label="操作"
width="160"
>
<template #default="scope"> <template #default="scope">
<el-button <el-button
icon="download" icon="download"
type="primary" type="primary"
link link
@click="downloadFile(scope.row)" @click="downloadFile(scope.row)"
>下载</el-button> >下载</el-button
>
<el-button <el-button
icon="delete" icon="delete"
type="primary" type="primary"
link link
@click="deleteFileFunc(scope.row)" @click="deleteFileFunc(scope.row)"
>删除</el-button> >删除</el-button
>
</template> </template>
</el-table-column> </el-table-column>
</el-table> </el-table>
@ -129,175 +92,189 @@
</template> </template>
<script setup> <script setup>
import {getFileList, deleteFile, editFileName, importURL} from '@/api/fileUploadAndDownload' import {
import { downloadImage } from '@/utils/downloadImg' getFileList,
import CustomPic from '@/components/customPic/index.vue' deleteFile,
import UploadImage from '@/components/upload/image.vue' editFileName,
import UploadCommon from '@/components/upload/common.vue' importURL
import {CreateUUID, formatDate} from '@/utils/format' } from '@/api/fileUploadAndDownload'
import WarningBar from '@/components/warningBar/warningBar.vue' import { downloadImage } from '@/utils/downloadImg'
import CustomPic from '@/components/customPic/index.vue'
import UploadImage from '@/components/upload/image.vue'
import UploadCommon from '@/components/upload/common.vue'
import { CreateUUID, formatDate } from '@/utils/format'
import WarningBar from '@/components/warningBar/warningBar.vue'
import { ref } from 'vue' import { ref } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus' import { ElMessage, ElMessageBox } from 'element-plus'
defineOptions({ defineOptions({
name: 'Upload', name: 'Upload'
})
const path = ref(import.meta.env.VITE_BASE_API)
const imageUrl = ref('')
const imageCommon = ref('')
const page = ref(1)
const total = ref(0)
const pageSize = ref(10)
const search = ref({})
const tableData = ref([])
//
const handleSizeChange = (val) => {
pageSize.value = val
getTableData()
}
const handleCurrentChange = (val) => {
page.value = val
getTableData()
}
//
const getTableData = async() => {
const table = await getFileList({ page: page.value, pageSize: pageSize.value, ...search.value })
if (table.code === 0) {
tableData.value = table.data.list
total.value = table.data.total
page.value = table.data.page
pageSize.value = table.data.pageSize
}
}
getTableData()
const deleteFileFunc = async(row) => {
ElMessageBox.confirm('此操作将永久删除文件, 是否继续?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
}) })
.then(async() => {
const res = await deleteFile(row) const path = ref(import.meta.env.VITE_BASE_API)
if (res.code === 0) {
ElMessage({ const imageUrl = ref('')
type: 'success', const imageCommon = ref('')
message: '删除成功!',
}) const page = ref(1)
if (tableData.value.length === 1 && page.value > 1) { const total = ref(0)
page.value-- const pageSize = ref(10)
const search = ref({})
const tableData = ref([])
//
const handleSizeChange = (val) => {
pageSize.value = val
getTableData()
}
const handleCurrentChange = (val) => {
page.value = val
getTableData()
}
//
const getTableData = async () => {
const table = await getFileList({
page: page.value,
pageSize: pageSize.value,
...search.value
})
if (table.code === 0) {
tableData.value = table.data.list
total.value = table.data.total
page.value = table.data.page
pageSize.value = table.data.pageSize
}
}
getTableData()
const deleteFileFunc = async (row) => {
ElMessageBox.confirm('此操作将永久删除文件, 是否继续?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
})
.then(async () => {
const res = await deleteFile(row)
if (res.code === 0) {
ElMessage({
type: 'success',
message: '删除成功!'
})
if (tableData.value.length === 1 && page.value > 1) {
page.value--
}
getTableData()
} }
getTableData()
}
})
.catch(() => {
ElMessage({
type: 'info',
message: '已取消删除',
}) })
}) .catch(() => {
} ElMessage({
type: 'info',
const downloadFile = (row) => { message: '已取消删除'
if (row.url.indexOf('http://') > -1 || row.url.indexOf('https://') > -1) {
downloadImage(row.url, row.name)
} else {
downloadImage(path.value + '/' + row.url, row.name)
}
}
/**
* 编辑文件名或者备注
* @param row
* @returns {Promise<void>}
*/
const editFileNameFunc = async(row) => {
ElMessageBox.prompt('请输入文件名或者备注', '编辑', {
confirmButtonText: '确定',
cancelButtonText: '取消',
inputPattern: /\S/,
inputErrorMessage: '不能为空',
inputValue: row.name
}).then(async({ value }) => {
row.name = value
// console.log(row)
const res = await editFileName(row)
if (res.code === 0) {
ElMessage({
type: 'success',
message: '编辑成功!',
})
await getTableData()
}
}).catch(() => {
ElMessage({
type: 'info',
message: '取消修改'
})
})
}
/**
* 导入URL
*/
const importUrlFunc = () => {
ElMessageBox.prompt('格式:文件名|链接或者仅链接。', '导入', {
confirmButtonText: '确定',
cancelButtonText: '取消',
inputType: 'textarea',
inputPlaceholder: '我的图片|https://my-oss.com/my.png\nhttps://my-oss.com/my_1.png',
inputPattern: /\S/,
inputErrorMessage: '不能为空',
}).then(async({ value }) => {
let data = value.split('\n')
let importData = []
data.forEach(item => {
let oneData = item.trim().split('|')
let url, name
if (oneData.length > 1) {
name = oneData[0].trim()
url = oneData[1]
} else {
url = oneData[0].trim()
let str = url.substring(url.lastIndexOf('/') + 1)
name = str.substring(0, str.lastIndexOf('.'))
}
if (url) {
importData.push({
name: name,
url: url,
tag: url.substring(url.lastIndexOf(".") + 1),
key: CreateUUID()
}) })
}
})
const res = await importURL(importData)
if (res.code === 0) {
ElMessage({
type: 'success',
message: '导入成功!',
}) })
await getTableData() }
const downloadFile = (row) => {
if (row.url.indexOf('http://') > -1 || row.url.indexOf('https://') > -1) {
downloadImage(row.url, row.name)
} else {
downloadImage(path.value + '/' + row.url, row.name)
} }
}).catch(() => { }
ElMessage({
type: 'info', /**
message: '取消导入', * 编辑文件名或者备注
* @param row
* @returns {Promise<void>}
*/
const editFileNameFunc = async (row) => {
ElMessageBox.prompt('请输入文件名或者备注', '编辑', {
confirmButtonText: '确定',
cancelButtonText: '取消',
inputPattern: /\S/,
inputErrorMessage: '不能为空',
inputValue: row.name
}) })
}) .then(async ({ value }) => {
} row.name = value
// console.log(row)
const res = await editFileName(row)
if (res.code === 0) {
ElMessage({
type: 'success',
message: '编辑成功!'
})
await getTableData()
}
})
.catch(() => {
ElMessage({
type: 'info',
message: '取消修改'
})
})
}
/**
* 导入URL
*/
const importUrlFunc = () => {
ElMessageBox.prompt('格式:文件名|链接或者仅链接。', '导入', {
confirmButtonText: '确定',
cancelButtonText: '取消',
inputType: 'textarea',
inputPlaceholder:
'我的图片|https://my-oss.com/my.png\nhttps://my-oss.com/my_1.png',
inputPattern: /\S/,
inputErrorMessage: '不能为空'
})
.then(async ({ value }) => {
let data = value.split('\n')
let importData = []
data.forEach((item) => {
let oneData = item.trim().split('|')
let url, name
if (oneData.length > 1) {
name = oneData[0].trim()
url = oneData[1]
} else {
url = oneData[0].trim()
let str = url.substring(url.lastIndexOf('/') + 1)
name = str.substring(0, str.lastIndexOf('.'))
}
if (url) {
importData.push({
name: name,
url: url,
tag: url.substring(url.lastIndexOf('.') + 1),
key: CreateUUID()
})
}
})
const res = await importURL(importData)
if (res.code === 0) {
ElMessage({
type: 'success',
message: '导入成功!'
})
await getTableData()
}
})
.catch(() => {
ElMessage({
type: 'info',
message: '取消导入'
})
})
}
</script> </script>
<style scoped> <style scoped>
.name { .name {
cursor: pointer; cursor: pointer;
} }
</style> </style>

View File

@ -1,58 +1,74 @@
<template> <template>
<div class="rounded-lg flex items-center justify-evenly w-full h-full relative md:w-screen md:h-screen md:bg-[#194bfb] overflow-hidden"> <div
<div class="rounded-md w-full h-full flex items-center justify-center overflow-hidden"> class="rounded-lg flex items-center justify-evenly w-full h-full relative md:w-screen md:h-screen md:bg-[#194bfb] overflow-hidden"
<div class="oblique h-[130%] w-3/5 bg-white dark:bg-slate-900 transform -rotate-12 absolute -ml-80" /> >
<div
class="rounded-md w-full h-full flex items-center justify-center overflow-hidden"
>
<div
class="oblique h-[130%] w-3/5 bg-white dark:bg-slate-900 transform -rotate-12 absolute -ml-80"
/>
<div <div
v-if="!page.showForm" v-if="!page.showForm"
:class="[page.showReadme ?'slide-out-right' :'slide-in-fwd-top' ]" :class="[page.showReadme ? 'slide-out-right' : 'slide-in-fwd-top']"
> >
<div class=" text-lg"> <div class="text-lg">
<div class="font-sans text-4xl font-bold text-center mb-4 dark:text-white">GIN-VUE-ADMIN</div> <div
class="font-sans text-4xl font-bold text-center mb-4 dark:text-white"
>
GIN-VUE-ADMIN
</div>
<p class="text-gray-600 dark:text-gray-300 mb-2">初始化须知</p> <p class="text-gray-600 dark:text-gray-300 mb-2">初始化须知</p>
<p class="text-gray-600 dark:text-gray-300 mb-2">1.您需有用一定的VUE和GOLANG基础</p> <p class="text-gray-600 dark:text-gray-300 mb-2">
<p class="text-gray-600 dark:text-gray-300 mb-2">2.请您确认是否已经阅读过<a 1.您需有用一定的VUE和GOLANG基础
class="text-blue-600 font-bold" </p>
href="https://www.gin-vue-admin.com" <p class="text-gray-600 dark:text-gray-300 mb-2">
target="_blank" 2.请您确认是否已经阅读过<a
>官方文档</a> <a class="text-blue-600 font-bold"
class="text-blue-600 font-bold" href="https://www.gin-vue-admin.com"
href="https://www.bilibili.com/video/BV1kv4y1g7nT?p=2" target="_blank"
target="_blank" >官方文档</a
>初始化视频</a></p>
<p class="text-gray-600 dark:text-gray-300 mb-2">3.请您确认是否了解后续的配置流程</p>
<p class="text-gray-600 dark:text-gray-300 mb-2">4.如果您使用mysql数据库请确认数据库引擎为<span class="text-red-600 font-bold text-3xl ml-2 ">innoDB</span></p>
<p class="text-gray-600 dark:text-gray-300 mb-2">开发组不为文档中书写过的内容提供无偿服务</p>
<p class="flex items-center justify-between mt-8">
<el-button
type="primary"
size="large"
@click="goDoc"
> >
<a
class="text-blue-600 font-bold"
href="https://www.bilibili.com/video/BV1kv4y1g7nT?p=2"
target="_blank"
>初始化视频</a
>
</p>
<p class="text-gray-600 dark:text-gray-300 mb-2">
3.请您确认是否了解后续的配置流程
</p>
<p class="text-gray-600 dark:text-gray-300 mb-2">
4.如果您使用mysql数据库请确认数据库引擎为<span
class="text-red-600 font-bold text-3xl ml-2"
>innoDB</span
>
</p>
<p class="text-gray-600 dark:text-gray-300 mb-2">
开发组不为文档中书写过的内容提供无偿服务
</p>
<p class="flex items-center justify-between mt-8">
<el-button type="primary" size="large" @click="goDoc">
阅读文档 阅读文档
</el-button> </el-button>
<el-button <el-button type="primary" size="large" @click="showNext">
type="primary"
size="large"
@click="showNext"
>
我已确认 我已确认
</el-button> </el-button>
</p> </p>
</div> </div>
</div> </div>
<div <div
v-if="page.showForm " v-if="page.showForm"
:class="[ page.showForm ? 'slide-in-left' : 'slide-out-right' ]" :class="[page.showForm ? 'slide-in-left' : 'slide-out-right']"
class="w-96" class="w-96"
> >
<el-form <el-form ref="formRef" :model="form" label-width="100px" size="large">
ref="formRef"
:model="form"
label-width="100px"
size="large"
>
<el-form-item label="管理员密码"> <el-form-item label="管理员密码">
<el-input v-model="form.adminPassword" placeholder="admin账号的默认密码"></el-input> <el-input
v-model="form.adminPassword"
placeholder="admin账号的默认密码"
></el-input>
</el-form-item> </el-form-item>
<el-form-item label="数据库类型"> <el-form-item label="数据库类型">
<el-select <el-select
@ -61,79 +77,35 @@
class="w-full" class="w-full"
@change="changeDB" @change="changeDB"
> >
<el-option <el-option key="mysql" label="mysql" value="mysql" />
key="mysql" <el-option key="pgsql" label="pgsql" value="pgsql" />
label="mysql" <el-option key="oracle" label="oracle" value="oracle" />
value="mysql" <el-option key="mssql" label="mssql" value="mssql" />
/> <el-option key="sqlite" label="sqlite" value="sqlite" />
<el-option
key="pgsql"
label="pgsql"
value="pgsql"
/>
<el-option
key="oracle"
label="oracle"
value="oracle"
/>
<el-option
key="mssql"
label="mssql"
value="mssql"
/>
<el-option
key="sqlite"
label="sqlite"
value="sqlite"
/>
</el-select> </el-select>
</el-form-item> </el-form-item>
<el-form-item <el-form-item v-if="form.dbType !== 'sqlite'" label="host">
v-if="form.dbType !== 'sqlite'" <el-input v-model="form.host" placeholder="请输入数据库链接" />
label="host"
>
<el-input
v-model="form.host"
placeholder="请输入数据库链接"
/>
</el-form-item> </el-form-item>
<el-form-item <el-form-item v-if="form.dbType !== 'sqlite'" label="port">
v-if="form.dbType !== 'sqlite'" <el-input v-model="form.port" placeholder="请输入数据库端口" />
label="port"
>
<el-input
v-model="form.port"
placeholder="请输入数据库端口"
/>
</el-form-item> </el-form-item>
<el-form-item <el-form-item v-if="form.dbType !== 'sqlite'" label="userName">
v-if="form.dbType !== 'sqlite'"
label="userName"
>
<el-input <el-input
v-model="form.userName" v-model="form.userName"
placeholder="请输入数据库用户名" placeholder="请输入数据库用户名"
/> />
</el-form-item> </el-form-item>
<el-form-item <el-form-item v-if="form.dbType !== 'sqlite'" label="password">
v-if="form.dbType !== 'sqlite'"
label="password"
>
<el-input <el-input
v-model="form.password" v-model="form.password"
placeholder="请输入数据库密码(没有则为空)" placeholder="请输入数据库密码(没有则为空)"
/> />
</el-form-item> </el-form-item>
<el-form-item label="dbName"> <el-form-item label="dbName">
<el-input <el-input v-model="form.dbName" placeholder="请输入数据库名称" />
v-model="form.dbName"
placeholder="请输入数据库名称"
/>
</el-form-item> </el-form-item>
<el-form-item <el-form-item v-if="form.dbType === 'sqlite'" label="dbPath">
v-if="form.dbType === 'sqlite'"
label="dbPath"
>
<el-input <el-input
v-model="form.dbPath" v-model="form.dbPath"
placeholder="请输入sqlite数据库文件存放路径" placeholder="请输入sqlite数据库文件存放路径"
@ -141,255 +113,249 @@
</el-form-item> </el-form-item>
<el-form-item> <el-form-item>
<div style="text-align: right"> <div style="text-align: right">
<el-button <el-button type="primary" @click="onSubmit">立即初始化</el-button>
type="primary"
@click="onSubmit"
>立即初始化</el-button>
</div> </div>
</el-form-item> </el-form-item>
</el-form> </el-form>
</div> </div>
</div> </div>
<div class="hidden md:block w-1/2 h-full float-right bg-[#194bfb]"><img <div class="hidden md:block w-1/2 h-full float-right bg-[#194bfb]">
class="h-full" <img class="h-full" src="@/assets/login_right_banner.jpg" alt="banner" />
src="@/assets/login_right_banner.jpg" </div>
alt="banner"
></div>
</div> </div>
</template> </template>
<script setup> <script setup>
// @ts-ignore // @ts-ignore
import { initDB } from '@/api/initdb' import { initDB } from '@/api/initdb'
import { reactive, ref } from 'vue' import { reactive, ref } from 'vue'
import { ElLoading, ElMessage } from 'element-plus' import { ElLoading, ElMessage } from 'element-plus'
import { useRouter } from 'vue-router' import { useRouter } from 'vue-router'
defineOptions({ defineOptions({
name: 'Init', name: 'Init'
})
const router = useRouter()
const page = reactive({
showReadme: false,
showForm: false
})
const showNext = () => {
page.showReadme = false
setTimeout(() => {
page.showForm = true
}, 20)
}
const goDoc = () => {
window.open('https://www.gin-vue-admin.com/guide/start-quickly/env.html')
}
const out = ref(false)
const form = reactive({
adminPassword: '123456',
dbType: 'mysql',
host: '127.0.0.1',
port: '3306',
userName: 'root',
password: '',
dbName: 'gva',
dbPath: ''
})
const changeDB = (val) => {
switch (val) {
case 'mysql':
Object.assign(form, {
adminPassword:'123456',
reAdminPassword:'',
dbType: 'mysql',
host: '127.0.0.1',
port: '3306',
userName: 'root',
password: '',
dbName: 'gva',
dbPath: ''
})
break
case 'pgsql':
Object.assign(form, {
adminPassword:'123456',
dbType: 'pgsql',
host: '127.0.0.1',
port: '5432',
userName: 'postgres',
password: '',
dbName: 'gva',
dbPath: ''
})
break
case 'oracle':
Object.assign(form, {
adminPassword:'123456',
dbType: 'oracle',
host: '127.0.0.1',
port: '1521',
userName: 'oracle',
password: '',
dbName: 'gva',
dbPath: ''
})
break
case 'mssql':
Object.assign(form, {
adminPassword:'123456',
dbType: 'mssql',
host: '127.0.0.1',
port: '1433',
userName: 'mssql',
password: '',
dbName: 'gva',
dbPath: ''
})
break
case 'sqlite':
Object.assign(form, {
adminPassword:'123456',
dbType: 'sqlite',
host: '',
port: '',
userName: '',
password: '',
dbName: 'gva',
dbPath: ''
})
break
default:
Object.assign(form, {
adminPassword:'123456',
dbType: 'mysql',
host: '127.0.0.1',
port: '3306',
userName: 'root',
password: '',
dbName: 'gva',
dbPath: ''
})
}
}
const onSubmit = async() => {
if (form.adminPassword.length < 6) {
ElMessage({
type: 'error',
message: '密码长度不能小于6位',
})
return
}
const loading = ElLoading.service({
lock: true,
text: '正在初始化数据库,请稍候',
spinner: 'loading',
background: 'rgba(0, 0, 0, 0.7)',
}) })
try {
const res = await initDB(form) const router = useRouter()
if (res.code === 0) {
out.value = true const page = reactive({
ElMessage({ showReadme: false,
type: 'success', showForm: false
message: res.msg, })
})
router.push({ name: 'Login' }) const showNext = () => {
} page.showReadme = false
loading.close() setTimeout(() => {
} catch (_) { page.showForm = true
loading.close() }, 20)
}
const goDoc = () => {
window.open('https://www.gin-vue-admin.com/guide/start-quickly/env.html')
}
const out = ref(false)
const form = reactive({
adminPassword: '123456',
dbType: 'mysql',
host: '127.0.0.1',
port: '3306',
userName: 'root',
password: '',
dbName: 'gva',
dbPath: ''
})
const changeDB = (val) => {
switch (val) {
case 'mysql':
Object.assign(form, {
adminPassword: '123456',
reAdminPassword: '',
dbType: 'mysql',
host: '127.0.0.1',
port: '3306',
userName: 'root',
password: '',
dbName: 'gva',
dbPath: ''
})
break
case 'pgsql':
Object.assign(form, {
adminPassword: '123456',
dbType: 'pgsql',
host: '127.0.0.1',
port: '5432',
userName: 'postgres',
password: '',
dbName: 'gva',
dbPath: ''
})
break
case 'oracle':
Object.assign(form, {
adminPassword: '123456',
dbType: 'oracle',
host: '127.0.0.1',
port: '1521',
userName: 'oracle',
password: '',
dbName: 'gva',
dbPath: ''
})
break
case 'mssql':
Object.assign(form, {
adminPassword: '123456',
dbType: 'mssql',
host: '127.0.0.1',
port: '1433',
userName: 'mssql',
password: '',
dbName: 'gva',
dbPath: ''
})
break
case 'sqlite':
Object.assign(form, {
adminPassword: '123456',
dbType: 'sqlite',
host: '',
port: '',
userName: '',
password: '',
dbName: 'gva',
dbPath: ''
})
break
default:
Object.assign(form, {
adminPassword: '123456',
dbType: 'mysql',
host: '127.0.0.1',
port: '3306',
userName: 'root',
password: '',
dbName: 'gva',
dbPath: ''
})
}
}
const onSubmit = async () => {
if (form.adminPassword.length < 6) {
ElMessage({
type: 'error',
message: '密码长度不能小于6位'
})
return
}
const loading = ElLoading.service({
lock: true,
text: '正在初始化数据库,请稍候',
spinner: 'loading',
background: 'rgba(0, 0, 0, 0.7)'
})
try {
const res = await initDB(form)
if (res.code === 0) {
out.value = true
ElMessage({
type: 'success',
message: res.msg
})
router.push({ name: 'Login' })
}
loading.close()
} catch (_) {
loading.close()
}
} }
}
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.slide-in-fwd-top {
.slide-in-fwd-top { -webkit-animation: slide-in-fwd-top 0.4s
-webkit-animation: slide-in-fwd-top 0.4s cubic-bezier(0.25, 0.46, 0.45, 0.94) cubic-bezier(0.25, 0.46, 0.45, 0.94) both;
both; animation: slide-in-fwd-top 0.4s cubic-bezier(0.25, 0.46, 0.45, 0.94) both;
animation: slide-in-fwd-top 0.4s cubic-bezier(0.25, 0.46, 0.45, 0.94) both;
}
.slide-out-right {
-webkit-animation: slide-out-right 0.5s cubic-bezier(0.55, 0.085, 0.68, 0.53)
both;
animation: slide-out-right 0.5s cubic-bezier(0.55, 0.085, 0.68, 0.53) both;
}
.slide-in-left {
-webkit-animation: slide-in-left 0.5s cubic-bezier(0.25, 0.46, 0.45, 0.94)
both;
animation: slide-in-left 0.5s cubic-bezier(0.25, 0.46, 0.45, 0.94) both;
}
@-webkit-keyframes slide-in-fwd-top {
0% {
transform: translateZ(-1400px) translateY(-800px);
opacity: 0;
} }
100% { .slide-out-right {
transform: translateZ(0) translateY(0); -webkit-animation: slide-out-right 0.5s
opacity: 1; cubic-bezier(0.55, 0.085, 0.68, 0.53) both;
animation: slide-out-right 0.5s cubic-bezier(0.55, 0.085, 0.68, 0.53) both;
} }
} .slide-in-left {
@keyframes slide-in-fwd-top { -webkit-animation: slide-in-left 0.5s cubic-bezier(0.25, 0.46, 0.45, 0.94)
0% { both;
transform: translateZ(-1400px) translateY(-800px); animation: slide-in-left 0.5s cubic-bezier(0.25, 0.46, 0.45, 0.94) both;
opacity: 0;
} }
100% { @-webkit-keyframes slide-in-fwd-top {
transform: translateZ(0) translateY(0); 0% {
opacity: 1; transform: translateZ(-1400px) translateY(-800px);
opacity: 0;
}
100% {
transform: translateZ(0) translateY(0);
opacity: 1;
}
} }
} @keyframes slide-in-fwd-top {
@-webkit-keyframes slide-out-right { 0% {
0% { transform: translateZ(-1400px) translateY(-800px);
transform: translateX(0); opacity: 0;
opacity: 1; }
100% {
transform: translateZ(0) translateY(0);
opacity: 1;
}
} }
100% { @-webkit-keyframes slide-out-right {
transform: translateX(1000px); 0% {
opacity: 0; transform: translateX(0);
opacity: 1;
}
100% {
transform: translateX(1000px);
opacity: 0;
}
} }
} @keyframes slide-out-right {
@keyframes slide-out-right { 0% {
0% { transform: translateX(0);
transform: translateX(0); opacity: 1;
opacity: 1; }
100% {
transform: translateX(1000px);
opacity: 0;
}
} }
100% { @-webkit-keyframes slide-in-left {
transform: translateX(1000px); 0% {
opacity: 0; transform: translateX(-1000px);
opacity: 0;
}
100% {
transform: translateX(0);
opacity: 1;
}
} }
} @keyframes slide-in-left {
@-webkit-keyframes slide-in-left { 0% {
0% { transform: translateX(-1000px);
transform: translateX(-1000px); opacity: 0;
opacity: 0; }
100% {
transform: translateX(0);
opacity: 1;
}
} }
100% { @media (max-width: 750px) {
transform: translateX(0); .form {
opacity: 1; width: 94vw !important;
padding: 0;
}
} }
}
@keyframes slide-in-left {
0% {
transform: translateX(-1000px);
opacity: 0;
}
100% {
transform: translateX(0);
opacity: 1;
}
}
@media (max-width: 750px) {
.form {
width: 94vw !important;
padding: 0;
}
}
</style> </style>

View File

@ -7,9 +7,9 @@
<template #title> <template #title>
<div <div
v-if="!isCollapse" v-if="!isCollapse"
class="flex items-center " class="flex items-center"
:style="{ :style="{
height : sideHeight, height: sideHeight
}" }"
> >
<el-icon v-if="routerInfo.meta.icon"> <el-icon v-if="routerInfo.meta.icon">
@ -29,39 +29,38 @@
</template> </template>
<script setup> <script setup>
import { inject,computed } from 'vue' import { inject, computed } from 'vue'
import { useAppStore } from '@/pinia' import { useAppStore } from '@/pinia'
import { storeToRefs } from 'pinia' import { storeToRefs } from 'pinia'
const appStore = useAppStore() const appStore = useAppStore()
const { config } = storeToRefs(appStore) const { config } = storeToRefs(appStore)
defineOptions({ defineOptions({
name: 'AsyncSubmenu', name: 'AsyncSubmenu'
}) })
defineProps({ defineProps({
routerInfo: { routerInfo: {
default: function() { default: function () {
return null return null
}, },
type: Object type: Object
}, }
}) })
const isCollapse = inject('isCollapse', { const isCollapse = inject('isCollapse', {
default: false, default: false
}) })
const sideHeight = computed(() => {
return config.value.layout_side_item_height + 'px'
})
const sideHeight = computed(() => {
return config.value.layout_side_item_height + 'px'
})
</script> </script>
<style lang="scss"> <style lang="scss">
.gva-sub-menu{ .gva-sub-menu {
.el-sub-menu__title{ .el-sub-menu__title {
height: v-bind('sideHeight') !important; height: v-bind('sideHeight') !important;
}
} }
}
</style> </style>

View File

@ -4,7 +4,7 @@
v-if="!routerInfo.hidden" v-if="!routerInfo.hidden"
:router-info="routerInfo" :router-info="routerInfo"
> >
<template v-if="routerInfo.children&&routerInfo.children.length"> <template v-if="routerInfo.children && routerInfo.children.length">
<AsideComponent <AsideComponent
v-for="item in routerInfo.children" v-for="item in routerInfo.children"
:key="item.name" :key="item.name"
@ -14,35 +14,34 @@
</component> </component>
</template> </template>
<script setup> <script setup>
import MenuItem from './menuItem.vue' import MenuItem from './menuItem.vue'
import AsyncSubmenu from './asyncSubmenu.vue' import AsyncSubmenu from './asyncSubmenu.vue'
import { computed } from 'vue' import { computed } from 'vue'
defineOptions({ defineOptions({
name: 'AsideComponent', name: 'AsideComponent'
}) })
const props = defineProps({ const props = defineProps({
routerInfo: { routerInfo: {
type: Object, type: Object,
default: () => null, default: () => null
}, },
mode :{ mode: {
type : String, type: String,
default: "vertical" default: 'vertical'
} }
}) })
const menuComponent = computed(() => {
if (props.routerInfo.children && props.routerInfo.children.filter(item => !item.hidden).length) {
return AsyncSubmenu
} else {
return MenuItem
}
})
const menuComponent = computed(() => {
if (
props.routerInfo.children &&
props.routerInfo.children.filter((item) => !item.hidden).length
) {
return AsyncSubmenu
} else {
return MenuItem
}
})
</script> </script>

View File

@ -3,7 +3,7 @@
:index="routerInfo.name" :index="routerInfo.name"
class="dark:text-slate-300 overflow-hidden" class="dark:text-slate-300 overflow-hidden"
:style="{ :style="{
height : sideHeight, height: sideHeight
}" }"
> >
<el-icon v-if="routerInfo.meta.icon"> <el-icon v-if="routerInfo.meta.icon">
@ -15,33 +15,29 @@
</el-menu-item> </el-menu-item>
</template> </template>
<script setup> <script setup>
import { computed } from 'vue' import { computed } from 'vue'
import { useAppStore } from '@/pinia' import { useAppStore } from '@/pinia'
import { storeToRefs } from 'pinia' import { storeToRefs } from 'pinia'
const appStore = useAppStore() const appStore = useAppStore()
const { config } = storeToRefs(appStore) const { config } = storeToRefs(appStore)
defineOptions({ defineOptions({
name: 'MenuItem', name: 'MenuItem'
}) })
defineProps({ defineProps({
routerInfo: { routerInfo: {
default: function() { default: function () {
return null return null
}, },
type: Object type: Object
}, }
}) })
const sideHeight = computed(() => {
return config.value.layout_side_item_height + 'px'
})
const sideHeight = computed(() => {
return config.value.layout_side_item_height + 'px'
})
</script> </script>
<style lang="scss"> <style lang="scss"></style>
</style>

View File

@ -1,12 +1,15 @@
<template> <template>
<div class="h-full"> <div class="h-full">
<div v-if="mode==='head'" class="bg-white h-[calc(100%-4px)] text-slate-700 dark:text-slate-300 mx-2 dark:bg-slate-900 flex items-center w-[calc(100vw-600px)] overflow-auto"> <div
v-if="mode === 'head'"
class="bg-white h-[calc(100%-4px)] text-slate-700 dark:text-slate-300 mx-2 dark:bg-slate-900 flex items-center w-[calc(100vw-600px)] overflow-auto"
>
<el-menu <el-menu
:default-active="routerStore.topActive" :default-active="routerStore.topActive"
mode="horizontal" mode="horizontal"
class="border-r-0 border-b-0 w-full flex gap-1 items-center box-border h-[calc(100%-1px)]" class="border-r-0 border-b-0 w-full flex gap-1 items-center box-border h-[calc(100%-1px)]"
unique-opened unique-opened
@select="(index, _, ele)=>selectMenuItem(index, _, ele,true)" @select="(index, _, ele) => selectMenuItem(index, _, ele, true)"
> >
<template v-for="item in routerStore.topMenu"> <template v-for="item in routerStore.topMenu">
<aside-component <aside-component
@ -19,11 +22,11 @@
</el-menu> </el-menu>
</div> </div>
<div <div
v-if="mode==='normal'" v-if="mode === 'normal'"
class="relative h-full bg-white text-slate-700 dark:text-slate-300 dark:bg-slate-900 border-r shadow dark:shadow-gray-700" class="relative h-full bg-white text-slate-700 dark:text-slate-300 dark:bg-slate-900 border-r shadow dark:shadow-gray-700"
:class="isCollapse ? '' : ' px-2'" :class="isCollapse ? '' : ' px-2'"
:style="{ :style="{
width: layoutSideWidth + 'px', width: layoutSideWidth + 'px'
}" }"
> >
<el-scrollbar> <el-scrollbar>
@ -33,7 +36,7 @@
:default-active="active" :default-active="active"
class="border-r-0 w-full" class="border-r-0 w-full"
unique-opened unique-opened
@select="(index, _, ele)=>selectMenuItem(index, _, ele,false)" @select="(index, _, ele) => selectMenuItem(index, _, ele, false)"
> >
<template v-for="item in routerStore.leftMenu"> <template v-for="item in routerStore.leftMenu">
<aside-component <aside-component
@ -60,79 +63,79 @@
</div> </div>
</template> </template>
<script setup> <script setup>
import AsideComponent from "@/view/layout/aside/asideComponent/index.vue"; import AsideComponent from '@/view/layout/aside/asideComponent/index.vue'
import { ref, provide, watchEffect, computed } from "vue"; import { ref, provide, watchEffect, computed } from 'vue'
import { useRoute, useRouter } from "vue-router"; import { useRoute, useRouter } from 'vue-router'
import { useRouterStore } from "@/pinia/modules/router"; import { useRouterStore } from '@/pinia/modules/router'
import { useAppStore } from "@/pinia"; import { useAppStore } from '@/pinia'
import { storeToRefs } from "pinia"; import { storeToRefs } from 'pinia'
const appStore = useAppStore(); const appStore = useAppStore()
const { device, config } = storeToRefs(appStore); const { device, config } = storeToRefs(appStore)
defineOptions({ defineOptions({
name: "GvaAside", name: 'GvaAside'
}); })
defineProps({ defineProps({
mode:{ mode: {
type: String, type: String,
default: "normal", default: 'normal'
}, }
}) })
const route = useRoute(); const route = useRoute()
const router = useRouter(); const router = useRouter()
const routerStore = useRouterStore(); const routerStore = useRouterStore()
const isCollapse = ref(false); const isCollapse = ref(false)
const active = ref(""); const active = ref('')
const layoutSideWidth = computed(() => { const layoutSideWidth = computed(() => {
if (!isCollapse.value) { if (!isCollapse.value) {
return config.value.layout_side_width; return config.value.layout_side_width
} else {
return config.value.layout_side_collapsed_width;
}
});
watchEffect(() => {
active.value = route.meta.activeName || route.name;
});
watchEffect(() => {
if (device.value === "mobile") {
isCollapse.value = true;
} else {
isCollapse.value = false;
}
});
provide("isCollapse", isCollapse);
const selectMenuItem = (index, _, ele,top) => {
const query = {};
const params = {};
routerStore.routeMap[index]?.parameters &&
routerStore.routeMap[index]?.parameters.forEach((item) => {
if (item.type === "query") {
query[item.key] = item.value;
} else { } else {
params[item.key] = item.value; return config.value.layout_side_collapsed_width
} }
}); })
if (index === route.name) return;
if (index.indexOf("http://") > -1 || index.indexOf("https://") > -1) { watchEffect(() => {
window.open(index); active.value = route.meta.activeName || route.name
} else { })
if(!top){
router.push({ name: index, query, params }); watchEffect(() => {
return if (device.value === 'mobile') {
isCollapse.value = true
} else {
isCollapse.value = false
} }
if (!routerStore.setLeftMenu(index)){ })
router.push({ name: index, query, params });
provide('isCollapse', isCollapse)
const selectMenuItem = (index, _, ele, top) => {
const query = {}
const params = {}
routerStore.routeMap[index]?.parameters &&
routerStore.routeMap[index]?.parameters.forEach((item) => {
if (item.type === 'query') {
query[item.key] = item.value
} else {
params[item.key] = item.value
}
})
if (index === route.name) return
if (index.indexOf('http://') > -1 || index.indexOf('https://') > -1) {
window.open(index)
} else {
if (!top) {
router.push({ name: index, query, params })
return
}
if (!routerStore.setLeftMenu(index)) {
router.push({ name: index, query, params })
}
} }
} }
};
const toggleCollapse = () => { const toggleCollapse = () => {
isCollapse.value = !isCollapse.value; isCollapse.value = !isCollapse.value
}; }
</script> </script>

View File

@ -1,5 +1,7 @@
<template> <template>
<div class="bg-white h-[calc(100%-4px)] text-slate-700 dark:text-slate-300 mx-2 dark:bg-slate-900 flex items-center w-[calc(100vw-600px)] overflow-auto"> <div
class="bg-white h-[calc(100%-4px)] text-slate-700 dark:text-slate-300 mx-2 dark:bg-slate-900 flex items-center w-[calc(100vw-600px)] overflow-auto"
>
<el-menu <el-menu
:default-active="active" :default-active="active"
mode="horizontal" mode="horizontal"
@ -20,72 +22,70 @@
</template> </template>
<script setup> <script setup>
import AsideComponent from "@/view/layout/aside/asideComponent/index.vue"; import AsideComponent from '@/view/layout/aside/asideComponent/index.vue'
import { ref, provide, watchEffect } from "vue"; import { ref, provide, watchEffect } from 'vue'
import { useRoute, useRouter } from "vue-router"; import { useRoute, useRouter } from 'vue-router'
import { useRouterStore } from "@/pinia/modules/router"; import { useRouterStore } from '@/pinia/modules/router'
import { useAppStore } from "@/pinia"; import { useAppStore } from '@/pinia'
import { storeToRefs } from "pinia"; import { storeToRefs } from 'pinia'
const appStore = useAppStore(); const appStore = useAppStore()
const { device } = storeToRefs(appStore); const { device } = storeToRefs(appStore)
defineOptions({ defineOptions({
name: "GvaAside", name: 'GvaAside'
}); })
const route = useRoute(); const route = useRoute()
const router = useRouter(); const router = useRouter()
const routerStore = useRouterStore(); const routerStore = useRouterStore()
const isCollapse = ref(false); const isCollapse = ref(false)
const active = ref(""); const active = ref('')
watchEffect(() => { watchEffect(() => {
active.value = route.meta.activeName || route.name; active.value = route.meta.activeName || route.name
}); })
watchEffect(() => { watchEffect(() => {
if (device.value === "mobile") { if (device.value === 'mobile') {
isCollapse.value = true; isCollapse.value = true
} else { } else {
isCollapse.value = false; isCollapse.value = false
}
})
provide('isCollapse', isCollapse)
const selectMenuItem = (index) => {
const query = {}
const params = {}
routerStore.routeMap[index]?.parameters &&
routerStore.routeMap[index]?.parameters.forEach((item) => {
if (item.type === 'query') {
query[item.key] = item.value
} else {
params[item.key] = item.value
}
})
if (index === route.name) return
if (index.indexOf('http://') > -1 || index.indexOf('https://') > -1) {
window.open(index)
} else {
router.push({ name: index, query, params })
}
} }
});
provide("isCollapse", isCollapse);
const selectMenuItem = (index) => {
const query = {};
const params = {};
routerStore.routeMap[index]?.parameters &&
routerStore.routeMap[index]?.parameters.forEach((item) => {
if (item.type === "query") {
query[item.key] = item.value;
} else {
params[item.key] = item.value;
}
});
if (index === route.name) return;
if (index.indexOf("http://") > -1 || index.indexOf("https://") > -1) {
window.open(index);
} else {
router.push({ name: index, query, params });
}
};
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.el-menu--horizontal.el-menu, .el-menu--horizontal.el-menu,
.el-menu--horizontal > .el-menu-item.is-active{ .el-menu--horizontal > .el-menu-item.is-active {
border-bottom : none !important; border-bottom: none !important;
}
} .el-menu-item.is-active {
.el-menu-item.is-active{
background-color: var(--el-color-primary-light-8) !important; background-color: var(--el-color-primary-light-8) !important;
} }
.dark{ .dark {
.el-menu-item.is-active{ .el-menu-item.is-active {
background-color: var(--el-color-primary-bg) !important; background-color: var(--el-color-primary-bg) !important;
} }
} }
</style> </style>

View File

@ -1,25 +1,34 @@
<template> <template>
<div> <div>
<normal-mode v-if="config.side_mode === 'normal' || (device === 'mobile' && config.side_mode == 'head' ) || (device === 'mobile' && config.side_mode == 'combination' )" /> <normal-mode
<head-mode v-if="config.side_mode === 'head' && device !== 'mobile'"/> v-if="
<combination-mode v-if="config.side_mode === 'combination' && device !== 'mobile'" :mode="mode"/> config.side_mode === 'normal' ||
(device === 'mobile' && config.side_mode == 'head') ||
(device === 'mobile' && config.side_mode == 'combination')
"
/>
<head-mode v-if="config.side_mode === 'head' && device !== 'mobile'" />
<combination-mode
v-if="config.side_mode === 'combination' && device !== 'mobile'"
:mode="mode"
/>
</div> </div>
</template> </template>
<script setup> <script setup>
import NormalMode from "./normalMode.vue" import NormalMode from './normalMode.vue'
import HeadMode from "./headMode.vue" import HeadMode from './headMode.vue'
import CombinationMode from "./combinationMode.vue" import CombinationMode from './combinationMode.vue'
defineProps({ defineProps({
mode:{ mode: {
type: String, type: String,
default: 'normal' default: 'normal'
} }
}) })
import { storeToRefs } from "pinia"; import { storeToRefs } from 'pinia'
import { useAppStore } from "@/pinia"; import { useAppStore } from '@/pinia'
const appStore = useAppStore(); const appStore = useAppStore()
const { config, device } = storeToRefs(appStore); const { config, device } = storeToRefs(appStore)
</script> </script>

View File

@ -3,7 +3,7 @@
class="relative h-full bg-white text-slate-700 dark:text-slate-300 dark:bg-slate-900 border-r shadow dark:shadow-gray-700" class="relative h-full bg-white text-slate-700 dark:text-slate-300 dark:bg-slate-900 border-r shadow dark:shadow-gray-700"
:class="isCollapse ? '' : ' px-2'" :class="isCollapse ? '' : ' px-2'"
:style="{ :style="{
width: layoutSideWidth + 'px', width: layoutSideWidth + 'px'
}" }"
> >
<el-scrollbar> <el-scrollbar>
@ -39,67 +39,67 @@
</div> </div>
</template> </template>
<script setup> <script setup>
import AsideComponent from "@/view/layout/aside/asideComponent/index.vue"; import AsideComponent from '@/view/layout/aside/asideComponent/index.vue'
import { ref, provide, watchEffect, computed } from "vue"; import { ref, provide, watchEffect, computed } from 'vue'
import { useRoute, useRouter } from "vue-router"; import { useRoute, useRouter } from 'vue-router'
import { useRouterStore } from "@/pinia/modules/router"; import { useRouterStore } from '@/pinia/modules/router'
import { useAppStore } from "@/pinia"; import { useAppStore } from '@/pinia'
import { storeToRefs } from "pinia"; import { storeToRefs } from 'pinia'
const appStore = useAppStore(); const appStore = useAppStore()
const { device, config } = storeToRefs(appStore); const { device, config } = storeToRefs(appStore)
defineOptions({ defineOptions({
name: "GvaAside", name: 'GvaAside'
}); })
const route = useRoute(); const route = useRoute()
const router = useRouter(); const router = useRouter()
const routerStore = useRouterStore(); const routerStore = useRouterStore()
const isCollapse = ref(false); const isCollapse = ref(false)
const active = ref(""); const active = ref('')
const layoutSideWidth = computed(() => { const layoutSideWidth = computed(() => {
if (!isCollapse.value) { if (!isCollapse.value) {
return config.value.layout_side_width; return config.value.layout_side_width
} else { } else {
return config.value.layout_side_collapsed_width; return config.value.layout_side_collapsed_width
}
})
watchEffect(() => {
active.value = route.meta.activeName || route.name
})
watchEffect(() => {
if (device.value === 'mobile') {
isCollapse.value = true
} else {
isCollapse.value = false
}
})
provide('isCollapse', isCollapse)
const selectMenuItem = (index) => {
const query = {}
const params = {}
routerStore.routeMap[index]?.parameters &&
routerStore.routeMap[index]?.parameters.forEach((item) => {
if (item.type === 'query') {
query[item.key] = item.value
} else {
params[item.key] = item.value
}
})
if (index === route.name) return
if (index.indexOf('http://') > -1 || index.indexOf('https://') > -1) {
window.open(index)
} else {
router.push({ name: index, query, params })
}
} }
});
watchEffect(() => {
active.value = route.meta.activeName || route.name;
});
watchEffect(() => { const toggleCollapse = () => {
if (device.value === "mobile") { isCollapse.value = !isCollapse.value
isCollapse.value = true;
} else {
isCollapse.value = false;
} }
});
provide("isCollapse", isCollapse);
const selectMenuItem = (index) => {
const query = {};
const params = {};
routerStore.routeMap[index]?.parameters &&
routerStore.routeMap[index]?.parameters.forEach((item) => {
if (item.type === "query") {
query[item.key] = item.value;
} else {
params[item.key] = item.value;
}
});
if (index === route.name) return;
if (index.indexOf("http://") > -1 || index.indexOf("https://") > -1) {
window.open(index);
} else {
router.push({ name: index, query, params });
}
};
const toggleCollapse = () => {
isCollapse.value = !isCollapse.value;
};
</script> </script>
<style lang="scss"></style> <style lang="scss"></style>

View File

@ -4,53 +4,66 @@
!--> !-->
<template> <template>
<div class="flex justify-between fixed top-0 left-0 right-0 z-10 h-16 bg-white text-slate-700 dark:text-slate-300 dark:bg-slate-900 shadow dark:shadow-gray-700 items-center px-2"> <div
<div class="flex justify-between fixed top-0 left-0 right-0 z-10 h-16 bg-white text-slate-700 dark:text-slate-300 dark:bg-slate-900 shadow dark:shadow-gray-700 items-center px-2"
class="flex items-center cursor-pointer flex-1" >
> <div class="flex items-center cursor-pointer flex-1">
<div class="flex items-center cursor-pointer min-w-48" @click="router.push({ path: '/' })"> <div
class="flex items-center cursor-pointer min-w-48"
@click="router.push({ path: '/' })"
>
<img <img
alt alt
class="h-12 bg-white rounded-full" class="h-12 bg-white rounded-full"
:src="$GIN_VUE_ADMIN.appLogo" :src="$GIN_VUE_ADMIN.appLogo"
> />
<div <div
v-if="!isMobile" v-if="!isMobile"
class="inline-flex font-bold text-2xl ml-2" class="inline-flex font-bold text-2xl ml-2"
:class="(config.side_mode === 'head' || config.side_mode === 'combination') &&'min-w-fit'" :class="
(config.side_mode === 'head' ||
config.side_mode === 'combination') &&
'min-w-fit'
"
> >
{{ $GIN_VUE_ADMIN.appName }} {{ $GIN_VUE_ADMIN.appName }}
</div> </div>
</div> </div>
<el-breadcrumb v-show="!isMobile" v-if="config.side_mode !== 'head'&& config.side_mode !== 'combination'" class="ml-4"> <el-breadcrumb
v-show="!isMobile"
v-if="config.side_mode !== 'head' && config.side_mode !== 'combination'"
class="ml-4"
>
<el-breadcrumb-item <el-breadcrumb-item
v-for="item in matched.slice(1, matched.length)" v-for="item in matched.slice(1, matched.length)"
:key="item.path" :key="item.path"
> >
{{ {{ fmtTitle(item.meta.title, route) }}
fmtTitle(item.meta.title, route)
}}
</el-breadcrumb-item> </el-breadcrumb-item>
</el-breadcrumb> </el-breadcrumb>
<gva-aside v-if="config.side_mode === 'head' && !isMobile" class="flex-1" /> <gva-aside
<gva-aside v-if="config.side_mode === 'combination' && !isMobile" mode="head" class="flex-1" /> v-if="config.side_mode === 'head' && !isMobile"
class="flex-1"
/>
<gva-aside
v-if="config.side_mode === 'combination' && !isMobile"
mode="head"
class="flex-1"
/>
</div> </div>
<div class="ml-2 flex items-center"> <div class="ml-2 flex items-center">
<tools /> <tools />
<el-dropdown> <el-dropdown>
<div <div class="flex justify-center items-center h-full w-full">
class="flex justify-center items-center h-full w-full"
>
<span <span
class="cursor-pointer flex justify-center items-center text-black dark:text-gray-100" class="cursor-pointer flex justify-center items-center text-black dark:text-gray-100"
> >
<CustomPic /> <CustomPic />
<span <span v-show="!isMobile" class="w-16">{{
v-show="!isMobile" userStore.userInfo.nickName
class="w-16" }}</span>
>{{ userStore.userInfo.nickName }}</span>
<el-icon> <el-icon>
<arrow-down /> <arrow-down />
</el-icon> </el-icon>
@ -60,33 +73,24 @@
<el-dropdown-menu> <el-dropdown-menu>
<el-dropdown-item> <el-dropdown-item>
<span class="font-bold"> <span class="font-bold">
当前角色{{ 当前角色{{ userStore.userInfo.authority.authorityName }}
userStore.userInfo.authority.authorityName
}}
</span> </span>
</el-dropdown-item> </el-dropdown-item>
<template v-if="userStore.userInfo.authorities"> <template v-if="userStore.userInfo.authorities">
<el-dropdown-item <el-dropdown-item
v-for="item in userStore.userInfo.authorities.filter( v-for="item in userStore.userInfo.authorities.filter(
(i) => (i) => i.authorityId !== userStore.userInfo.authorityId
i.authorityId !==
userStore.userInfo.authorityId
)" )"
:key="item.authorityId" :key="item.authorityId"
@click="changeUserAuth(item.authorityId)" @click="changeUserAuth(item.authorityId)"
> >
<span> <span> 切换为{{ item.authorityName }} </span>
切换为{{ item.authorityName }}
</span>
</el-dropdown-item> </el-dropdown-item>
</template> </template>
<el-dropdown-item icon="avatar" @click="toPerson"> <el-dropdown-item icon="avatar" @click="toPerson">
个人信息 个人信息
</el-dropdown-item> </el-dropdown-item>
<el-dropdown-item <el-dropdown-item icon="reading-lamp" @click="userStore.LoginOut">
icon="reading-lamp"
@click="userStore.LoginOut"
>
</el-dropdown-item> </el-dropdown-item>
</el-dropdown-menu> </el-dropdown-menu>
@ -97,44 +101,39 @@
</template> </template>
<script setup> <script setup>
import tools from "./tools.vue" import tools from './tools.vue'
import CustomPic from '@/components/customPic/index.vue' import CustomPic from '@/components/customPic/index.vue'
import { useUserStore } from "@/pinia/modules/user"; import { useUserStore } from '@/pinia/modules/user'
import { useRoute, useRouter } from 'vue-router' import { useRoute, useRouter } from 'vue-router'
import { useAppStore } from "@/pinia" import { useAppStore } from '@/pinia'
import { storeToRefs } from "pinia" import { storeToRefs } from 'pinia'
import { computed, } from 'vue' import { computed } from 'vue'
import { setUserAuthority } from '@/api/user' import { setUserAuthority } from '@/api/user'
import { fmtTitle } from "@/utils/fmtRouterTitle"; import { fmtTitle } from '@/utils/fmtRouterTitle'
import gvaAside from "@/view/layout/aside/index.vue" import gvaAside from '@/view/layout/aside/index.vue'
const userStore = useUserStore(); const userStore = useUserStore()
const router = useRouter() const router = useRouter()
const route = useRoute() const route = useRoute()
const appStore = useAppStore() const appStore = useAppStore()
const { device , config } = storeToRefs(appStore) const { device, config } = storeToRefs(appStore)
const isMobile = computed(() =>{ const isMobile = computed(() => {
return device.value === 'mobile' return device.value === 'mobile'
}) })
const toPerson = () => { const toPerson = () => {
router.push({ name: "person" }); router.push({ name: 'person' })
};
const matched = computed(() => route.meta.matched);
const changeUserAuth = async (id) => {
const res = await setUserAuthority({
authorityId: id,
});
if (res.code === 0) {
window.sessionStorage.setItem("needCloseAll", "true");
window.sessionStorage.setItem("needToHome", "true");
window.location.reload();
} }
}; const matched = computed(() => route.meta.matched)
const changeUserAuth = async (id) => {
const res = await setUserAuthority({
authorityId: id
})
if (res.code === 0) {
window.sessionStorage.setItem('needCloseAll', 'true')
window.sessionStorage.setItem('needToHome', 'true')
window.location.reload()
}
}
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss"></style>
</style>

View File

@ -5,53 +5,46 @@
<template> <template>
<div class="flex items-center mx-4 gap-4"> <div class="flex items-center mx-4 gap-4">
<el-tooltip <el-tooltip class="" effect="dark" content="视频教程" placement="bottom">
class=""
effect="dark"
content="视频教程"
placement="bottom"
>
<el-dropdown @command="toDoc"> <el-dropdown @command="toDoc">
<el-icon class="w-8 h-8 shadow rounded-full border border-gray-200 dark:border-gray-600 cursor-pointer border-solid"> <el-icon
<Film /> class="w-8 h-8 shadow rounded-full border border-gray-200 dark:border-gray-600 cursor-pointer border-solid"
</el-icon> >
<Film />
</el-icon>
<template #dropdown> <template #dropdown>
<el-dropdown-menu> <el-dropdown-menu>
<el-dropdown-item v-for="item in videoList" :key="item.link" :command="item.link">{{ item.title }}</el-dropdown-item> <el-dropdown-item
</el-dropdown-menu> v-for="item in videoList"
</template> :key="item.link"
:command="item.link"
>{{ item.title }}</el-dropdown-item
>
</el-dropdown-menu>
</template>
</el-dropdown> </el-dropdown>
</el-tooltip> </el-tooltip>
<el-tooltip <el-tooltip class="" effect="dark" content="搜索" placement="bottom">
class="" <el-icon
effect="dark" @click="handleCommand"
content="搜索" class="w-8 h-8 shadow rounded-full border border-gray-200 dark:border-gray-600 cursor-pointer border-solid"
placement="bottom" >
>
<el-icon @click="handleCommand" class="w-8 h-8 shadow rounded-full border border-gray-200 dark:border-gray-600 cursor-pointer border-solid">
<Search /> <Search />
</el-icon> </el-icon>
</el-tooltip> </el-tooltip>
<el-tooltip <el-tooltip class="" effect="dark" content="系统设置" placement="bottom">
class="" <el-icon
effect="dark" class="w-8 h-8 shadow rounded-full border border-gray-200 dark:border-gray-600 cursor-pointer border-solid"
content="系统设置" @click="toggleSetting"
placement="bottom" >
>
<el-icon class="w-8 h-8 shadow rounded-full border border-gray-200 dark:border-gray-600 cursor-pointer border-solid" @click="toggleSetting">
<Setting /> <Setting />
</el-icon> </el-icon>
</el-tooltip> </el-tooltip>
<el-tooltip <el-tooltip class="" effect="dark" content="刷新" placement="bottom">
class=""
effect="dark"
content="刷新"
placement="bottom"
>
<el-icon <el-icon
class="w-8 h-8 shadow rounded-full border border-gray-200 dark:border-gray-600 cursor-pointer border-solid" class="w-8 h-8 shadow rounded-full border border-gray-200 dark:border-gray-600 cursor-pointer border-solid"
:class="showRefreshAnmite ? 'animate-spin' : ''" :class="showRefreshAnmite ? 'animate-spin' : ''"
@ -67,10 +60,18 @@
placement="bottom" placement="bottom"
:disabled="appStore.theme === 'auto'" :disabled="appStore.theme === 'auto'"
> >
<el-icon v-if="appStore.theme === 'dark'" class="w-8 h-8 shadow rounded-full border border-gray-600 cursor-pointer border-solid" @click="appStore.toggleTheme(false )"> <el-icon
v-if="appStore.theme === 'dark'"
class="w-8 h-8 shadow rounded-full border border-gray-600 cursor-pointer border-solid"
@click="appStore.toggleTheme(false)"
>
<Sunny /> <Sunny />
</el-icon> </el-icon>
<el-icon v-else class="w-8 h-8 shadow rounded-full border border-gray-200 cursor-pointer border-solid" @click="appStore.toggleTheme(true)"> <el-icon
v-else
class="w-8 h-8 shadow rounded-full border border-gray-200 cursor-pointer border-solid"
@click="appStore.toggleTheme(true)"
>
<Moon /> <Moon />
</el-icon> </el-icon>
</el-tooltip> </el-tooltip>
@ -81,119 +82,112 @@
</template> </template>
<script setup> <script setup>
import { useAppStore } from '@/pinia'
import GvaSetting from '@/view/layout/setting/index.vue'
import { ref } from 'vue'
import { emitter } from '@/utils/bus.js'
import CommandMenu from '@/components/commandMenu/index.vue'
import { toDoc } from '@/utils/doc'
import { useAppStore } from "@/pinia" const appStore = useAppStore()
import GvaSetting from "@/view/layout/setting/index.vue" const showSettingDrawer = ref(false)
import { ref } from "vue" const showRefreshAnmite = ref(false)
import { emitter } from "@/utils/bus.js"; const toggleRefresh = () => {
import CommandMenu from "@/components/commandMenu/index.vue"; showRefreshAnmite.value = true
import {toDoc} from "@/utils/doc"; emitter.emit('reload')
setTimeout(() => {
const appStore = useAppStore() showRefreshAnmite.value = false
const showSettingDrawer = ref(false) }, 1000)
const showRefreshAnmite = ref(false)
const toggleRefresh = () =>{
showRefreshAnmite.value = true
emitter.emit('reload')
setTimeout(() => {
showRefreshAnmite.value = false
}, 1000);
}
const toggleSetting = () => {
showSettingDrawer.value = true
}
const first = ref("");
const command = ref();
const handleCommand = () => {
command.value.open();
};
const initPage = () => {
//
if (window.localStorage.getItem("osType") === "WIN") {
first.value = "Ctrl";
} else {
first.value = "⌘";
} }
// ctrlk
const handleKeyDown = (e) => { const toggleSetting = () => {
if (e.ctrlKey && e.key === "k") { showSettingDrawer.value = true
// }
e.preventDefault();
handleCommand(); const first = ref('')
const command = ref()
const handleCommand = () => {
command.value.open()
}
const initPage = () => {
//
if (window.localStorage.getItem('osType') === 'WIN') {
first.value = 'Ctrl'
} else {
first.value = '⌘'
} }
}; // ctrlk
window.addEventListener("keydown", handleKeyDown); const handleKeyDown = (e) => {
}; if (e.ctrlKey && e.key === 'k') {
//
initPage(); e.preventDefault()
handleCommand()
}
const videoList = [ }
{ window.addEventListener('keydown', handleKeyDown)
title:"1.clone项目和安装依赖",
link:"https://www.bilibili.com/video/BV1jx4y1s7xx",
},
{
title:"2.初始化项目",
link:"https://www.bilibili.com/video/BV1sr421K7sv",
},
{
title:"3.开启调试工具+创建初始化包",
link:"https://www.bilibili.com/video/BV1iH4y1c7Na",
},
{
title:"4.手动使用自动化创建功能",
link:"https://www.bilibili.com/video/BV1UZ421T7fV",
},
{
title:"5.使用已有表格创建业务",
link:"https://www.bilibili.com/video/BV1NE4m1977s",
},
{
title:"6.使用AI创建业务和创建数据源模式的可选项",
link:"https://www.bilibili.com/video/BV17i421a7DE",
},
{
title:"7.创建自己的后端方法",
link:"https://www.bilibili.com/video/BV1Yw4m1k7fg",
},
{
title:"8.新增一个前端页面",
link:"https://www.bilibili.com/video/BV12y411i7oE",
},
{
title:"9.配置一个前端二级页面",
link:"https://www.bilibili.com/video/BV1ZM4m1y7i3",
},
{
title:"10.配置一个前端菜单参数",
link:"https://www.bilibili.com/video/BV1WS42197DZ",
},
{
title:"11.菜单参数实战+动态菜单标题+菜单高亮配置",
link:"https://www.bilibili.com/video/BV1NE4m1979c",
},
{
title:"12.增加菜单可控按钮",
link:"https://www.bilibili.com/video/BV1Sw4m1k746",
},
{
title:"14.新增客户角色和其相关配置教学",
link:"https://www.bilibili.com/video/BV1Ki421a7X2",
},
{
title:"15.发布项目上线",
link:"https://www.bilibili.com/video/BV1Lx4y1s77D",
} }
]
initPage()
const videoList = [
{
title: '1.clone项目和安装依赖',
link: 'https://www.bilibili.com/video/BV1jx4y1s7xx'
},
{
title: '2.初始化项目',
link: 'https://www.bilibili.com/video/BV1sr421K7sv'
},
{
title: '3.开启调试工具+创建初始化包',
link: 'https://www.bilibili.com/video/BV1iH4y1c7Na'
},
{
title: '4.手动使用自动化创建功能',
link: 'https://www.bilibili.com/video/BV1UZ421T7fV'
},
{
title: '5.使用已有表格创建业务',
link: 'https://www.bilibili.com/video/BV1NE4m1977s'
},
{
title: '6.使用AI创建业务和创建数据源模式的可选项',
link: 'https://www.bilibili.com/video/BV17i421a7DE'
},
{
title: '7.创建自己的后端方法',
link: 'https://www.bilibili.com/video/BV1Yw4m1k7fg'
},
{
title: '8.新增一个前端页面',
link: 'https://www.bilibili.com/video/BV12y411i7oE'
},
{
title: '9.配置一个前端二级页面',
link: 'https://www.bilibili.com/video/BV1ZM4m1y7i3'
},
{
title: '10.配置一个前端菜单参数',
link: 'https://www.bilibili.com/video/BV1WS42197DZ'
},
{
title: '11.菜单参数实战+动态菜单标题+菜单高亮配置',
link: 'https://www.bilibili.com/video/BV1NE4m1979c'
},
{
title: '12.增加菜单可控按钮',
link: 'https://www.bilibili.com/video/BV1Sw4m1k746'
},
{
title: '14.新增客户角色和其相关配置教学',
link: 'https://www.bilibili.com/video/BV1Ki421a7X2'
},
{
title: '15.发布项目上线',
link: 'https://www.bilibili.com/video/BV1Lx4y1s77D'
}
]
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss"></style>
</style>

Some files were not shown because too many files have changed in this diff Show More