| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508 |
- <template>
- <view class="sub-page">
- <TopBar title="每日任务" show-back />
- <view class="px-4 pt-3">
- <!-- 今日任务概览 -->
- <view class="glass-card p-4">
- <view class="section-head-inner mb-3">
- <view class="section-bar mr-2"></view>
- <text class="section-title">今日概览</text>
- </view>
- <view class="grid grid-cols-2 gap-3">
- <view class="stat-card">
- <view class="stat-bar"></view>
- <text class="stat-value">{{ todayTasks.length }}</text>
- <text class="stat-label">今日任务</text>
- </view>
- <view class="stat-card">
- <view class="stat-bar"></view>
- <text class="stat-value">{{ completedTasks }}</text>
- <text class="stat-label">已完成</text>
- </view>
- </view>
- </view>
- <!-- 车辆检查 -->
- <view class="glass-card p-4">
- <view class="section-head-inner mb-3">
- <view class="section-bar mr-2"></view>
- <text class="section-title flex-1">车辆检查</text>
- <text v-if="vehicleCheckStatus" class="check-state check-state-done">已完成</text>
- <text v-else class="check-state check-state-pending">待检查</text>
- </view>
- <view v-if="!vehicleCheckStatus">
- <view class="check-group">
- <text class="check-group-title">常规检查</text>
- <view
- v-for="item in checkItems"
- :key="item.key"
- class="check-row"
- @click="toggleCheck(item.key)"
- >
- <text class="check-label">{{ item.label }}</text>
- <view class="check-circle" :class="{ 'check-circle-on': checks[item.key] }">
- <text v-if="checks[item.key]" class="uni-icons uniui-checkmarkempty check-tick"></text>
- </view>
- </view>
- </view>
- <view class="check-group">
- <text class="check-group-title">设备检查</text>
- <view
- v-for="item in equipmentItems"
- :key="item.key"
- class="check-row"
- @click="toggleCheck(item.key)"
- >
- <text class="check-label">{{ item.label }}</text>
- <view class="check-circle" :class="{ 'check-circle-on': checks[item.key] }">
- <text v-if="checks[item.key]" class="uni-icons uniui-checkmarkempty check-tick"></text>
- </view>
- </view>
- </view>
- <view class="check-group">
- <text class="check-group-title">三大系统检查</text>
- <view
- v-for="item in systemItems"
- :key="item.key"
- class="check-row"
- @click="toggleCheck(item.key)"
- >
- <text class="check-label">{{ item.label }}</text>
- <view class="check-circle" :class="{ 'check-circle-on': checks[item.key] }">
- <text v-if="checks[item.key]" class="uni-icons uniui-checkmarkempty check-tick"></text>
- </view>
- </view>
- </view>
- <view class="mb-3">
- <text class="check-group-title">备注</text>
- <textarea
- v-model="remark"
- class="form-textarea"
- placeholder="请输入检查备注..."
- :maxlength="200"
- />
- </view>
- <button
- class="submit-btn"
- hover-class="submit-btn-hover"
- :hover-start-time="0"
- :hover-stay-time="120"
- :disabled="!vehicleCheckStatus"
- @click="submitCheck"
- >
- 提交检查
- </button>
- </view>
- <view v-else class="done-wrap">
- <view class="done-icon">
- <text class="uni-icons uniui-checkmarkempty done-tick"></text>
- </view>
- <text class="done-text">今日车辆检查已完成</text>
- <text v-if="remark" class="done-meta">备注:{{ remark }}</text>
- <text class="done-meta">{{ today }}</text>
- </view>
- </view>
- <!-- 今日任务列表 -->
- <view class="glass-card">
- <view class="section-head">
- <view class="section-bar mr-2"></view>
- <text class="section-title flex-1">今日任务列表</text>
- </view>
- <view v-if="taskStore.loading" class="py-10 text-center">
- <text class="empty-text">加载中...</text>
- </view>
- <view v-else-if="todayTasks.length === 0" class="pb-3">
- <EmptyState message="今日无任务" />
- </view>
- <view v-else>
- <view
- v-for="task in todayTasks"
- :key="task.taskId || task.id"
- class="list-row"
- hover-class="row-hover"
- :hover-start-time="0"
- :hover-stay-time="120"
- @click="goToDetail(task.taskId || task.id)"
- >
- <view class="icon-chip mr-3">
- <text class="uni-icons uniui-flag chip-glyph"></text>
- </view>
- <view class="flex-1 min-w-0 mr-2">
- <text class="row-title truncate">{{ task.taskName || task.projectName }}</text>
- <text class="row-sub truncate">{{ task.address || '暂无地址' }}</text>
- </view>
- <StatusTag :status="task.status" />
- </view>
- </view>
- </view>
- </view>
- </view>
- </template>
- <script setup lang="ts">
- import { ref, computed, onMounted } from 'vue'
- import { useTaskStore } from '../../stores/task'
- import TopBar from '../../components/common/TopBar.vue'
- import StatusTag from '../../components/common/StatusTag.vue'
- import EmptyState from '../../components/common/EmptyState.vue'
- const taskStore = useTaskStore()
- const today = new Date().toISOString().split('T')[0]
- const storageKey = `vehicle_check_${today}`
- const remarkKey = `vehicle_check_remark_${today}`
- const checkItems = [
- { key: 'exterior', label: '车辆外观检查' },
- { key: 'tire', label: '轮胎气压检查' },
- { key: 'fluid', label: '油液检查' },
- { key: 'brake', label: '刹车系统检查' },
- { key: 'safety', label: '安全设备检查' },
- ]
- const equipmentItems = [
- { key: 'pump', label: '抽水泵检查' },
- { key: 'hose', label: '高压水管检查' },
- { key: 'tool', label: '施工工具检查' },
- { key: 'protective', label: '防护用品检查' },
- ]
- const systemItems = [
- { key: 'power', label: '动力系统' },
- { key: 'hydraulic', label: '液压系统' },
- { key: 'electrical', label: '电气系统' },
- ]
- const allItems = [...checkItems, ...equipmentItems, ...systemItems]
- const initialChecks: Record<string, boolean> = {}
- allItems.forEach((item) => {
- initialChecks[item.key] = false
- })
- const checks = ref<Record<string, boolean>>({ ...initialChecks })
- const remark = ref('')
- const vehicleCheckStatus = computed(() => {
- return allItems.every((item) => checks.value[item.key])
- })
- const completedTasks = computed(() => {
- return todayTasks.value.filter((t) => t.status === 'completed').length
- })
- const todayTasks = computed<any[]>(() => {
- return taskStore.tasks.filter(
- (t: any) => t.planDate === today || t.scheduleDate === today || t.serviceDate === today
- )
- })
- function toggleCheck(type: string) {
- checks.value[type] = !checks.value[type]
- uni.setStorageSync(storageKey, JSON.stringify(checks.value))
- }
- function submitCheck() {
- if (!vehicleCheckStatus.value) {
- uni.showToast({ title: '请完成所有检查项', icon: 'none' })
- return
- }
- uni.setStorageSync(storageKey, JSON.stringify(checks.value))
- uni.setStorageSync(remarkKey, remark.value)
- uni.showToast({ title: '提交成功', icon: 'success' })
- }
- function goToDetail(taskId: string | number) {
- if (!taskId) return
- uni.navigateTo({ url: `/subPackages/pages-construction/taskDetail?id=${taskId}` })
- }
- onMounted(() => {
- const saved = uni.getStorageSync(storageKey)
- if (saved) {
- try {
- checks.value = JSON.parse(saved)
- } catch {
- // 解析失败则使用默认值
- }
- }
- const savedRemark = uni.getStorageSync(remarkKey)
- if (savedRemark) {
- remark.value = savedRemark
- }
- taskStore.fetchMyTasks(today)
- })
- </script>
- <style scoped>
- .sub-page {
- max-width: 430px;
- margin: 0 auto;
- min-height: 100vh;
- background-color: #f6fbf9;
- padding-bottom: 96px;
- }
- .glass-card {
- background-color: rgba(255, 255, 255, 0.85);
- backdrop-filter: blur(14px);
- -webkit-backdrop-filter: blur(14px);
- border: 1px solid rgba(164, 216, 152, 0.35);
- border-radius: 24px;
- box-shadow: 0 18px 50px -12px rgba(54, 143, 111, 0.28);
- overflow: hidden;
- margin-bottom: 12px;
- }
- /* 区块标题 */
- .section-head {
- display: flex;
- align-items: center;
- padding: 16px 16px 12px;
- }
- .section-head-inner {
- display: flex;
- align-items: center;
- }
- .section-bar {
- width: 4px;
- height: 16px;
- border-radius: 2px;
- background: linear-gradient(180deg, #368f6f 0%, #5ab8d0 100%);
- }
- .section-title {
- font-size: 16px;
- font-weight: 700;
- color: #1f2937;
- }
- /* 统计卡 */
- .stat-card {
- position: relative;
- display: flex;
- flex-direction: column;
- align-items: center;
- justify-content: center;
- min-height: 88px;
- padding: 14px 6px 12px;
- border-radius: 16px;
- background-color: rgba(164, 216, 152, 0.14);
- border: 1px solid rgba(164, 216, 152, 0.3);
- overflow: hidden;
- }
- .stat-bar {
- position: absolute;
- top: 0;
- left: 50%;
- transform: translateX(-50%);
- width: 26px;
- height: 3px;
- border-radius: 0 0 3px 3px;
- background: linear-gradient(135deg, #368f6f 0%, #5ab8d0 100%);
- }
- .stat-value {
- font-size: 24px;
- font-weight: 800;
- line-height: 1.1;
- color: #368f6f;
- }
- .stat-label {
- margin-top: 6px;
- font-size: 12px;
- color: #6b7280;
- }
- /* 检查项 */
- .check-state {
- font-size: 12px;
- font-weight: 600;
- }
- .check-state-done {
- color: #368f6f;
- }
- .check-state-pending {
- color: #b7791f;
- }
- .check-group {
- margin-bottom: 12px;
- padding-top: 12px;
- border-top: 1px solid rgba(164, 216, 152, 0.18);
- }
- .check-group:first-of-type {
- border-top: none;
- padding-top: 0;
- }
- .check-group-title {
- display: block;
- margin-bottom: 8px;
- font-size: 13px;
- font-weight: 600;
- color: #6b7280;
- }
- .check-row {
- display: flex;
- align-items: center;
- justify-content: space-between;
- padding: 8px 0;
- }
- .check-label {
- font-size: 14px;
- color: #4b5563;
- }
- .check-circle {
- width: 24px;
- height: 24px;
- border-radius: 50%;
- border: 2px solid #d1d5db;
- display: flex;
- align-items: center;
- justify-content: center;
- }
- .check-circle-on {
- border-color: #368f6f;
- background: linear-gradient(135deg, #368f6f 0%, #4ba98a 100%);
- }
- .check-tick {
- font-size: 13px;
- color: #ffffff;
- }
- .form-textarea {
- width: 100%;
- min-height: 72px;
- padding: 10px 12px;
- border-radius: 12px;
- border: 1px solid #e5e7eb;
- background-color: #ffffff;
- font-size: 14px;
- color: #1f2937;
- }
- .submit-btn {
- width: 100%;
- padding: 12px 0;
- border-radius: 14px;
- font-size: 14px;
- font-weight: 600;
- color: #ffffff;
- background: linear-gradient(135deg, #368f6f 0%, #4ba98a 100%);
- box-shadow: 0 10px 24px -10px rgba(54, 143, 111, 0.7);
- line-height: 1.4;
- }
- .submit-btn-hover {
- opacity: 0.88;
- }
- .submit-btn[disabled] {
- opacity: 0.5;
- }
- /* 已完成态 */
- .done-wrap {
- display: flex;
- flex-direction: column;
- align-items: center;
- padding: 24px 0 12px;
- }
- .done-icon {
- width: 64px;
- height: 64px;
- border-radius: 50%;
- display: flex;
- align-items: center;
- justify-content: center;
- background-color: rgba(164, 216, 152, 0.24);
- margin-bottom: 12px;
- }
- .done-tick {
- font-size: 28px;
- color: #368f6f;
- }
- .done-text {
- font-size: 14px;
- font-weight: 600;
- color: #1f2937;
- }
- .done-meta {
- margin-top: 4px;
- font-size: 12px;
- color: #9ca3af;
- }
- /* 列表行 */
- .list-row {
- display: flex;
- align-items: center;
- padding: 13px 16px;
- border-top: 1px solid rgba(164, 216, 152, 0.18);
- }
- .row-title {
- display: block;
- font-size: 14px;
- font-weight: 500;
- color: #1f2937;
- }
- .row-sub {
- display: block;
- margin-top: 3px;
- font-size: 12px;
- color: #9ca3af;
- }
- .row-hover {
- background-color: rgba(164, 216, 152, 0.1);
- }
- .icon-chip {
- width: 42px;
- height: 42px;
- border-radius: 12px;
- flex-shrink: 0;
- display: flex;
- align-items: center;
- justify-content: center;
- background-color: rgba(164, 216, 152, 0.2);
- }
- .chip-glyph {
- font-size: 20px;
- color: #368f6f;
- }
- .empty-text {
- font-size: 13px;
- color: #9ca3af;
- }
- </style>
|