3 Commits cad74acff6 ... d6cdb79f2c

Autor SHA1 Mensagem Data
  闪电 d6cdb79f2c Merge branch 'master' of http://39.164.159.226:3000/hnsh-smart-steward/smart-steward-mobile 3 semanas atrás
  闪电 e1f078bd60 mod: 任务详情 3 semanas atrás
  闪电 61d3d8228e mod: 检查任务 3 semanas atrás

+ 1 - 0
package.json

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

+ 8 - 0
pnpm-lock.yaml

@@ -81,6 +81,9 @@ importers:
81 81
       js-cookie:
82 82
         specifier: ^3.0.5
83 83
         version: 3.0.5
84
+      lodash-es:
85
+        specifier: ^4.17.23
86
+        version: 4.17.23
84 87
       pinia:
85 88
         specifier: 2.0.36
86 89
         version: 2.0.36(typescript@5.8.3)(vue@3.4.21(typescript@5.8.3))
@@ -4657,6 +4660,9 @@ packages:
4657 4660
     resolution: {integrity: sha512-gvVijfZvn7R+2qyPX8mAuKcFGDf6Nc61GdvGafQsHL0sBIxfKzA+usWn4GFC/bk+QdwPUD4kWFJLhElipq+0VA==}
4658 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 4666
   lodash.camelcase@4.3.0:
4661 4667
     resolution: {integrity: sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==}
4662 4668
 
@@ -12453,6 +12459,8 @@ snapshots:
12453 12459
     dependencies:
12454 12460
       p-locate: 6.0.0
12455 12461
 
12462
+  lodash-es@4.17.23: {}
12463
+
12456 12464
   lodash.camelcase@4.3.0: {}
12457 12465
 
12458 12466
   lodash.debounce@4.0.8: {}

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

@@ -0,0 +1,26 @@
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

@@ -0,0 +1,9 @@
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
+}

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

@@ -2,20 +2,41 @@ import { http } from '@/http/http'
2 2
 
3 3
 enum Api {
4 4
   base = '/scheduleTasks/tasks',
5
+  confirmBase = '/scheduleTasks/tasks/confirm',
6
+  uploadBase = '/taskAttachment/attachment',
7
+  photoBase = '/taskPhoto/photo',
5 8
 }
6 9
 
7 10
 export function transferTask(data: any) {
8
-  return http.put(`${Api.base}/forward`, data, {
9
-    successMessageMode: 'message',
10
-  })
11
+  return http.put(`${Api.base}/forward`, data)
11 12
 }
12 13
 // 关闭任务
13 14
 export function closeTask(data: any) {
14
-  return http.put(`${Api.base}/cancel`, data, {
15
-    successMessageMode: 'message',
16
-  })
15
+  return http.put(`${Api.base}/cancel`, data)
17 16
 }
18 17
 
19 18
 export function getTaskDetail(id: number) {
20 19
   return http.get(`${Api.base}/${id}`)
21 20
 }
21
+
22
+/**
23
+ * 确认完成任务
24
+ * @param id 任务id
25
+ * @returns
26
+ */
27
+export function confirmDoneTask(id: number) {
28
+  return http.put(`${Api.confirmBase}/${id}`)
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
+}

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

@@ -0,0 +1,14 @@
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

@@ -0,0 +1,15 @@
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
+}

+ 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>

+ 66 - 2
src/hooks/useUpload.ts

@@ -1,7 +1,7 @@
1 1
 import { ref } from 'vue'
2 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 6
 type TfileType = 'image' | 'file'
7 7
 type TImage = 'png' | 'jpg' | 'jpeg' | 'webp' | '*'
@@ -16,6 +16,18 @@ interface TOptions<T extends TfileType> {
16 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 31
 export default function useUpload<T extends TfileType>(options: TOptions<T> = {} as TOptions<T>) {
20 32
   const {
21 33
     formData = {},
@@ -130,7 +142,59 @@ export default function useUpload<T extends TfileType>(options: TOptions<T> = {}
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 200
 async function uploadFile({

+ 27 - 19
src/pages/schedule/details/taskmanagement/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;

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

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

+ 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>

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

@@ -0,0 +1,170 @@
1
+<script setup lang="ts">
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()
8
+
9
+const taskId = ref('')
10
+const checkId = ref('')
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
+// 页面加载时获取参数
123
+onMounted(() => {
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 || ''
131
+})
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>

+ 228 - 167
src/pages/schedule/details/taskdetails/index.vue

@@ -1,32 +1,148 @@
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
-        <view class="head-Card-title">未来路加油站</view>
110
+        <view class="head-Card-title">
111
+          {{ taskInfo.stationName || '' }}
112
+        </view>
6 113
         <view class="head-Card-tab">
7 114
           <view class="head-Card-tab-item">
8
-            <view class="tab-item-num">0</view>
9
-            <view class="tab-item-text">无拍照</view>
115
+            <view class="tab-item-num">
116
+              {{ statics.photoCount || 0 }}
117
+            </view>
118
+            <view class="tab-item-text">
119
+              无拍照
120
+            </view>
10 121
           </view>
11 122
           <view class="head-Card-tab-item">
12 123
             <view class="tab-item-Middle">
13
-              <view class="tab-item-num">0</view>
14
-              <view class="tab-item-text">异常总数</view>
124
+              <view class="tab-item-num">
125
+                {{ statics.totalItems || 0 }}
126
+              </view>
127
+              <view class="tab-item-text">
128
+                异常总数
129
+              </view>
15 130
             </view>
16 131
           </view>
17 132
           <view class="head-Card-tab-item">
18
-            <view class="tab-item-num"
19
-              >0 /
20
-              <text style="font-size: 24rpx; font-weight: 500">10</text>
133
+            <view class="tab-item-num">
134
+              {{ statics.score || 0 }} / {{ statics.totalScore || 0 }}
135
+            </view>
136
+            <view class="tab-item-text">
137
+              总得分
21 138
             </view>
22
-            <view class="tab-item-text">总得分</view>
23 139
           </view>
24 140
         </view>
25 141
       </view>
26 142
       <view>
27
-        <wd-tabs v-model="tab">
28
-          <block v-for="item in tabs" :key="item">
29
-            <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" />
30 146
           </block>
31 147
         </wd-tabs>
32 148
       </view>
@@ -34,207 +150,117 @@
34 150
 
35 151
     <scroll-view class="task-container" scroll-y>
36 152
       <view class="task-top-left">
37
-        <text
38
-          >异常数<text style="color: #31373d; font-weight: 600"> 0</text></text
39
-        >
40
-        <text style="color: #31373d; font-weight: 600">|</text>
41
-        <text
42
-          >得分<text style="color: #31373d; font-weight: 600"> 0/0</text></text
43
-        >
153
+        <text>
154
+          异常数 <text style="color: #31373d; font-weight: 600">
155
+            {{ currentCheckItem?.statics?.totalItems || 0 }}
156
+          </text>
157
+        </text>
158
+        <text style="color: #31373d; font-weight: 600"> | </text>
159
+        <text>
160
+          得分 <text style="color: #31373d; font-weight: 600">
161
+            {{ currentCheckItem?.statics?.score || 0 }} / {{ currentCheckItem?.statics?.totalScore || 0 }}
162
+          </text>
163
+        </text>
44 164
       </view>
45 165
       <view class="tasks">
46 166
         <view
47
-          v-for="(task, index) in currentTasks"
167
+          v-for="(task, index) in currentCheckItem?.items || []"
48 168
           :key="index"
49 169
           class="task-item"
50 170
         >
51 171
           <!-- 任务描述 -->
52 172
           <view class="task-description">
53
-            <text class="task-number">{{ index + 1 }}.</text>
54
-            <text class="task-text"
55
-              >{{ task.description }}
56
-              <wd-icon
57
-                v-if="task?.questionicon"
58
-                :name="task?.questionicon"
59
-                size="32rpx"
60
-                @click="() => (popupshow.centershow = true)"
61
-              ></wd-icon>
173
+            <text class="task-number">{{ +index + 1 }}.</text>
174
+            <text class="task-text">
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)" />
62 178
             </text>
63 179
           </view>
64 180
           <!-- 操作说明-图片示例 -->
65 181
           <view
182
+            v-if="task.exampleImageUrl.length > 0"
66 183
             class="task-instructions-example"
67
-            v-if="task.instructions && task.examples.length > 0"
68 184
           >
69
-            <view class="task-instructions">
70
-              <text class="instructions-text">{{ task.instructions }}</text>
185
+            <view v-if="task.photoPrompt" class="task-instructions">
186
+              <text class="instructions-text">{{ `拍照:${task.photoPrompt}` }}</text>
71 187
             </view>
72 188
             <view class="example-images">
73 189
               <view
74
-                v-for="(example, idx) in task.examples"
190
+                v-for="(example, idx) in task.exampleImageUrl"
75 191
                 :key="idx"
76 192
                 class="example-image"
77 193
               >
78
-                <image :src="example" mode="aspectFill"></image>
194
+                <image :src="example" mode="aspectFill" />
79 195
                 <text class="example-text">示例</text>
80 196
               </view>
81 197
             </view>
82 198
           </view>
83 199
           <!-- 已上传图片 -->
84
-          <view v-if="task.images.length > 0" class="uploaded-images">
200
+          <view v-if="task?.photoUrl?.length > 0" class="uploaded-images">
85 201
             <view
86
-              v-for="(image, idx) in task.images"
202
+              v-for="(image, idx) in task.photoUrl"
87 203
               :key="idx"
88 204
               class="uploaded-image"
89 205
             >
90
-              <image :src="image" mode="aspectFill"></image>
206
+              <!-- <image :src="image" mode="aspectFill" /> -->
207
+              <wd-img style="width: 100%; height: 100%" :src="image.url" :enable-preview="true" />
91 208
             </view>
92 209
           </view>
93 210
           <!-- 几号枪 -->
94
-          <view class="jihaoqiang" v-if="task.jihaoqiang">
95
-            <text class="jihaoqiang-text">{{ task.jihaoqiang }}</text>
211
+          <view v-if="task?.inputItem" class="whichGun">
212
+            <text class="whichGun-text">{{ task.inputItem }}</text>
96 213
           </view>
97 214
           <!-- 状态 -->
98 215
           <view class="task-status">
99 216
             <view>
100
-              <text class="status-text"
101
-                >整体:{{ task.status === 'normal' ? '正常' : '异常' }}</text
102
-              >
217
+              <text class="status-text">
218
+                整体:{{ task.result === 1 ? '正常' : task.result === 2 ? '异常' : '不适用' }}
219
+              </text>
103 220
             </view>
104 221
           </view>
105
-          <view class="exceptiondescription" v-if="task.exceptiondescription">
106
-            <view class="exceptiondescription-title">异常描述:</view>
107
-            <view class="exceptiondescription-content">
108
-              {{ task.exceptiondescription }}
222
+          <view v-if="task?.formOperationIssue?.id" class="anomalyDescription">
223
+            <view class="anomalyDescription-title">
224
+              异常描述:
225
+            </view>
226
+            <view class="anomalyDescription-content">
227
+              {{ task.formOperationIssue?.problemDescription || '' }}
109 228
             </view>
110 229
           </view>
111 230
         </view>
112 231
       </view>
113 232
     </scroll-view>
114
-    <centerpopup v-model:centershow="popupshow.centershow" />
115
-    <bottompopup v-model:bottomshow="popupshow.bottomshow" />
116
-    <!-- 确定按钮 -->
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>
117 251
     <view class="confirm-btn">
118
-      <button class="btn" @click="submitTasks">评 论</button>
252
+      <button class="btn" @click="submitTasks">
253
+        评 论
254
+      </button>
119 255
     </view>
120 256
   </view>
121 257
 </template>
122
-<script lang="ts" setup>
123
-import { ref, computed } from 'vue'
124
-import useUpload from '@/hooks/useUpload'
125
-import centerpopup from '../popup/centerpopup.vue'
126
-import bottompopup from '../popup/bottompopup.vue'
127
-const popupshow = ref({
128
-  centershow: false,
129
-  bottomshow: false,
130
-})
131
-// const popupshow = ref({})
132
-const tab = ref('前庭')
133
-const tabs = ['前庭', '自助服务区', '罐区', '便利店']
134
-
135
-// 定义任务类型
136
-interface Task {
137
-  id: string
138
-  type: string
139
-  description: string
140
-  icon?: string
141
-  instructions: string
142
-  examples: string[]
143
-  images: string[]
144
-  jihaoqiang: string
145
-  status: 'normal' | 'abnormal'
146
-  questionicon: string
147
-  exceptiondescription: string
148
-}
149
-
150
-// 任务数据
151
-const tasks = ref<Task[]>([
152
-  {
153
-    id: '1',
154
-    type: '前庭',
155
-    description:
156
-      '[AI] [卫生] 前庭海报整齐不歪斜,干净无灰尘污渍(立柱、灯箱、展架等)。',
157
-    instructions: '拍照:按照示例图拍摄前庭内海报宣传物,可上传多张照片',
158
-    examples: [
159
-      'https://picsum.photos/id/1018/200/150',
160
-      'https://picsum.photos/id/1019/200/150',
161
-    ],
162
-    images: [
163
-      'https://picsum.photos/id/1028/200/150',
164
-      'https://picsum.photos/id/1022/200/150',
165
-      'https://picsum.photos/id/1023/200/150',
166
-      'https://picsum.photos/id/1026/200/150',
167
-      'https://picsum.photos/id/1025/200/150',
168
-      'https://picsum.photos/id/1029/200/150',
169
-      'https://picsum.photos/id/1025/200/150',
170
-    ],
171
-    questionicon: 'help-circle',
172
-    jihaoqiang: '',
173
-    status: 'normal',
174
-    exceptiondescription: '',
175
-  },
176
-  {
177
-    id: '2',
178
-    type: '前庭',
179
-    description: '[卫生] 抽油枪栓是否出现空转或存在明显渗油。',
180
-    icon: 'help-circle',
181
-    instructions: '',
182
-    examples: [],
183
-    images: [],
184
-    questionicon: 'help-circle',
185
-    jihaoqiang: '12号枪',
186
-
187
-    status: 'normal',
188
-    exceptiondescription: '',
189
-  },
190
-  {
191
-    id: '3',
192
-    type: '前庭',
193
-    description:
194
-      '[AI] [卫生] 前庭海报整齐不歪斜,干净无灰尘污渍(立柱、灯箱、展架等)。',
195
-    instructions: '',
196
-    examples: [],
197
-    images: [],
198
-    questionicon: 'help-circle',
199
-    jihaoqiang: '13号枪',
200
-    status: 'abnormal',
201
-    exceptiondescription:
202
-      '异常描述异常描述异常描述异常描述异常描述异常描述异常描述异常描述异常描述异常描述异常描述',
203
-  },
204
-  {
205
-    id: '4',
206
-    type: '前庭',
207
-    description:
208
-      '[AI] [卫生] 前庭海报整齐不歪斜,干净无灰尘污渍(立柱、灯箱、展架等)。',
209
-    instructions: '',
210
-    examples: [],
211
-    images: [],
212
-    questionicon: 'help-circle',
213
-    jihaoqiang: '',
214
-    status: 'normal',
215
-    exceptiondescription: '',
216
-  },
217
-])
218 258
 
219
-// 根据当前标签筛选任务
220
-const currentTasks = computed(() => {
221
-  return tasks.value.filter((task) => task.type === tab.value)
222
-})
223
-// 提交任务
224
-const submitTasks = () => {
225
-  console.log('提交任务', tasks.value)
226
-  // 这里可以添加提交逻辑
227
-  uni.showToast({
228
-    title: '提交成功',
229
-    icon: 'success',
230
-  })
231
-}
232
-</script>
233 259
 <style lang="scss" scoped>
234 260
 .task-management {
235 261
   display: flex;
236 262
   flex-direction: column;
237
-  height: 1500rpx;
263
+  min-height: 100vh;
238 264
   overflow: hidden;
239 265
   .fixed-area {
240 266
     flex-shrink: 0;
@@ -307,7 +333,7 @@ const submitTasks = () => {
307 333
     // padding: 0 30rpx;
308 334
     .task-top-left {
309 335
       margin-left: 30rpx;
310
-      width: 230rpx;
336
+      width: 250rpx;
311 337
       height: 52rpx;
312 338
       background-color: #e8f3ff;
313 339
       border-radius: 12rpx;
@@ -397,7 +423,7 @@ const submitTasks = () => {
397 423
           }
398 424
         }
399 425
       }
400
-      .jihaoqiang {
426
+      .whichGun {
401 427
         width: 100%;
402 428
         height: 96rpx;
403 429
         background-color: #f5f5f5;
@@ -409,7 +435,7 @@ const submitTasks = () => {
409 435
         box-sizing: border-box;
410 436
         margin-bottom: 10rpx;
411 437
         border-radius: 20rpx;
412
-        .jihaoqiang-text {
438
+        .whichGun-text {
413 439
           font-size: 28rpx;
414 440
           color: #4e5969;
415 441
           font-weight: 500;
@@ -427,16 +453,16 @@ const submitTasks = () => {
427 453
           color: #31373d;
428 454
         }
429 455
       }
430
-      .exceptiondescription {
456
+      .anomalyDescription {
431 457
         width: 100%;
432 458
         border-bottom: 1px solid #eeeeee;
433 459
         font-size: 28rpx;
434 460
         padding-bottom: 40rpx;
435
-        .exceptiondescription-title {
461
+        .anomalyDescription-title {
436 462
           font-weight: 500;
437 463
           color: #31373d;
438 464
         }
439
-        .exceptiondescription-content {
465
+        .anomalyDescription-content {
440 466
           font-weight: 400;
441 467
           color: #4e5969;
442 468
         }
@@ -464,9 +490,14 @@ const submitTasks = () => {
464 490
 
465 491
   // 确定按钮
466 492
   .confirm-btn {
493
+    position: fixed;
494
+    bottom: 0;
495
+    left: 0;
496
+    right: 0;
467 497
     padding: 20rpx 30rpx;
468 498
     background-color: white;
469 499
     border-top: 1rpx solid #eee;
500
+    z-index: 100;
470 501
 
471 502
     .btn {
472 503
       width: 100%;
@@ -481,4 +512,34 @@ const submitTasks = () => {
481 512
     }
482 513
   }
483 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
+}
484 545
 </style>

+ 603 - 129
src/pages/schedule/details/index.vue

@@ -1,22 +1,371 @@
1
+<script setup lang="ts">
2
+import dayjs from 'dayjs'
3
+import { keyBy } from 'lodash-es'
4
+import { storeToRefs } from 'pinia'
5
+import { onMounted, ref } from 'vue'
6
+import { useRoute, useRouter } from 'vue-router'
7
+import { useMessage, useToast } from 'wot-design-uni'
8
+import { closeTask, confirmDoneTask, getTaskDetail, transferTask } from '@/api/schedule/task'
9
+import { getAllUsers } from '@/api/system/users'
10
+
11
+import AttachmentList from '@/components/AttachmentList.vue'
12
+import { useUserStore } from '@/store'
13
+import { authStatusOptions, taskLevelOptions, taskStatusOptions } from '@/utils/dicts'
14
+
15
+const userStore = useUserStore()
16
+
17
+const { userInfo } = storeToRefs(userStore)
18
+
19
+const wotMessage = useMessage()
20
+const toast = useToast()
21
+
22
+// 定义页面配置,注册路由
23
+definePage({
24
+  excludeLoginPath: true,
25
+})
26
+
27
+// 状态管理
28
+const isExpanded = ref(false)
29
+const router = useRouter()
30
+
31
+const route = useRoute()
32
+const taskId = ref(Number(route.query.id))
33
+
34
+const taskInfo: any = ref({})
35
+const taskOther: any = ref({})
36
+const taskResult: any = ref({})
37
+
38
+// 转交功能相关
39
+const users = ref<any[]>([])
40
+const value = ref<string[]>([])
41
+const customShow = ref<string>('')
42
+
43
+// 切换更多内容显示/隐藏
44
+function toggleMore() {
45
+  isExpanded.value = !isExpanded.value
46
+}
47
+
48
+async function dealConfirmTaskEvent() {
49
+// 二次弹框确认
50
+  wotMessage.confirm({
51
+    msg: `确认完成任务 ${taskInfo.value.taskName} 吗?`,
52
+    title: '确认完成',
53
+  }).then(async () => {
54
+    try {
55
+      await confirmDoneTask(taskInfo.value.id || 0)
56
+
57
+      toast.success('确认完成任务成功')
58
+      await init()
59
+    }
60
+    catch {
61
+      toast.error('操作失败')
62
+    }
63
+  }).catch(() => {
64
+    return
65
+  })
66
+}
67
+
68
+// 查看详情按钮点击事件
69
+async function dealTaskEvent() {
70
+  // 根据任务类型进行判断
71
+  let params: any = {}
72
+  let isModelPage = false
73
+  switch (taskInfo.value.formType) {
74
+    case 'inspection': {
75
+      uni.navigateTo({
76
+        url: `/pages/schedule/details/deal/inspection?id=${taskId.value}&checkId=${taskInfo.value.taskList[0].id}`,
77
+      })
78
+      return
79
+    }
80
+    case 'photo': {
81
+      uni.navigateTo({
82
+        url: `/pages/schedule/details/deal/photo?id=${taskInfo.value.taskList?.[0]?.id}`,
83
+      })
84
+      return
85
+    }
86
+    case 'confirm': {
87
+      await dealConfirmTaskEvent()
88
+
89
+      return
90
+    }
91
+    case 'form': {
92
+      if (
93
+        ['annualExamination_task', 'replacement_task'].includes(
94
+          taskInfo.value.subFormType,
95
+        )
96
+      ) {
97
+        try {
98
+          // await dealAnnualExaminationTask()
99
+        }
100
+        catch {
101
+          // ElMessage.error('处理任务失败')
102
+        }
103
+        return
104
+      }
105
+
106
+      params = {
107
+        taskId: taskInfo.value.taskList?.[0]?.id || '',
108
+        taskName: taskInfo.value.taskName,
109
+        taskList: taskInfo.value.taskList || [],
110
+        remark: taskResult.value.content,
111
+        stationId: taskInfo.value.stationId || 0,
112
+        subFormType: taskInfo.value.subFormType,
113
+        attachmentUrl: taskResult.value.images,
114
+      }
115
+
116
+      if (
117
+        [
118
+          'ambulance_check',
119
+          'emergency_protection_monthly_check',
120
+          'filter_maintenance',
121
+          'fire_extinguisher_check',
122
+        ].includes(taskInfo.value.subFormType)
123
+      ) {
124
+        isModelPage = true
125
+      }
126
+
127
+      break
128
+    }
129
+    case 'upload': {
130
+      uni.navigateTo({
131
+        url: `/pages/schedule/details/deal/upload?id=${taskInfo.value.taskList?.[0]?.id}`,
132
+      })
133
+      return
134
+    }
135
+    default: {
136
+      // return ElMessage.error('当前任务无法处理')
137
+    }
138
+  }
139
+  // if (isModelPage) {
140
+  //   pageModalApi.open()
141
+  // }
142
+  // else {
143
+  //   dealDrawerApi.setData(params).open()
144
+  // }
145
+}
146
+// 查看详情按钮点击事件2
147
+function viewDetails2() {
148
+  uni.navigateTo({
149
+    url: '/pages/schedule/details/taskmanagement/index2',
150
+  })
151
+}
152
+function viewDetails3() {
153
+  uni.navigateTo({
154
+    url: '/pages/schedule/details/taskdetails/index',
155
+  })
156
+}
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
+
213
+/**
214
+ * 获取任务时间
215
+ * @returns 任务时间字符串
216
+ */
217
+function getTaskTime() {
218
+  return `${dayjs(taskInfo.value.startTime).format('MM-DD HH:mm')}~${dayjs(taskInfo.value.endTime).format('MM-DD HH:mm')}`
219
+}
220
+
221
+/**
222
+ * 下载文件
223
+ * @param file 文件对象或链接
224
+ * @param fileName 文件名
225
+ */
226
+function downloadFile(file: any, fileName: string) {
227
+  // 这里实现文件下载逻辑
228
+  console.log('downloadFile:', file, fileName)
229
+}
230
+
231
+const buttons = ref({
232
+  deal: false,
233
+  transfer: false,
234
+  close: false,
235
+})
236
+
237
+async function init() {
238
+  const res: any = await getTaskDetail(taskId.value)
239
+  taskInfo.value = res.data || {}
240
+  if (!taskInfo.value.status) {
241
+    taskInfo.value.status = 0
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
+
258
+  // 假设从接口返回数据中获取taskOther和taskResult
259
+  taskOther.value.taskTypeName = taskInfo.value?.formTypeName || '-'
260
+
261
+  if (!taskInfo.value.executorName && taskInfo.value.taskList?.length) {
262
+    taskInfo.value.executorName = taskInfo.value.taskList[0].executorName
263
+  }
264
+
265
+  if (taskInfo.value.formType === 'photo') {
266
+    const result = taskInfo.value?.taskList?.[0] || {}
267
+    if (result.remark) {
268
+      taskResult.value.content = result.remark
269
+    }
270
+    if (result?.attachmentUrl?.length) {
271
+      taskResult.value.images = result.attachmentUrl
272
+    }
273
+  }
274
+  else if (taskInfo.value.formType === 'upload') {
275
+    // attachmentUrl
276
+    const result = taskInfo.value?.taskList?.[0] || {}
277
+    if (result.remark) {
278
+      taskResult.value.content = result.remark
279
+    }
280
+    if (result?.attachmentUrl?.length) {
281
+      taskResult.value.files = result.attachmentUrl
282
+      taskResult.value.fileNames = result.attachmentName.split(',')
283
+    }
284
+  }
285
+  else {
286
+    // taskResult.value = taskInfo.value?.taskList?.[0] || {};
287
+  }
288
+
289
+  if (taskInfo.value?.taskTemplate) {
290
+    taskOther.value.taskFrequencyName
291
+      = taskInfo.value?.taskTemplate?.taskFrequencyName || '-'
292
+  }
293
+
294
+  // 获取用户列表
295
+  if (taskInfo.value.stationId) {
296
+    await getUserList()
297
+  }
298
+}
299
+
300
+// 状态标签配置
301
+const statusConfig: any = keyBy(taskStatusOptions, 'value')
302
+
303
+// 优先级标签配置
304
+const priorityConfig: any = keyBy(taskLevelOptions, 'value')
305
+
306
+// 授权状态标签配置
307
+const authStatusConfig: any = keyBy(authStatusOptions, 'value')
308
+
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 () => {
341
+  await init()
342
+})
343
+
344
+onMounted(async () => {
345
+  // await init()
346
+})
347
+</script>
348
+
1 349
 <template>
2 350
   <view class="details-box">
3 351
     <!-- 标题区域 -->
4 352
     <view class="details-box-title">
5 353
       <view class="details-box-title-text">
6
-        巡站日检 AI
354
+        {{ taskInfo.taskName || '任务详情' }}
7 355
       </view>
8 356
     </view>
9 357
 
10 358
     <!-- 标签区域 -->
11 359
     <view class="tags-box">
12
-      <view class="tag tag-mandatory">
13
-        必做
360
+      <view class="tag">
361
+        {{ priorityConfig[taskInfo.level]?.label || taskInfo.level }}
14 362
       </view>
15
-      <view class="tag tag-authorize">
16
-        可授权
363
+
364
+      <view class="tag">
365
+        {{ statusConfig[`${taskInfo.status || 0}`]?.label || taskInfo.status }}
17 366
       </view>
18
-      <view class="tag tag-time">
19
-        按时完成
367
+      <view class="tag">
368
+        {{ authStatusConfig[taskInfo.authStatus || 0]?.label || taskInfo.authStatus }}
20 369
       </view>
21 370
     </view>
22 371
 
@@ -25,34 +374,46 @@
25 374
       <view class="section-title">
26 375
         任务描述
27 376
       </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>
377
+      <view class="task-description" v-html="taskInfo.description || '暂无'" />
42 378
     </view>
43 379
 
44 380
     <!-- 标准指引 -->
45 381
     <view class="section">
46 382
       <view class="section-title">
47
-        标准指引
383
+        基础信息
48 384
       </view>
49 385
       <view class="standard-guide">
50 386
         <view class="guide-item">
51 387
           <view class="guide-label">
52
-            执行人
388
+            场站
389
+          </view>
390
+          <view class="guide-value">
391
+            {{ taskInfo.stationName || '-' }}
392
+          </view>
393
+        </view>
394
+        <view class="guide-item">
395
+          <view class="guide-label">
396
+            标准指引
53 397
           </view>
54 398
           <view class="guide-value">
55
-            李玫玮(站长)
399
+            {{ taskInfo.standardGuide || '-' }}
400
+          </view>
401
+        </view>
402
+        <view class="guide-item">
403
+          <view class="guide-label">
404
+            执行人
405
+          </view>
406
+          <view class="guide-value flex items-center justify-between">
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>
56 417
           </view>
57 418
         </view>
58 419
         <view class="guide-item">
@@ -60,7 +421,39 @@
60 421
             计划时间
61 422
           </view>
62 423
           <view class="guide-value">
63
-            12月5日
424
+            {{ taskInfo.displayDate || '-' }}
425
+          </view>
426
+        </view>
427
+        <view class="guide-item">
428
+          <view class="guide-label">
429
+            创建
430
+          </view>
431
+          <view class="guide-value">
432
+            {{ taskInfo.createBy || '' }}
433
+          </view>
434
+        </view>
435
+        <view class="guide-item">
436
+          <view class="guide-label">
437
+            任务类型
438
+          </view>
439
+          <view class="guide-value">
440
+            {{ taskInfo.subFormTypeName || taskOther.taskTypeName || '-' }}
441
+          </view>
442
+        </view>
443
+        <view class="guide-item">
444
+          <view class="guide-label">
445
+            任务负责人
446
+          </view>
447
+          <view class="guide-value">
448
+            {{ taskInfo.taskLeaderName || '-' }}
449
+          </view>
450
+        </view>
451
+        <view class="guide-item">
452
+          <view class="guide-label">
453
+            任务频率
454
+          </view>
455
+          <view class="guide-value">
456
+            {{ taskOther.taskFrequencyName || '-' }}
64 457
           </view>
65 458
         </view>
66 459
         <view class="guide-item">
@@ -68,29 +461,71 @@
68 461
             任务时间
69 462
           </view>
70 463
           <view class="guide-value">
71
-            12月5日 00:00-12月5日 18:00
464
+            {{ getTaskTime() || '-' }}
465
+          </view>
466
+        </view>
467
+
468
+        <view v-if="taskInfo.descriptionFilesUrl && taskInfo.descriptionFilesUrl.length > 0" class="guide-item attachment-guide-item">
469
+          <view class="guide-label">
470
+            详情附件
471
+          </view>
472
+          <view class="guide-value">
473
+            <!-- 使用附件列表组件显示附件 -->
474
+            <AttachmentList
475
+              :files="taskInfo.descriptionFilesUrl"
476
+              :size="30"
477
+            />
478
+          </view>
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">
488
+        <view v-if="taskResult.handleContent || taskInfo.cancelReason" class="guide-item">
489
+          <view class="guide-label">
490
+            处理情况
491
+          </view>
492
+          <view class="guide-value">
493
+            {{ taskResult.handleContent || taskInfo.cancelReason || '' }}
72 494
           </view>
73 495
         </view>
74 496
         <view class="guide-item">
75 497
           <view class="guide-label">
76
-            完成时间
498
+            处理时间
499
+          </view>
500
+          <view class="guide-value">
501
+            {{ taskInfo.completeTime || taskInfo.cancelTime || '-' }}
502
+          </view>
503
+        </view>
504
+        <view v-if="taskResult.files && taskResult.files.length > 0" class="guide-item attachment-guide-item">
505
+          <view class="guide-label">
506
+            处理附件
77 507
           </view>
78 508
           <view class="guide-value">
79
-            12月5日
509
+            <!-- 使用附件列表组件显示附件 -->
510
+            <AttachmentList
511
+              :files="taskResult.files"
512
+              :file-names="taskResult.fileNames"
513
+              :size="30"
514
+            />
80 515
           </view>
81 516
         </view>
82 517
       </view>
83 518
     </view>
84 519
 
85 520
     <!-- 查看更多 -->
86
-    <view class="more-btn" @tap="toggleMore">
521
+    <!-- <view class="more-btn" @tap="toggleMore">
87 522
       <text class="arrow">点击查看更多</text>
88 523
       <wd-icon
89 524
         class="arrow"
90 525
         :name="isExpanded ? 'arrow-up' : 'arrow-down'"
91 526
         size="20px"
92 527
       />
93
-    </view>
528
+    </view> -->
94 529
 
95 530
     <!-- 更多内容(默认隐藏) -->
96 531
     <view v-if="isExpanded" class="more-content">
@@ -112,84 +547,62 @@
112 547
       </view>
113 548
     </view>
114 549
 
115
-    <view class="detail-btn" @tap="viewDetails1">
116
-      <text>查 看 详 情1</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>
117 582
     </view>
118
-    <view class="detail-btn" @tap="viewDetails2">
583
+    <!-- <view class="detail-btn" @tap="viewDetails2">
119 584
       <text>查 看 详 情2</text>
120 585
     </view>
121 586
     <view class="detail-btn" @tap="viewDetails3">
122 587
       <text>查 看 详 情3</text>
123
-    </view>
588
+    </view> -->
589
+    <wd-message-box />
590
+    <wd-toast />
124 591
   </view>
125 592
 </template>
126 593
 
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 594
 <style lang="scss" scoped>
182 595
 .details-box {
183 596
   display: flex;
184 597
   flex-direction: column;
185
-  padding: 30rpx 24rpx;
598
+  padding: 30rpx 24rpx 160rpx;
186 599
   background-color: #ffffff;
187 600
 
188 601
   .details-box-title {
189 602
     margin-bottom: 24rpx;
190 603
 
191 604
     .details-box-title-text {
192
-      font-size: 38rpx;
605
+      font-size: 48rpx;
193 606
       font-weight: 500;
194 607
       color: #31373d;
195 608
     }
@@ -198,29 +611,16 @@ onMounted(async () => {
198 611
   // 标签样式
199 612
   .tags-box {
200 613
     display: flex;
201
-    gap: 16rpx;
202
-    margin-bottom: 32rpx;
614
+    gap: 12rpx;
615
+    margin-bottom: 24rpx;
203 616
 
204 617
     .tag {
205 618
       padding: 8rpx 16rpx;
206 619
       border-radius: 14rpx;
207 620
       font-size: 24rpx;
208 621
       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
-      }
622
+      color: #215acd;
623
+      background-color: #e8f3ff;
224 624
     }
225 625
   }
226 626
 
@@ -238,20 +638,30 @@ onMounted(async () => {
238 638
 
239 639
   // 任务描述样式
240 640
   .task-description {
241
-    background-color: #f8f9fa;
242
-    padding: 24rpx;
243
-    border-radius: 12rpx;
641
+    padding: 0 0 24rpx 0;
642
+    border-bottom: 1rpx solid #e5e5e5;
643
+
644
+    // 针对HTML内容的样式
645
+    & :deep(ol),
646
+    & :deep(ul) {
647
+      padding-left: 32rpx;
648
+      margin: 0;
649
+    }
244 650
 
245
-    .task-item {
246
-      font-size: 27rpx;
651
+    & :deep(li) {
652
+      font-size: 28rpx;
247 653
       font-weight: 400;
248
-      color: #4e5969;
654
+      color: #31373d;
249 655
       line-height: 52rpx;
250
-      margin-bottom: 12rpx;
656
+      margin-bottom: 8rpx;
657
+    }
251 658
 
252
-      &:last-child {
253
-        margin-bottom: 0;
254
-      }
659
+    & :deep(p) {
660
+      font-size: 28rpx;
661
+      font-weight: 400;
662
+      color: #31373d;
663
+      line-height: 52rpx;
664
+      margin: 0 0 12rpx 0;
255 665
     }
256 666
   }
257 667
 
@@ -259,6 +669,7 @@ onMounted(async () => {
259 669
   .standard-guide {
260 670
     .guide-item {
261 671
       display: flex;
672
+      align-items: center;
262 673
       margin-bottom: 24rpx;
263 674
 
264 675
       &:last-child {
@@ -270,6 +681,7 @@ onMounted(async () => {
270 681
         font-size: 28rpx;
271 682
         color: #6c757d;
272 683
         font-weight: 400;
684
+        line-height: 52rpx;
273 685
       }
274 686
 
275 687
       .guide-value {
@@ -277,6 +689,35 @@ onMounted(async () => {
277 689
         font-size: 28rpx;
278 690
         font-weight: 400;
279 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%;
280 721
       }
281 722
     }
282 723
   }
@@ -300,20 +741,38 @@ onMounted(async () => {
300 741
     }
301 742
   }
302 743
 
303
-  // 查看详情按钮
304
-  .detail-btn {
744
+  // 按钮容器
745
+  .btn-container {
305 746
     display: flex;
306
-    align-items: center;
307
-    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;
308 758
     height: 96rpx;
309
-    background-color: #215acd;
310
-    border-radius: 14rpx;
311
-    margin-bottom: 32rpx;
312
-    text {
313
-      font-size: 32rpx;
314
-      color: #ffffff;
315
-      font-weight: 500;
316
-    }
759
+    font-size: 32rpx;
760
+  }
761
+
762
+  // 查看详情按钮容器
763
+  .view-detail-container {
764
+    position: fixed;
765
+    bottom: 32rpx;
766
+    left: 24rpx;
767
+    right: 24rpx;
768
+    z-index: 100;
769
+  }
770
+
771
+  // 查看详情按钮样式
772
+  .view-detail-btn {
773
+    width: 100%;
774
+    height: 96rpx;
775
+    font-size: 32rpx;
317 776
   }
318 777
 
319 778
   // 更多内容样式
@@ -337,5 +796,20 @@ onMounted(async () => {
337 796
       }
338 797
     }
339 798
   }
799
+
800
+  // 附件列表样式
801
+  .attachment-list {
802
+    display: flex;
803
+    flex-wrap: wrap;
804
+    gap: 12rpx;
805
+
806
+    .attachment-tag {
807
+      margin-bottom: 12rpx;
808
+    }
809
+  }
810
+}
811
+:deep(.attachment-tag) {
812
+  margin-right: 12rpx;
813
+  margin-bottom: 12rpx;
340 814
 }
341 815
 </style>

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

@@ -0,0 +1,497 @@
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
+  taskId: {
15
+    type: Number,
16
+    default: 0,
17
+  },
18
+  stationId: {
19
+    type: Number,
20
+    default: 0,
21
+  },
22
+  checkItemId: {
23
+    type: Number,
24
+    default: 0,
25
+  },
26
+  anomalyData: {
27
+    type: Object,
28
+    default: () => ({}),
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
+
38
+async function getUserList() {
39
+  console.log('调用getAllUsers时的stationId:', props.stationId)
40
+  const res: any = await getAllUsers({
41
+    pageNum: 10000,
42
+    stationId: props.stationId,
43
+  })
44
+  if (res.code === 200) {
45
+    users.value = res.data.map((item: any) => ({
46
+      label: item.nickName,
47
+      value: item.userId,
48
+    }))
49
+  }
50
+}
51
+
52
+async function getTagsList(name: string = '') {
53
+  const params: any = {
54
+    pageNum: 10000,
55
+  }
56
+  if (name) {
57
+    params.tagName = name
58
+  }
59
+  const res: any = await getTags(params)
60
+  if (res.code === 200) {
61
+    tags.value = res.rows.map((item: any) => ({
62
+      label: item.tagName,
63
+      value: item.id,
64
+    }))
65
+  }
66
+}
67
+
68
+const { success: showSuccess } = useToast()
69
+
70
+const model = reactive<{
71
+  isTransferTask: string
72
+  problemDescription: string
73
+  handlingSuggestion: string
74
+  deadline: string
75
+  executorId: string
76
+  ccTo: string[]
77
+  handlingMeasure: string
78
+  handlingPhoto: string[]
79
+  problemPhoto: any
80
+  tags: string[]
81
+  correlationTaskId: string
82
+}>({
83
+  isTransferTask: '1',
84
+  problemDescription: '',
85
+  handlingSuggestion: '',
86
+  deadline: '',
87
+  executorId: '',
88
+  ccTo: [],
89
+  handlingMeasure: '',
90
+  handlingPhoto: [],
91
+  problemPhoto: [],
92
+  tags: [],
93
+  correlationTaskId: '',
94
+})
95
+
96
+// 更新 model 数据
97
+function updateModelData(data: any) {
98
+  if (!data)
99
+    return
100
+
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() || ''
110
+}
111
+
112
+const form = ref()
113
+const loading = ref(false)
114
+
115
+// 上传问题照片
116
+const uploadProblemPhoto: any = (file, formData, options) => {
117
+  const { customUpload } = useUpload({
118
+    fileType: 'image',
119
+  })
120
+  return customUpload(file, formData, options)
121
+}
122
+
123
+// 删除问题照片
124
+function deleteProblemPhoto(index: number) {
125
+  model.problemPhoto.splice(index, 1)
126
+}
127
+
128
+function handleSubmit() {
129
+  form.value
130
+    .validate()
131
+    .then(({ valid, errors }) => {
132
+      if (valid) {
133
+        // 准备提交数据
134
+        const submitData = {
135
+          isTransferTask: Number.parseInt(model.isTransferTask),
136
+          problemDescription: model.problemDescription,
137
+          handlingSuggestion: model.handlingSuggestion,
138
+          deadline: dayjs(model.deadline).format('YYYY-MM-DD 23:59:59'),
139
+          executorId: model.executorId?.[0] || '',
140
+          ccTo: model?.ccTo?.join(',') || '',
141
+          tags: model?.tags?.join(',') || '',
142
+          problemPhoto: (model.problemPhoto?.map((item: any) => item.name) || []).join(','),
143
+          correlationTaskId: props.taskId,
144
+          checkItemId: props.checkItemId,
145
+          // 其他字段根据实际情况处理
146
+        }
147
+
148
+        console.log('提交数据:', submitData)
149
+
150
+        // 开始提交,显示 loading
151
+        loading.value = true
152
+
153
+        submitIssue(submitData)
154
+          .then((res) => {
155
+            // 提交成功后向父组件传递数据
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 = ''
174
+          })
175
+          .catch((error) => {
176
+            console.log(error, 'error')
177
+          })
178
+          .finally(() => {
179
+            // 提交完成,隐藏 loading
180
+            loading.value = false
181
+            emit('update:anomalyShow', false)
182
+          })
183
+      }
184
+    })
185
+    .catch((error) => {
186
+      console.log(error, 'error')
187
+    })
188
+}
189
+
190
+onMounted(() => {
191
+  getTagsList()
192
+  getUserList()
193
+})
194
+
195
+// 监听stationId变化,当stationId从0变为实际值时重新获取用户列表
196
+watch(() => props.stationId, (newStationId) => {
197
+  console.log('stationId变化:', newStationId)
198
+  if (newStationId > 0) {
199
+    getUserList()
200
+  }
201
+}, { immediate: false })
202
+
203
+defineExpose({
204
+  updateModelData,
205
+})
206
+</script>
207
+
208
+<template>
209
+  <view>
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="
225
+                    font-size: 28rpx;
226
+                    font-weight: 400;
227
+                    color: #4e5969;
228
+                    margin-bottom: 20rpx;
229
+                  "
230
+              >
231
+                转任务后会出现在执行人的任务日程视图中
232
+              </view>
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="
257
+                    margin-bottom: 10rpx;
258
+                    font-size: 28rpx;
259
+                    font-weight: 500;
260
+                    color: #333;
261
+                  "
262
+              >
263
+                标签
264
+              </view>
265
+              <wd-select-picker
266
+                v-model="model.tags" :columns="tags" filterable
267
+                :max="1"
268
+                placeholder="请选择"
269
+              />
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="
283
+                    margin-bottom: 10rpx;
284
+                    font-size: 28rpx;
285
+                    font-weight: 500;
286
+                    color: #333;
287
+                  "
288
+              >
289
+                <text style="color: red; margin-right: 4rpx; font-size: 28rpx;">* </text>截止时间
290
+              </view>
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="
304
+                    margin-bottom: 10rpx;
305
+                    font-size: 28rpx;
306
+                    font-weight: 500;
307
+                    color: #333;
308
+                  "
309
+              >
310
+                问题照片
311
+              </view>
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="
324
+                    margin-bottom: 10rpx;
325
+                    font-size: 28rpx;
326
+                    font-weight: 500;
327
+                    color: #333;
328
+                  "
329
+              >
330
+                <text style="color: red; margin-right: 4rpx; font-size: 28rpx;">* </text>执行人
331
+              </view>
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="
343
+                    margin-bottom: 10rpx;
344
+                    font-size: 28rpx;
345
+                    font-weight: 500;
346
+                    color: #333;
347
+                  "
348
+              >
349
+                抄送人
350
+              </view>
351
+              <wd-select-picker
352
+                v-model="model.ccTo" :columns="users" filterable
353
+                placeholder="请选择"
354
+              />
355
+            </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>
364
+      </view>
365
+    </view>
366
+  </view>
367
+</template>
368
+
369
+<style lang="scss" scoped>
370
+.custom-txt {
371
+  width: 400rpx;
372
+  height: 400rpx;
373
+  display: flex;
374
+  justify-content: center;
375
+  align-items: center;
376
+  border-radius: 32rpx;
377
+}
378
+.popup-content {
379
+  min-height: 500rpx;
380
+  display: flex;
381
+  flex-direction: column;
382
+  align-items: center;
383
+  padding: 30rpx;
384
+  // padding: 0 30rpx 30rpx 30rpx;
385
+  box-sizing: border-box;
386
+  overflow-y: auto;
387
+  .content-header {
388
+    // height: 80rpx;
389
+    width: 100%;
390
+    display: flex;
391
+    justify-content: center;
392
+    align-items: center;
393
+    margin-bottom: 30rpx;
394
+    // position: sticky;
395
+    // top: 50rpx;
396
+    // background-color: #fff;
397
+    // border: 1px solid red;
398
+    // z-index: 50;
399
+    text {
400
+      font-size: 32rpx;
401
+      font-weight: 600;
402
+    }
403
+  }
404
+  .content-main {
405
+    width: 100%;
406
+    .wd-select-picker,
407
+    .wd-datetime-picker {
408
+      width: 100%;
409
+      & .wd-cell {
410
+        padding: 0;
411
+      }
412
+      & .wd-cell__right {
413
+        width: 100%;
414
+        & .wd-cell__value {
415
+          width: 100%;
416
+          & .wd-select-picker__input,
417
+          & .wd-datetime-picker__input {
418
+            width: 100%;
419
+            height: 70rpx;
420
+            border: 1px solid #e5e5e5;
421
+            border-radius: 8rpx;
422
+            padding: 0 20rpx;
423
+            font-size: 28rpx;
424
+          }
425
+        }
426
+      }
427
+    }
428
+    .custom-vertical-select {
429
+      margin-bottom: 30rpx;
430
+    }
431
+    .wd-input {
432
+      // margin-bottom: 30rpx;
433
+      padding: 0px;
434
+      &.custom-vertical-input {
435
+        display: flex;
436
+        flex-direction: column;
437
+        gap: 10rpx;
438
+        margin-bottom: 30rpx;
439
+
440
+        .wd-input__label {
441
+          width: 100% !important;
442
+          margin-bottom: 10rpx;
443
+          font-size: 28rpx;
444
+          font-weight: 500;
445
+          color: #333;
446
+        }
447
+
448
+        .wd-input__body {
449
+          width: 100%;
450
+          position: relative;
451
+        }
452
+
453
+        /* 使用Vue 3深度选择器确保样式能正确应用到输入框 */
454
+        :deep(.wd-input__inner) {
455
+          width: 20rem !important;
456
+          height: 70rpx !important;
457
+          border: 1px solid #e5e5e5 !important;
458
+          border-radius: 8rpx !important;
459
+          padding: 0 60rpx 0 20rpx !important;
460
+          font-size: 28rpx !important;
461
+        }
462
+
463
+        /* 确保图标容器在输入框内部 */
464
+        :deep(.wd-input__suffix) {
465
+          position: absolute;
466
+          right: 20rpx;
467
+          top: 50%;
468
+          transform: translateY(-50%);
469
+          z-index: 1;
470
+        }
471
+      }
472
+    }
473
+  }
474
+}
475
+.footer {
476
+  position: fixed;
477
+  bottom: 0;
478
+  left: 0;
479
+  right: 0;
480
+  padding: 30rpx;
481
+  padding-bottom: calc(30rpx + env(safe-area-inset-bottom));
482
+  background-color: white;
483
+  box-shadow: 0 -2rpx 10rpx rgba(0, 0, 0, 0.05);
484
+  z-index: 10;
485
+  .btn {
486
+    width: 100%;
487
+    height: 80rpx;
488
+    background-color: #215acd;
489
+    color: white;
490
+    font-size: 32rpx;
491
+    border-radius: 12rpx;
492
+    font-weight: 400;
493
+    border: none;
494
+    outline: none;
495
+  }
496
+}
497
+</style>

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

@@ -1,374 +0,0 @@
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>

+ 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>

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

@@ -1,541 +0,0 @@
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

@@ -0,0 +1,55 @@
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,7 +67,8 @@ export default defineConfig(({ command, mode }) => {
67 67
     plugins: [
68 68
       UniLayouts(),
69 69
       UniPlatform(),
70
-      UniManifest(),
70
+      // 暂时禁用 UniManifest 插件,以解决权限错误
71
+      // UniManifest(),
71 72
       UniEcharts(),
72 73
       UniPages({
73 74
         exclude: ['**/components/**/**.*'],
@@ -79,17 +80,18 @@ export default defineConfig(({ command, mode }) => {
79 80
         dts: 'src/types/uni-pages.d.ts',
80 81
       }),
81 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 95
       // UniXXX 需要在 Uni 之前引入
94 96
       // 若存在改变 pages.json 的插件,请将 UniKuRoot 放置其后
95 97
       UniKuRoot({