Browse Source

新增智慧农作管理

yawuga 9 months ago
parent
commit
b29c040251
3 changed files with 2750 additions and 150 deletions
  1. 6 0
      src/router/index.js
  2. 529 150
      src/views/base/tasks/index.vue
  3. 2215 0
      src/views/base/tasks/stats/index.vue

+ 6 - 0
src/router/index.js

@@ -117,6 +117,12 @@ export const constantRoutes = [
         component: () => import('@/views/base/machines/machines-monitor'),
         name: 'MachinesMonitor',
         meta: { title: '农机监控汇总', activeMenu: '/base/machines' }
+      },
+      {
+        path: 'tasks/stats',
+        component: () => import('@/views/base/tasks/stats/index'),
+        name: 'TaskStats',
+        meta: { title: '农事任务 · 汇总统计', activeMenu: '/base/tasks' }
       }
     ]
   },

+ 529 - 150
src/views/base/tasks/index.vue

@@ -1,14 +1,6 @@
 <template>
   <div class="app-container">
     <el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="68px">
-      <el-form-item label="关联地块ID" prop="plotId">
-        <el-input
-          v-model="queryParams.plotId"
-          placeholder="请输入关联地块ID"
-          clearable
-          @keyup.enter.native="handleQuery"
-        />
-      </el-form-item>
       <el-form-item label="任务名称" prop="taskName">
         <el-input
           v-model="queryParams.taskName"
@@ -17,53 +9,94 @@
           @keyup.enter.native="handleQuery"
         />
       </el-form-item>
-      <el-form-item label="任务图片URL" prop="taskImages">
-        <el-input
-          v-model="queryParams.taskImages"
-          placeholder="请输入任务图片URL"
-          clearable
-          @keyup.enter.native="handleQuery"
-        />
+      <el-form-item label="任务类型" prop="taskType">
+        <el-select v-model="queryParams.taskType" placeholder="请选择任务类型" clearable>
+          <el-option label="施肥" value="0" />
+          <el-option label="灌溉" value="1" />
+          <el-option label="打药" value="2" />
+          <el-option label="采摘" value="3" />
+          <el-option label="巡检" value="4" />
+          <el-option label="除草" value="5" />
+          <el-option label="其他" value="6" />
+        </el-select>
       </el-form-item>
-      <el-form-item label="任务类型" prop="typeName">
-        <el-input
-          v-model="queryParams.typeName"
-          placeholder="请输入任务类型"
+      <el-form-item label="所属地块" prop="plotNames">
+        <el-select 
+          v-model="queryParams.plotNames" 
+          placeholder="请选择地块" 
+          multiple 
+          collapse-tags
           clearable
-          @keyup.enter.native="handleQuery"
-        />
+          style="width: 200px"
+        >
+          <el-option 
+            v-for="item in plotOptions" 
+            :key="item.value" 
+            :label="item.label" 
+            :value="item.value"
+          />
+          <div v-if="plotOptions.length === 0" style="text-align: center; padding: 20px; color: #999;">
+            暂无数据
+          </div>
+        </el-select>
       </el-form-item>
-      <el-form-item label="计划执行时间" prop="executeTime">
-        <el-date-picker clearable
-          v-model="queryParams.executeTime"
-          type="date"
-          value-format="yyyy-MM-dd"
-          placeholder="请选择计划执行时间">
-        </el-date-picker>
+      <el-form-item label="所属农场" prop="farmNames">
+        <el-select 
+          v-model="queryParams.farmNames" 
+          placeholder="请选择农场" 
+          multiple 
+          collapse-tags
+          clearable
+          style="width: 200px"
+        >
+          <el-option 
+            v-for="item in farmOptions" 
+            :key="item.value" 
+            :label="item.label" 
+            :value="item.value"
+          />
+          <div v-if="farmOptions.length === 0" style="text-align: center; padding: 20px; color: #999;">
+            暂无数据
+          </div>
+        </el-select>
       </el-form-item>
-      <el-form-item label="负责人名称" prop="assigneeId">
+      <el-form-item label="执行人" prop="executor">
         <el-input
-          v-model="queryParams.assigneeId"
-          placeholder="请输入负责人名称"
+          v-model="queryParams.executor"
+          placeholder="请输入执行人"
           clearable
           @keyup.enter.native="handleQuery"
         />
       </el-form-item>
-      <el-form-item label="实际完成时间" prop="completionTime">
-        <el-date-picker clearable
-          v-model="queryParams.completionTime"
-          type="date"
-          value-format="yyyy-MM-dd"
-          placeholder="请选择实际完成时间">
+      <el-form-item label="任务状态" prop="taskStatus">
+        <el-select v-model="queryParams.taskStatus" placeholder="请选择任务状态" clearable>
+          <el-option label="待完成" value="0" />
+          <el-option label="已完成" value="1" />
+        </el-select>
+      </el-form-item>
+      <el-form-item label="执行时间" prop="executeTimeRange">
+        <el-date-picker
+          v-model="queryParams.executeTimeRange"
+          type="datetimerange"
+          range-separator="至"
+          start-placeholder="开始时间"
+          end-placeholder="结束时间"
+          value-format="yyyy-MM-dd HH:mm:ss"
+          clearable
+          style="width: 350px">
         </el-date-picker>
       </el-form-item>
-      <el-form-item label="创建人ID" prop="creatorId">
-        <el-input
-          v-model="queryParams.creatorId"
-          placeholder="请输入创建人ID"
+      <el-form-item label="完成时间" prop="completionTimeRange">
+        <el-date-picker
+          v-model="queryParams.completionTimeRange"
+          type="datetimerange"
+          range-separator="至"
+          start-placeholder="开始时间"
+          end-placeholder="结束时间"
+          value-format="yyyy-MM-dd HH:mm:ss"
           clearable
-          @keyup.enter.native="handleQuery"
-        />
+          style="width: 350px">
+        </el-date-picker>
       </el-form-item>
       <el-form-item>
         <el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
@@ -82,17 +115,7 @@
           v-hasPermi="['base:tasks:add']"
         >新增</el-button>
       </el-col>
-      <el-col :span="1.5">
-        <el-button
-          type="success"
-          plain
-          icon="el-icon-edit"
-          size="mini"
-          :disabled="single"
-          @click="handleUpdate"
-          v-hasPermi="['base:tasks:edit']"
-        >修改</el-button>
-      </el-col>
+
       <el-col :span="1.5">
         <el-button
           type="danger"
@@ -119,31 +142,46 @@
 
     <el-table v-loading="loading" :data="tasksList" @selection-change="handleSelectionChange">
       <el-table-column type="selection" width="55" align="center" />
-      <el-table-column label="${comment}" align="center" prop="id" />
-      <el-table-column label="关联地块ID" align="center" prop="plotId" />
       <el-table-column label="任务名称" align="center" prop="taskName" />
-      <el-table-column label="任务图片URL" align="center" prop="taskImages" />
-      <el-table-column label="任务类型" align="center" prop="typeName">
+      <el-table-column label="任务类型" align="center" prop="taskType" width="100">
+        <template slot-scope="scope">
+          <el-tag :type="getTaskTypeTagType(scope.row.taskType)" size="small">
+            {{ getTaskTypeName(scope.row.taskType) }}
+          </el-tag>
+        </template>
+      </el-table-column>
+      <el-table-column label="作物名称" align="center" prop="cropName" />
+      <el-table-column label="所属地块" align="center" prop="plotName" />
+      <el-table-column label="所属农场" align="center" prop="farmName" />
+      <el-table-column label="执行人" align="center" prop="executor" />
+      <el-table-column label="执行时间" align="center" prop="executeTime" width="180">
+        <template slot-scope="scope">
+          <span>{{ parseTime(scope.row.executeTime, '{y}-{m}-{d} {h}:{i}:{s}') }}</span>
+        </template>
+      </el-table-column>
+      <el-table-column label="任务状态" align="center" prop="taskStatus" width="100">
         <template slot-scope="scope">
-          <dict-tag :options="dict.type.agricultural_tasks_type" :value="scope.row.typeName"/>
+          <el-tag :type="scope.row.taskStatus === 1 ? 'success' : 'warning'" size="small">
+            {{ scope.row.taskStatus === 1 ? '已完成' : '待完成' }}
+          </el-tag>
         </template>
       </el-table-column>
-      <el-table-column label="任务状态" align="center" prop="taskStatus" />
-      <el-table-column label="计划执行时间" align="center" prop="executeTime" width="180">
+      <el-table-column label="任务说明" align="center" prop="taskDescription" width="150" show-overflow-tooltip />
+      <el-table-column label="完成时间" align="center" prop="completionTime" width="180">
         <template slot-scope="scope">
-          <span>{{ parseTime(scope.row.executeTime, '{y}-{m}-{d}') }}</span>
+          <span>{{ scope.row.completionTime ? parseTime(scope.row.completionTime, '{y}-{m}-{d} {h}:{i}:{s}') : '-' }}</span>
         </template>
       </el-table-column>
-      <el-table-column label="负责人名称" align="center" prop="assigneeId" />
-      <el-table-column label="任务备注" align="center" prop="remark" />
-      <el-table-column label="实际完成时间" align="center" prop="completionTime" width="180">
+      <el-table-column label="完成说明" align="center" prop="completionDescription" width="150" show-overflow-tooltip />
+      <el-table-column label="附件" align="center" prop="attachmentImages" width="80">
         <template slot-scope="scope">
-          <span>{{ parseTime(scope.row.completionTime, '{y}-{m}-{d}') }}</span>
+          <el-tag v-if="scope.row.attachmentImages && scope.row.attachmentImages.length > 0" type="success" size="mini">
+            {{ scope.row.attachmentImages.length }}张
+          </el-tag>
+          <span v-else style="color: #999;">-</span>
         </template>
       </el-table-column>
-      <el-table-column label="完成说明" align="center" prop="completionDesc" />
-      <el-table-column label="创建人ID" align="center" prop="creatorId" />
-      <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
+      <el-table-column label="操作" align="center" class-name="small-padding fixed-width" fixed="right" width="150">
         <template slot-scope="scope">
           <el-button
             size="mini"
@@ -172,47 +210,127 @@
     />
 
     <!-- 添加或修改农事任务对话框 -->
-    <el-dialog :title="title" :visible.sync="open" width="500px" append-to-body>
+    <el-dialog :title="title" :visible.sync="open" width="600px" append-to-body>
       <el-form ref="form" :model="form" :rules="rules" label-width="80px">
-        <el-form-item label="关联地块ID" prop="plotId">
-          <el-input v-model="form.plotId" placeholder="请输入关联地块ID" />
-        </el-form-item>
-        <el-form-item label="任务名称" prop="taskName">
-          <el-input v-model="form.taskName" placeholder="请输入任务名称" />
-        </el-form-item>
-        <el-form-item label="任务图片URL" prop="taskImages">
-          <el-input v-model="form.taskImages" placeholder="请输入任务图片URL" />
-        </el-form-item>
-        <el-form-item label="任务类型" prop="typeName">
-          <el-input v-model="form.typeName" placeholder="请输入任务类型" />
-        </el-form-item>
-        <el-form-item label="计划执行时间" prop="executeTime">
-          <el-date-picker clearable
-            v-model="form.executeTime"
-            type="date"
-            value-format="yyyy-MM-dd"
-            placeholder="请选择计划执行时间">
-          </el-date-picker>
-        </el-form-item>
-        <el-form-item label="负责人名称" prop="assigneeId">
-          <el-input v-model="form.assigneeId" placeholder="请输入负责人名称" />
-        </el-form-item>
-        <el-form-item label="任务备注" prop="remark">
-          <el-input v-model="form.remark" type="textarea" placeholder="请输入内容" />
+        <el-row :gutter="20">
+          <el-col :span="12">
+            <el-form-item label="任务名称" prop="taskName">
+              <el-input v-model="form.taskName" placeholder="请输入任务名称" />
+            </el-form-item>
+          </el-col>
+          <el-col :span="12">
+            <el-form-item label="任务类型" prop="taskType">
+              <el-select v-model="form.taskType" placeholder="请选择任务类型">
+                <el-option label="施肥" value="0" />
+                <el-option label="灌溉" value="1" />
+                <el-option label="打药" value="2" />
+                <el-option label="采摘" value="3" />
+                <el-option label="巡检" value="4" />
+                <el-option label="除草" value="5" />
+                <el-option label="其他" value="6" />
+              </el-select>
+            </el-form-item>
+          </el-col>
+        </el-row>
+        <el-row :gutter="20">
+          <el-col :span="12">
+            <el-form-item label="所属地块" prop="plotName">
+              <el-select v-model="form.plotName" placeholder="请选择地块" clearable style="width: 100%">
+                <el-option 
+                  v-for="item in plotOptions" 
+                  :key="item.value" 
+                  :label="item.label" 
+                  :value="item.value"
+                />
+                <div v-if="plotOptions.length === 0" style="text-align: center; padding: 20px; color: #999;">
+                  暂无数据
+                </div>
+              </el-select>
+            </el-form-item>
+          </el-col>
+          <el-col :span="12">
+            <el-form-item label="所属农场" prop="farmName">
+              <el-select v-model="form.farmName" placeholder="请选择农场" clearable style="width: 100%">
+                <el-option 
+                  v-for="item in farmOptions" 
+                  :key="item.value" 
+                  :label="item.label" 
+                  :value="item.value"
+                />
+                <div v-if="farmOptions.length === 0" style="text-align: center; padding: 20px; color: #999;">
+                  暂无数据
+                </div>
+              </el-select>
+            </el-form-item>
+          </el-col>
+        </el-row>
+        <el-row :gutter="20">
+          <el-col :span="12">
+            <el-form-item label="执行人" prop="executor">
+              <el-select v-model="form.executor" placeholder="请选择执行人" clearable style="width: 100%">
+                <el-option 
+                  v-for="item in executorOptions" 
+                  :key="item.value" 
+                  :label="item.label" 
+                  :value="item.value"
+                />
+                <div v-if="executorOptions.length === 0" style="text-align: center; padding: 20px; color: #999;">
+                  暂无数据
+                </div>
+              </el-select>
+            </el-form-item>
+          </el-col>
+          <el-col :span="12">
+            <el-form-item label="执行时间" prop="executeTime">
+              <el-date-picker clearable
+                v-model="form.executeTime"
+                type="datetime"
+                value-format="yyyy-MM-dd HH:mm:ss"
+                placeholder="请选择执行时间"
+                style="width: 100%">
+              </el-date-picker>
+            </el-form-item>
+          </el-col>
+        </el-row>
+        <el-row :gutter="20">
+          <el-col :span="12">
+            <el-form-item label="任务状态" prop="taskStatus">
+              <el-select v-model="form.taskStatus" placeholder="请选择任务状态" style="width: 100%">
+                <el-option label="待完成" value="0" />
+                <el-option label="已完成" value="1" />
+              </el-select>
+            </el-form-item>
+          </el-col>
+        </el-row>
+        <el-form-item label="任务说明" prop="taskDescription">
+          <el-input v-model="form.taskDescription" type="textarea" :rows="3" placeholder="请输入任务说明" />
         </el-form-item>
-        <el-form-item label="实际完成时间" prop="completionTime">
-          <el-date-picker clearable
-            v-model="form.completionTime"
-            type="date"
-            value-format="yyyy-MM-dd"
-            placeholder="请选择实际完成时间">
-          </el-date-picker>
+        <el-row :gutter="20" v-if="form.taskStatus == 1">
+          <el-col :span="12">
+            <el-form-item label="完成时间" prop="completionTime">
+              <el-date-picker clearable
+                v-model="form.completionTime"
+                type="datetime"
+                value-format="yyyy-MM-dd HH:mm:ss"
+                placeholder="请选择完成时间"
+                style="width: 100%">
+              </el-date-picker>
+            </el-form-item>
+          </el-col>
+        </el-row>
+        <el-form-item label="完成说明" prop="completionDescription" v-if="form.taskStatus == 1">
+          <el-input v-model="form.completionDescription" type="textarea" :rows="3" placeholder="请输入完成说明" />
         </el-form-item>
-        <el-form-item label="完成说明" prop="completionDesc">
-          <el-input v-model="form.completionDesc" type="textarea" placeholder="请输入内容" />
-        </el-form-item>
-        <el-form-item label="创建人ID" prop="creatorId">
-          <el-input v-model="form.creatorId" placeholder="请输入创建人ID" />
+        <el-form-item label="上传附件" prop="attachmentImages" v-if="form.taskStatus == 1">
+          <image-upload 
+            v-model="form.attachmentImages"
+            :limit="5"
+            :fileSize="10"
+            :fileType="['jpg', 'jpeg', 'png', 'gif']"
+          />
+          <div style="color: #999; font-size: 12px; margin-top: 5px;">
+            支持jpg、jpeg、png、gif格式,单张图片不超过10MB,最多上传5张
+          </div>
         </el-form-item>
       </el-form>
       <div slot="footer" class="dialog-footer">
@@ -225,9 +343,13 @@
 
 <script>
 import { listTasks, getTasks, delTasks, addTasks, updateTasks } from "@/api/base/tasks"
+import ImageUpload from "@/components/ImageUpload"
 
 export default {
   name: "Tasks",
+  components: {
+    ImageUpload
+  },
   data() {
     return {
       // 遮罩层
@@ -252,63 +374,296 @@ export default {
       queryParams: {
         pageNum: 1,
         pageSize: 10,
-        plotId: null,
         taskName: null,
-        taskImages: null,
-        typeName: null,
+        taskType: null,
+        plotNames: [],
+        farmNames: [],
+        executor: null,
         taskStatus: null,
-        executeTime: null,
-        assigneeId: null,
-        completionTime: null,
-        completionDesc: null,
-        creatorId: null,
+        executeTimeRange: null,
+        completionTimeRange: null,
       },
+      // 地块选项
+      plotOptions: [],
+      // 农场选项
+      farmOptions: [],
+      // 执行人选项
+      executorOptions: [],
       // 表单参数
       form: {},
       // 表单校验
       rules: {
-        plotId: [
-          { required: true, message: "关联地块ID不能为空", trigger: "blur" }
-        ],
         taskName: [
           { required: true, message: "任务名称不能为空", trigger: "blur" }
         ],
-        taskImages: [
-          { required: true, message: "任务图片URL不能为空", trigger: "blur" }
-        ],
-        typeName: [
-          { required: true, message: "任务类型不能为空", trigger: "blur" }
+        taskType: [
+          { required: true, message: "任务类型不能为空", trigger: "change" }
         ],
-        executeTime: [
-          { required: true, message: "计划执行时间不能为空", trigger: "blur" }
+        plotName: [
+          { required: true, message: "所属地块不能为空", trigger: "change" }
         ],
-        assigneeId: [
-          { required: true, message: "负责人名称不能为空", trigger: "blur" }
+        farmName: [
+          { required: true, message: "所属农场不能为空", trigger: "change" }
         ],
-        creatorId: [
-          { required: true, message: "创建人ID不能为空", trigger: "blur" }
+        executor: [
+          { required: true, message: "执行人不能为空", trigger: "change" }
         ],
-        createTime: [
-          { required: true, message: "$comment不能为空", trigger: "blur" }
+        executeTime: [
+          { required: true, message: "执行时间不能为空", trigger: "blur" }
         ],
-        updateTime: [
-          { required: true, message: "$comment不能为空", trigger: "blur" }
+        taskStatus: [
+          { required: true, message: "任务状态不能为空", trigger: "change" }
         ]
       }
     }
   },
   created() {
     this.getList()
+    this.initOptions()
   },
   methods: {
+    /** 初始化下拉选项 */
+    initOptions() {
+      // 为了支持修改表单回填,这里提供一些模拟数据
+      // 实际开发中应该调用API获取真实数据
+      this.plotOptions = [
+        { label: "东区1号田", value: "东区1号田" },
+        { label: "西区2号田", value: "西区2号田" },
+        { label: "南区果园", value: "南区果园" },
+        { label: "北区菜地", value: "北区菜地" },
+        { label: "温室大棚A区", value: "温室大棚A区" },
+        { label: "智能温室B区", value: "智能温室B区" },
+        { label: "智能温室C区", value: "智能温室C区" },
+        { label: "有机种植区", value: "有机种植区" },
+        { label: "葡萄园A区", value: "葡萄园A区" },
+        { label: "机械库房", value: "机械库房" }
+      ]
+      
+      this.farmOptions = [
+        { label: "阳光农场", value: "阳光农场" },
+        { label: "绿野农场", value: "绿野农场" },
+        { label: "丰收农场", value: "丰收农场" },
+        { label: "田园农场", value: "田园农场" },
+        { label: "科技农场", value: "科技农场" },
+        { label: "有机农场", value: "有机农场" },
+        { label: "现代农场", value: "现代农场" },
+        { label: "生态农场", value: "生态农场" },
+        { label: "智慧农场", value: "智慧农场" },
+        { label: "酒庄农场", value: "酒庄农场" }
+      ]
+      
+      this.executorOptions = [
+        { label: "张三", value: "张三" },
+        { label: "李四", value: "李四" },
+        { label: "王五", value: "王五" },
+        { label: "赵六", value: "赵六" },
+        { label: "钱七", value: "钱七" },
+        { label: "孙八", value: "孙八" },
+        { label: "周九", value: "周九" },
+        { label: "吴十", value: "吴十" },
+        { label: "郑十一", value: "郑十一" },
+        { label: "冯十二", value: "冯十二" }
+      ]
+    },
+    /** 获取任务类型名称 */
+    getTaskTypeName(type) {
+      const typeMap = {
+        '0': '施肥',
+        '1': '灌溉', 
+        '2': '打药',
+        '3': '采摘',
+        '4': '巡检',
+        '5': '除草',
+        '6': '其他'
+      }
+      return typeMap[type] || '未知'
+    },
+    /** 获取任务类型标签类型 */
+    getTaskTypeTagType(type) {
+      const tagTypeMap = {
+        '0': 'success',  // 施肥
+        '1': 'primary',  // 灌溉
+        '2': 'warning',  // 打药
+        '3': 'danger',   // 采摘
+        '4': 'info',     // 巡检
+        '5': '',         // 除草
+        '6': 'default'   // 其他
+      }
+      return tagTypeMap[type] || ''
+    },
     /** 查询农事任务列表 */
     getList() {
       this.loading = true
-      listTasks(this.queryParams).then(response => {
-        this.tasksList = response.rows
-        this.total = response.total
+      // 模拟假数据,实际开发中应该调用真实的API
+      const mockData = {
+        rows: [
+          {
+            id: 1,
+            taskName: "水稻施肥作业",
+            taskType: "0",
+            cropName: "水稻",
+            plotName: "东区1号田",
+            farmName: "阳光农场",
+            executor: "张三",
+            executeTime: "2024-01-15 09:00:00",
+            taskStatus: 1,
+            taskDescription: "对水稻进行有机肥施肥,提高产量和品质,需要注意施肥量的控制,避免过量施肥导致烧苗",
+            completionTime: "2024-01-15 11:30:00",
+            completionDescription: "已完成有机肥施肥,共施肥50亩,肥料用量符合标准要求,苗情良好",
+            attachmentImages: ["https://example.com/images/fertilizer1.jpg", "https://example.com/images/fertilizer2.jpg"]
+          },
+          {
+            id: 2,
+            taskName: "玉米灌溉任务",
+            taskType: "1",
+            cropName: "玉米",
+            plotName: "西区2号田",
+            farmName: "绿野农场",
+            executor: "李四",
+            executeTime: "2024-01-16 06:30:00",
+            taskStatus: 0,
+            taskDescription: "对玉米田进行滴灌作业,确保土壤湿度适宜",
+            completionTime: null,
+            completionDescription: null,
+            attachmentImages: null
+          },
+          {
+            id: 3,
+            taskName: "果园病虫害防治",
+            taskType: "2",
+            cropName: "苹果",
+            plotName: "南区果园",
+            farmName: "丰收农场",
+            executor: "王五",
+            executeTime: "2024-01-17 07:00:00",
+            taskStatus: 1,
+            taskDescription: "对苹果树进行病虫害防治,使用生物农药,确保果实品质安全",
+            completionTime: "2024-01-17 10:45:00",
+            completionDescription: "病虫害防治作业完成,使用生物农药150公斤,覆盖果园100亩",
+            attachmentImages: ["https://example.com/images/pesticide1.jpg"]
+          },
+          {
+            id: 4,
+            taskName: "草莓采摘作业",
+            taskType: "3",
+            cropName: "草莓",
+            plotName: "温室大棚A区",
+            farmName: "田园农场",
+            executor: "赵六",
+            executeTime: "2024-01-18 05:00:00",
+            taskStatus: 0,
+            taskDescription: "对成熟草莓进行采摘,注意保持果实完整性,分级包装",
+            completionTime: null,
+            completionDescription: null,
+            attachmentImages: null
+          },
+          {
+            id: 5,
+            taskName: "大棚设施巡检",
+            taskType: "4",
+            cropName: "番茄",
+            plotName: "智能温室B区",
+            farmName: "科技农场",
+            executor: "钱七",
+            executeTime: "2024-01-19 08:00:00",
+            taskStatus: 1,
+            taskDescription: "对温室大棚设施进行全面巡检,检查通风、灌溉、温控等系统运行状态",
+            completionTime: "2024-01-19 12:00:00",
+            completionDescription: "巡检完成,发现2号风机需要维护,其他设施运行正常",
+            attachmentImages: ["https://example.com/images/inspection1.jpg", "https://example.com/images/inspection2.jpg", "https://example.com/images/inspection3.jpg"]
+          },
+          {
+            id: 6,
+            taskName: "蔬菜田除草工作",
+            taskType: "5",
+            cropName: "白菜",
+            plotName: "北区菜地",
+            farmName: "有机农场",
+            executor: "孙八",
+            executeTime: "2024-01-20 07:30:00",
+            taskStatus: 0,
+            taskDescription: "对白菜田进行人工除草,清除田间杂草,提高蔬菜生长环境",
+            completionTime: null,
+            completionDescription: null,
+            attachmentImages: null
+          },
+          {
+            id: 7,
+            taskName: "农机设备维护",
+            taskType: "6",
+            cropName: "小麦",
+            plotName: "机械库房",
+            farmName: "现代农场",
+            executor: "周九",
+            executeTime: "2024-01-21 09:00:00",
+            taskStatus: 1,
+            taskDescription: "对收割机进行定期维护保养,更换机油、清洁滤芯、检查传动系统",
+            completionTime: "2024-01-21 16:30:00",
+            completionDescription: "设备维护完成,更换机油20升,清洁空气滤芯,传动系统正常",
+            attachmentImages: ["https://example.com/images/maintenance1.jpg"]
+          },
+          {
+            id: 8,
+            taskName: "有机肥料施用",
+            taskType: "0",
+            cropName: "茄子",
+            plotName: "有机种植区",
+            farmName: "生态农场",
+            executor: "吴十",
+            executeTime: "2024-01-22 08:30:00",
+            taskStatus: 0,
+            taskDescription: "为茄子田施用有机肥料,改善土壤结构,提供充足养分支持茄子生长发育",
+            completionTime: null,
+            completionDescription: null,
+            attachmentImages: null
+          },
+          {
+            id: 9,
+            taskName: "精准灌溉系统调试",
+            taskType: "1",
+            cropName: "黄瓜",
+            plotName: "智能温室C区",
+            farmName: "智慧农场",
+            executor: "郑十一",
+            executeTime: "2024-01-23 10:00:00",
+            taskStatus: 1,
+            taskDescription: "调试精准灌溉系统,设置不同生长期的灌溉参数,确保黄瓜水分需求得到满足",
+            completionTime: "2024-01-23 14:20:00",
+            completionDescription: "灌溉系统调试完成,设置了3个不同生长期的灌溉方案,系统运行稳定",
+            attachmentImages: ["https://example.com/images/irrigation1.jpg", "https://example.com/images/irrigation2.jpg"]
+          },
+          {
+            id: 10,
+            taskName: "葡萄园修剪作业",
+            taskType: "6",
+            cropName: "葡萄",
+            plotName: "葡萄园A区",
+            farmName: "酒庄农场",
+            executor: "冯十二",
+            executeTime: "2024-01-24 07:00:00",
+            taskStatus: 0,
+            taskDescription: "对葡萄藤进行冬季修剪,去除病枝、弱枝,保持树形美观,为来年丰产奠定基础",
+            completionTime: null,
+            completionDescription: null,
+            attachmentImages: null
+          }
+        ],
+        total: 10
+      }
+      
+      // 模拟网络延迟
+      setTimeout(() => {
+        this.tasksList = mockData.rows
+        this.total = mockData.total
         this.loading = false
-      })
+      }, 500)
+      
+      // 真实API调用 (注释掉,用于实际开发)
+      // listTasks(this.queryParams).then(response => {
+      //   this.tasksList = response.rows
+      //   this.total = response.total
+      //   this.loading = false
+      // })
     },
     // 取消按钮
     cancel() {
@@ -319,19 +674,17 @@ export default {
     reset() {
       this.form = {
         id: null,
-        plotId: null,
         taskName: null,
-        taskImages: null,
-        typeName: null,
-        taskStatus: null,
+        taskType: null,
+        plotName: null,
+        farmName: null,
+        executor: null,
         executeTime: null,
-        assigneeId: null,
-        remark: null,
+        taskStatus: "0",
+        taskDescription: null,
         completionTime: null,
-        completionDesc: null,
-        creatorId: null,
-        createTime: null,
-        updateTime: null
+        completionDescription: null,
+        attachmentImages: null
       }
       this.resetForm("form")
     },
@@ -357,15 +710,41 @@ export default {
       this.open = true
       this.title = "添加农事任务"
     },
+    /** 格式化表单数据 */
+    formatFormData(rowData) {
+      return {
+        id: rowData.id,
+        taskName: rowData.taskName || '',
+        taskType: rowData.taskType ? rowData.taskType.toString() : '',
+        plotName: rowData.plotName || '',
+        farmName: rowData.farmName || '',
+        executor: rowData.executor || '',
+        executeTime: rowData.executeTime || null,
+        taskStatus: rowData.taskStatus ? rowData.taskStatus.toString() : '0',
+        taskDescription: rowData.taskDescription || '',
+        completionTime: rowData.completionTime || null,
+        completionDescription: rowData.completionDescription || '',
+        attachmentImages: rowData.attachmentImages || null
+      }
+    },
     /** 修改按钮操作 */
     handleUpdate(row) {
       this.reset()
       const id = row.id || this.ids
-      getTasks(id).then(response => {
-        this.form = response.data
+      
+      // 如果是从表格行点击修改,直接使用行数据回填
+      if (row && row.id) {
+        this.form = this.formatFormData(row)
         this.open = true
         this.title = "修改农事任务"
-      })
+      } else {
+        // 如果是批量修改或者需要从服务器获取数据
+        getTasks(id).then(response => {
+          this.form = this.formatFormData(response.data)
+          this.open = true
+          this.title = "修改农事任务"
+        })
+      }
     },
     /** 提交按钮 */
     submitForm() {

+ 2215 - 0
src/views/base/tasks/stats/index.vue

@@ -0,0 +1,2215 @@
+<template>
+  <div class="task-stats-container stats-page">
+    <!-- 顶部筛选条 -->
+    <div class="filter-section stats-card" style="margin-bottom: 16px;">
+      <!-- 快捷时间选择 -->
+      <div class="time-shortcuts">
+        <span class="shortcuts-label">快速选择:</span>
+        <div class="shortcut-pills">
+          <button
+            v-for="shortcut in timeShortcuts"
+            :key="shortcut.value"
+            :class="['shortcut-pill', { active: currentShortcut === shortcut.value }]"
+            @click="handleShortcutClick(shortcut)"
+          >
+            {{ shortcut.label }}
+          </button>
+        </div>
+      </div>
+      
+      <div class="filter-divider"></div>
+      
+      <el-form
+        :model="filters"
+        ref="filterForm"
+        :inline="true"
+        size="small"
+        label-width="80px"
+        class="filter-form"
+      >
+        <el-form-item label="时间范围" prop="dateRange">
+          <el-date-picker
+            v-model="filters.dateRange"
+            type="daterange"
+            range-separator="至"
+            start-placeholder="开始日期"
+            end-placeholder="结束日期"
+            value-format="yyyy-MM-dd"
+            style="width: 300px"
+            :picker-options="pickerOptions"
+            @change="handleDateRangeChange"
+          />
+        </el-form-item>
+        
+        <el-form-item label="农场" prop="farms">
+          <el-select
+            v-model="filters.farms"
+            placeholder="请选择农场"
+            multiple
+            collapse-tags
+            style="width: 180px"
+          >
+            <el-option
+              v-for="item in farmOptions"
+              :key="item.value"
+              :label="item.label"
+              :value="item.value"
+            />
+          </el-select>
+        </el-form-item>
+        
+        <el-form-item label="地块" prop="plots">
+          <el-select
+            v-model="filters.plots"
+            placeholder="请选择地块"
+            multiple
+            collapse-tags
+            style="width: 180px"
+          >
+            <el-option
+              v-for="item in plotOptions"
+              :key="item.value"
+              :label="item.label"
+              :value="item.value"
+            />
+          </el-select>
+        </el-form-item>
+        
+        <el-form-item label="作物" prop="crops">
+          <el-select
+            v-model="filters.crops"
+            placeholder="请选择作物"
+            multiple
+            collapse-tags
+            style="width: 180px"
+          >
+            <el-option
+              v-for="item in cropOptions"
+              :key="item.value"
+              :label="item.label"
+              :value="item.value"
+            />
+          </el-select>
+        </el-form-item>
+        
+        <el-form-item label="任务类型" prop="taskTypes">
+          <el-select
+            v-model="filters.taskTypes"
+            placeholder="请选择任务类型"
+            multiple
+            collapse-tags
+            style="width: 180px"
+          >
+            <el-option
+              v-for="item in taskTypeOptions"
+              :key="item.value"
+              :label="item.label"
+              :value="item.value"
+            />
+          </el-select>
+        </el-form-item>
+        
+        <el-form-item label="执行人" prop="executors">
+          <el-select
+            v-model="filters.executors"
+            placeholder="请选择执行人"
+            multiple
+            collapse-tags
+            style="width: 180px"
+          >
+            <el-option
+              v-for="item in executorOptions"
+              :key="item.value"
+              :label="item.label"
+              :value="item.value"
+            />
+          </el-select>
+        </el-form-item>
+        
+        <el-form-item label="状态" prop="statuses">
+          <el-select
+            v-model="filters.statuses"
+            placeholder="请选择状态"
+            multiple
+            collapse-tags
+            style="width: 180px"
+          >
+            <el-option
+              v-for="item in statusOptions"
+              :key="item.value"
+              :label="item.label"
+              :value="item.value"
+            />
+          </el-select>
+        </el-form-item>
+        
+        <el-form-item>
+          <div class="action-buttons">
+            <el-button type="primary" icon="el-icon-search" @click="handleQuery" class="btn-primary">
+              查询
+            </el-button>
+            <el-button icon="el-icon-refresh" @click="handleReset" class="btn-secondary">
+              重置
+            </el-button>
+          </div>
+        </el-form-item>
+      </el-form>
+    </div>
+
+    <!-- KPI 卡片区域 -->
+    <div class="kpi-section stats-card">
+      <div class="kpi-grid">
+        <div 
+          v-for="(kpi, index) in kpiData" 
+          :key="index"
+          class="kpi-card" 
+          @click="handleKpiClick(kpi.key)"
+        >
+          <div class="kpi-card__icon" :style="{ backgroundColor: kpi.iconBg }">
+            <i :class="kpi.icon" :style="{ color: kpi.color }"></i>
+          </div>
+          <div class="kpi-card__content">
+            <div class="kpi-card__meta">{{ kpi.title }}</div>
+            <div class="kpi-card__value">
+              <span class="kpi-number">
+                <count-to
+                  :start-val="0"
+                  :end-val="kpi.value"
+                  :duration="1500"
+                  :decimals="kpi.type === 'percent' || kpi.type === 'duration' ? 1 : 0"
+                  :suffix="kpi.type === 'percent' ? '%' : ''"
+                  class="animated-number"
+                />
+              </span>
+              <span v-if="kpi.unit" class="kpi-unit">{{ kpi.unit }}</span>
+            </div>
+          </div>
+          <el-dropdown trigger="click" @command="handleKpiAction" class="kpi-card__menu">
+            <span class="kpi-menu-trigger">
+              <i class="el-icon-more"></i>
+            </span>
+            <el-dropdown-menu slot="dropdown">
+              <el-dropdown-item :command="{ action: 'export', key: kpi.key }">
+                <i class="el-icon-download"></i> 导出数据
+              </el-dropdown-item>
+              <el-dropdown-item :command="{ action: 'detail', key: kpi.key }">
+                <i class="el-icon-view"></i> 查看明细
+              </el-dropdown-item>
+            </el-dropdown-menu>
+          </el-dropdown>
+        </div>
+      </div>
+    </div>
+
+    <!-- 主内容区域 -->
+    <el-row :gutter="16" class="main-content">
+      <!-- 左侧主栏 -->
+      <el-col :xs="24" :sm="24" :md="16" :lg="18" :xl="19">
+        <!-- 趋势图 -->
+        <div class="chart-section stats-card" style="margin-bottom: 16px;">
+            <div class="chart-header">
+              <h3 class="chart-title">任务趋势</h3>
+              <div class="chart-controls">
+                <div class="metric-segment">
+                  <button
+                    v-for="metric in trendMetrics"
+                    :key="metric.value"
+                    :class="['segment-btn', { active: trendMetric === metric.value }]"
+                    @click="handleTrendMetricChange(metric.value)"
+                  >
+                    {{ metric.label }}
+                  </button>
+                </div>
+                <div class="chart-tools">
+                  <el-tooltip content="下载图片" placement="top">
+                    <el-button size="mini" circle icon="el-icon-download" @click="downloadChart('trend')" />
+                  </el-tooltip>
+                  <el-tooltip content="全屏显示" placement="top">
+                    <el-button size="mini" circle icon="el-icon-full-screen" @click="fullscreenChart('trend')" />
+                  </el-tooltip>
+                </div>
+              </div>
+            </div>
+            <div
+              ref="trendChart"
+              v-loading="trendLoading"
+              class="chart-container"
+              style="height: 380px"
+            >
+              <div v-if="!trendLoading && trendEmpty" class="chart-empty">
+                <i class="el-icon-s-data chart-empty-icon"></i>
+                <p class="chart-empty-text">暂无数据</p>
+              </div>
+            </div>
+        </div>
+
+        <!-- 维度对比图 -->
+        <div class="chart-section stats-card" style="margin-top: 16px">
+            <div class="chart-header">
+              <h3 class="chart-title">维度对比</h3>
+              <div class="chart-controls">
+                <div class="dimension-controls">
+                  <div class="control-group">
+                    <span class="control-label">维度:</span>
+                    <div class="metric-segment">
+                      <button
+                        v-for="dimension in dimensionTypes"
+                        :key="dimension.value"
+                        :class="['segment-btn', { active: dimensionType === dimension.value }]"
+                        @click="handleDimensionTypeChange(dimension.value)"
+                      >
+                        {{ dimension.label }}
+                      </button>
+                    </div>
+                  </div>
+                  <div class="control-group">
+                    <span class="control-label">指标:</span>
+                    <div class="metric-segment">
+                      <button
+                        v-for="metric in dimensionMetrics"
+                        :key="metric.value"
+                        :class="['segment-btn', { active: dimensionMetric === metric.value }]"
+                        @click="handleDimensionMetricChange(metric.value)"
+                      >
+                        {{ metric.label }}
+                      </button>
+                    </div>
+                  </div>
+                </div>
+                <div class="chart-tools">
+                  <el-tooltip content="下载图片" placement="top">
+                    <el-button size="mini" circle icon="el-icon-download" @click="downloadChart('dimension')" />
+                  </el-tooltip>
+                  <el-tooltip content="全屏显示" placement="top">
+                    <el-button size="mini" circle icon="el-icon-full-screen" @click="fullscreenChart('dimension')" />
+                  </el-tooltip>
+                </div>
+              </div>
+            </div>
+            <div
+              ref="dimensionChart"
+              v-loading="dimensionLoading"
+              class="chart-container"
+              style="height: 380px"
+            >
+              <div v-if="!dimensionLoading && dimensionEmpty" class="chart-empty">
+                <i class="el-icon-s-data chart-empty-icon"></i>
+                <p class="chart-empty-text">暂无数据</p>
+              </div>
+            </div>
+        </div>
+      </el-col>
+
+      <!-- 右侧固钉侧栏 -->
+      <el-col :xs="24" :sm="24" :md="8" :lg="6" :xl="5">
+        <div class="sidebar-sticky">
+          <!-- 今日到期 -->
+          <div class="todo-section stats-card">
+              <div class="todo-header" @click="toggleTodoSection('today')">
+                <div class="todo-title-wrapper">
+                  <h4>今日到期</h4>
+                  <el-badge :value="todoData.today.length" class="todo-badge" />
+                </div>
+                <i :class="['el-icon-arrow-down', 'toggle-icon', { collapsed: collapsedSections.today }]"></i>
+              </div>
+              <el-collapse-transition>
+                <div v-show="!collapsedSections.today" class="todo-list">
+                  <div class="timeline-container">
+                    <div
+                      v-for="task in todoData.today"
+                      :key="task.id"
+                      class="todo-item today"
+                      @click="handleTodoClick(task)"
+                      @mouseenter="showTodoAction(task.id)"
+                      @mouseleave="hideTodoAction"
+                    >
+                      <div class="timeline-dot today-dot"></div>
+                      <div class="todo-content">
+                        <div class="task-name">{{ task.taskName }}</div>
+                        <div class="task-meta">
+                          <span>{{ task.plotName }}</span> | 
+                          <span>{{ task.executor }}</span>
+                        </div>
+                        <div class="task-time">{{ task.deadline }}</div>
+                      </div>
+                      <div v-show="hoveredTaskId === task.id" class="todo-action">
+                        <el-button size="mini" type="text" class="action-btn">查看</el-button>
+                      </div>
+                    </div>
+                  </div>
+                </div>
+              </el-collapse-transition>
+          </div>
+
+          <!-- 未来3天到期 -->
+          <div class="todo-section stats-card" style="margin-top: 16px">
+              <div class="todo-header" @click="toggleTodoSection('next3Days')">
+                <div class="todo-title-wrapper">
+                  <h4>未来3天到期</h4>
+                  <el-badge :value="todoData.next3Days.length" class="todo-badge" />
+                </div>
+                <i :class="['el-icon-arrow-down', 'toggle-icon', { collapsed: collapsedSections.next3Days }]"></i>
+              </div>
+              <el-collapse-transition>
+                <div v-show="!collapsedSections.next3Days" class="todo-list">
+                  <div class="timeline-container">
+                    <div
+                      v-for="task in todoData.next3Days"
+                      :key="task.id"
+                      class="todo-item next"
+                      @click="handleTodoClick(task)"
+                      @mouseenter="showTodoAction(task.id)"
+                      @mouseleave="hideTodoAction"
+                    >
+                      <div class="timeline-dot next-dot"></div>
+                      <div class="todo-content">
+                        <div class="task-name">{{ task.taskName }}</div>
+                        <div class="task-meta">
+                          <span>{{ task.plotName }}</span> | 
+                          <span>{{ task.executor }}</span>
+                        </div>
+                        <div class="task-time">{{ task.deadline }}</div>
+                      </div>
+                      <div v-show="hoveredTaskId === task.id" class="todo-action">
+                        <el-button size="mini" type="text" class="action-btn">查看</el-button>
+                      </div>
+                    </div>
+                  </div>
+                </div>
+              </el-collapse-transition>
+          </div>
+
+          <!-- 已逾期 -->
+          <div class="todo-section stats-card" style="margin-top: 16px">
+              <div class="todo-header" @click="toggleTodoSection('overdue')">
+                <div class="todo-title-wrapper">
+                  <h4>已逾期</h4>
+                  <el-badge :value="todoData.overdue.length" class="todo-badge danger" />
+                </div>
+                <i :class="['el-icon-arrow-down', 'toggle-icon', { collapsed: collapsedSections.overdue }]"></i>
+              </div>
+              <el-collapse-transition>
+                <div v-show="!collapsedSections.overdue" class="todo-list">
+                  <div class="timeline-container">
+                    <div
+                      v-for="task in todoData.overdue"
+                      :key="task.id"
+                      class="todo-item overdue"
+                      @click="handleTodoClick(task)"
+                      @mouseenter="showTodoAction(task.id)"
+                      @mouseleave="hideTodoAction"
+                    >
+                      <div class="timeline-dot overdue-dot"></div>
+                      <div class="todo-content">
+                        <div class="task-name">{{ task.taskName }}</div>
+                        <div class="task-meta">
+                          <span>{{ task.plotName }}</span> | 
+                          <span>{{ task.executor }}</span>
+                        </div>
+                        <div class="task-time danger">{{ task.deadline }}</div>
+                      </div>
+                      <div v-show="hoveredTaskId === task.id" class="todo-action">
+                        <el-button size="mini" type="text" class="action-btn">查看</el-button>
+                      </div>
+                    </div>
+                  </div>
+                </div>
+              </el-collapse-transition>
+          </div>
+        </div>
+      </el-col>
+    </el-row>
+
+    <!-- 任务明细抽屉 -->
+    <el-drawer
+      :title="drawerTitle"
+      :visible.sync="drawerVisible"
+      direction="rtl"
+      size="70%"
+      :before-close="handleDrawerClose"
+    >
+      <div class="drawer-content">
+        <el-table
+          v-loading="drawerLoading"
+          :data="drawerData"
+          border
+          style="width: 100%; margin-bottom: 20px"
+        >
+          <el-table-column label="任务名称" prop="taskName" min-width="180" />
+          <el-table-column label="类型" prop="taskType" width="90">
+            <template slot-scope="scope">
+              <el-tag size="mini" :type="getTaskTypeTagType(scope.row.taskType)">
+                {{ getTaskTypeName(scope.row.taskType) }}
+              </el-tag>
+            </template>
+          </el-table-column>
+          <el-table-column label="作物" prop="cropName" width="90" />
+          <el-table-column label="地块" prop="plotName" min-width="140" />
+          <el-table-column label="执行人" prop="executor" width="90" />
+          <el-table-column label="计划开始" prop="startTime" width="140" />
+          <el-table-column label="计划结束" prop="endTime" width="140" />
+          <el-table-column label="状态" prop="status" width="110">
+            <template slot-scope="scope">
+              <el-tag size="mini" :type="getStatusTagType(scope.row.status)">
+                {{ getStatusName(scope.row.status) }}
+              </el-tag>
+            </template>
+          </el-table-column>
+        </el-table>
+        
+        <el-pagination
+          :current-page="drawerPagination.page"
+          :page-sizes="[10, 20, 50, 100]"
+          :page-size="drawerPagination.size"
+          :total="drawerPagination.total"
+          layout="total, sizes, prev, pager, next, jumper"
+          @size-change="handleDrawerSizeChange"
+          @current-change="handleDrawerCurrentChange"
+        />
+        
+        <div class="drawer-actions" style="margin-top: 20px">
+          <el-button type="primary" @click="handleExportCsv">
+            <i class="el-icon-download"></i>
+            导出CSV
+          </el-button>
+        </div>
+      </div>
+    </el-drawer>
+  </div>
+</template>
+
+<script>
+import * as echarts from 'echarts'
+import seedrandom from 'seedrandom'
+import CountTo from 'vue-count-to'
+
+export default {
+  name: 'TaskStats',
+  components: {
+    CountTo
+  },
+  data() {
+    return {
+      // 快捷时间选择
+      timeShortcuts: [
+        { label: '近7天', value: 7 },
+        { label: '近30天', value: 30 },
+        { label: '近90天', value: 90 }
+      ],
+      currentShortcut: 30,
+      
+      // 趋势图指标选项
+      trendMetrics: [
+        { label: '创建数', value: 'created' },
+        { label: '完成数', value: 'completed' },
+        { label: '完成率', value: 'completion_rate' }
+      ],
+      
+      // 维度对比选项
+      dimensionTypes: [
+        { label: '地块', value: 'plot' },
+        { label: '作物', value: 'crop' },
+        { label: '任务类型', value: 'taskType' }
+      ],
+      dimensionMetrics: [
+        { label: '完成率', value: 'completion_rate' },
+        { label: '准时率', value: 'ontime_rate' }
+      ],
+      
+      // 侧栏折叠状态
+      collapsedSections: {
+        today: false,
+        next3Days: false,
+        overdue: false
+      },
+      hoveredTaskId: null,
+      
+      // 图表空状态
+      trendEmpty: false,
+      dimensionEmpty: false,
+      
+      // 筛选参数
+      filters: {
+        dateRange: [],
+        farms: [],
+        plots: [],
+        crops: [],
+        taskTypes: [],
+        executors: [],
+        statuses: []
+      },
+      
+      // 下拉选项
+      farmOptions: [],
+      plotOptions: [],
+      cropOptions: [],
+      taskTypeOptions: [
+        { label: '施肥', value: 'fertilize' },
+        { label: '灌溉', value: 'irrigate' },
+        { label: '打药', value: 'spray' },
+        { label: '采摘', value: 'harvest' },
+        { label: '巡检', value: 'inspect' },
+        { label: '除草', value: 'weed' },
+        { label: '其他', value: 'other' }
+      ],
+      executorOptions: [],
+      statusOptions: [
+        { label: '未开始', value: 'not_started' },
+        { label: '进行中', value: 'in_progress' },
+        { label: '已完成', value: 'completed' },
+        { label: '已逾期', value: 'overdue' },
+        { label: '已取消', value: 'cancelled' }
+      ],
+      
+      // 日期选择器配置
+      pickerOptions: {
+        shortcuts: [
+          {
+            text: '近7天',
+            onClick: (picker) => {
+              const end = new Date()
+              const start = new Date()
+              start.setTime(start.getTime() - 3600 * 1000 * 24 * 7)
+              picker.$emit('pick', [start, end])
+            }
+          },
+          {
+            text: '近30天',
+            onClick: (picker) => {
+              const end = new Date()
+              const start = new Date()
+              start.setTime(start.getTime() - 3600 * 1000 * 24 * 30)
+              picker.$emit('pick', [start, end])
+            }
+          },
+          {
+            text: '近90天',
+            onClick: (picker) => {
+              const end = new Date()
+              const start = new Date()
+              start.setTime(start.getTime() - 3600 * 1000 * 24 * 90)
+              picker.$emit('pick', [start, end])
+            }
+          }
+        ]
+      },
+      
+      // KPI 数据
+      kpiData: [],
+      
+      // 趋势图
+      trendChart: null,
+      trendMetric: 'created',
+      trendLoading: false,
+      
+      // 维度对比图
+      dimensionChart: null,
+      dimensionType: 'plot',
+      dimensionMetric: 'completion_rate',
+      dimensionLoading: false,
+      
+      // 待办数据
+      todoData: {
+        today: [],
+        next3Days: [],
+        overdue: []
+      },
+      
+      // 抽屉
+      drawerVisible: false,
+      drawerTitle: '任务明细',
+      drawerData: [],
+      drawerLoading: false,
+      drawerPagination: {
+        page: 1,
+        size: 20,
+        total: 0
+      }
+    }
+  },
+  
+  mounted() {
+    this.initDefaultFilters()
+    this.initOptions()
+    this.loadAllData()
+    this.initCharts()
+    
+    // 监听窗口大小变化
+    window.addEventListener('resize', this.handleResize)
+  },
+  
+  beforeDestroy() {
+    if (this.trendChart) {
+      this.trendChart.dispose()
+    }
+    if (this.dimensionChart) {
+      this.dimensionChart.dispose()
+    }
+    window.removeEventListener('resize', this.handleResize)
+  },
+  
+  methods: {
+    // 初始化默认筛选条件
+    initDefaultFilters() {
+      const end = new Date()
+      const start = new Date()
+      start.setTime(start.getTime() - 3600 * 1000 * 24 * 30) // 默认近30天
+      
+      this.filters.dateRange = [
+        this.formatDate(start),
+        this.formatDate(end)
+      ]
+    },
+    
+    // 初始化选项数据
+    initOptions() {
+      this.farmOptions = this.mockFarms()
+      this.plotOptions = this.mockPlots()
+      this.cropOptions = this.mockCrops()
+      this.executorOptions = this.mockExecutors()
+    },
+    
+    // 加载所有数据
+    loadAllData() {
+      this.loadKpiData()
+      this.loadTrendData()
+      this.loadDimensionData()
+      this.loadTodoData()
+    },
+    
+    // 初始化图表
+    initCharts() {
+      this.$nextTick(() => {
+        this.initTrendChart()
+        this.initDimensionChart()
+      })
+    },
+    
+    // 处理查询
+    handleQuery() {
+      this.loadAllData()
+    },
+    
+    // 处理重置
+    handleReset() {
+      this.$refs.filterForm.resetFields()
+      this.initDefaultFilters()
+      this.loadAllData()
+    },
+    
+    // 处理快捷时间选择
+    handleShortcutClick(shortcut) {
+      this.currentShortcut = shortcut.value
+      const end = new Date()
+      const start = new Date()
+      start.setTime(start.getTime() - 3600 * 1000 * 24 * shortcut.value)
+      
+      this.filters.dateRange = [
+        this.formatDate(start),
+        this.formatDate(end)
+      ]
+      this.loadAllData()
+    },
+    
+    // 处理日期范围变更
+    handleDateRangeChange() {
+      this.currentShortcut = null
+    },
+    
+    // 处理KPI菜单操作
+    handleKpiAction(command) {
+      if (command.action === 'export') {
+        this.exportKpiData(command.key)
+      } else if (command.action === 'detail') {
+        this.handleKpiClick(command.key)
+      }
+    },
+    
+    // 导出KPI数据
+    exportKpiData(key) {
+      const kpi = this.kpiData.find(item => item.key === key)
+      if (kpi) {
+        this.$message.success(`导出 ${kpi.title} 数据成功`)
+      }
+    },
+    
+    // 下载图表
+    downloadChart(type) {
+      const chart = type === 'trend' ? this.trendChart : this.dimensionChart
+      if (chart) {
+        const url = chart.getDataURL({
+          type: 'png',
+          backgroundColor: '#fff'
+        })
+        const link = document.createElement('a')
+        link.href = url
+        link.download = `${type === 'trend' ? '任务趋势' : '维度对比'}.png`
+        link.click()
+      }
+    },
+    
+    // 全屏显示图表
+    fullscreenChart(type) {
+      this.$message.info('全屏功能开发中...')
+    },
+    
+    // 切换侧栏折叠状态
+    toggleTodoSection(section) {
+      this.collapsedSections[section] = !this.collapsedSections[section]
+    },
+    
+    // 显示待办操作按钮
+    showTodoAction(taskId) {
+      this.hoveredTaskId = taskId
+    },
+    
+    // 隐藏待办操作按钮
+    hideTodoAction() {
+      this.hoveredTaskId = null
+    },
+    
+    // 加载KPI数据
+    loadKpiData() {
+      const kpi = this.mockKpi(this.filters)
+      this.kpiData = [
+        { 
+          title: '任务总数', 
+          value: kpi.total, 
+          type: 'number', 
+          color: '#1F2937', 
+          key: 'total',
+          icon: 'el-icon-s-data',
+          iconBg: 'rgba(74, 144, 226, 0.12)'
+        },
+        { 
+          title: '已完成', 
+          value: kpi.done, 
+          type: 'number', 
+          color: '#3BB44A', 
+          key: 'completed',
+          icon: 'el-icon-circle-check',
+          iconBg: 'rgba(59, 180, 74, 0.12)'
+        },
+        { 
+          title: '完成率', 
+          value: kpi.doneRate * 100, 
+          type: 'percent', 
+          color: '#3BB44A', 
+          key: 'completion_rate',
+          icon: 'el-icon-pie-chart',
+          iconBg: 'rgba(59, 180, 74, 0.12)'
+        },
+        { 
+          title: '准时率', 
+          value: kpi.ontimeRate * 100, 
+          type: 'percent', 
+          color: '#4A90E2', 
+          key: 'ontime_rate',
+          icon: 'el-icon-timer',
+          iconBg: 'rgba(74, 144, 226, 0.12)'
+        },
+        { 
+          title: '平均完成时长', 
+          value: kpi.avgDuration, 
+          type: 'duration', 
+          color: '#20B2AA', 
+          key: 'avg_duration',
+          icon: 'el-icon-stopwatch',
+          iconBg: 'rgba(32, 178, 170, 0.12)',
+          unit: kpi.durationUnit
+        },
+        { 
+          title: '逾期数', 
+          value: kpi.overdue, 
+          type: 'number', 
+          color: '#E85D75', 
+          key: 'overdue',
+          icon: 'el-icon-warning',
+          iconBg: 'rgba(232, 93, 117, 0.12)'
+        },
+        { 
+          title: '进行中', 
+          value: kpi.inProgress, 
+          type: 'number', 
+          color: '#4A90E2', 
+          key: 'in_progress',
+          icon: 'el-icon-loading',
+          iconBg: 'rgba(74, 144, 226, 0.12)'
+        },
+        { 
+          title: '未开始', 
+          value: kpi.notStarted, 
+          type: 'number', 
+          color: '#8B949E', 
+          key: 'not_started',
+          icon: 'el-icon-time',
+          iconBg: 'rgba(139, 148, 158, 0.12)'
+        }
+      ]
+    },
+    
+    // 加载趋势数据
+    loadTrendData() {
+      this.trendLoading = true
+      setTimeout(() => {
+        const data = this.mockTrend(this.filters, this.trendMetric)
+        this.updateTrendChart(data)
+        this.trendLoading = false
+      }, 500)
+    },
+    
+    // 加载维度对比数据
+    loadDimensionData() {
+      this.dimensionLoading = true
+      setTimeout(() => {
+        const data = this.mockDimension(this.filters, this.dimensionType, this.dimensionMetric)
+        this.updateDimensionChart(data)
+        this.dimensionLoading = false
+      }, 500)
+    },
+    
+    // 加载待办数据
+    loadTodoData() {
+      this.todoData = this.mockTodo(this.filters)
+    },
+    
+    // 初始化趋势图
+    initTrendChart() {
+      this.trendChart = echarts.init(this.$refs.trendChart)
+      this.loadTrendData()
+    },
+    
+    // 更新趋势图
+    updateTrendChart(data) {
+      if (!this.trendChart) return
+      
+      const isPercent = this.trendMetric === 'completion_rate'
+      
+      const option = {
+        grid: {
+          left: '6%',
+          right: '6%',
+          bottom: '12%',
+          top: '20%',
+          containLabel: true
+        },
+        tooltip: {
+          trigger: 'axis',
+          backgroundColor: 'rgba(255, 255, 255, 0.9)',
+          borderColor: '#E4E7ED',
+          borderWidth: 1,
+          textStyle: {
+            color: '#606266'
+          },
+          formatter: (params) => {
+            const param = params[0]
+            const value = isPercent ? (param.value * 100).toFixed(1) + '%' : param.value
+            return `${param.axisValue}<br/>${param.seriesName}: ${value}`
+          }
+        },
+        xAxis: {
+          type: 'category',
+          data: data.map(item => item.date),
+          boundaryGap: false,
+          axisLabel: {
+            color: '#909399',
+            fontSize: 12
+          },
+          axisTick: {
+            show: false
+          },
+          axisLine: {
+            lineStyle: {
+              color: '#E4E7ED'
+            }
+          }
+        },
+        yAxis: {
+          type: 'value',
+          min: isPercent ? 0 : undefined,
+          max: isPercent ? 1 : undefined,
+          axisLabel: {
+            color: '#909399',
+            fontSize: 12,
+            formatter: isPercent ? (value) => (value * 100) + '%' : undefined
+          },
+          axisTick: {
+            show: false
+          },
+          axisLine: {
+            show: false
+          },
+          splitLine: {
+            lineStyle: {
+              color: '#F5F7FA'
+            }
+          }
+        },
+        series: [{
+          name: this.getTrendMetricName(this.trendMetric),
+          type: 'line',
+          data: data.map(item => item.value),
+          smooth: true,
+          symbolSize: 5,
+          symbol: 'circle',
+          lineStyle: {
+            width: 2,
+            color: '#12B67F'
+          },
+          areaStyle: {
+            color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
+              { offset: 0, color: 'rgba(18, 182, 127, 0.25)' },
+              { offset: 1, color: 'rgba(18, 182, 127, 0)' }
+            ])
+          },
+          itemStyle: {
+            color: '#12B67F',
+            borderColor: '#fff',
+            borderWidth: 2
+          },
+          emphasis: {
+            focus: 'series',
+            itemStyle: {
+              shadowBlur: 10,
+              shadowColor: 'rgba(18, 182, 127, 0.3)'
+            }
+          }
+        }]
+      }
+      
+      this.trendChart.setOption(option)
+    },
+    
+    // 初始化维度对比图
+    initDimensionChart() {
+      this.dimensionChart = echarts.init(this.$refs.dimensionChart)
+      this.loadDimensionData()
+      
+      // 添加点击事件
+      this.dimensionChart.on('click', (params) => {
+        this.openDrawer({
+          type: 'dimension',
+          dimension: this.dimensionType,
+          name: params.name,
+          metric: this.dimensionMetric
+        })
+      })
+    },
+    
+    // 更新维度对比图
+    updateDimensionChart(data) {
+      if (!this.dimensionChart) return
+      
+      const option = {
+        grid: {
+          left: '5%',
+          right: '8%',
+          bottom: '8%',
+          top: '5%',
+          containLabel: true
+        },
+        tooltip: {
+          trigger: 'axis',
+          axisPointer: { type: 'shadow' },
+          backgroundColor: 'rgba(255, 255, 255, 0.9)',
+          borderColor: '#E4E7ED',
+          borderWidth: 1,
+          textStyle: {
+            color: '#606266'
+          },
+          formatter: (params) => {
+            const param = params[0]
+            const value = (param.value * 100).toFixed(1) + '%'
+            return `${param.axisValue}<br/>${param.seriesName}: ${value}`
+          }
+        },
+        xAxis: {
+          type: 'value',
+          max: 1,
+          axisLabel: {
+            color: '#909399',
+            fontSize: 12,
+            formatter: v => (v * 100).toFixed(0) + '%'
+          },
+          axisTick: {
+            show: false
+          },
+          axisLine: {
+            show: false
+          },
+          splitLine: {
+            lineStyle: {
+              color: '#F5F7FA'
+            }
+          }
+        },
+        yAxis: {
+          type: 'category',
+          data: data.map(item => item.name),
+          axisLabel: {
+            color: '#606266',
+            fontSize: 12,
+            width: 100,
+            overflow: 'truncate'
+          },
+          axisTick: {
+            show: false
+          },
+          axisLine: {
+            lineStyle: {
+              color: '#E4E7ED'
+            }
+          }
+        },
+        series: [{
+          name: this.getDimensionMetricName(this.dimensionMetric),
+          type: 'bar',
+          barWidth: 18,
+          data: data.map(item => item.value),
+          itemStyle: {
+            color: new echarts.graphic.LinearGradient(0, 0, 1, 0, [
+              { offset: 0, color: '#3BB44A' },
+              { offset: 1, color: '#66CC6A' }
+            ]),
+            borderRadius: [0, 8, 8, 0],
+            shadowBlur: 6,
+            shadowColor: 'rgba(59, 180, 74, 0.3)',
+            shadowOffsetX: 2
+          },
+          backgroundStyle: {
+            color: 'rgba(0, 0, 0, 0.05)',
+            borderRadius: [0, 8, 8, 0]
+          },
+          showBackground: true,
+          label: {
+            show: true,
+            position: 'right',
+            formatter: p => (p.value * 100).toFixed(1) + '%',
+            color: '#4B5563',
+            fontSize: 12,
+            fontWeight: 500,
+            backgroundColor: '#fff',
+            borderRadius: 6,
+            padding: [4, 8],
+            borderColor: '#E5E7EB',
+            borderWidth: 1,
+            shadowBlur: 4,
+            shadowColor: 'rgba(0, 0, 0, 0.1)'
+          },
+          emphasis: {
+            itemStyle: {
+              color: new echarts.graphic.LinearGradient(0, 0, 1, 0, [
+                { offset: 0, color: '#2D9A3A' },
+                { offset: 1, color: '#5ABF60' }
+              ]),
+              shadowBlur: 10,
+              shadowColor: 'rgba(59, 180, 74, 0.5)',
+              shadowOffsetX: 3
+            }
+          }
+        }]
+      }
+      
+      this.dimensionChart.setOption(option)
+    },
+    
+    // 处理趋势指标变更
+    handleTrendMetricChange(metric) {
+      if (metric) {
+        this.trendMetric = metric
+      }
+      this.loadTrendData()
+    },
+    
+    // 处理维度类型变更
+    handleDimensionTypeChange(type) {
+      if (type) {
+        this.dimensionType = type
+      }
+      this.loadDimensionData()
+    },
+    
+    // 处理维度指标变更
+    handleDimensionMetricChange(metric) {
+      if (metric) {
+        this.dimensionMetric = metric
+      }
+      this.loadDimensionData()
+    },
+    
+    // 处理KPI点击
+    handleKpiClick(key) {
+      this.openDrawer({ type: 'kpi', key })
+    },
+    
+    // 处理待办点击
+    handleTodoClick(task) {
+      this.openDrawer({ type: 'todo', task })
+    },
+    
+    // 处理维度图点击
+    handleDimensionClick(params) {
+      this.openDrawer({
+        type: 'dimension',
+        dimension: this.dimensionType,
+        name: params.name,
+        metric: this.dimensionMetric
+      })
+    },
+    
+    // 打开抽屉
+    openDrawer(params) {
+      this.drawerVisible = true
+      this.drawerTitle = this.getDrawerTitle(params)
+      this.loadDrawerData(params)
+    },
+    
+    // 获取抽屉标题
+    getDrawerTitle(params) {
+      if (params.type === 'kpi') {
+        const kpi = this.kpiData.find(item => item.key === params.key)
+        return `任务明细 - ${kpi ? kpi.title : ''}`
+      } else if (params.type === 'todo') {
+        return `任务明细 - ${params.task.taskName}`
+      } else if (params.type === 'dimension') {
+        return `任务明细 - ${params.name}`
+      }
+      return '任务明细'
+    },
+    
+    // 加载抽屉数据
+    loadDrawerData(params) {
+      this.drawerLoading = true
+      setTimeout(() => {
+        const data = this.mockDetail(params)
+        this.drawerData = data.slice(
+          (this.drawerPagination.page - 1) * this.drawerPagination.size,
+          this.drawerPagination.page * this.drawerPagination.size
+        )
+        this.drawerPagination.total = data.length
+        this.drawerLoading = false
+      }, 500)
+    },
+    
+    // 处理抽屉分页大小变更
+    handleDrawerSizeChange(size) {
+      this.drawerPagination.size = size
+      this.drawerPagination.page = 1
+      // 重新加载数据
+    },
+    
+    // 处理抽屉页码变更
+    handleDrawerCurrentChange(page) {
+      this.drawerPagination.page = page
+      // 重新加载数据
+    },
+    
+    // 处理抽屉关闭
+    handleDrawerClose() {
+      this.drawerVisible = false
+      this.drawerPagination.page = 1
+      this.drawerPagination.size = 20
+    },
+    
+    // 导出CSV
+    handleExportCsv() {
+      const csvContent = this.generateCsvContent(this.drawerData)
+      this.downloadCsv(csvContent, '任务明细.csv')
+    },
+    
+    // 生成CSV内容
+    generateCsvContent(data) {
+      const headers = ['任务名称', '类型', '作物', '地块', '执行人', '计划开始', '计划结束', '状态']
+      const rows = data.map(item => [
+        item.taskName,
+        this.getTaskTypeName(item.taskType),
+        item.cropName,
+        item.plotName,
+        item.executor,
+        item.startTime,
+        item.endTime,
+        this.getStatusName(item.status)
+      ])
+      
+      return [headers, ...rows].map(row => row.join(',')).join('\n')
+    },
+    
+    // 下载CSV
+    downloadCsv(content, filename) {
+      const blob = new Blob(['\ufeff' + content], { type: 'text/csv;charset=utf-8;' })
+      const link = document.createElement('a')
+      const url = URL.createObjectURL(blob)
+      link.setAttribute('href', url)
+      link.setAttribute('download', filename)
+      link.style.visibility = 'hidden'
+      document.body.appendChild(link)
+      link.click()
+      document.body.removeChild(link)
+    },
+    
+    // 处理窗口大小变更
+    handleResize() {
+      if (this.trendChart) {
+        this.trendChart.resize()
+      }
+      if (this.dimensionChart) {
+        this.dimensionChart.resize()
+      }
+    },
+    
+    // 格式化KPI值
+    formatKpiValue(value, type) {
+      if (type === 'percent') {
+        return (value * 100).toFixed(1) + '%'
+      }
+      return value.toLocaleString()
+    },
+    
+    // 获取趋势指标名称
+    getTrendMetricName(metric) {
+      const names = {
+        created: '创建数',
+        completed: '完成数',
+        completion_rate: '完成率'
+      }
+      return names[metric] || metric
+    },
+    
+    // 获取维度指标名称
+    getDimensionMetricName(metric) {
+      const names = {
+        completion_rate: '完成率',
+        ontime_rate: '准时率'
+      }
+      return names[metric] || metric
+    },
+    
+    // 获取任务类型名称
+    getTaskTypeName(type) {
+      const names = {
+        fertilize: '施肥',
+        irrigate: '灌溉',
+        spray: '打药',
+        harvest: '采摘',
+        inspect: '巡检',
+        weed: '除草',
+        other: '其他'
+      }
+      return names[type] || type
+    },
+    
+    // 获取任务类型标签类型
+    getTaskTypeTagType(type) {
+      const types = {
+        fertilize: 'success',
+        irrigate: 'primary',
+        spray: 'warning',
+        harvest: 'danger',
+        inspect: 'info',
+        weed: '',
+        other: 'default'
+      }
+      return types[type] || ''
+    },
+    
+    // 获取状态名称
+    getStatusName(status) {
+      const names = {
+        not_started: '未开始',
+        in_progress: '进行中',
+        completed: '已完成',
+        overdue: '已逾期',
+        cancelled: '已取消'
+      }
+      return names[status] || status
+    },
+    
+    // 获取状态标签类型
+    getStatusTagType(status) {
+      const types = {
+        not_started: 'info',
+        in_progress: 'primary',
+        completed: 'success',
+        overdue: 'danger',
+        cancelled: 'default'
+      }
+      return types[status] || ''
+    },
+    
+    // 格式化日期
+    formatDate(date) {
+      const d = new Date(date)
+      const year = d.getFullYear()
+      const month = String(d.getMonth() + 1).padStart(2, '0')
+      const day = String(d.getDate()).padStart(2, '0')
+      return `${year}-${month}-${day}`
+    },
+    
+    // 生成哈希种子
+    generateSeed(filters) {
+      const str = JSON.stringify(filters)
+      let hash = 0
+      for (let i = 0; i < str.length; i++) {
+        const char = str.charCodeAt(i)
+        hash = ((hash << 5) - hash) + char
+        hash = hash & hash // Convert to 32bit integer
+      }
+      return Math.abs(hash)
+    },
+    
+    // Mock 数据生成方法
+    mockFarms() {
+      return [
+        { label: '阳光农场', value: 'farm1' },
+        { label: '绿野农场', value: 'farm2' },
+        { label: '丰收农场', value: 'farm3' },
+        { label: '田园农场', value: 'farm4' },
+        { label: '科技农场', value: 'farm5' }
+      ]
+    },
+    
+    mockPlots() {
+      return [
+        { label: '东区1号田', value: 'plot1' },
+        { label: '西区2号田', value: 'plot2' },
+        { label: '南区果园', value: 'plot3' },
+        { label: '北区菜地', value: 'plot4' },
+        { label: '温室大棚A区', value: 'plot5' },
+        { label: '智能温室B区', value: 'plot6' }
+      ]
+    },
+    
+    mockCrops() {
+      return [
+        { label: '水稻', value: 'rice' },
+        { label: '玉米', value: 'corn' },
+        { label: '小麦', value: 'wheat' },
+        { label: '番茄', value: 'tomato' },
+        { label: '黄瓜', value: 'cucumber' },
+        { label: '草莓', value: 'strawberry' }
+      ]
+    },
+    
+    mockExecutors() {
+      return [
+        { label: '张三', value: 'user1' },
+        { label: '李四', value: 'user2' },
+        { label: '王五', value: 'user3' },
+        { label: '赵六', value: 'user4' },
+        { label: '钱七', value: 'user5' }
+      ]
+    },
+    
+    mockKpi(filters) {
+      const seed = this.generateSeed(filters)
+      const random = seedrandom(seed)
+      
+      const total = Math.floor(random() * 500) + 200
+      const done = Math.floor(random() * total * 0.8)
+      const overdue = Math.floor(random() * total * 0.1)
+      const inProgress = Math.floor(random() * (total - done - overdue) * 0.6)
+      const notStarted = total - done - overdue - inProgress
+      
+      const doneRate = total > 0 ? done / total : 0
+      const ontimeRate = done > 0 ? (done - Math.floor(random() * done * 0.2)) / done : 0
+      
+      // 生成平均完成时长数据
+      const duration = random() * 96 + 4 // 4-100小时
+      let avgDuration, durationUnit
+      
+      if (duration >= 24) {
+        avgDuration = Number((duration / 24).toFixed(1))
+        durationUnit = '天'
+      } else {
+        avgDuration = Number(duration.toFixed(1))
+        durationUnit = '小时'
+      }
+      
+      return {
+        total,
+        done,
+        doneRate,
+        ontimeRate,
+        overdue,
+        inProgress,
+        notStarted,
+        avgDuration,
+        durationUnit
+      }
+    },
+    
+    mockTrend(filters, metric) {
+      const seed = this.generateSeed({ ...filters, metric })
+      const random = seedrandom(seed)
+      
+      const data = []
+      const days = 30
+      const startDate = new Date(filters.dateRange[0])
+      
+      for (let i = 0; i < days; i++) {
+        const date = new Date(startDate)
+        date.setDate(date.getDate() + i)
+        
+        let value
+        if (metric === 'completion_rate') {
+          value = 0.6 + random() * 0.3 // 60%-90%
+        } else {
+          value = Math.floor(random() * 50) + 10 // 10-60
+        }
+        
+        data.push({
+          date: this.formatDate(date),
+          value
+        })
+      }
+      
+      return data
+    },
+    
+    mockDimension(filters, type, metric) {
+      const seed = this.generateSeed({ ...filters, type, metric })
+      const random = seedrandom(seed)
+      
+      let names = []
+      if (type === 'plot') {
+        names = this.plotOptions.map(item => item.label)
+      } else if (type === 'crop') {
+        names = this.cropOptions.map(item => item.label)
+      } else if (type === 'taskType') {
+        names = this.taskTypeOptions.map(item => item.label)
+      }
+      
+      return names.map(name => ({
+        name,
+        value: 0.5 + random() * 0.4 // 50%-90%
+      }))
+    },
+    
+    mockTodo(filters) {
+      const seed = this.generateSeed(filters)
+      const random = seedrandom(seed)
+      
+      const today = []
+      const next3Days = []
+      const overdue = []
+      
+      // 生成今日到期任务
+      for (let i = 0; i < 3; i++) {
+        today.push({
+          id: `today_${i}`,
+          taskName: `任务${i + 1}`,
+          plotName: this.plotOptions[Math.floor(random() * this.plotOptions.length)].label,
+          executor: this.executorOptions[Math.floor(random() * this.executorOptions.length)].label,
+          deadline: '今日 18:00'
+        })
+      }
+      
+      // 生成未来3天到期任务
+      for (let i = 0; i < 5; i++) {
+        next3Days.push({
+          id: `next_${i}`,
+          taskName: `任务${i + 4}`,
+          plotName: this.plotOptions[Math.floor(random() * this.plotOptions.length)].label,
+          executor: this.executorOptions[Math.floor(random() * this.executorOptions.length)].label,
+          deadline: `${i + 1}天后`
+        })
+      }
+      
+      // 生成逾期任务
+      for (let i = 0; i < 2; i++) {
+        overdue.push({
+          id: `overdue_${i}`,
+          taskName: `逾期任务${i + 1}`,
+          plotName: this.plotOptions[Math.floor(random() * this.plotOptions.length)].label,
+          executor: this.executorOptions[Math.floor(random() * this.executorOptions.length)].label,
+          deadline: `逾期${i + 1}天`
+        })
+      }
+      
+      return { today, next3Days, overdue }
+    },
+    
+    mockDetail(params) {
+      const seed = this.generateSeed(params)
+      const random = seedrandom(seed)
+      
+      const data = []
+      const count = Math.floor(random() * 30) + 20
+      
+      for (let i = 0; i < count; i++) {
+        data.push({
+          id: i + 1,
+          taskName: `任务${i + 1}`,
+          taskType: this.taskTypeOptions[Math.floor(random() * this.taskTypeOptions.length)].value,
+          cropName: this.cropOptions[Math.floor(random() * this.cropOptions.length)].label,
+          plotName: this.plotOptions[Math.floor(random() * this.plotOptions.length)].label,
+          executor: this.executorOptions[Math.floor(random() * this.executorOptions.length)].label,
+          startTime: '2024-01-15 08:00',
+          endTime: '2024-01-15 18:00',
+          status: this.statusOptions[Math.floor(random() * this.statusOptions.length)].value
+        })
+      }
+      
+      return data
+    }
+  }
+}
+</script>
+
+<style scoped>
+/* 页面背景与容器 */
+.stats-page {
+  background: #F7F8FA;
+  min-height: 100vh;
+  padding: 16px;
+  width: 100%;
+  box-sizing: border-box;
+}
+
+/* 通用卡片底板 */
+.stats-card {
+  --card-bg: #fff;
+  --card-radius: 10px;
+  --card-border: 1px solid #EEF1F5;
+  --card-shadow: 0 6px 18px rgba(22,31,41,.06);
+  --card-shadow-hover: 0 10px 24px rgba(22,31,41,.10);
+
+  background: var(--card-bg);
+  border: var(--card-border);
+  border-radius: var(--card-radius);
+  box-shadow: var(--card-shadow);
+  padding: 20px;
+  margin-left: 0;
+  margin-right: 0;
+  transition: box-shadow .2s ease, transform .2s ease;
+}
+
+.stats-card:hover {
+  box-shadow: var(--card-shadow-hover);
+  transform: translateY(-2px);
+}
+
+/* KPI 卡片区域 */
+.kpi-section {
+  margin-bottom: 16px;
+}
+
+/* KPI 网格布局 */
+.kpi-grid {
+  display: grid;
+  grid-template-columns: repeat(8, 1fr);
+  gap: 12px;
+  width: 100%;
+}
+
+.kpi-card {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+  padding: 12px;
+  border: 1px solid #E5E7EB;
+  border-radius: 8px;
+  background: #ffffff;
+  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04), 0 1px 3px rgba(0, 0, 0, 0.06);
+  cursor: pointer;
+  transition: all 0.25s ease;
+  position: relative;
+  min-height: 68px;
+  box-sizing: border-box;
+}
+
+.kpi-card:hover {
+  transform: translateY(-2px);
+  box-shadow: 0 8px 25px rgba(0, 0, 0, 0.08), 0 3px 10px rgba(0, 0, 0, 0.1);
+  border-color: #D1D5DB;
+}
+
+.kpi-card__icon {
+  width: 28px;
+  height: 28px;
+  border-radius: 6px;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  font-size: 14px;
+  flex-shrink: 0;
+}
+
+.kpi-card__content {
+  flex: 1;
+  min-width: 0;
+  display: flex;
+  flex-direction: column;
+  justify-content: center;
+}
+
+.kpi-card__meta {
+  font-size: 12px;
+  color: #6B7280;
+  line-height: 16px;
+  margin-bottom: 4px;
+  font-weight: 500;
+}
+
+.kpi-card__value {
+  display: flex;
+  align-items: baseline;
+  gap: 4px;
+  line-height: 1.2;
+}
+
+.kpi-number {
+  font-size: 18px;
+  font-weight: 700;
+  color: #111827;
+}
+
+.kpi-unit {
+  font-size: 12px;
+  font-weight: 500;
+  color: #6B7280;
+  margin-left: 2px;
+}
+
+.kpi-card__menu {
+  position: absolute;
+  top: 8px;
+  right: 8px;
+  opacity: 0;
+  transition: opacity 0.2s ease;
+  z-index: 10;
+}
+
+.kpi-card:hover .kpi-card__menu {
+  opacity: 1;
+}
+
+.kpi-menu-trigger {
+  display: inline-flex;
+  align-items: center;
+  justify-content: center;
+  width: 20px;
+  height: 20px;
+  border-radius: 4px;
+  color: #9CA3AF;
+  transition: all 0.2s ease;
+  cursor: pointer;
+}
+
+.kpi-menu-trigger:hover {
+  background: #F3F4F6;
+  color: #6B7280;
+}
+
+/* 保底措施 */
+.el-card {
+  border: 1px solid #EEF1F5 !important;
+}
+
+.el-card__body {
+  padding: 0 !important;
+}
+
+/* 筛选区域 */
+.time-shortcuts {
+  display: flex;
+  align-items: center;
+  margin-bottom: 16px;
+}
+
+.shortcuts-label {
+  font-size: 14px;
+  color: #606266;
+  margin-right: 12px;
+  font-weight: 500;
+}
+
+.shortcut-pills {
+  display: flex;
+  gap: 8px;
+}
+
+.shortcut-pill {
+  padding: 6px 12px;
+  border: 1px solid #EEF1F5;
+  border-radius: 16px;
+  background: #fff;
+  color: #606266;
+  font-size: 12px;
+  cursor: pointer;
+  transition: all 0.2s ease;
+}
+
+.shortcut-pill:hover {
+  border-color: #12B67F;
+  color: #12B67F;
+}
+
+.shortcut-pill.active {
+  background: #12B67F;
+  border-color: #12B67F;
+  color: #fff;
+}
+
+.filter-divider {
+  height: 1px;
+  background: #EFF2F7;
+  margin: 16px 0;
+}
+
+.filter-form .el-form-item {
+  margin-right: 12px;
+  margin-bottom: 12px;
+}
+
+.action-buttons {
+  display: flex;
+  gap: 8px;
+}
+
+.btn-primary {
+  background: linear-gradient(135deg, #12B67F 0%, #21ba45 100%) !important;
+  border: none !important;
+  border-radius: 8px !important;
+  font-weight: 600;
+  padding: 8px 20px !important;
+  transition: all 0.3s ease;
+  transform: translateY(0);
+}
+
+.btn-primary:hover {
+  transform: translateY(-1px);
+  box-shadow: 0 4px 12px rgba(18, 182, 127, 0.3);
+}
+
+.btn-secondary {
+  background: #fff !important;
+  border: 1px solid #EEF1F5 !important;
+  color: #606266 !important;
+  border-radius: 8px !important;
+  font-weight: 500;
+  padding: 8px 20px !important;
+  transition: all 0.3s ease;
+  transform: translateY(0);
+}
+
+.btn-secondary:hover {
+  transform: translateY(-1px);
+  box-shadow: 0 2px 8px rgba(0,0,0,0.1);
+  border-color: #12B67F !important;
+  color: #12B67F !important;
+}
+
+/* 图表区域 */
+.main-content {
+  margin-bottom: 0;
+  width: 100%;
+}
+
+/* 响应式调整 */
+@media (max-width: 1400px) {
+  .kpi-grid {
+    grid-template-columns: repeat(4, 1fr);
+    gap: 10px;
+  }
+  
+  .kpi-card {
+    padding: 10px;
+    gap: 6px;
+  }
+}
+
+@media (max-width: 992px) {
+  .kpi-grid {
+    grid-template-columns: repeat(2, 1fr);
+    gap: 12px;
+  }
+  
+  .kpi-card {
+    padding: 14px;
+    gap: 8px;
+  }
+  
+  .kpi-card__icon {
+    width: 28px;
+    height: 28px;
+    font-size: 14px;
+  }
+  
+  .kpi-number {
+    font-size: 18px;
+  }
+}
+
+@media (max-width: 768px) {
+  .stats-page {
+    padding: 12px;
+  }
+  
+  .kpi-grid {
+    grid-template-columns: repeat(2, 1fr);
+    gap: 10px;
+  }
+  
+  .kpi-card {
+    padding: 12px;
+    gap: 8px;
+    min-height: 70px;
+  }
+  
+  .kpi-card__icon {
+    width: 26px;
+    height: 26px;
+    font-size: 12px;
+  }
+  
+  .kpi-card__meta {
+    font-size: 11px;
+  }
+  
+  .kpi-number {
+    font-size: 16px;
+  }
+  
+  .kpi-unit {
+    font-size: 10px;
+  }
+  
+  .chart-container {
+    height: 300px !important;
+  }
+}
+
+@media (max-width: 480px) {
+  .kpi-grid {
+    grid-template-columns: 1fr;
+    gap: 8px;
+  }
+  
+  .kpi-card {
+    padding: 10px;
+    min-height: 60px;
+  }
+  
+  .kpi-card__icon {
+    width: 24px;
+    height: 24px;
+    font-size: 11px;
+  }
+  
+  .kpi-number {
+    font-size: 14px;
+  }
+}
+
+.chart-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: flex-start;
+  margin-bottom: 24px;
+}
+
+.chart-title {
+  margin: 0;
+  font-size: 14px;
+  font-weight: 600;
+  color: #1F2329;
+  margin-bottom: 4px;
+}
+
+.chart-controls {
+  display: flex;
+  align-items: center;
+  gap: 20px;
+}
+
+.dimension-controls {
+  display: flex;
+  align-items: center;
+  gap: 20px;
+}
+
+.control-group {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+}
+
+.control-label {
+  font-size: 12px;
+  color: #606266;
+  font-weight: 500;
+}
+
+.metric-segment {
+  display: flex;
+  background: #F7F8FA;
+  border-radius: 8px;
+  padding: 2px;
+  border: 1px solid #EEF1F5;
+}
+
+.segment-btn {
+  padding: 6px 12px;
+  border: none;
+  background: transparent;
+  color: #606266;
+  font-size: 12px;
+  font-weight: 500;
+  border-radius: 6px;
+  cursor: pointer;
+  transition: all 0.2s ease;
+  white-space: nowrap;
+}
+
+.segment-btn:hover {
+  color: #12B67F;
+}
+
+.segment-btn.active {
+  background: #fff;
+  color: #12B67F;
+  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
+}
+
+.chart-tools {
+  display: flex;
+  gap: 8px;
+}
+
+.chart-container {
+  width: 100%;
+  padding: 0;
+  display: flex;
+  justify-content: center;
+  align-items: center;
+}
+
+/* 确保图表在卡片中居中 */
+.chart-section .chart-container > div {
+  width: 100% !important;
+  height: 100% !important;
+}
+
+.chart-empty {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+  height: 300px;
+  color: #909399;
+}
+
+.chart-empty-icon {
+  font-size: 48px;
+  margin-bottom: 12px;
+  opacity: 0.5;
+}
+
+.chart-empty-text {
+  font-size: 14px;
+  margin: 0;
+}
+
+/* 右侧侧栏 */
+.sidebar-sticky {
+  position: sticky;
+  top: 88px;
+  width: 100%;
+}
+
+/* 主内容区域对齐 */
+.main-content > .el-col {
+  padding-left: 8px;
+  padding-right: 8px;
+}
+
+.main-content > .el-col:first-child {
+  padding-left: 0 !important;
+}
+
+.main-content > .el-col:last-child {
+  padding-right: 0 !important;
+}
+
+/* 统一对齐样式 - 确保所有区域都完全对齐 */
+.main-content {
+  margin-left: 0 !important;
+  margin-right: 0 !important;
+  width: 100%;
+}
+
+/* 确保待办卡片完全撑满容器 */
+.todo-section {
+  width: 100%;
+  margin-bottom: 16px;
+}
+
+.todo-section:last-child {
+  margin-bottom: 0;
+}
+
+@media (max-width: 992px) {
+  .sidebar-sticky {
+    position: static;
+    margin-top: 16px;
+  }
+}
+
+.todo-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  cursor: pointer;
+  user-select: none;
+  margin-bottom: 16px;
+}
+
+.todo-title-wrapper {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+}
+
+.todo-header h4 {
+  margin: 0;
+  font-size: 14px;
+  font-weight: 600;
+  color: #1F2329;
+}
+
+.todo-badge {
+  line-height: 1;
+}
+
+.toggle-icon {
+  font-size: 12px;
+  color: #909399;
+  transition: transform 0.3s ease;
+}
+
+.toggle-icon.collapsed {
+  transform: rotate(-90deg);
+}
+
+.todo-list {
+  max-height: 300px;
+  overflow-y: auto;
+}
+
+.timeline-container {
+  position: relative;
+}
+
+.todo-item {
+  padding: 12px 0;
+  cursor: pointer;
+  transition: all 0.2s ease;
+  border-bottom: 1px dashed #EFF2F7;
+  display: flex;
+  align-items: flex-start;
+  gap: 12px;
+}
+
+.todo-item:last-child {
+  border-bottom: none;
+}
+
+.todo-item:hover {
+  background-color: #F7F8FA;
+  margin: 0 -12px;
+  padding: 12px;
+  border-radius: 6px;
+  border-bottom: 1px dashed #EFF2F7;
+}
+
+.todo-item:last-child:hover {
+  border-bottom: none;
+}
+
+.todo-item.overdue {
+  background: #FFF2F0;
+  border-radius: 6px;
+  padding: 8px 10px;
+  margin: 4px 0;
+  border-bottom: none;
+}
+
+.todo-item.overdue:hover {
+  background: #FFE8E6;
+  margin: 4px -12px;
+  padding: 8px 22px;
+}
+
+.todo-content {
+  flex: 1;
+  min-width: 0;
+}
+
+.task-name {
+  font-size: 14px;
+  font-weight: 600;
+  color: #1F2329;
+  margin-bottom: 4px;
+  line-height: 1.4;
+}
+
+.task-meta {
+  font-size: 12px;
+  color: #606266;
+  margin-bottom: 4px;
+}
+
+.task-time {
+  font-size: 11px;
+  color: #909399;
+  font-weight: 500;
+}
+
+.task-time.danger {
+  color: #F56C6C;
+  font-weight: 600;
+}
+
+.todo-action {
+  flex-shrink: 0;
+  opacity: 0;
+  transition: opacity 0.2s ease;
+}
+
+.todo-item:hover .todo-action {
+  opacity: 1;
+}
+
+.action-btn {
+  padding: 4px 8px;
+  font-size: 11px;
+  border-radius: 4px;
+  color: #12B67F;
+}
+
+.action-btn:hover {
+  background: rgba(18, 182, 127, 0.1);
+}
+
+.drawer-content {
+  padding: 20px;
+}
+
+.drawer-content .el-table {
+  margin-bottom: 20px;
+}
+
+.drawer-content .el-table th {
+  background-color: #f8f9fa;
+  font-weight: 600;
+}
+
+.drawer-content .el-table td {
+  padding: 12px 0;
+}
+
+.drawer-actions {
+  text-align: center;
+}
+</style>