|
@@ -0,0 +1,846 @@
|
|
|
|
|
+<template>
|
|
|
|
|
+ <main class="safety-platform-container__main">
|
|
|
|
|
+ <el-form
|
|
|
|
|
+ ref="formRef"
|
|
|
|
|
+ :model="ruleFormData"
|
|
|
|
|
+ :rules="formRules"
|
|
|
|
|
+ label-width="auto"
|
|
|
|
|
+ class="evaluation-form"
|
|
|
|
|
+ >
|
|
|
|
|
+ <el-form-item label="考核表名称:" prop="evaluationTableName">
|
|
|
|
|
+ <el-input
|
|
|
|
|
+ v-model="ruleFormData.evaluationTableName"
|
|
|
|
|
+ placeholder="请输入考核表名称"
|
|
|
|
|
+ :disabled="isViewMode"
|
|
|
|
|
+ />
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+ <el-form-item label="上传附件文档:" prop="attachmentDocument">
|
|
|
|
|
+ <UploadFiles
|
|
|
|
|
+ label="上传附件"
|
|
|
|
|
+ :file-list="ruleFormData.attachmentDocument"
|
|
|
|
|
+ @uploadSuccess="handleUploadSuccess"
|
|
|
|
|
+ />
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+ <el-form-item label="评分说明:" prop="scoringDescription">
|
|
|
|
|
+ <el-input
|
|
|
|
|
+ v-model="ruleFormData.scoringDescription"
|
|
|
|
|
+ type="textarea"
|
|
|
|
|
+ :rows="5"
|
|
|
|
|
+ placeholder="请输入评分说明"
|
|
|
|
|
+ :disabled="isViewMode"
|
|
|
|
|
+ />
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+ </el-form>
|
|
|
|
|
+
|
|
|
|
|
+ <div class="evaluation-items-section">
|
|
|
|
|
+ <div class="section-header">
|
|
|
|
|
+ <!-- <el-button plain @click="handleDownloadTemplate">模板下载</el-button> -->
|
|
|
|
|
+ <el-button plain @click="handleImport">导入</el-button>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="evaluation-items-table">
|
|
|
|
|
+ <el-table :data="evaluationItems" border :span-method="handleSpanMethod">
|
|
|
|
|
+ <el-table-column label="编号" type="index" width="80" align="center" />
|
|
|
|
|
+ <el-table-column label="考核项目" min-width="150">
|
|
|
|
|
+ <template #header>
|
|
|
|
|
+ <span>考核项目<span style="color: red">*</span></span>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ <template #default="scope">
|
|
|
|
|
+ <el-input
|
|
|
|
|
+ :model-value="getCurrentEvaluationItem(scope.$index)"
|
|
|
|
|
+ @update:model-value="(val) => handleEvaluationItemInput(scope.$index, val)"
|
|
|
|
|
+ @blur="handleEvaluationItemBlur(scope.$index)"
|
|
|
|
|
+ placeholder="请输入考核项目"
|
|
|
|
|
+ :disabled="isViewMode"
|
|
|
|
|
+ />
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </el-table-column>
|
|
|
|
|
+ <el-table-column label="考核内容" min-width="200">
|
|
|
|
|
+ <template #header>
|
|
|
|
|
+ <span>考核内容<span style="color: red">*</span></span>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ <template #default="scope">
|
|
|
|
|
+ <el-input
|
|
|
|
|
+ v-model="scope.row.evaluationContent"
|
|
|
|
|
+ placeholder="请输入考核内容"
|
|
|
|
|
+ :disabled="isViewMode"
|
|
|
|
|
+ />
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </el-table-column>
|
|
|
|
|
+ <el-table-column label="评分方式" min-width="150">
|
|
|
|
|
+ <template #default="scope">
|
|
|
|
|
+ <el-input
|
|
|
|
|
+ v-model="scope.row.scoringMethod"
|
|
|
|
|
+ placeholder="请输入评分方式"
|
|
|
|
|
+ :disabled="isViewMode"
|
|
|
|
|
+ />
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </el-table-column>
|
|
|
|
|
+ <el-table-column label="加减分项" min-width="150" align="center">
|
|
|
|
|
+ <template #default="scope">
|
|
|
|
|
+ <el-select
|
|
|
|
|
+ v-model="scope.row.isAdd"
|
|
|
|
|
+ placeholder="请选择"
|
|
|
|
|
+ :disabled="isViewMode"
|
|
|
|
|
+ style="width: 100%"
|
|
|
|
|
+ >
|
|
|
|
|
+ <el-option label="加分项" :value="1" />
|
|
|
|
|
+ <el-option label="减分项" :value="0" />
|
|
|
|
|
+ </el-select>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </el-table-column>
|
|
|
|
|
+ <el-table-column label="复评人" min-width="150">
|
|
|
|
|
+ <template #default="scope">
|
|
|
|
|
+ <el-select
|
|
|
|
|
+ v-model="scope.row.reviewUserId"
|
|
|
|
|
+ placeholder="请选择复评人"
|
|
|
|
|
+ filterable
|
|
|
|
|
+ clearable
|
|
|
|
|
+ :disabled="isViewMode"
|
|
|
|
|
+ style="width: 100%"
|
|
|
|
|
+ @change="(val) => handleReviewUserChange(scope.$index, val)"
|
|
|
|
|
+ >
|
|
|
|
|
+ <el-option
|
|
|
|
|
+ v-for="user in reviewUserList"
|
|
|
|
|
+ :key="user.id"
|
|
|
|
|
+ :label="user.realname"
|
|
|
|
|
+ :value="user.id"
|
|
|
|
|
+ />
|
|
|
|
|
+ </el-select>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </el-table-column>
|
|
|
|
|
+ <el-table-column v-if="!isViewMode" label="操作" fixed="right" width="350" align="center">
|
|
|
|
|
+ <template #default="scope">
|
|
|
|
|
+ <el-button type="primary" link @click="handleAddItem(scope.$index)">新增</el-button>
|
|
|
|
|
+ <el-button type="primary" link @click="handleMoveUp(scope.$index)">向上插入分类</el-button>
|
|
|
|
|
+ <el-button type="primary" link @click="handleMoveDown(scope.$index)">向下插入分类</el-button>
|
|
|
|
|
+ <el-button type="danger" link @click="handleDeleteItem(scope.$index)">删除</el-button>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </el-table-column>
|
|
|
|
|
+ </el-table>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 导入弹窗 -->
|
|
|
|
|
+ <el-dialog v-model="importDialogVisible" title="导入考核项目" width="500px" destroy-on-close>
|
|
|
|
|
+ <el-upload
|
|
|
|
|
+ :file-list="importFileList"
|
|
|
|
|
+ :auto-upload="false"
|
|
|
|
|
+ :on-change="handleFileChange"
|
|
|
|
|
+ :on-remove="handleFileRemove"
|
|
|
|
|
+ :limit="1"
|
|
|
|
|
+ drag
|
|
|
|
|
+ accept=".xlsx,.xls"
|
|
|
|
|
+ >
|
|
|
|
|
+ <el-icon class="el-icon--upload"><Document /></el-icon>
|
|
|
|
|
+ <div class="el-upload__text">
|
|
|
|
|
+ <div style="font-size: 12px; color: red; margin-bottom: 5px">请下载模板并按要求填写后上传</div>
|
|
|
|
|
+ <div style="font-size: 16px">点击或将文件拖拽到这里上传</div>
|
|
|
|
|
+ <div style="font-size: 12px; color: rgba(0, 0, 0, 0.45); margin-top: 5px">
|
|
|
|
|
+ 文件支持.xlsx .xls格式,仅支持上传一个文件
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </el-upload>
|
|
|
|
|
+ <template #footer>
|
|
|
|
|
+ <span class="dialog-footer">
|
|
|
|
|
+ <el-button @click="importDialogVisible = false">取消</el-button>
|
|
|
|
|
+ <el-button type="primary" @click="handleConfirmImport" :disabled="importFileList.length === 0">
|
|
|
|
|
+ 导入
|
|
|
|
|
+ </el-button>
|
|
|
|
|
+ </span>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </el-dialog>
|
|
|
|
|
+ </main>
|
|
|
|
|
+ <footer class="safety-platform-container__footer">
|
|
|
|
|
+ <el-button @click="router.back()">取消</el-button>
|
|
|
|
|
+ <el-button v-if="!isViewMode" type="primary" @click="handleSubmit">
|
|
|
|
|
+ {{ isCreateMode ? '提交' : '保存' }}
|
|
|
|
|
+ </el-button>
|
|
|
|
|
+ </footer>
|
|
|
|
|
+</template>
|
|
|
|
|
+
|
|
|
|
|
+<script setup lang="ts">
|
|
|
|
|
+ import { computed, nextTick, onMounted, ref } from 'vue';
|
|
|
|
|
+ import { useRoute, useRouter } from 'vue-router';
|
|
|
|
|
+ import { ElMessage } from 'element-plus';
|
|
|
|
|
+ import type { FormInstance } from 'element-plus';
|
|
|
|
|
+ import { Document } from '@element-plus/icons-vue';
|
|
|
|
|
+ import UploadFiles from '@/components/UploadFiles/UploadFiles.vue';
|
|
|
|
|
+ import { EVALUATION_SYSTEM_FORM_DATA, EVALUATION_SYSTEM_FORM_RULES } from '../configs/form';
|
|
|
|
|
+ import { importSecurityExamineDet, saveSecurityExamine, querySecurityExamineDetail, updateSecurityExamine } from '@/api/evaluationSystem';
|
|
|
|
|
+ import type { EvaluationContent } from '@/api/evaluationSystem';
|
|
|
|
|
+ import type { FileItem } from '@/components/UploadFiles/types';
|
|
|
|
|
+ import { useUserInfoHook } from '@/hooks/useUserInfoHook';
|
|
|
|
|
+ import { formatAttachmentList } from '@/components/UploadFiles/utils';
|
|
|
|
|
+ import { getUserList } from '@/api/system/user-operate';
|
|
|
|
|
+
|
|
|
|
|
+ const props = defineProps<{
|
|
|
|
|
+ id?: number;
|
|
|
|
|
+ }>();
|
|
|
|
|
+
|
|
|
|
|
+ const router = useRouter();
|
|
|
|
|
+ const route = useRoute();
|
|
|
|
|
+
|
|
|
|
|
+ const operate = computed(() => (route.query.operate as string) || 'evaluationSystem-create');
|
|
|
|
|
+ const isCreateMode = computed(() => operate.value === 'evaluationSystem-create');
|
|
|
|
|
+ const isEditMode = computed(() => operate.value === 'evaluationSystem-edit');
|
|
|
|
|
+ const isViewMode = computed(() => operate.value === 'evaluationSystem-view');
|
|
|
|
|
+
|
|
|
|
|
+ const formRef = ref<FormInstance>();
|
|
|
|
|
+ const ruleFormData = ref({ ...EVALUATION_SYSTEM_FORM_DATA });
|
|
|
|
|
+ const formRules = EVALUATION_SYSTEM_FORM_RULES;
|
|
|
|
|
+
|
|
|
|
|
+ // 获取当前用户信息
|
|
|
|
|
+ const { id: userId, realname: userName } = useUserInfoHook();
|
|
|
|
|
+
|
|
|
|
|
+ // 初始化一条空白数据
|
|
|
|
|
+ const evaluationItems = ref<any[]>([
|
|
|
|
|
+ {
|
|
|
|
|
+ id: 0,
|
|
|
|
|
+ psemId: 0,
|
|
|
|
|
+ evaluationItem: '',
|
|
|
|
|
+ evaluationContent: '',
|
|
|
|
|
+ scoringMethod: '',
|
|
|
|
|
+ isAdd: 1, // 是否加分项(0-否,1-是),默认为1(加分项)
|
|
|
|
|
+ reviewUserId: null as number | null, // 复评人ID
|
|
|
|
|
+ reviewUserName: '', // 复评人姓名
|
|
|
|
|
+ },
|
|
|
|
|
+ ]);
|
|
|
|
|
+
|
|
|
|
|
+ // 复评人用户列表
|
|
|
|
|
+ const reviewUserList = ref<UserLisItem[]>([]);
|
|
|
|
|
+ const getReviewUserList = async () => {
|
|
|
|
|
+ try {
|
|
|
|
|
+ const res = await getUserList({
|
|
|
|
|
+ pageNumber: 1,
|
|
|
|
|
+ pageSize: 9999,
|
|
|
|
|
+ queryParam: {}, // 不传递 deptId 参数
|
|
|
|
|
+ });
|
|
|
|
|
+ reviewUserList.value = res?.records || [];
|
|
|
|
|
+ } catch (e) {
|
|
|
|
|
+ console.error('获取复评人列表失败:', e);
|
|
|
|
|
+ reviewUserList.value = [];
|
|
|
|
|
+ }
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ // 处理复评人选择变化
|
|
|
|
|
+ const handleReviewUserChange = (index: number, userId: number | null) => {
|
|
|
|
|
+ if (userId) {
|
|
|
|
|
+ const selectedUser = reviewUserList.value.find((user) => user.id === userId);
|
|
|
|
|
+ if (selectedUser) {
|
|
|
|
|
+ evaluationItems.value[index].reviewUserName = selectedUser.realname || '';
|
|
|
|
|
+ }
|
|
|
|
|
+ } else {
|
|
|
|
|
+ evaluationItems.value[index].reviewUserName = '';
|
|
|
|
|
+ }
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ // 用于存储输入过程中的临时值,避免实时合并
|
|
|
|
|
+ const tempEvaluationItems = ref<Record<number, string>>({});
|
|
|
|
|
+
|
|
|
|
|
+ const handleValidate = async () => {
|
|
|
|
|
+ if (!formRef.value) return;
|
|
|
|
|
+ return new Promise((resolve) => {
|
|
|
|
|
+ formRef.value?.validate((valid: boolean) => {
|
|
|
|
|
+ resolve(valid);
|
|
|
|
|
+ });
|
|
|
|
|
+ });
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ const handleUploadSuccess = (files: any[]) => {
|
|
|
|
|
+ ruleFormData.value.attachmentDocument = files;
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ const handleDownloadTemplate = () => {
|
|
|
|
|
+ // TODO: 下载模板
|
|
|
|
|
+ console.log('download template');
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ // 导入弹窗相关
|
|
|
|
|
+ const importDialogVisible = ref(false);
|
|
|
|
|
+ const importFileList = ref<any[]>([]);
|
|
|
|
|
+
|
|
|
|
|
+ const handleImport = () => {
|
|
|
|
|
+ importDialogVisible.value = true;
|
|
|
|
|
+ importFileList.value = [];
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ // 处理导入成功
|
|
|
|
|
+ const handleImportSuccess = async (response: any) => {
|
|
|
|
|
+ try {
|
|
|
|
|
+ // 接口返回的数据格式:{ code: 0, message: "string", data: EvaluationContent[] }
|
|
|
|
|
+ if (response && response.data && Array.isArray(response.data)) {
|
|
|
|
|
+ // 将导入的数据映射到 evaluationItems 格式
|
|
|
|
|
+ // 导入的数据视为新增数据,不带后端已保存的 id / psemId
|
|
|
|
|
+ evaluationItems.value = response.data.map((item: any) => ({
|
|
|
|
|
+ id: 0,
|
|
|
|
|
+ psemId: 0,
|
|
|
|
|
+ evaluationItem: item.exProgram || '', // 考核项目
|
|
|
|
|
+ evaluationContent: item.exContent || '', // 考核内容
|
|
|
|
|
+ scoringMethod: item.scoringWay || '', // 评分方式
|
|
|
|
|
+ isAdd: item.isAdd !== undefined ? item.isAdd : 1, // 是否加分项(0-否,1-是),默认为1
|
|
|
|
|
+ reviewUserId: item.reviewUserId || null, // 复评人ID
|
|
|
|
|
+ reviewUserName: item.reviewUserName || '', // 复评人姓名
|
|
|
|
|
+ }));
|
|
|
|
|
+
|
|
|
|
|
+ // 如果导入后列表为空,至少保留一条空白数据
|
|
|
|
|
+ if (evaluationItems.value.length === 0) {
|
|
|
|
|
+ evaluationItems.value = [
|
|
|
|
|
+ {
|
|
|
|
|
+ evaluationItem: '',
|
|
|
|
|
+ evaluationContent: '',
|
|
|
|
|
+ scoringMethod: '',
|
|
|
|
|
+ },
|
|
|
|
|
+ ];
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ ElMessage.success(`导入成功,共导入 ${response.data.length} 条数据`);
|
|
|
|
|
+ importDialogVisible.value = false;
|
|
|
|
|
+ } else {
|
|
|
|
|
+ ElMessage.error('导入失败:返回数据格式错误');
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (e) {
|
|
|
|
|
+ console.error('导入处理失败:', e);
|
|
|
|
|
+ ElMessage.error('导入处理失败');
|
|
|
|
|
+ }
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ // 处理文件上传
|
|
|
|
|
+ const handleFileChange = (file: any) => {
|
|
|
|
|
+ importFileList.value = [file];
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ // 处理文件移除
|
|
|
|
|
+ const handleFileRemove = () => {
|
|
|
|
|
+ importFileList.value = [];
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ // 确认导入
|
|
|
|
|
+ const handleConfirmImport = async () => {
|
|
|
|
|
+ if (importFileList.value.length === 0) {
|
|
|
|
|
+ ElMessage.warning('请先选择要导入的文件');
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const file = importFileList.value[0].raw || importFileList.value[0];
|
|
|
|
|
+ if (!file) {
|
|
|
|
|
+ ElMessage.warning('文件不存在');
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 检查文件类型
|
|
|
|
|
+ const isExcel = /\.(xlsx|xls)$/.test(file.name.toLowerCase());
|
|
|
|
|
+ if (!isExcel) {
|
|
|
|
|
+ ElMessage.error('仅支持上传.xlsx .xls格式文件');
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ try {
|
|
|
|
|
+ const formData = new FormData();
|
|
|
|
|
+ formData.append('file', file);
|
|
|
|
|
+
|
|
|
|
|
+ const res = await importSecurityExamineDet(formData);
|
|
|
|
|
+ if (res) {
|
|
|
|
|
+ handleImportSuccess({ data: res });
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (e: any) {
|
|
|
|
|
+ console.error('导入失败:', e);
|
|
|
|
|
+ ElMessage.error(e?.message || '导入失败,请重试');
|
|
|
|
|
+ }
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ // 获取当前行的考核项目值(考虑临时输入值)
|
|
|
|
|
+ const getCurrentEvaluationItem = (rowIndex: number): string => {
|
|
|
|
|
+ // 如果正在输入(有临时值),使用临时值;否则使用实际值
|
|
|
|
|
+ if (tempEvaluationItems.value[rowIndex] !== undefined) {
|
|
|
|
|
+ return tempEvaluationItems.value[rowIndex];
|
|
|
|
|
+ }
|
|
|
|
|
+ return evaluationItems.value[rowIndex]?.evaluationItem || '';
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ // 处理单元格合并(合并考核项目列)
|
|
|
|
|
+ const handleSpanMethod = ({ row, column, rowIndex, columnIndex }: any) => {
|
|
|
|
|
+ // 只合并考核项目列(columnIndex === 1,因为编号列是 0)
|
|
|
|
|
+ if (columnIndex === 1) {
|
|
|
|
|
+ const currentItem = row.evaluationItem;
|
|
|
|
|
+ if (!currentItem) {
|
|
|
|
|
+ // 如果当前行的考核项目为空,不合并
|
|
|
|
|
+ return {
|
|
|
|
|
+ rowspan: 1,
|
|
|
|
|
+ colspan: 1,
|
|
|
|
|
+ };
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 查找相同考核项目的连续行
|
|
|
|
|
+ let rowspan = 1;
|
|
|
|
|
+ for (let i = rowIndex + 1; i < evaluationItems.value.length; i++) {
|
|
|
|
|
+ if (evaluationItems.value[i].evaluationItem === currentItem) {
|
|
|
|
|
+ rowspan++;
|
|
|
|
|
+ } else {
|
|
|
|
|
+ break;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 检查是否是同一组的第一行
|
|
|
|
|
+ if (rowIndex > 0 && evaluationItems.value[rowIndex - 1].evaluationItem === currentItem) {
|
|
|
|
|
+ // 不是第一行,不显示(被合并)
|
|
|
|
|
+ return {
|
|
|
|
|
+ rowspan: 0,
|
|
|
|
|
+ colspan: 0,
|
|
|
|
|
+ };
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return {
|
|
|
|
|
+ rowspan,
|
|
|
|
|
+ colspan: 1,
|
|
|
|
|
+ };
|
|
|
|
|
+ }
|
|
|
|
|
+ // 其他列不合并
|
|
|
|
|
+ return {
|
|
|
|
|
+ rowspan: 1,
|
|
|
|
|
+ colspan: 1,
|
|
|
|
|
+ };
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ // 检查是否是相邻行的相同内容(用于合并)
|
|
|
|
|
+ const isAdjacentSameValue = (index: number, value: string): boolean => {
|
|
|
|
|
+ if (!value) return false;
|
|
|
|
|
+
|
|
|
|
|
+ // 检查上一行或下一行是否有相同的值
|
|
|
|
|
+ const prevRow = evaluationItems.value[index - 1];
|
|
|
|
|
+ const nextRow = evaluationItems.value[index + 1];
|
|
|
|
|
+
|
|
|
|
|
+ return (
|
|
|
|
|
+ (prevRow && prevRow.evaluationItem === value) ||
|
|
|
|
|
+ (nextRow && nextRow.evaluationItem === value)
|
|
|
|
|
+ );
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ // 检查是否有跨行的相同考核项目(非相邻的)
|
|
|
|
|
+ const checkNonConsecutiveDuplicate = (index: number, value: string): boolean => {
|
|
|
|
|
+ if (!value) return false;
|
|
|
|
|
+
|
|
|
|
|
+ // 检查是否有非相邻行的相同值
|
|
|
|
|
+ for (let i = 0; i < evaluationItems.value.length; i++) {
|
|
|
|
|
+ if (i === index) continue; // 跳过当前行
|
|
|
|
|
+
|
|
|
|
|
+ if (evaluationItems.value[i].evaluationItem === value) {
|
|
|
|
|
+ // 检查是否是相邻行
|
|
|
|
|
+ const isAdjacent = i === index - 1 || i === index + 1;
|
|
|
|
|
+
|
|
|
|
|
+ if (!isAdjacent) {
|
|
|
|
|
+ return true; // 发现跨行的重复
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ return false;
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ // 考核项目输入时的处理(只更新临时值,不触发合并)
|
|
|
|
|
+ const handleEvaluationItemInput = (index: number, value: string) => {
|
|
|
|
|
+ // 只更新临时值,不更新实际数据,避免实时合并
|
|
|
|
|
+ tempEvaluationItems.value[index] = value;
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ // 考核项目失去焦点时的处理
|
|
|
|
|
+ const handleEvaluationItemBlur = async (index: number) => {
|
|
|
|
|
+ // 获取临时输入值
|
|
|
|
|
+ const tempValue = tempEvaluationItems.value[index];
|
|
|
|
|
+ const finalValue = tempValue !== undefined ? tempValue : evaluationItems.value[index].evaluationItem;
|
|
|
|
|
+
|
|
|
|
|
+ // 清除临时值
|
|
|
|
|
+ delete tempEvaluationItems.value[index];
|
|
|
|
|
+
|
|
|
|
|
+ // 如果值为空,直接更新并返回
|
|
|
|
|
+ if (!finalValue || !finalValue.trim()) {
|
|
|
|
|
+ evaluationItems.value[index].evaluationItem = '';
|
|
|
|
|
+ await nextTick();
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 先检查是否是相邻行的相同内容(允许合并)
|
|
|
|
|
+ const isAdjacent = isAdjacentSameValue(index, finalValue);
|
|
|
|
|
+
|
|
|
|
|
+ if (isAdjacent) {
|
|
|
|
|
+ // 相邻行相同,允许合并,直接更新数据
|
|
|
|
|
+ evaluationItems.value[index].evaluationItem = finalValue;
|
|
|
|
|
+ await nextTick();
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 检查是否有跨行的相同考核项目(非相邻的)
|
|
|
|
|
+ if (checkNonConsecutiveDuplicate(index, finalValue)) {
|
|
|
|
|
+ ElMessage.warning('考核项目一致,请使用连续的相同考核项目');
|
|
|
|
|
+ evaluationItems.value[index].evaluationItem = '';
|
|
|
|
|
+ await nextTick();
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 没有冲突,更新实际数据
|
|
|
|
|
+ evaluationItems.value[index].evaluationItem = finalValue;
|
|
|
|
|
+
|
|
|
|
|
+ // 等待 DOM 更新后,强制表格重新计算合并
|
|
|
|
|
+ await nextTick();
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ // 新增:在当前行下面插入一条新数据,如果当前行有考核项目值,新行也使用相同的值
|
|
|
|
|
+ const handleAddItem = (index: number) => {
|
|
|
|
|
+ const currentItem = evaluationItems.value[index];
|
|
|
|
|
+ evaluationItems.value.splice(index + 1, 0, {
|
|
|
|
|
+ id: 0,
|
|
|
|
|
+ psemId: 0,
|
|
|
|
|
+ evaluationItem: currentItem.evaluationItem || '', // 合并考核项目字段
|
|
|
|
|
+ evaluationContent: '',
|
|
|
|
|
+ scoringMethod: '',
|
|
|
|
|
+ isAdd: currentItem.isAdd !== undefined ? currentItem.isAdd : 1, // 是否加分项,继承当前行的值
|
|
|
|
|
+ reviewUserId: null, // 复评人ID
|
|
|
|
|
+ reviewUserName: '', // 复评人姓名
|
|
|
|
|
+ });
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ // 删除当前行
|
|
|
|
|
+ const handleDeleteItem = (index: number) => {
|
|
|
|
|
+ if (evaluationItems.value.length <= 1) {
|
|
|
|
|
+ ElMessage.warning('至少需要保留一条数据');
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ evaluationItems.value.splice(index, 1);
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ // 查找当前行所在合并组的起始位置
|
|
|
|
|
+ const findGroupStartIndex = (index: number): number => {
|
|
|
|
|
+ const currentItem = evaluationItems.value[index].evaluationItem;
|
|
|
|
|
+ if (!currentItem) {
|
|
|
|
|
+ return index; // 如果当前行考核项目为空,直接返回当前索引
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 向上查找,找到相同考核项目的第一行
|
|
|
|
|
+ let startIndex = index;
|
|
|
|
|
+ for (let i = index - 1; i >= 0; i--) {
|
|
|
|
|
+ if (evaluationItems.value[i].evaluationItem === currentItem) {
|
|
|
|
|
+ startIndex = i;
|
|
|
|
|
+ } else {
|
|
|
|
|
+ break;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ return startIndex;
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ // 查找当前行所在合并组的结束位置
|
|
|
|
|
+ const findGroupEndIndex = (index: number): number => {
|
|
|
|
|
+ const currentItem = evaluationItems.value[index].evaluationItem;
|
|
|
|
|
+ if (!currentItem) {
|
|
|
|
|
+ return index; // 如果当前行考核项目为空,直接返回当前索引
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 向下查找,找到相同考核项目的最后一行
|
|
|
|
|
+ let endIndex = index;
|
|
|
|
|
+ for (let i = index + 1; i < evaluationItems.value.length; i++) {
|
|
|
|
|
+ if (evaluationItems.value[i].evaluationItem === currentItem) {
|
|
|
|
|
+ endIndex = i;
|
|
|
|
|
+ } else {
|
|
|
|
|
+ break;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ return endIndex;
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ // 向上插入分类:在当前行所在合并组的第一行之前插入一条空白数据
|
|
|
|
|
+ const handleMoveUp = (index: number) => {
|
|
|
|
|
+ const groupStartIndex = findGroupStartIndex(index);
|
|
|
|
|
+ evaluationItems.value.splice(groupStartIndex, 0, {
|
|
|
|
|
+ id: 0,
|
|
|
|
|
+ psemId: 0,
|
|
|
|
|
+ evaluationItem: '',
|
|
|
|
|
+ evaluationContent: '',
|
|
|
|
|
+ scoringMethod: '',
|
|
|
|
|
+ isAdd: 1, // 是否加分项(0-否,1-是),默认为1
|
|
|
|
|
+ reviewUserId: null, // 复评人ID
|
|
|
|
|
+ reviewUserName: '', // 复评人姓名
|
|
|
|
|
+ });
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ // 向下插入分类:在当前行所在合并组的最后一行之后插入一条空白数据
|
|
|
|
|
+ const handleMoveDown = (index: number) => {
|
|
|
|
|
+ const groupEndIndex = findGroupEndIndex(index);
|
|
|
|
|
+ evaluationItems.value.splice(groupEndIndex + 1, 0, {
|
|
|
|
|
+ id: 0,
|
|
|
|
|
+ psemId: 0,
|
|
|
|
|
+ evaluationItem: '',
|
|
|
|
|
+ evaluationContent: '',
|
|
|
|
|
+ scoringMethod: '',
|
|
|
|
|
+ isAdd: 1, // 是否加分项(0-否,1-是),默认为1
|
|
|
|
|
+ reviewUserId: null, // 复评人ID
|
|
|
|
|
+ reviewUserName: '', // 复评人姓名
|
|
|
|
|
+ });
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ // 将逗号分隔的URL字符串转换为FileItem数组
|
|
|
|
|
+ const convertAttachmentsToFileItems = (attachmentsStr: string): FileItem[] => {
|
|
|
|
|
+ if (!attachmentsStr || !attachmentsStr.trim()) {
|
|
|
|
|
+ return [];
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 按逗号分割URL
|
|
|
|
|
+ const urls = attachmentsStr.split(',').map(url => url.trim()).filter(url => url);
|
|
|
|
|
+
|
|
|
|
|
+ return urls.map((url, index) => {
|
|
|
|
|
+ // 从URL中提取文件名
|
|
|
|
|
+ const urlParts = url.split('/');
|
|
|
|
|
+ const fileName = urlParts[urlParts.length - 1] || `附件${index + 1}`;
|
|
|
|
|
+
|
|
|
|
|
+ // 根据文件扩展名判断文件类型
|
|
|
|
|
+ const extension = fileName.split('.').pop()?.toLowerCase() || '';
|
|
|
|
|
+ let fileType = 'pdf';
|
|
|
|
|
+ if (extension === 'doc' || extension === 'docx') {
|
|
|
|
|
+ fileType = 'word';
|
|
|
|
|
+ } else if (extension === 'xls' || extension === 'xlsx') {
|
|
|
|
|
+ fileType = 'excel';
|
|
|
|
|
+ } else if (extension === 'ppt' || extension === 'pptx') {
|
|
|
|
|
+ fileType = 'ppt';
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return {
|
|
|
|
|
+ fileId: 0,
|
|
|
|
|
+ fileName,
|
|
|
|
|
+ fileType,
|
|
|
|
|
+ fileSize: '0',
|
|
|
|
|
+ fileUrl: url,
|
|
|
|
|
+ };
|
|
|
|
|
+ });
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ const getDetail = async () => {
|
|
|
|
|
+ if (!props.id) return;
|
|
|
|
|
+
|
|
|
|
|
+ try {
|
|
|
|
|
+ const detail = await querySecurityExamineDetail(props.id);
|
|
|
|
|
+ if (!detail) return;
|
|
|
|
|
+
|
|
|
|
|
+ // 填充表单数据
|
|
|
|
|
+ ruleFormData.value.evaluationTableName = detail.exName || '';
|
|
|
|
|
+ ruleFormData.value.scoringDescription = detail.ratingDescribe || '';
|
|
|
|
|
+
|
|
|
|
|
+ // 转换附件文档:将逗号分隔的URL字符串转换为FileItem数组
|
|
|
|
|
+ ruleFormData.value.attachmentDocument = convertAttachmentsToFileItems(detail.attachments || '');
|
|
|
|
|
+
|
|
|
|
|
+ // 填充考核项目列表
|
|
|
|
|
+ if (detail.exContents && detail.exContents.length > 0) {
|
|
|
|
|
+ // 详情接口返回的数据视为已持久化的数据,保留 id / psemId
|
|
|
|
|
+ evaluationItems.value = detail.exContents.map((item: any) => ({
|
|
|
|
|
+ id: item.id || 0,
|
|
|
|
|
+ psemId: item.psemId || 0,
|
|
|
|
|
+ evaluationItem: item.exProgram || '',
|
|
|
|
|
+ evaluationContent: item.exContent || '',
|
|
|
|
|
+ scoringMethod: item.scoringWay || '',
|
|
|
|
|
+ isAdd: item.isAdd !== undefined ? item.isAdd : 1, // 是否加分项(0-否,1-是),默认为1
|
|
|
|
|
+ reviewUserId: item.reviewUserId || null, // 复评人ID
|
|
|
|
|
+ reviewUserName: item.reviewUserName || '', // 复评人姓名
|
|
|
|
|
+ }));
|
|
|
|
|
+ } else {
|
|
|
|
|
+ // 如果没有数据,至少保留一条空白数据
|
|
|
|
|
+ evaluationItems.value = [
|
|
|
|
|
+ {
|
|
|
|
|
+ id: 0,
|
|
|
|
|
+ psemId: 0,
|
|
|
|
|
+ evaluationItem: '',
|
|
|
|
|
+ evaluationContent: '',
|
|
|
|
|
+ scoringMethod: '',
|
|
|
|
|
+ isAdd: 1, // 是否加分项(0-否,1-是),默认为1
|
|
|
|
|
+ reviewUserId: null, // 复评人ID
|
|
|
|
|
+ reviewUserName: '', // 复评人姓名
|
|
|
|
|
+ },
|
|
|
|
|
+ ];
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (e: any) {
|
|
|
|
|
+ console.error('获取详情失败:', e);
|
|
|
|
|
+ ElMessage.error(e?.message || '获取详情失败,请重试');
|
|
|
|
|
+ }
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ // 验证考核项目列表
|
|
|
|
|
+ const validateEvaluationItems = (): boolean => {
|
|
|
|
|
+ for (let i = 0; i < evaluationItems.value.length; i++) {
|
|
|
|
|
+ const item = evaluationItems.value[i];
|
|
|
|
|
+ if (!item.evaluationItem || !item.evaluationItem.trim()) {
|
|
|
|
|
+ ElMessage.warning(`第 ${i + 1} 行的考核项目不能为空`);
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+ if (!item.evaluationContent || !item.evaluationContent.trim()) {
|
|
|
|
|
+ ElMessage.warning(`第 ${i + 1} 行的考核内容不能为空`);
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ return true;
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ const handleSubmit = async () => {
|
|
|
|
|
+ const res = await handleValidate();
|
|
|
|
|
+ if (!res) return;
|
|
|
|
|
+
|
|
|
|
|
+ // 验证考核项目列表
|
|
|
|
|
+ if (!validateEvaluationItems()) {
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ try {
|
|
|
|
|
+ // 处理附件文档:先上传文件获取 URL,然后提取 fileUrl,多个用逗号分隔
|
|
|
|
|
+ let attachments = '';
|
|
|
|
|
+ if (ruleFormData.value.attachmentDocument && ruleFormData.value.attachmentDocument.length > 0) {
|
|
|
|
|
+ // 分离已有URL的文件和新上传的文件
|
|
|
|
|
+ const existingFiles: string[] = [];
|
|
|
|
|
+ const newFiles: any[] = [];
|
|
|
|
|
+
|
|
|
|
|
+ ruleFormData.value.attachmentDocument.forEach((file: any) => {
|
|
|
|
|
+ // 如果文件已经有 fileUrl 且没有 file 对象,说明是已有文件
|
|
|
|
|
+ if (file.fileUrl && !file.file) {
|
|
|
|
|
+ existingFiles.push(file.fileUrl);
|
|
|
|
|
+ } else {
|
|
|
|
|
+ // 否则是需要上传的新文件
|
|
|
|
|
+ newFiles.push(file);
|
|
|
|
|
+ }
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ // 上传新文件
|
|
|
|
|
+ let uploadedUrls: string[] = [];
|
|
|
|
|
+ if (newFiles.length > 0) {
|
|
|
|
|
+ const uploadedFiles = await formatAttachmentList(newFiles);
|
|
|
|
|
+ uploadedUrls = uploadedFiles
|
|
|
|
|
+ .map((file: any) => file.fileUrl || file.url || '')
|
|
|
|
|
+ .filter((url: string) => url);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 合并已有URL和新上传的URL
|
|
|
|
|
+ attachments = [...existingFiles, ...uploadedUrls].filter((url: string) => url).join(',');
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 映射考核项目列表,添加序号
|
|
|
|
|
+ const exContents: EvaluationContent[] = evaluationItems.value.map((item, index) => {
|
|
|
|
|
+ const base: any = {
|
|
|
|
|
+ serialNum: index + 1, // 序号从1开始
|
|
|
|
|
+ exProgram: item.evaluationItem || '', // 考核项目
|
|
|
|
|
+ exContent: item.evaluationContent || '', // 考核内容
|
|
|
|
|
+ scoringWay: item.scoringMethod || '', // 评分方式
|
|
|
|
|
+ isAdd: item.isAdd !== undefined ? item.isAdd : 1, // 是否加分项(0-否,1-是)
|
|
|
|
|
+ reviewUserId: item.reviewUserId || null, // 复评人ID
|
|
|
|
|
+ reviewUserName: item.reviewUserName || '', // 复评人姓名
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ // 只有详情接口返回的已存在数据才携带 id / psemId
|
|
|
|
|
+ if (item.id && item.psemId) {
|
|
|
|
|
+ base.id = item.id;
|
|
|
|
|
+ base.psemId = item.psemId;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return base as EvaluationContent;
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ if (isEditMode.value && props.id) {
|
|
|
|
|
+ // 编辑模式:调用更新接口
|
|
|
|
|
+ const payload = {
|
|
|
|
|
+ id: props.id, // 编辑时必须传ID
|
|
|
|
|
+ exName: ruleFormData.value.evaluationTableName || '', // 考核表名称
|
|
|
|
|
+ attachments, // 考核文档
|
|
|
|
|
+ ratingDescribe: ruleFormData.value.scoringDescription || '', // 评分说明
|
|
|
|
|
+ deptNames: '', // 下发部门(编辑时为空)
|
|
|
|
|
+ deptIds: [], // 下发部门ID数组(编辑时为空)
|
|
|
|
|
+ getUserGroupId: 0,
|
|
|
|
|
+ planStartTime: '', // 计划开始时间(编辑时为空)
|
|
|
|
|
+ planEndTime: '', // 计划结束时间(编辑时为空)
|
|
|
|
|
+ status: 0, // 状态:0-未下发
|
|
|
|
|
+ createdUserId: userId || 0, // 创建人ID
|
|
|
|
|
+ createdUserName: userName || '', // 创建人名称
|
|
|
|
|
+ exContents, // 考核项目列表
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ await updateSecurityExamine(payload);
|
|
|
|
|
+ ElMessage.success('保存成功');
|
|
|
|
|
+ } else {
|
|
|
|
|
+ // 创建模式:调用保存接口
|
|
|
|
|
+ const payload = {
|
|
|
|
|
+ id: 0, // 新增时为0
|
|
|
|
|
+ exName: ruleFormData.value.evaluationTableName || '', // 考核表名称
|
|
|
|
|
+ attachments, // 考核文档
|
|
|
|
|
+ ratingDescribe: ruleFormData.value.scoringDescription || '', // 评分说明
|
|
|
|
|
+ deptNames: '', // 下发部门(创建时为空)
|
|
|
|
|
+ deptIds: [], // 下发部门ID数组(创建时为空)
|
|
|
|
|
+ getUserGroupId: 0,
|
|
|
|
|
+ planStartTime: '', // 计划开始时间(创建时为空)
|
|
|
|
|
+ planEndTime: '', // 计划结束时间(创建时为空)
|
|
|
|
|
+ status: 0, // 状态:0-未下发
|
|
|
|
|
+ createdUserId: userId || 0, // 创建人ID
|
|
|
|
|
+ createdUserName: userName || '', // 创建人名称
|
|
|
|
|
+ exContents, // 考核项目列表
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ await saveSecurityExamine(payload);
|
|
|
|
|
+ ElMessage.success('创建成功');
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ router.back();
|
|
|
|
|
+ } catch (e: any) {
|
|
|
|
|
+ console.error('保存失败:', e);
|
|
|
|
|
+ ElMessage.error(e?.message || '保存失败,请重试');
|
|
|
|
|
+ }
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ onMounted(() => {
|
|
|
|
|
+ // 获取复评人列表
|
|
|
|
|
+ getReviewUserList();
|
|
|
|
|
+
|
|
|
|
|
+ if (isEditMode.value || isViewMode.value) {
|
|
|
|
|
+ getDetail();
|
|
|
|
|
+ } else {
|
|
|
|
|
+ // 创建模式下,确保附件文档字段为空
|
|
|
|
|
+ ruleFormData.value.attachmentDocument = [];
|
|
|
|
|
+ // 创建模式下,确保至少有一条空白数据
|
|
|
|
|
+ if (evaluationItems.value.length === 0) {
|
|
|
|
|
+ evaluationItems.value = [
|
|
|
|
|
+ {
|
|
|
|
|
+ id: 0,
|
|
|
|
|
+ psemId: 0,
|
|
|
|
|
+ evaluationItem: '',
|
|
|
|
|
+ evaluationContent: '',
|
|
|
|
|
+ scoringMethod: '',
|
|
|
|
|
+ isAdd: 1, // 是否加分项(0-否,1-是),默认为1
|
|
|
|
|
+ reviewUserId: null, // 复评人ID
|
|
|
|
|
+ reviewUserName: '', // 复评人姓名
|
|
|
|
|
+ },
|
|
|
|
|
+ ];
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ });
|
|
|
|
|
+</script>
|
|
|
|
|
+
|
|
|
|
|
+<style scoped lang="scss">
|
|
|
|
|
+ @use '@/styles/page-details-layout.scss' as *;
|
|
|
|
|
+
|
|
|
|
|
+ .evaluation-form {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ flex-direction: column;
|
|
|
|
|
+ width: 600px;
|
|
|
|
|
+ gap: 32px;
|
|
|
|
|
+
|
|
|
|
|
+ :deep(.el-form-item) {
|
|
|
|
|
+ margin-bottom: 0;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ :deep(.el-form-item__label) {
|
|
|
|
|
+ padding: 0;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .evaluation-items-section {
|
|
|
|
|
+ margin-top: 32px;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .section-header {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ gap: 10px;
|
|
|
|
|
+ margin-bottom: 20px;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .evaluation-items-table {
|
|
|
|
|
+ width: 100%;
|
|
|
|
|
+ }
|
|
|
|
|
+</style>
|
|
|
|
|
+
|