初始化项目
This commit is contained in:
191
src/view/about/index.vue
Normal file
191
src/view/about/index.vue
Normal file
@@ -0,0 +1,191 @@
|
||||
<template>
|
||||
<div>
|
||||
<el-row :gutter="10">
|
||||
<el-col :span="12">
|
||||
<el-card>
|
||||
<template #header>
|
||||
<el-divider>gin-vue-admin</el-divider>
|
||||
</template>
|
||||
<div>
|
||||
<el-row>
|
||||
<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>
|
||||
<el-row :gutter="10">
|
||||
<el-col :span="8">
|
||||
<a href="https://github.com/flipped-aurora/gin-vue-admin">
|
||||
<img
|
||||
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">
|
||||
<a href="https://github.com/flipped-aurora/gin-vue-admin">
|
||||
<img
|
||||
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">
|
||||
<a href="https://github.com/flipped-aurora/gin-vue-admin">
|
||||
<img
|
||||
class="dom-center"
|
||||
src="https://img.shields.io/github/forks/flipped-aurora/gin-vue-admin.svg?label=Fork"
|
||||
alt=""
|
||||
>
|
||||
</a>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
</el-card>
|
||||
<el-card style="margin-top: 20px">
|
||||
<template #header>
|
||||
<div>flipped-aurora团队</div>
|
||||
</template>
|
||||
<div>
|
||||
<el-row>
|
||||
<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>
|
||||
<el-row style="margin-left: 40px" :gutter="20">
|
||||
<el-col v-for="(item, index) in members" :key="index" :span="8">
|
||||
<a :href="item.html_url">
|
||||
<img class="avatar-img" :src="item.avatar_url">
|
||||
<a class="author-name" style="">{{ item.login }}</a>
|
||||
</a>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-card>
|
||||
<template #header>
|
||||
<div>提交记录</div>
|
||||
</template>
|
||||
<div>
|
||||
<el-timeline>
|
||||
<el-timeline-item
|
||||
v-for="(item,index) in dataTimeline"
|
||||
:key="index"
|
||||
:timestamp="item.from"
|
||||
placement="top"
|
||||
>
|
||||
<el-card>
|
||||
<h4>{{ item.title }}</h4>
|
||||
<p>{{ item.message }}</p>
|
||||
</el-card>
|
||||
</el-timeline-item>
|
||||
</el-timeline>
|
||||
</div>
|
||||
<el-button
|
||||
class="load-more"
|
||||
type="primary"
|
||||
link
|
||||
@click="loadMore"
|
||||
>Load more</el-button>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'About',
|
||||
}
|
||||
</script>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
import { Commits, Members } from '@/api/github'
|
||||
import { formatTimeToStr } from '@/utils/date'
|
||||
const page = ref(0)
|
||||
|
||||
const loadMore = () => {
|
||||
page.value++
|
||||
loadCommits()
|
||||
}
|
||||
|
||||
const dataTimeline = ref([])
|
||||
const loadCommits = () => {
|
||||
Commits(page.value).then(({ data }) => {
|
||||
data.forEach((element) => {
|
||||
if (element.commit.message) {
|
||||
dataTimeline.value.push({
|
||||
from: formatTimeToStr(element.commit.author.date, 'yyyy-MM-dd'),
|
||||
title: element.commit.author.name,
|
||||
showDayAndMonth: true,
|
||||
message: element.commit.message,
|
||||
})
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
const members = ref([])
|
||||
const loadMembers = () => {
|
||||
Members().then(({ data }) => {
|
||||
members.value = data
|
||||
members.value.sort()
|
||||
})
|
||||
}
|
||||
|
||||
loadCommits()
|
||||
loadMembers()
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.load-more {
|
||||
margin-left: 120px;
|
||||
}
|
||||
|
||||
.avatar-img {
|
||||
float: left;
|
||||
height: 40px;
|
||||
width: 40px;
|
||||
border-radius: 50%;
|
||||
-webkit-border-radius: 50%;
|
||||
-moz-border-radius: 50%;
|
||||
margin-top: 15px;
|
||||
}
|
||||
|
||||
.org-img {
|
||||
height: 150px;
|
||||
width: 150px;
|
||||
}
|
||||
|
||||
.author-name {
|
||||
float: left;
|
||||
line-height: 65px !important;
|
||||
margin-left: 10px;
|
||||
color: darkblue;
|
||||
line-height: 100px;
|
||||
font-family: "Lucida Sans", "Lucida Sans Regular", "Lucida Grande",
|
||||
"Lucida Sans Unicode", Geneva, Verdana, sans-serif;
|
||||
}
|
||||
|
||||
.dom-center {
|
||||
margin-left: 50%;
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
</style>
|
129
src/view/dashboard/dashboardCharts/echartsLine.vue
Normal file
129
src/view/dashboard/dashboardCharts/echartsLine.vue
Normal file
@@ -0,0 +1,129 @@
|
||||
<template>
|
||||
<div class="dashboard-line-box">
|
||||
<div class="dashboard-line-title">
|
||||
访问趋势
|
||||
</div>
|
||||
<div
|
||||
ref="echart"
|
||||
class="dashboard-line"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
<script setup>
|
||||
import * as echarts from 'echarts'
|
||||
import { nextTick, onMounted, onUnmounted, ref, shallowRef } from 'vue'
|
||||
// import 'echarts/theme/macarons'
|
||||
|
||||
var dataAxis = []
|
||||
for (var i = 1; i < 13; i++) {
|
||||
dataAxis.push(`${i}月`)
|
||||
}
|
||||
var data = [
|
||||
220,
|
||||
182,
|
||||
191,
|
||||
234,
|
||||
290,
|
||||
330,
|
||||
310,
|
||||
123,
|
||||
442,
|
||||
321,
|
||||
90,
|
||||
149,
|
||||
]
|
||||
var yMax = 500
|
||||
var dataShadow = []
|
||||
|
||||
// eslint-disable-next-line no-redeclare
|
||||
for (var i = 0; i < data.length; i++) {
|
||||
dataShadow.push(yMax)
|
||||
}
|
||||
|
||||
const chart = shallowRef(null)
|
||||
const echart = ref(null)
|
||||
const initChart = () => {
|
||||
chart.value = echarts.init(echart.value /* 'macarons' */)
|
||||
setOptions()
|
||||
}
|
||||
const setOptions = () => {
|
||||
chart.value.setOption({
|
||||
grid: {
|
||||
left: '40',
|
||||
right: '20',
|
||||
top: '40',
|
||||
bottom: '20',
|
||||
},
|
||||
xAxis: {
|
||||
data: dataAxis,
|
||||
axisTick: {
|
||||
show: false,
|
||||
},
|
||||
axisLine: {
|
||||
show: false,
|
||||
},
|
||||
z: 10,
|
||||
},
|
||||
yAxis: {
|
||||
axisLine: {
|
||||
show: false,
|
||||
},
|
||||
axisTick: {
|
||||
show: false,
|
||||
},
|
||||
axisLabel: {
|
||||
textStyle: {
|
||||
color: '#999',
|
||||
},
|
||||
},
|
||||
},
|
||||
dataZoom: [
|
||||
{
|
||||
type: 'inside',
|
||||
},
|
||||
],
|
||||
series: [
|
||||
{
|
||||
type: 'bar',
|
||||
barWidth: '40%',
|
||||
itemStyle: {
|
||||
borderRadius: [5, 5, 0, 0],
|
||||
color: '#188df0',
|
||||
},
|
||||
emphasis: {
|
||||
itemStyle: {
|
||||
color: '#188df0',
|
||||
},
|
||||
},
|
||||
data: data,
|
||||
},
|
||||
],
|
||||
})
|
||||
}
|
||||
|
||||
onMounted(async() => {
|
||||
await nextTick()
|
||||
initChart()
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
if (!chart.value) {
|
||||
return
|
||||
}
|
||||
chart.value.dispose()
|
||||
chart.value = null
|
||||
})
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.dashboard-line-box {
|
||||
.dashboard-line {
|
||||
background-color: #fff;
|
||||
height: 360px;
|
||||
width: 100%;
|
||||
}
|
||||
.dashboard-line-title {
|
||||
font-weight: 600;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
}
|
||||
</style>
|
112
src/view/dashboard/dashboardTable/dashboardTable.vue
Normal file
112
src/view/dashboard/dashboardTable/dashboardTable.vue
Normal file
@@ -0,0 +1,112 @@
|
||||
<template>
|
||||
<div class="commit-table">
|
||||
<div class="commit-table-title">
|
||||
更新日志
|
||||
</div>
|
||||
<div class="log">
|
||||
<div v-for="(item,key) in dataTimeline" :key="key" class="log-item">
|
||||
<div class="flex-1 flex key-box">
|
||||
<span class="key" :class="key<3&&'top'">{{ key+1 }}</span>
|
||||
</div>
|
||||
<div class="flex-5 flex message">{{ item.message }}</div>
|
||||
<div class="flex-3 flex form">{{ item.from }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'DashboardTable',
|
||||
}
|
||||
</script>
|
||||
<script setup>
|
||||
import { Commits } from '@/api/github'
|
||||
import { formatTimeToStr } from '@/utils/date.js'
|
||||
import { ref } from 'vue'
|
||||
|
||||
const loading = ref(true)
|
||||
const dataTimeline = ref([])
|
||||
|
||||
const loadCommits = () => {
|
||||
Commits(0).then(({ data }) => {
|
||||
loading.value = false
|
||||
data.forEach((element, index) => {
|
||||
if (element.commit.message && index < 10) {
|
||||
dataTimeline.value.push({
|
||||
from: formatTimeToStr(element.commit.author.date, 'yyyy-MM-dd'),
|
||||
title: element.commit.author.name,
|
||||
showDayAndMonth: true,
|
||||
message: element.commit.message,
|
||||
})
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
loadCommits()
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.commit-table{
|
||||
background-color: #fff;
|
||||
height: 400px;
|
||||
&-title{
|
||||
font-weight: 600;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
.log{
|
||||
&-item{
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-top: 14px;
|
||||
.key-box{
|
||||
justify-content: center;
|
||||
}
|
||||
.key{
|
||||
&.top{
|
||||
background: #314659;
|
||||
color: #FFFFFF;;
|
||||
}
|
||||
display: inline-flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border-radius: 50%;
|
||||
background: #F0F2F5;
|
||||
text-align: center;
|
||||
color:rgba($color: #000000, $alpha: 0.65)
|
||||
}
|
||||
.message{
|
||||
color: rgba(0, 0, 0, 0.65);
|
||||
}
|
||||
.form{
|
||||
color: rgba(0, 0, 0, 0.65);
|
||||
margin-left: 12px;
|
||||
}
|
||||
.flex{
|
||||
line-height: 20px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.flex-1{
|
||||
flex:1;
|
||||
}
|
||||
.flex-2{
|
||||
flex:2;
|
||||
}
|
||||
.flex-3{
|
||||
flex:3;
|
||||
}
|
||||
.flex-4{
|
||||
flex:4;
|
||||
}
|
||||
.flex-5{
|
||||
flex:5;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
329
src/view/dashboard/index.vue
Normal file
329
src/view/dashboard/index.vue
Normal file
@@ -0,0 +1,329 @@
|
||||
<template>
|
||||
<div class="page">
|
||||
<div class="gva-card-box">
|
||||
<div class="gva-card gva-top-card">
|
||||
<div class="gva-top-card-left">
|
||||
<div class="gva-top-card-left-title">早安,管理员,请开始一天的工作吧</div>
|
||||
<div class="gva-top-card-left-dot">{{ weatherInfo }}</div>
|
||||
<div class="gva-top-card-left-rows">
|
||||
<el-row>
|
||||
<el-col :span="8" :xs="24" :sm="8">
|
||||
<div class="flex-center">
|
||||
<el-icon class="dashboard-icon">
|
||||
<sort />
|
||||
</el-icon>
|
||||
今日流量 (1231231)
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="8" :xs="24" :sm="8">
|
||||
<div class="flex-center">
|
||||
<el-icon class="dashboard-icon">
|
||||
<avatar />
|
||||
</el-icon>
|
||||
总用户数 (24001)
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="8" :xs="24" :sm="8">
|
||||
<div class="flex-center">
|
||||
<el-icon class="dashboard-icon">
|
||||
<comment />
|
||||
</el-icon>
|
||||
好评率 (99%)
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
<div>
|
||||
<div class="gva-top-card-left-item">
|
||||
使用教学:
|
||||
<a
|
||||
style="color:#409EFF"
|
||||
target="view_window"
|
||||
href="https://www.bilibili.com/video/BV1Rg411u7xH/"
|
||||
>https://www.bilibili.com/video/BV1Rg411u7xH</a>
|
||||
</div>
|
||||
<div class="gva-top-card-left-item">
|
||||
插件仓库:
|
||||
<a
|
||||
style="color:#409EFF"
|
||||
target="view_window"
|
||||
href="https://plugin.gin-vue-admin.com/#/layout/home"
|
||||
>https://plugin.gin-vue-admin.com</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<img src="@/assets/dashboard.png" class="gva-top-card-right" alt>
|
||||
</div>
|
||||
</div>
|
||||
<div class="gva-card-box">
|
||||
<el-card class="gva-card quick-entrance">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<span>快捷入口</span>
|
||||
</div>
|
||||
</template>
|
||||
<el-row :gutter="20">
|
||||
<el-col
|
||||
v-for="(card, key) in toolCards"
|
||||
:key="key"
|
||||
:span="4"
|
||||
:xs="8"
|
||||
class="quick-entrance-items"
|
||||
@click="toTarget(card.name)"
|
||||
>
|
||||
<div class="quick-entrance-item">
|
||||
<div class="quick-entrance-item-icon" :style="{ backgroundColor: card.bg }">
|
||||
<el-icon>
|
||||
<component :is="card.icon" :style="{ color: card.color }" />
|
||||
</el-icon>
|
||||
</div>
|
||||
<p>{{ card.label }}</p>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-card>
|
||||
<!-- <div class="quick-entrance-title"></div> -->
|
||||
</div>
|
||||
<div class="gva-card-box">
|
||||
<div class="gva-card">
|
||||
<div class="card-header">
|
||||
<span>数据统计</span>
|
||||
</div>
|
||||
<div class="echart-box">
|
||||
<el-row :gutter="20">
|
||||
<el-col :xs="24" :sm="18">
|
||||
<echarts-line />
|
||||
</el-col>
|
||||
<el-col :xs="24" :sm="6">
|
||||
<dashboard-table />
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import EchartsLine from '@/view/dashboard/dashboardCharts/echartsLine.vue'
|
||||
import DashboardTable from '@/view/dashboard/dashboardTable/dashboardTable.vue'
|
||||
import { ref } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { useWeatherInfo } from '@/view/dashboard/weather.js'
|
||||
|
||||
const weatherInfo = useWeatherInfo()
|
||||
|
||||
const toolCards = ref([
|
||||
{
|
||||
label: '用户管理',
|
||||
icon: 'monitor',
|
||||
name: 'user',
|
||||
color: '#ff9c6e',
|
||||
bg: 'rgba(255, 156, 110,.3)'
|
||||
},
|
||||
{
|
||||
label: '角色管理',
|
||||
icon: 'setting',
|
||||
name: 'authority',
|
||||
color: '#69c0ff',
|
||||
bg: 'rgba(105, 192, 255,.3)'
|
||||
},
|
||||
{
|
||||
label: '菜单管理',
|
||||
icon: 'menu',
|
||||
name: 'menu',
|
||||
color: '#b37feb',
|
||||
bg: 'rgba(179, 127, 235,.3)'
|
||||
},
|
||||
{
|
||||
label: '代码生成器',
|
||||
icon: 'cpu',
|
||||
name: 'autoCode',
|
||||
color: '#ffd666',
|
||||
bg: 'rgba(255, 214, 102,.3)'
|
||||
},
|
||||
{
|
||||
label: '表单生成器',
|
||||
icon: 'document-checked',
|
||||
name: 'formCreate',
|
||||
color: '#ff85c0',
|
||||
bg: 'rgba(255, 133, 192,.3)'
|
||||
},
|
||||
{
|
||||
label: '关于我们',
|
||||
icon: 'user',
|
||||
name: 'about',
|
||||
color: '#5cdbd3',
|
||||
bg: 'rgba(92, 219, 211,.3)'
|
||||
}
|
||||
])
|
||||
|
||||
const router = useRouter()
|
||||
|
||||
const toTarget = (name) => {
|
||||
router.push({ name })
|
||||
}
|
||||
|
||||
</script>
|
||||
<script>
|
||||
export default {
|
||||
name: 'Dashboard'
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@mixin flex-center {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
.page {
|
||||
background: #f0f2f5;
|
||||
padding: 0;
|
||||
.gva-card-box{
|
||||
padding: 12px 16px;
|
||||
&+.gva-card-box{
|
||||
padding-top: 0px;
|
||||
}
|
||||
}
|
||||
.gva-card {
|
||||
box-sizing: border-box;
|
||||
background-color: #fff;
|
||||
border-radius: 2px;
|
||||
height: auto;
|
||||
padding: 26px 30px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 0 7px 1px rgba(0, 0, 0, 0.03);
|
||||
}
|
||||
.gva-top-card {
|
||||
height: 260px;
|
||||
@include flex-center;
|
||||
justify-content: space-between;
|
||||
color: #777;
|
||||
&-left {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
&-title {
|
||||
font-size: 22px;
|
||||
color: #343844;
|
||||
}
|
||||
&-dot {
|
||||
font-size: 16px;
|
||||
color: #6B7687;
|
||||
margin-top: 24px;
|
||||
}
|
||||
&-rows {
|
||||
// margin-top: 15px;
|
||||
margin-top: 18px;
|
||||
color: #6B7687;
|
||||
width: 600px;
|
||||
align-items: center;
|
||||
}
|
||||
&-item{
|
||||
+.gva-top-card-left-item{
|
||||
margin-top: 24px;
|
||||
}
|
||||
margin-top: 14px;
|
||||
}
|
||||
}
|
||||
&-right {
|
||||
height: 600px;
|
||||
width: 600px;
|
||||
margin-top: 28px;
|
||||
}
|
||||
}
|
||||
::v-deep(.el-card__header){
|
||||
padding:0;
|
||||
border-bottom: none;
|
||||
}
|
||||
.card-header{
|
||||
padding-bottom: 20px;
|
||||
border-bottom: 1px solid #e8e8e8;
|
||||
}
|
||||
.quick-entrance-title {
|
||||
height: 30px;
|
||||
font-size: 22px;
|
||||
color: #333;
|
||||
width: 100%;
|
||||
border-bottom: 1px solid #eee;
|
||||
}
|
||||
.quick-entrance-items {
|
||||
@include flex-center;
|
||||
justify-content: center;
|
||||
text-align: center;
|
||||
color: #333;
|
||||
.quick-entrance-item {
|
||||
padding: 16px 28px;
|
||||
margin-top: -16px;
|
||||
margin-bottom: -16px;
|
||||
border-radius: 4px;
|
||||
transition: all 0.2s;
|
||||
&:hover{
|
||||
box-shadow: 0px 0px 7px 0px rgba(217, 217, 217, 0.55);
|
||||
}
|
||||
cursor: pointer;
|
||||
height: auto;
|
||||
text-align: center;
|
||||
// align-items: center;
|
||||
&-icon {
|
||||
width: 50px;
|
||||
height: 50px !important;
|
||||
border-radius: 8px;
|
||||
@include flex-center;
|
||||
justify-content: center;
|
||||
margin: 0 auto;
|
||||
i {
|
||||
font-size: 24px;
|
||||
}
|
||||
}
|
||||
p {
|
||||
margin-top: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
.echart-box{
|
||||
padding: 14px;
|
||||
}
|
||||
}
|
||||
.dashboard-icon {
|
||||
font-size: 20px;
|
||||
color: rgb(85, 160, 248);
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
margin-right: 10px;
|
||||
@include flex-center;
|
||||
}
|
||||
.flex-center {
|
||||
@include flex-center;
|
||||
}
|
||||
|
||||
//小屏幕不显示右侧,将登录框居中
|
||||
@media (max-width: 750px) {
|
||||
.gva-card {
|
||||
padding: 20px 10px !important;
|
||||
.gva-top-card {
|
||||
height: auto;
|
||||
&-left {
|
||||
&-title {
|
||||
font-size: 20px !important;
|
||||
}
|
||||
&-rows {
|
||||
margin-top: 15px;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
&-right {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
.gva-middle-card {
|
||||
&-item {
|
||||
line-height: 20px;
|
||||
}
|
||||
}
|
||||
.dashboard-icon {
|
||||
font-size: 18px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
31
src/view/dashboard/weather.js
Normal file
31
src/view/dashboard/weather.js
Normal file
@@ -0,0 +1,31 @@
|
||||
|
||||
import axios from 'axios'
|
||||
import { ref } from 'vue'
|
||||
|
||||
const weatherInfo = ref('今日晴,0℃ - 10℃,天气寒冷,注意添加衣物。')
|
||||
const amapKey = '8e8baa8a7317586c29ec694895de6e0a'
|
||||
|
||||
export const useWeatherInfo = () => {
|
||||
ip()
|
||||
return weatherInfo
|
||||
}
|
||||
|
||||
export const ip = async() => {
|
||||
// key换成你自己的 https://console.amap.com/dev/index
|
||||
if (amapKey === '') {
|
||||
return false
|
||||
}
|
||||
const res = await axios.get('https://restapi.amap.com/v3/ip?key=' + amapKey)
|
||||
if (res.data.adcode) {
|
||||
getWeather(res.data.adcode)
|
||||
}
|
||||
}
|
||||
|
||||
const getWeather = async(code) => {
|
||||
const response = await axios.get('https://restapi.amap.com/v3/weather/weatherInfo?key=' + amapKey + '&extensions=base&city=' + code)
|
||||
if (response.data.status === '1') {
|
||||
const s = response.data.lives[0]
|
||||
weatherInfo.value = s.city + ' 天气:' + s.weather + ' 温度:' + s.temperature + '摄氏度 风向:' + s.winddirection + ' 风力:' + s.windpower + '级 空气湿度:' + s.humidity
|
||||
}
|
||||
}
|
||||
|
45
src/view/error/index.vue
Normal file
45
src/view/error/index.vue
Normal file
@@ -0,0 +1,45 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="big">
|
||||
<div class="inner">
|
||||
<img src="../../assets/notFound.png">
|
||||
<p>页面被神秘力量吸走了(如果您是开源版请联系我们修复)</p>
|
||||
<p style="font-size:18px;line-height:40px;">常见问题为当前此角色无当前路由,如果确定要使用本路由,请到角色管理进行分配</p>
|
||||
<p>↓</p>
|
||||
<img src="../../assets/qm.png" class="leftPic">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'Error'
|
||||
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.big{
|
||||
width: 100%;
|
||||
height: calc(100vh - 220px);
|
||||
background-color: rgb(244,244,244);
|
||||
position: relative;
|
||||
}
|
||||
.inner{
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
.inner p {
|
||||
text-align: center;
|
||||
font-size: 24px;
|
||||
}
|
||||
.inner .leftPic{
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
margin-left: 44%;
|
||||
margin-top: 20px;
|
||||
}
|
||||
</style>
|
14
src/view/error/reload.vue
Normal file
14
src/view/error/reload.vue
Normal file
@@ -0,0 +1,14 @@
|
||||
<template>
|
||||
<div />
|
||||
</template>
|
||||
<script>
|
||||
import { useRouter } from 'vue-router'
|
||||
export default {
|
||||
name: 'Reload'
|
||||
}
|
||||
</script>
|
||||
|
||||
<script setup>
|
||||
const router = useRouter()
|
||||
router.go(-1)
|
||||
</script>
|
266
src/view/example/breakpoint/breakpoint.vue
Normal file
266
src/view/example/breakpoint/breakpoint.vue
Normal file
@@ -0,0 +1,266 @@
|
||||
<template>
|
||||
<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">
|
||||
选择文件
|
||||
<input v-show="false" id="file" ref="FileInput" multiple="multiple" type="file" @change="choseFile">
|
||||
</div>
|
||||
</form>
|
||||
<el-button :disabled="limitFileSize" type="primary" size="small" class="uploadBtn" @click="getFile">上传文件</el-button>
|
||||
<div class="el-upload__tip">请上传不超过5MB的文件</div>
|
||||
<div class="list">
|
||||
<transition name="list" tag="p">
|
||||
<div v-if="file" class="list-item">
|
||||
<el-icon>
|
||||
<document />
|
||||
</el-icon>
|
||||
<span>{{ file.name }}</span>
|
||||
<span class="percentage">{{ percentage }}%</span>
|
||||
<el-progress :show-text="false" :text-inside="false" :stroke-width="2" :percentage="percentage" />
|
||||
</div>
|
||||
</transition>
|
||||
</div>
|
||||
<div class="tips">此版本为先行体验功能测试版,样式美化和性能优化正在进行中,上传切片文件和合成的完整文件分别再QMPlusserver目录的breakpointDir文件夹和fileDir文件夹</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import SparkMD5 from 'spark-md5'
|
||||
import {
|
||||
findFile,
|
||||
breakpointContinueFinish,
|
||||
removeChunk,
|
||||
breakpointContinue
|
||||
} from '@/api/breakpoint'
|
||||
import { ref, watch } from 'vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
|
||||
const file = ref(null)
|
||||
const fileMd5 = ref('')
|
||||
const formDataList = ref([])
|
||||
const waitUpLoad = ref([])
|
||||
const waitNum = ref(NaN)
|
||||
const limitFileSize = ref(false)
|
||||
const percentage = ref(0)
|
||||
const percentageFlage = ref(true)
|
||||
|
||||
// 选中文件的函数
|
||||
const choseFile = async(e) => {
|
||||
const fileR = new FileReader() // 创建一个reader用来读取文件流
|
||||
const fileInput = e.target.files[0] // 获取当前文件
|
||||
const maxSize = 5 * 1024 * 1024
|
||||
file.value = fileInput // file 丢全局方便后面用 可以改进为func传参形式
|
||||
percentage.value = 0
|
||||
if (file.value.size < maxSize) {
|
||||
fileR.readAsArrayBuffer(file.value) // 把文件读成ArrayBuffer 主要为了保持跟后端的流一致
|
||||
fileR.onload = async e => {
|
||||
// 读成arrayBuffer的回调 e 为方法自带参数 相当于 dom的e 流存在e.target.result 中
|
||||
const blob = e.target.result
|
||||
const spark = new SparkMD5.ArrayBuffer() // 创建md5制造工具 (md5用于检测文件一致性 这里不懂就打电话问我)
|
||||
spark.append(blob) // 文件流丢进工具
|
||||
fileMd5.value = spark.end() // 工具结束 产生一个a 总文件的md5
|
||||
const FileSliceCap = 1 * 1024 * 1024 // 分片字节数
|
||||
let start = 0 // 定义分片开始切的地方
|
||||
let end = 0 // 每片结束切的地方a
|
||||
let i = 0 // 第几片
|
||||
formDataList.value = [] // 分片存储的一个池子 丢全局
|
||||
while (end < file.value.size) {
|
||||
// 当结尾数字大于文件总size的时候 结束切片
|
||||
start = i * FileSliceCap // 计算每片开始位置
|
||||
end = (i + 1) * FileSliceCap // 计算每片结束位置
|
||||
var fileSlice = file.value.slice(start, end) // 开始切 file.slice 为 h5方法 对文件切片 参数为 起止字节数
|
||||
const formData = new window.FormData() // 创建FormData用于存储传给后端的信息
|
||||
formData.append('fileMd5', fileMd5.value) // 存储总文件的Md5 让后端知道自己是谁的切片
|
||||
formData.append('file', fileSlice) // 当前的切片
|
||||
formData.append('chunkNumber', i) // 当前是第几片
|
||||
formData.append('fileName', file.value.name) // 当前文件的文件名 用于后端文件切片的命名 formData.appen 为 formData对象添加参数的方法
|
||||
formDataList.value.push({ key: i, formData }) // 把当前切片信息 自己是第几片 存入我们方才准备好的池子
|
||||
i++
|
||||
}
|
||||
const params = {
|
||||
fileName: file.value.name,
|
||||
fileMd5: fileMd5.value,
|
||||
chunkTotal: formDataList.value.length
|
||||
}
|
||||
const res = await findFile(params)
|
||||
// 全部切完以后 发一个请求给后端 拉当前文件后台存储的切片信息 用于检测有多少上传成功的切片
|
||||
const finishList = res.data.file.ExaFileChunk // 上传成功的切片
|
||||
const IsFinish = res.data.file.IsFinish // 是否是同文件不同命 (文件md5相同 文件名不同 则默认是同一个文件但是不同文件名 此时后台数据库只需要拷贝一下数据库文件即可 不需要上传文件 即秒传功能)
|
||||
if (!IsFinish) {
|
||||
// 当是断点续传时候
|
||||
waitUpLoad.value = formDataList.value.filter(all => {
|
||||
return !(
|
||||
finishList &&
|
||||
finishList.some(fi => fi.FileChunkNumber === all.key)
|
||||
) // 找出需要上传的切片
|
||||
})
|
||||
} else {
|
||||
waitUpLoad.value = [] // 秒传则没有需要上传的切片
|
||||
ElMessage.success('文件已秒传')
|
||||
}
|
||||
waitNum.value = waitUpLoad.value.length // 记录长度用于百分比展示
|
||||
console.log(waitNum.value)
|
||||
}
|
||||
} else {
|
||||
limitFileSize.value = true
|
||||
ElMessage('请上传小于5M文件')
|
||||
}
|
||||
}
|
||||
|
||||
const getFile = () => {
|
||||
// 确定按钮
|
||||
if (file.value === null) {
|
||||
ElMessage('请先上传文件')
|
||||
return
|
||||
}
|
||||
if (percentage.value === 100) {
|
||||
percentageFlage.value = false
|
||||
}
|
||||
sliceFile() // 上传切片
|
||||
}
|
||||
|
||||
const sliceFile = () => {
|
||||
waitUpLoad.value &&
|
||||
waitUpLoad.value.forEach(item => {
|
||||
// 需要上传的切片
|
||||
item.formData.append('chunkTotal', formDataList.value.length) // 切片总数携带给后台 总有用的
|
||||
const fileR = new FileReader() // 功能同上
|
||||
const fileF = item.formData.get('file')
|
||||
fileR.readAsArrayBuffer(fileF)
|
||||
fileR.onload = e => {
|
||||
const spark = new SparkMD5.ArrayBuffer()
|
||||
spark.append(e.target.result)
|
||||
item.formData.append('chunkMd5', spark.end()) // 获取当前切片md5 后端用于验证切片完整性
|
||||
upLoadFileSlice(item)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
watch(() => waitNum.value, () => { percentage.value = Math.floor(((formDataList.value.length - waitNum.value) / formDataList.value.length) * 100) })
|
||||
|
||||
const upLoadFileSlice = async(item) => {
|
||||
// 切片上传
|
||||
const fileRe = await breakpointContinue(item.formData)
|
||||
if (fileRe.code !== 0) {
|
||||
return
|
||||
}
|
||||
waitNum.value-- // 百分数增加
|
||||
if (waitNum.value === 0) {
|
||||
// 切片传完以后 合成文件
|
||||
const params = {
|
||||
fileName: file.value.name,
|
||||
fileMd5: fileMd5.value
|
||||
}
|
||||
const res = await breakpointContinueFinish(params)
|
||||
if (res.code === 0) {
|
||||
// 合成文件过后 删除缓存切片
|
||||
const params = {
|
||||
fileName: file.value.name,
|
||||
fileMd5: fileMd5.value,
|
||||
filePath: res.data.filePath,
|
||||
}
|
||||
ElMessage.success('上传成功')
|
||||
await removeChunk(params)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const FileInput = ref(null)
|
||||
const inputChange = () => {
|
||||
FileInput.value.dispatchEvent(new MouseEvent('click'))
|
||||
}
|
||||
</script>
|
||||
|
||||
<script>
|
||||
|
||||
export default {
|
||||
name: 'BreakPoint'
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang='scss' scoped>
|
||||
h3 {
|
||||
margin: 40px 0 0;
|
||||
}
|
||||
ul {
|
||||
list-style-type: none;
|
||||
padding: 0;
|
||||
}
|
||||
li {
|
||||
display: inline-block;
|
||||
margin: 0 10px;
|
||||
}
|
||||
a {
|
||||
color: #42b983;
|
||||
}
|
||||
#fromCont{
|
||||
display: inline-block;
|
||||
}
|
||||
.fileUpload{
|
||||
padding: 3px 10px;
|
||||
font-size: 12px;
|
||||
height: 20px;
|
||||
line-height: 20px;
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
color: #000;
|
||||
border: 1px solid #c1c1c1;
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
display: inline-block;
|
||||
input{
|
||||
position: absolute;
|
||||
font-size: 100px;
|
||||
right: 0;
|
||||
top: 0;
|
||||
opacity: 0;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
.fileName{
|
||||
display: inline-block;
|
||||
vertical-align: top;
|
||||
margin: 6px 15px 0 15px;
|
||||
}
|
||||
.uploadBtn{
|
||||
position: relative;
|
||||
top: -10px;
|
||||
margin-left: 15px;
|
||||
}
|
||||
.tips{
|
||||
margin-top: 30px;
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
color: #606266;
|
||||
}
|
||||
.el-divider{
|
||||
margin: 0 0 30px 0;
|
||||
}
|
||||
|
||||
.list{
|
||||
margin-top:15px;
|
||||
}
|
||||
.list-item {
|
||||
display: block;
|
||||
margin-right: 10px;
|
||||
color: #606266;
|
||||
line-height: 25px;
|
||||
margin-bottom: 5px;
|
||||
width: 40%;
|
||||
.percentage{
|
||||
float: right;
|
||||
}
|
||||
}
|
||||
.list-enter-active, .list-leave-active {
|
||||
transition: all 1s;
|
||||
}
|
||||
.list-enter, .list-leave-to
|
||||
/* .list-leave-active for below version 2.1.8 */ {
|
||||
opacity: 0;
|
||||
transform: translateY(-30px);
|
||||
}
|
||||
</style>
|
182
src/view/example/customer/customer.vue
Normal file
182
src/view/example/customer/customer.vue
Normal file
@@ -0,0 +1,182 @@
|
||||
<template>
|
||||
<div>
|
||||
<warning-bar title="在资源权限中将此角色的资源权限清空 或者不包含创建者的角色 即可屏蔽此客户资源的显示" />
|
||||
<div class="gva-table-box">
|
||||
<div class="gva-btn-list">
|
||||
<el-button size="small" type="primary" icon="plus" @click="openDialog">新增</el-button>
|
||||
</div>
|
||||
<el-table
|
||||
ref="multipleTable"
|
||||
:data="tableData"
|
||||
style="width: 100%"
|
||||
tooltip-effect="dark"
|
||||
row-key="ID"
|
||||
>
|
||||
<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>
|
||||
</el-table-column>
|
||||
<el-table-column align="left" label="姓名" prop="customerName" width="120" />
|
||||
<el-table-column align="left" label="电话" prop="customerPhoneData" width="120" />
|
||||
<el-table-column align="left" label="接入人ID" prop="sysUserId" width="120" />
|
||||
<el-table-column align="left" label="按钮组" min-width="160">
|
||||
<template #default="scope">
|
||||
<el-button size="small" type="primary" link icon="edit" @click="updateCustomer(scope.row)">变更</el-button>
|
||||
<el-popover v-model="scope.row.visible" placement="top" width="160">
|
||||
<p>确定要删除吗?</p>
|
||||
<div style="text-align: right; margin-top: 8px;">
|
||||
<el-button size="small" type="primary" link @click="scope.row.visible = false">取消</el-button>
|
||||
<el-button type="primary" size="small" @click="deleteCustomer(scope.row)">确定</el-button>
|
||||
</div>
|
||||
<template #reference>
|
||||
<el-button type="primary" link icon="delete" size="small" @click="scope.row.visible = true">删除</el-button>
|
||||
</template>
|
||||
</el-popover>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<div class="gva-pagination">
|
||||
<el-pagination
|
||||
:current-page="page"
|
||||
:page-size="pageSize"
|
||||
:page-sizes="[10, 30, 50, 100]"
|
||||
:total="total"
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
@current-change="handleCurrentChange"
|
||||
@size-change="handleSizeChange"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<el-dialog v-model="dialogFormVisible" :before-close="closeDialog" title="客户">
|
||||
<el-form :inline="true" :model="form" label-width="80px">
|
||||
<el-form-item label="客户名">
|
||||
<el-input v-model="form.customerName" autocomplete="off" />
|
||||
</el-form-item>
|
||||
<el-form-item label="客户电话">
|
||||
<el-input v-model="form.customerPhoneData" autocomplete="off" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button size="small" @click="closeDialog">取 消</el-button>
|
||||
<el-button size="small" type="primary" @click="enterDialog">确 定</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {
|
||||
createExaCustomer,
|
||||
updateExaCustomer,
|
||||
deleteExaCustomer,
|
||||
getExaCustomer,
|
||||
getExaCustomerList
|
||||
} from '@/api/customer'
|
||||
import WarningBar from '@/components/warningBar/warningBar.vue'
|
||||
import { ref } from 'vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { formatDate } from '@/utils/format'
|
||||
|
||||
const form = ref({
|
||||
customerName: '',
|
||||
customerPhoneData: ''
|
||||
})
|
||||
|
||||
const page = ref(1)
|
||||
const total = ref(0)
|
||||
const pageSize = ref(10)
|
||||
const tableData = ref([])
|
||||
|
||||
// 分页
|
||||
const handleSizeChange = (val) => {
|
||||
pageSize.value = val
|
||||
getTableData()
|
||||
}
|
||||
|
||||
const handleCurrentChange = (val) => {
|
||||
page.value = val
|
||||
getTableData()
|
||||
}
|
||||
|
||||
// 查询
|
||||
const getTableData = async() => {
|
||||
const table = await getExaCustomerList({ page: page.value, pageSize: pageSize.value })
|
||||
if (table.code === 0) {
|
||||
tableData.value = table.data.list
|
||||
total.value = table.data.total
|
||||
page.value = table.data.page
|
||||
pageSize.value = table.data.pageSize
|
||||
}
|
||||
}
|
||||
|
||||
getTableData()
|
||||
|
||||
const dialogFormVisible = ref(false)
|
||||
const type = ref('')
|
||||
const updateCustomer = async(row) => {
|
||||
const res = await getExaCustomer({ ID: row.ID })
|
||||
type.value = 'update'
|
||||
if (res.code === 0) {
|
||||
form.value = res.data.customer
|
||||
dialogFormVisible.value = true
|
||||
}
|
||||
}
|
||||
const closeDialog = () => {
|
||||
dialogFormVisible.value = false
|
||||
form.value = {
|
||||
customerName: '',
|
||||
customerPhoneData: ''
|
||||
}
|
||||
}
|
||||
const deleteCustomer = async(row) => {
|
||||
row.visible = false
|
||||
const res = await deleteExaCustomer({ ID: row.ID })
|
||||
if (res.code === 0) {
|
||||
ElMessage({
|
||||
type: 'success',
|
||||
message: '删除成功'
|
||||
})
|
||||
if (tableData.value.length === 1 && page.value > 1) {
|
||||
page.value--
|
||||
}
|
||||
getTableData()
|
||||
}
|
||||
}
|
||||
const enterDialog = async() => {
|
||||
let res
|
||||
switch (type.value) {
|
||||
case 'create':
|
||||
res = await createExaCustomer(form.value)
|
||||
break
|
||||
case 'update':
|
||||
res = await updateExaCustomer(form.value)
|
||||
break
|
||||
default:
|
||||
res = await createExaCustomer(form.value)
|
||||
break
|
||||
}
|
||||
|
||||
if (res.code === 0) {
|
||||
closeDialog()
|
||||
getTableData()
|
||||
}
|
||||
}
|
||||
const openDialog = () => {
|
||||
type.value = 'create'
|
||||
dialogFormVisible.value = true
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<script>
|
||||
|
||||
export default {
|
||||
name: 'Customer'
|
||||
}
|
||||
</script>
|
||||
|
||||
<style></style>
|
21
src/view/example/index.vue
Normal file
21
src/view/example/index.vue
Normal file
@@ -0,0 +1,21 @@
|
||||
<template>
|
||||
<div>
|
||||
<router-view v-slot="{ Component }">
|
||||
<transition mode="out-in" name="el-fade-in-linear">
|
||||
<keep-alive :include="routerStore.keepAliveRouters">
|
||||
<component :is="Component" />
|
||||
</keep-alive>
|
||||
</transition>
|
||||
</router-view>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'Example'
|
||||
}
|
||||
</script>
|
||||
<script setup>
|
||||
import { useRouterStore } from '@/pinia/modules/router'
|
||||
const routerStore = useRouterStore()
|
||||
</script>
|
210
src/view/example/upload/upload.vue
Normal file
210
src/view/example/upload/upload.vue
Normal file
@@ -0,0 +1,210 @@
|
||||
<template>
|
||||
<div v-loading.fullscreen.lock="fullscreenLoading">
|
||||
<div class="gva-table-box">
|
||||
<warning-bar
|
||||
title="点击“文件名/备注”可以编辑文件名或者备注内容。"
|
||||
/>
|
||||
<div class="gva-btn-list">
|
||||
<upload-common
|
||||
v-model:imageCommon="imageCommon"
|
||||
class="upload-btn"
|
||||
@on-success="getTableData"
|
||||
/>
|
||||
<upload-image
|
||||
v-model:imageUrl="imageUrl"
|
||||
:file-size="512"
|
||||
:max-w-h="1080"
|
||||
class="upload-btn"
|
||||
@on-success="getTableData"
|
||||
/>
|
||||
|
||||
<el-form ref="searchForm" :inline="true" :model="search">
|
||||
<el-form-item label="">
|
||||
<el-input v-model="search.keyword" class="keyword" placeholder="请输入文件名或备注" />
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item>
|
||||
<el-button size="small" type="primary" icon="search" @click="getTableData">查询</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
</div>
|
||||
|
||||
<el-table :data="tableData">
|
||||
<el-table-column align="left" label="预览" width="100">
|
||||
<template #default="scope">
|
||||
<CustomPic pic-type="file" :pic-src="scope.row.url" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="left" label="日期" prop="UpdatedAt" width="180">
|
||||
<template #default="scope">
|
||||
<div>{{ formatDate(scope.row.UpdatedAt) }}</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="left" label="文件名/备注" prop="name" width="180">
|
||||
<template #default="scope">
|
||||
<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">
|
||||
<template #default="scope">
|
||||
<el-tag
|
||||
:type="scope.row.tag === 'jpg' ? 'primary' : 'success'"
|
||||
disable-transitions
|
||||
>{{ scope.row.tag }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="left" label="操作" width="160">
|
||||
<template #default="scope">
|
||||
<el-button size="small" icon="download" type="primary" link @click="downloadFile(scope.row)">下载</el-button>
|
||||
<el-button size="small" icon="delete" type="primary" link @click="deleteFileFunc(scope.row)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<div class="gva-pagination">
|
||||
<el-pagination
|
||||
:current-page="page"
|
||||
:page-size="pageSize"
|
||||
:page-sizes="[10, 30, 50, 100]"
|
||||
:style="{ float: 'right', padding: '20px' }"
|
||||
:total="total"
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
@current-change="handleCurrentChange"
|
||||
@size-change="handleSizeChange"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { getFileList, deleteFile, editFileName } from '@/api/fileUploadAndDownload'
|
||||
import { downloadImage } from '@/utils/downloadImg'
|
||||
import CustomPic from '@/components/customPic/index.vue'
|
||||
import UploadImage from '@/components/upload/image.vue'
|
||||
import UploadCommon from '@/components/upload/common.vue'
|
||||
import { formatDate } from '@/utils/format'
|
||||
import WarningBar from '@/components/warningBar/warningBar.vue'
|
||||
|
||||
import { ref } from 'vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
|
||||
const path = ref(import.meta.env.VITE_BASE_API)
|
||||
|
||||
const imageUrl = ref('')
|
||||
const imageCommon = ref('')
|
||||
|
||||
const page = ref(1)
|
||||
const total = ref(0)
|
||||
const pageSize = ref(10)
|
||||
const search = ref({})
|
||||
const tableData = ref([])
|
||||
|
||||
// 分页
|
||||
const handleSizeChange = (val) => {
|
||||
pageSize.value = val
|
||||
getTableData()
|
||||
}
|
||||
|
||||
const handleCurrentChange = (val) => {
|
||||
page.value = val
|
||||
getTableData()
|
||||
}
|
||||
|
||||
// 查询
|
||||
const getTableData = async() => {
|
||||
const table = await getFileList({ page: page.value, pageSize: pageSize.value, ...search.value })
|
||||
if (table.code === 0) {
|
||||
tableData.value = table.data.list
|
||||
total.value = table.data.total
|
||||
page.value = table.data.page
|
||||
pageSize.value = table.data.pageSize
|
||||
}
|
||||
}
|
||||
getTableData()
|
||||
|
||||
const deleteFileFunc = async(row) => {
|
||||
ElMessageBox.confirm('此操作将永久删除文件, 是否继续?', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
})
|
||||
.then(async() => {
|
||||
const res = await deleteFile(row)
|
||||
if (res.code === 0) {
|
||||
ElMessage({
|
||||
type: 'success',
|
||||
message: '删除成功!',
|
||||
})
|
||||
if (tableData.value.length === 1 && page.value > 1) {
|
||||
page.value--
|
||||
}
|
||||
getTableData()
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
ElMessage({
|
||||
type: 'info',
|
||||
message: '已取消删除',
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
const downloadFile = (row) => {
|
||||
if (row.url.indexOf('http://') > -1 || row.url.indexOf('https://') > -1) {
|
||||
downloadImage(row.url, row.name)
|
||||
} else {
|
||||
debugger
|
||||
downloadImage(path.value + '/' + row.url, row.name)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 编辑文件名或者备注
|
||||
* @param row
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
const editFileNameFunc = async(row) => {
|
||||
ElMessageBox.prompt('请输入文件名或者备注', '编辑', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
inputPattern: /\S/,
|
||||
inputErrorMessage: '不能为空',
|
||||
inputValue: row.name
|
||||
}).then(async({ value }) => {
|
||||
row.name = value
|
||||
// console.log(row)
|
||||
const res = await editFileName(row)
|
||||
if (res.code === 0) {
|
||||
ElMessage({
|
||||
type: 'success',
|
||||
message: '编辑成功!',
|
||||
})
|
||||
getTableData()
|
||||
}
|
||||
}).catch(() => {
|
||||
ElMessage({
|
||||
type: 'info',
|
||||
message: '取消修改'
|
||||
})
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<script>
|
||||
|
||||
export default {
|
||||
name: 'Upload',
|
||||
}
|
||||
</script>
|
||||
<style scoped>
|
||||
.name {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.upload-btn + .upload-btn {
|
||||
margin-left: 12px;
|
||||
}
|
||||
</style>
|
320
src/view/init/index.vue
Normal file
320
src/view/init/index.vue
Normal file
@@ -0,0 +1,320 @@
|
||||
<template>
|
||||
<div class="init_page">
|
||||
<div class="init_page_panel">
|
||||
<div v-if="hello < 2" id="hello" :class="[hello < 1 ? 'slide-in-fwd-top' : 'slide-out-right']" class="hello">
|
||||
<div>
|
||||
<div class="hello_title">GIN-VUE-ADMIN</div>
|
||||
<p class="in-two a-fadeinT">初始化须知</p>
|
||||
<p class="init_p">1.您需有用一定的VUE和GOLANG基础</p>
|
||||
<p class="init_p">2.请您确认是否已经阅读过官方文档</p>
|
||||
<p class="init_p">3.请您确认是否了解后续的配置流程</p>
|
||||
<p class="init_p">注:开发组不为文档中书写过的内容提供无偿服务</p>
|
||||
<p class="init_btn">
|
||||
<el-button type="primary" @click="goDoc">
|
||||
阅读文档
|
||||
</el-button>
|
||||
<el-button type="primary" @click="showNext">
|
||||
我已确认
|
||||
</el-button>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="hello > 0 " :class="[(hello > 0 && !out)? 'slide-in-left' : '' , out ? 'slide-out-right' : '']"
|
||||
class="form">
|
||||
<el-form ref="formRef" :model="form" label-width="100px">
|
||||
<el-form-item label="数据库类型">
|
||||
<el-select v-model="form.dbType" placeholder="请选择" @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-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="host">
|
||||
<el-input v-model="form.host" placeholder="请输入数据库链接" />
|
||||
</el-form-item>
|
||||
<el-form-item label="port">
|
||||
<el-input v-model="form.port" placeholder="请输入数据库端口" />
|
||||
</el-form-item>
|
||||
<el-form-item label="userName">
|
||||
<el-input v-model="form.userName" placeholder="请输入数据库用户名" />
|
||||
</el-form-item>
|
||||
<el-form-item 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-form-item>
|
||||
<el-form-item>
|
||||
<div style="text-align: right">
|
||||
<el-button type="primary" @click="onSubmit">立即初始化</el-button>
|
||||
</div>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'Init',
|
||||
}
|
||||
</script>
|
||||
|
||||
<script setup>
|
||||
// @ts-ignore
|
||||
import { initDB } from '@/api/initdb'
|
||||
import { reactive, ref } from 'vue'
|
||||
import { ElLoading, ElMessage } from 'element-plus'
|
||||
import { useRouter } from 'vue-router'
|
||||
|
||||
const router = useRouter()
|
||||
|
||||
const hello = ref(0)
|
||||
const showNext = () => {
|
||||
hello.value = hello.value + 1
|
||||
}
|
||||
|
||||
const goDoc = () => {
|
||||
window.open('https://www.gin-vue-admin.com/guide/start-quickly/env.html')
|
||||
}
|
||||
|
||||
const out = ref(false)
|
||||
|
||||
const form = reactive({
|
||||
dbType: 'mysql',
|
||||
host: '127.0.0.1',
|
||||
port: '3306',
|
||||
userName: 'root',
|
||||
password: '',
|
||||
dbName: 'gva',
|
||||
})
|
||||
const changeDB = (val) => {
|
||||
switch (val) {
|
||||
case 'mysql':
|
||||
Object.assign(form, {
|
||||
dbType: 'mysql',
|
||||
host: '127.0.0.1',
|
||||
port: '3306',
|
||||
userName: 'root',
|
||||
password: '',
|
||||
dbName: 'gva',
|
||||
})
|
||||
break
|
||||
case 'pgsql':
|
||||
Object.assign(form, {
|
||||
dbType: 'pgsql',
|
||||
host: '127.0.0.1',
|
||||
port: '5432',
|
||||
userName: 'postgres',
|
||||
password: '',
|
||||
dbName: 'gva',
|
||||
})
|
||||
break
|
||||
case 'oracle':
|
||||
Object.assign(form, {
|
||||
dbType: 'oracle',
|
||||
host: '127.0.0.1',
|
||||
port: '1521',
|
||||
userName: 'oracle',
|
||||
password: '',
|
||||
dbName: 'gva',
|
||||
})
|
||||
break
|
||||
case 'mssql':
|
||||
Object.assign(form, {
|
||||
dbType: 'mssql',
|
||||
host: '127.0.0.1',
|
||||
port: '1433',
|
||||
userName: 'mssql',
|
||||
password: '',
|
||||
dbName: 'gva',
|
||||
})
|
||||
break
|
||||
default:
|
||||
Object.assign(form, {
|
||||
dbType: 'mysql',
|
||||
host: '127.0.0.1',
|
||||
port: '3306',
|
||||
userName: 'root',
|
||||
password: '',
|
||||
dbName: 'gva',
|
||||
})
|
||||
}
|
||||
}
|
||||
const onSubmit = async () => {
|
||||
const loading = ElLoading.service({
|
||||
lock: true,
|
||||
text: '正在初始化数据库,请稍候',
|
||||
spinner: 'loading',
|
||||
background: 'rgba(0, 0, 0, 0.7)',
|
||||
})
|
||||
try {
|
||||
const res = await initDB(form)
|
||||
if (res.code === 0) {
|
||||
out.value = true
|
||||
ElMessage({
|
||||
type: 'success',
|
||||
message: res.msg,
|
||||
})
|
||||
router.push({ name: 'Login' })
|
||||
}
|
||||
loading.close()
|
||||
} catch (err) {
|
||||
loading.close()
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.init_page {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
background-image: url('@/assets/login_background.jpg');
|
||||
background-size: cover;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: relative;
|
||||
.init_page_panel {
|
||||
position: absolute;
|
||||
top: 3vh;
|
||||
left: 2vw;
|
||||
width: 96vw;
|
||||
height: 94vh;
|
||||
background-color: rgba(255, 255, 255, 0.8);
|
||||
border-radius: 10px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-evenly;
|
||||
.hello {
|
||||
position: absolute;
|
||||
z-index: 2;
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
.hello_title {
|
||||
font-size: 32px;
|
||||
line-height: 98px;
|
||||
}
|
||||
.in-two {
|
||||
font-size: 22px;
|
||||
}
|
||||
.init_p {
|
||||
margin-top: 20px;
|
||||
color: #777777;
|
||||
}
|
||||
.init_btn {
|
||||
margin-top: 20px;
|
||||
}
|
||||
}
|
||||
.form {
|
||||
position: absolute;
|
||||
z-index: 3;
|
||||
margin-top: 60px;
|
||||
width: 600px;
|
||||
height: auto;
|
||||
padding: 20px;
|
||||
border-radius: 6px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.slide-in-fwd-top {
|
||||
-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;
|
||||
animation: slide-out-right 0.5s cubic-bezier(0.55, 0.085, 0.68, 0.53) both;
|
||||
}
|
||||
.slide-in-left {
|
||||
-webkit-animation: slide-in-left 0.5s cubic-bezier(0.25, 0.46, 0.45, 0.94)
|
||||
both;
|
||||
animation: slide-in-left 0.5s cubic-bezier(0.25, 0.46, 0.45, 0.94) both;
|
||||
}
|
||||
@-webkit-keyframes slide-in-fwd-top {
|
||||
0% {
|
||||
-webkit-transform: translateZ(-1400px) translateY(-800px);
|
||||
transform: translateZ(-1400px) translateY(-800px);
|
||||
opacity: 0;
|
||||
}
|
||||
100% {
|
||||
-webkit-transform: translateZ(0) translateY(0);
|
||||
transform: translateZ(0) translateY(0);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
@keyframes slide-in-fwd-top {
|
||||
0% {
|
||||
-webkit-transform: translateZ(-1400px) translateY(-800px);
|
||||
transform: translateZ(-1400px) translateY(-800px);
|
||||
opacity: 0;
|
||||
}
|
||||
100% {
|
||||
-webkit-transform: translateZ(0) translateY(0);
|
||||
transform: translateZ(0) translateY(0);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
@-webkit-keyframes slide-out-right {
|
||||
0% {
|
||||
-webkit-transform: translateX(0);
|
||||
transform: translateX(0);
|
||||
opacity: 1;
|
||||
}
|
||||
100% {
|
||||
-webkit-transform: translateX(1000px);
|
||||
transform: translateX(1000px);
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
@keyframes slide-out-right {
|
||||
0% {
|
||||
-webkit-transform: translateX(0);
|
||||
transform: translateX(0);
|
||||
opacity: 1;
|
||||
}
|
||||
100% {
|
||||
-webkit-transform: translateX(1000px);
|
||||
transform: translateX(1000px);
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
@-webkit-keyframes slide-in-left {
|
||||
0% {
|
||||
-webkit-transform: translateX(-1000px);
|
||||
transform: translateX(-1000px);
|
||||
opacity: 0;
|
||||
}
|
||||
100% {
|
||||
-webkit-transform: translateX(0);
|
||||
transform: translateX(0);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
@keyframes slide-in-left {
|
||||
0% {
|
||||
-webkit-transform: translateX(-1000px);
|
||||
transform: translateX(-1000px);
|
||||
opacity: 0;
|
||||
}
|
||||
100% {
|
||||
-webkit-transform: translateX(0);
|
||||
transform: translateX(0);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
@media (max-width: 750px) {
|
||||
.form {
|
||||
width: 94vw !important;
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
</style>
|
95
src/view/layout/aside/asideComponent/asyncSubmenu.vue
Normal file
95
src/view/layout/aside/asideComponent/asyncSubmenu.vue
Normal file
@@ -0,0 +1,95 @@
|
||||
<template>
|
||||
<el-sub-menu ref="subMenu" :index="routerInfo.name">
|
||||
|
||||
<template #title>
|
||||
<div v-if="!isCollapse" class="gva-subMenu">
|
||||
<el-icon v-if="routerInfo.meta.icon">
|
||||
<component :is="routerInfo.meta.icon" />
|
||||
</el-icon>
|
||||
<span>{{ routerInfo.meta.title }}</span>
|
||||
</div>
|
||||
<template v-else>
|
||||
<el-icon v-if="routerInfo.meta.icon">
|
||||
<component :is="routerInfo.meta.icon" />
|
||||
</el-icon>
|
||||
<span>{{ routerInfo.meta.title }}</span>
|
||||
</template>
|
||||
</template>
|
||||
<slot />
|
||||
</el-sub-menu>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'AsyncSubmenu',
|
||||
}
|
||||
</script>
|
||||
|
||||
<script setup>
|
||||
import { ref, watch } from 'vue'
|
||||
const props = defineProps({
|
||||
routerInfo: {
|
||||
default: function() {
|
||||
return null
|
||||
},
|
||||
type: Object
|
||||
},
|
||||
isCollapse: {
|
||||
default: function() {
|
||||
return false
|
||||
},
|
||||
type: Boolean
|
||||
},
|
||||
theme: {
|
||||
default: function() {
|
||||
return {}
|
||||
},
|
||||
type: Object
|
||||
}
|
||||
})
|
||||
|
||||
const activeBackground = ref(props.theme.activeBackground)
|
||||
const activeText = ref(props.theme.activeText)
|
||||
const normalText = ref(props.theme.normalText)
|
||||
// const hoverBackground = ref(props.theme.hoverBackground)
|
||||
// const hoverText = ref(props.theme.hoverText)
|
||||
|
||||
watch(() => props.theme, () => {
|
||||
activeBackground.value = props.theme.activeBackground
|
||||
activeText.value = props.theme.activeText
|
||||
normalText.value = props.theme.normalText
|
||||
// hoverBackground.value = props.theme.hoverBackground
|
||||
// hoverText.value = props.theme.hoverText
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.el-sub-menu{
|
||||
::v-deep(.el-sub-menu__title){
|
||||
padding: 6px;
|
||||
color: v-bind(normalText);
|
||||
}
|
||||
}
|
||||
|
||||
.is-active:not(.is-opened){
|
||||
::v-deep(.el-sub-menu__title) .gva-subMenu{
|
||||
flex:1;
|
||||
height: 100%;
|
||||
line-height: 44px;
|
||||
background: v-bind(activeBackground) !important;
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 0 2px 1px v-bind(activeBackground) !important;
|
||||
i{
|
||||
color: v-bind(activeText);
|
||||
}
|
||||
span{
|
||||
color: v-bind(activeText);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.gva-subMenu {
|
||||
padding-left: 4px;
|
||||
}
|
||||
</style>
|
59
src/view/layout/aside/asideComponent/index.vue
Normal file
59
src/view/layout/aside/asideComponent/index.vue
Normal file
@@ -0,0 +1,59 @@
|
||||
<template>
|
||||
<component
|
||||
:is="menuComponent"
|
||||
v-if="!routerInfo.hidden"
|
||||
:is-collapse="isCollapse"
|
||||
:theme="theme"
|
||||
:router-info="routerInfo"
|
||||
>
|
||||
<template v-if="routerInfo.children&&routerInfo.children.length">
|
||||
<AsideComponent
|
||||
v-for="item in routerInfo.children"
|
||||
:key="item.name"
|
||||
:is-collapse="false"
|
||||
:router-info="item"
|
||||
:theme="theme"
|
||||
/>
|
||||
</template>
|
||||
</component>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'AsideComponent',
|
||||
}
|
||||
</script>
|
||||
|
||||
<script setup>
|
||||
import MenuItem from './menuItem.vue'
|
||||
import AsyncSubmenu from './asyncSubmenu.vue'
|
||||
import { computed } from 'vue'
|
||||
const props = defineProps({
|
||||
routerInfo: {
|
||||
type: Object,
|
||||
default: () => null,
|
||||
},
|
||||
isCollapse: {
|
||||
default: function() {
|
||||
return false
|
||||
},
|
||||
type: Boolean
|
||||
},
|
||||
theme: {
|
||||
default: function() {
|
||||
return {}
|
||||
},
|
||||
type: Object
|
||||
}
|
||||
})
|
||||
|
||||
const menuComponent = computed(() => {
|
||||
if (props.routerInfo.children && props.routerInfo.children.filter(item => !item.hidden).length) {
|
||||
return AsyncSubmenu
|
||||
} else {
|
||||
return MenuItem
|
||||
}
|
||||
})
|
||||
|
||||
</script>
|
||||
|
121
src/view/layout/aside/asideComponent/menuItem.vue
Normal file
121
src/view/layout/aside/asideComponent/menuItem.vue
Normal file
@@ -0,0 +1,121 @@
|
||||
<template>
|
||||
<el-menu-item :index="routerInfo.name">
|
||||
<template v-if="isCollapse">
|
||||
<el-tooltip
|
||||
class="box-item"
|
||||
effect="light"
|
||||
:content="routerInfo.meta.title"
|
||||
placement="right"
|
||||
>
|
||||
<el-icon v-if="routerInfo.meta.icon">
|
||||
<component :is="routerInfo.meta.icon" />
|
||||
</el-icon>
|
||||
</el-tooltip>
|
||||
</template>
|
||||
<template v-else>
|
||||
<div class="gva-menu-item">
|
||||
<el-icon v-if="routerInfo.meta.icon">
|
||||
<component :is="routerInfo.meta.icon" />
|
||||
</el-icon>
|
||||
<span class="gva-menu-item-title">{{ routerInfo.meta.title }}</span>
|
||||
</div>
|
||||
</template>
|
||||
</el-menu-item>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'MenuItem',
|
||||
}
|
||||
</script>
|
||||
|
||||
<script setup>
|
||||
import { ref, watch } from 'vue'
|
||||
|
||||
const props = defineProps({
|
||||
routerInfo: {
|
||||
default: function() {
|
||||
return null
|
||||
},
|
||||
type: Object
|
||||
},
|
||||
isCollapse: {
|
||||
default: function() {
|
||||
return false
|
||||
},
|
||||
type: Boolean
|
||||
},
|
||||
theme: {
|
||||
default: function() {
|
||||
return {}
|
||||
},
|
||||
type: Object
|
||||
}
|
||||
})
|
||||
|
||||
const activeBackground = ref(props.theme.activeBackground)
|
||||
const activeText = ref(props.theme.activeText)
|
||||
const normalText = ref(props.theme.normalText)
|
||||
const hoverBackground = ref(props.theme.hoverBackground)
|
||||
const hoverText = ref(props.theme.hoverText)
|
||||
|
||||
watch(() => props.theme, () => {
|
||||
activeBackground.value = props.theme.activeBackground
|
||||
activeText.value = props.theme.activeText
|
||||
normalText.value = props.theme.normalText
|
||||
hoverBackground.value = props.theme.hoverBackground
|
||||
hoverText.value = props.theme.hoverText
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
.gva-menu-item{
|
||||
width: 100%;
|
||||
flex:1;
|
||||
height: 44px;
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
padding-left: 4px;
|
||||
|
||||
.gva-menu-item-title {
|
||||
flex:1;
|
||||
}
|
||||
}
|
||||
|
||||
.el-menu--collapse{
|
||||
.el-menu-item.is-active{
|
||||
color: v-bind(activeBackground);
|
||||
}
|
||||
}
|
||||
|
||||
.el-menu-item{
|
||||
color: v-bind(normalText);
|
||||
}
|
||||
|
||||
.el-menu-item.is-active{
|
||||
.gva-menu-item{
|
||||
background: v-bind(activeBackground) !important;
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 0 2px 1px v-bind(activeBackground) !important;
|
||||
i{
|
||||
color: v-bind(activeText);
|
||||
}
|
||||
span{
|
||||
color: v-bind(activeText);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.el-menu-item:hover{
|
||||
.gva-menu-item{
|
||||
background: v-bind(hoverBackground);
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 0 2px 1px v-bind(hoverBackground);
|
||||
color: v-bind(hoverText);
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
363
src/view/layout/aside/historyComponent/history.vue
Normal file
363
src/view/layout/aside/historyComponent/history.vue
Normal file
@@ -0,0 +1,363 @@
|
||||
<template>
|
||||
<div class="router-history">
|
||||
<el-tabs
|
||||
v-model="activeValue"
|
||||
:closable="!(historys.length === 1 && $route.name === defaultRouter)"
|
||||
type="card"
|
||||
@contextmenu.prevent="openContextMenu($event)"
|
||||
@tab-click="changeTab"
|
||||
@tab-remove="removeTab"
|
||||
>
|
||||
<el-tab-pane
|
||||
v-for="item in historys"
|
||||
:key="name(item)"
|
||||
:label="item.meta.title"
|
||||
:name="name(item)"
|
||||
:tab="item"
|
||||
class="gva-tab"
|
||||
>
|
||||
<template #label>
|
||||
<span
|
||||
:tab="item"
|
||||
:style="{
|
||||
color: activeValue === name(item) ? userStore.activeColor : '#333',
|
||||
}"
|
||||
><i
|
||||
class="dot"
|
||||
:style="{
|
||||
backgroundColor:
|
||||
activeValue === name(item) ? userStore.activeColor : '#ddd',
|
||||
}"
|
||||
/>
|
||||
{{ fmtTitle(item.meta.title,item) }}</span>
|
||||
</template>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
|
||||
<!--自定义右键菜单html代码-->
|
||||
<ul
|
||||
v-show="contextMenuVisible"
|
||||
: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>
|
||||
</ul>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'HistoryComponent',
|
||||
}
|
||||
</script>
|
||||
|
||||
<script setup>
|
||||
import { emitter } from '@/utils/bus.js'
|
||||
import { computed, onUnmounted, ref, watch } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import { useUserStore } from '@/pinia/modules/user'
|
||||
import { fmtTitle } from '@/utils/fmtRouterTitle'
|
||||
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
|
||||
const getFmtString = (item) => {
|
||||
return item.name + JSON.stringify(item.query) + JSON.stringify(item.params)
|
||||
}
|
||||
|
||||
const historys = ref([])
|
||||
const activeValue = ref('')
|
||||
const contextMenuVisible = ref(false)
|
||||
|
||||
const userStore = useUserStore()
|
||||
|
||||
const name = (item) => {
|
||||
return (
|
||||
item.name + JSON.stringify(item.query) + JSON.stringify(item.params)
|
||||
)
|
||||
}
|
||||
|
||||
const left = ref(0)
|
||||
const top = ref(0)
|
||||
const isCollapse = ref(false)
|
||||
const isMobile = ref(false)
|
||||
const rightActive = ref('')
|
||||
const defaultRouter = computed(() => userStore.userInfo.authority.defaultRouter)
|
||||
const openContextMenu = (e) => {
|
||||
if (
|
||||
historys.value.length === 1 &&
|
||||
route.name === defaultRouter.value
|
||||
) {
|
||||
return false
|
||||
}
|
||||
let id = ''
|
||||
if (e.srcElement.nodeName === 'SPAN') {
|
||||
id = e.srcElement.offsetParent.id
|
||||
} else {
|
||||
id = e.srcElement.id
|
||||
}
|
||||
if (id) {
|
||||
contextMenuVisible.value = true
|
||||
let width
|
||||
if (isCollapse.value) {
|
||||
width = 54
|
||||
} else {
|
||||
width = 220
|
||||
}
|
||||
if (isMobile.value) {
|
||||
width = 0
|
||||
}
|
||||
left.value = e.clientX - width
|
||||
top.value = e.clientY + 10
|
||||
rightActive.value = id.substring(4)
|
||||
}
|
||||
}
|
||||
const closeAll = () => {
|
||||
historys.value = [
|
||||
{
|
||||
name: defaultRouter.value,
|
||||
meta: {
|
||||
title: '首页',
|
||||
},
|
||||
query: {},
|
||||
params: {},
|
||||
},
|
||||
]
|
||||
router.push({ name: defaultRouter.value })
|
||||
contextMenuVisible.value = false
|
||||
sessionStorage.setItem('historys', JSON.stringify(historys.value))
|
||||
}
|
||||
const closeLeft = () => {
|
||||
let right
|
||||
const rightIndex = historys.value.findIndex((item) => {
|
||||
if (getFmtString(item) === rightActive.value) {
|
||||
right = item
|
||||
}
|
||||
return getFmtString(item) === rightActive.value
|
||||
})
|
||||
const activeIndex = historys.value.findIndex(
|
||||
(item) => getFmtString(item) === activeValue.value
|
||||
)
|
||||
historys.value.splice(0, rightIndex)
|
||||
if (rightIndex > activeIndex) {
|
||||
router.push(right)
|
||||
}
|
||||
sessionStorage.setItem('historys', JSON.stringify(historys.value))
|
||||
}
|
||||
const closeRight = () => {
|
||||
let right
|
||||
const leftIndex = historys.value.findIndex((item) => {
|
||||
if (getFmtString(item) === rightActive.value) {
|
||||
right = item
|
||||
}
|
||||
return getFmtString(item) === rightActive.value
|
||||
})
|
||||
const activeIndex = historys.value.findIndex(
|
||||
(item) => getFmtString(item) === activeValue.value
|
||||
)
|
||||
historys.value.splice(leftIndex + 1, historys.value.length)
|
||||
if (leftIndex < activeIndex) {
|
||||
router.push(right)
|
||||
}
|
||||
sessionStorage.setItem('historys', JSON.stringify(historys.value))
|
||||
}
|
||||
const closeOther = () => {
|
||||
let right
|
||||
historys.value = historys.value.filter((item) => {
|
||||
if (getFmtString(item) === rightActive.value) {
|
||||
right = item
|
||||
}
|
||||
return getFmtString(item) === rightActive.value
|
||||
})
|
||||
router.push(right)
|
||||
sessionStorage.setItem('historys', JSON.stringify(historys.value))
|
||||
}
|
||||
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) {
|
||||
return false
|
||||
}
|
||||
for (const key in route1.query) {
|
||||
if (route1.query[key] !== route2.query[key]) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
for (const key in route1.params) {
|
||||
if (route1.params[key] !== route2.params[key]) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
const setTab = (route) => {
|
||||
if (!historys.value.some((item) => isSame(item, route))) {
|
||||
const obj = {}
|
||||
obj.name = route.name
|
||||
obj.meta = { ...route.meta }
|
||||
delete obj.meta.matched
|
||||
obj.query = route.query
|
||||
obj.params = route.params
|
||||
historys.value.push(obj)
|
||||
}
|
||||
window.sessionStorage.setItem('activeValue', getFmtString(route))
|
||||
}
|
||||
|
||||
const historyMap = ref({})
|
||||
|
||||
const changeTab = (TabsPaneContext) => {
|
||||
const name = TabsPaneContext?.props?.name
|
||||
if (!name) return
|
||||
const tab = historyMap.value[name]
|
||||
router.push({
|
||||
name: tab.name,
|
||||
query: tab.query,
|
||||
params: tab.params,
|
||||
})
|
||||
}
|
||||
const removeTab = (tab) => {
|
||||
const index = historys.value.findIndex(
|
||||
(item) => getFmtString(item) === tab
|
||||
)
|
||||
if (getFmtString(route) === tab) {
|
||||
if (historys.value.length === 1) {
|
||||
router.push({ name: defaultRouter.value })
|
||||
} else {
|
||||
if (index < historys.value.length - 1) {
|
||||
router.push({
|
||||
name: historys.value[index + 1].name,
|
||||
query: historys.value[index + 1].query,
|
||||
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,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
historys.value.splice(index, 1)
|
||||
}
|
||||
|
||||
watch(() => contextMenuVisible.value, () => {
|
||||
if (contextMenuVisible.value) {
|
||||
document.body.addEventListener('click', () => {
|
||||
contextMenuVisible.value = false
|
||||
})
|
||||
} else {
|
||||
document.body.removeEventListener('click', () => {
|
||||
contextMenuVisible.value = false
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
watch(() => route, (to, now) => {
|
||||
if (to.name === 'Login' || to.name === 'Reload') {
|
||||
return
|
||||
}
|
||||
historys.value = historys.value.filter((item) => !item.meta.closeTab)
|
||||
setTab(to)
|
||||
sessionStorage.setItem('historys', JSON.stringify(historys.value))
|
||||
activeValue.value = window.sessionStorage.getItem('activeValue')
|
||||
}, { deep: true })
|
||||
|
||||
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 = () => {
|
||||
// 全局监听 关闭当前页面函数
|
||||
emitter.on('closeThisPage', () => {
|
||||
removeTab(name(route))
|
||||
})
|
||||
// 全局监听 关闭所有页面函数
|
||||
emitter.on('closeAllPage', () => {
|
||||
closeAll()
|
||||
})
|
||||
emitter.on('mobile', (data) => {
|
||||
isMobile.value = data
|
||||
})
|
||||
emitter.on('collapse', (data) => {
|
||||
isCollapse.value = data
|
||||
})
|
||||
const initHistorys = [
|
||||
{
|
||||
name: defaultRouter.value,
|
||||
meta: {
|
||||
title: '首页',
|
||||
},
|
||||
query: {},
|
||||
params: {},
|
||||
},
|
||||
]
|
||||
historys.value =
|
||||
JSON.parse(sessionStorage.getItem('historys')) || initHistorys
|
||||
if (!window.sessionStorage.getItem('activeValue')) {
|
||||
activeValue.value = getFmtString(route)
|
||||
} else {
|
||||
activeValue.value = window.sessionStorage.getItem('activeValue')
|
||||
}
|
||||
setTab(route)
|
||||
if (window.sessionStorage.getItem('needCloseAll') === 'true') {
|
||||
closeAll()
|
||||
window.sessionStorage.removeItem('needCloseAll')
|
||||
}
|
||||
}
|
||||
initPage()
|
||||
|
||||
onUnmounted(() => {
|
||||
emitter.off('collapse')
|
||||
emitter.off('mobile')
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.contextmenu {
|
||||
width: 100px;
|
||||
margin: 0;
|
||||
border: 1px solid #ccc;
|
||||
background: #fff;
|
||||
z-index: 3000;
|
||||
position: absolute;
|
||||
list-style-type: none;
|
||||
padding: 5px 0;
|
||||
border-radius: 4px;
|
||||
font-size: 14px;
|
||||
color: #333;
|
||||
box-shadow: 2px 2px 3px 0 rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
.el-tabs__item .el-icon-close {
|
||||
color: initial !important;
|
||||
}
|
||||
.el-tabs__item .dot {
|
||||
content: "";
|
||||
width: 9px;
|
||||
height: 9px;
|
||||
margin-right: 8px;
|
||||
display: inline-block;
|
||||
border-radius: 50%;
|
||||
transition: background-color 0.2s;
|
||||
}
|
||||
|
||||
.contextmenu li {
|
||||
margin: 0;
|
||||
padding: 7px 16px;
|
||||
}
|
||||
.contextmenu li:hover {
|
||||
background: #f2f2f2;
|
||||
cursor: pointer;
|
||||
}
|
||||
</style>
|
151
src/view/layout/aside/index.vue
Normal file
151
src/view/layout/aside/index.vue
Normal file
@@ -0,0 +1,151 @@
|
||||
<template>
|
||||
<div :style="{ background: userStore.sideMode }">
|
||||
<el-scrollbar style="height: calc(100vh - 60px)">
|
||||
<transition
|
||||
:duration="{ enter: 800, leave: 100 }"
|
||||
mode="out-in"
|
||||
name="el-fade-in-linear"
|
||||
>
|
||||
<el-menu
|
||||
:collapse="isCollapse"
|
||||
:collapse-transition="false"
|
||||
:default-active="active"
|
||||
:background-color="theme.background"
|
||||
:active-text-color="theme.active"
|
||||
class="el-menu-vertical"
|
||||
unique-opened
|
||||
@select="selectMenuItem"
|
||||
>
|
||||
<template v-for="item in routerStore.asyncRouters[0].children">
|
||||
<aside-component
|
||||
v-if="!item.hidden"
|
||||
:key="item.name"
|
||||
:is-collapse="isCollapse"
|
||||
:router-info="item"
|
||||
:theme="theme"
|
||||
/>
|
||||
</template>
|
||||
</el-menu>
|
||||
</transition>
|
||||
</el-scrollbar>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'Aside',
|
||||
}
|
||||
</script>
|
||||
|
||||
<script setup>
|
||||
import AsideComponent from '@/view/layout/aside/asideComponent/index.vue'
|
||||
import { emitter } from '@/utils/bus.js'
|
||||
import { ref, watch, onUnmounted } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import { useUserStore } from '@/pinia/modules/user'
|
||||
import { useRouterStore } from '@/pinia/modules/router'
|
||||
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
|
||||
const userStore = useUserStore()
|
||||
const routerStore = useRouterStore()
|
||||
|
||||
const theme = ref({})
|
||||
|
||||
const getTheme = () => {
|
||||
switch (userStore.sideMode) {
|
||||
case '#fff':
|
||||
theme.value = {
|
||||
background: '#fff',
|
||||
activeBackground: '#4D70FF',
|
||||
activeText: '#fff',
|
||||
normalText: '#333',
|
||||
hoverBackground: 'rgba(64, 158, 255, 0.08)',
|
||||
hoverText: '#333',
|
||||
}
|
||||
break
|
||||
case '#191a23':
|
||||
theme.value = {
|
||||
background: '#191a23',
|
||||
activeBackground: '#4D70FF',
|
||||
activeText: '#fff',
|
||||
normalText: '#fff',
|
||||
hoverBackground: 'rgba(64, 158, 255, 0.08)',
|
||||
hoverText: '#fff',
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
getTheme()
|
||||
|
||||
const active = ref('')
|
||||
watch(() => route, () => {
|
||||
active.value = route.meta.activeName || route.name
|
||||
}, { deep: true })
|
||||
|
||||
watch(() => userStore.sideMode, () => {
|
||||
getTheme()
|
||||
})
|
||||
|
||||
const isCollapse = ref(false)
|
||||
const initPage = () => {
|
||||
active.value = route.meta.activeName || route.name
|
||||
const screenWidth = document.body.clientWidth
|
||||
if (screenWidth < 1000) {
|
||||
isCollapse.value = !isCollapse.value
|
||||
}
|
||||
|
||||
emitter.on('collapse', (item) => {
|
||||
isCollapse.value = item
|
||||
})
|
||||
}
|
||||
|
||||
initPage()
|
||||
|
||||
onUnmounted(() => {
|
||||
emitter.off('collapse')
|
||||
})
|
||||
|
||||
const selectMenuItem = (index, _, ele, aaa) => {
|
||||
const query = {}
|
||||
const params = {}
|
||||
routerStore.routeMap[index]?.parameters &&
|
||||
routerStore.routeMap[index]?.parameters.forEach((item) => {
|
||||
if (item.type === 'query') {
|
||||
query[item.key] = item.value
|
||||
} else {
|
||||
params[item.key] = item.value
|
||||
}
|
||||
})
|
||||
if (index === route.name) return
|
||||
if (index.indexOf('http://') > -1 || index.indexOf('https://') > -1) {
|
||||
window.open(index)
|
||||
} else {
|
||||
router.push({ name: index, query, params })
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
|
||||
.el-sub-menu__title:hover,
|
||||
.el-menu-item:hover {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.el-scrollbar {
|
||||
.el-scrollbar__view {
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
.menu-info {
|
||||
.menu-contorl {
|
||||
line-height: 52px;
|
||||
font-size: 20px;
|
||||
display: table-cell;
|
||||
vertical-align: middle;
|
||||
}
|
||||
}
|
||||
</style>
|
41
src/view/layout/bottomInfo/bottomInfo.vue
Normal file
41
src/view/layout/bottomInfo/bottomInfo.vue
Normal file
@@ -0,0 +1,41 @@
|
||||
<template>
|
||||
<div class="bottom-info">
|
||||
<div>
|
||||
<span>Powered by</span>
|
||||
<span>
|
||||
<a href="https://github.com/flipped-aurora/gin-vue-admin">{{ $GIN_VUE_ADMIN.appName }}</a>
|
||||
</span>
|
||||
<el-divider direction="vertical" />
|
||||
<span>Copyright</span>
|
||||
<span>
|
||||
<a href="https://github.com/flipped-aurora">flipped-aurora团队</a>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
// 此文件内容为版权信息,如需改动,请联系wx:shouzi_1994购买授权 未授权状态只需保留此代码 不影响任何正常使用
|
||||
// 项目为apatch协议 请遵守版权协议内容
|
||||
export default {
|
||||
name: 'BottomInfo'
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.bottom-info {
|
||||
color: #888;
|
||||
height: 30px;
|
||||
line-height: 12px;
|
||||
a {
|
||||
color: #888;
|
||||
}
|
||||
div {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
span{
|
||||
margin: 0 3px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
258
src/view/layout/index.vue
Normal file
258
src/view/layout/index.vue
Normal file
@@ -0,0 +1,258 @@
|
||||
<template>
|
||||
<el-container class="layout-cont">
|
||||
<el-container :class="[isSider?'openside':'hideside',isMobile ? 'mobile': '']">
|
||||
<el-row :class="[isShadowBg?'shadowBg':'']" @click="changeShadow()" />
|
||||
<el-aside class="main-cont main-left gva-aside">
|
||||
<div class="tilte" :style="{background: backgroundColor}">
|
||||
<img alt class="logoimg" :src="$GIN_VUE_ADMIN.appLogo">
|
||||
<div v-if="isSider" class="tit-text" :style="{color:textColor}">{{ $GIN_VUE_ADMIN.appName }}</div>
|
||||
</div>
|
||||
<Aside class="aside" />
|
||||
</el-aside>
|
||||
<!-- 分块滑动功能 -->
|
||||
<el-main class="main-cont main-right">
|
||||
<transition :duration="{ enter: 800, leave: 100 }" mode="out-in" name="el-fade-in-linear">
|
||||
<div
|
||||
:style="{width: `calc(100% - ${isMobile?'0px':isCollapse?'54px':'220px'})`}"
|
||||
class="topfix"
|
||||
>
|
||||
<el-row>
|
||||
<el-col>
|
||||
<el-header class="header-cont">
|
||||
<el-row class="pd-0">
|
||||
<el-col :xs="2" :lg="1" :md="1" :sm="1" :xl="1" style="z-index:100">
|
||||
<div class="menu-total" @click="totalCollapse">
|
||||
<div v-if="isCollapse" class="gvaIcon gvaIcon-arrow-double-right" />
|
||||
<div v-else class="gvaIcon gvaIcon-arrow-double-left" />
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :xs="10" :lg="14" :md="14" :sm="9" :xl="14" :pull="1">
|
||||
<!-- 修改为手机端不显示顶部标签 -->
|
||||
<el-breadcrumb v-show="!isMobile" class="breadcrumb">
|
||||
<el-breadcrumb-item
|
||||
v-for="item in matched.slice(1,matched.length)"
|
||||
:key="item.path"
|
||||
>{{ fmtTitle(item.meta.title,route) }}</el-breadcrumb-item>
|
||||
</el-breadcrumb>
|
||||
</el-col>
|
||||
<el-col :xs="12" :lg="9" :md="9" :sm="14" :xl="9">
|
||||
<div class="right-box">
|
||||
<Search />
|
||||
<el-dropdown>
|
||||
<div class="dp-flex justify-content-center align-items height-full width-full">
|
||||
<span class="header-avatar" style="cursor: pointer">
|
||||
<CustomPic />
|
||||
<span v-show="!isMobile" style="margin-left: 5px">{{ userStore.userInfo.nickName }}</span>
|
||||
<el-icon>
|
||||
<arrow-down />
|
||||
</el-icon>
|
||||
</span>
|
||||
</div>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu class="dropdown-group">
|
||||
<el-dropdown-item>
|
||||
<span style="font-weight: 600;">
|
||||
当前角色:{{ 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)" :key="item.authorityId" @click="changeUserAuth(item.authorityId)">
|
||||
<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>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-header>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<!-- 当前面包屑用路由自动生成可根据需求修改 -->
|
||||
<!--
|
||||
:to="{ path: item.path }" 暂时注释不用-->
|
||||
<HistoryComponent ref="layoutHistoryComponent" />
|
||||
</div>
|
||||
</transition>
|
||||
<router-view
|
||||
v-if="reloadFlag"
|
||||
v-slot="{ Component }"
|
||||
v-loading="loadingFlag"
|
||||
element-loading-text="正在加载中"
|
||||
class="admin-box"
|
||||
>
|
||||
<div>
|
||||
<transition mode="out-in" name="el-fade-in-linear">
|
||||
<keep-alive :include="routerStore.keepAliveRouters">
|
||||
<component :is="Component" />
|
||||
</keep-alive>
|
||||
</transition>
|
||||
</div>
|
||||
</router-view>
|
||||
<BottomInfo />
|
||||
<setting />
|
||||
</el-main>
|
||||
</el-container>
|
||||
|
||||
</el-container>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'Layout',
|
||||
}
|
||||
</script>
|
||||
|
||||
<script setup>
|
||||
import Aside from '@/view/layout/aside/index.vue'
|
||||
import HistoryComponent from '@/view/layout/aside/historyComponent/history.vue'
|
||||
import Search from '@/view/layout/search/search.vue'
|
||||
import BottomInfo from '@/view/layout/bottomInfo/bottomInfo.vue'
|
||||
import CustomPic from '@/components/customPic/index.vue'
|
||||
import Setting from './setting/index.vue'
|
||||
import { setUserAuthority } from '@/api/user'
|
||||
import { emitter } from '@/utils/bus.js'
|
||||
import { computed, ref, onMounted, nextTick } from 'vue'
|
||||
import { useRouter, useRoute } from 'vue-router'
|
||||
import { useRouterStore } from '@/pinia/modules/router'
|
||||
import { fmtTitle } from '@/utils/fmtRouterTitle'
|
||||
import { useUserStore } from '@/pinia/modules/user'
|
||||
|
||||
const router = useRouter()
|
||||
const route = useRoute()
|
||||
const routerStore = useRouterStore()
|
||||
// 三种窗口适配
|
||||
const isCollapse = ref(false)
|
||||
const isSider = ref(true)
|
||||
const isMobile = ref(false)
|
||||
|
||||
const initPage = () => {
|
||||
const screenWidth = document.body.clientWidth
|
||||
if (screenWidth < 1000) {
|
||||
isMobile.value = true
|
||||
isSider.value = false
|
||||
isCollapse.value = true
|
||||
} else if (screenWidth >= 1000 && screenWidth < 1200) {
|
||||
isMobile.value = false
|
||||
isSider.value = false
|
||||
isCollapse.value = true
|
||||
} else {
|
||||
isMobile.value = false
|
||||
isSider.value = true
|
||||
isCollapse.value = false
|
||||
}
|
||||
}
|
||||
|
||||
initPage()
|
||||
|
||||
const loadingFlag = ref(false)
|
||||
onMounted(() => {
|
||||
// 挂载一些通用的事件
|
||||
emitter.emit('collapse', isCollapse.value)
|
||||
emitter.emit('mobile', isMobile.value)
|
||||
emitter.on('reload', reload)
|
||||
emitter.on('showLoading', () => {
|
||||
loadingFlag.value = true
|
||||
})
|
||||
emitter.on('closeLoading', () => {
|
||||
loadingFlag.value = false
|
||||
})
|
||||
window.onresize = () => {
|
||||
return (() => {
|
||||
initPage()
|
||||
emitter.emit('collapse', isCollapse.value)
|
||||
emitter.emit('mobile', isMobile.value)
|
||||
})()
|
||||
}
|
||||
if (userStore.loadingInstance) {
|
||||
userStore.loadingInstance.close()
|
||||
}
|
||||
})
|
||||
|
||||
const userStore = useUserStore()
|
||||
|
||||
const textColor = computed(() => {
|
||||
if (userStore.sideMode === 'dark') {
|
||||
return '#fff'
|
||||
} else if (userStore.sideMode === 'light') {
|
||||
return '#191a23'
|
||||
} else {
|
||||
return userStore.baseColor
|
||||
}
|
||||
})
|
||||
|
||||
const backgroundColor = computed(() => {
|
||||
if (userStore.sideMode === 'dark') {
|
||||
return '#191a23'
|
||||
} else if (userStore.sideMode === 'light') {
|
||||
return '#fff'
|
||||
} else {
|
||||
return userStore.sideMode
|
||||
}
|
||||
})
|
||||
|
||||
const matched = computed(() => route.meta.matched)
|
||||
|
||||
const changeUserAuth = async(id) => {
|
||||
const res = await setUserAuthority({
|
||||
authorityId: id
|
||||
})
|
||||
if (res.code === 0) {
|
||||
window.sessionStorage.setItem('needCloseAll', 'true')
|
||||
window.location.reload()
|
||||
}
|
||||
}
|
||||
|
||||
const reloadFlag = ref(true)
|
||||
let reloadTimer = null
|
||||
const reload = async() => {
|
||||
if (reloadTimer) {
|
||||
window.clearTimeout(reloadTimer)
|
||||
}
|
||||
reloadTimer = window.setTimeout(async() => {
|
||||
if (route.meta.keepAlive) {
|
||||
reloadFlag.value = false
|
||||
await nextTick()
|
||||
reloadFlag.value = true
|
||||
} else {
|
||||
const title = route.meta.title
|
||||
router.push({ name: 'Reload', params: { title }})
|
||||
}
|
||||
}, 400)
|
||||
}
|
||||
|
||||
const isShadowBg = ref(false)
|
||||
const totalCollapse = () => {
|
||||
isCollapse.value = !isCollapse.value
|
||||
isSider.value = !isCollapse.value
|
||||
isShadowBg.value = !isCollapse.value
|
||||
emitter.emit('collapse', isCollapse.value)
|
||||
}
|
||||
|
||||
const toPerson = () => {
|
||||
router.push({ name: 'person' })
|
||||
}
|
||||
const changeShadow = () => {
|
||||
isShadowBg.value = !isShadowBg.value
|
||||
isSider.value = !!isCollapse.value
|
||||
totalCollapse()
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
@import '@/style/mobile.scss';
|
||||
|
||||
.dark{
|
||||
background-color: #191a23 !important;
|
||||
color: #fff !important;
|
||||
}
|
||||
.light{
|
||||
background-color: #fff !important;
|
||||
color: #000 !important;
|
||||
}
|
||||
</style>
|
64
src/view/layout/screenfull/index.vue
Normal file
64
src/view/layout/screenfull/index.vue
Normal file
@@ -0,0 +1,64 @@
|
||||
<template>
|
||||
<div @click="clickFull">
|
||||
<div v-if="isShow" class="gvaIcon gvaIcon-fullscreen-expand" />
|
||||
<div v-else class="gvaIcon gvaIcon-fullscreen-shrink" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'Screenfull',
|
||||
}
|
||||
</script>
|
||||
|
||||
<script setup>
|
||||
import screenfull from 'screenfull' // 引入screenfull
|
||||
import { onMounted, onUnmounted, ref } from 'vue'
|
||||
defineProps({
|
||||
width: {
|
||||
type: Number,
|
||||
default: 22
|
||||
},
|
||||
height: {
|
||||
type: Number,
|
||||
default: 22
|
||||
},
|
||||
fill: {
|
||||
type: String,
|
||||
default: '#48576a'
|
||||
}
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
if (screenfull.isEnabled) {
|
||||
screenfull.on('change', changeFullShow)
|
||||
}
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
screenfull.off('change')
|
||||
})
|
||||
|
||||
const clickFull = () => {
|
||||
if (screenfull.isEnabled) {
|
||||
screenfull.toggle()
|
||||
}
|
||||
}
|
||||
|
||||
const isShow = ref(true)
|
||||
const changeFullShow = () => {
|
||||
isShow.value = !screenfull.isFullscreen
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.screenfull-svg {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
cursor: pointer;
|
||||
vertical-align: middle;
|
||||
margin-right: 32px;
|
||||
fill: rgba(0, 0, 0, 0.45);
|
||||
}
|
||||
</style>
|
129
src/view/layout/search/search.vue
Normal file
129
src/view/layout/search/search.vue
Normal file
@@ -0,0 +1,129 @@
|
||||
<template>
|
||||
<div class="search-component">
|
||||
<div v-if="show" class="transition-box" style="display: inline-block;">
|
||||
<el-select
|
||||
ref="searchInput"
|
||||
v-model="value"
|
||||
filterable
|
||||
placeholder="请选择"
|
||||
@blur="hiddenSearch"
|
||||
@change="changeRouter"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in routerStore.routerList"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
/>
|
||||
</el-select>
|
||||
</div>
|
||||
<div
|
||||
v-if="btnShow"
|
||||
class="user-box"
|
||||
>
|
||||
<div class="gvaIcon gvaIcon-refresh" :class="[reload ? 'reloading' : '']" @click="handleReload" />
|
||||
</div>
|
||||
<div
|
||||
v-if="btnShow"
|
||||
class="user-box"
|
||||
>
|
||||
<div class="gvaIcon gvaIcon-search" @click="showSearch" />
|
||||
</div>
|
||||
<div
|
||||
v-if="btnShow"
|
||||
class="user-box"
|
||||
>
|
||||
<Screenfull class="search-icon" :style="{cursor:'pointer'}" />
|
||||
</div>
|
||||
<div
|
||||
v-if="btnShow"
|
||||
class="user-box"
|
||||
>
|
||||
<div class="service gvaIcon-customer-service" @click="toService" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'BtnBox',
|
||||
}
|
||||
</script>
|
||||
|
||||
<script setup>
|
||||
import Screenfull from '@/view/layout/screenfull/index.vue'
|
||||
import { emitter } from '@/utils/bus.js'
|
||||
import { ref, nextTick } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { useRouterStore } from '@/pinia/modules/router'
|
||||
|
||||
const router = useRouter()
|
||||
|
||||
const routerStore = useRouterStore()
|
||||
|
||||
const value = ref('')
|
||||
const changeRouter = () => {
|
||||
router.push({ name: value.value })
|
||||
value.value = ''
|
||||
}
|
||||
|
||||
const show = ref(false)
|
||||
const btnShow = ref(true)
|
||||
const hiddenSearch = () => {
|
||||
show.value = false
|
||||
btnShow.value = true
|
||||
}
|
||||
|
||||
const searchInput = ref(null)
|
||||
const showSearch = async() => {
|
||||
btnShow.value = false
|
||||
show.value = true
|
||||
await nextTick()
|
||||
searchInput.value.focus()
|
||||
}
|
||||
|
||||
const reload = ref(false)
|
||||
const handleReload = () => {
|
||||
reload.value = true
|
||||
emitter.emit('reload')
|
||||
setTimeout(() => {
|
||||
reload.value = false
|
||||
}, 500)
|
||||
}
|
||||
const toService = () => {
|
||||
window.open('https://support.qq.com/product/371961')
|
||||
}
|
||||
|
||||
</script>
|
||||
<style scoped lang="scss">
|
||||
.reload{
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.reloading{
|
||||
animation:turn 0.5s linear infinite;
|
||||
}
|
||||
@keyframes turn {
|
||||
0%{-webkit-transform:rotate(0deg);}
|
||||
25%{-webkit-transform:rotate(90deg);}
|
||||
50%{-webkit-transform:rotate(180deg);}
|
||||
75%{-webkit-transform:rotate(270deg);}
|
||||
100%{-webkit-transform:rotate(360deg);}
|
||||
}
|
||||
|
||||
|
||||
.service {
|
||||
font-family: "gvaIcon" !important;
|
||||
font-size: 16px;
|
||||
font-style: normal;
|
||||
font-weight: 800;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
//小屏幕不显示
|
||||
@media (max-width: 750px) {
|
||||
.service {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
</style>
|
137
src/view/layout/setting/index.vue
Normal file
137
src/view/layout/setting/index.vue
Normal file
@@ -0,0 +1,137 @@
|
||||
<template>
|
||||
<div>
|
||||
<el-button type="primary" class="drawer-container" icon="setting" @click="showSettingDrawer" />
|
||||
<el-drawer
|
||||
v-model="drawer"
|
||||
title="系统配置"
|
||||
:direction="direction"
|
||||
:before-close="handleClose"
|
||||
>
|
||||
<div class="setting_body">
|
||||
<div class="setting_card">
|
||||
<div class="setting_content">
|
||||
<div class="theme-box">
|
||||
<div class="item" @click="changeMode('light')">
|
||||
<div class="item-top">
|
||||
<el-icon v-if="userStore.mode === 'light'" class="check">
|
||||
<check />
|
||||
</el-icon>
|
||||
<img src="https://gw.alipayobjects.com/zos/antfincdn/NQ%24zoisaD2/jpRkZQMyYRryryPNtyIC.svg">
|
||||
</div>
|
||||
<p>
|
||||
简约白
|
||||
</p>
|
||||
</div>
|
||||
<div class="item" @click="changeMode('dark')">
|
||||
<div class="item-top">
|
||||
<el-icon v-if="userStore.mode === 'dark'" class="check">
|
||||
<check />
|
||||
</el-icon>
|
||||
<img src="https://gw.alipayobjects.com/zos/antfincdn/XwFOFbLkSM/LCkqqYNmvBEbokSDscrm.svg">
|
||||
</div>
|
||||
<p>
|
||||
商务黑
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-drawer>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'Setting',
|
||||
}
|
||||
</script>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
import { useUserStore } from '@/pinia/modules/user'
|
||||
const drawer = ref(false)
|
||||
const direction = ref('rtl')
|
||||
|
||||
const userStore = useUserStore()
|
||||
|
||||
const handleClose = () => {
|
||||
drawer.value = false
|
||||
}
|
||||
const showSettingDrawer = () => {
|
||||
drawer.value = true
|
||||
}
|
||||
const changeMode = (e) => {
|
||||
if (e === null) {
|
||||
userStore.changeSideMode('dark')
|
||||
return
|
||||
}
|
||||
userStore.changeSideMode(e)
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.drawer-container {
|
||||
transition: all 0.2s;
|
||||
&:hover{
|
||||
right: 0
|
||||
}
|
||||
position: fixed;
|
||||
right: -20px;
|
||||
bottom: 15%;
|
||||
height: 40px;
|
||||
width: 40px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 999;
|
||||
color: #fff;
|
||||
border-radius: 4px 0 0 4px;
|
||||
cursor: pointer;
|
||||
-webkit-box-shadow: inset 0 0 6px rgba(0 ,0 ,0, 10%);
|
||||
}
|
||||
.setting_body{
|
||||
padding: 20px;
|
||||
.setting_card{
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.setting_content{
|
||||
margin-top: 20px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
>.theme-box{
|
||||
display: flex;
|
||||
}
|
||||
>.color-box{
|
||||
div{
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
.item{
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
margin-right: 20px;
|
||||
.item-top{
|
||||
position: relative;
|
||||
}
|
||||
.check{
|
||||
position: absolute;
|
||||
font-size: 20px;
|
||||
color: #00afff;
|
||||
right:10px;
|
||||
bottom: 10px;
|
||||
}
|
||||
p{
|
||||
text-align: center;
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
228
src/view/login/index.vue
Normal file
228
src/view/login/index.vue
Normal file
@@ -0,0 +1,228 @@
|
||||
<template>
|
||||
<div id="userLayout">
|
||||
<div class="login_panel">
|
||||
<div class="login_panel_form">
|
||||
<div class="login_panel_form_title">
|
||||
<img
|
||||
class="login_panel_form_title_logo"
|
||||
:src="$GIN_VUE_ADMIN.appLogo"
|
||||
alt
|
||||
>
|
||||
<p class="login_panel_form_title_p">{{ $GIN_VUE_ADMIN.appName }}</p>
|
||||
</div>
|
||||
<el-form
|
||||
ref="loginForm"
|
||||
:model="loginFormData"
|
||||
:rules="rules"
|
||||
:validate-on-rule-change="false"
|
||||
@keyup.enter="submitForm"
|
||||
>
|
||||
<el-form-item prop="username">
|
||||
<el-input
|
||||
v-model="loginFormData.username"
|
||||
placeholder="请输入用户名"
|
||||
>
|
||||
<template #suffix>
|
||||
<span class="input-icon">
|
||||
<el-icon>
|
||||
<user />
|
||||
</el-icon>
|
||||
</span>
|
||||
</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item prop="password">
|
||||
<el-input
|
||||
v-model="loginFormData.password"
|
||||
:type="lock === 'lock' ? 'password' : 'text'"
|
||||
placeholder="请输入密码"
|
||||
>
|
||||
<template #suffix>
|
||||
<span class="input-icon">
|
||||
<el-icon>
|
||||
<component
|
||||
:is="lock"
|
||||
@click="changeLock"
|
||||
/>
|
||||
</el-icon>
|
||||
</span>
|
||||
</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item prop="captcha" v-if="loginFormData.openCaptcha">
|
||||
<div class="vPicBox">
|
||||
<el-input
|
||||
v-model="loginFormData.captcha"
|
||||
placeholder="请输入验证码"
|
||||
style="width: 60%"
|
||||
/>
|
||||
<div class="vPic">
|
||||
<img
|
||||
v-if="picPath"
|
||||
:src="picPath"
|
||||
alt="请输入验证码"
|
||||
@click="loginVerify()"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button
|
||||
type="primary"
|
||||
style="width: 46%"
|
||||
size="large"
|
||||
@click="checkInit"
|
||||
>前往初始化</el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
size="large"
|
||||
style="width: 46%; margin-left: 8%"
|
||||
@click="submitForm"
|
||||
>登 录</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
<div class="login_panel_right" />
|
||||
<div class="login_panel_foot">
|
||||
<div class="links">
|
||||
<a href="http://doc.henrongyi.top/" target="_blank">
|
||||
<img src="@/assets/docs.png" class="link-icon">
|
||||
</a>
|
||||
<a href="https://support.qq.com/product/371961" target="_blank">
|
||||
<img src="@/assets/kefu.png" class="link-icon">
|
||||
</a>
|
||||
<a
|
||||
href="https://github.com/flipped-aurora/gin-vue-admin"
|
||||
target="_blank"
|
||||
>
|
||||
<img src="@/assets/github.png" class="link-icon">
|
||||
</a>
|
||||
<a href="https://space.bilibili.com/322210472" target="_blank">
|
||||
<img src="@/assets/video.png" class="link-icon">
|
||||
</a>
|
||||
</div>
|
||||
<div class="copyright">
|
||||
<BottomInfo />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'Login',
|
||||
}
|
||||
</script>
|
||||
|
||||
<script setup>
|
||||
import { captcha } from '@/api/user'
|
||||
import { checkDB } from '@/api/initdb'
|
||||
import BottomInfo from '@/view/layout/bottomInfo/bottomInfo.vue'
|
||||
import { reactive, ref } from 'vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { useUserStore } from '@/pinia/modules/user'
|
||||
const router = useRouter()
|
||||
// 验证函数
|
||||
const checkUsername = (rule, value, callback) => {
|
||||
if (value.length < 5) {
|
||||
return callback(new Error('请输入正确的用户名'))
|
||||
} else {
|
||||
callback()
|
||||
}
|
||||
}
|
||||
const checkPassword = (rule, value, callback) => {
|
||||
if (value.length < 6) {
|
||||
return callback(new Error('请输入正确的密码'))
|
||||
} else {
|
||||
callback()
|
||||
}
|
||||
}
|
||||
|
||||
// 获取验证码
|
||||
const loginVerify = () => {
|
||||
captcha({}).then(async(ele) => {
|
||||
rules.captcha.push({
|
||||
max: ele.data.captchaLength,
|
||||
min: ele.data.captchaLength,
|
||||
message: `请输入${ele.data.captchaLength}位验证码`,
|
||||
trigger: 'blur',
|
||||
})
|
||||
picPath.value = ele.data.picPath
|
||||
loginFormData.captchaId = ele.data.captchaId
|
||||
loginFormData.openCaptcha = ele.data.openCaptcha
|
||||
})
|
||||
}
|
||||
loginVerify()
|
||||
|
||||
// 登录相关操作
|
||||
const lock = ref('lock')
|
||||
const changeLock = () => {
|
||||
lock.value = lock.value === 'lock' ? 'unlock' : 'lock'
|
||||
}
|
||||
|
||||
const loginForm = ref(null)
|
||||
const picPath = ref('')
|
||||
const loginFormData = reactive({
|
||||
username: 'admin',
|
||||
password: '123456',
|
||||
captcha: '',
|
||||
captchaId: '',
|
||||
openCaptcha: false,
|
||||
})
|
||||
const rules = reactive({
|
||||
username: [{ validator: checkUsername, trigger: 'blur' }],
|
||||
password: [{ validator: checkPassword, trigger: 'blur' }],
|
||||
captcha: [
|
||||
{
|
||||
message: '验证码格式不正确',
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
const userStore = useUserStore()
|
||||
const login = async() => {
|
||||
return await userStore.LoginIn(loginFormData)
|
||||
}
|
||||
const submitForm = () => {
|
||||
loginForm.value.validate(async(v) => {
|
||||
if (v) {
|
||||
const flag = await login()
|
||||
if (!flag) {
|
||||
loginVerify()
|
||||
}
|
||||
} else {
|
||||
ElMessage({
|
||||
type: 'error',
|
||||
message: '请正确填写登录信息',
|
||||
showClose: true,
|
||||
})
|
||||
loginVerify()
|
||||
return false
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 跳转初始化
|
||||
const checkInit = async() => {
|
||||
const res = await checkDB()
|
||||
if (res.code === 0) {
|
||||
if (res.data?.needInit) {
|
||||
userStore.NeedInit()
|
||||
router.push({ name: 'Init' })
|
||||
} else {
|
||||
ElMessage({
|
||||
type: 'info',
|
||||
message: '已配置数据库信息,无法初始化',
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "@/style/newLogin.scss";
|
||||
</style>
|
561
src/view/person/person.vue
Normal file
561
src/view/person/person.vue
Normal file
@@ -0,0 +1,561 @@
|
||||
<template>
|
||||
<div>
|
||||
<el-row>
|
||||
<el-col :span="6">
|
||||
<div class="fl-left avatar-box">
|
||||
<div class="user-card">
|
||||
<div
|
||||
class="user-headpic-update"
|
||||
:style="{
|
||||
'background-image': `url(${
|
||||
userStore.userInfo.headerImg &&
|
||||
userStore.userInfo.headerImg.slice(0, 4) !== 'http'
|
||||
? path + userStore.userInfo.headerImg
|
||||
: userStore.userInfo.headerImg
|
||||
})`,
|
||||
'background-repeat': 'no-repeat',
|
||||
'background-size': 'cover',
|
||||
}"
|
||||
>
|
||||
<span class="update" @click="openChooseImg">
|
||||
<el-icon>
|
||||
<edit />
|
||||
</el-icon>
|
||||
重新上传</span>
|
||||
</div>
|
||||
<div class="user-personality">
|
||||
<p v-if="!editFlag" class="nickName">
|
||||
{{ userStore.userInfo.nickName }}
|
||||
<el-icon class="pointer" color="#66b1ff" @click="openEdit">
|
||||
<edit />
|
||||
</el-icon>
|
||||
</p>
|
||||
<p v-if="editFlag" class="nickName">
|
||||
<el-input v-model="nickName" />
|
||||
<el-icon class="pointer" color="#67c23a" @click="enterEdit">
|
||||
<check />
|
||||
</el-icon>
|
||||
<el-icon class="pointer" color="#f23c3c" @click="closeEdit">
|
||||
<close />
|
||||
</el-icon>
|
||||
</p>
|
||||
<p class="person-info">这个家伙很懒,什么都没有留下</p>
|
||||
</div>
|
||||
<div class="user-information">
|
||||
<ul>
|
||||
<li>
|
||||
<el-icon>
|
||||
<user />
|
||||
</el-icon>
|
||||
{{ userStore.userInfo.nickName }}
|
||||
</li>
|
||||
<el-tooltip
|
||||
class="item"
|
||||
effect="light"
|
||||
content="北京反转极光科技有限公司-技术部-前端事业群"
|
||||
placement="top"
|
||||
>
|
||||
<li>
|
||||
<el-icon>
|
||||
<data-analysis />
|
||||
</el-icon>
|
||||
北京反转极光科技有限公司-技术部-前端事业群
|
||||
</li>
|
||||
</el-tooltip>
|
||||
<li>
|
||||
<el-icon>
|
||||
<video-camera />
|
||||
</el-icon>
|
||||
中国·北京市·朝阳区
|
||||
</li>
|
||||
<el-tooltip
|
||||
class="item"
|
||||
effect="light"
|
||||
content="GoLang/JavaScript/Vue/Gorm"
|
||||
placement="top"
|
||||
>
|
||||
<li>
|
||||
<el-icon>
|
||||
<medal />
|
||||
</el-icon>
|
||||
GoLang/JavaScript/Vue/Gorm
|
||||
</li>
|
||||
</el-tooltip>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="18">
|
||||
<div class="user-addcount">
|
||||
<el-tabs v-model="activeName" @tab-click="handleClick">
|
||||
<el-tab-pane label="账号绑定" name="second">
|
||||
<ul>
|
||||
<li>
|
||||
<p class="title">密保手机</p>
|
||||
<p class="desc">
|
||||
已绑定手机:{{ userStore.userInfo.phone }}
|
||||
<a href="javascript:void(0)" @click="changePhoneFlag = true">立即修改</a>
|
||||
</p>
|
||||
</li>
|
||||
<li>
|
||||
<p class="title">密保邮箱</p>
|
||||
<p class="desc">
|
||||
已绑定邮箱:{{ userStore.userInfo.email }}
|
||||
<a href="javascript:void(0)" @click="changeEmailFlag = true">立即修改</a>
|
||||
</p>
|
||||
</li>
|
||||
<li>
|
||||
<p class="title">密保问题</p>
|
||||
<p class="desc">
|
||||
未设置密保问题
|
||||
<a href="javascript:void(0)">去设置</a>
|
||||
</p>
|
||||
</li>
|
||||
<li>
|
||||
<p class="title">修改密码</p>
|
||||
<p class="desc">
|
||||
修改个人密码
|
||||
<a
|
||||
href="javascript:void(0)"
|
||||
@click="showPassword = true"
|
||||
>修改密码</a>
|
||||
</p>
|
||||
</li>
|
||||
</ul>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<ChooseImg ref="chooseImgRef" @enter-img="enterImg" />
|
||||
|
||||
<el-dialog
|
||||
v-model="showPassword"
|
||||
title="修改密码"
|
||||
width="360px"
|
||||
@close="clearPassword"
|
||||
>
|
||||
<el-form
|
||||
ref="modifyPwdForm"
|
||||
:model="pwdModify"
|
||||
:rules="rules"
|
||||
label-width="80px"
|
||||
>
|
||||
<el-form-item :minlength="6" label="原密码" prop="password">
|
||||
<el-input v-model="pwdModify.password" show-password />
|
||||
</el-form-item>
|
||||
<el-form-item :minlength="6" label="新密码" prop="newPassword">
|
||||
<el-input v-model="pwdModify.newPassword" show-password />
|
||||
</el-form-item>
|
||||
<el-form-item :minlength="6" label="确认密码" prop="confirmPassword">
|
||||
<el-input v-model="pwdModify.confirmPassword" show-password />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button
|
||||
size="small"
|
||||
@click="showPassword = false"
|
||||
>取 消</el-button>
|
||||
<el-button
|
||||
size="small"
|
||||
type="primary"
|
||||
@click="savePassword"
|
||||
>确 定</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<el-dialog v-model="changePhoneFlag" title="绑定手机" width="600px">
|
||||
<el-form :model="phoneForm">
|
||||
<el-form-item label="手机号" label-width="120px">
|
||||
<el-input v-model="phoneForm.phone" placeholder="请输入手机号" autocomplete="off" />
|
||||
</el-form-item>
|
||||
<el-form-item label="验证码" label-width="120px">
|
||||
<div class="code-box">
|
||||
<el-input v-model="phoneForm.code" autocomplete="off" placeholder="请自行设计短信服务,此处为模拟随便写" style="width:300px" />
|
||||
<el-button size="small" type="primary" :disabled="time>0" @click="getCode">{{ time>0?`(${time}s)后重新获取`:'获取验证码' }}</el-button>
|
||||
</div>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button
|
||||
size="small"
|
||||
@click="closeChangePhone"
|
||||
>取消</el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
size="small"
|
||||
@click="changePhone"
|
||||
>更改</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<el-dialog v-model="changeEmailFlag" title="绑定邮箱" width="600px">
|
||||
<el-form :model="emailForm">
|
||||
<el-form-item label="邮箱" label-width="120px">
|
||||
<el-input v-model="emailForm.email" placeholder="请输入邮箱" autocomplete="off" />
|
||||
</el-form-item>
|
||||
<el-form-item label="验证码" label-width="120px">
|
||||
<div class="code-box">
|
||||
<el-input v-model="emailForm.code" placeholder="请自行设计邮件服务,此处为模拟随便写" autocomplete="off" style="width:300px" />
|
||||
<el-button size="small" type="primary" :disabled="emailTime>0" @click="getEmailCode">{{ emailTime>0?`(${emailTime}s)后重新获取`:'获取验证码' }}</el-button>
|
||||
</div>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button
|
||||
size="small"
|
||||
@click="closeChangeEmail"
|
||||
>取消</el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
size="small"
|
||||
@click="changeEmail"
|
||||
>更改</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'Person',
|
||||
}
|
||||
</script>
|
||||
|
||||
<script setup>
|
||||
import ChooseImg from '@/components/chooseImg/index.vue'
|
||||
import { setSelfInfo, changePassword } from '@/api/user.js'
|
||||
import { reactive, ref } from 'vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { useUserStore } from '@/pinia/modules/user'
|
||||
|
||||
const path = ref(import.meta.env.VITE_BASE_API + '/')
|
||||
const activeName = ref('second')
|
||||
const rules = reactive({
|
||||
password: [
|
||||
{ required: true, message: '请输入密码', trigger: 'blur' },
|
||||
{ min: 6, message: '最少6个字符', trigger: 'blur' },
|
||||
],
|
||||
newPassword: [
|
||||
{ required: true, message: '请输入新密码', trigger: 'blur' },
|
||||
{ min: 6, message: '最少6个字符', trigger: 'blur' },
|
||||
],
|
||||
confirmPassword: [
|
||||
{ required: true, message: '请输入确认密码', trigger: 'blur' },
|
||||
{ min: 6, message: '最少6个字符', trigger: 'blur' },
|
||||
{
|
||||
validator: (rule, value, callback) => {
|
||||
if (value !== pwdModify.value.newPassword) {
|
||||
callback(new Error('两次密码不一致'))
|
||||
} else {
|
||||
callback()
|
||||
}
|
||||
},
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
const userStore = useUserStore()
|
||||
const modifyPwdForm = ref(null)
|
||||
const showPassword = ref(false)
|
||||
const pwdModify = ref({})
|
||||
const nickName = ref('')
|
||||
const editFlag = ref(false)
|
||||
const savePassword = async() => {
|
||||
modifyPwdForm.value.validate((valid) => {
|
||||
if (valid) {
|
||||
changePassword({
|
||||
password: pwdModify.value.password,
|
||||
newPassword: pwdModify.value.newPassword,
|
||||
}).then((res) => {
|
||||
if (res.code === 0) {
|
||||
ElMessage.success('修改密码成功!')
|
||||
}
|
||||
showPassword.value = false
|
||||
})
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const clearPassword = () => {
|
||||
pwdModify.value = {
|
||||
password: '',
|
||||
newPassword: '',
|
||||
confirmPassword: '',
|
||||
}
|
||||
modifyPwdForm.value.clearValidate()
|
||||
}
|
||||
|
||||
const chooseImgRef = ref(null)
|
||||
const openChooseImg = () => {
|
||||
chooseImgRef.value.open()
|
||||
}
|
||||
|
||||
const enterImg = async(url) => {
|
||||
const res = await setSelfInfo({ headerImg: url })
|
||||
if (res.code === 0) {
|
||||
userStore.ResetUserInfo({ headerImg: url })
|
||||
ElMessage({
|
||||
type: 'success',
|
||||
message: '设置成功',
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const openEdit = () => {
|
||||
nickName.value = userStore.userInfo.nickName
|
||||
editFlag.value = true
|
||||
}
|
||||
|
||||
const closeEdit = () => {
|
||||
nickName.value = ''
|
||||
editFlag.value = false
|
||||
}
|
||||
|
||||
const enterEdit = async() => {
|
||||
const res = await setSelfInfo({
|
||||
nickName: nickName.value
|
||||
})
|
||||
if (res.code === 0) {
|
||||
userStore.ResetUserInfo({ nickName: nickName.value })
|
||||
ElMessage({
|
||||
type: 'success',
|
||||
message: '设置成功',
|
||||
})
|
||||
}
|
||||
nickName.value = ''
|
||||
editFlag.value = false
|
||||
}
|
||||
|
||||
const handleClick = (tab, event) => {
|
||||
console.log(tab, event)
|
||||
}
|
||||
|
||||
const changePhoneFlag = ref(false)
|
||||
const time = ref(0)
|
||||
const phoneForm = reactive({
|
||||
phone: '',
|
||||
code: ''
|
||||
})
|
||||
|
||||
const getCode = async() => {
|
||||
time.value = 60
|
||||
let timer = setInterval(() => {
|
||||
time.value--
|
||||
if (time.value <= 0) {
|
||||
clearInterval(timer)
|
||||
timer = null
|
||||
}
|
||||
}, 1000)
|
||||
}
|
||||
|
||||
const closeChangePhone = () => {
|
||||
changePhoneFlag.value = false
|
||||
phoneForm.phone = ''
|
||||
phoneForm.code = ''
|
||||
}
|
||||
|
||||
const changePhone = async() => {
|
||||
const res = await setSelfInfo({ phone: phoneForm.phone })
|
||||
if (res.code === 0) {
|
||||
ElMessage.success('修改成功')
|
||||
userStore.ResetUserInfo({ phone: phoneForm.phone })
|
||||
closeChangePhone()
|
||||
}
|
||||
}
|
||||
|
||||
const changeEmailFlag = ref(false)
|
||||
const emailTime = ref(0)
|
||||
const emailForm = reactive({
|
||||
email: '',
|
||||
code: ''
|
||||
})
|
||||
|
||||
const getEmailCode = async() => {
|
||||
emailTime.value = 60
|
||||
let timer = setInterval(() => {
|
||||
emailTime.value--
|
||||
if (emailTime.value <= 0) {
|
||||
clearInterval(timer)
|
||||
timer = null
|
||||
}
|
||||
}, 1000)
|
||||
}
|
||||
|
||||
const closeChangeEmail = () => {
|
||||
changeEmailFlag.value = false
|
||||
emailForm.email = ''
|
||||
emailForm.code = ''
|
||||
}
|
||||
|
||||
const changeEmail = async() => {
|
||||
const res = await setSelfInfo({ email: emailForm.email })
|
||||
if (res.code === 0) {
|
||||
ElMessage.success('修改成功')
|
||||
userStore.ResetUserInfo({ email: emailForm.email })
|
||||
closeChangeEmail()
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.avatar-uploader .el-upload {
|
||||
border: 1px dashed #d9d9d9;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
.avatar-uploader .el-upload:hover {
|
||||
border-color: #409eff;
|
||||
}
|
||||
.avatar-uploader-icon {
|
||||
font-size: 28px;
|
||||
color: #8c939d;
|
||||
width: 178px;
|
||||
height: 178px;
|
||||
line-height: 178px;
|
||||
text-align: center;
|
||||
}
|
||||
.avatar {
|
||||
width: 178px;
|
||||
height: 178px;
|
||||
display: block;
|
||||
}
|
||||
.avatar-box {
|
||||
box-shadow: -2px 0 20px -16px;
|
||||
width: 80%;
|
||||
height: 100%;
|
||||
.user-card {
|
||||
min-height: calc(90vh - 200px);
|
||||
padding: 30px 20px;
|
||||
text-align: center;
|
||||
background-color: #fff;
|
||||
border-radius: 8px;
|
||||
flex-shrink: 0;
|
||||
.el-avatar {
|
||||
border-radius: 50%;
|
||||
}
|
||||
.user-personality {
|
||||
padding: 24px 0;
|
||||
text-align: center;
|
||||
p {
|
||||
font-size: 16px;
|
||||
}
|
||||
.nickName {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
font-size: 26px;
|
||||
}
|
||||
.person-info {
|
||||
margin-top: 6px;
|
||||
font-size: 14px;
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
.user-information {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
text-align: left;
|
||||
ul {
|
||||
display: inline-block;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
li {
|
||||
width: 100%;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
i {
|
||||
margin-right: 8px;
|
||||
}
|
||||
padding: 20px 0;
|
||||
font-size: 16px;
|
||||
font-weight: 400;
|
||||
color: #606266;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.user-addcount {
|
||||
background-color: #fff;
|
||||
padding: 20px;
|
||||
border-radius: 8px;
|
||||
ul {
|
||||
li {
|
||||
.title {
|
||||
padding: 10px;
|
||||
font-size: 18px;
|
||||
color: #696969;
|
||||
}
|
||||
.desc {
|
||||
font-size: 16px;
|
||||
padding: 0 10px 20px 10px;
|
||||
color: #a9a9a9;
|
||||
a {
|
||||
color: rgb(64, 158, 255);
|
||||
float: right;
|
||||
}
|
||||
}
|
||||
border-bottom: 2px solid #f0f2f5;
|
||||
&:last-child{
|
||||
border-bottom: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.user-headpic-update {
|
||||
width: 120px;
|
||||
height: 120px;
|
||||
line-height: 120px;
|
||||
margin: 0 auto;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
border-radius: 20px;
|
||||
&:hover {
|
||||
color: #fff;
|
||||
background: linear-gradient(
|
||||
to bottom,
|
||||
rgba(255, 255, 255, 0.15) 0%,
|
||||
rgba(0, 0, 0, 0.15) 100%
|
||||
),
|
||||
radial-gradient(
|
||||
at top center,
|
||||
rgba(255, 255, 255, 0.4) 0%,
|
||||
rgba(0, 0, 0, 0.4) 120%
|
||||
)
|
||||
#989898;
|
||||
background-blend-mode: multiply, multiply;
|
||||
.update {
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
.update {
|
||||
height: 120px;
|
||||
width: 120px;
|
||||
text-align: center;
|
||||
color: transparent;
|
||||
}
|
||||
}
|
||||
.pointer {
|
||||
cursor: pointer;
|
||||
}
|
||||
.code-box{
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
</style>
|
23
src/view/routerHolder.vue
Normal file
23
src/view/routerHolder.vue
Normal file
@@ -0,0 +1,23 @@
|
||||
<!-- 此路由可作为父类路由通用路由页面使用 如需自定义父类路由页面 请参考 @/view/superAdmin/index.vue -->
|
||||
<template>
|
||||
<div>
|
||||
<router-view v-slot="{ Component }">
|
||||
<transition mode="out-in" name="el-fade-in-linear">
|
||||
<keep-alive :include="routerStore.keepAliveRouters">
|
||||
<component :is="Component" />
|
||||
</keep-alive>
|
||||
</transition>
|
||||
</router-view>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'RouterHolder'
|
||||
}
|
||||
</script>
|
||||
|
||||
<script setup>
|
||||
import { useRouterStore } from '@/pinia/modules/router'
|
||||
const routerStore = useRouterStore()
|
||||
</script>
|
391
src/view/superAdmin/api/api.vue
Normal file
391
src/view/superAdmin/api/api.vue
Normal file
@@ -0,0 +1,391 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="gva-search-box">
|
||||
<el-form ref="searchForm" :inline="true" :model="searchInfo">
|
||||
<el-form-item label="路径">
|
||||
<el-input v-model="searchInfo.path" placeholder="路径" />
|
||||
</el-form-item>
|
||||
<el-form-item label="描述">
|
||||
<el-input v-model="searchInfo.description" placeholder="描述" />
|
||||
</el-form-item>
|
||||
<el-form-item label="API组">
|
||||
<el-input v-model="searchInfo.apiGroup" placeholder="api组" />
|
||||
</el-form-item>
|
||||
<el-form-item label="请求">
|
||||
<el-select v-model="searchInfo.method" clearable placeholder="请选择">
|
||||
<el-option
|
||||
v-for="item in methodOptions"
|
||||
:key="item.value"
|
||||
:label="`${item.label}(${item.value})`"
|
||||
:value="item.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button size="small" type="primary" icon="search" @click="onSubmit">查询</el-button>
|
||||
<el-button size="small" icon="refresh" @click="onReset">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
<div class="gva-table-box">
|
||||
<div class="gva-btn-list">
|
||||
<el-button size="small" type="primary" icon="plus" @click="openDialog('addApi')">新增</el-button>
|
||||
<el-popover v-model="deleteVisible" placement="top" width="160">
|
||||
<p>确定要删除吗?</p>
|
||||
<div style="text-align: right; margin-top: 8px;">
|
||||
<el-button size="small" type="primary" link @click="deleteVisible = false">取消</el-button>
|
||||
<el-button size="small" type="primary" @click="onDelete">确定</el-button>
|
||||
</div>
|
||||
<template #reference>
|
||||
<el-button icon="delete" size="small" :disabled="!apis.length" style="margin-left: 10px;" @click="deleteVisible = true">删除</el-button>
|
||||
</template>
|
||||
</el-popover>
|
||||
</div>
|
||||
<el-table :data="tableData" @sort-change="sortChange" @selection-change="handleSelectionChange">
|
||||
<el-table-column
|
||||
type="selection"
|
||||
width="55"
|
||||
/>
|
||||
<el-table-column align="left" label="id" min-width="60" prop="ID" sortable="custom" />
|
||||
<el-table-column align="left" label="API路径" min-width="150" prop="path" sortable="custom" />
|
||||
<el-table-column align="left" label="API分组" min-width="150" prop="apiGroup" sortable="custom" />
|
||||
<el-table-column align="left" label="API简介" min-width="150" prop="description" sortable="custom" />
|
||||
<el-table-column align="left" label="请求" min-width="150" prop="method" sortable="custom">
|
||||
<template #default="scope">
|
||||
<div>
|
||||
{{ scope.row.method }} / {{ methodFilter(scope.row.method) }}
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column align="left" fixed="right" label="操作" width="200">
|
||||
<template #default="scope">
|
||||
<el-button
|
||||
icon="edit"
|
||||
size="small"
|
||||
type="primary"
|
||||
link
|
||||
@click="editApiFunc(scope.row)"
|
||||
>编辑</el-button>
|
||||
<el-button
|
||||
icon="delete"
|
||||
size="small"
|
||||
type="primary"
|
||||
link
|
||||
@click="deleteApiFunc(scope.row)"
|
||||
>删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<div class="gva-pagination">
|
||||
<el-pagination
|
||||
:current-page="page"
|
||||
:page-size="pageSize"
|
||||
:page-sizes="[10, 30, 50, 100]"
|
||||
:total="total"
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
@current-change="handleCurrentChange"
|
||||
@size-change="handleSizeChange"
|
||||
/>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<el-dialog v-model="dialogFormVisible" :before-close="closeDialog" :title="dialogTitle">
|
||||
<warning-bar title="新增API,需要在角色管理内配置权限才可使用" />
|
||||
<el-form ref="apiForm" :model="form" :rules="rules" label-width="80px">
|
||||
<el-form-item label="路径" prop="path">
|
||||
<el-input v-model="form.path" autocomplete="off" />
|
||||
</el-form-item>
|
||||
<el-form-item label="请求" prop="method">
|
||||
<el-select v-model="form.method" placeholder="请选择" style="width:100%">
|
||||
<el-option
|
||||
v-for="item in methodOptions"
|
||||
:key="item.value"
|
||||
:label="`${item.label}(${item.value})`"
|
||||
:value="item.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="api分组" prop="apiGroup">
|
||||
<el-input v-model="form.apiGroup" autocomplete="off" />
|
||||
</el-form-item>
|
||||
<el-form-item label="api简介" prop="description">
|
||||
<el-input v-model="form.description" autocomplete="off" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button size="small" @click="closeDialog">取 消</el-button>
|
||||
<el-button size="small" type="primary" @click="enterDialog">确 定</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'Api',
|
||||
}
|
||||
</script>
|
||||
|
||||
<script setup>
|
||||
import {
|
||||
getApiById,
|
||||
getApiList,
|
||||
createApi,
|
||||
updateApi,
|
||||
deleteApi,
|
||||
deleteApisByIds
|
||||
} from '@/api/api'
|
||||
import { toSQLLine } from '@/utils/stringFun'
|
||||
import WarningBar from '@/components/warningBar/warningBar.vue'
|
||||
import { ref } from 'vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
|
||||
const methodFilter = (value) => {
|
||||
const target = methodOptions.value.filter(item => item.value === value)[0]
|
||||
return target && `${target.label}`
|
||||
}
|
||||
|
||||
const apis = ref([])
|
||||
const form = ref({
|
||||
path: '',
|
||||
apiGroup: '',
|
||||
method: '',
|
||||
description: ''
|
||||
})
|
||||
const methodOptions = ref([
|
||||
{
|
||||
value: 'POST',
|
||||
label: '创建',
|
||||
type: 'success'
|
||||
},
|
||||
{
|
||||
value: 'GET',
|
||||
label: '查看',
|
||||
type: ''
|
||||
},
|
||||
{
|
||||
value: 'PUT',
|
||||
label: '更新',
|
||||
type: 'warning'
|
||||
},
|
||||
{
|
||||
value: 'DELETE',
|
||||
label: '删除',
|
||||
type: 'danger'
|
||||
}
|
||||
])
|
||||
|
||||
const type = ref('')
|
||||
const rules = ref({
|
||||
path: [{ required: true, message: '请输入api路径', trigger: 'blur' }],
|
||||
apiGroup: [
|
||||
{ required: true, message: '请输入组名称', trigger: 'blur' }
|
||||
],
|
||||
method: [
|
||||
{ required: true, message: '请选择请求方式', trigger: 'blur' }
|
||||
],
|
||||
description: [
|
||||
{ required: true, message: '请输入api介绍', trigger: 'blur' }
|
||||
]
|
||||
})
|
||||
|
||||
const page = ref(1)
|
||||
const total = ref(0)
|
||||
const pageSize = ref(10)
|
||||
const tableData = ref([])
|
||||
const searchInfo = ref({})
|
||||
|
||||
const onReset = () => {
|
||||
searchInfo.value = {}
|
||||
}
|
||||
// 搜索
|
||||
|
||||
const onSubmit = () => {
|
||||
page.value = 1
|
||||
pageSize.value = 10
|
||||
getTableData()
|
||||
}
|
||||
|
||||
// 分页
|
||||
const handleSizeChange = (val) => {
|
||||
pageSize.value = val
|
||||
getTableData()
|
||||
}
|
||||
|
||||
const handleCurrentChange = (val) => {
|
||||
page.value = val
|
||||
getTableData()
|
||||
}
|
||||
|
||||
// 排序
|
||||
const sortChange = ({ prop, order }) => {
|
||||
if (prop) {
|
||||
if (prop === 'ID') {
|
||||
prop = 'id'
|
||||
}
|
||||
searchInfo.value.orderKey = toSQLLine(prop)
|
||||
searchInfo.value.desc = order === 'descending'
|
||||
}
|
||||
getTableData()
|
||||
}
|
||||
|
||||
// 查询
|
||||
const getTableData = async() => {
|
||||
const table = await getApiList({ page: page.value, pageSize: pageSize.value, ...searchInfo.value })
|
||||
if (table.code === 0) {
|
||||
tableData.value = table.data.list
|
||||
total.value = table.data.total
|
||||
page.value = table.data.page
|
||||
pageSize.value = table.data.pageSize
|
||||
}
|
||||
}
|
||||
|
||||
getTableData()
|
||||
|
||||
// 批量操作
|
||||
const handleSelectionChange = (val) => {
|
||||
apis.value = val
|
||||
}
|
||||
|
||||
const deleteVisible = ref(false)
|
||||
const onDelete = async() => {
|
||||
const ids = apis.value.map(item => item.ID)
|
||||
const res = await deleteApisByIds({ ids })
|
||||
if (res.code === 0) {
|
||||
ElMessage({
|
||||
type: 'success',
|
||||
message: res.msg
|
||||
})
|
||||
if (tableData.value.length === ids.length && page.value > 1) {
|
||||
page.value--
|
||||
}
|
||||
deleteVisible.value = false
|
||||
getTableData()
|
||||
}
|
||||
}
|
||||
|
||||
// 弹窗相关
|
||||
const apiForm = ref(null)
|
||||
const initForm = () => {
|
||||
apiForm.value.resetFields()
|
||||
form.value = {
|
||||
path: '',
|
||||
apiGroup: '',
|
||||
method: '',
|
||||
description: ''
|
||||
}
|
||||
}
|
||||
|
||||
const dialogTitle = ref('新增Api')
|
||||
const dialogFormVisible = ref(false)
|
||||
const openDialog = (key) => {
|
||||
switch (key) {
|
||||
case 'addApi':
|
||||
dialogTitle.value = '新增Api'
|
||||
break
|
||||
case 'edit':
|
||||
dialogTitle.value = '编辑Api'
|
||||
break
|
||||
default:
|
||||
break
|
||||
}
|
||||
type.value = key
|
||||
dialogFormVisible.value = true
|
||||
}
|
||||
const closeDialog = () => {
|
||||
initForm()
|
||||
dialogFormVisible.value = false
|
||||
}
|
||||
|
||||
const editApiFunc = async(row) => {
|
||||
const res = await getApiById({ id: row.ID })
|
||||
form.value = res.data.api
|
||||
openDialog('edit')
|
||||
}
|
||||
|
||||
const enterDialog = async() => {
|
||||
apiForm.value.validate(async valid => {
|
||||
if (valid) {
|
||||
switch (type.value) {
|
||||
case 'addApi':
|
||||
{
|
||||
const res = await createApi(form.value)
|
||||
if (res.code === 0) {
|
||||
ElMessage({
|
||||
type: 'success',
|
||||
message: '添加成功',
|
||||
showClose: true
|
||||
})
|
||||
}
|
||||
getTableData()
|
||||
closeDialog()
|
||||
}
|
||||
|
||||
break
|
||||
case 'edit':
|
||||
{
|
||||
const res = await updateApi(form.value)
|
||||
if (res.code === 0) {
|
||||
ElMessage({
|
||||
type: 'success',
|
||||
message: '编辑成功',
|
||||
showClose: true
|
||||
})
|
||||
}
|
||||
getTableData()
|
||||
closeDialog()
|
||||
}
|
||||
break
|
||||
default:
|
||||
// eslint-disable-next-line no-lone-blocks
|
||||
{
|
||||
ElMessage({
|
||||
type: 'error',
|
||||
message: '未知操作',
|
||||
showClose: true
|
||||
})
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const deleteApiFunc = async(row) => {
|
||||
ElMessageBox.confirm('此操作将永久删除所有角色下该api, 是否继续?', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
})
|
||||
.then(async() => {
|
||||
const res = await deleteApi(row)
|
||||
if (res.code === 0) {
|
||||
ElMessage({
|
||||
type: 'success',
|
||||
message: '删除成功!'
|
||||
})
|
||||
if (tableData.value.length === 1 && page.value > 1) {
|
||||
page.value--
|
||||
}
|
||||
getTableData()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.button-box {
|
||||
padding: 10px 20px;
|
||||
.el-button {
|
||||
float: right;
|
||||
}
|
||||
}
|
||||
.warning {
|
||||
color: #dc143c;
|
||||
}
|
||||
</style>
|
410
src/view/superAdmin/authority/authority.vue
Normal file
410
src/view/superAdmin/authority/authority.vue
Normal file
@@ -0,0 +1,410 @@
|
||||
<template>
|
||||
<div class="authority">
|
||||
<warning-bar title="注:右上角头像下拉可切换角色" />
|
||||
<div class="gva-table-box">
|
||||
<div class="gva-btn-list">
|
||||
<el-button size="small" type="primary" icon="plus" @click="addAuthority(0)">新增角色</el-button>
|
||||
</div>
|
||||
<el-table
|
||||
:data="tableData"
|
||||
:tree-props="{children: 'children', hasChildren: 'hasChildren'}"
|
||||
row-key="authorityId"
|
||||
style="width: 100%"
|
||||
>
|
||||
<el-table-column label="角色ID" min-width="180" prop="authorityId" />
|
||||
<el-table-column align="left" label="角色名称" min-width="180" prop="authorityName" />
|
||||
<el-table-column align="left" label="操作" width="460">
|
||||
<template #default="scope">
|
||||
<el-button
|
||||
icon="setting"
|
||||
size="small"
|
||||
type="primary"
|
||||
link
|
||||
@click="opdendrawer(scope.row)"
|
||||
>设置权限</el-button>
|
||||
<el-button
|
||||
icon="plus"
|
||||
size="small"
|
||||
type="primary"
|
||||
link
|
||||
@click="addAuthority(scope.row.authorityId)"
|
||||
>新增子角色</el-button>
|
||||
<el-button
|
||||
icon="copy-document"
|
||||
size="small"
|
||||
type="primary"
|
||||
link
|
||||
@click="copyAuthorityFunc(scope.row)"
|
||||
>拷贝</el-button>
|
||||
<el-button
|
||||
icon="edit"
|
||||
size="small"
|
||||
type="primary"
|
||||
link
|
||||
@click="editAuthority(scope.row)"
|
||||
>编辑</el-button>
|
||||
<el-button
|
||||
icon="delete"
|
||||
size="small"
|
||||
type="primary"
|
||||
link
|
||||
@click="deleteAuth(scope.row)"
|
||||
>删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
<!-- 新增角色弹窗 -->
|
||||
<el-dialog v-model="dialogFormVisible" :title="dialogTitle">
|
||||
<el-form ref="authorityForm" :model="form" :rules="rules" label-width="80px">
|
||||
<el-form-item label="父级角色" prop="parentId">
|
||||
<el-cascader
|
||||
v-model="form.parentId"
|
||||
style="width:100%"
|
||||
:disabled="dialogType=='add'"
|
||||
:options="AuthorityOption"
|
||||
:props="{ checkStrictly: true,label:'authorityName',value:'authorityId',disabled:'disabled',emitPath:false}"
|
||||
:show-all-levels="false"
|
||||
filterable
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="角色ID" prop="authorityId">
|
||||
<el-input v-model="form.authorityId" :disabled="dialogType=='edit'" autocomplete="off" />
|
||||
</el-form-item>
|
||||
<el-form-item label="角色姓名" prop="authorityName">
|
||||
<el-input v-model="form.authorityName" autocomplete="off" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button size="small" @click="closeDialog">取 消</el-button>
|
||||
<el-button size="small" type="primary" @click="enterDialog">确 定</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<el-drawer v-if="drawer" v-model="drawer" custom-class="auth-drawer" :with-header="false" size="40%" title="角色配置">
|
||||
<el-tabs :before-leave="autoEnter" type="border-card">
|
||||
<el-tab-pane label="角色菜单">
|
||||
<Menus ref="menus" :row="activeRow" @changeRow="changeRow" />
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="角色api">
|
||||
<Apis ref="apis" :row="activeRow" @changeRow="changeRow" />
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="资源权限">
|
||||
<Datas ref="datas" :authority="tableData" :row="activeRow" @changeRow="changeRow" />
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</el-drawer>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {
|
||||
getAuthorityList,
|
||||
deleteAuthority,
|
||||
createAuthority,
|
||||
updateAuthority,
|
||||
copyAuthority
|
||||
} from '@/api/authority'
|
||||
|
||||
import Menus from '@/view/superAdmin/authority/components/menus.vue'
|
||||
import Apis from '@/view/superAdmin/authority/components/apis.vue'
|
||||
import Datas from '@/view/superAdmin/authority/components/datas.vue'
|
||||
import WarningBar from '@/components/warningBar/warningBar.vue'
|
||||
|
||||
import { ref } from 'vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
|
||||
const mustUint = (rule, value, callback) => {
|
||||
if (!/^[0-9]*[1-9][0-9]*$/.test(value)) {
|
||||
return callback(new Error('请输入正整数'))
|
||||
}
|
||||
return callback()
|
||||
}
|
||||
|
||||
const AuthorityOption = ref([
|
||||
{
|
||||
authorityId: 0,
|
||||
authorityName: '根角色'
|
||||
}
|
||||
])
|
||||
const drawer = ref(false)
|
||||
const dialogType = ref('add')
|
||||
const activeRow = ref({})
|
||||
|
||||
const dialogTitle = ref('新增角色')
|
||||
const dialogFormVisible = ref(false)
|
||||
const apiDialogFlag = ref(false)
|
||||
const copyForm = ref({})
|
||||
|
||||
const form = ref({
|
||||
authorityId: 0,
|
||||
authorityName: '',
|
||||
parentId: 0
|
||||
})
|
||||
const rules = ref({
|
||||
authorityId: [
|
||||
{ required: true, message: '请输入角色ID', trigger: 'blur' },
|
||||
{ validator: mustUint, trigger: 'blur', message: '必须为正整数' }
|
||||
],
|
||||
authorityName: [
|
||||
{ required: true, message: '请输入角色名', trigger: 'blur' }
|
||||
],
|
||||
parentId: [
|
||||
{ required: true, message: '请选择父角色', trigger: 'blur' },
|
||||
]
|
||||
})
|
||||
|
||||
const page = ref(1)
|
||||
const total = ref(0)
|
||||
const pageSize = ref(999)
|
||||
const tableData = ref([])
|
||||
const searchInfo = ref({})
|
||||
|
||||
// 查询
|
||||
const getTableData = async() => {
|
||||
const table = await getAuthorityList({ page: page.value, pageSize: pageSize.value, ...searchInfo.value })
|
||||
if (table.code === 0) {
|
||||
tableData.value = table.data.list
|
||||
total.value = table.data.total
|
||||
page.value = table.data.page
|
||||
pageSize.value = table.data.pageSize
|
||||
}
|
||||
}
|
||||
|
||||
getTableData()
|
||||
|
||||
const changeRow = (key, value) => {
|
||||
activeRow.value[key] = value
|
||||
}
|
||||
const menus = ref(null)
|
||||
const apis = ref(null)
|
||||
const datas = ref(null)
|
||||
const autoEnter = (activeName, oldActiveName) => {
|
||||
const paneArr = [menus, apis, datas]
|
||||
if (oldActiveName) {
|
||||
if (paneArr[oldActiveName].value.needConfirm) {
|
||||
paneArr[oldActiveName].value.enterAndNext()
|
||||
paneArr[oldActiveName].value.needConfirm = false
|
||||
}
|
||||
}
|
||||
}
|
||||
// 拷贝角色
|
||||
const copyAuthorityFunc = (row) => {
|
||||
setOptions()
|
||||
dialogTitle.value = '拷贝角色'
|
||||
dialogType.value = 'copy'
|
||||
for (const k in form.value) {
|
||||
form.value[k] = row[k]
|
||||
}
|
||||
copyForm.value = row
|
||||
dialogFormVisible.value = true
|
||||
}
|
||||
const opdendrawer = (row) => {
|
||||
drawer.value = true
|
||||
activeRow.value = row
|
||||
}
|
||||
// 删除角色
|
||||
const deleteAuth = (row) => {
|
||||
ElMessageBox.confirm('此操作将永久删除该角色, 是否继续?', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
})
|
||||
.then(async() => {
|
||||
const res = await deleteAuthority({ authorityId: row.authorityId })
|
||||
if (res.code === 0) {
|
||||
ElMessage({
|
||||
type: 'success',
|
||||
message: '删除成功!'
|
||||
})
|
||||
if (tableData.value.length === 1 && page.value > 1) {
|
||||
page.value--
|
||||
}
|
||||
getTableData()
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
ElMessage({
|
||||
type: 'info',
|
||||
message: '已取消删除'
|
||||
})
|
||||
})
|
||||
}
|
||||
// 初始化表单
|
||||
const authorityForm = ref(null)
|
||||
const initForm = () => {
|
||||
if (authorityForm.value) {
|
||||
authorityForm.value.resetFields()
|
||||
}
|
||||
form.value = {
|
||||
authorityId: 0,
|
||||
authorityName: '',
|
||||
parentId: 0
|
||||
}
|
||||
}
|
||||
// 关闭窗口
|
||||
const closeDialog = () => {
|
||||
initForm()
|
||||
dialogFormVisible.value = false
|
||||
apiDialogFlag.value = false
|
||||
}
|
||||
// 确定弹窗
|
||||
|
||||
const enterDialog = () => {
|
||||
form.value.authorityId = Number(form.value.authorityId)
|
||||
if (form.value.authorityId === 0) {
|
||||
ElMessage({
|
||||
type: 'error',
|
||||
message: '角色id不能为0'
|
||||
})
|
||||
return false
|
||||
}
|
||||
authorityForm.value.validate(async valid => {
|
||||
if (valid) {
|
||||
switch (dialogType.value) {
|
||||
case 'add':
|
||||
{
|
||||
const res = await createAuthority(form.value)
|
||||
if (res.code === 0) {
|
||||
ElMessage({
|
||||
type: 'success',
|
||||
message: '添加成功!'
|
||||
})
|
||||
getTableData()
|
||||
closeDialog()
|
||||
}
|
||||
}
|
||||
break
|
||||
case 'edit':
|
||||
{
|
||||
const res = await updateAuthority(form.value)
|
||||
if (res.code === 0) {
|
||||
ElMessage({
|
||||
type: 'success',
|
||||
message: '添加成功!'
|
||||
})
|
||||
getTableData()
|
||||
closeDialog()
|
||||
}
|
||||
}
|
||||
break
|
||||
case 'copy': {
|
||||
const data = {
|
||||
authority: {
|
||||
authorityId: 0,
|
||||
authorityName: '',
|
||||
datauthorityId: [],
|
||||
parentId: 0
|
||||
},
|
||||
oldAuthorityId: 0
|
||||
}
|
||||
data.authority.authorityId = form.value.authorityId
|
||||
data.authority.authorityName = form.value.authorityName
|
||||
data.authority.parentId = form.value.parentId
|
||||
data.authority.dataAuthorityId = copyForm.value.dataAuthorityId
|
||||
data.oldAuthorityId = copyForm.value.authorityId
|
||||
const res = await copyAuthority(data)
|
||||
if (res.code === 0) {
|
||||
ElMessage({
|
||||
type: 'success',
|
||||
message: '复制成功!'
|
||||
})
|
||||
getTableData()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
initForm()
|
||||
dialogFormVisible.value = false
|
||||
}
|
||||
})
|
||||
}
|
||||
const setOptions = () => {
|
||||
AuthorityOption.value = [
|
||||
{
|
||||
authorityId: 0,
|
||||
authorityName: '根角色'
|
||||
}
|
||||
]
|
||||
setAuthorityOptions(tableData.value, AuthorityOption.value, false)
|
||||
}
|
||||
const setAuthorityOptions = (AuthorityData, optionsData, disabled) => {
|
||||
form.value.authorityId = String(form.value.authorityId)
|
||||
AuthorityData &&
|
||||
AuthorityData.forEach(item => {
|
||||
if (item.children && item.children.length) {
|
||||
const option = {
|
||||
authorityId: item.authorityId,
|
||||
authorityName: item.authorityName,
|
||||
disabled: disabled || item.authorityId === form.value.authorityId,
|
||||
children: []
|
||||
}
|
||||
setAuthorityOptions(
|
||||
item.children,
|
||||
option.children,
|
||||
disabled || item.authorityId === form.value.authorityId
|
||||
)
|
||||
optionsData.push(option)
|
||||
} else {
|
||||
const option = {
|
||||
authorityId: item.authorityId,
|
||||
authorityName: item.authorityName,
|
||||
disabled: disabled || item.authorityId === form.value.authorityId
|
||||
}
|
||||
optionsData.push(option)
|
||||
}
|
||||
})
|
||||
}
|
||||
// 增加角色
|
||||
const addAuthority = (parentId) => {
|
||||
initForm()
|
||||
dialogTitle.value = '新增角色'
|
||||
dialogType.value = 'add'
|
||||
form.value.parentId = parentId
|
||||
setOptions()
|
||||
dialogFormVisible.value = true
|
||||
}
|
||||
// 编辑角色
|
||||
const editAuthority = (row) => {
|
||||
setOptions()
|
||||
dialogTitle.value = '编辑角色'
|
||||
dialogType.value = 'edit'
|
||||
for (const key in form.value) {
|
||||
form.value[key] = row[key]
|
||||
}
|
||||
setOptions()
|
||||
dialogFormVisible.value = true
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<script>
|
||||
|
||||
export default {
|
||||
name: 'Authority'
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.authority {
|
||||
.el-input-number {
|
||||
margin-left: 15px;
|
||||
span {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
.tree-content{
|
||||
overflow: auto;
|
||||
height: calc(100vh - 100px);
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.auth-drawer{
|
||||
.el-drawer__body{
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
</style>
|
139
src/view/superAdmin/authority/components/apis.vue
Normal file
139
src/view/superAdmin/authority/components/apis.vue
Normal file
@@ -0,0 +1,139 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="clearfix sticky-button">
|
||||
<el-input v-model="filterText" class="fitler" placeholder="筛选" />
|
||||
<el-button class="fl-right" size="small" type="primary" @click="authApiEnter">确 定</el-button>
|
||||
</div>
|
||||
<div class="tree-content">
|
||||
<el-tree
|
||||
ref="apiTree"
|
||||
:data="apiTreeData"
|
||||
:default-checked-keys="apiTreeIds"
|
||||
:props="apiDefaultProps"
|
||||
default-expand-all
|
||||
highlight-current
|
||||
node-key="onlyId"
|
||||
show-checkbox
|
||||
:filter-node-method="filterNode"
|
||||
@check="nodeChange"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
name: 'Apis',
|
||||
}
|
||||
</script>
|
||||
|
||||
<script setup>
|
||||
import { getAllApis } from '@/api/api'
|
||||
import { UpdateCasbin, getPolicyPathByAuthorityId } from '@/api/casbin'
|
||||
import { ref, watch } from 'vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
const props = defineProps({
|
||||
row: {
|
||||
default: function() {
|
||||
return {}
|
||||
},
|
||||
type: Object
|
||||
}
|
||||
})
|
||||
|
||||
const apiDefaultProps = ref({
|
||||
children: 'children',
|
||||
label: 'description'
|
||||
})
|
||||
const filterText = ref('')
|
||||
const apiTreeData = ref([])
|
||||
const apiTreeIds = ref([])
|
||||
const activeUserId = ref('')
|
||||
const init = async() => {
|
||||
const res2 = await getAllApis()
|
||||
const apis = res2.data.apis
|
||||
|
||||
apiTreeData.value = buildApiTree(apis)
|
||||
const res = await getPolicyPathByAuthorityId({
|
||||
authorityId: props.row.authorityId
|
||||
})
|
||||
activeUserId.value = props.row.authorityId
|
||||
apiTreeIds.value = []
|
||||
res.data.paths && res.data.paths.forEach(item => {
|
||||
apiTreeIds.value.push('p:' + item.path + 'm:' + item.method)
|
||||
})
|
||||
}
|
||||
|
||||
init()
|
||||
|
||||
const needConfirm = ref(false)
|
||||
const nodeChange = () => {
|
||||
needConfirm.value = true
|
||||
}
|
||||
// 暴露给外层使用的切换拦截统一方法
|
||||
const enterAndNext = () => {
|
||||
authApiEnter()
|
||||
}
|
||||
|
||||
// 创建api树方法
|
||||
const buildApiTree = (apis) => {
|
||||
const apiObj = {}
|
||||
apis &&
|
||||
apis.forEach(item => {
|
||||
item.onlyId = 'p:' + item.path + 'm:' + item.method
|
||||
if (Object.prototype.hasOwnProperty.call(apiObj, item.apiGroup)) {
|
||||
apiObj[item.apiGroup].push(item)
|
||||
} else {
|
||||
Object.assign(apiObj, { [item.apiGroup]: [item] })
|
||||
}
|
||||
})
|
||||
const apiTree = []
|
||||
for (const key in apiObj) {
|
||||
const treeNode = {
|
||||
ID: key,
|
||||
description: key + '组',
|
||||
children: apiObj[key]
|
||||
}
|
||||
apiTree.push(treeNode)
|
||||
}
|
||||
return apiTree
|
||||
}
|
||||
|
||||
// 关联关系确定
|
||||
const apiTree = ref(null)
|
||||
const authApiEnter = async() => {
|
||||
const checkArr = apiTree.value.getCheckedNodes(true)
|
||||
var casbinInfos = []
|
||||
checkArr && checkArr.forEach(item => {
|
||||
var casbinInfo = {
|
||||
path: item.path,
|
||||
method: item.method
|
||||
}
|
||||
casbinInfos.push(casbinInfo)
|
||||
})
|
||||
const res = await UpdateCasbin({
|
||||
authorityId: activeUserId.value,
|
||||
casbinInfos
|
||||
})
|
||||
if (res.code === 0) {
|
||||
ElMessage({ type: 'success', message: 'api设置成功' })
|
||||
}
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
needConfirm,
|
||||
enterAndNext
|
||||
})
|
||||
|
||||
const filterNode = (value, data) => {
|
||||
if (!value) return true
|
||||
return data.description.indexOf(value) !== -1
|
||||
}
|
||||
watch(filterText, (val) => {
|
||||
apiTree.value.filter(val)
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "@/style/button.scss";
|
||||
</style>
|
118
src/view/superAdmin/authority/components/datas.vue
Normal file
118
src/view/superAdmin/authority/components/datas.vue
Normal file
@@ -0,0 +1,118 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="clearfix sticky-button" style="margin: 18px">
|
||||
<el-button class="fl-right" size="small" type="primary" @click="authDataEnter">确 定</el-button>
|
||||
<el-button class="fl-left" size="small" type="primary" @click="all">全选</el-button>
|
||||
<el-button class="fl-left" size="small" type="primary" @click="self">本角色</el-button>
|
||||
<el-button class="fl-left" size="small" type="primary" @click="selfAndChildren">本角色及子角色</el-button>
|
||||
</div>
|
||||
<div class="tree-content">
|
||||
<el-checkbox-group v-model="dataAuthorityId" @change="selectAuthority">
|
||||
<el-checkbox v-for="(item,key) in authoritys" :key="key" :label="item">{{ item.authorityName }}</el-checkbox>
|
||||
</el-checkbox-group>
|
||||
</div>
|
||||
<warning-bar title="此功能仅用于创建角色和角色的many2many关系表,具体使用还须自己结合表实现业务,详情参考示例代码(客户示例)" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'Datas'
|
||||
}
|
||||
</script>
|
||||
|
||||
<script setup>
|
||||
import { setDataAuthority } from '@/api/authority'
|
||||
import WarningBar from '@/components/warningBar/warningBar.vue'
|
||||
import { ref } from 'vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
const props = defineProps({
|
||||
row: {
|
||||
default: function() {
|
||||
return {}
|
||||
},
|
||||
type: Object
|
||||
},
|
||||
authority: {
|
||||
default: function() {
|
||||
return []
|
||||
},
|
||||
type: Array
|
||||
}
|
||||
})
|
||||
|
||||
const authoritys = ref([])
|
||||
const needConfirm = ref(false)
|
||||
// 平铺角色
|
||||
const roundAuthority = (authoritysData) => {
|
||||
authoritysData && authoritysData.forEach(item => {
|
||||
const obj = {}
|
||||
obj.authorityId = item.authorityId
|
||||
obj.authorityName = item.authorityName
|
||||
authoritys.value.push(obj)
|
||||
if (item.children && item.children.length) {
|
||||
roundAuthority(item.children)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const dataAuthorityId = ref([])
|
||||
const init = () => {
|
||||
roundAuthority(props.authority)
|
||||
props.row.dataAuthorityId && props.row.dataAuthorityId.forEach(item => {
|
||||
const obj = authoritys.value && authoritys.value.filter(au => au.authorityId === item.authorityId) && authoritys.value.filter(au => au.authorityId === item.authorityId)[0]
|
||||
dataAuthorityId.value.push(obj)
|
||||
})
|
||||
}
|
||||
|
||||
init()
|
||||
|
||||
// 暴露给外层使用的切换拦截统一方法
|
||||
const enterAndNext = () => {
|
||||
authDataEnter()
|
||||
}
|
||||
|
||||
const emit = defineEmits(['changeRow'])
|
||||
const all = () => {
|
||||
dataAuthorityId.value = [...authoritys.value]
|
||||
emit('changeRow', 'dataAuthorityId', dataAuthorityId.value)
|
||||
needConfirm.value = true
|
||||
}
|
||||
const self = () => {
|
||||
dataAuthorityId.value = authoritys.value.filter(item => item.authorityId === props.row.authorityId)
|
||||
emit('changeRow', 'dataAuthorityId', dataAuthorityId.value)
|
||||
needConfirm.value = true
|
||||
}
|
||||
const selfAndChildren = () => {
|
||||
const arrBox = []
|
||||
getChildrenId(props.row, arrBox)
|
||||
dataAuthorityId.value = authoritys.value.filter(item => arrBox.indexOf(item.authorityId) > -1)
|
||||
emit('changeRow', 'dataAuthorityId', dataAuthorityId.value)
|
||||
needConfirm.value = true
|
||||
}
|
||||
const getChildrenId = (row, arrBox) => {
|
||||
arrBox.push(row.authorityId)
|
||||
row.children && row.children.forEach(item => {
|
||||
getChildrenId(item, arrBox)
|
||||
})
|
||||
}
|
||||
// 提交
|
||||
const authDataEnter = async() => {
|
||||
const res = await setDataAuthority(props.row)
|
||||
if (res.code === 0) {
|
||||
ElMessage({ type: 'success', message: '资源设置成功' })
|
||||
}
|
||||
}
|
||||
|
||||
// 选择
|
||||
const selectAuthority = () => {
|
||||
emit('changeRow', 'dataAuthorityId', dataAuthorityId.value)
|
||||
needConfirm.value = true
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
enterAndNext,
|
||||
needConfirm
|
||||
})
|
||||
|
||||
</script>
|
225
src/view/superAdmin/authority/components/menus.vue
Normal file
225
src/view/superAdmin/authority/components/menus.vue
Normal file
@@ -0,0 +1,225 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="clearfix sticky-button">
|
||||
<el-input v-model="filterText" class="fitler" placeholder="筛选" />
|
||||
<el-button class="fl-right" size="small" type="primary" @click="relation">确 定</el-button>
|
||||
</div>
|
||||
<div class="tree-content">
|
||||
<el-tree
|
||||
ref="menuTree"
|
||||
:data="menuTreeData"
|
||||
:default-checked-keys="menuTreeIds"
|
||||
:props="menuDefaultProps"
|
||||
default-expand-all
|
||||
highlight-current
|
||||
node-key="ID"
|
||||
show-checkbox
|
||||
:filter-node-method="filterNode"
|
||||
@check="nodeChange"
|
||||
>
|
||||
<template #default="{ node , data }">
|
||||
<span class="custom-tree-node">
|
||||
<span>{{ node.label }}</span>
|
||||
<span>
|
||||
<el-button
|
||||
type="primary"
|
||||
link
|
||||
size="small"
|
||||
:style="{color:row.defaultRouter === data.name?'#E6A23C':'#85ce61'}"
|
||||
:disabled="!node.checked"
|
||||
@click="() => setDefault(data)"
|
||||
>
|
||||
{{ row.defaultRouter === data.name?"首页":"设为首页" }}
|
||||
</el-button>
|
||||
</span>
|
||||
<span v-if="data.menuBtn.length">
|
||||
<el-button
|
||||
type="primary"
|
||||
link
|
||||
size="small"
|
||||
@click="() => OpenBtn(data)"
|
||||
>
|
||||
分配按钮
|
||||
</el-button>
|
||||
</span>
|
||||
</span>
|
||||
</template>
|
||||
</el-tree>
|
||||
</div>
|
||||
<el-dialog v-model="btnVisible" title="分配按钮" destroy-on-close>
|
||||
<el-table
|
||||
ref="btnTableRef"
|
||||
:data="btnData"
|
||||
row-key="ID"
|
||||
@selection-change="handleSelectionChange"
|
||||
>
|
||||
<el-table-column type="selection" width="55" />
|
||||
<el-table-column label="按钮名称" prop="name" />
|
||||
<el-table-column label="按钮备注" prop="desc" />
|
||||
</el-table>
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button size="small" @click="closeDialog">取 消</el-button>
|
||||
<el-button size="small" type="primary" @click="enterDialog">确 定</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { getBaseMenuTree, getMenuAuthority, addMenuAuthority } from '@/api/menu'
|
||||
import {
|
||||
updateAuthority
|
||||
} from '@/api/authority'
|
||||
import { getAuthorityBtnApi, setAuthorityBtnApi } from '@/api/authorityBtn'
|
||||
import { nextTick, ref, watch } from 'vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
|
||||
const props = defineProps({
|
||||
row: {
|
||||
default: function() {
|
||||
return {}
|
||||
},
|
||||
type: Object
|
||||
}
|
||||
})
|
||||
|
||||
const emit = defineEmits(['changeRow'])
|
||||
const filterText = ref('')
|
||||
const menuTreeData = ref([])
|
||||
const menuTreeIds = ref([])
|
||||
const needConfirm = ref(false)
|
||||
const menuDefaultProps = ref({
|
||||
children: 'children',
|
||||
label: function(data) {
|
||||
return data.meta.title
|
||||
}
|
||||
})
|
||||
|
||||
const init = async() => {
|
||||
// 获取所有菜单树
|
||||
const res = await getBaseMenuTree()
|
||||
menuTreeData.value = res.data.menus
|
||||
const res1 = await getMenuAuthority({ authorityId: props.row.authorityId })
|
||||
const menus = res1.data.menus
|
||||
const arr = []
|
||||
menus.forEach(item => {
|
||||
// 防止直接选中父级造成全选
|
||||
if (!menus.some(same => same.parentId === item.menuId)) {
|
||||
arr.push(Number(item.menuId))
|
||||
}
|
||||
})
|
||||
menuTreeIds.value = arr
|
||||
}
|
||||
|
||||
init()
|
||||
|
||||
const setDefault = async(data) => {
|
||||
const res = await updateAuthority({ authorityId: props.row.authorityId, AuthorityName: props.row.authorityName, parentId: props.row.parentId, defaultRouter: data.name })
|
||||
if (res.code === 0) {
|
||||
ElMessage({ type: 'success', message: '设置成功' })
|
||||
emit('changeRow', 'defaultRouter', res.data.authority.defaultRouter)
|
||||
}
|
||||
}
|
||||
const nodeChange = () => {
|
||||
needConfirm.value = true
|
||||
}
|
||||
// 暴露给外层使用的切换拦截统一方法
|
||||
const enterAndNext = () => {
|
||||
relation()
|
||||
}
|
||||
// 关联树 确认方法
|
||||
const menuTree = ref(null)
|
||||
const relation = async() => {
|
||||
const checkArr = menuTree.value.getCheckedNodes(false, true)
|
||||
const res = await addMenuAuthority({
|
||||
menus: checkArr,
|
||||
authorityId: props.row.authorityId
|
||||
})
|
||||
if (res.code === 0) {
|
||||
ElMessage({
|
||||
type: 'success',
|
||||
message: '菜单设置成功!'
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
defineExpose({ enterAndNext, needConfirm })
|
||||
|
||||
const btnVisible = ref(false)
|
||||
|
||||
const btnData = ref([])
|
||||
const multipleSelection = ref([])
|
||||
const btnTableRef = ref()
|
||||
let menuID = ''
|
||||
const OpenBtn = async(data) => {
|
||||
menuID = data.ID
|
||||
const res = await getAuthorityBtnApi({ menuID: menuID, authorityId: props.row.authorityId })
|
||||
if (res.code === 0) {
|
||||
openDialog(data)
|
||||
await nextTick()
|
||||
if (res.data.selected) {
|
||||
res.data.selected.forEach(id => {
|
||||
btnData.value.some(item => {
|
||||
if (item.ID === id) {
|
||||
btnTableRef.value.toggleRowSelection(item, true)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const handleSelectionChange = (val) => {
|
||||
multipleSelection.value = val
|
||||
}
|
||||
|
||||
const openDialog = (data) => {
|
||||
btnVisible.value = true
|
||||
btnData.value = data.menuBtn
|
||||
}
|
||||
|
||||
const closeDialog = () => {
|
||||
btnVisible.value = false
|
||||
}
|
||||
const enterDialog = async() => {
|
||||
const selected = multipleSelection.value.map(item => item.ID)
|
||||
const res = await setAuthorityBtnApi({
|
||||
menuID,
|
||||
selected,
|
||||
authorityId: props.row.authorityId
|
||||
})
|
||||
if (res.code === 0) {
|
||||
ElMessage({ type: 'success', message: '设置成功' })
|
||||
btnVisible.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const filterNode = (value, data) => {
|
||||
if (!value) return true
|
||||
// console.log(data.mate.title)
|
||||
return data.meta.title.indexOf(value) !== -1
|
||||
}
|
||||
|
||||
watch(filterText, (val) => {
|
||||
menuTree.value.filter(val)
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<script>
|
||||
|
||||
export default {
|
||||
name: 'Menus'
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scope>
|
||||
@import "@/style/button.scss";
|
||||
.custom-tree-node{
|
||||
span+span{
|
||||
margin-left: 12px;
|
||||
}
|
||||
}
|
||||
</style>
|
375
src/view/superAdmin/dictionary/sysDictionary.vue
Normal file
375
src/view/superAdmin/dictionary/sysDictionary.vue
Normal file
@@ -0,0 +1,375 @@
|
||||
<template>
|
||||
<div>
|
||||
<warning-bar
|
||||
title="获取字典且缓存方法已在前端utils/dictionary 已经封装完成 不必自己书写 使用方法查看文件内注释"
|
||||
/>
|
||||
<div class="gva-search-box">
|
||||
<el-form :inline="true" :model="searchInfo">
|
||||
<el-form-item label="字典名(中)">
|
||||
<el-input v-model="searchInfo.name" placeholder="搜索条件" />
|
||||
</el-form-item>
|
||||
<el-form-item label="字典名(英)">
|
||||
<el-input v-model="searchInfo.type" placeholder="搜索条件" />
|
||||
</el-form-item>
|
||||
<el-form-item label="状态" prop="status">
|
||||
<el-select v-model="searchInfo.status" clear placeholder="请选择">
|
||||
<el-option key="true" label="是" value="true" />
|
||||
<el-option key="false" label="否" value="false" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="描述">
|
||||
<el-input v-model="searchInfo.desc" placeholder="搜索条件" />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button
|
||||
size="small"
|
||||
type="primary"
|
||||
icon="search"
|
||||
@click="onSubmit"
|
||||
>查询</el-button>
|
||||
<el-button
|
||||
size="small"
|
||||
icon="refresh"
|
||||
@click="onReset"
|
||||
>重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
<div class="gva-table-box">
|
||||
<div class="gva-btn-list">
|
||||
<el-button
|
||||
size="small"
|
||||
type="primary"
|
||||
icon="plus"
|
||||
@click="openDialog"
|
||||
>新增</el-button>
|
||||
</div>
|
||||
<el-table
|
||||
ref="multipleTable"
|
||||
:data="tableData"
|
||||
style="width: 100%"
|
||||
tooltip-effect="dark"
|
||||
row-key="ID"
|
||||
>
|
||||
<el-table-column type="selection" width="55" />
|
||||
<el-table-column align="left" label="日期" width="180">
|
||||
<template #default="scope">{{
|
||||
formatDate(scope.row.CreatedAt)
|
||||
}}</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column
|
||||
align="left"
|
||||
label="字典名(中)"
|
||||
prop="name"
|
||||
width="160"
|
||||
/>
|
||||
|
||||
<el-table-column
|
||||
align="left"
|
||||
label="字典名(英)"
|
||||
prop="type"
|
||||
width="120"
|
||||
/>
|
||||
|
||||
<el-table-column align="left" label="状态" prop="status" width="120">
|
||||
<template #default="scope">{{
|
||||
formatBoolean(scope.row.status)
|
||||
}}</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column align="left" label="描述" prop="desc" width="280" />
|
||||
|
||||
<el-table-column align="left" label="按钮组">
|
||||
<template #default="scope">
|
||||
<el-button
|
||||
size="small"
|
||||
icon="document"
|
||||
type="primary"
|
||||
link
|
||||
@click="toDetail(scope.row)"
|
||||
>详情</el-button>
|
||||
<el-button
|
||||
size="small"
|
||||
icon="edit"
|
||||
type="primary"
|
||||
link
|
||||
@click="updateSysDictionaryFunc(scope.row)"
|
||||
>变更</el-button>
|
||||
<el-popover
|
||||
v-model="scope.row.visible"
|
||||
placement="top"
|
||||
width="160"
|
||||
>
|
||||
<p>确定要删除吗?</p>
|
||||
<div style="text-align: right; margin-top: 8px">
|
||||
<el-button
|
||||
size="small"
|
||||
type="primary"
|
||||
link
|
||||
@click="scope.row.visible = false"
|
||||
>取消</el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
size="small"
|
||||
@click="deleteSysDictionaryFunc(scope.row)"
|
||||
>确定</el-button>
|
||||
</div>
|
||||
<template #reference>
|
||||
<el-button
|
||||
type="primary"
|
||||
link
|
||||
icon="delete"
|
||||
size="small"
|
||||
style="margin-left: 10px"
|
||||
@click="scope.row.visible = true"
|
||||
>删除</el-button>
|
||||
</template>
|
||||
</el-popover>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<div class="gva-pagination">
|
||||
<el-pagination
|
||||
:current-page="page"
|
||||
:page-size="pageSize"
|
||||
:page-sizes="[10, 30, 50, 100]"
|
||||
:total="total"
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
@current-change="handleCurrentChange"
|
||||
@size-change="handleSizeChange"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<el-dialog
|
||||
v-model="dialogFormVisible"
|
||||
:before-close="closeDialog"
|
||||
title="弹窗操作"
|
||||
>
|
||||
<el-form
|
||||
ref="dialogForm"
|
||||
:model="formData"
|
||||
:rules="rules"
|
||||
size="medium"
|
||||
label-width="110px"
|
||||
>
|
||||
<el-form-item label="字典名(中)" prop="name">
|
||||
<el-input
|
||||
v-model="formData.name"
|
||||
placeholder="请输入字典名(中)"
|
||||
clearable
|
||||
:style="{ width: '100%' }"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="字典名(英)" prop="type">
|
||||
<el-input
|
||||
v-model="formData.type"
|
||||
placeholder="请输入字典名(英)"
|
||||
clearable
|
||||
:style="{ width: '100%' }"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="状态" prop="status" required>
|
||||
<el-switch
|
||||
v-model="formData.status"
|
||||
active-text="开启"
|
||||
inactive-text="停用"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="描述" prop="desc">
|
||||
<el-input
|
||||
v-model="formData.desc"
|
||||
placeholder="请输入描述"
|
||||
clearable
|
||||
:style="{ width: '100%' }"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button size="small" @click="closeDialog">取 消</el-button>
|
||||
<el-button
|
||||
size="small"
|
||||
type="primary"
|
||||
@click="enterDialog"
|
||||
>确 定</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'SysDictionary',
|
||||
}
|
||||
</script>
|
||||
|
||||
<script setup>
|
||||
import {
|
||||
createSysDictionary,
|
||||
deleteSysDictionary,
|
||||
updateSysDictionary,
|
||||
findSysDictionary,
|
||||
getSysDictionaryList,
|
||||
} from '@/api/sysDictionary' // 此处请自行替换地址
|
||||
import WarningBar from '@/components/warningBar/warningBar.vue'
|
||||
import { ref } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { formatBoolean, formatDate } from '@/utils/format'
|
||||
|
||||
const router = useRouter()
|
||||
|
||||
const formData = ref({
|
||||
name: null,
|
||||
type: null,
|
||||
status: true,
|
||||
desc: null,
|
||||
})
|
||||
const rules = ref({
|
||||
name: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入字典名(中)',
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
type: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入字典名(英)',
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
desc: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入描述',
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
const page = ref(1)
|
||||
const total = ref(0)
|
||||
const pageSize = ref(10)
|
||||
const tableData = ref([])
|
||||
const searchInfo = ref({})
|
||||
|
||||
const onReset = () => {
|
||||
searchInfo.value = {}
|
||||
}
|
||||
|
||||
// 条件搜索前端看此方法
|
||||
const onSubmit = () => {
|
||||
page.value = 1
|
||||
pageSize.value = 10
|
||||
if (searchInfo.value.status === '') {
|
||||
searchInfo.value.status = null
|
||||
}
|
||||
getTableData()
|
||||
}
|
||||
|
||||
// 分页
|
||||
const handleSizeChange = (val) => {
|
||||
pageSize.value = val
|
||||
getTableData()
|
||||
}
|
||||
|
||||
const handleCurrentChange = (val) => {
|
||||
page.value = val
|
||||
getTableData()
|
||||
}
|
||||
|
||||
// 查询
|
||||
const getTableData = async() => {
|
||||
const table = await getSysDictionaryList({
|
||||
page: page.value,
|
||||
pageSize: pageSize.value,
|
||||
...searchInfo.value,
|
||||
})
|
||||
if (table.code === 0) {
|
||||
tableData.value = table.data.list
|
||||
total.value = table.data.total
|
||||
page.value = table.data.page
|
||||
pageSize.value = table.data.pageSize
|
||||
}
|
||||
}
|
||||
|
||||
getTableData()
|
||||
|
||||
const toDetail = (row) => {
|
||||
router.push({
|
||||
name: 'dictionaryDetail',
|
||||
params: {
|
||||
id: row.ID,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
const dialogFormVisible = ref(false)
|
||||
const type = ref('')
|
||||
const updateSysDictionaryFunc = async(row) => {
|
||||
const res = await findSysDictionary({ ID: row.ID, status: row.status })
|
||||
type.value = 'update'
|
||||
if (res.code === 0) {
|
||||
formData.value = res.data.resysDictionary
|
||||
dialogFormVisible.value = true
|
||||
}
|
||||
}
|
||||
const closeDialog = () => {
|
||||
dialogFormVisible.value = false
|
||||
formData.value = {
|
||||
name: null,
|
||||
type: null,
|
||||
status: true,
|
||||
desc: null,
|
||||
}
|
||||
}
|
||||
const deleteSysDictionaryFunc = async(row) => {
|
||||
row.visible = false
|
||||
const res = await deleteSysDictionary({ ID: row.ID })
|
||||
if (res.code === 0) {
|
||||
ElMessage({
|
||||
type: 'success',
|
||||
message: '删除成功',
|
||||
})
|
||||
if (tableData.value.length === 1 && page.value > 1) {
|
||||
page.value--
|
||||
}
|
||||
getTableData()
|
||||
}
|
||||
}
|
||||
|
||||
const dialogForm = ref(null)
|
||||
const enterDialog = async() => {
|
||||
dialogForm.value.validate(async(valid) => {
|
||||
if (!valid) return
|
||||
let res
|
||||
switch (type.value) {
|
||||
case 'create':
|
||||
res = await createSysDictionary(formData.value)
|
||||
break
|
||||
case 'update':
|
||||
res = await updateSysDictionary(formData.value)
|
||||
break
|
||||
default:
|
||||
res = await createSysDictionary(formData.value)
|
||||
break
|
||||
}
|
||||
if (res.code === 0) {
|
||||
ElMessage.success('操作成功')
|
||||
closeDialog()
|
||||
getTableData()
|
||||
}
|
||||
})
|
||||
}
|
||||
const openDialog = () => {
|
||||
type.value = 'create'
|
||||
dialogFormVisible.value = true
|
||||
}
|
||||
</script>
|
||||
|
||||
<style></style>
|
291
src/view/superAdmin/dictionary/sysDictionaryDetail.vue
Normal file
291
src/view/superAdmin/dictionary/sysDictionaryDetail.vue
Normal file
@@ -0,0 +1,291 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="gva-search-box">
|
||||
<el-form :inline="true" :model="searchInfo">
|
||||
<el-form-item label="展示值">
|
||||
<el-input v-model="searchInfo.label" placeholder="搜索条件" />
|
||||
</el-form-item>
|
||||
<el-form-item label="字典值">
|
||||
<el-input v-model="searchInfo.value" placeholder="搜索条件" />
|
||||
</el-form-item>
|
||||
<el-form-item label="启用状态" prop="status">
|
||||
<el-select v-model="searchInfo.status" placeholder="请选择">
|
||||
<el-option key="true" label="是" value="true" />
|
||||
<el-option key="false" label="否" value="false" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button size="small" type="primary" icon="search" @click="onSubmit">查询</el-button>
|
||||
<el-button size="small" icon="refresh" @click="onReset">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
<div class="gva-table-box">
|
||||
<div class="gva-btn-list">
|
||||
<el-button size="small" type="primary" icon="plus" @click="openDialog">新增字典项</el-button>
|
||||
</div>
|
||||
<el-table
|
||||
ref="multipleTable"
|
||||
:data="tableData"
|
||||
style="width: 100%"
|
||||
tooltip-effect="dark"
|
||||
row-key="ID"
|
||||
>
|
||||
<el-table-column type="selection" width="55" />
|
||||
<el-table-column align="left" label="日期" width="180">
|
||||
<template #default="scope">{{ formatDate(scope.row.CreatedAt) }}</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column align="left" label="展示值" prop="label" width="120" />
|
||||
|
||||
<el-table-column align="left" label="字典值" prop="value" width="120" />
|
||||
|
||||
<el-table-column align="left" label="启用状态" prop="status" width="120">
|
||||
<template #default="scope">{{ formatBoolean(scope.row.status) }}</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column align="left" label="排序标记" prop="sort" width="120" />
|
||||
|
||||
<el-table-column align="left" label="按钮组">
|
||||
<template #default="scope">
|
||||
<el-button size="small" type="primary" link icon="edit" @click="updateSysDictionaryDetailFunc(scope.row)">变更</el-button>
|
||||
<el-popover v-model="scope.row.visible" placement="top" width="160">
|
||||
<p>确定要删除吗?</p>
|
||||
<div style="text-align: right; margin-top: 8px;">
|
||||
<el-button size="small" type="primary" link @click="scope.row.visible = false">取消</el-button>
|
||||
<el-button type="primary" size="small" @click="deleteSysDictionaryDetailFunc(scope.row)">确定</el-button>
|
||||
</div>
|
||||
<template #reference>
|
||||
<el-button type="primary" link icon="delete" size="small" @click="scope.row.visible = true">删除</el-button>
|
||||
</template>
|
||||
</el-popover>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<div class="gva-pagination">
|
||||
<el-pagination
|
||||
:current-page="page"
|
||||
:page-size="pageSize"
|
||||
:page-sizes="[10, 30, 50, 100]"
|
||||
:total="total"
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
@current-change="handleCurrentChange"
|
||||
@size-change="handleSizeChange"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<el-dialog v-model="dialogFormVisible" :before-close="closeDialog" title="弹窗操作">
|
||||
<el-form ref="dialogForm" :model="formData" :rules="rules" size="medium" label-width="110px">
|
||||
<el-form-item label="展示值" prop="label">
|
||||
<el-input
|
||||
v-model="formData.label"
|
||||
placeholder="请输入展示值"
|
||||
clearable
|
||||
:style="{width: '100%'}"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="字典值" prop="value">
|
||||
<el-input-number
|
||||
v-model.number="formData.value"
|
||||
step-strictly
|
||||
:step="1"
|
||||
placeholder="请输入字典值"
|
||||
clearable
|
||||
:style="{width: '100%'}"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="启用状态" prop="status" required>
|
||||
<el-switch v-model="formData.status" active-text="开启" inactive-text="停用" />
|
||||
</el-form-item>
|
||||
<el-form-item label="排序标记" prop="sort">
|
||||
<el-input-number v-model.number="formData.sort" placeholder="排序标记" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button size="small" @click="closeDialog">取 消</el-button>
|
||||
<el-button size="small" type="primary" @click="enterDialog">确 定</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'SysDictionaryDetail'
|
||||
}
|
||||
</script>
|
||||
|
||||
<script setup>
|
||||
import {
|
||||
createSysDictionaryDetail,
|
||||
deleteSysDictionaryDetail,
|
||||
updateSysDictionaryDetail,
|
||||
findSysDictionaryDetail,
|
||||
getSysDictionaryDetailList
|
||||
} from '@/api/sysDictionaryDetail' // 此处请自行替换地址
|
||||
import { ref } from 'vue'
|
||||
import { useRoute, onBeforeRouteUpdate } from 'vue-router'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { formatBoolean, formatDate } from '@/utils/format'
|
||||
const route = useRoute()
|
||||
|
||||
onBeforeRouteUpdate((to, form) => {
|
||||
if (to.name === 'dictionaryDetail') {
|
||||
searchInfo.value.sysDictionaryID = to.params.id
|
||||
getTableData()
|
||||
}
|
||||
})
|
||||
|
||||
const formData = ref({
|
||||
label: null,
|
||||
value: null,
|
||||
status: true,
|
||||
sort: null
|
||||
})
|
||||
const rules = ref({
|
||||
label: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入展示值',
|
||||
trigger: 'blur'
|
||||
}
|
||||
],
|
||||
value: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入字典值',
|
||||
trigger: 'blur'
|
||||
}
|
||||
],
|
||||
sort: [
|
||||
{
|
||||
required: true,
|
||||
message: '排序标记',
|
||||
trigger: 'blur'
|
||||
}
|
||||
]
|
||||
})
|
||||
|
||||
const page = ref(1)
|
||||
const total = ref(0)
|
||||
const pageSize = ref(10)
|
||||
const tableData = ref([])
|
||||
const searchInfo = ref({ sysDictionaryID: Number(route.params.id) })
|
||||
const onReset = () => {
|
||||
searchInfo.value = { sysDictionaryID: Number(route.params.id) }
|
||||
}
|
||||
|
||||
// 条件搜索前端看此方法
|
||||
const onSubmit = () => {
|
||||
page.value = 1
|
||||
pageSize.value = 10
|
||||
if (searchInfo.value.status === '') {
|
||||
searchInfo.value.status = null
|
||||
}
|
||||
getTableData()
|
||||
}
|
||||
|
||||
// 分页
|
||||
const handleSizeChange = (val) => {
|
||||
pageSize.value = val
|
||||
getTableData()
|
||||
}
|
||||
|
||||
const handleCurrentChange = (val) => {
|
||||
page.value = val
|
||||
getTableData()
|
||||
}
|
||||
|
||||
// 查询
|
||||
const getTableData = async() => {
|
||||
const table = await getSysDictionaryDetailList({
|
||||
page: page.value,
|
||||
pageSize: pageSize.value,
|
||||
...searchInfo.value,
|
||||
})
|
||||
if (table.code === 0) {
|
||||
tableData.value = table.data.list
|
||||
total.value = table.data.total
|
||||
page.value = table.data.page
|
||||
pageSize.value = table.data.pageSize
|
||||
}
|
||||
}
|
||||
|
||||
getTableData()
|
||||
|
||||
const type = ref('')
|
||||
const dialogFormVisible = ref(false)
|
||||
const updateSysDictionaryDetailFunc = async(row) => {
|
||||
const res = await findSysDictionaryDetail({ ID: row.ID })
|
||||
type.value = 'update'
|
||||
if (res.code === 0) {
|
||||
formData.value = res.data.reSysDictionaryDetail
|
||||
dialogFormVisible.value = true
|
||||
}
|
||||
}
|
||||
|
||||
const closeDialog = () => {
|
||||
dialogFormVisible.value = false
|
||||
formData.value = {
|
||||
label: null,
|
||||
value: null,
|
||||
status: true,
|
||||
sort: null,
|
||||
sysDictionaryID: ''
|
||||
}
|
||||
}
|
||||
const deleteSysDictionaryDetailFunc = async(row) => {
|
||||
row.visible = false
|
||||
const res = await deleteSysDictionaryDetail({ ID: row.ID })
|
||||
if (res.code === 0) {
|
||||
ElMessage({
|
||||
type: 'success',
|
||||
message: '删除成功'
|
||||
})
|
||||
if (tableData.value.length === 1 && page.value > 1) {
|
||||
page.value--
|
||||
}
|
||||
getTableData()
|
||||
}
|
||||
}
|
||||
|
||||
const dialogForm = ref(null)
|
||||
const enterDialog = async() => {
|
||||
formData.value.sysDictionaryID = Number(route.params.id)
|
||||
dialogForm.value.validate(async valid => {
|
||||
if (!valid) return
|
||||
let res
|
||||
switch (type.value) {
|
||||
case 'create':
|
||||
res = await createSysDictionaryDetail(formData.value)
|
||||
break
|
||||
case 'update':
|
||||
res = await updateSysDictionaryDetail(formData.value)
|
||||
break
|
||||
default:
|
||||
res = await createSysDictionaryDetail(formData.value)
|
||||
break
|
||||
}
|
||||
if (res.code === 0) {
|
||||
ElMessage({
|
||||
type: 'success',
|
||||
message: '创建/更改成功'
|
||||
})
|
||||
closeDialog()
|
||||
getTableData()
|
||||
}
|
||||
})
|
||||
}
|
||||
const openDialog = () => {
|
||||
type.value = 'create'
|
||||
dialogFormVisible.value = true
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style>
|
||||
</style>
|
21
src/view/superAdmin/index.vue
Normal file
21
src/view/superAdmin/index.vue
Normal file
@@ -0,0 +1,21 @@
|
||||
<template>
|
||||
<div>
|
||||
<router-view v-slot="{ Component }">
|
||||
<transition mode="out-in" name="el-fade-in-linear">
|
||||
<keep-alive :include="routerStore.keepAliveRouters">
|
||||
<component :is="Component" />
|
||||
</keep-alive>
|
||||
</transition>
|
||||
</router-view>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'SuperAdmin'
|
||||
}
|
||||
</script>
|
||||
<script setup>
|
||||
import { useRouterStore } from '@/pinia/modules/router'
|
||||
const routerStore = useRouterStore()
|
||||
</script>
|
1190
src/view/superAdmin/menu/icon.vue
Normal file
1190
src/view/superAdmin/menu/icon.vue
Normal file
File diff suppressed because it is too large
Load Diff
529
src/view/superAdmin/menu/menu.vue
Normal file
529
src/view/superAdmin/menu/menu.vue
Normal file
@@ -0,0 +1,529 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="gva-table-box">
|
||||
<div class="gva-btn-list">
|
||||
<el-button size="small" type="primary" icon="plus" @click="addMenu('0')">新增根菜单</el-button>
|
||||
</div>
|
||||
|
||||
<!-- 由于此处菜单跟左侧列表一一对应所以不需要分页 pageSize默认999 -->
|
||||
<el-table :data="tableData" row-key="ID">
|
||||
<el-table-column align="left" label="ID" min-width="100" prop="ID" />
|
||||
<el-table-column align="left" label="展示名称" min-width="120" prop="authorityName">
|
||||
<template #default="scope">
|
||||
<span>{{ scope.row.meta.title }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="left" label="图标" min-width="140" prop="authorityName">
|
||||
<template #default="scope">
|
||||
<div v-if="scope.row.meta.icon" class="icon-column">
|
||||
<el-icon>
|
||||
<component :is="scope.row.meta.icon" />
|
||||
</el-icon>
|
||||
<span>{{ scope.row.meta.icon }}</span>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="left" label="路由Name" show-overflow-tooltip min-width="160" prop="name" />
|
||||
<el-table-column align="left" label="路由Path" show-overflow-tooltip min-width="160" prop="path" />
|
||||
<el-table-column align="left" label="是否隐藏" min-width="100" prop="hidden">
|
||||
<template #default="scope">
|
||||
<span>{{ scope.row.hidden?"隐藏":"显示" }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="left" label="父节点" min-width="90" prop="parentId" />
|
||||
<el-table-column align="left" label="排序" min-width="70" prop="sort" />
|
||||
<el-table-column align="left" label="文件路径" min-width="360" prop="component" />
|
||||
<el-table-column align="left" fixed="right" label="操作" width="300">
|
||||
<template #default="scope">
|
||||
<el-button
|
||||
size="small"
|
||||
type="primary"
|
||||
link
|
||||
icon="plus"
|
||||
@click="addMenu(scope.row.ID)"
|
||||
>添加子菜单</el-button>
|
||||
<el-button
|
||||
size="small"
|
||||
type="primary"
|
||||
link
|
||||
icon="edit"
|
||||
@click="editMenu(scope.row.ID)"
|
||||
>编辑</el-button>
|
||||
<el-button
|
||||
size="small"
|
||||
type="primary"
|
||||
link
|
||||
icon="delete"
|
||||
@click="deleteMenu(scope.row.ID)"
|
||||
>删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
<el-dialog v-model="dialogFormVisible" :before-close="handleClose" :title="dialogTitle">
|
||||
<warning-bar title="新增菜单,需要在角色管理内配置权限才可使用" />
|
||||
<el-form
|
||||
v-if="dialogFormVisible"
|
||||
ref="menuForm"
|
||||
:inline="true"
|
||||
:model="form"
|
||||
:rules="rules"
|
||||
label-position="top"
|
||||
label-width="85px"
|
||||
>
|
||||
<el-form-item label="路由Name" prop="path" style="width:30%">
|
||||
<el-input
|
||||
v-model="form.name"
|
||||
autocomplete="off"
|
||||
placeholder="唯一英文字符串"
|
||||
@change="changeName"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item prop="path" style="width:30%">
|
||||
<template #label>
|
||||
<div style="display:inline-flex">
|
||||
路由Path
|
||||
<el-checkbox v-model="checkFlag" style="float:right;margin-left:20px;">添加参数</el-checkbox>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<el-input
|
||||
v-model="form.path"
|
||||
:disabled="!checkFlag"
|
||||
autocomplete="off"
|
||||
placeholder="建议只在后方拼接参数"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="是否隐藏" style="width:30%">
|
||||
<el-select v-model="form.hidden" placeholder="是否在列表隐藏">
|
||||
<el-option :value="false" label="否" />
|
||||
<el-option :value="true" label="是" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="父节点ID" style="width:30%">
|
||||
<el-cascader
|
||||
v-model="form.parentId"
|
||||
style="width:100%"
|
||||
:disabled="!isEdit"
|
||||
:options="menuOption"
|
||||
:props="{ checkStrictly: true,label:'title',value:'ID',disabled:'disabled',emitPath:false}"
|
||||
:show-all-levels="false"
|
||||
filterable
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="文件路径" prop="component" style="width:60%">
|
||||
<el-input v-model="form.component" autocomplete="off" placeholder="页面:view/xxx/xx.vue 插件:plugin/xx/xx.vue" @blur="fmtComponent" />
|
||||
<span style="font-size:12px;margin-right:12px;">如果菜单包含子菜单,请创建router-view二级路由页面或者</span><el-button style="margin-top:4px" size="small" @click="form.component = 'view/routerHolder.vue'">点我设置</el-button>
|
||||
</el-form-item>
|
||||
<el-form-item label="展示名称" prop="meta.title" style="width:30%">
|
||||
<el-input v-model="form.meta.title" autocomplete="off" />
|
||||
</el-form-item>
|
||||
<el-form-item label="图标" prop="meta.icon" style="width:30%">
|
||||
<icon :meta="form.meta" style="width:100%" />
|
||||
</el-form-item>
|
||||
<el-form-item label="排序标记" prop="sort" style="width:30%">
|
||||
<el-input v-model.number="form.sort" autocomplete="off" />
|
||||
</el-form-item>
|
||||
<el-form-item prop="meta.activeName" style="width:30%">
|
||||
<template #label>
|
||||
<div>
|
||||
<span> 高亮菜单 </span>
|
||||
<el-tooltip content="注:当到达此路由时候,指定左侧菜单指定name会处于活跃状态(亮起),可为空,为空则为本路由Name。" placement="top" effect="light">
|
||||
<el-icon><QuestionFilled /></el-icon>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
</template>
|
||||
<el-input v-model="form.meta.activeName" :placeholder="form.name" autocomplete="off" />
|
||||
</el-form-item>
|
||||
<el-form-item label="KeepAlive" prop="meta.keepAlive" style="width:30%">
|
||||
<el-select v-model="form.meta.keepAlive" style="width:100%" placeholder="是否keepAlive缓存页面">
|
||||
<el-option :value="false" label="否" />
|
||||
<el-option :value="true" label="是" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="CloseTab" prop="meta.closeTab" style="width:30%">
|
||||
<el-select v-model="form.meta.closeTab" style="width:100%" placeholder="是否自动关闭tab">
|
||||
<el-option :value="false" label="否" />
|
||||
<el-option :value="true" label="是" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item style="width:30%">
|
||||
<template #label>
|
||||
<div>
|
||||
<span> 是否为基础页面 </span>
|
||||
<el-tooltip content="此项选择为是,则不会展示左侧菜单以及顶部信息。" placement="top" effect="light">
|
||||
<el-icon><QuestionFilled /></el-icon>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<el-select v-model="form.meta.defaultMenu" style="width:100%" placeholder="是否为基础页面">
|
||||
<el-option :value="false" label="否" />
|
||||
<el-option :value="true" label="是" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div>
|
||||
<el-button
|
||||
size="small"
|
||||
type="primary"
|
||||
icon="edit"
|
||||
@click="addParameter(form)"
|
||||
>新增菜单参数</el-button>
|
||||
<el-table :data="form.parameters" style="width: 100%">
|
||||
<el-table-column align="left" prop="type" label="参数类型" width="180">
|
||||
<template #default="scope">
|
||||
<el-select v-model="scope.row.type" placeholder="请选择">
|
||||
<el-option key="query" value="query" label="query" />
|
||||
<el-option key="params" value="params" label="params" />
|
||||
</el-select>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="left" prop="key" label="参数key" width="180">
|
||||
<template #default="scope">
|
||||
<div>
|
||||
<el-input v-model="scope.row.key" />
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="left" prop="value" label="参数值">
|
||||
<template #default="scope">
|
||||
<div>
|
||||
<el-input v-model="scope.row.value" />
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="left">
|
||||
<template #default="scope">
|
||||
<div>
|
||||
<el-button
|
||||
type="danger"
|
||||
size="small"
|
||||
icon="delete"
|
||||
@click="deleteParameter(form.parameters,scope.$index)"
|
||||
>删除</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<el-button
|
||||
style="margin-top:12px"
|
||||
size="small"
|
||||
type="primary"
|
||||
icon="edit"
|
||||
@click="addBtn(form)"
|
||||
>新增可控按钮</el-button>
|
||||
<el-table :data="form.menuBtn" style="width: 100%">
|
||||
<el-table-column align="left" prop="name" label="按钮名称" width="180">
|
||||
<template #default="scope">
|
||||
<div>
|
||||
<el-input v-model="scope.row.name" />
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="left" prop="name" label="备注" width="180">
|
||||
<template #default="scope">
|
||||
<div>
|
||||
<el-input v-model="scope.row.desc" />
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="left">
|
||||
<template #default="scope">
|
||||
<div>
|
||||
<el-button
|
||||
type="danger"
|
||||
size="small"
|
||||
icon="delete"
|
||||
@click="deleteBtn(form.menuBtn,scope.$index)"
|
||||
>删除</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button size="small" @click="closeDialog">取 消</el-button>
|
||||
<el-button size="small" type="primary" @click="enterDialog">确 定</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {
|
||||
updateBaseMenu,
|
||||
getMenuList,
|
||||
addBaseMenu,
|
||||
deleteBaseMenu,
|
||||
getBaseMenuById
|
||||
} from '@/api/menu'
|
||||
import icon from '@/view/superAdmin/menu/icon.vue'
|
||||
import WarningBar from '@/components/warningBar/warningBar.vue'
|
||||
import { canRemoveAuthorityBtnApi } from '@/api/authorityBtn'
|
||||
import { reactive, ref } from 'vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
|
||||
const rules = reactive({
|
||||
path: [{ required: true, message: '请输入菜单name', trigger: 'blur' }],
|
||||
component: [
|
||||
{ required: true, message: '请输入文件路径', trigger: 'blur' }
|
||||
],
|
||||
'meta.title': [
|
||||
{ required: true, message: '请输入菜单展示名称', trigger: 'blur' }
|
||||
]
|
||||
})
|
||||
|
||||
const page = ref(1)
|
||||
const total = ref(0)
|
||||
const pageSize = ref(999)
|
||||
const tableData = ref([])
|
||||
const searchInfo = ref({})
|
||||
// 查询
|
||||
const getTableData = async() => {
|
||||
const table = await getMenuList({ page: page.value, pageSize: pageSize.value, ...searchInfo.value })
|
||||
if (table.code === 0) {
|
||||
tableData.value = table.data.list
|
||||
total.value = table.data.total
|
||||
page.value = table.data.page
|
||||
pageSize.value = table.data.pageSize
|
||||
}
|
||||
}
|
||||
|
||||
getTableData()
|
||||
|
||||
// 新增参数
|
||||
const addParameter = (form) => {
|
||||
if (!form.parameters) {
|
||||
form.parameters = []
|
||||
}
|
||||
form.parameters.push({
|
||||
type: 'query',
|
||||
key: '',
|
||||
value: ''
|
||||
})
|
||||
}
|
||||
|
||||
const fmtComponent = () => {
|
||||
form.value.component = form.value.component.replace(/\\/g, '/')
|
||||
}
|
||||
|
||||
// 删除参数
|
||||
const deleteParameter = (parameters, index) => {
|
||||
parameters.splice(index, 1)
|
||||
}
|
||||
|
||||
// 新增可控按钮
|
||||
const addBtn = (form) => {
|
||||
if (!form.menuBtn) {
|
||||
form.menuBtn = []
|
||||
}
|
||||
form.menuBtn.push({
|
||||
name: '',
|
||||
desc: '',
|
||||
})
|
||||
}
|
||||
// 删除可控按钮
|
||||
const deleteBtn = async(btns, index) => {
|
||||
const btn = btns[index]
|
||||
if (btn.ID === 0) {
|
||||
btns.splice(index, 1)
|
||||
return
|
||||
}
|
||||
const res = await canRemoveAuthorityBtnApi({ id: btn.ID })
|
||||
if (res.code === 0) {
|
||||
btns.splice(index, 1)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
const form = ref({
|
||||
ID: 0,
|
||||
path: '',
|
||||
name: '',
|
||||
hidden: false,
|
||||
parentId: '',
|
||||
component: '',
|
||||
meta: {
|
||||
activeName: '',
|
||||
title: '',
|
||||
icon: '',
|
||||
defaultMenu: false,
|
||||
closeTab: false,
|
||||
keepAlive: false
|
||||
},
|
||||
parameters: [],
|
||||
menuBtn: []
|
||||
})
|
||||
const changeName = () => {
|
||||
form.value.path = form.value.name
|
||||
}
|
||||
|
||||
const handleClose = (done) => {
|
||||
initForm()
|
||||
done()
|
||||
}
|
||||
// 删除菜单
|
||||
const deleteMenu = (ID) => {
|
||||
ElMessageBox.confirm('此操作将永久删除所有角色下该菜单, 是否继续?', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
})
|
||||
.then(async() => {
|
||||
const res = await deleteBaseMenu({ ID })
|
||||
if (res.code === 0) {
|
||||
ElMessage({
|
||||
type: 'success',
|
||||
message: '删除成功!'
|
||||
})
|
||||
if (tableData.value.length === 1 && page.value > 1) {
|
||||
page.value--
|
||||
}
|
||||
getTableData()
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
ElMessage({
|
||||
type: 'info',
|
||||
message: '已取消删除'
|
||||
})
|
||||
})
|
||||
}
|
||||
// 初始化弹窗内表格方法
|
||||
const menuForm = ref(null)
|
||||
const checkFlag = ref(false)
|
||||
const initForm = () => {
|
||||
checkFlag.value = false
|
||||
menuForm.value.resetFields()
|
||||
form.value = {
|
||||
ID: 0,
|
||||
path: '',
|
||||
name: '',
|
||||
hidden: false,
|
||||
parentId: '',
|
||||
component: '',
|
||||
meta: {
|
||||
title: '',
|
||||
icon: '',
|
||||
defaultMenu: false,
|
||||
closeTab: false,
|
||||
keepAlive: false
|
||||
}
|
||||
}
|
||||
}
|
||||
// 关闭弹窗
|
||||
|
||||
const dialogFormVisible = ref(false)
|
||||
const closeDialog = () => {
|
||||
initForm()
|
||||
dialogFormVisible.value = false
|
||||
}
|
||||
// 添加menu
|
||||
const enterDialog = async() => {
|
||||
menuForm.value.validate(async valid => {
|
||||
if (valid) {
|
||||
let res
|
||||
if (isEdit.value) {
|
||||
res = await updateBaseMenu(form.value)
|
||||
} else {
|
||||
res = await addBaseMenu(form.value)
|
||||
}
|
||||
if (res.code === 0) {
|
||||
ElMessage({
|
||||
type: 'success',
|
||||
message: isEdit.value ? '编辑成功' : '添加成功!'
|
||||
})
|
||||
getTableData()
|
||||
}
|
||||
initForm()
|
||||
dialogFormVisible.value = false
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const menuOption = ref([
|
||||
{
|
||||
ID: '0',
|
||||
title: '根菜单'
|
||||
}
|
||||
])
|
||||
const setOptions = () => {
|
||||
menuOption.value = [
|
||||
{
|
||||
ID: '0',
|
||||
title: '根目录'
|
||||
}
|
||||
]
|
||||
setMenuOptions(tableData.value, menuOption.value, false)
|
||||
}
|
||||
const setMenuOptions = (menuData, optionsData, disabled) => {
|
||||
menuData &&
|
||||
menuData.forEach(item => {
|
||||
if (item.children && item.children.length) {
|
||||
const option = {
|
||||
title: item.meta.title,
|
||||
ID: String(item.ID),
|
||||
disabled: disabled || item.ID === form.value.ID,
|
||||
children: []
|
||||
}
|
||||
setMenuOptions(
|
||||
item.children,
|
||||
option.children,
|
||||
disabled || item.ID === form.value.ID
|
||||
)
|
||||
optionsData.push(option)
|
||||
} else {
|
||||
const option = {
|
||||
title: item.meta.title,
|
||||
ID: String(item.ID),
|
||||
disabled: disabled || item.ID === form.value.ID
|
||||
}
|
||||
optionsData.push(option)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 添加菜单方法,id为 0则为添加根菜单
|
||||
const isEdit = ref(false)
|
||||
const dialogTitle = ref('新增菜单')
|
||||
const addMenu = (id) => {
|
||||
dialogTitle.value = '新增菜单'
|
||||
form.value.parentId = String(id)
|
||||
isEdit.value = false
|
||||
setOptions()
|
||||
dialogFormVisible.value = true
|
||||
}
|
||||
// 修改菜单方法
|
||||
const editMenu = async(id) => {
|
||||
dialogTitle.value = '编辑菜单'
|
||||
const res = await getBaseMenuById({ id })
|
||||
form.value = res.data.menu
|
||||
isEdit.value = true
|
||||
setOptions()
|
||||
dialogFormVisible.value = true
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'Menus',
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.warning {
|
||||
color: #dc143c;
|
||||
}
|
||||
.icon-column{
|
||||
display: flex;
|
||||
align-items: center;
|
||||
.el-icon{
|
||||
margin-right: 8px;
|
||||
}
|
||||
}
|
||||
</style>
|
257
src/view/superAdmin/operation/sysOperationRecord.vue
Normal file
257
src/view/superAdmin/operation/sysOperationRecord.vue
Normal file
@@ -0,0 +1,257 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="gva-search-box">
|
||||
<el-form :inline="true" :model="searchInfo">
|
||||
<el-form-item label="请求方法">
|
||||
<el-input v-model="searchInfo.method" placeholder="搜索条件" />
|
||||
</el-form-item>
|
||||
<el-form-item label="请求路径">
|
||||
<el-input v-model="searchInfo.path" placeholder="搜索条件" />
|
||||
</el-form-item>
|
||||
<el-form-item label="结果状态码">
|
||||
<el-input v-model="searchInfo.status" placeholder="搜索条件" />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button size="small" type="primary" icon="search" @click="onSubmit">查询</el-button>
|
||||
<el-button size="small" icon="refresh" @click="onReset">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
<div class="gva-table-box">
|
||||
<div class="gva-btn-list">
|
||||
|
||||
<el-popover v-model="deleteVisible" placement="top" width="160">
|
||||
<p>确定要删除吗?</p>
|
||||
<div style="text-align: right; margin-top: 8px;">
|
||||
<el-button size="small" type="primary" link @click="deleteVisible = false">取消</el-button>
|
||||
<el-button size="small" type="primary" @click="onDelete">确定</el-button>
|
||||
</div>
|
||||
<template #reference>
|
||||
<el-button icon="delete" size="small" style="margin-left: 10px;" :disabled="!multipleSelection.length" @click="deleteVisible = true">删除</el-button>
|
||||
</template>
|
||||
</el-popover>
|
||||
</div>
|
||||
<el-table
|
||||
ref="multipleTable"
|
||||
:data="tableData"
|
||||
style="width: 100%"
|
||||
tooltip-effect="dark"
|
||||
row-key="ID"
|
||||
@selection-change="handleSelectionChange"
|
||||
>
|
||||
<el-table-column align="left" type="selection" width="55" />
|
||||
<el-table-column align="left" label="操作人" width="140">
|
||||
<template #default="scope">
|
||||
<div>{{ scope.row.user.userName }}({{ scope.row.user.nickName }})</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="left" label="日期" width="180">
|
||||
<template #default="scope">{{ formatDate(scope.row.CreatedAt) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="left" label="状态码" prop="status" width="120">
|
||||
<template #default="scope">
|
||||
<div>
|
||||
<el-tag type="success">{{ scope.row.status }}</el-tag>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="left" label="请求IP" prop="ip" width="120" />
|
||||
<el-table-column align="left" label="请求方法" prop="method" width="120" />
|
||||
<el-table-column align="left" label="请求路径" prop="path" width="240" />
|
||||
<el-table-column align="left" label="请求" prop="path" width="80">
|
||||
<template #default="scope">
|
||||
<div>
|
||||
<el-popover v-if="scope.row.body" placement="left-start" trigger="click">
|
||||
<div class="popover-box">
|
||||
<pre>{{ fmtBody(scope.row.body) }}</pre>
|
||||
</div>
|
||||
<template #reference>
|
||||
<el-icon style="cursor: pointer;"><warning /></el-icon>
|
||||
</template>
|
||||
</el-popover>
|
||||
|
||||
<span v-else>无</span>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="left" label="响应" prop="path" width="80">
|
||||
<template #default="scope">
|
||||
<div>
|
||||
<el-popover v-if="scope.row.resp" placement="left-start" trigger="click">
|
||||
<div class="popover-box">
|
||||
<pre>{{ fmtBody(scope.row.resp) }}</pre>
|
||||
</div>
|
||||
<template #reference>
|
||||
<el-icon style="cursor: pointer;"><warning /></el-icon>
|
||||
</template>
|
||||
</el-popover>
|
||||
<span v-else>无</span>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="left" label="按钮组">
|
||||
<template #default="scope">
|
||||
<el-popover v-model="scope.row.visible" placement="top" width="160">
|
||||
<p>确定要删除吗?</p>
|
||||
<div style="text-align: right; margin-top: 8px;">
|
||||
<el-button size="small" type="primary" link @click="scope.row.visible = false">取消</el-button>
|
||||
<el-button size="small" type="primary" @click="deleteSysOperationRecordFunc(scope.row)">确定</el-button>
|
||||
</div>
|
||||
<template #reference>
|
||||
<el-button icon="delete" size="small" type="primary" link @click="scope.row.visible = true">删除</el-button>
|
||||
</template>
|
||||
</el-popover>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<div class="gva-pagination">
|
||||
<el-pagination
|
||||
:current-page="page"
|
||||
:page-size="pageSize"
|
||||
:page-sizes="[10, 30, 50, 100]"
|
||||
:total="total"
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
@current-change="handleCurrentChange"
|
||||
@size-change="handleSizeChange"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {
|
||||
deleteSysOperationRecord,
|
||||
getSysOperationRecordList,
|
||||
deleteSysOperationRecordByIds
|
||||
} from '@/api/sysOperationRecord' // 此处请自行替换地址
|
||||
import { formatDate } from '@/utils/format'
|
||||
import { ref } from 'vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
|
||||
const page = ref(1)
|
||||
const total = ref(0)
|
||||
const pageSize = ref(10)
|
||||
const tableData = ref([])
|
||||
const searchInfo = ref({})
|
||||
const onReset = () => {
|
||||
searchInfo.value = {}
|
||||
}
|
||||
// 条件搜索前端看此方法
|
||||
const onSubmit = () => {
|
||||
page.value = 1
|
||||
pageSize.value = 10
|
||||
if (searchInfo.value.status === '') {
|
||||
searchInfo.value.status = null
|
||||
}
|
||||
getTableData()
|
||||
}
|
||||
|
||||
// 分页
|
||||
const handleSizeChange = (val) => {
|
||||
pageSize.value = val
|
||||
getTableData()
|
||||
}
|
||||
|
||||
const handleCurrentChange = (val) => {
|
||||
page.value = val
|
||||
getTableData()
|
||||
}
|
||||
|
||||
// 查询
|
||||
const getTableData = async() => {
|
||||
const table = await getSysOperationRecordList({
|
||||
page: page.value,
|
||||
pageSize: pageSize.value,
|
||||
...searchInfo.value,
|
||||
})
|
||||
if (table.code === 0) {
|
||||
tableData.value = table.data.list
|
||||
total.value = table.data.total
|
||||
page.value = table.data.page
|
||||
pageSize.value = table.data.pageSize
|
||||
}
|
||||
}
|
||||
|
||||
getTableData()
|
||||
|
||||
const deleteVisible = ref(false)
|
||||
const multipleSelection = ref([])
|
||||
const handleSelectionChange = (val) => {
|
||||
multipleSelection.value = val
|
||||
}
|
||||
const onDelete = async() => {
|
||||
const ids = []
|
||||
multipleSelection.value &&
|
||||
multipleSelection.value.forEach(item => {
|
||||
ids.push(item.ID)
|
||||
})
|
||||
const res = await deleteSysOperationRecordByIds({ ids })
|
||||
if (res.code === 0) {
|
||||
ElMessage({
|
||||
type: 'success',
|
||||
message: '删除成功'
|
||||
})
|
||||
if (tableData.value.length === ids.length && page.value > 1) {
|
||||
page.value--
|
||||
}
|
||||
deleteVisible.value = false
|
||||
getTableData()
|
||||
}
|
||||
}
|
||||
const deleteSysOperationRecordFunc = async(row) => {
|
||||
row.visible = false
|
||||
const res = await deleteSysOperationRecord({ ID: row.ID })
|
||||
if (res.code === 0) {
|
||||
ElMessage({
|
||||
type: 'success',
|
||||
message: '删除成功'
|
||||
})
|
||||
if (tableData.value.length === 1 && page.value > 1) {
|
||||
page.value--
|
||||
}
|
||||
getTableData()
|
||||
}
|
||||
}
|
||||
const fmtBody = (value) => {
|
||||
try {
|
||||
return JSON.parse(value)
|
||||
} catch (err) {
|
||||
return value
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<script>
|
||||
|
||||
export default {
|
||||
name: 'SysOperationRecord'
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.table-expand {
|
||||
padding-left: 60px;
|
||||
font-size: 0;
|
||||
label {
|
||||
width: 90px;
|
||||
color: #99a9bf;
|
||||
.el-form-item {
|
||||
margin-right: 0;
|
||||
margin-bottom: 0;
|
||||
width: 50%;
|
||||
}
|
||||
}
|
||||
}
|
||||
.popover-box {
|
||||
background: #112435;
|
||||
color: #f08047;
|
||||
height: 600px;
|
||||
width: 420px;
|
||||
overflow: auto;
|
||||
}
|
||||
.popover-box::-webkit-scrollbar {
|
||||
display: none; /* Chrome Safari */
|
||||
}
|
||||
</style>
|
446
src/view/superAdmin/user/user.vue
Normal file
446
src/view/superAdmin/user/user.vue
Normal file
@@ -0,0 +1,446 @@
|
||||
<template>
|
||||
<div>
|
||||
<warning-bar title="注:右上角头像下拉可切换角色" />
|
||||
<div class="gva-table-box">
|
||||
<div class="gva-btn-list">
|
||||
<el-button size="small" type="primary" icon="plus" @click="addUser">新增用户</el-button>
|
||||
</div>
|
||||
<el-table
|
||||
:data="tableData"
|
||||
row-key="ID"
|
||||
>
|
||||
<el-table-column align="left" label="头像" min-width="75">
|
||||
<template #default="scope">
|
||||
<CustomPic style="margin-top:8px" :pic-src="scope.row.headerImg" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="left" label="ID" min-width="50" prop="ID" />
|
||||
<el-table-column align="left" label="用户名" min-width="150" prop="userName" />
|
||||
<el-table-column align="left" label="昵称" min-width="150" prop="nickName" />
|
||||
<el-table-column align="left" label="手机号" min-width="180" prop="phone" />
|
||||
<el-table-column align="left" label="邮箱" min-width="180" prop="email" />
|
||||
<el-table-column align="left" label="用户角色" min-width="200">
|
||||
<template #default="scope">
|
||||
<el-cascader
|
||||
v-model="scope.row.authorityIds"
|
||||
:options="authOptions"
|
||||
:show-all-levels="false"
|
||||
collapse-tags
|
||||
:props="{ multiple:true,checkStrictly: true,label:'authorityName',value:'authorityId',disabled:'disabled',emitPath:false}"
|
||||
:clearable="false"
|
||||
@visible-change="(flag)=>{changeAuthority(scope.row,flag,0)}"
|
||||
@remove-tag="(removeAuth)=>{changeAuthority(scope.row,false,removeAuth)}"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="left" label="启用" min-width="150">
|
||||
<template #default="scope">
|
||||
<el-switch
|
||||
v-model="scope.row.enable"
|
||||
inline-prompt
|
||||
:active-value="1"
|
||||
:inactive-value="2"
|
||||
@change="()=>{switchEnable(scope.row)}"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column label="操作" min-width="250" fixed="right">
|
||||
<template #default="scope">
|
||||
<el-popover v-model="scope.row.visible" placement="top" width="160">
|
||||
<p>确定要删除此用户吗</p>
|
||||
<div style="text-align: right; margin-top: 8px;">
|
||||
<el-button size="small" type="primary" link @click="scope.row.visible = false">取消</el-button>
|
||||
<el-button type="primary" size="small" @click="deleteUserFunc(scope.row)">确定</el-button>
|
||||
</div>
|
||||
<template #reference>
|
||||
<el-button type="primary" link icon="delete" size="small">删除</el-button>
|
||||
</template>
|
||||
</el-popover>
|
||||
<el-button type="primary" link icon="edit" size="small" @click="openEdit(scope.row)">编辑</el-button>
|
||||
<el-button type="primary" link icon="magic-stick" size="small" @click="resetPasswordFunc(scope.row)">重置密码</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
</el-table>
|
||||
<div class="gva-pagination">
|
||||
<el-pagination
|
||||
:current-page="page"
|
||||
:page-size="pageSize"
|
||||
:page-sizes="[10, 30, 50, 100]"
|
||||
:total="total"
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
@current-change="handleCurrentChange"
|
||||
@size-change="handleSizeChange"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<el-dialog
|
||||
v-model="addUserDialog"
|
||||
custom-class="user-dialog"
|
||||
title="用户"
|
||||
:show-close="false"
|
||||
:close-on-press-escape="false"
|
||||
:close-on-click-modal="false"
|
||||
>
|
||||
<div style="height:60vh;overflow:auto;padding:0 12px;">
|
||||
<el-form ref="userForm" :rules="rules" :model="userInfo" label-width="80px">
|
||||
<el-form-item v-if="dialogFlag === 'add'" label="用户名" prop="userName">
|
||||
<el-input v-model="userInfo.userName" />
|
||||
</el-form-item>
|
||||
<el-form-item v-if="dialogFlag === 'add'" label="密码" prop="password">
|
||||
<el-input v-model="userInfo.password" />
|
||||
</el-form-item>
|
||||
<el-form-item label="昵称" prop="nickName">
|
||||
<el-input v-model="userInfo.nickName" />
|
||||
</el-form-item>
|
||||
<el-form-item label="手机号" prop="phone">
|
||||
<el-input v-model="userInfo.phone" />
|
||||
</el-form-item>
|
||||
<el-form-item label="邮箱" prop="email">
|
||||
<el-input v-model="userInfo.email" />
|
||||
</el-form-item>
|
||||
<el-form-item label="用户角色" prop="authorityId">
|
||||
<el-cascader
|
||||
v-model="userInfo.authorityIds"
|
||||
style="width:100%"
|
||||
:options="authOptions"
|
||||
:show-all-levels="false"
|
||||
:props="{ multiple:true,checkStrictly: true,label:'authorityName',value:'authorityId',disabled:'disabled',emitPath:false}"
|
||||
:clearable="false"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="启用" prop="disabled">
|
||||
<el-switch
|
||||
v-model="userInfo.enable"
|
||||
inline-prompt
|
||||
:active-value="1"
|
||||
:inactive-value="2"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="头像" label-width="80px">
|
||||
<div style="display:inline-block" @click="openHeaderChange">
|
||||
<img v-if="userInfo.headerImg" class="header-img-box" :src="(userInfo.headerImg && userInfo.headerImg.slice(0, 4) !== 'http')?path+userInfo.headerImg:userInfo.headerImg">
|
||||
<div v-else class="header-img-box">从媒体库选择</div>
|
||||
</div>
|
||||
</el-form-item>
|
||||
|
||||
</el-form>
|
||||
|
||||
</div>
|
||||
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button size="small" @click="closeAddUserDialog">取 消</el-button>
|
||||
<el-button size="small" type="primary" @click="enterAddUserDialog">确 定</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
<ChooseImg ref="chooseImg" :target="userInfo" :target-key="`headerImg`" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'User',
|
||||
}
|
||||
</script>
|
||||
|
||||
<script setup>
|
||||
|
||||
import {
|
||||
getUserList,
|
||||
setUserAuthorities,
|
||||
register,
|
||||
deleteUser
|
||||
} from '@/api/user'
|
||||
|
||||
import { getAuthorityList } from '@/api/authority'
|
||||
import CustomPic from '@/components/customPic/index.vue'
|
||||
import ChooseImg from '@/components/chooseImg/index.vue'
|
||||
import WarningBar from '@/components/warningBar/warningBar.vue'
|
||||
import { setUserInfo, resetPassword } from '@/api/user.js'
|
||||
|
||||
import { nextTick, ref, watch } from 'vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
const path = ref(import.meta.env.VITE_BASE_API + '/')
|
||||
// 初始化相关
|
||||
const setAuthorityOptions = (AuthorityData, optionsData) => {
|
||||
AuthorityData &&
|
||||
AuthorityData.forEach(item => {
|
||||
if (item.children && item.children.length) {
|
||||
const option = {
|
||||
authorityId: item.authorityId,
|
||||
authorityName: item.authorityName,
|
||||
children: []
|
||||
}
|
||||
setAuthorityOptions(item.children, option.children)
|
||||
optionsData.push(option)
|
||||
} else {
|
||||
const option = {
|
||||
authorityId: item.authorityId,
|
||||
authorityName: item.authorityName
|
||||
}
|
||||
optionsData.push(option)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const page = ref(1)
|
||||
const total = ref(0)
|
||||
const pageSize = ref(10)
|
||||
const tableData = ref([])
|
||||
// 分页
|
||||
const handleSizeChange = (val) => {
|
||||
pageSize.value = val
|
||||
getTableData()
|
||||
}
|
||||
|
||||
const handleCurrentChange = (val) => {
|
||||
page.value = val
|
||||
getTableData()
|
||||
}
|
||||
|
||||
// 查询
|
||||
const getTableData = async() => {
|
||||
const table = await getUserList({ page: page.value, pageSize: pageSize.value })
|
||||
if (table.code === 0) {
|
||||
tableData.value = table.data.list
|
||||
total.value = table.data.total
|
||||
page.value = table.data.page
|
||||
pageSize.value = table.data.pageSize
|
||||
}
|
||||
}
|
||||
|
||||
watch(() => tableData.value, () => {
|
||||
setAuthorityIds()
|
||||
})
|
||||
|
||||
const initPage = async() => {
|
||||
getTableData()
|
||||
const res = await getAuthorityList({ page: 1, pageSize: 999 })
|
||||
setOptions(res.data.list)
|
||||
}
|
||||
|
||||
initPage()
|
||||
|
||||
const resetPasswordFunc = (row) => {
|
||||
ElMessageBox.confirm(
|
||||
'是否将此用户密码重置为123456?',
|
||||
'警告',
|
||||
{
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
}
|
||||
).then(async() => {
|
||||
const res = await resetPassword({
|
||||
ID: row.ID,
|
||||
})
|
||||
if (res.code === 0) {
|
||||
ElMessage({
|
||||
type: 'success',
|
||||
message: res.msg,
|
||||
})
|
||||
} else {
|
||||
ElMessage({
|
||||
type: 'error',
|
||||
message: res.msg,
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
const setAuthorityIds = () => {
|
||||
tableData.value && tableData.value.forEach((user) => {
|
||||
const authorityIds = user.authorities && user.authorities.map(i => {
|
||||
return i.authorityId
|
||||
})
|
||||
user.authorityIds = authorityIds
|
||||
})
|
||||
}
|
||||
|
||||
const chooseImg = ref(null)
|
||||
const openHeaderChange = () => {
|
||||
chooseImg.value.open()
|
||||
}
|
||||
|
||||
const authOptions = ref([])
|
||||
const setOptions = (authData) => {
|
||||
authOptions.value = []
|
||||
setAuthorityOptions(authData, authOptions.value)
|
||||
}
|
||||
|
||||
const deleteUserFunc = async(row) => {
|
||||
const res = await deleteUser({ id: row.ID })
|
||||
if (res.code === 0) {
|
||||
ElMessage.success('删除成功')
|
||||
row.visible = false
|
||||
await getTableData()
|
||||
}
|
||||
}
|
||||
|
||||
// 弹窗相关
|
||||
const userInfo = ref({
|
||||
username: '',
|
||||
password: '',
|
||||
nickName: '',
|
||||
headerImg: '',
|
||||
authorityId: '',
|
||||
authorityIds: [],
|
||||
enable: 1,
|
||||
})
|
||||
|
||||
const rules = ref({
|
||||
userName: [
|
||||
{ required: true, message: '请输入用户名', trigger: 'blur' },
|
||||
{ min: 5, message: '最低5位字符', trigger: 'blur' }
|
||||
],
|
||||
password: [
|
||||
{ required: true, message: '请输入用户密码', trigger: 'blur' },
|
||||
{ min: 6, message: '最低6位字符', trigger: 'blur' }
|
||||
],
|
||||
nickName: [
|
||||
{ required: true, message: '请输入用户昵称', trigger: 'blur' }
|
||||
],
|
||||
phone: [
|
||||
{ pattern: /^1([38][0-9]|4[014-9]|[59][0-35-9]|6[2567]|7[0-8])\d{8}$/, message: "请输入合法手机号", trigger: "blur" },
|
||||
],
|
||||
email: [
|
||||
{ pattern: /^([0-9A-Za-z\-_\.]+)@([0-9a-z]+\.[a-z]{2,3}(\.[a-z]{2})?)$/g, message: "请输入正确的邮箱", trigger: "blur" },
|
||||
],
|
||||
authorityId: [
|
||||
{ required: true, message: '请选择用户角色', trigger: 'blur' }
|
||||
]
|
||||
})
|
||||
const userForm = ref(null)
|
||||
const enterAddUserDialog = async() => {
|
||||
userInfo.value.authorityId = userInfo.value.authorityIds[0]
|
||||
userForm.value.validate(async valid => {
|
||||
if (valid) {
|
||||
const req = {
|
||||
...userInfo.value
|
||||
}
|
||||
if (dialogFlag.value === 'add') {
|
||||
const res = await register(req)
|
||||
if (res.code === 0) {
|
||||
ElMessage({ type: 'success', message: '创建成功' })
|
||||
await getTableData()
|
||||
closeAddUserDialog()
|
||||
}
|
||||
}
|
||||
if (dialogFlag.value === 'edit') {
|
||||
const res = await setUserInfo(req)
|
||||
if (res.code === 0) {
|
||||
ElMessage({ type: 'success', message: '编辑成功' })
|
||||
await getTableData()
|
||||
closeAddUserDialog()
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const addUserDialog = ref(false)
|
||||
const closeAddUserDialog = () => {
|
||||
userForm.value.resetFields()
|
||||
userInfo.value.headerImg = ''
|
||||
userInfo.value.authorityIds = []
|
||||
addUserDialog.value = false
|
||||
}
|
||||
|
||||
const dialogFlag = ref('add')
|
||||
|
||||
const addUser = () => {
|
||||
dialogFlag.value = 'add'
|
||||
addUserDialog.value = true
|
||||
}
|
||||
|
||||
const tempAuth = {}
|
||||
const changeAuthority = async(row, flag, removeAuth) => {
|
||||
if (flag) {
|
||||
if (!removeAuth) {
|
||||
tempAuth[row.ID] = [...row.authorityIds]
|
||||
}
|
||||
return
|
||||
}
|
||||
await nextTick()
|
||||
const res = await setUserAuthorities({
|
||||
ID: row.ID,
|
||||
authorityIds: row.authorityIds
|
||||
})
|
||||
if (res.code === 0) {
|
||||
ElMessage({ type: 'success', message: '角色设置成功' })
|
||||
} else {
|
||||
if (!removeAuth) {
|
||||
row.authorityIds = [...tempAuth[row.ID]]
|
||||
delete tempAuth[row.ID]
|
||||
} else {
|
||||
row.authorityIds = [removeAuth, ...row.authorityIds]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const openEdit = (row) => {
|
||||
dialogFlag.value = 'edit'
|
||||
userInfo.value = JSON.parse(JSON.stringify(row))
|
||||
addUserDialog.value = true
|
||||
}
|
||||
|
||||
const switchEnable = async(row) => {
|
||||
userInfo.value = JSON.parse(JSON.stringify(row))
|
||||
await nextTick()
|
||||
const req = {
|
||||
...userInfo.value
|
||||
}
|
||||
const res = await setUserInfo(req)
|
||||
if (res.code === 0) {
|
||||
ElMessage({ type: 'success', message: `${req.enable === 2 ? '禁用' : '启用'}成功` })
|
||||
await getTableData()
|
||||
userInfo.value.headerImg = ''
|
||||
userInfo.value.authorityIds = []
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.user-dialog {
|
||||
.header-img-box {
|
||||
width: 200px;
|
||||
height: 200px;
|
||||
border: 1px dashed #ccc;
|
||||
border-radius: 20px;
|
||||
text-align: center;
|
||||
line-height: 200px;
|
||||
cursor: pointer;
|
||||
}
|
||||
.avatar-uploader .el-upload:hover {
|
||||
border-color: #409eff;
|
||||
}
|
||||
.avatar-uploader-icon {
|
||||
border: 1px dashed #d9d9d9 !important;
|
||||
border-radius: 6px;
|
||||
font-size: 28px;
|
||||
color: #8c939d;
|
||||
width: 178px;
|
||||
height: 178px;
|
||||
line-height: 178px;
|
||||
text-align: center;
|
||||
}
|
||||
.avatar {
|
||||
width: 178px;
|
||||
height: 178px;
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
.nickName{
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
}
|
||||
.pointer{
|
||||
cursor: pointer;
|
||||
font-size: 16px;
|
||||
margin-left: 2px;
|
||||
}
|
||||
</style>
|
183
src/view/system/state.vue
Normal file
183
src/view/system/state.vue
Normal file
@@ -0,0 +1,183 @@
|
||||
<template>
|
||||
<div>
|
||||
<el-row :gutter="15" class="system_state">
|
||||
<el-col :span="12">
|
||||
<el-card v-if="state.os" class="card_item">
|
||||
<template #header>
|
||||
<div>Runtime</div>
|
||||
</template>
|
||||
<div>
|
||||
<el-row :gutter="10">
|
||||
<el-col :span="12">os:</el-col>
|
||||
<el-col :span="12" v-text="state.os.goos" />
|
||||
</el-row>
|
||||
<el-row :gutter="10">
|
||||
<el-col :span="12">cpu nums:</el-col>
|
||||
<el-col :span="12" v-text="state.os.numCpu" />
|
||||
</el-row>
|
||||
<el-row :gutter="10">
|
||||
<el-col :span="12">compiler:</el-col>
|
||||
<el-col :span="12" v-text="state.os.compiler" />
|
||||
</el-row>
|
||||
<el-row :gutter="10">
|
||||
<el-col :span="12">go version:</el-col>
|
||||
<el-col :span="12" v-text="state.os.goVersion" />
|
||||
</el-row>
|
||||
<el-row :gutter="10">
|
||||
<el-col :span="12">goroutine nums:</el-col>
|
||||
<el-col :span="12" v-text="state.os.numGoroutine" />
|
||||
</el-row>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-card v-if="state.disk" class="card_item">
|
||||
<template #header>
|
||||
<div>Disk</div>
|
||||
</template>
|
||||
<div>
|
||||
<el-row :gutter="10">
|
||||
<el-col :span="12">
|
||||
<el-row :gutter="10">
|
||||
<el-col :span="12">total (MB)</el-col>
|
||||
<el-col :span="12" v-text="state.disk.totalMb" />
|
||||
</el-row>
|
||||
<el-row :gutter="10">
|
||||
<el-col :span="12">used (MB)</el-col>
|
||||
<el-col :span="12" v-text="state.disk.usedMb" />
|
||||
</el-row>
|
||||
<el-row :gutter="10">
|
||||
<el-col :span="12">total (GB)</el-col>
|
||||
<el-col :span="12" v-text="state.disk.totalGb" />
|
||||
</el-row>
|
||||
<el-row :gutter="10">
|
||||
<el-col :span="12">used (GB)</el-col>
|
||||
<el-col :span="12" v-text="state.disk.usedGb" />
|
||||
</el-row>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-progress
|
||||
type="dashboard"
|
||||
:percentage="state.disk.usedPercent"
|
||||
:color="colors"
|
||||
/>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="15" class="system_state">
|
||||
<el-col :span="12">
|
||||
<el-card
|
||||
v-if="state.cpu"
|
||||
class="card_item"
|
||||
:body-style="{ height: '180px', 'overflow-y': 'scroll' }"
|
||||
>
|
||||
<template #header>
|
||||
<div>CPU</div>
|
||||
</template>
|
||||
<div>
|
||||
<el-row :gutter="10">
|
||||
<el-col :span="12">physical number of cores:</el-col>
|
||||
<el-col :span="12" v-text="state.cpu.cores" />
|
||||
</el-row>
|
||||
<el-row v-for="(item, index) in state.cpu.cpus" :key="index" :gutter="10">
|
||||
<el-col :span="12">core {{ index }}:</el-col>
|
||||
<el-col
|
||||
:span="12"
|
||||
><el-progress
|
||||
type="line"
|
||||
:percentage="+item.toFixed(0)"
|
||||
:color="colors"
|
||||
/></el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-card v-if="state.ram" class="card_item">
|
||||
<template #header>
|
||||
<div>Ram</div>
|
||||
</template>
|
||||
<div>
|
||||
<el-row :gutter="10">
|
||||
<el-col :span="12">
|
||||
<el-row :gutter="10">
|
||||
<el-col :span="12">total (MB)</el-col>
|
||||
<el-col :span="12" v-text="state.ram.totalMb" />
|
||||
</el-row>
|
||||
<el-row :gutter="10">
|
||||
<el-col :span="12">used (MB)</el-col>
|
||||
<el-col :span="12" v-text="state.ram.usedMb" />
|
||||
</el-row>
|
||||
<el-row :gutter="10">
|
||||
<el-col :span="12">total (GB)</el-col>
|
||||
<el-col :span="12" v-text="state.ram.totalMb / 1024" />
|
||||
</el-row>
|
||||
<el-row :gutter="10">
|
||||
<el-col :span="12">used (GB)</el-col>
|
||||
<el-col
|
||||
:span="12"
|
||||
v-text="(state.ram.usedMb / 1024).toFixed(2)"
|
||||
/>
|
||||
</el-row>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-progress
|
||||
type="dashboard"
|
||||
:percentage="state.ram.usedPercent"
|
||||
:color="colors"
|
||||
/>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { getSystemState } from '@/api/system'
|
||||
import { onUnmounted, ref } from 'vue'
|
||||
const timer = ref(null)
|
||||
const state = ref({})
|
||||
const colors = ref([
|
||||
{ color: '#5cb87a', percentage: 20 },
|
||||
{ color: '#e6a23c', percentage: 40 },
|
||||
{ color: '#f56c6c', percentage: 80 }
|
||||
])
|
||||
|
||||
const reload = async() => {
|
||||
const { data } = await getSystemState()
|
||||
state.value = data.server
|
||||
}
|
||||
|
||||
reload()
|
||||
timer.value = setInterval(() => {
|
||||
reload()
|
||||
}, 1000 * 10)
|
||||
|
||||
onUnmounted(() => {
|
||||
clearInterval(timer.value)
|
||||
timer.value = null
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'State',
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.system_state {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.card_item {
|
||||
height: 280px;
|
||||
}
|
||||
</style>
|
234
src/view/systemTools/autoCode/component/fieldDialog.vue
Normal file
234
src/view/systemTools/autoCode/component/fieldDialog.vue
Normal file
@@ -0,0 +1,234 @@
|
||||
<template>
|
||||
<div>
|
||||
<warning-bar title="id , created_at , updated_at , deleted_at 会自动生成请勿重复创建。搜索时如果条件为LIKE只支持字符串" />
|
||||
<el-form
|
||||
ref="fieldDialogFrom"
|
||||
:model="middleDate"
|
||||
label-width="120px"
|
||||
label-position="right"
|
||||
:rules="rules"
|
||||
class="grid-form"
|
||||
>
|
||||
<el-form-item label="Field名称" prop="fieldName">
|
||||
<el-input v-model="middleDate.fieldName" autocomplete="off" style="width:80%" />
|
||||
<el-button size="small" style="width:18%;margin-left:2%" @click="autoFill">
|
||||
<span style="font-size: 12px">自动填充</span>
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
<el-form-item label="Field中文名" prop="fieldDesc">
|
||||
<el-input v-model="middleDate.fieldDesc" autocomplete="off" />
|
||||
</el-form-item>
|
||||
<el-form-item label="FieldJSON" prop="fieldJson">
|
||||
<el-input v-model="middleDate.fieldJson" autocomplete="off" />
|
||||
</el-form-item>
|
||||
<el-form-item label="数据库字段名" prop="columnName">
|
||||
<el-input v-model="middleDate.columnName" autocomplete="off" />
|
||||
</el-form-item>
|
||||
<el-form-item label="数据库字段描述" prop="comment">
|
||||
<el-input v-model="middleDate.comment" autocomplete="off" />
|
||||
</el-form-item>
|
||||
<el-form-item label="Field数据类型" prop="fieldType">
|
||||
<el-select
|
||||
v-model="middleDate.fieldType"
|
||||
style="width:100%"
|
||||
placeholder="请选择field数据类型"
|
||||
clearable
|
||||
@change="clearOther"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in typeOptions"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item :label="middleDate.fieldType === 'enum' ? '枚举值' : '类型长度'" prop="dataTypeLong">
|
||||
<el-input v-model="middleDate.dataTypeLong" :placeholder="middleDate.fieldType === 'enum'?`例:'北京','天津'`:'数据库类型长度'" />
|
||||
</el-form-item>
|
||||
<el-form-item label="Field查询条件" prop="fieldSearchType">
|
||||
<el-select
|
||||
v-model="middleDate.fieldSearchType"
|
||||
style="width:100%"
|
||||
placeholder="请选择Field查询条件"
|
||||
clearable
|
||||
>
|
||||
<el-option
|
||||
v-for="item in typeSearchOptions"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
:disabled="
|
||||
(middleDate.fieldType!=='string'&&item.value==='LIKE')||
|
||||
((middleDate.fieldType!=='int'&&middleDate.fieldType!=='time.Time'&&middleDate.fieldType!=='float64')&&(item.value==='BETWEEN' || item.value==='NOT BETWEEN'))
|
||||
"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="关联字典" prop="dictType">
|
||||
<el-select
|
||||
v-model="middleDate.dictType"
|
||||
style="width:100%"
|
||||
:disabled="middleDate.fieldType!=='int'"
|
||||
placeholder="请选择字典"
|
||||
clearable
|
||||
>
|
||||
<el-option
|
||||
v-for="item in dictOptions"
|
||||
:key="item.type"
|
||||
:label="`${item.type}(${item.name})`"
|
||||
:value="item.type"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="是否排序">
|
||||
<el-switch v-model="middleDate.sort" />
|
||||
</el-form-item>
|
||||
<el-form-item label="是否必填">
|
||||
<el-switch v-model="middleDate.require" />
|
||||
</el-form-item>
|
||||
<el-form-item label="是否可清空">
|
||||
<el-switch v-model="middleDate.clearable" />
|
||||
</el-form-item>
|
||||
<el-form-item label="校验失败文案">
|
||||
<el-input v-model="middleDate.errorText" />
|
||||
</el-form-item>
|
||||
|
||||
</el-form>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { toLowerCase, toSQLLine } from '@/utils/stringFun'
|
||||
import { getSysDictionaryList } from '@/api/sysDictionary'
|
||||
import WarningBar from '@/components/warningBar/warningBar.vue'
|
||||
import { ref } from 'vue'
|
||||
|
||||
const props = defineProps({
|
||||
dialogMiddle: {
|
||||
type: Object,
|
||||
default: function() {
|
||||
return {}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const middleDate = ref({})
|
||||
const dictOptions = ref([])
|
||||
const typeSearchOptions = ref([
|
||||
{
|
||||
label: '=',
|
||||
value: '='
|
||||
},
|
||||
{
|
||||
label: '<>',
|
||||
value: '<>'
|
||||
},
|
||||
{
|
||||
label: '>',
|
||||
value: '>'
|
||||
},
|
||||
{
|
||||
label: '<',
|
||||
value: '<'
|
||||
},
|
||||
{
|
||||
label: 'LIKE',
|
||||
value: 'LIKE'
|
||||
},
|
||||
{
|
||||
label: 'BETWEEN',
|
||||
value: 'BETWEEN'
|
||||
},
|
||||
{
|
||||
label: 'NOT BETWEEN',
|
||||
value: 'NOT BETWEEN'
|
||||
}
|
||||
])
|
||||
const typeOptions = ref([
|
||||
{
|
||||
label: '字符串',
|
||||
value: 'string'
|
||||
},
|
||||
{
|
||||
label: '整型',
|
||||
value: 'int'
|
||||
},
|
||||
{
|
||||
label: '布尔值',
|
||||
value: 'bool'
|
||||
},
|
||||
{
|
||||
label: '浮点型',
|
||||
value: 'float64'
|
||||
},
|
||||
{
|
||||
label: '时间',
|
||||
value: 'time.Time'
|
||||
},
|
||||
{
|
||||
label: '枚举',
|
||||
value: 'enum'
|
||||
}
|
||||
])
|
||||
const rules = ref({
|
||||
fieldName: [
|
||||
{ required: true, message: '请输入field英文名', trigger: 'blur' }
|
||||
],
|
||||
fieldDesc: [
|
||||
{ required: true, message: '请输入field中文名', trigger: 'blur' }
|
||||
],
|
||||
fieldJson: [
|
||||
{ required: true, message: '请输入field格式化json', trigger: 'blur' }
|
||||
],
|
||||
columnName: [
|
||||
{ required: true, message: '请输入数据库字段', trigger: 'blur' }
|
||||
],
|
||||
fieldType: [
|
||||
{ required: true, message: '请选择field数据类型', trigger: 'blur' }
|
||||
]
|
||||
})
|
||||
|
||||
const init = async() => {
|
||||
middleDate.value = props.dialogMiddle
|
||||
const dictRes = await getSysDictionaryList({
|
||||
page: 1,
|
||||
pageSize: 999999
|
||||
})
|
||||
|
||||
dictOptions.value = dictRes.data.list
|
||||
}
|
||||
init()
|
||||
|
||||
const autoFill = () => {
|
||||
middleDate.value.fieldJson = toLowerCase(middleDate.value.fieldName)
|
||||
middleDate.value.columnName = toSQLLine(middleDate.value.fieldJson)
|
||||
}
|
||||
|
||||
const clearOther = () => {
|
||||
middleDate.value.fieldSearchType = ''
|
||||
middleDate.value.dictType = ''
|
||||
}
|
||||
|
||||
const fieldDialogFrom = ref(null)
|
||||
defineExpose({ fieldDialogFrom })
|
||||
</script>
|
||||
|
||||
<script>
|
||||
|
||||
export default {
|
||||
name: 'FieldDialog'
|
||||
}
|
||||
</script>
|
||||
<style scoped>
|
||||
.grid-form{
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
}
|
||||
.click-text{
|
||||
color: #0d84ff;
|
||||
font-size: 13px;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
}
|
||||
</style>
|
93
src/view/systemTools/autoCode/component/previewCodeDialg.vue
Normal file
93
src/view/systemTools/autoCode/component/previewCodeDialg.vue
Normal file
@@ -0,0 +1,93 @@
|
||||
<template>
|
||||
<div class="previewCode">
|
||||
<el-tabs v-model="activeName">
|
||||
<el-tab-pane v-for="(item, key) in previewCode" :key="key" :label="key" :name="key">
|
||||
<div :id="key" class="tab-info" />
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import marked from 'marked'
|
||||
import hljs from 'highlight.js'
|
||||
import 'highlight.js/styles/atelier-plateau-light.css'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { onMounted, ref } from 'vue'
|
||||
|
||||
const props = defineProps({
|
||||
previewCode: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const activeName = ref('')
|
||||
onMounted(() => {
|
||||
marked.setOptions({
|
||||
renderer: new marked.Renderer(),
|
||||
highlight: function(code) {
|
||||
return hljs.highlightAuto(code).value
|
||||
},
|
||||
pedantic: false,
|
||||
gfm: true,
|
||||
tables: true,
|
||||
breaks: false,
|
||||
sanitize: false,
|
||||
smartLists: true,
|
||||
smartypants: false,
|
||||
xhtml: false
|
||||
})
|
||||
for (const key in props.previewCode) {
|
||||
if (activeName.value === '') {
|
||||
activeName.value = key
|
||||
}
|
||||
document.getElementById(key).innerHTML = marked(props.previewCode[key])
|
||||
}
|
||||
})
|
||||
|
||||
const selectText = () => {
|
||||
const element = document.getElementById(activeName.value)
|
||||
if (document.body.createTextRange) {
|
||||
const range = document.body.createTextRange()
|
||||
range.moveToElementText(element)
|
||||
range.select()
|
||||
} else if (window.getSelection) {
|
||||
const selection = window.getSelection()
|
||||
const range = document.createRange()
|
||||
range.selectNodeContents(element)
|
||||
selection.removeAllRanges()
|
||||
selection.addRange(range)
|
||||
} else {
|
||||
alert('none')
|
||||
}
|
||||
}
|
||||
const copy = () => {
|
||||
selectText()
|
||||
document.execCommand('copy')
|
||||
ElMessage.success('复制成功')
|
||||
}
|
||||
|
||||
defineExpose({ copy })
|
||||
|
||||
</script>
|
||||
|
||||
<script>
|
||||
|
||||
export default {
|
||||
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.previewCode {
|
||||
.tab-info {
|
||||
height: 50vh;
|
||||
background: #fff;
|
||||
padding: 0 20px;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
}
|
||||
</style>
|
723
src/view/systemTools/autoCode/index.vue
Normal file
723
src/view/systemTools/autoCode/index.vue
Normal file
@@ -0,0 +1,723 @@
|
||||
<template>
|
||||
<div>
|
||||
<warning-bar href="https://www.bilibili.com/video/BV1kv4y1g7nT?p=3" title="此功能为开发环境使用,不建议发布到生产,具体使用效果请看视频https://www.bilibili.com/video/BV1kv4y1g7nT?p=3" />
|
||||
<!-- 从数据库直接获取字段 -->
|
||||
<div class="gva-search-box">
|
||||
<el-collapse v-model="activeNames" style="margin-bottom:12px">
|
||||
<el-collapse-item name="1">
|
||||
<template #title>
|
||||
<div :style="{fontSize:'16px',paddingLeft:'20px'}">
|
||||
点这里从现有数据库创建代码
|
||||
<el-icon class="header-icon ">
|
||||
<pointer />
|
||||
</el-icon>
|
||||
</div>
|
||||
</template>
|
||||
<el-form ref="getTableForm" style="margin-top:24px" :inline="true" :model="dbform" label-width="120px">
|
||||
<el-form-item label="业务库" prop="selectDBtype">
|
||||
<template #label>
|
||||
<el-tooltip content="注:需要提前到db-list自行配置多数据库,如未配置需配置后重启服务方可使用。(此处可选择对应库表,可理解为从哪个库选择表)" placement="bottom" effect="light">
|
||||
<div> 业务库 <el-icon><QuestionFilled /></el-icon> </div>
|
||||
</el-tooltip>
|
||||
</template>
|
||||
<el-select v-model="dbform.businessDB" clearable style="width:194px" placeholder="选择业务库" @change="getDbFunc">
|
||||
<el-option
|
||||
v-for="item in dbList"
|
||||
:key="item.aliasName"
|
||||
:value="item.aliasName"
|
||||
:label="item.aliasName"
|
||||
:disabled="item.disable"
|
||||
>
|
||||
<div>
|
||||
<span>{{ item.aliasName }}</span>
|
||||
<span style="float:right;color:#8492a6;font-size:13px">{{ item.dbName }}</span>
|
||||
</div>
|
||||
</el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="数据库名" prop="structName">
|
||||
<el-select v-model="dbform.dbName" clearable filterable placeholder="请选择数据库" @change="getTableFunc">
|
||||
<el-option
|
||||
v-for="item in dbOptions"
|
||||
:key="item.database"
|
||||
:label="item.database"
|
||||
:value="item.database"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="表名" prop="structName">
|
||||
<el-select
|
||||
v-model="dbform.tableName"
|
||||
:disabled="!dbform.dbName"
|
||||
filterable
|
||||
placeholder="请选择表"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in tableOptions"
|
||||
:key="item.tableName"
|
||||
:label="item.tableName"
|
||||
:value="item.tableName"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button size="small" type="primary" @click="getColumnFunc">使用此表创建</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-collapse-item>
|
||||
</el-collapse>
|
||||
</div>
|
||||
<div class="gva-search-box">
|
||||
<!-- 初始版本自动化代码工具 -->
|
||||
<el-form ref="autoCodeForm" :rules="rules" :model="form" label-width="120px" :inline="true">
|
||||
<el-form-item label="Struct名称" prop="structName">
|
||||
<el-input v-model="form.structName" placeholder="首字母自动转换大写" />
|
||||
</el-form-item>
|
||||
<el-form-item label="TableName" prop="tableName">
|
||||
<el-input v-model="form.tableName" placeholder="指定表名(非必填)" />
|
||||
</el-form-item>
|
||||
<el-form-item label="Struct简称" prop="abbreviation">
|
||||
<el-input v-model="form.abbreviation" placeholder="简称会作为入参对象名和路由group" />
|
||||
</el-form-item>
|
||||
<el-form-item label="Struct中文名称" prop="description">
|
||||
<el-input v-model="form.description" placeholder="中文描述作为自动api描述" />
|
||||
</el-form-item>
|
||||
<el-form-item label="文件名称" prop="packageName">
|
||||
<el-input v-model="form.packageName" placeholder="生成文件的默认名称(建议为驼峰格式,首字母小写,如sysXxxXxxx)" @blur="toLowerCaseFunc(form,'packageName')" />
|
||||
</el-form-item>
|
||||
<el-form-item label="Package(包)" prop="package">
|
||||
<el-select v-model="form.package" style="width:194px">
|
||||
<el-option v-for="item in pkgs" :key="item.ID" :value="item.packageName" :label="item.packageName" />
|
||||
</el-select>
|
||||
<el-icon class="auto-icon" @click="getPkgs"><refresh /></el-icon>
|
||||
<el-icon class="auto-icon" @click="goPkgs"><document-add /></el-icon>
|
||||
</el-form-item>
|
||||
<el-form-item label="业务库" prop="businessDB">
|
||||
<template #label>
|
||||
<el-tooltip content="注:需要提前到db-list自行配置多数据库,此项为空则会使用gva本库创建自动化代码(global.GVA_DB),填写后则会创建指定库的代码(global.MustGetGlobalDBByDBName(dbname))" placement="bottom" effect="light">
|
||||
<div> 业务库 <el-icon><QuestionFilled /></el-icon> </div>
|
||||
</el-tooltip>
|
||||
</template>
|
||||
<el-select
|
||||
v-model="form.businessDB"
|
||||
style="width:194px"
|
||||
placeholder="选择业务库"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in dbList"
|
||||
:key="item.aliasName"
|
||||
:value="item.aliasName"
|
||||
:label="item.aliasName"
|
||||
:disabled="item.disable"
|
||||
>
|
||||
<div>
|
||||
<span>{{ item.aliasName }}</span>
|
||||
<span style="float:right;color:#8492a6;font-size:13px">{{ item.dbName }}</span>
|
||||
</div>
|
||||
</el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<template #label>
|
||||
<el-tooltip content="注:会自动在结构体添加 created_by updated_by deleted_by,方便用户进行资源权限控制" placement="bottom" effect="light">
|
||||
<div> 创建资源标识 <el-icon><QuestionFilled /></el-icon> </div>
|
||||
</el-tooltip>
|
||||
</template>
|
||||
<el-checkbox v-model="form.autoCreateResource" />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<template #label>
|
||||
<el-tooltip content="注:把自动生成的API注册进数据库" placement="bottom" effect="light">
|
||||
<div> 自动创建API </div>
|
||||
</el-tooltip>
|
||||
</template>
|
||||
<el-checkbox v-model="form.autoCreateApiToSql" />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<template #label>
|
||||
<el-tooltip content="注:自动迁移生成的文件到yaml配置的对应位置" placement="bottom" effect="light">
|
||||
<div> 自动移动文件 </div>
|
||||
</el-tooltip>
|
||||
</template>
|
||||
<el-checkbox v-model="form.autoMoveFile" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
<!-- 组件列表 -->
|
||||
<div class="gva-table-box">
|
||||
<div class="gva-btn-list">
|
||||
<el-button size="small" type="primary" @click="editAndAddField()">新增Field</el-button>
|
||||
</div>
|
||||
<el-table :data="form.fields">
|
||||
<el-table-column align="left" type="index" label="序列" width="60" />
|
||||
<el-table-column align="left" prop="fieldName" label="Field名" width="160">
|
||||
<template #default="{row}">
|
||||
<el-input v-model="row.fieldName" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="left" prop="fieldDesc" label="中文名" width="160">
|
||||
<template #default="{row}">
|
||||
<el-input v-model="row.fieldDesc" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="left" prop="require" label="必填">
|
||||
<template #default="{row}"> <el-checkbox v-model="row.require" /></template>
|
||||
</el-table-column>
|
||||
<el-table-column align="left" prop="sort" label="排序">
|
||||
<template #default="{row}"> <el-checkbox v-model="row.sort" /> </template>
|
||||
</el-table-column>
|
||||
<el-table-column align="left" prop="fieldJson" width="160px" label="FieldJson">
|
||||
<template #default="{row}">
|
||||
<el-input v-model="row.fieldJson" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="left" prop="fieldType" label="Field数据类型" width="160">
|
||||
<template #default="{row}">
|
||||
<el-select
|
||||
v-model="row.fieldType"
|
||||
style="width:100%"
|
||||
placeholder="请选择field数据类型"
|
||||
clearable
|
||||
>
|
||||
<el-option
|
||||
v-for="item in typeOptions"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
/>
|
||||
</el-select>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="left" prop="dataTypeLong" label="数据库字段长度" width="160">
|
||||
<template #default="{row}">
|
||||
<el-input v-model="row.dataTypeLong" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="left" prop="columnName" label="数据库字段" width="160">
|
||||
<template #default="{row}">
|
||||
<el-input v-model="row.columnName" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="left" prop="comment" label="数据库字段描述" width="160">
|
||||
<template #default="{row}">
|
||||
<el-input v-model="row.columnName" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="left" prop="fieldSearchType" label="搜索条件" width="130">
|
||||
<template #default="{row}">
|
||||
<el-select
|
||||
v-model="row.fieldSearchType"
|
||||
style="width:100%"
|
||||
placeholder="请选择Field查询条件"
|
||||
clearable
|
||||
>
|
||||
<el-option
|
||||
v-for="item in typeSearchOptions"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
:disabled="
|
||||
(row.fieldType!=='string'&&item.value==='LIKE')||
|
||||
((row.fieldType!=='int'&&row.fieldType!=='time.Time'&&row.fieldType!=='float64')&&(item.value==='BETWEEN' || item.value==='NOT BETWEEN'))
|
||||
"
|
||||
/>
|
||||
</el-select>
|
||||
</template>
|
||||
|
||||
</el-table-column>
|
||||
<el-table-column align="left" label="操作" width="300" fixed="right">
|
||||
<template #default="scope">
|
||||
<el-button
|
||||
size="small"
|
||||
type="primary"
|
||||
link
|
||||
icon="edit"
|
||||
@click="editAndAddField(scope.row)"
|
||||
>高级编辑</el-button>
|
||||
<el-button
|
||||
size="small"
|
||||
type="primary"
|
||||
link
|
||||
:disabled="scope.$index === 0"
|
||||
@click="moveUpField(scope.$index)"
|
||||
>上移</el-button>
|
||||
<el-button
|
||||
size="small"
|
||||
type="primary"
|
||||
link
|
||||
:disabled="(scope.$index + 1) === form.fields.length"
|
||||
@click="moveDownField(scope.$index)"
|
||||
>下移</el-button>
|
||||
<el-popover v-model="scope.row.visible" placement="top">
|
||||
<p>确定删除吗?</p>
|
||||
<div style="text-align: right; margin-top: 8px;">
|
||||
<el-button size="small" type="primary" link @click="scope.row.visible = false">取消</el-button>
|
||||
<el-button type="primary" size="small" @click="deleteField(scope.$index)">确定</el-button>
|
||||
</div>
|
||||
<template #reference>
|
||||
<el-button size="small" type="primary" link icon="delete" @click="scope.row.visible = true">删除</el-button>
|
||||
</template>
|
||||
</el-popover>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<!-- 组件列表 -->
|
||||
<div class="gva-btn-list justify-content-flex-end auto-btn-list">
|
||||
<el-button size="small" type="primary" @click="enterForm(true)">预览代码</el-button>
|
||||
<el-button size="small" type="primary" @click="enterForm(false)">生成代码</el-button>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 组件弹窗 -->
|
||||
<el-dialog v-model="dialogFlag" width="70%" title="组件内容">
|
||||
<FieldDialog v-if="dialogFlag" ref="fieldDialogNode" :dialog-middle="dialogMiddle" />
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button size="small" @click="closeDialog">取 消</el-button>
|
||||
<el-button size="small" type="primary" @click="enterDialog">确 定</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<el-dialog v-model="previewFlag">
|
||||
<template #header>
|
||||
<div class="previewCodeTool">
|
||||
<p>操作栏:</p>
|
||||
<el-button size="small" type="primary" @click="selectText">全选</el-button>
|
||||
<el-button size="small" type="primary" @click="copy">复制</el-button>
|
||||
</div>
|
||||
</template>
|
||||
<PreviewCodeDialog v-if="previewFlag" ref="previewNode" :preview-code="preViewCode" />
|
||||
<template #footer>
|
||||
<div class="dialog-footer" style="padding-top:14px;padding-right:14px">
|
||||
<el-button size="small" type="primary" @click="previewFlag = false">确 定</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
|
||||
import FieldDialog from '@/view/systemTools/autoCode/component/fieldDialog.vue'
|
||||
import PreviewCodeDialog from '@/view/systemTools/autoCode/component/previewCodeDialg.vue'
|
||||
import { toUpperCase, toHump, toSQLLine, toLowerCase } from '@/utils/stringFun'
|
||||
import { createTemp, getDB, getTable, getColumn, preview, getMeta, getPackageApi } from '@/api/autoCode'
|
||||
import { getDict } from '@/utils/dictionary'
|
||||
import { ref, getCurrentInstance, reactive, watch, toRaw } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import WarningBar from '@/components/warningBar/warningBar.vue'
|
||||
|
||||
const typeOptions = ref([
|
||||
{
|
||||
label: '字符串',
|
||||
value: 'string'
|
||||
},
|
||||
{
|
||||
label: '整型',
|
||||
value: 'int'
|
||||
},
|
||||
{
|
||||
label: '布尔值',
|
||||
value: 'bool'
|
||||
},
|
||||
{
|
||||
label: '浮点型',
|
||||
value: 'float64'
|
||||
},
|
||||
{
|
||||
label: '时间',
|
||||
value: 'time.Time'
|
||||
},
|
||||
{
|
||||
label: '枚举',
|
||||
value: 'enum'
|
||||
}
|
||||
])
|
||||
|
||||
const typeSearchOptions = ref([
|
||||
{
|
||||
label: '=',
|
||||
value: '='
|
||||
},
|
||||
{
|
||||
label: '<>',
|
||||
value: '<>'
|
||||
},
|
||||
{
|
||||
label: '>',
|
||||
value: '>'
|
||||
},
|
||||
{
|
||||
label: '<',
|
||||
value: '<'
|
||||
},
|
||||
{
|
||||
label: 'LIKE',
|
||||
value: 'LIKE'
|
||||
},
|
||||
{
|
||||
label: 'BETWEEN',
|
||||
value: 'BETWEEN'
|
||||
},
|
||||
{
|
||||
label: 'NOT BETWEEN',
|
||||
value: 'NOT BETWEEN'
|
||||
}
|
||||
])
|
||||
|
||||
const fieldTemplate = {
|
||||
fieldName: '',
|
||||
fieldDesc: '',
|
||||
fieldType: '',
|
||||
dataType: '',
|
||||
fieldJson: '',
|
||||
columnName: '',
|
||||
dataTypeLong: '',
|
||||
comment: '',
|
||||
require: false,
|
||||
sort: false,
|
||||
errorText: '',
|
||||
clearable: true,
|
||||
fieldSearchType: '',
|
||||
dictType: ''
|
||||
}
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
const activeNames = reactive([])
|
||||
const preViewCode = ref({})
|
||||
const dbform = ref({
|
||||
businessDB: '',
|
||||
dbName: '',
|
||||
tableName: ''
|
||||
})
|
||||
const tableOptions = ref([])
|
||||
const addFlag = ref('')
|
||||
const fdMap = ref({})
|
||||
const form = ref({
|
||||
structName: '',
|
||||
tableName: '',
|
||||
packageName: '',
|
||||
package: '',
|
||||
abbreviation: '',
|
||||
description: '',
|
||||
businessDB: '',
|
||||
autoCreateApiToSql: true,
|
||||
autoMoveFile: true,
|
||||
autoCreateResource: false,
|
||||
fields: []
|
||||
})
|
||||
const rules = ref({
|
||||
structName: [
|
||||
{ required: true, message: '请输入结构体名称', trigger: 'blur' }
|
||||
],
|
||||
abbreviation: [
|
||||
{ required: true, message: '请输入结构体简称', trigger: 'blur' }
|
||||
],
|
||||
description: [
|
||||
{ required: true, message: '请输入结构体描述', trigger: 'blur' }
|
||||
],
|
||||
packageName: [
|
||||
{
|
||||
required: true,
|
||||
message: '文件名称:sysXxxxXxxx',
|
||||
trigger: 'blur'
|
||||
}
|
||||
],
|
||||
package: [
|
||||
{ required: true, message: '请选择package', trigger: 'blur' }
|
||||
]
|
||||
})
|
||||
const dialogMiddle = ref({})
|
||||
const bk = ref({})
|
||||
const dialogFlag = ref(false)
|
||||
const previewFlag = ref(false)
|
||||
|
||||
const toLowerCaseFunc = (form, key) => {
|
||||
form[key] = toLowerCase(form[key])
|
||||
}
|
||||
const previewNode = ref(null)
|
||||
const selectText = () => {
|
||||
previewNode.value.selectText()
|
||||
}
|
||||
const copy = () => {
|
||||
previewNode.value.copy()
|
||||
}
|
||||
const editAndAddField = (item) => {
|
||||
dialogFlag.value = true
|
||||
if (item) {
|
||||
addFlag.value = 'edit'
|
||||
bk.value = JSON.parse(JSON.stringify(item))
|
||||
dialogMiddle.value = item
|
||||
} else {
|
||||
addFlag.value = 'add'
|
||||
dialogMiddle.value = JSON.parse(JSON.stringify(fieldTemplate))
|
||||
}
|
||||
}
|
||||
const moveUpField = (index) => {
|
||||
if (index === 0) {
|
||||
return
|
||||
}
|
||||
const oldUpField = form.value.fields[index - 1]
|
||||
form.value.fields.splice(index - 1, 1)
|
||||
form.value.fields.splice(index, 0, oldUpField)
|
||||
}
|
||||
const moveDownField = (index) => {
|
||||
const fCount = form.value.fields.length
|
||||
if (index === fCount - 1) {
|
||||
return
|
||||
}
|
||||
const oldDownField = form.value.fields[index + 1]
|
||||
form.value.fields.splice(index + 1, 1)
|
||||
form.value.fields.splice(index, 0, oldDownField)
|
||||
}
|
||||
|
||||
const currentInstance = getCurrentInstance()
|
||||
const enterDialog = () => {
|
||||
currentInstance.refs.fieldDialogNode.fieldDialogFrom.validate(valid => {
|
||||
if (valid) {
|
||||
dialogMiddle.value.fieldName = toUpperCase(
|
||||
dialogMiddle.value.fieldName
|
||||
)
|
||||
if (addFlag.value === 'add') {
|
||||
form.value.fields.push(dialogMiddle.value)
|
||||
}
|
||||
dialogFlag.value = false
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
})
|
||||
}
|
||||
const closeDialog = () => {
|
||||
if (addFlag.value === 'edit') {
|
||||
dialogMiddle.value = bk.value
|
||||
}
|
||||
dialogFlag.value = false
|
||||
}
|
||||
const deleteField = (index) => {
|
||||
form.value.fields.splice(index, 1)
|
||||
}
|
||||
const autoCodeForm = ref(null)
|
||||
const enterForm = async(isPreview) => {
|
||||
if (form.value.fields.length <= 0) {
|
||||
ElMessage({
|
||||
type: 'error',
|
||||
message: '请填写至少一个field'
|
||||
})
|
||||
return false
|
||||
}
|
||||
if (
|
||||
form.value.fields.some(item => item.fieldName === form.value.structName)
|
||||
) {
|
||||
ElMessage({
|
||||
type: 'error',
|
||||
message: '存在与结构体同名的字段'
|
||||
})
|
||||
return false
|
||||
}
|
||||
autoCodeForm.value.validate(async valid => {
|
||||
if (valid) {
|
||||
for (const key in form.value) {
|
||||
if (typeof form.value[key] === 'string') {
|
||||
form.value[key] = form.value[key].trim()
|
||||
}
|
||||
}
|
||||
form.value.structName = toUpperCase(form.value.structName)
|
||||
form.value.tableName = form.value.tableName.replace(' ', '')
|
||||
if (!form.value.tableName) {
|
||||
form.value.tableName = toSQLLine(toLowerCase(form.value.structName))
|
||||
}
|
||||
if (form.value.structName === form.value.abbreviation) {
|
||||
ElMessage({
|
||||
type: 'error',
|
||||
message: 'structName和struct简称不能相同'
|
||||
})
|
||||
return false
|
||||
}
|
||||
form.value.humpPackageName = toSQLLine(form.value.packageName)
|
||||
if (isPreview) {
|
||||
const data = await preview(form.value)
|
||||
preViewCode.value = data.data.autoCode
|
||||
previewFlag.value = true
|
||||
} else {
|
||||
const data = await createTemp(form.value)
|
||||
if (data.headers?.success === 'false') {
|
||||
return
|
||||
} else {
|
||||
if (form.value.autoMoveFile) {
|
||||
ElMessage({
|
||||
type: 'success',
|
||||
message: '自动化代码创建成功,自动移动成功'
|
||||
})
|
||||
return
|
||||
}
|
||||
ElMessage({
|
||||
type: 'success',
|
||||
message: '自动化代码创建成功,正在下载'
|
||||
})
|
||||
}
|
||||
const blob = new Blob([data])
|
||||
const fileName = 'ginvueadmin.zip'
|
||||
if ('download' in document.createElement('a')) {
|
||||
// 不是IE浏览器
|
||||
const url = window.URL.createObjectURL(blob)
|
||||
const link = document.createElement('a')
|
||||
link.style.display = 'none'
|
||||
link.href = url
|
||||
link.setAttribute('download', fileName)
|
||||
document.body.appendChild(link)
|
||||
link.click()
|
||||
document.body.removeChild(link) // 下载完成移除元素
|
||||
window.URL.revokeObjectURL(url) // 释放掉blob对象
|
||||
} else {
|
||||
// IE 10+
|
||||
window.navigator.msSaveBlob(blob, fileName)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const dbList = ref([])
|
||||
const dbOptions = ref([])
|
||||
|
||||
const getDbFunc = async() => {
|
||||
dbform.value.dbName = ''
|
||||
dbform.value.tableName = ''
|
||||
const res = await getDB({ businessDB: dbform.value.businessDB })
|
||||
if (res.code === 0) {
|
||||
dbOptions.value = res.data.dbs
|
||||
dbList.value = res.data.dbList
|
||||
}
|
||||
}
|
||||
const getTableFunc = async() => {
|
||||
const res = await getTable({ businessDB: dbform.value.businessDB, dbName: dbform.value.dbName })
|
||||
if (res.code === 0) {
|
||||
tableOptions.value = res.data.tables
|
||||
}
|
||||
dbform.value.tableName = ''
|
||||
}
|
||||
|
||||
const getColumnFunc = async() => {
|
||||
const gormModelList = ['id', 'created_at', 'updated_at', 'deleted_at']
|
||||
const res = await getColumn(dbform.value)
|
||||
if (res.code === 0) {
|
||||
let dbtype = ''
|
||||
if (dbform.value.businessDB !== '') {
|
||||
const dbtmp = dbList.value.find(item => item.aliasName === dbform.value.businessDB)
|
||||
console.log(dbtmp)
|
||||
const dbraw = toRaw(dbtmp)
|
||||
console.log(dbraw)
|
||||
dbtype = dbraw.dbtype
|
||||
}
|
||||
const tbHump = toHump(dbform.value.tableName)
|
||||
form.value.structName = toUpperCase(tbHump)
|
||||
form.value.tableName = dbform.value.tableName
|
||||
form.value.packageName = tbHump
|
||||
form.value.abbreviation = tbHump
|
||||
form.value.description = tbHump + '表'
|
||||
form.value.autoCreateApiToSql = true
|
||||
form.value.autoMoveFile = true
|
||||
form.value.fields = []
|
||||
res.data.columns &&
|
||||
res.data.columns.forEach(item => {
|
||||
if (!gormModelList.some(gormfd => gormfd === item.columnName)) {
|
||||
const fbHump = toHump(item.columnName)
|
||||
form.value.fields.push({
|
||||
fieldName: toUpperCase(fbHump),
|
||||
fieldDesc: item.columnComment || fbHump + '字段',
|
||||
fieldType: fdMap.value[item.dataType],
|
||||
dataType: item.dataType,
|
||||
fieldJson: fbHump,
|
||||
dataTypeLong: item.dataTypeLong && item.dataTypeLong.split(',')[0],
|
||||
columnName: dbtype == 'oracle' ? item.columnName.toUpperCase() : item.columnName,
|
||||
comment: item.columnComment,
|
||||
require: false,
|
||||
errorText: '',
|
||||
clearable: true,
|
||||
fieldSearchType: '',
|
||||
dictType: ''
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
const setFdMap = async() => {
|
||||
const fdTypes = ['string', 'int', 'bool', 'float64', 'time.Time']
|
||||
fdTypes.forEach(async fdtype => {
|
||||
const res = await getDict(fdtype)
|
||||
res && res.forEach(item => {
|
||||
fdMap.value[item.label] = fdtype
|
||||
})
|
||||
})
|
||||
}
|
||||
const getAutoCodeJson = async(id) => {
|
||||
const res = await getMeta({ id: Number(id) })
|
||||
if (res.code === 0) {
|
||||
form.value = JSON.parse(res.data.meta)
|
||||
}
|
||||
}
|
||||
|
||||
const pkgs = ref([])
|
||||
const getPkgs = async() => {
|
||||
const res = await getPackageApi()
|
||||
if (res.code === 0) {
|
||||
pkgs.value = res.data.pkgs
|
||||
}
|
||||
}
|
||||
|
||||
const goPkgs = () => {
|
||||
router.push({ name: 'autoPkg' })
|
||||
}
|
||||
|
||||
const init = () => {
|
||||
getDbFunc()
|
||||
setFdMap()
|
||||
getPkgs()
|
||||
const id = route.params.id
|
||||
if (id) {
|
||||
getAutoCodeJson(id)
|
||||
}
|
||||
}
|
||||
init()
|
||||
|
||||
watch(() => route.params.id, (id) => {
|
||||
if (route.name === 'autoCodeEdit') {
|
||||
init()
|
||||
}
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<script>
|
||||
|
||||
export default {
|
||||
name: 'AutoCode'
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.previewCodeTool {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 5px 0;
|
||||
}
|
||||
.button-box {
|
||||
padding: 10px 20px;
|
||||
.el-button {
|
||||
margin-right: 20px;
|
||||
float: right;
|
||||
}
|
||||
}
|
||||
.auto-btn-list{
|
||||
margin-top: 16px;
|
||||
}
|
||||
.auto-icon{
|
||||
margin-left: 6px;
|
||||
color: #666;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
</style>
|
188
src/view/systemTools/autoCodeAdmin/index.vue
Normal file
188
src/view/systemTools/autoCodeAdmin/index.vue
Normal file
@@ -0,0 +1,188 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="gva-table-box">
|
||||
<div class="gva-btn-list">
|
||||
<el-button size="small" type="primary" icon="plus" @click="goAutoCode(null)">新增</el-button>
|
||||
</div>
|
||||
<el-table :data="tableData">
|
||||
<el-table-column
|
||||
type="selection"
|
||||
width="55"
|
||||
/>
|
||||
<el-table-column align="left" label="id" width="60" prop="ID" />
|
||||
<el-table-column align="left" label="日期" width="180">
|
||||
<template #default="scope">{{ formatDate(scope.row.CreatedAt) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="left" label="结构体名" min-width="150" prop="structName" />
|
||||
<el-table-column align="left" label="结构体描述" min-width="150" prop="structCNName" />
|
||||
<el-table-column align="left" label="表名称" min-width="150" prop="tableName" />
|
||||
<el-table-column align="left" label="回滚标记" min-width="150" prop="flag">
|
||||
<template #default="scope">
|
||||
<el-tag
|
||||
v-if="scope.row.flag"
|
||||
type="danger"
|
||||
size="small"
|
||||
effect="dark"
|
||||
>
|
||||
已回滚
|
||||
</el-tag>
|
||||
<el-tag
|
||||
v-else
|
||||
size="small"
|
||||
type="success"
|
||||
effect="dark"
|
||||
>
|
||||
未回滚
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="left" label="操作" min-width="240">
|
||||
<template #default="scope">
|
||||
<div>
|
||||
<el-button size="small" type="primary" link :disabled="scope.row.flag === 1" @click="rollbackFunc(scope.row,true)">回滚(删表)</el-button>
|
||||
<el-button size="small" type="primary" link :disabled="scope.row.flag === 1" @click="rollbackFunc(scope.row,false)">回滚(不删表)</el-button>
|
||||
<el-button size="small" type="primary" link @click="goAutoCode(scope.row)">复用</el-button>
|
||||
<el-button size="small" type="primary" link @click="deleteRow(scope.row)">删除</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<div class="gva-pagination">
|
||||
<el-pagination
|
||||
:current-page="page"
|
||||
:page-size="pageSize"
|
||||
:page-sizes="[10, 30, 50, 100]"
|
||||
:total="total"
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
@current-change="handleCurrentChange"
|
||||
@size-change="handleSizeChange"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'AutoCodeAdmin',
|
||||
}
|
||||
</script>
|
||||
|
||||
<script setup>
|
||||
import { getSysHistory, rollback, delSysHistory } from '@/api/autoCode.js'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { ref } from 'vue'
|
||||
import { formatDate } from '@/utils/format'
|
||||
const router = useRouter()
|
||||
|
||||
const page = ref(1)
|
||||
const total = ref(0)
|
||||
const pageSize = ref(10)
|
||||
const tableData = ref([])
|
||||
|
||||
// 分页
|
||||
const handleSizeChange = (val) => {
|
||||
pageSize.value = val
|
||||
getTableData()
|
||||
}
|
||||
|
||||
const handleCurrentChange = (val) => {
|
||||
page.value = val
|
||||
getTableData()
|
||||
}
|
||||
|
||||
// 查询
|
||||
const getTableData = async() => {
|
||||
const table = await getSysHistory({
|
||||
page: page.value,
|
||||
pageSize: pageSize.value
|
||||
})
|
||||
if (table.code === 0) {
|
||||
tableData.value = table.data.list
|
||||
total.value = table.data.total
|
||||
page.value = table.data.page
|
||||
pageSize.value = table.data.pageSize
|
||||
}
|
||||
}
|
||||
|
||||
getTableData()
|
||||
|
||||
const deleteRow = async(row) => {
|
||||
ElMessageBox.confirm('此操作将删除本历史, 是否继续?', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}).then(async() => {
|
||||
const res = await delSysHistory({ id: Number(row.ID) })
|
||||
if (res.code === 0) {
|
||||
ElMessage.success('删除成功')
|
||||
getTableData()
|
||||
}
|
||||
})
|
||||
}
|
||||
const rollbackFunc = async(row, flag) => {
|
||||
if (flag) {
|
||||
ElMessageBox.confirm(`此操作将删除自动创建的文件和api(会删除表!!!), 是否继续?`, '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}).then(async() => {
|
||||
ElMessageBox.confirm(`此操作将删除自动创建的文件和api(会删除表!!!), 请继续确认!!!`, '会删除表', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}).then(async() => {
|
||||
ElMessageBox.confirm(`此操作将删除自动创建的文件和api(会删除表!!!), 请继续确认!!!`, '会删除表', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}).then(async() => {
|
||||
const res = await rollback({ id: Number(row.ID), deleteTable: flag })
|
||||
if (res.code === 0) {
|
||||
ElMessage.success('回滚成功')
|
||||
getTableData()
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
} else {
|
||||
ElMessageBox.confirm(`此操作将删除自动创建的文件和api, 是否继续?`, '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}).then(async() => {
|
||||
const res = await rollback({ id: Number(row.ID), deleteTable: flag })
|
||||
if (res.code === 0) {
|
||||
ElMessage.success('回滚成功')
|
||||
getTableData()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
const goAutoCode = (row) => {
|
||||
if (row) {
|
||||
router.push({ name: 'autoCodeEdit', params: {
|
||||
id: row.ID
|
||||
}})
|
||||
} else {
|
||||
router.push({ name: 'autoCode' })
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.button-box {
|
||||
padding: 10px 20px;
|
||||
.el-button {
|
||||
float: right;
|
||||
}
|
||||
}
|
||||
.el-tag--mini {
|
||||
margin-left: 5px;
|
||||
}
|
||||
.warning {
|
||||
color: #dc143c;
|
||||
}
|
||||
</style>
|
160
src/view/systemTools/autoPkg/autoPkg.vue
Normal file
160
src/view/systemTools/autoPkg/autoPkg.vue
Normal file
@@ -0,0 +1,160 @@
|
||||
<template>
|
||||
<div>
|
||||
<warning-bar href="https://www.bilibili.com/video/BV1kv4y1g7nT?p=3" title="此功能为开发环境使用,不建议发布到生产,具体使用效果请看视频https://www.bilibili.com/video/BV1kv4y1g7nT?p=3" />
|
||||
<div class="gva-table-box">
|
||||
<div class="gva-btn-list">
|
||||
<el-button size="small" type="primary" icon="plus" @click="openDialog('addApi')">新增</el-button>
|
||||
</div>
|
||||
<el-table :data="tableData">
|
||||
<el-table-column align="left" label="id" width="60" prop="ID" />
|
||||
<el-table-column align="left" label="包名" width="150" prop="packageName" />
|
||||
<el-table-column align="left" label="展示名" width="150" prop="label" />
|
||||
<el-table-column align="left" label="描述" min-width="150" prop="desc" />
|
||||
|
||||
<el-table-column align="left" label="操作" width="200">
|
||||
<template #default="scope">
|
||||
<el-button
|
||||
icon="delete"
|
||||
size="small"
|
||||
type="primary"
|
||||
link
|
||||
@click="deleteApiFunc(scope.row)"
|
||||
>删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
</div>
|
||||
|
||||
<el-dialog v-model="dialogFormVisible" :before-close="closeDialog" title="创建Package">
|
||||
<warning-bar title="新增Pkg用于自动化代码使用" />
|
||||
<el-form ref="pkgForm" :model="form" :rules="rules" label-width="80px">
|
||||
<el-form-item label="包名" prop="packageName">
|
||||
<el-input v-model="form.packageName" autocomplete="off" />
|
||||
</el-form-item>
|
||||
<el-form-item label="展示名" prop="label">
|
||||
<el-input v-model="form.label" autocomplete="off" />
|
||||
</el-form-item>
|
||||
<el-form-item label="描述" prop="desc">
|
||||
<el-input v-model="form.desc" autocomplete="off" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button size="small" @click="closeDialog">取 消</el-button>
|
||||
<el-button size="small" type="primary" @click="enterDialog">确 定</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'AutoPkg',
|
||||
}
|
||||
</script>
|
||||
|
||||
<script setup>
|
||||
import {
|
||||
createPackageApi,
|
||||
getPackageApi,
|
||||
deletePackageApi,
|
||||
} from '@/api/autoCode'
|
||||
import { ref } from 'vue'
|
||||
import WarningBar from '@/components/warningBar/warningBar.vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
|
||||
const form = ref({
|
||||
packageName: '',
|
||||
label: '',
|
||||
desc: '',
|
||||
})
|
||||
|
||||
const validateNum = (rule, value, callback) => {
|
||||
if ((/^\d+$/.test(value[0]))) {
|
||||
callback(new Error('不能够以数字开头'))
|
||||
} else {
|
||||
callback()
|
||||
}
|
||||
}
|
||||
|
||||
const rules = ref({
|
||||
packageName: [
|
||||
{ required: true, message: '请输入包名', trigger: 'blur' },
|
||||
{ validator: validateNum, trigger: 'blur' }
|
||||
],
|
||||
})
|
||||
|
||||
const dialogFormVisible = ref(false)
|
||||
const openDialog = () => {
|
||||
dialogFormVisible.value = true
|
||||
}
|
||||
|
||||
const closeDialog = () => {
|
||||
dialogFormVisible.value = false
|
||||
form.value = {
|
||||
packageName: '',
|
||||
label: '',
|
||||
desc: '',
|
||||
}
|
||||
}
|
||||
|
||||
const pkgForm = ref(null)
|
||||
const enterDialog = async() => {
|
||||
pkgForm.value.validate(async valid => {
|
||||
if (valid) {
|
||||
const res = await createPackageApi(form.value)
|
||||
if (res.code === 0) {
|
||||
ElMessage({
|
||||
type: 'success',
|
||||
message: '添加成功',
|
||||
showClose: true
|
||||
})
|
||||
}
|
||||
getTableData()
|
||||
closeDialog()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const tableData = ref([])
|
||||
const getTableData = async() => {
|
||||
const table = await getPackageApi()
|
||||
if (table.code === 0) {
|
||||
tableData.value = table.data.pkgs
|
||||
}
|
||||
}
|
||||
|
||||
const deleteApiFunc = async(row) => {
|
||||
ElMessageBox.confirm('此操作仅删除数据库中的pkg存储,后端相应目录结构请自行删除与数据库保持一致!', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
})
|
||||
.then(async() => {
|
||||
const res = await deletePackageApi(row)
|
||||
if (res.code === 0) {
|
||||
ElMessage({
|
||||
type: 'success',
|
||||
message: '删除成功!'
|
||||
})
|
||||
getTableData()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
getTableData()
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.button-box {
|
||||
padding: 10px 20px;
|
||||
.el-button {
|
||||
float: right;
|
||||
}
|
||||
}
|
||||
.warning {
|
||||
color: #dc143c;
|
||||
}
|
||||
</style>
|
220
src/view/systemTools/autoPlug/autoPlug.vue
Normal file
220
src/view/systemTools/autoPlug/autoPlug.vue
Normal file
@@ -0,0 +1,220 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="gva-table-box">
|
||||
<el-form label-width="140px" class="plug-form">
|
||||
<el-form-item label="插件名">
|
||||
<el-input v-model="form.plugName" placeholder="必填(英文大写字母开头)" @blur="titleCase" />
|
||||
</el-form-item>
|
||||
<el-form-item label="路由组">
|
||||
<el-input v-model="form.routerGroup" placeholder="将会作为插件路由组使用" />
|
||||
</el-form-item>
|
||||
<el-form-item label="使用全局属性">
|
||||
<el-checkbox v-model="form.hasGlobal" />
|
||||
</el-form-item>
|
||||
<el-form-item v-if="form.hasGlobal" label="全局属性">
|
||||
<div v-for="(i,k) in form.global" :key="k" class="plug-row">
|
||||
<span>
|
||||
<el-input v-model="i.key" placeholder="key 必填" />
|
||||
</span>
|
||||
<span>
|
||||
<el-select v-model="i.type" placeholder="type 必填">
|
||||
<el-option label="string" value="string" />
|
||||
<el-option label="int" value="int" />
|
||||
<el-option label="float32" value="float32" />
|
||||
<el-option label="float64" value="float64" />
|
||||
<el-option label="bool" value="bool" />
|
||||
</el-select>
|
||||
</span>
|
||||
<span>
|
||||
<el-input v-model="i.desc" placeholder="备注 必填" />
|
||||
</span>
|
||||
<span>
|
||||
<el-button :icon="Plus" circle @click="addkv(form.global)" />
|
||||
</span>
|
||||
<span>
|
||||
<el-button :icon="Minus" circle @click="minkv(form.global,k)" />
|
||||
</span>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="使用Request">
|
||||
<el-checkbox v-model="form.hasRequest" />
|
||||
</el-form-item>
|
||||
<el-form-item v-if="form.hasRequest" label="Request">
|
||||
<div v-for="(i,k) in form.request" :key="k" class="plug-row">
|
||||
<span>
|
||||
<el-input v-model="i.key" placeholder="key 必填" />
|
||||
</span>
|
||||
<span>
|
||||
<el-select v-model="i.type" placeholder="type 必填">
|
||||
<el-option label="string" value="string" />
|
||||
<el-option label="int" value="int" />
|
||||
<el-option label="float32" value="float32" />
|
||||
<el-option label="float64" value="float64" />
|
||||
<el-option label="bool" value="bool" />
|
||||
</el-select>
|
||||
</span>
|
||||
<span>
|
||||
<el-input v-model="i.desc" placeholder="备注 必填" />
|
||||
</span>
|
||||
<span>
|
||||
<el-button :icon="Plus" circle @click="addkv(form.request)" />
|
||||
</span>
|
||||
<span>
|
||||
<el-button :icon="Minus" circle @click="minkv(form.request,k)" />
|
||||
</span>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="使用Response">
|
||||
<el-checkbox v-model="form.hasResponse" />
|
||||
</el-form-item>
|
||||
<el-form-item v-if="form.hasResponse" label="Response">
|
||||
<div v-for="(i,k) in form.response" :key="k" class="plug-row">
|
||||
<span>
|
||||
<el-input v-model="i.key" placeholder="key 必填" />
|
||||
</span>
|
||||
<span>
|
||||
<el-select v-model="i.type" placeholder="type 必填">
|
||||
<el-option label="string" value="string" />
|
||||
<el-option label="int" value="int" />
|
||||
<el-option label="float32" value="float32" />
|
||||
<el-option label="float64" value="float64" />
|
||||
<el-option label="bool" value="bool" />
|
||||
</el-select>
|
||||
</span>
|
||||
<span>
|
||||
<el-input v-model="i.desc" placeholder="备注 必填" />
|
||||
</span>
|
||||
<span>
|
||||
<el-button :icon="Plus" circle @click="addkv(form.response)" />
|
||||
</span>
|
||||
<span>
|
||||
<el-button :icon="Minus" circle @click="minkv(form.response,k)" />
|
||||
</span>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="createPlug">创建</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { toUpperCase } from '@/utils/stringFun'
|
||||
|
||||
import {
|
||||
Plus,
|
||||
Minus
|
||||
} from '@element-plus/icons-vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
|
||||
import { createPlugApi } from '@/api/autoCode.js'
|
||||
|
||||
import { reactive } from 'vue'
|
||||
|
||||
const form = reactive({
|
||||
plugName: '',
|
||||
routerGroup: '',
|
||||
hasGlobal: true,
|
||||
hasRequest: true,
|
||||
hasResponse: true,
|
||||
global: [{
|
||||
key: '',
|
||||
type: '',
|
||||
desc: '',
|
||||
}],
|
||||
request: [{
|
||||
key: '',
|
||||
type: '',
|
||||
desc: '',
|
||||
}],
|
||||
response: [{
|
||||
key: '',
|
||||
type: '',
|
||||
desc: '',
|
||||
}]
|
||||
})
|
||||
|
||||
const titleCase = () => {
|
||||
form.plugName = toUpperCase(form.plugName)
|
||||
}
|
||||
|
||||
const createPlug = async() => {
|
||||
if (!form.plugName || !form.routerGroup) {
|
||||
ElMessage.error('插件名称和插件路由组为必填项')
|
||||
return
|
||||
}
|
||||
if (form.hasGlobal) {
|
||||
const intercept = form.global.some(i => {
|
||||
if (!i.key || !i.type) {
|
||||
return true
|
||||
}
|
||||
})
|
||||
if (intercept) {
|
||||
ElMessage.error('全局属性的key和type为必填项')
|
||||
return
|
||||
}
|
||||
}
|
||||
if (form.hasRequest) {
|
||||
const intercept = form.request.some(i => {
|
||||
if (!i.key || !i.type) {
|
||||
return true
|
||||
}
|
||||
})
|
||||
if (intercept) {
|
||||
ElMessage.error('请求属性的key和type为必填项')
|
||||
return
|
||||
}
|
||||
}
|
||||
if (form.hasResponse) {
|
||||
const intercept = form.response.some(i => {
|
||||
if (!i.key || !i.type) {
|
||||
return true
|
||||
}
|
||||
})
|
||||
if (intercept) {
|
||||
ElMessage.error('响应属性的key和type为必填项')
|
||||
return
|
||||
}
|
||||
}
|
||||
const res = await createPlugApi(form)
|
||||
if (res.code === 0) {
|
||||
ElMessageBox('创建成功,插件已自动写入后端plugin目录下,请按照自己的逻辑进行创造')
|
||||
}
|
||||
}
|
||||
|
||||
const addkv = (arr) => {
|
||||
arr.push({
|
||||
key: '',
|
||||
value: '',
|
||||
})
|
||||
}
|
||||
|
||||
const minkv = (arr, key) => {
|
||||
if (arr.length === 1) {
|
||||
ElMessage.warning('至少有一个全局属性')
|
||||
return
|
||||
}
|
||||
arr.splice(key, 1)
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.plug-form{
|
||||
width: 680px;
|
||||
}
|
||||
.plug-row{
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
&+&{
|
||||
margin-top: 12px;
|
||||
}
|
||||
&>span{
|
||||
margin-left: 8px;
|
||||
}
|
||||
}
|
||||
</style>
|
17
src/view/systemTools/formCreate/index.vue
Normal file
17
src/view/systemTools/formCreate/index.vue
Normal file
@@ -0,0 +1,17 @@
|
||||
<template>
|
||||
<div style="height:80vh">
|
||||
<iframe width="100%" height="100%" :src="`${basePath}:${basePort}/form-generator/#/`" frameborder="0" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'FormGenerator'
|
||||
}
|
||||
</script>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
const basePath = ref(import.meta.env.VITE_BASE_PATH)
|
||||
const basePort = ref(import.meta.env.VITE_SERVER_PORT)
|
||||
</script>
|
21
src/view/systemTools/index.vue
Normal file
21
src/view/systemTools/index.vue
Normal file
@@ -0,0 +1,21 @@
|
||||
<template>
|
||||
<div>
|
||||
<router-view v-slot="{ Component }">
|
||||
<transition mode="out-in" name="el-fade-in-linear">
|
||||
<keep-alive :include="routerStore.keepAliveRouters">
|
||||
<component :is="Component" />
|
||||
</keep-alive>
|
||||
</transition>
|
||||
</router-view>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'System'
|
||||
}
|
||||
</script>
|
||||
<script setup>
|
||||
import { useRouterStore } from '@/pinia/modules/router'
|
||||
const routerStore = useRouterStore()
|
||||
</script>
|
44
src/view/systemTools/installPlugin/index.vue
Normal file
44
src/view/systemTools/installPlugin/index.vue
Normal file
@@ -0,0 +1,44 @@
|
||||
<template>
|
||||
<div>
|
||||
<el-upload
|
||||
class="upload-demo"
|
||||
drag
|
||||
:action="`${path}/autoCode/installPlugin`"
|
||||
:headers="{'x-token':userStore.token}"
|
||||
:show-file-list="false"
|
||||
:on-success="handleSuccess"
|
||||
:on-error="handleSuccess"
|
||||
name="plug"
|
||||
>
|
||||
<el-icon class="el-icon--upload"><upload-filled /></el-icon>
|
||||
<div class="el-upload__text">
|
||||
拖拽或<em>点击上传</em>
|
||||
</div>
|
||||
<template #tip>
|
||||
<div class="el-upload__tip">
|
||||
请把安装包的zip拖拽至此处上传
|
||||
</div>
|
||||
</template>
|
||||
</el-upload>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
import { useUserStore } from '@/pinia/modules/user'
|
||||
import { ElMessage } from 'element-plus'
|
||||
const userStore = useUserStore()
|
||||
const path = ref(import.meta.env.VITE_BASE_API)
|
||||
|
||||
const handleSuccess = (res) => {
|
||||
if (res.code === 0) {
|
||||
let msg = ``
|
||||
res.data && res.data.forEach((item, index) => {
|
||||
msg += `${index + 1}.${item.msg}\n`
|
||||
})
|
||||
alert(msg)
|
||||
} else {
|
||||
ElMessage.error(res.msg)
|
||||
}
|
||||
}
|
||||
</script>
|
463
src/view/systemTools/system/system.vue
Normal file
463
src/view/systemTools/system/system.vue
Normal file
@@ -0,0 +1,463 @@
|
||||
<template>
|
||||
<div class="system">
|
||||
<el-form ref="form" :model="config" label-width="240px">
|
||||
<!-- System start -->
|
||||
<el-collapse v-model="activeNames">
|
||||
<el-collapse-item title="系统配置" name="1">
|
||||
<el-form-item label="环境值">
|
||||
<!-- <el-input v-model="config.system.env" />-->
|
||||
<el-select v-model="config.system.env" style="width:100%">
|
||||
<el-option value="public" />
|
||||
<el-option value="develop" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="端口值">
|
||||
<el-input v-model.number="config.system.addr" />
|
||||
</el-form-item>
|
||||
<el-form-item label="数据库类型">
|
||||
<el-select v-model="config.system['db-type']" style="width:100%">
|
||||
<el-option value="mysql" />
|
||||
<el-option value="pgsql" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="Oss类型">
|
||||
<el-select v-model="config.system['oss-type']" style="width:100%">
|
||||
<el-option value="local" />
|
||||
<el-option value="qiniu" />
|
||||
<el-option value="tencent-cos" />
|
||||
<el-option value="aliyun-oss" />
|
||||
<el-option value="huawei-obs" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="多点登录拦截">
|
||||
<el-checkbox v-model="config.system['use-multipoint']">开启</el-checkbox>
|
||||
</el-form-item>
|
||||
<el-form-item label="开启redis">
|
||||
<el-checkbox v-model="config.system['use-redis']">开启</el-checkbox>
|
||||
</el-form-item>
|
||||
<el-form-item label="限流次数">
|
||||
<el-input-number v-model.number="config.system['iplimit-count']" />
|
||||
</el-form-item>
|
||||
<el-form-item label="限流时间">
|
||||
<el-input-number v-model.number="config.system['iplimit-time']" />
|
||||
</el-form-item>
|
||||
</el-collapse-item>
|
||||
<el-collapse-item title="jwt签名" name="2">
|
||||
<el-form-item label="jwt签名">
|
||||
<el-input v-model="config.jwt['signing-key']" />
|
||||
</el-form-item>
|
||||
<el-form-item label="有效期">
|
||||
<el-input v-model="config.jwt['expires-time']" />
|
||||
</el-form-item>
|
||||
<el-form-item label="缓冲期">
|
||||
<el-input v-model="config.jwt['buffer-time']" />
|
||||
</el-form-item>
|
||||
<el-form-item label="签发者">
|
||||
<el-input v-model="config.jwt.issuer" />
|
||||
</el-form-item>
|
||||
</el-collapse-item>
|
||||
<el-collapse-item title="Zap日志配置" name="3">
|
||||
<el-form-item label="级别">
|
||||
<el-input v-model.number="config.zap.level" />
|
||||
</el-form-item>
|
||||
<el-form-item label="输出">
|
||||
<el-input v-model="config.zap.format" />
|
||||
</el-form-item>
|
||||
<el-form-item label="日志前缀">
|
||||
<el-input v-model="config.zap.prefix" />
|
||||
</el-form-item>
|
||||
<el-form-item label="日志文件夹">
|
||||
<el-input v-model="config.zap.director" />
|
||||
</el-form-item>
|
||||
<el-form-item label="编码级">
|
||||
<el-input v-model="config.zap['encode-level']" />
|
||||
</el-form-item>
|
||||
<el-form-item label="栈名">
|
||||
<el-input v-model="config.zap['stacktrace-key']" />
|
||||
</el-form-item>
|
||||
<el-form-item label="日志留存时间(默认以天为单位)">
|
||||
<el-input v-model.number="config.zap['max-age']" />
|
||||
</el-form-item>
|
||||
<el-form-item label="显示行">
|
||||
<el-checkbox v-model="config.zap['show-line']" />
|
||||
</el-form-item>
|
||||
<el-form-item label="输出控制台">
|
||||
<el-checkbox v-model="config.zap['log-in-console']" />
|
||||
</el-form-item>
|
||||
</el-collapse-item>
|
||||
<el-collapse-item title="Redis admin数据库配置" name="4">
|
||||
<el-form-item label="库">
|
||||
<el-input v-model.number="config.redis.db" />
|
||||
</el-form-item>
|
||||
<el-form-item label="地址">
|
||||
<el-input v-model="config.redis.addr" />
|
||||
</el-form-item>
|
||||
<el-form-item label="密码">
|
||||
<el-input v-model="config.redis.password" />
|
||||
</el-form-item>
|
||||
</el-collapse-item>
|
||||
|
||||
<el-collapse-item title="邮箱配置" name="5">
|
||||
<el-form-item label="接收者邮箱">
|
||||
<el-input v-model="config.email.to" placeholder="可多个,以逗号分隔" />
|
||||
</el-form-item>
|
||||
<el-form-item label="端口">
|
||||
<el-input v-model.number="config.email.port" />
|
||||
</el-form-item>
|
||||
<el-form-item label="发送者邮箱">
|
||||
<el-input v-model="config.email.from" />
|
||||
</el-form-item>
|
||||
<el-form-item label="host">
|
||||
<el-input v-model="config.email.host" />
|
||||
</el-form-item>
|
||||
<el-form-item label="是否为ssl">
|
||||
<el-checkbox v-model="config.email['is-ssl']" />
|
||||
</el-form-item>
|
||||
<el-form-item label="secret">
|
||||
<el-input v-model="config.email.secret" />
|
||||
</el-form-item>
|
||||
<el-form-item label="测试邮件">
|
||||
<el-button @click="email">测试邮件</el-button>
|
||||
</el-form-item>
|
||||
</el-collapse-item>
|
||||
<el-collapse-item title="验证码配置" name="7">
|
||||
<el-form-item label="字符长度">
|
||||
<el-input v-model.number="config.captcha['key-long']" />
|
||||
</el-form-item>
|
||||
<el-form-item label="平台宽度">
|
||||
<el-input v-model.number="config.captcha['img-width']" />
|
||||
</el-form-item>
|
||||
<el-form-item label="图片高度">
|
||||
<el-input v-model.number="config.captcha['img-height']" />
|
||||
</el-form-item>
|
||||
</el-collapse-item>
|
||||
<el-collapse-item title="数据库配置" name="9">
|
||||
<template v-if="config.system['db-type'] === 'mysql'">
|
||||
<el-form-item label="用户名">
|
||||
<el-input v-model="config.mysql.username" />
|
||||
</el-form-item>
|
||||
<el-form-item label="密码">
|
||||
<el-input v-model="config.mysql.password" />
|
||||
</el-form-item>
|
||||
<el-form-item label="地址">
|
||||
<el-input v-model="config.mysql.path" />
|
||||
</el-form-item>
|
||||
<el-form-item label="数据库">
|
||||
<el-input v-model="config.mysql['db-name']" />
|
||||
</el-form-item>
|
||||
<el-form-item label="前缀">
|
||||
<el-input v-model="config.mysql['refix']" />
|
||||
</el-form-item>
|
||||
<el-form-item label="复数表">
|
||||
<el-switch v-model="config.mysql['singular']" />
|
||||
</el-form-item>
|
||||
<el-form-item label="引擎">
|
||||
<el-input v-model="config.mysql['engine']" />
|
||||
</el-form-item>
|
||||
<el-form-item label="maxIdleConns">
|
||||
<el-input v-model.number="config.mysql['max-idle-conns']" />
|
||||
</el-form-item>
|
||||
<el-form-item label="maxOpenConns">
|
||||
<el-input v-model.number="config.mysql['max-open-conns']" />
|
||||
</el-form-item>
|
||||
<el-form-item label="写入日志">
|
||||
<el-checkbox v-model="config.mysql['log-zap']" />
|
||||
</el-form-item>
|
||||
<el-form-item label="日志模式">
|
||||
<el-input v-model="config.mysql['log-mode']" />
|
||||
</el-form-item>
|
||||
</template>
|
||||
<template v-if="config.system.dbType === 'pgsql'">
|
||||
<el-form-item label="用户名">
|
||||
<el-input v-model="config.pgsql.username" />
|
||||
</el-form-item>
|
||||
<el-form-item label="密码">
|
||||
<el-input v-model="config.pgsql.password" />
|
||||
</el-form-item>
|
||||
<el-form-item label="地址">
|
||||
<el-input v-model="config.pgsql.path" />
|
||||
</el-form-item>
|
||||
<el-form-item label="数据库">
|
||||
<el-input v-model="config.pgsql.dbname" />
|
||||
</el-form-item>
|
||||
<el-form-item label="前缀">
|
||||
<el-input v-model="config.pgsql['refix']" />
|
||||
</el-form-item>
|
||||
<el-form-item label="复数表">
|
||||
<el-switch v-model="config.pgsql['singular']" />
|
||||
</el-form-item>
|
||||
<el-form-item label="引擎">
|
||||
<el-input v-model="config.pgsql['engine']" />
|
||||
</el-form-item>
|
||||
<el-form-item label="maxIdleConns">
|
||||
<el-input v-model.number="config.pgsql['max-idle-conns']" />
|
||||
</el-form-item>
|
||||
<el-form-item label="maxOpenConns">
|
||||
<el-input v-model.number="config.pgsql['max-open-conns']" />
|
||||
</el-form-item>
|
||||
<el-form-item label="写入日志">
|
||||
<el-checkbox v-model="config.pgsql['log-zap']" />
|
||||
</el-form-item>
|
||||
<el-form-item label="日志模式">
|
||||
<el-input v-model="config.pgsql['log-mode']" />
|
||||
</el-form-item>
|
||||
</template>
|
||||
</el-collapse-item>
|
||||
|
||||
<el-collapse-item title="oss配置" name="10">
|
||||
<template v-if="config.system['oss-type'] === 'local'">
|
||||
<h2>本地文件配置</h2>
|
||||
<el-form-item label="本地文件访问路径">
|
||||
<el-input v-model="config.local.path" />
|
||||
</el-form-item>
|
||||
<el-form-item label="本地文件存储路径">
|
||||
<el-input v-model="config.local['store-path']" />
|
||||
</el-form-item>
|
||||
</template>
|
||||
<template v-if="config.system['oss-type'] === 'qiniu'">
|
||||
<h2>qiniu上传配置</h2>
|
||||
<el-form-item label="存储区域">
|
||||
<el-input v-model="config.qiniu.zone" />
|
||||
</el-form-item>
|
||||
<el-form-item label="空间名称">
|
||||
<el-input v-model="config.qiniu.bucket" />
|
||||
</el-form-item>
|
||||
<el-form-item label="CDN加速域名">
|
||||
<el-input v-model="config.qiniu['img-path']" />
|
||||
</el-form-item>
|
||||
<el-form-item label="是否使用https">
|
||||
<el-checkbox v-model="config.qiniu['use-https']">开启</el-checkbox>
|
||||
</el-form-item>
|
||||
<el-form-item label="accessKey">
|
||||
<el-input v-model="config.qiniu['access-key']" />
|
||||
</el-form-item>
|
||||
<el-form-item label="secretKey">
|
||||
<el-input v-model="config.qiniu['secret-key']" />
|
||||
</el-form-item>
|
||||
<el-form-item label="上传是否使用CDN上传加速">
|
||||
<el-checkbox v-model="config.qiniu['use-cdn-domains']">开启</el-checkbox>
|
||||
</el-form-item>
|
||||
</template>
|
||||
<template v-if="config.system['oss-type'] === 'tencent-cos'">
|
||||
<h2>腾讯云COS上传配置</h2>
|
||||
<el-form-item label="存储桶名称">
|
||||
<el-input v-model="config['tencent-cos']['bucket']" />
|
||||
</el-form-item>
|
||||
<el-form-item label="所属地域">
|
||||
<el-input v-model="config['tencent-cos'].region" />
|
||||
</el-form-item>
|
||||
<el-form-item label="secretID">
|
||||
<el-input v-model="config['tencent-cos']['secret-id']" />
|
||||
</el-form-item>
|
||||
<el-form-item label="secretKey">
|
||||
<el-input v-model="config['tencent-cos']['secret-key']" />
|
||||
</el-form-item>
|
||||
<el-form-item label="路径前缀">
|
||||
<el-input v-model="config['tencent-cos']['path-prefix']" />
|
||||
</el-form-item>
|
||||
<el-form-item label="访问域名">
|
||||
<el-input v-model="config['tencent-cos']['base-url']" />
|
||||
</el-form-item>
|
||||
</template>
|
||||
<template v-if="config.system['oss-type'] === 'aliyun-oss'">
|
||||
<h2>阿里云OSS上传配置</h2>
|
||||
<el-form-item label="区域">
|
||||
<el-input v-model="config['aliyun-oss'].endpoint" />
|
||||
</el-form-item>
|
||||
<el-form-item label="accessKeyId">
|
||||
<el-input v-model="config['aliyun-oss']['access-key-id']" />
|
||||
</el-form-item>
|
||||
<el-form-item label="accessKeySecret">
|
||||
<el-input v-model="config['aliyun-oss']['access-key-secret']" />
|
||||
</el-form-item>
|
||||
<el-form-item label="存储桶名称">
|
||||
<el-input v-model="config['aliyun-oss']['bucket-name']" />
|
||||
</el-form-item>
|
||||
<el-form-item label="访问域名">
|
||||
<el-input v-model="config['aliyun-oss']['bucket-url']" />
|
||||
</el-form-item>
|
||||
</template>
|
||||
<template v-if="config.system['oss-type'] === 'huawei-obs'">
|
||||
<h2>华为云Obs上传配置</h2>
|
||||
<el-form-item label="路径">
|
||||
<el-input v-model="config['hua-wei-obs'].path" />
|
||||
</el-form-item>
|
||||
<el-form-item label="存储桶名称">
|
||||
<el-input v-model="config['hua-wei-obs'].bucket" />
|
||||
</el-form-item>
|
||||
<el-form-item label="区域">
|
||||
<el-input v-model="config['hua-wei-obs'].endpoint" />
|
||||
</el-form-item>
|
||||
<el-form-item label="accessKey">
|
||||
<el-input v-model="config['hua-wei-obs']['access-key']" />
|
||||
</el-form-item>
|
||||
<el-form-item label="secretKey">
|
||||
<el-input v-model="config['hua-wei-obs']['secret-key']" />
|
||||
</el-form-item>
|
||||
</template>
|
||||
|
||||
</el-collapse-item>
|
||||
|
||||
<el-collapse-item title="Excel上传配置" name="11">
|
||||
<el-form-item label="合成目标地址">
|
||||
<el-input v-model="config.excel.dir" />
|
||||
</el-form-item>
|
||||
</el-collapse-item>
|
||||
|
||||
<el-collapse-item title="自动化代码配置" name="12">
|
||||
<el-form-item label="是否自动重启(linux)">
|
||||
<el-checkbox v-model="config.autocode['transfer-restart']" />
|
||||
</el-form-item>
|
||||
<el-form-item label="root(项目根路径)">
|
||||
<el-input v-model="config.autocode.root" disabled />
|
||||
</el-form-item>
|
||||
<el-form-item label="Server(后端代码地址)">
|
||||
<el-input v-model="config.autocode['transfer-restart']" />
|
||||
</el-form-item>
|
||||
<el-form-item label="SApi(后端api文件夹地址)">
|
||||
<el-input v-model="config.autocode['server-api']" />
|
||||
</el-form-item>
|
||||
<el-form-item label="SInitialize(后端Initialize文件夹)">
|
||||
<el-input v-model="config.autocode['server-initialize']" />
|
||||
</el-form-item>
|
||||
<el-form-item label="SModel(后端Model文件地址)">
|
||||
<el-input v-model="config.autocode['server-model']" />
|
||||
</el-form-item>
|
||||
<el-form-item label="SRequest(后端Request文件夹地址)">
|
||||
<el-input v-model="config.autocode['server-request']" />
|
||||
</el-form-item>
|
||||
<el-form-item label="SRouter(后端Router文件夹地址)">
|
||||
<el-input v-model="config.autocode['server-router']" />
|
||||
</el-form-item>
|
||||
<el-form-item label="SService(后端Service文件夹地址)">
|
||||
<el-input v-model="config.autocode['server-service']" />
|
||||
</el-form-item>
|
||||
<el-form-item label="Web(前端文件夹地址)">
|
||||
<el-input v-model="config.autocode.web" />
|
||||
</el-form-item>
|
||||
<el-form-item label="WApi(后端WApi文件夹地址)">
|
||||
<el-input v-model="config.autocode['web-api']" />
|
||||
</el-form-item>
|
||||
<el-form-item label="WForm(后端WForm文件夹地址)">
|
||||
<el-input v-model="config.autocode['web-form']" />
|
||||
</el-form-item>
|
||||
<el-form-item label="WTable(后端WTable文件夹地址)">
|
||||
<el-input v-model="config.autocode['web-table']" />
|
||||
</el-form-item>
|
||||
</el-collapse-item>
|
||||
|
||||
<el-collapse-item title="Timer(定时任务)" name="13">
|
||||
<el-form-item label="Start(是否启用)">
|
||||
<el-checkbox v-model="config.timer['start']" />
|
||||
</el-form-item>
|
||||
<el-form-item label="Spec(CRON表达式)">
|
||||
<el-input v-model="config.timer.spec" />
|
||||
</el-form-item>
|
||||
<template v-for="(item,k) in config.timer.detail">
|
||||
<div v-for="(key,k2) in item" :key="k2">
|
||||
<el-form-item :key="k+k2" :label="k2">
|
||||
<el-input v-model="item[k2]" />
|
||||
</el-form-item>
|
||||
</div>
|
||||
</template>
|
||||
</el-collapse-item>
|
||||
</el-collapse>
|
||||
</el-form>
|
||||
<div class="gva-btn-list">
|
||||
<el-button type="primary" size="small" @click="update">立即更新</el-button>
|
||||
<el-button type="primary" size="small" @click="reload">重启服务(开发中)</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'Config'
|
||||
}
|
||||
</script>
|
||||
<script setup>
|
||||
import { getSystemConfig, setSystemConfig } from '@/api/system'
|
||||
import { emailTest } from '@/api/email'
|
||||
import { ref, reactive } from 'vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
|
||||
const activeNames = reactive([])
|
||||
const config = ref({
|
||||
system: {
|
||||
'iplimit-count': 0,
|
||||
'iplimit-time': 0
|
||||
},
|
||||
jwt: {},
|
||||
mysql: {},
|
||||
pgsql: {},
|
||||
excel: {},
|
||||
autocode: {},
|
||||
redis: {},
|
||||
qiniu: {},
|
||||
'tencent-cos': {},
|
||||
'aliyun-oss': {},
|
||||
'hua-wei-obs': {},
|
||||
captcha: {},
|
||||
zap: {},
|
||||
local: {},
|
||||
email: {},
|
||||
timer: {
|
||||
detail: {}
|
||||
}
|
||||
})
|
||||
|
||||
const initForm = async() => {
|
||||
const res = await getSystemConfig()
|
||||
if (res.code === 0) {
|
||||
config.value = res.data.config
|
||||
}
|
||||
}
|
||||
initForm()
|
||||
const reload = () => {}
|
||||
const update = async() => {
|
||||
const res = await setSystemConfig({ config: config.value })
|
||||
if (res.code === 0) {
|
||||
ElMessage({
|
||||
type: 'success',
|
||||
message: '配置文件设置成功'
|
||||
})
|
||||
await initForm()
|
||||
}
|
||||
}
|
||||
const email = async() => {
|
||||
const res = await emailTest()
|
||||
if (res.code === 0) {
|
||||
ElMessage({
|
||||
type: 'success',
|
||||
message: '邮件发送成功'
|
||||
})
|
||||
await initForm()
|
||||
} else {
|
||||
ElMessage({
|
||||
type: 'error',
|
||||
message: '邮件发送失败'
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.system {
|
||||
background: #fff;
|
||||
padding:36px;
|
||||
border-radius: 2px;
|
||||
h2 {
|
||||
padding: 10px;
|
||||
margin: 10px 0;
|
||||
font-size: 16px;
|
||||
box-shadow: -4px 0px 0px 0px #e7e8e8;
|
||||
}
|
||||
::v-deep(.el-input-number__increase){
|
||||
top:5px !important;
|
||||
}
|
||||
.gva-btn-list{
|
||||
margin-top:16px;
|
||||
}
|
||||
}
|
||||
</style>
|
Reference in New Issue
Block a user