Przeglądaj źródła

mod: 检查任务

闪电 3 tygodni temu
rodzic
commit
61d3d8228e

+ 1 - 0
package.json

120
     "dayjs": "1.11.10",
120
     "dayjs": "1.11.10",
121
     "echarts": "^6.0.0",
121
     "echarts": "^6.0.0",
122
     "js-cookie": "^3.0.5",
122
     "js-cookie": "^3.0.5",
123
+    "lodash-es": "^4.17.23",
123
     "pinia": "2.0.36",
124
     "pinia": "2.0.36",
124
     "pinia-plugin-persistedstate": "3.2.1",
125
     "pinia-plugin-persistedstate": "3.2.1",
125
     "uni-echarts": "^2.4.1",
126
     "uni-echarts": "^2.4.1",

+ 8 - 0
pnpm-lock.yaml

81
       js-cookie:
81
       js-cookie:
82
         specifier: ^3.0.5
82
         specifier: ^3.0.5
83
         version: 3.0.5
83
         version: 3.0.5
84
+      lodash-es:
85
+        specifier: ^4.17.23
86
+        version: 4.17.23
84
       pinia:
87
       pinia:
85
         specifier: 2.0.36
88
         specifier: 2.0.36
86
         version: 2.0.36(typescript@5.8.3)(vue@3.4.21(typescript@5.8.3))
89
         version: 2.0.36(typescript@5.8.3)(vue@3.4.21(typescript@5.8.3))
4657
     resolution: {integrity: sha512-gvVijfZvn7R+2qyPX8mAuKcFGDf6Nc61GdvGafQsHL0sBIxfKzA+usWn4GFC/bk+QdwPUD4kWFJLhElipq+0VA==}
4660
     resolution: {integrity: sha512-gvVijfZvn7R+2qyPX8mAuKcFGDf6Nc61GdvGafQsHL0sBIxfKzA+usWn4GFC/bk+QdwPUD4kWFJLhElipq+0VA==}
4658
     engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
4661
     engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
4659
 
4662
 
4663
+  lodash-es@4.17.23:
4664
+    resolution: {integrity: sha512-kVI48u3PZr38HdYz98UmfPnXl2DXrpdctLrFLCd3kOx1xUkOmpFPx7gCWWM5MPkL/fD8zb+Ph0QzjGFs4+hHWg==}
4665
+
4660
   lodash.camelcase@4.3.0:
4666
   lodash.camelcase@4.3.0:
4661
     resolution: {integrity: sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==}
4667
     resolution: {integrity: sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==}
4662
 
4668
 
12453
     dependencies:
12459
     dependencies:
12454
       p-locate: 6.0.0
12460
       p-locate: 6.0.0
12455
 
12461
 
12462
+  lodash-es@4.17.23: {}
12463
+
12456
   lodash.camelcase@4.3.0: {}
12464
   lodash.camelcase@4.3.0: {}
12457
 
12465
 
12458
   lodash.debounce@4.0.8: {}
12466
   lodash.debounce@4.0.8: {}

+ 26 - 0
src/api/schedule/check.ts

1
+import { http as requestClient } from '@/http/http'
2
+
3
+enum Api {
4
+  base = '/taskCheckItem/item',
5
+  childBase = '/taskCheckSubItem/item',
6
+  handle = '/taskCheck/check/handle',
7
+}
8
+// 查询任务检查项列表
9
+export function getChecktItemsByTaskId(params: any) {
10
+  return requestClient.get(`${Api.base}/list`, params)
11
+}
12
+
13
+// 查询任务检查子项列表
14
+export function getCheckSubItemsByTaskId(params: any) {
15
+  return requestClient.get(`${Api.childBase}/list`, params)
16
+}
17
+
18
+// 提交任务检查子项日志
19
+export function submitCheckLogs(checkId: number, params: any) {
20
+  return requestClient.put(`${Api.childBase}/handle/${checkId}`, params)
21
+}
22
+
23
+// 处理任务检查项
24
+export function handleCheckTask(checkId: number) {
25
+  return requestClient.put(`${Api.handle}/${checkId}`)
26
+}

+ 9 - 0
src/api/schedule/issue.ts

1
+import { http as requestClient } from '@/http/http'
2
+
3
+enum Api {
4
+  base = '/formOperationIssue/issue',
5
+}
6
+
7
+export function submitIssue(params: any) {
8
+  return requestClient.post(`${Api.base}`, params)
9
+}

+ 10 - 0
src/api/schedule/task.ts

2
 
2
 
3
 enum Api {
3
 enum Api {
4
   base = '/scheduleTasks/tasks',
4
   base = '/scheduleTasks/tasks',
5
+  confirmBase = '/scheduleTasks/tasks/confirm',
5
 }
6
 }
6
 
7
 
7
 export function transferTask(data: any) {
8
 export function transferTask(data: any) {
19
 export function getTaskDetail(id: number) {
20
 export function getTaskDetail(id: number) {
20
   return http.get(`${Api.base}/${id}`)
21
   return http.get(`${Api.base}/${id}`)
21
 }
22
 }
23
+
24
+/**
25
+ * 确认完成任务
26
+ * @param id 任务id
27
+ * @returns
28
+ */
29
+export function confirmDoneTask(id: number) {
30
+  return http.put(`${Api.confirmBase}/${id}`)
31
+}

+ 14 - 0
src/api/system/tags.ts

1
+import { http as requestClient } from '@/http/http'
2
+
3
+enum Api {
4
+  base = '/inspectionTag/tag',
5
+}
6
+
7
+/**
8
+ * 获取标签列表
9
+ * @param params
10
+ * @returns
11
+ */
12
+export function getTags(params: any) {
13
+  return requestClient.get(`${Api.base}/list`, params)
14
+}

+ 15 - 0
src/api/system/users.ts

1
+import { http as requestClient } from '@/http/http'
2
+
3
+enum Api {
4
+  all = '/system/user/allList',
5
+  station = '/system/user/stationList',
6
+}
7
+
8
+/**
9
+ * 获取所有用户列表
10
+ * @param params
11
+ * @returns
12
+ */
13
+export function getAllUsers(params: any) {
14
+  return requestClient.get(`${Api.station}`, params)
15
+}

+ 66 - 2
src/hooks/useUpload.ts

1
 import { ref } from 'vue'
1
 import { ref } from 'vue'
2
 import { getEnvBaseUrl } from '@/utils/index'
2
 import { getEnvBaseUrl } from '@/utils/index'
3
 
3
 
4
-const VITE_UPLOAD_BASEURL = `${getEnvBaseUrl()}/upload`
4
+const VITE_UPLOAD_BASEURL = `${getEnvBaseUrl()}/common/upload`
5
 
5
 
6
 type TfileType = 'image' | 'file'
6
 type TfileType = 'image' | 'file'
7
 type TImage = 'png' | 'jpg' | 'jpeg' | 'webp' | '*'
7
 type TImage = 'png' | 'jpg' | 'jpeg' | 'webp' | '*'
16
   error?: (err: any) => void
16
   error?: (err: any) => void
17
 }
17
 }
18
 
18
 
19
+// 定义上传方法类型,与 wotui 的 UploadMethod 兼容
20
+export interface UploadMethodOptions {
21
+  header?: Record<string, string>
22
+  name?: string
23
+  fileName?: string
24
+  fileType?: string
25
+  statusCode?: number
26
+  onSuccess: (response: any, file: any, formData: any) => void
27
+  onError: (error: any, file: any, formData: any) => void
28
+  onProgress: (progress: any, file: any) => void
29
+}
30
+
19
 export default function useUpload<T extends TfileType>(options: TOptions<T> = {} as TOptions<T>) {
31
 export default function useUpload<T extends TfileType>(options: TOptions<T> = {} as TOptions<T>) {
20
   const {
32
   const {
21
     formData = {},
33
     formData = {},
130
     }
142
     }
131
   }
143
   }
132
 
144
 
133
-  return { loading, error, data, run }
145
+  /**
146
+   * 自定义上传函数,用于 wotui 的 upload 组件
147
+   * @param file 上传的文件对象
148
+   * @param formData 表单数据
149
+   * @param options 上传选项
150
+   * @returns 上传任务对象
151
+   */
152
+  const customUpload = (file: any, formData: any, options: UploadMethodOptions) => {
153
+    const uploadTask = uni.uploadFile({
154
+      url: VITE_UPLOAD_BASEURL,
155
+      header: options.header,
156
+      name: options.name || 'file',
157
+      fileName: options.fileName || 'file',
158
+      fileType: options.fileType,
159
+      formData,
160
+      filePath: file.url,
161
+      success(res) {
162
+        if (res.statusCode === (options.statusCode || 200)) {
163
+          // 尝试解析响应数据
164
+          let parsedData: any = res.data
165
+          try {
166
+            const jsonData = JSON.parse(res.data)
167
+            parsedData = jsonData.data || jsonData
168
+            file.url = parsedData.url || ''
169
+            file.name = parsedData.fileName || parsedData.name || ''
170
+          }
171
+          catch (e) {
172
+            console.log('Response is not JSON, using raw data:', res.data)
173
+          }
174
+
175
+          // 设置上传成功
176
+          options.onSuccess(parsedData, file, formData)
177
+        }
178
+        else {
179
+          // 设置上传失败
180
+          options.onError({ ...res, errMsg: res.errMsg || '' }, file, formData)
181
+        }
182
+      },
183
+      fail(err) {
184
+        // 设置上传失败
185
+        options.onError(err, file, formData)
186
+      },
187
+    })
188
+
189
+    // 设置当前文件加载的百分比
190
+    uploadTask.onProgressUpdate((res) => {
191
+      options.onProgress(res, file)
192
+    })
193
+
194
+    return uploadTask
195
+  }
196
+
197
+  return { loading, error, data, run, customUpload }
134
 }
198
 }
135
 
199
 
136
 async function uploadFile({
200
 async function uploadFile({

src/pages/schedule/details/taskmanagement/index2.vue → src/pages/schedule/details/deal/index2.vue


+ 842 - 0
src/pages/schedule/details/deal/inspection.vue

1
+<script lang="ts" setup>
2
+import { computed, onMounted, ref } from 'vue'
3
+import { useRoute, useRouter } from 'vue-router'
4
+import { getCheckSubItemsByTaskId, getChecktItemsByTaskId, handleCheckTask, submitCheckLogs } from '@/api/schedule/check'
5
+import { getTaskDetail } from '@/api/schedule/task'
6
+import useUpload from '@/hooks/useUpload'
7
+import anomalyPop from '../popup/anomaly.vue'
8
+import centerpopup from '../popup/centerpopup.vue'
9
+
10
+const route = useRoute()
11
+const taskId = ref(Number(route.query.id))
12
+const checkId = ref(Number(route.query.checkId))
13
+
14
+const popShow = ref({
15
+  centerShow: false,
16
+  anomalyShow: false,
17
+})
18
+// const popShow = ref({})
19
+const tab = ref('前庭')
20
+
21
+const taskInfo: any = ref({})
22
+const checkSubItems: any = ref([])
23
+
24
+const currentCheckList: any = ref([])
25
+const activeArea: any = ref(0)
26
+
27
+// 定义任务类型
28
+interface Task {
29
+  id: string
30
+  type: string
31
+  description: string
32
+  icon?: string
33
+  instructions: string
34
+  examples: string[]
35
+  images: string[]
36
+  status: 'normal' | 'abnormal'
37
+  isimages: boolean
38
+  details?: {
39
+    name: string
40
+    status: 'normal' | 'abnormal'
41
+  }[]
42
+}
43
+
44
+// 任务数据
45
+// const tasks = ref<Task[]>([
46
+//   {
47
+//     id: '1',
48
+//     type: '前庭',
49
+//     description:
50
+//       '[AI] [卫生] 前庭海报整齐不歪斜,干净无灰尘污渍(立柱、灯箱、展架等)。',
51
+//     instructions: '拍照:按照示例图拍摄前庭内海报宣传物,可上传多张照片',
52
+//     examples: [
53
+//       'https://picsum.photos/id/1018/200/150',
54
+//       'https://picsum.photos/id/1019/200/150',
55
+//     ],
56
+//     images: [],
57
+//     isimages: true,
58
+//     status: 'normal',
59
+//   },
60
+//   {
61
+//     id: '2',
62
+//     type: '前庭',
63
+//     description: '[卫生] 抽油枪栓是否出现空转或存在明显渗油。',
64
+//     icon: 'help-circle',
65
+//     instructions: '',
66
+//     examples: [],
67
+//     images: [],
68
+//     isimages: false,
69
+//     status: 'normal',
70
+//     details: [{ name: '12号枪', status: 'normal' }],
71
+//   },
72
+//   {
73
+//     id: '3',
74
+//     type: '前庭',
75
+//     description:
76
+//       '[AI] [卫生] 前庭海报整齐不歪斜,干净无灰尘污渍(立柱、灯箱、展架等)。',
77
+//     instructions: '拍照:按照示例图拍摄前庭内海报宣传物,可上传多张照片',
78
+//     examples: [
79
+//       'https://picsum.photos/id/1018/200/150',
80
+//       'https://picsum.photos/id/1019/200/150',
81
+//     ],
82
+//     images: ['https://picsum.photos/id/1025/200/150'],
83
+//     isimages: true,
84
+//     status: 'normal',
85
+//   },
86
+// ])
87
+
88
+// 选择图片
89
+function chooseImage(index: number) {
90
+  // 为每个上传请求创建独立的upload实例,确保能正确处理对应的任务索引
91
+  const { run: uploadImage } = useUpload({
92
+    fileType: 'image',
93
+    success: (res) => {
94
+      console.log('上传成功', res)
95
+      const imageUrl = res.url || ''
96
+      if (imageUrl) {
97
+        console.log('currentCheckList.value[index]', currentCheckList.value[index], imageUrl)
98
+        if (!currentCheckList.value[index]?.dealPhoto) {
99
+          currentCheckList.value[index].dealPhoto = []
100
+        }
101
+        nextTick(() => {
102
+          currentCheckList.value[index].dealPhoto.push({
103
+            url: imageUrl,
104
+            name: res.fileName,
105
+          })
106
+        })
107
+      }
108
+    },
109
+    error: (err) => {
110
+      console.error('上传失败', err)
111
+    },
112
+  })
113
+  uploadImage()
114
+}
115
+const selectTask = ref<any>({})
116
+// 切换任务状态
117
+function toggleTaskStatus(task: any) {
118
+  selectTask.value = task
119
+  popShow.value.anomalyShow = true
120
+}
121
+
122
+// 处理异常信息提交
123
+function handleAnomalySubmitted({ checkItemId, data }: { checkItemId: number, data: any }) {
124
+  // 在 currentCheckList 中找到对应的任务
125
+  const taskIndex = currentCheckList.value.findIndex((item: any) => item.id === checkItemId)
126
+  if (taskIndex !== -1) {
127
+    // 在任务对象中添加标记,表明异常信息已填写
128
+    currentCheckList.value[taskIndex].hasAnomalyInfo = true
129
+    // 保存提交的异常数据
130
+    currentCheckList.value[taskIndex].anomalyData = data
131
+  }
132
+}
133
+
134
+// 处理 dealResult 变化
135
+function handleDealResultChange(task: any) {
136
+  // 当用户选择异常时,自动弹出异常信息填写抽屉
137
+  if (task.dealResult === '2') {
138
+    toggleTaskStatus(task)
139
+  }
140
+}
141
+
142
+// 切换分数选择
143
+function toggleScore(taskIndex: number, score: number | null) {
144
+  const task = currentCheckList.value[taskIndex]
145
+  task.score = score
146
+  // 当分数小于 5 时,设置任务状态为异常
147
+  if (score !== null && score < 5) {
148
+    task.status = 'abnormal'
149
+  }
150
+  else {
151
+    task.status = 'normal'
152
+  }
153
+}
154
+
155
+const lastIndex = ref(0)
156
+
157
+async function saveCheckLog({ index }: { index: number }) {
158
+  try {
159
+    // 获取当前数据
160
+    const logs = []
161
+    const items = checkSubItems.value[index]?.items || []
162
+    currentCheckList.value.forEach((item: any, index: number) => {
163
+      const logInfo = {
164
+        inputItem: item.dealInputItem || '', // 输入内容
165
+        itemId: item.id || '', // 检查项ID
166
+        photo: item.dealPhoto?.map((photo: any) => photo.name).join(',') || '', // 照片
167
+        result: item.dealHasScore ? (item.dealScore || 0) === item.limitScore ? '1' : '2' : '3', // 检查结果1正常2异常3不适用
168
+        score: item.dealHasScore ? item.dealScore || 0 : 0, // 分数
169
+      }
170
+
171
+      // 做一些判断,比如必填项
172
+      if (item.hasPhoto === 2 && !item.dealPhoto?.length) {
173
+        throw new Error('请上传照片')
174
+      }
175
+
176
+      if (item.hasInput === 1 && !item.dealInputItem) {
177
+        throw new Error('请输入内容')
178
+      }
179
+
180
+      if (logInfo.result === '2') {
181
+      // 判断是否输入异常项
182
+      // if (!item.dealScore) {
183
+      //   uni.showToast({
184
+      //     title: '请输入分数',
185
+      //     icon: 'none',
186
+      //   })
187
+      //   return
188
+      // }
189
+      }
190
+
191
+      logs.push(logInfo)
192
+    })
193
+
194
+    const res: any = await submitCheckLogs(checkId.value, logs)
195
+    console.log('提交检查子项日志', res)
196
+    if (res.code === 200) {
197
+      // uni.showToast({
198
+      //   title: '提交成功',
199
+      //   icon: 'success',
200
+      // })
201
+      lastIndex.value = index
202
+      currentCheckList.value = checkSubItems.value[index]?.items || []
203
+    }
204
+    else {
205
+      uni.showToast({
206
+        title: '提交失败',
207
+        icon: 'none',
208
+      })
209
+    }
210
+  }
211
+  catch (error) {
212
+    uni.showToast({
213
+      title: error.message || '提交失败',
214
+      icon: 'none',
215
+    })
216
+    console.log('lastIndex.value', lastIndex.value)
217
+    nextTick(() => {
218
+      activeArea.value = lastIndex.value
219
+    })
220
+  }
221
+}
222
+
223
+// 提交任务
224
+function submitTasks() {
225
+  saveCheckLog({ index: activeArea.value }).then(() => {
226
+    // 提交任务
227
+    handleCheckTask(checkId.value).then(() => {
228
+      uni.showToast({
229
+        title: '提交成功',
230
+        icon: 'success',
231
+      })
232
+    }).catch(() => {
233
+      uni.showToast({
234
+        title: '提交失败',
235
+        icon: 'none',
236
+      })
237
+    })
238
+  })
239
+}
240
+
241
+function init() {
242
+  // 并发获取数据
243
+  Promise.all([
244
+    getTaskDetail(taskId.value),
245
+    getChecktItemsByTaskId({
246
+      checkId: checkId.value,
247
+      pageSize: 100,
248
+    }),
249
+    getCheckSubItemsByTaskId({
250
+      checkId: checkId.value,
251
+      pageSize: 100,
252
+    }),
253
+  ]).then(([taskDetailRes, checkItemsRes, checkSubItemsRes]: any) => {
254
+    if (taskDetailRes.data) {
255
+      taskInfo.value = taskDetailRes.data
256
+    }
257
+    // 处理检查项数据
258
+    checkSubItems.value = checkSubItemsRes?.rows || []
259
+    checkSubItems.value.forEach((item: any) => {
260
+      item.items
261
+        = checkItemsRes?.rows?.filter(
262
+          (subItem: any) => subItem.subItemId === item.id,
263
+        ) || []
264
+      // 为每个任务添加 score 字段
265
+      item.items.forEach((task: any) => {
266
+        task.score = task.score || null
267
+      })
268
+    })
269
+    // 更新检查项数据
270
+    console.log('checkSubItems', checkSubItems.value)
271
+    activeArea.value = 0
272
+    currentCheckList.value = checkSubItems.value[0]?.items || []
273
+  })
274
+}
275
+
276
+function getPhotos(item: any): Array<{ isExample: boolean, url: string }> {
277
+  const list: Array<{ isExample: boolean, url: string }> = (
278
+    item.exampleImageUrl || []
279
+  ).map((url: string) => ({
280
+    url,
281
+    isExample: true,
282
+  }));
283
+  (item.photoUrl || []).forEach((url: string) => {
284
+    list.push({
285
+      url,
286
+      isExample: false,
287
+    })
288
+  })
289
+
290
+  return list
291
+}
292
+
293
+onMounted(async () => {
294
+  await init()
295
+})
296
+</script>
297
+
298
+<template>
299
+  <view class="task-management">
300
+    <view class="fixed-area">
301
+      <view class="title">
302
+        <text class="title-text">
303
+          {{ taskInfo.stationName || '' }}
304
+        </text>
305
+      </view>
306
+      <view>
307
+        <wd-tabs v-model="activeArea" @click="saveCheckLog">
308
+          <block v-for="(item, index) in checkSubItems" :key="index">
309
+            <wd-tab :title="`${item.name}`" :name="index" />
310
+          </block>
311
+        </wd-tabs>
312
+      </view>
313
+    </view>
314
+
315
+    <scroll-view class="task-container" scroll-y>
316
+      <view class="tasks">
317
+        <view
318
+          v-for="(task, index) in currentCheckList"
319
+          :key="index"
320
+          class="task-item"
321
+        >
322
+          <!-- 任务描述 -->
323
+          <view class="task-description">
324
+            <text class="task-number">{{ +index + 1 }}.</text>
325
+            <text class="task-text">
326
+              {{ task.itemName }}
327
+              <wd-icon
328
+                v-if="task?.icon"
329
+                :name="task?.icon"
330
+                size="32rpx"
331
+                @click="() => (popShow.centerShow = true)"
332
+              />
333
+            </text>
334
+          </view>
335
+
336
+          <view v-if="task.hasInput > 0" class="input-container" style="margin-right: 1rem;">
337
+            <wd-textarea v-model="task.dealInputItem" :placeholder="task.inputContent || '请输入内容'" :maxlength="100" show-word-limit />
338
+          </view>
339
+
340
+          <!-- 操作说明 -->
341
+          <view v-if="task.hasPhoto > 0" class="task-instructions">
342
+            <text v-if="task.photoPrompt" class="instructions-text">
343
+              {{ `拍照:${task.photoPrompt}` || '' }}
344
+            </text>
345
+            <text v-if="task.hasPhoto === 2 && !task.photoPrompt" class="instructions-text">
346
+              <span style="color: red;">*</span>必拍
347
+            </text>
348
+          </view>
349
+
350
+          <!-- 示例图片 -->
351
+          <view v-if="task.hasPhoto > 0" class="example-images">
352
+            <view
353
+              v-for="(img, idx) in task.exampleImageUrl"
354
+              :key="idx"
355
+              class="example-image"
356
+            >
357
+              <image :src="img" mode="aspectFill" />
358
+              <text class="example-text">示例</text>
359
+            </view>
360
+          </view>
361
+
362
+          <!-- 图片上传区域 -->
363
+          <view v-if="task.hasPhoto > 0 || (task?.images?.length > 0)" class="image-upload-container">
364
+            <!-- 上传按钮 -->
365
+            <view
366
+              v-if="task.hasPhoto > 0"
367
+              class="upload-btn"
368
+              @tap="chooseImage(+index)"
369
+            >
370
+              <view class="btn-content">
371
+                <text class="btn-icon">+</text>
372
+                <text class="btn-text">拍照</text>
373
+              </view>
374
+            </view>
375
+
376
+            <!-- 已上传图片 -->
377
+            <view v-if="task?.dealPhoto?.length > 0" class="uploaded-images">
378
+              <view
379
+                v-for="(image, idx) in task.dealPhoto"
380
+                :key="idx"
381
+                class="uploaded-image"
382
+              >
383
+                <image :src="image.url" mode="aspectFill" />
384
+              </view>
385
+            </view>
386
+          </view>
387
+          <view v-if="task.checkResult === 1" style="margin-left: 1rem;">
388
+            <wd-radio-group v-model="task.dealResult" shape="dot" inline @change="handleDealResultChange(task)">
389
+              <wd-radio value="1">
390
+                正常
391
+              </wd-radio>
392
+              <wd-radio value="2">
393
+                异常
394
+              </wd-radio>
395
+              <wd-radio value="3">
396
+                不适用
397
+              </wd-radio>
398
+            </wd-radio-group>
399
+            <view v-if="task.dealResult === '2'" style="margin-top: 8rpx;">
400
+              <text class="link-text" @tap="toggleTaskStatus(task)">{{ task.hasAnomalyInfo ? '点击查看异常' : '点击填写异常' }}</text>
401
+            </view>
402
+          </view>
403
+
404
+          <!-- 打分组件 -->
405
+          <view v-if="[2, 3].includes(task.checkResult)" class="score-container">
406
+            <view class="score-row">
407
+              <view class="score-label">
408
+                打分
409
+              </view>
410
+              <wd-checkbox v-model="task.dealHasScore">
411
+                不适用
412
+              </wd-checkbox>
413
+              <wd-input-number v-model="task.dealScore" :min="0" :max="task.limitScore" :disabled="task.dealHasScore" />
414
+              <view class="score-tip">
415
+                满分为{{ task.limitScore }}分
416
+              </view>
417
+            </view>
418
+            <view v-if="task.dealScore !== null && task.dealScore < task.limitScore && !task.dealHasScore" class="abnormal-link">
419
+              <text class="link-text" @tap="toggleTaskStatus(task)">{{ task.hasAnomalyInfo ? '点击查看异常' : '点击填写异常(未满分必填)' }}</text>
420
+            </view>
421
+          </view>
422
+        </view>
423
+      </view>
424
+    </scroll-view>
425
+    <centerpopup v-model:center-show="popShow.centerShow" />
426
+
427
+    <anomalyPop v-model:anomaly-show="popShow.anomalyShow" :task-id="taskId" :station-id="taskInfo.stationId" :check-item-id="selectTask.id" @anomaly-submitted="handleAnomalySubmitted" />
428
+    <!-- 确定按钮 -->
429
+    <view class="confirm-btn">
430
+      <button class="btn" @click="submitTasks">
431
+        提交
432
+      </button>
433
+      <button class="btn" @click="saveCheckLog({ index: activeArea.value })">
434
+        保存
435
+      </button>
436
+    </view>
437
+  </view>
438
+</template>
439
+
440
+<style lang="scss" scoped>
441
+.task-management {
442
+  display: flex;
443
+  flex-direction: column;
444
+  height: 1500rpx;
445
+  overflow: hidden;
446
+
447
+  // 全局链接文本样式
448
+  .link-text {
449
+    font-size: 24rpx;
450
+    color: red;
451
+    text-decoration: underline;
452
+    cursor: pointer;
453
+  }
454
+
455
+  .fixed-area {
456
+    flex-shrink: 0; // 不让它被压缩
457
+    background-color: #fff;
458
+    position: sticky; // 若想随外层滚就 sticky;想完全固定用 fixed 即可
459
+    top: 0;
460
+    z-index: 10;
461
+  }
462
+  .title {
463
+    width: 100%;
464
+    height: 128rpx;
465
+    background-color: #4a80c3;
466
+    display: flex;
467
+    align-items: center;
468
+    box-sizing: border-box;
469
+    padding-left: 46rpx;
470
+  }
471
+
472
+  .title-text {
473
+    font-size: 36rpx;
474
+    font-weight: 500;
475
+    color: #fff;
476
+  }
477
+
478
+  // // 任务容器
479
+  // .task-container {
480
+  //   flex: 1;
481
+  //   padding: 0 30rpx;
482
+
483
+  //   .tasks {
484
+  //     padding: 20rpx 0;
485
+  //   }
486
+  // }
487
+  .task-container {
488
+    flex: 1; // 占剩余高度
489
+    overflow-y: auto; // 仅这里滚动
490
+    padding: 0 30rpx 120rpx; // 为底部固定按钮留出空间
491
+  }
492
+  // 任务项
493
+  .task-item {
494
+    margin-bottom: 40rpx;
495
+
496
+    // 任务描述
497
+    .task-description {
498
+      display: flex;
499
+      margin-bottom: 16rpx;
500
+
501
+      .task-number {
502
+        font-size: 28rpx;
503
+        color: #333;
504
+        margin-right: 8rpx;
505
+        font-weight: 500;
506
+      }
507
+
508
+      .task-text {
509
+        font-size: 28rpx;
510
+        font-weight: 400;
511
+        color: #333;
512
+        line-height: 44rpx;
513
+      }
514
+    }
515
+
516
+    // 操作说明
517
+    .task-instructions {
518
+      margin-bottom: 20rpx;
519
+      margin-left: 1rem;
520
+
521
+      .instructions-text {
522
+        font-size: 24rpx;
523
+        color: #666;
524
+        line-height: 36rpx;
525
+      }
526
+    }
527
+
528
+    // 示例图片
529
+    .example-images {
530
+      display: flex;
531
+      gap: 16rpx;
532
+      margin-bottom: 20rpx;
533
+      margin-left: 1rem;
534
+
535
+      .example-image {
536
+        width: 140rpx;
537
+        height: 100rpx;
538
+        position: relative;
539
+
540
+        image {
541
+          width: 100%;
542
+          height: 100%;
543
+          border-radius: 8rpx;
544
+        }
545
+
546
+        .example-text {
547
+          position: absolute;
548
+          bottom: 0;
549
+          left: 0;
550
+          right: 0;
551
+          background-color: rgba(0, 0, 0, 0.5);
552
+          color: white;
553
+          font-size: 20rpx;
554
+          text-align: center;
555
+          padding: 4rpx 0;
556
+          border-radius: 0 0 8rpx 8rpx;
557
+        }
558
+      }
559
+    }
560
+
561
+    // 已上传图片
562
+    .uploaded-images {
563
+      display: flex;
564
+      gap: 16rpx;
565
+      margin-bottom: 20rpx;
566
+
567
+      .uploaded-image {
568
+        width: 140rpx;
569
+        height: 100rpx;
570
+
571
+        image {
572
+          width: 100%;
573
+          height: 100%;
574
+          border-radius: 8rpx;
575
+        }
576
+      }
577
+    }
578
+
579
+    // 图片上传区域容器
580
+    .image-upload-container {
581
+      display: flex;
582
+      gap: 16rpx;
583
+      margin-bottom: 24rpx;
584
+      margin-left: 1rem;
585
+      overflow-x: auto;
586
+      padding-bottom: 8rpx;
587
+      white-space: nowrap;
588
+      -webkit-overflow-scrolling: touch;
589
+
590
+      &::-webkit-scrollbar {
591
+        height: 4rpx;
592
+      }
593
+
594
+      &::-webkit-scrollbar-track {
595
+        background: #f1f1f1;
596
+        border-radius: 2rpx;
597
+      }
598
+
599
+      &::-webkit-scrollbar-thumb {
600
+        background: #ccc;
601
+        border-radius: 2rpx;
602
+      }
603
+    }
604
+
605
+    // 上传按钮
606
+    .upload-btn {
607
+      width: 140rpx;
608
+      height: 100rpx;
609
+      border: 2rpx dashed #ccc;
610
+      border-radius: 8rpx;
611
+      display: flex;
612
+      align-items: center;
613
+      justify-content: center;
614
+      flex-shrink: 0;
615
+
616
+      .btn-content {
617
+        display: flex;
618
+        flex-direction: column;
619
+        align-items: center;
620
+
621
+        .btn-icon {
622
+          font-size: 40rpx;
623
+          color: #999;
624
+          line-height: 40rpx;
625
+        }
626
+
627
+        .btn-text {
628
+          font-size: 24rpx;
629
+          color: #999;
630
+          margin-top: 4rpx;
631
+        }
632
+      }
633
+    }
634
+
635
+    // 已上传图片
636
+    .uploaded-images {
637
+      display: flex;
638
+      gap: 16rpx;
639
+      flex-shrink: 0;
640
+
641
+      .uploaded-image {
642
+        width: 140rpx;
643
+        height: 100rpx;
644
+        flex-shrink: 0;
645
+
646
+        image {
647
+          width: 100%;
648
+          height: 100%;
649
+          border-radius: 8rpx;
650
+        }
651
+      }
652
+    }
653
+
654
+    // 状态选择
655
+    .status-select {
656
+      margin-bottom: 20rpx;
657
+
658
+      .radio-group {
659
+        display: flex;
660
+        gap: 40rpx;
661
+      }
662
+    }
663
+
664
+    // 带详情的状态选择
665
+    .detail-status {
666
+      margin-bottom: 20rpx;
667
+
668
+      .detail-name {
669
+        font-size: 28rpx;
670
+        color: #333;
671
+        margin-right: 20rpx;
672
+        // margin-bottom: 10rpx;
673
+      }
674
+
675
+      .radio-group {
676
+        display: flex;
677
+        gap: 40rpx;
678
+      }
679
+
680
+      .detail-items {
681
+        margin-top: 16rpx;
682
+        display: flex;
683
+        flex-wrap: wrap;
684
+        gap: 40rpx;
685
+      }
686
+    }
687
+
688
+    // 单选按钮
689
+    .radio-group {
690
+      .radio-item {
691
+        // margin-top: 50rpx;
692
+        display: flex;
693
+        align-items: center;
694
+        gap: 8rpx;
695
+
696
+        .radio {
697
+          width: 32rpx;
698
+          height: 32rpx;
699
+          border: 2rpx solid #ccc;
700
+          border-radius: 50%;
701
+          display: flex;
702
+          align-items: center;
703
+          justify-content: center;
704
+
705
+          &.selected {
706
+            border-color: #215acd;
707
+            // background-color: #4a80c3;
708
+
709
+            &::after {
710
+              content: '';
711
+              width: 16rpx;
712
+              height: 16rpx;
713
+              border-radius: 50%;
714
+              background-color: #215acd;
715
+            }
716
+          }
717
+        }
718
+
719
+        .radio-text {
720
+          font-size: 28rpx;
721
+          color: #333;
722
+        }
723
+      }
724
+    }
725
+
726
+    // 打分组件
727
+    .score-select {
728
+      margin-bottom: 20rpx;
729
+
730
+      .score-label {
731
+        font-size: 28rpx;
732
+        color: #333;
733
+        margin-bottom: 12rpx;
734
+      }
735
+
736
+      .score-container {
737
+        display: flex;
738
+        align-items: center;
739
+        gap: 40rpx;
740
+        margin-bottom: 16rpx;
741
+      }
742
+
743
+      .score-buttons {
744
+        display: flex;
745
+        gap: 20rpx;
746
+      }
747
+
748
+      .score-button {
749
+        width: 60rpx;
750
+        height: 60rpx;
751
+        border: 2rpx solid #ccc;
752
+        border-radius: 50%;
753
+        display: flex;
754
+        align-items: center;
755
+        justify-content: center;
756
+        font-size: 28rpx;
757
+        color: #333;
758
+        background-color: #fff;
759
+        transition: all 0.2s ease;
760
+
761
+        &.selected {
762
+          border-color: #215acd;
763
+          background-color: #215acd;
764
+          color: #fff;
765
+        }
766
+      }
767
+
768
+      .abnormal-link {
769
+        margin-top: 12rpx;
770
+      }
771
+
772
+      .link-text {
773
+        font-size: 24rpx;
774
+        color: red;
775
+        text-decoration: underline;
776
+        cursor: pointer;
777
+      }
778
+    }
779
+
780
+    // 新打分组件样式
781
+    .score-container {
782
+      margin-bottom: 20rpx;
783
+      margin-left: 1rem;
784
+      .score-row {
785
+        display: flex;
786
+        align-items: center;
787
+        gap: 20rpx;
788
+        margin-bottom: 12rpx;
789
+      }
790
+
791
+      .score-label {
792
+        font-size: 28rpx;
793
+        color: #333;
794
+        white-space: nowrap;
795
+      }
796
+
797
+      .score-tip {
798
+        font-size: 24rpx;
799
+        color: #666;
800
+        margin-bottom: 12rpx;
801
+      }
802
+
803
+      .abnormal-link {
804
+        margin-top: 8rpx;
805
+      }
806
+
807
+      .link-text {
808
+        font-size: 24rpx;
809
+        color: red;
810
+        text-decoration: underline;
811
+        cursor: pointer;
812
+      }
813
+    }
814
+  }
815
+
816
+  // 确定按钮
817
+  .confirm-btn {
818
+    position: fixed;
819
+    bottom: 0;
820
+    left: 0;
821
+    right: 0;
822
+    padding: 20rpx 30rpx;
823
+    background-color: white;
824
+    border-top: 1rpx solid #eee;
825
+    display: flex;
826
+    gap: 20rpx;
827
+    z-index: 90;
828
+
829
+    .btn {
830
+      flex: 1;
831
+      height: 80rpx;
832
+      background-color: #215acd;
833
+      color: white;
834
+      font-size: 32rpx;
835
+      border-radius: 12rpx;
836
+      font-weight: 400;
837
+      border: none;
838
+      outline: none;
839
+    }
840
+  }
841
+}
842
+</style>

+ 23 - 0
src/pages/schedule/details/deal/upload.vue

1
+<template>
2
+  <view class="page-container">
3
+    <view class="page-header">
4
+      <view class="page-header-left">
5
+        <view class="page-header-left-title">
6
+          上传文件
7
+        </view>
8
+      </view>
9
+    </view>
10
+  </view>
11
+</template>
12
+
13
+<script setup lang="ts">
14
+import { onMounted, ref } from 'vue'
15
+
16
+const taskId = ref('')
17
+const checkId = ref('')
18
+
19
+onMounted(() => {
20
+//   taskId.value = getQueryParam('id') || ''
21
+//   checkId.value = getQueryParam('checkId') || ''
22
+})
23
+</script>

+ 382 - 115
src/pages/schedule/details/index.vue

1
+<script setup lang="ts">
2
+import dayjs from 'dayjs'
3
+import { keyBy } from 'lodash-es'
4
+import { ref } from 'vue'
5
+import { useRoute, useRouter } from 'vue-router'
6
+import { useMessage, useToast } from 'wot-design-uni'
7
+import { confirmDoneTask, getTaskDetail } from '@/api/schedule/task'
8
+
9
+import { authStatusOptions, taskLevelOptions, taskStatusOptions } from '@/utils/dicts'
10
+
11
+const message = useMessage()
12
+const toast = useToast()
13
+
14
+// 定义页面配置,注册路由
15
+definePage({
16
+  excludeLoginPath: true,
17
+})
18
+
19
+// 状态管理
20
+const isExpanded = ref(false)
21
+const router = useRouter()
22
+
23
+const route = useRoute()
24
+const taskId = ref(Number(route.query.id))
25
+
26
+const taskInfo: any = ref({})
27
+const taskOther: any = ref({})
28
+const taskResult: any = ref({})
29
+
30
+// 切换更多内容显示/隐藏
31
+function toggleMore() {
32
+  isExpanded.value = !isExpanded.value
33
+}
34
+
35
+async function dealConfirmTaskEvent() {
36
+// 二次弹框确认
37
+  message.confirm({
38
+    msg: `确认完成任务 ${taskInfo.value.taskName} 吗?`,
39
+    title: '确认完成',
40
+  }).then(async () => {
41
+    try {
42
+      await confirmDoneTask(taskInfo.value.id || 0)
43
+
44
+      toast.success('确认完成任务成功')
45
+      await init()
46
+    }
47
+    catch {
48
+      toast.error('操作失败')
49
+    }
50
+  }).catch(() => {
51
+    return
52
+  })
53
+}
54
+
55
+// 查看详情按钮点击事件
56
+async function dealTaskEvent() {
57
+  // 根据任务类型进行判断
58
+  let params: any = {}
59
+  let isModelPage = false
60
+  switch (taskInfo.value.formType) {
61
+    case 'inspection': {
62
+      uni.navigateTo({
63
+        url: `/pages/schedule/details/deal/inspection?id=${taskId.value}&checkId=${taskInfo.value.taskList[0].id}`,
64
+      })
65
+      return
66
+    }
67
+    case 'confirm': {
68
+      await dealConfirmTaskEvent()
69
+
70
+      return
71
+    }
72
+    case 'form': {
73
+      if (
74
+        ['annualExamination_task', 'replacement_task'].includes(
75
+          taskInfo.value.subFormType,
76
+        )
77
+      ) {
78
+        try {
79
+          // await dealAnnualExaminationTask()
80
+        }
81
+        catch {
82
+          // ElMessage.error('处理任务失败')
83
+        }
84
+        return
85
+      }
86
+
87
+      params = {
88
+        taskId: taskInfo.value.taskList?.[0]?.id || '',
89
+        taskName: taskInfo.value.taskName,
90
+        taskList: taskInfo.value.taskList || [],
91
+        remark: taskResult.value.content,
92
+        stationId: taskInfo.value.stationId || 0,
93
+        subFormType: taskInfo.value.subFormType,
94
+        attachmentUrl: taskResult.value.images,
95
+      }
96
+
97
+      if (
98
+        [
99
+          'ambulance_check',
100
+          'emergency_protection_monthly_check',
101
+          'filter_maintenance',
102
+          'fire_extinguisher_check',
103
+        ].includes(taskInfo.value.subFormType)
104
+      ) {
105
+        isModelPage = true
106
+      }
107
+
108
+      break
109
+    }
110
+    case 'upload': {
111
+      uni.navigateTo({
112
+        url: `/pages/schedule/details/deal/upload?id=${taskId.value}`,
113
+      })
114
+      return
115
+    }
116
+    default: {
117
+      // return ElMessage.error('当前任务无法处理')
118
+    }
119
+  }
120
+  // if (isModelPage) {
121
+  //   pageModalApi.open()
122
+  // }
123
+  // else {
124
+  //   dealDrawerApi.setData(params).open()
125
+  // }
126
+}
127
+// 查看详情按钮点击事件2
128
+function viewDetails2() {
129
+  uni.navigateTo({
130
+    url: '/pages/schedule/details/taskmanagement/index2',
131
+  })
132
+}
133
+function viewDetails3() {
134
+  uni.navigateTo({
135
+    url: '/pages/schedule/details/taskdetails/index',
136
+  })
137
+}
138
+
139
+/**
140
+ * 获取任务时间
141
+ * @returns 任务时间字符串
142
+ */
143
+function getTaskTime() {
144
+  return `${dayjs(taskInfo.value.startTime).format('MM-DD HH:mm')}~${dayjs(taskInfo.value.endTime).format('MM-DD HH:mm')}`
145
+}
146
+
147
+/**
148
+ * 下载文件
149
+ * @param file 文件对象或链接
150
+ * @param fileName 文件名
151
+ */
152
+function downloadFile(file: any, fileName: string) {
153
+  // 这里实现文件下载逻辑
154
+  console.log('downloadFile:', file, fileName)
155
+}
156
+
157
+async function init() {
158
+  const res: any = await getTaskDetail(taskId.value)
159
+  taskInfo.value = res.data || {}
160
+  if (!taskInfo.value.status) {
161
+    taskInfo.value.status = 0
162
+  }
163
+  // 假设从接口返回数据中获取taskOther和taskResult
164
+  taskOther.value.taskTypeName = taskInfo.value?.formTypeName || '-'
165
+
166
+  if (!taskInfo.value.executorName && taskInfo.value.taskList?.length) {
167
+    taskInfo.value.executorName = taskInfo.value.taskList[0].executorName
168
+  }
169
+
170
+  if (taskInfo.value.formType === 'photo') {
171
+    const result = taskInfo.value?.taskList?.[0] || {}
172
+    if (result.remark) {
173
+      taskResult.value.content = result.remark
174
+    }
175
+    if (result?.attachmentUrl?.length) {
176
+      taskResult.value.images = result.attachmentUrl
177
+    }
178
+  }
179
+  else if (taskInfo.value.formType === 'upload') {
180
+    // attachmentUrl
181
+    const result = taskInfo.value?.taskList?.[0] || {}
182
+    if (result.remark) {
183
+      taskResult.value.content = result.remark
184
+    }
185
+    if (result?.attachmentUrl?.length) {
186
+      taskResult.value.files = result.attachmentUrl
187
+      taskResult.value.fileNames = result.attachmentName.split(',')
188
+    }
189
+  }
190
+  else {
191
+    // taskResult.value = taskInfo.value?.taskList?.[0] || {};
192
+  }
193
+
194
+  if (taskInfo.value?.taskTemplate) {
195
+    taskOther.value.taskFrequencyName
196
+      = taskInfo.value?.taskTemplate?.taskFrequencyName || '-'
197
+  }
198
+}
199
+
200
+// 状态标签配置
201
+const statusConfig: any = keyBy(taskStatusOptions, 'value')
202
+
203
+// 优先级标签配置
204
+const priorityConfig: any = keyBy(taskLevelOptions, 'value')
205
+
206
+// 授权状态标签配置
207
+const authStatusConfig: any = keyBy(authStatusOptions, 'value')
208
+
209
+onMounted(async () => {
210
+  await init()
211
+})
212
+</script>
213
+
1
 <template>
214
 <template>
2
   <view class="details-box">
215
   <view class="details-box">
3
     <!-- 标题区域 -->
216
     <!-- 标题区域 -->
4
     <view class="details-box-title">
217
     <view class="details-box-title">
5
       <view class="details-box-title-text">
218
       <view class="details-box-title-text">
6
-        巡站日检 AI
219
+        {{ taskInfo.taskName || '任务详情' }}
7
       </view>
220
       </view>
8
     </view>
221
     </view>
9
 
222
 
10
     <!-- 标签区域 -->
223
     <!-- 标签区域 -->
11
     <view class="tags-box">
224
     <view class="tags-box">
12
-      <view class="tag tag-mandatory">
13
-        必做
225
+      <view class="tag">
226
+        {{ priorityConfig[taskInfo.level]?.label || taskInfo.level }}
14
       </view>
227
       </view>
15
-      <view class="tag tag-authorize">
16
-        可授权
228
+
229
+      <view class="tag">
230
+        {{ statusConfig[`${taskInfo.status || 0}`]?.label || taskInfo.status }}
17
       </view>
231
       </view>
18
-      <view class="tag tag-time">
19
-        按时完成
232
+      <view class="tag">
233
+        {{ authStatusConfig[taskInfo.authStatus || 0]?.label || taskInfo.authStatus }}
20
       </view>
234
       </view>
21
     </view>
235
     </view>
22
 
236
 
25
       <view class="section-title">
239
       <view class="section-title">
26
         任务描述
240
         任务描述
27
       </view>
241
       </view>
28
-      <view class="task-description">
29
-        <view class="task-item">
30
-          1、每日到站第一事,巡视油站;
31
-        </view>
32
-        <view class="task-item">
33
-          2、了解人、财、物情况;
34
-        </view>
35
-        <view class="task-item">
36
-          3、可随手解决当场解决,其他后续跟进,直到解决;
37
-        </view>
38
-        <view class="task-item">
39
-          4、检查标识识:AI的不需要人为判断异常,默认正常即可。
40
-        </view>
41
-      </view>
242
+      <view class="task-description" v-html="taskInfo.description || '暂无'" />
42
     </view>
243
     </view>
43
 
244
 
44
     <!-- 标准指引 -->
245
     <!-- 标准指引 -->
45
     <view class="section">
246
     <view class="section">
46
       <view class="section-title">
247
       <view class="section-title">
47
-        标准指引
248
+        任务信息
48
       </view>
249
       </view>
49
       <view class="standard-guide">
250
       <view class="standard-guide">
50
         <view class="guide-item">
251
         <view class="guide-item">
51
           <view class="guide-label">
252
           <view class="guide-label">
253
+            场站
254
+          </view>
255
+          <view class="guide-value">
256
+            {{ taskInfo.stationName || '-' }}
257
+          </view>
258
+        </view>
259
+        <view class="guide-item">
260
+          <view class="guide-label">
261
+            标准指引
262
+          </view>
263
+          <view class="guide-value">
264
+            {{ taskInfo.standardGuide || '-' }}
265
+          </view>
266
+        </view>
267
+        <view class="guide-item">
268
+          <view class="guide-label">
52
             执行人
269
             执行人
53
           </view>
270
           </view>
54
           <view class="guide-value">
271
           <view class="guide-value">
55
-            李玫玮(站长)
272
+            {{ taskInfo.executorName || '-' }}
56
           </view>
273
           </view>
57
         </view>
274
         </view>
58
         <view class="guide-item">
275
         <view class="guide-item">
60
             计划时间
277
             计划时间
61
           </view>
278
           </view>
62
           <view class="guide-value">
279
           <view class="guide-value">
63
-            12月5日
280
+            {{ taskInfo.displayDate || '-' }}
281
+          </view>
282
+        </view>
283
+        <view class="guide-item">
284
+          <view class="guide-label">
285
+            创建
286
+          </view>
287
+          <view class="guide-value">
288
+            {{ taskInfo.createBy || '' }}
289
+          </view>
290
+        </view>
291
+        <view class="guide-item">
292
+          <view class="guide-label">
293
+            任务类型
294
+          </view>
295
+          <view class="guide-value">
296
+            {{ taskInfo.subFormTypeName || taskOther.taskTypeName || '-' }}
297
+          </view>
298
+        </view>
299
+        <view class="guide-item">
300
+          <view class="guide-label">
301
+            任务负责人
302
+          </view>
303
+          <view class="guide-value">
304
+            {{ taskInfo.taskLeaderName || '-' }}
305
+          </view>
306
+        </view>
307
+        <view class="guide-item">
308
+          <view class="guide-label">
309
+            任务频率
310
+          </view>
311
+          <view class="guide-value">
312
+            {{ taskOther.taskFrequencyName || '-' }}
64
           </view>
313
           </view>
65
         </view>
314
         </view>
66
         <view class="guide-item">
315
         <view class="guide-item">
68
             任务时间
317
             任务时间
69
           </view>
318
           </view>
70
           <view class="guide-value">
319
           <view class="guide-value">
71
-            12月5日 00:00-12月5日 18:00
320
+            {{ getTaskTime() || '-' }}
321
+          </view>
322
+        </view>
323
+        <!-- <view class="guide-item">
324
+          <view class="guide-label">
325
+            任务描述
326
+          </view>
327
+          <view class="guide-value">
328
+            {{ taskInfo.description || '-' }}
329
+          </view>
330
+        </view> -->
331
+        <view v-if="taskInfo.descriptionFilesUrl && taskInfo.descriptionFilesUrl.length > 0" class="guide-item">
332
+          <view class="guide-label">
333
+            详情附件
334
+          </view>
335
+          <view class="guide-value">
336
+            <!-- 使用wot-ui样式显示附件 -->
337
+            <view class="attachment-list">
338
+              <wd-tag
339
+                v-for="(file, index) in taskInfo.descriptionFilesUrl"
340
+                :key="index"
341
+                custom-class="attachment-tag"
342
+                type="primary"
343
+                plain
344
+              >
345
+                附件{{ index + 1 }}
346
+              </wd-tag>
347
+            </view>
348
+          </view>
349
+        </view>
350
+        <view v-if="taskResult.handleContent || taskInfo.cancelReason" class="guide-item">
351
+          <view class="guide-label">
352
+            处理情况
353
+          </view>
354
+          <view class="guide-value">
355
+            {{ taskResult.handleContent || taskInfo.cancelReason || '' }}
72
           </view>
356
           </view>
73
         </view>
357
         </view>
74
         <view class="guide-item">
358
         <view class="guide-item">
75
           <view class="guide-label">
359
           <view class="guide-label">
76
-            完成时间
360
+            处理时间
361
+          </view>
362
+          <view class="guide-value">
363
+            {{ taskInfo.completeTime || taskInfo.cancelTime || '-' }}
364
+          </view>
365
+        </view>
366
+        <view v-if="taskResult.files && taskResult.files.length > 0" class="guide-item">
367
+          <view class="guide-label">
368
+            处理附件
77
           </view>
369
           </view>
78
           <view class="guide-value">
370
           <view class="guide-value">
79
-            12月5日
371
+            <!-- 使用wot-ui样式显示附件列表 -->
372
+            <view class="attachment-list">
373
+              <wd-tag
374
+                v-for="(file, index) in taskResult.files"
375
+                :key="index"
376
+                custom-class="attachment-tag"
377
+                type="success"
378
+                plain
379
+                @click="downloadFile(file, taskResult.fileNames[index] || `附件${index + 1}`)"
380
+              >
381
+                {{ taskResult.fileNames[index] || `附件${index + 1}` }}
382
+              </wd-tag>
383
+            </view>
80
           </view>
384
           </view>
81
         </view>
385
         </view>
82
       </view>
386
       </view>
83
     </view>
387
     </view>
84
 
388
 
85
     <!-- 查看更多 -->
389
     <!-- 查看更多 -->
86
-    <view class="more-btn" @tap="toggleMore">
390
+    <!-- <view class="more-btn" @tap="toggleMore">
87
       <text class="arrow">点击查看更多</text>
391
       <text class="arrow">点击查看更多</text>
88
       <wd-icon
392
       <wd-icon
89
         class="arrow"
393
         class="arrow"
90
         :name="isExpanded ? 'arrow-up' : 'arrow-down'"
394
         :name="isExpanded ? 'arrow-up' : 'arrow-down'"
91
         size="20px"
395
         size="20px"
92
       />
396
       />
93
-    </view>
397
+    </view> -->
94
 
398
 
95
     <!-- 更多内容(默认隐藏) -->
399
     <!-- 更多内容(默认隐藏) -->
96
     <view v-if="isExpanded" class="more-content">
400
     <view v-if="isExpanded" class="more-content">
112
       </view>
416
       </view>
113
     </view>
417
     </view>
114
 
418
 
115
-    <view class="detail-btn" @tap="viewDetails1">
116
-      <text>查 看 详 情1</text>
419
+    <view class="detail-btn" @tap="dealTaskEvent">
420
+      <text>处理任务</text>
117
     </view>
421
     </view>
118
-    <view class="detail-btn" @tap="viewDetails2">
422
+    <!-- <view class="detail-btn" @tap="viewDetails2">
119
       <text>查 看 详 情2</text>
423
       <text>查 看 详 情2</text>
120
     </view>
424
     </view>
121
     <view class="detail-btn" @tap="viewDetails3">
425
     <view class="detail-btn" @tap="viewDetails3">
122
       <text>查 看 详 情3</text>
426
       <text>查 看 详 情3</text>
123
-    </view>
427
+    </view> -->
124
   </view>
428
   </view>
125
 </template>
429
 </template>
126
 
430
 
127
-<script setup lang="ts">
128
-import { ref } from 'vue'
129
-import { useRoute, useRouter } from 'vue-router'
130
-
131
-import { getTaskDetail } from '@/api/schedule/task'
132
-
133
-// 定义页面配置,注册路由
134
-definePage({
135
-  excludeLoginPath: true,
136
-})
137
-
138
-// 状态管理
139
-const isExpanded = ref(false)
140
-const router = useRouter()
141
-
142
-const route = useRoute()
143
-const taskId = ref(Number(route.params.id))
144
-
145
-// 切换更多内容显示/隐藏
146
-function toggleMore() {
147
-  isExpanded.value = !isExpanded.value
148
-}
149
-
150
-// 查看详情按钮点击事件
151
-function viewDetails1() {
152
-  uni.navigateTo({
153
-    url: '/pages/schedule/details/taskmanagement/index1',
154
-  })
155
-}
156
-// 查看详情按钮点击事件2
157
-function viewDetails2() {
158
-  uni.navigateTo({
159
-    url: '/pages/schedule/details/taskmanagement/index2',
160
-  })
161
-}
162
-function viewDetails3() {
163
-  uni.navigateTo({
164
-    url: '/pages/schedule/details/taskdetails/index',
165
-  })
166
-}
167
-
168
-async function init() {
169
-  const res = await getTaskDetail(taskId.value)
170
-  taskInfo.value = res
171
-  if (!taskInfo.value.status) {
172
-    taskInfo.value.status = 0
173
-  }
174
-}
175
-
176
-onMounted(async () => {
177
-  await init()
178
-})
179
-</script>
180
-
181
 <style lang="scss" scoped>
431
 <style lang="scss" scoped>
182
 .details-box {
432
 .details-box {
183
   display: flex;
433
   display: flex;
189
     margin-bottom: 24rpx;
439
     margin-bottom: 24rpx;
190
 
440
 
191
     .details-box-title-text {
441
     .details-box-title-text {
192
-      font-size: 38rpx;
442
+      font-size: 48rpx;
193
       font-weight: 500;
443
       font-weight: 500;
194
       color: #31373d;
444
       color: #31373d;
195
     }
445
     }
198
   // 标签样式
448
   // 标签样式
199
   .tags-box {
449
   .tags-box {
200
     display: flex;
450
     display: flex;
201
-    gap: 16rpx;
202
-    margin-bottom: 32rpx;
451
+    gap: 12rpx;
452
+    margin-bottom: 24rpx;
203
 
453
 
204
     .tag {
454
     .tag {
205
       padding: 8rpx 16rpx;
455
       padding: 8rpx 16rpx;
206
       border-radius: 14rpx;
456
       border-radius: 14rpx;
207
       font-size: 24rpx;
457
       font-size: 24rpx;
208
       font-weight: 400;
458
       font-weight: 400;
209
-
210
-      &.tag-mandatory {
211
-        color: #215acd;
212
-        background-color: #e8f3ff;
213
-      }
214
-
215
-      &.tag-authorize {
216
-        color: #215acd;
217
-        background-color: #e8f3ff;
218
-      }
219
-
220
-      &.tag-time {
221
-        color: #215acd;
222
-        background-color: #e8f3ff;
223
-      }
459
+      color: #215acd;
460
+      background-color: #e8f3ff;
224
     }
461
     }
225
   }
462
   }
226
 
463
 
238
 
475
 
239
   // 任务描述样式
476
   // 任务描述样式
240
   .task-description {
477
   .task-description {
241
-    background-color: #f8f9fa;
242
-    padding: 24rpx;
243
-    border-radius: 12rpx;
478
+    padding: 0 0 24rpx 0;
479
+    border-bottom: 1rpx solid #e5e5e5;
480
+
481
+    // 针对HTML内容的样式
482
+    & :deep(ol),
483
+    & :deep(ul) {
484
+      padding-left: 32rpx;
485
+      margin: 0;
486
+    }
244
 
487
 
245
-    .task-item {
246
-      font-size: 27rpx;
488
+    & :deep(li) {
489
+      font-size: 28rpx;
247
       font-weight: 400;
490
       font-weight: 400;
248
-      color: #4e5969;
491
+      color: #31373d;
249
       line-height: 52rpx;
492
       line-height: 52rpx;
250
-      margin-bottom: 12rpx;
493
+      margin-bottom: 8rpx;
494
+    }
251
 
495
 
252
-      &:last-child {
253
-        margin-bottom: 0;
254
-      }
496
+    & :deep(p) {
497
+      font-size: 28rpx;
498
+      font-weight: 400;
499
+      color: #31373d;
500
+      line-height: 52rpx;
501
+      margin: 0 0 12rpx 0;
255
     }
502
     }
256
   }
503
   }
257
 
504
 
309
     background-color: #215acd;
556
     background-color: #215acd;
310
     border-radius: 14rpx;
557
     border-radius: 14rpx;
311
     margin-bottom: 32rpx;
558
     margin-bottom: 32rpx;
559
+    position: fixed;
560
+    bottom: 32rpx;
561
+    left: 24rpx;
562
+    right: 24rpx;
563
+    z-index: 999;
312
     text {
564
     text {
313
       font-size: 32rpx;
565
       font-size: 32rpx;
314
       color: #ffffff;
566
       color: #ffffff;
337
       }
589
       }
338
     }
590
     }
339
   }
591
   }
592
+
593
+  // 附件列表样式
594
+  .attachment-list {
595
+    display: flex;
596
+    flex-wrap: wrap;
597
+    gap: 12rpx;
598
+
599
+    .attachment-tag {
600
+      margin-bottom: 12rpx;
601
+    }
602
+  }
603
+}
604
+:deep(.attachment-tag) {
605
+  margin-right: 12rpx;
606
+  margin-bottom: 12rpx;
340
 }
607
 }
341
 </style>
608
 </style>

+ 510 - 0
src/pages/schedule/details/popup/anomaly.vue

1
+<script setup lang="ts">
2
+import dayjs from 'dayjs'
3
+// import { storeToRefs } from 'pinia'
4
+import { onMounted, reactive, ref, watch } from 'vue'
5
+import { useToast } from 'wot-design-uni'
6
+import { submitIssue } from '@/api/schedule/issue'
7
+import { getTags } from '@/api/system/tags'
8
+import { getAllUsers } from '@/api/system/users'
9
+import useUpload from '@/hooks/useUpload'
10
+// import { useUserStore } from '@/store'
11
+import calendar from '@/utils/calendar.vue'
12
+
13
+const props = defineProps({
14
+  anomalyShow: {
15
+    type: Boolean,
16
+    default: false,
17
+  },
18
+  taskId: {
19
+    type: Number,
20
+    default: 0,
21
+  },
22
+  stationId: {
23
+    type: Number,
24
+    default: 0,
25
+  },
26
+  checkItemId: {
27
+    type: Number,
28
+    default: 0,
29
+  },
30
+})
31
+const emit = defineEmits(['update:anomalyShow', 'anomaly-submitted'])
32
+console.log(props.stationId)
33
+const tags = ref<any[]>([])
34
+const users = ref<any[]>([])
35
+// const userStore = useUserStore()
36
+// const { userInfo } = storeToRefs(userStore)
37
+function handleClose() {
38
+  emit('update:anomalyShow', false)
39
+}
40
+
41
+async function getUserList() {
42
+  console.log('调用getAllUsers时的stationId:', props.stationId)
43
+  const res: any = await getAllUsers({
44
+    pageNum: 10000,
45
+    stationId: props.stationId,
46
+  })
47
+  if (res.code === 200) {
48
+    users.value = res.data.map((item: any) => ({
49
+      label: item.nickName,
50
+      value: item.userId,
51
+    }))
52
+  }
53
+}
54
+
55
+async function getTagsList(name: string = '') {
56
+  const params: any = {
57
+    pageNum: 10000,
58
+  }
59
+  if (name) {
60
+    params.tagName = name
61
+  }
62
+  const res: any = await getTags(params)
63
+  if (res.code === 200) {
64
+    tags.value = res.rows.map((item: any) => ({
65
+      label: item.tagName,
66
+      value: item.id,
67
+    }))
68
+  }
69
+}
70
+
71
+const { success: showSuccess } = useToast()
72
+
73
+const model = reactive<{
74
+  isTransferTask: string
75
+  problemDescription: string
76
+  handlingSuggestion: string
77
+  deadline: string
78
+  executorId: string
79
+  ccTo: string[]
80
+  handlingMeasure: string
81
+  handlingPhoto: string[]
82
+  problemPhoto: any
83
+  tags: string[]
84
+  correlationTaskId: string
85
+}>({
86
+  isTransferTask: '1',
87
+  problemDescription: '',
88
+  handlingSuggestion: '',
89
+  deadline: '',
90
+  executorId: '',
91
+  ccTo: [],
92
+  handlingMeasure: '',
93
+  handlingPhoto: [],
94
+  problemPhoto: [],
95
+  tags: [],
96
+  correlationTaskId: '',
97
+})
98
+
99
+const form = ref()
100
+const loading = ref(false)
101
+const action: string
102
+  = ''
103
+
104
+const datePickerVisible = ref(false)
105
+
106
+function handleClickCalendar() {
107
+  datePickerVisible.value = true
108
+}
109
+function onconfirmDate(date: string) {
110
+  model.deadline = date
111
+}
112
+
113
+// 上传问题照片
114
+const uploadProblemPhoto: any = (file, formData, options) => {
115
+  const { customUpload } = useUpload({
116
+    fileType: 'image',
117
+  })
118
+  return customUpload(file, formData, options)
119
+}
120
+
121
+// 删除问题照片
122
+function deleteProblemPhoto(index: number) {
123
+  model.problemPhoto.splice(index, 1)
124
+}
125
+
126
+// 删除处理照片
127
+function deleteHandlingPhoto(index: number) {
128
+  model.handlingPhoto.splice(index, 1)
129
+}
130
+
131
+// 验证截止时间是否大于当前时间
132
+function validateDeadline(value: number | string, resolve: (valid: boolean) => void) {
133
+  const now = Date.now()
134
+  // 检查value类型,如果是字符串则转换为时间戳
135
+  const deadline = typeof value === 'string' ? new Date(value).getTime() : value
136
+  if (deadline < now) {
137
+    showSuccess({ msg: '截止时间不能小于当前时间' })
138
+    resolve(false)
139
+  }
140
+  else {
141
+    resolve(true)
142
+  }
143
+}
144
+function handleSubmit() {
145
+  form.value
146
+    .validate()
147
+    .then(({ valid, errors }) => {
148
+      if (valid) {
149
+        // 准备提交数据
150
+        const submitData = {
151
+          isTransferTask: Number.parseInt(model.isTransferTask),
152
+          problemDescription: model.problemDescription,
153
+          handlingSuggestion: model.handlingSuggestion,
154
+          deadline: dayjs(model.deadline).format('YYYY-MM-DD 23:59:59'),
155
+          executorId: model.executorId?.[0] || '',
156
+          ccTo: model?.ccTo?.join(',') || '',
157
+          tags: model?.tags?.join(',') || '',
158
+          problemPhoto: (model.problemPhoto?.map((item: any) => item.name) || []).join(','),
159
+          correlationTaskId: props.taskId,
160
+          checkItemId: props.checkItemId,
161
+          // 其他字段根据实际情况处理
162
+        }
163
+
164
+        console.log('提交数据:', submitData)
165
+
166
+        // 开始提交,显示 loading
167
+        loading.value = true
168
+
169
+        submitIssue(submitData)
170
+          .then((res) => {
171
+            console.log(res, 'res')
172
+            // 提交成功后向父组件传递数据
173
+            emit('anomaly-submitted', { checkItemId: props.checkItemId, data: submitData })
174
+            // showSuccess({
175
+            //   msg: '校验通过,数据已提交',
176
+            // })
177
+          })
178
+          .catch((error) => {
179
+            console.log(error, 'error')
180
+          })
181
+          .finally(() => {
182
+            // 提交完成,隐藏 loading
183
+            loading.value = false
184
+            emit('update:anomalyShow', false)
185
+          })
186
+      }
187
+    })
188
+    .catch((error) => {
189
+      console.log(error, 'error')
190
+    })
191
+}
192
+
193
+onMounted(() => {
194
+  getTagsList()
195
+  getUserList()
196
+})
197
+
198
+// 监听stationId变化,当stationId从0变为实际值时重新获取用户列表
199
+watch(() => props.stationId, (newStationId) => {
200
+  console.log('stationId变化:', newStationId)
201
+  if (newStationId > 0) {
202
+    getUserList()
203
+  }
204
+}, { immediate: false })
205
+</script>
206
+
207
+<template>
208
+  <view>
209
+    <wd-popup
210
+      v-model="props.anomalyShow"
211
+      custom-style="border-radius: 32rpx 32rpx 0 0; height: 80%;z-index: 100;"
212
+      position="bottom"
213
+      :safe-area-inset-bottom="true"
214
+      closable
215
+      @close="handleClose"
216
+    >
217
+      <view class="popup-content">
218
+        <view class="content-header">
219
+          <text style="font-size: 34rpx; font-weight: 500; color: #464c51">
220
+            异常情况
221
+          </text>
222
+        </view>
223
+        <view class="content-main">
224
+          <wd-form ref="form" :model="model">
225
+            <view style="display: flex; flex-direction: column; gap: 40rpx; padding-bottom: 100rpx;">
226
+              <view>
227
+                <view style="font-size: 32rpx; font-weight: 500; color: #31373d">
228
+                  是否转任务
229
+                </view>
230
+                <view
231
+                  style="
232
+                    font-size: 28rpx;
233
+                    font-weight: 400;
234
+                    color: #4e5969;
235
+                    margin-bottom: 20rpx;
236
+                  "
237
+                >
238
+                  转任务后会出现在执行人的任务日程视图中
239
+                </view>
240
+                <wd-radio-group v-model="model.isTransferTask" shape="dot" inline>
241
+                  <view style="display: flex; gap: 260rpx">
242
+                    <wd-radio value="1">
243
+                      是
244
+                    </wd-radio>
245
+                    <wd-radio value="0">
246
+                      否
247
+                    </wd-radio>
248
+                  </view>
249
+                </wd-radio-group>
250
+              </view>
251
+              <wd-input
252
+                v-model="model.problemDescription"
253
+                label="问题描述"
254
+                label-width="100%"
255
+                prop="problemDescription"
256
+                clearable
257
+                placeholder="请输入"
258
+                :rules="[{ required: true, message: '请填写问题描述' }]"
259
+                custom-class="custom-vertical-input"
260
+              />
261
+              <view class="custom-vertical-select">
262
+                <view
263
+                  style="
264
+                    margin-bottom: 10rpx;
265
+                    font-size: 28rpx;
266
+                    font-weight: 500;
267
+                    color: #333;
268
+                  "
269
+                >
270
+                  标签
271
+                </view>
272
+                <wd-select-picker
273
+                  v-model="model.tags" :columns="tags" filterable
274
+                  :max="1"
275
+                  placeholder="请选择"
276
+                />
277
+              </view>
278
+              <wd-input
279
+                v-model="model.handlingSuggestion"
280
+                label="处理建议"
281
+                label-width="100%"
282
+                prop="handlingSuggestion"
283
+                clearable
284
+                placeholder="请输入"
285
+                custom-class="custom-vertical-input"
286
+              />
287
+              <view class="custom-vertical-select">
288
+                <view
289
+                  style="
290
+                    margin-bottom: 10rpx;
291
+                    font-size: 28rpx;
292
+                    font-weight: 500;
293
+                    color: #333;
294
+                  "
295
+                >
296
+                  <text style="color: red; margin-right: 4rpx; font-size: 28rpx;">* </text>截止时间
297
+                </view>
298
+                <wd-datetime-picker
299
+                  v-model="model.deadline"
300
+                  type="date"
301
+                  use-second
302
+                  :min-date="new Date().getTime()"
303
+                  :max-date="new Date(Date.now() + 365 * 24 * 60 * 60 * 1000).getTime()"
304
+                  prop="deadline"
305
+                  :rules="[{ required: true, message: '请选择截止时间' }]"
306
+                />
307
+              </view>
308
+              <view>
309
+                <view
310
+                  style="
311
+                    margin-bottom: 10rpx;
312
+                    font-size: 28rpx;
313
+                    font-weight: 500;
314
+                    color: #333;
315
+                  "
316
+                >
317
+                  问题照片
318
+                </view>
319
+                <view>
320
+                  <wd-upload
321
+                    v-model:file-list="model.problemPhoto"
322
+                    image-mode="aspectFill"
323
+                    :upload-method="uploadProblemPhoto"
324
+                    @delete="deleteProblemPhoto"
325
+                  />
326
+                </view>
327
+              </view>
328
+              <view class="custom-vertical-select">
329
+                <view
330
+                  style="
331
+                    margin-bottom: 10rpx;
332
+                    font-size: 28rpx;
333
+                    font-weight: 500;
334
+                    color: #333;
335
+                  "
336
+                >
337
+                  <text style="color: red; margin-right: 4rpx; font-size: 28rpx;">* </text>执行人
338
+                </view>
339
+                <wd-select-picker
340
+                  v-model="model.executorId" :columns="users" filterable
341
+                  placeholder="请选择"
342
+                  prop="executorId"
343
+                  :max="1"
344
+                  :rules="[{ required: true, message: '请选择执行人' }]"
345
+                />
346
+              </view>
347
+              <view class="custom-vertical-select">
348
+                <view
349
+                  style="
350
+                    margin-bottom: 10rpx;
351
+                    font-size: 28rpx;
352
+                    font-weight: 500;
353
+                    color: #333;
354
+                  "
355
+                >
356
+                  抄送人
357
+                </view>
358
+                <wd-select-picker
359
+                  v-model="model.ccTo" :columns="users" filterable
360
+                  placeholder="请选择"
361
+                />
362
+              </view>
363
+            </view>
364
+          </wd-form>
365
+        </view>
366
+        <view class="footer">
367
+          <button class="btn" :disabled="loading" @click="handleSubmit">
368
+            <wd-loading v-if="loading" size="20px" color="#ffffff" />
369
+            <text v-else>提 交</text>
370
+          </button>
371
+        </view>
372
+      </view>
373
+    </wd-popup>
374
+    <calendar
375
+      v-model:date-picker-visible="datePickerVisible"
376
+      :detailedtime="false"
377
+      @confirm-date="onconfirmDate"
378
+    />
379
+  </view>
380
+</template>
381
+
382
+<style lang="scss" scoped>
383
+.custom-txt {
384
+  width: 400rpx;
385
+  height: 400rpx;
386
+  display: flex;
387
+  justify-content: center;
388
+  align-items: center;
389
+  border-radius: 32rpx;
390
+}
391
+.popup-content {
392
+  min-height: 500rpx;
393
+  display: flex;
394
+  flex-direction: column;
395
+  align-items: center;
396
+  padding: 30rpx;
397
+  // padding: 0 30rpx 30rpx 30rpx;
398
+  box-sizing: border-box;
399
+  overflow-y: auto;
400
+  .content-header {
401
+    // height: 80rpx;
402
+    width: 100%;
403
+    display: flex;
404
+    justify-content: center;
405
+    align-items: center;
406
+    margin-bottom: 30rpx;
407
+    // position: sticky;
408
+    // top: 50rpx;
409
+    // background-color: #fff;
410
+    // border: 1px solid red;
411
+    // z-index: 50;
412
+    text {
413
+      font-size: 32rpx;
414
+      font-weight: 600;
415
+    }
416
+  }
417
+  .content-main {
418
+    width: 100%;
419
+    .wd-select-picker,
420
+    .wd-datetime-picker {
421
+      width: 100%;
422
+      & .wd-cell {
423
+        padding: 0;
424
+      }
425
+      & .wd-cell__right {
426
+        width: 100%;
427
+        & .wd-cell__value {
428
+          width: 100%;
429
+          & .wd-select-picker__input,
430
+          & .wd-datetime-picker__input {
431
+            width: 100%;
432
+            height: 70rpx;
433
+            border: 1px solid #e5e5e5;
434
+            border-radius: 8rpx;
435
+            padding: 0 20rpx;
436
+            font-size: 28rpx;
437
+          }
438
+        }
439
+      }
440
+    }
441
+    .custom-vertical-select {
442
+      margin-bottom: 30rpx;
443
+    }
444
+    .wd-input {
445
+      // margin-bottom: 30rpx;
446
+      padding: 0px;
447
+      &.custom-vertical-input {
448
+        display: flex;
449
+        flex-direction: column;
450
+        gap: 10rpx;
451
+        margin-bottom: 30rpx;
452
+
453
+        .wd-input__label {
454
+          width: 100% !important;
455
+          margin-bottom: 10rpx;
456
+          font-size: 28rpx;
457
+          font-weight: 500;
458
+          color: #333;
459
+        }
460
+
461
+        .wd-input__body {
462
+          width: 100%;
463
+          position: relative;
464
+        }
465
+
466
+        /* 使用Vue 3深度选择器确保样式能正确应用到输入框 */
467
+        :deep(.wd-input__inner) {
468
+          width: 20rem !important;
469
+          height: 70rpx !important;
470
+          border: 1px solid #e5e5e5 !important;
471
+          border-radius: 8rpx !important;
472
+          padding: 0 60rpx 0 20rpx !important;
473
+          font-size: 28rpx !important;
474
+        }
475
+
476
+        /* 确保图标容器在输入框内部 */
477
+        :deep(.wd-input__suffix) {
478
+          position: absolute;
479
+          right: 20rpx;
480
+          top: 50%;
481
+          transform: translateY(-50%);
482
+          z-index: 1;
483
+        }
484
+      }
485
+    }
486
+  }
487
+}
488
+.footer {
489
+  position: fixed;
490
+  bottom: 0;
491
+  left: 0;
492
+  right: 0;
493
+  padding: 30rpx;
494
+  padding-bottom: calc(30rpx + env(safe-area-inset-bottom));
495
+  background-color: white;
496
+  box-shadow: 0 -2rpx 10rpx rgba(0, 0, 0, 0.05);
497
+  z-index: 10;
498
+  .btn {
499
+    width: 100%;
500
+    height: 80rpx;
501
+    background-color: #215acd;
502
+    color: white;
503
+    font-size: 32rpx;
504
+    border-radius: 12rpx;
505
+    font-weight: 400;
506
+    border: none;
507
+    outline: none;
508
+  }
509
+}
510
+</style>

+ 0 - 374
src/pages/schedule/details/popup/bottompopup.vue

1
-<template>
2
-  <view>
3
-    <wd-popup
4
-      v-model="props.bottomshow"
5
-      custom-style="border-radius: 32rpx 32rpx 0 0; "
6
-      @close="handleClose"
7
-      position="bottom"
8
-    >
9
-      <view class="popup-content">
10
-        <view class="content-header">
11
-          <text style="font-size: 34rpx; font-weight: 500; color: #464c51"
12
-            >异常情况</text
13
-          >
14
-          <wd-icon
15
-            style="position: absolute; right: 20rpx; color: #464c51"
16
-            name="close"
17
-            size="18px"
18
-            @tap="handleClose"
19
-          ></wd-icon>
20
-        </view>
21
-        <view class="content-main">
22
-          <wd-form ref="form" :model="model">
23
-            <view style="display: flex; flex-direction: column; gap: 40rpx">
24
-              <view>
25
-                <view style="font-size: 32rpx; font-weight: 500; color: #31373d"
26
-                  >是否转任务</view
27
-                >
28
-                <view
29
-                  style="
30
-                    font-size: 28rpx;
31
-                    font-weight: 400;
32
-                    color: #4e5969;
33
-                    margin-bottom: 20rpx;
34
-                  "
35
-                >
36
-                  转任务后会出现在执行人的任务日程视图中</view
37
-                >
38
-                <wd-radio-group v-model="model.radioValue" shape="dot" inline>
39
-                  <view style="display: flex; gap: 260rpx">
40
-                    <wd-radio value="1">是</wd-radio>
41
-                    <wd-radio value="2">否</wd-radio>
42
-                  </view>
43
-                </wd-radio-group>
44
-              </view>
45
-              <wd-input
46
-                label="发现问题"
47
-                label-width="100%"
48
-                prop="value1"
49
-                clearable
50
-                v-model="model.value1"
51
-                placeholder="请输入"
52
-                :rules="[{ required: true, message: '请填写发现问题' }]"
53
-                custom-class="custom-vertical-input"
54
-              />
55
-              <wd-input
56
-                label="处理措施"
57
-                label-width="100%"
58
-                prop="value2"
59
-                clearable
60
-                v-model="model.value2"
61
-                placeholder="请输入"
62
-                :rules="[{ required: true, message: '请填写处理措施' }]"
63
-                custom-class="custom-vertical-input"
64
-              />
65
-              <wd-input
66
-                label="处理时间"
67
-                label-width="100%"
68
-                prop="value3"
69
-                clearable
70
-                v-model="model.value3"
71
-                placeholder="请选择"
72
-                :rules="[{ required: true, message: '请填写处理时间' }]"
73
-                custom-class="custom-vertical-input"
74
-                suffix-icon="calendar"
75
-                @click="handleClickCalendar"
76
-              />
77
-              <view>
78
-                <view
79
-                  style="
80
-                    margin-bottom: 10rpx;
81
-                    font-size: 28rpx;
82
-                    font-weight: 500;
83
-                    color: #333;
84
-                  "
85
-                  >拍照</view
86
-                >
87
-                <view>
88
-                  <wd-upload
89
-                    v-model:file-list="model.fileList"
90
-                    image-mode="aspectFill"
91
-                    :action="action"
92
-                  ></wd-upload>
93
-                </view>
94
-              </view>
95
-              <wd-input
96
-                label="执行人"
97
-                label-width="100%"
98
-                prop="value3"
99
-                clearable
100
-                v-model="model.value4"
101
-                placeholder="请选择"
102
-                :rules="[{ required: true, message: '请填写执行人' }]"
103
-                custom-class="custom-vertical-input"
104
-                @click="handleselect"
105
-              />
106
-              <view class="footer">
107
-                <button class="btn" @click="handleSubmit">提 交</button>
108
-              </view>
109
-            </view>
110
-          </wd-form>
111
-        </view>
112
-      </view>
113
-    </wd-popup>
114
-    <calendar
115
-      v-model:datePickerVisible="datePickerVisible"
116
-      @confirmDate="onconfirmDate"
117
-      :detailedtime="false"
118
-    />
119
-    <!-- 执行人选择器 -->
120
-    <wd-popup
121
-      v-model="isexecutorselect"
122
-      custom-style="border-radius: 32rpx 32rpx 0 0;"
123
-      position="bottom"
124
-    >
125
-      <view style="padding: 30rpx; width: 100%; box-sizing: border-box">
126
-        <view
127
-          style="
128
-            display: flex;
129
-            justify-content: space-between;
130
-            align-items: center;
131
-            margin-bottom: 30rpx;
132
-            width: 100%;
133
-          "
134
-        >
135
-          <text
136
-            style="
137
-              font-size: 30rpx;
138
-              color: #333;
139
-              font-weight: 500;
140
-              flex: 1;
141
-              text-align: left;
142
-            "
143
-            @click="isexecutorselect = false"
144
-            >选择执行人</text
145
-          >
146
-        </view>
147
-        <!-- 执行人列表 -->
148
-        <view class="executor-list">
149
-          <view
150
-            v-for="(item, index) in selectcontent"
151
-            :key="index"
152
-            class="executor-item"
153
-            :class="{ selected: model.value4 === item }"
154
-            @click="selectExecutor(item)"
155
-          >
156
-            <text class="executor-name">{{ item }}</text>
157
-            <wd-icon
158
-              v-if="model.value4 === item"
159
-              name="check"
160
-              color="#215acd"
161
-              size="28rpx"
162
-              style="position: absolute; right: 30rpx"
163
-            />
164
-          </view>
165
-        </view>
166
-      </view>
167
-    </wd-popup>
168
-  </view>
169
-</template>
170
-
171
-<script setup lang="ts">
172
-import { ref, reactive, defineProps, defineEmits, computed } from 'vue'
173
-import { useToast } from 'wot-design-uni'
174
-import calendar from '@/utils/calendar.vue'
175
-const props = defineProps({
176
-  bottomshow: {
177
-    type: Boolean,
178
-    default: false,
179
-  },
180
-})
181
-const emit = defineEmits(['update:bottomshow'])
182
-const handleClose = () => {
183
-  emit('update:bottomshow', false)
184
-}
185
-
186
-const { success: showSuccess } = useToast()
187
-
188
-const model = reactive<{
189
-  radioValue: string
190
-  value1: string
191
-  value2: string
192
-  value3: string
193
-  fileList: any[]
194
-  value4: string
195
-}>({
196
-  radioValue: '1',
197
-  value1: '',
198
-  value2: '',
199
-  value3: '',
200
-  fileList: [
201
-    {
202
-      url: 'https://img12.360buyimg.com//n0/jfs/t1/29118/6/4823/55969/5c35c16bE7c262192/c9fdecec4b419355.jpg',
203
-    },
204
-  ],
205
-  value4: '',
206
-})
207
-
208
-const form = ref()
209
-const action: string =
210
-  'https://mockapi.eolink.com/zhTuw2P8c29bc981a741931bdd86eb04dc1e8fd64865cb5/upload'
211
-const isexecutorselect = ref(false)
212
-const selectcontent = [
213
-  '张三',
214
-  '李四',
215
-  '王五',
216
-  '赵六',
217
-  '王二',
218
-  '王三',
219
-  '王四',
220
-  '王五',
221
-  '李飞',
222
-]
223
-const datePickerVisible = ref(false)
224
-//执行人选择
225
-const handleselect = () => {
226
-  isexecutorselect.value = !isexecutorselect.value
227
-}
228
-
229
-// 选择执行人
230
-function selectExecutor(executor: string) {
231
-  model.value4 = executor
232
-  isexecutorselect.value = false
233
-}
234
-function handleClickCalendar() {
235
-  datePickerVisible.value = true
236
-}
237
-const onconfirmDate = (date: string) => {
238
-  model.value3 = date
239
-}
240
-function handleSubmit() {
241
-  form.value
242
-    .validate()
243
-    .then(({ valid, errors }) => {
244
-      if (valid) {
245
-        showSuccess({
246
-          msg: '校验通过',
247
-        })
248
-      }
249
-    })
250
-    .catch((error) => {
251
-      console.log(error, 'error')
252
-    })
253
-}
254
-</script>
255
-<style lang="scss" scoped>
256
-.custom-txt {
257
-  width: 400rpx;
258
-  height: 400rpx;
259
-  display: flex;
260
-  justify-content: center;
261
-  align-items: center;
262
-  border-radius: 32rpx;
263
-}
264
-.popup-content {
265
-  min-height: 500rpx;
266
-  display: flex;
267
-  flex-direction: column;
268
-  align-items: center;
269
-  padding: 30rpx;
270
-  // padding: 0 30rpx 30rpx 30rpx;
271
-  box-sizing: border-box;
272
-  overflow-y: auto;
273
-  .content-header {
274
-    // height: 80rpx;
275
-    width: 100%;
276
-    display: flex;
277
-    justify-content: center;
278
-    align-items: center;
279
-    margin-bottom: 30rpx;
280
-    // position: sticky;
281
-    // top: 50rpx;
282
-    // background-color: #fff;
283
-    // border: 1px solid red;
284
-    // z-index: 50;
285
-    text {
286
-      font-size: 32rpx;
287
-      font-weight: 600;
288
-    }
289
-  }
290
-  .content-main {
291
-    width: 100%;
292
-    .wd-input {
293
-      // margin-bottom: 30rpx;
294
-      padding: 0px;
295
-      &.custom-vertical-input {
296
-        display: flex;
297
-        flex-direction: column;
298
-        gap: 10rpx;
299
-
300
-        .wd-input__label {
301
-          width: 100% !important;
302
-          margin-bottom: 10rpx;
303
-          font-size: 28rpx;
304
-          font-weight: 500;
305
-          color: #333;
306
-        }
307
-
308
-        .wd-input__body {
309
-          width: 100%;
310
-          position: relative;
311
-        }
312
-
313
-        /* 使用Vue 3深度选择器确保样式能正确应用到输入框 */
314
-        :deep(.wd-input__inner) {
315
-          width: 20rem !important;
316
-          height: 70rpx !important;
317
-          border: 1px solid #e5e5e5 !important;
318
-          border-radius: 8rpx !important;
319
-          padding: 0 60rpx 0 20rpx !important;
320
-          font-size: 28rpx !important;
321
-        }
322
-
323
-        /* 确保图标容器在输入框内部 */
324
-        :deep(.wd-input__suffix) {
325
-          position: absolute;
326
-          right: 20rpx;
327
-          top: 50%;
328
-          transform: translateY(-50%);
329
-          z-index: 1;
330
-        }
331
-      }
332
-    }
333
-  }
334
-}
335
-.footer {
336
-  .btn {
337
-    width: 100%;
338
-    height: 80rpx;
339
-    background-color: #215acd;
340
-    color: white;
341
-    font-size: 32rpx;
342
-    border-radius: 12rpx;
343
-    font-weight: 400;
344
-    border: none;
345
-    outline: none;
346
-  }
347
-}
348
-
349
-/* 执行人选择器样式 */
350
-.executor-list {
351
-  max-height: 500rpx;
352
-  overflow-y: auto;
353
-}
354
-
355
-.executor-item {
356
-  display: flex;
357
-  justify-content: center;
358
-  align-items: center;
359
-  padding: 24rpx 0;
360
-  border-bottom: 1rpx solid #f5f5f5;
361
-  font-size: 28rpx;
362
-  color: #333;
363
-  position: relative;
364
-}
365
-
366
-.executor-item.selected {
367
-  color: #215acd;
368
-}
369
-
370
-.executor-name {
371
-  font-size: 28rpx;
372
-  color: inherit;
373
-}
374
-</style>

+ 50 - 31
src/pages/schedule/details/taskdetails/index.vue

2
   <view class="task-management">
2
   <view class="task-management">
3
     <view class="fixed-area">
3
     <view class="fixed-area">
4
       <view class="head-Card">
4
       <view class="head-Card">
5
-        <view class="head-Card-title">未来路加油站</view>
5
+        <view class="head-Card-title">
6
+          未来路加油站
7
+        </view>
6
         <view class="head-Card-tab">
8
         <view class="head-Card-tab">
7
           <view class="head-Card-tab-item">
9
           <view class="head-Card-tab-item">
8
-            <view class="tab-item-num">0</view>
9
-            <view class="tab-item-text">无拍照</view>
10
+            <view class="tab-item-num">
11
+              0
12
+            </view>
13
+            <view class="tab-item-text">
14
+              无拍照
15
+            </view>
10
           </view>
16
           </view>
11
           <view class="head-Card-tab-item">
17
           <view class="head-Card-tab-item">
12
             <view class="tab-item-Middle">
18
             <view class="tab-item-Middle">
13
-              <view class="tab-item-num">0</view>
14
-              <view class="tab-item-text">异常总数</view>
19
+              <view class="tab-item-num">
20
+                0
21
+              </view>
22
+              <view class="tab-item-text">
23
+                异常总数
24
+              </view>
15
             </view>
25
             </view>
16
           </view>
26
           </view>
17
           <view class="head-Card-tab-item">
27
           <view class="head-Card-tab-item">
18
-            <view class="tab-item-num"
19
-              >0 /
28
+            <view class="tab-item-num">
29
+              0 /
20
               <text style="font-size: 24rpx; font-weight: 500">10</text>
30
               <text style="font-size: 24rpx; font-weight: 500">10</text>
21
             </view>
31
             </view>
22
-            <view class="tab-item-text">总得分</view>
32
+            <view class="tab-item-text">
33
+              总得分
34
+            </view>
23
           </view>
35
           </view>
24
         </view>
36
         </view>
25
       </view>
37
       </view>
34
 
46
 
35
     <scroll-view class="task-container" scroll-y>
47
     <scroll-view class="task-container" scroll-y>
36
       <view class="task-top-left">
48
       <view class="task-top-left">
37
-        <text
38
-          >异常数<text style="color: #31373d; font-weight: 600"> 0</text></text
39
-        >
49
+        <text>
50
+          异常数<text style="color: #31373d; font-weight: 600"> 0</text>
51
+        </text>
40
         <text style="color: #31373d; font-weight: 600">|</text>
52
         <text style="color: #31373d; font-weight: 600">|</text>
41
-        <text
42
-          >得分<text style="color: #31373d; font-weight: 600"> 0/0</text></text
43
-        >
53
+        <text>
54
+          得分<text style="color: #31373d; font-weight: 600"> 0/0</text>
55
+        </text>
44
       </view>
56
       </view>
45
       <view class="tasks">
57
       <view class="tasks">
46
         <view
58
         <view
51
           <!-- 任务描述 -->
63
           <!-- 任务描述 -->
52
           <view class="task-description">
64
           <view class="task-description">
53
             <text class="task-number">{{ index + 1 }}.</text>
65
             <text class="task-number">{{ index + 1 }}.</text>
54
-            <text class="task-text"
55
-              >{{ task.description }}
66
+            <text class="task-text">
67
+              {{ task.description }}
56
               <wd-icon
68
               <wd-icon
57
                 v-if="task?.questionicon"
69
                 v-if="task?.questionicon"
58
                 :name="task?.questionicon"
70
                 :name="task?.questionicon"
59
                 size="32rpx"
71
                 size="32rpx"
60
                 @click="() => (popupshow.centershow = true)"
72
                 @click="() => (popupshow.centershow = true)"
61
-              ></wd-icon>
73
+              />
62
             </text>
74
             </text>
63
           </view>
75
           </view>
64
           <!-- 操作说明-图片示例 -->
76
           <!-- 操作说明-图片示例 -->
65
           <view
77
           <view
66
-            class="task-instructions-example"
67
             v-if="task.instructions && task.examples.length > 0"
78
             v-if="task.instructions && task.examples.length > 0"
79
+            class="task-instructions-example"
68
           >
80
           >
69
             <view class="task-instructions">
81
             <view class="task-instructions">
70
               <text class="instructions-text">{{ task.instructions }}</text>
82
               <text class="instructions-text">{{ task.instructions }}</text>
75
                 :key="idx"
87
                 :key="idx"
76
                 class="example-image"
88
                 class="example-image"
77
               >
89
               >
78
-                <image :src="example" mode="aspectFill"></image>
90
+                <image :src="example" mode="aspectFill" />
79
                 <text class="example-text">示例</text>
91
                 <text class="example-text">示例</text>
80
               </view>
92
               </view>
81
             </view>
93
             </view>
87
               :key="idx"
99
               :key="idx"
88
               class="uploaded-image"
100
               class="uploaded-image"
89
             >
101
             >
90
-              <image :src="image" mode="aspectFill"></image>
102
+              <image :src="image" mode="aspectFill" />
91
             </view>
103
             </view>
92
           </view>
104
           </view>
93
           <!-- 几号枪 -->
105
           <!-- 几号枪 -->
94
-          <view class="jihaoqiang" v-if="task.jihaoqiang">
106
+          <view v-if="task.jihaoqiang" class="jihaoqiang">
95
             <text class="jihaoqiang-text">{{ task.jihaoqiang }}</text>
107
             <text class="jihaoqiang-text">{{ task.jihaoqiang }}</text>
96
           </view>
108
           </view>
97
           <!-- 状态 -->
109
           <!-- 状态 -->
98
           <view class="task-status">
110
           <view class="task-status">
99
             <view>
111
             <view>
100
-              <text class="status-text"
101
-                >整体:{{ task.status === 'normal' ? '正常' : '异常' }}</text
102
-              >
112
+              <text class="status-text">
113
+                整体:{{ task.status === 'normal' ? '正常' : '异常' }}
114
+              </text>
103
             </view>
115
             </view>
104
           </view>
116
           </view>
105
-          <view class="exceptiondescription" v-if="task.exceptiondescription">
106
-            <view class="exceptiondescription-title">异常描述:</view>
117
+          <view v-if="task.exceptiondescription" class="exceptiondescription">
118
+            <view class="exceptiondescription-title">
119
+              异常描述:
120
+            </view>
107
             <view class="exceptiondescription-content">
121
             <view class="exceptiondescription-content">
108
               {{ task.exceptiondescription }}
122
               {{ task.exceptiondescription }}
109
             </view>
123
             </view>
115
     <bottompopup v-model:bottomshow="popupshow.bottomshow" />
129
     <bottompopup v-model:bottomshow="popupshow.bottomshow" />
116
     <!-- 确定按钮 -->
130
     <!-- 确定按钮 -->
117
     <view class="confirm-btn">
131
     <view class="confirm-btn">
118
-      <button class="btn" @click="submitTasks">评 论</button>
132
+      <button class="btn" @click="submitTasks">
133
+        评 论
134
+      </button>
119
     </view>
135
     </view>
120
   </view>
136
   </view>
121
 </template>
137
 </template>
138
+
122
 <script lang="ts" setup>
139
 <script lang="ts" setup>
123
-import { ref, computed } from 'vue'
140
+import { computed, ref } from 'vue'
124
 import useUpload from '@/hooks/useUpload'
141
 import useUpload from '@/hooks/useUpload'
125
-import centerpopup from '../popup/centerpopup.vue'
126
 import bottompopup from '../popup/bottompopup.vue'
142
 import bottompopup from '../popup/bottompopup.vue'
143
+import centerpopup from '../popup/centerpopup.vue'
144
+
127
 const popupshow = ref({
145
 const popupshow = ref({
128
   centershow: false,
146
   centershow: false,
129
   bottomshow: false,
147
   bottomshow: false,
218
 
236
 
219
 // 根据当前标签筛选任务
237
 // 根据当前标签筛选任务
220
 const currentTasks = computed(() => {
238
 const currentTasks = computed(() => {
221
-  return tasks.value.filter((task) => task.type === tab.value)
239
+  return tasks.value.filter(task => task.type === tab.value)
222
 })
240
 })
223
 // 提交任务
241
 // 提交任务
224
-const submitTasks = () => {
242
+function submitTasks() {
225
   console.log('提交任务', tasks.value)
243
   console.log('提交任务', tasks.value)
226
   // 这里可以添加提交逻辑
244
   // 这里可以添加提交逻辑
227
   uni.showToast({
245
   uni.showToast({
230
   })
248
   })
231
 }
249
 }
232
 </script>
250
 </script>
251
+
233
 <style lang="scss" scoped>
252
 <style lang="scss" scoped>
234
 .task-management {
253
 .task-management {
235
   display: flex;
254
   display: flex;

+ 0 - 541
src/pages/schedule/details/taskmanagement/index1.vue

1
-<template>
2
-  <view class="task-management">
3
-    <view class="fixed-area">
4
-      <view class="title">
5
-        <text class="title-text">龙飞街加油站</text>
6
-      </view>
7
-      <view>
8
-        <wd-tabs v-model="tab">
9
-          <block v-for="item in tabs" :key="item">
10
-            <wd-tab :title="`${item}`" :name="item" />
11
-          </block>
12
-        </wd-tabs>
13
-      </view>
14
-    </view>
15
-
16
-    <scroll-view class="task-container" scroll-y>
17
-      <view class="tasks">
18
-        <view
19
-          v-for="(task, index) in currentTasks"
20
-          :key="index"
21
-          class="task-item"
22
-        >
23
-          <!-- 任务描述 -->
24
-          <view class="task-description">
25
-            <text class="task-number">{{ index + 1 }}.</text>
26
-            <text class="task-text"
27
-              >{{ task.description }}
28
-              <wd-icon
29
-                v-if="task?.icon"
30
-                :name="task?.icon"
31
-                size="32rpx"
32
-                @click="() => (popupshow.centershow = true)"
33
-              ></wd-icon>
34
-            </text>
35
-          </view>
36
-
37
-          <!-- 操作说明 -->
38
-          <view v-if="task.instructions" class="task-instructions">
39
-            <text class="instructions-text">{{ task.instructions }}</text>
40
-          </view>
41
-
42
-          <!-- 示例图片 -->
43
-          <view v-if="task.examples.length > 0" class="example-images">
44
-            <view
45
-              v-for="(example, idx) in task.examples"
46
-              :key="idx"
47
-              class="example-image"
48
-            >
49
-              <image :src="example" mode="aspectFill"></image>
50
-              <text class="example-text">示例</text>
51
-            </view>
52
-          </view>
53
-
54
-          <!-- 已上传图片 -->
55
-          <view v-if="task.images.length > 0" class="uploaded-images">
56
-            <view
57
-              v-for="(image, idx) in task.images"
58
-              :key="idx"
59
-              class="uploaded-image"
60
-            >
61
-              <image :src="image" mode="aspectFill"></image>
62
-            </view>
63
-          </view>
64
-
65
-          <!-- 上传按钮 -->
66
-          <view
67
-            v-if="task.isimages"
68
-            class="upload-btn"
69
-            @tap="chooseImage(index)"
70
-          >
71
-            <view class="btn-content">
72
-              <text class="btn-icon">+</text>
73
-              <text class="btn-text">拍照</text>
74
-            </view>
75
-          </view>
76
-
77
-          <!-- 状态选择 -->
78
-          <view v-if="!task.details" class="status-select">
79
-            <view class="radio-group">
80
-              <view class="radio-item" @tap="toggleTaskStatus(index)">
81
-                <view
82
-                  class="radio"
83
-                  :class="{ selected: task.status === 'normal' }"
84
-                ></view>
85
-                <text class="radio-text">正常</text>
86
-              </view>
87
-              <view class="radio-item" @tap="toggleTaskStatus(index)">
88
-                <view
89
-                  class="radio"
90
-                  :class="{ selected: task.status === 'abnormal' }"
91
-                ></view>
92
-                <text class="radio-text">异常</text>
93
-              </view>
94
-            </view>
95
-          </view>
96
-
97
-          <!-- 带详情的状态选择 -->
98
-          <view v-if="task.details" class="detail-status">
99
-            <text class="detail-name">{{ task.details[0].name }}</text>
100
-            <view class="radio-group">
101
-              <view class="radio-item" @tap="toggleTaskStatus(index, 0)">
102
-                <view
103
-                  class="radio"
104
-                  :class="{ selected: task.details[0].status === 'normal' }"
105
-                ></view>
106
-                <text class="radio-text">正常</text>
107
-              </view>
108
-              <view class="radio-item" @tap="toggleTaskStatus(index, 0)">
109
-                <view
110
-                  class="radio"
111
-                  :class="{ selected: task.details[0].status === 'abnormal' }"
112
-                ></view>
113
-                <text class="radio-text">异常</text>
114
-              </view>
115
-            </view>
116
-            <view class="detail-items">
117
-              <view
118
-                v-for="(detail, idx) in task.details.slice(1)"
119
-                :key="idx"
120
-                class="detail-item"
121
-              >
122
-                <view class="detail-name">{{ detail.name }}</view>
123
-                <view
124
-                  class="radio-item"
125
-                  @tap="toggleTaskStatus(index, idx + 1)"
126
-                >
127
-                  <view
128
-                    class="radio"
129
-                    :class="{ selected: detail.status === 'normal' }"
130
-                  ></view>
131
-                  <text class="radio-text">正常</text>
132
-                </view>
133
-                <view
134
-                  class="radio-item"
135
-                  @tap="toggleTaskStatus(index, idx + 1)"
136
-                >
137
-                  <view
138
-                    class="radio"
139
-                    :class="{ selected: detail.status === 'abnormal' }"
140
-                  ></view>
141
-                  <text class="radio-text">异常</text>
142
-                </view>
143
-              </view>
144
-            </view>
145
-          </view>
146
-        </view>
147
-      </view>
148
-    </scroll-view>
149
-    <centerpopup v-model:centershow="popupshow.centershow" />
150
-    <bottompopup v-model:bottomshow="popupshow.bottomshow" />
151
-    <!-- 确定按钮 -->
152
-    <view class="confirm-btn">
153
-      <button class="btn" @click="submitTasks">确 定</button>
154
-    </view>
155
-  </view>
156
-</template>
157
-<script lang="ts" setup>
158
-import { ref, computed } from 'vue'
159
-import useUpload from '@/hooks/useUpload'
160
-import centerpopup from '../popup/centerpopup.vue'
161
-import bottompopup from '../popup/bottompopup.vue'
162
-const popupshow = ref({
163
-  centershow: false,
164
-  bottomshow: false,
165
-})
166
-// const popupshow = ref({})
167
-const tab = ref('前庭')
168
-const tabs = ['营销', '出入口', '前庭', '便利店', '洗手间']
169
-
170
-// 定义任务类型
171
-interface Task {
172
-  id: string
173
-  type: string
174
-  description: string
175
-  icon?: string
176
-  instructions: string
177
-  examples: string[]
178
-  images: string[]
179
-  status: 'normal' | 'abnormal'
180
-  isimages: boolean
181
-  details?: {
182
-    name: string
183
-    status: 'normal' | 'abnormal'
184
-  }[]
185
-}
186
-
187
-// 任务数据
188
-const tasks = ref<Task[]>([
189
-  {
190
-    id: '1',
191
-    type: '前庭',
192
-    description:
193
-      '[AI] [卫生] 前庭海报整齐不歪斜,干净无灰尘污渍(立柱、灯箱、展架等)。',
194
-    instructions: '拍照:按照示例图拍摄前庭内海报宣传物,可上传多张照片',
195
-    examples: [
196
-      'https://picsum.photos/id/1018/200/150',
197
-      'https://picsum.photos/id/1019/200/150',
198
-    ],
199
-    images: [],
200
-    isimages: true,
201
-    status: 'normal',
202
-  },
203
-  {
204
-    id: '2',
205
-    type: '前庭',
206
-    description: '[卫生] 抽油枪栓是否出现空转或存在明显渗油。',
207
-    icon: 'help-circle',
208
-    instructions: '',
209
-    examples: [],
210
-    images: [],
211
-    isimages: false,
212
-    status: 'normal',
213
-    details: [{ name: '12号枪', status: 'normal' }],
214
-  },
215
-  {
216
-    id: '3',
217
-    type: '前庭',
218
-    description:
219
-      '[AI] [卫生] 前庭海报整齐不歪斜,干净无灰尘污渍(立柱、灯箱、展架等)。',
220
-    instructions: '拍照:按照示例图拍摄前庭内海报宣传物,可上传多张照片',
221
-    examples: [
222
-      'https://picsum.photos/id/1018/200/150',
223
-      'https://picsum.photos/id/1019/200/150',
224
-    ],
225
-    images: ['https://picsum.photos/id/1025/200/150'],
226
-    isimages: true,
227
-    status: 'normal',
228
-  },
229
-])
230
-
231
-// 根据当前标签筛选任务
232
-const currentTasks = computed(() => {
233
-  return tasks.value.filter((task) => task.type === tab.value)
234
-})
235
-
236
-// 选择图片
237
-const chooseImage = (index: number) => {
238
-  // 为每个上传请求创建独立的upload实例,确保能正确处理对应的任务索引
239
-  const { run: uploadImage } = useUpload({
240
-    fileType: 'image',
241
-    success: (res) => {
242
-      console.log('上传成功', res)
243
-      // 将上传成功的图片URL添加到对应任务的images数组中
244
-      // 假设res包含图片URL,实际结构需要根据后端返回调整
245
-      const imageUrl = typeof res === 'string' ? res : res.url || ''
246
-      if (imageUrl) {
247
-        tasks.value[index].images.push(imageUrl)
248
-      }
249
-    },
250
-    error: (err) => {
251
-      console.error('上传失败', err)
252
-    },
253
-  })
254
-  uploadImage()
255
-}
256
-
257
-// 切换任务状态
258
-const toggleTaskStatus = (taskIndex: number, detailIndex?: number) => {
259
-  const task = tasks.value[taskIndex]
260
-  if (detailIndex !== undefined && task.details) {
261
-    // 有子项的任务
262
-    const detail = task.details[detailIndex]
263
-    detail.status = detail.status === 'normal' ? 'abnormal' : 'normal'
264
-    // 检查是否有任一子项异常
265
-    task.status = task.details.some((d) => d.status === 'abnormal')
266
-      ? 'abnormal'
267
-      : 'normal'
268
-  } else {
269
-    // 无子项的任务
270
-    task.status = task.status === 'normal' ? 'abnormal' : 'normal'
271
-  }
272
-  if (task.status === 'abnormal') {
273
-    popupshow.value.bottomshow = true
274
-  }
275
-}
276
-
277
-// 提交任务
278
-const submitTasks = () => {
279
-  console.log('提交任务', tasks.value)
280
-  // 这里可以添加提交逻辑
281
-  uni.showToast({
282
-    title: '提交成功',
283
-    icon: 'success',
284
-  })
285
-}
286
-// 关闭弹窗按钮
287
-// const handleClose = () => {
288
-//   popupshow.value = false
289
-// }
290
-</script>
291
-<style lang="scss" scoped>
292
-.task-management {
293
-  display: flex;
294
-  flex-direction: column;
295
-  height: 1500rpx;
296
-  overflow: hidden;
297
-  .fixed-area {
298
-    flex-shrink: 0; // 不让它被压缩
299
-    background-color: #fff;
300
-    position: sticky; // 若想随外层滚就 sticky;想完全固定用 fixed 即可
301
-    top: 0;
302
-    z-index: 10;
303
-  }
304
-  .title {
305
-    width: 100%;
306
-    height: 128rpx;
307
-    background-color: #4a80c3;
308
-    display: flex;
309
-    align-items: center;
310
-    box-sizing: border-box;
311
-    padding-left: 46rpx;
312
-  }
313
-
314
-  .title-text {
315
-    font-size: 36rpx;
316
-    font-weight: 500;
317
-    color: #fff;
318
-  }
319
-
320
-  // // 任务容器
321
-  // .task-container {
322
-  //   flex: 1;
323
-  //   padding: 0 30rpx;
324
-
325
-  //   .tasks {
326
-  //     padding: 20rpx 0;
327
-  //   }
328
-  // }
329
-  .task-container {
330
-    flex: 1; // 占剩余高度
331
-    overflow-y: auto; // 仅这里滚动
332
-    padding: 0 30rpx;
333
-  }
334
-  // 任务项
335
-  .task-item {
336
-    margin-bottom: 40rpx;
337
-
338
-    // 任务描述
339
-    .task-description {
340
-      display: flex;
341
-      margin-bottom: 16rpx;
342
-
343
-      .task-number {
344
-        font-size: 28rpx;
345
-        color: #333;
346
-        margin-right: 8rpx;
347
-        font-weight: 500;
348
-      }
349
-
350
-      .task-text {
351
-        font-size: 28rpx;
352
-        font-weight: 400;
353
-        color: #333;
354
-        line-height: 44rpx;
355
-      }
356
-    }
357
-
358
-    // 操作说明
359
-    .task-instructions {
360
-      margin-bottom: 20rpx;
361
-
362
-      .instructions-text {
363
-        font-size: 24rpx;
364
-        color: #666;
365
-        line-height: 36rpx;
366
-      }
367
-    }
368
-
369
-    // 示例图片
370
-    .example-images {
371
-      display: flex;
372
-      gap: 16rpx;
373
-      margin-bottom: 20rpx;
374
-
375
-      .example-image {
376
-        width: 140rpx;
377
-        height: 100rpx;
378
-        position: relative;
379
-
380
-        image {
381
-          width: 100%;
382
-          height: 100%;
383
-          border-radius: 8rpx;
384
-        }
385
-
386
-        .example-text {
387
-          position: absolute;
388
-          bottom: 0;
389
-          left: 0;
390
-          right: 0;
391
-          background-color: rgba(0, 0, 0, 0.5);
392
-          color: white;
393
-          font-size: 20rpx;
394
-          text-align: center;
395
-          padding: 4rpx 0;
396
-          border-radius: 0 0 8rpx 8rpx;
397
-        }
398
-      }
399
-    }
400
-
401
-    // 已上传图片
402
-    .uploaded-images {
403
-      display: flex;
404
-      gap: 16rpx;
405
-      margin-bottom: 20rpx;
406
-
407
-      .uploaded-image {
408
-        width: 140rpx;
409
-        height: 100rpx;
410
-
411
-        image {
412
-          width: 100%;
413
-          height: 100%;
414
-          border-radius: 8rpx;
415
-        }
416
-      }
417
-    }
418
-
419
-    // 上传按钮
420
-    .upload-btn {
421
-      width: 140rpx;
422
-      height: 100rpx;
423
-      border: 2rpx dashed #ccc;
424
-      border-radius: 8rpx;
425
-      display: flex;
426
-      align-items: center;
427
-      justify-content: center;
428
-      margin-bottom: 24rpx;
429
-
430
-      .btn-content {
431
-        display: flex;
432
-        flex-direction: column;
433
-        align-items: center;
434
-
435
-        .btn-icon {
436
-          font-size: 40rpx;
437
-          color: #999;
438
-          line-height: 40rpx;
439
-        }
440
-
441
-        .btn-text {
442
-          font-size: 24rpx;
443
-          color: #999;
444
-          margin-top: 4rpx;
445
-        }
446
-      }
447
-    }
448
-
449
-    // 状态选择
450
-    .status-select {
451
-      margin-bottom: 20rpx;
452
-
453
-      .radio-group {
454
-        display: flex;
455
-        gap: 40rpx;
456
-      }
457
-    }
458
-
459
-    // 带详情的状态选择
460
-    .detail-status {
461
-      margin-bottom: 20rpx;
462
-
463
-      .detail-name {
464
-        font-size: 28rpx;
465
-        color: #333;
466
-        margin-right: 20rpx;
467
-        // margin-bottom: 10rpx;
468
-      }
469
-
470
-      .radio-group {
471
-        display: flex;
472
-        gap: 40rpx;
473
-      }
474
-
475
-      .detail-items {
476
-        margin-top: 16rpx;
477
-        display: flex;
478
-        flex-wrap: wrap;
479
-        gap: 40rpx;
480
-      }
481
-    }
482
-
483
-    // 单选按钮
484
-    .radio-group {
485
-      .radio-item {
486
-        // margin-top: 50rpx;
487
-        display: flex;
488
-        align-items: center;
489
-        gap: 8rpx;
490
-
491
-        .radio {
492
-          width: 32rpx;
493
-          height: 32rpx;
494
-          border: 2rpx solid #ccc;
495
-          border-radius: 50%;
496
-          display: flex;
497
-          align-items: center;
498
-          justify-content: center;
499
-
500
-          &.selected {
501
-            border-color: #215acd;
502
-            // background-color: #4a80c3;
503
-
504
-            &::after {
505
-              content: '';
506
-              width: 16rpx;
507
-              height: 16rpx;
508
-              border-radius: 50%;
509
-              background-color: #215acd;
510
-            }
511
-          }
512
-        }
513
-
514
-        .radio-text {
515
-          font-size: 28rpx;
516
-          color: #333;
517
-        }
518
-      }
519
-    }
520
-  }
521
-
522
-  // 确定按钮
523
-  .confirm-btn {
524
-    padding: 20rpx 30rpx;
525
-    background-color: white;
526
-    border-top: 1rpx solid #eee;
527
-
528
-    .btn {
529
-      width: 100%;
530
-      height: 80rpx;
531
-      background-color: #215acd;
532
-      color: white;
533
-      font-size: 32rpx;
534
-      border-radius: 12rpx;
535
-      font-weight: 400;
536
-      border: none;
537
-      outline: none;
538
-    }
539
-  }
540
-}
541
-</style>

+ 55 - 0
src/utils/dicts.ts

1
+/**
2
+ * 题目类型
3
+ */
4
+export const questionTypes = [
5
+  { label: '单选题', value: 'single_choice' },
6
+  { label: '多选题', value: 'multiple_choice' },
7
+  { label: '填空题', value: 'fill_blank' },
8
+  { label: '简答题', value: 'essay' },
9
+  { label: '判断题', value: 'judgment' },
10
+]
11
+
12
+// 试题难度选项 easy-简单、medium-中等、hard-困难
13
+export const difficultyOptions = [
14
+  { label: '简单', value: 'easy', color: '' },
15
+  { label: '中等', value: 'medium', color: 'yellow' },
16
+  { label: '困难', value: 'hard', color: 'red' },
17
+]
18
+
19
+// p=pending-待发布、ongoing-已发布、finished-已结束、cancelled-已关闭
20
+export const examStatusOptions = [
21
+  { label: '已发布', value: 'ongoing', color: 'success' },
22
+  { label: '待发布', value: 'pending', color: 'primary' },
23
+  { label: '已关闭', value: 'cancelled', color: 'danger' },
24
+  { label: '已结束', value: 'finished', color: 'info' },
25
+]
26
+
27
+export const reviewStatusOptions = [
28
+  { label: '待批改', value: 'unreviewed', color: '#EB5E12' },
29
+  { label: '批改中', value: 'reviewing', color: '#FFC107' },
30
+  { label: '已批改', value: 'reviewed', color: '#38B865' },
31
+]
32
+
33
+export const taskLevelOptions = [
34
+  { label: '常规', value: 0, color: '#409EFF' },
35
+  { label: '选做', value: 1, color: '#E6A23C' },
36
+  { label: '必做', value: 2, color: '#67C23A' },
37
+]
38
+
39
+export const taskStatusOptions: any = [
40
+  { label: '已取消', value: 0, color: '#409EFF' },
41
+  { label: '待处理', value: 1, color: '#E6A23C' },
42
+  { label: '超期未处理', value: 2, color: '#F56C6C' },
43
+  { label: '已处理', value: 3, color: '#909399' },
44
+]
45
+
46
+export const authStatusOptions = [
47
+  { label: '可授权', value: 0, color: '#215ACD' },
48
+  { label: '已授权', value: 1, color: '#339169' },
49
+  { label: '被授权', value: 2, color: '#EB5E12' },
50
+]
51
+
52
+export function getLabel(dict: any[], value: string) {
53
+  const item = dict.find(item => item.value === value)
54
+  return item?.label || value
55
+}

+ 14 - 12
vite.config.ts

67
     plugins: [
67
     plugins: [
68
       UniLayouts(),
68
       UniLayouts(),
69
       UniPlatform(),
69
       UniPlatform(),
70
-      UniManifest(),
70
+      // 暂时禁用 UniManifest 插件,以解决权限错误
71
+      // UniManifest(),
71
       UniEcharts(),
72
       UniEcharts(),
72
       UniPages({
73
       UniPages({
73
         exclude: ['**/components/**/**.*'],
74
         exclude: ['**/components/**/**.*'],
79
         dts: 'src/types/uni-pages.d.ts',
80
         dts: 'src/types/uni-pages.d.ts',
80
       }),
81
       }),
81
       // Optimization 插件需要 page.json 文件,故应在 UniPages 插件之后执行
82
       // Optimization 插件需要 page.json 文件,故应在 UniPages 插件之后执行
82
-      Optimization({
83
-        enable: {
84
-          optimization: true,
85
-          'async-import': true,
86
-          'async-component': true,
87
-        },
88
-        dts: {
89
-          base: 'src/types',
90
-        },
91
-        logger: false,
92
-      }),
83
+      // 暂时禁用 Optimization 插件,以解决导入错误
84
+      // Optimization({
85
+      //   enable: {
86
+      //     optimization: true,
87
+      //     'async-import': true,
88
+      //     'async-component': true,
89
+      //   },
90
+      //   dts: {
91
+      //     base: 'src/types',
92
+      //   },
93
+      //   logger: false,
94
+      // }),
93
       // UniXXX 需要在 Uni 之前引入
95
       // UniXXX 需要在 Uni 之前引入
94
       // 若存在改变 pages.json 的插件,请将 UniKuRoot 放置其后
96
       // 若存在改变 pages.json 的插件,请将 UniKuRoot 放置其后
95
       UniKuRoot({
97
       UniKuRoot({