170 lines
5.4 KiB
Vue
170 lines
5.4 KiB
Vue
<script setup lang="ts">
|
||
import { computed, onBeforeUnmount, onMounted, provide, ref, watch } from 'vue';
|
||
import { onBeforeRouteLeave } from 'vue-router';
|
||
import { ElMessageBox } from 'element-plus';
|
||
import { GridItem, GridLayout } from 'grid-layout-plus';
|
||
import { useWorkbenchStore } from '@/store/modules/workbench';
|
||
import { type WorkbenchModuleKey, useWorkbenchModules } from './composables/use-workbench-modules';
|
||
import type { WorkbenchGridItem } from './composables/workbench-layout-types';
|
||
import WorkbenchBanner from './modules/workbench-banner.vue';
|
||
import WorkbenchEditOverlay from './modules/workbench-edit-overlay.vue';
|
||
import WorkbenchModuleLibrary from './modules/workbench-module-library.vue';
|
||
// 保留 6 个 + 重构 2 个(key 沿用)
|
||
import WorkbenchTodoPanel from './modules/workbench-todo-panel.vue';
|
||
import WorkbenchProjectGrid from './modules/workbench-project-grid.vue';
|
||
import WorkbenchShortcut from './modules/workbench-shortcut.vue';
|
||
import WorkbenchProjectHealth from './modules/workbench-project-health.vue';
|
||
// 新增 10 个(蓝图 2026-05-22,原 A3 myTicket、A4 mentions、A5 approval、A6 worklogReminder、B10 personalItem、F23 projectSnapshot 已废弃)
|
||
import WorkbenchMyExecution from './modules/workbench-my-execution.vue';
|
||
import WorkbenchProductSnapshot from './modules/workbench-product-snapshot.vue';
|
||
import WorkbenchTeamLoad from './modules/workbench-team-load.vue';
|
||
import WorkbenchMyWeekWorklog from './modules/workbench-my-week-worklog.vue';
|
||
|
||
defineOptions({ name: 'Workbench' });
|
||
|
||
const { registerModuleComponent, getModuleMeta } = useWorkbenchModules();
|
||
// 保留 6 个 + 重构 2 个
|
||
registerModuleComponent('myTodo', WorkbenchTodoPanel);
|
||
registerModuleComponent('myProject', WorkbenchProjectGrid);
|
||
registerModuleComponent('shortcut', WorkbenchShortcut);
|
||
registerModuleComponent('projectHealth', WorkbenchProjectHealth);
|
||
// 新增 10 个
|
||
registerModuleComponent('myExecution', WorkbenchMyExecution);
|
||
registerModuleComponent('productSnapshot', WorkbenchProductSnapshot);
|
||
registerModuleComponent('teamLoad', WorkbenchTeamLoad);
|
||
registerModuleComponent('myWeekWorklog', WorkbenchMyWeekWorklog);
|
||
|
||
const workbench = useWorkbenchStore();
|
||
const libraryOpen = ref(false);
|
||
|
||
// 暴露给 workbench-module-card 内的"编辑布局"按钮,避免每个 widget 都透传 emit
|
||
provide('workbenchEnterEditing', () => workbench.enterEditing());
|
||
|
||
onMounted(() => {
|
||
workbench.load();
|
||
});
|
||
|
||
function onBeforeUnload(e: BeforeUnloadEvent) {
|
||
if (workbench.mode === 'editing' && workbench.dirty) {
|
||
e.preventDefault();
|
||
e.returnValue = '';
|
||
}
|
||
}
|
||
onMounted(() => window.addEventListener('beforeunload', onBeforeUnload));
|
||
onBeforeUnmount(() => window.removeEventListener('beforeunload', onBeforeUnload));
|
||
|
||
watch(
|
||
() => workbench.error,
|
||
err => {
|
||
if (err) window.$message?.error(`布局保存失败:${err.message}`);
|
||
}
|
||
);
|
||
|
||
const editing = computed(() => workbench.mode === 'editing');
|
||
|
||
function onGridUpdated(grid: WorkbenchGridItem[]) {
|
||
workbench.updateGrid(grid);
|
||
}
|
||
|
||
async function handleReset() {
|
||
try {
|
||
await ElMessageBox.confirm('重置后将恢复默认布局,确认继续?', '重置默认布局', { type: 'warning' });
|
||
await workbench.resetToDefault();
|
||
} catch {
|
||
/* cancelled */
|
||
}
|
||
}
|
||
|
||
onBeforeRouteLeave(async (_to, _from, next) => {
|
||
if (workbench.mode === 'editing' && workbench.dirty) {
|
||
try {
|
||
await ElMessageBox.confirm('编辑布局未保存,确认离开?', '确认离开', { type: 'warning' });
|
||
workbench.cancelEditing();
|
||
next();
|
||
} catch {
|
||
next(false);
|
||
}
|
||
} else {
|
||
next();
|
||
}
|
||
});
|
||
</script>
|
||
|
||
<template>
|
||
<div class="workbench work-report-page-shell">
|
||
<WorkbenchBanner />
|
||
|
||
<WorkbenchEditOverlay
|
||
v-if="workbench.mode === 'editing'"
|
||
:dirty="workbench.dirty"
|
||
:saving="workbench.saving"
|
||
@save="workbench.saveEditing"
|
||
@cancel="workbench.cancelEditing"
|
||
@reset="handleReset"
|
||
@open-library="libraryOpen = true"
|
||
/>
|
||
|
||
<ElEmpty v-if="workbench.layout.grid.length === 0" description="还没有可见模块">
|
||
<ElButton type="primary" @click="workbench.enterEditing">添加模块</ElButton>
|
||
</ElEmpty>
|
||
|
||
<div v-else class="workbench__main">
|
||
<GridLayout
|
||
:layout="workbench.layout.grid"
|
||
:col-num="12"
|
||
:row-height="10"
|
||
:margin="[16, 16]"
|
||
:is-draggable="editing"
|
||
:is-resizable="editing"
|
||
:vertical-compact="true"
|
||
:use-css-transforms="true"
|
||
@layout-updated="onGridUpdated"
|
||
>
|
||
<GridItem
|
||
v-for="item in workbench.layout.grid"
|
||
:key="item.i"
|
||
:i="item.i"
|
||
:x="item.x"
|
||
:y="item.y"
|
||
:w="item.w"
|
||
:h="item.h"
|
||
:min-w="item.minW"
|
||
:min-h="item.minH"
|
||
drag-allow-from=".module-card__head"
|
||
>
|
||
<component
|
||
:is="getModuleMeta(item.i)?.component"
|
||
:module-key="item.i"
|
||
:editing="editing"
|
||
@hide="workbench.hideModule(item.i as WorkbenchModuleKey)"
|
||
@open-settings="() => {}"
|
||
/>
|
||
</GridItem>
|
||
</GridLayout>
|
||
</div>
|
||
|
||
<WorkbenchModuleLibrary
|
||
v-model="libraryOpen"
|
||
:hidden-metas="workbench.hiddenMetas"
|
||
@add-module="
|
||
key => {
|
||
workbench.showModule(key);
|
||
libraryOpen = false;
|
||
}
|
||
"
|
||
/>
|
||
</div>
|
||
</template>
|
||
|
||
<style scoped>
|
||
.workbench {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 16px;
|
||
overflow-x: auto;
|
||
}
|
||
.workbench__main {
|
||
min-width: 1100px;
|
||
}
|
||
</style>
|