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,32 +1,37 @@
# gin-vue-admin web
## Project setup
```
npm install
```
### Compiles and hot-reloads for development
```
npm run serve
```
### Compiles and minifies for production
```
npm run build
```
### Run your tests
```
npm run test
```
### Lints and fixes files
```
npm run lint
```
整理代码结构
```lua
web
├── babel.config.js

View File

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

View File

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

View File

@@ -1,16 +1,19 @@
<!DOCTYPE html>
<!doctype html>
<html lang="zh-cn" class="transition-colors">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<meta content="Gin,Vue,Admin.Gin-Vue-Admin,GVA,gin-vue-admin,后台管理框架,vue后台管理框架,gin-vue-admin文档,gin-vue-admin首页,gin-vue-admin" name="keywords" />
<link rel="icon" href="favicon.ico">
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width,initial-scale=1.0" />
<meta
content="Gin,Vue,Admin.Gin-Vue-Admin,GVA,gin-vue-admin,后台管理框架,vue后台管理框架,gin-vue-admin文档,gin-vue-admin首页,gin-vue-admin"
name="keywords"
/>
<link rel="icon" href="favicon.ico" />
<title></title>
<style>
.transition-colors {
transition-property: color, background-color, border-color, text-decoration-color, fill, stroke;
transition-property: color, background-color, border-color,
text-decoration-color, fill, stroke;
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
transition-duration: 150ms;
}
@@ -41,28 +44,40 @@
left: calc(50% - 20px);
}
@keyframes loader {
0% { left: -100px }
100% { left: 110%; }
0% {
left: -100px;
}
100% {
left: 110%;
}
}
#box {
width: 50px;
height: 50px;
background: var(--64f90c3645474bd5);
animation: animate .5s linear infinite;
animation: animate 0.5s linear infinite;
position: absolute;
top: 0;
left: 0;
border-radius: 3px;
}
@keyframes animate {
17% { border-bottom-right-radius: 3px; }
25% { transform: translateY(9px) rotate(22.5deg); }
17% {
border-bottom-right-radius: 3px;
}
25% {
transform: translateY(9px) rotate(22.5deg);
}
50% {
transform: translateY(18px) scale(1,.9) rotate(45deg) ;
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); }
75% {
transform: translateY(9px) rotate(67.5deg);
}
100% {
transform: translateY(0) rotate(90deg);
}
}
#shadow {
width: 50px;
@@ -73,7 +88,7 @@
top: 59px;
left: 0;
border-radius: 50%;
animation: shadow .5s linear infinite;
animation: shadow 0.5s linear infinite;
}
.dark #shadow {
background: #fff;
@@ -83,7 +98,6 @@
transform: scale(1.2, 1);
}
}
</style>
</head>
@@ -98,5 +112,4 @@
<div id="app"></div>
<script type="module" src="./src/main.js"></script>
</body>
</html>

View File

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

View File

@@ -1,5 +1,8 @@
<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">
<router-view />
</el-config-provider>
@@ -8,15 +11,13 @@
<script setup>
import zhCn from 'element-plus/dist/locale/zh-cn.mjs'
import {useAppStore} from "@/pinia";
import { useAppStore } from '@/pinia'
useAppStore()
defineOptions({
name: 'App'
})
</script>
<style lang="scss">
// 引入初始化样式
#app {
height: 100vh;
@@ -37,5 +38,4 @@ defineOptions({
.gva-container2 {
height: calc(100% - 4.5rem);
}
</style>

View File

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

View File

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

View File

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

View File

@@ -139,7 +139,6 @@ export const pubPlug = (params) => {
})
}
export const llmAuto = (data) => {
return service({
url: '/autoCode/llmAuto',
@@ -149,18 +148,17 @@ export const llmAuto = (data) => {
loadingOption: {
lock: true,
fullscreen: true,
text: `小淼正在思考,请稍候...`,
text: `小淼正在思考,请稍候...`
}
})
}
export const butler = (data) => {
return service({
url: '/autoCode/llmAuto',
method: 'post',
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) {
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'
})
}

View File

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

View File

@@ -27,9 +27,8 @@
</template>
<script setup>
defineOptions({
name: 'ArrayCtrl',
name: 'ArrayCtrl'
})
import { nextTick, ref } from 'vue'

View File

@@ -3,14 +3,17 @@
未经授权的商用使用可能会被我们的资产搜索引擎爬取并可能导致后续索赔索赔金额将不低于高级授权费的十倍请您遵守版权法律法规尊重知识产权
-->
<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">
<span class="mr-1">Powered by</span>
<span>
<a
class="font-bold text-active"
href="https://github.com/flipped-aurora/gin-vue-admin"
>Gin-Vue-Admin</a>
>Gin-Vue-Admin</a
>
</span>
</div>
<slot />
@@ -20,7 +23,8 @@
<a
class="font-bold text-active"
href="https://github.com/flipped-aurora"
>flipped-aurora团队</a>
>flipped-aurora团队</a
>
</span>
</div>
</div>
@@ -38,4 +42,3 @@ console.log(
'background:transparent'
)
</script>

View File

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

View File

@@ -10,17 +10,13 @@
v-model="searchInput"
class="quick-input"
placeholder="请输入你需要快捷到达的功能"
>
/>
</template>
<div
v-for="(option,index) in options"
:key="index"
>
<div
v-if="option.children.length"
class="quick-title"
>{{ option.label }}</div>
<div v-for="(option, index) in options" :key="index">
<div v-if="option.children.length" class="quick-title">
{{ option.label }}
</div>
<div
v-for="(item, key) in option.children"
:key="index + '-' + key"
@@ -45,7 +41,7 @@ import { useRouter } from 'vue-router'
import { useRouterStore } from '@/pinia/modules/router'
import { useAppStore, useUserStore } from '@/pinia'
defineOptions({
name: 'CommandMenu',
name: 'CommandMenu'
})
const appStore = useAppStore()
const userStore = useUserStore()
@@ -58,11 +54,14 @@ const searchInput = ref('')
const options = reactive([])
const deepMenus = (menus) => {
const arr = []
menus.forEach(menu => {
menus.forEach((menu) => {
if (menu.children && menu.children.length > 0) {
arr.push(...deepMenus(menu.children))
} else {
if (menu.meta.title && menu.meta.title.indexOf(searchInput.value) > -1) {
if (
menu.meta.title &&
menu.meta.title.indexOf(searchInput.value) > -1
) {
arr.push({
label: menu.meta.title,
func: () => changeRouter(menu)
@@ -92,15 +91,19 @@ const addQuickOption = () => {
{
label: '亮色主题',
func: () => changeMode('light')
}, {
},
{
label: '暗色主题',
func: () => changeMode('dark')
}, {
},
{
label: '退出登录',
func: () => userStore.LoginOut()
}
]
option.children.push(...quickArr.filter(item => item.label.indexOf(searchInput.value) > -1))
option.children.push(
...quickArr.filter((item) => item.label.indexOf(searchInput.value) > -1)
)
options.push(option)
}
@@ -193,5 +196,4 @@ watch(searchInput, () => {
}
}
}
</style>

View File

@@ -1,28 +1,12 @@
<template>
<span class="headerAvatar">
<template v-if="picType === 'avatar'">
<el-avatar
v-if="userStore.userInfo.headerImg"
:size="30"
:src="avatar"
/>
<el-avatar
v-else
:size="30"
:src="noAvatar"
/>
<el-avatar v-if="userStore.userInfo.headerImg" :size="30" :src="avatar" />
<el-avatar v-else :size="30" :src="noAvatar" />
</template>
<template v-if="picType === 'img'">
<img
v-if="userStore.userInfo.headerImg"
:src="avatar"
class="avatar"
>
<img
v-else
:src="noAvatar"
class="avatar"
>
<img v-if="userStore.userInfo.headerImg" :src="avatar" class="avatar" />
<img v-else :src="noAvatar" class="avatar" />
</template>
<template v-if="picType === 'file'">
<el-image
@@ -68,7 +52,10 @@ const userStore = useUserStore()
const avatar = computed(() => {
if (props.picSrc === '') {
if (userStore.userInfo.headerImg !== '' && userStore.userInfo.headerImg.slice(0, 4) === 'http') {
if (
userStore.userInfo.headerImg !== '' &&
userStore.userInfo.headerImg.slice(0, 4) === 'http'
) {
return userStore.userInfo.headerImg
}
return path.value + userStore.userInfo.headerImg
@@ -85,8 +72,7 @@ const file = computed(() => {
}
return props.picSrc
})
const previewSrcList = computed(() => props.preview ? [file.value] : [])
const previewSrcList = computed(() => (props.preview ? [file.value] : []))
</script>
<style scoped>

View File

@@ -1,9 +1,7 @@
<template>
<el-button
type="primary"
icon="download"
@click="exportExcelFunc"
>导出</el-button>
<el-button type="primary" icon="download" @click="exportExcelFunc"
>导出</el-button
>
</template>
<script setup>
@@ -49,7 +47,10 @@ const exportExcelFunc = async() => {
paramsCopy.order = props.order
}
const params = Object.entries(paramsCopy)
.map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(value)}`)
.map(
([key, value]) =>
`${encodeURIComponent(key)}=${encodeURIComponent(value)}`
)
.join('&')
const url = `${baseUrl}/sysExportTemplate/exportExcel?templateID=${props.templateId}${params ? '&' + params : ''}`

View File

@@ -1,9 +1,7 @@
<template>
<el-button
type="primary"
icon="download"
@click="exportTemplate"
>下载模板</el-button>
<el-button type="primary" icon="download" @click="exportTemplate"
>下载模板</el-button
>
</template>
<script setup>

View File

@@ -5,13 +5,7 @@
:on-success="handleSuccess"
:multiple="false"
>
<el-button
type="primary"
icon="upload"
class="ml-3"
>
导入
</el-button>
<el-button type="primary" icon="upload" class="ml-3"> 导入 </el-button>
</el-upload>
</template>

View File

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

View File

@@ -1,5 +1,10 @@
<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>
<script>
export default {
@@ -16,18 +21,16 @@ import {ref, watch} from 'vue'
const props = defineProps({
modelValue: {
type: String,
default: () => ""
default: () => ''
}
})
const excel = ref('')
watch(() => props.modelValue, val => excel.value = val, {immediate: true})
const renderedHandler = () => {
}
const errorHandler = () => {
}
watch(
() => props.modelValue,
(val) => (excel.value = val),
{ immediate: true }
)
const renderedHandler = () => {}
const errorHandler = () => {}
</script>
<style>
</style>
<style></style>

View File

@@ -11,13 +11,9 @@
<Excel v-model="fullFileURL" />
</div>
<div v-else-if="ext === 'image'">
<el-image
:src="fullFileURL"
lazy
/>
<el-image :src="fullFileURL" lazy />
</div>
</el-row>
</div>
</template>
<script>
@@ -39,7 +35,7 @@ const fileUrl = ref('')
const ext = ref('')
watch(
() => model,
val => {
(val) => {
fileUrl.value = val
const fileExt = val.split('.')[1] || ''
const image = ['png', 'jpg', 'jpeg', 'gif']

View File

@@ -1,6 +1,5 @@
<template>
<vue-office-pdf
:src="pdf"
@rendered="renderedHandler"
@error="errorHandler"
@@ -8,29 +7,33 @@
</template>
<script>
export default {
name: "Pdf"
name: 'Pdf'
}
</script>
<script setup>
import {ref, watch} from "vue"
import { ref, watch } from 'vue'
//引入VueOfficeDocx组件
import VueOfficePdf from "@vue-office/pdf";
import VueOfficePdf from '@vue-office/pdf'
//引入相关样式
import '@vue-office/docx/lib/index.css'
console.log("pdf===>")
console.log('pdf===>')
const props = defineProps({
modelValue: {
type: String,
default: () => ""
default: () => ''
}
})
const pdf = ref(null)
watch(() => props.modelValue, val => pdf.value = val, {immediate: true})
watch(
() => props.modelValue,
(val) => (pdf.value = val),
{ immediate: true }
)
const renderedHandler = () => {
console.log("pdf 加载成功")
console.log('pdf 加载成功')
}
const errorHandler = () => {
console.log("pdf 错误")
console.log('pdf 错误')
}
</script>

View File

@@ -8,7 +8,7 @@
<Editor
v-model="valueHtml"
class="overflow-y-hidden mt-0.5"
style="height: 18rem;"
style="height: 18rem"
:default-config="editorConfig"
mode="default"
@onCreated="handleCreated"
@@ -18,7 +18,6 @@
</template>
<script setup>
import '@wangeditor/editor/dist/css/style.css' // 引入 css
const basePath = import.meta.env.VITE_BASE_API
@@ -76,12 +75,12 @@ const handleCreated = (editor) => {
valueHtml.value = props.modelValue
}
watch(() => props.modelValue, () => {
watch(
() => props.modelValue,
() => {
valueHtml.value = props.modelValue
})
}
)
</script>
<style scoped lang="scss">
</style>
<style scoped lang="scss"></style>

View File

@@ -11,7 +11,6 @@
</div>
</template>
<script setup>
import '@wangeditor/editor/dist/css/style.css' // 引入 css
import { onBeforeUnmount, ref, shallowRef, watch } from 'vue'
@@ -48,11 +47,12 @@ const handleCreated = (editor) => {
valueHtml.value = props.modelValue
}
watch(() => props.modelValue, () => {
watch(
() => props.modelValue,
() => {
valueHtml.value = props.modelValue
})
}
)
</script>
<style scoped lang="scss">
</style>
<style scoped lang="scss"></style>

View File

@@ -12,21 +12,18 @@
:accept="accept"
class="upload-btn"
>
<el-button type="primary">
上传文件
</el-button>
<el-button type="primary"> 上传文件 </el-button>
</el-upload>
</div>
</template>
<script setup>
import { ref } from 'vue'
import { ElMessage } from 'element-plus'
import { getBaseUrl } from '@/utils/format'
defineOptions({
name: 'UploadCommon',
name: 'UploadCommon'
})
defineProps({
@@ -37,10 +34,9 @@ defineProps({
accept: {
type: String,
default: ''
},
}
})
const fullscreenLoading = ref(false)
const model = defineModel({ type: Array })
@@ -82,6 +78,4 @@ const uploadError = (err) => {
fullscreenLoading.value = false
emits('on-error', err)
}
</script>

View File

@@ -15,10 +15,15 @@
muted
preload="metadata"
>
<source :src="getUrl(model) + '#t=1'">
<source :src="getUrl(model) + '#t=1'" />
</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
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"
@@ -61,5 +66,4 @@ const chooseItem = () => {
const deleteItem = () => {
emits('deleteItem')
}
</script>

View File

@@ -6,10 +6,7 @@
@chooseItem="openChooseImg"
@deleteItem="openChooseImg"
/>
<div
v-else
class="w-full gap-4 flex flex-wrap"
>
<div v-else class="w-full gap-4 flex flex-wrap">
<selectComponent
v-for="(item, index) in model"
:key="index"
@@ -18,25 +15,18 @@
@deleteItem="deleteImg(index)"
/>
<selectComponent
v-if="model?.length < props.maxUpdateCount || props.maxUpdateCount === 0"
v-if="
model?.length < props.maxUpdateCount || props.maxUpdateCount === 0
"
@chooseItem="openChooseImg"
@deleteItem="openChooseImg"
/>
</div>
<el-drawer
v-model="drawer"
title="媒体库"
size="650px"
>
<warning-bar
title="点击“文件名/备注”可以编辑文件名或者备注内容。"
/>
<el-drawer v-model="drawer" title="媒体库" size="650px">
<warning-bar title="点击“文件名/备注”可以编辑文件名或者备注内容。" />
<div class="gva-btn-list gap-2">
<upload-common
:image-common="imageCommon"
@on-success="getImageList"
/>
<upload-common :image-common="imageCommon" @on-success="getImageList" />
<upload-image
:image-url="imageUrl"
:file-size="512"
@@ -48,21 +38,15 @@
class="keyword"
placeholder="请输入文件名或备注"
/>
<el-button
type="primary"
icon="search"
@click="getImageList"
>
<el-button type="primary" icon="search" @click="getImageList">
查询
</el-button>
</div>
<div class="flex flex-wrap gap-4">
<div v-for="(item, key) in picList" :key="key" class="w-40">
<div
v-for="(item,key) in picList"
:key="key"
class="w-40"
class="w-40 h-40 border rounded overflow-hidden border-dashed border-gray-300 cursor-pointer relative group"
>
<div class="w-40 h-40 border rounded overflow-hidden border-dashed border-gray-300 cursor-pointer relative group">
<el-image
:key="key"
:src="getUrl(item.url)"
@@ -85,7 +69,7 @@
preload="metadata"
@click="chooseImg(item.url)"
>
<source :src="getUrl(item.url) + '#t=1'">
<source :src="getUrl(item.url) + '#t=1'" />
您的浏览器不支持视频播放
</video>
<div
@@ -98,7 +82,10 @@
</div>
</template>
</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>
</div>
</div>
@@ -126,7 +113,11 @@
<script setup>
import { getUrl, isVideoExt } from '@/utils/image'
import { ref } from 'vue'
import { getFileList, editFileName, deleteFile } from '@/api/fileUploadAndDownload'
import {
getFileList,
editFileName,
deleteFile
} from '@/api/fileUploadAndDownload'
import UploadImage from '@/components/upload/image.vue'
import UploadCommon from '@/components/upload/common.vue'
import WarningBar from '@/components/warningBar/warningBar.vue'
@@ -180,17 +171,19 @@ const editFileNameFunc = async(row) => {
inputPattern: /\S/,
inputErrorMessage: '不能为空',
inputValue: row.name
}).then(async({ value }) => {
})
.then(async ({ value }) => {
row.name = value
const res = await editFileName(row)
if (res.code === 0) {
ElMessage({
type: 'success',
message: '编辑成功!',
message: '编辑成功!'
})
getImageList()
}
}).catch(() => {
})
.catch(() => {
ElMessage({
type: 'info',
message: '取消修改'
@@ -202,7 +195,20 @@ 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 videoTyteList = [
'mp4',
'avi',
'rmvb',
'rm',
'asf',
'divx',
'mpg',
'mpeg',
'mpe',
'wmv',
'mkv',
'vob'
]
const listObj = {
image: imageTypeList,
@@ -211,7 +217,7 @@ const listObj = {
const chooseImg = (url) => {
if (props.fileType) {
const typeSuccess = listObj[props.fileType].some(item => {
const typeSuccess = listObj[props.fileType].some((item) => {
if (url.includes(item)) {
return true
}
@@ -242,7 +248,11 @@ const openChooseImg = async() => {
}
const getImageList = async () => {
const res = await getFileList({ page: page.value, pageSize: pageSize.value, ...search.value })
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
@@ -256,21 +266,22 @@ const deleteCheck = (item) => {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(async() => {
})
.then(async () => {
const res = await deleteFile(item)
if (res.code === 0) {
ElMessage({
type: 'success',
message: '删除成功!',
message: '删除成功!'
})
getImageList()
}
}).catch(() => {
})
.catch(() => {
ElMessage({
type: 'info',
message: '已取消删除'
})
})
}
</script>

View File

@@ -1,13 +1,6 @@
<template>
<svg
:class="svgClass"
v-bind="$attrs"
:color="color"
>
<use
:xlink:href="'#'+name"
rel="external nofollow"
/>
<svg :class="svgClass" v-bind="$attrs" :color="color">
<use :xlink:href="'#' + name" rel="external nofollow" />
</svg>
</template>
<script setup>

View File

@@ -15,14 +15,13 @@
</template>
<script setup>
import { ref } from 'vue'
import { ElMessage } from 'element-plus'
import { isVideoMime, isImageMime } from '@/utils/image'
import { getBaseUrl } from '@/utils/format'
defineOptions({
name: 'UploadCommon',
name: 'UploadCommon'
})
const emit = defineEmits(['on-success'])
@@ -37,7 +36,9 @@ const checkFile = (file) => {
const isImage = isImageMime(file.type)
let pass = true
if (!isVideo && !isImage) {
ElMessage.error('上传图片只能是 jpg,png,svg,webp 格式, 上传视频只能是 mp4,webm 格式!')
ElMessage.error(
'上传图片只能是 jpg,png,svg,webp 格式, 上传视频只能是 mp4,webm 格式!'
)
fullscreenLoading.value = false
pass = false
}
@@ -71,6 +72,4 @@ const uploadError = () => {
})
fullscreenLoading.value = false
}
</script>

View File

@@ -1,4 +1,3 @@
<template>
<div>
<el-upload
@@ -19,7 +18,7 @@ import { ElMessage } from 'element-plus'
import { getBaseUrl } from '@/utils/format'
defineOptions({
name: 'UploadImage',
name: 'UploadImage'
})
const emit = defineEmits(['on-success'])
@@ -61,7 +60,6 @@ const handleImageSuccess = (res) => {
emit('on-success', data.file.url)
}
}
</script>
<style lang="scss" scoped>

View File

@@ -1,30 +1,52 @@
/**
* 网站配置文件
*/
const greenText = (text) => `\x1b[32m${text}\x1b[0m`;
const greenText = (text) => `\x1b[32m${text}\x1b[0m`
const config = {
appName: 'Gin-Vue-Admin',
appLogo: 'logo.png',
showViteLogo: true,
logs: [],
logs: []
}
export const viteLogo = (env) => {
if (config.showViteLogo) {
console.log(greenText(`> 欢迎使用Gin-Vue-Admin开源地址https://github.com/flipped-aurora/gin-vue-admin`));
console.log(greenText(`> 当前版本:v2.7.7`));
console.log(greenText(`> 加群方式:微信shouzi_1994 QQ群470239250`));
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(`> 默认自动化文档地址: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');
console.log(
greenText(
`> 欢迎使用Gin-Vue-Admin开源地址https://github.com/flipped-aurora/gin-vue-admin`
)
)
console.log(greenText(`> 当前版本:v2.7.7`))
console.log(greenText(`> 加群方式:微信shouzi_1994 QQ群470239250`))
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(
`> 默认自动化文档地址: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',
render() {
return h(svgIcon, {
name: name,
name: name
})
},
}
})
const registerIcons = async (app) => {
const iconModules = import.meta.glob('@/assets/icons/**/*.svg') // 系统目录 svg 图标
const pluginIconModules = import.meta.glob('@/plugin/**/assets/icons/**/*.svg') // 插件目录 svg 图标
const mergedIconModules = Object.assign({}, iconModules, pluginIconModules); // 合并所有 svg 图标
const pluginIconModules = import.meta.glob(
'@/plugin/**/assets/icons/**/*.svg'
) // 插件目录 svg 图标
const mergedIconModules = Object.assign({}, iconModules, pluginIconModules) // 合并所有 svg 图标
for (const path in mergedIconModules) {
let pluginName = ""
if (path.startsWith("/src/plugin/")) {
let pluginName = ''
if (path.startsWith('/src/plugin/')) {
pluginName = `${path.split('/')[3]}-`
}
const iconName = path.split('/').pop().replace(/\.svg$/, '')
const iconName = path
.split('/')
.pop()
.replace(/\.svg$/, '')
// 如果iconName带空格则不加入到图标库中并且提示名称不合法
if (iconName.indexOf(' ') !== -1) {
console.error(`icon ${iconName}.svg includes whitespace in ${path}`)
@@ -32,11 +37,12 @@ const registerIcons = async(app) => {
}
const key = `${pluginName}${iconName}`
// 开发模式下列出所有 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)
config.logs.push({
'key': key,
'label': key,
key: key,
label: key
})
app.component(key, iconComponent)
}

View File

@@ -27,7 +27,7 @@ export default {
return
}
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) {
flag = !flag
}
@@ -38,4 +38,3 @@ export default {
})
}
}

View File

@@ -1,19 +1,18 @@
// 本组件参考 arco-pro 的实现
// 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 { useAppStore } from '@/pinia';
import { computed } from 'vue'
import { useAppStore } from '@/pinia'
export default function useChartOption(sourceOption) {
const appStore = useAppStore();
const appStore = useAppStore()
const isDark = computed(() => {
return appStore.theme === 'dark';
});
return appStore.theme === 'dark'
})
const chartOption = computed(() => {
return sourceOption(isDark.value);
});
return sourceOption(isDark.value)
})
return {
chartOption,
};
chartOption
}
}

View File

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

View File

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

View File

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

View File

@@ -13,21 +13,20 @@ const getRouter = async(userStore) => {
await routerStore.SetAsyncRouter()
await userStore.GetUserInfo()
const asyncRouters = routerStore.asyncRouters
asyncRouters.forEach(asyncRouter => {
asyncRouters.forEach((asyncRouter) => {
router.addRoute(asyncRouter)
})
}
const removeLoading = () => {
const element = document.getElementById('gva-loading-box');
const element = document.getElementById('gva-loading-box')
if (element) {
element.remove();
element.remove()
}
}
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) {
for (let i = 1; i < to.matched.length; i++) {
const element = to.matched[i - 1]
@@ -85,8 +84,8 @@ router.beforeEach(async(to, from) => {
} else {
// 不在白名单中并且已经登录的时候
if (token) {
if(sessionStorage.getItem("needToHome") === 'true') {
sessionStorage.removeItem("needToHome")
if (sessionStorage.getItem('needToHome') === 'true') {
sessionStorage.removeItem('needToHome')
return { path: '/' }
}
// 添加flag防止多次获取动态路由和栈溢出
@@ -124,7 +123,6 @@ router.beforeEach(async(to, from) => {
}
})
router.afterEach(() => {
// 路由加载完成后关闭进度条
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()
export {
store,
useAppStore,
useUserStore,
useDictionaryStore
}
export { store, useAppStore, useUserStore, useDictionaryStore }

View File

@@ -1,10 +1,8 @@
import { defineStore } from 'pinia'
import { ref, watchEffect, reactive } from 'vue'
import { setBodyPrimaryColor } from '@/utils/format'
export const useAppStore = defineStore('app', () => {
const device = ref("")
const device = ref('')
const config = reactive({
weakness: false,
grey: false,
@@ -22,30 +20,30 @@ export const useAppStore = defineStore('app', () => {
const toggleTheme = (dark) => {
if (dark) {
theme.value = 'dark';
theme.value = 'dark'
} else {
theme.value = 'light';
theme.value = 'light'
}
}
const toggleWeakness = (e) => {
config.weakness = e;
config.weakness = e
}
const toggleGrey = (e) => {
config.grey = e;
config.grey = e
}
const togglePrimaryColor = (e) => {
config.primaryColor = e;
config.primaryColor = e
}
const toggleTabs = (e) => {
config.showTabs = e;
config.showTabs = e
}
const toggleDevice = (e) => {
device.value = e;
device.value = e
}
const toggleDarkMode = (e) => {
@@ -63,19 +61,19 @@ export const useAppStore = defineStore('app', () => {
}
const toggleConfigSideWidth = (e) => {
config.layout_side_width = e;
config.layout_side_width = e
}
const toggleConfigSideCollapsedWidth = (e) => {
config.layout_side_collapsed_width = e;
config.layout_side_collapsed_width = e
}
const toggleConfigSideItemHeight = (e) => {
config.layout_side_item_height = e;
config.layout_side_item_height = e
}
const toggleConfigWatermark = (e) => {
config.show_watermark = e;
config.show_watermark = e
}
const toggleSideModel = (e) => {
@@ -84,32 +82,30 @@ export const useAppStore = defineStore('app', () => {
watchEffect(() => {
if (theme.value === 'dark') {
document.documentElement.classList.add('dark');
document.documentElement.classList.remove('light');
document.documentElement.classList.add('dark')
document.documentElement.classList.remove('light')
} else {
document.documentElement.classList.add('light');
document.documentElement.classList.remove('dark');
document.documentElement.classList.add('light')
document.documentElement.classList.remove('dark')
}
})
watchEffect(() => {
// 色弱模式监听处理
if (config.weakness) {
document.documentElement.classList.add('html-weakenss');
document.documentElement.classList.add('html-weakenss')
} else {
document.documentElement.classList.remove('html-weakenss');
document.documentElement.classList.remove('html-weakenss')
}
})
watchEffect(() => {
// 灰色模式监听处理
if (config.grey) {
document.documentElement.classList.add('html-grey');
document.documentElement.classList.add('html-grey')
} else {
document.documentElement.classList.remove('html-grey');
document.documentElement.classList.remove('html-grey')
}
})
watchEffect(() => {
if (config.darkMode === 'auto') {
toggleDarkModeAuto()
@@ -143,5 +139,4 @@ export const useAppStore = defineStore('app', () => {
toggleConfigWatermark,
toggleSideModel
}
})

View File

@@ -18,7 +18,8 @@ export const useDictionaryStore = defineStore('dictionary', () => {
if (res.code === 0) {
const dictionaryRes = {}
const dict = []
res.data.resysDictionary.sysDictionaryDetails && res.data.resysDictionary.sysDictionaryDetails.forEach(item => {
res.data.resysDictionary.sysDictionaryDetails &&
res.data.resysDictionary.sysDictionaryDetails.forEach((item) => {
dict.push({
label: item.label,
value: item.value,

View File

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

View File

@@ -7,7 +7,7 @@ import { ref, watch } from 'vue'
import { useRouterStore } from './router'
import cookie from 'js-cookie'
import {useAppStore} from "@/pinia";
import { useAppStore } from '@/pinia'
export const useUserStore = defineStore('user', () => {
const appStore = useAppStore()
@@ -17,13 +17,15 @@ export const useUserStore = defineStore('user', () => {
uuid: '',
nickName: '',
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) => {
userInfo.value = val
if (val.originSetting) {
Object.keys(appStore.config).forEach(key => {
Object.keys(appStore.config).forEach((key) => {
appStore.config[key] = val.originSetting[key]
})
}
@@ -57,7 +59,7 @@ export const useUserStore = defineStore('user', () => {
const LoginIn = async (loginInfo) => {
loadingInstance.value = ElLoading.service({
fullscreen: true,
text: '登录中,请稍候...',
text: '登录中,请稍候...'
})
const res = await login(loginInfo)
@@ -78,7 +80,7 @@ export const useUserStore = defineStore('user', () => {
const asyncRouters = routerStore.asyncRouters
// 注册到路由表里
asyncRouters.forEach(asyncRouter => {
asyncRouters.forEach((asyncRouter) => {
router.addRoute(asyncRouter)
})
@@ -95,7 +97,6 @@ export const useUserStore = defineStore('user', () => {
window.localStorage.setItem('osType', 'MAC')
}
// 全部操作均结束关闭loading并返回
loadingInstance.value.close()
return true
@@ -124,9 +125,12 @@ export const useUserStore = defineStore('user', () => {
localStorage.removeItem('originSetting')
}
watch(() => token.value, () => {
watch(
() => token.value,
() => {
window.localStorage.setItem('token', token.value)
})
}
)
return {
userInfo,

View File

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

View File

@@ -1,16 +1,36 @@
<template>
<div>
<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-input v-model="formData.title" :clearable="true" placeholder="请输入标题" />
<el-input
v-model="formData.title"
:clearable="true"
placeholder="请输入标题"
/>
</el-form-item>
<el-form-item label="内容:" prop="content">
<RichEdit v-model="formData.content" />
</el-form-item>
<el-form-item label="作者:" prop="userID">
<el-select 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
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-form-item>
<el-form-item label="附件:" prop="attachments">
@@ -38,7 +58,7 @@ defineOptions({
})
// 自动获取字典
import { useRoute, useRouter } from "vue-router"
import { useRoute, useRouter } from 'vue-router'
import { ElMessage } from 'element-plus'
import { ref, reactive } from 'vue'
import SelectFile from '@/components/selectFile/selectFile.vue'
@@ -53,11 +73,10 @@ const formData = ref({
title: '',
content: '',
userID: undefined,
attachments: [],
attachments: []
})
// 验证规则
const rule = reactive({
})
const rule = reactive({})
const elFormRef = ref()
const dataSource = ref([])
@@ -113,8 +132,6 @@ const save = async() => {
const back = () => {
router.go(-1)
}
</script>
<style>
</style>
<style></style>

View File

@@ -1,22 +1,50 @@
<template>
<div>
<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">
<template #label>
<span>
创建日期
<el-tooltip content="搜索范围是开始日期(包含)至结束日期(不包含)">
<el-tooltip
content="搜索范围是开始日期(包含)至结束日期(不包含)"
>
<el-icon><QuestionFilled /></el-icon>
</el-tooltip>
</span>
</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>
<template v-if="showAllQuery">
<!-- 将需要控制显示状态的查询条件添加到此范围内 -->
</template>
@@ -25,13 +53,23 @@
<el-button type="primary" icon="search" @click="onSubmit">
查询
</el-button>
<el-button icon="refresh" @click="onReset">
重置
</el-button>
<el-button v-if="!showAllQuery" link type="primary" icon="arrow-down" @click="showAllQuery=true">
<el-button icon="refresh" @click="onReset"> 重置 </el-button>
<el-button
v-if="!showAllQuery"
link
type="primary"
icon="arrow-down"
@click="showAllQuery = true"
>
展开
</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-form-item>
@@ -42,7 +80,12 @@
<el-button type="primary" icon="plus" @click="openDialog">
新增
</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>
</div>
@@ -65,24 +108,46 @@
<el-table-column align="left" label="标题" prop="title" width="120" />
<el-table-column align="left" label="作者" prop="userID" width="120">
<template #default="scope">
<span>{{ filterDataSource(dataSource.userID,scope.row.userID) }}</span>
<span>{{
filterDataSource(dataSource.userID, scope.row.userID)
}}</span>
</template>
</el-table-column>
<el-table-column label="附件" prop="attachments" width="200">
<template #default="scope">
<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 }}
</el-tag>
</div>
</template>
</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">
<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 type="primary" link icon="delete" @click="deleteRow(scope.row)">
<el-button
type="primary"
link
icon="delete"
@click="deleteRow(scope.row)"
>
删除
</el-button>
</template>
@@ -100,31 +165,53 @@
/>
</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>
<div class="flex justify-between items-center">
<span class="text-lg">{{ type === 'create' ? '添加' : '修改' }}</span>
<div>
<el-button type="primary" @click="enterDialog">
</el-button>
<el-button @click="closeDialog">
</el-button>
<el-button type="primary" @click="enterDialog"> 确 定 </el-button>
<el-button @click="closeDialog"> 取 消 </el-button>
</div>
</div>
</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-input v-model="formData.title" :clearable="true" placeholder="请输入标题" />
<el-input
v-model="formData.title"
:clearable="true"
placeholder="请输入标题"
/>
</el-form-item>
<el-form-item label="内容:" prop="content">
<RichEdit v-model="formData.content" />
</el-form-item>
<el-form-item label="作者:" prop="userID">
<el-select 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
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-form-item>
<el-form-item label="附件:" prop="attachments">
@@ -168,7 +255,7 @@ const formData = ref({
title: '',
content: '',
userID: undefined,
attachments: [],
attachments: []
})
const dataSource = ref([])
const getDataSourceFunc = async () => {
@@ -179,26 +266,39 @@ const formData = ref({
}
getDataSourceFunc()
// 验证规则
const rule = reactive({
})
const rule = reactive({})
const searchRule = reactive({
createdAt: [
{ validator: (rule, value, callback) => {
if (searchInfo.value.startCreatedAt && !searchInfo.value.endCreatedAt) {
{
validator: (rule, value, callback) => {
if (
searchInfo.value.startCreatedAt &&
!searchInfo.value.endCreatedAt
) {
callback(new Error('请填写结束日期'))
} else if (!searchInfo.value.startCreatedAt && searchInfo.value.endCreatedAt) {
} else if (
!searchInfo.value.startCreatedAt &&
searchInfo.value.endCreatedAt
) {
callback(new Error('请填写开始日期'))
} else if (searchInfo.value.startCreatedAt && searchInfo.value.endCreatedAt && (searchInfo.value.startCreatedAt.getTime() === searchInfo.value.endCreatedAt.getTime() || searchInfo.value.startCreatedAt.getTime() > searchInfo.value.endCreatedAt.getTime())) {
} 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('开始日期应当早于结束日期'))
} else {
callback()
}
}, trigger: 'change' }
],
},
trigger: 'change'
}
]
})
const elFormRef = ref()
@@ -241,7 +341,11 @@ const handleCurrentChange = (val) => {
// 查询
const getTableData = async () => {
const table = await getInfoList({ page: page.value, pageSize: pageSize.value, ...searchInfo.value })
const table = await getInfoList({
page: page.value,
pageSize: pageSize.value,
...searchInfo.value
})
if (table.code === 0) {
tableData.value = table.data.list
total.value = table.data.total
@@ -255,13 +359,11 @@ getTableData()
// ============== 表格控制部分结束 ===============
// 获取需要的字典 可能为空 按需保留
const setOptions = async () =>{
}
const setOptions = async () => {}
// 获取需要的字典 可能为空 按需保留
setOptions()
// 多选数据
const multipleSelection = ref([])
// 多选
@@ -296,7 +398,7 @@ const onDelete = async() => {
return
}
multipleSelection.value &&
multipleSelection.value.map(item => {
multipleSelection.value.map((item) => {
IDs.push(item.ID)
})
const res = await deleteInfoByIds({ IDs })
@@ -326,7 +428,6 @@ const updateInfoFunc = async(row) => {
}
}
// 删除行
const deleteInfoFunc = async (row) => {
const res = await deleteInfo({ ID: row.ID })
@@ -358,7 +459,7 @@ const closeDialog = () => {
title: '',
content: '',
userID: undefined,
attachments: [],
attachments: []
}
}
// 弹窗确定
@@ -391,11 +492,9 @@ const enterDialog = async () => {
const downloadFile = (url) => {
window.open(getUrl(url), '_blank')
}
</script>
<style>
.file-list {
display: flex;
flex-wrap: wrap;
@@ -409,5 +508,4 @@ const downloadFile = (url) => {
.fileBtn:last-child {
margin-bottom: 0;
}
</style>

View File

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

View File

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

View File

@@ -1,6 +1,7 @@
import { createRouter, createWebHashHistory } from 'vue-router'
const routes = [{
const routes = [
{
path: '/',
redirect: '/login'
},
@@ -17,7 +18,7 @@ const routes = [{
{
path: '/:catchAll(.*)',
meta: {
closeTab: true,
closeTab: true
},
component: () => import('@/view/error/index.vue')
}
@@ -25,7 +26,7 @@ const routes = [{
const router = createRouter({
history: createWebHashHistory(),
routes,
routes
})
export default router

View File

@@ -3,22 +3,22 @@
'white': #ffffff,
'black': #000000,
'primary': (
'base': #4d70ff,
'base': #4d70ff
),
'success': (
'base': #67c23a,
'base': #67c23a
),
'warning': (
'base': #e6a23c,
'base': #e6a23c
),
'danger': (
'base': #f56c6c,
'base': #f56c6c
),
'error': (
'base': #f56c6c,
'base': #f56c6c
),
'info': (
'base': #909399,
),
'base': #909399
)
)
);

View File

@@ -1,5 +1,5 @@
@use '@/style/main.scss';
@use "@/style/reset";
@use '@/style/reset';
@tailwind base;
@tailwind components;
@@ -25,7 +25,6 @@
}
}
.el-drawer__header {
margin-bottom: 0 !important;
padding-top: 16px !important;
@@ -33,20 +32,22 @@
@apply border-0 border-b border-solid border-gray-200;
}
.el-form--inline {
.el-form-item {
& > .el-input, .el-cascader, .el-select, .el-date-editor, .el-autocomplete {
& > .el-input,
.el-cascader,
.el-select,
.el-date-editor,
.el-autocomplete {
@apply w-52;
}
}
}
.el-dropdown {
@apply overflow-hidden
@apply overflow-hidden;
}
.el-table {
tr {
th {
@@ -70,7 +71,6 @@
@apply dark:bg-slate-900;
}
}
}
}
@@ -104,12 +104,10 @@
}
}
.el-sub-menu.el-sub-menu__hide-arrow {
height: 44px;
}
.el-tabs__header {
margin: 0 0 1px !important;
}

View File

@@ -1,12 +1,13 @@
@font-face {
font-family: 'gvaIcon';
src: url('data:font/ttf;charset=utf-8;base64,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AAAAAAALABMBkAADAAEECQAAACYAAAADAAEECQABABAAPAADAAEECQACAA4AVwADAAEECQADABAAbwADAAEECQAEABAAigADAAEECQAFABYApQADAAEECQAGABAAyQADAAEECQAKAFYA5AADAAEECQALACYBaABDAHIAZQBhAHQAZQBkACAAYgB5ACAAaQBjAG8AbgBmAG8AbgB0AABDcmVhdGVkIGJ5IGljb25mb250AABpAGMAbwBuAGYAbwBuAHQAAGljb25mb250AABSAGUAZwB1AGwAYQByAABSZWd1bGFyAABpAGMAbwBuAGYAbwBuAHQAAGljb25mb250AABpAGMAbwBuAGYAbwBuAHQAAGljb25mb250AABWAGUAcgBzAGkAbwBuACAAMQAuADAAAFZlcnNpb24gMS4wAABpAGMAbwBuAGYAbwBuAHQAAGljb25mb250AABHAGUAbgBlAHIAYQB0AGUAZAAgAGIAeQAgAHMAdgBnADIAdAB0AGYAIABmAHIAbwBtACAARgBvAG4AdABlAGwAbABvACAAcAByAG8AagBlAGMAdAAuAABHZW5lcmF0ZWQgYnkgc3ZnMnR0ZiBmcm9tIEZvbnRlbGxvIHByb2plY3QuAABoAHQAdABwADoALwAvAGYAbwBuAHQAZQBsAGwAbwAuAGMAbwBtAABodHRwOi8vZm9udGVsbG8uY29tAAAAAAIAAAAAAAAACgAAAAAAAQAAAAAAAAAAAAAAAAAAAAAACwAAAAEAAgECAQMBBAEFAQYBBwEIAQkRYXJyb3ctZG91YmxlLWxlZnQSYXJyb3ctZG91YmxlLXJpZ2h0EGN1c3RvbWVyLXNlcnZpY2URZnVsbHNjcmVlbi1leHBhbmQRZnVsbHNjcmVlbi1zaHJpbmsGcHJvbXB0B3JlZnJlc2gGc2VhcmNoAAAAAf//AAIAAQAAAAwAAAAWAAAAAgABAAMACgABAAQAAAACAAAAAAAAAAEAAAAA1aQnCAAAAADeWFEjAAAAAN5YUSM=') format('truetype');
src: url('data:font/ttf;charset=utf-8;base64,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AAAAAAALABMBkAADAAEECQAAACYAAAADAAEECQABABAAPAADAAEECQACAA4AVwADAAEECQADABAAbwADAAEECQAEABAAigADAAEECQAFABYApQADAAEECQAGABAAyQADAAEECQAKAFYA5AADAAEECQALACYBaABDAHIAZQBhAHQAZQBkACAAYgB5ACAAaQBjAG8AbgBmAG8AbgB0AABDcmVhdGVkIGJ5IGljb25mb250AABpAGMAbwBuAGYAbwBuAHQAAGljb25mb250AABSAGUAZwB1AGwAYQByAABSZWd1bGFyAABpAGMAbwBuAGYAbwBuAHQAAGljb25mb250AABpAGMAbwBuAGYAbwBuAHQAAGljb25mb250AABWAGUAcgBzAGkAbwBuACAAMQAuADAAAFZlcnNpb24gMS4wAABpAGMAbwBuAGYAbwBuAHQAAGljb25mb250AABHAGUAbgBlAHIAYQB0AGUAZAAgAGIAeQAgAHMAdgBnADIAdAB0AGYAIABmAHIAbwBtACAARgBvAG4AdABlAGwAbABvACAAcAByAG8AagBlAGMAdAAuAABHZW5lcmF0ZWQgYnkgc3ZnMnR0ZiBmcm9tIEZvbnRlbGxvIHByb2plY3QuAABoAHQAdABwADoALwAvAGYAbwBuAHQAZQBsAGwAbwAuAGMAbwBtAABodHRwOi8vZm9udGVsbG8uY29tAAAAAAIAAAAAAAAACgAAAAAAAQAAAAAAAAAAAAAAAAAAAAAACwAAAAEAAgECAQMBBAEFAQYBBwEIAQkRYXJyb3ctZG91YmxlLWxlZnQSYXJyb3ctZG91YmxlLXJpZ2h0EGN1c3RvbWVyLXNlcnZpY2URZnVsbHNjcmVlbi1leHBhbmQRZnVsbHNjcmVlbi1zaHJpbmsGcHJvbXB0B3JlZnJlc2gGc2VhcmNoAAAAAf//AAIAAQAAAAwAAAAWAAAAAgABAAMACgABAAQAAAACAAAAAAAAAAEAAAAA1aQnCAAAAADeWFEjAAAAAN5YUSM=')
format('truetype');
font-weight: 600;
font-style: normal;
font-display: swap;
}
.gvaIcon {
font-family: "gvaIcon" !important;
font-family: 'gvaIcon' !important;
font-size: 16px;
font-style: normal;
font-weight: 800;
@@ -15,33 +16,32 @@
}
.gvaIcon-arrow-double-left:before {
content: "\e665";
content: '\e665';
}
.gvaIcon-arrow-double-right:before {
content: "\e666";
content: '\e666';
}
.gvaIcon-fullscreen-shrink:before {
content: "\e676";
content: '\e676';
}
.gvaIcon-customer-service:before {
content: "\e66a";
content: '\e66a';
}
.gvaIcon-fullscreen-expand:before {
content: "\e675";
content: '\e675';
}
.gvaIcon-prompt:before {
content: "\e67b";
content: '\e67b';
}
.gvaIcon-refresh:before {
content: "\e67c";
content: '\e67c';
}
.gvaIcon-search:before {
content: "\e67d";
content: '\e67d';
}

View File

@@ -1,4 +1,3 @@
@use '@/style/iconfont.css';
.html-grey {
@@ -20,7 +19,6 @@
@apply mb-3 flex items-center;
}
#nprogress .bar {
background: #29d !important;
}
@@ -32,7 +30,6 @@
@apply hidden;
}
.gva-search-box {
@apply p-4 bg-white text-slate-700 dark:text-slate-400 dark:bg-slate-900 rounded my-2;
}

View File

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

View File

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

View File

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

View File

@@ -11,11 +11,21 @@ Date.prototype.Format = function(fmt) {
'm+': this.getMinutes(), // 分
's+': this.getSeconds(), // 秒
'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) {
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
}

View File

@@ -18,7 +18,7 @@ export const showDictLabel = (
return ''
}
const dictMap = {}
dict.forEach(item => {
dict.forEach((item) => {
if (Reflect.has(item, keyCode) && Reflect.has(item, valueCode)) {
dictMap[item[keyCode]] = item[valueCode]
}
@@ -26,7 +26,6 @@ export const showDictLabel = (
return Reflect.has(dictMap, code) ? dictMap[code] : ''
}
export const getParams = async (key) => {
const res = await getSysParam({ key })
if (res.code === 0) {

View File

@@ -1,4 +1,5 @@
export const downloadImage = (imgsrc, name) => { // 下载图片地址和图片名
export const downloadImage = (imgsrc, name) => {
// 下载图片地址和图片名
var image = new Image()
image.setAttribute('crossOrigin', 'anonymous')
image.onload = function () {

View File

@@ -1,29 +1,17 @@
export function addEventListen(
target,
event,
handler,
capture = false
) {
export function addEventListen(target, event, handler, capture = false) {
if (
target.addEventListener &&
typeof target.addEventListener === 'function'
) {
target.addEventListener(event, handler, capture);
target.addEventListener(event, handler, capture)
}
}
export function removeEventListen(
target,
event,
handler,
capture = false
) {
export function removeEventListen(target, event, handler, capture = false) {
if (
target.removeEventListener &&
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 { getDict } from '@/utils/dictionary'
import {ref} from "vue";
import { ref } from 'vue'
export const formatBoolean = (bool) => {
if (bool !== null) {
@@ -19,18 +19,18 @@ export const formatDate = (time) => {
}
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
}
export const filterDataSource = (dataSource, value) => {
if (Array.isArray(value)) {
return value.map(item => {
const rowLabel = dataSource && dataSource.find(i => i.value === item)
return value.map((item) => {
const rowLabel = dataSource && dataSource.find((i) => i.value === item)
return rowLabel?.label
})
}
const rowLabel = dataSource && dataSource.find(item => item.value === value)
const rowLabel = dataSource && dataSource.find((item) => item.value === value)
return rowLabel?.label
}
@@ -39,10 +39,12 @@ export const getDictFunc = async(type) => {
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) => {
const imgArr = []
if (arr instanceof Array) { // 如果是数组类型
if (arr instanceof Array) {
// 如果是数组类型
for (const arrKey in arr) {
if (arr[arrKey].slice(0, 4) !== 'http') {
imgArr.push(path + arr[arrKey])
@@ -50,7 +52,8 @@ export const ReturnArrImg = (arr) => {
imgArr.push(arr[arrKey])
}
}
} else { // 如果不是数组类型
} else {
// 如果不是数组类型
if (arr.slice(0, 4) !== 'http') {
imgArr.push(path + arr)
} else {
@@ -65,65 +68,69 @@ export const returnArrImg = ReturnArrImg
export const onDownloadFile = (url) => {
window.open(path + url)
}
const colorToHex = u=>{
let e = u.replace("#", "").match(/../g);
for (let t = 0; t < 3; t++)
e[t] = parseInt(e[t], 16);
const colorToHex = (u) => {
let e = u.replace('#', '').match(/../g)
for (let t = 0; t < 3; t++) e[t] = parseInt(e[t], 16)
return e
}
const hexToColor = (u, e, t) => {
let a = [u.toString(16), e.toString(16), t.toString(16)];
for (let n = 0; n < 3; n++)
a[n].length === 1 && (a[n] = `0${a[n]}`);
return `#${a.join("")}`
let a = [u.toString(16), e.toString(16), t.toString(16)]
for (let n = 0; n < 3; n++) a[n].length === 1 && (a[n] = `0${a[n]}`)
return `#${a.join('')}`
}
const generateAllColors = (u, e) => {
let t = colorToHex(u);
const target = [10, 10, 30];
for (let a = 0; a < 3; a++)
t[a] = Math.floor(t[a] * (1 - e) + target[a] * e);
let t = colorToHex(u)
const target = [10, 10, 30]
for (let a = 0; a < 3; a++) t[a] = Math.floor(t[a] * (1 - e) + target[a] * e)
return hexToColor(t[0], t[1], t[2])
}
const generateAllLightColors = (u, e) => {
let t = colorToHex(u);
const target = [240, 248, 255]; // RGB for blue white color
for (let a = 0; a < 3; a++)
t[a] = Math.floor(t[a] * (1 - e) + target[a] * e);
return hexToColor(t[0], t[1], t[2]);
let t = colorToHex(u)
const target = [240, 248, 255] // RGB for blue white color
for (let a = 0; a < 3; a++) t[a] = Math.floor(t[a] * (1 - e) + target[a] * e)
return hexToColor(t[0], t[1], t[2])
}
function addOpacityToColor(u, opacity) {
let t = colorToHex(u);
return `rgba(${t[0]}, ${t[1]}, ${ t[2]}, ${opacity})`;
let t = colorToHex(u)
return `rgba(${t[0]}, ${t[1]}, ${t[2]}, ${opacity})`
}
export const setBodyPrimaryColor = (primaryColor, darkMode) => {
let fmtColorFunc = generateAllColors
if (darkMode === 'light') {
fmtColorFunc = generateAllLightColors
}
document.documentElement.style.setProperty('--el-color-primary', primaryColor)
document.documentElement.style.setProperty('--el-color-primary-bg', addOpacityToColor(primaryColor, 0.4))
document.documentElement.style.setProperty(
'--el-color-primary-bg',
addOpacityToColor(primaryColor, 0.4)
)
for (let times = 1; times <= 2; times++) {
document.documentElement.style.setProperty(`--el-color-primary-dark-${times}`, fmtColorFunc(primaryColor, times / 10))
document.documentElement.style.setProperty(
`--el-color-primary-dark-${times}`,
fmtColorFunc(primaryColor, times / 10)
)
}
for (let times = 1; times <= 10; times++) {
document.documentElement.style.setProperty(`--el-color-primary-light-${times}`, fmtColorFunc(primaryColor, times / 10))
document.documentElement.style.setProperty(
`--el-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)
export const getBaseUrl = () => {
return baseUrl.value === "/" ? "" : baseUrl.value
return baseUrl.value === '/' ? '' : baseUrl.value
}
export const CreateUUID = () => {
@@ -134,6 +141,6 @@ export const CreateUUID = () => {
return '00000000-0000-0000-0000-000000000000'.replace(/0/g, (c) => {
const r = (d + Math.random() * 16) % 16 | 0 // d是随机种子
d = Math.floor(d / 16)
return (c === '0' ? r : (r & 0x3 | 0x8)).toString(16)
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 fileSize = this.file.size / 1024
return new Promise(resolve => {
return new Promise((resolve) => {
const reader = new FileReader()
reader.readAsDataURL(this.file)
reader.onload = () => {
@@ -26,7 +26,7 @@ export default class ImageCompress {
ctx.clearRect(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)
@@ -69,7 +69,7 @@ export default class ImageCompress {
fileSizeKB(dataURL) {
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
}
@@ -94,20 +94,29 @@ export default class ImageCompress {
const path = import.meta.env.VITE_FILE_API
export const getUrl = (url) => {
if (url && url.slice(0, 4) !== 'http') {
if (path === "/"){
if (path === '/') {
return url
}
if (url.slice(0, 1) === "/"){
if (url.slice(0, 1) === '/') {
return path + url
}
return path + "/" + url
return path + '/' + url
} else {
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 timer
let loadingInstance
const showLoading = (option = {
target: null,
}) => {
const showLoading = (
option = {
target: null
}
) => {
const loadDom = document.getElementById('gva-base-load-dom')
activeAxios++
if (timer) {
@@ -36,7 +38,7 @@ const closeLoading = () => {
}
// http request 拦截器
service.interceptors.request.use(
config => {
(config) => {
if (!config.donNotShowLoading) {
showLoading(config.loadingOption)
}
@@ -49,7 +51,7 @@ service.interceptors.request.use(
}
return config
},
error => {
(error) => {
if (!error.config.donNotShowLoading) {
closeLoading()
}
@@ -64,7 +66,7 @@ service.interceptors.request.use(
// http response 拦截器
service.interceptors.response.use(
response => {
(response) => {
const userStore = useUserStore()
if (!response.config.donNotShowLoading) {
closeLoading()
@@ -86,63 +88,77 @@ service.interceptors.response.use(
return response.data.msg ? response.data : response
}
},
error => {
(error) => {
if (!error.config.donNotShowLoading) {
closeLoading()
}
if (!error.response) {
ElMessageBox.confirm(`
ElMessageBox.confirm(
`
<p>检测到请求错误</p>
<p>${error}</p>
`, '请求报错', {
`,
'请求报错',
{
dangerouslyUseHTMLString: true,
distinguishCancelAndClose: true,
confirmButtonText: '稍后重试',
cancelButtonText: '取消'
})
}
)
return
}
switch (error.response.status) {
case 500:
ElMessageBox.confirm(`
ElMessageBox.confirm(
`
<p>检测到接口错误${error}</p>
<p>错误码<span style="color:red"> 500 </span>此类错误内容常见于后台panic请先查看后台日志如果影响您正常使用可强制登出清理缓存</p>
`, '接口报错', {
`,
'接口报错',
{
dangerouslyUseHTMLString: true,
distinguishCancelAndClose: true,
confirmButtonText: '清理缓存',
cancelButtonText: '取消'
})
.then(() => {
}
).then(() => {
const userStore = useUserStore()
userStore.ClearStorage()
router.push({ name: 'Login', replace: true })
})
break
case 404:
ElMessageBox.confirm(`
ElMessageBox.confirm(
`
<p>检测到接口错误${error}</p>
<p>错误码<span style="color:red"> 404 </span>此类错误多为接口未注册或未重启或者请求路径方法与api路径方法不符--如果为自动化代码请检查是否存在空格</p>
`, '接口报错', {
`,
'接口报错',
{
dangerouslyUseHTMLString: true,
distinguishCancelAndClose: true,
confirmButtonText: '我知道了',
cancelButtonText: '取消'
})
}
)
break
case 401:
ElMessageBox.confirm(`
ElMessageBox.confirm(
`
<p>无效的令牌</p>
<p>错误码:<span style="color:red"> 401 </span>错误信息:${error}</p>
`, '身份信息', {
`,
'身份信息',
{
dangerouslyUseHTMLString: true,
distinguishCancelAndClose: true,
confirmButtonText: '重新登录',
cancelButtonText: '取消'
})
.then(() => {
}
).then(() => {
const userStore = useUserStore()
userStore.ClearStorage()
router.push({ name: 'Login', replace: true })

View File

@@ -18,12 +18,12 @@ export const toLowerCase = (str) => {
// 驼峰转换下划线
export const toSQLLine = (str) => {
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) => {
return name.replace(/\_(\w)/g, function (all, letter) {
return letter.toUpperCase();
});
return letter.toUpperCase()
})
}

View File

@@ -8,16 +8,13 @@
</template>
<div>
<el-row>
<el-col
:span="8"
:offset="8"
>
<el-col :span="8" :offset="8">
<a href="https://github.com/flipped-aurora/gin-vue-admin">
<img
class="org-img dom-center"
src="@/assets/logo.png"
alt="gin-vue-admin"
>
/>
</a>
</el-col>
</el-row>
@@ -28,7 +25,7 @@
class="dom-center"
src="https://img.shields.io/github/watchers/flipped-aurora/gin-vue-admin.svg?label=Watch"
alt=""
>
/>
</a>
</el-col>
<el-col :span="8">
@@ -37,7 +34,7 @@
class="dom-center"
src="https://img.shields.io/github/stars/flipped-aurora/gin-vue-admin.svg?style=social"
alt=""
>
/>
</a>
</el-col>
<el-col :span="8">
@@ -46,7 +43,7 @@
class="dom-center"
src="https://img.shields.io/github/forks/flipped-aurora/gin-vue-admin.svg?label=Fork"
alt=""
>
/>
</a>
</el-col>
</el-row>
@@ -58,34 +55,27 @@
</template>
<div>
<el-row>
<el-col
:span="8"
:offset="8"
>
<el-col :span="8" :offset="8">
<a href="https://github.com/flipped-aurora">
<img
class="org-img dom-center"
src="@/assets/flipped-aurora.png"
alt="flipped-aurora"
>
/>
</a>
</el-col>
</el-row>
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4 mt-4">
<div
v-for="(item, index) in members"
:key="index"
:span="8"
class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4 mt-4"
>
<div v-for="(item, index) in members" :key="index" :span="8">
<a :href="item.html_url" class="flex items-center">
<img
class="w-8 h-8 rounded-full"
:src="item.avatar_url"
>
<img class="w-8 h-8 rounded-full" :src="item.avatar_url" />
<el-link
class="text-blue-700 ml-2 text-xl font-bold font-sans"
style=""
>{{ item.login }}</el-link>
>{{ item.login }}</el-link
>
</a>
</div>
</div>
@@ -112,12 +102,7 @@
</el-timeline-item>
</el-timeline>
</div>
<el-button
class="load-more"
type="primary"
link
@click="loadMore"
>
<el-button class="load-more" type="primary" link @click="loadMore">
Load more
</el-button>
</el-card>
@@ -150,7 +135,7 @@ const loadCommits = () => {
from: formatTimeToStr(element.commit.author.date, 'yyyy-MM-dd'),
title: element.commit.author.name,
showDayAndMonth: true,
message: element.commit.message,
message: element.commit.message
})
}
})
@@ -167,7 +152,6 @@ const loadMembers = () => {
loadCommits()
loadMembers()
</script>
<style scoped>
@@ -190,7 +174,6 @@ loadMembers()
width: 150px;
}
.dom-center {
margin-left: 50%;
transform: translateX(-50%);

View File

@@ -5,15 +5,20 @@
<template>
<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-carousel-item>
</el-carousel>
</template>
<script setup>
import banner from "@/assets/banner.jpg"
import banner2 from "@/assets/banner2.jpg"
import banner from '@/assets/banner.jpg'
import banner2 from '@/assets/banner2.jpg'
const openLink = (link) => {
window.open(link, '_blank')
@@ -22,19 +27,17 @@ const openLink = (link) => {
const banners = [
{
img: banner,
link: "https://gin-vue-admin.com/empower/index.html"
link: 'https://gin-vue-admin.com/empower/index.html'
},
{
img: banner2,
link: "https://plugin.gin-vue-admin.com"
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"
img: 'https://qmplusimg.henrongyi.top/gvaDemo/k8s.jpg',
link: 'https://plugin.gin-vue-admin.com/#/layout/newPluginInfo?id=42'
}
]
</script>
<style scoped lang="scss">
</style>
<style scoped lang="scss"></style>

View File

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

View File

@@ -10,8 +10,8 @@
</template>
<script setup>
import Chart from "@/components/charts/index.vue";
import useChartOption from '@/hooks/charts';
import Chart from '@/components/charts/index.vue'
import useChartOption from '@/hooks/charts'
import { graphic } from 'echarts'
import { computed, ref } from 'vue'
import { useAppStore } from '@/pinia'
@@ -21,8 +21,8 @@ const { config } = storeToRefs(appStore)
defineProps({
height: {
type: String,
default: '128px',
},
default: '128px'
}
})
const dotColor = computed(() => {
console.log(appStore.theme)
@@ -37,23 +37,32 @@ const graphicFactory = (side) => {
text: '',
textAlign: 'center',
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 chartsData = ref([12,22,32,45,32,78,89,92]);
}
}
const xAxis = ref([
'2024-1',
'2024-2',
'2024-3',
'2024-4',
'2024-5',
'2024-6',
'2024-7',
'2024-8'
])
const chartsData = ref([12, 22, 32, 45, 32, 78, 89, 92])
const graphicElements = ref([
graphicFactory({ left: '5%' }),
graphicFactory({ right: 0 }),
]);
graphicFactory({ right: 0 })
])
const { chartOption } = useChartOption(() => {
return {
grid: {
left: '40',
right: '0',
top: '10',
bottom: '30',
bottom: '30'
},
xAxis: {
type: 'category',
@@ -63,54 +72,54 @@ const { chartOption } = useChartOption(() => {
axisLabel: {
color: '#4E5969',
formatter(value, idx) {
if (idx === 0) return '';
if (idx === xAxis.value.length - 1) return '';
return `${value}`;
},
if (idx === 0) return ''
if (idx === xAxis.value.length - 1) return ''
return `${value}`
}
},
axisLine: {
show: false,
show: false
},
axisTick: {
show: false,
show: false
},
splitLine: {
show: true,
interval: (idx) => {
if (idx === 0) return false;
if (idx === xAxis.value.length - 1) return false;
return true;
if (idx === 0) return false
if (idx === xAxis.value.length - 1) return false
return true
},
lineStyle: {
color: dotColor.value,
},
color: dotColor.value
}
},
axisPointer: {
show: true,
lineStyle: {
color: `${config.value.primaryColor}FF`,
width: 2,
},
},
width: 2
}
}
},
yAxis: {
type: 'value',
axisLine: {
show: false,
show: false
},
axisLabel: {
formatter(value, idx) {
if (idx === 0) return value;
return `${value}k`;
},
if (idx === 0) return value
return `${value}k`
}
},
splitLine: {
show: true,
lineStyle: {
type: 'dashed',
color: dotColor.value,
},
},
color: dotColor.value
}
}
},
tooltip: {
trigger: 'axis',
@@ -121,12 +130,12 @@ const { chartOption } = useChartOption(() => {
<div class="content-panel"><span>总内容量</span><span class="tooltip-value">${(
Number(firstElement.value) * 10000
).toLocaleString()}</span></div>
</div>`;
</div>`
},
className: 'echarts-tooltip-diy',
className: 'echarts-tooltip-diy'
},
graphic: {
elements: graphicElements.value,
elements: graphicElements.value
},
series: [
{
@@ -138,25 +147,25 @@ const { chartOption } = useChartOption(() => {
emphasis: {
focus: 'series',
itemStyle: {
borderWidth: 2,
},
borderWidth: 2
}
},
lineStyle: {
width: 3,
color: new graphic.LinearGradient(0, 0, 1, 0, [
{
offset: 0,
color: `${config.value.primaryColor}80`,
color: `${config.value.primaryColor}80`
},
{
offset: 0.5,
color: `${config.value.primaryColor}92`,
color: `${config.value.primaryColor}92`
},
{
offset: 1,
color: `${config.value.primaryColor}FF`,
},
]),
color: `${config.value.primaryColor}FF`
}
])
},
showSymbol: false,
areaStyle: {
@@ -164,20 +173,18 @@ const { chartOption } = useChartOption(() => {
color: new graphic.LinearGradient(0, 0, 0, 1, [
{
offset: 0,
color: `${config.value.primaryColor}20`,
color: `${config.value.primaryColor}20`
},
{
offset: 1,
color: `${config.value.primaryColor}08`,
},
]),
},
},
],
};
});
color: `${config.value.primaryColor}08`
}
])
}
}
]
}
})
</script>
<style scoped lang="scss">
</style>
<style scoped lang="scss"></style>

View File

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

View File

@@ -31,10 +31,8 @@
</template>
<script setup>
import chartsPeopleNumber from "./charts-people-numbers.vue"
import chartsContentNumber from "./charts-content-numbers.vue"
import chartsPeopleNumber from './charts-people-numbers.vue'
import chartsContentNumber from './charts-content-numbers.vue'
defineProps({
type: {
type: Number,
@@ -42,17 +40,15 @@ defineProps({
},
title: {
type: String,
default : ""
default: ''
}
})
const data = [
[12, 22, 32, 45, 32, 78, 89, 92],
[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>
<style scoped lang="scss">
</style>
<style scoped lang="scss"></style>

View File

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

View File

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

View File

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

View File

@@ -5,8 +5,15 @@
<template>
<div class="mt-8 w-full">
<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 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">
<div
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>
</div>
<div class="text-xs mt-2 text-gray-700 dark:text-gray-300">
@@ -15,8 +22,15 @@
</div>
</div>
<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 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">
<div
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>
</div>
<div class="text-xs mt-2 text-gray-700 dark:text-gray-300">
@@ -27,8 +41,17 @@
</div>
</template>
<script setup>
import { Menu,Link,User,Service,Document,Reading,Files,Memo } from '@element-plus/icons-vue'
import {useRouter} from "vue-router";
import {
Menu,
Link,
User,
Service,
Document,
Reading,
Files,
Memo
} from '@element-plus/icons-vue'
import { useRouter } from 'vue-router'
const router = useRouter()
const toPath = (item) => {
@@ -41,51 +64,48 @@ const openLink = (item) => {
const shortcuts = [
{
icon: Menu,
title : "菜单管理",
path : "menu",
title: '菜单管理',
path: 'menu'
},
{
icon: Link,
title : "API管理",
path : "api",
title: 'API管理',
path: 'api'
},
{
icon: Service,
title : "角色管理",
path : "authority",
title: '角色管理',
path: 'authority'
},
{
icon: User,
title : "用户管理",
path : "user",
title: '用户管理',
path: 'user'
},
{
icon: Files,
title : "自动化包",
path: "autoPkg",
title: '自动化包',
path: 'autoPkg'
},
{
icon: Memo,
title : "自动代码",
path: "autoCode",
title: '自动代码',
path: 'autoCode'
}
]
const recentVisits = [
{
icon: Reading,
title : "授权购买",
path: "https://gin-vue-admin.com/empower/index.html",
title: '授权购买',
path: 'https://gin-vue-admin.com/empower/index.html'
},
{
icon: Document,
title : "插件市场",
path: "https://plugin.gin-vue-admin.com/#/layout/home",
title: '插件市场',
path: 'https://plugin.gin-vue-admin.com/#/layout/home'
}
]
</script>
<style scoped lang="scss">
</style>
<style scoped lang="scss"></style>

View File

@@ -18,37 +18,35 @@
const tableData = [
{
ranking: 1,
title : "更简洁的使用界面,更快速的操作体验",
title: '更简洁的使用界面,更快速的操作体验',
click_num: 523,
hot: 263
},
{
ranking: 2,
title : "更优质的服务,更便捷的使用体验",
title: '更优质的服务,更便捷的使用体验',
click_num: 416,
hot: 223
},
{
ranking: 3,
title : "更快速的创意实现,更高效的工作效率",
title: '更快速的创意实现,更高效的工作效率',
click_num: 337,
hot: 176
},
{
ranking: 4,
title : "更多的创意资源,更多的创意灵感",
title: '更多的创意资源,更多的创意灵感',
click_num: 292,
hot: 145
},
{
ranking: 5,
title : "更合理的代码结构,更清晰的代码逻辑",
title: '更合理的代码结构,更清晰的代码逻辑',
click_num: 173,
hot: 110
},
}
]
</script>
<style scoped lang="scss">
</style>
<style scoped lang="scss"></style>

View File

@@ -5,7 +5,13 @@
<template>
<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 }}
</a>
</div>
@@ -22,21 +28,18 @@ const wikis = [
url: 'https://gin-gonic.com/'
},
{
title : "GVA 文档",
title: 'GVA 文档',
url: 'https://www.gin-vue-admin.com/'
},
{
title : "插件市场",
title: '插件市场',
url: 'https://plugin.gin-vue-admin.com/'
},
{
title : "github 仓库",
title: 'github 仓库',
url: 'https://github.com/flipped-aurora/gin-vue-admin'
}
]
</script>
<style scoped lang="scss">
</style>
<style scoped lang="scss"></style>

View File

@@ -1,5 +1,7 @@
<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-chart :type="1" title="访问人数" />
</gva-card>
@@ -9,20 +11,37 @@
<gva-card custom-class="col-span-1 lg:col-span-2 h-32">
<gva-chart :type="3" title="解决数量" />
</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-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-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-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-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-card>
@@ -30,18 +49,29 @@
<gva-notice />
</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-card>
</div>
</template>
<script setup>
import { GvaPluginTable,GvaTable, GvaChart, GvaWiki , GvaNotice , GvaQuickLink , GvaCard , GvaBanner } from "./components"
import {
GvaPluginTable,
GvaTable,
GvaChart,
GvaWiki,
GvaNotice,
GvaQuickLink,
GvaCard,
GvaBanner
} from './components'
defineOptions({
name: 'Dashboard'
})
</script>
<style lang="scss" scoped>
</style>
<style lang="scss" scoped></style>

View File

@@ -2,10 +2,19 @@
<div>
<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">
<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>项目地址<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 class="text-lg">
常见问题为当前此角色无当前路由如果确定要使用本路由请到角色管理进行分配
</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>
</div>
</div>

View File

@@ -2,14 +2,8 @@
<div class="break-point">
<div class="gva-table-box">
<el-divider content-position="left">大文件上传</el-divider>
<form
id="fromCont"
method="post"
>
<div
class="fileUpload"
@click="inputChange"
>
<form id="fromCont" method="post">
<div class="fileUpload" @click="inputChange">
选择文件
<input
v-show="false"
@@ -18,7 +12,7 @@
multiple="multiple"
type="file"
@change="choseFile"
>
/>
</div>
</form>
<el-button
@@ -26,17 +20,12 @@
type="primary"
class="uploadBtn"
@click="getFile"
>上传文件</el-button>
>上传文件</el-button
>
<div class="el-upload__tip">请上传不超过5MB的文件</div>
<div class="list">
<transition
name="list"
tag="p"
>
<div
v-if="file"
class="list-item"
>
<transition name="list" tag="p">
<div v-if="file" class="list-item">
<el-icon>
<document />
</el-icon>
@@ -51,10 +40,11 @@
</div>
</transition>
</div>
<div class="tips">此版本为先行体验功能测试版样式美化和性能优化正在进行中上传切片文件和合成的完整文件分别再QMPlusserver目录的breakpointDir文件夹和fileDir文件夹</div>
<div class="tips">
此版本为先行体验功能测试版样式美化和性能优化正在进行中上传切片文件和合成的完整文件分别再QMPlusserver目录的breakpointDir文件夹和fileDir文件夹
</div>
</div>
</div>
</template>
<script setup>
@@ -83,7 +73,6 @@ const percentageFlage = ref(true)
// 选中文件的函数
const choseFile = async (e) => {
// 点击选择文件后取消 直接return
if (!e.target.files.length) {
return
@@ -95,7 +84,7 @@ const choseFile = async(e) => {
percentage.value = 0
if (file.value.size < maxSize) {
fileR.readAsArrayBuffer(file.value) // 把文件读成ArrayBuffer 主要为了保持跟后端的流一致
fileR.onload = async e => {
fileR.onload = async (e) => {
// 读成arrayBuffer的回调 e 为方法自带参数 相当于 dom的e 流存在e.target.result 中
const blob = e.target.result
const spark = new SparkMD5.ArrayBuffer() // 创建md5制造工具 md5用于检测文件一致性 这里不懂就打电话问我)
@@ -130,10 +119,10 @@ const choseFile = async(e) => {
const IsFinish = res.data.file.IsFinish // 是否是同文件不同命 文件md5相同 文件名不同 则默认是同一个文件但是不同文件名 此时后台数据库只需要拷贝一下数据库文件即可 不需要上传文件 即秒传功能)
if (!IsFinish) {
// 当是断点续传时候
waitUpLoad.value = formDataList.value.filter(all => {
waitUpLoad.value = formDataList.value.filter((all) => {
return !(
finishList &&
finishList.some(fi => fi.FileChunkNumber === all.key)
finishList.some((fi) => fi.FileChunkNumber === all.key)
) // 找出需要上传的切片
})
} else {
@@ -162,13 +151,13 @@ const getFile = () => {
const sliceFile = () => {
waitUpLoad.value &&
waitUpLoad.value.forEach(item => {
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 => {
fileR.onload = (e) => {
const spark = new SparkMD5.ArrayBuffer()
spark.append(e.target.result)
item.formData.append('chunkMd5', spark.end()) // 获取当前切片md5 后端用于验证切片完整性
@@ -177,7 +166,16 @@ const sliceFile = () => {
})
}
watch(() => waitNum.value, () => { percentage.value = Math.floor(((formDataList.value.length - waitNum.value) / formDataList.value.length) * 100) })
watch(
() => waitNum.value,
() => {
percentage.value = Math.floor(
((formDataList.value.length - waitNum.value) /
formDataList.value.length) *
100
)
}
)
const upLoadFileSlice = async (item) => {
// 切片上传
@@ -198,7 +196,7 @@ const upLoadFileSlice = async(item) => {
const params = {
fileName: file.value.name,
fileMd5: fileMd5.value,
filePath: res.data.filePath,
filePath: res.data.filePath
}
ElMessage.success('上传成功')
await removeChunk(params)
@@ -212,7 +210,7 @@ const inputChange = () => {
}
</script>
<style lang='scss' scoped>
<style lang="scss" scoped>
h3 {
margin: 40px 0 0;
}
@@ -284,7 +282,8 @@ a {
float: right;
}
}
.list-enter-active, .list-leave-active {
.list-enter-active,
.list-leave-active {
transition: all 1s;
}
.list-enter, .list-leave-to

View File

@@ -1,13 +1,13 @@
<template>
<div>
<warning-bar title="在资源权限中将此角色的资源权限清空 或者不包含创建者的角色 即可屏蔽此客户资源的显示" />
<warning-bar
title="在资源权限中将此角色的资源权限清空 或者不包含创建者的角色 即可屏蔽此客户资源的显示"
/>
<div class="gva-table-box">
<div class="gva-btn-list">
<el-button
type="primary"
icon="plus"
@click="openDrawer"
>新增</el-button>
<el-button type="primary" icon="plus" @click="openDrawer"
>新增</el-button
>
</div>
<el-table
ref="multipleTable"
@@ -16,15 +16,8 @@
tooltip-effect="dark"
row-key="ID"
>
<el-table-column
type="selection"
width="55"
/>
<el-table-column
align="left"
label="接入日期"
width="180"
>
<el-table-column type="selection" width="55" />
<el-table-column align="left" label="接入日期" width="180">
<template #default="scope">
<span>{{ formatDate(scope.row.CreatedAt) }}</span>
</template>
@@ -47,24 +40,22 @@
prop="sysUserId"
width="120"
/>
<el-table-column
align="left"
label="操作"
min-width="160"
>
<el-table-column align="left" label="操作" min-width="160">
<template #default="scope">
<el-button
type="primary"
link
icon="edit"
@click="updateCustomer(scope.row)"
>变更</el-button>
>变更</el-button
>
<el-button
type="primary"
link
icon="delete"
@click="deleteCustomer(scope.row)"
>删除</el-button>
>删除</el-button
>
</template>
</el-table-column>
</el-table>
@@ -90,29 +81,16 @@
<span class="text-lg">客户</span>
<div>
<el-button @click="closeDrawer"> </el-button>
<el-button
type="primary"
@click="enterDrawer"
> </el-button>
<el-button type="primary" @click="enterDrawer"> </el-button>
</div>
</div>
</template>
<el-form
:inline="true"
:model="form"
label-width="80px"
>
<el-form :inline="true" :model="form" label-width="80px">
<el-form-item label="客户名">
<el-input
v-model="form.customerName"
autocomplete="off"
/>
<el-input v-model="form.customerName" autocomplete="off" />
</el-form-item>
<el-form-item label="客户电话">
<el-input
v-model="form.customerPhoneData"
autocomplete="off"
/>
<el-input v-model="form.customerPhoneData" autocomplete="off" />
</el-form-item>
</el-form>
</el-drawer>
@@ -159,7 +137,10 @@ const handleCurrentChange = (val) => {
// 查询
const getTableData = async () => {
const table = await getExaCustomerList({ page: page.value, pageSize: pageSize.value })
const table = await getExaCustomerList({
page: page.value,
pageSize: pageSize.value
})
if (table.code === 0) {
tableData.value = table.data.list
total.value = table.data.total
@@ -229,7 +210,6 @@ const openDrawer = () => {
type.value = 'create'
drawerFormVisible.value = true
}
</script>
<style></style>

View File

@@ -1,10 +1,7 @@
<template>
<div>
<router-view v-slot="{ Component }">
<transition
mode="out-in"
name="el-fade-in-linear"
>
<transition mode="out-in" name="el-fade-in-linear">
<keep-alive :include="routerStore.keepAliveRouters">
<component :is="Component" />
</keep-alive>

View File

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

View File

@@ -1,40 +1,58 @@
<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 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
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="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
v-if="!page.showForm"
:class="[page.showReadme ? 'slide-out-right' : 'slide-in-fwd-top']"
>
<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">1.您需有用一定的VUE和GOLANG基础</p>
<p class="text-gray-600 dark:text-gray-300 mb-2">2.请您确认是否已经阅读过<a
<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">
2.请您确认是否已经阅读过<a
class="text-blue-600 font-bold"
href="https://www.gin-vue-admin.com"
target="_blank"
>官方文档</a> <a
>官方文档</a
>
<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"
>初始化视频</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
type="primary"
size="large"
@click="showNext"
>
<el-button type="primary" size="large" @click="showNext">
我已确认
</el-button>
</p>
@@ -45,14 +63,12 @@
:class="[page.showForm ? 'slide-in-left' : 'slide-out-right']"
class="w-96"
>
<el-form
ref="formRef"
:model="form"
label-width="100px"
size="large"
>
<el-form ref="formRef" :model="form" label-width="100px" size="large">
<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 label="数据库类型">
<el-select
@@ -61,79 +77,35 @@
class="w-full"
@change="changeDB"
>
<el-option
key="mysql"
label="mysql"
value="mysql"
/>
<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-option key="mysql" label="mysql" value="mysql" />
<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-form-item>
<el-form-item
v-if="form.dbType !== 'sqlite'"
label="host"
>
<el-input
v-model="form.host"
placeholder="请输入数据库链接"
/>
<el-form-item v-if="form.dbType !== 'sqlite'" label="host">
<el-input v-model="form.host" placeholder="请输入数据库链接" />
</el-form-item>
<el-form-item
v-if="form.dbType !== 'sqlite'"
label="port"
>
<el-input
v-model="form.port"
placeholder="请输入数据库端口"
/>
<el-form-item v-if="form.dbType !== 'sqlite'" label="port">
<el-input v-model="form.port" placeholder="请输入数据库端口" />
</el-form-item>
<el-form-item
v-if="form.dbType !== 'sqlite'"
label="userName"
>
<el-form-item v-if="form.dbType !== 'sqlite'" label="userName">
<el-input
v-model="form.userName"
placeholder="请输入数据库用户名"
/>
</el-form-item>
<el-form-item
v-if="form.dbType !== 'sqlite'"
label="password"
>
<el-form-item v-if="form.dbType !== 'sqlite'" label="password">
<el-input
v-model="form.password"
placeholder="请输入数据库密码(没有则为空)"
/>
</el-form-item>
<el-form-item label="dbName">
<el-input
v-model="form.dbName"
placeholder="请输入数据库名称"
/>
<el-input v-model="form.dbName" placeholder="请输入数据库名称" />
</el-form-item>
<el-form-item
v-if="form.dbType === 'sqlite'"
label="dbPath"
>
<el-form-item v-if="form.dbType === 'sqlite'" label="dbPath">
<el-input
v-model="form.dbPath"
placeholder="请输入sqlite数据库文件存放路径"
@@ -141,21 +113,16 @@
</el-form-item>
<el-form-item>
<div style="text-align: right">
<el-button
type="primary"
@click="onSubmit"
>立即初始化</el-button>
<el-button type="primary" @click="onSubmit">立即初始化</el-button>
</div>
</el-form-item>
</el-form>
</div>
</div>
<div class="hidden md:block w-1/2 h-full float-right bg-[#194bfb]"><img
class="h-full"
src="@/assets/login_right_banner.jpg"
alt="banner"
></div>
<div class="hidden md:block w-1/2 h-full float-right bg-[#194bfb]">
<img class="h-full" src="@/assets/login_right_banner.jpg" alt="banner" />
</div>
</div>
</template>
@@ -167,7 +134,7 @@ import { ElLoading, ElMessage } from 'element-plus'
import { useRouter } from 'vue-router'
defineOptions({
name: 'Init',
name: 'Init'
})
const router = useRouter()
@@ -281,7 +248,7 @@ const onSubmit = async() => {
if (form.adminPassword.length < 6) {
ElMessage({
type: 'error',
message: '密码长度不能小于6位',
message: '密码长度不能小于6位'
})
return
}
@@ -290,7 +257,7 @@ const onSubmit = async() => {
lock: true,
text: '正在初始化数据库,请稍候',
spinner: 'loading',
background: 'rgba(0, 0, 0, 0.7)',
background: 'rgba(0, 0, 0, 0.7)'
})
try {
const res = await initDB(form)
@@ -298,7 +265,7 @@ const onSubmit = async() => {
out.value = true
ElMessage({
type: 'success',
message: res.msg,
message: res.msg
})
router.push({ name: 'Login' })
}
@@ -310,15 +277,14 @@ const onSubmit = async() => {
</script>
<style lang="scss" scoped>
.slide-in-fwd-top {
-webkit-animation: slide-in-fwd-top 0.4s cubic-bezier(0.25, 0.46, 0.45, 0.94)
both;
-webkit-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;
-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 {

View File

@@ -9,7 +9,7 @@
v-if="!isCollapse"
class="flex items-center"
:style="{
height : sideHeight,
height: sideHeight
}"
>
<el-icon v-if="routerInfo.meta.icon">
@@ -36,7 +36,7 @@ const appStore = useAppStore()
const { config } = storeToRefs(appStore)
defineOptions({
name: 'AsyncSubmenu',
name: 'AsyncSubmenu'
})
defineProps({
@@ -45,17 +45,16 @@ defineOptions({
return null
},
type: Object
},
}
})
const isCollapse = inject('isCollapse', {
default: false,
default: false
})
const sideHeight = computed(() => {
return config.value.layout_side_item_height + 'px'
})
</script>
<style lang="scss">

View File

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

View File

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

View File

@@ -1,6 +1,9 @@
<template>
<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
:default-active="routerStore.topActive"
mode="horizontal"
@@ -23,7 +26,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="isCollapse ? '' : ' px-2'"
:style="{
width: layoutSideWidth + 'px',
width: layoutSideWidth + 'px'
}"
>
<el-scrollbar>
@@ -60,79 +63,79 @@
</div>
</template>
<script setup>
import AsideComponent from "@/view/layout/aside/asideComponent/index.vue";
import { ref, provide, watchEffect, computed } from "vue";
import { useRoute, useRouter } from "vue-router";
import { useRouterStore } from "@/pinia/modules/router";
import { useAppStore } from "@/pinia";
import { storeToRefs } from "pinia";
const appStore = useAppStore();
const { device, config } = storeToRefs(appStore);
import AsideComponent from '@/view/layout/aside/asideComponent/index.vue'
import { ref, provide, watchEffect, computed } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { useRouterStore } from '@/pinia/modules/router'
import { useAppStore } from '@/pinia'
import { storeToRefs } from 'pinia'
const appStore = useAppStore()
const { device, config } = storeToRefs(appStore)
defineOptions({
name: "GvaAside",
});
name: 'GvaAside'
})
defineProps({
mode: {
type: String,
default: "normal",
},
default: 'normal'
}
})
const route = useRoute();
const router = useRouter();
const routerStore = useRouterStore();
const isCollapse = ref(false);
const active = ref("");
const route = useRoute()
const router = useRouter()
const routerStore = useRouterStore()
const isCollapse = ref(false)
const active = ref('')
const layoutSideWidth = computed(() => {
if (!isCollapse.value) {
return config.value.layout_side_width;
return config.value.layout_side_width
} else {
return config.value.layout_side_collapsed_width;
return config.value.layout_side_collapsed_width
}
});
})
watchEffect(() => {
active.value = route.meta.activeName || route.name;
});
active.value = route.meta.activeName || route.name
})
watchEffect(() => {
if (device.value === "mobile") {
isCollapse.value = true;
if (device.value === 'mobile') {
isCollapse.value = true
} else {
isCollapse.value = false;
isCollapse.value = false
}
});
})
provide("isCollapse", isCollapse);
provide('isCollapse', isCollapse)
const selectMenuItem = (index, _, ele, top) => {
const query = {};
const params = {};
const query = {}
const params = {}
routerStore.routeMap[index]?.parameters &&
routerStore.routeMap[index]?.parameters.forEach((item) => {
if (item.type === "query") {
query[item.key] = item.value;
if (item.type === 'query') {
query[item.key] = item.value
} else {
params[item.key] = item.value;
params[item.key] = item.value
}
});
if (index === route.name) return;
if (index.indexOf("http://") > -1 || index.indexOf("https://") > -1) {
window.open(index);
})
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 });
router.push({ name: index, query, params })
return
}
if (!routerStore.setLeftMenu(index)) {
router.push({ name: index, query, params });
router.push({ name: index, query, params })
}
}
}
};
const toggleCollapse = () => {
isCollapse.value = !isCollapse.value;
};
isCollapse.value = !isCollapse.value
}
</script>

View File

@@ -1,5 +1,7 @@
<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
:default-active="active"
mode="horizontal"
@@ -20,63 +22,61 @@
</template>
<script setup>
import AsideComponent from "@/view/layout/aside/asideComponent/index.vue";
import { ref, provide, watchEffect } from "vue";
import { useRoute, useRouter } from "vue-router";
import { useRouterStore } from "@/pinia/modules/router";
import { useAppStore } from "@/pinia";
import { storeToRefs } from "pinia";
const appStore = useAppStore();
const { device } = storeToRefs(appStore);
import AsideComponent from '@/view/layout/aside/asideComponent/index.vue'
import { ref, provide, watchEffect } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { useRouterStore } from '@/pinia/modules/router'
import { useAppStore } from '@/pinia'
import { storeToRefs } from 'pinia'
const appStore = useAppStore()
const { device } = storeToRefs(appStore)
defineOptions({
name: "GvaAside",
});
const route = useRoute();
const router = useRouter();
const routerStore = useRouterStore();
const isCollapse = ref(false);
const active = ref("");
name: 'GvaAside'
})
const route = useRoute()
const router = useRouter()
const routerStore = useRouterStore()
const isCollapse = ref(false)
const active = ref('')
watchEffect(() => {
active.value = route.meta.activeName || route.name;
});
active.value = route.meta.activeName || route.name
})
watchEffect(() => {
if (device.value === "mobile") {
isCollapse.value = true;
if (device.value === 'mobile') {
isCollapse.value = true
} else {
isCollapse.value = false;
isCollapse.value = false
}
});
})
provide("isCollapse", isCollapse);
provide('isCollapse', isCollapse)
const selectMenuItem = (index) => {
const query = {};
const params = {};
const query = {}
const params = {}
routerStore.routeMap[index]?.parameters &&
routerStore.routeMap[index]?.parameters.forEach((item) => {
if (item.type === "query") {
query[item.key] = item.value;
if (item.type === 'query') {
query[item.key] = item.value
} else {
params[item.key] = item.value;
params[item.key] = item.value
}
});
if (index === route.name) return;
if (index.indexOf("http://") > -1 || index.indexOf("https://") > -1) {
window.open(index);
})
if (index === route.name) return
if (index.indexOf('http://') > -1 || index.indexOf('https://') > -1) {
window.open(index)
} else {
router.push({ name: index, query, params });
router.push({ name: index, query, params })
}
}
};
</script>
<style lang="scss" scoped>
.el-menu--horizontal.el-menu,
.el-menu--horizontal > .el-menu-item.is-active {
border-bottom: none !important;
}
.el-menu-item.is-active {

View File

@@ -1,15 +1,24 @@
<template>
<div>
<normal-mode v-if="config.side_mode === 'normal' || (device === 'mobile' && config.side_mode == 'head' ) || (device === 'mobile' && config.side_mode == 'combination' )" />
<normal-mode
v-if="
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"/>
<combination-mode
v-if="config.side_mode === 'combination' && device !== 'mobile'"
:mode="mode"
/>
</div>
</template>
<script setup>
import NormalMode from "./normalMode.vue"
import HeadMode from "./headMode.vue"
import CombinationMode from "./combinationMode.vue"
import NormalMode from './normalMode.vue'
import HeadMode from './headMode.vue'
import CombinationMode from './combinationMode.vue'
defineProps({
mode: {
@@ -18,8 +27,8 @@ defineProps({
}
})
import { storeToRefs } from "pinia";
import { useAppStore } from "@/pinia";
const appStore = useAppStore();
const { config, device } = storeToRefs(appStore);
import { storeToRefs } from 'pinia'
import { useAppStore } from '@/pinia'
const appStore = useAppStore()
const { config, device } = storeToRefs(appStore)
</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="isCollapse ? '' : ' px-2'"
:style="{
width: layoutSideWidth + 'px',
width: layoutSideWidth + 'px'
}"
>
<el-scrollbar>
@@ -40,66 +40,66 @@
</template>
<script setup>
import AsideComponent from "@/view/layout/aside/asideComponent/index.vue";
import { ref, provide, watchEffect, computed } from "vue";
import { useRoute, useRouter } from "vue-router";
import { useRouterStore } from "@/pinia/modules/router";
import { useAppStore } from "@/pinia";
import { storeToRefs } from "pinia";
const appStore = useAppStore();
const { device, config } = storeToRefs(appStore);
import AsideComponent from '@/view/layout/aside/asideComponent/index.vue'
import { ref, provide, watchEffect, computed } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { useRouterStore } from '@/pinia/modules/router'
import { useAppStore } from '@/pinia'
import { storeToRefs } from 'pinia'
const appStore = useAppStore()
const { device, config } = storeToRefs(appStore)
defineOptions({
name: "GvaAside",
});
const route = useRoute();
const router = useRouter();
const routerStore = useRouterStore();
const isCollapse = ref(false);
const active = ref("");
name: 'GvaAside'
})
const route = useRoute()
const router = useRouter()
const routerStore = useRouterStore()
const isCollapse = ref(false)
const active = ref('')
const layoutSideWidth = computed(() => {
if (!isCollapse.value) {
return config.value.layout_side_width;
return config.value.layout_side_width
} else {
return config.value.layout_side_collapsed_width;
return config.value.layout_side_collapsed_width
}
});
})
watchEffect(() => {
active.value = route.meta.activeName || route.name;
});
active.value = route.meta.activeName || route.name
})
watchEffect(() => {
if (device.value === "mobile") {
isCollapse.value = true;
if (device.value === 'mobile') {
isCollapse.value = true
} else {
isCollapse.value = false;
isCollapse.value = false
}
});
})
provide("isCollapse", isCollapse);
provide('isCollapse', isCollapse)
const selectMenuItem = (index) => {
const query = {};
const params = {};
const query = {}
const params = {}
routerStore.routeMap[index]?.parameters &&
routerStore.routeMap[index]?.parameters.forEach((item) => {
if (item.type === "query") {
query[item.key] = item.value;
if (item.type === 'query') {
query[item.key] = item.value
} else {
params[item.key] = item.value;
params[item.key] = item.value
}
});
if (index === route.name) return;
if (index.indexOf("http://") > -1 || index.indexOf("https://") > -1) {
window.open(index);
})
if (index === route.name) return
if (index.indexOf('http://') > -1 || index.indexOf('https://') > -1) {
window.open(index)
} else {
router.push({ name: index, query, params });
router.push({ name: index, query, params })
}
}
};
const toggleCollapse = () => {
isCollapse.value = !isCollapse.value;
};
isCollapse.value = !isCollapse.value
}
</script>
<style lang="scss"></style>

View File

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

View File

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

View File

@@ -12,8 +12,17 @@
/>
<gva-header />
<div class="flex flex-row w-full gva-container pt-16 box-border h-full">
<gva-aside v-if="config.side_mode === 'normal' || (device === 'mobile' && config.side_mode == 'head' ) || (device === 'mobile' && config.side_mode == 'combination' )" />
<gva-aside v-if="config.side_mode === 'combination' && device !== 'mobile'" mode="normal"/>
<gva-aside
v-if="
config.side_mode === 'normal' ||
(device === 'mobile' && config.side_mode == 'head') ||
(device === 'mobile' && config.side_mode == 'combination')
"
/>
<gva-aside
v-if="config.side_mode === 'combination' && device !== 'mobile'"
mode="normal"
/>
<div class="flex-1 p-2 w-0 h-full">
<gva-tabs v-if="config.showTabs" />
<div
@@ -40,67 +49,66 @@
</template>
<script setup>
import GvaAside from "@/view/layout/aside/index.vue";
import GvaHeader from "@/view/layout/header/index.vue";
import useResponsive from "@/hooks/responsive";
import GvaTabs from "./tabs/index.vue";
import BottomInfo from "@/components/bottomInfo/bottomInfo.vue";
import { emitter } from "@/utils/bus.js";
import { ref, onMounted, nextTick, reactive, watchEffect } from "vue";
import { useRouter, useRoute } from "vue-router";
import { useRouterStore } from "@/pinia/modules/router";
import { useUserStore } from "@/pinia/modules/user";
import { useAppStore } from "@/pinia";
import { storeToRefs } from "pinia";
const appStore = useAppStore();
const { config, theme, device } = storeToRefs(appStore);
import GvaAside from '@/view/layout/aside/index.vue'
import GvaHeader from '@/view/layout/header/index.vue'
import useResponsive from '@/hooks/responsive'
import GvaTabs from './tabs/index.vue'
import BottomInfo from '@/components/bottomInfo/bottomInfo.vue'
import { emitter } from '@/utils/bus.js'
import { ref, onMounted, nextTick, reactive, watchEffect } from 'vue'
import { useRouter, useRoute } from 'vue-router'
import { useRouterStore } from '@/pinia/modules/router'
import { useUserStore } from '@/pinia/modules/user'
import { useAppStore } from '@/pinia'
import { storeToRefs } from 'pinia'
const appStore = useAppStore()
const { config, theme, device } = storeToRefs(appStore)
defineOptions({
name: "GvaLayout",
});
name: 'GvaLayout'
})
useResponsive(true);
useResponsive(true)
const font = reactive({
color: "rgba(0, 0, 0, .15)",
});
color: 'rgba(0, 0, 0, .15)'
})
watchEffect(() => {
font.color =
theme.value === "dark" ? "rgba(255,255,255, .15)" : "rgba(0, 0, 0, .15)";
});
theme.value === 'dark' ? 'rgba(255,255,255, .15)' : 'rgba(0, 0, 0, .15)'
})
const router = useRouter();
const route = useRoute();
const routerStore = useRouterStore();
const router = useRouter()
const route = useRoute()
const routerStore = useRouterStore()
onMounted(() => {
// 挂载一些通用的事件
emitter.on("reload", reload);
emitter.on('reload', reload)
if (userStore.loadingInstance) {
userStore.loadingInstance.close();
userStore.loadingInstance.close()
}
});
})
const userStore = useUserStore();
const userStore = useUserStore()
const reloadFlag = ref(true);
let reloadTimer = null;
const reloadFlag = ref(true)
let reloadTimer = null
const reload = async () => {
if (reloadTimer) {
window.clearTimeout(reloadTimer);
window.clearTimeout(reloadTimer)
}
reloadTimer = window.setTimeout(async () => {
if (route.meta.keepAlive) {
reloadFlag.value = false;
await nextTick();
reloadFlag.value = true;
reloadFlag.value = false
await nextTick()
reloadFlag.value = true
} else {
const title = route.meta.title;
router.push({ name: "Reload", params: { title } });
const title = route.meta.title
router.push({ name: 'Reload', params: { title } })
}
}, 400)
}
}, 400);
};
</script>
<style lang="scss">
</style>
<style lang="scss"></style>

View File

@@ -1,13 +1,7 @@
<template>
<div @click="clickFull">
<div
v-if="isShow"
class="gvaIcon gvaIcon-fullscreen-expand"
/>
<div
v-else
class="gvaIcon gvaIcon-fullscreen-shrink"
/>
<div v-if="isShow" class="gvaIcon gvaIcon-fullscreen-expand" />
<div v-else class="gvaIcon gvaIcon-fullscreen-shrink" />
</div>
</template>
@@ -16,7 +10,7 @@ import screenfull from 'screenfull' // 引入screenfull
import { onMounted, onUnmounted, ref } from 'vue'
defineOptions({
name: 'Screenfull',
name: 'Screenfull'
})
defineProps({
@@ -54,7 +48,6 @@ const isShow = ref(true)
const changeFullShow = () => {
isShow.value = !screenfull.isFullscreen
}
</script>
<style scoped lang="scss">

View File

@@ -6,10 +6,7 @@
@click="handleReload"
/>
<Screenfull class="search-icon" />
<div
class="gvaIcon gvaIcon-customer-service"
@click="toService"
/>
<div class="gvaIcon gvaIcon-customer-service" @click="toService" />
<el-switch
v-model="isDark"
:active-action-icon="Moon"
@@ -26,7 +23,7 @@ import { Sunny, Moon } from '@element-plus/icons-vue'
import { ref, watchEffect } from 'vue'
defineOptions({
name: 'BtnBox',
name: 'BtnBox'
})
const isDark = ref(localStorage.getItem('isDark') === 'true' || true)
@@ -54,10 +51,8 @@ const toService = () => {
const handleDarkSwitch = (e) => {
isDark.value = e
}
</script>
<style scoped lang="scss">
.search-component {
@apply inline-flex overflow-hidden text-center gap-5 mr-5 text-black dark:text-gray-100;
div {
@@ -100,5 +95,4 @@ const handleDarkSwitch = (e) =>{
transform: rotate(360deg);
}
}
</style>

View File

@@ -1,5 +1,11 @@
<template>
<el-drawer v-model="drawer" title="系统配置" direction="rtl" :size="width" :show-close="false">
<el-drawer
v-model="drawer"
title="系统配置"
direction="rtl"
:size="width"
:show-close="false"
>
<template #header>
<div class="flex justify-between items-center">
<span class="text-lg">系统配置</span>
@@ -127,50 +133,50 @@
</template>
<script setup>
import { useAppStore } from "@/pinia";
import { storeToRefs } from "pinia";
import { ref, computed } from "vue";
import { ElMessage } from "element-plus";
import {setSelfSetting} from "@/api/user";
const appStore = useAppStore();
const { config, device } = storeToRefs(appStore);
import { useAppStore } from '@/pinia'
import { storeToRefs } from 'pinia'
import { ref, computed } from 'vue'
import { ElMessage } from 'element-plus'
import { setSelfSetting } from '@/api/user'
const appStore = useAppStore()
const { config, device } = storeToRefs(appStore)
defineOptions({
name: "GvaSetting",
});
name: 'GvaSetting'
})
const width = computed(() => {
return device.value === "mobile" ? "100%" : "500px";
});
return device.value === 'mobile' ? '100%' : '500px'
})
const colors = [
"#EB2F96",
"#3b82f6",
"#2FEB54",
"#EBEB2F",
"#EB2F2F",
"#2FEBEB",
];
'#EB2F96',
'#3b82f6',
'#2FEB54',
'#EBEB2F',
'#EB2F2F',
'#2FEBEB'
]
const drawer = defineModel("drawer", {
const drawer = defineModel('drawer', {
default: true,
type: Boolean,
});
type: Boolean
})
const options = ["dark", "light", "auto"];
const options = ['dark', 'light', 'auto']
const sideModes = [
{
label : "正常模式",
value : "normal"
label: '正常模式',
value: 'normal'
},
{
label : "顶部菜单栏模式",
value: "head"
label: '顶部菜单栏模式',
value: 'head'
},
{
label : "组合模式",
value: "combination"
label: '组合模式',
value: 'combination'
}
];
]
const saveConfig = async () => {
/*const input = document.createElement("textarea");
@@ -188,9 +194,9 @@ const saveConfig = async () => {
ElMessage.success('保存成功')
drawer.value = false
}
};
}
const customColor = ref("");
const customColor = ref('')
</script>
<style lang="scss" scoped>

View File

@@ -25,11 +25,20 @@
<template #label>
<span
:tab="item"
:class="activeValue === getFmtString(item) ? 'text-active' : 'text-gray-600 dark:text-slate-400 '"
:class="
activeValue === getFmtString(item)
? 'text-active'
: 'text-gray-600 dark:text-slate-400 '
"
><i
:class="activeValue === getFmtString(item) ? 'text-active' : 'text-gray-600 dark:text-slate-400'"
:class="
activeValue === getFmtString(item)
? 'text-active'
: 'text-gray-600 dark:text-slate-400'
"
/>
{{ fmtTitle(item.meta.title,item) }}</span>
{{ fmtTitle(item.meta.title, item) }}</span
>
</template>
</el-tab-pane>
</el-tabs>
@@ -40,18 +49,10 @@
:style="{ left: left + 'px', top: top + 'px' }"
class="contextmenu"
>
<li @click="closeAll">
关闭所有
</li>
<li @click="closeLeft">
关闭左侧
</li>
<li @click="closeRight">
关闭右侧
</li>
<li @click="closeOther">
关闭其他
</li>
<li @click="closeAll">关闭所有</li>
<li @click="closeLeft">关闭左侧</li>
<li @click="closeRight">关闭右侧</li>
<li @click="closeOther">关闭其他</li>
</ul>
</div>
</template>
@@ -64,7 +65,7 @@ import { useUserStore } from '@/pinia/modules/user'
import { fmtTitle } from '@/utils/fmtRouterTitle'
defineOptions({
name: 'HistoryComponent',
name: 'HistoryComponent'
})
const route = useRoute()
@@ -85,12 +86,11 @@ const top = ref(0)
const isCollapse = ref(false)
const isMobile = ref(false)
const rightActive = ref('')
const defaultRouter = computed(() => userStore.userInfo.authority.defaultRouter)
const defaultRouter = computed(
() => userStore.userInfo.authority.defaultRouter
)
const openContextMenu = (e) => {
if (
historys.value.length === 1 &&
route.name === defaultRouter.value
) {
if (historys.value.length === 1 && route.name === defaultRouter.value) {
return false
}
let id = ''
@@ -112,11 +112,11 @@ const closeAll = () => {
{
name: defaultRouter.value,
meta: {
title: '首页',
title: '首页'
},
query: {},
params: {},
},
params: {}
}
]
router.push({ name: defaultRouter.value })
contextMenuVisible.value = false
@@ -171,7 +171,10 @@ const isSame = (route1, route2) => {
if (route1.name !== route2.name) {
return false
}
if (Object.keys(route1.query).length !== Object.keys(route2.query).length || Object.keys(route1.params).length !== Object.keys(route2.params).length) {
if (
Object.keys(route1.query).length !== Object.keys(route2.query).length ||
Object.keys(route1.params).length !== Object.keys(route2.params).length
) {
return false
}
for (const key in route1.query) {
@@ -208,13 +211,11 @@ const changeTab = (TabsPaneContext) => {
router.push({
name: tab.name,
query: tab.query,
params: tab.params,
params: tab.params
})
}
const removeTab = (tab) => {
const index = historys.value.findIndex(
(item) => getFmtString(item) === tab
)
const index = historys.value.findIndex((item) => getFmtString(item) === tab)
if (getFmtString(route) === tab) {
if (historys.value.length === 1) {
router.push({ name: defaultRouter.value })
@@ -223,13 +224,13 @@ const removeTab = (tab) => {
router.push({
name: historys.value[index + 1].name,
query: historys.value[index + 1].query,
params: historys.value[index + 1].params,
params: historys.value[index + 1].params
})
} else {
router.push({
name: historys.value[index - 1].name,
query: historys.value[index - 1].query,
params: historys.value[index - 1].params,
params: historys.value[index - 1].params
})
}
}
@@ -237,7 +238,9 @@ const removeTab = (tab) => {
historys.value.splice(index, 1)
}
watch(() => contextMenuVisible.value, () => {
watch(
() => contextMenuVisible.value,
() => {
if (contextMenuVisible.value) {
document.body.addEventListener('click', () => {
contextMenuVisible.value = false
@@ -247,9 +250,12 @@ watch(() => contextMenuVisible.value, () => {
contextMenuVisible.value = false
})
}
})
}
)
watch(() => route, (to) => {
watch(
() => route,
(to) => {
if (to.name === 'Login' || to.name === 'Reload') {
return
}
@@ -257,18 +263,24 @@ watch(() => route, (to) => {
setTab(to)
sessionStorage.setItem('historys', JSON.stringify(historys.value))
activeValue.value = window.sessionStorage.getItem('activeValue')
}, { deep: true })
},
{ deep: true }
)
watch(() => historys.value, () => {
watch(
() => historys.value,
() => {
sessionStorage.setItem('historys', JSON.stringify(historys.value))
historyMap.value = {}
historys.value.forEach((item) => {
historyMap.value[getFmtString(item)] = item
})
emitter.emit('setKeepAlive', historys.value)
}, {
},
{
deep: true
})
}
)
const initPage = () => {
// 全局监听 关闭当前页面函数
@@ -294,7 +306,11 @@ const initPage = () => {
activeValue.value = getFmtString(historys.value[index])
const currentUrl = window.location.href.split('?')[0]
const currentSearchParams = new URLSearchParams(data).toString()
window.history.replaceState({}, '', `${currentUrl}?${currentSearchParams}`)
window.history.replaceState(
{},
'',
`${currentUrl}?${currentSearchParams}`
)
sessionStorage.setItem('historys', JSON.stringify(historys.value))
})
@@ -319,11 +335,11 @@ const initPage = () => {
{
name: defaultRouter.value,
meta: {
title: '首页',
title: '首页'
},
query: {},
params: {},
},
params: {}
}
]
setTab(route)
historys.value =
@@ -362,7 +378,6 @@ const middleCloseTab = (e) => {
</script>
<style lang="scss" scoped>
.contextmenu {
@apply bg-white dark:bg-slate-900 w-28 m-0 py-2.5 px-0 border border-gray-200 text-sm shadow-md rounded absolute z-50 border-solid dark:border-slate-800;
}
@@ -406,8 +421,5 @@ $base-tag-item-height : 4rem;
border: 1px solid var(--el-color-primary);
}
}
}
</style>

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