Procházet zdrojové kódy

feat: 新增5个台账查询页面及台账数据清理功能

- 新增日常培训记录、组长履职情况记录、健康锐兵、宿舍消防安全专项自查、培训台账问题通报五个台账查询页面
- 台账一键导入页新增按时间范围清理台账数据功能,支持二次确认及逐表展示删除结果
- 同步更新 API 函数及页面组件
simonlll před 1 měsícem
rodič
revize
876b093e47

+ 75 - 0
src/api/ledger/index.js

@@ -5,6 +5,11 @@ export function importCombinedLedger(data) {
5 5
   return request({ url: '/ledger/import/combined', method: 'post', data })
6 6
 }
7 7
 
8
+// ===== 台账数据清理(按导入时间范围)=====
9
+export function clearLedgerByTimeRange(params) {
10
+  return request({ url: '/ledger/import/clear', method: 'delete', params })
11
+}
12
+
8 13
 // ===== 台账一键导入 =====
9 14
 export function importSupervisionProblem(data) {
10 15
   return request({ url: '/ledger/import/supervisionProblem', method: 'post', data })
@@ -201,3 +206,73 @@ export function delBannerLetter(ids) {
201 206
 export function exportBannerLetter(query) {
202 207
   return request({ url: '/ledger/bannerLetter/export', method: 'post', params: query, responseType: 'blob' })
203 208
 }
209
+
210
+// ===== 日常培训记录 =====
211
+export function importDailyTraining(data) {
212
+  return request({ url: '/ledger/import/dailyTraining', method: 'post', data })
213
+}
214
+export function listDailyTraining(query) {
215
+  return request({ url: '/ledger/dailyTraining/list', method: 'get', params: query })
216
+}
217
+export function getDailyTraining(id) {
218
+  return request({ url: '/ledger/dailyTraining/' + id, method: 'get' })
219
+}
220
+export function exportDailyTraining(query) {
221
+  return request({ url: '/ledger/dailyTraining/export', method: 'post', params: query, responseType: 'blob' })
222
+}
223
+
224
+// ===== 组长履职情况记录 =====
225
+export function importLeaderDuty(data) {
226
+  return request({ url: '/ledger/import/leaderDuty', method: 'post', data })
227
+}
228
+export function listLeaderDuty(query) {
229
+  return request({ url: '/ledger/leaderDuty/list', method: 'get', params: query })
230
+}
231
+export function getLeaderDuty(id) {
232
+  return request({ url: '/ledger/leaderDuty/' + id, method: 'get' })
233
+}
234
+export function exportLeaderDuty(query) {
235
+  return request({ url: '/ledger/leaderDuty/export', method: 'post', params: query, responseType: 'blob' })
236
+}
237
+
238
+// ===== 健康锐兵 =====
239
+export function importHealthSoldier(data) {
240
+  return request({ url: '/ledger/import/healthSoldier', method: 'post', data })
241
+}
242
+export function listHealthSoldier(query) {
243
+  return request({ url: '/ledger/healthSoldier/list', method: 'get', params: query })
244
+}
245
+export function getHealthSoldier(id) {
246
+  return request({ url: '/ledger/healthSoldier/' + id, method: 'get' })
247
+}
248
+export function exportHealthSoldier(query) {
249
+  return request({ url: '/ledger/healthSoldier/export', method: 'post', params: query, responseType: 'blob' })
250
+}
251
+
252
+// ===== 宿舍消防安全专项自查 =====
253
+export function importDormFireSafety(data) {
254
+  return request({ url: '/ledger/import/dormFireSafety', method: 'post', data })
255
+}
256
+export function listDormFireSafety(query) {
257
+  return request({ url: '/ledger/dormFireSafety/list', method: 'get', params: query })
258
+}
259
+export function getDormFireSafety(id) {
260
+  return request({ url: '/ledger/dormFireSafety/' + id, method: 'get' })
261
+}
262
+export function exportDormFireSafety(query) {
263
+  return request({ url: '/ledger/dormFireSafety/export', method: 'post', params: query, responseType: 'blob' })
264
+}
265
+
266
+// ===== 培训台账问题通报 =====
267
+export function importTrainingIssue(data) {
268
+  return request({ url: '/ledger/import/trainingIssue', method: 'post', data })
269
+}
270
+export function listTrainingIssue(query) {
271
+  return request({ url: '/ledger/trainingIssue/list', method: 'get', params: query })
272
+}
273
+export function getTrainingIssue(id) {
274
+  return request({ url: '/ledger/trainingIssue/' + id, method: 'get' })
275
+}
276
+export function exportTrainingIssue(query) {
277
+  return request({ url: '/ledger/trainingIssue/export', method: 'post', params: query, responseType: 'blob' })
278
+}

+ 64 - 0
src/views/ledger/dailyTraining/index.vue

@@ -0,0 +1,64 @@
1
+<template>
2
+  <div class="app-container">
3
+    <el-form :model="queryParams" ref="queryRef" :inline="true" v-show="showSearch" label-width="80px">
4
+      <el-form-item label="班组" prop="teamName">
5
+        <el-input v-model="queryParams.teamName" placeholder="请输入班组" clearable @keyup.enter="handleQuery" />
6
+      </el-form-item>
7
+      <el-form-item label="项目名称" prop="projectName">
8
+        <el-input v-model="queryParams.projectName" placeholder="请输入项目名称" clearable @keyup.enter="handleQuery" />
9
+      </el-form-item>
10
+      <el-form-item label="培训日期">
11
+        <el-date-picker v-model="dateRange" type="daterange" value-format="YYYY-MM-DD"
12
+          range-separator="-" start-placeholder="开始日期" end-placeholder="结束日期" clearable />
13
+      </el-form-item>
14
+      <el-form-item>
15
+        <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
16
+        <el-button icon="Refresh" @click="resetQuery">重置</el-button>
17
+      </el-form-item>
18
+    </el-form>
19
+    <el-row :gutter="10" class="mb8">
20
+      <el-col :span="1.5">
21
+        <el-button type="warning" plain icon="Download" @click="handleExport" v-hasPermi="['ledger:dailyTraining:export']">导出</el-button>
22
+      </el-col>
23
+      <right-toolbar v-model:showSearch="showSearch" @queryTable="getList" />
24
+    </el-row>
25
+    <el-table v-loading="loading" :data="list">
26
+      <el-table-column label="培训日期" align="center" prop="recordDate" width="110">
27
+        <template #default="{ row }">{{ parseTime(row.recordDate, '{y}-{m}-{d}') }}</template>
28
+      </el-table-column>
29
+      <el-table-column label="班组" align="center" prop="teamName" width="120" />
30
+      <el-table-column label="项目名称" align="center" prop="projectName" min-width="150" show-overflow-tooltip />
31
+      <el-table-column label="培训教员" align="center" prop="trainer" width="100" />
32
+      <el-table-column label="课时" align="center" prop="hours" width="70" />
33
+      <el-table-column label="参训人数" align="center" prop="participantCount" width="90" />
34
+      <el-table-column label="是否完成" align="center" prop="isCompleted" width="90" />
35
+      <el-table-column label="培训地点" align="center" prop="trainingLocation" width="120" show-overflow-tooltip />
36
+      <el-table-column label="培训内容" align="center" prop="trainingContent" min-width="200" show-overflow-tooltip />
37
+      <el-table-column label="备注" align="center" prop="remark" show-overflow-tooltip />
38
+    </el-table>
39
+    <pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
40
+  </div>
41
+</template>
42
+<script setup>
43
+import { ref, reactive, onMounted } from 'vue'
44
+import { listDailyTraining, exportDailyTraining } from '@/api/ledger/index'
45
+import { parseTime } from '@/utils/ruoyi'
46
+defineOptions({ name: 'LedgerDailyTraining' })
47
+const loading = ref(false), list = ref([]), total = ref(0), showSearch = ref(true)
48
+const dateRange = ref([]), queryRef = ref(null)
49
+const queryParams = reactive({ pageNum: 1, pageSize: 10, teamName: '', projectName: '' })
50
+function getList() {
51
+  loading.value = true
52
+  const p = { ...queryParams }
53
+  if (dateRange.value?.length === 2) { p['params[beginTime]'] = dateRange.value[0]; p['params[endTime]'] = dateRange.value[1] }
54
+  listDailyTraining(p).then(r => { list.value = r.rows; total.value = r.total }).finally(() => loading.value = false)
55
+}
56
+function handleQuery() { queryParams.pageNum = 1; getList() }
57
+function resetQuery() { dateRange.value = []; queryRef.value?.resetFields(); handleQuery() }
58
+function handleExport() {
59
+  const p = { ...queryParams }
60
+  if (dateRange.value?.length === 2) { p['params[beginTime]'] = dateRange.value[0]; p['params[endTime]'] = dateRange.value[1] }
61
+  exportDailyTraining(p)
62
+}
63
+onMounted(getList)
64
+</script>

+ 71 - 0
src/views/ledger/dormFireSafety/index.vue

@@ -0,0 +1,71 @@
1
+<template>
2
+  <div class="app-container">
3
+    <el-form :model="queryParams" ref="queryRef" :inline="true" v-show="showSearch" label-width="80px">
4
+      <el-form-item label="寝室位置" prop="dormLocation">
5
+        <el-input v-model="queryParams.dormLocation" placeholder="请输入寝室位置" clearable @keyup.enter="handleQuery" />
6
+      </el-form-item>
7
+      <el-form-item label="寝室号" prop="dormNo">
8
+        <el-input v-model="queryParams.dormNo" placeholder="请输入寝室号" clearable @keyup.enter="handleQuery" />
9
+      </el-form-item>
10
+      <el-form-item label="提交人" prop="submitter">
11
+        <el-input v-model="queryParams.submitter" placeholder="请输入提交人" clearable @keyup.enter="handleQuery" />
12
+      </el-form-item>
13
+      <el-form-item label="检查日期">
14
+        <el-date-picker v-model="dateRange" type="daterange" value-format="YYYY-MM-DD"
15
+          range-separator="-" start-placeholder="开始日期" end-placeholder="结束日期" clearable />
16
+      </el-form-item>
17
+      <el-form-item>
18
+        <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
19
+        <el-button icon="Refresh" @click="resetQuery">重置</el-button>
20
+      </el-form-item>
21
+    </el-form>
22
+    <el-row :gutter="10" class="mb8">
23
+      <el-col :span="1.5">
24
+        <el-button type="warning" plain icon="Download" @click="handleExport" v-hasPermi="['ledger:dormFireSafety:export']">导出</el-button>
25
+      </el-col>
26
+      <right-toolbar v-model:showSearch="showSearch" @queryTable="getList" />
27
+    </el-row>
28
+    <el-table v-loading="loading" :data="list">
29
+      <el-table-column label="检查日期" align="center" prop="checkDate" width="110">
30
+        <template #default="{ row }">{{ parseTime(row.checkDate, '{y}-{m}-{d}') }}</template>
31
+      </el-table-column>
32
+      <el-table-column label="寝室所在位置" align="center" prop="dormLocation" width="120" />
33
+      <el-table-column label="寝室号" align="center" prop="dormNo" width="90" />
34
+      <el-table-column label="自查覆盖项" align="center" prop="checkItems" min-width="200" show-overflow-tooltip />
35
+      <el-table-column label="风险类型排查情况" align="center" prop="riskStatus" min-width="150" show-overflow-tooltip />
36
+      <el-table-column label="发现具体隐患" align="center" prop="foundRisk" min-width="150" show-overflow-tooltip />
37
+      <el-table-column label="整改责任人" align="center" prop="rectifyPerson" width="110" />
38
+      <el-table-column label="整改完成时间" align="center" prop="rectifyTime" width="120">
39
+        <template #default="{ row }">{{ parseTime(row.rectifyTime, '{y}-{m}-{d}') }}</template>
40
+      </el-table-column>
41
+      <el-table-column label="提交人" align="center" prop="submitter" width="100" />
42
+      <el-table-column label="提交时间" align="center" prop="submitTime" width="160">
43
+        <template #default="{ row }">{{ parseTime(row.submitTime) }}</template>
44
+      </el-table-column>
45
+    </el-table>
46
+    <pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
47
+  </div>
48
+</template>
49
+<script setup>
50
+import { ref, reactive, onMounted } from 'vue'
51
+import { listDormFireSafety, exportDormFireSafety } from '@/api/ledger/index'
52
+import { parseTime } from '@/utils/ruoyi'
53
+defineOptions({ name: 'LedgerDormFireSafety' })
54
+const loading = ref(false), list = ref([]), total = ref(0), showSearch = ref(true)
55
+const dateRange = ref([]), queryRef = ref(null)
56
+const queryParams = reactive({ pageNum: 1, pageSize: 10, dormLocation: '', dormNo: '', submitter: '' })
57
+function getList() {
58
+  loading.value = true
59
+  const p = { ...queryParams }
60
+  if (dateRange.value?.length === 2) { p['params[beginTime]'] = dateRange.value[0]; p['params[endTime]'] = dateRange.value[1] }
61
+  listDormFireSafety(p).then(r => { list.value = r.rows; total.value = r.total }).finally(() => loading.value = false)
62
+}
63
+function handleQuery() { queryParams.pageNum = 1; getList() }
64
+function resetQuery() { dateRange.value = []; queryRef.value?.resetFields(); handleQuery() }
65
+function handleExport() {
66
+  const p = { ...queryParams }
67
+  if (dateRange.value?.length === 2) { p['params[beginTime]'] = dateRange.value[0]; p['params[endTime]'] = dateRange.value[1] }
68
+  exportDormFireSafety(p)
69
+}
70
+onMounted(getList)
71
+</script>

+ 71 - 0
src/views/ledger/healthSoldier/index.vue

@@ -0,0 +1,71 @@
1
+<template>
2
+  <div class="app-container">
3
+    <el-form :model="queryParams" ref="queryRef" :inline="true" v-show="showSearch" label-width="90px">
4
+      <el-form-item label="提交人" prop="submitter">
5
+        <el-input v-model="queryParams.submitter" placeholder="请输入提交人" clearable @keyup.enter="handleQuery" />
6
+      </el-form-item>
7
+      <el-form-item label="班组" prop="teamName">
8
+        <el-input v-model="queryParams.teamName" placeholder="请输入班组" clearable @keyup.enter="handleQuery" />
9
+      </el-form-item>
10
+      <el-form-item label="队室负责人" prop="teamLeader">
11
+        <el-input v-model="queryParams.teamLeader" placeholder="请输入队室负责人" clearable @keyup.enter="handleQuery" />
12
+      </el-form-item>
13
+      <el-form-item label="亚健康状态" prop="subHealthStatus">
14
+        <el-input v-model="queryParams.subHealthStatus" placeholder="请输入状态" clearable @keyup.enter="handleQuery" />
15
+      </el-form-item>
16
+      <el-form-item label="提交时间">
17
+        <el-date-picker v-model="dateRange" type="daterange" value-format="YYYY-MM-DD"
18
+          range-separator="-" start-placeholder="开始日期" end-placeholder="结束日期" clearable />
19
+      </el-form-item>
20
+      <el-form-item>
21
+        <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
22
+        <el-button icon="Refresh" @click="resetQuery">重置</el-button>
23
+      </el-form-item>
24
+    </el-form>
25
+    <el-row :gutter="10" class="mb8">
26
+      <el-col :span="1.5">
27
+        <el-button type="warning" plain icon="Download" @click="handleExport" v-hasPermi="['ledger:healthSoldier:export']">导出</el-button>
28
+      </el-col>
29
+      <right-toolbar v-model:showSearch="showSearch" @queryTable="getList" />
30
+    </el-row>
31
+    <el-table v-loading="loading" :data="list">
32
+      <el-table-column label="提交人" align="center" prop="submitter" width="100" />
33
+      <el-table-column label="提交时间" align="center" prop="submitTime" width="160">
34
+        <template #default="{ row }">{{ parseTime(row.submitTime) }}</template>
35
+      </el-table-column>
36
+      <el-table-column label="班组" align="center" prop="teamName" width="120" />
37
+      <el-table-column label="队室负责人" align="center" prop="teamLeader" width="110" />
38
+      <el-table-column label="亚健康人员状态" align="center" prop="subHealthStatus" width="130">
39
+        <template #default="{ row }">
40
+          <el-tag :type="row.subHealthStatus === '健康人员' ? 'success' : 'warning'">
41
+            {{ row.subHealthStatus || '未填写' }}
42
+          </el-tag>
43
+        </template>
44
+      </el-table-column>
45
+    </el-table>
46
+    <pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
47
+  </div>
48
+</template>
49
+<script setup>
50
+import { ref, reactive, onMounted } from 'vue'
51
+import { listHealthSoldier, exportHealthSoldier } from '@/api/ledger/index'
52
+import { parseTime } from '@/utils/ruoyi'
53
+defineOptions({ name: 'LedgerHealthSoldier' })
54
+const loading = ref(false), list = ref([]), total = ref(0), showSearch = ref(true)
55
+const dateRange = ref([]), queryRef = ref(null)
56
+const queryParams = reactive({ pageNum: 1, pageSize: 10, submitter: '', teamName: '', teamLeader: '', subHealthStatus: '' })
57
+function getList() {
58
+  loading.value = true
59
+  const p = { ...queryParams }
60
+  if (dateRange.value?.length === 2) { p['params[beginTime]'] = dateRange.value[0]; p['params[endTime]'] = dateRange.value[1] }
61
+  listHealthSoldier(p).then(r => { list.value = r.rows; total.value = r.total }).finally(() => loading.value = false)
62
+}
63
+function handleQuery() { queryParams.pageNum = 1; getList() }
64
+function resetQuery() { dateRange.value = []; queryRef.value?.resetFields(); handleQuery() }
65
+function handleExport() {
66
+  const p = { ...queryParams }
67
+  if (dateRange.value?.length === 2) { p['params[beginTime]'] = dateRange.value[0]; p['params[endTime]'] = dateRange.value[1] }
68
+  exportHealthSoldier(p)
69
+}
70
+onMounted(getList)
71
+</script>

+ 206 - 5
src/views/ledger/import/index.vue

@@ -12,7 +12,7 @@
12 12
           <el-icon class="combined-icon"><Files /></el-icon>
13 13
           <div>
14 14
             <div class="combined-title">一键全量导入</div>
15
-            <div class="combined-desc">上传「旅检三部"三三"数字管理平台.xlsx」,系统将自动识别15个Sheet并分别导入对应台账表</div>
15
+            <div class="combined-desc">上传「旅检三部"三三"数字管理平台.xlsx」,系统将自动识别20个Sheet并分别导入对应台账表</div>
16 16
           </div>
17 17
         </div>
18 18
         <el-upload
@@ -39,6 +39,48 @@
39 39
       </div>
40 40
     </el-card>
41 41
 
42
+    <!-- 按时间范围清理台账数据 -->
43
+    <el-card class="clear-card" shadow="never">
44
+      <div class="clear-inner">
45
+        <div class="clear-info">
46
+          <el-icon class="clear-icon"><DeleteFilled /></el-icon>
47
+          <div>
48
+            <div class="clear-title">清理台账数据</div>
49
+            <div class="clear-desc">按导入时间范围删除全部20张台账表数据,同步清除台账来源的配分事项(手动录入不受影响)</div>
50
+          </div>
51
+        </div>
52
+        <div class="clear-actions">
53
+          <el-date-picker
54
+            v-model="clearBeginDate"
55
+            type="date"
56
+            value-format="YYYY-MM-DD"
57
+            placeholder="开始日期"
58
+            style="width: 150px"
59
+          />
60
+          <span style="margin: 0 6px; color: #909399;">至</span>
61
+          <el-date-picker
62
+            v-model="clearEndDate"
63
+            type="date"
64
+            value-format="YYYY-MM-DD"
65
+            placeholder="结束日期"
66
+            :disabled-date="(d) => clearBeginDate && d < new Date(clearBeginDate)"
67
+            style="width: 150px"
68
+          />
69
+          <el-button type="danger" :loading="clearLoading" :disabled="!clearBeginDate || !clearEndDate" @click="handleClear">
70
+            <el-icon><Delete /></el-icon> 清理
71
+          </el-button>
72
+        </div>
73
+      </div>
74
+      <div v-if="clearResult" class="clear-result">
75
+        <div class="result-title">清理结果(按导入时间 {{ clearBeginDate }} ~ {{ clearEndDate }}):</div>
76
+        <div class="result-table">
77
+          <span v-for="(count, name) in clearResult" :key="name" class="result-item">
78
+            <el-tag :type="count > 0 ? 'warning' : 'info'" size="small">{{ name }}:{{ count }} 条</el-tag>
79
+          </span>
80
+        </div>
81
+      </div>
82
+    </el-card>
83
+
42 84
     <el-divider content-position="left">或按类型单独导入</el-divider>
43 85
 
44 86
     <div class="import-grid">
@@ -79,9 +121,9 @@
79 121
 
80 122
 <script setup>
81 123
 import { ref, reactive } from 'vue'
82
-import { ElMessage } from 'element-plus'
83
-import { Upload, Download, Document, DocumentChecked, Warning, Trophy, UserFilled, Ticket, DataAnalysis, Histogram, Medal, Memo, Money, Calendar, Flag, Files } from '@element-plus/icons-vue'
84
-import { importCombinedLedger } from '@/api/ledger/index'
124
+import { ElMessage, ElMessageBox } from 'element-plus'
125
+import { Upload, Download, Document, DocumentChecked, Warning, Trophy, UserFilled, Ticket, DataAnalysis, Histogram, Medal, Memo, Money, Calendar, Flag, Files, Reading, Management, FirstAidKit, House, Bell, Delete, DeleteFilled } from '@element-plus/icons-vue'
126
+import { importCombinedLedger, clearLedgerByTimeRange } from '@/api/ledger/index'
85 127
 import {
86 128
   importSupervisionProblem,
87 129
   importPatrolInspection,
@@ -94,7 +136,12 @@ import {
94 136
   importSeizureStats,
95 137
   importTerminalBonus,
96 138
   importExamScore,
97
-  importRewardApproval
139
+  importRewardApproval,
140
+  importDailyTraining,
141
+  importLeaderDuty,
142
+  importHealthSoldier,
143
+  importDormFireSafety,
144
+  importTrainingIssue
98 145
 } from '@/api/ledger/index'
99 146
 
100 147
 defineOptions({ name: 'LedgerImport' })
@@ -103,6 +150,49 @@ defineOptions({ name: 'LedgerImport' })
103 150
 const combinedLoading = ref(false)
104 151
 const combinedResult = ref(null)
105 152
 
153
+// ── 台账数据清理 ──────────────────────────────────────
154
+const clearBeginDate = ref('')
155
+const clearEndDate = ref('')
156
+const clearLoading = ref(false)
157
+const clearResult = ref(null)
158
+
159
+async function handleClear() {
160
+  if (!clearBeginDate.value || !clearEndDate.value) {
161
+    ElMessage.warning('请先选择清理时间范围')
162
+    return
163
+  }
164
+  if (clearBeginDate.value > clearEndDate.value) {
165
+    ElMessage.warning('开始日期不能晚于结束日期')
166
+    return
167
+  }
168
+  const beginTime = clearBeginDate.value
169
+  const endTime = clearEndDate.value
170
+  try {
171
+    await ElMessageBox.confirm(
172
+      `确认删除导入时间在 ${beginTime} ~ ${endTime} 之间的全部台账数据?\n此操作不可恢复,请谨慎操作!`,
173
+      '危险操作确认',
174
+      { type: 'warning', confirmButtonText: '确认清理', cancelButtonText: '取消', confirmButtonClass: 'el-button--danger' }
175
+    )
176
+  } catch {
177
+    return
178
+  }
179
+  clearLoading.value = true
180
+  clearResult.value = null
181
+  try {
182
+    const res = await clearLedgerByTimeRange({ beginTime, endTime })
183
+    if (res.code === 200) {
184
+      clearResult.value = res.data || {}
185
+      ElMessage.success(res.msg || '清理完成')
186
+    } else {
187
+      ElMessage.error(res.msg || '清理失败')
188
+    }
189
+  } catch (e) {
190
+    ElMessage.error('清理失败,请查看后端日志')
191
+  } finally {
192
+    clearLoading.value = false
193
+  }
194
+}
195
+
106 196
 async function handleCombinedChange(uploadFile) {
107 197
   const file = uploadFile.raw
108 198
   if (!file) return
@@ -238,6 +328,51 @@ const importItems = reactive([
238 328
     api: importRewardApproval,
239 329
     loading: false,
240 330
     lastResult: null
331
+  },
332
+  {
333
+    key: 'dailyTraining',
334
+    title: '日常培训记录',
335
+    desc: '月度培训任务及完成情况记录',
336
+    icon: 'Reading',
337
+    api: importDailyTraining,
338
+    loading: false,
339
+    lastResult: null
340
+  },
341
+  {
342
+    key: 'leaderDuty',
343
+    title: '组长履职情况记录',
344
+    desc: '队室组长本班点评及问题处置',
345
+    icon: 'Management',
346
+    api: importLeaderDuty,
347
+    loading: false,
348
+    lastResult: null
349
+  },
350
+  {
351
+    key: 'healthSoldier',
352
+    title: '健康锐兵',
353
+    desc: '员工身心健康状况台账记录',
354
+    icon: 'FirstAidKit',
355
+    api: importHealthSoldier,
356
+    loading: false,
357
+    lastResult: null
358
+  },
359
+  {
360
+    key: 'dormFireSafety',
361
+    title: '宿舍消防安全专项自查',
362
+    desc: '宿舍消防隐患自查情况记录',
363
+    icon: 'House',
364
+    api: importDormFireSafety,
365
+    loading: false,
366
+    lastResult: null
367
+  },
368
+  {
369
+    key: 'trainingIssue',
370
+    title: '培训台账问题通报',
371
+    desc: '培训台账发现问题及整改通报',
372
+    icon: 'Bell',
373
+    api: importTrainingIssue,
374
+    loading: false,
375
+    lastResult: null
241 376
   }
242 377
 ])
243 378
 
@@ -373,6 +508,72 @@ function downloadTemplate(item) {
373 508
   }
374 509
 }
375 510
 
511
+.clear-card {
512
+  margin-bottom: 16px;
513
+  border: 1px solid #fde2e2;
514
+  background: #fff8f8;
515
+
516
+  .clear-inner {
517
+    display: flex;
518
+    align-items: center;
519
+    justify-content: space-between;
520
+    gap: 16px;
521
+    flex-wrap: wrap;
522
+  }
523
+
524
+  .clear-info {
525
+    display: flex;
526
+    align-items: flex-start;
527
+    gap: 12px;
528
+  }
529
+
530
+  .clear-icon {
531
+    font-size: 36px;
532
+    color: #f56c6c;
533
+    flex-shrink: 0;
534
+    margin-top: 2px;
535
+  }
536
+
537
+  .clear-title {
538
+    font-size: 17px;
539
+    font-weight: 600;
540
+    color: #f56c6c;
541
+    margin-bottom: 4px;
542
+  }
543
+
544
+  .clear-desc {
545
+    font-size: 13px;
546
+    color: #909399;
547
+    max-width: 520px;
548
+  }
549
+
550
+  .clear-actions {
551
+    display: flex;
552
+    gap: 10px;
553
+    align-items: center;
554
+    flex-wrap: wrap;
555
+  }
556
+
557
+  .clear-result {
558
+    margin-top: 14px;
559
+    padding-top: 14px;
560
+    border-top: 1px dashed #fde2e2;
561
+
562
+    .result-title {
563
+      font-size: 13px;
564
+      color: #606266;
565
+      margin-bottom: 8px;
566
+      font-weight: 500;
567
+    }
568
+
569
+    .result-table {
570
+      display: flex;
571
+      flex-wrap: wrap;
572
+      gap: 6px;
573
+    }
574
+  }
575
+}
576
+
376 577
 .import-grid {
377 578
   display: grid;
378 579
   grid-template-columns: repeat(2, 1fr);

+ 51 - 0
src/views/ledger/leaderDuty/index.vue

@@ -0,0 +1,51 @@
1
+<template>
2
+  <div class="app-container">
3
+    <el-form :model="queryParams" ref="queryRef" :inline="true" v-show="showSearch" label-width="80px">
4
+      <el-form-item label="提交人" prop="submitter">
5
+        <el-input v-model="queryParams.submitter" placeholder="请输入提交人" clearable @keyup.enter="handleQuery" />
6
+      </el-form-item>
7
+      <el-form-item label="队室负责人" prop="teamLeader">
8
+        <el-input v-model="queryParams.teamLeader" placeholder="请输入队室负责人" clearable @keyup.enter="handleQuery" />
9
+      </el-form-item>
10
+      <el-form-item>
11
+        <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
12
+        <el-button icon="Refresh" @click="resetQuery">重置</el-button>
13
+      </el-form-item>
14
+    </el-form>
15
+    <el-row :gutter="10" class="mb8">
16
+      <el-col :span="1.5">
17
+        <el-button type="warning" plain icon="Download" @click="handleExport" v-hasPermi="['ledger:leaderDuty:export']">导出</el-button>
18
+      </el-col>
19
+      <right-toolbar v-model:showSearch="showSearch" @queryTable="getList" />
20
+    </el-row>
21
+    <el-table v-loading="loading" :data="list">
22
+      <el-table-column label="提交人" align="center" prop="submitter" width="100" />
23
+      <el-table-column label="队室负责人" align="center" prop="teamLeader" width="110" />
24
+      <el-table-column label="队室质控员" align="center" prop="qualityOfficer" width="110" />
25
+      <el-table-column label="工作提示" align="center" prop="workTip" min-width="150" show-overflow-tooltip />
26
+      <el-table-column label="问题处置及整改措施" align="center" prop="problemHandling" min-width="200" show-overflow-tooltip />
27
+      <el-table-column label="本班点评" align="center" prop="classComment" min-width="200" show-overflow-tooltip />
28
+      <el-table-column label="导入时间" align="center" prop="createTime" width="160">
29
+        <template #default="{ row }">{{ parseTime(row.createTime) }}</template>
30
+      </el-table-column>
31
+    </el-table>
32
+    <pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
33
+  </div>
34
+</template>
35
+<script setup>
36
+import { ref, reactive, onMounted } from 'vue'
37
+import { listLeaderDuty, exportLeaderDuty } from '@/api/ledger/index'
38
+import { parseTime } from '@/utils/ruoyi'
39
+defineOptions({ name: 'LedgerLeaderDuty' })
40
+const loading = ref(false), list = ref([]), total = ref(0), showSearch = ref(true)
41
+const queryRef = ref(null)
42
+const queryParams = reactive({ pageNum: 1, pageSize: 10, submitter: '', teamLeader: '' })
43
+function getList() {
44
+  loading.value = true
45
+  listLeaderDuty({ ...queryParams }).then(r => { list.value = r.rows; total.value = r.total }).finally(() => loading.value = false)
46
+}
47
+function handleQuery() { queryParams.pageNum = 1; getList() }
48
+function resetQuery() { queryRef.value?.resetFields(); handleQuery() }
49
+function handleExport() { exportLeaderDuty({ ...queryParams }) }
50
+onMounted(getList)
51
+</script>

+ 74 - 0
src/views/ledger/trainingIssue/index.vue

@@ -0,0 +1,74 @@
1
+<template>
2
+  <div class="app-container">
3
+    <el-form :model="queryParams" ref="queryRef" :inline="true" v-show="showSearch" label-width="80px">
4
+      <el-form-item label="班组" prop="teamName">
5
+        <el-input v-model="queryParams.teamName" placeholder="请输入班组" clearable @keyup.enter="handleQuery" />
6
+      </el-form-item>
7
+      <el-form-item label="处理人" prop="handler">
8
+        <el-input v-model="queryParams.handler" placeholder="请输入处理人" clearable @keyup.enter="handleQuery" />
9
+      </el-form-item>
10
+      <el-form-item label="整改状态" prop="isRectified">
11
+        <el-select v-model="queryParams.isRectified" placeholder="请选择" clearable style="width:130px">
12
+          <el-option label="整改完毕" value="整改完毕" />
13
+          <el-option label="整改中" value="整改中" />
14
+          <el-option label="未整改" value="未整改" />
15
+        </el-select>
16
+      </el-form-item>
17
+      <el-form-item label="台账日期">
18
+        <el-date-picker v-model="dateRange" type="daterange" value-format="YYYY-MM-DD"
19
+          range-separator="-" start-placeholder="开始日期" end-placeholder="结束日期" clearable />
20
+      </el-form-item>
21
+      <el-form-item>
22
+        <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
23
+        <el-button icon="Refresh" @click="resetQuery">重置</el-button>
24
+      </el-form-item>
25
+    </el-form>
26
+    <el-row :gutter="10" class="mb8">
27
+      <el-col :span="1.5">
28
+        <el-button type="warning" plain icon="Download" @click="handleExport" v-hasPermi="['ledger:trainingIssue:export']">导出</el-button>
29
+      </el-col>
30
+      <right-toolbar v-model:showSearch="showSearch" @queryTable="getList" />
31
+    </el-row>
32
+    <el-table v-loading="loading" :data="list">
33
+      <el-table-column label="问题台账日期" align="center" prop="recordDate" width="120">
34
+        <template #default="{ row }">{{ parseTime(row.recordDate, '{y}-{m}-{d}') }}</template>
35
+      </el-table-column>
36
+      <el-table-column label="班组" align="center" prop="teamName" width="120" />
37
+      <el-table-column label="处理人" align="center" prop="handler" width="100" />
38
+      <el-table-column label="问题台账内容" align="center" prop="ledgerContent" min-width="200" show-overflow-tooltip />
39
+      <el-table-column label="具体问题" align="center" prop="specificProblem" min-width="150" show-overflow-tooltip />
40
+      <el-table-column label="二次复核问题" align="center" prop="recheckProblem" min-width="150" show-overflow-tooltip />
41
+      <el-table-column label="是否完成整改" align="center" prop="isRectified" width="120">
42
+        <template #default="{ row }">
43
+          <el-tag :type="row.isRectified === '整改完毕' ? 'success' : row.isRectified === '整改中' ? 'warning' : 'danger'">
44
+            {{ row.isRectified || '-' }}
45
+          </el-tag>
46
+        </template>
47
+      </el-table-column>
48
+    </el-table>
49
+    <pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
50
+  </div>
51
+</template>
52
+<script setup>
53
+import { ref, reactive, onMounted } from 'vue'
54
+import { listTrainingIssue, exportTrainingIssue } from '@/api/ledger/index'
55
+import { parseTime } from '@/utils/ruoyi'
56
+defineOptions({ name: 'LedgerTrainingIssue' })
57
+const loading = ref(false), list = ref([]), total = ref(0), showSearch = ref(true)
58
+const dateRange = ref([]), queryRef = ref(null)
59
+const queryParams = reactive({ pageNum: 1, pageSize: 10, teamName: '', handler: '', isRectified: '' })
60
+function getList() {
61
+  loading.value = true
62
+  const p = { ...queryParams }
63
+  if (dateRange.value?.length === 2) { p['params[beginTime]'] = dateRange.value[0]; p['params[endTime]'] = dateRange.value[1] }
64
+  listTrainingIssue(p).then(r => { list.value = r.rows; total.value = r.total }).finally(() => loading.value = false)
65
+}
66
+function handleQuery() { queryParams.pageNum = 1; getList() }
67
+function resetQuery() { dateRange.value = []; queryRef.value?.resetFields(); handleQuery() }
68
+function handleExport() {
69
+  const p = { ...queryParams }
70
+  if (dateRange.value?.length === 2) { p['params[beginTime]'] = dateRange.value[0]; p['params[endTime]'] = dateRange.value[1] }
71
+  exportTrainingIssue(p)
72
+}
73
+onMounted(getList)
74
+</script>