qiangdanxitong/frontend/src/views/DesignerHall.vue
Your Name 3922d3653b feat: 所有页面积分显示支持小数点后两位
- Analytics.vue: 设计师绩效、用户需求、扣分明细、CSV导出
- DesignerHall.vue: 设计师卡片累计积分
- TaskHall.vue: 任务卡片积分、typeDetails积分
- LeaderWorkbench.vue: 任务积分
- MyTasks.vue: 任务积分
- CreateTask.vue: 预估积分、任务类型积分
- RewardRecords.vue: 累计积分、奖励积分
- Goals.vue: 奖励积分
- IncentiveCenter.vue: 用户积分、奖励积分
- Contests.vue: 竞赛得分、奖励积分
- CheckIn.vue: 历史奖励积分
- ScorePoolManagement.vue: 积分变动
- Achievements.vue: 奖励积分
2026-01-15 15:39:32 +08:00

1172 lines
31 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.

<template>
<DefaultLayout>
<div class="designer-hall-page">
<div class="container">
<!-- 总览卡片 -->
<div class="overview-cards">
<div class="overview-card stat-card">
<div class="stat-icon">
<Icon name="users" size="xl" color="#31f08b" />
</div>
<div class="stat-content">
<div class="stat-label">在线设计师</div>
<div class="stat-value">{{ onlineCount }}/{{ totalCount }}</div>
</div>
</div>
<div class="overview-card stat-card">
<div class="stat-icon">
<Icon name="clipboard" size="xl" color="#31f08b" />
</div>
<div class="stat-content">
<div class="stat-label">进行中任务</div>
<div class="stat-value">{{ totalInProgress }}</div>
</div>
</div>
<div class="overview-card stat-card">
<div class="stat-icon">
<Icon name="clock" size="xl" color="#31f08b" />
</div>
<div class="stat-content">
<div class="stat-label">任务队列</div>
<div class="stat-value">{{ totalQueue }}</div>
</div>
</div>
<div class="overview-card stat-card">
<div class="stat-icon">
<Icon name="star" size="xl" color="#31f08b" />
</div>
<div class="stat-content">
<div class="stat-label">平均评分</div>
<div class="stat-value">{{ avgRating }}</div>
</div>
</div>
</div>
<!-- 排行榜Tab -->
<div class="leaderboard-tabs chart-card">
<div class="tabs-header-row" @click="leaderboardExpanded = !leaderboardExpanded" style="cursor: pointer;">
<div class="tabs-header">
<button
v-for="tab in leaderboardTabs"
:key="tab.key"
:class="['tab-button', { active: activeLeaderboard === tab.key }]"
@click.stop="switchLeaderboard(tab.key)"
>
<Icon :name="tab.iconName" size="sm" color="#31f08b" style="margin-right: 4px; vertical-align: middle;" />
{{ tab.label }}
</button>
</div>
<div style="display: flex; align-items: center; gap: 12px;">
<!-- 月份选择器 -->
<div class="month-selector" @click.stop>
<a-select
v-model:value="selectedMonth"
placeholder="选择月份"
style="width: 150px"
@change="handleMonthChange"
>
<a-select-option
v-for="month in availableMonths"
:key="month.value"
:value="month.value"
>
{{ month.label }}
</a-select-option>
</a-select>
</div>
<!-- 展开/折叠按钮 -->
<a-button type="link" size="small" style="color: #31f08b;">
{{ leaderboardExpanded ? '收起' : '展开' }}
<Icon :name="leaderboardExpanded ? 'chevronUp' : 'chevronDown'" size="sm" style="margin-left: 4px;" />
</a-button>
</div>
</div>
<div v-show="leaderboardExpanded" class="leaderboard-content">
<div
v-for="(designer, index) in leaderboardData"
:key="designer.userId"
class="leaderboard-item"
>
<div class="rank">
<span class="rank-number">{{ index + 1 }}</span>
</div>
<div class="designer-info">
<div class="designer-avatar">
{{ designer.userName?.substring(0, 1) }}
</div>
<div class="designer-details">
<div class="designer-name">{{ designer.userName }}</div>
<div class="designer-dept">{{ designer.deptName }}</div>
</div>
</div>
<div class="stat-value">
<span class="value">{{ getLeaderboardValue(designer) }}</span>
<span class="label">{{ getLeaderboardLabel() }}</span>
</div>
</div>
</div>
</div>
<!-- 设计师列表 -->
<div class="designers-section">
<div class="section-header">
<h2 class="section-title">设计师列表</h2>
<div class="filter-area">
<a-select
v-model:value="selectedDeptId"
placeholder="选择部门"
style="width: 180px"
allowClear
@change="handleDeptChange"
>
<a-select-option :value="undefined">全部部门</a-select-option>
<a-select-option
v-for="dept in receivingDepts"
:key="dept.id"
:value="dept.id"
>
{{ dept.deptName }}
</a-select-option>
</a-select>
</div>
</div>
<div class="table-card">
<div v-if="loading" class="loading-container">
<a-spin size="large" />
</div>
<div v-else class="designers-grid">
<div
v-for="designer in designersList"
:key="designer.userId"
class="designer-card chart-card"
>
<!-- 设计师头像和基本信息 -->
<div class="designer-header">
<div class="avatar-wrapper">
<div class="avatar">
{{ designer.userName?.substring(0, 1) }}
</div>
<span
:class="['status-indicator', designer.onlineStatus ? 'online' : 'offline']"
></span>
</div>
<div class="designer-basic">
<h3 class="name">{{ designer.userName }}</h3>
<p class="dept">{{ designer.deptName }}</p>
</div>
</div>
<!-- 统计信息 -->
<div class="designer-stats">
<div class="stat-item">
<div class="stat-label">进行中</div>
<div class="stat-value text-warning">{{ designer.inProgressCount }}</div>
</div>
<div class="stat-item">
<div class="stat-label">队列</div>
<div class="stat-value">{{ designer.queueCount }}</div>
</div>
<div class="stat-item">
<div class="stat-label">累计积分</div>
<div class="stat-value text-brand">{{ formatScore(designer.totalScore) }}</div>
</div>
</div>
<!-- 进度和评分 -->
<div class="designer-metrics">
<div class="metric-row">
<span class="metric-label">完成率</span>
<div class="metric-bar">
<div
class="metric-fill"
:style="{ width: designer.completionRate + '%' }"
></div>
</div>
<span class="metric-value">{{ designer.completionRate?.toFixed(0) }}%</span>
</div>
<div class="metric-row">
<span class="metric-label">准时率</span>
<div class="metric-bar">
<div
class="metric-fill success"
:style="{ width: designer.ontimeRate + '%' }"
></div>
</div>
<span class="metric-value">{{ designer.ontimeRate?.toFixed(0) }}%</span>
</div>
</div>
<!-- 评分和任务数 -->
<div class="designer-footer">
<div class="footer-item">
<span class="label">平均评分</span>
<span class="value">
<Icon name="star" size="sm" color="#31f08b" style="margin-right: 4px; vertical-align: middle;" />
{{ designer.avgRating?.toFixed(1) || '0.0' }}
</span>
</div>
<div class="footer-item">
<span class="label">总任务</span>
<span class="value">{{ designer.totalTaskCount }}</span>
</div>
</div>
<!-- 进行中的任务列表 -->
<div v-if="designer.inProgressCount > 0" class="in-progress-tasks">
<div class="tasks-header">
<span class="tasks-title">
<Icon name="clipboard" size="sm" color="#31f08b" style="margin-right: 4px; vertical-align: middle;" />
进行中任务 ({{ designer.inProgressTasks?.length || 0 }})
</span>
<a-button
v-if="!designer.showTasks"
type="link"
size="small"
@click="loadDesignerTasks(designer)"
>
展开
</a-button>
<a-button
v-else
type="link"
size="small"
@click="designer.showTasks = false"
>
收起
</a-button>
</div>
<div v-if="designer.showTasks" class="tasks-list">
<div v-if="designer.loadingTasks" class="tasks-loading">
<a-spin size="small" />
</div>
<div v-else-if="designer.inProgressTasks && designer.inProgressTasks.length > 0">
<div
v-for="task in designer.inProgressTasks"
:key="task.id"
class="task-item-expanded"
>
<div class="task-header">
<span class="task-title-text">{{ task.spuInfo || task.title || '未知任务' }}</span>
<a-tag :color="task.isSubTask ? 'purple' : 'blue'" size="small">
{{ task.isSubTask ? '子任务' : '主任务' }}
</a-tag>
</div>
<div class="task-no-row">
<span class="task-no-label">编号:</span>
<span class="task-no-value">{{ task.taskNo }}</span>
</div>
<div class="task-progress-bar">
<div class="progress-label">进度</div>
<a-progress
:percent="task.progress || 0"
:stroke-color="getProgressColor(task.progress || 0)"
:trail-color="'rgba(255,255,255,0.1)'"
size="small"
style="flex: 1;"
/>
<span class="progress-percent">{{ task.progress || 0 }}%</span>
</div>
<div class="task-time-info">
<div class="time-item">
<Icon name="clock" size="xs" color="#31f08b" />
<span class="time-label">开始:</span>
<span class="time-value">{{ formatDate(task.startTime) }}</span>
</div>
<div class="time-item">
<Icon name="clock" size="xs" color="#ff7a45" />
<span class="time-label">截止:</span>
<span class="time-value">{{ formatDate(task.deadline) }}</span>
</div>
</div>
</div>
</div>
<div v-else class="tasks-empty">
暂无进行中的任务
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</DefaultLayout>
</template>
<script setup lang="ts">
import { ref, computed, onMounted } from 'vue'
import { message } from 'ant-design-vue'
import DefaultLayout from '@/layouts/DefaultLayout.vue'
import Icon from '@/components/Icon.vue'
import * as designerApi from '@/api/designer'
import { fetchDepartmentOverview } from '@/api/department'
import type { DesignerStats } from '@/api/designer'
import dayjs from 'dayjs'
// 部门类型定义
interface Department {
id: number
deptName: string
deptType: string
parentId: number | null
}
// 状态
const loading = ref(false)
const designersList = ref<DesignerStats[]>([])
const leaderboardData = ref<DesignerStats[]>([])
const activeLeaderboard = ref('score')
const selectedMonth = ref('') // 选中的月份
const leaderboardExpanded = ref(false) // 排行榜默认折叠
const selectedDeptId = ref<number | undefined>(undefined) // 选中的部门ID
const receivingDepts = ref<Department[]>([]) // 接单部门列表(二级部门)
// 排行榜Tab配置
const leaderboardTabs = [
{ key: 'score', label: '积分榜', iconName: 'star' },
{ key: 'quality', label: '好评榜', iconName: 'star' },
{ key: 'efficiency', label: '效率榜', iconName: 'bolt' },
{ key: 'diligence', label: '勤勉榜', iconName: 'fire' }
]
// 生成可选月份列表(当前月及往前11个月,共12个月)
const availableMonths = computed(() => {
const months = []
const now = dayjs()
for (let i = 0; i < 12; i++) {
const month = now.subtract(i, 'month')
months.push({
value: month.format('YYYY-MM'),
label: i === 0 ? '本月' : month.format('YYYY年MM月')
})
}
return months
})
// 格式化日期
const formatDate = (date: string | null | undefined) => {
if (!date) return '-'
return dayjs(date).format('MM/DD HH:mm')
}
// 格式化积分(保留两位小数)
const formatScore = (score: number | null | undefined): string => {
if (score === null || score === undefined) return '0.00'
return Number(score).toFixed(2)
}
// 获取进度条颜色
const getProgressColor = (progress: number) => {
if (progress >= 80) return '#31f08b' // 绿色
if (progress >= 50) return '#faad14' // 黄色
if (progress >= 20) return '#ff7a45' // 橙色
return '#ff4d4f' // 红色
}
// 总览数据
const totalCount = computed(() => designersList.value.length)
const onlineCount = computed(() =>
designersList.value.filter(d => d.onlineStatus === 1).length
)
const totalInProgress = computed(() =>
designersList.value.reduce((sum, d) => sum + (d.inProgressCount || 0), 0)
)
const totalQueue = computed(() =>
designersList.value.reduce((sum, d) => sum + (d.queueCount || 0), 0)
)
const avgRating = computed(() => {
const ratings = designersList.value.map(d => d.avgRating || 0)
if (ratings.length === 0) return '0.0'
const sum = ratings.reduce((a, b) => a + b, 0)
return (sum / ratings.length).toFixed(1)
})
// 排名已改为数字显示,不再使用 emoji 徽章
// 获取排行榜数值
const getLeaderboardValue = (designer: DesignerStats) => {
switch (activeLeaderboard.value) {
case 'score':
return designer.totalScore || 0
case 'quality':
return designer.goodReviewCount || 0
case 'efficiency':
return designer.completionRate?.toFixed(0) + '%' || '0%'
case 'diligence':
return designer.totalTaskCount || 0
default:
return 0
}
}
// 获取排行榜标签
const getLeaderboardLabel = () => {
const labels: Record<string, string> = {
score: '积分',
quality: '好评',
efficiency: '完成率',
diligence: '任务数'
}
return labels[activeLeaderboard.value] || ''
}
// 加载设计师数据
const loadDesigners = async () => {
loading.value = true
try {
const response = await designerApi.getAllDesignerStats(selectedDeptId.value)
if (response.code === 200) {
designersList.value = response.data || []
}
} catch (error) {
console.error('加载设计师数据失败:', error)
message.error('加载设计师数据失败')
} finally {
loading.value = false
}
}
// 加载接单部门列表(二级部门)
const loadReceivingDepts = async () => {
try {
const response = await fetchDepartmentOverview()
if (response.code === 200 && response.data) {
// 只获取接单类型的二级部门(parentId 不为空且 deptType 为 receiving)
receivingDepts.value = response.data.filter((dept: Department) =>
dept.deptType === 'receiving' && dept.parentId != null
)
console.log('接单部门列表:', receivingDepts.value)
}
} catch (error) {
console.error('加载部门列表失败:', error)
}
}
// 部门筛选变更
const handleDeptChange = () => {
loadDesigners()
}
// 加载排行榜数据
const loadLeaderboard = async (type: string, month?: string) => {
try {
const monthParam = month || selectedMonth.value
const response = await designerApi.getLeaderboard(type, 10, monthParam)
if (response.code === 200) {
leaderboardData.value = response.data || []
}
} catch (error) {
console.error('加载排行榜失败:', error)
}
}
// 切换排行榜
const switchLeaderboard = (type: string) => {
activeLeaderboard.value = type
loadLeaderboard(type)
}
// 月份变更
const handleMonthChange = () => {
loadLeaderboard(activeLeaderboard.value)
}
// 加载设计师进行中的任务
const loadDesignerTasks = async (designer: DesignerStats) => {
if (designer.loadingTasks) return
designer.loadingTasks = true
try {
const response = await designerApi.getDesignerInProgressTasks(designer.userId)
if (response.code === 200) {
designer.inProgressTasks = response.data || []
designer.showTasks = true
}
} catch (error) {
console.error('加载设计师任务失败:', error)
message.error('加载任务列表失败')
} finally {
designer.loadingTasks = false
}
}
// 组件挂载
onMounted(() => {
// 设置默认选中当前月份
selectedMonth.value = dayjs().format('YYYY-MM')
loadReceivingDepts() // 加载部门列表
loadDesigners()
loadLeaderboard('score')
})
</script>
<style lang="scss" scoped>
.designer-hall-page {
min-height: 100vh;
padding: $space-xl 0;
background: $bg-primary;
}
.container {
max-width: $container-max-width;
margin: 0 auto;
padding: 0 $space-xl;
@media (max-width: $breakpoint-tablet) {
padding: 0 $space-lg;
}
@media (max-width: $breakpoint-mobile) {
padding: 0 $space-md;
}
}
// 区块标题
.section-title {
font-size: $font-size-xl;
font-weight: $font-weight-semibold;
color: $text-primary;
margin-bottom: $space-lg;
}
// 数据总览区域
.overview-cards {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
gap: $space-lg;
margin-bottom: $space-2xl;
}
.overview-section {
margin-bottom: $space-2xl;
}
// 统计卡片 - Cursor 风格
.stat-card {
background: $bg-card;
border: 1px solid $border-primary;
border-radius: $radius-lg;
padding: $space-lg;
display: flex;
gap: $space-lg;
transition: all $transition-base $ease-out;
&:hover {
border-color: $border-color;
background: $bg-card-hover;
box-shadow: $shadow-base;
}
.stat-icon {
width: 60px;
height: 60px;
border-radius: $radius-md;
display: flex;
align-items: center;
justify-content: center;
font-size: 28px;
color: #31f08b;
background: transparent;
flex-shrink: 0;
}
.stat-content {
flex: 1;
.stat-label {
font-size: $font-size-sm;
color: $text-secondary;
margin-bottom: $space-xs;
}
.stat-value {
font-size: $font-size-2xl;
font-weight: $font-weight-bold;
color: $text-primary;
}
}
}
// 排行榜区域
.leaderboard-section {
margin-bottom: $space-2xl;
}
// 图表卡片 - Cursor 风格
.chart-card {
background: $bg-card;
border: 1px solid $border-primary;
border-radius: $radius-lg;
padding: $space-xl;
transition: all $transition-base $ease-out;
&:hover {
border-color: $border-color;
background: $bg-card-hover;
}
}
// 表格卡片 - Cursor 风格
.table-card {
background: transparent; // 移除背景色,和其他模块一致
border: none; // 移除边框
border-radius: 0; // 移除圆角
overflow: visible; // 允许内容溢出
transition: none; // 移除过渡效果
&:hover {
border-color: transparent;
}
}
// 排行榜
.leaderboard-tabs {
overflow: hidden;
}
.tabs-header-row {
display: flex;
align-items: center;
justify-content: space-between;
border-bottom: 1px solid $border-primary;
}
.tabs-header {
display: flex;
flex: 1;
}
.month-selector {
padding: $space-sm $space-md;
border-left: 1px solid $border-primary;
}
.tab-button {
flex: 1;
padding: $space-md;
background: transparent;
border: none;
color: $text-secondary;
font-size: $font-size-base;
font-weight: $font-weight-medium;
cursor: pointer;
transition: all $transition-base $ease-out;
border-bottom: 2px solid transparent;
&:hover {
color: $text-primary;
background: rgba($brand-primary, 0.05);
}
&.active {
color: $brand-primary;
border-bottom-color: $brand-primary;
background: rgba($brand-primary, 0.05);
}
}
.leaderboard-content {
padding: $space-lg;
}
.leaderboard-item {
display: flex;
align-items: center;
padding: $space-md;
margin-bottom: $space-sm;
border-radius: $radius-small;
transition: all $transition-base $ease-out;
&:hover {
background: darken($bg-card, 3%);
}
.rank {
width: 40px;
text-align: center;
.rank-medal {
font-size: 24px;
}
.rank-number {
font-size: $font-size-lg;
font-weight: $font-weight-semibold;
color: $text-secondary;
}
}
.designer-info {
display: flex;
align-items: center;
flex: 1;
margin: 0 $space-md;
.designer-avatar {
width: 40px;
height: 40px;
border-radius: 50%;
background: linear-gradient(135deg, $brand-primary 0%, lighten($brand-primary, 15%) 100%);
display: flex;
align-items: center;
justify-content: center;
color: #06301d;
font-weight: $font-weight-bold;
margin-right: $space-md;
}
.designer-details {
.designer-name {
font-weight: $font-weight-medium;
color: $text-primary;
margin-bottom: 2px;
}
.designer-dept {
font-size: $font-size-sm;
color: $text-tertiary;
}
}
}
.stat-value {
display: flex;
flex-direction: column;
align-items: flex-end;
.value {
font-size: $font-size-xl;
font-weight: $font-weight-bold;
color: $brand-primary;
}
.label {
font-size: $font-size-xs;
color: $text-tertiary;
margin-top: 2px;
}
}
}
// 设计师列表
.designers-section {
margin-top: $space-xxxl;
.section-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: $space-lg;
.section-title {
margin-bottom: 0;
}
.filter-area {
display: flex;
gap: $space-md;
align-items: center;
}
}
}
.section-title {
font-size: $font-size-xl;
font-weight: $font-weight-semibold;
color: $text-primary;
margin-bottom: $space-xl;
}
.loading-container {
display: flex;
justify-content: center;
padding: $space-xxxl 0;
}
.designers-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
gap: $space-lg;
}
// 设计师卡片
.designer-card {
padding: $space-lg;
transition: all $transition-base $ease-out;
&:hover {
transform: translateY(-4px);
box-shadow: $shadow-elevated;
}
}
.designer-header {
display: flex;
align-items: center;
margin-bottom: $space-md;
padding-bottom: $space-md;
border-bottom: 1px solid $border-primary;
}
.avatar-wrapper {
position: relative;
margin-right: $space-md;
.avatar {
width: 56px;
height: 56px;
border-radius: 50%;
background: linear-gradient(135deg, $brand-primary 0%, lighten($brand-primary, 15%) 100%);
display: flex;
align-items: center;
justify-content: center;
color: #06301d;
font-size: $font-size-xl;
font-weight: $font-weight-bold;
}
.status-indicator {
position: absolute;
bottom: 2px;
right: 2px;
width: 12px;
height: 12px;
border-radius: 50%;
border: 2px solid $bg-card;
&.online {
background: $color-success;
}
&.offline {
background: $text-tertiary;
}
}
}
.designer-basic {
flex: 1;
.name {
font-size: $font-size-lg;
font-weight: $font-weight-semibold;
color: $text-primary;
margin: 0 0 4px 0;
}
.dept {
font-size: $font-size-sm;
color: $text-secondary;
margin: 0;
}
}
.designer-stats {
display: flex;
justify-content: space-between;
margin-bottom: $space-md;
.stat-item {
text-align: center;
.stat-label {
font-size: $font-size-xs;
color: $text-tertiary;
margin-bottom: 4px;
}
.stat-value {
font-size: $font-size-xl;
font-weight: $font-weight-bold;
color: $text-primary;
&.text-warning {
color: $color-warning;
}
&.text-brand {
color: $brand-primary;
}
}
}
}
.designer-metrics {
margin-bottom: $space-md;
}
.metric-row {
display: flex;
align-items: center;
gap: $space-sm;
margin-bottom: $space-sm;
.metric-label {
width: 60px;
font-size: $font-size-sm;
color: $text-secondary;
}
.metric-bar {
flex: 1;
height: 8px;
background: $border-primary;
border-radius: $radius-small;
overflow: hidden;
.metric-fill {
height: 100%;
background: $brand-primary;
transition: width $transition-base $ease-out;
&.success {
background: $color-success;
}
}
}
.metric-value {
width: 50px;
text-align: right;
font-size: $font-size-sm;
font-weight: $font-weight-medium;
color: $text-primary;
}
}
.designer-footer {
display: flex;
justify-content: space-between;
padding-top: $space-md;
border-top: 1px solid $border-primary;
}
.footer-item {
display: flex;
flex-direction: column;
}
// 进行中任务列表
.in-progress-tasks {
margin-top: $space-md;
padding-top: $space-md;
border-top: 1px solid $border-primary;
.tasks-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: $space-sm;
.tasks-title {
font-size: $font-size-sm;
font-weight: $font-weight-medium;
color: $text-primary;
display: flex;
align-items: center;
}
}
.tasks-list {
margin-top: $space-sm;
}
.tasks-loading {
text-align: center;
padding: $space-md;
}
.tasks-empty {
text-align: center;
padding: $space-md;
color: $text-tertiary;
font-size: $font-size-sm;
}
// 展开的任务项样式
.task-item-expanded {
padding: 12px;
margin-bottom: 8px;
background: linear-gradient(135deg, rgba(49, 240, 139, 0.08) 0%, rgba(26, 26, 26, 0.95) 100%);
border-radius: 8px;
border: 1px solid rgba(49, 240, 139, 0.2);
&:last-child {
margin-bottom: 0;
}
.task-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 8px;
.task-title-text {
font-size: 13px;
font-weight: 500;
color: #fff;
flex: 1;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
margin-right: 8px;
}
}
.task-no-row {
display: flex;
align-items: center;
margin-bottom: 8px;
font-size: 11px;
.task-no-label {
color: rgba(255, 255, 255, 0.5);
margin-right: 4px;
}
.task-no-value {
color: rgba(255, 255, 255, 0.8);
font-family: monospace;
}
}
.task-progress-bar {
display: flex;
align-items: center;
gap: 8px;
margin-bottom: 8px;
.progress-label {
font-size: 11px;
color: rgba(255, 255, 255, 0.5);
min-width: 28px;
}
:deep(.ant-progress) {
margin: 0;
.ant-progress-inner {
background: rgba(255, 255, 255, 0.1);
}
}
.progress-percent {
font-size: 12px;
font-weight: 500;
color: #31f08b;
min-width: 35px;
text-align: right;
}
}
.task-time-info {
display: flex;
justify-content: space-between;
gap: 16px;
.time-item {
display: flex;
align-items: center;
gap: 4px;
font-size: 11px;
.time-label {
color: rgba(255, 255, 255, 0.5);
}
.time-value {
color: rgba(255, 255, 255, 0.9);
}
}
}
}
.task-item {
padding: $space-sm;
margin-bottom: $space-sm;
background: $bg-card-hover;
border-radius: $radius-md;
border: 1px solid $border-primary;
&:last-child {
margin-bottom: 0;
}
.task-info-row {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: $space-xs;
.task-title {
font-size: $font-size-sm;
font-weight: $font-weight-medium;
color: $text-primary;
flex: 1;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.task-no {
font-size: $font-size-xs;
color: $text-tertiary;
margin-left: $space-sm;
}
}
.task-progress-row {
display: flex;
align-items: center;
gap: $space-sm;
margin-bottom: $space-xs;
:deep(.ant-progress) {
flex: 1;
margin: 0;
}
.progress-text {
font-size: $font-size-xs;
color: $text-secondary;
min-width: 35px;
text-align: right;
}
}
.task-time-row {
display: flex;
justify-content: space-between;
font-size: $font-size-xs;
color: $text-tertiary;
.task-time,
.task-deadline {
display: flex;
align-items: center;
gap: 2px;
}
}
}
gap: 4px;
.label {
font-size: $font-size-xs;
color: $text-tertiary;
}
.value {
font-size: $font-size-base;
font-weight: $font-weight-medium;
color: $text-primary;
}
}
</style>