Browse Source

Merge remote-tracking branch 'origin/develop' into develop

# Conflicts:
#	src/router/index.ts
#	src/view/Home2.vue
jiajunchen 1 week ago
parent
commit
3e4bb2d9ac

+ 10
- 0
src/router/index.ts View File

624
 			name: '安全文化编辑',
624
 			name: '安全文化编辑',
625
 			component: () => import('@/view/knowledge/CultureList.vue')
625
 			component: () => import('@/view/knowledge/CultureList.vue')
626
 		},
626
 		},
627
+		{
628
+			path: '/knowledge/project',
629
+			name: '项目案例库',
630
+			component: () => import('@/view/knowledge/project.vue')
631
+		},
632
+		{
633
+			path: '/projectList',
634
+			name: '项目案例库编辑',
635
+			component: () => import('@/view/knowledge/projectList.vue')
636
+		},
627
 	]
637
 	]
628
 })
638
 })
629
 
639
 

+ 4
- 0
src/view/Home2.vue View File

248
           <img src="../../public/images/zd.png" width="45rpx" />
248
           <img src="../../public/images/zd.png" width="45rpx" />
249
           <span class="vanicon_text">安全环保文化</span>
249
           <span class="vanicon_text">安全环保文化</span>
250
         </van-grid-item>
250
         </van-grid-item>
251
+        <van-grid-item to="/knowledge/project">
252
+          <img src="../../public/images/zd.png" width="45rpx" />
253
+          <span class="vanicon_text">项目案例库</span>
254
+        </van-grid-item>
251
       </van-grid>
255
       </van-grid>
252
     </div>
256
     </div>
253
 
257
 

+ 55
- 7
src/view/knowledge/accident.vue View File

24
                   <template #label>
24
                   <template #label>
25
                       <div>案例编号:{{ item.caseNumber }} </div>
25
                       <div>案例编号:{{ item.caseNumber }} </div>
26
                       <div>事故等级:{{ item.accidentLevel }}</div>
26
                       <div>事故等级:{{ item.accidentLevel }}</div>
27
-                      <div>下载量:{{ item.downloadCount }} 浏览量:{{item.viewCount}}</div>
27
+                      <div> 浏览量:{{item.viewCount}}</div>
28
 
28
 
29
 <!--                    <div style="width: 112px" :class="getStatusClass(item.isFinish)">
29
 <!--                    <div style="width: 112px" :class="getStatusClass(item.isFinish)">
30
                       类型:
30
                       类型:
42
             </template>
42
             </template>
43
 
43
 
44
             <template #right>
44
             <template #right>
45
-              <van-button  square class="delete-button" text="删除" @click="handleDelete(item)" />
45
+              <van-button v-if="item.canDelete" square class="delete-button" text="删除" @click="handleDelete(item)" />
46
             </template>
46
             </template>
47
           </van-swipe-cell>
47
           </van-swipe-cell>
48
         </div>
48
         </div>
72
 const onClickLeft = () => {
72
 const onClickLeft = () => {
73
   history.back();
73
   history.back();
74
 };
74
 };
75
+// const headers = ref({
76
+//   token: localStorage.getItem('token'),
77
+//   userId: localStorage.getItem('userId'),
78
+//   dept: JSON.parse(localStorage.getItem('dept'))[0].deptCode
79
+// });
75
 const headers = ref({
80
 const headers = ref({
76
-  token: localStorage.getItem('token'),
77
-  userId: localStorage.getItem('userId'),
78
-  dept: JSON.parse(localStorage.getItem('dept'))[0].deptCode
81
+  token: localStorage.getItem('token') || '',
82
+  userId: localStorage.getItem('userId') || '', // 防止 null/undefined
83
+  dept: JSON.parse(localStorage.getItem('dept'))?.[0]?.deptCode || ''
79
 });
84
 });
80
 const switchIconState = (idx) => {
85
 const switchIconState = (idx) => {
81
   openStatus.value[idx] = !openStatus.value[idx]
86
   openStatus.value[idx] = !openStatus.value[idx]
123
   })
128
   })
124
 }
129
 }
125
 
130
 
126
-const edits = (row) => {
127
-  const isOwner = String(row.addId) === currentUserId;
131
+const edits = async (row) => {
132
+  const currentUserId = localStorage.getItem('userId');
133
+  const addId = row.addId;
134
+
135
+  const isOwner = String(addId).trim().toLowerCase() === String(currentUserId).trim().toLowerCase();
136
+
137
+  // 更新浏览量
138
+  await updateViewCount(row);
139
+
128
   kz.value = true;
140
   kz.value = true;
129
   form.value = { ...row };
141
   form.value = { ...row };
130
   router.push({ path: "/accidentList",
142
   router.push({ path: "/accidentList",
152
   downloadCount:''
164
   downloadCount:''
153
 });
165
 });
154
 
166
 
167
+const updateViewCount = async (item) => {
168
+  try {
169
+    const payload = { ...item };
170
+    // 将浏览量 +1
171
+    payload.viewCount = String((Number(payload.viewCount) || 0) + 1);
172
+
173
+    const url = '/sgsafe/Manager/saveAccident';
174
+    const param = {
175
+      json: JSON.stringify(payload)
176
+    };
177
+
178
+    const response = await proxy.$axios.post(url, param);
179
+    if (response.data.code === '0' || response.data.code === 0) {
180
+      // 更新成功后,更新本地列表中的浏览量显示
181
+      const index = resultData.value.findIndex(data => data.id === item.id);
182
+      if (index !== -1) {
183
+        resultData.value[index].viewCount = payload.viewCount;
184
+      }
185
+    }
186
+  } catch (error) {
187
+    console.error('更新浏览量失败:', error);
188
+    // 即使更新失败也不阻塞页面跳转
189
+  }
190
+};
155
 
191
 
156
 const isRefreshing = ref(false);
192
 const isRefreshing = ref(false);
157
 const isLoading = ref(false);
193
 const isLoading = ref(false);
255
   handleSearch();
291
   handleSearch();
256
 });
292
 });
257
 
293
 
294
+
258
 const handleSearch = () => {
295
 const handleSearch = () => {
259
 /!*  currentPage.value = 1;
296
 /!*  currentPage.value = 1;
260
   isFinished.value = false;
297
   isFinished.value = false;
307
 };
344
 };
308
 
345
 
309
 const handleDetailLook = (row) => {
346
 const handleDetailLook = (row) => {
347
+
310
   form.value = { ...row };
348
   form.value = { ...row };
311
   proxy.$router.push({
349
   proxy.$router.push({
312
     name: 'taiZhang_detail',
350
     name: 'taiZhang_detail',
319
 const deleteData=ref({})
357
 const deleteData=ref({})
320
 
358
 
321
 const handleDelete = (item) => {
359
 const handleDelete = (item) => {
360
+  const currentUserId = headers.value.userId;
361
+  const addId = item.addId;
322
 
362
 
363
+  if (!currentUserId || !addId || String(addId).trim() !== String(currentUserId).trim()) {
364
+    showToast({
365
+      type: 'warning',
366
+      message: '无权限删除!只能删除自己添加的案例。'
367
+    });
368
+    return;
369
+  }
370
+  
323
   deleteData.value=item
371
   deleteData.value=item
324
   const now = new Date();
372
   const now = new Date();
325
   const year = now.getFullYear();
373
   const year = now.getFullYear();

+ 199
- 21
src/view/knowledge/accidentList.vue View File

109
 let planInfo = {}
109
 let planInfo = {}
110
 const  userName1=localStorage.getItem('userName');
110
 const  userName1=localStorage.getItem('userName');
111
 const isEdit = ref(route.query.mark === '1');
111
 const isEdit = ref(route.query.mark === '1');
112
-
112
+const isReadOnly = ref(route.query.readOnly === 'true');
113
+const isCaseSubmitted = computed(() => isReadOnly.value && isEdit.value);
113
 const result=ref('')
114
 const result=ref('')
114
 const fromVue=ref({})
115
 const fromVue=ref({})
115
 if (route.query.mark) {
116
 if (route.query.mark) {
116
   planInfo = JSON.parse(route.query.mark)
117
   planInfo = JSON.parse(route.query.mark)
117
 }
118
 }
118
 console.log(planInfo);
119
 console.log(planInfo);
120
+// 新增模式
119
 if (planInfo==-1){
121
 if (planInfo==-1){
120
-  result.value=guid()
122
+  const caseNumber = generateCode();
123
+  const fileId = ref()
124
+  result.value= caseNumber
125
+  //初始化 fromVue
126
+  fromVue.value = {
127
+    caseNumber: caseNumber,
128
+    caseTitle: '',
129
+    caseSource: '',
130
+    accidentLevel: '',
131
+    accidentDept: '',
132
+    accidentLocation: '',
133
+    accidentTime: '',
134
+    accidentType: '',
135
+    accidentTags: '',
136
+    casualtyCount: '',
137
+    accidentSummary: '',
138
+    preventiveMeasures: '',
139
+    fileId: fileId,
140
+    viewCount: '0',
141
+    downloadCount: '0'
142
+  };
121
   console.log( result.value);
143
   console.log( result.value);
122
 }
144
 }
123
 
145
 
141
 const distestType=ref(false)
163
 const distestType=ref(false)
142
 if (planInfo==1) {
164
 if (planInfo==1) {
143
   console.log(planInfo);
165
   console.log(planInfo);
144
-  title = '修改事故案例'
166
+  title = '查看事故案例'
145
   fromVue.value= JSON.parse(route.query.data)
167
   fromVue.value= JSON.parse(route.query.data)
168
+  if (!fromVue.value.fileId) {
169
+    const newFileId = guid();
170
+    fromVue.value.fileId = newFileId;
171
+    result.value = newFileId;
172
+  } else {
173
+    result.value = fromVue.value.fileId;
174
+  }
146
 
175
 
147
-  result.value=fromVue.value.fileId
148
-  console.log(result.value);
176
+  console.log('编辑模式 - fileId:', result.value);
149
 }
177
 }
150
  const  whether=ref(false)
178
  const  whether=ref(false)
151
 
179
 
152
 const planLevelList1=ref([])
180
 const planLevelList1=ref([])
153
 
181
 
182
+const onConfirmDatetime = () => {
183
+  const year = currentDate.value[0];
184
+  const month = currentDate.value[1].toString().padStart(2, '0');
185
+  const day = currentDate.value[2].toString().padStart(2, '0');
186
+  const hours = currentTime.value[0].toString().padStart(2, '0');
187
+  const minutes = currentTime.value[1].toString().padStart(2, '0');
188
+  const seconds = currentTime.value[2].toString().padStart(2, '0');
189
+
190
+  fromVue.value.accidentTime = `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
191
+  showDatetimePicker.value = false;
192
+  dateOrTime.value = false;
193
+};
194
+
195
+
196
+const showDatetimePicker = ref(false);
197
+const currentTime = ref();
198
+const minDate = ref();
199
+const maxDate = ref();
200
+
201
+const dateOrTime = ref(false);
202
+const onDateConfirm = () => {
203
+  // 日期选择确认后的处理
204
+  dateOrTime.value = true;
205
+};
206
+const resetDatetime = () => {
207
+  dateOrTime.value = false;
208
+
209
+  // 初始化日期和时间
210
+  if (fromVue.value.accidentTime) {
211
+    const dt = new Date(fromVue.value.accidentTime);
212
+    currentDate.value = [dt.getFullYear(), dt.getMonth() + 1, dt.getDate()];
213
+    currentTime.value = [
214
+      String(dt.getHours()).padStart(2, '0'),
215
+      String(dt.getMinutes()).padStart(2, '0'),
216
+      String(dt.getSeconds()).padStart(2, '0')
217
+    ];
218
+  } else {
219
+    const now = new Date();
220
+    currentDate.value = [now.getFullYear(), now.getMonth() + 1, now.getDate()];
221
+    currentTime.value = [
222
+      String(now.getHours()).padStart(2, '0'),
223
+      String(now.getMinutes()).padStart(2, '0'),
224
+      String(now.getSeconds()).padStart(2, '0')
225
+    ];
226
+  }
227
+};
228
+const cancelDatePicker = () => {
229
+  showDatetimePicker.value = false;
230
+  resetDatetime();
231
+};
232
+
233
+// 时间选择器取消
234
+const cancelTimePicker = () => {
235
+  resetDatetime();
236
+  // 重置为当前时间或从表单获取时间
237
+  if (fromVue.value.accidentTime) {
238
+    const dt = new Date(fromVue.value.accidentTime);
239
+    currentTime.value = [
240
+      String(dt.getHours()).padStart(2, '0'),
241
+      String(dt.getMinutes()).padStart(2, '0'),
242
+      String(dt.getSeconds()).padStart(2, '0')
243
+    ];
244
+  } else {
245
+    const now = new Date();
246
+    currentTime.value = [
247
+      String(now.getHours()).padStart(2, '0'),
248
+      String(now.getMinutes()).padStart(2, '0'),
249
+      String(now.getSeconds()).padStart(2, '0')
250
+    ];
251
+  }
252
+};
253
+
154
 
254
 
155
 /* 下拉框 */
255
 /* 下拉框 */
156
 const showActionSheet = ref(false)
256
 const showActionSheet = ref(false)
260
     message: '加载中',
360
     message: '加载中',
261
     forbidClick: true
361
     forbidClick: true
262
   })
362
   })
363
+  
364
+  fromVue.value.fileId = result.value
365
+
263
   var url = '/sgsafe/Manager/saveAccident';
366
   var url = '/sgsafe/Manager/saveAccident';
264
   const params = {
367
   const params = {
265
     json: JSON.stringify(fromVue.value)
368
     json: JSON.stringify(fromVue.value)
306
   const month = today.getMonth() + 1 // 月份从 0 开始
409
   const month = today.getMonth() + 1 // 月份从 0 开始
307
   const day = today.getDate()
410
   const day = today.getDate()
308
   currentDate.value = [year, month, day]
411
   currentDate.value = [year, month, day]
412
+  // 初始化时间
413
+  currentTime.value = [today.getHours(), today.getMinutes(), today.getSeconds()];
414
+  // 如果是编辑模式且有已有的事故时间,解析并初始化
415
+  if (isEdit.value && fromVue.value.accidentTime) {
416
+    try {
417
+      // 解析格式如 "2025-01-15 14:30:00" 的时间字符串
418
+      const timeStr = fromVue.value.accidentTime;
419
+      const [datePart, timePart] = timeStr.split(' ');
420
+      if (datePart && timePart) {
421
+        const [year, month, day] = datePart.split('-').map(Number);
422
+        const [hours, minutes, seconds] = timePart.split(':').map(Number);
423
+        currentDate.value = [year, month, day];
424
+        currentTime.value = [hours || 0, minutes || 0, seconds || 0];
425
+      }
426
+    } catch (error) {
427
+      console.error('解析事故时间失败:', error);
428
+    }
429
+  }
309
   //selectedDateText.value = `${year}-${String(month).padStart(2, '0')}-${String(day).padStart(2, '0')}`
430
   //selectedDateText.value = `${year}-${String(month).padStart(2, '0')}-${String(day).padStart(2, '0')}`
310
   getDicList()
431
   getDicList()
311
 })
432
 })
361
 <!--          :rules="[{required: true, message: '请输入文件编号'}]"-->
482
 <!--          :rules="[{required: true, message: '请输入文件编号'}]"-->
362
 <!--        />-->
483
 <!--        />-->
363
         <van-field
484
         <van-field
364
-            v-model="generatedCode"
485
+            v-model="fromVue.caseNumber"
365
         label="案例编号"
486
         label="案例编号"
366
         name="caseNumber"
487
         name="caseNumber"
367
-            :readonly="isCaseSubmitted"
488
+            readonly
368
         :rules="[{required: true, message: '编号生成失败,请点击“重新生成”'}]"
489
         :rules="[{required: true, message: '编号生成失败,请点击“重新生成”'}]"
369
         />
490
         />
370
 
491
 
372
           v-model="fromVue.caseTitle"
493
           v-model="fromVue.caseTitle"
373
           label="案例标题"
494
           label="案例标题"
374
           name="caseTitle"
495
           name="caseTitle"
496
+          :readonly="isCaseSubmitted"
375
           required
497
           required
376
           placeholder="请输入案例标题"
498
           placeholder="请输入案例标题"
377
-          :rules="[{required: true, message: '请输入文件名称'}]"
499
+          :rules="[{required: true, message: '请输入标题名称'}]"
378
         />
500
         />
379
 
501
 
380
         <van-field
502
         <van-field
382
           readonly
504
           readonly
383
           label="案例来源"
505
           label="案例来源"
384
           name="caseSource"
506
           name="caseSource"
385
-          required
386
-          @click="caseSourceFlag = true"
507
+          :readonly="isCaseSubmitted"
508
+          @click="!isCaseSubmitted && (caseSourceFlag = true)"
387
         />
509
         />
388
         <van-field
510
         <van-field
389
           readonly
511
           readonly
390
           v-model="fromVue.accidentLevel"
512
           v-model="fromVue.accidentLevel"
391
           label="事故等级"
513
           label="事故等级"
392
           name="accidentLevel"
514
           name="accidentLevel"
393
-          @click="accidentLevelFlag = true"
515
+          :readonly="isCaseSubmitted"
516
+          @click="!isCaseSubmitted && (accidentLevelFlag = true)"
394
         />
517
         />
395
 
518
 
396
-
397
-
398
         <van-field
519
         <van-field
399
             v-model="fromVue.accidentDept"
520
             v-model="fromVue.accidentDept"
400
             label="事故单位"
521
             label="事故单位"
401
             name="accidentDept"
522
             name="accidentDept"
402
-            required
403
             placeholder="请输入事故单位"
523
             placeholder="请输入事故单位"
524
+            :readonly="isCaseSubmitted"
404
             :rules="[{required: true, message: '请输入事故单位'}]"
525
             :rules="[{required: true, message: '请输入事故单位'}]"
405
         />
526
         />
406
 
527
 
408
             v-model="fromVue.accidentLocation"
529
             v-model="fromVue.accidentLocation"
409
             label="事故地点"
530
             label="事故地点"
410
             name="accidentLocation"
531
             name="accidentLocation"
411
-            required
532
+            :readonly="isCaseSubmitted"
412
             placeholder="请输入事故地点"
533
             placeholder="请输入事故地点"
413
             :rules="[{required: true, message: '请输入事故地点'}]"
534
             :rules="[{required: true, message: '请输入事故地点'}]"
414
         />
535
         />
415
 
536
 
416
 <!--        //时间-->
537
 <!--        //时间-->
538
+        <van-field
539
+            v-model="fromVue.accidentTime"
540
+            is-link
541
+            readonly
542
+            name="datetime"
543
+            label="发生时间"
544
+            :colon="true"
545
+            placeholder="点击选择日期和时间"
546
+            @click="showDatetimePicker = !isCaseSubmitted"
547
+            :rules="[{ required:true, message: '请选择内容' }]"
548
+        />
417
 
549
 
550
+        <van-popup
551
+            v-model:show="showDatetimePicker"
552
+            position="bottom"
553
+            round
554
+            style="max-height: 50vh;"
555
+        >
556
+          <van-date-picker
557
+              v-if="!dateOrTime"
558
+              v-model="currentDate"
559
+              title="选择日期"
560
+              :min-date="minDate"
561
+              :max-date="maxDate"
562
+              @confirm="onDateConfirm"
563
+              @cancel="cancelDatePicker"
564
+          >
565
+            <template #confirm>
566
+              <van-button type="primary" @click="onDateConfirm">下一步</van-button>
567
+            </template>
568
+            <template #cancel>
569
+              <van-button type="danger" @click="cancelDatePicker">取消</van-button>
570
+            </template>
571
+          </van-date-picker>
572
+
573
+          <van-time-picker
574
+              v-if="dateOrTime"
575
+              v-model="currentTime"
576
+              title="选择时间"
577
+              :columns-type="['hour', 'minute', 'second']"
578
+              @confirm="onConfirmDatetime"
579
+              @cancel="cancelTimePicker"
580
+          >
581
+            <template #confirm>
582
+              <van-button type="primary" @click="onConfirmDatetime">确定</van-button>
583
+            </template>
584
+            <template #cancel>
585
+              <van-button type="danger" @click="cancelTimePicker">取消</van-button>
586
+            </template>
587
+          </van-time-picker>
588
+        </van-popup>
418
 <!--        // 标签-->
589
 <!--        // 标签-->
590
+
591
+        <van-field
592
+            v-model="fromVue.accidentTags"
593
+            label="关键词/标签"
594
+            name="accidentTags"
595
+            :readonly="isCaseSubmitted"
596
+            :rules="[{required: true, message: '请输入事故关键词/标签”'}]"
597
+        />
598
+
419
         <van-field
599
         <van-field
420
             v-model="fromVue.casualtyCount"
600
             v-model="fromVue.casualtyCount"
421
             label="事故造成后果"
601
             label="事故造成后果"
422
             name="casualtyCount"
602
             name="casualtyCount"
423
-            required
424
             rows="1"
603
             rows="1"
425
             autosize
604
             autosize
426
             type="textarea"
605
             type="textarea"
427
             placeholder="请输入事故造成后果"
606
             placeholder="请输入事故造成后果"
607
+            :readonly="isCaseSubmitted"
428
             :rules="[{required: true, message: '请输入事故造成后果'}]"
608
             :rules="[{required: true, message: '请输入事故造成后果'}]"
429
         />
609
         />
430
         <van-field
610
         <van-field
431
             v-model="fromVue.accidentSummary"
611
             v-model="fromVue.accidentSummary"
432
             label="事故简要经过"
612
             label="事故简要经过"
433
             name="accidentSummary"
613
             name="accidentSummary"
434
-            required
435
             rows="3"
614
             rows="3"
436
             autosize
615
             autosize
437
             type="textarea"
616
             type="textarea"
438
             placeholder="请输入事故简要经过"
617
             placeholder="请输入事故简要经过"
618
+            :readonly="isCaseSubmitted"
439
             :rules="[{required: true, message: '请输入事故简要经过'}]"
619
             :rules="[{required: true, message: '请输入事故简要经过'}]"
440
         />
620
         />
441
         <van-field
621
         <van-field
442
             v-model="fromVue.preventiveMeasures"
622
             v-model="fromVue.preventiveMeasures"
443
             label="防范与整改措施"
623
             label="防范与整改措施"
444
             name="preventiveMeasures"
624
             name="preventiveMeasures"
445
-            required
446
             rows="3"
625
             rows="3"
447
             autosize
626
             autosize
448
             type="textarea"
627
             type="textarea"
449
             placeholder="请输入防范与整改措施"
628
             placeholder="请输入防范与整改措施"
629
+            :readonly="isCaseSubmitted"
450
             :rules="[{required: true, message: '请输入防范与整改措施'}]"
630
             :rules="[{required: true, message: '请输入防范与整改措施'}]"
451
         />
631
         />
452
 
632
 
453
-
454
-
455
         <van-field label="附件上传" >
633
         <van-field label="附件上传" >
456
           <template #input>
634
           <template #input>
457
             <AttachmentS3 :f-id="result" />
635
             <AttachmentS3 :f-id="result" />
458
           </template>
636
           </template>
459
         </van-field>
637
         </van-field>
460
         <div style="margin: 16px;">
638
         <div style="margin: 16px;">
461
-          <van-button round block type="primary" native-type="submit">
639
+          <van-button v-if="!isReadOnly" round block type="primary" native-type="submit">
462
             {{ isEdit ? '保存' : '提交' }}
640
             {{ isEdit ? '保存' : '提交' }}
463
           </van-button>
641
           </van-button>
464
         </div>
642
         </div>

+ 363
- 0
src/view/knowledge/project.vue View File

1
+<template>
2
+  <div class="h5-container">
3
+    <van-nav-bar title="项目案例" @click-left="onClickLeft" @click-right="handAdd">
4
+      <template #right>
5
+        <van-icon name="add" size="25" color="#000" />
6
+      </template>
7
+    </van-nav-bar>
8
+    
9
+    <van-search 
10
+      v-model="query.projectName" 
11
+      show-action 
12
+      placeholder="请输入项目名称" 
13
+      @search="onRefresh"
14
+      @cancel="handleClearSearch" 
15
+    />
16
+
17
+    <!-- 项目列表 -->
18
+    <van-pull-refresh v-model="isRefreshing" success-text="刷新成功" @refresh="onRefresh">
19
+      <van-list 
20
+        v-model:loading="isLoading" 
21
+        :finished="isFinished" 
22
+        finished-text="没有更多了" 
23
+        offset="200" 
24
+        @load="onLoad"
25
+      >
26
+        <div v-for="(item, idx) in resultData" :key="item.id">
27
+          <van-swipe-cell 
28
+            title-style="color: #007aff" 
29
+            style="height: 80px;" 
30
+            :ref="el => getSwipeCellRef(el, idx)"
31
+          >
32
+            <template #default>
33
+              <div class="swipe-cell-default">
34
+                <van-cell 
35
+                  style="min-height: 120px; padding: 0 0 0 0; display: flex; align-items: flex-start;" 
36
+                  @click="edits(item)"
37
+                >
38
+                  <template #title>
39
+                    <div class="cell-title">
40
+                      {{ item.projectName }}
41
+                    </div>
42
+                  </template>
43
+                  <template #label>
44
+                    <div>案例编号:{{ item.caseNumber }}</div>
45
+                    <div>案例类型:{{ item.caseType }}</div>
46
+                    <div>浏览量:{{ item.viewCount }}</div>
47
+                  </template>
48
+                </van-cell>
49
+                <div class="swipe-cell-default-icon">
50
+                  <van-icon 
51
+                    v-if="openStatus[idx]" 
52
+                    name="arrow-double-left" 
53
+                    @click.stop="openSwipe(idx)" 
54
+                  />
55
+                  <van-icon 
56
+                    v-else 
57
+                    name="arrow-double-right" 
58
+                    @click.stop="closeSwipe(idx)" 
59
+                  />
60
+                </div>
61
+              </div>
62
+            </template>
63
+
64
+            <template #right>
65
+              <van-button 
66
+                v-if="item.canDelete" 
67
+                square 
68
+                class="delete-button" 
69
+                text="删除" 
70
+                @click="handleDelete(item)" 
71
+              />
72
+            </template>
73
+          </van-swipe-cell>
74
+        </div>
75
+      </van-list>
76
+    </van-pull-refresh>
77
+  </div>
78
+</template>
79
+
80
+<script setup>
81
+import { ref, getCurrentInstance } from 'vue';
82
+import { useRouter } from 'vue-router';
83
+import { showDialog, showSuccessToast, showToast } from 'vant';
84
+
85
+const { proxy } = getCurrentInstance();
86
+const router = useRouter();
87
+
88
+const onClickLeft = () => {
89
+  history.back();
90
+};
91
+
92
+const query = ref({
93
+  caseNumber: '',
94
+  projectName: ''
95
+});
96
+
97
+const isRefreshing = ref(false);
98
+const isLoading = ref(false);
99
+const isFinished = ref(false);
100
+const currentPage = ref(1);
101
+const pageSize = ref(10);
102
+const totalRows = ref(0);
103
+const resultData = ref([]);
104
+const tableData = ref([]);
105
+
106
+const currentUserId = String(localStorage.getItem('userId'));
107
+
108
+// 获取列表数据
109
+const getTableData = async () => {
110
+  const url = '/sgsafe/Manager/queryProject';
111
+  const param = {
112
+    page: currentPage.value,
113
+    rows: pageSize.value,
114
+    params: JSON.stringify(query.value)
115
+  };
116
+  
117
+  const response = await proxy.$axios.get(url, param);
118
+  if (response.data.code == 0) {
119
+    tableData.value = response.data.data.records.map(item => ({
120
+      ...item,
121
+      canDelete: String(item.addId) === currentUserId
122
+    }));
123
+    console.log('列表数据', tableData.value);
124
+    totalRows.value = response.data.data.total;
125
+  } else {
126
+    showToast({
127
+      type: 'error',
128
+      message: '操作失败!' + response.data.msg
129
+    });
130
+  }
131
+};
132
+
133
+// 刷新
134
+const onRefresh = () => {
135
+  basicReset();
136
+  onLoad();
137
+};
138
+
139
+// 加载数据
140
+const onLoad = async () => {
141
+  if (isRefreshing.value) {
142
+    resultData.value = [];
143
+    currentPage.value = 1;
144
+    isRefreshing.value = false;
145
+  }
146
+
147
+  try {
148
+    await getTableData();
149
+
150
+    if (pageSize.value * currentPage.value < totalRows.value) {
151
+      resultData.value = [...resultData.value, ...tableData.value];
152
+      openStatus.value = new Array(resultData.value.length).fill(true);
153
+      currentPage.value++;
154
+    } else {
155
+      resultData.value = [...resultData.value, ...tableData.value];
156
+      openStatus.value = new Array(resultData.value.length).fill(true);
157
+      isFinished.value = true;
158
+    }
159
+
160
+    console.log('resultData', resultData.value);
161
+  } catch (error) {
162
+    console.log(error);
163
+    isFinished.value = true;
164
+  } finally {
165
+    isLoading.value = false;
166
+  }
167
+};
168
+
169
+// 重置列表
170
+const basicReset = () => {
171
+  isFinished.value = false;
172
+  isLoading.value = true;
173
+  currentPage.value = 1;
174
+  resultData.value = [];
175
+};
176
+
177
+// 清空搜索
178
+const handleClearSearch = () => {
179
+  query.value.projectName = '';
180
+  onRefresh();
181
+};
182
+
183
+// 新增
184
+const handAdd = () => {
185
+  router.push({ 
186
+    path: "/projectList",
187
+    query: { mark: -1 } 
188
+  });
189
+};
190
+
191
+// 编辑/查看
192
+const edits = async (row) => {
193
+  const currentUserId = localStorage.getItem('userId');
194
+  const addId = row.addId;
195
+  const isOwner = String(addId).trim().toLowerCase() === String(currentUserId).trim().toLowerCase();
196
+
197
+  // 更新浏览量
198
+  await updateViewCount(row);
199
+
200
+  router.push({ 
201
+    path: "/projectList",
202
+    query: {
203
+      mark: 1,
204
+      data: JSON.stringify(row),
205
+      readOnly: !isOwner ? 'true' : undefined
206
+    } 
207
+  });
208
+};
209
+
210
+// 更新浏览量
211
+const updateViewCount = async (item) => {
212
+  try {
213
+    const payload = { ...item };
214
+    payload.viewCount = String((Number(payload.viewCount) || 0) + 1);
215
+
216
+    const url = '/sgsafe/Manager/saveProject';
217
+    const param = {
218
+      json: JSON.stringify(payload)
219
+    };
220
+
221
+    const response = await proxy.$axios.post(url, param);
222
+    if (response.data.code === '0' || response.data.code === 0) {
223
+      const index = resultData.value.findIndex(data => data.id === item.id);
224
+      if (index !== -1) {
225
+        resultData.value[index].viewCount = payload.viewCount;
226
+      }
227
+    }
228
+  } catch (error) {
229
+    console.error('更新浏览量失败:', error);
230
+  }
231
+};
232
+
233
+// 删除
234
+const handleDelete = (item) => {
235
+  const currentUserId = localStorage.getItem('userId');
236
+  const addId = item.addId;
237
+  
238
+  if (!currentUserId || !addId || String(addId).trim() !== String(currentUserId).trim()) {
239
+    showToast({
240
+      type: 'warning',
241
+      message: '无权限删除!只能删除自己添加的案例。'
242
+    });
243
+    return;
244
+  }
245
+  
246
+  showDialog({
247
+    title: '删除确认',
248
+    message: `确定要删除项目"${item.projectName}"吗?删除后将无法恢复。`,
249
+    showCancelButton: true,
250
+    confirmButtonText: '确定删除',
251
+    cancelButtonText: '取消',
252
+  }).then(() => {
253
+    executeDelete(item);
254
+  }).catch(() => {
255
+    console.log('取消删除');
256
+  });
257
+};
258
+
259
+// 执行删除
260
+const executeDelete = (item) => {
261
+  const deleteData = { ...item };
262
+  const now = new Date();
263
+  const year = now.getFullYear();
264
+  const month = String(now.getMonth() + 1).padStart(2, '0');
265
+  const day = String(now.getDate()).padStart(2, '0');
266
+  const hours = String(now.getHours()).padStart(2, '0');
267
+  const minutes = String(now.getMinutes()).padStart(2, '0');
268
+  const seconds = String(now.getSeconds()).padStart(2, '0');
269
+  deleteData.cancelTime = `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
270
+  deleteData.cancelFlag = '1';
271
+  
272
+  const url = '/sgsafe/Manager/saveProject';
273
+  const param = {
274
+    json: JSON.stringify(deleteData)
275
+  };
276
+  
277
+  proxy.$axios.post(url, param).then(response => {
278
+    if (response.data.code == '0' || response.data.code === 0) {
279
+      showSuccessToast("删除成功");
280
+      onRefresh();
281
+    } else {
282
+      showToast({
283
+        type: 'fail',
284
+        message: '删除失败:' + (response.data.msg || '未知错误')
285
+      });
286
+    }
287
+  }).catch(error => {
288
+    showToast({
289
+      type: 'fail',
290
+      message: '删除失败:网络错误'
291
+    });
292
+    console.error('删除失败:', error);
293
+  });
294
+};
295
+
296
+// 滑动单元格控制
297
+const openStatus = ref([]);
298
+const swipeCellRefs = ref([]);
299
+
300
+const getSwipeCellRef = (el, index) => {
301
+  if (el) {
302
+    swipeCellRefs.value[index] = el;
303
+  }
304
+};
305
+
306
+const openSwipe = (idx) => {
307
+  openStatus.value = new Array(resultData.value.length).fill(true);
308
+  if (idx >= 0 && idx < swipeCellRefs.value.length) {
309
+    openStatus.value[idx] = false;
310
+    swipeCellRefs.value[idx].open('right');
311
+  }
312
+};
313
+
314
+const closeSwipe = (idx) => {
315
+  if (idx >= 0 && idx < swipeCellRefs.value.length) {
316
+    openStatus.value[idx] = true;
317
+    swipeCellRefs.value[idx].close();
318
+  }
319
+};
320
+</script>
321
+
322
+<style scoped>
323
+.h5-container {
324
+  width: 100%;
325
+  padding: 5px;
326
+  box-sizing: border-box;
327
+}
328
+
329
+.cell-title {
330
+  display: -webkit-box;
331
+  -webkit-box-orient: vertical;
332
+  -webkit-line-clamp: 2;
333
+  overflow: hidden;
334
+  text-overflow: ellipsis;
335
+  line-height: 1.5;
336
+  max-height: calc(1.5em * 2);
337
+  font-size: 16px;
338
+  font-weight: bold;
339
+  color: #333;
340
+}
341
+
342
+.swipe-cell-default {
343
+  display: flex;
344
+  background-color: #ffffff;
345
+  justify-content: center;
346
+  align-items: center;
347
+}
348
+
349
+.swipe-cell-default-icon {
350
+  width: 60px;
351
+  display: flex;
352
+  justify-content: center;
353
+}
354
+
355
+.delete-button {
356
+  height: 100%;
357
+  border: none;
358
+  color: #ff0000;
359
+  background-image: url('@/assets/img/del.png');
360
+  background-size: auto 100%;
361
+  background-repeat: no-repeat;
362
+}
363
+</style>

+ 563
- 0
src/view/knowledge/projectList.vue View File

1
+<script setup>
2
+import { getCurrentInstance, onMounted, ref, computed } from 'vue';
3
+import { useRoute, useRouter } from 'vue-router';
4
+import tools from '@/tools'
5
+const {
6
+  proxy
7
+} = getCurrentInstance()
8
+
9
+const projectDictList = ref([])
10
+const caseTypeColumns = ref([])
11
+const caseSourceColumns = ref([])
12
+
13
+// 定义生成编号的函数
14
+const generateCode = () => {
15
+  const now = new Date();
16
+  const year = now.getFullYear();
17
+  const month = String(now.getMonth() + 1).padStart(2, '0');
18
+  const day = String(now.getDate()).padStart(2, '0');
19
+  const formattedDate = `${year}${month}${day}`;
20
+  const hours = String(now.getHours()).padStart(2, '0');
21
+  const minutes = String(now.getMinutes()).padStart(2, '0');
22
+  const seconds = String(now.getSeconds()).padStart(2, '0');
23
+  const formattedTime = `${hours}${minutes}${seconds}`;
24
+  const sequenceNumber = Math.floor(Math.random() * 1000);
25
+  const paddedSequence = String(sequenceNumber).padStart(3, '0');
26
+  return `XMAL${formattedDate}${formattedTime}${paddedSequence}`;
27
+};
28
+
29
+// 获取字典数据
30
+const getProjectDicList = () => {
31
+  tools.dic.getDicList(['sgsafe_project_case_type', 'sgsafe_project_case_source']).then((response => {
32
+    console.log(JSON.stringify(response.data.data))
33
+    projectDictList.value = response.data.data
34
+    
35
+    caseTypeColumns.value = projectDictList.value.sgsafe_project_case_type?.map(item => ({
36
+      text: item.dicName,
37
+      value: item.dicCode
38
+    })) || [];
39
+    
40
+    caseSourceColumns.value = projectDictList.value.sgsafe_project_case_source?.map(item => ({
41
+      text: item.dicName,
42
+      value: item.dicCode
43
+    })) || [];
44
+    
45
+    console.log('案例类型:', caseTypeColumns.value)
46
+  }))
47
+}
48
+
49
+const caseTypeFlag = ref(false)
50
+const caseSourceFlag = ref(false)
51
+let title = '新增项目案例'
52
+
53
+/* 返回上一级页面 */
54
+const router = useRouter()
55
+const onClickLeft = () => {
56
+  router.go(-1)
57
+}
58
+
59
+const guid = () => {
60
+  function S4() {
61
+    return (((1 + Math.random()) * 0x10000) | 0).toString(16).substring(1);
62
+  }
63
+  return (S4() + S4() + S4() + S4() + S4() + S4() + S4() + S4())
64
+}
65
+
66
+const route = useRoute()
67
+let planInfo = {}
68
+const isEdit = ref(route.query.mark === '1');
69
+const isReadOnly = ref(route.query.readOnly === 'true');
70
+const isCaseSubmitted = computed(() => isReadOnly.value && isEdit.value);
71
+const result = ref('')
72
+const fromVue = ref({})
73
+
74
+if (route.query.mark) {
75
+  planInfo = JSON.parse(route.query.mark)
76
+}
77
+
78
+
79
+// 新增模式
80
+if (planInfo == -1) {
81
+  const caseNumber = generateCode();
82
+  result.value = caseNumber;
83
+  
84
+  fromVue.value = {
85
+    caseNumber: caseNumber,
86
+    projectName: '',
87
+    caseType: '',
88
+    caseSource: '',
89
+    startTime: '',
90
+    endTime: '',
91
+    tags: '',
92
+    caseSummary: '',
93
+    highLights: '',
94
+    resultsValue: '',
95
+    fileId: caseNumber,
96
+    viewCount: '0',
97
+    downloadCount: '0'
98
+  };
99
+  
100
+}
101
+
102
+// 编辑模式
103
+if (planInfo == 1) {
104
+  title = '查看项目案例'
105
+  fromVue.value = JSON.parse(route.query.data)
106
+  
107
+  // 清理微秒格式
108
+  if (fromVue.value.startTime && String(fromVue.value.startTime).includes('.')) {
109
+    fromVue.value.startTime = String(fromVue.value.startTime).split('.')[0];
110
+  }
111
+  if (fromVue.value.endTime && String(fromVue.value.endTime).includes('.')) {
112
+    fromVue.value.endTime = String(fromVue.value.endTime).split('.')[0];
113
+  }
114
+  
115
+  // fileId 与 caseNumber 保持一致
116
+  if (!fromVue.value.fileId || fromVue.value.fileId !== fromVue.value.caseNumber) {
117
+    fromVue.value.fileId = fromVue.value.caseNumber;
118
+  }
119
+  result.value = fromVue.value.fileId;
120
+}
121
+
122
+// 时间选择器
123
+const showStartTimePicker = ref(false);
124
+const showEndTimePicker = ref(false);
125
+const currentStartDate = ref([2025, 1, 1])
126
+const currentEndDate = ref([2025, 1, 1])
127
+const currentStartTime = ref([0, 0, 0]);
128
+const currentEndTime = ref([0, 0, 0]);
129
+const minDate = ref(new Date(1900, 0, 1));
130
+const maxDate = ref(new Date(2100, 11, 31));
131
+
132
+const startDateOrTime = ref(false);
133
+const endDateOrTime = ref(false);
134
+
135
+// 开始时间选择
136
+const onStartDateConfirm = () => {
137
+  startDateOrTime.value = true;
138
+};
139
+
140
+const onConfirmStartDatetime = () => {
141
+  const year = currentStartDate.value[0];
142
+  const month = currentStartDate.value[1].toString().padStart(2, '0');
143
+  const day = currentStartDate.value[2].toString().padStart(2, '0');
144
+  const hours = currentStartTime.value[0].toString().padStart(2, '0');
145
+  const minutes = currentStartTime.value[1].toString().padStart(2, '0');
146
+  const seconds = currentStartTime.value[2].toString().padStart(2, '0');
147
+
148
+  fromVue.value.startTime = `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
149
+  showStartTimePicker.value = false;
150
+  startDateOrTime.value = false;
151
+};
152
+
153
+const cancelStartDatePicker = () => {
154
+  showStartTimePicker.value = false;
155
+  startDateOrTime.value = false;
156
+};
157
+
158
+const cancelStartTimePicker = () => {
159
+  showStartTimePicker.value = false;
160
+  startDateOrTime.value = false;
161
+};
162
+
163
+// 结束时间选择
164
+const onEndDateConfirm = () => {
165
+  endDateOrTime.value = true;
166
+};
167
+
168
+const onConfirmEndDatetime = () => {
169
+  const year = currentEndDate.value[0];
170
+  const month = currentEndDate.value[1].toString().padStart(2, '0');
171
+  const day = currentEndDate.value[2].toString().padStart(2, '0');
172
+  const hours = currentEndTime.value[0].toString().padStart(2, '0');
173
+  const minutes = currentEndTime.value[1].toString().padStart(2, '0');
174
+  const seconds = currentEndTime.value[2].toString().padStart(2, '0');
175
+
176
+  fromVue.value.endTime = `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
177
+  showEndTimePicker.value = false;
178
+  endDateOrTime.value = false;
179
+};
180
+
181
+const cancelEndDatePicker = () => {
182
+  showEndTimePicker.value = false;
183
+  endDateOrTime.value = false;
184
+};
185
+
186
+const cancelEndTimePicker = () => {
187
+  showEndTimePicker.value = false;
188
+  endDateOrTime.value = false;
189
+};
190
+
191
+// 标签选择
192
+const addTag = (item) => {
193
+  if (isCaseSubmitted.value) return;
194
+  
195
+  const valueToStore = item.value?.trim() || '';
196
+  if (!valueToStore) return;
197
+
198
+  const currentTags = fromVue.value.tags
199
+    ? fromVue.value.tags.split(',').map(t => t.trim()).filter(Boolean)
200
+    : [];
201
+
202
+  if (currentTags.includes(valueToStore)) return;
203
+
204
+  currentTags.push(valueToStore);
205
+  fromVue.value.tags = currentTags.join(', ');
206
+};
207
+
208
+/* 组织树选择 */
209
+import { showFailToast, showLoadingToast, showSuccessToast } from 'vant';
210
+
211
+// 保存
212
+const addEmergencyDrillPlan = async () => {
213
+  const loadingToast = showLoadingToast({
214
+    duration: 0,
215
+    message: '加载中',
216
+    forbidClick: true
217
+  })
218
+  
219
+  fromVue.value.fileId = result.value
220
+
221
+  var url = '/sgsafe/Manager/saveProject';
222
+  const params = {
223
+    json: JSON.stringify(fromVue.value)
224
+  }
225
+  proxy.$axios.post(url, params).then(res => {
226
+    if (res.data.code === 0 || res.data.code === '0') {
227
+      loadingToast.close()
228
+      showSuccessToast('保存成功')
229
+      onClickLeft()
230
+    } else {
231
+      loadingToast.close()
232
+      showFailToast('操作失败!' + res.data.msg)
233
+    }
234
+  }).catch(error => {
235
+    loadingToast.close()
236
+    showFailToast('保存失败: 网络错误')
237
+    console.error('保存失败:', error)
238
+  })
239
+}
240
+
241
+onMounted(() => {
242
+  getProjectDicList()
243
+  const today = new Date()
244
+  const year = today.getFullYear()
245
+  const month = today.getMonth() + 1
246
+  const day = today.getDate()
247
+  currentStartDate.value = [year, month, day]
248
+  currentEndDate.value = [year, month, day]
249
+  currentStartTime.value = [today.getHours(), today.getMinutes(), today.getSeconds()];
250
+  currentEndTime.value = [today.getHours(), today.getMinutes(), today.getSeconds()];
251
+  
252
+  // 如果是编辑模式且有已有的时间,解析并初始化
253
+  if (isEdit.value) {
254
+    if (fromVue.value.startTime) {
255
+      try {
256
+        const timeStr = fromVue.value.startTime;
257
+        const [datePart, timePart] = timeStr.split(' ');
258
+        if (datePart && timePart) {
259
+          const [year, month, day] = datePart.split('-').map(Number);
260
+          const [hours, minutes, seconds] = timePart.split(':').map(Number);
261
+          currentStartDate.value = [year, month, day];
262
+          currentStartTime.value = [hours || 0, minutes || 0, seconds || 0];
263
+        }
264
+      } catch (error) {
265
+        // 解析失败,使用默认值
266
+      }
267
+    }
268
+    
269
+    if (fromVue.value.endTime) {
270
+      try {
271
+        const timeStr = fromVue.value.endTime;
272
+        const [datePart, timePart] = timeStr.split(' ');
273
+        if (datePart && timePart) {
274
+          const [year, month, day] = datePart.split('-').map(Number);
275
+          const [hours, minutes, seconds] = timePart.split(':').map(Number);
276
+          currentEndDate.value = [year, month, day];
277
+          currentEndTime.value = [hours || 0, minutes || 0, seconds || 0];
278
+        }
279
+      } catch (error) {
280
+        // 解析失败,使用默认值
281
+      }
282
+    }
283
+  }
284
+})
285
+
286
+/* 文件上传 */
287
+import AttachmentS3 from '@/components/AttachmentS3.vue';
288
+
289
+const onSubmit = (values) => {
290
+  addEmergencyDrillPlan()
291
+}
292
+
293
+const onCaseTypeConfirm = ({ selectedOptions }) => {
294
+  caseTypeFlag.value = false;
295
+  fromVue.value.caseType = selectedOptions[0].text;
296
+};
297
+
298
+const onCaseSourceConfirm = ({ selectedOptions }) => {
299
+  caseSourceFlag.value = false;
300
+  fromVue.value.caseSource = selectedOptions[0].text;
301
+};
302
+
303
+</script>
304
+
305
+<template>
306
+
307
+  <div class="page-container">
308
+    <van-sticky class="header">
309
+      <van-nav-bar
310
+        :title="title"
311
+        left-text="返回"
312
+        left-arrow
313
+        @click-left="onClickLeft" >
314
+      </van-nav-bar>
315
+    </van-sticky>
316
+    <div class="scroll-container">
317
+      <van-form @submit="onSubmit">
318
+        <van-field
319
+            v-model="fromVue.caseNumber"
320
+            label="案例编号"
321
+            name="caseNumber"
322
+            readonly
323
+            :rules="[{required: true, message: '编号生成失败'}]"
324
+        />
325
+
326
+        <van-field
327
+          v-model="fromVue.projectName"
328
+          label="项目名称"
329
+          name="projectName"
330
+          :readonly="isCaseSubmitted"
331
+          required
332
+          placeholder="请输入项目名称"
333
+          :rules="[{required: true, message: '请输入项目名称'}]"
334
+        />
335
+
336
+        <van-field
337
+          v-model="fromVue.caseType"
338
+          readonly
339
+          label="案例类型"
340
+          name="caseType"
341
+          required
342
+          placeholder="请选择案例类型"
343
+          :rules="[{required: true, message: '请选择案例类型'}]"
344
+          @click="!isCaseSubmitted && (caseTypeFlag = true)"
345
+        />
346
+        
347
+        <van-field
348
+          v-model="fromVue.caseSource"
349
+          readonly
350
+          label="案例来源"
351
+          name="caseSource"
352
+          placeholder="请选择案例来源"
353
+          @click="!isCaseSubmitted && (caseSourceFlag = true)"
354
+        />
355
+
356
+        <!-- 开始时间 -->
357
+        <van-field
358
+            v-model="fromVue.startTime"
359
+            is-link
360
+            readonly
361
+            name="startTime"
362
+            label="开始时间"
363
+            :colon="true"
364
+            placeholder="点击选择开始时间"
365
+            @click="!isCaseSubmitted && (showStartTimePicker = true)"
366
+        />
367
+
368
+        <!-- 结束时间 -->
369
+        <van-field
370
+            v-model="fromVue.endTime"
371
+            is-link
372
+            readonly
373
+            name="endTime"
374
+            label="结束时间"
375
+            :colon="true"
376
+            placeholder="点击选择结束时间"
377
+            @click="!isCaseSubmitted && (showEndTimePicker = true)"
378
+        />
379
+
380
+        <!-- 关键词/标签 -->
381
+        <van-field
382
+            v-model="fromVue.tags"
383
+            label="关键词/标签"
384
+            name="tags"
385
+            placeholder="请手动输入标签,多个标签用逗号分隔"
386
+            :readonly="isCaseSubmitted"
387
+        />
388
+        
389
+        <!-- 标签按钮 -->
390
+        <!-- 整个 van-cell 删除 -->
391
+
392
+        <van-field
393
+            v-model="fromVue.caseSummary"
394
+            label="案例摘要"
395
+            name="caseSummary"
396
+            rows="3"
397
+            autosize
398
+            type="textarea"
399
+            placeholder="请输入案例摘要"
400
+            :readonly="isCaseSubmitted"
401
+        />
402
+        
403
+        <van-field
404
+            v-model="fromVue.highLights"
405
+            label="创新点与亮点"
406
+            name="highLights"
407
+            rows="3"
408
+            autosize
409
+            type="textarea"
410
+            placeholder="请输入创新点与亮点"
411
+            :readonly="isCaseSubmitted"
412
+        />
413
+        
414
+        <van-field
415
+            v-model="fromVue.resultsValue"
416
+            label="应用成效与价值"
417
+            name="resultsValue"
418
+            rows="3"
419
+            autosize
420
+            type="textarea"
421
+            placeholder="请输入应用成效与价值"
422
+            :readonly="isCaseSubmitted"
423
+        />
424
+
425
+        <van-field label="附件上传" >
426
+          <template #input>
427
+            <AttachmentS3 :f-id="result" :readonly="isCaseSubmitted" />
428
+          </template>
429
+        </van-field>
430
+        
431
+        <div style="margin: 16px;">
432
+          <van-button v-if="!isReadOnly" round block type="primary" native-type="submit">
433
+            {{ isEdit ? '保存' : '提交' }}
434
+          </van-button>
435
+        </div>
436
+      </van-form>
437
+
438
+      <!-- 案例类型选择器 -->
439
+      <van-popup v-model:show="caseTypeFlag" round position="bottom">
440
+        <van-picker
441
+            :columns="caseTypeColumns"
442
+            @cancel="caseTypeFlag = false"
443
+            @confirm="onCaseTypeConfirm"
444
+        />
445
+      </van-popup>
446
+
447
+      <!-- 案例来源选择器 -->
448
+      <van-popup v-model:show="caseSourceFlag" round position="bottom">
449
+        <van-picker
450
+            :columns="caseSourceColumns"
451
+            @cancel="caseSourceFlag = false"
452
+            @confirm="onCaseSourceConfirm"
453
+        />
454
+      </van-popup>
455
+
456
+      <!-- 开始时间选择器 -->
457
+      <van-popup
458
+          v-model:show="showStartTimePicker"
459
+          position="bottom"
460
+          round
461
+          style="max-height: 50vh;"
462
+      >
463
+        <van-date-picker
464
+            v-if="!startDateOrTime"
465
+            v-model="currentStartDate"
466
+            title="选择开始日期"
467
+            :min-date="minDate"
468
+            :max-date="maxDate"
469
+            @confirm="onStartDateConfirm"
470
+            @cancel="cancelStartDatePicker"
471
+        >
472
+          <template #confirm>
473
+            <van-button type="primary" @click="onStartDateConfirm">下一步</van-button>
474
+          </template>
475
+          <template #cancel>
476
+            <van-button type="danger" @click="cancelStartDatePicker">取消</van-button>
477
+          </template>
478
+        </van-date-picker>
479
+
480
+        <van-time-picker
481
+            v-if="startDateOrTime"
482
+            v-model="currentStartTime"
483
+            title="选择开始时间"
484
+            :columns-type="['hour', 'minute', 'second']"
485
+            @confirm="onConfirmStartDatetime"
486
+            @cancel="cancelStartTimePicker"
487
+        >
488
+          <template #confirm>
489
+            <van-button type="primary" @click="onConfirmStartDatetime">确定</van-button>
490
+          </template>
491
+          <template #cancel>
492
+            <van-button type="danger" @click="cancelStartTimePicker">取消</van-button>
493
+          </template>
494
+        </van-time-picker>
495
+      </van-popup>
496
+
497
+      <!-- 结束时间选择器 -->
498
+      <van-popup
499
+          v-model:show="showEndTimePicker"
500
+          position="bottom"
501
+          round
502
+          style="max-height: 50vh;"
503
+      >
504
+        <van-date-picker
505
+            v-if="!endDateOrTime"
506
+            v-model="currentEndDate"
507
+            title="选择结束日期"
508
+            :min-date="minDate"
509
+            :max-date="maxDate"
510
+            @confirm="onEndDateConfirm"
511
+            @cancel="cancelEndDatePicker"
512
+        >
513
+          <template #confirm>
514
+            <van-button type="primary" @click="onEndDateConfirm">下一步</van-button>
515
+          </template>
516
+          <template #cancel>
517
+            <van-button type="danger" @click="cancelEndDatePicker">取消</van-button>
518
+          </template>
519
+        </van-date-picker>
520
+
521
+        <van-time-picker
522
+            v-if="endDateOrTime"
523
+            v-model="currentEndTime"
524
+            title="选择结束时间"
525
+            :columns-type="['hour', 'minute', 'second']"
526
+            @confirm="onConfirmEndDatetime"
527
+            @cancel="cancelEndTimePicker"
528
+        >
529
+          <template #confirm>
530
+            <van-button type="primary" @click="onConfirmEndDatetime">确定</van-button>
531
+          </template>
532
+          <template #cancel>
533
+            <van-button type="danger" @click="cancelEndTimePicker">取消</van-button>
534
+          </template>
535
+        </van-time-picker>
536
+      </van-popup>
537
+    </div>
538
+  </div>
539
+</template>
540
+
541
+<style scoped>
542
+.page-container {
543
+  height: 100vh;
544
+  display: flex;
545
+  flex-direction: column;
546
+}
547
+
548
+.scroll-container {
549
+  flex: 1;
550
+  overflow: auto;
551
+  -webkit-overflow-scrolling: touch;
552
+}
553
+
554
+.scroll-container::-webkit-scrollbar {
555
+  display: none;
556
+}
557
+
558
+.header, .footer {
559
+  flex-shrink: 0;
560
+  background: #f5f5f5;
561
+  padding: 12px;
562
+}
563
+</style>

Loading…
Cancel
Save