refactor(projects): 页面布局调整为rdms风格
This commit is contained in:
@@ -16,18 +16,18 @@ defineOptions({ name: 'BaseLayout' });
|
||||
|
||||
const appStore = useAppStore();
|
||||
const themeStore = useThemeStore();
|
||||
const { childLevelMenus, isActiveFirstLevelMenuHasChildren } = setupMixMenuContext();
|
||||
const { childLevelMenus } = setupMixMenuContext();
|
||||
|
||||
const GlobalMenu = defineAsyncComponent(() => import('../modules/global-menu/index.vue'));
|
||||
|
||||
const layoutMode = computed(() => {
|
||||
const vertical: LayoutMode = 'vertical';
|
||||
const horizontal: LayoutMode = 'horizontal';
|
||||
return themeStore.layout.mode.includes(vertical) ? vertical : horizontal;
|
||||
return themeStore.layoutMode.includes(vertical) ? vertical : horizontal;
|
||||
});
|
||||
|
||||
const headerProps = computed(() => {
|
||||
const { mode, reverseHorizontalMix } = themeStore.layout;
|
||||
const mode = themeStore.layoutMode;
|
||||
|
||||
const headerPropsConfig: Record<UnionKey.ThemeLayoutMode, App.Global.HeaderProps> = {
|
||||
vertical: {
|
||||
@@ -48,31 +48,26 @@ const headerProps = computed(() => {
|
||||
'horizontal-mix': {
|
||||
showLogo: true,
|
||||
showMenu: true,
|
||||
showMenuToggler: reverseHorizontalMix && isActiveFirstLevelMenuHasChildren.value
|
||||
showMenuToggler: false
|
||||
}
|
||||
};
|
||||
|
||||
return headerPropsConfig[mode];
|
||||
});
|
||||
|
||||
const siderVisible = computed(() => themeStore.layout.mode !== 'horizontal');
|
||||
const siderVisible = computed(() => themeStore.layoutMode !== 'horizontal');
|
||||
|
||||
const isVerticalMix = computed(() => themeStore.layout.mode === 'vertical-mix');
|
||||
const isVerticalMix = computed(() => themeStore.layoutMode === 'vertical-mix');
|
||||
|
||||
const isHorizontalMix = computed(() => themeStore.layout.mode === 'horizontal-mix');
|
||||
const isHorizontalMix = computed(() => themeStore.layoutMode === 'horizontal-mix');
|
||||
|
||||
const siderWidth = computed(() => getSiderWidth());
|
||||
|
||||
const siderCollapsedWidth = computed(() => getSiderCollapsedWidth());
|
||||
|
||||
function getSiderWidth() {
|
||||
const { reverseHorizontalMix } = themeStore.layout;
|
||||
const { width, mixWidth, mixChildMenuWidth } = themeStore.sider;
|
||||
|
||||
if (isHorizontalMix.value && reverseHorizontalMix) {
|
||||
return isActiveFirstLevelMenuHasChildren.value ? width : 0;
|
||||
}
|
||||
|
||||
let w = isVerticalMix.value || isHorizontalMix.value ? mixWidth : width;
|
||||
|
||||
if (isVerticalMix.value && appStore.mixSiderFixed && childLevelMenus.value.length) {
|
||||
@@ -83,13 +78,8 @@ function getSiderWidth() {
|
||||
}
|
||||
|
||||
function getSiderCollapsedWidth() {
|
||||
const { reverseHorizontalMix } = themeStore.layout;
|
||||
const { collapsedWidth, mixCollapsedWidth, mixChildMenuWidth } = themeStore.sider;
|
||||
|
||||
if (isHorizontalMix.value && reverseHorizontalMix) {
|
||||
return isActiveFirstLevelMenuHasChildren.value ? collapsedWidth : 0;
|
||||
}
|
||||
|
||||
let w = isVerticalMix.value || isHorizontalMix.value ? mixCollapsedWidth : collapsedWidth;
|
||||
|
||||
if (isVerticalMix.value && appStore.mixSiderFixed && childLevelMenus.value.length) {
|
||||
@@ -110,7 +100,7 @@ function getSiderCollapsedWidth() {
|
||||
:full-content="appStore.fullContent"
|
||||
:fixed-top="themeStore.fixedHeaderAndTab"
|
||||
:header-height="themeStore.header.height"
|
||||
:tab-visible="themeStore.tab.visible"
|
||||
:tab-visible="themeStore.tabVisible"
|
||||
:tab-height="themeStore.tab.height"
|
||||
:content-class="appStore.contentXScrollable ? 'overflow-x-hidden' : ''"
|
||||
:sider-visible="siderVisible"
|
||||
|
||||
@@ -8,7 +8,6 @@ import { useRouterPush } from '@/hooks/common/router';
|
||||
export const { setupStore: setupMixMenuContext, useStore: useMixMenuContext } = useContext('mix-menu', useMixMenu);
|
||||
|
||||
function useMixMenu() {
|
||||
const route = useRoute();
|
||||
const routeStore = useRouteStore();
|
||||
const { selectedKey } = useMenu();
|
||||
|
||||
@@ -19,9 +18,9 @@ function useMixMenu() {
|
||||
}
|
||||
|
||||
function getActiveFirstLevelMenuKey() {
|
||||
const [firstLevelRouteName] = selectedKey.value.split('_');
|
||||
const [firstLevelMenuKey = ''] = routeStore.getSelectedMenuKeyPath(selectedKey.value);
|
||||
|
||||
setActiveFirstLevelMenuKey(firstLevelRouteName);
|
||||
setActiveFirstLevelMenuKey(firstLevelMenuKey);
|
||||
}
|
||||
|
||||
const allMenus = computed<App.Global.Menu[]>(() => routeStore.menus);
|
||||
@@ -49,7 +48,7 @@ function useMixMenu() {
|
||||
});
|
||||
|
||||
watch(
|
||||
() => route.name,
|
||||
[selectedKey, allMenus],
|
||||
() => {
|
||||
getActiveFirstLevelMenuKey();
|
||||
},
|
||||
|
||||
@@ -14,9 +14,12 @@ withDefaults(defineProps<Props>(), {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<RouterLink to="/" class="w-full flex-center nowrap-hidden">
|
||||
<SystemLogo class="text-32px text-primary" />
|
||||
<h2 v-show="showTitle" class="pl-8px text-16px text-primary font-bold transition duration-300 ease-in-out">
|
||||
<RouterLink to="/" class="h-full w-full flex-y-center justify-start gap-8px nowrap-hidden px-12px">
|
||||
<SystemLogo class="shrink-0 text-32px text-primary" />
|
||||
<h2
|
||||
v-show="showTitle"
|
||||
class="min-w-0 flex-1-hidden ellipsis-text text-16px text-primary font-bold transition duration-300 ease-in-out"
|
||||
>
|
||||
{{ $t('system.title') }}
|
||||
</h2>
|
||||
</RouterLink>
|
||||
|
||||
@@ -7,7 +7,6 @@ import VerticalMenu from './modules/vertical-menu.vue';
|
||||
import VerticalMixMenu from './modules/vertical-mix-menu.vue';
|
||||
import HorizontalMenu from './modules/horizontal-menu.vue';
|
||||
import HorizontalMixMenu from './modules/horizontal-mix-menu.vue';
|
||||
import ReversedHorizontalMixMenu from './modules/reversed-horizontal-mix-menu.vue';
|
||||
|
||||
defineOptions({
|
||||
name: 'GlobalMenu'
|
||||
@@ -21,13 +20,13 @@ const activeMenu = computed(() => {
|
||||
vertical: VerticalMenu,
|
||||
'vertical-mix': VerticalMixMenu,
|
||||
horizontal: HorizontalMenu,
|
||||
'horizontal-mix': themeStore.layout.reverseHorizontalMix ? ReversedHorizontalMixMenu : HorizontalMixMenu
|
||||
'horizontal-mix': HorizontalMixMenu
|
||||
};
|
||||
|
||||
return menuMap[themeStore.layout.mode];
|
||||
return menuMap[themeStore.layoutMode];
|
||||
});
|
||||
|
||||
const reRenderVertical = computed(() => themeStore.layout.mode === 'vertical' && appStore.isMobile);
|
||||
const reRenderVertical = computed(() => themeStore.layoutMode === 'vertical' && appStore.isMobile);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
||||
@@ -1,43 +1,111 @@
|
||||
<script setup lang="ts">
|
||||
import type { RouteKey } from '@elegant-router/types';
|
||||
import { computed } from 'vue';
|
||||
import { GLOBAL_HEADER_MENU_ID, GLOBAL_SIDER_MENU_ID } from '@/constants/app';
|
||||
import { useAppStore } from '@/store/modules/app';
|
||||
import { useRouteStore } from '@/store/modules/route';
|
||||
import { useThemeStore } from '@/store/modules/theme';
|
||||
import { useRouterPush } from '@/hooks/common/router';
|
||||
import FirstLevelMenu from '../components/first-level-menu.vue';
|
||||
import { useMenu, useMixMenuContext } from '../../../context';
|
||||
import MenuItem from '../components/menu-item.vue';
|
||||
|
||||
defineOptions({
|
||||
name: 'HorizontalMixMenu'
|
||||
});
|
||||
|
||||
const appStore = useAppStore();
|
||||
const routeStore = useRouteStore();
|
||||
const themeStore = useThemeStore();
|
||||
const { routerPushByKeyWithMetaQuery } = useRouterPush();
|
||||
const { allMenus, childLevelMenus, activeFirstLevelMenuKey, setActiveFirstLevelMenuKey } = useMixMenuContext();
|
||||
const { selectedKeyDummy, handleSelect } = useMenu();
|
||||
const { selectedKey } = useMenu();
|
||||
|
||||
const activeFirstLevelMenu = computed(
|
||||
() => allMenus.value.find(menu => menu.key === activeFirstLevelMenuKey.value) || null
|
||||
);
|
||||
const headerMenuHeight = computed(() => `${themeStore.header.height}px`);
|
||||
const selectedMenuKeyPath = computed(() => routeStore.getSelectedMenuKeyPath(selectedKey.value));
|
||||
|
||||
function handleSelectMixMenu(menu: App.Global.Menu) {
|
||||
setActiveFirstLevelMenuKey(menu.key);
|
||||
|
||||
if (!menu.children?.length) {
|
||||
routerPushByKeyWithMetaQuery(menu.routeKey);
|
||||
routerPushByKeyWithMetaQuery(menu.routeKey);
|
||||
}
|
||||
|
||||
function handleClickNavItem(menu: App.Global.Menu) {
|
||||
routerPushByKeyWithMetaQuery(menu.routeKey);
|
||||
}
|
||||
|
||||
function handleClickDomainAnchor() {
|
||||
if (!activeFirstLevelMenu.value) {
|
||||
return;
|
||||
}
|
||||
|
||||
routerPushByKeyWithMetaQuery(activeFirstLevelMenu.value.routeKey);
|
||||
}
|
||||
|
||||
function isMenuActive(menu: App.Global.Menu) {
|
||||
return selectedMenuKeyPath.value.includes(menu.key);
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Teleport :to="`#${GLOBAL_HEADER_MENU_ID}`">
|
||||
<ElMenu
|
||||
ellipsis
|
||||
class="w-full"
|
||||
mode="horizontal"
|
||||
:default-active="selectedKeyDummy"
|
||||
@select="val => handleSelect(val as RouteKey)"
|
||||
>
|
||||
<MenuItem v-for="item in childLevelMenus" :key="item.key" :item="item" :index="item.key" />
|
||||
</ElMenu>
|
||||
<div class="mix-header-nav size-full min-w-0 flex-y-center">
|
||||
<button
|
||||
v-if="activeFirstLevelMenu"
|
||||
type="button"
|
||||
class="domain-anchor h-full flex-y-center gap-8px px-8px text-left"
|
||||
@click="handleClickDomainAnchor"
|
||||
>
|
||||
<component :is="activeFirstLevelMenu.icon" v-if="activeFirstLevelMenu.icon" class="text-icon" />
|
||||
<span class="domain-anchor__label">{{ activeFirstLevelMenu.label }}</span>
|
||||
</button>
|
||||
<div v-if="childLevelMenus.length" class="mx-12px h-20px w-1px shrink-0 bg-[var(--el-border-color)]"></div>
|
||||
<div v-if="childLevelMenus.length" class="header-nav-list h-full min-w-0 flex-1">
|
||||
<template v-for="item in childLevelMenus" :key="item.key">
|
||||
<button
|
||||
v-if="!item.children?.length"
|
||||
type="button"
|
||||
class="header-nav-item"
|
||||
:class="{ 'is-active': isMenuActive(item) }"
|
||||
@click="handleClickNavItem(item)"
|
||||
>
|
||||
<span class="header-nav-item__label">{{ item.label }}</span>
|
||||
</button>
|
||||
<ElDropdown
|
||||
v-else
|
||||
trigger="hover"
|
||||
placement="bottom"
|
||||
popper-class="header-nav-dropdown"
|
||||
:show-timeout="120"
|
||||
:hide-timeout="120"
|
||||
:teleported="true"
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
class="header-nav-item header-nav-item--dropdown"
|
||||
:class="{ 'is-active': isMenuActive(item) }"
|
||||
>
|
||||
<span class="header-nav-item__label">{{ item.label }}</span>
|
||||
<icon-ep:arrow-down class="header-nav-item__arrow" />
|
||||
</button>
|
||||
<template #dropdown>
|
||||
<ElDropdownMenu>
|
||||
<ElDropdownItem
|
||||
v-for="child in item.children"
|
||||
:key="child.key"
|
||||
class="header-nav-dropdown__item"
|
||||
:class="{ 'is-active-route': isMenuActive(child) }"
|
||||
@click="handleClickNavItem(child)"
|
||||
>
|
||||
{{ child.label }}
|
||||
</ElDropdownItem>
|
||||
</ElDropdownMenu>
|
||||
</template>
|
||||
</ElDropdown>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</Teleport>
|
||||
<Teleport :to="`#${GLOBAL_SIDER_MENU_ID}`">
|
||||
<FirstLevelMenu
|
||||
@@ -52,4 +120,138 @@ function handleSelectMixMenu(menu: App.Global.Menu) {
|
||||
</Teleport>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
||||
<style scoped>
|
||||
.mix-header-nav {
|
||||
height: v-bind(headerMenuHeight);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.domain-anchor {
|
||||
appearance: none;
|
||||
-webkit-appearance: none;
|
||||
border: none;
|
||||
background: transparent;
|
||||
margin: 0;
|
||||
padding-top: 0;
|
||||
padding-bottom: 0;
|
||||
font: inherit;
|
||||
flex-shrink: 0;
|
||||
min-width: 0;
|
||||
line-height: 1;
|
||||
color: var(--el-text-color-primary);
|
||||
}
|
||||
|
||||
.domain-anchor:hover {
|
||||
color: var(--el-color-primary);
|
||||
}
|
||||
|
||||
.domain-anchor__label {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
max-width: 12rem;
|
||||
line-height: 1;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.header-nav-list {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.header-nav-item {
|
||||
appearance: none;
|
||||
-webkit-appearance: none;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 6px;
|
||||
position: relative;
|
||||
margin: 0;
|
||||
height: 100%;
|
||||
flex-shrink: 0;
|
||||
padding: 0 14px;
|
||||
border: none;
|
||||
background: transparent;
|
||||
font: inherit;
|
||||
line-height: 1;
|
||||
color: var(--el-text-color-primary);
|
||||
white-space: nowrap;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.header-nav-item:hover {
|
||||
color: var(--el-color-primary);
|
||||
}
|
||||
|
||||
.header-nav-item__label {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.header-nav-item__arrow {
|
||||
font-size: 12px;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.header-nav-item.is-active {
|
||||
color: var(--el-color-primary);
|
||||
}
|
||||
|
||||
.header-nav-item.is-active::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: 12px;
|
||||
right: 12px;
|
||||
bottom: 0;
|
||||
height: 2px;
|
||||
border-radius: 999px;
|
||||
background-color: var(--el-color-primary);
|
||||
}
|
||||
|
||||
:global(.header-nav-dropdown.el-popper) {
|
||||
padding: 0;
|
||||
border: none;
|
||||
border-radius: 14px;
|
||||
background-color: rgb(255 255 255 / 98%);
|
||||
box-shadow:
|
||||
0 12px 28px rgb(15 23 42 / 10%),
|
||||
0 2px 8px rgb(15 23 42 / 6%);
|
||||
backdrop-filter: blur(8px);
|
||||
}
|
||||
|
||||
:global(.header-nav-dropdown .el-popper__arrow) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
:global(.header-nav-dropdown .el-dropdown-menu) {
|
||||
padding: 8px;
|
||||
border: 1px solid rgb(226 232 240 / 90%);
|
||||
border-radius: 14px;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
:global(.header-nav-dropdown .el-dropdown-menu__item) {
|
||||
height: 40px;
|
||||
margin: 2px 0;
|
||||
padding: 0 12px;
|
||||
border-radius: 10px;
|
||||
font-size: 14px;
|
||||
line-height: 40px;
|
||||
color: rgb(15 23 42 / 88%);
|
||||
}
|
||||
|
||||
:global(.header-nav-dropdown .el-dropdown-menu__item:hover) {
|
||||
background-color: rgb(99 102 241 / 8%);
|
||||
}
|
||||
|
||||
:global(.header-nav-dropdown .el-dropdown-menu__item.is-active-route) {
|
||||
color: var(--el-color-primary);
|
||||
background-color: rgb(99 102 241 / 10%);
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -10,8 +10,8 @@ defineOptions({ name: 'GlobalSider' });
|
||||
const appStore = useAppStore();
|
||||
const themeStore = useThemeStore();
|
||||
|
||||
const isVerticalMix = computed(() => themeStore.layout.mode === 'vertical-mix');
|
||||
const isHorizontalMix = computed(() => themeStore.layout.mode === 'horizontal-mix');
|
||||
const isVerticalMix = computed(() => themeStore.layoutMode === 'vertical-mix');
|
||||
const isHorizontalMix = computed(() => themeStore.layoutMode === 'horizontal-mix');
|
||||
const darkMenu = computed(() => !themeStore.darkMode && !isHorizontalMix.value && themeStore.sider.inverted);
|
||||
const showLogo = computed(() => !isVerticalMix.value && !isHorizontalMix.value);
|
||||
const menuWrapperClass = computed(() => (showLogo.value ? 'flex-1-hidden' : 'h-full'));
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
import { useAppStore } from '@/store/modules/app';
|
||||
import { $t } from '@/locales';
|
||||
import DarkMode from './modules/dark-mode.vue';
|
||||
import LayoutMode from './modules/layout-mode.vue';
|
||||
import ThemeColor from './modules/theme-color.vue';
|
||||
import PageFun from './modules/page-fun.vue';
|
||||
import ConfigOperation from './modules/config-operation.vue';
|
||||
@@ -15,7 +14,6 @@ const appStore = useAppStore();
|
||||
<template>
|
||||
<ElDrawer v-model="appStore.themeDrawerVisible" :title="$t('theme.themeDrawerTitle')" :size="360">
|
||||
<DarkMode />
|
||||
<LayoutMode />
|
||||
<ThemeColor />
|
||||
<PageFun />
|
||||
<template #footer>
|
||||
|
||||
@@ -27,7 +27,7 @@ function handleColourWeaknessChange(value: boolean) {
|
||||
themeStore.setColourWeakness(value);
|
||||
}
|
||||
|
||||
const showSiderInverted = computed(() => !themeStore.darkMode && themeStore.layout.mode.includes('vertical'));
|
||||
const showSiderInverted = computed(() => !themeStore.darkMode && themeStore.layoutMode.includes('vertical'));
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue';
|
||||
import { themePageAnimationModeOptions, themeScrollModeOptions, themeTabModeOptions } from '@/constants/app';
|
||||
import { themePageAnimationModeOptions, themeScrollModeOptions } from '@/constants/app';
|
||||
import { useThemeStore } from '@/store/modules/theme';
|
||||
import { translateOptions } from '@/utils/common';
|
||||
import { $t } from '@/locales';
|
||||
@@ -10,7 +10,7 @@ defineOptions({ name: 'PageFun' });
|
||||
|
||||
const themeStore = useThemeStore();
|
||||
|
||||
const layoutMode = computed(() => themeStore.layout.mode);
|
||||
const layoutMode = computed(() => themeStore.layoutMode);
|
||||
|
||||
const isMixLayoutMode = computed(() => layoutMode.value.includes('mix'));
|
||||
|
||||
@@ -55,25 +55,6 @@ const isWrapperScrollMode = computed(() => themeStore.layout.scrollMode === 'wra
|
||||
<SettingItem v-if="themeStore.header.breadcrumb.visible" key="4-1" :label="$t('theme.header.breadcrumb.showIcon')">
|
||||
<ElSwitch v-model="themeStore.header.breadcrumb.showIcon" />
|
||||
</SettingItem>
|
||||
<SettingItem key="5" :label="$t('theme.tab.visible')">
|
||||
<ElSwitch v-model="themeStore.tab.visible" />
|
||||
</SettingItem>
|
||||
<SettingItem v-if="themeStore.tab.visible" key="5-1" :label="$t('theme.tab.cache')">
|
||||
<ElSwitch v-model="themeStore.tab.cache" />
|
||||
</SettingItem>
|
||||
<SettingItem v-if="themeStore.tab.visible" key="5-2" :label="$t('theme.tab.height')">
|
||||
<ElInputNumber v-model="themeStore.tab.height" size="small" :step="1" class="w-120px" />
|
||||
</SettingItem>
|
||||
<SettingItem v-if="themeStore.tab.visible" key="5-3" :label="$t('theme.tab.mode.title')">
|
||||
<ElSelect v-model="themeStore.tab.mode" size="small" class="w-120px">
|
||||
<ElOption
|
||||
v-for="{ label, value } in translateOptions(themeTabModeOptions)"
|
||||
:key="value"
|
||||
:label="label"
|
||||
:value="value"
|
||||
/>
|
||||
</ElSelect>
|
||||
</SettingItem>
|
||||
<SettingItem v-if="layoutMode === 'vertical'" key="6-1" :label="$t('theme.sider.width')">
|
||||
<ElInputNumber v-model="themeStore.sider.width" size="small" :step="1" class="w-120px" />
|
||||
</SettingItem>
|
||||
|
||||
@@ -83,11 +83,10 @@ export const useAppStore = defineStore(SetupStoreId.App, () => {
|
||||
if (newValue) {
|
||||
// backup theme setting before is mobile
|
||||
localStg.set('backupThemeSettingBeforeIsMobile', {
|
||||
layout: themeStore.layout.mode,
|
||||
layout: themeStore.layoutMode,
|
||||
siderCollapse: siderCollapse.value
|
||||
});
|
||||
|
||||
themeStore.setThemeLayout('vertical');
|
||||
setSiderCollapse(true);
|
||||
} else {
|
||||
// when is not mobile, recover the backup theme setting
|
||||
@@ -95,7 +94,6 @@ export const useAppStore = defineStore(SetupStoreId.App, () => {
|
||||
|
||||
if (backup) {
|
||||
nextTick(() => {
|
||||
themeStore.setThemeLayout(backup.layout);
|
||||
setSiderCollapse(backup.siderCollapse);
|
||||
|
||||
localStg.remove('backupThemeSettingBeforeIsMobile');
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { computed, effectScope, onScopeDispose, ref, toRefs, watch } from 'vue';
|
||||
import type { Ref } from 'vue';
|
||||
import { usePreferredColorScheme } from '@vueuse/core';
|
||||
import { breakpointsTailwind, useBreakpoints, usePreferredColorScheme } from '@vueuse/core';
|
||||
import { defineStore } from 'pinia';
|
||||
import { getPaletteColorByNumber } from '@sa/color';
|
||||
import { localStg } from '@/utils/storage';
|
||||
@@ -19,6 +19,8 @@ import {
|
||||
export const useThemeStore = defineStore(SetupStoreId.Theme, () => {
|
||||
const scope = effectScope();
|
||||
const osTheme = usePreferredColorScheme();
|
||||
const breakpoints = useBreakpoints(breakpointsTailwind);
|
||||
const isMobile = breakpoints.smaller('sm');
|
||||
|
||||
/** Theme settings */
|
||||
const settings: Ref<App.Theme.ThemeSetting> = ref(initThemeSettings());
|
||||
@@ -51,6 +53,12 @@ export const useThemeStore = defineStore(SetupStoreId.Theme, () => {
|
||||
/** UI theme */
|
||||
const uiTheme = computed(() => getNaiveTheme(themeColors.value, settings.value.recommendColor));
|
||||
|
||||
/** Product layout mode */
|
||||
const layoutMode = computed<UnionKey.ThemeLayoutMode>(() => (isMobile.value ? 'vertical' : 'horizontal-mix'));
|
||||
|
||||
/** Product tab visible */
|
||||
const tabVisible = computed(() => (isMobile.value ? settings.value.tab.visible : false));
|
||||
|
||||
/**
|
||||
* Settings json
|
||||
*
|
||||
@@ -216,6 +224,8 @@ export const useThemeStore = defineStore(SetupStoreId.Theme, () => {
|
||||
darkMode,
|
||||
themeColors,
|
||||
uiTheme,
|
||||
layoutMode,
|
||||
tabVisible,
|
||||
settingsJson,
|
||||
setGrayscale,
|
||||
setColourWeakness,
|
||||
|
||||
@@ -13,7 +13,7 @@ export const themeSettings: App.Theme.ThemeSetting = {
|
||||
},
|
||||
isInfoFollowPrimary: true,
|
||||
layout: {
|
||||
mode: 'vertical',
|
||||
mode: 'horizontal-mix',
|
||||
scrollMode: 'content',
|
||||
reverseHorizontalMix: false
|
||||
},
|
||||
|
||||
26
src/typings/api/system-manage.d.ts
vendored
26
src/typings/api/system-manage.d.ts
vendored
@@ -40,8 +40,8 @@ declare namespace Api {
|
||||
|
||||
type RoleSearchParams = CommonType.RecordNullable<Pick<Role, 'name' | 'code' | 'status'>> &
|
||||
PageParams & {
|
||||
createTime?: string[];
|
||||
};
|
||||
createTime?: string[];
|
||||
};
|
||||
|
||||
type SaveRoleParams = Pick<Role, 'name' | 'code' | 'sort' | 'status'> & {
|
||||
remark?: string | null;
|
||||
@@ -143,12 +143,12 @@ declare namespace Api {
|
||||
|
||||
type UserSearchParams = CommonType.RecordNullable<
|
||||
Pick<User, 'status'> &
|
||||
Pick<PageParams, 'pageNo' | 'pageSize'> & {
|
||||
username?: string;
|
||||
mobile?: string;
|
||||
deptId?: number;
|
||||
roleId?: number;
|
||||
}
|
||||
Pick<PageParams, 'pageNo' | 'pageSize'> & {
|
||||
username?: string;
|
||||
mobile?: string;
|
||||
deptId?: number;
|
||||
roleId?: number;
|
||||
}
|
||||
>;
|
||||
|
||||
type UserList = PageResult<User>;
|
||||
@@ -388,11 +388,11 @@ declare namespace Api {
|
||||
*/
|
||||
type UserManagementRelationQueryReqVO = CommonType.RecordNullable<
|
||||
Pick<UserManagementRelation, 'managerUserId' | 'subordinateUserId'> & {
|
||||
/** 是否来自带人关系的index组件 */
|
||||
fromUserIndex: boolean;
|
||||
/** 部门ID */
|
||||
deptId?: number | null;
|
||||
}
|
||||
/** 是否来自带人关系的index组件 */
|
||||
fromUserIndex: boolean;
|
||||
/** 部门ID */
|
||||
deptId?: number | null;
|
||||
}
|
||||
>;
|
||||
|
||||
/**
|
||||
|
||||
1
src/typings/components.d.ts
vendored
1
src/typings/components.d.ts
vendored
@@ -82,6 +82,7 @@ declare module 'vue' {
|
||||
IconCarbonPlay: typeof import('~icons/carbon/play')['default']
|
||||
IconCarbonStop: typeof import('~icons/carbon/stop')['default']
|
||||
'IconCharm:download': typeof import('~icons/charm/download')['default']
|
||||
'IconEp:arrowDown': typeof import('~icons/ep/arrow-down')['default']
|
||||
IconEpRemoveFilled: typeof import('~icons/ep/remove-filled')['default']
|
||||
IconEpSuccessFilled: typeof import('~icons/ep/success-filled')['default']
|
||||
'IconF7:circleFill': typeof import('~icons/f7/circle-fill')['default']
|
||||
|
||||
@@ -37,6 +37,11 @@ const treeProps = {
|
||||
label: 'name'
|
||||
} as const;
|
||||
|
||||
function applyCheckedKeys(keys: number[]) {
|
||||
checkedKeys.value = [...keys];
|
||||
treeRef.value?.setCheckedKeys(keys);
|
||||
}
|
||||
|
||||
function getTagType(type: Api.SystemManage.MenuType): UI.ThemeColor {
|
||||
const tagMap: Record<Api.SystemManage.MenuType, UI.ThemeColor> = {
|
||||
1: 'info',
|
||||
@@ -83,8 +88,7 @@ function collectExpandableNodeIds(nodes: Api.SystemManage.MenuSimple[]) {
|
||||
|
||||
async function loadRoleMenus() {
|
||||
if (!props.role) {
|
||||
checkedKeys.value = [];
|
||||
treeRef.value?.setCheckedKeys([]);
|
||||
applyCheckedKeys([]);
|
||||
treeRef.value?.filter(filterKeyword.value);
|
||||
return;
|
||||
}
|
||||
@@ -96,14 +100,14 @@ async function loadRoleMenus() {
|
||||
permissionLoading.value = false;
|
||||
|
||||
if (error) {
|
||||
checkedKeys.value = [];
|
||||
treeRef.value?.setCheckedKeys([]);
|
||||
applyCheckedKeys([]);
|
||||
treeRef.value?.filter(filterKeyword.value);
|
||||
return;
|
||||
}
|
||||
|
||||
checkedKeys.value = data;
|
||||
treeRef.value?.setCheckedKeys(data);
|
||||
// Role-menu bindings are exact IDs from the backend, so tree echo must not
|
||||
// cascade parent checks down to unrelated descendants.
|
||||
applyCheckedKeys(data);
|
||||
treeRef.value?.filter(filterKeyword.value);
|
||||
}
|
||||
|
||||
@@ -131,7 +135,7 @@ async function handleSave() {
|
||||
return;
|
||||
}
|
||||
|
||||
checkedKeys.value = menuIds;
|
||||
checkedKeys.value = [...menuIds];
|
||||
|
||||
window.$message?.success($t('common.modifySuccess'));
|
||||
emit('saved');
|
||||
@@ -153,7 +157,7 @@ watch(
|
||||
() => props.menuTree.length,
|
||||
value => {
|
||||
if (value && props.role) {
|
||||
treeRef.value?.setCheckedKeys(checkedKeys.value);
|
||||
applyCheckedKeys(checkedKeys.value);
|
||||
treeRef.value?.filter(filterKeyword.value);
|
||||
}
|
||||
}
|
||||
@@ -203,6 +207,7 @@ watch(
|
||||
ref="treeRef"
|
||||
node-key="id"
|
||||
show-checkbox
|
||||
check-strictly
|
||||
:default-expanded-keys="defaultExpandedKeys"
|
||||
:data="menuTree"
|
||||
:props="treeProps"
|
||||
|
||||
@@ -15,20 +15,21 @@
|
||||
* - 叶子节点:基层员工,没有下级
|
||||
*/
|
||||
|
||||
import {nextTick, onMounted, reactive, ref} from 'vue';
|
||||
import type {ElTree} from 'element-plus';
|
||||
import {ElButton, ElPopconfirm, ElTag} from 'element-plus';
|
||||
import {useBoolean} from '@sa/hooks';
|
||||
import { nextTick, onMounted, reactive, ref } from 'vue';
|
||||
import type { ElTree } from 'element-plus';
|
||||
import { ElButton, ElPopconfirm, ElTag } from 'element-plus';
|
||||
import { useBoolean } from '@sa/hooks';
|
||||
import {
|
||||
fetchBatchDeleteUserManagementRelation,
|
||||
fetchDeleteUserManagementRelation, fetchGetUserListByDeptId,
|
||||
fetchDeleteUserManagementRelation,
|
||||
fetchGetUserListByDeptId,
|
||||
fetchGetUserManagementRelationQuery,
|
||||
fetchGetUserManagementRelationTree,
|
||||
fetchGetUserManagementRelationTree
|
||||
} from '@/service/api';
|
||||
import RelationOperateDialog from './modules/relation-operate-dialog.vue';
|
||||
import RelationSearch from './modules/relation-search.vue';
|
||||
|
||||
defineOptions({name: 'UserManagementRelation'});
|
||||
defineOptions({ name: 'UserManagementRelation' });
|
||||
|
||||
/**
|
||||
* 组件 userQuery 定义
|
||||
@@ -40,8 +41,8 @@ interface userQuery {
|
||||
deptId?: number | null;
|
||||
}
|
||||
|
||||
//从user的index组件访问带人关系,fromUserIndex为true,否则false; dept=100是灿能电力的id
|
||||
const {fromUserIndex = false, deptId = 100} = defineProps<userQuery>()
|
||||
// 从user的index组件访问带人关系,fromUserIndex为true,否则false; dept=100是灿能电力的id
|
||||
const { fromUserIndex = false, deptId = 100 } = defineProps<userQuery>();
|
||||
|
||||
/**
|
||||
* 初始化搜索参数
|
||||
@@ -86,7 +87,7 @@ const treeProps: any = {
|
||||
* 获取用户简单列表,供搜索组件和对话框组件共享使用
|
||||
*/
|
||||
async function loadUserList() {
|
||||
const {data, error} = await fetchGetUserListByDeptId(deptId);
|
||||
const { data, error } = await fetchGetUserListByDeptId(deptId);
|
||||
if (!error) {
|
||||
userList.value = data || [];
|
||||
}
|
||||
@@ -103,10 +104,10 @@ async function loadTreeData() {
|
||||
try {
|
||||
// 默认不是来自user的index组件访问且deptId=100,查询灿能电力及其以下所有部门的用户的带人关系
|
||||
const query: Api.SystemManage.UserManagementRelationQueryReqVO = {
|
||||
fromUserIndex: fromUserIndex,
|
||||
deptId: deptId
|
||||
fromUserIndex,
|
||||
deptId
|
||||
};
|
||||
const {data, error} = await fetchGetUserManagementRelationTree(query);
|
||||
const { data, error } = await fetchGetUserManagementRelationTree(query);
|
||||
|
||||
if (!error) {
|
||||
treeData.value = data || [];
|
||||
@@ -127,7 +128,7 @@ async function loadTreeDataByQuery(query: Api.SystemManage.UserManagementRelatio
|
||||
loading.value = true;
|
||||
|
||||
try {
|
||||
const {data, error} = await fetchGetUserManagementRelationQuery(query);
|
||||
const { data, error } = await fetchGetUserManagementRelationQuery(query);
|
||||
|
||||
if (!error) {
|
||||
treeData.value = data || [];
|
||||
@@ -164,8 +165,8 @@ async function handleSearch() {
|
||||
// 有搜索条件,调用查询接口
|
||||
const query: Api.SystemManage.UserManagementRelationQueryReqVO = {
|
||||
subordinateUserId: searchParams.subordinateUserId,
|
||||
fromUserIndex: fromUserIndex,
|
||||
deptId: deptId
|
||||
fromUserIndex,
|
||||
deptId
|
||||
};
|
||||
await loadTreeDataByQuery(query);
|
||||
} else {
|
||||
@@ -188,7 +189,7 @@ function resetSearchParams() {
|
||||
}
|
||||
|
||||
// 对话框相关状态
|
||||
const {bool: operateVisible, setTrue: openOperateModal, setFalse: closeOperateModal} = useBoolean();
|
||||
const { bool: operateVisible, setTrue: openOperateModal, setFalse: closeOperateModal } = useBoolean();
|
||||
const operateType = ref<UI.TableOperateType>('add');
|
||||
const editingData = ref<Api.SystemManage.UserManagementRelation | null>(null);
|
||||
|
||||
@@ -203,14 +204,14 @@ function openAdd(item?: Api.SystemManage.UserManagementRelationTreeRespVO) {
|
||||
// 否则默认管理者为当前登录用户(在对话框组件中处理)
|
||||
editingData.value = item
|
||||
? {
|
||||
id: null,
|
||||
managerUserId: item.userId,
|
||||
subordinateUserId: null,
|
||||
effectiveFrom: null,
|
||||
effectiveUntil: null,
|
||||
remark: null,
|
||||
createTime: Date.now()
|
||||
}
|
||||
id: null,
|
||||
managerUserId: item.userId,
|
||||
subordinateUserId: null,
|
||||
effectiveFrom: null,
|
||||
effectiveUntil: null,
|
||||
remark: null,
|
||||
createTime: Date.now()
|
||||
}
|
||||
: null;
|
||||
openOperateModal();
|
||||
}
|
||||
@@ -225,14 +226,14 @@ function openEdit(item: Api.SystemManage.UserManagementRelationTreeRespVO) {
|
||||
// 构建树节点数据为编辑所需格式
|
||||
editingData.value = item.id
|
||||
? {
|
||||
id: item.id,
|
||||
managerUserId: item.managerUserId,
|
||||
subordinateUserId: item.userId,
|
||||
effectiveFrom: null,
|
||||
effectiveUntil: null,
|
||||
remark: null,
|
||||
createTime: Date.now()
|
||||
}
|
||||
id: item.id,
|
||||
managerUserId: item.managerUserId,
|
||||
subordinateUserId: item.userId,
|
||||
effectiveFrom: null,
|
||||
effectiveUntil: null,
|
||||
remark: null,
|
||||
createTime: Date.now()
|
||||
}
|
||||
: null;
|
||||
openOperateModal();
|
||||
}
|
||||
@@ -243,7 +244,7 @@ function openEdit(item: Api.SystemManage.UserManagementRelationTreeRespVO) {
|
||||
* @param item 要删除的关系记录
|
||||
*/
|
||||
async function handleDelete(item: Api.SystemManage.UserManagementRelationTreeRespVO) {
|
||||
const {error} = await fetchDeleteUserManagementRelation(item.id);
|
||||
const { error } = await fetchDeleteUserManagementRelation(item.id);
|
||||
|
||||
if (error) {
|
||||
return;
|
||||
@@ -263,7 +264,7 @@ async function handleBatchDelete() {
|
||||
return;
|
||||
}
|
||||
|
||||
const {error} = await fetchBatchDeleteUserManagementRelation(checkedNodeKeys.value);
|
||||
const { error } = await fetchBatchDeleteUserManagementRelation(checkedNodeKeys.value);
|
||||
|
||||
if (error) {
|
||||
return;
|
||||
@@ -350,23 +351,23 @@ onMounted(async () => {
|
||||
<div class="flex items-center gap-10px">
|
||||
<ElButton plain type="primary" @click="openAdd()">
|
||||
<template #icon>
|
||||
<icon-ic-round-plus class="text-icon"/>
|
||||
<icon-ic-round-plus class="text-icon" />
|
||||
</template>
|
||||
新增
|
||||
</ElButton>
|
||||
<!-- <ElPopconfirm title="确认删除选中的关系吗?" @confirm="handleBatchDelete">-->
|
||||
<!-- <template #reference>-->
|
||||
<!-- <ElButton type="danger" plain :disabled="checkedNodeKeys.length === 0">-->
|
||||
<!-- <template #icon>-->
|
||||
<!-- <icon-ic-round-delete class="text-icon"/>-->
|
||||
<!-- </template>-->
|
||||
<!-- 批量删除-->
|
||||
<!-- </ElButton>-->
|
||||
<!-- </template>-->
|
||||
<!-- </ElPopconfirm>-->
|
||||
<!-- <ElPopconfirm title="确认删除选中的关系吗?" @confirm="handleBatchDelete">-->
|
||||
<!-- <template #reference>-->
|
||||
<!-- <ElButton type="danger" plain :disabled="checkedNodeKeys.length === 0">-->
|
||||
<!-- <template #icon>-->
|
||||
<!-- <icon-ic-round-delete class="text-icon"/>-->
|
||||
<!-- </template>-->
|
||||
<!-- 批量删除-->
|
||||
<!-- </ElButton>-->
|
||||
<!-- </template>-->
|
||||
<!-- </ElPopconfirm>-->
|
||||
<ElButton @click="reloadTreeData">
|
||||
<template #icon>
|
||||
<icon-ic-round-refresh class="text-icon"/>
|
||||
<icon-ic-round-refresh class="text-icon" />
|
||||
</template>
|
||||
刷新
|
||||
</ElButton>
|
||||
@@ -390,18 +391,18 @@ onMounted(async () => {
|
||||
<div class="flex flex-1 items-center justify-between">
|
||||
<span class="flex items-center gap-8px">
|
||||
<span>{{ node.label }}</span>
|
||||
<!-- <ElTag v-if="data.managerNickname" size="small" type="info">上级:{{ data.managerNickname }}</ElTag>-->
|
||||
<!-- <ElTag v-if="data.managerNickname" size="small" type="info">上级:{{ data.managerNickname }}</ElTag>-->
|
||||
</span>
|
||||
<div class="flex items-center">
|
||||
<ElButton link type="primary" size="default" @click.stop="openAdd(data)">
|
||||
<template #icon>
|
||||
<icon-ic-round-plus class="text-icon"/>
|
||||
<icon-ic-round-plus class="text-icon" />
|
||||
</template>
|
||||
新增
|
||||
</ElButton>
|
||||
<ElButton link type="primary" size="small" @click.stop="openEdit(data)">
|
||||
<template #icon>
|
||||
<icon-ic-round-edit class="text-icon"/>
|
||||
<icon-ic-round-edit class="text-icon" />
|
||||
</template>
|
||||
编辑
|
||||
</ElButton>
|
||||
@@ -413,7 +414,7 @@ onMounted(async () => {
|
||||
<template #reference>
|
||||
<ElButton link type="danger" size="small">
|
||||
<template #icon>
|
||||
<icon-ic-round-delete class="text-icon"/>
|
||||
<icon-ic-round-delete class="text-icon" />
|
||||
</template>
|
||||
删除
|
||||
</ElButton>
|
||||
|
||||
@@ -284,26 +284,26 @@ watch(visible, value => {
|
||||
</ElRow>
|
||||
<ElRow :gutter="16">
|
||||
<ElCol :span="12">
|
||||
<ElFormItem label="生效开始时间" prop="effectiveFrom" style="width:100%">
|
||||
<ElFormItem label="生效开始时间" prop="effectiveFrom" style="width: 100%">
|
||||
<ElDatePicker
|
||||
v-model="model.effectiveFrom"
|
||||
class="w-full"
|
||||
type="datetime"
|
||||
placeholder="请选择生效开始时间"
|
||||
value-format="x"
|
||||
style="width:100%"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</ElFormItem>
|
||||
</ElCol>
|
||||
<ElCol :span="12">
|
||||
<ElFormItem label="生效结束时间" prop="effectiveUntil" style="width:100%">
|
||||
<ElFormItem label="生效结束时间" prop="effectiveUntil" style="width: 100%">
|
||||
<ElDatePicker
|
||||
v-model="model.effectiveUntil"
|
||||
class="w-full"
|
||||
type="datetime"
|
||||
placeholder="请选择生效结束时间"
|
||||
value-format="x"
|
||||
style="width:100%"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</ElFormItem>
|
||||
</ElCol>
|
||||
@@ -319,6 +319,4 @@ watch(visible, value => {
|
||||
</BusinessFormDialog>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
<style scoped></style>
|
||||
|
||||
@@ -14,9 +14,9 @@
|
||||
* - 用户列表通过 props 传入,由父组件统一管理
|
||||
*/
|
||||
|
||||
import {defineEmits, defineModel, defineOptions} from 'vue';
|
||||
import { defineEmits, defineModel, defineOptions } from 'vue';
|
||||
|
||||
defineOptions({name: 'RelationSearch'});
|
||||
defineOptions({ name: 'RelationSearch' });
|
||||
|
||||
/**
|
||||
* 组件 Emits 定义
|
||||
@@ -43,7 +43,7 @@ defineProps<Props>();
|
||||
/**
|
||||
* 搜索参数模型,支持双向绑定
|
||||
*/
|
||||
const model = defineModel<Api.SystemManage.UserManagementRelationQueryReqVO>('model', {required: true});
|
||||
const model = defineModel<Api.SystemManage.UserManagementRelationQueryReqVO>('model', { required: true });
|
||||
|
||||
/**
|
||||
* 重置搜索
|
||||
@@ -83,7 +83,7 @@ function search() {
|
||||
<ElCol :lg="8" :md="12" :sm="12">
|
||||
<ElFormItem label="用户名" prop="subordinateUserId" style="margin-left: -50px">
|
||||
<ElSelect v-model="model.subordinateUserId" class="w-full" placeholder="请选择用户名" clearable filterable>
|
||||
<ElOption v-for="user in userList" :key="user.id" :label="user.nickname" :value="user.id"/>
|
||||
<ElOption v-for="user in userList" :key="user.id" :label="user.nickname" :value="user.id" />
|
||||
</ElSelect>
|
||||
</ElFormItem>
|
||||
</ElCol>
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
<script setup lang="tsx">
|
||||
import {computed, nextTick, onMounted, reactive, ref, watch} from 'vue';
|
||||
import type {TableInstance} from 'element-plus';
|
||||
import {ElButton, ElPopconfirm, ElSwitch, ElTag} from 'element-plus';
|
||||
import { computed, nextTick, onMounted, reactive, ref, watch } from 'vue';
|
||||
import type { TableInstance } from 'element-plus';
|
||||
import { ElButton, ElPopconfirm, ElSwitch, ElTag } from 'element-plus';
|
||||
import dayjs from 'dayjs';
|
||||
import type {FlatResponseData} from '@sa/axios';
|
||||
import {userGenderRecord} from '@/constants/business';
|
||||
import type { FlatResponseData } from '@sa/axios';
|
||||
import { userGenderRecord } from '@/constants/business';
|
||||
import {
|
||||
fetchBatchDeleteUser,
|
||||
fetchDeleteDept,
|
||||
@@ -17,11 +17,12 @@ import {
|
||||
fetchUpdateUser,
|
||||
fetchUpdateUserStatus
|
||||
} from '@/service/api';
|
||||
import {useUIPaginatedTable} from '@/hooks/common/table';
|
||||
import { useUIPaginatedTable } from '@/hooks/common/table';
|
||||
import BusinessTableActionCell from '@/components/custom/business-table-action-cell';
|
||||
import BusinessFormDialog from '@/components/custom/business-form-dialog.vue';
|
||||
import {$t} from '@/locales';
|
||||
import {buildMenuTree} from '@/views/system/shared/menu-tree';
|
||||
import { $t } from '@/locales';
|
||||
import { buildMenuTree } from '@/views/system/shared/menu-tree';
|
||||
import UserManagementRelation from '@/views/system/user-management-relation/index.vue';
|
||||
import UserOperateDialog from './modules/user-operate-dialog.vue';
|
||||
import UserOrgLeaderDialog from './modules/user-org-leader-dialog.vue';
|
||||
import UserOrgOperateDialog from './modules/user-org-operate-dialog.vue';
|
||||
@@ -29,9 +30,8 @@ import UserOrgPanel from './modules/user-org-panel.vue';
|
||||
import UserResignedDialog from './modules/user-resigned-dialog.vue';
|
||||
import UserResetPasswordDialog from './modules/user-reset-password-dialog.vue';
|
||||
import UserSearch from './modules/user-search.vue';
|
||||
import UserManagementRelation from '@/views/system/user-management-relation/index.vue';
|
||||
|
||||
defineOptions({name: 'UserManage'});
|
||||
defineOptions({ name: 'UserManage' });
|
||||
|
||||
function getInitSearchParams(): Api.SystemManage.UserSearchParams {
|
||||
return {
|
||||
@@ -154,7 +154,7 @@ const deptTree = computed(() => buildMenuTree(deptList.value));
|
||||
const currentDept = computed(() => deptList.value.find(item => item.id === currentDeptId.value) ?? null);
|
||||
const deptCount = computed(() => deptList.value.length);
|
||||
|
||||
const {columns, columnChecks, data, loading, getDataByPage, mobilePagination} = useUIPaginatedTable<
|
||||
const { columns, columnChecks, data, loading, getDataByPage, mobilePagination } = useUIPaginatedTable<
|
||||
FlatResponseData<any, Api.SystemManage.UserList>,
|
||||
Api.SystemManage.User
|
||||
>({
|
||||
@@ -178,9 +178,9 @@ const {columns, columnChecks, data, loading, getDataByPage, mobilePagination} =
|
||||
searchParams.pageSize = params.pageSize ?? 10;
|
||||
},
|
||||
columns: () => [
|
||||
{prop: 'selection', type: 'selection', width: 48},
|
||||
{prop: 'index', type: 'index', label: $t('common.index'), width: 64},
|
||||
{prop: 'username', label: $t('page.system.user.userName'), minWidth: 140, showOverflowTooltip: true},
|
||||
{ prop: 'selection', type: 'selection', width: 48 },
|
||||
{ prop: 'index', type: 'index', label: $t('common.index'), width: 64 },
|
||||
{ prop: 'username', label: $t('page.system.user.userName'), minWidth: 140, showOverflowTooltip: true },
|
||||
{
|
||||
prop: 'nickname',
|
||||
label: $t('page.system.user.nickName'),
|
||||
@@ -260,9 +260,9 @@ const {columns, columnChecks, data, loading, getDataByPage, mobilePagination} =
|
||||
formatter: row => {
|
||||
const state = getUserResignedState(row);
|
||||
const stateMap: Record<UserResignedState, { type: UI.ThemeColor; label: App.I18n.I18nKey }> = {
|
||||
active: {type: 'success', label: 'page.system.user.resignedStateEnum.active'},
|
||||
pending: {type: 'warning', label: 'page.system.user.resignedStateEnum.pending'},
|
||||
resigned: {type: 'info', label: 'page.system.user.resignedStateEnum.resigned'}
|
||||
active: { type: 'success', label: 'page.system.user.resignedStateEnum.active' },
|
||||
pending: { type: 'warning', label: 'page.system.user.resignedStateEnum.pending' },
|
||||
resigned: { type: 'info', label: 'page.system.user.resignedStateEnum.resigned' }
|
||||
};
|
||||
|
||||
return <ElTag type={stateMap[state].type}>{$t(stateMap[state].label)}</ElTag>;
|
||||
@@ -323,7 +323,7 @@ const {columns, columnChecks, data, loading, getDataByPage, mobilePagination} =
|
||||
async function loadDeptTree() {
|
||||
deptLoading.value = true;
|
||||
|
||||
const {error, data: deptItems} = await fetchGetDeptList({
|
||||
const { error, data: deptItems } = await fetchGetDeptList({
|
||||
status: 0
|
||||
});
|
||||
|
||||
@@ -425,7 +425,7 @@ function openOrgLeader(row: Api.SystemManage.Dept) {
|
||||
}
|
||||
|
||||
async function handleDeleteDeptAction(row: Api.SystemManage.Dept) {
|
||||
const {error} = await fetchDeleteDept(row.id);
|
||||
const { error } = await fetchDeleteDept(row.id);
|
||||
|
||||
if (error) {
|
||||
return;
|
||||
@@ -450,7 +450,7 @@ async function handleDeleteAction(row: Api.SystemManage.User) {
|
||||
return;
|
||||
}
|
||||
|
||||
const {error} = await fetchDeleteUser(row.id);
|
||||
const { error } = await fetchDeleteUser(row.id);
|
||||
|
||||
if (error) {
|
||||
return;
|
||||
@@ -469,7 +469,7 @@ async function updateUserResignedAt(userId: number, value: number | null) {
|
||||
|
||||
const user = detailResult.data;
|
||||
|
||||
const {error} = await fetchUpdateUser({
|
||||
const { error } = await fetchUpdateUser({
|
||||
id: userId,
|
||||
username: user.username,
|
||||
nickname: user.nickname ?? null,
|
||||
@@ -521,7 +521,7 @@ async function handleBatchDelete() {
|
||||
return;
|
||||
}
|
||||
|
||||
const {error} = await fetchBatchDeleteUser(userCheckedRowKeys.value);
|
||||
const { error } = await fetchBatchDeleteUser(userCheckedRowKeys.value);
|
||||
|
||||
if (error) {
|
||||
return;
|
||||
@@ -534,7 +534,7 @@ async function handleBatchDelete() {
|
||||
async function handleToggleStatus(row: Api.SystemManage.User, enabled: boolean) {
|
||||
statusLoadingIds.value = [...statusLoadingIds.value, row.id];
|
||||
|
||||
const {error} = await fetchUpdateUserStatus({
|
||||
const { error } = await fetchUpdateUserStatus({
|
||||
id: row.id,
|
||||
status: enabled ? 0 : 1
|
||||
});
|
||||
@@ -643,13 +643,13 @@ onMounted(async () => {
|
||||
<template #default>
|
||||
<ElButton plain type="primary" :disabled="!currentDept" @click="openAdd">
|
||||
<template #icon>
|
||||
<icon-ic-round-plus class="text-icon"/>
|
||||
<icon-ic-round-plus class="text-icon" />
|
||||
</template>
|
||||
{{ $t('common.add') }}
|
||||
</ElButton>
|
||||
<ElButton plain type="primary" :disabled="!currentDept" @click="userManagementRelationVisible = true">
|
||||
<template #icon>
|
||||
<icon-ic-round-plus class="text-icon"/>
|
||||
<icon-ic-round-plus class="text-icon" />
|
||||
</template>
|
||||
带人关系
|
||||
</ElButton>
|
||||
@@ -657,7 +657,7 @@ onMounted(async () => {
|
||||
<template #reference>
|
||||
<ElButton type="danger" plain :disabled="userCheckedRowKeys.length === 0">
|
||||
<template #icon>
|
||||
<icon-ic-round-delete class="text-icon"/>
|
||||
<icon-ic-round-delete class="text-icon" />
|
||||
</template>
|
||||
{{ $t('common.batchDelete') }}
|
||||
</ElButton>
|
||||
@@ -679,7 +679,7 @@ onMounted(async () => {
|
||||
:data="data"
|
||||
@selection-change="handleUserSelectionChange"
|
||||
>
|
||||
<ElTableColumn v-for="col in columns" :key="String(col.prop)" v-bind="col"/>
|
||||
<ElTableColumn v-for="col in columns" :key="String(col.prop)" v-bind="col" />
|
||||
</ElTable>
|
||||
</div>
|
||||
<div class="mt-20px flex justify-end">
|
||||
@@ -694,7 +694,7 @@ onMounted(async () => {
|
||||
</template>
|
||||
|
||||
<div v-else class="h-full flex items-center justify-center">
|
||||
<ElEmpty :description="$t('page.system.user.emptyOrg')"/>
|
||||
<ElEmpty :description="$t('page.system.user.emptyOrg')" />
|
||||
</div>
|
||||
</ElCard>
|
||||
</div>
|
||||
@@ -733,7 +733,7 @@ onMounted(async () => {
|
||||
@submitted="handleDeptSubmitted"
|
||||
/>
|
||||
|
||||
<UserOrgLeaderDialog v-model:visible="orgLeaderVisible" :dept="leaderDeptData"/>
|
||||
<UserOrgLeaderDialog v-model:visible="orgLeaderVisible" :dept="leaderDeptData" />
|
||||
|
||||
<BusinessFormDialog
|
||||
v-model="userManagementRelationVisible"
|
||||
@@ -742,7 +742,7 @@ onMounted(async () => {
|
||||
:show-footer="false"
|
||||
max-body-height="70vh"
|
||||
>
|
||||
<UserManagementRelation :fromUserIndex="true" :deptId="currentDeptId"/>
|
||||
<UserManagementRelation :from-user-index="true" :dept-id="currentDeptId" />
|
||||
</BusinessFormDialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
Reference in New Issue
Block a user