Browse Source

feat: 新增漏检统计相关功能及基础API

1. 新增漏检物品分类、岗位、漏检管理的API接口
2. 新增漏检统计页面路由配置
3. 新增漏检记录新增页面,包含完整的表单功能和数据交互逻辑
huoyi 3 weeks ago
parent
commit
24fa06aeb4

+ 62 - 0
src/api/missedInspection/missedInspection.js

@@ -0,0 +1,62 @@
1
+import request from '@/utils/request'
2
+
3
+// 查询漏检列表
4
+export function listMissedInspection(query) {
5
+  return request({
6
+    url: '/blocked/missReview/list',
7
+    method: 'get',
8
+    params: query
9
+  })
10
+}
11
+
12
+// 查询漏检详细
13
+export function getMissedInspection(id) {
14
+  return request({
15
+    url: `/blocked/missReview/${id}`,
16
+    method: 'get'
17
+  })
18
+}
19
+
20
+// 新增漏检
21
+export function addMissedInspection(data) {
22
+  return request({
23
+    url: '/blocked/missReview',
24
+    method: 'post',
25
+    data: data
26
+  })
27
+}
28
+
29
+// 修改漏检
30
+export function updateMissedInspection(data) {
31
+  return request({
32
+    url: '/blocked/missReview',
33
+    method: 'post',
34
+    data: data
35
+  })
36
+}
37
+
38
+// 删除漏检
39
+export function delMissedInspection(ids) {
40
+  return request({
41
+    url: `/blocked/missReview/${ids}`,
42
+    method: 'delete'
43
+  })
44
+}
45
+
46
+// 导出漏检
47
+export function exportMissedInspection(data) {
48
+  return request({
49
+    url: '/blocked/missReview/export',
50
+    method: 'post',
51
+    data: data
52
+  })
53
+}
54
+
55
+// 下载导入模板
56
+export function downloadTemplate() {
57
+  return request({
58
+    url: '/blocked/missReview/importTemplate',
59
+    method: 'get',
60
+    responseType: 'blob'
61
+  })
62
+}

+ 10 - 0
src/api/system/category.js

@@ -0,0 +1,10 @@
1
+import request from '@/utils/request'
2
+
3
+// 查询漏检物品分类列表
4
+export function listCategory(query) {
5
+  return request({
6
+    url: '/system/category/list',
7
+    method: 'get',
8
+    params: query
9
+  })
10
+}

+ 8 - 0
src/api/system/dept/dept.js

@@ -29,3 +29,11 @@ export function getDeptDetail(deptId) {
29
     method: 'get',
29
     method: 'get',
30
   })
30
   })
31
 }
31
 }
32
+// 查询部门列表
33
+export function listDept(query) {
34
+  return request({
35
+    url: '/system/dept/list',
36
+    method: 'get',
37
+    params: query
38
+  })
39
+}

+ 51 - 0
src/api/system/position.js

@@ -0,0 +1,51 @@
1
+import request from '@/utils/request'
2
+
3
+// 查询位置列表
4
+export function listPosition(query) {
5
+  return request({
6
+    url: '/system/position/list',
7
+    method: 'get',
8
+    params: query
9
+  })
10
+}
11
+
12
+// 查询位置详细
13
+export function getPosition(id) {
14
+  return request({
15
+    url: '/system/position/' + id,
16
+    method: 'get'
17
+  })
18
+}
19
+
20
+// 新增位置
21
+export function addPosition(data) {
22
+  return request({
23
+    url: '/system/position',
24
+    method: 'post',
25
+    data: data
26
+  })
27
+}
28
+
29
+// 修改位置
30
+export function updatePosition(data) {
31
+  return request({
32
+    url: '/system/position',
33
+    method: 'put',
34
+    data: data
35
+  })
36
+}
37
+
38
+// 删除位置
39
+export function delPosition(id) {
40
+  return request({
41
+    url: '/system/position/' + id,
42
+    method: 'delete'
43
+  })
44
+}
45
+//查询位置列表树形结构
46
+export function treePosition() {
47
+  return request({
48
+    url: '/system/position/listTree',
49
+    method: 'get'
50
+  })
51
+}

+ 8 - 0
src/api/system/user.js

@@ -125,4 +125,12 @@ export function getPostAllList() {
125
     url: '/system/post/listAllTree',
125
     url: '/system/post/listAllTree',
126
     method: 'get',
126
     method: 'get',
127
   })
127
   })
128
+}
129
+//根据用户ID和角色编码列表查询各级直属领导列表
130
+export function selectUserLeaderListByCondition(data) {
131
+  return request({
132
+    url: '/system/user/selectUserLeaderListByCondition',
133
+    method: 'post',
134
+    data: data
135
+  })
128
 }
136
 }

+ 6 - 0
src/pages.json

@@ -315,6 +315,12 @@
315
       }
315
       }
316
     },
316
     },
317
     {
317
     {
318
+      "path": "pages/missedInspectionList/index",
319
+      "style": {
320
+        "navigationBarTitleText": "漏检统计"
321
+      }
322
+    },
323
+    {
318
       "path": "pages/seizureRecord/index",
324
       "path": "pages/seizureRecord/index",
319
       "style": {
325
       "style": {
320
         "navigationBarTitleText": "查获记录"
326
         "navigationBarTitleText": "查获记录"

+ 509 - 0
src/pages/missedInspectionList/index.vue

@@ -0,0 +1,509 @@
1
+<template>
2
+  <home-container>
3
+  
4
+
5
+      <scroll-view class="form-scroll" scroll-y>
6
+        <view class="form-container">
7
+          <uni-forms ref="formRef" :rules="rules" :modelValue="formData" label-position="left" err-show-type="modal" :label-width="100">
8
+            <view class="form-row">
9
+              <uni-forms-item label="大队" name="brigadeId" required>
10
+                <uni-data-picker :localdata="brigadeOptions" popup-title="请选择大队" 
11
+                  v-model="formData.brigadeId" @change="handleFormBrigadeChange" />
12
+              </uni-forms-item>
13
+            </view>
14
+            <view class="form-row">
15
+              <uni-forms-item label="被回查人" name="reviewedUserId" required>
16
+                <uni-data-picker :localdata="personOptions" popup-title="请选择被回查人" 
17
+                  v-model="formData.reviewedUserId" @change="handleReviewedUserChange" />
18
+              </uni-forms-item>
19
+            </view>
20
+            <view class="form-row">
21
+              <uni-forms-item label="回查日期" name="reviewDate" required>
22
+                <uni-datetime-picker type="date" v-model="formData.reviewDate" />
23
+              </uni-forms-item>
24
+            </view>
25
+            <view class="form-row">
26
+              <uni-forms-item label="漏检时间" name="missCheckTime" required>
27
+                <uni-datetime-picker type="datetime" v-model="formData.missCheckTime" />
28
+              </uni-forms-item>
29
+            </view>
30
+            <view class="form-row">
31
+              <uni-forms-item label="漏检时间段" name="missCheckTimePeriod" required>
32
+                <uni-data-picker :localdata="blockedTimePeriodOptions" popup-title="请选择漏检时间段" 
33
+                  v-model="formData.missCheckTimePeriod" />
34
+              </uni-forms-item>
35
+            </view>
36
+            <view class="form-row">
37
+              <uni-forms-item label="上岗位置" name="channelId" required>
38
+                <uni-data-picker :localdata="areaOptions" popup-title="请选择上岗位置" 
39
+                  v-model="formData.channelId" />
40
+              </uni-forms-item>
41
+            </view>
42
+            <view class="form-row">
43
+              <uni-forms-item label="漏检物品" name="missCheckItem" required>
44
+                <uni-data-picker :localdata="missCheckItemOptions" popup-title="请选择漏检物品" 
45
+                  v-model="formData.missCheckItem" />
46
+              </uni-forms-item>
47
+            </view>
48
+            <view class="form-row">
49
+              <uni-forms-item label="分管主管" name="supervisorId" required>
50
+                <uni-data-picker :localdata="supervisorOptions" popup-title="请选择分管主管" 
51
+                  v-model="formData.supervisorId" />
52
+              </uni-forms-item>
53
+            </view>
54
+            <view class="form-row">
55
+              <uni-forms-item label="代管主管" name="actingSupervisorId">
56
+                <uni-data-picker :localdata="supervisorOptions" popup-title="请选择代管主管" 
57
+                  v-model="formData.actingSupervisorId" />
58
+              </uni-forms-item>
59
+            </view>
60
+            <view class="form-row">
61
+              <uni-forms-item label="分管班组长" name="teamLeaderId" required>
62
+                <uni-data-picker :localdata="teamLeaderOptions" popup-title="请选择分管班组长" 
63
+                  v-model="formData.teamLeaderId" />
64
+              </uni-forms-item>
65
+            </view>
66
+            <view class="form-row">
67
+              <uni-forms-item label="物品位置" name="itemLocation" required>
68
+                <uni-data-picker :localdata="blockedItemPositionOptions" popup-title="请选择物品位置" 
69
+                  v-model="formData.itemLocation" />
70
+              </uni-forms-item>
71
+            </view>
72
+            <view class="form-row">
73
+              <uni-forms-item label="简单/难" name="difficultyLevel" required>
74
+                <uni-data-picker :localdata="difficultyLevelOptions" popup-title="请选择难度" 
75
+                  v-model="formData.difficultyLevel" />
76
+              </uni-forms-item>
77
+            </view>
78
+            <view class="form-row">
79
+              <uni-forms-item label="回查人" name="reviewUserId" required>
80
+                <uni-data-picker :localdata="personOptions" popup-title="请选择回查人" 
81
+                  v-model="formData.reviewUserId" />
82
+              </uni-forms-item>
83
+            </view>
84
+            <view class="form-row">
85
+              <uni-forms-item label="判别类型" name="discriminationType" required>
86
+                <uni-data-picker :localdata="discriminationTypeOptions" popup-title="请选择判别类型" 
87
+                  v-model="formData.discriminationType" />
88
+              </uni-forms-item>
89
+            </view>
90
+            <view class="form-row">
91
+              <uni-forms-item label="是否追回" name="isRecovered" required>
92
+                <uni-data-picker :localdata="isRecoveredOptions" popup-title="请选择是否追回" 
93
+                  v-model="formData.isRecovered" />
94
+              </uni-forms-item>
95
+            </view>
96
+            <view class="form-row">
97
+              <uni-forms-item label="开机年限" name="machineOperatingYears" required>
98
+                <uni-data-picker :localdata="blockedOperatingYearsOptions" popup-title="请选择开机年限" 
99
+                  v-model="formData.machineOperatingYears" />
100
+              </uni-forms-item>
101
+            </view>
102
+            <view class="form-row">
103
+              <uni-forms-item label="证书级别" name="certificateLevel" required>
104
+                <uni-data-picker :localdata="certificateLevelOptions" popup-title="请选择证书级别" 
105
+                  v-model="formData.certificateLevel" />
106
+              </uni-forms-item>
107
+            </view>
108
+            <view class="form-row">
109
+              <uni-forms-item label="人员性别" name="gender" required>
110
+                <uni-data-picker :localdata="genderOptions" popup-title="请选择人员性别" 
111
+                  v-model="formData.gender" />
112
+              </uni-forms-item>
113
+            </view>
114
+            <view class="form-row">
115
+              <uni-forms-item label="漏检原因分类" name="missCheckReasonCategory" required>
116
+                <uni-data-picker :localdata="blockedMissCheckReasonOptions" popup-title="请选择漏检原因分类" 
117
+                  v-model="formData.missCheckReasonCategory" />
118
+              </uni-forms-item>
119
+            </view>
120
+            <view class="form-row">
121
+              <uni-forms-item label="月考成绩" name="monthlyAssessment" required>
122
+                <uni-data-picker :localdata="blockedMonthlyExamResultOptions" popup-title="请选择月考成绩" 
123
+                  v-model="formData.monthlyAssessment" />
124
+              </uni-forms-item>
125
+            </view>
126
+            <view class="form-row">
127
+              <uni-forms-item label="本月自测有无漏检" name="selfTestHasMissCheck" required>
128
+                <uni-easyinput type="number" v-model="formData.selfTestHasMissCheck" placeholder="请输入0-2的整数" />
129
+              </uni-forms-item>
130
+            </view>
131
+          </uni-forms>
132
+        </view>
133
+      </scroll-view>
134
+
135
+      <view class="submit-bar">
136
+        <view class="submit-btn" @click="submitForm">提交</view>
137
+      </view>
138
+  
139
+  </home-container>
140
+</template>
141
+
142
+<script>
143
+import HomeContainer from "@/components/HomeContainer.vue";
144
+import { listPosition } from '@/api/system/position';
145
+import { addMissedInspection } from '@/api/missedInspection/missedInspection';
146
+import { listDept } from '@/api/system/dept/dept';
147
+import { getUserList, selectUserLeaderListByCondition, getUserInfoById } from '@/api/system/user';
148
+import { listCategory } from '@/api/system/category';
149
+import { formatTime } from '@/utils/formatUtils';
150
+import useDictMixin from '@/utils/dict';
151
+
152
+export default {
153
+  name: 'MissedInspectionAdd',
154
+  components: {
155
+    HomeContainer
156
+  },
157
+  mixins: [useDictMixin],
158
+  data() {
159
+    return {
160
+      formData: {
161
+        id: null,
162
+        brigadeId: null,
163
+        brigadeName: null,
164
+        reviewedUserId: null,
165
+        reviewedUserName: null,
166
+        reviewDate: '',
167
+        missCheckTime: '',
168
+        missCheckTimePeriod: null,
169
+        channelId: null,
170
+        channelName: null,
171
+        missCheckItem: null,
172
+        supervisorId: null,
173
+        supervisorName: null,
174
+        actingSupervisorId: null,
175
+        actingSupervisorName: null,
176
+        teamLeaderId: null,
177
+        teamLeaderName: null,
178
+        itemLocation: null,
179
+        difficultyLevel: null,
180
+        reviewUserId: null,
181
+        reviewUserName: null,
182
+        discriminationType: null,
183
+        isRecovered: null,
184
+        machineOperatingYears: null,
185
+        certificateLevel: null,
186
+        gender: null,
187
+        missCheckReasonCategory: null,
188
+        monthlyAssessment: null,
189
+        selfTestHasMissCheck: 0
190
+      },
191
+      
192
+      rules: {
193
+        brigadeId: [{ required: true, errorMessage: '大队不能为空' }],
194
+        reviewedUserId: [{ required: true, errorMessage: '被回查人不能为空' }],
195
+        reviewDate: [{ required: true, errorMessage: '回查日期不能为空' }],
196
+        missCheckTime: [{ required: true, errorMessage: '漏检时间不能为空' }],
197
+        missCheckTimePeriod: [{ required: true, errorMessage: '漏检时间段不能为空' }],
198
+        channelId: [{ required: true, errorMessage: '上岗位置不能为空' }],
199
+        missCheckItem: [{ required: true, errorMessage: '漏检物品不能为空' }],
200
+        supervisorId: [{ required: true, errorMessage: '分管主管不能为空' }],
201
+        teamLeaderId: [{ required: true, errorMessage: '分管班组长不能为空' }],
202
+        itemLocation: [{ required: true, errorMessage: '物品位置不能为空' }],
203
+        difficultyLevel: [{ required: true, errorMessage: '简单/难不能为空' }],
204
+        reviewUserId: [{ required: true, errorMessage: '回查人不能为空' }],
205
+        discriminationType: [{ required: true, errorMessage: '判别类型不能为空' }],
206
+        isRecovered: [{ required: true, errorMessage: '是否追回不能为空' }],
207
+        machineOperatingYears: [{ required: true, errorMessage: '开机年限不能为空' }],
208
+        certificateLevel: [{ required: true, errorMessage: '证书级别不能为空' }],
209
+        gender: [{ required: true, errorMessage: '人员性别不能为空' }],
210
+        missCheckReasonCategory: [{ required: true, errorMessage: '漏检原因分类不能为空' }],
211
+        monthlyAssessment: [{ required: true, errorMessage: '月考成绩不能为空' }],
212
+        selfTestHasMissCheck: [{ required: true, errorMessage: '本月自测有无漏检不能为空' }]
213
+      },
214
+      
215
+      brigadeOptions: [],
216
+      personOptions: [],
217
+      teamLeaderOptions: [],
218
+      supervisorOptions: [],
219
+      areaOptions: [],
220
+      missCheckItemOptions: [],
221
+      
222
+      discriminationTypeOptions: [],
223
+      blockedTimePeriodOptions: [],
224
+      blockedItemPositionOptions: [],
225
+      blockedMonthlyExamResultOptions: [],
226
+      blockedOperatingYearsOptions: [],
227
+      blockedMissCheckReasonOptions: [],
228
+      difficultyLevelOptions: [
229
+        { text: '简单', value: '简单' },
230
+        { text: '难', value: '难' }
231
+      ],
232
+      isRecoveredOptions: [
233
+        { text: '是', value: 1 },
234
+        { text: '否', value: 0 }
235
+      ],
236
+      certificateLevelOptions: [
237
+        { text: '高级', value: '高级' },
238
+        { text: '中级', value: '中级' },
239
+        { text: '初级', value: '初级' }
240
+      ],
241
+      genderOptions: [
242
+        { text: '男', value: '男' },
243
+        { text: '女', value: '女' }
244
+      ]
245
+    }
246
+  },
247
+  onLoad() {
248
+    this.initData();
249
+  },
250
+  methods: {
251
+    initData() {
252
+      this.getUserAll();
253
+      this.getDeptList();
254
+      this.getPositionList();
255
+      this.getTeamLeaderOptions();
256
+      this.getSupervisorOptions();
257
+      this.getMissCheckItemOptions();
258
+      this.getDictData();
259
+      this.formData.missCheckTime = formatTime(new Date(), 'YYYY-MM-DD hh:mm');
260
+      this.formData.reviewDate = formatTime(new Date(), 'YYYY-MM-DD');
261
+    },
262
+    
263
+    async getDictData() {
264
+      const dict = await this.useDict(
265
+        'discrimination_type',
266
+        'blocked_time_period',
267
+        'blocked_item_position',
268
+        'blocked_monthly_exam_result',
269
+        'blocked_operating_years',
270
+        'blocked_miss_check_reason'
271
+      );
272
+      
273
+      const formatDict = (options) => {
274
+        return (options || []).map(item => ({
275
+          value: item.value,
276
+          text: item.label || item.text
277
+        }));
278
+      };
279
+      
280
+      this.discriminationTypeOptions = formatDict(dict.discrimination_type);
281
+      this.blockedTimePeriodOptions = formatDict(dict.blocked_time_period);
282
+      this.blockedItemPositionOptions = formatDict(dict.blocked_item_position);
283
+      this.blockedMonthlyExamResultOptions = formatDict(dict.blocked_monthly_exam_result);
284
+      this.blockedOperatingYearsOptions = formatDict(dict.blocked_operating_years);
285
+      this.blockedMissCheckReasonOptions = formatDict(dict.blocked_miss_check_reason);
286
+    },
287
+    
288
+    getDeptList() {
289
+      listDept({}).then(res => {
290
+        const deptList = res.data || [];
291
+        this.brigadeOptions = deptList.filter(item => item.deptType === 'BRIGADE' && [311, 314, 315].includes(item.deptId)).map(item => ({
292
+          value: item.deptId,
293
+          text: item.deptName
294
+        }));
295
+      });
296
+    },
297
+    
298
+    getUserAll() {
299
+      getUserList({ pageNum: 1, pageSize: 1000 }).then(res => {
300
+        this.personOptions = (res.rows || []).map(item => ({
301
+          value: item.userId,
302
+          text: item.nickName
303
+        }));
304
+      });
305
+    },
306
+    
307
+    getPositionList() {
308
+      listPosition({ positionType: 'CHANNEL' }).then(res => {
309
+        this.areaOptions = (res.data || []).map(item => ({
310
+          value: item.id,
311
+          text: item.name
312
+        }));
313
+      });
314
+    },
315
+    
316
+    getTeamLeaderOptions() {
317
+      selectUserLeaderListByCondition({ roleKeyList: ['banzuzhang'] }).then(res => {
318
+        this.teamLeaderOptions = (res.data || []).map(item => ({
319
+          value: item.userId,
320
+          text: item.nickName
321
+        }));
322
+      });
323
+    },
324
+    
325
+    getSupervisorOptions() {
326
+      selectUserLeaderListByCondition({ roleKeyList: ['kezhang'] }).then(res => {
327
+        this.supervisorOptions = (res.data || []).map(item => ({
328
+          value: item.userId,
329
+          text: item.nickName
330
+        }));
331
+      });
332
+    },
333
+    
334
+    getMissCheckItemOptions() {
335
+      listCategory().then(res => {
336
+        const data = res.data || [];
337
+        this.missCheckItemOptions = this.flattenTree(data);
338
+      });
339
+    },
340
+    
341
+    flattenTree(data, parentLabel = '') {
342
+      const result = [];
343
+      data.forEach(item => {
344
+        const label = parentLabel ? `${parentLabel} / ${item.name}` : item.name;
345
+        if (item.children && item.children.length > 0) {
346
+          result.push(...this.flattenTree(item.children, label));
347
+        } else {
348
+          result.push({
349
+            value: item.name,
350
+            text: label
351
+          });
352
+        }
353
+      });
354
+      return result;
355
+    },
356
+    
357
+    handleReviewedUserChange(e) {
358
+      let userId = e.detail.value;
359
+      if (Array.isArray(userId)) {
360
+        userId = userId[0]?.value;
361
+      } else if (userId && typeof userId === 'object') {
362
+        userId = userId.value;
363
+      }
364
+      if (userId) {
365
+        selectUserLeaderListByCondition({ userId: userId, roleKeyList: ['banzuzhang'] }).then(res => {
366
+          if (res.data && res.data.length > 0) {
367
+            this.formData.teamLeaderId = res.data[0].userId;
368
+          }
369
+        });
370
+        selectUserLeaderListByCondition({ userId: userId, roleKeyList: ['kezhang'] }).then(res => {
371
+          if (res.data && res.data.length > 0) {
372
+            this.formData.supervisorId = res.data[0].userId;
373
+          }
374
+        });
375
+        getUserInfoById(userId).then(res => {
376
+          const data = res?.data;
377
+          if (data) {
378
+            this.formData.gender = data.sex === '0' ? '男' : '女';
379
+          }
380
+        });
381
+      } else {
382
+        this.formData.supervisorId = null;
383
+        this.formData.teamLeaderId = null;
384
+        this.formData.gender = null;
385
+      }
386
+    },
387
+    
388
+    handleFormBrigadeChange(e) {
389
+      const brigadeId = e.detail.value;
390
+      const brigade = this.brigadeOptions.find(item => item.value === brigadeId);
391
+      this.formData.brigadeName = brigade ? brigade.label : null;
392
+    },
393
+    
394
+    submitForm() {
395
+      this.$refs.formRef.validate((valid) => {
396
+        if (valid) {
397
+          this.mapNames();
398
+          
399
+          addMissedInspection(this.formData).then(() => {
400
+            uni.showToast({ title: '新增成功', icon: 'success' });
401
+            setTimeout(() => {
402
+              uni.navigateBack();
403
+            }, 1500);
404
+          }).catch(() => {
405
+            uni.showToast({ title: '新增失败', icon: 'none' });
406
+          });
407
+        }
408
+      });
409
+    },
410
+    
411
+    mapNames() {
412
+      if (this.formData.brigadeId) {
413
+        const brigade = this.brigadeOptions.find(item => item.value === this.formData.brigadeId);
414
+        this.formData.brigadeName = brigade ? brigade.text : null;
415
+      }
416
+      
417
+      if (this.formData.reviewedUserId) {
418
+        const reviewedUser = this.personOptions.find(item => item.value === this.formData.reviewedUserId);
419
+        this.formData.reviewedUserName = reviewedUser ? reviewedUser.text : null;
420
+      }
421
+      
422
+      if (this.formData.channelId) {
423
+        const channel = this.areaOptions.find(item => item.value === this.formData.channelId);
424
+        this.formData.channelName = channel ? channel.text : null;
425
+      }
426
+      
427
+      if (this.formData.supervisorId) {
428
+        const supervisor = this.supervisorOptions.find(item => item.value === this.formData.supervisorId);
429
+        this.formData.supervisorName = supervisor ? supervisor.text : null;
430
+      }
431
+      
432
+      if (this.formData.actingSupervisorId) {
433
+        const actingSupervisor = this.supervisorOptions.find(item => item.value === this.formData.actingSupervisorId);
434
+        this.formData.actingSupervisorName = actingSupervisor ? actingSupervisor.text : null;
435
+      }
436
+      
437
+      if (this.formData.teamLeaderId) {
438
+        const teamLeader = this.teamLeaderOptions.find(item => item.value === this.formData.teamLeaderId);
439
+        this.formData.teamLeaderName = teamLeader ? teamLeader.text : null;
440
+      }
441
+      
442
+      if (this.formData.reviewUserId) {
443
+        const reviewUser = this.personOptions.find(item => item.value === this.formData.reviewUserId);
444
+        this.formData.reviewUserName = reviewUser ? reviewUser.text : null;
445
+      }
446
+    }
447
+  }
448
+}
449
+</script>
450
+
451
+<style scoped>
452
+.missed-inspection-page {
453
+  width: 100%;
454
+  min-height: 100vh;
455
+  background-color: #f5f7fa;
456
+  display: flex;
457
+  flex-direction: column;
458
+}
459
+
460
+.page-header {
461
+  background-color: #fff;
462
+  padding: 16px;
463
+  border-bottom: 1px solid #f0f0f0;
464
+}
465
+
466
+.page-title {
467
+  font-size: 18px;
468
+  font-weight: bold;
469
+  color: #333;
470
+}
471
+
472
+.form-scroll {
473
+  flex: 1;
474
+  
475
+}
476
+
477
+.form-container {
478
+  background-color: #fff;
479
+  border-radius: 12px;
480
+  padding: 16px;
481
+  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
482
+}
483
+
484
+.form-row {
485
+  margin-bottom: 16px;
486
+}
487
+
488
+.submit-bar {
489
+  position: fixed;
490
+  bottom: 0;
491
+  left: 0;
492
+  right: 0;
493
+  background-color: #fff;
494
+  padding: 12px 16px;
495
+  border-top: 1px solid #f0f0f0;
496
+  padding-bottom: calc(12px + env(safe-area-inset-bottom));
497
+}
498
+
499
+.submit-btn {
500
+  height: 44px;
501
+  display: flex;
502
+  align-items: center;
503
+  justify-content: center;
504
+  border-radius: 8px;
505
+  font-size: 16px;
506
+  color: #fff;
507
+  background-color: #409EFF;
508
+}
509
+</style>