push
This commit is contained in:
23
lib/saas/view/system/layout/baseContent.vue
Normal file
23
lib/saas/view/system/layout/baseContent.vue
Normal file
@@ -0,0 +1,23 @@
|
||||
<!-- @format -->
|
||||
|
||||
<template>
|
||||
<router-view />
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
export default defineComponent({
|
||||
name: 'NsBaseContent',
|
||||
props: {
|
||||
keepAliveList: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
},
|
||||
setup() {
|
||||
return {};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped></style>
|
||||
98
lib/saas/view/system/layout/breadcrumb.vue
Normal file
98
lib/saas/view/system/layout/breadcrumb.vue
Normal file
@@ -0,0 +1,98 @@
|
||||
<template>
|
||||
<div class="NsBreadcrumb">
|
||||
<a-breadcrumb v-if="initBreadcrumbList && initBreadcrumbList.length">
|
||||
<a-breadcrumb-item v-for="item in initBreadcrumbList" :key="item.name"
|
||||
><a @click="redirectByName(item)">{{ item.meta?.title }}</a></a-breadcrumb-item
|
||||
>
|
||||
</a-breadcrumb>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, ref, watch } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { appConfigStore } from '/nerv-lib/saas/store/modules/app-config';
|
||||
export default defineComponent({
|
||||
props: {
|
||||
isCheck: {
|
||||
type: Boolean,
|
||||
default: () => true,
|
||||
},
|
||||
breadcrumbList: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
},
|
||||
setup(props) {
|
||||
const router = useRouter();
|
||||
const initBreadcrumbList = ref<object[]>([]);
|
||||
initBreadcrumbList.value = props.breadcrumbList as object[];
|
||||
const configStore = appConfigStore();
|
||||
const dealBreadcrumbList = () => {
|
||||
if (initBreadcrumbList.value.length) {
|
||||
let info = initBreadcrumbList.value[initBreadcrumbList.value.length - 2];
|
||||
let finalInfo = initBreadcrumbList.value[initBreadcrumbList.value.length - 1];
|
||||
if (info.meta && info.meta.hideChildren && `${info.name}Index` === finalInfo.name) {
|
||||
initBreadcrumbList.value = initBreadcrumbList.value.slice(
|
||||
0,
|
||||
initBreadcrumbList.value.length - 1,
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
dealBreadcrumbList();
|
||||
watch(
|
||||
() => props.breadcrumbList,
|
||||
(val) => {
|
||||
initBreadcrumbList.value = val;
|
||||
if (props.isCheck) {
|
||||
dealBreadcrumbList();
|
||||
}
|
||||
},
|
||||
{ deep: true },
|
||||
);
|
||||
const redirectByName = (menuInfo: object) => {
|
||||
if (configStore.resourceName) {
|
||||
menuInfo.name = menuInfo.name.replace(configStore.resourceName, '');
|
||||
}
|
||||
|
||||
if (props.isCheck) {
|
||||
router.push({ name: menuInfo.name });
|
||||
} else {
|
||||
if (menuInfo.menus && menuInfo.menus.length) {
|
||||
if (menuInfo.type === 'noChildrenMenu') {
|
||||
router.push({ name: `${menuInfo.name}Index` });
|
||||
} else {
|
||||
let code = menuInfo.menus[0].code;
|
||||
if (configStore.resourceName) {
|
||||
code = code.replace(configStore.resourceName, '');
|
||||
}
|
||||
router.push({ name: code });
|
||||
}
|
||||
} else {
|
||||
router.push({ name: menuInfo.name });
|
||||
}
|
||||
}
|
||||
};
|
||||
return {
|
||||
initBreadcrumbList,
|
||||
redirectByName,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.NsBreadcrumb {
|
||||
width: 100%;
|
||||
padding: 0 16px;
|
||||
min-height: 42px;
|
||||
background: #ffffff;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
position: fixed;
|
||||
top: 48px;
|
||||
z-index: 505;
|
||||
border-bottom: 1px solid rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
</style>
|
||||
54
lib/saas/view/system/layout/content.vue
Normal file
54
lib/saas/view/system/layout/content.vue
Normal file
@@ -0,0 +1,54 @@
|
||||
<!-- @format -->
|
||||
|
||||
<template>
|
||||
<router-view v-slot="{ Component }" v-if="isRouterAlive">
|
||||
<transition name="fade-slide" mode="out-in">
|
||||
<div class="ns-content-main">
|
||||
<keep-alive :include="data">
|
||||
<component :is="Component" />
|
||||
</keep-alive>
|
||||
</div>
|
||||
</transition>
|
||||
</router-view>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, ref, computed, nextTick } from 'vue';
|
||||
import { useKeepAlive } from '/nerv-base/store/modules/keepAlive';
|
||||
import { listenerReloadChange } from '/nerv-lib/util/routeChange';
|
||||
export default defineComponent({
|
||||
name: 'NsContent',
|
||||
props: {
|
||||
keepAliveList: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
},
|
||||
setup() {
|
||||
const cachedViews = ref(['Status']);
|
||||
const keepAliveStore = useKeepAlive();
|
||||
const isRouterAlive = ref(true);
|
||||
|
||||
listenerReloadChange(() => {
|
||||
isRouterAlive.value = false;
|
||||
nextTick(() => {
|
||||
isRouterAlive.value = true;
|
||||
});
|
||||
});
|
||||
const data = computed(() => {
|
||||
return keepAliveStore.getKeepAlive;
|
||||
});
|
||||
|
||||
return { cachedViews, data, isRouterAlive };
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.ns-content-main {
|
||||
height: 100%;
|
||||
& > div {
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
607
lib/saas/view/system/layout/customHeader.vue
Normal file
607
lib/saas/view/system/layout/customHeader.vue
Normal file
@@ -0,0 +1,607 @@
|
||||
<!-- @format -->
|
||||
|
||||
<template>
|
||||
<a-layout-header :style="`background-image:url(${headerBgImg})`" class="header">
|
||||
<div class="logo">
|
||||
<img
|
||||
v-if="themeConfig.logoLessUrl"
|
||||
:src="themeConfig.logoLessUrl"
|
||||
style="width: 192px; height: 48px; object-fit: contain" />
|
||||
<ns-icon v-else name="headerLogin" class="headerLogin" style="width: auto; height: 48px" />
|
||||
</div>
|
||||
<div class="header-menu">
|
||||
<a-menu style="width: 100%" theme="dark" mode="horizontal" :selectedKeys="initHeaderKey">
|
||||
<a-menu-item @click="tochildren(item)" v-for="item in menuList" :key="item.code">
|
||||
<i
|
||||
style="margin-right: 5px; transform: translateY(2px); font-size: 14px; color: #ffffff"
|
||||
v-if="item.icon?.includes('icon-')"
|
||||
:class="`iconfont ${item.icon}`"></i>
|
||||
<ns-icon
|
||||
v-else
|
||||
:name="item.icon"
|
||||
size="16"
|
||||
style="margin-right: 5px; transform: translateY(2px); color: #ffffff" />
|
||||
<span style="font-size: 14px; color: #ffffff">{{ item.label }}</span>
|
||||
</a-menu-item>
|
||||
<template v-for="item in subMenuList" :key="item.name">
|
||||
<a-sub-menu v-if="getOPMenu(item.meta?.itemList)?.length > 1">
|
||||
<template #title>
|
||||
<ns-icon
|
||||
:name="item.meta.icon"
|
||||
size="12"
|
||||
style="margin-right: 5px; transform: translateY(1px)" /><span
|
||||
style="font-size: 14px"
|
||||
>{{ item.meta.title }}
|
||||
</span></template
|
||||
>
|
||||
<a-menu-item
|
||||
:key="menuItem.name"
|
||||
v-for="menuItem in item.meta?.itemList ? getOPMenu(item.meta?.itemList) : []">
|
||||
<a
|
||||
style="color: inherit !important"
|
||||
:href="menuItem.url ? menuItem.url : menuItem.getUrl ? menuItem.getUrl() : ''"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer">
|
||||
{{ menuItem.name }}
|
||||
</a></a-menu-item
|
||||
>
|
||||
</a-sub-menu>
|
||||
<template v-if="checkMenus(item.meta?.itemList)?.length == 1">
|
||||
<a-menu-item
|
||||
:key="menuItem.name"
|
||||
v-for="menuItem in checkMenus(item.meta?.itemList)
|
||||
? checkMenus(item.meta?.itemList)
|
||||
: []">
|
||||
<div v-if="!item.meta.isNewOpen">
|
||||
<ns-icon
|
||||
:name="item.meta.icon"
|
||||
size="12"
|
||||
style="margin-right: 5px; transform: translateY(1px)" />{{ item.meta.title }}
|
||||
<a
|
||||
:href="menuItem.url ? menuItem.url : menuItem.getUrl ? menuItem.getUrl() : ''"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer">
|
||||
</a>
|
||||
</div>
|
||||
<div v-else @click="toCustomUrl(menuItem.url)">
|
||||
<ns-icon
|
||||
:name="item.meta.icon"
|
||||
size="14"
|
||||
style="margin-right: 5px; transform: translateY(1px)" />{{ item.meta.title }}
|
||||
<a target="_blank" rel="noopener noreferrer"> </a>
|
||||
</div>
|
||||
</a-menu-item>
|
||||
</template>
|
||||
</template>
|
||||
</a-menu>
|
||||
</div>
|
||||
<div class="nsHeader_action">
|
||||
<div class="projectName action" v-if="showProject">
|
||||
{{ projectName ? projectName : enterpriseName }}
|
||||
</div>
|
||||
<div
|
||||
class="projectName action"
|
||||
v-if="['服务管理平台', '报表中心'].includes(configStore.resourceInfo?.application?.label)"
|
||||
@click="backDoor"
|
||||
>{{ '返回门户' }}</div
|
||||
>
|
||||
<div v-if="bellInfo.isShow" class="bells action" @click="backMessage">
|
||||
<a-badge :count="messageCount > 99 ? 99 : messageCount">
|
||||
<ns-icon name="bells" size="32" />
|
||||
</a-badge>
|
||||
</div>
|
||||
<a-dropdown :trigger="['hover']">
|
||||
<div class="userName action">
|
||||
<ns-icon class="headerAdminIcon" name="headerAdminIcon" size="20" />
|
||||
<span
|
||||
style="
|
||||
display: block;
|
||||
max-width: 100px;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
"
|
||||
>{{ userName }}</span
|
||||
>
|
||||
<ns-icon class="downArrow" name="downArrow" size="20" />
|
||||
</div>
|
||||
|
||||
<template #overlay>
|
||||
<a-menu>
|
||||
<a-menu-item
|
||||
class="menuItem"
|
||||
style="
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 52px;
|
||||
font-size: 14px;
|
||||
justify-content: center;
|
||||
color: rgba(0, 0, 0, 0.65);
|
||||
">
|
||||
<ns-icon name="updatePassword" size="18" style="position: relative; top: 2px" />
|
||||
<a href="javascript:;" @click="toUpdate" style="margin-left: 10px">修改密码</a>
|
||||
</a-menu-item>
|
||||
<a-menu-item
|
||||
class="menuItem"
|
||||
style="
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 52px;
|
||||
font-size: 14px;
|
||||
justify-content: center;
|
||||
color: rgba(0, 0, 0, 0.65);
|
||||
">
|
||||
<ns-icon name="leaveout" size="14" style="position: relative; top: 2px; left: 2px" />
|
||||
<a href="javascript:;" @click="dropOut" style="margin-left: 12px">退出登录</a>
|
||||
</a-menu-item>
|
||||
</a-menu>
|
||||
</template>
|
||||
</a-dropdown>
|
||||
<!-- <div class="setting action" v-if="currentThemeColor" @click="changeSettingVisible(true)">
|
||||
<setting-outlined />
|
||||
</div> -->
|
||||
</div>
|
||||
<a-drawer v-model:visible="settingVisible" title="外观管理" placement="right">
|
||||
<a-divider>主题</a-divider>
|
||||
<div class="theme-picker">
|
||||
<template v-for="color in themeColorList" :key="color">
|
||||
<span
|
||||
class="theme-picker_item"
|
||||
@click="changeTheme(color)"
|
||||
:class="{ current: currentThemeColor === color }"
|
||||
:style="{ backgroundColor: color }"
|
||||
><check-outlined /></span
|
||||
></template>
|
||||
</div>
|
||||
</a-drawer>
|
||||
</a-layout-header>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, ref, watch, computed, getCurrentInstance } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { message } from 'ant-design-vue';
|
||||
import { Cookies } from '/nerv-lib/util/cookie';
|
||||
import { getLocalSetting, setLocalSetting, http } from '/nerv-lib/saas';
|
||||
import { appConfigStore } from '/nerv-lib/saas/store/modules/app-config';
|
||||
import { useTags } from '/nerv-base/store/modules/tags';
|
||||
import { authorizationService } from '/nerv-base/store/modules/authorization-service';
|
||||
import { replaceStyleVariables } from 'vite-plugin-theme/es/client';
|
||||
import { getThemeColors } from '../../../../../build/themeConfig';
|
||||
import { SettingOutlined, CheckOutlined } from '@ant-design/icons-vue';
|
||||
import { messagecount } from '/nerv-lib/saas/store/modules/messagecount';
|
||||
import { storeToRefs, mapWritableState } from 'pinia';
|
||||
export default defineComponent({
|
||||
name: 'CustomHeader',
|
||||
components: {
|
||||
SettingOutlined,
|
||||
CheckOutlined,
|
||||
},
|
||||
props: {
|
||||
initHeaderKey: { type: Array },
|
||||
},
|
||||
setup: () => {
|
||||
const configStore = appConfigStore();
|
||||
const { getThemeConfig: themeConfig } = storeToRefs(configStore);
|
||||
const messagecountStore = messagecount();
|
||||
const bellInfo = ref();
|
||||
const subMenuList = ref([]);
|
||||
const router = useRouter();
|
||||
const time = ref();
|
||||
const headerBgImg = computed(() => {
|
||||
if (themeConfig.topUrl) {
|
||||
return themeConfig.topUrl;
|
||||
} else {
|
||||
return `${import.meta.env.VITE_PUBLIC_PATH}asset/image/header.png`;
|
||||
}
|
||||
});
|
||||
|
||||
const toCustomUrl = (val: string) => {
|
||||
window.open(`${val}?nervsid=${Cookies.get('nervsid')}`);
|
||||
};
|
||||
const messageCount = computed(() => {
|
||||
return messagecountStore.getCount;
|
||||
});
|
||||
bellInfo.value = configStore.headerBellInfo;
|
||||
if (bellInfo.value.isShow) {
|
||||
async function initMessageCount() {
|
||||
try {
|
||||
const res = await http.get(bellInfo.value.api);
|
||||
if (res.success) {
|
||||
messagecountStore.updateCount(
|
||||
(bellInfo.value.dealData ? bellInfo.value.dealData(res) : res.data.count) > 99
|
||||
? '99+'
|
||||
: bellInfo.value.dealData
|
||||
? bellInfo.value.dealData(res)
|
||||
: res.data.count,
|
||||
);
|
||||
}
|
||||
} catch (err) {}
|
||||
}
|
||||
if (!configStore?.customInitMessageCount) {
|
||||
initMessageCount();
|
||||
time.value = setInterval(initMessageCount, 10000);
|
||||
}
|
||||
}
|
||||
const userName = ref<string>('-');
|
||||
const authorizationStore = authorizationService();
|
||||
// const { getInitMenus: menuList } = storeToRefs(authorizationStore);
|
||||
const menuList = computed(() => {
|
||||
let list = [];
|
||||
subMenuList.value = [];
|
||||
const initMenu = authorizationStore.getInitMenus;
|
||||
if (initMenu && initMenu.length) {
|
||||
initMenu.forEach((item) => {
|
||||
if (item.extend) {
|
||||
let extend = JSON.parse(item.extend);
|
||||
if (extend.itemList) {
|
||||
item.meta = {
|
||||
title: '智慧社区大屏',
|
||||
icon: 'shujudaping',
|
||||
index: 1000,
|
||||
isNewOpen: true,
|
||||
itemList: [
|
||||
{
|
||||
code: 'wisdomCommunity',
|
||||
name: '智慧社区运营管理驾驶舱',
|
||||
url: '/nervui-community-screen/community-screen',
|
||||
},
|
||||
{
|
||||
code: 'wisdomCommunity1',
|
||||
name: '智慧社区安防监控驾驶舱',
|
||||
url: '/nervui-community-screen/community-security',
|
||||
},
|
||||
],
|
||||
};
|
||||
subMenuList.value.push(item);
|
||||
} else {
|
||||
list.push(item);
|
||||
}
|
||||
} else {
|
||||
list.push(item);
|
||||
}
|
||||
});
|
||||
}
|
||||
return list;
|
||||
});
|
||||
const { getProjectName: projectName } = storeToRefs(authorizationStore);
|
||||
const { getEnterpriseName: enterpriseName } = storeToRefs(authorizationStore);
|
||||
|
||||
const initUserInfo = window.sessionStorage['userInfo'];
|
||||
if (!authorizationStore.getProjectName && Cookies.get('projectName')) {
|
||||
authorizationStore.setProjectName(Cookies.get('projectName'));
|
||||
}
|
||||
if (!authorizationStore.getEnterpriseName && window.sessionStorage['userInfo']) {
|
||||
const userInfo = JSON.parse(window.sessionStorage['userInfo']);
|
||||
authorizationStore.setEnterpriseName(
|
||||
!userInfo.organizationalName ? '' : userInfo.organizationalName,
|
||||
);
|
||||
}
|
||||
initUserInfo
|
||||
? (userName.value = JSON.parse(initUserInfo).accountRealName
|
||||
? JSON.parse(initUserInfo).accountRealName
|
||||
: JSON.parse(initUserInfo).accountName)
|
||||
: '';
|
||||
|
||||
const tochildren = (val) => {
|
||||
if (val.type === 'menus') {
|
||||
if (!val.menus || val.menus.length === 0) {
|
||||
let code = val.code;
|
||||
if (configStore.resourceName) {
|
||||
code = code.toString().replace(configStore.resourceName, '');
|
||||
}
|
||||
router.push({
|
||||
name: code,
|
||||
});
|
||||
} else {
|
||||
tochildren(val.menus[0]);
|
||||
}
|
||||
} else {
|
||||
if (val.type === 'noChildrenMenu') {
|
||||
let code = val.code;
|
||||
if (configStore.resourceName) {
|
||||
code = code.toString().replace(configStore.resourceName, '');
|
||||
}
|
||||
if (val.menus) {
|
||||
router.push({
|
||||
name: `${code}Index`,
|
||||
});
|
||||
} else {
|
||||
router.push({
|
||||
name: `${code}`,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
const getOPMenu = (list) => {
|
||||
if (configStore.enablePermissions !== undefined && configStore.enablePermissions) {
|
||||
return list.filter((item) => {
|
||||
return authorizationStore.checkPermission(item.code);
|
||||
});
|
||||
} else {
|
||||
return list;
|
||||
}
|
||||
};
|
||||
const checkMenus = (list) => {
|
||||
if (configStore.enablePermissions !== undefined && configStore.enablePermissions) {
|
||||
return list.filter((item) => {
|
||||
return authorizationStore.checkAllPermission(item.code);
|
||||
});
|
||||
} else {
|
||||
return list;
|
||||
}
|
||||
};
|
||||
|
||||
const toUpdate = () => {
|
||||
router.push({
|
||||
name: 'UpdatePassWord',
|
||||
});
|
||||
};
|
||||
const backMessage = () => {
|
||||
if (bellInfo.value.toRouterName) {
|
||||
router.push({
|
||||
name: bellInfo.value.toRouterName,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const dropOut = () => {
|
||||
message.loading('正在退出', 0.1);
|
||||
if (configStore.dropOut) {
|
||||
configStore.dropOut(Cookies, router, useTags, authorizationStore, http);
|
||||
} else {
|
||||
Cookies.remove('nervsid');
|
||||
sessionStorage.clear();
|
||||
router.push('/login');
|
||||
useTags().clearTags();
|
||||
configStore.setInitThemeCoinfig(false);
|
||||
authorizationStore.clearAuthorization();
|
||||
}
|
||||
};
|
||||
const updatePassWord = () => {
|
||||
router.push('/synthetical/user/updatePassWord');
|
||||
};
|
||||
const currentThemeColor = ref(null);
|
||||
function changeTheme(color: string) {
|
||||
replaceStyleVariables({ colorVariables: [...getThemeColors(color)] });
|
||||
currentThemeColor.value = color;
|
||||
setLocalSetting({ themeColor: color });
|
||||
}
|
||||
|
||||
const appConfig = getCurrentInstance()?.appContext.config.globalProperties.$appConfig;
|
||||
const themeColor = getLocalSetting()?.themeColor || appConfig?.themeColor;
|
||||
if (themeColor) {
|
||||
changeTheme(themeColor);
|
||||
}
|
||||
|
||||
const settingVisible = ref<boolean>(false);
|
||||
function changeSettingVisible(visible: boolean) {
|
||||
settingVisible.value = visible;
|
||||
}
|
||||
const themeColorList = [
|
||||
'#37abc4',
|
||||
'#1677FF',
|
||||
'#009688',
|
||||
'#536dfe',
|
||||
'#ff5c93',
|
||||
'#ee4f12',
|
||||
'#0096c7',
|
||||
'#9c27b0',
|
||||
'#ff9800',
|
||||
];
|
||||
const backDoor = () => {
|
||||
let protocol = window.location.protocol;
|
||||
const nervsid = Cookies.get('nervsid');
|
||||
http.get('/portalurl.json').then((res) => {
|
||||
if (nervsid) {
|
||||
window.location.href = `${protocol}//${res.url}report?nervsid=${nervsid}`;
|
||||
} else {
|
||||
window.location.href = `${protocol}//${res.url}report`;
|
||||
}
|
||||
});
|
||||
};
|
||||
return {
|
||||
headerBgImg,
|
||||
backDoor,
|
||||
themeColorList,
|
||||
currentThemeColor,
|
||||
settingVisible,
|
||||
changeSettingVisible,
|
||||
checkMenus,
|
||||
toCustomUrl,
|
||||
time,
|
||||
messageCount,
|
||||
backMessage,
|
||||
bellInfo,
|
||||
showProject: configStore.showProject,
|
||||
projectName,
|
||||
enterpriseName,
|
||||
tochildren,
|
||||
menuList,
|
||||
toUpdate,
|
||||
subMenuList,
|
||||
changeTheme,
|
||||
userName,
|
||||
dropOut,
|
||||
updatePassWord,
|
||||
getOPMenu,
|
||||
configStore,
|
||||
themeConfig,
|
||||
};
|
||||
},
|
||||
beforeUnmount() {
|
||||
clearInterval(this.time);
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.theme-picker {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
margin: 16px 0;
|
||||
justify-content: space-around;
|
||||
|
||||
.theme-picker_item {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
cursor: pointer;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 2px;
|
||||
|
||||
span {
|
||||
display: none;
|
||||
font-size: 12px;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
&.current span {
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.header {
|
||||
padding: 0;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
z-index: 505;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
:deep(.ant-badge-count) {
|
||||
// width: 16px;
|
||||
height: 16px;
|
||||
line-height: 8px;
|
||||
text-align: center;
|
||||
padding: 4px;
|
||||
// line-height: 16px;
|
||||
// background: #ec4d28;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 0 0 0px #fff;
|
||||
transform: translate(45%, -35%);
|
||||
}
|
||||
|
||||
:deep(.hiden) {
|
||||
opacity: 0 !important;
|
||||
position: absolute;
|
||||
user-select: none;
|
||||
width: 1px !important;
|
||||
height: 1px !important;
|
||||
}
|
||||
|
||||
.header-menu {
|
||||
width: calc(100% - @layout-sider-width - 208px);
|
||||
}
|
||||
|
||||
.logo {
|
||||
width: calc(@layout-sider-width - 16px);
|
||||
min-width: @layout-sider-width !important;
|
||||
display: flex;
|
||||
padding-left: 16px;
|
||||
align-items: center;
|
||||
|
||||
img {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.ant-layout-header {
|
||||
display: flex;
|
||||
padding: 0 !important;
|
||||
}
|
||||
|
||||
.nsHeader_action {
|
||||
color: #fff;
|
||||
padding-left: 24px;
|
||||
padding-right: 24px;
|
||||
width: 208px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: right;
|
||||
|
||||
.action {
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
background-color: @layout-header-hover;
|
||||
}
|
||||
}
|
||||
|
||||
.projectName {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.bells,
|
||||
.setting {
|
||||
height: 100%;
|
||||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.projectName {
|
||||
color: #ffffff;
|
||||
white-space: nowrap;
|
||||
padding-left: 10px;
|
||||
padding-right: 10px;
|
||||
}
|
||||
|
||||
// padding-right: 30px;
|
||||
.dropOut {
|
||||
height: 100%;
|
||||
padding-right: 16px;
|
||||
padding-left: 16px;
|
||||
}
|
||||
|
||||
.userName {
|
||||
line-height: 48px;
|
||||
display: flex;
|
||||
white-space: nowrap;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
padding-left: 10px;
|
||||
padding-right: 10px;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
img {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
margin-right: 12px;
|
||||
}
|
||||
|
||||
span {
|
||||
user-select: none;
|
||||
}
|
||||
}
|
||||
|
||||
.menuItem {
|
||||
height: 80px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
:deep(
|
||||
.ant-menu.ant-menu-dark .ant-menu-item-selected,
|
||||
.ant-menu-submenu-popup.ant-menu-dark .ant-menu-item-selected
|
||||
) {
|
||||
background-color: @layout-header-hover;
|
||||
}
|
||||
:deep(.ant-menu.ant-menu-dark .ant-menu-item) {
|
||||
&:hover {
|
||||
background-color: @layout-header-hover;
|
||||
}
|
||||
}
|
||||
|
||||
.headerAdminIcon {
|
||||
margin-right: 8px;
|
||||
}
|
||||
.downArrow {
|
||||
margin-left: 8px;
|
||||
}
|
||||
</style>
|
||||
278
lib/saas/view/system/layout/customSider.vue
Normal file
278
lib/saas/view/system/layout/customSider.vue
Normal file
@@ -0,0 +1,278 @@
|
||||
<!-- @format -->
|
||||
|
||||
<template>
|
||||
<div class="ns-left-menu-space" :class="{ 'ns-left-menu-space-collapsed': collapsed }">
|
||||
<a-layout-sider
|
||||
class="ns-left-menu"
|
||||
:width="208"
|
||||
:style="`background-image:url(${sideBgImg})`"
|
||||
:collapsedWidth="48"
|
||||
:collapsed="collapsed"
|
||||
breakpoint="lg"
|
||||
:trigger="null">
|
||||
<a-menu
|
||||
mode="inline"
|
||||
:inlineIndent="16"
|
||||
:openKeys="collapsed ? [] : initSiderOpenKey"
|
||||
:selectedKeys="initSiderKey"
|
||||
v-for="(item, index) in menuList"
|
||||
:key="index">
|
||||
<a-sub-menu
|
||||
v-if="item.type === 'menus' && item.menus && item.menus.length"
|
||||
:key="item.code">
|
||||
<template #title>
|
||||
<span role="img" class="anticon">
|
||||
<i
|
||||
style="margin-right: 5px; transform: translateY(2px); font-size: 14px"
|
||||
v-if="item.icon?.includes('icon-')"
|
||||
:class="`iconfont ${item.icon}`"></i>
|
||||
<ns-icon v-else :name="item.icon ? item.icon : ''" size="16" />
|
||||
</span>
|
||||
<span>{{ item.label }}</span>
|
||||
</template>
|
||||
<!-- 跳转外部链接 -->
|
||||
<!-- <div v-if="item.meta?.isNewOpen">
|
||||
<div
|
||||
v-for="newOpen in item.meta?.itemList ? item.meta?.itemList : []"
|
||||
:key="newOpen.code">
|
||||
<a-menu-item v-if="checkOpAuth(newOpen.code)" :key="newOpen.code">
|
||||
<a
|
||||
:href="newOpen.url ? newOpen.url : newOpen.getUrl ? newOpen.getUrl() : ''"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer">
|
||||
{{ newOpen.name }}
|
||||
</a></a-menu-item
|
||||
>
|
||||
</div>
|
||||
</div> -->
|
||||
<!-- <div v-else> -->
|
||||
<div v-for="sitem in item.menus" :key="sitem.code">
|
||||
<a-menu-item
|
||||
v-if="
|
||||
sitem.type === 'noChildrenMenu' ||
|
||||
(sitem.type === 'menus' && (!sitem.menus || !sitem.menus?.length))
|
||||
"
|
||||
:key="sitem.code">
|
||||
<a @click="goTargetRouter(sitem)">
|
||||
<span style="margin-left: 8px">
|
||||
{{ sitem.label }}
|
||||
</span>
|
||||
</a>
|
||||
</a-menu-item>
|
||||
<a-sub-menu
|
||||
v-if="sitem.type === 'menus' && sitem.menus && sitem.menus.length"
|
||||
:key="sitem.code">
|
||||
<template #title>
|
||||
<span role="img" style="margin-left: 8px" class="anticon" v-show="sitem.icon">
|
||||
<i
|
||||
style="margin-right: 5px; transform: translateY(2px); font-size: 14px"
|
||||
v-if="sitem.icon?.includes('icon-')"
|
||||
:class="`iconfont ${sitem.icon}`"></i>
|
||||
<ns-icon v-else :name="sitem.icon" size="16" />
|
||||
</span>
|
||||
<span>{{ sitem.label }}</span>
|
||||
</template>
|
||||
<a-menu-item v-for="ditem in sitem.menus" :key="ditem.code">
|
||||
<a @click="goTargetRouter(ditem)">
|
||||
<span style="margin-left: 8px">{{ ditem.label }}</span>
|
||||
</a>
|
||||
</a-menu-item>
|
||||
</a-sub-menu>
|
||||
</div>
|
||||
<!-- </div> -->
|
||||
</a-sub-menu>
|
||||
<a-menu-item
|
||||
v-if="
|
||||
item.type === 'noChildrenMenu' ||
|
||||
(item.type === 'menus' && (!item.menus || !item.menus?.length))
|
||||
"
|
||||
:class="initSiderKey.includes(item.code) ? 'firstMenuItem-selected' : ''"
|
||||
:key="item.code">
|
||||
<a @click="goTargetRouter(item)">
|
||||
<span role="img" class="anticon" v-show="item.icon">
|
||||
<i
|
||||
style="margin-right: 5px; transform: translateY(2px); font-size: 14px"
|
||||
v-if="item.icon?.includes('icon-')"
|
||||
:class="`iconfont ${item.icon}`"></i>
|
||||
<ns-icon v-else :name="item.icon ? item.icon : ''" size="16" />
|
||||
</span>
|
||||
<span>{{ item.label }}</span>
|
||||
</a>
|
||||
</a-menu-item>
|
||||
</a-menu>
|
||||
<div class="ns-left-menu-trigger" @click="leftMenuTrigger">
|
||||
<menu-unfold-outlined v-if="collapsed" class="trigger" />
|
||||
<menu-fold-outlined v-else class="trigger" />
|
||||
</div>
|
||||
</a-layout-sider>
|
||||
<div class="ns-left-menu-trigger" @click="leftMenuTrigger">
|
||||
<ns-icon name="trigger" class="trigger" size="22" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { MenuUnfoldOutlined, MenuFoldOutlined } from '@ant-design/icons-vue';
|
||||
import { defineComponent, inject, ref, watchEffect, computed } from 'vue';
|
||||
import { Emitter } from 'mitt';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { appConfigStore } from '/nerv-lib/saas/store/modules/app-config';
|
||||
import { emitEvents } from '/nerv-base/view/system/application.d';
|
||||
import { storeToRefs } from 'pinia';
|
||||
export default defineComponent({
|
||||
name: 'CustomSider',
|
||||
components: {
|
||||
MenuUnfoldOutlined,
|
||||
MenuFoldOutlined,
|
||||
},
|
||||
props: {
|
||||
menuList: { type: Array },
|
||||
initSiderKey: { type: Array },
|
||||
initSiderOpenKey: { type: Array },
|
||||
},
|
||||
setup: (props) => {
|
||||
const mittEmit = inject('mittEmit') as Emitter<emitEvents>;
|
||||
const configStore = appConfigStore();
|
||||
const { getThemeConfig: themeConfig } = storeToRefs(configStore);
|
||||
const router = useRouter();
|
||||
const dealRouter = (menuList, routerInfo) => {
|
||||
menuList?.forEach((item) => {
|
||||
if (item.name === routerInfo.name) {
|
||||
const redirectItem = item.children?.filter((x) => !x.isHide)[0];
|
||||
if (redirectItem) {
|
||||
router.push({ name: redirectItem.name });
|
||||
}
|
||||
} else {
|
||||
if (item.children?.length) {
|
||||
dealRouter(item.children, routerInfo);
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
const sideBgImg = computed(() => {
|
||||
if (themeConfig.sideUrl) {
|
||||
return themeConfig.sideUrl;
|
||||
} else {
|
||||
return `${import.meta.env.VITE_PUBLIC_PATH}asset/image/side.png`;
|
||||
}
|
||||
});
|
||||
const goTargetRouter = (menu: object) => {
|
||||
let code = menu.code;
|
||||
if (configStore.resourceName) {
|
||||
code = code.toString().replace(configStore.resourceName, '');
|
||||
}
|
||||
let toName = code;
|
||||
if (menu.type === 'noChildrenMenu' && menu.menus && menu.menus.length) {
|
||||
toName = `${code}Index`;
|
||||
}
|
||||
router.push({
|
||||
name: toName,
|
||||
});
|
||||
};
|
||||
watchEffect(() => {
|
||||
if (!router.currentRoute.value.redirectedFrom) {
|
||||
dealRouter(props.menuList, router.currentRoute.value);
|
||||
}
|
||||
});
|
||||
const collapsed = ref<boolean>(false);
|
||||
const leftMenuTrigger = () => {
|
||||
collapsed.value = !collapsed.value;
|
||||
mittEmit.emit('leftMenuTrigger', collapsed.value);
|
||||
};
|
||||
return {
|
||||
sideBgImg,
|
||||
themeConfig,
|
||||
goTargetRouter,
|
||||
leftMenuTrigger,
|
||||
collapsed,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.ns-left-menu {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
height: 100%;
|
||||
background-color: @white;
|
||||
padding-top: 48px;
|
||||
:deep(::-webkit-scrollbar) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
#iframeApplication .ns-left-menu {
|
||||
padding-top: 0px;
|
||||
}
|
||||
|
||||
.ant-menu-submenu-title .anticon + span {
|
||||
margin-left: 8px !important;
|
||||
}
|
||||
.ns-left-menu-space {
|
||||
width: 208px;
|
||||
height: 100%;
|
||||
background-color: #fff;
|
||||
overflow: hidden;
|
||||
transition: all 0.2s;
|
||||
flex: 0 0 208px;
|
||||
&.ns-left-menu-space-collapsed {
|
||||
width: 48px;
|
||||
flex: 0 0 48px;
|
||||
}
|
||||
}
|
||||
.ns-left-menu-space-collapsed {
|
||||
.ns-left-menu-trigger {
|
||||
width: 48px !important;
|
||||
justify-content: center;
|
||||
padding-left: 0px;
|
||||
}
|
||||
.trigger {
|
||||
padding: 0;
|
||||
transform: rotate(180deg);
|
||||
// transform: rotateX('90deg');
|
||||
}
|
||||
}
|
||||
|
||||
.ns-left-menu-trigger {
|
||||
width: 208px;
|
||||
height: 40px;
|
||||
transition: all 0.2s;
|
||||
border-top: 1px solid rgba(0, 0, 0, 0.06);
|
||||
position: absolute;
|
||||
z-index: 999;
|
||||
cursor: pointer;
|
||||
background: #163361;
|
||||
bottom: 0px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: left;
|
||||
padding-left: 24px;
|
||||
|
||||
// justify-content: center;
|
||||
&:hover {
|
||||
.trigger {
|
||||
color: @primary-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
.trigger {
|
||||
font-size: 18px;
|
||||
line-height: 64px;
|
||||
transform: rotate(0deg);
|
||||
transition: color 0.3s;
|
||||
}
|
||||
.ant-menu-submenu-selected > .ant-menu-submenu-title > .ant-menu-submenu-expand-icon,
|
||||
.ant-menu-submenu-selected > .ant-menu-submenu-title > .ant-menu-submenu-arrow {
|
||||
color: red !important;
|
||||
}
|
||||
.ant-menu-submenu-selected .secendIcon {
|
||||
color: @primary-color !important;
|
||||
}
|
||||
.ant-menu-inline,
|
||||
.ant-menu-vertical,
|
||||
.ant-menu-vertical-left {
|
||||
border: unset;
|
||||
}
|
||||
</style>
|
||||
579
lib/saas/view/system/layout/header.vue
Normal file
579
lib/saas/view/system/layout/header.vue
Normal file
@@ -0,0 +1,579 @@
|
||||
<!-- @format -->
|
||||
|
||||
<template>
|
||||
<a-layout-header class="header">
|
||||
<div class="logo">
|
||||
<img
|
||||
v-if="themeConfig.logoLessUrl"
|
||||
:src="themeConfig.logoLessUrl"
|
||||
style="width: 192px; height: 48px; object-fit: contain" />
|
||||
<ns-icon v-else name="headerLogin" class="headerLogin" style="width: auto; height: 48px" />
|
||||
</div>
|
||||
<div class="header-menu">
|
||||
<a-menu style="width: 100%" mode="horizontal" :selectedKeys="initHeaderKey">
|
||||
<a-menu-item v-for="item in menuList" :key="item.name">
|
||||
<div @click="tochildren(item)">
|
||||
<ns-icon :name="item.meta.icon" size="16" /><span>{{ item.meta.title }}</span>
|
||||
</div>
|
||||
</a-menu-item>
|
||||
<template v-for="item in subMenuList" :key="item.name">
|
||||
<a-sub-menu v-if="getOPMenu(item.meta?.itemList)?.length > 1">
|
||||
<template #title>
|
||||
<ns-icon :name="item.meta.icon" size="16" /><span
|
||||
>{{ item.meta.title }}
|
||||
</span></template
|
||||
>
|
||||
<a-menu-item
|
||||
:key="menuItem.name"
|
||||
v-for="menuItem in item.meta?.itemList ? getOPMenu(item.meta?.itemList) : []">
|
||||
<a
|
||||
style="color: inherit !important"
|
||||
:href="menuItem.url ? menuItem.url : menuItem.getUrl ? menuItem.getUrl() : ''"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer">
|
||||
{{ menuItem.name }}
|
||||
</a></a-menu-item
|
||||
>
|
||||
</a-sub-menu>
|
||||
|
||||
<template v-if="checkMenus(item.meta?.itemList)?.length == 1">
|
||||
<a-menu-item
|
||||
:key="menuItem.name"
|
||||
v-for="menuItem in checkMenus(item.meta?.itemList)
|
||||
? checkMenus(item.meta?.itemList)
|
||||
: []">
|
||||
<div v-if="!item.meta.isNewOpen">
|
||||
<ns-icon :name="item.meta.icon" size="16" />{{ item.meta.title }}
|
||||
<a
|
||||
:href="menuItem.url ? menuItem.url : menuItem.getUrl ? menuItem.getUrl() : ''"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer">
|
||||
</a>
|
||||
</div>
|
||||
<div v-else @click="toCustomUrl(menuItem.url)">
|
||||
<ns-icon :name="item.meta.icon" size="16" />{{ item.meta.title }}
|
||||
<a target="_blank" rel="noopener noreferrer"> </a>
|
||||
</div>
|
||||
</a-menu-item>
|
||||
</template>
|
||||
</template>
|
||||
</a-menu>
|
||||
</div>
|
||||
<div class="nsHeader_action">
|
||||
<div class="projectName action" v-if="showProject">
|
||||
{{ projectName ? projectName : enterpriseName }}
|
||||
</div>
|
||||
<div
|
||||
class="projectName action"
|
||||
v-if="['服务管理平台', '报表中心'].includes(configStore.resourceInfo?.application?.label)"
|
||||
@click="backDoor"
|
||||
>{{ '返回门户' }}</div
|
||||
>
|
||||
<div v-if="bellInfo.isShow" class="bells action" @click="backMessage">
|
||||
<a-badge :count="messageCount > 99 ? 99 : messageCount">
|
||||
<ns-icon name="bells" size="32" />
|
||||
</a-badge>
|
||||
</div>
|
||||
<a-dropdown :trigger="['hover']">
|
||||
<div class="userName action">
|
||||
<!-- <img src="/asset/image/login/adminIcon.png" /> -->
|
||||
<ns-icon class="headerAdminIcon" name="headerAdminIcon" size="20" />
|
||||
<span
|
||||
style="
|
||||
display: block;
|
||||
max-width: 100px;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
"
|
||||
>{{ userName }}</span
|
||||
>
|
||||
<ns-icon class="downArrow" name="downArrow" size="20" />
|
||||
</div>
|
||||
|
||||
<template #overlay>
|
||||
<a-menu>
|
||||
<a-menu-item
|
||||
class="menuItem"
|
||||
style="
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 52px;
|
||||
font-size: 14px;
|
||||
justify-content: center;
|
||||
color: rgba(0, 0, 0, 0.65);
|
||||
">
|
||||
<ns-icon name="updatePassword" size="18" style="position: relative; top: 2px" />
|
||||
<a href="javascript:;" @click="toUpdate" style="margin-left: 10px">修改密码</a>
|
||||
</a-menu-item>
|
||||
<a-menu-item
|
||||
class="menuItem"
|
||||
style="
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 52px;
|
||||
font-size: 14px;
|
||||
justify-content: center;
|
||||
color: rgba(0, 0, 0, 0.65);
|
||||
">
|
||||
<ns-icon name="leaveout" size="14" style="position: relative; top: 2px; left: 2px" />
|
||||
<a href="javascript:;" @click="dropOut" style="margin-left: 12px">退出登录</a>
|
||||
</a-menu-item>
|
||||
</a-menu>
|
||||
</template>
|
||||
</a-dropdown>
|
||||
|
||||
<!-- <div class="setting action" v-if="currentThemeColor" @click="changeSettingVisible(true)">
|
||||
<setting-outlined />
|
||||
</div> -->
|
||||
</div>
|
||||
<!-- <a-drawer v-model:visible="settingVisible" title="外观管理" placement="right">
|
||||
<a-divider>主题</a-divider>
|
||||
<div class="theme-picker">
|
||||
<template v-for="color in themeColorList" :key="color">
|
||||
<span
|
||||
class="theme-picker_item"
|
||||
@click="changeTheme(color)"
|
||||
:class="{ current: currentThemeColor === color }"
|
||||
:style="{ backgroundColor: color }"
|
||||
><check-outlined /></span
|
||||
></template>
|
||||
</div>
|
||||
</a-drawer> -->
|
||||
</a-layout-header>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, ref, watch, computed, getCurrentInstance } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { message } from 'ant-design-vue';
|
||||
import { Cookies } from '/nerv-lib/util/cookie';
|
||||
import { getLocalSetting, setLocalSetting, http } from '/nerv-lib/saas';
|
||||
import { appConfigStore } from '/nerv-lib/saas/store/modules/app-config';
|
||||
import { useTags } from '/nerv-base/store/modules/tags';
|
||||
import { authorizationService } from '/nerv-base/store/modules/authorization-service';
|
||||
import { replaceStyleVariables } from 'vite-plugin-theme/es/client';
|
||||
import { getThemeColors } from '../../../../../build/themeConfig';
|
||||
import { SettingOutlined, CheckOutlined } from '@ant-design/icons-vue';
|
||||
import { messagecount } from '/nerv-lib/saas/store/modules/messagecount';
|
||||
import { storeToRefs } from 'pinia';
|
||||
export default defineComponent({
|
||||
name: 'NsHeader',
|
||||
components: {
|
||||
SettingOutlined,
|
||||
CheckOutlined,
|
||||
},
|
||||
props: {
|
||||
// eslint-disable-next-line vue/require-valid-default-prop
|
||||
headerList: { type: Array, default: [] },
|
||||
initHeaderKey: { type: Array },
|
||||
},
|
||||
setup: (props: any) => {
|
||||
const configStore = appConfigStore();
|
||||
const { getThemeConfig: themeConfig } = storeToRefs(configStore);
|
||||
const { getHeaderBellInfo: headerBellInfo } = storeToRefs(configStore);
|
||||
const messagecountStore = messagecount();
|
||||
const bellInfo = ref();
|
||||
// const messageCount = ref();
|
||||
const time = ref();
|
||||
const toCustomUrl = (val: string) => {
|
||||
window.open(`${val}?nervsid=${Cookies.get('nervsid')}`);
|
||||
// window.location.href = `${val}?nervsid=${Cookies.get('nervsid')}`;
|
||||
};
|
||||
const messageCount = computed(() => {
|
||||
return messagecountStore.getCount;
|
||||
});
|
||||
bellInfo.value = configStore.headerBellInfo;
|
||||
if (bellInfo.value.isShow) {
|
||||
async function initMessageCount() {
|
||||
try {
|
||||
const res = await http.get(bellInfo.value.api);
|
||||
if (res.success) {
|
||||
messagecountStore.updateCount(
|
||||
(bellInfo.value.dealData ? bellInfo.value.dealData(res) : res.data.count) > 99
|
||||
? '99+'
|
||||
: bellInfo.value.dealData
|
||||
? bellInfo.value.dealData(res)
|
||||
: res.data.count,
|
||||
);
|
||||
}
|
||||
} catch (err) {}
|
||||
}
|
||||
if (!configStore?.customInitMessageCount) {
|
||||
initMessageCount();
|
||||
time.value = setInterval(initMessageCount, 10000);
|
||||
}
|
||||
}
|
||||
const userName = ref<string>('-');
|
||||
const authorizationStore = authorizationService();
|
||||
|
||||
const initUserInfo = window.sessionStorage['userInfo'];
|
||||
if (!authorizationStore.getProjectName && Cookies.get('projectName')) {
|
||||
authorizationStore.setProjectName(Cookies.get('projectName'));
|
||||
}
|
||||
if (!authorizationStore.getEnterpriseName && window.sessionStorage['userInfo']) {
|
||||
const userInfo = JSON.parse(window.sessionStorage['userInfo']);
|
||||
authorizationStore.setEnterpriseName(
|
||||
!userInfo.organizationalName ? '' : userInfo.organizationalName,
|
||||
);
|
||||
}
|
||||
const projectName = computed(() => authorizationStore.getProjectName);
|
||||
const enterpriseName = computed(() => authorizationStore.getEnterpriseName);
|
||||
initUserInfo
|
||||
? (userName.value = JSON.parse(initUserInfo).accountRealName
|
||||
? JSON.parse(initUserInfo).accountRealName
|
||||
: JSON.parse(initUserInfo).accountName)
|
||||
: '';
|
||||
const menuList = ref([]);
|
||||
const subMenuList = ref([]);
|
||||
const router = useRouter();
|
||||
const tochildren = (val) => {
|
||||
if (val.children === undefined || val.children.length === 0) {
|
||||
router.push({
|
||||
name: val.name,
|
||||
});
|
||||
} else {
|
||||
for (var i = 0; i < val.children.length; i++) {
|
||||
if (!val.children[i].isHide) {
|
||||
tochildren(val.children[i]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
const getOPMenu = (list) => {
|
||||
if (configStore.enablePermissions !== undefined && configStore.enablePermissions) {
|
||||
return list.filter((item) => {
|
||||
return authorizationStore.checkPermission(item.code);
|
||||
});
|
||||
} else {
|
||||
return list;
|
||||
}
|
||||
};
|
||||
const checkMenus = (list) => {
|
||||
if (configStore.enablePermissions !== undefined && configStore.enablePermissions) {
|
||||
return list.filter((item) => {
|
||||
return authorizationStore.checkAllPermission(item.code);
|
||||
});
|
||||
} else {
|
||||
return list;
|
||||
}
|
||||
};
|
||||
props.headerList.forEach((item) => {
|
||||
if (!item.isHide) {
|
||||
if (!item.meta.isNewOpen) {
|
||||
menuList.value.push(item);
|
||||
} else {
|
||||
subMenuList.value.push(item);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
watch(
|
||||
() => props.headerList,
|
||||
(e) => {
|
||||
menuList.value = [];
|
||||
subMenuList.value = [];
|
||||
e.forEach((item) => {
|
||||
if (!item.isHide) {
|
||||
if (!item.meta.isNewOpen) {
|
||||
menuList.value.push(item);
|
||||
} else {
|
||||
subMenuList.value.push(item);
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
{ deep: true },
|
||||
);
|
||||
const toUpdate = () => {
|
||||
router.push({
|
||||
name: 'UpdatePassWord',
|
||||
});
|
||||
};
|
||||
const backMessage = () => {
|
||||
if (headerBellInfo.value.toRouterName) {
|
||||
router.push({
|
||||
name: headerBellInfo.value.toRouterName,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const dropOut = () => {
|
||||
message.loading('正在退出', 0.1);
|
||||
if (configStore.dropOut) {
|
||||
configStore.dropOut(Cookies, router, useTags, authorizationStore, http);
|
||||
} else {
|
||||
Cookies.remove('nervsid');
|
||||
sessionStorage.clear();
|
||||
router.push('/login');
|
||||
useTags().clearTags();
|
||||
authorizationStore.clearAuthorization();
|
||||
}
|
||||
};
|
||||
const updatePassWord = () => {
|
||||
router.push('/synthetical/user/updatePassWord');
|
||||
};
|
||||
const currentThemeColor = ref(null);
|
||||
function changeTheme(color: string) {
|
||||
replaceStyleVariables({ colorVariables: [...getThemeColors(color)] });
|
||||
currentThemeColor.value = color;
|
||||
setLocalSetting({ themeColor: color });
|
||||
}
|
||||
|
||||
const appConfig = getCurrentInstance()?.appContext.config.globalProperties.$appConfig;
|
||||
const themeColor = getLocalSetting()?.themeColor || appConfig?.themeColor;
|
||||
|
||||
if (themeColor) {
|
||||
changeTheme(themeColor);
|
||||
}
|
||||
|
||||
const settingVisible = ref<boolean>(false);
|
||||
function changeSettingVisible(visible: boolean) {
|
||||
settingVisible.value = visible;
|
||||
}
|
||||
const themeColorList = [
|
||||
'#37abc4',
|
||||
'#1677FF',
|
||||
'#009688',
|
||||
'#536dfe',
|
||||
'#ff5c93',
|
||||
'#ee4f12',
|
||||
'#0096c7',
|
||||
'#9c27b0',
|
||||
'#ff9800',
|
||||
];
|
||||
const backDoor = () => {
|
||||
let protocol = window.location.protocol;
|
||||
const nervsid = Cookies.get('nervsid');
|
||||
http.get('/portalurl.json').then((res) => {
|
||||
if (nervsid) {
|
||||
window.location.href = `${protocol}//${res.url}report?nervsid=${nervsid}`;
|
||||
} else {
|
||||
window.location.href = `${protocol}//${res.url}report`;
|
||||
}
|
||||
});
|
||||
};
|
||||
return {
|
||||
themeConfig,
|
||||
backDoor,
|
||||
themeColorList,
|
||||
currentThemeColor,
|
||||
settingVisible,
|
||||
changeSettingVisible,
|
||||
checkMenus,
|
||||
toCustomUrl,
|
||||
time,
|
||||
messageCount,
|
||||
backMessage,
|
||||
bellInfo,
|
||||
showProject: configStore.showProject,
|
||||
projectName,
|
||||
enterpriseName,
|
||||
tochildren,
|
||||
menuList,
|
||||
toUpdate,
|
||||
subMenuList,
|
||||
changeTheme,
|
||||
userName,
|
||||
dropOut,
|
||||
updatePassWord,
|
||||
getOPMenu,
|
||||
configStore,
|
||||
};
|
||||
},
|
||||
beforeUnmount() {
|
||||
clearInterval(this.time);
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.theme-picker {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
margin: 16px 0;
|
||||
justify-content: space-around;
|
||||
|
||||
.theme-picker_item {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
cursor: pointer;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 2px;
|
||||
|
||||
span {
|
||||
display: none;
|
||||
font-size: 12px;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
&.current span {
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
}
|
||||
:deep(.ant-menu-submenu-title) {
|
||||
color: rgba(255, 255, 255, 0.9);
|
||||
}
|
||||
:deep(.header-menu .ant-menu-title-content) {
|
||||
div {
|
||||
color: rgba(255, 255, 255, 0.9);
|
||||
.ns-icon {
|
||||
margin-right: 7px;
|
||||
transform: translateY(2px);
|
||||
}
|
||||
}
|
||||
}
|
||||
:deep(.ant-menu-submenu-title .ant-menu-title-content) {
|
||||
color: rgba(255, 255, 255, 0.9);
|
||||
.ns-icon {
|
||||
margin-right: 7px;
|
||||
transform: translateY(2px);
|
||||
}
|
||||
}
|
||||
|
||||
.header {
|
||||
padding: 0;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
z-index: 505;
|
||||
width: 100%;
|
||||
background-image: url(/asset/image/header.png);
|
||||
background-size: 100% 100%;
|
||||
}
|
||||
|
||||
:deep(.ant-badge-count) {
|
||||
// width: 16px;
|
||||
height: 16px;
|
||||
line-height: 8px;
|
||||
text-align: center;
|
||||
padding: 4px;
|
||||
// line-height: 16px;
|
||||
// background: #ec4d28;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 0 0 0px #fff;
|
||||
transform: translate(55%, -10%);
|
||||
}
|
||||
|
||||
:deep(.hiden) {
|
||||
opacity: 0 !important;
|
||||
position: absolute;
|
||||
user-select: none;
|
||||
width: 1px !important;
|
||||
height: 1px !important;
|
||||
}
|
||||
|
||||
.header-menu {
|
||||
width: calc(100% - @layout-sider-width - 208px);
|
||||
.ant-menu-root {
|
||||
background-color: transparent;
|
||||
}
|
||||
}
|
||||
|
||||
.logo {
|
||||
width: calc(@layout-sider-width - 16px);
|
||||
min-width: @layout-sider-width !important;
|
||||
display: flex;
|
||||
padding-left: 16px;
|
||||
align-items: center;
|
||||
|
||||
img {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
// .headerLogin {
|
||||
// width: @layout-sider-width;
|
||||
// height: 48px;
|
||||
// }
|
||||
}
|
||||
|
||||
.ant-layout-header {
|
||||
display: flex;
|
||||
padding: 0 !important;
|
||||
}
|
||||
|
||||
.nsHeader_action {
|
||||
color: rgba(255, 255, 255, 0.9);
|
||||
padding-left: 16px;
|
||||
padding-right: 16px;
|
||||
width: 208px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: right;
|
||||
|
||||
.action {
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
background-color: @layout-header-hover;
|
||||
}
|
||||
}
|
||||
|
||||
.projectName {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.bells,
|
||||
.setting {
|
||||
height: 100%;
|
||||
// width: 50px;
|
||||
padding-left: 12px;
|
||||
padding-right: 12px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.projectName {
|
||||
color: rgba(255, 255, 255, 0.9);
|
||||
white-space: nowrap;
|
||||
padding-left: 10px;
|
||||
padding-right: 10px;
|
||||
}
|
||||
|
||||
// padding-right: 30px;
|
||||
.dropOut {
|
||||
height: 100%;
|
||||
padding-right: 16px;
|
||||
padding-left: 16px;
|
||||
}
|
||||
|
||||
.userName {
|
||||
line-height: 48px;
|
||||
display: flex;
|
||||
white-space: nowrap;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
padding-left: 10px;
|
||||
padding-right: 10px;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.headerAdminIcon {
|
||||
margin-right: 8px;
|
||||
}
|
||||
.downArrow {
|
||||
margin-left: 8px;
|
||||
}
|
||||
|
||||
span {
|
||||
user-select: none;
|
||||
}
|
||||
}
|
||||
|
||||
.menuItem {
|
||||
height: 80px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
:deep(.ant-menu-horizontal) {
|
||||
border: unset !important;
|
||||
}
|
||||
:deep(.ant-menu-item-selected) {
|
||||
background: @primary-color;
|
||||
}
|
||||
</style>
|
||||
366
lib/saas/view/system/layout/sider.vue
Normal file
366
lib/saas/view/system/layout/sider.vue
Normal file
@@ -0,0 +1,366 @@
|
||||
<!-- @format -->
|
||||
|
||||
<template>
|
||||
<div class="ns-left-menu-space" :class="{ 'ns-left-menu-space-collapsed': collapsed }">
|
||||
<a-layout-sider
|
||||
class="ns-left-menu"
|
||||
:width="208"
|
||||
:collapsedWidth="48"
|
||||
:collapsed="collapsed"
|
||||
breakpoint="lg"
|
||||
:trigger="null"
|
||||
v-if="menuList && menuList[0] && menuList[0].children">
|
||||
<a-menu
|
||||
mode="inline"
|
||||
:inlineIndent="16"
|
||||
:openKeys="collapsed ? [] : initSiderOpenKey"
|
||||
:selectedKeys="initSiderKey"
|
||||
v-for="(item, index) in menuList[0].children"
|
||||
:key="index">
|
||||
<a-sub-menu
|
||||
v-if="item.children !== undefined && !item.meta.hideChildren && !item.isHide"
|
||||
:key="item.name">
|
||||
<template #title>
|
||||
<span role="img" class="anticon"
|
||||
><ns-icon :name="item.meta.icon ? item.meta.icon : ''" size="16"
|
||||
/></span>
|
||||
<span>{{ item.meta.title }}</span>
|
||||
</template>
|
||||
<!-- 跳转外部链接 -->
|
||||
<div v-if="item.meta?.isNewOpen">
|
||||
<div
|
||||
v-for="newOpen in item.meta?.itemList ? item.meta?.itemList : []"
|
||||
:key="newOpen.code">
|
||||
<a-menu-item v-if="checkOpAuth(newOpen.code)" :key="newOpen.code">
|
||||
<a
|
||||
:href="newOpen.url ? newOpen.url : newOpen.getUrl ? newOpen.getUrl() : ''"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer">
|
||||
{{ newOpen.name }}
|
||||
</a></a-menu-item
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else>
|
||||
<div v-for="sitem in checkAuthList(item.children)" :key="sitem.name">
|
||||
<a-menu-item
|
||||
v-if="(sitem.children === undefined || sitem.meta.hideChildren) && !sitem.isHide"
|
||||
:key="sitem.name"
|
||||
><router-link style="margin-left: 8px" :to="{ name: sitem.name }">{{
|
||||
sitem.meta.title
|
||||
}}</router-link></a-menu-item
|
||||
>
|
||||
<a-sub-menu
|
||||
class="threeSubMenu"
|
||||
v-if="sitem.children !== undefined && !sitem.meta.hideChildren && !sitem.isHide"
|
||||
:key="sitem.name">
|
||||
<template #title>
|
||||
<span role="img" style="margin-left: 8px" class="anticon" v-show="sitem.meta.icon"
|
||||
><ns-icon :name="sitem.meta.icon" size="16"
|
||||
/></span>
|
||||
<span>{{ sitem.meta.title }}</span>
|
||||
</template>
|
||||
<div v-for="ditem in checkAuthList(sitem.children)" :key="ditem.name">
|
||||
<a-sub-menu
|
||||
class="fourSubMenu"
|
||||
v-if="ditem.children !== undefined && !ditem.isHide"
|
||||
:key="ditem.name">
|
||||
<template #title>
|
||||
<span
|
||||
role="img"
|
||||
style="margin-left: 8px"
|
||||
class="anticon"
|
||||
v-show="ditem.meta.icon"
|
||||
><ns-icon :name="ditem.meta.icon" size="16"
|
||||
/></span>
|
||||
<span>{{ ditem.meta.title }}</span>
|
||||
</template>
|
||||
<div
|
||||
v-for="fiveFloorItem in checkAuthList(ditem.children)"
|
||||
:key="fiveFloorItem.name">
|
||||
<a-menu-item
|
||||
:class="
|
||||
initSiderKey.includes(fiveFloorItem.name) ? 'ant-menu-item-selected' : ''
|
||||
"
|
||||
v-if="fiveFloorItem.meta?.type !== 'op'"
|
||||
><router-link style="margin-left: 8px" :to="{ name: fiveFloorItem.name }">{{
|
||||
fiveFloorItem?.meta?.title ? fiveFloorItem?.meta?.title : ''
|
||||
}}</router-link></a-menu-item
|
||||
>
|
||||
</div>
|
||||
</a-sub-menu>
|
||||
|
||||
<a-menu-item v-if="ditem.meta?.type !== 'op' && item.children === undefined"
|
||||
><router-link style="margin-left: 8px" :to="{ name: ditem.name }">{{
|
||||
ditem?.meta?.title ? ditem?.meta?.title : ''
|
||||
}}</router-link></a-menu-item
|
||||
>
|
||||
</div>
|
||||
</a-sub-menu>
|
||||
</div>
|
||||
</div>
|
||||
</a-sub-menu>
|
||||
<a-menu-item
|
||||
:class="initSiderKey.includes(item.name) ? 'firstMenuItem-selected' : ''"
|
||||
v-if="
|
||||
(item.children === undefined || item.meta.hideChildren) &&
|
||||
!item.isHide &&
|
||||
item.type !== 'op'
|
||||
"
|
||||
:key="item.name">
|
||||
<router-link :to="{ name: item.name }">
|
||||
<span role="img" class="anticon" v-show="item.meta.icon">
|
||||
<ns-icon :name="item.meta.icon ? item.meta.icon : ''" size="16" />
|
||||
</span>
|
||||
<span style="margin-left: 8px">{{ item.meta.title }}</span>
|
||||
<!-- </span> -->
|
||||
</router-link>
|
||||
</a-menu-item>
|
||||
</a-menu>
|
||||
</a-layout-sider>
|
||||
<div class="ns-left-menu-trigger" @click="leftMenuTrigger">
|
||||
<!-- <menu-unfold-outlined v-if="collapsed" class="trigger" />
|
||||
<menu-fold-outlined v-else class="trigger" /> -->
|
||||
<ns-icon name="trigger" class="trigger" size="22" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { MenuUnfoldOutlined, MenuFoldOutlined } from '@ant-design/icons-vue';
|
||||
import { defineComponent, inject, ref, watchEffect } from 'vue';
|
||||
import { appConfigStore } from '/nerv-lib/saas/store/modules/app-config';
|
||||
import { authorizationService } from '/nerv-base/store/modules/authorization-service';
|
||||
import { Emitter } from 'mitt';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { emitEvents } from '/nerv-base/view/system/application.d';
|
||||
export default defineComponent({
|
||||
name: 'NsSider',
|
||||
components: {
|
||||
MenuUnfoldOutlined,
|
||||
MenuFoldOutlined,
|
||||
},
|
||||
props: {
|
||||
menuList: { type: Array },
|
||||
initSiderKey: { type: Array },
|
||||
initSiderOpenKey: { type: Array },
|
||||
},
|
||||
setup: (props) => {
|
||||
const mittEmit = inject('mittEmit') as Emitter<emitEvents>;
|
||||
const router = useRouter();
|
||||
const dealRouter = (menuList, routerInfo) => {
|
||||
menuList?.forEach((item) => {
|
||||
if (item.name === routerInfo.name) {
|
||||
const redirectItem = item.children?.filter((x) => !x.isHide)[0];
|
||||
if (redirectItem) {
|
||||
router.push({ name: redirectItem.name });
|
||||
}
|
||||
} else {
|
||||
if (item.children?.length) {
|
||||
dealRouter(item.children, routerInfo);
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
watchEffect(() => {
|
||||
if (!router.currentRoute.value.redirectedFrom) {
|
||||
dealRouter(props.menuList, router.currentRoute.value);
|
||||
}
|
||||
});
|
||||
const authorizationStore = authorizationService();
|
||||
const configStore = appConfigStore();
|
||||
const checkOpAuth = (val: any) => {
|
||||
if (configStore.enablePermissions) {
|
||||
return authorizationStore.checkPermission(val);
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
};
|
||||
const checkAuth = (val: any) => {
|
||||
if (configStore.enablePermissions) {
|
||||
return authorizationStore.checkPermissionRouter(val);
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
};
|
||||
const checkAuthList = (val: any) => {
|
||||
if (Object.prototype.toString.call(val) !== '[object Array]') return;
|
||||
let array: any[] = [];
|
||||
val.forEach((item: any) => {
|
||||
if (checkAuth(item.name)) {
|
||||
array.push(item);
|
||||
}
|
||||
});
|
||||
return array;
|
||||
};
|
||||
const collapsed = ref<boolean>(false);
|
||||
const leftMenuTrigger = () => {
|
||||
collapsed.value = !collapsed.value;
|
||||
mittEmit.emit('leftMenuTrigger', collapsed.value);
|
||||
};
|
||||
return {
|
||||
leftMenuTrigger,
|
||||
collapsed,
|
||||
checkOpAuth,
|
||||
checkAuthList,
|
||||
checkAuth,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.ns-left-menu {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
height: calc(100% - 40px);
|
||||
// background-color: @white;
|
||||
background-image: url(/asset/image/side.png);
|
||||
background-size: cover;
|
||||
padding-top: 48px;
|
||||
:deep(.ant-layout-sider-children) {
|
||||
background-color: transparent;
|
||||
.ant-menu-root {
|
||||
background-color: transparent;
|
||||
}
|
||||
.ant-menu-submenu-title {
|
||||
// color: rgba(255, 255, 255, 0.9);
|
||||
.ant-menu-submenu-arrow {
|
||||
// color:inherit
|
||||
// color: rgba(255, 255, 255, 0.9);
|
||||
}
|
||||
}
|
||||
.ant-menu {
|
||||
color: rgba(255, 255, 255, 0.9);
|
||||
}
|
||||
.ant-menu-item a {
|
||||
color: rgba(255, 255, 255, 0.9);
|
||||
}
|
||||
.ant-menu-submenu-expand-icon,
|
||||
.ant-menu-submenu-arrow {
|
||||
color: rgba(255, 255, 255, 0.9);
|
||||
}
|
||||
.ant-menu-sub.ant-menu-inline {
|
||||
background-color: #001027;
|
||||
}
|
||||
}
|
||||
}
|
||||
#iframeApplication .ns-left-menu {
|
||||
padding-top: 0px;
|
||||
}
|
||||
|
||||
.ant-menu-submenu-title .anticon + span {
|
||||
margin-left: 8px !important;
|
||||
}
|
||||
.ns-left-menu-space {
|
||||
width: 208px;
|
||||
height: 100%;
|
||||
background-color: #fff;
|
||||
overflow: hidden;
|
||||
transition: all 0.2s;
|
||||
flex: 0 0 208px;
|
||||
&.ns-left-menu-space-collapsed {
|
||||
width: 48px;
|
||||
flex: 0 0 48px;
|
||||
}
|
||||
}
|
||||
.ns-left-menu-space-collapsed {
|
||||
.ns-left-menu-trigger {
|
||||
width: 48px !important;
|
||||
justify-content: center;
|
||||
padding-left: 0px;
|
||||
}
|
||||
.trigger {
|
||||
padding: 0;
|
||||
transform: rotate(180deg);
|
||||
// transform: rotateX('90deg');
|
||||
}
|
||||
}
|
||||
|
||||
.ns-left-menu-trigger {
|
||||
width: 208px;
|
||||
height: 40px;
|
||||
transition: all 0.2s;
|
||||
border-top: 1px solid rgba(0, 0, 0, 0.06);
|
||||
position: absolute;
|
||||
z-index: 999;
|
||||
cursor: pointer;
|
||||
background: #163361;
|
||||
bottom: 0px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: left;
|
||||
padding-left: 24px;
|
||||
|
||||
// justify-content: center;
|
||||
&:hover {
|
||||
.trigger {
|
||||
color: @primary-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
.trigger {
|
||||
font-size: 18px;
|
||||
line-height: 64px;
|
||||
transform: rotate(0deg);
|
||||
transition: color 0.3s;
|
||||
}
|
||||
.ant-menu-submenu-selected > .ant-menu-submenu-title > .ant-menu-submenu-expand-icon,
|
||||
.ant-menu-submenu-selected > .ant-menu-submenu-title > .ant-menu-submenu-arrow {
|
||||
color: red !important;
|
||||
}
|
||||
.ant-menu-submenu-selected .secendIcon {
|
||||
color: @primary-color !important;
|
||||
}
|
||||
|
||||
.ant-menu-inline,
|
||||
.ant-menu-vertical,
|
||||
.ant-menu-vertical-left {
|
||||
border: unset;
|
||||
}
|
||||
|
||||
:deep(.ant-menu-inline .ant-menu-item-selected::after) {
|
||||
position: absolute;
|
||||
border: unset !important;
|
||||
content: '';
|
||||
}
|
||||
:deep(.ant-menu-sub .ant-menu-item-selected::after) {
|
||||
position: absolute;
|
||||
width: 2px;
|
||||
height: 14px;
|
||||
background: #fff;
|
||||
border: unset;
|
||||
top: 14px;
|
||||
left: 20px;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
content: '';
|
||||
}
|
||||
:deep(.ant-menu-submenu .ant-menu-submenu .ant-menu-sub .ant-menu-item-selected::after) {
|
||||
position: absolute;
|
||||
width: 2px;
|
||||
height: 14px;
|
||||
background: #fff;
|
||||
border: unset;
|
||||
top: 14px;
|
||||
left: 40px;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
content: '';
|
||||
}
|
||||
|
||||
// :deep(.ant-menu-title-content) {
|
||||
// padding-left: 8px;
|
||||
// }
|
||||
// :deep(.firstMenuSub .ant-menu-submenu-title) {
|
||||
// padding-left: 8px !important;
|
||||
// }
|
||||
:deep(.firstMenuItem-selected) {
|
||||
background: @primary-color!important;
|
||||
border-radius: 4px;
|
||||
}
|
||||
:deep(.ns-left-menu .ant-layout-sider-children .ant-menu-submenu-arrow) {
|
||||
color: rgba(255, 255, 255, 0.5);
|
||||
}
|
||||
</style>
|
||||
118
lib/saas/view/system/layout/tabContent.vue
Normal file
118
lib/saas/view/system/layout/tabContent.vue
Normal file
@@ -0,0 +1,118 @@
|
||||
<template>
|
||||
<a-tabs v-model:activeKey="activeKey" size="large" @tabClick="tabChange">
|
||||
<a-tab-pane v-for="(item, index) in tabListAuth" :key="index" :tab="item.title">
|
||||
<router-view v-slot="{ Component }">
|
||||
<!-- <keep-alive :include="data"> -->
|
||||
<component :is="Component" />
|
||||
<!-- </keep-alive> -->
|
||||
</router-view> </a-tab-pane
|
||||
>>
|
||||
</a-tabs>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, ref, computed, watch } from 'vue';
|
||||
import { useKeepAlive } from '/nerv-base/store/modules/keepAlive';
|
||||
import { useRouter, onBeforeRouteUpdate, useRoute } from 'vue-router';
|
||||
import { authorizationService } from '/nerv-base/store/modules/authorization-service';
|
||||
export default defineComponent({
|
||||
name: 'RecipientsToReturnIndex',
|
||||
props: {
|
||||
tabList: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
selfName: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
},
|
||||
setup(props) {
|
||||
let activeKey = ref(0);
|
||||
const authorizationStore = authorizationService();
|
||||
const keepAliveStore = useKeepAlive();
|
||||
const router = useRouter();
|
||||
const route = useRoute();
|
||||
const selfName = props['selfName'];
|
||||
let tabListAuth = props['tabList'].filter((el) => {
|
||||
if (authorizationStore.checkAllPermission(el.name)) {
|
||||
return el;
|
||||
}
|
||||
});
|
||||
const tabChange = function (params) {
|
||||
let name = tabListAuth[params]['name'];
|
||||
router.push({
|
||||
name: name,
|
||||
});
|
||||
};
|
||||
|
||||
const exitRouter = (to) => {
|
||||
let nameList = to.matched.map((el) => {
|
||||
return el.name;
|
||||
});
|
||||
tabListAuth.some((el, i) => {
|
||||
if (nameList.includes(el.name)) {
|
||||
activeKey.value = i;
|
||||
return true;
|
||||
}
|
||||
});
|
||||
// tabChange(activeKey.value);
|
||||
};
|
||||
|
||||
exitRouter(router.currentRoute.value);
|
||||
|
||||
onBeforeRouteUpdate((to) => {
|
||||
exitRouter(to);
|
||||
});
|
||||
|
||||
const data = computed(() => {
|
||||
return keepAliveStore.getKeepAlive;
|
||||
});
|
||||
|
||||
if (tabListAuth.length) {
|
||||
if (route.name?.toString().toLowerCase().includes('index')) {
|
||||
tabChange(activeKey.value);
|
||||
}
|
||||
}
|
||||
|
||||
watch(
|
||||
() => route.path,
|
||||
(value) => {
|
||||
// tabChange(activeKey.value)
|
||||
exitRouter(route);
|
||||
if (route.name === selfName) {
|
||||
tabChange(activeKey.value);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
// onActivated
|
||||
// onActivated(()=>{
|
||||
// console.log(',mmm')
|
||||
// });
|
||||
|
||||
return {
|
||||
activeKey,
|
||||
data,
|
||||
tabChange,
|
||||
tabListAuth,
|
||||
};
|
||||
},
|
||||
activated() {
|
||||
this.tabChange(this.activeKey);
|
||||
},
|
||||
});
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
.desc {
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
display: block;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
:deep(.ant-tabs-nav-wrap) {
|
||||
padding: 0 24px !important;
|
||||
}
|
||||
</style>
|
||||
312
lib/saas/view/system/layout/tag/index.vue
Normal file
312
lib/saas/view/system/layout/tag/index.vue
Normal file
@@ -0,0 +1,312 @@
|
||||
<!-- @format -->
|
||||
|
||||
<template>
|
||||
<div
|
||||
:id="`${dealMenus(menuList) ? '' : 'fullTab'}`"
|
||||
:class="leftMenuTrigger ? 'ns-tabs-collapsed' : 'ns-tabs'">
|
||||
<Tabs
|
||||
type="editable-card"
|
||||
size="small"
|
||||
style="width: 100%"
|
||||
ref="tags"
|
||||
:animated="false"
|
||||
:hideAdd="true"
|
||||
:tabBarGutter="3"
|
||||
:activeKey="activeKeyRef"
|
||||
@change="handleChange"
|
||||
@edit="handleEdit">
|
||||
<template
|
||||
v-for="(item, index) in getTabsState"
|
||||
:key="item.currentUrl ? item.currentUrl : item.query ? item.fullPath : item.path">
|
||||
<TabPane :closable="!(item && item.meta && index === 0)">
|
||||
<template #tab>
|
||||
<a-dropdown :trigger="index === 0 ? [] : ['contextmenu']">
|
||||
<div>
|
||||
{{ item?.meta?.title }}
|
||||
<ReloadOutlined
|
||||
@click="reload"
|
||||
v-if="
|
||||
activeKeyRef ===
|
||||
(item.currentUrl ? item.currentUrl : item.query ? item.fullPath : item.path)
|
||||
"
|
||||
:class="loadingClass" />
|
||||
</div>
|
||||
<template #overlay>
|
||||
<a-menu>
|
||||
<a-menu-item>
|
||||
<a @click="closeMenu('all', item)">关闭所有标签页</a>
|
||||
</a-menu-item>
|
||||
<a-menu-item>
|
||||
<a @click="closeMenu('other', item)">关闭其他标签页</a>
|
||||
</a-menu-item>
|
||||
</a-menu>
|
||||
</template>
|
||||
</a-dropdown>
|
||||
</template>
|
||||
</TabPane>
|
||||
</template>
|
||||
</Tabs>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { ReloadOutlined } from '@ant-design/icons-vue';
|
||||
import { defineComponent, computed, unref, ref, inject } from 'vue';
|
||||
import { Tabs } from 'ant-design-vue';
|
||||
import { useTags } from '/nerv-base/store/modules/tags';
|
||||
import { listenerRouteChange, setReloadChange } from '/nerv-lib/util/routeChange';
|
||||
import { useKeepAlive } from '/nerv-base/store/modules/keepAlive';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { Emitter } from 'mitt';
|
||||
import { emitEvents } from '/nerv-base/view/system/application.d';
|
||||
export default defineComponent({
|
||||
name: 'NsTags',
|
||||
components: {
|
||||
Tabs,
|
||||
ReloadOutlined,
|
||||
TabPane: Tabs.TabPane,
|
||||
},
|
||||
props: {
|
||||
menuList: { type: Array },
|
||||
},
|
||||
setup(props) {
|
||||
const mittEmit = inject('mittEmit') as Emitter<emitEvents>;
|
||||
const leftMenuTrigger = ref(false);
|
||||
mittEmit.on('leftMenuTrigger', (val) => {
|
||||
leftMenuTrigger.value = val;
|
||||
});
|
||||
const dealMenus = (arr: object[]) => {
|
||||
if (!arr || !arr.length) {
|
||||
return true;
|
||||
} else {
|
||||
let isTrue = false;
|
||||
arr.forEach((item) => {
|
||||
if (item.type !== 'op') {
|
||||
isTrue = true;
|
||||
}
|
||||
});
|
||||
return isTrue;
|
||||
}
|
||||
};
|
||||
const activeKeyRef = ref('');
|
||||
const router = useRouter();
|
||||
const visible = ref(false);
|
||||
const tagsStore = useTags();
|
||||
const loadingClass = ref('reload');
|
||||
const keepAliveStore = useKeepAlive();
|
||||
const getTabsState = computed(() => tagsStore.getTags);
|
||||
const reload = () => {
|
||||
setReloadChange();
|
||||
loadingClass.value = 'iconLoading';
|
||||
setTimeout(() => {
|
||||
loadingClass.value = 'reload';
|
||||
}, 1000);
|
||||
};
|
||||
const keepAliveList = computed(() => {
|
||||
return keepAliveStore.getKeepAlive;
|
||||
});
|
||||
const closeMenu = (key: string, info: object) => {
|
||||
if (key === 'all') {
|
||||
tagsStore.sliceTags(0);
|
||||
} else {
|
||||
tagsStore.sliceTags(0);
|
||||
tagsStore.addTags(info);
|
||||
}
|
||||
};
|
||||
|
||||
const unClose = computed(() => unref(getTabsState).length === 1);
|
||||
|
||||
listenerRouteChange((route) => {
|
||||
if (!route) {
|
||||
return;
|
||||
}
|
||||
console.log('rrr', route);
|
||||
|
||||
if (
|
||||
getTabsState.value.findIndex(
|
||||
(x) => x.currentUrl === route.fullPath || x.path === route.fullPath,
|
||||
) !== -1
|
||||
) {
|
||||
if (activeKeyRef.value !== route.fullPath) {
|
||||
activeKeyRef.value = route.fullPath as string;
|
||||
}
|
||||
} else {
|
||||
const { name } = route;
|
||||
|
||||
const index = route.matched.findIndex((x) => (x.meta ? x.meta.hideChildren : ''));
|
||||
const parentRoute = route.matched[index];
|
||||
|
||||
if (parentRoute) {
|
||||
if (activeKeyRef.value !== route.fullPath) {
|
||||
activeKeyRef.value = route.fullPath as string;
|
||||
}
|
||||
if (route?.meta?.type === 'op') {
|
||||
if (route.matched.length) {
|
||||
if (getTabsState.value.findIndex((x: any) => x.name === parentRoute?.name) === -1) {
|
||||
parentRoute.currentUrl = route.fullPath;
|
||||
tagsStore.addTags(parentRoute);
|
||||
if (route.meta?.keepAlive) {
|
||||
if (keepAliveList.value.findIndex((x) => x === route.name) === -1) {
|
||||
keepAliveStore.addKeepAlive(route.name);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
const info = getTabsState.value.filter(
|
||||
(x: any) => x.name === parentRoute?.name,
|
||||
)[0];
|
||||
if (info) {
|
||||
info.currentUrl = route.fullPath;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (getTabsState.value.findIndex((x: any) => x.name === parentRoute?.name) === -1) {
|
||||
parentRoute.currentUrl = route.fullPath;
|
||||
tagsStore.addTags(parentRoute);
|
||||
if (route.meta?.keepAlive) {
|
||||
if (keepAliveList.value.findIndex((x) => x === route.name) === -1) {
|
||||
keepAliveStore.addKeepAlive(route.name);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
const info = getTabsState.value.filter((x: any) => x.name === parentRoute?.name)[0];
|
||||
if (info) {
|
||||
info.currentUrl = route.fullPath;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
function handleChange(activeKey: any) {
|
||||
loadingClass.value = 'reload';
|
||||
activeKeyRef.value = activeKey;
|
||||
console.log('activeKey', activeKey);
|
||||
router.push(activeKey);
|
||||
// router.replace(activeKey);
|
||||
}
|
||||
|
||||
function handleEdit(targetKey: string, action: string) {
|
||||
console.log('targetKey', targetKey);
|
||||
console.log('activeKeyRef', activeKeyRef.value);
|
||||
if (action === 'remove') {
|
||||
const i = getTabsState.value.findIndex((x) => {
|
||||
if (x.currentUrl) {
|
||||
return x.currentUrl === targetKey;
|
||||
} else {
|
||||
return x.fullPath === targetKey;
|
||||
}
|
||||
});
|
||||
const index = keepAliveList.value.findIndex(
|
||||
(x: string) => x === router.currentRoute.value.name,
|
||||
);
|
||||
tagsStore.spliceTags(i);
|
||||
keepAliveStore.removeKeepAlive(index);
|
||||
if (activeKeyRef.value === targetKey) {
|
||||
const nowTag = getTabsState.value[i === 0 ? 0 : i - 1];
|
||||
if (nowTag) {
|
||||
router.push(nowTag.currentUrl ? nowTag.currentUrl : nowTag.fullPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (unref(unClose)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
return {
|
||||
dealMenus,
|
||||
leftMenuTrigger,
|
||||
keepAliveList,
|
||||
loadingClass,
|
||||
reload,
|
||||
visible,
|
||||
unClose,
|
||||
handleEdit,
|
||||
handleChange,
|
||||
activeKeyRef,
|
||||
closeMenu,
|
||||
getTabsState,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
#fullTab {
|
||||
width: 100% !important;
|
||||
}
|
||||
.iconLoading {
|
||||
color: @primary-color;
|
||||
margin-right: -4px;
|
||||
margin-left: 2px;
|
||||
font-size: 13px;
|
||||
animation: loadingCircle 1s linear;
|
||||
}
|
||||
:deep(.ant-tabs-tab-remove) {
|
||||
margin-left: 2px !important;
|
||||
font-size: 14px !important;
|
||||
margin-right: -8px;
|
||||
}
|
||||
.reload {
|
||||
color: rgba(0, 0, 0, 0.45);
|
||||
margin-right: -4px;
|
||||
margin-left: 2px;
|
||||
font-size: 13px;
|
||||
&:hover {
|
||||
color: @primary-color;
|
||||
}
|
||||
}
|
||||
.ns-tabs {
|
||||
width: calc(100% - 208px);
|
||||
padding: 0 12px;
|
||||
min-height: 32px;
|
||||
background: #ffffff;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
position: fixed;
|
||||
top: 48px;
|
||||
z-index: 505;
|
||||
}
|
||||
#iframeApplication .ns-tabs {
|
||||
top: 0px;
|
||||
}
|
||||
|
||||
.ns-tabs-collapsed {
|
||||
width: calc(100% - 40px);
|
||||
padding: 0 12px;
|
||||
min-height: 55px;
|
||||
background: #ffffff;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
position: fixed;
|
||||
top: 48px;
|
||||
z-index: 505;
|
||||
}
|
||||
:deep(.ant-tabs-nav) {
|
||||
margin: 0px !important;
|
||||
}
|
||||
.contextmenu {
|
||||
margin: 0;
|
||||
min-width: 135px;
|
||||
background: #fff;
|
||||
z-index: 3000;
|
||||
position: absolute;
|
||||
list-style-type: none;
|
||||
padding: 5px 0;
|
||||
border-radius: 2px;
|
||||
font-size: 12px;
|
||||
font-weight: 400;
|
||||
color: #333;
|
||||
border-radius: 2px;
|
||||
outline: none;
|
||||
box-shadow: 0 2px 8px rgb(0 0 0 / 15%);
|
||||
li {
|
||||
margin: 0;
|
||||
padding: 7px 25px;
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
background: #eee;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
455
lib/saas/view/system/layout/tags.vue
Normal file
455
lib/saas/view/system/layout/tags.vue
Normal file
@@ -0,0 +1,455 @@
|
||||
<!-- @format -->
|
||||
|
||||
<template>
|
||||
<div :class="leftMenuTrigger ? 'nav-shutters-collapsed' : 'nav-shutters'">
|
||||
<div class="shutters-left">
|
||||
<ns-icon
|
||||
@click.stop="moveTags('right')"
|
||||
:id="isdisabledr ? '' : 'disabled'"
|
||||
name="fastBackward"
|
||||
class="icon_kj" />
|
||||
</div>
|
||||
<div class="fatherTags">
|
||||
<ul id="tag-list" ref="tags">
|
||||
<div v-for="(item, index) in tagList" :key="index" @click="clickTag(item)">
|
||||
<li
|
||||
@click.right="openMenu($event, index)"
|
||||
:id="selectTags.path === item.path ? 'ischoice' : ''">
|
||||
<span>{{ isdisabledl || isdisabledr ? item.title.substring(0, 4) : item.title }}</span>
|
||||
<ns-icon
|
||||
style="min-width: 12px; min-height: 12px; color: rgba(0, 0, 0, 0.45)"
|
||||
v-if="item.path !== baseHeader.redirect"
|
||||
name="close"
|
||||
@click="delTags($event, item)" /> </li
|
||||
></div>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="shutters-right">
|
||||
<ns-icon
|
||||
@click.stop="moveTags('left')"
|
||||
:id="isdisabledl ? '' : 'disabled'"
|
||||
name="fastBackward"
|
||||
class="icon_kt" />
|
||||
</div>
|
||||
<ul v-show="visible" :style="{ left: left + 'px', top: top + 'px' }" class="contextmenu">
|
||||
<li @click="closeAllMenu">关闭所有标签页</li>
|
||||
<li @click="closeOhterMenu">关闭其他标签页</li>
|
||||
</ul>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { computed, defineComponent, inject, ref } from 'vue';
|
||||
import { tagsClass } from '../application';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { useKeepAlive } from '/nerv-base/store/modules/keepAlive';
|
||||
import { useTags } from '/nerv-base/store/modules/tags';
|
||||
import { Emitter } from 'mitt';
|
||||
import { emitEvents } from '/nerv-base/view/system/application.d';
|
||||
export default defineComponent({
|
||||
name: 'NsTags',
|
||||
setup() {
|
||||
const mittEmit = inject('mittEmit') as Emitter<emitEvents>;
|
||||
const visible = ref<boolean>(false);
|
||||
const leftMenuTrigger = ref(false);
|
||||
mittEmit.on('leftMenuTrigger', (val) => {
|
||||
console.log('leftMenuTrigger', val);
|
||||
leftMenuTrigger.value = val;
|
||||
});
|
||||
// const tagList = ref<tagsClass[]>([]);
|
||||
const rightNumber = ref<number>(0);
|
||||
const tagsStore = useTags();
|
||||
const router = useRouter();
|
||||
const keepAliveStore = useKeepAlive();
|
||||
const keepAliveList = computed(() => {
|
||||
return keepAliveStore.getKeepAlive;
|
||||
});
|
||||
const tagList = computed(() => tagsStore.getTags);
|
||||
//右键关闭所有标签页
|
||||
const closeAllMenu = () => {
|
||||
tagsStore.sliceTags(0);
|
||||
};
|
||||
//右键关闭其他标签页
|
||||
const closeOhterMenu = () => {
|
||||
let choicetag = tagList.value[rightNumber.value];
|
||||
tagsStore.sliceTags(0);
|
||||
tagsStore.addTags(choicetag);
|
||||
};
|
||||
const selectTags = inject('selectTags');
|
||||
console.log(selectTags);
|
||||
const baseHeader = inject('baseHeader');
|
||||
const closeMenu = () => {
|
||||
visible.value = false;
|
||||
};
|
||||
const clickTag = (e: any) => {
|
||||
if (e.params) {
|
||||
router.push({
|
||||
name: e.name,
|
||||
query: e.params,
|
||||
});
|
||||
} else {
|
||||
router.push({
|
||||
name: e.name,
|
||||
});
|
||||
}
|
||||
};
|
||||
return {
|
||||
leftMenuTrigger,
|
||||
router,
|
||||
clickTag,
|
||||
visible,
|
||||
tagList,
|
||||
tagsStore,
|
||||
selectTags,
|
||||
keepAliveStore,
|
||||
closeAllMenu,
|
||||
closeOhterMenu,
|
||||
closeMenu,
|
||||
baseHeader,
|
||||
keepAliveList,
|
||||
rightNumber,
|
||||
left: ref<number>(0),
|
||||
top: ref<number>(0),
|
||||
number: ref<number>(0),
|
||||
currentPage: ref<number>(0),
|
||||
isdisabledl: ref<boolean>(false),
|
||||
isdisabledr: ref<boolean>(false),
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
tagList: {
|
||||
deep: true,
|
||||
handler() {
|
||||
this.resizeButtom();
|
||||
},
|
||||
},
|
||||
selectTags: {
|
||||
deep: true,
|
||||
handler(val) {
|
||||
this.delWithTags(val);
|
||||
this.move(val.path);
|
||||
},
|
||||
},
|
||||
visible(val) {
|
||||
val
|
||||
? document.body.addEventListener('click', this.closeMenu)
|
||||
: document.body.removeEventListener('click', this.closeMenu);
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.delWithTags(this.selectTags);
|
||||
this.move(this.selectTags.path);
|
||||
this.resizeButtom();
|
||||
window.onresize = () => {
|
||||
this.resizeButtom();
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
//处理tags
|
||||
delWithTags(val: any) {
|
||||
if (this.tagList.findIndex((item) => item.path === val.path) === -1) {
|
||||
// if (val.meta?.keepAlive) {
|
||||
// if (this.keepAliveList.findIndex((x) => x === val.name) === -1) {
|
||||
// this.keepAliveStore.addKeepAlive(val.name);
|
||||
// }
|
||||
// }
|
||||
val.path === this.baseHeader.redirect
|
||||
? this.tagsStore.unshiftTags({ title: val.meta.title, path: val.path, name: val.name })
|
||||
: this.tagsStore.addTags({ title: val.meta.title, path: val.path, name: val.name });
|
||||
this.$nextTick(() => {
|
||||
const arr = Array.prototype.slice.call((this.$refs.tags as HTMLElement).children);
|
||||
arr[arr.length - 1].id = 'moveIn';
|
||||
});
|
||||
}
|
||||
},
|
||||
openMenu(e: any, i: number) {
|
||||
e.preventDefault();
|
||||
if (i !== 0) {
|
||||
this.rightNumber = i;
|
||||
const menuMinWidth = 100;
|
||||
const offsetLeft = this.$el.getBoundingClientRect().left;
|
||||
const offsetTop = this.$el.getBoundingClientRect().top;
|
||||
let offset = 0;
|
||||
const str = (this.$refs.tags as HTMLElement).style.transform;
|
||||
if (str !== '') {
|
||||
offset = str.match(/\d+/)[0];
|
||||
}
|
||||
this.left = offsetLeft + menuMinWidth * (i - 1) - 50 - offset;
|
||||
this.top = offsetTop;
|
||||
this.visible = true;
|
||||
}
|
||||
},
|
||||
delTags(e: { stopPropagation: () => void }, val: tagsClass) {
|
||||
e.stopPropagation();
|
||||
|
||||
this.resizeButtom();
|
||||
let i = this.tagList.findIndex((x) => x.path === val.path);
|
||||
this.tagsStore.spliceTags(i);
|
||||
// const index = this.keepAliveList.value.findIndex(
|
||||
// (x: string) => x === this.router.currentRoute.value.name,
|
||||
// );
|
||||
// this.keepAliveStore.removeKeepAlive(index);
|
||||
if (val.path === this.selectTags.path) {
|
||||
// this.selectTags.path = this.tagList[i === 0 ? 0 : i - 1].path;
|
||||
this.$router.push(this.tagList[i === 0 ? 0 : i - 1].path);
|
||||
}
|
||||
},
|
||||
moveTags(val: string) {
|
||||
switch (val) {
|
||||
case 'left':
|
||||
this.currentPage += 1;
|
||||
this.buttomRealize();
|
||||
break;
|
||||
case 'right':
|
||||
this.currentPage -= 1;
|
||||
this.buttomRealize();
|
||||
break;
|
||||
}
|
||||
},
|
||||
buttomRealize() {
|
||||
this.$nextTick(() => {
|
||||
const clientWidth = (this.$refs.tags as HTMLElement).clientWidth;
|
||||
this.number = Math.floor(clientWidth / 100);
|
||||
if (this.number * this.currentPage >= this.tagList.length) {
|
||||
let multiple = Math.floor(this.tagList.length / this.number);
|
||||
this.currentPage =
|
||||
multiple !== 0 && this.number * multiple === this.tagList.length
|
||||
? multiple - 1
|
||||
: multiple;
|
||||
}
|
||||
(this.$refs.tags as HTMLElement).style.cssText = `transform: translateX(${
|
||||
-this.number * 100 * this.currentPage
|
||||
}px)`;
|
||||
this.isdisabledl = (this.currentPage + 1) * this.number < this.tagList.length;
|
||||
this.isdisabledr = this.currentPage > 0;
|
||||
});
|
||||
},
|
||||
move(val: string) {
|
||||
this.$nextTick(() => {
|
||||
const clientWidth = (this.$refs.tags as HTMLElement).clientWidth;
|
||||
this.number = Math.floor(clientWidth / 100);
|
||||
const index = this.tagList.findIndex((x: tagsClass) => x.path === val) + 1;
|
||||
if (index !== 0) {
|
||||
let multiple = Math.floor(index / this.number);
|
||||
multiple = index % this.number === 0 ? multiple - 1 : multiple;
|
||||
this.moveRealize(index, multiple);
|
||||
this.changeButtom(index, multiple);
|
||||
}
|
||||
});
|
||||
},
|
||||
moveRealize(index: number, multiple: number) {
|
||||
index > this.number
|
||||
? ((this.$refs.tags as HTMLElement).style.cssText = `transform: translateX(${
|
||||
this.number * 100 * -multiple
|
||||
}px)`)
|
||||
: ((this.$refs.tags as HTMLElement).style.cssText = `transform: translateX(${
|
||||
this.number * 100 * (index === this.number ? 0 : multiple)
|
||||
}px)`);
|
||||
},
|
||||
resizeButtom() {
|
||||
this.$nextTick(() => {
|
||||
if (this.$refs.tags !== undefined) {
|
||||
const clientWidth = (this.$refs.tags as HTMLElement).clientWidth;
|
||||
if (this.currentPage === 0) {
|
||||
this.isdisabledl = this.tagList.length * 100 > clientWidth;
|
||||
} else {
|
||||
this.isdisabledl =
|
||||
(this.tagList.length - this.number * this.currentPage) * 100 > clientWidth;
|
||||
if (this.tagList.length * 100 < clientWidth) {
|
||||
(this.$refs.tags as HTMLElement).style.cssText = `transform: translateX(${0}px)`;
|
||||
this.isdisabledr = false;
|
||||
this.currentPage = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
changeButtom(index: number, multiple: number) {
|
||||
if (index !== this.number * multiple) {
|
||||
this.isdisabledl = this.tagList.length > (multiple + 1) * this.number;
|
||||
this.isdisabledr = multiple > 0;
|
||||
this.currentPage = multiple;
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.nav-shutters {
|
||||
width: calc(100% - 208px);
|
||||
// padding: 0 12px;
|
||||
min-height: 32px;
|
||||
height: 32px;
|
||||
background: #ffffff;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
position: fixed;
|
||||
top: 48px;
|
||||
z-index: 505;
|
||||
border-bottom: 1px solid rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
.nav-shutters-collapsed {
|
||||
width: calc(100% - 40px);
|
||||
// padding: 0 12px;
|
||||
min-height: 32px;
|
||||
height: 32px;
|
||||
background: #ffffff;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
position: fixed;
|
||||
top: 48px;
|
||||
z-index: 505;
|
||||
}
|
||||
.shutters-left {
|
||||
height: @ns-nav-shutters-height;
|
||||
width: @ns-nav-shutters-height;
|
||||
border-right: 1px solid #f0f2f5;
|
||||
min-width: 32px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
.icon_kj {
|
||||
mix-blend-mode: normal;
|
||||
cursor: pointer;
|
||||
width: 12px !important;
|
||||
height: 12px !important;
|
||||
}
|
||||
}
|
||||
.shutters-right {
|
||||
height: @ns-nav-shutters-height;
|
||||
width: @ns-nav-shutters-height;
|
||||
border-left: 1px solid #f0f2f5;
|
||||
cursor: pointer;
|
||||
min-width: 32px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
.icon_kt {
|
||||
cursor: pointer;
|
||||
width: 12px !important;
|
||||
height: 12px !important;
|
||||
transform: rotateY(180deg);
|
||||
}
|
||||
}
|
||||
#disabled {
|
||||
pointer-events: none;
|
||||
cursor: default;
|
||||
opacity: 0.524902;
|
||||
}
|
||||
|
||||
.fatherTags {
|
||||
width: calc(100% - 64px);
|
||||
height: 31px;
|
||||
min-width: 400px;
|
||||
overflow: hidden;
|
||||
}
|
||||
#moveIn {
|
||||
animation: move-in 0.5s;
|
||||
}
|
||||
|
||||
#tag-list {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
justify-content: left;
|
||||
list-style-type: none;
|
||||
padding: 0;
|
||||
transition: transform 1s;
|
||||
|
||||
a:hover {
|
||||
background: #f5f9fb;
|
||||
color: @layout-sider-arrow-color;
|
||||
}
|
||||
/* will-change: transform; 增
|
||||
加动画流畅度但较耗性能,推荐动态添加该属性 动画执行完再去除该属性 */
|
||||
li {
|
||||
min-width: 100px;
|
||||
padding: 0 5px 0 16px;
|
||||
text-align: center;
|
||||
justify-content: center;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
border-right: 1px solid #ecedef;
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
height: 100%;
|
||||
color: #808d96;
|
||||
span {
|
||||
font-size: 14px;
|
||||
color: @text-color-secondary;
|
||||
margin-right: 6px;
|
||||
height: 20px;
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
}
|
||||
@keyframes move-in {
|
||||
0% {
|
||||
transform: translateY(-50px);
|
||||
opacity: 0;
|
||||
}
|
||||
100% {
|
||||
transform: translateY(0px);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
#ischoice {
|
||||
background-color: #f5f9fb;
|
||||
}
|
||||
#ischoice span {
|
||||
color: @text-color !important;
|
||||
}
|
||||
.checkBox {
|
||||
height: 200px;
|
||||
width: 100px;
|
||||
background-color: #fff;
|
||||
box-shadow: 1px 3px 3px 0px #b5deff;
|
||||
border-radius: 5px;
|
||||
position: absolute;
|
||||
left: 599px;
|
||||
top: 99px;
|
||||
z-index: 999;
|
||||
}
|
||||
.contextmenu {
|
||||
margin: 0;
|
||||
min-width: 135px;
|
||||
background: #fff;
|
||||
z-index: 3000;
|
||||
position: absolute;
|
||||
list-style-type: none;
|
||||
padding: 5px 0;
|
||||
border-radius: 2px;
|
||||
font-size: 12px;
|
||||
font-weight: 400;
|
||||
color: #333;
|
||||
border-radius: 2px;
|
||||
outline: none;
|
||||
box-shadow: 0 2px 8px rgb(0 0 0 / 15%);
|
||||
li {
|
||||
margin: 0;
|
||||
padding: 7px 25px;
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
background: #eee;
|
||||
}
|
||||
}
|
||||
}
|
||||
@keyframes move-down {
|
||||
0% {
|
||||
transform: translateY(-50px) scale(0);
|
||||
opacity: 0;
|
||||
}
|
||||
100% {
|
||||
transform: translateY(0px) scale(1);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
.tagTitle {
|
||||
max-width: 56px;
|
||||
text-align: center;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
transition: all 1.5s;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user