提交
This commit is contained in:
1 parent
320f7e75f0
commit
53d3b21f57
41 files changed
+10854
-7752
No files matched your search
@@ -39,7 +39,7 @@
|
||||
<div class="section-title">
|
||||
<div class="section-title-left"><h3>基本信息</h3></div>
|
||||
</div>
|
||||
<div class="form-grid">
|
||||
<div class="form-grid form-grid--col4">
|
||||
<el-form-item label="协议号" prop="protocolNo">
|
||||
<el-select
|
||||
v-model="formData.protocolNoId"
|
||||
@@ -110,89 +110,89 @@
|
||||
</el-form-item>
|
||||
</div>
|
||||
|
||||
<!-- 协议附件 -->
|
||||
<el-form-item label="协议附件" style="margin-top: 18px">
|
||||
<div class="attachment-upload">
|
||||
<div class="attachment-list">
|
||||
<el-upload
|
||||
ref="attachmentUploadRef"
|
||||
:auto-upload="false"
|
||||
:show-file-list="false"
|
||||
:multiple="true"
|
||||
accept="image/*,video/*,.pdf,.doc,.docx,.xls,.xlsx,.ppt,.pptx,.txt,.zip,.rar"
|
||||
@change="handleAttachmentChange"
|
||||
>
|
||||
<div class="upload-card">
|
||||
<el-icon :size="28" color="#10b981"><Plus /></el-icon>
|
||||
<span class="upload-card-text">上传附件</span>
|
||||
</div>
|
||||
</el-upload>
|
||||
<div
|
||||
v-for="(file, index) in formData.attachments"
|
||||
:key="file.id || index"
|
||||
class="attachment-item"
|
||||
:class="{ 'is-uploading': file._uploading }"
|
||||
>
|
||||
<div
|
||||
class="attachment-preview"
|
||||
@click="
|
||||
isImageFile(file) && file.fileUrl
|
||||
? handlePreviewAttachment(file)
|
||||
: undefined
|
||||
"
|
||||
<div class="form-grid form-grid--col2 basic-remark-attach-row">
|
||||
<el-form-item label="备注">
|
||||
<el-input
|
||||
v-model="formData.remark"
|
||||
type="textarea"
|
||||
:rows="3"
|
||||
placeholder="可选"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="协议附件">
|
||||
<div class="attachment-upload">
|
||||
<div class="attachment-list">
|
||||
<el-upload
|
||||
ref="attachmentUploadRef"
|
||||
:auto-upload="false"
|
||||
:show-file-list="false"
|
||||
:multiple="true"
|
||||
accept="image/*,video/*,.pdf,.doc,.docx,.xls,.xlsx,.ppt,.pptx,.txt,.zip,.rar"
|
||||
@change="handleAttachmentChange"
|
||||
>
|
||||
<template v-if="isImageFile(file) && file.fileUrl">
|
||||
<img :src="fullFileUrl(file.fileUrl)" :alt="file.fileName" />
|
||||
</template>
|
||||
<template v-else-if="isImageFile(file) && !file.fileUrl">
|
||||
<el-icon :size="24" color="#94a3b8"><Loading /></el-icon>
|
||||
</template>
|
||||
<template v-else-if="isVideoFile(file)">
|
||||
<div class="attachment-preview__icon attachment-preview__icon--video">
|
||||
<el-icon :size="28"><VideoPlay /></el-icon>
|
||||
<div class="upload-card">
|
||||
<el-icon :size="28" color="#10b981"><Plus /></el-icon>
|
||||
<span class="upload-card-text">上传附件</span>
|
||||
</div>
|
||||
</el-upload>
|
||||
<div
|
||||
v-for="(file, index) in formData.attachments"
|
||||
:key="file.id || index"
|
||||
class="attachment-item"
|
||||
:class="{ 'is-uploading': file._uploading }"
|
||||
>
|
||||
<div
|
||||
class="attachment-preview"
|
||||
@click="
|
||||
isImageFile(file) && file.fileUrl
|
||||
? handlePreviewAttachment(file)
|
||||
: undefined
|
||||
"
|
||||
>
|
||||
<template v-if="isImageFile(file) && file.fileUrl">
|
||||
<img :src="fullFileUrl(file.fileUrl)" :alt="file.fileName" />
|
||||
</template>
|
||||
<template v-else-if="isImageFile(file) && !file.fileUrl">
|
||||
<el-icon :size="24" color="#94a3b8"><Loading /></el-icon>
|
||||
</template>
|
||||
<template v-else-if="isVideoFile(file)">
|
||||
<div class="attachment-preview__icon attachment-preview__icon--video">
|
||||
<el-icon :size="28"><VideoPlay /></el-icon>
|
||||
</div>
|
||||
</template>
|
||||
<template v-else>
|
||||
<div class="attachment-preview__icon attachment-preview__icon--doc">
|
||||
<el-icon :size="28"><Document /></el-icon>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
<div class="attachment-footer">
|
||||
<span class="attachment-name" :title="file.fileName">{{
|
||||
file.fileName
|
||||
}}</span>
|
||||
<div class="attachment-actions">
|
||||
<el-icon
|
||||
class="action-btn"
|
||||
title="下载"
|
||||
@click="handleDownloadAttachment(file)"
|
||||
><Download
|
||||
/></el-icon>
|
||||
<el-icon
|
||||
class="action-btn action-btn--danger"
|
||||
title="删除"
|
||||
@click="removeAttachment(index)"
|
||||
><Delete
|
||||
/></el-icon>
|
||||
</div>
|
||||
</template>
|
||||
<template v-else>
|
||||
<div class="attachment-preview__icon attachment-preview__icon--doc">
|
||||
<el-icon :size="28"><Document /></el-icon>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
<div class="attachment-footer">
|
||||
<span class="attachment-name" :title="file.fileName">{{
|
||||
file.fileName
|
||||
}}</span>
|
||||
<div class="attachment-actions">
|
||||
<el-icon
|
||||
class="action-btn"
|
||||
title="下载"
|
||||
@click="handleDownloadAttachment(file)"
|
||||
><Download
|
||||
/></el-icon>
|
||||
<el-icon
|
||||
class="action-btn action-btn--danger"
|
||||
title="删除"
|
||||
@click="removeAttachment(index)"
|
||||
><Delete
|
||||
/></el-icon>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<span class="upload-hint"
|
||||
>支持图片、视频、PDF、Word、Excel等文件,单文件不超过 20MB</span
|
||||
>
|
||||
</div>
|
||||
<span class="upload-hint"
|
||||
>支持图片、视频、PDF、Word、Excel等文件,单文件不超过 20MB</span
|
||||
>
|
||||
</div>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="备注">
|
||||
<el-input
|
||||
v-model="formData.remark"
|
||||
type="textarea"
|
||||
:rows="3"
|
||||
placeholder="可选"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-form-item>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -527,7 +527,7 @@
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-grid">
|
||||
<div class="form-grid form-grid--col4">
|
||||
<el-form-item label="样本类型">
|
||||
<el-select
|
||||
v-model="row.sampleTypeId"
|
||||
@@ -749,7 +749,7 @@
|
||||
<!-- 缴费主表 -->
|
||||
<div class="payment-main-card">
|
||||
<div class="section-subtitle">缴费主表</div>
|
||||
<div class="form-grid">
|
||||
<div class="form-grid form-grid--col5">
|
||||
<el-form-item label="缴费模式">
|
||||
<el-radio-group v-model="formData.payMode" @change="onPayModeChange">
|
||||
<el-radio label="dj">趸交</el-radio>
|
||||
@@ -872,7 +872,7 @@
|
||||
</div>
|
||||
<div v-show="detail._expanded" class="detail-body">
|
||||
<div class="detail-body-inner">
|
||||
<div class="form-grid">
|
||||
<div class="form-grid form-grid--col4">
|
||||
<el-form-item label="应缴费金额(元)">
|
||||
<el-input
|
||||
v-model="detail.dueAmount"
|
||||
@@ -3008,6 +3008,22 @@
|
||||
&--col2 {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
|
||||
&--col4 {
|
||||
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||||
}
|
||||
|
||||
&--col5 {
|
||||
grid-template-columns: repeat(5, minmax(0, 1fr));
|
||||
}
|
||||
|
||||
:deep(.el-form-item) {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.basic-remark-attach-row {
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
.form-col-span-2 {
|
||||
@@ -3516,6 +3532,18 @@
|
||||
&--col2 {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
&--col4 {
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
}
|
||||
|
||||
&--col5 {
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
}
|
||||
|
||||
&--col2.basic-remark-attach-row {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
.client-sub-cards {
|
||||
|
||||
@@ -1877,6 +1877,8 @@
|
||||
/* 储存证:储存证附件 / 制作状态 / 制作日期同一行(在协议附件行下方) */
|
||||
.storage-cert-info-row {
|
||||
margin-top: 14px;
|
||||
padding-top: 14px;
|
||||
border-top: 1px dashed #e2e8f0;
|
||||
}
|
||||
|
||||
.storage-cert-attachments-item {
|
||||
|
||||
@@ -249,7 +249,7 @@
|
||||
<el-dialog
|
||||
v-model="dialogVisible"
|
||||
:title="dialogTitle"
|
||||
width="960px"
|
||||
width="1080px"
|
||||
class="dialog-form-layout"
|
||||
align-center
|
||||
destroy-on-close
|
||||
@@ -264,7 +264,7 @@
|
||||
scroll-to-error
|
||||
:scroll-into-view-options="{ behavior: 'smooth', block: 'center' }"
|
||||
>
|
||||
<div class="form-grid-3">
|
||||
<div class="form-grid-4">
|
||||
<el-form-item label="产品编码" prop="productCode">
|
||||
<el-input
|
||||
v-model="formData.productCode"
|
||||
|
||||
@@ -334,7 +334,7 @@
|
||||
</div>
|
||||
<div class="route-config-right-body">
|
||||
<el-form label-position="top" class="route-config-step-form">
|
||||
<div class="form-grid-2">
|
||||
<div class="form-grid-4">
|
||||
<el-form-item label="工序名称">
|
||||
<el-input v-model="selectedStep.processName" @change="saveStepBasic" />
|
||||
</el-form-item>
|
||||
@@ -353,7 +353,13 @@
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="描述" class="form-col-span-2">
|
||||
<el-form-item label="设备名称">
|
||||
<el-input v-model="selectedStep.equipmentName" @change="saveStepBasic" />
|
||||
</el-form-item>
|
||||
<el-form-item label="预计时长(min)">
|
||||
<el-input v-model="selectedStep.estimatedDuration" placeholder="如 2h" @change="saveStepBasic" />
|
||||
</el-form-item>
|
||||
<el-form-item label="描述" class="step-form-col-half">
|
||||
<el-input
|
||||
v-model="selectedStep.description"
|
||||
type="textarea"
|
||||
@@ -362,13 +368,7 @@
|
||||
@change="saveStepBasic"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="设备名称">
|
||||
<el-input v-model="selectedStep.equipmentName" @change="saveStepBasic" />
|
||||
</el-form-item>
|
||||
<el-form-item label="预计时长(min)">
|
||||
<el-input v-model="selectedStep.estimatedDuration" placeholder="如 2h" @change="saveStepBasic" />
|
||||
</el-form-item>
|
||||
<el-form-item label="关联SOP" class="form-col-span-2">
|
||||
<el-form-item label="关联SOP" class="step-form-col-half">
|
||||
<el-select
|
||||
v-model="sopIdsArr"
|
||||
multiple
|
||||
@@ -384,6 +384,16 @@
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<div
|
||||
v-if="sopIdsArr.length"
|
||||
class="step-sop-attachment-row step-form-col-full"
|
||||
>
|
||||
<ProtocolAttachmentReadonly
|
||||
:files="linkedSopAttachmentFiles"
|
||||
@preview="handleSopAttachmentPreview"
|
||||
@download="handleSopAttachmentDownload"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</el-form>
|
||||
|
||||
@@ -715,7 +725,10 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import { Plus, Delete, Rank, Close, Check, CopyDocument, RefreshLeft, Search } from '@element-plus/icons-vue'
|
||||
import ProtocolAttachmentReadonly from '@/views/cell-storage/protocol/ProtocolAttachmentReadonly.vue'
|
||||
import { downloadAttachmentFile } from '@/utils/download-attachment'
|
||||
import { VueDraggable as draggable } from 'vue-draggable-plus'
|
||||
import {
|
||||
fetchProcessRouteList,
|
||||
@@ -1275,6 +1288,63 @@
|
||||
saveStepBasic()
|
||||
}
|
||||
|
||||
const minioBaseUrl = (import.meta.env.VITE_MINIO_BASE_URL || '').replace(/\/$/, '')
|
||||
|
||||
function sopAttachmentFullUrl(url: string) {
|
||||
const u = String(url || '').trim()
|
||||
if (!u) return ''
|
||||
if (/^https?:\/\//i.test(u)) return u
|
||||
return minioBaseUrl ? `${minioBaseUrl}/${u.replace(/^\//, '')}` : u
|
||||
}
|
||||
|
||||
function attachmentsFromUrls(urls: string, sopId: string): Array<Record<string, unknown>> {
|
||||
return urls
|
||||
.split(',')
|
||||
.map((u) => u.trim())
|
||||
.filter(Boolean)
|
||||
.map((url, i) => ({
|
||||
id: `${sopId}_url_${i}`,
|
||||
fileName: decodeURIComponent(url.split('/').pop() || `附件${i + 1}`),
|
||||
fileUrl: url,
|
||||
fileType: ''
|
||||
}))
|
||||
}
|
||||
|
||||
const linkedSopAttachmentFiles = computed(() => {
|
||||
const ids = sopIdsArr.value
|
||||
if (!ids?.length) return []
|
||||
const idSet = new Set(ids)
|
||||
const files: Array<Record<string, unknown>> = []
|
||||
for (const sop of sopOptions.value) {
|
||||
if (!idSet.has(sop.id)) continue
|
||||
if (sop.attachmentFiles?.length) {
|
||||
for (const f of sop.attachmentFiles) {
|
||||
if (!f?.fileUrl) continue
|
||||
files.push({
|
||||
id: f.id || `${sop.id}_${f.fileUrl}`,
|
||||
fileName:
|
||||
f.fileName ||
|
||||
decodeURIComponent(String(f.fileUrl).split('/').pop() || '文档'),
|
||||
fileUrl: f.fileUrl,
|
||||
fileType: f.fileType || ''
|
||||
})
|
||||
}
|
||||
} else if (sop.sopAttachments) {
|
||||
files.push(...attachmentsFromUrls(sop.sopAttachments, sop.id))
|
||||
}
|
||||
}
|
||||
return files
|
||||
})
|
||||
|
||||
function handleSopAttachmentPreview(file: Record<string, unknown>) {
|
||||
const url = sopAttachmentFullUrl(String(file.fileUrl || ''))
|
||||
if (url) window.open(url, '_blank')
|
||||
}
|
||||
|
||||
function handleSopAttachmentDownload(file: Record<string, unknown>) {
|
||||
downloadAttachmentFile(file, { fullFileUrl: sopAttachmentFullUrl })
|
||||
}
|
||||
|
||||
// 按分类分组的工艺库(支持搜索过滤)
|
||||
const filteredProcessLibrary = computed(() => {
|
||||
const keyword = processSearchKeyword.value?.trim().toLowerCase() || ''
|
||||
@@ -1835,7 +1905,7 @@
|
||||
|
||||
/* 中间面板:步骤列表 */
|
||||
.route-config-center {
|
||||
width: 640px;
|
||||
width: 480px;
|
||||
flex-shrink: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@@ -2054,4 +2124,57 @@
|
||||
padding: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
.form-grid-4 .step-form-col-half {
|
||||
grid-column: span 2;
|
||||
}
|
||||
|
||||
.step-form-col-full {
|
||||
grid-column: 1 / -1;
|
||||
}
|
||||
|
||||
.step-sop-attachment-row {
|
||||
margin-top: -4px;
|
||||
|
||||
:deep(.attachment-list) {
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
:deep(.attachment-item) {
|
||||
width: 100px;
|
||||
}
|
||||
|
||||
:deep(.attachment-preview) {
|
||||
height: 68px;
|
||||
}
|
||||
|
||||
:deep(.attachment-preview__icon) {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
|
||||
.el-icon {
|
||||
font-size: 22px !important;
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.attachment-footer) {
|
||||
padding: 4px 6px;
|
||||
}
|
||||
|
||||
:deep(.attachment-name) {
|
||||
font-size: 10px;
|
||||
min-height: calc(10px * 1.4 * 2);
|
||||
}
|
||||
|
||||
:deep(.attachment-actions) {
|
||||
margin-top: 2px;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
:deep(.action-btn) {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
font-size: 11px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -228,7 +228,7 @@
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="描述" class="form-col-span-2">
|
||||
<el-form-item label="描述">
|
||||
<el-input
|
||||
v-model="formData.description"
|
||||
type="textarea"
|
||||
@@ -236,8 +236,8 @@
|
||||
placeholder="请输入描述"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="备注" class="form-col-span-2">
|
||||
<el-input v-model="formData.remark" placeholder="可选" />
|
||||
<el-form-item label="备注">
|
||||
<el-input v-model="formData.remark" type="textarea" :rows="2" placeholder="可选" />
|
||||
</el-form-item>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -240,5 +240,9 @@
|
||||
overflow: hidden;
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.route-config-center {
|
||||
width: 480px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -40,7 +40,7 @@
|
||||
</div>
|
||||
<div class="route-config-right-body">
|
||||
<el-form label-position="top" class="route-config-step-form">
|
||||
<div class="form-grid-2">
|
||||
<div class="form-grid-4">
|
||||
<el-form-item label="工序名称">
|
||||
<el-input v-model="editor.selectedStep.value.processName" />
|
||||
</el-form-item>
|
||||
@@ -50,23 +50,33 @@
|
||||
:label="cat.categoryName || cat.name" :value="cat.id" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="描述" class="form-col-span-2">
|
||||
<el-input v-model="editor.selectedStep.value.description" type="textarea" :rows="2"
|
||||
placeholder="工序描述" />
|
||||
</el-form-item>
|
||||
<el-form-item label="设备名称">
|
||||
<el-input v-model="editor.selectedStep.value.equipmentName" />
|
||||
</el-form-item>
|
||||
<el-form-item label="预计时长(min)">
|
||||
<el-input v-model="editor.selectedStep.value.estimatedDuration" placeholder="如 2h" />
|
||||
</el-form-item>
|
||||
<el-form-item label="关联SOP" class="form-col-span-2">
|
||||
<el-form-item label="描述" class="step-form-col-half">
|
||||
<el-input v-model="editor.selectedStep.value.description" type="textarea" :rows="2"
|
||||
placeholder="工序描述" />
|
||||
</el-form-item>
|
||||
<el-form-item label="关联SOP" class="step-form-col-half">
|
||||
<el-select v-model="editor.sopIdsArr.value" multiple placeholder="选择SOP文档" filterable
|
||||
@change="editor.onSopIdsChange">
|
||||
<el-option v-for="sop in editor.sopOptions.value" :key="sop.id"
|
||||
:label="sop.sopName || sop.name" :value="sop.id" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<div
|
||||
v-if="editor.sopIdsArr.value.length"
|
||||
class="step-sop-attachment-row step-form-col-full"
|
||||
>
|
||||
<ProtocolAttachmentReadonly
|
||||
:files="linkedSopAttachmentFiles"
|
||||
@preview="handleSopAttachmentPreview"
|
||||
@download="handleSopAttachmentDownload"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</el-form>
|
||||
|
||||
@@ -308,13 +318,73 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import { Check, Plus, Rank, VideoPlay } from '@element-plus/icons-vue'
|
||||
import ProtocolAttachmentReadonly from '@/views/cell-storage/protocol/ProtocolAttachmentReadonly.vue'
|
||||
import { downloadAttachmentFile } from '@/utils/download-attachment'
|
||||
import { useTaskStepEditor } from './useTaskStepEditor'
|
||||
|
||||
defineProps<{
|
||||
const props = defineProps<{
|
||||
editor: ReturnType<typeof useTaskStepEditor>
|
||||
readonly?: boolean
|
||||
}>()
|
||||
|
||||
const minioBaseUrl = (import.meta.env.VITE_MINIO_BASE_URL || '').replace(/\/$/, '')
|
||||
|
||||
function sopAttachmentFullUrl(url: string) {
|
||||
const u = String(url || '').trim()
|
||||
if (!u) return ''
|
||||
if (/^https?:\/\//i.test(u)) return u
|
||||
return minioBaseUrl ? `${minioBaseUrl}/${u.replace(/^\//, '')}` : u
|
||||
}
|
||||
|
||||
function attachmentsFromUrls(urls: string, sopId: string): Array<Record<string, unknown>> {
|
||||
return urls
|
||||
.split(',')
|
||||
.map((u) => u.trim())
|
||||
.filter(Boolean)
|
||||
.map((url, i) => ({
|
||||
id: `${sopId}_url_${i}`,
|
||||
fileName: decodeURIComponent(url.split('/').pop() || `附件${i + 1}`),
|
||||
fileUrl: url,
|
||||
fileType: ''
|
||||
}))
|
||||
}
|
||||
|
||||
const linkedSopAttachmentFiles = computed(() => {
|
||||
const ids = props.editor.sopIdsArr.value
|
||||
if (!ids?.length) return []
|
||||
const idSet = new Set(ids)
|
||||
const files: Array<Record<string, unknown>> = []
|
||||
for (const sop of props.editor.sopOptions.value) {
|
||||
if (!idSet.has(sop.id)) continue
|
||||
if (sop.attachmentFiles?.length) {
|
||||
for (const f of sop.attachmentFiles) {
|
||||
if (!f?.fileUrl) continue
|
||||
files.push({
|
||||
id: f.id || `${sop.id}_${f.fileUrl}`,
|
||||
fileName:
|
||||
f.fileName ||
|
||||
decodeURIComponent(String(f.fileUrl).split('/').pop() || '文档'),
|
||||
fileUrl: f.fileUrl,
|
||||
fileType: f.fileType || ''
|
||||
})
|
||||
}
|
||||
} else if (sop.sopAttachments) {
|
||||
files.push(...attachmentsFromUrls(sop.sopAttachments, sop.id))
|
||||
}
|
||||
}
|
||||
return files
|
||||
})
|
||||
|
||||
function handleSopAttachmentPreview(file: Record<string, unknown>) {
|
||||
const url = sopAttachmentFullUrl(String(file.fileUrl || ''))
|
||||
if (url) window.open(url, '_blank')
|
||||
}
|
||||
|
||||
function handleSopAttachmentDownload(file: Record<string, unknown>) {
|
||||
downloadAttachmentFile(file, { fullFileUrl: sopAttachmentFullUrl })
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@@ -326,4 +396,58 @@
|
||||
gap: 8px;
|
||||
margin-left: 12px;
|
||||
}
|
||||
|
||||
/* 4 列栅格下各占半行(2/4 列) */
|
||||
.form-grid-4 .step-form-col-half {
|
||||
grid-column: span 2;
|
||||
}
|
||||
|
||||
.step-form-col-full {
|
||||
grid-column: 1 / -1;
|
||||
}
|
||||
|
||||
.step-sop-attachment-row {
|
||||
margin-top: -4px;
|
||||
|
||||
:deep(.attachment-list) {
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
:deep(.attachment-item) {
|
||||
width: 100px;
|
||||
}
|
||||
|
||||
:deep(.attachment-preview) {
|
||||
height: 68px;
|
||||
}
|
||||
|
||||
:deep(.attachment-preview__icon) {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
|
||||
.el-icon {
|
||||
font-size: 22px !important;
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.attachment-footer) {
|
||||
padding: 4px 6px;
|
||||
}
|
||||
|
||||
:deep(.attachment-name) {
|
||||
font-size: 10px;
|
||||
min-height: calc(10px * 1.4 * 2);
|
||||
}
|
||||
|
||||
:deep(.attachment-actions) {
|
||||
margin-top: 2px;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
:deep(.action-btn) {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
font-size: 11px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -84,7 +84,7 @@
|
||||
|
||||
<el-dialog v-model="dialogVisible" :title="dialogTitle" width="840px" class="dialog-form-layout" align-center destroy-on-close :close-on-click-modal="false">
|
||||
<el-form ref="formRef" :model="formData" :rules="formRules" label-position="top" class="biz-dialog-form">
|
||||
<div class="form-grid-2">
|
||||
<div class="form-grid-3">
|
||||
<el-form-item label="项目编码" prop="itemCode">
|
||||
<el-input v-model="formData.itemCode" :disabled="isEdit" />
|
||||
</el-form-item>
|
||||
@@ -97,10 +97,12 @@
|
||||
<el-radio value="0">停用</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="描述" class="form-col-span-2">
|
||||
</div>
|
||||
<div class="form-grid-2">
|
||||
<el-form-item label="描述">
|
||||
<el-input v-model="formData.description" type="textarea" :rows="2" />
|
||||
</el-form-item>
|
||||
<el-form-item label="备注" class="form-col-span-2">
|
||||
<el-form-item label="备注">
|
||||
<el-input v-model="formData.remark" type="textarea" :rows="2" />
|
||||
</el-form-item>
|
||||
</div>
|
||||
@@ -371,6 +373,10 @@
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.biz-dialog-form .form-grid-3 + .form-grid-2 {
|
||||
margin-top: 12px;
|
||||
}
|
||||
|
||||
.form-section {
|
||||
background: #fafafa;
|
||||
border-radius: 8px;
|
||||
|
||||
@@ -220,19 +220,19 @@
|
||||
>
|
||||
<div v-if="currentRow" class="sample-reception-readonly">
|
||||
<h4 class="detail-section-title">样本信息</h4>
|
||||
<div class="readonly-grid form-grid-2">
|
||||
<div class="readonly-grid form-grid-3">
|
||||
<div class="readonly-item"><span class="label">协议号</span>{{ currentRow.protocolNo || '-' }}</div>
|
||||
<div class="readonly-item"><span class="label">样本编码</span>{{ currentRow.sampleCode || '-' }}</div>
|
||||
<div class="readonly-item"><span class="label">样本类型</span>{{ currentRow.sampleTypeName || '-' }}</div>
|
||||
<div class="readonly-item"><span class="label">预计到达</span>{{ currentRow.etaTime || '-' }}</div>
|
||||
<div class="readonly-item form-col-span-2">
|
||||
<div class="readonly-item readonly-col-span-2">
|
||||
<span class="label">客户</span>{{ currentRow.motherName || '-' }} / {{ currentRow.newbornName || '-' }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="currentRow" class="form-section-divider" />
|
||||
<h4 class="detail-section-title">运输信息</h4>
|
||||
<div class="readonly-grid form-grid-2">
|
||||
<div class="readonly-grid form-grid-3">
|
||||
<div class="readonly-item">
|
||||
<span class="label">运输方式</span>{{ transportTypeLabel(String(currentRow?.transportType || '')) }}
|
||||
</div>
|
||||
@@ -255,7 +255,7 @@
|
||||
label-position="top"
|
||||
class="biz-dialog-form"
|
||||
>
|
||||
<div class="form-grid-2">
|
||||
<div class="form-grid-3">
|
||||
<el-form-item label="样本接收时间" prop="receptionTime">
|
||||
<el-date-picker
|
||||
v-model="receiveForm.receptionTime"
|
||||
@@ -453,7 +453,7 @@
|
||||
<div class="readonly-item"><span class="label">运输方式</span>{{ entry.transportType === 'kdys' ? '快递运输' : entry.transportType === 'ryxd' ? '人员携带' : '-' }}</div>
|
||||
</div>
|
||||
<el-form label-position="top" class="biz-dialog-form">
|
||||
<div class="form-grid-2">
|
||||
<div class="form-grid-3">
|
||||
<el-form-item label="样本接收时间" required>
|
||||
<el-date-picker
|
||||
v-model="entry.receptionTime"
|
||||
@@ -1224,6 +1224,10 @@
|
||||
}
|
||||
}
|
||||
|
||||
.readonly-grid.form-grid-3 .readonly-item.readonly-col-span-2 {
|
||||
grid-column: span 2;
|
||||
}
|
||||
|
||||
.check-list {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
|
||||
@@ -101,60 +101,60 @@
|
||||
<el-dialog
|
||||
v-model="companyDialogVisible"
|
||||
:title="companyDialogTitle"
|
||||
width="720px"
|
||||
width="920px"
|
||||
class="dialog-form-layout"
|
||||
align-center
|
||||
destroy-on-close
|
||||
:close-on-click-modal="false"
|
||||
>
|
||||
<el-form ref="companyFormRef" :model="companyForm" :rules="companyFormRules" label-position="top" class="biz-dialog-form">
|
||||
<div class="form-grid-2">
|
||||
<el-form-item label="公司编码" prop="companyCode">
|
||||
<el-input v-model="companyForm.companyCode" :disabled="companyIsEdit" placeholder="租户内唯一" />
|
||||
<div class="form-grid-3">
|
||||
<el-form-item label="公司编码" prop="companyCode">
|
||||
<el-input v-model="companyForm.companyCode" :disabled="companyIsEdit" placeholder="租户内唯一" />
|
||||
</el-form-item>
|
||||
<el-form-item label="公司名称" prop="companyName">
|
||||
<el-input v-model="companyForm.companyName" />
|
||||
</el-form-item>
|
||||
<el-form-item label="联系人">
|
||||
<el-input v-model="companyForm.contactName" />
|
||||
</el-form-item>
|
||||
<el-form-item label="联系电话">
|
||||
<el-input v-model="companyForm.contactPhone" />
|
||||
</el-form-item>
|
||||
<el-form-item label="地址" class="form-col-span-2-in-grid3">
|
||||
<el-input v-model="companyForm.address" />
|
||||
</el-form-item>
|
||||
</div>
|
||||
<div class="form-section">
|
||||
<div class="section-title section-title--static">
|
||||
<div class="section-title-left"><h3>开票信息</h3></div>
|
||||
</div>
|
||||
<div class="form-grid-3">
|
||||
<el-form-item label="发票抬头">
|
||||
<el-input v-model="companyForm.invoiceTitle" placeholder="与营业执照一致" />
|
||||
</el-form-item>
|
||||
<el-form-item label="公司名称" prop="companyName">
|
||||
<el-input v-model="companyForm.companyName" />
|
||||
<el-form-item label="纳税人识别号">
|
||||
<el-input v-model="companyForm.taxId" placeholder="统一社会信用代码" />
|
||||
</el-form-item>
|
||||
<el-form-item label="联系人">
|
||||
<el-input v-model="companyForm.contactName" />
|
||||
<el-form-item label="开户银行">
|
||||
<el-input v-model="companyForm.bankName" placeholder="收款银行全称" />
|
||||
</el-form-item>
|
||||
<el-form-item label="联系电话">
|
||||
<el-input v-model="companyForm.contactPhone" />
|
||||
</el-form-item>
|
||||
<el-form-item label="地址" class="form-col-span-2">
|
||||
<el-input v-model="companyForm.address" />
|
||||
</el-form-item>
|
||||
</div>
|
||||
<div class="form-section">
|
||||
<div class="section-title section-title--static">
|
||||
<div class="section-title-left"><h3>开票信息</h3></div>
|
||||
</div>
|
||||
<div class="form-grid-2">
|
||||
<el-form-item label="发票抬头">
|
||||
<el-input v-model="companyForm.invoiceTitle" placeholder="与营业执照一致" />
|
||||
</el-form-item>
|
||||
<el-form-item label="纳税人识别号">
|
||||
<el-input v-model="companyForm.taxId" placeholder="统一社会信用代码" />
|
||||
</el-form-item>
|
||||
<el-form-item label="开户银行">
|
||||
<el-input v-model="companyForm.bankName" placeholder="收款银行全称" />
|
||||
</el-form-item>
|
||||
<el-form-item label="银行账号">
|
||||
<el-input v-model="companyForm.bankAccount" placeholder="收款账号" />
|
||||
</el-form-item>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-grid-2">
|
||||
<el-form-item label="状态" prop="status">
|
||||
<el-radio-group v-model="companyForm.status">
|
||||
<el-radio value="1">启用</el-radio>
|
||||
<el-radio value="0">停用</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="备注" class="form-col-span-2">
|
||||
<el-input v-model="companyForm.remark" type="textarea" :rows="2" />
|
||||
<el-form-item label="银行账号">
|
||||
<el-input v-model="companyForm.bankAccount" placeholder="收款账号" />
|
||||
</el-form-item>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-grid-3">
|
||||
<el-form-item label="状态" prop="status">
|
||||
<el-radio-group v-model="companyForm.status">
|
||||
<el-radio value="1">启用</el-radio>
|
||||
<el-radio value="0">停用</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="备注" class="form-col-span-2">
|
||||
<el-input v-model="companyForm.remark" type="textarea" :rows="2" />
|
||||
</el-form-item>
|
||||
</div>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button class="btn-ghost" @click="companyDialogVisible = false">
|
||||
@@ -876,6 +876,10 @@
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.form-grid-3 .form-col-span-2-in-grid3 {
|
||||
grid-column: span 2;
|
||||
}
|
||||
</style>
|
||||
|
||||
<!-- 检测项目配置弹窗 teleport 到 body,尺寸类名需非 scoped -->
|
||||
|
||||
@@ -39,7 +39,7 @@
|
||||
<div class="section-title">
|
||||
<div class="section-title-left"><h3>基本信息</h3></div>
|
||||
</div>
|
||||
<div class="form-grid">
|
||||
<div class="form-grid form-grid--col4">
|
||||
<el-form-item label="捐赠协议号" prop="donationNoId">
|
||||
<el-select
|
||||
v-model="formData.donationNoId"
|
||||
@@ -444,7 +444,7 @@
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-grid">
|
||||
<div class="form-grid form-grid--col4">
|
||||
<el-form-item label="样本类型">
|
||||
<el-select
|
||||
v-model="row.sampleTypeId"
|
||||
@@ -2413,6 +2413,14 @@
|
||||
&--col2 {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
|
||||
&--col4 {
|
||||
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||||
}
|
||||
|
||||
:deep(.el-form-item) {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.form-col-span-2 {
|
||||
@@ -2804,6 +2812,10 @@
|
||||
&--col2 {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
&--col4 {
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
}
|
||||
}
|
||||
|
||||
.client-sub-cards {
|
||||
|
||||
Reference in new issue
Block a user