Browse Source

feat: 完成 UI2.0 改版并新增登录页记住密码与密码可见性切换

zhangzhicheng 1 week ago
parent
commit
e4a44fe7f0
78 changed files with 11859 additions and 3361 deletions
  1. 48 14
      src/api/project.ts
  2. 43 4
      src/api/task.ts
  3. 10 3
      src/components/common/EmptyState.vue
  4. 9 3
      src/components/common/StatusTag.vue
  5. 681 133
      src/components/dispatch/ScheduleView.vue
  6. 493 102
      src/components/home/ConstructionHome.vue
  7. 423 76
      src/components/home/DispatchHome.vue
  8. 492 87
      src/components/home/SalesHome.vue
  9. 193 78
      src/components/my/ProfileView.vue
  10. 34 12
      src/components/project/ProjectCard.vue
  11. 41 13
      src/components/task/ConstructionTaskCard.vue
  12. 46 25
      src/components/task/DispatchTaskCard.vue
  13. 35 11
      src/components/task/SalesTaskCard.vue
  14. 127 51
      src/components/task/TaskListView.vue
  15. 1 1
      src/custom-tab-bar/index.js
  16. 1 0
      src/custom-tab-bar/index.wxml
  17. 27 6
      src/custom-tab-bar/index.wxss
  18. 2 2
      src/mock/index.ts
  19. 4 0
      src/pages.json
  20. 204 61
      src/pages/contract/contract.vue
  21. 184 58
      src/pages/customer/customer.vue
  22. 96 13
      src/pages/login/login.vue
  23. 17 4
      src/pages/task/task.vue
  24. BIN
      src/static/tabbar/home-active.png
  25. BIN
      src/static/tabbar/home.png
  26. BIN
      src/static/tabbar/my-active.png
  27. BIN
      src/static/tabbar/my.png
  28. BIN
      src/static/tabbar/task-active.png
  29. BIN
      src/static/tabbar/task.png
  30. 15 27
      src/stores/project.ts
  31. 14 2
      src/stores/task.ts
  32. 9 0
      src/styles/global.scss
  33. 534 211
      src/subPackages/pages-common/addProject.vue
  34. 94 22
      src/subPackages/pages-common/changePassword.vue
  35. 168 45
      src/subPackages/pages-common/helpCenter.vue
  36. 127 27
      src/subPackages/pages-common/knowledgeDetail.vue
  37. 96 35
      src/subPackages/pages-common/knowledgeList.vue
  38. 104 27
      src/subPackages/pages-common/messageDetail.vue
  39. 148 39
      src/subPackages/pages-common/messageList.vue
  40. 116 27
      src/subPackages/pages-common/noticeDetail.vue
  41. 185 53
      src/subPackages/pages-common/noticeList.vue
  42. 195 174
      src/subPackages/pages-common/projectDetail.vue
  43. 221 84
      src/subPackages/pages-common/projectList.vue
  44. 266 60
      src/subPackages/pages-common/publishTask.vue
  45. 181 64
      src/subPackages/pages-common/taskDetail.vue
  46. 362 100
      src/subPackages/pages-construction/dailyTask.vue
  47. 108 27
      src/subPackages/pages-construction/historyTask.vue
  48. 97 20
      src/subPackages/pages-construction/noticeDetail.vue
  49. 159 36
      src/subPackages/pages-construction/noticeList.vue
  50. 119 25
      src/subPackages/pages-construction/notification.vue
  51. 832 189
      src/subPackages/pages-construction/taskDetail.vue
  52. 113 29
      src/subPackages/pages-construction/tomorrowTask.vue
  53. 77 8
      src/subPackages/pages-contract/contractDetail.vue
  54. 43 1
      src/subPackages/pages-contract/contractForm.vue
  55. 51 4
      src/subPackages/pages-customer/customerDetail.vue
  56. 57 2
      src/subPackages/pages-customer/customerForm.vue
  57. 292 121
      src/subPackages/pages-dispatch/availableVehicles.vue
  58. 134 39
      src/subPackages/pages-dispatch/dutyPersonnel.vue
  59. 212 45
      src/subPackages/pages-dispatch/emergencyStandby.vue
  60. 210 36
      src/subPackages/pages-dispatch/emergencyTeam.vue
  61. 63 10
      src/subPackages/pages-dispatch/noticeDetail.vue
  62. 159 36
      src/subPackages/pages-dispatch/noticeList.vue
  63. 119 25
      src/subPackages/pages-dispatch/notification.vue
  64. 145 43
      src/subPackages/pages-dispatch/onDutyStaff.vue
  65. 175 58
      src/subPackages/pages-dispatch/personnel.vue
  66. 288 120
      src/subPackages/pages-dispatch/publishTask.vue
  67. 330 107
      src/subPackages/pages-dispatch/schedule.vue
  68. 565 173
      src/subPackages/pages-dispatch/taskDetail.vue
  69. 183 88
      src/subPackages/pages-dispatch/vehicleList.vue
  70. 227 83
      src/subPackages/pages-dispatch/visualization.vue
  71. 172 45
      src/subPackages/pages-sales/knowledgeDetail.vue
  72. 162 44
      src/subPackages/pages-sales/knowledgeList.vue
  73. 144 41
      src/subPackages/pages-sales/messageDetail.vue
  74. 166 61
      src/subPackages/pages-sales/messageList.vue
  75. 119 25
      src/subPackages/pages-sales/notification.vue
  76. 147 33
      src/subPackages/pages-sales/taskReminder.vue
  77. 54 21
      src/types/index.ts
  78. 91 12
      src/utils/index.ts

+ 48 - 14
src/api/project.ts

@@ -1,31 +1,65 @@
 import { get, post, put, del } from './request'
+import type {
+  CooperationType,
+  FacilityType,
+  Industry,
+  PageResult,
+  PaymentMethod,
+  Project,
+  ProjectStatus
+} from '../types'
 
+/** 提交给后台的项目字段(对齐 BizProject,facilityTypes/servicePoints 为 JSON 字符串) */
 export interface ProjectParams {
   projectName: string
-  projectType: string
-  customerName: string
-  customerPhone: string
-  address: string
-  servicePoints?: { address?: string; contactName?: string; contactPhone?: string }[]
-  contacts?: { name?: string; phone?: string; role?: string }[]
+  cooperationType: CooperationType
+  contractId: number | string
+  customerId?: number | string
+  customerName?: string
+  customerPhone?: string
+  contractNo?: string
+  facilityTypes?: string
+  industry?: Industry
+  occupancyRate?: number
+  buildingArea?: number
+  contractStartDate?: string
+  contractEndDate?: string
+  projectDescription?: string
+  servicePoints?: string
+  address?: string
+  province?: string
+  city?: string
+  district?: string
+  street?: string
+  detailAddress?: string
+  latitude?: string
+  longitude?: string
+  primaryContactName?: string
+  primaryContactPhone?: string
+  backupContactName?: string
+  backupContactPhone?: string
+  specialContactReq?: string
+  paymentMethod?: PaymentMethod
+  status?: ProjectStatus
+  remark?: string
 }
 
 export function getProjectList(pageNum = 1, pageSize = 10) {
-  return get('/project/list', { pageNum, pageSize })
+  return get<PageResult<Project>>('/project/list', { pageNum, pageSize })
 }
 
-export function getProjectDetail(projectId: number) {
-  return get(`/project/${projectId}`)
+export function getProjectDetail(projectId: number | string) {
+  return get<Project>(`/project/${projectId}`)
 }
 
 export function createProject(data: ProjectParams) {
-  return post('/project', data)
+  return post<number>('/project', data)
 }
 
-export function updateProject(data: ProjectParams & { projectId: number }) {
-  return put('/project', data)
+export function updateProject(data: ProjectParams & { projectId: number | string }) {
+  return put<void>('/project', data)
 }
 
-export function deleteProject(projectId: number) {
-  return del(`/project/${projectId}`)
+export function deleteProject(projectId: number | string) {
+  return del<void>(`/project/${projectId}`)
 }

+ 43 - 4
src/api/task.ts

@@ -1,18 +1,39 @@
 import { get, post, put, del } from './request'
 
 export interface TaskParams {
-  projectId?: number
+  taskNo: string
+  taskName?: string
+  projectId: number | string
   taskType: string
-  urgentLevel: string
+  planDate: string
+  address: string
+  status?: string
+  urgencyLevel?: string
+  faultLocation?: string
+  faultType?: string
+  customerWords?: string
+  constructionType?: string
+  constructionFacility?: string
+  serviceMethod?: string
+  waterDistance?: string
+  assistantInfo?: string
+  estimatedWorkload?: string
+  estimatedDuration?: string
+  acceptanceRequirement?: string
+  acceptanceMethod?: string
   remark?: string
-  staffId?: number
-  vehicleId?: number
+  extraData?: string
+  [key: string]: any
 }
 
 export function getTaskList(pageNum = 1, pageSize = 10) {
   return get('/task/list', { pageNum, pageSize })
 }
 
+export function getMyTasks(planDate?: string) {
+  return get<any[]>('/task/my', { planDate })
+}
+
 export function getTaskDetail(taskId: number) {
   return get(`/task/${taskId}`)
 }
@@ -44,3 +65,21 @@ export function cancelTask(taskId: number, cancelReason: string, operatorId: num
 export function getTaskLogs(taskId: number) {
   return get(`/task/${taskId}/logs`)
 }
+
+export interface AuditParams {
+  remark?: string
+  auditUserId?: number | string
+  auditUserName?: string
+}
+
+export function approveTask(taskId: number, data: AuditParams) {
+  return post(`/task/audit/${taskId}/approve`, data)
+}
+
+export function rejectTaskAudit(taskId: number, data: AuditParams) {
+  return post(`/task/audit/${taskId}/reject`, data)
+}
+
+export function dispatchTask(taskId: number, data: AuditParams & { teamId: number; vehicleId: number }) {
+  return post(`/task/audit/${taskId}/dispatch`, data)
+}

+ 10 - 3
src/components/common/EmptyState.vue

@@ -1,9 +1,9 @@
 <template>
   <view class="empty-state flex flex-col items-center justify-center py-16 px-6">
-    <view class="w-16 h-16 rounded-full flex items-center justify-center mb-4" style="background-color: #f3f4f6;">
-      <text class="uni-icons text-3xl text-gray-300" :class="icon || 'uniui-info-filled'"></text>
+    <view class="empty-icon w-16 h-16 rounded-full flex items-center justify-center mb-4">
+      <text class="uni-icons text-3xl" :class="icon || 'uniui-info-filled'"></text>
     </view>
-    <text class="text-sm text-gray-400 font-medium">{{ text || '暂无数据' }}</text>
+    <text class="empty-text text-sm font-medium">{{ text || '暂无数据' }}</text>
     <slot />
   </view>
 </template>
@@ -24,4 +24,11 @@ withDefaults(defineProps<Props>(), {
 .empty-state {
   min-height: 200px;
 }
+.empty-icon {
+  background-color: rgba(164, 216, 152, 0.16);
+  color: rgba(54, 143, 111, 0.55);
+}
+.empty-text {
+  color: #6b7280;
+}
 </style>

+ 9 - 3
src/components/common/StatusTag.vue

@@ -10,7 +10,9 @@ import {
   getStatusText,
   getStatusColor,
   getStatusBgColor,
-  getProjectStatusText
+  getProjectStatusText,
+  getProjectStatusColor,
+  getProjectStatusBgColor
 } from '@/utils'
 
 type StatusTagType = 'task' | 'project'
@@ -31,8 +33,12 @@ const label = computed(() => {
     : getStatusText(props.status)
 })
 
-const textColor = computed(() => getStatusColor(props.status))
-const bgColor = computed(() => getStatusBgColor(props.status))
+const textColor = computed(() =>
+  props.type === 'project' ? getProjectStatusColor(props.status) : getStatusColor(props.status)
+)
+const bgColor = computed(() =>
+  props.type === 'project' ? getProjectStatusBgColor(props.status) : getStatusBgColor(props.status)
+)
 </script>
 
 <style scoped>

+ 681 - 133
src/components/dispatch/ScheduleView.vue

@@ -1,172 +1,214 @@
 <template>
-  <view class="app-container pb-20">
-    <StatusBar v-if="showStatusBar" />
-    <TopBar v-if="showTopBar" title="排班管理" :show-back="showBack" />
-
-    <!-- tab 页面专用标题栏 -->
-    <view v-if="!showTopBar" class="bg-white px-4 py-3 flex items-center justify-between border-b border-gray-100">
-      <text class="text-base font-semibold text-gray-800">排班管理</text>
-    </view>
-
-    <!-- 月份切换 -->
-    <view class="bg-white px-4 py-3 flex items-center justify-center border-b border-gray-100">
-      <view class="flex items-center">
-        <view class="p-2" @click="changeMonth(-1)">
-          <text class="uni-icons uniui-back text-gray-600"></text>
+  <view class="schedule-page">
+    <!-- tab 模式:山水 banner 标题区 -->
+    <template v-if="!showTopBar">
+      <view class="banner">
+        <image class="banner-bg" src="/static/index-backpage.png" mode="aspectFill" />
+        <view class="banner-veil"></view>
+        <StatusBar v-if="showStatusBar" />
+        <view class="banner-inner px-4">
+          <text class="banner-title block">排班管理</text>
+          <text class="banner-sub block">安排班组与车辆的工作日程</text>
         </view>
-        <text class="text-base font-semibold text-gray-800 mx-2">{{ currentYear }}年{{ currentMonth }}月</text>
-        <view class="p-2" @click="changeMonth(1)">
-          <text class="uni-icons uniui-arrowright text-gray-600"></text>
-        </view>
-      </view>
-    </view>
-
-    <!-- 页面中部操作按钮 -->
-    <view class="bg-white px-4 py-2 flex items-center justify-center gap-6 border-b border-gray-100">
-      <view class="text-sm text-blue-500 flex items-center" @click="goToToday">
-        <text class="uni-icons uniui-calendar-filled mr-1"></text>
-        今天
-      </view>
-      <view class="text-blue-500 text-sm flex items-center" @click="addSchedule">
-        <text class="uni-icons uniui-plusempty mr-1"></text>
-        新增排班
       </view>
-    </view>
+    </template>
+    <template v-else>
+      <StatusBar v-if="showStatusBar" />
+      <TopBar title="排班管理" :show-back="showBack" />
+    </template>
 
-    <!-- 日历 -->
-    <view class="mx-4 mt-4 bg-white rounded-xl shadow-sm p-4">
-      <!-- 星期标题 -->
-      <view class="flex mb-2">
-        <view v-for="day in weekDays" :key="day" class="flex-1 text-center">
-          <text class="text-xs text-gray-400">{{ day }}</text>
+    <view class="content-wrap px-4" :class="showTopBar ? 'pt-3 pb-6' : 'pb-24'">
+      <!-- 月份切换 + 快捷操作 -->
+      <view class="glass-card p-4">
+        <view class="month-row">
+          <view
+            class="month-arrow"
+            hover-class="month-arrow-hover"
+            :hover-start-time="0"
+            :hover-stay-time="120"
+            @click="changeMonth(-1)"
+          >
+            <text class="uni-icons uniui-back month-arrow-icon"></text>
+          </view>
+          <text class="month-text">{{ currentYear }}年{{ currentMonth }}月</text>
+          <view
+            class="month-arrow"
+            hover-class="month-arrow-hover"
+            :hover-start-time="0"
+            :hover-stay-time="120"
+            @click="changeMonth(1)"
+          >
+            <text class="uni-icons uniui-arrowright month-arrow-icon"></text>
+          </view>
+        </view>
+        <view class="action-row">
+          <view
+            class="action-chip"
+            hover-class="action-chip-hover"
+            :hover-start-time="0"
+            :hover-stay-time="120"
+            @click="goToToday"
+          >
+            <text class="uni-icons uniui-calendar-filled action-chip-icon mr-1"></text>
+            <text class="action-chip-text">今天</text>
+          </view>
+          <view
+            class="action-chip action-chip-primary"
+            hover-class="action-chip-primary-hover"
+            :hover-start-time="0"
+            :hover-stay-time="120"
+            @click="addSchedule"
+          >
+            <text class="uni-icons uniui-plusempty action-chip-icon-light mr-1"></text>
+            <text class="action-chip-text-light">新增排班</text>
+          </view>
         </view>
       </view>
 
-      <!-- 日期 -->
-      <view v-for="(week, weekIndex) in calendarWeeks" :key="weekIndex" class="flex mb-2">
-        <view
-          v-for="(date, dateIndex) in week"
-          :key="dateIndex"
-          class="flex-1 h-14 flex flex-col items-center justify-center relative mx-1 rounded-xl transition-transform duration-150"
-          :class="{
-            'opacity-30': date.isOtherMonth,
-            'bg-blue-50 text-primary': date.isToday && !isSelected(date),
-            'bg-primary text-white shadow-sm': isSelected(date),
-          }"
-          hover-class="scale-down"
-          :hover-start-time="0"
-          :hover-stay-time="150"
-          @click="selectDate(date)"
-        >
-          <text
-            class="text-sm font-medium"
+      <!-- 日历 -->
+      <view class="glass-card p-4">
+        <view class="flex mb-2">
+          <view v-for="day in weekDays" :key="day" class="flex-1 text-center">
+            <text class="weekday-text">{{ day }}</text>
+          </view>
+        </view>
+        <view v-for="(week, weekIndex) in calendarWeeks" :key="weekIndex" class="flex mb-2">
+          <view
+            v-for="(date, dateIndex) in week"
+            :key="dateIndex"
+            class="day-cell"
             :class="{
-              'text-primary': date.isToday && !isSelected(date),
-              'text-white': isSelected(date),
-              'text-gray-800': !date.isToday && !isSelected(date)
+              'day-other': date.isOtherMonth,
+              'day-today': date.isToday && !isSelected(date),
+              'day-selected': isSelected(date),
             }"
+            hover-class="day-hover"
+            :hover-start-time="0"
+            :hover-stay-time="120"
+            @click="selectDate(date)"
           >
-            {{ date.day }}
-          </text>
-          <view
-            v-if="date.hasSchedule"
-            class="w-2 h-2 rounded-full mt-1"
-            :class="isSelected(date) ? 'bg-white' : 'bg-primary'"
-          ></view>
+            <text
+              class="day-num"
+              :class="{
+                'day-num-today': date.isToday && !isSelected(date),
+                'day-num-selected': isSelected(date),
+              }"
+            >
+              {{ date.day }}
+            </text>
+            <view
+              v-if="date.hasSchedule"
+              class="day-dot"
+              :class="isSelected(date) ? 'day-dot-light' : ''"
+            ></view>
+          </view>
         </view>
       </view>
-    </view>
 
-    <!-- 排班列表 -->
-    <view class="px-4 mt-4">
-      <view class="flex justify-between items-center mb-3">
-        <text class="text-base font-semibold text-gray-800">{{ selectedDate }} 排班</text>
-      </view>
+      <!-- 排班列表 -->
+      <view class="glass-card">
+        <view class="section-head">
+          <view class="section-bar mr-2"></view>
+          <text class="section-title flex-1">{{ selectedDate }} 排班</text>
+        </view>
 
-      <view v-if="loading" class="py-10 text-center">
-        <text class="text-gray-400">加载中...</text>
-      </view>
+        <view v-if="loading" class="py-10 text-center">
+          <text class="empty-text">加载中...</text>
+        </view>
 
-      <view v-else-if="daySchedules.length === 0">
-        <EmptyState message="该日无排班" />
-      </view>
+        <view v-else-if="daySchedules.length === 0" class="py-2">
+          <EmptyState message="该日无排班" />
+        </view>
 
-      <view v-else>
-        <view
-          v-for="schedule in daySchedules"
-          :key="schedule.scheduleId"
-          class="bg-white rounded-xl shadow-sm mb-3"
-          hover-class="scale-down"
-          :hover-start-time="0"
-          :hover-stay-time="150"
-          @click="viewSchedule(schedule)"
-        >
-          <uni-list-item
-            :title="schedule.teamName || '未命名班组'"
-            :note="(schedule.staffIds || '未分配人员') + ' | ' + (schedule.vehicleIds || '未分配车辆')"
-            :right-text="getStatusText(schedule.status)"
-            show-arrow
+        <view v-else>
+          <view
+            v-for="schedule in daySchedules"
+            :key="schedule.scheduleId"
+            class="list-row"
+            hover-class="row-hover"
+            :hover-start-time="0"
+            :hover-stay-time="120"
+            @click="viewSchedule(schedule)"
           >
-            <template #header>
-              <view class="w-10 h-10 rounded-xl flex items-center justify-center mr-3" :class="getScheduleIconBgClass(schedule.shiftType)">
-                <text class="uni-icons text-lg" :class="getScheduleIconClass(schedule.shiftType)"></text>
+            <view
+              class="icon-chip mr-3"
+              :class="schedule.shiftTypeNorm === 'emergency' ? 'icon-chip-danger' : ''"
+            >
+              <text
+                class="uni-icons uniui-calendar-filled chip-glyph"
+                :class="schedule.shiftTypeNorm === 'emergency' ? 'chip-glyph-danger' : ''"
+              ></text>
+            </view>
+            <view class="flex-1 min-w-0 mr-2">
+              <view class="flex items-center">
+                <text class="row-title truncate mr-2">{{ schedule.teamName || '未命名班组' }}</text>
+                <text
+                  class="shift-tag flex-shrink-0"
+                  :class="schedule.shiftTypeNorm === 'emergency' ? 'shift-tag-danger' : ''"
+                >
+                  {{ schedule.shiftTypeText }}
+                </text>
               </view>
-            </template>
-          </uni-list-item>
+              <text class="row-sub truncate">{{ schedule.vehicleNamesText || '未分配车辆' }}</text>
+            </view>
+            <text class="status-text mr-2">{{ schedule.statusText }}</text>
+            <text class="uni-icons uniui-arrowright row-arrow"></text>
+          </view>
         </view>
       </view>
     </view>
+
     <!-- 新增排班底部弹窗 -->
-    <view v-if="showModal" class="fixed inset-0 z-50 flex flex-col justify-end" style="padding-bottom: var(--window-bottom, 0);">
-      <view class="absolute inset-0" style="background-color: rgba(0, 0, 0, 0.5);" @click="closeModal"></view>
-      <view class="relative bg-white rounded-t-3xl w-full p-4 overflow-y-auto" style="max-height: 60vh; margin-bottom: 100rpx;">
+    <view v-if="showModal" class="schedule-modal">
+      <view class="schedule-modal__overlay" @click="closeModal"></view>
+      <view class="schedule-modal__panel">
         <view class="flex justify-between items-center mb-4">
-          <text class="text-base font-semibold text-gray-800">{{ isDetail ? '排班详情' : '新增排班' }}</text>
-          <text class="uni-icons uniui-close text-gray-400 p-2" @click="closeModal"></text>
+          <text class="modal-title">{{ isDetail ? '排班详情' : '新增排班' }}</text>
+          <text class="uni-icons uniui-close modal-close p-2" @click="closeModal"></text>
         </view>
 
         <view class="mb-4">
-          <text class="text-sm text-gray-600 block mb-1">选择班组</text>
+          <text class="form-label">选择班组</text>
           <picker mode="selector" :range="teamOptions" range-key="label" :value="teamIndex" @change="(e: any) => { const idx = e.detail.value; form.teamId = teamOptions[idx].value; onTeamChange(form.teamId) }">
-            <view class="border border-gray-200 rounded-lg px-3 py-2 text-sm text-gray-800">
+            <view class="form-field">
               {{ teamNameDisplay || '请选择班组' }}
             </view>
           </picker>
         </view>
 
         <view class="mb-4">
-          <text class="text-sm text-gray-600 block mb-1">工作日期</text>
+          <text class="form-label">工作日期</text>
           <picker mode="date" :value="form.workDate" @change="(e: any) => form.workDate = e.detail.value">
-            <view class="border border-gray-200 rounded-lg px-3 py-2 text-sm text-gray-800">
+            <view class="form-field">
               {{ form.workDate || '请选择日期' }}
             </view>
           </picker>
         </view>
 
         <view class="mb-4">
-          <text class="text-sm text-gray-600 block mb-1">排班类型</text>
-          <view class="flex rounded-lg border border-gray-200 overflow-hidden">
+          <text class="form-label">排班类型</text>
+          <view class="shift-tabs">
             <view
-              class="flex-1 py-2 text-center text-sm"
-              :class="form.shiftType === 'month_plan' ? 'bg-primary text-white' : 'bg-white text-gray-600'"
+              class="shift-tab"
+              :class="form.shiftType === 'month_plan' ? 'shift-tab-active' : ''"
               @click="form.shiftType = 'month_plan'"
             >月计划</view>
             <view
-              class="flex-1 py-2 text-center text-sm"
-              :class="form.shiftType === 'emergency' ? 'bg-red-500 text-white' : 'bg-white text-gray-600'"
+              class="shift-tab"
+              :class="form.shiftType === 'emergency' ? 'shift-tab-danger' : ''"
               @click="form.shiftType = 'emergency'"
             >应急</view>
           </view>
         </view>
 
         <view class="mb-4">
-          <text class="text-sm text-gray-600 block mb-1">选择车辆</text>
+          <text class="form-label">选择车辆</text>
           <view class="flex flex-wrap gap-2">
             <view
               v-for="v in vehicleOptions"
               :key="v.value"
-              class="px-2 py-1 rounded-lg text-xs border"
-              :class="form.vehicleIds.includes(v.value) ? 'bg-primary text-white border-primary' : 'bg-white text-gray-600 border-gray-200'"
+              class="vehicle-chip"
+              :class="form.vehicleIds.includes(v.value) ? 'vehicle-chip-active' : ''"
               @click="toggleVehicle(v.value)"
             >
               {{ v.label }}
@@ -175,18 +217,29 @@
         </view>
 
         <view class="mb-4">
-          <text class="text-sm text-gray-600 block mb-1">备注(可选)</text>
+          <text class="form-label">备注(可选)</text>
           <textarea
             v-model="form.remark"
-            class="w-full border border-gray-200 rounded-lg px-3 py-2 text-sm"
-            style="min-height: 80px;"
+            class="form-textarea"
             placeholder="请输入备注信息..."
           />
         </view>
 
         <view class="flex gap-3 mt-4">
-          <button class="flex-1 py-3 bg-white text-gray-600 rounded-xl border border-gray-200 text-sm transition-colors" hover-class="bg-gray-50" :hover-start-time="0" :hover-stay-time="150" @click="closeModal">取消</button>
-          <button class="flex-1 py-3 bg-primary text-white rounded-xl text-sm transition-colors" hover-class="bg-blue-600" :hover-start-time="0" :hover-stay-time="150" @click="submitSchedule">{{ isDetail ? '保存' : '确认' }}</button>
+          <button
+            class="modal-btn modal-btn-cancel"
+            hover-class="modal-btn-cancel-hover"
+            :hover-start-time="0"
+            :hover-stay-time="120"
+            @click="closeModal"
+          >取消</button>
+          <button
+            class="modal-btn modal-btn-primary"
+            hover-class="modal-btn-primary-hover"
+            :hover-start-time="0"
+            :hover-stay-time="120"
+            @click="submitSchedule"
+          >{{ isDetail ? '保存' : '确认' }}</button>
         </view>
       </view>
     </view>
@@ -198,8 +251,6 @@ import { ref, computed, onMounted, watch } from 'vue'
 import StatusBar from '../common/StatusBar.vue'
 import TopBar from '../common/TopBar.vue'
 import EmptyState from '../common/EmptyState.vue'
-import UniList from '../uni-list/uni-list.vue'
-import UniListItem from '../uni-list/uni-list-item.vue'
 import { getSchedulesByDate, createSchedule, updateSchedule, getScheduleList } from '@/api/schedule'
 import { getTeamSimpleList, getTeamMembers } from '@/api/team'
 import { getVehicleSimpleList } from '@/api/vehicle'
@@ -336,19 +387,21 @@ function isSelected(date: any): boolean {
 
 function getStatusText(status: string) {
   const map: Record<string, string> = {
+    idle: '待执行',
     scheduled: '已排班',
+    in_progress: '进行中',
     completed: '已完成',
     cancelled: '已取消',
   }
-  return map[status] || status
-}
-
-function getScheduleIconBgClass(shiftType: string) {
-  return shiftType === 'emergency' ? 'bg-red-50' : 'bg-blue-50'
+  return map[status] || '待执行'
 }
 
-function getScheduleIconClass(shiftType: string) {
-  return shiftType === 'emergency' ? 'uniui-calendar-filled text-red-500' : 'uniui-calendar-filled text-blue-500'
+function getVehicleNamesText(schedule: any): string {
+  const ids = parseVehicleIds(schedule.vehicleIds)
+  if (ids.length === 0) return '未分配车辆'
+  return ids
+    .map(id => vehicleOptions.value.find(v => v.value === id)?.label || String(id))
+    .join('、')
 }
 
 function toggleVehicle(vehicleId: number) {
@@ -458,7 +511,6 @@ async function submitSchedule() {
   try {
     const payload = {
       teamId: form.value.teamId,
-      teamName: form.value.teamName,
       workDate: form.value.workDate,
       shiftType: form.value.shiftType,
       vehicleIds: form.value.vehicleIds.join(','),
@@ -485,6 +537,20 @@ async function fetchSchedules() {
   if (!selectedDate.value) return
   loading.value = true
   try {
+    if (teamOptions.value.length === 0) {
+      try {
+        teamOptions.value = await getTeamSimpleList()
+      } catch (e) {
+        console.error('加载班组失败', e)
+      }
+    }
+    if (vehicleOptions.value.length === 0) {
+      try {
+        vehicleOptions.value = await getVehicleSimpleList()
+      } catch (e) {
+        console.error('加载车辆失败', e)
+      }
+    }
     const [dayRes, monthRes] = await Promise.all([
       getSchedulesByDate(selectedDate.value),
       getScheduleList(currentYear.value, currentMonth.value)
@@ -497,6 +563,15 @@ async function fetchSchedules() {
         allSchedules.push(s)
       }
     })
+    allSchedules.forEach((s: any) => {
+      if (!s.teamName && s.teamId != null) {
+        s.teamName = teamOptions.value.find(t => t.value === Number(s.teamId))?.label || ''
+      }
+      s.vehicleNamesText = getVehicleNamesText(s)
+      s.shiftTypeNorm = normalizeShiftType(s.shiftType)
+      s.shiftTypeText = s.shiftTypeNorm === 'emergency' ? '应急' : '月计划'
+      s.statusText = getStatusText(s.status)
+    })
     schedules.value = allSchedules
   } catch (error) {
     console.error('获取排班列表失败:', error)
@@ -515,8 +590,481 @@ onMounted(() => {
 </script>
 
 <style scoped>
-.scale-down {
-  transition: transform 0.15s ease;
-  transform: scale(0.95);
+/* ==================== 页面外壳 ==================== */
+.schedule-page {
+  position: relative;
+  max-width: 430px;
+  margin: 0 auto;
+  min-height: 100vh;
+  background-color: #f6fbf9; /* 云雾白 */
+}
+
+/* ==================== 顶部 banner(山水背景) ==================== */
+.banner {
+  position: relative;
+  height: 140px;
+  overflow: hidden;
+  background-color: #dff0e8;
+}
+
+.banner-bg {
+  position: absolute;
+  top: 0;
+  left: 0;
+  width: 100%;
+  height: 100%;
+  z-index: 0;
+}
+
+.banner-veil {
+  position: absolute;
+  top: 0;
+  left: 0;
+  width: 100%;
+  height: 100%;
+  z-index: 1;
+  background: linear-gradient(
+    180deg,
+    rgba(255, 255, 255, 0.5) 0%,
+    rgba(255, 255, 255, 0.16) 45%,
+    rgba(246, 251, 249, 0.66) 100%
+  );
+}
+
+.banner-inner {
+  position: relative;
+  z-index: 2;
+  padding-top: 14px;
+  padding-bottom: 24px;
+}
+
+.banner-title {
+  font-size: 20px;
+  font-weight: 800;
+  letter-spacing: 0.5px;
+  color: #1f2937;
+}
+
+.banner-sub {
+  margin-top: 4px;
+  font-size: 12px;
+  color: rgba(31, 41, 37, 0.62);
+}
+
+/* ==================== 内容区 ==================== */
+.content-wrap {
+  position: relative;
+  z-index: 2;
+  margin-top: -18px;
+}
+
+/* 磨砂玻璃卡 */
+.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;
+}
+
+/* ==================== 月份切换 ==================== */
+.month-row {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
+
+.month-arrow {
+  width: 32px;
+  height: 32px;
+  border-radius: 50%;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  background-color: rgba(164, 216, 152, 0.16);
+}
+
+.month-arrow-hover {
+  background-color: rgba(164, 216, 152, 0.3);
+}
+
+.month-arrow-icon {
+  font-size: 16px;
+  color: #368f6f;
+}
+
+.month-text {
+  margin: 0 16px;
+  font-size: 16px;
+  font-weight: 700;
+  color: #1f2937;
+}
+
+/* ==================== 快捷操作 ==================== */
+.action-row {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  gap: 12px;
+  margin-top: 14px;
+}
+
+.action-chip {
+  display: flex;
+  align-items: center;
+  padding: 7px 18px;
+  border-radius: 999px;
+  background-color: #ffffff;
+  border: 1px solid rgba(164, 216, 152, 0.45);
+}
+
+.action-chip-hover {
+  background-color: rgba(164, 216, 152, 0.14);
+}
+
+.action-chip-icon {
+  font-size: 14px;
+  color: #368f6f;
+}
+
+.action-chip-text {
+  font-size: 13px;
+  font-weight: 500;
+  color: #368f6f;
+}
+
+.action-chip-primary {
+  background: linear-gradient(135deg, #368f6f 0%, #4ba98a 100%);
+  border-color: transparent;
+  box-shadow: 0 8px 20px -8px rgba(54, 143, 111, 0.6);
+}
+
+.action-chip-primary-hover {
+  opacity: 0.88;
+}
+
+.action-chip-icon-light {
+  font-size: 14px;
+  color: #ffffff;
+}
+
+.action-chip-text-light {
+  font-size: 13px;
+  font-weight: 600;
+  color: #ffffff;
+}
+
+/* ==================== 日历 ==================== */
+.weekday-text {
+  font-size: 12px;
+  color: #9ca3af;
+}
+
+.day-cell {
+  flex: 1;
+  height: 56px;
+  margin: 0 4px;
+  border-radius: 14px;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+}
+
+.day-other {
+  opacity: 0.3;
+}
+
+.day-today {
+  background-color: rgba(164, 216, 152, 0.2);
+}
+
+.day-selected {
+  background: linear-gradient(135deg, #368f6f 0%, #4ba98a 100%);
+  box-shadow: 0 8px 18px -8px rgba(54, 143, 111, 0.65);
+}
+
+.day-hover {
+  transform: scale(0.94);
+}
+
+.day-num {
+  font-size: 14px;
+  font-weight: 500;
+  color: #1f2937;
+}
+
+.day-num-today {
+  color: #368f6f;
+  font-weight: 700;
+}
+
+.day-num-selected {
+  color: #ffffff;
+  font-weight: 700;
+}
+
+.day-dot {
+  width: 6px;
+  height: 6px;
+  margin-top: 4px;
+  border-radius: 50%;
+  background-color: #368f6f;
+}
+
+.day-dot-light {
+  background-color: #ffffff;
+}
+
+/* ==================== 区块标题栏 ==================== */
+.section-head {
+  display: flex;
+  align-items: center;
+  padding: 16px 16px 12px;
+}
+
+.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;
+}
+
+/* ==================== 列表行 ==================== */
+.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;
+}
+
+.shift-tag {
+  padding: 1px 8px;
+  border-radius: 999px;
+  font-size: 11px;
+  font-weight: 600;
+  color: #368f6f;
+  background-color: rgba(164, 216, 152, 0.24);
+}
+
+.shift-tag-danger {
+  color: #ef4444;
+  background-color: rgba(239, 68, 68, 0.1);
+}
+
+.row-sub {
+  display: block;
+  margin-top: 3px;
+  font-size: 12px;
+  color: #9ca3af;
+}
+
+.row-hover {
+  background-color: rgba(164, 216, 152, 0.1);
+}
+
+.row-arrow {
+  font-size: 16px;
+  color: #c4d0cb;
+  flex-shrink: 0;
+}
+
+.status-text {
+  font-size: 12px;
+  font-weight: 600;
+  color: #368f6f;
+  flex-shrink: 0;
+}
+
+/* 嫩芽浅底图标块 */
+.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);
+}
+
+.icon-chip-danger {
+  background-color: rgba(239, 68, 68, 0.1);
+}
+
+.chip-glyph {
+  font-size: 20px;
+  color: #368f6f;
+}
+
+.chip-glyph-danger {
+  color: #ef4444;
+}
+
+.empty-text {
+  font-size: 13px;
+  color: #9ca3af;
+}
+
+/* ==================== 弹窗 ==================== */
+.schedule-modal {
+  position: fixed;
+  top: 0;
+  right: 0;
+  bottom: 0;
+  left: 0;
+  z-index: 50;
+  display: flex;
+  flex-direction: column;
+  justify-content: flex-end;
+  padding-bottom: var(--window-bottom, 0);
+}
+
+.schedule-modal__overlay {
+  position: absolute;
+  top: 0;
+  right: 0;
+  bottom: 0;
+  left: 0;
+  background-color: rgba(0, 0, 0, 0.5);
+}
+
+.schedule-modal__panel {
+  position: relative;
+  width: 100%;
+  max-height: 60vh;
+  margin-bottom: 100rpx;
+  padding: 32rpx;
+  overflow-y: auto;
+  background-color: #ffffff;
+  border-top-left-radius: 48rpx;
+  border-top-right-radius: 48rpx;
+}
+
+.modal-title {
+  font-size: 16px;
+  font-weight: 700;
+  color: #1f2937;
+}
+
+.modal-close {
+  font-size: 18px;
+  color: #9ca3af;
+}
+
+.form-label {
+  display: block;
+  margin-bottom: 6px;
+  font-size: 13px;
+  font-weight: 500;
+  color: #6b7280;
+}
+
+.form-field {
+  padding: 10px 12px;
+  border-radius: 12px;
+  border: 1px solid #e5e7eb;
+  background-color: #ffffff;
+  font-size: 14px;
+  color: #1f2937;
+}
+
+.form-textarea {
+  width: 100%;
+  min-height: 80px;
+  padding: 10px 12px;
+  border-radius: 12px;
+  border: 1px solid #e5e7eb;
+  background-color: #ffffff;
+  font-size: 14px;
+  color: #1f2937;
+}
+
+.shift-tabs {
+  display: flex;
+  border-radius: 12px;
+  border: 1px solid #e5e7eb;
+  overflow: hidden;
+}
+
+.shift-tab {
+  flex: 1;
+  padding: 10px 0;
+  text-align: center;
+  font-size: 14px;
+  color: #6b7280;
+  background-color: #ffffff;
+}
+
+.shift-tab-active {
+  background: linear-gradient(135deg, #368f6f 0%, #4ba98a 100%);
+  color: #ffffff;
+  font-weight: 600;
+}
+
+.shift-tab-danger {
+  background-color: #ef4444;
+  color: #ffffff;
+  font-weight: 600;
+}
+
+.vehicle-chip {
+  padding: 6px 12px;
+  border-radius: 10px;
+  font-size: 12px;
+  color: #6b7280;
+  background-color: #ffffff;
+  border: 1px solid #e5e7eb;
+}
+
+.vehicle-chip-active {
+  color: #ffffff;
+  background: linear-gradient(135deg, #368f6f 0%, #4ba98a 100%);
+  border-color: transparent;
+}
+
+.modal-btn {
+  flex: 1;
+  padding: 12px 0;
+  border-radius: 14px;
+  font-size: 14px;
+  line-height: 1.4;
+}
+
+.modal-btn-cancel {
+  background-color: #ffffff;
+  color: #6b7280;
+  border: 1px solid #e5e7eb;
+}
+
+.modal-btn-cancel-hover {
+  background-color: #f9fafb;
+}
+
+.modal-btn-primary {
+  background: linear-gradient(135deg, #368f6f 0%, #4ba98a 100%);
+  color: #ffffff;
+  font-weight: 600;
+  box-shadow: 0 10px 24px -10px rgba(54, 143, 111, 0.7);
+}
+
+.modal-btn-primary-hover {
+  opacity: 0.88;
 }
 </style>

+ 493 - 102
src/components/home/ConstructionHome.vue

@@ -1,151 +1,189 @@
 <template>
-  <view class="app-container">
-    <!-- 顶部渐变区域 -->
-    <view class="header-gradient px-4 pt-8 pb-6">
-      <view class="flex items-center justify-between mb-4">
-        <view class="flex items-center">
-          <view class="w-12 h-12 bg-white rounded-full flex items-center justify-center mr-3">
-            <text class="uni-icons uniui-person-filled text-primary text-lg"></text>
+  <view class="construction-home">
+    <!-- 顶部山水背景 banner -->
+    <view class="banner">
+      <image class="banner-bg" src="/static/index-backpage.png" mode="aspectFill" />
+      <view class="banner-veil"></view>
+      <StatusBar />
+      <view class="banner-inner px-4">
+        <view class="flex items-center flex-1 min-w-0">
+          <view class="avatar mr-3">
+            <text class="uni-icons uniui-person-filled avatar-icon"></text>
           </view>
-          <view>
-            <text class="text-blue-100 text-xs block">工程一班</text>
-            <text class="text-white font-medium text-base">张师傅</text>
+          <view class="min-w-0">
+            <text class="greet block">{{ greetText }},欢迎回来</text>
+            <text class="username truncate">{{ userName }}</text>
           </view>
         </view>
         <view
-          class="w-10 h-10 rounded-full flex items-center justify-center relative"
-          style="background-color: rgba(255, 255, 255, 0.2);"
+          class="notify-btn flex-shrink-0"
+          hover-class="notify-hover"
+          :hover-start-time="0"
+          :hover-stay-time="120"
           @click="goToNotification"
         >
-          <text class="uni-icons uniui-notification-filled text-white"></text>
-          <view
-            v-if="notificationCount > 0"
-            class="absolute -top-1 -right-1 min-w-5 h-5 bg-danger rounded-full text-white text-xs flex items-center justify-center px-1"
-          >
-            {{ notificationCount }}
+          <text class="uni-icons uniui-notification-filled notify-icon"></text>
+          <view v-if="notificationCount > 0" class="notify-badge">
+            {{ notificationCount > 99 ? '99+' : notificationCount }}
           </view>
         </view>
       </view>
     </view>
 
-    <view class="px-4 -mt-4 pb-6">
+    <!-- 内容区 -->
+    <view class="content-wrap px-4 pb-24">
       <!-- 统计卡片 -->
-      <view class="card p-4">
+      <view class="glass-card p-4">
         <view class="grid grid-cols-3 gap-3">
-          <GradientStatCard
-            :value="completedCount"
-            label="已完成"
-            gradient="linear-gradient(135deg, #3b82f6 0%, #2563eb 100%)"
+          <view
+            class="stat-card"
+            hover-class="stat-hover"
+            :hover-start-time="0"
+            :hover-stay-time="120"
             @click="goToHistoryTask"
-          />
-          <GradientStatCard
-            :value="currentTaskCount"
-            label="进行中"
-            gradient="linear-gradient(135deg, #60a5fa 0%, #3b82f6 100%)"
+          >
+            <view class="stat-bar"></view>
+            <text class="stat-value">{{ completedCount }}</text>
+            <text class="stat-label">已完成</text>
+          </view>
+          <view
+            class="stat-card"
+            hover-class="stat-hover"
+            :hover-start-time="0"
+            :hover-stay-time="120"
             @click="goToTaskList"
-          />
-          <GradientStatCard
-            :value="tomorrowCount"
-            label="明日任务"
-            gradient="linear-gradient(135deg, #93c5fd 0%, #60a5fa 100%)"
+          >
+            <view class="stat-bar"></view>
+            <text class="stat-value">{{ currentTaskCount }}</text>
+            <text class="stat-label">进行中</text>
+          </view>
+          <view
+            class="stat-card"
+            hover-class="stat-hover"
+            :hover-start-time="0"
+            :hover-stay-time="120"
             @click="goToTomorrowTask"
-          />
+          >
+            <view class="stat-bar"></view>
+            <text class="stat-value">{{ tomorrowCount }}</text>
+            <text class="stat-label">明日任务</text>
+          </view>
         </view>
       </view>
 
       <!-- 每日任务入口 -->
-      <view class="card p-4 flex items-center justify-between active:bg-gray-50" @click="goToDailyTask">
-        <view class="flex items-center">
-          <view class="icon-box mr-4">
-            <text class="uni-icons uniui-gear-filled"></text>
-          </view>
-          <view>
-            <text class="text-base font-bold text-gray-800">每日任务</text>
-            <text class="text-sm text-gray-400 mt-1">今日检查待完成</text>
-          </view>
+      <view
+        class="glass-card reminder-row"
+        hover-class="reminder-hover"
+        :hover-start-time="0"
+        :hover-stay-time="120"
+        @click="goToDailyTask"
+      >
+        <view class="icon-chip mr-3">
+          <text class="uni-icons uniui-gear-filled chip-glyph"></text>
         </view>
-        <view class="flex items-center">
-          <view class="w-2 h-2 bg-danger rounded-full mr-2"></view>
-          <text class="uni-icons uniui-arrowright text-gray-400"></text>
+        <view class="flex-1 min-w-0">
+          <text class="reminder-text block">每日任务</text>
+          <text class="row-sub truncate">今日检查待完成</text>
         </view>
+        <view class="reminder-dot mr-2"></view>
+        <text class="uni-icons uniui-arrowright row-arrow"></text>
       </view>
 
-      <!-- 快捷功能 -->
-      <view class="card p-4">
+      <!-- 快捷入口 -->
+      <view class="glass-card p-4">
         <view class="grid grid-cols-3 gap-3">
-          <QuickEntry
-            label="我的任务"
-            icon="uniui-flag-filled"
+          <view
+            class="entry-card"
+            hover-class="entry-hover"
+            :hover-start-time="0"
+            :hover-stay-time="120"
             @click="goToTaskList"
-          />
-          <QuickEntry
-            label="车辆检查"
-            icon="uniui-gear-filled"
+          >
+            <view class="icon-chip">
+              <text class="uni-icons uniui-flag-filled chip-glyph"></text>
+            </view>
+            <text class="entry-label">我的任务</text>
+          </view>
+          <view
+            class="entry-card"
+            hover-class="entry-hover"
+            :hover-start-time="0"
+            :hover-stay-time="120"
             @click="goToDailyTask"
-          />
-          <QuickEntry
-            label="帮助中心"
-            icon="uniui-help-filled"
+          >
+            <view class="icon-chip">
+              <text class="uni-icons uniui-gear-filled chip-glyph"></text>
+            </view>
+            <text class="entry-label">车辆检查</text>
+          </view>
+          <view
+            class="entry-card"
+            hover-class="entry-hover"
+            :hover-start-time="0"
+            :hover-stay-time="120"
             @click="goToHelpCenter"
-          />
+          >
+            <view class="icon-chip">
+              <text class="uni-icons uniui-help-filled chip-glyph"></text>
+            </view>
+            <text class="entry-label">帮助中心</text>
+          </view>
         </view>
       </view>
 
       <!-- 当前任务 -->
-      <view class="card">
-        <view class="flex items-center justify-between p-4 border-b border-gray-100">
-          <text class="text-base font-semibold text-gray-800">当前任务</text>
-          <text class="text-sm text-primary" @click="goToTaskList">查看全部</text>
+      <view class="glass-card">
+        <view class="section-head">
+          <view class="section-bar mr-2"></view>
+          <text class="section-title flex-1">当前任务</text>
+          <text class="section-more" @click="goToTaskList">查看全部</text>
         </view>
         <view v-if="currentTasks.length === 0" class="py-6 text-center">
-          <text class="text-desc">暂无进行中的任务</text>
+          <text class="empty-text">暂无进行中的任务</text>
         </view>
         <view v-else>
           <view
             v-for="task in currentTasks"
-            :key="task.id"
-            class="p-4 border-b border-gray-100 active:bg-gray-50"
-            @click="goToTaskDetail(task.id)"
+            :key="taskIdOf(task)"
+            class="list-row"
+            hover-class="row-hover"
+            :hover-start-time="0"
+            :hover-stay-time="120"
+            @click="goToTaskDetail(taskIdOf(task))"
           >
-            <view class="flex justify-between items-start mb-2">
-              <text class="text-sm font-semibold text-gray-800">{{ task.projectName }}</text>
-              <StatusTag :status="task.status" />
+            <view class="icon-chip mr-3">
+              <text class="uni-icons uniui-tools chip-glyph"></text>
             </view>
-            <view class="flex items-center text-xs text-gray-500 mb-1">
-              <text class="uni-icons uniui-tools text-gray-400 mr-1"></text>
-              <text>{{ task.serviceType || '施工服务' }}</text>
-            </view>
-            <view class="flex items-center text-xs text-gray-500">
-              <text class="uni-icons uniui-clock-filled text-gray-400 mr-1"></text>
-              <text>{{ task.scheduleDate }} {{ task.scheduleTime }}</text>
+            <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.serviceType || task.serviceContent || '施工服务' }}</text>
+              <text class="row-sub truncate">{{ task.planDate || task.scheduleDate }} {{ task.scheduleTime }}</text>
             </view>
+            <StatusTag :status="task.status" />
           </view>
         </view>
       </view>
 
       <!-- 通知公告 -->
-      <view class="card">
-        <view class="flex items-center justify-between p-4 border-b border-gray-100">
-          <text class="text-base font-semibold text-gray-800">通知公告</text>
-          <text class="text-sm text-primary" @click="goToNoticeList">查看全部</text>
+      <view class="glass-card">
+        <view class="section-head">
+          <view class="section-bar mr-2"></view>
+          <text class="section-title flex-1">通知公告</text>
+          <text class="section-more" @click="goToNoticeList">更多</text>
         </view>
         <view
           v-for="(notice, index) in notices"
           :key="index"
-          class="p-4 border-b border-gray-100 flex items-center active:bg-gray-50"
+          class="list-row"
+          hover-class="row-hover"
+          :hover-start-time="0"
+          :hover-stay-time="120"
           @click="goToNoticeDetail"
         >
-          <view
-            v-if="notice.important"
-            class="px-2 py-1 bg-danger-light text-danger rounded-lg font-medium mr-2 flex-shrink-0"
-            style="font-size: 10px"
-          >
-            重要
-          </view>
-          <view class="flex-1">
-            <text class="text-sm text-gray-700">{{ notice.title }}</text>
-          </view>
-          <text class="text-xs text-gray-400 ml-2">{{ notice.date }}</text>
+          <view class="dot mr-3" :class="notice.important ? 'dot-active' : 'dot-muted'"></view>
+          <text class="flex-1 row-title truncate mr-2">{{ notice.title }}</text>
+          <text class="row-date flex-shrink-0">{{ notice.date }}</text>
         </view>
       </view>
     </view>
@@ -154,24 +192,39 @@
 
 <script setup lang="ts">
 import { ref, computed, onMounted } from 'vue'
+import { useAuthStore } from '@/stores/auth'
 import { useTaskStore } from '@/stores/task'
-import GradientStatCard from '@/components/common/GradientStatCard.vue'
-import QuickEntry from '@/components/common/QuickEntry.vue'
+import StatusBar from '@/components/common/StatusBar.vue'
 import StatusTag from '@/components/common/StatusTag.vue'
 
+const authStore = useAuthStore()
 const taskStore = useTaskStore()
 
+const userName = ref(authStore.user?.name || '张师傅')
 const notificationCount = ref(1)
 const tomorrowCount = ref(2)
 
 const completedCount = computed(() => taskStore.tasks.filter(t => t.status === 'completed').length)
 const currentTaskCount = computed(() =>
-  taskStore.tasks.filter(t => ['scheduled', 'departed', 'arrived', 'constructing'].includes(t.status)).length
+  taskStore.tasks.filter(t => ['assigned', 'in_progress'].includes(t.status)).length
 )
 const currentTasks = computed(() =>
-  taskStore.tasks.filter(t => ['scheduled', 'departed', 'arrived', 'constructing'].includes(t.status)).slice(0, 2)
+  taskStore.tasks.filter(t => ['assigned', 'in_progress'].includes(t.status)).slice(0, 2)
 )
 
+function taskIdOf(task: any): string {
+  return String(task?.id ?? task?.taskId ?? '')
+}
+
+const greetText = computed(() => {
+  const hour = new Date().getHours()
+  if (hour < 6) return '凌晨好'
+  if (hour < 12) return '上午好'
+  if (hour < 14) return '中午好'
+  if (hour < 18) return '下午好'
+  return '晚上好'
+})
+
 const notices = [
   { title: '关于规范施工现场安全管理的通知', date: '2026-04-20', important: true },
   { title: '下周安全培训会议安排', date: '2026-04-18', important: false },
@@ -202,8 +255,8 @@ function goToNotification() {
 }
 
 function goToTaskDetail(taskId: string) {
-  taskStore.setCurrentTask(taskId)
-  uni.navigateTo({ url: '/subPackages/pages-construction/taskDetail' })
+  if (!taskId) return
+  uni.navigateTo({ url: `/subPackages/pages-construction/taskDetail?id=${taskId}` })
 }
 
 function goToNoticeList() {
@@ -215,6 +268,344 @@ function goToNoticeDetail() {
 }
 
 onMounted(() => {
-  taskStore.fetchTasks()
+  const today = new Date().toISOString().split('T')[0]
+  taskStore.fetchMyTasks(today)
 })
 </script>
+
+<style scoped>
+/* ==================== 页面外壳 ==================== */
+.construction-home {
+  position: relative;
+  max-width: 430px;
+  margin: 0 auto;
+  min-height: 100vh;
+  background-color: #f6fbf9; /* 云雾白 */
+}
+
+/* ==================== 顶部 banner(山水背景) ==================== */
+.banner {
+  position: relative;
+  height: 168px;
+  overflow: hidden;
+  background-color: #dff0e8; /* 图片加载前的浅青绿兜底 */
+}
+
+.banner-bg {
+  position: absolute;
+  top: 0;
+  left: 0;
+  width: 100%;
+  height: 100%;
+  z-index: 0;
+}
+
+/* 蒙版:顶部提亮保文字、底部淡出衔接云雾白 */
+.banner-veil {
+  position: absolute;
+  top: 0;
+  left: 0;
+  width: 100%;
+  height: 100%;
+  z-index: 1;
+  background: linear-gradient(
+    180deg,
+    rgba(255, 255, 255, 0.5) 0%,
+    rgba(255, 255, 255, 0.16) 45%,
+    rgba(246, 251, 249, 0.66) 100%
+  );
+}
+
+.banner-inner {
+  position: relative;
+  z-index: 2;
+  display: flex;
+  align-items: center;
+  padding-top: 14px;
+  padding-bottom: 24px;
+}
+
+.avatar {
+  width: 48px;
+  height: 48px;
+  border-radius: 50%;
+  background-color: rgba(255, 255, 255, 0.9);
+  backdrop-filter: blur(8px);
+  -webkit-backdrop-filter: blur(8px);
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  box-shadow: 0 8px 22px -8px rgba(31, 41, 37, 0.4);
+  flex-shrink: 0;
+}
+
+.avatar-icon {
+  font-size: 24px;
+  color: #368f6f;
+}
+
+.greet {
+  font-size: 12px;
+  color: rgba(31, 41, 37, 0.62);
+}
+
+.username {
+  display: block;
+  margin-top: 2px;
+  font-size: 18px;
+  font-weight: 800;
+  letter-spacing: 0.5px;
+  color: #1f2937;
+}
+
+/* 通知铃铛 */
+.notify-btn {
+  position: relative;
+  width: 40px;
+  height: 40px;
+  border-radius: 50%;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  background-color: rgba(255, 255, 255, 0.6);
+  backdrop-filter: blur(8px);
+  -webkit-backdrop-filter: blur(8px);
+  box-shadow: 0 6px 18px -8px rgba(31, 41, 37, 0.35);
+}
+
+.notify-icon {
+  font-size: 20px;
+  color: #368f6f;
+}
+
+.notify-badge {
+  position: absolute;
+  top: -4px;
+  right: -4px;
+  min-width: 18px;
+  height: 18px;
+  padding: 0 4px;
+  border-radius: 9px;
+  background-color: #ef4444;
+  border: 1.5px solid rgba(255, 255, 255, 0.9);
+  color: #ffffff;
+  font-size: 10px;
+  font-weight: 700;
+  line-height: 15px;
+  text-align: center;
+}
+
+.notify-hover {
+  background-color: rgba(255, 255, 255, 0.88);
+}
+
+/* ==================== 内容区(轻微上叠 banner) ==================== */
+.content-wrap {
+  position: relative;
+  z-index: 2;
+  margin-top: -18px;
+}
+
+/* 磨砂玻璃卡 */
+.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;
+}
+
+/* ==================== 统计卡 ==================== */
+.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;
+}
+
+.stat-hover {
+  transform: scale(0.97);
+  background-color: rgba(164, 216, 152, 0.24);
+}
+
+/* ==================== 每日任务入口 ==================== */
+.reminder-row {
+  display: flex;
+  align-items: center;
+  padding: 14px 16px;
+}
+
+.reminder-text {
+  font-size: 15px;
+  font-weight: 600;
+  color: #1f2937;
+}
+
+.reminder-dot {
+  width: 8px;
+  height: 8px;
+  border-radius: 50%;
+  flex-shrink: 0;
+  background-color: #ef4444;
+}
+
+.row-arrow {
+  font-size: 16px;
+  color: #c4d0cb;
+}
+
+.reminder-hover {
+  background-color: rgba(164, 216, 152, 0.12);
+}
+
+/* ==================== 快捷入口 ==================== */
+.entry-card {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+  padding: 14px 4px 12px;
+  border-radius: 16px;
+}
+
+.entry-label {
+  margin-top: 8px;
+  font-size: 13px;
+  font-weight: 500;
+  color: #374151;
+}
+
+.entry-hover {
+  background-color: rgba(164, 216, 152, 0.14);
+  transform: scale(0.97);
+}
+
+/* 嫩芽浅底图标块 */
+.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;
+}
+
+/* ==================== 区块标题栏 ==================== */
+.section-head {
+  display: flex;
+  align-items: center;
+  padding: 16px 16px 12px;
+}
+
+.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;
+}
+
+.section-more {
+  font-size: 13px;
+  color: #368f6f;
+}
+
+/* ==================== 列表行 ==================== */
+.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-date {
+  font-size: 12px;
+  color: #9ca3af;
+}
+
+.row-hover {
+  background-color: rgba(164, 216, 152, 0.1);
+}
+
+/* 通知圆点 */
+.dot {
+  width: 8px;
+  height: 8px;
+  border-radius: 50%;
+  flex-shrink: 0;
+}
+
+.dot-active {
+  background-color: #368f6f;
+  box-shadow: 0 0 0 3px rgba(164, 216, 152, 0.3);
+}
+
+.dot-muted {
+  background-color: #c4d0cb;
+}
+
+.empty-text {
+  font-size: 13px;
+  color: #9ca3af;
+}
+</style>

+ 423 - 76
src/components/home/DispatchHome.vue

@@ -1,112 +1,159 @@
 <template>
-  <view class="app-container">
-    <!-- 顶部渐变区域 -->
-    <view class="header-gradient px-4 pt-8 pb-6">
-      <view class="flex items-center justify-between mb-4">
-        <view class="flex items-center">
-          <view class="w-12 h-12 bg-white rounded-full flex items-center justify-center mr-3">
-            <text class="uni-icons uniui-person-filled text-primary text-lg"></text>
+  <view class="dispatch-home">
+    <!-- 顶部山水背景 banner -->
+    <view class="banner">
+      <image class="banner-bg" src="/static/index-backpage.png" mode="aspectFill" />
+      <view class="banner-veil"></view>
+      <StatusBar />
+      <view class="banner-inner px-4">
+        <view class="flex items-center flex-1 min-w-0">
+          <view class="avatar mr-3">
+            <text class="uni-icons uniui-person-filled avatar-icon"></text>
           </view>
-          <view>
-            <text class="text-blue-100 text-xs block">调度中心</text>
-            <text class="text-white font-medium text-base">控制台</text>
+          <view class="min-w-0">
+            <text class="greet block">{{ greetText }},欢迎回来</text>
+            <text class="username truncate">{{ userName }}</text>
           </view>
         </view>
         <view
-          class="w-10 h-10 rounded-full flex items-center justify-center relative"
-          style="background-color: rgba(255, 255, 255, 0.2);"
+          class="notify-btn flex-shrink-0"
+          hover-class="notify-hover"
+          :hover-start-time="0"
+          :hover-stay-time="120"
           @click="goToNotification"
         >
-          <text class="uni-icons uniui-notification-filled text-white"></text>
-          <view
-            v-if="notificationCount > 0"
-            class="absolute -top-1 -right-1 min-w-5 h-5 bg-danger rounded-full text-white text-xs flex items-center justify-center px-1"
-          >
-            {{ notificationCount }}
+          <text class="uni-icons uniui-notification-filled notify-icon"></text>
+          <view v-if="notificationCount > 0" class="notify-badge">
+            {{ notificationCount > 99 ? '99+' : notificationCount }}
           </view>
         </view>
       </view>
     </view>
 
-    <view class="px-4 -mt-4 pb-6">
+    <!-- 内容区 -->
+    <view class="content-wrap px-4 pb-24">
       <!-- 统计卡片 -->
-      <view class="card p-4">
+      <view class="glass-card p-4">
         <view class="grid grid-cols-3 gap-3">
-          <GradientStatCard
-            :value="taskStore.total"
-            label="今日任务"
-            gradient="linear-gradient(135deg, #667eea 0%, #764ba2 100%)"
+          <view
+            class="stat-card"
+            hover-class="stat-hover"
+            :hover-start-time="0"
+            :hover-stay-time="120"
             @click="goToTaskList"
-          />
-          <GradientStatCard
-            :value="taskStore.pendingCount"
-            label="待排班"
-            gradient="linear-gradient(135deg, #4facfe 0%, #00f2fe 100%)"
+          >
+            <view class="stat-bar"></view>
+            <text class="stat-value">{{ taskStore.total }}</text>
+            <text class="stat-label">今日任务</text>
+          </view>
+          <view
+            class="stat-card"
+            hover-class="stat-hover"
+            :hover-start-time="0"
+            :hover-stay-time="120"
             @click="goToTaskList"
-          />
-          <GradientStatCard
-            :value="emergencyCount"
-            label="应急中"
-            gradient="linear-gradient(135deg, #f093fb 0%, #f5576c 100%)"
+          >
+            <view class="stat-bar"></view>
+            <text class="stat-value">{{ taskStore.pendingCount }}</text>
+            <text class="stat-label">待排班</text>
+          </view>
+          <view
+            class="stat-card"
+            hover-class="stat-hover"
+            :hover-start-time="0"
+            :hover-stay-time="120"
             @click="goToEmergencyTeam"
-          />
+          >
+            <view class="stat-bar"></view>
+            <text class="stat-value">{{ emergencyCount }}</text>
+            <text class="stat-label">应急中</text>
+          </view>
         </view>
       </view>
 
-      <!-- 快捷操作 -->
-      <view class="card p-4">
+      <!-- 快捷入口 -->
+      <view class="glass-card p-4">
         <view class="grid grid-cols-4 gap-3">
-          <QuickEntry
-            label="排班管理"
-            icon="uniui-calendar-filled"
+          <view
+            class="entry-card"
+            hover-class="entry-hover"
+            :hover-start-time="0"
+            :hover-stay-time="120"
             @click="goToSchedule"
-          />
-          <QuickEntry
-            label="任务列表"
-            icon="uniui-flag-filled"
+          >
+            <view class="icon-chip">
+              <text class="uni-icons uniui-calendar-filled chip-glyph"></text>
+            </view>
+            <text class="entry-label">排班管理</text>
+          </view>
+          <view
+            class="entry-card"
+            hover-class="entry-hover"
+            :hover-start-time="0"
+            :hover-stay-time="120"
             @click="goToTaskList"
-          />
-          <QuickEntry
-            label="车辆管理"
-            icon="uniui-cart-filled"
+          >
+            <view class="icon-chip">
+              <text class="uni-icons uniui-flag-filled chip-glyph"></text>
+            </view>
+            <text class="entry-label">任务列表</text>
+          </view>
+          <view
+            class="entry-card"
+            hover-class="entry-hover"
+            :hover-start-time="0"
+            :hover-stay-time="120"
             @click="goToVehicleList"
-          />
-          <QuickEntry
-            label="可视化"
-            icon="uniui-map-filled"
+          >
+            <view class="icon-chip">
+              <text class="uni-icons uniui-cart-filled chip-glyph"></text>
+            </view>
+            <text class="entry-label">车辆管理</text>
+          </view>
+          <view
+            class="entry-card"
+            hover-class="entry-hover"
+            :hover-start-time="0"
+            :hover-stay-time="120"
             @click="goToVisualization"
-          />
+          >
+            <view class="icon-chip">
+              <text class="uni-icons uniui-map-filled chip-glyph"></text>
+            </view>
+            <text class="entry-label">可视化</text>
+          </view>
         </view>
       </view>
 
       <!-- 待审核任务 -->
-      <view class="card">
-        <view class="flex items-center justify-between p-4 border-b border-gray-100">
-          <text class="text-base font-semibold text-gray-800">待审核任务</text>
-          <text class="text-sm text-primary" @click="goToTaskList">查看全部</text>
+      <view class="glass-card">
+        <view class="section-head">
+          <view class="section-bar mr-2"></view>
+          <text class="section-title flex-1">待审核任务</text>
+          <text class="section-more" @click="goToTaskList">查看全部</text>
         </view>
         <view v-if="pendingTasks.length === 0" class="py-6 text-center">
-          <text class="text-desc">暂无待审核任务</text>
+          <text class="empty-text">暂无待审核任务</text>
         </view>
         <view v-else>
           <view
             v-for="task in pendingTasks"
-            :key="task.id"
-            class="p-4 border-b border-gray-100 active:bg-gray-50"
-            @click="goToTaskDetail(task.id)"
+            :key="taskIdOf(task)"
+            class="list-row"
+            hover-class="row-hover"
+            :hover-start-time="0"
+            :hover-stay-time="120"
+            @click="goToTaskDetail(taskIdOf(task))"
           >
-            <view class="flex justify-between items-start mb-2">
-              <text class="text-sm font-semibold text-gray-800">{{ task.projectName }}</text>
-              <StatusTag :status="task.status" />
+            <view class="icon-chip mr-3">
+              <text class="uni-icons uniui-flag chip-glyph"></text>
             </view>
-            <view class="flex items-center text-xs text-gray-500 mb-1">
-              <text class="uni-icons uniui-location-filled text-gray-400 mr-1"></text>
-              <text class="truncate">{{ task.address || '暂无地址' }}</text>
-            </view>
-            <view class="flex items-center text-xs text-gray-500">
-              <text class="uni-icons uniui-clock-filled text-gray-400 mr-1"></text>
-              <text>申请时间:{{ task.applyTime || task.createTime }}</text>
+            <view class="flex-1 min-w-0 mr-2">
+              <text class="row-title truncate">{{ task.projectName }}</text>
+              <text class="row-sub truncate">{{ task.address || '暂无地址' }}</text>
+              <text class="row-sub truncate">申请时间:{{ task.applyTime || task.createTime }}</text>
             </view>
+            <StatusTag :status="task.status" />
           </view>
         </view>
       </view>
@@ -116,25 +163,40 @@
 
 <script setup lang="ts">
 import { ref, computed, onMounted } from 'vue'
+import { useAuthStore } from '@/stores/auth'
 import { useTaskStore } from '@/stores/task'
-import GradientStatCard from '@/components/common/GradientStatCard.vue'
-import QuickEntry from '@/components/common/QuickEntry.vue'
+import StatusBar from '@/components/common/StatusBar.vue'
 import StatusTag from '@/components/common/StatusTag.vue'
 
+const authStore = useAuthStore()
 const taskStore = useTaskStore()
 
+const userName = ref(authStore.user?.name || '调度员')
 const notificationCount = ref(2)
 
 const emergencyCount = computed(() => taskStore.tasks.filter(t => t.type === 'emergency').length)
-const pendingTasks = computed(() => taskStore.tasks.filter(t => t.status === 'pending').slice(0, 3))
+const pendingTasks = computed(() => taskStore.tasks.filter(t => ['pending', 'auditing'].includes(t.status)).slice(0, 3))
+
+function taskIdOf(task: any): string {
+  return String(task?.id ?? task?.taskId ?? '')
+}
+
+const greetText = computed(() => {
+  const hour = new Date().getHours()
+  if (hour < 6) return '凌晨好'
+  if (hour < 12) return '上午好'
+  if (hour < 14) return '中午好'
+  if (hour < 18) return '下午好'
+  return '晚上好'
+})
 
 function goToTaskList() {
   uni.switchTab({ url: '/pages/task/task' })
 }
 
 function goToTaskDetail(taskId: string) {
-  taskStore.setCurrentTask(taskId)
-  uni.navigateTo({ url: '/subPackages/pages-dispatch/taskDetail' })
+  if (!taskId) return
+  uni.navigateTo({ url: `/subPackages/pages-dispatch/taskDetail?id=${taskId}` })
 }
 
 function goToSchedule() {
@@ -161,3 +223,288 @@ onMounted(() => {
   taskStore.fetchTasks()
 })
 </script>
+
+<style scoped>
+/* ==================== 页面外壳 ==================== */
+.dispatch-home {
+  position: relative;
+  max-width: 430px;
+  margin: 0 auto;
+  min-height: 100vh;
+  background-color: #f6fbf9; /* 云雾白 */
+}
+
+/* ==================== 顶部 banner(山水背景) ==================== */
+.banner {
+  position: relative;
+  height: 168px;
+  overflow: hidden;
+  background-color: #dff0e8; /* 图片加载前的浅青绿兜底 */
+}
+
+.banner-bg {
+  position: absolute;
+  top: 0;
+  left: 0;
+  width: 100%;
+  height: 100%;
+  z-index: 0;
+}
+
+/* 蒙版:顶部提亮保文字、底部淡出衔接云雾白 */
+.banner-veil {
+  position: absolute;
+  top: 0;
+  left: 0;
+  width: 100%;
+  height: 100%;
+  z-index: 1;
+  background: linear-gradient(
+    180deg,
+    rgba(255, 255, 255, 0.5) 0%,
+    rgba(255, 255, 255, 0.16) 45%,
+    rgba(246, 251, 249, 0.66) 100%
+  );
+}
+
+.banner-inner {
+  position: relative;
+  z-index: 2;
+  display: flex;
+  align-items: center;
+  padding-top: 14px;
+  padding-bottom: 24px;
+}
+
+.avatar {
+  width: 48px;
+  height: 48px;
+  border-radius: 50%;
+  background-color: rgba(255, 255, 255, 0.9);
+  backdrop-filter: blur(8px);
+  -webkit-backdrop-filter: blur(8px);
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  box-shadow: 0 8px 22px -8px rgba(31, 41, 37, 0.4);
+  flex-shrink: 0;
+}
+
+.avatar-icon {
+  font-size: 24px;
+  color: #368f6f;
+}
+
+.greet {
+  font-size: 12px;
+  color: rgba(31, 41, 37, 0.62);
+}
+
+.username {
+  display: block;
+  margin-top: 2px;
+  font-size: 18px;
+  font-weight: 800;
+  letter-spacing: 0.5px;
+  color: #1f2937;
+}
+
+/* 通知铃铛 */
+.notify-btn {
+  position: relative;
+  width: 40px;
+  height: 40px;
+  border-radius: 50%;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  background-color: rgba(255, 255, 255, 0.6);
+  backdrop-filter: blur(8px);
+  -webkit-backdrop-filter: blur(8px);
+  box-shadow: 0 6px 18px -8px rgba(31, 41, 37, 0.35);
+}
+
+.notify-icon {
+  font-size: 20px;
+  color: #368f6f;
+}
+
+.notify-badge {
+  position: absolute;
+  top: -4px;
+  right: -4px;
+  min-width: 18px;
+  height: 18px;
+  padding: 0 4px;
+  border-radius: 9px;
+  background-color: #ef4444;
+  border: 1.5px solid rgba(255, 255, 255, 0.9);
+  color: #ffffff;
+  font-size: 10px;
+  font-weight: 700;
+  line-height: 15px;
+  text-align: center;
+}
+
+.notify-hover {
+  background-color: rgba(255, 255, 255, 0.88);
+}
+
+/* ==================== 内容区(轻微上叠 banner) ==================== */
+.content-wrap {
+  position: relative;
+  z-index: 2;
+  margin-top: -18px;
+}
+
+/* 磨砂玻璃卡 */
+.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;
+}
+
+/* ==================== 统计卡 ==================== */
+.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;
+}
+
+.stat-hover {
+  transform: scale(0.97);
+  background-color: rgba(164, 216, 152, 0.24);
+}
+
+/* ==================== 快捷入口 ==================== */
+.entry-card {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+  padding: 14px 4px 12px;
+  border-radius: 16px;
+}
+
+.entry-label {
+  margin-top: 8px;
+  font-size: 13px;
+  font-weight: 500;
+  color: #374151;
+}
+
+.entry-hover {
+  background-color: rgba(164, 216, 152, 0.14);
+  transform: scale(0.97);
+}
+
+/* 嫩芽浅底图标块 */
+.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;
+}
+
+/* ==================== 区块标题栏 ==================== */
+.section-head {
+  display: flex;
+  align-items: center;
+  padding: 16px 16px 12px;
+}
+
+.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;
+}
+
+.section-more {
+  font-size: 13px;
+  color: #368f6f;
+}
+
+/* ==================== 列表行 ==================== */
+.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);
+}
+
+.empty-text {
+  font-size: 13px;
+  color: #9ca3af;
+}
+</style>

+ 492 - 87
src/components/home/SalesHome.vue

@@ -1,112 +1,160 @@
 <template>
-  <view class="app-container">
-    <!-- 顶部渐变区域 -->
-    <view class="header-gradient px-4 pt-8 pb-6">
-      <view class="flex items-center justify-between mb-4">
-        <view class="flex items-center">
-          <view class="w-12 h-12 bg-white rounded-full flex items-center justify-center mr-3">
-            <text class="uni-icons uniui-person-filled text-primary text-lg"></text>
+  <view class="sales-home">
+    <!-- 顶部山水背景 banner -->
+    <view class="banner">
+      <image class="banner-bg" src="/static/index-backpage.png" mode="aspectFill" />
+      <view class="banner-veil"></view>
+      <StatusBar />
+      <view class="banner-inner px-4">
+        <view class="flex items-center flex-1 min-w-0">
+          <view class="avatar mr-3">
+            <text class="uni-icons uniui-person-filled avatar-icon"></text>
           </view>
-          <view>
-            <text class="text-blue-100 text-xs block">下午好,欢迎回来</text>
-            <text class="text-white font-medium text-base">{{ userName }}</text>
+          <view class="min-w-0">
+            <text class="greet block">{{ greetText }},欢迎回来</text>
+            <text class="username truncate">{{ userName }}</text>
           </view>
         </view>
         <view
-          class="w-10 h-10 rounded-full flex items-center justify-center relative"
-          style="background-color: rgba(255, 255, 255, 0.2);"
+          class="notify-btn flex-shrink-0"
+          hover-class="notify-hover"
+          :hover-start-time="0"
+          :hover-stay-time="120"
           @click="goToNotification"
         >
-          <text class="uni-icons uniui-notification-filled text-white"></text>
-          <view
-            v-if="notificationCount > 0"
-            class="absolute -top-1 -right-1 min-w-5 h-5 bg-danger rounded-full text-white text-xs flex items-center justify-center px-1"
-          >
-            {{ notificationCount }}
+          <text class="uni-icons uniui-notification-filled notify-icon"></text>
+          <view v-if="notificationCount > 0" class="notify-badge">
+            {{ notificationCount > 99 ? '99+' : notificationCount }}
           </view>
         </view>
       </view>
     </view>
 
-    <view class="px-4 -mt-4 pb-6">
+    <!-- 内容区 -->
+    <view class="content-wrap px-4 pb-24">
       <!-- 统计卡片 -->
-      <view class="card p-4">
+      <view class="glass-card p-4">
         <view class="grid grid-cols-3 gap-3">
-          <GradientStatCard
-            :value="projectStore.totalCount"
-            label="本月项目"
-            gradient="linear-gradient(135deg, #667eea 0%, #764ba2 100%)"
+          <view
+            class="stat-card"
+            hover-class="stat-hover"
+            :hover-start-time="0"
+            :hover-stay-time="120"
             @click="goToProjectList"
-          />
-          <GradientStatCard
-            :value="taskStore.pendingCount"
-            label="待确认"
-            gradient="linear-gradient(135deg, #f093fb 0%, #f5576c 100%)"
+          >
+            <view class="stat-bar"></view>
+            <text class="stat-value">{{ projectStore.totalCount }}</text>
+            <text class="stat-label">本月项目</text>
+          </view>
+          <view
+            class="stat-card"
+            hover-class="stat-hover"
+            :hover-start-time="0"
+            :hover-stay-time="120"
             @click="goToTaskList"
-          />
-          <GradientStatCard
-            :value="taskStore.todayCount"
-            label="进行中"
-            gradient="linear-gradient(135deg, #4facfe 0%, #00f2fe 100%)"
+          >
+            <view class="stat-bar"></view>
+            <text class="stat-value">{{ taskStore.pendingCount }}</text>
+            <text class="stat-label">待确认</text>
+          </view>
+          <view
+            class="stat-card"
+            hover-class="stat-hover"
+            :hover-start-time="0"
+            :hover-stay-time="120"
             @click="goToTaskList"
-          />
+          >
+            <view class="stat-bar"></view>
+            <text class="stat-value">{{ taskStore.todayCount }}</text>
+            <text class="stat-label">进行中</text>
+          </view>
         </view>
       </view>
 
       <!-- 快捷入口 -->
-      <view class="card p-4">
+      <view class="glass-card p-4">
         <view class="grid grid-cols-3 gap-3">
-          <QuickEntry
-            label="项目库"
-            icon="uniui-folder-add-filled"
+          <view
+            class="entry-card"
+            hover-class="entry-hover"
+            :hover-start-time="0"
+            :hover-stay-time="120"
             @click="goToProjectList"
-          />
-          <QuickEntry
-            label="客户管理"
-            icon="uniui-personadd-filled"
+          >
+            <view class="icon-chip">
+              <text class="uni-icons uniui-folder-add chip-glyph"></text>
+            </view>
+            <text class="entry-label">项目库</text>
+          </view>
+          <view
+            class="entry-card"
+            hover-class="entry-hover"
+            :hover-start-time="0"
+            :hover-stay-time="120"
             @click="goToCustomer"
-          />
-          <QuickEntry
-            label="合同管理"
-            icon="uniui-file-text-filled"
+          >
+            <view class="icon-chip">
+              <text class="uni-icons uniui-personadd chip-glyph"></text>
+            </view>
+            <text class="entry-label">客户管理</text>
+          </view>
+          <view
+            class="entry-card"
+            hover-class="entry-hover"
+            :hover-start-time="0"
+            :hover-stay-time="120"
             @click="goToContract"
-          />
+          >
+            <view class="icon-chip">
+              <text class="uni-icons uniui-compose chip-glyph"></text>
+            </view>
+            <text class="entry-label">合同管理</text>
+          </view>
         </view>
       </view>
 
       <!-- 任务提醒入口 -->
       <view
-        class="card p-4 flex items-center justify-center"
+        class="glass-card reminder-row"
+        hover-class="reminder-hover"
+        :hover-start-time="0"
+        :hover-stay-time="120"
         @click="goToTaskReminder"
       >
-        <text class="uni-icons uniui-notification-filled mr-2 text-gray-500"></text>
-        <text class="text-sm text-gray-600 font-medium">任务提醒 ({{ taskStore.pendingCount }})</text>
+        <view class="icon-chip mr-3">
+          <text class="uni-icons uniui-notification-filled chip-glyph"></text>
+        </view>
+        <text class="flex-1 reminder-text">任务提醒</text>
+        <view class="reminder-count">{{ taskStore.pendingCount }}</view>
+        <text class="uni-icons uniui-arrowright row-arrow ml-2"></text>
       </view>
 
       <!-- 最新任务 -->
-      <view class="card">
-        <view class="flex items-center justify-between p-4 border-b border-gray-100">
-          <text class="text-base font-semibold text-gray-800">最新任务</text>
-          <text class="text-sm text-primary" @click="goToTaskList">查看全部</text>
+      <view class="glass-card">
+        <view class="section-head">
+          <view class="section-bar mr-2"></view>
+          <text class="section-title flex-1">最新任务</text>
+          <text class="section-more" @click="goToTaskList">查看全部</text>
         </view>
         <view v-if="taskStore.tasks.length === 0" class="py-6 text-center">
-          <text class="text-desc">暂无任务</text>
+          <text class="empty-text">暂无任务</text>
         </view>
         <view v-else>
           <view
             v-for="task in latestTasks"
             :key="task.id"
-            class="p-4 border-b border-gray-100 flex items-center justify-between active:bg-gray-50"
+            class="list-row"
+            hover-class="row-hover"
+            :hover-start-time="0"
+            :hover-stay-time="120"
             @click="goToTaskDetail(task.id)"
           >
-            <view class="flex items-center">
-              <view class="icon-box mr-3">
-                <text class="uni-icons uniui-flag"></text>
-              </view>
-              <view>
-                <text class="text-sm font-medium text-gray-800">{{ task.projectName }}</text>
-                <text class="text-xs text-gray-400 mt-1">{{ task.address || task.scheduleDate }}</text>
-              </view>
+            <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.projectName }}</text>
+              <text class="row-sub truncate">{{ task.address || task.scheduleDate }}</text>
             </view>
             <StatusTag :status="task.status" />
           </view>
@@ -114,43 +162,49 @@
       </view>
 
       <!-- 通知公告 -->
-      <view class="card">
-        <view class="flex items-center justify-between p-4 border-b border-gray-100">
-          <text class="text-base font-semibold text-gray-800">通知公告</text>
-          <text class="text-sm text-primary" @click="goToNoticeList">更多</text>
+      <view class="glass-card">
+        <view class="section-head">
+          <view class="section-bar mr-2"></view>
+          <text class="section-title flex-1">通知公告</text>
+          <text class="section-more" @click="goToNoticeList">更多</text>
         </view>
         <view
           v-for="(notice, index) in notices"
           :key="index"
-          class="p-4 border-b border-gray-100 flex items-center active:bg-gray-50"
+          class="list-row"
+          hover-class="row-hover"
+          :hover-start-time="0"
+          :hover-stay-time="120"
           @click="goToNoticeDetail"
         >
-          <view class="w-2 h-2 rounded-full mr-3 flex-shrink-0" :class="index === 0 ? 'bg-danger' : 'bg-gray-300'"></view>
-          <view class="flex-1">
-            <text class="text-sm text-gray-700">{{ notice.title }}</text>
-          </view>
-          <text class="text-xs text-gray-400 ml-2">{{ notice.date }}</text>
+          <view class="dot mr-3" :class="index === 0 ? 'dot-active' : 'dot-muted'"></view>
+          <text class="flex-1 row-title truncate mr-2">{{ notice.title }}</text>
+          <text class="row-date flex-shrink-0">{{ notice.date }}</text>
         </view>
       </view>
 
       <!-- 行业知识 -->
-      <view class="card">
-        <view class="flex items-center justify-between p-4 border-b border-gray-100">
-          <text class="text-base font-semibold text-gray-800">行业知识</text>
-          <text class="text-sm text-primary" @click="goToKnowledgeList">更多</text>
+      <view class="glass-card">
+        <view class="section-head">
+          <view class="section-bar mr-2"></view>
+          <text class="section-title flex-1">行业知识</text>
+          <text class="section-more" @click="goToKnowledgeList">更多</text>
         </view>
         <view
           v-for="(item, index) in knowledgeList"
           :key="index"
-          class="p-4 border-b border-gray-100 flex items-center active:bg-gray-50"
+          class="list-row"
+          hover-class="row-hover"
+          :hover-start-time="0"
+          :hover-stay-time="120"
           @click="goToKnowledgeDetail"
         >
-          <view class="icon-box mr-3">
-            <text class="uni-icons uniui-chart"></text>
+          <view class="icon-chip mr-3">
+            <text class="uni-icons uniui-chart chip-glyph"></text>
           </view>
-          <view class="flex-1">
-            <text class="text-sm font-medium text-gray-700">{{ item.title }}</text>
-            <text class="text-xs text-gray-400 mt-1 truncate">{{ item.summary }}</text>
+          <view class="flex-1 min-w-0">
+            <text class="row-title truncate">{{ item.title }}</text>
+            <text class="row-sub truncate">{{ item.summary }}</text>
           </view>
         </view>
       </view>
@@ -163,8 +217,7 @@ import { ref, computed, onMounted } from 'vue'
 import { useAuthStore } from '@/stores/auth'
 import { useProjectStore } from '@/stores/project'
 import { useTaskStore } from '@/stores/task'
-import GradientStatCard from '@/components/common/GradientStatCard.vue'
-import QuickEntry from '@/components/common/QuickEntry.vue'
+import StatusBar from '@/components/common/StatusBar.vue'
 import StatusTag from '@/components/common/StatusTag.vue'
 
 const authStore = useAuthStore()
@@ -176,6 +229,15 @@ const notificationCount = ref(3)
 
 const latestTasks = computed(() => taskStore.tasks.slice(0, 3))
 
+const greetText = computed(() => {
+  const hour = new Date().getHours()
+  if (hour < 6) return '凌晨好'
+  if (hour < 12) return '上午好'
+  if (hour < 14) return '中午好'
+  if (hour < 18) return '下午好'
+  return '晚上好'
+})
+
 const notices = [
   { title: '关于2024年第一季度销售激励方案的通知', date: '2024-01-15' },
   { title: '春节期间服务安排调整公告', date: '2024-01-10' },
@@ -236,3 +298,346 @@ onMounted(() => {
   taskStore.fetchTasks()
 })
 </script>
+
+<style scoped>
+/* ==================== 页面外壳 ==================== */
+.sales-home {
+  position: relative;
+  max-width: 430px;
+  margin: 0 auto;
+  min-height: 100vh;
+  background-color: #f6fbf9; /* 云雾白 */
+}
+
+/* ==================== 顶部 banner(山水背景) ==================== */
+.banner {
+  position: relative;
+  height: 168px;
+  overflow: hidden;
+  background-color: #dff0e8; /* 图片加载前的浅青绿兜底 */
+}
+
+.banner-bg {
+  position: absolute;
+  top: 0;
+  left: 0;
+  width: 100%;
+  height: 100%;
+  z-index: 0;
+}
+
+/* 蒙版:顶部提亮保文字、底部淡出衔接云雾白 */
+.banner-veil {
+  position: absolute;
+  top: 0;
+  left: 0;
+  width: 100%;
+  height: 100%;
+  z-index: 1;
+  background: linear-gradient(
+    180deg,
+    rgba(255, 255, 255, 0.5) 0%,
+    rgba(255, 255, 255, 0.16) 45%,
+    rgba(246, 251, 249, 0.66) 100%
+  );
+}
+
+.banner-inner {
+  position: relative;
+  z-index: 2;
+  display: flex;
+  align-items: center;
+  padding-top: 14px;
+  padding-bottom: 24px;
+}
+
+.avatar {
+  width: 48px;
+  height: 48px;
+  border-radius: 50%;
+  background-color: rgba(255, 255, 255, 0.9);
+  backdrop-filter: blur(8px);
+  -webkit-backdrop-filter: blur(8px);
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  box-shadow: 0 8px 22px -8px rgba(31, 41, 37, 0.4);
+  flex-shrink: 0;
+}
+
+.avatar-icon {
+  font-size: 24px;
+  color: #368f6f;
+}
+
+.greet {
+  font-size: 12px;
+  color: rgba(31, 41, 37, 0.62);
+}
+
+.username {
+  display: block;
+  margin-top: 2px;
+  font-size: 18px;
+  font-weight: 800;
+  letter-spacing: 0.5px;
+  color: #1f2937;
+}
+
+/* 通知铃铛 */
+.notify-btn {
+  position: relative;
+  width: 40px;
+  height: 40px;
+  border-radius: 50%;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  background-color: rgba(255, 255, 255, 0.6);
+  backdrop-filter: blur(8px);
+  -webkit-backdrop-filter: blur(8px);
+  box-shadow: 0 6px 18px -8px rgba(31, 41, 37, 0.35);
+}
+
+.notify-icon {
+  font-size: 20px;
+  color: #368f6f;
+}
+
+.notify-badge {
+  position: absolute;
+  top: -4px;
+  right: -4px;
+  min-width: 18px;
+  height: 18px;
+  padding: 0 4px;
+  border-radius: 9px;
+  background-color: #ef4444;
+  border: 1.5px solid rgba(255, 255, 255, 0.9);
+  color: #ffffff;
+  font-size: 10px;
+  font-weight: 700;
+  line-height: 15px;
+  text-align: center;
+}
+
+.notify-hover {
+  background-color: rgba(255, 255, 255, 0.88);
+}
+
+/* ==================== 内容区(轻微上叠 banner) ==================== */
+.content-wrap {
+  position: relative;
+  z-index: 2;
+  margin-top: -18px;
+}
+
+/* 磨砂玻璃卡 */
+.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;
+}
+
+/* ==================== 统计卡 ==================== */
+.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;
+}
+
+.stat-hover {
+  transform: scale(0.97);
+  background-color: rgba(164, 216, 152, 0.24);
+}
+
+/* ==================== 快捷入口 ==================== */
+.entry-card {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+  padding: 14px 4px 12px;
+  border-radius: 16px;
+}
+
+.entry-label {
+  margin-top: 8px;
+  font-size: 13px;
+  font-weight: 500;
+  color: #374151;
+}
+
+.entry-hover {
+  background-color: rgba(164, 216, 152, 0.14);
+  transform: scale(0.97);
+}
+
+/* 嫩芽浅底图标块(与个人中心 menu-icon 一致) */
+.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;
+}
+
+/* ==================== 任务提醒入口 ==================== */
+.reminder-row {
+  display: flex;
+  align-items: center;
+  padding: 14px 16px;
+}
+
+.reminder-text {
+  font-size: 15px;
+  font-weight: 600;
+  color: #1f2937;
+}
+
+.reminder-count {
+  min-width: 22px;
+  height: 22px;
+  padding: 0 7px;
+  border-radius: 11px;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  font-size: 12px;
+  font-weight: 700;
+  color: #368f6f;
+  background-color: rgba(164, 216, 152, 0.24);
+}
+
+.row-arrow {
+  font-size: 16px;
+  color: #c4d0cb;
+}
+
+.reminder-hover {
+  background-color: rgba(164, 216, 152, 0.12);
+}
+
+/* ==================== 区块标题栏 ==================== */
+.section-head {
+  display: flex;
+  align-items: center;
+  padding: 16px 16px 12px;
+}
+
+.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;
+}
+
+.section-more {
+  font-size: 13px;
+  color: #368f6f;
+}
+
+/* ==================== 列表行 ==================== */
+.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-date {
+  font-size: 12px;
+  color: #9ca3af;
+}
+
+.row-hover {
+  background-color: rgba(164, 216, 152, 0.1);
+}
+
+/* 通知圆点 */
+.dot {
+  width: 8px;
+  height: 8px;
+  border-radius: 50%;
+  flex-shrink: 0;
+}
+
+.dot-active {
+  background-color: #368f6f;
+  box-shadow: 0 0 0 3px rgba(164, 216, 152, 0.3);
+}
+
+.dot-muted {
+  background-color: #c4d0cb;
+}
+
+.empty-text {
+  font-size: 13px;
+  color: #9ca3af;
+}
+</style>

+ 193 - 78
src/components/my/ProfileView.vue

@@ -1,40 +1,40 @@
 <template>
-  <view class="app-container pb-24">
-    <!-- 顶部个人信息 -->
-    <view class="header-gradient">
+  <view class="profile-page">
+    <!-- 顶部个人信息 banner:山水线条背景 -->
+    <view class="banner">
+      <image class="banner-bg" src="/static/peoplebackpage.png" mode="aspectFill" />
+      <view class="banner-veil"></view>
       <StatusBar />
-      <view class="px-4 pt-4 pb-8">
-        <view class="flex items-center">
-          <view class="profile-avatar mr-4">
-            <text class="uni-icons uniui-person-filled text-primary text-3xl"></text>
+      <view class="banner-inner">
+        <view class="avatar mr-4">
+          <text class="uni-icons uniui-person-filled avatar-icon"></text>
+        </view>
+        <view class="flex-1 min-w-0">
+          <view class="flex items-center flex-wrap">
+            <text class="name truncate">{{ displayName }}</text>
+            <text class="role-chip">{{ roleLabel }}</text>
           </view>
-          <view class="flex-1 min-w-0">
-            <view class="flex items-center flex-wrap">
-              <text class="text-white text-xl font-bold truncate">{{ displayName }}</text>
-              <text class="role-chip">{{ roleLabel }}</text>
-            </view>
-            <view v-if="primaryMeta || secondaryMeta" class="flex items-center flex-wrap mt-2">
-              <text v-if="primaryMeta" class="profile-meta">{{ primaryMeta }}</text>
-              <text v-if="primaryMeta && secondaryMeta" class="profile-meta-dot">·</text>
-              <text v-if="secondaryMeta" class="profile-meta">{{ secondaryMeta }}</text>
-            </view>
-            <view v-if="userInfo?.employeeNo" class="mt-2">
-              <text class="emp-chip">工号 {{ userInfo.employeeNo }}</text>
-            </view>
+          <view v-if="primaryMeta || secondaryMeta" class="flex items-center flex-wrap mt-2">
+            <text v-if="primaryMeta" class="meta">{{ primaryMeta }}</text>
+            <text v-if="primaryMeta && secondaryMeta" class="meta-dot">·</text>
+            <text v-if="secondaryMeta" class="meta">{{ secondaryMeta }}</text>
+          </view>
+          <view v-if="userInfo?.employeeNo" class="mt-2">
+            <text class="emp-chip">工号 {{ userInfo.employeeNo }}</text>
           </view>
         </view>
       </view>
     </view>
 
-    <!-- 内容区 -->
-    <view class="px-4 -mt-4">
+    <!-- 卡片组 -->
+    <view class="cards-wrap px-4 pb-24">
       <!-- 账号信息 -->
-      <view class="card">
-        <view class="flex items-center px-4 pt-4 pb-3">
+      <view class="glass-card">
+        <view class="flex items-center px-5 pt-6 pb-3">
           <view class="section-bar mr-2"></view>
-          <text class="text-base font-bold text-gray-800">账号信息</text>
+          <text class="card-title">账号信息</text>
         </view>
-        <view class="px-4 pb-2">
+        <view class="px-5 pb-4">
           <view class="info-row">
             <text class="info-label">姓名</text>
             <text class="info-value">{{ userInfo?.name || '-' }}</text>
@@ -87,7 +87,7 @@
       </view>
 
       <!-- 功能菜单 -->
-      <view class="card p-2">
+      <view class="glass-card mt-4 p-2">
         <view
           class="menu-item"
           hover-class="menu-hover"
@@ -95,14 +95,14 @@
           :hover-stay-time="120"
           @click="goToChangePassword"
         >
-          <view class="menu-icon bg-blue-50">
-            <text class="uni-icons uniui-locked-filled text-blue-600"></text>
+          <view class="menu-icon">
+            <text class="uni-icons uniui-locked menu-glyph"></text>
           </view>
           <view class="flex-1 min-w-0">
             <text class="menu-title">修改密码</text>
             <text class="menu-desc">定期更换,保障账号安全</text>
           </view>
-          <text class="uni-icons uniui-arrowright text-gray-400"></text>
+          <text class="uni-icons uniui-arrowright menu-arrow"></text>
         </view>
         <view
           class="menu-item"
@@ -111,14 +111,14 @@
           :hover-stay-time="120"
           @click="goToNotices"
         >
-          <view class="menu-icon bg-orange-50">
-            <text class="uni-icons uniui-notification-filled text-orange-600"></text>
+          <view class="menu-icon">
+            <text class="uni-icons uniui-notification menu-glyph"></text>
           </view>
           <view class="flex-1 min-w-0">
             <text class="menu-title">通知公告</text>
             <text class="menu-desc">查看公司最新通知与公告</text>
           </view>
-          <text class="uni-icons uniui-arrowright text-gray-400"></text>
+          <text class="uni-icons uniui-arrowright menu-arrow"></text>
         </view>
         <view
           class="menu-item"
@@ -127,30 +127,30 @@
           :hover-stay-time="120"
           @click="goToHelp"
         >
-          <view class="menu-icon bg-green-50">
-            <text class="uni-icons uniui-help-filled text-green-600"></text>
+          <view class="menu-icon">
+            <text class="uni-icons uniui-help menu-glyph"></text>
           </view>
           <view class="flex-1 min-w-0">
             <text class="menu-title">帮助中心</text>
             <text class="menu-desc">常见问题与使用指南</text>
           </view>
-          <text class="uni-icons uniui-arrowright text-gray-400"></text>
+          <text class="uni-icons uniui-arrowright menu-arrow"></text>
         </view>
       </view>
 
       <!-- 退出登录 -->
       <view
-        class="card logout-row"
+        class="glass-card mt-4 logout-row"
         hover-class="logout-hover"
         :hover-start-time="0"
         :hover-stay-time="120"
         @click="handleLogout"
       >
-        <view class="menu-icon bg-red-50 mr-3">
-          <text class="uni-icons uniui-loop text-red-600"></text>
+        <view class="logout-icon mr-3">
+          <text class="uni-icons uniui-loop logout-glyph"></text>
         </view>
-        <text class="flex-1 text-base font-medium text-red-600">退出登录</text>
-        <text class="uni-icons uniui-arrowright text-red-300"></text>
+        <text class="flex-1 logout-text">退出登录</text>
+        <text class="uni-icons uniui-arrowright logout-arrow"></text>
       </view>
     </view>
   </view>
@@ -224,59 +224,140 @@ async function handleLogout() {
 </script>
 
 <style scoped>
-/* 头像 */
-.profile-avatar {
+/* ==================== 页面外壳 ==================== */
+.profile-page {
+  position: relative;
+  max-width: 430px;
+  margin: 0 auto;
+  min-height: 100vh;
+  background-color: #f6fbf9;        /* 云雾白 */
+}
+
+/* ==================== 顶部 banner(山水背景) ==================== */
+.banner {
+  position: relative;
+  height: 232px;
+  overflow: hidden;
+}
+
+.banner-bg {
+  position: absolute;
+  top: 0;
+  left: 0;
+  width: 100%;
+  height: 100%;
+  z-index: 0;
+}
+
+/* 蒙版:顶部提亮保文字、底部淡出衔接云雾白 */
+.banner-veil {
+  position: absolute;
+  top: 0;
+  left: 0;
+  width: 100%;
+  height: 100%;
+  z-index: 1;
+  background: linear-gradient(
+    180deg,
+    rgba(255, 255, 255, 0.45) 0%,
+    rgba(255, 255, 255, 0.12) 45%,
+    rgba(246, 251, 249, 0.62) 100%
+  );
+}
+
+.banner-inner {
+  position: relative;
+  z-index: 2;
+  display: flex;
+  align-items: center;
+  padding: 30px 16px 30px;
+}
+
+.avatar {
   width: 72px;
   height: 72px;
   border-radius: 50%;
-  background-color: #ffffff;
+  background-color: rgba(255, 255, 255, 0.88);
+  backdrop-filter: blur(8px);
+  -webkit-backdrop-filter: blur(8px);
   display: flex;
   align-items: center;
   justify-content: center;
-  box-shadow: 0 4px 14px rgba(0, 0, 0, 0.12);
+  box-shadow: 0 10px 28px -8px rgba(54, 143, 111, 0.45);
   flex-shrink: 0;
 }
 
-/* 角色徽标 */
+.avatar-icon {
+  font-size: 34px;
+  color: #368f6f;
+}
+
+.name {
+  font-size: 24px;
+  font-weight: 800;
+  letter-spacing: 1px;
+  color: #1f2937;
+}
+
 .role-chip {
   margin-left: 8px;
   padding: 2px 10px;
   border-radius: 20px;
   font-size: 12px;
-  font-weight: 500;
+  font-weight: 600;
   color: #ffffff;
-  background-color: rgba(255, 255, 255, 0.22);
-  border: 1px solid rgba(255, 255, 255, 0.35);
+  background: linear-gradient(135deg, #368f6f 0%, #5ab8d0 100%);
 }
 
-/* 头部 meta 文字 */
-.profile-meta {
-  color: rgba(255, 255, 255, 0.9);
+.meta {
   font-size: 13px;
+  color: #6b7280;
 }
 
-.profile-meta-dot {
-  color: rgba(255, 255, 255, 0.5);
-  font-size: 13px;
+.meta-dot {
   margin: 0 6px;
+  font-size: 13px;
+  color: rgba(107, 114, 128, 0.6);
 }
 
-/* 工号 chip */
 .emp-chip {
   display: inline-block;
   padding: 2px 10px;
   border-radius: 20px;
   font-size: 12px;
-  color: rgba(255, 255, 255, 0.92);
-  background-color: rgba(0, 0, 0, 0.16);
+  color: #1f2937;
+  background-color: rgba(255, 255, 255, 0.7);
+}
+
+/* ==================== 卡片组(轻微上叠 banner) ==================== */
+.cards-wrap {
+  position: relative;
+  z-index: 2;
+  margin-top: -18px;
+}
+
+/* 磨砂玻璃卡 */
+.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;
+}
+
+.card-title {
+  font-size: 16px;
+  font-weight: 700;
+  color: #1f2937;
 }
 
-/* 区块标题装饰条 */
 .section-bar {
   width: 4px;
   height: 16px;
   border-radius: 2px;
-  background: linear-gradient(180deg, #3b82f6 0%, #2563eb 100%);
+  background: linear-gradient(180deg, #368f6f 0%, #5ab8d0 100%);
 }
 
 /* 信息行 */
@@ -284,8 +365,8 @@ async function handleLogout() {
   display: flex;
   align-items: flex-start;
   justify-content: space-between;
-  padding: 12px 0;
-  border-bottom: 1px solid #f3f4f6;
+  padding: 13px 0;
+  border-bottom: 1px solid rgba(164, 216, 152, 0.22);
 }
 
 .info-row:last-child {
@@ -293,10 +374,10 @@ async function handleLogout() {
 }
 
 .info-label {
-  font-size: 14px;
-  color: #6b7280;
   flex-shrink: 0;
   margin-right: 16px;
+  font-size: 14px;
+  color: #6b7280;
 }
 
 .info-value {
@@ -307,30 +388,36 @@ async function handleLogout() {
   text-align: right;
 }
 
-/* 菜单 */
+/* 功能菜单 */
 .menu-item {
   display: flex;
   align-items: center;
-  padding: 12px;
-  border-radius: 12px;
+  min-height: 60px;
+  padding: 13px 14px;
+  border-radius: 16px;
 }
 
 .menu-icon {
-  width: 40px;
-  height: 40px;
-  border-radius: 10px;
+  width: 42px;
+  height: 42px;
+  margin-right: 12px;
+  border-radius: 12px;
+  flex-shrink: 0;
   display: flex;
   align-items: center;
   justify-content: center;
-  flex-shrink: 0;
-  margin-right: 12px;
+  background-color: rgba(164, 216, 152, 0.20);
+}
+
+.menu-glyph {
   font-size: 20px;
+  color: #368f6f;
 }
 
 .menu-title {
   display: block;
   font-size: 15px;
-  font-weight: 500;
+  font-weight: 600;
   color: #1f2937;
 }
 
@@ -338,11 +425,16 @@ async function handleLogout() {
   display: block;
   margin-top: 2px;
   font-size: 12px;
-  color: #9ca3af;
+  color: #6b7280;
+}
+
+.menu-arrow {
+  font-size: 16px;
+  color: #c4d0cb;                   /* 浅灰箭头 */
 }
 
 .menu-hover {
-  background-color: #f9fafb;
+  background-color: rgba(164, 216, 152, 0.14);
 }
 
 /* 退出登录 */
@@ -352,11 +444,34 @@ async function handleLogout() {
   padding: 14px 16px;
 }
 
-.logout-hover {
-  background-color: #fef2f2;
+.logout-icon {
+  width: 42px;
+  height: 42px;
+  border-radius: 12px;
+  flex-shrink: 0;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  background-color: rgba(239, 68, 68, 0.10);
 }
 
-.text-red-300 {
+.logout-glyph {
+  font-size: 20px;
+  color: #ef4444;
+}
+
+.logout-text {
+  font-size: 16px;
+  font-weight: 600;
+  color: #dc2626;
+}
+
+.logout-arrow {
+  font-size: 16px;
   color: #fca5a5;
 }
+
+.logout-hover {
+  background-color: rgba(239, 68, 68, 0.06);
+}
 </style>

+ 34 - 12
src/components/project/ProjectCard.vue

@@ -2,7 +2,8 @@
   <view class="project-card bg-white rounded-card p-4 mb-3 clickable" @click="onClick">
     <view class="flex justify-between items-start mb-2">
       <view class="flex-1 min-w-0 mr-2">
-        <text class="project-name text-sm font-semibold text-gray-800 truncate">{{ project.name }}</text>
+        <text class="project-name text-sm font-semibold text-gray-800 truncate">{{ project.projectName }}</text>
+        <text v-if="project.projectCode" class="text-xs text-gray-400 block mt-1">{{ project.projectCode }}</text>
       </view>
       <StatusTag type="project" :status="project.status" />
     </view>
@@ -10,43 +11,64 @@
     <view class="gap-y-1 mb-3">
       <view class="text-xs text-gray-500 flex items-center">
         <text class="mr-1">客</text>
-        <text class="truncate">{{ project.customerName }} {{ project.customerPhone }}</text>
+        <text class="truncate">{{ project.customerName || '-' }} {{ project.customerPhone || '' }}</text>
       </view>
       <view class="text-xs text-gray-500 flex items-center">
         <text class="mr-1">地</text>
-        <text class="truncate">{{ project.address }}</text>
+        <text class="truncate">{{ addressText }}</text>
       </view>
       <view class="text-xs text-gray-500 flex items-center">
         <text class="mr-1">类</text>
-        <text>{{ getProjectTypeText(project.type) }}</text>
+        <text>{{ getCooperationTypeText(project.cooperationType) }}</text>
+        <text v-if="project.industry" class="ml-2">{{ getIndustryText(project.industry) }}</text>
       </view>
     </view>
 
     <view class="flex justify-between items-center pt-3 border-t border-gray-100">
-      <view class="flex gap-2">
-        <text class="text-xs text-gray-400">{{ project.servicePoints.length }} 个服务点</text>
-        <text class="text-xs text-gray-400">{{ project.contacts.length }} 位联系人</text>
+      <view class="flex gap-2 flex-wrap">
+        <text
+          v-for="ft in facilityList"
+          :key="ft"
+          class="px-2 py-1 bg-cyan-50 text-cyan-600 rounded text-xs"
+        >{{ getFacilityTypeText(ft) }}</text>
+        <text v-if="servicePointCount > 0" class="text-xs text-gray-400 self-center">{{ servicePointCount }} 个服务点</text>
       </view>
-      <text class="text-xs text-gray-400">{{ formatDate(project.createTime, 'YYYY-MM-DD') }}</text>
+      <text class="text-xs text-gray-400">{{ formatDate(project.createTime || '', 'YYYY-MM-DD') }}</text>
     </view>
   </view>
 </template>
 
 <script setup lang="ts">
-import type { Project } from '@/types'
+import { computed } from 'vue'
+import type { FacilityType, Project, ServicePoint } from '@/types'
 import StatusTag from '../common/StatusTag.vue'
-import { formatDate, getProjectTypeText } from '@/utils'
+import {
+  formatDate,
+  getCooperationTypeText,
+  getIndustryText,
+  getFacilityTypeText,
+  parseJsonArray
+} from '@/utils'
 
 const props = defineProps<{
   project: Project
 }>()
 
 const emit = defineEmits<{
-  (e: 'click', projectId: string): void
+  (e: 'click', projectId: number | string): void
 }>()
 
+const facilityList = computed(() => parseJsonArray<FacilityType>(props.project.facilityTypes))
+const servicePointCount = computed(() => parseJsonArray<ServicePoint>(props.project.servicePoints).length)
+
+const addressText = computed(() => {
+  const p = props.project
+  if (p.address) return p.address
+  return [p.province, p.city, p.district, p.street, p.detailAddress].filter(Boolean).join('') || '-'
+})
+
 function onClick() {
-  emit('click', props.project.id)
+  emit('click', props.project.projectId)
 }
 </script>
 

+ 41 - 13
src/components/task/ConstructionTaskCard.vue

@@ -1,16 +1,13 @@
 <template>
   <view
-    class="card p-4 mb-3 clickable"
+    class="task-card p-4 clickable"
     hover-class="scale-down"
-    :class="{
-      'border-l-4 border-red-500': task.type === 'emergency',
-      'bg-green-50': task.status === 'completed'
-    }"
-    @click="$emit('click', task.id)"
+    :class="{ 'is-emergency': task.type === 'emergency' }"
+    @click="$emit('click', String((task as any).id ?? (task as any).taskId ?? ''))"
   >
     <view class="flex justify-between items-start mb-2">
       <view class="flex-1 min-w-0 mr-3">
-        <text class="text-sm font-semibold text-gray-800 truncate block">{{ task.projectName || '未命名任务' }}</text>
+        <text class="text-sm font-semibold text-gray-800 truncate block">{{ (task as any).taskName || task.projectName || '未命名任务' }}</text>
       </view>
       <StatusTag :status="task.status" />
     </view>
@@ -19,7 +16,7 @@
       <view v-if="task.urgent === 'high'" class="px-2 py-1 bg-danger-light text-danger rounded text-xs font-medium">
         加急
       </view>
-      <view v-if="task.coopType" class="px-2 py-1 bg-primary-light text-primary rounded text-xs font-medium">
+      <view v-if="task.coopType" class="chip-type px-2 py-1 rounded text-xs font-medium">
         {{ task.coopType }}
       </view>
       <view v-if="task.serviceMode" class="px-2 py-1 bg-success-light text-success rounded text-xs font-medium">
@@ -29,15 +26,15 @@
 
     <view class="gap-y-2 mb-3">
       <view class="flex items-center">
-        <text class="uni-icons uniui-tools text-gray-400 text-xs mr-1"></text>
+        <text class="uni-icons uniui-tools info-icon text-xs mr-1"></text>
         <text class="text-gray-500 text-xs">{{ task.serviceType || '施工服务' }}</text>
       </view>
       <view class="flex items-center">
-        <text class="uni-icons uniui-clock-filled text-gray-400 text-xs mr-1"></text>
+        <text class="uni-icons uniui-clock-filled info-icon text-xs mr-1"></text>
         <text class="text-gray-500 text-xs">{{ task.serviceDate || task.scheduleDate || '待定' }} {{ task.serviceTime || task.scheduleTime || '' }}</text>
       </view>
       <view class="flex items-center">
-        <text class="uni-icons uniui-location-filled text-gray-400 text-xs mr-1"></text>
+        <text class="uni-icons uniui-location-filled info-icon text-xs mr-1"></text>
         <text class="text-gray-500 text-xs truncate">{{ task.address || '暂无地址' }}</text>
       </view>
     </view>
@@ -50,8 +47,8 @@
           class="flex-1 flex flex-col items-center"
         >
           <view
-            class="w-6 h-6 rounded-full flex items-center justify-center text-xs mb-1"
-            :class="step.active ? 'bg-primary text-white' : 'bg-gray-100 text-gray-400'"
+            class="step-dot w-6 h-6 rounded-full flex items-center justify-center text-xs mb-1"
+            :class="step.active ? 'step-dot-on' : 'step-dot-off'"
           >
             {{ step.label }}
           </view>
@@ -89,6 +86,37 @@ const steps = computed(() => {
 </script>
 
 <style scoped>
+.task-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;
+}
+.task-card.is-emergency {
+  border-left: 4px solid #ef4444;
+}
+.info-icon {
+  color: #368f6f;
+}
+.chip-type {
+  background-color: rgba(164, 216, 152, 0.2);
+  color: #368f6f;
+}
+.step-dot {
+  font-weight: 600;
+}
+.step-dot-on {
+  background: linear-gradient(135deg, #368f6f 0%, #5ab8d0 100%);
+  color: #ffffff;
+  box-shadow: 0 4px 10px -4px rgba(54, 143, 111, 0.5);
+}
+.step-dot-off {
+  background-color: rgba(164, 216, 152, 0.15);
+  color: #9ca3af;
+}
 .scale-down {
   transition: transform 0.15s ease;
 }

+ 46 - 25
src/components/task/DispatchTaskCard.vue

@@ -1,39 +1,35 @@
 <template>
   <view
-    class="card p-4 mb-3 clickable border-l-4 border-primary"
+    class="task-card p-4 clickable"
     hover-class="scale-down"
-    :class="{
-      'border-red-500': task.type === 'emergency',
-      'bg-green-50': task.status === 'completed'
-    }"
     :style="borderStyle"
-    @click="$emit('click', task.id)"
+    @click="$emit('click', String((task as any).id ?? (task as any).taskId ?? ''))"
   >
     <view class="flex justify-between items-start mb-2">
       <view class="flex-1 min-w-0 mr-3">
-        <text class="text-sm font-semibold text-gray-800 truncate block">{{ task.projectName || '未命名任务' }}</text>
+        <text class="text-sm font-semibold text-gray-800 truncate block">{{ (task as any).taskName || task.projectName || (task as any).serviceContent || (task as any).faultLocation || (task as any).taskNo || '未命名任务' }}</text>
       </view>
       <StatusTag :status="task.status" />
     </view>
 
     <view class="gap-y-2 mb-3">
       <view class="flex items-center">
-        <text class="uni-icons uniui-location-filled text-gray-400 text-xs mr-1"></text>
+        <text class="uni-icons uniui-location-filled info-icon text-xs mr-1"></text>
         <text class="text-gray-500 text-xs truncate">{{ task.address || '暂无地址' }}</text>
       </view>
       <view class="flex items-center">
-        <text class="uni-icons uniui-tools text-gray-400 text-xs mr-1"></text>
+        <text class="uni-icons uniui-tools info-icon text-xs mr-1"></text>
         <text class="text-gray-500 text-xs">
           {{ task.serviceType || '施工服务' }}
           <text v-if="task.facilityType" class="text-gray-400"> / {{ task.facilityType }}</text>
         </text>
       </view>
       <view class="flex items-center">
-        <text class="uni-icons uniui-clock-filled text-gray-400 text-xs mr-1"></text>
+        <text class="uni-icons uniui-clock-filled info-icon text-xs mr-1"></text>
         <text class="text-gray-500 text-xs">{{ formatTime }}</text>
       </view>
       <view v-if="task.applicant" class="flex items-center">
-        <text class="uni-icons uniui-person-filled text-gray-400 text-xs mr-1"></text>
+        <text class="uni-icons uniui-person-filled info-icon text-xs mr-1"></text>
         <text class="text-gray-500 text-xs">申请人:{{ task.applicant }}</text>
       </view>
     </view>
@@ -43,8 +39,8 @@
       <text class="text-xs font-medium" :style="{ color: actionColor }">{{ actionText }}</text>
     </view>
 
-    <view v-if="task.rejectReason" class="pt-2 border-t border-gray-100">
-      <text class="text-xs text-danger">驳回原因:{{ task.rejectReason }}</text>
+    <view v-if="rejectReasonText" class="pt-2 border-t border-gray-100">
+      <text class="text-xs text-danger">驳回原因:{{ rejectReasonText }}</text>
     </view>
   </view>
 </template>
@@ -64,19 +60,26 @@ defineEmits<{
 }>()
 
 const borderStyle = computed(() => {
+  if (props.task.type === 'emergency') {
+    return { borderLeftWidth: '4px', borderLeftStyle: 'solid', borderLeftColor: '#ef4444' }
+  }
   const color = {
     pending: '#f59e0b',
-    confirmed: '#2563eb',
-    scheduled: '#2563eb',
-    departed: '#8b5cf6',
+    auditing: '#f59e0b',
+    approved: '#2563eb',
+    assigned: '#2563eb',
+    in_progress: '#2563eb',
+    confirmed: '#368f6f',
+    scheduled: '#368f6f',
+    departed: '#5ab8d0',
     arrived: '#10b981',
-    constructing: '#2563eb',
+    constructing: '#368f6f',
     inspecting: '#f59e0b',
     completed: '#10b981',
     cancelled: '#ef4444',
     rejected: '#ef4444',
-  }[props.task.status] || '#2563eb'
-  return { borderLeftColor: color }
+  }[props.task.status] || '#368f6f'
+  return { borderLeftWidth: '4px', borderLeftStyle: 'solid', borderLeftColor: color } as any
 })
 
 const formatTime = computed(() => {
@@ -85,9 +88,18 @@ const formatTime = computed(() => {
   return '待定'
 })
 
+const rejectReasonText = computed(() => {
+  if (props.task.status !== 'rejected') return ''
+  return props.task.rejectReason || (props.task as any).auditRemark || ''
+})
+
 const statusHint = computed(() => {
   const map: Record<string, string> = {
-    pending: '需调度安排',
+    pending: '需调度审核',
+    auditing: '需调度审核',
+    approved: '待安排班组',
+    assigned: '已安排班组',
+    in_progress: '进行中',
     confirmed: '待安排班组',
     scheduled: '已安排,待出车',
     departed: '已出车',
@@ -104,6 +116,8 @@ const statusHint = computed(() => {
 const actionText = computed(() => {
   const map: Record<string, string> = {
     pending: '去审核',
+    auditing: '去审核',
+    approved: '去安排',
     confirmed: '去排班',
     scheduled: '查看',
     rejected: '查看',
@@ -112,20 +126,27 @@ const actionText = computed(() => {
 })
 
 const actionColor = computed(() => {
-  if (['pending', 'confirmed'].includes(props.task.status)) return '#2563eb'
+  if (['pending', 'auditing', 'approved', 'confirmed'].includes(props.task.status)) return '#368f6f'
   return '#9ca3af'
 })
 </script>
 
 <style scoped>
-.border-l-4 {
-  border-left-width: 4px;
+.task-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;
+}
+.info-icon {
+  color: #368f6f;
 }
 .scale-down {
   transition: transform 0.15s ease;
-  transform: scale(0.95);
 }
-.scale-down:hover,
 .scale-down:active {
   transform: scale(0.95);
 }

+ 35 - 11
src/components/task/SalesTaskCard.vue

@@ -1,18 +1,17 @@
 <template>
   <view
-    class="card p-4 mb-3 clickable"
+    class="task-card p-4 clickable"
     hover-class="scale-down"
     :hover-start-time="0"
     :hover-stay-time="150"
     :class="{
-      'border-l-4 border-red-500': task.type === 'emergency',
-      'bg-green-50': task.status === 'completed'
+      'border-l-4 border-red-500': task.type === 'emergency'
     }"
-    @click="$emit('click', task.id)"
+    @click="$emit('click', String((task as any).id ?? (task as any).taskId ?? ''))"
   >
     <view class="flex justify-between items-start mb-2">
       <view class="flex-1 min-w-0 mr-3">
-        <text class="text-sm font-semibold text-gray-800 truncate block">{{ task.projectName || '未命名任务' }}</text>
+        <text class="text-sm font-semibold text-gray-800 truncate block">{{ (task as any).taskName || task.projectName || '未命名任务' }}</text>
       </view>
       <StatusTag :status="task.status" />
     </view>
@@ -24,22 +23,22 @@
       <view v-if="task.type === 'emergency'" class="px-2 py-1 bg-warning-light text-warning rounded text-xs font-medium">
         应急
       </view>
-      <view v-if="task.coopType" class="px-2 py-1 bg-primary-light text-primary rounded text-xs font-medium">
+      <view v-if="task.coopType" class="chip-type px-2 py-1 rounded text-xs font-medium">
         {{ task.coopType }}
       </view>
     </view>
 
     <view class="gap-y-1 mb-3">
       <view class="flex items-center">
-        <text class="uni-icons uniui-location-filled text-gray-400 text-xs mr-1"></text>
+        <text class="uni-icons uniui-location-filled info-icon text-xs mr-1"></text>
         <text class="text-gray-500 text-xs truncate">{{ task.address || '暂无地址' }}</text>
       </view>
       <view v-if="task.team" class="flex items-center">
-        <text class="uni-icons uniui-person-filled text-gray-400 text-xs mr-1"></text>
+        <text class="uni-icons uniui-person-filled info-icon text-xs mr-1"></text>
         <text class="text-gray-500 text-xs">施工班组:{{ task.team }}</text>
       </view>
       <view v-if="task.acceptType" class="flex items-center">
-        <text class="uni-icons uniui-checkmarkempty text-gray-400 text-xs mr-1"></text>
+        <text class="uni-icons uniui-checkmarkempty info-icon text-xs mr-1"></text>
         <text class="text-gray-500 text-xs">验收方式:{{ task.acceptType }}</text>
       </view>
     </view>
@@ -49,8 +48,8 @@
         <text>进度</text>
         <text>{{ progressPercent }}%</text>
       </view>
-      <view class="h-2 bg-gray-100 rounded-full overflow-hidden">
-        <view class="h-full bg-primary rounded-full" :style="{ width: `${progressPercent}%` }"></view>
+      <view class="progress-track h-2 rounded-full overflow-hidden">
+        <view class="progress-fill h-full rounded-full" :style="{ width: `${progressPercent}%` }"></view>
       </view>
     </view>
   </view>
@@ -87,6 +86,31 @@ const progressPercent = computed(() => {
 </script>
 
 <style scoped>
+.task-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;
+}
+.border-l-4 {
+  border-left-width: 4px;
+}
+.info-icon {
+  color: #368f6f;
+}
+.chip-type {
+  background-color: rgba(164, 216, 152, 0.2);
+  color: #368f6f;
+}
+.progress-track {
+  background-color: rgba(164, 216, 152, 0.18);
+}
+.progress-fill {
+  background: linear-gradient(90deg, #368f6f 0%, #5ab8d0 100%);
+}
 .scale-down {
   transform: scale(0.95);
   transition: transform 0.15s ease;

+ 127 - 51
src/components/task/TaskListView.vue

@@ -1,43 +1,54 @@
 <template>
-  <view class="app-container pb-20">
-    <TopBar :title="pageTitle">
-      <template #right>
-        <view class="flex items-center pr-2">
-          <!-- 预留右侧安全区 -->
-        </view>
-      </template>
-    </TopBar>
+  <view class="app-container pb-24">
+    <TopBar :title="pageTitle" />
 
-    <!-- 筛选标签 -->
-    <view class="bg-white px-4 py-3 flex gap-2 overflow-x-auto hide-scrollbar">
+    <!-- 工具栏:搜索 + 状态下拉 + 新增 -->
+    <view class="toolbar px-4 py-3 sticky top-0 z-40 flex items-center gap-2">
+      <view class="search-box flex-1 min-w-0 flex items-center rounded-2xl px-4 py-2" :class="{ 'search-focus': focusedSearch }">
+        <text class="uni-icons uniui-search search-icon mr-2 text-base"></text>
+        <input
+          class="flex-1 bg-transparent text-sm text-gray-800"
+          placeholder="搜索项目/客户/故障位置"
+          placeholder-class="search-ph"
+          v-model="searchKeyword"
+          confirm-type="search"
+          @focus="focusedSearch = true"
+          @blur="focusedSearch = false"
+        />
+      </view>
+      <picker mode="selector" :range="filterLabels" :value="currentFilterIndex" @change="onFilterChange">
+        <view class="status-picker picker-box flex items-center justify-between rounded-2xl px-3 py-2">
+          <text class="text-sm text-gray-800 truncate mr-1">{{ currentFilterLabel }}</text>
+          <text class="uni-icons uniui-arrowdown picker-arrow text-xs flex-shrink-0"></text>
+        </view>
+      </picker>
       <view
-        v-for="status in filterStatuses"
-        :key="status.value"
-        class="filter-tab px-3 py-2 rounded-full text-sm whitespace-nowrap transition-all duration-150"
-        :class="currentFilter === status.value ? 'bg-primary text-white' : 'bg-surface text-gray-500'"
-        hover-class="scale-down"
-        :hover-stay-time="150"
-        @click="currentFilter = status.value"
+        v-if="config.publishPath && role !== 'dispatch'"
+        class="add-btn w-9 h-9 rounded-full flex items-center justify-center flex-shrink-0"
+        hover-class="add-btn-hover"
+        :hover-start-time="0"
+        :hover-stay-time="120"
+        @click="goToPublishTask"
       >
-        {{ status.label }}
+        <text class="uni-icons uniui-plusempty text-white text-xl"></text>
       </view>
     </view>
 
     <!-- 加载/空态 -->
     <view class="px-4 mt-3">
       <view v-if="taskStore.loading" class="py-10 text-center">
-        <text class="text-gray-400">加载中...</text>
+        <text class="loading-text">加载中...</text>
       </view>
 
       <view v-else-if="filteredTasks.length === 0">
-        <EmptyState message="暂无任务" />
+        <EmptyState text="暂无任务" />
       </view>
 
       <!-- 销售端:任务卡片 -->
       <view v-else-if="role === 'sales'" class="gap-y-3">
         <SalesTaskCard
           v-for="task in filteredTasks"
-          :key="task.id"
+          :key="taskIdOf(task)"
           :task="task"
           @click="goToDetail"
         />
@@ -47,7 +58,7 @@
       <view v-else-if="role === 'dispatch'" class="gap-y-3">
         <DispatchTaskCard
           v-for="task in filteredTasks"
-          :key="task.id"
+          :key="taskIdOf(task)"
           :task="task"
           @click="goToDetail"
         />
@@ -57,22 +68,13 @@
       <view v-else class="gap-y-3">
         <ConstructionTaskCard
           v-for="task in filteredTasks"
-          :key="task.id"
+          :key="taskIdOf(task)"
           :task="task"
           @click="goToDetail"
         />
       </view>
     </view>
 
-    <!-- 发布/新增任务按钮 -->
-    <view v-if="config.publishPath && role !== 'dispatch'" class="fixed bottom-20 right-4">
-      <button
-        class="w-14 h-14 rounded-full bg-primary text-white flex items-center justify-center shadow-lg text-2xl"
-        @click="goToPublishTask"
-      >
-        +
-      </button>
-    </view>
   </view>
 </template>
 
@@ -91,6 +93,8 @@ const props = defineProps<{
 
 const taskStore = useTaskStore()
 const currentFilter = ref('all')
+const searchKeyword = ref('')
+const focusedSearch = ref(false)
 
 const roleConfig: Record<string, { filterStatuses: { value: string; label: string }[]; detailPath: string; publishPath?: string }> = {
   sales: {
@@ -107,8 +111,8 @@ const roleConfig: Record<string, { filterStatuses: { value: string; label: strin
   dispatch: {
     filterStatuses: [
       { value: 'all', label: '全部' },
-      { value: 'pending', label: '待审核' },
-      { value: 'confirmed', label: '待排班' },
+      { value: 'auditing', label: '待审核' },
+      { value: 'approved', label: '待安排' },
       { value: 'processing', label: '进行中' },
       { value: 'completed', label: '已完成' },
       { value: 'rejected', label: '已驳回' },
@@ -119,11 +123,10 @@ const roleConfig: Record<string, { filterStatuses: { value: string; label: strin
   construction: {
     filterStatuses: [
       { value: 'all', label: '全部' },
-      { value: 'pending', label: '待确认' },
-      { value: 'scheduled', label: '待出车' },
-      { value: 'departed', label: '已出车' },
-      { value: 'constructing', label: '施工中' },
-      { value: 'inspecting', label: '待验收' },
+      { value: 'assigned', label: '待确认' },
+      { value: 'in_progress', label: '进行中' },
+      { value: 'completed', label: '已完成' },
+      { value: 'cancelled', label: '已取消' },
     ],
     detailPath: '/subPackages/pages-construction/taskDetail',
   },
@@ -131,6 +134,12 @@ const roleConfig: Record<string, { filterStatuses: { value: string; label: strin
 
 const config = computed(() => roleConfig[props.role] || roleConfig.sales)
 const filterStatuses = computed(() => config.value.filterStatuses)
+const filterLabels = computed(() => filterStatuses.value.map(s => s.label))
+const currentFilterIndex = computed(() => {
+  const idx = filterStatuses.value.findIndex(s => s.value === currentFilter.value)
+  return idx >= 0 ? idx : 0
+})
+const currentFilterLabel = computed(() => filterStatuses.value[currentFilterIndex.value]?.label || '全部')
 const detailPath = computed(() => config.value.detailPath)
 
 const pageTitle = computed(() => {
@@ -146,16 +155,39 @@ const publishText = computed(() => {
 })
 
 const filteredTasks = computed(() => {
-  if (currentFilter.value === 'all') return taskStore.tasks
-  if (currentFilter.value === 'dispatching') return taskStore.tasks.filter(t => ['confirmed', 'scheduled'].includes(t.status))
-  if (currentFilter.value === 'processing') return taskStore.tasks.filter(t => ['scheduled', 'departed', 'arrived', 'constructing'].includes(t.status))
-  if (currentFilter.value === 'emergency') return taskStore.tasks.filter(t => t.type === 'emergency')
-  return taskStore.tasks.filter(t => t.status === currentFilter.value)
+  let list = taskStore.tasks
+  if (currentFilter.value !== 'all') {
+    if (currentFilter.value === 'dispatching') list = list.filter(t => ['confirmed', 'scheduled'].includes(t.status))
+    else if (currentFilter.value === 'auditing') list = list.filter(t => ['auditing', 'pending'].includes(t.status))
+    else if (currentFilter.value === 'approved') list = list.filter(t => (t.status as string) === 'approved')
+    else if (currentFilter.value === 'processing') list = list.filter(t => ['scheduled', 'departed', 'arrived', 'constructing', 'assigned', 'in_progress'].includes(t.status))
+    else if (currentFilter.value === 'emergency') list = list.filter(t => t.type === 'emergency')
+    else list = list.filter(t => t.status === currentFilter.value)
+  }
+  const kw = searchKeyword.value.trim()
+  if (kw) {
+    list = list.filter(t =>
+      [t.projectName, t.customerName, t.faultLocation, t.address]
+        .filter(Boolean)
+        .some(s => String(s).includes(kw))
+    )
+  }
+  return list
 })
 
+function onFilterChange(e: any) {
+  const item = filterStatuses.value[Number(e.detail.value)]
+  if (item) currentFilter.value = item.value
+}
+
+function taskIdOf(task: any): string {
+  return String(task?.id ?? task?.taskId ?? '')
+}
+
 function goToDetail(taskId: string) {
+  if (!taskId) return
   taskStore.setCurrentTask(taskId)
-  uni.navigateTo({ url: detailPath.value })
+  uni.navigateTo({ url: `${detailPath.value}?id=${taskId}` })
 }
 
 function goToPublishTask() {
@@ -164,6 +196,9 @@ function goToPublishTask() {
 }
 
 function refresh() {
+  if (props.role === 'construction') {
+    return taskStore.fetchMyTasks()
+  }
   return taskStore.fetchTasks()
 }
 
@@ -171,12 +206,53 @@ defineExpose({ refresh })
 </script>
 
 <style scoped>
-/* WXSS-safe tap feedback for filter tabs */
-.scale-down {
-  transition: transform 0.15s ease;
+/* 工具栏:云雾白磨砂条,底部浅绿分割线,衔接 TopBar 与卡片区 */
+.toolbar {
+  background-color: rgba(246, 251, 249, 0.88);
+  backdrop-filter: blur(12px);
+  -webkit-backdrop-filter: blur(12px);
+  border-bottom: 1px solid rgba(164, 216, 152, 0.28);
+}
+
+/* 搜索框 / 状态下拉:默认白底浅灰描边,聚焦时青山绿强调 */
+.search-box,
+.picker-box {
+  background-color: #ffffff;
+  border: 1px solid #e5e7eb;
+  transition: border-color 0.15s ease, box-shadow 0.15s ease, background-color 0.15s ease;
+}
+.search-focus {
+  background-color: #ffffff;
+  border-color: #368f6f;
+  box-shadow: 0 0 0 3px rgba(164, 216, 152, 0.30);
+}
+.search-icon {
+  color: #9ca3af;
+}
+.search-ph {
+  color: #9ca3af;
+}
+.picker-arrow {
+  color: #368f6f;
 }
-.scale-down:hover,
-.scale-down:active {
-  transform: scale(0.95);
+
+/* 状态下拉:固定宽度,避免挤占搜索框与新增按钮 */
+.status-picker {
+  width: 104px;
+  flex-shrink: 0;
+}
+
+/* 新增按钮:青山绿→流水青渐变 + 柔绿阴影 */
+.add-btn {
+  background: linear-gradient(135deg, #368f6f 0%, #5ab8d0 100%);
+  box-shadow: 0 8px 18px -8px rgba(54, 143, 111, 0.55);
+}
+.add-btn-hover {
+  opacity: 0.92;
+  transform: scale(0.94);
+}
+
+.loading-text {
+  color: #6b7280;
 }
 </style>

+ 1 - 1
src/custom-tab-bar/index.js

@@ -6,7 +6,7 @@ const commonTabs = [
 
 const dispatchTabs = [
   { pagePath: 'pages/home/home', text: '首页', icon: '/static/tabbar/home.png', activeIcon: '/static/tabbar/home-active.png' },
-  { pagePath: 'pages/schedule/schedule', text: '排班', iconText: '▤' },
+  { pagePath: 'pages/schedule/schedule', text: '排班', icon: '/static/tabbar/排班.png', activeIcon: '/static/tabbar/排班-active.png' },
   { pagePath: 'pages/task/task', text: '任务', icon: '/static/tabbar/task.png', activeIcon: '/static/tabbar/task-active.png' },
   { pagePath: 'pages/my/my', text: '我的', icon: '/static/tabbar/my.png', activeIcon: '/static/tabbar/my-active.png' },
 ]

+ 1 - 0
src/custom-tab-bar/index.wxml

@@ -15,5 +15,6 @@
     />
     <view wx:else class="tab-icon tab-icon-text">{{item.iconText}}</view>
     <view class="tab-text">{{item.text}}</view>
+    <view wx:if="{{selected === index}}" class="tab-active-bar"></view>
   </view>
 </view>

+ 27 - 6
src/custom-tab-bar/index.wxss

@@ -3,27 +3,31 @@
   bottom: 0;
   left: 0;
   right: 0;
-  height: 50px;
-  background: #ffffff;
+  min-height: 50px;
+  padding-bottom: env(safe-area-inset-bottom);
+  background: rgba(255, 255, 255, 0.9);
+  backdrop-filter: blur(12px);
+  -webkit-backdrop-filter: blur(12px);
   display: flex;
   justify-content: space-around;
   align-items: center;
-  border-top: 1px solid #e5e7eb;
+  border-top: 1px solid rgba(164, 216, 152, 0.4);
   z-index: 9999;
 }
 
 .tab-bar-item {
+  position: relative;
   flex: 1;
   display: flex;
   flex-direction: column;
   align-items: center;
   justify-content: center;
-  height: 100%;
-  color: #9ca3af;
+  height: 50px;
+  color: #709484;
 }
 
 .tab-bar-item.active {
-  color: #2563eb;
+  color: #368f6f;
 }
 
 .tab-icon {
@@ -43,4 +47,21 @@
 .tab-text {
   font-size: 10px;
   line-height: 14px;
+  font-weight: 500;
+}
+
+.tab-bar-item.active .tab-text {
+  font-weight: 600;
+}
+
+/* 激活态青山绿高亮指示 */
+.tab-active-bar {
+  position: absolute;
+  top: 0;
+  left: 50%;
+  transform: translateX(-50%);
+  width: 22px;
+  height: 3px;
+  border-radius: 2px;
+  background: linear-gradient(135deg, #368f6f 0%, #5ab8d0 100%);
 }

+ 2 - 2
src/mock/index.ts

@@ -1,6 +1,5 @@
 import type {
   User,
-  Project,
   Task,
   Schedule,
   Vehicle,
@@ -41,7 +40,8 @@ export const mockUsers: User[] = [
   }
 ]
 
-export const mockProjects: Project[] = [
+// 旧版演示数据(已不再被任何页面引用),字段沿用旧结构,故以 any[] 保留
+export const mockProjects: any[] = [
   {
     id: 'p1',
     name: '绿城小区年度清洁服务',

+ 4 - 0
src/pages.json

@@ -81,6 +81,10 @@
         "pagePath": "pages/task/task",
         "text": "任务"
       },
+      {
+        "pagePath": "pages/schedule/schedule",
+        "text": "排班"
+      },
       {
         "pagePath": "pages/my/my",
         "text": "我的"

+ 204 - 61
src/pages/contract/contract.vue

@@ -1,87 +1,114 @@
 <template>
-  <view class="app-container pb-24">
-    <TopBar title="合同管理" show-back>
-      <template #right>
-        <view class="px-2 py-1 flex items-center" @click="goToAdd">
-          <text class="uni-icons uniui-plusempty text-primary text-xl"></text>
-          <text class="text-primary text-sm ml-1">新增</text>
-        </view>
-      </template>
-    </TopBar>
+  <view class="app-container list-page pb-24">
+    <TopBar title="合同管理" show-back />
 
-    <!-- 搜索栏 -->
-    <view class="bg-white px-4 py-3 sticky top-0 z-40">
-      <view class="flex items-center bg-gray-100 rounded-2xl px-4 py-2">
-        <text class="uni-icons uniui-search text-gray-400 mr-2 text-base"></text>
+    <!-- 工具栏:搜索 + 新增 -->
+    <view class="toolbar px-4 py-3 sticky top-0 z-40 flex items-center gap-2">
+      <view class="search-box flex-1 min-w-0 flex items-center rounded-2xl px-4 py-2" :class="{ 'search-focus': focusedSearch }">
+        <text class="uni-icons uniui-search search-icon mr-2 text-base"></text>
         <input
-          class="flex-1 bg-transparent text-sm text-gray-700"
+          class="flex-1 bg-transparent text-sm text-gray-800"
           placeholder="搜索合同号/备注"
+          placeholder-class="search-ph"
           v-model="searchKeyword"
           confirm-type="search"
+          @focus="focusedSearch = true"
+          @blur="focusedSearch = false"
           @confirm="onSearch"
         />
       </view>
+      <view
+        class="add-btn w-9 h-9 rounded-full flex items-center justify-center flex-shrink-0"
+        hover-class="add-btn-hover"
+        :hover-start-time="0"
+        :hover-stay-time="120"
+        @click="goToAdd"
+      >
+        <text class="uni-icons uniui-plusempty text-white text-xl"></text>
+      </view>
     </view>
 
     <!-- 状态筛选 -->
-    <uni-segmented-control
-      :current="currentFilterIndex"
-      :values="filterLabels"
-      style-type="text"
-      active-color="#4f8ef7"
-      @clickItem="onFilterChange"
-    />
+    <scroll-view scroll-x class="filter-bar" :show-scrollbar="false" enhanced>
+      <view class="flex items-center gap-2 px-4 py-3">
+        <view
+          v-for="(t, i) in filterTypes"
+          :key="t.value"
+          class="filter-tab flex-shrink-0 rounded-full px-4 py-1.5 text-sm border"
+          :class="i === currentFilterIndex ? 'filter-tab--active' : 'filter-tab--normal'"
+          @click="onFilterChange(i)"
+        >
+          {{ t.label }}
+        </view>
+      </view>
+    </scroll-view>
 
     <!-- 列表 -->
-    <view class="mt-2">
+    <view class="px-4 mt-3">
       <view v-if="contractStore.loading" class="py-16 text-center">
-        <text class="text-gray-400">加载中...</text>
+        <text class="loading-text">加载中...</text>
       </view>
 
       <view v-else-if="contractStore.contracts.length === 0">
         <EmptyState icon="uniui-file-text-filled" text="暂无合同" />
       </view>
 
-      <uni-list v-else border>
+      <view v-else class="gap-y-3">
         <uni-swipe-action
           v-for="contract in contractStore.contracts"
           :key="contract.id"
           :actions="swipeActions"
           @click="onSwipeAction(contract.id, $event)"
         >
-          <uni-list-item
-            :title="contract.contractNo"
-            :note="buildNote(contract)"
-            show-arrow
-            @click="goToDetail(contract.id)"
-          >
-            <template #header>
-              <view class="w-10 h-10 bg-green-50 rounded-xl flex items-center justify-center mr-3">
-                <text class="uni-icons uniui-file-text-filled text-green-500 text-lg"></text>
+          <view class="data-card" @click="goToDetail(contract.id)">
+            <!-- 头部:合同号 + 状态 -->
+            <view class="flex items-start justify-between">
+              <view class="min-w-0">
+                <text class="card-title block truncate">{{ contract.contractNo }}</text>
+                <text class="card-sub block mt-0.5">合同编号</text>
               </view>
-            </template>
-            <template #footer>
               <view
-                class="px-2 py-1 rounded text-xs"
+                class="status-pill flex-shrink-0 px-2.5 py-0.5 rounded-full text-xs ml-2"
                 :style="{ backgroundColor: statusBg(contract.status), color: statusColor(contract.status) }"
               >
                 {{ statusText(contract.status) }}
               </view>
-            </template>
-          </uni-list-item>
-        </uni-swipe-action>
-      </uni-list>
-    </view>
+            </view>
 
-    <!-- 加载更多 -->
-    <view v-if="contractStore.contracts.length > 0" class="px-4 mt-4 mb-4">
-      <view v-if="contractStore.hasMore" class="text-center py-4">
-        <text class="text-sm text-blue-500 font-medium" @click="loadMore">
-          {{ contractStore.loadingMore ? '加载中...' : '加载更多' }}
-        </text>
+            <!-- 金额高亮条 -->
+            <view class="amount-row mt-3 rounded-2xl px-3.5 py-2.5 flex items-center justify-between">
+              <view class="flex items-center">
+                <text class="uni-icons uniui-wallet-filled amount-icon text-sm mr-2"></text>
+                <text class="amount-label text-xs">合同金额</text>
+              </view>
+              <text class="amount-value">{{ formatMoney(contract.contractAmount) }}</text>
+            </view>
+
+            <!-- 底部信息行 -->
+            <view class="card-foot mt-3 pt-2.5 flex items-center justify-between">
+              <view class="foot-item flex items-center min-w-0 mr-3">
+                <text class="uni-icons uniui-wallet-filled foot-icon text-xs mr-1.5 flex-shrink-0"></text>
+                <text class="foot-text text-xs truncate">{{ getPaymentMethodText(contract.paymentMethod) || '未约定' }}</text>
+              </view>
+              <view class="foot-item flex items-center flex-shrink-0">
+                <text class="uni-icons uniui-calendar foot-icon text-xs mr-1.5"></text>
+                <text class="foot-text text-xs">{{ contract.signDate || '未签约' }}</text>
+              </view>
+            </view>
+          </view>
+        </uni-swipe-action>
       </view>
-      <view v-else class="text-center py-4">
-        <text class="text-xs text-gray-400">已经到底了</text>
+
+      <!-- 加载更多 -->
+      <view v-if="contractStore.contracts.length > 0" class="mt-2 mb-4">
+        <view v-if="contractStore.hasMore" class="text-center py-4">
+          <text class="load-more" @click="loadMore">
+            {{ contractStore.loadingMore ? '加载中...' : '加载更多' }}
+          </text>
+        </view>
+        <view v-else class="text-center py-4">
+          <text class="text-xs text-gray-400">已经到底了</text>
+        </view>
       </view>
     </view>
   </view>
@@ -91,7 +118,7 @@
 import { ref, onMounted, onUnmounted } from 'vue'
 import { onPullDownRefresh } from '@dcloudio/uni-app'
 import { useContractStore } from '@/stores/contract'
-import type { Contract, ContractStatus } from '@/types'
+import type { ContractStatus } from '@/types'
 import {
   formatMoney,
   getPaymentMethodText,
@@ -101,14 +128,12 @@ import {
 } from '@/utils'
 import TopBar from '@/components/common/TopBar.vue'
 import EmptyState from '@/components/common/EmptyState.vue'
-import UniList from '@/components/uni-list/uni-list.vue'
-import UniListItem from '@/components/uni-list/uni-list-item.vue'
 import UniSwipeAction from '@/components/uni-swipe-action/uni-swipe-action.vue'
-import UniSegmentedControl from '@/components/uni-segmented-control/uni-segmented-control.vue'
 
 const contractStore = useContractStore()
 const searchKeyword = ref('')
 const currentFilterIndex = ref(0)
+const focusedSearch = ref(false)
 
 const filterTypes: { value: ContractStatus | ''; label: string }[] = [
   { value: '', label: '全部' },
@@ -118,10 +143,9 @@ const filterTypes: { value: ContractStatus | ''; label: string }[] = [
   { value: 'expired', label: '已过期' },
   { value: 'terminated', label: '已终止' }
 ]
-const filterLabels = filterTypes.map(t => t.label)
 
 const swipeActions = [
-  { text: '编辑', color: '#4f8ef7', key: 'edit' },
+  { text: '编辑', color: '#368f6f', key: 'edit' },
   { text: '删除', color: '#ff3b30', key: 'delete' }
 ]
 
@@ -129,12 +153,6 @@ const statusText = getContractStatusText
 const statusColor = getContractStatusColor
 const statusBg = getContractStatusBgColor
 
-function buildNote(contract: Contract) {
-  const amount = formatMoney(contract.contractAmount)
-  const method = getPaymentMethodText(contract.paymentMethod)
-  return [amount, method, contract.signDate].filter(Boolean).join(' · ')
-}
-
 let searchTimer: ReturnType<typeof setTimeout> | null = null
 function onSearch() {
   if (searchTimer) clearTimeout(searchTimer)
@@ -201,3 +219,128 @@ onPullDownRefresh(() => {
   refreshData()
 })
 </script>
+
+<style scoped>
+.list-page {
+  background-color: #f6fbf9;
+}
+
+/* 工具栏:云雾白磨砂条 + 嫩芽浅底分割线 */
+.toolbar {
+  background-color: rgba(246, 251, 249, 0.88);
+  backdrop-filter: blur(12px);
+  -webkit-backdrop-filter: blur(12px);
+  border-bottom: 1px solid rgba(164, 216, 152, 0.28);
+}
+
+.search-box {
+  background-color: #ffffff;
+  border: 1px solid #e5e7eb;
+  transition: border-color 0.15s ease, box-shadow 0.15s ease, background-color 0.15s ease;
+}
+.search-focus {
+  background-color: #ffffff;
+  border-color: #368f6f;
+  box-shadow: 0 0 0 3px rgba(164, 216, 152, 0.30);
+}
+.search-icon {
+  color: #9ca3af;
+}
+.search-ph {
+  color: #9ca3af;
+}
+
+.add-btn {
+  background: linear-gradient(135deg, #368f6f 0%, #5ab8d0 100%);
+  box-shadow: 0 8px 18px -8px rgba(54, 143, 111, 0.55);
+}
+.add-btn-hover {
+  opacity: 0.92;
+  transform: scale(0.94);
+}
+
+/* 筛选胶囊条 */
+.filter-bar {
+  background-color: rgba(246, 251, 249, 0.88);
+  border-bottom: 1px solid rgba(164, 216, 152, 0.20);
+  white-space: nowrap;
+}
+.filter-tab {
+  transition: all 0.15s ease;
+}
+.filter-tab--normal {
+  background-color: #ffffff;
+  border-color: #e5e7eb;
+  color: #4b5563;
+}
+.filter-tab--active {
+  background: linear-gradient(135deg, #368f6f 0%, #5ab8d0 100%);
+  border-color: transparent;
+  color: #ffffff;
+  box-shadow: 0 6px 14px -8px rgba(54, 143, 111, 0.6);
+}
+
+/* 数据卡片 */
+.data-card {
+  background-color: #ffffff;
+  border-radius: 20px;
+  padding: 14px 16px;
+  box-shadow: 0 14px 34px -18px rgba(54, 143, 111, 0.35);
+  border: 1px solid rgba(164, 216, 152, 0.28);
+}
+.data-card:active {
+  transform: scale(0.99);
+  background-color: #fbfefb;
+}
+.card-title {
+  font-size: 15px;
+  font-weight: 600;
+  color: #1f2937;
+}
+.card-sub {
+  font-size: 11px;
+  color: #b6c9be;
+}
+.status-pill {
+  font-weight: 500;
+}
+
+/* 金额高亮条 */
+.amount-row {
+  background: linear-gradient(135deg, rgba(164, 216, 152, 0.22) 0%, rgba(90, 184, 208, 0.16) 100%);
+  border: 1px solid rgba(164, 216, 152, 0.30);
+}
+.amount-icon {
+  color: #368f6f;
+}
+.amount-label {
+  color: #6b8a7a;
+  font-weight: 500;
+}
+.amount-value {
+  font-size: 17px;
+  font-weight: 700;
+  color: #2f7a5e;
+  letter-spacing: 0.3px;
+}
+
+/* 底部信息行 */
+.card-foot {
+  border-top: 1px dashed rgba(164, 216, 152, 0.45);
+}
+.foot-icon {
+  color: #5ab8d0;
+}
+.foot-text {
+  color: #6b7280;
+}
+
+.loading-text {
+  color: #6b7280;
+}
+.load-more {
+  font-size: 14px;
+  font-weight: 500;
+  color: #368f6f;
+}
+</style>

+ 184 - 58
src/pages/customer/customer.vue

@@ -1,87 +1,109 @@
 <template>
-  <view class="app-container pb-24">
-    <TopBar title="客户管理" show-back>
-      <template #right>
-        <view class="px-2 py-1 flex items-center" @click="goToAdd">
-          <text class="uni-icons uniui-plusempty text-primary text-xl"></text>
-          <text class="text-primary text-sm ml-1">新增</text>
-        </view>
-      </template>
-    </TopBar>
+  <view class="app-container list-page pb-24">
+    <TopBar title="客户管理" show-back />
 
-    <!-- 搜索栏 -->
-    <view class="bg-white px-4 py-3 sticky top-0 z-40">
-      <view class="flex items-center bg-gray-100 rounded-2xl px-4 py-2">
-        <text class="uni-icons uniui-search text-gray-400 mr-2 text-base"></text>
+    <!-- 工具栏:搜索 + 新增 -->
+    <view class="toolbar px-4 py-3 sticky top-0 z-40 flex items-center gap-2">
+      <view class="search-box flex-1 min-w-0 flex items-center rounded-2xl px-4 py-2" :class="{ 'search-focus': focusedSearch }">
+        <text class="uni-icons uniui-search search-icon mr-2 text-base"></text>
         <input
-          class="flex-1 bg-transparent text-sm text-gray-700"
+          class="flex-1 bg-transparent text-sm text-gray-800"
           placeholder="搜索客户名称/联系人/电话"
+          placeholder-class="search-ph"
           v-model="searchKeyword"
           confirm-type="search"
+          @focus="focusedSearch = true"
+          @blur="focusedSearch = false"
           @confirm="onSearch"
         />
       </view>
+      <view
+        class="add-btn w-9 h-9 rounded-full flex items-center justify-center flex-shrink-0"
+        hover-class="add-btn-hover"
+        :hover-start-time="0"
+        :hover-stay-time="120"
+        @click="goToAdd"
+      >
+        <text class="uni-icons uniui-plusempty text-white text-xl"></text>
+      </view>
     </view>
 
     <!-- 状态筛选 -->
-    <uni-segmented-control
-      :current="currentFilterIndex"
-      :values="filterLabels"
-      style-type="text"
-      active-color="#4f8ef7"
-      @clickItem="onFilterChange"
-    />
+    <scroll-view scroll-x class="filter-bar" :show-scrollbar="false" enhanced>
+      <view class="flex items-center gap-2 px-4 py-3">
+        <view
+          v-for="(t, i) in filterTypes"
+          :key="t.value"
+          class="filter-tab flex-shrink-0 rounded-full px-4 py-1.5 text-sm border"
+          :class="i === currentFilterIndex ? 'filter-tab--active' : 'filter-tab--normal'"
+          @click="onFilterChange(i)"
+        >
+          {{ t.label }}
+        </view>
+      </view>
+    </scroll-view>
 
     <!-- 列表 -->
-    <view class="mt-2">
+    <view class="px-4 mt-3">
       <view v-if="customerStore.loading" class="py-16 text-center">
-        <text class="text-gray-400">加载中...</text>
+        <text class="loading-text">加载中...</text>
       </view>
 
       <view v-else-if="filteredCustomers.length === 0">
         <EmptyState icon="uniui-personadd-filled" text="暂无客户" />
       </view>
 
-      <uni-list v-else border>
+      <view v-else class="gap-y-3">
         <uni-swipe-action
           v-for="customer in filteredCustomers"
           :key="customer.id"
           :actions="swipeActions"
           @click="onSwipeAction(customer.id, $event)"
         >
-          <uni-list-item
-            :title="customer.customerName"
-            :note="buildNote(customer)"
-            show-arrow
-            @click="goToDetail(customer.id)"
-          >
-            <template #header>
-              <view class="w-10 h-10 bg-blue-50 rounded-xl flex items-center justify-center mr-3">
-                <text class="uni-icons uniui-person-filled text-blue-500 text-lg"></text>
-              </view>
-            </template>
-            <template #footer>
+          <view class="data-card" @click="goToDetail(customer.id)">
+            <!-- 名称 + 状态 -->
+            <view class="flex items-start justify-between gap-2">
+              <text class="card-title truncate">{{ customer.customerName }}</text>
               <view
-                class="px-2 py-1 rounded text-xs"
+                class="status-pill flex-shrink-0 px-2.5 py-0.5 rounded-full text-xs"
                 :style="{ backgroundColor: statusBg(customer.status), color: statusColor(customer.status) }"
               >
                 {{ statusText(customer.status) }}
               </view>
-            </template>
-          </uni-list-item>
+            </view>
+            <!-- 联系人 + 电话 -->
+            <view v-if="customer.contactName || customer.phone" class="mt-1.5 flex items-center flex-wrap">
+              <view v-if="customer.contactName" class="info-item flex items-center mr-4">
+                <text class="uni-icons uniui-person-filled info-icon text-xs mr-1"></text>
+                <text class="info-text text-xs truncate">{{ customer.contactName }}</text>
+              </view>
+              <view v-if="customer.phone" class="info-item flex items-center">
+                <text class="uni-icons uniui-phone-filled info-icon text-xs mr-1"></text>
+                <text class="info-text text-xs">{{ customer.phone }}</text>
+              </view>
+            </view>
+            <view v-else class="mt-1.5">
+              <text class="info-text text-xs">暂无联系人信息</text>
+            </view>
+            <!-- 地址 -->
+            <view v-if="fullAddress(customer)" class="card-foot mt-2.5 pt-2 flex items-center">
+              <text class="uni-icons uniui-location-filled foot-icon text-xs mr-1 flex-shrink-0"></text>
+              <text class="foot-text text-xs truncate flex-1">{{ fullAddress(customer) }}</text>
+            </view>
+          </view>
         </uni-swipe-action>
-      </uni-list>
-    </view>
-
-    <!-- 加载更多 -->
-    <view v-if="filteredCustomers.length > 0" class="px-4 mt-4 mb-4">
-      <view v-if="customerStore.hasMore" class="text-center py-4">
-        <text class="text-sm text-blue-500 font-medium" @click="loadMore">
-          {{ customerStore.loadingMore ? '加载中...' : '加载更多' }}
-        </text>
       </view>
-      <view v-else class="text-center py-4">
-        <text class="text-xs text-gray-400">已经到底了</text>
+
+      <!-- 加载更多 -->
+      <view v-if="filteredCustomers.length > 0" class="mt-2 mb-4">
+        <view v-if="customerStore.hasMore" class="text-center py-4">
+          <text class="load-more" @click="loadMore">
+            {{ customerStore.loadingMore ? '加载中...' : '加载更多' }}
+          </text>
+        </view>
+        <view v-else class="text-center py-4">
+          <text class="text-xs text-gray-400">已经到底了</text>
+        </view>
       </view>
     </view>
   </view>
@@ -99,14 +121,12 @@ import {
 } from '@/utils'
 import TopBar from '@/components/common/TopBar.vue'
 import EmptyState from '@/components/common/EmptyState.vue'
-import UniList from '@/components/uni-list/uni-list.vue'
-import UniListItem from '@/components/uni-list/uni-list-item.vue'
 import UniSwipeAction from '@/components/uni-swipe-action/uni-swipe-action.vue'
-import UniSegmentedControl from '@/components/uni-segmented-control/uni-segmented-control.vue'
 
 const customerStore = useCustomerStore()
 const searchKeyword = ref('')
 const currentFilterIndex = ref(0)
+const focusedSearch = ref(false)
 
 const filterTypes = [
   { value: 'all', label: '全部' },
@@ -114,10 +134,9 @@ const filterTypes = [
   { value: '2', label: '潜在客户' },
   { value: '0', label: '已停用' }
 ]
-const filterLabels = filterTypes.map(t => t.label)
 
 const swipeActions = [
-  { text: '编辑', color: '#4f8ef7', key: 'edit' },
+  { text: '编辑', color: '#368f6f', key: 'edit' },
   { text: '删除', color: '#ff3b30', key: 'delete' }
 ]
 
@@ -131,9 +150,10 @@ const filteredCustomers = computed(() => {
   return customerStore.customers.filter(c => String(c.status) === filterValue)
 })
 
-function buildNote(customer: Customer) {
-  const parts = [customer.contactName, customer.phone].filter(Boolean)
-  return parts.length ? parts.join(' · ') : (customer.detailAddress || '暂无联系人信息')
+function fullAddress(customer: Customer) {
+  return [customer.province, customer.city, customer.district, customer.street, customer.detailAddress]
+    .filter(Boolean)
+    .join('')
 }
 
 let searchTimer: ReturnType<typeof setTimeout> | null = null
@@ -200,3 +220,109 @@ onPullDownRefresh(() => {
   refreshData()
 })
 </script>
+
+<style scoped>
+.list-page {
+  background-color: #f6fbf9;
+}
+
+.toolbar {
+  background-color: rgba(246, 251, 249, 0.88);
+  backdrop-filter: blur(12px);
+  -webkit-backdrop-filter: blur(12px);
+  border-bottom: 1px solid rgba(164, 216, 152, 0.28);
+}
+
+.search-box {
+  background-color: #ffffff;
+  border: 1px solid #e5e7eb;
+  transition: border-color 0.15s ease, box-shadow 0.15s ease, background-color 0.15s ease;
+}
+.search-focus {
+  background-color: #ffffff;
+  border-color: #368f6f;
+  box-shadow: 0 0 0 3px rgba(164, 216, 152, 0.30);
+}
+.search-icon {
+  color: #9ca3af;
+}
+.search-ph {
+  color: #9ca3af;
+}
+
+.add-btn {
+  background: linear-gradient(135deg, #368f6f 0%, #5ab8d0 100%);
+  box-shadow: 0 8px 18px -8px rgba(54, 143, 111, 0.55);
+}
+.add-btn-hover {
+  opacity: 0.92;
+  transform: scale(0.94);
+}
+
+.filter-bar {
+  background-color: rgba(246, 251, 249, 0.88);
+  border-bottom: 1px solid rgba(164, 216, 152, 0.20);
+  white-space: nowrap;
+}
+.filter-tab {
+  transition: all 0.15s ease;
+}
+.filter-tab--normal {
+  background-color: #ffffff;
+  border-color: #e5e7eb;
+  color: #4b5563;
+}
+.filter-tab--active {
+  background: linear-gradient(135deg, #368f6f 0%, #5ab8d0 100%);
+  border-color: transparent;
+  color: #ffffff;
+  box-shadow: 0 6px 14px -8px rgba(54, 143, 111, 0.6);
+}
+
+.data-card {
+  background-color: #ffffff;
+  border-radius: 20px;
+  padding: 14px 16px;
+  box-shadow: 0 14px 34px -18px rgba(54, 143, 111, 0.35);
+  border: 1px solid rgba(164, 216, 152, 0.28);
+}
+.data-card:active {
+  transform: scale(0.99);
+  background-color: #fbfefb;
+}
+.card-title {
+  font-size: 15px;
+  font-weight: 600;
+  color: #1f2937;
+}
+.status-pill {
+  font-weight: 500;
+}
+
+.info-icon {
+  color: #5ab8d0;
+}
+.info-text {
+  color: #6b7280;
+}
+
+/* 底部地址行 */
+.card-foot {
+  border-top: 1px dashed rgba(164, 216, 152, 0.45);
+}
+.foot-icon {
+  color: #368f6f;
+}
+.foot-text {
+  color: #9ca3af;
+}
+
+.loading-text {
+  color: #6b7280;
+}
+.load-more {
+  font-size: 14px;
+  font-weight: 500;
+  color: #368f6f;
+}
+</style>

+ 96 - 13
src/pages/login/login.vue

@@ -42,13 +42,27 @@
           <text class="uni-icons uniui-locked field-icon"></text>
           <input
             class="field-input"
-            type="password"
+            type="text"
+            :password="!showPassword"
             placeholder="请输入密码"
             placeholder-class="field-ph"
             v-model="form.password"
             @focus="focusedField = 'password'"
             @blur="focusedField = ''"
           />
+          <text
+            class="uni-icons field-eye"
+            :class="showPassword ? 'uniui-eye' : 'uniui-eye-slash'"
+            @click="showPassword = !showPassword"
+          ></text>
+        </view>
+
+        <!-- 记住密码 -->
+        <view class="remember-row" @click="remember = !remember">
+          <view class="checkbox" :class="{ 'checkbox-checked': remember }">
+            <text v-if="remember" class="uni-icons uniui-checkmarkempty checkbox-icon"></text>
+          </view>
+          <text class="remember-text">记住密码</text>
         </view>
 
         <!-- 登录按钮:青山绿 → 流水青 -->
@@ -67,24 +81,45 @@
 </template>
 
 <script setup lang="ts">
-import { ref, reactive, computed } from 'vue'
+import { ref, reactive, computed, onMounted } from 'vue'
 import { useAuthStore } from '@/stores/auth'
 import StatusBar from '@/components/common/StatusBar.vue'
 
 const authStore = useAuthStore()
 
+const REMEMBER_KEY = 'login_remember'
+
 const form = reactive({ account: '', password: '' })
 const focusedField = ref('')
 const loading = ref(false)
+const remember = ref(false)
+const showPassword = ref(false)
 
 const canLogin = computed(() => form.account.trim() && form.password.trim())
 
+onMounted(() => {
+  const saved = uni.getStorageSync(REMEMBER_KEY)
+  if (saved && saved.account) {
+    form.account = saved.account
+    form.password = saved.password || ''
+    remember.value = true
+  }
+})
+
 async function handleLogin() {
   if (!canLogin.value || loading.value) return
   loading.value = true
   try {
     // 后端按账号识别角色,无需前端选择角色
     await authStore.doLogin(form.account.trim(), form.password)
+    if (remember.value) {
+      uni.setStorageSync(REMEMBER_KEY, {
+        account: form.account.trim(),
+        password: form.password
+      })
+    } else {
+      uni.removeStorageSync(REMEMBER_KEY)
+    }
     uni.switchTab({ url: '/pages/home/home' })
   } catch (error) {
     // 请求封装已统一 toast,避免重复提示
@@ -108,23 +143,27 @@ async function handleLogin() {
   background-color: #f6fbf9;     /* 云雾白兜底 */
 }
 
-/* 背景图铺满(竖屏 aspectFill 不变形) */
+/* 背景图铺满(竖屏 aspectFill 不变形;fixed+100vh 不依赖父高度) */
 .bg-img {
-  position: absolute;
+  position: fixed;
   top: 0;
-  left: 0;
+  left: 50%;
+  transform: translateX(-50%);
   width: 100%;
-  height: 100%;
+  max-width: 448px;
+  height: 100vh;
   z-index: 0;
 }
 
 /* 蒙版:顶部提亮、中部通透、底部微压,保证层级清晰 */
 .veil {
-  position: absolute;
+  position: fixed;
   top: 0;
-  left: 0;
+  left: 50%;
+  transform: translateX(-50%);
   width: 100%;
-  height: 100%;
+  max-width: 448px;
+  height: 100vh;
   z-index: 1;
   background: linear-gradient(
     180deg,
@@ -144,7 +183,7 @@ async function handleLogin() {
   flex-direction: column;
   justify-content: center;
   padding: 56px 32px calc(40px + env(safe-area-inset-bottom));
-  transform: translateY(3vh);    /* 居中基础上略向下 */
+  transform: translateY(-4vh);   /* 整体上移一些 */
 }
 
 /* 品牌区 */
@@ -206,9 +245,9 @@ async function handleLogin() {
 
 /* ==================== 登录卡片:磨砂玻璃 ==================== */
 .login-card {
-  background-color: rgba(255, 255, 255, 0.8);   /* 白色/80 */
-  backdrop-filter: blur(14px);
-  -webkit-backdrop-filter: blur(14px);
+  background-color: rgba(255, 255, 255, 0.55);  /* 更通透的白色 */
+  backdrop-filter: blur(18px);
+  -webkit-backdrop-filter: blur(18px);
   border: 1px solid rgba(164, 216, 152, 0.35);  /* 嫩芽绿描边 */
   border-radius: 24px;
   padding: 30px 26px 28px;
@@ -268,6 +307,50 @@ async function handleLogin() {
   font-size: 15px;
 }
 
+.field-eye {
+  width: 22px;
+  margin-left: 12px;
+  font-size: 18px;
+  color: #6f9686;
+  text-align: center;
+}
+
+/* ==================== 记住密码 ==================== */
+.remember-row {
+  display: flex;
+  align-items: center;
+  margin: -4px 2px 16px;
+}
+
+.checkbox {
+  width: 18px;
+  height: 18px;
+  border-radius: 6px;
+  background-color: #ffffff;
+  border: 1.5px solid #c4d4cc;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  transition: background-color 0.18s ease, border-color 0.18s ease;
+}
+
+.checkbox-checked {
+  background-color: #368f6f;       /* 青山绿 */
+  border-color: #368f6f;
+}
+
+.checkbox-icon {
+  font-size: 13px;
+  color: #ffffff;
+  line-height: 1;
+}
+
+.remember-text {
+  margin-left: 8px;
+  font-size: 14px;
+  color: #4d6b5e;
+}
+
 /* ==================== 登录按钮 ==================== */
 .login-btn {
   height: 54px;

+ 17 - 4
src/pages/task/task.vue

@@ -1,5 +1,5 @@
 <template>
-  <view class="app-container">
+  <view class="app-container task-page">
     <TaskListView ref="taskListRef" :role="role" />
   </view>
 </template>
@@ -17,12 +17,19 @@ const taskListRef = ref<InstanceType<typeof TaskListView> | null>(null)
 
 const role = computed(() => (authStore.userRole as 'sales' | 'dispatch' | 'construction') || 'sales')
 
+function loadTasks() {
+  if (authStore.userRole === 'construction') {
+    return taskStore.fetchMyTasks()
+  }
+  return taskStore.fetchTasks()
+}
+
 onLoad(() => {
-  taskStore.fetchTasks()
+  loadTasks()
 })
 
 onShow(() => {
-  taskStore.fetchTasks()
+  loadTasks()
   const pages = getCurrentPages()
   const page = pages[pages.length - 1] as any
   const role = authStore.userRole
@@ -31,7 +38,13 @@ onShow(() => {
 })
 
 onPullDownRefresh(async () => {
-  await taskStore.fetchTasks()
+  await loadTasks()
   uni.stopPullDownRefresh()
 })
 </script>
+
+<style scoped>
+.task-page {
+  background-color: #f6fbf9;
+}
+</style>

BIN
src/static/tabbar/home-active.png


BIN
src/static/tabbar/home.png


BIN
src/static/tabbar/my-active.png


BIN
src/static/tabbar/my.png


BIN
src/static/tabbar/task-active.png


BIN
src/static/tabbar/task.png


+ 15 - 27
src/stores/project.ts

@@ -1,46 +1,40 @@
 import { defineStore } from 'pinia'
 import { ref, computed } from 'vue'
-import type { Project } from '../types'
+import type { CooperationType, Project } from '../types'
 import { getProjectList, deleteProject as apiDeleteProject } from '../api/project'
 
 export const useProjectStore = defineStore('project', () => {
-  // State
   const projects = ref<Project[]>([])
   const currentProject = ref<Project | null>(null)
-  const filterType = ref<string>('all')
+  const filterType = ref<CooperationType | 'all'>('all')
   const loading = ref(false)
   const loadingMore = ref(false)
   const pageNum = ref(1)
   const pageSize = ref(10)
   const total = ref(0)
 
-  // Getters
   const filteredProjects = computed(() => {
     if (filterType.value === 'all') return projects.value
-    return projects.value.filter(p => p.type === filterType.value)
+    return projects.value.filter(p => p.cooperationType === filterType.value)
   })
 
   const totalCount = computed(() => total.value)
   const pendingCount = computed(() => projects.value.filter(p => p.status === 'pending').length)
-  const processingCount = computed(() => projects.value.filter(p => p.status === 'processing').length)
+  const processingCount = computed(() => projects.value.filter(p => p.status === 'in_progress').length)
   const completedCount = computed(() => projects.value.filter(p => p.status === 'completed').length)
   const hasMore = computed(() => projects.value.length < total.value)
 
-  // Actions
   async function fetchProjects(reset = false) {
     if (reset) {
       pageNum.value = 1
       projects.value = []
     }
-    
+
     loading.value = true
     try {
       const res = await getProjectList(pageNum.value, pageSize.value)
-      if (reset) {
-        projects.value = res.rows || []
-      } else {
-        projects.value = [...projects.value, ...(res.rows || [])]
-      }
+      const rows = res.rows || []
+      projects.value = reset ? rows : [...projects.value, ...rows]
       total.value = res.total || 0
     } finally {
       loading.value = false
@@ -49,7 +43,7 @@ export const useProjectStore = defineStore('project', () => {
 
   async function loadMore() {
     if (loadingMore.value || !hasMore.value) return
-    
+
     loadingMore.value = true
     pageNum.value++
     try {
@@ -62,22 +56,17 @@ export const useProjectStore = defineStore('project', () => {
     }
   }
 
-  function setFilter(type: string) {
+  function setFilter(type: CooperationType | 'all') {
     filterType.value = type
   }
 
-  function setCurrentProject(projectId: string) {
-    currentProject.value = projects.value.find(p => p.id === projectId) || null
-  }
-
-  function addProject(project: Project) {
-    projects.value.unshift(project)
-    total.value++
+  function setCurrentProject(projectId: number | string) {
+    currentProject.value = projects.value.find(p => String(p.projectId) === String(projectId)) || null
   }
 
-  async function deleteProject(projectId: string) {
-    await apiDeleteProject(Number(projectId))
-    projects.value = projects.value.filter(p => p.id !== projectId)
+  async function deleteProject(projectId: number | string) {
+    await apiDeleteProject(projectId)
+    projects.value = projects.value.filter(p => String(p.projectId) !== String(projectId))
     total.value--
   }
 
@@ -98,7 +87,6 @@ export const useProjectStore = defineStore('project', () => {
     loadMore,
     setFilter,
     setCurrentProject,
-    addProject,
-    deleteProject,
+    deleteProject
   }
 })

+ 14 - 2
src/stores/task.ts

@@ -1,7 +1,7 @@
 import { defineStore } from 'pinia'
 import { ref, computed } from 'vue'
 import type { Task, TimelineRecord } from '../types'
-import { getTaskList, getTaskDetail, advanceTaskStep, getTaskLogs } from '../api/task'
+import { getTaskList, getMyTasks, getTaskDetail, advanceTaskStep, getTaskLogs } from '../api/task'
 
 export const useTaskStore = defineStore('task', () => {
   // State
@@ -43,6 +43,17 @@ export const useTaskStore = defineStore('task', () => {
     }
   }
 
+  async function fetchMyTasks(planDate?: string) {
+    loading.value = true
+    try {
+      const res = await getMyTasks(planDate)
+      tasks.value = (res || []) as any
+      total.value = tasks.value.length
+    } finally {
+      loading.value = false
+    }
+  }
+
   async function fetchTaskDetail(taskId: number) {
     loading.value = true
     try {
@@ -67,7 +78,7 @@ export const useTaskStore = defineStore('task', () => {
   }
 
   function setCurrentTask(taskId: string) {
-    currentTask.value = tasks.value.find(t => t.id === taskId) || null
+    currentTask.value = tasks.value.find(t => String((t as any).id ?? (t as any).taskId) === String(taskId)) || null
   }
 
   async function advanceStep(taskId: number, step: number) {
@@ -97,6 +108,7 @@ export const useTaskStore = defineStore('task', () => {
     tomorrowCount,
     historyCount,
     fetchTasks,
+    fetchMyTasks,
     fetchTaskDetail,
     setFilter,
     setCurrentTask,

+ 9 - 0
src/styles/global.scss

@@ -478,6 +478,7 @@ page {
 .text-red-600 { color: #dc2626; }
 .text-purple-500 { color: #a855f7; }
 .text-cyan-500 { color: #06b6d4; }
+.text-yellow-600 { color: #d97706; }
 
 /* ---------- 背景颜色 ---------- */
 .bg-white { background-color: #ffffff; }
@@ -504,6 +505,10 @@ page {
 .bg-purple-100 { background-color: #f3e8ff; }
 .bg-cyan-50 { background-color: #ecfeff; }
 .bg-cyan-100 { background-color: #cffafe; }
+.bg-blue-500 { background-color: #3b82f6; }
+.bg-green-500 { background-color: #22c55e; }
+.bg-red-500 { background-color: #ef4444; }
+.bg-yellow-600 { background-color: #d97706; }
 .bg-surface { background-color: $surface; }
 
 /* ---------- 间距(padding) ---------- */
@@ -596,6 +601,8 @@ page {
 
 /* ---------- 尺寸(width/height) ---------- */
 .w-full { width: 100%; }
+.w-px { width: 1px; }
+.w-3 { width: 12px; }
 .w-4 { width: 16px; }
 .w-5 { width: 20px; }
 .w-6 { width: 24px; }
@@ -603,11 +610,13 @@ page {
 .w-10 { width: 40px; }
 .w-12 { width: 48px; }
 .w-16 { width: 64px; }
+.w-24 { width: 96px; }
 
 .min-w-0 { min-width: 0px; }
 .min-w-5 { min-width: 20px; }
 
 .h-full { height: 100%; }
+.h-3 { height: 12px; }
 .h-4 { height: 16px; }
 .h-5 { height: 20px; }
 .h-6 { height: 24px; }

+ 534 - 211
src/subPackages/pages-common/addProject.vue

@@ -1,184 +1,186 @@
 <template>
-  <view class="app-container pb-24">
-    <TopBar title="新增项目" show-back />
+  <view class="app-container detail-page pb-24">
+    <TopBar :title="mode === 'edit' ? '编辑项目' : '新增项目'" show-back />
 
-    <view class="px-4 py-4 gap-y-4">
-      <!-- 基本信息 -->
+    <view v-if="loadingDetail" class="py-20 text-center">
+      <text class="text-gray-400">加载中...</text>
+    </view>
+
+    <view v-else class="px-4 py-4 gap-y-4">
+      <!-- 关联合同 -->
       <view class="card p-4">
         <view class="detail-section-title mb-4">
-          <view class="w-8 h-8 rounded-lg bg-blue-100 flex items-center justify-center mr-3">
-            <text class="uni-icons uniui-folder-add-filled text-primary"></text>
+          <view class="w-8 h-8 rounded-lg bg-green-100 flex items-center justify-center mr-3">
+            <text class="uni-icons uniui-wallet-filled text-green-500"></text>
           </view>
-          <text>基本信息</text>
+          <text>关联合同</text>
         </view>
 
         <view class="gap-y-4">
           <view>
             <text class="form-label">
-              项目名称
+              选择合同
               <text class="text-red-500">*</text>
             </text>
-            <uni-easyinput class="form-input" placeholder="请输入项目名称" v-model="form.name" @blur="validateField('name')" />
-            <text v-if="errors.name" class="text-red-500 text-xs mt-1">{{ errors.name }}</text>
-          </view>
-
-          <view>
-            <text class="form-label">
-              项目类型
-              <text class="text-red-500">*</text>
-            </text>
-            <picker mode="selector" :range="types" :value="typeIndex" @change="onTypeChange">
-              <view class="form-select flex items-center" :class="{ 'text-gray-400': !form.type }">
-                <text class="uni-icons uniui-arrowdown text-gray-400 mr-2"></text>
-                {{ form.type || '请选择项目类型' }}
+            <picker mode="selector" :range="contractLabels" :value="selectedContractIndex" @change="onContractChange">
+              <view class="form-select flex items-center justify-between" :class="{ 'text-gray-400': selectedContractIndex < 0 }">
+                <text class="truncate flex-1">{{ selectedContractIndex >= 0 ? contractLabels[selectedContractIndex] : '请选择合同' }}</text>
+                <text class="uni-icons uniui-arrowdown text-gray-400 ml-2"></text>
               </view>
             </picker>
-            <text v-if="errors.type" class="text-red-500 text-xs mt-1">{{ errors.type }}</text>
+            <text v-if="errors.contractId" class="text-red-500 text-xs mt-1">{{ errors.contractId }}</text>
           </view>
 
-          <view class="grid grid-cols-3 gap-3">
-            <view>
-              <text class="form-label">省</text>
-              <uni-easyinput class="form-input" placeholder="省" v-model="form.province" />
+          <view v-if="selectedContract" class="bg-gray-50 rounded-xl p-3 gap-y-2">
+            <view class="flex justify-between text-sm">
+              <text class="text-gray-500">客户</text>
+              <text class="text-gray-800">{{ form.customerName || '-' }}</text>
             </view>
-            <view>
-              <text class="form-label">市</text>
-              <uni-easyinput class="form-input" placeholder="市" v-model="form.city" />
+            <view class="flex justify-between text-sm">
+              <text class="text-gray-500">主联系人</text>
+              <text class="text-gray-800">{{ form.primaryContactName || '-' }} {{ form.primaryContactPhone || '' }}</text>
             </view>
-            <view>
-              <text class="form-label">区/县</text>
-              <uni-easyinput class="form-input" placeholder="区/县" v-model="form.district" />
+            <view class="flex justify-between text-sm">
+              <text class="text-gray-500">合同金额</text>
+              <text class="text-gray-800">{{ formatMoney(form.contractAmount) }}</text>
+            </view>
+            <view class="flex justify-between text-sm">
+              <text class="text-gray-500">付款方式</text>
+              <text class="text-gray-800">{{ getPaymentMethodText(form.paymentMethod || '') }}</text>
+            </view>
+            <view class="flex justify-between text-sm">
+              <text class="text-gray-500">项目地址</text>
+              <text class="text-gray-800 flex-1 text-right ml-3">{{ derivedAddress || '-' }}</text>
             </view>
-          </view>
-
-          <view>
-            <text class="form-label">
-              详细地址
-              <text class="text-red-500">*</text>
-            </text>
-            <uni-easyinput class="form-input" placeholder="请输入详细地址" v-model="form.address" @blur="validateField('address')" />
-            <text v-if="errors.address" class="text-red-500 text-xs mt-1">{{ errors.address }}</text>
           </view>
         </view>
       </view>
 
-      <!-- 客户信息 -->
+      <!-- 基本信息 -->
       <view class="card p-4">
         <view class="detail-section-title mb-4">
-          <view class="w-8 h-8 rounded-lg bg-purple-100 flex items-center justify-center mr-3">
-            <text class="uni-icons uniui-person-filled text-purple-500"></text>
+          <view class="w-8 h-8 rounded-lg bg-blue-100 flex items-center justify-center mr-3">
+            <text class="uni-icons uniui-folder-add-filled text-primary"></text>
           </view>
-          <text>客户信息</text>
+          <text>基本信息</text>
         </view>
 
         <view class="gap-y-4">
           <view>
             <text class="form-label">
-              客户名称
+              项目名称
               <text class="text-red-500">*</text>
             </text>
-            <uni-easyinput class="form-input" placeholder="请输入客户名称" v-model="form.customerName" @blur="validateField('customerName')" />
-            <text v-if="errors.customerName" class="text-red-500 text-xs mt-1">{{ errors.customerName }}</text>
+            <uni-easyinput class="form-input" placeholder="请输入项目名称" v-model="form.projectName" @blur="validateField('projectName')" />
+            <text v-if="errors.projectName" class="text-red-500 text-xs mt-1">{{ errors.projectName }}</text>
           </view>
 
           <view>
             <text class="form-label">
-              联系电话
+              合作类型
               <text class="text-red-500">*</text>
             </text>
-            <input
-              class="form-input"
-              placeholder="请输入联系电话"
-              v-model="form.customerPhone"
-              type="number"
-              maxlength="11"
-              @blur="validateField('customerPhone')"
-            />
-            <text v-if="errors.customerPhone" class="text-red-500 text-xs mt-1">{{ errors.customerPhone }}</text>
+            <picker mode="selector" :range="cooperationLabels" :value="cooperationIndex" @change="onCooperationChange">
+              <view class="form-select flex items-center" :class="{ 'text-gray-400': !form.cooperationType }">
+                <text class="uni-icons uniui-arrowdown text-gray-400 mr-2"></text>
+                {{ form.cooperationType ? getCooperationTypeText(form.cooperationType) : '请选择合作类型' }}
+              </view>
+            </picker>
+            <text v-if="errors.cooperationType" class="text-red-500 text-xs mt-1">{{ errors.cooperationType }}</text>
           </view>
-        </view>
-      </view>
 
-      <!-- 合同信息 -->
-      <view class="card p-4">
-        <view class="detail-section-title mb-4">
-          <view class="w-8 h-8 rounded-lg bg-green-100 flex items-center justify-center mr-3">
-            <text class="uni-icons uniui-wallet-filled text-green-500"></text>
+          <view>
+            <text class="form-label">
+              所属行业
+              <text class="text-red-500">*</text>
+            </text>
+            <picker mode="selector" :range="industryLabels" :value="industryIndex" @change="onIndustryChange">
+              <view class="form-select flex items-center" :class="{ 'text-gray-400': !form.industry }">
+                <text class="uni-icons uniui-arrowdown text-gray-400 mr-2"></text>
+                {{ form.industry ? getIndustryText(form.industry) : '请选择所属行业' }}
+              </view>
+            </picker>
+            <text v-if="errors.industry" class="text-red-500 text-xs mt-1">{{ errors.industry }}</text>
           </view>
-          <text>合同信息</text>
-        </view>
 
-        <view class="gap-y-4">
           <view>
-            <text class="form-label">合同金额(元)</text>
-            <uni-easyinput class="form-input" placeholder="请输入合同金额" v-model="form.contractAmount" type="digit" />
+            <text class="form-label">
+              设施类型
+              <text class="text-red-500">*</text>
+              <text class="text-gray-400 text-xs ml-2">可多选</text>
+            </text>
+            <view class="flex flex-wrap gap-2 mt-1">
+              <view
+                v-for="ft in facilityTypes"
+                :key="ft.value"
+                class="px-3 py-2 rounded-xl text-sm border"
+                :class="form.facilityTypes.includes(ft.value) ? 'bg-primary text-white border-primary' : 'bg-gray-50 text-gray-600 border-gray-200'"
+                @click="toggleFacility(ft.value)"
+              >
+                {{ ft.label }}
+              </view>
+            </view>
+            <text v-if="errors.facilityTypes" class="text-red-500 text-xs mt-1">{{ errors.facilityTypes }}</text>
           </view>
 
           <view class="grid grid-cols-2 gap-3">
             <view>
-              <text class="form-label">合同开始日期</text>
-              <picker mode="date" :value="form.contractStartDate" @change="onStartDateChange">
-                <view class="form-select flex items-center text-gray-700">
-                  <text class="uni-icons uniui-calendar text-gray-400 mr-2"></text>
-                  {{ form.contractStartDate || '选择日期' }}
-                </view>
-              </picker>
+              <text class="form-label">
+                入住率(%)
+                <text class="text-red-500">*</text>
+              </text>
+              <uni-easyinput class="form-input" placeholder="如 90" v-model="form.occupancyRate" type="digit" @blur="validateField('occupancyRate')" />
+              <text v-if="errors.occupancyRate" class="text-red-500 text-xs mt-1">{{ errors.occupancyRate }}</text>
             </view>
-
             <view>
-              <text class="form-label">合同结束日期</text>
-              <picker mode="date" :value="form.contractEndDate" @change="onEndDateChange">
-                <view class="form-select flex items-center text-gray-700">
-                  <text class="uni-icons uniui-calendar text-gray-400 mr-2"></text>
-                  {{ form.contractEndDate || '选择日期' }}
-                </view>
-              </picker>
+              <text class="form-label">
+                建筑面积(㎡)
+                <text class="text-red-500">*</text>
+              </text>
+              <uni-easyinput class="form-input" placeholder="如 12000" v-model="form.buildingArea" type="digit" @blur="validateField('buildingArea')" />
+              <text v-if="errors.buildingArea" class="text-red-500 text-xs mt-1">{{ errors.buildingArea }}</text>
             </view>
           </view>
 
           <view>
-            <text class="form-label">服务周期</text>
-            <uni-easyinput class="form-input" placeholder="如:每周2次" v-model="form.serviceCycle" />
+            <text class="form-label">项目介绍</text>
+            <textarea
+              v-model="form.projectDescription"
+              class="w-full p-3 bg-surface rounded-xl text-sm text-gray-700"
+              placeholder="请输入项目介绍..."
+              :maxlength="500"
+            />
           </view>
         </view>
       </view>
 
-      <!-- 项目联系人 -->
+      <!-- 合同日期 -->
       <view class="card p-4">
         <view class="detail-section-title mb-4">
-          <view class="w-8 h-8 rounded-lg bg-orange-100 flex items-center justify-center mr-3">
-            <text class="uni-icons uniui-personadd-filled text-orange-500"></text>
+          <view class="w-8 h-8 rounded-lg bg-purple-100 flex items-center justify-center mr-3">
+            <text class="uni-icons uniui-calendar text-purple-500"></text>
           </view>
-          <text>项目联系人</text>
+          <text>合同日期</text>
         </view>
-
-        <view class="gap-y-4">
-          <view
-            v-for="(contact, index) in form.contacts"
-            :key="index"
-            class="border border-gray-200 rounded-xl p-4"
-          >
-            <view class="flex justify-between items-center mb-3">
-              <text class="text-sm font-semibold text-gray-700">联系人 {{ index + 1 }}</text>
-              <text class="text-red-500 text-sm flex items-center" @click="removeContact(index)">
-                <text class="uni-icons uniui-trash text-sm mr-1"></text>
-                删除
-              </text>
-            </view>
-            <view class="gap-y-3">
-              <uni-easyinput class="form-input mb-2" placeholder="姓名" v-model="contact.name" />
-              <uni-easyinput class="form-input mb-2" placeholder="角色/职位" v-model="contact.role" />
-              <uni-easyinput class="form-input" placeholder="联系电话" v-model="contact.phone" type="number" maxlength="11" />
-            </view>
+        <view class="grid grid-cols-2 gap-3">
+          <view>
+            <text class="form-label">合同开始</text>
+            <picker mode="date" :value="form.contractStartDate" @change="onStartDateChange">
+              <view class="form-select flex items-center text-gray-700">
+                <text class="uni-icons uniui-calendar text-gray-400 mr-2"></text>
+                {{ form.contractStartDate || '选择日期' }}
+              </view>
+            </picker>
+          </view>
+          <view>
+            <text class="form-label">合同结束</text>
+            <picker mode="date" :value="form.contractEndDate" @change="onEndDateChange">
+              <view class="form-select flex items-center text-gray-700">
+                <text class="uni-icons uniui-calendar text-gray-400 mr-2"></text>
+                {{ form.contractEndDate || '选择日期' }}
+              </view>
+            </picker>
           </view>
-
-          <button 
-            class="w-full py-3 border border-dashed border-gray-300 rounded-xl text-sm text-gray-500 flex items-center justify-center bg-transparent" 
-            @click="addContact"
-          >
-            <text class="uni-icons uniui-plusempty mr-1"></text>
-            添加联系人
-          </button>
         </view>
       </view>
 
@@ -188,7 +190,7 @@
           <view class="w-8 h-8 rounded-lg bg-cyan-100 flex items-center justify-center mr-3">
             <text class="uni-icons uniui-location-filled text-cyan-500"></text>
           </view>
-          <text>服务点信息</text>
+          <text>服务点与服务项</text>
         </view>
 
         <view class="gap-y-4">
@@ -208,13 +210,13 @@
               <uni-easyinput class="form-input mb-2" placeholder="服务点名称" v-model="point.name" />
               <uni-easyinput class="form-input mb-2" placeholder="服务点地址" v-model="point.address" />
               <uni-easyinput class="form-input mb-2" placeholder="服务类型" v-model="point.serviceType" />
-              <uni-easyinput class="form-input mb-2" placeholder="联系人" v-model="point.contactName" />
-              <uni-easyinput class="form-input" placeholder="联系电话" v-model="point.contactPhone" type="number" maxlength="11" />
+              <uni-easyinput class="form-input mb-2" placeholder="联系人" v-model="point.contact" />
+              <uni-easyinput class="form-input" placeholder="联系电话" v-model="point.phone" type="number" maxlength="11" />
             </view>
           </view>
 
-          <button 
-            class="w-full py-3 border border-dashed border-gray-300 rounded-xl text-sm text-gray-500 flex items-center justify-center bg-transparent" 
+          <button
+            class="add-point-btn w-full py-3 rounded-xl text-sm flex items-center justify-center"
             @click="addServicePoint"
           >
             <text class="uni-icons uniui-plusempty mr-1"></text>
@@ -248,7 +250,7 @@
         >
           <text v-if="submitting" class="uni-icons uniui-spinner-cycle mr-2 animate-spin"></text>
           <text class="uni-icons uniui-checkmarkempty mr-2" v-else></text>
-          {{ submitting ? '提交中...' : '提交项目' }}
+          {{ submitting ? '提交中...' : (mode === 'edit' ? '保存修改' : '提交项目') }}
         </button>
       </view>
     </view>
@@ -256,141 +258,462 @@
 </template>
 
 <script setup lang="ts">
-import { ref, reactive } from 'vue'
+import { ref, reactive, computed } from 'vue'
+import { onLoad } from '@dcloudio/uni-app'
 import TopBar from '../../components/common/TopBar.vue'
-import { createProject } from '../../api/project'
+import { createProject, updateProject, getProjectDetail } from '../../api/project'
+import { getContractList } from '../../api/contract'
+import { getCustomerDetail } from '../../api/customer'
+import {
+  getCooperationTypeText,
+  getIndustryText,
+  getPaymentMethodText,
+  formatMoney,
+  parseJsonArray
+} from '../../utils'
+import type {
+  Contract,
+  CooperationType,
+  FacilityType,
+  Industry,
+  PaymentMethod,
+  ProjectStatus,
+  ServicePoint
+} from '../../types'
+
+const mode = ref<'add' | 'edit'>('add')
+const editId = ref<number | string>('')
+const loadingDetail = ref(false)
+const submitting = ref(false)
 
-const types = ['包年', '单次', '季度', '月付']
-const typeIndex = ref(0)
+const cooperationTypes: { value: CooperationType; label: string }[] = [
+  { value: 'annual', label: '包年' },
+  { value: 'single', label: '单次' },
+  { value: 'quarter', label: '季度' },
+  { value: 'monthly', label: '月付' }
+]
+const cooperationLabels = cooperationTypes.map(t => t.label)
+
+const industries: { value: Industry; label: string }[] = [
+  { value: 'residential', label: '住宅' },
+  { value: 'commercial', label: '商业' },
+  { value: 'industrial', label: '工业' },
+  { value: 'municipal', label: '市政' },
+  { value: 'medical', label: '医疗' },
+  { value: 'education', label: '教育' },
+  { value: 'catering', label: '餐饮' }
+]
+const industryLabels = industries.map(t => t.label)
+
+const facilityTypes: { value: FacilityType; label: string }[] = [
+  { value: 'septic_tank', label: '化粪池' },
+  { value: 'direct_pump', label: '直抽直排' },
+  { value: 'grease_pipe', label: '油污管+化粪池' },
+  { value: 'pvc_pipe', label: 'PVC+化粪池' }
+]
+
+const contracts = ref<Contract[]>([])
+const contractLabels = computed(() =>
+  contracts.value.map(c => `${c.contractNo}${c.customerName ? ' - ' + c.customerName : ''}`)
+)
+const selectedContractIndex = computed(() => {
+  if (!form.contractId) return -1
+  return contracts.value.findIndex(c => String(c.id) === String(form.contractId))
+})
+const selectedContract = computed(() =>
+  selectedContractIndex.value >= 0 ? contracts.value[selectedContractIndex.value] : null
+)
+
+interface ProjectForm {
+  projectName: string
+  contractId: number | string
+  cooperationType: CooperationType | ''
+  industry: Industry | ''
+  facilityTypes: FacilityType[]
+  occupancyRate: string
+  buildingArea: string
+  contractStartDate: string
+  contractEndDate: string
+  projectDescription: string
+  servicePoints: ServicePoint[]
+  remark: string
+  customerId: number | string
+  customerName: string
+  customerPhone: string
+  contractNo: string
+  contractAmount?: number
+  paymentMethod: PaymentMethod | ''
+  address: string
+  province: string
+  city: string
+  district: string
+  street: string
+  detailAddress: string
+  latitude: string
+  longitude: string
+  primaryContactName: string
+  primaryContactPhone: string
+  backupContactName: string
+  backupContactPhone: string
+  status: ProjectStatus
+}
 
-const form = ref({
-  name: '',
-  type: '',
-  province: '',
-  city: '',
-  district: '',
-  address: '',
-  customerName: '',
-  customerPhone: '',
-  contractAmount: '',
+const form = reactive<ProjectForm>({
+  projectName: '',
+  contractId: '',
+  cooperationType: '',
+  industry: '',
+  facilityTypes: [],
+  occupancyRate: '',
+  buildingArea: '',
   contractStartDate: '',
   contractEndDate: '',
-  serviceCycle: '',
+  projectDescription: '',
+  servicePoints: [],
   remark: '',
-  contacts: [] as { name: string; role: string; phone: string }[],
-  servicePoints: [] as { name: string; address: string; serviceType: string; contactName: string; contactPhone: string }[],
+  customerId: '',
+  customerName: '',
+  customerPhone: '',
+  contractNo: '',
+  contractAmount: undefined,
+  paymentMethod: '',
+  address: '',
+  province: '',
+  city: '',
+  district: '',
+  street: '',
+  detailAddress: '',
+  latitude: '',
+  longitude: '',
+  primaryContactName: '',
+  primaryContactPhone: '',
+  backupContactName: '',
+  backupContactPhone: '',
+  status: 'pending'
 })
 
 const errors = reactive<Record<string, string>>({
-  name: '',
-  type: '',
-  address: '',
-  customerName: '',
-  customerPhone: '',
+  contractId: '',
+  projectName: '',
+  cooperationType: '',
+  industry: '',
+  facilityTypes: '',
+  occupancyRate: '',
+  buildingArea: ''
 })
 
-const submitting = ref(false)
+const cooperationIndex = computed(() =>
+  cooperationTypes.findIndex(t => t.value === form.cooperationType)
+)
+const industryIndex = computed(() =>
+  industries.findIndex(t => t.value === form.industry)
+)
 
-function onTypeChange(e: any) {
-  typeIndex.value = e.detail.value
-  form.value.type = types[typeIndex.value]
-  errors.type = ''
-}
+const derivedAddress = computed(() => {
+  if (form.address) return form.address
+  return [form.province, form.city, form.district, form.street, form.detailAddress].filter(Boolean).join('')
+})
 
-function onStartDateChange(e: any) {
-  form.value.contractStartDate = e.detail.value
+function joinAddress() {
+  return form.address || [form.province, form.city, form.district, form.street, form.detailAddress].filter(Boolean).join('')
 }
 
-function onEndDateChange(e: any) {
-  form.value.contractEndDate = e.detail.value
+function splitServicePeriod(period?: string) {
+  if (!period) return { start: '', end: '' }
+  const parts = period.split(/\s*至\s*/)
+  return { start: parts[0] || '', end: parts[1] || '' }
 }
 
-function validateField(field: string) {
-  const value = form.value[field as keyof typeof form.value] as string
-  errors[field] = ''
+async function onContractChange(e: any) {
+  const idx = Number(e.detail.value)
+  const contract = contracts.value[idx]
+  if (!contract) return
 
-  if (!value || !value.toString().trim()) {
-    const fieldNames: Record<string, string> = {
-      name: '项目名称',
-      address: '详细地址',
-      customerName: '客户名称',
-      customerPhone: '联系电话',
-    }
-    errors[field] = `请输入${fieldNames[field] || field}`
-    return false
-  }
+  form.contractId = contract.id
+  form.customerId = contract.customerId
+  form.customerName = contract.customerName || ''
+  form.contractNo = contract.contractNo
+  form.contractAmount = contract.contractAmount
+  form.paymentMethod = contract.paymentMethod
+
+  const period = splitServicePeriod(contract.servicePeriod)
+  if (!form.contractStartDate) form.contractStartDate = period.start
+  if (!form.contractEndDate) form.contractEndDate = period.end
 
-  if (field === 'customerPhone') {
-    const phoneRegex = /^1[3-9]\d{9}$/
-    if (!phoneRegex.test(value)) {
-      errors[field] = '请输入正确的手机号'
-      return false
+  errors.contractId = ''
+
+  try {
+    const customer = await getCustomerDetail(contract.customerId)
+    if (customer) {
+      form.customerName = customer.customerName || form.customerName
+      form.customerPhone = customer.phone || ''
+      form.primaryContactName = customer.contactName || ''
+      form.primaryContactPhone = customer.phone || ''
+      form.province = customer.province || ''
+      form.city = customer.city || ''
+      form.district = customer.district || ''
+      form.street = customer.street || ''
+      form.detailAddress = customer.detailAddress || ''
+      form.address = customer.address || joinAddress()
+      form.latitude = customer.latitude || ''
+      form.longitude = customer.longitude || ''
     }
+  } catch (e) {
+    // 客户详情获取失败不阻断,仅使用合同自带信息
   }
 
-  return true
+  if (!form.projectName && form.customerName) {
+    form.projectName = `${form.customerName}项目`
+  }
 }
 
-function validateForm() {
-  let isValid = true
-  const requiredFields = ['name', 'type', 'address', 'customerName', 'customerPhone']
+function onCooperationChange(e: any) {
+  form.cooperationType = cooperationTypes[Number(e.detail.value)].value
+  errors.cooperationType = ''
+}
 
-  for (const field of requiredFields) {
-    if (!validateField(field)) {
-      isValid = false
-    }
-  }
+function onIndustryChange(e: any) {
+  form.industry = industries[Number(e.detail.value)].value
+  errors.industry = ''
+}
 
-  return isValid
+function toggleFacility(value: FacilityType) {
+  const idx = form.facilityTypes.indexOf(value)
+  if (idx >= 0) form.facilityTypes.splice(idx, 1)
+  else form.facilityTypes.push(value)
+  errors.facilityTypes = ''
 }
 
-function addContact() {
-  form.value.contacts.push({ name: '', role: '', phone: '' })
+function onStartDateChange(e: any) {
+  form.contractStartDate = e.detail.value
 }
 
-function removeContact(index: number) {
-  form.value.contacts.splice(index, 1)
+function onEndDateChange(e: any) {
+  form.contractEndDate = e.detail.value
 }
 
 function addServicePoint() {
-  form.value.servicePoints.push({ name: '', address: '', serviceType: '', contactName: '', contactPhone: '' })
+  form.servicePoints.push({ name: '', address: '', serviceType: '', contact: '', phone: '' })
 }
 
 function removeServicePoint(index: number) {
-  form.value.servicePoints.splice(index, 1)
+  form.servicePoints.splice(index, 1)
+}
+
+function validateField(field: string) {
+  errors[field] = ''
+  switch (field) {
+    case 'projectName':
+      if (!form.projectName.trim()) errors.projectName = '请输入项目名称'
+      break
+    case 'occupancyRate':
+      if (form.occupancyRate === '' || isNaN(Number(form.occupancyRate))) errors.occupancyRate = '请输入入住率'
+      break
+    case 'buildingArea':
+      if (form.buildingArea === '' || isNaN(Number(form.buildingArea))) errors.buildingArea = '请输入建筑面积'
+      break
+  }
+  return !errors[field]
+}
+
+function validateForm() {
+  let valid = true
+  if (!form.contractId) { errors.contractId = '请选择关联合同'; valid = false } else errors.contractId = ''
+  if (!form.projectName.trim()) { errors.projectName = '请输入项目名称'; valid = false } else errors.projectName = ''
+  if (!form.cooperationType) { errors.cooperationType = '请选择合作类型'; valid = false } else errors.cooperationType = ''
+  if (!form.industry) { errors.industry = '请选择所属行业'; valid = false } else errors.industry = ''
+  if (form.facilityTypes.length === 0) { errors.facilityTypes = '请选择设施类型'; valid = false } else errors.facilityTypes = ''
+  if (form.occupancyRate === '' || isNaN(Number(form.occupancyRate))) { errors.occupancyRate = '请输入入住率'; valid = false } else errors.occupancyRate = ''
+  if (form.buildingArea === '' || isNaN(Number(form.buildingArea))) { errors.buildingArea = '请输入建筑面积'; valid = false } else errors.buildingArea = ''
+  return valid
+}
+
+function buildPayload() {
+  const points = form.servicePoints
+    .filter(p => p.name || p.address || p.contact || p.phone)
+    .map(p => ({
+      name: p.name || '',
+      address: p.address,
+      contact: p.contact,
+      phone: p.phone,
+      serviceType: p.serviceType
+    }))
+
+  return {
+    projectName: form.projectName.trim(),
+    contractId: form.contractId,
+    customerId: form.customerId || undefined,
+    customerName: form.customerName || undefined,
+    customerPhone: form.customerPhone || undefined,
+    contractNo: form.contractNo || undefined,
+    cooperationType: form.cooperationType as CooperationType,
+    industry: form.industry as Industry,
+    facilityTypes: JSON.stringify(form.facilityTypes),
+    occupancyRate: Number(form.occupancyRate),
+    buildingArea: Number(form.buildingArea),
+    contractStartDate: form.contractStartDate || undefined,
+    contractEndDate: form.contractEndDate || undefined,
+    projectDescription: form.projectDescription || undefined,
+    servicePoints: JSON.stringify(points),
+    address: joinAddress() || undefined,
+    province: form.province || undefined,
+    city: form.city || undefined,
+    district: form.district || undefined,
+    street: form.street || undefined,
+    detailAddress: form.detailAddress || undefined,
+    latitude: form.latitude || undefined,
+    longitude: form.longitude || undefined,
+    primaryContactName: form.primaryContactName || undefined,
+    primaryContactPhone: form.primaryContactPhone || undefined,
+    backupContactName: form.backupContactName || undefined,
+    backupContactPhone: form.backupContactPhone || undefined,
+    paymentMethod: (form.paymentMethod || undefined) as PaymentMethod | undefined,
+    status: form.status,
+    remark: form.remark || undefined
+  }
 }
 
 async function submitForm() {
   if (!validateForm()) {
-    uni.showToast({ title: '请完善表单信息', icon: 'none' })
+    uni.showToast({ title: '请完善必填信息', icon: 'none' })
     return
   }
 
   submitting.value = true
   try {
-    const payload = {
-      projectName: form.value.name,
-      projectType: form.value.type,
-      address: [form.value.province, form.value.city, form.value.district, form.value.address].filter(Boolean).join(''),
-      customerName: form.value.customerName,
-      customerPhone: form.value.customerPhone,
-      contractAmount: form.value.contractAmount ? Number(form.value.contractAmount) : undefined,
-      contractStartDate: form.value.contractStartDate || undefined,
-      contractEndDate: form.value.contractEndDate || undefined,
-      serviceCycle: form.value.serviceCycle || undefined,
-      remark: form.value.remark || undefined,
-      contacts: form.value.contacts.filter((c) => c.name || c.phone),
-      servicePoints: form.value.servicePoints.filter((p) => p.name || p.address),
+    if (mode.value === 'edit') {
+      await updateProject({ ...buildPayload(), projectId: editId.value })
+    } else {
+      await createProject(buildPayload())
     }
-    await createProject(payload)
-    uni.showToast({ title: '创建成功', icon: 'success' })
-    setTimeout(() => {
-      uni.navigateBack()
-    }, 1500)
-  } catch (error) {
-    uni.showToast({ title: '创建失败', icon: 'none' })
+    uni.showToast({ title: mode.value === 'edit' ? '保存成功' : '创建成功', icon: 'success' })
+    setTimeout(() => uni.navigateBack(), 1500)
+  } catch (e) {
+    uni.showToast({ title: mode.value === 'edit' ? '保存失败' : '创建失败', icon: 'none' })
   } finally {
     submitting.value = false
   }
 }
+
+async function loadContracts() {
+  try {
+    const res = await getContractList(1, 1000)
+    contracts.value = res.rows || []
+  } catch (e) {
+    contracts.value = []
+  }
+}
+
+async function loadDetail(id: number | string) {
+  loadingDetail.value = true
+  try {
+    const p = await getProjectDetail(id)
+    form.projectName = p.projectName || ''
+    form.contractId = p.contractId || ''
+    form.cooperationType = (p.cooperationType || '') as CooperationType | ''
+    form.industry = (p.industry || '') as Industry | ''
+    form.facilityTypes = parseJsonArray<FacilityType>(p.facilityTypes)
+    form.occupancyRate = p.occupancyRate !== undefined && p.occupancyRate !== null ? String(p.occupancyRate) : ''
+    form.buildingArea = p.buildingArea !== undefined && p.buildingArea !== null ? String(p.buildingArea) : ''
+    form.contractStartDate = p.contractStartDate || ''
+    form.contractEndDate = p.contractEndDate || ''
+    form.projectDescription = p.projectDescription || ''
+    form.servicePoints = parseJsonArray<ServicePoint>(p.servicePoints).map(sp => ({
+      name: sp.name || '',
+      address: sp.address,
+      contact: sp.contact,
+      phone: sp.phone,
+      serviceType: sp.serviceType
+    }))
+    form.remark = p.remark || ''
+    form.customerId = p.customerId || ''
+    form.customerName = p.customerName || ''
+    form.customerPhone = p.customerPhone || ''
+    form.contractNo = p.contractNo || ''
+    form.contractAmount = p.contractAmount
+    form.paymentMethod = (p.paymentMethod || '') as PaymentMethod | ''
+    form.address = p.address || ''
+    form.province = p.province || ''
+    form.city = p.city || ''
+    form.district = p.district || ''
+    form.street = p.street || ''
+    form.detailAddress = p.detailAddress || ''
+    form.latitude = p.latitude || ''
+    form.longitude = p.longitude || ''
+    form.primaryContactName = p.primaryContactName || ''
+    form.primaryContactPhone = p.primaryContactPhone || ''
+    form.backupContactName = p.backupContactName || ''
+    form.backupContactPhone = p.backupContactPhone || ''
+    form.status = p.status || 'pending'
+  } catch (e) {
+    uni.showToast({ title: '加载项目失败', icon: 'none' })
+  } finally {
+    loadingDetail.value = false
+  }
+}
+
+onLoad(async (options) => {
+  await loadContracts()
+  if (options?.mode === 'edit' && options?.id) {
+    mode.value = 'edit'
+    editId.value = options.id
+    await loadDetail(options.id)
+  }
+})
 </script>
+
+<style scoped>
+.detail-page {
+  background-color: #f6fbf9;
+}
+
+.card {
+  border-radius: 20px;
+  margin-bottom: 0;
+  border: 1px solid rgba(164, 216, 152, 0.28);
+  box-shadow: 0 14px 34px -18px rgba(54, 143, 111, 0.35);
+}
+
+.detail-section-title {
+  padding: 0;
+  border-bottom: none;
+}
+.detail-section-title .w-8 {
+  background-color: rgba(164, 216, 152, 0.2) !important;
+}
+.detail-section-title .uni-icons {
+  color: #368f6f !important;
+}
+
+/* 设施类型多选选中态 */
+.bg-primary {
+  background: linear-gradient(135deg, #368f6f 0%, #5ab8d0 100%);
+}
+.border-primary {
+  border-color: #368f6f;
+}
+
+.btn-primary {
+  background: linear-gradient(135deg, #368f6f 0%, #5ab8d0 100%);
+  box-shadow: 0 10px 22px -10px rgba(54, 143, 111, 0.55);
+}
+
+.form-input :deep(.uni-easyinput__content.focus) {
+  border-color: #368f6f !important;
+}
+
+.bg-surface {
+  background-color: #ffffff;
+  border: 1px solid #e5e7eb;
+}
+
+/* 添加服务点 */
+.add-point-btn {
+  background-color: rgba(164, 216, 152, 0.10);
+  border: 1px dashed rgba(54, 143, 111, 0.5);
+  color: #368f6f;
+}
+.add-point-btn:active {
+  background-color: rgba(164, 216, 152, 0.22);
+}
+</style>

+ 94 - 22
src/subPackages/pages-common/changePassword.vue

@@ -1,43 +1,45 @@
 
 <template>
-  <view class="app-container pb-20">
+  <view class="sub-page">
     <TopBar title="修改密码" show-back />
 
-    <view class="px-4 py-4">
-      <view class="card p-4">
+    <view class="px-4 pt-3">
+      <view class="glass-card p-4">
         <view class="form-group">
           <text class="form-label">
-            <text class="text-red-500">*</text>当前密码
+            <text class="required">*</text>当前密码
           </text>
-          <uni-easyinput class="form-input"type="password" v-model="currentPassword" placeholder="请输入当前密码" />
+          <uni-easyinput class="form-input" type="password" v-model="currentPassword" placeholder="请输入当前密码" />
         </view>
         <view class="form-group">
           <text class="form-label">
-            <text class="text-red-500">*</text>新密码
+            <text class="required">*</text>新密码
           </text>
-          <uni-easyinput class="form-input"type="password" v-model="newPassword" placeholder="请输入新密码(6-20位)" />
+          <uni-easyinput class="form-input" type="password" v-model="newPassword" placeholder="请输入新密码(6-20位)" />
         </view>
         <view class="form-group">
           <text class="form-label">
-            <text class="text-red-500">*</text>确认新密码
+            <text class="required">*</text>确认新密码
           </text>
-          <uni-easyinput class="form-input"type="password" v-model="confirmNewPassword" placeholder="请再次输入新密码" />
+          <uni-easyinput class="form-input" type="password" v-model="confirmNewPassword" placeholder="请再次输入新密码" />
         </view>
 
-        <view class="mt-4">
-          <view class="bg-blue-50 rounded-lg p-3 mb-4">
-            <text class="text-blue-600 text-sm block">ℹ️ 密码要求:</text>
-            <view class="text-gray-500 text-xs mt-2 gap-y-1">
-              <text class="block">• 长度6-20位</text>
-              <text class="block">• 必须包含字母和数字</text>
-              <text class="block">• 不能与当前密码相同</text>
-            </view>
-          </view>
-
-          <button class="w-full py-3 bg-blue-500 text-white rounded-xl font-medium" @click="submitChangePassword">
-            确认修改
-          </button>
+        <view class="rule-box">
+          <text class="rule-title">密码要求:</text>
+          <text class="rule-item">• 长度6-20位</text>
+          <text class="rule-item">• 必须包含字母和数字</text>
+          <text class="rule-item">• 不能与当前密码相同</text>
         </view>
+
+        <button
+          class="submit-btn"
+          hover-class="submit-btn-hover"
+          :hover-start-time="0"
+          :hover-stay-time="120"
+          @click="submitChangePassword"
+        >
+          确认修改
+        </button>
       </view>
     </view>
   </view>
@@ -90,7 +92,77 @@ function submitChangePassword() {
 </script>
 
 <style scoped>
+.sub-page {
+  max-width: 430px;
+  margin: 0 auto;
+  min-height: 100vh;
+  background-color: #f6fbf9;
+  padding-bottom: 24px;
+}
+
+.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;
+}
+
 .form-group {
   margin-bottom: 16px;
 }
+
+.form-label {
+  display: block;
+  margin-bottom: 8px;
+  font-size: 13px;
+  font-weight: 600;
+  color: #6b7280;
+}
+
+.required {
+  color: #ef4444;
+}
+
+.rule-box {
+  padding: 12px;
+  margin-bottom: 16px;
+  border-radius: 12px;
+  background-color: rgba(164, 216, 152, 0.14);
+  border: 1px solid rgba(164, 216, 152, 0.3);
+}
+
+.rule-title {
+  display: block;
+  font-size: 13px;
+  font-weight: 600;
+  color: #368f6f;
+  margin-bottom: 6px;
+}
+
+.rule-item {
+  display: block;
+  font-size: 12px;
+  color: #6b7280;
+  line-height: 1.8;
+}
+
+.submit-btn {
+  width: 100%;
+  padding: 12px 0;
+  border-radius: 14px;
+  font-size: 15px;
+  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;
+}
 </style>

+ 168 - 45
src/subPackages/pages-common/helpCenter.vue

@@ -1,75 +1,71 @@
 <template>
-  <view class="app-container pb-20">
+  <view class="sub-page">
     <TopBar title="帮助中心" show-back />
 
-    <view class="px-4 py-4">
+    <view class="px-4 pt-3">
       <!-- 角色提示 -->
-      <view class="card p-4 mb-4">
+      <view class="glass-card p-4">
         <view class="flex items-center">
-          <view class="w-10 h-10 rounded-full bg-primary-light flex items-center justify-center mr-3">
-            <text class="uni-icons uniui-person-filled text-primary"></text>
+          <view class="icon-chip mr-3">
+            <text class="uni-icons uniui-person-filled chip-glyph"></text>
           </view>
           <view>
-            <text class="text-sm text-gray-500">当前角色</text>
-            <text class="text-base font-semibold text-gray-800">{{ roleText }}</text>
+            <text class="meta-label">当前角色</text>
+            <text class="role-text">{{ roleText }}</text>
           </view>
         </view>
       </view>
 
       <!-- 常见问题 -->
-      <view class="card p-4 mb-4">
-        <view class="flex items-center mb-4">
-          <text class="uni-icons uniui-help-filled text-primary mr-2"></text>
-          <text class="text-base font-semibold text-gray-800">常见问题</text>
+      <view class="glass-card">
+        <view class="section-head">
+          <view class="section-bar mr-2"></view>
+          <text class="section-title flex-1">常见问题</text>
         </view>
-
-        <view class="gap-y-3">
-          <view
-            v-for="(faq, index) in roleFaqs"
-            :key="faq.id"
-            class="border-b border-gray-100 pb-3"
-            :class="index === roleFaqs.length - 1 ? 'border-b-0 pb-0' : ''"
-          >
-            <view class="flex justify-between items-center" @click="toggleFaq(index)">
-              <text class="text-sm text-gray-700 flex-1 pr-2">{{ faq.question }}</text>
-              <text
-                class="uni-icons uniui-arrowdown text-gray-400 text-xs transition-transform"
-                :class="expandedIndex === index ? 'rotate-180' : ''"
-              ></text>
-            </view>
-            <view v-if="expandedIndex === index" class="mt-2 text-gray-500 text-sm bg-gray-50 rounded-lg p-3">
-              {{ faq.answer }}
-            </view>
+        <view
+          v-for="(faq, index) in roleFaqs"
+          :key="faq.id"
+          class="faq-row"
+        >
+          <view class="flex justify-between items-center" @click="toggleFaq(index)">
+            <text class="faq-question flex-1 pr-2">{{ faq.question }}</text>
+            <text
+              class="uni-icons uniui-arrowdown faq-arrow transition-transform"
+              :class="expandedIndex === index ? 'rotate-180' : ''"
+            ></text>
+          </view>
+          <view v-if="expandedIndex === index" class="faq-answer">
+            {{ faq.answer }}
           </view>
         </view>
       </view>
 
       <!-- 联系客服 -->
-      <view class="card p-4">
-        <view class="flex items-center mb-4">
-          <text class="uni-icons uniui-phone-filled text-success mr-2"></text>
-          <text class="text-base font-semibold text-gray-800">联系客服</text>
+      <view class="glass-card">
+        <view class="section-head">
+          <view class="section-bar mr-2"></view>
+          <text class="section-title flex-1">联系客服</text>
         </view>
-        <view class="flex items-center justify-between py-3 border-b border-gray-100" @click="makePhoneCall('400-888-8888')">
+        <view class="contact-row" hover-class="row-hover" :hover-start-time="0" :hover-stay-time="120" @click="makePhoneCall('400-888-8888')">
           <view class="flex items-center">
-            <text class="uni-icons uniui-phone text-primary mr-3"></text>
-            <text class="text-sm text-gray-700">客服热线</text>
+            <text class="uni-icons uniui-phone contact-icon mr-3"></text>
+            <text class="contact-label">客服热线</text>
           </view>
-          <text class="text-primary text-sm">400-888-8888</text>
+          <text class="contact-value">400-888-8888</text>
         </view>
-        <view class="flex items-center justify-between py-3 border-b border-gray-100">
+        <view class="contact-row">
           <view class="flex items-center">
-            <text class="uni-icons uniui-email text-primary mr-3"></text>
-            <text class="text-sm text-gray-700">电子邮箱</text>
+            <text class="uni-icons uniui-email contact-icon mr-3"></text>
+            <text class="contact-label">电子邮箱</text>
           </view>
-          <text class="text-gray-600 text-sm">service@qingdaofu.com</text>
+          <text class="contact-plain">service@qingdaofu.com</text>
         </view>
-        <view class="flex items-center justify-between py-3">
+        <view class="contact-row">
           <view class="flex items-center">
-            <text class="uni-icons uniui-clock-filled text-primary mr-3"></text>
-            <text class="text-sm text-gray-700">服务时间</text>
+            <text class="uni-icons uniui-clock-filled contact-icon mr-3"></text>
+            <text class="contact-label">服务时间</text>
           </view>
-          <text class="text-gray-600 text-sm">9:00-18:00</text>
+          <text class="contact-plain">9:00-18:00</text>
         </view>
       </view>
     </view>
@@ -103,3 +99,130 @@ function makePhoneCall(phone: string) {
   uni.makePhoneCall({ phoneNumber: phone })
 }
 </script>
+
+<style scoped>
+.sub-page {
+  max-width: 430px;
+  margin: 0 auto;
+  min-height: 100vh;
+  background-color: #f6fbf9;
+  padding-bottom: 24px;
+}
+
+.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;
+}
+
+.icon-chip {
+  width: 42px;
+  height: 42px;
+  border-radius: 50%;
+  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;
+}
+
+.meta-label {
+  display: block;
+  font-size: 12px;
+  color: #9ca3af;
+}
+
+.role-text {
+  display: block;
+  font-size: 16px;
+  font-weight: 700;
+  color: #1f2937;
+}
+
+.section-head {
+  display: flex;
+  align-items: center;
+  padding: 16px 16px 12px;
+}
+
+.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;
+}
+
+.faq-row {
+  padding: 13px 16px;
+  border-top: 1px solid rgba(164, 216, 152, 0.18);
+}
+
+.faq-question {
+  font-size: 14px;
+  color: #4b5563;
+}
+
+.faq-arrow {
+  font-size: 12px;
+  color: #9ca3af;
+}
+
+.faq-answer {
+  margin-top: 10px;
+  padding: 10px 12px;
+  border-radius: 12px;
+  font-size: 13px;
+  line-height: 1.7;
+  color: #6b7280;
+  background-color: rgba(164, 216, 152, 0.12);
+}
+
+.contact-row {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  padding: 13px 16px;
+  border-top: 1px solid rgba(164, 216, 152, 0.18);
+}
+
+.row-hover {
+  background-color: rgba(164, 216, 152, 0.1);
+}
+
+.contact-icon {
+  font-size: 16px;
+  color: #368f6f;
+}
+
+.contact-label {
+  font-size: 14px;
+  color: #4b5563;
+}
+
+.contact-value {
+  font-size: 14px;
+  font-weight: 600;
+  color: #368f6f;
+}
+
+.contact-plain {
+  font-size: 14px;
+  color: #6b7280;
+}
+</style>

+ 127 - 27
src/subPackages/pages-common/knowledgeDetail.vue

@@ -1,38 +1,33 @@
-
-
 <template>
-  <view class="app-container pb-20">
+  <view class="sub-page">
     <TopBar title="知识详情" show-back />
 
-    <view class="px-4 py-4">
-      <view class="card overflow-hidden">
-        <!-- 封面 -->
-        <view class="h-48 bg-primary-light flex items-center justify-center">
-          <text class="text-primary text-5xl">{{ icon }}</text>
+    <view class="px-4 pt-3">
+      <view class="glass-card">
+        <!-- 封面 -->
+        <view class="cover">
+          <text class="cover-icon">{{ icon }}</text>
         </view>
 
         <view class="p-4">
-          <text class="text-lg font-bold text-gray-800 mb-2 block">{{ knowledge?.title || '知识详情' }}</text>
-          <view class="flex items-center text-gray-500 text-sm mb-4">
-            <text class="mr-1"></text>
-            <text>{{ knowledge?.createTime ? formatDate(knowledge.createTime, 'YYYY-MM-DD') : '' }}</text>
+          <text class="notice-title">{{ knowledge?.title || '知识详情' }}</text>
+          <view class="meta-row">
+            <text class="meta-text">{{ knowledge?.createTime ? formatDate(knowledge.createTime, 'YYYY-MM-DD') : '' }}</text>
+          </view>
+
+          <view class="summary-box">
+            <text class="summary-title">摘要</text>
+            <text class="summary-text">{{ knowledge?.summary || '暂无摘要' }}</text>
+          </view>
+
+          <view class="content-box">
+            <text class="summary-title">内容</text>
+            <text class="summary-text pre-wrap">{{ knowledge?.content || '暂无内容' }}</text>
           </view>
 
-          <view class="border-t border-gray-200 pt-4 gap-y-4">
-            <view class="bg-primary-light rounded-xl p-4">
-              <text class="font-medium text-primary mb-2 block text-sm"> 摘要</text>
-              <text class="text-gray-500 text-sm block">{{ knowledge?.summary || '暂无摘要' }}</text>
-            </view>
-
-            <view class="bg-success-light rounded-xl p-4">
-              <text class="font-medium text-success mb-2 block text-sm"> 内容</text>
-              <text class="text-gray-500 text-sm block whitespace-pre-wrap">{{ knowledge?.content || '暂无内容' }}</text>
-            </view>
-
-            <view class="bg-surface rounded-xl p-4">
-              <text class="font-medium text-gray-800 mb-2 block text-sm">️ 分类</text>
-              <text class="text-gray-500 text-sm block">{{ knowledge?.category || '-' }}</text>
-            </view>
+          <view class="meta-line">
+            <text class="meta-label">分类</text>
+            <text class="type-tag">{{ knowledge?.category || '-' }}</text>
           </view>
         </view>
       </view>
@@ -67,4 +62,109 @@ onMounted(() => {
 </script>
 
 <style scoped>
+.sub-page {
+  max-width: 430px;
+  margin: 0 auto;
+  min-height: 100vh;
+  background-color: #f6fbf9;
+  padding-bottom: 24px;
+}
+
+.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;
+}
+
+.cover {
+  height: 160px;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  background: linear-gradient(135deg, rgba(164, 216, 152, 0.35) 0%, rgba(90, 184, 208, 0.25) 100%);
+}
+
+.cover-icon {
+  font-size: 48px;
+}
+
+.notice-title {
+  display: block;
+  font-size: 17px;
+  font-weight: 700;
+  color: #1f2937;
+  line-height: 1.4;
+}
+
+.meta-row {
+  margin-top: 8px;
+  padding-bottom: 12px;
+  border-bottom: 1px solid rgba(164, 216, 152, 0.18);
+}
+
+.meta-text {
+  font-size: 12px;
+  color: #9ca3af;
+}
+
+.summary-box {
+  margin-top: 14px;
+  padding: 12px;
+  border-radius: 14px;
+  background-color: rgba(164, 216, 152, 0.14);
+  border: 1px solid rgba(164, 216, 152, 0.3);
+}
+
+.content-box {
+  margin-top: 12px;
+  padding: 12px;
+  border-radius: 14px;
+  background-color: #f9fafb;
+  border: 1px solid #f0f0f0;
+}
+
+.summary-title {
+  display: block;
+  margin-bottom: 6px;
+  font-size: 13px;
+  font-weight: 600;
+  color: #368f6f;
+}
+
+.summary-text {
+  display: block;
+  font-size: 13px;
+  line-height: 1.8;
+  color: #4b5563;
+}
+
+.pre-wrap {
+  white-space: pre-wrap;
+}
+
+.meta-line {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  margin-top: 14px;
+}
+
+.meta-label {
+  font-size: 13px;
+  color: #9ca3af;
+}
+
+.type-tag {
+  padding: 2px 10px;
+  border-radius: 999px;
+  font-size: 11px;
+  font-weight: 600;
+  color: #368f6f;
+  background-color: rgba(164, 216, 152, 0.24);
+}
 </style>

+ 96 - 35
src/subPackages/pages-common/knowledgeList.vue

@@ -1,32 +1,34 @@
-
-
 <template>
-  <view class="app-container pb-20">
+  <view class="sub-page">
     <TopBar title="行业知识" show-back />
 
-    <view class="px-4 py-4">
-      <view
-        v-for="(item, index) in knowledges"
-        :key="item.id"
-        class="knowledge-card flex"
-        @click="goToDetail(item)"
-      >
+    <view class="px-4 pt-3">
+      <view v-if="knowledges.length === 0">
+        <EmptyState message="暂无知识内容" />
+      </view>
+
+      <view v-else class="glass-card">
         <view
-          class="w-24 h-24 rounded-xl flex items-center justify-center mr-3 flex-shrink-0 bg-primary-light"
+          v-for="item in knowledges"
+          :key="item.id"
+          class="list-row"
+          hover-class="row-hover"
+          :hover-start-time="0"
+          :hover-stay-time="120"
+          @click="goToDetail(item)"
         >
-          <text class="text-primary text-2xl">{{ getIcon(index) }}</text>
-        </view>
-        <view class="flex-1 flex flex-col justify-center">
-          <text class="font-medium text-gray-800 mb-1 text-sm">{{ item.title }}</text>
-          <text class="text-gray-500 text-xs line-clamp-2 mb-1">{{ item.summary }}</text>
-          <text class="text-gray-400 text-xs">{{ formatDate(item.createTime, 'YYYY-MM-DD') }}</text>
+          <view class="icon-chip mr-3">
+            <text class="uni-icons uniui-folder-add-filled chip-glyph"></text>
+          </view>
+          <view class="flex-1 min-w-0 mr-2">
+            <text class="row-title truncate">{{ item.title }}</text>
+            <text class="row-sub row-clamp">{{ item.summary }}</text>
+            <text class="row-date">{{ formatDate(item.createTime, 'YYYY-MM-DD') }}</text>
+          </view>
+          <text class="uni-icons uniui-arrowright row-arrow"></text>
         </view>
       </view>
     </view>
-
-    <view v-if="knowledges.length === 0" class="px-4 mt-3">
-      <EmptyState message="暂无知识内容" />
-    </view>
   </view>
 </template>
 
@@ -41,12 +43,6 @@ import type { Knowledge } from '@/types'
 const commonStore = useCommonStore()
 const knowledges = computed(() => commonStore.knowledges)
 
-const icons = ['', '', '', '', '']
-
-function getIcon(index: number): string {
-  return icons[index % icons.length]
-}
-
 function goToDetail(item: Knowledge) {
   commonStore.setCurrentKnowledge(item.id)
   uni.setStorageSync('current_knowledge', item)
@@ -55,22 +51,87 @@ function goToDetail(item: Knowledge) {
 </script>
 
 <style scoped>
-.knowledge-card {
-  background: white;
-  padding: 12px;
-  border-radius: 12px;
+.sub-page {
+  max-width: 430px;
+  margin: 0 auto;
+  min-height: 100vh;
+  background-color: #f6fbf9;
+  padding-bottom: 24px;
+}
+
+.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;
-  border: 1px solid #e5e7eb;
 }
 
-.knowledge-card:active {
-  opacity: 0.95;
+.list-row {
+  display: flex;
+  align-items: center;
+  padding: 13px 16px;
+  border-top: 1px solid rgba(164, 216, 152, 0.18);
+}
+
+.list-row:first-child {
+  border-top: none;
 }
 
-.line-clamp-2 {
+.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;
+}
+
+.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-clamp {
   display: -webkit-box;
   -webkit-line-clamp: 2;
   -webkit-box-orient: vertical;
   overflow: hidden;
 }
+
+.row-date {
+  display: block;
+  margin-top: 3px;
+  font-size: 12px;
+  color: #9ca3af;
+}
+
+.row-arrow {
+  font-size: 16px;
+  color: #c4d0cb;
+  flex-shrink: 0;
+}
 </style>

+ 104 - 27
src/subPackages/pages-common/messageDetail.vue

@@ -1,42 +1,38 @@
 
 <template>
-  <view class="app-container pb-20">
+  <view class="sub-page">
     <TopBar title="消息详情" show-back />
 
-    <view class="px-4 py-4">
-      <view class="card p-4">
-        <view class="flex items-center justify-between mb-3">
-          <text class="text-base font-bold text-gray-800">{{ message?.title || '消息详情' }}</text>
+    <view class="px-4 pt-3">
+      <view class="glass-card p-5">
+        <text class="notice-title">{{ message?.title || '消息详情' }}</text>
+        <view class="meta-row">
+          <text class="meta-text">{{ message?.createTime ? formatDate(message.createTime, 'YYYY-MM-DD HH:mm') : '' }}</text>
         </view>
-        <view class="flex items-center text-gray-400 text-sm mb-4">
-          <text class="mr-1"></text>
-          <text>{{ message?.createTime ? formatDate(message.createTime, 'YYYY-MM-DD HH:mm') : '' }}</text>
-        </view>
-        <view class="border-t border-gray-100 pt-4">
-          <text class="text-gray-600 text-sm leading-relaxed block mb-4">{{ message?.content || '暂无内容' }}</text>
 
-          <view v-if="message?.type === 'task'" class="bg-gray-50 rounded-lg p-4 mb-4">
-            <view class="flex justify-between text-sm mb-2">
-              <text class="text-gray-500">任务名称:</text>
-              <text class="text-gray-700">{{ message.title }}</text>
+        <view class="notice-body">
+          <text class="body-p">{{ message?.content || '暂无内容' }}</text>
+
+          <view v-if="message?.type === 'task'" class="task-box">
+            <view class="meta-line">
+              <text class="meta-label">任务名称</text>
+              <text class="meta-value">{{ message.title }}</text>
             </view>
-            <view class="flex justify-between text-sm mb-2">
-              <text class="text-gray-500">执行班组:</text>
-              <text class="text-gray-700">张师傅组</text>
+            <view class="meta-line">
+              <text class="meta-label">执行班组</text>
+              <text class="meta-value">张师傅组</text>
             </view>
-            <view class="flex justify-between text-sm mb-2">
-              <text class="text-gray-500">预计到达:</text>
-              <text class="text-gray-700">09:30</text>
+            <view class="meta-line">
+              <text class="meta-label">预计到达</text>
+              <text class="meta-value">09:30</text>
             </view>
-            <view class="flex justify-between text-sm">
-              <text class="text-gray-500">联系电话:</text>
-              <text class="text-gray-700">139****6666</text>
+            <view class="meta-line">
+              <text class="meta-label">联系电话</text>
+              <text class="meta-value">139****6666</text>
             </view>
           </view>
 
-          <text class="text-gray-600 text-sm leading-relaxed block">
-            施工过程中如有任何问题,请及时联系调度中心。服务完成后,请及时验收确认。
-          </text>
+          <text class="body-p">施工过程中如有任何问题,请及时联系调度中心。服务完成后,请及时验收确认。</text>
         </view>
       </view>
     </view>
@@ -58,3 +54,84 @@ onMounted(() => {
   }
 })
 </script>
+
+<style scoped>
+.sub-page {
+  max-width: 430px;
+  margin: 0 auto;
+  min-height: 100vh;
+  background-color: #f6fbf9;
+  padding-bottom: 24px;
+}
+
+.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;
+}
+
+.notice-title {
+  display: block;
+  font-size: 17px;
+  font-weight: 700;
+  color: #1f2937;
+  line-height: 1.4;
+}
+
+.meta-row {
+  margin-top: 8px;
+  padding-bottom: 12px;
+  border-bottom: 1px solid rgba(164, 216, 152, 0.18);
+}
+
+.meta-text {
+  font-size: 12px;
+  color: #9ca3af;
+}
+
+.notice-body {
+  padding-top: 12px;
+}
+
+.body-p {
+  display: block;
+  margin-bottom: 12px;
+  font-size: 14px;
+  line-height: 1.8;
+  color: #4b5563;
+}
+
+.task-box {
+  padding: 12px;
+  margin-bottom: 12px;
+  border-radius: 14px;
+  background-color: rgba(164, 216, 152, 0.14);
+  border: 1px solid rgba(164, 216, 152, 0.3);
+}
+
+.meta-line {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  margin-bottom: 6px;
+}
+
+.meta-line:last-child {
+  margin-bottom: 0;
+}
+
+.meta-label {
+  font-size: 12px;
+  color: #9ca3af;
+}
+
+.meta-value {
+  font-size: 13px;
+  color: #4b5563;
+}
+</style>

+ 148 - 39
src/subPackages/pages-common/messageList.vue

@@ -1,48 +1,52 @@
 
 <template>
-  <view class="app-container pb-20">
+  <view class="sub-page">
     <TopBar title="消息通知" show-back>
       <template #right>
-        <text class="text-blue-500 text-sm" @click="markAllRead">全部已读</text>
+        <text class="right-link" @click="markAllRead">全部已读</text>
       </template>
     </TopBar>
 
-    <!-- 筛选标签 -->
-    <view class="bg-white px-4 py-3 flex gap-2">
-      <view
-        v-for="tab in filterTabs"
-        :key="tab.value"
-        class="px-4 py-2 rounded-full text-sm whitespace-nowrap"
-        :class="currentFilter === tab.value ? 'bg-blue-500 text-white' : 'bg-gray-100 text-gray-600'"
-        @click="currentFilter = tab.value"
-      >
-        {{ tab.label }}
+    <view class="px-4 pt-3">
+      <!-- 筛选 -->
+      <view class="filter-bar mb-3">
+        <view
+          v-for="tab in filterTabs"
+          :key="tab.value"
+          class="filter-chip"
+          :class="{ 'filter-chip-active': currentFilter === tab.value }"
+          @click="currentFilter = tab.value"
+        >
+          {{ tab.label }}
+        </view>
       </view>
-    </view>
 
-    <!-- 消息列表 -->
-    <view class="bg-white">
-      <view
-        v-for="msg in filteredMessages"
-        :key="msg.id"
-        class="notice-item"
-        @click="goToDetail(msg)"
-      >
-        <view class="flex justify-between items-start mb-2">
-          <view class="flex items-center flex-1">
-            <text v-if="!msg.read" class="w-2 h-2 bg-red-500 rounded-full mr-2 flex-shrink-0"></text>
-            <text v-else class="w-2 h-2 rounded-full mr-2 flex-shrink-0"></text>
-            <text class="font-medium text-sm" :class="msg.read ? 'text-gray-600' : 'text-gray-800'">{{ msg.title }}</text>
+      <view v-if="filteredMessages.length === 0">
+        <EmptyState message="暂无消息" />
+      </view>
+
+      <view v-else class="glass-card">
+        <view
+          v-for="msg in filteredMessages"
+          :key="msg.id"
+          class="list-row"
+          hover-class="row-hover"
+          :hover-start-time="0"
+          :hover-stay-time="120"
+          @click="goToDetail(msg)"
+        >
+          <view class="icon-chip mr-3">
+            <text class="uni-icons uniui-chatbubble-filled chip-glyph"></text>
+            <view v-if="!msg.read" class="unread-dot"></view>
+          </view>
+          <view class="flex-1 min-w-0 mr-2">
+            <text class="row-title truncate" :class="{ 'row-title-read': msg.read }">{{ msg.title }}</text>
+            <text class="row-sub row-clamp">{{ msg.content }}</text>
           </view>
-          <text class="text-gray-400 text-xs whitespace-nowrap">{{ formatRelativeTime(msg.createTime) }}</text>
+          <text class="row-date flex-shrink-0">{{ formatRelativeTime(msg.createTime) }}</text>
         </view>
-        <text class="text-gray-500 text-sm line-clamp-2">{{ msg.content }}</text>
       </view>
     </view>
-
-    <view v-if="filteredMessages.length === 0" class="px-4 mt-3">
-      <EmptyState message="暂无消息" />
-    </view>
   </view>
 </template>
 
@@ -85,20 +89,125 @@ function goToDetail(msg: typeof filteredMessages.value[0]) {
 </script>
 
 <style scoped>
-.notice-item {
-  background: white;
-  padding: 16px;
-  border-bottom: 1px solid #f0f0f0;
+.sub-page {
+  max-width: 430px;
+  margin: 0 auto;
+  min-height: 100vh;
+  background-color: #f6fbf9;
+  padding-bottom: 24px;
+}
+
+.right-link {
+  font-size: 13px;
+  color: #368f6f;
+}
+
+/* 筛选条:默认白底灰边,激活态绿色 */
+.filter-bar {
+  display: flex;
+  gap: 8px;
+  overflow-x: auto;
+}
+
+.filter-chip {
+  padding: 6px 16px;
+  border-radius: 999px;
+  font-size: 13px;
+  color: #6b7280;
+  background-color: #ffffff;
+  border: 1px solid #e5e7eb;
+  flex-shrink: 0;
+}
+
+.filter-chip-active {
+  color: #ffffff;
+  font-weight: 600;
+  background: linear-gradient(135deg, #368f6f 0%, #4ba98a 100%);
+  border-color: transparent;
+}
+
+.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;
 }
 
-.notice-item:active {
-  background: #f9fafb;
+.list-row {
+  display: flex;
+  align-items: center;
+  padding: 13px 16px;
+  border-top: 1px solid rgba(164, 216, 152, 0.18);
 }
 
-.line-clamp-2 {
+.list-row:first-child {
+  border-top: none;
+}
+
+.row-hover {
+  background-color: rgba(164, 216, 152, 0.1);
+}
+
+.icon-chip {
+  position: relative;
+  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;
+}
+
+.unread-dot {
+  position: absolute;
+  top: -3px;
+  right: -3px;
+  width: 10px;
+  height: 10px;
+  border-radius: 50%;
+  background-color: #ef4444;
+  border: 2px solid #ffffff;
+}
+
+.row-title {
+  display: block;
+  font-size: 14px;
+  font-weight: 600;
+  color: #1f2937;
+}
+
+.row-title-read {
+  font-weight: 500;
+  color: #6b7280;
+}
+
+.row-sub {
+  display: block;
+  margin-top: 3px;
+  font-size: 12px;
+  color: #9ca3af;
+}
+
+.row-clamp {
   display: -webkit-box;
   -webkit-line-clamp: 2;
   -webkit-box-orient: vertical;
   overflow: hidden;
 }
+
+.row-date {
+  font-size: 12px;
+  color: #9ca3af;
+}
 </style>

+ 116 - 27
src/subPackages/pages-common/noticeDetail.vue

@@ -1,37 +1,37 @@
 
 <template>
-  <view class="app-container pb-20">
+  <view class="sub-page">
     <TopBar title="通知详情" show-back />
-    
+
     <view v-if="loading" class="py-20 text-center">
-      <text class="text-gray-400">加载中...</text>
+      <text class="empty-text">加载中...</text>
     </view>
-    
-    <view v-else-if="notice" class="px-4 py-4 gap-y-4">
-      <view class="card p-4">
-        <view class="flex justify-between items-start mb-3">
-          <view class="flex items-center">
-            <view v-if="notice.priority === 3" class="w-2 h-2 bg-red-500 rounded-full mr-2"></view>
-            <text class="text-lg font-semibold text-gray-800">{{ notice.title }}</text>
+
+    <view v-else-if="notice" class="px-4 pt-3">
+      <view class="glass-card p-5">
+        <view class="flex items-start justify-between mb-3">
+          <view class="flex items-center flex-1 min-w-0 mr-2">
+            <view v-if="notice.priority === 3" class="priority-dot mr-2"></view>
+            <text class="notice-title truncate">{{ notice.title }}</text>
           </view>
-          <view class="tag" :class="getTypeClass(notice.type)">
+          <text class="type-tag flex-shrink-0" :class="getTypeClass(notice.type)">
             {{ getTypeText(notice.type) }}
-          </view>
+          </text>
         </view>
-        
-        <view class="text-sm text-gray-500 mb-4">
-          <text class="block">发布人: {{ notice.publisherName }}</text>
-          <text class="block mt-1">发布时间: {{ formatDate(notice.publishTime) }}</text>
-          <text class="block mt-1">阅读量: {{ notice.readCount }}</text>
+
+        <view class="meta-row">
+          <text class="meta-text">发布人: {{ notice.publisherName }}</text>
+          <text class="meta-text mt-1 block">发布时间: {{ formatDate(notice.publishTime) }}</text>
+          <text class="meta-text mt-1 block">阅读量: {{ notice.readCount }}</text>
         </view>
-        
-        <view class="text-sm text-gray-800 leading-relaxed">
-          {{ notice.content }}
+
+        <view class="notice-body">
+          <text class="body-p">{{ notice.content }}</text>
         </view>
       </view>
     </view>
-    
-    <view v-else class="px-4 py-6">
+
+    <view v-else class="px-4 pt-3">
       <EmptyState message="通知不存在" />
     </view>
   </view>
@@ -62,11 +62,11 @@ function getTypeText(type: string) {
 
 function getTypeClass(type: string) {
   const map: Record<string, string> = {
-    system: 'tag-blue',
-    emergency: 'tag-red',
-    task: 'tag-green',
+    system: 'type-tag-green',
+    emergency: 'type-tag-red',
+    task: 'type-tag-gold',
   }
-  return map[type] || 'tag-gray'
+  return map[type] || 'type-tag-gray'
 }
 
 onMounted(async () => {
@@ -75,7 +75,7 @@ onMounted(async () => {
     loading.value = false
     return
   }
-  
+
   try {
     const res = await getNoticeDetail(Number(noticeId))
     notice.value = res
@@ -86,3 +86,92 @@ onMounted(async () => {
   }
 })
 </script>
+
+<style scoped>
+.sub-page {
+  max-width: 430px;
+  margin: 0 auto;
+  min-height: 100vh;
+  background-color: #f6fbf9;
+  padding-bottom: 24px;
+}
+
+.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;
+}
+
+.notice-title {
+  font-size: 17px;
+  font-weight: 700;
+  color: #1f2937;
+  line-height: 1.4;
+}
+
+.priority-dot {
+  width: 8px;
+  height: 8px;
+  border-radius: 50%;
+  background-color: #ef4444;
+  flex-shrink: 0;
+}
+
+.meta-row {
+  padding-bottom: 12px;
+  border-bottom: 1px solid rgba(164, 216, 152, 0.18);
+}
+
+.meta-text {
+  font-size: 12px;
+  color: #9ca3af;
+}
+
+.notice-body {
+  padding-top: 12px;
+}
+
+.body-p {
+  display: block;
+  font-size: 14px;
+  line-height: 1.8;
+  color: #4b5563;
+}
+
+.type-tag {
+  padding: 2px 10px;
+  border-radius: 999px;
+  font-size: 11px;
+  font-weight: 600;
+}
+
+.type-tag-green {
+  color: #368f6f;
+  background-color: rgba(164, 216, 152, 0.24);
+}
+
+.type-tag-red {
+  color: #ef4444;
+  background-color: rgba(239, 68, 68, 0.1);
+}
+
+.type-tag-gold {
+  color: #b7791f;
+  background-color: rgba(251, 211, 141, 0.25);
+}
+
+.type-tag-gray {
+  color: #6b7280;
+  background-color: #f3f4f6;
+}
+
+.empty-text {
+  font-size: 13px;
+  color: #9ca3af;
+}
+</style>

+ 185 - 53
src/subPackages/pages-common/noticeList.vue

@@ -1,48 +1,57 @@
 <template>
-  <view class="app-container pb-20">
+  <view class="sub-page">
     <TopBar title="通知公告" show-back />
-    
-    <!-- 筛选标签 - 使用 uni-segmented-control -->
-    <uni-segmented-control 
-      :current="currentFilterIndex" 
-      :values="filterLabels"
-      style-type="text"
-      active-color="#4f8ef7"
-      @clickItem="onFilterChange"
-    />
-
-    <!-- 通知列表 -->
-    <view class="mt-2">
+
+    <view class="px-4 pt-3">
+      <!-- 筛选 -->
+      <view class="filter-bar mb-3">
+        <view
+          v-for="type in filterTypes"
+          :key="type.value"
+          class="filter-chip"
+          :class="{ 'filter-chip-active': currentFilter === type.value }"
+          @click="currentFilter = type.value"
+        >
+          {{ type.label }}
+        </view>
+      </view>
+
       <view v-if="loading" class="py-10 text-center">
-        <text class="text-gray-400">加载中...</text>
+        <text class="empty-text">加载中...</text>
       </view>
-      
+
       <view v-else-if="filteredNotices.length === 0">
         <EmptyState message="暂无通知" />
       </view>
-      
-      <uni-list v-else border>
+
+      <view v-else class="glass-card">
         <uni-swipe-action
           v-for="notice in filteredNotices"
           :key="notice.noticeId"
-          :actions="[{text: '标记已读', color: '#4f8ef7', key: 'read'}]"
+          :actions="[{text: '标记已读', color: '#368f6f', key: 'read'}]"
           @click="onSwipeAction(notice.noticeId, $event)"
         >
-          <uni-list-item 
-            :title="notice.title"
-            :note="notice.publisherName + ' · ' + formatDate(notice.publishTime)"
-            :right-text="getTypeText(notice.type)"
-            show-arrow
+          <view
+            class="list-row"
+            hover-class="row-hover"
+            :hover-start-time="0"
+            :hover-stay-time="120"
             @click="goToDetail(notice.noticeId)"
           >
-            <template #header>
-              <view class="w-10 h-10 rounded-xl flex items-center justify-center mr-3" :class="getTypeBgClass(notice.type)">
-                <text class="uni-icons text-lg" :class="getTypeIconClass(notice.type)"></text>
-              </view>
-            </template>
-          </uni-list-item>
+            <view class="icon-chip mr-3" :class="{ 'icon-chip-red': notice.type === 'emergency' }">
+              <text class="uni-icons chip-glyph" :class="getTypeIcon(notice.type)"></text>
+            </view>
+            <view class="flex-1 min-w-0 mr-2">
+              <text class="row-title truncate">{{ notice.title }}</text>
+              <text class="row-sub truncate">{{ notice.publisherName }} · {{ formatDate(notice.publishTime) }}</text>
+            </view>
+            <text class="type-tag flex-shrink-0 mr-1" :class="getTypeClass(notice.type)">
+              {{ getTypeText(notice.type) }}
+            </text>
+            <text class="uni-icons uniui-arrowright row-arrow"></text>
+          </view>
         </uni-swipe-action>
-      </uni-list>
+      </view>
     </view>
   </view>
 </template>
@@ -51,16 +60,13 @@
 import { ref, computed, onMounted } from 'vue'
 import TopBar from '../../components/common/TopBar.vue'
 import EmptyState from '../../components/common/EmptyState.vue'
-import UniList from '../../components/uni-list/uni-list.vue'
-import UniListItem from '../../components/uni-list/uni-list-item.vue'
 import UniSwipeAction from '../../components/uni-swipe-action/uni-swipe-action.vue'
-import UniSegmentedControl from '../../components/uni-segmented-control/uni-segmented-control.vue'
 import { getNoticeList } from '../../api/notice'
 import { formatDate } from '../../utils'
 
 const loading = ref(false)
 const notices = ref<any[]>([])
-const currentFilterIndex = ref(0)
+const currentFilter = ref('all')
 
 const filterTypes = [
   { value: 'all', label: '全部' },
@@ -69,12 +75,9 @@ const filterTypes = [
   { value: 'task', label: '任务' },
 ]
 
-const filterLabels = filterTypes.map(t => t.label)
-
 const filteredNotices = computed(() => {
-  const filterValue = filterTypes[currentFilterIndex.value].value
-  if (filterValue === 'all') return notices.value
-  return notices.value.filter(n => n.type === filterValue)
+  if (currentFilter.value === 'all') return notices.value
+  return notices.value.filter(n => n.type === currentFilter.value)
 })
 
 function getTypeText(type: string) {
@@ -86,26 +89,22 @@ function getTypeText(type: string) {
   return map[type] || type
 }
 
-function getTypeBgClass(type: string) {
+function getTypeIcon(type: string) {
   const map: Record<string, string> = {
-    system: 'bg-blue-50',
-    emergency: 'bg-red-50',
-    task: 'bg-green-50',
+    system: 'uniui-sound',
+    emergency: 'uniui-notification-filled',
+    task: 'uniui-flag',
   }
-  return map[type] || 'bg-gray-50'
+  return map[type] || 'uniui-sound'
 }
 
-function getTypeIconClass(type: string) {
+function getTypeClass(type: string) {
   const map: Record<string, string> = {
-    system: 'uniui-sound text-blue-500',
-    emergency: 'uniui-notification-filled text-red-500',
-    task: 'uniui-flag text-green-500',
+    system: 'type-tag-green',
+    emergency: 'type-tag-red',
+    task: 'type-tag-gold',
   }
-  return map[type] || 'uniui-sound text-gray-400'
-}
-
-function onFilterChange(index: number) {
-  currentFilterIndex.value = index
+  return map[type] || 'type-tag-gray'
 }
 
 function onSwipeAction(noticeId: number, action: { key?: string; text?: string }) {
@@ -115,6 +114,7 @@ function onSwipeAction(noticeId: number, action: { key?: string; text?: string }
 }
 
 function goToDetail(noticeId: number) {
+  uni.setStorageSync('currentNoticeId', noticeId)
   uni.navigateTo({ url: `/subPackages/pages-common/noticeDetail?id=${noticeId}` })
 }
 
@@ -130,3 +130,135 @@ onMounted(async () => {
   }
 })
 </script>
+
+<style scoped>
+.sub-page {
+  max-width: 430px;
+  margin: 0 auto;
+  min-height: 100vh;
+  background-color: #f6fbf9;
+  padding-bottom: 24px;
+}
+
+/* 筛选条:默认白底灰边,激活态绿色 */
+.filter-bar {
+  display: flex;
+  gap: 8px;
+  overflow-x: auto;
+}
+
+.filter-chip {
+  padding: 6px 16px;
+  border-radius: 999px;
+  font-size: 13px;
+  color: #6b7280;
+  background-color: #ffffff;
+  border: 1px solid #e5e7eb;
+  flex-shrink: 0;
+}
+
+.filter-chip-active {
+  color: #ffffff;
+  font-weight: 600;
+  background: linear-gradient(135deg, #368f6f 0%, #4ba98a 100%);
+  border-color: transparent;
+}
+
+.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;
+}
+
+.list-row {
+  display: flex;
+  align-items: center;
+  padding: 13px 16px;
+  border-top: 1px solid rgba(164, 216, 152, 0.18);
+}
+
+.row-hover {
+  background-color: rgba(164, 216, 152, 0.1);
+}
+
+.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-arrow {
+  font-size: 16px;
+  color: #c4d0cb;
+  flex-shrink: 0;
+}
+
+.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);
+}
+
+.icon-chip-red {
+  background-color: rgba(239, 68, 68, 0.1);
+}
+
+.icon-chip-red .chip-glyph {
+  color: #ef4444;
+}
+
+.chip-glyph {
+  font-size: 20px;
+  color: #368f6f;
+}
+
+.type-tag {
+  padding: 2px 10px;
+  border-radius: 999px;
+  font-size: 11px;
+  font-weight: 600;
+}
+
+.type-tag-green {
+  color: #368f6f;
+  background-color: rgba(164, 216, 152, 0.24);
+}
+
+.type-tag-red {
+  color: #ef4444;
+  background-color: rgba(239, 68, 68, 0.1);
+}
+
+.type-tag-gold {
+  color: #b7791f;
+  background-color: rgba(251, 211, 141, 0.25);
+}
+
+.type-tag-gray {
+  color: #6b7280;
+  background-color: #f3f4f6;
+}
+
+.empty-text {
+  font-size: 13px;
+  color: #9ca3af;
+}
+</style>

+ 195 - 174
src/subPackages/pages-common/projectDetail.vue

@@ -1,74 +1,53 @@
 <template>
-  <view class="app-container pb-24">
+  <view class="app-container detail-page pb-24">
     <TopBar title="项目详情" show-back />
 
-    <view v-if="projectStore.loading" class="py-20 text-center">
+    <view v-if="loading" class="py-20 text-center">
       <text class="text-gray-400">加载中...</text>
     </view>
 
     <view v-else-if="!project" class="px-4 py-6">
-      <EmptyState message="项目不存在" />
+      <EmptyState icon="uniui-folder-add-filled" text="项目不存在" />
     </view>
 
     <view v-else class="px-4 py-4 gap-y-4">
-      <!-- 项目状态卡片 -->
+      <!-- 头部 -->
       <view class="card p-4">
-        <view class="flex items-center justify-between mb-4">
-          <view>
-            <text class="text-lg font-bold text-gray-800">{{ project.name }}</text>
-            <text class="text-xs text-gray-400 block mt-1">{{ formatDate(project.createTime, 'YYYY-MM-DD') }}</text>
+        <view class="flex items-start justify-between mb-2">
+          <view class="flex-1 min-w-0 mr-3">
+            <text class="text-lg font-bold text-gray-800">{{ project.projectName }}</text>
+            <text v-if="project.projectCode" class="text-xs text-gray-400 block mt-1">{{ project.projectCode }}</text>
           </view>
           <StatusTag :status="project.status" type="project" />
         </view>
-
-        <!-- 项目关键信息 -->
-        <view class="flex items-center justify-between py-3 border-t border-gray-100">
-          <view class="flex items-center">
-            <text class="uni-icons uniui-folder-add-filled text-primary mr-2"></text>
-            <text class="text-sm text-gray-500">项目类型</text>
-          </view>
-          <text class="text-sm text-gray-800">{{ project.type || '-' }}</text>
-        </view>
-        <view class="flex items-center justify-between py-3 border-t border-gray-100">
-          <view class="flex items-center">
-            <text class="uni-icons uniui-person-filled text-primary mr-2"></text>
-            <text class="text-sm text-gray-500">客户</text>
-          </view>
-          <text class="text-sm text-gray-800">{{ project.customerName || '-' }}</text>
-        </view>
-        <view class="flex items-center justify-between py-3 border-t border-gray-100">
-          <view class="flex items-center">
-            <text class="uni-icons uniui-phone-filled text-primary mr-2"></text>
-            <text class="text-sm text-gray-500">电话</text>
-          </view>
-          <view class="flex items-center" @click="callPhone(project.customerPhone)">
-            <text class="text-sm text-primary">{{ project.customerPhone || '-' }}</text>
-            <text class="uni-icons uniui-arrowright text-gray-400 ml-1"></text>
-          </view>
+        <view class="flex flex-wrap gap-2 mt-3">
+          <text class="chip chip-type px-2.5 py-0.5 rounded-full text-xs">{{ getCooperationTypeText(project.cooperationType) }}</text>
+          <text v-if="project.industry" class="chip chip-industry px-2.5 py-0.5 rounded-full text-xs">{{ getIndustryText(project.industry) }}</text>
+          <text
+            v-for="ft in facilityList"
+            :key="ft"
+            class="chip chip-facility px-2.5 py-0.5 rounded-full text-xs"
+          >{{ getFacilityTypeText(ft) }}</text>
         </view>
       </view>
 
-      <!-- 地址信息 -->
+      <!-- 合同/费用 -->
       <view class="card p-4">
         <view class="detail-section-title">
-          <text class="uni-icons uniui-location-filled text-primary mr-2"></text>
-          <text>地址信息</text>
+          <text class="uni-icons uniui-wallet-filled text-primary mr-2"></text>
+          <text>合同 / 费用</text>
         </view>
         <view class="detail-row">
-          <text class="detail-label">完整地址</text>
-          <text class="detail-value text-right flex-1">{{ fullAddress || '-' }}</text>
-        </view>
-      </view>
-
-      <!-- 合同信息 -->
-      <view v-if="showContractInfo" class="card p-4">
-        <view class="detail-section-title">
-          <text class="uni-icons uniui-wallet-filled text-primary mr-2"></text>
-          <text>合同信息</text>
+          <text class="detail-label">合同编号</text>
+          <text class="detail-value">{{ project.contractNo || '-' }}</text>
         </view>
         <view class="detail-row">
           <text class="detail-label">合同金额</text>
-          <text class="detail-value">{{ project.contractAmount ? `¥${project.contractAmount.toLocaleString()}` : '-' }}</text>
+          <text class="detail-value">{{ formatMoney(project.contractAmount) }}</text>
+        </view>
+        <view class="detail-row">
+          <text class="detail-label">付款方式</text>
+          <text class="detail-value">{{ getPaymentMethodText(project.paymentMethod || '') }}</text>
         </view>
         <view class="detail-row">
           <text class="detail-label">合同开始</text>
@@ -78,56 +57,78 @@
           <text class="detail-label">合同结束</text>
           <text class="detail-value">{{ project.contractEndDate || '-' }}</text>
         </view>
-        <view class="detail-row">
-          <text class="detail-label">服务周期</text>
-          <text class="detail-value">{{ project.serviceCycle || '-' }}</text>
-        </view>
       </view>
 
-      <!-- 项目联系人 -->
-      <view v-if="project.contacts && project.contacts.length > 0" class="card p-4">
+      <!-- 客户 / 联系人 -->
+      <view class="card p-4">
         <view class="detail-section-title">
           <text class="uni-icons uniui-person-filled text-primary mr-2"></text>
-          <text>项目联系人</text>
+          <text>客户 / 联系人</text>
         </view>
-        <view
-          v-for="(contact, index) in project.contacts"
-          :key="index"
-          class="flex items-center justify-between py-3"
-          :class="{ 'border-t border-gray-100': index > 0 }"
-        >
-          <view class="flex items-center">
-            <view class="w-10 h-10 rounded-full bg-primary-light flex items-center justify-center mr-3">
-              <text class="uni-icons uniui-person text-primary"></text>
-            </view>
-            <view>
-              <text class="text-sm text-gray-800">{{ contact.name }}</text>
-              <text v-if="contact.role" class="text-xs text-gray-400 block">{{ contact.role }}</text>
-            </view>
-          </view>
-          <view class="flex items-center" @click="callPhone(contact.phone)">
-            <text class="text-sm text-primary">{{ contact.phone }}</text>
-            <view class="w-8 h-8 rounded-full bg-primary-light flex items-center justify-center ml-2">
-              <text class="uni-icons uniui-phone-filled text-primary text-sm"></text>
-            </view>
+        <view class="detail-row">
+          <text class="detail-label">客户名称</text>
+          <text class="detail-value">{{ project.customerName || '-' }}</text>
+        </view>
+        <view class="detail-row">
+          <text class="detail-label">主联系人</text>
+          <text class="detail-value">{{ project.primaryContactName || '-' }}</text>
+        </view>
+        <view class="detail-row">
+          <text class="detail-label">联系电话</text>
+          <view class="detail-value flex items-center" @click="callPhone(project.primaryContactPhone || project.customerPhone)">
+            <text class="text-primary">{{ project.primaryContactPhone || project.customerPhone || '-' }}</text>
           </view>
         </view>
+        <view v-if="project.backupContactName" class="detail-row">
+          <text class="detail-label">备用联系人</text>
+          <text class="detail-value">{{ project.backupContactName }} {{ project.backupContactPhone || '' }}</text>
+        </view>
+      </view>
+
+      <!-- 地址信息 -->
+      <view class="card p-4">
+        <view class="detail-section-title">
+          <text class="uni-icons uniui-location-filled text-primary mr-2"></text>
+          <text>地址信息</text>
+        </view>
+        <view class="detail-row">
+          <text class="detail-label">完整地址</text>
+          <text class="detail-value text-right flex-1">{{ fullAddress || '-' }}</text>
+        </view>
+        <view v-if="hasLocation" class="mt-3">
+          <MapView
+            :latitude="projectLatitude"
+            :longitude="projectLongitude"
+            :title="project.projectName"
+            :address="fullAddress"
+            :height="180"
+          />
+        </view>
+      </view>
+
+      <!-- 项目介绍 -->
+      <view v-if="project.projectDescription" class="card p-4">
+        <view class="detail-section-title">
+          <text class="uni-icons uniui-info-filled text-primary mr-2"></text>
+          <text>项目介绍</text>
+        </view>
+        <text class="text-sm text-gray-600">{{ project.projectDescription }}</text>
       </view>
 
-      <!-- 服务点信息 -->
+      <!-- 服务点 -->
       <view class="card p-4">
         <view class="detail-section-title">
           <text class="uni-icons uniui-location-filled text-primary mr-2"></text>
           <text>服务点与服务项</text>
         </view>
-        <view v-if="project.servicePoints && project.servicePoints.length > 0">
+        <view v-if="servicePointList.length > 0">
           <view
-            v-for="(point, index) in project.servicePoints"
+            v-for="(point, index) in servicePointList"
             :key="index"
             class="py-3"
             :class="{ 'border-t border-gray-100': index > 0 }"
           >
-            <view class="flex items-center mb-2">
+            <view class="flex items-center mb-1">
               <text class="uni-icons uniui-location-filled text-primary mr-2"></text>
               <text class="text-sm font-medium text-gray-800">{{ point.name || `服务点 ${index + 1}` }}</text>
             </view>
@@ -135,7 +136,8 @@
               <text v-if="point.address" class="text-xs text-gray-500 block mb-1">{{ point.address }}</text>
               <view class="flex items-center flex-wrap gap-2">
                 <text v-if="point.serviceType" class="px-2 py-1 bg-primary-light text-primary rounded text-xs">{{ point.serviceType }}</text>
-                <text v-if="point.contactName" class="text-xs text-gray-500">联系人:{{ point.contactName }}</text>
+                <text v-if="point.contact" class="text-xs text-gray-500">联系人:{{ point.contact }}</text>
+                <text v-if="point.phone" class="text-xs text-gray-500">{{ point.phone }}</text>
               </view>
             </view>
           </view>
@@ -145,21 +147,6 @@
         </view>
       </view>
 
-      <!-- 项目位置 -->
-      <view class="card p-4">
-        <view class="detail-section-title">
-          <text class="uni-icons uniui-map-filled text-primary mr-2"></text>
-          <text>项目位置</text>
-        </view>
-        <MapView
-          :latitude="projectLatitude"
-          :longitude="projectLongitude"
-          :title="project.name"
-          :address="fullAddress"
-          :height="180"
-        />
-      </view>
-
       <!-- 备注 -->
       <view v-if="project.remark" class="card p-4">
         <view class="detail-section-title">
@@ -169,44 +156,21 @@
         <text class="text-sm text-gray-600">{{ project.remark }}</text>
       </view>
 
-      <!-- 关联任务 -->
-      <view class="card p-4">
-        <view class="flex items-center justify-between mb-4">
-          <view class="detail-section-title mb-0">
-            <text class="uni-icons uniui-flag-filled text-primary mr-2"></text>
-            <text>关联任务</text>
-          </view>
-          <text class="text-sm text-primary" @click="goToTaskList">查看全部</text>
-        </view>
-        <view v-if="projectTasks.length > 0">
-          <view
-            v-for="task in projectTasks"
-            :key="task.id"
-            class="flex items-center justify-between py-3 active:bg-gray-50"
-            @click="goToTask(task.id)"
-          >
-            <view class="flex items-center">
-              <view class="w-10 h-10 rounded-full bg-primary-light flex items-center justify-center mr-3">
-                <text class="uni-icons uniui-flag text-primary"></text>
-              </view>
-              <view>
-                <text class="text-sm text-gray-800">{{ task.projectName }}</text>
-                <text class="text-xs text-gray-400 block">{{ formatDate(task.scheduleDate || task.serviceDate || task.createTime, 'YYYY-MM-DD') }}</text>
-              </view>
-            </view>
-            <StatusTag :status="task.status" />
-          </view>
-        </view>
-        <view v-else class="py-4 text-center">
-          <text class="text-sm text-gray-400">暂无关联任务</text>
-        </view>
-      </view>
-
-      <!-- 底部操作按钮 -->
-      <view class="px-4 pt-2 pb-6">
-        <button class="w-full py-4 btn-primary rounded-2xl text-base font-semibold flex items-center justify-center" @click="goToPublishTask">
+      <!-- 操作按钮 -->
+      <view class="px-4 pt-2 pb-6 flex gap-3">
+        <button
+          class="flex-1 py-4 btn-ghost rounded-2xl text-base font-semibold flex items-center justify-center"
+          @click="goToEdit"
+        >
+          <text class="uni-icons uniui-compose mr-2"></text>
+          编辑
+        </button>
+        <button
+          class="flex-1 py-4 btn-primary rounded-2xl text-base font-semibold flex items-center justify-center"
+          @click="goToPublishTask"
+        >
           <text class="uni-icons uniui-plusempty mr-2"></text>
-          发布任务
+          发布任务
         </button>
       </view>
     </view>
@@ -214,73 +178,130 @@
 </template>
 
 <script setup lang="ts">
-import { ref, computed, onMounted } from 'vue'
-import { useProjectStore } from '../../stores/project'
-import { useTaskStore } from '../../stores/task'
+import { ref, computed } from 'vue'
+import { onLoad } from '@dcloudio/uni-app'
+import type { Project, ServicePoint } from '../../types'
+import { getProjectDetail } from '../../api/project'
+import {
+  formatMoney,
+  parseJsonArray,
+  getCooperationTypeText,
+  getIndustryText,
+  getFacilityTypeText,
+  getPaymentMethodText
+} from '../../utils'
 import TopBar from '../../components/common/TopBar.vue'
 import StatusTag from '../../components/common/StatusTag.vue'
 import EmptyState from '../../components/common/EmptyState.vue'
 import MapView from '../../components/common/MapView.vue'
-import { formatDate } from '../../utils'
 
-const projectStore = useProjectStore()
-const taskStore = useTaskStore()
+const project = ref<Project | null>(null)
+const loading = ref(true)
+const projectId = ref<number | string>('')
 
-const project = computed(() => projectStore.currentProject)
-const projectTasks = ref<any[]>([])
+const facilityList = computed(() => parseJsonArray<string>(project.value?.facilityTypes))
+const servicePointList = computed(() => parseJsonArray<ServicePoint>(project.value?.servicePoints))
 
 const fullAddress = computed(() => {
   if (!project.value) return ''
-  const parts = [project.value.province, project.value.city, project.value.district, project.value.address]
+  if (project.value.address) return project.value.address
+  const parts = [project.value.province, project.value.city, project.value.district, project.value.street, project.value.detailAddress]
   return parts.filter(Boolean).join('')
 })
 
-const showContractInfo = computed(() => {
-  if (!project.value) return false
-  return !!(
-    project.value.contractAmount ||
-    project.value.contractStartDate ||
-    project.value.contractEndDate ||
-    project.value.serviceCycle
-  )
-})
-
-const projectLatitude = computed(() => project.value?.location?.lat || 39.9)
-const projectLongitude = computed(() => project.value?.location?.lng || 116.4)
+const hasLocation = computed(() => !!(project.value?.latitude && project.value?.longitude))
+const projectLatitude = computed(() => Number(project.value?.latitude) || 39.9)
+const projectLongitude = computed(() => Number(project.value?.longitude) || 116.4)
 
-function callPhone(phone: string) {
+function callPhone(phone?: string) {
   if (!phone) return
   uni.makePhoneCall({ phoneNumber: phone })
 }
 
-function goToTask(taskId: string) {
-  taskStore.setCurrentTask(taskId)
-  uni.navigateTo({ url: '/subPackages/pages-common/taskDetail' })
-}
-
-function goToTaskList() {
-  uni.switchTab({ url: '/pages/task/task' })
+function goToEdit() {
+  uni.navigateTo({ url: `/subPackages/pages-common/addProject?mode=edit&id=${projectId.value}` })
 }
 
 function goToPublishTask() {
-  uni.navigateTo({ 
-    url: `/subPackages/pages-common/publishTask?projectId=${project.value?.id}` 
-  })
+  uni.navigateTo({ url: `/subPackages/pages-common/publishTask?projectId=${projectId.value}` })
 }
 
-onMounted(async () => {
-  if (!project.value) {
-    const pages = getCurrentPages()
-    const currentPage = pages[pages.length - 1] as any
-    const projectId = currentPage.options?.id
-    if (projectId) {
-      projectStore.setCurrentProject(projectId)
-    }
+async function loadDetail(id: number | string) {
+  loading.value = true
+  try {
+    project.value = await getProjectDetail(id)
+  } catch (e) {
+    project.value = null
+  } finally {
+    loading.value = false
   }
+}
 
-  if (project.value) {
-    await taskStore.fetchTasks()
-    projectTasks.value = taskStore.tasks.filter((t) => t.projectId === project.value?.id)
+onLoad((options) => {
+  const id = options?.id
+  if (id) {
+    projectId.value = id
+    loadDetail(id)
+  } else {
+    loading.value = false
   }
 })
 </script>
+
+<style scoped>
+.detail-page {
+  background-color: #f6fbf9;
+}
+
+.card {
+  border-radius: 20px;
+  margin-bottom: 0;
+  border: 1px solid rgba(164, 216, 152, 0.28);
+  box-shadow: 0 14px 34px -18px rgba(54, 143, 111, 0.35);
+}
+
+.detail-section-title {
+  padding: 0;
+  border-bottom: none;
+  margin-bottom: 12px;
+}
+.text-primary {
+  color: #368f6f;
+}
+
+/* 头部标签 */
+.chip {
+  font-weight: 500;
+}
+.chip-type {
+  background-color: rgba(164, 216, 152, 0.22);
+  color: #368f6f;
+}
+.chip-industry {
+  background-color: rgba(90, 184, 208, 0.16);
+  color: #3a8fb0;
+}
+.chip-facility {
+  background-color: #f3f4f6;
+  color: #6b7280;
+}
+
+/* 服务点类型标签 */
+.bg-primary-light {
+  background-color: rgba(164, 216, 152, 0.22);
+}
+
+/* 操作按钮 */
+.btn-ghost {
+  background-color: #ffffff;
+  border: 1px solid #368f6f;
+  color: #368f6f;
+}
+.btn-ghost:active {
+  background-color: rgba(164, 216, 152, 0.12);
+}
+.btn-primary {
+  background: linear-gradient(135deg, #368f6f 0%, #5ab8d0 100%);
+  box-shadow: 0 10px 22px -10px rgba(54, 143, 111, 0.55);
+}
+</style>

+ 221 - 84
src/subPackages/pages-common/projectList.vue

@@ -1,82 +1,99 @@
-
 <template>
-  <view class="app-container pb-24">
-    <TopBar title="项目库" show-back>
-      <template #right>
-        <view class="px-2 py-1 flex items-center" @click="goToAdd">
-          <text class="uni-icons uniui-plusempty text-primary text-xl"></text>
-          <text class="text-primary text-sm ml-1">新增</text>
-        </view>
-      </template>
-    </TopBar>
-    
-    <!-- 顶部搜索栏 -->
-    <view class="bg-white px-4 py-3 sticky top-0 z-40">
-      <view class="flex items-center bg-gray-100 rounded-2xl px-4 py-2">
-        <text class="uni-icons uniui-search text-gray-400 mr-2 text-base"></text>
-        <input 
-          class="flex-1 bg-transparent text-sm text-gray-700" 
-          placeholder="搜索项目名称" 
+  <view class="app-container list-page pb-24">
+    <TopBar title="项目库" show-back />
+
+    <!-- 工具栏:搜索 + 新增 -->
+    <view class="toolbar px-4 py-3 sticky top-0 z-40 flex items-center gap-2">
+      <view class="search-box flex-1 min-w-0 flex items-center rounded-2xl px-4 py-2" :class="{ 'search-focus': focusedSearch }">
+        <text class="uni-icons uniui-search search-icon mr-2 text-base"></text>
+        <input
+          class="flex-1 bg-transparent text-sm text-gray-800"
+          placeholder="搜索项目名称/客户"
+          placeholder-class="search-ph"
           v-model="searchKeyword"
+          confirm-type="search"
+          @focus="focusedSearch = true"
+          @blur="focusedSearch = false"
         />
       </view>
+      <view
+        class="add-btn w-9 h-9 rounded-full flex items-center justify-center flex-shrink-0"
+        hover-class="add-btn-hover"
+        :hover-start-time="0"
+        :hover-stay-time="120"
+        @click="goToAdd"
+      >
+        <text class="uni-icons uniui-plusempty text-white text-xl"></text>
+      </view>
     </view>
 
-    <!-- 顶部筛选标签 - 使用 uni-segmented-control -->
-    <uni-segmented-control 
-      :current="currentFilterIndex" 
-      :values="filterLabels"
-      style-type="text"
-      active-color="#4f8ef7"
-      @clickItem="onFilterChange"
-    />
-
-    <!-- 项目列表 - 使用 uni-list + uni-card + uni-swipe-action -->
-    <view class="mt-2">
+    <!-- 合作类型筛选(前端过滤) -->
+    <scroll-view scroll-x class="filter-bar" :show-scrollbar="false" enhanced>
+      <view class="flex items-center gap-2 px-4 py-3">
+        <view
+          v-for="(t, i) in filterTypes"
+          :key="t.value"
+          class="filter-tab flex-shrink-0 rounded-full px-4 py-1.5 text-sm border"
+          :class="i === currentFilterIndex ? 'filter-tab--active' : 'filter-tab--normal'"
+          @click="onFilterChange(i)"
+        >
+          {{ t.label }}
+        </view>
+      </view>
+    </scroll-view>
+
+    <!-- 项目列表 -->
+    <view class="px-4 mt-3">
       <view v-if="projectStore.loading" class="py-16 text-center">
-        <text class="text-gray-400">加载中...</text>
+        <text class="loading-text">加载中...</text>
       </view>
-      
+
       <view v-else-if="filteredProjects.length === 0">
-        <EmptyState message="暂无项目" />
+        <EmptyState icon="uniui-folder-add-filled" text="暂无项目" />
       </view>
-      
-      <uni-list v-else border>
+
+      <view v-else class="gap-y-3">
         <uni-swipe-action
           v-for="project in filteredProjects"
-          :key="project.id"
+          :key="project.projectId"
           :actions="swipeActions"
-          @click="onSwipeAction(project.id, $event)"
+          @click="onSwipeAction(project.projectId, $event)"
         >
-          <uni-list-item 
-            :title="project.name"
-            :note="project.address + ' | ' + project.customerName"
-            :right-text="project.type"
-            show-arrow
-            @click="goToDetail(project.id)"
-          >
-            <template #header>
-              <view class="w-10 h-10 bg-blue-50 rounded-xl flex items-center justify-center mr-3">
-                <text class="uni-icons uniui-folder-add-filled text-blue-500 text-lg"></text>
+          <view class="data-card" @click="goToDetail(project.projectId)">
+            <!-- 头部:项目名/客户 + 合作类型 -->
+            <view class="flex items-start justify-between">
+              <view class="min-w-0">
+                <text class="card-title block truncate">{{ project.projectName }}</text>
+                <view class="mt-1 flex items-center">
+                  <text class="uni-icons uniui-person-filled info-icon text-xs mr-1 flex-shrink-0"></text>
+                  <text class="info-text text-xs truncate">{{ project.customerName || '暂无客户' }}</text>
+                </view>
               </view>
-            </template>
-          </uni-list-item>
-        </uni-swipe-action>
-      </uni-list>
-    </view>
+              <view class="type-pill flex-shrink-0 px-2.5 py-0.5 rounded-full text-xs ml-2">
+                {{ getCooperationTypeText(project.cooperationType) }}
+              </view>
+            </view>
 
-    <!-- 加载更多 -->
-    <view v-if="filteredProjects.length > 0" class="px-4 mt-4 mb-4">
-      <view v-if="projectStore.hasMore" class="text-center py-4">
-        <text 
-          class="text-sm text-blue-500 font-medium"
-          @click="loadMore"
-        >
-          {{ projectStore.loadingMore ? '加载中...' : '加载更多' }}
-        </text>
+            <!-- 底部地址行 -->
+            <view class="card-foot mt-3 pt-2.5 flex items-center">
+              <text class="uni-icons uniui-location-filled foot-icon text-xs mr-1.5 flex-shrink-0"></text>
+              <text class="foot-text text-xs truncate flex-1">{{ addressText(project) }}</text>
+              <text class="uni-icons uniui-arrowright card-arrow ml-2 flex-shrink-0"></text>
+            </view>
+          </view>
+        </uni-swipe-action>
       </view>
-      <view v-else class="text-center py-4">
-        <text class="text-xs text-gray-400">已经到底了</text>
+
+      <!-- 加载更多 -->
+      <view v-if="filteredProjects.length > 0" class="mt-2 mb-4">
+        <view v-if="projectStore.hasMore" class="text-center py-4">
+          <text class="load-more" @click="loadMore">
+            {{ projectStore.loadingMore ? '加载中...' : '加载更多' }}
+          </text>
+        </view>
+        <view v-else class="text-center py-4">
+          <text class="text-xs text-gray-400">已经到底了</text>
+        </view>
       </view>
     </view>
   </view>
@@ -86,52 +103,61 @@
 import { ref, computed, onMounted } from 'vue'
 import { onPullDownRefresh } from '@dcloudio/uni-app'
 import { useProjectStore } from '../../stores/project'
+import type { CooperationType, Project } from '../../types'
+import { getCooperationTypeText } from '../../utils'
 import EmptyState from '../../components/common/EmptyState.vue'
 import TopBar from '../../components/common/TopBar.vue'
-import UniList from '../../components/uni-list/uni-list.vue'
-import UniListItem from '../../components/uni-list/uni-list-item.vue'
 import UniSwipeAction from '../../components/uni-swipe-action/uni-swipe-action.vue'
-import UniSegmentedControl from '../../components/uni-segmented-control/uni-segmented-control.vue'
 
 const projectStore = useProjectStore()
 const searchKeyword = ref('')
 const currentFilterIndex = ref(0)
+const focusedSearch = ref(false)
 
-const filterTypes = [
+const filterTypes: { value: CooperationType | 'all'; label: string }[] = [
   { value: 'all', label: '全部' },
-  { value: '包年', label: '包年' },
-  { value: '单次', label: '单次' },
-  { value: '季度', label: '季度' },
-  { value: '月付', label: '月付' },
+  { value: 'annual', label: '包年' },
+  { value: 'single', label: '单次' },
+  { value: 'quarter', label: '季度' },
+  { value: 'monthly', label: '月付' }
 ]
 
-const filterLabels = filterTypes.map(t => t.label)
-
 const filteredProjects = computed(() => {
   let list = projectStore.projects
   const filterValue = filterTypes[currentFilterIndex.value].value
-  
+
   if (filterValue !== 'all') {
-    list = list.filter(p => p.type === filterValue)
+    list = list.filter(p => p.cooperationType === filterValue)
   }
-  
+
   if (searchKeyword.value) {
-    list = list.filter(p => p.name.includes(searchKeyword.value))
+    const kw = searchKeyword.value.trim()
+    list = list.filter(p =>
+      (p.projectName || '').includes(kw) || (p.customerName || '').includes(kw)
+    )
   }
-  
+
   return list
 })
 
 const swipeActions: { text: string; color: string; key: string }[] = [
-  { text: '编辑', color: '#4f8ef7', key: 'edit' },
-  { text: '删除', color: '#ff3b30', key: 'delete' },
+  { text: '编辑', color: '#368f6f', key: 'edit' },
+  { text: '删除', color: '#ff3b30', key: 'delete' }
 ]
 
+function addressText(project: Project) {
+  return (
+    project.address ||
+    [project.province, project.city, project.district, project.detailAddress].filter(Boolean).join('') ||
+    '暂无地址'
+  )
+}
+
 function onFilterChange(index: number) {
   currentFilterIndex.value = index
 }
 
-function onSwipeAction(projectId: string, action: { key?: string; text?: string }) {
+function onSwipeAction(projectId: number | string, action: { key?: string; text?: string }) {
   if (action.key === 'delete') {
     uni.showModal({
       title: '确认删除',
@@ -145,13 +171,13 @@ function onSwipeAction(projectId: string, action: { key?: string; text?: string
     })
   } else if (action.key === 'edit') {
     projectStore.setCurrentProject(projectId)
-    uni.navigateTo({ url: '/subPackages/pages-common/addProject?mode=edit' })
+    uni.navigateTo({ url: `/subPackages/pages-common/addProject?mode=edit&id=${projectId}` })
   }
 }
 
-function goToDetail(projectId: string) {
+function goToDetail(projectId: number | string) {
   projectStore.setCurrentProject(projectId)
-  uni.navigateTo({ url: '/subPackages/pages-common/projectDetail' })
+  uni.navigateTo({ url: `/subPackages/pages-common/projectDetail?id=${projectId}` })
 }
 
 function goToAdd() {
@@ -172,8 +198,119 @@ onMounted(() => {
   projectStore.fetchProjects(true)
 })
 
-// 下拉刷新
 onPullDownRefresh(() => {
   refreshData()
 })
 </script>
+
+<style scoped>
+.list-page {
+  background-color: #f6fbf9;
+}
+
+.toolbar {
+  background-color: rgba(246, 251, 249, 0.88);
+  backdrop-filter: blur(12px);
+  -webkit-backdrop-filter: blur(12px);
+  border-bottom: 1px solid rgba(164, 216, 152, 0.28);
+}
+
+.search-box {
+  background-color: #ffffff;
+  border: 1px solid #e5e7eb;
+  transition: border-color 0.15s ease, box-shadow 0.15s ease, background-color 0.15s ease;
+}
+.search-focus {
+  background-color: #ffffff;
+  border-color: #368f6f;
+  box-shadow: 0 0 0 3px rgba(164, 216, 152, 0.30);
+}
+.search-icon {
+  color: #9ca3af;
+}
+.search-ph {
+  color: #9ca3af;
+}
+
+.add-btn {
+  background: linear-gradient(135deg, #368f6f 0%, #5ab8d0 100%);
+  box-shadow: 0 8px 18px -8px rgba(54, 143, 111, 0.55);
+}
+.add-btn-hover {
+  opacity: 0.92;
+  transform: scale(0.94);
+}
+
+.filter-bar {
+  background-color: rgba(246, 251, 249, 0.88);
+  border-bottom: 1px solid rgba(164, 216, 152, 0.20);
+  white-space: nowrap;
+}
+.filter-tab {
+  transition: all 0.15s ease;
+}
+.filter-tab--normal {
+  background-color: #ffffff;
+  border-color: #e5e7eb;
+  color: #4b5563;
+}
+.filter-tab--active {
+  background: linear-gradient(135deg, #368f6f 0%, #5ab8d0 100%);
+  border-color: transparent;
+  color: #ffffff;
+  box-shadow: 0 6px 14px -8px rgba(54, 143, 111, 0.6);
+}
+
+.data-card {
+  background-color: #ffffff;
+  border-radius: 20px;
+  padding: 14px 16px;
+  box-shadow: 0 14px 34px -18px rgba(54, 143, 111, 0.35);
+  border: 1px solid rgba(164, 216, 152, 0.28);
+}
+.data-card:active {
+  transform: scale(0.99);
+  background-color: #fbfefb;
+}
+.card-title {
+  font-size: 15px;
+  font-weight: 600;
+  color: #1f2937;
+}
+.type-pill {
+  background-color: rgba(164, 216, 152, 0.20);
+  color: #368f6f;
+  font-weight: 500;
+}
+
+.info-icon {
+  color: #5ab8d0;
+}
+.info-text {
+  color: #6b7280;
+}
+
+/* 底部地址行 */
+.card-foot {
+  border-top: 1px dashed rgba(164, 216, 152, 0.45);
+}
+.foot-icon {
+  color: #368f6f;
+}
+.foot-text {
+  color: #9ca3af;
+}
+.card-arrow {
+  color: #c4d9cc;
+  font-size: 14px;
+}
+
+.loading-text {
+  color: #6b7280;
+}
+.load-more {
+  font-size: 14px;
+  font-weight: 500;
+  color: #368f6f;
+}
+</style>

+ 266 - 60
src/subPackages/pages-common/publishTask.vue

@@ -1,21 +1,19 @@
 <template>
-  <view class="app-container pb-24">
+  <view class="sub-page">
     <TopBar title="发布任务" show-back />
 
-    <view class="px-4 py-4">
+    <view class="px-4 pt-3">
       <!-- 板块一:项目基本信息 -->
-      <view class="card p-4 mb-4">
-        <view class="detail-section-title mb-4">
-          <view class="w-8 h-8 rounded-lg bg-blue-100 flex items-center justify-center mr-3">
-            <text class="uni-icons uniui-folder-add-filled text-primary"></text>
-          </view>
-          <text>项目基本信息</text>
+      <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="bg-blue-50 rounded-lg px-3 py-2 mb-4 flex items-center">
-          <text class="uni-icons uniui-info-filled text-blue-500 mr-2 text-sm"></text>
-          <text class="text-blue-600 text-sm">请从项目库选择或新增项目</text>
+        <view class="hint-box">
+          <text class="uni-icons uniui-info-filled hint-icon mr-2"></text>
+          <text class="hint-text">请从项目库选择或新增项目</text>
         </view>
 
         <!-- 项目选择 -->
@@ -25,30 +23,30 @@
           </text>
           <picker mode="selector" :range="projectNames" :value="projectIndex" @change="onProjectSelectChange">
             <view class="form-select flex justify-between items-center">
-              <text :class="selectedProject ? 'text-gray-800' : 'text-gray-400'">{{ selectedProject?.name || '请选择项目' }}</text>
+              <text :class="selectedProject ? 'text-gray-800' : 'text-gray-400'">{{ selectedProject?.projectName || '请选择项目' }}</text>
               <text class="uni-icons uniui-arrowdown text-gray-400"></text>
             </view>
           </picker>
         </view>
 
         <!-- 已选项目信息 -->
-        <view v-if="selectedProject" class="bg-green-50 rounded-xl p-3 mb-4">
+        <view v-if="selectedProject" class="selected-box">
           <view class="flex items-center justify-between mb-3">
-            <text class="text-green-600 text-sm font-medium">已选择:{{ selectedProject.name }}</text>
+            <text class="text-green-600 text-sm font-medium">已选择:{{ selectedProject.projectName }}</text>
             <text class="text-green-500 text-xs" @click="resetProjectSelection">重新选择</text>
           </view>
           <view class="gap-y-2">
             <view class="flex justify-between text-sm">
               <text class="text-gray-500">合作类型:</text>
-              <text class="text-gray-700">{{ selectedProject.type }}</text>
+              <text class="text-gray-700">{{ getCooperationTypeText(selectedProject.cooperationType) }}</text>
             </view>
             <view class="flex justify-between text-sm">
               <text class="text-gray-500">设施类型:</text>
-              <text class="text-gray-700">油污管、化粪池</text>
+              <text class="text-gray-700 flex-1 text-right">{{ selectedFacilityText }}</text>
             </view>
             <view class="flex justify-between text-sm">
               <text class="text-gray-500">项目地址:</text>
-              <text class="text-gray-700 flex-1 text-right">{{ selectedProject.address }}</text>
+              <text class="text-gray-700 flex-1 text-right">{{ selectedAddressText }}</text>
             </view>
           </view>
         </view>
@@ -56,10 +54,10 @@
         <!-- 联系人列表 -->
         <view class="form-group mb-0">
           <text class="form-label">联系人</text>
-          <view v-if="selectedProject" class="gap-y-2 mb-2">
-            <view 
-              v-for="(contact, index) in selectedProject.contacts" 
-              :key="index" 
+          <view v-if="selectedProject && contactList.length > 0" class="gap-y-2 mb-2">
+            <view
+              v-for="(contact, index) in contactList"
+              :key="index"
               class="bg-gray-50 rounded-lg p-3 flex justify-between items-center"
             >
               <view>
@@ -69,16 +67,25 @@
               <text class="text-sm text-primary">{{ contact.phone }}</text>
             </view>
           </view>
+          <view v-else-if="selectedProject" class="py-2">
+            <text class="text-xs text-gray-400">暂无联系人</text>
+          </view>
         </view>
       </view>
 
       <!-- 板块二:服务信息 -->
-      <view class="card p-4">
-        <view class="detail-section-title mb-4">
-          <view class="w-8 h-8 rounded-lg bg-purple-100 flex items-center justify-center mr-3">
-            <text class="uni-icons uniui-gear-filled text-purple-500"></text>
-          </view>
-          <text>服务信息</text>
+      <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="form-group">
+          <text class="form-label">
+            <text class="text-red-500">*</text>任务名称
+          </text>
+          <uni-easyinput class="form-input" v-model="taskName" placeholder="如:积丹白鹭小区疏通" />
         </view>
 
         <!-- 服务时间 -->
@@ -133,8 +140,8 @@
             <view
               v-for="type in faultTypes"
               :key="type"
-              class="px-3 py-1 rounded-full text-sm border"
-              :class="selectedFaultTypes.includes(type) ? 'bg-primary text-white border-primary' : 'bg-gray-100 text-gray-600 border-gray-200'"
+              class="fault-chip"
+              :class="{ 'fault-chip-on': selectedFaultTypes.includes(type) }"
               @click="toggleFaultType(type)"
             >
               {{ type }}
@@ -264,8 +271,14 @@
       </view>
 
       <!-- 提交按钮 -->
-      <view class="pt-4 pb-6">
-        <button class="w-full py-4 btn-primary rounded-2xl text-base font-semibold" @click="submitTask">
+      <view class="pt-2 pb-6">
+        <button
+          class="submit-btn"
+          hover-class="submit-btn-hover"
+          :hover-start-time="0"
+          :hover-stay-time="120"
+          @click="submitTask"
+        >
           发布任务
         </button>
       </view>
@@ -279,24 +292,55 @@ import { onLoad } from '@dcloudio/uni-app'
 import TopBar from '@/components/common/TopBar.vue'
 import PhotoUploader from '@/components/common/PhotoUploader.vue'
 import { useProjectStore } from '@/stores/project'
-import { useTaskStore } from '@/stores/task'
-import { showToast, generateId, navigateTo } from '@/utils'
-import type { Task } from '@/types'
+import { createTask } from '@/api/task'
+import {
+  showToast,
+  navigateTo,
+  getCooperationTypeText,
+  getFacilityTypeText,
+  parseJsonArray
+} from '@/utils'
+import type { FacilityType } from '@/types'
 
 const projectStore = useProjectStore()
-const taskStore = useTaskStore()
 
-const projectNames = computed(() => [...projectStore.projects.map(p => p.name), '+ 新增项目'])
+const projectNames = computed(() => [...projectStore.projects.map(p => p.projectName), '+ 新增项目'])
 const projects = computed(() => projectStore.projects)
 
 const selectedProject = ref<typeof projects.value[0] | null>(null)
 const projectIndex = computed(() => {
   if (!selectedProject.value) return -1
-  return projects.value.findIndex(p => p.id === selectedProject.value?.id)
+  return projects.value.findIndex(p => String(p.projectId) === String(selectedProject.value?.projectId))
+})
+
+const contactList = computed(() => {
+  const p = selectedProject.value
+  if (!p) return [] as { name: string; phone: string; role: string }[]
+  const list: { name: string; phone: string; role: string }[] = []
+  if (p.primaryContactName || p.primaryContactPhone) {
+    list.push({ name: p.primaryContactName || '-', phone: p.primaryContactPhone || '', role: '主联系人' })
+  }
+  if (p.backupContactName || p.backupContactPhone) {
+    list.push({ name: p.backupContactName || '-', phone: p.backupContactPhone || '', role: '备用联系人' })
+  }
+  return list
+})
+
+const selectedFacilityText = computed(() => {
+  const list = parseJsonArray<FacilityType>(selectedProject.value?.facilityTypes)
+  return list.length > 0 ? list.map(getFacilityTypeText).join('、') : '-'
+})
+
+const selectedAddressText = computed(() => {
+  const p = selectedProject.value
+  if (!p) return ''
+  if (p.address) return p.address
+  return [p.province, p.city, p.district, p.street, p.detailAddress].filter(Boolean).join('') || '-'
 })
 
 const serviceDate = ref('')
 const serviceTime = ref('')
+const taskName = ref('')
 const faultLocation = ref('')
 const faultTypes = ['堵塞', '满溢', '排水慢', '异味', '其他']
 const selectedFaultTypes = ref<string[]>([])
@@ -326,9 +370,12 @@ const acceptType = ref('')
 const acceptTypeIndex = computed(() => acceptTypes.indexOf(acceptType.value))
 const photos = ref<string[]>([])
 
-onLoad((query) => {
+onLoad(async (query) => {
+  if (projectStore.projects.length === 0) {
+    try { await projectStore.fetchProjects(true) } catch (e) { /* ignore */ }
+  }
   if (query?.projectId) {
-    const project = projectStore.projects.find(p => p.id === query.projectId)
+    const project = projectStore.projects.find(p => String(p.projectId) === String(query.projectId))
     if (project) {
       selectedProject.value = project
     }
@@ -394,6 +441,10 @@ function validateForm(): boolean {
     showToast('请选择项目')
     return false
   }
+  if (!taskName.value.trim()) {
+    showToast('请输入任务名称')
+    return false
+  }
   if (!serviceDate.value) {
     showToast('请选择服务时间')
     return false
@@ -433,32 +484,187 @@ function validateForm(): boolean {
   return true
 }
 
-function submitTask() {
+async function submitTask() {
   if (!validateForm()) return
 
-  const newTask: Task = {
-    id: generateId('t'),
-    projectId: selectedProject.value!.id,
-    projectName: selectedProject.value!.name,
-    type: serviceMode.value === '应急' ? 'emergency' : 'regular',
+  const p = selectedProject.value!
+  const address =
+    p.address || [p.province, p.city, p.district, p.street, p.detailAddress].filter(Boolean).join('') || '-'
+  const planDate = serviceTime.value ? `${serviceDate.value} ${serviceTime.value}` : serviceDate.value
+
+  // 任务类型:应急优先;否则按施工类型映射到后端字典(dredge/clean/inspect)
+  const taskTypeMap: Record<string, string> = { 疏通: 'dredge', 清淤: 'clean', 排查: 'inspect' }
+  const taskType = serviceMode.value === '应急' ? 'emergency' : taskTypeMap[serviceType.value] || 'dredge'
+
+  const payload = {
+    taskNo: `T${Date.now()}`,
+    taskName: taskName.value.trim(),
+    projectId: p.projectId,
+    taskType,
+    planDate,
+    address,
     status: 'pending',
-    urgent: serviceMode.value === '应急' ? 'high' : 'normal',
-    createTime: new Date().toISOString(),
-    timeline: [
-      {
-        time: new Date().toISOString(),
-        action: '任务创建',
-        operator: '张销售',
-        role: 'sales'
-      }
-    ]
+    urgencyLevel: serviceMode.value === '应急' ? 'urgent' : 'normal',
+    faultLocation: faultLocation.value,
+    faultType: selectedFaultTypes.value.join(','),
+    customerWords: customerWords.value,
+    serviceContent: faultDesc.value,
+    constructionType: serviceType.value,
+    constructionFacility: facility.value,
+    serviceMethod: serviceMode.value,
+    waterDistance: waterSource.value,
+    assistantInfo: constructionHelp.value,
+    estimatedWorkload: estimatedQuantity.value,
+    estimatedDuration: estimatedHours.value,
+    acceptanceRequirement: acceptanceReq.value,
+    acceptanceMethod: acceptType.value,
+    extraData: JSON.stringify({
+      photos: photos.value,
+      projectName: p.projectName,
+      customerName: p.customerName,
+      customerPhone: p.customerPhone
+    })
   }
 
-  taskStore.tasks.push(newTask)
-  showToast({ title: '任务发布成功', icon: 'success' })
-  setTimeout(() => {
-    navigateTo('/pages/task/task')
-  }, 1500)
+  try {
+    await createTask(payload)
+    showToast({ title: '任务发布成功', icon: 'success' })
+    setTimeout(() => uni.navigateBack(), 1500)
+  } catch (e) {
+    // request.ts 已统一提示错误信息
+  }
 }
 </script>
 
+<style scoped>
+.sub-page {
+  max-width: 430px;
+  margin: 0 auto;
+  min-height: 100vh;
+  background-color: #f6fbf9;
+  padding-bottom: 32px;
+}
+
+.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-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;
+}
+
+.hint-box {
+  display: flex;
+  align-items: center;
+  padding: 10px 12px;
+  margin-bottom: 14px;
+  border-radius: 12px;
+  background-color: rgba(164, 216, 152, 0.14);
+  border: 1px solid rgba(164, 216, 152, 0.3);
+}
+
+.hint-icon {
+  font-size: 14px;
+  color: #368f6f;
+}
+
+.hint-text {
+  font-size: 13px;
+  color: #368f6f;
+}
+
+.selected-box {
+  padding: 12px;
+  margin-bottom: 14px;
+  border-radius: 14px;
+  background-color: rgba(164, 216, 152, 0.14);
+  border: 1px solid rgba(164, 216, 152, 0.3);
+}
+
+/* 故障类型:默认白底灰边,激活态绿色 */
+.fault-chip {
+  padding: 5px 14px;
+  border-radius: 999px;
+  font-size: 13px;
+  color: #6b7280;
+  background-color: #ffffff;
+  border: 1px solid #e5e7eb;
+}
+
+.fault-chip-on {
+  color: #ffffff;
+  font-weight: 600;
+  background: linear-gradient(135deg, #368f6f 0%, #4ba98a 100%);
+  border-color: transparent;
+}
+
+.form-group {
+  margin-bottom: 14px;
+}
+
+.form-label {
+  display: block;
+  margin-bottom: 8px;
+  font-size: 13px;
+  font-weight: 600;
+  color: #6b7280;
+}
+
+.form-select {
+  width: 100%;
+  padding: 11px 12px;
+  border-radius: 12px;
+  border: 1px solid #e5e7eb;
+  background-color: #ffffff;
+  font-size: 14px;
+}
+
+.form-textarea {
+  width: 100%;
+  min-height: 80px;
+  padding: 10px 12px;
+  border-radius: 12px;
+  border: 1px solid #e5e7eb;
+  background-color: #ffffff;
+  font-size: 14px;
+  color: #1f2937;
+}
+
+.submit-btn {
+  width: 100%;
+  padding: 13px 0;
+  border-radius: 14px;
+  font-size: 15px;
+  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;
+}
+</style>
+

+ 181 - 64
src/subPackages/pages-common/taskDetail.vue

@@ -1,94 +1,95 @@
 <template>
-  <view class="app-container">
+  <view class="sub-page">
     <TopBar title="任务详情" show-back />
-    <view class="px-4 py-4 pb-8">
+
+    <view class="px-4 pt-3">
       <!-- 任务状态卡片 -->
-      <view class="card p-4">
+      <view class="glass-card p-4">
         <view class="flex justify-between items-start mb-3">
-          <view>
-            <text class="text-base font-bold text-gray-800">{{ task.projectName }}</text>
-            <view class="mt-2">
+          <view class="flex-1 min-w-0 mr-2">
+            <text class="notice-title">{{ task.projectName }}</text>
+            <view class="mt-2 flex items-center">
               <StatusTag :status="task.status" />
-              <text v-if="task.isEmergency" class="tag-danger ml-2 px-2 py-1 rounded text-xs">应急</text>
+              <text v-if="task.isEmergency" class="emergency-tag ml-2">应急</text>
             </view>
           </view>
-          <text class="text-xs text-gray-400">{{ task.taskNo }}</text>
+          <text class="meta-text flex-shrink-0">{{ task.taskNo }}</text>
         </view>
-        <view class="flex items-center text-sm text-gray-600 mb-2">
-          <text class="uni-icons uniui-location-filled text-gray-400 mr-2"></text>
-          <text>{{ task.address }}</text>
+        <view class="meta-line mb-2">
+          <text class="uni-icons uniui-location-filled meta-icon mr-2"></text>
+          <text class="meta-value flex-1">{{ task.address }}</text>
         </view>
-        <view class="flex items-center text-sm text-gray-600">
-          <text class="uni-icons uniui-clock-filled text-gray-400 mr-2"></text>
-          <text>{{ task.scheduleDate }} {{ task.scheduleTime }}</text>
+        <view class="meta-line">
+          <text class="uni-icons uniui-clock-filled meta-icon mr-2"></text>
+          <text class="meta-value">{{ task.scheduleDate }} {{ task.scheduleTime }}</text>
         </view>
       </view>
 
       <!-- 基本信息 -->
-      <view class="card">
-        <view class="detail-section-title">
-          <text class="uni-icons uniui-info-filled text-primary mr-2"></text>
-          <text>基本信息</text>
+      <view class="glass-card">
+        <view class="section-head">
+          <view class="section-bar mr-2"></view>
+          <text class="section-title flex-1">基本信息</text>
         </view>
-        <view class="detail-row">
-          <text class="detail-label">服务类型</text>
-          <text class="detail-value">{{ task.serviceType }}</text>
+        <view class="info-row">
+          <text class="info-label">服务类型</text>
+          <text class="info-value">{{ task.serviceType }}</text>
         </view>
-        <view class="detail-row">
-          <text class="detail-label">客户名称</text>
-          <text class="detail-value">{{ task.customerName }}</text>
+        <view class="info-row">
+          <text class="info-label">客户名称</text>
+          <text class="info-value">{{ task.customerName }}</text>
         </view>
-        <view class="detail-row">
-          <text class="detail-label">联系电话</text>
-          <text class="detail-value">{{ task.customerPhone }}</text>
+        <view class="info-row">
+          <text class="info-label">联系电话</text>
+          <text class="info-value">{{ task.customerPhone }}</text>
         </view>
-        <view class="detail-row">
-          <text class="detail-label">项目地址</text>
-          <text class="detail-value">{{ task.address }}</text>
+        <view class="info-row">
+          <text class="info-label">项目地址</text>
+          <text class="info-value info-value-left">{{ task.address }}</text>
         </view>
-        <view class="detail-row">
-          <text class="detail-label">预约时间</text>
-          <text class="detail-value">{{ task.scheduleDate }} {{ task.scheduleTime }}</text>
+        <view class="info-row">
+          <text class="info-label">预约时间</text>
+          <text class="info-value">{{ task.scheduleDate }} {{ task.scheduleTime }}</text>
         </view>
-        <view class="detail-row">
-          <text class="detail-label">紧急程度</text>
-          <view class="detail-value">
-            <text :class="task.isEmergency ? 'text-danger' : 'text-success'">
-              {{ task.isEmergency ? '紧急' : '普通' }}
-            </text>
-          </view>
+        <view class="info-row">
+          <text class="info-label">紧急程度</text>
+          <text class="info-value" :class="task.isEmergency ? 'urgent-text' : 'normal-text'">
+            {{ task.isEmergency ? '紧急' : '普通' }}
+          </text>
         </view>
       </view>
 
       <!-- 服务点信息 -->
-      <view v-if="task.servicePoints && task.servicePoints.length > 0" class="card">
-        <view class="detail-section-title">
-          <text class="uni-icons uniui-location-filled text-primary mr-2"></text>
-          <text>服务点信息</text>
+      <view v-if="task.servicePoints && task.servicePoints.length > 0" class="glass-card">
+        <view class="section-head">
+          <view class="section-bar mr-2"></view>
+          <text class="section-title flex-1">服务点信息</text>
         </view>
         <view
           v-for="(point, index) in task.servicePoints"
           :key="index"
-          class="service-point-card"
+          class="point-row"
         >
           <view class="flex justify-between items-center mb-2">
-            <text class="text-sm font-semibold text-gray-800">{{ point.name }}</text>
-            <text class="tag-blue px-2 py-1 rounded text-xs">{{ point.serviceType }}</text>
+            <text class="point-name">{{ point.name }}</text>
+            <text class="type-tag">{{ point.serviceType }}</text>
           </view>
-          <view class="flex items-center text-xs text-gray-500">
-            <text class="uni-icons uniui-location-filled text-gray-400 mr-1"></text>
-            <text>{{ point.address }}</text>
+          <view class="flex items-center">
+            <text class="uni-icons uniui-location-filled meta-icon mr-1"></text>
+            <text class="meta-text">{{ point.address }}</text>
           </view>
         </view>
       </view>
 
       <!-- 处理记录 -->
-      <view class="card">
-        <view class="detail-section-title">
-          <text class="uni-icons uniui-compose-filled text-primary mr-2"></text>
-          <text>处理记录</text>
+      <view class="glass-card">
+        <view class="section-head">
+          <view class="section-bar mr-2"></view>
+          <text class="section-title flex-1">处理记录</text>
+        </view>
+        <view class="pb-3 px-2">
+          <Timeline :records="timelineRecords" />
         </view>
-        <Timeline :records="timelineRecords" />
       </view>
     </view>
   </view>
@@ -131,16 +132,132 @@ const timelineRecords = ref<TimelineRecord[]>([
 </script>
 
 <style scoped>
-/* 仅保留组件特有样式 */
-.service-point-card {
-  background: #f9fafb;
-  border-radius: 8px;
-  padding: 12px;
-  margin: 8px 16px;
+.sub-page {
+  max-width: 430px;
+  margin: 0 auto;
+  min-height: 100vh;
+  background-color: #f6fbf9;
+  padding-bottom: 24px;
+}
+
+.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;
+}
+
+.notice-title {
+  display: block;
+  font-size: 17px;
+  font-weight: 700;
+  color: #1f2937;
+  line-height: 1.4;
+}
+
+.emergency-tag {
+  padding: 2px 10px;
+  border-radius: 999px;
+  font-size: 11px;
+  font-weight: 600;
+  color: #ef4444;
+  background-color: rgba(239, 68, 68, 0.1);
+}
+
+.meta-line {
+  display: flex;
+  align-items: center;
+}
+
+.meta-icon {
+  font-size: 14px;
+  color: #9ca3af;
+}
+
+.meta-text {
+  font-size: 12px;
+  color: #9ca3af;
+}
+
+.meta-value {
+  font-size: 13px;
+  color: #4b5563;
+}
+
+.section-head {
+  display: flex;
+  align-items: center;
+  padding: 16px 16px 12px;
+}
+
+.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;
+}
+
+.info-row {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  padding: 12px 16px;
+  border-top: 1px solid rgba(164, 216, 152, 0.18);
+}
+
+.info-label {
+  font-size: 13px;
+  color: #9ca3af;
+  flex-shrink: 0;
+  margin-right: 12px;
+}
+
+.info-value {
+  font-size: 13px;
+  color: #1f2937;
+  text-align: right;
+}
+
+.info-value-left {
+  flex: 1;
+}
+
+.urgent-text {
+  color: #ef4444;
+  font-weight: 600;
+}
+
+.normal-text {
+  color: #368f6f;
+}
+
+.point-row {
+  padding: 13px 16px;
+  border-top: 1px solid rgba(164, 216, 152, 0.18);
+}
+
+.point-name {
+  font-size: 14px;
+  font-weight: 600;
+  color: #1f2937;
 }
 
-.tag-blue {
-  background: #e6f0ff;
-  color: #2563eb;
+.type-tag {
+  padding: 2px 10px;
+  border-radius: 999px;
+  font-size: 11px;
+  font-weight: 600;
+  color: #368f6f;
+  background-color: rgba(164, 216, 152, 0.24);
 }
 </style>

+ 362 - 100
src/subPackages/pages-construction/dailyTask.vue

@@ -1,114 +1,98 @@
 <template>
-  <view class="app-container pb-24">
+  <view class="sub-page">
     <TopBar title="每日任务" show-back />
 
-    <view class="px-4 py-4 gap-y-4">
+    <view class="px-4 pt-3">
       <!-- 今日任务概览 -->
-      <view class="card p-4">
-        <view class="detail-section-title mb-4">
-          <text class="uni-icons uniui-calendar-filled text-primary mr-2"></text>
-          <text>今日概览</text>
+      <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="bg-blue-50 rounded-xl p-4 text-center">
-            <text class="text-2xl font-bold text-blue-600 block">{{ todayTasks.length }}</text>
-            <text class="text-xs text-gray-500 mt-1 block">今日任务</text>
+          <view class="stat-card">
+            <view class="stat-bar"></view>
+            <text class="stat-value">{{ todayTasks.length }}</text>
+            <text class="stat-label">今日任务</text>
           </view>
-          <view class="bg-green-50 rounded-xl p-4 text-center">
-            <text class="text-2xl font-bold text-green-500 block">{{ completedTasks }}</text>
-            <text class="text-xs text-gray-500 mt-1 block">已完成</text>
+          <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="card p-4">
-        <view class="detail-section-title mb-4">
-          <text class="uni-icons uniui-cart-filled text-primary mr-2"></text>
-          <text>车辆检查</text>
-          <view class="ml-auto">
-            <text v-if="vehicleCheckStatus" class="text-xs text-success">已完成</text>
-            <text v-else class="text-xs text-warning">待检查</text>
-          </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="mb-3">
-            <text class="text-sm font-medium text-gray-700 mb-2 block">常规检查</text>
-            <view class="space-y-2">
-              <view
-                v-for="item in checkItems"
-                :key="item.key"
-                class="flex items-center justify-between py-2"
-                @click="toggleCheck(item.key)"
-              >
-                <text class="text-sm text-gray-600">{{ item.label }}</text>
-                <view
-                  class="w-6 h-6 rounded-full border-2 flex items-center justify-center"
-                  :class="checks[item.key] ? 'bg-primary border-primary' : 'border-gray-300'"
-                >
-                  <text v-if="checks[item.key]" class="uni-icons uniui-checkmarkempty text-white text-xs"></text>
-                </view>
+          <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="mb-3 border-t border-gray-100 pt-3">
-            <text class="text-sm font-medium text-gray-700 mb-2 block">设备检查</text>
-            <view class="space-y-2">
-              <view
-                v-for="item in equipmentItems"
-                :key="item.key"
-                class="flex items-center justify-between py-2"
-                @click="toggleCheck(item.key)"
-              >
-                <text class="text-sm text-gray-600">{{ item.label }}</text>
-                <view
-                  class="w-6 h-6 rounded-full border-2 flex items-center justify-center"
-                  :class="checks[item.key] ? 'bg-primary border-primary' : 'border-gray-300'"
-                >
-                  <text v-if="checks[item.key]" class="uni-icons uniui-checkmarkempty text-white text-xs"></text>
-                </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="mb-3 border-t border-gray-100 pt-3">
-            <text class="text-sm font-medium text-gray-700 mb-2 block">三大系统检查</text>
-            <view class="space-y-2">
-              <view
-                v-for="item in systemItems"
-                :key="item.key"
-                class="flex items-center justify-between py-2"
-                @click="toggleCheck(item.key)"
-              >
-                <text class="text-sm text-gray-600">{{ item.label }}</text>
-                <view
-                  class="w-6 h-6 rounded-full border-2 flex items-center justify-center"
-                  :class="checks[item.key] ? 'bg-primary border-primary' : 'border-gray-300'"
-                >
-                  <text v-if="checks[item.key]" class="uni-icons uniui-checkmarkempty text-white text-xs"></text>
-                </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="form-label">备注</text>
+            <text class="check-group-title">备注</text>
             <textarea
               v-model="remark"
-              class="w-full p-3 bg-surface rounded-xl text-sm text-gray-700 border border-gray-200"
+              class="form-textarea"
               placeholder="请输入检查备注..."
               :maxlength="200"
             />
           </view>
 
           <button
-            class="w-full py-3 btn-primary rounded-xl text-sm font-medium"
+            class="submit-btn"
+            hover-class="submit-btn-hover"
+            :hover-start-time="0"
+            :hover-stay-time="120"
             :disabled="!vehicleCheckStatus"
             @click="submitCheck"
           >
@@ -116,35 +100,47 @@
           </button>
         </view>
 
-        <view v-else class="text-center py-8">
-          <view class="w-16 h-16 bg-green-100 rounded-full flex items-center justify-center mx-auto mb-3">
-            <text class="uni-icons uniui-checkmarkempty text-green-500 text-2xl"></text>
+        <view v-else class="done-wrap">
+          <view class="done-icon">
+            <text class="uni-icons uniui-checkmarkempty done-tick"></text>
           </view>
-          <text class="text-sm text-gray-600 font-medium">今日车辆检查已完成</text>
-          <text v-if="remark" class="text-xs text-gray-400 mt-1 block">备注:{{ remark }}</text>
-          <text class="text-xs text-gray-400 mt-1 block">{{ today }}</text>
+          <text class="done-text">今日车辆检查已完成</text>
+          <text v-if="remark" class="done-meta">备注:{{ remark }}</text>
+          <text class="done-meta">{{ today }}</text>
         </view>
       </view>
 
       <!-- 今日任务列表 -->
-      <view class="card p-4">
-        <view class="detail-section-title mb-4">
-          <text class="uni-icons uniui-flag-filled text-primary mr-2"></text>
-          <text>今日任务列表</text>
+      <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="text-gray-400">加载中...</text>
+          <text class="empty-text">加载中...</text>
         </view>
-        <view v-else-if="todayTasks.length === 0">
+        <view v-else-if="todayTasks.length === 0" class="pb-3">
           <EmptyState message="今日无任务" />
         </view>
-        <view v-else class="space-y-3">
-          <TaskCard
+        <view v-else>
+          <view
             v-for="task in todayTasks"
-            :key="task.id"
-            :task="task"
-            @click="goToDetail(task.id)"
-          />
+            :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>
@@ -155,7 +151,7 @@
 import { ref, computed, onMounted } from 'vue'
 import { useTaskStore } from '../../stores/task'
 import TopBar from '../../components/common/TopBar.vue'
-import TaskCard from '../../components/task/TaskCard.vue'
+import StatusTag from '../../components/common/StatusTag.vue'
 import EmptyState from '../../components/common/EmptyState.vue'
 
 const taskStore = useTaskStore()
@@ -203,8 +199,10 @@ const completedTasks = computed(() => {
   return todayTasks.value.filter((t) => t.status === 'completed').length
 })
 
-const todayTasks = computed(() => {
-  return taskStore.tasks.filter((t) => t.scheduleDate === today || t.serviceDate === today)
+const todayTasks = computed<any[]>(() => {
+  return taskStore.tasks.filter(
+    (t: any) => t.planDate === today || t.scheduleDate === today || t.serviceDate === today
+  )
 })
 
 function toggleCheck(type: string) {
@@ -222,9 +220,9 @@ function submitCheck() {
   uni.showToast({ title: '提交成功', icon: 'success' })
 }
 
-function goToDetail(taskId: string) {
-  taskStore.setCurrentTask(taskId)
-  uni.navigateTo({ url: '/subPackages/pages-construction/taskDetail' })
+function goToDetail(taskId: string | number) {
+  if (!taskId) return
+  uni.navigateTo({ url: `/subPackages/pages-construction/taskDetail?id=${taskId}` })
 }
 
 onMounted(() => {
@@ -241,6 +239,270 @@ onMounted(() => {
     remark.value = savedRemark
   }
 
-  taskStore.fetchTasks()
+  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>

+ 108 - 27
src/subPackages/pages-construction/historyTask.vue

@@ -1,33 +1,36 @@
 <template>
-  <view class="app-container pb-20">
+  <view class="sub-page">
     <TopBar title="历史任务" show-back />
-    
-    <view class="px-4 mt-3">
+
+    <view class="px-4 pt-3">
       <view v-if="taskStore.loading" class="py-10 text-center">
-        <text class="text-gray-400">加载中...</text>
+        <text class="empty-text">加载中...</text>
       </view>
-      
+
       <view v-else-if="completedTasks.length === 0">
         <EmptyState message="暂无历史任务" />
       </view>
-      
-      <uni-list v-else border>
-        <uni-list-item 
-          v-for="task in completedTasks" 
-          :key="task.id"
-          :title="task.projectName"
-          :note="task.address"
-          right-text="已完成"
-          show-arrow
-          @click="goToDetail(task.id)"
+
+      <view v-else class="glass-card">
+        <view
+          v-for="task in completedTasks"
+          :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)"
         >
-          <template #header>
-            <view class="w-10 h-10 rounded-xl bg-green-50 flex items-center justify-center mr-3">
-              <text class="uni-icons uniui-checkmarkempty text-green-500"></text>
-            </view>
-          </template>
-        </uni-list-item>
-      </uni-list>
+          <view class="icon-chip mr-3">
+            <text class="uni-icons uniui-checkmarkempty 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>
+          <text class="done-tag flex-shrink-0">已完成</text>
+        </view>
+      </view>
     </view>
   </view>
 </template>
@@ -37,19 +40,97 @@ import { computed, onMounted } from 'vue'
 import { useTaskStore } from '../../stores/task'
 import TopBar from '../../components/common/TopBar.vue'
 import EmptyState from '../../components/common/EmptyState.vue'
-import UniList from '../../components/uni-list/uni-list.vue'
-import UniListItem from '../../components/uni-list/uni-list-item.vue'
 
 const taskStore = useTaskStore()
 
 const completedTasks = computed(() => taskStore.tasks.filter(t => t.status === 'completed'))
 
 function goToDetail(taskId: string) {
-  taskStore.setCurrentTask(taskId)
-  uni.navigateTo({ url: '/subPackages/pages-construction/taskDetail' })
+  if (!taskId) return
+  uni.navigateTo({ url: `/subPackages/pages-construction/taskDetail?id=${taskId}` })
 }
 
 onMounted(() => {
-  taskStore.fetchTasks()
+  taskStore.fetchMyTasks()
 })
 </script>
+
+<style scoped>
+.sub-page {
+  max-width: 430px;
+  margin: 0 auto;
+  min-height: 100vh;
+  background-color: #f6fbf9;
+  padding-bottom: 24px;
+}
+
+.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;
+}
+
+.list-row {
+  display: flex;
+  align-items: center;
+  padding: 13px 16px;
+  border-top: 1px solid rgba(164, 216, 152, 0.18);
+}
+
+.list-row:first-child {
+  border-top: none;
+}
+
+.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);
+}
+
+.done-tag {
+  padding: 2px 10px;
+  border-radius: 999px;
+  font-size: 11px;
+  font-weight: 600;
+  color: #368f6f;
+  background-color: rgba(164, 216, 152, 0.24);
+}
+
+.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>

+ 97 - 20
src/subPackages/pages-construction/noticeDetail.vue

@@ -1,28 +1,28 @@
 <template>
-  <view class="app-container pb-4">
+  <view class="sub-page">
     <TopBar title="公告详情" showBack />
 
-    <view class="px-4 py-4">
-      <view v-if="notice" class="bg-white rounded-xl p-4 shadow-sm">
-        <view class="flex justify-between items-start mb-3">
-          <text class="text-base font-bold text-gray-800">{{ notice.title }}</text>
-          <view v-if="notice.important" class="px-2 py-1 bg-red-50 text-red-500 text-xs rounded">重要</view>
+    <view class="px-4 pt-3">
+      <view v-if="notice" class="glass-card p-5">
+        <view class="flex items-start justify-between mb-3">
+          <text class="notice-title flex-1 mr-2">{{ notice.title }}</text>
+          <text v-if="notice.important" class="important-tag flex-shrink-0">重要</text>
         </view>
-        <view class="text-xs text-gray-400 mb-4">
-          <text> {{ formatDate(notice.createTime, 'YYYY-MM-DD') }}</text>
-          <text class="mx-2">|</text>
-          <text> 调度中心</text>
+        <view class="meta-row mb-4">
+          <text class="meta-text">{{ formatDate(notice.createTime, 'YYYY-MM-DD') }}</text>
+          <text class="meta-text mx-2">|</text>
+          <text class="meta-text">调度中心</text>
         </view>
-        <view class="text-sm text-gray-600 leading-relaxed">
-          <text class="block mb-3">各位施工同事:</text>
-          <text class="block mb-3">{{ notice.summary }}</text>
-          <text class="block mb-2">一、进入施工现场必须佩戴安全帽、安全背心等防护用品。</text>
-          <text class="block mb-2">二、施工前必须进行安全检查,确保设备正常运转。</text>
-          <text class="block mb-2">三、施工现场必须设置警示标志,做好安全防护措施。</text>
-          <text class="block mb-2">四、发现安全隐患及时上报,不得隐瞒。</text>
-          <text class="block mb-3">请各位同事严格遵守以上规定,确保施工安全。</text>
-          <text class="block">调度中心</text>
-          <text class="block">{{ formatDate(notice.createTime, 'YYYY年MM月DD日') }}</text>
+        <view class="notice-body">
+          <text class="body-p">各位施工同事:</text>
+          <text class="body-p">{{ notice.summary }}</text>
+          <text class="body-item">一、进入施工现场必须佩戴安全帽、安全背心等防护用品。</text>
+          <text class="body-item">二、施工前必须进行安全检查,确保设备正常运转。</text>
+          <text class="body-item">三、施工现场必须设置警示标志,做好安全防护措施。</text>
+          <text class="body-item">四、发现安全隐患及时上报,不得隐瞒。</text>
+          <text class="body-p">请各位同事严格遵守以上规定,确保施工安全。</text>
+          <text class="body-sign">调度中心</text>
+          <text class="body-sign">{{ formatDate(notice.createTime, 'YYYY年MM月DD日') }}</text>
         </view>
       </view>
       <EmptyState v-else message="公告不存在" icon="" />
@@ -75,3 +75,80 @@ onLoad((options) => {
   }
 })
 </script>
+
+<style scoped>
+.sub-page {
+  max-width: 430px;
+  margin: 0 auto;
+  min-height: 100vh;
+  background-color: #f6fbf9;
+  padding-bottom: 24px;
+}
+
+.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;
+}
+
+.notice-title {
+  font-size: 17px;
+  font-weight: 700;
+  color: #1f2937;
+  line-height: 1.4;
+}
+
+.important-tag {
+  padding: 2px 10px;
+  border-radius: 999px;
+  font-size: 11px;
+  font-weight: 600;
+  color: #ef4444;
+  background-color: rgba(239, 68, 68, 0.1);
+}
+
+.meta-row {
+  display: flex;
+  align-items: center;
+  padding-bottom: 12px;
+  border-bottom: 1px solid rgba(164, 216, 152, 0.18);
+}
+
+.meta-text {
+  font-size: 12px;
+  color: #9ca3af;
+}
+
+.notice-body {
+  padding-top: 4px;
+}
+
+.body-p {
+  display: block;
+  margin-bottom: 12px;
+  font-size: 14px;
+  line-height: 1.8;
+  color: #4b5563;
+}
+
+.body-item {
+  display: block;
+  margin-bottom: 8px;
+  font-size: 14px;
+  line-height: 1.8;
+  color: #4b5563;
+}
+
+.body-sign {
+  display: block;
+  text-align: right;
+  font-size: 13px;
+  line-height: 1.8;
+  color: #6b7280;
+}
+</style>

+ 159 - 36
src/subPackages/pages-construction/noticeList.vue

@@ -1,53 +1,55 @@
-
 <template>
-  <view class="app-container pb-20">
+  <view class="sub-page">
     <TopBar title="通知公告" show-back />
-    
+
     <!-- 筛选 -->
-    <view class="bg-white px-4 py-3 flex gap-2 overflow-x-auto hide-scrollbar">
-      <view 
-        v-for="type in filterTypes" 
-        :key="type.value"
-        class="filter-tab"
-        :class="{ active: currentFilter === type.value }"
-        @click="currentFilter = type.value"
-      >
-        {{ type.label }}
+    <view class="px-4 pt-3">
+      <view class="filter-bar">
+        <view
+          v-for="type in filterTypes"
+          :key="type.value"
+          class="filter-chip"
+          :class="{ 'filter-chip-active': currentFilter === type.value }"
+          @click="currentFilter = type.value"
+        >
+          {{ type.label }}
+        </view>
       </view>
     </view>
 
     <!-- 通知列表 -->
-    <view class="px-4 mt-3">
+    <view class="px-4 pt-3">
       <view v-if="loading" class="py-10 text-center">
-        <text class="text-gray-400">加载中...</text>
+        <text class="empty-text">加载中...</text>
       </view>
-      
+
       <view v-else-if="filteredNotices.length === 0">
         <EmptyState message="暂无通知" />
       </view>
-      
-      <view v-else class="gap-y-3">
-        <view 
-          v-for="notice in filteredNotices" 
+
+      <view v-else class="glass-card">
+        <view
+          v-for="notice in filteredNotices"
           :key="notice.noticeId"
-          class="card p-4 cursor-pointer"
+          class="notice-row"
+          hover-class="row-hover"
+          :hover-start-time="0"
+          :hover-stay-time="120"
           @click="goToDetail(notice.noticeId)"
         >
-          <view class="flex justify-between items-start mb-2">
-            <view class="flex items-center">
-              <view v-if="notice.priority === 3" class="w-2 h-2 bg-red-500 rounded-full mr-2"></view>
-              <text class="text-sm font-semibold text-gray-800">{{ notice.title }}</text>
+          <view class="flex items-center justify-between mb-2">
+            <view class="flex items-center flex-1 min-w-0 mr-2">
+              <view v-if="notice.priority === 3" class="priority-dot mr-2"></view>
+              <text class="row-title truncate">{{ notice.title }}</text>
             </view>
-            <view class="tag" :class="getTypeClass(notice.type)">
+            <text class="type-tag flex-shrink-0" :class="getTypeClass(notice.type)">
               {{ getTypeText(notice.type) }}
-            </view>
+            </text>
           </view>
-          
-          <text class="text-xs text-gray-500 line-clamp-2 mb-2 block">{{ notice.content }}</text>
-          
-          <view class="flex justify-between items-center">
-            <text class="text-xs text-gray-400">{{ notice.publisherName }} · {{ formatDate(notice.publishTime) }}</text>
-            <text class="text-xs text-gray-400"> {{ notice.readCount }}</text>
+          <text class="row-sub notice-content">{{ notice.content }}</text>
+          <view class="flex items-center justify-between mt-2">
+            <text class="row-date">{{ notice.publisherName }} · {{ formatDate(notice.publishTime) }}</text>
+            <text class="row-date">阅读 {{ notice.readCount }}</text>
           </view>
         </view>
       </view>
@@ -89,11 +91,11 @@ function getTypeText(type: string) {
 
 function getTypeClass(type: string) {
   const map: Record<string, string> = {
-    system: 'tag-blue',
-    emergency: 'tag-red',
-    task: 'tag-green',
+    system: 'type-tag-green',
+    emergency: 'type-tag-red',
+    task: 'type-tag-gold',
   }
-  return map[type] || 'tag-gray'
+  return map[type] || 'type-tag-gray'
 }
 
 function goToDetail(noticeId: number) {
@@ -112,3 +114,124 @@ onMounted(async () => {
   }
 })
 </script>
+
+<style scoped>
+.sub-page {
+  max-width: 430px;
+  margin: 0 auto;
+  min-height: 100vh;
+  background-color: #f6fbf9;
+  padding-bottom: 24px;
+}
+
+/* 筛选条:默认白底灰边,激活态绿色 */
+.filter-bar {
+  display: flex;
+  gap: 8px;
+  overflow-x: auto;
+}
+
+.filter-chip {
+  padding: 6px 16px;
+  border-radius: 999px;
+  font-size: 13px;
+  color: #6b7280;
+  background-color: #ffffff;
+  border: 1px solid #e5e7eb;
+  flex-shrink: 0;
+}
+
+.filter-chip-active {
+  color: #ffffff;
+  font-weight: 600;
+  background: linear-gradient(135deg, #368f6f 0%, #4ba98a 100%);
+  border-color: transparent;
+}
+
+.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;
+}
+
+.notice-row {
+  padding: 14px 16px;
+  border-top: 1px solid rgba(164, 216, 152, 0.18);
+}
+
+.notice-row:first-child {
+  border-top: none;
+}
+
+.row-hover {
+  background-color: rgba(164, 216, 152, 0.1);
+}
+
+.row-title {
+  font-size: 14px;
+  font-weight: 600;
+  color: #1f2937;
+}
+
+.row-sub {
+  font-size: 12px;
+  color: #9ca3af;
+}
+
+.notice-content {
+  display: -webkit-box;
+  -webkit-line-clamp: 2;
+  -webkit-box-orient: vertical;
+  overflow: hidden;
+}
+
+.row-date {
+  font-size: 12px;
+  color: #9ca3af;
+}
+
+.priority-dot {
+  width: 8px;
+  height: 8px;
+  border-radius: 50%;
+  background-color: #ef4444;
+  flex-shrink: 0;
+}
+
+.type-tag {
+  padding: 2px 10px;
+  border-radius: 999px;
+  font-size: 11px;
+  font-weight: 600;
+}
+
+.type-tag-green {
+  color: #368f6f;
+  background-color: rgba(164, 216, 152, 0.24);
+}
+
+.type-tag-red {
+  color: #ef4444;
+  background-color: rgba(239, 68, 68, 0.1);
+}
+
+.type-tag-gold {
+  color: #b7791f;
+  background-color: rgba(251, 211, 141, 0.25);
+}
+
+.type-tag-gray {
+  color: #6b7280;
+  background-color: #f3f4f6;
+}
+
+.empty-text {
+  font-size: 13px;
+  color: #9ca3af;
+}
+</style>

+ 119 - 25
src/subPackages/pages-construction/notification.vue

@@ -1,36 +1,38 @@
 <template>
-  <view class="app-container pb-20">
+  <view class="sub-page">
     <TopBar title="通知消息" show-back />
-    
-    <view class="px-4 mt-3">
+
+    <view class="px-4 pt-3">
       <view v-if="loading" class="py-10 text-center">
-        <text class="text-gray-400">加载中...</text>
+        <text class="empty-text">加载中...</text>
       </view>
-      
+
       <view v-else-if="noticeList.length === 0">
         <EmptyState message="暂无通知" />
       </view>
-      
-      <uni-list v-else border>
-        <uni-list-item 
-          v-for="notice in noticeList" 
+
+      <view v-else class="glass-card">
+        <view
+          v-for="notice in noticeList"
           :key="notice.noticeId"
-          :title="notice.title"
-          :note="notice.content"
-          :right-text="formatDate(notice.publishTime)"
-          show-arrow
+          class="list-row"
+          hover-class="row-hover"
+          :hover-start-time="0"
+          :hover-stay-time="120"
           @click="goToDetail(notice.noticeId)"
         >
-          <template #header>
-            <view class="relative mr-3">
-              <view class="w-10 h-10 rounded-xl bg-blue-50 flex items-center justify-center">
-                <text class="uni-icons uniui-notification-filled text-blue-500"></text>
-              </view>
-              <view v-if="!notice.read" class="absolute -top-1 -right-1 w-3 h-3 bg-red-500 rounded-full border-2 border-white"></view>
-            </view>
-          </template>
-        </uni-list-item>
-      </uni-list>
+          <view class="icon-chip mr-3">
+            <text class="uni-icons uniui-notification-filled chip-glyph"></text>
+            <view v-if="!notice.read" class="unread-dot"></view>
+          </view>
+          <view class="flex-1 min-w-0 mr-2">
+            <text class="row-title truncate">{{ notice.title }}</text>
+            <text class="row-sub truncate">{{ notice.content }}</text>
+          </view>
+          <text class="row-date flex-shrink-0 mr-1">{{ formatDate(notice.publishTime) }}</text>
+          <text class="uni-icons uniui-arrowright row-arrow"></text>
+        </view>
+      </view>
     </view>
   </view>
 </template>
@@ -39,8 +41,6 @@
 import { ref, onMounted } from 'vue'
 import TopBar from '../../components/common/TopBar.vue'
 import EmptyState from '../../components/common/EmptyState.vue'
-import UniList from '../../components/uni-list/uni-list.vue'
-import UniListItem from '../../components/uni-list/uni-list-item.vue'
 import { getNoticeList } from '../../api/notice'
 import { formatDate } from '../../utils'
 
@@ -63,3 +63,97 @@ onMounted(async () => {
   }
 })
 </script>
+
+<style scoped>
+.sub-page {
+  max-width: 430px;
+  margin: 0 auto;
+  min-height: 100vh;
+  background-color: #f6fbf9;
+  padding-bottom: 24px;
+}
+
+.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;
+}
+
+.list-row {
+  display: flex;
+  align-items: center;
+  padding: 13px 16px;
+  border-top: 1px solid rgba(164, 216, 152, 0.18);
+}
+
+.list-row:first-child {
+  border-top: none;
+}
+
+.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-date {
+  font-size: 12px;
+  color: #9ca3af;
+}
+
+.row-hover {
+  background-color: rgba(164, 216, 152, 0.1);
+}
+
+.row-arrow {
+  font-size: 16px;
+  color: #c4d0cb;
+  flex-shrink: 0;
+}
+
+.icon-chip {
+  position: relative;
+  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;
+}
+
+.unread-dot {
+  position: absolute;
+  top: -3px;
+  right: -3px;
+  width: 10px;
+  height: 10px;
+  border-radius: 50%;
+  background-color: #ef4444;
+  border: 2px solid #ffffff;
+}
+
+.empty-text {
+  font-size: 13px;
+  color: #9ca3af;
+}
+</style>

+ 832 - 189
src/subPackages/pages-construction/taskDetail.vue

@@ -1,177 +1,347 @@
 <template>
-  <view class="app-container pb-24">
-    <TopBar title="任务详情" showBack />
-
-    <view v-if="task" class="px-4 py-4 gap-y-4">
-      <!-- 施工流程进度 -->
-      <ConstructionFlowBar :status="task.status" :timeline="task.timeline" />
-
-      <!-- 操作按钮区 -->
-      <view class="card p-4">
-        <!-- 待确认 -->
-        <view v-if="task.status === 'pending'" class="gap-y-3">
-          <view class="bg-blue-50 rounded-xl p-4 mb-4 text-center">
-            <text class="uni-icons uniui-compose text-4xl block mb-2 text-blue-500"></text>
-            <text class="text-lg font-semibold text-gray-800 block">待确认任务</text>
-            <text class="text-gray-500 text-sm mt-1 block">请查看任务详情,确认是否接受</text>
-          </view>
-          <button class="w-full py-3 btn-success rounded-xl font-medium flex items-center justify-center gap-2" @click="onConfirm">
-            <text>确认任务</text>
-          </button>
-          <button class="w-full py-3 bg-orange-50 border border-orange-200 text-orange-600 rounded-xl font-medium flex items-center justify-center gap-2" @click="onFeedback">
-            <text>反馈问题</text>
-          </button>
-          <button class="w-full py-3 bg-red-50 border border-red-200 text-red-500 rounded-xl font-medium flex items-center justify-center gap-2" @click="onReject">
-            <text>拒绝任务</text>
-          </button>
+  <view class="sub-page">
+    <TopBar title="任务详情" show-back />
+
+    <view v-if="loading" class="py-10 text-center">
+      <text class="empty-text">加载中...</text>
+    </view>
+
+    <view v-else-if="task" class="px-4 pt-3">
+      <!-- 标题卡片 -->
+      <view class="glass-card p-4">
+        <view class="flex items-center justify-between mb-2">
+          <text class="text-base font-semibold text-gray-800 flex-1 mr-2">{{ taskName }}</text>
+          <StatusTag :status="task.status" />
         </view>
+        <view class="flex items-center">
+          <text class="text-xs text-gray-400">任务编号:{{ task.taskNo || '-' }}</text>
+          <text v-if="urgencyText" class="text-xs ml-3" :class="task.urgencyLevel === 'very_urgent' ? 'text-red-500' : 'text-gray-400'">{{ urgencyText }}</text>
+        </view>
+      </view>
 
-        <!-- 待出车 -->
-        <view v-else-if="task.status === 'scheduled'" class="gap-y-3">
-          <view class="bg-orange-50 rounded-xl p-4 mb-4 text-center">
-            <text class="uni-icons uniui-cart-filled text-4xl block mb-2 text-orange-500"></text>
-            <text class="text-lg font-semibold text-gray-800 block">准备出发</text>
-            <text class="text-gray-500 text-sm mt-1 block">请确认设备准备完毕后出发</text>
-          </view>
-          <view class="bg-white rounded-lg p-3 border border-gray-100 mb-3">
-            <view class="flex justify-between text-sm">
-              <text class="text-gray-400">目的地</text>
-              <text class="text-gray-800">{{ task.projectName }}</text>
-            </view>
-            <view v-if="task.vehicle" class="flex justify-between text-sm mt-2">
-              <text class="text-gray-400">车辆</text>
-              <text class="text-gray-800">{{ task.vehicle }}</text>
+      <!-- SOP 8步流程条 -->
+      <view class="glass-card p-4">
+        <view class="flow-row">
+          <view v-for="(label, i) in stepLabels" :key="i" class="flow-step">
+            <view class="flow-node-wrap">
+              <view v-if="i !== 0" class="flow-line" :class="i <= currentStep ? 'flow-line-active' : ''" />
+              <view class="flow-node" :class="nodeClass(i + 1)">
+                <text v-if="i + 1 <= currentStep" class="uni-icons uniui-checkmarkempty flow-check"></text>
+                <text v-else class="flow-node-text">{{ i + 1 }}</text>
+              </view>
             </view>
+            <text class="flow-label" :class="i + 1 <= currentStep ? 'flow-label-active' : ''">{{ label }}</text>
           </view>
-          <button class="w-full py-3 btn-primary rounded-xl font-semibold flex items-center justify-center gap-2" @click="onDepart">
-            <text>出发</text>
-          </button>
         </view>
+      </view>
 
-        <!-- 已出车 -->
-        <view v-else-if="task.status === 'departed'" class="gap-y-3">
-          <view class="bg-purple-50 rounded-xl p-4 mb-4 text-center">
-            <text class="uni-icons uniui-location-filled text-4xl block mb-2 text-purple-500"></text>
-            <text class="text-lg font-semibold text-gray-800 block">前往现场</text>
-            <text class="text-gray-500 text-sm mt-1 block">正在前往施工地点</text>
-          </view>
-          <button class="w-full py-3 btn-success rounded-xl font-semibold flex items-center justify-center gap-2" @click="onArrive">
-            <text>到达现场</text>
+      <!-- 待确认操作区 -->
+      <view v-if="isPendingConfirm" class="glass-card p-4">
+        <view class="banner banner-green mb-4">
+          <text class="banner-title">待确认任务</text>
+          <text class="banner-desc">{{ constructionTypeText }} · 服务时间 {{ serviceTimeText }}</text>
+        </view>
+        <view class="action-group">
+          <button class="action-btn action-btn--confirm" hover-class="action-btn--hover" :disabled="submitting" @click="onConfirm">
+            <text class="uni-icons uniui-checkmarkempty action-btn__icon"></text>
+            <text>确认任务</text>
           </button>
+          <view class="action-row">
+            <button class="action-btn action-btn--feedback" hover-class="action-btn--hover" @click="onFeedback">
+              <text class="uni-icons uniui-compose action-btn__icon"></text>
+              <text>反馈问题</text>
+            </button>
+            <button class="action-btn action-btn--reject" hover-class="action-btn--hover" @click="onReject">
+              <text class="uni-icons uniui-close action-btn__icon"></text>
+              <text>拒绝任务</text>
+            </button>
+          </view>
+        </view>
+      </view>
+
+      <!-- 已拒绝 / 已取消 -->
+      <view v-if="task.status === 'rejected' || task.status === 'cancelled'" class="glass-card p-4">
+        <view class="banner banner-red">
+          <text class="banner-title">{{ task.status === 'rejected' ? '任务已拒绝' : '任务已取消' }}</text>
+          <text v-if="task.cancelReason" class="banner-desc">原因:{{ task.cancelReason }}</text>
         </view>
+      </view>
 
-        <!-- 已到达(勘验) -->
-        <view v-else-if="task.status === 'arrived'" class="gap-y-3">
-          <view class="bg-green-50 rounded-xl p-4 mb-4 text-center">
-            <text class="uni-icons uniui-location text-4xl block mb-2 text-green-500"></text>
-            <text class="text-lg font-semibold text-gray-800 block">已到达现场</text>
-            <text class="text-gray-500 text-sm mt-1 block">请完成现场勘验后开始施工作业</text>
+      <!-- 出车准备(SOP:推进到第2步前) -->
+      <view v-if="currentStep === 1" class="glass-card p-4">
+        <text class="text-base font-semibold text-gray-800 mb-4 block">出车准备(SOP)</text>
+        <view class="gap-y-3 mb-4">
+          <view
+            v-for="item in checklist"
+            :key="item.key"
+            class="flex items-center justify-between py-2 border-b border-gray-100"
+            @click="item.checked = !item.checked"
+          >
+            <text class="text-sm text-gray-700">{{ item.label }}</text>
+            <view class="check-box" :class="item.checked ? 'check-box-on' : ''">
+              <text v-if="item.checked" class="uni-icons uniui-checkmarkempty check-icon"></text>
+            </view>
           </view>
-          <button class="w-full py-3 btn-primary rounded-xl font-semibold flex items-center justify-center gap-2" @click="onStartWork">
-            <text>开始施工</text>
-          </button>
         </view>
+        <text class="text-xs text-gray-400 block">请完成全部车辆点检后方可出发</text>
+      </view>
+
+      <!-- SOP 推进操作区 -->
+      <view v-if="canAdvance" class="glass-card p-4">
+        <view class="banner banner-green mb-4">
+          <text class="banner-title">当前步骤:{{ stepLabels[currentStep] || '待开始' }}</text>
+          <text class="banner-desc">下一步:{{ stepLabels[currentStep + 1] }}</text>
+        </view>
+        <view class="mb-4">
+          <text class="text-sm text-gray-500 mb-2 block">现场照片(可选)</text>
+          <ImageUploader v-model="stepPhotos" :max-count="6" tip="上传本步骤现场照片" />
+        </view>
+        <view class="mb-4">
+          <text class="text-sm text-gray-500 mb-2 block">备注(可选)</text>
+          <textarea v-model="stepRemark" class="form-textarea" placeholder="如:堵塞物情况、设施反馈等"></textarea>
+        </view>
+        <button
+          class="advance-btn"
+          :class="advanceDisabled ? 'advance-btn-disabled' : ''"
+          hover-class="advance-btn-hover"
+          :hover-start-time="0"
+          :hover-stay-time="120"
+          :disabled="submitting || advanceDisabled"
+          @click="onAdvance"
+        >
+          {{ nextActionText }}
+        </button>
+      </view>
 
-        <!-- 施工中 -->
-        <view v-else-if="task.status === 'constructing'" class="gap-y-3">
-          <view class="bg-blue-50 rounded-xl p-4 mb-4 text-center">
-            <text class="uni-icons uniui-gear-filled text-4xl block mb-2 text-blue-500"></text>
-            <text class="text-lg font-semibold text-gray-800 block">施工中</text>
-            <text class="text-gray-500 text-sm mt-1 block">请按规范完成施工作业</text>
+      <!-- 已完成 -->
+      <view v-if="task.status === 'completed'" class="glass-card p-4">
+        <view class="banner banner-green">
+          <text class="banner-title">任务已完成</text>
+          <text v-if="task.completionTime" class="banner-desc">完成时间:{{ formatDate(task.completionTime, 'YYYY-MM-DD HH:mm') }}</text>
+        </view>
+      </view>
+
+      <!-- 项目信息 -->
+      <view v-if="project" class="glass-card p-4">
+        <view class="section-header" @click="sections.project = !sections.project">
+          <text class="text-base font-semibold text-gray-800">项目信息</text>
+          <text class="uni-icons text-gray-400" :class="sections.project ? 'uniui-arrowup' : 'uniui-arrowdown'"></text>
+        </view>
+        <view v-if="sections.project" class="gap-y-3 mt-3">
+          <view class="flex items-start">
+            <text class="text-gray-500 text-sm w-24">项目名称</text>
+            <text class="text-sm text-gray-800 flex-1">{{ project.projectName }}</text>
+          </view>
+          <view v-if="coopTypeText" class="flex items-start">
+            <text class="text-gray-500 text-sm w-24">合作类型</text>
+            <text class="text-sm text-gray-800 flex-1">{{ coopTypeText }}</text>
+          </view>
+          <view v-if="projectAddress" class="flex items-start">
+            <text class="text-gray-500 text-sm w-24">项目地址</text>
+            <text class="text-sm text-gray-800 flex-1">{{ projectAddress }}</text>
+          </view>
+          <view v-if="project.primaryContactName" class="flex items-start">
+            <text class="text-gray-500 text-sm w-24">联系人</text>
+            <view class="flex-1 flex items-center justify-between">
+              <text class="text-sm text-gray-800">{{ project.primaryContactName }} {{ project.primaryContactPhone || '' }}</text>
+              <button
+                v-if="project.primaryContactPhone"
+                class="phone-btn"
+                @click="callPhone(project.primaryContactPhone)"
+              >
+                <text class="uni-icons uniui-phone-filled phone-icon"></text>
+              </button>
+            </view>
           </view>
-          <button class="w-full py-3 btn-success rounded-xl font-semibold flex items-center justify-center gap-2" @click="onComplete">
-            <text>完成施工</text>
-          </button>
         </view>
+      </view>
 
-        <!-- 待验收 -->
-        <view v-else-if="task.status === 'inspecting'" class="gap-y-3">
-          <view class="bg-warning-light rounded-xl p-4 mb-4 text-center">
-            <text class="uni-icons uniui-person-filled text-4xl block mb-2 text-warning"></text>
-            <text class="text-lg font-semibold text-gray-800 block">等待验收</text>
-            <text class="text-gray-500 text-sm mt-1 block">请等待客户或调度验收</text>
+      <!-- 施工信息 -->
+      <view class="glass-card p-4">
+        <view class="section-header" @click="sections.work = !sections.work">
+          <text class="text-base font-semibold text-gray-800">施工信息</text>
+          <text class="uni-icons text-gray-400" :class="sections.work ? 'uniui-arrowup' : 'uniui-arrowdown'"></text>
+        </view>
+        <view v-if="sections.work" class="gap-y-3 mt-3">
+          <view class="flex items-start">
+            <text class="text-gray-500 text-sm w-24">施工类型</text>
+            <text class="text-sm text-gray-800 flex-1">{{ constructionTypeText }}</text>
+          </view>
+          <view v-if="task.facilityType" class="flex items-start">
+            <text class="text-gray-500 text-sm w-24">设施类型</text>
+            <text class="text-sm text-gray-800 flex-1">{{ task.facilityType }}</text>
+          </view>
+          <view v-if="task.serviceMethod" class="flex items-start">
+            <text class="text-gray-500 text-sm w-24">服务方式</text>
+            <text class="text-sm text-gray-800 flex-1">{{ task.serviceMethod }}</text>
+          </view>
+          <view class="flex items-start">
+            <text class="text-gray-500 text-sm w-24">服务时间</text>
+            <text class="text-sm text-gray-800 flex-1">{{ serviceTimeText }}</text>
+          </view>
+          <view v-if="task.address" class="flex items-start">
+            <text class="text-gray-500 text-sm w-24">施工地址</text>
+            <text class="text-sm text-gray-800 flex-1">{{ task.address }}</text>
+          </view>
+          <view v-if="geoLocationText" class="flex items-start">
+            <text class="text-gray-500 text-sm w-24">地理定位</text>
+            <text class="text-sm link-text flex-1" @click="openMap">{{ geoLocationText }}(点击导航)</text>
+          </view>
+          <view v-if="task.constructionInstruction" class="flex items-start">
+            <text class="text-gray-500 text-sm w-24">施工指令</text>
+            <text class="text-sm text-gray-800 flex-1">{{ task.constructionInstruction }}</text>
+          </view>
+          <view v-if="task.riskTip" class="flex items-start">
+            <text class="text-gray-500 text-sm w-24">风险提示</text>
+            <text class="text-sm text-red-500 flex-1">{{ task.riskTip }}</text>
           </view>
-          <button class="w-full py-3 btn-success rounded-xl font-semibold flex items-center justify-center gap-2" @click="onAccept">
-            <text>完成验收</text>
-          </button>
         </view>
+      </view>
 
-        <!-- 已完成 -->
-        <view v-else-if="task.status === 'completed'" class="gap-y-3">
-          <view class="bg-green-50 rounded-xl p-4 text-center">
-            <text class="uni-icons uniui-checkmarkempty text-4xl block mb-2 text-green-500"></text>
-            <text class="text-lg font-semibold text-green-700 block">任务已完成</text>
-            <text class="text-gray-500 text-sm mt-1 block">该任务已验收通过</text>
+      <!-- 故障信息 -->
+      <view v-if="faultDescText || task.faultLocation || faultMedia.length > 0" class="glass-card p-4">
+        <view class="section-header" @click="sections.fault = !sections.fault">
+          <text class="text-base font-semibold text-gray-800">故障信息</text>
+          <text class="uni-icons text-gray-400" :class="sections.fault ? 'uniui-arrowup' : 'uniui-arrowdown'"></text>
+        </view>
+        <view v-if="sections.fault" class="gap-y-3 mt-3">
+          <view v-if="task.faultLocation" class="flex items-start">
+            <text class="text-gray-500 text-sm w-24">故障位置</text>
+            <text class="text-sm text-gray-800 flex-1">{{ task.faultLocation }}</text>
+          </view>
+          <view v-if="faultDescText" class="flex items-start">
+            <text class="text-gray-500 text-sm w-24">故障描述</text>
+            <text class="text-sm text-gray-800 flex-1">{{ faultDescText }}</text>
+          </view>
+          <view v-if="task.customerWords" class="flex items-start">
+            <text class="text-gray-500 text-sm w-24">客户原话</text>
+            <text class="text-sm text-gray-800 flex-1">{{ task.customerWords }}</text>
+          </view>
+          <view v-if="faultMedia.length > 0" class="flex items-start">
+            <text class="text-gray-500 text-sm w-24">故障图片</text>
+            <view class="flex-1 flex flex-wrap gap-2">
+              <image
+                v-for="(url, index) in faultMedia"
+                :key="index"
+                :src="url"
+                class="w-16 h-16 rounded-lg bg-gray-100"
+                mode="aspectFill"
+                @click="previewMedia(index)"
+              />
+            </view>
           </view>
         </view>
       </view>
 
-      <!-- 任务信息 -->
-      <view class="card p-4">
-        <view class="detail-section-title mb-4">
-          <text class="uni-icons uniui-flag-filled text-primary mr-2"></text>
-          <text>{{ task.projectName }}</text>
-          <StatusTag :status="task.status" class="ml-auto" />
+      <!-- 施工要求 -->
+      <view class="glass-card p-4">
+        <view class="section-header" @click="sections.require = !sections.require">
+          <text class="text-base font-semibold text-gray-800">施工要求</text>
+          <text class="uni-icons text-gray-400" :class="sections.require ? 'uniui-arrowup' : 'uniui-arrowdown'"></text>
         </view>
-        <view class="space-y-3">
-          <view class="detail-row">
-            <text class="detail-label">任务类型</text>
-            <text class="detail-value">{{ getTypeText(task.type) }}</text>
+        <view v-if="sections.require" class="gap-y-3 mt-3">
+          <view v-if="task.waterDistance" class="flex items-start">
+            <text class="text-gray-500 text-sm w-24">取水距离</text>
+            <text class="text-sm text-gray-800 flex-1">{{ task.waterDistance }}</text>
           </view>
-          <view class="detail-row">
-            <text class="detail-label">紧急程度</text>
-            <text :class="task.urgent === 'high' ? 'text-danger' : 'text-gray-800'">{{ getUrgentText(task.urgent) }}</text>
+          <view v-if="task.outletNormal !== undefined && task.outletNormal !== null" class="flex items-start">
+            <text class="text-gray-500 text-sm w-24">化粪池排口</text>
+            <text class="text-sm text-gray-800 flex-1">{{ task.outletNormal === 1 ? '正常' : '异常' }}</text>
           </view>
-          <view v-if="task.serviceType" class="detail-row">
-            <text class="detail-label">施工类型</text>
-            <text class="detail-value">{{ task.serviceType }}</text>
+          <view v-if="task.assistantInfo" class="flex items-start">
+            <text class="text-gray-500 text-sm w-24">施工助力</text>
+            <text class="text-sm text-gray-800 flex-1">{{ task.assistantInfo }}</text>
           </view>
-          <view v-if="task.facilityType" class="detail-row">
-            <text class="detail-label">设施类型</text>
-            <text class="detail-value">{{ task.facilityType }}</text>
+          <view v-if="task.estimatedWorkload" class="flex items-start">
+            <text class="text-gray-500 text-sm w-24">预计作业量</text>
+            <text class="text-sm text-gray-800 flex-1">{{ task.estimatedWorkload }}</text>
           </view>
-          <view v-if="task.faultLocation" class="detail-row">
-            <text class="detail-label">故障位置</text>
-            <text class="detail-value">{{ task.faultLocation }}</text>
+          <view v-if="task.estimatedDuration" class="flex items-start">
+            <text class="text-gray-500 text-sm w-24">预计工时</text>
+            <text class="text-sm text-gray-800 flex-1">{{ task.estimatedDuration }}</text>
           </view>
-          <view v-if="task.faultDesc" class="detail-row">
-            <text class="detail-label">故障描述</text>
-            <text class="detail-value">{{ task.faultDesc }}</text>
+          <view v-if="task.acceptanceRequirement" class="flex items-start">
+            <text class="text-gray-500 text-sm w-24">验收要求</text>
+            <text class="text-sm text-gray-800 flex-1">{{ task.acceptanceRequirement }}</text>
           </view>
-          <view v-if="task.assignee" class="detail-row">
-            <text class="detail-label">负责人</text>
-            <text class="detail-value">{{ task.assignee }}</text>
+          <view v-if="task.acceptanceMethod" class="flex items-start">
+            <text class="text-gray-500 text-sm w-24">验收方式</text>
+            <text class="text-sm text-gray-800 flex-1">{{ task.acceptanceMethod }}</text>
           </view>
-          <view v-if="task.vehicle" class="detail-row">
-            <text class="detail-label">车辆</text>
-            <text class="detail-value">{{ task.vehicle }}</text>
+          <view v-if="task.attentionPoint" class="flex items-start">
+            <text class="text-gray-500 text-sm w-24">注意要点</text>
+            <text class="text-sm text-gray-800 flex-1">{{ task.attentionPoint }}</text>
           </view>
-          <view v-if="task.team" class="detail-row">
-            <text class="detail-label">班组</text>
-            <text class="detail-value">{{ task.team }}</text>
+          <view v-if="task.toolRequirement" class="flex items-start">
+            <text class="text-gray-500 text-sm w-24">工具要求</text>
+            <text class="text-sm text-gray-800 flex-1">{{ task.toolRequirement }}</text>
           </view>
-          <view v-if="task.scheduledDate" class="detail-row">
-            <text class="detail-label">计划日期</text>
-            <text class="detail-value">{{ task.scheduledDate }}</text>
+        </view>
+      </view>
+
+      <!-- 甲方确认人 -->
+      <view v-if="partyAContacts.length > 0" class="glass-card p-4">
+        <text class="text-base font-semibold text-gray-800 mb-4 block">甲方确认人</text>
+        <view class="gap-y-2">
+          <view
+            v-for="(contact, index) in partyAContacts"
+            :key="index"
+            class="flex items-center justify-between py-2 border-b border-gray-100"
+          >
+            <text class="text-sm text-gray-800">{{ contact.name }} {{ contact.phone }}</text>
+            <button
+              v-if="contact.phone"
+              class="phone-btn"
+              @click="callPhone(contact.phone)"
+            >
+              <text class="uni-icons uniui-phone-filled phone-icon"></text>
+            </button>
           </view>
-          <view v-if="task.address" class="detail-row">
-            <text class="detail-label">项目地址</text>
-            <text class="detail-value text-right flex-1">{{ task.address }}</text>
+        </view>
+      </view>
+
+      <!-- 调度信息 -->
+      <view v-if="isDispatched" class="glass-card p-4">
+        <text class="text-base font-semibold text-gray-800 mb-4 block">调度信息</text>
+        <view class="gap-y-3">
+          <view class="flex items-start">
+            <text class="text-gray-500 text-sm w-24">执行班组</text>
+            <text class="text-sm text-gray-800 flex-1">{{ dispatchedTeamName }}</text>
+          </view>
+          <view class="flex items-start">
+            <text class="text-gray-500 text-sm w-24">调度车辆</text>
+            <text class="text-sm text-gray-800 flex-1">{{ dispatchedVehicleName }}</text>
           </view>
-          <view class="detail-row">
-            <text class="detail-label">创建时间</text>
-            <text class="detail-value">{{ formatDate(task.createTime, 'YYYY-MM-DD HH:mm') }}</text>
+          <view v-if="task.departureTime" class="flex items-start">
+            <text class="text-gray-500 text-sm w-24">出车时间</text>
+            <text class="text-sm text-gray-800 flex-1">{{ formatDate(task.departureTime, 'MM-DD HH:mm') }}</text>
+          </view>
+          <view v-if="task.arrivalTime" class="flex items-start">
+            <text class="text-gray-500 text-sm w-24">到达时间</text>
+            <text class="text-sm text-gray-800 flex-1">{{ formatDate(task.arrivalTime, 'MM-DD HH:mm') }}</text>
           </view>
         </view>
       </view>
 
-      <!-- 时间轴 -->
-      <view class="card p-4">
-        <view class="detail-section-title mb-4">
-          <text class="uni-icons uniui-time-filled text-primary mr-2"></text>
-          <text>操作记录</text>
+      <!-- 操作日志 -->
+      <view v-if="taskLogs.length > 0" class="glass-card p-4">
+        <text class="text-base font-semibold text-gray-800 mb-4 block">任务日志</text>
+        <view class="gap-y-4">
+          <view v-for="(log, index) in taskLogs" :key="log.logId || index" class="flex items-start">
+            <view class="flex flex-col items-center mr-3">
+              <view class="w-3 h-3 rounded-full" :class="index === 0 ? 'log-dot-active' : 'log-dot'"></view>
+              <view v-if="index < taskLogs.length - 1" class="w-px h-8 bg-gray-200 mt-1"></view>
+            </view>
+            <view class="flex-1 pb-2">
+              <text class="text-sm font-medium text-gray-800">{{ log.operation }}</text>
+              <view class="flex justify-between mt-1">
+                <text class="text-xs text-gray-500">{{ log.operatorName || '系统' }}</text>
+                <text class="text-xs text-gray-400">{{ formatDate(log.createTime, 'MM-DD HH:mm') }}</text>
+              </view>
+              <text v-if="log.remark" class="text-xs text-gray-400 block mt-1">{{ log.remark }}</text>
+            </view>
+          </view>
         </view>
-        <Timeline :records="sortedTimeline" />
       </view>
     </view>
 
@@ -180,95 +350,568 @@
 </template>
 
 <script setup lang="ts">
-import { computed } from 'vue'
+import { computed, reactive, ref } from 'vue'
 import { onLoad } from '@dcloudio/uni-app'
 import TopBar from '../../components/common/TopBar.vue'
 import StatusTag from '../../components/common/StatusTag.vue'
 import EmptyState from '../../components/common/EmptyState.vue'
-import Timeline from '../../components/common/Timeline.vue'
-import ConstructionFlowBar from '../../components/common/ConstructionFlowBar.vue'
-import { useTaskStore } from '../../stores/task'
-import { formatDate, getTypeText, getUrgentText, showToast, showModal, goBack } from '../../utils'
-import { updateTask } from '../../api/task'
-
-const taskStore = useTaskStore()
-const task = computed(() => taskStore.currentTask)
-
-const sortedTimeline = computed(() => {
-  if (!task.value?.timeline) return []
-  return [...task.value.timeline].sort((a, b) => new Date(b.time).getTime() - new Date(a.time).getTime())
+import ImageUploader from '../../components/common/ImageUploader.vue'
+import { formatDate, showToast } from '../../utils'
+import { getTaskDetail, advanceTaskStep, updateTask } from '../../api/task'
+import { getProjectDetail } from '../../api/project'
+import { getTeamSimpleList } from '../../api/team'
+import { getVehicleSimpleList } from '../../api/vehicle'
+
+const STEP_LABELS = ['确认', '准备', '现场', '施工', '拍照', '清洗', '签字', '清理']
+const stepLabels = STEP_LABELS
+
+const task = ref<any>(null)
+const project = ref<any>(null)
+const loading = ref(false)
+const submitting = ref(false)
+const taskId = ref<number>(0)
+
+const stepPhotos = ref<string[]>([])
+const stepRemark = ref('')
+
+const checklist = reactive([
+  { key: 'oil', label: '机油检查', checked: false },
+  { key: 'tire', label: '轮胎检查', checked: false },
+  { key: 'tools', label: '灯光、水管、工具检查', checked: false },
+])
+
+const sections = reactive({
+  project: true,
+  work: true,
+  fault: true,
+  require: false,
 })
 
-async function advanceStatus(step: number, successMsg: string) {
-  if (!task.value) return
+const teamOptions = ref<Array<{ value: number; label: string }>>([])
+const vehicleOptions = ref<Array<{ value: number; label: string }>>([])
+
+const currentStep = computed(() => Number(task.value?.currentStep) || 0)
+const taskLogs = computed(() => task.value?.logList || [])
+
+const isPendingConfirm = computed(() => task.value?.status === 'assigned' && currentStep.value === 0)
+const isDispatched = computed(() => ['assigned', 'in_progress', 'completed'].includes(task.value?.status))
+const canAdvance = computed(() => {
+  if (!task.value) return false
+  if (!['assigned', 'in_progress'].includes(task.value.status)) return false
+  if (currentStep.value === 0) return false
+  return currentStep.value < 8
+})
+
+const advanceDisabled = computed(() => currentStep.value === 1 && !checklist.every(c => c.checked))
+
+const nextActionText = computed(() => {
+  const next = currentStep.value + 1
+  const map: Record<number, string> = {
+    2: '完成准备,出发',
+    3: '到达现场',
+    4: '开始施工',
+    5: '完成拍照记录',
+    6: '完成清洗',
+    7: '完成签字验收',
+    8: '完成清理,结束任务',
+  }
+  return map[next] || '推进到下一步'
+})
+
+const extra = computed(() => {
+  if (!task.value?.extraData) return {} as any
   try {
-    await taskStore.advanceStep(Number(task.value.id), step)
-    showToast(successMsg)
-    await taskStore.fetchTaskDetail(Number(task.value.id))
-  } catch (error) {
-    showToast('操作失败')
+    return JSON.parse(task.value.extraData)
+  } catch {
+    return {} as any
+  }
+})
+
+const taskName = computed(() => {
+  if (!task.value) return ''
+  return task.value.taskName || task.value.serviceContent || task.value.faultLocation || taskTypeText(task.value.taskType) || '未命名任务'
+})
+
+const constructionTypeText = computed(() => {
+  if (!task.value) return ''
+  return task.value.constructionType || taskTypeText(task.value.taskType) || '-'
+})
+
+const serviceTimeText = computed(() => {
+  if (!task.value) return '待定'
+  const date = task.value.planDate || ''
+  const rangeMap: Record<string, string> = { morning: '上午', afternoon: '下午', evening: '晚上' }
+  const range = rangeMap[task.value.planTimeRange] || task.value.planTimeRange || ''
+  return date || range ? `${date} ${range}`.trim() : '待定'
+})
+
+const urgencyText = computed(() => {
+  const map: Record<string, string> = { normal: '普通', urgent: '紧急', very_urgent: '特急' }
+  return task.value?.urgencyLevel ? map[task.value.urgencyLevel] || task.value.urgencyLevel : ''
+})
+
+const coopTypeText = computed(() => {
+  const map: Record<string, string> = { annual: '包年', monthly: '包月', once: '单次', contract: '合同制' }
+  const v = project.value?.cooperationType
+  return v ? map[v] || v : ''
+})
+
+const projectAddress = computed(() => {
+  if (!project.value) return ''
+  const p = project.value
+  return [p.province, p.city, p.district, p.street, p.detailAddress || p.address].filter(Boolean).join('') || p.address || ''
+})
+
+const faultDescText = computed(() => {
+  if (!task.value) return ''
+  return [task.value.faultType, task.value.customerWords].filter(Boolean).join(';')
+})
+
+const geoLocationText = computed(() => {
+  if (extra.value.geoLocation) return extra.value.geoLocation
+  if (extra.value.latitude && extra.value.longitude) return `${extra.value.latitude}, ${extra.value.longitude}`
+  return ''
+})
+
+const faultMedia = computed<string[]>(() => {
+  const photos = extra.value.photos || extra.value.faultPhotos || extra.value.media
+  if (!photos) return []
+  if (Array.isArray(photos)) return photos
+  return String(photos).split(',').filter(Boolean)
+})
+
+const partyAContacts = computed<Array<{ name: string; phone: string }>>(() => {
+  const list = extra.value.partyAContacts || extra.value.partyA
+  if (!list) return []
+  if (Array.isArray(list)) return list
+  return []
+})
+
+const dispatchedTeamName = computed(() => {
+  if (!task.value?.dispatchTeamId) return '未安排'
+  return teamOptions.value.find(t => t.value === Number(task.value.dispatchTeamId))?.label || `班组#${task.value.dispatchTeamId}`
+})
+
+const dispatchedVehicleName = computed(() => {
+  if (!task.value?.dispatchVehicleId) return '未安排'
+  return vehicleOptions.value.find(v => v.value === Number(task.value.dispatchVehicleId))?.label || `车辆#${task.value.dispatchVehicleId}`
+})
+
+function nodeClass(step: number): string {
+  if (step <= currentStep.value) return 'flow-node-active'
+  if (step === currentStep.value + 1) return 'flow-node-current'
+  return ''
+}
+
+function taskTypeText(type: string): string {
+  const map: Record<string, string> = {
+    dredge: '疏通', repair: '维修', clean: '清掏', inspect: '检查', emergency: '应急',
+    '疏通': '疏通', '维修': '维修', '清掏': '清掏', '检查': '检查', '应急': '应急',
   }
+  return type ? map[type] || type : ''
 }
 
-function onConfirm() {
-  advanceStatus(1, '任务已确认')
+function callPhone(phone: string) {
+  if (!phone) return
+  uni.makePhoneCall({ phoneNumber: phone })
 }
 
-function onDepart() {
-  advanceStatus(3, '已出车')
+function openMap() {
+  const lat = Number(extra.value.latitude)
+  const lng = Number(extra.value.longitude)
+  if (!lat || !lng) return
+  uni.openLocation({ latitude: lat, longitude: lng, name: task.value?.faultLocation || '故障位置' })
 }
 
-function onArrive() {
-  advanceStatus(4, '已到达现场')
+function previewMedia(index: number) {
+  uni.previewImage({ urls: faultMedia.value, current: index })
 }
 
-function onStartWork() {
-  advanceStatus(5, '开始施工')
+onLoad((options) => {
+  if (options?.id) {
+    taskId.value = Number(options.id)
+    loadDetail()
+  }
+})
+
+async function loadDetail() {
+  loading.value = true
+  try {
+    const res: any = await getTaskDetail(taskId.value)
+    task.value = res
+    if (res?.projectId) {
+      getProjectDetail(res.projectId).then(p => { project.value = p }).catch(() => {})
+    }
+    if (teamOptions.value.length === 0) {
+      getTeamSimpleList().then(list => { teamOptions.value = list }).catch(() => {})
+    }
+    if (vehicleOptions.value.length === 0) {
+      getVehicleSimpleList().then(list => { vehicleOptions.value = list }).catch(() => {})
+    }
+  } catch (e) {
+    console.error('加载任务详情失败', e)
+    showToast('加载任务详情失败')
+  } finally {
+    loading.value = false
+  }
 }
 
-function onComplete() {
-  advanceStatus(6, '施工完成')
+async function doAdvance(step: number, successMsg: string) {
+  submitting.value = true
+  try {
+    const data: Record<string, any> = {}
+    if (stepRemark.value.trim()) data.remark = stepRemark.value.trim()
+    if (stepPhotos.value.length > 0) data.photos = stepPhotos.value
+    await advanceTaskStep(taskId.value, step, Object.keys(data).length > 0 ? data : undefined)
+    showToast(successMsg)
+    stepPhotos.value = []
+    stepRemark.value = ''
+    await loadDetail()
+  } catch (e) {
+    console.error('推进SOP步骤失败', e)
+  } finally {
+    submitting.value = false
+  }
+}
+
+function onConfirm() {
+  doAdvance(1, '任务已确认')
 }
 
-function onAccept() {
-  advanceStatus(7, '验收完成')
+function onAdvance() {
+  if (advanceDisabled.value) {
+    showToast('请先完成全部出车点检')
+    return
+  }
+  const next = currentStep.value + 1
+  if (next > 8) return
+  doAdvance(next, next === 8 ? '任务已完成' : `已推进到「${STEP_LABELS[next - 1]}」`)
 }
 
 async function onReject() {
-  const res = await showModal({
-    title: '拒绝任务',
-    content: '确定要拒绝此任务吗?',
-    showCancel: true,
-    confirmText: '确认拒绝',
+  const res = await new Promise<any>((resolve) => {
+    uni.showModal({
+      title: '拒绝任务',
+      editable: true,
+      placeholderText: '请输入拒绝原因(可选)',
+      confirmText: '确认拒绝',
+      success: resolve,
+      fail: () => resolve({ confirm: false }),
+    })
   })
-  if (res.confirm && task.value) {
-    try {
-      await updateTask(Number(task.value.id), { status: 'rejected' } as any)
-      showToast('已拒绝任务')
-      goBack()
-    } catch {
-      showToast('操作失败')
-    }
+  if (!res.confirm) return
+  submitting.value = true
+  try {
+    const t = task.value
+    await updateTask(taskId.value, {
+      taskNo: t.taskNo,
+      address: t.address,
+      taskType: t.taskType,
+      projectId: t.projectId,
+      planDate: t.planDate,
+      status: 'rejected',
+      remark: res.content || t.remark || '',
+    } as any)
+    showToast('已拒绝任务')
+    await loadDetail()
+  } catch (e) {
+    console.error('拒绝任务失败', e)
+  } finally {
+    submitting.value = false
   }
 }
 
 function onFeedback() {
-  showModal({
+  uni.showModal({
     title: '反馈问题',
-    content: '请描述遇到的问题,将提交给调度中心处理',
-    showCancel: true,
+    editable: true,
+    placeholderText: '请描述遇到的问题,将提交给调度中心处理',
     confirmText: '提交',
     success: (res) => {
       if (res.confirm) {
         showToast('反馈已提交')
       }
-    }
+    },
   })
 }
-
-onLoad((options) => {
-  if (options?.id) {
-    taskStore.setCurrentTask(options.id)
-  }
-})
 </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;
+}
+
+.empty-text {
+  font-size: 13px;
+  color: #9ca3af;
+}
+
+.link-text {
+  color: #368f6f;
+}
+
+/* 电话按钮 */
+.phone-btn {
+  width: 32px;
+  height: 32px;
+  border-radius: 50%;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  background-color: rgba(164, 216, 152, 0.2);
+  line-height: 32px;
+}
+
+.phone-btn::after {
+  border: none;
+}
+
+.phone-icon {
+  font-size: 16px;
+  color: #368f6f;
+}
+
+/* 日志圆点 */
+.log-dot {
+  background-color: #c4d0cb;
+}
+
+.log-dot-active {
+  background-color: #368f6f;
+  box-shadow: 0 0 0 3px rgba(164, 216, 152, 0.3);
+}
+
+/* 推进按钮 */
+.advance-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;
+}
+
+.advance-btn::after {
+  border: none;
+}
+
+.advance-btn-hover {
+  opacity: 0.88;
+}
+
+.advance-btn-disabled {
+  background: #e5e7eb;
+  color: #9ca3af;
+  box-shadow: none;
+}
+
+/* SOP 流程条 */
+.flow-row {
+  display: flex;
+  align-items: flex-start;
+  justify-content: space-between;
+}
+.flow-step {
+  flex: 1;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  min-width: 0;
+}
+.flow-node-wrap {
+  width: 100%;
+  height: 24px;
+  position: relative;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
+.flow-line {
+  position: absolute;
+  top: 50%;
+  left: 0;
+  right: 50%;
+  height: 2px;
+  background-color: #e5e7eb;
+  transform: translateY(-50%);
+}
+.flow-line-active {
+  background-color: #368f6f;
+}
+.flow-node {
+  width: 22px;
+  height: 22px;
+  border-radius: 50%;
+  background-color: #f3f4f6;
+  border: 2px solid #e5e7eb;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  z-index: 1;
+}
+.flow-node-active {
+  background-color: #368f6f;
+  border-color: #368f6f;
+}
+.flow-node-current {
+  background-color: #fff;
+  border-color: #368f6f;
+  box-shadow: 0 0 0 3px rgba(54, 143, 111, 0.2);
+}
+.flow-check {
+  color: #fff;
+  font-size: 11px;
+}
+.flow-node-text {
+  font-size: 10px;
+  color: #9ca3af;
+}
+.flow-node-current .flow-node-text {
+  color: #368f6f;
+  font-weight: 600;
+}
+.flow-label {
+  margin-top: 6px;
+  font-size: 10px;
+  color: #9ca3af;
+  text-align: center;
+}
+.flow-label-active {
+  color: #368f6f;
+}
+
+/* 状态横幅 */
+.banner {
+  border-radius: 12px;
+  padding: 16px;
+  text-align: center;
+}
+.banner-green {
+  background-color: rgba(164, 216, 152, 0.16);
+}
+.banner-red {
+  background-color: #fef2f2;
+}
+.banner-title {
+  display: block;
+  font-size: 16px;
+  font-weight: 600;
+  color: #1f2937;
+}
+.banner-desc {
+  display: block;
+  font-size: 12px;
+  color: #6b7280;
+  margin-top: 4px;
+}
+
+/* 待确认操作按钮 */
+.action-group {
+  display: flex;
+  flex-direction: column;
+  gap: 12px;
+}
+.action-row {
+  display: flex;
+  gap: 12px;
+}
+.action-row .action-btn {
+  flex: 1;
+}
+.action-btn {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  height: 44px;
+  border-radius: 12px;
+  font-size: 14px;
+  font-weight: 600;
+  line-height: 44px;
+  border: 1px solid transparent;
+  transition: transform 0.15s ease, opacity 0.15s ease;
+}
+.action-btn::after {
+  border: none;
+}
+.action-btn__icon {
+  font-size: 16px;
+  margin-right: 6px;
+}
+.action-btn--hover {
+  transform: scale(0.97);
+  opacity: 0.85;
+}
+.action-btn--confirm {
+  background: linear-gradient(135deg, #368f6f 0%, #5ab8d0 100%);
+  color: #ffffff;
+  box-shadow: 0 8px 20px -8px rgba(54, 143, 111, 0.6);
+}
+.action-btn--feedback {
+  background-color: #fffbeb;
+  border-color: #fcd34d;
+  color: #d97706;
+}
+.action-btn--reject {
+  background-color: #fef2f2;
+  border-color: #fca5a5;
+  color: #dc2626;
+}
+
+/* 点检勾选框 */
+.check-box {
+  width: 22px;
+  height: 22px;
+  border-radius: 6px;
+  border: 2px solid #d1d5db;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
+.check-box-on {
+  background-color: #368f6f;
+  border-color: #368f6f;
+}
+.check-icon {
+  color: #fff;
+  font-size: 12px;
+}
+
+/* 折叠区块标题 */
+.section-header {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+}
+
+/* 表单 */
+.form-textarea {
+  width: 100%;
+  padding: 12px 16px;
+  border: 1px solid #e5e7eb;
+  border-radius: 8px;
+  font-size: 14px;
+  color: #333;
+  background: #fff;
+  min-height: 72px;
+}
+</style>

+ 113 - 29
src/subPackages/pages-construction/tomorrowTask.vue

@@ -1,33 +1,37 @@
 <template>
-  <view class="app-container pb-20">
+  <view class="sub-page">
     <TopBar title="明日任务" show-back />
-    
-    <view class="px-4 mt-3">
+
+    <view class="px-4 pt-3">
       <view v-if="taskStore.loading" class="py-10 text-center">
-        <text class="text-gray-400">加载中...</text>
+        <text class="empty-text">加载中...</text>
       </view>
-      
+
       <view v-else-if="tomorrowTasks.length === 0">
         <EmptyState message="暂无明日任务" />
       </view>
-      
-      <uni-list v-else border>
-        <uni-list-item 
-          v-for="task in tomorrowTasks" 
-          :key="task.id"
-          :title="task.projectName"
-          :note="task.address"
-          :right-text="task.scheduleTime"
-          show-arrow
-          @click="goToDetail(task.id)"
+
+      <view v-else class="glass-card">
+        <view
+          v-for="task in tomorrowTasks"
+          :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)"
         >
-          <template #header>
-            <view class="w-10 h-10 rounded-xl bg-blue-50 flex items-center justify-center mr-3">
-              <text class="uni-icons uniui-calendar-filled text-blue-500"></text>
-            </view>
-          </template>
-        </uni-list-item>
-      </uni-list>
+          <view class="icon-chip mr-3">
+            <text class="uni-icons uniui-calendar-filled 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>
+          <text class="row-date flex-shrink-0 mr-1">{{ task.scheduleTime }}</text>
+          <text class="uni-icons uniui-arrowright row-arrow"></text>
+        </view>
+      </view>
     </view>
   </view>
 </template>
@@ -37,24 +41,104 @@ import { computed, onMounted } from 'vue'
 import { useTaskStore } from '../../stores/task'
 import TopBar from '../../components/common/TopBar.vue'
 import EmptyState from '../../components/common/EmptyState.vue'
-import UniList from '../../components/uni-list/uni-list.vue'
-import UniListItem from '../../components/uni-list/uni-list-item.vue'
 
 const taskStore = useTaskStore()
 
-const tomorrowTasks = computed(() => {
+const tomorrowTasks = computed<any[]>(() => {
   const tomorrow = new Date()
   tomorrow.setDate(tomorrow.getDate() + 1)
   const tomorrowStr = tomorrow.toISOString().split('T')[0]
-  return taskStore.tasks.filter(t => t.scheduleDate === tomorrowStr)
+  return taskStore.tasks.filter((t: any) => t.planDate === tomorrowStr || t.scheduleDate === tomorrowStr)
 })
 
 function goToDetail(taskId: string) {
-  taskStore.setCurrentTask(taskId)
-  uni.navigateTo({ url: '/subPackages/pages-construction/taskDetail' })
+  if (!taskId) return
+  uni.navigateTo({ url: `/subPackages/pages-construction/taskDetail?id=${taskId}` })
 }
 
 onMounted(() => {
-  taskStore.fetchTasks()
+  taskStore.fetchMyTasks()
 })
 </script>
+
+<style scoped>
+.sub-page {
+  max-width: 430px;
+  margin: 0 auto;
+  min-height: 100vh;
+  background-color: #f6fbf9;
+  padding-bottom: 24px;
+}
+
+.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;
+}
+
+.list-row {
+  display: flex;
+  align-items: center;
+  padding: 13px 16px;
+  border-top: 1px solid rgba(164, 216, 152, 0.18);
+}
+
+.list-row:first-child {
+  border-top: none;
+}
+
+.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-date {
+  font-size: 12px;
+  color: #9ca3af;
+}
+
+.row-hover {
+  background-color: rgba(164, 216, 152, 0.1);
+}
+
+.row-arrow {
+  font-size: 16px;
+  color: #c4d0cb;
+  flex-shrink: 0;
+}
+
+.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>

+ 77 - 8
src/subPackages/pages-contract/contractDetail.vue

@@ -1,5 +1,5 @@
 <template>
-  <view class="app-container pb-24">
+  <view class="app-container detail-page pb-24">
     <TopBar title="合同详情" show-back />
 
     <view v-if="loading" class="py-20 text-center">
@@ -14,17 +14,20 @@
       <!-- 头部 -->
       <view class="card p-4">
         <view class="flex items-center justify-between mb-3">
-          <text class="text-base font-bold text-gray-800 flex-1 min-w-0 mr-3 truncate">{{ contract.contractNo }}</text>
+          <view class="min-w-0 mr-3">
+            <text class="text-base font-bold text-gray-800 block truncate">{{ contract.contractNo }}</text>
+            <text class="card-sub block mt-0.5">合同编号</text>
+          </view>
           <view
-            class="px-2 py-1 rounded text-xs flex-shrink-0"
+            class="status-pill px-2.5 py-0.5 rounded-full text-xs flex-shrink-0"
             :style="{ backgroundColor: statusBg(contract.status), color: statusColor(contract.status) }"
           >
             {{ statusText(contract.status) }}
           </view>
         </view>
-        <view class="bg-green-50 rounded-xl p-4 text-center">
-          <text class="text-xs text-gray-500 block mb-1">合同金额</text>
-          <text class="text-2xl font-bold text-green-600">{{ formatMoney(contract.contractAmount) }}</text>
+        <view class="amount-hero rounded-2xl p-4 text-center">
+          <text class="amount-hero-label block mb-1">合同金额</text>
+          <text class="amount-hero-value">{{ formatMoney(contract.contractAmount) }}</text>
         </view>
       </view>
 
@@ -95,14 +98,14 @@
       <!-- 操作按钮 -->
       <view class="px-4 pt-2 pb-6 flex gap-3">
         <button
-          class="flex-1 py-4 bg-white border border-blue-200 text-blue-600 rounded-2xl text-base font-semibold flex items-center justify-center"
+          class="flex-1 py-4 btn-ghost rounded-2xl text-base font-semibold flex items-center justify-center"
           @click="goToEdit"
         >
           <text class="uni-icons uniui-compose mr-2"></text>
           编辑
         </button>
         <button
-          class="flex-1 py-4 bg-white border border-red-200 text-red-500 rounded-2xl text-base font-semibold flex items-center justify-center"
+          class="flex-1 py-4 btn-danger-ghost rounded-2xl text-base font-semibold flex items-center justify-center"
           @click="onDelete"
         >
           <text class="uni-icons uniui-trash mr-2"></text>
@@ -205,3 +208,69 @@ onLoad((options) => {
   }
 })
 </script>
+
+<style scoped>
+.detail-page {
+  background-color: #f6fbf9;
+}
+
+/* 卡片统一升级 */
+.card {
+  border-radius: 20px;
+  margin-bottom: 0;
+  border: 1px solid rgba(164, 216, 152, 0.28);
+  box-shadow: 0 14px 34px -18px rgba(54, 143, 111, 0.35);
+}
+
+/* 区块标题:去掉全局 padding/分割线,图标改青山绿 */
+.detail-section-title {
+  padding: 0;
+  border-bottom: none;
+  margin-bottom: 12px;
+}
+.text-primary {
+  color: #368f6f;
+}
+
+.card-sub {
+  font-size: 11px;
+  color: #b6c9be;
+}
+.status-pill {
+  font-weight: 500;
+}
+
+/* 金额 hero */
+.amount-hero {
+  background: linear-gradient(135deg, rgba(164, 216, 152, 0.22) 0%, rgba(90, 184, 208, 0.16) 100%);
+  border: 1px solid rgba(164, 216, 152, 0.30);
+}
+.amount-hero-label {
+  font-size: 12px;
+  color: #6b8a7a;
+}
+.amount-hero-value {
+  font-size: 26px;
+  font-weight: 700;
+  color: #2f7a5e;
+  letter-spacing: 0.5px;
+}
+
+/* 操作按钮 */
+.btn-ghost {
+  background-color: #ffffff;
+  border: 1px solid #368f6f;
+  color: #368f6f;
+}
+.btn-ghost:active {
+  background-color: rgba(164, 216, 152, 0.12);
+}
+.btn-danger-ghost {
+  background-color: #ffffff;
+  border: 1px solid #fca5a5;
+  color: #ef4444;
+}
+.btn-danger-ghost:active {
+  background-color: #fef2f2;
+}
+</style>

+ 43 - 1
src/subPackages/pages-contract/contractForm.vue

@@ -1,5 +1,5 @@
 <template>
-  <view class="app-container pb-24">
+  <view class="app-container detail-page pb-24">
     <TopBar :title="isEdit ? '编辑合同' : '新增合同'" show-back />
 
     <view v-if="loading" class="py-20 text-center">
@@ -348,3 +348,45 @@ onLoad(async (options) => {
   }
 })
 </script>
+
+<style scoped>
+.detail-page {
+  background-color: #f6fbf9;
+}
+
+.card {
+  border-radius: 20px;
+  margin-bottom: 0;
+  border: 1px solid rgba(164, 216, 152, 0.28);
+  box-shadow: 0 14px 34px -18px rgba(54, 143, 111, 0.35);
+}
+
+/* 区块标题与图标块统一青山绿水 */
+.detail-section-title {
+  padding: 0;
+  border-bottom: none;
+}
+.detail-section-title .w-8 {
+  background-color: rgba(164, 216, 152, 0.2) !important;
+}
+.detail-section-title .uni-icons {
+  color: #368f6f !important;
+}
+
+/* 主按钮 */
+.btn-primary {
+  background: linear-gradient(135deg, #368f6f 0%, #5ab8d0 100%);
+  box-shadow: 0 10px 22px -10px rgba(54, 143, 111, 0.55);
+}
+
+/* 输入聚焦态 */
+.form-input :deep(.uni-easyinput__content.focus) {
+  border-color: #368f6f !important;
+}
+
+/* 备注框白底 */
+.bg-surface {
+  background-color: #ffffff;
+  border: 1px solid #e5e7eb;
+}
+</style>

+ 51 - 4
src/subPackages/pages-customer/customerDetail.vue

@@ -1,5 +1,5 @@
 <template>
-  <view class="app-container pb-24">
+  <view class="app-container detail-page pb-24">
     <TopBar title="客户详情" show-back />
 
     <view v-if="loading" class="py-20 text-center">
@@ -21,7 +21,7 @@
             </text>
           </view>
           <view
-            class="px-2 py-1 rounded text-xs flex-shrink-0"
+            class="status-pill px-2.5 py-0.5 rounded-full text-xs flex-shrink-0"
             :style="{ backgroundColor: statusBg(customer.status), color: statusColor(customer.status) }"
           >
             {{ statusText(customer.status) }}
@@ -65,6 +65,10 @@
           <text class="detail-label">详细地址</text>
           <text class="detail-value text-right flex-1">{{ customer.detailAddress || '-' }}</text>
         </view>
+        <view v-if="customer.latitude || customer.longitude" class="detail-row">
+          <text class="detail-label">经纬度</text>
+          <text class="detail-value text-right flex-1">{{ customer.latitude || '-' }}, {{ customer.longitude || '-' }}</text>
+        </view>
       </view>
 
       <!-- 备注 -->
@@ -88,14 +92,14 @@
       <!-- 操作按钮 -->
       <view class="px-4 pt-2 pb-6 flex gap-3">
         <button
-          class="flex-1 py-4 bg-white border border-blue-200 text-blue-600 rounded-2xl text-base font-semibold flex items-center justify-center"
+          class="flex-1 py-4 btn-ghost rounded-2xl text-base font-semibold flex items-center justify-center"
           @click="goToEdit"
         >
           <text class="uni-icons uniui-compose mr-2"></text>
           编辑
         </button>
         <button
-          class="flex-1 py-4 bg-white border border-red-200 text-red-500 rounded-2xl text-base font-semibold flex items-center justify-center"
+          class="flex-1 py-4 btn-danger-ghost rounded-2xl text-base font-semibold flex items-center justify-center"
           @click="onDelete"
         >
           <text class="uni-icons uniui-trash mr-2"></text>
@@ -182,3 +186,46 @@ onLoad((options) => {
   }
 })
 </script>
+
+<style scoped>
+.detail-page {
+  background-color: #f6fbf9;
+}
+
+.card {
+  border-radius: 20px;
+  margin-bottom: 0;
+  border: 1px solid rgba(164, 216, 152, 0.28);
+  box-shadow: 0 14px 34px -18px rgba(54, 143, 111, 0.35);
+}
+
+.detail-section-title {
+  padding: 0;
+  border-bottom: none;
+  margin-bottom: 12px;
+}
+.text-primary {
+  color: #368f6f;
+}
+
+.status-pill {
+  font-weight: 500;
+}
+
+.btn-ghost {
+  background-color: #ffffff;
+  border: 1px solid #368f6f;
+  color: #368f6f;
+}
+.btn-ghost:active {
+  background-color: rgba(164, 216, 152, 0.12);
+}
+.btn-danger-ghost {
+  background-color: #ffffff;
+  border: 1px solid #fca5a5;
+  color: #ef4444;
+}
+.btn-danger-ghost:active {
+  background-color: #fef2f2;
+}
+</style>

+ 57 - 2
src/subPackages/pages-customer/customerForm.vue

@@ -1,5 +1,5 @@
 <template>
-  <view class="app-container pb-24">
+  <view class="app-container detail-page pb-24">
     <TopBar :title="isEdit ? '编辑客户' : '新增客户'" show-back />
 
     <view v-if="loading" class="py-20 text-center">
@@ -101,6 +101,17 @@
             <uni-easyinput class="form-input" placeholder="请输入详细地址" v-model="form.detailAddress" @blur="validateField('detailAddress')" />
             <text v-if="errors.detailAddress" class="text-red-500 text-xs mt-1">{{ errors.detailAddress }}</text>
           </view>
+
+          <view class="grid grid-cols-2 gap-3">
+            <view>
+              <text class="form-label">纬度</text>
+              <uni-easyinput class="form-input" placeholder="选填" v-model="form.latitude" type="digit" />
+            </view>
+            <view>
+              <text class="form-label">经度</text>
+              <uni-easyinput class="form-input" placeholder="选填" v-model="form.longitude" type="digit" />
+            </view>
+          </view>
         </view>
       </view>
 
@@ -164,6 +175,8 @@ const form = ref({
   district: '',
   street: '',
   detailAddress: '',
+  latitude: '',
+  longitude: '',
   status: 1 as CustomerStatus,
   remark: ''
 })
@@ -199,7 +212,7 @@ function validateField(field: string) {
     return false
   }
   if (field === 'phone') {
-    const re = /^1[3-9]\d{9}$/
+    const re = /^1(3[0-9]|4[5-9]|5[0-3,5-9]|6[6-7]|7[0-8]|8[0-9]|9[0-35-9])\d{8}$/
     if (!re.test(value)) {
       errors[field] = '请输入正确的手机号'
       return false
@@ -229,6 +242,8 @@ async function loadDetail(id: number | string) {
       district: c.district || '',
       street: c.street || '',
       detailAddress: c.detailAddress || '',
+      latitude: c.latitude || '',
+      longitude: c.longitude || '',
       status: (Number(c.status) as CustomerStatus) ?? 1,
       remark: c.remark || ''
     }
@@ -255,6 +270,8 @@ async function submitForm() {
       district: form.value.district || undefined,
       street: form.value.street || undefined,
       detailAddress: form.value.detailAddress.trim(),
+      latitude: form.value.latitude || undefined,
+      longitude: form.value.longitude || undefined,
       status: form.value.status,
       remark: form.value.remark || undefined
     }
@@ -281,3 +298,41 @@ onLoad((options) => {
   }
 })
 </script>
+
+<style scoped>
+.detail-page {
+  background-color: #f6fbf9;
+}
+
+.card {
+  border-radius: 20px;
+  margin-bottom: 0;
+  border: 1px solid rgba(164, 216, 152, 0.28);
+  box-shadow: 0 14px 34px -18px rgba(54, 143, 111, 0.35);
+}
+
+.detail-section-title {
+  padding: 0;
+  border-bottom: none;
+}
+.detail-section-title .w-8 {
+  background-color: rgba(164, 216, 152, 0.2) !important;
+}
+.detail-section-title .uni-icons {
+  color: #368f6f !important;
+}
+
+.btn-primary {
+  background: linear-gradient(135deg, #368f6f 0%, #5ab8d0 100%);
+  box-shadow: 0 10px 22px -10px rgba(54, 143, 111, 0.55);
+}
+
+.form-input :deep(.uni-easyinput__content.focus) {
+  border-color: #368f6f !important;
+}
+
+.bg-surface {
+  background-color: #ffffff;
+  border: 1px solid #e5e7eb;
+}
+</style>

+ 292 - 121
src/subPackages/pages-dispatch/availableVehicles.vue

@@ -1,99 +1,106 @@
 <template>
-  <view class="app-container pb-20">
+  <view class="sub-page">
     <TopBar title="可用车辆" show-back />
 
-    <!-- 统计卡片 -->
-    <view class="px-4 py-4">
-      <view class="grid grid-cols-3 gap-3">
-        <view class="bg-green-50 rounded-xl p-3 text-center">
-          <text class="text-xl font-bold text-green-600 block">{{ availableCount }}</text>
-          <text class="text-xs text-gray-500">可用</text>
-        </view>
-        <view class="bg-blue-50 rounded-xl p-3 text-center">
-          <text class="text-xl font-bold text-blue-600 block">{{ inUseCount }}</text>
-          <text class="text-xs text-gray-500">使用中</text>
-        </view>
-        <view class="bg-yellow-50 rounded-xl p-3 text-center">
-          <text class="text-xl font-bold text-yellow-600 block">{{ maintenanceCount }}</text>
-          <text class="text-xs text-gray-500">维修中</text>
+    <view class="px-4 pt-3">
+      <!-- 统计卡片 -->
+      <view class="glass-card p-4">
+        <view class="grid grid-cols-3 gap-3">
+          <view class="stat-card">
+            <view class="stat-bar"></view>
+            <text class="stat-value">{{ availableCount }}</text>
+            <text class="stat-label">可用</text>
+          </view>
+          <view class="stat-card">
+            <view class="stat-bar"></view>
+            <text class="stat-value stat-value-blue">{{ inUseCount }}</text>
+            <text class="stat-label">使用中</text>
+          </view>
+          <view class="stat-card">
+            <view class="stat-bar"></view>
+            <text class="stat-value stat-value-gold">{{ maintenanceCount }}</text>
+            <text class="stat-label">维修中</text>
+          </view>
         </view>
       </view>
-    </view>
 
-    <!-- 筛选 -->
-    <view class="px-4 pb-2">
-      <uni-segmented-control
-        :current="currentFilterIndex"
-        :values="filterLabels"
-        style-type="text"
-        active-color="#4f8ef7"
-        @clickItem="onFilterChange"
-      />
-    </view>
+      <!-- 筛选 -->
+      <view class="filter-bar mb-3">
+        <view
+          v-for="type in filterTypes"
+          :key="type.value"
+          class="filter-chip"
+          :class="{ 'filter-chip-active': currentFilter === type.value }"
+          @click="currentFilter = type.value"
+        >
+          {{ type.label }}
+        </view>
+      </view>
 
-    <!-- 车辆列表 -->
-    <view class="px-4 mt-2">
+      <!-- 车辆列表 -->
       <view v-if="loading" class="py-10 text-center">
-        <text class="text-gray-400">加载中...</text>
+        <text class="empty-text">加载中...</text>
       </view>
 
       <view v-else-if="filteredVehicles.length === 0">
         <EmptyState message="暂无车辆" />
       </view>
 
-      <view v-else class="gap-y-3">
+      <view v-else class="glass-card">
         <view
           v-for="vehicle in filteredVehicles"
           :key="vehicle.id"
-          class="card p-4"
+          class="vehicle-row"
         >
-          <view class="flex items-center justify-between mb-3">
-            <view class="flex items-center">
-              <view class="w-10 h-10 rounded-xl flex items-center justify-center mr-3" :class="getStatusBgClass(vehicle.status)">
-                <text class="uni-icons text-lg" :class="getStatusIconClass(vehicle.status)"></text>
+          <view class="flex items-center justify-between">
+            <view class="flex items-center flex-1 min-w-0 mr-2">
+              <view class="icon-chip mr-3">
+                <text class="uni-icons uniui-cart-filled chip-glyph"></text>
               </view>
-              <view>
-                <text class="text-sm font-semibold text-gray-800">{{ vehicle.plateNumber }}</text>
-                <text class="text-xs text-gray-500">{{ vehicle.type }}</text>
+              <view class="min-w-0">
+                <text class="row-title truncate">{{ vehicle.plateNumber }}</text>
+                <text class="row-sub truncate">{{ vehicle.type }}</text>
               </view>
             </view>
-            <text class="tag" :class="getStatusTagClass(vehicle.status)">{{ getStatusText(vehicle.status) }}</text>
+            <text class="status-tag flex-shrink-0" :class="getStatusTagClass(vehicle.status)">
+              {{ getStatusText(vehicle.status) }}
+            </text>
           </view>
 
-          <view class="grid grid-cols-2 gap-y-2 gap-x-4 text-sm">
-            <view class="flex justify-between">
-              <text class="text-gray-400">尺寸</text>
-              <text class="text-gray-700">{{ vehicle.size || '-' }}</text>
-            </view>
-            <view class="flex justify-between">
-              <text class="text-gray-400">载重</text>
-              <text class="text-gray-700">{{ vehicle.loadCapacity || '-' }}</text>
-            </view>
-            <view class="flex justify-between">
-              <text class="text-gray-400">容积</text>
-              <text class="text-gray-700">{{ vehicle.capacity || '-' }}</text>
-            </view>
-            <view class="flex justify-between">
-              <text class="text-gray-400">驾驶员</text>
-              <text class="text-gray-700">{{ vehicle.driver || '未分配' }}</text>
+          <view class="row-divider">
+            <view class="grid grid-cols-2 gap-y-2 gap-x-4">
+              <view class="meta-line">
+                <text class="meta-label">尺寸</text>
+                <text class="meta-value">{{ vehicle.size || '-' }}</text>
+              </view>
+              <view class="meta-line">
+                <text class="meta-label">载重</text>
+                <text class="meta-value">{{ vehicle.loadCapacity || '-' }}</text>
+              </view>
+              <view class="meta-line">
+                <text class="meta-label">容积</text>
+                <text class="meta-value">{{ vehicle.capacity || '-' }}</text>
+              </view>
+              <view class="meta-line">
+                <text class="meta-label">驾驶员</text>
+                <text class="meta-value">{{ vehicle.driver || '未分配' }}</text>
+              </view>
             </view>
           </view>
 
-          <view class="mt-3 pt-3 border-t border-gray-100 flex gap-3">
-            <button
-              v-if="vehicle.driverPhone"
-              class="flex-1 py-2 bg-primary-light text-primary rounded-lg text-sm font-medium"
-              @click="callPhone(vehicle.driverPhone)"
-            >
-              联系司机
-            </button>
-            <button
-              v-else
-              class="flex-1 py-2 bg-gray-100 text-gray-400 rounded-lg text-sm font-medium"
-              disabled
-            >
-              暂无司机
-            </button>
+          <view
+            v-if="vehicle.driverPhone"
+            class="phone-btn mt-3"
+            hover-class="phone-btn-hover"
+            :hover-start-time="0"
+            :hover-stay-time="120"
+            @click="callPhone(vehicle.driverPhone)"
+          >
+            <text class="uni-icons uniui-phone-filled phone-glyph mr-1"></text>
+            <text class="phone-text">联系司机</text>
+          </view>
+          <view v-else class="phone-btn-disabled mt-3">
+            <text class="phone-text-disabled">暂无司机</text>
           </view>
         </view>
       </view>
@@ -105,12 +112,11 @@
 import { ref, computed, onMounted } from 'vue'
 import TopBar from '../../components/common/TopBar.vue'
 import EmptyState from '../../components/common/EmptyState.vue'
-import UniSegmentedControl from '../../components/uni-segmented-control/uni-segmented-control.vue'
 import { useVehicleStore } from '../../stores/vehicle'
 
 const vehicleStore = useVehicleStore()
 const loading = ref(false)
-const currentFilterIndex = ref(0)
+const currentFilter = ref('all')
 
 const filterTypes = [
   { value: 'all', label: '全部' },
@@ -119,12 +125,9 @@ const filterTypes = [
   { value: 'maintenance', label: '维修中' },
 ]
 
-const filterLabels = filterTypes.map((t) => t.label)
-
 const filteredVehicles = computed(() => {
-  const filterValue = filterTypes[currentFilterIndex.value].value
-  if (filterValue === 'all') return vehicleStore.vehicles
-  return vehicleStore.vehicles.filter((v) => v.status === filterValue)
+  if (currentFilter.value === 'all') return vehicleStore.vehicles
+  return vehicleStore.vehicles.filter((v) => v.status === currentFilter.value)
 })
 
 const availableCount = computed(() => vehicleStore.vehicles.filter((v) => v.status === 'available').length)
@@ -141,38 +144,14 @@ function getStatusText(status: string) {
   return map[status] || status
 }
 
-function getStatusBgClass(status: string) {
-  const map: Record<string, string> = {
-    available: 'bg-green-50',
-    in_use: 'bg-blue-50',
-    maintenance: 'bg-yellow-50',
-    offline: 'bg-gray-50',
-  }
-  return map[status] || 'bg-gray-50'
-}
-
-function getStatusIconClass(status: string) {
-  const map: Record<string, string> = {
-    available: 'uniui-checkmarkempty text-green-500',
-    in_use: 'uniui-cart-filled text-blue-500',
-    maintenance: 'uniui-gear-filled text-yellow-500',
-    offline: 'uniui-close text-gray-400',
-  }
-  return map[status] || 'uniui-cart text-gray-400'
-}
-
 function getStatusTagClass(status: string) {
   const map: Record<string, string> = {
-    available: 'tag-green',
-    in_use: 'tag-blue',
-    maintenance: 'tag-yellow',
-    offline: 'tag-gray',
+    available: 'status-tag-green',
+    in_use: 'status-tag-blue',
+    maintenance: 'status-tag-gold',
+    offline: 'status-tag-gray',
   }
-  return map[status] || 'tag-gray'
-}
-
-function onFilterChange(index: number) {
-  currentFilterIndex.value = index
+  return map[status] || 'status-tag-gray'
 }
 
 function callPhone(phone: string) {
@@ -189,31 +168,223 @@ onMounted(() => {
 </script>
 
 <style scoped>
-.tag {
-  display: inline-block;
-  padding: 2px 8px;
-  border-radius: 4px;
+.sub-page {
+  max-width: 430px;
+  margin: 0 auto;
+  min-height: 100vh;
+  background-color: #f6fbf9;
+  padding-bottom: 24px;
+}
+
+.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;
+}
+
+.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-value-blue {
+  color: #2563eb;
+}
+
+.stat-value-gold {
+  color: #b7791f;
+}
+
+.stat-label {
+  margin-top: 6px;
   font-size: 12px;
-  font-weight: 500;
+  color: #6b7280;
 }
 
-.tag-green {
-  background: #e6f7f1;
-  color: #059669;
+/* 筛选条:默认白底灰边,激活态绿色 */
+.filter-bar {
+  display: flex;
+  gap: 8px;
+  overflow-x: auto;
 }
 
-.tag-blue {
-  background: #eff6ff;
+.filter-chip {
+  padding: 6px 16px;
+  border-radius: 999px;
+  font-size: 13px;
+  color: #6b7280;
+  background-color: #ffffff;
+  border: 1px solid #e5e7eb;
+  flex-shrink: 0;
+}
+
+.filter-chip-active {
+  color: #ffffff;
+  font-weight: 600;
+  background: linear-gradient(135deg, #368f6f 0%, #4ba98a 100%);
+  border-color: transparent;
+}
+
+.vehicle-row {
+  padding: 14px 16px;
+  border-top: 1px solid rgba(164, 216, 152, 0.18);
+}
+
+.vehicle-row:first-child {
+  border-top: none;
+}
+
+.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;
+}
+
+.row-title {
+  display: block;
+  font-size: 14px;
+  font-weight: 600;
+  color: #1f2937;
+}
+
+.row-sub {
+  display: block;
+  margin-top: 3px;
+  font-size: 12px;
+  color: #9ca3af;
+}
+
+.status-tag {
+  padding: 2px 10px;
+  border-radius: 999px;
+  font-size: 11px;
+  font-weight: 600;
+}
+
+.status-tag-green {
+  color: #368f6f;
+  background-color: rgba(164, 216, 152, 0.24);
+}
+
+.status-tag-blue {
   color: #2563eb;
+  background-color: rgba(37, 99, 235, 0.1);
 }
 
-.tag-yellow {
-  background: #fffbeb;
-  color: #d97706;
+.status-tag-gold {
+  color: #b7791f;
+  background-color: rgba(251, 211, 141, 0.25);
 }
 
-.tag-gray {
-  background: #f3f4f6;
+.status-tag-gray {
   color: #6b7280;
+  background-color: #f3f4f6;
+}
+
+.row-divider {
+  margin-top: 12px;
+  padding-top: 12px;
+  border-top: 1px solid rgba(164, 216, 152, 0.18);
+}
+
+.meta-line {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+}
+
+.meta-label {
+  font-size: 12px;
+  color: #9ca3af;
+}
+
+.meta-value {
+  font-size: 13px;
+  color: #4b5563;
+}
+
+.phone-btn {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  padding: 9px 0;
+  border-radius: 12px;
+  background: linear-gradient(135deg, #368f6f 0%, #4ba98a 100%);
+}
+
+.phone-btn-hover {
+  opacity: 0.88;
+}
+
+.phone-glyph {
+  font-size: 14px;
+  color: #ffffff;
+}
+
+.phone-text {
+  font-size: 14px;
+  font-weight: 600;
+  color: #ffffff;
+}
+
+.phone-btn-disabled {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  padding: 9px 0;
+  border-radius: 12px;
+  background-color: #f3f4f6;
+}
+
+.phone-text-disabled {
+  font-size: 14px;
+  color: #9ca3af;
+}
+
+.empty-text {
+  font-size: 13px;
+  color: #9ca3af;
 }
 </style>

+ 134 - 39
src/subPackages/pages-dispatch/dutyPersonnel.vue

@@ -1,43 +1,45 @@
 <template>
-  <view class="app-container pb-20">
+  <view class="sub-page">
     <TopBar title="当班人员" show-back />
-    
-    <!-- 筛选 -->
-    <view class="bg-white px-4 py-3">
-      <uni-segmented-control 
-        :current="currentIndex" 
-        :values="teams.map(t => t.label)"
-        style-type="text"
-        active-color="#4f8ef7"
-        @clickItem="onTabChange"
-      />
-    </view>
 
-    <!-- 当班人员列表 -->
-    <view class="px-4 mt-3">
+    <view class="px-4 pt-3">
+      <!-- 筛选 -->
+      <view class="filter-bar mb-3">
+        <view
+          v-for="team in teams"
+          :key="team.value"
+          class="filter-chip"
+          :class="{ 'filter-chip-active': currentFilter === team.value }"
+          @click="currentFilter = team.value"
+        >
+          {{ team.label }}
+        </view>
+      </view>
+
       <view v-if="loading" class="py-10 text-center">
-        <text class="text-gray-400">加载中...</text>
+        <text class="empty-text">加载中...</text>
       </view>
-      
+
       <view v-else-if="filteredStaff.length === 0">
         <EmptyState message="暂无当班人员" />
       </view>
-      
-      <uni-list v-else border>
-        <uni-list-item 
-          v-for="staff in filteredStaff" 
+
+      <view v-else class="glass-card">
+        <view
+          v-for="staff in filteredStaff"
           :key="staff.staffId"
-          :title="staff.name"
-          :note="(staff.phone || '') + ' | ' + (staff.teamName || '未分配班组')"
-          :right-text="getStatusText(staff.status)"
+          class="list-row"
         >
-          <template #header>
-            <view class="w-10 h-10 rounded-full bg-blue-50 flex items-center justify-center mr-3">
-              <text class="uni-icons uniui-person-filled text-blue-500"></text>
-            </view>
-          </template>
-        </uni-list-item>
-      </uni-list>
+          <view class="icon-chip mr-3">
+            <text class="uni-icons uniui-person-filled chip-glyph"></text>
+          </view>
+          <view class="flex-1 min-w-0 mr-2">
+            <text class="row-title truncate">{{ staff.name }}</text>
+            <text class="row-sub truncate">{{ staff.phone || '' }} | {{ staff.teamName || '未分配班组' }}</text>
+          </view>
+          <text class="status-tag flex-shrink-0">{{ getStatusText(staff.status) }}</text>
+        </view>
+      </view>
     </view>
   </view>
 </template>
@@ -46,14 +48,11 @@
 import { ref, computed, onMounted } from 'vue'
 import TopBar from '../../components/common/TopBar.vue'
 import EmptyState from '../../components/common/EmptyState.vue'
-import UniSegmentedControl from '../../components/uni-segmented-control/uni-segmented-control.vue'
-import UniList from '../../components/uni-list/uni-list.vue'
-import UniListItem from '../../components/uni-list/uni-list-item.vue'
 import { getStaffList } from '../../api/staff'
 
 const loading = ref(false)
 const staffList = ref<any[]>([])
-const currentIndex = ref(0)
+const currentFilter = ref('all')
 
 const teams = [
   { value: 'all', label: '全部' },
@@ -63,15 +62,11 @@ const teams = [
 ]
 
 const filteredStaff = computed(() => {
-  const filter = teams[currentIndex.value].value
+  const filter = currentFilter.value
   if (filter === 'all') return staffList.value.filter(s => s.status === 'on_duty' || s.status === 'working')
   return staffList.value.filter(s => (s.status === 'on_duty' || s.status === 'working') && s.teamName?.includes(filter))
 })
 
-function onTabChange(index: number) {
-  currentIndex.value = index
-}
-
 function getStatusText(status: string) {
   const map: Record<string, string> = {
     on_duty: '在岗',
@@ -92,3 +87,103 @@ onMounted(async () => {
   }
 })
 </script>
+
+<style scoped>
+.sub-page {
+  max-width: 430px;
+  margin: 0 auto;
+  min-height: 100vh;
+  background-color: #f6fbf9;
+  padding-bottom: 24px;
+}
+
+.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;
+}
+
+/* 筛选条:默认白底灰边,激活态绿色 */
+.filter-bar {
+  display: flex;
+  gap: 8px;
+  overflow-x: auto;
+}
+
+.filter-chip {
+  padding: 6px 16px;
+  border-radius: 999px;
+  font-size: 13px;
+  color: #6b7280;
+  background-color: #ffffff;
+  border: 1px solid #e5e7eb;
+  flex-shrink: 0;
+}
+
+.filter-chip-active {
+  color: #ffffff;
+  font-weight: 600;
+  background: linear-gradient(135deg, #368f6f 0%, #4ba98a 100%);
+  border-color: transparent;
+}
+
+.list-row {
+  display: flex;
+  align-items: center;
+  padding: 13px 16px;
+  border-top: 1px solid rgba(164, 216, 152, 0.18);
+}
+
+.list-row:first-child {
+  border-top: none;
+}
+
+.row-title {
+  display: block;
+  font-size: 14px;
+  font-weight: 500;
+  color: #1f2937;
+}
+
+.row-sub {
+  display: block;
+  margin-top: 3px;
+  font-size: 12px;
+  color: #9ca3af;
+}
+
+.status-tag {
+  padding: 2px 10px;
+  border-radius: 999px;
+  font-size: 11px;
+  font-weight: 600;
+  color: #368f6f;
+  background-color: rgba(164, 216, 152, 0.24);
+}
+
+.icon-chip {
+  width: 42px;
+  height: 42px;
+  border-radius: 50%;
+  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>

+ 212 - 45
src/subPackages/pages-dispatch/emergencyStandby.vue

@@ -1,73 +1,71 @@
 <template>
-  <view class="app-container pb-20">
+  <view class="sub-page">
     <TopBar title="应急待命" show-back />
 
-    <!-- 应急热线 -->
-    <view class="px-4 mt-4">
-      <view class="p-4 bg-danger-light rounded-xl flex items-center justify-between">
-        <view class="flex items-center">
-          <view class="w-12 h-12 bg-danger rounded-full flex items-center justify-center mr-3">
-            <text class="uni-icons uniui-phone-filled text-white text-xl"></text>
+    <view class="px-4 pt-3">
+      <!-- 应急热线 -->
+      <view class="hotline-card">
+        <view class="flex items-center flex-1 min-w-0">
+          <view class="hotline-icon mr-3">
+            <text class="uni-icons uniui-phone-filled hotline-glyph"></text>
           </view>
-          <view>
-            <text class="text-sm text-danger font-medium">应急热线</text>
-            <text class="text-lg font-bold text-gray-800">400-119-1100</text>
+          <view class="min-w-0">
+            <text class="hotline-label">应急热线</text>
+            <text class="hotline-number">400-119-1100</text>
           </view>
         </view>
-        <button class="px-4 py-2 bg-danger text-white rounded-lg text-sm font-medium" @click="callPhone('400-119-1100')">
+        <view class="hotline-btn" hover-class="hotline-btn-hover" :hover-start-time="0" :hover-stay-time="120" @click="callPhone('400-119-1100')">
           拨打
-        </button>
+        </view>
       </view>
-    </view>
 
-    <!-- 待命说明 -->
-    <view class="px-4 mt-4">
-      <view class="p-3 bg-red-50 rounded-lg flex items-center">
-        <text class="uni-icons uniui-notification-filled text-danger mr-2"></text>
-        <text class="text-sm text-red-600">应急待命人员保持通讯畅通,随时准备响应紧急任务</text>
+      <!-- 待命说明 -->
+      <view class="notice-banner">
+        <text class="uni-icons uniui-notification-filled notice-icon mr-2"></text>
+        <text class="notice-text">应急待命人员保持通讯畅通,随时准备响应紧急任务</text>
+      </view>
+
+      <view v-if="emergencyTeam.length === 0">
+        <EmptyState message="暂无应急待命人员" />
       </view>
-    </view>
 
-    <view class="px-4 py-4">
-      <view v-if="emergencyTeam.length > 0" class="gap-y-3">
+      <view v-else class="glass-card">
         <view
           v-for="person in emergencyTeam"
           :key="person.id"
-          class="card p-4"
+          class="person-row"
         >
           <view class="flex items-center justify-between">
-            <view class="flex items-center">
-              <view class="w-12 h-12 bg-red-100 rounded-full flex items-center justify-center mr-3">
-                <text class="uni-icons uniui-person-filled text-danger text-xl"></text>
+            <view class="flex items-center flex-1 min-w-0 mr-2">
+              <view class="icon-chip icon-chip-red mr-3">
+                <text class="uni-icons uniui-person-filled chip-glyph-red"></text>
               </view>
-              <view>
-                <text class="font-medium text-gray-800 block">{{ person.name }}</text>
-                <text class="text-sm text-gray-500">{{ person.team }} · {{ person.position || '应急队员' }}</text>
+              <view class="min-w-0">
+                <text class="row-title truncate">{{ person.name }}</text>
+                <text class="row-sub truncate">{{ person.team }} · {{ person.position || '应急队员' }}</text>
               </view>
             </view>
-            <text class="tag tag-red">{{ getPersonnelStatusText(person.status) }}</text>
+            <text class="status-tag flex-shrink-0">{{ getPersonnelStatusText(person.status) }}</text>
           </view>
 
-          <view class="mt-3 pt-3 border-t border-gray-100">
+          <view class="row-divider">
             <view v-if="person.qualifications && person.qualifications.length > 0" class="flex flex-wrap gap-2 mb-3">
-              <view
+              <text
                 v-for="qual in person.qualifications"
                 :key="qual"
-                class="px-2 py-1 bg-danger-light text-danger rounded text-xs"
+                class="qual-tag"
               >
                 {{ qual }}
-              </view>
+              </text>
             </view>
 
-            <view class="flex gap-3">
-              <button class="flex-1 py-2 bg-danger text-white rounded-lg text-sm font-medium" @click="callPhone(person.phone)">
-                电话联系
-              </button>
+            <view class="phone-btn" hover-class="phone-btn-hover" :hover-start-time="0" :hover-stay-time="120" @click="callPhone(person.phone)">
+              <text class="uni-icons uniui-phone-filled phone-glyph mr-1"></text>
+              <text class="phone-text">电话联系</text>
             </view>
           </view>
         </view>
       </view>
-      <EmptyState v-else message="暂无应急待命人员" />
     </view>
   </view>
 </template>
@@ -89,16 +87,185 @@ function callPhone(phone: string) {
 </script>
 
 <style scoped>
-.tag {
-  display: inline-block;
-  padding: 2px 8px;
-  border-radius: 4px;
-  font-size: 12px;
+.sub-page {
+  max-width: 430px;
+  margin: 0 auto;
+  min-height: 100vh;
+  background-color: #f6fbf9;
+  padding-bottom: 24px;
+}
+
+.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;
+}
+
+.hotline-card {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  padding: 16px;
+  margin-bottom: 12px;
+  border-radius: 24px;
+  background-color: rgba(239, 68, 68, 0.06);
+  border: 1px solid rgba(239, 68, 68, 0.2);
+}
+
+.hotline-icon {
+  width: 48px;
+  height: 48px;
+  border-radius: 50%;
+  flex-shrink: 0;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  background: linear-gradient(135deg, #ef4444 0%, #f87171 100%);
+}
+
+.hotline-glyph {
+  font-size: 22px;
+  color: #ffffff;
+}
+
+.hotline-label {
+  display: block;
+  font-size: 13px;
+  color: #ef4444;
+}
+
+.hotline-number {
+  display: block;
+  font-size: 20px;
+  font-weight: 800;
+  color: #1f2937;
+}
+
+.hotline-btn {
+  padding: 8px 20px;
+  border-radius: 999px;
+  font-size: 14px;
+  font-weight: 600;
+  color: #ffffff;
+  background: linear-gradient(135deg, #ef4444 0%, #f87171 100%);
+}
+
+.hotline-btn-hover {
+  opacity: 0.88;
+}
+
+.notice-banner {
+  display: flex;
+  align-items: center;
+  padding: 10px 12px;
+  margin-bottom: 12px;
+  border-radius: 12px;
+  background-color: rgba(239, 68, 68, 0.08);
+}
+
+.notice-icon {
+  font-size: 16px;
+  color: #ef4444;
+}
+
+.notice-text {
+  font-size: 13px;
+  color: #dc2626;
+}
+
+.person-row {
+  padding: 14px 16px;
+  border-top: 1px solid rgba(164, 216, 152, 0.18);
+}
+
+.person-row:first-child {
+  border-top: none;
+}
+
+.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);
+}
+
+.icon-chip-red {
+  border-radius: 50%;
+  background-color: rgba(239, 68, 68, 0.1);
+}
+
+.chip-glyph-red {
+  font-size: 20px;
+  color: #ef4444;
+}
+
+.row-title {
+  display: block;
+  font-size: 14px;
   font-weight: 500;
+  color: #1f2937;
+}
+
+.row-sub {
+  display: block;
+  margin-top: 3px;
+  font-size: 12px;
+  color: #9ca3af;
+}
+
+.status-tag {
+  padding: 2px 10px;
+  border-radius: 999px;
+  font-size: 11px;
+  font-weight: 600;
+  color: #ef4444;
+  background-color: rgba(239, 68, 68, 0.1);
+}
+
+.row-divider {
+  margin-top: 12px;
+  padding-top: 12px;
+  border-top: 1px solid rgba(164, 216, 152, 0.18);
 }
 
-.tag-red {
-  background: #fef2f2;
+.qual-tag {
+  padding: 3px 10px;
+  border-radius: 999px;
+  font-size: 11px;
   color: #ef4444;
+  background-color: rgba(239, 68, 68, 0.08);
+}
+
+.phone-btn {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  padding: 9px 0;
+  border-radius: 12px;
+  background: linear-gradient(135deg, #ef4444 0%, #f87171 100%);
+}
+
+.phone-btn-hover {
+  opacity: 0.88;
+}
+
+.phone-glyph {
+  font-size: 14px;
+  color: #ffffff;
+}
+
+.phone-text {
+  font-size: 14px;
+  font-weight: 600;
+  color: #ffffff;
 }
 </style>

+ 210 - 36
src/subPackages/pages-dispatch/emergencyTeam.vue

@@ -1,52 +1,68 @@
 <template>
-  <view class="app-container pb-20">
+  <view class="sub-page">
     <TopBar title="应急班组" show-back />
-    
-    <view class="px-4 py-4 gap-y-4">
+
+    <view class="px-4 pt-3">
       <!-- 应急状态概览 -->
-      <uni-card title="应急状态" icon="uniui-notification-filled" extra="实时">
+      <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 class="realtime-tag">实时</text>
+        </view>
         <view class="grid grid-cols-3 gap-3">
-          <view class="bg-red-50 rounded-xl p-3 text-center">
-            <text class="text-xl font-bold text-red-600 block">{{ standbyTeams }}</text>
-            <text class="text-xs text-gray-500">待命班组</text>
+          <view class="stat-card">
+            <view class="stat-bar"></view>
+            <text class="stat-value stat-value-red">{{ standbyTeams }}</text>
+            <text class="stat-label">待命班组</text>
           </view>
-          <view class="bg-orange-50 rounded-xl p-3 text-center">
-            <text class="text-xl font-bold text-orange-600 block">{{ standbyVehicles }}</text>
-            <text class="text-xs text-gray-500">待命车辆</text>
+          <view class="stat-card">
+            <view class="stat-bar"></view>
+            <text class="stat-value stat-value-gold">{{ standbyVehicles }}</text>
+            <text class="stat-label">待命车辆</text>
           </view>
-          <view class="bg-purple-50 rounded-xl p-3 text-center">
-            <text class="text-xl font-bold text-purple-600 block">{{ emergencyTasks }}</text>
-            <text class="text-xs text-gray-500">应急任务</text>
+          <view class="stat-card">
+            <view class="stat-bar"></view>
+            <text class="stat-value">{{ emergencyTasks }}</text>
+            <text class="stat-label">应急任务</text>
           </view>
         </view>
-      </uni-card>
+      </view>
 
       <!-- 应急任务列表 -->
-      <uni-card title="应急任务" icon="uniui-flag-filled">
-        <view v-if="loading" class="py-6 text-center">
-          <text class="text-gray-400">加载中...</text>
+      <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-else-if="emergencyTaskList.length === 0">
+        <view v-if="loading" class="py-10 text-center">
+          <text class="empty-text">加载中...</text>
+        </view>
+        <view v-else-if="emergencyTaskList.length === 0" class="pb-3">
           <EmptyState message="暂无应急任务" />
         </view>
-        <uni-list v-else border>
-          <uni-list-item 
-            v-for="task in emergencyTaskList" 
+        <view v-else>
+          <view
+            v-for="task in emergencyTaskList"
             :key="task.id"
-            :title="task.projectName || '应急任务'"
-            :note="task.address"
-            right-text="应急"
-            show-arrow
+            class="list-row"
+            hover-class="row-hover"
+            :hover-start-time="0"
+            :hover-stay-time="120"
             @click="goToTaskDetail(task.id)"
           >
-            <template #header>
-              <view class="w-10 h-10 rounded-xl bg-red-50 flex items-center justify-center mr-3">
-                <text class="uni-icons uniui-notification-filled text-red-500"></text>
-              </view>
-            </template>
-          </uni-list-item>
-        </uni-list>
-      </uni-card>
+            <view class="icon-chip icon-chip-red mr-3">
+              <text class="uni-icons uniui-notification-filled chip-glyph-red"></text>
+            </view>
+            <view class="flex-1 min-w-0 mr-2">
+              <text class="row-title truncate">{{ task.projectName || '应急任务' }}</text>
+              <text class="row-sub truncate">{{ task.address || '暂无地址' }}</text>
+            </view>
+            <text class="emergency-tag flex-shrink-0 mr-1">应急</text>
+            <text class="uni-icons uniui-arrowright row-arrow"></text>
+          </view>
+        </view>
+      </view>
     </view>
   </view>
 </template>
@@ -55,9 +71,6 @@
 import { ref, computed, onMounted } from 'vue'
 import TopBar from '../../components/common/TopBar.vue'
 import EmptyState from '../../components/common/EmptyState.vue'
-import UniCard from '../../components/uni-card/uni-card.vue'
-import UniList from '../../components/uni-list/uni-list.vue'
-import UniListItem from '../../components/uni-list/uni-list-item.vue'
 import { getTaskList } from '../../api/task'
 import { getStaffList } from '../../api/staff'
 import { getVehicleList } from '../../api/vehicle'
@@ -101,3 +114,164 @@ onMounted(async () => {
   }
 })
 </script>
+
+<style scoped>
+.sub-page {
+  max-width: 430px;
+  margin: 0 auto;
+  min-height: 100vh;
+  background-color: #f6fbf9;
+  padding-bottom: 24px;
+}
+
+.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;
+}
+
+.realtime-tag {
+  font-size: 12px;
+  color: #9ca3af;
+}
+
+.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-value-red {
+  color: #ef4444;
+}
+
+.stat-value-gold {
+  color: #b7791f;
+}
+
+.stat-label {
+  margin-top: 6px;
+  font-size: 12px;
+  color: #6b7280;
+}
+
+.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);
+}
+
+.row-arrow {
+  font-size: 16px;
+  color: #c4d0cb;
+  flex-shrink: 0;
+}
+
+.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);
+}
+
+.icon-chip-red {
+  background-color: rgba(239, 68, 68, 0.1);
+}
+
+.chip-glyph-red {
+  font-size: 20px;
+  color: #ef4444;
+}
+
+.emergency-tag {
+  padding: 2px 10px;
+  border-radius: 999px;
+  font-size: 11px;
+  font-weight: 600;
+  color: #ef4444;
+  background-color: rgba(239, 68, 68, 0.1);
+}
+
+.empty-text {
+  font-size: 13px;
+  color: #9ca3af;
+}
+</style>

+ 63 - 10
src/subPackages/pages-dispatch/noticeDetail.vue

@@ -1,23 +1,25 @@
 <template>
-  <view class="app-container pb-20">
+  <view class="sub-page">
     <TopBar title="通知详情" show-back />
 
-    <view v-if="notice" class="px-4 py-4">
-      <view class="card p-4">
-        <text class="text-lg font-semibold text-gray-800 mb-2 block">{{ notice.title }}</text>
-        <text class="text-xs text-gray-400 mb-4 block">{{ formatDate(notice.createTime, 'YYYY-MM-DD HH:mm') }}</text>
-        <view class="border-t border-gray-100 pt-4">
-          <text class="text-sm text-gray-600 leading-relaxed block">{{ notice.content }}</text>
+    <view class="px-4 pt-3">
+      <view v-if="notice" class="glass-card p-5">
+        <text class="notice-title">{{ notice.title }}</text>
+        <view class="meta-row">
+          <text class="meta-text">{{ formatDate(notice.createTime, 'YYYY-MM-DD HH:mm') }}</text>
+        </view>
+        <view class="notice-body">
+          <text class="body-p">{{ notice.content }}</text>
         </view>
       </view>
-    </view>
 
-    <EmptyState v-else message="公告不存在" />
+      <EmptyState v-else message="公告不存在" />
+    </view>
   </view>
 </template>
 
 <script setup lang="ts">
-import { computed, ref } from 'vue'
+import { ref } from 'vue'
 import { onLoad } from '@dcloudio/uni-app'
 import TopBar from '../../components/common/TopBar.vue'
 import EmptyState from '../../components/common/EmptyState.vue'
@@ -45,3 +47,54 @@ onLoad((options) => {
   }
 })
 </script>
+
+<style scoped>
+.sub-page {
+  max-width: 430px;
+  margin: 0 auto;
+  min-height: 100vh;
+  background-color: #f6fbf9;
+  padding-bottom: 24px;
+}
+
+.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;
+}
+
+.notice-title {
+  display: block;
+  font-size: 17px;
+  font-weight: 700;
+  color: #1f2937;
+  line-height: 1.4;
+}
+
+.meta-row {
+  margin-top: 10px;
+  padding-bottom: 12px;
+  border-bottom: 1px solid rgba(164, 216, 152, 0.18);
+}
+
+.meta-text {
+  font-size: 12px;
+  color: #9ca3af;
+}
+
+.notice-body {
+  padding-top: 12px;
+}
+
+.body-p {
+  display: block;
+  font-size: 14px;
+  line-height: 1.8;
+  color: #4b5563;
+}
+</style>

+ 159 - 36
src/subPackages/pages-dispatch/noticeList.vue

@@ -1,53 +1,55 @@
-
 <template>
-  <view class="app-container pb-20">
+  <view class="sub-page">
     <TopBar title="通知公告" show-back />
-    
+
     <!-- 筛选 -->
-    <view class="bg-white px-4 py-3 flex gap-2 overflow-x-auto hide-scrollbar">
-      <view 
-        v-for="type in filterTypes" 
-        :key="type.value"
-        class="filter-tab"
-        :class="{ active: currentFilter === type.value }"
-        @click="currentFilter = type.value"
-      >
-        {{ type.label }}
+    <view class="px-4 pt-3">
+      <view class="filter-bar">
+        <view
+          v-for="type in filterTypes"
+          :key="type.value"
+          class="filter-chip"
+          :class="{ 'filter-chip-active': currentFilter === type.value }"
+          @click="currentFilter = type.value"
+        >
+          {{ type.label }}
+        </view>
       </view>
     </view>
 
     <!-- 通知列表 -->
-    <view class="px-4 mt-3">
+    <view class="px-4 pt-3">
       <view v-if="loading" class="py-10 text-center">
-        <text class="text-gray-400">加载中...</text>
+        <text class="empty-text">加载中...</text>
       </view>
-      
+
       <view v-else-if="filteredNotices.length === 0">
         <EmptyState message="暂无通知" />
       </view>
-      
-      <view v-else class="gap-y-3">
-        <view 
-          v-for="notice in filteredNotices" 
+
+      <view v-else class="glass-card">
+        <view
+          v-for="notice in filteredNotices"
           :key="notice.noticeId"
-          class="card p-4 cursor-pointer"
+          class="notice-row"
+          hover-class="row-hover"
+          :hover-start-time="0"
+          :hover-stay-time="120"
           @click="goToDetail(notice.noticeId)"
         >
-          <view class="flex justify-between items-start mb-2">
-            <view class="flex items-center">
-              <view v-if="notice.priority === 3" class="w-2 h-2 bg-red-500 rounded-full mr-2"></view>
-              <text class="text-sm font-semibold text-gray-800">{{ notice.title }}</text>
+          <view class="flex items-center justify-between mb-2">
+            <view class="flex items-center flex-1 min-w-0 mr-2">
+              <view v-if="notice.priority === 3" class="priority-dot mr-2"></view>
+              <text class="row-title truncate">{{ notice.title }}</text>
             </view>
-            <view class="tag" :class="getTypeClass(notice.type)">
+            <text class="type-tag flex-shrink-0" :class="getTypeClass(notice.type)">
               {{ getTypeText(notice.type) }}
-            </view>
+            </text>
           </view>
-          
-          <text class="text-xs text-gray-500 line-clamp-2 mb-2 block">{{ notice.content }}</text>
-          
-          <view class="flex justify-between items-center">
-            <text class="text-xs text-gray-400">{{ notice.publisherName }} · {{ formatDate(notice.publishTime) }}</text>
-            <text class="text-xs text-gray-400"> {{ notice.readCount }}</text>
+          <text class="row-sub notice-content">{{ notice.content }}</text>
+          <view class="flex items-center justify-between mt-2">
+            <text class="row-date">{{ notice.publisherName }} · {{ formatDate(notice.publishTime) }}</text>
+            <text class="row-date">阅读 {{ notice.readCount }}</text>
           </view>
         </view>
       </view>
@@ -89,11 +91,11 @@ function getTypeText(type: string) {
 
 function getTypeClass(type: string) {
   const map: Record<string, string> = {
-    system: 'tag-blue',
-    emergency: 'tag-red',
-    task: 'tag-green',
+    system: 'type-tag-green',
+    emergency: 'type-tag-red',
+    task: 'type-tag-gold',
   }
-  return map[type] || 'tag-gray'
+  return map[type] || 'type-tag-gray'
 }
 
 function goToDetail(noticeId: number) {
@@ -112,3 +114,124 @@ onMounted(async () => {
   }
 })
 </script>
+
+<style scoped>
+.sub-page {
+  max-width: 430px;
+  margin: 0 auto;
+  min-height: 100vh;
+  background-color: #f6fbf9;
+  padding-bottom: 24px;
+}
+
+/* 筛选条:默认白底灰边,激活态绿色 */
+.filter-bar {
+  display: flex;
+  gap: 8px;
+  overflow-x: auto;
+}
+
+.filter-chip {
+  padding: 6px 16px;
+  border-radius: 999px;
+  font-size: 13px;
+  color: #6b7280;
+  background-color: #ffffff;
+  border: 1px solid #e5e7eb;
+  flex-shrink: 0;
+}
+
+.filter-chip-active {
+  color: #ffffff;
+  font-weight: 600;
+  background: linear-gradient(135deg, #368f6f 0%, #4ba98a 100%);
+  border-color: transparent;
+}
+
+.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;
+}
+
+.notice-row {
+  padding: 14px 16px;
+  border-top: 1px solid rgba(164, 216, 152, 0.18);
+}
+
+.notice-row:first-child {
+  border-top: none;
+}
+
+.row-hover {
+  background-color: rgba(164, 216, 152, 0.1);
+}
+
+.row-title {
+  font-size: 14px;
+  font-weight: 600;
+  color: #1f2937;
+}
+
+.row-sub {
+  font-size: 12px;
+  color: #9ca3af;
+}
+
+.notice-content {
+  display: -webkit-box;
+  -webkit-line-clamp: 2;
+  -webkit-box-orient: vertical;
+  overflow: hidden;
+}
+
+.row-date {
+  font-size: 12px;
+  color: #9ca3af;
+}
+
+.priority-dot {
+  width: 8px;
+  height: 8px;
+  border-radius: 50%;
+  background-color: #ef4444;
+  flex-shrink: 0;
+}
+
+.type-tag {
+  padding: 2px 10px;
+  border-radius: 999px;
+  font-size: 11px;
+  font-weight: 600;
+}
+
+.type-tag-green {
+  color: #368f6f;
+  background-color: rgba(164, 216, 152, 0.24);
+}
+
+.type-tag-red {
+  color: #ef4444;
+  background-color: rgba(239, 68, 68, 0.1);
+}
+
+.type-tag-gold {
+  color: #b7791f;
+  background-color: rgba(251, 211, 141, 0.25);
+}
+
+.type-tag-gray {
+  color: #6b7280;
+  background-color: #f3f4f6;
+}
+
+.empty-text {
+  font-size: 13px;
+  color: #9ca3af;
+}
+</style>

+ 119 - 25
src/subPackages/pages-dispatch/notification.vue

@@ -1,36 +1,38 @@
 <template>
-  <view class="app-container pb-20">
+  <view class="sub-page">
     <TopBar title="通知消息" show-back />
-    
-    <view class="px-4 mt-3">
+
+    <view class="px-4 pt-3">
       <view v-if="loading" class="py-10 text-center">
-        <text class="text-gray-400">加载中...</text>
+        <text class="empty-text">加载中...</text>
       </view>
-      
+
       <view v-else-if="noticeList.length === 0">
         <EmptyState message="暂无通知" />
       </view>
-      
-      <uni-list v-else border>
-        <uni-list-item 
-          v-for="notice in noticeList" 
+
+      <view v-else class="glass-card">
+        <view
+          v-for="notice in noticeList"
           :key="notice.noticeId"
-          :title="notice.title"
-          :note="notice.content"
-          :right-text="formatDate(notice.publishTime)"
-          show-arrow
+          class="list-row"
+          hover-class="row-hover"
+          :hover-start-time="0"
+          :hover-stay-time="120"
           @click="goToDetail(notice.noticeId)"
         >
-          <template #header>
-            <view class="relative mr-3">
-              <view class="w-10 h-10 rounded-xl bg-blue-50 flex items-center justify-center">
-                <text class="uni-icons uniui-notification-filled text-blue-500"></text>
-              </view>
-              <view v-if="!notice.read" class="absolute -top-1 -right-1 w-3 h-3 bg-red-500 rounded-full border-2 border-white"></view>
-            </view>
-          </template>
-        </uni-list-item>
-      </uni-list>
+          <view class="icon-chip mr-3">
+            <text class="uni-icons uniui-notification-filled chip-glyph"></text>
+            <view v-if="!notice.read" class="unread-dot"></view>
+          </view>
+          <view class="flex-1 min-w-0 mr-2">
+            <text class="row-title truncate">{{ notice.title }}</text>
+            <text class="row-sub truncate">{{ notice.content }}</text>
+          </view>
+          <text class="row-date flex-shrink-0 mr-1">{{ formatDate(notice.publishTime) }}</text>
+          <text class="uni-icons uniui-arrowright row-arrow"></text>
+        </view>
+      </view>
     </view>
   </view>
 </template>
@@ -39,8 +41,6 @@
 import { ref, onMounted } from 'vue'
 import TopBar from '../../components/common/TopBar.vue'
 import EmptyState from '../../components/common/EmptyState.vue'
-import UniList from '../../components/uni-list/uni-list.vue'
-import UniListItem from '../../components/uni-list/uni-list-item.vue'
 import { getNoticeList } from '../../api/notice'
 import { formatDate } from '../../utils'
 
@@ -63,3 +63,97 @@ onMounted(async () => {
   }
 })
 </script>
+
+<style scoped>
+.sub-page {
+  max-width: 430px;
+  margin: 0 auto;
+  min-height: 100vh;
+  background-color: #f6fbf9;
+  padding-bottom: 24px;
+}
+
+.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;
+}
+
+.list-row {
+  display: flex;
+  align-items: center;
+  padding: 13px 16px;
+  border-top: 1px solid rgba(164, 216, 152, 0.18);
+}
+
+.list-row:first-child {
+  border-top: none;
+}
+
+.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-date {
+  font-size: 12px;
+  color: #9ca3af;
+}
+
+.row-hover {
+  background-color: rgba(164, 216, 152, 0.1);
+}
+
+.row-arrow {
+  font-size: 16px;
+  color: #c4d0cb;
+  flex-shrink: 0;
+}
+
+.icon-chip {
+  position: relative;
+  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;
+}
+
+.unread-dot {
+  position: absolute;
+  top: -3px;
+  right: -3px;
+  width: 10px;
+  height: 10px;
+  border-radius: 50%;
+  background-color: #ef4444;
+  border: 2px solid #ffffff;
+}
+
+.empty-text {
+  font-size: 13px;
+  color: #9ca3af;
+}
+</style>

+ 145 - 43
src/subPackages/pages-dispatch/onDutyStaff.vue

@@ -1,58 +1,57 @@
 <template>
-  <view class="app-container pb-20">
+  <view class="sub-page">
     <TopBar title="在岗人员" show-back />
 
-    <view class="px-4 py-4">
-      <view v-if="onDutyStaff.length > 0" class="gap-y-3">
+    <view class="px-4 pt-3">
+      <view v-if="onDutyStaff.length === 0">
+        <EmptyState message="暂无在岗人员" />
+      </view>
+
+      <view v-else class="glass-card">
         <view
           v-for="person in onDutyStaff"
           :key="person.id"
-          class="card p-4"
+          class="person-row"
         >
           <view class="flex items-center justify-between">
-            <view class="flex items-center">
-              <view
-                class="w-12 h-12 rounded-full flex items-center justify-center mr-3"
-                :class="getPersonIconClass(person.id)"
-              >
-                <text class="uni-icons uniui-person-filled text-xl"></text>
+            <view class="flex items-center flex-1 min-w-0 mr-2">
+              <view class="icon-chip mr-3">
+                <text class="uni-icons uniui-person-filled chip-glyph"></text>
               </view>
-              <view>
-                <text class="font-medium text-gray-800 block">{{ person.name }}</text>
-                <text class="text-sm text-gray-500">{{ person.team }} · {{ person.position || '施工员' }}</text>
+              <view class="min-w-0">
+                <text class="row-title truncate">{{ person.name }}</text>
+                <text class="row-sub truncate">{{ person.team }} · {{ person.position || '施工员' }}</text>
               </view>
             </view>
-            <text class="tag tag-green">{{ getPersonnelStatusText(person.status) }}</text>
+            <text class="status-tag flex-shrink-0">{{ getPersonnelStatusText(person.status) }}</text>
           </view>
 
-          <view class="mt-3 pt-3 border-t border-gray-100">
-            <view class="flex items-center justify-between mb-2">
-              <text class="text-xs text-gray-400">技能等级</text>
-              <text class="text-sm text-gray-700">{{ person.skillLevel || '-' }}</text>
+          <view class="row-divider">
+            <view class="meta-line">
+              <text class="meta-label">技能等级</text>
+              <text class="meta-value">{{ person.skillLevel || '-' }}</text>
             </view>
-            <view class="flex items-center justify-between mb-2">
-              <text class="text-xs text-gray-400">从业年限</text>
-              <text class="text-sm text-gray-700">{{ person.workYears ? `${person.workYears}年` : '-' }}</text>
+            <view class="meta-line">
+              <text class="meta-label">从业年限</text>
+              <text class="meta-value">{{ person.workYears ? `${person.workYears}年` : '-' }}</text>
             </view>
             <view v-if="person.qualifications && person.qualifications.length > 0" class="flex flex-wrap gap-2 mt-2">
-              <view
+              <text
                 v-for="qual in person.qualifications"
                 :key="qual"
-                class="px-2 py-1 bg-primary-light text-primary rounded text-xs"
+                class="qual-tag"
               >
                 {{ qual }}
-              </view>
+              </text>
             </view>
           </view>
 
-          <view class="mt-3 flex gap-3">
-            <button class="flex-1 py-2 bg-primary-light text-primary rounded-lg text-sm font-medium" @click="callPhone(person.phone)">
-              电话联系
-            </button>
+          <view class="phone-btn mt-3" hover-class="phone-btn-hover" :hover-start-time="0" :hover-stay-time="120" @click="callPhone(person.phone)">
+            <text class="uni-icons uniui-phone-filled phone-glyph mr-1"></text>
+            <text class="phone-text">电话联系</text>
           </view>
         </view>
       </view>
-      <EmptyState v-else message="暂无在岗人员" />
     </view>
   </view>
 </template>
@@ -67,12 +66,6 @@ import { getPersonnelStatusText } from '../../utils'
 const personnelStore = usePersonnelStore()
 const onDutyStaff = computed(() => personnelStore.onDutyStaff)
 
-function getPersonIconClass(id: string) {
-  const colors = ['bg-blue-100', 'bg-green-100', 'bg-purple-100', 'bg-orange-100', 'bg-red-100']
-  const index = parseInt(id.replace(/\D/g, '')) % colors.length
-  return colors[index]
-}
-
 function callPhone(phone: string) {
   if (!phone) return
   uni.makePhoneCall({ phoneNumber: phone })
@@ -80,16 +73,125 @@ function callPhone(phone: string) {
 </script>
 
 <style scoped>
-.tag {
-  display: inline-block;
-  padding: 2px 8px;
-  border-radius: 4px;
-  font-size: 12px;
+.sub-page {
+  max-width: 430px;
+  margin: 0 auto;
+  min-height: 100vh;
+  background-color: #f6fbf9;
+  padding-bottom: 24px;
+}
+
+.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;
+}
+
+.person-row {
+  padding: 14px 16px;
+  border-top: 1px solid rgba(164, 216, 152, 0.18);
+}
+
+.person-row:first-child {
+  border-top: none;
+}
+
+.icon-chip {
+  width: 42px;
+  height: 42px;
+  border-radius: 50%;
+  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;
+}
+
+.row-title {
+  display: block;
+  font-size: 14px;
   font-weight: 500;
+  color: #1f2937;
+}
+
+.row-sub {
+  display: block;
+  margin-top: 3px;
+  font-size: 12px;
+  color: #9ca3af;
+}
+
+.status-tag {
+  padding: 2px 10px;
+  border-radius: 999px;
+  font-size: 11px;
+  font-weight: 600;
+  color: #368f6f;
+  background-color: rgba(164, 216, 152, 0.24);
+}
+
+.row-divider {
+  margin-top: 12px;
+  padding-top: 12px;
+  border-top: 1px solid rgba(164, 216, 152, 0.18);
+}
+
+.meta-line {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  margin-bottom: 6px;
+}
+
+.meta-label {
+  font-size: 12px;
+  color: #9ca3af;
+}
+
+.meta-value {
+  font-size: 13px;
+  color: #4b5563;
+}
+
+.qual-tag {
+  padding: 3px 10px;
+  border-radius: 999px;
+  font-size: 11px;
+  color: #368f6f;
+  background-color: rgba(164, 216, 152, 0.24);
+}
+
+.phone-btn {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  padding: 9px 0;
+  border-radius: 12px;
+  background: linear-gradient(135deg, #368f6f 0%, #4ba98a 100%);
+}
+
+.phone-btn-hover {
+  opacity: 0.88;
+}
+
+.phone-glyph {
+  font-size: 14px;
+  color: #ffffff;
 }
 
-.tag-green {
-  background: #e6f7f1;
-  color: #059669;
+.phone-text {
+  font-size: 14px;
+  font-weight: 600;
+  color: #ffffff;
 }
 </style>

+ 175 - 58
src/subPackages/pages-dispatch/personnel.vue

@@ -1,43 +1,52 @@
 <template>
-  <view class="app-container pb-20">
+  <view class="sub-page">
     <TopBar title="人员管理" show-back />
-    
-    <!-- 筛选标签 - 使用 uni-segmented-control -->
-    <uni-segmented-control 
-      :current="currentFilterIndex" 
-      :values="filterLabels"
-      style-type="text"
-      active-color="#4f8ef7"
-      @clickItem="onFilterChange"
-    />
-
-    <!-- 人员列表 -->
-    <view class="mt-2">
+
+    <view class="px-4 pt-3">
+      <!-- 筛选 -->
+      <view class="filter-bar mb-3">
+        <view
+          v-for="status in filterStatuses"
+          :key="status.value"
+          class="filter-chip"
+          :class="{ 'filter-chip-active': currentFilter === status.value }"
+          @click="currentFilter = status.value"
+        >
+          {{ status.label }}
+        </view>
+      </view>
+
       <view v-if="loading" class="py-10 text-center">
-        <text class="text-gray-400">加载中...</text>
+        <text class="empty-text">加载中...</text>
       </view>
-      
+
       <view v-else-if="filteredStaff.length === 0">
         <EmptyState message="暂无人员" />
       </view>
-      
-      <uni-list v-else border>
-        <uni-list-item 
-          v-for="staff in filteredStaff" 
+
+      <view v-else class="glass-card">
+        <view
+          v-for="staff in filteredStaff"
           :key="staff.staffId"
-          :title="staff.name"
-          :note="staff.phone + ' | ' + (staff.teamName || '未分配班组')"
-          :right-text="getStatusText(staff.status)"
-          show-arrow
+          class="list-row"
+          hover-class="row-hover"
+          :hover-start-time="0"
+          :hover-stay-time="120"
           @click="showStaffDetail(staff)"
         >
-          <template #header>
-            <view class="w-10 h-10 rounded-full flex items-center justify-center mr-3" :class="getStatusBgClass(staff.status)">
-              <text class="uni-icons uniui-person-filled text-lg" :class="getStatusIconClass(staff.status)"></text>
-            </view>
-          </template>
-        </uni-list-item>
-      </uni-list>
+          <view class="icon-chip mr-3">
+            <text class="uni-icons uniui-person-filled chip-glyph"></text>
+          </view>
+          <view class="flex-1 min-w-0 mr-2">
+            <text class="row-title truncate">{{ staff.name }}</text>
+            <text class="row-sub truncate">{{ staff.phone }} | {{ staff.teamName || '未分配班组' }}</text>
+          </view>
+          <text class="status-tag flex-shrink-0 mr-1" :class="getStatusTagClass(staff.status)">
+            {{ getStatusText(staff.status) }}
+          </text>
+          <text class="uni-icons uniui-arrowright row-arrow"></text>
+        </view>
+      </view>
     </view>
   </view>
 </template>
@@ -47,14 +56,11 @@ import { ref, computed, onMounted } from 'vue'
 import { onPullDownRefresh } from '@dcloudio/uni-app'
 import TopBar from '../../components/common/TopBar.vue'
 import EmptyState from '../../components/common/EmptyState.vue'
-import UniList from '../../components/uni-list/uni-list.vue'
-import UniListItem from '../../components/uni-list/uni-list-item.vue'
-import UniSegmentedControl from '../../components/uni-segmented-control/uni-segmented-control.vue'
 import { getStaffList } from '../../api/staff'
 
 const loading = ref(false)
 const staffList = ref<any[]>([])
-const currentFilterIndex = ref(0)
+const currentFilter = ref('all')
 
 const filterStatuses = [
   { value: 'all', label: '全部' },
@@ -63,12 +69,9 @@ const filterStatuses = [
   { value: 'rest', label: '休息' },
 ]
 
-const filterLabels = filterStatuses.map(s => s.label)
-
 const filteredStaff = computed(() => {
-  const filterValue = filterStatuses[currentFilterIndex.value].value
-  if (filterValue === 'all') return staffList.value
-  return staffList.value.filter(s => s.status === filterValue)
+  if (currentFilter.value === 'all') return staffList.value
+  return staffList.value.filter(s => s.status === currentFilter.value)
 })
 
 function getStatusText(status: string) {
@@ -80,26 +83,13 @@ function getStatusText(status: string) {
   return map[status] || status
 }
 
-function getStatusBgClass(status: string) {
-  const map: Record<string, string> = {
-    on_duty: 'bg-green-50',
-    working: 'bg-blue-50',
-    rest: 'bg-orange-50',
-  }
-  return map[status] || 'bg-gray-50'
-}
-
-function getStatusIconClass(status: string) {
+function getStatusTagClass(status: string) {
   const map: Record<string, string> = {
-    on_duty: 'text-green-500',
-    working: 'text-blue-500',
-    rest: 'text-orange-500',
+    on_duty: 'status-tag-green',
+    working: 'status-tag-blue',
+    rest: 'status-tag-gold',
   }
-  return map[status] || 'text-gray-400'
-}
-
-function onFilterChange(index: number) {
-  currentFilterIndex.value = index
+  return map[status] || 'status-tag-gray'
 }
 
 function showStaffDetail(staff: any) {
@@ -126,9 +116,136 @@ async function fetchStaff() {
   }
 }
 
-// 下拉刷新
 onPullDownRefresh(async () => {
   await fetchStaff()
   uni.stopPullDownRefresh()
 })
 </script>
+
+<style scoped>
+.sub-page {
+  max-width: 430px;
+  margin: 0 auto;
+  min-height: 100vh;
+  background-color: #f6fbf9;
+  padding-bottom: 24px;
+}
+
+.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;
+}
+
+/* 筛选条:默认白底灰边,激活态绿色 */
+.filter-bar {
+  display: flex;
+  gap: 8px;
+  overflow-x: auto;
+}
+
+.filter-chip {
+  padding: 6px 16px;
+  border-radius: 999px;
+  font-size: 13px;
+  color: #6b7280;
+  background-color: #ffffff;
+  border: 1px solid #e5e7eb;
+  flex-shrink: 0;
+}
+
+.filter-chip-active {
+  color: #ffffff;
+  font-weight: 600;
+  background: linear-gradient(135deg, #368f6f 0%, #4ba98a 100%);
+  border-color: transparent;
+}
+
+.list-row {
+  display: flex;
+  align-items: center;
+  padding: 13px 16px;
+  border-top: 1px solid rgba(164, 216, 152, 0.18);
+}
+
+.list-row:first-child {
+  border-top: none;
+}
+
+.row-hover {
+  background-color: rgba(164, 216, 152, 0.1);
+}
+
+.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-arrow {
+  font-size: 16px;
+  color: #c4d0cb;
+  flex-shrink: 0;
+}
+
+.status-tag {
+  padding: 2px 10px;
+  border-radius: 999px;
+  font-size: 11px;
+  font-weight: 600;
+}
+
+.status-tag-green {
+  color: #368f6f;
+  background-color: rgba(164, 216, 152, 0.24);
+}
+
+.status-tag-blue {
+  color: #2563eb;
+  background-color: rgba(37, 99, 235, 0.1);
+}
+
+.status-tag-gold {
+  color: #b7791f;
+  background-color: rgba(251, 211, 141, 0.25);
+}
+
+.status-tag-gray {
+  color: #6b7280;
+  background-color: #f3f4f6;
+}
+
+.icon-chip {
+  width: 42px;
+  height: 42px;
+  border-radius: 50%;
+  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>

+ 288 - 120
src/subPackages/pages-dispatch/publishTask.vue

@@ -1,147 +1,150 @@
 <template>
-  <view class="app-container pb-24">
+  <view class="sub-page">
     <TopBar title="发布任务" show-back />
 
-    <view class="px-4 py-4 gap-y-4">
-      <!-- 项目基本信息 -->
-      <uni-card title="项目信息" icon="uniui-flag-filled" :is-shadow="true">
-        <view class="gap-y-4 pt-2">
-          <view>
-            <text class="text-sm text-gray-600 block mb-2">
-              <text class="text-red-500">*</text> 选择项目
-            </text>
-            <picker mode="selector" :range="projectNames" :value="selectedProjectIndex" @change="onProjectChange">
-              <view class="w-full px-4 py-3 border border-gray-200 rounded-lg text-sm flex items-center justify-between bg-white">
-                <text :class="selectedProject ? 'text-gray-800' : 'text-gray-400'">{{ selectedProject || '请选择项目' }}</text>
-                <text class="uni-icons uniui-arrowright text-gray-400"></text>
-              </view>
-            </picker>
-          </view>
+    <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 v-if="selectedProjectObj" class="bg-green-50 rounded-xl p-3">
-            <view class="flex items-center justify-between mb-2">
-              <text class="text-green-600 text-sm font-medium">已选择:{{ selectedProjectObj.name }}</text>
-              <text class="text-green-500 text-xs" @click="selectedProject = ''; selectedProjectObj = null">重新选择</text>
-            </view>
-            <view class="gap-y-1">
-              <view class="flex justify-between text-sm">
-                <text class="text-gray-500">合作类型:</text>
-                <text class="text-gray-700">{{ selectedProjectObj.type }}</text>
-              </view>
-              <view class="flex justify-between text-sm">
-                <text class="text-gray-500">项目地址:</text>
-                <text class="text-gray-700">{{ selectedProjectObj.address }}</text>
-              </view>
+        <view class="form-group">
+          <text class="form-label"><text class="required">*</text> 选择项目</text>
+          <picker mode="selector" :range="projectNames" :value="selectedProjectIndex" @change="onProjectChange">
+            <view class="form-input flex items-center justify-between">
+              <text :class="selectedProject ? 'input-text' : 'placeholder-text'">{{ selectedProject || '请选择项目' }}</text>
+              <text class="uni-icons uniui-arrowright input-arrow"></text>
             </view>
+          </picker>
+        </view>
+
+        <view v-if="selectedProjectObj" class="selected-project">
+          <view class="flex items-center justify-between mb-2">
+            <text class="selected-name">已选择:{{ selectedProjectObj.name }}</text>
+            <text class="reselect-link" @click="selectedProject = ''; selectedProjectObj = null">重新选择</text>
+          </view>
+          <view class="meta-line mb-1">
+            <text class="meta-label">合作类型:</text>
+            <text class="meta-value">{{ selectedProjectObj.type }}</text>
+          </view>
+          <view class="meta-line">
+            <text class="meta-label">项目地址:</text>
+            <text class="meta-value">{{ selectedProjectObj.address }}</text>
           </view>
         </view>
-      </uni-card>
+      </view>
 
       <!-- 服务信息 -->
-      <uni-card title="服务信息" icon="uniui-calendar-filled" :is-shadow="true">
-        <view class="gap-y-4 pt-2">
-          <view>
-            <text class="text-sm text-gray-600 block mb-2">
-              <text class="text-red-500">*</text> 服务时间
-            </text>
-            <picker mode="date" :value="serviceDate" @change="onServiceDateChange">
-              <view class="w-full px-4 py-3 border border-gray-200 rounded-lg text-sm flex items-center justify-between bg-white">
-                <text :class="serviceDate ? 'text-gray-800' : 'text-gray-400'">{{ serviceDate || '请选择日期' }}</text>
-                <text class="uni-icons uniui-calendar-filled text-gray-400"></text>
-              </view>
-            </picker>
-          </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">服务信息</text>
+        </view>
 
-          <view>
-            <text class="text-sm text-gray-600 block mb-2">
-              <text class="text-red-500">*</text> 故障位置
-            </text>
-            <input v-model="faultLocation" class="w-full px-4 py-3 border border-gray-200 rounded-lg text-sm text-gray-800 bg-white" placeholder="请输入故障位置" />
-          </view>
+        <view class="form-group">
+          <text class="form-label"><text class="required">*</text> 服务时间</text>
+          <picker mode="date" :value="serviceDate" @change="onServiceDateChange">
+            <view class="form-input flex items-center justify-between">
+              <text :class="serviceDate ? 'input-text' : 'placeholder-text'">{{ serviceDate || '请选择日期' }}</text>
+              <text class="uni-icons uniui-calendar-filled input-arrow"></text>
+            </view>
+          </picker>
+        </view>
 
-          <view>
-            <text class="text-sm text-gray-600 block mb-2">故障描述</text>
-            <textarea v-model="faultDesc" class="w-full px-4 py-3 border border-gray-200 rounded-lg text-sm text-gray-800 bg-white" style="min-height: 80px;" placeholder="请描述故障情况..." />
-          </view>
+        <view class="form-group">
+          <text class="form-label"><text class="required">*</text> 故障位置</text>
+          <input v-model="faultLocation" class="form-input" placeholder="请输入故障位置" />
+        </view>
 
-          <view>
-            <text class="text-sm text-gray-600 block mb-2">服务方式</text>
-            <picker mode="selector" :range="serviceModes" :value="selectedServiceModeIndex" @change="onServiceModeChange">
-              <view class="w-full px-4 py-3 border border-gray-200 rounded-lg text-sm flex items-center justify-between bg-white">
-                <text :class="serviceMode ? 'text-gray-800' : 'text-gray-400'">{{ serviceMode || '请选择服务方式' }}</text>
-                <text class="uni-icons uniui-arrowright text-gray-400"></text>
-              </view>
-            </picker>
-          </view>
+        <view class="form-group">
+          <text class="form-label">故障描述</text>
+          <textarea v-model="faultDesc" class="form-textarea" placeholder="请描述故障情况..." />
+        </view>
 
-          <view>
-            <text class="text-sm text-gray-600 block mb-2">紧急程度</text>
-            <view class="flex gap-3">
-              <view
-                class="flex-1 py-2 px-3 rounded-lg border text-center text-sm"
-                :class="urgentLevel === 'normal' ? 'border-blue-500 bg-blue-50 text-blue-600' : 'border-gray-200 text-gray-600'"
-                @click="urgentLevel = 'normal'"
-              >
-                <text class="uni-icons mr-1" :class="urgentLevel === 'normal' ? 'uniui-checkmarkempty' : ''"></text>
-                普通
-              </view>
-              <view
-                class="flex-1 py-2 px-3 rounded-lg border text-center text-sm"
-                :class="urgentLevel === 'high' ? 'border-red-500 bg-red-50 text-red-600' : 'border-gray-200 text-gray-600'"
-                @click="urgentLevel = 'high'"
-              >
-                <text class="uni-icons mr-1" :class="urgentLevel === 'high' ? 'uniui-checkmarkempty' : ''"></text>
-                加急
-              </view>
+        <view class="form-group">
+          <text class="form-label">服务方式</text>
+          <picker mode="selector" :range="serviceModes" :value="selectedServiceModeIndex" @change="onServiceModeChange">
+            <view class="form-input flex items-center justify-between">
+              <text :class="serviceMode ? 'input-text' : 'placeholder-text'">{{ serviceMode || '请选择服务方式' }}</text>
+              <text class="uni-icons uniui-arrowright input-arrow"></text>
+            </view>
+          </picker>
+        </view>
+
+        <view class="form-group">
+          <text class="form-label">紧急程度</text>
+          <view class="flex gap-3">
+            <view
+              class="urgent-chip"
+              :class="{ 'urgent-chip-on': urgentLevel === 'normal' }"
+              @click="urgentLevel = 'normal'"
+            >
+              普通
+            </view>
+            <view
+              class="urgent-chip"
+              :class="{ 'urgent-chip-danger': urgentLevel === 'high' }"
+              @click="urgentLevel = 'high'"
+            >
+              加急
             </view>
           </view>
-          <!-- 图片上传 -->
-          <view>
-            <text class="text-sm text-gray-600 block mb-2">现场照片</text>
-            <ImageUploader v-model="taskImages" :max-count="6" tip="最多上传6张照片" />
-          </view>
+        </view>
 
+        <view class="form-group">
+          <text class="form-label">现场照片</text>
+          <ImageUploader v-model="taskImages" :max-count="6" tip="最多上传6张照片" />
         </view>
-      </uni-card>
+      </view>
 
       <!-- 施工信息 -->
-      <uni-card title="施工信息" icon="uniui-cart-filled" :is-shadow="true">
-        <view class="gap-y-4 pt-2">
-          <view>
-            <text class="text-sm text-gray-600 block mb-2">施工设施</text>
-            <input v-model="facility" class="w-full px-4 py-3 border border-gray-200 rounded-lg text-sm text-gray-800 bg-white" placeholder="请输入施工设施" />
-          </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">施工信息</text>
+        </view>
 
-          <view>
-            <text class="text-sm text-gray-600 block mb-2">建议出车</text>
-            <picker mode="selector" :range="vehicleOptions" :value="selectedVehicleIndex" @change="onVehicleSelectChange">
-              <view class="w-full px-4 py-3 border border-gray-200 rounded-lg text-sm flex items-center justify-between bg-white">
-                <text :class="selectedVehicle ? 'text-gray-800' : 'text-gray-400'">{{ selectedVehicle || '请选择车辆' }}</text>
-                <text class="uni-icons uniui-arrowright text-gray-400"></text>
-              </view>
-            </picker>
-          </view>
+        <view class="form-group">
+          <text class="form-label">施工设施</text>
+          <input v-model="facility" class="form-input" placeholder="请输入施工设施" />
+        </view>
 
-          <view>
-            <text class="text-sm text-gray-600 block mb-2">预计工时</text>
-            <input v-model="estimatedHours" class="w-full px-4 py-3 border border-gray-200 rounded-lg text-sm text-gray-800 bg-white" placeholder="例如:2小时" />
-          </view>
+        <view class="form-group">
+          <text class="form-label">建议出车</text>
+          <picker mode="selector" :range="vehicleOptions" :value="selectedVehicleIndex" @change="onVehicleSelectChange">
+            <view class="form-input flex items-center justify-between">
+              <text :class="selectedVehicle ? 'input-text' : 'placeholder-text'">{{ selectedVehicle || '请选择车辆' }}</text>
+              <text class="uni-icons uniui-arrowright input-arrow"></text>
+            </view>
+          </picker>
+        </view>
 
-          <view>
-            <text class="text-sm text-gray-600 block mb-2">验收要求</text>
-            <input v-model="acceptReq" class="w-full px-4 py-3 border border-gray-200 rounded-lg text-sm text-gray-800 bg-white" placeholder="请输入验收要求" />
-          </view>
+        <view class="form-group">
+          <text class="form-label">预计工时</text>
+          <input v-model="estimatedHours" class="form-input" placeholder="例如:2小时" />
+        </view>
 
-          <view>
-            <text class="text-sm text-gray-600 block mb-2">备注</text>
-            <textarea v-model="remark" class="w-full px-4 py-3 border border-gray-200 rounded-lg text-sm text-gray-800 bg-white" style="min-height: 80px;" placeholder="请输入备注信息..." />
-          </view>
+        <view class="form-group">
+          <text class="form-label">验收要求</text>
+          <input v-model="acceptReq" class="form-input" placeholder="请输入验收要求" />
+        </view>
+
+        <view class="form-group">
+          <text class="form-label">备注</text>
+          <textarea v-model="remark" class="form-textarea" placeholder="请输入备注信息..." />
         </view>
-      </uni-card>
+      </view>
 
       <!-- 提交按钮 -->
-      <button class="w-full py-3 bg-blue-500 text-white rounded-xl font-medium flex items-center justify-center" @click="submitTask">
-        <text class="uni-icons uniui-plusempty mr-2"></text>
+      <button
+        class="submit-btn"
+        hover-class="submit-btn-hover"
+        :hover-start-time="0"
+        :hover-stay-time="120"
+        @click="submitTask"
+      >
         发布任务
       </button>
     </view>
@@ -151,7 +154,6 @@
 <script setup lang="ts">
 import { computed, ref } from 'vue'
 import TopBar from '../../components/common/TopBar.vue'
-import UniCard from '../../components/uni-card/uni-card.vue'
 import ImageUploader from '../../components/common/ImageUploader.vue'
 
 const selectedProject = ref('')
@@ -168,7 +170,6 @@ const acceptReq = ref('')
 const remark = ref('')
 const taskImages = ref<string[]>([])
 
-// 模拟项目数据
 const mockProjects = [
   { name: '积丹白鹭小区', type: '包年', address: 'XX市XX区XX路123号' },
   { name: '俊发城', type: '单次', address: 'XX市XX区XX路456号' },
@@ -221,3 +222,170 @@ function submitTask() {
   }, 1500)
 }
 </script>
+
+<style scoped>
+.sub-page {
+  max-width: 430px;
+  margin: 0 auto;
+  min-height: 100vh;
+  background-color: #f6fbf9;
+  padding-bottom: 32px;
+}
+
+.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-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;
+}
+
+.form-group {
+  margin-bottom: 14px;
+}
+
+.form-label {
+  display: block;
+  margin-bottom: 8px;
+  font-size: 13px;
+  font-weight: 600;
+  color: #6b7280;
+}
+
+.required {
+  color: #ef4444;
+}
+
+.form-input {
+  width: 100%;
+  padding: 11px 12px;
+  border-radius: 12px;
+  border: 1px solid #e5e7eb;
+  background-color: #ffffff;
+  font-size: 14px;
+  color: #1f2937;
+}
+
+.form-textarea {
+  width: 100%;
+  min-height: 80px;
+  padding: 10px 12px;
+  border-radius: 12px;
+  border: 1px solid #e5e7eb;
+  background-color: #ffffff;
+  font-size: 14px;
+  color: #1f2937;
+}
+
+.input-text {
+  font-size: 14px;
+  color: #1f2937;
+}
+
+.placeholder-text {
+  font-size: 14px;
+  color: #9ca3af;
+}
+
+.input-arrow {
+  font-size: 14px;
+  color: #9ca3af;
+}
+
+.selected-project {
+  padding: 12px;
+  border-radius: 14px;
+  background-color: rgba(164, 216, 152, 0.14);
+  border: 1px solid rgba(164, 216, 152, 0.3);
+}
+
+.selected-name {
+  font-size: 14px;
+  font-weight: 600;
+  color: #368f6f;
+}
+
+.reselect-link {
+  font-size: 12px;
+  color: #368f6f;
+}
+
+.meta-line {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+}
+
+.meta-label {
+  font-size: 12px;
+  color: #9ca3af;
+}
+
+.meta-value {
+  font-size: 13px;
+  color: #4b5563;
+}
+
+/* 紧急程度:默认白底灰边,激活态绿色/红色 */
+.urgent-chip {
+  flex: 1;
+  padding: 9px 0;
+  border-radius: 12px;
+  text-align: center;
+  font-size: 14px;
+  color: #6b7280;
+  background-color: #ffffff;
+  border: 1px solid #e5e7eb;
+}
+
+.urgent-chip-on {
+  color: #ffffff;
+  font-weight: 600;
+  background: linear-gradient(135deg, #368f6f 0%, #4ba98a 100%);
+  border-color: transparent;
+}
+
+.urgent-chip-danger {
+  color: #ffffff;
+  font-weight: 600;
+  background: linear-gradient(135deg, #ef4444 0%, #f87171 100%);
+  border-color: transparent;
+}
+
+.submit-btn {
+  width: 100%;
+  padding: 13px 0;
+  border-radius: 14px;
+  font-size: 15px;
+  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;
+}
+</style>

+ 330 - 107
src/subPackages/pages-dispatch/schedule.vue

@@ -1,99 +1,103 @@
 <template>
-  <view class="app-container pb-20">
+  <view class="sub-page">
     <TopBar title="排班管理" show-back />
-    
-    <!-- 月份切换 -->
-    <view class="bg-white px-4 py-3 flex items-center justify-between border-b border-gray-100">
-      <view class="flex items-center">
-        <view class="p-2" @click="changeMonth(-1)">
-          <text class="uni-icons uniui-arrowleft text-gray-600"></text>
-        </view>
-        <text class="text-base font-semibold text-gray-800 mx-2">{{ currentYear }}年{{ currentMonth }}月</text>
-        <view class="p-2" @click="changeMonth(1)">
-          <text class="uni-icons uniui-arrowright text-gray-600"></text>
+
+    <view class="px-4 pt-3">
+      <!-- 日历卡片 -->
+      <view class="glass-card p-4">
+        <!-- 月份切换 -->
+        <view class="flex items-center justify-between mb-3">
+          <view class="flex items-center">
+            <view class="month-arrow" hover-class="month-arrow-hover" :hover-start-time="0" :hover-stay-time="120" @click="changeMonth(-1)">
+              <text class="uni-icons uniui-arrowleft month-arrow-icon"></text>
+            </view>
+            <text class="month-title">{{ currentYear }}年{{ currentMonth }}月</text>
+            <view class="month-arrow" hover-class="month-arrow-hover" :hover-start-time="0" :hover-stay-time="120" @click="changeMonth(1)">
+              <text class="uni-icons uniui-arrowright month-arrow-icon"></text>
+            </view>
+          </view>
+          <view class="today-btn" hover-class="month-arrow-hover" :hover-start-time="0" :hover-stay-time="120" @click="goToToday">
+            <text class="uni-icons uniui-calendar-filled today-icon mr-1"></text>
+            <text class="today-text">今天</text>
+          </view>
         </view>
-      </view>
-      <view class="text-sm text-blue-500 flex items-center" @click="goToToday">
-        <text class="uni-icons uniui-calendar-filled mr-1"></text>
-        今天
-      </view>
-    </view>
 
-    <!-- 日历 -->
-    <view class="bg-white px-4 py-3">
-      <!-- 星期标题 -->
-      <view class="flex mb-2">
-        <view v-for="day in weekDays" :key="day" class="flex-1 text-center">
-          <text class="text-xs text-gray-400">{{ day }}</text>
+        <!-- 星期标题 -->
+        <view class="flex mb-2">
+          <view v-for="day in weekDays" :key="day" class="flex-1 text-center">
+            <text class="week-label">{{ day }}</text>
+          </view>
         </view>
-      </view>
-      
-      <!-- 日期 -->
-      <view v-for="(week, weekIndex) in calendarWeeks" :key="weekIndex" class="flex mb-2">
-        <view
-          v-for="(date, dateIndex) in week"
-          :key="dateIndex"
-          class="flex-1 h-11 flex flex-col items-center justify-center relative mx-1"
-          :class="{ 
-            'opacity-30': date.isOtherMonth,
-            'bg-blue-500 rounded-xl shadow-md': date.isToday && !isSelected(date),
-            'bg-white border-2 border-blue-500 rounded-xl shadow-sm': isSelected(date) && !date.isToday,
-            'bg-blue-600 rounded-xl shadow-md': isSelected(date) && date.isToday,
-          }"
-          @click="selectDate(date)"
-        >
-          <text 
-            class="text-sm font-medium"
-            :class="{ 
-              'text-white': date.isToday || isSelected(date), 
-              'text-gray-800': !date.isToday && !isSelected(date) 
+
+        <!-- 日期 -->
+        <view v-for="(week, weekIndex) in calendarWeeks" :key="weekIndex" class="flex mb-2">
+          <view
+            v-for="(date, dateIndex) in week"
+            :key="dateIndex"
+            class="date-cell"
+            :class="{
+              'date-other': date.isOtherMonth,
+              'date-today': date.isToday && !isSelected(date),
+              'date-selected': isSelected(date),
             }"
+            @click="selectDate(date)"
           >
-            {{ date.day }}
-          </text>
-          <view 
-            v-if="date.hasSchedule" 
-            class="w-1 h-1 rounded-full mt-1"
-            :class="date.isToday || isSelected(date) ? 'bg-white' : 'bg-blue-500'"
-          ></view>
+            <text
+              class="date-num"
+              :class="{
+                'date-num-today': date.isToday && !isSelected(date),
+                'date-num-selected': isSelected(date),
+              }"
+            >
+              {{ date.day }}
+            </text>
+            <view
+              v-if="date.hasSchedule"
+              class="schedule-dot"
+              :class="isSelected(date) ? 'schedule-dot-on' : ''"
+            ></view>
+          </view>
         </view>
       </view>
-    </view>
 
-    <!-- 排班列表 -->
-    <view class="px-4 mt-4">
-      <view class="flex justify-between items-center mb-3">
-        <text class="text-base font-semibold text-gray-800">{{ selectedDate }} 排班</text>
-        <view class="text-blue-500 text-sm flex items-center" @click="addSchedule">
-          <text class="uni-icons uniui-plusempty mr-1"></text>
-          新增排班
+      <!-- 排班列表 -->
+      <view class="glass-card">
+        <view class="section-head">
+          <view class="section-bar mr-2"></view>
+          <text class="section-title flex-1">{{ selectedDate }} 排班</text>
+          <view class="add-btn" hover-class="month-arrow-hover" :hover-start-time="0" :hover-stay-time="120" @click="addSchedule">
+            <text class="uni-icons uniui-plusempty add-icon mr-1"></text>
+            <text class="add-text">新增排班</text>
+          </view>
         </view>
-      </view>
-      
-      <view v-if="loading" class="py-10 text-center">
-        <text class="text-gray-400">加载中...</text>
-      </view>
-      
-      <view v-else-if="daySchedules.length === 0">
-        <EmptyState message="该日无排班" />
-      </view>
-      
-      <uni-list v-else border>
-        <uni-list-item 
-          v-for="schedule in daySchedules" 
-          :key="schedule.scheduleId"
-          :title="schedule.taskName"
-          :note="schedule.staffName + ' | ' + (schedule.vehicleNo || '未分配车辆')"
-          :right-text="getStatusText(schedule.status)"
-          show-arrow
-        >
-          <template #header>
-            <view class="w-10 h-10 rounded-xl flex items-center justify-center mr-3" :class="getStatusBgClass(schedule.status)">
-              <text class="uni-icons text-lg" :class="getStatusIconClass(schedule.status)"></text>
+
+        <view v-if="loading" class="py-10 text-center">
+          <text class="empty-text">加载中...</text>
+        </view>
+
+        <view v-else-if="daySchedules.length === 0" class="pb-3">
+          <EmptyState message="该日无排班" />
+        </view>
+
+        <view v-else>
+          <view
+            v-for="schedule in daySchedules"
+            :key="schedule.scheduleId"
+            class="list-row"
+          >
+            <view class="icon-chip mr-3">
+              <text class="uni-icons uniui-calendar-filled chip-glyph"></text>
+            </view>
+            <view class="flex-1 min-w-0 mr-2">
+              <text class="row-title truncate">{{ schedule.taskName }}</text>
+              <text class="row-sub truncate">{{ schedule.staffName }} | {{ schedule.vehicleNo || '未分配车辆' }}</text>
             </view>
-          </template>
-        </uni-list-item>
-      </uni-list>
+            <text class="status-tag flex-shrink-0" :class="getStatusTagClass(schedule.status)">
+              {{ getStatusText(schedule.status) }}
+            </text>
+          </view>
+        </view>
+      </view>
     </view>
   </view>
 </template>
@@ -102,8 +106,6 @@
 import { ref, computed, onMounted, watch } from 'vue'
 import TopBar from '../../components/common/TopBar.vue'
 import EmptyState from '../../components/common/EmptyState.vue'
-import UniList from '../../components/uni-list/uni-list.vue'
-import UniListItem from '../../components/uni-list/uni-list-item.vue'
 import { getScheduleList } from '../../api/schedule'
 
 const loading = ref(false)
@@ -121,10 +123,10 @@ const calendarWeeks = computed(() => {
   const lastDay = new Date(year, month, 0)
   const firstWeekDay = firstDay.getDay()
   const daysInMonth = lastDay.getDate()
-  
+
   const weeks: any[] = []
   let currentWeek: any[] = []
-  
+
   const prevMonthDays = new Date(year, month - 1, 0).getDate()
   for (let i = firstWeekDay - 1; i >= 0; i--) {
     currentWeek.push({
@@ -134,12 +136,12 @@ const calendarWeeks = computed(() => {
       hasSchedule: false,
     })
   }
-  
+
   for (let day = 1; day <= daysInMonth; day++) {
     const dateStr = `${year}-${String(month).padStart(2, '0')}-${String(day).padStart(2, '0')}`
     const isToday = dateStr === new Date().toISOString().split('T')[0]
     const hasSchedule = schedules.value.some(s => s.scheduleDate === dateStr)
-    
+
     currentWeek.push({
       day,
       isOtherMonth: false,
@@ -147,13 +149,13 @@ const calendarWeeks = computed(() => {
       hasSchedule,
       dateStr,
     })
-    
+
     if (currentWeek.length === 7) {
       weeks.push(currentWeek)
       currentWeek = []
     }
   }
-  
+
   if (currentWeek.length > 0) {
     for (let i = 1; currentWeek.length < 7; i++) {
       currentWeek.push({
@@ -165,7 +167,7 @@ const calendarWeeks = computed(() => {
     }
     weeks.push(currentWeek)
   }
-  
+
   return weeks
 })
 
@@ -212,22 +214,13 @@ function getStatusText(status: string) {
   return map[status] || status
 }
 
-function getStatusBgClass(status: string) {
+function getStatusTagClass(status: string) {
   const map: Record<string, string> = {
-    scheduled: 'bg-blue-50',
-    completed: 'bg-green-50',
-    cancelled: 'bg-red-50',
+    scheduled: 'status-tag-blue',
+    completed: 'status-tag-green',
+    cancelled: 'status-tag-red',
   }
-  return map[status] || 'bg-gray-50'
-}
-
-function getStatusIconClass(status: string) {
-  const map: Record<string, string> = {
-    scheduled: 'uniui-calendar-filled text-blue-500',
-    completed: 'uniui-checkmarkempty text-green-500',
-    cancelled: 'uniui-close text-red-500',
-  }
-  return map[status] || 'uniui-calendar text-gray-400'
+  return map[status] || 'status-tag-gray'
 }
 
 function addSchedule() {
@@ -254,3 +247,233 @@ onMounted(() => {
   fetchSchedules()
 })
 </script>
+
+<style scoped>
+.sub-page {
+  max-width: 430px;
+  margin: 0 auto;
+  min-height: 100vh;
+  background-color: #f6fbf9;
+  padding-bottom: 24px;
+}
+
+.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;
+}
+
+.month-arrow {
+  width: 32px;
+  height: 32px;
+  border-radius: 10px;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  background-color: rgba(164, 216, 152, 0.2);
+}
+
+.month-arrow-hover {
+  opacity: 0.75;
+}
+
+.month-arrow-icon {
+  font-size: 16px;
+  color: #368f6f;
+}
+
+.month-title {
+  margin: 0 10px;
+  font-size: 16px;
+  font-weight: 700;
+  color: #1f2937;
+}
+
+.today-btn {
+  display: flex;
+  align-items: center;
+  padding: 6px 12px;
+  border-radius: 999px;
+  background-color: rgba(164, 216, 152, 0.2);
+}
+
+.today-icon {
+  font-size: 14px;
+  color: #368f6f;
+}
+
+.today-text {
+  font-size: 13px;
+  font-weight: 600;
+  color: #368f6f;
+}
+
+.week-label {
+  font-size: 12px;
+  color: #9ca3af;
+}
+
+.date-cell {
+  flex: 1;
+  height: 44px;
+  margin: 0 2px;
+  border-radius: 12px;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+}
+
+.date-other {
+  opacity: 0.3;
+}
+
+.date-today {
+  border: 1px solid #368f6f;
+}
+
+.date-selected {
+  background: linear-gradient(135deg, #368f6f 0%, #4ba98a 100%);
+  box-shadow: 0 8px 18px -8px rgba(54, 143, 111, 0.8);
+}
+
+.date-num {
+  font-size: 14px;
+  font-weight: 500;
+  color: #1f2937;
+}
+
+.date-num-today {
+  color: #368f6f;
+  font-weight: 700;
+}
+
+.date-num-selected {
+  color: #ffffff;
+  font-weight: 700;
+}
+
+.schedule-dot {
+  width: 4px;
+  height: 4px;
+  border-radius: 50%;
+  margin-top: 3px;
+  background-color: #368f6f;
+}
+
+.schedule-dot-on {
+  background-color: #ffffff;
+}
+
+.section-head {
+  display: flex;
+  align-items: center;
+  padding: 16px 16px 12px;
+}
+
+.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;
+}
+
+.add-btn {
+  display: flex;
+  align-items: center;
+  padding: 5px 12px;
+  border-radius: 999px;
+  background: linear-gradient(135deg, #368f6f 0%, #4ba98a 100%);
+}
+
+.add-icon {
+  font-size: 13px;
+  color: #ffffff;
+}
+
+.add-text {
+  font-size: 12px;
+  font-weight: 600;
+  color: #ffffff;
+}
+
+.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;
+}
+
+.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;
+}
+
+.status-tag {
+  padding: 2px 10px;
+  border-radius: 999px;
+  font-size: 11px;
+  font-weight: 600;
+}
+
+.status-tag-green {
+  color: #368f6f;
+  background-color: rgba(164, 216, 152, 0.24);
+}
+
+.status-tag-blue {
+  color: #2563eb;
+  background-color: rgba(37, 99, 235, 0.1);
+}
+
+.status-tag-red {
+  color: #ef4444;
+  background-color: rgba(239, 68, 68, 0.1);
+}
+
+.status-tag-gray {
+  color: #6b7280;
+  background-color: #f3f4f6;
+}
+
+.empty-text {
+  font-size: 13px;
+  color: #9ca3af;
+}
+</style>

+ 565 - 173
src/subPackages/pages-dispatch/taskDetail.vue

@@ -1,184 +1,222 @@
 <template>
-  <view class="app-container pb-24">
+  <view class="sub-page">
     <TopBar title="任务详情" show-back />
 
-    <view v-if="task" class="px-4 py-4">
+    <view v-if="loading" class="py-10 text-center">
+      <text class="empty-text">加载中...</text>
+    </view>
+
+    <view v-else-if="task" class="px-4 pt-3">
       <!-- 基本信息卡片 -->
-      <view class="card p-4 mb-4">
-        <view class="flex items-center justify-between mb-4">
-          <view class="flex-1 min-w-0 mr-3">
-            <text class="text-base font-semibold text-gray-800 block truncate">{{ task.projectName }}</text>
-          </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>
           <StatusTag :status="task.status" />
         </view>
-
         <view class="gap-y-3">
           <view class="flex items-start">
             <text class="text-gray-500 text-sm w-24">任务名称</text>
-            <text class="text-sm text-gray-800 flex-1">{{ task.projectName }}</text>
+            <text class="text-sm text-gray-800 flex-1">{{ taskName }}</text>
+          </view>
+          <view v-if="task.taskNo" class="flex items-start">
+            <text class="text-gray-500 text-sm w-24">任务编号</text>
+            <text class="text-sm text-gray-800 flex-1">{{ task.taskNo }}</text>
           </view>
           <view v-if="task.faultLocation" class="flex items-start">
             <text class="text-gray-500 text-sm w-24">故障位置</text>
             <text class="text-sm text-gray-800 flex-1">{{ task.faultLocation }}</text>
           </view>
-          <view v-if="task.serviceType" class="flex items-start">
+          <view class="flex items-start">
             <text class="text-gray-500 text-sm w-24">施工类型</text>
-            <text class="text-sm text-gray-800 flex-1">{{ task.serviceType }}</text>
-          </view>
-          <view v-if="task.facilityType" class="flex items-start">
-            <text class="text-gray-500 text-sm w-24">设施类型</text>
-            <text class="text-sm text-gray-800 flex-1">{{ task.facilityType }}</text>
+            <text class="text-sm text-gray-800 flex-1">{{ constructionTypeText }}</text>
           </view>
           <view class="flex items-start">
             <text class="text-gray-500 text-sm w-24">服务时间</text>
             <text class="text-sm text-gray-800 flex-1">{{ serviceTimeText }}</text>
           </view>
-          <view v-if="task.applicant" class="flex items-start">
+          <view v-if="task.createBy" class="flex items-start">
             <text class="text-gray-500 text-sm w-24">申请人</text>
-            <text class="text-sm text-gray-800 flex-1">{{ task.applicant }}</text>
+            <text class="text-sm text-gray-800 flex-1">{{ task.createBy }}</text>
           </view>
-          <view v-if="task.applyTime" class="flex items-start">
+          <view v-if="task.createTime" class="flex items-start">
             <text class="text-gray-500 text-sm w-24">申请时间</text>
-            <text class="text-sm text-gray-800 flex-1">{{ formatDate(task.applyTime, 'YYYY-MM-DD HH:mm') }}</text>
-          </view>
-          <view v-if="task.address" class="flex items-start">
-            <text class="text-gray-500 text-sm w-24">项目地址</text>
-            <text class="text-sm text-gray-800 flex-1">{{ task.address }}</text>
-          </view>
-          <view class="flex items-start">
-            <text class="text-gray-500 text-sm w-24">创建时间</text>
             <text class="text-sm text-gray-800 flex-1">{{ formatDate(task.createTime, 'YYYY-MM-DD HH:mm') }}</text>
           </view>
+          <view v-if="urgencyText" class="flex items-start">
+            <text class="text-gray-500 text-sm w-24">紧急程度</text>
+            <text class="text-sm flex-1" :class="task.urgencyLevel === 'very_urgent' ? 'text-red-500' : 'text-gray-800'">{{ urgencyText }}</text>
+          </view>
         </view>
       </view>
 
-      <!-- 项目信息 -->
-      <view class="card p-4 mb-4">
-        <text class="text-base font-semibold text-gray-800 mb-4 block">项目信息</text>
+      <!-- 项目信息卡片 -->
+      <view v-if="project" 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="gap-y-3">
-          <view v-if="task.customerName" class="flex items-start">
-            <text class="text-gray-500 text-sm w-24">客户名称</text>
-            <text class="text-sm text-gray-800 flex-1">{{ task.customerName }}</text>
-          </view>
-          <view v-if="task.customerPhone" class="flex items-start">
-            <text class="text-gray-500 text-sm w-24">客户电话</text>
-            <text class="text-sm text-gray-800 flex-1">{{ task.customerPhone }}</text>
+          <view class="flex items-start">
+            <text class="text-gray-500 text-sm w-24">项目名称</text>
+            <text class="text-sm text-gray-800 flex-1">{{ project.projectName }}</text>
           </view>
-          <view v-if="task.coopType" class="flex items-start">
+          <view v-if="project.cooperationType" class="flex items-start">
             <text class="text-gray-500 text-sm w-24">合作类型</text>
-            <text class="text-sm text-gray-800 flex-1">{{ task.coopType }}</text>
+            <text class="text-sm text-gray-800 flex-1">{{ coopTypeText }}</text>
           </view>
-          <view v-if="task.serviceMode" class="flex items-start">
-            <text class="text-gray-500 text-sm w-24">服务方式</text>
-            <text class="text-sm text-gray-800 flex-1">{{ task.serviceMode }}</text>
+          <view v-if="projectAddress" class="flex items-start">
+            <text class="text-gray-500 text-sm w-24">项目地址</text>
+            <text class="text-sm text-gray-800 flex-1">{{ projectAddress }}</text>
           </view>
-          <view v-if="task.acceptType" class="flex items-start">
-            <text class="text-gray-500 text-sm w-24">验收方式</text>
-            <text class="text-sm text-gray-800 flex-1">{{ task.acceptType }}</text>
+          <view v-if="project.primaryContactName" class="flex items-start">
+            <text class="text-gray-500 text-sm w-24">联系人</text>
+            <view class="flex-1 flex items-center justify-between">
+              <text class="text-sm text-gray-800">{{ project.primaryContactName }} {{ project.primaryContactPhone || '' }}</text>
+              <button
+                v-if="project.primaryContactPhone"
+                class="phone-circle"
+                @click="callPhone(project.primaryContactPhone)"
+              >
+                <text class="uni-icons uniui-phone-filled phone-icon"></text>
+              </button>
+            </view>
           </view>
         </view>
       </view>
 
-      <!-- 施工信息 -->
-      <view v-if="task.faultDesc || task.estimatedQuantity || task.estimatedHours || task.constructionHelp" class="card p-4 mb-4">
-        <text class="text-base font-semibold text-gray-800 mb-4 block">施工信息</text>
+      <!-- 故障详情卡片 -->
+      <view v-if="geoLocationText || faultMedia.length > 0" 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="gap-y-3">
-          <view v-if="task.faultDesc" class="flex items-start">
+          <view v-if="geoLocationText" class="flex items-start">
+            <text class="text-gray-500 text-sm w-24">地理位置</text>
+            <text class="text-sm link-text flex-1" @click="openMap">{{ geoLocationText }}(点击查看地图)</text>
+          </view>
+          <view v-if="faultMedia.length > 0" class="flex items-start">
+            <text class="text-gray-500 text-sm w-24">故障图片</text>
+            <view class="flex-1 flex flex-wrap gap-2">
+              <image
+                v-for="(url, index) in faultMedia"
+                :key="index"
+                :src="url"
+                class="w-16 h-16 rounded-lg bg-gray-100"
+                mode="aspectFill"
+                @click="previewMedia(index)"
+              />
+            </view>
+          </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">施工信息</text>
+        </view>
+        <view class="gap-y-3">
+          <view v-if="faultDescText" class="flex items-start">
             <text class="text-gray-500 text-sm w-24">故障描述</text>
-            <text class="text-sm text-gray-800 flex-1">{{ task.faultDesc }}</text>
+            <text class="text-sm text-gray-800 flex-1">{{ faultDescText }}</text>
           </view>
-          <view v-if="task.estimatedQuantity" class="flex items-start">
-            <text class="text-gray-500 text-sm w-24">预估方量</text>
-            <text class="text-sm text-gray-800 flex-1">{{ task.estimatedQuantity }}</text>
+          <view v-if="task.constructionFacility" class="flex items-start">
+            <text class="text-gray-500 text-sm w-24">施工设施</text>
+            <text class="text-sm text-gray-800 flex-1">{{ task.constructionFacility }}</text>
           </view>
-          <view v-if="task.estimatedHours" class="flex items-start">
-            <text class="text-gray-500 text-sm w-24">预估工时</text>
-            <text class="text-sm text-gray-800 flex-1">{{ task.estimatedHours }}</text>
+          <view v-if="task.serviceMethod" class="flex items-start">
+            <text class="text-gray-500 text-sm w-24">服务方式</text>
+            <text class="text-sm text-gray-800 flex-1">{{ task.serviceMethod }}</text>
           </view>
-          <view v-if="task.constructionHelp" class="flex items-start">
-            <text class="text-gray-500 text-sm w-24">施工协助</text>
-            <text class="text-sm text-gray-800 flex-1">{{ task.constructionHelp }}</text>
+          <view v-if="extra.suggestedVehicle" class="flex items-start">
+            <text class="text-gray-500 text-sm w-24">建议出车</text>
+            <text class="text-sm text-gray-800 flex-1">{{ extra.suggestedVehicle }}</text>
           </view>
-          <view v-if="task.waterSource" class="flex items-start">
-            <text class="text-gray-500 text-sm w-24">水源情况</text>
-            <text class="text-sm text-gray-800 flex-1">{{ task.waterSource }}</text>
+          <view v-if="task.waterDistance" class="flex items-start">
+            <text class="text-gray-500 text-sm w-24">取水距离</text>
+            <text class="text-sm text-gray-800 flex-1">{{ task.waterDistance }}</text>
           </view>
-          <view v-if="task.septicTank" class="flex items-start">
-            <text class="text-gray-500 text-sm w-24">化粪池情况</text>
-            <text class="text-sm text-gray-800 flex-1">{{ task.septicTank }}</text>
+          <view v-if="task.outletNormal !== undefined && task.outletNormal !== null" class="flex items-start">
+            <text class="text-gray-500 text-sm w-24">化粪池排口</text>
+            <text class="text-sm text-gray-800 flex-1">{{ task.outletNormal === 1 ? '正常' : '异常' }}</text>
           </view>
-        </view>
-      </view>
-
-      <!-- 甲方联系人 -->
-      <view v-if="partyAContacts.length > 0" class="card p-4 mb-4">
-        <text class="text-base font-semibold text-gray-800 mb-4 block">甲方联系人</text>
-        <view class="gap-y-3">
-          <view
-            v-for="(contact, index) in partyAContacts"
-            :key="index"
-            class="flex items-center justify-between p-3 bg-surface rounded-lg"
-          >
-            <view>
-              <text class="text-sm text-gray-800">{{ contact.name }}</text>
-            </view>
-            <view class="flex items-center">
-              <text class="text-sm text-gray-600 mr-3">{{ contact.phone }}</text>
-              <button class="w-8 h-8 bg-primary-light rounded-full flex items-center justify-center" @click="callPhone(contact.phone)">
-                <text class="uni-icons uniui-phone-filled text-primary"></text>
-              </button>
-            </view>
+          <view v-if="task.assistantInfo" class="flex items-start">
+            <text class="text-gray-500 text-sm w-24">施工助力</text>
+            <text class="text-sm text-gray-800 flex-1">{{ task.assistantInfo }}</text>
           </view>
-        </view>
-      </view>
-
-      <!-- 任务进度时间线 -->
-      <view class="card p-4 mb-4">
-        <text class="text-base font-semibold text-gray-800 mb-4 block">任务进度</text>
-        <view class="gap-y-4">
-          <view v-for="(record, index) in task.timeline" :key="index" class="flex items-start">
-            <view class="flex flex-col items-center mr-3">
-              <view class="w-3 h-3 rounded-full" :class="index === 0 ? 'bg-green-500' : 'bg-blue-500'"></view>
-              <view v-if="index < task.timeline.length - 1" class="w-px h-8 bg-gray-200 mt-1"></view>
-            </view>
-            <view class="flex-1 pb-2">
-              <text class="text-sm font-medium text-gray-800">{{ record.action }}</text>
-              <view class="flex justify-between mt-1">
-                <text class="text-xs text-gray-500">{{ record.operator }} · {{ getRoleText(record.role) }}</text>
-                <text class="text-xs text-gray-400">{{ formatDate(record.time, 'MM-DD HH:mm') }}</text>
+          <view v-if="task.estimatedWorkload" class="flex items-start">
+            <text class="text-gray-500 text-sm w-24">预计作业量</text>
+            <text class="text-sm text-gray-800 flex-1">{{ task.estimatedWorkload }}</text>
+          </view>
+          <view v-if="task.estimatedDuration" class="flex items-start">
+            <text class="text-gray-500 text-sm w-24">预计工时</text>
+            <text class="text-sm text-gray-800 flex-1">{{ task.estimatedDuration }}</text>
+          </view>
+          <view v-if="task.acceptanceRequirement" class="flex items-start">
+            <text class="text-gray-500 text-sm w-24">验收要求</text>
+            <text class="text-sm text-gray-800 flex-1">{{ task.acceptanceRequirement }}</text>
+          </view>
+          <view v-if="task.acceptanceMethod" class="flex items-start">
+            <text class="text-gray-500 text-sm w-24">验收方式</text>
+            <text class="text-sm text-gray-800 flex-1">{{ task.acceptanceMethod }}</text>
+          </view>
+          <view v-if="partyAContacts.length > 0" class="flex items-start">
+            <text class="text-gray-500 text-sm w-24">甲方当事人</text>
+            <view class="flex-1">
+              <view
+                v-for="(contact, index) in partyAContacts"
+                :key="index"
+                class="flex items-center justify-between py-1"
+              >
+                <text class="text-sm text-gray-800">{{ contact.name }} {{ contact.phone }}</text>
+                <button
+                  v-if="contact.phone"
+                  class="phone-circle"
+                  @click="callPhone(contact.phone)"
+                >
+                  <text class="uni-icons uniui-phone-filled phone-icon"></text>
+                </button>
               </view>
             </view>
           </view>
+          <view v-if="task.remark" class="flex items-start">
+            <text class="text-gray-500 text-sm w-24">备注</text>
+            <text class="text-sm text-red-500 flex-1">{{ task.remark }}</text>
+          </view>
         </view>
       </view>
 
       <!-- 审核操作卡片(待审核状态显示) -->
-      <view v-if="task.status === 'pending'" class="card p-4 mb-4">
-        <text class="text-base font-semibold text-gray-800 mb-4 block">审核操作</text>
+      <view v-if="isPendingAudit" 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 v-if="!showArrangeForm">
-          <text class="text-sm text-gray-500 mb-4 block">请确认该任务是否可以执行,然后进行后续安排。</text>
+          <text class="hint-text mb-4 block">请确认该任务是否可以执行,然后进行后续安排。</text>
           <view class="flex gap-3">
-            <button class="flex-1 py-3 bg-red-500 text-white rounded-lg text-sm font-medium" @click="rejectTask">驳回</button>
-            <button class="flex-1 py-3 bg-green-500 text-white rounded-lg text-sm font-medium" @click="confirmTask">确认</button>
+            <view class="action-btn action-btn-reject" hover-class="action-btn-hover" :hover-start-time="0" :hover-stay-time="120" @click="rejectTask">驳回</view>
+            <view class="action-btn action-btn-confirm" hover-class="action-btn-hover" :hover-start-time="0" :hover-stay-time="120" @click="confirmTask">确认</view>
           </view>
         </view>
         <view v-else>
-          <text class="text-sm text-yellow-600 mb-4 block">任务已确认,请安排班组和车辆</text>
+          <text class="hint-text hint-gold mb-4 block">任务已确认,请安排班组和车辆</text>
           <view class="form-group mb-4">
             <text class="form-label">调度班组</text>
-            <picker mode="selector" :range="teamOptions" :value="selectedTeamIndex" @change="onTeamChange">
+            <picker mode="selector" :range="teamOptions" range-key="label" :value="selectedTeamIndex" @change="onTeamChange">
               <view class="form-input flex items-center justify-between">
-                <text :class="selectedTeam ? 'text-gray-800' : 'text-gray-400'">{{ selectedTeam || '请选择班组' }}</text>
-                <text class="text-gray-400"></text>
+                <text :class="selectedTeamLabel ? 'text-gray-800' : 'text-gray-400'">{{ selectedTeamLabel || '请选择班组' }}</text>
               </view>
             </picker>
           </view>
           <view class="form-group mb-4">
             <text class="form-label">调度车辆</text>
-            <picker mode="selector" :range="vehicleOptions" :value="selectedVehicleIndex" @change="onVehicleChange">
+            <picker mode="selector" :range="vehicleOptions" range-key="label" :value="selectedVehicleIndex" @change="onVehicleChange">
               <view class="form-input flex items-center justify-between">
-                <text :class="selectedVehicle ? 'text-gray-800' : 'text-gray-400'">{{ selectedVehicle || '请选择车辆' }}</text>
-                <text class="text-gray-400"></text>
+                <text :class="selectedVehicleLabel ? 'text-gray-800' : 'text-gray-400'">{{ selectedVehicleLabel || '请选择车辆' }}</text>
               </view>
             </picker>
           </view>
@@ -187,31 +225,32 @@
             <textarea v-model="auditRemark" class="form-textarea" placeholder="请输入审核备注..."></textarea>
           </view>
           <view class="flex gap-3">
-            <button class="flex-1 py-3 bg-gray-200 text-gray-700 rounded-lg text-sm font-medium" @click="showArrangeForm = false">返回</button>
-            <button class="flex-1 py-3 bg-blue-500 text-white rounded-lg text-sm font-medium" @click="arrangeTeam">安排</button>
+            <view class="action-btn action-btn-back" hover-class="action-btn-hover" :hover-start-time="0" :hover-stay-time="120" @click="showArrangeForm = false">返回</view>
+            <view class="action-btn action-btn-primary" hover-class="action-btn-hover" :hover-start-time="0" :hover-stay-time="120" @click="arrangeTeam">安排</view>
           </view>
         </view>
       </view>
 
-      <!-- 待排班操作卡片(confirmed状态显示) -->
-      <view v-if="task.status === 'confirmed'" class="card p-4 mb-4">
-        <text class="text-base font-semibold text-gray-800 mb-4 block">安排班组</text>
-        <text class="text-sm text-yellow-600 mb-4 block">任务已确认,请安排班组和车辆</text>
+      <!-- 待安排操作卡片(approved 状态显示) -->
+      <view v-if="task.status === 'approved'" 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>
+        <text class="hint-text hint-gold mb-4 block">任务已审核通过,请安排班组和车辆</text>
         <view class="form-group mb-4">
           <text class="form-label">调度班组</text>
-          <picker mode="selector" :range="teamOptions" :value="selectedTeamIndex" @change="onTeamChange">
+          <picker mode="selector" :range="teamOptions" range-key="label" :value="selectedTeamIndex" @change="onTeamChange">
             <view class="form-input flex items-center justify-between">
-              <text :class="selectedTeam ? 'text-gray-800' : 'text-gray-400'">{{ selectedTeam || '请选择班组' }}</text>
-              <text class="text-gray-400"></text>
+              <text :class="selectedTeamLabel ? 'text-gray-800' : 'text-gray-400'">{{ selectedTeamLabel || '请选择班组' }}</text>
             </view>
           </picker>
         </view>
         <view class="form-group mb-4">
           <text class="form-label">调度车辆</text>
-          <picker mode="selector" :range="vehicleOptions" :value="selectedVehicleIndex" @change="onVehicleChange">
+          <picker mode="selector" :range="vehicleOptions" range-key="label" :value="selectedVehicleIndex" @change="onVehicleChange">
             <view class="form-input flex items-center justify-between">
-              <text :class="selectedVehicle ? 'text-gray-800' : 'text-gray-400'">{{ selectedVehicle || '请选择车辆' }}</text>
-              <text class="text-gray-400"></text>
+              <text :class="selectedVehicleLabel ? 'text-gray-800' : 'text-gray-400'">{{ selectedVehicleLabel || '请选择车辆' }}</text>
             </view>
           </picker>
         </view>
@@ -219,14 +258,67 @@
           <text class="form-label">审核备注(可选)</text>
           <textarea v-model="auditRemark" class="form-textarea" placeholder="请输入审核备注..."></textarea>
         </view>
-        <button class="w-full py-3 bg-blue-500 text-white rounded-lg text-sm font-medium" @click="arrangeTeam">安排</button>
+        <view class="action-btn action-btn-primary" hover-class="action-btn-hover" :hover-start-time="0" :hover-stay-time="120" @click="arrangeTeam">安排</view>
       </view>
 
       <!-- 驳回原因(已驳回状态显示) -->
-      <view v-if="task.status === 'rejected'" class="card p-4 mb-4">
-        <text class="text-base font-semibold text-gray-800 mb-4 block">驳回原因</text>
-        <view class="p-3 bg-red-50 rounded-lg">
-          <text class="text-sm text-red-600">{{ task.rejectReason || '任务不符合执行条件,已驳回' }}</text>
+      <view v-if="task.status === 'rejected'" 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="reject-box">
+          <text class="reject-text">{{ task.auditRemark || '任务不符合执行条件,已驳回' }}</text>
+        </view>
+      </view>
+
+      <!-- 调度信息(已安排状态显示) -->
+      <view v-if="isDispatched" 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="gap-y-3">
+          <view class="flex items-start">
+            <text class="text-gray-500 text-sm w-24">执行班组</text>
+            <text class="text-sm text-gray-800 flex-1">{{ dispatchedTeamName }}</text>
+          </view>
+          <view class="flex items-start">
+            <text class="text-gray-500 text-sm w-24">调度车辆</text>
+            <text class="text-sm text-gray-800 flex-1">{{ dispatchedVehicleName }}</text>
+          </view>
+          <view v-if="auditOperator" class="flex items-start">
+            <text class="text-gray-500 text-sm w-24">审核人</text>
+            <text class="text-sm text-gray-800 flex-1">{{ auditOperator }}</text>
+          </view>
+          <view v-if="task.auditTime" class="flex items-start">
+            <text class="text-gray-500 text-sm w-24">审核时间</text>
+            <text class="text-sm text-gray-800 flex-1">{{ formatDate(task.auditTime, 'YYYY-MM-DD HH:mm') }}</text>
+          </view>
+        </view>
+      </view>
+
+      <!-- 操作日志 -->
+      <view v-if="taskLogs.length > 0" 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="gap-y-4">
+          <view v-for="(log, index) in taskLogs" :key="log.logId || index" class="flex items-start">
+            <view class="flex flex-col items-center mr-3">
+              <view class="log-dot" :class="{ 'log-dot-active': index === 0 }"></view>
+              <view v-if="index < taskLogs.length - 1" class="w-px h-8 bg-gray-200 mt-1"></view>
+            </view>
+            <view class="flex-1 pb-2">
+              <text class="text-sm font-medium text-gray-800">{{ log.operation }}</text>
+              <view class="flex justify-between mt-1">
+                <text class="text-xs text-gray-500">{{ log.operatorName || '系统' }}</text>
+                <text class="text-xs text-gray-400">{{ formatDate(log.createTime, 'MM-DD HH:mm') }}</text>
+              </view>
+              <text v-if="log.remark" class="text-xs text-gray-400 block mt-1">{{ log.remark }}</text>
+            </view>
+          </view>
         </view>
       </view>
     </view>
@@ -241,107 +333,407 @@ import { onLoad } from '@dcloudio/uni-app'
 import TopBar from '../../components/common/TopBar.vue'
 import StatusTag from '../../components/common/StatusTag.vue'
 import EmptyState from '../../components/common/EmptyState.vue'
-import { useTaskStore } from '../../stores/task'
 import { useAuthStore } from '../../stores/auth'
-import { formatDate, getTypeText, getRoleText, showToast, showModal } from '../../utils'
-import { updateTask } from '../../api/task'
+import { formatDate, showToast } from '../../utils'
+import { getTaskDetail, approveTask, rejectTaskAudit, dispatchTask } from '../../api/task'
+import { getProjectDetail } from '../../api/project'
+import { getTeamSimpleList } from '../../api/team'
+import { getVehicleSimpleList } from '../../api/vehicle'
 
-const taskStore = useTaskStore()
 const authStore = useAuthStore()
 
-const task = computed(() => taskStore.currentTask)
+const task = ref<any>(null)
+const project = ref<any>(null)
+const loading = ref(false)
+const submitting = ref(false)
+const taskId = ref<number>(0)
+
 const showArrangeForm = ref(false)
-const selectedTeam = ref('')
-const selectedVehicle = ref('')
 const auditRemark = ref('')
+const teamOptions = ref<Array<{ value: number; label: string }>>([])
+const vehicleOptions = ref<Array<{ value: number; label: string }>>([])
+const selectedTeamId = ref<number | undefined>(undefined)
+const selectedVehicleId = ref<number | undefined>(undefined)
 
-const teamOptions = ['一班组', '二班组', '三班组', '应急组']
-const vehicleOptions = ['浙A·12345 清洁车', '浙A·67890 垃圾清运车', '浙A·11111 高压清洗车', '浙A·33333 清洁车']
+const selectedTeamIndex = computed(() => teamOptions.value.findIndex(t => t.value === selectedTeamId.value))
+const selectedVehicleIndex = computed(() => vehicleOptions.value.findIndex(v => v.value === selectedVehicleId.value))
+const selectedTeamLabel = computed(() => teamOptions.value.find(t => t.value === selectedTeamId.value)?.label || '')
+const selectedVehicleLabel = computed(() => vehicleOptions.value.find(v => v.value === selectedVehicleId.value)?.label || '')
 
-const selectedTeamIndex = computed(() => teamOptions.indexOf(selectedTeam.value))
-const selectedVehicleIndex = computed(() => vehicleOptions.indexOf(selectedVehicle.value))
+const extra = computed(() => {
+  if (!task.value?.extraData) return {} as any
+  try {
+    return JSON.parse(task.value.extraData)
+  } catch {
+    return {} as any
+  }
+})
+
+const taskLogs = computed(() => task.value?.logList || [])
+
+const isPendingAudit = computed(() => ['auditing', 'pending'].includes(task.value?.status))
+const isDispatched = computed(() => ['assigned', 'in_progress', 'completed'].includes(task.value?.status))
+
+const taskName = computed(() => {
+  if (!task.value) return ''
+  return task.value.taskName || task.value.serviceContent || task.value.faultLocation || taskTypeText(task.value.taskType) || '未命名任务'
+})
+
+const constructionTypeText = computed(() => {
+  if (!task.value) return ''
+  return task.value.constructionType || taskTypeText(task.value.taskType) || '-'
+})
 
 const serviceTimeText = computed(() => {
   if (!task.value) return '待定'
-  const date = task.value.serviceDate || task.value.scheduleDate || ''
-  const time = task.value.serviceTime || task.value.scheduleTime || ''
-  return date || time ? `${date} ${time}`.trim() : '待定'
+  const date = task.value.planDate || ''
+  const rangeMap: Record<string, string> = { morning: '上午', afternoon: '下午', evening: '晚上' }
+  const range = rangeMap[task.value.planTimeRange] || task.value.planTimeRange || ''
+  return date || range ? `${date} ${range}`.trim() : '待定'
 })
 
-const partyAContacts = computed(() => {
-  if (!task.value) return []
-  return task.value.partyAContacts || []
+const urgencyText = computed(() => {
+  const map: Record<string, string> = { normal: '普通', urgent: '紧急', very_urgent: '特急' }
+  return task.value?.urgencyLevel ? map[task.value.urgencyLevel] || task.value.urgencyLevel : ''
 })
 
+const coopTypeText = computed(() => {
+  const map: Record<string, string> = { annual: '包年', monthly: '包月', once: '单次', contract: '合同制' }
+  const v = project.value?.cooperationType
+  return v ? map[v] || v : ''
+})
+
+const projectAddress = computed(() => {
+  if (!project.value) return ''
+  const p = project.value
+  return [p.province, p.city, p.district, p.street, p.detailAddress || p.address].filter(Boolean).join('') || p.address || ''
+})
+
+const faultDescText = computed(() => {
+  if (!task.value) return ''
+  const parts = [task.value.faultType, task.value.customerWords].filter(Boolean)
+  return parts.join(';')
+})
+
+const geoLocationText = computed(() => {
+  if (extra.value.geoLocation) return extra.value.geoLocation
+  if (extra.value.latitude && extra.value.longitude) return `${extra.value.latitude}, ${extra.value.longitude}`
+  return ''
+})
+
+const faultMedia = computed<string[]>(() => {
+  const photos = extra.value.photos || extra.value.faultPhotos || extra.value.media
+  if (!photos) return []
+  if (Array.isArray(photos)) return photos
+  return String(photos).split(',').filter(Boolean)
+})
+
+const partyAContacts = computed<Array<{ name: string; phone: string }>>(() => {
+  const list = extra.value.partyAContacts || extra.value.partyA
+  if (!list) return []
+  if (Array.isArray(list)) return list
+  return []
+})
+
+const dispatchedTeamName = computed(() => {
+  if (!task.value?.dispatchTeamId) return '未安排'
+  return teamOptions.value.find(t => t.value === Number(task.value.dispatchTeamId))?.label || `班组#${task.value.dispatchTeamId}`
+})
+
+const dispatchedVehicleName = computed(() => {
+  if (!task.value?.dispatchVehicleId) return '未安排'
+  return vehicleOptions.value.find(v => v.value === Number(task.value.dispatchVehicleId))?.label || `车辆#${task.value.dispatchVehicleId}`
+})
+
+const auditOperator = computed(() => {
+  const auditLog = taskLogs.value.find((l: any) => l.operationType === 'audit')
+  return auditLog?.operatorName || ''
+})
+
+function taskTypeText(type: string): string {
+  const map: Record<string, string> = {
+    dredge: '疏通', repair: '维修', clean: '清掏', inspect: '检查', emergency: '应急',
+    '疏通': '疏通', '维修': '维修', '清掏': '清掏', '检查': '检查', '应急': '应急',
+  }
+  return type ? map[type] || type : ''
+}
+
 function callPhone(phone: string) {
   if (!phone) return
   uni.makePhoneCall({ phoneNumber: phone })
 }
 
+function openMap() {
+  const lat = Number(extra.value.latitude)
+  const lng = Number(extra.value.longitude)
+  if (!lat || !lng) return
+  uni.openLocation({ latitude: lat, longitude: lng, name: task.value?.faultLocation || '故障位置' })
+}
+
+function previewMedia(index: number) {
+  uni.previewImage({ urls: faultMedia.value, current: index })
+}
+
 onLoad((options) => {
   if (options?.id) {
-    taskStore.setCurrentTask(options.id)
+    taskId.value = Number(options.id)
+    loadDetail()
   }
 })
 
+async function loadDetail() {
+  loading.value = true
+  try {
+    const res: any = await getTaskDetail(taskId.value)
+    task.value = res
+    if (res?.projectId) {
+      getProjectDetail(res.projectId).then(p => { project.value = p }).catch(() => {})
+    }
+    if (teamOptions.value.length === 0) {
+      getTeamSimpleList().then(list => { teamOptions.value = list }).catch(() => {})
+    }
+    if (vehicleOptions.value.length === 0) {
+      getVehicleSimpleList().then(list => { vehicleOptions.value = list }).catch(() => {})
+    }
+  } catch (e) {
+    console.error('加载任务详情失败', e)
+    showToast('加载任务详情失败')
+  } finally {
+    loading.value = false
+  }
+}
+
+function auditUser() {
+  return {
+    auditUserId: authStore.userInfo?.id,
+    auditUserName: authStore.userInfo?.name,
+  }
+}
+
 function onTeamChange(e: any) {
-  selectedTeam.value = teamOptions[e.detail.value]
+  selectedTeamId.value = teamOptions.value[Number(e.detail.value)]?.value
 }
 
 function onVehicleChange(e: any) {
-  selectedVehicle.value = vehicleOptions[e.detail.value]
+  selectedVehicleId.value = vehicleOptions.value[Number(e.detail.value)]?.value
 }
 
 async function confirmTask() {
-  showArrangeForm.value = true
+  submitting.value = true
+  try {
+    await approveTask(taskId.value, { ...auditUser() })
+    showToast('审核通过,请安排班组和车辆')
+    showArrangeForm.value = true
+  } catch (e) {
+    console.error('审核通过失败', e)
+  } finally {
+    submitting.value = false
+  }
 }
 
 async function rejectTask() {
-  const res = await showModal({
-    title: '确认驳回',
-    content: '确定要驳回该任务吗?'
+  const res = await new Promise<any>((resolve) => {
+    uni.showModal({
+      title: '驳回任务',
+      editable: true,
+      placeholderText: '请输入驳回原因(可选)',
+      confirmText: '确认驳回',
+      success: resolve,
+      fail: () => resolve({ confirm: false }),
+    })
   })
-  if (res.confirm && task.value) {
-    try {
-      await updateTask(Number(task.value.id), { status: 'rejected' } as any)
-      showToast('任务已驳回')
-    } catch {
-      showToast('操作失败')
-    }
+  if (!res.confirm) return
+  submitting.value = true
+  try {
+    await rejectTaskAudit(taskId.value, { remark: res.content || '', ...auditUser() })
+    showToast('任务已驳回')
+    await loadDetail()
+  } catch (e) {
+    console.error('驳回失败', e)
+  } finally {
+    submitting.value = false
   }
 }
 
 async function arrangeTeam() {
-  if (!selectedTeam.value) {
+  if (!selectedTeamId.value) {
     showToast('请选择班组')
     return
   }
-  if (!selectedVehicle.value) {
+  if (!selectedVehicleId.value) {
     showToast('请选择车辆')
     return
   }
-  if (task.value) {
-    try {
-      await updateTask(Number(task.value.id), { status: 'scheduled' } as any)
-      showToast('排班安排成功')
-      showArrangeForm.value = false
-    } catch {
-      showToast('操作失败')
-    }
+  submitting.value = true
+  try {
+    await dispatchTask(taskId.value, {
+      teamId: selectedTeamId.value,
+      vehicleId: selectedVehicleId.value,
+      remark: auditRemark.value,
+      ...auditUser(),
+    })
+    showToast('安排成功')
+    showArrangeForm.value = false
+    auditRemark.value = ''
+    await loadDetail()
+  } catch (e) {
+    console.error('安排失败', e)
+  } finally {
+    submitting.value = false
   }
 }
 </script>
 
 <style scoped>
+.sub-page {
+  max-width: 430px;
+  margin: 0 auto;
+  min-height: 100vh;
+  background-color: #f6fbf9;
+  padding-bottom: 32px;
+}
+
+.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-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;
+}
+
+.hint-text {
+  font-size: 13px;
+  color: #9ca3af;
+}
+
+.hint-gold {
+  color: #b7791f;
+}
+
+.link-text {
+  color: #368f6f;
+}
+
+.reject-box {
+  padding: 12px;
+  border-radius: 12px;
+  background-color: rgba(239, 68, 68, 0.08);
+}
+
+.reject-text {
+  font-size: 13px;
+  color: #dc2626;
+}
+
+.action-btn {
+  flex: 1;
+  padding: 11px 0;
+  border-radius: 12px;
+  text-align: center;
+  font-size: 14px;
+  font-weight: 600;
+  color: #ffffff;
+}
+
+.action-btn-hover {
+  opacity: 0.88;
+}
+
+.action-btn-reject {
+  background: linear-gradient(135deg, #ef4444 0%, #f87171 100%);
+}
+
+.action-btn-confirm {
+  background: linear-gradient(135deg, #368f6f 0%, #4ba98a 100%);
+}
+
+.action-btn-primary {
+  background: linear-gradient(135deg, #368f6f 0%, #4ba98a 100%);
+}
+
+.action-btn-back {
+  color: #6b7280;
+  background-color: #f3f4f6;
+}
+
+.phone-circle {
+  width: 32px;
+  height: 32px;
+  border-radius: 50%;
+  flex-shrink: 0;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  background-color: rgba(164, 216, 152, 0.24);
+}
+
+.phone-icon {
+  font-size: 14px;
+  color: #368f6f;
+}
+
+.log-dot {
+  width: 12px;
+  height: 12px;
+  border-radius: 50%;
+  background-color: #c4d0cb;
+}
+
+.log-dot-active {
+  background: linear-gradient(135deg, #368f6f 0%, #4ba98a 100%);
+}
+
+.empty-text {
+  font-size: 13px;
+  color: #9ca3af;
+}
+
 .form-group {
   margin-bottom: 16px;
 }
 
+.form-label {
+  display: block;
+  font-size: 14px;
+  color: #6b7280;
+  margin-bottom: 8px;
+}
+
+.form-input {
+  width: 100%;
+  padding: 12px 16px;
+  border: 1px solid #e5e7eb;
+  border-radius: 12px;
+  font-size: 14px;
+  background: #fff;
+}
+
 .form-textarea {
   width: 100%;
   padding: 12px 16px;
   border: 1px solid #e5e7eb;
-  border-radius: 8px;
+  border-radius: 12px;
   font-size: 14px;
   color: #333;
   background: #fff;

+ 183 - 88
src/subPackages/pages-dispatch/vehicleList.vue

@@ -1,62 +1,72 @@
 <template>
-  <view class="app-container pb-20">
+  <view class="sub-page">
     <TopBar title="车辆列表" show-back />
 
-    <!-- 筛选标签 -->
-    <uni-segmented-control
-      :current="currentFilterIndex"
-      :values="filterLabels"
-      style-type="text"
-      active-color="#4f8ef7"
-      @clickItem="onFilterChange"
-    />
-
-    <!-- 车辆列表 -->
-    <view class="mt-2 px-4">
+    <view class="px-4 pt-3">
+      <!-- 筛选 -->
+      <view class="filter-bar mb-3">
+        <view
+          v-for="status in filterStatuses"
+          :key="status.value"
+          class="filter-chip"
+          :class="{ 'filter-chip-active': currentFilter === status.value }"
+          @click="currentFilter = status.value"
+        >
+          {{ status.label }}
+        </view>
+      </view>
+
       <view v-if="loading" class="py-10 text-center">
-        <text class="text-gray-400">加载中...</text>
+        <text class="empty-text">加载中...</text>
       </view>
 
       <view v-else-if="filteredVehicles.length === 0">
         <EmptyState message="暂无车辆" />
       </view>
 
-      <view v-else class="gap-y-3">
+      <view v-else class="glass-card">
         <view
           v-for="vehicle in filteredVehicles"
           :key="vehicle.id"
-          class="card p-4"
+          class="vehicle-row"
+          hover-class="row-hover"
+          :hover-start-time="0"
+          :hover-stay-time="120"
           @click="showVehicleDetail(vehicle)"
         >
-          <view class="flex items-center justify-between mb-3">
-            <view class="flex items-center">
-              <view class="w-10 h-10 rounded-xl flex items-center justify-center mr-3" :class="getStatusBgClass(vehicle.status)">
-                <text class="uni-icons text-lg" :class="getStatusIconClass(vehicle.status)"></text>
+          <view class="flex items-center justify-between">
+            <view class="flex items-center flex-1 min-w-0 mr-2">
+              <view class="icon-chip mr-3">
+                <text class="uni-icons uniui-cart-filled chip-glyph"></text>
               </view>
-              <view>
-                <text class="text-sm font-semibold text-gray-800">{{ vehicle.plateNumber }}</text>
-                <text class="text-xs text-gray-500">{{ vehicle.type }}</text>
+              <view class="min-w-0">
+                <text class="row-title truncate">{{ vehicle.plateNumber }}</text>
+                <text class="row-sub truncate">{{ vehicle.type }}</text>
               </view>
             </view>
-            <text class="tag" :class="getStatusTagClass(vehicle.status)">{{ getStatusText(vehicle.status) }}</text>
+            <text class="status-tag flex-shrink-0" :class="getStatusTagClass(vehicle.status)">
+              {{ getStatusText(vehicle.status) }}
+            </text>
           </view>
 
-          <view class="grid grid-cols-2 gap-y-2 gap-x-4 text-sm">
-            <view class="flex justify-between">
-              <text class="text-gray-400">尺寸</text>
-              <text class="text-gray-700">{{ vehicle.size || '-' }}</text>
-            </view>
-            <view class="flex justify-between">
-              <text class="text-gray-400">载重</text>
-              <text class="text-gray-700">{{ vehicle.loadCapacity || '-' }}</text>
-            </view>
-            <view class="flex justify-between">
-              <text class="text-gray-400">容积</text>
-              <text class="text-gray-700">{{ vehicle.capacity || '-' }}</text>
-            </view>
-            <view class="flex justify-between">
-              <text class="text-gray-400">驾驶员</text>
-              <text class="text-gray-700">{{ vehicle.driver || '未分配' }}</text>
+          <view class="row-divider">
+            <view class="grid grid-cols-2 gap-y-2 gap-x-4">
+              <view class="meta-line">
+                <text class="meta-label">尺寸</text>
+                <text class="meta-value">{{ vehicle.size || '-' }}</text>
+              </view>
+              <view class="meta-line">
+                <text class="meta-label">载重</text>
+                <text class="meta-value">{{ vehicle.loadCapacity || '-' }}</text>
+              </view>
+              <view class="meta-line">
+                <text class="meta-label">容积</text>
+                <text class="meta-value">{{ vehicle.capacity || '-' }}</text>
+              </view>
+              <view class="meta-line">
+                <text class="meta-label">驾驶员</text>
+                <text class="meta-value">{{ vehicle.driver || '未分配' }}</text>
+              </view>
             </view>
           </view>
         </view>
@@ -70,13 +80,12 @@ import { ref, computed, onMounted } from 'vue'
 import { onPullDownRefresh } from '@dcloudio/uni-app'
 import TopBar from '../../components/common/TopBar.vue'
 import EmptyState from '../../components/common/EmptyState.vue'
-import UniSegmentedControl from '../../components/uni-segmented-control/uni-segmented-control.vue'
 import { useVehicleStore } from '../../stores/vehicle'
 import type { Vehicle } from '../../types'
 
 const vehicleStore = useVehicleStore()
 const loading = ref(false)
-const currentFilterIndex = ref(0)
+const currentFilter = ref('all')
 
 const filterStatuses = [
   { value: 'all', label: '全部' },
@@ -85,12 +94,9 @@ const filterStatuses = [
   { value: 'maintenance', label: '维修中' },
 ]
 
-const filterLabels = filterStatuses.map((s) => s.label)
-
 const filteredVehicles = computed(() => {
-  const filterValue = filterStatuses[currentFilterIndex.value].value
-  if (filterValue === 'all') return vehicleStore.vehicles
-  return vehicleStore.vehicles.filter((v) => v.status === filterValue)
+  if (currentFilter.value === 'all') return vehicleStore.vehicles
+  return vehicleStore.vehicles.filter((v) => v.status === currentFilter.value)
 })
 
 function getStatusText(status: string) {
@@ -102,35 +108,13 @@ function getStatusText(status: string) {
   return map[status] || status
 }
 
-function getStatusBgClass(status: string) {
-  const map: Record<string, string> = {
-    available: 'bg-green-50',
-    in_use: 'bg-blue-50',
-    maintenance: 'bg-orange-50',
-  }
-  return map[status] || 'bg-gray-50'
-}
-
-function getStatusIconClass(status: string) {
-  const map: Record<string, string> = {
-    available: 'uniui-checkmarkempty text-green-500',
-    in_use: 'uniui-cart-filled text-blue-500',
-    maintenance: 'uniui-gear-filled text-orange-500',
-  }
-  return map[status] || 'uniui-cart text-gray-400'
-}
-
 function getStatusTagClass(status: string) {
   const map: Record<string, string> = {
-    available: 'tag-green',
-    in_use: 'tag-blue',
-    maintenance: 'tag-orange',
+    available: 'status-tag-green',
+    in_use: 'status-tag-blue',
+    maintenance: 'status-tag-gold',
   }
-  return map[status] || 'tag-gray'
-}
-
-function onFilterChange(index: number) {
-  currentFilterIndex.value = index
+  return map[status] || 'status-tag-gray'
 }
 
 function showVehicleDetail(vehicle: Vehicle) {
@@ -161,7 +145,6 @@ onMounted(async () => {
   }, 300)
 })
 
-// 下拉刷新
 onPullDownRefresh(async () => {
   setTimeout(() => {
     uni.stopPullDownRefresh()
@@ -170,31 +153,143 @@ onPullDownRefresh(async () => {
 </script>
 
 <style scoped>
-.tag {
-  display: inline-block;
-  padding: 2px 8px;
-  border-radius: 4px;
+.sub-page {
+  max-width: 430px;
+  margin: 0 auto;
+  min-height: 100vh;
+  background-color: #f6fbf9;
+  padding-bottom: 24px;
+}
+
+.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;
+}
+
+/* 筛选条:默认白底灰边,激活态绿色 */
+.filter-bar {
+  display: flex;
+  gap: 8px;
+  overflow-x: auto;
+}
+
+.filter-chip {
+  padding: 6px 16px;
+  border-radius: 999px;
+  font-size: 13px;
+  color: #6b7280;
+  background-color: #ffffff;
+  border: 1px solid #e5e7eb;
+  flex-shrink: 0;
+}
+
+.filter-chip-active {
+  color: #ffffff;
+  font-weight: 600;
+  background: linear-gradient(135deg, #368f6f 0%, #4ba98a 100%);
+  border-color: transparent;
+}
+
+.vehicle-row {
+  padding: 14px 16px;
+  border-top: 1px solid rgba(164, 216, 152, 0.18);
+}
+
+.vehicle-row:first-child {
+  border-top: none;
+}
+
+.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;
+}
+
+.row-title {
+  display: block;
+  font-size: 14px;
+  font-weight: 600;
+  color: #1f2937;
+}
+
+.row-sub {
+  display: block;
+  margin-top: 3px;
   font-size: 12px;
-  font-weight: 500;
+  color: #9ca3af;
+}
+
+.status-tag {
+  padding: 2px 10px;
+  border-radius: 999px;
+  font-size: 11px;
+  font-weight: 600;
 }
 
-.tag-green {
-  background: #e6f7f1;
-  color: #059669;
+.status-tag-green {
+  color: #368f6f;
+  background-color: rgba(164, 216, 152, 0.24);
 }
 
-.tag-blue {
-  background: #eff6ff;
+.status-tag-blue {
   color: #2563eb;
+  background-color: rgba(37, 99, 235, 0.1);
 }
 
-.tag-orange {
-  background: #fff7ed;
-  color: #ea580c;
+.status-tag-gold {
+  color: #b7791f;
+  background-color: rgba(251, 211, 141, 0.25);
 }
 
-.tag-gray {
-  background: #f3f4f6;
+.status-tag-gray {
   color: #6b7280;
+  background-color: #f3f4f6;
+}
+
+.row-divider {
+  margin-top: 12px;
+  padding-top: 12px;
+  border-top: 1px solid rgba(164, 216, 152, 0.18);
+}
+
+.meta-line {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+}
+
+.meta-label {
+  font-size: 12px;
+  color: #9ca3af;
+}
+
+.meta-value {
+  font-size: 13px;
+  color: #4b5563;
+}
+
+.empty-text {
+  font-size: 13px;
+  color: #9ca3af;
 }
 </style>

+ 227 - 83
src/subPackages/pages-dispatch/visualization.vue

@@ -1,105 +1,133 @@
 <template>
-  <view class="app-container pb-20">
+  <view class="sub-page">
     <TopBar title="数据可视化" show-back />
-    
+
     <view v-if="loading" class="py-20 text-center">
-      <text class="text-gray-400">加载中...</text>
+      <text class="empty-text">加载中...</text>
     </view>
-    
-    <view v-else class="px-4 py-4 gap-y-4">
+
+    <view v-else class="px-4 pt-3">
       <!-- 任务统计 -->
-      <uni-card title="任务统计" icon="uniui-flag-filled" extra="实时">
+      <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 class="realtime-tag">实时</text>
+        </view>
         <view class="grid grid-cols-2 gap-3">
-          <view class="bg-blue-50 rounded-xl p-3 text-center">
-            <text class="text-xl font-bold text-blue-600 block">{{ stats.task?.total || 0 }}</text>
-            <text class="text-xs text-gray-500">总任务</text>
+          <view class="stat-card">
+            <view class="stat-bar"></view>
+            <text class="stat-value">{{ stats.task?.total || 0 }}</text>
+            <text class="stat-label">总任务</text>
           </view>
-          <view class="bg-yellow-50 rounded-xl p-3 text-center">
-            <text class="text-xl font-bold text-yellow-600 block">{{ stats.task?.pending || 0 }}</text>
-            <text class="text-xs text-gray-500">待处理</text>
+          <view class="stat-card">
+            <view class="stat-bar"></view>
+            <text class="stat-value stat-value-gold">{{ stats.task?.pending || 0 }}</text>
+            <text class="stat-label">待处理</text>
           </view>
-          <view class="bg-green-50 rounded-xl p-3 text-center">
-            <text class="text-xl font-bold text-green-600 block">{{ stats.task?.completed || 0 }}</text>
-            <text class="text-xs text-gray-500">已完成</text>
+          <view class="stat-card">
+            <view class="stat-bar"></view>
+            <text class="stat-value">{{ stats.task?.completed || 0 }}</text>
+            <text class="stat-label">已完成</text>
           </view>
-          <view class="bg-purple-50 rounded-xl p-3 text-center">
-            <text class="text-xl font-bold text-purple-600 block">{{ stats.task?.inProgress || 0 }}</text>
-            <text class="text-xs text-gray-500">进行中</text>
+          <view class="stat-card">
+            <view class="stat-bar"></view>
+            <text class="stat-value stat-value-blue">{{ stats.task?.inProgress || 0 }}</text>
+            <text class="stat-label">进行中</text>
           </view>
         </view>
-      </uni-card>
+      </view>
 
       <!-- 任务分布图表 -->
-      <uni-card title="任务分布" icon="uniui-chart" extra="图表">
+      <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 class="realtime-tag">图表</text>
+        </view>
         <ChartView type="pie" :data="taskPieData" :height="200" />
-      </uni-card>
+      </view>
 
       <!-- 任务趋势 -->
-      <uni-card title="任务趋势" icon="uniui-trend-up" extra="图表">
+      <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 class="realtime-tag">图表</text>
+        </view>
         <ChartView type="line" :data="taskTrendData" :height="200" />
-      </uni-card>
+      </view>
 
       <!-- 车辆统计 -->
-      <uni-card title="车辆状态" icon="uniui-cart-filled" extra="实时">
-        <uni-list border>
-          <uni-list-item 
-            title="总车辆数" 
-            :right-text="String(stats.vehicle?.total || 0)"
-          />
-          <uni-list-item 
-            title="可用" 
-            :right-text="String(stats.vehicle?.available || 0)"
-          />
-          <uni-list-item 
-            title="使用中" 
-            :right-text="String(stats.vehicle?.inUse || 0)"
-          />
-          <uni-list-item 
-            title="维修中" 
-            :right-text="String(stats.vehicle?.maintenance || 0)"
-          />
-        </uni-list>
-      </uni-card>
+      <view class="glass-card">
+        <view class="section-head">
+          <view class="section-bar mr-2"></view>
+          <text class="section-title flex-1">车辆状态</text>
+          <text class="realtime-tag">实时</text>
+        </view>
+        <view class="info-row">
+          <text class="info-label">总车辆数</text>
+          <text class="info-value">{{ stats.vehicle?.total || 0 }}</text>
+        </view>
+        <view class="info-row">
+          <text class="info-label">可用</text>
+          <text class="info-value">{{ stats.vehicle?.available || 0 }}</text>
+        </view>
+        <view class="info-row">
+          <text class="info-label">使用中</text>
+          <text class="info-value">{{ stats.vehicle?.inUse || 0 }}</text>
+        </view>
+        <view class="info-row">
+          <text class="info-label">维修中</text>
+          <text class="info-value">{{ stats.vehicle?.maintenance || 0 }}</text>
+        </view>
+      </view>
 
       <!-- 人员统计 -->
-      <uni-card title="人员状态" icon="uniui-person-filled" extra="实时">
-        <uni-list border>
-          <uni-list-item 
-            title="总人数" 
-            :right-text="String(stats.personnel?.total || 0)"
-          />
-          <uni-list-item 
-            title="在岗" 
-            :right-text="String(stats.personnel?.onDuty || 0)"
-          />
-          <uni-list-item 
-            title="施工中" 
-            :right-text="String(stats.personnel?.working || 0)"
-          />
-          <uni-list-item 
-            title="休息" 
-            :right-text="String(stats.personnel?.rest || 0)"
-          />
-        </uni-list>
-      </uni-card>
+      <view class="glass-card">
+        <view class="section-head">
+          <view class="section-bar mr-2"></view>
+          <text class="section-title flex-1">人员状态</text>
+          <text class="realtime-tag">实时</text>
+        </view>
+        <view class="info-row">
+          <text class="info-label">总人数</text>
+          <text class="info-value">{{ stats.personnel?.total || 0 }}</text>
+        </view>
+        <view class="info-row">
+          <text class="info-label">在岗</text>
+          <text class="info-value">{{ stats.personnel?.onDuty || 0 }}</text>
+        </view>
+        <view class="info-row">
+          <text class="info-label">施工中</text>
+          <text class="info-value">{{ stats.personnel?.working || 0 }}</text>
+        </view>
+        <view class="info-row">
+          <text class="info-label">休息</text>
+          <text class="info-value">{{ stats.personnel?.rest || 0 }}</text>
+        </view>
+      </view>
 
       <!-- 应急统计 -->
-      <uni-card title="应急状态" icon="uniui-notification-filled" extra="实时">
-        <uni-list border>
-          <uni-list-item 
-            title="待命班组" 
-            :right-text="String(stats.emergency?.standbyTeams || 0)"
-          />
-          <uni-list-item 
-            title="待命车辆" 
-            :right-text="String(stats.emergency?.standbyVehicles || 0)"
-          />
-          <uni-list-item 
-            title="应急任务" 
-            :right-text="String(stats.emergency?.emergencyTasks || 0)"
-          />
-        </uni-list>
-      </uni-card>
+      <view class="glass-card">
+        <view class="section-head">
+          <view class="section-bar mr-2"></view>
+          <text class="section-title flex-1">应急状态</text>
+          <text class="realtime-tag">实时</text>
+        </view>
+        <view class="info-row">
+          <text class="info-label">待命班组</text>
+          <text class="info-value">{{ stats.emergency?.standbyTeams || 0 }}</text>
+        </view>
+        <view class="info-row">
+          <text class="info-label">待命车辆</text>
+          <text class="info-value">{{ stats.emergency?.standbyVehicles || 0 }}</text>
+        </view>
+        <view class="info-row">
+          <text class="info-label">应急任务</text>
+          <text class="info-value">{{ stats.emergency?.emergencyTasks || 0 }}</text>
+        </view>
+      </view>
     </view>
   </view>
 </template>
@@ -107,9 +135,6 @@
 <script setup lang="ts">
 import { ref, onMounted } from 'vue'
 import TopBar from '../../components/common/TopBar.vue'
-import UniCard from '../../components/uni-card/uni-card.vue'
-import UniList from '../../components/uni-list/uni-list.vue'
-import UniListItem from '../../components/uni-list/uni-list-item.vue'
 import { getDashboardStats } from '../../api/dashboard'
 
 import ChartView from '../../components/common/ChartView.vue'
@@ -117,7 +142,6 @@ import ChartView from '../../components/common/ChartView.vue'
 const loading = ref(true)
 const stats = ref<Record<string, any>>({})
 
-// 图表数据
 const taskPieData = ref([
   { label: '已完成', value: 42, color: '#34c759' },
   { label: '进行中', value: 15, color: '#4f8ef7' },
@@ -146,3 +170,123 @@ onMounted(async () => {
   }
 })
 </script>
+
+<style scoped>
+.sub-page {
+  max-width: 430px;
+  margin: 0 auto;
+  min-height: 100vh;
+  background-color: #f6fbf9;
+  padding-bottom: 24px;
+}
+
+.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;
+}
+
+.realtime-tag {
+  font-size: 12px;
+  color: #9ca3af;
+}
+
+.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-value-gold {
+  color: #b7791f;
+}
+
+.stat-value-blue {
+  color: #2563eb;
+}
+
+.stat-label {
+  margin-top: 6px;
+  font-size: 12px;
+  color: #6b7280;
+}
+
+.info-row {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  padding: 13px 16px;
+  border-top: 1px solid rgba(164, 216, 152, 0.18);
+}
+
+.info-label {
+  font-size: 14px;
+  color: #4b5563;
+}
+
+.info-value {
+  font-size: 14px;
+  font-weight: 600;
+  color: #368f6f;
+}
+
+.empty-text {
+  font-size: 13px;
+  color: #9ca3af;
+}
+</style>

+ 172 - 45
src/subPackages/pages-sales/knowledgeDetail.vue

@@ -1,54 +1,58 @@
 <template>
-  <view class="app-container pb-20">
+  <view class="sub-page">
     <TopBar title="知识详情" show-back />
-    
-    <view v-if="knowledge" class="px-4 py-4 gap-y-4">
+
+    <view v-if="knowledge" class="px-4 pt-3">
       <!-- 知识标题 -->
-      <uni-card :is-shadow="true">
-        <view class="mb-3">
-          <text class="text-lg font-semibold text-gray-800 block">{{ knowledge.title }}</text>
-          <view class="flex items-center mt-2 gap-4">
-            <view class="px-2 py-1 bg-blue-50 rounded-md">
-              <text class="text-xs text-blue-500">{{ knowledge.category }}</text>
-            </view>
-            <view class="flex items-center text-xs text-gray-400">
-              <text class="uni-icons uniui-calendar-filled mr-1"></text>
-              {{ formatDate(knowledge.createTime) }}
-            </view>
-            <view class="flex items-center text-xs text-gray-400">
-              <text class="uni-icons uniui-eye-filled mr-1"></text>
-              {{ knowledge.viewCount }}
-            </view>
+      <view class="glass-card p-5">
+        <text class="notice-title">{{ knowledge.title }}</text>
+        <view class="flex items-center mt-3 gap-4">
+          <text class="type-tag">{{ knowledge.category }}</text>
+          <view class="flex items-center">
+            <text class="uni-icons uniui-calendar-filled meta-icon mr-1"></text>
+            <text class="meta-text">{{ formatDate(knowledge.createTime) }}</text>
+          </view>
+          <view class="flex items-center">
+            <text class="uni-icons uniui-eye-filled meta-icon mr-1"></text>
+            <text class="meta-text">{{ knowledge.viewCount }}</text>
           </view>
         </view>
-      </uni-card>
+      </view>
 
       <!-- 知识内容 -->
-      <uni-card title="正文内容" icon="uniui-compose" :is-shadow="true">
-        <view class="text-gray-700 text-sm leading-relaxed gap-y-3">
-          <text class="block">{{ knowledge.content }}</text>
+      <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>
-      </uni-card>
+        <text class="body-p">{{ knowledge.content }}</text>
+      </view>
 
       <!-- 附件列表 -->
-      <uni-card v-if="knowledge.attachments?.length" title="相关附件" icon="uniui-folder-add-filled" :is-shadow="true">
-        <uni-list border>
-          <uni-list-item 
-            v-for="(file, index) in knowledge.attachments" 
-            :key="index"
-            :title="file.name"
-            :note="file.size"
-            show-arrow
-            @click="downloadFile(file.url)"
-          >
-            <template #header>
-              <view class="w-10 h-10 rounded-xl bg-blue-50 flex items-center justify-center mr-3">
-                <text class="uni-icons uniui-download-filled text-blue-500"></text>
-              </view>
-            </template>
-          </uni-list-item>
-        </uni-list>
-      </uni-card>
+      <view v-if="knowledge.attachments?.length" class="glass-card">
+        <view class="section-head">
+          <view class="section-bar mr-2"></view>
+          <text class="section-title flex-1">相关附件</text>
+        </view>
+        <view
+          v-for="(file, index) in knowledge.attachments"
+          :key="index"
+          class="list-row"
+          hover-class="row-hover"
+          :hover-start-time="0"
+          :hover-stay-time="120"
+          @click="downloadFile(file.url)"
+        >
+          <view class="icon-chip mr-3">
+            <text class="uni-icons uniui-download-filled chip-glyph"></text>
+          </view>
+          <view class="flex-1 min-w-0 mr-2">
+            <text class="row-title truncate">{{ file.name }}</text>
+            <text class="row-sub truncate">{{ file.size }}</text>
+          </view>
+          <text class="uni-icons uniui-arrowright row-arrow"></text>
+        </view>
+      </view>
     </view>
 
     <view v-else class="py-20 text-center">
@@ -61,9 +65,6 @@
 import { ref, onMounted } from 'vue'
 import TopBar from '../../components/common/TopBar.vue'
 import EmptyState from '../../components/common/EmptyState.vue'
-import UniCard from '../../components/uni-card/uni-card.vue'
-import UniList from '../../components/uni-list/uni-list.vue'
-import UniListItem from '../../components/uni-list/uni-list-item.vue'
 import { formatDate } from '../../utils'
 
 const knowledge = ref<any>(null)
@@ -73,7 +74,6 @@ function downloadFile(url: string) {
 }
 
 onMounted(() => {
-  // 模拟数据
   knowledge.value = {
     id: '1',
     title: '化粪池清掏操作规范',
@@ -88,3 +88,130 @@ onMounted(() => {
   }
 })
 </script>
+
+<style scoped>
+.sub-page {
+  max-width: 430px;
+  margin: 0 auto;
+  min-height: 100vh;
+  background-color: #f6fbf9;
+  padding-bottom: 24px;
+}
+
+.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;
+}
+
+.notice-title {
+  display: block;
+  font-size: 17px;
+  font-weight: 700;
+  color: #1f2937;
+  line-height: 1.4;
+}
+
+.type-tag {
+  padding: 2px 10px;
+  border-radius: 999px;
+  font-size: 11px;
+  font-weight: 600;
+  color: #368f6f;
+  background-color: rgba(164, 216, 152, 0.24);
+}
+
+.meta-icon {
+  font-size: 12px;
+  color: #9ca3af;
+}
+
+.meta-text {
+  font-size: 12px;
+  color: #9ca3af;
+}
+
+.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;
+}
+
+.body-p {
+  display: block;
+  font-size: 14px;
+  line-height: 1.8;
+  color: #4b5563;
+  white-space: pre-wrap;
+}
+
+.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);
+}
+
+.row-arrow {
+  font-size: 16px;
+  color: #c4d0cb;
+  flex-shrink: 0;
+}
+
+.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;
+}
+</style>

+ 162 - 44
src/subPackages/pages-sales/knowledgeList.vue

@@ -1,68 +1,65 @@
 <template>
-  <view class="app-container pb-20">
+  <view class="sub-page">
     <TopBar title="行业知识" show-back />
-    
-    <!-- 搜索栏 -->
-    <view class="px-4 py-3">
-      <view class="relative">
-        <text class="uni-icons uniui-search absolute left-3 text-gray-400" style="top: 50%; transform: translateY(-50%);"></text>
-        <input 
+
+    <view class="px-4 pt-3">
+      <!-- 搜索栏:默认白底灰边 -->
+      <view class="search-bar mb-3">
+        <text class="uni-icons uniui-search search-icon"></text>
+        <input
           v-model="searchText"
-          class="w-full pl-10 pr-4 py-2 bg-gray-100 rounded-xl text-sm text-gray-800"
+          class="search-input"
           placeholder="搜索知识标题或关键词"
         />
       </view>
-    </view>
 
-    <!-- 分类筛选 -->
-    <view class="px-4 pb-2">
-      <view class="flex gap-2 overflow-x-auto">
-        <view 
-          v-for="(cat, index) in categories" 
-          :key="index"
-          class="px-3 py-1 rounded-full text-sm whitespace-nowrap"
-          :class="currentCategory === cat.value ? 'bg-blue-500 text-white' : 'bg-gray-100 text-gray-600'"
+      <!-- 分类筛选 -->
+      <view class="filter-bar mb-3">
+        <view
+          v-for="cat in categories"
+          :key="cat.value"
+          class="filter-chip"
+          :class="{ 'filter-chip-active': currentCategory === cat.value }"
           @click="currentCategory = cat.value"
         >
           {{ cat.label }}
         </view>
       </view>
-    </view>
 
-    <!-- 知识列表 -->
-    <view class="px-4 mt-2">
       <view v-if="loading" class="py-10 text-center">
-        <text class="text-gray-400">加载中...</text>
+        <text class="empty-text">加载中...</text>
       </view>
-      
+
       <view v-else-if="filteredKnowledges.length === 0">
         <EmptyState message="暂无知识内容" />
       </view>
-      
-      <view v-else class="gap-y-3">
-        <uni-card 
-          v-for="item in filteredKnowledges" 
+
+      <view v-else class="glass-card">
+        <view
+          v-for="item in filteredKnowledges"
           :key="item.id"
-          :title="item.title"
-          :extra="item.category"
-          icon="uniui-folder-add-filled"
-          :is-shadow="true"
+          class="notice-row"
+          hover-class="row-hover"
+          :hover-start-time="0"
+          :hover-stay-time="120"
           @click="goToDetail(item.id)"
         >
-          <text class="text-sm text-gray-500 line-clamp-2">{{ item.summary }}</text>
-          <template #footer>
-            <view class="flex justify-between items-center">
-              <view class="flex items-center text-xs text-gray-400">
-                <text class="uni-icons uniui-calendar-filled mr-1"></text>
-                {{ formatDate(item.createTime) }}
-              </view>
-              <view class="flex items-center text-xs text-gray-400">
-                <text class="uni-icons uniui-eye-filled mr-1"></text>
-                {{ item.viewCount }}
-              </view>
+          <view class="flex items-center justify-between mb-2">
+            <text class="row-title truncate flex-1 min-w-0 mr-2">{{ item.title }}</text>
+            <text class="type-tag flex-shrink-0">{{ item.category }}</text>
+          </view>
+          <text class="row-sub notice-content">{{ item.summary }}</text>
+          <view class="flex items-center justify-between mt-2">
+            <view class="flex items-center">
+              <text class="uni-icons uniui-calendar-filled meta-icon mr-1"></text>
+              <text class="row-date">{{ formatDate(item.createTime) }}</text>
+            </view>
+            <view class="flex items-center">
+              <text class="uni-icons uniui-eye-filled meta-icon mr-1"></text>
+              <text class="row-date">{{ item.viewCount }}</text>
             </view>
-          </template>
-        </uni-card>
+          </view>
+        </view>
       </view>
     </view>
   </view>
@@ -72,7 +69,6 @@
 import { ref, computed, onMounted } from 'vue'
 import TopBar from '../../components/common/TopBar.vue'
 import EmptyState from '../../components/common/EmptyState.vue'
-import UniCard from '../../components/uni-card/uni-card.vue'
 import { formatDate } from '../../utils'
 
 const loading = ref(false)
@@ -114,3 +110,125 @@ onMounted(() => {
   loading.value = false
 })
 </script>
+
+<style scoped>
+.sub-page {
+  max-width: 430px;
+  margin: 0 auto;
+  min-height: 100vh;
+  background-color: #f6fbf9;
+  padding-bottom: 24px;
+}
+
+.search-bar {
+  display: flex;
+  align-items: center;
+  padding: 0 12px;
+  height: 40px;
+  border-radius: 999px;
+  background-color: #ffffff;
+  border: 1px solid #e5e7eb;
+}
+
+.search-icon {
+  font-size: 16px;
+  color: #9ca3af;
+  margin-right: 8px;
+}
+
+.search-input {
+  flex: 1;
+  font-size: 14px;
+  color: #1f2937;
+}
+
+/* 筛选条:默认白底灰边,激活态绿色 */
+.filter-bar {
+  display: flex;
+  gap: 8px;
+  overflow-x: auto;
+}
+
+.filter-chip {
+  padding: 6px 16px;
+  border-radius: 999px;
+  font-size: 13px;
+  color: #6b7280;
+  background-color: #ffffff;
+  border: 1px solid #e5e7eb;
+  flex-shrink: 0;
+}
+
+.filter-chip-active {
+  color: #ffffff;
+  font-weight: 600;
+  background: linear-gradient(135deg, #368f6f 0%, #4ba98a 100%);
+  border-color: transparent;
+}
+
+.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;
+}
+
+.notice-row {
+  padding: 14px 16px;
+  border-top: 1px solid rgba(164, 216, 152, 0.18);
+}
+
+.notice-row:first-child {
+  border-top: none;
+}
+
+.row-hover {
+  background-color: rgba(164, 216, 152, 0.1);
+}
+
+.row-title {
+  font-size: 14px;
+  font-weight: 600;
+  color: #1f2937;
+}
+
+.row-sub {
+  font-size: 12px;
+  color: #9ca3af;
+}
+
+.notice-content {
+  display: -webkit-box;
+  -webkit-line-clamp: 2;
+  -webkit-box-orient: vertical;
+  overflow: hidden;
+}
+
+.row-date {
+  font-size: 12px;
+  color: #9ca3af;
+}
+
+.meta-icon {
+  font-size: 12px;
+  color: #9ca3af;
+}
+
+.type-tag {
+  padding: 2px 10px;
+  border-radius: 999px;
+  font-size: 11px;
+  font-weight: 600;
+  color: #368f6f;
+  background-color: rgba(164, 216, 152, 0.24);
+}
+
+.empty-text {
+  font-size: 13px;
+  color: #9ca3af;
+}
+</style>

+ 144 - 41
src/subPackages/pages-sales/messageDetail.vue

@@ -1,39 +1,37 @@
 <template>
-  <view class="app-container pb-20">
+  <view class="sub-page">
     <TopBar title="消息详情" show-back />
-    
-    <view v-if="message" class="px-4 py-4 gap-y-4">
+
+    <view v-if="message" class="px-4 pt-3">
       <!-- 消息头部 -->
-      <uni-card :is-shadow="true">
+      <view class="glass-card p-5">
         <view class="flex items-start">
-          <view class="w-12 h-12 rounded-xl flex items-center justify-center mr-4" :class="getTypeBgClass(message.type)">
-            <text class="uni-icons text-xl" :class="getTypeIconClass(message.type)"></text>
-          </view>
-          <view class="flex-1">
-            <text class="text-lg font-semibold text-gray-800 block">{{ message.title }}</text>
-            <text class="text-sm text-gray-500 mt-1 block">{{ formatDate(message.createTime) }}</text>
+          <view class="icon-chip mr-3" :class="{ 'icon-chip-red': message.type === 'urgent' }">
+            <text class="uni-icons chip-glyph" :class="getTypeIcon(message.type)"></text>
           </view>
-          <view v-if="!message.read" class="px-2 py-1 bg-red-100 rounded-lg">
-            <text class="text-xs text-red-500">未读</text>
+          <view class="flex-1 min-w-0">
+            <text class="notice-title">{{ message.title }}</text>
+            <text class="meta-text mt-1 block">{{ formatDate(message.createTime) }}</text>
           </view>
+          <text v-if="!message.read" class="unread-tag flex-shrink-0">未读</text>
         </view>
-      </uni-card>
+      </view>
 
       <!-- 消息内容 -->
-      <uni-card title="消息内容" icon="uniui-chatbubble-filled" :is-shadow="true">
-        <text class="text-gray-700 text-sm leading-relaxed">{{ message.content }}</text>
-      </uni-card>
+      <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>
+        <text class="body-p">{{ message.content }}</text>
+      </view>
 
       <!-- 操作按钮 -->
-      <view class="pt-2 gap-y-3">
-        <button class="w-full py-3 bg-blue-500 text-white rounded-xl font-medium" @click="markAsRead">
-          <text class="uni-icons uniui-checkmarkempty mr-1"></text>
-          标记已读
-        </button>
-        <button class="w-full py-3 bg-gray-200 text-gray-700 rounded-xl font-medium" @click="goToTaskDetail">
-          <text class="uni-icons uniui-flag-filled mr-1"></text>
-          查看关联任务
-        </button>
+      <view class="action-btn action-btn-primary mb-3" hover-class="action-btn-hover" :hover-start-time="0" :hover-stay-time="120" @click="markAsRead">
+        标记已读
+      </view>
+      <view class="action-btn action-btn-plain" hover-class="action-btn-hover" :hover-start-time="0" :hover-stay-time="120" @click="goToTaskDetail">
+        查看关联任务
       </view>
     </view>
 
@@ -47,27 +45,17 @@
 import { ref, onMounted } from 'vue'
 import TopBar from '../../components/common/TopBar.vue'
 import EmptyState from '../../components/common/EmptyState.vue'
-import UniCard from '../../components/uni-card/uni-card.vue'
 import { formatDate } from '../../utils'
 
 const message = ref<any>(null)
 
-function getTypeBgClass(type: string) {
+function getTypeIcon(type: string) {
   const map: Record<string, string> = {
-    task: 'bg-blue-50',
-    system: 'bg-gray-50',
-    urgent: 'bg-red-50',
+    task: 'uniui-flag-filled',
+    system: 'uniui-gear-filled',
+    urgent: 'uniui-notification-filled',
   }
-  return map[type] || 'bg-gray-50'
-}
-
-function getTypeIconClass(type: string) {
-  const map: Record<string, string> = {
-    task: 'uniui-flag-filled text-blue-500',
-    system: 'uniui-gear-filled text-gray-500',
-    urgent: 'uniui-notification-filled text-red-500',
-  }
-  return map[type] || 'uniui-chatbubble text-gray-400'
+  return map[type] || 'uniui-chatbubble'
 }
 
 function markAsRead() {
@@ -82,7 +70,6 @@ function goToTaskDetail() {
 }
 
 onMounted(() => {
-  // 模拟数据
   message.value = {
     id: '1',
     title: '新任务分配',
@@ -93,3 +80,119 @@ onMounted(() => {
   }
 })
 </script>
+
+<style scoped>
+.sub-page {
+  max-width: 430px;
+  margin: 0 auto;
+  min-height: 100vh;
+  background-color: #f6fbf9;
+  padding-bottom: 24px;
+}
+
+.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;
+}
+
+.notice-title {
+  display: block;
+  font-size: 17px;
+  font-weight: 700;
+  color: #1f2937;
+  line-height: 1.4;
+}
+
+.meta-text {
+  font-size: 12px;
+  color: #9ca3af;
+}
+
+.unread-tag {
+  padding: 2px 10px;
+  border-radius: 999px;
+  font-size: 11px;
+  font-weight: 600;
+  color: #ef4444;
+  background-color: rgba(239, 68, 68, 0.1);
+}
+
+.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;
+}
+
+.body-p {
+  display: block;
+  font-size: 14px;
+  line-height: 1.8;
+  color: #4b5563;
+}
+
+.icon-chip {
+  width: 48px;
+  height: 48px;
+  border-radius: 14px;
+  flex-shrink: 0;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  background-color: rgba(164, 216, 152, 0.2);
+}
+
+.icon-chip-red {
+  background-color: rgba(239, 68, 68, 0.1);
+}
+
+.icon-chip-red .chip-glyph {
+  color: #ef4444;
+}
+
+.chip-glyph {
+  font-size: 22px;
+  color: #368f6f;
+}
+
+.action-btn {
+  width: 100%;
+  padding: 12px 0;
+  border-radius: 14px;
+  text-align: center;
+  font-size: 15px;
+  font-weight: 600;
+}
+
+.action-btn-hover {
+  opacity: 0.88;
+}
+
+.action-btn-primary {
+  color: #ffffff;
+  background: linear-gradient(135deg, #368f6f 0%, #4ba98a 100%);
+  box-shadow: 0 10px 24px -10px rgba(54, 143, 111, 0.7);
+}
+
+.action-btn-plain {
+  color: #6b7280;
+  background-color: #f3f4f6;
+}
+</style>

+ 166 - 61
src/subPackages/pages-sales/messageList.vue

@@ -1,53 +1,56 @@
 <template>
-  <view class="app-container pb-20">
+  <view class="sub-page">
     <TopBar title="消息通知" show-back />
-    
-    <!-- 筛选标签 -->
-    <view class="bg-white px-4 py-2">
-      <uni-segmented-control 
-        :current="currentFilterIndex" 
-        :values="filterLabels"
-        style-type="text"
-        active-color="#4f8ef7"
-        @clickItem="onFilterChange"
-      />
-    </view>
 
-    <!-- 消息列表 -->
-    <view class="px-4 mt-2">
+    <view class="px-4 pt-3">
+      <!-- 筛选 -->
+      <view class="filter-bar mb-3">
+        <view
+          v-for="type in filterTypes"
+          :key="type.value"
+          class="filter-chip"
+          :class="{ 'filter-chip-active': currentFilter === type.value }"
+          @click="currentFilter = type.value"
+        >
+          {{ type.label }}
+        </view>
+      </view>
+
       <view v-if="loading" class="py-10 text-center">
-        <text class="text-gray-400">加载中...</text>
+        <text class="empty-text">加载中...</text>
       </view>
-      
+
       <view v-else-if="filteredMessages.length === 0">
         <EmptyState message="暂无消息" />
       </view>
-      
-      <uni-list v-else border>
+
+      <view v-else class="glass-card">
         <uni-swipe-action
           v-for="msg in filteredMessages"
           :key="msg.id"
           :actions="[{text: '删除', color: '#ff3b30', key: 'delete'}]"
           @click="onSwipeAction(msg.id, $event)"
         >
-          <uni-list-item 
-            :title="msg.title"
-            :note="msg.content.length > 20 ? msg.content.substring(0, 20) + '...' : msg.content"
-            :right-text="formatTime(msg.createTime)"
-            show-arrow
+          <view
+            class="list-row"
+            hover-class="row-hover"
+            :hover-start-time="0"
+            :hover-stay-time="120"
             @click="goToDetail(msg.id)"
           >
-            <template #header>
-              <view class="relative mr-3">
-                <view class="w-10 h-10 rounded-xl flex items-center justify-center" :class="getTypeBgClass(msg.type)">
-                  <text class="uni-icons text-lg" :class="getTypeIconClass(msg.type)"></text>
-                </view>
-                <view v-if="!msg.read" class="absolute -top-1 -right-1 w-3 h-3 bg-red-500 rounded-full border-2 border-white"></view>
-              </view>
-            </template>
-          </uni-list-item>
+            <view class="icon-chip mr-3" :class="{ 'icon-chip-red': msg.type === 'urgent' }">
+              <text class="uni-icons chip-glyph" :class="getTypeIcon(msg.type)"></text>
+              <view v-if="!msg.read" class="unread-dot"></view>
+            </view>
+            <view class="flex-1 min-w-0 mr-2">
+              <text class="row-title truncate">{{ msg.title }}</text>
+              <text class="row-sub truncate">{{ msg.content.length > 20 ? msg.content.substring(0, 20) + '...' : msg.content }}</text>
+            </view>
+            <text class="row-date flex-shrink-0 mr-1">{{ formatTime(msg.createTime) }}</text>
+            <text class="uni-icons uniui-arrowright row-arrow"></text>
+          </view>
         </uni-swipe-action>
-      </uni-list>
+      </view>
     </view>
   </view>
 </template>
@@ -56,15 +59,12 @@
 import { ref, computed, onMounted } from 'vue'
 import TopBar from '../../components/common/TopBar.vue'
 import EmptyState from '../../components/common/EmptyState.vue'
-import UniList from '../../components/uni-list/uni-list.vue'
-import UniListItem from '../../components/uni-list/uni-list-item.vue'
 import UniSwipeAction from '../../components/uni-swipe-action/uni-swipe-action.vue'
-import UniSegmentedControl from '../../components/uni-segmented-control/uni-segmented-control.vue'
 import { formatDate } from '../../utils'
 
 const loading = ref(false)
 const messages = ref<any[]>([])
-const currentFilterIndex = ref(0)
+const currentFilter = ref('all')
 
 const filterTypes = [
   { value: 'all', label: '全部' },
@@ -73,34 +73,18 @@ const filterTypes = [
   { value: 'urgent', label: '紧急' },
 ]
 
-const filterLabels = filterTypes.map(t => t.label)
-
 const filteredMessages = computed(() => {
-  const filterValue = filterTypes[currentFilterIndex.value].value
-  if (filterValue === 'all') return messages.value
-  return messages.value.filter(m => m.type === filterValue)
+  if (currentFilter.value === 'all') return messages.value
+  return messages.value.filter(m => m.type === currentFilter.value)
 })
 
-function getTypeBgClass(type: string) {
-  const map: Record<string, string> = {
-    task: 'bg-blue-50',
-    system: 'bg-gray-50',
-    urgent: 'bg-red-50',
-  }
-  return map[type] || 'bg-gray-50'
-}
-
-function getTypeIconClass(type: string) {
+function getTypeIcon(type: string) {
   const map: Record<string, string> = {
-    task: 'uniui-flag-filled text-blue-500',
-    system: 'uniui-gear-filled text-gray-500',
-    urgent: 'uniui-notification-filled text-red-500',
+    task: 'uniui-flag-filled',
+    system: 'uniui-gear-filled',
+    urgent: 'uniui-notification-filled',
   }
-  return map[type] || 'uniui-chatbubble text-gray-400'
-}
-
-function onFilterChange(index: number) {
-  currentFilterIndex.value = index
+  return map[type] || 'uniui-chatbubble'
 }
 
 function formatTime(date: string) {
@@ -127,7 +111,6 @@ function goToDetail(msgId: string) {
 }
 
 onMounted(() => {
-  // 模拟数据
   messages.value = [
     { id: '1', title: '新任务分配', content: '您有一条新的清掏任务待处理', type: 'task', read: false, createTime: '2026-06-22 14:30' },
     { id: '2', title: '任务即将到期', content: '积丹白鹭小区任务将于明日到期', type: 'urgent', read: false, createTime: '2026-06-22 10:00' },
@@ -136,3 +119,125 @@ onMounted(() => {
   ]
 })
 </script>
+
+<style scoped>
+.sub-page {
+  max-width: 430px;
+  margin: 0 auto;
+  min-height: 100vh;
+  background-color: #f6fbf9;
+  padding-bottom: 24px;
+}
+
+/* 筛选条:默认白底灰边,激活态绿色 */
+.filter-bar {
+  display: flex;
+  gap: 8px;
+  overflow-x: auto;
+}
+
+.filter-chip {
+  padding: 6px 16px;
+  border-radius: 999px;
+  font-size: 13px;
+  color: #6b7280;
+  background-color: #ffffff;
+  border: 1px solid #e5e7eb;
+  flex-shrink: 0;
+}
+
+.filter-chip-active {
+  color: #ffffff;
+  font-weight: 600;
+  background: linear-gradient(135deg, #368f6f 0%, #4ba98a 100%);
+  border-color: transparent;
+}
+
+.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;
+}
+
+.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-date {
+  font-size: 12px;
+  color: #9ca3af;
+}
+
+.row-hover {
+  background-color: rgba(164, 216, 152, 0.1);
+}
+
+.row-arrow {
+  font-size: 16px;
+  color: #c4d0cb;
+  flex-shrink: 0;
+}
+
+.icon-chip {
+  position: relative;
+  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);
+}
+
+.icon-chip-red {
+  background-color: rgba(239, 68, 68, 0.1);
+}
+
+.icon-chip-red .chip-glyph {
+  color: #ef4444;
+}
+
+.chip-glyph {
+  font-size: 20px;
+  color: #368f6f;
+}
+
+.unread-dot {
+  position: absolute;
+  top: -3px;
+  right: -3px;
+  width: 10px;
+  height: 10px;
+  border-radius: 50%;
+  background-color: #ef4444;
+  border: 2px solid #ffffff;
+}
+
+.empty-text {
+  font-size: 13px;
+  color: #9ca3af;
+}
+</style>

+ 119 - 25
src/subPackages/pages-sales/notification.vue

@@ -1,36 +1,38 @@
 <template>
-  <view class="app-container pb-20">
+  <view class="sub-page">
     <TopBar title="通知消息" show-back />
-    
-    <view class="px-4 mt-3">
+
+    <view class="px-4 pt-3">
       <view v-if="loading" class="py-10 text-center">
-        <text class="text-gray-400">加载中...</text>
+        <text class="empty-text">加载中...</text>
       </view>
-      
+
       <view v-else-if="noticeList.length === 0">
         <EmptyState message="暂无通知" />
       </view>
-      
-      <uni-list v-else border>
-        <uni-list-item 
-          v-for="notice in noticeList" 
+
+      <view v-else class="glass-card">
+        <view
+          v-for="notice in noticeList"
           :key="notice.noticeId"
-          :title="notice.title"
-          :note="notice.content"
-          :right-text="formatDate(notice.publishTime)"
-          show-arrow
+          class="list-row"
+          hover-class="row-hover"
+          :hover-start-time="0"
+          :hover-stay-time="120"
           @click="goToDetail(notice.noticeId)"
         >
-          <template #header>
-            <view class="relative mr-3">
-              <view class="w-10 h-10 rounded-xl bg-blue-50 flex items-center justify-center">
-                <text class="uni-icons uniui-notification-filled text-blue-500"></text>
-              </view>
-              <view v-if="!notice.read" class="absolute -top-1 -right-1 w-3 h-3 bg-red-500 rounded-full border-2 border-white"></view>
-            </view>
-          </template>
-        </uni-list-item>
-      </uni-list>
+          <view class="icon-chip mr-3">
+            <text class="uni-icons uniui-notification-filled chip-glyph"></text>
+            <view v-if="!notice.read" class="unread-dot"></view>
+          </view>
+          <view class="flex-1 min-w-0 mr-2">
+            <text class="row-title truncate">{{ notice.title }}</text>
+            <text class="row-sub truncate">{{ notice.content }}</text>
+          </view>
+          <text class="row-date flex-shrink-0 mr-1">{{ formatDate(notice.publishTime) }}</text>
+          <text class="uni-icons uniui-arrowright row-arrow"></text>
+        </view>
+      </view>
     </view>
   </view>
 </template>
@@ -39,8 +41,6 @@
 import { ref, onMounted } from 'vue'
 import TopBar from '../../components/common/TopBar.vue'
 import EmptyState from '../../components/common/EmptyState.vue'
-import UniList from '../../components/uni-list/uni-list.vue'
-import UniListItem from '../../components/uni-list/uni-list-item.vue'
 import { getNoticeList } from '../../api/notice'
 import { formatDate } from '../../utils'
 
@@ -63,3 +63,97 @@ onMounted(async () => {
   }
 })
 </script>
+
+<style scoped>
+.sub-page {
+  max-width: 430px;
+  margin: 0 auto;
+  min-height: 100vh;
+  background-color: #f6fbf9;
+  padding-bottom: 24px;
+}
+
+.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;
+}
+
+.list-row {
+  display: flex;
+  align-items: center;
+  padding: 13px 16px;
+  border-top: 1px solid rgba(164, 216, 152, 0.18);
+}
+
+.list-row:first-child {
+  border-top: none;
+}
+
+.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-date {
+  font-size: 12px;
+  color: #9ca3af;
+}
+
+.row-hover {
+  background-color: rgba(164, 216, 152, 0.1);
+}
+
+.row-arrow {
+  font-size: 16px;
+  color: #c4d0cb;
+  flex-shrink: 0;
+}
+
+.icon-chip {
+  position: relative;
+  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;
+}
+
+.unread-dot {
+  position: absolute;
+  top: -3px;
+  right: -3px;
+  width: 10px;
+  height: 10px;
+  border-radius: 50%;
+  background-color: #ef4444;
+  border: 2px solid #ffffff;
+}
+
+.empty-text {
+  font-size: 13px;
+  color: #9ca3af;
+}
+</style>

+ 147 - 33
src/subPackages/pages-sales/taskReminder.vue

@@ -1,48 +1,54 @@
 <template>
-  <view class="app-container pb-20">
+  <view class="sub-page">
     <TopBar title="任务提醒" show-back />
-    
-    <view class="px-4 mt-3">
+
+    <view class="px-4 pt-3">
       <view v-if="taskStore.loading" class="py-10 text-center">
-        <text class="text-gray-400">加载中...</text>
+        <text class="empty-text">加载中...</text>
       </view>
-      
+
       <view v-else-if="reminderTasks.length === 0">
         <EmptyState message="暂无任务提醒" />
       </view>
-      
-      <view v-else class="gap-y-3">
-        <uni-card 
-          v-for="task in reminderTasks" 
+
+      <view v-else class="glass-card">
+        <view
+          v-for="task in reminderTasks"
           :key="task.id"
-          :title="task.projectName"
-          icon="uniui-notification-filled"
-          extra="待确认"
-          extra-color="#ff3b30"
+          class="task-row"
+          hover-class="row-hover"
+          :hover-start-time="0"
+          :hover-stay-time="120"
           @click="goToDetail(task.id)"
         >
-          <view class="text-sm text-gray-500 mb-1">
-            <text class="uni-icons uniui-location-filled text-gray-400 mr-1"></text>
-            {{ task.address }}
+          <view class="flex items-center justify-between mb-2">
+            <view class="flex items-center flex-1 min-w-0 mr-2">
+              <view class="icon-chip mr-3">
+                <text class="uni-icons uniui-notification-filled chip-glyph"></text>
+              </view>
+              <text class="row-title truncate">{{ task.projectName }}</text>
+            </view>
+            <text class="pending-tag flex-shrink-0">待确认</text>
+          </view>
+
+          <view class="meta-line mb-1">
+            <text class="uni-icons uniui-location-filled meta-icon mr-1"></text>
+            <text class="meta-text truncate">{{ task.address }}</text>
           </view>
-          <view class="text-sm text-gray-500 mb-2">
-            <text class="uni-icons uniui-calendar-filled text-gray-400 mr-1"></text>
-            预计: {{ task.scheduleDate }} {{ task.scheduleTime }}
+          <view class="meta-line mb-3">
+            <text class="uni-icons uniui-calendar-filled meta-icon mr-1"></text>
+            <text class="meta-text">预计: {{ task.scheduleDate }} {{ task.scheduleTime }}</text>
           </view>
-          
-          <template #footer>
-            <view class="flex gap-2">
-              <button class="flex-1 py-2 bg-blue-500 text-white text-xs rounded-lg" @click.stop="confirmTask(task.id)">
-                <text class="uni-icons uniui-checkmarkempty mr-1"></text>
-                确认
-              </button>
-              <button class="flex-1 py-2 bg-gray-200 text-gray-600 text-xs rounded-lg" @click.stop="rejectTask(task.id)">
-                <text class="uni-icons uniui-close mr-1"></text>
-                拒绝
-              </button>
+
+          <view class="flex gap-3">
+            <view class="action-btn action-btn-confirm" hover-class="action-btn-hover" :hover-start-time="0" :hover-stay-time="120" @click.stop="confirmTask(task.id)">
+              确认
+            </view>
+            <view class="action-btn action-btn-reject" hover-class="action-btn-hover" :hover-start-time="0" :hover-stay-time="120" @click.stop="rejectTask(task.id)">
+              拒绝
             </view>
-          </template>
-        </uni-card>
+          </view>
+        </view>
       </view>
     </view>
   </view>
@@ -53,7 +59,6 @@ import { computed, onMounted } from 'vue'
 import { useTaskStore } from '../../stores/task'
 import TopBar from '../../components/common/TopBar.vue'
 import EmptyState from '../../components/common/EmptyState.vue'
-import UniCard from '../../components/uni-card/uni-card.vue'
 
 const taskStore = useTaskStore()
 
@@ -94,3 +99,112 @@ onMounted(() => {
   taskStore.fetchTasks()
 })
 </script>
+
+<style scoped>
+.sub-page {
+  max-width: 430px;
+  margin: 0 auto;
+  min-height: 100vh;
+  background-color: #f6fbf9;
+  padding-bottom: 24px;
+}
+
+.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;
+}
+
+.task-row {
+  padding: 14px 16px;
+  border-top: 1px solid rgba(164, 216, 152, 0.18);
+}
+
+.task-row:first-child {
+  border-top: none;
+}
+
+.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;
+}
+
+.row-title {
+  font-size: 14px;
+  font-weight: 600;
+  color: #1f2937;
+}
+
+.pending-tag {
+  padding: 2px 10px;
+  border-radius: 999px;
+  font-size: 11px;
+  font-weight: 600;
+  color: #b7791f;
+  background-color: rgba(251, 211, 141, 0.25);
+}
+
+.meta-line {
+  display: flex;
+  align-items: center;
+  padding-left: 54px;
+}
+
+.meta-icon {
+  font-size: 13px;
+  color: #9ca3af;
+}
+
+.meta-text {
+  font-size: 12px;
+  color: #9ca3af;
+}
+
+.action-btn {
+  flex: 1;
+  padding: 8px 0;
+  border-radius: 10px;
+  text-align: center;
+  font-size: 13px;
+  font-weight: 600;
+}
+
+.action-btn-hover {
+  opacity: 0.88;
+}
+
+.action-btn-confirm {
+  color: #ffffff;
+  background: linear-gradient(135deg, #368f6f 0%, #4ba98a 100%);
+}
+
+.action-btn-reject {
+  color: #6b7280;
+  background-color: #f3f4f6;
+}
+
+.empty-text {
+  font-size: 13px;
+  color: #9ca3af;
+}
+</style>

+ 54 - 21
src/types/index.ts

@@ -16,8 +16,21 @@ export interface User {
   status: UserStatus
 }
 
-export type ProjectType = '包年' | '单次' | '季度' | '月付'
-export type ProjectStatus = 'pending' | 'confirmed' | 'processing' | 'completed' | 'cancelled'
+/** 项目状态(后台 project_status 字典) */
+export type ProjectStatus = 'pending' | 'in_progress' | 'completed' | 'cancelled'
+/** 合作类型(后台 cooperation_type 字典) */
+export type CooperationType = 'annual' | 'single' | 'quarter' | 'monthly'
+/** 所属行业(后台 industry 字典) */
+export type Industry =
+  | 'residential'
+  | 'commercial'
+  | 'industrial'
+  | 'municipal'
+  | 'medical'
+  | 'education'
+  | 'catering'
+/** 设施类型(后台 facility_type 字典) */
+export type FacilityType = 'septic_tank' | 'direct_pump' | 'grease_pipe' | 'pvc_pipe'
 
 export interface Contact {
   name: string
@@ -25,37 +38,57 @@ export interface Contact {
   role: string
 }
 
+/** 项目服务点(后台 service_points JSON 中的单项) */
 export interface ServicePoint {
-  id: string
   name: string
-  location: string
   address?: string
-  serviceType: string
-  contactName?: string
-  contactPhone?: string
+  contact?: string
+  phone?: string
+  serviceType?: string
 }
 
+/**
+ * 项目(对齐后台 BizProject 实体)
+ * 注意:列表/详情返回的主键为 projectId;facilityTypes、servicePoints 后端以 JSON 字符串存储。
+ */
 export interface Project {
-  id: string
-  name: string
-  type: ProjectType
-  status: ProjectStatus
-  customerName: string
-  customerPhone: string
-  address: string
+  projectId: number | string
+  projectName: string
+  projectCode?: string
+  cooperationType: CooperationType
+  facilityTypes?: string | FacilityType[]
+  address?: string
   province?: string
   city?: string
   district?: string
-  contacts: Contact[]
-  servicePoints: ServicePoint[]
-  contractAmount?: number
+  street?: string
+  detailAddress?: string
+  latitude?: string
+  longitude?: string
+  primaryContactName?: string
+  primaryContactPhone?: string
+  backupContactName?: string
+  backupContactPhone?: string
+  specialContactReq?: string
+  industry?: Industry
+  occupancyRate?: number
+  buildingArea?: number
   contractStartDate?: string
   contractEndDate?: string
-  serviceCycle?: string
+  projectDescription?: string
+  contractAmount?: number
+  servicePoints?: string | ServicePoint[]
+  customerId?: number | string
+  customerName?: string
+  customerPhone?: string
+  contractId?: number | string
+  contractNo?: string
+  paymentMethod?: PaymentMethod
+  contractStatus?: ContractStatus
+  status: ProjectStatus
   remark?: string
-  location?: { lat: number; lng: number }
-  createTime: string
-  updateTime: string
+  createTime?: string
+  updateTime?: string
 }
 
 /** 后端分页结果(PageResult) */

+ 91 - 12
src/utils/index.ts

@@ -184,7 +184,11 @@ export function goBack(delta = 1): void {
  */
 export function getStatusText(status: string): string {
   const statusMap: Record<string, string> = {
-    pending: '待确认',
+    pending: '待审核',
+    auditing: '待审核',
+    approved: '待安排',
+    assigned: '已安排',
+    in_progress: '进行中',
     confirmed: '待排班',
     scheduled: '待出车',
     departed: '已出车',
@@ -204,6 +208,10 @@ export function getStatusText(status: string): string {
 export function getStatusColor(status: string): string {
   const colorMap: Record<string, string> = {
     pending: '#f59e0b',
+    auditing: '#f59e0b',
+    approved: '#2563eb',
+    assigned: '#2563eb',
+    in_progress: '#2563eb',
     confirmed: '#2563eb',
     scheduled: '#2563eb',
     departed: '#8b5cf6',
@@ -223,6 +231,10 @@ export function getStatusColor(status: string): string {
 export function getStatusBgColor(status: string): string {
   const bgMap: Record<string, string> = {
     pending: '#fff7ed',
+    auditing: '#fff7ed',
+    approved: '#eff6ff',
+    assigned: '#eff6ff',
+    in_progress: '#eff6ff',
     confirmed: '#eff6ff',
     scheduled: '#eff6ff',
     departed: '#f3e8ff',
@@ -292,30 +304,97 @@ export function getPersonnelStatusText(status: string): string {
 }
 
 /**
- * 项目类型文本
+ * 合作类型文本(后台 cooperation_type:annual/single/quarter/monthly)
  */
+export function getCooperationTypeText(type: string): string {
+  const map: Record<string, string> = {
+    annual: '包年',
+    single: '单次',
+    quarter: '季度',
+    monthly: '月付'
+  }
+  return map[type] || type || '-'
+}
+
+/** @deprecated 兼容旧调用,等价于 getCooperationTypeText */
 export function getProjectTypeText(type: string): string {
+  return getCooperationTypeText(type)
+}
+
+/**
+ * 所属行业文本(后台 industry 字典)
+ */
+export function getIndustryText(industry: string): string {
   const map: Record<string, string> = {
-    包年: '包年',
-    单次: '单次',
-    季度: '季度',
-    月付: '月付'
+    residential: '住宅',
+    commercial: '商业',
+    industrial: '工业',
+    municipal: '市政',
+    medical: '医疗',
+    education: '教育',
+    catering: '餐饮'
+  }
+  return map[industry] || industry || '-'
+}
+
+/**
+ * 设施类型文本(后台 facility_type 字典)
+ */
+export function getFacilityTypeText(type: string): string {
+  const map: Record<string, string> = {
+    septic_tank: '化粪池',
+    direct_pump: '直抽直排',
+    grease_pipe: '油污管+化粪池',
+    pvc_pipe: 'PVC+化粪池'
+  }
+  return map[type] || type || '-'
+}
+
+/**
+ * 解析后端以 JSON 字符串返回的数组字段(如 facilityTypes、servicePoints),容错返回数组
+ */
+export function parseJsonArray<T = any>(value: string | T[] | undefined | null): T[] {
+  if (!value) return []
+  if (Array.isArray(value)) return value
+  try {
+    const parsed = JSON.parse(value as string)
+    return Array.isArray(parsed) ? parsed : []
+  } catch (e) {
+    return []
   }
-  return map[type] || type
 }
 
 /**
- * 项目状态文本
+ * 项目状态文本(后台 project_status:pending/in_progress/completed/cancelled)
  */
 export function getProjectStatusText(status: string): string {
   const map: Record<string, string> = {
-    pending: '待确认',
-    confirmed: '已确认',
-    processing: '进行中',
+    pending: '待启动',
+    in_progress: '进行中',
     completed: '已完成',
     cancelled: '已取消'
   }
-  return map[status] || status
+  return map[status] || status || '-'
+}
+
+export function getProjectStatusColor(status: string): string {
+  const map: Record<string, string> = {
+    pending: '#f59e0b',
+    in_progress: '#2563eb',
+    completed: '#10b981',
+    cancelled: '#ef4444'
+  }
+  return map[status] || '#6b7280'
+}
+
+export function getProjectStatusBgColor(status: string): string {
+  const map: Record<string, string> = {
+    pending: '#fff7ed',
+    in_progress: '#eff6ff',
+    completed: '#ecfdf5',
+    cancelled: '#fef2f2'
+  }
+  return map[status] || '#f3f4f6'
 }
 
 /**