perf:优化缓存,布局,样式
This commit is contained in:
parent
4784ff379e
commit
a985df0af7
|
|
@ -49,7 +49,7 @@
|
|||
v-if="item.type"
|
||||
:type="item.type"
|
||||
:width="item.width"
|
||||
:align="item.align"
|
||||
:align="item.align!=null?item.align:'center'"
|
||||
:fixed="item.fixed"
|
||||
:label="item.label"
|
||||
/>
|
||||
|
|
@ -57,7 +57,7 @@
|
|||
v-else
|
||||
:prop="item.name"
|
||||
:width="item.width"
|
||||
:align="item.align"
|
||||
:align="item.align!=null?item.align:'center'"
|
||||
:fixed="item.fixed"
|
||||
:label="item.label"
|
||||
>
|
||||
|
|
@ -186,18 +186,23 @@ const deleteAction = (row) => {
|
|||
flex-direction:column;
|
||||
|
||||
.header{
|
||||
display: flex;
|
||||
padding: 16px 16px 0 16px;
|
||||
margin-bottom: 16px;
|
||||
display: flex;
|
||||
border-radius: 4px;
|
||||
background: white;
|
||||
box-shadow: 0 0 12px rgb(0 0 0 / 5%);
|
||||
|
||||
}
|
||||
.footer{
|
||||
flex: 1;
|
||||
display: flex;
|
||||
padding: 16px;
|
||||
flex-direction: column;
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
background: white;
|
||||
box-shadow: 0 0 12px rgb(0 0 0 / 5%);
|
||||
.operator{
|
||||
margin-bottom: 15px
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,12 +1,8 @@
|
|||
<template>
|
||||
<el-breadcrumb class="app-breadcrumb" separator="/">
|
||||
<transition-group name="breadcrumb" mode="out-in">
|
||||
<el-breadcrumb-item v-for="(item, index) in obj.levelList" :key="item.path">
|
||||
<span
|
||||
v-if="item.redirect === 'noRedirect' || index == obj.levelList.length - 1"
|
||||
class="no-redirect"
|
||||
>{{ item.meta.title }}</span
|
||||
>
|
||||
<el-breadcrumb-item v-for="(item, index) in levelList" :key="item.path">
|
||||
<span v-if="item.redirect === 'noRedirect' || index == levelList.length - 1" class="no-redirect">{{ item.meta.title }}</span>
|
||||
<a v-else @click.prevent="handleLink(item)">{{ item.meta.title }}</a>
|
||||
</el-breadcrumb-item>
|
||||
</transition-group>
|
||||
|
|
@ -15,20 +11,25 @@
|
|||
|
||||
<script lang="ts" setup>
|
||||
import pathToRegexp from 'path-to-regexp'
|
||||
import { onMounted, reactive, watch } from 'vue'
|
||||
import { onMounted, ref, watch } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
|
||||
const obj = reactive({ levelList: {} })
|
||||
const levelList = ref([])
|
||||
const route = useRoute()
|
||||
|
||||
// 获取面包屑
|
||||
const getBreadcrumb = () => {
|
||||
let matched = route.matched.filter((item) => item.meta && item.meta.title)
|
||||
const first = matched[0]
|
||||
obj.levelList = matched.filter(
|
||||
levelList.value = matched.filter(
|
||||
(item) => item.meta && item.meta.title && item.meta.breadcrumb !== false,
|
||||
)
|
||||
}
|
||||
|
||||
const handleLink = ()=>{
|
||||
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
getBreadcrumb()
|
||||
watch(route, () => {
|
||||
|
|
@ -47,6 +48,7 @@
|
|||
font-size: 14px;
|
||||
margin-bottom: 4px;
|
||||
.no-redirect {
|
||||
color:#303133;
|
||||
cursor: text;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,13 @@
|
|||
<template>
|
||||
<div
|
||||
class="m-layout-header"
|
||||
:class="[SettingStore.themeConfig.fixedHeader&&'zb-fixed-header']"
|
||||
:style="{ left: `${mode === 'horizontal' ? 0 : isCollapse ? '60' : '210'}px` }"
|
||||
:class="[
|
||||
SettingStore.themeConfig.fixedHeader&&'zb-fixed-header',
|
||||
isCollapse?'fixed-header-collapse':'fixed-header-no-collapse'
|
||||
]"
|
||||
|
||||
>
|
||||
<!-- :style="{ left: `${mode === 'horizontal' ? 0 : isCollapse ? '60' : '210'}px` }"-->
|
||||
<div
|
||||
class="header"
|
||||
:class="{
|
||||
|
|
@ -122,6 +126,7 @@
|
|||
.mobile {
|
||||
.m-layout-header {
|
||||
left: 0 !important;
|
||||
width: 100%!important;
|
||||
}
|
||||
}
|
||||
.icon {
|
||||
|
|
@ -152,17 +157,24 @@
|
|||
.zb-fixed-header{
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
z-index: 9;
|
||||
}
|
||||
|
||||
.m-layout-header {
|
||||
width: 100%;
|
||||
background: white;
|
||||
transition: left 0.3s;
|
||||
transition: width 0.28s;
|
||||
flex-shrink: 0;
|
||||
box-sizing: border-box;
|
||||
box-shadow: 0 1px 4px rgb(0 21 41 / 8%);
|
||||
}
|
||||
.fixed-header-collapse{
|
||||
width: calc(100% - 60px);
|
||||
}
|
||||
.fixed-header-no-collapse{
|
||||
width: calc(100% - 210px);
|
||||
}
|
||||
.el-dropdown {
|
||||
display: flex;
|
||||
height: 100%;
|
||||
|
|
|
|||
|
|
@ -2,10 +2,9 @@
|
|||
<div class="app-main" v-if="isReload">
|
||||
<router-view v-slot="{ Component, route }">
|
||||
<transition name="fade-slide" mode="out-in" appear>
|
||||
<keep-alive v-if="route.meta && route.meta.keepAlive">
|
||||
<keep-alive :include="cacheRoutes">
|
||||
<component :is="Component" :key="route.path" />
|
||||
</keep-alive>
|
||||
<component :is="Component" :key="route.path" v-else />
|
||||
</transition>
|
||||
</router-view>
|
||||
<u-theme />
|
||||
|
|
@ -14,15 +13,13 @@
|
|||
|
||||
<script lang="ts" setup>
|
||||
import UTheme from '@/components/u-theme/index.vue'
|
||||
|
||||
import { computed, ref } from 'vue'
|
||||
import {useSettingStore} from "@/store/modules/setting"
|
||||
import {useTagsViewStore} from "@/store/modules/tagsView"
|
||||
import {usePermissionStore} from "@/store/modules/permission"
|
||||
const SettingStore = useSettingStore()
|
||||
const TagsViewStore = useTagsViewStore()
|
||||
|
||||
const cachedViews = computed(() =>TagsViewStore.cachedViews)
|
||||
const PermissionStor = usePermissionStore()
|
||||
|
||||
const cacheRoutes = computed(() =>PermissionStor.cacheRoutes)
|
||||
const isReload = computed(() => SettingStore.isReload)
|
||||
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@ interface extendRoute {
|
|||
}
|
||||
|
||||
// 引入组件
|
||||
//
|
||||
import tableRouter from './modules/table'
|
||||
import dataScreenRouter from './modules/dataScreen'
|
||||
import errorRouter from './modules/error'
|
||||
|
|
@ -19,6 +18,30 @@ import chatRouter from './modules/chat'
|
|||
import othersRouter from './modules/other'
|
||||
import externalLink from './modules/externalLink'
|
||||
import formRouter from './modules/from'
|
||||
import zipRoutes from './modules/zip'
|
||||
import clipboardTable from './modules/clipboard'
|
||||
|
||||
|
||||
// 异步组件
|
||||
export const asyncRoutes = [
|
||||
dataScreenRouter,
|
||||
chartsRouter,
|
||||
tableRouter,
|
||||
formRouter,
|
||||
chatRouter,
|
||||
othersRouter,
|
||||
nestedRouter,
|
||||
excelRouter,
|
||||
zipRoutes,
|
||||
errorRouter,
|
||||
externalLink,
|
||||
clipboardTable,
|
||||
systemRouter,
|
||||
{
|
||||
path: '/:pathMatch(.*)',
|
||||
redirect: '/error/404'
|
||||
}
|
||||
]
|
||||
|
||||
/**
|
||||
* path ==> 路由路径
|
||||
|
|
@ -32,6 +55,7 @@ import formRouter from './modules/from'
|
|||
* meta.title ==> 路由标题
|
||||
* meta.icon ==> 菜单icon
|
||||
* meta.affix ==> 如果设置为true将会出现在 标签栏中
|
||||
* meta.breadcrumb ==> 如果设置为false,该项将隐藏在breadcrumb中(默认值为true)
|
||||
*/
|
||||
|
||||
export const constantRoutes: Array<RouteRecordRaw&extendRoute> = [
|
||||
|
|
@ -58,70 +82,8 @@ export const constantRoutes: Array<RouteRecordRaw&extendRoute> = [
|
|||
},
|
||||
]
|
||||
|
||||
const clipboardTable = {
|
||||
path: '/clipboard',
|
||||
component: Layout,
|
||||
redirect: 'noRedirect',
|
||||
name: 'clipboard',
|
||||
meta: {
|
||||
title: 'clipboard',
|
||||
icon: 'document-copy',
|
||||
roles:['other']
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: 'index',
|
||||
component: () => import('@/views/clipboard/index.vue'),
|
||||
name: 'map',
|
||||
meta: { title: '剪贴板', roles:['other'] ,icon: 'document-copy',}
|
||||
},
|
||||
|
||||
]
|
||||
}
|
||||
// //
|
||||
const zipRoutes = {
|
||||
path: '/zip',
|
||||
component: Layout,
|
||||
isShow:true,
|
||||
redirect: 'noRedirect',
|
||||
name: 'zip',
|
||||
alwaysShow:true,
|
||||
meta: {
|
||||
title: 'Zip',
|
||||
icon: 'document-copy',
|
||||
roles:['other']
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: 'download',
|
||||
component: () => import('@/views/zip/download.vue'),
|
||||
name: 'download',
|
||||
meta: { title: 'Zip', roles:['other'] ,icon: 'document-copy',}
|
||||
},
|
||||
|
||||
]
|
||||
}
|
||||
|
||||
// // 异步组件
|
||||
export const asyncRoutes = [
|
||||
dataScreenRouter,
|
||||
chartsRouter,
|
||||
tableRouter,
|
||||
formRouter,
|
||||
chatRouter,
|
||||
othersRouter,
|
||||
nestedRouter,
|
||||
excelRouter,
|
||||
zipRoutes,
|
||||
errorRouter,
|
||||
externalLink,
|
||||
clipboardTable,
|
||||
systemRouter,
|
||||
{
|
||||
path: '/:pathMatch(.*)',
|
||||
redirect: '/error/404'
|
||||
}
|
||||
]
|
||||
|
||||
const router = createRouter({
|
||||
// history: createWebHistory(process.env.BASE_URL), // history
|
||||
|
|
|
|||
|
|
@ -0,0 +1,23 @@
|
|||
import Layout from '@/layout/index.vue'
|
||||
|
||||
const clipboardTable = {
|
||||
path: '/clipboard',
|
||||
component: Layout,
|
||||
redirect: 'noRedirect',
|
||||
name: 'clipboard',
|
||||
meta: {
|
||||
title: 'clipboard',
|
||||
icon: 'document-copy',
|
||||
roles:['other']
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: 'index',
|
||||
component: () => import('@/views/clipboard/index.vue'),
|
||||
name: 'map',
|
||||
meta: { title: '剪贴板', roles:['other'] ,icon: 'document-copy',}
|
||||
},
|
||||
|
||||
]
|
||||
}
|
||||
export default clipboardTable
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
import Layout from '@/layout/index.vue'
|
||||
|
||||
const zipRoutes = {
|
||||
path: '/zip',
|
||||
component: Layout,
|
||||
isShow:true,
|
||||
redirect: 'noRedirect',
|
||||
name: 'zip',
|
||||
alwaysShow:true,
|
||||
meta: {
|
||||
title: 'Zip',
|
||||
icon: 'document-copy',
|
||||
roles:['other']
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: 'download',
|
||||
component: () => import('@/views/zip/download.vue'),
|
||||
name: 'download',
|
||||
meta: { title: 'Zip', roles:['other'] ,icon: 'document-copy',}
|
||||
},
|
||||
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
export default zipRoutes
|
||||
|
|
@ -1,40 +1,7 @@
|
|||
import {defineStore} from 'pinia'
|
||||
import { asyncRoutes, constantRoutes } from '@/router/index'
|
||||
/**
|
||||
* 通过递归过滤异步路由表
|
||||
* @param routes asyncRoutes
|
||||
* @param roles
|
||||
*/
|
||||
export function filterAsyncRoutes(routes, roles) {
|
||||
const res = []
|
||||
routes.forEach(route => {
|
||||
const tmp = { ...route }
|
||||
if (hasPermission(roles, tmp)) {
|
||||
if (tmp.children) {
|
||||
tmp.children = filterAsyncRoutes(tmp.children, roles)
|
||||
}
|
||||
res.push(tmp)
|
||||
}
|
||||
})
|
||||
return res
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用 meta.role 来确定当前用户是否具有权限
|
||||
* @param roles
|
||||
* @param route
|
||||
*/
|
||||
function hasPermission(roles, route) {
|
||||
if (route.meta && route.meta.roles) {
|
||||
return roles.some(role => route.meta.roles.includes(role))
|
||||
} else {
|
||||
// return true
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
import {hasPermission,filterAsyncRoutes} from "@/utils/routers"
|
||||
import {filterKeepAlive} from "../../utils/routers";
|
||||
export const usePermissionStore = defineStore({
|
||||
// id: 必须的,在所有 Store 中唯一
|
||||
id:'permissionState',
|
||||
|
|
@ -43,7 +10,9 @@ export const usePermissionStore = defineStore({
|
|||
// 路由
|
||||
routes:[],
|
||||
// 动态路由
|
||||
addRoutes:{},
|
||||
addRoutes:[],
|
||||
// 缓存路由
|
||||
cacheRoutes:{},
|
||||
}),
|
||||
getters: {
|
||||
permission_routes:state=> {
|
||||
|
|
@ -55,6 +24,7 @@ export const usePermissionStore = defineStore({
|
|||
// 生成路由
|
||||
generateRoutes(roles){
|
||||
return new Promise(resolve => {
|
||||
this.getCacheRoutes()
|
||||
// 在这判断是否有权限,哪些角色拥有哪些权限
|
||||
let accessedRoutes
|
||||
if (roles&&roles.length&&!roles.includes('admin')) {
|
||||
|
|
@ -71,13 +41,14 @@ export const usePermissionStore = defineStore({
|
|||
clearRoutes(){
|
||||
this.routes = []
|
||||
this.addRoutes = []
|
||||
this.cacheRoutes = []
|
||||
},
|
||||
getCacheRoutes(){
|
||||
this.cacheRoutes = filterKeepAlive(asyncRoutes)
|
||||
}
|
||||
},
|
||||
// persist: {
|
||||
// // 本地存储的名称
|
||||
// key: "permissionState",
|
||||
// //保存的位置
|
||||
// storage: window.localStorage,//localstorage
|
||||
// },
|
||||
|
||||
})
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -7,13 +7,10 @@ body {
|
|||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
|
||||
body{
|
||||
background: #f6f8f9;
|
||||
background: #f0f2f5;
|
||||
}
|
||||
|
||||
|
||||
/* 常用 flex */
|
||||
.flex-center {
|
||||
display: flex;
|
||||
|
|
|
|||
|
|
@ -14,17 +14,17 @@
|
|||
/* 路由 */
|
||||
.fade-slide-leave-active,
|
||||
.fade-slide-enter-active {
|
||||
transition: all 0.2s;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.fade-slide-enter-from {
|
||||
opacity: 0;
|
||||
transform: translateX(-20px);
|
||||
transform: translateX(-30px);
|
||||
}
|
||||
|
||||
.fade-slide-leave-to {
|
||||
opacity: 0;
|
||||
transform: translateX(20px);
|
||||
transform: translateX(30px);
|
||||
}
|
||||
|
||||
// logo动画
|
||||
|
|
@ -41,28 +41,28 @@
|
|||
|
||||
|
||||
// 面包屑动画 方案1
|
||||
//.breadcrumb-enter-active {
|
||||
// transition: all 0.25s;
|
||||
//}
|
||||
//.breadcrumb-enter-from,
|
||||
//.breadcrumb-leave-active {
|
||||
// opacity: 0;
|
||||
// transform: translateX(30px) skewX(-50deg);
|
||||
//}
|
||||
/* 面包屑动画 方案2 */
|
||||
.breadcrumb-enter-active,
|
||||
.breadcrumb-leave-active {
|
||||
transition: all 0.2s ease;
|
||||
.breadcrumb-enter-active {
|
||||
transition: all 0.25s;
|
||||
}
|
||||
.breadcrumb-enter-from,
|
||||
.breadcrumb-leave-active {
|
||||
opacity: 0;
|
||||
transform: translateX(10px);
|
||||
}
|
||||
.breadcrumb-leave-active {
|
||||
position: absolute;
|
||||
z-index: -1;
|
||||
transform: translateX(30px) skewX(-50deg);
|
||||
}
|
||||
/* 面包屑动画 方案2 */
|
||||
//.breadcrumb-enter-active,
|
||||
//.breadcrumb-leave-active {
|
||||
// transition: all 0.2s ease;
|
||||
//}
|
||||
//.breadcrumb-enter-from,
|
||||
//.breadcrumb-leave-active {
|
||||
// opacity: 0;
|
||||
// transform: translateX(10px);
|
||||
//}
|
||||
//.breadcrumb-leave-active {
|
||||
// position: absolute;
|
||||
// z-index: -1;
|
||||
//}
|
||||
|
||||
|
||||
.fade-transform-leave-active,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,56 @@
|
|||
|
||||
/**
|
||||
* 通过递归过滤异步路由表
|
||||
* @param routes asyncRoutes
|
||||
* @param roles
|
||||
*/
|
||||
export function filterAsyncRoutes(routes, roles) {
|
||||
const res = []
|
||||
routes.forEach(route => {
|
||||
const tmp = { ...route }
|
||||
if (hasPermission(roles, tmp)) {
|
||||
if (tmp.children) {
|
||||
tmp.children = filterAsyncRoutes(tmp.children, roles)
|
||||
}
|
||||
res.push(tmp)
|
||||
}
|
||||
})
|
||||
return res
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用 meta.role 来确定当前用户是否具有权限
|
||||
* @param roles
|
||||
* @param route
|
||||
*/
|
||||
export function hasPermission(roles, route) {
|
||||
if (route.meta && route.meta.roles) {
|
||||
return roles.some(role => route.meta.roles.includes(role))
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 使用递归,过滤需要缓存的路由
|
||||
* @param {Array} _route 所有路由表
|
||||
* @param {Array} _cache 缓存的路由表
|
||||
* @return void
|
||||
* */
|
||||
|
||||
|
||||
export function filterKeepAlive(routers){
|
||||
let cacheRouter: any[] = [];
|
||||
let deep = (routers)=>{
|
||||
routers.forEach(item=>{
|
||||
if(item.meta?.keepAlive&& item.name){
|
||||
cacheRouter.push(item.name)
|
||||
}
|
||||
if(item.children&&item.children.length){
|
||||
deep(item.children)
|
||||
}
|
||||
})
|
||||
}
|
||||
deep(routers)
|
||||
return cacheRouter
|
||||
}
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
<template>
|
||||
<div class="about">
|
||||
<h1>This is an about page</h1>
|
||||
</div>
|
||||
</template>
|
||||
|
|
@ -1,18 +0,0 @@
|
|||
<template>
|
||||
<div class="home">
|
||||
<img alt="Vue logo" src="../assets/logo.png" />
|
||||
<HelloWorld msg="Welcome to Your Vue.js + TypeScript App" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue'
|
||||
import HelloWorld from '@/components/HelloWorld.vue' // @ is an alias to /src
|
||||
|
||||
export default defineComponent({
|
||||
name: 'Home',
|
||||
components: {
|
||||
HelloWorld,
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
|
@ -2,11 +2,15 @@ $dark_gray: #889aa4;
|
|||
::v-deep(input) {
|
||||
background: transparent;
|
||||
border: 0;
|
||||
color: white;
|
||||
-webkit-appearance: none;
|
||||
border-radius: 0;
|
||||
padding: 12px 5px 12px 15px;
|
||||
height: 47px;
|
||||
}
|
||||
::v-deep(.el-input__wrapper){
|
||||
background: none;
|
||||
}
|
||||
.login-box {
|
||||
width: 80%;
|
||||
max-width: 500px;
|
||||
|
|
|
|||
|
|
@ -87,7 +87,6 @@ for (let i = 0; i < 100; i++) {
|
|||
}
|
||||
const column = [
|
||||
{ type: 'selection', width: 60 ,fixed: 'left'},
|
||||
{ name: 'id', label: 'id', width: 80,fixed: 'left' },
|
||||
{ name: 'name', label: '姓名', inSearch: true, valueType: 'input', width: 80 },
|
||||
{ name: 'age', label: '年龄', align: 'right' },
|
||||
{
|
||||
|
|
@ -107,16 +106,11 @@ const column = [
|
|||
],
|
||||
valueType: 'select',
|
||||
},
|
||||
{
|
||||
name: 'price',
|
||||
label: '价格',
|
||||
inSearch: true,
|
||||
valueType: 'input',
|
||||
},
|
||||
{name: 'price', label: '价格', inSearch: true, valueType: 'input',},
|
||||
{ name: 'admin', label: '账号', inSearch: true, valueType: 'input' },
|
||||
{ name: 'address', label: '地址', inSearch: true, valueType: 'input' },
|
||||
{ name: 'date', label: '日期', sorter: true, inSearch: true, valueType: 'input' },
|
||||
{ name: 'province', label: '省份' },
|
||||
{ name: 'address', label: '地址', inSearch: true, valueType: 'input' , width: 180},
|
||||
{ name: 'date', label: '日期', sorter: true, inSearch: true, valueType: 'input', width: 180 },
|
||||
{ name: 'province', label: '省份' , width: 100},
|
||||
{ name: 'city', label: '城市' },
|
||||
{ name: 'zip', label: '邮编' },
|
||||
{ name: 'operation', slot: true, fixed: 'right', width: 200,label: '操作' },
|
||||
|
|
|
|||
|
|
@ -37,9 +37,9 @@
|
|||
</el-table-column>
|
||||
<el-table-column prop="price" label="价格" />
|
||||
<el-table-column prop="admin" label="账号" />
|
||||
<el-table-column prop="address" label="地址" />
|
||||
<el-table-column prop="date" label="日期" />
|
||||
<el-table-column prop="province" label="省份" />
|
||||
<el-table-column prop="address" label="地址" width="180"/>
|
||||
<el-table-column prop="date" label="日期" width="180"/>
|
||||
<el-table-column prop="province" label="省份" width="120"/>
|
||||
<el-table-column prop="city" label="城市" />
|
||||
<el-table-column prop="operator" label="操作" width="180px" fixed="right">
|
||||
<template #default="scope">
|
||||
|
|
|
|||
Loading…
Reference in New Issue