2 コミット

作成者 SHA1 メッセージ 日付
  wangqi 18699b9386 Merge branch 'develop' of http://123.206.9.27:3000/ShinSoft_Xxhsyb/Proj_SafePlat_Vue_Sgh5 into develop 2週間前
  wangqi 1f5186907d 重点事项和计划管理移动端 2週間前

+ 0
- 3
.env.wangqi ファイルの表示

@@ -1,5 +1,4 @@
1 1
 VITE_BASE_API = 'http://127.0.0.1:8003'
2
-#VITE_BASE_API = 'http://10.19.13.166/sgiipapi'
3 2
 VITE_PREVIEW_API= 'http://172.16.7.51:10023'
4 3
 VITE_RAQ_API= 'http://172.16.2.78:8080'
5 4
 VITE_UREPORT_API= 'http://172.16.2.78:8001'
@@ -12,6 +11,4 @@ VITE_QW_APP_CODE='com.shansteelgroup.sxaqxt'
12 11
 #VITE_QW_APP_ID='ww0a57c5a416cc3df3'
13 12
 
14 13
 VITE_QW_APP_ID='ww9263769246e752c9'
15
-VITE_BUCKET = 'common'
16 14
 VITE_USER_ID='91EE370A4ECB11B297751DCDBB6A11DD'
17
-

バイナリ
public/images/keyMatters.png ファイルの表示


バイナリ
public/images/planManagement.png ファイルの表示


+ 36
- 0
src/router/index.ts ファイルの表示

@@ -534,6 +534,42 @@ const router = createRouter({
534 534
 			name: '通知公告新增',
535 535
 			component: () => import('@/view/announ/noticeList.vue')
536 536
 		},
537
+		{
538
+			path: '/keyMatters',
539
+			name: '重点事项',
540
+			component: () => import('@/view/keyMatters/index.vue')
541
+		},
542
+		{
543
+			path: '/keyMatters/view',
544
+			name: '重点事项查看',
545
+			component: () => import('@/view/keyMatters/view.vue')
546
+		},
547
+		{
548
+			path: '/keyMatters/report',
549
+			name: '重点事项汇报',
550
+			component: () => import('@/view/keyMatters/report.vue')
551
+		},
552
+		{
553
+			path: '/planManagement',
554
+			name: '计划任务汇报',
555
+			component: () => import('@/view/planManagement/index.vue')
556
+		},
557
+		{
558
+			path: '/planManagement/manifest',
559
+			name: '任务清单',
560
+			component: () => import('@/view/planManagement/manifest.vue')
561
+		},
562
+		{
563
+			path: '/planManagement/manifestView',
564
+			name: '任务清单查看',
565
+			component: () => import('@/view/planManagement/manifestView.vue')
566
+		},
567
+		{
568
+			path: '/planManagement/manifestReport',
569
+			name: '任务清单汇报',
570
+			component: () => import('@/view/planManagement/manifestReport.vue')
571
+		},
572
+		},
537 573
 		{
538 574
 			path: '/accidentManager/accidentBaoGaoLedger/index',
539 575
 			name: 'accidentBaoGaoLedger',

+ 12
- 0
src/utils/commonMethod.js ファイルの表示

@@ -0,0 +1,12 @@
1
+export function guid() {
2
+  const chars = '0123456789abcdefghijklmnopqrstuvwxyz'
3
+  const length = chars.length
4
+  const randomString = (len) => {
5
+    const array = new Uint8Array(len)
6
+    crypto.getRandomValues(array)
7
+    return Array.from(array)
8
+      .map(byte => chars[byte % length])
9
+      .join('');
10
+  }
11
+  return `${randomString(8)}${randomString(8)}${randomString(8)}${randomString(8)}`;
12
+}

+ 20
- 2
src/view/Home2.vue ファイルの表示

@@ -190,6 +190,19 @@
190 190
         </van-grid-item>
191 191
       </van-grid>
192 192
     </div>
193
+    <div class="card">
194
+      <div class="title">事务汇报</div>
195
+      <van-grid :border="false" :column-num="4">
196
+        <van-grid-item to="/keyMatters">
197
+          <img src="../../public/images/keyMatters.png" width="45rpx" />
198
+          <span class="vanicon_text">重点事项</span>
199
+        </van-grid-item>
200
+        <van-grid-item to="/planManagement">
201
+          <img src="../../public/images/planManagement.png" width="45rpx" />
202
+          <span class="vanicon_text">计划任务</span>
203
+        </van-grid-item>
204
+      </van-grid>
205
+    </div>
193 206
     <div class="card">
194 207
       <div class="title">考核管理</div>
195 208
       <van-grid :border="false" :column-num="4" v-if="showCheckTakeN">
@@ -216,6 +229,7 @@
216 229
         </van-grid-item>
217 230
       </van-grid>
218 231
     </div>
232
+
219 233
   </div>
220 234
 
221 235
 </template>
@@ -365,7 +379,9 @@ const getNameByPath = (path) => {
365 379
     '/drillProcess': '掌上演练',
366 380
     '/checkTake': '逢查必考',
367 381
     '/projectManage/projectConstructionOperation': '项目施工作业管理',
368
-    '/projectManage/projectWorkLedger': '项目作业台账'
382
+    '/projectManage/projectWorkLedger': '项目作业台账',
383
+    '/keyMatters': '重点事项',
384
+    '/planManagement': '计划任务'
369 385
 
370 386
   };
371 387
   return recentlyUsedMapping[path];
@@ -399,7 +415,9 @@ const getPicPathByPath = (path) => {
399 415
     '/drillProcess': 'images/drillProcess.png',
400 416
     '/checkTake': 'images/dt.png',
401 417
     '/projectManage/projectConstructionOperation': 'images/xm.png',
402
-    '/projectManage/projectWorkLedger': 'images/xm.png'
418
+    '/projectManage/projectWorkLedger': 'images/xm.png',
419
+    '/keyMatters': 'images/keyMatters.png',
420
+    '/planManagement': 'images/planManagement.png'
403 421
   };
404 422
   return recentlyUsedMapping[path];
405 423
 };

+ 279
- 0
src/view/keyMatters/index.vue ファイルの表示

@@ -0,0 +1,279 @@
1
+<script setup>
2
+import { getCurrentInstance, onMounted, ref } from 'vue';
3
+import { onBeforeRouteLeave, useRouter } from 'vue-router';
4
+import { useEmergencyStore } from '@/stores/emergencyManager.js';
5
+
6
+const emergencyInfo = useEmergencyStore();
7
+const {
8
+  proxy
9
+} = getCurrentInstance();
10
+
11
+const base_url = '/sgsafe/KeyMattersManagement';
12
+
13
+/* 通用方法: 重置list数据 */
14
+const basicReset = () => {
15
+  finished.value = false;
16
+  loading.value = true;
17
+  pageNum.value = 1;
18
+  list.value = [];
19
+};
20
+
21
+/* 返回上一级页面 */
22
+const router = useRouter();
23
+
24
+/* 查询数据 */
25
+const pageNum = ref(1);
26
+const pageSize = ref(10);
27
+const total = ref(0);
28
+const resultData = ref([]);
29
+const fetchData = ref({
30
+  itemTitle: '',
31
+  itemSource: 'ASSIGNED',
32
+});
33
+
34
+const onSearch = () => {
35
+  basicReset();
36
+  onLoad();
37
+};
38
+
39
+const resetCondition = () => {
40
+  basicReset();
41
+  fetchData.value.itemTitle = '';
42
+  onLoad();
43
+};
44
+
45
+/* 查询请求 */
46
+const queryFetch = async () => {
47
+  const url = base_url + '/query';
48
+  const param = {
49
+    page: pageNum.value,
50
+    rows: pageSize.value,
51
+    params: JSON.stringify(fetchData.value)
52
+  };
53
+  try {
54
+    const res = await proxy.$axios.post(url, param);
55
+    if (res.data.code === 0) {
56
+      total.value = res.data.data.total;
57
+      resultData.value = res.data.data.records;
58
+    } else {
59
+      console.log('查询失败!' + res.data.msg);
60
+    }
61
+  } catch (error) {
62
+    console.error('请求出错:', error);
63
+  }
64
+};
65
+
66
+/* 列表加载与下拉刷新 */
67
+const list = ref([]);
68
+const refreshing = ref(false);
69
+const loading = ref(false);
70
+const finished = ref(false);
71
+
72
+const onRefresh = () => {
73
+  basicReset();
74
+  onLoad();
75
+};
76
+
77
+const onLoad = async () => {
78
+  if (refreshing.value) {
79
+    list.value = [];
80
+    pageNum.value = 1;
81
+    refreshing.value = false;
82
+  }
83
+  try {
84
+    await queryFetch();
85
+    if (pageSize.value * pageNum.value < total.value) {
86
+      list.value = [...list.value, ...resultData.value];
87
+      openStates.value = new Array(list.value.length).fill(true);
88
+      pageNum.value++;
89
+    } else {
90
+      list.value = [...list.value, ...resultData.value];
91
+      openStates.value = new Array(list.value.length).fill(true);
92
+      finished.value = true;
93
+    }
94
+  } catch (error) {
95
+    console.log(error);
96
+    finished.value = true;
97
+  } finally {
98
+    loading.value = false;
99
+  }
100
+};
101
+
102
+/**
103
+ * 按钮实现swipe-cell滑动
104
+ */
105
+const openStates = ref([]);
106
+const swipeCellRefs = ref([]);
107
+const getSwipeCellRef = (el, index) => {
108
+  if (el) {
109
+    swipeCellRefs.value[index] = el;
110
+  }
111
+};
112
+const openSwipe = (idx) => {
113
+  openStates.value = new Array(list.value.length).fill(true);
114
+  if (idx >= 0 && idx < swipeCellRefs.value.length) {
115
+    openStates.value[idx] = false;
116
+    swipeCellRefs.value[idx].open('right');
117
+  }
118
+  document.addEventListener('click', handleDocumentClick);
119
+};
120
+const closeSwipe = (idx) => {
121
+  if (idx >= 0 && idx < swipeCellRefs.value.length) {
122
+    openStates.value[idx] = true;
123
+    swipeCellRefs.value[idx].close();
124
+  }
125
+};
126
+/** 滑动监听 **/
127
+const handleSwipeOpen = (idx) => {
128
+  openStates.value[idx] = false;
129
+};
130
+
131
+const handleSwipeClose = (idx) => {
132
+  openStates.value[idx] = true;
133
+};
134
+
135
+/**
136
+ * 当点击滑动单元格时,开始监听点击事件
137
+ */
138
+const handleDocumentClick = (event) => {
139
+  openStates.value = new Array(list.value.length).fill(true);
140
+};
141
+
142
+/**
143
+ * pinia读取和缓存数据
144
+ */
145
+onMounted(() => {
146
+  if (emergencyInfo.conditionalQueryAttributes) {
147
+    fetchData.value.itemTitle = emergencyInfo.conditionalQueryAttributes;
148
+  }
149
+  basicReset();
150
+  onLoad();
151
+})
152
+onBeforeRouteLeave((to, from, next) => {
153
+  if (to.path === '/keyMatters/view' || to.path === '/keyMatters/report') {
154
+    emergencyInfo.$patch({
155
+      conditionalQueryAttributes: fetchData.value.itemTitle
156
+    });
157
+  } else {
158
+    emergencyInfo.clearMainDeptInfo();
159
+  }
160
+  next();
161
+});
162
+
163
+</script>
164
+
165
+<template>
166
+  <div class="page-container">
167
+    <van-sticky>
168
+      <van-nav-bar title="重点事项汇报">
169
+      </van-nav-bar>
170
+
171
+      <van-search
172
+        v-model="fetchData.itemTitle"
173
+        @search="onSearch"
174
+        @clear="resetCondition"
175
+        placeholder="请输入事项标题"
176
+      />
177
+    </van-sticky>
178
+
179
+    <div class="scroll-container">
180
+      <van-pull-refresh
181
+        v-model="refreshing"
182
+        success-text="刷新成功"
183
+        @refresh="onRefresh"
184
+      >
185
+        <van-list
186
+          class="listDiv"
187
+          :immediate-check="false"
188
+          v-model:loading="loading"
189
+          :finished="finished"
190
+          finished-text="没有更多了"
191
+          @load="onLoad"
192
+        >
193
+          <div v-for="(item,idx) in list" :key="item.id">
194
+            <van-swipe-cell
195
+              title-style="color: #007aff"
196
+              class="goods-card"
197
+              :ref="el => getSwipeCellRef(el, idx)"
198
+              @open="handleSwipeOpen(idx)"
199
+              @close="handleSwipeClose(idx)"
200
+            >
201
+              <template #default>
202
+                <div class="swipe-cell-default">
203
+                  <van-cell :to="{ path: '/keyMatters/view', query: { keyMatter: JSON.stringify(item) } }">
204
+                    <template #title>
205
+                      <span class="bold-title" style="margin-right: 10px">{{ item.itemTitle }}</span>
206
+                      <van-tag style="margin-right: 10px" :type="item.isFinished === '已完成' ? 'success' : 'danger'">{{ item.isFinished }}</van-tag>
207
+                      <van-tag :type="item.isOverdue === '逾期' ? 'danger' : 'success'">{{ item.isOverdue === '逾期' ? '逾期' : '未逾期' }}</van-tag>
208
+                    </template>
209
+                    <template #label>
210
+                      <span style="margin-right: 10px">事项内容:{{ item.itemContent }}</span>
211
+                    </template>
212
+                  </van-cell>
213
+                  <div class="swipe-cell-default-icon">
214
+                    <van-icon v-if="openStates[idx]" name="arrow-double-left" @click.stop="openSwipe(idx)" />
215
+                    <van-icon v-else name="arrow-double-right" @click.stop="closeSwipe(idx)" />
216
+                  </div>
217
+                </div>
218
+              </template>
219
+
220
+              <template #right>
221
+                <van-button class="delete-button" square type="primary" text="汇报"
222
+                            :to="{ path: '/keyMatters/report', query: { keyMatter: JSON.stringify(item) } }" />
223
+              </template>
224
+            </van-swipe-cell>
225
+          </div>
226
+        </van-list>
227
+      </van-pull-refresh>
228
+    </div>
229
+  </div>
230
+</template>
231
+
232
+<style scoped>
233
+.page-container {
234
+  height: 100vh; /* 关键:外层容器高度设为视口高度 */
235
+  display: flex;
236
+  flex-direction: column;
237
+
238
+}
239
+
240
+/*  overflow-y: auto; !* 启用垂直滚动 *!*/
241
+
242
+
243
+.scroll-container {
244
+  flex: 1;
245
+  overflow: auto;
246
+  -webkit-overflow-scrolling: touch; /* iOS 平滑滚动 */
247
+}
248
+
249
+/* 可选:隐藏滚动条(视觉优化) */
250
+.scroll-container::-webkit-scrollbar {
251
+  display: none;
252
+}
253
+
254
+.goods-card {
255
+  margin: 0;
256
+}
257
+
258
+.delete-button {
259
+  height: 100%;
260
+}
261
+
262
+.bold-title {
263
+  font-weight: bold;
264
+  color: #333;
265
+}
266
+
267
+.swipe-cell-default {
268
+  display: flex;
269
+  background-color: #ffffff;
270
+  justify-content: center;
271
+  align-items: center;
272
+}
273
+
274
+.swipe-cell-default-icon {
275
+  width: 60px;
276
+  display: flex;
277
+  justify-content: center;
278
+}
279
+</style>

+ 353
- 0
src/view/keyMatters/report.vue ファイルの表示

@@ -0,0 +1,353 @@
1
+<script setup>
2
+import { computed, getCurrentInstance, onMounted, ref } from 'vue';
3
+import { useRoute, useRouter } from 'vue-router';
4
+import { showFailToast, showLoadingToast, showSuccessToast } from 'vant';
5
+
6
+const base_url = '/sgsafe/KeyMattersRecord';
7
+const { proxy } = getCurrentInstance();
8
+
9
+/* 返回上一级页面 */
10
+const router = useRouter();
11
+const route = useRoute();
12
+const onClickLeft = () => {
13
+  router.go(-1);
14
+};
15
+
16
+const lowerReportForm = ref({
17
+  keyMattersId: ''
18
+})
19
+
20
+const reportingForm = ref({
21
+  itemManifestId: '',
22
+  completionReporting: '',
23
+  completionCount: '1',
24
+  completionTime: ''
25
+});
26
+
27
+/**
28
+ * 获取日期
29
+ */
30
+const timeVisible = ref(false);
31
+const timeArr = ref([]);
32
+const formattedTime = computed(() => {
33
+  if (timeArr.value.length === 0) {
34
+    return '';
35
+  }
36
+  const date = timeArr.value;
37
+  if (!date) return '';
38
+  const d = new Date(date);
39
+  return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`;
40
+});
41
+const onConfirmTime = () => {
42
+  timeVisible.value = false;
43
+  reportingForm.value.completionTime = formattedTime.value;
44
+};
45
+
46
+
47
+const dataList = ref([])
48
+const queryLowerReporting = () => {
49
+  const url = '/sgsafe/KeyMattersRecord/viewReport'
50
+  const param = {
51
+    json: JSON.stringify(lowerReportForm.value)
52
+  }
53
+  proxy.$axios.post(url, param).then(res => {
54
+    if (res.data.code === 0) {
55
+      dataList.value = res.data.data;
56
+    } else {
57
+      showFailToast('查询下级部门汇报失败')
58
+    }
59
+  })
60
+}
61
+
62
+const onSubmit = async (values) => {
63
+  const loadingToast = showLoadingToast({
64
+    duration: 0,
65
+    message: '加载中',
66
+    forbidClick: true
67
+  });
68
+  const url = base_url + '/save';
69
+  const params = {
70
+    json: JSON.stringify(reportingForm.value)
71
+  };
72
+  proxy.$axios.post(url, params).then(res => {
73
+    if (res.data.code === 0) {
74
+      loadingToast.close();
75
+      showSuccessToast('保存成功');
76
+      onClickLeft();
77
+    } else {
78
+      loadingToast.close();
79
+      showFailToast('操作失败!' + res.data.msg);
80
+    }
81
+  });
82
+};
83
+
84
+/*const rowAnalysisText = ref('')
85
+const generateRowAnalysis = (row) => {
86
+  // 设置当前行的加载状态
87
+  row.analysisLoading = true
88
+
89
+  // 构造提示词
90
+  let prompt = `请总结下面一段话:` + row.completionReporting
91
+
92
+  // 构造消息对象
93
+  const messages = [
94
+    {
95
+      role: "user",
96
+      content: prompt
97
+    }
98
+  ]
99
+
100
+  // 调用后端AI接口,避免暴露API密钥
101
+  callAIStream(
102
+    messages,
103
+    (content, isEnd) => {
104
+
105
+      row.aiAnalysis = content
106
+      if (content) {
107
+        rowAnalysisText.value = content
108
+      }
109
+      // console.log('111',row.aiAnalysis)
110
+      // 如果是最终结果,停止加载状态
111
+      if (isEnd) {
112
+        row.analysisLoading = false
113
+      }
114
+    }
115
+  )
116
+}
117
+const callAIStream = async (messageHistory, callback) => {
118
+  try {
119
+    const url = `${import.meta.env.VITE_BASE_API}/sgsafe/deepseek/chat-stream`
120
+
121
+    const response = await fetch(url, {
122
+      method: 'POST',
123
+      headers: {
124
+        'Content-Type': 'application/json',
125
+        'token': localStorage.getItem('token') || '',
126
+        'userId': localStorage.getItem('userId') || ''
127
+      },
128
+      body: JSON.stringify({
129
+        messages: messageHistory
130
+      })
131
+    })
132
+
133
+    if (!response.ok) {
134
+      throw new Error(`HTTP error! status: ${response.status}`)
135
+    }
136
+
137
+    if (!response.body) {
138
+      throw new Error('ReadableStream not supported in this browser.')
139
+    }
140
+
141
+    // 处理流式响应
142
+    const reader = response.body.getReader()
143
+    const decoder = new TextDecoder('utf-8')
144
+    let buffer = ''
145
+    let fullResponse = ''
146
+
147
+    try {
148
+      while (true) {
149
+        const {done, value} = await reader.read()
150
+
151
+        if (done) {
152
+          callback(fullResponse, true)
153
+          break
154
+        }
155
+
156
+        const chunk = decoder.decode(value, {stream: true})
157
+        buffer += chunk
158
+
159
+        // 处理缓冲区数据
160
+        const lines = buffer.split('\n')
161
+        let remainingBuffer = lines.pop() // 保留不完整的行
162
+
163
+        for (const line of lines) {
164
+          if (line.trim() === '') continue
165
+
166
+          if (line.startsWith('data:')) {
167
+            const data = line.slice(5).trim()
168
+
169
+            if (data === '[DONE]') {
170
+              callback(fullResponse, true)
171
+              return
172
+            }
173
+
174
+            if (data === '') continue
175
+
176
+            try {
177
+              const jsonData = JSON.parse(data)
178
+
179
+              if (jsonData.choices?.[0]?.delta) {
180
+                let content = ''
181
+                if (jsonData.choices[0].delta.reasoning_content) {
182
+                  content += jsonData.choices[0].delta.reasoning_content
183
+                }
184
+                if (jsonData.choices[0].delta.content) {
185
+                  content += jsonData.choices[0].delta.content
186
+                }
187
+
188
+                if (content) {
189
+                  fullResponse += content
190
+                  callback(fullResponse, false)
191
+                }
192
+              }
193
+            } catch (parseError) {
194
+              console.error('JSON解析错误:', parseError, '数据:', data)
195
+            }
196
+          }
197
+        }
198
+
199
+        buffer = remainingBuffer
200
+      }
201
+    } catch (error) {
202
+      console.error('处理流响应失败:', error)
203
+      callback('处理响应时发生错误: ' + error.message, true)
204
+    }
205
+  } catch (error) {
206
+    console.error('AI调用失败:', error)
207
+    callback('AI调用失败: ' + error.message, true)
208
+  }
209
+}*/
210
+
211
+const keyMatter = ref({});
212
+const isQuantified = ref(false);
213
+onMounted(() => {
214
+  if (route.query.keyMatter) {
215
+    keyMatter.value = JSON.parse(route.query.keyMatter);
216
+    if (!keyMatter.value.id) {
217
+      showFailToast('数据错误,请联系管理员');
218
+      onClickLeft();
219
+    }
220
+    // console.log(keyMatter.value);
221
+    reportingForm.value.itemManifestId = keyMatter.value.id;
222
+    lowerReportForm.value.keyMattersId = keyMatter.value.id;
223
+    isQuantified.value = keyMatter.value.isQuantified;
224
+  }
225
+  queryLowerReporting()
226
+})
227
+
228
+</script>
229
+
230
+<template>
231
+  <div class="page-container">
232
+    <van-sticky>
233
+      <van-nav-bar title="重点事项汇报">
234
+      </van-nav-bar>
235
+    </van-sticky>
236
+
237
+    <div
238
+      v-for="(report, index) in dataList"
239
+      :key="index"
240
+      class="mobile-report-card"
241
+    >
242
+      <div class="card-header">
243
+        <h4>汇报部门: {{ report.reportingDeptName }}</h4>
244
+      </div>
245
+      <p><strong>汇报内容:</strong> {{ report.completionReporting }}</p>
246
+      <p><strong>完成次数:</strong> {{ report.completionCount }}</p>
247
+      <p><strong>完成时间:</strong> {{ report.completionTime }}</p>
248
+    </div>
249
+
250
+  <van-form @submit="onSubmit">
251
+    <van-field
252
+      v-model="reportingForm.completionReporting"
253
+      label="汇报内容"
254
+      type="textarea"
255
+      name="completionReporting"
256
+      required
257
+      placeholder="请输入汇报内容"
258
+      :rules="[{required: true, message: '请输入汇报内容'}]"
259
+    />
260
+
261
+    <van-field
262
+      v-if="isQuantified"
263
+      v-model="reportingForm.completionCount"
264
+      type="number"
265
+      label="完成次数"
266
+      name="completionCount"
267
+      required
268
+      :rules="[{required: true, message: '请输入完成次数'},
269
+           { pattern: /^[1-9]\d*$/, message: '必须为正整数' }]"
270
+    />
271
+
272
+    <van-field
273
+      readonly
274
+      v-model="formattedTime"
275
+      label="完成时间"
276
+      name="formattedTime"
277
+      placeholder="请选择完成时间"
278
+      required
279
+      :rules="[{required: true, message: '请选择完成时间'}]"
280
+      @click="timeVisible = true"
281
+    />
282
+
283
+    <div style="margin: 16px;">
284
+      <van-button round block type="primary" native-type="submit">提交</van-button>
285
+    </div>
286
+  </van-form>
287
+
288
+  <van-popup
289
+    :close-on-click-overlay="false"
290
+    v-model:show="timeVisible"
291
+    position="bottom"
292
+    :teleport="false"
293
+  >
294
+    <van-date-picker
295
+      v-model="timeArr"
296
+      type="date"
297
+      @confirm="onConfirmTime"
298
+      @cancel="timeVisible = false"
299
+    />
300
+  </van-popup>
301
+  </div>
302
+</template>
303
+
304
+<style scoped>
305
+.page-container {
306
+  height: 100vh;
307
+  display: flex;
308
+  flex-direction: column;
309
+}
310
+
311
+.scroll-container {
312
+  flex: 1;
313
+  overflow: auto;
314
+  -webkit-overflow-scrolling: touch; /* iOS 平滑滚动 */
315
+}
316
+
317
+/* 可选:隐藏滚动条(视觉优化) */
318
+.scroll-container::-webkit-scrollbar {
319
+  display: none;
320
+}
321
+
322
+.mobile-report-card {
323
+  display: flex;
324
+  flex-direction: column;
325
+  gap: 12px;
326
+  background-color: #ffffff;
327
+  padding: 16px;
328
+  margin: 0 12px 12px;
329
+  border-radius: 8px;
330
+  word-break: break-word;
331
+}
332
+
333
+.mobile-report-card .card-header h4 {
334
+  margin: 0;
335
+  font-size: 16px;
336
+  font-weight: 600;
337
+  color: #323233;
338
+}
339
+
340
+.mobile-report-card p {
341
+  margin: 0;
342
+  font-size: 14px;
343
+  color: #646566;
344
+  line-height: 1.5;
345
+}
346
+
347
+.mobile-report-card p strong {
348
+  color: #323233;
349
+  font-weight: 500;
350
+  margin-right: 4px;
351
+}
352
+
353
+</style>

+ 96
- 0
src/view/keyMatters/view.vue ファイルの表示

@@ -0,0 +1,96 @@
1
+<script setup>
2
+import { computed } from 'vue';
3
+import { useRoute } from 'vue-router';
4
+
5
+const route = useRoute();
6
+let keyMatter = {};
7
+if (route.query.keyMatter) {
8
+  keyMatter = JSON.parse(route.query.keyMatter);
9
+}
10
+
11
+const displayOverdueText = computed(() => {
12
+  return keyMatter.isOverdue === '逾期' ? '逾期' : '未逾期'
13
+})
14
+
15
+const displayQuantifiedText = computed(() => {
16
+  return keyMatter.isQunatified ? '是' : '否'
17
+})
18
+
19
+
20
+</script>
21
+
22
+<template>
23
+
24
+  <div class="page-container">
25
+    <van-sticky>
26
+      <van-nav-bar title="重点事项查看">
27
+      </van-nav-bar>
28
+    </van-sticky>
29
+    <div class="scroll-container">
30
+      <van-form>
31
+        <van-field
32
+          readonly
33
+          v-model="keyMatter.itemTitle"
34
+          label="事项标题:"
35
+        />
36
+
37
+        <van-field
38
+          readonly
39
+          v-model="keyMatter.itemContent"
40
+          label="事项内容:"
41
+          type="textarea"
42
+        />
43
+
44
+        <van-field
45
+          readonly
46
+          v-model="keyMatter.itemStartTime"
47
+          label="开始时间:"
48
+        />
49
+
50
+        <van-field
51
+          readonly
52
+          v-model="keyMatter.itemEndTime"
53
+          label="结束时间:"
54
+        />
55
+
56
+        <van-field
57
+          readonly
58
+          v-model="keyMatter.isFinished"
59
+          label="完成状态:"
60
+        />
61
+
62
+        <van-field
63
+          v-model="displayOverdueText"
64
+          readonly
65
+          label="是否逾期:"
66
+        />
67
+
68
+        <van-field
69
+          v-model="displayQuantifiedText"
70
+          readonly
71
+          label="是否量化:"
72
+        />
73
+      </van-form>
74
+    </div>
75
+  </div>
76
+</template>
77
+
78
+<style scoped>
79
+.page-container {
80
+  height: 100vh; /* 关键:外层容器高度设为视口高度 */
81
+  display: flex;
82
+  flex-direction: column;
83
+}
84
+
85
+.scroll-container {
86
+  flex: 1;
87
+  overflow: auto;
88
+  -webkit-overflow-scrolling: touch; /* iOS 平滑滚动 */
89
+}
90
+
91
+/* 可选:隐藏滚动条(视觉优化) */
92
+.scroll-container::-webkit-scrollbar {
93
+  display: none;
94
+}
95
+
96
+</style>

+ 194
- 0
src/view/planManagement/index.vue ファイルの表示

@@ -0,0 +1,194 @@
1
+<script setup>
2
+import { getCurrentInstance, onMounted, ref } from 'vue';
3
+import { onBeforeRouteLeave, useRouter } from 'vue-router';
4
+import { useEmergencyStore } from '@/stores/emergencyManager.js';
5
+
6
+const emergencyInfo = useEmergencyStore();
7
+const { proxy } = getCurrentInstance();
8
+const base_url = '/sgsafe/PlanManagement';
9
+
10
+/* 通用方法: 重置list数据 */
11
+const basicReset = () => {
12
+  finished.value = false;
13
+  loading.value = true;
14
+  pageNum.value = 1;
15
+  list.value = [];
16
+};
17
+
18
+/* 返回上一级页面 */
19
+const router = useRouter();
20
+
21
+/* 查询数据 */
22
+const pageNum = ref(1);
23
+const pageSize = ref(10);
24
+const total = ref(0);
25
+const resultData = ref([]);
26
+const fetchData = ref({
27
+  planName: '',
28
+  planSource: 'ASSIGNED'
29
+});
30
+
31
+const onSearch = () => {
32
+  basicReset();
33
+  onLoad();
34
+};
35
+
36
+const resetCondition = () => {
37
+  basicReset();
38
+  fetchData.value.planName = '';
39
+  onLoad();
40
+};
41
+
42
+/* 查询请求 */
43
+const queryFetch = async () => {
44
+  const url = base_url + '/query';
45
+  const param = {
46
+    page: pageNum.value,
47
+    rows: pageSize.value,
48
+    params: JSON.stringify(fetchData.value)
49
+  };
50
+  try {
51
+    const res = await proxy.$axios.post(url, param);
52
+    if (res.data.code === 0) {
53
+      total.value = res.data.data.total;
54
+      resultData.value = res.data.data.records;
55
+    } else {
56
+      console.log('查询失败!' + res.data.msg);
57
+    }
58
+  } catch (error) {
59
+    console.error('请求出错:', error);
60
+  }
61
+};
62
+
63
+/* 列表加载与下拉刷新 */
64
+const list = ref([]);
65
+const refreshing = ref(false);
66
+const loading = ref(false);
67
+const finished = ref(false);
68
+
69
+const onRefresh = () => {
70
+  basicReset();
71
+  onLoad();
72
+};
73
+
74
+const onLoad = async () => {
75
+  if (refreshing.value) {
76
+    list.value = [];
77
+    pageNum.value = 1;
78
+    refreshing.value = false;
79
+  }
80
+  try {
81
+    await queryFetch();
82
+    if (pageSize.value * pageNum.value < total.value) {
83
+      list.value = [...list.value, ...resultData.value];
84
+      pageNum.value++;
85
+    } else {
86
+      list.value = [...list.value, ...resultData.value];
87
+      finished.value = true;
88
+    }
89
+  } catch (error) {
90
+    console.log(error);
91
+    finished.value = true;
92
+  } finally {
93
+    loading.value = false;
94
+  }
95
+};
96
+
97
+/**
98
+ * pinia读取和缓存数据
99
+ */
100
+onMounted(() => {
101
+  if (emergencyInfo.conditionalQueryAttributes) {
102
+    fetchData.value.planName = emergencyInfo.conditionalQueryAttributes;
103
+  }
104
+  basicReset();
105
+  onLoad();
106
+});
107
+onBeforeRouteLeave((to, from, next) => {
108
+  if (to.path === '/planManagement/manifest') {
109
+    emergencyInfo.$patch({
110
+      conditionalQueryAttributes: fetchData.value.planName
111
+    });
112
+  } else {
113
+    emergencyInfo.clearMainDeptInfo();
114
+  }
115
+  next();
116
+});
117
+
118
+</script>
119
+
120
+<template>
121
+  <div class="page-container">
122
+    <van-sticky>
123
+      <van-nav-bar title="待完成计划">
124
+      </van-nav-bar>
125
+
126
+      <van-search
127
+        v-model="fetchData.planName"
128
+        @search="onSearch"
129
+        @clear="resetCondition"
130
+        placeholder="请输入计划名称"
131
+      />
132
+    </van-sticky>
133
+
134
+    <div class="scroll-container">
135
+      <van-pull-refresh
136
+        v-model="refreshing"
137
+        success-text="刷新成功"
138
+        @refresh="onRefresh"
139
+      >
140
+        <van-list
141
+          class="listDiv"
142
+          :immediate-check="false"
143
+          v-model:loading="loading"
144
+          :finished="finished"
145
+          finished-text="没有更多了"
146
+          @load="onLoad"
147
+        >
148
+          <div v-for="item in list" :key="item.id">
149
+            <van-cell :to="{ path: '/planManagement/manifest', query: { plan: JSON.stringify(item) } }"
150
+                      style="margin-right: 10px;">
151
+              <template #title>
152
+                <span class="bold-title" style="margin-right: 10px;">{{ item.planName }}</span>
153
+                <van-tag style="margin-right: 10px;" :type="item.isFinished === '已完成' ? 'success' : 'danger'">{{ item.isFinished }}
154
+                </van-tag>
155
+                <van-tag style="margin-right: 10px;" :type="item.isOverdue === '逾期' ? 'danger' : 'success'">
156
+                  {{ item.isOverdue === '逾期' ? '逾期' : '未逾期' }}
157
+                </van-tag>
158
+              </template>
159
+              <template #label>
160
+                <span style="margin-right: 10px;">开始时间:{{ item.planStartTime }}</span>
161
+                <span>结束时间:{{ item.planEndTime }}</span>
162
+              </template>
163
+            </van-cell>
164
+          </div>
165
+        </van-list>
166
+      </van-pull-refresh>
167
+    </div>
168
+  </div>
169
+</template>
170
+
171
+<style scoped>
172
+.page-container {
173
+  height: 100vh; /* 关键:外层容器高度设为视口高度 */
174
+  display: flex;
175
+  flex-direction: column;
176
+}
177
+
178
+.scroll-container {
179
+  flex: 1;
180
+  overflow: auto;
181
+  -webkit-overflow-scrolling: touch; /* iOS 平滑滚动 */
182
+}
183
+
184
+/* 可选:隐藏滚动条(视觉优化) */
185
+.scroll-container::-webkit-scrollbar {
186
+  display: none;
187
+}
188
+
189
+.bold-title {
190
+  font-weight: bold;
191
+  color: #333;
192
+}
193
+
194
+</style>

+ 290
- 0
src/view/planManagement/manifest.vue ファイルの表示

@@ -0,0 +1,290 @@
1
+<script setup>
2
+import { getCurrentInstance, onMounted, ref } from 'vue';
3
+import { onBeforeRouteLeave, useRouter,useRoute } from 'vue-router';
4
+import { useEmergencyStore } from '@/stores/emergencyManager.js';
5
+import { showFailToast } from 'vant';
6
+
7
+const emergencyInfo = useEmergencyStore();
8
+const {
9
+  proxy
10
+} = getCurrentInstance();
11
+
12
+const base_url = '/sgsafe/TaskManifest';
13
+
14
+/* 通用方法: 重置list数据 */
15
+const basicReset = () => {
16
+  finished.value = false;
17
+  loading.value = true;
18
+  pageNum.value = 1;
19
+  list.value = [];
20
+};
21
+
22
+/* 返回上一级页面 */
23
+const router = useRouter();
24
+const route = useRoute();
25
+
26
+/* 查询数据 */
27
+const pageNum = ref(1);
28
+const pageSize = ref(10);
29
+const total = ref(0);
30
+const resultData = ref([]);
31
+const fetchData = ref({
32
+  work: '',
33
+  owningPlanId: '',
34
+});
35
+
36
+const onSearch = () => {
37
+  basicReset();
38
+  onLoad();
39
+};
40
+
41
+const resetCondition = () => {
42
+  basicReset();
43
+  fetchData.value.work = '';
44
+  onLoad();
45
+};
46
+
47
+/* 查询请求 */
48
+const queryFetch = async () => {
49
+  const url = base_url + '/query';
50
+  const param = {
51
+    page: pageNum.value,
52
+    rows: pageSize.value,
53
+    params: JSON.stringify(fetchData.value)
54
+  };
55
+  try {
56
+    const res = await proxy.$axios.post(url, param);
57
+    if (res.data.code === 0) {
58
+      total.value = res.data.data.total;
59
+      resultData.value = res.data.data.records;
60
+    } else {
61
+      console.log('查询失败!' + res.data.msg);
62
+    }
63
+  } catch (error) {
64
+    console.error('请求出错:', error);
65
+  }
66
+};
67
+
68
+/* 列表加载与下拉刷新 */
69
+const list = ref([]);
70
+const refreshing = ref(false);
71
+const loading = ref(false);
72
+const finished = ref(false);
73
+
74
+const onRefresh = () => {
75
+  basicReset();
76
+  onLoad();
77
+};
78
+
79
+const onLoad = async () => {
80
+  if (refreshing.value) {
81
+    list.value = [];
82
+    pageNum.value = 1;
83
+    refreshing.value = false;
84
+  }
85
+  try {
86
+    await queryFetch();
87
+    if (pageSize.value * pageNum.value < total.value) {
88
+      list.value = [...list.value, ...resultData.value];
89
+      openStates.value = new Array(list.value.length).fill(true);
90
+      pageNum.value++;
91
+    } else {
92
+      list.value = [...list.value, ...resultData.value];
93
+      openStates.value = new Array(list.value.length).fill(true);
94
+      finished.value = true;
95
+    }
96
+  } catch (error) {
97
+    console.log(error);
98
+    finished.value = true;
99
+  } finally {
100
+    loading.value = false;
101
+  }
102
+};
103
+
104
+/**
105
+ * 按钮实现swipe-cell滑动
106
+ */
107
+const openStates = ref([]);
108
+const swipeCellRefs = ref([]);
109
+const getSwipeCellRef = (el, index) => {
110
+  if (el) {
111
+    swipeCellRefs.value[index] = el;
112
+  }
113
+};
114
+const openSwipe = (idx) => {
115
+  openStates.value = new Array(list.value.length).fill(true);
116
+  if (idx >= 0 && idx < swipeCellRefs.value.length) {
117
+    openStates.value[idx] = false;
118
+    swipeCellRefs.value[idx].open('right');
119
+  }
120
+  document.addEventListener('click', handleDocumentClick);
121
+};
122
+const closeSwipe = (idx) => {
123
+  if (idx >= 0 && idx < swipeCellRefs.value.length) {
124
+    openStates.value[idx] = true;
125
+    swipeCellRefs.value[idx].close();
126
+  }
127
+};
128
+/** 滑动监听 **/
129
+const handleSwipeOpen = (idx) => {
130
+  openStates.value[idx] = false;
131
+};
132
+
133
+const handleSwipeClose = (idx) => {
134
+  openStates.value[idx] = true;
135
+};
136
+
137
+/**
138
+ * 当点击滑动单元格时,开始监听点击事件
139
+ */
140
+const handleDocumentClick = (event) => {
141
+  openStates.value = new Array(list.value.length).fill(true);
142
+};
143
+
144
+/**
145
+ * pinia读取和缓存数据
146
+ */
147
+const plan = ref({})
148
+onMounted(() => {
149
+  plan.value = JSON.parse(route.query.plan);
150
+  if (!plan.value) {
151
+    showFailToast('数据传递出错,请联系管理员')
152
+    router.go(-1)
153
+  }
154
+
155
+  fetchData.value.owningPlanId = plan.value.planManifestId
156
+  console.log('planId', plan.value.planManifestId);
157
+  if (emergencyInfo.childQueryAttributes) {
158
+    fetchData.value.work = emergencyInfo.childQueryAttributes;
159
+  }
160
+  console.log(fetchData.value);
161
+  basicReset();
162
+  onLoad();
163
+})
164
+onBeforeRouteLeave((to, from, next) => {
165
+  if (to.path === '/planManagement/manifestView' || to.path === '/planManagement/manifestReport') {
166
+    emergencyInfo.$patch({
167
+      childQueryAttributes: fetchData.value.work
168
+    });
169
+  } else {
170
+    emergencyInfo.clearChildQueryInfo()
171
+  }
172
+  next();
173
+});
174
+
175
+</script>
176
+
177
+<template>
178
+  <div class="page-container">
179
+    <van-sticky>
180
+      <van-nav-bar title="任务清单">
181
+      </van-nav-bar>
182
+
183
+      <van-search
184
+        v-model="fetchData.work"
185
+        @search="onSearch"
186
+        @clear="resetCondition"
187
+        placeholder="请输入工作名称"
188
+      />
189
+    </van-sticky>
190
+
191
+    <div class="scroll-container">
192
+      <van-pull-refresh
193
+        v-model="refreshing"
194
+        success-text="刷新成功"
195
+        @refresh="onRefresh"
196
+      >
197
+        <van-list
198
+          class="listDiv"
199
+          :immediate-check="false"
200
+          v-model:loading="loading"
201
+          :finished="finished"
202
+          finished-text="没有更多了"
203
+          @load="onLoad"
204
+        >
205
+          <div v-for="(item,idx) in list" :key="item.id">
206
+            <van-swipe-cell
207
+              title-style="color: #007aff"
208
+              class="goods-card"
209
+              :ref="el => getSwipeCellRef(el, idx)"
210
+              @open="handleSwipeOpen(idx)"
211
+              @close="handleSwipeClose(idx)"
212
+            >
213
+              <template #default>
214
+                <div class="swipe-cell-default">
215
+                  <van-cell :to="{ path: '/planManagement/manifestView', query: { manifest: JSON.stringify(item) } }">
216
+                    <template #title>
217
+                      <span class="bold-title" style="margin-right: 10px">{{ item.work }}</span>
218
+                      <van-tag :type="item.isFinished === '已完成' ? 'success' : 'danger'">{{ item.isFinished }}</van-tag>
219
+                    </template>
220
+                    <template #label>
221
+                      <span style="margin-right: 10px">完成时限:{{ item.subTaskCompletionDeadline }}</span>
222
+                    </template>
223
+                  </van-cell>
224
+                  <div class="swipe-cell-default-icon">
225
+                    <van-icon v-if="openStates[idx]" name="arrow-double-left" @click.stop="openSwipe(idx)" />
226
+                    <van-icon v-else name="arrow-double-right" @click.stop="closeSwipe(idx)" />
227
+                  </div>
228
+                </div>
229
+              </template>
230
+
231
+              <template #right>
232
+                <van-button class="delete-button" square type="primary" text="汇报"
233
+                            :to="{ path: '/planManagement/manifestReport', query: { manifest: JSON.stringify(item) } }" />
234
+              </template>
235
+            </van-swipe-cell>
236
+          </div>
237
+        </van-list>
238
+      </van-pull-refresh>
239
+    </div>
240
+  </div>
241
+</template>
242
+
243
+<style scoped>
244
+.page-container {
245
+  height: 100vh; /* 关键:外层容器高度设为视口高度 */
246
+  display: flex;
247
+  flex-direction: column;
248
+
249
+}
250
+
251
+/*  overflow-y: auto; !* 启用垂直滚动 *!*/
252
+
253
+
254
+.scroll-container {
255
+  flex: 1;
256
+  overflow: auto;
257
+  -webkit-overflow-scrolling: touch; /* iOS 平滑滚动 */
258
+}
259
+
260
+/* 可选:隐藏滚动条(视觉优化) */
261
+.scroll-container::-webkit-scrollbar {
262
+  display: none;
263
+}
264
+
265
+.goods-card {
266
+  margin: 0;
267
+}
268
+
269
+.delete-button {
270
+  height: 100%;
271
+}
272
+
273
+.bold-title {
274
+  font-weight: bold;
275
+  color: #333;
276
+}
277
+
278
+.swipe-cell-default {
279
+  display: flex;
280
+  background-color: #ffffff;
281
+  justify-content: center;
282
+  align-items: center;
283
+}
284
+
285
+.swipe-cell-default-icon {
286
+  width: 60px;
287
+  display: flex;
288
+  justify-content: center;
289
+}
290
+</style>

+ 211
- 0
src/view/planManagement/manifestReport.vue ファイルの表示

@@ -0,0 +1,211 @@
1
+<script setup>
2
+import { computed, getCurrentInstance, onMounted, ref } from 'vue';
3
+import { useRoute, useRouter } from 'vue-router';
4
+import { showFailToast, showLoadingToast, showSuccessToast } from 'vant';
5
+
6
+const base_url = '/sgsafe/TaskManifestReceipt';
7
+const { proxy } = getCurrentInstance();
8
+
9
+/* 返回上一级页面 */
10
+const router = useRouter();
11
+const route = useRoute();
12
+
13
+const reportingForm = ref({
14
+  taskManifestId: '',
15
+  completionReporting: '',
16
+  completionCount: '1',
17
+  completionTime: ''
18
+});
19
+
20
+/**
21
+ * 获取日期
22
+ */
23
+const timeVisible = ref(false);
24
+const timeArr = ref([]);
25
+const formattedTime = computed(() => {
26
+  if (timeArr.value.length === 0) {
27
+    return '';
28
+  }
29
+  const date = timeArr.value;
30
+  if (!date) return '';
31
+  const d = new Date(date);
32
+  return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`;
33
+});
34
+const onConfirmTime = () => {
35
+  timeVisible.value = false;
36
+  reportingForm.value.completionTime = formattedTime.value;
37
+};
38
+
39
+
40
+const lowerReportForm = ref({
41
+  associateMasterId: ''
42
+})
43
+const dataList = ref([])
44
+const queryLowerReporting = () => {
45
+  const url = '/sgsafe/TaskManifestReceipt/queryByManifestId'
46
+  const param = {
47
+    json: JSON.stringify(lowerReportForm.value)
48
+  }
49
+  proxy.$axios.post(url, param).then(res => {
50
+    if (res.data.code === 0) {
51
+      dataList.value = res.data.data;
52
+    } else {
53
+      showFailToast('查询下级部门汇报失败')
54
+    }
55
+  })
56
+}
57
+
58
+const onSubmit = async (values) => {
59
+  const loadingToast = showLoadingToast({
60
+    duration: 0,
61
+    message: '加载中',
62
+    forbidClick: true
63
+  });
64
+  const url = base_url + '/save';
65
+  const params = {
66
+    json: JSON.stringify(reportingForm.value)
67
+  };
68
+  proxy.$axios.post(url, params).then(res => {
69
+    if (res.data.code === 0) {
70
+      loadingToast.close();
71
+      showSuccessToast('保存成功');
72
+      router.go(-1)
73
+    } else {
74
+      loadingToast.close();
75
+      showFailToast('操作失败!' + res.data.msg);
76
+    }
77
+  });
78
+};
79
+
80
+const manifest = ref({});
81
+const isQuantified = ref(false);
82
+onMounted(() => {
83
+  manifest.value = JSON.parse(route.query.manifest);
84
+  console.log('manifest', manifest.value);
85
+  if (!manifest.value.id) {
86
+    showFailToast('数据错误,请联系管理员');
87
+    router.go(-1)
88
+  }
89
+  lowerReceiptForm.value.associateMasterId = manifest.value.id
90
+  reportingForm.value.taskManifestId = manifest.value.id;
91
+  isQuantified.value = manifest.value.isQuantified;
92
+  queryLowerReporting();
93
+});
94
+
95
+</script>
96
+
97
+<template>
98
+  <div class="page-container">
99
+    <van-sticky>
100
+      <van-nav-bar title="计划任务汇报">
101
+      </van-nav-bar>
102
+    </van-sticky>
103
+
104
+    <div
105
+      v-for="(report, index) in dataList"
106
+      :key="index"
107
+      class="mobile-report-card"
108
+    >
109
+      <div class="card-header">
110
+        <h4>汇报部门: {{ report.reportingDeptName }}</h4>
111
+      </div>
112
+      <p><strong>汇报内容:</strong> {{ report.completionReporting }}</p>
113
+      <p><strong>完成次数:</strong> {{ report.completionCount }}</p>
114
+      <p><strong>完成时间:</strong> {{ report.completionTime }}</p>
115
+    </div>
116
+
117
+    <van-form @submit="onSubmit">
118
+      <van-field
119
+        v-model="reportingForm.completionReporting"
120
+        label="汇报内容"
121
+        type="textarea"
122
+        name="completionReporting"
123
+        required
124
+        placeholder="请输入汇报内容"
125
+        :rules="[{required: true, message: '请输入汇报内容'}]"
126
+      />
127
+
128
+      <van-field
129
+        v-if="isQuantified"
130
+        v-model="reportingForm.completionCount"
131
+        type="number"
132
+        label="完成次数"
133
+        name="completionCount"
134
+        required
135
+        :rules="[{required: true, message: '请输入完成次数'},
136
+           { pattern: /^[1-9]\d*$/, message: '必须为正整数' }]"
137
+      />
138
+
139
+      <van-field
140
+        readonly
141
+        v-model="formattedTime"
142
+        label="完成时间"
143
+        name="formattedTime"
144
+        placeholder="请选择完成时间"
145
+        required
146
+        :rules="[{required: true, message: '请选择完成时间'}]"
147
+        @click="timeVisible = true"
148
+      />
149
+
150
+      <div style="margin: 16px;">
151
+        <van-button round block type="primary" native-type="submit">提交</van-button>
152
+      </div>
153
+    </van-form>
154
+
155
+    <van-popup
156
+      :close-on-click-overlay="false"
157
+      v-model:show="timeVisible"
158
+      position="bottom"
159
+      :teleport="false"
160
+    >
161
+      <van-date-picker
162
+        v-model="timeArr"
163
+        type="date"
164
+        @confirm="onConfirmTime"
165
+        @cancel="timeVisible = false"
166
+      />
167
+    </van-popup>
168
+  </div>
169
+</template>
170
+
171
+<style scoped>
172
+.page-container {
173
+  height: 100vh; /* 关键:外层容器高度设为视口高度 */
174
+  display: flex;
175
+  flex-direction: column;
176
+}
177
+
178
+/*  overflow-y: auto; !* 启用垂直滚动 *!*/
179
+
180
+
181
+.scroll-container {
182
+  flex: 1;
183
+  overflow: auto;
184
+  -webkit-overflow-scrolling: touch; /* iOS 平滑滚动 */
185
+}
186
+
187
+/* 可选:隐藏滚动条(视觉优化) */
188
+.scroll-container::-webkit-scrollbar {
189
+  display: none;
190
+}
191
+
192
+.reports-container {
193
+  display: flex;
194
+  flex-wrap: wrap;
195
+  gap: 16px;
196
+  padding: 16px;
197
+}
198
+
199
+.report-card .report-header h4 {
200
+  margin-top: 0;
201
+  font-size: 16px;
202
+  color: #333;
203
+}
204
+
205
+.report-card p {
206
+  margin: 8px 0;
207
+  font-size: 14px;
208
+  line-height: 1.5;
209
+}
210
+
211
+</style>

+ 119
- 0
src/view/planManagement/manifestView.vue ファイルの表示

@@ -0,0 +1,119 @@
1
+<script setup>
2
+import { computed, onMounted, ref } from 'vue';
3
+import { useRoute, useRouter } from 'vue-router';
4
+import { showFailToast } from 'vant';
5
+
6
+const router = useRouter();
7
+const route = useRoute();
8
+
9
+const manifest = ref({})
10
+onMounted(() => {
11
+  manifest.value = JSON.parse(route.query.manifest)
12
+  console.log('manifest', manifest.value);
13
+  if (!manifest.value) {
14
+    showFailToast('数据传参失败,请联系管理员!')
15
+    router.go(-1)
16
+  }
17
+})
18
+
19
+const displayQuantifiedText = computed(() => {
20
+  return manifest.value.isQuantified ? '是' : '否'
21
+})
22
+
23
+const displayFinishedText = computed(() => {
24
+  return manifest.value.isFinished === '已完成' ? '已完成' : '待完成'
25
+})
26
+
27
+</script>
28
+
29
+<template>
30
+
31
+  <div class="page-container">
32
+    <van-sticky>
33
+      <van-nav-bar title="计划任务查看">
34
+      </van-nav-bar>
35
+    </van-sticky>
36
+    <div class="scroll-container">
37
+      <van-form >
38
+        <van-field
39
+          readonly
40
+          type="textarea"
41
+          v-model="manifest.work"
42
+          label="工作:"
43
+        />
44
+
45
+        <van-field
46
+          readonly
47
+          type="textarea"
48
+          v-model="manifest.subItemWorkContent"
49
+          label="小项工作内容:"
50
+        />
51
+
52
+        <van-field
53
+          readonly
54
+          type="textarea"
55
+          v-model="manifest.targetRequirements"
56
+          label="目标要求:"
57
+        />
58
+
59
+        <van-field
60
+          readonly
61
+          type="textarea"
62
+          v-model="manifest.subTask"
63
+          label="分项任务:"
64
+        />
65
+
66
+        <van-field
67
+          readonly
68
+          type="textarea"
69
+          v-model="manifest.subTaskCompletionDeadline"
70
+          label="分项任务完成时限:"
71
+        />
72
+
73
+        <van-field
74
+          readonly
75
+          v-model="displayFinishedText"
76
+          label="完成状态:"
77
+        />
78
+
79
+        <van-field
80
+          readonly
81
+          v-model="displayQuantifiedText"
82
+          label="是否可量化:"
83
+        />
84
+
85
+      </van-form>
86
+    </div>
87
+  </div>
88
+</template>
89
+
90
+<style scoped>
91
+.page-container {
92
+  height: 100vh; /* 关键:外层容器高度设为视口高度 */
93
+  display: flex;
94
+  flex-direction: column;
95
+}
96
+
97
+/*  overflow-y: auto; !* 启用垂直滚动 *!*/
98
+
99
+
100
+.scroll-container {
101
+  flex: 1;
102
+  overflow: auto;
103
+  -webkit-overflow-scrolling: touch; /* iOS 平滑滚动 */
104
+}
105
+
106
+/* 可选:隐藏滚动条(视觉优化) */
107
+.scroll-container::-webkit-scrollbar {
108
+  display: none;
109
+}
110
+
111
+
112
+.button-select {
113
+  display: flex;
114
+  justify-content: space-around;
115
+  align-items: center;
116
+  margin: 20px;
117
+}
118
+
119
+</style>

読み込み中…
キャンセル
保存