Browse Source

mod: 任务详情

闪电 3 weeks ago
parent
commit
e1f078bd60

+ 17 - 6
src/api/schedule/task.ts

@@ -3,18 +3,16 @@ import { http } from '@/http/http'
3 3
 enum Api {
4 4
   base = '/scheduleTasks/tasks',
5 5
   confirmBase = '/scheduleTasks/tasks/confirm',
6
+  uploadBase = '/taskAttachment/attachment',
7
+  photoBase = '/taskPhoto/photo',
6 8
 }
7 9
 
8 10
 export function transferTask(data: any) {
9
-  return http.put(`${Api.base}/forward`, data, {
10
-    successMessageMode: 'message',
11
-  })
11
+  return http.put(`${Api.base}/forward`, data)
12 12
 }
13 13
 // 关闭任务
14 14
 export function closeTask(data: any) {
15
-  return http.put(`${Api.base}/cancel`, data, {
16
-    successMessageMode: 'message',
17
-  })
15
+  return http.put(`${Api.base}/cancel`, data)
18 16
 }
19 17
 
20 18
 export function getTaskDetail(id: number) {
@@ -29,3 +27,16 @@ export function getTaskDetail(id: number) {
29 27
 export function confirmDoneTask(id: number) {
30 28
   return http.put(`${Api.confirmBase}/${id}`)
31 29
 }
30
+
31
+/**
32
+ * 处理任务附件
33
+ * @param params
34
+ * @returns
35
+ */
36
+export function dealUploadTask(params: any) {
37
+  return http.put(`${Api.uploadBase}/handle`, params)
38
+}
39
+
40
+export function dealPhotoTask(params: any) {
41
+  return http.put(`${Api.photoBase}/handle`, params)
42
+}

+ 212 - 0
src/components/AttachmentList.vue

@@ -0,0 +1,212 @@
1
+<script setup lang="ts">
2
+import { computed } from 'vue'
3
+
4
+// 组件属性定义
5
+interface Props {
6
+  files: string[] // 文件URL数组
7
+  fileNames?: string[] // 文件名数组,可选
8
+  size?: number // 卡片大小(默认50)
9
+}
10
+
11
+const props = withDefaults(defineProps<Props>(), {
12
+  fileNames: () => [],
13
+  size: 50,
14
+})
15
+
16
+// 图片文件扩展名
17
+const imageExtensions = ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp']
18
+
19
+/**
20
+ * 获取文件名
21
+ * @param url 文件URL
22
+ * @param index 文件索引
23
+ * @returns 文件名
24
+ */
25
+function getFileName(url: string, index: number): string {
26
+  if (props.fileNames && props.fileNames[index]) {
27
+    return props.fileNames[index]
28
+  }
29
+  // 从URL中提取文件名
30
+  const parts = url.split('/')
31
+  return parts[parts.length - 1] || `file${index + 1}`
32
+}
33
+
34
+/**
35
+ * 判断是否为图片文件
36
+ * @param fileName 文件名
37
+ * @returns 是否为图片
38
+ */
39
+function isImage(fileName: string): boolean {
40
+  const extension = fileName.split('.').pop()?.toLowerCase()
41
+  return extension ? imageExtensions.includes(extension) : false
42
+}
43
+
44
+/**
45
+ * 获取文件扩展名
46
+ * @param fileName 文件名
47
+ * @returns 文件扩展名(大写)
48
+ */
49
+function getFileExtension(fileName: string): string {
50
+  const extension = fileName.split('.').pop()?.toUpperCase()
51
+  return extension || 'FILE'
52
+}
53
+
54
+/**
55
+ * 处理文件下载
56
+ * @param url 文件URL
57
+ * @param fileName 文件名
58
+ */
59
+function handleFileDownload(url: string, fileName: string) {
60
+  // #ifdef H5
61
+  // H5平台使用a标签下载
62
+  const link = document.createElement('a')
63
+  link.href = url
64
+  link.download = fileName
65
+  link.target = '_blank'
66
+  document.body.appendChild(link)
67
+  link.click()
68
+  document.body.removeChild(link)
69
+  // #endif
70
+
71
+  // #ifndef H5
72
+  // 非H5平台使用uni.downloadFile
73
+  uni.downloadFile({
74
+    url,
75
+    success: (res) => {
76
+      if (res.statusCode === 200) {
77
+        uni.saveFile({
78
+          tempFilePath: res.tempFilePath,
79
+          success: (saveRes) => {
80
+            uni.showToast({
81
+              title: '下载成功',
82
+              icon: 'success',
83
+            })
84
+          },
85
+          fail: (err) => {
86
+            console.error('保存文件失败:', err)
87
+            uni.showToast({
88
+              title: '保存文件失败',
89
+              icon: 'error',
90
+            })
91
+          },
92
+        })
93
+      }
94
+    },
95
+    fail: (err) => {
96
+      console.error('下载文件失败:', err)
97
+      uni.showToast({
98
+        title: '下载文件失败',
99
+        icon: 'error',
100
+      })
101
+    },
102
+  })
103
+  // #endif
104
+}
105
+</script>
106
+
107
+<template>
108
+  <view class="attachment-list">
109
+    <view
110
+      v-for="(file, index) in files"
111
+      :key="index"
112
+      class="attachment-item"
113
+      :style="{ width: `${size * 4}rpx`, height: `${size * 4}rpx` }"
114
+    >
115
+      <!-- 图片文件 -->
116
+      <view v-if="isImage(getFileName(file, index))" class="image-container">
117
+        <wd-img
118
+          :width="size * 4"
119
+          :height="size * 4"
120
+          :src="file"
121
+          :enable-preview="true"
122
+        />
123
+      </view>
124
+
125
+      <!-- 其他文件 -->
126
+      <view
127
+        v-else
128
+        class="file-container"
129
+        @tap="handleFileDownload(file, getFileName(file, index))"
130
+      >
131
+        <view class="file-icon">
132
+          <wd-icon name="document" size="64rpx" />
133
+        </view>
134
+        <view class="file-extension">
135
+          {{ getFileExtension(getFileName(file, index)) }}
136
+        </view>
137
+      </view>
138
+    </view>
139
+  </view>
140
+</template>
141
+
142
+<style lang="scss" scoped>
143
+.attachment-list {
144
+  width: 100%;
145
+  display: flex;
146
+  overflow-x: auto;
147
+  padding: 10rpx 0;
148
+  scrollbar-width: thin;
149
+  scrollbar-color: #c1c1c1 #f5f5f5;
150
+
151
+  &::-webkit-scrollbar {
152
+    height: 6rpx;
153
+  }
154
+
155
+  &::-webkit-scrollbar-track {
156
+    background: #f5f5f5;
157
+    border-radius: 3rpx;
158
+  }
159
+
160
+  &::-webkit-scrollbar-thumb {
161
+    background: #c1c1c1;
162
+    border-radius: 3rpx;
163
+  }
164
+
165
+  &::-webkit-scrollbar-thumb:hover {
166
+    background: #a8a8a8;
167
+  }
168
+}
169
+
170
+.attachment-item {
171
+  margin-right: 20rpx;
172
+  border-radius: 12rpx;
173
+  overflow: hidden;
174
+  box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1);
175
+  transition: transform 0.3s, box-shadow 0.3s;
176
+  flex-shrink: 0;
177
+  
178
+  &:hover {
179
+    transform: translateY(-4rpx);
180
+    box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.15);
181
+  }
182
+}
183
+
184
+.image-container {
185
+  width: 100%;
186
+  height: 100%;
187
+  overflow: hidden;
188
+}
189
+
190
+.file-container {
191
+  width: 100%;
192
+  height: 100%;
193
+  display: flex;
194
+  flex-direction: column;
195
+  align-items: center;
196
+  justify-content: center;
197
+  background-color: #f5f7fa;
198
+  cursor: pointer;
199
+}
200
+
201
+.file-icon {
202
+  margin-bottom: 16rpx;
203
+  color: #215acd;
204
+}
205
+
206
+.file-extension {
207
+  font-size: 24rpx;
208
+  font-weight: 500;
209
+  color: #31373d;
210
+  text-transform: uppercase;
211
+}
212
+</style>

+ 27 - 19
src/pages/schedule/details/deal/index2.vue

@@ -9,7 +9,7 @@
9 9
             style="margin-left: 10rpx; transform: translateY(6rpx)"
10 10
             name="arrow-down"
11 11
             size="20px"
12
-          ></wd-icon>
12
+          />
13 13
         </text>
14 14
       </view>
15 15
       <view>
@@ -22,32 +22,33 @@
22 22
     </view>
23 23
     <scroll-view class="task-container" scroll-y>
24 24
       <view class="content-main">
25
-        <view style="font-size: 36rpx; font-weight: 500; margin-bottom: 50rpx"
26
-          >油品:0#</view
27
-        >
25
+        <view style="font-size: 36rpx; font-weight: 500; margin-bottom: 50rpx">
26
+          油品:0#
27
+        </view>
28 28
         <wd-form ref="form" :model="model">
29 29
           <view style="display: flex; flex-direction: column; gap: 90rpx">
30 30
             <view>
31 31
               <view
32 32
                 style="font-size: 32rpx; font-weight: 500; margin-bottom: 30rpx"
33
-                >液位仪数据</view
34 33
               >
34
+                液位仪数据
35
+              </view>
35 36
               <wd-input
37
+                v-model="model.value1"
36 38
                 label="油高 h1(mm)"
37 39
                 label-width="100%"
38 40
                 prop="value1"
39 41
                 clearable
40
-                v-model="model.value1"
41 42
                 placeholder="请输入"
42 43
                 :rules="[{ required: true, message: '请填写发现问题' }]"
43 44
                 custom-class="custom-vertical-input"
44 45
               />
45 46
               <wd-input
47
+                v-model="model.value2"
46 48
                 label="水高(mm)"
47 49
                 label-width="100%"
48 50
                 prop="value2"
49 51
                 clearable
50
-                v-model="model.value2"
51 52
                 placeholder="请输入"
52 53
                 :rules="[{ required: true, message: '请填写处理措施' }]"
53 54
                 custom-class="custom-vertical-input"
@@ -56,34 +57,35 @@
56 57
             <view>
57 58
               <view
58 59
                 style="font-size: 32rpx; font-weight: 500; margin-bottom: 30rpx"
59
-                >手工量缸数据</view
60 60
               >
61
+                手工量缸数据
62
+              </view>
61 63
               <wd-input
64
+                v-model="model.value1"
62 65
                 label="油高 h1(mm)"
63 66
                 label-width="100%"
64 67
                 prop="value1"
65 68
                 clearable
66
-                v-model="model.value1"
67 69
                 placeholder="请输入"
68 70
                 :rules="[{ required: true, message: '请填写发现问题' }]"
69 71
                 custom-class="custom-vertical-input"
70 72
               />
71 73
               <wd-input
74
+                v-model="model.value2"
72 75
                 label="水高(mm)"
73 76
                 label-width="100%"
74 77
                 prop="value2"
75 78
                 clearable
76
-                v-model="model.value2"
77 79
                 placeholder="请输入"
78 80
                 :rules="[{ required: true, message: '请填写处理措施' }]"
79 81
                 custom-class="custom-vertical-input"
80 82
               />
81 83
               <wd-input
84
+                v-model="model.value2"
82 85
                 label="水高高度差(mm)(h1-h2)"
83 86
                 label-width="100%"
84 87
                 prop="value2"
85 88
                 clearable
86
-                v-model="model.value2"
87 89
                 placeholder="请输入"
88 90
                 :rules="[{ required: true, message: '请填写处理措施' }]"
89 91
                 custom-class="custom-vertical-input"
@@ -92,11 +94,12 @@
92 94
             <view>
93 95
               <view
94 96
                 style="font-size: 32rpx; font-weight: 500; margin-bottom: 30rpx"
95
-                >异常记录</view
96 97
               >
98
+                异常记录
99
+              </view>
97 100
               <wd-textarea
98
-                prop="value1"
99 101
                 v-model="model.value1"
102
+                prop="value1"
100 103
                 placeholder="请填写异常记录"
101 104
                 :rules="[{ required: true, message: '请填写异常记录' }]"
102 105
                 :auto-height="false"
@@ -110,19 +113,23 @@
110 113
     </scroll-view>
111 114
     <!-- 确定按钮 -->
112 115
     <view class="confirm-btn">
113
-      <button class="btn" @click="submitTasks">确 定</button>
116
+      <button class="btn" @click="submitTasks">
117
+        确 定
118
+      </button>
114 119
     </view>
115 120
     <calendar
116
-      v-model:datePickerVisible="datePickerVisible"
117
-      @confirmDate="onconfirmDate"
121
+      v-model:date-picker-visible="datePickerVisible"
118 122
       :detailedtime="true"
123
+      @confirm-date="onconfirmDate"
119 124
     />
120 125
   </view>
121 126
 </template>
127
+
122 128
 <script lang="ts" setup>
123
-import { ref, reactive, onMounted } from 'vue'
129
+import { onMounted, reactive, ref } from 'vue'
124 130
 import { useToast } from 'wot-design-uni'
125 131
 import calendar from '@/utils/calendar.vue'
132
+
126 133
 const tab = ref('1缸')
127 134
 const tabs = ['1缸', '2缸', '3缸', '4缸']
128 135
 const { success: showSuccess } = useToast()
@@ -140,10 +147,10 @@ function formatDateTime() {
140 147
 
141 148
   currentDateTime.value = `${year}-${month}-${day} ${hours}:${minutes}`
142 149
 }
143
-const handleClickCalendar = () => {
150
+function handleClickCalendar() {
144 151
   datePickerVisible.value = true
145 152
 }
146
-const onconfirmDate = (date: string) => {
153
+function onconfirmDate(date: string) {
147 154
   currentDateTime.value = date
148 155
 }
149 156
 onMounted(() => {
@@ -172,6 +179,7 @@ function submitTasks() {
172 179
     })
173 180
 }
174 181
 </script>
182
+
175 183
 <style lang="scss" scoped>
176 184
 .task-management {
177 185
   display: flex;

+ 69 - 93
src/pages/schedule/details/deal/inspection.vue

@@ -1,23 +1,21 @@
1 1
 <script lang="ts" setup>
2
-import { computed, onMounted, ref } from 'vue'
3
-import { useRoute, useRouter } from 'vue-router'
2
+import { cloneDeep, isArray } from 'lodash-es'
3
+import { onMounted, ref } from 'vue'
4
+import { useRoute } from 'vue-router'
4 5
 import { getCheckSubItemsByTaskId, getChecktItemsByTaskId, handleCheckTask, submitCheckLogs } from '@/api/schedule/check'
5 6
 import { getTaskDetail } from '@/api/schedule/task'
6 7
 import useUpload from '@/hooks/useUpload'
7 8
 import anomalyPop from '../popup/anomaly.vue'
8
-import centerpopup from '../popup/centerpopup.vue'
9 9
 
10 10
 const route = useRoute()
11 11
 const taskId = ref(Number(route.query.id))
12 12
 const checkId = ref(Number(route.query.checkId))
13
-
13
+const anomalyPopRef = ref<any>(null)
14 14
 const popShow = ref({
15 15
   centerShow: false,
16 16
   anomalyShow: false,
17 17
 })
18 18
 // const popShow = ref({})
19
-const tab = ref('前庭')
20
-
21 19
 const taskInfo: any = ref({})
22 20
 const checkSubItems: any = ref([])
23 21
 
@@ -41,50 +39,6 @@ interface Task {
41 39
   }[]
42 40
 }
43 41
 
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 42
 // 选择图片
89 43
 function chooseImage(index: number) {
90 44
   // 为每个上传请求创建独立的upload实例,确保能正确处理对应的任务索引
@@ -95,11 +49,11 @@ function chooseImage(index: number) {
95 49
       const imageUrl = res.url || ''
96 50
       if (imageUrl) {
97 51
         console.log('currentCheckList.value[index]', currentCheckList.value[index], imageUrl)
98
-        if (!currentCheckList.value[index]?.dealPhoto) {
99
-          currentCheckList.value[index].dealPhoto = []
52
+        if (!currentCheckList.value[index]?.photoUrl) {
53
+          currentCheckList.value[index].photoUrl = []
100 54
         }
101 55
         nextTick(() => {
102
-          currentCheckList.value[index].dealPhoto.push({
56
+          currentCheckList.value[index].photoUrl.push({
103 57
             url: imageUrl,
104 58
             name: res.fileName,
105 59
           })
@@ -113,28 +67,50 @@ function chooseImage(index: number) {
113 67
   uploadImage()
114 68
 }
115 69
 const selectTask = ref<any>({})
70
+const anomalyData = ref<any>({})
116 71
 // 切换任务状态
117
-function toggleTaskStatus(task: any) {
72
+async function toggleTaskStatus(task: any) {
118 73
   selectTask.value = task
74
+  // 初始化异常数据
75
+  console.log('task.formOperationIssue', task.formOperationIssue)
76
+  anomalyData.value = task.formOperationIssue || {}
119 77
   popShow.value.anomalyShow = true
78
+  await nextTick()
79
+  let problemPhoto = cloneDeep(anomalyData.value.problemPhoto)
80
+  if (problemPhoto && !isArray(problemPhoto)) {
81
+    problemPhoto = problemPhoto.split(',').map((url: string, index: number) => ({
82
+      name: url.trim(),
83
+      url: anomalyData.value.problemPhotoUrl[index],
84
+    }))
85
+  }
86
+  const data = {
87
+    ...anomalyData.value,
88
+    problemPhoto,
89
+  }
90
+  console.log('data123', data)
91
+  anomalyPopRef.value?.updateModelData(data)
120 92
 }
121 93
 
122 94
 // 处理异常信息提交
123 95
 function handleAnomalySubmitted({ checkItemId, data }: { checkItemId: number, data: any }) {
124 96
   // 在 currentCheckList 中找到对应的任务
125 97
   const taskIndex = currentCheckList.value.findIndex((item: any) => item.id === checkItemId)
98
+  console.log('taskIndex', taskIndex)
126 99
   if (taskIndex !== -1) {
127 100
     // 在任务对象中添加标记,表明异常信息已填写
128
-    currentCheckList.value[taskIndex].hasAnomalyInfo = true
101
+    // currentCheckList.value[taskIndex].hasAnomalyInfo = true
129 102
     // 保存提交的异常数据
130
-    currentCheckList.value[taskIndex].anomalyData = data
103
+    currentCheckList.value[taskIndex].formOperationIssue = data
131 104
   }
105
+
106
+  popShow.value.anomalyShow = false
132 107
 }
133 108
 
134 109
 // 处理 dealResult 变化
135 110
 function handleDealResultChange(task: any) {
136 111
   // 当用户选择异常时,自动弹出异常信息填写抽屉
137
-  if (task.dealResult === '2') {
112
+  if (task.result === 2) {
113
+    anomalyData.value = task.formOperationIssue || {}
138 114
     toggleTaskStatus(task)
139 115
   }
140 116
 }
@@ -160,24 +136,31 @@ async function saveCheckLog({ index }: { index: number }) {
160 136
     const logs = []
161 137
     const items = checkSubItems.value[index]?.items || []
162 138
     currentCheckList.value.forEach((item: any, index: number) => {
139
+      let result = 1
140
+      if (item.dealHasScore) {
141
+        result = (item.dealScore || 0) === item.limitScore ? 1 : 2
142
+      }
143
+      else {
144
+        result = item.result || 1
145
+      }
163 146
       const logInfo = {
164
-        inputItem: item.dealInputItem || '', // 输入内容
147
+        inputItem: item.inputItem || '', // 输入内容
165 148
         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不适用
149
+        photo: item.photoUrl?.map((photo: any) => photo.name).join(',') || '', // 照片
150
+        result, // 检查结果1正常2异常3不适用
168 151
         score: item.dealHasScore ? item.dealScore || 0 : 0, // 分数
169 152
       }
170 153
 
171 154
       // 做一些判断,比如必填项
172
-      if (item.hasPhoto === 2 && !item.dealPhoto?.length) {
155
+      if (item.hasPhoto === 2 && !item.photoUrl?.length) {
173 156
         throw new Error('请上传照片')
174 157
       }
175 158
 
176
-      if (item.hasInput === 1 && !item.dealInputItem) {
159
+      if (item.hasInput === 1 && !item.inputItem) {
177 160
         throw new Error('请输入内容')
178 161
       }
179 162
 
180
-      if (logInfo.result === '2') {
163
+      if (logInfo.result === 2) {
181 164
       // 判断是否输入异常项
182 165
       // if (!item.dealScore) {
183 166
       //   uni.showToast({
@@ -264,6 +247,10 @@ function init() {
264 247
       // 为每个任务添加 score 字段
265 248
       item.items.forEach((task: any) => {
266 249
         task.score = task.score || null
250
+        task.photoUrl = (task.photoUrl || []).map((url: string, index: number) => ({
251
+          url,
252
+          name: task.photo.split(',')[index],
253
+        }))
267 254
       })
268 255
     })
269 256
     // 更新检查项数据
@@ -273,23 +260,6 @@ function init() {
273 260
   })
274 261
 }
275 262
 
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 263
 onMounted(async () => {
294 264
   await init()
295 265
 })
@@ -334,7 +304,7 @@ onMounted(async () => {
334 304
           </view>
335 305
 
336 306
           <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 />
307
+            <wd-textarea v-model="task.inputItem" :placeholder="task.inputContent || '请输入内容'" :maxlength="100" show-word-limit />
338 308
           </view>
339 309
 
340 310
           <!-- 操作说明 -->
@@ -374,9 +344,9 @@ onMounted(async () => {
374 344
             </view>
375 345
 
376 346
             <!-- 已上传图片 -->
377
-            <view v-if="task?.dealPhoto?.length > 0" class="uploaded-images">
347
+            <view v-if="task?.photoUrl?.length > 0" class="uploaded-images">
378 348
               <view
379
-                v-for="(image, idx) in task.dealPhoto"
349
+                v-for="(image, idx) in task.photoUrl"
380 350
                 :key="idx"
381 351
                 class="uploaded-image"
382 352
               >
@@ -385,19 +355,19 @@ onMounted(async () => {
385 355
             </view>
386 356
           </view>
387 357
           <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">
358
+            <wd-radio-group v-model="task.result" shape="dot" inline @change="handleDealResultChange(task)">
359
+              <wd-radio :value="1">
390 360
                 正常
391 361
               </wd-radio>
392
-              <wd-radio value="2">
362
+              <wd-radio :value="2">
393 363
                 异常
394 364
               </wd-radio>
395
-              <wd-radio value="3">
365
+              <wd-radio :value="3">
396 366
                 不适用
397 367
               </wd-radio>
398 368
             </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>
369
+            <view v-if="task.result === 2" style="margin-top: 8rpx;">
370
+              <text class="link-text" @tap="toggleTaskStatus(task)">{{ task.formOperationIssue ? '点击查看异常' : '点击填写异常' }}</text>
401 371
             </view>
402 372
           </view>
403 373
 
@@ -416,15 +386,21 @@ onMounted(async () => {
416 386
               </view>
417 387
             </view>
418 388
             <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>
389
+              <text class="link-text" @tap="toggleTaskStatus(task)">{{ task.formOperationIssue ? '点击查看异常' : '点击填写异常(未满分必填)' }}</text>
420 390
             </view>
421 391
           </view>
422 392
         </view>
423 393
       </view>
424 394
     </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" />
395
+    <wd-popup
396
+      v-model="popShow.anomalyShow"
397
+      custom-style="border-radius: 32rpx 32rpx 0 0; height: 80%;z-index: 100;"
398
+      position="bottom"
399
+      :safe-area-inset-bottom="true"
400
+      closable
401
+    >
402
+      <anomalyPop v-if="popShow.anomalyShow" ref="anomalyPopRef" :task-id="taskId" :station-id="taskInfo.stationId" :check-item-id="selectTask.id" :anomaly-data="anomalyData.value" @anomaly-submitted="handleAnomalySubmitted" />
403
+    </wd-popup>
428 404
     <!-- 确定按钮 -->
429 405
     <view class="confirm-btn">
430 406
       <button class="btn" @click="submitTasks">

+ 145 - 0
src/pages/schedule/details/deal/photo.vue

@@ -0,0 +1,145 @@
1
+<script setup lang="ts">
2
+import { onMounted, ref } from 'vue'
3
+import { useToast } from 'wot-design-uni'
4
+import { dealPhotoTask } from '@/api/schedule/task'
5
+import useUpload from '@/hooks/useUpload'
6
+
7
+const toast = useToast()
8
+
9
+definePage({
10
+  style: {
11
+    navigationBarTitleText: '拍照任务',
12
+  },
13
+})
14
+
15
+// 任务和检查项ID
16
+const taskId = ref('')
17
+const checkId = ref('')
18
+
19
+// 表单数据
20
+const formData = ref({
21
+  attachments: '',
22
+  remark: '',
23
+})
24
+
25
+// 上传图片列表
26
+const fileList = ref<any[]>([])
27
+
28
+// 上传状态
29
+const loading = ref(false)
30
+
31
+// 上传问题照片
32
+const uploadProblemPhoto: any = (file, formData, options) => {
33
+  const { customUpload } = useUpload({
34
+    fileType: 'image',
35
+  })
36
+  return customUpload(file, formData, options)
37
+}
38
+
39
+// 删除问题照片
40
+function deleteProblemPhoto(index: number) {
41
+  fileList.value.splice(index, 1)
42
+
43
+  // 更新附件字段
44
+  const fileNames = fileList.value.map((item: any) => item.name).filter(Boolean)
45
+  formData.value.attachments = fileNames.join(',')
46
+}
47
+
48
+// 提交表单
49
+async function submitForm() {
50
+  // 检查是否有图片上传
51
+  if (fileList.value.length === 0) {
52
+    toast.info('请上传图片')
53
+    return
54
+  }
55
+
56
+  // 构建请求参数
57
+  const params = {
58
+    id: taskId.value,
59
+    attachment: fileList.value.map((item: any) => item.name).join(','),
60
+    result: formData.value.remark,
61
+  }
62
+
63
+  // 开始提交,显示 loading
64
+  loading.value = true
65
+
66
+  // 调用处理任务附件接口
67
+  try {
68
+    const res: any = await dealPhotoTask(params)
69
+    if (res.code === 200) {
70
+      toast.success('提交成功')
71
+      // 跳转回上一页
72
+      setTimeout(() => {
73
+        uni.navigateBack()
74
+      }, 1500)
75
+    }
76
+    else {
77
+      toast.error(res.msg || '提交失败,请重试')
78
+      return
79
+    }
80
+  }
81
+  catch (error) {
82
+    toast.error('提交失败,请重试')
83
+    return
84
+  }
85
+  finally {
86
+    // 提交完成,隐藏 loading
87
+    loading.value = false
88
+  }
89
+}
90
+
91
+// 页面加载时获取参数
92
+onMounted(() => {
93
+  // 获取页面参数
94
+  const pages = getCurrentPages()
95
+  const currentPage = pages[pages.length - 1]
96
+  // 使用类型断言获取options
97
+  const options = (currentPage as any).options || {}
98
+  taskId.value = options.id || ''
99
+  checkId.value = options.checkId || ''
100
+})
101
+</script>
102
+
103
+<template>
104
+  <view class="page min-h-screen bg-gray-100 p-4">
105
+    <view class="rounded-lg bg-white p-4 shadow-sm">
106
+      <!-- 上传图片 -->
107
+      <view class="mb-6">
108
+        <view class="mb-4 flex items-center justify-between">
109
+          <text class="font-medium">上传图片</text>
110
+        </view>
111
+        <view>
112
+          <wd-upload
113
+            v-model:file-list="fileList"
114
+            image-mode="aspectFill"
115
+            :upload-method="uploadProblemPhoto"
116
+            @delete="deleteProblemPhoto"
117
+          />
118
+        </view>
119
+      </view>
120
+
121
+      <!-- 备注 -->
122
+      <view class="mb-6">
123
+        <text class="mb-2 block font-medium">处理结果</text>
124
+        <wd-textarea v-model="formData.remark" placeholder="请填写处理结果" auto-height :maxlength="120" clearable show-word-limit />
125
+      </view>
126
+
127
+      <!-- 提交按钮 -->
128
+      <view class="mt-8 flex justify-center">
129
+        <wd-button
130
+          type="primary"
131
+          size="large"
132
+          :loading="loading"
133
+          @click="submitForm"
134
+        >
135
+          提交
136
+        </wd-button>
137
+      </view>
138
+    </view>
139
+    <wd-toast />
140
+  </view>
141
+</template>
142
+
143
+<style lang="scss" scoped>
144
+// 保持与upload.vue一致的样式
145
+</style>

+ 161 - 14
src/pages/schedule/details/deal/upload.vue

@@ -1,23 +1,170 @@
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 1
 <script setup lang="ts">
14 2
 import { onMounted, ref } from 'vue'
3
+import { useToast } from 'wot-design-uni'
4
+import { dealUploadTask } from '@/api/schedule/task'
5
+import useUpload from '@/hooks/useUpload'
6
+
7
+const toast = useToast()
15 8
 
16 9
 const taskId = ref('')
17 10
 const checkId = ref('')
18 11
 
12
+// 表单数据
13
+const formData = ref({
14
+  attachments: '',
15
+  remark: '',
16
+})
17
+
18
+// 上传文件列表
19
+const uploadFiles = ref<any[]>([])
20
+
21
+// 初始化useUpload
22
+const { run: uploadRun, loading: uploadLoading } = useUpload({
23
+  fileType: 'file',
24
+  success: (data) => {
25
+    console.log('上传成功:', data)
26
+
27
+    // 构建文件对象
28
+    const fileObj = {
29
+      url: data.url || '',
30
+      originalFilename: data.originalFilename || data.name || '',
31
+      name: data.fileName || data.name || '',
32
+    }
33
+
34
+    // 添加到上传文件列表
35
+    uploadFiles.value.push(fileObj)
36
+
37
+    // 更新附件字段
38
+    const fileNames = uploadFiles.value.map((f) => {
39
+      try {
40
+        const response = JSON.parse(f.response)
41
+        return response.data.fileName
42
+      }
43
+      catch (error) {
44
+        console.error('解析文件响应失败:', error)
45
+        return ''
46
+      }
47
+    }).filter(Boolean)
48
+
49
+    formData.value.attachments = fileNames.join(',')
50
+    console.log('文件名称:', formData.value.attachments)
51
+  },
52
+  error: (err) => {
53
+    console.error('上传失败:', err)
54
+    toast.error('上传失败,请重试')
55
+  },
56
+})
57
+
58
+// 处理删除文件
59
+function handleRemoveFile(file: any) {
60
+  const index = uploadFiles.value.findIndex(f => f.url === file.url)
61
+  if (index !== -1) {
62
+    uploadFiles.value.splice(index, 1)
63
+
64
+    // 更新附件字段
65
+    const fileNames = uploadFiles.value.map((f) => {
66
+      try {
67
+        const response = JSON.parse(f.response)
68
+        return response.data.fileName
69
+      }
70
+      catch (error) {
71
+        console.error('解析文件响应失败:', error)
72
+        return ''
73
+      }
74
+    }).filter(Boolean)
75
+
76
+    formData.value.attachments = fileNames.join(',')
77
+  }
78
+}
79
+
80
+// 提交表单
81
+async function submitForm() {
82
+  // 这里可以添加表单验证逻辑
83
+
84
+  // 检查是否有文件上传
85
+  if (uploadFiles.value.length === 0) {
86
+    toast.info('请上传附件')
87
+    return
88
+  }
89
+
90
+  // 构建请求参数
91
+  const params = {
92
+    taskId: Number(taskId.value),
93
+    checkId: Number(checkId.value),
94
+    ...formData.value,
95
+    id: taskId.value,
96
+    attachment: uploadFiles.value.map(f => f.name).join(','),
97
+    attachmentName: uploadFiles.value.map(f => f.originalFilename).join(','),
98
+    remark: formData.value.remark,
99
+  }
100
+
101
+  // 调用处理任务附件接口
102
+  try {
103
+    const res: any = await dealUploadTask(params)
104
+    if (res.code === 200) {
105
+      toast.success('提交成功')
106
+      // 跳转回上一页
107
+      setTimeout(() => {
108
+        uni.navigateBack()
109
+      }, 1500)
110
+    }
111
+    else {
112
+      toast.error(res.msg || '提交失败,请重试')
113
+      return
114
+    }
115
+  }
116
+  catch (error) {
117
+    toast.error('提交失败,请重试')
118
+    return
119
+  }
120
+}
121
+
122
+// 页面加载时获取参数
19 123
 onMounted(() => {
20
-//   taskId.value = getQueryParam('id') || ''
21
-//   checkId.value = getQueryParam('checkId') || ''
124
+  // 获取页面参数
125
+  const pages = getCurrentPages()
126
+  const currentPage = pages[pages.length - 1]
127
+  // 使用类型断言获取options
128
+  const options = (currentPage as any).options || {}
129
+  taskId.value = options.id || ''
130
+  checkId.value = options.checkId || ''
22 131
 })
23 132
 </script>
133
+
134
+<template>
135
+  <view class="page min-h-screen bg-gray-100 p-4">
136
+    <view class="rounded-lg bg-white p-4 shadow-sm">
137
+      <!-- 上传附件 -->
138
+      <view class="mb-6">
139
+        <view class="flex items-center justify-between">
140
+          <text class="font-medium">上传附件</text>
141
+          <wd-button type="text" @click="uploadRun">
142
+            点击上传附件
143
+          </wd-button>
144
+        </view>
145
+        <view v-for="(file, index) in uploadFiles" :key="index" class="mt-2 flex items-center justify-between">
146
+          <wd-icon name="close-circle" class="mr-2 text-red-500" @click="handleRemoveFile(file)" />
147
+          <wd-text :text="file.originalFilename" class="flex-1" />
148
+        </view>
149
+      </view>
150
+
151
+      <!-- 备注 -->
152
+      <view class="mb-6">
153
+        <text class="mb-2 block font-medium">备注</text>
154
+        <wd-textarea v-model="formData.remark" placeholder="请填写备注" auto-height :maxlength="120" clearable show-word-limit />
155
+      </view>
156
+
157
+      <!-- 提交按钮 -->
158
+      <view class="mt-8 flex justify-center">
159
+        <wd-button
160
+          type="primary"
161
+          size="large"
162
+          @click="submitForm"
163
+        >
164
+          提交
165
+        </wd-button>
166
+      </view>
167
+      <wd-toast />
168
+    </view>
169
+  </view>
170
+</template>

+ 199 - 157
src/pages/schedule/details/taskdetails/index.vue

@@ -1,14 +1,119 @@
1
+<script lang="ts" setup>
2
+import { ref } from 'vue'
3
+
4
+import { useRoute } from 'vue-router'
5
+import { getCheckSubItemsByTaskId, getChecktItemsByTaskId } from '@/api/schedule/check'
6
+import { getTaskDetail } from '@/api/schedule/task'
7
+
8
+const route = useRoute()
9
+const taskId = ref(Number(route.query.id))
10
+const checkId = ref(Number(route.query.checkId))
11
+const taskInfo = ref<any>({})
12
+const checkSubItems = ref<any[]>([])
13
+const activeArea = ref(0)
14
+const popupShow = ref({
15
+  centerShow: false,
16
+  bottomShow: false,
17
+})
18
+
19
+const currentCheckItem = ref<any>({})
20
+
21
+const statics = ref({
22
+  score: 0,
23
+  totalScore: 0,
24
+  totalItems: 0,
25
+  photoCount: 0,
26
+})
27
+
28
+function init() {
29
+  // 并发获取数据
30
+  Promise.all([
31
+    getTaskDetail(taskId.value),
32
+    getChecktItemsByTaskId({
33
+      checkId: checkId.value,
34
+      pageSize: 100,
35
+    }),
36
+    getCheckSubItemsByTaskId({
37
+      checkId: checkId.value,
38
+      pageSize: 100,
39
+    }),
40
+  ]).then(([taskDetailRes, checkItemsRes, checkSubItemsRes]: any) => {
41
+    if (taskDetailRes.data) {
42
+      taskInfo.value = taskDetailRes.data
43
+    }
44
+    // 处理检查项数据
45
+    checkSubItems.value = checkSubItemsRes?.rows || []
46
+    checkSubItems.value.forEach((item: any) => {
47
+      item.statics = {
48
+        score: 0,
49
+        totalScore: 0,
50
+        totalItems: 0,
51
+        photoCount: 0,
52
+      }
53
+      item.items
54
+        = checkItemsRes?.rows?.filter(
55
+          (subItem: any) => subItem.subItemId === item.id,
56
+        ) || []
57
+      // 为每个任务添加 score 字段
58
+      item.items.forEach((task: any) => {
59
+        if (task.checkResult === 2) {
60
+          item.statics.score += task.score || 0
61
+          item.statics.totalScore += (task.limitScore || 0).length
62
+        }
63
+        if (task?.result === 2) {
64
+          item.statics.totalItems += 1
65
+        }
66
+        if (task?.hasPhoto > 0 && (!task?.photoUrl || task?.photoUrl?.length === 0)) {
67
+          item.statics.photoCount += 1
68
+        }
69
+        task.score = task.score || null
70
+        task.photoUrl = (task.photoUrl || []).map((url: string, index: number) => ({
71
+          url,
72
+          name: task.photo.split(',')[index],
73
+        }))
74
+      })
75
+      // 计算总得分
76
+      statics.value.score += item.statics.score
77
+      statics.value.totalScore += item.statics.totalScore
78
+      statics.value.totalItems += item.statics.totalItems
79
+      statics.value.photoCount += item.statics.photoCount
80
+    })
81
+    // 更新检查项数据
82
+    activeArea.value = 0
83
+    currentCheckItem.value = checkSubItems.value[0] || {}
84
+  })
85
+}
86
+const toastHtml = ref('')
87
+function helpClick(task: any) {
88
+  toastHtml.value = task.checkDescription
89
+  popupShow.value.centerShow = true
90
+}
91
+
92
+function helpClose() {
93
+  toastHtml.value = ''
94
+  popupShow.value.centerShow = false
95
+}
96
+
97
+async function changeTab({ index }: { index: number }) {
98
+  currentCheckItem.value = checkSubItems.value[index] || {}
99
+}
100
+
101
+onMounted(async () => {
102
+  await init()
103
+})
104
+</script>
105
+
1 106
 <template>
2 107
   <view class="task-management">
3 108
     <view class="fixed-area">
4 109
       <view class="head-Card">
5 110
         <view class="head-Card-title">
6
-          未来路加油站
111
+          {{ taskInfo.stationName || '' }}
7 112
         </view>
8 113
         <view class="head-Card-tab">
9 114
           <view class="head-Card-tab-item">
10 115
             <view class="tab-item-num">
11
-              0
116
+              {{ statics.photoCount || 0 }}
12 117
             </view>
13 118
             <view class="tab-item-text">
14 119
               无拍照
@@ -17,7 +122,7 @@
17 122
           <view class="head-Card-tab-item">
18 123
             <view class="tab-item-Middle">
19 124
               <view class="tab-item-num">
20
-                0
125
+                {{ statics.totalItems || 0 }}
21 126
               </view>
22 127
               <view class="tab-item-text">
23 128
                 异常总数
@@ -26,8 +131,7 @@
26 131
           </view>
27 132
           <view class="head-Card-tab-item">
28 133
             <view class="tab-item-num">
29
-              0 /
30
-              <text style="font-size: 24rpx; font-weight: 500">10</text>
134
+              {{ statics.score || 0 }} / {{ statics.totalScore || 0 }}
31 135
             </view>
32 136
             <view class="tab-item-text">
33 137
               总得分
@@ -36,9 +140,9 @@
36 140
         </view>
37 141
       </view>
38 142
       <view>
39
-        <wd-tabs v-model="tab">
40
-          <block v-for="item in tabs" :key="item">
41
-            <wd-tab :title="`${item}`" :name="item" />
143
+        <wd-tabs v-model="activeArea" @click="changeTab">
144
+          <block v-for="(item, index) in checkSubItems" :key="index">
145
+            <wd-tab :title="`${item.name}`" :name="index" />
42 146
           </block>
43 147
         </wd-tabs>
44 148
       </view>
@@ -47,43 +151,43 @@
47 151
     <scroll-view class="task-container" scroll-y>
48 152
       <view class="task-top-left">
49 153
         <text>
50
-          异常数<text style="color: #31373d; font-weight: 600"> 0</text>
154
+          异常数 <text style="color: #31373d; font-weight: 600">
155
+            {{ currentCheckItem?.statics?.totalItems || 0 }}
156
+          </text>
51 157
         </text>
52
-        <text style="color: #31373d; font-weight: 600">|</text>
158
+        <text style="color: #31373d; font-weight: 600"> | </text>
53 159
         <text>
54
-          得分<text style="color: #31373d; font-weight: 600"> 0/0</text>
160
+          得分 <text style="color: #31373d; font-weight: 600">
161
+            {{ currentCheckItem?.statics?.score || 0 }} / {{ currentCheckItem?.statics?.totalScore || 0 }}
162
+          </text>
55 163
         </text>
56 164
       </view>
57 165
       <view class="tasks">
58 166
         <view
59
-          v-for="(task, index) in currentTasks"
167
+          v-for="(task, index) in currentCheckItem?.items || []"
60 168
           :key="index"
61 169
           class="task-item"
62 170
         >
63 171
           <!-- 任务描述 -->
64 172
           <view class="task-description">
65
-            <text class="task-number">{{ index + 1 }}.</text>
173
+            <text class="task-number">{{ +index + 1 }}.</text>
66 174
             <text class="task-text">
67
-              {{ task.description }}
68
-              <wd-icon
69
-                v-if="task?.questionicon"
70
-                :name="task?.questionicon"
71
-                size="32rpx"
72
-                @click="() => (popupshow.centershow = true)"
73
-              />
175
+              {{ task.tagName ? `【${task.tagName.replace(/,/g, '|')}】` : '' }}
176
+              {{ task.itemName }}
177
+              <wd-icon v-if="task?.checkDescription" name="help-circle" size="16px" @click="helpClick(task)" />
74 178
             </text>
75 179
           </view>
76 180
           <!-- 操作说明-图片示例 -->
77 181
           <view
78
-            v-if="task.instructions && task.examples.length > 0"
182
+            v-if="task.exampleImageUrl.length > 0"
79 183
             class="task-instructions-example"
80 184
           >
81
-            <view class="task-instructions">
82
-              <text class="instructions-text">{{ task.instructions }}</text>
185
+            <view v-if="task.photoPrompt" class="task-instructions">
186
+              <text class="instructions-text">{{ `拍照:${task.photoPrompt}` }}</text>
83 187
             </view>
84 188
             <view class="example-images">
85 189
               <view
86
-                v-for="(example, idx) in task.examples"
190
+                v-for="(example, idx) in task.exampleImageUrl"
87 191
                 :key="idx"
88 192
                 class="example-image"
89 193
               >
@@ -93,41 +197,57 @@
93 197
             </view>
94 198
           </view>
95 199
           <!-- 已上传图片 -->
96
-          <view v-if="task.images.length > 0" class="uploaded-images">
200
+          <view v-if="task?.photoUrl?.length > 0" class="uploaded-images">
97 201
             <view
98
-              v-for="(image, idx) in task.images"
202
+              v-for="(image, idx) in task.photoUrl"
99 203
               :key="idx"
100 204
               class="uploaded-image"
101 205
             >
102
-              <image :src="image" mode="aspectFill" />
206
+              <!-- <image :src="image" mode="aspectFill" /> -->
207
+              <wd-img style="width: 100%; height: 100%" :src="image.url" :enable-preview="true" />
103 208
             </view>
104 209
           </view>
105 210
           <!-- 几号枪 -->
106
-          <view v-if="task.jihaoqiang" class="jihaoqiang">
107
-            <text class="jihaoqiang-text">{{ task.jihaoqiang }}</text>
211
+          <view v-if="task?.inputItem" class="whichGun">
212
+            <text class="whichGun-text">{{ task.inputItem }}</text>
108 213
           </view>
109 214
           <!-- 状态 -->
110 215
           <view class="task-status">
111 216
             <view>
112 217
               <text class="status-text">
113
-                整体:{{ task.status === 'normal' ? '正常' : '异常' }}
218
+                整体:{{ task.result === 1 ? '正常' : task.result === 2 ? '异常' : '不适用' }}
114 219
               </text>
115 220
             </view>
116 221
           </view>
117
-          <view v-if="task.exceptiondescription" class="exceptiondescription">
118
-            <view class="exceptiondescription-title">
222
+          <view v-if="task?.formOperationIssue?.id" class="anomalyDescription">
223
+            <view class="anomalyDescription-title">
119 224
               异常描述:
120 225
             </view>
121
-            <view class="exceptiondescription-content">
122
-              {{ task.exceptiondescription }}
226
+            <view class="anomalyDescription-content">
227
+              {{ task.formOperationIssue?.problemDescription || '' }}
123 228
             </view>
124 229
           </view>
125 230
         </view>
126 231
       </view>
127 232
     </scroll-view>
128
-    <centerpopup v-model:centershow="popupshow.centershow" />
129
-    <bottompopup v-model:bottomshow="popupshow.bottomshow" />
130
-    <!-- 确定按钮 -->
233
+
234
+    <wd-popup
235
+      v-model="popupShow.centerShow"
236
+      custom-style="border-radius:32rpx;"
237
+      @close="helpClose"
238
+    >
239
+      <view class="popup-content">
240
+        <view style="font-size: 36rpx; font-weight: 500; color: #020917">
241
+          说明
242
+        </view>
243
+        <view style="font-size: 32rpx; font-weight: 400; color: #343a45" v-html="toastHtml" />
244
+        <view style="width: 100%">
245
+          <button class="btn" @click="helpClose">
246
+            我 知 道 了
247
+          </button>
248
+        </view>
249
+      </view>
250
+    </wd-popup>
131 251
     <view class="confirm-btn">
132 252
       <button class="btn" @click="submitTasks">
133 253
         评 论
@@ -136,124 +256,11 @@
136 256
   </view>
137 257
 </template>
138 258
 
139
-<script lang="ts" setup>
140
-import { computed, ref } from 'vue'
141
-import useUpload from '@/hooks/useUpload'
142
-import bottompopup from '../popup/bottompopup.vue'
143
-import centerpopup from '../popup/centerpopup.vue'
144
-
145
-const popupshow = ref({
146
-  centershow: false,
147
-  bottomshow: false,
148
-})
149
-// const popupshow = ref({})
150
-const tab = ref('前庭')
151
-const tabs = ['前庭', '自助服务区', '罐区', '便利店']
152
-
153
-// 定义任务类型
154
-interface Task {
155
-  id: string
156
-  type: string
157
-  description: string
158
-  icon?: string
159
-  instructions: string
160
-  examples: string[]
161
-  images: string[]
162
-  jihaoqiang: string
163
-  status: 'normal' | 'abnormal'
164
-  questionicon: string
165
-  exceptiondescription: string
166
-}
167
-
168
-// 任务数据
169
-const tasks = ref<Task[]>([
170
-  {
171
-    id: '1',
172
-    type: '前庭',
173
-    description:
174
-      '[AI] [卫生] 前庭海报整齐不歪斜,干净无灰尘污渍(立柱、灯箱、展架等)。',
175
-    instructions: '拍照:按照示例图拍摄前庭内海报宣传物,可上传多张照片',
176
-    examples: [
177
-      'https://picsum.photos/id/1018/200/150',
178
-      'https://picsum.photos/id/1019/200/150',
179
-    ],
180
-    images: [
181
-      'https://picsum.photos/id/1028/200/150',
182
-      'https://picsum.photos/id/1022/200/150',
183
-      'https://picsum.photos/id/1023/200/150',
184
-      'https://picsum.photos/id/1026/200/150',
185
-      'https://picsum.photos/id/1025/200/150',
186
-      'https://picsum.photos/id/1029/200/150',
187
-      'https://picsum.photos/id/1025/200/150',
188
-    ],
189
-    questionicon: 'help-circle',
190
-    jihaoqiang: '',
191
-    status: 'normal',
192
-    exceptiondescription: '',
193
-  },
194
-  {
195
-    id: '2',
196
-    type: '前庭',
197
-    description: '[卫生] 抽油枪栓是否出现空转或存在明显渗油。',
198
-    icon: 'help-circle',
199
-    instructions: '',
200
-    examples: [],
201
-    images: [],
202
-    questionicon: 'help-circle',
203
-    jihaoqiang: '12号枪',
204
-
205
-    status: 'normal',
206
-    exceptiondescription: '',
207
-  },
208
-  {
209
-    id: '3',
210
-    type: '前庭',
211
-    description:
212
-      '[AI] [卫生] 前庭海报整齐不歪斜,干净无灰尘污渍(立柱、灯箱、展架等)。',
213
-    instructions: '',
214
-    examples: [],
215
-    images: [],
216
-    questionicon: 'help-circle',
217
-    jihaoqiang: '13号枪',
218
-    status: 'abnormal',
219
-    exceptiondescription:
220
-      '异常描述异常描述异常描述异常描述异常描述异常描述异常描述异常描述异常描述异常描述异常描述',
221
-  },
222
-  {
223
-    id: '4',
224
-    type: '前庭',
225
-    description:
226
-      '[AI] [卫生] 前庭海报整齐不歪斜,干净无灰尘污渍(立柱、灯箱、展架等)。',
227
-    instructions: '',
228
-    examples: [],
229
-    images: [],
230
-    questionicon: 'help-circle',
231
-    jihaoqiang: '',
232
-    status: 'normal',
233
-    exceptiondescription: '',
234
-  },
235
-])
236
-
237
-// 根据当前标签筛选任务
238
-const currentTasks = computed(() => {
239
-  return tasks.value.filter(task => task.type === tab.value)
240
-})
241
-// 提交任务
242
-function submitTasks() {
243
-  console.log('提交任务', tasks.value)
244
-  // 这里可以添加提交逻辑
245
-  uni.showToast({
246
-    title: '提交成功',
247
-    icon: 'success',
248
-  })
249
-}
250
-</script>
251
-
252 259
 <style lang="scss" scoped>
253 260
 .task-management {
254 261
   display: flex;
255 262
   flex-direction: column;
256
-  height: 1500rpx;
263
+  min-height: 100vh;
257 264
   overflow: hidden;
258 265
   .fixed-area {
259 266
     flex-shrink: 0;
@@ -326,7 +333,7 @@ function submitTasks() {
326 333
     // padding: 0 30rpx;
327 334
     .task-top-left {
328 335
       margin-left: 30rpx;
329
-      width: 230rpx;
336
+      width: 250rpx;
330 337
       height: 52rpx;
331 338
       background-color: #e8f3ff;
332 339
       border-radius: 12rpx;
@@ -416,7 +423,7 @@ function submitTasks() {
416 423
           }
417 424
         }
418 425
       }
419
-      .jihaoqiang {
426
+      .whichGun {
420 427
         width: 100%;
421 428
         height: 96rpx;
422 429
         background-color: #f5f5f5;
@@ -428,7 +435,7 @@ function submitTasks() {
428 435
         box-sizing: border-box;
429 436
         margin-bottom: 10rpx;
430 437
         border-radius: 20rpx;
431
-        .jihaoqiang-text {
438
+        .whichGun-text {
432 439
           font-size: 28rpx;
433 440
           color: #4e5969;
434 441
           font-weight: 500;
@@ -446,16 +453,16 @@ function submitTasks() {
446 453
           color: #31373d;
447 454
         }
448 455
       }
449
-      .exceptiondescription {
456
+      .anomalyDescription {
450 457
         width: 100%;
451 458
         border-bottom: 1px solid #eeeeee;
452 459
         font-size: 28rpx;
453 460
         padding-bottom: 40rpx;
454
-        .exceptiondescription-title {
461
+        .anomalyDescription-title {
455 462
           font-weight: 500;
456 463
           color: #31373d;
457 464
         }
458
-        .exceptiondescription-content {
465
+        .anomalyDescription-content {
459 466
           font-weight: 400;
460 467
           color: #4e5969;
461 468
         }
@@ -483,9 +490,14 @@ function submitTasks() {
483 490
 
484 491
   // 确定按钮
485 492
   .confirm-btn {
493
+    position: fixed;
494
+    bottom: 0;
495
+    left: 0;
496
+    right: 0;
486 497
     padding: 20rpx 30rpx;
487 498
     background-color: white;
488 499
     border-top: 1rpx solid #eee;
500
+    z-index: 100;
489 501
 
490 502
     .btn {
491 503
       width: 100%;
@@ -500,4 +512,34 @@ function submitTasks() {
500 512
     }
501 513
   }
502 514
 }
515
+
516
+.custom-txt {
517
+  width: 400rpx;
518
+  height: 400rpx;
519
+  display: flex;
520
+  justify-content: center;
521
+  align-items: center;
522
+  border-radius: 32rpx;
523
+}
524
+.popup-content {
525
+  width: 600rpx;
526
+  display: flex;
527
+  flex-direction: column;
528
+  justify-content: space-between;
529
+  align-items: center;
530
+  padding: 40rpx;
531
+  box-sizing: border-box;
532
+  gap: 40rpx;
533
+  .btn {
534
+    width: 100%;
535
+    height: 80rpx;
536
+    background-color: #215acd;
537
+    color: white;
538
+    font-size: 32rpx;
539
+    border-radius: 12rpx;
540
+    font-weight: 400;
541
+    border: none;
542
+    outline: none;
543
+  }
544
+}
503 545
 </style>

+ 266 - 59
src/pages/schedule/details/index.vue

@@ -1,14 +1,22 @@
1 1
 <script setup lang="ts">
2 2
 import dayjs from 'dayjs'
3 3
 import { keyBy } from 'lodash-es'
4
-import { ref } from 'vue'
4
+import { storeToRefs } from 'pinia'
5
+import { onMounted, ref } from 'vue'
5 6
 import { useRoute, useRouter } from 'vue-router'
6 7
 import { useMessage, useToast } from 'wot-design-uni'
7
-import { confirmDoneTask, getTaskDetail } from '@/api/schedule/task'
8
+import { closeTask, confirmDoneTask, getTaskDetail, transferTask } from '@/api/schedule/task'
9
+import { getAllUsers } from '@/api/system/users'
8 10
 
11
+import AttachmentList from '@/components/AttachmentList.vue'
12
+import { useUserStore } from '@/store'
9 13
 import { authStatusOptions, taskLevelOptions, taskStatusOptions } from '@/utils/dicts'
10 14
 
11
-const message = useMessage()
15
+const userStore = useUserStore()
16
+
17
+const { userInfo } = storeToRefs(userStore)
18
+
19
+const wotMessage = useMessage()
12 20
 const toast = useToast()
13 21
 
14 22
 // 定义页面配置,注册路由
@@ -27,6 +35,11 @@ const taskInfo: any = ref({})
27 35
 const taskOther: any = ref({})
28 36
 const taskResult: any = ref({})
29 37
 
38
+// 转交功能相关
39
+const users = ref<any[]>([])
40
+const value = ref<string[]>([])
41
+const customShow = ref<string>('')
42
+
30 43
 // 切换更多内容显示/隐藏
31 44
 function toggleMore() {
32 45
   isExpanded.value = !isExpanded.value
@@ -34,7 +47,7 @@ function toggleMore() {
34 47
 
35 48
 async function dealConfirmTaskEvent() {
36 49
 // 二次弹框确认
37
-  message.confirm({
50
+  wotMessage.confirm({
38 51
     msg: `确认完成任务 ${taskInfo.value.taskName} 吗?`,
39 52
     title: '确认完成',
40 53
   }).then(async () => {
@@ -64,6 +77,12 @@ async function dealTaskEvent() {
64 77
       })
65 78
       return
66 79
     }
80
+    case 'photo': {
81
+      uni.navigateTo({
82
+        url: `/pages/schedule/details/deal/photo?id=${taskInfo.value.taskList?.[0]?.id}`,
83
+      })
84
+      return
85
+    }
67 86
     case 'confirm': {
68 87
       await dealConfirmTaskEvent()
69 88
 
@@ -109,7 +128,7 @@ async function dealTaskEvent() {
109 128
     }
110 129
     case 'upload': {
111 130
       uni.navigateTo({
112
-        url: `/pages/schedule/details/deal/upload?id=${taskId.value}`,
131
+        url: `/pages/schedule/details/deal/upload?id=${taskInfo.value.taskList?.[0]?.id}`,
113 132
       })
114 133
       return
115 134
     }
@@ -136,6 +155,61 @@ function viewDetails3() {
136 155
   })
137 156
 }
138 157
 
158
+// 获取用户列表
159
+async function getUserList() {
160
+  console.log('调用getAllUsers时的stationId:', taskInfo.value.stationId)
161
+  const res: any = await getAllUsers({
162
+    pageNum: 10000,
163
+    stationId: taskInfo.value.stationId,
164
+  })
165
+  if (res.code === 200) {
166
+    users.value = res.data.map((item: any) => ({
167
+      label: item.nickName,
168
+      value: item.userId,
169
+    }))
170
+  }
171
+}
172
+
173
+// 选择器确认事件
174
+function handleTransferTask({ value, selectedItems }: any) {
175
+  console.log('value:', value[0], taskInfo.value.executorId)
176
+  if (value.length === 0) {
177
+    toast.error('请选择执行人')
178
+    return
179
+  }
180
+
181
+  if (value[0] === taskInfo.value.executorId) {
182
+    toast.error('不能将任务转交给当前执行人')
183
+    console.log('不能将任务转交给当前执行人', toast)
184
+
185
+    return
186
+  }
187
+
188
+  wotMessage.confirm({
189
+    msg: `确认将任务 ${taskInfo.value.taskName} 转交给 ${selectedItems.map((item: any) => item.label).join(', ')} 吗?`,
190
+    title: '确认转交',
191
+  }).then(async () => {
192
+    try {
193
+      const res: any = await transferTask({
194
+        id: taskInfo.value.id || 0,
195
+        executorId: value[0],
196
+      })
197
+      if (res.code !== 200) {
198
+        toast.error(res.msg || '操作失败')
199
+        return
200
+      }
201
+
202
+      toast.success('转交任务成功')
203
+      await init()
204
+    }
205
+    catch {
206
+      toast.error('操作失败')
207
+    }
208
+  }).catch(() => {
209
+    return
210
+  })
211
+}
212
+
139 213
 /**
140 214
  * 获取任务时间
141 215
  * @returns 任务时间字符串
@@ -154,12 +228,33 @@ function downloadFile(file: any, fileName: string) {
154 228
   console.log('downloadFile:', file, fileName)
155 229
 }
156 230
 
231
+const buttons = ref({
232
+  deal: false,
233
+  transfer: false,
234
+  close: false,
235
+})
236
+
157 237
 async function init() {
158 238
   const res: any = await getTaskDetail(taskId.value)
159 239
   taskInfo.value = res.data || {}
160 240
   if (!taskInfo.value.status) {
161 241
     taskInfo.value.status = 0
162 242
   }
243
+  console.log(userInfo.value)
244
+
245
+  if (taskInfo.value.status === 1) {
246
+    if (taskInfo.value.executorId === userInfo.value.user?.userId) {
247
+      buttons.value.deal = true
248
+    }
249
+    if (userInfo.value.user?.createBy === taskInfo.value.createBy || userInfo.value.user?.post?.type === '"headquarters"' || (taskInfo.value?.taskTemplate?.isMust === 0 && taskInfo.value.executorId === userInfo.value.user?.userId)) {
250
+      buttons.value.close = true
251
+
252
+      if (taskInfo.value.authStatus === 0) {
253
+        buttons.value.transfer = true
254
+      }
255
+    }
256
+  }
257
+
163 258
   // 假设从接口返回数据中获取taskOther和taskResult
164 259
   taskOther.value.taskTypeName = taskInfo.value?.formTypeName || '-'
165 260
 
@@ -195,6 +290,11 @@ async function init() {
195 290
     taskOther.value.taskFrequencyName
196 291
       = taskInfo.value?.taskTemplate?.taskFrequencyName || '-'
197 292
   }
293
+
294
+  // 获取用户列表
295
+  if (taskInfo.value.stationId) {
296
+    await getUserList()
297
+  }
198 298
 }
199 299
 
200 300
 // 状态标签配置
@@ -206,9 +306,44 @@ const priorityConfig: any = keyBy(taskLevelOptions, 'value')
206 306
 // 授权状态标签配置
207 307
 const authStatusConfig: any = keyBy(authStatusOptions, 'value')
208 308
 
209
-onMounted(async () => {
309
+function goDetail() {
310
+  uni.navigateTo({
311
+    url: `/pages/schedule/details/detail/inspection?id=${taskId.value}&checkId=${taskInfo.value.taskList[0].id}`,
312
+  })
313
+}
314
+
315
+/**
316
+ * 关闭任务按钮点击事件
317
+ */
318
+async function closeTaskEvent() {
319
+  // 二次弹框确认
320
+  wotMessage.confirm({
321
+    msg: `确认关闭任务 ${taskInfo.value.taskName} 吗?`,
322
+    title: '确认关闭',
323
+  }).then(async () => {
324
+    try {
325
+      await closeTask({
326
+        id: taskInfo.value.id || 0,
327
+      })
328
+
329
+      toast.success('关闭任务成功')
330
+      await init()
331
+    }
332
+    catch {
333
+      toast.error('操作失败')
334
+    }
335
+  }).catch(() => {
336
+    return
337
+  })
338
+}
339
+
340
+onShow(async () => {
210 341
   await init()
211 342
 })
343
+
344
+onMounted(async () => {
345
+  // await init()
346
+})
212 347
 </script>
213 348
 
214 349
 <template>
@@ -245,7 +380,7 @@ onMounted(async () => {
245 380
     <!-- 标准指引 -->
246 381
     <view class="section">
247 382
       <view class="section-title">
248
-        任务信息
383
+        基础信息
249 384
       </view>
250 385
       <view class="standard-guide">
251 386
         <view class="guide-item">
@@ -268,8 +403,17 @@ onMounted(async () => {
268 403
           <view class="guide-label">
269 404
             执行人
270 405
           </view>
271
-          <view class="guide-value">
406
+          <view class="guide-value flex items-center justify-between">
272 407
             {{ taskInfo.executorName || '-' }}
408
+            <wd-select-picker
409
+              v-model="value"
410
+              title="选择转交人"
411
+              filterable :max="1" use-default-slot :columns="users" style="z-index:101" @confirm="handleTransferTask"
412
+            >
413
+              <wd-button v-if="buttons.transfer" type="text">
414
+                转交
415
+              </wd-button>
416
+            </wd-select-picker>
273 417
           </view>
274 418
         </view>
275 419
         <view class="guide-item">
@@ -320,33 +464,27 @@ onMounted(async () => {
320 464
             {{ getTaskTime() || '-' }}
321 465
           </view>
322 466
         </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">
467
+
468
+        <view v-if="taskInfo.descriptionFilesUrl && taskInfo.descriptionFilesUrl.length > 0" class="guide-item attachment-guide-item">
332 469
           <view class="guide-label">
333 470
             详情附件
334 471
           </view>
335 472
           <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>
473
+            <!-- 使用附件列表组件显示附件 -->
474
+            <AttachmentList
475
+              :files="taskInfo.descriptionFilesUrl"
476
+              :size="30"
477
+            />
348 478
           </view>
349 479
         </view>
480
+      </view>
481
+    </view>
482
+
483
+    <view v-if="taskInfo.status === 3" class="section">
484
+      <view class="section-title">
485
+        处理结果
486
+      </view>
487
+      <view class="standard-guide">
350 488
         <view v-if="taskResult.handleContent || taskInfo.cancelReason" class="guide-item">
351 489
           <view class="guide-label">
352 490
             处理情况
@@ -363,24 +501,17 @@ onMounted(async () => {
363 501
             {{ taskInfo.completeTime || taskInfo.cancelTime || '-' }}
364 502
           </view>
365 503
         </view>
366
-        <view v-if="taskResult.files && taskResult.files.length > 0" class="guide-item">
504
+        <view v-if="taskResult.files && taskResult.files.length > 0" class="guide-item attachment-guide-item">
367 505
           <view class="guide-label">
368 506
             处理附件
369 507
           </view>
370 508
           <view class="guide-value">
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>
509
+            <!-- 使用附件列表组件显示附件 -->
510
+            <AttachmentList
511
+              :files="taskResult.files"
512
+              :file-names="taskResult.fileNames"
513
+              :size="30"
514
+            />
384 515
           </view>
385 516
         </view>
386 517
       </view>
@@ -416,8 +547,38 @@ onMounted(async () => {
416 547
       </view>
417 548
     </view>
418 549
 
419
-    <view class="detail-btn" @tap="dealTaskEvent">
420
-      <text>处理任务</text>
550
+    <!-- 按钮容器 -->
551
+    <view class="btn-container">
552
+      <!-- 关闭任务按钮 -->
553
+      <wd-button
554
+        v-if="buttons.close"
555
+        type="error"
556
+        class="close-btn"
557
+        @click="closeTaskEvent"
558
+      >
559
+        关闭任务
560
+      </wd-button>
561
+      <!-- 处理任务按钮 -->
562
+      <wd-button
563
+        v-if="buttons.deal"
564
+        type="primary"
565
+        class="deal-btn"
566
+        @click="dealTaskEvent"
567
+      >
568
+        处理任务
569
+      </wd-button>
570
+    </view>
571
+
572
+    <!-- 查看详情按钮 -->
573
+    <view class="view-detail-container">
574
+      <wd-button
575
+        v-if="[3].includes(taskInfo.status)"
576
+        type="primary"
577
+        class="view-detail-btn"
578
+        @click="goDetail"
579
+      >
580
+        查看详情
581
+      </wd-button>
421 582
     </view>
422 583
     <!-- <view class="detail-btn" @tap="viewDetails2">
423 584
       <text>查 看 详 情2</text>
@@ -425,6 +586,8 @@ onMounted(async () => {
425 586
     <view class="detail-btn" @tap="viewDetails3">
426 587
       <text>查 看 详 情3</text>
427 588
     </view> -->
589
+    <wd-message-box />
590
+    <wd-toast />
428 591
   </view>
429 592
 </template>
430 593
 
@@ -432,7 +595,7 @@ onMounted(async () => {
432 595
 .details-box {
433 596
   display: flex;
434 597
   flex-direction: column;
435
-  padding: 30rpx 24rpx;
598
+  padding: 30rpx 24rpx 160rpx;
436 599
   background-color: #ffffff;
437 600
 
438 601
   .details-box-title {
@@ -506,6 +669,7 @@ onMounted(async () => {
506 669
   .standard-guide {
507 670
     .guide-item {
508 671
       display: flex;
672
+      align-items: center;
509 673
       margin-bottom: 24rpx;
510 674
 
511 675
       &:last-child {
@@ -517,6 +681,7 @@ onMounted(async () => {
517 681
         font-size: 28rpx;
518 682
         color: #6c757d;
519 683
         font-weight: 400;
684
+        line-height: 52rpx;
520 685
       }
521 686
 
522 687
       .guide-value {
@@ -524,6 +689,35 @@ onMounted(async () => {
524 689
         font-size: 28rpx;
525 690
         font-weight: 400;
526 691
         color: #31373d;
692
+        line-height: 52rpx;
693
+      }
694
+
695
+      // 调整转交按钮样式,确保不改变行高
696
+      .guide-value :deep(.wd-button) {
697
+        margin: 0;
698
+        padding: 0;
699
+        height: auto;
700
+        line-height: 52rpx;
701
+      }
702
+
703
+      .guide-value :deep(.wd-button__text) {
704
+        margin: 0;
705
+        padding: 0;
706
+        line-height: 52rpx;
707
+      }
708
+    }
709
+
710
+    // 附件列表特殊样式
711
+    .attachment-guide-item {
712
+      flex-direction: column;
713
+      align-items: flex-start;
714
+
715
+      .guide-label {
716
+        margin-bottom: 12rpx;
717
+      }
718
+
719
+      .guide-value {
720
+        width: 100%;
527 721
       }
528 722
     }
529 723
   }
@@ -547,25 +741,38 @@ onMounted(async () => {
547 741
     }
548 742
   }
549 743
 
550
-  // 查看详情按钮
551
-  .detail-btn {
744
+  // 按钮容器
745
+  .btn-container {
552 746
     display: flex;
553
-    align-items: center;
554
-    justify-content: center;
747
+    gap: 16rpx;
748
+    position: fixed;
749
+    bottom: 32rpx;
750
+    left: 24rpx;
751
+    right: 24rpx;
752
+    z-index: 100;
753
+  }
754
+
755
+  // 并排放置的按钮样式
756
+  .btn-container .wd-button {
757
+    flex: 1;
555 758
     height: 96rpx;
556
-    background-color: #215acd;
557
-    border-radius: 14rpx;
558
-    margin-bottom: 32rpx;
759
+    font-size: 32rpx;
760
+  }
761
+
762
+  // 查看详情按钮容器
763
+  .view-detail-container {
559 764
     position: fixed;
560 765
     bottom: 32rpx;
561 766
     left: 24rpx;
562 767
     right: 24rpx;
563
-    z-index: 999;
564
-    text {
565
-      font-size: 32rpx;
566
-      color: #ffffff;
567
-      font-weight: 500;
568
-    }
768
+    z-index: 100;
769
+  }
770
+
771
+  // 查看详情按钮样式
772
+  .view-detail-btn {
773
+    width: 100%;
774
+    height: 96rpx;
775
+    font-size: 32rpx;
569 776
   }
570 777
 
571 778
   // 更多内容样式

+ 160 - 173
src/pages/schedule/details/popup/anomaly.vue

@@ -8,13 +8,9 @@ import { getTags } from '@/api/system/tags'
8 8
 import { getAllUsers } from '@/api/system/users'
9 9
 import useUpload from '@/hooks/useUpload'
10 10
 // import { useUserStore } from '@/store'
11
-import calendar from '@/utils/calendar.vue'
11
+// import calendar from '@/utils/calendar.vue'
12 12
 
13 13
 const props = defineProps({
14
-  anomalyShow: {
15
-    type: Boolean,
16
-    default: false,
17
-  },
18 14
   taskId: {
19 15
     type: Number,
20 16
     default: 0,
@@ -27,6 +23,10 @@ const props = defineProps({
27 23
     type: Number,
28 24
     default: 0,
29 25
   },
26
+  anomalyData: {
27
+    type: Object,
28
+    default: () => ({}),
29
+  },
30 30
 })
31 31
 const emit = defineEmits(['update:anomalyShow', 'anomaly-submitted'])
32 32
 console.log(props.stationId)
@@ -34,9 +34,6 @@ const tags = ref<any[]>([])
34 34
 const users = ref<any[]>([])
35 35
 // const userStore = useUserStore()
36 36
 // const { userInfo } = storeToRefs(userStore)
37
-function handleClose() {
38
-  emit('update:anomalyShow', false)
39
-}
40 37
 
41 38
 async function getUserList() {
42 39
   console.log('调用getAllUsers时的stationId:', props.stationId)
@@ -96,20 +93,25 @@ const model = reactive<{
96 93
   correlationTaskId: '',
97 94
 })
98 95
 
99
-const form = ref()
100
-const loading = ref(false)
101
-const action: string
102
-  = ''
103
-
104
-const datePickerVisible = ref(false)
96
+// 更新 model 数据
97
+function updateModelData(data: any) {
98
+  if (!data)
99
+    return
105 100
 
106
-function handleClickCalendar() {
107
-  datePickerVisible.value = true
108
-}
109
-function onconfirmDate(date: string) {
110
-  model.deadline = date
101
+  model.isTransferTask = data.isTransferTask?.toString() || '1'
102
+  model.problemDescription = data.problemDescription || ''
103
+  model.handlingSuggestion = data.handlingSuggestion || ''
104
+  model.deadline = data.deadline || ''
105
+  model.executorId = data.executorId ? [Number(data.executorId)] : []
106
+  model.ccTo = data.ccTo ? data.ccTo.split(',').map((item: string) => Number(item.trim())) : []
107
+  model.problemPhoto = data.problemPhoto || []
108
+  model.tags = data.tags ? data.tags.split(',').map((item: string) => Number(item.trim())) : []
109
+  model.correlationTaskId = data.correlationTaskId?.toString() || ''
111 110
 }
112 111
 
112
+const form = ref()
113
+const loading = ref(false)
114
+
113 115
 // 上传问题照片
114 116
 const uploadProblemPhoto: any = (file, formData, options) => {
115 117
   const { customUpload } = useUpload({
@@ -123,24 +125,6 @@ function deleteProblemPhoto(index: number) {
123 125
   model.problemPhoto.splice(index, 1)
124 126
 }
125 127
 
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 128
 function handleSubmit() {
145 129
   form.value
146 130
     .validate()
@@ -168,12 +152,25 @@ function handleSubmit() {
168 152
 
169 153
         submitIssue(submitData)
170 154
           .then((res) => {
171
-            console.log(res, 'res')
172 155
             // 提交成功后向父组件传递数据
173
-            emit('anomaly-submitted', { checkItemId: props.checkItemId, data: submitData })
174
-            // showSuccess({
175
-            //   msg: '校验通过,数据已提交',
176
-            // })
156
+            emit('anomaly-submitted', { checkItemId: props.checkItemId, data: {
157
+              ...submitData,
158
+              problemPhoto: model.problemPhoto,
159
+            } })
160
+
161
+            console.log(res, 'res')
162
+            // 重置表单数据
163
+            model.isTransferTask = '1'
164
+            model.problemDescription = ''
165
+            model.handlingSuggestion = ''
166
+            model.deadline = ''
167
+            model.executorId = ''
168
+            model.ccTo = []
169
+            model.handlingMeasure = ''
170
+            model.handlingPhoto = []
171
+            model.problemPhoto = []
172
+            model.tags = []
173
+            model.correlationTaskId = ''
177 174
           })
178 175
           .catch((error) => {
179 176
             console.log(error, 'error')
@@ -202,180 +199,170 @@ watch(() => props.stationId, (newStationId) => {
202 199
     getUserList()
203 200
   }
204 201
 }, { immediate: false })
202
+
203
+defineExpose({
204
+  updateModelData,
205
+})
205 206
 </script>
206 207
 
207 208
 <template>
208 209
   <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="
210
+    <view class="popup-content">
211
+      <view class="content-header">
212
+        <text style="font-size: 34rpx; font-weight: 500; color: #464c51">
213
+          异常情况
214
+        </text>
215
+      </view>
216
+      <view class="content-main">
217
+        <wd-form ref="form" :model="model">
218
+          <view style="display: flex; flex-direction: column; gap: 40rpx; padding-bottom: 100rpx;">
219
+            <view>
220
+              <view style="font-size: 32rpx; font-weight: 500; color: #31373d">
221
+                是否转任务
222
+              </view>
223
+              <view
224
+                style="
232 225
                     font-size: 28rpx;
233 226
                     font-weight: 400;
234 227
                     color: #4e5969;
235 228
                     margin-bottom: 20rpx;
236 229
                   "
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>
230
+              >
231
+                转任务后会出现在执行人的任务日程视图中
250 232
               </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="
233
+              <wd-radio-group v-model="model.isTransferTask" shape="dot" inline>
234
+                <view style="display: flex; gap: 260rpx">
235
+                  <wd-radio value="1">
236
+                    是
237
+                  </wd-radio>
238
+                  <wd-radio value="0">
239
+                    否
240
+                  </wd-radio>
241
+                </view>
242
+              </wd-radio-group>
243
+            </view>
244
+            <wd-input
245
+              v-model="model.problemDescription"
246
+              label="问题描述"
247
+              label-width="100%"
248
+              prop="problemDescription"
249
+              clearable
250
+              placeholder="请输入"
251
+              :rules="[{ required: true, message: '请填写问题描述' }]"
252
+              custom-class="custom-vertical-input"
253
+            />
254
+            <view class="custom-vertical-select">
255
+              <view
256
+                style="
264 257
                     margin-bottom: 10rpx;
265 258
                     font-size: 28rpx;
266 259
                     font-weight: 500;
267 260
                     color: #333;
268 261
                   "
269
-                >
270
-                  标签
271
-                </view>
272
-                <wd-select-picker
273
-                  v-model="model.tags" :columns="tags" filterable
274
-                  :max="1"
275
-                  placeholder="请选择"
276
-                />
262
+              >
263
+                标签
277 264
               </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"
265
+              <wd-select-picker
266
+                v-model="model.tags" :columns="tags" filterable
267
+                :max="1"
268
+                placeholder="请选择"
286 269
               />
287
-              <view class="custom-vertical-select">
288
-                <view
289
-                  style="
270
+            </view>
271
+            <wd-input
272
+              v-model="model.handlingSuggestion"
273
+              label="处理建议"
274
+              label-width="100%"
275
+              prop="handlingSuggestion"
276
+              clearable
277
+              placeholder="请输入"
278
+              custom-class="custom-vertical-input"
279
+            />
280
+            <view class="custom-vertical-select">
281
+              <view
282
+                style="
290 283
                     margin-bottom: 10rpx;
291 284
                     font-size: 28rpx;
292 285
                     font-weight: 500;
293 286
                     color: #333;
294 287
                   "
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
-                />
288
+              >
289
+                <text style="color: red; margin-right: 4rpx; font-size: 28rpx;">* </text>截止时间
307 290
               </view>
308
-              <view>
309
-                <view
310
-                  style="
291
+              <wd-datetime-picker
292
+                v-model="model.deadline"
293
+                type="date"
294
+                use-second
295
+                :min-date="new Date().getTime()"
296
+                :max-date="new Date(Date.now() + 365 * 24 * 60 * 60 * 1000).getTime()"
297
+                prop="deadline"
298
+                :rules="[{ required: true, message: '请选择截止时间' }]"
299
+              />
300
+            </view>
301
+            <view>
302
+              <view
303
+                style="
311 304
                     margin-bottom: 10rpx;
312 305
                     font-size: 28rpx;
313 306
                     font-weight: 500;
314 307
                     color: #333;
315 308
                   "
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>
309
+              >
310
+                问题照片
327 311
               </view>
328
-              <view class="custom-vertical-select">
329
-                <view
330
-                  style="
312
+              <view>
313
+                <wd-upload
314
+                  v-model:file-list="model.problemPhoto"
315
+                  image-mode="aspectFill"
316
+                  :upload-method="uploadProblemPhoto"
317
+                  @delete="deleteProblemPhoto"
318
+                />
319
+              </view>
320
+            </view>
321
+            <view class="custom-vertical-select">
322
+              <view
323
+                style="
331 324
                     margin-bottom: 10rpx;
332 325
                     font-size: 28rpx;
333 326
                     font-weight: 500;
334 327
                     color: #333;
335 328
                   "
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
-                />
329
+              >
330
+                <text style="color: red; margin-right: 4rpx; font-size: 28rpx;">* </text>执行人
346 331
               </view>
347
-              <view class="custom-vertical-select">
348
-                <view
349
-                  style="
332
+              <wd-select-picker
333
+                v-model="model.executorId" :columns="users" filterable
334
+                placeholder="请选择"
335
+                prop="executorId"
336
+                :max="1"
337
+                :rules="[{ required: true, message: '请选择执行人' }]"
338
+              />
339
+            </view>
340
+            <view class="custom-vertical-select">
341
+              <view
342
+                style="
350 343
                     margin-bottom: 10rpx;
351 344
                     font-size: 28rpx;
352 345
                     font-weight: 500;
353 346
                     color: #333;
354 347
                   "
355
-                >
356
-                  抄送人
357
-                </view>
358
-                <wd-select-picker
359
-                  v-model="model.ccTo" :columns="users" filterable
360
-                  placeholder="请选择"
361
-                />
348
+              >
349
+                抄送人
362 350
               </view>
351
+              <wd-select-picker
352
+                v-model="model.ccTo" :columns="users" filterable
353
+                placeholder="请选择"
354
+              />
363 355
             </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>
356
+          </view>
357
+        </wd-form>
358
+      </view>
359
+      <view class="footer">
360
+        <button class="btn" :disabled="loading" @click="handleSubmit">
361
+          <wd-loading v-if="loading" size="20px" color="#ffffff" />
362
+          <text v-else>提 交</text>
363
+        </button>
372 364
       </view>
373
-    </wd-popup>
374
-    <calendar
375
-      v-model:date-picker-visible="datePickerVisible"
376
-      :detailedtime="false"
377
-      @confirm-date="onconfirmDate"
378
-    />
365
+    </view>
379 366
   </view>
380 367
 </template>
381 368
 

+ 0 - 72
src/pages/schedule/details/popup/centerpopup.vue

@@ -1,72 +0,0 @@
1
-<template>
2
-  <view>
3
-    <wd-popup
4
-      v-model="props.centershow"
5
-      custom-style="border-radius:32rpx;"
6
-      @close="handleClose"
7
-    >
8
-      <view class="popup-content">
9
-        <view style="font-size: 36rpx; font-weight: 500; color: #020917"
10
-          >说明</view
11
-        >
12
-        <view style="font-size: 32rpx; font-weight: 400; color: #343a45">
13
-          出入口清洁:【运营手册-第九章-第 2节】<br />
14
-          1、清洁要求:绿化带修葺良好,草地清洁;<br />
15
-          2、清洁频率:绿化带每非天繁忙时间至少检查清洁一次。
16
-        </view>
17
-        <view style="width: 100%">
18
-          <button class="btn" @click="handleClose">我 知 道 了</button>
19
-        </view>
20
-      </view>
21
-    </wd-popup>
22
-  </view>
23
-</template>
24
-
25
-<script setup lang="ts">
26
-import { defineProps, defineEmits } from 'vue'
27
-const props = defineProps({
28
-  centershow: {
29
-    type: Boolean,
30
-    default: false,
31
-  },
32
-  bottomshow: {
33
-    type: Boolean,
34
-    default: false,
35
-  },
36
-})
37
-const emit = defineEmits(['update:centershow', 'update:bottomshow'])
38
-const handleClose = () => {
39
-  emit('update:centershow', false)
40
-}
41
-</script>
42
-<style lang="scss" scoped>
43
-.custom-txt {
44
-  width: 400rpx;
45
-  height: 400rpx;
46
-  display: flex;
47
-  justify-content: center;
48
-  align-items: center;
49
-  border-radius: 32rpx;
50
-}
51
-.popup-content {
52
-  width: 600rpx;
53
-  display: flex;
54
-  flex-direction: column;
55
-  justify-content: space-between;
56
-  align-items: center;
57
-  padding: 40rpx;
58
-  box-sizing: border-box;
59
-  gap: 40rpx;
60
-  .btn {
61
-    width: 100%;
62
-    height: 80rpx;
63
-    background-color: #215acd;
64
-    color: white;
65
-    font-size: 32rpx;
66
-    border-radius: 12rpx;
67
-    font-weight: 400;
68
-    border: none;
69
-    outline: none;
70
-  }
71
-}
72
-</style>