浏览代码

feat:UI2.0

xuyunhui 2 周之前
父节点
当前提交
89b6ed8251

+ 20 - 13
src/components/common/EmptyState.vue

@@ -1,20 +1,27 @@
 <template>
-  <view class="empty-state flex flex-col items-center justify-center py-16">
-    <text class="text-5xl mb-4">{{ icon }}</text>
-    <text class="text-gray-400 text-sm">{{ message }}</text>
+  <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>
+    <text class="text-sm text-gray-400 font-medium">{{ text || '暂无数据' }}</text>
     <slot />
   </view>
 </template>
 
 <script setup lang="ts">
-withDefaults(
-  defineProps<{
-    message?: string
-    icon?: string
-  }>(),
-  {
-    message: '暂无数据',
-    icon: ''
-  }
-)
+interface Props {
+  icon?: string
+  text?: string
+}
+
+withDefaults(defineProps<Props>(), {
+  icon: 'uniui-info-filled',
+  text: '暂无数据'
+})
 </script>
+
+<style scoped>
+.empty-state {
+  min-height: 200px;
+}
+</style>

+ 118 - 113
src/components/home/ConstructionHome.vue

@@ -1,98 +1,110 @@
 <template>
   <view class="app-container">
-    <TopBar title="" gradient>
-      <template #left>
+    <!-- 顶部渐变区域 -->
+    <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 rounded-2xl flex items-center justify-center mr-3" style="background-color: rgba(255, 255, 255, 0.2);">
-            <text class="uni-icons uniui-person-filled text-white text-xl"></text>
+          <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>
           <view>
-            <text class="text-white text-xs block" style="opacity: 0.8;">工程一班</text>
-            <text class="text-white text-lg font-bold">张师傅</text>
+            <text class="text-blue-100 text-xs block">工程一班</text>
+            <text class="text-white font-medium text-base">张师傅</text>
           </view>
-          <button class="ml-4 w-10 h-10 rounded-full flex items-center justify-center relative" style="background-color: rgba(255, 255, 255, 0.2);" @click="goToNotification">
-            <text class="uni-icons uniui-notification-filled text-white text-lg"></text>
-          </button>
         </view>
-      </template>
-    </TopBar>
-    <view class="px-4 py-4 gap-y-4 pb-24">
+        <view
+          class="w-10 h-10 rounded-full flex items-center justify-center relative"
+          style="background-color: rgba(255, 255, 255, 0.2);"
+          @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 }}
+          </view>
+        </view>
+      </view>
+    </view>
+
+    <view class="px-4 -mt-4 pb-6">
       <!-- 统计卡片 -->
-      <uni-card title="工作统计" :is-shadow="false">
+      <view class="card p-4">
         <view class="grid grid-cols-3 gap-3">
-          <view class="rounded-xl p-4 text-center text-white" style="background: linear-gradient(135deg, #06b6d4, #3b82f6);" @click="goToHistoryTask">
-            <text class="stat-number text-white block">{{ completedCount }}</text>
-            <text class="text-white mt-1 block text-xs" style="opacity: 0.8;">已完成</text>
-          </view>
-          <view class="rounded-xl p-4 text-center text-white" style="background: linear-gradient(135deg, #06b6d4, #3b82f6);" @click="goToTaskList">
-            <text class="stat-number text-white block">{{ currentTaskCount }}</text>
-            <text class="text-white mt-1 block text-xs" style="opacity: 0.8;">进行中</text>
-          </view>
-          <view class="rounded-xl p-4 text-center text-white" style="background: linear-gradient(135deg, #06b6d4, #3b82f6);" @click="goToTomorrowTask">
-            <text class="stat-number text-white block">{{ tomorrowCount }}</text>
-            <text class="text-white mt-1 block text-xs" style="opacity: 0.8;">明日任务</text>
-          </view>
+          <GradientStatCard
+            :value="completedCount"
+            label="已完成"
+            gradient="linear-gradient(135deg, #3b82f6 0%, #2563eb 100%)"
+            @click="goToHistoryTask"
+          />
+          <GradientStatCard
+            :value="currentTaskCount"
+            label="进行中"
+            gradient="linear-gradient(135deg, #60a5fa 0%, #3b82f6 100%)"
+            @click="goToTaskList"
+          />
+          <GradientStatCard
+            :value="tomorrowCount"
+            label="明日任务"
+            gradient="linear-gradient(135deg, #93c5fd 0%, #60a5fa 100%)"
+            @click="goToTomorrowTask"
+          />
         </view>
-      </uni-card>
+      </view>
 
       <!-- 每日任务入口 -->
-      <uni-card :is-shadow="false" @click="goToDailyTask">
-        <view class="flex items-center justify-between cursor-pointer bg-white rounded-xl p-3" hover-class="scale-down" hover-stay-time="150">
-          <view class="flex items-center">
-            <view class="icon-box mr-4 bg-primary-light rounded-xl">
-              <text class="uni-icons uniui-gear-filled"></text>
-            </view>
-            <view>
-              <text class="text-base font-bold text-gray-800 block">每日任务</text>
-              <text class="text-sm text-gray-400 mt-1 block">今日检查待完成</text>
-            </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 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-lg"></text>
+          <view>
+            <text class="text-base font-bold text-gray-800">每日任务</text>
+            <text class="text-sm text-gray-400 mt-1">今日检查待完成</text>
           </view>
         </view>
-      </uni-card>
+        <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>
+      </view>
 
       <!-- 快捷功能 -->
-      <uni-card title="快捷功能" :is-shadow="false">
-        <view class="grid grid-cols-3 gap-4">
-          <view class="flex flex-col items-center" @click="goToTaskList">
-            <view class="w-14 h-14 bg-primary-light rounded-2xl flex items-center justify-center mb-2" hover-class="scale-down" hover-stay-time="150">
-              <text class="uni-icons uniui-flag-filled text-primary text-2xl"></text>
-            </view>
-            <text class="text-xs text-gray-500 font-medium">我的任务</text>
-          </view>
-          <view class="flex flex-col items-center" @click="goToDailyTask">
-            <view class="w-14 h-14 bg-primary-light rounded-2xl flex items-center justify-center mb-2" hover-class="scale-down" hover-stay-time="150">
-              <text class="uni-icons uniui-gear-filled text-primary text-2xl"></text>
-            </view>
-            <text class="text-xs text-gray-500 font-medium">车辆检查</text>
-          </view>
-          <view class="flex flex-col items-center" @click="goToHelpCenter">
-            <view class="w-14 h-14 bg-primary-light rounded-2xl flex items-center justify-center mb-2" hover-class="scale-down" hover-stay-time="150">
-              <text class="uni-icons uniui-help-filled text-primary text-2xl"></text>
-            </view>
-            <text class="text-xs text-gray-500 font-medium">帮助中心</text>
-          </view>
+      <view class="card p-4">
+        <view class="grid grid-cols-3 gap-3">
+          <QuickEntry
+            label="我的任务"
+            icon="uniui-flag-filled"
+            @click="goToTaskList"
+          />
+          <QuickEntry
+            label="车辆检查"
+            icon="uniui-gear-filled"
+            @click="goToDailyTask"
+          />
+          <QuickEntry
+            label="帮助中心"
+            icon="uniui-help-filled"
+            @click="goToHelpCenter"
+          />
+        </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>
-      </uni-card>
-
-      <!-- 当前任务预览 -->
-      <uni-card
-        title="当前任务"
-        extra="查看全部"
-        :is-shadow="false"
-        @click-extra="goToTaskList"
-      >
         <view v-if="currentTasks.length === 0" class="py-6 text-center">
           <text class="text-desc">暂无进行中的任务</text>
         </view>
-        <view v-else class="gap-y-3">
+        <view v-else>
           <view
             v-for="task in currentTasks"
             :key="task.id"
-            class="card p-3 cursor-pointer" hover-class="scale-down" hover-stay-time="150"
+            class="p-4 border-b border-gray-100 active:bg-gray-50"
             @click="goToTaskDetail(task.id)"
           >
             <view class="flex justify-between items-start mb-2">
@@ -109,32 +121,33 @@
             </view>
           </view>
         </view>
-      </uni-card>
+      </view>
 
       <!-- 通知公告 -->
-      <uni-card
-        title="通知公告"
-        extra="查看全部"
-        :is-shadow="false"
-        @click-extra="goToNoticeList"
-      >
-        <uni-list :border="false">
-          <uni-list-item
-            v-for="(notice, index) in notices"
-            :key="index"
-            :title="notice.title"
-            :note="notice.date"
-            show-arrow
-            @click="goToNoticeDetail"
+      <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>
+        <view
+          v-for="(notice, index) in notices"
+          :key="index"
+          class="p-4 border-b border-gray-100 flex items-center active:bg-gray-50"
+          @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"
           >
-            <template #header>
-              <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>
-            </template>
-          </uni-list-item>
-        </uni-list>
-      </uni-card>
+            重要
+          </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>
+      </view>
     </view>
   </view>
 </template>
@@ -142,20 +155,23 @@
 <script setup lang="ts">
 import { ref, computed, onMounted } from 'vue'
 import { useTaskStore } from '@/stores/task'
-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 GradientStatCard from '@/components/common/GradientStatCard.vue'
+import QuickEntry from '@/components/common/QuickEntry.vue'
 import StatusTag from '@/components/common/StatusTag.vue'
 
 const taskStore = useTaskStore()
 
-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)
-const currentTasks = computed(() => taskStore.tasks.filter(t => ['scheduled', 'departed', 'arrived', 'constructing'].includes(t.status)).slice(0, 2))
+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
+)
+const currentTasks = computed(() =>
+  taskStore.tasks.filter(t => ['scheduled', 'departed', 'arrived', 'constructing'].includes(t.status)).slice(0, 2)
+)
+
 const notices = [
   { title: '关于规范施工现场安全管理的通知', date: '2026-04-20', important: true },
   { title: '下周安全培训会议安排', date: '2026-04-18', important: false },
@@ -202,14 +218,3 @@ onMounted(() => {
   taskStore.fetchTasks()
 })
 </script>
-
-<style scoped>
-.stat-number {
-  font-size: 24px;
-  font-weight: 700;
-}
-.scale-down {
-  transform: scale(0.95);
-  transition: transform 0.15s ease;
-}
-</style>

+ 80 - 78
src/components/home/DispatchHome.vue

@@ -1,85 +1,98 @@
 <template>
   <view class="app-container">
-    <TopBar title="" gradient>
-      <template #left>
+    <!-- 顶部渐变区域 -->
+    <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 rounded-2xl flex items-center justify-center mr-3" style="background-color: rgba(255, 255, 255, 0.2);">
-            <text class="uni-icons uniui-person-filled text-white text-xl"></text>
+          <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>
           <view>
-            <text class="text-white text-xs block" style="opacity: 0.8;">调度中心</text>
-            <text class="text-white text-lg font-bold">控制台</text>
+            <text class="text-blue-100 text-xs block">调度中心</text>
+            <text class="text-white font-medium text-base">控制台</text>
           </view>
-          <button class="ml-4 w-10 h-10 rounded-full flex items-center justify-center relative" style="background-color: rgba(255, 255, 255, 0.2);" @click="goToNotification">
-            <text class="uni-icons uniui-notification-filled text-white text-lg"></text>
-          </button>
         </view>
-      </template>
-    </TopBar>
-    <view class="px-4 py-4 gap-y-4 pb-24">
+        <view
+          class="w-10 h-10 rounded-full flex items-center justify-center relative"
+          style="background-color: rgba(255, 255, 255, 0.2);"
+          @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 }}
+          </view>
+        </view>
+      </view>
+    </view>
+
+    <view class="px-4 -mt-4 pb-6">
       <!-- 统计卡片 -->
-      <uni-card title="今日任务统计" :is-shadow="false" :is-full="false">
+      <view class="card p-4">
         <view class="grid grid-cols-3 gap-3">
-          <view class="rounded-xl p-4 text-center text-white" style="background: linear-gradient(135deg, #3b82f6, #6366f1);" @click="goToTaskList">
-            <text class="stat-number text-white block">{{ taskStore.total }}</text>
-            <text class="text-white mt-1 block text-xs" style="opacity: 0.8;">今日任务</text>
-          </view>
-          <view class="rounded-xl p-4 text-center text-white" style="background: linear-gradient(135deg, #3b82f6, #6366f1);" @click="goToTaskList">
-            <text class="stat-number text-white block">{{ taskStore.pendingCount }}</text>
-            <text class="text-white mt-1 block text-xs" style="opacity: 0.8;">待排班</text>
-          </view>
-          <view class="rounded-xl p-4 text-center text-white" style="background: linear-gradient(135deg, #3b82f6, #6366f1);" @click="goToEmergencyTeam">
-            <text class="stat-number text-white block">{{ emergencyCount }}</text>
-            <text class="text-white mt-1 block text-xs" style="opacity: 0.8;">应急中</text>
-          </view>
+          <GradientStatCard
+            :value="taskStore.total"
+            label="今日任务"
+            gradient="linear-gradient(135deg, #667eea 0%, #764ba2 100%)"
+            @click="goToTaskList"
+          />
+          <GradientStatCard
+            :value="taskStore.pendingCount"
+            label="待排班"
+            gradient="linear-gradient(135deg, #4facfe 0%, #00f2fe 100%)"
+            @click="goToTaskList"
+          />
+          <GradientStatCard
+            :value="emergencyCount"
+            label="应急中"
+            gradient="linear-gradient(135deg, #f093fb 0%, #f5576c 100%)"
+            @click="goToEmergencyTeam"
+          />
         </view>
-      </uni-card>
+      </view>
 
-      <!-- 快捷功能 -->
-      <uni-card title="快捷操作" :is-shadow="false">
-        <view class="grid grid-cols-4 gap-4">
-          <view class="flex flex-col items-center" @click="goToSchedule">
-            <view class="w-14 h-14 bg-primary-light rounded-2xl flex items-center justify-center mb-2" hover-class="scale-down" hover-stay-time="150">
-              <text class="uni-icons uniui-calendar-filled text-primary text-2xl"></text>
-            </view>
-            <text class="text-xs text-gray-500 font-medium">排班管理</text>
-          </view>
-          <view class="flex flex-col items-center" @click="goToTaskList">
-            <view class="w-14 h-14 bg-warning-light rounded-2xl flex items-center justify-center mb-2" hover-class="scale-down" hover-stay-time="150">
-              <text class="uni-icons uniui-flag-filled text-warning text-2xl"></text>
-            </view>
-            <text class="text-xs text-gray-500 font-medium">任务列表</text>
-          </view>
-          <view class="flex flex-col items-center" @click="goToVehicleList">
-            <view class="w-14 h-14 bg-success-light rounded-2xl flex items-center justify-center mb-2" hover-class="scale-down" hover-stay-time="150">
-              <text class="uni-icons uniui-cart-filled text-success text-2xl"></text>
-            </view>
-            <text class="text-xs text-gray-500 font-medium">车辆管理</text>
-          </view>
-          <view class="flex flex-col items-center" @click="goToVisualization">
-            <view class="w-14 h-14 bg-danger-light rounded-2xl flex items-center justify-center mb-2" hover-class="scale-down" hover-stay-time="150">
-              <text class="uni-icons uniui-map-filled text-danger text-2xl"></text>
-            </view>
-            <text class="text-xs text-gray-500 font-medium">可视化</text>
-          </view>
+      <!-- 快捷操作 -->
+      <view class="card p-4">
+        <view class="grid grid-cols-4 gap-3">
+          <QuickEntry
+            label="排班管理"
+            icon="uniui-calendar-filled"
+            @click="goToSchedule"
+          />
+          <QuickEntry
+            label="任务列表"
+            icon="uniui-flag-filled"
+            @click="goToTaskList"
+          />
+          <QuickEntry
+            label="车辆管理"
+            icon="uniui-cart-filled"
+            @click="goToVehicleList"
+          />
+          <QuickEntry
+            label="可视化"
+            icon="uniui-map-filled"
+            @click="goToVisualization"
+          />
         </view>
-      </uni-card>
+      </view>
 
       <!-- 待审核任务 -->
-      <uni-card
-        title="待审核任务"
-        extra="查看全部"
-        :is-shadow="false"
-        @click-extra="goToTaskList"
-      >
+      <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>
         <view v-if="pendingTasks.length === 0" class="py-6 text-center">
           <text class="text-desc">暂无待审核任务</text>
         </view>
-        <view v-else class="gap-y-3">
+        <view v-else>
           <view
             v-for="task in pendingTasks"
             :key="task.id"
-            class="card p-3 border-l-4 border-warning cursor-pointer" hover-class="scale-down" hover-stay-time="150"
+            class="p-4 border-b border-gray-100 active:bg-gray-50"
             @click="goToTaskDetail(task.id)"
           >
             <view class="flex justify-between items-start mb-2">
@@ -96,7 +109,7 @@
             </view>
           </view>
         </view>
-      </uni-card>
+      </view>
     </view>
   </view>
 </template>
@@ -104,14 +117,14 @@
 <script setup lang="ts">
 import { ref, computed, onMounted } from 'vue'
 import { useTaskStore } from '@/stores/task'
-import TopBar from '@/components/common/TopBar.vue'
+import GradientStatCard from '@/components/common/GradientStatCard.vue'
+import QuickEntry from '@/components/common/QuickEntry.vue'
 import StatusTag from '@/components/common/StatusTag.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'
 
 const taskStore = useTaskStore()
 
+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))
 
@@ -148,14 +161,3 @@ onMounted(() => {
   taskStore.fetchTasks()
 })
 </script>
-
-<style scoped>
-.stat-number {
-  font-size: 24px;
-  font-weight: 700;
-}
-.scale-down {
-  transform: scale(0.95);
-  transition: transform 0.15s ease;
-}
-</style>

+ 137 - 160
src/components/home/SalesHome.vue

@@ -1,192 +1,180 @@
-
 <template>
   <view class="app-container">
-    <TopBar title="" gradient>
-      <template #left>
+    <!-- 顶部渐变区域 -->
+    <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 rounded-2xl flex items-center justify-center mr-3" style="background-color: rgba(255, 255, 255, 0.2);">
-            <text class="uni-icons uniui-person-filled text-white text-xl"></text>
+          <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>
           <view>
-            <text class="text-white text-xs block" style="opacity: 0.8;">下午好</text>
-            <text class="text-white text-lg font-bold">{{ userName }}</text>
+            <text class="text-blue-100 text-xs block">下午好,欢迎回来</text>
+            <text class="text-white font-medium text-base">{{ userName }}</text>
           </view>
-          <button class="ml-4 w-10 h-10 rounded-full flex items-center justify-center relative" style="background-color: rgba(255, 255, 255, 0.2);" @click="goToNotification">
-            <text class="uni-icons uniui-notification-filled text-white text-lg"></text>
-          </button>
         </view>
-      </template>
-    </TopBar>
-    <view class="px-4 py-4 gap-y-4">
-      <!-- 统计卡片 -->
-      <uni-card title="数据概览" :is-shadow="false" :is-full="false">
-        <view class="grid grid-cols-3 gap-3">
-          <!-- 本月项目 -->
-          <view class="rounded-xl p-4 text-center text-white" style="background: linear-gradient(135deg, #3b82f6, #8b5cf6);" @click="goToProjectList">
-            <text class="stat-number text-white block">{{ projectStore.totalCount }}</text>
-            <text class="text-white mt-1 block text-xs" style="opacity: 0.8;">本月项目</text>
-          </view>
-
-          <!-- 待确认 -->
-          <view class="rounded-xl p-4 text-center text-white" style="background: linear-gradient(135deg, #ec4899, #8b5cf6);" @click="goToTaskList">
-            <text class="stat-number text-white block">{{ taskStore.pendingCount }}</text>
-            <text class="text-white mt-1 block text-xs" style="opacity: 0.8;">待确认</text>
+        <view
+          class="w-10 h-10 rounded-full flex items-center justify-center relative"
+          style="background-color: rgba(255, 255, 255, 0.2);"
+          @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 }}
           </view>
+        </view>
+      </view>
+    </view>
 
-          <!-- 进行中 -->
-          <view class="rounded-xl p-4 text-center text-white" style="background: linear-gradient(135deg, #06b6d4, #3b82f6);" @click="goToTaskList">
-            <text class="stat-number text-white block">{{ taskStore.todayCount }}</text>
-            <text class="text-white mt-1 block text-xs" style="opacity: 0.8;">进行中</text>
-          </view>
+    <view class="px-4 -mt-4 pb-6">
+      <!-- 统计卡片 -->
+      <view class="card p-4">
+        <view class="grid grid-cols-3 gap-3">
+          <GradientStatCard
+            :value="projectStore.totalCount"
+            label="本月项目"
+            gradient="linear-gradient(135deg, #667eea 0%, #764ba2 100%)"
+            @click="goToProjectList"
+          />
+          <GradientStatCard
+            :value="taskStore.pendingCount"
+            label="待确认"
+            gradient="linear-gradient(135deg, #f093fb 0%, #f5576c 100%)"
+            @click="goToTaskList"
+          />
+          <GradientStatCard
+            :value="taskStore.todayCount"
+            label="进行中"
+            gradient="linear-gradient(135deg, #4facfe 0%, #00f2fe 100%)"
+            @click="goToTaskList"
+          />
         </view>
-      </uni-card>
+      </view>
 
       <!-- 快捷入口 -->
-      <uni-card title="快捷功能" :is-shadow="false">
-        <view class="grid grid-cols-3 gap-4">
-          <view class="flex flex-col items-center" @click="goToProjectList">
-            <view class="w-14 h-14 bg-primary-light rounded-2xl flex items-center justify-center mb-2 tap-feedback">
-              <text class="uni-icons uniui-folder-add-filled text-primary text-2xl"></text>
-            </view>
-            <text class="text-xs text-gray-500 font-medium">项目库</text>
-          </view>
-          <view class="flex flex-col items-center opacity-50">
-            <view class="w-14 h-14 bg-primary-light rounded-2xl flex items-center justify-center mb-2 tap-feedback opacity-60">
-              <text class="uni-icons uniui-personadd-filled text-gray-400 text-2xl"></text>
-            </view>
-            <text class="text-xs text-gray-400 font-medium">客户管理</text>
-          </view>
-          <view class="flex flex-col items-center opacity-50">
-            <view class="w-14 h-14 bg-primary-light rounded-2xl flex items-center justify-center mb-2 tap-feedback opacity-60">
-              <text class="uni-icons uniui-file-text-filled text-gray-400 text-2xl"></text>
-            </view>
-            <text class="text-xs text-gray-400 font-medium">合同管理</text>
-          </view>
+      <view class="card p-4">
+        <view class="grid grid-cols-3 gap-3">
+          <QuickEntry
+            label="项目库"
+            icon="uniui-folder-add-filled"
+            @click="goToProjectList"
+          />
+          <QuickEntry
+            label="客户管理"
+            icon="uniui-personadd-filled"
+            @click="goToCustomer"
+          />
+          <QuickEntry
+            label="合同管理"
+            icon="uniui-file-text-filled"
+            @click="goToContract"
+          />
         </view>
-      </uni-card>
+      </view>
 
       <!-- 任务提醒入口 -->
-      <button
-        class="w-full py-3 bg-white border border-gray-200 text-gray-600 rounded-xl text-sm font-medium flex items-center justify-center task-reminder-btn"
+      <view
+        class="card p-4 flex items-center justify-center"
         @click="goToTaskReminder"
       >
         <text class="uni-icons uniui-notification-filled mr-2 text-gray-500"></text>
-        任务提醒 ({{ taskStore.pendingCount }})
-      </button>
+        <text class="text-sm text-gray-600 font-medium">任务提醒 ({{ taskStore.pendingCount }})</text>
+      </view>
 
       <!-- 最新任务 -->
-      <uni-card
-        title="最新任务"
-        extra="查看全部"
-        :is-shadow="false"
-        @click-extra="goToTaskList"
-      >
+      <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>
         <view v-if="taskStore.tasks.length === 0" class="py-6 text-center">
           <text class="text-desc">暂无任务</text>
         </view>
-        <uni-list v-else :border="false">
+        <view v-else>
           <view
-            v-for="task in taskStore.tasks.slice(0, 3)"
+            v-for="task in latestTasks"
             :key="task.id"
-            class="tap-feedback"
+            class="p-4 border-b border-gray-100 flex items-center justify-between active:bg-gray-50"
+            @click="goToTaskDetail(task.id)"
           >
-            <uni-list-item
-              :title="task.projectName"
-              :note="task.address || task.scheduleDate"
-              show-arrow
-              clickable
-              @click="goToTaskDetail(task.id)"
-            >
-              <template #header>
-                <view class="icon-box mr-3">
-                  <text class="uni-icons uniui-flag"></text>
-                </view>
-              </template>
-              <template #footer>
-                <StatusTag :status="task.status" />
-              </template>
-            </uni-list-item>
+            <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>
+            <StatusTag :status="task.status" />
           </view>
-        </uni-list>
-      </uni-card>
+        </view>
+      </view>
 
       <!-- 通知公告 -->
-      <uni-card
-        title="通知公告"
-        extra="更多"
-        :is-shadow="false"
-        @click-extra="goToNoticeList"
-      >
-        <uni-list :border="false">
-          <view
-            v-for="(notice, index) in notices"
-            :key="index"
-            class="tap-feedback"
-          >
-            <uni-list-item
-              :title="notice.title"
-              :note="notice.date"
-              show-arrow
-              clickable
-              @click="goToNoticeDetail"
-            >
-              <template #header>
-                <view class="w-2 h-2 rounded-full mt-1 mr-2 flex-shrink-0" :class="index === 0 ? 'bg-danger' : 'bg-gray-300'"></view>
-              </template>
-            </uni-list-item>
+      <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>
+        <view
+          v-for="(notice, index) in notices"
+          :key="index"
+          class="p-4 border-b border-gray-100 flex items-center active:bg-gray-50"
+          @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>
-        </uni-list>
-      </uni-card>
+          <text class="text-xs text-gray-400 ml-2">{{ notice.date }}</text>
+        </view>
+      </view>
 
       <!-- 行业知识 -->
-      <uni-card
-        title="行业知识"
-        extra="更多"
-        :is-shadow="false"
-        @click-extra="goToKnowledgeList"
-      >
-        <uni-list :border="false">
-          <view
-            v-for="(item, index) in knowledgeList"
-            :key="index"
-            class="tap-feedback"
-          >
-            <uni-list-item
-              :title="item.title"
-              :note="item.summary"
-              show-arrow
-              clickable
-              @click="goToKnowledgeDetail"
-            >
-              <template #header>
-                <view class="w-10 h-10 bg-primary-light rounded-xl flex items-center justify-center mr-3 flex-shrink-0">
-                  <text class="uni-icons uniui-chart text-primary text-lg"></text>
-                </view>
-              </template>
-            </uni-list-item>
+      <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>
+        <view
+          v-for="(item, index) in knowledgeList"
+          :key="index"
+          class="p-4 border-b border-gray-100 flex items-center active:bg-gray-50"
+          @click="goToKnowledgeDetail"
+        >
+          <view class="icon-box mr-3">
+            <text class="uni-icons uniui-chart"></text>
           </view>
-        </uni-list>
-      </uni-card>
+          <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>
+        </view>
+      </view>
     </view>
   </view>
 </template>
 
 <script setup lang="ts">
-import { ref, onMounted } from 'vue'
+import { ref, computed, onMounted } from 'vue'
 import { useAuthStore } from '@/stores/auth'
 import { useProjectStore } from '@/stores/project'
 import { useTaskStore } from '@/stores/task'
-import TopBar from '@/components/common/TopBar.vue'
+import GradientStatCard from '@/components/common/GradientStatCard.vue'
+import QuickEntry from '@/components/common/QuickEntry.vue'
 import StatusTag from '@/components/common/StatusTag.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'
 
 const authStore = useAuthStore()
 const projectStore = useProjectStore()
 const taskStore = useTaskStore()
 
 const userName = ref(authStore.user?.name || '张销售')
+const notificationCount = ref(3)
+
+const latestTasks = computed(() => taskStore.tasks.slice(0, 3))
 
 const notices = [
   { title: '关于2024年第一季度销售激励方案的通知', date: '2024-01-15' },
@@ -235,27 +223,16 @@ function goToKnowledgeDetail() {
   uni.navigateTo({ url: '/subPackages/pages-common/knowledgeDetail' })
 }
 
+function goToCustomer() {
+  uni.navigateTo({ url: '/pages/customer/customer' })
+}
+
+function goToContract() {
+  uni.navigateTo({ url: '/pages/contract/contract' })
+}
+
 onMounted(() => {
   projectStore.fetchProjects()
   taskStore.fetchTasks()
 })
 </script>
-
-<style scoped>
-.stat-number {
-  font-size: 24px;
-  font-weight: 700;
-}
-.tap-feedback {
-  transition: transform 0.15s ease;
-}
-.tap-feedback:active {
-  transform: scale(0.95);
-}
-.task-reminder-btn {
-  transition: background-color 0.15s ease;
-}
-.task-reminder-btn:active {
-  background-color: #f9fafb;
-}
-</style>

+ 1 - 1
src/components/my/ProfileView.vue

@@ -1,5 +1,5 @@
 <template>
-  <view class="app-container pb-20">
+  <view class="app-container pb-40">
     <!-- 顶部个人信息 -->
     <view class="px-4 pb-6 rounded-b-3xl" style="background: linear-gradient(135deg, #1e3a8a, #1e40af);">
       <StatusBar />

+ 144 - 36
src/components/task/TaskCard.vue

@@ -1,49 +1,157 @@
-
 <template>
-  <view class="card p-4 mb-3 clickable" @click="$emit('click', task.id)">
-    <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>
-      </view>
-      <StatusTag :status="task.status" />
+  <view
+    class="task-card"
+    :class="cardClass"
+    @click="onClick"
+  >
+    <view class="header">
+      <text class="task-name">{{ projectName }}</text>
+      <StatusTag :status="status" />
     </view>
-    
-    <view class="gap-y-1 mb-3">
-      <view class="flex items-center">
-        <text class="text-gray-400 text-xs mr-1"></text>
-        <text class="text-gray-500 text-xs truncate">{{ task.address || '暂无地址' }}</text>
-      </view>
-      <view class="flex items-center">
-        <text class="text-gray-400 text-xs mr-1">⏰</text>
-        <text class="text-gray-500 text-xs">{{ task.scheduleDate || '待定' }} {{ task.scheduleTime || '' }}</text>
+
+    <slot />
+
+    <!-- 进度条(如果有步骤) -->
+    <view v-if="steps && steps.length > 0" class="mt-3">
+      <view class="progress-track">
+        <view
+          class="progress-fill"
+          :style="{ width: progressPercent + '%' }"
+        />
       </view>
-    </view>
-    
-    <view class="flex items-center justify-between pt-2 border-t border-gray-100">
-      <view class="flex items-center">
-        <view v-if="task.vehicleNo" class="flex items-center mr-3">
-          <text class="text-gray-400 text-xs mr-1"></text>
-          <text class="text-gray-500 text-xs">{{ task.vehicleNo }}</text>
-        </view>
-        <view v-if="task.staffName" class="flex items-center">
-          <text class="text-gray-400 text-xs mr-1"></text>
-          <text class="text-gray-500 text-xs">{{ task.staffName }}</text>
-        </view>
+      <view class="flex justify-between mt-1">
+        <text
+          v-for="(step, index) in steps"
+          :key="index"
+          class="step-label"
+          :class="{ 'text-primary': index <= currentStep, 'text-gray-400': index > currentStep }"
+        >
+          {{ step }}
+        </text>
       </view>
-      <text class="text-gray-300 text-lg leading-none">›</text>
     </view>
   </view>
 </template>
 
 <script setup lang="ts">
-import StatusTag from '../common/StatusTag.vue'
-import type { Task } from '@/types'
+import { computed } from 'vue'
+import StatusTag from '@/components/common/StatusTag.vue'
 
-defineProps<{
-  task: Task
-}>()
+interface Props {
+  projectName: string
+  status: string
+  statusType?: 'task' | 'project'
+  isEmergency?: boolean
+  isTimeout?: boolean
+  isCompleted?: boolean
+  steps?: string[]
+  currentStep?: number
+}
+
+const props = withDefaults(defineProps<Props>(), {
+  statusType: 'task',
+  isEmergency: false,
+  isTimeout: false,
+  isCompleted: false,
+  steps: () => [],
+  currentStep: 0
+})
 
-defineEmits<{
-  click: [taskId: string]
+const emit = defineEmits<{
+  (e: 'click'): void
 }>()
+
+const cardClass = computed(() => {
+  const classes: string[] = []
+  if (props.isEmergency) classes.push('emergency')
+  if (props.isTimeout) classes.push('timeout')
+  if (props.isCompleted) classes.push('completed')
+  return classes.join(' ')
+})
+
+const progressPercent = computed(() => {
+  if (!props.steps || props.steps.length === 0) return 0
+  return ((props.currentStep + 1) / props.steps.length) * 100
+})
+
+function onClick() {
+  emit('click')
+}
 </script>
+
+<style scoped>
+.task-card {
+  background: white;
+  border-radius: 12px;
+  padding: 16px;
+  margin-bottom: 12px;
+  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
+  transition: transform 0.2s, box-shadow 0.2s, background-color 0.2s;
+  position: relative;
+  overflow: hidden;
+}
+
+.task-card:active {
+  transform: scale(0.98);
+  box-shadow: 0 1px 4px rgba(0, 0, 0, 0.1);
+}
+
+/* 应急/加急 - 左侧红色边框 */
+.task-card.emergency {
+  border-left: 4px solid #ef4444;
+}
+
+/* 超时/即将超时 - 淡红色背景 */
+.task-card.timeout {
+  background: #fef2f2;
+}
+
+/* 已完成 - 淡绿色背景 */
+.task-card.completed {
+  background: #f0fdf4;
+}
+
+.header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  margin-bottom: 10px;
+}
+
+.task-name {
+  font-size: 15px;
+  font-weight: 600;
+  color: #333;
+}
+
+.progress-track {
+  height: 4px;
+  background: #e5e7eb;
+  border-radius: 2px;
+  position: relative;
+  overflow: hidden;
+}
+
+.progress-fill {
+  position: absolute;
+  left: 0;
+  top: 0;
+  height: 100%;
+  background: #3b82f6;
+  border-radius: 2px;
+  transition: width 0.3s ease;
+}
+
+.step-label {
+  font-size: 10px;
+  font-weight: 500;
+}
+
+.text-primary {
+  color: #2563eb;
+}
+
+.text-gray-400 {
+  color: #9ca3af;
+}
+</style>

+ 1 - 1
src/components/uni-card/uni-card.vue

@@ -57,7 +57,7 @@ const props = withDefaults(defineProps<Props>(), {
   border-radius: 12px;
   overflow: hidden;
   margin: 12px 0;
-  border: 1px solid #e5e7eb;
+  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
 }
 .uni-card--full {
   margin: 0;

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

@@ -11,6 +11,12 @@ const dispatchTabs = [
   { pagePath: 'pages/my/my', text: '我的', icon: '/static/tabbar/my.png', activeIcon: '/static/tabbar/my-active.png' },
 ]
 
+const constructionTabs = [
+  { pagePath: 'pages/home/home', text: '首页', icon: '/static/tabbar/home.png', activeIcon: '/static/tabbar/home-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' },
+]
+
 function getRole() {
   try {
     const userStr = wx.getStorageSync('user')
@@ -33,7 +39,9 @@ Component({
   lifetimes: {
     attached() {
       const role = getRole()
-      const tabs = role === 'dispatch' ? dispatchTabs : commonTabs
+      let tabs = commonTabs
+      if (role === 'dispatch') tabs = dispatchTabs
+      else if (role === 'construction') tabs = constructionTabs
       this.setData({ tabs })
       this.updateSelected()
     },

+ 0 - 1
src/main.ts

@@ -1,7 +1,6 @@
 import { createSSRApp } from 'vue'
 import { createPinia } from 'pinia'
 import App from './App.vue'
-import './styles/tailwind.css'
 import './styles/global.scss'
 
 export function createApp() {

+ 68 - 4
src/pages.json

@@ -48,6 +48,22 @@
         "navigationBarTitleText": "排班",
         "navigationStyle": "custom"
       }
+    },
+    {
+      "path": "pages/customer/customer",
+      "style": {
+        "navigationBarTitleText": "客户管理",
+        "navigationStyle": "custom",
+        "enablePullDownRefresh": true
+      }
+    },
+    {
+      "path": "pages/contract/contract",
+      "style": {
+        "navigationBarTitleText": "合同管理",
+        "navigationStyle": "custom",
+        "enablePullDownRefresh": true
+      }
     }
   ],
   "tabBar": {
@@ -61,10 +77,6 @@
         "pagePath": "pages/home/home",
         "text": "首页"
       },
-      {
-        "pagePath": "pages/schedule/schedule",
-        "text": "排班"
-      },
       {
         "pagePath": "pages/task/task",
         "text": "任务"
@@ -381,6 +393,58 @@
           }
         }
       ]
+    },
+    {
+      "root": "subPackages/pages-customer",
+      "pages": [
+        {
+          "path": "customerDetail",
+          "style": {
+            "navigationBarTitleText": "客户详情",
+            "navigationStyle": "custom"
+          }
+        },
+        {
+          "path": "customerForm",
+          "style": {
+            "navigationBarTitleText": "客户表单",
+            "navigationStyle": "custom"
+          }
+        },
+        {
+          "path": "followUpList",
+          "style": {
+            "navigationBarTitleText": "跟进记录",
+            "navigationStyle": "custom"
+          }
+        }
+      ]
+    },
+    {
+      "root": "subPackages/pages-contract",
+      "pages": [
+        {
+          "path": "contractDetail",
+          "style": {
+            "navigationBarTitleText": "合同详情",
+            "navigationStyle": "custom"
+          }
+        },
+        {
+          "path": "contractForm",
+          "style": {
+            "navigationBarTitleText": "合同表单",
+            "navigationStyle": "custom"
+          }
+        },
+        {
+          "path": "paymentRecord",
+          "style": {
+            "navigationBarTitleText": "付款记录",
+            "navigationStyle": "custom"
+          }
+        }
+      ]
     }
   ],
   "preloadRule": {

+ 427 - 88
src/styles/global.scss

@@ -24,7 +24,7 @@ page {
 
 /* 点击反馈 */
 .clickable {
-  transition: transform 0.15s ease, background-color 0.15s ease;
+  transition: transform 0.15s ease, opacity 0.15s ease;
 
   &:active {
     transform: scale(0.98);
@@ -46,21 +46,21 @@ page {
   pointer-events: none;
 }
 
-/* 卡片基础样式 - 简洁原生风 */
+/* ==================== 卡片(原型:白底圆角阴影,无边框) ==================== */
 .card {
   background-color: $card-bg;
   border-radius: $radius-card;
   padding: $card-padding;
   margin-bottom: 12px;
   box-shadow: $shadow-card;
-  border: 1px solid $border-color;
+  overflow: hidden;
 
   &:active {
     opacity: 0.95;
   }
 }
 
-/* 按钮基础样式 */
+/* ==================== 按钮(原型:渐变) ==================== */
 .btn {
   display: inline-flex;
   align-items: center;
@@ -71,6 +71,7 @@ page {
   font-weight: 500;
   line-height: 1.4;
   transition: opacity 0.15s ease, transform 0.15s ease;
+  border: none;
 
   &:active {
     transform: scale(0.98);
@@ -79,22 +80,22 @@ page {
 }
 
 .btn-primary {
-  background-color: $primary;
+  background: $gradient-primary;
   color: #fff;
 }
 
 .btn-success {
-  background-color: $success;
+  background: $gradient-success;
   color: #fff;
 }
 
 .btn-warning {
-  background-color: $warning;
+  background: $gradient-warning;
   color: #fff;
 }
 
 .btn-danger {
-  background-color: $danger;
+  background: $gradient-danger;
   color: #fff;
 }
 
@@ -103,7 +104,7 @@ page {
   color: $text-primary;
 }
 
-/* 状态标签 */
+/* ==================== 状态标签 ==================== */
 .tag {
   display: inline-flex;
   align-items: center;
@@ -139,9 +140,29 @@ page {
   color: $text-secondary;
 }
 
-/* 表单 */
+/* 表单组容器 */
+.form-group {
+  margin-bottom: 16px;
+}
+
+/* 表单选择器(picker 用) */
+.form-select {
+  width: 100%;
+  padding: 12px 14px;
+  border: 1px solid $border-color;
+  border-radius: $radius-button;
+  font-size: 15px;
+  color: $text-primary;
+  background: $card-bg;
+  box-sizing: border-box;
+  display: flex;
+  align-items: center;
+  line-height: 1.4;
+  min-height: 44px;
+}
+
+/* ==================== 表单 ==================== */
 .form-input,
-.form-select,
 .form-textarea {
   width: 100%;
   font-size: 15px;
@@ -213,58 +234,25 @@ page {
   margin-bottom: 8px;
 }
 
-/* 空状态 */
-.empty-state {
-  text-align: center;
-  padding: 80px 24px;
-
-  &__icon {
-    font-size: 56px;
-    margin-bottom: 20px;
-    opacity: 0.5;
-  }
-
-  &__text {
-    color: $text-tertiary;
-    font-size: 15px;
-    font-weight: 500;
-  }
-}
-
-/* 安全区域 */
-.safe-bottom {
-  padding-bottom: constant(safe-area-inset-bottom);
-  padding-bottom: env(safe-area-inset-bottom);
-}
-
-/* 隐藏滚动条 */
-.hide-scrollbar::-webkit-scrollbar {
-  display: none;
-}
-
-.hide-scrollbar {
-  -ms-overflow-style: none;
-  scrollbar-width: none;
-}
-
-/* 列表项 */
-.list-item {
-  background: $card-bg;
-  padding: 16px;
-  border-bottom: 1px solid $border-color;
-
-  &:last-child {
-    border-bottom: none;
-  }
+/* ==================== 详情页区块标题(原型带图标) ==================== */
+.detail-section-title {
+  padding: 12px 16px;
+  font-size: 15px;
+  font-weight: 600;
+  color: $text-primary;
+  border-bottom: 1px solid #f0f0f0;
+  display: flex;
+  align-items: center;
+  margin-bottom: 0;
 }
 
-/* 详情行 */
+/* ==================== 详情行 ==================== */
 .detail-row {
   padding: 14px 16px;
   display: flex;
   justify-content: space-between;
   align-items: flex-start;
-  border-bottom: 1px solid $border-color;
+  border-bottom: 1px solid #f5f5f5;
 
   &:last-child {
     border-bottom: none;
@@ -285,9 +273,17 @@ page {
   flex: 1;
   margin-left: 12px;
   font-weight: 500;
+  word-break: break-all;
+}
+
+/* ==================== 空状态 ==================== */
+.empty-state {
+  text-align: center;
+  padding: 80px 24px;
+  min-height: 200px;
 }
 
-/* 进度条 */
+/* ==================== 进度条 ==================== */
 .progress-bar-track {
   height: 6px;
   background: $border-color;
@@ -306,7 +302,7 @@ page {
   transition: width 0.4s ease;
 }
 
-/* 筛选标签 */
+/* ==================== 筛选标签 ==================== */
 .filter-tab {
   flex-shrink: 0;
   padding: 6px 16px;
@@ -329,62 +325,405 @@ page {
   }
 }
 
-/* 统计卡片 */
+/* ==================== 统计卡片(无边框) ==================== */
 .stat-card {
   background: $card-bg;
   border-radius: $radius-card;
   padding: 16px;
-  border: 1px solid $border-color;
+  box-shadow: $shadow-card;
+}
+
+/* ==================== 图标容器 ==================== */
+.icon-box {
+  width: 40px;
+  height: 40px;
+  border-radius: 10px;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  background-color: $primary-light;
+  color: $primary;
+}
+
+/* ==================== 文字样式 ==================== */
+.text-desc {
+  font-size: 13px;
+  color: $text-tertiary;
+  line-height: 1.5;
+}
+
+.stat-number {
+  font-size: 26px;
+  font-weight: 700;
+  line-height: 1.2;
 }
 
-/* 页面标题 */
 .page-title {
   font-size: 20px;
   font-weight: 700;
   color: $text-primary;
 }
 
-/* 区块标题 */
 .section-title {
   font-size: 17px;
   font-weight: 700;
   color: $text-primary;
 }
 
-/* 描述文字 */
-.text-desc {
-  font-size: 13px;
-  color: $text-tertiary;
-  line-height: 1.5;
+/* ==================== 安全区域 & 其他 ==================== */
+.safe-bottom {
+  padding-bottom: constant(safe-area-inset-bottom);
+  padding-bottom: env(safe-area-inset-bottom);
 }
 
-/* 高亮数字 */
-.stat-number {
-  font-size: 26px;
-  font-weight: 700;
-  line-height: 1.2;
+.hide-scrollbar::-webkit-scrollbar {
+  display: none;
 }
 
-/* 图标容器 */
-.icon-box {
-  width: 40px;
-  height: 40px;
-  border-radius: 10px;
-  display: flex;
-  align-items: center;
-  justify-content: center;
-  background-color: $primary-light;
-  color: $primary;
+.hide-scrollbar {
+  -ms-overflow-style: none;
+  scrollbar-width: none;
 }
 
-/* 顶部 Header 区域 */
-.page-header {
-  background-color: $card-bg;
+.list-item {
+  background: $card-bg;
   padding: 16px;
   border-bottom: 1px solid $border-color;
+
+  &:last-child {
+    border-bottom: none;
+  }
 }
 
-.page-header-primary {
-  background-color: $primary;
-  color: #fff;
+/* ==================== 顶部渐变区域 ==================== */
+.header-gradient {
+  background: $gradient-topbar;
+}
+
+/* ==================== active 状态(WXSS 兼容) ==================== */
+.active-bg-gray-50:active {
+  background-color: #f9fafb;
+}
+
+/* ==========================================================
+   以下为 TailwindCSS 替代类(全面兼容小程序 WXSS)
+   ========================================================== */
+
+/* ---------- Flexbox ---------- */
+.flex { display: flex; }
+.inline-flex { display: inline-flex; }
+.flex-1 { flex: 1 1 0%; }
+.flex-row { flex-direction: row; }
+.flex-col { flex-direction: column; }
+.flex-wrap { flex-wrap: wrap; }
+.flex-nowrap { flex-wrap: nowrap; }
+.items-start { align-items: flex-start; }
+.items-center { align-items: center; }
+.items-end { align-items: flex-end; }
+.justify-start { justify-content: flex-start; }
+.justify-end { justify-content: flex-end; }
+.justify-center { justify-content: center; }
+.justify-between { justify-content: space-between; }
+.justify-around { justify-content: space-around; }
+.justify-evenly { justify-content: space-evenly; }
+
+/* ---------- Grid ---------- */
+.grid { display: grid; }
+.grid-cols-1 { grid-template-columns: repeat(1, minmax(0, 1fr)); }
+.grid-cols-2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
+.grid-cols-3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
+.grid-cols-4 { grid-template-columns: repeat(4, minmax(0, 1fr)); }
+
+/* ---------- 文字对齐 ---------- */
+.text-left { text-align: left; }
+.text-center { text-align: center; }
+.text-right { text-align: right; }
+
+/* ---------- 文字大小 ---------- */
+.text-xs { font-size: 12px; }
+.text-sm { font-size: 13px; }
+.text-base { font-size: 15px; }
+.text-lg { font-size: 17px; }
+.text-xl { font-size: 20px; }
+.text-2xl { font-size: 24px; }
+.text-3xl { font-size: 30px; }
+
+/* ---------- 文字粗细 ---------- */
+.font-normal { font-weight: 400; }
+.font-medium { font-weight: 500; }
+.font-semibold { font-weight: 600; }
+.font-bold { font-weight: 700; }
+
+/* ---------- 文字颜色 ---------- */
+.text-white { color: #ffffff; }
+.text-primary { color: $primary; }
+.text-success { color: $success; }
+.text-warning { color: $warning; }
+.text-danger { color: $danger; }
+.text-gray-400 { color: #9ca3af; }
+.text-gray-500 { color: #6b7280; }
+.text-gray-600 { color: #4b5563; }
+.text-gray-700 { color: #374151; }
+.text-gray-800 { color: #1f2937; }
+.text-gray-900 { color: #111827; }
+.text-blue-500 { color: #3b82f6; }
+.text-blue-600 { color: #2563eb; }
+.text-blue-700 { color: #1d4ed8; }
+.text-green-500 { color: #10b981; }
+.text-green-600 { color: #059669; }
+.text-green-700 { color: #047857; }
+.text-orange-500 { color: #f97316; }
+.text-orange-600 { color: #ea580c; }
+.text-red-500 { color: #ef4444; }
+.text-red-600 { color: #dc2626; }
+.text-purple-500 { color: #a855f7; }
+.text-cyan-500 { color: #06b6d4; }
+
+/* ---------- 背景颜色 ---------- */
+.bg-white { background-color: #ffffff; }
+.bg-primary { background-color: $primary; }
+.bg-primary-light { background-color: $primary-light; }
+.bg-success { background-color: $success; }
+.bg-success-light { background-color: $success-light; }
+.bg-warning { background-color: $warning; }
+.bg-warning-light { background-color: $warning-light; }
+.bg-danger { background-color: $danger; }
+.bg-danger-light { background-color: $danger-light; }
+.bg-gray-50 { background-color: #f9fafb; }
+.bg-gray-100 { background-color: #f3f4f6; }
+.bg-gray-200 { background-color: #e5e7eb; }
+.bg-blue-50 { background-color: #eff6ff; }
+.bg-blue-100 { background-color: #dbeafe; }
+.bg-green-50 { background-color: #f0fdf4; }
+.bg-green-100 { background-color: #dcfce7; }
+.bg-orange-50 { background-color: #fff7ed; }
+.bg-orange-100 { background-color: #ffedd5; }
+.bg-red-50 { background-color: #fef2f2; }
+.bg-red-100 { background-color: #fee2e2; }
+.bg-purple-50 { background-color: #faf5ff; }
+.bg-purple-100 { background-color: #f3e8ff; }
+.bg-cyan-50 { background-color: #ecfeff; }
+.bg-cyan-100 { background-color: #cffafe; }
+.bg-surface { background-color: $surface; }
+
+/* ---------- 间距(padding) ---------- */
+.p-1 { padding: 4px; }
+.p-2 { padding: 8px; }
+.p-3 { padding: 12px; }
+.p-4 { padding: 16px; }
+.p-5 { padding: 20px; }
+.p-6 { padding: 24px; }
+
+.px-1 { padding-left: 4px; padding-right: 4px; }
+.px-2 { padding-left: 8px; padding-right: 8px; }
+.px-3 { padding-left: 12px; padding-right: 12px; }
+.px-4 { padding-left: 16px; padding-right: 16px; }
+.px-5 { padding-left: 20px; padding-right: 20px; }
+.px-6 { padding-left: 24px; padding-right: 24px; }
+
+.py-1 { padding-top: 4px; padding-bottom: 4px; }
+.py-2 { padding-top: 8px; padding-bottom: 8px; }
+.py-3 { padding-top: 12px; padding-bottom: 12px; }
+.py-4 { padding-top: 16px; padding-bottom: 16px; }
+.py-5 { padding-top: 20px; padding-bottom: 20px; }
+.py-6 { padding-top: 24px; padding-bottom: 24px; }
+.py-8 { padding-top: 32px; padding-bottom: 32px; }
+.py-10 { padding-top: 40px; padding-bottom: 40px; }
+.py-16 { padding-top: 64px; padding-bottom: 64px; }
+.py-20 { padding-top: 80px; padding-bottom: 80px; }
+
+.pt-1 { padding-top: 4px; }
+.pt-2 { padding-top: 8px; }
+.pt-3 { padding-top: 12px; }
+.pt-4 { padding-top: 16px; }
+.pt-6 { padding-top: 24px; }
+.pt-8 { padding-top: 32px; }
+
+.pb-2 { padding-bottom: 8px; }
+.pb-4 { padding-bottom: 16px; }
+.pb-6 { padding-bottom: 24px; }
+.pb-8 { padding-bottom: 32px; }
+.pb-16 { padding-bottom: 64px; }
+.pb-24 { padding-bottom: 96px; }
+
+.pl-2 { padding-left: 8px; }
+.pl-4 { padding-left: 16px; }
+.pl-6 { padding-left: 24px; }
+
+.pr-2 { padding-right: 8px; }
+.pr-4 { padding-right: 16px; }
+
+/* ---------- 间距(margin) ---------- */
+.m-0 { margin: 0; }
+.m-1 { margin: 4px; }
+.m-2 { margin: 8px; }
+.m-3 { margin: 12px; }
+.m-4 { margin: 16px; }
+
+.mx-auto { margin-left: auto; margin-right: auto; }
+.mx-2 { margin-left: 8px; margin-right: 8px; }
+.mx-4 { margin-left: 16px; margin-right: 16px; }
+
+.my-1 { margin-top: 4px; margin-bottom: 4px; }
+.my-2 { margin-top: 8px; margin-bottom: 8px; }
+.my-3 { margin-top: 12px; margin-bottom: 12px; }
+.my-4 { margin-top: 16px; margin-bottom: 16px; }
+
+.mt-0 { margin-top: 0; }
+.mt-1 { margin-top: 4px; }
+.mt-2 { margin-top: 8px; }
+.mt-3 { margin-top: 12px; }
+.mt-4 { margin-top: 16px; }
+.mt-6 { margin-top: 24px; }
+
+.mb-0 { margin-bottom: 0; }
+.mb-1 { margin-bottom: 4px; }
+.mb-2 { margin-bottom: 8px; }
+.mb-3 { margin-bottom: 12px; }
+.mb-4 { margin-bottom: 16px; }
+.mb-6 { margin-bottom: 24px; }
+
+.ml-1 { margin-left: 4px; }
+.ml-2 { margin-left: 8px; }
+.ml-3 { margin-left: 12px; }
+.ml-4 { margin-left: 16px; }
+.ml-auto { margin-left: auto; }
+
+.mr-1 { margin-right: 4px; }
+.mr-2 { margin-right: 8px; }
+.mr-3 { margin-right: 12px; }
+.mr-4 { margin-right: 16px; }
+
+/* ---------- 尺寸(width/height) ---------- */
+.w-full { width: 100%; }
+.w-4 { width: 16px; }
+.w-5 { width: 20px; }
+.w-6 { width: 24px; }
+.w-8 { width: 32px; }
+.w-10 { width: 40px; }
+.w-12 { width: 48px; }
+.w-16 { width: 64px; }
+
+.min-w-0 { min-width: 0px; }
+.min-w-5 { min-width: 20px; }
+
+.h-full { height: 100%; }
+.h-4 { height: 16px; }
+.h-5 { height: 20px; }
+.h-6 { height: 24px; }
+.h-8 { height: 32px; }
+.h-10 { height: 40px; }
+.h-12 { height: 48px; }
+.h-16 { height: 64px; }
+.h-20 { height: 80px; }
+.h-24 { height: 96px; }
+
+/* ---------- 圆角 ---------- */
+.rounded { border-radius: 4px; }
+.rounded-sm { border-radius: 2px; }
+.rounded-md { border-radius: 6px; }
+.rounded-lg { border-radius: 8px; }
+.rounded-xl { border-radius: 12px; }
+.rounded-2xl { border-radius: 16px; }
+.rounded-full { border-radius: 9999px; }
+.rounded-none { border-radius: 0; }
+
+/* ---------- 边框 ---------- */
+.border { border: 1px solid $border-color; }
+.border-t { border-top: 1px solid $border-color; }
+.border-b { border-bottom: 1px solid $border-color; }
+.border-l { border-left: 1px solid $border-color; }
+.border-r { border-right: 1px solid $border-color; }
+.border-none { border: none; }
+
+.border-dashed { border-style: dashed; }
+.border-gray-100 { border-color: #f3f4f6; }
+.border-gray-200 { border-color: #e5e7eb; }
+.border-gray-300 { border-color: #d1d5db; }
+
+/* ---------- 定位 ---------- */
+.relative { position: relative; }
+.absolute { position: absolute; }
+.fixed { position: fixed; }
+.sticky { position: sticky; }
+
+.top-0 { top: 0; }
+.top-1 { top: 4px; }
+.right-0 { right: 0; }
+.right-1 { right: 4px; }
+.bottom-0 { bottom: 0; }
+.left-0 { left: 0; }
+
+.z-0 { z-index: 0; }
+.z-10 { z-index: 10; }
+.z-20 { z-index: 20; }
+.z-30 { z-index: 30; }
+.z-40 { z-index: 40; }
+
+/* ---------- 显示 ---------- */
+.block { display: block; }
+.inline-block { display: inline-block; }
+.inline { display: inline; }
+.hidden { display: none; }
+
+/* ---------- 溢出 ---------- */
+.overflow-hidden { overflow: hidden; }
+.overflow-auto { overflow: auto; }
+
+/* ---------- 文字截断 ---------- */
+.truncate {
+  overflow: hidden;
+  text-overflow: ellipsis;
+  white-space: nowrap;
+}
+
+.whitespace-nowrap { white-space: nowrap; }
+.break-all { word-break: break-all; }
+.break-words { overflow-wrap: break-word; }
+
+/* ---------- 行高 ---------- */
+.leading-none { line-height: 1; }
+.leading-tight { line-height: 1.25; }
+.leading-snug { line-height: 1.375; }
+.leading-normal { line-height: 1.5; }
+.leading-relaxed { line-height: 1.625; }
+
+/* ---------- 透明度和交互 ---------- */
+.opacity-50 { opacity: 0.5; }
+.opacity-90 { opacity: 0.9; }
+.pointer-events-none { pointer-events: none; }
+
+/* ---------- 其他常用 ---------- */
+.gap-1 { gap: 4px; }
+.gap-2 { gap: 8px; }
+.gap-3 { gap: 12px; }
+.gap-4 { gap: 16px; }
+
+.gap-y-2 > view + view { margin-top: 8px; }
+.gap-y-3 > view + view { margin-top: 12px; }
+.gap-y-4 > view + view { margin-top: 16px; }
+
+.space-y-1 > view + view { margin-top: 4px; }
+.space-y-2 > view + view { margin-top: 8px; }
+.space-y-3 > view + view { margin-top: 12px; }
+.space-y-4 > view + view { margin-top: 16px; }
+
+.space-x-1 > view + view { margin-left: 4px; }
+.space-x-2 > view + view { margin-left: 8px; }
+.space-x-3 > view + view { margin-left: 12px; }
+.space-x-4 > view + view { margin-left: 16px; }
+
+.flex-shrink-0 { flex-shrink: 0; }
+
+/* 装饰性下划线删除 */
+.line-through { text-decoration: line-through; }
+
+/* 旋转 */
+.animate-spin {
+  animation: spin 1s linear infinite;
+}
+
+@keyframes spin {
+  from { transform: rotate(0deg); }
+  to { transform: rotate(360deg); }
 }

+ 0 - 45
src/styles/tailwind.css

@@ -1,45 +0,0 @@
-/* 注意:微信小程序 WXSS 不支持 * 和 ::before/::after 选择器
-   因此不能包含 @tailwind base(Tailwind 的 CSS reset)
-   只保留 components 和 utilities */
-
-/* WXSS 兼容的基础样式(替代 @tailwind base) */
-page {
-  line-height: 1.5;
-  -webkit-text-size-adjust: 100%;
-}
-
-view, text, input, textarea, button, image, scroll-view, swiper, picker, label, navigator {
-  box-sizing: border-box;
-  margin: 0;
-  padding: 0;
-  border: 0 solid #e5e7eb;
-}
-
-button {
-  background-color: transparent;
-  border-radius: 0;
-  font-size: inherit;
-  line-height: inherit;
-  color: inherit;
-}
-
-button::after {
-  border: none;
-}
-
-input, textarea {
-  font-family: inherit;
-  font-size: inherit;
-  line-height: inherit;
-  color: inherit;
-}
-
-image {
-  display: block;
-  vertical-align: middle;
-  max-width: 100%;
-  height: auto;
-}
-
-@tailwind components;
-@tailwind utilities;

+ 28 - 2
src/styles/variables.scss

@@ -40,8 +40,34 @@ $nav-height: 70px;
 $top-bar-height: 44px;
 
 /* 阴影 */
-$shadow-card: 0 1px 3px rgba(0, 0, 0, 0.04), 0 2px 8px rgba(0, 0, 0, 0.04);
-$shadow-nav: 0 -2px 10px rgba(0, 0, 0, 0.06);
+$shadow-card: 0 2px 8px rgba(0, 0, 0, 0.06);
+$shadow-nav: 0 -2px 10px rgba(0, 0, 0, 0.08);
 
 /* 字体 */
 $font-family: -apple-system, BlinkMacSystemFont, 'SF Pro Display', 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
+
+/* ==================== 渐变变量(按原型) ==================== */
+
+/* 销售端统计卡片渐变 */
+$gradient-sales-1: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+$gradient-sales-2: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
+$gradient-sales-3: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);
+
+/* 调度端统计卡片渐变 */
+$gradient-dispatch-1: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+$gradient-dispatch-2: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);
+$gradient-dispatch-3: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
+
+/* 施工端统计卡片渐变 */
+$gradient-construction-1: linear-gradient(135deg, #3b82f6 0%, #2563eb 100%);
+$gradient-construction-2: linear-gradient(135deg, #60a5fa 0%, #3b82f6 100%);
+$gradient-construction-3: linear-gradient(135deg, #93c5fd 0%, #60a5fa 100%);
+
+/* 通用按钮渐变 */
+$gradient-primary: linear-gradient(135deg, #3b82f6 0%, #2563eb 100%);
+$gradient-success: linear-gradient(135deg, #10b981 0%, #059669 100%);
+$gradient-warning: linear-gradient(135deg, #f59e0b 0%, #d97706 100%);
+$gradient-danger: linear-gradient(135deg, #ef4444 0%, #dc2626 100%);
+
+/* 顶部导航渐变 */
+$gradient-topbar: linear-gradient(135deg, #3b82f6 0%, #2563eb 100%);

+ 71 - 25
src/subPackages/pages-common/addProject.vue

@@ -4,7 +4,14 @@
 
     <view class="px-4 py-4 gap-y-4">
       <!-- 基本信息 -->
-      <uni-card title="基本信息" :is-shadow="true">
+      <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>
+          <text>基本信息</text>
+        </view>
+
         <view class="gap-y-4">
           <view>
             <text class="form-label">
@@ -21,7 +28,7 @@
               <text class="text-red-500">*</text>
             </text>
             <picker mode="selector" :range="types" :value="typeIndex" @change="onTypeChange">
-              <view class="form-input flex items-center" :class="{ 'text-gray-400': !form.type }">
+              <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 || '请选择项目类型' }}
               </view>
@@ -53,10 +60,17 @@
             <text v-if="errors.address" class="text-red-500 text-xs mt-1">{{ errors.address }}</text>
           </view>
         </view>
-      </uni-card>
+      </view>
 
       <!-- 客户信息 -->
-      <uni-card title="客户信息" :is-shadow="true">
+      <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>
+          <text>客户信息</text>
+        </view>
+
         <view class="gap-y-4">
           <view>
             <text class="form-label">
@@ -83,10 +97,17 @@
             <text v-if="errors.customerPhone" class="text-red-500 text-xs mt-1">{{ errors.customerPhone }}</text>
           </view>
         </view>
-      </uni-card>
+      </view>
 
       <!-- 合同信息 -->
-      <uni-card title="合同信息" :is-shadow="true">
+      <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>合同信息</text>
+        </view>
+
         <view class="gap-y-4">
           <view>
             <text class="form-label">合同金额(元)</text>
@@ -97,7 +118,7 @@
             <view>
               <text class="form-label">合同开始日期</text>
               <picker mode="date" :value="form.contractStartDate" @change="onStartDateChange">
-                <view class="form-input flex items-center text-gray-700">
+                <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>
@@ -107,7 +128,7 @@
             <view>
               <text class="form-label">合同结束日期</text>
               <picker mode="date" :value="form.contractEndDate" @change="onEndDateChange">
-                <view class="form-input flex items-center text-gray-700">
+                <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>
@@ -120,10 +141,17 @@
             <uni-easyinput class="form-input" placeholder="如:每周2次" v-model="form.serviceCycle" />
           </view>
         </view>
-      </uni-card>
+      </view>
 
       <!-- 项目联系人 -->
-      <uni-card title="项目联系人" :is-shadow="true">
+      <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>
+          <text>项目联系人</text>
+        </view>
+
         <view class="gap-y-4">
           <view
             v-for="(contact, index) in form.contacts"
@@ -138,21 +166,31 @@
               </text>
             </view>
             <view class="gap-y-3">
-              <uni-easyinput class="form-input" placeholder="姓名" v-model="contact.name" />
-              <uni-easyinput class="form-input" placeholder="角色/职位" v-model="contact.role" />
+              <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>
 
-          <button class="w-full py-2 border border-dashed border-gray-300 rounded-xl text-sm text-gray-500 flex items-center justify-center" @click="addContact">
+          <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>
-      </uni-card>
+      </view>
 
       <!-- 服务点信息 -->
-      <uni-card title="服务点信息" :is-shadow="true">
+      <view class="card p-4">
+        <view class="detail-section-title mb-4">
+          <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>
+        </view>
+
         <view class="gap-y-4">
           <view
             v-for="(point, index) in form.servicePoints"
@@ -167,33 +205,42 @@
               </text>
             </view>
             <view class="gap-y-3">
-              <uni-easyinput class="form-input" placeholder="服务点名称" v-model="point.name" />
-              <uni-easyinput class="form-input" placeholder="服务点地址" v-model="point.address" />
-              <uni-easyinput class="form-input" placeholder="服务类型" v-model="point.serviceType" />
-              <uni-easyinput class="form-input" placeholder="联系人" v-model="point.contactName" />
+              <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" />
             </view>
           </view>
 
-          <button class="w-full py-2 border border-dashed border-gray-300 rounded-xl text-sm text-gray-500 flex items-center justify-center" @click="addServicePoint">
+          <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="addServicePoint"
+          >
             <text class="uni-icons uniui-plusempty mr-1"></text>
             添加服务点
           </button>
         </view>
-      </uni-card>
+      </view>
 
       <!-- 备注 -->
-      <uni-card title="备注" :is-shadow="true">
+      <view class="card p-4">
+        <view class="detail-section-title mb-4">
+          <view class="w-8 h-8 rounded-lg bg-gray-100 flex items-center justify-center mr-3">
+            <text class="uni-icons uniui-compose text-gray-500"></text>
+          </view>
+          <text>备注</text>
+        </view>
         <textarea
           v-model="form.remark"
           class="w-full p-3 bg-surface rounded-xl text-sm text-gray-700"
           placeholder="请输入项目备注..."
           :maxlength="200"
         />
-      </uni-card>
+      </view>
 
       <!-- 提交按钮 -->
-      <view class="px-4 pt-2">
+      <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="submitForm"
@@ -211,7 +258,6 @@
 <script setup lang="ts">
 import { ref, reactive } from 'vue'
 import TopBar from '../../components/common/TopBar.vue'
-import UniCard from '../../components/uni-card/uni-card.vue'
 import { createProject } from '../../api/project'
 
 const types = ['包年', '单次', '季度', '月付']

+ 159 - 71
src/subPackages/pages-common/projectDetail.vue

@@ -1,5 +1,5 @@
 <template>
-  <view class="app-container pb-20">
+  <view class="app-container pb-24">
     <TopBar title="项目详情" show-back />
 
     <view v-if="projectStore.loading" class="py-20 text-center">
@@ -11,66 +11,121 @@
     </view>
 
     <view v-else class="px-4 py-4 gap-y-4">
-      <!-- 项目基本信息 -->
-      <uni-card :title="project.name" :is-shadow="true">
-        <view class="flex justify-between items-start mb-3">
+      <!-- 项目状态卡片 -->
+      <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>
           <StatusTag :status="project.status" type="project" />
-          <text class="text-xs text-gray-400">{{ formatDate(project.createTime, 'YYYY-MM-DD') }}</text>
-        </view>
-
-        <uni-list :border="false">
-          <uni-list-item title="项目类型" :note="project.type" icon="uniui-folder-add-filled" />
-          <uni-list-item title="地址" :note="fullAddress" icon="uniui-location-filled" />
-          <uni-list-item title="客户" :note="project.customerName" icon="uniui-person-filled" />
-          <uni-list-item
-            title="电话"
-            :note="project.customerPhone"
-            icon="uniui-phone-filled"
-            @click="callPhone(project.customerPhone)"
-          />
-        </uni-list>
-      </uni-card>
+        </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>
+      </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>
 
       <!-- 合同信息 -->
-      <uni-card v-if="showContractInfo" title="合同信息" :is-shadow="true">
-        <uni-list :border="false">
-          <uni-list-item v-if="project.contractAmount" title="合同金额" :note="`¥${project.contractAmount.toLocaleString()}`" icon="uniui-wallet-filled" />
-          <uni-list-item v-if="project.contractStartDate" title="合同开始" :note="project.contractStartDate" icon="uniui-calendar-filled" />
-          <uni-list-item v-if="project.contractEndDate" title="合同结束" :note="project.contractEndDate" icon="uniui-calendar-filled" />
-          <uni-list-item v-if="project.serviceCycle" title="服务周期" :note="project.serviceCycle" icon="uniui-reload" />
-        </uni-list>
-      </uni-card>
+      <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>
+        </view>
+        <view class="detail-row">
+          <text class="detail-label">合同金额</text>
+          <text class="detail-value">{{ project.contractAmount ? `¥${project.contractAmount.toLocaleString()}` : '-' }}</text>
+        </view>
+        <view class="detail-row">
+          <text class="detail-label">合同开始</text>
+          <text class="detail-value">{{ project.contractStartDate || '-' }}</text>
+        </view>
+        <view class="detail-row">
+          <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>
 
       <!-- 项目联系人 -->
-      <uni-card v-if="project.contacts && project.contacts.length > 0" title="项目联系人" :is-shadow="true">
-        <view class="gap-y-3">
-          <view
-            v-for="(contact, index) in project.contacts"
-            :key="index"
-            class="flex items-center justify-between p-3 bg-surface rounded-lg"
-          >
+      <view v-if="project.contacts && project.contacts.length > 0" class="card p-4">
+        <view class="detail-section-title">
+          <text class="uni-icons uniui-person-filled text-primary mr-2"></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 ml-2">{{ contact.role }}</text>
+              <text v-if="contact.role" class="text-xs text-gray-400 block">{{ contact.role }}</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 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>
         </view>
-      </uni-card>
+      </view>
 
       <!-- 服务点信息 -->
-      <uni-card title="服务点与服务项" :is-shadow="true">
+      <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-for="(point, index) in project.servicePoints"
             :key="index"
-            class="mb-3 pb-3 border-b border-gray-100"
-            :class="{ 'border-b-0 pb-0 mb-0': index === project.servicePoints.length - 1 }"
+            class="py-3"
+            :class="{ 'border-t border-gray-100': index > 0 }"
           >
             <view class="flex items-center mb-2">
               <text class="uni-icons uniui-location-filled text-primary mr-2"></text>
@@ -85,13 +140,17 @@
             </view>
           </view>
         </view>
-        <view v-else class="text-center py-4">
+        <view v-else class="py-4 text-center">
           <text class="text-sm text-gray-400">暂无服务点信息</text>
         </view>
-      </uni-card>
+      </view>
 
       <!-- 项目位置 -->
-      <uni-card title="项目位置" icon="uniui-location-filled" :is-shadow="true">
+      <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"
@@ -99,35 +158,57 @@
           :address="fullAddress"
           :height="180"
         />
-      </uni-card>
+      </view>
 
       <!-- 备注 -->
-      <uni-card v-if="project.remark" title="备注" :is-shadow="true">
+      <view v-if="project.remark" class="card p-4">
+        <view class="detail-section-title">
+          <text class="uni-icons uniui-compose text-primary mr-2"></text>
+          <text>备注</text>
+        </view>
         <text class="text-sm text-gray-600">{{ project.remark }}</text>
-      </uni-card>
+      </view>
 
       <!-- 关联任务 -->
-      <uni-card title="关联任务" :is-shadow="true">
+      <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">
-          <uni-list :border="false">
-            <uni-list-item
-              v-for="task in projectTasks"
-              :key="task.id"
-              :title="task.projectName"
-              :note="formatDate(task.scheduleDate || task.serviceDate || task.createTime, 'YYYY-MM-DD')"
-              show-arrow
-              @click="goToTask(task.id)"
-            >
-              <template #footer>
-                <StatusTag :status="task.status" />
-              </template>
-            </uni-list-item>
-          </uni-list>
-        </view>
-        <view v-else class="text-center py-4">
+          <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>
-      </uni-card>
+      </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">
+          <text class="uni-icons uniui-plusempty mr-2"></text>
+          发布新任务
+        </button>
+      </view>
     </view>
   </view>
 </template>
@@ -139,9 +220,6 @@ import { useTaskStore } from '../../stores/task'
 import TopBar from '../../components/common/TopBar.vue'
 import StatusTag from '../../components/common/StatusTag.vue'
 import EmptyState from '../../components/common/EmptyState.vue'
-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 MapView from '../../components/common/MapView.vue'
 import { formatDate } from '../../utils'
 
@@ -180,6 +258,16 @@ function goToTask(taskId: string) {
   uni.navigateTo({ url: '/subPackages/pages-common/taskDetail' })
 }
 
+function goToTaskList() {
+  uni.switchTab({ url: '/pages/task/task' })
+}
+
+function goToPublishTask() {
+  uni.navigateTo({ 
+    url: `/subPackages/pages-common/publishTask?projectId=${project.value?.id}` 
+  })
+}
+
 onMounted(async () => {
   if (!project.value) {
     const pages = getCurrentPages()

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

@@ -5,16 +5,16 @@
     <view class="px-4 py-4">
       <!-- 板块一:项目基本信息 -->
       <view class="card p-4 mb-4">
-        <view class="flex items-center mb-4">
-          <view class="w-8 h-8 bg-blue-100 rounded-lg flex items-center justify-center mr-3">
-            <text class="uni-icons uniui-flag text-blue-500"></text>
+        <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 class="text-base font-semibold text-gray-800">项目基本信息</text>
+          <text>项目基本信息</text>
         </view>
 
         <!-- 项目选择提示 -->
         <view class="bg-blue-50 rounded-lg px-3 py-2 mb-4 flex items-center">
-          <text class="text-blue-500 mr-2 text-sm">ℹ️</text>
+          <text class="uni-icons uniui-info-filled text-blue-500 mr-2 text-sm"></text>
           <text class="text-blue-600 text-sm">请从项目库选择或新增项目</text>
         </view>
 
@@ -26,7 +26,7 @@
           <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="text-gray-400 text-xs"></text>
+              <text class="uni-icons uniui-arrowdown text-gray-400"></text>
             </view>
           </picker>
         </view>
@@ -34,7 +34,7 @@
         <!-- 已选项目信息 -->
         <view v-if="selectedProject" class="bg-green-50 rounded-xl p-3 mb-4">
           <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.name }}</text>
             <text class="text-green-500 text-xs" @click="resetProjectSelection">重新选择</text>
           </view>
           <view class="gap-y-2">
@@ -48,7 +48,7 @@
             </view>
             <view class="flex justify-between text-sm">
               <text class="text-gray-500">项目地址:</text>
-              <text class="text-gray-700">{{ selectedProject.address }}</text>
+              <text class="text-gray-700 flex-1 text-right">{{ selectedProject.address }}</text>
             </view>
           </view>
         </view>
@@ -57,12 +57,16 @@
         <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" class="bg-gray-50 rounded-lg p-3 flex justify-between items-center">
+            <view 
+              v-for="(contact, index) in selectedProject.contacts" 
+              :key="index" 
+              class="bg-gray-50 rounded-lg p-3 flex justify-between items-center"
+            >
               <view>
                 <text class="text-sm text-gray-800">{{ contact.name }}</text>
                 <text class="text-xs text-gray-400 ml-2">{{ contact.role }}</text>
               </view>
-              <text class="text-sm text-blue-500">{{ contact.phone }}</text>
+              <text class="text-sm text-primary">{{ contact.phone }}</text>
             </view>
           </view>
         </view>
@@ -70,11 +74,11 @@
 
       <!-- 板块二:服务信息 -->
       <view class="card p-4">
-        <view class="flex items-center mb-4">
-          <view class="w-8 h-8 bg-purple-100 rounded-lg flex items-center justify-center mr-3">
-            <text class="text-purple-500"></text>
+        <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 class="text-base font-semibold text-gray-800">服务信息</text>
+          <text>服务信息</text>
         </view>
 
         <!-- 服务时间 -->
@@ -83,14 +87,16 @@
             <text class="text-red-500">*</text>服务时间
           </text>
           <view class="flex gap-2">
-            <picker mode="date" :value="serviceDate" @change="onServiceDateChange">
+            <picker mode="date" :value="serviceDate" @change="onServiceDateChange" class="flex-1">
               <view class="form-select flex justify-between items-center">
                 <text :class="serviceDate ? 'text-gray-800' : 'text-gray-400'">{{ serviceDate || '选择日期' }}</text>
+                <text class="uni-icons uniui-calendar text-gray-400"></text>
               </view>
             </picker>
-            <picker mode="time" :value="serviceTime" @change="onServiceTimeChange">
-              <view class="form-select flex justify-between items-center" style="flex: 0.6;">
+            <picker mode="time" :value="serviceTime" @change="onServiceTimeChange" style="flex: 0.6;">
+              <view class="form-select flex justify-between items-center">
                 <text :class="serviceTime ? 'text-gray-800' : 'text-gray-400'">{{ serviceTime || '选择时间' }}</text>
+                <text class="uni-icons uniui-clock text-gray-400"></text>
               </view>
             </picker>
           </view>
@@ -107,9 +113,12 @@
         <!-- 地理位置 -->
         <view class="form-group">
           <text class="form-label">地理位置</text>
-          <view class="bg-gray-100 rounded-xl h-24 flex items-center justify-center border border-gray-200" @click="showToast('地图定位功能开发中')">
+          <view 
+            class="bg-gray-100 rounded-xl h-24 flex items-center justify-center border border-gray-200" 
+            @click="showToast('地图定位功能开发中')"
+          >
             <view class="text-center">
-              <text class="text-gray-400 text-2xl block mb-1">️</text>
+              <text class="uni-icons uniui-location text-gray-400 text-2xl block mb-1"></text>
               <text class="text-gray-400 text-xs">点击获取当前位置</text>
             </view>
           </view>
@@ -125,7 +134,7 @@
               v-for="type in faultTypes"
               :key="type"
               class="px-3 py-1 rounded-full text-sm border"
-              :class="selectedFaultTypes.includes(type) ? 'bg-blue-500 text-white border-blue-500' : 'bg-gray-100 text-gray-600 border-gray-200'"
+              :class="selectedFaultTypes.includes(type) ? 'bg-primary text-white border-primary' : 'bg-gray-100 text-gray-600 border-gray-200'"
               @click="toggleFaultType(type)"
             >
               {{ type }}
@@ -155,7 +164,7 @@
           <picker mode="selector" :range="serviceTypes" :value="serviceTypeIndex" @change="onServiceTypeChange">
             <view class="form-select flex justify-between items-center">
               <text :class="serviceType ? 'text-gray-800' : 'text-gray-400'">{{ serviceType || '请选择施工类型' }}</text>
-              <text class="text-gray-400 text-xs"></text>
+              <text class="uni-icons uniui-arrowdown text-gray-400"></text>
             </view>
           </picker>
         </view>
@@ -168,7 +177,7 @@
           <picker mode="selector" :range="facilityOptions" :value="facilityIndex" @change="onFacilityChange">
             <view class="form-select flex justify-between items-center">
               <text :class="facility ? 'text-gray-800' : 'text-gray-400'">{{ facility || '请选择施工设施' }}</text>
-              <text class="text-gray-400 text-xs"></text>
+              <text class="uni-icons uniui-arrowdown text-gray-400"></text>
             </view>
           </picker>
         </view>
@@ -181,7 +190,7 @@
           <picker mode="selector" :range="serviceModes" :value="serviceModeIndex" @change="onServiceModeChange">
             <view class="form-select flex justify-between items-center">
               <text :class="serviceMode ? 'text-gray-800' : 'text-gray-400'">{{ serviceMode || '请选择服务方式' }}</text>
-              <text class="text-gray-400 text-xs"></text>
+              <text class="uni-icons uniui-arrowdown text-gray-400"></text>
             </view>
           </picker>
         </view>
@@ -194,7 +203,7 @@
           <picker mode="selector" :range="vehicleOptions" :value="vehicleIndex" @change="onVehicleChange">
             <view class="form-select flex justify-between items-center">
               <text :class="vehicle ? 'text-gray-800' : 'text-gray-400'">{{ vehicle || '请选择车辆' }}</text>
-              <text class="text-gray-400 text-xs"></text>
+              <text class="uni-icons uniui-arrowdown text-gray-400"></text>
             </view>
           </picker>
         </view>
@@ -205,7 +214,7 @@
           <picker mode="selector" :range="waterOptions" :value="waterIndex" @change="onWaterChange">
             <view class="form-select flex justify-between items-center">
               <text :class="waterSource ? 'text-gray-800' : 'text-gray-400'">{{ waterSource || '请选择' }}</text>
-              <text class="text-gray-400 text-xs"></text>
+              <text class="uni-icons uniui-arrowdown text-gray-400"></text>
             </view>
           </picker>
         </view>
@@ -242,7 +251,7 @@
           <picker mode="selector" :range="acceptTypes" :value="acceptTypeIndex" @change="onAcceptTypeChange">
             <view class="form-select flex justify-between items-center">
               <text :class="acceptType ? 'text-gray-800' : 'text-gray-400'">{{ acceptType || '请选择验收方式' }}</text>
-              <text class="text-gray-400 text-xs"></text>
+              <text class="uni-icons uniui-arrowdown text-gray-400"></text>
             </view>
           </picker>
         </view>
@@ -250,17 +259,15 @@
         <!-- 故障图片 -->
         <view class="form-group">
           <text class="form-label">故障图片/视频</text>
-          <view class="border-2 border-dashed border-gray-200 rounded-xl p-6 text-center" @click="showToast('上传功能开发中')">
-            <text class="text-gray-300 text-2xl block mb-2">️</text>
-            <text class="text-gray-400 text-sm">点击上传图片或视频</text>
-            <text class="text-gray-300 text-xs block mt-1">支持 JPG、PNG、MP4 格式</text>
-          </view>
+          <PhotoUploader v-model="photos" :max-count="9" />
         </view>
       </view>
 
       <!-- 提交按钮 -->
       <view class="pt-4 pb-6">
-        <button class="w-full py-3 bg-blue-500 text-white rounded-xl font-medium" @click="submitTask">发布任务</button>
+        <button class="w-full py-4 btn-primary rounded-2xl text-base font-semibold" @click="submitTask">
+          发布任务
+        </button>
       </view>
     </view>
   </view>
@@ -270,6 +277,7 @@
 import { ref, computed } from 'vue'
 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'
@@ -316,6 +324,7 @@ const acceptanceReq = ref('')
 const acceptTypes = ['签署验收计量单', '累计自动计费']
 const acceptType = ref('')
 const acceptTypeIndex = computed(() => acceptTypes.indexOf(acceptType.value))
+const photos = ref<string[]>([])
 
 onLoad((query) => {
   if (query?.projectId) {
@@ -453,31 +462,3 @@ function submitTask() {
 }
 </script>
 
-<style scoped>
-.form-group {
-  margin-bottom: 16px;
-}
-
-.form-select {
-  width: 100%;
-  padding: 12px 16px;
-  border: 1px solid #e5e7eb;
-  border-radius: 8px;
-  font-size: 14px;
-  color: #333;
-  background: #fff;
-  box-sizing: border-box;
-}
-
-.form-textarea {
-  width: 100%;
-  padding: 12px 16px;
-  border: 1px solid #e5e7eb;
-  border-radius: 8px;
-  font-size: 14px;
-  color: #333;
-  background: #fff;
-  min-height: 100px;
-  box-sizing: border-box;
-}
-</style>

+ 124 - 222
src/subPackages/pages-common/taskDetail.vue

@@ -1,244 +1,146 @@
 <template>
-  <view class="app-container pb-20">
-    <TopBar :title="'任务详情'" show-back />
-
-    <view v-if="taskStore.loading" class="py-20 text-center">
-      <text class="text-gray-400">加载中...</text>
-    </view>
-
-    <view v-else-if="!task" class="px-4 py-6">
-      <EmptyState message="任务不存在" />
-    </view>
-
-    <view v-else class="px-4 py-4 gap-y-4">
-      <!-- 项目信息 -->
-      <uni-card title="项目信息" :is-shadow="true">
+  <view class="app-container">
+    <TopBar title="任务详情" show-back />
+    <view class="px-4 py-4 pb-8">
+      <!-- 任务状态卡片 -->
+      <view class="card p-4">
         <view class="flex justify-between items-start mb-3">
-          <view class="flex-1 min-w-0 mr-3">
-            <text class="text-base font-semibold text-gray-800 block">{{ task.projectName }}</text>
+          <view>
+            <text class="text-base font-bold text-gray-800">{{ task.projectName }}</text>
+            <view class="mt-2">
+              <StatusTag :status="task.status" />
+              <text v-if="task.isEmergency" class="tag-danger ml-2 px-2 py-1 rounded text-xs">应急</text>
+            </view>
           </view>
-          <StatusTag :status="task.status" />
+          <text class="text-xs text-gray-400">{{ 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>
+        <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>
+      </view>
 
-        <uni-list :border="false">
-          <uni-list-item title="地址" :note="task.address || '暂无地址'" icon="uniui-location-filled" />
-          <uni-list-item title="客户" :note="task.customerName || '暂无客户'" icon="uniui-person-filled" />
-          <uni-list-item
-            title="电话"
-            :note="task.customerPhone || '暂无电话'"
-            icon="uniui-phone-filled"
-            @click="callPhone(task.customerPhone)"
-          />
-          <uni-list-item v-if="task.coopType" title="合作类型" :note="task.coopType" icon="uniui-folder-add-filled" />
-          <uni-list-item v-if="task.serviceMode" title="服务方式" :note="task.serviceMode" icon="uniui-calendar-filled" />
-        </uni-list>
-      </uni-card>
-
-      <!-- 施工信息 -->
-      <uni-card title="施工信息" :is-shadow="true">
-        <uni-list :border="false">
-          <uni-list-item title="施工类型" :note="task.serviceType || '未填写'" icon="uniui-tools" />
-          <uni-list-item title="设施类型" :note="task.facilityType || '未填写'" icon="uniui-flag-filled" />
-          <uni-list-item title="服务时间" :note="serviceTimeText" icon="uniui-clock-filled" />
-          <uni-list-item v-if="task.team" title="施工班组" :note="task.team" icon="uniui-personadd-filled" />
-          <uni-list-item v-if="task.vehicleNo" title="车辆" :note="task.vehicleNo" icon="uniui-cart-filled" />
-          <uni-list-item v-if="task.acceptType" title="验收方式" :note="task.acceptType" icon="uniui-checkmarkempty" />
-        </uni-list>
-      </uni-card>
-
-      <!-- 故障信息 -->
-      <uni-card v-if="task.faultLocation || task.faultDesc" title="故障信息" :is-shadow="true">
-        <uni-list :border="false">
-          <uni-list-item v-if="task.faultLocation" title="故障位置" :note="task.faultLocation" icon="uniui-location-filled" />
-          <uni-list-item v-if="task.faultDesc" title="故障描述" :note="task.faultDesc" icon="uniui-compose" />
-        </uni-list>
-      </uni-card>
-
-      <!-- 任务进度 -->
-      <uni-card title="任务进度" :is-shadow="true">
-        <view class="flex items-center justify-between px-2">
-          <view
-            v-for="(step, index) in progressSteps"
-            :key="step.key"
-            class="flex-1 flex flex-col items-center relative"
-          >
-            <view
-              class="w-8 h-8 rounded-full flex items-center justify-center text-xs mb-1"
-              :class="step.active ? 'bg-primary text-white' : 'bg-gray-100 text-gray-400'"
-            >
-              <text class="uni-icons" :class="step.icon"></text>
-            </view>
-            <text class="text-xs" :class="step.active ? 'text-primary font-medium' : 'text-gray-400'">{{ step.label }}</text>
+      <!-- 基本信息 -->
+      <view class="card">
+        <view class="detail-section-title">
+          <text class="uni-icons uniui-info-filled text-primary mr-2"></text>
+          <text>基本信息</text>
+        </view>
+        <view class="detail-row">
+          <text class="detail-label">服务类型</text>
+          <text class="detail-value">{{ task.serviceType }}</text>
+        </view>
+        <view class="detail-row">
+          <text class="detail-label">客户名称</text>
+          <text class="detail-value">{{ task.customerName }}</text>
+        </view>
+        <view class="detail-row">
+          <text class="detail-label">联系电话</text>
+          <text class="detail-value">{{ task.customerPhone }}</text>
+        </view>
+        <view class="detail-row">
+          <text class="detail-label">项目地址</text>
+          <text class="detail-value">{{ task.address }}</text>
+        </view>
+        <view class="detail-row">
+          <text class="detail-label">预约时间</text>
+          <text class="detail-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>
-      </uni-card>
+      </view>
 
-      <!-- 施工照片 -->
-      <uni-card title="施工照片" icon="uniui-camera-filled" :is-shadow="true">
-        <ImageUploader v-model="taskImages" tip="上传施工过程照片" />
-      </uni-card>
-
-      <!-- 地图位置 -->
-      <uni-card title="项目位置" icon="uniui-location-filled" :is-shadow="true">
-        <MapView
-          :latitude="taskLatitude"
-          :longitude="taskLongitude"
-          :title="task.projectName"
-          :address="task.address"
-          :height="180"
-        />
-      </uni-card>
+      <!-- 服务点信息 -->
+      <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>
+        <view
+          v-for="(point, index) in task.servicePoints"
+          :key="index"
+          class="service-point-card"
+        >
+          <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>
+          </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>
+        </view>
+      </view>
 
-      <!-- 操作按钮 -->
-      <uni-card :is-shadow="true">
-        <view class="grid grid-cols-2 gap-3">
-          <button
-            v-if="canUrge"
-            class="py-3 bg-warning text-white rounded-xl text-sm flex items-center justify-center"
-            @click="urgeTask"
-          >
-            <text class="uni-icons uniui-notification-filled mr-1"></text>
-            催单
-          </button>
-          <button
-            v-if="canCancel"
-            class="py-3 bg-danger text-white rounded-xl text-sm flex items-center justify-center"
-            @click="cancelTask"
-          >
-            <text class="uni-icons uniui-closeempty mr-1"></text>
-            取消任务
-          </button>
-          <button
-            v-if="canViewAcceptance"
-            class="py-3 bg-success text-white rounded-xl text-sm flex items-center justify-center"
-            @click="viewAcceptance"
-          >
-            <text class="uni-icons uniui-checkmarkempty mr-1"></text>
-            查看验收单
-          </button>
-          <button
-            v-if="canRebook"
-            class="py-3 bg-primary text-white rounded-xl text-sm flex items-center justify-center"
-            @click="rebookTask"
-          >
-            <text class="uni-icons uniui-calendar-filled mr-1"></text>
-            再次预约
-          </button>
+      <!-- 处理记录 -->
+      <view class="card">
+        <view class="detail-section-title">
+          <text class="uni-icons uniui-compose-filled text-primary mr-2"></text>
+          <text>处理记录</text>
         </view>
-      </uni-card>
+        <Timeline :records="timelineRecords" />
+      </view>
     </view>
   </view>
 </template>
 
 <script setup lang="ts">
-import { ref, computed, onMounted, onUnmounted } from 'vue'
-import { useTaskStore } from '../../stores/task'
-import { useAuthStore } from '../../stores/auth'
-import TopBar from '../../components/common/TopBar.vue'
-import StatusTag from '../../components/common/StatusTag.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 { urgeTask as apiUrgeTask, cancelTask as apiCancelTask } from '../../api/task'
-
-import ImageUploader from '../../components/common/ImageUploader.vue'
-import MapView from '../../components/common/MapView.vue'
+import { ref, computed } from 'vue'
+import { useTaskStore } from '@/stores/task'
+import TopBar from '@/components/common/TopBar.vue'
+import StatusTag from '@/components/common/StatusTag.vue'
+import Timeline from '@/components/common/Timeline.vue'
+import type { TimelineRecord } from '@/components/common/Timeline.vue'
 
 const taskStore = useTaskStore()
-const authStore = useAuthStore()
-const task = computed(() => taskStore.currentTask)
-const taskImages = ref<string[]>([])
-
-const taskLatitude = computed(() => task.value?.location?.lat || 39.9)
-const taskLongitude = computed(() => task.value?.location?.lng || 116.4)
-
-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 progressSteps = computed(() => {
-  const status = task.value?.status || 'pending'
-  return [
-    { key: 'publish', label: '发布', icon: 'uniui-compose', active: true },
-    { key: 'dispatch', label: '调度确认', icon: 'uniui-flag-filled', active: ['confirmed', 'scheduled', 'departed', 'arrived', 'constructing', 'inspecting', 'completed'].includes(status) },
-    { key: 'constructing', label: '施工中', icon: 'uniui-gear-filled', active: ['constructing', 'inspecting', 'completed'].includes(status) },
-    { key: 'completed', label: '完成', icon: 'uniui-checkmarkempty', active: status === 'completed' },
-  ]
-})
-
-const canUrge = computed(() => ['pending', 'confirmed', 'scheduled'].includes(task.value?.status || ''))
-const canCancel = computed(() => ['pending', 'confirmed', 'scheduled'].includes(task.value?.status || ''))
-const canViewAcceptance = computed(() => task.value?.status === 'completed')
-const canRebook = computed(() => task.value?.status === 'completed')
-
-function callPhone(phone: string | undefined) {
-  if (!phone) return
-  uni.makePhoneCall({ phoneNumber: phone })
-}
-
-async function urgeTask() {
-  if (!task.value) return
-  try {
-    await apiUrgeTask(
-      Number(task.value.id),
-      '客户催单',
-      Number(authStore.user?.id || 0),
-      authStore.user?.name || '销售'
-    )
-    uni.showToast({ title: '催单成功', icon: 'success' })
-  } catch (error) {
-    uni.showToast({ title: '催单失败', icon: 'none' })
+const task = computed(() => taskStore.currentTask || {})
+
+const timelineRecords = ref<TimelineRecord[]>([
+  {
+    action: '任务已创建,等待调度',
+    time: '2026-04-20 09:00',
+    operator: '销售-李明',
+    role: 'sales',
+    isCurrent: false
+  },
+  {
+    action: '任务已排班,分配工程一班',
+    time: '2026-04-20 10:30',
+    operator: '调度-王调度',
+    role: 'dispatch',
+    isCurrent: false
+  },
+  {
+    action: '工程人员已出发',
+    time: '2026-04-20 11:00',
+    operator: '施工-张师傅',
+    role: 'worker',
+    isCurrent: true
   }
-}
-
-async function cancelTask() {
-  if (!task.value) return
-  uni.showModal({
-    title: '确认取消',
-    content: '确定要取消此任务吗?',
-    success: async (res) => {
-      if (res.confirm) {
-        try {
-          await apiCancelTask(
-            Number(task.value!.id),
-            '销售取消',
-            Number(authStore.user?.id || 0),
-            authStore.user?.name || '销售'
-          )
-          uni.showToast({ title: '取消成功', icon: 'success' })
-          await taskStore.fetchTaskDetail(Number(task.value!.id))
-        } catch (error) {
-          uni.showToast({ title: '取消失败', icon: 'none' })
-        }
-      }
-    },
-  })
-}
+])
+</script>
 
-function viewAcceptance() {
-  uni.showToast({ title: '验收单功能开发中', icon: 'none' })
+<style scoped>
+/* 仅保留组件特有样式 */
+.service-point-card {
+  background: #f9fafb;
+  border-radius: 8px;
+  padding: 12px;
+  margin: 8px 16px;
 }
 
-function rebookTask() {
-  uni.navigateTo({ url: '/subPackages/pages-common/publishTask' })
+.tag-blue {
+  background: #e6f0ff;
+  color: #2563eb;
 }
-
-onMounted(() => {
-  if (!task.value) {
-    const pages = getCurrentPages()
-    const currentPage = pages[pages.length - 1] as any
-    const taskId = currentPage.options?.id
-    if (taskId) {
-      taskStore.fetchTaskDetail(Number(taskId))
-    }
-  }
-})
-
-onUnmounted(() => {
-  taskStore.currentTask = null
-})
-</script>
+</style>

+ 70 - 63
src/subPackages/pages-construction/dailyTask.vue

@@ -4,7 +4,11 @@
 
     <view class="px-4 py-4 gap-y-4">
       <!-- 今日任务概览 -->
-      <uni-card title="今日概览" icon="uniui-calendar-filled">
+      <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>
         <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>
@@ -15,94 +19,96 @@
             <text class="text-xs text-gray-500 mt-1 block">已完成</text>
           </view>
         </view>
-      </uni-card>
+      </view>
 
       <!-- 车辆检查 -->
-      <uni-card
-        title="车辆检查"
-        icon="uniui-cart-filled"
-        :extra="vehicleCheckStatus ? '已完成' : '待检查'"
-        :extra-color="vehicleCheckStatus ? '#34c759' : '#ff9500'"
-      >
+      <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>
+
         <view v-if="!vehicleCheckStatus">
+          <!-- 常规检查 -->
           <view class="mb-3">
             <text class="text-sm font-medium text-gray-700 mb-2 block">常规检查</text>
-            <uni-list :border="false">
-              <uni-list-item
+            <view class="space-y-2">
+              <view
                 v-for="item in checkItems"
                 :key="item.key"
-                :title="item.label"
-                clickable
+                class="flex items-center justify-between py-2"
                 @click="toggleCheck(item.key)"
               >
-                <template #header>
-                  <view
-                    class="w-6 h-6 rounded-full border-2 flex items-center justify-center mr-3"
-                    :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>
-                </template>
-              </uni-list-item>
-            </uni-list>
+                <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>
+            </view>
           </view>
 
-          <view class="mb-3">
+          <!-- 设备检查 -->
+          <view class="mb-3 border-t border-gray-100 pt-3">
             <text class="text-sm font-medium text-gray-700 mb-2 block">设备检查</text>
-            <uni-list :border="false">
-              <uni-list-item
+            <view class="space-y-2">
+              <view
                 v-for="item in equipmentItems"
                 :key="item.key"
-                :title="item.label"
-                clickable
+                class="flex items-center justify-between py-2"
                 @click="toggleCheck(item.key)"
               >
-                <template #header>
-                  <view
-                    class="w-6 h-6 rounded-full border-2 flex items-center justify-center mr-3"
-                    :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>
-                </template>
-              </uni-list-item>
-            </uni-list>
+                <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>
+            </view>
           </view>
 
-          <view class="mb-3">
+          <!-- 三大系统检查 -->
+          <view class="mb-3 border-t border-gray-100 pt-3">
             <text class="text-sm font-medium text-gray-700 mb-2 block">三大系统检查</text>
-            <uni-list :border="false">
-              <uni-list-item
+            <view class="space-y-2">
+              <view
                 v-for="item in systemItems"
                 :key="item.key"
-                :title="item.label"
-                clickable
+                class="flex items-center justify-between py-2"
                 @click="toggleCheck(item.key)"
               >
-                <template #header>
-                  <view
-                    class="w-6 h-6 rounded-full border-2 flex items-center justify-center mr-3"
-                    :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>
-                </template>
-              </uni-list-item>
-            </uni-list>
+                <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>
+            </view>
           </view>
 
+          <!-- 备注 -->
           <view class="mb-3">
-            <text class="text-sm font-medium text-gray-700 mb-2 block">备注</text>
+            <text class="form-label">备注</text>
             <textarea
               v-model="remark"
-              class="w-full p-3 bg-surface rounded-xl text-sm text-gray-700"
+              class="w-full p-3 bg-surface rounded-xl text-sm text-gray-700 border border-gray-200"
               placeholder="请输入检查备注..."
               :maxlength="200"
             />
           </view>
 
           <button
-            class="w-full py-3 bg-primary text-white rounded-xl text-sm font-medium"
+            class="w-full py-3 btn-primary rounded-xl text-sm font-medium"
             :disabled="!vehicleCheckStatus"
             @click="submitCheck"
           >
@@ -118,25 +124,29 @@
           <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>
         </view>
-      </uni-card>
+      </view>
 
       <!-- 今日任务列表 -->
-      <uni-card title="今日任务列表" icon="uniui-flag-filled">
+      <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>
         <view v-if="taskStore.loading" class="py-10 text-center">
           <text class="text-gray-400">加载中...</text>
         </view>
         <view v-else-if="todayTasks.length === 0">
           <EmptyState message="今日无任务" />
         </view>
-        <view v-else class="gap-y-3">
+        <view v-else class="space-y-3">
           <TaskCard
             v-for="task in todayTasks"
             :key="task.id"
             :task="task"
-            @click="goToDetail"
+            @click="goToDetail(task.id)"
           />
         </view>
-      </uni-card>
+      </view>
     </view>
   </view>
 </template>
@@ -147,9 +157,6 @@ import { useTaskStore } from '../../stores/task'
 import TopBar from '../../components/common/TopBar.vue'
 import TaskCard from '../../components/task/TaskCard.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'
 
 const taskStore = useTaskStore()
 

+ 58 - 139
src/subPackages/pages-construction/taskDetail.vue

@@ -1,28 +1,13 @@
 <template>
-  <view class="app-container pb-4">
+  <view class="app-container pb-24">
     <TopBar title="任务详情" showBack />
 
     <view v-if="task" class="px-4 py-4 gap-y-4">
       <!-- 施工流程进度 -->
-      <view class="bg-white rounded-card p-4 shadow-card">
-        <view class="flex items-center justify-between">
-          <view
-            v-for="(step, index) in flowSteps"
-            :key="step.key"
-            class="flex-1 flex flex-col items-center"
-          >
-            <view
-              class="w-7 h-7 rounded-full flex items-center justify-center text-xs mb-1"
-              :class="stepStatusClass(step.key)"
-            >
-              {{ step.label }}
-            </view>
-          </view>
-        </view>
-      </view>
+      <ConstructionFlowBar :status="task.status" :timeline="task.timeline" />
 
       <!-- 操作按钮区 -->
-      <view class="bg-white rounded-card p-4 shadow-card">
+      <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">
@@ -30,7 +15,7 @@
             <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 bg-green-500 text-white rounded-xl font-medium flex items-center justify-center gap-2" @click="onConfirm">
+          <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">
@@ -58,7 +43,7 @@
               <text class="text-gray-800">{{ task.vehicle }}</text>
             </view>
           </view>
-          <button class="w-full py-3 bg-gradient-to-r from-orange-500 to-orange-600 text-white rounded-xl font-semibold flex items-center justify-center gap-2" @click="onDepart">
+          <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>
@@ -70,7 +55,7 @@
             <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 bg-gradient-to-r from-green-500 to-green-600 text-white rounded-xl font-semibold flex items-center justify-center gap-2" @click="onArrive">
+          <button class="w-full py-3 btn-success rounded-xl font-semibold flex items-center justify-center gap-2" @click="onArrive">
             <text>到达现场</text>
           </button>
         </view>
@@ -82,7 +67,7 @@
             <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 bg-gradient-to-r from-blue-500 to-blue-600 text-white rounded-xl font-semibold flex items-center justify-center gap-2" @click="onStartWork">
+          <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>
@@ -94,7 +79,7 @@
             <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 bg-gradient-to-r from-green-500 to-green-600 text-white rounded-xl font-semibold flex items-center justify-center gap-2" @click="onComplete">
+          <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>
@@ -106,7 +91,7 @@
             <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 bg-gradient-to-r from-green-500 to-green-600 text-white rounded-xl font-semibold flex items-center justify-center gap-2" @click="onAccept">
+          <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>
@@ -122,147 +107,93 @@
       </view>
 
       <!-- 任务信息 -->
-      <view class="bg-white rounded-card shadow-card overflow-hidden">
-        <view class="detail-section-title">
+      <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-2" />
+          <StatusTag :status="task.status" class="ml-auto" />
         </view>
-        <view class="p-4 gap-y-3">
-          <view class="flex justify-between text-sm">
-            <text class="text-gray-400">任务类型</text>
-            <text class="text-gray-800">{{ getTypeText(task.type) }}</text>
+        <view class="space-y-3">
+          <view class="detail-row">
+            <text class="detail-label">任务类型</text>
+            <text class="detail-value">{{ getTypeText(task.type) }}</text>
           </view>
-          <view class="flex justify-between text-sm">
-            <text class="text-gray-400">紧急程度</text>
-            <text :class="task.urgent === 'high' ? 'text-red-500' : 'text-gray-800'">{{ getUrgentText(task.urgent) }}</text>
+          <view class="detail-row">
+            <text class="detail-label">紧急程度</text>
+            <text :class="task.urgent === 'high' ? 'text-danger' : 'text-gray-800'">{{ getUrgentText(task.urgent) }}</text>
           </view>
-          <view v-if="task.serviceType" class="flex justify-between text-sm">
-            <text class="text-gray-400">施工类型</text>
-            <text class="text-gray-800">{{ task.serviceType }}</text>
+          <view v-if="task.serviceType" class="detail-row">
+            <text class="detail-label">施工类型</text>
+            <text class="detail-value">{{ task.serviceType }}</text>
           </view>
-          <view v-if="task.facilityType" class="flex justify-between text-sm">
-            <text class="text-gray-400">设施类型</text>
-            <text class="text-gray-800">{{ task.facilityType }}</text>
+          <view v-if="task.facilityType" class="detail-row">
+            <text class="detail-label">设施类型</text>
+            <text class="detail-value">{{ task.facilityType }}</text>
           </view>
-          <view v-if="task.faultLocation" class="flex justify-between text-sm">
-            <text class="text-gray-400">故障位置</text>
-            <text class="text-gray-800">{{ task.faultLocation }}</text>
+          <view v-if="task.faultLocation" class="detail-row">
+            <text class="detail-label">故障位置</text>
+            <text class="detail-value">{{ task.faultLocation }}</text>
           </view>
-          <view v-if="task.faultDesc" class="flex justify-between text-sm">
-            <text class="text-gray-400">故障描述</text>
-            <text class="text-gray-800">{{ task.faultDesc }}</text>
+          <view v-if="task.faultDesc" class="detail-row">
+            <text class="detail-label">故障描述</text>
+            <text class="detail-value">{{ task.faultDesc }}</text>
           </view>
-          <view v-if="task.assignee" class="flex justify-between text-sm">
-            <text class="text-gray-400">负责人</text>
-            <text class="text-gray-800">{{ task.assignee }}</text>
+          <view v-if="task.assignee" class="detail-row">
+            <text class="detail-label">负责人</text>
+            <text class="detail-value">{{ task.assignee }}</text>
           </view>
-          <view v-if="task.vehicle" class="flex justify-between text-sm">
-            <text class="text-gray-400">车辆</text>
-            <text class="text-gray-800">{{ task.vehicle }}</text>
+          <view v-if="task.vehicle" class="detail-row">
+            <text class="detail-label">车辆</text>
+            <text class="detail-value">{{ task.vehicle }}</text>
           </view>
-          <view v-if="task.team" class="flex justify-between text-sm">
-            <text class="text-gray-400">班组</text>
-            <text class="text-gray-800">{{ task.team }}</text>
+          <view v-if="task.team" class="detail-row">
+            <text class="detail-label">班组</text>
+            <text class="detail-value">{{ task.team }}</text>
           </view>
-          <view v-if="task.scheduledDate" class="flex justify-between text-sm">
-            <text class="text-gray-400">计划日期</text>
-            <text class="text-gray-800">{{ task.scheduledDate }}</text>
+          <view v-if="task.scheduledDate" class="detail-row">
+            <text class="detail-label">计划日期</text>
+            <text class="detail-value">{{ task.scheduledDate }}</text>
           </view>
-          <view v-if="task.address" class="flex justify-between text-sm">
-            <text class="text-gray-400">项目地址</text>
-            <text class="text-gray-800">{{ task.address }}</text>
+          <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 class="flex justify-between text-sm">
-            <text class="text-gray-400">创建时间</text>
-            <text class="text-gray-800">{{ formatDate(task.createTime, 'YYYY-MM-DD HH:mm') }}</text>
+          <view class="detail-row">
+            <text class="detail-label">创建时间</text>
+            <text class="detail-value">{{ formatDate(task.createTime, 'YYYY-MM-DD HH:mm') }}</text>
           </view>
         </view>
       </view>
 
       <!-- 时间轴 -->
-      <view class="bg-white rounded-card shadow-card overflow-hidden">
-        <view class="detail-section-title">
+      <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>
-        <view class="p-4">
-          <view
-            v-for="(record, index) in sortedTimeline"
-            :key="index"
-            class="flex items-start mb-4"
-            :class="{ 'mb-0': index === sortedTimeline.length - 1 }"
-          >
-            <view class="flex flex-col items-center mr-3">
-              <view class="w-2 h-2 bg-primary rounded-full"></view>
-              <view v-if="index < sortedTimeline.length - 1" class="w-px h-10 bg-gray-200"></view>
-            </view>
-            <view class="flex-1">
-              <text class="text-sm text-gray-800 block">{{ record.action }}</text>
-              <view class="flex justify-between mt-1">
-                <text class="text-xs text-gray-400">{{ record.operator }}</text>
-                <text class="text-xs text-gray-400">{{ formatDate(record.time, 'MM-DD HH:mm') }}</text>
-              </view>
-            </view>
-          </view>
-        </view>
+        <Timeline :records="sortedTimeline" />
       </view>
     </view>
 
-    <EmptyState v-else message="任务不存在" icon="" />
+    <EmptyState v-else message="任务不存在" />
   </view>
 </template>
 
 <script setup lang="ts">
-import { ref, computed } from 'vue'
+import { computed } 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 { useAuthStore } from '../../stores/auth'
 import { formatDate, getTypeText, getUrgentText, showToast, showModal, goBack } from '../../utils'
 import { updateTask } from '../../api/task'
-import type { TaskStatus } from '../../types'
 
 const taskStore = useTaskStore()
-const authStore = useAuthStore()
 const task = computed(() => taskStore.currentTask)
 
-const flowSteps = [
-  { key: 'confirmed', label: '确认' },
-  { key: 'departed', label: '出车' },
-  { key: 'arrived', label: '到达' },
-  { key: 'survey', label: '勘验' },
-  { key: 'constructing', label: '施工' },
-  { key: 'completed', label: '完成' },
-  { key: 'clean', label: '清洗' },
-  { key: 'inspecting', label: '验收' },
-]
-
-const statusOrder: TaskStatus[] = ['pending', 'confirmed', 'scheduled', 'departed', 'arrived', 'constructing', 'inspecting', 'completed']
-
-function isStepCompleted(stepKey: string): boolean {
-  if (!task.value) return false
-  const currentIndex = statusOrder.indexOf(task.value.status)
-  if (stepKey === 'survey') return currentIndex > statusOrder.indexOf('arrived')
-  if (stepKey === 'clean') return task.value.cleanTime != null || currentIndex > statusOrder.indexOf('inspecting')
-  const stepIndex = statusOrder.indexOf(stepKey as TaskStatus)
-  if (stepIndex < 0) return false
-  return currentIndex > stepIndex
-}
-
-function isStepActive(stepKey: string): boolean {
-  if (!task.value) return false
-  if (stepKey === 'survey') return task.value.status === 'arrived'
-  if (stepKey === 'clean') return task.value.status === 'inspecting' && !task.value.cleanTime
-  return task.value.status === stepKey
-}
-
-function stepStatusClass(stepKey: string): string {
-  if (isStepCompleted(stepKey)) return 'bg-green-500 text-white'
-  if (isStepActive(stepKey)) return 'bg-primary text-white'
-  return 'bg-gray-100 text-gray-400'
-}
-
 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())
@@ -341,15 +272,3 @@ onLoad((options) => {
   }
 })
 </script>
-
-<style scoped>
-.detail-section-title {
-  padding: 12px 16px;
-  font-size: 14px;
-  font-weight: 600;
-  color: #333;
-  border-bottom: 1px solid #f0f0f0;
-  display: flex;
-  align-items: center;
-}
-</style>

+ 18 - 0
src/utils/index.ts

@@ -78,6 +78,24 @@ export function hideLoading(): void {
   uni.hideLoading()
 }
 
+/**
+ * 显示确认弹窗(简化版,返回 boolean)
+ */
+export function showConfirm(title: string, content?: string): Promise<boolean> {
+  return new Promise((resolve) => {
+    uni.showModal({
+      title,
+      content: content || '',
+      showCancel: true,
+      cancelText: '取消',
+      confirmText: '确定',
+      success: (res) => {
+        resolve(res.confirm)
+      }
+    })
+  })
+}
+
 /**
  * 显示确认弹窗
  */

+ 0 - 49
tailwind.config.js

@@ -1,49 +0,0 @@
-/** @type {import('tailwindcss').Config} */
-module.exports = {
-  content: [
-    './index.html',
-    './src/**/*.{vue,js,ts,jsx,tsx}'
-  ],
-  theme: {
-    extend: {
-      colors: {
-        primary: '#2563eb',
-        'primary-dark': '#1d4ed8',
-        'primary-light': '#eff6ff',
-        success: '#10b981',
-        'success-dark': '#059669',
-        'success-light': '#ecfdf5',
-        warning: '#f59e0b',
-        'warning-dark': '#d97706',
-        'warning-light': '#fff7ed',
-        danger: '#ef4444',
-        'danger-dark': '#dc2626',
-        'danger-light': '#fef2f2',
-        background: '#f5f5f5',
-        surface: '#f3f4f6',
-        'text-primary': '#1f2937',
-        'text-secondary': '#6b7280',
-        'text-tertiary': '#9ca3af',
-        'border-color': '#e5e7eb',
-        'card-bg': '#ffffff'
-      },
-      maxWidth: {
-        app: '430px'
-      },
-      borderRadius: {
-        card: '12px',
-        button: '8px',
-        tag: '4px',
-        pill: '20px'
-      },
-      boxShadow: {
-        card: '0 1px 3px rgba(0, 0, 0, 0.04), 0 2px 8px rgba(0, 0, 0, 0.04)',
-        nav: '0 -2px 10px rgba(0, 0, 0, 0.06)'
-      }
-    }
-  },
-  plugins: [],
-  corePlugins: {
-    preflight: false
-  }
-}

+ 0 - 6
vite.config.ts

@@ -20,12 +20,6 @@ export default defineConfig({
     }
   },
   css: {
-    postcss: {
-      plugins: [
-        require('tailwindcss'),
-        require('autoprefixer'),
-      ]
-    },
     preprocessorOptions: {
       scss: {
         api: 'modern',