Browse Source

新增教育学习目录下的培训学习

余思翰 1 month ago
parent
commit
3d023f39b5
2 changed files with 538 additions and 1 deletions
  1. 1
    1
      oa-ui/src/views/oa/study/record.vue
  2. 537
    0
      oa-ui/src/views/oa/study/train.vue

+ 1
- 1
oa-ui/src/views/oa/study/record.vue View File

@@ -2,7 +2,7 @@
2 2
  * @Author: ysh
3 3
  * @Date: 2025-03-12 10:06:03
4 4
  * @LastEditors: Please set LastEditors
5
- * @LastEditTime: 2025-03-17 09:28:35
5
+ * @LastEditTime: 2025-04-22 13:45:22
6 6
 -->
7 7
 <template>
8 8
   <div class="app-container">

+ 537
- 0
oa-ui/src/views/oa/study/train.vue View File

@@ -0,0 +1,537 @@
1
+<template>
2
+  <div class="app-container">
3
+    <el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="68px">
4
+      <el-form-item label="资料名称" prop="title">
5
+        <el-input v-model="queryParams.title" placeholder="请输入资料名称" clearable @keyup.enter.native="handleQuery" />
6
+      </el-form-item>
7
+      <el-form-item label="类型" prop="title">
8
+        <el-select v-model="queryParams.type" @change="handleQuery" clearable>
9
+          <el-option label="视频" value="视频"></el-option>
10
+          <el-option label="文档" value="文档"></el-option>
11
+        </el-select>
12
+      </el-form-item>
13
+      <el-form-item label="姓名" prop="userId">
14
+        <el-select v-model="queryParams.userId" clearable filterable placeholder="请输入姓名" style="width: 160px"
15
+          @change="handleQuery">
16
+          <el-option v-for="item in $store.state.user.userList" :key="item.userId" :label="item.nickName"
17
+            :value="item.userId">
18
+          </el-option>
19
+        </el-select>
20
+      </el-form-item>
21
+      <el-form-item label="学习年份" prop="lastTime">
22
+        <el-date-picker clearable v-model="queryParams.lastTime" type="year" value-format="yyyy-MM-dd"
23
+          placeholder="请选择学习年份" @change="handleQuery">
24
+        </el-date-picker>
25
+      </el-form-item>
26
+      <el-form-item>
27
+        <el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
28
+        <el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
29
+      </el-form-item>
30
+    </el-form>
31
+    <el-row :gutter="10" class="mb8">
32
+      <el-col :span="1.5">
33
+        <el-button type="primary" plain icon="el-icon-plus" size="mini" @click="handleAdd"
34
+          v-hasPermi="['oa:train:add']">新增</el-button>
35
+        <el-button type="warning" plain icon="el-icon-download" size="mini" @click="handleExport"
36
+          v-hasPermi="['oa:train:export']">导出</el-button>
37
+      </el-col>
38
+      <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
39
+    </el-row>
40
+    <el-table v-loading="loading" :data="studyList">
41
+      <el-table-column type="selection" width="55" align="center" />
42
+      <el-table-column type="index" label="序号" width="55" align="center" />
43
+      <el-table-column align="center" label="资料名称" prop="resource.title" />
44
+      <el-table-column align="center" label="类型" prop="resource.type"></el-table-column>
45
+      <el-table-column align="center" label="学习进度" prop="lastPoint">
46
+        <template slot-scope="scope">
47
+          <el-progress :text-inside="true" :stroke-width="26" :status="formatStatus(scope.row.lastPoint)"
48
+            :percentage="Number(scope.row.lastPoint)" text-color="#fff"></el-progress>
49
+        </template>
50
+      </el-table-column>
51
+      <el-table-column label="姓名" align="center" prop="user.nickName" />
52
+      <el-table-column label="上次学习时间" align="center" prop="lastTime" width="180">
53
+        <template slot-scope="scope">
54
+          <span>{{ parseTime(scope.row.lastTime, '{y}-{m}-{d}') }}</span>
55
+        </template>
56
+      </el-table-column>
57
+      <el-table-column label="已获学时" align="center" prop="getHours" />
58
+      <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
59
+        <template slot-scope="scope">
60
+          <el-button size="mini" type="text" icon="el-icon-delete" @click="handleDelete(scope.row)"
61
+            v-hasPermi="['oa:train:remove']">删除</el-button>
62
+        </template>
63
+      </el-table-column>
64
+    </el-table>
65
+    <pagination v-show="total > 0" :total="total" :page.sync="queryParams.pageNum" :limit.sync="queryParams.pageSize"
66
+      @pagination="getList" />
67
+
68
+    <!-- 添加或修改培训学习记录对话框 -->
69
+    <el-dialog :title="title" :visible.sync="open" width="500px" append-to-body>
70
+      <el-form ref="form" :model="form" :rules="rules" label-width="80px">
71
+        <el-form-item label="培训资料" prop="resourceId">
72
+          <el-button type="primary" size="mini" @click="showResourceDialog">选择培训资料</el-button>
73
+          <span v-if="selectedResource" class="ml10">{{ selectedResource.title }}</span>
74
+        </el-form-item>
75
+        <el-form-item label="参培成员" prop="userIds">
76
+          <el-button type="primary" size="mini" @click="showUserDialog" :disabled="!form.resourceId">选择参培成员</el-button>
77
+          <div v-if="selectedUsers.length > 0" class="selected-users mt10">
78
+            <el-tag v-for="user in selectedUsers" :key="user.userId" closable @close="removeUser(user.userId)"
79
+              class="mr10 mb10">
80
+              {{ user.nickName }}
81
+            </el-tag>
82
+          </div>
83
+        </el-form-item>
84
+        <el-form-item label="学习时间" prop="lastTime">
85
+          <el-date-picker v-model="form.lastTime" type="date" placeholder="选择学习时间" value-format="yyyy-MM-dd">
86
+          </el-date-picker>
87
+        </el-form-item>
88
+      </el-form>
89
+      <div slot="footer" class="dialog-footer">
90
+        <el-button type="primary" @click="submitForm">确 定</el-button>
91
+        <el-button @click="cancel">取 消</el-button>
92
+      </div>
93
+    </el-dialog>
94
+
95
+    <!-- 培训资料选择对话框 -->
96
+    <el-dialog title="选择培训资料" :visible.sync="resourceDialogVisible" width="800px" append-to-body>
97
+      <el-form :inline="true" :model="resourceQueryParams" class="demo-form-inline">
98
+        <el-form-item label="资料名称">
99
+          <el-input v-model="resourceQueryParams.title" placeholder="请输入资料名称" clearable
100
+            @keyup.enter.native="handleResourceQuery" />
101
+        </el-form-item>
102
+        <el-form-item>
103
+          <el-button type="primary" @click="handleResourceQuery">查询</el-button>
104
+          <el-button @click="resetResourceQuery">重置</el-button>
105
+        </el-form-item>
106
+      </el-form>
107
+      <el-table v-loading="resourceLoading" :data="resourceList" @row-click="handleResourceSelect">
108
+        <el-table-column type="index" label="序号" width="55" align="center" />
109
+        <el-table-column label="资料名称" align="center" prop="title" />
110
+        <el-table-column label="类型" align="center" prop="type" />
111
+        <el-table-column label="学时" align="center" prop="hours" />
112
+      </el-table>
113
+      <pagination v-show="resourceTotal > 0" :total="resourceTotal" :page.sync="resourceQueryParams.pageNum"
114
+        :limit.sync="resourceQueryParams.pageSize" @pagination="getResourceList" />
115
+    </el-dialog>
116
+
117
+    <!-- 参培成员选择对话框 -->
118
+    <el-dialog title="选择参培成员" :visible.sync="userDialogVisible" width="800px" append-to-body>
119
+      <el-table v-loading="userLoading" :data="userList" @selection-change="handleUserSelectionChange" ref="userTable">
120
+        <el-table-column type="selection" width="55" :selectable="checkUserSelectable" reserve-selection />
121
+        <el-table-column type="index" label="序号" width="55" align="center" />
122
+        <el-table-column label="姓名" align="center" prop="nickName" />
123
+        <el-table-column label="部门" align="center" prop="dept.deptName" />
124
+      </el-table>
125
+      <pagination v-show="userTotal > 0" :total="userTotal" :page.sync="userQueryParams.pageNum"
126
+        :limit.sync="userQueryParams.pageSize" @pagination="getUserList" />
127
+      <div slot="footer" class="dialog-footer">
128
+        <el-button type="primary" @click="confirmUserSelection">确 定</el-button>
129
+        <el-button @click="userDialogVisible = false">取 消</el-button>
130
+      </div>
131
+    </el-dialog>
132
+  </div>
133
+</template>
134
+
135
+<script>
136
+import { listStudy, getStudy, delStudy, addStudy, updateStudy } from "@/api/oa/study/myStudy";
137
+import { listResource } from "@/api/oa/study/resource";
138
+import { listUser } from "@/api/system/user";
139
+
140
+export default {
141
+  name: "Train",
142
+  data() {
143
+    return {
144
+      // 遮罩层
145
+      loading: true,
146
+      // 显示搜索条件
147
+      showSearch: true,
148
+      // 总条数
149
+      total: 0,
150
+      // cmc学习记录表格数据
151
+      studyList: [],
152
+      // 查询参数
153
+      queryParams: {
154
+        pageNum: 1,
155
+        pageSize: 10,
156
+        resourceId: null,
157
+        userId: null,
158
+        lastPoint: null,
159
+        lastTime: null,
160
+        getHours: null
161
+      },
162
+      // 表单参数
163
+      form: {
164
+        resourceId: null,
165
+        userIds: [],
166
+        resourceHours: 0,
167
+        lastTime: null
168
+      },
169
+      // 表单校验
170
+      rules: {
171
+        resourceId: [
172
+          { required: true, message: "培训资料不能为空", trigger: "change" }
173
+        ],
174
+        userIds: [
175
+          { required: true, message: "参培成员不能为空", trigger: "change" }
176
+        ],
177
+        lastTime: [
178
+          { required: true, message: "学习时间不能为空", trigger: "change" }
179
+        ]
180
+      },
181
+      // 弹出层标题
182
+      title: "",
183
+      // 是否显示弹出层
184
+      open: false,
185
+      // 培训资料选项
186
+      resourceOptions: [],
187
+      // 用户选项
188
+      userOptions: [],
189
+      // 已学习记录
190
+      existingRecords: [],
191
+      // 培训资料选择对话框
192
+      resourceDialogVisible: false,
193
+      resourceLoading: false,
194
+      resourceList: [],
195
+      resourceTotal: 0,
196
+      resourceQueryParams: {
197
+        pageNum: 1,
198
+        pageSize: 10,
199
+        title: undefined
200
+      },
201
+      selectedResource: null,
202
+      // 参培成员选择对话框
203
+      userDialogVisible: false,
204
+      userLoading: false,
205
+      userList: [],
206
+      userTotal: 0,
207
+      userQueryParams: {
208
+        pageNum: 1,
209
+        pageSize: 10
210
+      },
211
+      selectedUsers: [],
212
+      existingUserIds: [],
213
+      tempSelectedUsers: [], // 临时存储选中的用户
214
+    };
215
+  },
216
+  created() {
217
+    this.getList();
218
+    this.getResourceOptions();
219
+  },
220
+  methods: {
221
+    /** 查询cmc学习记录列表 */
222
+    getList() {
223
+      this.loading = true;
224
+      listStudy(this.queryParams).then(response => {
225
+        this.studyList = response.rows;
226
+        this.total = response.total;
227
+        this.loading = false;
228
+      });
229
+    },
230
+    // 表单重置
231
+    reset() {
232
+      this.form = {
233
+        resourceId: null,
234
+        userIds: [],
235
+        resourceHours: 0,
236
+        lastTime: null
237
+      };
238
+      this.selectedResource = null;
239
+      this.selectedUsers = [];
240
+      this.existingUserIds = [];
241
+      this.tempSelectedUsers = [];
242
+      this.resetForm("form");
243
+    },
244
+    /** 搜索按钮操作 */
245
+    handleQuery() {
246
+      this.queryParams.pageNum = 1;
247
+      this.getList();
248
+    },
249
+    /** 重置按钮操作 */
250
+    resetQuery() {
251
+      this.resetForm("queryForm");
252
+      this.handleQuery();
253
+    },
254
+    /** 导出按钮操作 */
255
+    handleExport() {
256
+      this.download('oa/study/export', {
257
+        ...this.queryParams
258
+      }, `study_${new Date().getTime()}.xlsx`)
259
+    },
260
+    formatStatus(row) {
261
+      if (!row) {
262
+        row = 0
263
+        return 'exception'
264
+      }
265
+      if (row <= 20) {
266
+        return 'exception'
267
+      } else if (row > 20 && row <= 50) {
268
+        return 'warning'
269
+      } else if (row > 50 && row <= 80) {
270
+        return null
271
+      } else {
272
+        return 'success'
273
+      }
274
+    },
275
+    /** 获取培训资料选项 */
276
+    getResourceOptions() {
277
+      listResource().then(response => {
278
+        this.resourceOptions = response.rows;
279
+      });
280
+    },
281
+    /** 处理培训资料选择变化 */
282
+    handleResourceChange(value) {
283
+      this.form.userIds = []; // 清空已选择的用户
284
+      this.form.resourceId = value;
285
+      // 获取选中资源的学时
286
+      const selectedResource = this.resourceOptions.find(item => item.resourceId === value);
287
+      if (selectedResource) {
288
+        this.form.resourceHours = selectedResource.hours || 0;
289
+      }
290
+      this.getExistingRecords(value);
291
+    },
292
+    /** 获取已学习记录 */
293
+    getExistingRecords(resourceId) {
294
+      if (!resourceId) return;
295
+      // 设置较大的pageSize以获取所有记录
296
+      listStudy({
297
+        resourceId,
298
+        pageSize: 9999, // 设置一个足够大的值以获取所有记录
299
+        pageNum: 1
300
+      }).then(response => {
301
+        this.existingRecords = response.rows;
302
+        this.updateUserOptions();
303
+      });
304
+    },
305
+    /** 更新用户选项 */
306
+    updateUserOptions() {
307
+      // 获取所有已学习该资源的用户ID
308
+      const existingUserIds = this.existingRecords.map(record => record.userId);
309
+
310
+      // 更新用户选项,禁用已存在的用户
311
+      this.userOptions = this.$store.state.user.userList.map(user => ({
312
+        ...user,
313
+        disabled: existingUserIds.includes(user.userId)
314
+      }));
315
+
316
+      // 过滤掉已选择的已存在用户
317
+      this.form.userIds = this.form.userIds.filter(userId =>
318
+        !existingUserIds.includes(userId)
319
+      );
320
+    },
321
+    /** 处理用户选择变化 */
322
+    handleUserChange(value) {
323
+      // 获取所有已学习该资源的用户ID
324
+      const existingUserIds = this.existingRecords.map(record => record.userId);
325
+
326
+      // 过滤掉已存在的用户
327
+      const validUserIds = value.filter(userId => !existingUserIds.includes(userId));
328
+
329
+      // 如果过滤后的数组与原数组不同,说明有无效选择
330
+      if (validUserIds.length !== value.length) {
331
+        this.$message.warning('已存在的学习记录将被忽略');
332
+        this.form.userIds = validUserIds;
333
+      }
334
+    },
335
+    /** 取消按钮 */
336
+    cancel() {
337
+      this.open = false;
338
+      this.reset();
339
+    },
340
+    /** 新增按钮操作 */
341
+    handleAdd() {
342
+      this.reset();
343
+      this.open = true;
344
+      this.title = "添加培训学习记录";
345
+    },
346
+    /** 提交按钮 */
347
+    submitForm() {
348
+      this.$refs["form"].validate(valid => {
349
+        if (valid) {
350
+          const promises = this.form.userIds.map(userId =>
351
+            addStudy({
352
+              resourceId: this.form.resourceId,
353
+              userId,
354
+              lastPoint: 100, // 设置学习进度为100%
355
+              getHours: this.form.resourceHours, // 使用资源的学时
356
+              lastTime: this.form.lastTime // 使用选择的学习时间
357
+            })
358
+          );
359
+
360
+          Promise.all(promises)
361
+            .then(() => {
362
+              this.$modal.msgSuccess("新增成功");
363
+              this.open = false;
364
+              this.getList();
365
+            })
366
+            .catch(() => {
367
+              this.$modal.msgError("新增失败");
368
+            });
369
+        }
370
+      });
371
+    },
372
+    /** 删除按钮操作 */
373
+    handleDelete(row) {
374
+      const studyIds = row.studyId;
375
+      this.$modal.confirm('是否确认删除该培训学习记录?').then(() => {
376
+        return delStudy(studyIds);
377
+      }).then(() => {
378
+        this.getList();
379
+        this.$modal.msgSuccess("删除成功");
380
+      }).catch(() => { });
381
+    },
382
+    /** 显示培训资料选择对话框 */
383
+    showResourceDialog() {
384
+      this.resourceDialogVisible = true;
385
+      this.getResourceList();
386
+    },
387
+    /** 查询培训资料列表 */
388
+    getResourceList() {
389
+      this.resourceLoading = true;
390
+      listResource(this.resourceQueryParams).then(response => {
391
+        this.resourceList = response.rows;
392
+        this.resourceTotal = response.total;
393
+        this.resourceLoading = false;
394
+      });
395
+    },
396
+    /** 培训资料查询按钮操作 */
397
+    handleResourceQuery() {
398
+      this.resourceQueryParams.pageNum = 1;
399
+      this.getResourceList();
400
+    },
401
+    /** 重置培训资料查询 */
402
+    resetResourceQuery() {
403
+      this.resourceQueryParams = {
404
+        pageNum: 1,
405
+        pageSize: 10,
406
+        title: undefined
407
+      };
408
+      this.handleResourceQuery();
409
+    },
410
+    /** 选择培训资料 */
411
+    handleResourceSelect(row) {
412
+      this.selectedResource = row;
413
+      this.form.resourceId = row.resourceId;
414
+      this.form.resourceHours = row.hours || 0;
415
+      this.resourceDialogVisible = false;
416
+      this.getExistingRecords(row.resourceId);
417
+    },
418
+    /** 显示参培成员选择对话框 */
419
+    showUserDialog() {
420
+      this.userDialogVisible = true;
421
+      this.tempSelectedUsers = [...this.selectedUsers]; // 初始化临时选中用户
422
+      this.getUserList();
423
+      // 在下一个tick中设置选中状态
424
+      this.$nextTick(() => {
425
+        if (this.$refs.userTable) {
426
+          this.userList.forEach(row => {
427
+            if (this.tempSelectedUsers.some(user => user.userId === row.userId)) {
428
+              this.$refs.userTable.toggleRowSelection(row, true);
429
+            }
430
+          });
431
+        }
432
+      });
433
+    },
434
+    /** 查询用户列表 */
435
+    getUserList() {
436
+      this.userLoading = true;
437
+      listUser(this.userQueryParams).then(response => {
438
+        this.userList = response.rows;
439
+        this.userTotal = response.total;
440
+        this.userLoading = false;
441
+        // 在数据加载后恢复选中状态
442
+        this.$nextTick(() => {
443
+          if (this.$refs.userTable) {
444
+            this.userList.forEach(row => {
445
+              if (this.tempSelectedUsers.some(user => user.userId === row.userId)) {
446
+                this.$refs.userTable.toggleRowSelection(row, true);
447
+              }
448
+            });
449
+          }
450
+        });
451
+      });
452
+    },
453
+    /** 检查用户是否可选 */
454
+    checkUserSelectable(row) {
455
+      return !this.existingUserIds.includes(row.userId);
456
+    },
457
+    /** 用户选择变化 */
458
+    handleUserSelectionChange(selection) {
459
+      // 更新临时选中用户
460
+      this.tempSelectedUsers = selection;
461
+    },
462
+    /** 确认用户选择 */
463
+    confirmUserSelection() {
464
+      // 去重处理
465
+      const uniqueUsers = this.tempSelectedUsers.reduce((acc, current) => {
466
+        const x = acc.find(item => item.userId === current.userId);
467
+        if (!x) {
468
+          return acc.concat([current]);
469
+        } else {
470
+          return acc;
471
+        }
472
+      }, []);
473
+      
474
+      this.selectedUsers = uniqueUsers;
475
+      this.form.userIds = this.selectedUsers.map(user => user.userId);
476
+      this.userDialogVisible = false;
477
+    },
478
+    /** 移除已选用户 */
479
+    removeUser(userId) {
480
+      this.selectedUsers = this.selectedUsers.filter(user => user.userId !== userId);
481
+      this.form.userIds = this.selectedUsers.map(user => user.userId);
482
+      // 同时更新临时选中用户
483
+      this.tempSelectedUsers = this.tempSelectedUsers.filter(user => user.userId !== userId);
484
+      // 更新表格选中状态
485
+      this.$nextTick(() => {
486
+        if (this.$refs.userTable) {
487
+          this.userList.forEach(row => {
488
+            if (row.userId === userId) {
489
+              this.$refs.userTable.toggleRowSelection(row, false);
490
+            }
491
+          });
492
+        }
493
+      });
494
+    },
495
+    /** 获取已学习记录 */
496
+    getExistingRecords(resourceId) {
497
+      if (!resourceId) return;
498
+      listStudy({
499
+        resourceId,
500
+        pageSize: 9999,
501
+        pageNum: 1
502
+      }).then(response => {
503
+        this.existingUserIds = response.rows.map(record => record.userId);
504
+        this.selectedUsers = this.selectedUsers.filter(user => !this.existingUserIds.includes(user.userId));
505
+        this.form.userIds = this.selectedUsers.map(user => user.userId);
506
+      });
507
+    }
508
+  }
509
+};
510
+</script>
511
+
512
+<style lang="scss" scoped>
513
+.mb8 {
514
+  margin-bottom: 8px;
515
+}
516
+
517
+.ml10 {
518
+  margin-left: 10px;
519
+}
520
+
521
+.mt10 {
522
+  margin-top: 10px;
523
+}
524
+
525
+.mr10 {
526
+  margin-right: 10px;
527
+}
528
+
529
+.mb10 {
530
+  margin-bottom: 10px;
531
+}
532
+
533
+.selected-users {
534
+  display: flex;
535
+  flex-wrap: wrap;
536
+}
537
+</style>

Loading…
Cancel
Save