Files
cn-rdms-web/src/views/workbench/modules/workbench-shortcut.vue

224 lines
5.9 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<script setup lang="ts">
import type { VNode } from 'vue';
import { computed, ref } from 'vue';
import { objectContextDomainConfigs } from '@/constants/object-context';
import { useRouteStore } from '@/store/modules/route';
import { useWorkbenchStore } from '@/store/modules/workbench';
import { useRouterPush } from '@/hooks/common/router';
import { useWorkbenchRefresh } from '../composables/use-workbench-refresh';
import WorkbenchModuleCard from './workbench-module-card.vue';
import WorkbenchShortcutPicker from './workbench-shortcut-picker.vue';
interface Props {
editing?: boolean;
}
withDefaults(defineProps<Props>(), {
editing: false
});
defineEmits<{
(e: 'hide'): void;
}>();
const routeStore = useRouteStore();
const workbench = useWorkbenchStore();
const { routerPushByKey } = useRouterPush();
const { loading, refresh } = useWorkbenchRefresh();
interface FlatMenu {
key: string;
label: string;
/** 来自 routeStore.menus 的 icon VNodeSvgIconVNode 渲染产物,与侧栏菜单同源) */
icon?: VNode;
}
// 与 picker 保持一致对象域入口project_list / product_list当叶子
// 其下的对象上下文页不进入快捷入口候选范围。
const OBJECT_DOMAIN_ENTRY_KEYS = new Set(objectContextDomainConfigs.map(c => c.entryRouteKey));
function flatten(menus: typeof routeStore.menus): FlatMenu[] {
const out: FlatMenu[] = [];
function walk(list: typeof menus) {
list.forEach((m: any) => {
const isDomainEntry = OBJECT_DOMAIN_ENTRY_KEYS.has(m.key);
const hasChildren = !isDomainEntry && m.children && m.children.length > 0;
if (hasChildren) {
walk(m.children);
} else {
out.push({
key: m.key,
label: m.label as string,
icon: m.icon
});
}
});
}
walk(menus);
return out;
}
const flatMenus = computed(() => flatten(routeStore.menus));
const selectedKeys = computed(() => workbench.layout.settings.shortcut?.menuKeys ?? []);
const selected = computed(() =>
selectedKeys.value.map(k => flatMenus.value.find(m => m.key === k)).filter((x): x is FlatMenu => Boolean(x))
);
const pickerOpen = ref(false);
function openPicker() {
pickerOpen.value = true;
}
function handleClick(key: string) {
routerPushByKey(key as any);
}
function handleConfirm(keys: string[]) {
workbench.updateModuleSettings('shortcut', { menuKeys: keys });
}
function handleRemove(key: string) {
workbench.updateModuleSettings('shortcut', { menuKeys: selectedKeys.value.filter(k => k !== key) });
}
</script>
<template>
<WorkbenchModuleCard
v-loading="loading"
title="快捷入口"
icon="mdi:rocket-launch-outline"
:badge-count="selected.length || undefined"
:editing="editing"
has-settings
@hide="$emit('hide')"
@open-settings="openPicker"
@refresh="refresh"
>
<div v-if="selected.length === 0" class="shortcut-empty">
<ElEmpty description="还未选择菜单" :image-size="48">
<ElButton type="primary" size="small" @click="openPicker">+ 选择菜单</ElButton>
</ElEmpty>
</div>
<div v-else class="shortcut-grid">
<button v-for="item in selected" :key="item.key" class="shortcut-item" @click="handleClick(item.key)">
<ElIcon v-if="item.icon" class="shortcut-item__icon">
<component :is="item.icon" />
</ElIcon>
<SvgIcon v-else icon="mdi:link-variant" class="shortcut-item__icon" />
<span class="shortcut-item__label">{{ item.label }}</span>
<span class="shortcut-item__remove" title="移除此快捷入口" @click.stop="handleRemove(item.key)">
<SvgIcon icon="mdi:close" />
</span>
</button>
<button class="shortcut-item shortcut-item--add" title="添加快捷入口" @click="openPicker">
<SvgIcon icon="mdi:plus" />
<span>添加</span>
</button>
</div>
<WorkbenchShortcutPicker v-model="pickerOpen" :initial-selected="selectedKeys" @confirm="handleConfirm" />
</WorkbenchModuleCard>
</template>
<style scoped>
.shortcut-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(110px, 1fr));
gap: 10px;
flex: 1;
min-height: 0;
overflow: auto;
align-content: start;
}
.shortcut-item {
position: relative;
display: flex;
flex-direction: column;
align-items: center;
gap: 6px;
padding: 12px 8px;
border: 1px solid var(--el-border-color-lighter);
background: var(--el-fill-color-lighter);
border-radius: 8px;
cursor: pointer;
font-size: 12px;
color: var(--el-text-color-primary);
transition: all 120ms;
}
.shortcut-item__remove {
position: absolute;
top: 4px;
right: 4px;
display: inline-flex;
align-items: center;
justify-content: center;
width: 16px;
height: 16px;
border-radius: 50%;
color: var(--el-text-color-placeholder);
font-size: 12px;
opacity: 0;
transition:
opacity 120ms,
color 120ms,
background-color 120ms;
}
.shortcut-item:hover .shortcut-item__remove {
opacity: 1;
}
.shortcut-item__remove:hover {
background-color: var(--el-color-danger-light-9);
color: var(--el-color-danger);
}
.shortcut-item__icon {
font-size: 20px;
color: var(--el-color-primary);
transition: color 120ms;
}
.shortcut-item__label {
max-width: 100%;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.shortcut-item:hover {
border-color: var(--el-color-primary);
color: var(--el-color-primary);
}
.shortcut-item--add {
border-style: dashed;
border-color: var(--el-border-color);
background: transparent;
color: var(--el-text-color-secondary);
}
.shortcut-item--add:hover {
border-color: var(--el-color-primary);
background: var(--el-color-primary-light-9);
color: var(--el-color-primary);
}
.shortcut-empty {
flex: 1;
min-height: 0;
display: flex;
align-items: center;
justify-content: center;
}
/* 压缩 ElEmpty 默认大 padding空态在最小高度下也不溢出 */
.shortcut-empty :deep(.el-empty) {
padding: 12px 0;
}
</style>