17 changed files with 1551 additions and 24 deletions
@ -0,0 +1,392 @@ |
|||
<template> |
|||
<div class="detail-page"> |
|||
<!-- 顶部导航 --> |
|||
<div class="nav-bar"> |
|||
<div class="nav-back" @click="onBack"> |
|||
<van-icon name="arrow-left" size="20" color="#333" /> |
|||
</div> |
|||
<div class="nav-title">任务详情</div> |
|||
<div class="nav-right"></div> |
|||
</div> |
|||
|
|||
<!-- 加载中 --> |
|||
<div v-if="loading" class="detail-loading"> |
|||
<van-loading size="24" color="#2382cf" /> |
|||
</div> |
|||
|
|||
<!-- 详情内容 --> |
|||
<div v-else-if="detail" class="detail-content"> |
|||
<!-- 顶部标题区 --> |
|||
<div class="header-card"> |
|||
<div class="header-top"> |
|||
<span class="plan-type-tag">【{{ planTypeText }}】</span> |
|||
<span v-if="detail.isUnion === 1" class="union-badge">联合执法</span> |
|||
</div> |
|||
<span class="header-title">{{ detail.enforceContent }}</span> |
|||
</div> |
|||
|
|||
<!-- 基本信息卡片 --> |
|||
<div class="detail-card"> |
|||
<div class="card-header"> |
|||
<div class="header-icon-wrap green"> |
|||
<van-icon name="info-o" size="15" color="#fff" /> |
|||
</div> |
|||
<span>执法计划</span> |
|||
</div> |
|||
<div class="info-list"> |
|||
<div class="info-item"> |
|||
<span class="info-label">计划日期</span> |
|||
<span class="info-value">{{ formatDate(detail.planDate) }}</span> |
|||
</div> |
|||
<div class="info-item"> |
|||
<span class="info-label">执行时段</span> |
|||
<span class="info-value">{{ timeRangeText }}</span> |
|||
</div> |
|||
<div v-if="detail.planActualDate && (formatDate(detail.planActualDate) !== formatDate(detail.planDate) || detail.planActualTime !== detail.planTime)" class="info-item highlight"> |
|||
<span class="info-label">实际日期</span> |
|||
<span class="info-value">{{ formatDate(detail.planActualDate) }} {{ actualTimeRangeText }}</span> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
|
|||
<!-- 企业信息卡片 --> |
|||
<div class="detail-card"> |
|||
<div class="card-header"> |
|||
<div class="header-icon-wrap orange"> |
|||
<van-icon name="wap-home-o" size="15" color="#fff" /> |
|||
</div> |
|||
<span>检查对象</span> |
|||
</div> |
|||
<div class="info-list"> |
|||
<div class="info-item"> |
|||
<span class="info-label">企业名称</span> |
|||
<span class="info-value bold">{{ detail.enterpriseName || '-' }}</span> |
|||
</div> |
|||
<div v-if="detail.enterpriseNumber" class="info-item"> |
|||
<span class="info-label">信用代码</span> |
|||
<span class="info-value bold">{{ detail.enterpriseNumber }}</span> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
|
|||
<!-- 执法人员卡片 --> |
|||
<div class="detail-card"> |
|||
<div class="card-header"> |
|||
<div class="header-icon-wrap purple"> |
|||
<van-icon name="friends-o" size="15" color="#fff" /> |
|||
</div> |
|||
<span>执法人员</span> |
|||
</div> |
|||
<div class="info-list"> |
|||
<div class="info-item"> |
|||
<span class="info-label">执法单位</span> |
|||
<span class="info-value">{{ detail.bureauDeptName || '-' }}</span> |
|||
</div> |
|||
<div class="info-item"> |
|||
<span class="info-label">执法人员</span> |
|||
<span class="info-value">{{ detail.enforceUserNames || '-' }}</span> |
|||
</div> |
|||
<div v-if="detail.isUnion === 1 && detail.unionBureauDeptNames" class="info-item"> |
|||
<span class="info-label">联合单位</span> |
|||
<span class="info-value">{{ detail.unionBureauDeptNames }}</span> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
|
|||
<!-- 备注卡片 --> |
|||
<div v-if="detail.remark" class="detail-card"> |
|||
<div class="card-header"> |
|||
<div class="header-icon-wrap gray"> |
|||
<van-icon name="edit" size="15" color="#fff" /> |
|||
</div> |
|||
<span>备注</span> |
|||
</div> |
|||
<div class="content-block"> |
|||
{{ detail.remark }} |
|||
</div> |
|||
</div> |
|||
</div> |
|||
|
|||
<!-- 空状态 --> |
|||
<div v-else class="detail-empty"> |
|||
<van-empty description="未找到任务信息" /> |
|||
</div> |
|||
</div> |
|||
</template> |
|||
|
|||
<script setup name="cpTaskDetail"> |
|||
import { useRouter, useRoute } from 'vue-router'; |
|||
import { getMyTaskDetail } from '@/api/enforcement/task'; |
|||
|
|||
const router = useRouter(); |
|||
const route = useRoute(); |
|||
|
|||
const loading = ref(true); |
|||
const detail = ref(null); |
|||
|
|||
// 返回 |
|||
const onBack = () => { |
|||
router.back(); |
|||
}; |
|||
|
|||
// 格式化日期 |
|||
const formatDate = (d) => { |
|||
if (!d) return '-'; |
|||
if (typeof d === 'string') return d.substring(0, 10); |
|||
const date = new Date(d); |
|||
const y = date.getFullYear(); |
|||
const m = String(date.getMonth() + 1).padStart(2, '0'); |
|||
const day = String(date.getDate()).padStart(2, '0'); |
|||
return `${y}-${m}-${day}`; |
|||
}; |
|||
|
|||
// 时段文字 |
|||
const getTimeText = (time) => { |
|||
if (time === 1) return '上午 (09:00-12:00)'; |
|||
if (time === 2) return '下午 (14:00-18:00)'; |
|||
return '其他'; |
|||
}; |
|||
|
|||
const timeRangeText = computed(() => getTimeText(detail.value?.planTime)); |
|||
const actualTimeRangeText = computed(() => getTimeText(detail.value?.planActualTime)); |
|||
|
|||
// 计划类型 |
|||
const planTypeText = computed(() => { |
|||
const t = detail.value?.planType; |
|||
if (t === 1) return '日常计划'; |
|||
if (t === 2) return '专项计划'; |
|||
return '其他'; |
|||
}); |
|||
|
|||
|
|||
const statusClass = computed(() => { |
|||
const s = detail.value?.reserveStatus; |
|||
if (s === 3) return 'status-done'; |
|||
if (s === 2) return 'status-progress'; |
|||
if (s === 1) return 'status-pending'; |
|||
return 'status-draft'; |
|||
}); |
|||
|
|||
// 加载详情 |
|||
const loadDetail = () => { |
|||
const planId = route.query.planId; |
|||
if (!planId) { |
|||
loading.value = false; |
|||
return; |
|||
} |
|||
loading.value = true; |
|||
getMyTaskDetail(planId, (res) => { |
|||
loading.value = false; |
|||
if (res.code === 200 && res.data) { |
|||
detail.value = res.data; |
|||
} |
|||
}, () => { |
|||
loading.value = false; |
|||
}); |
|||
}; |
|||
|
|||
onMounted(() => { |
|||
loadDetail(); |
|||
}); |
|||
</script> |
|||
|
|||
<style lang="less" scoped> |
|||
.detail-page { |
|||
display: flex; |
|||
flex-direction: column; |
|||
height: 100%; |
|||
background: #f4f7fc; |
|||
font-family: -apple-system, BlinkMacSystemFont, 'PingFang SC', 'Microsoft YaHei', sans-serif; |
|||
} |
|||
|
|||
/* 导航栏 */ |
|||
.nav-bar { |
|||
display: flex; |
|||
align-items: center; |
|||
justify-content: space-between; |
|||
padding: 14px 16px; |
|||
background: linear-gradient(135deg, #2382cf, #1a6db5); |
|||
flex-shrink: 0; |
|||
|
|||
.nav-back { |
|||
width: 32px; |
|||
cursor: pointer; |
|||
:deep(.van-icon) { |
|||
color: #fff !important; |
|||
} |
|||
} |
|||
.nav-title { |
|||
font-size: 17px; |
|||
font-weight: 600; |
|||
color: #fff; |
|||
} |
|||
.nav-right { |
|||
width: 32px; |
|||
} |
|||
} |
|||
|
|||
.detail-loading { |
|||
flex: 1; |
|||
display: flex; |
|||
align-items: center; |
|||
justify-content: center; |
|||
} |
|||
|
|||
.detail-empty { |
|||
flex: 1; |
|||
display: flex; |
|||
align-items: center; |
|||
justify-content: center; |
|||
} |
|||
|
|||
/* 详情内容 */ |
|||
.detail-content { |
|||
flex: 1; |
|||
overflow-y: auto; |
|||
padding: 0 14px 24px; |
|||
} |
|||
|
|||
/* 顶部标题区 */ |
|||
.header-card { |
|||
background: linear-gradient(135deg, #2382cf, #1a6db5); |
|||
border-radius: 16px 16px 16px 16px; |
|||
padding: 20px 18px 22px; |
|||
margin: 8px 0px; |
|||
color: #fff; |
|||
|
|||
.header-top { |
|||
display: flex; |
|||
align-items: center; |
|||
gap: 8px; |
|||
margin-bottom: 10px; |
|||
} |
|||
.plan-type-tag { |
|||
font-size: 14px; |
|||
padding: 6px 10px; |
|||
border-radius: 10px; |
|||
background: rgba(255,255,255,0.2); |
|||
color: #fff; |
|||
font-weight: 500; |
|||
} |
|||
.union-badge { |
|||
font-size: 11px; |
|||
padding: 2px 10px; |
|||
border-radius: 10px; |
|||
background: linear-gradient(135deg, #f5a623, #f7c948); |
|||
color: #fff; |
|||
font-weight: 600; |
|||
} |
|||
.header-title { |
|||
font-size: 18px; |
|||
font-weight: 700; |
|||
line-height: 1.4; |
|||
margin-bottom: 8px; |
|||
} |
|||
.header-meta { |
|||
display: flex; |
|||
align-items: center; |
|||
gap: 5px; |
|||
font-size: 13px; |
|||
color: rgba(255,255,255,0.85); |
|||
} |
|||
} |
|||
|
|||
/* 卡片 */ |
|||
.detail-card { |
|||
background: #fff; |
|||
border-radius: 12px; |
|||
padding: 16px; |
|||
margin-bottom: 12px; |
|||
box-shadow: 0 2px 8px rgba(35, 130, 207, 0.06); |
|||
} |
|||
|
|||
/* 卡片头部 */ |
|||
.card-header { |
|||
display: flex; |
|||
align-items: center; |
|||
gap: 8px; |
|||
font-size: 15px; |
|||
font-weight: 600; |
|||
color: #1a1a2e; |
|||
margin-bottom: 14px; |
|||
|
|||
.header-icon-wrap { |
|||
width: 26px; |
|||
height: 26px; |
|||
border-radius: 7px; |
|||
display: flex; |
|||
align-items: center; |
|||
justify-content: center; |
|||
flex-shrink: 0; |
|||
|
|||
&.blue { background: linear-gradient(135deg, #2382cf, #5ba8e6); } |
|||
&.green { background: linear-gradient(135deg, #36b37e, #57d9a3); } |
|||
&.orange { background: linear-gradient(135deg, #f5a623, #f7c948); } |
|||
&.purple { background: linear-gradient(135deg, #7b68ee, #a78bfa); } |
|||
&.gray { background: linear-gradient(135deg, #8e99a4, #b0bac5); } |
|||
} |
|||
} |
|||
|
|||
/* 信息列表 */ |
|||
.info-list { |
|||
.info-item { |
|||
display: flex; |
|||
padding: 8px 0; |
|||
border-bottom: 1px dashed #f2f3f5; |
|||
|
|||
&:last-child { |
|||
border-bottom: none; |
|||
padding-bottom: 0; |
|||
} |
|||
&:first-child { |
|||
padding-top: 0; |
|||
} |
|||
&.highlight { |
|||
background: #fef9ed; |
|||
margin: 4px -8px; |
|||
padding: 8px; |
|||
border-radius: 6px; |
|||
border-bottom: none; |
|||
} |
|||
|
|||
.info-label { |
|||
flex-shrink: 0; |
|||
width: 68px; |
|||
font-size: 13px; |
|||
color: #8c8c8c; |
|||
line-height: 1.6; |
|||
} |
|||
.info-value { |
|||
flex: 1; |
|||
font-size: 13px; |
|||
color: #333; |
|||
word-break: break-all; |
|||
line-height: 1.6; |
|||
|
|||
&.bold { |
|||
font-weight: 600; |
|||
color: #1a1a2e; |
|||
} |
|||
&.mono { |
|||
font-family: 'SF Mono', 'Menlo', monospace; |
|||
font-size: 12px; |
|||
color: #666; |
|||
letter-spacing: 0.3px; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
/* 内容块 */ |
|||
.content-block { |
|||
font-size: 13px; |
|||
color: #444; |
|||
line-height: 1.8; |
|||
word-break: break-all; |
|||
white-space: pre-wrap; |
|||
background: #f9fafb; |
|||
padding: 12px; |
|||
border-radius: 8px; |
|||
border-left: 3px solid #2382cf; |
|||
} |
|||
</style> |
|||
@ -0,0 +1,578 @@ |
|||
<template> |
|||
<div class="task-page"> |
|||
<!-- 顶部导航 --> |
|||
<div class="nav-bar"> |
|||
<div class="nav-back" @click="onBack"> |
|||
<van-icon name="arrow-left" size="20" color="#333" /> |
|||
</div> |
|||
<div class="nav-title">我的任务</div> |
|||
<div class="nav-right"></div> |
|||
</div> |
|||
|
|||
<!-- 月份切换 --> |
|||
<div class="month-switch"> |
|||
<div class="month-arrow" @click="onPrevMonth"> |
|||
<van-icon name="arrow-left" size="16" color="#2382cf" /> |
|||
</div> |
|||
<div class="month-text" @click="showMonthPicker = true"> |
|||
{{ currentYear }}年{{ currentMonth }}月 |
|||
</div> |
|||
<div class="month-arrow" @click="onNextMonth"> |
|||
<van-icon name="arrow" size="16" color="#2382cf" /> |
|||
</div> |
|||
</div> |
|||
|
|||
<!-- 日历 --> |
|||
<div class="calendar"> |
|||
<div class="week-header"> |
|||
<div class="week-cell" v-for="w in weekDays" :key="w">{{ w }}</div> |
|||
</div> |
|||
<div class="date-grid"> |
|||
<div |
|||
v-for="(cell, idx) in calendarCells" |
|||
:key="idx" |
|||
class="date-cell" |
|||
:class="{ |
|||
'other-month': !cell.isCurrentMonth, |
|||
'is-today': cell.isToday, |
|||
'is-selected': cell.isSelected, |
|||
}" |
|||
@click="onSelectDate(cell)" |
|||
> |
|||
<span class="date-num">{{ cell.day }}</span> |
|||
<span class="date-lunar">{{ cell.lunar }}</span> |
|||
<span v-if="cell.hasDot && cell.isCurrentMonth" class="date-dot"></span> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
|
|||
<!-- 任务数量 --> |
|||
<div class="task-count"> |
|||
今天共 <span class="count-num">{{ taskList.length }}</span> 个任务 |
|||
</div> |
|||
|
|||
<!-- 任务列表 --> |
|||
<div class="task-list-wrap"> |
|||
<div v-if="loading" class="task-loading"> |
|||
<van-loading size="24" color="#2382cf" /> |
|||
</div> |
|||
<div v-else-if="taskList.length === 0" class="task-empty"> |
|||
<van-empty description="当天暂无任务" /> |
|||
</div> |
|||
<div v-else class="task-list"> |
|||
<div |
|||
v-for="item in taskList" |
|||
:key="item.id" |
|||
class="task-card" |
|||
@click="onTaskClick(item)" |
|||
> |
|||
<div class="task-time-col"> |
|||
<div class="time-start">{{ getTimeStart(item) }}</div> |
|||
<div class="time-end">{{ getTimeEnd(item) }}</div> |
|||
</div> |
|||
<div class="task-info-col"> |
|||
<div class="task-title-row"> |
|||
<span class="task-title">{{ item.enforceContent }}</span> |
|||
<span v-if="item.isUnion === 1" class="union-badge">联</span> |
|||
</div> |
|||
<div class="task-detail"> |
|||
<van-icon name="wap-home-o" size="14" /> |
|||
<span>{{ item.enterpriseName }}</span> |
|||
</div> |
|||
<div class="task-detail"> |
|||
<van-icon name="clock-o" size="14" color="#999" /> |
|||
<span>{{ formatPlanDate(item) }} {{ getTimeRange(item) }}</span> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
|
|||
<!-- 月份选择弹窗 --> |
|||
<van-popup v-model:show="showMonthPicker" position="bottom" round> |
|||
<van-date-picker |
|||
v-model="monthPickerValue" |
|||
title="选择月份" |
|||
:columns-type="['year', 'month']" |
|||
@confirm="onMonthPickerConfirm" |
|||
@cancel="showMonthPicker = false" |
|||
/> |
|||
</van-popup> |
|||
</div> |
|||
</template> |
|||
|
|||
<script setup name="cpTaskMine"> |
|||
import { useRouter } from 'vue-router'; |
|||
import { getMyTaskList, getMyTaskDates } from '@/api/enforcement/task'; |
|||
|
|||
const router = useRouter(); |
|||
|
|||
// ========== 日历相关 ========== |
|||
const weekDays = ['日', '一', '二', '三', '四', '五', '六']; |
|||
const today = new Date(); |
|||
const currentYear = ref(today.getFullYear()); |
|||
const currentMonth = ref(today.getMonth() + 1); |
|||
const selectedDate = ref(new Date(today.getFullYear(), today.getMonth(), today.getDate())); |
|||
const taskDatesSet = ref(new Set()); |
|||
|
|||
// 月份选择器 |
|||
const showMonthPicker = ref(false); |
|||
const monthPickerValue = ref([ |
|||
String(today.getFullYear()), |
|||
String(today.getMonth() + 1).padStart(2, '0'), |
|||
]); |
|||
|
|||
// 简易农历(仅用于展示,不做精确计算) |
|||
const lunarDays = [ |
|||
'', '初一', '初二', '初三', '初四', '初五', '初六', '初七', '初八', '初九', '初十', |
|||
'十一', '十二', '十三', '十四', '十五', '十六', '十七', '十八', '十九', '二十', |
|||
'廿一', '廿二', '廿三', '廿四', '廿五', '廿六', '廿七', '廿八', '廿九', '三十', |
|||
]; |
|||
|
|||
const getLunarDay = (date) => { |
|||
// 简化处理:使用日期的日数模拟农历显示 |
|||
const d = date.getDate(); |
|||
return lunarDays[d] || ''; |
|||
}; |
|||
|
|||
// 生成日历单元格 |
|||
const calendarCells = computed(() => { |
|||
const year = currentYear.value; |
|||
const month = currentMonth.value; |
|||
const firstDay = new Date(year, month - 1, 1); |
|||
const lastDay = new Date(year, month, 0); |
|||
const startWeekDay = firstDay.getDay(); // 0=Sunday |
|||
const totalDays = lastDay.getDate(); |
|||
|
|||
const cells = []; |
|||
|
|||
// 上个月补位 |
|||
const prevMonthLastDay = new Date(year, month - 1, 0).getDate(); |
|||
for (let i = startWeekDay - 1; i >= 0; i--) { |
|||
const day = prevMonthLastDay - i; |
|||
const d = new Date(year, month - 2, day); |
|||
cells.push({ |
|||
day, |
|||
date: d, |
|||
lunar: getLunarDay(d), |
|||
isCurrentMonth: false, |
|||
isToday: false, |
|||
isSelected: false, |
|||
hasDot: false, |
|||
}); |
|||
} |
|||
|
|||
// 当月 |
|||
for (let day = 1; day <= totalDays; day++) { |
|||
const d = new Date(year, month - 1, day); |
|||
const dateStr = formatDateStr(d); |
|||
const isToday = dateStr === formatDateStr(today); |
|||
const isSelected = dateStr === formatDateStr(selectedDate.value); |
|||
cells.push({ |
|||
day, |
|||
date: d, |
|||
lunar: getLunarDay(d), |
|||
isCurrentMonth: true, |
|||
isToday, |
|||
isSelected, |
|||
hasDot: taskDatesSet.value.has(dateStr), |
|||
}); |
|||
} |
|||
|
|||
// 下个月补位(补满6行) |
|||
const remaining = 42 - cells.length; |
|||
for (let day = 1; day <= remaining; day++) { |
|||
const d = new Date(year, month, day); |
|||
cells.push({ |
|||
day, |
|||
date: d, |
|||
lunar: getLunarDay(d), |
|||
isCurrentMonth: false, |
|||
isToday: false, |
|||
isSelected: false, |
|||
hasDot: false, |
|||
}); |
|||
} |
|||
|
|||
return cells; |
|||
}); |
|||
|
|||
const formatDateStr = (d) => { |
|||
const y = d.getFullYear(); |
|||
const m = String(d.getMonth() + 1).padStart(2, '0'); |
|||
const day = String(d.getDate()).padStart(2, '0'); |
|||
return `${y}-${m}-${day}`; |
|||
}; |
|||
|
|||
const formatYearMonth = () => { |
|||
return `${currentYear.value}-${String(currentMonth.value).padStart(2, '0')}`; |
|||
}; |
|||
|
|||
// 切换月份 |
|||
const onPrevMonth = () => { |
|||
if (currentMonth.value === 1) { |
|||
currentYear.value--; |
|||
currentMonth.value = 12; |
|||
} else { |
|||
currentMonth.value--; |
|||
} |
|||
loadTaskDates(); |
|||
}; |
|||
|
|||
const onNextMonth = () => { |
|||
if (currentMonth.value === 12) { |
|||
currentYear.value++; |
|||
currentMonth.value = 1; |
|||
} else { |
|||
currentMonth.value++; |
|||
} |
|||
loadTaskDates(); |
|||
}; |
|||
|
|||
// 月份选择器确认 |
|||
const onMonthPickerConfirm = ({ selectedValues }) => { |
|||
currentYear.value = parseInt(selectedValues[0]); |
|||
currentMonth.value = parseInt(selectedValues[1]); |
|||
showMonthPicker.value = false; |
|||
loadTaskDates(); |
|||
}; |
|||
|
|||
// 选择日期 |
|||
const onSelectDate = (cell) => { |
|||
if (!cell.isCurrentMonth) return; |
|||
selectedDate.value = cell.date; |
|||
loadTaskList(); |
|||
}; |
|||
|
|||
// 返回 |
|||
const onBack = () => { |
|||
router.back(); |
|||
}; |
|||
|
|||
// 点击任务卡片跳转详情 |
|||
const onTaskClick = (item) => { |
|||
router.push({ path: '/enforcement/task/detail', query: { planId: item.planId } }); |
|||
}; |
|||
|
|||
// ========== 任务列表 ========== |
|||
const taskList = ref([]); |
|||
const loading = ref(false); |
|||
|
|||
// 时段转时间显示 |
|||
const getTimeStart = (item) => { |
|||
const time = item.planActualTime || item.planTime; |
|||
if (time === 1) return '09:00'; |
|||
if (time === 2) return '14:00'; |
|||
return '00:00'; |
|||
}; |
|||
|
|||
const getTimeEnd = (item) => { |
|||
const time = item.planActualTime || item.planTime; |
|||
if (time === 1) return '12:00'; |
|||
if (time === 2) return '18:00'; |
|||
return '23:59'; |
|||
}; |
|||
|
|||
const getTimeRange = (item) => { |
|||
return `${getTimeStart(item)}-${getTimeEnd(item)}`; |
|||
}; |
|||
|
|||
const formatPlanDate = (item) => { |
|||
const d = item.planActualDate || item.planDate; |
|||
if (!d) return ''; |
|||
if (typeof d === 'string') return d.substring(0, 10); |
|||
const date = new Date(d); |
|||
return formatDateStr(date); |
|||
}; |
|||
|
|||
// 加载任务日期(月份打点) |
|||
const loadTaskDates = () => { |
|||
const ym = formatYearMonth(); |
|||
getMyTaskDates(ym, (res) => { |
|||
if (res.code === 200 && res.data) { |
|||
taskDatesSet.value = new Set(res.data); |
|||
} |
|||
}); |
|||
// 如果选中日期不在当月,自动选中当月1号 |
|||
if (selectedDate.value.getFullYear() !== currentYear.value |
|||
|| selectedDate.value.getMonth() + 1 !== currentMonth.value) { |
|||
selectedDate.value = new Date(currentYear.value, currentMonth.value - 1, 1); |
|||
} |
|||
loadTaskList(); |
|||
}; |
|||
|
|||
// 加载任务列表 |
|||
const loadTaskList = () => { |
|||
loading.value = true; |
|||
const dateStr = formatDateStr(selectedDate.value); |
|||
getMyTaskList( |
|||
{ planDate: dateStr }, |
|||
(res) => { |
|||
loading.value = false; |
|||
if (res.code === 200 && res.data) { |
|||
taskList.value = res.data; |
|||
} else { |
|||
taskList.value = []; |
|||
} |
|||
}, |
|||
() => { |
|||
loading.value = false; |
|||
taskList.value = []; |
|||
} |
|||
); |
|||
}; |
|||
|
|||
// 初始化 |
|||
onMounted(() => { |
|||
loadTaskDates(); |
|||
}); |
|||
</script> |
|||
|
|||
<style lang="less" scoped> |
|||
.task-page { |
|||
display: flex; |
|||
flex-direction: column; |
|||
height: 100%; |
|||
background: #f0f6ff; |
|||
font-family: -apple-system, BlinkMacSystemFont, 'PingFang SC', 'Microsoft YaHei', sans-serif; |
|||
} |
|||
|
|||
/* 导航栏 */ |
|||
.nav-bar { |
|||
display: flex; |
|||
align-items: center; |
|||
justify-content: space-between; |
|||
padding: 12px 16px; |
|||
background: #fff; |
|||
flex-shrink: 0; |
|||
|
|||
.nav-back { |
|||
width: 32px; |
|||
cursor: pointer; |
|||
} |
|||
.nav-title { |
|||
font-size: 18px; |
|||
font-weight: 600; |
|||
color: #1a1a2e; |
|||
} |
|||
.nav-right { |
|||
width: 32px; |
|||
} |
|||
} |
|||
|
|||
/* 月份切换 */ |
|||
.month-switch { |
|||
display: flex; |
|||
align-items: center; |
|||
justify-content: center; |
|||
gap: 20px; |
|||
padding: 8px 0; |
|||
background: #fff; |
|||
flex-shrink: 0; |
|||
|
|||
.month-arrow { |
|||
width: 24px; |
|||
height: 24px; |
|||
display: flex; |
|||
align-items: center; |
|||
justify-content: center; |
|||
cursor: pointer; |
|||
} |
|||
.month-text { |
|||
font-size: 15px; |
|||
font-weight: 600; |
|||
color: #2382cf; |
|||
cursor: pointer; |
|||
} |
|||
} |
|||
|
|||
/* 日历 */ |
|||
.calendar { |
|||
background: #fff; |
|||
padding: 0 8px 8px; |
|||
flex-shrink: 0; |
|||
} |
|||
|
|||
.week-header { |
|||
display: grid; |
|||
grid-template-columns: repeat(7, 1fr); |
|||
text-align: center; |
|||
padding: 4px 0; |
|||
|
|||
.week-cell { |
|||
font-size: 11px; |
|||
color: #999; |
|||
font-weight: 500; |
|||
} |
|||
} |
|||
|
|||
.date-grid { |
|||
display: grid; |
|||
grid-template-columns: repeat(7, 1fr); |
|||
gap: 1px 0; |
|||
} |
|||
|
|||
.date-cell { |
|||
display: flex; |
|||
flex-direction: column; |
|||
align-items: center; |
|||
justify-content: center; |
|||
padding: 3px 0; |
|||
min-height: 38px; |
|||
cursor: pointer; |
|||
position: relative; |
|||
border-radius: 6px; |
|||
transition: background 0.15s; |
|||
|
|||
.date-num { |
|||
font-size: 13px; |
|||
font-weight: 600; |
|||
color: #333; |
|||
line-height: 1.3; |
|||
} |
|||
|
|||
.date-lunar { |
|||
font-size: 9px; |
|||
color: #999; |
|||
line-height: 1.1; |
|||
} |
|||
|
|||
.date-dot { |
|||
width: 4px; |
|||
height: 4px; |
|||
border-radius: 50%; |
|||
background: #2382cf; |
|||
margin-top: 1px; |
|||
} |
|||
|
|||
&.other-month { |
|||
.date-num { color: #ccc; } |
|||
.date-lunar { color: #ddd; } |
|||
} |
|||
|
|||
&.is-today:not(.is-selected) { |
|||
.date-num { |
|||
color: #2382cf; |
|||
font-weight: 700; |
|||
} |
|||
} |
|||
|
|||
&.is-selected { |
|||
background: #2382cf; |
|||
.date-num { color: #fff; font-weight: 700; } |
|||
.date-lunar { color: rgba(255,255,255,0.8); } |
|||
.date-dot { background: #fff; } |
|||
} |
|||
} |
|||
|
|||
/* 任务计数 */ |
|||
.task-count { |
|||
padding: 12px 16px 8px; |
|||
font-size: 14px; |
|||
color: #666; |
|||
flex-shrink: 0; |
|||
|
|||
.count-num { |
|||
font-size: 18px; |
|||
font-weight: 700; |
|||
color: #2382cf; |
|||
} |
|||
} |
|||
|
|||
/* 任务列表 */ |
|||
.task-list-wrap { |
|||
flex: 1; |
|||
overflow-y: auto; |
|||
padding: 0 12px 16px; |
|||
} |
|||
|
|||
.task-loading { |
|||
display: flex; |
|||
justify-content: center; |
|||
padding: 40px 0; |
|||
} |
|||
|
|||
.task-list { |
|||
display: flex; |
|||
flex-direction: column; |
|||
gap: 12px; |
|||
} |
|||
|
|||
.task-card { |
|||
display: flex; |
|||
background: #fff; |
|||
border-radius: 14px; |
|||
padding: 16px; |
|||
box-shadow: 0 2px 10px rgba(44, 116, 190, 0.07); |
|||
gap: 14px; |
|||
} |
|||
|
|||
.task-time-col { |
|||
display: flex; |
|||
flex-direction: column; |
|||
align-items: center; |
|||
justify-content: center; |
|||
min-width: 48px; |
|||
flex-shrink: 0; |
|||
|
|||
.time-start, .time-end { |
|||
font-size: 15px; |
|||
font-weight: 700; |
|||
color: #333; |
|||
line-height: 1.6; |
|||
} |
|||
} |
|||
|
|||
.task-info-col { |
|||
flex: 1; |
|||
min-width: 0; |
|||
display: flex; |
|||
flex-direction: column; |
|||
gap: 6px; |
|||
} |
|||
|
|||
.task-title-row { |
|||
display: flex; |
|||
align-items: center; |
|||
gap: 6px; |
|||
} |
|||
|
|||
.task-title { |
|||
font-size: 15px; |
|||
font-weight: 600; |
|||
color: #1a1a2e; |
|||
overflow: hidden; |
|||
text-overflow: ellipsis; |
|||
white-space: nowrap; |
|||
flex: 1; |
|||
} |
|||
|
|||
.union-badge { |
|||
display: inline-flex; |
|||
align-items: center; |
|||
justify-content: center; |
|||
width: 22px; |
|||
height: 22px; |
|||
border-radius: 4px; |
|||
background: #2382cf; |
|||
color: #fff; |
|||
font-size: 12px; |
|||
font-weight: 600; |
|||
flex-shrink: 0; |
|||
} |
|||
|
|||
.task-detail { |
|||
display: flex; |
|||
align-items: center; |
|||
gap: 4px; |
|||
font-size: 13px; |
|||
color: #393838; |
|||
overflow: hidden; |
|||
|
|||
span { |
|||
overflow: hidden; |
|||
text-overflow: ellipsis; |
|||
white-space: nowrap; |
|||
} |
|||
} |
|||
</style> |
|||
Loading…
Reference in new issue