refactor: 样式优化

This commit is contained in:
meilin.huang
2025-08-04 21:02:27 +08:00
parent 7d344c71e1
commit 614a144f60
38 changed files with 274 additions and 313 deletions

View File

@@ -23,7 +23,7 @@
"clipboard": "^2.0.11",
"crypto-js": "^4.2.0",
"dayjs": "^1.11.13",
"echarts": "^5.6.0",
"echarts": "^6.0.0",
"element-plus": "^2.10.5",
"js-base64": "^3.7.7",
"jsencrypt": "^3.3.2",

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.9 KiB

After

Width:  |  Height:  |  Size: 9.4 KiB

View File

@@ -1,35 +1,34 @@
<template>
<el-config-provider :size="getGlobalComponentSize" :locale="getGlobalI18n">
<div class="h-full">
<el-watermark
:zIndex="100000"
:width="210"
v-if="themeConfig.isWatermark"
:font="{ color: 'rgba(180, 180, 180, 0.3)' }"
:content="themeConfig.watermarkText"
class="!h-full"
>
<router-view />
</el-watermark>
<router-view v-if="!themeConfig.isWatermark" />
<el-watermark
:zIndex="100000"
:width="210"
v-if="themeConfig.isWatermark"
:font="{ color: 'rgba(180, 180, 180, 0.3)' }"
:content="themeConfig.watermarkText"
class="!h-full"
>
<router-view />
</el-watermark>
<router-view v-if="!themeConfig.isWatermark" />
<Setings />
</div>
<Setings />
</el-config-provider>
</template>
<script setup lang="ts" name="app">
import { onMounted, nextTick, watch, computed } from 'vue';
import { onMounted, nextTick, watch, computed, defineAsyncComponent } from 'vue';
import { useRoute } from 'vue-router';
import { storeToRefs } from 'pinia';
import { useThemeConfig } from '@/store/themeConfig';
import Setings from '@/layout/navBars/breadcrumb/setings.vue';
import { useIntervalFn } from '@vueuse/core';
import { useI18n } from 'vue-i18n';
import EnumValue from './common/Enum';
import { I18nEnum } from './common/commonEnum';
import { saveThemeConfig } from './common/utils/storage';
const Setings = defineAsyncComponent(() => import('@/layout/navBars/breadcrumb/setings.vue'));
const route = useRoute();
const themeConfigStores = useThemeConfig();

View File

@@ -1 +1,9 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1621859009605" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="9709" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M820.203922 812.172549H684.67451v-45.176471h112.439215V279.090196H633.47451l-85.333334 277.082353c-3.011765 10.039216-12.047059 16.062745-22.086274 16.062745-10.039216 0-19.07451-7.027451-21.082353-17.066667l-71.278431-280.094117h-180.705883V762.980392h120.470589v45.176471H229.898039c-12.047059 0-22.086275-10.039216-22.086274-22.086275V252.988235c0-12.047059 10.039216-22.086275 22.086274-22.086274H451.764706c10.039216 0 19.07451 7.027451 22.086274 17.066666l55.215687 218.854902L595.32549 250.980392c3.011765-9.035294 12.047059-16.062745 21.082353-16.062745h202.792157c12.047059 0 22.086275 10.039216 22.086275 22.086275v533.082353c1.003922 12.047059-9.035294 22.086275-21.082353 22.086274z m0 0" fill="#e25813" p-id="9710"></path><path d="M731.858824 425.662745c4.015686-12.047059-2.007843-25.098039-14.054902-29.113725-12.047059-4.015686-25.098039 2.007843-29.113726 14.054902L563.2 766.996078h-73.286275L371.45098 410.603922c-4.015686-12.047059-17.066667-18.070588-28.109804-14.054902-12.047059 4.015686-18.070588 17.066667-14.054901 28.109804l123.482352 371.45098c3.011765 9.035294 12.047059 15.058824 21.082353 15.058823h72.282353l-53.207843 160.627451 46.180392 2.007844 192.752942-548.141177z" fill="#2c2c2c" p-id="9711"></path></svg>
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg t="1621859009605" class="icon" viewBox="0 0 1024 1024" version="1.1"
xmlns="http://www.w3.org/2000/svg" p-id="9709" xmlns:xlink="http://www.w3.org/1999/xlink"
width="200" height="200">
<defs><style type="text/css"></style></defs>
<path d="M820.203922 812.172549H684.67451v-45.176471h112.439215V279.090196H633.47451l-85.333334 277.082353c-3.011765 10.039216-12.047059 16.062745-22.086274 16.062745-10.039216 0-19.07451-7.027451-21.082353-17.066667l-71.278431-280.094117h-180.705883V762.980392h120.470589v45.176471H229.898039c-12.047059 0-22.086275-10.039216-22.086274-22.086275V252.988235c0-12.047059 10.039216-22.086275 22.086274-22.086274H451.764706c10.039216 0 19.07451 7.027451 22.086274 17.066666l55.215687 218.854902L595.32549 250.980392c3.011765-9.035294 12.047059-16.062745 21.082353-16.062745h202.792157c12.047059 0 22.086275 10.039216 22.086275 22.086275v533.082353c1.003922 12.047059-9.035294 22.086275-21.082353 22.086274z m0 0" fill="#e25813" p-id="9710" stroke-width="30" stroke="#e25813"></path>
<path d="M731.858824 425.662745c4.015686-12.047059-2.007843-25.098039-14.054902-29.113725-12.047059-4.015686-25.098039 2.007843-29.113726 14.054902L563.2 766.996078h-73.286275L371.45098 410.603922c-4.015686-12.047059-17.066667-18.070588-28.109804-14.054902-12.047059 4.015686-18.070588 17.066667-14.054901 28.109804l123.482352 371.45098c3.011765 9.035294 12.047059 15.058824 21.082353 15.058823h72.282353l-53.207843 160.627451 46.180392 2.007844 192.752942-548.141177z" fill="#2c2c2c" p-id="9711" stroke-width="30" stroke="#2c2c2c"></path>
</svg>

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

@@ -16,15 +16,16 @@
</template>
<script lang="ts" setup name="layoutAside">
import { reactive, computed, watch, getCurrentInstance, onBeforeMount, onUnmounted, inject } from 'vue';
import { reactive, computed, watch, getCurrentInstance, onBeforeMount, inject, defineAsyncComponent } from 'vue';
import pinia from '@/store/index';
import { storeToRefs } from 'pinia';
import { useThemeConfig } from '@/store/themeConfig';
import { useRoutesList } from '@/store/routesList';
import Logo from '@/layout/logo/index.vue';
import Vertical from '@/layout/navMenu/vertical.vue';
import { useWindowSize } from '@vueuse/core';
const Logo = defineAsyncComponent(() => import('@/layout/logo/index.vue'));
const Vertical = defineAsyncComponent(() => import('@/layout/navMenu/vertical.vue'));
const { proxy } = getCurrentInstance() as any;
const { themeConfig } = storeToRefs(useThemeConfig());

View File

@@ -103,9 +103,14 @@ const setFilterRoutes = () => {
state.columnsAsideList = filterRoutesFun(useRoutesList().routesList);
const resData: any = setSendChildren(route.path);
onColumnsAsideDown(resData.item[0].k);
if (columnsMenuData) {
columnsMenuData.value = resData;
}
nextTick(() => {
setTimeout(() => {
if (columnsMenuData) {
columnsMenuData.value = resData;
}
}, 300);
});
};
// 传送当前子级数据到菜单中
const setSendChildren = (path: string) => {

View File

@@ -1,26 +1,11 @@
<template>
<el-header class="layout-header" :height="setHeaderHeight">
<el-header class="layout-header">
<NavBarsIndex />
</el-header>
</template>
<script lang="ts">
import { computed } from 'vue';
import NavBarsIndex from '@/layout/navBars/index.vue';
import { useThemeConfig } from '@/store/themeConfig';
export default {
name: 'layoutHeader',
components: { NavBarsIndex },
setup() {
// 设置 header 的高度
const setHeaderHeight = computed(() => {
let { isTagsview, layout } = useThemeConfig().themeConfig;
if (isTagsview && layout !== 'classic') return '84px';
else return '50px';
});
return {
setHeaderHeight,
};
},
};
<script setup lang="ts" name="layoutHeader">
import { defineAsyncComponent } from 'vue';
const NavBarsIndex = defineAsyncComponent(() => import('@/layout/navBars/index.vue'));
</script>

View File

@@ -3,6 +3,8 @@
<el-scrollbar ref="layoutScrollbarRef" view-class="!h-full">
<LayoutParentView />
</el-scrollbar>
<el-backtop target=".layout-backtop .el-main .el-scrollbar__wrap"></el-backtop>
</el-main>
<el-footer v-if="themeConfig.isFooter">

View File

@@ -1,17 +1,18 @@
<template>
<Defaults v-if="themeConfig.layout === 'defaults'" />
<Classic v-else-if="themeConfig.layout === 'classic'" />
<Transverse v-else-if="themeConfig.layout === 'transverse'" />
<Columns v-else-if="themeConfig.layout === 'columns'" />
<component :is="layouts[themeConfig.layout]" />
</template>
<script setup lang="ts" name="layout">
import { storeToRefs } from 'pinia';
import { useThemeConfig } from '@/store/themeConfig';
import Defaults from '@/layout/main/defaults.vue';
import Classic from '@/layout/main/classic.vue';
import Transverse from '@/layout/main/transverse.vue';
import Columns from '@/layout/main/columns.vue';
import { defineAsyncComponent } from 'vue';
const layouts: any = {
defaults: defineAsyncComponent(() => import('@/layout/main/defaults.vue')),
classic: defineAsyncComponent(() => import('@/layout/main/classic.vue')),
transverse: defineAsyncComponent(() => import('@/layout/main/transverse.vue')),
columns: defineAsyncComponent(() => import('@/layout/main/columns.vue')),
};
const { themeConfig } = storeToRefs(useThemeConfig());
</script>

View File

@@ -1,7 +1,7 @@
<template>
<div class="layout-logo" v-if="setShowLogo" @click="onThemeConfigChange">
<img :src="themeConfig.logoIcon" class="layout-logo-medium-img" />
<span>
<span class="logo-title">
{{ `${themeConfig.globalTitle}` }}
<sub
><span style="font-size: 10px; color: goldenrod">{{ ` ${config.version}` }}</span></sub
@@ -53,8 +53,17 @@ const onThemeConfigChange = () => {
}
&-medium-img {
width: 20px;
margin-right: 5px;
width: 24px;
height: 24px;
margin-right: 8px;
}
.logo-title {
font-weight: 600;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
max-width: calc(100% - 32px);
}
}
@@ -64,10 +73,12 @@ const onThemeConfigChange = () => {
display: flex;
cursor: pointer;
animation: logoAnimation 0.3s ease-in-out;
justify-content: center;
align-items: center;
&-img {
width: 20px;
margin: auto;
width: 24px;
height: 24px;
}
&:hover {

View File

@@ -8,18 +8,18 @@
<Main />
</div>
</el-container>
<el-backtop target=".layout-backtop .el-main .el-scrollbar__wrap"></el-backtop>
</el-container>
</template>
<script lang="ts" setup name="layoutClassic">
import { storeToRefs } from 'pinia';
import { useThemeConfig } from '@/store/themeConfig';
import Aside from '@/layout/component/aside.vue';
import Header from '@/layout/component/header.vue';
import Main from '@/layout/component/main.vue';
import TagsView from '@/layout/navBars/tagsView/tagsView.vue';
import { provide, ref } from 'vue';
import { defineAsyncComponent, provide, ref } from 'vue';
const Aside = defineAsyncComponent(() => import('@/layout/component/aside.vue'));
const Header = defineAsyncComponent(() => import('@/layout/component/header.vue'));
const Main = defineAsyncComponent(() => import('@/layout/component/main.vue'));
const TagsView = defineAsyncComponent(() => import('@/layout/navBars/tagsView/tagsView.vue'));
const { themeConfig } = storeToRefs(useThemeConfig());

View File

@@ -9,18 +9,18 @@
<Main />
</el-container>
</div>
<el-backtop target=".layout-backtop .el-scrollbar__wrap"></el-backtop>
</el-container>
</template>
<script lang="ts" setup name="layoutColumns">
import { computed, provide, ref } from 'vue';
import Aside from '@/layout/component/aside.vue';
import Header from '@/layout/component/header.vue';
import Main from '@/layout/component/main.vue';
import ColumnsAside from '@/layout/component/columnsAside.vue';
import { computed, defineAsyncComponent, provide, ref } from 'vue';
import { useThemeConfig } from '@/store/themeConfig';
const Aside = defineAsyncComponent(() => import('@/layout/component/aside.vue'));
const Header = defineAsyncComponent(() => import('@/layout/component/header.vue'));
const Main = defineAsyncComponent(() => import('@/layout/component/main.vue'));
const ColumnsAside = defineAsyncComponent(() => import('@/layout/component/columnsAside.vue'));
// 提供响应式数据给子组件
const columnsMenuData = ref<any>(null);
provide('columnsMenuData', columnsMenuData);

View File

@@ -6,18 +6,18 @@
<Header v-if="!isFixedHeader" />
<Main />
</el-container>
<el-backtop target=".layout-backtop .el-scrollbar__wrap"></el-backtop>
</el-container>
</template>
<script lang="ts" setup name="layoutDefaults">
import { computed, getCurrentInstance, watch } from 'vue';
import { computed, defineAsyncComponent, getCurrentInstance, watch } from 'vue';
import { useRoute } from 'vue-router';
import Aside from '@/layout/component/aside.vue';
import Header from '@/layout/component/header.vue';
import Main from '@/layout/component/main.vue';
import { useThemeConfig } from '@/store/themeConfig';
const Aside = defineAsyncComponent(() => import('@/layout/component/aside.vue'));
const Header = defineAsyncComponent(() => import('@/layout/component/header.vue'));
const Main = defineAsyncComponent(() => import('@/layout/component/main.vue'));
const { proxy } = getCurrentInstance() as any;
const route = useRoute();
const isFixedHeader = computed(() => {

View File

@@ -2,11 +2,12 @@
<el-container class="layout-container flex-center layout-backtop">
<Header />
<Main />
<el-backtop target=".layout-backtop .el-main .el-scrollbar__wrap"></el-backtop>
</el-container>
</template>
<script lang="ts" setup name="layoutTransverse">
import Header from '@/layout/component/header.vue';
import Main from '@/layout/component/main.vue';
import { defineAsyncComponent } from 'vue';
const Main = defineAsyncComponent(() => import('@/layout/component/main.vue'));
const Header = defineAsyncComponent(() => import('@/layout/component/header.vue'));
</script>

View File

@@ -1,15 +1,19 @@
<template>
<div class="layout-navbars-breadcrumb" v-show="themeConfig.isBreadcrumb">
<SvgIcon class="layout-navbars-breadcrumb-icon" :name="themeConfig.isCollapse ? 'expand' : 'fold'" @click="onThemeConfigChange" />
<div class="flex flex-1 h-inherit items-center pl-4" v-show="themeConfig.isBreadcrumb">
<SvgIcon
class="cursor-pointer text-18px mr-4 text-[var(--bg-topBarColor)]"
:name="themeConfig.isCollapse ? 'expand' : 'fold'"
@click="onThemeConfigChange"
/>
<el-breadcrumb class="layout-navbars-breadcrumb-hide">
<transition-group name="breadcrumb">
<el-breadcrumb-item v-for="(v, k) in state.breadcrumbList" :key="v.meta.title">
<span v-if="k === state.breadcrumbList.length - 1" class="layout-navbars-breadcrumb-span">
<SvgIcon :name="v.meta.icon" class="layout-navbars-breadcrumb-iconfont" v-if="themeConfig.isBreadcrumbIcon" />
<span v-if="k === state.breadcrumbList.length - 1 || (!v.redirect && !v.component)" class="opacity-70 text-[var(--bg-topBarColor)]">
<SvgIcon :name="v.meta.icon" class="text-14px mr-1.25" v-if="themeConfig.isBreadcrumbIcon" />
{{ $t(v.meta.title) }}
</span>
<a v-else @click.prevent="onBreadcrumbClick(v)">
<SvgIcon :name="v.meta.icon" class="layout-navbars-breadcrumb-iconfont" v-if="themeConfig.isBreadcrumbIcon" />
<a v-else @click.prevent="onBreadcrumbClick(v)" class="opacity-100 text-[var(--bg-topBarColor)] hover:opacity-100">
<SvgIcon :name="v.meta.icon" class="text-14px mr-1.25" v-if="themeConfig.isBreadcrumbIcon" />
{{ $t(v.meta.title) }}
</a>
</el-breadcrumb-item>
@@ -29,84 +33,99 @@ const { themeConfig } = storeToRefs(useThemeConfig());
const { routesList } = storeToRefs(useRoutesList());
const route = useRoute();
const router = useRouter();
const state: any = reactive({
breadcrumbList: [],
routeSplit: [],
routeSplitFirst: '',
routeSplitIndex: 1,
const state = reactive({
breadcrumbList: [] as any[],
});
// 面包屑点击时
const onBreadcrumbClick = (v: any) => {
const { redirect, path } = v;
if (redirect) router.push(redirect);
else router.push(path);
if (redirect) {
router.push(redirect);
return;
}
if (v.component) {
router.push(path);
}
};
// 展开/收起左侧菜单点击
const onThemeConfigChange = () => {
themeConfig.value.isCollapse = !themeConfig.value.isCollapse;
};
// 处理面包屑数据
const getBreadcrumbList = (arr: Array<object>) => {
arr.map((item: any) => {
state.routeSplit.map((v: any, k: number, arrs: any) => {
if (state.routeSplitFirst === item.path) {
state.routeSplitFirst += `/${arrs[state.routeSplitIndex]}`;
state.breadcrumbList.push(item);
state.routeSplitIndex++;
if (item.children) getBreadcrumbList(item.children);
// 根据当前路径生成面包屑列表
const generateBreadcrumbList = (currentPath: string) => {
if (!themeConfig.value.isBreadcrumb) {
return;
}
// 初始化面包屑列表,包含首页
const homeRoute = routesList.value.length > 0 ? routesList.value[0] : null;
const breadcrumbList = homeRoute ? [homeRoute] : [];
// 查找匹配的路由及其所有父级路由(除了首页)
if (homeRoute && currentPath !== homeRoute.path) {
const matchedRoutes = findMatchedRoutes(routesList.value, currentPath);
// 如果找到匹配的路由,添加到面包屑列表中(排除首页,避免重复)
if (matchedRoutes.length > 0) {
// 过滤掉首页路由,避免重复添加
const filteredRoutes = matchedRoutes.filter((r) => r !== homeRoute);
breadcrumbList.push(...filteredRoutes);
}
}
state.breadcrumbList = breadcrumbList;
};
// 在路由树中查找匹配当前路径的路由,并返回该路由及其所有父级路由
const findMatchedRoutes = (routes: any[], currentPath: string): any[] => {
for (const route of routes) {
// 精确匹配
if (route.path === currentPath) {
return [route];
}
// 前缀匹配且有子路由
if (currentPath.startsWith(route.path + '/') && route.children) {
const matchedChildren = findMatchedRoutes(route.children, currentPath);
if (matchedChildren.length > 0) {
return [route, ...matchedChildren];
}
});
});
};
// 当前路由字符串切割成数组,并删除第一项空内容
const initRouteSplit = (path: string) => {
if (!themeConfig.value.isBreadcrumb) return false;
state.breadcrumbList = [routesList.value[0]];
state.routeSplit = path.split('/');
state.routeSplit.shift();
state.routeSplitFirst = `/${state.routeSplit[0]}`;
state.routeSplitIndex = 1;
getBreadcrumbList(routesList.value);
}
// 处理子路由匹配但当前路由是根路径的情况
if (route.path === '/' && route.children) {
const matchedChildren = findMatchedRoutes(route.children, currentPath);
if (matchedChildren.length > 0) {
return [route, ...matchedChildren];
}
}
// 递归查找子路由
if (route.children) {
const matchedChildren = findMatchedRoutes(route.children, currentPath);
if (matchedChildren.length > 0) {
return [route, ...matchedChildren];
}
}
}
return [];
};
// 页面加载时
onMounted(() => {
initRouteSplit(route.path);
generateBreadcrumbList(route.path);
});
// 路由更新时
onBeforeRouteUpdate((to) => {
initRouteSplit(to.path);
generateBreadcrumbList(to.path);
});
</script>
<style scoped lang="scss">
.layout-navbars-breadcrumb {
flex: 1;
height: inherit;
display: flex;
align-items: center;
padding-left: 15px;
.layout-navbars-breadcrumb-icon {
cursor: pointer;
font-size: 18px;
margin-right: 15px;
color: var(--bg-topBarColor);
}
.layout-navbars-breadcrumb-span {
opacity: 0.7;
color: var(--bg-topBarColor);
}
.layout-navbars-breadcrumb-iconfont {
font-size: 14px;
margin-right: 5px;
}
::v-deep(.el-breadcrumb__separator) {
opacity: 0.7;
color: var(--bg-topBarColor);
}
<style scoped>
::v-deep(.el-breadcrumb__separator) {
opacity: 0.7;
color: var(--bg-topBarColor);
}
</style>

View File

@@ -8,16 +8,17 @@
</template>
<script lang="ts" setup name="layoutBreadcrumbIndex">
import { computed, reactive, onMounted, onUnmounted, watch } from 'vue';
import { computed, reactive, onMounted, watch, defineAsyncComponent } from 'vue';
import { useRoute } from 'vue-router';
import pinia from '@/store/index';
import { storeToRefs } from 'pinia';
import { useThemeConfig } from '@/store/themeConfig';
import { useRoutesList } from '@/store/routesList';
import Breadcrumb from '@/layout/navBars/breadcrumb/breadcrumb.vue';
import User from '@/layout/navBars/breadcrumb/user.vue';
import Logo from '@/layout/logo/index.vue';
import Horizontal from '@/layout/navMenu/horizontal.vue';
const Breadcrumb = defineAsyncComponent(() => import('@/layout/navBars/breadcrumb/breadcrumb.vue'));
const User = defineAsyncComponent(() => import('@/layout/navBars/breadcrumb/user.vue'));
const Logo = defineAsyncComponent(() => import('@/layout/logo/index.vue'));
const Horizontal = defineAsyncComponent(() => import('@/layout/navMenu/horizontal.vue'));
const { themeConfig } = storeToRefs(useThemeConfig());
const { routesList } = storeToRefs(useRoutesList());

View File

@@ -5,25 +5,17 @@
</div>
</template>
<script lang="ts">
<script setup lang="ts" name="layoutNavBars">
import { computed } from 'vue';
import { useThemeConfig } from '@/store/themeConfig';
import BreadcrumbIndex from '@/layout/navBars/breadcrumb/index.vue';
import TagsView from '@/layout/navBars/tagsView/tagsView.vue';
export default {
name: 'layoutNavBars',
components: { BreadcrumbIndex, TagsView },
setup() {
// 是否显示 tagsView
const setShowTagsView = computed(() => {
let { layout, isTagsview } = useThemeConfig().themeConfig;
return layout !== 'classic' && isTagsview;
});
return {
setShowTagsView,
};
},
};
// 是否显示 tagsView
const setShowTagsView = computed(() => {
let { layout, isTagsview } = useThemeConfig().themeConfig;
return layout !== 'classic' && isTagsview;
});
</script>
<style scoped lang="scss">

View File

@@ -34,12 +34,13 @@
</template>
<script lang="ts" setup name="navMenuHorizontal">
import { reactive, computed, onMounted, inject } from 'vue';
import { reactive, computed, onMounted, inject, defineAsyncComponent } from 'vue';
import { useRoute, onBeforeRouteUpdate } from 'vue-router';
import SubItem from '@/layout/navMenu/subItem.vue';
import { useRoutesList } from '@/store/routesList';
import { useThemeConfig } from '@/store/themeConfig';
const SubItem = defineAsyncComponent(() => import('@/layout/navMenu/subItem.vue'));
// 定义父组件传过来的值
const props = defineProps({
// 菜单列表

View File

@@ -22,24 +22,20 @@
</template>
</template>
<script lang="ts">
import { computed, defineComponent } from 'vue';
export default defineComponent({
name: 'navMenuSubItem',
props: {
chil: {
type: Array,
default: () => [],
},
},
setup(props) {
// 获取父级菜单数据
const chils = computed(() => {
return props.chil as any;
});
return {
chils,
};
},
<script setup lang="ts" name="navMenuSubItem">
import { computed } from 'vue';
// 定义 props
interface Props {
chil?: any[];
}
const props = withDefaults(defineProps<Props>(), {
chil: () => [],
});
// 获取父级菜单数据
const chils = computed(() => {
return props.chil as any;
});
</script>

View File

@@ -29,11 +29,12 @@
</template>
<script lang="ts" setup name="navMenuVertical">
import { reactive, computed } from 'vue';
import { reactive, computed, defineAsyncComponent } from 'vue';
import { storeToRefs } from 'pinia';
import { useThemeConfig } from '@/store/themeConfig';
import { useRoute, onBeforeRouteUpdate } from 'vue-router';
import SubItem from '@/layout/navMenu/subItem.vue';
const SubItem = defineAsyncComponent(() => import('@/layout/navMenu/subItem.vue'));
// 定义父组件传过来的值
const props = defineProps({

View File

@@ -31,9 +31,6 @@ const state = reactive({
// 立即前往
const onGotoFullPage = () => {
window.open(state.link);
// const { origin, pathname } = window.location;
// if (verifyUrl(<string>state.isLink)) window.open(state.isLink);
// else window.open(`${origin}${pathname}#${state.isLink}`);
};
// 监听路由的变化,设置内容

View File

@@ -9,6 +9,9 @@ import { RouteRecordRaw } from 'vue-router';
import { LAYOUT_ROUTE_NAME } from './staticRouter';
import { LinkTypeEnum } from '@/common/commonEnum';
const Link = () => import('@/layout/routerView/link.vue');
const Iframe = () => import('@/layout/routerView/iframes.vue');
/**
* 获取目录下的 route.ts 全部文件
* @method import.meta.glob
@@ -125,9 +128,9 @@ export function backEndRouterConverter(allModuleRoutes: any, routes: any, callba
// 如果是外链类型name的路由名都是Link 或者 Iframes会导致路由名重复无法添加多个外链
if (item.meta.link) {
if (item.meta.linkType == LinkTypeEnum.Link.value) {
item.component = () => import('@/layout/routerView/link.vue');
item.component = Link;
} else {
item.component = () => import('@/layout/routerView/iframes.vue');
item.component = Iframe;
}
} else {
// routerName == 模块下route.ts 字段key == 组件名

View File

@@ -6,7 +6,7 @@ import { getLocal, getThemeConfig } from '@/common/utils/storage';
// 系统默认logo图标对应于@/assets/image/logo.svg
const logoIcon =
'data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBzdGFuZGFsb25lPSJubyI/PjwhRE9DVFlQRSBzdmcgUFVCTElDICItLy9XM0MvL0RURCBTVkcgMS4xLy9FTiIgImh0dHA6Ly93d3cudzMub3JnL0dyYXBoaWNzL1NWRy8xLjEvRFREL3N2ZzExLmR0ZCI+PHN2ZyB0PSIxNjIxODU5MDA5NjA1IiBjbGFzcz0iaWNvbiIgdmlld0JveD0iMCAwIDEwMjQgMTAyNCIgdmVyc2lvbj0iMS4xIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHAtaWQ9Ijk3MDkiIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB3aWR0aD0iMjAwIiBoZWlnaHQ9IjIwMCI+PGRlZnM+PHN0eWxlIHR5cGU9InRleHQvY3NzIj48L3N0eWxlPjwvZGVmcz48cGF0aCBkPSJNODIwLjIwMzkyMiA4MTIuMTcyNTQ5SDY4NC42NzQ1MXYtNDUuMTc2NDcxaDExMi40MzkyMTVWMjc5LjA5MDE5Nkg2MzMuNDc0NTFsLTg1LjMzMzMzNCAyNzcuMDgyMzUzYy0zLjAxMTc2NSAxMC4wMzkyMTYtMTIuMDQ3MDU5IDE2LjA2Mjc0NS0yMi4wODYyNzQgMTYuMDYyNzQ1LTEwLjAzOTIxNiAwLTE5LjA3NDUxLTcuMDI3NDUxLTIxLjA4MjM1My0xNy4wNjY2NjdsLTcxLjI3ODQzMS0yODAuMDk0MTE3aC0xODAuNzA1ODgzVjc2Mi45ODAzOTJoMTIwLjQ3MDU4OXY0NS4xNzY0NzFIMjI5Ljg5ODAzOWMtMTIuMDQ3MDU5IDAtMjIuMDg2Mjc1LTEwLjAzOTIxNi0yMi4wODYyNzQtMjIuMDg2Mjc1VjI1Mi45ODgyMzVjMC0xMi4wNDcwNTkgMTAuMDM5MjE2LTIyLjA4NjI3NSAyMi4wODYyNzQtMjIuMDg2Mjc0SDQ1MS43NjQ3MDZjMTAuMDM5MjE2IDAgMTkuMDc0NTEgNy4wMjc0NTEgMjIuMDg2Mjc0IDE3LjA2NjY2Nmw1NS4yMTU2ODcgMjE4Ljg1NDkwMkw1OTUuMzI1NDkgMjUwLjk4MDM5MmMzLjAxMTc2NS05LjAzNTI5NCAxMi4wNDcwNTktMTYuMDYyNzQ1IDIxLjA4MjM1My0xNi4wNjI3NDVoMjAyLjc5MjE1N2MxMi4wNDcwNTkgMCAyMi4wODYyNzUgMTAuMDM5MjE2IDIyLjA4NjI3NSAyMi4wODYyNzV2NTMzLjA4MjM1M2MxLjAwMzkyMiAxMi4wNDcwNTktOS4wMzUyOTQgMjIuMDg2Mjc1LTIxLjA4MjM1MyAyMi4wODYyNzR6IG0wIDAiIGZpbGw9IiNlMjU4MTMiIHAtaWQ9Ijk3MTAiPjwvcGF0aD48cGF0aCBkPSJNNzMxLjg1ODgyNCA0MjUuNjYyNzQ1YzQuMDE1Njg2LTEyLjA0NzA1OS0yLjAwNzg0My0yNS4wOTgwMzktMTQuMDU0OTAyLTI5LjExMzcyNS0xMi4wNDcwNTktNC4wMTU2ODYtMjUuMDk4MDM5IDIuMDA3ODQzLTI5LjExMzcyNiAxNC4wNTQ5MDJMNTYzLjIgNzY2Ljk5NjA3OGgtNzMuMjg2Mjc1TDM3MS40NTA5OCA0MTAuNjAzOTIyYy00LjAxNTY4Ni0xMi4wNDcwNTktMTcuMDY2NjY3LTE4LjA3MDU4OC0yOC4xMDk4MDQtMTQuMDU0OTAyLTEyLjA0NzA1OSA0LjAxNTY4Ni0xOC4wNzA1ODggMTcuMDY2NjY3LTE0LjA1NDkwMSAyOC4xMDk4MDRsMTIzLjQ4MjM1MiAzNzEuNDUwOThjMy4wMTE3NjUgOS4wMzUyOTQgMTIuMDQ3MDU5IDE1LjA1ODgyNCAyMS4wODIzNTMgMTUuMDU4ODIzaDcyLjI4MjM1M2wtNTMuMjA3ODQzIDE2MC42Mjc0NTEgNDYuMTgwMzkyIDIuMDA3ODQ0IDE5Mi43NTI5NDItNTQ4LjE0MTE3N3oiIGZpbGw9IiMyYzJjMmMiIHAtaWQ9Ijk3MTEiPjwvcGF0aD48L3N2Zz4=';
'data:image/svg+xml;charset=utf-8;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBzdGFuZGFsb25lPSJubyI/Pgo8IURPQ1RZUEUgc3ZnIFBVQkxJQyAiLS8vVzNDLy9EVEQgU1ZHIDEuMS8vRU4iICJodHRwOi8vd3d3LnczLm9yZy9HcmFwaGljcy9TVkcvMS4xL0RURC9zdmcxMS5kdGQiPgo8c3ZnIHQ9IjE2MjE4NTkwMDk2MDUiIGNsYXNzPSJpY29uIiB2aWV3Qm94PSIwIDAgMTAyNCAxMDI0IiB2ZXJzaW9uPSIxLjEiIAogICAgIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgcC1pZD0iOTcwOSIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIAogICAgIHdpZHRoPSIyMDAiIGhlaWdodD0iMjAwIj4KICAgICA8ZGVmcz48c3R5bGUgdHlwZT0idGV4dC9jc3MiPjwvc3R5bGU+PC9kZWZzPgogICAgIDxwYXRoIGQ9Ik04MjAuMjAzOTIyIDgxMi4xNzI1NDlINjg0LjY3NDUxdi00NS4xNzY0NzFoMTEyLjQzOTIxNVYyNzkuMDkwMTk2SDYzMy40NzQ1MWwtODUuMzMzMzM0IDI3Ny4wODIzNTNjLTMuMDExNzY1IDEwLjAzOTIxNi0xMi4wNDcwNTkgMTYuMDYyNzQ1LTIyLjA4NjI3NCAxNi4wNjI3NDUtMTAuMDM5MjE2IDAtMTkuMDc0NTEtNy4wMjc0NTEtMjEuMDgyMzUzLTE3LjA2NjY2N2wtNzEuMjc4NDMxLTI4MC4wOTQxMTdoLTE4MC43MDU4ODNWNzYyLjk4MDM5MmgxMjAuNDcwNTg5djQ1LjE3NjQ3MUgyMjkuODk4MDM5Yy0xMi4wNDcwNTkgMC0yMi4wODYyNzUtMTAuMDM5MjE2LTIyLjA4NjI3NC0yMi4wODYyNzVWMjUyLjk4ODIzNWMwLTEyLjA0NzA1OSAxMC4wMzkyMTYtMjIuMDg2Mjc1IDIyLjA4NjI3NC0yMi4wODYyNzRINDUxLjc2NDcwNmMxMC4wMzkyMTYgMCAxOS4wNzQ1MSA3LjAyNzQ1MSAyMi4wODYyNzQgMTcuMDY2NjY2bDU1LjIxNTY4NyAyMTguODU0OTAyTDU5NS4zMjU0OSAyNTAuOTgwMzkyYzMuMDExNzY1LTkuMDM1Mjk0IDEyLjA0NzA1OS0xNi4wNjI3NDUgMjEuMDgyMzUzLTE2LjA2Mjc0NWgyMDIuNzkyMTU3YzEyLjA0NzA1OSAwIDIyLjA4NjI3NSAxMC4wMzkyMTYgMjIuMDg2Mjc1IDIyLjA4NjI3NXY1MzMuMDgyMzUzYzEuMDAzOTIyIDEyLjA0NzA1OS05LjAzNTI5NCAyMi4wODYyNzUtMjEuMDgyMzUzIDIyLjA4NjI3NHogbTAgMCIgZmlsbD0iI2UyNTgxMyIgcC1pZD0iOTcxMCIgc3Ryb2tlLXdpZHRoPSIzMCIgc3Ryb2tlPSIjZTI1ODEzIj48L3BhdGg+CiAgICAgPHBhdGggZD0iTTczMS44NTg4MjQgNDI1LjY2Mjc0NWM0LjAxNTY4Ni0xMi4wNDcwNTktMi4wMDc4NDMtMjUuMDk4MDM5LTE0LjA1NDkwMi0yOS4xMTM3MjUtMTIuMDQ3MDU5LTQuMDE1Njg2LTI1LjA5ODAzOSAyLjAwNzg0My0yOS4xMTM3MjYgMTQuMDU0OTAyTDU2My4yIDc2Ni45OTYwNzhoLTczLjI4NjI3NUwzNzEuNDUwOTggNDEwLjYwMzkyMmMtNC4wMTU2ODYtMTIuMDQ3MDU5LTE3LjA2NjY2Ny0xOC4wNzA1ODgtMjguMTA5ODA0LTE0LjA1NDkwMi0xMi4wNDcwNTkgNC4wMTU2ODYtMTguMDcwNTg4IDE3LjA2NjY2Ny0xNC4wNTQ5MDEgMjguMTA5ODA0bDEyMy40ODIzNTIgMzcxLjQ1MDk4YzMuMDExNzY1IDkuMDM1Mjk0IDEyLjA0NzA1OSAxNS4wNTg4MjQgMjEuMDgyMzUzIDE1LjA1ODgyM2g3Mi4yODIzNTNsLTUzLjIwNzg0MyAxNjAuNjI3NDUxIDQ2LjE4MDM5MiAyLjAwNzg0NCAxOTIuNzUyOTQyLTU0OC4xNDExNzd6IiBmaWxsPSIjMmMyYzJjIiBwLWlkPSI5NzExIiBzdHJva2Utd2lkdGg9IjMwIiBzdHJva2U9IiMyYzJjMmMiPjwvcGF0aD4KPC9zdmc+';
export const useThemeConfig = defineStore('themeConfig', {
state: (): ThemeConfigState => ({

View File

@@ -60,6 +60,7 @@ body,
.layout-header {
padding: 0 !important;
height: auto !important;
}
.layout-main {

View File

@@ -1,34 +1,5 @@
@use 'mixins/index' as mixins;
/* Button 按钮
------------------------------- */
/* Input 输入框、InputNumber 计数器
------------------------------- */
// 菜单搜索
.el-autocomplete-suggestion__wrap {
max-height: 280px !important;
}
/* Alert 警告
------------------------------- */
.el-alert {
border: 1px solid;
}
.el-alert__title {
word-break: break-all;
}
/* Message 消息提示
------------------------------- */
.el-message {
min-width: unset !important;
padding: 15px !important;
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.02);
}
/* NavMenu 导航菜单
------------------------------- */
$radius: 6px;
@@ -306,9 +277,6 @@ html.dark {
/* Tabs 标签页
------------------------------- */
.el-tabs__nav-wrap::after {
height: 1px !important;
}
/* Dropdown 下拉菜单
------------------------------- */
@@ -326,8 +294,6 @@ html.dark {
}
}
/* Dialog 对话框
------------------------------- */
/* Card 卡片
------------------------------- */
@@ -335,62 +301,6 @@ html.dark {
padding: 15px 20px;
}
/* Table 表格 element plus 2.2.0 版本
------------------------------- */
.el-table {
.el-button.is-text {
padding: 0;
}
}
/* scrollbar
------------------------------- */
.el-scrollbar__bar {
z-index: 4;
}
/*防止页面切换时,滚动条高度不变的问题(滚动条高度非滚动条滚动高度)*/
.el-scrollbar__wrap {
max-height: 100%;
}
.el-select-dropdown .el-scrollbar__wrap {
overflow-x: scroll !important;
}
/*修复Select 选择器高度问题*/
.el-select-dropdown__wrap {
max-height: 274px !important;
}
/*修复Cascader 级联选择器高度问题*/
.el-cascader-menu__wrap.el-scrollbar__wrap {
height: 204px !important;
}
/*用于界面高度自适应main.vue区分 scrollbar__view防止其它使用 scrollbar 的地方出现滚动条消失*/
.layout-container-view .el-scrollbar__view {
height: 100%;
}
/*防止分栏布局二级菜单很多时,滚动条消失问题*/
.layout-columns-warp .layout-aside .el-scrollbar__view {
height: unset !important;
}
/* Pagination 分页
------------------------------- */
.el-pagination__editor {
margin-right: 8px;
}
/*深色模式时分页高亮问题*/
.el-pagination.is-background .btn-next.is-active,
.el-pagination.is-background .btn-prev.is-active,
.el-pagination.is-background .el-pager li.is-active {
background-color: var(--el-color-primary) !important;
color: var(--el-color-white) !important;
}
/* Breadcrumb 面包屑
------------------------------- */
@@ -418,6 +328,8 @@ html.dark {
}
/* Dialog 对话框
------------------------------- */
.el-dialog {
border-radius: 6px;
/* 设置圆角 */

View File

@@ -17,7 +17,7 @@
</el-form-item>
<el-form-item prop="type" :label="$t('common.type')" required>
<el-select @change="changeDbType" style="width: 100%" v-model="form.type">
<el-select @change="changeDbType" v-model="form.type">
<el-option
v-for="(dbTypeAndDialect, key) in getDbDialectMap()"
:key="key"
@@ -34,11 +34,15 @@
</el-select>
</el-form-item>
<el-form-item v-if="form.type !== DbType.sqlite" prop="host" label="Host" required>
<el-form-item v-if="form.type !== DbType.sqlite" label="Host" required>
<el-col :span="18">
<el-input v-model.trim="form.host" auto-complete="off"></el-input>
<el-form-item prop="host" required>
<el-input v-model.trim="form.host" auto-complete="off"></el-input>
</el-form-item>
</el-col>
<el-col style="text-align: center" :span="1">:</el-col>
<el-col class="text-center" :span="1">:</el-col>
<el-col :span="5">
<el-input type="number" v-model.number="form.port" :placeholder="$t('db.port')"></el-input>
</el-col>

View File

@@ -272,10 +272,7 @@ import { formatByteSize, formatDate } from '@/common/utils/format';
import { TagResourceTypePath } from '@/common/commonEnum';
import { SearchItem } from '@/components/pagetable/SearchForm';
import { getTagPathSearchItem } from '../component/tag';
import MachineFile from '@/views/ops/machine/file/MachineFile.vue';
import ResourceAuthCert from '../component/ResourceAuthCert.vue';
import { MachineProtocolEnum } from './enums';
import MachineRdpDialogComp from '@/components/terminal-rdp/MachineRdpDialog.vue';
import { useI18n } from 'vue-i18n';
import { useI18nDeleteConfirm, useI18nDeleteSuccessMsg } from '@/hooks/useI18n';
@@ -287,6 +284,9 @@ const FileConfList = defineAsyncComponent(() => import('./file/FileConfList.vue'
const MachineStats = defineAsyncComponent(() => import('./MachineStats.vue'));
const MachineRec = defineAsyncComponent(() => import('./MachineRec.vue'));
const ProcessList = defineAsyncComponent(() => import('./ProcessList.vue'));
const MachineFile = defineAsyncComponent(() => import('./file/MachineFile.vue'));
const ResourceAuthCert = defineAsyncComponent(() => import('../component/ResourceAuthCert.vue'));
const MachineRdpDialogComp = defineAsyncComponent(() => import('@/components/terminal-rdp/MachineRdpDialog.vue'));
const { t } = useI18n();

View File

@@ -87,12 +87,10 @@
</template>
<script lang="ts" setup>
import { ref, toRefs, reactive, Ref } from 'vue';
import { ref, toRefs, reactive, Ref, defineAsyncComponent } from 'vue';
import { ElMessage } from 'element-plus';
import TerminalBody from '@/components/terminal/TerminalBody.vue';
import { getMachineTerminalSocketUrl, machineApi } from './api';
import { ScriptResultEnum, ScriptTypeEnum } from './enums';
import ScriptEdit from './ScriptEdit.vue';
import PageTable from '@/components/pagetable/PageTable.vue';
import { TableColumn } from '@/components/pagetable';
import { DynamicFormDialog } from '@/components/dynamic-form';
@@ -101,6 +99,9 @@ import { useI18n } from 'vue-i18n';
import { useI18nCreateTitle, useI18nDeleteConfirm, useI18nDeleteSuccessMsg, useI18nEditTitle } from '@/hooks/useI18n';
import { OptionsApi } from '@/components/pagetable/SearchForm/index';
const ScriptEdit = defineAsyncComponent(() => import('./ScriptEdit.vue'));
const TerminalBody = defineAsyncComponent(() => import('@/components/terminal/TerminalBody.vue'));
const { t } = useI18n();
const props = defineProps({

View File

@@ -64,14 +64,15 @@
</template>
<script lang="ts" setup>
import { reactive, toRefs, watch } from 'vue';
import { defineAsyncComponent, reactive, toRefs, watch } from 'vue';
import { machineApi } from '../api';
import { FileTypeEnum } from '../enums';
import MachineFile from './MachineFile.vue';
import MachineFileContent from './MachineFileContent.vue';
import EnumSelect from '@/components/enumselect/EnumSelect.vue';
import { useI18nDeleteConfirm, useI18nSaveSuccessMsg } from '@/hooks/useI18n';
const MachineFile = defineAsyncComponent(() => import('./MachineFile.vue'));
const MachineFileContent = defineAsyncComponent(() => import('./MachineFileContent.vue'));
const props = defineProps({
protocol: { type: Number, default: 1 },
machineId: { type: Number },

View File

@@ -309,14 +309,13 @@
</template>
<script lang="ts" setup>
import { computed, onMounted, reactive, ref, toRefs } from 'vue';
import { computed, defineAsyncComponent, onMounted, reactive, ref, toRefs } from 'vue';
import { ElInput, ElMessage } from 'element-plus';
import { machineApi } from '../api';
import { joinClientParams } from '@/common/request';
import config from '@/common/config';
import { isTrue, notBlank } from '@/common/assert';
import MachineFileContent from './MachineFileContent.vue';
import { getToken } from '@/common/utils/storage';
import { convertToBytes, formatByteSize } from '@/common/utils/format';
import { getMachineConfig } from '@/common/sysconfig';
@@ -325,6 +324,8 @@ import { fuzzyMatchField } from '@/common/utils/string';
import { useI18n } from 'vue-i18n';
import { useI18nDeleteConfirm, useI18nDeleteSuccessMsg } from '@/hooks/useI18n';
const MachineFileContent = defineAsyncComponent(() => import('./MachineFileContent.vue'));
const { t } = useI18n();
const props = defineProps({
@@ -556,6 +557,8 @@ const setFiles = async (path: string) => {
}
state.fileNameFilter = '';
state.loading = true;
state.files = []; // 清空旧数据,可能出现莫名其妙的文件展示错误,先这么处理
state.nowPath = '';
state.files = await lsFile(path);
state.nowPath = path;
} finally {

View File

@@ -22,11 +22,12 @@
</template>
<script lang="ts" setup>
import { computed, reactive, Ref, ref, toRefs, watch } from 'vue';
import { computed, reactive, Ref, ref, toRefs, watch, defineAsyncComponent } from 'vue';
import { machineApi } from '../api';
import MonacoEditor from '@/components/monaco/MonacoEditor.vue';
import { useI18nSaveSuccessMsg } from '@/hooks/useI18n';
const MonacoEditor = defineAsyncComponent(() => import('@/components/monaco/MonacoEditor.vue'));
const props = defineProps({
protocol: { type: Number, default: 1 },
title: { type: String, default: '' },

View File

@@ -623,6 +623,13 @@ const delKey = async (key: string) => {
padding: 0 10px;
height: 29px;
}
::v-deep(.el-tabs__nav-next) {
line-height: 29px;
}
::v-deep(.el-tabs__nav-prev) {
line-height: 29px;
}
}
.redis-data-op {

View File

@@ -42,7 +42,6 @@
<script lang="ts" setup>
import { toRefs, reactive, onMounted, ref, Ref } from 'vue';
import { resourceAuthCertApi } from './api';
import { ElMessage, ElMessageBox } from 'element-plus';
import PageTable from '@/components/pagetable/PageTable.vue';
import { TableColumn } from '@/components/pagetable';
import { SearchItem } from '@/components/pagetable/SearchForm';

View File

@@ -61,9 +61,7 @@
</template>
<script lang="ts" setup>
import { ref, toRefs, reactive, onMounted, Ref } from 'vue';
import RoleAllocation from './RoleAllocation.vue';
import AccountEdit from './AccountEdit.vue';
import { ref, toRefs, reactive, onMounted, Ref, defineAsyncComponent } from 'vue';
import { AccountStatusEnum } from '../enums';
import { accountApi } from '../api';
import { formatDate } from '@/common/utils/format';
@@ -73,6 +71,9 @@ import { hasPerms } from '@/components/auth/auth';
import { SearchItem } from '@/components/pagetable/SearchForm';
import { useI18nCreateTitle, useI18nDeleteConfirm, useI18nDeleteSuccessMsg, useI18nEditTitle, useI18nOperateSuccessMsg } from '@/hooks/useI18n';
const AccountEdit = defineAsyncComponent(() => import('./AccountEdit.vue'));
const RoleAllocation = defineAsyncComponent(() => import('./RoleAllocation.vue'));
const perms = {
addAccount: 'account:add',
delAccount: 'account:del',

View File

@@ -45,13 +45,17 @@
</template>
</el-dialog>
<config-edit :title="$t(configEdit.title)" v-model:visible="configEdit.visible" :data="configEdit.config" @val-change="onConfigEditChange" />
<config-edit
:title="$t(state.configEdit.title)"
v-model:visible="state.configEdit.visible"
:data="state.configEdit.config"
@val-change="onConfigEditChange"
/>
</div>
</template>
<script lang="ts" setup>
import { ref, toRefs, reactive, onMounted, Ref } from 'vue';
import ConfigEdit from './ConfigEdit.vue';
import { ref, toRefs, reactive, onMounted, Ref, defineAsyncComponent } from 'vue';
import { configApi } from '../api';
import PageTable from '@/components/pagetable/PageTable.vue';
import { TableColumn } from '@/components/pagetable';
@@ -61,6 +65,8 @@ import { SearchItem } from '@/components/pagetable/SearchForm';
import { useI18n } from 'vue-i18n';
import { useI18nSaveSuccessMsg } from '@/hooks/useI18n';
const ConfigEdit = defineAsyncComponent(() => import('./ConfigEdit.vue'));
const { t } = useI18n();
const perms = {
@@ -103,7 +109,7 @@ const state = reactive({
},
});
const { query, paramsDialog, configEdit } = toRefs(state);
const { query, paramsDialog } = toRefs(state);
onMounted(() => {
if (Object.keys(actionBtns).length > 0) {

View File

@@ -141,9 +141,8 @@
</template>
<script lang="ts" setup>
import { ref, toRefs, reactive, onMounted, watch } from 'vue';
import { ref, toRefs, reactive, onMounted, watch, defineAsyncComponent } from 'vue';
import { ElMessage } from 'element-plus';
import ResourceEdit from './ResourceEdit.vue';
import { ResourceTypeEnum, RoleStatusEnum } from '../enums';
import { resourceApi } from '../api';
import { formatDate } from '@/common/utils/format';
@@ -154,6 +153,8 @@ import { useI18n } from 'vue-i18n';
import { useI18nDeleteConfirm, useI18nDeleteSuccessMsg } from '@/hooks/useI18n';
import { getMenuIcon } from './index';
const ResourceEdit = defineAsyncComponent(() => import('./ResourceEdit.vue'));
const { t } = useI18n();
const menuTypeValue = ResourceTypeEnum.Menu.value;

View File

@@ -49,20 +49,21 @@
</template>
<script lang="ts" setup>
import { ref, toRefs, reactive, onMounted, Ref } from 'vue';
import RoleEdit from './RoleEdit.vue';
import ResourceEdit from './ResourceEdit.vue';
import ShowResource from './ShowResource.vue';
import { ref, toRefs, reactive, onMounted, Ref, defineAsyncComponent } from 'vue';
import { roleApi, resourceApi } from '../api';
import PageTable from '@/components/pagetable/PageTable.vue';
import { TableColumn } from '@/components/pagetable';
import { hasPerms } from '@/components/auth/auth';
import { RoleStatusEnum } from '../enums';
import { SearchItem } from '@/components/pagetable/SearchForm';
import AccountAllocation from './AccountAllocation.vue';
import { useI18n } from 'vue-i18n';
import { useI18nCreateTitle, useI18nDeleteConfirm, useI18nDeleteSuccessMsg, useI18nEditTitle, useI18nSaveSuccessMsg } from '@/hooks/useI18n';
const RoleEdit = defineAsyncComponent(() => import('./RoleEdit.vue'));
const ShowResource = defineAsyncComponent(() => import('./ShowResource.vue'));
const ResourceEdit = defineAsyncComponent(() => import('./ResourceEdit.vue'));
const AccountAllocation = defineAsyncComponent(() => import('./AccountAllocation.vue'));
const { t } = useI18n();
const perms = {