diff --git a/pages/task/list/index.vue b/pages/task/list/index.vue index 35caaf1..5fc10ff 100644 --- a/pages/task/list/index.vue +++ b/pages/task/list/index.vue @@ -467,171 +467,6 @@ onLoad((options) => { height: 100%; } -.task-container { - padding: 16px; - display: flex; - flex-direction: column; - gap: 12px; -} - -.task-card { - background: #fff; - border-radius: 12px; - padding: 16px; - display: flex; - flex-direction: column; - position: relative; - cursor: pointer; - transition: all 0.3s ease; - box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08); -} - -.task-card:active { - transform: scale(0.98); - opacity: 0.9; -} - -// 即将逾期卡片样式 -.task-card-imminent { - border-left: 4px solid #ff9800; -} - -// 待完成卡片样式 -.task-card-pending { - border-left: 4px solid #2885ff; -} - -// 已完成卡片样式 -.task-card-completed { - border-left: 4px solid #909399; - opacity: 0.85; -} - -// 逾期卡片样式 -.task-card-overdue { - background: linear-gradient(135deg, #fff5f5 0%, #ffe6e6 100%); - border-left: 4px solid #f56c6c; - box-shadow: 0 2px 12px rgba(255, 68, 68, 0.1); -} - -.task-header { - display: flex; - align-items: center; - gap: 12px; - margin-bottom: 12px; - justify-content: space-between; -} - -.task-badge-wrapper { - flex-shrink: 0; -} - -.task-date-wrapper { - background: rgba(0, 0, 0, 0.04); - border-radius: 4px; - padding: 4px 8px; -} - -.task-date { - font-size: 14px; - color: #333; - font-weight: 500; -} - -.task-content { - flex: 1; - display: flex; - flex-direction: column; - gap: 8px; -} - -.task-project { - font-size: 12px; - color: #666; - line-height: 1.5; -} - -.task-description { - font-size: 14px; - color: #333; - line-height: 1.5; - margin-bottom: 4px; -} - -.task-meta { - display: flex; - flex-direction: column; - gap: 4px; -} - -.task-owner { - font-size: 12px; - color: #666; -} - -.task-time-row { - display: flex; - align-items: center; - justify-content: space-between; - gap: 8px; -} - -.task-time { - font-size: 12px; - color: #666; -} - -.task-countdown { - display: flex; - align-items: center; - gap: 4px; -} - -.countdown-icon { - font-size: 14px; -} - -.countdown-text { - font-size: 12px; - font-weight: 500; -} - -.countdown-warning { - color: #ff9800; -} - -.countdown-primary { - color: #2885ff; -} - -.countdown-error { - color: #f56c6c; -} - -.task-action { - - display: flex; - justify-content: flex-end; -} - -.empty-state { - padding: 60px 20px; - text-align: center; -} - -.empty-text { - font-size: 14px; - color: #999; -} - -.load-more-tip { - padding: 20px; - text-align: center; -} - -.load-more-text { - font-size: 12px; - color: #999; -} +@import '@/styles/task-card.scss'; diff --git a/pages/task/manage/index.vue b/pages/task/manage/index.vue index 1794a0a..4313c8d 100644 --- a/pages/task/manage/index.vue +++ b/pages/task/manage/index.vue @@ -6,7 +6,24 @@ - + + + + 待完成 + + + 全部 + + + 筛选 @@ -108,70 +125,41 @@ + + + 排序方式 + + + 发布时间{{ sortAsc ? '↑' : '↓' }} + + + 到期时间{{ sortAsc ? '↑' : '↓' }} + + + 通过时间{{ sortAsc ? '↑' : '↓' }} + + + + + 重置 确定 - - - - - 待完成 - - - 已完成 - - - 已取消 - - - 全部任务 - - - - - - 发布时间{{ sortAsc ? '↑' : '↓' }} - - - 到期时间{{ sortAsc ? '↑' : '↓' }} - - - 通过时间{{ sortAsc ? '↑' : '↓' }} - - - - + - - - - - - {{ task.projectName || '未分配项目' }} - {{ formatDate(task.expireTime) }} + + + + + + + {{ formatDate(task.expireTime) }} + + + 所属项目: {{ task.projectName || '未分配项目' }} {{ truncateText(task.description, 80) }} - - - - - 创建人: - {{ task.createName || '未知' }} - 负责人: - {{ getOwnerNames(task.memberList) || '未分配' }} - - - 发布时间: {{ formatDate(task.createTime) }} + + 负责人: {{ getOwnerNames(task.memberList) || '未分配' }} + 创建人: {{ task.createName || '未知' }} + + 发布时间: {{ formatDate(task.createTime) }} + + 🕐 + + {{ task.remainingDays < 0 ? `已逾期${Math.abs(task.remainingDays)}天` : `剩余${task.remainingDays}天` }} + + + 通过时间: {{ formatDate(task.passTime) }} @@ -455,8 +448,67 @@ const { defaultParams: {} }); -// 任务列表 -const tasks = computed(() => list.value); +// 计算剩余天数 +const calculateRemainingDays = (expireTime) => { + if (!expireTime) return null; + const expireDate = new Date(expireTime); + const now = new Date(); + now.setHours(0, 0, 0, 0); + expireDate.setHours(0, 0, 0, 0); + const diffTime = expireDate.getTime() - now.getTime(); + const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24)); + return diffDays; +}; + +// 根据过期时间和状态判断任务状态(用于卡片样式) +const determineTaskStatusForCard = (task) => { + // 如果任务已完成(状态为4),直接返回 completed + const taskStatus = task.status; + if (taskStatus === 4 || taskStatus === '4') { + return 'completed'; + } + + // 如果没有过期时间,返回 pending + const expireTime = task.expireTime; + if (!expireTime) { + return 'pending'; + } + + const expireDate = new Date(expireTime); + const now = new Date(); + + // 如果已过期,标记为逾期 + if (expireDate.getTime() < now.getTime()) { + return 'overdue'; + } + + // 计算距离过期的天数 + const diffTime = expireDate.getTime() - now.getTime(); + const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24)); + + // 如果3天内到期,标记为即将逾期 + if (diffDays <= 3 && diffDays >= 0) { + return 'imminent'; + } + + // 否则返回待完成状态 + return 'pending'; +}; + +// 任务列表(添加状态和剩余天数) +const tasks = computed(() => { + return list.value.map(task => { + const expireTime = task.expireTime || ''; + const remainingDays = calculateRemainingDays(expireTime); + const cardStatus = determineTaskStatusForCard(task); + + return { + ...task, + remainingDays, + cardStatus + }; + }); +}); // 获取状态文本(根据数字状态) const getStatusText = (status) => { @@ -538,6 +590,25 @@ const getTagCustomStyle = (status) => { return colorMap[statusItem.listClass] || defaultStyle; }; +// 获取卡片样式类 +const getTaskCardClass = (task) => { + return { + 'task-card-imminent': task.cardStatus === 'imminent', + 'task-card-pending': task.cardStatus === 'pending', + 'task-card-completed': task.cardStatus === 'completed', + 'task-card-overdue': task.cardStatus === 'overdue' + }; +}; + +// 获取倒计时样式类 +const getCountdownClass = (cardStatus) => { + return { + 'countdown-warning': cardStatus === 'imminent', + 'countdown-primary': cardStatus === 'pending', + 'countdown-error': cardStatus === 'overdue' + }; +}; + // 格式化日期 const formatDate = (dateStr) => { if (!dateStr) return ''; @@ -809,7 +880,7 @@ onMounted(() => { display: flex; align-items: center; gap: 16px; - padding: 5px 24px; + padding: 8px 24px; background-color: #fff; border-bottom: 1px solid #e4e7ed; position: fixed; @@ -817,6 +888,15 @@ onMounted(() => { right: 0; left: 0; z-index: 100; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04); + min-height: 48px; +} + +.header-center { + flex: 1; + display: flex; + justify-content: center; + align-items: center; } .filter-btn { @@ -842,7 +922,7 @@ onMounted(() => { padding: 16px; border-bottom: 1px solid #e4e7ed; position: fixed; - top: 41px; + top: 56px; right: 0; left: 0; z-index: 99; @@ -947,28 +1027,10 @@ onMounted(() => { margin-right: 4px; } -.status-sort-section { - background: #fff; - padding: 12px 16px; - margin-bottom: 8px; - position: fixed; - top: 41px; - right: 0; - left: 0; - z-index: 98; - transition: top 0.3s ease; - - &.with-filter { - top: auto; - position: relative; - margin-top: 0; - } -} - .task-scroll { flex: 1; width: 100%; - padding-top: 100px; /* header(41px) + status-sort-section(约60px) */ + padding-top: 56px; /* header高度 */ transition: padding-top 0.3s ease; &.with-filter { @@ -979,20 +1041,25 @@ onMounted(() => { .status-tabs { display: flex; gap: 8px; - margin-bottom: 12px; - flex-wrap: wrap; + flex-wrap: nowrap; } .status-tab { display: flex; align-items: center; gap: 4px; - padding: 6px 12px; + padding: 4px 12px; background: #f5f5f5; border-radius: 16px; - font-size: 14px; + font-size: 13px; color: #666; transition: all 0.2s; + white-space: nowrap; + cursor: pointer; + + &:active { + opacity: 0.7; + } &.active { background: #2885ff; @@ -1005,23 +1072,29 @@ onMounted(() => { } } -.sort-options { +.sort-options-filter { display: flex; - gap: 16px; - align-items: center; + gap: 12px; + margin-top: 8px; } -.sort-option { +.sort-option-filter { + flex: 1; display: flex; align-items: center; + justify-content: center; gap: 4px; + background: #f5f5f5; + border-radius: 8px; + padding: 8px; + border: 2px solid transparent; font-size: 14px; color: #666; - padding: 4px 8px; - border-radius: 4px; transition: all 0.2s; &.active { + background: #e3f2fd; + border-color: #2885ff; color: #2885ff; font-weight: 500; } @@ -1032,121 +1105,5 @@ onMounted(() => { } -.task-container { - padding: 16px; - display: flex; - flex-direction: column; - gap: 12px; -} - -.task-card { - background: #fff; - border-radius: 12px; - padding: 16px; - display: flex; - flex-direction: column; - gap: 12px; - box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08); - transition: all 0.3s ease; -} - -.task-card:active { - transform: scale(0.98); - opacity: 0.9; -} - -.task-header { - display: flex; - align-items: center; - justify-content: space-between; - gap: 12px; -} - -.task-badge-wrapper { - flex-shrink: 0; -} - -.task-meta { - flex: 1; - display: flex; - flex-direction: column; - gap: 4px; - align-items: flex-end; -} - -.task-project { - font-size: 14px; - color: #666; -} - -.task-date { - font-size: 12px; - color: #999; -} - -.task-content { - margin-top: 4px; -} - -.task-description { - font-size: 14px; - color: #333; - line-height: 1.6; -} - -.task-footer { - display: flex; - flex-direction: column; - gap: 8px; - padding-top: 8px; - border-top: 1px solid #f0f0f0; -} - -.task-users { - display: flex; - align-items: center; - flex-wrap: wrap; - gap: 4px; -} - -.task-user-label { - font-size: 12px; - color: #999; -} - -.task-user-name { - font-size: 12px; - color: #666; -} - -.task-times { - display: flex; - flex-direction: column; - gap: 4px; -} - -.task-time { - font-size: 12px; - color: #999; -} - -.empty-state { - padding: 60px 20px; - text-align: center; -} - -.empty-text { - font-size: 14px; - color: #999; -} - -.load-more-tip { - padding: 20px; - text-align: center; -} - -.load-more-text { - font-size: 12px; - color: #999; -} +@import '@/styles/task-card.scss'; diff --git a/styles/task-card.scss b/styles/task-card.scss new file mode 100644 index 0000000..70124ba --- /dev/null +++ b/styles/task-card.scss @@ -0,0 +1,173 @@ +// 任务卡片公共样式 +.task-card { + background: #fff; + border-radius: 12px; + padding: 16px; + display: flex; + flex-direction: column; + position: relative; + cursor: pointer; + transition: all 0.3s ease; + box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08); + + &:active { + transform: scale(0.98); + opacity: 0.9; + } + + // 即将逾期卡片样式 + &.task-card-imminent { + border-left: 4px solid #ff9800; + } + + // 待完成卡片样式 + &.task-card-pending { + border-left: 4px solid #2885ff; + } + + // 已完成卡片样式 + &.task-card-completed { + border-left: 4px solid #909399; + opacity: 0.85; + } + + // 逾期卡片样式 + &.task-card-overdue { + background: linear-gradient(135deg, #fff5f5 0%, #ffe6e6 100%); + border-left: 4px solid #f56c6c; + box-shadow: 0 2px 12px rgba(255, 68, 68, 0.1); + } +} + +.task-header { + display: flex; + align-items: center; + gap: 12px; + margin-bottom: 12px; + justify-content: space-between; +} + +.task-badge-wrapper { + flex-shrink: 0; + display: flex; + align-items: center; + gap: 8px; +} + +.task-date-wrapper { + background: rgba(0, 0, 0, 0.04); + border-radius: 4px; + padding: 4px 8px; +} + +.task-date { + font-size: 14px; + color: #333; + font-weight: 500; +} + +.task-content { + flex: 1; + display: flex; + flex-direction: column; + gap: 8px; +} + +.task-project { + font-size: 12px; + color: #666; + line-height: 1.5; +} + +.task-description { + font-size: 14px; + color: #333; + line-height: 1.5; + margin-bottom: 4px; +} + +.task-meta { + display: flex; + flex-direction: column; + gap: 4px; +} + +.task-owner { + font-size: 12px; + color: #666; +} + +.task-time-row { + display: flex; + align-items: center; + justify-content: space-between; + gap: 8px; +} + +.task-time { + font-size: 12px; + color: #666; +} + +.task-countdown { + display: flex; + align-items: center; + gap: 4px; +} + +.countdown-icon { + font-size: 14px; +} + +.countdown-text { + font-size: 12px; + font-weight: 500; + + &.countdown-warning { + color: #ff9800; + } + + &.countdown-primary { + color: #2885ff; + } + + &.countdown-error { + color: #f56c6c; + } +} + +.task-action { + display: flex; + justify-content: flex-end; +} + +// 任务容器样式 +.task-container { + padding: 16px; + display: flex; + flex-direction: column; + gap: 12px; +} + +// 空状态样式 +.empty-state { + padding: 60px 20px; + text-align: center; +} + +.empty-text { + font-size: 14px; + color: #999; +} + +// 加载更多提示样式 +.load-more-tip { + padding: 20px; + text-align: center; +} + +.load-more-text { + font-size: 12px; + color: #999; +} +