Files
fad_oa/ruoyi-ui/src/views/oa/project/dashboard2/index.vue
2026-04-22 16:32:43 +08:00

997 lines
29 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.

<!--
前端路径d:\RuoYI_workspace\fad_oa\ruoyi-ui\src\views\oa\project\dashboard2\index.vue
后端聚合接口GET /oa/project/dashboard/{projectId}
-->
<template>
<div class="app-container dashboard2" v-loading="pageLoading">
<div class="layout">
<!-- 左侧 20%项目列表 -->
<div class="left">
<div class="left-search">
<el-input
v-model="projectQuery.keyword"
size="small"
clearable
placeholder="名称 / 编号 / 代号"
class="left-keyword-input"
prefix-icon="el-icon-search"
@keyup.enter.native="getProjectList"
/>
<el-button type="primary" size="small" class="left-search-btn" @click="getProjectList">搜索</el-button>
</div>
<div class="project-list" v-loading="projectLoading">
<div
v-for="p in projectList"
:key="p.projectId"
class="project-item"
:class="{ active: String(p.projectId) === String(currentProjectId) }"
@click="handleSelectProject(p)"
:title="p.projectName"
>
<div class="project-name text-ellipsis">{{ p.projectName }}</div>
<div class="project-meta text-ellipsis">
<span v-if="p.projectCode" class="code">{{ p.projectCode }}</span>
<span class="time">{{ formatDate(p.beginTime) }} ~ {{ formatDate(p.finishTime) }}</span>
</div>
</div>
<div v-if="!projectList || projectList.length === 0" class="left-empty">
<el-empty :image-size="72" description="暂无项目"></el-empty>
</div>
</div>
<div class="left-pager">
<el-pagination
small
background
layout="total, prev, pager, next"
:total="projectTotal"
:current-page.sync="projectQuery.pageNum"
:page-size.sync="projectQuery.pageSize"
@current-change="getProjectList"
/>
</div>
</div>
<!-- 右侧 80% -->
<div class="right">
<div v-if="!currentProjectId" class="right-empty">
<el-empty description="请选择项目"></el-empty>
</div>
<template v-else>
<div class="panel task-panel">
<div class="panel-header task-panel__toolbar">
<div class="toolbar">
<el-input
v-model="taskQuery.projectCode"
size="small"
clearable
placeholder="项目代号"
style="width: 140px"
/>
<el-input
v-model="taskQuery.taskKeyword"
size="small"
clearable
placeholder="任务主题关键词"
style="width: 160px"
@keyup.enter.native="resetTaskPage"
/>
<el-button type="primary" size="mini" @click="resetTaskPage">搜索</el-button>
<el-button size="mini" @click="resetTaskFilters">重置</el-button>
<el-button size="mini" @click="refreshCurrent">刷新</el-button>
</div>
</div>
<div class="panel-body panel-body-flex">
<div class="task-table-wrap">
<vxe-table
ref="taskTable"
size="mini"
class="task-vxe"
border
stripe
show-overflow="tooltip"
height="auto"
:data="pagedTaskList"
:row-config="{ isHover: true }"
>
<vxe-column field="projectCode" title="代号" width="72"></vxe-column>
<vxe-column field="taskTitle" title="任务主题" min-width="96"></vxe-column>
<vxe-column field="scheduleProgress" title="对应进度" min-width="112">
<template #default="{ row }">
<div v-if="scheduleProgressUnlinked(row)" class="schedule-progress-wrap">
<el-tag type="info" effect="plain" size="mini">未关联进度</el-tag>
</div>
<div
v-else
class="schedule-progress-wrap schedule-progress-wrap--linked"
:title="scheduleProgressTitle(row)"
>
<el-tag :type="scheduleStepTagType(row)" size="mini" class="schedule-progress-status-tag">
{{ scheduleStepStatusLabel(row) }}
</el-tag>
<span class="schedule-progress-path">{{ scheduleProgressPath(row) }}</span>
</div>
</template>
</vxe-column>
<vxe-column field="taskType" title="工作类型" width="86">
<template #default="{ row }">
<dict-tag :options="dict.type.sys_work_type" :value="row.taskType" />
</template>
</vxe-column>
<vxe-column field="taskGrade" title="任务级别" width="78">
<template #default="{ row }">
<dict-tag :options="dict.type.sys_sort_grade" :value="row.taskGrade" />
</template>
</vxe-column>
<vxe-column field="workerNickName" title="执行人" width="76">
<template #default="{ row }">{{ row.workerNickName || '-' }}</template>
</vxe-column>
<vxe-column field="createUserNickName" title="创建人" width="76">
<template #default="{ row }">{{ row.createUserNickName || '-' }}</template>
</vxe-column>
<vxe-column field="state" title="状态" width="88">
<template #default="{ row }">
<el-tag :type="getStateTagType(row.state)" size="mini">{{ stateText(row.state) }}</el-tag>
</template>
</vxe-column>
<vxe-column field="finishTime" title="预期结束" width="100">
<template #default="{ row }">{{ formatDate(row.finishTime) }}</template>
</vxe-column>
</vxe-table>
</div>
<div class="task-pager">
<el-pagination
small
background
layout="total, prev, pager, next"
:total="filteredTaskTotal"
:current-page.sync="taskQuery.pageNum"
:page-size.sync="taskQuery.pageSize"
@current-change="noop"
/>
</div>
</div>
</div>
<!-- 进度板块 与进度中心(step) 同款的简要信息条 + Tab -->
<div class="panel schedule-panel">
<div v-if="currentProjectId" class="schedule-board-summary schedule-board-summary--pace-like">
<el-row>
<div>
<el-row>
<el-col :span="12">
<div style="font-size: small;">
<span style="color:#d0d0d0 ">项目名</span>
<el-popover placement="bottom" trigger="hover" width="800">
<template slot="reference">
<span style="color: #409eff;">{{ projectDisplayName }}</span>
</template>
<ProjectInfo :info="projectDetail || {}" />
</el-popover>
</div>
</el-col>
<el-col :span="12">
<div style="font-size: small;">
<span style="color:#d0d0d0 ">项目负责人</span>
<span>{{ projectManagerName }}</span>
</div>
</el-col>
<el-col :span="12">
<div style="font-size: small;">
<span style="color:#d0d0d0 ">当前进度</span>
<span>{{ dashboardScheduleSummary }}</span>
</div>
</el-col>
<el-col :span="12">
<div style="font-size: small;">
<span style="color:#d0d0d0 ">项目状态</span>
<span v-if="projectIsTop" style="color: #ff4d4f;">重点关注</span>
<span v-else>一般项目</span>
</div>
</el-col>
</el-row>
</div>
</el-row>
</div>
<el-tabs v-model="scheduleViewTab" class="schedule-panel-tabs" @tab-click="onScheduleTabClick">
<el-tab-pane label="进度明细" name="list">
<!-- 进度数据表格自然撑开高度 .progress-table-scroll 单独承担纵向/横向滚动 -->
<div class="progress-table-pane" v-loading="xmindLoading">
<el-empty
v-if="!stepList || stepList.length === 0"
:image-size="72"
description="暂无进度步骤数据"
/>
<div v-else class="progress-table-scroll">
<el-table
:data="scheduleStepsForTable"
size="mini"
border
stripe
class="progress-step-el-table"
>
<el-table-column prop="firstLevelNode" label="一级节点" min-width="112" show-overflow-tooltip />
<el-table-column prop="secondLevelNode" label="二级节点" min-width="112" show-overflow-tooltip />
<el-table-column prop="stepName" label="步骤名称" min-width="128" show-overflow-tooltip />
<el-table-column label="状态" width="92" align="center">
<template slot-scope="{ row }">
<el-tag :type="stepStatusTagType(row)" size="mini">{{ stepStatusLabel(row) }}</el-tag>
</template>
</el-table-column>
<el-table-column label="计划开始" width="108" align="center">
<template slot-scope="{ row }">{{ formatStepPlanStart(row) }}</template>
</el-table-column>
<el-table-column label="计划结束" width="108" align="center">
<template slot-scope="{ row }">{{ formatStepPlanEnd(row) }}</template>
</el-table-column>
<el-table-column label="负责人" min-width="96" show-overflow-tooltip>
<template slot-scope="{ row }">{{ formatStepResponsible(row) }}</template>
</el-table-column>
</el-table>
</div>
</div>
</el-tab-pane>
<el-tab-pane label="进度导图" name="mind">
<div class="schedule-tab-pane-inner schedule-tab-pane-inner--mind">
<div class="schedule-mind">
<div class="xmind-wrap" v-loading="xmindLoading">
<div v-if="xmindEmpty" class="xmind-empty">
<el-empty :image-size="80" description="暂无进度步骤数据"></el-empty>
</div>
<xmind
ref="dashboardMindXmind"
v-else-if="scheduleViewTab === 'mind'"
:list="stepList"
height="100%"
dashboard-mode
@refresh="onXmindRefresh"
/>
</div>
<aside v-if="scheduleViewTab === 'mind' && !xmindEmpty && stepList && stepList.length" class="mind-legend-aside">
<div class="mind-legend-title">状态图例</div>
<div class="mind-legend-row">
<i class="lg-dot lg-dot--done" />
<span>已完成</span>
</div>
<div class="mind-legend-row">
<i class="lg-dot lg-dot--doing" />
<span>进行中 / 待验收</span>
</div>
<div class="mind-legend-row">
<i class="lg-dot lg-dot--todo" />
<span>未开始 / 暂停</span>
</div>
<p class="mind-legend-tip">连线已完成节点为绿色可滚轮缩放拖动画布</p>
</aside>
</div>
</div>
</el-tab-pane>
</el-tabs>
</div>
</template>
</div>
</div>
</div>
</template>
<script>
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, ProjectInfo },
dicts: ['sys_work_type', 'sys_sort_grade'],
data () {
return {
pageLoading: false,
projectLoading: false,
xmindLoading: false,
/** 进度板块:默认进度明细表,切换为进度导图(原思维导图) */
scheduleViewTab: 'list'
}
},
computed: {
...mapState('oaProjectDashboard2', [
'currentProjectId',
'projectQuery',
'projectList',
'projectTotal',
'taskQuery',
'taskList',
'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()
const kw = (this.taskQuery.taskKeyword || '').trim().toLowerCase()
return list.filter(r => {
const okCode = !code || String(r.projectCode || '').toLowerCase().indexOf(code) !== -1
const title = String(r.taskTitle || '').toLowerCase()
const okKw = !kw || title.indexOf(kw) !== -1
return okCode && okKw
})
},
filteredTaskTotal () {
return (this.filteredTaskList || []).length
},
pagedTaskList () {
const pageNum = Number(this.taskQuery.pageNum || 1)
const pageSize = Number(this.taskQuery.pageSize || 10)
const start = (pageNum - 1) * pageSize
return (this.filteredTaskList || []).slice(start, start + pageSize)
},
xmindEmpty () {
return !this.stepList || this.stepList.length === 0
},
/** 进度明细表:按步骤序号排序,便于阅读 */
scheduleStepsForTable () {
const list = this.stepList || []
return [...list].sort((a, b) => {
const oa = Number(a.stepOrder != null ? a.stepOrder : 0)
const ob = Number(b.stepOrder != null ? b.stepOrder : 0)
return oa - ob
})
}
},
created () {
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 {
await this.$store.dispatch('oaProjectDashboard2/fetchProjectList', {
pageNum: this.projectQuery.pageNum,
pageSize: this.projectQuery.pageSize,
keyword: this.projectQuery.keyword
})
} catch (e) {
this.$message.error('项目列表加载失败,请稍后重试')
} finally {
this.projectLoading = false
}
},
async handleSelectProject (p) {
const projectId = p && p.projectId
if (!projectId) return
this.scheduleViewTab = 'list'
this.pageLoading = true
this.xmindLoading = true
try {
this.$store.commit('oaProjectDashboard2/SET_TASK_QUERY', { pageNum: 1 })
await this.$store.dispatch('oaProjectDashboard2/selectProject', projectId)
} catch (e) {
this.$message.error((e && e.message) || '加载项目数据失败,请稍后重试')
} finally {
this.pageLoading = false
this.xmindLoading = false
}
},
resetTaskPage () {
this.$store.commit('oaProjectDashboard2/SET_TASK_QUERY', { pageNum: 1 })
},
resetTaskFilters () {
this.$store.commit('oaProjectDashboard2/SET_TASK_QUERY', {
pageNum: 1,
pageSize: 10,
projectCode: '',
taskKeyword: ''
})
},
async refreshCurrent () {
if (!this.currentProjectId) return
this.pageLoading = true
this.xmindLoading = true
try {
await this.$store.dispatch('oaProjectDashboard2/selectProject', this.currentProjectId)
} catch (e) {
this.$message.error('刷新失败,请稍后重试')
} 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()
}
})
})
}
}
},
/** 计划开始:库表 plan_start 可能为空,与进度页一致回退 start_time */
formatStepPlanStart (row) {
const raw = row && (row.planStart != null && row.planStart !== '' ? row.planStart : row.startTime)
return this.formatDateFlexible(raw)
},
formatStepPlanEnd (row) {
const raw = row && (row.planEnd != null && row.planEnd !== '' ? row.planEnd : row.endTime)
return this.formatDateFlexible(raw)
},
formatDateFlexible (val) {
if (val == null || val === '') return '-'
if (Array.isArray(val) && val.length >= 3) {
const y = val[0]
const mo = String(val[1]).padStart(2, '0')
const d = String(val[2]).padStart(2, '0')
return `${y}-${mo}-${d}`
}
if (typeof val === 'string') {
const s = val.trim()
if (!s) return '-'
if (s.length >= 10 && s[4] === '-' && s[7] === '-') return s.substring(0, 10)
const m = s.match(/^(\d{4}-\d{2}-\d{2})/)
return m ? m[1] : s
}
if (val instanceof Date && !isNaN(val.getTime())) {
const y = val.getFullYear()
const mo = String(val.getMonth() + 1).padStart(2, '0')
const d = String(val.getDate()).padStart(2, '0')
return `${y}-${mo}-${d}`
}
return '-'
},
/** 负责人:业务侧常用 node_header兼容 header */
formatStepResponsible (row) {
if (!row) return '-'
const h = row.header != null && String(row.header).trim() !== '' ? String(row.header).trim() : ''
if (h) return h
const n = row.nodeHeader != null && String(row.nodeHeader).trim() !== '' ? String(row.nodeHeader).trim() : ''
return n || '-'
},
async onXmindRefresh () {
await this.refreshCurrent()
},
noop () {},
/** 无 track_id 或无步骤名称 → 未关联(后端联表 oa_project_schedule_stepuse_flag=1 */
scheduleProgressUnlinked (row) {
if (!row || row.trackId == null || row.trackId === '') return true
const n = row.scheduleStepName
return n == null || String(n).trim() === ''
},
scheduleStepTagType (row) {
const s = row && row.scheduleStatus
if (s === null || s === undefined) return 'info'
const v = Number(s)
if (v === 2) return 'success'
if (v === 3) return 'warning'
if (v === 1) return 'primary'
if (v === 0) return 'info'
return 'info'
},
scheduleStepStatusLabel (row) {
const s = row && row.scheduleStatus
if (s === null || s === undefined) return '—'
const v = Number(s)
if (v === 2) return '已完成'
if (v === 1) return '进行中'
if (v === 3) return '暂停'
if (v === 0) return '未开始'
return '—'
},
scheduleProgressPath (row) {
if (!row) return ''
const n = row.scheduleStepName
return (n != null && String(n).trim() !== '') ? String(n).trim() : ''
},
scheduleProgressTitle (row) {
return `${this.scheduleStepStatusLabel(row)} ${this.scheduleProgressPath(row)}`.trim()
},
/** oa_project_schedule_step.status与任务表「对应进度」标签一致 */
stepStatusLabel (row) {
return this.scheduleStepStatusLabel({ scheduleStatus: row && row.status })
},
stepStatusTagType (row) {
return this.scheduleStepTagType({ scheduleStatus: row && row.status })
},
formatDate (val) {
if (!val) return '-'
const s = String(val)
return s.length >= 10 ? s.substring(0, 10) : s
},
stateText (val) {
if (val === null || val === undefined || val === '') return '-'
const v = Number(val)
if (v === 2) return '执行完成'
if (v === 1) return '待验收'
if (v === 15) return '延期申请中'
if (v === 0) return '进行中'
return '其他'
},
getStateTagType (val) {
if (val === null || val === undefined || val === '') return 'info'
const v = Number(val)
if (v === 2) return 'success'
if (v === 1) return 'warning'
if (v === 15) return 'warning'
if (v === 0) return 'info'
return 'info'
}
}
}
</script>
<style scoped>
.dashboard2 {
font-size: 13px;
color: #606266;
}
.layout {
display: flex;
gap: 8px;
align-items: stretch;
height: calc(100vh - 120px);
min-height: 0;
max-height: calc(100vh - 120px);
}
.left {
width: 20%;
flex: 0 0 20%;
min-width: 220px;
max-width: 300px;
border: 1px solid #e4e7ed;
border-radius: 6px;
overflow: hidden;
display: flex;
flex-direction: column;
padding: 10px;
box-sizing: border-box;
background: #fafbfc;
}
.left-search {
display: flex;
flex-direction: row;
align-items: center;
gap: 8px;
margin-bottom: 10px;
}
.left-keyword-input {
flex: 1;
min-width: 0;
}
.left-search-btn {
flex-shrink: 0;
padding-left: 14px;
padding-right: 14px;
}
.project-list {
flex: 1;
overflow: auto;
min-height: 0;
}
.project-item {
padding: 6px 8px;
cursor: pointer;
border-bottom: 1px solid #eef0f3;
border-radius: 4px;
margin-bottom: 2px;
min-height: 42px;
display: flex;
flex-direction: column;
justify-content: center;
transition: background 0.15s ease;
}
.project-item:hover {
background: #f0f2f5;
}
.project-item.active {
background: #ecf5ff;
border-left: 3px solid #409eff;
padding-left: 5px;
box-shadow: inset 0 0 0 1px rgba(64, 158, 255, 0.15);
}
.project-name {
font-weight: 500;
font-size: 12px;
line-height: 1.35;
color: #303133;
}
.project-meta {
font-size: 11px;
color: #909399;
line-height: 1.35;
margin-top: 3px;
}
.project-meta .code {
margin-right: 8px;
color: #409eff;
}
.left-pager {
padding-top: 8px;
margin-top: 4px;
border-top: 1px solid #ebeef5;
}
.left-pager :deep(.el-pagination) {
justify-content: center;
}
.left-empty {
padding: 10px;
}
.right {
flex: 1;
min-width: 0;
min-height: 0;
display: flex;
flex-direction: column;
gap: 8px;
height: 100%;
}
.right-empty {
border: 1px dashed #dcdfe6;
border-radius: 4px;
flex: 1;
display: flex;
align-items: center;
justify-content: center;
}
.panel {
border: 1px solid #ebeef5;
border-radius: 4px;
overflow: hidden;
background: #fff;
display: flex;
flex-direction: column;
min-height: 0;
}
.panel-header {
padding: 8px 12px;
border-bottom: 1px solid #ebeef5;
}
/* 原「任务列表」标题条去掉后,工具栏区沿用顶栏灰底,与下方表格白底区分不变 */
.task-panel__toolbar {
background: #fafafa;
}
.panel-body {
padding: 8px 12px;
}
.panel-body-flex {
display: flex;
flex-direction: column;
flex: 1;
min-height: 0;
}
.toolbar {
display: flex;
align-items: center;
gap: 4px;
flex-wrap: wrap;
}
.task-panel {
flex: 4 1 0%;
min-height: 0;
}
.schedule-panel {
flex: 6 1 0%;
min-height: 0;
display: flex;
flex-direction: column;
padding: 0;
}
.schedule-board-summary--pace-like {
flex-shrink: 0;
padding: 10px 12px 8px;
border-bottom: 1px solid #ebeef5;
}
/*
* 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;
flex-direction: column;
height: 100%;
}
.schedule-panel-tabs.el-tabs :deep(.el-tabs__header) {
margin-bottom: 0;
padding: 0 12px;
flex-shrink: 0;
}
.schedule-panel-tabs.el-tabs :deep(.el-tabs__content) {
flex: 1 1 0%;
min-height: 0;
overflow: hidden;
padding: 0;
display: flex;
flex-direction: column;
}
/* 当前激活的 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;
}
.schedule-tab-pane-inner {
flex: 1 1 0%;
min-height: 0;
min-width: 0;
width: 100%;
height: 100%;
box-sizing: border-box;
display: flex;
flex-direction: column;
}
.schedule-tab-pane-inner--mind {
min-height: 280px;
overflow: hidden;
width: 100%;
min-width: 0;
}
/* 进度明细 Tab表格与面板四边留白避免贴边 */
.progress-table-pane {
flex: 1 1 0%;
min-height: 0;
width: 100%;
height: 300px;
box-sizing: border-box;
display: flex;
flex-direction: column;
padding: 14px 18px 18px;
}
.progress-table-pane :deep(.el-empty) {
padding: 24px 8px;
}
.progress-table-scroll {
flex: 1 1 0%;
min-height: 0;
min-width: 0;
overflow: auto;
-webkit-overflow-scrolling: touch;
/* 表格与滚动容器边缘再留一线间距,滚动条不压表格边框 */
padding: 2px 4px 4px 2px;
}
.progress-step-el-table {
width: 100%;
}
.schedule-mind {
display: flex;
flex-direction: row;
align-items: stretch;
flex: 1;
flex-basis: 0;
min-height: 0;
min-width: 0;
width: 100%;
}
.task-table-wrap {
flex: 1;
min-height: 0;
min-width: 0;
overflow: hidden;
}
.task-table-wrap :deep(.vxe-table) {
font-size: 12px;
width: 100%;
}
.task-table-wrap :deep(.vxe-table--body-wrapper),
.task-table-wrap :deep(.vxe-table--header-wrapper) {
overflow-x: hidden;
}
.schedule-progress-wrap {
display: flex;
align-items: center;
gap: 6px;
max-width: 100%;
line-height: 1.35;
}
.schedule-progress-wrap--linked {
min-width: 0;
}
.schedule-progress-status-tag {
flex-shrink: 0;
}
.schedule-progress-path {
flex: 1;
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
font-size: 12px;
color: #606266;
}
.task-pager {
margin-top: 8px;
display: flex;
justify-content: flex-end;
}
.xmind-wrap {
flex: 1 1 0%;
min-width: 0;
position: relative;
padding: 0;
display: flex;
flex-direction: column;
min-height: 320px;
}
.xmind-wrap :deep(.xmind-box--dashboard) {
flex: 1;
min-height: 0;
display: flex;
flex-direction: column;
}
.xmind-wrap :deep(.xmind-container) {
flex: 1;
min-height: 300px !important;
}
.mind-legend-aside {
flex: 0 0 132px;
padding: 10px 12px;
border-left: 1px solid #ebeef5;
background: #fafafa;
font-size: 12px;
color: #606266;
line-height: 1.5;
}
.mind-legend-title {
font-weight: 600;
color: #303133;
margin-bottom: 10px;
font-size: 13px;
}
.mind-legend-row {
display: flex;
align-items: center;
gap: 8px;
margin-bottom: 8px;
}
.lg-dot {
display: inline-block;
width: 8px;
height: 8px;
border-radius: 50%;
flex-shrink: 0;
}
.lg-dot--done {
background: #67c23a;
}
.lg-dot--doing {
background: #e6a23c;
}
.lg-dot--todo {
background: #909399;
}
.mind-legend-tip {
margin: 12px 0 0;
font-size: 11px;
color: #909399;
line-height: 1.45;
}
.xmind-empty {
height: 100%;
min-height: 240px;
display: flex;
align-items: center;
justify-content: center;
border: 1px dashed #ebeef5;
border-radius: 4px;
}
.text-ellipsis {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
</style>