修改路由以及tags
This commit is contained in:
parent
92e551acfb
commit
e6acb5040b
27
README.md
27
README.md
@ -31,3 +31,30 @@ yarn dev
|
||||
</el-container>
|
||||
</div>
|
||||
</template>
|
||||
我已经完成了路由配置的修改,实现了您描述的导航结构:
|
||||
顶部导航栏:
|
||||
平台概览
|
||||
车辆管理
|
||||
系统管理
|
||||
左侧导航栏:
|
||||
点击平台概览时不显示左侧导航栏
|
||||
点击车辆管理时显示:
|
||||
车辆监控
|
||||
车辆管理
|
||||
车辆入库
|
||||
类型管理
|
||||
点击系统管理时显示:
|
||||
角色管理
|
||||
人员管理
|
||||
用户管理
|
||||
驾驶员管理
|
||||
菜单管理
|
||||
操作日志
|
||||
|
||||
handleTabClick 点击事件处理的存在问题导致显示不对 之前的菜单是通过SidebarItem来获取的是吗?只是当前我想修改样式 并且将这个原始的导航栏分在不同的Tab栏展示 我觉得可以根据数据库获取到所有的菜单 然后再根据不同的Tab栏来确定是否显示呀
|
||||
|
||||
|
||||
点击平台概览:不显示标签
|
||||
,其余都不显示,只有点击二级菜单的时候才在右侧显示
|
||||
点击系统管理:只显示默认显示"角色管理"标签,其余都不显示只有点击二级菜单的时候才在右侧显示 先理解我的需求 不明白的问我
|
||||
现在我需要修改tagsView的展示:当我点击顶部导航栏车辆管理的时候 默认显示车辆监控5690E7(不可关闭),当我点击车辆管理下的车辆入库,则显示:车辆监控(白色) 车辆入库(5690E7),鼠标滑入显示车辆入库(5690E7)叉号(5690E7),点击叉号返回到车辆监控;点击车辆管理下的类型管理,则显示:车辆监控(白色) 类型管理(5690E7),鼠标滑入显示类型管理(5690E7)叉号(5690E7),点击叉号返回到车辆监控;点击系统管理默认显示角色管理5690E7(不可关闭),当我点击人员管理下的用户管理,则显示:角色管理(白色) 用户管理(5690E7),鼠标滑入显示用户管理(5690E7)叉号(5690E7),点击叉号返回到角色管理;当我点击人员管理下的驾驶员理,则显示:角色管理(白色) 驾驶员管理(5690E7),鼠标滑入显示驾驶员管理(5690E7)叉号(5690E7),点击叉号返回到角色管理;当我点击操作日志则显示:角色管理(白色) 操作日志(5690E7),鼠标滑入显示操作日志(5690E7)叉号(5690E7),点击叉号返回到角色管理 我们可以点击车辆管理:只默认显示"车辆监控"标签理解为将车辆管理下的车辆监控和系统管理下的角色管理作为首页来看
|
||||
18
src/App.vue
18
src/App.vue
@ -4,12 +4,30 @@
|
||||
|
||||
<script setup>
|
||||
import useSettingsStore from '@/store/modules/settings'
|
||||
import usePermissionStore from '@/store/modules/permission'
|
||||
import { handleThemeStyle } from '@/utils/theme'
|
||||
import { useRoute } from 'vue-router'
|
||||
|
||||
const route = useRoute()
|
||||
const permissionStore = usePermissionStore()
|
||||
|
||||
onMounted(() => {
|
||||
nextTick(() => {
|
||||
// 初始化主题样式
|
||||
handleThemeStyle(useSettingsStore().theme)
|
||||
|
||||
// 根据当前路径设置正确的Tab
|
||||
const path = route.path
|
||||
if (path.startsWith('/platform')) {
|
||||
permissionStore.setCurrentTab('platform')
|
||||
} else if (path.startsWith('/car')) {
|
||||
permissionStore.setCurrentTab('car')
|
||||
} else if (path.startsWith('/system')) {
|
||||
permissionStore.setCurrentTab('system')
|
||||
} else if (path === '/' || path === '/index') {
|
||||
// 根路径默认设为platform
|
||||
permissionStore.setCurrentTab('platform')
|
||||
}
|
||||
})
|
||||
})
|
||||
</script>
|
||||
|
||||
@ -97,3 +97,37 @@
|
||||
font-size: 14px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
/* 全局设置 */
|
||||
/* 搜索按钮 */
|
||||
.search-btn {
|
||||
width: 68px;
|
||||
background: #347AE2 !important;
|
||||
color: #fff !important;
|
||||
border-radius: 8px !important;
|
||||
height: 36px !important;
|
||||
border: none !important;
|
||||
font-size: 15px;
|
||||
padding: 0 24px;
|
||||
}
|
||||
/* 重置按钮 */
|
||||
.reset-btn {
|
||||
width: 68px;
|
||||
background: rgba(52, 122, 226, 0.2) !important;
|
||||
color: #fff !important;
|
||||
border: 1px solid #347AE2 !important;
|
||||
border-radius: 8px !important;
|
||||
height: 36px !important;
|
||||
font-size: 15px;
|
||||
padding: 0 24px;
|
||||
}
|
||||
/* 导出按钮 */
|
||||
.export-btn {
|
||||
width: 68px;
|
||||
background: #343744 !important;
|
||||
color: #fff !important;
|
||||
border-radius: 8px !important;
|
||||
height: 36px !important;
|
||||
border: none !important;
|
||||
font-size: 15px;
|
||||
padding: 0 24px;
|
||||
}
|
||||
@ -45,13 +45,79 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
.el-input__wrapper{
|
||||
background: #343744 !important;
|
||||
border: none !important;
|
||||
border-radius: 8px !important;
|
||||
height: 36px !important;
|
||||
font-size: 15px;
|
||||
color: #96A0B5 !important;
|
||||
box-shadow: none !important;
|
||||
}
|
||||
// 输入框 下拉框 搜索栏
|
||||
.el-input__wrapper:hover {
|
||||
box-shadow: none !important;
|
||||
}
|
||||
.el-select {
|
||||
border:none !important;
|
||||
width:140px !important;
|
||||
// 修改el-select的输入框的border
|
||||
--el-border-color-hover: #343744!important;
|
||||
--el-border-color: #343744!important;
|
||||
|
||||
}
|
||||
.el-input__wrapper {
|
||||
background: #343744 !important;
|
||||
border: none !important;
|
||||
border-radius: 8px !important;
|
||||
height: 36px !important;
|
||||
font-size: 15px;
|
||||
color: #96A0B5 !important;
|
||||
box-shadow: none !important;
|
||||
}
|
||||
.el-select__wrapper.is-focused {
|
||||
box-shadow: none !important;
|
||||
}
|
||||
.el-select__wrapper {
|
||||
background: #343744 !important;
|
||||
color: #96A0B5 !important;
|
||||
border-radius: 8px !important;
|
||||
height: 36px !important;
|
||||
width: 140px;
|
||||
font-size: 15px;
|
||||
border: none !important;
|
||||
border:none !important;
|
||||
}
|
||||
.el-input__wrapper {
|
||||
background: #343744 !important;
|
||||
color: #96A0B5 !important;
|
||||
border-radius: 8px !important;
|
||||
height: 36px !important;
|
||||
border: none !important;
|
||||
box-shadow: none !important;
|
||||
|
||||
}
|
||||
.el-select__placeholder {
|
||||
color: #96A0B5 !important;
|
||||
}
|
||||
// to fixed https://github.com/ElemeFE/element/issues/2461
|
||||
.el-dialog {
|
||||
transform: none;
|
||||
left: 0;
|
||||
position: relative;
|
||||
margin: 0 auto;
|
||||
border-radius: 12px!important;
|
||||
background: #292C38!important;
|
||||
color: #fff!important;
|
||||
.el-dialog__title {
|
||||
color: #fff!important;
|
||||
}
|
||||
.el-dialog__headerbtn .el-dialog__close {
|
||||
color: #fff!important;
|
||||
}
|
||||
.el-form-item__label {
|
||||
color:rgba(255,255,255,0.6)!important;
|
||||
}
|
||||
}
|
||||
|
||||
// refine element ui upload
|
||||
@ -94,3 +160,5 @@
|
||||
.el-dropdown .el-dropdown-link{
|
||||
color: var(--el-color-primary) !important;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -124,6 +124,8 @@ aside {
|
||||
//main-container全局样式
|
||||
.app-container {
|
||||
padding: 20px;
|
||||
// min-height: calc(100vh - 150px)!important;
|
||||
|
||||
}
|
||||
|
||||
.components-container {
|
||||
@ -131,9 +133,9 @@ aside {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.pagination-container {
|
||||
margin-top: 30px;
|
||||
}
|
||||
// .pagination-container {
|
||||
// margin-top: 30px;
|
||||
// }
|
||||
|
||||
.text-center {
|
||||
text-align: center
|
||||
|
||||
@ -75,6 +75,7 @@
|
||||
}
|
||||
|
||||
.el-table {
|
||||
min-height: calc(100vh - 310px)!important;
|
||||
.el-table__header-wrapper, .el-table__fixed-header-wrapper {
|
||||
th {
|
||||
word-break: break-word;
|
||||
@ -101,13 +102,13 @@
|
||||
}
|
||||
|
||||
/** 表格布局 **/
|
||||
.pagination-container {
|
||||
// position: relative;
|
||||
height: 25px;
|
||||
margin-bottom: 10px;
|
||||
margin-top: 15px;
|
||||
padding: 10px 20px !important;
|
||||
}
|
||||
// .pagination-container {
|
||||
// // position: relative;
|
||||
// height: 25px;
|
||||
// margin-bottom: 10px;
|
||||
// margin-top: 15px;
|
||||
// padding: 10px 20px !important;
|
||||
// }
|
||||
|
||||
/* tree border */
|
||||
.tree-border {
|
||||
@ -118,19 +119,19 @@
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.pagination-container .el-pagination {
|
||||
right: 0;
|
||||
position: absolute;
|
||||
}
|
||||
// .pagination-container .el-pagination {
|
||||
// right: 0;
|
||||
// position: absolute;
|
||||
// }
|
||||
|
||||
@media ( max-width : 768px) {
|
||||
.pagination-container .el-pagination > .el-pagination__jump {
|
||||
display: none !important;
|
||||
}
|
||||
.pagination-container .el-pagination > .el-pagination__sizes {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
// @media ( max-width : 768px) {
|
||||
// .pagination-container .el-pagination > .el-pagination__jump {
|
||||
// display: none !important;
|
||||
// }
|
||||
// .pagination-container .el-pagination > .el-pagination__sizes {
|
||||
// display: none !important;
|
||||
// }
|
||||
// }
|
||||
|
||||
.el-table .fixed-width .el-button--small {
|
||||
padding-left: 0;
|
||||
|
||||
@ -68,12 +68,14 @@
|
||||
border: none;
|
||||
height: 100%;
|
||||
width: 100% !important;
|
||||
|
||||
}
|
||||
|
||||
.el-menu-item, .menu-title {
|
||||
overflow: hidden !important;
|
||||
text-overflow: ellipsis !important;
|
||||
white-space: nowrap !important;
|
||||
|
||||
}
|
||||
|
||||
.el-menu-item .el-menu-tooltip__trigger {
|
||||
@ -83,6 +85,7 @@
|
||||
// menu hover
|
||||
.sub-menu-title-noDropdown,
|
||||
.el-sub-menu__title {
|
||||
|
||||
&:hover {
|
||||
background-color: rgba(0, 0, 0, 0.06) !important;
|
||||
}
|
||||
@ -90,6 +93,7 @@
|
||||
|
||||
& .theme-dark .is-active > .el-sub-menu__title {
|
||||
color: $base-menu-color-active !important;
|
||||
|
||||
}
|
||||
|
||||
& .nest-menu .el-sub-menu>.el-sub-menu__title,
|
||||
@ -211,9 +215,32 @@
|
||||
|
||||
.nest-menu .el-sub-menu>.el-sub-menu__title,
|
||||
.el-menu-item {
|
||||
// border: 1px solid red!important;
|
||||
border-radius: 10px!important;
|
||||
&:hover {
|
||||
// you can use $sub-menuHover
|
||||
background-color: rgba(0, 0, 0, 0.06) !important;
|
||||
background-color: #343744!important;
|
||||
}
|
||||
&.is-active{
|
||||
background-color: #343744!important;
|
||||
.svg-icon{
|
||||
color: #347AE2 !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.el-sub-menu >.el-sub-menu__title{
|
||||
// border: 1px solid red!important;
|
||||
border-radius: 10px!important;
|
||||
&:hover {
|
||||
// you can use $sub-menuHover
|
||||
background-color: #343744!important;
|
||||
}
|
||||
&.is-active{
|
||||
background-color: #343744!important;
|
||||
.svg-icon{
|
||||
color: #347AE2 !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
108
src/components/Pagination/index copy.vue
Normal file
108
src/components/Pagination/index copy.vue
Normal file
@ -0,0 +1,108 @@
|
||||
<template>
|
||||
<!-- <div :class="{ 'hidden': hidden }" class="pagination-container"> -->
|
||||
<el-pagination
|
||||
class="pagination"
|
||||
:background="background"
|
||||
v-model:current-page="currentPage"
|
||||
v-model:page-size="pageSize"
|
||||
:layout="layout"
|
||||
size="small"
|
||||
:page-sizes="pageSizes"
|
||||
:pager-count="pagerCount"
|
||||
:total="total"
|
||||
@size-change="handleSizeChange"
|
||||
@current-change="handleCurrentChange"
|
||||
/>
|
||||
<!-- </div> -->
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { scrollTo } from '@/utils/scroll-to'
|
||||
|
||||
const props = defineProps({
|
||||
total: {
|
||||
required: true,
|
||||
type: Number
|
||||
},
|
||||
page: {
|
||||
type: Number,
|
||||
default: 1
|
||||
},
|
||||
limit: {
|
||||
type: Number,
|
||||
default: 20
|
||||
},
|
||||
pageSizes: {
|
||||
type: Array,
|
||||
default() {
|
||||
return [10, 20, 30, 50]
|
||||
}
|
||||
},
|
||||
// 移动端页码按钮的数量端默认值5
|
||||
pagerCount: {
|
||||
type: Number,
|
||||
default: document.body.clientWidth < 992 ? 5 : 7
|
||||
},
|
||||
layout: {
|
||||
type: String,
|
||||
default: ' prev, pager, next'
|
||||
},
|
||||
background: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
autoScroll: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
hidden: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
})
|
||||
|
||||
const emit = defineEmits();
|
||||
const currentPage = computed({
|
||||
get() {
|
||||
return props.page
|
||||
},
|
||||
set(val) {
|
||||
emit('update:page', val)
|
||||
}
|
||||
})
|
||||
const pageSize = computed({
|
||||
get() {
|
||||
return props.limit
|
||||
},
|
||||
set(val){
|
||||
emit('update:limit', val)
|
||||
}
|
||||
})
|
||||
function handleSizeChange(val) {
|
||||
if (currentPage.value * val > props.total) {
|
||||
currentPage.value = 1
|
||||
}
|
||||
emit('pagination', { page: currentPage.value, limit: val })
|
||||
if (props.autoScroll) {
|
||||
scrollTo(0, 800)
|
||||
}
|
||||
}
|
||||
function handleCurrentChange(val) {
|
||||
emit('pagination', { page: val, limit: pageSize.value })
|
||||
if (props.autoScroll) {
|
||||
scrollTo(0, 800)
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
.pagination{
|
||||
border:1px solid blue;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
align-items: center;
|
||||
height: 100%;
|
||||
}
|
||||
</style>
|
||||
@ -1,10 +1,12 @@
|
||||
<template>
|
||||
<div :class="{ 'hidden': hidden }" class="pagination-container">
|
||||
<div class="pagination-container">
|
||||
<el-pagination
|
||||
class="pagination"
|
||||
:background="background"
|
||||
v-model:current-page="currentPage"
|
||||
v-model:page-size="pageSize"
|
||||
:layout="layout"
|
||||
size="small"
|
||||
:page-sizes="pageSizes"
|
||||
:pager-count="pagerCount"
|
||||
:total="total"
|
||||
@ -43,7 +45,7 @@ const props = defineProps({
|
||||
},
|
||||
layout: {
|
||||
type: String,
|
||||
default: 'total, sizes, prev, pager, next, jumper'
|
||||
default: ' prev, pager, next'
|
||||
},
|
||||
background: {
|
||||
type: Boolean,
|
||||
@ -94,12 +96,311 @@ function handleCurrentChange(val) {
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
<style lang="scss" scoped>
|
||||
.pagination-container {
|
||||
background: #fff;
|
||||
padding: 32px 16px;
|
||||
width: 100%;
|
||||
height: 50px;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
align-items: center;
|
||||
flex-wrap: nowrap;
|
||||
flex-direction: row;
|
||||
align-content: flex-start;
|
||||
}
|
||||
.pagination-container.hidden {
|
||||
display: none;
|
||||
:deep(.el-pagination) {
|
||||
border: 1px solid #4c4f5f;
|
||||
border-radius: 8px;
|
||||
background-color: #343744;
|
||||
padding: 0;
|
||||
--el-pagination-bg-color: #343744;
|
||||
--el-pagination-text-color: #fff;
|
||||
--el-pagination-button-color: #fff;
|
||||
--el-pagination-hover-color: #409eff;
|
||||
--el-disabled-bg-color: transparent;
|
||||
|
||||
.el-pagination__total,
|
||||
.el-pagination__jump,
|
||||
.btn-prev,
|
||||
.btn-next {
|
||||
color: #96a0b5;
|
||||
background-color: transparent;
|
||||
margin:0;
|
||||
}
|
||||
|
||||
// 上一页/下一页按钮样式
|
||||
.btn-prev,
|
||||
.btn-next {
|
||||
width: 45px;
|
||||
height: 30px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
// border-right: 1px solid #4c4f5f;
|
||||
|
||||
&:hover:not(.is-disabled) {
|
||||
background-color: transparent;
|
||||
color: #409eff;
|
||||
}
|
||||
}
|
||||
|
||||
// 最后一个按钮不需要右边框
|
||||
.btn-next:last-child {
|
||||
border-right: none;
|
||||
}
|
||||
|
||||
// 禁用状态的上一页/下一页按钮样式
|
||||
.btn-prev.is-disabled,
|
||||
.btn-next.is-disabled {
|
||||
background-color: transparent !important;
|
||||
color: #606266;
|
||||
opacity: 0.5;
|
||||
|
||||
&:hover {
|
||||
background-color: transparent !important;
|
||||
border-color: transparent !important;
|
||||
}
|
||||
}
|
||||
|
||||
.el-pager li {
|
||||
.number {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
margin: 0;
|
||||
width: 45px;
|
||||
height: 30px;
|
||||
border-right: 1px solid #4c4f5f;
|
||||
background-color: transparent;
|
||||
color: #96a0b5;
|
||||
|
||||
&.is-active {
|
||||
color: #409eff;
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
color: #409eff;
|
||||
background-color: transparent;
|
||||
border-color: #4c4f5f;
|
||||
}
|
||||
|
||||
// 最后一个页码不需要右边框
|
||||
&:first-child {
|
||||
border-left: 1px solid #4c4f5f;
|
||||
}
|
||||
|
||||
// 更多按钮(省略号)样式
|
||||
&.more {
|
||||
background-color: transparent;
|
||||
color: #96a0b5;
|
||||
|
||||
&:hover {
|
||||
background-color: transparent;
|
||||
color: #409eff;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// .pagination {
|
||||
// display: flex;
|
||||
// justify-content: flex-end;
|
||||
// align-items: center;
|
||||
// width: 100%;
|
||||
|
||||
// 覆盖Element Plus分页组件的默认样式
|
||||
// ::v-deep(.el-pagination) {
|
||||
// padding: 0;
|
||||
// background: none;
|
||||
// border: 1px solid #0a4273;
|
||||
// box-sizing: border-box;
|
||||
// color: #fff;
|
||||
// font-weight: normal;
|
||||
// display: flex;
|
||||
// align-items: center;
|
||||
// justify-content: center;
|
||||
// border: 1px solid #4C4F5F; // 添加整体灰色边框
|
||||
// border-radius: 4px;
|
||||
// overflow: hidden; // 防止内部元素溢出
|
||||
|
||||
// // 总体布局调整
|
||||
// .el-pagination__total,
|
||||
// .el-pagination__sizes,
|
||||
// .el-pagination__jump {
|
||||
// margin: 0;
|
||||
// color: #fff;
|
||||
// padding: 0 10px;
|
||||
// border-right: 1px solid #4C4F5F;
|
||||
// height: 30px;
|
||||
// line-height: 30px;
|
||||
// }
|
||||
|
||||
// // 按钮统一样式
|
||||
// button, li {
|
||||
// border: 1px solid red!important;
|
||||
// min-width: 30px;
|
||||
// height: 30px;
|
||||
// line-height: 30px;
|
||||
// text-align: center;
|
||||
// margin: 0; // 取消间距
|
||||
// color: #fff;
|
||||
// border-radius: 0; // 取消圆角
|
||||
// background-color: #343744;
|
||||
// border-right: 1px solid #4C4F5F; // 添加右边框
|
||||
// transition: all 0.3s;
|
||||
// font-size: 14px;
|
||||
|
||||
// &:last-child {
|
||||
// border-right: none; // 最后一个元素不需要右边框
|
||||
// }
|
||||
|
||||
// &:hover {
|
||||
// background-color: #5690E7;
|
||||
// }
|
||||
|
||||
// // 当前页面样式 - 选中状态
|
||||
// &.is-active {
|
||||
// color: #fff;
|
||||
// background-color: #5690E7;
|
||||
// font-weight: bold;
|
||||
// }
|
||||
|
||||
// // 禁用状态
|
||||
// &.is-disabled {
|
||||
// background-color: #343744;
|
||||
// color: #606266;
|
||||
// opacity: 0.5;
|
||||
// cursor: not-allowed;
|
||||
|
||||
// &:hover {
|
||||
// background-color: #343744;
|
||||
// border:1px solid #347AE2;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
// // 页码按钮样式
|
||||
// .el-pager {
|
||||
// display: flex;
|
||||
// align-items: center;
|
||||
// margin: 0;
|
||||
// padding: 0;
|
||||
// background-color: #343744;
|
||||
|
||||
// li {
|
||||
// background-color: #343744;
|
||||
// color: #fff;
|
||||
// border-radius: 0;
|
||||
// margin: 0;
|
||||
// list-style: none;
|
||||
// border-right: 1px solid #4C4F5F; // 添加右边框
|
||||
|
||||
// &:last-child {
|
||||
// border-right: none; // 最后一个元素不需要右边框
|
||||
// }
|
||||
|
||||
// &.is-active {
|
||||
// background-color: #5690E7;
|
||||
// color: #fff;
|
||||
// }
|
||||
|
||||
// &.is-disabled {
|
||||
// color: #606266;
|
||||
// }
|
||||
|
||||
// // 更多按钮样式
|
||||
// &.more {
|
||||
// color: #fff;
|
||||
// background-color: #343744;
|
||||
|
||||
// &:hover {
|
||||
// background-color: #444956;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
// // 上一页、下一页按钮
|
||||
// .btn-prev, .btn-next {
|
||||
// background-color: #343744;
|
||||
// color: #fff;
|
||||
// border-radius: 0;
|
||||
// display: flex;
|
||||
// align-items: center;
|
||||
// justify-content: center;
|
||||
// border-right: 1px solid #4C4F5F; // 添加右边框
|
||||
|
||||
// &:last-child {
|
||||
// border-right: none; // 最后一个元素不需要右边框
|
||||
// }
|
||||
|
||||
// &:hover {
|
||||
// background-color: #5690E7;
|
||||
// }
|
||||
|
||||
// &.is-disabled {
|
||||
// color: #606266;
|
||||
// background-color: #343744;
|
||||
// opacity: 0.5;
|
||||
|
||||
// &:hover {
|
||||
// background-color: #343744;
|
||||
// }
|
||||
// }
|
||||
|
||||
// .el-icon {
|
||||
// font-size: 12px;
|
||||
// }
|
||||
// }
|
||||
|
||||
// // 输入框样式
|
||||
// .el-pagination__editor {
|
||||
// &.el-input {
|
||||
// width: 50px;
|
||||
// margin: 0;
|
||||
|
||||
// .el-input__wrapper {
|
||||
// background-color: #343744;
|
||||
// box-shadow: none;
|
||||
// border-radius: 0;
|
||||
|
||||
// .el-input__inner {
|
||||
// color: #fff;
|
||||
// height: 32px;
|
||||
// text-align: center;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
// // 每页条数选择器
|
||||
// .el-pagination__sizes {
|
||||
// .el-select {
|
||||
// .el-input {
|
||||
// .el-input__wrapper {
|
||||
// background-color: #343744;
|
||||
// box-shadow: none;
|
||||
|
||||
// .el-input__inner {
|
||||
// color: #fff;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
// .el-select-dropdown__item {
|
||||
// color: #fff;
|
||||
// background-color: #343744;
|
||||
|
||||
// &.selected {
|
||||
// color: #5690E7;
|
||||
// }
|
||||
|
||||
// &:hover {
|
||||
// background-color: #444956;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
</style>
|
||||
@ -21,12 +21,13 @@ const tagsViewStore = useTagsViewStore()
|
||||
<style lang="scss" scoped>
|
||||
.app-main {
|
||||
/* 50= navbar 50 */
|
||||
min-height: calc(100vh - 65px);
|
||||
// min-height: calc(100vh - 60px);
|
||||
|
||||
width: 100%;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
background-color: #343744;
|
||||
padding:30px;
|
||||
padding:0 30px;
|
||||
}
|
||||
|
||||
.fixed-header + .app-main {
|
||||
@ -35,12 +36,12 @@ const tagsViewStore = useTagsViewStore()
|
||||
|
||||
.hasTagsView {
|
||||
.app-main {
|
||||
/* 84 = navbar + tags-view = 65 + 34 */
|
||||
min-height: calc(100vh - 99px);
|
||||
/* 84 = navbar + tags-view = 65 + 70 */
|
||||
height: calc(100vh - 130px);
|
||||
}
|
||||
|
||||
.fixed-header + .app-main {
|
||||
padding-top: 99px;
|
||||
// padding-top: 99px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -21,25 +21,54 @@
|
||||
<script setup>
|
||||
import { computed } from "vue";
|
||||
import { useRouter, useRoute } from "vue-router";
|
||||
import useAppStore from '@/store/modules/app';
|
||||
import usePermissionStore from '@/store/modules/permission';
|
||||
|
||||
const router = useRouter();
|
||||
const route = useRoute();
|
||||
const appStore = useAppStore();
|
||||
const permissionStore = usePermissionStore();
|
||||
|
||||
// 定义Tab选项
|
||||
const tabs = [
|
||||
{ id: "platform", name: "平台概览", path: "/platform" },
|
||||
{ id: "vehicle", name: "车辆管理", path: "/car" },
|
||||
{ id: "car", name: "车辆管理", path: "/car" },
|
||||
{ id: "system", name: "系统管理", path: "/system" },
|
||||
];
|
||||
|
||||
// 判断路由是否激活
|
||||
function isActive(path) {
|
||||
return route.path === path;
|
||||
return route.path.startsWith(path);
|
||||
}
|
||||
|
||||
// 处理Tab点击,使用路由导航
|
||||
function handleTabClick(path) {
|
||||
if (path === '/platform') {
|
||||
// 平台概览页面,隐藏左侧菜单
|
||||
appStore.toggleSideBarHide(true);
|
||||
permissionStore.setCurrentTab('platform');
|
||||
router.push(path);
|
||||
} else if (path === '/car') {
|
||||
// 车辆管理页面,显示左侧菜单
|
||||
appStore.toggleSideBarHide(false);
|
||||
|
||||
// 设置当前Tab为car,过滤显示车辆相关菜单
|
||||
permissionStore.setCurrentTab('car');
|
||||
|
||||
// 直接跳转到车辆监控页面
|
||||
router.push('/car/monitor');
|
||||
} else if (path === '/system') {
|
||||
// 系统管理页面,显示左侧菜单
|
||||
appStore.toggleSideBarHide(false);
|
||||
|
||||
// 设置当前Tab为system,过滤显示系统相关菜单
|
||||
permissionStore.setCurrentTab('system');
|
||||
|
||||
// 角色管理页面应该是 /system/role
|
||||
router.push('/system/role');
|
||||
} else {
|
||||
router.push(path);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
@ -179,6 +179,20 @@ watch(() => route.path, (newPath) => {
|
||||
activeSubmenu.value = newPath;
|
||||
}
|
||||
});
|
||||
|
||||
// 监听侧边栏路由变化,确保路由更新时组件能正确刷新
|
||||
watch(() => permissionStore.sidebarRouters, (newRoutes) => {
|
||||
if (newRoutes && newRoutes.length > 0) {
|
||||
// 延迟执行,确保DOM更新完成
|
||||
nextTick(() => {
|
||||
// 当前路由路径
|
||||
const currentPath = route.path;
|
||||
if (currentPath) {
|
||||
activeSubmenu.value = currentPath;
|
||||
}
|
||||
});
|
||||
}
|
||||
}, { deep: true });
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
@ -5,7 +5,8 @@
|
||||
v-for="tag in visitedViews"
|
||||
:key="tag.path"
|
||||
:data-path="tag.path"
|
||||
:class="isActive(tag) ? 'active' : ''"
|
||||
v-show="shouldShowTag(tag)"
|
||||
:class="[isActive(tag) ? 'active' : '', isDefaultMenuTag(tag) && !isActive(tag) ? 'default-tag' : '']"
|
||||
:to="{ path: tag.path, query: tag.query, fullPath: tag.fullPath }"
|
||||
class="tags-view-item"
|
||||
:style="activeStyle(tag)"
|
||||
@ -63,10 +64,214 @@ const visitedViews = computed(() => useTagsViewStore().visitedViews);
|
||||
const routes = computed(() => usePermissionStore().routes);
|
||||
const theme = computed(() => useSettingsStore().theme);
|
||||
|
||||
watch(route, () => {
|
||||
addTags()
|
||||
moveToCurrentTag()
|
||||
})
|
||||
// 主菜单默认页面配置
|
||||
const defaultPages = {
|
||||
'/car': '/car/monitor', // 车辆管理 -> 车辆监控
|
||||
'/system': '/system/role', // 系统管理 -> 角色管理
|
||||
};
|
||||
|
||||
// 从路由结构中动态获取主菜单信息
|
||||
function getMenuInfoFromRoutes() {
|
||||
const menuInfo = {};
|
||||
const mainMenus = [];
|
||||
|
||||
try {
|
||||
// 获取所有一级路由
|
||||
const routes = router.options.routes;
|
||||
|
||||
routes.forEach(route => {
|
||||
// 检查是否有子路由且不是隐藏的路由
|
||||
if (route.children && route.children.length > 0 && !route.hidden) {
|
||||
// 如果有元数据且有标题,则认为是主菜单
|
||||
if (route.meta && route.meta.title) {
|
||||
mainMenus.push(route.meta.title);
|
||||
|
||||
// 查找默认子路由(通常是第一个非隐藏的子路由)
|
||||
let defaultChild = null;
|
||||
for (const child of route.children) {
|
||||
if (!child.hidden) {
|
||||
defaultChild = child;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// 如果找到默认子路由,记录主菜单路径与默认子路由路径的映射
|
||||
if (defaultChild && route.path) {
|
||||
const mainPath = route.path;
|
||||
const defaultPath = mainPath + '/' + (defaultChild.path || '');
|
||||
menuInfo[mainPath] = defaultPath;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('获取菜单信息时出错:', error);
|
||||
}
|
||||
|
||||
console.log('从路由中获取的菜单信息:', menuInfo, '主菜单列表:', mainMenus);
|
||||
return { menuInfo, mainMenus };
|
||||
}
|
||||
|
||||
// 初始化菜单信息
|
||||
const dynamicMenuInfo = ref({});
|
||||
const mainMenuTitles = ref([]);
|
||||
|
||||
// 检查路径所属的主菜单
|
||||
function getMainPath(path) {
|
||||
// 对于系统管理下的子菜单特殊处理,如人员管理/用户管理
|
||||
if (path.startsWith('/system/person/')) {
|
||||
return '/system';
|
||||
}
|
||||
|
||||
// 特别处理用户管理
|
||||
if (path === '/system/user' || path === '/system/user/index') {
|
||||
return '/system';
|
||||
}
|
||||
|
||||
const mainPaths = Object.keys(defaultPages);
|
||||
for (const mainPath of mainPaths) {
|
||||
// 检查路径是否以主菜单路径开头
|
||||
if (path.startsWith(mainPath + '/')) {
|
||||
return mainPath;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// 获取主菜单的默认页面路径
|
||||
function getDefaultPage(path) {
|
||||
const mainPath = getMainPath(path);
|
||||
return mainPath ? defaultPages[mainPath] : null;
|
||||
}
|
||||
|
||||
// 判断标签是否是主菜单的默认标签(车辆监控、角色管理)
|
||||
function isDefaultMenuTag(tag) {
|
||||
return Object.values(defaultPages).includes(tag.path);
|
||||
}
|
||||
|
||||
// 判断是否是当前路径所属主菜单的默认标签
|
||||
function isCurrentMainDefault(tag) {
|
||||
const mainPath = getMainPath(route.path);
|
||||
return mainPath && defaultPages[mainPath] === tag.path;
|
||||
}
|
||||
|
||||
// 判断是否是主菜单路径(例如/vehicle, /system)
|
||||
function isMainMenuPath(path) {
|
||||
// 检查是否是主菜单路径,这些路径不需要在标签中显示
|
||||
return Object.keys(defaultPages).includes(path);
|
||||
}
|
||||
|
||||
// 判断是否是主菜单路径或主菜单名称
|
||||
function isMainMenuOrTitle(tag) {
|
||||
// 检查路径
|
||||
if (Object.keys(defaultPages).includes(tag.path)) return true;
|
||||
|
||||
// 检查标题
|
||||
if (tag.meta && tag.meta.title) {
|
||||
return ['车辆管理', '系统管理', '用户管理', '平台概览'].includes(tag.meta.title);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// 判断是否显示当前标签(只显示当前主菜单下的标签)
|
||||
function shouldShowTag(tag) {
|
||||
// 对于车辆监控标签,只有在/car/路径下才显示
|
||||
if (tag.path === '/car/monitor') {
|
||||
return route.path.startsWith('/car/');
|
||||
}
|
||||
|
||||
// 对于角色管理标签,只有在/system/路径下才显示
|
||||
if (tag.path === '/system/role') {
|
||||
return route.path.startsWith('/system/');
|
||||
}
|
||||
|
||||
// 用户管理页面特殊处理 - 只在访问用户管理页面时显示
|
||||
if ((tag.path === '/system/user' || tag.path === '/system/user/index' ||
|
||||
(tag.meta && tag.meta.title === '用户管理'))) {
|
||||
// 只有当前访问用户管理相关页面时才显示用户管理标签
|
||||
if (route.path.includes('/user') ||
|
||||
(route.meta && route.meta.title === '用户管理')) {
|
||||
return true;
|
||||
} else {
|
||||
return false; // 不在用户管理页面不显示用户管理标签
|
||||
}
|
||||
}
|
||||
|
||||
// 检查标题 - 如果是主菜单标题,不显示
|
||||
if (tag.meta && tag.meta.title && mainMenuTitles.value.includes(tag.meta.title)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 检查路径 - 如果是主菜单路径,不显示
|
||||
if (isMainMenuPath(tag.path)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 如果是当前路径,始终显示
|
||||
if (tag.path === route.path) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 获取当前路径所属的主菜单
|
||||
const currentMainPath = getMainPath(route.path);
|
||||
|
||||
// 如果当前路径不属于任何主菜单,则显示该标签
|
||||
if (!currentMainPath) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 如果标签与当前路径属于同一主菜单,显示
|
||||
if (tag.path.startsWith(currentMainPath + '/')) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// 确保在visitedViews中添加了用户管理页面
|
||||
function ensureUserManagementTag() {
|
||||
// 只有在用户管理页面才添加用户管理标签
|
||||
// 检查路径是否包含user或标题是否为用户管理
|
||||
if (!route.path.includes('/user') &&
|
||||
!(route.meta && route.meta.title === '用户管理')) {
|
||||
return; // 不在用户管理页面不添加
|
||||
}
|
||||
|
||||
// 检查是否已经有用户管理标签
|
||||
const hasUserTag = visitedViews.value.some(tag =>
|
||||
tag.path === '/system/user' ||
|
||||
tag.path === '/system/person/user' ||
|
||||
(tag.meta && tag.meta.title === '用户管理')
|
||||
);
|
||||
|
||||
if (!hasUserTag) {
|
||||
// 创建用户管理标签
|
||||
const userTag = {
|
||||
path: '/system/user',
|
||||
name: 'User',
|
||||
meta: { title: '用户管理' },
|
||||
fullPath: '/system/user'
|
||||
};
|
||||
|
||||
// 手动添加到标签视图
|
||||
useTagsViewStore().addVisitedView(userTag);
|
||||
console.log('手动添加用户管理标签');
|
||||
}
|
||||
}
|
||||
|
||||
// 监听路由变化
|
||||
watch(route, (newRoute) => {
|
||||
console.log('Route changed to:', newRoute.path, newRoute.meta?.title);
|
||||
addTags();
|
||||
moveToCurrentTag();
|
||||
|
||||
// 只有在访问用户管理页面时才添加用户管理标签
|
||||
if (newRoute.path.includes('/user') ||
|
||||
(newRoute.meta && newRoute.meta.title === '用户管理')) {
|
||||
ensureUserManagementTag();
|
||||
}
|
||||
});
|
||||
watch(visible, (value) => {
|
||||
if (value) {
|
||||
document.body.addEventListener('click', closeMenu)
|
||||
@ -75,23 +280,71 @@ watch(visible, (value) => {
|
||||
}
|
||||
})
|
||||
onMounted(() => {
|
||||
initTags()
|
||||
addTags()
|
||||
})
|
||||
// 动态获取菜单信息
|
||||
const { menuInfo, mainMenus } = getMenuInfoFromRoutes();
|
||||
// 合并静态配置和动态获取的配置
|
||||
Object.keys(menuInfo).forEach(key => {
|
||||
if (!defaultPages[key]) {
|
||||
defaultPages[key] = menuInfo[key];
|
||||
}
|
||||
});
|
||||
// 更新主菜单标题列表
|
||||
mainMenuTitles.value = [...new Set([
|
||||
'车辆管理', '系统管理', '用户管理', '平台概览', '人员管理',
|
||||
...mainMenus
|
||||
])];
|
||||
|
||||
console.log('合并后的默认页面配置:', defaultPages);
|
||||
console.log('更新后的主菜单标题列表:', mainMenuTitles.value);
|
||||
|
||||
initTags();
|
||||
addTags();
|
||||
|
||||
// 如果当前路径是主菜单路径,自动跳转到默认页面
|
||||
if (isMainMenuPath(route.path)) {
|
||||
const defaultPage = defaultPages[route.path];
|
||||
if (defaultPage && route.path !== defaultPage) {
|
||||
router.replace(defaultPage);
|
||||
}
|
||||
}
|
||||
|
||||
// 添加调试日志
|
||||
console.log('Tags initialized, current route:', route.path, route.meta?.title);
|
||||
|
||||
// 只有在用户管理页面才添加用户管理标签
|
||||
if (route.path.includes('/user') ||
|
||||
(route.meta && route.meta.title === '用户管理')) {
|
||||
ensureUserManagementTag();
|
||||
}
|
||||
});
|
||||
|
||||
function isActive(r) {
|
||||
return r.path === route.path
|
||||
}
|
||||
|
||||
function activeStyle(tag) {
|
||||
if (!isActive(tag)) return {};
|
||||
if (isActive(tag)) {
|
||||
// 活动标签使用蓝色样式
|
||||
return {
|
||||
"background-color": theme.value,
|
||||
"border-color": theme.value
|
||||
"background-color": "transparent",
|
||||
color: '#5690E7',
|
||||
"border-color": "none"
|
||||
};
|
||||
}
|
||||
function isAffix(tag) {
|
||||
return tag.meta && tag.meta.affix
|
||||
|
||||
// 非活动标签
|
||||
return {
|
||||
"background-color": "transparent",
|
||||
color: '#ffffff',
|
||||
"border-color": "transparent"
|
||||
};
|
||||
}
|
||||
|
||||
function isAffix(tag) {
|
||||
// 判断是否为主菜单的默认页面,如果是则不允许关闭
|
||||
return (tag.meta && tag.meta.affix) || isDefaultMenuTag(tag);
|
||||
}
|
||||
|
||||
function isFirstView() {
|
||||
try {
|
||||
return selectedTag.value.fullPath === visitedViews.value[1].fullPath || selectedTag.value.fullPath === '/index'
|
||||
@ -99,6 +352,7 @@ function isFirstView() {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
function isLastView() {
|
||||
try {
|
||||
return selectedTag.value.fullPath === visitedViews.value[visitedViews.value.length - 1].fullPath
|
||||
@ -106,6 +360,7 @@ function isLastView() {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
function filterAffixTags(routes, basePath = '') {
|
||||
let tags = []
|
||||
routes.forEach(route => {
|
||||
@ -118,6 +373,18 @@ function filterAffixTags(routes, basePath = '') {
|
||||
meta: { ...route.meta }
|
||||
})
|
||||
}
|
||||
|
||||
// 添加默认页面作为固定标签
|
||||
const fullPath = getNormalPath(basePath + '/' + route.path);
|
||||
if (Object.values(defaultPages).includes(fullPath)) {
|
||||
tags.push({
|
||||
fullPath: fullPath,
|
||||
path: fullPath,
|
||||
name: route.name,
|
||||
meta: { ...route.meta, affix: true } // 将默认页面设为固定标签
|
||||
})
|
||||
}
|
||||
|
||||
if (route.children) {
|
||||
const tempTags = filterAffixTags(route.children, route.path)
|
||||
if (tempTags.length >= 1) {
|
||||
@ -127,26 +394,204 @@ function filterAffixTags(routes, basePath = '') {
|
||||
})
|
||||
return tags
|
||||
}
|
||||
|
||||
function initTags() {
|
||||
const res = filterAffixTags(routes.value);
|
||||
affixTags.value = res;
|
||||
for (const tag of res) {
|
||||
// Must have tag name
|
||||
|
||||
// 确保默认标签始终被添加
|
||||
// 添加车辆监控标签
|
||||
const carMonitorTag = {
|
||||
fullPath: '/car/monitor',
|
||||
path: '/car/monitor',
|
||||
name: 'CarMonitor',
|
||||
meta: { title: '车辆监控', affix: true }
|
||||
};
|
||||
|
||||
// 添加角色管理标签
|
||||
const roleTag = {
|
||||
fullPath: '/system/role',
|
||||
path: '/system/role',
|
||||
name: 'systemRole',
|
||||
meta: { title: '角色管理', affix: true }
|
||||
};
|
||||
|
||||
// 添加到固定标签列表
|
||||
const hasCarMonitor = affixTags.value.some(tag => tag.path === '/car/monitor');
|
||||
const hasRole = affixTags.value.some(tag => tag.path === '/system/role');
|
||||
|
||||
if (!hasCarMonitor) {
|
||||
affixTags.value.push(carMonitorTag);
|
||||
}
|
||||
|
||||
if (!hasRole) {
|
||||
affixTags.value.push(roleTag);
|
||||
}
|
||||
|
||||
// 将固定标签添加到访问视图中
|
||||
for (const tag of affixTags.value) {
|
||||
if (tag.name) {
|
||||
useTagsViewStore().addVisitedView(tag)
|
||||
useTagsViewStore().addVisitedView(tag);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function addTags() {
|
||||
const { name } = route
|
||||
const { name } = route;
|
||||
// 添加更详细的控制台输出,帮助调试
|
||||
console.log('Adding tags for route:', route.path, route.meta?.title, 'isMainMenu:', isMainMenuPath(route.path));
|
||||
|
||||
// 特别处理用户管理页面
|
||||
if (route.path === '/system/user' || route.path === '/system/user/index' ||
|
||||
(route.meta && route.meta.title === '用户管理')) {
|
||||
console.log('特别处理用户管理页面');
|
||||
|
||||
// 清除之前的非默认系统管理标签
|
||||
const cleanedViews = useTagsViewStore().visitedViews.filter(tag => {
|
||||
// 保留非系统管理相关标签和角色管理标签
|
||||
return !tag.path.startsWith('/system/') || tag.path === '/system/role';
|
||||
});
|
||||
useTagsViewStore().visitedViews = cleanedViews;
|
||||
|
||||
// 确保系统管理的默认标签(角色管理)也被添加,且在前面
|
||||
const roleTag = {
|
||||
fullPath: '/system/role',
|
||||
path: '/system/role',
|
||||
name: 'systemRole',
|
||||
meta: { title: '角色管理', affix: true }
|
||||
};
|
||||
|
||||
// 确保roleTag在数组开头(可能在第1个位置,如果0是首页的话)
|
||||
const hasRoleTag = useTagsViewStore().visitedViews.some(tag => tag.path === '/system/role');
|
||||
if (!hasRoleTag) {
|
||||
// 找到首页标签的位置,在其后添加角色管理标签
|
||||
const indexPosition = useTagsViewStore().visitedViews.findIndex(tag => tag.path === '/index');
|
||||
if (indexPosition !== -1) {
|
||||
useTagsViewStore().visitedViews.splice(indexPosition + 1, 0, roleTag);
|
||||
} else {
|
||||
useTagsViewStore().visitedViews.unshift(roleTag);
|
||||
}
|
||||
}
|
||||
|
||||
// 添加用户管理标签
|
||||
useTagsViewStore().addView(route);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if (name) {
|
||||
useTagsViewStore().addView(route)
|
||||
if (route.meta.link) {
|
||||
// 处理车辆管理相关页面
|
||||
if (route.path.startsWith('/car/') && route.path !== '/car/monitor') {
|
||||
// 清除之前的非默认车辆管理标签
|
||||
const cleanedViews = useTagsViewStore().visitedViews.filter(tag => {
|
||||
// 保留非车辆管理相关标签和车辆监控标签
|
||||
return !tag.path.startsWith('/car/') || tag.path === '/car/monitor';
|
||||
});
|
||||
useTagsViewStore().visitedViews = cleanedViews;
|
||||
|
||||
// 确保车辆管理的默认标签(车辆监控)在前面
|
||||
const carMonitorTag = {
|
||||
fullPath: '/car/monitor',
|
||||
path: '/car/monitor',
|
||||
name: 'CarMonitor',
|
||||
meta: { title: '车辆监控', affix: true }
|
||||
};
|
||||
|
||||
// 确保carMonitorTag在数组开头(可能在第1个位置,如果0是首页的话)
|
||||
const hasCarMonitorTag = useTagsViewStore().visitedViews.some(tag => tag.path === '/car/monitor');
|
||||
if (!hasCarMonitorTag) {
|
||||
// 找到首页标签的位置,在其后添加车辆监控标签
|
||||
const indexPosition = useTagsViewStore().visitedViews.findIndex(tag => tag.path === '/index');
|
||||
if (indexPosition !== -1) {
|
||||
useTagsViewStore().visitedViews.splice(indexPosition + 1, 0, carMonitorTag);
|
||||
} else {
|
||||
useTagsViewStore().visitedViews.unshift(carMonitorTag);
|
||||
}
|
||||
}
|
||||
|
||||
// 添加当前页面标签
|
||||
useTagsViewStore().addView(route);
|
||||
return false;
|
||||
}
|
||||
|
||||
// 处理系统管理相关页面
|
||||
if (route.path.startsWith('/system/') && route.path !== '/system/role') {
|
||||
// 清除之前的非默认系统管理标签
|
||||
const cleanedViews = useTagsViewStore().visitedViews.filter(tag => {
|
||||
// 保留非系统管理相关标签和角色管理标签
|
||||
return !tag.path.startsWith('/system/') || tag.path === '/system/role';
|
||||
});
|
||||
useTagsViewStore().visitedViews = cleanedViews;
|
||||
|
||||
// 确保系统管理的默认标签(角色管理)在前面
|
||||
const roleTag = {
|
||||
fullPath: '/system/role',
|
||||
path: '/system/role',
|
||||
name: 'systemRole',
|
||||
meta: { title: '角色管理', affix: true }
|
||||
};
|
||||
|
||||
// 确保roleTag在数组开头(可能在第1个位置,如果0是首页的话)
|
||||
const hasRoleTag = useTagsViewStore().visitedViews.some(tag => tag.path === '/system/role');
|
||||
if (!hasRoleTag) {
|
||||
// 找到首页标签的位置,在其后添加角色管理标签
|
||||
const indexPosition = useTagsViewStore().visitedViews.findIndex(tag => tag.path === '/index');
|
||||
if (indexPosition !== -1) {
|
||||
useTagsViewStore().visitedViews.splice(indexPosition + 1, 0, roleTag);
|
||||
} else {
|
||||
useTagsViewStore().visitedViews.unshift(roleTag);
|
||||
}
|
||||
}
|
||||
|
||||
// 添加当前页面标签
|
||||
useTagsViewStore().addView(route);
|
||||
return false;
|
||||
}
|
||||
|
||||
// 如果是主菜单路径或标题,不添加标签,而是添加对应的默认页面标签
|
||||
if (isMainMenuPath(route.path) || (route.meta && route.meta.title && ['车辆管理', '系统管理'].includes(route.meta.title))) {
|
||||
const defaultPage = defaultPages[route.path];
|
||||
if (defaultPage && route.path !== defaultPage) {
|
||||
// 找到默认页面的路由
|
||||
let defaultRoute = null;
|
||||
if (defaultPage === '/car/monitor') {
|
||||
defaultRoute = {
|
||||
fullPath: '/car/monitor',
|
||||
path: '/car/monitor',
|
||||
name: 'CarMonitor',
|
||||
meta: { title: '车辆监控', affix: true }
|
||||
};
|
||||
} else if (defaultPage === '/system/role') {
|
||||
defaultRoute = {
|
||||
fullPath: '/system/role',
|
||||
path: '/system/role',
|
||||
name: 'systemRole',
|
||||
meta: { title: '角色管理', affix: true }
|
||||
};
|
||||
}
|
||||
|
||||
// 添加默认页面的标签
|
||||
if (defaultRoute) {
|
||||
useTagsViewStore().addVisitedView(defaultRoute);
|
||||
}
|
||||
|
||||
// 跳转到默认页面
|
||||
router.replace(defaultPage);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// 不添加主菜单标签
|
||||
if (!isMainMenuOrTitle(route)) {
|
||||
useTagsViewStore().addView(route);
|
||||
if (route.meta && route.meta.link) {
|
||||
useTagsViewStore().addIframeView(route);
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function moveToCurrentTag() {
|
||||
nextTick(() => {
|
||||
for (const r of visitedViews.value) {
|
||||
@ -160,19 +605,30 @@ function moveToCurrentTag() {
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function refreshSelectedTag(view) {
|
||||
proxy.$tab.refreshPage(view);
|
||||
if (route.meta.link) {
|
||||
useTagsViewStore().delIframeView(route);
|
||||
}
|
||||
}
|
||||
|
||||
function closeSelectedTag(view) {
|
||||
proxy.$tab.closePage(view).then(({ visitedViews }) => {
|
||||
if (isActive(view)) {
|
||||
toLastView(visitedViews, view)
|
||||
// 获取当前路径所属的主菜单
|
||||
const mainPath = getMainPath(view.path);
|
||||
if (mainPath) {
|
||||
// 返回到对应主菜单的默认页面
|
||||
router.push(defaultPages[mainPath]);
|
||||
} else {
|
||||
// 原有的逻辑,返回到上一个标签
|
||||
toLastView(visitedViews, view);
|
||||
}
|
||||
})
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function closeRightTags() {
|
||||
proxy.$tab.closeRightPage(selectedTag.value).then(visitedViews => {
|
||||
if (!visitedViews.find(i => i.fullPath === route.fullPath)) {
|
||||
@ -180,6 +636,7 @@ function closeRightTags() {
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function closeLeftTags() {
|
||||
proxy.$tab.closeLeftPage(selectedTag.value).then(visitedViews => {
|
||||
if (!visitedViews.find(i => i.fullPath === route.fullPath)) {
|
||||
@ -187,12 +644,14 @@ function closeLeftTags() {
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function closeOthersTags() {
|
||||
router.push(selectedTag.value).catch(() => { });
|
||||
proxy.$tab.closeOtherPage(selectedTag.value).then(() => {
|
||||
moveToCurrentTag()
|
||||
})
|
||||
}
|
||||
|
||||
function closeAllTags(view) {
|
||||
proxy.$tab.closeAllPage().then(({ visitedViews }) => {
|
||||
if (affixTags.value.some(tag => tag.path === route.path)) {
|
||||
@ -201,6 +660,7 @@ function closeAllTags(view) {
|
||||
toLastView(visitedViews, view)
|
||||
})
|
||||
}
|
||||
|
||||
function toLastView(visitedViews, view) {
|
||||
const latestView = visitedViews.slice(-1)[0]
|
||||
if (latestView) {
|
||||
@ -216,6 +676,7 @@ function toLastView(visitedViews, view) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function openMenu(tag, e) {
|
||||
const menuMinWidth = 105
|
||||
const offsetLeft = proxy.$el.getBoundingClientRect().left // container margin left
|
||||
@ -233,9 +694,11 @@ function openMenu(tag, e) {
|
||||
visible.value = true
|
||||
selectedTag.value = tag
|
||||
}
|
||||
|
||||
function closeMenu() {
|
||||
visible.value = false
|
||||
}
|
||||
|
||||
function handleScroll() {
|
||||
closeMenu()
|
||||
}
|
||||
@ -243,25 +706,26 @@ function handleScroll() {
|
||||
|
||||
<style lang='scss' scoped>
|
||||
.tags-view-container {
|
||||
height: 34px;
|
||||
height: 70px;
|
||||
line-height: 70px;
|
||||
width: 100%;
|
||||
background: #fff;
|
||||
border-bottom: 1px solid #d8dce5;
|
||||
box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.12), 0 0 3px 0 rgba(0, 0, 0, 0.04);
|
||||
padding: 0 0 0 20px;
|
||||
background: #343744;
|
||||
.tags-view-wrapper {
|
||||
.tags-view-item {
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
height: 26px;
|
||||
line-height: 26px;
|
||||
border: 1px solid #d8dce5;
|
||||
color: #495060;
|
||||
background: #fff;
|
||||
padding: 0 8px;
|
||||
font-size: 12px;
|
||||
margin-left: 5px;
|
||||
margin-top: 4px;
|
||||
height: 28px;
|
||||
line-height: 28px;
|
||||
|
||||
color: #ffffff;
|
||||
background: #343744;
|
||||
padding: 0 5px;
|
||||
font-size: 16px;
|
||||
margin-left: 10px;
|
||||
|
||||
border-radius: 2px;
|
||||
&:first-of-type {
|
||||
margin-left: 15px;
|
||||
}
|
||||
@ -269,25 +733,41 @@ function handleScroll() {
|
||||
margin-right: 15px;
|
||||
}
|
||||
&.active {
|
||||
background-color: #42b983;
|
||||
background-color: #5690E7;
|
||||
color: #fff;
|
||||
border-color: #42b983;
|
||||
&::before {
|
||||
content: "";
|
||||
background: #fff;
|
||||
border-color: #5690E7;
|
||||
}
|
||||
&.default-tag {
|
||||
color: #ffffff;
|
||||
background-color: #343744;
|
||||
border-color: #4c4f5f;
|
||||
}
|
||||
|
||||
/* 鼠标悬停在标签上显示关闭按钮 */
|
||||
.el-icon-close {
|
||||
display: none;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
.el-icon-close {
|
||||
display: inline-block;
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-radius: 50%;
|
||||
position: relative;
|
||||
margin-right: 2px;
|
||||
color: #5690E7;
|
||||
}
|
||||
}
|
||||
|
||||
/* 活动标签样式 */
|
||||
&.active:hover {
|
||||
background-color: #5690E7;
|
||||
|
||||
.el-icon-close {
|
||||
color: #5690E7;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.contextmenu {
|
||||
margin: 0;
|
||||
background: #fff;
|
||||
background: #292C38;
|
||||
z-index: 3000;
|
||||
position: absolute;
|
||||
list-style-type: none;
|
||||
@ -295,14 +775,14 @@ function handleScroll() {
|
||||
border-radius: 4px;
|
||||
font-size: 12px;
|
||||
font-weight: 400;
|
||||
color: #333;
|
||||
color: #ffffff;
|
||||
box-shadow: 2px 2px 3px 0 rgba(0, 0, 0, 0.3);
|
||||
li {
|
||||
margin: 0;
|
||||
padding: 7px 16px;
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
background: #eee;
|
||||
background: #5690E7;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -327,12 +807,16 @@ function handleScroll() {
|
||||
vertical-align: -3px;
|
||||
}
|
||||
&:hover {
|
||||
background-color: #b4bccc;
|
||||
color: #fff;
|
||||
// background-color: #5690E7;
|
||||
color: #5690E7;
|
||||
width: 12px !important;
|
||||
height: 12px !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.active .el-icon-close {
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
338
src/layout/components/TagsView/index原版.vue
Normal file
338
src/layout/components/TagsView/index原版.vue
Normal file
@ -0,0 +1,338 @@
|
||||
<template>
|
||||
<div id="tags-view-container" class="tags-view-container">
|
||||
<scroll-pane ref="scrollPaneRef" class="tags-view-wrapper" @scroll="handleScroll">
|
||||
<router-link
|
||||
v-for="tag in visitedViews"
|
||||
:key="tag.path"
|
||||
:data-path="tag.path"
|
||||
:class="isActive(tag) ? 'active' : ''"
|
||||
:to="{ path: tag.path, query: tag.query, fullPath: tag.fullPath }"
|
||||
class="tags-view-item"
|
||||
:style="activeStyle(tag)"
|
||||
@click.middle="!isAffix(tag) ? closeSelectedTag(tag) : ''"
|
||||
@contextmenu.prevent="openMenu(tag, $event)"
|
||||
>
|
||||
{{ tag.title }}
|
||||
<span v-if="!isAffix(tag)" @click.prevent.stop="closeSelectedTag(tag)">
|
||||
<close class="el-icon-close" style="width: 1em; height: 1em;vertical-align: middle;" />
|
||||
</span>
|
||||
</router-link>
|
||||
</scroll-pane>
|
||||
<ul v-show="visible" :style="{ left: left + 'px', top: top + 'px' }" class="contextmenu">
|
||||
<li @click="refreshSelectedTag(selectedTag)">
|
||||
<refresh-right style="width: 1em; height: 1em;" /> 刷新页面
|
||||
</li>
|
||||
<li v-if="!isAffix(selectedTag)" @click="closeSelectedTag(selectedTag)">
|
||||
<close style="width: 1em; height: 1em;" /> 关闭当前
|
||||
</li>
|
||||
<li @click="closeOthersTags">
|
||||
<circle-close style="width: 1em; height: 1em;" /> 关闭其他
|
||||
</li>
|
||||
<li v-if="!isFirstView()" @click="closeLeftTags">
|
||||
<back style="width: 1em; height: 1em;" /> 关闭左侧
|
||||
</li>
|
||||
<li v-if="!isLastView()" @click="closeRightTags">
|
||||
<right style="width: 1em; height: 1em;" /> 关闭右侧
|
||||
</li>
|
||||
<li @click="closeAllTags(selectedTag)">
|
||||
<circle-close style="width: 1em; height: 1em;" /> 全部关闭
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import ScrollPane from './ScrollPane'
|
||||
import { getNormalPath } from '@/utils/ruoyi'
|
||||
import useTagsViewStore from '@/store/modules/tagsView'
|
||||
import useSettingsStore from '@/store/modules/settings'
|
||||
import usePermissionStore from '@/store/modules/permission'
|
||||
|
||||
const visible = ref(false);
|
||||
const top = ref(0);
|
||||
const left = ref(0);
|
||||
const selectedTag = ref({});
|
||||
const affixTags = ref([]);
|
||||
const scrollPaneRef = ref(null);
|
||||
|
||||
const { proxy } = getCurrentInstance();
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
|
||||
const visitedViews = computed(() => useTagsViewStore().visitedViews);
|
||||
const routes = computed(() => usePermissionStore().routes);
|
||||
const theme = computed(() => useSettingsStore().theme);
|
||||
|
||||
watch(route, () => {
|
||||
addTags()
|
||||
moveToCurrentTag()
|
||||
})
|
||||
watch(visible, (value) => {
|
||||
if (value) {
|
||||
document.body.addEventListener('click', closeMenu)
|
||||
} else {
|
||||
document.body.removeEventListener('click', closeMenu)
|
||||
}
|
||||
})
|
||||
onMounted(() => {
|
||||
initTags()
|
||||
addTags()
|
||||
})
|
||||
|
||||
function isActive(r) {
|
||||
return r.path === route.path
|
||||
}
|
||||
function activeStyle(tag) {
|
||||
if (!isActive(tag)) return {};
|
||||
return {
|
||||
"background-color": theme.value,
|
||||
"border-color": theme.value
|
||||
};
|
||||
}
|
||||
function isAffix(tag) {
|
||||
return tag.meta && tag.meta.affix
|
||||
}
|
||||
function isFirstView() {
|
||||
try {
|
||||
return selectedTag.value.fullPath === visitedViews.value[1].fullPath || selectedTag.value.fullPath === '/index'
|
||||
} catch (err) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
function isLastView() {
|
||||
try {
|
||||
return selectedTag.value.fullPath === visitedViews.value[visitedViews.value.length - 1].fullPath
|
||||
} catch (err) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
function filterAffixTags(routes, basePath = '') {
|
||||
let tags = []
|
||||
routes.forEach(route => {
|
||||
if (route.meta && route.meta.affix) {
|
||||
const tagPath = getNormalPath(basePath + '/' + route.path)
|
||||
tags.push({
|
||||
fullPath: tagPath,
|
||||
path: tagPath,
|
||||
name: route.name,
|
||||
meta: { ...route.meta }
|
||||
})
|
||||
}
|
||||
if (route.children) {
|
||||
const tempTags = filterAffixTags(route.children, route.path)
|
||||
if (tempTags.length >= 1) {
|
||||
tags = [...tags, ...tempTags]
|
||||
}
|
||||
}
|
||||
})
|
||||
return tags
|
||||
}
|
||||
function initTags() {
|
||||
const res = filterAffixTags(routes.value);
|
||||
affixTags.value = res;
|
||||
for (const tag of res) {
|
||||
// Must have tag name
|
||||
if (tag.name) {
|
||||
useTagsViewStore().addVisitedView(tag)
|
||||
}
|
||||
}
|
||||
}
|
||||
function addTags() {
|
||||
const { name } = route
|
||||
if (name) {
|
||||
useTagsViewStore().addView(route)
|
||||
if (route.meta.link) {
|
||||
useTagsViewStore().addIframeView(route);
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
function moveToCurrentTag() {
|
||||
nextTick(() => {
|
||||
for (const r of visitedViews.value) {
|
||||
if (r.path === route.path) {
|
||||
scrollPaneRef.value.moveToTarget(r);
|
||||
// when query is different then update
|
||||
if (r.fullPath !== route.fullPath) {
|
||||
useTagsViewStore().updateVisitedView(route)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
function refreshSelectedTag(view) {
|
||||
proxy.$tab.refreshPage(view);
|
||||
if (route.meta.link) {
|
||||
useTagsViewStore().delIframeView(route);
|
||||
}
|
||||
}
|
||||
function closeSelectedTag(view) {
|
||||
proxy.$tab.closePage(view).then(({ visitedViews }) => {
|
||||
if (isActive(view)) {
|
||||
toLastView(visitedViews, view)
|
||||
}
|
||||
})
|
||||
}
|
||||
function closeRightTags() {
|
||||
proxy.$tab.closeRightPage(selectedTag.value).then(visitedViews => {
|
||||
if (!visitedViews.find(i => i.fullPath === route.fullPath)) {
|
||||
toLastView(visitedViews)
|
||||
}
|
||||
})
|
||||
}
|
||||
function closeLeftTags() {
|
||||
proxy.$tab.closeLeftPage(selectedTag.value).then(visitedViews => {
|
||||
if (!visitedViews.find(i => i.fullPath === route.fullPath)) {
|
||||
toLastView(visitedViews)
|
||||
}
|
||||
})
|
||||
}
|
||||
function closeOthersTags() {
|
||||
router.push(selectedTag.value).catch(() => { });
|
||||
proxy.$tab.closeOtherPage(selectedTag.value).then(() => {
|
||||
moveToCurrentTag()
|
||||
})
|
||||
}
|
||||
function closeAllTags(view) {
|
||||
proxy.$tab.closeAllPage().then(({ visitedViews }) => {
|
||||
if (affixTags.value.some(tag => tag.path === route.path)) {
|
||||
return
|
||||
}
|
||||
toLastView(visitedViews, view)
|
||||
})
|
||||
}
|
||||
function toLastView(visitedViews, view) {
|
||||
const latestView = visitedViews.slice(-1)[0]
|
||||
if (latestView) {
|
||||
router.push(latestView.fullPath)
|
||||
} else {
|
||||
// now the default is to redirect to the home page if there is no tags-view,
|
||||
// you can adjust it according to your needs.
|
||||
if (view.name === 'Dashboard') {
|
||||
// to reload home page
|
||||
router.replace({ path: '/redirect' + view.fullPath })
|
||||
} else {
|
||||
router.push('/')
|
||||
}
|
||||
}
|
||||
}
|
||||
function openMenu(tag, e) {
|
||||
const menuMinWidth = 105
|
||||
const offsetLeft = proxy.$el.getBoundingClientRect().left // container margin left
|
||||
const offsetWidth = proxy.$el.offsetWidth // container width
|
||||
const maxLeft = offsetWidth - menuMinWidth // left boundary
|
||||
const l = e.clientX - offsetLeft + 15 // 15: margin right
|
||||
|
||||
if (l > maxLeft) {
|
||||
left.value = maxLeft
|
||||
} else {
|
||||
left.value = l
|
||||
}
|
||||
|
||||
top.value = e.clientY
|
||||
visible.value = true
|
||||
selectedTag.value = tag
|
||||
}
|
||||
function closeMenu() {
|
||||
visible.value = false
|
||||
}
|
||||
function handleScroll() {
|
||||
closeMenu()
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang='scss' scoped>
|
||||
.tags-view-container {
|
||||
height: 70px;
|
||||
line-height: 70px;
|
||||
width: 100%;
|
||||
padding:0 0 0 20px;
|
||||
background: #343744;;
|
||||
.tags-view-wrapper {
|
||||
.tags-view-item {
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
height: 26px;
|
||||
line-height: 26px;
|
||||
border: 1px solid #d8dce5;
|
||||
color: #495060;
|
||||
background: #fff;
|
||||
padding: 0 8px;
|
||||
font-size: 12px;
|
||||
margin-left: 5px;
|
||||
margin-top: 4px;
|
||||
&:first-of-type {
|
||||
margin-left: 15px;
|
||||
}
|
||||
&:last-of-type {
|
||||
margin-right: 15px;
|
||||
}
|
||||
&.active {
|
||||
background-color: #42b983;
|
||||
color: #fff;
|
||||
border-color: #42b983;
|
||||
&::before {
|
||||
content: "";
|
||||
background: #fff;
|
||||
display: inline-block;
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-radius: 50%;
|
||||
position: relative;
|
||||
margin-right: 2px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.contextmenu {
|
||||
margin: 0;
|
||||
background: #fff;
|
||||
z-index: 3000;
|
||||
position: absolute;
|
||||
list-style-type: none;
|
||||
padding: 5px 0;
|
||||
border-radius: 4px;
|
||||
font-size: 12px;
|
||||
font-weight: 400;
|
||||
color: #333;
|
||||
box-shadow: 2px 2px 3px 0 rgba(0, 0, 0, 0.3);
|
||||
li {
|
||||
margin: 0;
|
||||
padding: 7px 16px;
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
background: #eee;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<style lang="scss">
|
||||
//reset element css of el-icon-close
|
||||
.tags-view-wrapper {
|
||||
.tags-view-item {
|
||||
.el-icon-close {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
vertical-align: 2px;
|
||||
border-radius: 50%;
|
||||
text-align: center;
|
||||
transition: all 0.3s cubic-bezier(0.645, 0.045, 0.355, 1);
|
||||
transform-origin: 100% 50%;
|
||||
&:before {
|
||||
transform: scale(0.6);
|
||||
display: inline-block;
|
||||
vertical-align: -3px;
|
||||
}
|
||||
&:hover {
|
||||
background-color: #b4bccc;
|
||||
color: #fff;
|
||||
width: 12px !important;
|
||||
height: 12px !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
647
src/layout/components/TagsView/index备份.vue
Normal file
647
src/layout/components/TagsView/index备份.vue
Normal file
@ -0,0 +1,647 @@
|
||||
<template>
|
||||
<div id="tags-view-container" class="tags-view-container">
|
||||
<scroll-pane ref="scrollPaneRef" class="tags-view-wrapper" @scroll="handleScroll">
|
||||
<router-link
|
||||
v-for="tag in visitedViews"
|
||||
:key="tag.path"
|
||||
:data-path="tag.path"
|
||||
v-show="shouldShowTag(tag)"
|
||||
:class="[isActive(tag) ? 'active' : '', isDefaultMenuTag(tag) && !isActive(tag) ? 'default-tag' : '']"
|
||||
:to="{ path: tag.path, query: tag.query, fullPath: tag.fullPath }"
|
||||
class="tags-view-item"
|
||||
:style="activeStyle(tag)"
|
||||
@click.middle="!isAffix(tag) ? closeSelectedTag(tag) : ''"
|
||||
@contextmenu.prevent="openMenu(tag, $event)"
|
||||
>
|
||||
{{ tag.title }}
|
||||
<span v-if="!isAffix(tag)" @click.prevent.stop="closeSelectedTag(tag)">
|
||||
<close class="el-icon-close" style="width: 1em; height: 1em;vertical-align: middle;" />
|
||||
</span>
|
||||
</router-link>
|
||||
</scroll-pane>
|
||||
<ul v-show="visible" :style="{ left: left + 'px', top: top + 'px' }" class="contextmenu">
|
||||
<li @click="refreshSelectedTag(selectedTag)">
|
||||
<refresh-right style="width: 1em; height: 1em;" /> 刷新页面
|
||||
</li>
|
||||
<li v-if="!isAffix(selectedTag)" @click="closeSelectedTag(selectedTag)">
|
||||
<close style="width: 1em; height: 1em;" /> 关闭当前
|
||||
</li>
|
||||
<li @click="closeOthersTags">
|
||||
<circle-close style="width: 1em; height: 1em;" /> 关闭其他
|
||||
</li>
|
||||
<li v-if="!isFirstView()" @click="closeLeftTags">
|
||||
<back style="width: 1em; height: 1em;" /> 关闭左侧
|
||||
</li>
|
||||
<li v-if="!isLastView()" @click="closeRightTags">
|
||||
<right style="width: 1em; height: 1em;" /> 关闭右侧
|
||||
</li>
|
||||
<li @click="closeAllTags(selectedTag)">
|
||||
<circle-close style="width: 1em; height: 1em;" /> 全部关闭
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import ScrollPane from './ScrollPane'
|
||||
import { getNormalPath } from '@/utils/ruoyi'
|
||||
import useTagsViewStore from '@/store/modules/tagsView'
|
||||
import useSettingsStore from '@/store/modules/settings'
|
||||
import usePermissionStore from '@/store/modules/permission'
|
||||
|
||||
const visible = ref(false);
|
||||
const top = ref(0);
|
||||
const left = ref(0);
|
||||
const selectedTag = ref({});
|
||||
const affixTags = ref([]);
|
||||
const scrollPaneRef = ref(null);
|
||||
|
||||
const { proxy } = getCurrentInstance();
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
|
||||
const visitedViews = computed(() => useTagsViewStore().visitedViews);
|
||||
const routes = computed(() => usePermissionStore().routes);
|
||||
const theme = computed(() => useSettingsStore().theme);
|
||||
|
||||
// 主菜单默认页面配置
|
||||
const defaultPages = {
|
||||
'/car': '/car/monitor/index', // 车辆管理 -> 车辆监控
|
||||
'/system': '/system/role/index', // 系统管理 -> 角色管理
|
||||
};
|
||||
|
||||
// 检查路径所属的主菜单
|
||||
function getMainPath(path) {
|
||||
// 对于系统管理下的子菜单特殊处理,如人员管理/用户管理
|
||||
if (path.startsWith('/system/person/')) {
|
||||
return '/system';
|
||||
}
|
||||
|
||||
// 特别处理用户管理
|
||||
if (path === '/system/user' || path === '/system/user/index') {
|
||||
return '/system';
|
||||
}
|
||||
|
||||
const mainPaths = Object.keys(defaultPages);
|
||||
for (const mainPath of mainPaths) {
|
||||
// 检查路径是否以主菜单路径开头
|
||||
if (path.startsWith(mainPath + '/')) {
|
||||
return mainPath;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// 获取主菜单的默认页面路径
|
||||
function getDefaultPage(path) {
|
||||
const mainPath = getMainPath(path);
|
||||
return mainPath ? defaultPages[mainPath] : null;
|
||||
}
|
||||
|
||||
// 判断标签是否是主菜单的默认标签(车辆监控、角色管理)
|
||||
function isDefaultMenuTag(tag) {
|
||||
return Object.values(defaultPages).includes(tag.path);
|
||||
}
|
||||
|
||||
// 判断是否是当前路径所属主菜单的默认标签
|
||||
function isCurrentMainDefault(tag) {
|
||||
const mainPath = getMainPath(route.path);
|
||||
return mainPath && defaultPages[mainPath] === tag.path;
|
||||
}
|
||||
|
||||
// 判断是否是主菜单路径(例如/vehicle, /system)
|
||||
function isMainMenuPath(path) {
|
||||
// 检查是否是主菜单路径,这些路径不需要在标签中显示
|
||||
return Object.keys(defaultPages).includes(path);
|
||||
}
|
||||
|
||||
// 判断是否是主菜单路径或主菜单名称
|
||||
function isMainMenuOrTitle(tag) {
|
||||
// 检查路径
|
||||
if (Object.keys(defaultPages).includes(tag.path)) return true;
|
||||
|
||||
// 检查标题
|
||||
if (tag.meta && tag.meta.title) {
|
||||
return ['车辆管理', '系统管理', '用户管理', '平台概览'].includes(tag.meta.title);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// 判断是否显示当前标签(只显示当前主菜单下的标签)
|
||||
function shouldShowTag(tag) {
|
||||
// 用户管理页面特殊处理 - 只在访问用户管理页面时显示
|
||||
if ((tag.path === '/system/user' || tag.path === '/system/user/index' ||
|
||||
(tag.meta && tag.meta.title === '用户管理'))) {
|
||||
// 只有当前访问用户管理相关页面时才显示用户管理标签
|
||||
if (route.path.includes('/user') ||
|
||||
(route.meta && route.meta.title === '用户管理')) {
|
||||
console.log('在用户管理页面显示用户管理标签');
|
||||
return true;
|
||||
} else {
|
||||
return false; // 不在用户管理页面不显示用户管理标签
|
||||
}
|
||||
}
|
||||
|
||||
// 车辆管理子页面特殊处理 - 车辆监控以外的子页面只在访问时显示
|
||||
// 如果不是车辆监控标签,且路径以/car/开头
|
||||
if (tag.path.startsWith('/car/') && tag.path !== defaultPages['/car'] &&
|
||||
!(tag.meta && tag.meta.title === '车辆监控')) {
|
||||
// 只有当前正在访问该页面时才显示
|
||||
return tag.path === route.path;
|
||||
}
|
||||
|
||||
// 主菜单标题列表
|
||||
const mainMenuTitles = ['车辆管理', '系统管理', '用户管理', '平台概览', '人员管理'];
|
||||
|
||||
// 检查标题 - 如果是主菜单标题,不显示
|
||||
if (tag.meta && tag.meta.title && mainMenuTitles.includes(tag.meta.title)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 检查路径 - 如果是主菜单路径,不显示
|
||||
if (isMainMenuPath(tag.path)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 如果是当前路径,始终显示
|
||||
if (tag.path === route.path) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 获取当前路径所属的主菜单
|
||||
const currentMainPath = getMainPath(route.path);
|
||||
|
||||
// 如果当前路径不属于任何主菜单,则显示该标签
|
||||
if (!currentMainPath) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 特殊处理系统管理下的子菜单
|
||||
if (currentMainPath === '/system') {
|
||||
// 如果是角色管理,始终显示
|
||||
if (tag.path === defaultPages['/system']) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 其他系统管理下的页面,只在访问相关页面时显示
|
||||
if (tag.path.startsWith('/system/') && tag.meta && tag.meta.title) {
|
||||
// 如果当前正在访问该页面,显示标签
|
||||
if (tag.path === route.path) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 特殊处理:用户管理页面的情况在上面已经处理过了
|
||||
if (tag.meta.title === '用户管理') {
|
||||
return false; // 默认不显示用户管理标签
|
||||
}
|
||||
|
||||
// 其他系统管理页面按需显示
|
||||
return tag.path === route.path;
|
||||
}
|
||||
}
|
||||
// 特殊处理车辆管理下的子菜单
|
||||
else if (currentMainPath === '/car') {
|
||||
// 如果是车辆监控,始终显示
|
||||
if (tag.path === defaultPages['/car']) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 其他车辆管理下的页面,只在访问相关页面时显示
|
||||
if (tag.path.startsWith('/car/') && tag.meta && tag.meta.title) {
|
||||
// 如果当前正在访问该页面,显示标签
|
||||
return tag.path === route.path;
|
||||
}
|
||||
}
|
||||
else {
|
||||
// 如果是当前主菜单的默认页面,显示
|
||||
if (tag.path === defaultPages[currentMainPath]) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 如果标签与当前路径属于同一主菜单,显示
|
||||
const tagMainPath = getMainPath(tag.path);
|
||||
return tagMainPath === currentMainPath;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// 确保在visitedViews中添加了用户管理页面
|
||||
function ensureUserManagementTag() {
|
||||
// 只有在用户管理页面才添加用户管理标签
|
||||
// 检查路径是否包含user或标题是否为用户管理
|
||||
if (!route.path.includes('/user') &&
|
||||
!(route.meta && route.meta.title === '用户管理')) {
|
||||
return; // 不在用户管理页面不添加
|
||||
}
|
||||
|
||||
// 检查是否已经有用户管理标签
|
||||
const hasUserTag = visitedViews.value.some(tag =>
|
||||
tag.path === '/system/user' ||
|
||||
tag.path === '/system/person/user' ||
|
||||
(tag.meta && tag.meta.title === '用户管理')
|
||||
);
|
||||
|
||||
if (!hasUserTag) {
|
||||
// 创建用户管理标签
|
||||
const userTag = {
|
||||
path: '/system/user',
|
||||
name: 'User',
|
||||
meta: { title: '用户管理' },
|
||||
fullPath: '/system/user'
|
||||
};
|
||||
|
||||
// 手动添加到标签视图
|
||||
useTagsViewStore().addVisitedView(userTag);
|
||||
console.log('手动添加用户管理标签');
|
||||
}
|
||||
}
|
||||
|
||||
// 监听路由变化
|
||||
watch(route, (newRoute) => {
|
||||
console.log('Route changed to:', newRoute.path, newRoute.meta?.title);
|
||||
addTags();
|
||||
moveToCurrentTag();
|
||||
|
||||
// 只有在访问用户管理页面时才添加用户管理标签
|
||||
if (newRoute.path.includes('/user') ||
|
||||
(newRoute.meta && newRoute.meta.title === '用户管理')) {
|
||||
ensureUserManagementTag();
|
||||
}
|
||||
});
|
||||
watch(visible, (value) => {
|
||||
if (value) {
|
||||
document.body.addEventListener('click', closeMenu)
|
||||
} else {
|
||||
document.body.removeEventListener('click', closeMenu)
|
||||
}
|
||||
})
|
||||
onMounted(() => {
|
||||
initTags();
|
||||
addTags();
|
||||
|
||||
// 如果当前路径是主菜单路径,自动跳转到默认页面
|
||||
if (isMainMenuPath(route.path)) {
|
||||
const defaultPage = defaultPages[route.path];
|
||||
if (defaultPage && route.path !== defaultPage) {
|
||||
router.replace(defaultPage);
|
||||
}
|
||||
}
|
||||
|
||||
// 添加调试日志
|
||||
console.log('Tags initialized, current route:', route.path, route.meta?.title);
|
||||
|
||||
// 只有在用户管理页面才添加用户管理标签
|
||||
if (route.path.includes('/user') ||
|
||||
(route.meta && route.meta.title === '用户管理')) {
|
||||
ensureUserManagementTag();
|
||||
}
|
||||
})
|
||||
|
||||
function isActive(r) {
|
||||
return r.path === route.path
|
||||
}
|
||||
|
||||
function activeStyle(tag) {
|
||||
if (!isActive(tag)) {
|
||||
// 非活动标签没有特殊样式
|
||||
return {};
|
||||
}
|
||||
|
||||
// 活动标签使用蓝色样式
|
||||
return {
|
||||
"background-color": "#5690E7",
|
||||
"border-color": "#5690E7"
|
||||
};
|
||||
}
|
||||
|
||||
function isAffix(tag) {
|
||||
// 判断是否为主菜单的默认页面,如果是则不允许关闭
|
||||
return (tag.meta && tag.meta.affix) || isDefaultMenuTag(tag);
|
||||
}
|
||||
|
||||
function isFirstView() {
|
||||
try {
|
||||
return selectedTag.value.fullPath === visitedViews.value[1].fullPath || selectedTag.value.fullPath === '/index'
|
||||
} catch (err) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
function isLastView() {
|
||||
try {
|
||||
return selectedTag.value.fullPath === visitedViews.value[visitedViews.value.length - 1].fullPath
|
||||
} catch (err) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
function filterAffixTags(routes, basePath = '') {
|
||||
let tags = []
|
||||
routes.forEach(route => {
|
||||
if (route.meta && route.meta.affix) {
|
||||
const tagPath = getNormalPath(basePath + '/' + route.path)
|
||||
tags.push({
|
||||
fullPath: tagPath,
|
||||
path: tagPath,
|
||||
name: route.name,
|
||||
meta: { ...route.meta }
|
||||
})
|
||||
}
|
||||
|
||||
// 添加默认页面作为固定标签
|
||||
const fullPath = getNormalPath(basePath + '/' + route.path);
|
||||
if (Object.values(defaultPages).includes(fullPath)) {
|
||||
tags.push({
|
||||
fullPath: fullPath,
|
||||
path: fullPath,
|
||||
name: route.name,
|
||||
meta: { ...route.meta, affix: true } // 将默认页面设为固定标签
|
||||
})
|
||||
}
|
||||
|
||||
if (route.children) {
|
||||
const tempTags = filterAffixTags(route.children, route.path)
|
||||
if (tempTags.length >= 1) {
|
||||
tags = [...tags, ...tempTags]
|
||||
}
|
||||
}
|
||||
})
|
||||
return tags
|
||||
}
|
||||
|
||||
function initTags() {
|
||||
const res = filterAffixTags(routes.value);
|
||||
affixTags.value = res;
|
||||
for (const tag of res) {
|
||||
// Must have tag name
|
||||
if (tag.name) {
|
||||
useTagsViewStore().addVisitedView(tag)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function addTags() {
|
||||
const { name } = route;
|
||||
// 添加更详细的控制台输出,帮助调试
|
||||
console.log('Adding tags for route:', route.path, route.meta?.title, 'isMainMenu:', isMainMenuPath(route.path));
|
||||
|
||||
// 特别处理用户管理页面
|
||||
if (route.path === '/system/user' || route.path === '/system/user/index' ||
|
||||
(route.meta && route.meta.title === '用户管理')) {
|
||||
console.log('特别处理用户管理页面');
|
||||
// 确保标签存储包含用户管理页面
|
||||
useTagsViewStore().addView(route);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (name) {
|
||||
// 如果是主菜单路径或标题,不添加标签,而是直接跳转到对应的默认页面
|
||||
if (isMainMenuPath(route.path) || (route.meta && route.meta.title && ['车辆管理', '系统管理'].includes(route.meta.title))) {
|
||||
const defaultPage = defaultPages[route.path];
|
||||
if (defaultPage && route.path !== defaultPage) {
|
||||
router.replace(defaultPage);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// 不添加主菜单标签
|
||||
if (!isMainMenuOrTitle(route)) {
|
||||
useTagsViewStore().addView(route);
|
||||
if (route.meta && route.meta.link) {
|
||||
useTagsViewStore().addIframeView(route);
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function moveToCurrentTag() {
|
||||
nextTick(() => {
|
||||
for (const r of visitedViews.value) {
|
||||
if (r.path === route.path) {
|
||||
scrollPaneRef.value.moveToTarget(r);
|
||||
// when query is different then update
|
||||
if (r.fullPath !== route.fullPath) {
|
||||
useTagsViewStore().updateVisitedView(route)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function refreshSelectedTag(view) {
|
||||
proxy.$tab.refreshPage(view);
|
||||
if (route.meta.link) {
|
||||
useTagsViewStore().delIframeView(route);
|
||||
}
|
||||
}
|
||||
|
||||
function closeSelectedTag(view) {
|
||||
proxy.$tab.closePage(view).then(({ visitedViews }) => {
|
||||
if (isActive(view)) {
|
||||
// 获取当前路径所属的主菜单
|
||||
const mainPath = getMainPath(view.path);
|
||||
if (mainPath) {
|
||||
// 返回到对应主菜单的默认页面
|
||||
router.push(defaultPages[mainPath]);
|
||||
} else {
|
||||
// 原有的逻辑,返回到上一个标签
|
||||
toLastView(visitedViews, view);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function closeRightTags() {
|
||||
proxy.$tab.closeRightPage(selectedTag.value).then(visitedViews => {
|
||||
if (!visitedViews.find(i => i.fullPath === route.fullPath)) {
|
||||
toLastView(visitedViews)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function closeLeftTags() {
|
||||
proxy.$tab.closeLeftPage(selectedTag.value).then(visitedViews => {
|
||||
if (!visitedViews.find(i => i.fullPath === route.fullPath)) {
|
||||
toLastView(visitedViews)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function closeOthersTags() {
|
||||
router.push(selectedTag.value).catch(() => { });
|
||||
proxy.$tab.closeOtherPage(selectedTag.value).then(() => {
|
||||
moveToCurrentTag()
|
||||
})
|
||||
}
|
||||
|
||||
function closeAllTags(view) {
|
||||
proxy.$tab.closeAllPage().then(({ visitedViews }) => {
|
||||
if (affixTags.value.some(tag => tag.path === route.path)) {
|
||||
return
|
||||
}
|
||||
toLastView(visitedViews, view)
|
||||
})
|
||||
}
|
||||
|
||||
function toLastView(visitedViews, view) {
|
||||
const latestView = visitedViews.slice(-1)[0]
|
||||
if (latestView) {
|
||||
router.push(latestView.fullPath)
|
||||
} else {
|
||||
// now the default is to redirect to the home page if there is no tags-view,
|
||||
// you can adjust it according to your needs.
|
||||
if (view.name === 'Dashboard') {
|
||||
// to reload home page
|
||||
router.replace({ path: '/redirect' + view.fullPath })
|
||||
} else {
|
||||
router.push('/')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function openMenu(tag, e) {
|
||||
const menuMinWidth = 105
|
||||
const offsetLeft = proxy.$el.getBoundingClientRect().left // container margin left
|
||||
const offsetWidth = proxy.$el.offsetWidth // container width
|
||||
const maxLeft = offsetWidth - menuMinWidth // left boundary
|
||||
const l = e.clientX - offsetLeft + 15 // 15: margin right
|
||||
|
||||
if (l > maxLeft) {
|
||||
left.value = maxLeft
|
||||
} else {
|
||||
left.value = l
|
||||
}
|
||||
|
||||
top.value = e.clientY
|
||||
visible.value = true
|
||||
selectedTag.value = tag
|
||||
}
|
||||
|
||||
function closeMenu() {
|
||||
visible.value = false
|
||||
}
|
||||
|
||||
function handleScroll() {
|
||||
closeMenu()
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang='scss' scoped>
|
||||
.tags-view-container {
|
||||
height: 44px;
|
||||
line-height: 44px;
|
||||
width: 100%;
|
||||
padding: 0 0 0 20px;
|
||||
background: #343744;
|
||||
.tags-view-wrapper {
|
||||
.tags-view-item {
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
height: 28px;
|
||||
line-height: 28px;
|
||||
border: 1px solid #4c4f5f;
|
||||
color: #ffffff;
|
||||
background: #343744;
|
||||
padding: 0 12px;
|
||||
font-size: 12px;
|
||||
margin-left: 5px;
|
||||
margin-top: 8px;
|
||||
border-radius: 2px;
|
||||
&:first-of-type {
|
||||
margin-left: 15px;
|
||||
}
|
||||
&:last-of-type {
|
||||
margin-right: 15px;
|
||||
}
|
||||
&.active {
|
||||
background-color: #5690E7;
|
||||
color: #fff;
|
||||
border-color: #5690E7;
|
||||
}
|
||||
&.default-tag {
|
||||
color: #ffffff;
|
||||
background-color: #343744;
|
||||
border-color: #4c4f5f;
|
||||
}
|
||||
|
||||
/* 鼠标悬停在标签上显示关闭按钮 */
|
||||
.el-icon-close {
|
||||
display: none;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
.el-icon-close {
|
||||
display: inline-block;
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
|
||||
/* 活动标签样式 */
|
||||
&.active:hover {
|
||||
background-color: #5690E7;
|
||||
|
||||
.el-icon-close {
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.contextmenu {
|
||||
margin: 0;
|
||||
background: #292C38;
|
||||
z-index: 3000;
|
||||
position: absolute;
|
||||
list-style-type: none;
|
||||
padding: 5px 0;
|
||||
border-radius: 4px;
|
||||
font-size: 12px;
|
||||
font-weight: 400;
|
||||
color: #ffffff;
|
||||
box-shadow: 2px 2px 3px 0 rgba(0, 0, 0, 0.3);
|
||||
li {
|
||||
margin: 0;
|
||||
padding: 7px 16px;
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
background: #5690E7;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<style lang="scss">
|
||||
//reset element css of el-icon-close
|
||||
.tags-view-wrapper {
|
||||
.tags-view-item {
|
||||
.el-icon-close {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
vertical-align: 2px;
|
||||
border-radius: 50%;
|
||||
text-align: center;
|
||||
transition: all 0.3s cubic-bezier(0.645, 0.045, 0.355, 1);
|
||||
transform-origin: 100% 50%;
|
||||
&:before {
|
||||
transform: scale(0.6);
|
||||
display: inline-block;
|
||||
vertical-align: -3px;
|
||||
}
|
||||
&:hover {
|
||||
background-color: #5690E7;
|
||||
color: #fff;
|
||||
width: 12px !important;
|
||||
height: 12px !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.active .el-icon-close {
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@ -1,124 +0,0 @@
|
||||
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<el-container>
|
||||
<el-header><navbar/></el-header>
|
||||
<el-container>
|
||||
<el-aside width="280px"><sidebar/></el-aside>
|
||||
<el-main><app-main/></el-main>
|
||||
</el-container>
|
||||
</el-container>
|
||||
</div>
|
||||
</template>
|
||||
<template>
|
||||
<div :class="classObj" class="app-wrapper" :style="{ '--current-color': theme }">
|
||||
<div v-if="device === 'mobile' && sidebar.opened" class="drawer-bg" @click="handleClickOutside"/>
|
||||
<sidebar v-if="!sidebar.hide" class="sidebar-container" />
|
||||
<div :class="{ hasTagsView: needTagsView, sidebarHide: sidebar.hide }" class="main-container">
|
||||
<div :class="{ 'fixed-header': fixedHeader }">
|
||||
<navbar @setLayout="setLayout" />
|
||||
<tags-view v-if="needTagsView" />
|
||||
</div>
|
||||
<app-main />
|
||||
<settings ref="settingRef" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { useWindowSize } from '@vueuse/core'
|
||||
import Sidebar from './components/Sidebar/index.vue'
|
||||
import { AppMain, Navbar, Settings, TagsView } from './components'
|
||||
import defaultSettings from '@/settings'
|
||||
|
||||
import useAppStore from '@/store/modules/app'
|
||||
import useSettingsStore from '@/store/modules/settings'
|
||||
|
||||
const settingsStore = useSettingsStore()
|
||||
const theme = computed(() => settingsStore.theme);
|
||||
const sideTheme = computed(() => settingsStore.sideTheme);
|
||||
const sidebar = computed(() => useAppStore().sidebar);
|
||||
const device = computed(() => useAppStore().device);
|
||||
const needTagsView = computed(() => settingsStore.tagsView);
|
||||
const fixedHeader = computed(() => settingsStore.fixedHeader);
|
||||
|
||||
const classObj = computed(() => ({
|
||||
hideSidebar: !sidebar.value.opened,
|
||||
openSidebar: sidebar.value.opened,
|
||||
withoutAnimation: sidebar.value.withoutAnimation,
|
||||
mobile: device.value === 'mobile'
|
||||
}))
|
||||
|
||||
const { width, height } = useWindowSize();
|
||||
const WIDTH = 992; // refer to Bootstrap's responsive design
|
||||
|
||||
watchEffect(() => {
|
||||
if (device.value === 'mobile' && sidebar.value.opened) {
|
||||
useAppStore().closeSideBar({ withoutAnimation: false })
|
||||
}
|
||||
if (width.value - 1 < WIDTH) {
|
||||
useAppStore().toggleDevice('mobile')
|
||||
useAppStore().closeSideBar({ withoutAnimation: true })
|
||||
} else {
|
||||
useAppStore().toggleDevice('desktop')
|
||||
}
|
||||
})
|
||||
|
||||
function handleClickOutside() {
|
||||
useAppStore().closeSideBar({ withoutAnimation: false })
|
||||
}
|
||||
|
||||
const settingRef = ref(null);
|
||||
function setLayout() {
|
||||
settingRef.value.openSetting();
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "@/assets/styles/mixin.scss";
|
||||
@import "@/assets/styles/variables.module.scss";
|
||||
|
||||
.app-wrapper {
|
||||
@include clearfix;
|
||||
position: relative;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
|
||||
&.mobile.openSidebar {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.drawer-bg {
|
||||
background: #000;
|
||||
opacity: 0.3;
|
||||
width: 100%;
|
||||
top: 0;
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
z-index: 999;
|
||||
}
|
||||
|
||||
.fixed-header {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
right: 0;
|
||||
z-index: 9;
|
||||
width: calc(100% - #{$base-sidebar-width});
|
||||
transition: width 0.28s;
|
||||
}
|
||||
|
||||
.hideSidebar .fixed-header {
|
||||
width: calc(100% - 54px);
|
||||
}
|
||||
|
||||
.sidebarHide .fixed-header {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.mobile .fixed-header {
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
@ -2,8 +2,11 @@
|
||||
<el-container class="layout-container">
|
||||
<el-header class="layout-header"><navbar/></el-header>
|
||||
<el-container>
|
||||
<el-aside class="layout-sidebar" width="248px"><sidebar/></el-aside>
|
||||
<el-main class="layout-main"><app-main/></el-main>
|
||||
<el-aside class="layout-sidebar" width="248px" v-if="!isHideSidebar"><sidebar/></el-aside>
|
||||
<el-main :class="['layout-main', {'full-width': isHideSidebar}]">
|
||||
<tags-view v-if="!isPlatform && needTagsView" />
|
||||
<app-main/>
|
||||
</el-main>
|
||||
</el-container>
|
||||
</el-container>
|
||||
|
||||
@ -14,10 +17,11 @@ import { useWindowSize } from '@vueuse/core'
|
||||
import Sidebar from './components/Sidebar/index.vue'
|
||||
import { AppMain, Navbar, Settings, TagsView } from './components'
|
||||
import defaultSettings from '@/settings'
|
||||
|
||||
import { useRoute } from 'vue-router'
|
||||
import useAppStore from '@/store/modules/app'
|
||||
import useSettingsStore from '@/store/modules/settings'
|
||||
|
||||
const route = useRoute()
|
||||
const settingsStore = useSettingsStore()
|
||||
const theme = computed(() => settingsStore.theme);
|
||||
const sideTheme = computed(() => settingsStore.sideTheme);
|
||||
@ -26,6 +30,17 @@ const device = computed(() => useAppStore().device);
|
||||
const needTagsView = computed(() => settingsStore.tagsView);
|
||||
const fixedHeader = computed(() => settingsStore.fixedHeader);
|
||||
|
||||
// 判断是否是平台概览页面
|
||||
const isPlatform = computed(() => {
|
||||
return route.path.startsWith('/platform');
|
||||
});
|
||||
|
||||
// 判断是否隐藏侧边栏
|
||||
const isHideSidebar = computed(() => {
|
||||
// 只有在平台概览页面时隐藏侧边栏,或者在app store中设置了隐藏
|
||||
return sidebar.value.hide || isPlatform.value;
|
||||
});
|
||||
|
||||
const classObj = computed(() => ({
|
||||
hideSidebar: !sidebar.value.opened,
|
||||
openSidebar: sidebar.value.opened,
|
||||
@ -93,21 +108,40 @@ function setLayout() {
|
||||
}
|
||||
|
||||
.layout-sidebar {
|
||||
width:248px!important;
|
||||
display: flex!important;
|
||||
flex-direction: column!important;
|
||||
align-items: center!important;
|
||||
|
||||
transition: width 0.28s;
|
||||
background-color: $base-menu-background;
|
||||
height: 100%;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
padding:0px!important;
|
||||
padding:30px 0!important;
|
||||
}
|
||||
|
||||
.layout-main {
|
||||
background-color: #343744;
|
||||
padding: 0;
|
||||
overflow: auto;
|
||||
transition: all 0.3s;
|
||||
|
||||
|
||||
&.full-width {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
// .tags-view-container {
|
||||
// background: #343744;
|
||||
|
||||
// padding: 0 16px;
|
||||
// }
|
||||
}
|
||||
|
||||
.drawer-bg {
|
||||
background: #000;
|
||||
|
||||
opacity: 0.3;
|
||||
width: 100%;
|
||||
top: 0;
|
||||
@ -117,7 +151,9 @@ function setLayout() {
|
||||
}
|
||||
|
||||
.hideSidebar {
|
||||
|
||||
.layout-sidebar {
|
||||
|
||||
width: 54px !important;
|
||||
}
|
||||
}
|
||||
|
||||
@ -8,6 +8,7 @@ import { isRelogin } from '@/utils/request'
|
||||
import useUserStore from '@/store/modules/user'
|
||||
import useSettingsStore from '@/store/modules/settings'
|
||||
import usePermissionStore from '@/store/modules/permission'
|
||||
import useAppStore from '@/store/modules/app'
|
||||
|
||||
NProgress.configure({ showSpinner: false });
|
||||
|
||||
@ -22,6 +23,16 @@ router.beforeEach((to, from, next) => {
|
||||
next({ path: '/' })
|
||||
NProgress.done()
|
||||
} else {
|
||||
// 根据路径设置当前Tab
|
||||
const path = to.path;
|
||||
if (path.startsWith('/platform')) {
|
||||
usePermissionStore().setCurrentTab('platform');
|
||||
} else if (path.startsWith('/car')) {
|
||||
usePermissionStore().setCurrentTab('car');
|
||||
} else if (path.startsWith('/system')) {
|
||||
usePermissionStore().setCurrentTab('system');
|
||||
}
|
||||
|
||||
if (useUserStore().roles.length === 0) {
|
||||
isRelogin.show = true
|
||||
// 判断当前用户是否已拉取完user_info信息
|
||||
@ -34,7 +45,13 @@ router.beforeEach((to, from, next) => {
|
||||
router.addRoute(route) // 动态添加可访问路由表
|
||||
}
|
||||
})
|
||||
// 修改:如果是首次访问根路径,重定向到平台概览页面
|
||||
if (to.path === '/' || to.path === '/index') {
|
||||
useAppStore().toggleSideBarHide(true);
|
||||
next({ path: '/platform', replace: true })
|
||||
} else {
|
||||
next({ ...to, replace: true }) // hack方法 确保addRoutes已完成
|
||||
}
|
||||
})
|
||||
}).catch(err => {
|
||||
useUserStore().logOut().then(() => {
|
||||
@ -42,10 +59,16 @@ router.beforeEach((to, from, next) => {
|
||||
next({ path: '/' })
|
||||
})
|
||||
})
|
||||
} else {
|
||||
// 如果是首次访问根路径,重定向到平台概览页面
|
||||
if (to.path === '/' || to.path === '/index') {
|
||||
useAppStore().toggleSideBarHide(true);
|
||||
next({ path: '/platform' })
|
||||
} else {
|
||||
next()
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// 没有token
|
||||
if (whiteList.indexOf(to.path) !== -1) {
|
||||
|
||||
@ -57,6 +57,97 @@ export const constantRoutes = [
|
||||
component: () => import('@/views/error/401'),
|
||||
hidden: true
|
||||
},
|
||||
// 平台概览路由
|
||||
{
|
||||
path: '/platform',
|
||||
component: Layout,
|
||||
name: 'Platform',
|
||||
meta: { title: '平台概览', icon: 'dashboard' },
|
||||
children: [
|
||||
{
|
||||
path: '',
|
||||
component: () => import('@/views/platform/index'),
|
||||
name: 'PlatformIndex',
|
||||
meta: { title: '平台概览', icon: 'dashboard', affix: true }
|
||||
}
|
||||
]
|
||||
},
|
||||
// 车辆管理路由
|
||||
{
|
||||
path: '/car',
|
||||
component: Layout,
|
||||
name: 'Car',
|
||||
redirect: '/car/index',
|
||||
meta: { title: '车辆管理', icon: 'car' },
|
||||
children: [
|
||||
{
|
||||
path: 'index',
|
||||
component: () => import('@/views/index'),
|
||||
name: 'CarIndex',
|
||||
meta: { title: '车辆管理', icon: 'car', affix: true }
|
||||
},
|
||||
]
|
||||
},
|
||||
// 车辆监控路由(一级路由)
|
||||
{
|
||||
path: '/car/monitor',
|
||||
component: Layout,
|
||||
name: 'CarMonitor',
|
||||
meta: { title: '车辆监控', icon: 'monitor', affix: false },
|
||||
children: [
|
||||
{
|
||||
path: '',
|
||||
component: () => import('@/views/car/monitor/index'),
|
||||
name: 'CarMonitorIndex',
|
||||
meta: { title: '车辆监控', icon: 'monitor', affix: false }
|
||||
}
|
||||
]
|
||||
},
|
||||
// 系统管理路由
|
||||
{
|
||||
path: '/system',
|
||||
component: Layout,
|
||||
name: 'System',
|
||||
redirect: '/system/index',
|
||||
meta: { title: '系统管理', icon: 'system' },
|
||||
|
||||
children: [
|
||||
{
|
||||
path: 'index',
|
||||
component: () => import('@/views/index'),
|
||||
name: 'SystemIndex',
|
||||
meta: { title: '系统管理', icon: 'system', affix: true }
|
||||
},
|
||||
|
||||
{
|
||||
path: 'user',
|
||||
component: () => import('@/views/system/user/index'),
|
||||
name: 'User',
|
||||
meta: { title: '用户管理', icon: 'user', affix: false }
|
||||
},
|
||||
{
|
||||
path: 'operlog',
|
||||
component: () => import('@/views/system/operlog/index'),
|
||||
name: 'systemOperlog',
|
||||
meta: { title: '操作日志', icon: 'form', affix: false }
|
||||
}
|
||||
]
|
||||
},
|
||||
// 角色管理路由(一级路由)
|
||||
{
|
||||
path: '/system/role',
|
||||
component: Layout,
|
||||
name: 'systemRole',
|
||||
meta: { title: '角色管理', icon: 'peoples', affix: false },
|
||||
children: [
|
||||
{
|
||||
path: '',
|
||||
component: () => import('@/views/system/role/index'),
|
||||
name: 'SystemRoleIndex',
|
||||
meta: { title: '角色管理', icon: 'peoples', affix: false }
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: '',
|
||||
component: Layout,
|
||||
|
||||
@ -16,7 +16,9 @@ const usePermissionStore = defineStore(
|
||||
addRoutes: [],
|
||||
defaultRoutes: [],
|
||||
topbarRouters: [],
|
||||
sidebarRouters: []
|
||||
sidebarRouters: [],
|
||||
currentTab: 'car', // 默认选中tab
|
||||
allMenus: [] // 保存完整菜单
|
||||
}),
|
||||
actions: {
|
||||
setRoutes(routes) {
|
||||
@ -32,6 +34,48 @@ const usePermissionStore = defineStore(
|
||||
setSidebarRouters(routes) {
|
||||
this.sidebarRouters = routes
|
||||
},
|
||||
setCurrentTab(tab) {
|
||||
this.currentTab = tab;
|
||||
this.updateSidebarByTab(tab);
|
||||
},
|
||||
setAllMenus(menus) {
|
||||
this.allMenus = menus;
|
||||
},
|
||||
updateSidebarByTab(tab) {
|
||||
if (!this.allMenus || this.allMenus.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 菜单映射配置 - 这里定义每个Tab下要显示的菜单
|
||||
const tabMenuMap = {
|
||||
'car': ['车辆监控', '车辆管理'],
|
||||
'system': ['角色管理', '人员管理', '操作日志']
|
||||
};
|
||||
|
||||
// 如果是平台概览,不显示菜单
|
||||
if (tab === 'platform') {
|
||||
this.setSidebarRouters([]);
|
||||
return;
|
||||
}
|
||||
|
||||
// 获取当前Tab对应的菜单名称列表
|
||||
const allowedMenuNames = tabMenuMap[tab] || [];
|
||||
|
||||
// 深拷贝菜单,避免修改原始数据
|
||||
const allMenusCopy = JSON.parse(JSON.stringify(this.allMenus));
|
||||
|
||||
// 过滤菜单
|
||||
const filteredMenus = allMenusCopy.filter(route => {
|
||||
// 只保留这些名称的菜单
|
||||
if (route.meta && route.meta.title) {
|
||||
return allowedMenuNames.includes(route.meta.title);
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
// 设置过滤后的菜单
|
||||
this.setSidebarRouters(filteredMenus);
|
||||
},
|
||||
generateRoutes(roles) {
|
||||
return new Promise(resolve => {
|
||||
// 向后端请求路由数据
|
||||
@ -45,7 +89,13 @@ const usePermissionStore = defineStore(
|
||||
const asyncRoutes = filterDynamicRoutes(dynamicRoutes)
|
||||
asyncRoutes.forEach(route => { router.addRoute(route) })
|
||||
this.setRoutes(rewriteRoutes)
|
||||
this.setSidebarRouters(constantRoutes.concat(sidebarRoutes))
|
||||
|
||||
// 保存所有菜单数据
|
||||
this.setAllMenus(sidebarRoutes)
|
||||
|
||||
// 初始根据当前Tab设置侧边栏
|
||||
this.updateSidebarByTab(this.currentTab)
|
||||
|
||||
this.setDefaultRoutes(sidebarRoutes)
|
||||
this.setTopbarRoutes(defaultRoutes)
|
||||
resolve(rewriteRoutes)
|
||||
|
||||
@ -4,9 +4,33 @@ const useTagsViewStore = defineStore(
|
||||
state: () => ({
|
||||
visitedViews: [],
|
||||
cachedViews: [],
|
||||
iframeViews: []
|
||||
iframeViews: [],
|
||||
// 主菜单默认页面配置
|
||||
defaultPages: {
|
||||
'/car': '/car/monitor', // 车辆管理 -> 车辆监控
|
||||
'/system': '/system/role', // 系统管理 -> 角色管理
|
||||
}
|
||||
}),
|
||||
actions: {
|
||||
// 检查路径所属的主菜单
|
||||
getMainPath(path) {
|
||||
const mainPaths = Object.keys(this.defaultPages);
|
||||
for (const mainPath of mainPaths) {
|
||||
if (path.startsWith(mainPath + '/')) {
|
||||
return mainPath;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
},
|
||||
// 获取主菜单的默认页面路径
|
||||
getDefaultPage(path) {
|
||||
const mainPath = this.getMainPath(path);
|
||||
return mainPath ? this.defaultPages[mainPath] : null;
|
||||
},
|
||||
// 检查是否为默认页面
|
||||
isDefaultPage(path) {
|
||||
return Object.values(this.defaultPages).includes(path);
|
||||
},
|
||||
addView(view) {
|
||||
this.addVisitedView(view)
|
||||
this.addCachedView(view)
|
||||
@ -45,6 +69,12 @@ const useTagsViewStore = defineStore(
|
||||
},
|
||||
delVisitedView(view) {
|
||||
return new Promise(resolve => {
|
||||
// 如果是默认页面,不允许删除
|
||||
if (this.isDefaultPage(view.path)) {
|
||||
resolve([...this.visitedViews]);
|
||||
return;
|
||||
}
|
||||
|
||||
for (const [i, v] of this.visitedViews.entries()) {
|
||||
if (v.path === view.path) {
|
||||
this.visitedViews.splice(i, 1)
|
||||
@ -110,8 +140,11 @@ const useTagsViewStore = defineStore(
|
||||
},
|
||||
delAllVisitedViews(view) {
|
||||
return new Promise(resolve => {
|
||||
const affixTags = this.visitedViews.filter(tag => tag.meta.affix)
|
||||
this.visitedViews = affixTags
|
||||
// 保留默认页面和固定标签
|
||||
const keepTags = this.visitedViews.filter(tag =>
|
||||
tag.meta.affix || this.isDefaultPage(tag.path)
|
||||
);
|
||||
this.visitedViews = keepTags
|
||||
this.iframeViews = []
|
||||
resolve([...this.visitedViews])
|
||||
})
|
||||
|
||||
456
src/views/car/monitor/index.vue
Normal file
456
src/views/car/monitor/index.vue
Normal file
@ -0,0 +1,456 @@
|
||||
<template>
|
||||
<div class="app-container">
|
||||
<!-- 车辆状态卡片 -->
|
||||
<div class="status-cards">
|
||||
<el-card class="status-card online" shadow="hover">
|
||||
<div class="card-header">
|
||||
<div class="card-title">在线</div>
|
||||
<div class="card-value">{{ statistics.online }}</div>
|
||||
</div>
|
||||
<div class="card-indicator">
|
||||
<span class="percentage">+30%</span>
|
||||
<i class="el-icon-arrow-up"></i>
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<el-card class="status-card task" shadow="hover">
|
||||
<div class="card-header">
|
||||
<div class="card-title">任务中</div>
|
||||
<div class="card-value">{{ statistics.inTask }}</div>
|
||||
</div>
|
||||
<div class="card-indicator">
|
||||
<span class="percentage">+20%</span>
|
||||
<i class="el-icon-arrow-up"></i>
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<el-card class="status-card error" shadow="hover">
|
||||
<div class="card-header">
|
||||
<div class="card-title">故障</div>
|
||||
<div class="card-value">{{ statistics.error }}</div>
|
||||
</div>
|
||||
<div class="card-indicator">
|
||||
<span class="percentage">-10%</span>
|
||||
<i class="el-icon-arrow-down"></i>
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<el-card class="status-card offline" shadow="hover">
|
||||
<div class="card-header">
|
||||
<div class="card-title">离线</div>
|
||||
<div class="card-value">{{ statistics.offline }}</div>
|
||||
</div>
|
||||
<div class="card-indicator">
|
||||
<span class="percentage">-5%</span>
|
||||
<i class="el-icon-arrow-down"></i>
|
||||
</div>
|
||||
</el-card>
|
||||
</div>
|
||||
|
||||
<!-- 搜索栏 -->
|
||||
<div class="search-bar">
|
||||
<div class="search-input">
|
||||
<el-input
|
||||
v-model="searchQuery"
|
||||
placeholder="请输入车辆号或者司机"
|
||||
prefix-icon="Search"
|
||||
@keyup.enter="handleSearch"
|
||||
>
|
||||
</el-input>
|
||||
</div>
|
||||
|
||||
<el-select v-model="vehicleState" placeholder="车辆状态" class="search-select">
|
||||
<el-option label="全部" value=""></el-option>
|
||||
<el-option label="在线" value="online"></el-option>
|
||||
<el-option label="离线" value="offline"></el-option>
|
||||
<el-option label="故障" value="error"></el-option>
|
||||
</el-select>
|
||||
|
||||
<el-select v-model="operationState" placeholder="运行状态" class="search-select">
|
||||
<el-option label="全部" value=""></el-option>
|
||||
<el-option label="任务中" value="running"></el-option>
|
||||
<el-option label="空闲中" value="idle"></el-option>
|
||||
<el-option label="充电中" value="charging"></el-option>
|
||||
<el-option label="维保中" value="maintenance"></el-option>
|
||||
</el-select>
|
||||
|
||||
<div class="search-buttons">
|
||||
<el-button type="primary" @click="handleSearch">搜索</el-button>
|
||||
<el-button @click="resetSearch">重置</el-button>
|
||||
</div>
|
||||
|
||||
<div class="view-buttons">
|
||||
<el-button :icon="List"></el-button>
|
||||
<el-button :icon="Grid"></el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 车辆表格 -->
|
||||
<el-table
|
||||
:data="vehicleList"
|
||||
border
|
||||
stripe
|
||||
style="width: 100%"
|
||||
@selection-change="handleSelectionChange"
|
||||
:header-cell-style="{background:'#292c38',color:'#fff'}"
|
||||
>
|
||||
<el-table-column type="selection" width="55" />
|
||||
<el-table-column prop="id" label="序号" width="80" />
|
||||
<el-table-column prop="name" label="车辆" width="100">
|
||||
<template #default="scope">
|
||||
<span class="vehicle-link">{{ scope.row.name }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="vehicleNo" label="车辆号" width="100" />
|
||||
<el-table-column prop="driverName" label="驾驶员" width="100" />
|
||||
<el-table-column prop="vehicleType" label="特性值" width="100" />
|
||||
<el-table-column prop="company" label="所属管理公司" width="150" />
|
||||
<el-table-column prop="seats" label="座位数" width="80" />
|
||||
<el-table-column prop="taskStatus" label="任务状态" width="100">
|
||||
<template #default="scope">
|
||||
<el-tag :type="getTaskStatusType(scope.row.taskStatus)">{{ scope.row.taskStatus }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="runStatus" label="在线状态" width="100">
|
||||
<template #default="scope">
|
||||
<el-tag :type="getRunStatusType(scope.row.runStatus)">{{ scope.row.runStatus }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="parkStatus" label="停车状态" width="100" />
|
||||
<el-table-column prop="voltage" label="电压" width="100" />
|
||||
<el-table-column prop="current" label="电流" width="100" />
|
||||
<el-table-column prop="battery" label="电量" width="100" />
|
||||
<el-table-column prop="updateTime" label="数据更新时间" width="180" />
|
||||
<el-table-column prop="temperature" label="T3机器" width="100" />
|
||||
<el-table-column prop="status" label="状态" width="80" />
|
||||
<el-table-column prop="deviceId" label="设备标识码" width="150" />
|
||||
</el-table>
|
||||
|
||||
<!-- 分页 -->
|
||||
<div class="pagination-container">
|
||||
<el-pagination
|
||||
v-model:currentPage="currentPage"
|
||||
v-model:page-size="pageSize"
|
||||
:page-sizes="[10, 20, 30, 50]"
|
||||
layout="total, prev, pager, next, jumper"
|
||||
:total="total"
|
||||
@size-change="handleSizeChange"
|
||||
@current-change="handleCurrentChange"
|
||||
background
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup name="CarMonitor">
|
||||
import { ref, onMounted, reactive } from 'vue';
|
||||
import { Search, List, Grid } from '@element-plus/icons-vue';
|
||||
|
||||
// 统计数据
|
||||
const statistics = reactive({
|
||||
online: 85,
|
||||
inTask: 57,
|
||||
error: 4,
|
||||
offline: 12
|
||||
});
|
||||
|
||||
// 搜索条件
|
||||
const searchQuery = ref('');
|
||||
const vehicleState = ref('');
|
||||
const operationState = ref('');
|
||||
|
||||
// 分页参数
|
||||
const currentPage = ref(1);
|
||||
const pageSize = ref(10);
|
||||
const total = ref(100);
|
||||
|
||||
// 表格数据
|
||||
const vehicleList = ref([
|
||||
{
|
||||
id: 1,
|
||||
name: '蓝奇',
|
||||
vehicleNo: 'QN001',
|
||||
driverName: '驾驶员1',
|
||||
vehicleType: '电动',
|
||||
company: '青岛管理公司',
|
||||
seats: 3,
|
||||
taskStatus: '任务中',
|
||||
runStatus: '在线',
|
||||
parkStatus: '停车充电',
|
||||
voltage: '360V',
|
||||
current: '200A',
|
||||
battery: '90%',
|
||||
updateTime: '2023-06-17 15:48:30',
|
||||
temperature: '正常',
|
||||
status: '正常',
|
||||
deviceId: '123456789001'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: '蓝奇',
|
||||
vehicleNo: 'QN002',
|
||||
driverName: '驾驶员2',
|
||||
vehicleType: '电动',
|
||||
company: '青岛管理公司',
|
||||
seats: 3,
|
||||
taskStatus: '任务中',
|
||||
runStatus: '在线',
|
||||
parkStatus: '停车充电',
|
||||
voltage: '360V',
|
||||
current: '200A',
|
||||
battery: '85%',
|
||||
updateTime: '2023-06-17 15:45:30',
|
||||
temperature: '正常',
|
||||
status: '正常',
|
||||
deviceId: '123456789002'
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
name: '蓝奇',
|
||||
vehicleNo: 'QN003',
|
||||
driverName: '驾驶员3',
|
||||
vehicleType: '电动',
|
||||
company: '青岛管理公司',
|
||||
seats: 3,
|
||||
taskStatus: '任务中',
|
||||
runStatus: '在线',
|
||||
parkStatus: '停车充电',
|
||||
voltage: '360V',
|
||||
current: '200A',
|
||||
battery: '75%',
|
||||
updateTime: '2023-06-17 15:40:30',
|
||||
temperature: '正常',
|
||||
status: '正常',
|
||||
deviceId: '123456789003'
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
name: '蓝奇',
|
||||
vehicleNo: 'QN004',
|
||||
driverName: '驾驶员4',
|
||||
vehicleType: '电动',
|
||||
company: '青岛管理公司',
|
||||
seats: 3,
|
||||
taskStatus: '闲置中',
|
||||
runStatus: '离线',
|
||||
parkStatus: '停车充电',
|
||||
voltage: '360V',
|
||||
current: '200A',
|
||||
battery: '95%',
|
||||
updateTime: '2023-06-17 15:30:30',
|
||||
temperature: '正常',
|
||||
status: '正常',
|
||||
deviceId: '123456789004'
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
name: '蓝奇',
|
||||
vehicleNo: 'QN005',
|
||||
driverName: '驾驶员5',
|
||||
vehicleType: '电动',
|
||||
company: '青岛管理公司',
|
||||
seats: 3,
|
||||
taskStatus: '闲置中',
|
||||
runStatus: '离线',
|
||||
parkStatus: '停车充电',
|
||||
voltage: '360V',
|
||||
current: '200A',
|
||||
battery: '100%',
|
||||
updateTime: '2023-06-17 15:20:30',
|
||||
temperature: '正常',
|
||||
status: '正常',
|
||||
deviceId: '123456789005'
|
||||
}
|
||||
]);
|
||||
|
||||
// 选中的行
|
||||
const selectedRows = ref([]);
|
||||
|
||||
// 方法
|
||||
function handleSearch() {
|
||||
// 实际应用中这里会调用API获取数据
|
||||
console.log('Search with:', {
|
||||
query: searchQuery.value,
|
||||
vehicleState: vehicleState.value,
|
||||
operationState: operationState.value
|
||||
});
|
||||
// 重置到第一页
|
||||
currentPage.value = 1;
|
||||
}
|
||||
|
||||
function resetSearch() {
|
||||
searchQuery.value = '';
|
||||
vehicleState.value = '';
|
||||
operationState.value = '';
|
||||
handleSearch();
|
||||
}
|
||||
|
||||
function handleSelectionChange(selection) {
|
||||
selectedRows.value = selection;
|
||||
}
|
||||
|
||||
function handleSizeChange(val) {
|
||||
pageSize.value = val;
|
||||
// 实际应用中这里会重新获取数据
|
||||
}
|
||||
|
||||
function handleCurrentChange(val) {
|
||||
currentPage.value = val;
|
||||
// 实际应用中这里会重新获取数据
|
||||
}
|
||||
|
||||
function getTaskStatusType(status) {
|
||||
if (status === '任务中') return 'success';
|
||||
if (status === '闲置中') return 'warning';
|
||||
if (status === '等待中') return 'info';
|
||||
return 'info';
|
||||
}
|
||||
|
||||
function getRunStatusType(status) {
|
||||
if (status === '在线') return 'success';
|
||||
if (status === '离线') return 'warning';
|
||||
if (status === '故障') return 'danger';
|
||||
return 'info';
|
||||
}
|
||||
|
||||
// 加载初始数据
|
||||
onMounted(() => {
|
||||
// 实际应用中这里会调用API获取数据
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.app-container {
|
||||
padding: 20px;
|
||||
background-color: #1e2028;
|
||||
color: #ffffff;
|
||||
min-height: 100%;
|
||||
}
|
||||
|
||||
.status-cards {
|
||||
display: flex;
|
||||
margin-bottom: 20px;
|
||||
gap: 20px;
|
||||
|
||||
.status-card {
|
||||
flex: 1;
|
||||
padding: 15px;
|
||||
border-radius: 5px;
|
||||
background-color: #292c38;
|
||||
border: none;
|
||||
|
||||
&.online {
|
||||
.card-header {
|
||||
border-bottom: 2px solid #67c23a;
|
||||
}
|
||||
}
|
||||
|
||||
&.task {
|
||||
.card-header {
|
||||
border-bottom: 2px solid #409eff;
|
||||
}
|
||||
}
|
||||
|
||||
&.error {
|
||||
.card-header {
|
||||
border-bottom: 2px solid #f56c6c;
|
||||
}
|
||||
}
|
||||
|
||||
&.offline {
|
||||
.card-header {
|
||||
border-bottom: 2px solid #e6a23c;
|
||||
}
|
||||
}
|
||||
|
||||
.card-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding-bottom: 10px;
|
||||
|
||||
.card-title {
|
||||
font-size: 14px;
|
||||
color: #96a0b5;
|
||||
}
|
||||
|
||||
.card-value {
|
||||
font-size: 24px;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
|
||||
.card-indicator {
|
||||
font-size: 12px;
|
||||
margin-top: 10px;
|
||||
color: #67c23a;
|
||||
|
||||
.percentage {
|
||||
margin-right: 5px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.search-bar {
|
||||
display: flex;
|
||||
margin-bottom: 20px;
|
||||
gap: 15px;
|
||||
align-items: center;
|
||||
|
||||
.search-input {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.search-select {
|
||||
width: 140px;
|
||||
}
|
||||
|
||||
.search-buttons {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.view-buttons {
|
||||
margin-left: auto;
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.el-table) {
|
||||
background-color: #292c38;
|
||||
color: #fff;
|
||||
|
||||
.el-table__header-wrapper th {
|
||||
background-color: #292c38;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.el-table__row {
|
||||
background-color: #292c38;
|
||||
&:hover > td {
|
||||
background-color: #363945 !important;
|
||||
}
|
||||
}
|
||||
|
||||
.el-table__row--striped td {
|
||||
background-color: #1e2028;
|
||||
}
|
||||
}
|
||||
|
||||
// .pagination-container {
|
||||
// display: flex;
|
||||
// justify-content: flex-end;
|
||||
// margin-top: 20px;
|
||||
// }
|
||||
|
||||
.vehicle-link {
|
||||
color: #409eff;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
7
src/views/car/park/index.vue
Normal file
7
src/views/car/park/index.vue
Normal file
@ -0,0 +1,7 @@
|
||||
<template>
|
||||
<p>车辆入库</p>
|
||||
</template>
|
||||
<script setup>
|
||||
|
||||
</script>
|
||||
|
||||
7
src/views/car/type/index.vue
Normal file
7
src/views/car/type/index.vue
Normal file
@ -0,0 +1,7 @@
|
||||
<template>
|
||||
<p>车辆类型</p>
|
||||
</template>
|
||||
<script setup>
|
||||
|
||||
</script>
|
||||
|
||||
131
src/views/monitor/cache/index.vue
vendored
131
src/views/monitor/cache/index.vue
vendored
@ -1,131 +0,0 @@
|
||||
<template>
|
||||
<div class="app-container">
|
||||
<el-row>
|
||||
<el-col :span="24" class="card-box">
|
||||
<el-card>
|
||||
<template #header><span>基本信息</span></template>
|
||||
<div class="el-table el-table--enable-row-hover el-table--medium">
|
||||
<table cellspacing="0" style="width: 100%">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td class="el-table__cell is-leaf"><div class="cell">Redis版本</div></td>
|
||||
<td class="el-table__cell is-leaf"><div class="cell" v-if="cache.info">{{ cache.info.redis_version }}</div></td>
|
||||
<td class="el-table__cell is-leaf"><div class="cell">运行模式</div></td>
|
||||
<td class="el-table__cell is-leaf"><div class="cell" v-if="cache.info">{{ cache.info.redis_mode == "standalone" ? "单机" : "集群" }}</div></td>
|
||||
<td class="el-table__cell is-leaf"><div class="cell">端口</div></td>
|
||||
<td class="el-table__cell is-leaf"><div class="cell" v-if="cache.info">{{ cache.info.tcp_port }}</div></td>
|
||||
<td class="el-table__cell is-leaf"><div class="cell">客户端数</div></td>
|
||||
<td class="el-table__cell is-leaf"><div class="cell" v-if="cache.info">{{ cache.info.connected_clients }}</div></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="el-table__cell is-leaf"><div class="cell">运行时间(天)</div></td>
|
||||
<td class="el-table__cell is-leaf"><div class="cell" v-if="cache.info">{{ cache.info.uptime_in_days }}</div></td>
|
||||
<td class="el-table__cell is-leaf"><div class="cell">使用内存</div></td>
|
||||
<td class="el-table__cell is-leaf"><div class="cell" v-if="cache.info">{{ cache.info.used_memory_human }}</div></td>
|
||||
<td class="el-table__cell is-leaf"><div class="cell">使用CPU</div></td>
|
||||
<td class="el-table__cell is-leaf"><div class="cell" v-if="cache.info">{{ parseFloat(cache.info.used_cpu_user_children).toFixed(2) }}</div></td>
|
||||
<td class="el-table__cell is-leaf"><div class="cell">内存配置</div></td>
|
||||
<td class="el-table__cell is-leaf"><div class="cell" v-if="cache.info">{{ cache.info.maxmemory_human }}</div></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="el-table__cell is-leaf"><div class="cell">AOF是否开启</div></td>
|
||||
<td class="el-table__cell is-leaf"><div class="cell" v-if="cache.info">{{ cache.info.aof_enabled == "0" ? "否" : "是" }}</div></td>
|
||||
<td class="el-table__cell is-leaf"><div class="cell">RDB是否成功</div></td>
|
||||
<td class="el-table__cell is-leaf"><div class="cell" v-if="cache.info">{{ cache.info.rdb_last_bgsave_status }}</div></td>
|
||||
<td class="el-table__cell is-leaf"><div class="cell">Key数量</div></td>
|
||||
<td class="el-table__cell is-leaf"><div class="cell" v-if="cache.dbSize">{{ cache.dbSize }} </div></td>
|
||||
<td class="el-table__cell is-leaf"><div class="cell">网络入口/出口</div></td>
|
||||
<td class="el-table__cell is-leaf"><div class="cell" v-if="cache.info">{{ cache.info.instantaneous_input_kbps }}kps/{{cache.info.instantaneous_output_kbps}}kps</div></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
|
||||
<el-col :span="12" class="card-box">
|
||||
<el-card>
|
||||
<template #header><span>命令统计</span></template>
|
||||
<div class="el-table el-table--enable-row-hover el-table--medium">
|
||||
<div ref="commandstats" style="height: 420px" />
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
|
||||
<el-col :span="12" class="card-box">
|
||||
<el-card>
|
||||
<template #header>
|
||||
<span>内存信息</span>
|
||||
</template>
|
||||
<div class="el-table el-table--enable-row-hover el-table--medium">
|
||||
<div ref="usedmemory" style="height: 420px" />
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup name="Cache">
|
||||
import { getCache } from '@/api/monitor/cache';
|
||||
import * as echarts from 'echarts';
|
||||
|
||||
const cache = ref([]);
|
||||
const commandstats = ref(null);
|
||||
const usedmemory = ref(null);
|
||||
const { proxy } = getCurrentInstance();
|
||||
|
||||
function getList() {
|
||||
proxy.$modal.loading("正在加载缓存监控数据,请稍候!");
|
||||
getCache().then(response => {
|
||||
proxy.$modal.closeLoading();
|
||||
cache.value = response.data;
|
||||
|
||||
const commandstatsIntance = echarts.init(commandstats.value, "macarons");
|
||||
commandstatsIntance.setOption({
|
||||
tooltip: {
|
||||
trigger: "item",
|
||||
formatter: "{a} <br/>{b} : {c} ({d}%)"
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: "命令",
|
||||
type: "pie",
|
||||
roseType: "radius",
|
||||
radius: [15, 95],
|
||||
center: ["50%", "38%"],
|
||||
data: response.data.commandStats,
|
||||
animationEasing: "cubicInOut",
|
||||
animationDuration: 1000
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
const usedmemoryInstance = echarts.init(usedmemory.value, "macarons");
|
||||
usedmemoryInstance.setOption({
|
||||
tooltip: {
|
||||
formatter: "{b} <br/>{a} : " + cache.value.info.used_memory_human
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: "峰值",
|
||||
type: "gauge",
|
||||
min: 0,
|
||||
max: 1000,
|
||||
detail: {
|
||||
formatter: cache.value.info.used_memory_human
|
||||
},
|
||||
data: [
|
||||
{
|
||||
value: parseFloat(cache.value.info.used_memory_human),
|
||||
name: "内存消耗"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
getList();
|
||||
</script>
|
||||
246
src/views/monitor/cache/list.vue
vendored
246
src/views/monitor/cache/list.vue
vendored
@ -1,246 +0,0 @@
|
||||
<template>
|
||||
<div class="app-container">
|
||||
<el-row :gutter="10">
|
||||
<el-col :span="8">
|
||||
<el-card style="height: calc(100vh - 125px)">
|
||||
<template #header>
|
||||
<span>缓存列表</span>
|
||||
<el-button
|
||||
style="float: right; padding: 3px 0"
|
||||
link
|
||||
type="primary"
|
||||
icon="Refresh"
|
||||
@click="refreshCacheNames()"
|
||||
></el-button>
|
||||
</template>
|
||||
<el-table
|
||||
v-loading="loading"
|
||||
:data="cacheNames"
|
||||
:height="tableHeight"
|
||||
highlight-current-row
|
||||
@row-click="getCacheKeys"
|
||||
style="width: 100%"
|
||||
>
|
||||
<el-table-column
|
||||
label="序号"
|
||||
width="60"
|
||||
type="index"
|
||||
></el-table-column>
|
||||
|
||||
<el-table-column
|
||||
label="缓存名称"
|
||||
align="center"
|
||||
prop="cacheName"
|
||||
:show-overflow-tooltip="true"
|
||||
:formatter="nameFormatter"
|
||||
></el-table-column>
|
||||
|
||||
<el-table-column
|
||||
label="备注"
|
||||
align="center"
|
||||
prop="remark"
|
||||
:show-overflow-tooltip="true"
|
||||
/>
|
||||
<el-table-column
|
||||
label="操作"
|
||||
width="60"
|
||||
align="center"
|
||||
class-name="small-padding fixed-width"
|
||||
>
|
||||
<template #default="scope">
|
||||
<el-button
|
||||
link
|
||||
type="primary"
|
||||
icon="Delete"
|
||||
@click="handleClearCacheName(scope.row)"
|
||||
></el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-card>
|
||||
</el-col>
|
||||
|
||||
<el-col :span="8">
|
||||
<el-card style="height: calc(100vh - 125px)">
|
||||
<template #header>
|
||||
<span>键名列表</span>
|
||||
<el-button
|
||||
style="float: right; padding: 3px 0"
|
||||
link
|
||||
type="primary"
|
||||
icon="Refresh"
|
||||
@click="refreshCacheKeys()"
|
||||
></el-button>
|
||||
</template>
|
||||
<el-table
|
||||
v-loading="subLoading"
|
||||
:data="cacheKeys"
|
||||
:height="tableHeight"
|
||||
highlight-current-row
|
||||
@row-click="handleCacheValue"
|
||||
style="width: 100%"
|
||||
>
|
||||
<el-table-column
|
||||
label="序号"
|
||||
width="60"
|
||||
type="index"
|
||||
></el-table-column>
|
||||
<el-table-column
|
||||
label="缓存键名"
|
||||
align="center"
|
||||
:show-overflow-tooltip="true"
|
||||
:formatter="keyFormatter"
|
||||
>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
label="操作"
|
||||
width="60"
|
||||
align="center"
|
||||
class-name="small-padding fixed-width"
|
||||
>
|
||||
<template #default="scope">
|
||||
<el-button
|
||||
link
|
||||
type="primary"
|
||||
icon="Delete"
|
||||
@click="handleClearCacheKey(scope.row)"
|
||||
></el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-card>
|
||||
</el-col>
|
||||
|
||||
<el-col :span="8">
|
||||
<el-card :bordered="false" style="height: calc(100vh - 125px)">
|
||||
<template #header>
|
||||
<span>缓存内容</span>
|
||||
<el-button
|
||||
style="float: right; padding: 3px 0"
|
||||
link
|
||||
type="primary"
|
||||
icon="Refresh"
|
||||
@click="handleClearCacheAll()"
|
||||
>清理全部</el-button
|
||||
>
|
||||
</template>
|
||||
<el-form :model="cacheForm">
|
||||
<el-row :gutter="32">
|
||||
<el-col :offset="1" :span="22">
|
||||
<el-form-item label="缓存名称:" prop="cacheName">
|
||||
<el-input v-model="cacheForm.cacheName" :readOnly="true" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :offset="1" :span="22">
|
||||
<el-form-item label="缓存键名:" prop="cacheKey">
|
||||
<el-input v-model="cacheForm.cacheKey" :readOnly="true" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :offset="1" :span="22">
|
||||
<el-form-item label="缓存内容:" prop="cacheValue">
|
||||
<el-input
|
||||
v-model="cacheForm.cacheValue"
|
||||
type="textarea"
|
||||
:rows="8"
|
||||
:readOnly="true"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup name="CacheList">
|
||||
import { listCacheName, listCacheKey, getCacheValue, clearCacheName, clearCacheKey, clearCacheAll } from "@/api/monitor/cache";
|
||||
|
||||
const { proxy } = getCurrentInstance();
|
||||
|
||||
const cacheNames = ref([]);
|
||||
const cacheKeys = ref([]);
|
||||
const cacheForm = ref({});
|
||||
const loading = ref(true);
|
||||
const subLoading = ref(false);
|
||||
const nowCacheName = ref("");
|
||||
const tableHeight = ref(window.innerHeight - 200);
|
||||
|
||||
/** 查询缓存名称列表 */
|
||||
function getCacheNames() {
|
||||
loading.value = true;
|
||||
listCacheName().then(response => {
|
||||
cacheNames.value = response.data;
|
||||
loading.value = false;
|
||||
});
|
||||
}
|
||||
|
||||
/** 刷新缓存名称列表 */
|
||||
function refreshCacheNames() {
|
||||
getCacheNames();
|
||||
proxy.$modal.msgSuccess("刷新缓存列表成功");
|
||||
}
|
||||
|
||||
/** 清理指定名称缓存 */
|
||||
function handleClearCacheName(row) {
|
||||
clearCacheName(row.cacheName).then(response => {
|
||||
proxy.$modal.msgSuccess("清理缓存名称[" + nowCacheName.value + "]成功");
|
||||
getCacheKeys();
|
||||
});
|
||||
}
|
||||
|
||||
/** 查询缓存键名列表 */
|
||||
function getCacheKeys(row) {
|
||||
const cacheName = row !== undefined ? row.cacheName : nowCacheName.value;
|
||||
if (cacheName === "") {
|
||||
return;
|
||||
}
|
||||
subLoading.value = true;
|
||||
listCacheKey(cacheName).then(response => {
|
||||
cacheKeys.value = response.data;
|
||||
subLoading.value = false;
|
||||
nowCacheName.value = cacheName;
|
||||
});
|
||||
}
|
||||
|
||||
/** 刷新缓存键名列表 */
|
||||
function refreshCacheKeys() {
|
||||
getCacheKeys();
|
||||
proxy.$modal.msgSuccess("刷新键名列表成功");
|
||||
}
|
||||
|
||||
/** 清理指定键名缓存 */
|
||||
function handleClearCacheKey(cacheKey) {
|
||||
clearCacheKey(cacheKey).then(response => {
|
||||
proxy.$modal.msgSuccess("清理缓存键名[" + cacheKey + "]成功");
|
||||
getCacheKeys();
|
||||
});
|
||||
}
|
||||
|
||||
/** 列表前缀去除 */
|
||||
function nameFormatter(row) {
|
||||
return row.cacheName.replace(":", "");
|
||||
}
|
||||
|
||||
/** 键名前缀去除 */
|
||||
function keyFormatter(cacheKey) {
|
||||
return cacheKey.replace(nowCacheName.value, "");
|
||||
}
|
||||
|
||||
/** 查询缓存内容详细 */
|
||||
function handleCacheValue(cacheKey) {
|
||||
getCacheValue(nowCacheName.value, cacheKey).then(response => {
|
||||
cacheForm.value = response.data;
|
||||
});
|
||||
}
|
||||
|
||||
/** 清理全部缓存 */
|
||||
function handleClearCacheAll() {
|
||||
clearCacheAll().then(response => {
|
||||
proxy.$modal.msgSuccess("清理全部缓存成功");
|
||||
});
|
||||
}
|
||||
|
||||
getCacheNames();
|
||||
</script>
|
||||
@ -1,13 +0,0 @@
|
||||
<template>
|
||||
<div>
|
||||
<i-frame v-model:src="url"></i-frame>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import iFrame from '@/components/iFrame'
|
||||
|
||||
import { ref } from 'vue';
|
||||
|
||||
const url = ref(import.meta.env.VITE_APP_BASE_API + '/druid/login.html');
|
||||
</script>
|
||||
456
src/views/monitor/index.vue
Normal file
456
src/views/monitor/index.vue
Normal file
@ -0,0 +1,456 @@
|
||||
<template>
|
||||
<div class="app-container">
|
||||
<!-- 车辆状态卡片 -->
|
||||
<div class="status-cards">
|
||||
<el-card class="status-card online" shadow="hover">
|
||||
<div class="card-header">
|
||||
<div class="card-title">在线</div>
|
||||
<div class="card-value">{{ statistics.online }}</div>
|
||||
</div>
|
||||
<div class="card-indicator">
|
||||
<span class="percentage">+30%</span>
|
||||
<i class="el-icon-arrow-up"></i>
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<el-card class="status-card task" shadow="hover">
|
||||
<div class="card-header">
|
||||
<div class="card-title">任务中</div>
|
||||
<div class="card-value">{{ statistics.inTask }}</div>
|
||||
</div>
|
||||
<div class="card-indicator">
|
||||
<span class="percentage">+20%</span>
|
||||
<i class="el-icon-arrow-up"></i>
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<el-card class="status-card error" shadow="hover">
|
||||
<div class="card-header">
|
||||
<div class="card-title">故障</div>
|
||||
<div class="card-value">{{ statistics.error }}</div>
|
||||
</div>
|
||||
<div class="card-indicator">
|
||||
<span class="percentage">-10%</span>
|
||||
<i class="el-icon-arrow-down"></i>
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<el-card class="status-card offline" shadow="hover">
|
||||
<div class="card-header">
|
||||
<div class="card-title">离线</div>
|
||||
<div class="card-value">{{ statistics.offline }}</div>
|
||||
</div>
|
||||
<div class="card-indicator">
|
||||
<span class="percentage">-5%</span>
|
||||
<i class="el-icon-arrow-down"></i>
|
||||
</div>
|
||||
</el-card>
|
||||
</div>
|
||||
|
||||
<!-- 搜索栏 -->
|
||||
<div class="search-bar">
|
||||
<div class="search-input">
|
||||
<el-input
|
||||
v-model="searchQuery"
|
||||
placeholder="请输入车辆号或者司机"
|
||||
prefix-icon="Search"
|
||||
@keyup.enter="handleSearch"
|
||||
>
|
||||
</el-input>
|
||||
</div>
|
||||
|
||||
<el-select v-model="vehicleState" placeholder="车辆状态" class="search-select">
|
||||
<el-option label="全部" value=""></el-option>
|
||||
<el-option label="在线" value="online"></el-option>
|
||||
<el-option label="离线" value="offline"></el-option>
|
||||
<el-option label="故障" value="error"></el-option>
|
||||
</el-select>
|
||||
|
||||
<el-select v-model="operationState" placeholder="运行状态" class="search-select">
|
||||
<el-option label="全部" value=""></el-option>
|
||||
<el-option label="任务中" value="running"></el-option>
|
||||
<el-option label="空闲中" value="idle"></el-option>
|
||||
<el-option label="充电中" value="charging"></el-option>
|
||||
<el-option label="维保中" value="maintenance"></el-option>
|
||||
</el-select>
|
||||
|
||||
<div class="search-buttons">
|
||||
<el-button type="primary" @click="handleSearch">搜索</el-button>
|
||||
<el-button @click="resetSearch">重置</el-button>
|
||||
</div>
|
||||
|
||||
<div class="view-buttons">
|
||||
<el-button :icon="List"></el-button>
|
||||
<el-button :icon="Grid"></el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 车辆表格 -->
|
||||
<el-table
|
||||
:data="vehicleList"
|
||||
border
|
||||
stripe
|
||||
style="width: 100%"
|
||||
@selection-change="handleSelectionChange"
|
||||
:header-cell-style="{background:'#292c38',color:'#fff'}"
|
||||
>
|
||||
<el-table-column type="selection" width="55" />
|
||||
<el-table-column prop="id" label="序号" width="80" />
|
||||
<el-table-column prop="name" label="车辆" width="100">
|
||||
<template #default="scope">
|
||||
<span class="vehicle-link">{{ scope.row.name }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="vehicleNo" label="车辆号" width="100" />
|
||||
<el-table-column prop="driverName" label="驾驶员" width="100" />
|
||||
<el-table-column prop="vehicleType" label="特性值" width="100" />
|
||||
<el-table-column prop="company" label="所属管理公司" width="150" />
|
||||
<el-table-column prop="seats" label="座位数" width="80" />
|
||||
<el-table-column prop="taskStatus" label="任务状态" width="100">
|
||||
<template #default="scope">
|
||||
<el-tag :type="getTaskStatusType(scope.row.taskStatus)">{{ scope.row.taskStatus }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="runStatus" label="在线状态" width="100">
|
||||
<template #default="scope">
|
||||
<el-tag :type="getRunStatusType(scope.row.runStatus)">{{ scope.row.runStatus }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="parkStatus" label="停车状态" width="100" />
|
||||
<el-table-column prop="voltage" label="电压" width="100" />
|
||||
<el-table-column prop="current" label="电流" width="100" />
|
||||
<el-table-column prop="battery" label="电量" width="100" />
|
||||
<el-table-column prop="updateTime" label="数据更新时间" width="180" />
|
||||
<el-table-column prop="temperature" label="T3机器" width="100" />
|
||||
<el-table-column prop="status" label="状态" width="80" />
|
||||
<el-table-column prop="deviceId" label="设备标识码" width="150" />
|
||||
</el-table>
|
||||
|
||||
<!-- 分页 -->
|
||||
<div class="pagination-container">
|
||||
<el-pagination
|
||||
v-model:currentPage="currentPage"
|
||||
v-model:page-size="pageSize"
|
||||
:page-sizes="[10, 20, 30, 50]"
|
||||
layout="total, prev, pager, next, jumper"
|
||||
:total="total"
|
||||
@size-change="handleSizeChange"
|
||||
@current-change="handleCurrentChange"
|
||||
background
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup name="CarMonitor">
|
||||
import { ref, onMounted, reactive } from 'vue';
|
||||
import { Search, List, Grid } from '@element-plus/icons-vue';
|
||||
|
||||
// 统计数据
|
||||
const statistics = reactive({
|
||||
online: 85,
|
||||
inTask: 57,
|
||||
error: 4,
|
||||
offline: 12
|
||||
});
|
||||
|
||||
// 搜索条件
|
||||
const searchQuery = ref('');
|
||||
const vehicleState = ref('');
|
||||
const operationState = ref('');
|
||||
|
||||
// 分页参数
|
||||
const currentPage = ref(1);
|
||||
const pageSize = ref(10);
|
||||
const total = ref(100);
|
||||
|
||||
// 表格数据
|
||||
const vehicleList = ref([
|
||||
{
|
||||
id: 1,
|
||||
name: '蓝奇',
|
||||
vehicleNo: 'QN001',
|
||||
driverName: '驾驶员1',
|
||||
vehicleType: '电动',
|
||||
company: '青岛管理公司',
|
||||
seats: 3,
|
||||
taskStatus: '任务中',
|
||||
runStatus: '在线',
|
||||
parkStatus: '停车充电',
|
||||
voltage: '360V',
|
||||
current: '200A',
|
||||
battery: '90%',
|
||||
updateTime: '2023-06-17 15:48:30',
|
||||
temperature: '正常',
|
||||
status: '正常',
|
||||
deviceId: '123456789001'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: '蓝奇',
|
||||
vehicleNo: 'QN002',
|
||||
driverName: '驾驶员2',
|
||||
vehicleType: '电动',
|
||||
company: '青岛管理公司',
|
||||
seats: 3,
|
||||
taskStatus: '任务中',
|
||||
runStatus: '在线',
|
||||
parkStatus: '停车充电',
|
||||
voltage: '360V',
|
||||
current: '200A',
|
||||
battery: '85%',
|
||||
updateTime: '2023-06-17 15:45:30',
|
||||
temperature: '正常',
|
||||
status: '正常',
|
||||
deviceId: '123456789002'
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
name: '蓝奇',
|
||||
vehicleNo: 'QN003',
|
||||
driverName: '驾驶员3',
|
||||
vehicleType: '电动',
|
||||
company: '青岛管理公司',
|
||||
seats: 3,
|
||||
taskStatus: '任务中',
|
||||
runStatus: '在线',
|
||||
parkStatus: '停车充电',
|
||||
voltage: '360V',
|
||||
current: '200A',
|
||||
battery: '75%',
|
||||
updateTime: '2023-06-17 15:40:30',
|
||||
temperature: '正常',
|
||||
status: '正常',
|
||||
deviceId: '123456789003'
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
name: '蓝奇',
|
||||
vehicleNo: 'QN004',
|
||||
driverName: '驾驶员4',
|
||||
vehicleType: '电动',
|
||||
company: '青岛管理公司',
|
||||
seats: 3,
|
||||
taskStatus: '闲置中',
|
||||
runStatus: '离线',
|
||||
parkStatus: '停车充电',
|
||||
voltage: '360V',
|
||||
current: '200A',
|
||||
battery: '95%',
|
||||
updateTime: '2023-06-17 15:30:30',
|
||||
temperature: '正常',
|
||||
status: '正常',
|
||||
deviceId: '123456789004'
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
name: '蓝奇',
|
||||
vehicleNo: 'QN005',
|
||||
driverName: '驾驶员5',
|
||||
vehicleType: '电动',
|
||||
company: '青岛管理公司',
|
||||
seats: 3,
|
||||
taskStatus: '闲置中',
|
||||
runStatus: '离线',
|
||||
parkStatus: '停车充电',
|
||||
voltage: '360V',
|
||||
current: '200A',
|
||||
battery: '100%',
|
||||
updateTime: '2023-06-17 15:20:30',
|
||||
temperature: '正常',
|
||||
status: '正常',
|
||||
deviceId: '123456789005'
|
||||
}
|
||||
]);
|
||||
|
||||
// 选中的行
|
||||
const selectedRows = ref([]);
|
||||
|
||||
// 方法
|
||||
function handleSearch() {
|
||||
// 实际应用中这里会调用API获取数据
|
||||
console.log('Search with:', {
|
||||
query: searchQuery.value,
|
||||
vehicleState: vehicleState.value,
|
||||
operationState: operationState.value
|
||||
});
|
||||
// 重置到第一页
|
||||
currentPage.value = 1;
|
||||
}
|
||||
|
||||
function resetSearch() {
|
||||
searchQuery.value = '';
|
||||
vehicleState.value = '';
|
||||
operationState.value = '';
|
||||
handleSearch();
|
||||
}
|
||||
|
||||
function handleSelectionChange(selection) {
|
||||
selectedRows.value = selection;
|
||||
}
|
||||
|
||||
function handleSizeChange(val) {
|
||||
pageSize.value = val;
|
||||
// 实际应用中这里会重新获取数据
|
||||
}
|
||||
|
||||
function handleCurrentChange(val) {
|
||||
currentPage.value = val;
|
||||
// 实际应用中这里会重新获取数据
|
||||
}
|
||||
|
||||
function getTaskStatusType(status) {
|
||||
if (status === '任务中') return 'success';
|
||||
if (status === '闲置中') return 'warning';
|
||||
if (status === '等待中') return 'info';
|
||||
return 'info';
|
||||
}
|
||||
|
||||
function getRunStatusType(status) {
|
||||
if (status === '在线') return 'success';
|
||||
if (status === '离线') return 'warning';
|
||||
if (status === '故障') return 'danger';
|
||||
return 'info';
|
||||
}
|
||||
|
||||
// 加载初始数据
|
||||
onMounted(() => {
|
||||
// 实际应用中这里会调用API获取数据
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.app-container {
|
||||
padding: 20px;
|
||||
background-color: #1e2028;
|
||||
color: #ffffff;
|
||||
min-height: 100%;
|
||||
}
|
||||
|
||||
.status-cards {
|
||||
display: flex;
|
||||
margin-bottom: 20px;
|
||||
gap: 20px;
|
||||
|
||||
.status-card {
|
||||
flex: 1;
|
||||
padding: 15px;
|
||||
border-radius: 5px;
|
||||
background-color: #292c38;
|
||||
border: none;
|
||||
|
||||
&.online {
|
||||
.card-header {
|
||||
border-bottom: 2px solid #67c23a;
|
||||
}
|
||||
}
|
||||
|
||||
&.task {
|
||||
.card-header {
|
||||
border-bottom: 2px solid #409eff;
|
||||
}
|
||||
}
|
||||
|
||||
&.error {
|
||||
.card-header {
|
||||
border-bottom: 2px solid #f56c6c;
|
||||
}
|
||||
}
|
||||
|
||||
&.offline {
|
||||
.card-header {
|
||||
border-bottom: 2px solid #e6a23c;
|
||||
}
|
||||
}
|
||||
|
||||
.card-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding-bottom: 10px;
|
||||
|
||||
.card-title {
|
||||
font-size: 14px;
|
||||
color: #96a0b5;
|
||||
}
|
||||
|
||||
.card-value {
|
||||
font-size: 24px;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
|
||||
.card-indicator {
|
||||
font-size: 12px;
|
||||
margin-top: 10px;
|
||||
color: #67c23a;
|
||||
|
||||
.percentage {
|
||||
margin-right: 5px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.search-bar {
|
||||
display: flex;
|
||||
margin-bottom: 20px;
|
||||
gap: 15px;
|
||||
align-items: center;
|
||||
|
||||
.search-input {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.search-select {
|
||||
width: 140px;
|
||||
}
|
||||
|
||||
.search-buttons {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.view-buttons {
|
||||
margin-left: auto;
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.el-table) {
|
||||
background-color: #292c38;
|
||||
color: #fff;
|
||||
|
||||
.el-table__header-wrapper th {
|
||||
background-color: #292c38;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.el-table__row {
|
||||
background-color: #292c38;
|
||||
&:hover > td {
|
||||
background-color: #363945 !important;
|
||||
}
|
||||
}
|
||||
|
||||
.el-table__row--striped td {
|
||||
background-color: #1e2028;
|
||||
}
|
||||
}
|
||||
|
||||
// .pagination-container {
|
||||
// display: flex;
|
||||
// justify-content: flex-end;
|
||||
// margin-top: 20px;
|
||||
// }
|
||||
|
||||
.vehicle-link {
|
||||
color: #409eff;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@ -1,483 +0,0 @@
|
||||
<template>
|
||||
<div class="app-container">
|
||||
<el-form :model="queryParams" ref="queryRef" :inline="true" v-show="showSearch">
|
||||
<el-form-item label="任务名称" prop="jobName">
|
||||
<el-input
|
||||
v-model="queryParams.jobName"
|
||||
placeholder="请输入任务名称"
|
||||
clearable
|
||||
style="width: 200px"
|
||||
@keyup.enter="handleQuery"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="任务组名" prop="jobGroup">
|
||||
<el-select v-model="queryParams.jobGroup" placeholder="请选择任务组名" clearable style="width: 200px">
|
||||
<el-option
|
||||
v-for="dict in sys_job_group"
|
||||
:key="dict.value"
|
||||
:label="dict.label"
|
||||
:value="dict.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="任务状态" prop="status">
|
||||
<el-select v-model="queryParams.status" placeholder="请选择任务状态" clearable style="width: 200px">
|
||||
<el-option
|
||||
v-for="dict in sys_job_status"
|
||||
:key="dict.value"
|
||||
:label="dict.label"
|
||||
:value="dict.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
|
||||
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<el-row :gutter="10" class="mb8">
|
||||
<el-col :span="1.5">
|
||||
<el-button
|
||||
type="primary"
|
||||
plain
|
||||
icon="Plus"
|
||||
@click="handleAdd"
|
||||
v-hasPermi="['monitor:job:add']"
|
||||
>新增</el-button>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<el-button
|
||||
type="success"
|
||||
plain
|
||||
icon="Edit"
|
||||
:disabled="single"
|
||||
@click="handleUpdate"
|
||||
v-hasPermi="['monitor:job:edit']"
|
||||
>修改</el-button>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<el-button
|
||||
type="danger"
|
||||
plain
|
||||
icon="Delete"
|
||||
:disabled="multiple"
|
||||
@click="handleDelete"
|
||||
v-hasPermi="['monitor:job:remove']"
|
||||
>删除</el-button>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<el-button
|
||||
type="warning"
|
||||
plain
|
||||
icon="Download"
|
||||
@click="handleExport"
|
||||
v-hasPermi="['monitor:job:export']"
|
||||
>导出</el-button>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<el-button
|
||||
type="info"
|
||||
plain
|
||||
icon="Operation"
|
||||
@click="handleJobLog"
|
||||
v-hasPermi="['monitor:job:query']"
|
||||
>日志</el-button>
|
||||
</el-col>
|
||||
<right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
|
||||
</el-row>
|
||||
|
||||
<el-table v-loading="loading" :data="jobList" @selection-change="handleSelectionChange">
|
||||
<el-table-column type="selection" width="55" align="center" />
|
||||
<el-table-column label="任务编号" width="100" align="center" prop="jobId" />
|
||||
<el-table-column label="任务名称" align="center" prop="jobName" :show-overflow-tooltip="true" />
|
||||
<el-table-column label="任务组名" align="center" prop="jobGroup">
|
||||
<template #default="scope">
|
||||
<dict-tag :options="sys_job_group" :value="scope.row.jobGroup" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="调用目标字符串" align="center" prop="invokeTarget" :show-overflow-tooltip="true" />
|
||||
<el-table-column label="cron执行表达式" align="center" prop="cronExpression" :show-overflow-tooltip="true" />
|
||||
<el-table-column label="状态" align="center">
|
||||
<template #default="scope">
|
||||
<el-switch
|
||||
v-model="scope.row.status"
|
||||
active-value="0"
|
||||
inactive-value="1"
|
||||
@change="handleStatusChange(scope.row)"
|
||||
></el-switch>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" align="center" width="200" class-name="small-padding fixed-width">
|
||||
<template #default="scope">
|
||||
<el-tooltip content="修改" placement="top">
|
||||
<el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['monitor:job:edit']"></el-button>
|
||||
</el-tooltip>
|
||||
<el-tooltip content="删除" placement="top">
|
||||
<el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['monitor:job:remove']"></el-button>
|
||||
</el-tooltip>
|
||||
<el-tooltip content="执行一次" placement="top">
|
||||
<el-button link type="primary" icon="CaretRight" @click="handleRun(scope.row)" v-hasPermi="['monitor:job:changeStatus']"></el-button>
|
||||
</el-tooltip>
|
||||
<el-tooltip content="任务详细" placement="top">
|
||||
<el-button link type="primary" icon="View" @click="handleView(scope.row)" v-hasPermi="['monitor:job:query']"></el-button>
|
||||
</el-tooltip>
|
||||
<el-tooltip content="调度日志" placement="top">
|
||||
<el-button link type="primary" icon="Operation" @click="handleJobLog(scope.row)" v-hasPermi="['monitor:job:query']"></el-button>
|
||||
</el-tooltip>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<pagination
|
||||
v-show="total > 0"
|
||||
:total="total"
|
||||
v-model:page="queryParams.pageNum"
|
||||
v-model:limit="queryParams.pageSize"
|
||||
@pagination="getList"
|
||||
/>
|
||||
|
||||
<!-- 添加或修改定时任务对话框 -->
|
||||
<el-dialog :title="title" v-model="open" width="800px" append-to-body>
|
||||
<el-form ref="jobRef" :model="form" :rules="rules" label-width="120px">
|
||||
<el-row>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="任务名称" prop="jobName">
|
||||
<el-input v-model="form.jobName" placeholder="请输入任务名称" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="任务分组" prop="jobGroup">
|
||||
<el-select v-model="form.jobGroup" placeholder="请选择">
|
||||
<el-option
|
||||
v-for="dict in sys_job_group"
|
||||
:key="dict.value"
|
||||
:label="dict.label"
|
||||
:value="dict.value"
|
||||
></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="24">
|
||||
<el-form-item prop="invokeTarget">
|
||||
<template #label>
|
||||
<span>
|
||||
调用方法
|
||||
<el-tooltip placement="top">
|
||||
<template #content>
|
||||
<div>
|
||||
Bean调用示例:ryTask.ryParams('ry')
|
||||
<br />Class类调用示例:com.ruoyi.quartz.task.RyTask.ryParams('ry')
|
||||
<br />参数说明:支持字符串,布尔类型,长整型,浮点型,整型
|
||||
</div>
|
||||
</template>
|
||||
<el-icon><question-filled /></el-icon>
|
||||
</el-tooltip>
|
||||
</span>
|
||||
</template>
|
||||
<el-input v-model="form.invokeTarget" placeholder="请输入调用目标字符串" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="24">
|
||||
<el-form-item label="cron表达式" prop="cronExpression">
|
||||
<el-input v-model="form.cronExpression" placeholder="请输入cron执行表达式">
|
||||
<template #append>
|
||||
<el-button type="primary" @click="handleShowCron">
|
||||
生成表达式
|
||||
<i class="el-icon-time el-icon--right"></i>
|
||||
</el-button>
|
||||
</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="24">
|
||||
<el-form-item label="执行策略" prop="misfirePolicy">
|
||||
<el-radio-group v-model="form.misfirePolicy">
|
||||
<el-radio-button label="1">立即执行</el-radio-button>
|
||||
<el-radio-button label="2">执行一次</el-radio-button>
|
||||
<el-radio-button label="3">放弃执行</el-radio-button>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="是否并发" prop="concurrent">
|
||||
<el-radio-group v-model="form.concurrent">
|
||||
<el-radio-button label="0">允许</el-radio-button>
|
||||
<el-radio-button label="1">禁止</el-radio-button>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="状态">
|
||||
<el-radio-group v-model="form.status">
|
||||
<el-radio
|
||||
v-for="dict in sys_job_status"
|
||||
:key="dict.value"
|
||||
:label="dict.value"
|
||||
>{{ dict.label }}</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button type="primary" @click="submitForm">确 定</el-button>
|
||||
<el-button @click="cancel">取 消</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<el-dialog title="Cron表达式生成器" v-model="openCron" append-to-body destroy-on-close>
|
||||
<crontab ref="crontabRef" @hide="openCron=false" @fill="crontabFill" :expression="expression"></crontab>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 任务日志详细 -->
|
||||
<el-dialog title="任务详细" v-model="openView" width="700px" append-to-body>
|
||||
<el-form :model="form" label-width="120px">
|
||||
<el-row>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="任务编号:">{{ form.jobId }}</el-form-item>
|
||||
<el-form-item label="任务名称:">{{ form.jobName }}</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="任务分组:">{{ jobGroupFormat(form) }}</el-form-item>
|
||||
<el-form-item label="创建时间:">{{ form.createTime }}</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="cron表达式:">{{ form.cronExpression }}</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="下次执行时间:">{{ parseTime(form.nextValidTime) }}</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="24">
|
||||
<el-form-item label="调用目标方法:">{{ form.invokeTarget }}</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="任务状态:">
|
||||
<div v-if="form.status == 0">正常</div>
|
||||
<div v-else-if="form.status == 1">失败</div>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="是否并发:">
|
||||
<div v-if="form.concurrent == 0">允许</div>
|
||||
<div v-else-if="form.concurrent == 1">禁止</div>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="执行策略:">
|
||||
<div v-if="form.misfirePolicy == 0">默认策略</div>
|
||||
<div v-else-if="form.misfirePolicy == 1">立即执行</div>
|
||||
<div v-else-if="form.misfirePolicy == 2">执行一次</div>
|
||||
<div v-else-if="form.misfirePolicy == 3">放弃执行</div>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button @click="openView = false">关 闭</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup name="Job">
|
||||
import { listJob, getJob, delJob, addJob, updateJob, runJob, changeJobStatus } from "@/api/monitor/job";
|
||||
import Crontab from '@/components/Crontab'
|
||||
const router = useRouter();
|
||||
const { proxy } = getCurrentInstance();
|
||||
const { sys_job_group, sys_job_status } = proxy.useDict("sys_job_group", "sys_job_status");
|
||||
|
||||
const jobList = ref([]);
|
||||
const open = ref(false);
|
||||
const loading = ref(true);
|
||||
const showSearch = ref(true);
|
||||
const ids = ref([]);
|
||||
const single = ref(true);
|
||||
const multiple = ref(true);
|
||||
const total = ref(0);
|
||||
const title = ref("");
|
||||
const openView = ref(false);
|
||||
const openCron = ref(false);
|
||||
const expression = ref("");
|
||||
|
||||
const data = reactive({
|
||||
form: {},
|
||||
queryParams: {
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
jobName: undefined,
|
||||
jobGroup: undefined,
|
||||
status: undefined
|
||||
},
|
||||
rules: {
|
||||
jobName: [{ required: true, message: "任务名称不能为空", trigger: "blur" }],
|
||||
invokeTarget: [{ required: true, message: "调用目标字符串不能为空", trigger: "blur" }],
|
||||
cronExpression: [{ required: true, message: "cron执行表达式不能为空", trigger: "change" }]
|
||||
}
|
||||
});
|
||||
|
||||
const { queryParams, form, rules } = toRefs(data);
|
||||
|
||||
/** 查询定时任务列表 */
|
||||
function getList() {
|
||||
loading.value = true;
|
||||
listJob(queryParams.value).then(response => {
|
||||
jobList.value = response.rows;
|
||||
total.value = response.total;
|
||||
loading.value = false;
|
||||
});
|
||||
}
|
||||
/** 任务组名字典翻译 */
|
||||
function jobGroupFormat(row, column) {
|
||||
return proxy.selectDictLabel(sys_job_group.value, row.jobGroup);
|
||||
}
|
||||
/** 取消按钮 */
|
||||
function cancel() {
|
||||
open.value = false;
|
||||
reset();
|
||||
}
|
||||
/** 表单重置 */
|
||||
function reset() {
|
||||
form.value = {
|
||||
jobId: undefined,
|
||||
jobName: undefined,
|
||||
jobGroup: undefined,
|
||||
invokeTarget: undefined,
|
||||
cronExpression: undefined,
|
||||
misfirePolicy: 1,
|
||||
concurrent: 1,
|
||||
status: "0"
|
||||
};
|
||||
proxy.resetForm("jobRef");
|
||||
}
|
||||
/** 搜索按钮操作 */
|
||||
function handleQuery() {
|
||||
queryParams.value.pageNum = 1;
|
||||
getList();
|
||||
}
|
||||
/** 重置按钮操作 */
|
||||
function resetQuery() {
|
||||
proxy.resetForm("queryRef");
|
||||
handleQuery();
|
||||
}
|
||||
// 多选框选中数据
|
||||
function handleSelectionChange(selection) {
|
||||
ids.value = selection.map(item => item.jobId);
|
||||
single.value = selection.length != 1;
|
||||
multiple.value = !selection.length;
|
||||
}
|
||||
// 更多操作触发
|
||||
function handleCommand(command, row) {
|
||||
switch (command) {
|
||||
case "handleRun":
|
||||
handleRun(row);
|
||||
break;
|
||||
case "handleView":
|
||||
handleView(row);
|
||||
break;
|
||||
case "handleJobLog":
|
||||
handleJobLog(row);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
// 任务状态修改
|
||||
function handleStatusChange(row) {
|
||||
let text = row.status === "0" ? "启用" : "停用";
|
||||
proxy.$modal.confirm('确认要"' + text + '""' + row.jobName + '"任务吗?').then(function () {
|
||||
return changeJobStatus(row.jobId, row.status);
|
||||
}).then(() => {
|
||||
proxy.$modal.msgSuccess(text + "成功");
|
||||
}).catch(function () {
|
||||
row.status = row.status === "0" ? "1" : "0";
|
||||
});
|
||||
}
|
||||
/* 立即执行一次 */
|
||||
function handleRun(row) {
|
||||
proxy.$modal.confirm('确认要立即执行一次"' + row.jobName + '"任务吗?').then(function () {
|
||||
return runJob(row.jobId, row.jobGroup);
|
||||
}).then(() => {
|
||||
proxy.$modal.msgSuccess("执行成功");})
|
||||
.catch(() => {});
|
||||
}
|
||||
/** 任务详细信息 */
|
||||
function handleView(row) {
|
||||
getJob(row.jobId).then(response => {
|
||||
form.value = response.data;
|
||||
openView.value = true;
|
||||
});
|
||||
}
|
||||
/** cron表达式按钮操作 */
|
||||
function handleShowCron() {
|
||||
expression.value = form.value.cronExpression;
|
||||
openCron.value = true;
|
||||
}
|
||||
/** 确定后回传值 */
|
||||
function crontabFill(value) {
|
||||
form.value.cronExpression = value;
|
||||
}
|
||||
/** 任务日志列表查询 */
|
||||
function handleJobLog(row) {
|
||||
const jobId = row.jobId || 0;
|
||||
router.push('/monitor/job-log/index/' + jobId)
|
||||
}
|
||||
/** 新增按钮操作 */
|
||||
function handleAdd() {
|
||||
reset();
|
||||
open.value = true;
|
||||
title.value = "添加任务";
|
||||
}
|
||||
/** 修改按钮操作 */
|
||||
function handleUpdate(row) {
|
||||
reset();
|
||||
const jobId = row.jobId || ids.value;
|
||||
getJob(jobId).then(response => {
|
||||
form.value = response.data;
|
||||
open.value = true;
|
||||
title.value = "修改任务";
|
||||
});
|
||||
}
|
||||
/** 提交按钮 */
|
||||
function submitForm() {
|
||||
proxy.$refs["jobRef"].validate(valid => {
|
||||
if (valid) {
|
||||
if (form.value.jobId != undefined) {
|
||||
updateJob(form.value).then(response => {
|
||||
proxy.$modal.msgSuccess("修改成功");
|
||||
open.value = false;
|
||||
getList();
|
||||
});
|
||||
} else {
|
||||
addJob(form.value).then(response => {
|
||||
proxy.$modal.msgSuccess("新增成功");
|
||||
open.value = false;
|
||||
getList();
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
/** 删除按钮操作 */
|
||||
function handleDelete(row) {
|
||||
const jobIds = row.jobId || ids.value;
|
||||
proxy.$modal.confirm('是否确认删除定时任务编号为"' + jobIds + '"的数据项?').then(function () {
|
||||
return delJob(jobIds);
|
||||
}).then(() => {
|
||||
getList();
|
||||
proxy.$modal.msgSuccess("删除成功");
|
||||
}).catch(() => {});
|
||||
}
|
||||
/** 导出按钮操作 */
|
||||
function handleExport() {
|
||||
proxy.download("monitor/job/export", {
|
||||
...queryParams.value,
|
||||
}, `job_${new Date().getTime()}.xlsx`);
|
||||
}
|
||||
|
||||
getList();
|
||||
</script>
|
||||
@ -1,277 +0,0 @@
|
||||
<template>
|
||||
<div class="app-container">
|
||||
<el-form :model="queryParams" ref="queryRef" :inline="true" v-show="showSearch" label-width="68px">
|
||||
<el-form-item label="任务名称" prop="jobName">
|
||||
<el-input
|
||||
v-model="queryParams.jobName"
|
||||
placeholder="请输入任务名称"
|
||||
clearable
|
||||
style="width: 240px"
|
||||
@keyup.enter="handleQuery"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="任务组名" prop="jobGroup">
|
||||
<el-select
|
||||
v-model="queryParams.jobGroup"
|
||||
placeholder="请选择任务组名"
|
||||
clearable
|
||||
style="width: 240px"
|
||||
>
|
||||
<el-option
|
||||
v-for="dict in sys_job_group"
|
||||
:key="dict.value"
|
||||
:label="dict.label"
|
||||
:value="dict.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="执行状态" prop="status">
|
||||
<el-select
|
||||
v-model="queryParams.status"
|
||||
placeholder="请选择执行状态"
|
||||
clearable
|
||||
style="width: 240px"
|
||||
>
|
||||
<el-option
|
||||
v-for="dict in sys_common_status"
|
||||
:key="dict.value"
|
||||
:label="dict.label"
|
||||
:value="dict.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="执行时间" style="width: 308px">
|
||||
<el-date-picker
|
||||
v-model="dateRange"
|
||||
value-format="YYYY-MM-DD"
|
||||
type="daterange"
|
||||
range-separator="-"
|
||||
start-placeholder="开始日期"
|
||||
end-placeholder="结束日期"
|
||||
></el-date-picker>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
|
||||
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<el-row :gutter="10" class="mb8">
|
||||
<el-col :span="1.5">
|
||||
<el-button
|
||||
type="danger"
|
||||
plain
|
||||
icon="Delete"
|
||||
:disabled="multiple"
|
||||
@click="handleDelete"
|
||||
v-hasPermi="['monitor:job:remove']"
|
||||
>删除</el-button>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<el-button
|
||||
type="danger"
|
||||
plain
|
||||
icon="Delete"
|
||||
@click="handleClean"
|
||||
v-hasPermi="['monitor:job:remove']"
|
||||
>清空</el-button>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<el-button
|
||||
type="warning"
|
||||
plain
|
||||
icon="Download"
|
||||
@click="handleExport"
|
||||
v-hasPermi="['monitor:job:export']"
|
||||
>导出</el-button>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<el-button
|
||||
type="warning"
|
||||
plain
|
||||
icon="Close"
|
||||
@click="handleClose"
|
||||
>关闭</el-button>
|
||||
</el-col>
|
||||
<right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
|
||||
</el-row>
|
||||
|
||||
<el-table v-loading="loading" :data="jobLogList" @selection-change="handleSelectionChange">
|
||||
<el-table-column type="selection" width="55" align="center" />
|
||||
<el-table-column label="日志编号" width="80" align="center" prop="jobLogId" />
|
||||
<el-table-column label="任务名称" align="center" prop="jobName" :show-overflow-tooltip="true" />
|
||||
<el-table-column label="任务组名" align="center" prop="jobGroup" :show-overflow-tooltip="true">
|
||||
<template #default="scope">
|
||||
<dict-tag :options="sys_job_group" :value="scope.row.jobGroup" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="调用目标字符串" align="center" prop="invokeTarget" :show-overflow-tooltip="true" />
|
||||
<el-table-column label="日志信息" align="center" prop="jobMessage" :show-overflow-tooltip="true" />
|
||||
<el-table-column label="执行状态" align="center" prop="status">
|
||||
<template #default="scope">
|
||||
<dict-tag :options="sys_common_status" :value="scope.row.status" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="执行时间" align="center" prop="createTime" width="180">
|
||||
<template #default="scope">
|
||||
<span>{{ parseTime(scope.row.createTime) }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
|
||||
<template #default="scope">
|
||||
<el-button link type="primary" icon="View" @click="handleView(scope.row)" v-hasPermi="['monitor:job:query']">详细</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<pagination
|
||||
v-show="total > 0"
|
||||
:total="total"
|
||||
v-model:page="queryParams.pageNum"
|
||||
v-model:limit="queryParams.pageSize"
|
||||
@pagination="getList"
|
||||
/>
|
||||
|
||||
<!-- 调度日志详细 -->
|
||||
<el-dialog title="调度日志详细" v-model="open" width="700px" append-to-body>
|
||||
<el-form :model="form" label-width="100px">
|
||||
<el-row>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="日志序号:">{{ form.jobLogId }}</el-form-item>
|
||||
<el-form-item label="任务名称:">{{ form.jobName }}</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="任务分组:">{{ form.jobGroup }}</el-form-item>
|
||||
<el-form-item label="执行时间:">{{ form.createTime }}</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="24">
|
||||
<el-form-item label="调用方法:">{{ form.invokeTarget }}</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="24">
|
||||
<el-form-item label="日志信息:">{{ form.jobMessage }}</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="24">
|
||||
<el-form-item label="执行状态:">
|
||||
<div v-if="form.status == 0">正常</div>
|
||||
<div v-else-if="form.status == 1">失败</div>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="24">
|
||||
<el-form-item label="异常信息:" v-if="form.status == 1">{{ form.exceptionInfo }}</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button @click="open = false">关 闭</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup name="JobLog">
|
||||
import { getJob } from "@/api/monitor/job";
|
||||
import { listJobLog, delJobLog, cleanJobLog } from "@/api/monitor/jobLog";
|
||||
|
||||
const { proxy } = getCurrentInstance();
|
||||
const { sys_common_status, sys_job_group } = proxy.useDict("sys_common_status", "sys_job_group");
|
||||
|
||||
const jobLogList = ref([]);
|
||||
const open = ref(false);
|
||||
const loading = ref(true);
|
||||
const showSearch = ref(true);
|
||||
const ids = ref([]);
|
||||
const multiple = ref(true);
|
||||
const total = ref(0);
|
||||
const dateRange = ref([]);
|
||||
const route = useRoute();
|
||||
|
||||
const data = reactive({
|
||||
form: {},
|
||||
queryParams: {
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
dictName: undefined,
|
||||
dictType: undefined,
|
||||
status: undefined
|
||||
}
|
||||
});
|
||||
|
||||
const { queryParams, form, rules } = toRefs(data);
|
||||
|
||||
/** 查询调度日志列表 */
|
||||
function getList() {
|
||||
loading.value = true;
|
||||
listJobLog(proxy.addDateRange(queryParams.value, dateRange.value)).then(response => {
|
||||
jobLogList.value = response.rows;
|
||||
total.value = response.total;
|
||||
loading.value = false;
|
||||
});
|
||||
}
|
||||
// 返回按钮
|
||||
function handleClose() {
|
||||
const obj = { path: "/monitor/job" };
|
||||
proxy.$tab.closeOpenPage(obj);
|
||||
}
|
||||
/** 搜索按钮操作 */
|
||||
function handleQuery() {
|
||||
queryParams.value.pageNum = 1;
|
||||
getList();
|
||||
}
|
||||
/** 重置按钮操作 */
|
||||
function resetQuery() {
|
||||
dateRange.value = [];
|
||||
proxy.resetForm("queryRef");
|
||||
handleQuery();
|
||||
}
|
||||
// 多选框选中数据
|
||||
function handleSelectionChange(selection) {
|
||||
ids.value = selection.map(item => item.jobLogId);
|
||||
multiple.value = !selection.length;
|
||||
}
|
||||
/** 详细按钮操作 */
|
||||
function handleView(row) {
|
||||
open.value = true;
|
||||
form.value = row;
|
||||
}
|
||||
/** 删除按钮操作 */
|
||||
function handleDelete(row) {
|
||||
proxy.$modal.confirm('是否确认删除调度日志编号为"' + ids.value + '"的数据项?').then(function () {
|
||||
return delJobLog(ids.value);
|
||||
}).then(() => {
|
||||
getList();
|
||||
proxy.$modal.msgSuccess("删除成功");
|
||||
}).catch(() => {});
|
||||
}
|
||||
/** 清空按钮操作 */
|
||||
function handleClean() {
|
||||
proxy.$modal.confirm("是否确认清空所有调度日志数据项?").then(function () {
|
||||
return cleanJobLog();
|
||||
}).then(() => {
|
||||
getList();
|
||||
proxy.$modal.msgSuccess("清空成功");
|
||||
}).catch(() => {});
|
||||
}
|
||||
/** 导出按钮操作 */
|
||||
function handleExport() {
|
||||
proxy.download("monitor/jobLog/export", {
|
||||
...queryParams.value,
|
||||
}, `job_log_${new Date().getTime()}.xlsx`);
|
||||
}
|
||||
|
||||
(() => {
|
||||
const jobId = route.params && route.params.jobId;
|
||||
if (jobId !== undefined && jobId != 0) {
|
||||
getJob(jobId).then(response => {
|
||||
queryParams.value.jobName = response.data.jobName;
|
||||
queryParams.value.jobGroup = response.data.jobGroup;
|
||||
getList();
|
||||
});
|
||||
} else {
|
||||
getList();
|
||||
}
|
||||
})();
|
||||
|
||||
getList();
|
||||
</script>
|
||||
@ -1,224 +0,0 @@
|
||||
<template>
|
||||
<div class="app-container">
|
||||
<el-form :model="queryParams" ref="queryRef" :inline="true" v-show="showSearch" label-width="68px">
|
||||
<el-form-item label="登录地址" prop="ipaddr">
|
||||
<el-input
|
||||
v-model="queryParams.ipaddr"
|
||||
placeholder="请输入登录地址"
|
||||
clearable
|
||||
style="width: 240px;"
|
||||
@keyup.enter="handleQuery"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="用户名称" prop="userName">
|
||||
<el-input
|
||||
v-model="queryParams.userName"
|
||||
placeholder="请输入用户名称"
|
||||
clearable
|
||||
style="width: 240px;"
|
||||
@keyup.enter="handleQuery"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="状态" prop="status">
|
||||
<el-select
|
||||
v-model="queryParams.status"
|
||||
placeholder="登录状态"
|
||||
clearable
|
||||
style="width: 240px"
|
||||
>
|
||||
<el-option
|
||||
v-for="dict in sys_common_status"
|
||||
:key="dict.value"
|
||||
:label="dict.label"
|
||||
:value="dict.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="登录时间" style="width: 308px">
|
||||
<el-date-picker
|
||||
v-model="dateRange"
|
||||
value-format="YYYY-MM-DD"
|
||||
type="daterange"
|
||||
range-separator="-"
|
||||
start-placeholder="开始日期"
|
||||
end-placeholder="结束日期"
|
||||
></el-date-picker>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
|
||||
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<el-row :gutter="10" class="mb8">
|
||||
<el-col :span="1.5">
|
||||
<el-button
|
||||
type="danger"
|
||||
plain
|
||||
icon="Delete"
|
||||
:disabled="multiple"
|
||||
@click="handleDelete"
|
||||
v-hasPermi="['monitor:logininfor:remove']"
|
||||
>删除</el-button>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<el-button
|
||||
type="danger"
|
||||
plain
|
||||
icon="Delete"
|
||||
@click="handleClean"
|
||||
v-hasPermi="['monitor:logininfor:remove']"
|
||||
>清空</el-button>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<el-button
|
||||
type="primary"
|
||||
plain
|
||||
icon="Unlock"
|
||||
:disabled="single"
|
||||
@click="handleUnlock"
|
||||
v-hasPermi="['monitor:logininfor:unlock']"
|
||||
>解锁</el-button>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<el-button
|
||||
type="warning"
|
||||
plain
|
||||
icon="Download"
|
||||
@click="handleExport"
|
||||
v-hasPermi="['monitor:logininfor:export']"
|
||||
>导出</el-button>
|
||||
</el-col>
|
||||
<right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
|
||||
</el-row>
|
||||
|
||||
<el-table ref="logininforRef" v-loading="loading" :data="logininforList" @selection-change="handleSelectionChange" :default-sort="defaultSort" @sort-change="handleSortChange">
|
||||
<el-table-column type="selection" width="55" align="center" />
|
||||
<el-table-column label="访问编号" align="center" prop="infoId" />
|
||||
<el-table-column label="用户名称" align="center" prop="userName" :show-overflow-tooltip="true" sortable="custom" :sort-orders="['descending', 'ascending']" />
|
||||
<el-table-column label="地址" align="center" prop="ipaddr" :show-overflow-tooltip="true" />
|
||||
<el-table-column label="登录地点" align="center" prop="loginLocation" :show-overflow-tooltip="true" />
|
||||
<el-table-column label="操作系统" align="center" prop="os" :show-overflow-tooltip="true" />
|
||||
<el-table-column label="浏览器" align="center" prop="browser" :show-overflow-tooltip="true" />
|
||||
<el-table-column label="登录状态" align="center" prop="status">
|
||||
<template #default="scope">
|
||||
<dict-tag :options="sys_common_status" :value="scope.row.status" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="描述" align="center" prop="msg" />
|
||||
<el-table-column label="访问时间" align="center" prop="loginTime" sortable="custom" :sort-orders="['descending', 'ascending']" width="180">
|
||||
<template #default="scope">
|
||||
<span>{{ parseTime(scope.row.loginTime) }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<pagination
|
||||
v-show="total > 0"
|
||||
:total="total"
|
||||
v-model:page="queryParams.pageNum"
|
||||
v-model:limit="queryParams.pageSize"
|
||||
@pagination="getList"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup name="Logininfor">
|
||||
import { list, delLogininfor, cleanLogininfor, unlockLogininfor } from "@/api/monitor/logininfor";
|
||||
|
||||
const { proxy } = getCurrentInstance();
|
||||
const { sys_common_status } = proxy.useDict("sys_common_status");
|
||||
|
||||
const logininforList = ref([]);
|
||||
const loading = ref(true);
|
||||
const showSearch = ref(true);
|
||||
const ids = ref([]);
|
||||
const single = ref(true);
|
||||
const multiple = ref(true);
|
||||
const selectName = ref("");
|
||||
const total = ref(0);
|
||||
const dateRange = ref([]);
|
||||
const defaultSort = ref({ prop: "loginTime", order: "descending" });
|
||||
|
||||
// 查询参数
|
||||
const queryParams = ref({
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
ipaddr: undefined,
|
||||
userName: undefined,
|
||||
status: undefined,
|
||||
orderByColumn: undefined,
|
||||
isAsc: undefined
|
||||
});
|
||||
|
||||
/** 查询登录日志列表 */
|
||||
function getList() {
|
||||
loading.value = true;
|
||||
list(proxy.addDateRange(queryParams.value, dateRange.value)).then(response => {
|
||||
logininforList.value = response.rows;
|
||||
total.value = response.total;
|
||||
loading.value = false;
|
||||
});
|
||||
}
|
||||
/** 搜索按钮操作 */
|
||||
function handleQuery() {
|
||||
queryParams.value.pageNum = 1;
|
||||
getList();
|
||||
}
|
||||
/** 重置按钮操作 */
|
||||
function resetQuery() {
|
||||
dateRange.value = [];
|
||||
proxy.resetForm("queryRef");
|
||||
queryParams.value.pageNum = 1;
|
||||
proxy.$refs["logininforRef"].sort(defaultSort.value.prop, defaultSort.value.order);
|
||||
}
|
||||
/** 多选框选中数据 */
|
||||
function handleSelectionChange(selection) {
|
||||
ids.value = selection.map(item => item.infoId);
|
||||
multiple.value = !selection.length;
|
||||
single.value = selection.length != 1;
|
||||
selectName.value = selection.map(item => item.userName);
|
||||
}
|
||||
/** 排序触发事件 */
|
||||
function handleSortChange(column, prop, order) {
|
||||
queryParams.value.orderByColumn = column.prop;
|
||||
queryParams.value.isAsc = column.order;
|
||||
getList();
|
||||
}
|
||||
/** 删除按钮操作 */
|
||||
function handleDelete(row) {
|
||||
const infoIds = row.infoId || ids.value;
|
||||
proxy.$modal.confirm('是否确认删除访问编号为"' + infoIds + '"的数据项?').then(function () {
|
||||
return delLogininfor(infoIds);
|
||||
}).then(() => {
|
||||
getList();
|
||||
proxy.$modal.msgSuccess("删除成功");
|
||||
}).catch(() => {});
|
||||
}
|
||||
/** 清空按钮操作 */
|
||||
function handleClean() {
|
||||
proxy.$modal.confirm("是否确认清空所有登录日志数据项?").then(function () {
|
||||
return cleanLogininfor();
|
||||
}).then(() => {
|
||||
getList();
|
||||
proxy.$modal.msgSuccess("清空成功");
|
||||
}).catch(() => {});
|
||||
}
|
||||
/** 解锁按钮操作 */
|
||||
function handleUnlock() {
|
||||
const username = selectName.value;
|
||||
proxy.$modal.confirm('是否确认解锁用户"' + username + '"数据项?').then(function () {
|
||||
return unlockLogininfor(username);
|
||||
}).then(() => {
|
||||
proxy.$modal.msgSuccess("用户" + username + "解锁成功");
|
||||
}).catch(() => {});
|
||||
}
|
||||
/** 导出按钮操作 */
|
||||
function handleExport() {
|
||||
proxy.download("monitor/logininfor/export", {
|
||||
...queryParams.value,
|
||||
}, `config_${new Date().getTime()}.xlsx`);
|
||||
}
|
||||
|
||||
getList();
|
||||
</script>
|
||||
@ -1,106 +0,0 @@
|
||||
<template>
|
||||
<div class="app-container">
|
||||
<el-form :model="queryParams" ref="queryRef" :inline="true">
|
||||
<el-form-item label="登录地址" prop="ipaddr">
|
||||
<el-input
|
||||
v-model="queryParams.ipaddr"
|
||||
placeholder="请输入登录地址"
|
||||
clearable
|
||||
style="width: 200px"
|
||||
@keyup.enter="handleQuery"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="用户名称" prop="userName">
|
||||
<el-input
|
||||
v-model="queryParams.userName"
|
||||
placeholder="请输入用户名称"
|
||||
clearable
|
||||
style="width: 200px"
|
||||
@keyup.enter="handleQuery"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
|
||||
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<el-table
|
||||
v-loading="loading"
|
||||
:data="onlineList.slice((pageNum - 1) * pageSize, pageNum * pageSize)"
|
||||
style="width: 100%;"
|
||||
>
|
||||
<el-table-column label="序号" width="50" type="index" align="center">
|
||||
<template #default="scope">
|
||||
<span>{{ (pageNum - 1) * pageSize + scope.$index + 1 }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="会话编号" align="center" prop="tokenId" :show-overflow-tooltip="true" />
|
||||
<el-table-column label="登录名称" align="center" prop="userName" :show-overflow-tooltip="true" />
|
||||
<el-table-column label="所属部门" align="center" prop="deptName" :show-overflow-tooltip="true" />
|
||||
<el-table-column label="主机" align="center" prop="ipaddr" :show-overflow-tooltip="true" />
|
||||
<el-table-column label="登录地点" align="center" prop="loginLocation" :show-overflow-tooltip="true" />
|
||||
<el-table-column label="操作系统" align="center" prop="os" :show-overflow-tooltip="true" />
|
||||
<el-table-column label="浏览器" align="center" prop="browser" :show-overflow-tooltip="true" />
|
||||
<el-table-column label="登录时间" align="center" prop="loginTime" width="180">
|
||||
<template #default="scope">
|
||||
<span>{{ parseTime(scope.row.loginTime) }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
|
||||
<template #default="scope">
|
||||
<el-button link type="primary" icon="Delete" @click="handleForceLogout(scope.row)" v-hasPermi="['monitor:online:forceLogout']">强退</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<pagination v-show="total > 0" :total="total" v-model:page="pageNum" v-model:limit="pageSize" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup name="Online">
|
||||
import { forceLogout, list as initData } from "@/api/monitor/online";
|
||||
|
||||
const { proxy } = getCurrentInstance();
|
||||
|
||||
const onlineList = ref([]);
|
||||
const loading = ref(true);
|
||||
const total = ref(0);
|
||||
const pageNum = ref(1);
|
||||
const pageSize = ref(10);
|
||||
|
||||
const queryParams = ref({
|
||||
ipaddr: undefined,
|
||||
userName: undefined
|
||||
});
|
||||
|
||||
/** 查询登录日志列表 */
|
||||
function getList() {
|
||||
loading.value = true;
|
||||
initData(queryParams.value).then(response => {
|
||||
onlineList.value = response.rows;
|
||||
total.value = response.total;
|
||||
loading.value = false;
|
||||
});
|
||||
}
|
||||
/** 搜索按钮操作 */
|
||||
function handleQuery() {
|
||||
pageNum.value = 1;
|
||||
getList();
|
||||
}
|
||||
/** 重置按钮操作 */
|
||||
function resetQuery() {
|
||||
proxy.resetForm("queryRef");
|
||||
handleQuery();
|
||||
}
|
||||
/** 强退按钮操作 */
|
||||
function handleForceLogout(row) {
|
||||
proxy.$modal.confirm('是否确认强退名称为"' + row.userName + '"的用户?').then(function () {
|
||||
return forceLogout(row.tokenId);
|
||||
}).then(() => {
|
||||
getList();
|
||||
proxy.$modal.msgSuccess("删除成功");
|
||||
}).catch(() => {});
|
||||
}
|
||||
|
||||
getList();
|
||||
</script>
|
||||
@ -1,187 +0,0 @@
|
||||
<template>
|
||||
<div class="app-container">
|
||||
<el-row>
|
||||
<el-col :span="12" class="card-box">
|
||||
<el-card>
|
||||
<template #header><span>CPU</span></template>
|
||||
<div class="el-table el-table--enable-row-hover el-table--medium">
|
||||
<table cellspacing="0" style="width: 100%;">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="el-table__cell is-leaf"><div class="cell">属性</div></th>
|
||||
<th class="el-table__cell is-leaf"><div class="cell">值</div></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td class="el-table__cell is-leaf"><div class="cell">核心数</div></td>
|
||||
<td class="el-table__cell is-leaf"><div class="cell" v-if="server.cpu">{{ server.cpu.cpuNum }}</div></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="el-table__cell is-leaf"><div class="cell">用户使用率</div></td>
|
||||
<td class="el-table__cell is-leaf"><div class="cell" v-if="server.cpu">{{ server.cpu.used }}%</div></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="el-table__cell is-leaf"><div class="cell">系统使用率</div></td>
|
||||
<td class="el-table__cell is-leaf"><div class="cell" v-if="server.cpu">{{ server.cpu.sys }}%</div></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="el-table__cell is-leaf"><div class="cell">当前空闲率</div></td>
|
||||
<td class="el-table__cell is-leaf"><div class="cell" v-if="server.cpu">{{ server.cpu.free }}%</div></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
|
||||
<el-col :span="12" class="card-box">
|
||||
<el-card>
|
||||
<template #header><span>内存</span></template>
|
||||
<div class="el-table el-table--enable-row-hover el-table--medium">
|
||||
<table cellspacing="0" style="width: 100%;">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="el-table__cell is-leaf"><div class="cell">属性</div></th>
|
||||
<th class="el-table__cell is-leaf"><div class="cell">内存</div></th>
|
||||
<th class="el-table__cell is-leaf"><div class="cell">JVM</div></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td class="el-table__cell is-leaf"><div class="cell">总内存</div></td>
|
||||
<td class="el-table__cell is-leaf"><div class="cell" v-if="server.mem">{{ server.mem.total }}G</div></td>
|
||||
<td class="el-table__cell is-leaf"><div class="cell" v-if="server.jvm">{{ server.jvm.total }}M</div></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="el-table__cell is-leaf"><div class="cell">已用内存</div></td>
|
||||
<td class="el-table__cell is-leaf"><div class="cell" v-if="server.mem">{{ server.mem.used}}G</div></td>
|
||||
<td class="el-table__cell is-leaf"><div class="cell" v-if="server.jvm">{{ server.jvm.used}}M</div></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="el-table__cell is-leaf"><div class="cell">剩余内存</div></td>
|
||||
<td class="el-table__cell is-leaf"><div class="cell" v-if="server.mem">{{ server.mem.free }}G</div></td>
|
||||
<td class="el-table__cell is-leaf"><div class="cell" v-if="server.jvm">{{ server.jvm.free }}M</div></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="el-table__cell is-leaf"><div class="cell">使用率</div></td>
|
||||
<td class="el-table__cell is-leaf"><div class="cell" v-if="server.mem" :class="{'text-danger': server.mem.usage > 80}">{{ server.mem.usage }}%</div></td>
|
||||
<td class="el-table__cell is-leaf"><div class="cell" v-if="server.jvm" :class="{'text-danger': server.jvm.usage > 80}">{{ server.jvm.usage }}%</div></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
|
||||
<el-col :span="24" class="card-box">
|
||||
<el-card>
|
||||
<template #header><span>服务器信息</span></template>
|
||||
<div class="el-table el-table--enable-row-hover el-table--medium">
|
||||
<table cellspacing="0" style="width: 100%;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td class="el-table__cell is-leaf"><div class="cell">服务器名称</div></td>
|
||||
<td class="el-table__cell is-leaf"><div class="cell" v-if="server.sys">{{ server.sys.computerName }}</div></td>
|
||||
<td class="el-table__cell is-leaf"><div class="cell">操作系统</div></td>
|
||||
<td class="el-table__cell is-leaf"><div class="cell" v-if="server.sys">{{ server.sys.osName }}</div></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="el-table__cell is-leaf"><div class="cell">服务器IP</div></td>
|
||||
<td class="el-table__cell is-leaf"><div class="cell" v-if="server.sys">{{ server.sys.computerIp }}</div></td>
|
||||
<td class="el-table__cell is-leaf"><div class="cell">系统架构</div></td>
|
||||
<td class="el-table__cell is-leaf"><div class="cell" v-if="server.sys">{{ server.sys.osArch }}</div></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
|
||||
<el-col :span="24" class="card-box">
|
||||
<el-card>
|
||||
<template #header><span>Java虚拟机信息</span></template>
|
||||
<div class="el-table el-table--enable-row-hover el-table--medium">
|
||||
<table cellspacing="0" style="width: 100%;table-layout:fixed;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td class="el-table__cell is-leaf"><div class="cell">Java名称</div></td>
|
||||
<td class="el-table__cell is-leaf"><div class="cell" v-if="server.jvm">{{ server.jvm.name }}</div></td>
|
||||
<td class="el-table__cell is-leaf"><div class="cell">Java版本</div></td>
|
||||
<td class="el-table__cell is-leaf"><div class="cell" v-if="server.jvm">{{ server.jvm.version }}</div></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="el-table__cell is-leaf"><div class="cell">启动时间</div></td>
|
||||
<td class="el-table__cell is-leaf"><div class="cell" v-if="server.jvm">{{ server.jvm.startTime }}</div></td>
|
||||
<td class="el-table__cell is-leaf"><div class="cell">运行时长</div></td>
|
||||
<td class="el-table__cell is-leaf"><div class="cell" v-if="server.jvm">{{ server.jvm.runTime }}</div></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="1" class="el-table__cell is-leaf"><div class="cell">安装路径</div></td>
|
||||
<td colspan="3" class="el-table__cell is-leaf"><div class="cell" v-if="server.jvm">{{ server.jvm.home }}</div></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="1" class="el-table__cell is-leaf"><div class="cell">项目路径</div></td>
|
||||
<td colspan="3" class="el-table__cell is-leaf"><div class="cell" v-if="server.sys">{{ server.sys.userDir }}</div></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="1" class="el-table__cell is-leaf"><div class="cell">运行参数</div></td>
|
||||
<td colspan="3" class="el-table__cell is-leaf"><div class="cell" v-if="server.jvm">{{ server.jvm.inputArgs }}</div></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
|
||||
<el-col :span="24" class="card-box">
|
||||
<el-card>
|
||||
<template #header><span>磁盘状态</span></template>
|
||||
<div class="el-table el-table--enable-row-hover el-table--medium">
|
||||
<table cellspacing="0" style="width: 100%;">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="el-table__cell el-table__cell is-leaf"><div class="cell">盘符路径</div></th>
|
||||
<th class="el-table__cell is-leaf"><div class="cell">文件系统</div></th>
|
||||
<th class="el-table__cell is-leaf"><div class="cell">盘符类型</div></th>
|
||||
<th class="el-table__cell is-leaf"><div class="cell">总大小</div></th>
|
||||
<th class="el-table__cell is-leaf"><div class="cell">可用大小</div></th>
|
||||
<th class="el-table__cell is-leaf"><div class="cell">已用大小</div></th>
|
||||
<th class="el-table__cell is-leaf"><div class="cell">已用百分比</div></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody v-if="server.sysFiles">
|
||||
<tr v-for="(sysFile, index) in server.sysFiles" :key="index">
|
||||
<td class="el-table__cell is-leaf"><div class="cell">{{ sysFile.dirName }}</div></td>
|
||||
<td class="el-table__cell is-leaf"><div class="cell">{{ sysFile.sysTypeName }}</div></td>
|
||||
<td class="el-table__cell is-leaf"><div class="cell">{{ sysFile.typeName }}</div></td>
|
||||
<td class="el-table__cell is-leaf"><div class="cell">{{ sysFile.total }}</div></td>
|
||||
<td class="el-table__cell is-leaf"><div class="cell">{{ sysFile.free }}</div></td>
|
||||
<td class="el-table__cell is-leaf"><div class="cell">{{ sysFile.used }}</div></td>
|
||||
<td class="el-table__cell is-leaf"><div class="cell" :class="{'text-danger': sysFile.usage > 80}">{{ sysFile.usage }}%</div></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { getServer } from '@/api/monitor/server'
|
||||
|
||||
const server = ref([]);
|
||||
const { proxy } = getCurrentInstance();
|
||||
|
||||
function getList() {
|
||||
proxy.$modal.loading("正在加载服务监控数据,请稍候!");
|
||||
getServer().then(response => {
|
||||
server.value = response.data;
|
||||
proxy.$modal.closeLoading();
|
||||
});
|
||||
}
|
||||
|
||||
getList();
|
||||
</script>
|
||||
7
src/views/platform/index.vue
Normal file
7
src/views/platform/index.vue
Normal file
@ -0,0 +1,7 @@
|
||||
<template>
|
||||
<p>平台概览</p>
|
||||
</template>
|
||||
<script setup>
|
||||
|
||||
</script>
|
||||
|
||||
596
src/views/system/driver/index.vue
Normal file
596
src/views/system/driver/index.vue
Normal file
@ -0,0 +1,596 @@
|
||||
<template>
|
||||
<div class="app-container">
|
||||
<div class="user-container">
|
||||
<!-- 搜索区域 -->
|
||||
<div class="search-area">
|
||||
<el-input
|
||||
v-model="queryParams.driverName"
|
||||
placeholder="请输入驾驶员姓名"
|
||||
clearable
|
||||
prefix-icon="Search"
|
||||
class="search-input"
|
||||
/>
|
||||
|
||||
<el-select v-model="queryParams.status" placeholder="状态" clearable class="search-select">
|
||||
<el-option label="全部" value="" />
|
||||
<el-option
|
||||
v-for="dict in sys_normal_disable"
|
||||
:key="dict.value"
|
||||
:label="dict.label"
|
||||
:value="dict.value"
|
||||
/>
|
||||
</el-select>
|
||||
|
||||
<div class="search-buttons">
|
||||
<el-button type="primary" class="search-btn" @click="handleQuery">搜索</el-button>
|
||||
<el-button class="reset-btn" @click="resetQuery">重置</el-button>
|
||||
</div>
|
||||
|
||||
<div class="action-buttons">
|
||||
<el-button type="primary" class="search-btn" @click="handleAdd">新增</el-button>
|
||||
<el-button class="reset-btn" plain @click="handleImport">导入</el-button>
|
||||
<el-button class="reset-btn" plain @click="handleExport">导出</el-button>
|
||||
<el-button class="export-btn" type="info" @click="handleDelete">删除</el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 驾驶员表格 -->
|
||||
<el-table
|
||||
v-loading="loading"
|
||||
:data="driverList"
|
||||
@selection-change="handleSelectionChange"
|
||||
style="width: 100%"
|
||||
class="custom-table"
|
||||
:header-cell-style="{ backgroundColor: '#343744', color: '#fff' }"
|
||||
>
|
||||
<el-table-column type="selection" width="50" align="left" />
|
||||
<el-table-column label="序号" width="60" align="left">
|
||||
<template #default="scope">
|
||||
{{ scope.$index + 1 }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="120" align="left">
|
||||
<template #default="scope">
|
||||
<el-button link text @click="handleUpdate(scope.row)">编辑</el-button>
|
||||
<el-button link text type="primary" @click="handleDelete(scope.row)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="驾驶员姓名" prop="driverName" align="left" />
|
||||
<el-table-column label="身份证号" prop="idCard" align="left" />
|
||||
<el-table-column label="驾照类型" prop="licenseType" align="left" />
|
||||
<el-table-column label="联系电话" prop="phonenumber" align="left" />
|
||||
<el-table-column label="状态" align="left">
|
||||
<template #default="scope">
|
||||
{{ scope.row.status === '0' ? '正常' : '停用' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="创建时间" align="left" prop="createTime" width="160">
|
||||
<template #default="scope">
|
||||
<span>{{ parseTime(scope.row.createTime) }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<!-- 分页组件 -->
|
||||
<pagination
|
||||
v-show="total > 0"
|
||||
:total="total"
|
||||
v-model:page="queryParams.pageNum"
|
||||
v-model:limit="queryParams.pageSize"
|
||||
@pagination="getList"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- 添加或修改驾驶员对话框 -->
|
||||
<el-dialog :title="title" v-model="open" width="600px" append-to-body>
|
||||
<el-form :model="form" :rules="rules" ref="driverRef" label-width="100px">
|
||||
<el-row>
|
||||
<el-col :span="24">
|
||||
<el-form-item label="驾驶员姓名" prop="driverName">
|
||||
<el-input v-model="form.driverName" placeholder="请输入驾驶员姓名" maxlength="30" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="24">
|
||||
<el-form-item label="身份证号" prop="idCard">
|
||||
<el-input v-model="form.idCard" placeholder="请输入身份证号" maxlength="18" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row>
|
||||
<el-col :span="24">
|
||||
<el-form-item label="联系电话" prop="phonenumber">
|
||||
<el-input v-model="form.phonenumber" placeholder="请输入联系电话" maxlength="11" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="24">
|
||||
<el-form-item label="驾照类型" prop="licenseType">
|
||||
<el-select v-model="form.licenseType" placeholder="请选择驾照类型">
|
||||
<el-option label="A1" value="A1" />
|
||||
<el-option label="A2" value="A2" />
|
||||
<el-option label="B1" value="B1" />
|
||||
<el-option label="B2" value="B2" />
|
||||
<el-option label="C1" value="C1" />
|
||||
<el-option label="C2" value="C2" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row>
|
||||
<el-col :span="24">
|
||||
<el-form-item label="状态">
|
||||
<el-radio-group v-model="form.status">
|
||||
<el-radio
|
||||
v-for="dict in sys_normal_disable"
|
||||
:key="dict.value"
|
||||
:label="dict.value"
|
||||
>{{ dict.label }}</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button class="export-btn" @click="cancel">取 消</el-button>
|
||||
<el-button class="search-btn" type="primary" @click="submitForm">确 定</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 驾驶员导入对话框 -->
|
||||
<el-dialog :title="upload.title" v-model="upload.open" width="400px" append-to-body>
|
||||
<el-upload
|
||||
ref="uploadRef"
|
||||
:limit="1"
|
||||
accept=".xlsx, .xls"
|
||||
:headers="upload.headers"
|
||||
:action="upload.url + '?updateSupport=' + upload.updateSupport"
|
||||
:disabled="upload.isUploading"
|
||||
:on-progress="handleFileUploadProgress"
|
||||
:on-success="handleFileSuccess"
|
||||
:auto-upload="false"
|
||||
drag
|
||||
>
|
||||
<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 text-center">
|
||||
<div class="el-upload__tip">
|
||||
<el-checkbox v-model="upload.updateSupport" />是否更新已经存在的驾驶员数据
|
||||
</div>
|
||||
<span>仅允许导入xls、xlsx格式文件。</span>
|
||||
<el-link type="primary" :underline="false" style="font-size:12px;vertical-align: baseline;" @click="importTemplate">下载模板</el-link>
|
||||
</div>
|
||||
</template>
|
||||
</el-upload>
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button type="primary" @click="submitFileForm">确 定</el-button>
|
||||
<el-button @click="upload.open = false">取 消</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup name="Driver">
|
||||
import { getToken } from "@/utils/auth";
|
||||
import { parseTime } from "@/utils/ruoyi";
|
||||
|
||||
const { proxy } = getCurrentInstance();
|
||||
const { sys_normal_disable } = proxy.useDict("sys_normal_disable");
|
||||
|
||||
const driverList = ref([]);
|
||||
const open = ref(false);
|
||||
const loading = ref(false);
|
||||
const ids = ref([]);
|
||||
const single = ref(true);
|
||||
const multiple = ref(true);
|
||||
const total = ref(0);
|
||||
const title = ref("");
|
||||
|
||||
// 驾驶员导入参数
|
||||
const upload = reactive({
|
||||
open: false,
|
||||
title: "",
|
||||
isUploading: false,
|
||||
updateSupport: 0,
|
||||
headers: { Authorization: "Bearer " + getToken() },
|
||||
url: import.meta.env.VITE_APP_BASE_API + "/system/driver/importData"
|
||||
});
|
||||
|
||||
const queryParams = ref({
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
driverName: undefined,
|
||||
phonenumber: undefined,
|
||||
status: undefined
|
||||
});
|
||||
|
||||
const form = ref({});
|
||||
|
||||
const rules = ref({
|
||||
driverName: [
|
||||
{ required: true, message: "驾驶员姓名不能为空", trigger: "blur" }
|
||||
],
|
||||
idCard: [
|
||||
{ required: true, message: "身份证号不能为空", trigger: "blur" },
|
||||
{ pattern: /(^\d{15}$)|(^\d{18}$)|(^\d{17}(\d|X|x)$)/, message: "请输入正确的身份证号码", trigger: "blur" }
|
||||
],
|
||||
phonenumber: [
|
||||
{ required: true, message: "联系电话不能为空", trigger: "blur" },
|
||||
{ pattern: /^1[3|4|5|6|7|8|9][0-9]\d{8}$/, message: "请输入正确的手机号码", trigger: "blur" }
|
||||
],
|
||||
licenseType: [
|
||||
{ required: true, message: "驾照类型不能为空", trigger: "change" }
|
||||
]
|
||||
});
|
||||
|
||||
// 模拟数据
|
||||
const mockDriverData = [
|
||||
{
|
||||
driverId: 1,
|
||||
driverName: '张三',
|
||||
idCard: '110101199001011234',
|
||||
licenseType: 'A1',
|
||||
phonenumber: '13800138001',
|
||||
status: '0',
|
||||
createTime: '2023-01-01 08:00:00'
|
||||
},
|
||||
{
|
||||
driverId: 2,
|
||||
driverName: '李四',
|
||||
idCard: '110101199001021235',
|
||||
licenseType: 'B1',
|
||||
phonenumber: '13800138002',
|
||||
status: '0',
|
||||
createTime: '2023-01-02 08:00:00'
|
||||
},
|
||||
{
|
||||
driverId: 3,
|
||||
driverName: '王五',
|
||||
idCard: '110101199001031236',
|
||||
licenseType: 'A2',
|
||||
phonenumber: '13800138003',
|
||||
status: '1',
|
||||
createTime: '2023-01-03 08:00:00'
|
||||
},
|
||||
{
|
||||
driverId: 4,
|
||||
driverName: '赵六',
|
||||
idCard: '110101199001041237',
|
||||
licenseType: 'C1',
|
||||
phonenumber: '13800138004',
|
||||
status: '0',
|
||||
createTime: '2023-01-04 08:00:00'
|
||||
},
|
||||
{
|
||||
driverId: 5,
|
||||
driverName: '钱七',
|
||||
idCard: '110101199001051238',
|
||||
licenseType: 'B2',
|
||||
phonenumber: '13800138005',
|
||||
status: '0',
|
||||
createTime: '2023-01-05 08:00:00'
|
||||
}
|
||||
];
|
||||
|
||||
/** 查询驾驶员列表 */
|
||||
function getList() {
|
||||
loading.value = true;
|
||||
// 模拟API调用
|
||||
setTimeout(() => {
|
||||
loading.value = false;
|
||||
driverList.value = mockDriverData;
|
||||
total.value = mockDriverData.length;
|
||||
}, 300);
|
||||
}
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
function handleQuery() {
|
||||
queryParams.value.pageNum = 1;
|
||||
getList();
|
||||
}
|
||||
|
||||
/** 重置按钮操作 */
|
||||
function resetQuery() {
|
||||
proxy.resetForm("queryRef");
|
||||
queryParams.value.pageNum = 1;
|
||||
handleQuery();
|
||||
}
|
||||
|
||||
/** 选择条数 */
|
||||
function handleSelectionChange(selection) {
|
||||
ids.value = selection.map(item => item.driverId);
|
||||
single.value = selection.length != 1;
|
||||
multiple.value = !selection.length;
|
||||
}
|
||||
|
||||
/** 删除按钮操作 */
|
||||
function handleDelete(row) {
|
||||
const driverIds = row.driverId || ids.value;
|
||||
proxy.$modal.confirm('是否确认删除驾驶员编号为"' + driverIds + '"的数据项?').then(function() {
|
||||
// 模拟删除操作
|
||||
proxy.$modal.msgSuccess("删除成功");
|
||||
getList();
|
||||
}).catch(() => {});
|
||||
}
|
||||
|
||||
/** 新增按钮操作 */
|
||||
function handleAdd() {
|
||||
reset();
|
||||
open.value = true;
|
||||
title.value = "添加驾驶员";
|
||||
}
|
||||
|
||||
/** 修改按钮操作 */
|
||||
function handleUpdate(row) {
|
||||
reset();
|
||||
const driverId = row.driverId || ids.value[0];
|
||||
// 模拟获取数据
|
||||
const driverInfo = mockDriverData.find(d => d.driverId === driverId);
|
||||
if (driverInfo) {
|
||||
form.value = {...driverInfo};
|
||||
open.value = true;
|
||||
title.value = "修改驾驶员";
|
||||
}
|
||||
}
|
||||
|
||||
/** 重置表单 */
|
||||
function reset() {
|
||||
form.value = {
|
||||
driverId: undefined,
|
||||
driverName: undefined,
|
||||
idCard: undefined,
|
||||
phonenumber: undefined,
|
||||
licenseType: undefined,
|
||||
status: "0"
|
||||
};
|
||||
proxy.resetForm("driverRef");
|
||||
}
|
||||
|
||||
/** 提交表单 */
|
||||
function submitForm() {
|
||||
proxy.$refs["driverRef"].validate(valid => {
|
||||
if (valid) {
|
||||
if (form.value.driverId != undefined) {
|
||||
// 模拟修改操作
|
||||
proxy.$modal.msgSuccess("修改成功");
|
||||
open.value = false;
|
||||
getList();
|
||||
} else {
|
||||
// 模拟新增操作
|
||||
proxy.$modal.msgSuccess("新增成功");
|
||||
open.value = false;
|
||||
getList();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/** 取消按钮 */
|
||||
function cancel() {
|
||||
open.value = false;
|
||||
reset();
|
||||
}
|
||||
|
||||
/** 导出按钮操作 */
|
||||
function handleExport() {
|
||||
proxy.$modal.msgSuccess("导出功能暂未实现");
|
||||
}
|
||||
|
||||
/** 导入按钮操作 */
|
||||
function handleImport() {
|
||||
upload.title = "驾驶员导入";
|
||||
upload.open = true;
|
||||
}
|
||||
|
||||
/** 导入对话框关闭时清除数据 */
|
||||
function handleFileUploadProgress(event, file, fileList) {
|
||||
upload.isUploading = true;
|
||||
}
|
||||
|
||||
/** 导入成功处理 */
|
||||
function handleFileSuccess(response, file, fileList) {
|
||||
upload.open = false;
|
||||
upload.isUploading = false;
|
||||
proxy.$refs["uploadRef"].clearFiles();
|
||||
proxy.$modal.msgSuccess("导入成功");
|
||||
getList();
|
||||
}
|
||||
|
||||
/** 下载导入模板 */
|
||||
function importTemplate() {
|
||||
proxy.$modal.msgSuccess("下载模板功能暂未实现");
|
||||
}
|
||||
|
||||
/** 提交上传文件 */
|
||||
function submitFileForm() {
|
||||
proxy.$refs["uploadRef"].submit();
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
getList();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.app-container {
|
||||
padding: 20px;
|
||||
background-color: #292C38;
|
||||
color: #ffffff;
|
||||
min-height: 100%;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
.user-container {
|
||||
background-color: #292c38;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.search-area {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 20px;
|
||||
|
||||
.search-input {
|
||||
width: 250px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.search-select {
|
||||
width: 150px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.search-buttons {
|
||||
margin-right: 20px;
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.action-buttons {
|
||||
margin-left: auto;
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
|
||||
// 输入框 下拉框 搜索栏
|
||||
::v-deep(.el-input__wrapper:hover) {
|
||||
box-shadow: none !important;
|
||||
}
|
||||
|
||||
::v-deep(.el-select) {
|
||||
border: none !important;
|
||||
width: 140px !important;
|
||||
// 修改el-select的输入框的border
|
||||
--el-border-color-hover: #343744!important;
|
||||
--el-border-color: #343744!important;
|
||||
}
|
||||
|
||||
::v-deep(.el-select__wrapper.is-focused) {
|
||||
box-shadow: none !important;
|
||||
}
|
||||
|
||||
::v-deep(.el-select__wrapper) {
|
||||
background: #343744 !important;
|
||||
color: #96A0B5 !important;
|
||||
border-radius: 8px !important;
|
||||
height: 36px !important;
|
||||
width: 140px;
|
||||
font-size: 15px;
|
||||
border: none !important;
|
||||
}
|
||||
|
||||
::v-deep(.el-input__wrapper) {
|
||||
background: #343744 !important;
|
||||
color: #96A0B5 !important;
|
||||
border-radius: 8px !important;
|
||||
height: 36px !important;
|
||||
border: none !important;
|
||||
box-shadow: none !important;
|
||||
}
|
||||
|
||||
::v-deep(.el-select__placeholder) {
|
||||
color: #96A0B5 !important;
|
||||
}
|
||||
|
||||
.custom-table {
|
||||
background-color: #292c38;
|
||||
color: #ffffff;
|
||||
margin-bottom: 20px;
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
|
||||
:deep(.el-table__inner-wrapper::before) {
|
||||
display: none; /* 隐藏表格顶部的边框线 */
|
||||
}
|
||||
|
||||
:deep(.el-table__header) {
|
||||
border-top-left-radius: 4px;
|
||||
border-top-right-radius: 4px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
:deep(.el-table__header-wrapper) {
|
||||
th {
|
||||
background-color: #343744 !important;
|
||||
color: #96A0B5!important;
|
||||
font-weight: normal;
|
||||
border-bottom: none; /* 移除th底部边线 */
|
||||
}
|
||||
|
||||
tr th.el-table__cell:first-child {
|
||||
border-top-left-radius: 6px;
|
||||
|
||||
.cell {
|
||||
border-top-left-radius: 6px;
|
||||
}
|
||||
}
|
||||
|
||||
tr th.el-table__cell:last-child {
|
||||
border-top-right-radius: 4px;
|
||||
|
||||
.cell {
|
||||
border-top-right-radius: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
.el-table__header th.el-table__cell {
|
||||
background-color: #343744 !important;
|
||||
color: #96A0B5;
|
||||
}
|
||||
|
||||
tr {
|
||||
background-color: #343744 !important;
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.el-table__body-wrapper) {
|
||||
td {
|
||||
background-color: #292c38;
|
||||
color: #ffffff;
|
||||
border-bottom: 1px solid #4C4F5F;
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.el-table__body tr:hover > td) {
|
||||
background-color: #1e2233 !important;
|
||||
}
|
||||
|
||||
:deep(.el-button--link) {
|
||||
border: none;
|
||||
background: transparent;
|
||||
color: #347AE2;
|
||||
padding: 0;
|
||||
margin: 0 5px;
|
||||
}
|
||||
|
||||
:deep(.el-button.is-text) {
|
||||
background-color: transparent !important;
|
||||
border-color: transparent;
|
||||
color: #347AE2;
|
||||
|
||||
&:hover, &:focus, &:active {
|
||||
background-color: transparent !important;
|
||||
color: #347AE2;
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.el-button.el-button--primary.is-text) {
|
||||
background-color: transparent !important;
|
||||
color: #347AE2;
|
||||
|
||||
&:hover {
|
||||
background-color: transparent !important;
|
||||
color: #347AE2;
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.el-table__body tr:hover .el-button--link.is-text) {
|
||||
background-color: transparent !important;
|
||||
color: #347AE2;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@ -180,7 +180,7 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup name="Operlog">
|
||||
<script setup name="systemOperlog">
|
||||
import { list, delOperlog, cleanOperlog } from "@/api/monitor/operlog";
|
||||
|
||||
const { proxy } = getCurrentInstance();
|
||||
@ -241,7 +241,7 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup name="Role">
|
||||
<script setup name="systemRole">
|
||||
import { addRole, changeRoleStatus, dataScope, delRole, getRole, listRole, updateRole, deptTreeSelect } from "@/api/system/role";
|
||||
import { roleMenuTreeselect, treeselect as menuTreeselect } from "@/api/system/menu";
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user