miaofuhao 1 месяц назад
Родитель
Сommit
4b06615d10

+ 50 - 0
apps/web-ele/src/api/archive/operationsManagement/model.ts

@@ -0,0 +1,50 @@
1
+export interface TaskCheckModel {
2
+  // 主键id
3
+  id?: number;
4
+  // 任务id
5
+  taskId?: number;
6
+  // 任务名称
7
+  taskName?: string;
8
+  // 任务类型
9
+  type?: string;
10
+  // 油站ID
11
+  stationId?: number;
12
+  // 片区id
13
+  areaId?: number;
14
+  // 执行人ID
15
+  executorId?: number;
16
+  // 执行人姓名
17
+  executorName?: string;
18
+  // 岗位
19
+  position?: number;
20
+  // 截止时间
21
+  deadline?: string;
22
+  // 处理时间
23
+  processTime?: string;
24
+  // 完成状态: 0-按时完成, 1-逾期完成
25
+  completionStatus?: number;
26
+  // 总得分
27
+  score?: number;
28
+  // 异常数
29
+  abnormalNumber?: number;
30
+  // 无拍照数
31
+  noPhotoNumber?: number;
32
+  // 是否评论0否1是
33
+  isComment?: number;
34
+  // 评论id
35
+  commentId?: number;
36
+  // 标签
37
+  tag?: string;
38
+  // 创建类型0系统1新增
39
+  createType?: number;
40
+  // 逻辑删除标识
41
+  delFlag?: number;
42
+  // 创建人
43
+  createBy?: string;
44
+  // 创建时间
45
+  createTime?: string;
46
+  // 更新人
47
+  updateBy?: string;
48
+  // 更新时间
49
+  updateTime?: string;
50
+}

+ 22 - 0
apps/web-ele/src/api/archive/operationsManagement/operationsManagement.ts

@@ -0,0 +1,22 @@
1
+import type { TaskCheckModel } from './model';
2
+
3
+import type { BaseResult } from '#/api/base-result';
4
+
5
+import { requestClient } from '#/api/request';
6
+
7
+// API路径常量枚举
8
+enum Api {
9
+  // 任务检查记录列表
10
+  taskCheckList = '/taskCheck/check/list',
11
+}
12
+
13
+/**
14
+ * 查询任务检查记录列表
15
+ * @param params 查询参数
16
+ * @returns 任务检查记录列表
17
+ */
18
+export function getTaskCheckList(params: any) {
19
+  return requestClient.get<BaseResult<TaskCheckModel[]>>(Api.taskCheckList, {
20
+    params,
21
+  });
22
+}

+ 24 - 147
apps/web-ele/src/views/Archive/visitorFeedback/visitorCheckTask/index.vue

@@ -11,11 +11,21 @@ import { useVbenVxeGrid } from '#/adapter/vxe-table';
11 11
 
12 12
 import { queryFormSchema, tableColumns } from './visitorCheckTask-data';
13 13
 
14
+import { getTaskCheckList } from '#/api/archive/operationsManagement/operationsManagement';
15
+
14 16
 const router = useRouter();
15 17
 const route = useRoute();
16 18
 
17
-// 打印当前路由和路由参数
18
-console.log('当前路由路径,参数:', route.path, route.query);
19
+// 表单字段到 API 字段的映射
20
+function mapFormToApi(formValues: any, page: any) {
21
+  const apiParams = {
22
+    pageNum: page.currentPage,
23
+    pageSize: page.pageSize,
24
+    type: route.query.type || '',
25
+    ...formValues,
26
+  };
27
+  return apiParams;
28
+}
19 29
 
20 30
 const formOptions: VbenFormProps = {
21 31
   commonConfig: {
@@ -28,83 +38,6 @@ const formOptions: VbenFormProps = {
28 38
   wrapperClass: 'grid-cols-1 md:grid-cols-2 lg:grid-cols-4',
29 39
 };
30 40
 
31
-// 模拟数据
32
-const mockData = {
33
-  rows: [
34
-    {
35
-      id: 1,
36
-      taskName: '访客检查任务1',
37
-      station: '油站1',
38
-      exceptionCount: 5,
39
-      noPhotoCount: 2,
40
-      executor: '张三',
41
-      position: 'manager',
42
-      comment: 'commented',
43
-      deadline: '2024-05-01',
44
-      handleTime: '2024-04-30',
45
-      status: 'onTime',
46
-      createType: 'system',
47
-    },
48
-    {
49
-      id: 2,
50
-      taskName: '访客检查任务2',
51
-      station: '油站2',
52
-      exceptionCount: 3,
53
-      noPhotoCount: 1,
54
-      executor: '李四',
55
-      position: 'clerk',
56
-      comment: 'uncommented',
57
-      deadline: '2024-05-02',
58
-      handleTime: '2024-05-03',
59
-      status: 'overdue',
60
-      createType: 'manual',
61
-    },
62
-    {
63
-      id: 3,
64
-      taskName: '访客检查任务3',
65
-      station: '油站3',
66
-      exceptionCount: 0,
67
-      noPhotoCount: 0,
68
-      executor: '王五',
69
-      position: 'safety',
70
-      comment: 'commented',
71
-      deadline: '2024-05-03',
72
-      handleTime: '2024-05-03',
73
-      status: 'onTime',
74
-      createType: 'system',
75
-    },
76
-    {
77
-      id: 4,
78
-      taskName: '访客检查任务4',
79
-      station: '油站1',
80
-      exceptionCount: 2,
81
-      noPhotoCount: 0,
82
-      executor: '赵六',
83
-      position: 'manager',
84
-      comment: 'uncommented',
85
-      deadline: '2024-05-04',
86
-      handleTime: '2024-05-05',
87
-      status: 'overdue',
88
-      createType: 'manual',
89
-    },
90
-    {
91
-      id: 5,
92
-      taskName: '访客检查任务5',
93
-      station: '油站2',
94
-      exceptionCount: 7,
95
-      noPhotoCount: 3,
96
-      executor: '孙七',
97
-      position: 'clerk',
98
-      comment: 'commented',
99
-      deadline: '2024-05-05',
100
-      handleTime: '2024-05-05',
101
-      status: 'onTime',
102
-      createType: 'system',
103
-    },
104
-  ],
105
-  total: 5,
106
-};
107
-
108 41
 const gridOptions: VxeGridProps = {
109 42
   checkboxConfig: {
110 43
     highlight: true,
@@ -117,68 +50,18 @@ const gridOptions: VxeGridProps = {
117 50
   proxyConfig: {
118 51
     ajax: {
119 52
       query: async ({ page }, formValues = {}) => {
120
-        // 模拟API请求 - 这里需要根据页面类型调用不同的接口
121
-        // 注意:该页面(visitorCheckTask)应调用与casualInspection不同的接口
122
-        // visitorCheckTask 页面接口示例:/api/visitorFeedback/visitorCheckTask/list
123
-        // casualInspection 页面接口示例:/api/visitorFeedback/casualInspection/list
124
-        console.log('查询参数:', formValues, page);
125
-        // 模拟过滤
126
-        let filteredRows = [...mockData.rows];
127
-
128
-        // 根据搜索条件过滤
129
-        if (formValues.area) {
130
-          filteredRows = filteredRows.filter(
131
-            (row) => row.area === formValues.area,
132
-          );
133
-        }
134
-        if (formValues.station) {
135
-          filteredRows = filteredRows.filter(
136
-            (row) => row.station === formValues.station,
137
-          );
138
-        }
139
-        if (formValues.status) {
140
-          filteredRows = filteredRows.filter(
141
-            (row) => row.status === formValues.status,
142
-          );
143
-        }
144
-        if (formValues.handleTime) {
145
-          filteredRows = filteredRows.filter(
146
-            (row) => row.handleTime === formValues.handleTime,
147
-          );
148
-        }
149
-        if (formValues.position) {
150
-          filteredRows = filteredRows.filter(
151
-            (row) => row.position === formValues.position,
152
-          );
153
-        }
154
-        if (formValues.comment) {
155
-          filteredRows = filteredRows.filter(
156
-            (row) => row.comment === formValues.comment,
157
-          );
158
-        }
159
-        if (formValues.createType) {
160
-          filteredRows = filteredRows.filter(
161
-            (row) => row.createType === formValues.createType,
162
-          );
163
-        }
164
-        if (formValues.executor) {
165
-          filteredRows = filteredRows.filter((row) =>
166
-            row.executor.includes(formValues.executor),
167
-          );
168
-        }
169
-        if (formValues.taskName) {
170
-          filteredRows = filteredRows.filter((row) =>
171
-            row.taskName.includes(formValues.taskName),
172
-          );
173
-        }
174
-
175
-        // 模拟分页
176
-        const start = (page.currentPage - 1) * page.pageSize;
177
-        const end = start + page.pageSize;
178
-        const paginatedRows = filteredRows.slice(start, end);
53
+        // 映射表单字段到 API 字段
54
+        const apiParams = mapFormToApi(formValues, page);
55
+        
56
+        // 调用API
57
+        const response = await getTaskCheckList(apiParams);
58
+        
59
+        // 映射 API 响应数据到页面显示字段
60
+        const items = response.rows;
61
+        
179 62
         return {
180
-          items: paginatedRows,
181
-          total: filteredRows.length,
63
+          items,
64
+          total: response.total,
182 65
         };
183 66
       },
184 67
     },
@@ -194,7 +77,7 @@ const gridOptions: VxeGridProps = {
194 77
   id: 'system-checklis-visitorCheckTask-index',
195 78
 };
196 79
 
197
-const [BasicTable, basicTableApi] = useVbenVxeGrid({
80
+const [BasicTable] = useVbenVxeGrid({
198 81
   formOptions,
199 82
   gridOptions,
200 83
 });
@@ -203,12 +86,6 @@ const [BasicTable, basicTableApi] = useVbenVxeGrid({
203 86
 function handleView(row: any) {
204 87
   router.push(`/schedule/detail/${row.id}`);
205 88
 }
206
-
207
-// 打印任务
208
-function handlePrint(row: any) {
209
-  // 打印功能暂未实现
210
-  console.log('打印任务:', row.id);
211
-}
212 89
 </script>
213 90
 
214 91
 <template>

+ 99 - 44
apps/web-ele/src/views/Archive/visitorFeedback/visitorCheckTask/visitorCheckTask-data.tsx

@@ -1,76 +1,131 @@
1 1
 import type { FormSchemaGetter } from '#/adapter/form';
2 2
 import type { VxeGridProps } from '#/adapter/vxe-table';
3 3
 
4
+import { selectAllSysAreaList,selectAllSysStationAreaList } from '#/api/system/infoEntry/stationInfo/stationInfo';
5
+
6
+
7
+// 假设岗位列表接口
8
+import { requestClient } from '#/api/request';
9
+
10
+// 获取岗位列表
11
+const getPostList = async () => {
12
+  const resp = await requestClient.get('/system/post/list');
13
+  return resp || [];
14
+};
15
+
4 16
 export const queryFormSchema: FormSchemaGetter = () => [
5 17
   {
6
-    component: 'Select',
7
-    fieldName: 'area',
18
+    component: 'ApiSelect',
19
+    fieldName: 'areaId',
8 20
     label: '片区',
9 21
     componentProps: {
10 22
       placeholder: '请选择片区',
11
-      options: [
12
-        { label: '片区1', value: 'area1' },
13
-        { label: '片区2', value: 'area2' },
14
-        { label: '片区3', value: 'area3' },
15
-      ],
23
+      clearable: true,
24
+      api: async () => {
25
+        const resp = await selectAllSysAreaList();
26
+        const data = resp || [];
27
+        return Array.isArray(data)
28
+          ? data.map((item: any) => ({
29
+              label: item.areaName,
30
+              value: item.id,
31
+            }))
32
+          : [];
33
+      },
34
+      labelField: 'label',
35
+      valueField: 'value',
16 36
     },
17 37
   },
18 38
   {
19
-    component: 'Select',
20
-    fieldName: 'station',
39
+    component: 'ApiSelect',
40
+    fieldName: 'stationId',
21 41
     label: '油站',
22 42
     componentProps: {
23 43
       placeholder: '请选择油站',
24
-      options: [
25
-        { label: '油站1', value: 'station1' },
26
-        { label: '油站2', value: 'station2' },
27
-        { label: '油站3', value: 'station3' },
28
-      ],
44
+      clearable: true,
45
+      api: async () => {
46
+        const resp = await selectAllSysStationAreaList();
47
+        const data = resp || [];
48
+        return Array.isArray(data)
49
+          ? data.map((item: any) => ({
50
+              label: item.stationName,
51
+              value: item.id.toString(),
52
+            }))
53
+          : [];
54
+      },
55
+      labelField: 'label',
56
+      valueField: 'value',
29 57
     },
30 58
   },
31 59
   {
32 60
     component: 'Select',
33
-    fieldName: 'status',
61
+    fieldName: 'completionStatus',
34 62
     label: '完成状态',
35 63
     componentProps: {
36 64
       placeholder: '请选择完成状态',
37 65
       options: [
38
-        { label: '按时完成', value: 'onTime' },
39
-        { label: '逾期完成', value: 'overdue' },
66
+        { label: '按时完成', value: 0 },
67
+        { label: '逾期完成', value: 1 },
40 68
       ],
41 69
     },
42 70
   },
43 71
   {
44 72
     component: 'DatePicker',
45
-    fieldName: 'handleTime',
73
+    fieldName: 'processStartTime',
74
+    label: '处理时间',
75
+     componentProps: {
76
+      placeholder: '请选择处理开始日期',
77
+      type: 'date',
78
+      format: 'YYYY-MM-DD',
79
+      valueFormat: 'YYYY-MM-DD',
80
+      style:{
81
+        width:'100%'
82
+      }
83
+    },
84
+  },
85
+  {
86
+    component: 'DatePicker',
87
+    fieldName: 'processEndTime',
46 88
     label: '处理时间',
47 89
     componentProps: {
48
-      placeholder: '请选择处理时间',
90
+      placeholder: '请选择处理结束日期',
49 91
       type: 'date',
92
+      format: 'YYYY-MM-DD',
93
+      valueFormat: 'YYYY-MM-DD',
94
+      style:{
95
+        width:'100%'
96
+      }
50 97
     },
51 98
   },
99
+
52 100
   {
53
-    component: 'Select',
101
+    component: 'ApiSelect',
54 102
     fieldName: 'position',
55 103
     label: '岗位',
56 104
     componentProps: {
57 105
       placeholder: '请选择岗位',
58
-      options: [
59
-        { label: '站长', value: 'manager' },
60
-        { label: '营业员', value: 'clerk' },
61
-        { label: '安全员', value: 'safety' },
62
-      ],
106
+      clearable: true,
107
+      api: async () => {
108
+        const data = await getPostList();
109
+        return Array.isArray(data.rows)
110
+          ? data.rows.map((item: any) => ({
111
+              label: item.postName || item.name,
112
+              value: item.postId,
113
+            }))
114
+          : [];
115
+      },
116
+      labelField: 'label',
117
+      valueField: 'value',
63 118
     },
64 119
   },
65 120
   {
66 121
     component: 'Select',
67
-    fieldName: 'comment',
122
+    fieldName: 'isComment',
68 123
     label: '评论',
69 124
     componentProps: {
70 125
       placeholder: '请选择评论状态',
71 126
       options: [
72
-        { label: '未评', value: 'uncommented' },
73
-        { label: '已评', value: 'commented' },
127
+        { label: '未评', value: 0 },
128
+        { label: '已评', value: 1 },
74 129
       ],
75 130
     },
76 131
   },
@@ -81,14 +136,14 @@ export const queryFormSchema: FormSchemaGetter = () => [
81 136
     componentProps: {
82 137
       placeholder: '请选择创建方式',
83 138
       options: [
84
-        { label: '系统', value: 'system' },
85
-        { label: '新增', value: 'manual' },
139
+        { label: '系统', value: 0 },
140
+        { label: '新增', value: 1 },
86 141
       ],
87 142
     },
88 143
   },
89 144
   {
90 145
     component: 'Input',
91
-    fieldName: 'executor',
146
+    fieldName: 'executorName',
92 147
     label: '执行人',
93 148
     componentProps: {
94 149
       placeholder: '请输入执行人',
@@ -115,24 +170,24 @@ export const tableColumns: VxeGridProps['columns'] = [
115 170
     minWidth: 150,
116 171
   },
117 172
   {
118
-    field: 'station',
173
+    field: 'stationId',
119 174
     title: '油站',
120 175
     minWidth: 120,
121 176
   },
122 177
   {
123
-    field: 'exceptionCount',
178
+    field: 'abnormalNumber',
124 179
     title: '异常总数',
125 180
     minWidth: 100,
126 181
     align: 'center',
127 182
   },
128 183
   {
129
-    field: 'noPhotoCount',
184
+    field: 'noPhotoNumber',
130 185
     title: '无拍照数',
131 186
     minWidth: 100,
132 187
     align: 'center',
133 188
   },
134 189
   {
135
-    field: 'executor',
190
+    field: 'executorName',
136 191
     title: '执行人',
137 192
     minWidth: 120,
138 193
   },
@@ -142,12 +197,12 @@ export const tableColumns: VxeGridProps['columns'] = [
142 197
     minWidth: 120,
143 198
   },
144 199
   {
145
-    field: 'comment',
200
+    field: 'isComment',
146 201
     title: '评论',
147 202
     minWidth: 100,
148 203
     align: 'center',
149
-    formatter: (cellValue: string) => {
150
-      return cellValue === 'commented' ? '已评' : '未评';
204
+    formatter: (cellValue: number) => {
205
+      return cellValue === 1 ? '已评' : '未评';
151 206
     },
152 207
   },
153 208
   {
@@ -156,17 +211,17 @@ export const tableColumns: VxeGridProps['columns'] = [
156 211
     minWidth: 180,
157 212
   },
158 213
   {
159
-    field: 'handleTime',
214
+    field: 'processTime',
160 215
     title: '处理时间',
161 216
     minWidth: 180,
162 217
   },
163 218
   {
164
-    field: 'status',
219
+    field: 'completionStatus',
165 220
     title: '完成状态',
166 221
     minWidth: 120,
167 222
     align: 'center',
168
-    formatter: (cellValue: string) => {
169
-      return cellValue === 'onTime' ? '按时完成' : '逾期完成';
223
+    formatter: (cellValue: number) => {
224
+      return cellValue === 0 ? '按时完成' : '逾期完成';
170 225
     },
171 226
   },
172 227
   {
@@ -174,8 +229,8 @@ export const tableColumns: VxeGridProps['columns'] = [
174 229
     title: '创建',
175 230
     minWidth: 100,
176 231
     align: 'center',
177
-    formatter: (cellValue: string) => {
178
-      return cellValue === 'system' ? '系统' : '新增';
232
+    formatter: (cellValue: number) => {
233
+      return cellValue === 0 ? '系统' : '新增';
179 234
     },
180 235
   },
181 236
   {

+ 42 - 8
apps/web-ele/src/views/oilstation/connect/connect-data.tsx

@@ -68,24 +68,58 @@ export const queryFormSchema: FormSchemaGetter = () => [
68 68
   // },
69 69
   {
70 70
     component: 'DatePicker',
71
-    fieldName: 'handoverStartTime',
72
-    label: '交接开始时间',
71
+    fieldName: 'startHandoverStartTime',
72
+    label: '交接开始',
73
+     componentProps: {
74
+      placeholder: '请选择交接开始日期',
75
+      type: 'date',
76
+      format: 'YYYY-MM-DD',
77
+      valueFormat: 'YYYY-MM-DD',
78
+      style:{
79
+        width:'100%'
80
+      }
81
+    },
82
+  },
83
+  {
84
+    component: 'DatePicker',
85
+    fieldName: 'startHandoverEndTime',
86
+    label: '交接开始',
73 87
     componentProps: {
74
-      placeholder: '请选择交接开始时间',
75
-      type: 'daterange',
88
+      placeholder: '请选择交接开始结束日期',
89
+      type: 'date',
90
+      format: 'YYYY-MM-DD',
91
+      valueFormat: 'YYYY-MM-DD',
92
+      style:{
93
+        width:'100%'
94
+      }
95
+    },
96
+  },
97
+  {
98
+    component: 'DatePicker',
99
+    fieldName: 'handoverStartTime',
100
+    label: '交接完成',
101
+     componentProps: {
102
+      placeholder: '请选择交接完成日期',
103
+      type: 'date',
76 104
       format: 'YYYY-MM-DD',
77 105
       valueFormat: 'YYYY-MM-DD',
106
+      style:{
107
+        width:'100%'
108
+      }
78 109
     },
79 110
   },
80 111
   {
81 112
     component: 'DatePicker',
82 113
     fieldName: 'handoverEndTime',
83
-    label: '交接完成时间',
84
-    componentProps: {
85
-      placeholder: '请选择交接完成时间',
86
-      type: 'daterange',
114
+    label: '交接完成',
115
+     componentProps: {
116
+      placeholder: '请选择交接完成日期',
117
+      type: 'date',
87 118
       format: 'YYYY-MM-DD',
88 119
       valueFormat: 'YYYY-MM-DD',
120
+      style:{
121
+        width:'100%'
122
+      }
89 123
     },
90 124
   },
91 125
 ];

+ 1 - 1
apps/web-ele/src/views/oilstation/connect/index.vue

@@ -116,7 +116,7 @@ function handleHandover(row: any) {
116 116
         <!-- 操作列 -->
117 117
         <ElSpace>
118 118
           <ElButton size="small" type="primary" plain @click="handleHandover(row)">
119
-            交接
119
+            {{ row.handoverEndTime ? '查看' : '交接' }}
120 120
           </ElButton>
121 121
           <ElPopconfirm title="确认删除" @confirm="confirmEvent(row)">
122 122
             <template #reference>

+ 299 - 148
apps/web-ele/src/views/oilstation/connect/process/index.vue

@@ -1,13 +1,16 @@
1 1
 <script setup lang="ts">
2 2
 import { ref, onMounted, computed } from 'vue';
3
-import { useRoute, useRouter } from 'vue-router';
4
-import { ElCard, ElButton, ElDescriptions, ElDescriptionsItem, ElDivider, ElUpload, ElImage, ElInput, ElPageHeader } from 'element-plus';
3
+import { useRoute } from 'vue-router';
4
+import { ElButton, ElDescriptions, ElDescriptionsItem, ElUpload, ElInput, ElIcon, ElImageViewer } from 'element-plus';
5 5
 import { ElMessage } from 'element-plus';
6
+import { Plus } from '@element-plus/icons-vue';
6 7
 
8
+import { downloadByUrl } from '#/utils/file/download';
9
+
10
+import { uploadApi } from '#/api/core/upload';
7 11
 import { requestClient } from '#/api/request';
8 12
 
9 13
 const route = useRoute();
10
-const router = useRouter();
11 14
 const id = computed(() => route.params.id as string);
12 15
 
13 16
 // 交接详情数据
@@ -16,6 +19,11 @@ const handoverDetail = ref<any>(null);
16 19
 const loading = ref(true);
17 20
 // 保存状态
18 21
 const saving = ref(false);
22
+// 确认交接状态
23
+const confirming = ref(false);
24
+// 图片预览相关
25
+const previewVisible = ref(false);
26
+const previewUrl = ref('');
19 27
 
20 28
 // 获取交接详情
21 29
 const getHandoverDetail = async () => {
@@ -35,7 +43,7 @@ const getHandoverDetail = async () => {
35 43
 const saveItem = async (item: any) => {
36 44
   try {
37 45
     saving.value = true;
38
-    const response = await requestClient.put('/gasHandoverItems/items', {
46
+    await requestClient.put('/gasHandoverItems/items', {
39 47
       id: item.id,
40 48
       attachments: item.attachments,
41 49
       photos: item.photos,
@@ -50,9 +58,135 @@ const saveItem = async (item: any) => {
50 58
   }
51 59
 };
52 60
 
53
-// 返回列表页
54
-const goBack = () => {
55
-  router.push('/oilstation/connect');
61
+// 自定义上传方法
62
+const handleUpload = async (options: any, item: any, fieldName: string) => {
63
+  try {
64
+    const res = await uploadApi(options.file);
65
+    const fileName = res.originalFilename || res.newFileName || res.fileName;
66
+    const responseData = { 
67
+      url: res.url,
68
+      name: fileName // 使用原始文件名作为显示名称
69
+    };
70
+    
71
+    // 根据字段名设置对应的fileName
72
+    if (fieldName === 'photos') {
73
+      item.photos = item.photos ? `${item.photos},${res.fileName}` : res.fileName;
74
+      item.photosUrl = item.photosUrl ? `${item.photosUrl},${res.url}` : res.url;
75
+    } else if (fieldName === 'attachments') {
76
+      item.attachments = item.attachments ? `${item.attachments},${res.fileName}` : res.fileName;
77
+      item.attachmentsUrl = item.attachmentsUrl ? `${item.attachmentsUrl},${res.url}` : res.url;
78
+    }
79
+    
80
+    options.onSuccess(responseData);
81
+  } catch (error) {
82
+    console.error('上传失败:', error);
83
+    options.onError(error);
84
+  }
85
+};
86
+
87
+// 删除文件处理
88
+const handleRemove = (file: any, fileList: any[], item: any, fieldName: string) => {
89
+  // 从字段值中移除对应的fileName
90
+  if (fieldName === 'photos' && item.photos) {
91
+    item.photos = item.photos.split(',').filter(fileName => fileName !== file.name).join(',');
92
+    item.photosUrl = item.photosUrl.split(',').filter(url => url !== file.url).join(',');
93
+  } else if (fieldName === 'attachments' && item.attachments) {
94
+    item.attachments = item.attachments.split(',').filter(fileName => fileName !== file.name).join(',');
95
+    item.attachmentsUrl = item.attachmentsUrl.split(',').filter(url => url !== file.url).join(',');
96
+  }
97
+  return true;
98
+};
99
+
100
+// 预览文件处理
101
+const handlePreview = (file: any) => {
102
+  // 判断是否是图片文件
103
+  const isImage = /\.(jpg|jpeg|png|gif|bmp)$/i.test(file.name);
104
+  
105
+  if (isImage) {
106
+    // 图片预览
107
+    previewUrl.value = file.url;
108
+    previewVisible.value = true;
109
+  } else {
110
+    // 附件下载
111
+    downloadByUrl({
112
+      url: file.url,
113
+      fileName: file.name
114
+    });
115
+  }
116
+};
117
+
118
+// 生成文件列表
119
+const generateFileList = (item: any, fieldName: string) => {
120
+  const newData = [];
121
+  let urls, fileNames;
122
+  
123
+  // 根据字段名获取对应的URL和文件名
124
+  if (fieldName === 'photos') {
125
+    urls = item.photosUrl;
126
+    fileNames = item.photos;
127
+  } else if (fieldName === 'attachments') {
128
+    urls = item.attachmentsUrl;
129
+    fileNames = item.attachments;
130
+  } else {
131
+    return newData;
132
+  }
133
+  
134
+  // 处理URL
135
+  if (urls) {
136
+    if (Array.isArray(urls)) {
137
+      urls.forEach((url: string, index: number) => {
138
+        // 从 fileNames 中获取对应的文件名
139
+        const fileNameArray = fileNames ? fileNames.split(',') : [];
140
+        let fileName = fileNameArray[index] || '';
141
+        
142
+        // 如果没有获取到文件名,从URL中提取
143
+        if (!fileName) {
144
+          fileName = url.split('/').pop()?.split('?')[0] || '';
145
+        }
146
+        
147
+        newData.push({
148
+          url: url.includes('http') ? url : `/common/upload/${url}`,
149
+          name: fileName
150
+        });
151
+      });
152
+    } else if (typeof urls === 'string') {
153
+      // 处理字符串形式的URL
154
+      const urlArray = urls.split(',');
155
+      const fileNameArray = fileNames ? fileNames.split(',') : [];
156
+      
157
+      urlArray.forEach((url: string, index: number) => {
158
+        let fileName = fileNameArray[index] || '';
159
+        
160
+        // 如果没有获取到文件名,从URL中提取
161
+        if (!fileName) {
162
+          fileName = url.split('/').pop()?.split('?')[0] || '';
163
+        }
164
+        
165
+        newData.push({
166
+          url: url.includes('http') ? url : `/common/upload/${url}`,
167
+          name: fileName
168
+        });
169
+      });
170
+    }
171
+  }
172
+  
173
+  return newData;
174
+};
175
+
176
+// 确认交接
177
+const handleConfirmHandover = async () => {
178
+  try {
179
+    confirming.value = true;
180
+    await requestClient.put(`/gasHandoverMain/main/process/${handoverDetail.value.id}`);
181
+    ElMessage.success('确认交接成功');
182
+    // 重新获取交接详情,更新状态
183
+    await getHandoverDetail();
184
+  } catch (error) {
185
+    ElMessage.error('确认交接失败');
186
+    console.error('确认交接失败:', error);
187
+  } finally {
188
+    confirming.value = false;
189
+  }
56 190
 };
57 191
 
58 192
 // 初始化
@@ -63,135 +197,179 @@ onMounted(() => {
63 197
 
64 198
 <template>
65 199
   <div class="handover-process-container">
66
-    <ElPageHeader @back="goBack" content="站长交接详情" />
200
+    <div class="flex items-center gap-4 jiayi_header">
201
+      <div style="width: 4px; height: 12px; background-color: #215acd"></div>
202
+      <span class="text-lg font-bold text-gray-800" style="font-size: 14px; font-weight: 600">
203
+        站长交接详情
204
+      </span>
205
+    </div>
67 206
     
68
-    <ElCard :loading="loading" class="mt-4">
69
-      <!-- 基本信息 -->
70
-      <ElDescriptions :column="2" border>
71
-        <ElDescriptionsItem label="片区">{{ handoverDetail?.areaId }}</ElDescriptionsItem>
72
-        <ElDescriptionsItem label="油站">{{ handoverDetail?.stationId }}</ElDescriptionsItem>
73
-        <ElDescriptionsItem label="移交人">{{ handoverDetail?.transferPersonName }}</ElDescriptionsItem>
74
-        <ElDescriptionsItem label="接收人">{{ handoverDetail?.receiver ? handoverDetail.receiver : '未指定' }}</ElDescriptionsItem>
75
-        <ElDescriptionsItem label="监交人">{{ handoverDetail?.supervisorName }}</ElDescriptionsItem>
76
-        <ElDescriptionsItem label="交接开始时间">{{ handoverDetail?.handoverStartTime }}</ElDescriptionsItem>
77
-        <ElDescriptionsItem label="交接完成时间" :span="2">
78
-          {{ handoverDetail?.handoverEndTime ? handoverDetail.handoverEndTime : '未完成' }}
79
-        </ElDescriptionsItem>
80
-      </ElDescriptions>
81
-
82
-      <ElDivider />
83
-
84
-      <!-- 交接项列表 -->
85
-      <div v-if="handoverDetail?.gasHandoverItemsList && handoverDetail.gasHandoverItemsList.length > 0">
86
-        <h3>交接事项</h3>
87
-        <div v-for="item in handoverDetail.gasHandoverItemsList" :key="item.id" class="item-card mt-4">
88
-          <ElCard :header="`${item.linkName} (${item.isMandatory === 1 ? '必做' : '可选'})`">
89
-            <ElDescriptions :column="1" border>
90
-              <ElDescriptionsItem label="交接事项">{{ item.handoverItem }}</ElDescriptionsItem>
91
-              <ElDescriptionsItem label="建议时长">{{ item.suggestDuration }} 天</ElDescriptionsItem>
92
-              <ElDescriptionsItem label="要求/指引">{{ item.requirementGuide }}</ElDescriptionsItem>
93
-              <ElDescriptionsItem label="负责人">{{ item.principal ? item.principal : '未指定' }}</ElDescriptionsItem>
94
-            </ElDescriptions>
95
-
96
-            <!-- 文本内容 -->
97
-            <div v-if="item.hasTextBox === 1" class="form-item mt-4">
98
-              <label>文本内容:</label>
99
-              <ElInput
100
-                type="textarea"
101
-                v-model="item.textContent"
102
-                placeholder="请输入文本内容"
103
-                :rows="4"
104
-                class="w-full"
105
-              />
106
-            </div>
207
+    <!-- 基本信息 -->
208
+    <ElDescriptions :column="4" class="task-info">
209
+      <ElDescriptionsItem label="片区">{{ handoverDetail?.areaIdName }}</ElDescriptionsItem>
210
+      <ElDescriptionsItem label="油站">{{ handoverDetail?.stationName }}</ElDescriptionsItem>
211
+      <ElDescriptionsItem label="移交人">{{ handoverDetail?.transferPersonName }}</ElDescriptionsItem>
212
+      <ElDescriptionsItem label="接收人">{{ handoverDetail?.receiver || '未指定' }}</ElDescriptionsItem>
213
+      <ElDescriptionsItem label="监交人">{{ handoverDetail?.supervisorName }}</ElDescriptionsItem>
214
+      <ElDescriptionsItem label="交接开始时间">{{ handoverDetail?.handoverStartTime }}</ElDescriptionsItem>
215
+      <ElDescriptionsItem label="交接完成时间">
216
+        {{ handoverDetail?.handoverEndTime || '未完成' }}
217
+      </ElDescriptionsItem>
218
+    </ElDescriptions>
107 219
 
108
-            <!-- 照片 -->
109
-            <div v-if="item.hasPhoto === 1" class="form-item mt-4">
110
-              <label>照片:</label>
111
-              <div class="photo-upload">
112
-                <ElUpload
113
-                  action=""
114
-                  :auto-upload="false"
115
-                  :show-file-list="false"
116
-                  @change="(file) => {
117
-                    // 这里需要实现照片上传逻辑
118
-                    console.log('照片上传:', file);
119
-                  }"
120
-                >
121
-                  <ElButton type="primary">上传照片</ElButton>
122
-                </ElUpload>
123
-                <div v-if="item.photos" class="photo-list mt-2">
124
-                  <ElImage
125
-                    v-for="(photo, index) in item.photos.split(',')"
126
-                    :key="index"
127
-                    :src="photo"
128
-                    :preview-src-list="[photo]"
129
-                    fit="cover"
130
-                    class="photo-item"
131
-                  />
132
-                </div>
133
-              </div>
134
-            </div>
220
+    <!-- 交接项列表 -->
221
+    <div v-if="handoverDetail?.gasHandoverItemsList && handoverDetail.gasHandoverItemsList.length > 0">
222
+      <div v-for="item in handoverDetail.gasHandoverItemsList" :key="item.id">
223
+        <div class="flex items-center gap-4">
224
+          <div style="width: 4px; height: 12px; background-color: #215acd"></div>
225
+          <span class="text-lg font-bold text-gray-800" style="font-size: 14px; font-weight: 600">
226
+            {{ item.linkName }}({{ item.isMandatory === 1 ? '必做' : '可选' }})
227
+          </span>
228
+        </div>
229
+        
230
+        <ElDescriptions :column="4" class="task-info">
231
+          <ElDescriptionsItem label="交接事项">{{ item.handoverItem }}</ElDescriptionsItem>
232
+          <ElDescriptionsItem label="建议时长">{{ item.suggestDuration }} 天</ElDescriptionsItem>
233
+          <ElDescriptionsItem label="负责人">{{ item.principal || '未指定' }}</ElDescriptionsItem>
234
+          <ElDescriptionsItem label="要求/指引" :span="4">{{ item.requirementGuide }}</ElDescriptionsItem>
235
+        </ElDescriptions>
236
+        
237
+        <div class="task-info">
238
+          <!-- 文本内容 -->
239
+          <div v-if="item.hasTextBox === 1" class="form-item">
240
+            <label>文本内容:</label>
241
+            <ElInput
242
+              type="textarea"
243
+              v-model="item.textContent"
244
+              placeholder="请输入文本内容"
245
+              :rows="4"
246
+              class="w-full"
247
+            />
248
+          </div>
135 249
 
136
-            <!-- 附件 -->
137
-            <div v-if="item.hasAttachment === 1" class="form-item mt-4">
138
-              <label>附件:</label>
139
-              <div class="attachment-upload">
140
-                <ElUpload
141
-                  action=""
142
-                  :auto-upload="false"
143
-                  :show-file-list="false"
144
-                  @change="(file) => {
145
-                    // 这里需要实现附件上传逻辑
146
-                    console.log('附件上传:', file);
147
-                  }"
148
-                >
149
-                  <ElButton type="primary">上传附件</ElButton>
150
-                </ElUpload>
151
-                <div v-if="item.attachments" class="attachment-list mt-2">
152
-                  <a
153
-                    v-for="(attachment, index) in item.attachments.split(',')"
154
-                    :key="index"
155
-                    :href="attachment"
156
-                    target="_blank"
157
-                    class="attachment-item"
158
-                  >
159
-                    {{ attachment.split('/').pop() }}
160
-                  </a>
250
+          <!-- 照片 -->
251
+          <div v-if="item.hasPhoto === 1" class="form-item">
252
+            <label>照片:</label>
253
+            <div class="photo-upload">
254
+              <ElUpload
255
+                action="#"
256
+                accept=".jpg,.jpeg,.png"
257
+                list-type="picture-card"
258
+                :auto-upload="true"
259
+                :http-request="(options) => handleUpload(options, item, 'photos')"
260
+                :on-remove="(file, fileList) => handleRemove(file, fileList, item, 'photos')"
261
+                :on-preview="handlePreview"
262
+                :file-list="generateFileList(item,'photos')"
263
+              >
264
+                <div v-if="handoverDetail && !handoverDetail.handoverEndTime">
265
+                  <ElIcon class="el-upload__icon">
266
+                    <Plus size="30" />
267
+                  </ElIcon>
161 268
                 </div>
162
-              </div>
269
+              </ElUpload>
163 270
             </div>
271
+          </div>
164 272
 
165
-            <!-- 保存按钮 -->
166
-            <div class="item-actions mt-4">
167
-              <ElButton type="primary" @click="saveItem(item)" :loading="saving">
168
-                保存
169
-              </ElButton>
273
+          <!-- 附件 -->
274
+          <div v-if="item.hasAttachment === 1" class="form-item">
275
+            <label>附件:</label>
276
+            <div class="attachment-upload">
277
+              <ElUpload
278
+                action="#"
279
+                list-type="text"
280
+                :auto-upload="true"
281
+                :http-request="(options) => handleUpload(options, item, 'attachments')"
282
+                :on-remove="(file, fileList) => handleRemove(file, fileList, item, 'attachments')"
283
+                :on-preview="handlePreview"
284
+                :file-list="generateFileList(item ,'attachments')"
285
+              >
286
+                <ElButton type="primary" v-if="handoverDetail && !handoverDetail.handoverEndTime">
287
+                  <ElIcon>
288
+                    <Plus />
289
+                  </ElIcon>
290
+                  上传附件
291
+                </ElButton>
292
+              </ElUpload>
170 293
             </div>
171
-          </ElCard>
294
+          </div>
295
+        </div>
296
+        
297
+        <!-- 保存按钮 -->
298
+        <div class="item-actions" style="margin-right: 20px;" v-if="handoverDetail && !handoverDetail.handoverEndTime">
299
+          <ElButton type="primary" @click="saveItem(item)" :loading="saving">
300
+            保存
301
+          </ElButton>
172 302
         </div>
173 303
       </div>
304
+    </div>
174 305
 
175
-      <div v-else-if="!loading" class="no-data mt-4">
176
-        暂无交接事项
177
-      </div>
178
-    </ElCard>
306
+    <div v-else-if="!loading" class="no-data">
307
+      暂无交接事项
308
+    </div>
309
+    
310
+    <!-- 确认交接按钮 -->
311
+    <div v-if="handoverDetail && !handoverDetail.handoverEndTime" class="confirm-handover-btn">
312
+      <ElButton type="primary" size="large" @click="handleConfirmHandover" :loading="confirming">
313
+        确认交接
314
+      </ElButton>
315
+    </div>
316
+    
317
+    <!-- 图片预览组件 -->
318
+    <ElImageViewer
319
+      v-if="previewVisible"
320
+      :url-list="[previewUrl]"
321
+      @close="() => previewVisible = false"
322
+    />
179 323
   </div>
180 324
 </template>
181 325
 
182 326
 <style scoped lang="scss">
183
-.handover-process-container {
184
-  padding: 20px;
327
+.jiayi_header {
328
+  padding-top: 18px !important;
329
+  padding-bottom: 4px !important;
330
+  padding-left: 0px !important;
331
+  border-bottom: none !important;
185 332
 }
186 333
 
187
-.item-card {
188
-  margin-bottom: 20px;
334
+// 去掉描述列表的边框
335
+:deep(.el-descriptions) {
336
+  border: none !important;
337
+}
338
+
339
+// 去掉描述列表项的边框
340
+:deep(.el-descriptions__cell) {
341
+  border: none !important;
342
+}
343
+
344
+.task-info {
345
+  padding: 0px 15px 15px 15px;
346
+  
347
+  :deep(.el-descriptions__label) {
348
+    font-size: 14px;
349
+    font-weight: 400;
350
+    color: var(--text-color-secondary);
351
+  }
352
+
353
+  :deep(.el-descriptions__content) {
354
+    font-size: 14px;
355
+    font-weight: 500;
356
+    color: var(--text-color-primary);
357
+  }
358
+  
359
+  :deep(.el-descriptions__table) {
360
+    background: transparent !important;
361
+  }
362
+}
363
+
364
+.handover-process-container {
365
+  background: #ffffff;
366
+  margin: 15px;
189 367
 }
190 368
 
191 369
 .form-item {
192 370
   margin-top: 20px;
193 371
   margin-bottom: 20px;
194
-
372
+  
195 373
   label {
196 374
     display: block;
197 375
     margin-bottom: 8px;
@@ -204,35 +382,9 @@ onMounted(() => {
204 382
   margin-top: 8px;
205 383
 }
206 384
 
207
-.photo-list {
208
-  display: flex;
209
-  flex-wrap: wrap;
210
-  gap: 10px;
211
-}
212
-
213
-.photo-item {
214
-  width: 120px;
215
-  height: 120px;
216
-  border: 1px solid #e8e8e8;
217
-  border-radius: 4px;
218
-}
219
-
220
-.attachment-list {
221
-  margin-top: 10px;
222
-}
223
-
224
-.attachment-item {
225
-  display: inline-block;
226
-  margin-right: 10px;
227
-  padding: 4px 8px;
228
-  background-color: #f5f5f5;
229
-  border-radius: 4px;
230
-  text-decoration: none;
231
-  color: #1890ff;
232
-}
233
-
234 385
 .item-actions {
235 386
   margin-top: 20px;
387
+  margin-bottom: 20px;
236 388
   text-align: right;
237 389
 }
238 390
 
@@ -242,11 +394,10 @@ onMounted(() => {
242 394
   color: #999;
243 395
 }
244 396
 
245
-.mt-2 {
246
-  margin-top: 8px;
247
-}
248
-
249
-.mt-4 {
250
-  margin-top: 16px;
397
+.confirm-handover-btn {
398
+  margin-top: 40px;
399
+  margin-bottom: 20px;
400
+  padding-bottom: 20px;
401
+  text-align: center;
251 402
 }
252 403
 </style>