Преглед изворни кода

feat: 添加培训人员导入对话框,支持文件上传和模板下载功能

xieyong пре 1 недеља
родитељ
комит
0824587fc9

+ 7 - 1
src/views/safetyTraining/onlineTrainingPlans/person-import-dialog.vue → src/components/upload/person-import-dialog.vue

@@ -1,6 +1,6 @@
 <template>
   <el-dialog
-    title="导入培训人员"
+    :title="title"
     :visible.sync="visible"
     width="760px"
     append-to-body
@@ -117,6 +117,12 @@
   }
 
   export default {
+    props: {
+      title: {
+        type: String,
+        default: '导入培训人员'
+      }
+    },
     data() {
       return {
         visible: false,

+ 77 - 25
src/views/safetyTraining/offlineTrainingRecords/add.vue

@@ -151,7 +151,10 @@
           </el-col> -->
           <el-col :span="8">
             <el-form-item label="是否扫码签到:" prop="needSignIn">
-              <el-radio-group v-model="form.needSignIn" :disabled="dialogType != 'add'">
+              <el-radio-group
+                v-model="form.needSignIn"
+                :disabled="dialogType != 'add'"
+              >
                 <el-radio :label="1">是</el-radio>
                 <el-radio :label="0">否</el-radio>
               </el-radio-group>
@@ -219,7 +222,10 @@
           </el-col>
           <el-col :span="8">
             <el-form-item label="实到人数:" prop="actualCount">
-              <el-input v-model="form.actualCount" placeholder="请输入实到人数" />
+              <el-input
+                v-model="form.actualCount"
+                placeholder="请输入实到人数"
+              />
             </el-form-item>
           </el-col>
         </el-row>
@@ -260,7 +266,10 @@
         <!-- 结果附件 -->
         <el-row :gutter="20">
           <el-col :span="12">
-            <el-form-item label="培训结果附件:" prop="trainingResultAttachment">
+            <el-form-item
+              label="培训结果附件:"
+              prop="trainingResultAttachment"
+            >
               <fileMain
                 v-model="form.trainingResultAttachment"
                 :type="isView ? 'view' : 'add'"
@@ -328,17 +337,29 @@
         <!-- 参加培训人员 -->
         <div class="section-title">参加培训人员</div>
         <div class="section-toolbar">
-          <el-button
-            type="primary"
-            size="small"
-            icon="el-icon-plus"
-            v-if="!isView"
-            @click="handleAddPerson"
-            >添加培训人员</el-button
-          >
+          <template v-if="!isView">
+            <el-button
+              type="primary"
+              size="small"
+              icon="el-icon-plus"
+              @click="handleAddPerson"
+              >添加培训人员</el-button
+            >
+            <el-button
+              size="small"
+              icon="el-icon-upload2"
+              @click="handleImportPerson"
+              >导入</el-button
+            >
+          </template>
         </div>
         <el-table :data="form.persons" border style="width: 100%">
-          <el-table-column type="index" label="序号" width="60" align="center" />
+          <el-table-column
+            type="index"
+            label="序号"
+            width="60"
+            align="center"
+          />
           <el-table-column label="参训人员" align="center" min-width="110">
             <template slot-scope="scope">
               <el-input v-model="scope.row.personName" readonly />
@@ -380,10 +401,7 @@
           </el-table-column>
           <el-table-column label="追踪成绩" align="center" min-width="110">
             <template slot-scope="scope">
-              <el-input
-                v-model="scope.row.trackingScore"
-                placeholder="0-100"
-              />
+              <el-input v-model="scope.row.trackingScore" placeholder="0-100" />
             </template>
           </el-table-column>
           <el-table-column label="追踪效果" align="center" min-width="230">
@@ -443,6 +461,11 @@
           })
         "
       />
+      <person-import
+        ref="personImportRef"
+        title="导入参训人员"
+        @confirm="handlePersonImported"
+      />
     </ele-modal>
   </div>
 </template>
@@ -451,6 +474,7 @@
   import DeptSelect from '@/components/CommomSelect/dept-select.vue';
   import personSelect from '@/components/CommomSelect/person-select.vue';
   import userSelect from '@/components/user-select/user-select.vue';
+  import personImport from '@/components/upload/person-import-dialog.vue';
   import {
     getById,
     save as saveTraining,
@@ -508,7 +532,8 @@
     components: {
       DeptSelect,
       personSelect,
-      userSelect
+      userSelect,
+      personImport
     },
     data() {
       return {
@@ -542,7 +567,11 @@
             { required: true, message: '请选择结束时间', trigger: 'change' }
           ],
           archiveFlag: [
-            { required: true, message: '请选择是否一人一档归档', trigger: 'change' }
+            {
+              required: true,
+              message: '请选择是否一人一档归档',
+              trigger: 'change'
+            }
           ],
           trainingContent: [
             { max: 500, message: '培训内容不能超过500字', trigger: 'blur' }
@@ -556,7 +585,7 @@
     computed: {
       ...mapGetters(['user']),
       deptId() {
-        return this.user.info.deptIds?.[0] || "";
+        return this.user.info.deptIds?.[0] || '';
       },
       dialogTitle() {
         const titleMap = {
@@ -608,8 +637,7 @@
               ? ''
               : item.trainingHours,
           assessmentScore:
-            item.assessmentScore === undefined ||
-            item.assessmentScore === null
+            item.assessmentScore === undefined || item.assessmentScore === null
               ? ''
               : item.assessmentScore,
           trainingEffect:
@@ -631,7 +659,8 @@
           id: detail.id || '',
           trainingProject: detail.trainingProject || '',
           organizationId:
-            detail.organizationId === undefined || detail.organizationId === null
+            detail.organizationId === undefined ||
+            detail.organizationId === null
               ? ''
               : detail.organizationId,
           organizationName: detail.organizationName || '',
@@ -642,7 +671,8 @@
               : detail.responsibleUserId,
           responsibleUserName: detail.responsibleUserName || '',
           trainingUnitId:
-            detail.trainingUnitId === undefined || detail.trainingUnitId === null
+            detail.trainingUnitId === undefined ||
+            detail.trainingUnitId === null
               ? ''
               : detail.trainingUnitId,
           trainingUnitName: detail.trainingUnitName || '',
@@ -716,13 +746,34 @@
       handleAddPerson() {
         this.$refs.userSelectRef.open(null, 0, true);
       },
+      handleImportPerson() {
+        this.$refs.personImportRef.open(
+          this.form.persons.map((item) => item.personId)
+        );
+      },
       handlePersonSelected(rows) {
-        const list = Array.isArray(rows) ? rows : [rows];
+        this.appendPersons(Array.isArray(rows) ? rows : [rows]);
+      },
+      handlePersonImported(persons, ignoredCount) {
+        const added = this.appendPersons(persons);
+        if (added) {
+          this.$message.success(`成功导入 ${added} 名参训人员`);
+        }
+        if (ignoredCount) {
+          this.$message.warning(`已忽略 ${ignoredCount} 行未导入的数据`);
+        }
+      },
+      // 追加参训人员并按 personId 去重,返回实际新增条数
+      appendPersons(list) {
         const exists = new Set(
           this.form.persons.map((item) => String(item.personId))
         );
+        let added = 0;
         list.forEach((item) => {
-          if (exists.has(String(item.id))) return;
+          const personId = String(item.id);
+          if (exists.has(personId)) return;
+          exists.add(personId);
+          added++;
           this.form.persons.push({
             personId: item.id,
             personName: item.name || '',
@@ -736,6 +787,7 @@
           });
         });
         this.form.plannedCount = this.form.persons.length;
+        return added;
       },
       handleDeletePerson(index) {
         this.form.persons.splice(index, 1);

+ 1 - 1
src/views/safetyTraining/onlineTrainingPlans/deliver.vue

@@ -300,7 +300,7 @@
 
 <script>
   import userSelect from '@/components/user-select/user-select.vue';
-  import personImport from './person-import-dialog.vue';
+  import personImport from '@/components/upload/person-import-dialog.vue';
   import { getById, deliver } from '@/api/onlineTrainingPlan/index.js';
   import { getById as getPaperById } from '@/api/examPaper/examPaper.js';
   import { getAll as getAllTrainingMaterials } from '@/api/trainingMaterials/index.js';

+ 7 - 0
vue.config.js

@@ -19,6 +19,13 @@ module.exports = {
       maxAssetSize: 2000000,
       maxEntrypointSize: 2000000
     },
+    resolve: {
+      // xlsx 依赖的 sax 里有一行 require('stream')(仅流式解析用,浏览器走不到),
+      // webpack5 默认不打包 node 内置模块,显式置空消掉构建警告
+      fallback: {
+        stream: false
+      }
+    },
     output: {
       // 把子应用打包成 umd 库格式
       library: {