From 335dc88a2a7c5b55fbf46fa613023e0d5fa5de7a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E6=96=87=E6=98=8A?= Date: Wed, 22 Apr 2026 16:32:43 +0800 Subject: [PATCH] =?UTF-8?q?=E7=BB=BC=E5=90=88=E8=BF=90=E8=90=A5=EF=BC=9A?= =?UTF-8?q?=E5=88=86=E6=94=AF=E5=9B=BEBUG=E3=80=81=E8=B7=B3=E8=BD=AC=20fea?= =?UTF-8?q?t:=E5=AE=8C=E5=96=84=E8=BF=9B=E5=BA=A6/=E6=80=BB=E6=95=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/views/oa/project/dashboard2/index.vue | 142 +++++++++++++++--- .../oa/project/pace/components/xmind.vue | 89 +++++++++-- 2 files changed, 198 insertions(+), 33 deletions(-) diff --git a/ruoyi-ui/src/views/oa/project/dashboard2/index.vue b/ruoyi-ui/src/views/oa/project/dashboard2/index.vue index 9797b8b..04a02e2 100644 --- a/ruoyi-ui/src/views/oa/project/dashboard2/index.vue +++ b/ruoyi-ui/src/views/oa/project/dashboard2/index.vue @@ -158,9 +158,47 @@ - +
- +
+ +
+ + +
+ 项目名: + + + + +
+
+ +
+ 项目负责人: + {{ projectManagerName }} +
+
+ +
+ 当前进度: + {{ dashboardScheduleSummary }} +
+
+ +
+ 项目状态: + 重点关注 + 一般项目 +
+
+
+
+
+
+
@@ -206,6 +244,7 @@
import { mapState } from 'vuex' import Xmind from '@/views/oa/project/pace/components/xmind.vue' +import ProjectInfo from '@/components/fad-service/ProjectInfo/index.vue' export default { name: 'OaProjectDashboard2', - components: { Xmind }, + components: { Xmind, ProjectInfo }, dicts: ['sys_work_type', 'sys_sort_grade'], data () { return { @@ -265,8 +305,31 @@ export default { 'projectTotal', 'taskQuery', 'taskList', - 'stepList' + 'stepList', + 'projectDetail' ]), + projectDisplayName () { + const p = this.projectDetail + return (p && p.projectName) ? String(p.projectName) : '' + }, + projectManagerName () { + const p = this.projectDetail + return (p && p.functionary) ? String(p.functionary) : '' + }, + projectIsTop () { + const p = this.projectDetail + if (!p) return false + const top = p.isTop + return top === true || top === 1 || top === '1' + }, + /** 与 pace/step 中 scheduleSummary 文案规则一致 */ + dashboardScheduleSummary () { + const list = this.stepList || [] + const totalCount = list.length + const completedCount = list.filter((item) => item.status === 2).length + const pendingCount = list.filter((item) => item.status === 1).length + return `已完成(${completedCount})+ 待验收(${pendingCount}) / 总节点数(${totalCount})` + }, filteredTaskList () { const list = this.taskList || [] const code = (this.taskQuery.projectCode || '').trim().toLowerCase() @@ -304,6 +367,19 @@ export default { this.getProjectList() }, methods: { + /** 进度导图挂在 Tab 内,切换为可见后触发 ECharts 重新测量,避免线上画布宽/高为 0 */ + onScheduleTabClick (tab) { + if (!tab || tab.name !== 'mind') return; + this.$nextTick(() => { + requestAnimationFrame(() => { + const comp = this.$refs.dashboardMindXmind; + if (comp && typeof comp.scheduleResize === 'function') { + comp.scheduleResize(); + } + }); + }); + }, + async getProjectList () { this.projectLoading = true try { @@ -360,6 +436,16 @@ export default { } finally { this.pageLoading = false this.xmindLoading = false + if (this.scheduleViewTab === 'mind') { + this.$nextTick(() => { + requestAnimationFrame(() => { + const comp = this.$refs.dashboardMindXmind + if (comp && typeof comp.scheduleResize === 'function') { + comp.scheduleResize() + } + }) + }) + } } }, @@ -666,16 +752,18 @@ export default { padding: 0; } -.schedule-panel-tabs { - flex: 1 1 0%; - min-height: 0; - display: flex; - flex-direction: column; - +.schedule-board-summary--pace-like { + flex-shrink: 0; + padding: 10px 12px 8px; + border-bottom: 1px solid #ebeef5; } -/* Element Tabs 根节点参与纵向 flex,才能把剩余高度交给内容区 */ -.schedule-panel-tabs :deep(.el-tabs) { +/* + * schedule-panel-tabs 与 .el-tabs 在同一 DOM 节点上,不能用「.schedule-panel-tabs :deep(.el-tabs)」后代选择器(永远匹配不到)。 + * 生产包合并 CSS 后,Element 全局 .el-tabs 可能后加载并盖住单独的 .schedule-panel-tabs,导致 flex 丢失。 + * 使用双类选择器提高命中与优先级。 + */ +.schedule-panel-tabs.el-tabs { flex: 1 1 0%; min-height: 0; display: flex; @@ -683,13 +771,13 @@ export default { height: 100%; } -.schedule-panel-tabs :deep(.el-tabs__header) { +.schedule-panel-tabs.el-tabs :deep(.el-tabs__header) { margin-bottom: 0; padding: 0 12px; flex-shrink: 0; } -.schedule-panel-tabs :deep(.el-tabs__content) { +.schedule-panel-tabs.el-tabs :deep(.el-tabs__content) { flex: 1 1 0%; min-height: 0; overflow: hidden; @@ -699,9 +787,11 @@ export default { } /* 当前激活的 pane 占满内容区高度,子元素才能算出可滚动区域 */ -.schedule-panel-tabs :deep(.el-tab-pane) { +.schedule-panel-tabs.el-tabs :deep(.el-tab-pane) { flex: 1 1 0%; min-height: 0; + min-width: 0; + width: 100%; overflow: hidden; display: flex; flex-direction: column; @@ -710,6 +800,8 @@ export default { .schedule-tab-pane-inner { flex: 1 1 0%; min-height: 0; + min-width: 0; + width: 100%; height: 100%; box-sizing: border-box; display: flex; @@ -719,19 +811,24 @@ export default { .schedule-tab-pane-inner--mind { min-height: 280px; overflow: hidden; + width: 100%; + min-width: 0; } -/* 进度明细 Tab:占满下方板块;内部仅滚动区参与滚动 */ +/* 进度明细 Tab:表格与面板四边留白,避免贴边 */ .progress-table-pane { flex: 1 1 0%; min-height: 0; width: 100%; - height:300px; + height: 300px; box-sizing: border-box; display: flex; flex-direction: column; - padding: 8px 12px 10px; - + padding: 14px 18px 18px; +} + +.progress-table-pane :deep(.el-empty) { + padding: 24px 8px; } .progress-table-scroll { @@ -740,6 +837,8 @@ export default { min-width: 0; overflow: auto; -webkit-overflow-scrolling: touch; + /* 表格与滚动容器边缘再留一线间距,滚动条不压表格边框 */ + padding: 2px 4px 4px 2px; } .progress-step-el-table { @@ -751,7 +850,10 @@ export default { flex-direction: row; align-items: stretch; flex: 1; + flex-basis: 0; min-height: 0; + min-width: 0; + width: 100%; } .task-table-wrap { @@ -804,7 +906,7 @@ export default { } .xmind-wrap { - flex: 1; + flex: 1 1 0%; min-width: 0; position: relative; padding: 0; diff --git a/ruoyi-ui/src/views/oa/project/pace/components/xmind.vue b/ruoyi-ui/src/views/oa/project/pace/components/xmind.vue index f636f2d..099056f 100644 --- a/ruoyi-ui/src/views/oa/project/pace/components/xmind.vue +++ b/ruoyi-ui/src/views/oa/project/pace/components/xmind.vue @@ -106,7 +106,9 @@ export default { return { width: '100%', height: this.height, - minHeight: this.dashboardMode ? '300px' : '240px' + minHeight: this.dashboardMode ? '300px' : '240px', + /* 看板嵌在横向 flex 内,避免 min-width:auto 把可用宽压成 0 */ + ...(this.dashboardMode ? { minWidth: 0, boxSizing: 'border-box' } : {}) } } }, @@ -118,37 +120,92 @@ export default { clickEvent: null, // 新增:存储点击事件句柄,用于销毁解绑 users: [], supplierList: [], + chartResizeObserver: null, + chartResizeObserverRaf: null }; }, watch: { - // 监听列表数据变化,自动更新图表 list: { deep: true, handler () { - if (this.chartInstance) { + this.$nextTick(() => { this.initChart(); - } + }); } } }, mounted () { this.initChart(); - this.$nextTick(() => this.resizeChart()); + this.deferResizeChart(); window.addEventListener('resize', this.resizeChart); + this.$nextTick(() => { + this.bindChartResizeObserver(); + }); }, beforeDestroy () { window.removeEventListener('resize', this.resizeChart); - // 新增:解绑Echarts点击事件,防止内存泄漏 + this.unbindChartResizeObserver(); if (this.chartInstance && this.clickEvent) { this.chartInstance.off('click', this.clickEvent); } - // 销毁图表实例,防止内存泄漏 this.chartInstance?.dispose(); }, methods: { - // 优化:增加防抖处理-窗口自适应,避免频繁触发 resizeChart () { - this.chartInstance?.resize() + this.chartInstance?.resize(); + }, + + /** + * 供综合看板在切换 Tab 后调用:Tab 从 display:none 变为可见后需重新测量画布。 + */ + scheduleResize () { + this.deferResizeChart(); + }, + + /** + * el-tab / flex 布局下首帧常为 0 宽高,生产环境更明显;延迟到布局稳定后再 resize。 + */ + deferResizeChart () { + this.$nextTick(() => { + this.$nextTick(() => { + requestAnimationFrame(() => { + this.resizeChart(); + requestAnimationFrame(() => this.resizeChart()); + }); + }); + }); + }, + + bindChartResizeObserver () { + if (!this.dashboardMode || typeof ResizeObserver === 'undefined') { + return; + } + const el = this.$refs.chart; + if (!el) { + return; + } + this.unbindChartResizeObserver(); + this.chartResizeObserver = new ResizeObserver(() => { + if (this.chartResizeObserverRaf != null) { + cancelAnimationFrame(this.chartResizeObserverRaf); + } + this.chartResizeObserverRaf = requestAnimationFrame(() => { + this.chartResizeObserverRaf = null; + this.resizeChart(); + }); + }); + this.chartResizeObserver.observe(el); + }, + + unbindChartResizeObserver () { + if (this.chartResizeObserverRaf != null) { + cancelAnimationFrame(this.chartResizeObserverRaf); + this.chartResizeObserverRaf = null; + } + if (this.chartResizeObserver) { + this.chartResizeObserver.disconnect(); + this.chartResizeObserver = null; + } }, handleSubmit () { @@ -288,11 +345,13 @@ export default { }; }, - // 初始化图表 initChart () { - // 初始化图表实例 + const dom = this.$refs.chart; + if (!dom) { + return; + } if (!this.chartInstance) { - this.chartInstance = echarts.init(this.$refs.chart); + this.chartInstance = echarts.init(dom); } // 重要:先解绑已有点击事件,防止多次绑定导致弹窗多次触发 if (this.clickEvent) { @@ -422,8 +481,8 @@ export default { } ] }; - // 渲染图表 this.chartInstance?.setOption(option, true); + this.deferResizeChart(); this.clickEvent = (params) => { const data = params.data; @@ -461,12 +520,16 @@ export default { flex-direction: column; height: 100%; min-height: 0; + width: 100%; + min-width: 0; } /* 与综合看板折线图区域一致:白底、细边框、轻圆角 */ .xmind-box--dashboard .xmind-container { flex: 1; min-height: 0; + min-width: 0; + width: 100%; background: #fff; border: 1px solid #ebeef5; border-radius: 6px;