perf:优化缓存,布局,样式

This commit is contained in:
zouzhibing 2022-09-28 17:08:12 +08:00
parent 4784ff379e
commit a985df0af7
16 changed files with 215 additions and 188 deletions

View File

@ -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
}

View File

@ -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;
}
}

View File

@ -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%;

View File

@ -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>

View File

@ -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 ==> falsebreadcrumb中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

View File

@ -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

27
src/router/modules/zip.ts Normal file
View File

@ -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

View File

@ -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
// },
})

View File

@ -7,13 +7,10 @@ body {
margin: 0;
padding: 0;
}
body{
background: #f6f8f9;
background: #f0f2f5;
}
/* 常用 flex */
.flex-center {
display: flex;

View File

@ -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,

56
src/utils/routers.ts Normal file
View File

@ -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
}

View File

@ -1,5 +0,0 @@
<template>
<div class="about">
<h1>This is an about page</h1>
</div>
</template>

View File

@ -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>

View File

@ -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;

View File

@ -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: '操作' },

View File

@ -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">