feat: 质量监控/人员培训/体系文件控制功能与界面调整
- 质量监控:质控方式多选、记录名称统一、导入模板与提示更新
- 人员培训:支持外聘讲师,结果明细排版、列表与查询优化
- 体系文件控制:附件格式放开,修复下载与盖章预览
- 通用:导入提示不换行、表格序号兜底
Co-Authored-By: Claude Code <noreply@anthropic.com>
| | |
| | | .template-tip { |
| | | display: flex; |
| | | align-items: center; |
| | | gap: 6px; |
| | | flex-wrap: nowrap; |
| | | gap: 4px; |
| | | margin-bottom: 12px; |
| | | padding: 8px 12px; |
| | | border-radius: 4px; |
| | |
| | | font-size: 13px; |
| | | } |
| | | |
| | | .template-tip > span { |
| | | white-space: nowrap; |
| | | } |
| | | |
| | | .template-tip .el-button { |
| | | flex-shrink: 0; |
| | | margin-left: 0; |
| | | } |
| | | |
| | | .upload-tips { |
| | | margin-top: 10px; |
| | | color: var(--el-text-color-secondary); |
| | |
| | | } |
| | | |
| | | function indexMethod(index) { |
| | | return (props.page.current - 1) * props.page.size + index + 1 |
| | | // æªä¼ page æ¶é»è®¤ current 为 0ï¼ä¼ç®åº -9ã-8 è¿ç±»è´åºå·ï¼è¿éå
åºä¸ºç¬¬ä¸é¡µ |
| | | const current = props.page && props.page.current > 0 ? props.page.current : 1 |
| | | const size = props.page && props.page.size > 0 ? props.page.size : 10 |
| | | return (current - 1) * size + index + 1 |
| | | } |
| | | |
| | | function onPagination({ page, limit }) { |
| | |
| | | <template> |
| | | <div class="up-pdf-stamp"> |
| | | <div v-if="isUpFile" class="work" style="display: flex;justify-content: space-between;align-items: center;margin-bottom: 10px;"> |
| | | <input ref="fileInput" type="file" accept="application/pdf" @change="handleFileUpload" /> |
| | | <input ref="fileInput" type="file" accept=".pdf,.doc,.docx,.xls,.xlsx,.ppt,.pptx,.wps,.et,.dps,.txt,.jpg,.jpeg,.png" @change="handleFileUpload" /> |
| | | <span v-if="fileName !== 'æä»¶å'" style="margin-left: 10px;font-size: 12px;color: var(--el-text-color-secondary);"> |
| | | 已鿩ï¼{{ fileName }}<template v-if="!isPdfFile">ï¼é PDF æä»¶æåæä»¶ä¸ä¼ ï¼ä¸æ¯æçç« ï¼</template> |
| | | </span> |
| | | </div> |
| | | <div v-if="canvasNumPages > 0" style="color: red;font-size: 12px;margin: 16px 0;">æç¤ºï¼å¨æä»¶èå´å
ï¼åå»é¼ æ çç« ï¼åå»é¼ æ å·²çå¥½ç« å¤å¯å é¤å½åç« </div> |
| | | <canvas |
| | |
| | | stampHeight: 80, // çç« é«åº¦ |
| | | stampsName: '', |
| | | stampsList: ['主任', 'è´¨éè´è´£äºº', 'ææ¯è´è´£äºº'], |
| | | fileName: 'æä»¶å' |
| | | fileName: 'æä»¶å', |
| | | selectedFile: null, // é PDF æä»¶ï¼ä¿çåå§ Fileï¼ç´æ¥ä¸ä¼ |
| | | isPdfFile: false |
| | | } |
| | | }, |
| | | methods: { |
| | |
| | | const file = event.target.files[0] |
| | | if (file.size > 20 * 1024 * 1024) { |
| | | this.$refs.fileInput.value = '' // æ¸
空æä»¶è¾å
¥æ¡å
容 |
| | | this.selectedFile = null |
| | | this.isPdfFile = false |
| | | this.fileName = 'æä»¶å' |
| | | return this.$message.error('æä»¶å¤§å°ä¸è½è¶
è¿20M') |
| | | } |
| | | this.lookFile(file) |
| | |
| | | this.stampsName = '综å室' |
| | | } |
| | | } |
| | | if (file && file.type === 'application/pdf') { |
| | | this.isPdfFile = !!file && (file.type === 'application/pdf' || /\.pdf$/i.test(file.name || '')) |
| | | if (this.isPdfFile) { |
| | | this.selectedFile = file |
| | | const reader = new FileReader() |
| | | reader.onload = e => { |
| | | const typedArray = new Uint8Array(e.target.result) |
| | | this.loadPDF(typedArray) |
| | | } |
| | | reader.readAsArrayBuffer(file) |
| | | } else if (this.isUpFile) { |
| | | // æä»¶æ¸
åä¸ä¼ æ¯æ Word/Excel/å¾ççæ ¼å¼ï¼ä¸åçç« ï¼éä¸åæåæä»¶ä¸ä¼ |
| | | this.selectedFile = file |
| | | this.pdfDoc = null |
| | | this.canvasNumPages = 0 |
| | | this.stamps = [] |
| | | this.contextList = [] |
| | | } else { |
| | | // å
¶ä»æµç¨ï¼æä»¶ç³è¯·ãåæ´ç³è¯·çï¼ä»åªå¤ç PDF |
| | | this.selectedFile = null |
| | | this.$message.error('è¯·éæ© PDF æä»¶') |
| | | } |
| | | }, |
| | |
| | | }, |
| | | // çæ PDF ç彿° |
| | | async generatePDF() { |
| | | // é PDFï¼ä¸çç« ï¼åæä»¶ç´æ¥ä¸ä¼ |
| | | if (this.selectedFile && !this.isPdfFile) { |
| | | return this.$emit('uploadPDF', this.selectedFile, this.fileName) |
| | | } |
| | | if (this.contextList.length === 0) { |
| | | this.$message({ message: '请å
ä¸ä¼ PDFæä»¶', type: 'error' }) |
| | | this.$message({ message: '请å
éæ©æä»¶', type: 'error' }) |
| | | this.$emit('uploadPDFErr') |
| | | return false |
| | | } |
| | |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="å¹è®è®²å¸:" prop="trainingLecturerId"> |
| | | <el-select v-model="trainingPlan.trainingLecturerId" |
| | | clearable filterable |
| | | placeholder="è¯·éæ©" size="small" style="width: 100%;"> |
| | | <el-option v-for="item in responsibleOptions" :key="item.id" :label="item.name" :value="item.id"> |
| | | </el-option> |
| | | </el-select> |
| | | <div style="display: flex; align-items: center; gap: 8px; width: 100%;"> |
| | | <el-radio-group v-model="lecturerType" size="small" @change="handleLecturerTypeChange"> |
| | | <el-radio-button label="internal">å
é¨äººå</el-radio-button> |
| | | <el-radio-button label="external">å¤è讲å¸</el-radio-button> |
| | | </el-radio-group> |
| | | <el-select v-if="lecturerType === 'internal'" v-model="trainingPlan.trainingLecturerId" |
| | | clearable filterable |
| | | placeholder="è¯·éæ©" size="small" style="flex: 1;"> |
| | | <el-option v-for="item in responsibleOptions" :key="item.id" :label="item.name" :value="item.id"> |
| | | </el-option> |
| | | </el-select> |
| | | <el-input v-else v-model="trainingPlan.trainingLecturerName" clearable |
| | | placeholder="请è¾å
¥å¤è讲å¸å§å" size="small" style="flex: 1;"></el-input> |
| | | </div> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | |
| | | return { |
| | | addTrainingPlanDia: false, |
| | | submitAddLoading: false, |
| | | lecturerType: 'internal', |
| | | trainingPlan: { |
| | | planId: '', |
| | | id: '', |
| | |
| | | participants: '', |
| | | trainingContent: '', |
| | | trainingLecturerId: '', |
| | | trainingLecturerName: '', |
| | | trainingMode: '', |
| | | holdingDepartment: '', |
| | | remarks: '', |
| | |
| | | trainingPlanRules: { |
| | | trainingDate: [{ required: true, message: 'è¯·éæ©å¹è®æ¥æ', trigger: 'change' }], |
| | | trainingContent: [{ required: true, message: '请è¾å
¥å¹è®å
容', trigger: 'blur' }], |
| | | trainingLecturerId: [{ required: true, message: 'è¯·éæ©å¹è®è®²å¸', trigger: 'blur' }], |
| | | }, |
| | | responsibleOptions: [], |
| | | operationType: '' |
| | |
| | | this.operationType = type |
| | | if (this.operationType === 'edit') { |
| | | this.trainingPlan = this.HaveJson(row) |
| | | this.lecturerType = row.trainingLecturerId ? 'internal' : (row.trainingLecturerName ? 'external' : 'internal') |
| | | } else { |
| | | this.lecturerType = 'internal' |
| | | this.trainingPlan = { |
| | | id: '', |
| | | trainingDate: '', |
| | |
| | | this.trainingPlan.planId = id |
| | | this.getUserList() |
| | | }, |
| | | handleLecturerTypeChange(type) { |
| | | if (type === 'internal') { |
| | | this.trainingPlan.trainingLecturerName = '' |
| | | } else { |
| | | this.trainingPlan.trainingLecturerId = null |
| | | } |
| | | }, |
| | | // æäº¤æ°å¢ |
| | | submitAdd() { |
| | | this.$refs.trainingPlanForm.validate((valid) => { |
| | | if (valid) { |
| | | if (this.lecturerType === 'internal' && !this.trainingPlan.trainingLecturerId) { |
| | | this.$message.warning('è¯·éæ©å¹è®è®²å¸') |
| | | return |
| | | } |
| | | if (this.lecturerType === 'external') { |
| | | const lecturerName = (this.trainingPlan.trainingLecturerName || '').trim() |
| | | if (!lecturerName) { |
| | | this.$message.warning('请è¾å
¥å¤è讲å¸å§å') |
| | | return |
| | | } |
| | | this.trainingPlan.trainingLecturerId = null |
| | | this.trainingPlan.trainingLecturerName = lecturerName |
| | | } else { |
| | | this.trainingPlan.trainingLecturerName = null |
| | | } |
| | | this.submitAddLoading = true |
| | | this.trainingPlan.planId = this.currentChangeRow.id |
| | | const personTrainingDetailed = this.trainingPlan |
| | |
| | | </div> |
| | | <div class="form_title"> |
| | | <el-row> |
| | | <el-col :span="7"> |
| | | <el-col :span="8"> |
| | | <span class="form_label">å¹è®å
容ï¼</span> |
| | | <span> {{ trainingForm.trainingContent }} </span> |
| | | </el-col> |
| | | <el-col :span="5"> |
| | | <el-col :span="4"> |
| | | <span class="form_label">ç¶æï¼</span> |
| | | <el-tag v-if="trainingForm.state === 1" type="success">已宿</el-tag> |
| | | <el-tag v-if="trainingForm.state === 2" type="warning">å¾
è¯ä»·</el-tag> |
| | | <el-tag v-if="trainingForm.state === 3" type="primary">æªå¼å§</el-tag> |
| | | </el-col> |
| | | <el-col :span="4"> |
| | | <el-col :span="5"> |
| | | <span class="form_label">å¹è®è®²å¸ï¼</span> |
| | | <span> {{ trainingForm.trainingLecturerName }} </span> |
| | | </el-col> |
| | | <el-col :span="7"> |
| | | <span class="form_label">å¹è®æ¥æï¼</span> |
| | | <span> {{ trainingForm.trainingDate || '-' }} </span> |
| | | </el-col> |
| | | </el-row> |
| | | <el-row style="margin: 15px 0"> |
| | | <el-form> |
| | | <el-form label-width="130px"> |
| | | <el-row> |
| | | <el-col :span="6"> |
| | | <el-form-item label="å¹è®æ¥æ:"> |
| | | <el-form-item label="å¼å§æ¶é´:"> |
| | | <el-date-picker v-model="trainingForm.openingTime" format="YYYY-MM-DD" :disabled="currentRow.state !== 3" |
| | | placeholder="éæ©æ¥æ" size="small" value-format="YYYY-MM-DD" |
| | | type="date" style="width: 60%"></el-date-picker> |
| | | placeholder="éæ©å®é
å¼å§æ¥æ" size="small" value-format="YYYY-MM-DD" |
| | | type="date" style="width: 100%"></el-date-picker> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="7"> |
| | | <el-col :span="9"> |
| | | <el-form-item label="å¹è®å°ç¹"> |
| | | <el-input v-model="trainingForm.placeTraining" :disabled="currentRow.state !== 3" :rows="2" placeholder="请è¾å
¥" |
| | | size="small" style="width: 60%" type="text"></el-input> |
| | | size="small" style="width: 100%" type="text"></el-input> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="6"> |
| | | <el-col :span="9"> |
| | | <el-form-item label="è¯ä»·äºº"> |
| | | <el-select v-model="trainingForm.assessmentUserId" :disabled="currentRow.state !== 3" placeholder="è¯·éæ©" size="small" style="width: 50%"> |
| | | <el-select v-model="trainingForm.assessmentUserId" :disabled="currentRow.state !== 3" placeholder="è¯·éæ©" size="small" style="width: 100%"> |
| | | <el-option v-for="item in userList" :key="item.id" :label="item.name" :value="item.id"></el-option> |
| | | </el-select> |
| | | </el-form-item> |
| | |
| | | <el-col :span="12"> |
| | | <el-form-item label="èæ ¸æ¹å¼"> |
| | | <el-input v-model="trainingForm.assessmentMethod" :disabled="currentRow.state !== 3" :rows="2" placeholder="请è¾å
¥" |
| | | size="small" style="width: 79%" type="textarea"></el-input> |
| | | size="small" style="width: 100%" type="textarea"></el-input> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | |
| | | <el-form-item label="è¯ä»·æ¶é´"> |
| | | <el-date-picker v-model="trainingForm.assessmentDate" :disabled="currentRow.state !== 2 || isDisabled" |
| | | type="date" format="YYYY-MM-DD" value-format="YYYY-MM-DD" size="small" |
| | | placeholder="éæ©æ¥æ"> |
| | | placeholder="éæ©æ¥æ" style="width: 100%"> |
| | | </el-date-picker> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="æ¬æ¬¡å¹è®ç»¼åè¯ä»·"> |
| | | <el-input v-model="trainingForm.comprehensiveAssessment" :disabled="currentRow.state !== 2 || isDisabled" :rows="2" placeholder="请è¾å
¥" |
| | | size="small" style="width: 68%" type="textarea"></el-input> |
| | | size="small" style="width: 100%" type="textarea"></el-input> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | |
| | | </div> |
| | | <el-divider>人å详æ
</el-divider> |
| | | <div> |
| | | <div class="items_center"> |
| | | <span>å§å</span> |
| | | <el-input v-model="userName" class="search" placeholder="请è¾å
¥" |
| | | size="small"></el-input> |
| | | <el-button size="small" type="primary" @click="getInfo">æ¥è¯¢</el-button> |
| | | </div> |
| | | <div class="items_btn"> |
| | | <el-button :disabled="currentRow.state === 1" size="small" type="primary" @click="addPerson">æ°å¢äººå</el-button> |
| | | <el-button :disabled="currentRow.state === 1" size="small" @click="batchDelete">æ¹éå é¤</el-button> |
| | | <div class="items_header"> |
| | | <div class="items_center"> |
| | | <span>å§å</span> |
| | | <el-input v-model="userName" class="person-search-input" placeholder="请è¾å
¥" |
| | | size="small" clearable @keyup.enter="getInfo"></el-input> |
| | | <el-button size="small" type="primary" @click="getInfo">æ¥è¯¢</el-button> |
| | | </div> |
| | | <div class="items_btn"> |
| | | <el-button :disabled="currentRow.state === 1" size="small" type="primary" @click="addPerson">æ°å¢äººå</el-button> |
| | | <el-button :disabled="currentRow.state === 1" size="small" @click="batchDelete">æ¹éå é¤</el-button> |
| | | </div> |
| | | </div> |
| | | <el-table :data="trainingTableData" height="calc(100vh - 30em)" stripe style="width: 100%" |
| | | border |
| | |
| | | </el-form> |
| | | </div> |
| | | <div v-if="selectUserDia" class="body" style="height: 60vh;"> |
| | | <lims-table :tableData="tableData1" :column="column1" |
| | | <lims-table :tableData="tableData1" :column="column1" :page="page1" |
| | | :isSelection="true" :handleSelectionChange="selectMethod" |
| | | height="500" :tableLoading="tableLoading1"></lims-table> |
| | | </div> |
| | |
| | | } |
| | | |
| | | |
| | | .items_header { |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: space-between; |
| | | margin-bottom: 10px; |
| | | } |
| | | .items_center { |
| | | float: left; |
| | | width: 50%; |
| | | text-align: left; |
| | | display: flex; |
| | | align-items: center; |
| | | gap: 8px; |
| | | } |
| | | .items_center > span { |
| | | line-height: 24px; |
| | | white-space: nowrap; |
| | | } |
| | | .items_center .person-search-input { |
| | | width: 200px; |
| | | } |
| | | .items_btn { |
| | | text-align: right; |
| | | width: 50%; |
| | | float: right; |
| | | margin-bottom: 1em; |
| | | display: flex; |
| | | align-items: center; |
| | | gap: 8px; |
| | | } |
| | | </style> |
| | |
| | | </el-option> |
| | | </el-select> |
| | | </div> |
| | | <div style="display: flex; align-items: center; gap: 8px; margin-bottom: 10px; padding: 8px 12px; border-radius: 4px; background: var(--el-color-primary-light-9); font-size: 13px;"> |
| | | <span>ç¬¬ä¸æ¬¡å¯¼å
¥è¯·å
ä¸è½½æ¨¡æ¿ï¼ææ¨¡æ¿æ ¼å¼å¡«åååä¸ä¼ ã</span> |
| | | <el-button link type="primary" @click="downloadTemplate">ä¸è½½å¯¼å
¥æ¨¡æ¿</el-button> |
| | | <div style="display: flex; align-items: center; flex-wrap: nowrap; gap: 4px; margin-bottom: 10px; padding: 8px 12px; border-radius: 4px; background: var(--el-color-primary-light-9); font-size: 13px;"> |
| | | <span style="white-space: nowrap;">ç¬¬ä¸æ¬¡å¯¼å
¥è¯·å
ä¸è½½æ¨¡æ¿ï¼ææ¨¡æ¿æ ¼å¼å¡«åååä¸ä¼ ã</span> |
| | | <el-button link type="primary" style="flex-shrink: 0; margin-left: 0;" @click="downloadTemplate">ä¸è½½å¯¼å
¥æ¨¡æ¿</el-button> |
| | | </div> |
| | | <div style="margin: 0 auto;"> |
| | | <el-upload ref="upload" |
| | |
| | | </div> |
| | | <div style="margin-top: 10px; color: var(--el-text-color-secondary); font-size: 12px; line-height: 20px;"> |
| | | <div>æ¯ææ ¼å¼ï¼.xlsã.xlsxï¼å个æä»¶ä¸è¶
è¿10MBã</div> |
| | | <div>å¹è®è®²å¸å¿
须填åç³»ç»ä¸å·²åå¨ç人åå§åï¼å¦åæ´æ¹å¯¼å
¥å¤±è´¥ã</div> |
| | | <div>å¹è®è®²å¸å¡«åç³»ç»å
人åå§åæ¶èªå¨å
³èï¼å¤è讲å¸ç´æ¥å¡«åå§åï¼æææ¬ä¿åã</div> |
| | | <div>å¹è®æ¶é´å»ºè®®å¡«å YYYY-MM-DDï¼è¯¾æ¶å¡«åæ´æ°ã</div> |
| | | <div>导å
¥åªè¯»å模æ¿ç第ä¸ä¸ªå·¥ä½è¡¨ã</div> |
| | | </div> |
| | |
| | | { |
| | | label: 'å¹è®å¤§ç±»', |
| | | prop: 'trainingCategory', |
| | | width: '100px', |
| | | minWidth: '100px', |
| | | },{ |
| | | label: 'å¹è®ç®æ ', |
| | | prop: 'trainingObjectives', |
| | | width: '100px', |
| | | minWidth: '100px', |
| | | }, { |
| | | label: 'å¹è®å
容', |
| | | prop: 'trainingContent', |
| | | width: '100px', |
| | | minWidth: '100px', |
| | | }, { |
| | | label: 'å¹è®æ¹å¼', |
| | | prop: 'trainingMode', |
| | | width: '100px', |
| | | minWidth: '100px', |
| | | }, { |
| | | dataType: 'tag', |
| | | label: '课ç¨ç¶æ', |
| | | width: '100px', |
| | | minWidth: '100px', |
| | | prop: 'state', |
| | | formatData: (params) => { |
| | | if (params == 1) { |
| | |
| | | }, { |
| | | label: 'åå 对象', |
| | | prop: 'participants', |
| | | width: '100px', |
| | | minWidth: '100px', |
| | | }, { |
| | | label: '举åé¨é¨', |
| | | prop: 'holdingDepartment', |
| | | width: '100px', |
| | | minWidth: '100px', |
| | | }, { |
| | | label: 'å¹è®è®²å¸', |
| | | prop: 'trainingLecturerName', |
| | | width: '100px', |
| | | minWidth: '100px', |
| | | }, { |
| | | label: 'å¹è®æ¥æ', |
| | | prop: 'trainingDate', |
| | | width: '100px', |
| | | minWidth: '100px', |
| | | }, { |
| | | label: 'å¼å§æ¶é´', |
| | | prop: 'openingTime', |
| | | width: '100px', |
| | | minWidth: '100px', |
| | | }, { |
| | | label: '课æ¶', |
| | | prop: 'classHour', |
| | | width: '100px', |
| | | minWidth: '100px', |
| | | }, { |
| | | label: '夿³¨', |
| | | prop: 'remarks', |
| | | width: '100px', |
| | | minWidth: '100px', |
| | | }, { |
| | | dataType: 'action', |
| | | width: '200', |
| | | minWidth: '200px', |
| | | label: 'æä½', |
| | | fixed: 'right', |
| | | operation: [ |
| | |
| | | // ä¸è½½å¹´åº¦è®¡å导å
¥æ¨¡æ¿ |
| | | downloadTemplate() { |
| | | const link = document.createElement('a') |
| | | link.href = '/import-templates/人åå¹è®å¹´åº¦è®¡å导å
¥æ¨¡æ¿.xlsx?v=2' |
| | | link.href = '/import-templates/人åå¹è®å¹´åº¦è®¡å导å
¥æ¨¡æ¿.xlsx?v=3' |
| | | link.download = '人åå¹è®å¹´åº¦è®¡å导å
¥æ¨¡æ¿.xlsx' |
| | | document.body.appendChild(link) |
| | | link.click() |
| | |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="è´¨æ§æ¹å¼" prop="monitorWay"> |
| | | <el-input v-model="form.monitorWay" size="small" clearable placeholder="å¦ç²æ ·æ£æµãäººåæ¯å¯¹ãçç£æ£æ¥" /> |
| | | <el-form-item label="è´¨æ§æ¹å¼" prop="monitorWayList"> |
| | | <el-select v-model="form.monitorWayList" multiple filterable allow-create default-first-option |
| | | size="small" style="width: 100%" placeholder="å¯å¤éï¼ä¹å¯è¾å
¥å
¶ä»æ¹å¼"> |
| | | <el-option v-for="item in monitorWayOptions" :key="item" :label="item" :value="item" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | |
| | | // è¿éåæ¾æ°æ® |
| | | return { |
| | | formDia: false, |
| | | // LMSY-CX30-2024ãè´¨éæ§å¶ç¨åºã4.1 è§å®ç常ç¨è´¨éæ§å¶ææ®µï¼å¯å¤é |
| | | monitorWayOptions: [ |
| | | 'äººåæ¯å¯¹', 'è®¾å¤æ¯å¯¹', 'æ¹æ³æ¯å¯¹', 'ç²æ ·æ£æµ', |
| | | 'çæ ·åæµ', 'æ åç©è´¨çæ§', 'å®éªå®¤é´æ¯å¯¹', 'è½åéªè¯', |
| | | ], |
| | | form: { |
| | | qualityMonitorDetailsId: '', |
| | | sortNo: null, |
| | |
| | | budget: '', |
| | | organization: '', |
| | | monitorWay: '', |
| | | monitorWayList: [], |
| | | itemKind: 'OTHER', |
| | | standardMethodId: null, |
| | | templateId: null, |
| | |
| | | rules: { |
| | | monitorProject: [{ required: true, message: '请è¾å
¥è´¨æ§é¡¹ç®', trigger: 'blur' }], |
| | | technicalStandardText: [{ required: true, message: '请è¾å
¥ææ¯æ å', trigger: 'blur' }], |
| | | monitorWay: [{ required: true, message: '请è¾å
¥è´¨æ§æ¹å¼', trigger: 'blur' }], |
| | | monitorWayList: [{ required: true, type: 'array', min: 1, message: 'è¯·éæ©è´¨æ§æ¹å¼', trigger: 'change' }], |
| | | completionMonth: [{ required: true, message: 'è¯·éæ©å®ææ¶é', trigger: 'change' }], |
| | | responsibleUserId: [{ required: true, message: 'è¯·éæ©è´è´£äºº', trigger: 'change' }], |
| | | templateIds: [{ required: true, type: 'array', min: 1, message: '请è³å°éæ©ä¸ä¸ªåå§è®°å½æ¨¡æ¿', trigger: 'change' }], |
| | |
| | | openDia(type, row) { |
| | | this.formDia = true |
| | | this.operationType = type |
| | | this.form.monitorWayList = [] |
| | | this.templateOptions = [] |
| | | this.allTemplateOptions = [] |
| | | this.templateBindingLoading = false |
| | |
| | | const templateIds = row.templateIds |
| | | ? String(row.templateIds).split(',').filter(Boolean).map(id => Number(id)) |
| | | : (row.templateId ? [Number(row.templateId)] : []) |
| | | this.form = {...row, itemKind: row.itemKind || 'OTHER', templateIds} |
| | | this.form = { |
| | | ...row, |
| | | itemKind: row.itemKind || 'OTHER', |
| | | templateIds, |
| | | monitorWayList: row.monitorWay ? String(row.monitorWay).split(/[ã,ï¼;ï¼]/).map(item => item.trim()).filter(Boolean) : [], |
| | | } |
| | | }, |
| | | handleStandardChange(id) { |
| | | const standard = this.standardOptions.find(item => Number(item.id) === Number(id)) |
| | |
| | | : [] |
| | | entity.templateIds = templateIds.join(',') || null |
| | | entity.templateId = templateIds.length ? templateIds[0] : null |
| | | entity.monitorWay = (this.form.monitorWayList || []).map(item => item.trim()).filter(Boolean).join('ã') |
| | | delete entity.monitorWayList |
| | | entity.reviewerUserId = null |
| | | return entity |
| | | }, |
| | |
| | | <div> |
| | | <el-dialog :close-on-click-modal="false" :close-on-press-escape="false" |
| | | v-model="formDia" |
| | | title="è´¨éçæ§è¯ä»·" |
| | | title="è´¨éæ§å¶è¯ä»·è®°å½" |
| | | width="60%" @close="closeEvaDia"> |
| | | <el-alert type="info" :closable="false" show-icon style="margin-bottom: 12px" |
| | | title="æ£æµå®¤ä¸»ä»»ç»ç»è¯ä»·å¹¶å¡«åæ¬è®°å½ï¼ææ¯è´è´£äººå®¡æ ¸ï¼è¯å®¡ç»è®ºå¦ééåæªæ½ï¼æãçº æ£æªæ½ç¨åºãæ§è¡å¹¶çåè®°å½ã" /> |
| | | <el-steps :active="currentStep" align-center finish-status="success"> |
| | | <el-step title="çæ§è®¡å宿½æ
åµ" @click="setStep(0)"></el-step> |
| | | <el-step title="çæ§ç»æè¯ä»·" @click="setStep(1)"></el-step> |
| | |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-form> |
| | | <div style="display: flex; align-items: center; gap: 8px; margin-bottom: 10px; padding: 8px 12px; border-radius: 4px; background: var(--el-color-primary-light-9); font-size: 13px;"> |
| | | <span>ç¬¬ä¸æ¬¡å¯¼å
¥è¯·å
ä¸è½½æ¨¡æ¿ï¼ææ¨¡æ¿æ ¼å¼å¡«åååä¸ä¼ ã</span> |
| | | <el-button link type="primary" @click="downloadTemplate">ä¸è½½å¯¼å
¥æ¨¡æ¿</el-button> |
| | | <div style="display: flex; align-items: center; flex-wrap: nowrap; gap: 4px; margin-bottom: 10px; padding: 8px 12px; border-radius: 4px; background: var(--el-color-primary-light-9); font-size: 13px;"> |
| | | <span style="white-space: nowrap;">ç¬¬ä¸æ¬¡å¯¼å
¥è¯·å
ä¸è½½æ¨¡æ¿ï¼ææ¨¡æ¿æ ¼å¼å¡«åååä¸ä¼ ã</span> |
| | | <el-button link type="primary" style="flex-shrink: 0; margin-left: 0;" @click="downloadTemplate">ä¸è½½å¯¼å
¥æ¨¡æ¿</el-button> |
| | | </div> |
| | | <div style="margin: 0 auto;"> |
| | | <el-upload ref="upload" :action="action" :auto-upload="false" v-model:file-list="fileList" :headers="uploadHeader" |
| | |
| | | downloadTemplate() { |
| | | const link = document.createElement('a') |
| | | // å¸¦çæ¬åæ°é¿å
æµè§å¨ç»§ç»ä½¿ç¨ç¼åéçæ§æ¨¡æ¿ |
| | | link.href = '/import-templates/è´¨éçæ§è®¡å导å
¥æ¨¡æ¿.xlsx?v=2' |
| | | link.href = '/import-templates/è´¨éçæ§è®¡å导å
¥æ¨¡æ¿.xlsx?v=3' |
| | | link.download = 'è´¨éçæ§è®¡å导å
¥æ¨¡æ¿.xlsx' |
| | | document.body.appendChild(link) |
| | | link.click() |
| | |
| | | exportQualityMonitorRatify({ qualityMonitorDetailsId: this.download.qualityMonitorDetailsId }).then(res => { |
| | | this.outLoading = false |
| | | const blob = new Blob([res], { type: 'application/msword' }); |
| | | this.$download.saveAs(blob, 'è´¨éçæ§å®æ½è®¡å.docx') |
| | | this.$download.saveAs(blob, 'è´¨éæ§å¶æªæ½.docx') |
| | | }) |
| | | }, |
| | | // 宿æ¥åå¯¼åº |
| | |
| | | exportQualityMonitorEvaluate({ qualityMonitorDetailsId: this.download.qualityMonitorDetailsId }).then(res => { |
| | | this.outLoading = false |
| | | const blob = new Blob([res], { type: 'application/msword' }); |
| | | this.$download.saveAs(blob, 'è´¨éçæ§è¯ä»·.docx') |
| | | this.$download.saveAs(blob, 'è´¨éæ§å¶è¯ä»·è®°å½.docx') |
| | | }) |
| | | }, |
| | | delYearPlanDetail(row) { |
| | |
| | | </el-select> |
| | | </el-form-item> |
| | | </el-form> |
| | | <div style="display: flex; align-items: center; gap: 8px; margin-bottom: 10px; padding: 8px 12px; border-radius: 4px; background: var(--el-color-primary-light-9); font-size: 13px;"> |
| | | <span>ç¬¬ä¸æ¬¡å¯¼å
¥è¯·å
ä¸è½½æ¨¡æ¿ï¼ææ¨¡æ¿æ ¼å¼å¡«åååä¸ä¼ ã</span> |
| | | <el-button link type="primary" @click="downloadTemplate">ä¸è½½å¯¼å
¥æ¨¡æ¿</el-button> |
| | | <div style="display: flex; align-items: center; flex-wrap: nowrap; gap: 4px; margin-bottom: 10px; padding: 8px 12px; border-radius: 4px; background: var(--el-color-primary-light-9); font-size: 13px;"> |
| | | <span style="white-space: nowrap;">ç¬¬ä¸æ¬¡å¯¼å
¥è¯·å
ä¸è½½æ¨¡æ¿ï¼ææ¨¡æ¿æ ¼å¼å¡«åååä¸ä¼ ã</span> |
| | | <el-button link type="primary" style="flex-shrink: 0; margin-left: 0;" @click="downloadTemplate">ä¸è½½å¯¼å
¥æ¨¡æ¿</el-button> |
| | | </div> |
| | | <div style="margin: 0 auto;"> |
| | | <el-upload ref="upload1" :action="action" :auto-upload="false" :file-list="fileList" :headers="uploadHeader" |
| | |
| | | <div class="search_thing"> |
| | | <div class="search_label">ä¸ä¼ éä»¶ï¼</div> |
| | | <div class="search_input"><el-upload style="margin: 8px 0 0px 50px;" action="#" :auto-upload="false" |
| | | :multiple="false" accept='.pdf' :on-change="handleChangeUpload" v-if="addDialogVisible"> |
| | | :multiple="false" accept='.pdf,.doc,.docx,.xls,.xlsx,.ppt,.pptx,.wps,.et,.dps,.txt,.jpg,.jpeg,.png' :on-change="handleChangeUpload" v-if="addDialogVisible"> |
| | | <el-button size="small" type="primary">ä¸ä¼ éä»¶</el-button> |
| | | </el-upload></div> |
| | | </div> |
| | |
| | | handleCheck0(row) { |
| | | this.checkStampDialogVisible = false |
| | | this.checkDialogVisible = true |
| | | checkManageDocumentControlledPdf({ id: row.id }).then(res => { |
| | | const blob = new Blob([res]); |
| | | checkManageDocumentControlledPdf({ id: row.id }).then(async res => { |
| | | // å端转 PDF 失败æ¶ä¼è¿å JSON é误ï¼blob é就䏿¯ PDF |
| | | if (res.type && res.type.includes('json')) { |
| | | const data = JSON.parse(await res.text()) |
| | | this.$message.error(data.msg || 'é件转æ¢é¢è§å¤±è´¥') |
| | | this.checkDialogVisible = false |
| | | return |
| | | } |
| | | const blob = new Blob([res], { type: 'application/pdf' }); |
| | | const file = new File([blob], row.name, { type: 'application/pdf' }) |
| | | this.$refs.UpPdfStamp.lookFile(file, this.currentStamp) |
| | | }).catch(err => { |
| | | this.checkDialogVisible = false |
| | | console.log(err) |
| | | }) |
| | | }, |
| | | handleDown(row) { |
| | | if (!row.url) return this.$message.warning('æä»¶æªä¸ä¼ ') |
| | | this.$download.saveAs(row.url, row.url); |
| | | const fileName = row.url.replace(/^[0-9a-fA-F-]{36}_/, '') |
| | | this.$download.saveAs(this.javaApi + '/word/' + row.url, fileName); |
| | | }, |
| | | async uploadPDF(pdfBlob) { |
| | | const formData = new FormData(); |
| | | formData.append('file', pdfBlob, this.fileName + '.pdf'); // æä»¶å段 |
| | | formData.append('file', pdfBlob, (this.currentInfo.name || 'controlled') + '.pdf'); // æä»¶å段 |
| | | formData.append('id', this.currentInfo.id); // æä»¶ååæ®µ |
| | | formData.append('state', this.type); // æä»¶ååæ®µ |
| | | formData.append('writer', this.currentInfo.writer); // æä»¶ååæ®µ |
| | |
| | | <div class="search_thing"> |
| | | <div class="search_label">ä¸ä¼ éä»¶ï¼</div> |
| | | <div class="search_input"><el-upload :auto-upload="false" :multiple="false" :on-change="handleChangeUpload" |
| | | accept='.pdf,.doc,.docx,.xls,.xlsx' action="#" style="margin: 8px 0 0px 50px;"> |
| | | accept='.pdf,.doc,.docx,.xls,.xlsx,.ppt,.pptx,.wps,.et,.dps,.txt,.jpg,.jpeg,.png' action="#" style="margin: 8px 0 0px 50px;"> |
| | | <el-button size="small" type="primary">ä¸ä¼ éä»¶</el-button> |
| | | </el-upload></div> |
| | | </div> |
| | |
| | | <div class="search_thing" style="width: 100%;"> |
| | | <div class="search_label">ç¸å
³éä»¶ï¼</div> |
| | | <div class="search_input"><el-upload v-if="addDialogVisible" :auto-upload="false" :multiple="false" |
| | | :on-change="handleChangeUpload" accept='.pdf' action="#" style="margin: 8px 0 0px 50px;"> |
| | | :on-change="handleChangeUpload" accept='.pdf,.doc,.docx,.xls,.xlsx,.ppt,.pptx,.wps,.et,.dps,.txt,.jpg,.jpeg,.png' action="#" style="margin: 8px 0 0px 50px;"> |
| | | <el-button size="small" type="primary">ä¸ä¼ éä»¶</el-button> |
| | | </el-upload></div> |
| | | </div> |
| | |
| | | this.getCurrentFile(alterBeforeCode) |
| | | this.addDialogVisible = true |
| | | this.diaLoading = true |
| | | checkManageDocumentAlterPdf({ id: row.id }).then(res => { |
| | | checkManageDocumentAlterPdf({ id: row.id }).then(async res => { |
| | | // |
| | | this.diaLoading = false |
| | | const blob = new Blob([res]); |
| | | // å端转 PDF 失败æ¶ä¼è¿å JSON é误ï¼blob é就䏿¯ PDF |
| | | if (res.type && res.type.includes('json')) { |
| | | const data = JSON.parse(await res.text()) |
| | | this.$message.error(data.msg || 'é件转æ¢é¢è§å¤±è´¥') |
| | | this.addDialogVisible = false |
| | | return |
| | | } |
| | | const blob = new Blob([res], { type: 'application/pdf' }); |
| | | const file = new File([blob], row.name, { type: 'application/pdf' }) |
| | | this.$refs.UpPdfStamp.lookFile(file) |
| | | this.currentInfo = row |
| | | }).catch(err => { |
| | | this.diaLoading = false |
| | | this.addDialogVisible = false |
| | | console.log(err) |
| | | }) |
| | | }, |
| | |
| | | :height="'calc(100vh - 290px)'" @pagination="pagination"></lims-table> |
| | | </div> |
| | | <el-dialog title="ä¸ä¼ " v-model="addDialogVisible" width="1000px" top="3vh"> |
| | | <div style="margin-bottom: 10px; padding: 8px 12px; border-radius: 4px; background: var(--el-color-primary-light-9); font-size: 13px; color: var(--el-text-color-regular);"> |
| | | æ¯æ PDFãWordï¼.doc/.docxï¼ãExcelï¼.xls/.xlsxï¼ãPPTãå¾ççå¸¸è§æ ¼å¼ï¼å个æä»¶ä¸è¶
è¿ 20MBãPDF å¯é¢è§å¹¶çç« ï¼å
¶ä»æ ¼å¼æåæä»¶ä¸ä¼ ï¼ä¸çç« ã |
| | | </div> |
| | | <UpPdfStamp ref="UpPdfStamp" v-if="addDialogVisible" @uploadPDF="uploadPDF" :isUpFile="true" |
| | | @uploadPDFErr="uploadPDFErr"></UpPdfStamp> |
| | | <template #footer><span class="dialog-footer"> |
| | |
| | | // ä¸è½½æä»¶ |
| | | handleDown(row) { |
| | | if (!row.url) return this.$message.warning('æä»¶æªä¸ä¼ ') |
| | | this.$download.saveAs(row.url, row.url); |
| | | // å端ä¿åçæ¯ word ç®å½ä¸ç uuid_åæä»¶åï¼ä¸è½½æ¶èµ°éæèµæºå°å并廿 uuid åç¼ |
| | | const fileName = row.url.replace(/^[0-9a-fA-F-]{36}_/, '') |
| | | this.$download.saveAs(this.javaApi + '/word/' + row.url, fileName); |
| | | }, |
| | | // æ¥çæä»¶ |
| | | handleLook(row) { |