2026-05-14 09:05:08 +08:00
|
|
|
|
<script setup lang="ts">
|
2026-05-28 08:20:01 +08:00
|
|
|
|
import { onBeforeUnmount, onMounted, provide, ref, watch } from 'vue';
|
2026-05-21 21:42:23 +08:00
|
|
|
|
import { onBeforeRouteLeave } from 'vue-router';
|
|
|
|
|
|
import { ElMessageBox } from 'element-plus';
|
|
|
|
|
|
import { useWorkbenchStore } from '@/store/modules/workbench';
|
2026-05-14 09:05:08 +08:00
|
|
|
|
import {
|
2026-05-21 21:42:23 +08:00
|
|
|
|
type WorkbenchColumnId,
|
|
|
|
|
|
type WorkbenchModuleKey,
|
|
|
|
|
|
useWorkbenchModules
|
|
|
|
|
|
} from './composables/use-workbench-modules';
|
2026-05-14 09:05:08 +08:00
|
|
|
|
import WorkbenchBanner from './modules/workbench-banner.vue';
|
2026-05-21 21:42:23 +08:00
|
|
|
|
import WorkbenchColumn from './modules/workbench-column.vue';
|
|
|
|
|
|
import WorkbenchEditOverlay from './modules/workbench-edit-overlay.vue';
|
|
|
|
|
|
import WorkbenchModuleLibrary from './modules/workbench-module-library.vue';
|
2026-05-23 14:22:58 +08:00
|
|
|
|
// 保留 6 个 + 重构 2 个(key 沿用)
|
2026-05-14 09:05:08 +08:00
|
|
|
|
import WorkbenchTodoPanel from './modules/workbench-todo-panel.vue';
|
2026-05-23 14:22:58 +08:00
|
|
|
|
import WorkbenchProjectGrid from './modules/workbench-project-grid.vue';
|
|
|
|
|
|
import WorkbenchShortcut from './modules/workbench-shortcut.vue';
|
2026-05-21 21:42:23 +08:00
|
|
|
|
import WorkbenchProjectHealth from './modules/workbench-project-health.vue';
|
2026-05-28 08:20:01 +08:00
|
|
|
|
// 新增 10 个(蓝图 2026-05-22,原 A3 myTicket、A4 mentions、A5 approval、A6 worklogReminder、B10 personalItem、F23 projectSnapshot 已废弃)
|
2026-05-23 14:22:58 +08:00
|
|
|
|
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';
|
|
|
|
|
|
import WorkbenchNoticeNotification from './modules/workbench-notice-notification.vue';
|
2026-05-14 09:05:08 +08:00
|
|
|
|
|
|
|
|
|
|
defineOptions({ name: 'Workbench' });
|
|
|
|
|
|
|
2026-05-21 21:42:23 +08:00
|
|
|
|
const { registerModuleComponent } = useWorkbenchModules();
|
2026-05-23 14:22:58 +08:00
|
|
|
|
// 保留 6 个 + 重构 2 个
|
2026-05-21 21:42:23 +08:00
|
|
|
|
registerModuleComponent('myTodo', WorkbenchTodoPanel);
|
2026-05-23 14:22:58 +08:00
|
|
|
|
registerModuleComponent('myProject', WorkbenchProjectGrid);
|
|
|
|
|
|
registerModuleComponent('shortcut', WorkbenchShortcut);
|
2026-05-21 21:42:23 +08:00
|
|
|
|
registerModuleComponent('projectHealth', WorkbenchProjectHealth);
|
2026-05-28 08:20:01 +08:00
|
|
|
|
// 新增 10 个
|
2026-05-23 14:22:58 +08:00
|
|
|
|
registerModuleComponent('myExecution', WorkbenchMyExecution);
|
|
|
|
|
|
registerModuleComponent('productSnapshot', WorkbenchProductSnapshot);
|
|
|
|
|
|
registerModuleComponent('teamLoad', WorkbenchTeamLoad);
|
|
|
|
|
|
registerModuleComponent('myWeekWorklog', WorkbenchMyWeekWorklog);
|
|
|
|
|
|
registerModuleComponent('noticeNotification', WorkbenchNoticeNotification);
|
2026-05-21 21:42:23 +08:00
|
|
|
|
|
|
|
|
|
|
const workbench = useWorkbenchStore();
|
|
|
|
|
|
const libraryOpen = ref(false);
|
|
|
|
|
|
|
2026-05-28 08:20:01 +08:00
|
|
|
|
// 暴露给 workbench-module-card 内的"编辑布局"按钮,避免每个 widget 都透传 emit
|
|
|
|
|
|
provide('workbenchEnterEditing', () => workbench.enterEditing());
|
|
|
|
|
|
|
2026-05-21 21:42:23 +08:00
|
|
|
|
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}`);
|
|
|
|
|
|
}
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
function onColumnUpdate(columnId: WorkbenchColumnId, modules: WorkbenchModuleKey[]) {
|
|
|
|
|
|
workbench.setColumnModules(columnId, modules);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
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();
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
2026-05-14 09:05:08 +08:00
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
|
|
<template>
|
|
|
|
|
|
<div class="workbench">
|
2026-05-28 08:20:01 +08:00
|
|
|
|
<WorkbenchBanner />
|
2026-05-21 21:42:23 +08:00
|
|
|
|
|
|
|
|
|
|
<WorkbenchEditOverlay
|
|
|
|
|
|
v-if="workbench.mode === 'editing'"
|
|
|
|
|
|
:dirty="workbench.dirty"
|
|
|
|
|
|
:saving="workbench.saving"
|
|
|
|
|
|
@save="workbench.saveEditing"
|
|
|
|
|
|
@cancel="workbench.cancelEditing"
|
|
|
|
|
|
@reset="handleReset"
|
2026-05-28 08:20:01 +08:00
|
|
|
|
@open-library="libraryOpen = true"
|
2026-05-21 21:42:23 +08:00
|
|
|
|
/>
|
2026-05-14 09:05:08 +08:00
|
|
|
|
|
2026-05-21 21:42:23 +08:00
|
|
|
|
<ElEmpty v-if="workbench.layout.columns.every(c => c.modules.length === 0)" description="还没有可见模块">
|
|
|
|
|
|
<ElButton type="primary" @click="workbench.enterEditing">添加模块</ElButton>
|
|
|
|
|
|
</ElEmpty>
|
|
|
|
|
|
|
|
|
|
|
|
<section v-else class="workbench__main">
|
|
|
|
|
|
<WorkbenchColumn
|
|
|
|
|
|
v-for="col in workbench.layout.columns"
|
|
|
|
|
|
:key="col.id"
|
|
|
|
|
|
:column-id="col.id"
|
|
|
|
|
|
:modules="col.modules"
|
|
|
|
|
|
:editing="workbench.mode === 'editing'"
|
|
|
|
|
|
:collapsed="workbench.layout.collapsed"
|
|
|
|
|
|
@update:modules="onColumnUpdate(col.id, $event)"
|
|
|
|
|
|
@hide="workbench.hideModule"
|
|
|
|
|
|
@toggle-collapse="workbench.toggleCollapse"
|
|
|
|
|
|
/>
|
2026-05-14 09:05:08 +08:00
|
|
|
|
</section>
|
|
|
|
|
|
|
2026-05-21 21:42:23 +08:00
|
|
|
|
<WorkbenchModuleLibrary
|
|
|
|
|
|
v-model="libraryOpen"
|
|
|
|
|
|
:hidden-metas="workbench.hiddenMetas"
|
|
|
|
|
|
@add-module="
|
|
|
|
|
|
(key, col) => {
|
|
|
|
|
|
workbench.showModule(key, col);
|
|
|
|
|
|
libraryOpen = false;
|
|
|
|
|
|
}
|
|
|
|
|
|
"
|
|
|
|
|
|
/>
|
2026-05-14 09:05:08 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
|
|
<style scoped>
|
|
|
|
|
|
.workbench {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
|
gap: 16px;
|
|
|
|
|
|
}
|
|
|
|
|
|
.workbench__main {
|
|
|
|
|
|
display: grid;
|
|
|
|
|
|
grid-template-columns: minmax(0, 1.35fr) minmax(0, 1fr);
|
|
|
|
|
|
gap: 16px;
|
|
|
|
|
|
}
|
|
|
|
|
|
@media (width <= 1280px) {
|
|
|
|
|
|
.workbench__main {
|
|
|
|
|
|
grid-template-columns: 1fr;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
</style>
|