style: align task progress cockpit with site visual standard

This commit is contained in:
Your Name 2026-02-07 18:24:30 +08:00
parent ba733cd47b
commit 9fc0e7cc43

View File

@ -1,20 +1,19 @@
<template>
<DefaultLayout>
<div class="task-progress-page">
<div class="cosmic-backdrop"></div>
<div class="dashboard-shell">
<div class="headline">
<div class="headline-text">
<p class="eyebrow">TASK PROGRESS INTELLIGENCE</p>
<p class="eyebrow">数据分析 / 任务进度</p>
<h1>全量任务进度指挥舱</h1>
<p class="subtitle">面向全角色的实时态势墙,串联 KPI、银河图、流程时间线与风险墙,帮助管理层快速定位卡点。</p>
<p class="subtitle">统一展示任务状态、风险和部门负载,保持与现有数据分析页面一致的视觉规范。</p>
</div>
<div class="headline-actions">
<div class="snapshot-chip">
<span>快照时间</span>
<strong>{{ snapshotTimeText }}</strong>
</div>
<a-button ghost size="large" @click="refreshData">
<a-button type="primary" @click="refreshData">
<template #icon><Icon name="refresh" size="sm" /></template>
刷新快照
</a-button>
@ -80,7 +79,7 @@
<h3>任务银河图</h3>
<p>颜色 = 状态 · X = 创建时间 · Y = 进度百分比</p>
</div>
<a-tag color="blue" v-if="galaxyCount">{{ galaxyCount }} 颗节点</a-tag>
<a-tag color="green" v-if="galaxyCount">{{ galaxyCount }} 颗节点</a-tag>
</div>
<div class="chart-layer" ref="galaxyChartRef"></div>
<div v-if="!galaxyHasData" class="panel-empty">
@ -145,7 +144,7 @@
<h3>风险雷达墙</h3>
<p>优先展示即将超时与已超时任务</p>
</div>
<a-tag color="red" v-if="riskCards.length">{{ riskCards.length }} 项</a-tag>
<a-tag color="orange" v-if="riskCards.length">{{ riskCards.length }} 项</a-tag>
</div>
<div class="risk-grid" v-if="riskCards.length">
<div
@ -212,7 +211,7 @@
<h3>关键任务序列</h3>
<p>按最新创建时间取前 20 个任务,可直接复制编号</p>
</div>
<a-tag color="gold" v-if="taskTableData.length">TOP {{ taskTableData.length }}</a-tag>
<a-tag color="green" v-if="taskTableData.length">TOP {{ taskTableData.length }}</a-tag>
</div>
<a-table
:columns="taskColumns"
@ -447,8 +446,8 @@ const copyTaskNo = async (taskNo?: string) => {
const workloadToBackground = (value: number) => {
const ratio = Math.min(1, value / (deptMaxWorkload.value || 1))
const alpha = 0.25 + ratio * 0.55
return `linear-gradient(135deg, rgba(60,126,255,${alpha}), rgba(36,209,196,${alpha - 0.1}))`
const alpha = 0.08 + ratio * 0.24
return `linear-gradient(135deg, rgba(49,240,139,${alpha}), rgba(255,255,255,0.02))`
}
const priorityLabel = (priority?: number) => {
@ -534,9 +533,9 @@ const renderGalaxyChart = () => {
backgroundColor: 'transparent',
tooltip: {
trigger: 'item',
backgroundColor: 'rgba(5,11,22,0.85)',
borderColor: 'rgba(255,255,255,0.1)',
textStyle: { color: '#e6f7ff' },
backgroundColor: '#1e1e1e',
borderColor: 'rgba(255,255,255,0.16)',
textStyle: { color: '#e5e5e5' },
formatter: (params: any) => {
const task = params.data.task as TaskProgressTaskPoint
return `任务:${task.title}<br/>状态:${statusLabel(task.status)}<br/>积分:${task.score || '-'}<br/>进度:${task.progress || 0}%`
@ -544,17 +543,17 @@ const renderGalaxyChart = () => {
},
xAxis: {
type: 'time',
axisLine: { lineStyle: { color: 'rgba(255,255,255,0.2)' } },
axisLine: { lineStyle: { color: 'rgba(255,255,255,0.16)' } },
axisLabel: { color: 'rgba(255,255,255,0.6)' },
splitLine: { lineStyle: { color: 'rgba(255,255,255,0.08)' } }
splitLine: { lineStyle: { color: 'rgba(255,255,255,0.06)' } }
},
yAxis: {
type: 'value',
min: 0,
max: 100,
axisLabel: { color: 'rgba(255,255,255,0.6)', formatter: '{value}%' },
axisLine: { lineStyle: { color: 'rgba(255,255,255,0.2)' } },
splitLine: { lineStyle: { color: 'rgba(255,255,255,0.08)' } }
axisLine: { lineStyle: { color: 'rgba(255,255,255,0.16)' } },
splitLine: { lineStyle: { color: 'rgba(255,255,255,0.06)' } }
},
series: [
{
@ -590,9 +589,9 @@ const renderStatusChart = () => {
type: 'pie',
radius: ['45%', '80%'],
startAngle: 220,
itemStyle: { borderColor: '#050b16', borderWidth: 2 },
itemStyle: { borderColor: '#1e1e1e', borderWidth: 2 },
label: {
color: '#e6f7ff',
color: '#e5e5e5',
formatter: '{b}\n{d}%'
},
data: entries.map(([status, value]) => ({
@ -620,19 +619,19 @@ const renderTrendChart = () => {
tooltip: { trigger: 'axis' },
legend: {
data: ['创建', '完结'],
textStyle: { color: '#e6f7ff' }
textStyle: { color: '#e5e5e5' }
},
grid: { left: 30, right: 20, top: 30, bottom: 20 },
xAxis: {
type: 'category',
data: trend.map(item => item.date),
axisLabel: { color: 'rgba(255,255,255,0.6)' },
axisLine: { lineStyle: { color: 'rgba(255,255,255,0.2)' } }
axisLine: { lineStyle: { color: 'rgba(255,255,255,0.16)' } }
},
yAxis: {
type: 'value',
axisLabel: { color: 'rgba(255,255,255,0.6)' },
splitLine: { lineStyle: { color: 'rgba(255,255,255,0.08)' } }
splitLine: { lineStyle: { color: 'rgba(255,255,255,0.06)' } }
},
series: [
{
@ -640,8 +639,8 @@ const renderTrendChart = () => {
type: 'line',
smooth: true,
showSymbol: false,
areaStyle: { color: 'rgba(60,126,255,0.2)' },
lineStyle: { color: '#3c7eff' },
areaStyle: { color: 'rgba(49,240,139,0.12)' },
lineStyle: { color: '#31f08b' },
data: trend.map(item => item.createdCount)
},
{
@ -649,8 +648,8 @@ const renderTrendChart = () => {
type: 'line',
smooth: true,
showSymbol: false,
areaStyle: { color: 'rgba(49,240,139,0.2)' },
lineStyle: { color: '#31f08b' },
areaStyle: { color: 'rgba(96,165,250,0.12)' },
lineStyle: { color: '#60a5fa' },
data: trend.map(item => item.completedCount)
}
]
@ -676,96 +675,88 @@ onBeforeUnmount(() => {
</script>
<style lang="scss" scoped>
.task-progress-page {
position: relative;
min-height: 100vh;
padding: 48px 0;
background: #030712;
color: #f7fbff;
overflow: hidden;
}
@import "@/styles/variables.scss";
.cosmic-backdrop {
position: absolute;
inset: 0;
background: radial-gradient(circle at 20% 20%, rgba(60, 126, 255, 0.35), transparent 55%),
radial-gradient(circle at 80% 0%, rgba(36, 209, 196, 0.25), transparent 40%),
#030712;
filter: blur(0px);
pointer-events: none;
.task-progress-page {
min-height: 100vh;
padding: $space-xl 0;
background: $bg-primary;
}
.dashboard-shell {
position: relative;
z-index: 1;
max-width: 1400px;
max-width: $container-max-width;
margin: 0 auto;
padding: 0 32px 80px;
padding: 0 $space-xl $space-xxl;
}
.headline {
display: flex;
justify-content: space-between;
gap: 24px;
margin-bottom: 32px;
flex-wrap: wrap;
align-items: flex-start;
gap: $space-lg;
margin-bottom: $space-xl;
}
.eyebrow {
color: #5f8cff;
letter-spacing: 0.2em;
font-size: 12px;
margin-bottom: 8px;
color: $text-secondary;
font-size: $font-size-sm;
margin-bottom: $space-xs;
}
.headline h1 {
margin: 0;
font-size: 40px;
font-weight: 600;
font-size: $font-size-2xl;
color: $text-white;
}
.subtitle {
max-width: 520px;
color: rgba(255, 255, 255, 0.7);
margin-top: $space-xs;
font-size: $font-size-base;
color: $text-secondary;
max-width: 700px;
}
.headline-actions {
display: flex;
align-items: center;
gap: 16px;
gap: $space-sm;
}
.snapshot-chip {
padding: 12px 20px;
border-radius: 16px;
border: 1px solid rgba(255, 255, 255, 0.1);
background: rgba(5, 11, 22, 0.6);
display: flex;
flex-direction: column;
padding: 10px 14px;
border-radius: $radius-md;
border: 1px solid $border-primary;
background: $bg-card;
min-width: 180px;
}
.snapshot-chip span {
font-size: 12px;
color: rgba(255, 255, 255, 0.6);
color: $text-secondary;
font-size: $font-size-sm;
}
.snapshot-chip strong {
font-size: 20px;
display: block;
color: $text-primary;
font-size: $font-size-lg;
}
.filter-row {
background: $bg-card;
border: 1px solid $border-primary;
border-radius: $radius-lg;
padding: $space-md;
display: flex;
justify-content: space-between;
align-items: center;
gap: 16px;
margin-bottom: 32px;
flex-wrap: wrap;
gap: $space-md;
margin-bottom: $space-lg;
}
.filter-left {
display: flex;
gap: 12px;
align-items: center;
gap: $space-sm;
flex-wrap: wrap;
}
@ -775,195 +766,206 @@ onBeforeUnmount(() => {
.kpi-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
gap: 16px;
margin-bottom: 32px;
grid-template-columns: repeat(6, minmax(0, 1fr));
gap: $space-sm;
margin-bottom: $space-lg;
}
.kpi-card {
padding: 16px;
border-radius: 16px;
border: 1px solid rgba(255, 255, 255, 0.1);
background: rgba(5, 12, 25, 0.7);
backdrop-filter: blur(20px);
background: $bg-card;
border: 1px solid $border-primary;
border-radius: $radius-lg;
padding: $space-md;
}
.kpi-label {
color: rgba(255, 255, 255, 0.6);
font-size: 13px;
color: $text-secondary;
font-size: $font-size-sm;
}
.kpi-value {
font-size: 28px;
font-weight: 600;
margin-top: 2px;
font-size: $font-size-xl;
font-weight: $font-weight-semibold;
color: $text-primary;
}
.kpi-meta {
color: rgba(255, 255, 255, 0.6);
font-size: 12px;
margin-top: $space-xs;
color: $text-tertiary;
font-size: $font-size-xs;
}
.hero-grid {
.hero-grid,
.secondary-grid {
display: grid;
grid-template-columns: 2fr 1.1fr;
gap: 20px;
margin-bottom: 24px;
grid-template-columns: 2fr 1fr;
gap: $space-md;
margin-bottom: $space-md;
}
.secondary-grid {
grid-template-columns: 1fr 1fr;
}
.panel {
position: relative;
border-radius: 24px;
padding: 24px;
background: rgba(4, 9, 18, 0.85);
border: 1px solid rgba(255, 255, 255, 0.06);
box-shadow: 0 20px 60px rgba(3, 7, 18, 0.6);
overflow: hidden;
background: $bg-card;
border: 1px solid $border-primary;
border-radius: $radius-lg;
padding: $space-md;
}
.panel-header {
display: flex;
justify-content: space-between;
align-items: flex-start;
margin-bottom: $space-sm;
}
.panel-header h3 {
margin: 0;
font-size: 20px;
font-size: $font-size-lg;
color: $text-primary;
}
.panel-header p {
margin: 4px 0 0;
color: rgba(255, 255, 255, 0.55);
margin: 2px 0 0;
font-size: $font-size-sm;
color: $text-secondary;
}
.chart-layer {
position: relative;
height: 320px;
height: 280px;
}
.galaxy-panel .chart-layer {
height: 420px;
height: 360px;
}
.panel-empty {
position: absolute;
inset: 24px;
display: flex;
align-items: center;
justify-content: center;
background: rgba(3, 7, 18, 0.8);
border-radius: 16px;
align-items: center;
min-height: 220px;
}
.panel-empty.flat {
position: static;
background: transparent;
border: none;
min-height: 120px;
}
.summary-panel {
display: flex;
flex-direction: column;
gap: 16px;
}
.status-block,
.trend-block {
flex: 1;
padding-bottom: 16px;
}
.secondary-grid {
display: grid;
grid-template-columns: 1.2fr 1fr;
gap: 20px;
margin-bottom: 24px;
gap: $space-sm;
}
.stage-list {
display: flex;
flex-direction: column;
gap: 16px;
display: grid;
gap: $space-sm;
}
.stage-item {
background: rgba(255, 255, 255, 0.02);
padding: 12px 16px;
border-radius: 16px;
border: 1px solid rgba(255, 255, 255, 0.06);
background: $bg-secondary;
border-radius: $radius-md;
padding: $space-sm $space-md;
}
.stage-label {
display: flex;
justify-content: space-between;
font-size: 14px;
font-size: $font-size-sm;
}
.stage-bar {
height: 6px;
background: rgba(255, 255, 255, 0.08);
border-radius: 999px;
margin: 8px 0;
background: rgba(255, 255, 255, 0.08);
margin: 6px 0;
}
.stage-bar-fill {
height: 100%;
border-radius: 999px;
background: linear-gradient(90deg, #3c7eff, #24d1c4);
border-radius: inherit;
background: linear-gradient(90deg, $brand-primary, #60a5fa);
}
.stage-percentage {
font-size: 12px;
color: rgba(255, 255, 255, 0.6);
font-size: $font-size-xs;
color: $text-secondary;
}
.insight-list {
margin: 24px 0 0;
padding-left: 18px;
color: rgba(255, 255, 255, 0.75);
margin: $space-md 0 0;
padding-left: 16px;
color: $text-secondary;
font-size: $font-size-sm;
}
.risk-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 12px;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: $space-sm;
}
.risk-card {
padding: 16px;
border-radius: 20px;
background: rgba(255, 255, 255, 0.04);
border: 1px solid rgba(255, 255, 255, 0.06);
border: 1px solid $border-primary;
background: $bg-secondary;
border-radius: $radius-md;
padding: $space-sm $space-md;
}
.risk-card h4 {
margin: $space-xs 0;
font-size: $font-size-base;
color: $text-primary;
}
.risk-card p {
margin: 0;
font-size: $font-size-sm;
color: $text-secondary;
line-height: 1.5;
}
.risk-card .risk-reason {
margin-top: $space-xs;
}
.risk-card.danger {
border-color: rgba(255, 95, 109, 0.6);
box-shadow: 0 10px 30px rgba(255, 95, 109, 0.25);
border-color: rgba(239, 68, 68, 0.45);
}
.risk-card.warning {
border-color: rgba(246, 166, 35, 0.4);
}
.ghost-link {
background: none;
border: none;
color: #5f8cff;
cursor: pointer;
font-size: 12px;
border-color: rgba(245, 158, 11, 0.4);
}
.risk-meta {
display: flex;
justify-content: space-between;
font-size: 12px;
margin-bottom: 4px;
align-items: center;
font-size: $font-size-xs;
color: $text-secondary;
}
.ghost-link {
background: none;
border: none;
color: $brand-primary;
cursor: pointer;
font-size: $font-size-xs;
}
.dept-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
gap: 16px;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: $space-sm;
}
.dept-card {
padding: 16px;
border-radius: 20px;
border: 1px solid rgba(255, 255, 255, 0.05);
min-height: 160px;
border-radius: $radius-md;
border: 1px solid $border-primary;
padding: $space-sm $space-md;
}
.dept-title {
@ -972,47 +974,58 @@ onBeforeUnmount(() => {
align-items: center;
}
.dept-title h4 {
margin: 0;
font-size: $font-size-base;
color: $text-primary;
}
.badge {
padding: 2px 10px;
background: rgba(49, 240, 139, 0.14);
color: $brand-primary;
border-radius: 999px;
background: rgba(0, 0, 0, 0.2);
font-size: 12px;
padding: 2px 8px;
font-size: $font-size-xs;
}
.dept-metrics {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 8px;
margin-top: 16px;
font-size: 12px;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: $space-sm;
margin-top: $space-sm;
}
.dept-metrics span {
display: block;
color: $text-secondary;
font-size: $font-size-xs;
}
.dept-metrics strong {
display: block;
font-size: 24px;
}
.task-panel .ant-table {
background: transparent;
color: $text-primary;
font-size: $font-size-lg;
}
.task-title {
display: flex;
flex-direction: column;
gap: 2px;
}
.task-no {
color: #5f8cff;
color: #60a5fa;
font-size: $font-size-xs;
cursor: pointer;
font-size: 12px;
}
.status-pill {
display: inline-flex;
padding: 4px 12px;
align-items: center;
padding: 2px 10px;
border-radius: 999px;
font-size: 12px;
color: #050b16;
color: $text-white;
font-size: $font-size-xs;
}
.progress-bar {
@ -1024,76 +1037,123 @@ onBeforeUnmount(() => {
.progress-bar-fill {
position: absolute;
left: 0;
top: 0;
bottom: 0;
inset: 0 auto 0 0;
border-radius: inherit;
background: linear-gradient(90deg, #31f08b, #24d1c4);
background: $brand-primary;
}
.progress-bar span {
position: absolute;
top: -20px;
right: 0;
font-size: 12px;
top: -18px;
font-size: $font-size-xs;
color: $text-secondary;
}
.priority-pill {
padding: 4px 10px;
border-radius: 10px;
font-size: 12px;
text-transform: uppercase;
.priority-pill,
.risk-flag {
font-size: $font-size-xs;
padding: 2px 8px;
border-radius: $radius-sm;
}
.priority-pill.danger {
background: rgba(255, 95, 109, 0.2);
color: #ff5f6d;
.priority-pill.danger,
.risk-flag.danger {
color: #ff6b6b;
background: rgba(255, 107, 107, 0.14);
}
.priority-pill.warning {
background: rgba(246, 166, 35, 0.2);
color: #f6a623;
.priority-pill.warning,
.risk-flag.warning {
color: #f6b454;
background: rgba(245, 158, 11, 0.14);
}
.priority-pill.info {
background: rgba(60, 126, 255, 0.2);
color: #3c7eff;
}
.priority-pill.muted {
background: rgba(255, 255, 255, 0.1);
color: rgba(255, 255, 255, 0.6);
}
.risk-flag {
padding: 2px 8px;
border-radius: 6px;
font-size: 12px;
}
.risk-flag.danger {
background: rgba(255, 95, 109, 0.2);
color: #ff5f6d;
}
.risk-flag.warning {
background: rgba(246, 166, 35, 0.2);
color: #f6a623;
color: #60a5fa;
background: rgba(96, 165, 250, 0.14);
}
.priority-pill.muted,
.risk-flag.muted {
color: $text-secondary;
background: rgba(255, 255, 255, 0.08);
color: rgba(255, 255, 255, 0.5);
}
@media (max-width: 1024px) {
.dark-table {
:deep(.ant-table) {
background: transparent !important;
}
:deep(.ant-table-thead > tr > th) {
background: rgba(0, 0, 0, 0.32) !important;
border: none !important;
color: rgba(255, 255, 255, 0.66) !important;
font-size: 13px;
font-weight: 500;
}
:deep(.ant-table-tbody > tr > td) {
border: none !important;
color: rgba(255, 255, 255, 0.86) !important;
font-size: 13px;
background: transparent !important;
}
:deep(.ant-table-tbody > tr:nth-child(even) > td) {
background: rgba(255, 255, 255, 0.015) !important;
}
:deep(.ant-table-tbody > tr:hover > td) {
background: rgba(49, 240, 139, 0.06) !important;
}
}
@media (max-width: 1200px) {
.kpi-grid {
grid-template-columns: repeat(3, minmax(0, 1fr));
}
.hero-grid,
.secondary-grid {
grid-template-columns: 1fr;
}
.dept-grid {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
}
@media (max-width: $breakpoint-mobile) {
.dashboard-shell {
padding: 0 16px 60px;
padding: 0 $space-md $space-xxl;
}
.headline {
flex-direction: column;
margin-bottom: $space-lg;
}
.headline h1 {
font-size: $font-size-xl;
}
.headline-actions {
width: 100%;
justify-content: space-between;
}
.filter-row {
padding: $space-sm;
}
.kpi-grid {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
.risk-grid,
.dept-grid {
grid-template-columns: 1fr;
}
}
</style>