提交
This commit is contained in:
1 parent
4a00f21bfb
commit
edf88dc016
1503 files changed
+329728
-41987
No files matched your search
@@ -8,9 +8,7 @@
|
||||
<img class="logo-img" src="/logo.png" alt="中科兴皖" />
|
||||
</div>
|
||||
<h1 class="slogan">一体化管理门户</h1>
|
||||
<p class="description">
|
||||
安全 · 高效 · 可扩展
|
||||
</p>
|
||||
<p class="description"> 安全 · 高效 · 可扩展 </p>
|
||||
<div class="features">
|
||||
<div class="feature-item">
|
||||
<i class="el-icon-setting"></i>
|
||||
@@ -157,154 +155,154 @@
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.login-container {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
height: 100vh;
|
||||
background: #f5f7fa;
|
||||
}
|
||||
|
||||
.login-left {
|
||||
flex: 1;
|
||||
background: linear-gradient(135deg, #10b981 0%, #059669 100%);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 40px;
|
||||
|
||||
@media (max-width: 768px) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.brand-content {
|
||||
color: #fff;
|
||||
text-align: center;
|
||||
max-width: 400px;
|
||||
}
|
||||
|
||||
.logo {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-bottom: 48px;
|
||||
}
|
||||
|
||||
.logo-img {
|
||||
width: min(200px, 55vw);
|
||||
height: auto;
|
||||
object-fit: contain;
|
||||
border-radius: 12px;
|
||||
background: #fff;
|
||||
padding: 12px;
|
||||
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.12);
|
||||
}
|
||||
|
||||
.slogan {
|
||||
font-size: 32px;
|
||||
font-weight: 600;
|
||||
margin: 0 0 16px 0;
|
||||
}
|
||||
|
||||
.description {
|
||||
font-size: 18px;
|
||||
opacity: 0.9;
|
||||
margin: 0 0 48px 0;
|
||||
}
|
||||
|
||||
.features {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 40px;
|
||||
}
|
||||
|
||||
.feature-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
|
||||
i {
|
||||
font-size: 32px;
|
||||
}
|
||||
|
||||
span {
|
||||
font-size: 14px;
|
||||
opacity: 0.9;
|
||||
}
|
||||
}
|
||||
|
||||
.login-right {
|
||||
width: 480px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: #fff;
|
||||
padding: 40px;
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.login-container {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
height: 100vh;
|
||||
background: #f5f7fa;
|
||||
}
|
||||
}
|
||||
|
||||
.form-container {
|
||||
width: 100%;
|
||||
max-width: 360px;
|
||||
}
|
||||
.login-left {
|
||||
flex: 1;
|
||||
background: linear-gradient(135deg, #10b981 0%, #059669 100%);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 40px;
|
||||
|
||||
.form-header {
|
||||
margin-bottom: 40px;
|
||||
text-align: center;
|
||||
@media (max-width: 768px) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: 28px;
|
||||
.brand-content {
|
||||
color: #fff;
|
||||
text-align: center;
|
||||
max-width: 400px;
|
||||
}
|
||||
|
||||
.logo {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-bottom: 48px;
|
||||
}
|
||||
|
||||
.logo-img {
|
||||
width: min(200px, 55vw);
|
||||
height: auto;
|
||||
object-fit: contain;
|
||||
border-radius: 12px;
|
||||
background: #fff;
|
||||
padding: 12px;
|
||||
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.12);
|
||||
}
|
||||
|
||||
.slogan {
|
||||
font-size: 32px;
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
margin: 0 0 12px 0;
|
||||
margin: 0 0 16px 0;
|
||||
}
|
||||
|
||||
p {
|
||||
font-size: 14px;
|
||||
color: #909399;
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.login-form {
|
||||
:deep(.el-form-item) {
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
:deep(.el-input__wrapper) {
|
||||
padding: 8px 16px;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
:deep(.el-input__inner) {
|
||||
height: 44px;
|
||||
}
|
||||
}
|
||||
|
||||
.login-btn {
|
||||
width: 100%;
|
||||
height: 48px;
|
||||
font-size: 16px;
|
||||
border-radius: 8px;
|
||||
background: linear-gradient(135deg, #10b981 0%, #059669 100%);
|
||||
border: none;
|
||||
|
||||
&:hover {
|
||||
.description {
|
||||
font-size: 18px;
|
||||
opacity: 0.9;
|
||||
margin: 0 0 48px 0;
|
||||
}
|
||||
}
|
||||
|
||||
.form-footer {
|
||||
margin-top: 40px;
|
||||
text-align: center;
|
||||
.features {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 40px;
|
||||
}
|
||||
|
||||
.copyright {
|
||||
font-size: 12px;
|
||||
color: #c0c4cc;
|
||||
.feature-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
|
||||
i {
|
||||
font-size: 32px;
|
||||
}
|
||||
|
||||
span {
|
||||
font-size: 14px;
|
||||
opacity: 0.9;
|
||||
}
|
||||
}
|
||||
|
||||
.login-right {
|
||||
width: 480px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: #fff;
|
||||
padding: 40px;
|
||||
|
||||
@media (max-width: 768px) {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.form-container {
|
||||
width: 100%;
|
||||
max-width: 360px;
|
||||
}
|
||||
|
||||
.form-header {
|
||||
margin-bottom: 40px;
|
||||
text-align: center;
|
||||
|
||||
h2 {
|
||||
font-size: 28px;
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
margin: 0 0 12px 0;
|
||||
}
|
||||
|
||||
p {
|
||||
font-size: 14px;
|
||||
color: #909399;
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.login-form {
|
||||
:deep(.el-form-item) {
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
:deep(.el-input__wrapper) {
|
||||
padding: 8px 16px;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
:deep(.el-input__inner) {
|
||||
height: 44px;
|
||||
}
|
||||
}
|
||||
|
||||
.login-btn {
|
||||
width: 100%;
|
||||
height: 48px;
|
||||
font-size: 16px;
|
||||
border-radius: 8px;
|
||||
background: linear-gradient(135deg, #10b981 0%, #059669 100%);
|
||||
border: none;
|
||||
|
||||
&:hover {
|
||||
opacity: 0.9;
|
||||
}
|
||||
}
|
||||
|
||||
.form-footer {
|
||||
margin-top: 40px;
|
||||
text-align: center;
|
||||
|
||||
.copyright {
|
||||
font-size: 12px;
|
||||
color: #c0c4cc;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -22,8 +22,12 @@
|
||||
<div class="filter-actions-row">
|
||||
<span class="filter-hint">按编码、名称模糊查询。</span>
|
||||
<div class="filter-actions-end">
|
||||
<el-button class="btn-ghost" @click="handleReset"><el-icon><RefreshLeft /></el-icon>重置</el-button>
|
||||
<el-button class="btn-main" type="primary" @click="handleSearch"><el-icon><Search /></el-icon>搜索</el-button>
|
||||
<el-button class="btn-ghost" @click="handleReset"
|
||||
><el-icon><RefreshLeft /></el-icon>重置</el-button
|
||||
>
|
||||
<el-button class="btn-main" type="primary" @click="handleSearch"
|
||||
><el-icon><Search /></el-icon>搜索</el-button
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</el-form>
|
||||
@@ -115,7 +119,9 @@
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<el-button class="btn-op-item" @click="handleView(row)"><el-icon><View /></el-icon>查看</el-button>
|
||||
<el-button class="btn-op-item" @click="handleView(row)"
|
||||
><el-icon><View /></el-icon>查看</el-button
|
||||
>
|
||||
<el-button class="btn-op-item primary" @click="handleEdit(row)">编辑</el-button>
|
||||
<el-button class="btn-op-item" @click="handleDelete(row)">删除</el-button>
|
||||
</template>
|
||||
@@ -210,7 +216,15 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, computed, onMounted } from 'vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { Plus, Close, Check, View, Collection, RefreshLeft, Search } from '@element-plus/icons-vue'
|
||||
import {
|
||||
Plus,
|
||||
Close,
|
||||
Check,
|
||||
View,
|
||||
Collection,
|
||||
RefreshLeft,
|
||||
Search
|
||||
} from '@element-plus/icons-vue'
|
||||
import { withFullscreenLoading, fullscreenLoadingText } from '@/utils/fullscreen-loading'
|
||||
import {
|
||||
fetchCellSampleTypeList,
|
||||
@@ -408,6 +422,5 @@
|
||||
:deep(.el-form-item) {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
}
|
||||
</style>
|
||||
@@ -38,8 +38,12 @@
|
||||
<div class="filter-actions-row">
|
||||
<span class="filter-hint">模糊查询。</span>
|
||||
<div class="filter-actions-end">
|
||||
<el-button class="btn-ghost" @click="handleReset"><el-icon><RefreshLeft /></el-icon>重置</el-button>
|
||||
<el-button class="btn-main" type="primary" @click="handleSearch"><el-icon><Search /></el-icon>搜索</el-button>
|
||||
<el-button class="btn-ghost" @click="handleReset"
|
||||
><el-icon><RefreshLeft /></el-icon>重置</el-button
|
||||
>
|
||||
<el-button class="btn-main" type="primary" @click="handleSearch"
|
||||
><el-icon><Search /></el-icon>搜索</el-button
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</el-form>
|
||||
@@ -165,7 +169,9 @@
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<el-button class="btn-op-item" @click="handleView(row)"><el-icon><View /></el-icon>查看</el-button>
|
||||
<el-button class="btn-op-item" @click="handleView(row)"
|
||||
><el-icon><View /></el-icon>查看</el-button
|
||||
>
|
||||
<el-button class="btn-op-item primary" @click="handleEdit(row)">编辑</el-button>
|
||||
<el-button class="btn-op-item" @click="handleDelete(row)">删除</el-button>
|
||||
</template>
|
||||
@@ -229,9 +235,7 @@
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="序号位数" prop="digitCount">
|
||||
<template #label
|
||||
>序号位数<span class="form-label-tip">补零后的总位数</span></template
|
||||
>
|
||||
<template #label>序号位数<span class="form-label-tip">补零后的总位数</span></template>
|
||||
<el-input
|
||||
type="number"
|
||||
step="any"
|
||||
@@ -251,7 +255,12 @@
|
||||
<el-icon><Close /></el-icon>
|
||||
取消
|
||||
</el-button>
|
||||
<el-button class="btn-main" type="primary" :loading="batchGenLoading" @click="handleBatchGenerate">
|
||||
<el-button
|
||||
class="btn-main"
|
||||
type="primary"
|
||||
:loading="batchGenLoading"
|
||||
@click="handleBatchGenerate"
|
||||
>
|
||||
<el-icon><Check /></el-icon>
|
||||
生成
|
||||
</el-button>
|
||||
@@ -301,7 +310,12 @@
|
||||
<el-icon><Close /></el-icon>
|
||||
取消
|
||||
</el-button>
|
||||
<el-button class="btn-main" type="primary" :loading="batchIssueLoading" @click="handleBatchIssue">
|
||||
<el-button
|
||||
class="btn-main"
|
||||
type="primary"
|
||||
:loading="batchIssueLoading"
|
||||
@click="handleBatchIssue"
|
||||
>
|
||||
<el-icon><Check /></el-icon>
|
||||
确定下发
|
||||
</el-button>
|
||||
@@ -468,14 +482,14 @@
|
||||
middle: '34',
|
||||
seqStart: 1,
|
||||
seqEnd: 100,
|
||||
digitCount: 6,
|
||||
digitCount: 6
|
||||
})
|
||||
const batchGenRules = {
|
||||
prefix: [{ required: true, message: '请输入固定前缀', trigger: 'blur' }],
|
||||
middle: [{ required: true, message: '请输入中间段', trigger: 'blur' }],
|
||||
seqStart: [{ required: true, message: '请输入开始序号', trigger: 'blur' }],
|
||||
seqEnd: [{ required: true, message: '请输入结束序号', trigger: 'blur' }],
|
||||
digitCount: [{ required: true, message: '请输入序号位数', trigger: 'blur' }],
|
||||
digitCount: [{ required: true, message: '请输入序号位数', trigger: 'blur' }]
|
||||
}
|
||||
const batchGenPreview = computed(() => {
|
||||
const p = batchGenForm.prefix || ''
|
||||
@@ -494,10 +508,10 @@
|
||||
const batchIssueLoading = ref(false)
|
||||
const batchIssueFormRef = ref()
|
||||
const batchIssueForm = reactive({
|
||||
issuedUserId: '',
|
||||
issuedUserId: ''
|
||||
})
|
||||
const batchIssueRules = {
|
||||
issuedUserId: [{ required: true, message: '请选择下发用户', trigger: 'change' }],
|
||||
issuedUserId: [{ required: true, message: '请选择下发用户', trigger: 'change' }]
|
||||
}
|
||||
const userOptions = ref<Array<{ label: string; value: string }>>([])
|
||||
|
||||
@@ -698,7 +712,7 @@
|
||||
middle: batchGenForm.middle.trim(),
|
||||
seqStart: batchGenForm.seqStart,
|
||||
seqEnd: batchGenForm.seqEnd,
|
||||
digitCount: batchGenForm.digitCount,
|
||||
digitCount: batchGenForm.digitCount
|
||||
})
|
||||
batchGenVisible.value = false
|
||||
await loadData()
|
||||
@@ -719,12 +733,12 @@
|
||||
await batchIssueFormRef.value?.validate?.()
|
||||
batchIssueLoading.value = true
|
||||
try {
|
||||
const ids = selectedRows.value.map(r => r.id)
|
||||
const userOpt = userOptions.value.find(u => u.value === batchIssueForm.issuedUserId)
|
||||
const ids = selectedRows.value.map((r) => r.id)
|
||||
const userOpt = userOptions.value.find((u) => u.value === batchIssueForm.issuedUserId)
|
||||
await fetchBatchIssueProtocolNos({
|
||||
ids,
|
||||
issuedUserId: batchIssueForm.issuedUserId,
|
||||
issuedUserName: userOpt?.label || undefined,
|
||||
issuedUserName: userOpt?.label || undefined
|
||||
})
|
||||
batchIssueVisible.value = false
|
||||
selectedRows.value = []
|
||||
@@ -763,6 +777,5 @@
|
||||
:deep(.el-form-item) {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
}
|
||||
</style>
|
||||
@@ -22,8 +22,12 @@
|
||||
<div class="filter-actions-row">
|
||||
<span class="filter-hint">按地区名称模糊查询。</span>
|
||||
<div class="filter-actions-end">
|
||||
<el-button class="btn-ghost" @click="handleReset"><el-icon><RefreshLeft /></el-icon>重置</el-button>
|
||||
<el-button class="btn-main" type="primary" @click="handleSearch"><el-icon><Search /></el-icon>搜索</el-button>
|
||||
<el-button class="btn-ghost" @click="handleReset"
|
||||
><el-icon><RefreshLeft /></el-icon>重置</el-button
|
||||
>
|
||||
<el-button class="btn-main" type="primary" @click="handleSearch"
|
||||
><el-icon><Search /></el-icon>搜索</el-button
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</el-form>
|
||||
@@ -84,11 +88,7 @@
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<template v-if="row.hospitalNames && row.hospitalNames.length">
|
||||
<div
|
||||
v-for="(name, idx) in row.hospitalNames"
|
||||
:key="idx"
|
||||
class="hospital-cell-line"
|
||||
>
|
||||
<div v-for="(name, idx) in row.hospitalNames" :key="idx" class="hospital-cell-line">
|
||||
{{ name }}
|
||||
</div>
|
||||
</template>
|
||||
@@ -123,7 +123,9 @@
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<el-button class="btn-op-item" @click="handleView(row)"><el-icon><View /></el-icon>查看</el-button>
|
||||
<el-button class="btn-op-item" @click="handleView(row)"
|
||||
><el-icon><View /></el-icon>查看</el-button
|
||||
>
|
||||
<el-button class="btn-op-item primary" @click="openHospitalDialog(row)"
|
||||
>地区医院配置</el-button
|
||||
>
|
||||
@@ -415,9 +417,7 @@
|
||||
const row = viewRow.value
|
||||
if (!row) return []
|
||||
const hospitals =
|
||||
row.hospitalNames && row.hospitalNames.length > 0
|
||||
? row.hospitalNames.join('、')
|
||||
: '无'
|
||||
row.hospitalNames && row.hospitalNames.length > 0 ? row.hospitalNames.join('、') : '无'
|
||||
return [
|
||||
{
|
||||
title: '基本信息',
|
||||
@@ -670,7 +670,6 @@
|
||||
:deep(.el-form-item) {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.hospital-dialog-head {
|
||||
|
||||
@@ -37,8 +37,12 @@
|
||||
<div class="filter-actions-row">
|
||||
<span class="filter-hint">支持模糊查询;默认展示近 30 天内创建的客户。</span>
|
||||
<div class="filter-actions-end">
|
||||
<el-button class="btn-ghost" @click="handleReset"><el-icon><RefreshLeft /></el-icon>重置</el-button>
|
||||
<el-button class="btn-main" type="primary" @click="handleSearch"><el-icon><Search /></el-icon>搜索</el-button>
|
||||
<el-button class="btn-ghost" @click="handleReset"
|
||||
><el-icon><RefreshLeft /></el-icon>重置</el-button
|
||||
>
|
||||
<el-button class="btn-main" type="primary" @click="handleSearch"
|
||||
><el-icon><Search /></el-icon>搜索</el-button
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</el-form>
|
||||
@@ -90,7 +94,13 @@
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column prop="sex" label="性别" min-width="72" align="center" header-align="center">
|
||||
<el-table-column
|
||||
prop="sex"
|
||||
label="性别"
|
||||
min-width="72"
|
||||
align="center"
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }">{{ sexLabel(row.sex) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
@@ -135,7 +145,9 @@
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<el-button class="btn-op-item" @click="handleView(row)"><el-icon><View /></el-icon>查看</el-button>
|
||||
<el-button class="btn-op-item" @click="handleView(row)"
|
||||
><el-icon><View /></el-icon>查看</el-button
|
||||
>
|
||||
<el-button class="btn-op-item primary" @click="handleEdit(row)">编辑</el-button>
|
||||
<el-button class="btn-op-item" @click="handleDelete(row)">删除</el-button>
|
||||
</template>
|
||||
@@ -459,6 +471,5 @@
|
||||
:deep(.el-form-item) {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
}
|
||||
</style>
|
||||
@@ -68,21 +68,67 @@
|
||||
header-align="center"
|
||||
:index="(i) => (pagination.page - 1) * pagination.pageSize + i + 1"
|
||||
/>
|
||||
<el-table-column prop="companyName" label="保险公司名称" min-width="160" align="center" header-align="center" />
|
||||
<el-table-column prop="companyAddress" label="地址" min-width="180" align="center" header-align="center" />
|
||||
<el-table-column prop="contactName" label="联系人" min-width="100" align="center" header-align="center" />
|
||||
<el-table-column prop="contactPhone" label="联系电话" min-width="120" align="center" header-align="center" />
|
||||
<el-table-column prop="tag" label="状态" min-width="90" align="center" header-align="center">
|
||||
<el-table-column
|
||||
prop="companyName"
|
||||
label="保险公司名称"
|
||||
min-width="160"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="companyAddress"
|
||||
label="地址"
|
||||
min-width="180"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="contactName"
|
||||
label="联系人"
|
||||
min-width="100"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="contactPhone"
|
||||
label="联系电话"
|
||||
min-width="120"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="tag"
|
||||
label="状态"
|
||||
min-width="90"
|
||||
align="center"
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="row.status == '1' ? 'success' : 'info'" size="small">{{ row.status == '1' ? '正常' : '停用' }}</el-tag>
|
||||
<el-tag :type="row.status == '1' ? 'success' : 'info'" size="small">{{
|
||||
row.status == '1' ? '正常' : '停用'
|
||||
}}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" min-width="400" fixed="right" align="center" header-align="center">
|
||||
<el-table-column
|
||||
label="操作"
|
||||
min-width="400"
|
||||
fixed="right"
|
||||
align="center"
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<el-button class="btn-op-item" @click="handleView(row)"><el-icon><View /></el-icon>查看</el-button>
|
||||
<el-button class="btn-op-item primary" @click="openItemDialog(row)"><el-icon><Setting /></el-icon>险种价格配置</el-button>
|
||||
<el-button class="btn-op-item" @click="handleEdit(row)"><el-icon><Edit /></el-icon>编辑</el-button>
|
||||
<el-button class="btn-op-item" @click="handleDelete(row)"><el-icon><Delete /></el-icon>删除</el-button>
|
||||
<el-button class="btn-op-item" @click="handleView(row)"
|
||||
><el-icon><View /></el-icon>查看</el-button
|
||||
>
|
||||
<el-button class="btn-op-item primary" @click="openItemDialog(row)"
|
||||
><el-icon><Setting /></el-icon>险种价格配置</el-button
|
||||
>
|
||||
<el-button class="btn-op-item" @click="handleEdit(row)"
|
||||
><el-icon><Edit /></el-icon>编辑</el-button
|
||||
>
|
||||
<el-button class="btn-op-item" @click="handleDelete(row)"
|
||||
><el-icon><Delete /></el-icon>删除</el-button
|
||||
>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
@@ -109,7 +155,13 @@
|
||||
destroy-on-close
|
||||
:close-on-click-modal="false"
|
||||
>
|
||||
<el-form ref="companyFormRef" :model="companyForm" :rules="companyFormRules" label-position="top" class="biz-dialog-form">
|
||||
<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="companyName">
|
||||
<el-input v-model="companyForm.companyName" placeholder="请输入保险公司名称" />
|
||||
@@ -155,7 +207,12 @@
|
||||
<el-button class="btn-ghost" @click="companyDialogVisible = false">
|
||||
<el-icon><Close /></el-icon>取消
|
||||
</el-button>
|
||||
<el-button class="btn-main" type="primary" :loading="companySubmitLoading" @click="submitCompany">
|
||||
<el-button
|
||||
class="btn-main"
|
||||
type="primary"
|
||||
:loading="companySubmitLoading"
|
||||
@click="submitCompany"
|
||||
>
|
||||
<el-icon><Check /></el-icon>确定
|
||||
</el-button>
|
||||
</template>
|
||||
@@ -197,21 +254,66 @@
|
||||
@selection-change="onItemSelectionChange"
|
||||
>
|
||||
<el-table-column type="selection" min-width="55" align="center" header-align="center" />
|
||||
<el-table-column type="index" label="序号" min-width="68" align="center" header-align="center" />
|
||||
<el-table-column prop="projectName" label="险种名称" min-width="160" align="center" header-align="center" />
|
||||
<el-table-column prop="projectFee" label="险种费用(元)" min-width="120" align="center" header-align="center">
|
||||
<template #default="{ row }">{{ row.projectFee != null ? '¥' + formatMoneyDisplay(row.projectFee) : '—' }}</template>
|
||||
<el-table-column
|
||||
type="index"
|
||||
label="序号"
|
||||
min-width="68"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="projectName"
|
||||
label="险种名称"
|
||||
min-width="160"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="projectFee"
|
||||
label="险种费用(元)"
|
||||
min-width="120"
|
||||
align="center"
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }">{{
|
||||
row.projectFee != null ? '¥' + formatMoneyDisplay(row.projectFee) : '—'
|
||||
}}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="status" label="状态" min-width="90" align="center" header-align="center">
|
||||
<el-table-column
|
||||
prop="status"
|
||||
label="状态"
|
||||
min-width="90"
|
||||
align="center"
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="row.status == '1' ? 'success' : 'info'" size="small">{{ row.status == '1' ? '正常' : '停用' }}</el-tag>
|
||||
<el-tag :type="row.status == '1' ? 'success' : 'info'" size="small">{{
|
||||
row.status == '1' ? '正常' : '停用'
|
||||
}}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="remark" label="备注" min-width="140" align="center" header-align="center" show-overflow-tooltip />
|
||||
<el-table-column label="操作" min-width="260" fixed="right" align="center" header-align="center">
|
||||
<el-table-column
|
||||
prop="remark"
|
||||
label="备注"
|
||||
min-width="140"
|
||||
align="center"
|
||||
header-align="center"
|
||||
show-overflow-tooltip
|
||||
/>
|
||||
<el-table-column
|
||||
label="操作"
|
||||
min-width="260"
|
||||
fixed="right"
|
||||
align="center"
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<el-button class="btn-op-item primary" @click="handleEditItem(row)"><el-icon><Edit /></el-icon>编辑</el-button>
|
||||
<el-button class="btn-op-item" @click="handleDeleteItem(row)"><el-icon><Delete /></el-icon>删除</el-button>
|
||||
<el-button class="btn-op-item primary" @click="handleEditItem(row)"
|
||||
><el-icon><Edit /></el-icon>编辑</el-button
|
||||
>
|
||||
<el-button class="btn-op-item" @click="handleDeleteItem(row)"
|
||||
><el-icon><Delete /></el-icon>删除</el-button
|
||||
>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
@@ -222,7 +324,9 @@
|
||||
:total="itemPagination.total"
|
||||
:page-sizes="[10, 20, 50]"
|
||||
layout="total, sizes, prev, pager, next"
|
||||
@size-change="(v) => ((itemPagination.page = 1), (itemPagination.pageSize = v), loadItems())"
|
||||
@size-change="
|
||||
(v) => ((itemPagination.page = 1), (itemPagination.pageSize = v), loadItems())
|
||||
"
|
||||
@current-change="(v) => ((itemPagination.page = v), loadItems())"
|
||||
/>
|
||||
</div>
|
||||
@@ -239,7 +343,13 @@
|
||||
destroy-on-close
|
||||
:close-on-click-modal="false"
|
||||
>
|
||||
<el-form ref="itemFormRef" :model="itemForm" :rules="itemFormRules" label-position="top" class="biz-dialog-form">
|
||||
<el-form
|
||||
ref="itemFormRef"
|
||||
:model="itemForm"
|
||||
:rules="itemFormRules"
|
||||
label-position="top"
|
||||
class="biz-dialog-form"
|
||||
>
|
||||
<div class="form-grid-2">
|
||||
<el-form-item label="险种名称" prop="projectName">
|
||||
<el-input v-model="itemForm.projectName" placeholder="请输入险种名称" />
|
||||
@@ -262,7 +372,12 @@
|
||||
<el-button class="btn-ghost" @click="itemFormVisible = false">
|
||||
<el-icon><Close /></el-icon>取消
|
||||
</el-button>
|
||||
<el-button class="btn-main" type="primary" :loading="itemSubmitLoading" @click="submitItem">
|
||||
<el-button
|
||||
class="btn-main"
|
||||
type="primary"
|
||||
:loading="itemSubmitLoading"
|
||||
@click="submitItem"
|
||||
>
|
||||
<el-icon><Check /></el-icon>确定
|
||||
</el-button>
|
||||
</template>
|
||||
@@ -287,7 +402,18 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { Plus, Close, Check, Search, RefreshLeft, View, Setting, Edit, Delete, OfficeBuilding } from '@element-plus/icons-vue'
|
||||
import {
|
||||
Plus,
|
||||
Close,
|
||||
Check,
|
||||
Search,
|
||||
RefreshLeft,
|
||||
View,
|
||||
Setting,
|
||||
Edit,
|
||||
Delete,
|
||||
OfficeBuilding
|
||||
} from '@element-plus/icons-vue'
|
||||
import SystemViewDialog from '@/components/business/system-view-dialog/index.vue'
|
||||
import type { ViewSection } from '@/components/business/system-view-dialog/index.vue'
|
||||
import {
|
||||
@@ -515,7 +641,7 @@
|
||||
invoiceTitle: companyForm.invoiceTitle?.trim() || undefined,
|
||||
taxNo: companyForm.taxNo?.trim() || undefined,
|
||||
bankName: companyForm.bankName?.trim() || undefined,
|
||||
bankAccount: companyForm.bankAccount?.trim() || undefined,
|
||||
bankAccount: companyForm.bankAccount?.trim() || undefined
|
||||
}
|
||||
await fetchCreateInsuranceCompanyInvoice((newInst as any).id, invoicePayload)
|
||||
}
|
||||
@@ -530,9 +656,13 @@
|
||||
|
||||
async function handleDelete(row: any) {
|
||||
try {
|
||||
await ElMessageBox.confirm(`确定删除「${row.companyName}」?其下属险种价格将一并删除。`, '提示', {
|
||||
type: 'warning'
|
||||
})
|
||||
await ElMessageBox.confirm(
|
||||
`确定删除「${row.companyName}」?其下属险种价格将一并删除。`,
|
||||
'提示',
|
||||
{
|
||||
type: 'warning'
|
||||
}
|
||||
)
|
||||
} catch {
|
||||
return
|
||||
}
|
||||
@@ -556,7 +686,11 @@
|
||||
{ label: '公司地址', value: row.companyAddress, span: true },
|
||||
{ label: '联系人', value: row.contactName },
|
||||
{ label: '联系电话', value: row.contactPhone },
|
||||
{ label: '状态', value: row.status == '1' ? '正常' : '停用', tagType: row.status == '1' ? 'success' : 'info' },
|
||||
{
|
||||
label: '状态',
|
||||
value: row.status == '1' ? '正常' : '停用',
|
||||
tagType: row.status == '1' ? 'success' : 'info'
|
||||
},
|
||||
{ label: '备注', value: row.remark, span: true }
|
||||
]
|
||||
}
|
||||
|
||||
@@ -49,8 +49,12 @@
|
||||
<div class="filter-actions-row">
|
||||
<span class="filter-hint">模糊查询。</span>
|
||||
<div class="filter-actions-end">
|
||||
<el-button class="btn-ghost" @click="handleReset"><el-icon><RefreshLeft /></el-icon>重置</el-button>
|
||||
<el-button class="btn-main" type="primary" @click="handleSearch"><el-icon><Search /></el-icon>搜索</el-button>
|
||||
<el-button class="btn-ghost" @click="handleReset"
|
||||
><el-icon><RefreshLeft /></el-icon>重置</el-button
|
||||
>
|
||||
<el-button class="btn-main" type="primary" @click="handleSearch"
|
||||
><el-icon><Search /></el-icon>搜索</el-button
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</el-form>
|
||||
@@ -185,7 +189,9 @@
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<el-button class="btn-op-item" @click="handleView(row)"><el-icon><View /></el-icon>查看</el-button>
|
||||
<el-button class="btn-op-item" @click="handleView(row)"
|
||||
><el-icon><View /></el-icon>查看</el-button
|
||||
>
|
||||
<el-button class="btn-op-item primary" @click="handleEdit(row)">编辑</el-button>
|
||||
<el-button class="btn-op-item" @click="handleDelete(row)">删除</el-button>
|
||||
</template>
|
||||
@@ -223,7 +229,11 @@
|
||||
>
|
||||
<div class="form-grid-2">
|
||||
<el-form-item label="编码类型" prop="sampleCodeType">
|
||||
<el-select v-model="batchGenForm.sampleCodeType" placeholder="请选择" style="width: 100%">
|
||||
<el-select
|
||||
v-model="batchGenForm.sampleCodeType"
|
||||
placeholder="请选择"
|
||||
style="width: 100%"
|
||||
>
|
||||
<el-option label="造血编码 (ZKZX)" value="zxbm" />
|
||||
<el-option label="脐带编码 (ZKQD)" value="qdbm" />
|
||||
</el-select>
|
||||
@@ -252,9 +262,7 @@
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="序号位数" prop="digitCount">
|
||||
<template #label
|
||||
>序号位数<span class="form-label-tip">补零后的总位数</span></template
|
||||
>
|
||||
<template #label>序号位数<span class="form-label-tip">补零后的总位数</span></template>
|
||||
<el-input
|
||||
type="number"
|
||||
step="any"
|
||||
@@ -274,7 +282,12 @@
|
||||
<el-icon><Close /></el-icon>
|
||||
取消
|
||||
</el-button>
|
||||
<el-button class="btn-main" type="primary" :loading="batchGenLoading" @click="handleBatchGenerate">
|
||||
<el-button
|
||||
class="btn-main"
|
||||
type="primary"
|
||||
:loading="batchGenLoading"
|
||||
@click="handleBatchGenerate"
|
||||
>
|
||||
<el-icon><Check /></el-icon>
|
||||
生成
|
||||
</el-button>
|
||||
@@ -324,7 +337,12 @@
|
||||
<el-icon><Close /></el-icon>
|
||||
取消
|
||||
</el-button>
|
||||
<el-button class="btn-main" type="primary" :loading="batchIssueLoading" @click="handleBatchIssue">
|
||||
<el-button
|
||||
class="btn-main"
|
||||
type="primary"
|
||||
:loading="batchIssueLoading"
|
||||
@click="handleBatchIssue"
|
||||
>
|
||||
<el-icon><Check /></el-icon>
|
||||
确定下发
|
||||
</el-button>
|
||||
@@ -357,17 +375,15 @@
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item v-if="!isEdit" label="编码类型" prop="sampleCodeType">
|
||||
<el-select
|
||||
v-model="formData.sampleCodeType"
|
||||
placeholder="请选择"
|
||||
style="width: 100%"
|
||||
>
|
||||
<el-select v-model="formData.sampleCodeType" placeholder="请选择" style="width: 100%">
|
||||
<el-option label="造血编码 (ZKZX)" value="zxbm" />
|
||||
<el-option label="脐带编码 (ZKQD)" value="qdbm" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item v-else label="编码类型">
|
||||
<span class="text-sm text-gray-500">{{ sampleCodeTypeLabel(formData.sampleCodeType) }}</span>
|
||||
<span class="text-sm text-gray-500">{{
|
||||
sampleCodeTypeLabel(formData.sampleCodeType)
|
||||
}}</span>
|
||||
</el-form-item>
|
||||
<el-form-item label="是否下发">
|
||||
<el-switch
|
||||
@@ -507,14 +523,14 @@
|
||||
middle: '34',
|
||||
seqStart: 1,
|
||||
seqEnd: 100,
|
||||
digitCount: 6,
|
||||
digitCount: 6
|
||||
})
|
||||
const batchGenRules = {
|
||||
sampleCodeType: [{ required: true, message: '请选择编码类型', trigger: 'change' }],
|
||||
middle: [{ required: true, message: '请输入中间段', trigger: 'blur' }],
|
||||
seqStart: [{ required: true, message: '请输入开始序号', trigger: 'blur' }],
|
||||
seqEnd: [{ required: true, message: '请输入结束序号', trigger: 'blur' }],
|
||||
digitCount: [{ required: true, message: '请输入序号位数', trigger: 'blur' }],
|
||||
digitCount: [{ required: true, message: '请输入序号位数', trigger: 'blur' }]
|
||||
}
|
||||
const typePrefixMap: Record<string, string> = { zxbm: 'ZKZX', qdbm: 'ZKQD' }
|
||||
const batchGenPreview = computed(() => {
|
||||
@@ -535,10 +551,10 @@
|
||||
const batchIssueLoading = ref(false)
|
||||
const batchIssueFormRef = ref()
|
||||
const batchIssueForm = reactive({
|
||||
issuedUserId: '',
|
||||
issuedUserId: ''
|
||||
})
|
||||
const batchIssueRules = {
|
||||
issuedUserId: [{ required: true, message: '请选择下发用户', trigger: 'change' }],
|
||||
issuedUserId: [{ required: true, message: '请选择下发用户', trigger: 'change' }]
|
||||
}
|
||||
const userOptions = ref<Array<{ label: string; value: string }>>([])
|
||||
|
||||
@@ -746,7 +762,7 @@
|
||||
middle: batchGenForm.middle.trim(),
|
||||
seqStart: batchGenForm.seqStart,
|
||||
seqEnd: batchGenForm.seqEnd,
|
||||
digitCount: batchGenForm.digitCount,
|
||||
digitCount: batchGenForm.digitCount
|
||||
})
|
||||
batchGenVisible.value = false
|
||||
await loadData()
|
||||
@@ -767,12 +783,12 @@
|
||||
await batchIssueFormRef.value?.validate?.()
|
||||
batchIssueLoading.value = true
|
||||
try {
|
||||
const ids = selectedRows.value.map(r => r.id)
|
||||
const userOpt = userOptions.value.find(u => u.value === batchIssueForm.issuedUserId)
|
||||
const ids = selectedRows.value.map((r) => r.id)
|
||||
const userOpt = userOptions.value.find((u) => u.value === batchIssueForm.issuedUserId)
|
||||
await fetchBatchIssueSampleCodes({
|
||||
ids,
|
||||
issuedUserId: batchIssueForm.issuedUserId,
|
||||
issuedUserName: userOpt?.label || undefined,
|
||||
issuedUserName: userOpt?.label || undefined
|
||||
})
|
||||
batchIssueVisible.value = false
|
||||
selectedRows.value = []
|
||||
@@ -802,6 +818,5 @@
|
||||
:deep(.el-form-item) {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
}
|
||||
</style>
|
||||
@@ -50,7 +50,9 @@
|
||||
</el-form-item>
|
||||
</div>
|
||||
<div class="filter-actions-row">
|
||||
<span class="filter-hint">支持按协议号、姓名模糊检索;待办状态默认未办保险,满月日期默认近 30 天。</span>
|
||||
<span class="filter-hint"
|
||||
>支持按协议号、姓名模糊检索;待办状态默认未办保险,满月日期默认近 30 天。</span
|
||||
>
|
||||
<div class="filter-actions-end">
|
||||
<el-button class="btn-ghost" @click="handleReset">
|
||||
<el-icon><RefreshLeft /></el-icon>重置
|
||||
@@ -123,9 +125,11 @@
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="protocolStatusTagType(String(row.protocolStatus || ''))" size="small">{{
|
||||
protocolStatusLabel(String(row.protocolStatus || ''))
|
||||
}}</el-tag>
|
||||
<el-tag
|
||||
:type="protocolStatusTagType(String(row.protocolStatus || ''))"
|
||||
size="small"
|
||||
>{{ protocolStatusLabel(String(row.protocolStatus || '')) }}</el-tag
|
||||
>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
@@ -297,11 +301,7 @@
|
||||
<el-input v-model="formData.insurancePolicyNo" placeholder="请输入保险单号" />
|
||||
</el-form-item>
|
||||
<el-form-item label="保单费用(元)">
|
||||
<AmountInput
|
||||
v-model="formData.insuranceFee"
|
||||
:min="0"
|
||||
placeholder="请输入保单费用"
|
||||
/>
|
||||
<AmountInput v-model="formData.insuranceFee" :min="0" placeholder="请输入保单费用" />
|
||||
</el-form-item>
|
||||
<el-form-item label="保险单生效时间">
|
||||
<el-date-picker
|
||||
@@ -413,9 +413,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<span class="upload-hint"
|
||||
>支持图片、视频、PDF、Word、Excel等文件</span
|
||||
>
|
||||
<span class="upload-hint">支持图片、视频、PDF、Word、Excel等文件</span>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="办理备注" class="form-col-span-2">
|
||||
@@ -486,10 +484,7 @@
|
||||
fetchInsuranceCompanyList,
|
||||
fetchInsuranceProjectPrices
|
||||
} from '@/api/admin'
|
||||
import {
|
||||
protocolStatusLabel,
|
||||
protocolStatusTagType,
|
||||
} from '@/utils/status-tag-registry'
|
||||
import { protocolStatusLabel, protocolStatusTagType } from '@/utils/status-tag-registry'
|
||||
|
||||
defineOptions({ name: 'CellStorageFullmoonTodo' })
|
||||
|
||||
|
||||
@@ -39,8 +39,12 @@
|
||||
<div class="filter-actions-row">
|
||||
<span class="filter-hint">按物料名称/编码查询,可按变动类型和日期范围筛选。</span>
|
||||
<div class="filter-actions-end">
|
||||
<el-button class="btn-ghost" @click="handleReset"><el-icon><RefreshLeft /></el-icon>重置</el-button>
|
||||
<el-button class="btn-main" type="primary" @click="handleSearch"><el-icon><Search /></el-icon>搜索</el-button>
|
||||
<el-button class="btn-ghost" @click="handleReset"
|
||||
><el-icon><RefreshLeft /></el-icon>重置</el-button
|
||||
>
|
||||
<el-button class="btn-main" type="primary" @click="handleSearch"
|
||||
><el-icon><Search /></el-icon>搜索</el-button
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</el-form>
|
||||
@@ -107,12 +111,12 @@
|
||||
/>
|
||||
<el-table-column label="领用人" min-width="100" align="center" header-align="center">
|
||||
<template #default="{ row }">
|
||||
{{ row.changeType === 'ck' ? (row.receiver || '-') : '-' }}
|
||||
{{ row.changeType === 'ck' ? row.receiver || '-' : '-' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="用途" min-width="120" align="center" header-align="center">
|
||||
<template #default="{ row }">
|
||||
{{ row.changeType === 'ck' ? (row.purpose || '-') : '-' }}
|
||||
{{ row.changeType === 'ck' ? row.purpose || '-' : '-' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作人" min-width="100" align="center" header-align="center">
|
||||
@@ -120,7 +124,13 @@
|
||||
{{ row.createByName || row.createBy || '-' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="备注" min-width="140" align="center" header-align="center" show-overflow-tooltip>
|
||||
<el-table-column
|
||||
label="备注"
|
||||
min-width="140"
|
||||
align="center"
|
||||
header-align="center"
|
||||
show-overflow-tooltip
|
||||
>
|
||||
<template #default="{ row }">
|
||||
{{ row.remark || '-' }}
|
||||
</template>
|
||||
@@ -132,7 +142,13 @@
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column label="操作" min-width="160" fixed="right" align="center" header-align="center">
|
||||
<el-table-column
|
||||
label="操作"
|
||||
min-width="160"
|
||||
fixed="right"
|
||||
align="center"
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<el-button class="btn-op-item" @click="handleView(row)">
|
||||
<el-icon><View /></el-icon>查看
|
||||
@@ -208,8 +224,8 @@
|
||||
{ label: '变动数量', value: formatChangeQty(row).text },
|
||||
{ label: '变动前库存', value: String(row.stockBefore ?? 0) },
|
||||
{ label: '变动后库存', value: String(row.stockAfter ?? 0) },
|
||||
{ label: '领用人', value: row.changeType === 'ck' ? (row.receiver || '-') : '-' },
|
||||
{ label: '用途', value: row.changeType === 'ck' ? (row.purpose || '-') : '-' },
|
||||
{ label: '领用人', value: row.changeType === 'ck' ? row.receiver || '-' : '-' },
|
||||
{ label: '用途', value: row.changeType === 'ck' ? row.purpose || '-' : '-' },
|
||||
{ label: '操作人', value: row.createByName || row.createBy || '-' },
|
||||
{ label: '备注', value: row.remark || '—', span: true },
|
||||
{ label: '操作时间', value: row.createTime }
|
||||
@@ -264,13 +280,10 @@
|
||||
)
|
||||
|
||||
// 日期范围变更时自动搜索
|
||||
watch(
|
||||
dateRange,
|
||||
() => {
|
||||
pagination.page = 1
|
||||
loadData()
|
||||
}
|
||||
)
|
||||
watch(dateRange, () => {
|
||||
pagination.page = 1
|
||||
loadData()
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
loadData()
|
||||
|
||||
@@ -38,8 +38,12 @@
|
||||
<div class="filter-actions-row">
|
||||
<span class="filter-hint">按编码、名称模糊查询,可按分类、状态筛选。</span>
|
||||
<div class="filter-actions-end">
|
||||
<el-button class="btn-ghost" @click="handleReset"><el-icon><RefreshLeft /></el-icon>重置</el-button>
|
||||
<el-button class="btn-main" type="primary" @click="handleSearch"><el-icon><Search /></el-icon>搜索</el-button>
|
||||
<el-button class="btn-ghost" @click="handleReset"
|
||||
><el-icon><RefreshLeft /></el-icon>重置</el-button
|
||||
>
|
||||
<el-button class="btn-main" type="primary" @click="handleSearch"
|
||||
><el-icon><Search /></el-icon>搜索</el-button
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</el-form>
|
||||
@@ -154,15 +158,29 @@
|
||||
header-align="center"
|
||||
show-overflow-tooltip
|
||||
/>
|
||||
<el-table-column label="操作" min-width="400" fixed="right" align="center" header-align="center">
|
||||
<el-table-column
|
||||
label="操作"
|
||||
min-width="400"
|
||||
fixed="right"
|
||||
align="center"
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<el-button class="btn-op-item" @click="handleStockIn(row)"><el-icon><Upload /></el-icon>入库</el-button>
|
||||
<el-button class="btn-op-item" @click="handleStockOut(row)"><el-icon><Download /></el-icon>出库</el-button>
|
||||
<el-button class="btn-op-item" @click="handleStockIn(row)"
|
||||
><el-icon><Upload /></el-icon>入库</el-button
|
||||
>
|
||||
<el-button class="btn-op-item" @click="handleStockOut(row)"
|
||||
><el-icon><Download /></el-icon>出库</el-button
|
||||
>
|
||||
<el-button class="btn-op-item" @click="handleView(row)"
|
||||
><el-icon><View /></el-icon>查看</el-button
|
||||
>
|
||||
<el-button class="btn-op-item primary" @click="handleEdit(row)"><el-icon><Edit /></el-icon>编辑</el-button>
|
||||
<el-button class="btn-op-item" @click="handleDelete(row)"><el-icon><Delete /></el-icon>删除</el-button>
|
||||
<el-button class="btn-op-item primary" @click="handleEdit(row)"
|
||||
><el-icon><Edit /></el-icon>编辑</el-button
|
||||
>
|
||||
<el-button class="btn-op-item" @click="handleDelete(row)"
|
||||
><el-icon><Delete /></el-icon>删除</el-button
|
||||
>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
@@ -238,7 +256,9 @@
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="预警库存">
|
||||
<template #label>预警库存<span class="form-label-tip">低于该值时列表显示库存预警</span></template>
|
||||
<template #label
|
||||
>预警库存<span class="form-label-tip">低于该值时列表显示库存预警</span></template
|
||||
>
|
||||
<el-input-number
|
||||
v-model="formData.warningQty"
|
||||
:min="0"
|
||||
@@ -255,12 +275,7 @@
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="备注" class="form-col-span-2">
|
||||
<el-input
|
||||
v-model="formData.remark"
|
||||
type="textarea"
|
||||
:rows="3"
|
||||
placeholder="可选"
|
||||
/>
|
||||
<el-input v-model="formData.remark" type="textarea" :rows="3" placeholder="可选" />
|
||||
</el-form-item>
|
||||
</div>
|
||||
</el-form>
|
||||
@@ -314,12 +329,7 @@
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="备注">
|
||||
<el-input
|
||||
v-model="stockInForm.remark"
|
||||
type="textarea"
|
||||
:rows="3"
|
||||
placeholder="可选"
|
||||
/>
|
||||
<el-input v-model="stockInForm.remark" type="textarea" :rows="3" placeholder="可选" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
@@ -397,20 +407,10 @@
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="用途">
|
||||
<el-input
|
||||
v-model="stockOutForm.purpose"
|
||||
type="textarea"
|
||||
:rows="2"
|
||||
placeholder="可选"
|
||||
/>
|
||||
<el-input v-model="stockOutForm.purpose" type="textarea" :rows="2" placeholder="可选" />
|
||||
</el-form-item>
|
||||
<el-form-item label="备注">
|
||||
<el-input
|
||||
v-model="stockOutForm.remark"
|
||||
type="textarea"
|
||||
:rows="2"
|
||||
placeholder="可选"
|
||||
/>
|
||||
<el-input v-model="stockOutForm.remark" type="textarea" :rows="2" placeholder="可选" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
@@ -511,13 +511,41 @@
|
||||
header-align="center"
|
||||
:index="(index) => index + 1"
|
||||
/>
|
||||
<el-table-column prop="materialCode" label="物料编码" min-width="120" align="center" header-align="center" />
|
||||
<el-table-column prop="materialName" label="物料名称" min-width="140" align="center" header-align="center" />
|
||||
<el-table-column prop="unit" label="单位" min-width="80" align="center" header-align="center" />
|
||||
<el-table-column label="当前库存" min-width="100" align="center" header-align="center">
|
||||
<el-table-column
|
||||
prop="materialCode"
|
||||
label="物料编码"
|
||||
min-width="120"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="materialName"
|
||||
label="物料名称"
|
||||
min-width="140"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="unit"
|
||||
label="单位"
|
||||
min-width="80"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
label="当前库存"
|
||||
min-width="100"
|
||||
align="center"
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }">{{ row.stockQty }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="入库数量" min-width="130" align="center" header-align="center">
|
||||
<el-table-column
|
||||
label="入库数量"
|
||||
min-width="130"
|
||||
align="center"
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<el-input-number
|
||||
v-model="row.qty"
|
||||
@@ -531,22 +559,28 @@
|
||||
</el-table-column>
|
||||
<el-table-column label="备注" min-width="160" align="center" header-align="center">
|
||||
<template #default="{ row }">
|
||||
<el-input v-model="row.remark" placeholder="可选" clearable class="inner-cell-input" />
|
||||
<el-input
|
||||
v-model="row.remark"
|
||||
placeholder="可选"
|
||||
clearable
|
||||
class="inner-cell-input"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" min-width="80" align="center" header-align="center">
|
||||
<template #default="{ $index }">
|
||||
<el-button link type="danger" size="small" @click="batchStockInLines.splice($index, 1)">
|
||||
<el-button
|
||||
link
|
||||
type="danger"
|
||||
size="small"
|
||||
@click="batchStockInLines.splice($index, 1)"
|
||||
>
|
||||
移除
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<el-empty
|
||||
v-else
|
||||
description="请通过上方搜索框添加要入库的物料"
|
||||
:image-size="56"
|
||||
/>
|
||||
<el-empty v-else description="请通过上方搜索框添加要入库的物料" :image-size="56" />
|
||||
</div>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
@@ -675,13 +709,41 @@
|
||||
header-align="center"
|
||||
:index="(index) => index + 1"
|
||||
/>
|
||||
<el-table-column prop="materialCode" label="物料编码" min-width="120" align="center" header-align="center" />
|
||||
<el-table-column prop="materialName" label="物料名称" min-width="140" align="center" header-align="center" />
|
||||
<el-table-column prop="unit" label="单位" min-width="80" align="center" header-align="center" />
|
||||
<el-table-column label="当前库存" min-width="100" align="center" header-align="center">
|
||||
<el-table-column
|
||||
prop="materialCode"
|
||||
label="物料编码"
|
||||
min-width="120"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="materialName"
|
||||
label="物料名称"
|
||||
min-width="140"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="unit"
|
||||
label="单位"
|
||||
min-width="80"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
label="当前库存"
|
||||
min-width="100"
|
||||
align="center"
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }">{{ row.stockQty }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="出库数量" min-width="130" align="center" header-align="center">
|
||||
<el-table-column
|
||||
label="出库数量"
|
||||
min-width="130"
|
||||
align="center"
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<el-input-number
|
||||
v-model="row.qty"
|
||||
@@ -696,22 +758,28 @@
|
||||
</el-table-column>
|
||||
<el-table-column label="备注" min-width="160" align="center" header-align="center">
|
||||
<template #default="{ row }">
|
||||
<el-input v-model="row.remark" placeholder="可选" clearable class="inner-cell-input" />
|
||||
<el-input
|
||||
v-model="row.remark"
|
||||
placeholder="可选"
|
||||
clearable
|
||||
class="inner-cell-input"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" min-width="80" align="center" header-align="center">
|
||||
<template #default="{ $index }">
|
||||
<el-button link type="danger" size="small" @click="batchStockOutLines.splice($index, 1)">
|
||||
<el-button
|
||||
link
|
||||
type="danger"
|
||||
size="small"
|
||||
@click="batchStockOutLines.splice($index, 1)"
|
||||
>
|
||||
移除
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<el-empty
|
||||
v-else
|
||||
description="请通过上方搜索框添加要出库的物料"
|
||||
:image-size="56"
|
||||
/>
|
||||
<el-empty v-else description="请通过上方搜索框添加要出库的物料" :image-size="56" />
|
||||
</div>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
@@ -746,7 +814,19 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, onMounted, computed } from 'vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { Plus, Close, Check, View, Box, RefreshLeft, Search, Upload, Download, Edit, Delete } from '@element-plus/icons-vue'
|
||||
import {
|
||||
Plus,
|
||||
Close,
|
||||
Check,
|
||||
View,
|
||||
Box,
|
||||
RefreshLeft,
|
||||
Search,
|
||||
Upload,
|
||||
Download,
|
||||
Edit,
|
||||
Delete
|
||||
} from '@element-plus/icons-vue'
|
||||
import SystemViewDialog from '@/components/business/system-view-dialog/index.vue'
|
||||
import { fetchDictDataOptions, fetchItemUnitList, fetchUserList } from '@/api/admin'
|
||||
import {
|
||||
@@ -989,7 +1069,13 @@
|
||||
{
|
||||
label: '物料分类',
|
||||
value: getCategoryLabel(row.category),
|
||||
tagType: getCategoryTagType(row.category) as 'success' | 'warning' | 'danger' | 'info' | '' | 'primary'
|
||||
tagType: getCategoryTagType(row.category) as
|
||||
| 'success'
|
||||
| 'warning'
|
||||
| 'danger'
|
||||
| 'info'
|
||||
| ''
|
||||
| 'primary'
|
||||
},
|
||||
{ label: '规格型号', value: row.specModel },
|
||||
{ label: '单位', value: row.unit },
|
||||
|
||||
@@ -55,7 +55,8 @@
|
||||
</div>
|
||||
<div class="filter-actions-row">
|
||||
<span class="filter-hint"
|
||||
>支持协议号、客户姓名、身份证号、新生儿姓名、新生儿身份证号模糊检索;默认展示近 30 天内应缴费的明细。</span
|
||||
>支持协议号、客户姓名、身份证号、新生儿姓名、新生儿身份证号模糊检索;默认展示近 30
|
||||
天内应缴费的明细。</span
|
||||
>
|
||||
<div class="filter-actions-end">
|
||||
<el-button class="btn-ghost" @click="handleReset">
|
||||
@@ -70,7 +71,14 @@
|
||||
</div>
|
||||
|
||||
<div class="list-body list-wrap payment-confirm-table-wrap">
|
||||
<el-table class="list-table" :data="tableData" v-loading="loading" stripe row-key="id" @row-dblclick="handleRowDblClick">
|
||||
<el-table
|
||||
class="list-table"
|
||||
:data="tableData"
|
||||
v-loading="loading"
|
||||
stripe
|
||||
row-key="id"
|
||||
@row-dblclick="handleRowDblClick"
|
||||
>
|
||||
<el-table-column
|
||||
type="index"
|
||||
label="序号"
|
||||
@@ -79,14 +87,38 @@
|
||||
header-align="center"
|
||||
:index="(index) => (pagination.page - 1) * pagination.pageSize + index + 1"
|
||||
/>
|
||||
<el-table-column prop="protocolNo" label="协议号" min-width="130" align="center" header-align="center" />
|
||||
<el-table-column prop="motherName" label="客户姓名" min-width="100" align="center" header-align="center">
|
||||
<el-table-column
|
||||
prop="protocolNo"
|
||||
label="协议号"
|
||||
min-width="130"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="motherName"
|
||||
label="客户姓名"
|
||||
min-width="100"
|
||||
align="center"
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }">{{ row.motherName || '-' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="motherIdCard" label="身份证号" min-width="170" align="center" header-align="center">
|
||||
<el-table-column
|
||||
prop="motherIdCard"
|
||||
label="身份证号"
|
||||
min-width="170"
|
||||
align="center"
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }">{{ maskIdCard(row.motherIdCard) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="newbornName" label="新生儿姓名" min-width="110" align="center" header-align="center">
|
||||
<el-table-column
|
||||
prop="newbornName"
|
||||
label="新生儿姓名"
|
||||
min-width="110"
|
||||
align="center"
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }">{{ row.newbornName || '-' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
@@ -98,33 +130,95 @@
|
||||
>
|
||||
<template #default="{ row }">{{ maskIdCard(row.newbornIdCard) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="protocolStatus" label="协议状态" min-width="100" align="center" header-align="center">
|
||||
<el-table-column
|
||||
prop="protocolStatus"
|
||||
label="协议状态"
|
||||
min-width="100"
|
||||
align="center"
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="protocolStatusTagType(String(row.protocolStatus || ''))" size="small">{{ protocolStatusLabel(String(row.protocolStatus || '')) }}</el-tag>
|
||||
<el-tag
|
||||
:type="protocolStatusTagType(String(row.protocolStatus || ''))"
|
||||
size="small"
|
||||
>{{ protocolStatusLabel(String(row.protocolStatus || '')) }}</el-tag
|
||||
>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="payMode" label="缴费方式" min-width="100" align="center" header-align="center">
|
||||
<el-table-column
|
||||
prop="payMode"
|
||||
label="缴费方式"
|
||||
min-width="100"
|
||||
align="center"
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }">{{ payModeLabel(String(row.payMode || '')) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="installmentNo" label="期数" min-width="80" align="center" header-align="center">
|
||||
<el-table-column
|
||||
prop="installmentNo"
|
||||
label="期数"
|
||||
min-width="80"
|
||||
align="center"
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }">{{ row.installmentNo ?? '-' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="installmentDueDate" label="应缴费时间" min-width="120" align="center" header-align="center">
|
||||
<template #default="{ row }">{{ formatPaymentDueDate(row.installmentDueDate) }}</template>
|
||||
<el-table-column
|
||||
prop="installmentDueDate"
|
||||
label="应缴费时间"
|
||||
min-width="120"
|
||||
align="center"
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }">{{
|
||||
formatPaymentDueDate(row.installmentDueDate)
|
||||
}}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="dueAmount" label="应缴金额(元)" min-width="120" align="center" header-align="center">
|
||||
<el-table-column
|
||||
prop="dueAmount"
|
||||
label="应缴金额(元)"
|
||||
min-width="120"
|
||||
align="center"
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }">{{ formatPaymentAmount(row.dueAmount) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="transferAmount" label="实缴金额(元)" min-width="120" align="center" header-align="center">
|
||||
<el-table-column
|
||||
prop="transferAmount"
|
||||
label="实缴金额(元)"
|
||||
min-width="120"
|
||||
align="center"
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }">{{ formatPaymentAmount(row.transferAmount) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="transferType" label="转账方式" min-width="110" align="center" header-align="center">
|
||||
<template #default="{ row }">{{ transferTypeLabel(String(row.transferType || '')) }}</template>
|
||||
<el-table-column
|
||||
prop="transferType"
|
||||
label="转账方式"
|
||||
min-width="110"
|
||||
align="center"
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }">{{
|
||||
transferTypeLabel(String(row.transferType || ''))
|
||||
}}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="transferTime" label="转账时间" min-width="170" align="center" header-align="center">
|
||||
<el-table-column
|
||||
prop="transferTime"
|
||||
label="转账时间"
|
||||
min-width="170"
|
||||
align="center"
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }">{{ row.transferTime || '-' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="payStatus" label="状态" min-width="110" align="center" header-align="center">
|
||||
<el-table-column
|
||||
prop="payStatus"
|
||||
label="状态"
|
||||
min-width="110"
|
||||
align="center"
|
||||
header-align="center"
|
||||
>
|
||||
<template #default>
|
||||
<el-tag :type="activeTab === 'pending' ? 'warning' : 'success'" size="small">
|
||||
{{ activeTab === 'pending' ? '缴费确认中' : '已缴费' }}
|
||||
@@ -158,7 +252,13 @@
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column label="操作" min-width="140" fixed="right" align="center" header-align="center">
|
||||
<el-table-column
|
||||
label="操作"
|
||||
min-width="140"
|
||||
fixed="right"
|
||||
align="center"
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<el-button
|
||||
v-if="activeTab === 'pending'"
|
||||
@@ -167,7 +267,9 @@
|
||||
>
|
||||
确认收款
|
||||
</el-button>
|
||||
<el-button v-else class="btn-op-item" @click="openViewConfirm(row)">查看确认信息</el-button>
|
||||
<el-button v-else class="btn-op-item" @click="openViewConfirm(row)"
|
||||
>查看确认信息</el-button
|
||||
>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
@@ -213,7 +315,12 @@
|
||||
<el-icon><Close /></el-icon>
|
||||
取消
|
||||
</el-button>
|
||||
<el-button class="btn-main" type="primary" :loading="submitLoading" @click="submitConfirm">
|
||||
<el-button
|
||||
class="btn-main"
|
||||
type="primary"
|
||||
:loading="submitLoading"
|
||||
@click="submitConfirm"
|
||||
>
|
||||
<el-icon><Check /></el-icon>
|
||||
确认
|
||||
</el-button>
|
||||
|
||||
@@ -4,7 +4,9 @@
|
||||
<div class="page-container">
|
||||
<header class="page-head">
|
||||
<h1>缴费信息</h1>
|
||||
<p class="page-desc">查看采集协议单缴费明细,按状态分为待缴费、缴费确认中、已缴费、退费确认中、已退费。</p>
|
||||
<p class="page-desc"
|
||||
>查看采集协议单缴费明细,按状态分为待缴费、缴费确认中、已缴费、退费确认中、已退费。</p
|
||||
>
|
||||
</header>
|
||||
|
||||
<div class="region-tabs region-tab-strip">
|
||||
@@ -74,10 +76,7 @@
|
||||
</el-form>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="activeTab === 'dfj' || activeTab === 'yjf'"
|
||||
class="list-toolbar"
|
||||
>
|
||||
<div v-if="activeTab === 'dfj' || activeTab === 'yjf'" class="list-toolbar">
|
||||
<div class="list-toolbar-row">
|
||||
<div class="list-toolbar-selected">
|
||||
已选 <span class="sel-num">{{ selectedRows.length }}</span> 项
|
||||
@@ -178,7 +177,11 @@
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="protocolStatusTagType(String(row.protocolStatus || ''))" size="small">{{ protocolStatusLabel(String(row.protocolStatus || '')) }}</el-tag>
|
||||
<el-tag
|
||||
:type="protocolStatusTagType(String(row.protocolStatus || ''))"
|
||||
size="small"
|
||||
>{{ protocolStatusLabel(String(row.protocolStatus || '')) }}</el-tag
|
||||
>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
@@ -379,8 +382,14 @@
|
||||
</template>
|
||||
</div>
|
||||
<div class="attachment-footer">
|
||||
<span class="attachment-name" :title="file.fileName">{{ file.fileName }}</span>
|
||||
<el-icon class="action-btn" title="删除" @click="removeRefundAttachment(fIdx)">
|
||||
<span class="attachment-name" :title="file.fileName">{{
|
||||
file.fileName
|
||||
}}</span>
|
||||
<el-icon
|
||||
class="action-btn"
|
||||
title="删除"
|
||||
@click="removeRefundAttachment(fIdx)"
|
||||
>
|
||||
<Delete />
|
||||
</el-icon>
|
||||
</div>
|
||||
@@ -580,11 +589,15 @@
|
||||
const id = String(row.id || '')
|
||||
if (!id) return
|
||||
try {
|
||||
await ElMessageBox.confirm('确认对该缴费明细进行缴费确认?提交后状态变为「缴费确认中」。', '缴费确认', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
})
|
||||
await ElMessageBox.confirm(
|
||||
'确认对该缴费明细进行缴费确认?提交后状态变为「缴费确认中」。',
|
||||
'缴费确认',
|
||||
{
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}
|
||||
)
|
||||
await fetchSubmitReceiptFinance(id)
|
||||
loadData()
|
||||
} catch {
|
||||
@@ -625,21 +638,12 @@
|
||||
refundFormRef.value?.clearValidate()
|
||||
}
|
||||
|
||||
function openRefundFinanceDialog(
|
||||
row?: Record<string, unknown>,
|
||||
batchIds?: string[]
|
||||
) {
|
||||
const ids = batchIds?.length
|
||||
? batchIds
|
||||
: row?.id
|
||||
? [String(row.id)]
|
||||
: []
|
||||
function openRefundFinanceDialog(row?: Record<string, unknown>, batchIds?: string[]) {
|
||||
const ids = batchIds?.length ? batchIds : row?.id ? [String(row.id)] : []
|
||||
if (!ids.length) return
|
||||
refundTargetIds.value = ids
|
||||
const defaultAmount =
|
||||
!batchIds?.length && row?.transferAmount != null
|
||||
? Number(row.transferAmount)
|
||||
: undefined
|
||||
!batchIds?.length && row?.transferAmount != null ? Number(row.transferAmount) : undefined
|
||||
resetRefundForm(
|
||||
defaultAmount != null && !Number.isNaN(defaultAmount) ? defaultAmount : undefined
|
||||
)
|
||||
|
||||
@@ -16,7 +16,11 @@
|
||||
<el-form :model="searchForm" class="filter-form" @submit.prevent>
|
||||
<div class="filter-grid">
|
||||
<el-form-item label="关键词">
|
||||
<el-input v-model="searchForm.keyword" placeholder="协议号/姓名/身份证号/电话" clearable />
|
||||
<el-input
|
||||
v-model="searchForm.keyword"
|
||||
placeholder="协议号/姓名/身份证号/电话"
|
||||
clearable
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="协议状态">
|
||||
<el-select
|
||||
@@ -57,7 +61,9 @@
|
||||
</el-form-item>
|
||||
</div>
|
||||
<div class="filter-actions-row">
|
||||
<span class="filter-hint">列表为概要信息,详情见弹窗;默认展示近 30 天内创建的协议单。</span>
|
||||
<span class="filter-hint"
|
||||
>列表为概要信息,详情见弹窗;默认展示近 30 天内创建的协议单。</span
|
||||
>
|
||||
<div class="filter-actions-end">
|
||||
<el-button class="btn-ghost" @click="handleReset"
|
||||
><el-icon><RefreshLeft /></el-icon>重置</el-button
|
||||
@@ -83,16 +89,32 @@
|
||||
<el-icon><CircleCheck /></el-icon>
|
||||
审核
|
||||
</el-button>
|
||||
<el-button class="btn-ghost" :disabled="!selectedRows.length" @click="openStatusChangeDialog('activate')">
|
||||
<el-button
|
||||
class="btn-ghost"
|
||||
:disabled="!selectedRows.length"
|
||||
@click="openStatusChangeDialog('activate')"
|
||||
>
|
||||
协议生效
|
||||
</el-button>
|
||||
<el-button class="btn-ghost" :disabled="!selectedRows.length" @click="openStatusChangeDialog('expire')">
|
||||
<el-button
|
||||
class="btn-ghost"
|
||||
:disabled="!selectedRows.length"
|
||||
@click="openStatusChangeDialog('expire')"
|
||||
>
|
||||
协议过期
|
||||
</el-button>
|
||||
<el-button class="btn-ghost" :disabled="!selectedRows.length" @click="openStatusChangeDialog('void')">
|
||||
<el-button
|
||||
class="btn-ghost"
|
||||
:disabled="!selectedRows.length"
|
||||
@click="openStatusChangeDialog('void')"
|
||||
>
|
||||
协议作废
|
||||
</el-button>
|
||||
<el-button class="btn-ghost" :disabled="!selectedRows.length" @click="openStatusChangeDialog('toResearch')">
|
||||
<el-button
|
||||
class="btn-ghost"
|
||||
:disabled="!selectedRows.length"
|
||||
@click="openStatusChangeDialog('toResearch')"
|
||||
>
|
||||
协议转科研
|
||||
</el-button>
|
||||
<el-button class="btn-ghost" :disabled="!selectedRows.length" @click="handleBatchDelete">
|
||||
@@ -136,9 +158,11 @@
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="protocolStatusTagType(String(row.protocolStatus || ''))" size="small">{{
|
||||
protocolStatusLabel(String(row.protocolStatus || ''))
|
||||
}}</el-tag>
|
||||
<el-tag
|
||||
:type="protocolStatusTagType(String(row.protocolStatus || ''))"
|
||||
size="small"
|
||||
>{{ protocolStatusLabel(String(row.protocolStatus || '')) }}</el-tag
|
||||
>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
@@ -345,7 +369,12 @@
|
||||
fetchDeleteCollectionProtocol,
|
||||
type CollectionProtocolRow
|
||||
} from '@/api/collection-protocol'
|
||||
import { protocolStatusLabel, protocolStatusTagType, newbornSexLabel, deliveryModeLabel } from '@/utils/status-tag-registry'
|
||||
import {
|
||||
protocolStatusLabel,
|
||||
protocolStatusTagType,
|
||||
newbornSexLabel,
|
||||
deliveryModeLabel
|
||||
} from '@/utils/status-tag-registry'
|
||||
|
||||
defineOptions({ name: 'CellStorageProtocolAudit' })
|
||||
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -787,9 +787,7 @@
|
||||
<div class="pdc-info-row">
|
||||
<div class="pdc-info-item">
|
||||
<span class="info-label">应缴费金额(元)</span>
|
||||
<span class="info-value">{{
|
||||
fmtMoney(detail.dueAmount)
|
||||
}}</span>
|
||||
<span class="info-value">{{ fmtMoney(detail.dueAmount) }}</span>
|
||||
</div>
|
||||
<div class="pdc-info-item">
|
||||
<span class="info-label">转账方式</span>
|
||||
@@ -799,9 +797,7 @@
|
||||
</div>
|
||||
<div class="pdc-info-item">
|
||||
<span class="info-label">转账金额(元)</span>
|
||||
<span class="info-value">{{
|
||||
fmtMoney(detail.transferAmount)
|
||||
}}</span>
|
||||
<span class="info-value">{{ fmtMoney(detail.transferAmount) }}</span>
|
||||
</div>
|
||||
<div class="pdc-info-item">
|
||||
<span class="info-label">转账时间</span>
|
||||
|
||||
@@ -16,7 +16,11 @@
|
||||
<el-form :model="searchForm" class="filter-form" @submit.prevent>
|
||||
<div class="filter-grid">
|
||||
<el-form-item label="关键词">
|
||||
<el-input v-model="searchForm.keyword" placeholder="协议号/姓名/身份证号/电话" clearable />
|
||||
<el-input
|
||||
v-model="searchForm.keyword"
|
||||
placeholder="协议号/姓名/身份证号/电话"
|
||||
clearable
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="协议状态">
|
||||
<el-select
|
||||
@@ -57,7 +61,9 @@
|
||||
</el-form-item>
|
||||
</div>
|
||||
<div class="filter-actions-row">
|
||||
<span class="filter-hint">列表为概要信息,详情见弹窗;默认展示近 30 天内创建的协议单。</span>
|
||||
<span class="filter-hint"
|
||||
>列表为概要信息,详情见弹窗;默认展示近 30 天内创建的协议单。</span
|
||||
>
|
||||
<div class="filter-actions-end">
|
||||
<el-button class="btn-ghost" @click="handleReset"
|
||||
><el-icon><RefreshLeft /></el-icon>重置</el-button
|
||||
@@ -120,9 +126,11 @@
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="protocolStatusTagType(String(row.protocolStatus || ''))" size="small">{{
|
||||
protocolStatusLabel(String(row.protocolStatus || ''))
|
||||
}}</el-tag>
|
||||
<el-tag
|
||||
:type="protocolStatusTagType(String(row.protocolStatus || ''))"
|
||||
size="small"
|
||||
>{{ protocolStatusLabel(String(row.protocolStatus || '')) }}</el-tag
|
||||
>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
@@ -287,14 +295,7 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import {
|
||||
Plus,
|
||||
View,
|
||||
Edit,
|
||||
Delete,
|
||||
Search,
|
||||
RefreshLeft
|
||||
} from '@element-plus/icons-vue'
|
||||
import { Plus, View, Edit, Delete, Search, RefreshLeft } from '@element-plus/icons-vue'
|
||||
import ProtocolFormDialog from './ProtocolFormDialog.vue'
|
||||
import ProtocolViewDialog from './ProtocolViewDialog.vue'
|
||||
import { withFullscreenLoading, fullscreenLoadingText } from '@/utils/fullscreen-loading'
|
||||
@@ -303,7 +304,12 @@
|
||||
fetchDeleteCollectionProtocol,
|
||||
type CollectionProtocolRow
|
||||
} from '@/api/collection-protocol'
|
||||
import { protocolStatusLabel, protocolStatusTagType, newbornSexLabel, deliveryModeLabel } from '@/utils/status-tag-registry'
|
||||
import {
|
||||
protocolStatusLabel,
|
||||
protocolStatusTagType,
|
||||
newbornSexLabel,
|
||||
deliveryModeLabel
|
||||
} from '@/utils/status-tag-registry'
|
||||
|
||||
defineOptions({ name: 'CellStorageProtocol' })
|
||||
|
||||
|
||||
@@ -55,7 +55,8 @@
|
||||
</div>
|
||||
<div class="filter-actions-row">
|
||||
<span class="filter-hint"
|
||||
>支持协议号、客户姓名、身份证号、新生儿姓名、新生儿身份证号模糊检索;默认展示近 30 天内应缴费的明细。</span
|
||||
>支持协议号、客户姓名、身份证号、新生儿姓名、新生儿身份证号模糊检索;默认展示近 30
|
||||
天内应缴费的明细。</span
|
||||
>
|
||||
<div class="filter-actions-end">
|
||||
<el-button class="btn-ghost" @click="handleReset">
|
||||
@@ -70,7 +71,14 @@
|
||||
</div>
|
||||
|
||||
<div class="list-body list-wrap refund-confirm-table-wrap">
|
||||
<el-table class="list-table" :data="tableData" v-loading="loading" stripe row-key="id" @row-dblclick="handleRowDblClick">
|
||||
<el-table
|
||||
class="list-table"
|
||||
:data="tableData"
|
||||
v-loading="loading"
|
||||
stripe
|
||||
row-key="id"
|
||||
@row-dblclick="handleRowDblClick"
|
||||
>
|
||||
<el-table-column
|
||||
type="index"
|
||||
label="序号"
|
||||
@@ -79,14 +87,38 @@
|
||||
header-align="center"
|
||||
:index="(index) => (pagination.page - 1) * pagination.pageSize + index + 1"
|
||||
/>
|
||||
<el-table-column prop="protocolNo" label="协议号" min-width="130" align="center" header-align="center" />
|
||||
<el-table-column prop="motherName" label="客户姓名" min-width="100" align="center" header-align="center">
|
||||
<el-table-column
|
||||
prop="protocolNo"
|
||||
label="协议号"
|
||||
min-width="130"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="motherName"
|
||||
label="客户姓名"
|
||||
min-width="100"
|
||||
align="center"
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }">{{ row.motherName || '-' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="motherIdCard" label="身份证号" min-width="170" align="center" header-align="center">
|
||||
<el-table-column
|
||||
prop="motherIdCard"
|
||||
label="身份证号"
|
||||
min-width="170"
|
||||
align="center"
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }">{{ maskIdCard(row.motherIdCard) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="newbornName" label="新生儿姓名" min-width="110" align="center" header-align="center">
|
||||
<el-table-column
|
||||
prop="newbornName"
|
||||
label="新生儿姓名"
|
||||
min-width="110"
|
||||
align="center"
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }">{{ row.newbornName || '-' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
@@ -98,33 +130,95 @@
|
||||
>
|
||||
<template #default="{ row }">{{ maskIdCard(row.newbornIdCard) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="protocolStatus" label="协议状态" min-width="100" align="center" header-align="center">
|
||||
<el-table-column
|
||||
prop="protocolStatus"
|
||||
label="协议状态"
|
||||
min-width="100"
|
||||
align="center"
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="protocolStatusTagType(String(row.protocolStatus || ''))" size="small">{{ protocolStatusLabel(String(row.protocolStatus || '')) }}</el-tag>
|
||||
<el-tag
|
||||
:type="protocolStatusTagType(String(row.protocolStatus || ''))"
|
||||
size="small"
|
||||
>{{ protocolStatusLabel(String(row.protocolStatus || '')) }}</el-tag
|
||||
>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="payMode" label="缴费方式" min-width="100" align="center" header-align="center">
|
||||
<el-table-column
|
||||
prop="payMode"
|
||||
label="缴费方式"
|
||||
min-width="100"
|
||||
align="center"
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }">{{ payModeLabel(String(row.payMode || '')) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="installmentNo" label="期数" min-width="80" align="center" header-align="center">
|
||||
<el-table-column
|
||||
prop="installmentNo"
|
||||
label="期数"
|
||||
min-width="80"
|
||||
align="center"
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }">{{ row.installmentNo ?? '-' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="installmentDueDate" label="应缴费时间" min-width="120" align="center" header-align="center">
|
||||
<template #default="{ row }">{{ formatPaymentDueDate(row.installmentDueDate) }}</template>
|
||||
<el-table-column
|
||||
prop="installmentDueDate"
|
||||
label="应缴费时间"
|
||||
min-width="120"
|
||||
align="center"
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }">{{
|
||||
formatPaymentDueDate(row.installmentDueDate)
|
||||
}}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="dueAmount" label="应缴金额(元)" min-width="120" align="center" header-align="center">
|
||||
<el-table-column
|
||||
prop="dueAmount"
|
||||
label="应缴金额(元)"
|
||||
min-width="120"
|
||||
align="center"
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }">{{ formatPaymentAmount(row.dueAmount) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="transferAmount" label="实缴金额(元)" min-width="120" align="center" header-align="center">
|
||||
<el-table-column
|
||||
prop="transferAmount"
|
||||
label="实缴金额(元)"
|
||||
min-width="120"
|
||||
align="center"
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }">{{ formatPaymentAmount(row.transferAmount) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="transferType" label="转账方式" min-width="110" align="center" header-align="center">
|
||||
<template #default="{ row }">{{ transferTypeLabel(String(row.transferType || '')) }}</template>
|
||||
<el-table-column
|
||||
prop="transferType"
|
||||
label="转账方式"
|
||||
min-width="110"
|
||||
align="center"
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }">{{
|
||||
transferTypeLabel(String(row.transferType || ''))
|
||||
}}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="transferTime" label="转账时间" min-width="170" align="center" header-align="center">
|
||||
<el-table-column
|
||||
prop="transferTime"
|
||||
label="转账时间"
|
||||
min-width="170"
|
||||
align="center"
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }">{{ row.transferTime || '-' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="refundAmount" label="退费金额(元)" min-width="120" align="center" header-align="center">
|
||||
<el-table-column
|
||||
prop="refundAmount"
|
||||
label="退费金额(元)"
|
||||
min-width="120"
|
||||
align="center"
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }">{{ formatPaymentAmount(row.refundAmount) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
@@ -137,7 +231,13 @@
|
||||
>
|
||||
<template #default="{ row }">{{ row.refundRemark || '-' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="payStatus" label="状态" min-width="110" align="center" header-align="center">
|
||||
<el-table-column
|
||||
prop="payStatus"
|
||||
label="状态"
|
||||
min-width="110"
|
||||
align="center"
|
||||
header-align="center"
|
||||
>
|
||||
<template #default>
|
||||
<el-tag :type="activeTab === 'pending' ? 'warning' : 'success'" size="small">
|
||||
{{ activeTab === 'pending' ? '退费确认中' : '已退费' }}
|
||||
@@ -171,7 +271,13 @@
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column label="操作" min-width="140" fixed="right" align="center" header-align="center">
|
||||
<el-table-column
|
||||
label="操作"
|
||||
min-width="140"
|
||||
fixed="right"
|
||||
align="center"
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<el-button
|
||||
v-if="activeTab === 'pending'"
|
||||
@@ -180,7 +286,9 @@
|
||||
>
|
||||
确认退费
|
||||
</el-button>
|
||||
<el-button v-else class="btn-op-item" @click="openViewConfirm(row)">查看确认信息</el-button>
|
||||
<el-button v-else class="btn-op-item" @click="openViewConfirm(row)"
|
||||
>查看确认信息</el-button
|
||||
>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
@@ -228,7 +336,12 @@
|
||||
<el-icon><Close /></el-icon>
|
||||
取消
|
||||
</el-button>
|
||||
<el-button class="btn-main" type="primary" :loading="submitLoading" @click="submitConfirm">
|
||||
<el-button
|
||||
class="btn-main"
|
||||
type="primary"
|
||||
:loading="submitLoading"
|
||||
@click="submitConfirm"
|
||||
>
|
||||
<el-icon><Check /></el-icon>
|
||||
确认
|
||||
</el-button>
|
||||
@@ -460,7 +573,8 @@
|
||||
submitLoading.value = true
|
||||
try {
|
||||
await fetchRefundConfirmSubmit(currentDetailId.value, {
|
||||
refundFinanceConfirmAttachments: financeAttachmentsToUrlString(attachments.value) || undefined,
|
||||
refundFinanceConfirmAttachments:
|
||||
financeAttachmentsToUrlString(attachments.value) || undefined,
|
||||
refundFinanceRemark: financeRemark.value.trim() || undefined
|
||||
})
|
||||
dialogVisible.value = false
|
||||
|
||||
@@ -18,14 +18,24 @@
|
||||
<el-input v-model="searchForm.keyword" placeholder="协议号/姓名/样本编码" clearable />
|
||||
</el-form-item>
|
||||
<el-form-item label="接收结论">
|
||||
<el-select v-model="searchForm.acceptConclusion" placeholder="全部" clearable style="width: 100%">
|
||||
<el-select
|
||||
v-model="searchForm.acceptConclusion"
|
||||
placeholder="全部"
|
||||
clearable
|
||||
style="width: 100%"
|
||||
>
|
||||
<el-option label="接收" value="js" />
|
||||
<el-option label="拒收" value="bj" />
|
||||
<el-option label="待接收确认" value="dqr" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="通知状态">
|
||||
<el-select v-model="searchForm.smsNotified" placeholder="全部" clearable style="width: 100%">
|
||||
<el-select
|
||||
v-model="searchForm.smsNotified"
|
||||
placeholder="全部"
|
||||
clearable
|
||||
style="width: 100%"
|
||||
>
|
||||
<el-option label="已通知" value="1" />
|
||||
<el-option label="未通知" value="0" />
|
||||
</el-select>
|
||||
@@ -43,7 +53,9 @@
|
||||
</el-form-item>
|
||||
</div>
|
||||
<div class="filter-actions-row">
|
||||
<span class="filter-hint">支持按协议号、姓名、样本编码模糊检索;默认展示近 30 天内接收的样本。</span>
|
||||
<span class="filter-hint"
|
||||
>支持按协议号、姓名、样本编码模糊检索;默认展示近 30 天内接收的样本。</span
|
||||
>
|
||||
<div class="filter-actions-end">
|
||||
<el-button class="btn-ghost" @click="handleReset">
|
||||
<el-icon><RefreshLeft /></el-icon>重置
|
||||
@@ -73,7 +85,13 @@
|
||||
header-align="center"
|
||||
:index="(i) => (pagination.page - 1) * pagination.pageSize + i + 1"
|
||||
/>
|
||||
<el-table-column prop="protocolNo" label="协议号" min-width="140" align="center" header-align="center" />
|
||||
<el-table-column
|
||||
prop="protocolNo"
|
||||
label="协议号"
|
||||
min-width="140"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="protocolStatus"
|
||||
label="协议状态"
|
||||
@@ -82,34 +100,98 @@
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="protocolStatusTagType(String(row.protocolStatus || ''))" size="small">{{
|
||||
protocolStatusLabel(String(row.protocolStatus || ''))
|
||||
}}</el-tag>
|
||||
<el-tag
|
||||
:type="protocolStatusTagType(String(row.protocolStatus || ''))"
|
||||
size="small"
|
||||
>{{ protocolStatusLabel(String(row.protocolStatus || '')) }}</el-tag
|
||||
>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="motherName" label="母亲姓名" min-width="100" align="center" header-align="center" />
|
||||
<el-table-column prop="newbornName" label="新生儿姓名" min-width="110" align="center" header-align="center" show-overflow-tooltip />
|
||||
<el-table-column prop="sampleCode" label="样本编码" min-width="130" align="center" header-align="center" />
|
||||
<el-table-column prop="sampleTypeName" label="样本类型" min-width="110" align="center" header-align="center" />
|
||||
<el-table-column prop="receptionTime" label="接收时间" min-width="170" align="center" header-align="center" />
|
||||
<el-table-column prop="acceptConclusion" label="接收结论" min-width="110" align="center" header-align="center">
|
||||
<el-table-column
|
||||
prop="motherName"
|
||||
label="母亲姓名"
|
||||
min-width="100"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="newbornName"
|
||||
label="新生儿姓名"
|
||||
min-width="110"
|
||||
align="center"
|
||||
header-align="center"
|
||||
show-overflow-tooltip
|
||||
/>
|
||||
<el-table-column
|
||||
prop="sampleCode"
|
||||
label="样本编码"
|
||||
min-width="130"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="sampleTypeName"
|
||||
label="样本类型"
|
||||
min-width="110"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="receptionTime"
|
||||
label="接收时间"
|
||||
min-width="170"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="acceptConclusion"
|
||||
label="接收结论"
|
||||
min-width="110"
|
||||
align="center"
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<el-tag
|
||||
:type="row.acceptConclusion === 'js' ? 'success' : row.acceptConclusion === 'bj' ? 'danger' : 'warning'"
|
||||
:type="
|
||||
row.acceptConclusion === 'js'
|
||||
? 'success'
|
||||
: row.acceptConclusion === 'bj'
|
||||
? 'danger'
|
||||
: 'warning'
|
||||
"
|
||||
size="small"
|
||||
>
|
||||
{{ row.acceptConclusionLabel || (row.acceptConclusion === 'js' ? '接收' : row.acceptConclusion === 'bj' ? '拒收' : '待接收确认') }}
|
||||
{{
|
||||
row.acceptConclusionLabel ||
|
||||
(row.acceptConclusion === 'js'
|
||||
? '接收'
|
||||
: row.acceptConclusion === 'bj'
|
||||
? '拒收'
|
||||
: '待接收确认')
|
||||
}}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="smsNotified" label="通知状态" min-width="100" align="center" header-align="center">
|
||||
<el-table-column
|
||||
prop="smsNotified"
|
||||
label="通知状态"
|
||||
min-width="100"
|
||||
align="center"
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="row.smsNotified == 1 ? 'success' : 'info'" size="small">
|
||||
{{ row.smsNotified == 1 ? '已通知' : '未通知' }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" min-width="360" fixed="right" align="center" header-align="center">
|
||||
<el-table-column
|
||||
label="操作"
|
||||
min-width="360"
|
||||
fixed="right"
|
||||
align="center"
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<el-button class="btn-op-item primary" @click="handleView(row)">
|
||||
<el-icon><View /></el-icon>查看
|
||||
@@ -160,10 +242,7 @@
|
||||
fetchGenerateSms
|
||||
} from '@/api/sample-sms-notification'
|
||||
import { type CollectionProtocolRow } from '@/api/collection-protocol'
|
||||
import {
|
||||
protocolStatusLabel,
|
||||
protocolStatusTagType,
|
||||
} from '@/utils/status-tag-registry'
|
||||
import { protocolStatusLabel, protocolStatusTagType } from '@/utils/status-tag-registry'
|
||||
|
||||
defineOptions({ name: 'CellStorageSampleReceptionInfo' })
|
||||
|
||||
|
||||
@@ -19,7 +19,12 @@
|
||||
<el-input v-model="searchForm.keyword" placeholder="协议号/姓名" clearable />
|
||||
</el-form-item>
|
||||
<el-form-item label="制作状态">
|
||||
<el-select v-model="searchForm.makeStatus" placeholder="全部" clearable style="width: 100%">
|
||||
<el-select
|
||||
v-model="searchForm.makeStatus"
|
||||
placeholder="全部"
|
||||
clearable
|
||||
style="width: 100%"
|
||||
>
|
||||
<el-option label="未制作" value="0" />
|
||||
<el-option label="已制作" value="1" />
|
||||
</el-select>
|
||||
@@ -37,7 +42,9 @@
|
||||
</el-form-item>
|
||||
</div>
|
||||
<div class="filter-actions-row">
|
||||
<span class="filter-hint">支持按协议号、姓名模糊检索;制作状态默认未制作,入库日期默认近 30 天。</span>
|
||||
<span class="filter-hint"
|
||||
>支持按协议号、姓名模糊检索;制作状态默认未制作,入库日期默认近 30 天。</span
|
||||
>
|
||||
<div class="filter-actions-end">
|
||||
<el-button class="btn-ghost" @click="handleReset">
|
||||
<el-icon><RefreshLeft /></el-icon>重置
|
||||
@@ -68,7 +75,13 @@
|
||||
header-align="center"
|
||||
:index="(i) => (pagination.page - 1) * pagination.pageSize + i + 1"
|
||||
/>
|
||||
<el-table-column prop="protocolNo" label="采集协议号" min-width="160" align="center" header-align="center" />
|
||||
<el-table-column
|
||||
prop="protocolNo"
|
||||
label="采集协议号"
|
||||
min-width="160"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="protocolStatus"
|
||||
label="协议状态"
|
||||
@@ -77,34 +90,92 @@
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="protocolStatusTagType(String(row.protocolStatus || ''))" size="small">{{
|
||||
protocolStatusLabel(String(row.protocolStatus || ''))
|
||||
}}</el-tag>
|
||||
<el-tag
|
||||
:type="protocolStatusTagType(String(row.protocolStatus || ''))"
|
||||
size="small"
|
||||
>{{ protocolStatusLabel(String(row.protocolStatus || '')) }}</el-tag
|
||||
>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="motherName" label="母亲姓名" min-width="100" align="center" header-align="center" />
|
||||
<el-table-column prop="motherPhone" label="母亲联系方式" min-width="130" align="center" header-align="center">
|
||||
<el-table-column
|
||||
prop="motherName"
|
||||
label="母亲姓名"
|
||||
min-width="100"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="motherPhone"
|
||||
label="母亲联系方式"
|
||||
min-width="130"
|
||||
align="center"
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }">{{ row.motherPhone || '—' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="motherIdCard" label="身份证号" min-width="170" align="center" header-align="center">
|
||||
<el-table-column
|
||||
prop="motherIdCard"
|
||||
label="身份证号"
|
||||
min-width="170"
|
||||
align="center"
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }">{{ maskIdCard(row.motherIdCard) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="newbornInfo" label="新生儿信息" min-width="200" align="center" header-align="center" show-overflow-tooltip />
|
||||
<el-table-column prop="sampleInfo" label="样本信息" min-width="200" align="center" header-align="center" show-overflow-tooltip />
|
||||
<el-table-column prop="storageInDate" label="入库日期" min-width="120" align="center" header-align="center">
|
||||
<el-table-column
|
||||
prop="newbornInfo"
|
||||
label="新生儿信息"
|
||||
min-width="200"
|
||||
align="center"
|
||||
header-align="center"
|
||||
show-overflow-tooltip
|
||||
/>
|
||||
<el-table-column
|
||||
prop="sampleInfo"
|
||||
label="样本信息"
|
||||
min-width="200"
|
||||
align="center"
|
||||
header-align="center"
|
||||
show-overflow-tooltip
|
||||
/>
|
||||
<el-table-column
|
||||
prop="storageInDate"
|
||||
label="入库日期"
|
||||
min-width="120"
|
||||
align="center"
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }">{{ formatStorageInDate(row.storageInDate) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="makeStatus" label="制作状态" min-width="100" align="center" header-align="center">
|
||||
<el-table-column
|
||||
prop="makeStatus"
|
||||
label="制作状态"
|
||||
min-width="100"
|
||||
align="center"
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="row.makeStatus == '1' ? 'success' : 'warning'" size="small">
|
||||
{{ row.makeStatus == '1' ? '已制作' : '未制作' }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="makeDate" label="制作日期" min-width="120" align="center" header-align="center">
|
||||
<el-table-column
|
||||
prop="makeDate"
|
||||
label="制作日期"
|
||||
min-width="120"
|
||||
align="center"
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }">{{ formatStorageInDate(row.makeDate) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" min-width="260" fixed="right" align="center" header-align="center">
|
||||
<el-table-column
|
||||
label="操作"
|
||||
min-width="260"
|
||||
fixed="right"
|
||||
align="center"
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<el-button class="btn-op-item primary" @click="handleUpload(row)">
|
||||
<el-icon><Upload /></el-icon>上传储存证
|
||||
@@ -180,9 +251,7 @@
|
||||
<div
|
||||
class="attachment-preview"
|
||||
@click="
|
||||
isImageFile(file) && file.fileUrl
|
||||
? handlePreviewAttachment(file)
|
||||
: undefined
|
||||
isImageFile(file) && file.fileUrl ? handlePreviewAttachment(file) : undefined
|
||||
"
|
||||
>
|
||||
<template v-if="isImageFile(file) && file.fileUrl">
|
||||
@@ -244,7 +313,12 @@
|
||||
<el-button class="btn-ghost" @click="uploadDialogVisible = false">
|
||||
<el-icon><Close /></el-icon>取消
|
||||
</el-button>
|
||||
<el-button class="btn-main" type="primary" :loading="submitLoading" @click="handleSubmitUpload">
|
||||
<el-button
|
||||
class="btn-main"
|
||||
type="primary"
|
||||
:loading="submitLoading"
|
||||
@click="handleSubmitUpload"
|
||||
>
|
||||
<el-icon><Check /></el-icon>确定
|
||||
</el-button>
|
||||
</template>
|
||||
@@ -312,10 +386,7 @@
|
||||
import { fetchUploadAttachment } from '@/api/business'
|
||||
import { downloadAttachmentFile } from '@/utils/download-attachment'
|
||||
import { maskIdCard } from '@/utils/mask-sensitive'
|
||||
import {
|
||||
protocolStatusLabel,
|
||||
protocolStatusTagType,
|
||||
} from '@/utils/status-tag-registry'
|
||||
import { protocolStatusLabel, protocolStatusTagType } from '@/utils/status-tag-registry'
|
||||
|
||||
interface CertAttachmentItem {
|
||||
id: string
|
||||
|
||||
@@ -18,14 +18,24 @@
|
||||
<el-input v-model="searchForm.keyword" placeholder="协议号/姓名/样本编码" clearable />
|
||||
</el-form-item>
|
||||
<el-form-item label="入库状态">
|
||||
<el-select v-model="searchForm.storageStatus" placeholder="全部" clearable style="width: 100%">
|
||||
<el-select
|
||||
v-model="searchForm.storageStatus"
|
||||
placeholder="全部"
|
||||
clearable
|
||||
style="width: 100%"
|
||||
>
|
||||
<el-option label="已入库" value="yrk" />
|
||||
<el-option label="拒绝入库" value="btg" />
|
||||
<el-option label="待入库确认" value="drk" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="通知状态">
|
||||
<el-select v-model="searchForm.smsNotified" placeholder="全部" clearable style="width: 100%">
|
||||
<el-select
|
||||
v-model="searchForm.smsNotified"
|
||||
placeholder="全部"
|
||||
clearable
|
||||
style="width: 100%"
|
||||
>
|
||||
<el-option label="已通知" value="1" />
|
||||
<el-option label="未通知" value="0" />
|
||||
</el-select>
|
||||
@@ -43,7 +53,9 @@
|
||||
</el-form-item>
|
||||
</div>
|
||||
<div class="filter-actions-row">
|
||||
<span class="filter-hint">支持按协议号、姓名、样本编码模糊检索;默认展示近 30 天内入库的样本。</span>
|
||||
<span class="filter-hint"
|
||||
>支持按协议号、姓名、样本编码模糊检索;默认展示近 30 天内入库的样本。</span
|
||||
>
|
||||
<div class="filter-actions-end">
|
||||
<el-button class="btn-ghost" @click="handleReset">
|
||||
<el-icon><RefreshLeft /></el-icon>重置
|
||||
@@ -73,7 +85,13 @@
|
||||
header-align="center"
|
||||
:index="(i) => (pagination.page - 1) * pagination.pageSize + i + 1"
|
||||
/>
|
||||
<el-table-column prop="protocolNo" label="协议号" min-width="140" align="center" header-align="center" />
|
||||
<el-table-column
|
||||
prop="protocolNo"
|
||||
label="协议号"
|
||||
min-width="140"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="protocolStatus"
|
||||
label="协议状态"
|
||||
@@ -82,17 +100,56 @@
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="protocolStatusTagType(String(row.protocolStatus || ''))" size="small">{{
|
||||
protocolStatusLabel(String(row.protocolStatus || ''))
|
||||
}}</el-tag>
|
||||
<el-tag
|
||||
:type="protocolStatusTagType(String(row.protocolStatus || ''))"
|
||||
size="small"
|
||||
>{{ protocolStatusLabel(String(row.protocolStatus || '')) }}</el-tag
|
||||
>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="motherName" label="母亲姓名" min-width="100" align="center" header-align="center" />
|
||||
<el-table-column prop="newbornName" label="新生儿姓名" min-width="110" align="center" header-align="center" show-overflow-tooltip />
|
||||
<el-table-column prop="sampleCode" label="样本编码" min-width="130" align="center" header-align="center" />
|
||||
<el-table-column prop="sampleTypeName" label="样本类型" min-width="110" align="center" header-align="center" />
|
||||
<el-table-column prop="storageTime" label="入库时间" min-width="170" align="center" header-align="center" />
|
||||
<el-table-column prop="storageStatusLabel" label="入库状态" min-width="100" align="center" header-align="center">
|
||||
<el-table-column
|
||||
prop="motherName"
|
||||
label="母亲姓名"
|
||||
min-width="100"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="newbornName"
|
||||
label="新生儿姓名"
|
||||
min-width="110"
|
||||
align="center"
|
||||
header-align="center"
|
||||
show-overflow-tooltip
|
||||
/>
|
||||
<el-table-column
|
||||
prop="sampleCode"
|
||||
label="样本编码"
|
||||
min-width="130"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="sampleTypeName"
|
||||
label="样本类型"
|
||||
min-width="110"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="storageTime"
|
||||
label="入库时间"
|
||||
min-width="170"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="storageStatusLabel"
|
||||
label="入库状态"
|
||||
min-width="100"
|
||||
align="center"
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<el-tag
|
||||
:type="
|
||||
@@ -108,14 +165,26 @@
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="smsNotified" label="通知状态" min-width="100" align="center" header-align="center">
|
||||
<el-table-column
|
||||
prop="smsNotified"
|
||||
label="通知状态"
|
||||
min-width="100"
|
||||
align="center"
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="row.smsNotified == 1 ? 'success' : 'info'" size="small">
|
||||
{{ row.smsNotified == 1 ? '已通知' : '未通知' }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" min-width="360" fixed="right" align="center" header-align="center">
|
||||
<el-table-column
|
||||
label="操作"
|
||||
min-width="360"
|
||||
fixed="right"
|
||||
align="center"
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<el-button class="btn-op-item primary" @click="handleView(row)">
|
||||
<el-icon><View /></el-icon>查看
|
||||
@@ -166,10 +235,7 @@
|
||||
fetchGenerateStorageEntrySms
|
||||
} from '@/api/storage-entry-sms-notification'
|
||||
import { type CollectionProtocolRow } from '@/api/collection-protocol'
|
||||
import {
|
||||
protocolStatusLabel,
|
||||
protocolStatusTagType,
|
||||
} from '@/utils/status-tag-registry'
|
||||
import { protocolStatusLabel, protocolStatusTagType } from '@/utils/status-tag-registry'
|
||||
|
||||
defineOptions({ name: 'CellStorageStorageEntryInfo' })
|
||||
|
||||
|
||||
@@ -11,7 +11,9 @@
|
||||
<h2>快速入口</h2>
|
||||
</div>
|
||||
<div class="console-lite__body">
|
||||
<p class="console-lite__hint">详见仓库根目录 `.cursorrules` 中「页面级 UI 模板(固定五页)」。</p>
|
||||
<p class="console-lite__hint"
|
||||
>详见仓库根目录 `.cursorrules` 中「页面级 UI 模板(固定五页)」。</p
|
||||
>
|
||||
<div class="console-lite__links">
|
||||
<el-button
|
||||
v-for="item in links"
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
flex-direction: column;
|
||||
min-width: 0;
|
||||
height: 100vh;
|
||||
overflow: hidden;
|
||||
overflow: hidden;
|
||||
|
||||
#app-header {
|
||||
position: sticky;
|
||||
|
||||
@@ -22,8 +22,12 @@
|
||||
<div class="filter-actions-row">
|
||||
<span class="filter-hint">按编号、名称模糊查询。</span>
|
||||
<div class="filter-actions-end">
|
||||
<el-button class="btn-ghost" @click="handleReset"><el-icon><RefreshLeft /></el-icon>重置</el-button>
|
||||
<el-button class="btn-main" type="primary" @click="handleSearch"><el-icon><Search /></el-icon>搜索</el-button>
|
||||
<el-button class="btn-ghost" @click="handleReset"
|
||||
><el-icon><RefreshLeft /></el-icon>重置</el-button
|
||||
>
|
||||
<el-button class="btn-main" type="primary" @click="handleSearch"
|
||||
><el-icon><Search /></el-icon>搜索</el-button
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</el-form>
|
||||
@@ -68,11 +72,27 @@
|
||||
header-align="center"
|
||||
:index="(index) => (pagination.page - 1) * pagination.pageSize + index + 1"
|
||||
/>
|
||||
<el-table-column prop="roomCode" label="编号" min-width="140" align="center" header-align="center" />
|
||||
<el-table-column prop="roomName" label="名称" min-width="160" align="center" header-align="center" />
|
||||
<el-table-column
|
||||
prop="roomCode"
|
||||
label="编号"
|
||||
min-width="140"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="roomName"
|
||||
label="名称"
|
||||
min-width="160"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column label="等级" min-width="100" align="center" header-align="center">
|
||||
<template #default="{ row }">
|
||||
<el-tag class="clean-room-grade-tag" :class="gradeTagClass(row.roomGrade)" effect="light">
|
||||
<el-tag
|
||||
class="clean-room-grade-tag"
|
||||
:class="gradeTagClass(row.roomGrade)"
|
||||
effect="light"
|
||||
>
|
||||
{{ gradeLabel(row.roomGrade) }}
|
||||
</el-tag>
|
||||
</template>
|
||||
@@ -84,8 +104,20 @@
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="areaSqm" label="面积(m²)" min-width="120" align="center" header-align="center" />
|
||||
<el-table-column label="操作" min-width="320" fixed="right" align="center" header-align="center">
|
||||
<el-table-column
|
||||
prop="areaSqm"
|
||||
label="面积(m²)"
|
||||
min-width="120"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
label="操作"
|
||||
min-width="320"
|
||||
fixed="right"
|
||||
align="center"
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<el-button class="btn-op-item" @click="handleView(row)"
|
||||
><el-icon><View /></el-icon>查看</el-button
|
||||
@@ -223,16 +255,68 @@
|
||||
header-align="center"
|
||||
:index="(index) => (logPagination.page - 1) * logPagination.pageSize + index + 1"
|
||||
/>
|
||||
<el-table-column prop="personName" label="人员姓名" min-width="100" align="center" header-align="center" />
|
||||
<el-table-column prop="deptName" label="部门" min-width="100" align="center" header-align="center" />
|
||||
<el-table-column prop="enterTime" label="进入时间" min-width="160" align="center" header-align="center" />
|
||||
<el-table-column prop="purpose" label="目的" min-width="120" align="center" header-align="center" show-overflow-tooltip />
|
||||
<el-table-column prop="leaveTime" label="离开时间" min-width="160" align="center" header-align="center" />
|
||||
<el-table-column prop="bodyTemp" label="体温(℃)" min-width="90" align="center" header-align="center">
|
||||
<template #default="{ row }">{{ row.bodyTemp != null ? row.bodyTemp + '℃' : '-' }}</template>
|
||||
<el-table-column
|
||||
prop="personName"
|
||||
label="人员姓名"
|
||||
min-width="100"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="deptName"
|
||||
label="部门"
|
||||
min-width="100"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="enterTime"
|
||||
label="进入时间"
|
||||
min-width="160"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="purpose"
|
||||
label="目的"
|
||||
min-width="120"
|
||||
align="center"
|
||||
header-align="center"
|
||||
show-overflow-tooltip
|
||||
/>
|
||||
<el-table-column
|
||||
prop="leaveTime"
|
||||
label="离开时间"
|
||||
min-width="160"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="bodyTemp"
|
||||
label="体温(℃)"
|
||||
min-width="90"
|
||||
align="center"
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }">{{
|
||||
row.bodyTemp != null ? row.bodyTemp + '℃' : '-'
|
||||
}}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="remark" label="备注" min-width="160" align="center" header-align="center" show-overflow-tooltip />
|
||||
<el-table-column label="操作" min-width="260" fixed="right" align="center" header-align="center">
|
||||
<el-table-column
|
||||
prop="remark"
|
||||
label="备注"
|
||||
min-width="160"
|
||||
align="center"
|
||||
header-align="center"
|
||||
show-overflow-tooltip
|
||||
/>
|
||||
<el-table-column
|
||||
label="操作"
|
||||
min-width="260"
|
||||
fixed="right"
|
||||
align="center"
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<el-button class="btn-op-item" @click="handleViewLog(row)"
|
||||
><el-icon><View /></el-icon>查看</el-button
|
||||
@@ -253,7 +337,13 @@
|
||||
:total="logPagination.total"
|
||||
:page-sizes="[10, 20, 50]"
|
||||
layout="total, sizes, prev, pager, next"
|
||||
@size-change="(val) => ((logPagination.page = 1), (logPagination.pageSize = val), loadAccessLogData())"
|
||||
@size-change="
|
||||
(val) => (
|
||||
(logPagination.page = 1),
|
||||
(logPagination.pageSize = val),
|
||||
loadAccessLogData()
|
||||
)
|
||||
"
|
||||
@current-change="(val) => ((logPagination.page = val), loadAccessLogData())"
|
||||
/>
|
||||
</div>
|
||||
@@ -302,7 +392,10 @@
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="部门" prop="deptName">
|
||||
<el-input v-model="logFormData.deptName" placeholder="选择人员后自动填入,可手填修改" />
|
||||
<el-input
|
||||
v-model="logFormData.deptName"
|
||||
placeholder="选择人员后自动填入,可手填修改"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="进入时间" prop="enterTime">
|
||||
<el-date-picker
|
||||
@@ -351,7 +444,12 @@
|
||||
<el-icon><Close /></el-icon>
|
||||
取消
|
||||
</el-button>
|
||||
<el-button class="btn-main" type="primary" @click="handleLogSubmit" :loading="logSubmitLoading">
|
||||
<el-button
|
||||
class="btn-main"
|
||||
type="primary"
|
||||
@click="handleLogSubmit"
|
||||
:loading="logSubmitLoading"
|
||||
>
|
||||
<el-icon><Check /></el-icon>
|
||||
确定
|
||||
</el-button>
|
||||
@@ -372,7 +470,18 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { Plus, Close, Check, View, Edit, Delete, User, OfficeBuilding, RefreshLeft, Search } from '@element-plus/icons-vue'
|
||||
import {
|
||||
Plus,
|
||||
Close,
|
||||
Check,
|
||||
View,
|
||||
Edit,
|
||||
Delete,
|
||||
User,
|
||||
OfficeBuilding,
|
||||
RefreshLeft,
|
||||
Search
|
||||
} from '@element-plus/icons-vue'
|
||||
import { withFullscreenLoading, fullscreenLoadingText } from '@/utils/fullscreen-loading'
|
||||
import { buildTreePathLabelFromFlat } from '@/utils/tree-path'
|
||||
import { cleanRoomGradeLabel } from '@/utils/status-tag-registry'
|
||||
@@ -443,7 +552,11 @@
|
||||
{ label: '编号', value: row.roomCode },
|
||||
{ label: '名称', value: row.roomName },
|
||||
{ label: '等级', value: gradeLabel(row.roomGrade) },
|
||||
{ label: '状态', value: row.roomStatus === 'zc' ? '正常' : '异常', tagType: row.roomStatus === 'zc' ? 'success' : 'danger' },
|
||||
{
|
||||
label: '状态',
|
||||
value: row.roomStatus === 'zc' ? '正常' : '异常',
|
||||
tagType: row.roomStatus === 'zc' ? 'success' : 'danger'
|
||||
},
|
||||
{ label: '面积', value: row.areaSqm ? `${row.areaSqm} m²` : '' },
|
||||
{ label: '备注', value: row.remark, span: true }
|
||||
]
|
||||
@@ -553,7 +666,9 @@
|
||||
async function handleBatchDelete() {
|
||||
const ids = selectedRows.value.map((r) => r.id)
|
||||
if (!ids.length) return
|
||||
await ElMessageBox.confirm(`确定删除选中的 ${ids.length} 个洁净室吗?`, '提示', { type: 'warning' })
|
||||
await ElMessageBox.confirm(`确定删除选中的 ${ids.length} 个洁净室吗?`, '提示', {
|
||||
type: 'warning'
|
||||
})
|
||||
await withFullscreenLoading(async () => {
|
||||
for (const id of ids) {
|
||||
await fetchDeleteCleanRoom(id)
|
||||
|
||||
@@ -27,9 +27,9 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { reactive } from 'vue'
|
||||
import { reactive } from 'vue'
|
||||
|
||||
const form = reactive({
|
||||
value: ''
|
||||
})
|
||||
const form = reactive({
|
||||
value: ''
|
||||
})
|
||||
</script>
|
||||
@@ -16,7 +16,11 @@
|
||||
<el-form :model="searchForm" class="filter-form" @submit.prevent>
|
||||
<div class="filter-grid">
|
||||
<el-form-item label="关键词">
|
||||
<el-input v-model="searchForm.keyword" placeholder="预约编号/来访单位/联系人" clearable />
|
||||
<el-input
|
||||
v-model="searchForm.keyword"
|
||||
placeholder="预约编号/来访单位/联系人"
|
||||
clearable
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="到访日期">
|
||||
<el-date-picker
|
||||
@@ -31,10 +35,16 @@
|
||||
</el-form-item>
|
||||
</div>
|
||||
<div class="filter-actions-row">
|
||||
<span class="filter-hint">按预约编号、来访单位、联系人模糊查询;默认展示未来 30 天内的到访预约。</span>
|
||||
<span class="filter-hint"
|
||||
>按预约编号、来访单位、联系人模糊查询;默认展示未来 30 天内的到访预约。</span
|
||||
>
|
||||
<div class="filter-actions-end">
|
||||
<el-button class="btn-ghost" @click="handleReset"><el-icon><RefreshLeft /></el-icon>重置</el-button>
|
||||
<el-button class="btn-main" type="primary" @click="handleSearch"><el-icon><Search /></el-icon>搜索</el-button>
|
||||
<el-button class="btn-ghost" @click="handleReset"
|
||||
><el-icon><RefreshLeft /></el-icon>重置</el-button
|
||||
>
|
||||
<el-button class="btn-main" type="primary" @click="handleSearch"
|
||||
><el-icon><Search /></el-icon>搜索</el-button
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</el-form>
|
||||
@@ -79,13 +89,55 @@
|
||||
header-align="center"
|
||||
:index="(index) => (pagination.page - 1) * pagination.pageSize + index + 1"
|
||||
/>
|
||||
<el-table-column prop="appointmentNo" label="预约编号" min-width="150" align="center" header-align="center" />
|
||||
<el-table-column prop="visitOrg" label="来访单位" min-width="150" align="center" header-align="center" />
|
||||
<el-table-column prop="contactName" label="联系人" min-width="120" align="center" header-align="center" />
|
||||
<el-table-column prop="visitDate" label="到访日期" min-width="180" align="center" header-align="center" />
|
||||
<el-table-column prop="visitorCount" label="人数" min-width="80" align="center" header-align="center" />
|
||||
<el-table-column prop="createTime" label="创建时间" min-width="180" align="center" header-align="center" />
|
||||
<el-table-column label="操作" min-width="320" fixed="right" align="center" header-align="center">
|
||||
<el-table-column
|
||||
prop="appointmentNo"
|
||||
label="预约编号"
|
||||
min-width="150"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="visitOrg"
|
||||
label="来访单位"
|
||||
min-width="150"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="contactName"
|
||||
label="联系人"
|
||||
min-width="120"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="visitDate"
|
||||
label="到访日期"
|
||||
min-width="180"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="visitorCount"
|
||||
label="人数"
|
||||
min-width="80"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="createTime"
|
||||
label="创建时间"
|
||||
min-width="180"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
label="操作"
|
||||
min-width="320"
|
||||
fixed="right"
|
||||
align="center"
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<el-button class="btn-op-item" @click="handleView(row)"
|
||||
><el-icon><View /></el-icon>查看</el-button
|
||||
@@ -128,7 +180,11 @@
|
||||
>
|
||||
<div class="form-grid-2">
|
||||
<el-form-item label="预约编号">
|
||||
<el-input v-model="formData.appointmentNo" placeholder="提交后自动生成" :disabled="true" />
|
||||
<el-input
|
||||
v-model="formData.appointmentNo"
|
||||
placeholder="提交后自动生成"
|
||||
:disabled="true"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="来访单位" prop="visitOrg">
|
||||
<el-input v-model="formData.visitOrg" placeholder="请输入来访单位" />
|
||||
@@ -146,7 +202,13 @@
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="人数">
|
||||
<el-input type="number" step="any" v-model="formData.visitorCount" :min="1" style="width: 100%" />
|
||||
<el-input
|
||||
type="number"
|
||||
step="any"
|
||||
v-model="formData.visitorCount"
|
||||
:min="1"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="备注" class="form-col-span-2">
|
||||
<el-input v-model="formData.remark" type="textarea" :rows="3" placeholder="可选" />
|
||||
@@ -367,7 +429,9 @@
|
||||
async function handleBatchDelete() {
|
||||
const ids = selectedRows.value.map((r) => r.id)
|
||||
if (!ids.length) return
|
||||
await ElMessageBox.confirm(`确定删除选中的 ${ids.length} 个预约吗?`, '提示', { type: 'warning' })
|
||||
await ElMessageBox.confirm(`确定删除选中的 ${ids.length} 个预约吗?`, '提示', {
|
||||
type: 'warning'
|
||||
})
|
||||
await withFullscreenLoading(async () => {
|
||||
for (const id of ids) {
|
||||
await fetchDeleteExhibitionAppointment(id)
|
||||
|
||||
@@ -1,7 +1,14 @@
|
||||
<!-- 库位管理(左树 + 右货架行布局) -->
|
||||
<!-- eslint-disable vue/no-v-model-argument -->
|
||||
<template>
|
||||
<div class="page-container storage-page" ref="pageContainerRef" :style="pageContainerStyle" v-loading="clearSlotsLoading" element-loading-text="正在清空格子..." element-loading-background="rgba(255,255,255,0.7)">
|
||||
<div
|
||||
class="page-container storage-page"
|
||||
ref="pageContainerRef"
|
||||
:style="pageContainerStyle"
|
||||
v-loading="clearSlotsLoading"
|
||||
element-loading-text="正在清空格子..."
|
||||
element-loading-background="rgba(255,255,255,0.7)"
|
||||
>
|
||||
<header class="page-head">
|
||||
<h1>库位管理</h1>
|
||||
<p class="page-desc">维护库位设施树结构(罐→层→盒),以及叶子节点的格子管理。</p>
|
||||
@@ -38,7 +45,10 @@
|
||||
>
|
||||
<template #default="{ data }">
|
||||
<div class="storage-tree-node">
|
||||
<span v-if="data.isLeaf === 1 && slotStatsMap[data.id]" class="storage-tree-node__stats">
|
||||
<span
|
||||
v-if="data.isLeaf === 1 && slotStatsMap[data.id]"
|
||||
class="storage-tree-node__stats"
|
||||
>
|
||||
{{ slotStatsMap[data.id].occupied }}/{{ slotStatsMap[data.id].total }}
|
||||
</span>
|
||||
<span class="storage-tree-node__name">{{ data.nodeName }}</span>
|
||||
@@ -82,11 +92,17 @@
|
||||
}}
|
||||
</h3>
|
||||
<div class="storage-content-stats">
|
||||
<span>总数 <strong>{{ slotStats.total }}</strong></span>
|
||||
<span
|
||||
>总数 <strong>{{ slotStats.total }}</strong></span
|
||||
>
|
||||
<span class="stats-dot">·</span>
|
||||
<span>空闲 <strong>{{ slotStats.empty }}</strong></span>
|
||||
<span
|
||||
>空闲 <strong>{{ slotStats.empty }}</strong></span
|
||||
>
|
||||
<span class="stats-dot">·</span>
|
||||
<span>占用 <strong>{{ slotStats.occupied }}</strong></span>
|
||||
<span
|
||||
>占用 <strong>{{ slotStats.occupied }}</strong></span
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -133,13 +149,24 @@
|
||||
:class="{ occupied: cell.slotStatus === '占用' }"
|
||||
@click="handleOpenOccupancyDialog(cell)"
|
||||
>
|
||||
<button class="cell-delete-btn" @click.stop="handleDeleteSlot(cell)" title="删除">×</button>
|
||||
<button
|
||||
class="cell-delete-btn"
|
||||
@click.stop="handleDeleteSlot(cell)"
|
||||
title="删除"
|
||||
>×</button
|
||||
>
|
||||
<span class="cell-code">{{ cellIndex + 1 }}</span>
|
||||
<span v-if="cell.occupiedCount > 0" class="cell-badge">{{ cell.occupiedCount }}</span>
|
||||
<span v-if="cell.occupiedCount > 0" class="cell-badge">{{
|
||||
cell.occupiedCount
|
||||
}}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="shelf-add-row" title="在下方新增一行" @click="handleOpenAddRowDialog(ri)">
|
||||
<div
|
||||
class="shelf-add-row"
|
||||
title="在下方新增一行"
|
||||
@click="handleOpenAddRowDialog(ri)"
|
||||
>
|
||||
<span class="add-row-icon">+ 新增行</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -199,7 +226,13 @@
|
||||
<el-switch v-model="nodeFormData.isLeaf" :active-value="1" :inactive-value="0" />
|
||||
</el-form-item>
|
||||
<el-form-item label="排序">
|
||||
<el-input-number v-model="nodeFormData.orderNum" :min="0" :step="1" controls-position="right" style="width: 100%" />
|
||||
<el-input-number
|
||||
v-model="nodeFormData.orderNum"
|
||||
:min="0"
|
||||
:step="1"
|
||||
controls-position="right"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="备注" class="form-col-span-2">
|
||||
<el-input v-model="nodeFormData.remark" placeholder="可选" />
|
||||
@@ -291,10 +324,24 @@
|
||||
>
|
||||
<div class="form-grid-2">
|
||||
<el-form-item label="行数">
|
||||
<el-input type="number" step="any" v-model="batchForm.rows" :min="1" :max="20" style="width: 100%" />
|
||||
<el-input
|
||||
type="number"
|
||||
step="any"
|
||||
v-model="batchForm.rows"
|
||||
:min="1"
|
||||
:max="20"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="列数">
|
||||
<el-input type="number" step="any" v-model="batchForm.cols" :min="1" :max="20" style="width: 100%" />
|
||||
<el-input
|
||||
type="number"
|
||||
step="any"
|
||||
v-model="batchForm.cols"
|
||||
:min="1"
|
||||
:max="20"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="编码前缀" class="form-col-span-2">
|
||||
<template #label>
|
||||
@@ -340,7 +387,13 @@
|
||||
>
|
||||
<el-form :model="addRowForm" label-position="top" class="storage-dialog-form">
|
||||
<el-form-item label="列数">
|
||||
<el-input type="number" step="any" v-model="addRowForm.cols" :min="1" style="width: 100%" />
|
||||
<el-input
|
||||
type="number"
|
||||
step="any"
|
||||
v-model="addRowForm.cols"
|
||||
:min="1"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</el-form-item>
|
||||
<div class="storage-batch-preview">
|
||||
将在当前行下方新增 <strong>{{ addRowForm.cols }}</strong> 个格子
|
||||
@@ -380,7 +433,12 @@
|
||||
入库
|
||||
</el-button>
|
||||
</div>
|
||||
<el-table :data="occupancyList" v-loading="occupancyLoading" size="small" max-height="360">
|
||||
<el-table
|
||||
:data="occupancyList"
|
||||
v-loading="occupancyLoading"
|
||||
size="small"
|
||||
max-height="360"
|
||||
>
|
||||
<el-table-column type="index" label="序号" min-width="60" />
|
||||
<el-table-column label="物品类型" min-width="120">
|
||||
<template #default="{ row }">
|
||||
@@ -393,7 +451,11 @@
|
||||
<el-table-column prop="itemName" label="物品名称" min-width="140" />
|
||||
<el-table-column label="来源" min-width="120">
|
||||
<template #default="{ row }">
|
||||
{{ { production: '生产入库', manual: '手动入库', transfer: '转移' }[row.itemSource] || row.itemSource }}
|
||||
{{
|
||||
{ production: '生产入库', manual: '手动入库', transfer: '转移' }[
|
||||
row.itemSource
|
||||
] || row.itemSource
|
||||
}}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="storedAt" label="入库时间" min-width="170" />
|
||||
@@ -431,14 +493,21 @@
|
||||
<el-table-column prop="itemName" label="物品名称" min-width="120" />
|
||||
<el-table-column label="来源" min-width="100">
|
||||
<template #default="{ row }">
|
||||
{{ { production: '生产入库', manual: '手动入库', transfer: '转移' }[row.itemSource] || row.itemSource }}
|
||||
{{
|
||||
{ production: '生产入库', manual: '手动入库', transfer: '转移' }[
|
||||
row.itemSource
|
||||
] || row.itemSource
|
||||
}}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="storedAt" label="入库时间" min-width="170" />
|
||||
<el-table-column prop="removedAt" label="出库时间" min-width="170" />
|
||||
<el-table-column label="状态" min-width="100">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="row.occupancyStatus === 'stored' ? 'success' : 'warning'" size="small">
|
||||
<el-tag
|
||||
:type="row.occupancyStatus === 'stored' ? 'success' : 'warning'"
|
||||
size="small"
|
||||
>
|
||||
{{ row.occupancyStatus === 'stored' ? '在库' : '已出库' }}
|
||||
</el-tag>
|
||||
</template>
|
||||
@@ -482,7 +551,12 @@
|
||||
</div>
|
||||
</el-form>
|
||||
<div class="occupancy-slot-save-bar">
|
||||
<el-button type="primary" size="small" :loading="occupancySlotSaving" @click="handleOccupancySlotSave">
|
||||
<el-button
|
||||
type="primary"
|
||||
size="small"
|
||||
:loading="occupancySlotSaving"
|
||||
@click="handleOccupancySlotSave"
|
||||
>
|
||||
保存
|
||||
</el-button>
|
||||
</div>
|
||||
@@ -524,7 +598,9 @@
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button class="btn-ghost" @click="storeDialogVisible = false">取消</el-button>
|
||||
<el-button type="primary" :loading="storeSubmitLoading" @click="handleStoreSubmit">确认入库</el-button>
|
||||
<el-button type="primary" :loading="storeSubmitLoading" @click="handleStoreSubmit"
|
||||
>确认入库</el-button
|
||||
>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</section>
|
||||
@@ -702,9 +778,7 @@
|
||||
})
|
||||
|
||||
/** 解析 slotCode "{prefix}{row}-{col}" 格式,提取行号和列号 */
|
||||
const parseSlotCode = (
|
||||
code: string
|
||||
): { prefix: string; row: number; col: number } | null => {
|
||||
const parseSlotCode = (code: string): { prefix: string; row: number; col: number } | null => {
|
||||
const match = code.match(/^(.*?)(\d+)-(\d+)$/)
|
||||
if (!match) return null
|
||||
return { prefix: match[1], row: parseInt(match[2], 10), col: parseInt(match[3], 10) }
|
||||
|
||||
@@ -22,8 +22,12 @@
|
||||
<div class="filter-actions-row">
|
||||
<span class="filter-hint">按项目名称、编码模糊查询。</span>
|
||||
<div class="filter-actions-end">
|
||||
<el-button class="btn-ghost" @click="handleReset"><el-icon><RefreshLeft /></el-icon>重置</el-button>
|
||||
<el-button class="btn-main" type="primary" @click="handleSearch"><el-icon><Search /></el-icon>搜索</el-button>
|
||||
<el-button class="btn-ghost" @click="handleReset"
|
||||
><el-icon><RefreshLeft /></el-icon>重置</el-button
|
||||
>
|
||||
<el-button class="btn-main" type="primary" @click="handleSearch"
|
||||
><el-icon><Search /></el-icon>搜索</el-button
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</el-form>
|
||||
@@ -68,18 +72,66 @@
|
||||
header-align="center"
|
||||
:index="(index) => (pagination.page - 1) * pagination.pageSize + index + 1"
|
||||
/>
|
||||
<el-table-column prop="projectName" label="项目名称" min-width="140" align="center" header-align="center" />
|
||||
<el-table-column prop="projectCode" label="项目编码" min-width="120" align="center" header-align="center" />
|
||||
<el-table-column prop="cellTypeName" label="细胞类型名称" min-width="140" align="center" header-align="center" />
|
||||
<el-table-column prop="specText" label="规格" min-width="120" align="center" header-align="center" />
|
||||
<el-table-column prop="unitPrice" label="单价" min-width="100" align="center" header-align="center">
|
||||
<el-table-column
|
||||
prop="projectName"
|
||||
label="项目名称"
|
||||
min-width="140"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="projectCode"
|
||||
label="项目编码"
|
||||
min-width="120"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="cellTypeName"
|
||||
label="细胞类型名称"
|
||||
min-width="140"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="specText"
|
||||
label="规格"
|
||||
min-width="120"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="unitPrice"
|
||||
label="单价"
|
||||
min-width="100"
|
||||
align="center"
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
{{ row.unitPrice != null ? `${formatMoneyDisplay(row.unitPrice)} 元` : '-' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="remark" label="备注" min-width="150" align="center" header-align="center" />
|
||||
<el-table-column prop="createTime" label="创建时间" min-width="180" align="center" header-align="center" />
|
||||
<el-table-column label="操作" min-width="320" fixed="right" align="center" header-align="center">
|
||||
<el-table-column
|
||||
prop="remark"
|
||||
label="备注"
|
||||
min-width="150"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="createTime"
|
||||
label="创建时间"
|
||||
min-width="180"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
label="操作"
|
||||
min-width="320"
|
||||
fixed="right"
|
||||
align="center"
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<el-button class="btn-op-item" @click="handleView(row)"
|
||||
><el-icon><View /></el-icon>查看</el-button
|
||||
@@ -164,11 +216,7 @@
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="单价(元)" prop="unitPrice">
|
||||
<AmountInput
|
||||
v-model="formData.unitPrice"
|
||||
:min="0"
|
||||
placeholder="请输入单价"
|
||||
/>
|
||||
<AmountInput v-model="formData.unitPrice" :min="0" placeholder="请输入单价" />
|
||||
</el-form-item>
|
||||
<el-form-item label="备注">
|
||||
<el-input v-model="formData.remark" type="textarea" :rows="3" placeholder="可选" />
|
||||
@@ -202,7 +250,17 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, onMounted, computed } from 'vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { Plus, Close, Check, View, Edit, Delete, ScaleToOriginal, RefreshLeft, Search } from '@element-plus/icons-vue'
|
||||
import {
|
||||
Plus,
|
||||
Close,
|
||||
Check,
|
||||
View,
|
||||
Edit,
|
||||
Delete,
|
||||
ScaleToOriginal,
|
||||
RefreshLeft,
|
||||
Search
|
||||
} from '@element-plus/icons-vue'
|
||||
import SystemViewDialog from '@/components/business/system-view-dialog/index.vue'
|
||||
import AmountInput from '@/components/common/amount-input/index.vue'
|
||||
import { formatMoneyDisplay } from '@/utils/money-format'
|
||||
@@ -278,7 +336,10 @@
|
||||
{ label: '项目编码', value: row.projectCode },
|
||||
{ label: '细胞类型名称', value: row.cellTypeName },
|
||||
{ label: '规格', value: row.specText },
|
||||
{ label: '单价(元)', value: row.unitPrice != null ? `${formatMoneyDisplay(row.unitPrice)} 元` : '-' },
|
||||
{
|
||||
label: '单价(元)',
|
||||
value: row.unitPrice != null ? `${formatMoneyDisplay(row.unitPrice)} 元` : '-'
|
||||
},
|
||||
{ label: '备注', value: row.remark, span: true }
|
||||
]
|
||||
}
|
||||
@@ -441,7 +502,9 @@
|
||||
async function handleBatchDelete() {
|
||||
const ids = selectedRows.value.map((r) => r.id)
|
||||
if (!ids.length) return
|
||||
await ElMessageBox.confirm(`确定删除选中的 ${ids.length} 条记录吗?`, '提示', { type: 'warning' })
|
||||
await ElMessageBox.confirm(`确定删除选中的 ${ids.length} 条记录吗?`, '提示', {
|
||||
type: 'warning'
|
||||
})
|
||||
await withFullscreenLoading(async () => {
|
||||
for (const id of ids) {
|
||||
await fetchDeleteCellProductPrice(id)
|
||||
|
||||
@@ -22,8 +22,12 @@
|
||||
<div class="filter-actions-row">
|
||||
<span class="filter-hint">按编码、名称模糊查询。</span>
|
||||
<div class="filter-actions-end">
|
||||
<el-button class="btn-ghost" @click="handleReset"><el-icon><RefreshLeft /></el-icon>重置</el-button>
|
||||
<el-button class="btn-main" type="primary" @click="handleSearch"><el-icon><Search /></el-icon>搜索</el-button>
|
||||
<el-button class="btn-ghost" @click="handleReset"
|
||||
><el-icon><RefreshLeft /></el-icon>重置</el-button
|
||||
>
|
||||
<el-button class="btn-main" type="primary" @click="handleSearch"
|
||||
><el-icon><Search /></el-icon>搜索</el-button
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</el-form>
|
||||
@@ -68,11 +72,41 @@
|
||||
header-align="center"
|
||||
:index="(index) => (pagination.page - 1) * pagination.pageSize + index + 1"
|
||||
/>
|
||||
<el-table-column prop="specCode" label="规格编码" min-width="120" align="center" header-align="center" />
|
||||
<el-table-column prop="specName" label="规格名称" min-width="150" align="center" header-align="center" />
|
||||
<el-table-column prop="remark" label="备注" min-width="150" align="center" header-align="center" />
|
||||
<el-table-column prop="createTime" label="创建时间" min-width="180" align="center" header-align="center" />
|
||||
<el-table-column label="操作" min-width="320" fixed="right" align="center" header-align="center">
|
||||
<el-table-column
|
||||
prop="specCode"
|
||||
label="规格编码"
|
||||
min-width="120"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="specName"
|
||||
label="规格名称"
|
||||
min-width="150"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="remark"
|
||||
label="备注"
|
||||
min-width="150"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="createTime"
|
||||
label="创建时间"
|
||||
min-width="180"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
label="操作"
|
||||
min-width="320"
|
||||
fixed="right"
|
||||
align="center"
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<el-button class="btn-op-item" @click="handleView(row)"
|
||||
><el-icon><View /></el-icon>查看</el-button
|
||||
@@ -156,7 +190,17 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, onMounted, computed } from 'vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { Plus, Close, Check, View, Edit, Delete, ScaleToOriginal, RefreshLeft, Search } from '@element-plus/icons-vue'
|
||||
import {
|
||||
Plus,
|
||||
Close,
|
||||
Check,
|
||||
View,
|
||||
Edit,
|
||||
Delete,
|
||||
ScaleToOriginal,
|
||||
RefreshLeft,
|
||||
Search
|
||||
} from '@element-plus/icons-vue'
|
||||
import SystemViewDialog from '@/components/business/system-view-dialog/index.vue'
|
||||
import { withFullscreenLoading, fullscreenLoadingText } from '@/utils/fullscreen-loading'
|
||||
import {
|
||||
@@ -309,7 +353,9 @@
|
||||
async function handleBatchDelete() {
|
||||
const ids = selectedRows.value.map((r) => r.id)
|
||||
if (!ids.length) return
|
||||
await ElMessageBox.confirm(`确定删除选中的 ${ids.length} 个规格吗?`, '提示', { type: 'warning' })
|
||||
await ElMessageBox.confirm(`确定删除选中的 ${ids.length} 个规格吗?`, '提示', {
|
||||
type: 'warning'
|
||||
})
|
||||
await withFullscreenLoading(async () => {
|
||||
for (const id of ids) {
|
||||
await fetchDeleteCellProductSpec(id)
|
||||
|
||||
@@ -22,8 +22,12 @@
|
||||
<div class="filter-actions-row">
|
||||
<span class="filter-hint">按编码、名称模糊查询。</span>
|
||||
<div class="filter-actions-end">
|
||||
<el-button class="btn-ghost" @click="handleReset"><el-icon><RefreshLeft /></el-icon>重置</el-button>
|
||||
<el-button class="btn-main" type="primary" @click="handleSearch"><el-icon><Search /></el-icon>搜索</el-button>
|
||||
<el-button class="btn-ghost" @click="handleReset"
|
||||
><el-icon><RefreshLeft /></el-icon>重置</el-button
|
||||
>
|
||||
<el-button class="btn-main" type="primary" @click="handleSearch"
|
||||
><el-icon><Search /></el-icon>搜索</el-button
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</el-form>
|
||||
@@ -68,11 +72,41 @@
|
||||
header-align="center"
|
||||
:index="(index) => (pagination.page - 1) * pagination.pageSize + index + 1"
|
||||
/>
|
||||
<el-table-column prop="productTypeCode" label="类型编码" min-width="120" align="center" header-align="center" />
|
||||
<el-table-column prop="productTypeName" label="类型名称" min-width="150" align="center" header-align="center" />
|
||||
<el-table-column prop="remark" label="备注" min-width="150" align="center" header-align="center" />
|
||||
<el-table-column prop="createTime" label="创建时间" min-width="180" align="center" header-align="center" />
|
||||
<el-table-column label="操作" min-width="320" fixed="right" align="center" header-align="center">
|
||||
<el-table-column
|
||||
prop="productTypeCode"
|
||||
label="类型编码"
|
||||
min-width="120"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="productTypeName"
|
||||
label="类型名称"
|
||||
min-width="150"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="remark"
|
||||
label="备注"
|
||||
min-width="150"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="createTime"
|
||||
label="创建时间"
|
||||
min-width="180"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
label="操作"
|
||||
min-width="320"
|
||||
fixed="right"
|
||||
align="center"
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<el-button class="btn-op-item" @click="handleView(row)"
|
||||
><el-icon><View /></el-icon>查看</el-button
|
||||
@@ -156,7 +190,17 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, onMounted, computed } from 'vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { Plus, Close, Check, View, Edit, Delete, ScaleToOriginal, RefreshLeft, Search } from '@element-plus/icons-vue'
|
||||
import {
|
||||
Plus,
|
||||
Close,
|
||||
Check,
|
||||
View,
|
||||
Edit,
|
||||
Delete,
|
||||
ScaleToOriginal,
|
||||
RefreshLeft,
|
||||
Search
|
||||
} from '@element-plus/icons-vue'
|
||||
import SystemViewDialog from '@/components/business/system-view-dialog/index.vue'
|
||||
import { withFullscreenLoading, fullscreenLoadingText } from '@/utils/fullscreen-loading'
|
||||
import {
|
||||
@@ -309,7 +353,9 @@
|
||||
async function handleBatchDelete() {
|
||||
const ids = selectedRows.value.map((r) => r.id)
|
||||
if (!ids.length) return
|
||||
await ElMessageBox.confirm(`确定删除选中的 ${ids.length} 个类型吗?`, '提示', { type: 'warning' })
|
||||
await ElMessageBox.confirm(`确定删除选中的 ${ids.length} 个类型吗?`, '提示', {
|
||||
type: 'warning'
|
||||
})
|
||||
await withFullscreenLoading(async () => {
|
||||
for (const id of ids) {
|
||||
await fetchDeleteCellProductType(id)
|
||||
|
||||
@@ -39,6 +39,7 @@
|
||||
<el-option label="退款" value="refund" />
|
||||
<el-option label="购买产品" value="purchase" />
|
||||
<el-option label="退订产品" value="unsubscribe" />
|
||||
<el-option label="输注结算" value="settlement" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="创建时间">
|
||||
@@ -81,7 +82,7 @@
|
||||
min-width="68"
|
||||
align="center"
|
||||
header-align="center"
|
||||
:index="index => (pagination.page - 1) * pagination.pageSize + index + 1"
|
||||
:index="(index) => (pagination.page - 1) * pagination.pageSize + index + 1"
|
||||
/>
|
||||
<el-table-column prop="txnType" label="流水类型" min-width="120" align="center">
|
||||
<template #default="{ row }">
|
||||
@@ -102,8 +103,18 @@
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="orderName" label="订单名称" min-width="160" show-overflow-tooltip />
|
||||
<el-table-column prop="channelName" label="渠道名称" min-width="160" show-overflow-tooltip />
|
||||
<el-table-column
|
||||
prop="orderName"
|
||||
label="订单名称"
|
||||
min-width="160"
|
||||
show-overflow-tooltip
|
||||
/>
|
||||
<el-table-column
|
||||
prop="channelName"
|
||||
label="渠道名称"
|
||||
min-width="160"
|
||||
show-overflow-tooltip
|
||||
/>
|
||||
<el-table-column prop="remark" label="备注" min-width="160" show-overflow-tooltip />
|
||||
<el-table-column prop="createTime" label="创建时间" min-width="170" align="center" />
|
||||
<template v-if="activeTab === 'done'">
|
||||
@@ -130,7 +141,13 @@
|
||||
</template>
|
||||
</el-table-column>
|
||||
</template>
|
||||
<el-table-column label="操作" min-width="300" fixed="right" align="center" header-align="center">
|
||||
<el-table-column
|
||||
label="操作"
|
||||
min-width="300"
|
||||
fixed="right"
|
||||
align="center"
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<el-button class="btn-op-item" @click="handleView(row)">
|
||||
<el-icon><View /></el-icon>查看
|
||||
@@ -150,10 +167,7 @@
|
||||
@confirm="handleVoidTxn(row)"
|
||||
>
|
||||
<template #reference>
|
||||
<el-button
|
||||
class="btn-op-item op-warn"
|
||||
:loading="voidTxnId === row.id"
|
||||
>
|
||||
<el-button class="btn-op-item op-warn" :loading="voidTxnId === row.id">
|
||||
<el-icon><Delete /></el-icon>作废
|
||||
</el-button>
|
||||
</template>
|
||||
@@ -199,14 +213,14 @@
|
||||
width="960px"
|
||||
>
|
||||
<template v-if="viewData.products?.length" #txn-products>
|
||||
<el-table
|
||||
:data="viewData.products"
|
||||
stripe
|
||||
class="list-table inner-table"
|
||||
size="small"
|
||||
>
|
||||
<el-table :data="viewData.products" stripe class="list-table inner-table" size="small">
|
||||
<el-table-column type="index" label="序号" min-width="60" align="center" />
|
||||
<el-table-column prop="projectName" label="项目名称" min-width="150" show-overflow-tooltip />
|
||||
<el-table-column
|
||||
prop="projectName"
|
||||
label="项目名称"
|
||||
min-width="150"
|
||||
show-overflow-tooltip
|
||||
/>
|
||||
<el-table-column prop="cellType" label="细胞类型" min-width="120" show-overflow-tooltip />
|
||||
<el-table-column prop="specText" label="规格" min-width="150" show-overflow-tooltip />
|
||||
<el-table-column label="单价(元)" min-width="120" align="right">
|
||||
@@ -225,10 +239,7 @@
|
||||
@download="handleDownloadAttachment"
|
||||
/>
|
||||
</template>
|
||||
<template
|
||||
v-if="viewData.financeConfirmAttachmentFiles?.length"
|
||||
#finance-confirm-attachments
|
||||
>
|
||||
<template v-if="viewData.financeConfirmAttachmentFiles?.length" #finance-confirm-attachments>
|
||||
<ProtocolAttachmentReadonly
|
||||
:files="viewData.financeConfirmAttachmentFiles"
|
||||
@preview="handlePreviewAttachment"
|
||||
@@ -261,7 +272,9 @@
|
||||
</div>
|
||||
<div class="confirm-flow-kv">
|
||||
<span class="confirm-flow-kv__label">{{ txnAmountLabel(currentRow.txnType) }}</span>
|
||||
<span class="confirm-flow-kv__value amount-text">¥{{ formatAmount(currentRow.amount) }}</span>
|
||||
<span class="confirm-flow-kv__value amount-text"
|
||||
>¥{{ formatAmount(currentRow.amount) }}</span
|
||||
>
|
||||
</div>
|
||||
<div v-if="currentRow.orderName" class="confirm-flow-kv">
|
||||
<span class="confirm-flow-kv__label">订单名称</span>
|
||||
@@ -311,15 +324,20 @@
|
||||
class="detail-section confirm-product-section"
|
||||
>
|
||||
<h4 class="detail-section-title">产品明细</h4>
|
||||
<el-table
|
||||
:data="confirmProducts"
|
||||
stripe
|
||||
class="list-table inner-table"
|
||||
size="small"
|
||||
>
|
||||
<el-table :data="confirmProducts" stripe class="list-table inner-table" size="small">
|
||||
<el-table-column type="index" label="序号" min-width="60" align="center" />
|
||||
<el-table-column prop="projectName" label="项目名称" min-width="150" show-overflow-tooltip />
|
||||
<el-table-column prop="cellType" label="细胞类型" min-width="120" show-overflow-tooltip />
|
||||
<el-table-column
|
||||
prop="projectName"
|
||||
label="项目名称"
|
||||
min-width="150"
|
||||
show-overflow-tooltip
|
||||
/>
|
||||
<el-table-column
|
||||
prop="cellType"
|
||||
label="细胞类型"
|
||||
min-width="120"
|
||||
show-overflow-tooltip
|
||||
/>
|
||||
<el-table-column prop="specText" label="规格" min-width="150" show-overflow-tooltip />
|
||||
<el-table-column label="单价(元)" min-width="120" align="right">
|
||||
<template #default="{ row }">¥{{ formatAmount(row.agreementUnitPrice) }}</template>
|
||||
@@ -337,7 +355,12 @@
|
||||
<h4 class="detail-section-title">确认信息</h4>
|
||||
<el-form label-position="top" class="view-confirm-form">
|
||||
<el-form-item label="财务备注">
|
||||
<el-input v-model="confirmForm.remark" type="textarea" :rows="3" placeholder="请输入备注" />
|
||||
<el-input
|
||||
v-model="confirmForm.remark"
|
||||
type="textarea"
|
||||
:rows="3"
|
||||
placeholder="请输入备注"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="确认凭证" class="form-col-span-2">
|
||||
<div class="attachment-upload">
|
||||
@@ -394,7 +417,10 @@
|
||||
@click="handlePreviewAttachment(file)"
|
||||
><View
|
||||
/></el-icon>
|
||||
<el-icon class="action-btn" title="下载" @click="handleDownloadAttachment(file)"
|
||||
<el-icon
|
||||
class="action-btn"
|
||||
title="下载"
|
||||
@click="handleDownloadAttachment(file)"
|
||||
><Download
|
||||
/></el-icon>
|
||||
<el-icon
|
||||
@@ -454,14 +480,25 @@
|
||||
<el-button class="btn-ghost" @click="amountConfirmVisible = false">
|
||||
<el-icon><Close /></el-icon>取消
|
||||
</el-button>
|
||||
<el-button class="btn-main" type="primary" :loading="submitLoading" @click="executeSubmitConfirm">
|
||||
<el-button
|
||||
class="btn-main"
|
||||
type="primary"
|
||||
:loading="submitLoading"
|
||||
@click="executeSubmitConfirm"
|
||||
>
|
||||
<el-icon><Check /></el-icon>确认
|
||||
</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 附件预览弹窗 -->
|
||||
<el-dialog v-model="previewVisible" title="文件预览" width="800px" align-center destroy-on-close>
|
||||
<el-dialog
|
||||
v-model="previewVisible"
|
||||
title="文件预览"
|
||||
width="800px"
|
||||
align-center
|
||||
destroy-on-close
|
||||
>
|
||||
<div v-if="previewType === 'image'" class="preview-container">
|
||||
<img
|
||||
:src="previewUrl"
|
||||
@@ -531,12 +568,13 @@
|
||||
if (txnType === 'refund') return '退款金额(元)'
|
||||
if (txnType === 'purchase') return '购买金额(元)'
|
||||
if (txnType === 'unsubscribe') return '退订金额(元)'
|
||||
if (txnType === 'settlement') return '结算金额(元)'
|
||||
return '金额(元)'
|
||||
}
|
||||
|
||||
/** 购买/退订流水需展示产品明细 */
|
||||
function txnTypeHasProducts(txnType?: string) {
|
||||
return txnType === 'purchase' || txnType === 'unsubscribe'
|
||||
return txnType === 'purchase' || txnType === 'unsubscribe' || txnType === 'settlement'
|
||||
}
|
||||
|
||||
function financeSubmitRemarkDisplay(d: Record<string, any>) {
|
||||
@@ -545,7 +583,11 @@
|
||||
|
||||
/** 发起确认区附件:退款优先展示 refundAttachmentFiles */
|
||||
function financeSubmitAttachmentDisplayFiles(d: Record<string, any>) {
|
||||
if (d.txnType === 'refund' && Array.isArray(d.refundAttachmentFiles) && d.refundAttachmentFiles.length) {
|
||||
if (
|
||||
d.txnType === 'refund' &&
|
||||
Array.isArray(d.refundAttachmentFiles) &&
|
||||
d.refundAttachmentFiles.length
|
||||
) {
|
||||
return d.refundAttachmentFiles
|
||||
}
|
||||
return Array.isArray(d.financeSubmitAttachmentFiles) ? d.financeSubmitAttachmentFiles : []
|
||||
@@ -700,7 +742,11 @@
|
||||
fields: [
|
||||
{ label: '流水类型', value: txnTypeLabel(d.txnType), tagType: txnTypeTagType(d.txnType) },
|
||||
{ label: '金额(元)', value: `¥${formatAmount(d.amount)}` },
|
||||
{ label: '状态', value: txnStatusLabel(d.txnStatus), tagType: txnStatusTagType(d.txnStatus) },
|
||||
{
|
||||
label: '状态',
|
||||
value: txnStatusLabel(d.txnStatus),
|
||||
tagType: txnStatusTagType(d.txnStatus)
|
||||
},
|
||||
{ label: '订单名称', value: d.orderName },
|
||||
{ label: '渠道名称', value: d.channelName },
|
||||
{ label: '创建人', value: d.createByName || d.createBy },
|
||||
@@ -1006,7 +1052,6 @@
|
||||
color: var(--el-color-primary);
|
||||
}
|
||||
|
||||
|
||||
.finance-receipt-dialog {
|
||||
:deep(.el-dialog__body) {
|
||||
padding-top: 16px;
|
||||
|
||||
+1145
-1101
File diff suppressed because it is too large.
Load diff
@@ -8,7 +8,13 @@
|
||||
class="list-table inner-table"
|
||||
:max-height="maxHeight"
|
||||
>
|
||||
<el-table-column type="index" label="序号" min-width="68" align="center" header-align="center" />
|
||||
<el-table-column
|
||||
type="index"
|
||||
label="序号"
|
||||
min-width="68"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column label="类型" min-width="120" align="center" header-align="center">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="txnTypeTagType(row.txnType)" size="small">
|
||||
@@ -29,7 +35,13 @@
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="remark" label="备注" min-width="160" show-overflow-tooltip />
|
||||
<el-table-column prop="createTime" label="创建时间" min-width="170" align="center" header-align="center" />
|
||||
<el-table-column
|
||||
prop="createTime"
|
||||
label="创建时间"
|
||||
min-width="170"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column label="作废信息" min-width="200" show-overflow-tooltip>
|
||||
<template #default="{ row }">
|
||||
<span v-if="row.txnStatus === 'yzf'" class="void-info">
|
||||
@@ -44,51 +56,51 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import {
|
||||
txnTypeLabel,
|
||||
txnTypeTagType,
|
||||
txnStatusLabel,
|
||||
txnStatusTagType
|
||||
} from '@/utils/status-tag-registry'
|
||||
import { formatMoneyDisplay } from '@/utils/money-format'
|
||||
import {
|
||||
txnTypeLabel,
|
||||
txnTypeTagType,
|
||||
txnStatusLabel,
|
||||
txnStatusTagType
|
||||
} from '@/utils/status-tag-registry'
|
||||
import { formatMoneyDisplay } from '@/utils/money-format'
|
||||
|
||||
defineOptions({ name: 'ServiceOrderTxnTable' })
|
||||
defineOptions({ name: 'ServiceOrderTxnTable' })
|
||||
|
||||
withDefaults(
|
||||
defineProps<{
|
||||
rows: Record<string, any>[]
|
||||
maxHeight?: number | string
|
||||
}>(),
|
||||
{
|
||||
rows: () => [],
|
||||
maxHeight: 360
|
||||
withDefaults(
|
||||
defineProps<{
|
||||
rows: Record<string, any>[]
|
||||
maxHeight?: number | string
|
||||
}>(),
|
||||
{
|
||||
rows: () => [],
|
||||
maxHeight: 360
|
||||
}
|
||||
)
|
||||
|
||||
const formatAmount = (v: unknown): string => {
|
||||
if (v == null || v === '') return '0.00'
|
||||
return formatMoneyDisplay(v)
|
||||
}
|
||||
)
|
||||
|
||||
const formatAmount = (v: unknown): string => {
|
||||
if (v == null || v === '') return '0.00'
|
||||
return formatMoneyDisplay(v)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.service-order-txn-table-wrap {
|
||||
margin-top: 0;
|
||||
padding: 0;
|
||||
border: none;
|
||||
}
|
||||
.service-order-txn-table-wrap {
|
||||
margin-top: 0;
|
||||
padding: 0;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.amount-text {
|
||||
font-weight: 600;
|
||||
color: var(--mint-primary, #10b981);
|
||||
}
|
||||
.amount-text {
|
||||
font-weight: 600;
|
||||
color: var(--mint-primary, #10b981);
|
||||
}
|
||||
|
||||
.void-info {
|
||||
font-size: 13px;
|
||||
color: var(--mint-text-muted, #64748b);
|
||||
.void-info {
|
||||
font-size: 13px;
|
||||
color: var(--mint-text-muted, #64748b);
|
||||
|
||||
&.muted {
|
||||
color: #cbd5e1;
|
||||
&.muted {
|
||||
color: #cbd5e1;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
File diff suppressed because it is too large.
Load diff
@@ -1,411 +1,422 @@
|
||||
<!-- 渠道信息 — 只读查看(样式对齐采集协议单 ProtocolViewDialog) -->
|
||||
<template>
|
||||
<div class="channel-view-dialog-root">
|
||||
<el-dialog
|
||||
:model-value="modelValue"
|
||||
:title="dialogTitle"
|
||||
width="1100px"
|
||||
class="dialog-form-layout channel-view-dialog"
|
||||
align-center
|
||||
destroy-on-close
|
||||
:close-on-click-modal="false"
|
||||
@update:model-value="emit('update:modelValue', $event)"
|
||||
@closed="handleClosed"
|
||||
>
|
||||
<div class="channel-view-body" :class="{ 'is-sub-loading': loading && channel }">
|
||||
<template v-if="channel">
|
||||
<div v-loading="loading" class="detail-tabs">
|
||||
<div
|
||||
v-for="tab in viewTabs"
|
||||
:key="tab.key"
|
||||
class="detail-tab"
|
||||
:class="{ active: activeTab === tab.key }"
|
||||
@click="scrollToSection(tab.key)"
|
||||
>
|
||||
{{ tab.label }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="kpi-strip">
|
||||
<div class="kpi-card">
|
||||
<div class="kpi-card__icon" aria-hidden="true">
|
||||
<el-icon><OfficeBuilding /></el-icon>
|
||||
</div>
|
||||
<div class="kpi-card__main">
|
||||
<div class="kpi-label">渠道名称</div>
|
||||
<div class="kpi-value kpi-value--emphasis">{{ dash(channel.channelName) }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="kpi-card">
|
||||
<div class="kpi-card__icon" aria-hidden="true">
|
||||
<el-icon><CollectionTag /></el-icon>
|
||||
</div>
|
||||
<div class="kpi-card__main">
|
||||
<div class="kpi-label">渠道类型</div>
|
||||
<div class="kpi-value">
|
||||
<el-tag :type="channelTypeTagType(channel.channelType)" size="small">
|
||||
{{ channelTypeLabel(channel.channelType) }}
|
||||
</el-tag>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="kpi-card">
|
||||
<div class="kpi-card__icon" aria-hidden="true">
|
||||
<el-icon><User /></el-icon>
|
||||
</div>
|
||||
<div class="kpi-card__main">
|
||||
<div class="kpi-label">联系人</div>
|
||||
<div class="kpi-value">{{ dash(channel.contactName) }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="kpi-card">
|
||||
<div class="kpi-card__icon" aria-hidden="true">
|
||||
<el-icon><Calendar /></el-icon>
|
||||
</div>
|
||||
<div class="kpi-card__main">
|
||||
<div class="kpi-label">创建时间</div>
|
||||
<div class="kpi-value">{{ dash(channel.createTime) }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="view-section-basic" class="section-card section-card--white">
|
||||
<div class="section-title"><h3>基础信息</h3></div>
|
||||
<div class="data-grid data-grid--3">
|
||||
<div class="data-item">
|
||||
<div class="data-label">渠道名称</div>
|
||||
<div class="data-value">{{ dash(channel.channelName) }}</div>
|
||||
</div>
|
||||
<div class="data-item">
|
||||
<div class="data-label">统一社会信用代码</div>
|
||||
<div class="data-value">{{ dash(channel.creditCode) }}</div>
|
||||
</div>
|
||||
<div class="data-item">
|
||||
<div class="data-label">渠道类型</div>
|
||||
<div class="data-value">
|
||||
<el-tag :type="channelTypeTagType(channel.channelType)" size="small">
|
||||
{{ channelTypeLabel(channel.channelType) }}
|
||||
</el-tag>
|
||||
</div>
|
||||
</div>
|
||||
<div class="data-item">
|
||||
<div class="data-label">联系人</div>
|
||||
<div class="data-value">{{ dash(channel.contactName) }}</div>
|
||||
</div>
|
||||
<div class="data-item">
|
||||
<div class="data-label">联系电话</div>
|
||||
<div class="data-value">{{ dash(channel.contactPhone) }}</div>
|
||||
</div>
|
||||
<div class="data-item">
|
||||
<div class="data-label">联系邮箱</div>
|
||||
<div class="data-value">{{ dash(channel.contactEmail) }}</div>
|
||||
</div>
|
||||
<div class="data-item">
|
||||
<div class="data-label">创建时间</div>
|
||||
<div class="data-value">{{ dash(channel.createTime) }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="protocolAttachmentFiles.length" class="attachment-row">
|
||||
<div class="data-label">渠道协议附件</div>
|
||||
<ProtocolAttachmentReadonly
|
||||
:files="protocolAttachmentFiles"
|
||||
@preview="openPreview"
|
||||
@download="downloadFile"
|
||||
/>
|
||||
</div>
|
||||
<div v-if="channel.remark" class="remark-row">
|
||||
<div class="data-item data-item--full">
|
||||
<div class="data-label">备注</div>
|
||||
<div class="data-value data-value--muted">{{ dash(channel.remark) }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="view-section-invoice" class="section-card section-card--green">
|
||||
<div class="section-title"><h3>开票信息</h3></div>
|
||||
<div v-if="invoiceList.length" class="sub-cards">
|
||||
<div v-for="(row, idx) in invoiceList" :key="row.id || idx" class="sub-card">
|
||||
<div class="sub-card-header">
|
||||
<h4>{{ row.companyName || '未命名' }}</h4>
|
||||
</div>
|
||||
<div class="data-grid data-grid--2">
|
||||
<div v-if="row.taxNo" class="data-item">
|
||||
<div class="data-label">税号</div>
|
||||
<div class="data-value">{{ dash(row.taxNo) }}</div>
|
||||
</div>
|
||||
<div v-if="row.phone || row.companyPhone" class="data-item">
|
||||
<div class="data-label">电话</div>
|
||||
<div class="data-value">{{ dash(row.phone || row.companyPhone) }}</div>
|
||||
</div>
|
||||
<div v-if="row.companyAddress" class="data-item data-item--full">
|
||||
<div class="data-label">单位地址</div>
|
||||
<div class="data-value data-value--muted">{{ dash(row.companyAddress) }}</div>
|
||||
</div>
|
||||
<div v-if="row.bankName" class="data-item">
|
||||
<div class="data-label">开户银行</div>
|
||||
<div class="data-value">{{ dash(row.bankName) }}</div>
|
||||
</div>
|
||||
<div v-if="row.bankAccount" class="data-item">
|
||||
<div class="data-label">银行账号</div>
|
||||
<div class="data-value">{{ dash(row.bankAccount) }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<el-empty v-else description="暂无开票信息" :image-size="48" />
|
||||
</div>
|
||||
|
||||
<div id="view-section-mail" class="section-card section-card--white">
|
||||
<div class="section-title"><h3>邮寄地址</h3></div>
|
||||
<div v-if="mailList.length" class="sub-cards">
|
||||
<div v-for="(row, idx) in mailList" :key="row.id || idx" class="sub-card">
|
||||
<div class="sub-card-header">
|
||||
<h4>{{ row.mailAddress || '未命名' }}</h4>
|
||||
</div>
|
||||
<div class="data-grid data-grid--2">
|
||||
<div v-if="row.mailContactName || row.mailContact" class="data-item">
|
||||
<div class="data-label">联系人</div>
|
||||
<div class="data-value">{{ dash(row.mailContactName || row.mailContact) }}</div>
|
||||
</div>
|
||||
<div v-if="row.mailContactPhone || row.mailPhone" class="data-item">
|
||||
<div class="data-label">联系电话</div>
|
||||
<div class="data-value">{{ dash(row.mailContactPhone || row.mailPhone) }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<el-empty v-else description="暂无邮寄地址" :image-size="48" />
|
||||
</div>
|
||||
|
||||
<div id="view-section-account" class="section-card section-card--white">
|
||||
<div class="section-title"><h3>账户信息</h3></div>
|
||||
<div class="account-balance-card">
|
||||
<div class="account-balance-card__label">账户余额</div>
|
||||
<div class="account-balance-card__value">
|
||||
¥ {{ accountLoading ? '—' : formatAmount(accountData?.balanceAmount) }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="account-quotas-head">
|
||||
<h4 class="account-quotas-title">产品份数</h4>
|
||||
</div>
|
||||
<div v-if="quotasLoading" v-loading="true" style="min-height: 120px"></div>
|
||||
<div v-else-if="productQuotas.length" class="list-body list-wrap account-quota-list-wrap">
|
||||
<el-table
|
||||
:data="productQuotas"
|
||||
class="list-table account-quota-table"
|
||||
stripe
|
||||
row-key="id"
|
||||
max-height="360"
|
||||
>
|
||||
<el-table-column
|
||||
type="index"
|
||||
label="序号"
|
||||
min-width="68"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="projectName"
|
||||
label="项目名称"
|
||||
min-width="150"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="cellTypeName"
|
||||
label="细胞类型"
|
||||
min-width="120"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="specText"
|
||||
label="规格"
|
||||
min-width="150"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="totalQty"
|
||||
label="总份数"
|
||||
min-width="100"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="usedQty"
|
||||
label="已用份数"
|
||||
min-width="100"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="remainingQty"
|
||||
label="剩余份数"
|
||||
min-width="100"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
</el-table>
|
||||
</div>
|
||||
<el-empty v-else description="暂无产品份数数据" :image-size="48" />
|
||||
</div>
|
||||
|
||||
<div id="view-section-agreement" class="section-card">
|
||||
<div class="section-title"><h3>协议信息</h3></div>
|
||||
<div v-if="agreementList.length" class="agreement-cards">
|
||||
<el-dialog
|
||||
:model-value="modelValue"
|
||||
:title="dialogTitle"
|
||||
width="1100px"
|
||||
class="dialog-form-layout channel-view-dialog"
|
||||
align-center
|
||||
destroy-on-close
|
||||
:close-on-click-modal="false"
|
||||
@update:model-value="emit('update:modelValue', $event)"
|
||||
@closed="handleClosed"
|
||||
>
|
||||
<div class="channel-view-body" :class="{ 'is-sub-loading': loading && channel }">
|
||||
<template v-if="channel">
|
||||
<div v-loading="loading" class="detail-tabs">
|
||||
<div
|
||||
v-for="(agr, idx) in agreementList"
|
||||
:key="agr.id || idx"
|
||||
class="agreement-card"
|
||||
v-for="tab in viewTabs"
|
||||
:key="tab.key"
|
||||
class="detail-tab"
|
||||
:class="{ active: activeTab === tab.key }"
|
||||
@click="scrollToSection(tab.key)"
|
||||
>
|
||||
<div class="agreement-card__header">
|
||||
<div class="agreement-card__tags">
|
||||
<el-tag :type="agreementTypeTagType(agr.agreementType)" size="small">
|
||||
{{ agreementTypeLabel(agr.agreementType) }}
|
||||
</el-tag>
|
||||
<el-tag :type="agreementStatusTagType(agr.status)" size="small">
|
||||
{{ agreementStatusLabel(agr.status) }}
|
||||
{{ tab.label }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="kpi-strip">
|
||||
<div class="kpi-card">
|
||||
<div class="kpi-card__icon" aria-hidden="true">
|
||||
<el-icon><OfficeBuilding /></el-icon>
|
||||
</div>
|
||||
<div class="kpi-card__main">
|
||||
<div class="kpi-label">渠道名称</div>
|
||||
<div class="kpi-value kpi-value--emphasis">{{ dash(channel.channelName) }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="kpi-card">
|
||||
<div class="kpi-card__icon" aria-hidden="true">
|
||||
<el-icon><CollectionTag /></el-icon>
|
||||
</div>
|
||||
<div class="kpi-card__main">
|
||||
<div class="kpi-label">渠道类型</div>
|
||||
<div class="kpi-value">
|
||||
<el-tag :type="channelTypeTagType(channel.channelType)" size="small">
|
||||
{{ channelTypeLabel(channel.channelType) }}
|
||||
</el-tag>
|
||||
</div>
|
||||
</div>
|
||||
<div class="data-grid data-grid--4 agreement-card__info">
|
||||
<div v-if="agr.settlementType" class="data-item">
|
||||
<div class="data-label">结算方式</div>
|
||||
<div class="data-value">
|
||||
<el-tag :type="settlementTypeTagType(agr.settlementType)" size="small">
|
||||
{{ settlementTypeLabel(agr.settlementType) }}
|
||||
</div>
|
||||
<div class="kpi-card">
|
||||
<div class="kpi-card__icon" aria-hidden="true">
|
||||
<el-icon><User /></el-icon>
|
||||
</div>
|
||||
<div class="kpi-card__main">
|
||||
<div class="kpi-label">联系人</div>
|
||||
<div class="kpi-value">{{ dash(channel.contactName) }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="kpi-card">
|
||||
<div class="kpi-card__icon" aria-hidden="true">
|
||||
<el-icon><Calendar /></el-icon>
|
||||
</div>
|
||||
<div class="kpi-card__main">
|
||||
<div class="kpi-label">创建时间</div>
|
||||
<div class="kpi-value">{{ dash(channel.createTime) }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="view-section-basic" class="section-card section-card--white">
|
||||
<div class="section-title"><h3>基础信息</h3></div>
|
||||
<div class="data-grid data-grid--3">
|
||||
<div class="data-item">
|
||||
<div class="data-label">渠道名称</div>
|
||||
<div class="data-value">{{ dash(channel.channelName) }}</div>
|
||||
</div>
|
||||
<div class="data-item">
|
||||
<div class="data-label">统一社会信用代码</div>
|
||||
<div class="data-value">{{ dash(channel.creditCode) }}</div>
|
||||
</div>
|
||||
<div class="data-item">
|
||||
<div class="data-label">渠道类型</div>
|
||||
<div class="data-value">
|
||||
<el-tag :type="channelTypeTagType(channel.channelType)" size="small">
|
||||
{{ channelTypeLabel(channel.channelType) }}
|
||||
</el-tag>
|
||||
</div>
|
||||
</div>
|
||||
<div class="data-item">
|
||||
<div class="data-label">联系人</div>
|
||||
<div class="data-value">{{ dash(channel.contactName) }}</div>
|
||||
</div>
|
||||
<div class="data-item">
|
||||
<div class="data-label">联系电话</div>
|
||||
<div class="data-value">{{ dash(channel.contactPhone) }}</div>
|
||||
</div>
|
||||
<div class="data-item">
|
||||
<div class="data-label">联系邮箱</div>
|
||||
<div class="data-value">{{ dash(channel.contactEmail) }}</div>
|
||||
</div>
|
||||
<div class="data-item">
|
||||
<div class="data-label">创建时间</div>
|
||||
<div class="data-value">{{ dash(channel.createTime) }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="protocolAttachmentFiles.length" class="attachment-row">
|
||||
<div class="data-label">渠道协议附件</div>
|
||||
<ProtocolAttachmentReadonly
|
||||
:files="protocolAttachmentFiles"
|
||||
@preview="openPreview"
|
||||
@download="downloadFile"
|
||||
/>
|
||||
</div>
|
||||
<div v-if="channel.remark" class="remark-row">
|
||||
<div class="data-item data-item--full">
|
||||
<div class="data-label">备注</div>
|
||||
<div class="data-value data-value--muted">{{ dash(channel.remark) }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="view-section-invoice" class="section-card section-card--green">
|
||||
<div class="section-title"><h3>开票信息</h3></div>
|
||||
<div v-if="invoiceList.length" class="sub-cards">
|
||||
<div v-for="(row, idx) in invoiceList" :key="row.id || idx" class="sub-card">
|
||||
<div class="sub-card-header">
|
||||
<h4>{{ row.companyName || '未命名' }}</h4>
|
||||
</div>
|
||||
<div class="data-grid data-grid--2">
|
||||
<div v-if="row.taxNo" class="data-item">
|
||||
<div class="data-label">税号</div>
|
||||
<div class="data-value">{{ dash(row.taxNo) }}</div>
|
||||
</div>
|
||||
<div v-if="row.phone || row.companyPhone" class="data-item">
|
||||
<div class="data-label">电话</div>
|
||||
<div class="data-value">{{ dash(row.phone || row.companyPhone) }}</div>
|
||||
</div>
|
||||
<div v-if="row.companyAddress" class="data-item data-item--full">
|
||||
<div class="data-label">单位地址</div>
|
||||
<div class="data-value data-value--muted">{{ dash(row.companyAddress) }}</div>
|
||||
</div>
|
||||
<div v-if="row.bankName" class="data-item">
|
||||
<div class="data-label">开户银行</div>
|
||||
<div class="data-value">{{ dash(row.bankName) }}</div>
|
||||
</div>
|
||||
<div v-if="row.bankAccount" class="data-item">
|
||||
<div class="data-label">银行账号</div>
|
||||
<div class="data-value">{{ dash(row.bankAccount) }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<el-empty v-else description="暂无开票信息" :image-size="48" />
|
||||
</div>
|
||||
|
||||
<div id="view-section-mail" class="section-card section-card--white">
|
||||
<div class="section-title"><h3>邮寄地址</h3></div>
|
||||
<div v-if="mailList.length" class="sub-cards">
|
||||
<div v-for="(row, idx) in mailList" :key="row.id || idx" class="sub-card">
|
||||
<div class="sub-card-header">
|
||||
<h4>{{ row.mailAddress || '未命名' }}</h4>
|
||||
</div>
|
||||
<div class="data-grid data-grid--2">
|
||||
<div v-if="row.mailContactName || row.mailContact" class="data-item">
|
||||
<div class="data-label">联系人</div>
|
||||
<div class="data-value">{{ dash(row.mailContactName || row.mailContact) }}</div>
|
||||
</div>
|
||||
<div v-if="row.mailContactPhone || row.mailPhone" class="data-item">
|
||||
<div class="data-label">联系电话</div>
|
||||
<div class="data-value">{{ dash(row.mailContactPhone || row.mailPhone) }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<el-empty v-else description="暂无邮寄地址" :image-size="48" />
|
||||
</div>
|
||||
|
||||
<div id="view-section-account" class="section-card section-card--white">
|
||||
<div class="section-title"><h3>账户信息</h3></div>
|
||||
<div class="account-balance-card">
|
||||
<div class="account-balance-card__label">账户余额</div>
|
||||
<div class="account-balance-card__value">
|
||||
¥ {{ accountLoading ? '—' : formatAmount(accountData?.balanceAmount) }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="account-quotas-head">
|
||||
<h4 class="account-quotas-title">产品份数</h4>
|
||||
</div>
|
||||
<div v-if="quotasLoading" v-loading="true" style="min-height: 120px"></div>
|
||||
<div
|
||||
v-else-if="productQuotas.length"
|
||||
class="list-body list-wrap account-quota-list-wrap"
|
||||
>
|
||||
<el-table
|
||||
:data="productQuotas"
|
||||
class="list-table account-quota-table"
|
||||
stripe
|
||||
row-key="id"
|
||||
max-height="360"
|
||||
>
|
||||
<el-table-column
|
||||
type="index"
|
||||
label="序号"
|
||||
min-width="68"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="projectName"
|
||||
label="项目名称"
|
||||
min-width="150"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="cellTypeName"
|
||||
label="细胞类型"
|
||||
min-width="120"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="specText"
|
||||
label="规格"
|
||||
min-width="150"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="totalQty"
|
||||
label="总份数"
|
||||
min-width="100"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="usedQty"
|
||||
label="已用份数"
|
||||
min-width="100"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="remainingQty"
|
||||
label="剩余份数"
|
||||
min-width="100"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
</el-table>
|
||||
</div>
|
||||
<el-empty v-else description="暂无产品份数数据" :image-size="48" />
|
||||
</div>
|
||||
|
||||
<div id="view-section-agreement" class="section-card">
|
||||
<div class="section-title"><h3>协议信息</h3></div>
|
||||
<div v-if="agreementList.length" class="agreement-cards">
|
||||
<div v-for="(agr, idx) in agreementList" :key="agr.id || idx" class="agreement-card">
|
||||
<div class="agreement-card__header">
|
||||
<div class="agreement-card__tags">
|
||||
<el-tag :type="agreementTypeTagType(agr.agreementType)" size="small">
|
||||
{{ agreementTypeLabel(agr.agreementType) }}
|
||||
</el-tag>
|
||||
<el-tag :type="agreementStatusTagType(agr.status)" size="small">
|
||||
{{ agreementStatusLabel(agr.status) }}
|
||||
</el-tag>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="agr.creditLimit != null" class="data-item">
|
||||
<div class="data-label">授信额度(元)</div>
|
||||
<div class="data-value data-value--price">{{ dash(agr.creditLimit) }}</div>
|
||||
<div class="data-grid data-grid--4 agreement-card__info">
|
||||
<div v-if="agr.settlementType" class="data-item">
|
||||
<div class="data-label">结算方式</div>
|
||||
<div class="data-value">
|
||||
<el-tag :type="settlementTypeTagType(agr.settlementType)" size="small">
|
||||
{{ settlementTypeLabel(agr.settlementType) }}
|
||||
</el-tag>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="agr.creditLimit != null" class="data-item">
|
||||
<div class="data-label">授信额度(元)</div>
|
||||
<div class="data-value data-value--price">{{ dash(agr.creditLimit) }}</div>
|
||||
</div>
|
||||
<div v-if="agr.effectiveDate" class="data-item">
|
||||
<div class="data-label">生效日期</div>
|
||||
<div class="data-value">{{ dash(agr.effectiveDate) }}</div>
|
||||
</div>
|
||||
<div v-if="agr.expiryDate" class="data-item">
|
||||
<div class="data-label">到期日期</div>
|
||||
<div class="data-value">{{ dash(agr.expiryDate) }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="agr.effectiveDate" class="data-item">
|
||||
<div class="data-label">生效日期</div>
|
||||
<div class="data-value">{{ dash(agr.effectiveDate) }}</div>
|
||||
</div>
|
||||
<div v-if="agr.expiryDate" class="data-item">
|
||||
<div class="data-label">到期日期</div>
|
||||
<div class="data-value">{{ dash(agr.expiryDate) }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="agreement-card__products">
|
||||
<div class="agreement-products-head">
|
||||
<h4 class="agreement-products-title">细胞产品信息</h4>
|
||||
</div>
|
||||
<div
|
||||
v-if="(agreementPricesMap[agr.id] || []).length > 0"
|
||||
class="list-body list-wrap agreement-price-list-wrap"
|
||||
>
|
||||
<el-table
|
||||
:data="agreementPricesMap[agr.id] || []"
|
||||
class="list-table agreement-price-table"
|
||||
stripe
|
||||
row-key="id"
|
||||
max-height="360"
|
||||
<div class="agreement-card__products">
|
||||
<div class="agreement-products-head">
|
||||
<h4 class="agreement-products-title">细胞产品信息</h4>
|
||||
</div>
|
||||
<div
|
||||
v-if="(agreementPricesMap[agr.id] || []).length > 0"
|
||||
class="list-body list-wrap agreement-price-list-wrap"
|
||||
>
|
||||
<el-table-column
|
||||
type="index"
|
||||
label="序号"
|
||||
min-width="68"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="projectName"
|
||||
label="项目名称"
|
||||
min-width="120"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="projectCode"
|
||||
label="项目编码"
|
||||
min-width="100"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="cellTypeName"
|
||||
label="细胞类型"
|
||||
min-width="100"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="specText"
|
||||
label="规格"
|
||||
min-width="100"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
label="原价(元)"
|
||||
min-width="100"
|
||||
align="center"
|
||||
header-align="center"
|
||||
<el-table
|
||||
:data="agreementPricesMap[agr.id] || []"
|
||||
class="list-table agreement-price-table"
|
||||
stripe
|
||||
row-key="id"
|
||||
max-height="360"
|
||||
>
|
||||
<template #default="{ row: priceRow }">
|
||||
{{ formatPrice(priceRow.originalPrice) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
label="协议价(元)"
|
||||
min-width="100"
|
||||
align="center"
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row: priceRow }">
|
||||
<span class="data-value--price">{{ formatPrice(priceRow.agreementPrice) }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="remark"
|
||||
label="备注"
|
||||
min-width="120"
|
||||
align="center"
|
||||
header-align="center"
|
||||
show-overflow-tooltip
|
||||
/>
|
||||
</el-table>
|
||||
<el-table-column
|
||||
type="index"
|
||||
label="序号"
|
||||
min-width="68"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="projectName"
|
||||
label="项目名称"
|
||||
min-width="120"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="projectCode"
|
||||
label="项目编码"
|
||||
min-width="100"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="cellTypeName"
|
||||
label="细胞类型"
|
||||
min-width="100"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="specText"
|
||||
label="规格"
|
||||
min-width="100"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
label="原价(元)"
|
||||
min-width="100"
|
||||
align="center"
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row: priceRow }">
|
||||
{{ formatPrice(priceRow.originalPrice) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
label="协议价(元)"
|
||||
min-width="100"
|
||||
align="center"
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row: priceRow }">
|
||||
<span class="data-value--price">{{
|
||||
formatPrice(priceRow.agreementPrice)
|
||||
}}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="remark"
|
||||
label="备注"
|
||||
min-width="120"
|
||||
align="center"
|
||||
header-align="center"
|
||||
show-overflow-tooltip
|
||||
/>
|
||||
</el-table>
|
||||
</div>
|
||||
<el-empty
|
||||
v-else
|
||||
description="暂无细胞产品信息"
|
||||
:image-size="48"
|
||||
class="agreement-price-empty"
|
||||
/>
|
||||
</div>
|
||||
<el-empty v-else description="暂无细胞产品信息" :image-size="48" class="agreement-price-empty" />
|
||||
</div>
|
||||
</div>
|
||||
<el-empty v-else description="暂无协议信息" :image-size="48" />
|
||||
</div>
|
||||
<el-empty v-else description="暂无协议信息" :image-size="48" />
|
||||
</div>
|
||||
</template>
|
||||
<div v-else v-loading="true" style="min-height: 200px"></div>
|
||||
</div>
|
||||
|
||||
<template #footer>
|
||||
<el-button class="btn-ghost" @click="emit('update:modelValue', false)">
|
||||
<el-icon><Close /></el-icon>
|
||||
关闭
|
||||
</el-button>
|
||||
</template>
|
||||
<div v-else v-loading="true" style="min-height: 200px"></div>
|
||||
</div>
|
||||
</el-dialog>
|
||||
|
||||
<template #footer>
|
||||
<el-button class="btn-ghost" @click="emit('update:modelValue', false)">
|
||||
<el-icon><Close /></el-icon>
|
||||
关闭
|
||||
</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<el-dialog v-model="previewVisible" title="文件预览" width="800px" align-center destroy-on-close>
|
||||
<div v-if="previewType === 'image'" class="preview-container">
|
||||
<img
|
||||
:src="previewUrl"
|
||||
alt="预览"
|
||||
style="max-width: 100%; max-height: 70vh; display: block; margin: 0 auto"
|
||||
/>
|
||||
</div>
|
||||
<div v-else class="preview-container">
|
||||
<video
|
||||
:src="previewUrl"
|
||||
controls
|
||||
style="max-width: 100%; max-height: 70vh; display: block; margin: 0 auto"
|
||||
/>
|
||||
</div>
|
||||
</el-dialog>
|
||||
<el-dialog
|
||||
v-model="previewVisible"
|
||||
title="文件预览"
|
||||
width="800px"
|
||||
align-center
|
||||
destroy-on-close
|
||||
>
|
||||
<div v-if="previewType === 'image'" class="preview-container">
|
||||
<img
|
||||
:src="previewUrl"
|
||||
alt="预览"
|
||||
style="max-width: 100%; max-height: 70vh; display: block; margin: 0 auto"
|
||||
/>
|
||||
</div>
|
||||
<div v-else class="preview-container">
|
||||
<video
|
||||
:src="previewUrl"
|
||||
controls
|
||||
style="max-width: 100%; max-height: 70vh; display: block; margin: 0 auto"
|
||||
/>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -600,9 +611,7 @@
|
||||
if (!agr.id) return [agr.id, []] as const
|
||||
try {
|
||||
const priceRes = await fetchChannelAgreementPrices(agr.id)
|
||||
const prices = Array.isArray(priceRes)
|
||||
? priceRes
|
||||
: (priceRes as any)?.list || []
|
||||
const prices = Array.isArray(priceRes) ? priceRes : (priceRes as any)?.list || []
|
||||
return [agr.id, prices] as const
|
||||
} catch {
|
||||
return [agr.id, []] as const
|
||||
|
||||
@@ -22,8 +22,12 @@
|
||||
<div class="filter-actions-row">
|
||||
<span class="filter-hint">按渠道名称、统一社会信用代码模糊查询。</span>
|
||||
<div class="filter-actions-end">
|
||||
<el-button class="btn-ghost" @click="handleReset"><el-icon><RefreshLeft /></el-icon>重置</el-button>
|
||||
<el-button class="btn-main" type="primary" @click="handleSearch"><el-icon><Search /></el-icon>搜索</el-button>
|
||||
<el-button class="btn-ghost" @click="handleReset"
|
||||
><el-icon><RefreshLeft /></el-icon>重置</el-button
|
||||
>
|
||||
<el-button class="btn-main" type="primary" @click="handleSearch"
|
||||
><el-icon><Search /></el-icon>搜索</el-button
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</el-form>
|
||||
@@ -116,7 +120,13 @@
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column label="操作" min-width="400" fixed="right" align="center" header-align="center">
|
||||
<el-table-column
|
||||
label="操作"
|
||||
min-width="400"
|
||||
fixed="right"
|
||||
align="center"
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<el-button class="btn-op-item" @click="handleView(row)">
|
||||
<el-icon><View /></el-icon>查看
|
||||
@@ -214,7 +224,11 @@
|
||||
<el-form-item label="联系邮箱" prop="contactEmail">
|
||||
<el-input v-model="formData.contactEmail" placeholder="请输入联系邮箱" />
|
||||
</el-form-item>
|
||||
<el-form-item label="渠道协议附件" class="form-col-span-2" style="margin-top: 18px">
|
||||
<el-form-item
|
||||
label="渠道协议附件"
|
||||
class="form-col-span-2"
|
||||
style="margin-top: 18px"
|
||||
>
|
||||
<div class="attachment-upload">
|
||||
<div class="attachment-list">
|
||||
<el-upload
|
||||
@@ -282,9 +296,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<span class="upload-hint"
|
||||
>支持图片、视频、PDF、Word、Excel等文件</span
|
||||
>
|
||||
<span class="upload-hint">支持图片、视频、PDF、Word、Excel等文件</span>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="备注" class="form-col-span-2">
|
||||
@@ -304,12 +316,7 @@
|
||||
<div class="section-card">
|
||||
<div class="section-title">
|
||||
<div class="section-title-left"><h3>开票信息</h3></div>
|
||||
<el-button
|
||||
class="btn-main"
|
||||
type="primary"
|
||||
size="small"
|
||||
@click="handleAddInvoice"
|
||||
>
|
||||
<el-button class="btn-main" type="primary" size="small" @click="handleAddInvoice">
|
||||
<el-icon><Plus /></el-icon>
|
||||
新增
|
||||
</el-button>
|
||||
@@ -320,11 +327,21 @@
|
||||
v-loading="!!deletingInvoiceId"
|
||||
element-loading-text="删除中…"
|
||||
>
|
||||
<div v-for="(row, $index) in invoiceList" :key="row.id || $index" class="sub-card">
|
||||
<div
|
||||
v-for="(row, $index) in invoiceList"
|
||||
:key="row.id || $index"
|
||||
class="sub-card"
|
||||
>
|
||||
<div class="sub-card-header">
|
||||
<h4>{{ row.companyName || '未命名' }}</h4>
|
||||
<div class="sub-card-actions">
|
||||
<el-button class="btn-op-item primary" link size="small" @click="handleEditInvoice(row)">编辑</el-button>
|
||||
<el-button
|
||||
class="btn-op-item primary"
|
||||
link
|
||||
size="small"
|
||||
@click="handleEditInvoice(row)"
|
||||
>编辑</el-button
|
||||
>
|
||||
<el-button
|
||||
class="btn-op-item"
|
||||
type="danger"
|
||||
@@ -371,12 +388,7 @@
|
||||
<div class="section-card">
|
||||
<div class="section-title">
|
||||
<div class="section-title-left"><h3>邮寄地址</h3></div>
|
||||
<el-button
|
||||
class="btn-main"
|
||||
type="primary"
|
||||
size="small"
|
||||
@click="handleAddMail"
|
||||
>
|
||||
<el-button class="btn-main" type="primary" size="small" @click="handleAddMail">
|
||||
<el-icon><Plus /></el-icon>
|
||||
新增
|
||||
</el-button>
|
||||
@@ -391,7 +403,13 @@
|
||||
<div class="sub-card-header">
|
||||
<h4>{{ row.mailAddress || '未命名' }}</h4>
|
||||
<div class="sub-card-actions">
|
||||
<el-button class="btn-op-item primary" link size="small" @click="handleEditMail(row)">编辑</el-button>
|
||||
<el-button
|
||||
class="btn-op-item primary"
|
||||
link
|
||||
size="small"
|
||||
@click="handleEditMail(row)"
|
||||
>编辑</el-button
|
||||
>
|
||||
<el-button
|
||||
class="btn-op-item"
|
||||
type="danger"
|
||||
@@ -408,7 +426,9 @@
|
||||
<div class="sub-card-fields">
|
||||
<div v-if="row.mailContactName || row.mailContact" class="sub-card-field">
|
||||
<span class="field-label">联系人</span>
|
||||
<span class="field-value">{{ row.mailContactName || row.mailContact }}</span>
|
||||
<span class="field-value">{{
|
||||
row.mailContactName || row.mailContact
|
||||
}}</span>
|
||||
</div>
|
||||
<div v-if="row.mailContactPhone || row.mailPhone" class="sub-card-field">
|
||||
<span class="field-label">联系电话</span>
|
||||
@@ -420,7 +440,6 @@
|
||||
<el-empty v-else description="暂无邮寄地址" :image-size="48" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</el-form>
|
||||
</div>
|
||||
|
||||
@@ -469,7 +488,12 @@
|
||||
<el-icon><Close /></el-icon>
|
||||
取消
|
||||
</el-button>
|
||||
<el-button class="btn-main" type="primary" @click="handleSubmitInvoice" :loading="invoiceSubmitLoading">
|
||||
<el-button
|
||||
class="btn-main"
|
||||
type="primary"
|
||||
@click="handleSubmitInvoice"
|
||||
:loading="invoiceSubmitLoading"
|
||||
>
|
||||
<el-icon><Check /></el-icon>
|
||||
确定
|
||||
</el-button>
|
||||
@@ -503,7 +527,10 @@
|
||||
<el-input v-model="mailFormData.mailContactName" placeholder="请输入邮寄联系人" />
|
||||
</el-form-item>
|
||||
<el-form-item label="邮寄联系电话" prop="mailContactPhone">
|
||||
<el-input v-model="mailFormData.mailContactPhone" placeholder="请输入邮寄联系电话" />
|
||||
<el-input
|
||||
v-model="mailFormData.mailContactPhone"
|
||||
placeholder="请输入邮寄联系电话"
|
||||
/>
|
||||
</el-form-item>
|
||||
</div>
|
||||
</el-form>
|
||||
@@ -512,7 +539,12 @@
|
||||
<el-icon><Close /></el-icon>
|
||||
取消
|
||||
</el-button>
|
||||
<el-button class="btn-main" type="primary" @click="handleSubmitMail" :loading="mailSubmitLoading">
|
||||
<el-button
|
||||
class="btn-main"
|
||||
type="primary"
|
||||
@click="handleSubmitMail"
|
||||
:loading="mailSubmitLoading"
|
||||
>
|
||||
<el-icon><Check /></el-icon>
|
||||
确定
|
||||
</el-button>
|
||||
@@ -544,12 +576,7 @@
|
||||
<div class="section-card" style="border: none; box-shadow: none; padding: 0">
|
||||
<div class="section-title">
|
||||
<div class="section-title-left"><h3>协议列表</h3></div>
|
||||
<el-button
|
||||
class="btn-main"
|
||||
type="primary"
|
||||
size="small"
|
||||
@click="handleAddAgreement"
|
||||
>
|
||||
<el-button class="btn-main" type="primary" size="small" @click="handleAddAgreement">
|
||||
<el-icon><Plus /></el-icon>
|
||||
新增协议
|
||||
</el-button>
|
||||
@@ -568,16 +595,10 @@
|
||||
<!-- 卡片上半部分:协议信息 -->
|
||||
<div class="protocol-card__header">
|
||||
<div class="protocol-card__tags">
|
||||
<el-tag
|
||||
:type="agreementTypeTagType(agr.agreementType)"
|
||||
size="small"
|
||||
>
|
||||
<el-tag :type="agreementTypeTagType(agr.agreementType)" size="small">
|
||||
{{ agreementTypeLabel(agr.agreementType) }}
|
||||
</el-tag>
|
||||
<el-tag
|
||||
:type="agreementStatusTagType(agr.status)"
|
||||
size="small"
|
||||
>
|
||||
<el-tag :type="agreementStatusTagType(agr.status)" size="small">
|
||||
{{ agreementStatusLabel(agr.status) }}
|
||||
</el-tag>
|
||||
</div>
|
||||
@@ -585,20 +606,36 @@
|
||||
<el-button
|
||||
v-if="agr.status === 'dsx'"
|
||||
class="btn-op-item primary"
|
||||
:loading="agreementOperatingId === agr.id && agreementOperatingAction === 'activate'"
|
||||
:loading="
|
||||
agreementOperatingId === agr.id && agreementOperatingAction === 'activate'
|
||||
"
|
||||
:disabled="!!agreementOperatingId"
|
||||
@click="handleActivateAgreement(agr)"
|
||||
>
|
||||
<el-icon v-if="!(agreementOperatingId === agr.id && agreementOperatingAction === 'activate')"><Check /></el-icon>生效
|
||||
<el-icon
|
||||
v-if="
|
||||
!(
|
||||
agreementOperatingId === agr.id && agreementOperatingAction === 'activate'
|
||||
)
|
||||
"
|
||||
><Check /></el-icon
|
||||
>生效
|
||||
</el-button>
|
||||
<el-button
|
||||
v-if="agr.status === 'dsx' || agr.status === 'sxz'"
|
||||
class="btn-op-item"
|
||||
:loading="agreementOperatingId === agr.id && agreementOperatingAction === 'void'"
|
||||
:loading="
|
||||
agreementOperatingId === agr.id && agreementOperatingAction === 'void'
|
||||
"
|
||||
:disabled="!!agreementOperatingId"
|
||||
@click="handleVoidAgreement(agr)"
|
||||
>
|
||||
<el-icon v-if="!(agreementOperatingId === agr.id && agreementOperatingAction === 'void')"><CircleClose /></el-icon>作废
|
||||
<el-icon
|
||||
v-if="
|
||||
!(agreementOperatingId === agr.id && agreementOperatingAction === 'void')
|
||||
"
|
||||
><CircleClose /></el-icon
|
||||
>作废
|
||||
</el-button>
|
||||
<el-button
|
||||
class="btn-op-item"
|
||||
@@ -610,11 +647,18 @@
|
||||
</el-button>
|
||||
<el-button
|
||||
class="btn-op-item"
|
||||
:loading="agreementOperatingId === agr.id && agreementOperatingAction === 'delete'"
|
||||
:loading="
|
||||
agreementOperatingId === agr.id && agreementOperatingAction === 'delete'
|
||||
"
|
||||
:disabled="!!agreementOperatingId"
|
||||
@click="handleDeleteAgreement(agr)"
|
||||
>
|
||||
<el-icon v-if="!(agreementOperatingId === agr.id && agreementOperatingAction === 'delete')"><Delete /></el-icon>删除
|
||||
<el-icon
|
||||
v-if="
|
||||
!(agreementOperatingId === agr.id && agreementOperatingAction === 'delete')
|
||||
"
|
||||
><Delete /></el-icon
|
||||
>删除
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -622,17 +666,16 @@
|
||||
<div v-if="agr.settlementType" class="sub-card-field">
|
||||
<span class="field-label">结算方式</span>
|
||||
<span class="field-value">
|
||||
<el-tag
|
||||
:type="settlementTypeTagType(agr.settlementType)"
|
||||
size="small"
|
||||
>
|
||||
<el-tag :type="settlementTypeTagType(agr.settlementType)" size="small">
|
||||
{{ settlementTypeLabel(agr.settlementType) }}
|
||||
</el-tag>
|
||||
</span>
|
||||
</div>
|
||||
<div v-if="agr.creditLimit != null" class="sub-card-field">
|
||||
<span class="field-label">授信额度(元)</span>
|
||||
<span class="field-value">{{ agr.creditLimit != null ? formatMoneyDisplay(agr.creditLimit) : '-' }}</span>
|
||||
<span class="field-value">{{
|
||||
agr.creditLimit != null ? formatMoneyDisplay(agr.creditLimit) : '-'
|
||||
}}</span>
|
||||
</div>
|
||||
<div v-if="agr.effectiveDate" class="sub-card-field">
|
||||
<span class="field-label">生效日期</span>
|
||||
@@ -650,27 +693,27 @@
|
||||
<div class="detail-section-head">
|
||||
<h3 class="detail-section-title">项目信息</h3>
|
||||
<div class="protocol-card__products-actions">
|
||||
<el-dropdown
|
||||
trigger="click"
|
||||
popper-class="channel-agreement-price-dropdown"
|
||||
@command="(cmd: string) => handleAddPriceCommandFor(cmd, agr)"
|
||||
>
|
||||
<el-button class="btn-main" type="primary" size="small">
|
||||
<el-icon><Plus /></el-icon>
|
||||
添加价格
|
||||
</el-button>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item command="fromPriceSystem"
|
||||
>从价格体系添加</el-dropdown-item
|
||||
>
|
||||
<el-dropdown-item command="fromProjectSystem"
|
||||
>从项目价格体系添加</el-dropdown-item
|
||||
>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
</div>
|
||||
<el-dropdown
|
||||
trigger="click"
|
||||
popper-class="channel-agreement-price-dropdown"
|
||||
@command="(cmd: string) => handleAddPriceCommandFor(cmd, agr)"
|
||||
>
|
||||
<el-button class="btn-main" type="primary" size="small">
|
||||
<el-icon><Plus /></el-icon>
|
||||
添加价格
|
||||
</el-button>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item command="fromPriceSystem"
|
||||
>从价格体系添加</el-dropdown-item
|
||||
>
|
||||
<el-dropdown-item command="fromProjectSystem"
|
||||
>从项目价格体系添加</el-dropdown-item
|
||||
>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
v-if="(agreementPricesMap[agr.id] || []).length > 0"
|
||||
@@ -728,7 +771,11 @@
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row: priceRow }">
|
||||
{{ priceRow.originalPrice != null ? formatMoneyDisplay(priceRow.originalPrice) : '-' }}
|
||||
{{
|
||||
priceRow.originalPrice != null
|
||||
? formatMoneyDisplay(priceRow.originalPrice)
|
||||
: '-'
|
||||
}}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
@@ -777,7 +824,9 @@
|
||||
:disabled="!!deletingAgreementPriceId"
|
||||
@click="handleDeletePriceInline(priceRow, agr)"
|
||||
>
|
||||
<el-icon v-if="deletingAgreementPriceId !== priceRow.id"><Delete /></el-icon>
|
||||
<el-icon v-if="deletingAgreementPriceId !== priceRow.id"
|
||||
><Delete
|
||||
/></el-icon>
|
||||
删除
|
||||
</el-button>
|
||||
</template>
|
||||
@@ -818,10 +867,7 @@
|
||||
>
|
||||
<div class="form-grid-2">
|
||||
<el-form-item label="协议状态" class="form-col-span-2">
|
||||
<el-tag
|
||||
:type="agreementStatusTagType(agreementFormStatus)"
|
||||
size="default"
|
||||
>
|
||||
<el-tag :type="agreementStatusTagType(agreementFormStatus)" size="default">
|
||||
{{ agreementStatusLabel(agreementFormStatus) }}
|
||||
</el-tag>
|
||||
<span v-if="!agreementIsEdit" class="form-label-tip" style="margin-left: 8px">
|
||||
@@ -829,7 +875,11 @@
|
||||
</span>
|
||||
</el-form-item>
|
||||
<el-form-item label="协议类型" prop="agreementType">
|
||||
<el-select v-model="agreementFormData.agreementType" placeholder="请选择协议类型" style="width: 100%">
|
||||
<el-select
|
||||
v-model="agreementFormData.agreementType"
|
||||
placeholder="请选择协议类型"
|
||||
style="width: 100%"
|
||||
>
|
||||
<el-option
|
||||
v-for="(item, key) in agreementType"
|
||||
:key="key"
|
||||
@@ -839,7 +889,11 @@
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="结算方式" prop="settlementType">
|
||||
<el-select v-model="agreementFormData.settlementType" placeholder="请选择结算方式" style="width: 100%">
|
||||
<el-select
|
||||
v-model="agreementFormData.settlementType"
|
||||
placeholder="请选择结算方式"
|
||||
style="width: 100%"
|
||||
>
|
||||
<el-option
|
||||
v-for="(item, key) in settlementType"
|
||||
:key="key"
|
||||
@@ -890,7 +944,12 @@
|
||||
<el-icon><Check /></el-icon>
|
||||
生效
|
||||
</el-button>
|
||||
<el-button class="btn-main" type="primary" @click="handleSubmitAgreement" :loading="agreementSubmitLoading">
|
||||
<el-button
|
||||
class="btn-main"
|
||||
type="primary"
|
||||
@click="handleSubmitAgreement"
|
||||
:loading="agreementSubmitLoading"
|
||||
>
|
||||
<el-icon><Check /></el-icon>
|
||||
确定
|
||||
</el-button>
|
||||
@@ -908,7 +967,10 @@
|
||||
append-to-body
|
||||
:close-on-click-modal="false"
|
||||
>
|
||||
<div v-if="priceSelectorMode === 'project'" class="price-selector-filter price-selector-system-row">
|
||||
<div
|
||||
v-if="priceSelectorMode === 'project'"
|
||||
class="price-selector-filter price-selector-system-row"
|
||||
>
|
||||
<span class="price-selector-system-label">选择项目价格体系</span>
|
||||
<el-select
|
||||
v-model="selectedProjectSystemId"
|
||||
@@ -953,16 +1015,47 @@
|
||||
header-align="center"
|
||||
:selectable="priceSelectorRowSelectable"
|
||||
/>
|
||||
<el-table-column prop="projectName" label="项目名称" min-width="120" align="center" header-align="center" />
|
||||
<el-table-column prop="projectCode" label="项目编码" min-width="100" align="center" header-align="center" />
|
||||
<el-table-column prop="cellTypeName" label="细胞类型" min-width="100" align="center" header-align="center" />
|
||||
<el-table-column prop="specText" label="规格" min-width="100" align="center" header-align="center" />
|
||||
<el-table-column
|
||||
prop="projectName"
|
||||
label="项目名称"
|
||||
min-width="120"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="projectCode"
|
||||
label="项目编码"
|
||||
min-width="100"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="cellTypeName"
|
||||
label="细胞类型"
|
||||
min-width="100"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="specText"
|
||||
label="规格"
|
||||
min-width="100"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column label="单价(元)" min-width="90" align="center" header-align="center">
|
||||
<template #default="{ row }">
|
||||
{{ row.unitPrice != null ? formatMoneyDisplay(row.unitPrice) : '-' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="remark" label="备注" min-width="120" align="center" header-align="center" show-overflow-tooltip />
|
||||
<el-table-column
|
||||
prop="remark"
|
||||
label="备注"
|
||||
min-width="120"
|
||||
align="center"
|
||||
header-align="center"
|
||||
show-overflow-tooltip
|
||||
/>
|
||||
</el-table>
|
||||
<template #footer>
|
||||
<el-button class="btn-ghost" @click="priceSelectorVisible = false">
|
||||
@@ -1023,7 +1116,21 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, computed, onMounted, nextTick } from 'vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { Plus, Close, Check, CircleClose, Search, View, Edit, Delete, Document, VideoPlay, Loading, Download, RefreshLeft } from '@element-plus/icons-vue'
|
||||
import {
|
||||
Plus,
|
||||
Close,
|
||||
Check,
|
||||
CircleClose,
|
||||
Search,
|
||||
View,
|
||||
Edit,
|
||||
Delete,
|
||||
Document,
|
||||
VideoPlay,
|
||||
Loading,
|
||||
Download,
|
||||
RefreshLeft
|
||||
} from '@element-plus/icons-vue'
|
||||
import ChannelViewDialog from './ChannelViewDialog.vue'
|
||||
import { withFullscreenLoading, fullscreenLoadingText } from '@/utils/fullscreen-loading'
|
||||
import { fetchUploadAttachment } from '@/api/business'
|
||||
@@ -1094,7 +1201,13 @@
|
||||
function normalizeMailRow(row: Record<string, any>) {
|
||||
const mailContactName = String(row.mailContact ?? row.mailContactName ?? '').trim()
|
||||
const mailContactPhone = String(row.mailPhone ?? row.mailContactPhone ?? '').trim()
|
||||
return { ...row, mailContactName, mailContactPhone, mailContact: mailContactName, mailPhone: mailContactPhone }
|
||||
return {
|
||||
...row,
|
||||
mailContactName,
|
||||
mailContactPhone,
|
||||
mailContact: mailContactName,
|
||||
mailPhone: mailContactPhone
|
||||
}
|
||||
}
|
||||
|
||||
/** 渠道协议:对齐后端 camelCase 字段,供列表/表单/卡片展示 */
|
||||
@@ -1368,13 +1481,7 @@
|
||||
const list = priceSelectorList.value
|
||||
if (!kw) return list
|
||||
return list.filter((row) => {
|
||||
const text = [
|
||||
row.projectName,
|
||||
row.projectCode,
|
||||
row.cellTypeName,
|
||||
row.specText,
|
||||
row.remark
|
||||
]
|
||||
const text = [row.projectName, row.projectCode, row.cellTypeName, row.specText, row.remark]
|
||||
.map((v) => String(v ?? '').toLowerCase())
|
||||
.join(' ')
|
||||
return text.includes(kw)
|
||||
@@ -1547,7 +1654,9 @@
|
||||
// ---------------------------------------------------------------------------
|
||||
async function handleDelete(row: any) {
|
||||
try {
|
||||
await ElMessageBox.confirm('确定要删除该渠道吗?关联的子表数据也将清除。', '提示', { type: 'warning' })
|
||||
await ElMessageBox.confirm('确定要删除该渠道吗?关联的子表数据也将清除。', '提示', {
|
||||
type: 'warning'
|
||||
})
|
||||
} catch {
|
||||
return
|
||||
}
|
||||
@@ -1561,7 +1670,9 @@
|
||||
async function handleBatchDelete() {
|
||||
const ids = selectedRows.value.map((r) => r.id)
|
||||
if (!ids.length) return
|
||||
await ElMessageBox.confirm(`确定删除选中的 ${ids.length} 条记录吗?`, '提示', { type: 'warning' })
|
||||
await ElMessageBox.confirm(`确定删除选中的 ${ids.length} 条记录吗?`, '提示', {
|
||||
type: 'warning'
|
||||
})
|
||||
await withFullscreenLoading(async () => {
|
||||
for (const id of ids) {
|
||||
await fetchDeleteChannel(id)
|
||||
@@ -1741,9 +1852,7 @@
|
||||
if (editingId.value) {
|
||||
await fetchCreateChannelMailAddress(editingId.value, payload)
|
||||
} else {
|
||||
mailList.value.push(
|
||||
normalizeMailRow({ id: `_local_mail_${Date.now()}`, ...payload })
|
||||
)
|
||||
mailList.value.push(normalizeMailRow({ id: `_local_mail_${Date.now()}`, ...payload }))
|
||||
}
|
||||
}
|
||||
ElMessage.success(mailIsEdit.value ? '更新成功' : '添加成功')
|
||||
@@ -2132,9 +2241,7 @@
|
||||
}
|
||||
|
||||
if (!toAdd.length) {
|
||||
ElMessage.warning(
|
||||
skippedCount > 0 ? '所选项目均已存在,不可重复添加' : '请选择要添加的项目'
|
||||
)
|
||||
ElMessage.warning(skippedCount > 0 ? '所选项目均已存在,不可重复添加' : '请选择要添加的项目')
|
||||
return
|
||||
}
|
||||
|
||||
@@ -2375,7 +2482,9 @@
|
||||
padding: 22px 24px;
|
||||
margin-bottom: 20px;
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.06);
|
||||
transition: transform 0.2s, box-shadow 0.2s;
|
||||
transition:
|
||||
transform 0.2s,
|
||||
box-shadow 0.2s;
|
||||
position: relative;
|
||||
background: #fff;
|
||||
|
||||
|
||||
@@ -23,10 +23,34 @@
|
||||
@selection-change="(rows) => (selectedRows = rows)"
|
||||
>
|
||||
<el-table-column type="selection" min-width="55" align="center" header-align="center" />
|
||||
<el-table-column type="index" label="序号" min-width="68" align="center" header-align="center" />
|
||||
<el-table-column prop="packageName" label="套餐名称" min-width="140" align="center" header-align="center" />
|
||||
<el-table-column prop="packageCode" label="套餐编码" min-width="120" align="center" header-align="center" />
|
||||
<el-table-column prop="itemCount" label="项目数" min-width="80" align="center" header-align="center" />
|
||||
<el-table-column
|
||||
type="index"
|
||||
label="序号"
|
||||
min-width="68"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="packageName"
|
||||
label="套餐名称"
|
||||
min-width="140"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="packageCode"
|
||||
label="套餐编码"
|
||||
min-width="120"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="itemCount"
|
||||
label="项目数"
|
||||
min-width="80"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column label="合计费用(元)" min-width="120" align="center" header-align="center">
|
||||
<template #default="{ row }">{{
|
||||
row.totalFee != null ? `¥${formatMoneyDisplay(row.totalFee)}` : '—'
|
||||
@@ -34,7 +58,9 @@
|
||||
</el-table-column>
|
||||
<el-table-column prop="status" label="状态" min-width="90" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="row.status == '1' ? 'success' : 'info'" size="small">{{ row.status == '1' ? '正常' : '停用' }}</el-tag>
|
||||
<el-tag :type="row.status == '1' ? 'success' : 'info'" size="small">{{
|
||||
row.status == '1' ? '正常' : '停用'
|
||||
}}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
@@ -44,7 +70,13 @@
|
||||
align="center"
|
||||
show-overflow-tooltip
|
||||
/>
|
||||
<el-table-column label="操作" min-width="260" fixed="right" align="center" header-align="center">
|
||||
<el-table-column
|
||||
label="操作"
|
||||
min-width="260"
|
||||
fixed="right"
|
||||
align="center"
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<el-button class="btn-op-item primary" @click="handleEdit(row)"
|
||||
><el-icon><Edit /></el-icon>编辑</el-button
|
||||
@@ -127,12 +159,14 @@
|
||||
<span v-if="row.sourceProjectDeleted" class="form-label-tip">源项目已删除</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="项目费用(元)" min-width="120" align="center" header-align="center">
|
||||
<el-table-column
|
||||
label="项目费用(元)"
|
||||
min-width="120"
|
||||
align="center"
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<AmountInput
|
||||
v-model="row.projectFee"
|
||||
:min="0"
|
||||
/>
|
||||
<AmountInput v-model="row.projectFee" :min="0" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="状态" min-width="90" align="center" header-align="center">
|
||||
@@ -147,7 +181,13 @@
|
||||
<el-input v-model="row.remark" placeholder="可选" clearable />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" min-width="160" fixed="right" align="center" header-align="center">
|
||||
<el-table-column
|
||||
label="操作"
|
||||
min-width="160"
|
||||
fixed="right"
|
||||
align="center"
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ $index }">
|
||||
<el-button class="btn-op-item" @click="detailItems.splice($index, 1)">
|
||||
<el-icon><Delete /></el-icon>删除
|
||||
@@ -211,12 +251,23 @@
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column label="项目费用(元)" min-width="120" align="center" header-align="center">
|
||||
<el-table-column
|
||||
label="项目费用(元)"
|
||||
min-width="120"
|
||||
align="center"
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }">{{
|
||||
row.projectFee != null ? `¥${formatMoneyDisplay(row.projectFee)}` : '—'
|
||||
}}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="status" label="状态" min-width="90" align="center" header-align="center">
|
||||
<el-table-column
|
||||
prop="status"
|
||||
label="状态"
|
||||
min-width="90"
|
||||
align="center"
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="row.status == '1' ? 'success' : 'info'" size="small">{{
|
||||
row.status == '1' ? '正常' : '停用'
|
||||
@@ -413,7 +464,9 @@
|
||||
|
||||
async function handleDelete(row: any) {
|
||||
try {
|
||||
await ElMessageBox.confirm(`确定删除套餐「${row.packageName}」?`, '提示', { type: 'warning' })
|
||||
await ElMessageBox.confirm(`确定删除套餐「${row.packageName}」?`, '提示', {
|
||||
type: 'warning'
|
||||
})
|
||||
} catch {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -68,21 +68,67 @@
|
||||
header-align="center"
|
||||
:index="(i) => (pagination.page - 1) * pagination.pageSize + i + 1"
|
||||
/>
|
||||
<el-table-column prop="institutionName" label="机构名称" min-width="160" align="center" header-align="center" />
|
||||
<el-table-column prop="institutionAddress" label="机构地址" min-width="180" align="center" header-align="center" />
|
||||
<el-table-column prop="contactName" label="联系人" min-width="100" align="center" header-align="center" />
|
||||
<el-table-column prop="contactPhone" label="联系电话" min-width="120" align="center" header-align="center" />
|
||||
<el-table-column prop="status" label="状态" min-width="90" align="center" header-align="center">
|
||||
<el-table-column
|
||||
prop="institutionName"
|
||||
label="机构名称"
|
||||
min-width="160"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="institutionAddress"
|
||||
label="机构地址"
|
||||
min-width="180"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="contactName"
|
||||
label="联系人"
|
||||
min-width="100"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="contactPhone"
|
||||
label="联系电话"
|
||||
min-width="120"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="status"
|
||||
label="状态"
|
||||
min-width="90"
|
||||
align="center"
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="row.status == '1' ? 'success' : 'info'" size="small">{{ row.status == '1' ? '正常' : '停用' }}</el-tag>
|
||||
<el-tag :type="row.status == '1' ? 'success' : 'info'" size="small">{{
|
||||
row.status == '1' ? '正常' : '停用'
|
||||
}}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" min-width="400" fixed="right" align="center" header-align="center">
|
||||
<el-table-column
|
||||
label="操作"
|
||||
min-width="400"
|
||||
fixed="right"
|
||||
align="center"
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<el-button class="btn-op-item" @click="handleView(row)"><el-icon><View /></el-icon>查看</el-button>
|
||||
<el-button class="btn-op-item primary" @click="openItemDialog(row)"><el-icon><Setting /></el-icon>项目价格配置</el-button>
|
||||
<el-button class="btn-op-item" @click="handleEdit(row)"><el-icon><Edit /></el-icon>编辑</el-button>
|
||||
<el-button class="btn-op-item" @click="handleDelete(row)"><el-icon><Delete /></el-icon>删除</el-button>
|
||||
<el-button class="btn-op-item" @click="handleView(row)"
|
||||
><el-icon><View /></el-icon>查看</el-button
|
||||
>
|
||||
<el-button class="btn-op-item primary" @click="openItemDialog(row)"
|
||||
><el-icon><Setting /></el-icon>项目价格配置</el-button
|
||||
>
|
||||
<el-button class="btn-op-item" @click="handleEdit(row)"
|
||||
><el-icon><Edit /></el-icon>编辑</el-button
|
||||
>
|
||||
<el-button class="btn-op-item" @click="handleDelete(row)"
|
||||
><el-icon><Delete /></el-icon>删除</el-button
|
||||
>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
@@ -109,7 +155,13 @@
|
||||
destroy-on-close
|
||||
:close-on-click-modal="false"
|
||||
>
|
||||
<el-form ref="companyFormRef" :model="companyForm" :rules="companyFormRules" label-position="top" class="biz-dialog-form">
|
||||
<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="institutionName">
|
||||
<el-input v-model="companyForm.institutionName" placeholder="请输入机构名称" />
|
||||
@@ -155,7 +207,12 @@
|
||||
<el-button class="btn-ghost" @click="companyDialogVisible = false">
|
||||
<el-icon><Close /></el-icon>取消
|
||||
</el-button>
|
||||
<el-button class="btn-main" type="primary" :loading="companySubmitLoading" @click="submitCompany">
|
||||
<el-button
|
||||
class="btn-main"
|
||||
type="primary"
|
||||
:loading="companySubmitLoading"
|
||||
@click="submitCompany"
|
||||
>
|
||||
<el-icon><Check /></el-icon>确定
|
||||
</el-button>
|
||||
</template>
|
||||
@@ -177,57 +234,109 @@
|
||||
</div>
|
||||
<el-tabs v-model="itemConfigTab" class="item-config-tabs">
|
||||
<el-tab-pane label="项目列表" name="projects">
|
||||
<div class="list-toolbar item-toolbar">
|
||||
<div class="list-toolbar-row">
|
||||
<div class="list-toolbar-selected">
|
||||
已选 <span class="sel-num">{{ itemSelected.length }}</span> 项
|
||||
<div class="list-toolbar item-toolbar">
|
||||
<div class="list-toolbar-row">
|
||||
<div class="list-toolbar-selected">
|
||||
已选 <span class="sel-num">{{ itemSelected.length }}</span> 项
|
||||
</div>
|
||||
<el-button class="btn-main" type="primary" @click="handleAddItem">
|
||||
<el-icon><Plus /></el-icon>新增项目
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="list-body list-wrap item-table-wrap">
|
||||
<el-table
|
||||
class="list-table"
|
||||
:data="itemTable"
|
||||
v-loading="itemLoading"
|
||||
stripe
|
||||
row-key="id"
|
||||
max-height="460"
|
||||
@selection-change="onItemSelectionChange"
|
||||
>
|
||||
<el-table-column
|
||||
type="selection"
|
||||
min-width="55"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
type="index"
|
||||
label="序号"
|
||||
min-width="68"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="projectName"
|
||||
label="项目名称"
|
||||
min-width="160"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="projectFee"
|
||||
label="项目费用(元)"
|
||||
min-width="120"
|
||||
align="center"
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }">{{
|
||||
row.projectFee != null ? `¥${formatMoneyDisplay(row.projectFee)}` : '—'
|
||||
}}</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="status"
|
||||
label="状态"
|
||||
min-width="90"
|
||||
align="center"
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="row.status == '1' ? 'success' : 'info'" size="small">{{
|
||||
row.status == '1' ? '正常' : '停用'
|
||||
}}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="remark"
|
||||
label="备注"
|
||||
min-width="140"
|
||||
align="center"
|
||||
header-align="center"
|
||||
show-overflow-tooltip
|
||||
/>
|
||||
<el-table-column
|
||||
label="操作"
|
||||
min-width="260"
|
||||
fixed="right"
|
||||
align="center"
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<el-button class="btn-op-item primary" @click="handleEditItem(row)"
|
||||
><el-icon><Edit /></el-icon>编辑</el-button
|
||||
>
|
||||
<el-button class="btn-op-item" @click="handleDeleteItem(row)"
|
||||
><el-icon><Delete /></el-icon>删除</el-button
|
||||
>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<div class="pagination list-pager-bar">
|
||||
<el-pagination
|
||||
:current-page="itemPagination.page"
|
||||
:page-size="itemPagination.pageSize"
|
||||
:total="itemPagination.total"
|
||||
:page-sizes="[10, 20, 50]"
|
||||
layout="total, sizes, prev, pager, next"
|
||||
@size-change="
|
||||
(v) => ((itemPagination.page = 1), (itemPagination.pageSize = v), loadItems())
|
||||
"
|
||||
@current-change="(v) => ((itemPagination.page = v), loadItems())"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<el-button class="btn-main" type="primary" @click="handleAddItem">
|
||||
<el-icon><Plus /></el-icon>新增项目
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="list-body list-wrap item-table-wrap">
|
||||
<el-table
|
||||
class="list-table"
|
||||
:data="itemTable"
|
||||
v-loading="itemLoading"
|
||||
stripe
|
||||
row-key="id"
|
||||
max-height="460"
|
||||
@selection-change="onItemSelectionChange"
|
||||
>
|
||||
<el-table-column type="selection" min-width="55" align="center" header-align="center" />
|
||||
<el-table-column type="index" label="序号" min-width="68" align="center" header-align="center" />
|
||||
<el-table-column prop="projectName" label="项目名称" min-width="160" align="center" header-align="center" />
|
||||
<el-table-column prop="projectFee" label="项目费用(元)" min-width="120" align="center" header-align="center">
|
||||
<template #default="{ row }">{{ row.projectFee != null ? `¥${formatMoneyDisplay(row.projectFee)}` : '—' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="status" label="状态" min-width="90" align="center" header-align="center">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="row.status == '1' ? 'success' : 'info'" size="small">{{ row.status == '1' ? '正常' : '停用' }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="remark" label="备注" min-width="140" align="center" header-align="center" show-overflow-tooltip />
|
||||
<el-table-column label="操作" min-width="260" fixed="right" align="center" header-align="center">
|
||||
<template #default="{ row }">
|
||||
<el-button class="btn-op-item primary" @click="handleEditItem(row)"><el-icon><Edit /></el-icon>编辑</el-button>
|
||||
<el-button class="btn-op-item" @click="handleDeleteItem(row)"><el-icon><Delete /></el-icon>删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<div class="pagination list-pager-bar">
|
||||
<el-pagination
|
||||
:current-page="itemPagination.page"
|
||||
:page-size="itemPagination.pageSize"
|
||||
:total="itemPagination.total"
|
||||
:page-sizes="[10, 20, 50]"
|
||||
layout="total, sizes, prev, pager, next"
|
||||
@size-change="(v) => ((itemPagination.page = 1), (itemPagination.pageSize = v), loadItems())"
|
||||
@current-change="(v) => ((itemPagination.page = v), loadItems())"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="套餐管理" name="packages">
|
||||
<CheckupPackagePanel
|
||||
@@ -249,7 +358,13 @@
|
||||
destroy-on-close
|
||||
:close-on-click-modal="false"
|
||||
>
|
||||
<el-form ref="itemFormRef" :model="itemForm" :rules="itemFormRules" label-position="top" class="biz-dialog-form">
|
||||
<el-form
|
||||
ref="itemFormRef"
|
||||
:model="itemForm"
|
||||
:rules="itemFormRules"
|
||||
label-position="top"
|
||||
class="biz-dialog-form"
|
||||
>
|
||||
<div class="form-grid-2">
|
||||
<el-form-item label="项目名称" prop="projectName">
|
||||
<el-input v-model="itemForm.projectName" />
|
||||
@@ -272,7 +387,12 @@
|
||||
<el-button class="btn-ghost" @click="itemFormVisible = false">
|
||||
<el-icon><Close /></el-icon>取消
|
||||
</el-button>
|
||||
<el-button class="btn-main" type="primary" :loading="itemSubmitLoading" @click="submitItem">
|
||||
<el-button
|
||||
class="btn-main"
|
||||
type="primary"
|
||||
:loading="itemSubmitLoading"
|
||||
@click="submitItem"
|
||||
>
|
||||
<el-icon><Check /></el-icon>确定
|
||||
</el-button>
|
||||
</template>
|
||||
@@ -547,7 +667,7 @@
|
||||
invoiceTitle: companyForm.invoiceTitle?.trim() || undefined,
|
||||
taxNo: companyForm.taxNo?.trim() || undefined,
|
||||
bankName: companyForm.bankName?.trim() || undefined,
|
||||
bankAccount: companyForm.bankAccount?.trim() || undefined,
|
||||
bankAccount: companyForm.bankAccount?.trim() || undefined
|
||||
}
|
||||
await fetchCreateCheckupInstitutionInvoice((newInst as any).id, invoicePayload)
|
||||
}
|
||||
@@ -562,9 +682,13 @@
|
||||
|
||||
async function handleDelete(row: any) {
|
||||
try {
|
||||
await ElMessageBox.confirm(`确定删除「${row.institutionName}」?其下属项目价格将一并删除。`, '提示', {
|
||||
type: 'warning'
|
||||
})
|
||||
await ElMessageBox.confirm(
|
||||
`确定删除「${row.institutionName}」?其下属项目价格将一并删除。`,
|
||||
'提示',
|
||||
{
|
||||
type: 'warning'
|
||||
}
|
||||
)
|
||||
} catch {
|
||||
return
|
||||
}
|
||||
@@ -588,7 +712,11 @@
|
||||
{ label: '机构地址', value: row.institutionAddress, span: true },
|
||||
{ label: '联系人', value: row.contactName },
|
||||
{ label: '联系电话', value: row.contactPhone },
|
||||
{ label: '状态', value: row.status == '1' ? '正常' : '停用', tagType: row.status == '1' ? 'success' : 'info' },
|
||||
{
|
||||
label: '状态',
|
||||
value: row.status == '1' ? '正常' : '停用',
|
||||
tagType: row.status == '1' ? 'success' : 'info'
|
||||
},
|
||||
{ label: '备注', value: row.remark, span: true }
|
||||
]
|
||||
}
|
||||
|
||||
@@ -15,19 +15,59 @@
|
||||
<div class="section-card" style="margin-bottom: 16px">
|
||||
<div class="section-title"><h3>基本信息</h3></div>
|
||||
<div class="form-grid form-grid-2">
|
||||
<div class="view-field"><span class="view-label">申请单号</span><span class="view-value">{{ checkupData.applicationNo || '—' }}</span></div>
|
||||
<div class="view-field"><span class="view-label">患者姓名</span><span class="view-value">{{ checkupData.patientName || '—' }}</span></div>
|
||||
<div class="view-field"><span class="view-label">性别</span><span class="view-value">{{ sexLabel(checkupData.sex) || '—' }}</span></div>
|
||||
<div class="view-field"><span class="view-label">年龄</span><span class="view-value">{{ checkupData.age ?? '—' }}</span></div>
|
||||
<div class="view-field"><span class="view-label">渠道名称</span><span class="view-value">{{ checkupData.channelName || '—' }}</span></div>
|
||||
<div class="view-field"><span class="view-label">体检机构</span><span class="view-value">{{ checkupData.institutionName || '—' }}</span></div>
|
||||
<div class="view-field"><span class="view-label">预约时间</span><span class="view-value">{{ checkupData.reserveTime || '—' }}</span></div>
|
||||
<div class="view-field"><span class="view-label">预约费用</span><span class="view-value">{{ checkupData.reserveFee != null ? '¥' + checkupData.reserveFee.toFixed(2) : '—' }}</span></div>
|
||||
<div class="view-field"><span class="view-label">实际体检时间</span><span class="view-value">{{ checkupData.actualCheckupTime || '—' }}</span></div>
|
||||
<div class="view-field"><span class="view-label">实际费用</span><span class="view-value">{{ checkupData.actualCheckupFee != null ? '¥' + checkupData.actualCheckupFee.toFixed(2) : '—' }}</span></div>
|
||||
<div class="view-field"
|
||||
><span class="view-label">申请单号</span
|
||||
><span class="view-value">{{ checkupData.applicationNo || '—' }}</span></div
|
||||
>
|
||||
<div class="view-field"
|
||||
><span class="view-label">患者姓名</span
|
||||
><span class="view-value">{{ checkupData.patientName || '—' }}</span></div
|
||||
>
|
||||
<div class="view-field"
|
||||
><span class="view-label">性别</span
|
||||
><span class="view-value">{{ sexLabel(checkupData.sex) || '—' }}</span></div
|
||||
>
|
||||
<div class="view-field"
|
||||
><span class="view-label">年龄</span
|
||||
><span class="view-value">{{ checkupData.age ?? '—' }}</span></div
|
||||
>
|
||||
<div class="view-field"
|
||||
><span class="view-label">渠道名称</span
|
||||
><span class="view-value">{{ checkupData.channelName || '—' }}</span></div
|
||||
>
|
||||
<div class="view-field"
|
||||
><span class="view-label">体检机构</span
|
||||
><span class="view-value">{{ checkupData.institutionName || '—' }}</span></div
|
||||
>
|
||||
<div class="view-field"
|
||||
><span class="view-label">预约时间</span
|
||||
><span class="view-value">{{ checkupData.reserveTime || '—' }}</span></div
|
||||
>
|
||||
<div class="view-field"
|
||||
><span class="view-label">预约费用</span
|
||||
><span class="view-value">{{
|
||||
checkupData.reserveFee != null ? '¥' + checkupData.reserveFee.toFixed(2) : '—'
|
||||
}}</span></div
|
||||
>
|
||||
<div class="view-field"
|
||||
><span class="view-label">实际体检时间</span
|
||||
><span class="view-value">{{ checkupData.actualCheckupTime || '—' }}</span></div
|
||||
>
|
||||
<div class="view-field"
|
||||
><span class="view-label">实际费用</span
|
||||
><span class="view-value">{{
|
||||
checkupData.actualCheckupFee != null
|
||||
? '¥' + checkupData.actualCheckupFee.toFixed(2)
|
||||
: '—'
|
||||
}}</span></div
|
||||
>
|
||||
<div class="view-field">
|
||||
<span class="view-label">体检状态</span>
|
||||
<span class="view-value"><el-tag :type="checkupStatusTagType(checkupData.checkupStatus)" size="small">{{ checkupStatusLabel(checkupData.checkupStatus) }}</el-tag></span>
|
||||
<span class="view-value"
|
||||
><el-tag :type="checkupStatusTagType(checkupData.checkupStatus)" size="small">{{
|
||||
checkupStatusLabel(checkupData.checkupStatus)
|
||||
}}</el-tag></span
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -39,7 +79,9 @@
|
||||
<el-table-column type="index" label="序号" min-width="60" align="center" />
|
||||
<el-table-column prop="projectName" label="项目名称" min-width="200" />
|
||||
<el-table-column prop="projectFee" label="项目费用(元)" min-width="120" align="center">
|
||||
<template #default="{ row }">{{ row.projectFee != null ? row.projectFee.toFixed(2) : '—' }}</template>
|
||||
<template #default="{ row }">{{
|
||||
row.projectFee != null ? row.projectFee.toFixed(2) : '—'
|
||||
}}</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<div style="margin-top: 8px; font-size: 13px; color: #334155">
|
||||
@@ -59,23 +101,40 @@
|
||||
<el-table-column type="index" label="序号" min-width="60" align="center" />
|
||||
<el-table-column prop="projectName" label="项目名称" min-width="200">
|
||||
<template #default="{ row }">
|
||||
<el-input v-if="row._editing" v-model="row.projectName" size="small" placeholder="请输入项目名称" />
|
||||
<el-input
|
||||
v-if="row._editing"
|
||||
v-model="row.projectName"
|
||||
size="small"
|
||||
placeholder="请输入项目名称"
|
||||
/>
|
||||
<span v-else>{{ row.projectName }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="projectFee" label="项目费用(元)" min-width="140" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-input-number v-if="row._editing" v-model="row.projectFee" size="small" :min="0" :precision="2" controls-position="right" style="width: 120px" />
|
||||
<el-input-number
|
||||
v-if="row._editing"
|
||||
v-model="row.projectFee"
|
||||
size="small"
|
||||
:min="0"
|
||||
:precision="2"
|
||||
controls-position="right"
|
||||
style="width: 120px"
|
||||
/>
|
||||
<span v-else>{{ row.projectFee != null ? row.projectFee.toFixed(2) : '—' }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" min-width="140" align="center">
|
||||
<template #default="{ row, $index }">
|
||||
<template v-if="row._editing">
|
||||
<el-button link type="primary" @click="saveActualItem(row, $index)"><el-icon><Check /></el-icon>保存</el-button>
|
||||
<el-button link type="primary" @click="saveActualItem(row, $index)"
|
||||
><el-icon><Check /></el-icon>保存</el-button
|
||||
>
|
||||
</template>
|
||||
<template v-else>
|
||||
<el-button link type="danger" @click="deleteActualItem(row, $index)"><el-icon><Delete /></el-icon>删除</el-button>
|
||||
<el-button link type="danger" @click="deleteActualItem(row, $index)"
|
||||
><el-icon><Delete /></el-icon>删除</el-button
|
||||
>
|
||||
</template>
|
||||
</template>
|
||||
</el-table-column>
|
||||
@@ -124,8 +183,12 @@
|
||||
<div class="attachment-footer">
|
||||
<span class="attachment-name" :title="file.fileName">{{ file.fileName }}</span>
|
||||
<div class="attachment-actions">
|
||||
<el-icon class="action-btn" @click.stop="downloadReport(file)"><Download /></el-icon>
|
||||
<el-icon class="action-btn action-btn--danger" @click.stop="removeReport(idx)"><Delete /></el-icon>
|
||||
<el-icon class="action-btn" @click.stop="downloadReport(file)"
|
||||
><Download
|
||||
/></el-icon>
|
||||
<el-icon class="action-btn action-btn--danger" @click.stop="removeReport(idx)"
|
||||
><Delete
|
||||
/></el-icon>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -163,7 +226,14 @@
|
||||
import { ref, computed, watch, nextTick } from 'vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import {
|
||||
Plus, Close, Check, Delete, Download, Document, Loading, CircleCheck
|
||||
Plus,
|
||||
Close,
|
||||
Check,
|
||||
Delete,
|
||||
Download,
|
||||
Document,
|
||||
Loading,
|
||||
CircleCheck
|
||||
} from '@element-plus/icons-vue'
|
||||
import {
|
||||
fetchPilotCheckupDetail,
|
||||
@@ -201,13 +271,17 @@
|
||||
const minioBaseUrl = (import.meta.env.VITE_MINIO_BASE_URL || '').replace(/\/$/, '')
|
||||
function fullFileUrl(suffix: string) {
|
||||
if (!suffix) return ''
|
||||
if (suffix.startsWith('http://') || suffix.startsWith('https://') || suffix.startsWith('/')) return suffix
|
||||
if (suffix.startsWith('http://') || suffix.startsWith('https://') || suffix.startsWith('/'))
|
||||
return suffix
|
||||
return `${minioBaseUrl}/${suffix}`
|
||||
}
|
||||
const isImageFile = (file: any) => {
|
||||
const url = (file.fileUrl || '').toLowerCase()
|
||||
const name = (file.fileName || '').toLowerCase()
|
||||
return /\.(jpg|jpeg|png|gif|bmp|webp|svg)(\?|$)/.test(url) || /\.(jpg|jpeg|png|gif|bmp|webp|svg)$/.test(name)
|
||||
return (
|
||||
/\.(jpg|jpeg|png|gif|bmp|webp|svg)(\?|$)/.test(url) ||
|
||||
/\.(jpg|jpeg|png|gif|bmp|webp|svg)$/.test(name)
|
||||
)
|
||||
}
|
||||
|
||||
// ── 加载数据 ──────────────────────────
|
||||
@@ -227,7 +301,9 @@
|
||||
const reserveData = reserveRes?.data ?? reserveRes
|
||||
reserveItems.value = Array.isArray(reserveData) ? reserveData : (reserveData?.list ?? [])
|
||||
const actualData = actualRes?.data ?? actualRes
|
||||
actualItems.value = (Array.isArray(actualData) ? actualData : (actualData?.list ?? [])).map((i: any) => ({ ...i, _editing: false }))
|
||||
actualItems.value = (Array.isArray(actualData) ? actualData : (actualData?.list ?? [])).map(
|
||||
(i: any) => ({ ...i, _editing: false })
|
||||
)
|
||||
|
||||
// 加载主信息 — 通过 checkup id 获取
|
||||
// fetchPilotCheckupDetail 接受 application_id 参数
|
||||
@@ -266,7 +342,10 @@
|
||||
items
|
||||
})
|
||||
const data = res?.data ?? res
|
||||
actualItems.value = (Array.isArray(data) ? data : (data?.list ?? [])).map((i: any) => ({ ...i, _editing: false }))
|
||||
actualItems.value = (Array.isArray(data) ? data : (data?.list ?? [])).map((i: any) => ({
|
||||
...i,
|
||||
_editing: false
|
||||
}))
|
||||
ElMessage.success('保存成功')
|
||||
} catch (e: any) {
|
||||
ElMessage.error(e?.message || '保存失败')
|
||||
@@ -295,7 +374,11 @@
|
||||
if (!file) return
|
||||
const tempId = `temp_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`
|
||||
reportFiles.value.push({
|
||||
id: tempId, fileName: file.name, fileUrl: '', fileSize: file.size, _uploading: true
|
||||
id: tempId,
|
||||
fileName: file.name,
|
||||
fileUrl: '',
|
||||
fileSize: file.size,
|
||||
_uploading: true
|
||||
})
|
||||
const idx = reportFiles.value.length - 1
|
||||
try {
|
||||
@@ -337,7 +420,9 @@
|
||||
ElMessage.success('标记成功')
|
||||
emit('success')
|
||||
emit('update:modelValue', false)
|
||||
} catch { /* cancelled */ }
|
||||
} catch {
|
||||
/* cancelled */
|
||||
}
|
||||
}
|
||||
|
||||
// ── 保存 ──────────────────────────────
|
||||
@@ -345,7 +430,10 @@
|
||||
try {
|
||||
await savePilotCheckup({
|
||||
application_id: checkupData.value.pilotApplicationId,
|
||||
report_attachments: reportFiles.value.map((f) => f.fileUrl).filter(Boolean).join(','),
|
||||
report_attachments: reportFiles.value
|
||||
.map((f) => f.fileUrl)
|
||||
.filter(Boolean)
|
||||
.join(','),
|
||||
checkup_remark: checkupRemark.value
|
||||
})
|
||||
ElMessage.success('保存成功')
|
||||
@@ -367,74 +455,163 @@
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.view-field {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
gap: 8px;
|
||||
min-height: 32px;
|
||||
}
|
||||
.view-label {
|
||||
font-size: 13px;
|
||||
color: #94a3b8;
|
||||
white-space: nowrap;
|
||||
flex-shrink: 0;
|
||||
&::after {
|
||||
content: ':';
|
||||
.view-field {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
gap: 8px;
|
||||
min-height: 32px;
|
||||
}
|
||||
.view-label {
|
||||
font-size: 13px;
|
||||
color: #94a3b8;
|
||||
white-space: nowrap;
|
||||
flex-shrink: 0;
|
||||
&::after {
|
||||
content: ':';
|
||||
}
|
||||
}
|
||||
.view-value {
|
||||
font-size: 13px;
|
||||
color: #334155;
|
||||
word-break: break-all;
|
||||
}
|
||||
}
|
||||
.view-value {
|
||||
font-size: 13px;
|
||||
color: #334155;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.attachment-upload { width: 100%; }
|
||||
.attachment-list { display: flex; flex-wrap: wrap; gap: 12px; }
|
||||
.upload-card {
|
||||
width: 140px; height: 96px;
|
||||
border: 1px dashed #a7f3d0; border-radius: 8px;
|
||||
background: #f0fdf4;
|
||||
display: flex; flex-direction: column; align-items: center; justify-content: center;
|
||||
gap: 6px; cursor: pointer;
|
||||
transition: border-color 0.15s, background 0.15s;
|
||||
&:hover { border-color: #10b981; border-style: solid; background: #ecfdf5; }
|
||||
}
|
||||
.upload-card-text { font-size: 12px; color: #10b981; font-weight: 500; line-height: 1; }
|
||||
.upload-hint { display: block; font-size: 11px; color: #94a3b8; line-height: 1; margin-top: 8px; }
|
||||
.attachment-item {
|
||||
width: 140px; display: flex; flex-direction: column;
|
||||
border: 1px solid #e2e8f0; border-radius: 8px; background: #fff;
|
||||
overflow: hidden; transition: border-color 0.15s, box-shadow 0.15s;
|
||||
&:hover { border-color: #10b981; box-shadow: 0 2px 8px rgba(16, 185, 129, 0.1); }
|
||||
&.is-uploading { opacity: 0.6; pointer-events: none; }
|
||||
}
|
||||
.attachment-preview {
|
||||
width: 100%; height: 96px; flex-shrink: 0;
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
background: #f8fafc; overflow: hidden;
|
||||
img { width: 100%; height: 100%; object-fit: cover; display: block; }
|
||||
}
|
||||
.attachment-preview__icon {
|
||||
width: 48px; height: 48px; border-radius: 10px;
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
&--doc { background: #e0f2fe; color: #0284c7; }
|
||||
}
|
||||
.attachment-footer { display: flex; flex-direction: column; flex: 1; padding: 6px 8px; }
|
||||
.attachment-name {
|
||||
display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
|
||||
overflow: hidden; text-overflow: ellipsis;
|
||||
font-size: 11px; color: #334155; line-height: 1.4; word-break: break-all;
|
||||
}
|
||||
.attachment-actions {
|
||||
display: flex; justify-content: center; align-items: center;
|
||||
gap: 8px; margin-top: 4px; flex-shrink: 0;
|
||||
}
|
||||
.action-btn {
|
||||
width: 22px; height: 22px; border-radius: 4px;
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
font-size: 12px; color: #64748b; cursor: pointer; background: #f1f5f9;
|
||||
transition: background 0.15s, color 0.15s;
|
||||
&:hover { background: #10b981; color: #fff; }
|
||||
&--danger:hover { background: #ef4444; color: #fff; }
|
||||
}
|
||||
.attachment-upload {
|
||||
width: 100%;
|
||||
}
|
||||
.attachment-list {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 12px;
|
||||
}
|
||||
.upload-card {
|
||||
width: 140px;
|
||||
height: 96px;
|
||||
border: 1px dashed #a7f3d0;
|
||||
border-radius: 8px;
|
||||
background: #f0fdf4;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 6px;
|
||||
cursor: pointer;
|
||||
transition:
|
||||
border-color 0.15s,
|
||||
background 0.15s;
|
||||
&:hover {
|
||||
border-color: #10b981;
|
||||
border-style: solid;
|
||||
background: #ecfdf5;
|
||||
}
|
||||
}
|
||||
.upload-card-text {
|
||||
font-size: 12px;
|
||||
color: #10b981;
|
||||
font-weight: 500;
|
||||
line-height: 1;
|
||||
}
|
||||
.upload-hint {
|
||||
display: block;
|
||||
font-size: 11px;
|
||||
color: #94a3b8;
|
||||
line-height: 1;
|
||||
margin-top: 8px;
|
||||
}
|
||||
.attachment-item {
|
||||
width: 140px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
border: 1px solid #e2e8f0;
|
||||
border-radius: 8px;
|
||||
background: #fff;
|
||||
overflow: hidden;
|
||||
transition:
|
||||
border-color 0.15s,
|
||||
box-shadow 0.15s;
|
||||
&:hover {
|
||||
border-color: #10b981;
|
||||
box-shadow: 0 2px 8px rgba(16, 185, 129, 0.1);
|
||||
}
|
||||
&.is-uploading {
|
||||
opacity: 0.6;
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
.attachment-preview {
|
||||
width: 100%;
|
||||
height: 96px;
|
||||
flex-shrink: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: #f8fafc;
|
||||
overflow: hidden;
|
||||
img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
.attachment-preview__icon {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
border-radius: 10px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
&--doc {
|
||||
background: #e0f2fe;
|
||||
color: #0284c7;
|
||||
}
|
||||
}
|
||||
.attachment-footer {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1;
|
||||
padding: 6px 8px;
|
||||
}
|
||||
.attachment-name {
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
font-size: 11px;
|
||||
color: #334155;
|
||||
line-height: 1.4;
|
||||
word-break: break-all;
|
||||
}
|
||||
.attachment-actions {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
margin-top: 4px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.action-btn {
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
border-radius: 4px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 12px;
|
||||
color: #64748b;
|
||||
cursor: pointer;
|
||||
background: #f1f5f9;
|
||||
transition:
|
||||
background 0.15s,
|
||||
color 0.15s;
|
||||
&:hover {
|
||||
background: #10b981;
|
||||
color: #fff;
|
||||
}
|
||||
&--danger:hover {
|
||||
background: #ef4444;
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -115,9 +115,26 @@
|
||||
header-align="center"
|
||||
:selectable="(row) => !isProjectAlreadyInReserve(row)"
|
||||
/>
|
||||
<el-table-column type="index" label="序号" min-width="68" align="center" header-align="center" />
|
||||
<el-table-column prop="projectName" label="项目名称" min-width="180" align="center" header-align="center" />
|
||||
<el-table-column label="项目费用(元)" min-width="120" align="center" header-align="center">
|
||||
<el-table-column
|
||||
type="index"
|
||||
label="序号"
|
||||
min-width="68"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="projectName"
|
||||
label="项目名称"
|
||||
min-width="180"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
label="项目费用(元)"
|
||||
min-width="120"
|
||||
align="center"
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }">{{
|
||||
row.projectFee != null ? formatMoneyDisplay(row.projectFee) : '—'
|
||||
}}</template>
|
||||
@@ -184,13 +201,42 @@
|
||||
@selection-change="(rows) => (packageSelectorSelected = rows)"
|
||||
>
|
||||
<el-table-column type="selection" min-width="55" align="center" header-align="center" />
|
||||
<el-table-column type="index" label="序号" min-width="68" align="center" header-align="center" />
|
||||
<el-table-column prop="packageName" label="套餐名称" min-width="140" align="center" header-align="center" />
|
||||
<el-table-column prop="packageCode" label="套餐编码" min-width="120" align="center" header-align="center" />
|
||||
<el-table-column prop="itemCount" label="项目数" min-width="80" align="center" header-align="center">
|
||||
<el-table-column
|
||||
type="index"
|
||||
label="序号"
|
||||
min-width="68"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="packageName"
|
||||
label="套餐名称"
|
||||
min-width="140"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="packageCode"
|
||||
label="套餐编码"
|
||||
min-width="120"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="itemCount"
|
||||
label="项目数"
|
||||
min-width="80"
|
||||
align="center"
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }">{{ row.itemCount ?? '—' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="合计费用(元)" min-width="120" align="center" header-align="center">
|
||||
<el-table-column
|
||||
label="合计费用(元)"
|
||||
min-width="120"
|
||||
align="center"
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }">{{
|
||||
row.totalFee != null ? formatMoneyDisplay(row.totalFee) : '—'
|
||||
}}</template>
|
||||
@@ -210,7 +256,13 @@
|
||||
header-align="center"
|
||||
show-overflow-tooltip
|
||||
/>
|
||||
<el-table-column label="操作" min-width="100" fixed="right" align="center" header-align="center">
|
||||
<el-table-column
|
||||
label="操作"
|
||||
min-width="100"
|
||||
fixed="right"
|
||||
align="center"
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<el-button class="btn-op-item" @click="handlePackageView(row)">
|
||||
<el-icon><View /></el-icon>查看
|
||||
@@ -252,8 +304,20 @@
|
||||
stripe
|
||||
max-height="420"
|
||||
>
|
||||
<el-table-column type="index" label="序号" min-width="68" align="center" header-align="center" />
|
||||
<el-table-column prop="projectName" label="项目名称" min-width="180" align="center" header-align="center" />
|
||||
<el-table-column
|
||||
type="index"
|
||||
label="序号"
|
||||
min-width="68"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="projectName"
|
||||
label="项目名称"
|
||||
min-width="180"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column label="项目费用(元)" min-width="120" align="center" header-align="center">
|
||||
<template #default="{ row }">{{
|
||||
row.projectFee != null ? formatMoneyDisplay(row.projectFee) : '—'
|
||||
@@ -404,9 +468,7 @@
|
||||
const filteredProjectOptions = computed(() => {
|
||||
const kw = projectSelectorKeyword.value.trim().toLowerCase()
|
||||
if (!kw) return projectOptions.value
|
||||
return projectOptions.value.filter((row) =>
|
||||
(row.projectName || '').toLowerCase().includes(kw)
|
||||
)
|
||||
return projectOptions.value.filter((row) => (row.projectName || '').toLowerCase().includes(kw))
|
||||
})
|
||||
|
||||
const handleOpenProjectSelector = async () => {
|
||||
|
||||
@@ -46,7 +46,13 @@
|
||||
stripe
|
||||
max-height="360"
|
||||
>
|
||||
<el-table-column type="index" label="序号" min-width="68" align="center" header-align="center" />
|
||||
<el-table-column
|
||||
type="index"
|
||||
label="序号"
|
||||
min-width="68"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="projectName"
|
||||
label="项目名称"
|
||||
@@ -55,7 +61,12 @@
|
||||
header-align="center"
|
||||
show-overflow-tooltip
|
||||
/>
|
||||
<el-table-column label="项目费用(元)" min-width="150" align="center" header-align="center">
|
||||
<el-table-column
|
||||
label="项目费用(元)"
|
||||
min-width="150"
|
||||
align="center"
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<AmountInput v-model="row.projectFee" :min="0" placeholder="请输入费用" />
|
||||
</template>
|
||||
@@ -114,8 +125,12 @@
|
||||
<div class="attachment-footer">
|
||||
<span class="attachment-name" :title="file.fileName">{{ file.fileName }}</span>
|
||||
<div class="attachment-actions">
|
||||
<el-icon class="action-btn" @click.stop="downloadReport(file)"><Download /></el-icon>
|
||||
<el-icon class="action-btn action-btn--danger" @click.stop="removeReport(idx)"><Delete /></el-icon>
|
||||
<el-icon class="action-btn" @click.stop="downloadReport(file)"
|
||||
><Download
|
||||
/></el-icon>
|
||||
<el-icon class="action-btn action-btn--danger" @click.stop="removeReport(idx)"
|
||||
><Delete
|
||||
/></el-icon>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -145,7 +160,13 @@
|
||||
import { ref, computed, watch, nextTick } from 'vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import {
|
||||
Plus, Close, CircleCheck, Delete, Download, Document, Loading
|
||||
Plus,
|
||||
Close,
|
||||
CircleCheck,
|
||||
Delete,
|
||||
Download,
|
||||
Document,
|
||||
Loading
|
||||
} from '@element-plus/icons-vue'
|
||||
import {
|
||||
fetchPilotCheckupDetail,
|
||||
@@ -206,7 +227,10 @@
|
||||
const isImageFile = (file: any) => {
|
||||
const url = (file.fileUrl || '').toLowerCase()
|
||||
const name = (file.fileName || '').toLowerCase()
|
||||
return /\.(jpg|jpeg|png|gif|bmp|webp|svg)(\?|$)/.test(url) || /\.(jpg|jpeg|png|gif|bmp|webp|svg)$/.test(name)
|
||||
return (
|
||||
/\.(jpg|jpeg|png|gif|bmp|webp|svg)(\?|$)/.test(url) ||
|
||||
/\.(jpg|jpeg|png|gif|bmp|webp|svg)$/.test(name)
|
||||
)
|
||||
}
|
||||
|
||||
const parseAttachmentUrls = (urls: string) => {
|
||||
@@ -235,8 +259,8 @@
|
||||
actualCheckupTime.value = detail.actualCheckupTime || formatNowDatetime()
|
||||
reportFiles.value = parseAttachmentUrls(detail.reportAttachments || '')
|
||||
|
||||
const reserveList = Array.isArray(reserveRes) ? reserveRes : reserveRes?.list ?? []
|
||||
const actualList = Array.isArray(actualRes) ? actualRes : actualRes?.list ?? []
|
||||
const reserveList = Array.isArray(reserveRes) ? reserveRes : (reserveRes?.list ?? [])
|
||||
const actualList = Array.isArray(actualRes) ? actualRes : (actualRes?.list ?? [])
|
||||
|
||||
if (actualList.length > 0) {
|
||||
actualItems.value = dedupeCheckupActualItems(
|
||||
@@ -261,7 +285,10 @@
|
||||
if (!file) return
|
||||
const tempId = `temp_${Date.now()}`
|
||||
reportFiles.value.push({
|
||||
id: tempId, fileName: file.name, fileUrl: '', _uploading: true
|
||||
id: tempId,
|
||||
fileName: file.name,
|
||||
fileUrl: '',
|
||||
_uploading: true
|
||||
})
|
||||
const idx = reportFiles.value.length - 1
|
||||
try {
|
||||
@@ -335,7 +362,10 @@
|
||||
await savePilotCheckup({
|
||||
checkupId: props.checkupId,
|
||||
actualCheckupTime: actualCheckupTime.value,
|
||||
reportAttachments: reportFiles.value.map((f) => f.fileUrl).filter(Boolean).join(','),
|
||||
reportAttachments: reportFiles.value
|
||||
.map((f) => f.fileUrl)
|
||||
.filter(Boolean)
|
||||
.join(','),
|
||||
checkupRemark: checkupRemark.value || undefined,
|
||||
actualCheckupFee: actualTotal.value || undefined
|
||||
})
|
||||
|
||||
@@ -17,20 +17,64 @@
|
||||
<h3>{{ isFillFromListMode ? '已选患者' : '选择患者' }}</h3>
|
||||
</div>
|
||||
<template v-if="isFillFromListMode">
|
||||
<el-table :data="listModePatients" class="list-table" stripe row-key="id" max-height="240">
|
||||
<el-table-column type="index" label="序号" min-width="68" align="center" header-align="center" />
|
||||
<el-table-column prop="applicationNo" label="申请单号" min-width="160" align="center" header-align="center" />
|
||||
<el-table-column prop="patientName" label="患者姓名" min-width="100" align="center" header-align="center" />
|
||||
<el-table-column prop="sex" label="性别" min-width="60" align="center" header-align="center">
|
||||
<el-table
|
||||
:data="listModePatients"
|
||||
class="list-table"
|
||||
stripe
|
||||
row-key="id"
|
||||
max-height="240"
|
||||
>
|
||||
<el-table-column
|
||||
type="index"
|
||||
label="序号"
|
||||
min-width="68"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="applicationNo"
|
||||
label="申请单号"
|
||||
min-width="160"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="patientName"
|
||||
label="患者姓名"
|
||||
min-width="100"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="sex"
|
||||
label="性别"
|
||||
min-width="60"
|
||||
align="center"
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }">{{ sexLabel(row.sex) || '—' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="age" label="年龄" min-width="60" align="center" header-align="center">
|
||||
<el-table-column
|
||||
prop="age"
|
||||
label="年龄"
|
||||
min-width="60"
|
||||
align="center"
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }">{{ row.age ?? '—' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="channelName" label="渠道名称" min-width="140" align="center" header-align="center" />
|
||||
<el-table-column
|
||||
prop="channelName"
|
||||
label="渠道名称"
|
||||
min-width="140"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
</el-table>
|
||||
<div style="margin-top: 8px; font-size: 13px; color: #64748b">
|
||||
共 <strong style="color: #10b981">{{ listModePatients.length }}</strong> 名患者,将统一预约相同机构、时间与体检项目
|
||||
共
|
||||
<strong style="color: #10b981">{{ listModePatients.length }}</strong>
|
||||
名患者,将统一预约相同机构、时间与体检项目
|
||||
</div>
|
||||
</template>
|
||||
<template v-else>
|
||||
@@ -61,19 +105,57 @@
|
||||
@selection-change="(rows) => (selectedPatients = rows)"
|
||||
>
|
||||
<el-table-column type="selection" min-width="55" align="center" header-align="center" />
|
||||
<el-table-column type="index" label="序号" min-width="68" align="center" header-align="center" />
|
||||
<el-table-column prop="applicationNo" label="申请单号" min-width="160" align="center" header-align="center" />
|
||||
<el-table-column prop="patientName" label="患者姓名" min-width="100" align="center" header-align="center" />
|
||||
<el-table-column prop="sex" label="性别" min-width="60" align="center" header-align="center">
|
||||
<el-table-column
|
||||
type="index"
|
||||
label="序号"
|
||||
min-width="68"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="applicationNo"
|
||||
label="申请单号"
|
||||
min-width="160"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="patientName"
|
||||
label="患者姓名"
|
||||
min-width="100"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="sex"
|
||||
label="性别"
|
||||
min-width="60"
|
||||
align="center"
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }">{{ sexLabel(row.sex) || '—' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="age" label="年龄" min-width="60" align="center" header-align="center">
|
||||
<el-table-column
|
||||
prop="age"
|
||||
label="年龄"
|
||||
min-width="60"
|
||||
align="center"
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }">{{ row.age ?? '—' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="channelName" label="渠道名称" min-width="140" align="center" header-align="center" />
|
||||
<el-table-column
|
||||
prop="channelName"
|
||||
label="渠道名称"
|
||||
min-width="140"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
</el-table>
|
||||
<div style="margin-top: 8px; font-size: 13px; color: #64748b">
|
||||
已选 <strong style="color: #10b981">{{ selectedPatients.length }}</strong> 名患者(需体检且尚未登记体检记录)
|
||||
已选
|
||||
<strong style="color: #10b981">{{ selectedPatients.length }}</strong>
|
||||
名患者(需体检且尚未登记体检记录)
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
@@ -183,9 +265,26 @@
|
||||
@selection-change="(rows) => (projectSelectorSelected = rows)"
|
||||
>
|
||||
<el-table-column type="selection" min-width="55" align="center" header-align="center" />
|
||||
<el-table-column type="index" label="序号" min-width="68" align="center" header-align="center" />
|
||||
<el-table-column prop="projectName" label="项目名称" min-width="180" align="center" header-align="center" />
|
||||
<el-table-column label="项目费用(元)" min-width="120" align="center" header-align="center">
|
||||
<el-table-column
|
||||
type="index"
|
||||
label="序号"
|
||||
min-width="68"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="projectName"
|
||||
label="项目名称"
|
||||
min-width="180"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
label="项目费用(元)"
|
||||
min-width="120"
|
||||
align="center"
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }">{{
|
||||
row.projectFee != null ? formatMoneyDisplay(row.projectFee) : '—'
|
||||
}}</template>
|
||||
@@ -252,13 +351,42 @@
|
||||
@selection-change="(rows) => (packageSelectorSelected = rows)"
|
||||
>
|
||||
<el-table-column type="selection" min-width="55" align="center" header-align="center" />
|
||||
<el-table-column type="index" label="序号" min-width="68" align="center" header-align="center" />
|
||||
<el-table-column prop="packageName" label="套餐名称" min-width="140" align="center" header-align="center" />
|
||||
<el-table-column prop="packageCode" label="套餐编码" min-width="120" align="center" header-align="center" />
|
||||
<el-table-column prop="itemCount" label="项目数" min-width="80" align="center" header-align="center">
|
||||
<el-table-column
|
||||
type="index"
|
||||
label="序号"
|
||||
min-width="68"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="packageName"
|
||||
label="套餐名称"
|
||||
min-width="140"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="packageCode"
|
||||
label="套餐编码"
|
||||
min-width="120"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="itemCount"
|
||||
label="项目数"
|
||||
min-width="80"
|
||||
align="center"
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }">{{ row.itemCount ?? '—' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="合计费用(元)" min-width="120" align="center" header-align="center">
|
||||
<el-table-column
|
||||
label="合计费用(元)"
|
||||
min-width="120"
|
||||
align="center"
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }">{{
|
||||
row.totalFee != null ? formatMoneyDisplay(row.totalFee) : '—'
|
||||
}}</template>
|
||||
@@ -278,7 +406,13 @@
|
||||
header-align="center"
|
||||
show-overflow-tooltip
|
||||
/>
|
||||
<el-table-column label="操作" min-width="100" fixed="right" align="center" header-align="center">
|
||||
<el-table-column
|
||||
label="操作"
|
||||
min-width="100"
|
||||
fixed="right"
|
||||
align="center"
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<el-button class="btn-op-item" @click="handlePackageView(row)">
|
||||
<el-icon><View /></el-icon>查看
|
||||
@@ -320,8 +454,20 @@
|
||||
stripe
|
||||
max-height="420"
|
||||
>
|
||||
<el-table-column type="index" label="序号" min-width="68" align="center" header-align="center" />
|
||||
<el-table-column prop="projectName" label="项目名称" min-width="180" align="center" header-align="center" />
|
||||
<el-table-column
|
||||
type="index"
|
||||
label="序号"
|
||||
min-width="68"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="projectName"
|
||||
label="项目名称"
|
||||
min-width="180"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column label="项目费用(元)" min-width="120" align="center" header-align="center">
|
||||
<template #default="{ row }">{{
|
||||
row.projectFee != null ? formatMoneyDisplay(row.projectFee) : '—'
|
||||
|
||||
@@ -12,14 +12,36 @@
|
||||
/>
|
||||
<div class="list-body list-wrap checkup-reserve-list-wrap">
|
||||
<el-table :data="items" class="list-table" stripe :max-height="maxHeight">
|
||||
<el-table-column type="index" label="序号" min-width="68" align="center" header-align="center" />
|
||||
<el-table-column prop="projectName" label="项目名称" min-width="160" align="center" header-align="center" />
|
||||
<el-table-column label="原始项目费用(元)" min-width="130" align="center" header-align="center">
|
||||
<el-table-column
|
||||
type="index"
|
||||
label="序号"
|
||||
min-width="68"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="projectName"
|
||||
label="项目名称"
|
||||
min-width="160"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
label="原始项目费用(元)"
|
||||
min-width="130"
|
||||
align="center"
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }">{{
|
||||
row.originalProjectFee != null ? formatMoneyDisplay(row.originalProjectFee) : '—'
|
||||
}}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="预约项目费用(元)" min-width="150" align="center" header-align="center">
|
||||
<el-table-column
|
||||
label="预约项目费用(元)"
|
||||
min-width="150"
|
||||
align="center"
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<AmountInput v-model="row.projectFee" :min="0" placeholder="请输入费用" />
|
||||
</template>
|
||||
|
||||
@@ -21,9 +21,25 @@
|
||||
class="list-table inner-table"
|
||||
max-height="320"
|
||||
>
|
||||
<el-table-column type="index" label="序号" min-width="68" align="center" header-align="center" />
|
||||
<el-table-column prop="projectName" label="项目名称" min-width="200" show-overflow-tooltip />
|
||||
<el-table-column label="项目费用(元)" min-width="120" align="right" header-align="center">
|
||||
<el-table-column
|
||||
type="index"
|
||||
label="序号"
|
||||
min-width="68"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="projectName"
|
||||
label="项目名称"
|
||||
min-width="200"
|
||||
show-overflow-tooltip
|
||||
/>
|
||||
<el-table-column
|
||||
label="项目费用(元)"
|
||||
min-width="120"
|
||||
align="right"
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
{{ row.projectFee != null ? `¥${formatMoneyDisplay(row.projectFee)}` : '—' }}
|
||||
</template>
|
||||
@@ -45,9 +61,25 @@
|
||||
class="list-table inner-table"
|
||||
max-height="320"
|
||||
>
|
||||
<el-table-column type="index" label="序号" min-width="68" align="center" header-align="center" />
|
||||
<el-table-column prop="projectName" label="项目名称" min-width="200" show-overflow-tooltip />
|
||||
<el-table-column label="项目费用(元)" min-width="120" align="right" header-align="center">
|
||||
<el-table-column
|
||||
type="index"
|
||||
label="序号"
|
||||
min-width="68"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="projectName"
|
||||
label="项目名称"
|
||||
min-width="200"
|
||||
show-overflow-tooltip
|
||||
/>
|
||||
<el-table-column
|
||||
label="项目费用(元)"
|
||||
min-width="120"
|
||||
align="right"
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
{{ row.projectFee != null ? `¥${formatMoneyDisplay(row.projectFee)}` : '—' }}
|
||||
</template>
|
||||
@@ -62,7 +94,11 @@
|
||||
|
||||
<template #reports>
|
||||
<div v-if="reportFiles.length" class="report-attachment-list">
|
||||
<div v-for="(file, idx) in reportFiles" :key="file.url || idx" class="report-attachment-item">
|
||||
<div
|
||||
v-for="(file, idx) in reportFiles"
|
||||
:key="file.url || idx"
|
||||
class="report-attachment-item"
|
||||
>
|
||||
<div class="report-attachment-preview">
|
||||
<img v-if="isImageUrl(file.url)" :src="fullFileUrl(file.url)" :alt="file.name" />
|
||||
<div v-else class="report-attachment-preview__icon">
|
||||
@@ -195,9 +231,7 @@
|
||||
})
|
||||
}
|
||||
|
||||
const reportFiles = computed(() =>
|
||||
parseAttachmentUrls(viewData.value.reportAttachments || '')
|
||||
)
|
||||
const reportFiles = computed(() => parseAttachmentUrls(viewData.value.reportAttachments || ''))
|
||||
|
||||
const minioBaseUrl = (import.meta.env.VITE_MINIO_BASE_URL || '').replace(/\/$/, '')
|
||||
function fullFileUrl(suffix: string) {
|
||||
|
||||
@@ -83,10 +83,7 @@ export function findDuplicateReserveItemNames(items: CheckupReserveItem[]): stri
|
||||
return [...dups]
|
||||
}
|
||||
|
||||
export function appendCheckupReserveItems(
|
||||
target: CheckupReserveItem[],
|
||||
projects: any[]
|
||||
): number {
|
||||
export function appendCheckupReserveItems(target: CheckupReserveItem[], projects: any[]): number {
|
||||
let added = 0
|
||||
for (const proj of projects) {
|
||||
const candidate = toReserveCandidate(proj)
|
||||
@@ -120,10 +117,7 @@ export type CheckupActualItem = {
|
||||
projectFee: number | null
|
||||
}
|
||||
|
||||
export function isSameActualItem(
|
||||
a: { projectName: string },
|
||||
b: { projectName: string }
|
||||
): boolean {
|
||||
export function isSameActualItem(a: { projectName: string }, b: { projectName: string }): boolean {
|
||||
return isSameReserveItem(
|
||||
{ projectName: a.projectName, projectId: undefined },
|
||||
{ projectName: b.projectName, projectId: undefined }
|
||||
|
||||
@@ -16,16 +16,30 @@
|
||||
<el-form :model="searchForm" class="filter-form" @submit.prevent>
|
||||
<div class="filter-grid">
|
||||
<el-form-item label="关键词">
|
||||
<el-input v-model="searchForm.keyword" placeholder="申请单号/患者姓名/渠道名称" clearable />
|
||||
<el-input
|
||||
v-model="searchForm.keyword"
|
||||
placeholder="申请单号/患者姓名/渠道名称"
|
||||
clearable
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="体检状态">
|
||||
<el-select v-model="searchForm.checkupStatus" placeholder="全部" clearable style="width: 100%">
|
||||
<el-select
|
||||
v-model="searchForm.checkupStatus"
|
||||
placeholder="全部"
|
||||
clearable
|
||||
style="width: 100%"
|
||||
>
|
||||
<el-option label="待体检" value="dtj" />
|
||||
<el-option label="已体检" value="ytj" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="体检机构">
|
||||
<el-select v-model="searchForm.checkupInstitutionId" placeholder="全部" clearable style="width: 100%">
|
||||
<el-select
|
||||
v-model="searchForm.checkupInstitutionId"
|
||||
placeholder="全部"
|
||||
clearable
|
||||
style="width: 100%"
|
||||
>
|
||||
<el-option
|
||||
v-for="inst in institutionOptions"
|
||||
:key="inst.id"
|
||||
@@ -84,35 +98,104 @@
|
||||
header-align="center"
|
||||
:selectable="isRowSelectable"
|
||||
/>
|
||||
<el-table-column type="index" label="序号" min-width="68" align="center" header-align="center" :index="(i: number) => (pagination.page - 1) * pagination.pageSize + i + 1" />
|
||||
<el-table-column prop="applicationNo" label="申请单号" min-width="160" align="center" header-align="center" />
|
||||
<el-table-column prop="patientName" label="患者姓名" min-width="100" align="center" header-align="center" />
|
||||
<el-table-column prop="sex" label="性别" min-width="60" align="center" header-align="center">
|
||||
<el-table-column
|
||||
type="index"
|
||||
label="序号"
|
||||
min-width="68"
|
||||
align="center"
|
||||
header-align="center"
|
||||
:index="(i: number) => (pagination.page - 1) * pagination.pageSize + i + 1"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="applicationNo"
|
||||
label="申请单号"
|
||||
min-width="160"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="patientName"
|
||||
label="患者姓名"
|
||||
min-width="100"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="sex"
|
||||
label="性别"
|
||||
min-width="60"
|
||||
align="center"
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }">{{ sexLabel(row.sex) || '—' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="age" label="年龄" min-width="60" align="center" header-align="center">
|
||||
<el-table-column
|
||||
prop="age"
|
||||
label="年龄"
|
||||
min-width="60"
|
||||
align="center"
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }">{{ row.age ?? '—' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="channelName" label="渠道名称" min-width="140" align="center" header-align="center">
|
||||
<el-table-column
|
||||
prop="channelName"
|
||||
label="渠道名称"
|
||||
min-width="140"
|
||||
align="center"
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }">{{ row.channelName || '—' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="institutionName" label="体检机构" min-width="160" align="center" header-align="center">
|
||||
<el-table-column
|
||||
prop="institutionName"
|
||||
label="体检机构"
|
||||
min-width="160"
|
||||
align="center"
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }">{{ row.institutionName || '—' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="reserveTime" label="预约时间" min-width="160" align="center" header-align="center">
|
||||
<el-table-column
|
||||
prop="reserveTime"
|
||||
label="预约时间"
|
||||
min-width="160"
|
||||
align="center"
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }">{{ row.reserveTime || '—' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="checkupStatus" label="体检状态" min-width="100" align="center" header-align="center">
|
||||
<el-table-column
|
||||
prop="checkupStatus"
|
||||
label="体检状态"
|
||||
min-width="100"
|
||||
align="center"
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="checkupStatusTagType(row.checkupStatus)" size="small">
|
||||
{{ checkupStatusLabel(row.checkupStatus) }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="actualCheckupFee" label="体检费用" min-width="100" align="center" header-align="center">
|
||||
<template #default="{ row }">{{ row.actualCheckupFee != null ? '¥' + formatMoneyDisplay(row.actualCheckupFee) : '—' }}</template>
|
||||
<el-table-column
|
||||
prop="actualCheckupFee"
|
||||
label="体检费用"
|
||||
min-width="100"
|
||||
align="center"
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }">{{
|
||||
row.actualCheckupFee != null ? '¥' + formatMoneyDisplay(row.actualCheckupFee) : '—'
|
||||
}}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" min-width="400" fixed="right" align="center" header-align="center">
|
||||
<el-table-column
|
||||
label="操作"
|
||||
min-width="400"
|
||||
fixed="right"
|
||||
align="center"
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<el-button
|
||||
v-if="!row.institutionName && row.checkupStatus === 'dtj'"
|
||||
@@ -186,12 +269,15 @@
|
||||
import { ref, reactive, onMounted } from 'vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import {
|
||||
RefreshLeft, Search, View, CircleCheck, Edit, EditPen, Calendar
|
||||
RefreshLeft,
|
||||
Search,
|
||||
View,
|
||||
CircleCheck,
|
||||
Edit,
|
||||
EditPen,
|
||||
Calendar
|
||||
} from '@element-plus/icons-vue'
|
||||
import {
|
||||
fetchPilotCheckupList,
|
||||
fetchCheckupInstitutionList
|
||||
} from '@/api/admin'
|
||||
import { fetchPilotCheckupList, fetchCheckupInstitutionList } from '@/api/admin'
|
||||
import { checkupStatusLabel, checkupStatusTagType, sexLabel } from '@/utils/status-tag-registry'
|
||||
import { formatMoneyDisplay } from '@/utils/money-format'
|
||||
import CheckupFillDialog from './CheckupFillDialog.vue'
|
||||
@@ -258,7 +344,7 @@
|
||||
const loadInstitutions = async () => {
|
||||
try {
|
||||
const res: any = await fetchCheckupInstitutionList({ page: 1, pageSize: 100, status: '1' })
|
||||
institutionOptions.value = Array.isArray(res) ? res : res?.list ?? []
|
||||
institutionOptions.value = Array.isArray(res) ? res : (res?.list ?? [])
|
||||
} catch {
|
||||
institutionOptions.value = []
|
||||
}
|
||||
|
||||
+89
@@ -0,0 +1,89 @@
|
||||
<!-- 输注计划管理 — 取消弹窗 -->
|
||||
<template>
|
||||
<el-dialog
|
||||
:model-value="modelValue"
|
||||
:title="'取消输注计划 — ' + (patientName || '')"
|
||||
width="500px"
|
||||
class="dialog-form-layout"
|
||||
align-center
|
||||
destroy-on-close
|
||||
:close-on-click-modal="false"
|
||||
@update:model-value="emit('update:modelValue', $event)"
|
||||
>
|
||||
<el-form label-position="top">
|
||||
<el-form-item label="取消原因">
|
||||
<el-input
|
||||
v-model="cancelReason"
|
||||
type="textarea"
|
||||
:rows="4"
|
||||
placeholder="请输入取消原因"
|
||||
maxlength="500"
|
||||
show-word-limit
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<template #footer>
|
||||
<el-button class="btn-ghost" @click="emit('update:modelValue', false)">
|
||||
<el-icon><Close /></el-icon>取消
|
||||
</el-button>
|
||||
<el-button
|
||||
class="btn-main"
|
||||
type="primary"
|
||||
:loading="submitLoading"
|
||||
:disabled="!cancelReason.trim()"
|
||||
@click="handleSubmit"
|
||||
>
|
||||
<el-icon><CircleCheck /></el-icon>确认取消
|
||||
</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, watch } from 'vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { Close, CircleCheck } from '@element-plus/icons-vue'
|
||||
import { updateInfusionScheduleStatus } from '@/api/admin'
|
||||
|
||||
const props = defineProps<{
|
||||
modelValue: boolean
|
||||
scheduleId: string
|
||||
patientName?: string
|
||||
}>()
|
||||
const emit = defineEmits(['update:modelValue', 'success'])
|
||||
|
||||
const cancelReason = ref('')
|
||||
const submitLoading = ref(false)
|
||||
|
||||
const handleSubmit = async () => {
|
||||
if (!cancelReason.value.trim()) {
|
||||
ElMessage.warning('请输入取消原因')
|
||||
return
|
||||
}
|
||||
submitLoading.value = true
|
||||
try {
|
||||
await updateInfusionScheduleStatus({
|
||||
scheduleId: props.scheduleId,
|
||||
infusionStatus: 'yqx',
|
||||
cancelReason: cancelReason.value.trim()
|
||||
})
|
||||
ElMessage.success('已取消输注计划')
|
||||
emit('update:modelValue', false)
|
||||
emit('success')
|
||||
} catch {
|
||||
// error handled by request util
|
||||
} finally {
|
||||
submitLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
watch(
|
||||
() => props.modelValue,
|
||||
(val) => {
|
||||
if (val) {
|
||||
cancelReason.value = ''
|
||||
}
|
||||
}
|
||||
)
|
||||
</script>
|
||||
+1719
File diff suppressed because it is too large.
Load diff
+253
@@ -0,0 +1,253 @@
|
||||
<!-- 输注计划管理 — 查看弹窗 -->
|
||||
<template>
|
||||
<SystemViewDialog
|
||||
v-model:visible="innerVisible"
|
||||
:title="viewData.patientName || '输注计划详情'"
|
||||
:icon="Document"
|
||||
:code="viewData.applicationNo"
|
||||
code-label="申请单号"
|
||||
:sections="sections"
|
||||
:loading="loading"
|
||||
:grid-columns="3"
|
||||
width="1080px"
|
||||
append-to-body
|
||||
>
|
||||
<template #products>
|
||||
<div class="list-body list-wrap view-inner-table-wrap">
|
||||
<el-table
|
||||
v-if="viewData.products && viewData.products.length"
|
||||
:data="viewData.products"
|
||||
stripe
|
||||
class="list-table inner-table"
|
||||
max-height="320"
|
||||
>
|
||||
<el-table-column
|
||||
type="index"
|
||||
label="序号"
|
||||
min-width="68"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="projectName"
|
||||
label="项目名称"
|
||||
min-width="160"
|
||||
show-overflow-tooltip
|
||||
/>
|
||||
<el-table-column
|
||||
prop="cellTypeName"
|
||||
label="细胞类型"
|
||||
min-width="120"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="specText"
|
||||
label="规格"
|
||||
min-width="100"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
label="协议价格(元)"
|
||||
min-width="120"
|
||||
align="right"
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
{{ row.agreementPrice != null ? `¥${formatMoneyDisplay(row.agreementPrice)}` : '—' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
label="实际价格(元)"
|
||||
min-width="120"
|
||||
align="right"
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
{{ row.actualPrice != null ? `¥${formatMoneyDisplay(row.actualPrice)}` : '—' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="quantity"
|
||||
label="数量"
|
||||
min-width="80"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
</el-table>
|
||||
<el-empty v-else description="暂无产品" :image-size="48" />
|
||||
</div>
|
||||
</template>
|
||||
</SystemViewDialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, watch } from 'vue'
|
||||
import { Document } from '@element-plus/icons-vue'
|
||||
import SystemViewDialog, {
|
||||
type ViewSection,
|
||||
type TagType
|
||||
} from '@/components/business/system-view-dialog/index.vue'
|
||||
import { fetchDictDataOptions, fetchInfusionScheduleMgmtDetail } from '@/api/admin'
|
||||
import {
|
||||
scheduleStatusLabel,
|
||||
scheduleStatusTagType,
|
||||
infusionStatusLabel,
|
||||
infusionStatusTagType,
|
||||
qcStatusLabel,
|
||||
qcStatusTagType,
|
||||
sexLabel
|
||||
} from '@/utils/status-tag-registry'
|
||||
import { formatMoneyDisplay } from '@/utils/money-format'
|
||||
|
||||
const props = defineProps<{
|
||||
modelValue: boolean
|
||||
scheduleRow: Record<string, any> | null
|
||||
}>()
|
||||
const emit = defineEmits(['update:modelValue'])
|
||||
|
||||
const innerVisible = computed({
|
||||
get: () => props.modelValue,
|
||||
set: (v: boolean) => emit('update:modelValue', v)
|
||||
})
|
||||
|
||||
const loading = ref(false)
|
||||
const viewData = ref<Record<string, any>>({})
|
||||
const infusionMethodOptions = ref<Api.Dict.DictDataOption[]>([])
|
||||
|
||||
const formatInfusionMethod = (code: unknown) => {
|
||||
if (!code) return undefined
|
||||
const hit = infusionMethodOptions.value.find((o) => o.value === code)
|
||||
return hit?.label || String(code)
|
||||
}
|
||||
|
||||
const sections = computed((): ViewSection[] => {
|
||||
const row = viewData.value
|
||||
if (!row?.id) return []
|
||||
return [
|
||||
{
|
||||
title: '申请信息',
|
||||
fields: [
|
||||
{ label: '申请单号', value: row.applicationNo },
|
||||
{ label: '渠道名称', value: row.channelName }
|
||||
]
|
||||
},
|
||||
{
|
||||
title: '患者信息',
|
||||
fields: [
|
||||
{ label: '患者姓名', value: row.patientName },
|
||||
{ label: '性别', value: sexLabel(row.sex) || '—' },
|
||||
{ label: '年龄', value: row.age != null ? String(row.age) : undefined }
|
||||
]
|
||||
},
|
||||
{
|
||||
title: '输注计划',
|
||||
fields: [
|
||||
{ label: '规格', value: row.specText },
|
||||
{ label: '输注时间', value: row.infusionTime },
|
||||
{ label: '输注地点', value: row.infusionLocation },
|
||||
{ label: '输注方式', value: formatInfusionMethod(row.infusionMethod) },
|
||||
{
|
||||
label: '生产状态',
|
||||
value: scheduleStatusLabel(row.scheduleStatus),
|
||||
tagType: scheduleStatusTagType(row.scheduleStatus) as TagType
|
||||
},
|
||||
{
|
||||
label: '输注状态',
|
||||
value: infusionStatusLabel(row.infusionStatus),
|
||||
tagType: infusionStatusTagType(row.infusionStatus) as TagType
|
||||
},
|
||||
{
|
||||
label: '质检状态',
|
||||
value: qcStatusLabel(row.qcStatus),
|
||||
tagType: qcStatusTagType(row.qcStatus) as TagType
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
title: '产品列表',
|
||||
fields: [],
|
||||
slot: 'products'
|
||||
},
|
||||
{
|
||||
title: '其他信息',
|
||||
fields: [{ label: '备注', value: row.remark || undefined, span: true }]
|
||||
}
|
||||
]
|
||||
})
|
||||
|
||||
const loadInfusionMethodOptions = async () => {
|
||||
try {
|
||||
infusionMethodOptions.value = (await fetchDictDataOptions('szfs')) || []
|
||||
} catch {
|
||||
infusionMethodOptions.value = []
|
||||
}
|
||||
}
|
||||
|
||||
const loadDetail = async () => {
|
||||
const id = props.scheduleRow?.id
|
||||
if (!id) return
|
||||
viewData.value = { ...(props.scheduleRow || {}) }
|
||||
loading.value = true
|
||||
try {
|
||||
await loadInfusionMethodOptions()
|
||||
const res: any = await fetchInfusionScheduleMgmtDetail({ scheduleId: id })
|
||||
const detail =
|
||||
res && typeof res === 'object' && !Array.isArray(res)
|
||||
? (res as Record<string, any>)
|
||||
: ((res as any)?.data ?? {})
|
||||
viewData.value = { ...viewData.value, ...detail }
|
||||
} catch {
|
||||
// keep initial data from row
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
watch(
|
||||
() => props.modelValue,
|
||||
(val) => {
|
||||
if (val && props.scheduleRow?.id) {
|
||||
loadDetail()
|
||||
} else if (!val) {
|
||||
viewData.value = {}
|
||||
}
|
||||
}
|
||||
)
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.view-inner-table-wrap.list-body.list-wrap {
|
||||
margin-top: 4px;
|
||||
border: 1px solid var(--mint-border, #e2e8f0);
|
||||
border-radius: var(--mint-radius, 10px);
|
||||
overflow: hidden;
|
||||
background: #fff;
|
||||
|
||||
:deep(.el-table) {
|
||||
--el-table-header-bg-color: var(--mint-table-head, #f8fafc);
|
||||
--el-table-border-color: var(--mint-border, #e2e8f0);
|
||||
--el-table-row-hover-bg-color: var(--mint-row-hover, #f0fdf4);
|
||||
--el-table-text-color: var(--mint-text, #0f172a);
|
||||
--el-table-header-text-color: var(--mint-text-muted, #64748b);
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
:deep(.el-table th.el-table__cell) {
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
color: var(--mint-text-muted, #64748b);
|
||||
}
|
||||
|
||||
:deep(.el-table td.el-table__cell) {
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
color: var(--mint-text, #0f172a);
|
||||
}
|
||||
|
||||
:deep(.el-empty) {
|
||||
padding: 24px 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,527 @@
|
||||
<!-- 输注计划管理 — 列表页 -->
|
||||
<template>
|
||||
<div class="page-container">
|
||||
<header class="page-head">
|
||||
<h1>输注计划管理</h1>
|
||||
<p class="page-desc">管理中试服务患者输注计划的确认、完成与取消。</p>
|
||||
</header>
|
||||
|
||||
<section class="panel">
|
||||
<div class="panel-top">
|
||||
<h2>输注计划</h2>
|
||||
</div>
|
||||
|
||||
<!-- 筛选 -->
|
||||
<div class="filter-block">
|
||||
<el-form :model="searchForm" class="filter-form" @submit.prevent>
|
||||
<div class="filter-grid">
|
||||
<el-form-item label="关键词">
|
||||
<el-input
|
||||
v-model="searchForm.keyword"
|
||||
placeholder="申请单号/患者姓名/渠道名称"
|
||||
clearable
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="输注状态">
|
||||
<el-select
|
||||
v-model="searchForm.infusionStatus"
|
||||
placeholder="全部"
|
||||
clearable
|
||||
style="width: 100%"
|
||||
>
|
||||
<el-option label="待输注" value="dtz" />
|
||||
<el-option label="已用户确认" value="yrqr" />
|
||||
<el-option label="已取消" value="yqx" />
|
||||
<el-option label="已完成" value="ywc" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="质检状态">
|
||||
<el-select
|
||||
v-model="searchForm.qcStatus"
|
||||
placeholder="全部"
|
||||
clearable
|
||||
style="width: 100%"
|
||||
>
|
||||
<el-option label="待质检" value="dzc" />
|
||||
<el-option label="已质检" value="yzj" />
|
||||
<el-option label="已取消" value="yqx" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="输注时间">
|
||||
<el-date-picker
|
||||
v-model="searchForm.infusionTimeRange"
|
||||
type="daterange"
|
||||
range-separator="至"
|
||||
start-placeholder="开始日期"
|
||||
end-placeholder="结束日期"
|
||||
value-format="YYYY-MM-DD"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</el-form-item>
|
||||
</div>
|
||||
<div class="filter-actions-row">
|
||||
<span class="filter-hint">支持申请单号、患者姓名、渠道名称模糊查询,输入即检索。</span>
|
||||
<div class="filter-actions-end">
|
||||
<el-button class="btn-ghost" @click="handleReset">
|
||||
<el-icon><RefreshLeft /></el-icon>重置
|
||||
</el-button>
|
||||
<el-button class="btn-main" type="primary" @click="handleSearch">
|
||||
<el-icon><Search /></el-icon>搜索
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</el-form>
|
||||
</div>
|
||||
|
||||
<div class="list-toolbar">
|
||||
<div class="list-toolbar-row">
|
||||
<div class="list-toolbar-selected">
|
||||
已选 <span class="sel-num">{{ selectedRows.length }}</span> 项
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 表格 -->
|
||||
<div class="list-body list-wrap">
|
||||
<el-table
|
||||
ref="tableRef"
|
||||
class="list-table"
|
||||
:data="tableData"
|
||||
v-loading="loading"
|
||||
stripe
|
||||
row-key="id"
|
||||
:row-class-name="infusionScheduleRowClassName"
|
||||
@selection-change="handleSelectionChange"
|
||||
@row-dblclick="handleView"
|
||||
>
|
||||
<el-table-column type="selection" min-width="55" align="center" header-align="center" />
|
||||
<el-table-column
|
||||
type="index"
|
||||
label="序号"
|
||||
min-width="68"
|
||||
align="center"
|
||||
header-align="center"
|
||||
:index="(i: number) => (pagination.page - 1) * pagination.pageSize + i + 1"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="applicationNo"
|
||||
label="申请单号"
|
||||
min-width="160"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="patientName"
|
||||
label="患者姓名"
|
||||
min-width="100"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="sex"
|
||||
label="性别"
|
||||
min-width="60"
|
||||
align="center"
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }">{{ sexLabel(row.patientSex ?? row.sex) || '—' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="age"
|
||||
label="年龄"
|
||||
min-width="60"
|
||||
align="center"
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }">{{ row.patientAge ?? row.age ?? '—' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="channelName"
|
||||
label="渠道名称"
|
||||
min-width="140"
|
||||
align="center"
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }">{{ row.channelName || '—' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="第几次" min-width="88" align="center" header-align="center">
|
||||
<template #default="{ row }">{{ formatScheduleSortNo(row) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="总次数" min-width="88" align="center" header-align="center">
|
||||
<template #default="{ row }">{{ formatScheduleTotalCount(row) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="infusionTime"
|
||||
label="输注时间"
|
||||
min-width="160"
|
||||
align="center"
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }">{{ row.infusionTime || '—' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="infusionLocation"
|
||||
label="输注地点"
|
||||
min-width="140"
|
||||
align="center"
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }">{{ row.infusionLocation || '—' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="scheduleStatus"
|
||||
label="生产状态"
|
||||
min-width="100"
|
||||
align="center"
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="scheduleStatusTagType(row.scheduleStatus)" size="small">
|
||||
{{ scheduleStatusLabel(row.scheduleStatus) }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="infusionStatus"
|
||||
label="输注状态"
|
||||
min-width="110"
|
||||
align="center"
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="infusionStatusTagType(row.infusionStatus)" size="small">
|
||||
{{ infusionStatusLabel(row.infusionStatus) }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="质检状态" min-width="120" align="center" header-align="center">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="qcStatusTagType(row.qcStatus)" size="small">{{
|
||||
qcStatusLabel(row.qcStatus)
|
||||
}}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
label="操作"
|
||||
min-width="420"
|
||||
fixed="right"
|
||||
align="center"
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<el-button v-if="canEditSchedule(row)" class="btn-op-item" @click="handleEdit(row)">
|
||||
<el-icon><EditPen /></el-icon>编辑
|
||||
</el-button>
|
||||
<el-button
|
||||
v-if="getInfusionStatus(row) === 'dtz'"
|
||||
class="btn-op-item"
|
||||
@click="handleConfirm(row)"
|
||||
>
|
||||
<el-icon><CircleCheck /></el-icon>用户已确认
|
||||
</el-button>
|
||||
<el-button
|
||||
v-if="canCompleteSchedule(row)"
|
||||
class="btn-op-item primary"
|
||||
@click="handleComplete(row)"
|
||||
>
|
||||
<el-icon><SuccessFilled /></el-icon>完成
|
||||
</el-button>
|
||||
<el-button v-if="canEditSchedule(row)" class="btn-op-item" @click="handleCancel(row)">
|
||||
<el-icon><CircleClose /></el-icon>取消
|
||||
</el-button>
|
||||
<el-button class="btn-op-item" @click="handleView(row)">
|
||||
<el-icon><View /></el-icon>查看
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<div class="pagination list-pager-bar">
|
||||
<el-pagination
|
||||
:current-page="pagination.page"
|
||||
:page-size="pagination.pageSize"
|
||||
:total="pagination.total"
|
||||
:page-sizes="[10, 20, 50]"
|
||||
layout="total, sizes, prev, pager, next"
|
||||
@size-change="
|
||||
(v: number) => ((pagination.page = 1), (pagination.pageSize = v), loadData())
|
||||
"
|
||||
@current-change="(v: number) => ((pagination.page = v), loadData())"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 编辑弹窗 -->
|
||||
<ScheduleEditDialog v-model="editDialogVisible" :schedule-row="editRow" @success="loadData" />
|
||||
|
||||
<!-- 查看弹窗 -->
|
||||
<ScheduleViewDialog v-model="viewDialogVisible" :schedule-row="viewRow" />
|
||||
|
||||
<!-- 取消弹窗 -->
|
||||
<ScheduleCancelDialog
|
||||
v-model="cancelDialogVisible"
|
||||
:schedule-id="cancelScheduleId"
|
||||
:patient-name="cancelPatientName"
|
||||
@success="loadData"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, onMounted } from 'vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import {
|
||||
RefreshLeft,
|
||||
Search,
|
||||
View,
|
||||
EditPen,
|
||||
CircleCheck,
|
||||
CircleClose,
|
||||
SuccessFilled
|
||||
} from '@element-plus/icons-vue'
|
||||
import { fetchInfusionScheduleMgmtList, updateInfusionScheduleStatus } from '@/api/admin'
|
||||
import {
|
||||
scheduleStatusLabel,
|
||||
scheduleStatusTagType,
|
||||
infusionStatusLabel,
|
||||
infusionStatusTagType,
|
||||
qcStatusLabel,
|
||||
qcStatusTagType,
|
||||
channelServiceOrderTypeLabel,
|
||||
sexLabel
|
||||
} from '@/utils/status-tag-registry'
|
||||
import ScheduleEditDialog from './ScheduleEditDialog.vue'
|
||||
import ScheduleViewDialog from './ScheduleViewDialog.vue'
|
||||
import ScheduleCancelDialog from './ScheduleCancelDialog.vue'
|
||||
|
||||
defineOptions({ name: 'InfusionScheduleManagement' })
|
||||
|
||||
// ── 搜索(输注时间默认最近 30 天) ──
|
||||
const getDefaultTimeRange = () => {
|
||||
const end = new Date()
|
||||
const start = new Date()
|
||||
start.setTime(start.getTime() - 30 * 24 * 3600 * 1000)
|
||||
const fmt = (d: Date) => d.toISOString().slice(0, 10)
|
||||
return [fmt(start), fmt(end)]
|
||||
}
|
||||
|
||||
const searchForm = reactive({
|
||||
keyword: '',
|
||||
infusionStatus: '',
|
||||
qcStatus: '',
|
||||
infusionTimeRange: getDefaultTimeRange() as string[]
|
||||
})
|
||||
|
||||
// ── 表格 ─────────────────────────────
|
||||
const loading = ref(false)
|
||||
const tableData = ref<any[]>([])
|
||||
const tableRef = ref()
|
||||
const selectedRows = ref<any[]>([])
|
||||
const pagination = reactive({ page: 1, pageSize: 10, total: 0 })
|
||||
|
||||
const handleSelectionChange = (rows: any[]) => {
|
||||
selectedRows.value = rows
|
||||
}
|
||||
|
||||
/** 输注时间落在本周(周一至周日) */
|
||||
const isInfusionThisWeek = (infusionTime?: string | null): boolean => {
|
||||
if (!infusionTime) return false
|
||||
const dateStr = String(infusionTime).trim().slice(0, 10)
|
||||
const infusionDate = new Date(`${dateStr}T00:00:00`)
|
||||
if (Number.isNaN(infusionDate.getTime())) return false
|
||||
|
||||
const today = new Date()
|
||||
today.setHours(0, 0, 0, 0)
|
||||
const weekday = today.getDay()
|
||||
const diffToMonday = weekday === 0 ? -6 : 1 - weekday
|
||||
const weekStart = new Date(today)
|
||||
weekStart.setDate(today.getDate() + diffToMonday)
|
||||
const weekEnd = new Date(weekStart)
|
||||
weekEnd.setDate(weekStart.getDate() + 6)
|
||||
weekEnd.setHours(23, 59, 59, 999)
|
||||
|
||||
return infusionDate >= weekStart && infusionDate <= weekEnd
|
||||
}
|
||||
|
||||
const infusionScheduleRowClassName = ({ row }: { row: Record<string, unknown> }) => {
|
||||
return isInfusionThisWeek(row.infusionTime as string) ? 'row-infusion-this-week' : ''
|
||||
}
|
||||
|
||||
const formatScheduleSortNo = (row: Record<string, unknown>) => {
|
||||
const n = row.sortNo ?? row.orderNum
|
||||
if (n == null || n === '' || Number(n) <= 0) return '—'
|
||||
return `第${Number(n)}次`
|
||||
}
|
||||
|
||||
const formatScheduleTotalCount = (row: Record<string, unknown>) => {
|
||||
const n = row.totalCourseCount
|
||||
if (n == null || n === '' || Number(n) <= 0) return '—'
|
||||
return `共${Number(n)}次`
|
||||
}
|
||||
|
||||
// ── 弹窗 ─────────────────────────────
|
||||
const editDialogVisible = ref(false)
|
||||
const editRow = ref<any>(null)
|
||||
const viewDialogVisible = ref(false)
|
||||
const viewRow = ref<any>(null)
|
||||
const cancelDialogVisible = ref(false)
|
||||
const cancelScheduleId = ref('')
|
||||
const cancelPatientName = ref('')
|
||||
|
||||
// ── 数据加载 ──────────────────────────
|
||||
const loadData = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const params: any = {
|
||||
page: pagination.page,
|
||||
pageSize: pagination.pageSize,
|
||||
keyword: searchForm.keyword || undefined,
|
||||
infusionStatus: searchForm.infusionStatus || undefined,
|
||||
qcStatus: searchForm.qcStatus || undefined
|
||||
}
|
||||
if (searchForm.infusionTimeRange?.length === 2) {
|
||||
params.infusionTimeStart = searchForm.infusionTimeRange[0]
|
||||
params.infusionTimeEnd = searchForm.infusionTimeRange[1]
|
||||
}
|
||||
const res: any = await fetchInfusionScheduleMgmtList(params)
|
||||
const data = res?.data ?? res
|
||||
tableData.value = data?.list ?? data?.records ?? []
|
||||
pagination.total = data?.pagination?.total ?? data?.total ?? 0
|
||||
} catch (e) {
|
||||
console.error('加载输注计划列表失败:', e)
|
||||
tableData.value = []
|
||||
pagination.total = 0
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// ── 事件 ──────────────────────────────
|
||||
const handleSearch = () => {
|
||||
pagination.page = 1
|
||||
loadData()
|
||||
}
|
||||
|
||||
const handleReset = () => {
|
||||
Object.assign(searchForm, {
|
||||
keyword: '',
|
||||
infusionStatus: '',
|
||||
qcStatus: '',
|
||||
infusionTimeRange: getDefaultTimeRange()
|
||||
})
|
||||
pagination.page = 1
|
||||
loadData()
|
||||
}
|
||||
|
||||
const handleEdit = (row: any) => {
|
||||
editRow.value = { ...row }
|
||||
editDialogVisible.value = true
|
||||
}
|
||||
|
||||
const handleView = (row: any) => {
|
||||
viewRow.value = { ...row }
|
||||
viewDialogVisible.value = true
|
||||
}
|
||||
|
||||
const getInfusionStatus = (row: any) =>
|
||||
String(row?.infusionStatus || row?.infusion_status || '').trim()
|
||||
|
||||
const canEditSchedule = (row: any) => {
|
||||
const s = getInfusionStatus(row)
|
||||
return s === 'dtz' || s === 'yrqr'
|
||||
}
|
||||
|
||||
/** 待输注、已用户确认均可完成(待输注时将自动先确认再完成) */
|
||||
const canCompleteSchedule = (row: any) => canEditSchedule(row)
|
||||
|
||||
const buildCompleteConfirmMessage = (row: any) => {
|
||||
const orderTypeLabel = channelServiceOrderTypeLabel(row.orderType)
|
||||
const patient = row.patientName ? `患者「${row.patientName}」` : '该患者'
|
||||
const statusHint =
|
||||
getInfusionStatus(row) === 'dtz' ? '当前为待输注,系统将先记录用户确认再完成结算。' : ''
|
||||
let settlementHint = '并触发关联服务订单的结算扣减。'
|
||||
if (row.orderType === 'yfk') {
|
||||
settlementHint = `关联服务订单类型为「${orderTypeLabel}」,系统将按渠道协议单价扣减服务订单账户余额。`
|
||||
} else if (row.orderType === 'gmcp') {
|
||||
settlementHint = `关联服务订单类型为「${orderTypeLabel}」,系统将按份数扣减服务订单中的细胞产品持仓。`
|
||||
}
|
||||
return `${patient}的输注计划将标记为已完成。${statusHint}${settlementHint}是否继续?`
|
||||
}
|
||||
|
||||
const handleConfirm = async (row: any) => {
|
||||
try {
|
||||
await ElMessageBox.confirm('确认该输注计划已由用户确认?', '用户已确认', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'info'
|
||||
})
|
||||
await updateInfusionScheduleStatus({
|
||||
scheduleId: row.id,
|
||||
infusionStatus: 'yrqr'
|
||||
})
|
||||
ElMessage.success('已确认用户确认')
|
||||
loadData()
|
||||
} catch (e: any) {
|
||||
if (e !== 'cancel') {
|
||||
// error handled by request util
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const handleComplete = async (row: any) => {
|
||||
try {
|
||||
await ElMessageBox.confirm(buildCompleteConfirmMessage(row), '完成输注计划', {
|
||||
confirmButtonText: '确定完成',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
})
|
||||
const scheduleId = row.id
|
||||
if (getInfusionStatus(row) === 'dtz') {
|
||||
await updateInfusionScheduleStatus({
|
||||
scheduleId,
|
||||
infusionStatus: 'yrqr'
|
||||
})
|
||||
}
|
||||
await updateInfusionScheduleStatus({
|
||||
scheduleId,
|
||||
infusionStatus: 'ywc'
|
||||
})
|
||||
ElMessage.success('输注计划已完成,结算扣减已处理')
|
||||
loadData()
|
||||
} catch (e: any) {
|
||||
if (e !== 'cancel') {
|
||||
// error handled by request util
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const handleCancel = (row: any) => {
|
||||
cancelScheduleId.value = row.id
|
||||
cancelPatientName.value = row.patientName || ''
|
||||
cancelDialogVisible.value = true
|
||||
}
|
||||
|
||||
watchDebounced(
|
||||
searchForm,
|
||||
() => {
|
||||
pagination.page = 1
|
||||
loadData()
|
||||
},
|
||||
{ debounce: 300, deep: true }
|
||||
)
|
||||
|
||||
onMounted(() => {
|
||||
loadData()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.list-table :deep(tr.row-infusion-this-week > td.el-table__cell) {
|
||||
background-color: #fee2e2 !important;
|
||||
color: #991b1b;
|
||||
}
|
||||
|
||||
.list-table :deep(tr.row-infusion-this-week:hover > td.el-table__cell) {
|
||||
background-color: #fecaca !important;
|
||||
}
|
||||
</style>
|
||||
@@ -24,7 +24,6 @@
|
||||
{{ applicationStatusLabel(applicationDetail.applicationStatus) }}
|
||||
</el-tag>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -32,7 +31,7 @@
|
||||
<!-- detail-tabs sticky 导航 -->
|
||||
<div class="detail-tabs">
|
||||
<div
|
||||
v-for="tab in (isCreateMode ? createDetailTabs : detailTabs)"
|
||||
v-for="tab in isCreateMode ? createDetailTabs : detailTabs"
|
||||
:key="tab.key"
|
||||
class="detail-tab"
|
||||
:class="{ active: activeTab === tab.key }"
|
||||
@@ -222,7 +221,7 @@
|
||||
</el-form-item>
|
||||
</div>
|
||||
</el-form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Tab 2: 输注方案(创建模式本地暂存) -->
|
||||
@@ -784,7 +783,11 @@
|
||||
<div class="attachment-footer">
|
||||
<span class="attachment-name" :title="file.name">{{ file.name }}</span>
|
||||
<div class="attachment-actions">
|
||||
<el-icon class="action-btn" title="下载" @click.stop="downloadFile(file)">
|
||||
<el-icon
|
||||
class="action-btn"
|
||||
title="下载"
|
||||
@click.stop="downloadFile(file)"
|
||||
>
|
||||
<Download />
|
||||
</el-icon>
|
||||
</div>
|
||||
|
||||
@@ -59,7 +59,11 @@
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="服务订单" prop="channelServiceOrderId" class="form-col-span-2-cols">
|
||||
<el-form-item
|
||||
label="服务订单"
|
||||
prop="channelServiceOrderId"
|
||||
class="form-col-span-2-cols"
|
||||
>
|
||||
<div class="field-with-action">
|
||||
<el-select
|
||||
v-model="formData.channelServiceOrderId"
|
||||
@@ -227,7 +231,9 @@
|
||||
</template>
|
||||
</div>
|
||||
<div class="attachment-footer">
|
||||
<span class="attachment-name" :title="file.fileName">{{ file.fileName }}</span>
|
||||
<span class="attachment-name" :title="file.fileName">{{
|
||||
file.fileName
|
||||
}}</span>
|
||||
<div class="attachment-actions">
|
||||
<el-icon class="action-btn" @click.stop="downloadFile(file)">
|
||||
<Download />
|
||||
@@ -305,10 +311,7 @@
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<template #label>疗程间隔<span class="form-label-tip">(月)</span></template>
|
||||
<el-input
|
||||
v-model="infusionPlan.courseInterval"
|
||||
placeholder="请输入疗程间隔"
|
||||
/>
|
||||
<el-input v-model="infusionPlan.courseInterval" placeholder="请输入疗程间隔" />
|
||||
</el-form-item>
|
||||
<el-form-item label="输注开始日期">
|
||||
<el-date-picker
|
||||
@@ -353,7 +356,24 @@
|
||||
<div class="schedule-header" @click="toggleScheduleExpand(sIdx)">
|
||||
<span class="schedule-chevron">▶</span>
|
||||
<span class="schedule-title">输注计划 #{{ sIdx + 1 }}</span>
|
||||
<el-tag
|
||||
v-if="schedule.infusionStatus"
|
||||
:type="infusionStatusTagType(normalizeInfusionStatusCode(schedule.infusionStatus))"
|
||||
size="small"
|
||||
class="schedule-status-tag"
|
||||
>
|
||||
{{ infusionStatusLabel(normalizeInfusionStatusCode(schedule.infusionStatus)) }}
|
||||
</el-tag>
|
||||
<el-tag
|
||||
v-if="schedule.scheduleStatus && isEdit"
|
||||
:type="scheduleStatusTagType(schedule.scheduleStatus)"
|
||||
size="small"
|
||||
class="schedule-status-tag"
|
||||
>
|
||||
{{ scheduleStatusLabel(schedule.scheduleStatus) }}
|
||||
</el-tag>
|
||||
<el-button
|
||||
v-if="canDeleteSchedule(schedule)"
|
||||
link
|
||||
type="danger"
|
||||
size="small"
|
||||
@@ -373,6 +393,7 @@
|
||||
value-format="YYYY-MM-DD"
|
||||
placeholder="自动计算,可修改"
|
||||
clearable
|
||||
:disabled="!isScheduleEditable(schedule)"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</el-form-item>
|
||||
@@ -380,6 +401,7 @@
|
||||
<el-input
|
||||
v-model="schedule.infusionLocation"
|
||||
placeholder="请输入输注地点"
|
||||
:disabled="!isScheduleEditable(schedule)"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="输注方式">
|
||||
@@ -388,6 +410,7 @@
|
||||
clearable
|
||||
filterable
|
||||
placeholder="请选择输注方式"
|
||||
:disabled="!isScheduleEditable(schedule)"
|
||||
style="width: 100%"
|
||||
>
|
||||
<el-option
|
||||
@@ -407,7 +430,7 @@
|
||||
<el-button
|
||||
type="primary"
|
||||
size="small"
|
||||
:disabled="!canAddCellProduct"
|
||||
:disabled="!canAddCellProduct || !isScheduleEditable(schedule)"
|
||||
@click="addCellProduct(sIdx)"
|
||||
>
|
||||
<el-icon><Plus /></el-icon>
|
||||
@@ -428,10 +451,8 @@
|
||||
clearable
|
||||
placeholder="选择项目"
|
||||
class="cell-product-table__control"
|
||||
:disabled="!canAddCellProduct"
|
||||
@change="
|
||||
onCellProductSelect(sIdx, pIdx, $event)
|
||||
"
|
||||
:disabled="!canAddCellProduct || !isScheduleEditable(schedule)"
|
||||
@change="onCellProductSelect(sIdx, pIdx, $event)"
|
||||
>
|
||||
<el-option
|
||||
v-for="p in getCellProductPriceOptions(sIdx, pIdx)"
|
||||
@@ -442,16 +463,8 @@
|
||||
</el-select>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="cellTypeName"
|
||||
label="细胞类型"
|
||||
min-width="100"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="specText"
|
||||
label="规格"
|
||||
min-width="100"
|
||||
/>
|
||||
<el-table-column prop="cellTypeName" label="细胞类型" min-width="100" />
|
||||
<el-table-column prop="specText" label="规格" min-width="100" />
|
||||
<el-table-column label="协议单价(元)" min-width="130" align="right">
|
||||
<template #default="{ row }">
|
||||
<span class="cell-product-price-text">
|
||||
@@ -465,6 +478,7 @@
|
||||
v-model="row.actualPrice"
|
||||
:min="0"
|
||||
:precision="2"
|
||||
:disabled="!isScheduleEditable(schedule)"
|
||||
controls-position="right"
|
||||
class="cell-product-table__control"
|
||||
/>
|
||||
@@ -475,6 +489,7 @@
|
||||
<el-input-number
|
||||
v-model="row.quantity"
|
||||
:min="1"
|
||||
:disabled="!isScheduleEditable(schedule)"
|
||||
controls-position="right"
|
||||
class="cell-product-table__control"
|
||||
/>
|
||||
@@ -485,18 +500,27 @@
|
||||
<el-input
|
||||
v-model="row.remark"
|
||||
placeholder="备注"
|
||||
:disabled="!isScheduleEditable(schedule)"
|
||||
class="cell-product-table__control"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="状态" min-width="80" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-tag size="small" type="info">{{ row.status }}</el-tag>
|
||||
<el-tag
|
||||
:type="infusionStatusTagType(normalizeInfusionStatusCode(row.status))"
|
||||
size="small"
|
||||
>
|
||||
{{
|
||||
infusionStatusLabel(normalizeInfusionStatusCode(row.status))
|
||||
}}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" min-width="60" align="center">
|
||||
<template #default="{ $index: pIdx }">
|
||||
<el-button
|
||||
v-if="isScheduleEditable(schedule)"
|
||||
link
|
||||
type="danger"
|
||||
size="small"
|
||||
@@ -622,15 +646,45 @@
|
||||
class="list-table inner-table"
|
||||
max-height="360"
|
||||
>
|
||||
<el-table-column type="index" label="序号" min-width="68" align="center" header-align="center" />
|
||||
<el-table-column prop="projectName" label="项目名称" min-width="150" show-overflow-tooltip />
|
||||
<el-table-column
|
||||
type="index"
|
||||
label="序号"
|
||||
min-width="68"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="projectName"
|
||||
label="项目名称"
|
||||
min-width="150"
|
||||
show-overflow-tooltip
|
||||
/>
|
||||
<el-table-column prop="cellType" label="细胞类型" min-width="120" show-overflow-tooltip />
|
||||
<el-table-column prop="specText" label="规格文本" min-width="150" show-overflow-tooltip />
|
||||
<el-table-column label="协议单价(元)" min-width="130" align="right" header-align="center">
|
||||
<template #default="{ row }">¥{{ formatOrderBalance(row.agreementUnitPrice) }}</template>
|
||||
<el-table-column
|
||||
label="协议单价(元)"
|
||||
min-width="130"
|
||||
align="right"
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }"
|
||||
>¥{{ formatOrderBalance(row.agreementUnitPrice) }}</template
|
||||
>
|
||||
</el-table-column>
|
||||
<el-table-column prop="currentQty" label="当前持有" min-width="100" align="center" header-align="center" />
|
||||
<el-table-column prop="totalBuyQty" label="累计购买" min-width="100" align="center" header-align="center" />
|
||||
<el-table-column
|
||||
prop="currentQty"
|
||||
label="当前持有"
|
||||
min-width="100"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="totalBuyQty"
|
||||
label="累计购买"
|
||||
min-width="100"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="totalUnsubscribeQty"
|
||||
label="累计退订"
|
||||
@@ -668,12 +722,38 @@
|
||||
class="list-table inner-table"
|
||||
max-height="360"
|
||||
>
|
||||
<el-table-column type="index" label="序号" min-width="68" align="center" header-align="center" />
|
||||
<el-table-column prop="projectName" label="项目名称" min-width="140" show-overflow-tooltip />
|
||||
<el-table-column prop="projectCode" label="项目编码" min-width="110" show-overflow-tooltip />
|
||||
<el-table-column prop="cellTypeName" label="细胞类型" min-width="110" show-overflow-tooltip />
|
||||
<el-table-column
|
||||
type="index"
|
||||
label="序号"
|
||||
min-width="68"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="projectName"
|
||||
label="项目名称"
|
||||
min-width="140"
|
||||
show-overflow-tooltip
|
||||
/>
|
||||
<el-table-column
|
||||
prop="projectCode"
|
||||
label="项目编码"
|
||||
min-width="110"
|
||||
show-overflow-tooltip
|
||||
/>
|
||||
<el-table-column
|
||||
prop="cellTypeName"
|
||||
label="细胞类型"
|
||||
min-width="110"
|
||||
show-overflow-tooltip
|
||||
/>
|
||||
<el-table-column prop="specText" label="规格" min-width="120" show-overflow-tooltip />
|
||||
<el-table-column label="协议单价(元)" min-width="130" align="right" header-align="center">
|
||||
<el-table-column
|
||||
label="协议单价(元)"
|
||||
min-width="130"
|
||||
align="right"
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }">¥{{ formatOrderBalance(row.unitPrice) }}</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
@@ -685,7 +765,17 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { FormInstance } from 'element-plus'
|
||||
import { Plus, Delete, Close, Check, Download, Document, VideoPlay, Loading, View } from '@element-plus/icons-vue'
|
||||
import {
|
||||
Plus,
|
||||
Delete,
|
||||
Close,
|
||||
Check,
|
||||
Download,
|
||||
Document,
|
||||
VideoPlay,
|
||||
Loading,
|
||||
View
|
||||
} from '@element-plus/icons-vue'
|
||||
import {
|
||||
savePilotApplication,
|
||||
fetchPilotApplicationDetail,
|
||||
@@ -702,7 +792,9 @@
|
||||
import { fetchUploadAttachment } from '@/api/business'
|
||||
import { fetchAllPagedRecords } from '@/utils/fetch-paged-list'
|
||||
import DialogFormBody from '@/components/business/dialog-form-body/index.vue'
|
||||
import SystemViewDialog, { type ViewSection } from '@/components/business/system-view-dialog/index.vue'
|
||||
import SystemViewDialog, {
|
||||
type ViewSection
|
||||
} from '@/components/business/system-view-dialog/index.vue'
|
||||
import ServiceOrderTxnTable from '@/views/pilot-service/channel-service-order/ServiceOrderTxnTable.vue'
|
||||
import {
|
||||
channelServiceOrderTypeLabel,
|
||||
@@ -714,7 +806,11 @@
|
||||
agreementStatusLabel,
|
||||
agreementStatusTagType,
|
||||
settlementTypeLabel,
|
||||
settlementTypeTagType
|
||||
settlementTypeTagType,
|
||||
infusionStatusLabel,
|
||||
infusionStatusTagType,
|
||||
scheduleStatusLabel,
|
||||
scheduleStatusTagType
|
||||
} from '@/utils/status-tag-registry'
|
||||
import { formatMoneyDisplay } from '@/utils/money-format'
|
||||
|
||||
@@ -1250,7 +1346,11 @@
|
||||
const remoteSearchCustomers = async (keyword: string) => {
|
||||
customerLoading.value = true
|
||||
try {
|
||||
const res: any = await fetchCustomerList({ page: 1, pageSize: 50, keyword: keyword || undefined })
|
||||
const res: any = await fetchCustomerList({
|
||||
page: 1,
|
||||
pageSize: 50,
|
||||
keyword: keyword || undefined
|
||||
})
|
||||
const data = res?.data ?? res
|
||||
customerOptions.value = data?.list ?? data?.records ?? []
|
||||
} catch {
|
||||
@@ -1381,7 +1481,7 @@
|
||||
fileSize: file.size,
|
||||
fileSizeDisplay: `${(file.size / 1024).toFixed(1)} KB`,
|
||||
fileType: file.type || '',
|
||||
_uploading: true,
|
||||
_uploading: true
|
||||
})
|
||||
const idx = diagnosisFileList.value.length - 1
|
||||
|
||||
@@ -1397,7 +1497,7 @@
|
||||
fileSize: res.fileSize || file.size,
|
||||
fileSizeDisplay: res.fileSizeDisplay || `${(file.size / 1024).toFixed(1)} KB`,
|
||||
fileType: res.fileType || file.type || '',
|
||||
_uploading: false,
|
||||
_uploading: false
|
||||
}
|
||||
} catch (e: any) {
|
||||
ElMessage.error(e?.message || '上传失败')
|
||||
@@ -1423,12 +1523,40 @@
|
||||
|
||||
// ── 输注方案(inline,一个申请单一个方案) ──────────────────────────────
|
||||
const infusionPlan = reactive({
|
||||
id: '' as string,
|
||||
totalCourseCount: 0,
|
||||
courseInterval: '',
|
||||
infusionStartDate: '',
|
||||
channelAgreementId: ''
|
||||
})
|
||||
|
||||
const INFUSION_STATUS_CN_TO_CODE: Record<string, string> = {
|
||||
待输注: 'dtz',
|
||||
已用户确认: 'yrqr',
|
||||
已取消: 'yqx',
|
||||
已完成: 'ywc'
|
||||
}
|
||||
|
||||
function normalizeInfusionStatusCode(raw: unknown): string {
|
||||
const s = String(raw ?? '').trim()
|
||||
if (!s) return ''
|
||||
return INFUSION_STATUS_CN_TO_CODE[s] || s
|
||||
}
|
||||
|
||||
/** 待输注 / 已用户确认:可编辑时间与细胞产品 */
|
||||
function isScheduleEditable(schedule: Record<string, any>): boolean {
|
||||
if (!isEdit.value) return true
|
||||
const st = normalizeInfusionStatusCode(schedule.infusionStatus)
|
||||
return st === 'dtz' || st === 'yrqr'
|
||||
}
|
||||
|
||||
/** 仅待输注可删疗程 */
|
||||
function canDeleteSchedule(schedule: Record<string, any>): boolean {
|
||||
if (!isEdit.value) return true
|
||||
if (!schedule.id) return true
|
||||
return normalizeInfusionStatusCode(schedule.infusionStatus) === 'dtz'
|
||||
}
|
||||
|
||||
// 输注计划列表
|
||||
let scheduleSeq = 0
|
||||
const infusionSchedules = ref<any[]>([])
|
||||
@@ -1574,6 +1702,11 @@
|
||||
}
|
||||
|
||||
const removeSchedule = (idx: number) => {
|
||||
const s = infusionSchedules.value[idx]
|
||||
if (s && !canDeleteSchedule(s)) {
|
||||
ElMessage.warning('当前疗程不可删除(仅待输注且未开始生产的计划可删)')
|
||||
return
|
||||
}
|
||||
infusionSchedules.value.splice(idx, 1)
|
||||
recalcAllScheduleTimes()
|
||||
syncTotalCourseCountWithSchedules()
|
||||
@@ -1583,6 +1716,10 @@
|
||||
const addCellProduct = (scheduleIdx: number) => {
|
||||
const s = infusionSchedules.value[scheduleIdx]
|
||||
if (!s) return
|
||||
if (!isScheduleEditable(s)) {
|
||||
ElMessage.warning('当前疗程状态不允许修改细胞产品')
|
||||
return
|
||||
}
|
||||
const usedKeys = new Set(
|
||||
(s.cellProducts || []).map((cp: any) => getCellProductProjectKey(cp)).filter(Boolean)
|
||||
)
|
||||
@@ -1605,13 +1742,17 @@
|
||||
actualPrice: 0,
|
||||
quantity: 1,
|
||||
remark: '',
|
||||
status: '待输注'
|
||||
status: 'dtz'
|
||||
})
|
||||
}
|
||||
|
||||
const removeCellProduct = (scheduleIdx: number, productIdx: number) => {
|
||||
const s = infusionSchedules.value[scheduleIdx]
|
||||
if (!s) return
|
||||
if (!isScheduleEditable(s)) {
|
||||
ElMessage.warning('当前疗程状态不允许修改细胞产品')
|
||||
return
|
||||
}
|
||||
s.cellProducts.splice(productIdx, 1)
|
||||
}
|
||||
|
||||
@@ -1701,6 +1842,7 @@
|
||||
patientForm.clinicalDiagnosis = ''
|
||||
patientForm.needCheckup = 0
|
||||
diagnosisFileList.value = []
|
||||
infusionPlan.id = ''
|
||||
infusionPlan.totalCourseCount = 0
|
||||
infusionPlan.courseInterval = ''
|
||||
infusionPlan.infusionStartDate = ''
|
||||
@@ -1775,7 +1917,7 @@
|
||||
fileSize: 0,
|
||||
fileSizeDisplay: '',
|
||||
fileType: '',
|
||||
_uploading: false,
|
||||
_uploading: false
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -1788,6 +1930,7 @@
|
||||
try {
|
||||
if (detail.infusionPlan) {
|
||||
const ip = detail.infusionPlan
|
||||
infusionPlan.id = ip.id || ''
|
||||
infusionPlan.totalCourseCount = ip.totalCourseCount || 0
|
||||
infusionPlan.courseInterval = ip.courseInterval || ''
|
||||
infusionPlan.infusionStartDate = ip.infusionStartDate || ''
|
||||
@@ -1795,12 +1938,16 @@
|
||||
}
|
||||
if (detail.infusionSchedules && Array.isArray(detail.infusionSchedules)) {
|
||||
infusionSchedules.value = detail.infusionSchedules.map((s: any, i: number) => ({
|
||||
id: s.id || '',
|
||||
_localId: ++scheduleSeq,
|
||||
infusionTime: s.infusionTime || '',
|
||||
infusionLocation: s.infusionLocation || '',
|
||||
infusionMethod: s.infusionMethod || '',
|
||||
infusionStatus: normalizeInfusionStatusCode(s.infusionStatus) || 'dtz',
|
||||
scheduleStatus: s.scheduleStatus || 'dsc',
|
||||
_expanded: i === 0,
|
||||
cellProducts: (s.cellProducts || []).map((cp: any) => ({
|
||||
id: cp.id || '',
|
||||
_localId: ++scheduleSeq,
|
||||
priceId: cp.priceId || '',
|
||||
projectCode: cp.projectCode || '',
|
||||
@@ -1811,7 +1958,7 @@
|
||||
actualPrice: cp.actualPrice ?? 0,
|
||||
quantity: cp.quantity || 1,
|
||||
remark: cp.remark || '',
|
||||
status: cp.status || '待输注'
|
||||
status: normalizeInfusionStatusCode(cp.status) || 'dtz'
|
||||
}))
|
||||
}))
|
||||
}
|
||||
@@ -1856,21 +2003,28 @@
|
||||
emergencyContact: patientForm.emergencyContact || undefined,
|
||||
emergencyPhone: patientForm.emergencyPhone || undefined,
|
||||
clinicalDiagnosis: patientForm.clinicalDiagnosis || undefined,
|
||||
diagnosisAttachments: diagnosisFileList.value.map((f) => f.fileUrl).filter(Boolean).join(',') || undefined,
|
||||
diagnosisAttachments:
|
||||
diagnosisFileList.value
|
||||
.map((f) => f.fileUrl)
|
||||
.filter(Boolean)
|
||||
.join(',') || undefined,
|
||||
needCheckup: patientForm.needCheckup
|
||||
},
|
||||
infusionPlan: {
|
||||
id: infusionPlan.id || undefined,
|
||||
totalCourseCount: infusionPlan.totalCourseCount,
|
||||
courseInterval: infusionPlan.courseInterval || undefined,
|
||||
infusionStartDate: infusionPlan.infusionStartDate || undefined,
|
||||
channelAgreementId: infusionPlan.channelAgreementId || undefined
|
||||
},
|
||||
infusionSchedules: infusionSchedules.value.map((s, idx) => ({
|
||||
id: s.id || undefined,
|
||||
sortNo: idx + 1,
|
||||
infusionTime: s.infusionTime || undefined,
|
||||
infusionLocation: s.infusionLocation || undefined,
|
||||
infusionMethod: s.infusionMethod || undefined,
|
||||
cellProducts: (s.cellProducts || []).map((cp: any) => ({
|
||||
id: cp.id || undefined,
|
||||
priceId: cp.priceId || undefined,
|
||||
projectCode: cp.projectCode || undefined,
|
||||
projectName: cp.projectName || undefined,
|
||||
@@ -1879,8 +2033,7 @@
|
||||
agreementPrice: cp.agreementPrice ?? undefined,
|
||||
actualPrice: cp.actualPrice ?? undefined,
|
||||
quantity: cp.quantity || 1,
|
||||
remark: cp.remark || undefined,
|
||||
status: cp.status || '待输注'
|
||||
remark: cp.remark || undefined
|
||||
}))
|
||||
}))
|
||||
}
|
||||
@@ -2370,6 +2523,10 @@
|
||||
color: #1e293b;
|
||||
}
|
||||
|
||||
.schedule-status-tag {
|
||||
margin-left: 8px;
|
||||
}
|
||||
|
||||
.schedule-delete-btn {
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -162,12 +162,12 @@
|
||||
<template #default="{ row }">{{ row.patientName || '-' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="infusionPlanCount"
|
||||
label="输注方案数"
|
||||
prop="infusionScheduleCount"
|
||||
label="输注计划数"
|
||||
min-width="100"
|
||||
align="center"
|
||||
>
|
||||
<template #default="{ row }">{{ row.infusionPlanCount ?? 0 }}</template>
|
||||
<template #default="{ row }">{{ row.infusionScheduleCount ?? 0 }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="applicationStatus" label="状态" min-width="100" align="center">
|
||||
<template #default="{ row }">
|
||||
|
||||
@@ -22,8 +22,12 @@
|
||||
<div class="filter-actions-row">
|
||||
<span class="filter-hint">按体系名称、编码模糊查询。</span>
|
||||
<div class="filter-actions-end">
|
||||
<el-button class="btn-ghost" @click="handleReset"><el-icon><RefreshLeft /></el-icon>重置</el-button>
|
||||
<el-button class="btn-main" type="primary" @click="handleSearch"><el-icon><Search /></el-icon>搜索</el-button>
|
||||
<el-button class="btn-ghost" @click="handleReset"
|
||||
><el-icon><RefreshLeft /></el-icon>重置</el-button
|
||||
>
|
||||
<el-button class="btn-main" type="primary" @click="handleSearch"
|
||||
><el-icon><Search /></el-icon>搜索</el-button
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</el-form>
|
||||
@@ -82,14 +86,26 @@
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column prop="status" label="状态" min-width="80" align="center" header-align="center">
|
||||
<el-table-column
|
||||
prop="status"
|
||||
label="状态"
|
||||
min-width="80"
|
||||
align="center"
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="String(row.status) === '1' ? 'success' : 'danger'" size="small">
|
||||
{{ String(row.status) === '1' ? '正常' : '停用' }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="remark" label="备注" min-width="150" align="center" header-align="center" />
|
||||
<el-table-column
|
||||
prop="remark"
|
||||
label="备注"
|
||||
min-width="150"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="createTime"
|
||||
label="创建时间"
|
||||
@@ -97,7 +113,13 @@
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column label="操作" min-width="320" fixed="right" align="center" header-align="center">
|
||||
<el-table-column
|
||||
label="操作"
|
||||
min-width="320"
|
||||
fixed="right"
|
||||
align="center"
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<el-button class="btn-op-item" @click="handleView(row)"
|
||||
><el-icon><View /></el-icon>查看</el-button
|
||||
@@ -166,24 +188,55 @@
|
||||
<div class="detail-section">
|
||||
<div class="detail-section-head">
|
||||
<h3 class="detail-section-title">价格明细</h3>
|
||||
<el-button class="btn-main" type="primary" size="small" @click="handleOpenPriceSelector">
|
||||
<el-button
|
||||
class="btn-main"
|
||||
type="primary"
|
||||
size="small"
|
||||
@click="handleOpenPriceSelector"
|
||||
>
|
||||
<el-icon><Plus /></el-icon>
|
||||
从价格体系添加
|
||||
</el-button>
|
||||
</div>
|
||||
<el-table :data="detailItems" size="small" class="detail-table" max-height="320">
|
||||
<el-table-column type="index" label="序号" min-width="55" align="center" header-align="center" />
|
||||
<el-table-column prop="projectName" label="项目名称" min-width="120" align="center" header-align="center" />
|
||||
<el-table-column prop="projectCode" label="项目编码" min-width="100" align="center" header-align="center" />
|
||||
<el-table-column prop="cellTypeName" label="细胞类型" min-width="100" align="center" header-align="center" />
|
||||
<el-table-column prop="specText" label="规格" min-width="100" align="center" header-align="center" />
|
||||
<el-table-column
|
||||
type="index"
|
||||
label="序号"
|
||||
min-width="55"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="projectName"
|
||||
label="项目名称"
|
||||
min-width="120"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="projectCode"
|
||||
label="项目编码"
|
||||
min-width="100"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="cellTypeName"
|
||||
label="细胞类型"
|
||||
min-width="100"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="specText"
|
||||
label="规格"
|
||||
min-width="100"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column label="单价(元)" min-width="100" align="center" header-align="center">
|
||||
<template #default="{ row }">
|
||||
<AmountInput
|
||||
v-model="row.unitPrice"
|
||||
:min="0"
|
||||
placeholder="请输入单价"
|
||||
/>
|
||||
<AmountInput v-model="row.unitPrice" :min="0" placeholder="请输入单价" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="备注" min-width="140" align="center" header-align="center">
|
||||
@@ -191,9 +244,21 @@
|
||||
<el-input v-model="row.remark" placeholder="备注" clearable />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" min-width="80" fixed="right" align="center" header-align="center">
|
||||
<el-table-column
|
||||
label="操作"
|
||||
min-width="80"
|
||||
fixed="right"
|
||||
align="center"
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ $index }">
|
||||
<el-button class="btn-op-item" type="danger" link size="small" @click="detailItems.splice($index, 1)">
|
||||
<el-button
|
||||
class="btn-op-item"
|
||||
type="danger"
|
||||
link
|
||||
size="small"
|
||||
@click="detailItems.splice($index, 1)"
|
||||
>
|
||||
<el-icon><Delete /></el-icon>删除
|
||||
</el-button>
|
||||
</template>
|
||||
@@ -239,16 +304,46 @@
|
||||
@selection-change="handlePriceSelectorChange"
|
||||
>
|
||||
<el-table-column type="selection" min-width="55" align="center" header-align="center" />
|
||||
<el-table-column prop="projectName" label="项目名称" min-width="120" align="center" header-align="center" />
|
||||
<el-table-column prop="projectCode" label="项目编码" min-width="100" align="center" header-align="center" />
|
||||
<el-table-column prop="cellTypeName" label="细胞类型" min-width="100" align="center" header-align="center" />
|
||||
<el-table-column prop="specText" label="规格" min-width="100" align="center" header-align="center" />
|
||||
<el-table-column
|
||||
prop="projectName"
|
||||
label="项目名称"
|
||||
min-width="120"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="projectCode"
|
||||
label="项目编码"
|
||||
min-width="100"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="cellTypeName"
|
||||
label="细胞类型"
|
||||
min-width="100"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="specText"
|
||||
label="规格"
|
||||
min-width="100"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column label="单价" min-width="80" align="center" header-align="center">
|
||||
<template #default="{ row }">
|
||||
{{ row.unitPrice != null ? `${formatMoneyDisplay(row.unitPrice)} 元` : '-' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="remark" label="备注" min-width="120" align="center" header-align="center" />
|
||||
<el-table-column
|
||||
prop="remark"
|
||||
label="备注"
|
||||
min-width="120"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
</el-table>
|
||||
<template #footer>
|
||||
<el-button class="btn-ghost" @click="priceSelectorVisible = false">取消</el-button>
|
||||
@@ -278,11 +373,21 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, onMounted, computed, nextTick } from 'vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { Plus, Close, Check, View, Edit, Delete, ScaleToOriginal, RefreshLeft, Search } from '@element-plus/icons-vue'
|
||||
import SystemViewDialog from '@/components/business/system-view-dialog/index.vue'
|
||||
import AmountInput from '@/components/common/amount-input/index.vue'
|
||||
import { formatMoneyDisplay } from '@/utils/money-format'
|
||||
import { withFullscreenLoading, fullscreenLoadingText } from '@/utils/fullscreen-loading'
|
||||
import {
|
||||
Plus,
|
||||
Close,
|
||||
Check,
|
||||
View,
|
||||
Edit,
|
||||
Delete,
|
||||
ScaleToOriginal,
|
||||
RefreshLeft,
|
||||
Search
|
||||
} from '@element-plus/icons-vue'
|
||||
import SystemViewDialog from '@/components/business/system-view-dialog/index.vue'
|
||||
import AmountInput from '@/components/common/amount-input/index.vue'
|
||||
import { formatMoneyDisplay } from '@/utils/money-format'
|
||||
import { withFullscreenLoading, fullscreenLoadingText } from '@/utils/fullscreen-loading'
|
||||
import {
|
||||
fetchProjectSystemList,
|
||||
fetchCreateProjectSystem,
|
||||
@@ -461,7 +566,9 @@ import { withFullscreenLoading, fullscreenLoadingText } from '@/utils/fullscreen
|
||||
// load existing detail items
|
||||
try {
|
||||
const res = await fetchProjectSystemItems(row.id)
|
||||
detailItems.value = ((res as any)?.list || (res as any) || []).map((item: any) => ({ ...item }))
|
||||
detailItems.value = ((res as any)?.list || (res as any) || []).map((item: any) => ({
|
||||
...item
|
||||
}))
|
||||
} catch {
|
||||
detailItems.value = []
|
||||
}
|
||||
@@ -545,7 +652,9 @@ import { withFullscreenLoading, fullscreenLoadingText } from '@/utils/fullscreen
|
||||
// --- Delete ---
|
||||
async function handleDelete(row: any) {
|
||||
try {
|
||||
await ElMessageBox.confirm('确定要删除该体系吗?删除后关联的价格明细也将清除。', '提示', { type: 'warning' })
|
||||
await ElMessageBox.confirm('确定要删除该体系吗?删除后关联的价格明细也将清除。', '提示', {
|
||||
type: 'warning'
|
||||
})
|
||||
} catch {
|
||||
return
|
||||
}
|
||||
@@ -560,7 +669,9 @@ import { withFullscreenLoading, fullscreenLoadingText } from '@/utils/fullscreen
|
||||
async function handleBatchDelete() {
|
||||
const ids = selectedRows.value.map((r) => r.id)
|
||||
if (!ids.length) return
|
||||
await ElMessageBox.confirm(`确定删除选中的 ${ids.length} 条记录吗?`, '提示', { type: 'warning' })
|
||||
await ElMessageBox.confirm(`确定删除选中的 ${ids.length} 条记录吗?`, '提示', {
|
||||
type: 'warning'
|
||||
})
|
||||
await withFullscreenLoading(async () => {
|
||||
for (const id of ids) {
|
||||
await fetchDeleteProjectSystem(id)
|
||||
|
||||
@@ -48,7 +48,14 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, watch, nextTick, computed, provide } from 'vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { VueFlow, useVueFlow, type Edge, type Node, type NodeMouseEvent, type EdgeMouseEvent } from '@vue-flow/core'
|
||||
import {
|
||||
VueFlow,
|
||||
useVueFlow,
|
||||
type Edge,
|
||||
type Node,
|
||||
type NodeMouseEvent,
|
||||
type EdgeMouseEvent
|
||||
} from '@vue-flow/core'
|
||||
import '@vue-flow/core/dist/style.css'
|
||||
import '@vue-flow/core/dist/theme-default.css'
|
||||
|
||||
@@ -58,11 +65,16 @@
|
||||
import ChainNodeCard from './source-chain/ChainNodeCard.vue'
|
||||
import ChainLinkEdge from './source-chain/ChainLinkEdge.vue'
|
||||
import SampleCustomerViewDialog from './source-chain/SampleCustomerViewDialog.vue'
|
||||
import type { ChainApiData, ChainEdgeData, ChainNode, ChainNodeData } from './source-chain/chain-types'
|
||||
import type {
|
||||
ChainApiData,
|
||||
ChainEdgeData,
|
||||
ChainNode,
|
||||
ChainNodeData
|
||||
} from './source-chain/chain-types'
|
||||
import {
|
||||
chainApiToElements,
|
||||
hasChainGraphContent,
|
||||
layoutChainElements,
|
||||
layoutChainElements
|
||||
} from './source-chain/chainToFlow'
|
||||
import { chainDefaultEdgeOptions } from './source-chain/chainFlowTheme'
|
||||
import { CHAIN_OPEN_TASK_KEY } from './source-chain/chainInject'
|
||||
@@ -87,9 +99,7 @@
|
||||
const sampleViewVisible = ref(false)
|
||||
const sampleViewNode = ref<ChainNode | null>(null)
|
||||
|
||||
const showFlow = computed(
|
||||
() => chainData.value && hasChainGraphContent(chainData.value)
|
||||
)
|
||||
const showFlow = computed(() => chainData.value && hasChainGraphContent(chainData.value))
|
||||
|
||||
const defaultEdgeOptions = chainDefaultEdgeOptions
|
||||
|
||||
@@ -149,9 +159,7 @@
|
||||
async function openSampleCustomer(chainNode: ChainNode) {
|
||||
let node = chainNode
|
||||
const c = chainNode.customer
|
||||
const hasCustomer =
|
||||
c &&
|
||||
(c.motherName || c.motherPhone || c.motherIdCard || c.newbornName)
|
||||
const hasCustomer = c && (c.motherName || c.motherPhone || c.motherIdCard || c.newbornName)
|
||||
|
||||
if (!hasCustomer && chainNode.collectionProtocolId) {
|
||||
try {
|
||||
@@ -167,8 +175,8 @@
|
||||
motherName: pc.motherName || '',
|
||||
motherPhone: pc.motherPhone || '',
|
||||
motherIdCard: pc.motherIdCard || '',
|
||||
newbornName: pc.newbornName || '',
|
||||
},
|
||||
newbornName: pc.newbornName || ''
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
@@ -217,7 +225,7 @@
|
||||
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: 'SourceChainGraph',
|
||||
name: 'SourceChainGraph'
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
@@ -44,12 +44,7 @@
|
||||
clearable
|
||||
style="width: 100%"
|
||||
>
|
||||
<el-option
|
||||
v-for="p in passageOptions"
|
||||
:key="p"
|
||||
:label="p"
|
||||
:value="p"
|
||||
/>
|
||||
<el-option v-for="p in passageOptions" :key="p" :label="p" :value="p" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="来源类型">
|
||||
@@ -108,12 +103,7 @@
|
||||
@selection-change="handleSelectionChange"
|
||||
@row-dblclick="handleView"
|
||||
>
|
||||
<el-table-column
|
||||
type="selection"
|
||||
min-width="55"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column type="selection" min-width="55" align="center" header-align="center" />
|
||||
<el-table-column
|
||||
type="index"
|
||||
label="序号"
|
||||
@@ -122,60 +112,25 @@
|
||||
header-align="center"
|
||||
:index="(index) => (pagination.page - 1) * pagination.pageSize + index + 1"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="productCode"
|
||||
label="产品编码"
|
||||
min-width="180"
|
||||
align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="productName"
|
||||
label="产品名称"
|
||||
min-width="160"
|
||||
align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="productTypeName"
|
||||
label="产品类型"
|
||||
min-width="120"
|
||||
align="center"
|
||||
>
|
||||
<el-table-column prop="productCode" label="产品编码" min-width="180" align="center" />
|
||||
<el-table-column prop="productName" label="产品名称" min-width="160" align="center" />
|
||||
<el-table-column prop="productTypeName" label="产品类型" min-width="120" align="center">
|
||||
<template #default="{ row }">
|
||||
{{ row.productTypeName || '—' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="specName"
|
||||
label="规格"
|
||||
min-width="120"
|
||||
align="center"
|
||||
>
|
||||
<el-table-column prop="specName" label="规格" min-width="120" align="center">
|
||||
<template #default="{ row }">
|
||||
{{ row.specName || '—' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="passage"
|
||||
label="代次"
|
||||
min-width="80"
|
||||
align="center"
|
||||
>
|
||||
<el-table-column prop="passage" label="代次" min-width="80" align="center">
|
||||
<template #default="{ row }">
|
||||
{{ row.passage || '—' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="batchNo"
|
||||
label="批次号"
|
||||
min-width="140"
|
||||
align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="storageSlotName"
|
||||
label="储物格位置"
|
||||
min-width="140"
|
||||
align="center"
|
||||
>
|
||||
<el-table-column prop="batchNo" label="批次号" min-width="140" align="center" />
|
||||
<el-table-column prop="storageSlotName" label="储物格位置" min-width="140" align="center">
|
||||
<template #default="{ row }">
|
||||
{{ row.storageSlotName || '—' }}
|
||||
</template>
|
||||
@@ -190,27 +145,14 @@
|
||||
{{ row.expectedCellCount ?? '—' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="viability"
|
||||
label="活性(%)"
|
||||
min-width="90"
|
||||
align="center"
|
||||
>
|
||||
<el-table-column prop="viability" label="活性(%)" min-width="90" align="center">
|
||||
<template #default="{ row }">
|
||||
{{ row.viability != null ? row.viability : '—' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="productStatus"
|
||||
label="状态"
|
||||
min-width="100"
|
||||
align="center"
|
||||
>
|
||||
<el-table-column prop="productStatus" label="状态" min-width="100" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-tag
|
||||
:type="productStatusTag(row.productStatus).type"
|
||||
size="small"
|
||||
>
|
||||
<el-tag :type="productStatusTag(row.productStatus).type" size="small">
|
||||
{{ productStatusTag(row.productStatus).label }}
|
||||
</el-tag>
|
||||
</template>
|
||||
@@ -227,56 +169,27 @@
|
||||
<span v-else>—</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
label="来源协议号"
|
||||
min-width="160"
|
||||
align="center"
|
||||
show-overflow-tooltip
|
||||
>
|
||||
<el-table-column label="来源协议号" min-width="160" align="center" show-overflow-tooltip>
|
||||
<template #default="{ row }">{{ row.sourceProtocolNo || '—' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
label="来源工单号"
|
||||
min-width="140"
|
||||
align="center"
|
||||
show-overflow-tooltip
|
||||
>
|
||||
<el-table-column label="来源工单号" min-width="140" align="center" show-overflow-tooltip>
|
||||
<template #default="{ row }">{{ row.sourceWorkOrderNo || '—' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
label="来源任务号"
|
||||
min-width="140"
|
||||
align="center"
|
||||
show-overflow-tooltip
|
||||
>
|
||||
<el-table-column label="来源任务号" min-width="140" align="center" show-overflow-tooltip>
|
||||
<template #default="{ row }">{{ row.sourceTaskNo || '—' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="expiryDate"
|
||||
label="有效期"
|
||||
min-width="120"
|
||||
align="center"
|
||||
>
|
||||
<el-table-column prop="expiryDate" label="有效期" min-width="120" align="center">
|
||||
<template #default="{ row }">
|
||||
{{ row.expiryDate || '—' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
label="操作"
|
||||
min-width="260"
|
||||
fixed="right"
|
||||
align="center"
|
||||
>
|
||||
<el-table-column label="操作" min-width="260" fixed="right" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-button class="btn-op-item" @click="handleView(row)">
|
||||
<el-icon><View /></el-icon>查看
|
||||
</el-button>
|
||||
<el-button class="btn-op-item primary" @click="handleEdit(row)">
|
||||
编辑
|
||||
</el-button>
|
||||
<el-button class="btn-op-item" @click="handleDelete(row)">
|
||||
删除
|
||||
</el-button>
|
||||
<el-button class="btn-op-item primary" @click="handleEdit(row)"> 编辑 </el-button>
|
||||
<el-button class="btn-op-item" @click="handleDelete(row)"> 删除 </el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
@@ -361,12 +274,7 @@
|
||||
clearable
|
||||
style="width: 100%"
|
||||
>
|
||||
<el-option
|
||||
v-for="p in passageOptions"
|
||||
:key="p"
|
||||
:label="p"
|
||||
:value="p"
|
||||
/>
|
||||
<el-option v-for="p in passageOptions" :key="p" :label="p" :value="p" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="批次号" prop="batchNo">
|
||||
@@ -415,10 +323,7 @@
|
||||
style="width: 100%"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
:label="isEdit ? '数量' : '入库份数'"
|
||||
prop="quantity"
|
||||
>
|
||||
<el-form-item :label="isEdit ? '数量' : '入库份数'" prop="quantity">
|
||||
<el-input-number
|
||||
v-model="formData.quantity"
|
||||
:min="isEdit ? 0 : 1"
|
||||
@@ -537,12 +442,7 @@
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="备注" class="form-col-span-2">
|
||||
<el-input
|
||||
v-model="formData.remark"
|
||||
type="textarea"
|
||||
:rows="3"
|
||||
placeholder="可选"
|
||||
/>
|
||||
<el-input v-model="formData.remark" type="textarea" :rows="3" placeholder="可选" />
|
||||
</el-form-item>
|
||||
<div v-if="!isEdit" class="product-lines-section">
|
||||
<div class="product-lines-head">
|
||||
@@ -552,12 +452,7 @@
|
||||
>
|
||||
</div>
|
||||
<el-table :data="productLines" size="small" class="product-lines-table">
|
||||
<el-table-column
|
||||
type="index"
|
||||
label="序号"
|
||||
min-width="64"
|
||||
align="center"
|
||||
/>
|
||||
<el-table-column type="index" label="序号" min-width="64" align="center" />
|
||||
<el-table-column label="产品编码" min-width="200">
|
||||
<template #default="{ row }">
|
||||
<el-input
|
||||
@@ -594,12 +489,7 @@
|
||||
<el-icon><Close /></el-icon>
|
||||
取消
|
||||
</el-button>
|
||||
<el-button
|
||||
class="btn-main"
|
||||
type="primary"
|
||||
:loading="submitLoading"
|
||||
@click="handleSubmit"
|
||||
>
|
||||
<el-button class="btn-main" type="primary" :loading="submitLoading" @click="handleSubmit">
|
||||
<el-icon><Check /></el-icon>
|
||||
确定
|
||||
</el-button>
|
||||
@@ -686,7 +576,7 @@
|
||||
Box,
|
||||
Delete,
|
||||
Download,
|
||||
Loading,
|
||||
Loading
|
||||
} from '@element-plus/icons-vue'
|
||||
import SystemViewDialog from '@/components/business/system-view-dialog/index.vue'
|
||||
import type { ViewSection } from '@/components/business/system-view-dialog/index.vue'
|
||||
@@ -698,14 +588,14 @@
|
||||
fetchCreateCellProduct,
|
||||
fetchUpdateCellProduct,
|
||||
fetchDeleteCellProduct,
|
||||
fetchNextCellProductCode,
|
||||
fetchNextCellProductCode
|
||||
} from '@/api/cell-product'
|
||||
import {
|
||||
fetchCellProductTypeList,
|
||||
fetchCellProductSpecList,
|
||||
fetchStorageFacilityList,
|
||||
fetchStorageSlotList,
|
||||
fetchItemUnitList,
|
||||
fetchItemUnitList
|
||||
} from '@/api/admin'
|
||||
import { loadStorageSlotSelectOptions } from '@/utils/storage-slot-options'
|
||||
import { fetchUploadAttachment } from '@/api/business'
|
||||
@@ -742,7 +632,7 @@
|
||||
sold: { label: '已售卖', type: 'warning' },
|
||||
used: { label: '已使用', type: 'info' },
|
||||
voided: { label: '已作废', type: 'danger' },
|
||||
thawed: { label: '已复苏', type: 'info' },
|
||||
thawed: { label: '已复苏', type: 'info' }
|
||||
}
|
||||
return map[status] || { label: status, type: 'info' }
|
||||
}
|
||||
@@ -788,7 +678,7 @@
|
||||
{ label: '已复苏', value: 'thawed' },
|
||||
{ label: '已售卖', value: 'sold' },
|
||||
{ label: '已使用', value: 'used' },
|
||||
{ label: '已作废', value: 'voided' },
|
||||
{ label: '已作废', value: 'voided' }
|
||||
]
|
||||
|
||||
function mapUnitOption(u: { unitName?: string; unitCode?: string }) {
|
||||
@@ -796,7 +686,7 @@
|
||||
const code = (u.unitCode || '').trim()
|
||||
return {
|
||||
label: code ? `${name}(${code})` : name,
|
||||
value: name,
|
||||
value: name
|
||||
}
|
||||
}
|
||||
|
||||
@@ -814,7 +704,7 @@
|
||||
const res = await fetchItemUnitList({
|
||||
page: 1,
|
||||
pageSize: 100,
|
||||
keyword: query?.trim() || undefined,
|
||||
keyword: query?.trim() || undefined
|
||||
})
|
||||
unitOptions.value = (res.list || []).map(mapUnitOption).filter((o) => o.value)
|
||||
} catch {
|
||||
@@ -841,7 +731,7 @@
|
||||
expiryDate: '',
|
||||
productStatus: 'frozen',
|
||||
productImages: '',
|
||||
remark: '',
|
||||
remark: ''
|
||||
})
|
||||
|
||||
const productLines = ref<ProductLine[]>([{ productCode: '', storageSlotId: '' }])
|
||||
@@ -849,7 +739,7 @@
|
||||
const formRules = {
|
||||
productCode: [{ required: true, message: '请输入产品编码', trigger: 'blur' }],
|
||||
productName: [{ required: true, message: '请输入产品名称', trigger: 'blur' }],
|
||||
batchNo: [{ required: true, message: '请输入批次号', trigger: 'blur' }],
|
||||
batchNo: [{ required: true, message: '请输入批次号', trigger: 'blur' }]
|
||||
}
|
||||
|
||||
function createQuantityValue() {
|
||||
@@ -889,7 +779,7 @@
|
||||
count > 1
|
||||
? buildLineProductCode(formData.productCode, i, count)
|
||||
: prev[i]?.productCode || formData.productCode || '',
|
||||
storageSlotId: prev[i]?.storageSlotId || '',
|
||||
storageSlotId: prev[i]?.storageSlotId || ''
|
||||
})
|
||||
}
|
||||
productLines.value = lines.length ? lines : [{ productCode: '', storageSlotId: '' }]
|
||||
@@ -961,11 +851,11 @@
|
||||
{ label: '储物格位置', value: row.storageSlotName || '—' },
|
||||
{
|
||||
label: '预计细胞数量(亿)',
|
||||
value: row.expectedCellCount != null ? String(row.expectedCellCount) : '—',
|
||||
value: row.expectedCellCount != null ? String(row.expectedCellCount) : '—'
|
||||
},
|
||||
{
|
||||
label: '活性(%)',
|
||||
value: row.viability != null ? String(row.viability) : '—',
|
||||
value: row.viability != null ? String(row.viability) : '—'
|
||||
},
|
||||
{ label: '数量', value: row.quantity != null ? String(row.quantity) : '—' },
|
||||
{ label: '单位', value: row.unit || '—' },
|
||||
@@ -980,23 +870,23 @@
|
||||
| 'danger'
|
||||
| 'info'
|
||||
| ''
|
||||
| 'primary',
|
||||
| 'primary'
|
||||
},
|
||||
{ label: '备注', value: row.remark || '—', span: true },
|
||||
],
|
||||
{ label: '备注', value: row.remark || '—', span: true }
|
||||
]
|
||||
},
|
||||
{
|
||||
title: '产品图片',
|
||||
titleStyle: 'field',
|
||||
slot: 'images',
|
||||
fields: [],
|
||||
fields: []
|
||||
},
|
||||
{
|
||||
title: '来源链路',
|
||||
titleStyle: 'field' as const,
|
||||
slot: 'sourceChain',
|
||||
fields: [],
|
||||
},
|
||||
fields: []
|
||||
}
|
||||
]
|
||||
})
|
||||
|
||||
@@ -1010,7 +900,7 @@
|
||||
keyword: searchForm.keyword || undefined,
|
||||
productStatus: searchForm.productStatus || undefined,
|
||||
passage: searchForm.passage || undefined,
|
||||
sourceType: searchForm.sourceType || undefined,
|
||||
sourceType: searchForm.sourceType || undefined
|
||||
})
|
||||
tableData.value = res.list || []
|
||||
pagination.total = res.total ?? 0
|
||||
@@ -1020,10 +910,7 @@
|
||||
}
|
||||
|
||||
async function loadStorageSlotOptions(facilities: any[]) {
|
||||
storageSlotOptions.value = await loadStorageSlotSelectOptions(
|
||||
facilities,
|
||||
fetchStorageSlotList
|
||||
)
|
||||
storageSlotOptions.value = await loadStorageSlotSelectOptions(facilities, fetchStorageSlotList)
|
||||
}
|
||||
|
||||
async function loadDropdownData() {
|
||||
@@ -1031,7 +918,7 @@
|
||||
const [typeRes, specRes, facilityRes] = await Promise.all([
|
||||
fetchCellProductTypeList({ page: 1, pageSize: 100 }),
|
||||
fetchCellProductSpecList({ page: 1, pageSize: 100 }),
|
||||
fetchStorageFacilityList(),
|
||||
fetchStorageFacilityList()
|
||||
])
|
||||
productTypeOptions.value = typeRes?.list || typeRes || []
|
||||
specOptions.value = specRes?.list || specRes || []
|
||||
@@ -1070,7 +957,7 @@
|
||||
.filter(Boolean)
|
||||
.map((url, index) => ({
|
||||
fileUrl: url,
|
||||
fileName: fileNameFromUrl(url, index),
|
||||
fileName: fileNameFromUrl(url, index)
|
||||
}))
|
||||
})
|
||||
|
||||
@@ -1093,7 +980,7 @@
|
||||
id: `existing_${i}`,
|
||||
fileName: fileNameFromUrl(url, i),
|
||||
fileUrl: url,
|
||||
fileType: 'image/',
|
||||
fileType: 'image/'
|
||||
} as ProductImageFile
|
||||
})
|
||||
.filter((f): f is ProductImageFile => Boolean(f))
|
||||
@@ -1105,15 +992,13 @@
|
||||
const file = uploadFile.raw || uploadFile
|
||||
if (!file) return
|
||||
|
||||
|
||||
|
||||
const tempId = `temp_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`
|
||||
productImageFiles.value.push({
|
||||
id: tempId,
|
||||
fileName: file.name,
|
||||
fileUrl: '',
|
||||
fileType: file.type || 'image/',
|
||||
_uploading: true,
|
||||
_uploading: true
|
||||
})
|
||||
const idx = productImageFiles.value.length - 1
|
||||
|
||||
@@ -1128,7 +1013,7 @@
|
||||
fileName: file.name || res.fileName || fileNameFromUrl(res.fileUrl, idx),
|
||||
fileUrl: res.fileUrl,
|
||||
fileType: res.fileType || file.type || 'image/',
|
||||
_uploading: false,
|
||||
_uploading: false
|
||||
}
|
||||
syncImagesToForm()
|
||||
} catch {
|
||||
@@ -1247,7 +1132,7 @@
|
||||
unit: formData.unit?.trim() || undefined,
|
||||
expiryDate: formData.expiryDate || undefined,
|
||||
productImages: formData.productImages?.trim() || undefined,
|
||||
remark: formData.remark?.trim() || undefined,
|
||||
remark: formData.remark?.trim() || undefined
|
||||
}
|
||||
// 仅「已冻存」可变更状态;生产中/已复苏等系统态勿提交,避免后端校验失败
|
||||
const editableStatuses = ['frozen', 'sold', 'used', 'voided']
|
||||
@@ -1258,14 +1143,14 @@
|
||||
await fetchUpdateCellProduct(editingId.value, {
|
||||
...shared,
|
||||
storageSlotId: formData.storageSlotId || undefined,
|
||||
quantity: formData.quantity,
|
||||
quantity: formData.quantity
|
||||
})
|
||||
ElMessage.success('更新成功')
|
||||
} else {
|
||||
const qty = createQuantityValue()
|
||||
const items = productLines.value.map((line) => ({
|
||||
productCode: line.productCode.trim(),
|
||||
storageSlotId: line.storageSlotId,
|
||||
storageSlotId: line.storageSlotId
|
||||
}))
|
||||
const res = await fetchCreateCellProduct({ ...shared, items })
|
||||
const n = (res as { count?: number })?.count ?? qty
|
||||
@@ -1308,7 +1193,7 @@
|
||||
const ids = selectedRows.value.map((r) => r.id)
|
||||
if (!ids.length) return
|
||||
await ElMessageBox.confirm(`确定删除选中的 ${ids.length} 个产品吗?`, '提示', {
|
||||
type: 'warning',
|
||||
type: 'warning'
|
||||
})
|
||||
await withFullscreenLoading(async () => {
|
||||
for (const id of ids) {
|
||||
|
||||
@@ -37,7 +37,7 @@
|
||||
EdgeLabelRenderer,
|
||||
getBezierPath,
|
||||
getSmoothStepPath,
|
||||
type EdgeProps,
|
||||
type EdgeProps
|
||||
} from '@vue-flow/core'
|
||||
import type { ChainEdgeData } from './chain-types'
|
||||
import { CHAIN_OPEN_TASK_KEY } from './chainInject'
|
||||
@@ -54,7 +54,7 @@
|
||||
sourcePosition: props.sourcePosition,
|
||||
targetX: props.targetX,
|
||||
targetY: props.targetY,
|
||||
targetPosition: props.targetPosition,
|
||||
targetPosition: props.targetPosition
|
||||
}))
|
||||
|
||||
const bezierCurvature = computed(() => {
|
||||
@@ -70,12 +70,12 @@
|
||||
if (props.data?.curved) {
|
||||
return getBezierPath({
|
||||
...pathInput.value,
|
||||
curvature: bezierCurvature.value,
|
||||
curvature: bezierCurvature.value
|
||||
})
|
||||
}
|
||||
return getSmoothStepPath({
|
||||
...pathInput.value,
|
||||
borderRadius: 8,
|
||||
borderRadius: 8
|
||||
})
|
||||
})
|
||||
|
||||
@@ -88,7 +88,7 @@
|
||||
const hasLabel = computed(() => Boolean(workOrderLabel.value || taskLabel.value))
|
||||
|
||||
const labelStyle = computed(() => ({
|
||||
transform: `translate(-50%, -50%) translate(${labelX.value}px, ${labelY.value}px)`,
|
||||
transform: `translate(-50%, -50%) translate(${labelX.value}px, ${labelY.value}px)`
|
||||
}))
|
||||
|
||||
function handleDblClick() {
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
:class="{
|
||||
'is-sample': isSample,
|
||||
'is-product': isProduct && !isCurrent,
|
||||
'is-current': isCurrent,
|
||||
'is-current': isCurrent
|
||||
}"
|
||||
>
|
||||
<Handle type="target" :position="Position.Left" class="chain-handle" />
|
||||
@@ -59,9 +59,7 @@
|
||||
const isSample = computed(() => node.value.nodeType === 'sample')
|
||||
const isProduct = computed(() => node.value.nodeType === 'cell_product')
|
||||
|
||||
const hintTitle = computed(() =>
|
||||
isSample.value ? '双击查看客户信息' : '双击查看产品详情'
|
||||
)
|
||||
const hintTitle = computed(() => (isSample.value ? '双击查看客户信息' : '双击查看产品详情'))
|
||||
|
||||
const statusTag = computed(() => {
|
||||
const map: Record<string, { label: string; type: 'success' | 'warning' | 'info' | 'danger' }> =
|
||||
@@ -71,7 +69,7 @@
|
||||
sold: { label: '已售卖', type: 'warning' },
|
||||
used: { label: '已使用', type: 'info' },
|
||||
voided: { label: '已作废', type: 'danger' },
|
||||
thawed: { label: '已复苏', type: 'info' },
|
||||
thawed: { label: '已复苏', type: 'info' }
|
||||
}
|
||||
const s = node.value.productStatus || ''
|
||||
return map[s] || { label: s, type: 'info' as const }
|
||||
|
||||
+4
-4
@@ -32,7 +32,7 @@
|
||||
|
||||
const innerVisible = computed({
|
||||
get: () => props.visible,
|
||||
set: (val) => emit('update:visible', val),
|
||||
set: (val) => emit('update:visible', val)
|
||||
})
|
||||
|
||||
const customer = computed(() => props.sample?.customer || {})
|
||||
@@ -53,9 +53,9 @@
|
||||
{ label: '母亲证件号', value: c.motherIdCard || '—' },
|
||||
{ label: '新生儿姓名', value: c.newbornName || '—' },
|
||||
{ label: '样本编码', value: props.sample?.sampleCode || '—' },
|
||||
{ label: '协议号', value: props.sample?.protocolNo || '—' },
|
||||
],
|
||||
},
|
||||
{ label: '协议号', value: props.sample?.protocolNo || '—' }
|
||||
]
|
||||
}
|
||||
]
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -8,11 +8,11 @@ export const chainEdgeMarkerEnd = {
|
||||
type: MarkerType.ArrowClosed,
|
||||
color: CHAIN_EDGE_COLOR,
|
||||
width: 12,
|
||||
height: 12,
|
||||
height: 12
|
||||
}
|
||||
|
||||
export const chainDefaultEdgeOptions = {
|
||||
type: 'chainLink',
|
||||
markerEnd: chainEdgeMarkerEnd,
|
||||
interactionWidth: 28,
|
||||
interactionWidth: 28
|
||||
}
|
||||
@@ -1,9 +1,8 @@
|
||||
import type { InjectionKey } from 'vue'
|
||||
import type { ChainEdgeData, ChainNode } from './chain-types'
|
||||
|
||||
export const CHAIN_OPEN_TASK_KEY: InjectionKey<
|
||||
(taskId: string, taskName?: string) => void
|
||||
> = Symbol('chainOpenTask')
|
||||
export const CHAIN_OPEN_TASK_KEY: InjectionKey<(taskId: string, taskName?: string) => void> =
|
||||
Symbol('chainOpenTask')
|
||||
|
||||
export const CHAIN_OPEN_PRODUCT_KEY: InjectionKey<(productId: string) => void> =
|
||||
Symbol('chainOpenProduct')
|
||||
|
||||
@@ -7,15 +7,13 @@ import type {
|
||||
ChainEdgeMeta,
|
||||
ChainNode,
|
||||
ChainNodeData,
|
||||
DescendantItem,
|
||||
DescendantItem
|
||||
} from './chain-types'
|
||||
|
||||
export const CHAIN_NODE_WIDTH = 148
|
||||
export const CHAIN_NODE_HEIGHT = 100
|
||||
|
||||
export function nextAncestorLink(
|
||||
parent: AncestorItem['parent']
|
||||
): AncestorItem | null {
|
||||
export function nextAncestorLink(parent: AncestorItem['parent']): AncestorItem | null {
|
||||
if (!parent) return null
|
||||
if (Array.isArray(parent)) return parent[0] ?? null
|
||||
return parent
|
||||
@@ -26,7 +24,7 @@ function makeFlowNode(n: ChainNode, isCurrent: boolean): Node<ChainNodeData> {
|
||||
id: n.id,
|
||||
type: 'chainNode',
|
||||
position: { x: 0, y: 0 },
|
||||
data: { node: n, isCurrent },
|
||||
data: { node: n, isCurrent }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,8 +43,8 @@ function makeFlowEdge(
|
||||
workOrderNo: edge.workOrderNo || undefined,
|
||||
taskNo: edge.taskNo || undefined,
|
||||
taskId: edge.taskId || undefined,
|
||||
taskName: edge.taskName || undefined,
|
||||
},
|
||||
taskName: edge.taskName || undefined
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -101,7 +99,7 @@ export function chainApiToElements(data: ChainApiData): {
|
||||
|
||||
return {
|
||||
nodes: Array.from(nodeMap.values()),
|
||||
edges: annotateForkEdges(edges),
|
||||
edges: annotateForkEdges(edges)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -119,8 +117,8 @@ function annotateForkEdges(edges: Edge<ChainEdgeData>[]): Edge<ChainEdgeData>[]
|
||||
...e,
|
||||
data: {
|
||||
...e.data,
|
||||
curved: true,
|
||||
},
|
||||
curved: true
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -139,7 +137,7 @@ export function layoutChainElements(
|
||||
nodesep: 44,
|
||||
ranksep: 180,
|
||||
marginx: 24,
|
||||
marginy: 24,
|
||||
marginy: 24
|
||||
})
|
||||
|
||||
nodes.forEach((node) => {
|
||||
@@ -157,10 +155,10 @@ export function layoutChainElements(
|
||||
...node,
|
||||
position: {
|
||||
x: pos.x - CHAIN_NODE_WIDTH / 2,
|
||||
y: pos.y - CHAIN_NODE_HEIGHT / 2,
|
||||
y: pos.y - CHAIN_NODE_HEIGHT / 2
|
||||
},
|
||||
sourcePosition: Position.Right,
|
||||
targetPosition: Position.Left,
|
||||
targetPosition: Position.Left
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -26,8 +26,12 @@
|
||||
<div class="filter-actions-row">
|
||||
<span class="filter-hint">按编号、名称模糊查询。</span>
|
||||
<div class="filter-actions-end">
|
||||
<el-button class="btn-ghost" @click="handleReset"><el-icon><RefreshLeft /></el-icon>重置</el-button>
|
||||
<el-button class="btn-main" type="primary" @click="handleSearch"><el-icon><Search /></el-icon>搜索</el-button>
|
||||
<el-button class="btn-ghost" @click="handleReset"
|
||||
><el-icon><RefreshLeft /></el-icon>重置</el-button
|
||||
>
|
||||
<el-button class="btn-main" type="primary" @click="handleSearch"
|
||||
><el-icon><Search /></el-icon>搜索</el-button
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</el-form>
|
||||
@@ -126,7 +130,13 @@
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column label="操作" min-width="320" fixed="right" align="center" header-align="center">
|
||||
<el-table-column
|
||||
label="操作"
|
||||
min-width="320"
|
||||
fixed="right"
|
||||
align="center"
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<el-button class="btn-op-item" @click="handleView(row)">
|
||||
<el-icon><View /></el-icon>查看
|
||||
@@ -173,12 +183,48 @@
|
||||
header-align="center"
|
||||
:index="(index) => index + 1"
|
||||
/>
|
||||
<el-table-column prop="consumableCode" label="耗材编码" min-width="120" align="center" header-align="center" />
|
||||
<el-table-column prop="consumableName" label="耗材名称" min-width="120" align="center" header-align="center" />
|
||||
<el-table-column prop="specModel" label="规格型号" min-width="120" align="center" header-align="center" />
|
||||
<el-table-column prop="standardUsage" label="标准用量" min-width="100" align="center" header-align="center" />
|
||||
<el-table-column prop="unit" label="单位" min-width="80" align="center" header-align="center" />
|
||||
<el-table-column prop="remark" label="备注" min-width="140" align="center" header-align="center" />
|
||||
<el-table-column
|
||||
prop="consumableCode"
|
||||
label="耗材编码"
|
||||
min-width="120"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="consumableName"
|
||||
label="耗材名称"
|
||||
min-width="120"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="specModel"
|
||||
label="规格型号"
|
||||
min-width="120"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="standardUsage"
|
||||
label="标准用量"
|
||||
min-width="100"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="unit"
|
||||
label="单位"
|
||||
min-width="80"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="remark"
|
||||
label="备注"
|
||||
min-width="140"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
</el-table>
|
||||
<el-empty v-else description="暂无耗材明细" :image-size="48" />
|
||||
</template>
|
||||
@@ -235,7 +281,12 @@
|
||||
<div class="form-section">
|
||||
<div class="section-title">
|
||||
<span>耗材明细</span>
|
||||
<el-button class="btn-main" type="primary" size="small" @click="openAddConsumableDialog">
|
||||
<el-button
|
||||
class="btn-main"
|
||||
type="primary"
|
||||
size="small"
|
||||
@click="openAddConsumableDialog"
|
||||
>
|
||||
<el-icon><Plus /></el-icon>
|
||||
添加耗材
|
||||
</el-button>
|
||||
@@ -249,10 +300,33 @@
|
||||
header-align="center"
|
||||
:index="(index) => index + 1"
|
||||
/>
|
||||
<el-table-column prop="consumableCode" label="耗材编码" min-width="120" align="center" header-align="center" />
|
||||
<el-table-column prop="consumableName" label="耗材名称" min-width="120" align="center" header-align="center" />
|
||||
<el-table-column prop="specModel" label="规格型号" min-width="120" align="center" header-align="center" />
|
||||
<el-table-column label="标准用量" min-width="120" align="center" header-align="center">
|
||||
<el-table-column
|
||||
prop="consumableCode"
|
||||
label="耗材编码"
|
||||
min-width="120"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="consumableName"
|
||||
label="耗材名称"
|
||||
min-width="120"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="specModel"
|
||||
label="规格型号"
|
||||
min-width="120"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
label="标准用量"
|
||||
min-width="120"
|
||||
align="center"
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<el-input
|
||||
v-model="row.standardUsage"
|
||||
@@ -261,7 +335,13 @@
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="unit" label="单位" min-width="80" align="center" header-align="center" />
|
||||
<el-table-column
|
||||
prop="unit"
|
||||
label="单位"
|
||||
min-width="80"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column label="备注" min-width="140" align="center" header-align="center">
|
||||
<template #default="{ row }">
|
||||
<el-input v-model="row.remark" placeholder="备注" class="inner-cell-input" />
|
||||
@@ -269,13 +349,22 @@
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" min-width="80" align="center" header-align="center">
|
||||
<template #default="{ $index }">
|
||||
<el-button link type="danger" size="small" @click="formData.items.splice($index, 1)">
|
||||
<el-button
|
||||
link
|
||||
type="danger"
|
||||
size="small"
|
||||
@click="formData.items.splice($index, 1)"
|
||||
>
|
||||
删除
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<el-empty v-if="formData.items.length === 0" description="暂无耗材,点击上方按钮添加" :image-size="48" />
|
||||
<el-empty
|
||||
v-if="formData.items.length === 0"
|
||||
description="暂无耗材,点击上方按钮添加"
|
||||
:image-size="48"
|
||||
/>
|
||||
</div>
|
||||
</el-form>
|
||||
|
||||
@@ -321,12 +410,48 @@
|
||||
row-key="id"
|
||||
@selection-change="handleConsumableSelectionChange"
|
||||
>
|
||||
<el-table-column type="selection" min-width="48" align="center" header-align="center" :reserve-selection="true" />
|
||||
<el-table-column prop="consumableCode" label="耗材编码" min-width="120" align="center" header-align="center" />
|
||||
<el-table-column prop="consumableName" label="耗材名称" min-width="140" align="center" header-align="center" />
|
||||
<el-table-column prop="specModel" label="规格型号" min-width="120" align="center" header-align="center" />
|
||||
<el-table-column prop="standardUsage" label="标准用量" min-width="100" align="center" header-align="center" />
|
||||
<el-table-column prop="unit" label="单位" min-width="80" align="center" header-align="center" />
|
||||
<el-table-column
|
||||
type="selection"
|
||||
min-width="48"
|
||||
align="center"
|
||||
header-align="center"
|
||||
:reserve-selection="true"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="consumableCode"
|
||||
label="耗材编码"
|
||||
min-width="120"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="consumableName"
|
||||
label="耗材名称"
|
||||
min-width="140"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="specModel"
|
||||
label="规格型号"
|
||||
min-width="120"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="standardUsage"
|
||||
label="标准用量"
|
||||
min-width="100"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="unit"
|
||||
label="单位"
|
||||
min-width="80"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="description"
|
||||
label="描述"
|
||||
@@ -581,7 +706,9 @@
|
||||
const ids = selectedRows.value.map((r) => r.id)
|
||||
if (!ids.length) return
|
||||
try {
|
||||
await ElMessageBox.confirm(`确定删除选中的 ${ids.length} 个耗材包吗?`, '提示', { type: 'warning' })
|
||||
await ElMessageBox.confirm(`确定删除选中的 ${ids.length} 个耗材包吗?`, '提示', {
|
||||
type: 'warning'
|
||||
})
|
||||
} catch {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -4,7 +4,9 @@
|
||||
<div class="page-container">
|
||||
<header class="page-head">
|
||||
<h1>耗材管理</h1>
|
||||
<p class="page-desc">维护生产耗材的编码、名称、规格、标准用量及单位等信息,支持入库、出库操作。</p>
|
||||
<p class="page-desc"
|
||||
>维护生产耗材的编码、名称、规格、标准用量及单位等信息,支持入库、出库操作。</p
|
||||
>
|
||||
</header>
|
||||
|
||||
<section class="panel">
|
||||
@@ -30,8 +32,12 @@
|
||||
<div class="filter-actions-row">
|
||||
<span class="filter-hint">按编码、名称模糊查询,可按状态筛选。</span>
|
||||
<div class="filter-actions-end">
|
||||
<el-button class="btn-ghost" @click="handleReset"><el-icon><RefreshLeft /></el-icon>重置</el-button>
|
||||
<el-button class="btn-main" type="primary" @click="handleSearch"><el-icon><Search /></el-icon>搜索</el-button>
|
||||
<el-button class="btn-ghost" @click="handleReset"
|
||||
><el-icon><RefreshLeft /></el-icon>重置</el-button
|
||||
>
|
||||
<el-button class="btn-main" type="primary" @click="handleSearch"
|
||||
><el-icon><Search /></el-icon>搜索</el-button
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</el-form>
|
||||
@@ -76,7 +82,12 @@
|
||||
@selection-change="handleSelectionChange"
|
||||
@row-dblclick="handleView"
|
||||
>
|
||||
<el-table-column type="selection" min-width="55" align="center" header-align="center" />
|
||||
<el-table-column
|
||||
type="selection"
|
||||
min-width="55"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
type="index"
|
||||
label="序号"
|
||||
@@ -85,18 +96,64 @@
|
||||
header-align="center"
|
||||
:index="(index) => (pagination.page - 1) * pagination.pageSize + index + 1"
|
||||
/>
|
||||
<el-table-column prop="consumableCode" label="耗材编码" min-width="130" align="center" header-align="center" />
|
||||
<el-table-column prop="consumableName" label="耗材名称" min-width="150" align="center" header-align="center" />
|
||||
<el-table-column prop="specModel" label="规格型号" min-width="120" align="center" header-align="center" />
|
||||
<el-table-column prop="standardUsage" label="标准用量" min-width="100" align="center" header-align="center" />
|
||||
<el-table-column prop="unit" label="单位" min-width="80" align="center" header-align="center" />
|
||||
<el-table-column prop="stockQty" label="当前库存" min-width="100" align="center" header-align="center" />
|
||||
<el-table-column label="预警库存" min-width="100" align="center" header-align="center">
|
||||
<el-table-column
|
||||
prop="consumableCode"
|
||||
label="耗材编码"
|
||||
min-width="130"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="consumableName"
|
||||
label="耗材名称"
|
||||
min-width="150"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="specModel"
|
||||
label="规格型号"
|
||||
min-width="120"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="standardUsage"
|
||||
label="标准用量"
|
||||
min-width="100"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="unit"
|
||||
label="单位"
|
||||
min-width="80"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="stockQty"
|
||||
label="当前库存"
|
||||
min-width="100"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
label="预警库存"
|
||||
min-width="100"
|
||||
align="center"
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
{{ row.warningQty ?? 0 }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="库存状态" min-width="100" align="center" header-align="center">
|
||||
<el-table-column
|
||||
label="库存状态"
|
||||
min-width="100"
|
||||
align="center"
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="isStockLow(row) ? 'danger' : 'success'" size="small">
|
||||
{{ isStockLow(row) ? '库存预警' : '正常' }}
|
||||
@@ -110,14 +167,32 @@
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" min-width="480" fixed="right" align="center" header-align="center">
|
||||
<el-table-column
|
||||
label="操作"
|
||||
min-width="480"
|
||||
fixed="right"
|
||||
align="center"
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<el-button class="btn-op-item" @click="handleStockIn(row)"><el-icon><Upload /></el-icon>入库</el-button>
|
||||
<el-button class="btn-op-item" @click="handleStockOut(row)"><el-icon><Download /></el-icon>出库</el-button>
|
||||
<el-button class="btn-op-item" @click="handleViewStockLog(row)"><el-icon><Document /></el-icon>日志</el-button>
|
||||
<el-button class="btn-op-item" @click="handleView(row)"><el-icon><View /></el-icon>查看</el-button>
|
||||
<el-button class="btn-op-item primary" @click="handleEdit(row)"><el-icon><Edit /></el-icon>编辑</el-button>
|
||||
<el-button class="btn-op-item" @click="handleDelete(row)"><el-icon><Delete /></el-icon>删除</el-button>
|
||||
<el-button class="btn-op-item" @click="handleStockIn(row)"
|
||||
><el-icon><Upload /></el-icon>入库</el-button
|
||||
>
|
||||
<el-button class="btn-op-item" @click="handleStockOut(row)"
|
||||
><el-icon><Download /></el-icon>出库</el-button
|
||||
>
|
||||
<el-button class="btn-op-item" @click="handleViewStockLog(row)"
|
||||
><el-icon><Document /></el-icon>日志</el-button
|
||||
>
|
||||
<el-button class="btn-op-item" @click="handleView(row)"
|
||||
><el-icon><View /></el-icon>查看</el-button
|
||||
>
|
||||
<el-button class="btn-op-item primary" @click="handleEdit(row)"
|
||||
><el-icon><Edit /></el-icon>编辑</el-button
|
||||
>
|
||||
<el-button class="btn-op-item" @click="handleDelete(row)"
|
||||
><el-icon><Delete /></el-icon>删除</el-button
|
||||
>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
@@ -128,7 +203,9 @@
|
||||
:total="pagination.total"
|
||||
:page-sizes="[10, 20, 50]"
|
||||
layout="total, sizes, prev, pager, next"
|
||||
@size-change="(val) => ((pagination.page = 1), (pagination.pageSize = val), loadData())"
|
||||
@size-change="
|
||||
(val) => ((pagination.page = 1), (pagination.pageSize = val), loadData())
|
||||
"
|
||||
@current-change="(val) => ((pagination.page = val), loadData())"
|
||||
/>
|
||||
</div>
|
||||
@@ -165,8 +242,12 @@
|
||||
<div class="filter-actions-row">
|
||||
<span class="filter-hint">按耗材编码/名称、变动类型和时间范围查询变动记录。</span>
|
||||
<div class="filter-actions-end">
|
||||
<el-button class="btn-ghost" @click="handleLogReset"><el-icon><RefreshLeft /></el-icon>重置</el-button>
|
||||
<el-button class="btn-main" type="primary" @click="handleLogSearch"><el-icon><Search /></el-icon>搜索</el-button>
|
||||
<el-button class="btn-ghost" @click="handleLogReset"
|
||||
><el-icon><RefreshLeft /></el-icon>重置</el-button
|
||||
>
|
||||
<el-button class="btn-main" type="primary" @click="handleLogSearch"
|
||||
><el-icon><Search /></el-icon>搜索</el-button
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</el-form>
|
||||
@@ -189,31 +270,75 @@
|
||||
header-align="center"
|
||||
:index="(index) => (logPagination.page - 1) * logPagination.pageSize + index + 1"
|
||||
/>
|
||||
<el-table-column prop="materialCode" label="耗材编码" min-width="120" align="center" header-align="center" />
|
||||
<el-table-column prop="materialName" label="耗材名称" min-width="140" align="center" header-align="center" />
|
||||
<el-table-column label="变动类型" min-width="100" align="center" header-align="center">
|
||||
<el-table-column
|
||||
prop="materialCode"
|
||||
label="耗材编码"
|
||||
min-width="120"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="materialName"
|
||||
label="耗材名称"
|
||||
min-width="140"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
label="变动类型"
|
||||
min-width="100"
|
||||
align="center"
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="getChangeTypeTagType(row)" size="small">
|
||||
{{ getChangeTypeLabel(row) }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="变动数量" min-width="120" align="center" header-align="center">
|
||||
<el-table-column
|
||||
label="变动数量"
|
||||
min-width="120"
|
||||
align="center"
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<span :class="row.changeQty < 0 ? 'qty-negative' : 'qty-positive'">
|
||||
{{ row.changeQty < 0 ? row.changeQty : `+${row.changeQty}` }}
|
||||
</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="stockBefore" label="变动前库存" min-width="120" align="center" header-align="center" />
|
||||
<el-table-column prop="stockAfter" label="变动后库存" min-width="120" align="center" header-align="center" />
|
||||
<el-table-column
|
||||
prop="stockBefore"
|
||||
label="变动前库存"
|
||||
min-width="120"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="stockAfter"
|
||||
label="变动后库存"
|
||||
min-width="120"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column label="工单号" min-width="140" align="center" header-align="center">
|
||||
<template #default="{ row }">{{ row.taskNo || '-' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="步骤名称" min-width="140" align="center" header-align="center">
|
||||
<el-table-column
|
||||
label="步骤名称"
|
||||
min-width="140"
|
||||
align="center"
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }">{{ row.stepName || '-' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="来源类型" min-width="120" align="center" header-align="center">
|
||||
<el-table-column
|
||||
label="来源类型"
|
||||
min-width="120"
|
||||
align="center"
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="getSourceTagType(row.sourceType)" size="small">
|
||||
{{ getSourceLabel(row.sourceType) }}
|
||||
@@ -223,18 +348,50 @@
|
||||
<el-table-column label="领用人" min-width="100" align="center" header-align="center">
|
||||
<template #default="{ row }">{{ displayLogReceiver(row) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="用途" min-width="120" align="center" header-align="center" show-overflow-tooltip>
|
||||
<el-table-column
|
||||
label="用途"
|
||||
min-width="120"
|
||||
align="center"
|
||||
header-align="center"
|
||||
show-overflow-tooltip
|
||||
>
|
||||
<template #default="{ row }">{{ displayLogPurpose(row) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="领用时间" min-width="170" align="center" header-align="center">
|
||||
<el-table-column
|
||||
label="领用时间"
|
||||
min-width="170"
|
||||
align="center"
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }">{{ displayLogUseTime(row) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="remark" label="备注" min-width="140" align="center" header-align="center" show-overflow-tooltip />
|
||||
<el-table-column
|
||||
prop="remark"
|
||||
label="备注"
|
||||
min-width="140"
|
||||
align="center"
|
||||
header-align="center"
|
||||
show-overflow-tooltip
|
||||
/>
|
||||
<el-table-column label="操作人" min-width="100" align="center" header-align="center">
|
||||
<template #default="{ row }">{{ row.createByName || row.createBy || '-' }}</template>
|
||||
<template #default="{ row }">{{
|
||||
row.createByName || row.createBy || '-'
|
||||
}}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="createTime" label="变动时间" min-width="170" align="center" header-align="center" />
|
||||
<el-table-column label="操作" min-width="160" fixed="right" align="center" header-align="center">
|
||||
<el-table-column
|
||||
prop="createTime"
|
||||
label="变动时间"
|
||||
min-width="170"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
label="操作"
|
||||
min-width="160"
|
||||
fixed="right"
|
||||
align="center"
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<el-button class="btn-op-item" @click="handleLogView(row)">
|
||||
<el-icon><View /></el-icon>查看
|
||||
@@ -249,7 +406,9 @@
|
||||
:total="logPagination.total"
|
||||
:page-sizes="[10, 20, 50, 100]"
|
||||
layout="total, sizes, prev, pager, next"
|
||||
@size-change="(val) => ((logPagination.page = 1), (logPagination.pageSize = val), loadLogData())"
|
||||
@size-change="
|
||||
(val) => ((logPagination.page = 1), (logPagination.pageSize = val), loadLogData())
|
||||
"
|
||||
@current-change="(val) => ((logPagination.page = val), loadLogData())"
|
||||
/>
|
||||
</div>
|
||||
@@ -300,7 +459,9 @@
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="预警库存">
|
||||
<template #label>预警库存<span class="form-label-tip">低于该值时列表显示库存预警</span></template>
|
||||
<template #label
|
||||
>预警库存<span class="form-label-tip">低于该值时列表显示库存预警</span></template
|
||||
>
|
||||
<el-input-number
|
||||
v-model="formData.warningQty"
|
||||
:min="0"
|
||||
@@ -317,7 +478,12 @@
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="描述" class="form-col-span-2">
|
||||
<el-input v-model="formData.description" type="textarea" :rows="3" placeholder="请输入描述" />
|
||||
<el-input
|
||||
v-model="formData.description"
|
||||
type="textarea"
|
||||
:rows="3"
|
||||
placeholder="请输入描述"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="备注" class="form-col-span-2">
|
||||
<el-input v-model="formData.remark" placeholder="可选" />
|
||||
@@ -365,12 +531,7 @@
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="备注">
|
||||
<el-input
|
||||
v-model="stockInForm.remark"
|
||||
type="textarea"
|
||||
:rows="3"
|
||||
placeholder="可选"
|
||||
/>
|
||||
<el-input v-model="stockInForm.remark" type="textarea" :rows="3" placeholder="可选" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
@@ -440,20 +601,10 @@
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="用途">
|
||||
<el-input
|
||||
v-model="stockOutForm.purpose"
|
||||
type="textarea"
|
||||
:rows="2"
|
||||
placeholder="可选"
|
||||
/>
|
||||
<el-input v-model="stockOutForm.purpose" type="textarea" :rows="2" placeholder="可选" />
|
||||
</el-form-item>
|
||||
<el-form-item label="备注">
|
||||
<el-input
|
||||
v-model="stockOutForm.remark"
|
||||
type="textarea"
|
||||
:rows="2"
|
||||
placeholder="可选"
|
||||
/>
|
||||
<el-input v-model="stockOutForm.remark" type="textarea" :rows="2" placeholder="可选" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
@@ -544,13 +695,41 @@
|
||||
header-align="center"
|
||||
:index="(index) => index + 1"
|
||||
/>
|
||||
<el-table-column prop="materialCode" label="耗材编码" min-width="120" align="center" header-align="center" />
|
||||
<el-table-column prop="materialName" label="耗材名称" min-width="140" align="center" header-align="center" />
|
||||
<el-table-column prop="unit" label="单位" min-width="80" align="center" header-align="center" />
|
||||
<el-table-column label="当前库存" min-width="100" align="center" header-align="center">
|
||||
<el-table-column
|
||||
prop="materialCode"
|
||||
label="耗材编码"
|
||||
min-width="120"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="materialName"
|
||||
label="耗材名称"
|
||||
min-width="140"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="unit"
|
||||
label="单位"
|
||||
min-width="80"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
label="当前库存"
|
||||
min-width="100"
|
||||
align="center"
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }">{{ row.stockQty }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="入库数量" min-width="130" align="center" header-align="center">
|
||||
<el-table-column
|
||||
label="入库数量"
|
||||
min-width="130"
|
||||
align="center"
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<el-input-number
|
||||
v-model="row.qty"
|
||||
@@ -564,22 +743,28 @@
|
||||
</el-table-column>
|
||||
<el-table-column label="备注" min-width="160" align="center" header-align="center">
|
||||
<template #default="{ row }">
|
||||
<el-input v-model="row.remark" placeholder="可选" clearable class="inner-cell-input" />
|
||||
<el-input
|
||||
v-model="row.remark"
|
||||
placeholder="可选"
|
||||
clearable
|
||||
class="inner-cell-input"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" min-width="80" align="center" header-align="center">
|
||||
<template #default="{ $index }">
|
||||
<el-button link type="danger" size="small" @click="batchStockInLines.splice($index, 1)">
|
||||
<el-button
|
||||
link
|
||||
type="danger"
|
||||
size="small"
|
||||
@click="batchStockInLines.splice($index, 1)"
|
||||
>
|
||||
移除
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<el-empty
|
||||
v-else
|
||||
description="请通过上方搜索框添加要入库的耗材"
|
||||
:image-size="56"
|
||||
/>
|
||||
<el-empty v-else description="请通过上方搜索框添加要入库的耗材" :image-size="56" />
|
||||
</div>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
@@ -699,13 +884,41 @@
|
||||
header-align="center"
|
||||
:index="(index) => index + 1"
|
||||
/>
|
||||
<el-table-column prop="materialCode" label="耗材编码" min-width="120" align="center" header-align="center" />
|
||||
<el-table-column prop="materialName" label="耗材名称" min-width="140" align="center" header-align="center" />
|
||||
<el-table-column prop="unit" label="单位" min-width="80" align="center" header-align="center" />
|
||||
<el-table-column label="当前库存" min-width="100" align="center" header-align="center">
|
||||
<el-table-column
|
||||
prop="materialCode"
|
||||
label="耗材编码"
|
||||
min-width="120"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="materialName"
|
||||
label="耗材名称"
|
||||
min-width="140"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="unit"
|
||||
label="单位"
|
||||
min-width="80"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
label="当前库存"
|
||||
min-width="100"
|
||||
align="center"
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }">{{ row.stockQty }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="出库数量" min-width="130" align="center" header-align="center">
|
||||
<el-table-column
|
||||
label="出库数量"
|
||||
min-width="130"
|
||||
align="center"
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<el-input-number
|
||||
v-model="row.qty"
|
||||
@@ -720,22 +933,28 @@
|
||||
</el-table-column>
|
||||
<el-table-column label="备注" min-width="160" align="center" header-align="center">
|
||||
<template #default="{ row }">
|
||||
<el-input v-model="row.remark" placeholder="可选" clearable class="inner-cell-input" />
|
||||
<el-input
|
||||
v-model="row.remark"
|
||||
placeholder="可选"
|
||||
clearable
|
||||
class="inner-cell-input"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" min-width="80" align="center" header-align="center">
|
||||
<template #default="{ $index }">
|
||||
<el-button link type="danger" size="small" @click="batchStockOutLines.splice($index, 1)">
|
||||
<el-button
|
||||
link
|
||||
type="danger"
|
||||
size="small"
|
||||
@click="batchStockOutLines.splice($index, 1)"
|
||||
>
|
||||
移除
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<el-empty
|
||||
v-else
|
||||
description="请通过上方搜索框添加要出库的耗材"
|
||||
:image-size="56"
|
||||
/>
|
||||
<el-empty v-else description="请通过上方搜索框添加要出库的耗材" :image-size="56" />
|
||||
</div>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
@@ -778,7 +997,9 @@
|
||||
min-width="56"
|
||||
align="center"
|
||||
header-align="center"
|
||||
:index="(index) => (stockLogPagination.page - 1) * stockLogPagination.pageSize + index + 1"
|
||||
:index="
|
||||
(index) => (stockLogPagination.page - 1) * stockLogPagination.pageSize + index + 1
|
||||
"
|
||||
/>
|
||||
<el-table-column label="变动类型" min-width="100" align="center" header-align="center">
|
||||
<template #default="{ row }">
|
||||
@@ -794,8 +1015,20 @@
|
||||
</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="stockBefore" label="变动前库存" min-width="120" align="center" header-align="center" />
|
||||
<el-table-column prop="stockAfter" label="变动后库存" min-width="120" align="center" header-align="center" />
|
||||
<el-table-column
|
||||
prop="stockBefore"
|
||||
label="变动前库存"
|
||||
min-width="120"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="stockAfter"
|
||||
label="变动后库存"
|
||||
min-width="120"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column label="来源类型" min-width="120" align="center" header-align="center">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="getSourceTagType(row.sourceType)" size="small">
|
||||
@@ -812,14 +1045,33 @@
|
||||
<el-table-column label="领用人" min-width="100" align="center" header-align="center">
|
||||
<template #default="{ row }">{{ row.receiver || '-' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="用途" min-width="120" align="center" header-align="center" show-overflow-tooltip>
|
||||
<el-table-column
|
||||
label="用途"
|
||||
min-width="120"
|
||||
align="center"
|
||||
header-align="center"
|
||||
show-overflow-tooltip
|
||||
>
|
||||
<template #default="{ row }">{{ row.purpose || '-' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="remark" label="备注" min-width="140" align="center" header-align="center" show-overflow-tooltip />
|
||||
<el-table-column
|
||||
prop="remark"
|
||||
label="备注"
|
||||
min-width="140"
|
||||
align="center"
|
||||
header-align="center"
|
||||
show-overflow-tooltip
|
||||
/>
|
||||
<el-table-column label="操作人" min-width="100" align="center" header-align="center">
|
||||
<template #default="{ row }">{{ row.createByName || row.createBy || '-' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="createTime" label="变动时间" min-width="170" align="center" header-align="center" />
|
||||
<el-table-column
|
||||
prop="createTime"
|
||||
label="变动时间"
|
||||
min-width="170"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
</el-table>
|
||||
</div>
|
||||
<div class="pagination list-pager-bar" style="padding-top: 12px">
|
||||
@@ -829,7 +1081,13 @@
|
||||
:total="stockLogPagination.total"
|
||||
:page-sizes="[10, 20, 50]"
|
||||
layout="total, sizes, prev, pager, next"
|
||||
@size-change="(val) => ((stockLogPagination.page = 1), (stockLogPagination.pageSize = val), loadStockLogData())"
|
||||
@size-change="
|
||||
(val) => (
|
||||
(stockLogPagination.page = 1),
|
||||
(stockLogPagination.pageSize = val),
|
||||
loadStockLogData()
|
||||
)
|
||||
"
|
||||
@current-change="(val) => ((stockLogPagination.page = val), loadStockLogData())"
|
||||
/>
|
||||
</div>
|
||||
@@ -1304,7 +1562,9 @@
|
||||
async function handleBatchDelete() {
|
||||
const ids = selectedRows.value.map((r) => r.id)
|
||||
if (!ids.length) return
|
||||
await ElMessageBox.confirm(`确定删除选中的 ${ids.length} 个耗材吗?`, '提示', { type: 'warning' })
|
||||
await ElMessageBox.confirm(`确定删除选中的 ${ids.length} 个耗材吗?`, '提示', {
|
||||
type: 'warning'
|
||||
})
|
||||
await withFullscreenLoading(async () => {
|
||||
for (const id of ids) {
|
||||
await fetchDeleteConsumable(id)
|
||||
|
||||
@@ -22,8 +22,12 @@
|
||||
<div class="filter-actions-row">
|
||||
<span class="filter-hint">按编码、名称模糊查询。</span>
|
||||
<div class="filter-actions-end">
|
||||
<el-button class="btn-ghost" @click="handleReset"><el-icon><RefreshLeft /></el-icon>重置</el-button>
|
||||
<el-button class="btn-main" type="primary" @click="handleSearch"><el-icon><Search /></el-icon>搜索</el-button>
|
||||
<el-button class="btn-ghost" @click="handleReset"
|
||||
><el-icon><RefreshLeft /></el-icon>重置</el-button
|
||||
>
|
||||
<el-button class="btn-main" type="primary" @click="handleSearch"
|
||||
><el-icon><Search /></el-icon>搜索</el-button
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</el-form>
|
||||
@@ -68,10 +72,34 @@
|
||||
header-align="center"
|
||||
:index="(index) => (pagination.page - 1) * pagination.pageSize + index + 1"
|
||||
/>
|
||||
<el-table-column prop="typeCode" label="类型编码" min-width="150" align="center" header-align="center" />
|
||||
<el-table-column prop="typeName" label="类型名称" min-width="150" align="center" header-align="center" />
|
||||
<el-table-column prop="createTime" label="创建时间" min-width="180" align="center" header-align="center" />
|
||||
<el-table-column label="操作" min-width="320" fixed="right" align="center" header-align="center">
|
||||
<el-table-column
|
||||
prop="typeCode"
|
||||
label="类型编码"
|
||||
min-width="150"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="typeName"
|
||||
label="类型名称"
|
||||
min-width="150"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="createTime"
|
||||
label="创建时间"
|
||||
min-width="180"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
label="操作"
|
||||
min-width="320"
|
||||
fixed="right"
|
||||
align="center"
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<el-button class="btn-op-item" @click="handleView(row)"
|
||||
><el-icon><View /></el-icon>查看</el-button
|
||||
@@ -151,7 +179,15 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, onMounted, computed } from 'vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { Plus, Close, Check, View, ScaleToOriginal, RefreshLeft, Search } from '@element-plus/icons-vue'
|
||||
import {
|
||||
Plus,
|
||||
Close,
|
||||
Check,
|
||||
View,
|
||||
ScaleToOriginal,
|
||||
RefreshLeft,
|
||||
Search
|
||||
} from '@element-plus/icons-vue'
|
||||
import SystemViewDialog from '@/components/business/system-view-dialog/index.vue'
|
||||
import { withFullscreenLoading, fullscreenLoadingText } from '@/utils/fullscreen-loading'
|
||||
import {
|
||||
@@ -304,7 +340,9 @@
|
||||
async function handleBatchDelete() {
|
||||
const ids = selectedRows.value.map((r) => r.id)
|
||||
if (!ids.length) return
|
||||
await ElMessageBox.confirm(`确定删除选中的 ${ids.length} 个类型吗?`, '提示', { type: 'warning' })
|
||||
await ElMessageBox.confirm(`确定删除选中的 ${ids.length} 个类型吗?`, '提示', {
|
||||
type: 'warning'
|
||||
})
|
||||
await withFullscreenLoading(async () => {
|
||||
for (const id of ids) {
|
||||
await fetchDeleteOutputType(id)
|
||||
|
||||
@@ -22,8 +22,12 @@
|
||||
<div class="filter-actions-row">
|
||||
<span class="filter-hint">按编码、名称模糊查询。</span>
|
||||
<div class="filter-actions-end">
|
||||
<el-button class="btn-ghost" @click="handleReset"><el-icon><RefreshLeft /></el-icon>重置</el-button>
|
||||
<el-button class="btn-main" type="primary" @click="handleSearch"><el-icon><Search /></el-icon>搜索</el-button>
|
||||
<el-button class="btn-ghost" @click="handleReset"
|
||||
><el-icon><RefreshLeft /></el-icon>重置</el-button
|
||||
>
|
||||
<el-button class="btn-main" type="primary" @click="handleSearch"
|
||||
><el-icon><Search /></el-icon>搜索</el-button
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</el-form>
|
||||
@@ -68,10 +72,34 @@
|
||||
header-align="center"
|
||||
:index="(index) => (pagination.page - 1) * pagination.pageSize + index + 1"
|
||||
/>
|
||||
<el-table-column prop="categoryCode" label="分类编码" min-width="150" align="center" header-align="center" />
|
||||
<el-table-column prop="categoryName" label="分类名称" min-width="150" align="center" header-align="center" />
|
||||
<el-table-column prop="createTime" label="创建时间" min-width="180" align="center" header-align="center" />
|
||||
<el-table-column label="操作" min-width="320" fixed="right" align="center" header-align="center">
|
||||
<el-table-column
|
||||
prop="categoryCode"
|
||||
label="分类编码"
|
||||
min-width="150"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="categoryName"
|
||||
label="分类名称"
|
||||
min-width="150"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="createTime"
|
||||
label="创建时间"
|
||||
min-width="180"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
label="操作"
|
||||
min-width="320"
|
||||
fixed="right"
|
||||
align="center"
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<el-button class="btn-op-item" @click="handleView(row)"
|
||||
><el-icon><View /></el-icon>查看</el-button
|
||||
@@ -151,7 +179,15 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, onMounted, computed } from 'vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { Plus, Close, Check, View, ScaleToOriginal, RefreshLeft, Search } from '@element-plus/icons-vue'
|
||||
import {
|
||||
Plus,
|
||||
Close,
|
||||
Check,
|
||||
View,
|
||||
ScaleToOriginal,
|
||||
RefreshLeft,
|
||||
Search
|
||||
} from '@element-plus/icons-vue'
|
||||
import SystemViewDialog from '@/components/business/system-view-dialog/index.vue'
|
||||
import { withFullscreenLoading, fullscreenLoadingText } from '@/utils/fullscreen-loading'
|
||||
import {
|
||||
@@ -304,7 +340,9 @@
|
||||
async function handleBatchDelete() {
|
||||
const ids = selectedRows.value.map((r) => r.id)
|
||||
if (!ids.length) return
|
||||
await ElMessageBox.confirm(`确定删除选中的 ${ids.length} 个分类吗?`, '提示', { type: 'warning' })
|
||||
await ElMessageBox.confirm(`确定删除选中的 ${ids.length} 个分类吗?`, '提示', {
|
||||
type: 'warning'
|
||||
})
|
||||
await withFullscreenLoading(async () => {
|
||||
for (const id of ids) {
|
||||
await fetchDeleteProcessCategory(id)
|
||||
|
||||
@@ -31,12 +31,7 @@
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="产品类型">
|
||||
<el-select
|
||||
v-model="searchForm.productTypeId"
|
||||
placeholder="全部"
|
||||
clearable
|
||||
filterable
|
||||
>
|
||||
<el-select v-model="searchForm.productTypeId" placeholder="全部" clearable filterable>
|
||||
<el-option
|
||||
v-for="pt in productTypeOptions"
|
||||
:key="pt.id"
|
||||
@@ -49,8 +44,12 @@
|
||||
<div class="filter-actions-row">
|
||||
<span class="filter-hint">按路线名称、编码、产品类型筛选。</span>
|
||||
<div class="filter-actions-end">
|
||||
<el-button class="btn-ghost" @click="handleReset"><el-icon><RefreshLeft /></el-icon>重置</el-button>
|
||||
<el-button class="btn-main" type="primary" @click="handleSearch"><el-icon><Search /></el-icon>搜索</el-button>
|
||||
<el-button class="btn-ghost" @click="handleReset"
|
||||
><el-icon><RefreshLeft /></el-icon>重置</el-button
|
||||
>
|
||||
<el-button class="btn-main" type="primary" @click="handleSearch"
|
||||
><el-icon><Search /></el-icon>搜索</el-button
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</el-form>
|
||||
@@ -94,33 +93,68 @@
|
||||
header-align="center"
|
||||
:index="(index: number) => (pagination.page - 1) * pagination.pageSize + index + 1"
|
||||
/>
|
||||
<el-table-column prop="routeName" label="路线名称" min-width="160" align="center" header-align="center" show-overflow-tooltip />
|
||||
<el-table-column prop="routeCode" label="路线编码" min-width="130" align="center" header-align="center" show-overflow-tooltip />
|
||||
<el-table-column
|
||||
prop="routeName"
|
||||
label="路线名称"
|
||||
min-width="160"
|
||||
align="center"
|
||||
header-align="center"
|
||||
show-overflow-tooltip
|
||||
/>
|
||||
<el-table-column
|
||||
prop="routeCode"
|
||||
label="路线编码"
|
||||
min-width="130"
|
||||
align="center"
|
||||
header-align="center"
|
||||
show-overflow-tooltip
|
||||
/>
|
||||
<el-table-column label="产品类型" min-width="130" align="center" header-align="center">
|
||||
<template #default="{ row }">
|
||||
{{ productTypeMap[row.productTypeId] || '-' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="version" label="版本号" min-width="90" align="center" header-align="center" />
|
||||
<el-table-column
|
||||
prop="version"
|
||||
label="版本号"
|
||||
min-width="90"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column label="当前版本" min-width="90" align="center" header-align="center">
|
||||
<template #default="{ row }">
|
||||
<el-tag v-if="row.isCurrent" type="success" size="small">当前</el-tag>
|
||||
<span v-else>-</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="remark" label="备注" min-width="120" align="center" header-align="center" show-overflow-tooltip />
|
||||
<el-table-column prop="createTime" label="创建时间" min-width="170" align="center" header-align="center" />
|
||||
<el-table-column label="操作" min-width="340" fixed="right" align="center" header-align="center">
|
||||
<el-table-column
|
||||
prop="remark"
|
||||
label="备注"
|
||||
min-width="120"
|
||||
align="center"
|
||||
header-align="center"
|
||||
show-overflow-tooltip
|
||||
/>
|
||||
<el-table-column
|
||||
prop="createTime"
|
||||
label="创建时间"
|
||||
min-width="170"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
label="操作"
|
||||
min-width="340"
|
||||
fixed="right"
|
||||
align="center"
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<el-button class="btn-op-item primary" @click="handleOpenConfig(row)">
|
||||
<el-icon><Rank /></el-icon>路线图配置
|
||||
</el-button>
|
||||
<el-button class="btn-op-item" @click="handleEdit(row)">编辑</el-button>
|
||||
<el-button
|
||||
v-if="!row.isCurrent"
|
||||
class="btn-op-item"
|
||||
@click="handleSetCurrent(row)"
|
||||
>
|
||||
<el-button v-if="!row.isCurrent" class="btn-op-item" @click="handleSetCurrent(row)">
|
||||
设为当前
|
||||
</el-button>
|
||||
<el-button class="btn-op-item" @click="handleDelete(row)">删除</el-button>
|
||||
@@ -134,7 +168,9 @@
|
||||
:total="pagination.total"
|
||||
:page-sizes="[10, 20, 50]"
|
||||
layout="total, sizes, prev, pager, next"
|
||||
@size-change="(val: number) => ((pagination.page = 1), (pagination.pageSize = val), loadData())"
|
||||
@size-change="
|
||||
(val: number) => ((pagination.page = 1), (pagination.pageSize = val), loadData())
|
||||
"
|
||||
@current-change="(val: number) => ((pagination.page = val), loadData())"
|
||||
/>
|
||||
</div>
|
||||
@@ -167,7 +203,12 @@
|
||||
<el-input v-model="formData.routeName" placeholder="请输入路线名称" />
|
||||
</el-form-item>
|
||||
<el-form-item label="产品类型">
|
||||
<el-select v-model="formData.productTypeId" placeholder="请选择产品类型" clearable filterable>
|
||||
<el-select
|
||||
v-model="formData.productTypeId"
|
||||
placeholder="请选择产品类型"
|
||||
clearable
|
||||
filterable
|
||||
>
|
||||
<el-option
|
||||
v-for="pt in productTypeOptions"
|
||||
:key="pt.id"
|
||||
@@ -238,7 +279,8 @@
|
||||
>
|
||||
<div class="route-config-process-info">
|
||||
<div class="route-config-process-name">
|
||||
<span class="route-config-process-seq">{{ procIndex + 1 }}</span>{{ proc.processName }}
|
||||
<span class="route-config-process-seq">{{ procIndex + 1 }}</span
|
||||
>{{ proc.processName }}
|
||||
</div>
|
||||
<div class="route-config-process-code">{{ proc.processCode }}</div>
|
||||
</div>
|
||||
@@ -288,7 +330,9 @@
|
||||
</div>
|
||||
<div class="route-config-step-order">{{ index + 1 }}</div>
|
||||
<div class="route-config-step-info">
|
||||
<div class="route-config-step-name">{{ element.processName || '未命名步骤' }}</div>
|
||||
<div class="route-config-step-name">{{
|
||||
element.processName || '未命名步骤'
|
||||
}}</div>
|
||||
<div class="route-config-step-desc">{{ element.description || '' }}</div>
|
||||
</div>
|
||||
<el-button
|
||||
@@ -357,7 +401,11 @@
|
||||
<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-input
|
||||
v-model="selectedStep.estimatedDuration"
|
||||
placeholder="如 2h"
|
||||
@change="saveStepBasic"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="描述" class="step-form-col-half">
|
||||
<el-input
|
||||
@@ -384,10 +432,7 @@
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<div
|
||||
v-if="sopIdsArr.length"
|
||||
class="step-sop-attachment-row step-form-col-full"
|
||||
>
|
||||
<div v-if="sopIdsArr.length" class="step-sop-attachment-row step-form-col-full">
|
||||
<ProtocolAttachmentReadonly
|
||||
:files="linkedSopAttachmentFiles"
|
||||
@preview="handleSopAttachmentPreview"
|
||||
@@ -401,12 +446,22 @@
|
||||
<!-- 试剂 -->
|
||||
<el-tab-pane label="试剂" name="reagent">
|
||||
<div class="route-config-tab-toolbar">
|
||||
<el-button class="btn-main" type="primary" size="small" @click="addReagentRow">
|
||||
<el-button
|
||||
class="btn-main"
|
||||
type="primary"
|
||||
size="small"
|
||||
@click="addReagentRow"
|
||||
>
|
||||
<el-icon><Plus /></el-icon>
|
||||
添加试剂
|
||||
</el-button>
|
||||
</div>
|
||||
<el-table :data="selectedStep.reagents" stripe class="inner-table" max-height="300">
|
||||
<el-table
|
||||
:data="selectedStep.reagents"
|
||||
stripe
|
||||
class="inner-table"
|
||||
max-height="300"
|
||||
>
|
||||
<el-table-column
|
||||
type="index"
|
||||
label="序号"
|
||||
@@ -415,12 +470,27 @@
|
||||
header-align="center"
|
||||
:index="(i: number) => i + 1"
|
||||
/>
|
||||
<el-table-column label="试剂编码" min-width="110" align="center" header-align="center">
|
||||
<el-table-column
|
||||
label="试剂编码"
|
||||
min-width="110"
|
||||
align="center"
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<el-input v-model="row.reagentCode" placeholder="编码" class="inner-cell-input" @change="saveReagents" />
|
||||
<el-input
|
||||
v-model="row.reagentCode"
|
||||
placeholder="编码"
|
||||
class="inner-cell-input"
|
||||
@change="saveReagents"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="试剂名称" min-width="160" align="center" header-align="center">
|
||||
<el-table-column
|
||||
label="试剂名称"
|
||||
min-width="160"
|
||||
align="center"
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<el-select
|
||||
v-model="row.reagentId"
|
||||
@@ -444,17 +514,42 @@
|
||||
</el-select>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="规格" min-width="100" align="center" header-align="center">
|
||||
<el-table-column
|
||||
label="规格"
|
||||
min-width="100"
|
||||
align="center"
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<el-input v-model="row.spec" placeholder="规格" class="inner-cell-input" @change="saveReagents" />
|
||||
<el-input
|
||||
v-model="row.spec"
|
||||
placeholder="规格"
|
||||
class="inner-cell-input"
|
||||
@change="saveReagents"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="标准用量" min-width="110" align="center" header-align="center">
|
||||
<el-table-column
|
||||
label="标准用量"
|
||||
min-width="110"
|
||||
align="center"
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<el-input v-model="row.standardUsage" placeholder="用量" class="inner-cell-input" @change="saveReagents" />
|
||||
<el-input
|
||||
v-model="row.standardUsage"
|
||||
placeholder="用量"
|
||||
class="inner-cell-input"
|
||||
@change="saveReagents"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="单位" min-width="120" align="center" header-align="center">
|
||||
<el-table-column
|
||||
label="单位"
|
||||
min-width="120"
|
||||
align="center"
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<el-select
|
||||
v-model="row.unit"
|
||||
@@ -478,34 +573,74 @@
|
||||
</el-select>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="关键物料" min-width="90" align="center" header-align="center">
|
||||
<el-table-column
|
||||
label="关键物料"
|
||||
min-width="90"
|
||||
align="center"
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<el-switch v-model="row.isKeyMaterial" @change="saveReagents" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="备注" min-width="120" align="center" header-align="center">
|
||||
<el-table-column
|
||||
label="备注"
|
||||
min-width="120"
|
||||
align="center"
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<el-input v-model="row.remark" placeholder="备注" class="inner-cell-input" @change="saveReagents" />
|
||||
<el-input
|
||||
v-model="row.remark"
|
||||
placeholder="备注"
|
||||
class="inner-cell-input"
|
||||
@change="saveReagents"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" min-width="80" align="center" header-align="center">
|
||||
<el-table-column
|
||||
label="操作"
|
||||
min-width="80"
|
||||
align="center"
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ $index }">
|
||||
<el-button link type="danger" size="small" @click="removeReagentRow($index)">删除</el-button>
|
||||
<el-button
|
||||
link
|
||||
type="danger"
|
||||
size="small"
|
||||
@click="removeReagentRow($index)"
|
||||
>删除</el-button
|
||||
>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<el-empty v-if="!selectedStep.reagents || selectedStep.reagents.length === 0" description="暂无试剂" :image-size="48" />
|
||||
<el-empty
|
||||
v-if="!selectedStep.reagents || selectedStep.reagents.length === 0"
|
||||
description="暂无试剂"
|
||||
:image-size="48"
|
||||
/>
|
||||
</el-tab-pane>
|
||||
|
||||
<!-- 耗材 -->
|
||||
<el-tab-pane label="耗材" name="consumable">
|
||||
<div class="route-config-tab-toolbar">
|
||||
<el-button class="btn-main" type="primary" size="small" @click="addConsumableRow">
|
||||
<el-button
|
||||
class="btn-main"
|
||||
type="primary"
|
||||
size="small"
|
||||
@click="addConsumableRow"
|
||||
>
|
||||
<el-icon><Plus /></el-icon>
|
||||
添加耗材
|
||||
</el-button>
|
||||
</div>
|
||||
<el-table :data="selectedStep.consumables" stripe class="inner-table" max-height="300">
|
||||
<el-table
|
||||
:data="selectedStep.consumables"
|
||||
stripe
|
||||
class="inner-table"
|
||||
max-height="300"
|
||||
>
|
||||
<el-table-column
|
||||
type="index"
|
||||
label="序号"
|
||||
@@ -514,12 +649,27 @@
|
||||
header-align="center"
|
||||
:index="(i: number) => i + 1"
|
||||
/>
|
||||
<el-table-column label="耗材编码" min-width="110" align="center" header-align="center">
|
||||
<el-table-column
|
||||
label="耗材编码"
|
||||
min-width="110"
|
||||
align="center"
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<el-input v-model="row.consumableCode" placeholder="编码" class="inner-cell-input" @change="saveConsumables" />
|
||||
<el-input
|
||||
v-model="row.consumableCode"
|
||||
placeholder="编码"
|
||||
class="inner-cell-input"
|
||||
@change="saveConsumables"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="耗材名称" min-width="160" align="center" header-align="center">
|
||||
<el-table-column
|
||||
label="耗材名称"
|
||||
min-width="160"
|
||||
align="center"
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<el-select
|
||||
v-model="row.consumableId"
|
||||
@@ -543,17 +693,42 @@
|
||||
</el-select>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="规格型号" min-width="110" align="center" header-align="center">
|
||||
<el-table-column
|
||||
label="规格型号"
|
||||
min-width="110"
|
||||
align="center"
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<el-input v-model="row.specModel" placeholder="规格型号" class="inner-cell-input" @change="saveConsumables" />
|
||||
<el-input
|
||||
v-model="row.specModel"
|
||||
placeholder="规格型号"
|
||||
class="inner-cell-input"
|
||||
@change="saveConsumables"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="标准用量" min-width="110" align="center" header-align="center">
|
||||
<el-table-column
|
||||
label="标准用量"
|
||||
min-width="110"
|
||||
align="center"
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<el-input v-model="row.standardUsage" placeholder="用量" class="inner-cell-input" @change="saveConsumables" />
|
||||
<el-input
|
||||
v-model="row.standardUsage"
|
||||
placeholder="用量"
|
||||
class="inner-cell-input"
|
||||
@change="saveConsumables"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="单位" min-width="120" align="center" header-align="center">
|
||||
<el-table-column
|
||||
label="单位"
|
||||
min-width="120"
|
||||
align="center"
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<el-select
|
||||
v-model="row.unit"
|
||||
@@ -577,23 +752,53 @@
|
||||
</el-select>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="必需" min-width="80" align="center" header-align="center">
|
||||
<el-table-column
|
||||
label="必需"
|
||||
min-width="80"
|
||||
align="center"
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<el-switch v-model="row.isRequired" @change="saveConsumables" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="备注" min-width="120" align="center" header-align="center">
|
||||
<el-table-column
|
||||
label="备注"
|
||||
min-width="120"
|
||||
align="center"
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<el-input v-model="row.remark" placeholder="备注" class="inner-cell-input" @change="saveConsumables" />
|
||||
<el-input
|
||||
v-model="row.remark"
|
||||
placeholder="备注"
|
||||
class="inner-cell-input"
|
||||
@change="saveConsumables"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" min-width="80" align="center" header-align="center">
|
||||
<el-table-column
|
||||
label="操作"
|
||||
min-width="80"
|
||||
align="center"
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ $index }">
|
||||
<el-button link type="danger" size="small" @click="removeConsumableRow($index)">删除</el-button>
|
||||
<el-button
|
||||
link
|
||||
type="danger"
|
||||
size="small"
|
||||
@click="removeConsumableRow($index)"
|
||||
>删除</el-button
|
||||
>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<el-empty v-if="!selectedStep.consumables || selectedStep.consumables.length === 0" description="暂无耗材" :image-size="48" />
|
||||
<el-empty
|
||||
v-if="!selectedStep.consumables || selectedStep.consumables.length === 0"
|
||||
description="暂无耗材"
|
||||
:image-size="48"
|
||||
/>
|
||||
</el-tab-pane>
|
||||
|
||||
<!-- 产出物 -->
|
||||
@@ -604,7 +809,12 @@
|
||||
添加产出物
|
||||
</el-button>
|
||||
</div>
|
||||
<el-table :data="selectedStep.outputs" stripe class="inner-table" max-height="300">
|
||||
<el-table
|
||||
:data="selectedStep.outputs"
|
||||
stripe
|
||||
class="inner-table"
|
||||
max-height="300"
|
||||
>
|
||||
<el-table-column
|
||||
type="index"
|
||||
label="序号"
|
||||
@@ -613,9 +823,20 @@
|
||||
header-align="center"
|
||||
:index="(i: number) => i + 1"
|
||||
/>
|
||||
<el-table-column label="产出类型" min-width="150" align="center" header-align="center">
|
||||
<el-table-column
|
||||
label="产出类型"
|
||||
min-width="150"
|
||||
align="center"
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<el-select v-model="row.outputTypeId" placeholder="选择类型" filterable class="inner-cell-select" @change="saveOutputs">
|
||||
<el-select
|
||||
v-model="row.outputTypeId"
|
||||
placeholder="选择类型"
|
||||
filterable
|
||||
class="inner-cell-select"
|
||||
@change="saveOutputs"
|
||||
>
|
||||
<el-option
|
||||
v-for="ot in outputTypeOptions"
|
||||
:key="ot.id"
|
||||
@@ -625,39 +846,94 @@
|
||||
</el-select>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="名称" min-width="150" align="center" header-align="center">
|
||||
<el-table-column
|
||||
label="名称"
|
||||
min-width="150"
|
||||
align="center"
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<el-input v-model="row.outputName" placeholder="名称" class="inner-cell-input" @change="saveOutputs" />
|
||||
<el-input
|
||||
v-model="row.outputName"
|
||||
placeholder="名称"
|
||||
class="inner-cell-input"
|
||||
@change="saveOutputs"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="条码" min-width="150" align="center" header-align="center">
|
||||
<el-table-column
|
||||
label="条码"
|
||||
min-width="150"
|
||||
align="center"
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<el-input v-model="row.outputBarcode" placeholder="条码" class="inner-cell-input" @change="saveOutputs" />
|
||||
<el-input
|
||||
v-model="row.outputBarcode"
|
||||
placeholder="条码"
|
||||
class="inner-cell-input"
|
||||
@change="saveOutputs"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="备注" min-width="120" align="center" header-align="center">
|
||||
<el-table-column
|
||||
label="备注"
|
||||
min-width="120"
|
||||
align="center"
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<el-input v-model="row.remark" placeholder="备注" class="inner-cell-input" @change="saveOutputs" />
|
||||
<el-input
|
||||
v-model="row.remark"
|
||||
placeholder="备注"
|
||||
class="inner-cell-input"
|
||||
@change="saveOutputs"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" min-width="80" align="center" header-align="center">
|
||||
<el-table-column
|
||||
label="操作"
|
||||
min-width="80"
|
||||
align="center"
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ $index }">
|
||||
<el-button link type="danger" size="small" @click="removeOutputRow($index)">删除</el-button>
|
||||
<el-button
|
||||
link
|
||||
type="danger"
|
||||
size="small"
|
||||
@click="removeOutputRow($index)"
|
||||
>删除</el-button
|
||||
>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<el-empty v-if="!selectedStep.outputs || selectedStep.outputs.length === 0" description="暂无产出物" :image-size="48" />
|
||||
<el-empty
|
||||
v-if="!selectedStep.outputs || selectedStep.outputs.length === 0"
|
||||
description="暂无产出物"
|
||||
:image-size="48"
|
||||
/>
|
||||
</el-tab-pane>
|
||||
|
||||
<!-- 数据采集 -->
|
||||
<el-tab-pane label="数据采集" name="dataCollection">
|
||||
<div class="route-config-tab-toolbar">
|
||||
<el-button class="btn-main" type="primary" size="small" @click="addDataCollectionRow">
|
||||
<el-button
|
||||
class="btn-main"
|
||||
type="primary"
|
||||
size="small"
|
||||
@click="addDataCollectionRow"
|
||||
>
|
||||
<el-icon><Plus /></el-icon>
|
||||
添加采集项
|
||||
</el-button>
|
||||
</div>
|
||||
<el-table :data="selectedStep.dataCollections" stripe class="inner-table" max-height="300">
|
||||
<el-table
|
||||
:data="selectedStep.dataCollections"
|
||||
stripe
|
||||
class="inner-table"
|
||||
max-height="300"
|
||||
>
|
||||
<el-table-column
|
||||
type="index"
|
||||
label="序号"
|
||||
@@ -666,12 +942,27 @@
|
||||
header-align="center"
|
||||
:index="(i: number) => i + 1"
|
||||
/>
|
||||
<el-table-column label="采集名称" min-width="180" align="center" header-align="center">
|
||||
<el-table-column
|
||||
label="采集名称"
|
||||
min-width="180"
|
||||
align="center"
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<el-input v-model="row.collectionName" placeholder="采集项名称" class="inner-cell-input" @change="saveDataCollections" />
|
||||
<el-input
|
||||
v-model="row.collectionName"
|
||||
placeholder="采集项名称"
|
||||
class="inner-cell-input"
|
||||
@change="saveDataCollections"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="单位" min-width="120" align="center" header-align="center">
|
||||
<el-table-column
|
||||
label="单位"
|
||||
min-width="120"
|
||||
align="center"
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<el-select
|
||||
v-model="row.unit"
|
||||
@@ -695,18 +986,45 @@
|
||||
</el-select>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="备注" min-width="120" align="center" header-align="center">
|
||||
<el-table-column
|
||||
label="备注"
|
||||
min-width="120"
|
||||
align="center"
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<el-input v-model="row.remark" placeholder="备注" class="inner-cell-input" @change="saveDataCollections" />
|
||||
<el-input
|
||||
v-model="row.remark"
|
||||
placeholder="备注"
|
||||
class="inner-cell-input"
|
||||
@change="saveDataCollections"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" min-width="80" align="center" header-align="center">
|
||||
<el-table-column
|
||||
label="操作"
|
||||
min-width="80"
|
||||
align="center"
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ $index }">
|
||||
<el-button link type="danger" size="small" @click="removeDataCollectionRow($index)">删除</el-button>
|
||||
<el-button
|
||||
link
|
||||
type="danger"
|
||||
size="small"
|
||||
@click="removeDataCollectionRow($index)"
|
||||
>删除</el-button
|
||||
>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<el-empty v-if="!selectedStep.dataCollections || selectedStep.dataCollections.length === 0" description="暂无采集项" :image-size="48" />
|
||||
<el-empty
|
||||
v-if="
|
||||
!selectedStep.dataCollections || selectedStep.dataCollections.length === 0
|
||||
"
|
||||
description="暂无采集项"
|
||||
:image-size="48"
|
||||
/>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</div>
|
||||
@@ -726,7 +1044,16 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import { Plus, Delete, Rank, Close, Check, CopyDocument, RefreshLeft, Search } from '@element-plus/icons-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'
|
||||
@@ -918,7 +1245,9 @@
|
||||
async function handleBatchDelete() {
|
||||
const ids = selectedRows.value.map((r) => r.id)
|
||||
if (!ids.length) return
|
||||
await ElMessageBox.confirm(`确定删除选中的 ${ids.length} 条路线吗?`, '提示', { type: 'warning' })
|
||||
await ElMessageBox.confirm(`确定删除选中的 ${ids.length} 条路线吗?`, '提示', {
|
||||
type: 'warning'
|
||||
})
|
||||
for (const id of ids) {
|
||||
await fetchDeleteProcessRoute(id)
|
||||
}
|
||||
@@ -971,7 +1300,28 @@
|
||||
// 工艺库数据
|
||||
const processLibrary = ref<Record<string, any>[]>([])
|
||||
const expandedCategories = ref<string[]>([])
|
||||
const chineseOrdinals = ['一', '二', '三', '四', '五', '六', '七', '八', '九', '十', '十一', '十二', '十三', '十四', '十五', '十六', '十七', '十八', '十九', '二十']
|
||||
const chineseOrdinals = [
|
||||
'一',
|
||||
'二',
|
||||
'三',
|
||||
'四',
|
||||
'五',
|
||||
'六',
|
||||
'七',
|
||||
'八',
|
||||
'九',
|
||||
'十',
|
||||
'十一',
|
||||
'十二',
|
||||
'十三',
|
||||
'十四',
|
||||
'十五',
|
||||
'十六',
|
||||
'十七',
|
||||
'十八',
|
||||
'十九',
|
||||
'二十'
|
||||
]
|
||||
const categoryOptions = ref<Record<string, any>[]>([])
|
||||
const sopOptions = ref<Record<string, any>[]>([])
|
||||
const outputTypeOptions = ref<Record<string, any>[]>([])
|
||||
@@ -1088,7 +1438,10 @@
|
||||
const legacyId = `legacy:${code || name}`
|
||||
row.reagentId = legacyId
|
||||
if (!reagentOptions.value.some((o) => o.id === legacyId)) {
|
||||
reagentOptions.value = [{ id: legacyId, label: mapReagentOptionLabel(row) }, ...reagentOptions.value]
|
||||
reagentOptions.value = [
|
||||
{ id: legacyId, label: mapReagentOptionLabel(row) },
|
||||
...reagentOptions.value
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1205,7 +1558,10 @@
|
||||
const legacyId = `legacy:${code || name}`
|
||||
row.consumableId = legacyId
|
||||
if (!consumableOptions.value.some((o) => o.id === legacyId)) {
|
||||
consumableOptions.value = [{ id: legacyId, label: mapConsumableOptionLabel(row) }, ...consumableOptions.value]
|
||||
consumableOptions.value = [
|
||||
{ id: legacyId, label: mapConsumableOptionLabel(row) },
|
||||
...consumableOptions.value
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1256,7 +1612,9 @@
|
||||
for (const step of stepList.value) {
|
||||
;(step.reagents || []).forEach((r: Record<string, any>) => ensureItemUnitOption(r.unit))
|
||||
;(step.consumables || []).forEach((c: Record<string, any>) => ensureItemUnitOption(c.unit))
|
||||
;(step.dataCollections || []).forEach((d: Record<string, any>) => ensureItemUnitOption(d.unit))
|
||||
;(step.dataCollections || []).forEach((d: Record<string, any>) =>
|
||||
ensureItemUnitOption(d.unit)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1323,8 +1681,7 @@
|
||||
files.push({
|
||||
id: f.id || `${sop.id}_${f.fileUrl}`,
|
||||
fileName:
|
||||
f.fileName ||
|
||||
decodeURIComponent(String(f.fileUrl).split('/').pop() || '文档'),
|
||||
f.fileName || decodeURIComponent(String(f.fileUrl).split('/').pop() || '文档'),
|
||||
fileUrl: f.fileUrl,
|
||||
fileType: f.fileType || ''
|
||||
})
|
||||
@@ -1379,10 +1736,16 @@
|
||||
outputTypeOptions.value = outputTypes || []
|
||||
|
||||
// 按分类分组
|
||||
const catMap: Record<string, { categoryId: string; categoryName: string; processes: Record<string, any>[] }> = {}
|
||||
const catMap: Record<
|
||||
string,
|
||||
{ categoryId: string; categoryName: string; processes: Record<string, any>[] }
|
||||
> = {}
|
||||
const uncategorizedKey = '__uncategorized__'
|
||||
catMap[uncategorizedKey] = { categoryId: uncategorizedKey, categoryName: '未分类', processes: [] }
|
||||
|
||||
catMap[uncategorizedKey] = {
|
||||
categoryId: uncategorizedKey,
|
||||
categoryName: '未分类',
|
||||
processes: []
|
||||
}
|
||||
;(categories || []).forEach((cat: Record<string, any>) => {
|
||||
catMap[cat.id] = {
|
||||
categoryId: cat.id,
|
||||
@@ -1390,7 +1753,6 @@
|
||||
processes: []
|
||||
}
|
||||
})
|
||||
|
||||
;(processes || []).forEach((proc: Record<string, any>) => {
|
||||
const catId = proc.categoryId || uncategorizedKey
|
||||
if (!catMap[catId]) {
|
||||
@@ -1402,7 +1764,11 @@
|
||||
// 展开所有分类
|
||||
expandedCategories.value = Object.keys(catMap)
|
||||
processLibrary.value = Object.values(catMap)
|
||||
await Promise.all([searchItemUnitOptions(''), searchReagentOptions(''), searchConsumableOptions('')])
|
||||
await Promise.all([
|
||||
searchItemUnitOptions(''),
|
||||
searchReagentOptions(''),
|
||||
searchConsumableOptions('')
|
||||
])
|
||||
} catch {
|
||||
/* ignore */
|
||||
}
|
||||
@@ -1433,7 +1799,9 @@
|
||||
async function loadRouteDetail() {
|
||||
try {
|
||||
const detail = await fetchProcessRouteDetail(configRouteId.value)
|
||||
stepList.value = (detail.steps || []).map((s: Record<string, any>) => normalizeStepSubTables(s))
|
||||
stepList.value = (detail.steps || []).map((s: Record<string, any>) =>
|
||||
normalizeStepSubTables(s)
|
||||
)
|
||||
ensureAllStepReagentOptions()
|
||||
ensureAllStepConsumableOptions()
|
||||
ensureAllStepUnitOptions()
|
||||
@@ -1494,7 +1862,7 @@
|
||||
selectedStepId.value = step.id
|
||||
}
|
||||
|
||||
async function handleCopyStep(step: Record<string, any>) {
|
||||
async function handleCopyStep(step: Record<string, any>) {
|
||||
stepListLoading.value = true
|
||||
try {
|
||||
const newStep = await fetchCopyRouteStep({ stepId: step.id })
|
||||
@@ -1580,9 +1948,7 @@
|
||||
const seen = new Map<string, number>()
|
||||
for (let i = 0; i < reagents.length; i++) {
|
||||
const r = reagents[i]
|
||||
const key = r.reagentId
|
||||
? String(r.reagentId)
|
||||
: (r.reagentCode || '').trim()
|
||||
const key = r.reagentId ? String(r.reagentId) : (r.reagentCode || '').trim()
|
||||
if (!key) continue
|
||||
if (seen.has(key)) {
|
||||
ElMessage.warning(`试剂"${r.reagentName || r.reagentCode}"重复,请移除重复项`)
|
||||
@@ -1638,9 +2004,7 @@
|
||||
const seen = new Map<string, number>()
|
||||
for (let i = 0; i < consumables.length; i++) {
|
||||
const c = consumables[i]
|
||||
const key = c.consumableId
|
||||
? String(c.consumableId)
|
||||
: (c.consumableCode || '').trim()
|
||||
const key = c.consumableId ? String(c.consumableId) : (c.consumableCode || '').trim()
|
||||
if (!key) continue
|
||||
if (seen.has(key)) {
|
||||
ElMessage.warning(`耗材"${c.consumableName || c.consumableCode}"重复,请移除重复项`)
|
||||
@@ -1732,12 +2096,14 @@
|
||||
try {
|
||||
await fetchUpdateStepDataCollections({
|
||||
stepId: selectedStep.value.id,
|
||||
items: (selectedStep.value.dataCollections || []).map((d: Record<string, any>, idx: number) => ({
|
||||
collectionName: d.collectionName,
|
||||
unit: d.unit,
|
||||
orderNum: idx,
|
||||
remark: d.remark || ''
|
||||
}))
|
||||
items: (selectedStep.value.dataCollections || []).map(
|
||||
(d: Record<string, any>, idx: number) => ({
|
||||
collectionName: d.collectionName,
|
||||
unit: d.unit,
|
||||
orderNum: idx,
|
||||
remark: d.remark || ''
|
||||
})
|
||||
)
|
||||
})
|
||||
} catch {
|
||||
/* ignore */
|
||||
|
||||
@@ -26,8 +26,12 @@
|
||||
<div class="filter-actions-row">
|
||||
<span class="filter-hint">按工序代码、名称模糊查询。</span>
|
||||
<div class="filter-actions-end">
|
||||
<el-button class="btn-ghost" @click="handleReset"><el-icon><RefreshLeft /></el-icon>重置</el-button>
|
||||
<el-button class="btn-main" type="primary" @click="handleSearch"><el-icon><Search /></el-icon>搜索</el-button>
|
||||
<el-button class="btn-ghost" @click="handleReset"
|
||||
><el-icon><RefreshLeft /></el-icon>重置</el-button
|
||||
>
|
||||
<el-button class="btn-main" type="primary" @click="handleSearch"
|
||||
><el-icon><Search /></el-icon>搜索</el-button
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</el-form>
|
||||
@@ -85,12 +89,7 @@
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
label="工序分类"
|
||||
min-width="130"
|
||||
align="center"
|
||||
header-align="center"
|
||||
>
|
||||
<el-table-column label="工序分类" min-width="130" align="center" header-align="center">
|
||||
<template #default="{ row }">
|
||||
{{ categoryMap[row.categoryId] || '-' }}
|
||||
</template>
|
||||
@@ -124,7 +123,13 @@
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column label="操作" min-width="260" fixed="right" align="center" header-align="center">
|
||||
<el-table-column
|
||||
label="操作"
|
||||
min-width="260"
|
||||
fixed="right"
|
||||
align="center"
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<el-button class="btn-op-item primary" @click="handleEdit(row)">编辑</el-button>
|
||||
<el-button class="btn-op-item" @click="handleDelete(row)">删除</el-button>
|
||||
@@ -182,63 +187,68 @@
|
||||
<div class="section-title-left"><h3>基本信息</h3></div>
|
||||
</div>
|
||||
<div class="form-grid">
|
||||
<el-form-item label="工序代码" prop="processCode">
|
||||
<el-input v-model="formData.processCode" placeholder="请输入工序代码" />
|
||||
</el-form-item>
|
||||
<el-form-item label="工序名称" prop="processName">
|
||||
<el-input v-model="formData.processName" placeholder="请输入工序名称" />
|
||||
</el-form-item>
|
||||
<el-form-item label="工序分类">
|
||||
<el-select
|
||||
v-model="formData.categoryId"
|
||||
placeholder="请选择分类"
|
||||
clearable
|
||||
filterable
|
||||
style="width: 100%"
|
||||
>
|
||||
<el-option
|
||||
v-for="cat in categoryOptions"
|
||||
:key="cat.id"
|
||||
:label="cat.categoryName || cat.name"
|
||||
:value="cat.id"
|
||||
<el-form-item label="工序代码" prop="processCode">
|
||||
<el-input v-model="formData.processCode" placeholder="请输入工序代码" />
|
||||
</el-form-item>
|
||||
<el-form-item label="工序名称" prop="processName">
|
||||
<el-input v-model="formData.processName" placeholder="请输入工序名称" />
|
||||
</el-form-item>
|
||||
<el-form-item label="工序分类">
|
||||
<el-select
|
||||
v-model="formData.categoryId"
|
||||
placeholder="请选择分类"
|
||||
clearable
|
||||
filterable
|
||||
style="width: 100%"
|
||||
>
|
||||
<el-option
|
||||
v-for="cat in categoryOptions"
|
||||
:key="cat.id"
|
||||
:label="cat.categoryName || cat.name"
|
||||
:value="cat.id"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="设备名称">
|
||||
<el-input v-model="formData.equipmentName" placeholder="请输入设备名称" />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<template #label>预估耗时<span class="form-label-tip">(min)</span></template>
|
||||
<el-input v-model="formData.estimatedDuration" placeholder="如 30" />
|
||||
</el-form-item>
|
||||
<el-form-item label="关联SOP">
|
||||
<el-select
|
||||
v-model="formData.sopIds"
|
||||
placeholder="请选择SOP文档"
|
||||
multiple
|
||||
clearable
|
||||
filterable
|
||||
style="width: 100%"
|
||||
>
|
||||
<el-option
|
||||
v-for="sop in sopOptions"
|
||||
:key="sop.id"
|
||||
:label="sop.sopName || sop.name"
|
||||
:value="sop.id"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="描述">
|
||||
<el-input
|
||||
v-model="formData.description"
|
||||
type="textarea"
|
||||
:rows="2"
|
||||
placeholder="请输入描述"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="设备名称">
|
||||
<el-input v-model="formData.equipmentName" placeholder="请输入设备名称" />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<template #label>预估耗时<span class="form-label-tip">(min)</span></template>
|
||||
<el-input v-model="formData.estimatedDuration" placeholder="如 30" />
|
||||
</el-form-item>
|
||||
<el-form-item label="关联SOP">
|
||||
<el-select
|
||||
v-model="formData.sopIds"
|
||||
placeholder="请选择SOP文档"
|
||||
multiple
|
||||
clearable
|
||||
filterable
|
||||
style="width: 100%"
|
||||
>
|
||||
<el-option
|
||||
v-for="sop in sopOptions"
|
||||
:key="sop.id"
|
||||
:label="sop.sopName || sop.name"
|
||||
:value="sop.id"
|
||||
</el-form-item>
|
||||
<el-form-item label="备注">
|
||||
<el-input
|
||||
v-model="formData.remark"
|
||||
type="textarea"
|
||||
:rows="2"
|
||||
placeholder="可选"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="描述">
|
||||
<el-input
|
||||
v-model="formData.description"
|
||||
type="textarea"
|
||||
:rows="2"
|
||||
placeholder="请输入描述"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="备注">
|
||||
<el-input v-model="formData.remark" type="textarea" :rows="2" placeholder="可选" />
|
||||
</el-form-item>
|
||||
</el-form-item>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -247,50 +257,104 @@
|
||||
<div class="section-card">
|
||||
<div class="section-title">
|
||||
<div class="section-title-left"><h3>试剂关联</h3></div>
|
||||
<el-button class="btn-main" type="primary" size="small" @click="openAddReagentDialog">
|
||||
<el-button
|
||||
class="btn-main"
|
||||
type="primary"
|
||||
size="small"
|
||||
@click="openAddReagentDialog"
|
||||
>
|
||||
<el-icon><Plus /></el-icon>
|
||||
添加试剂
|
||||
</el-button>
|
||||
</div>
|
||||
<el-table :data="reagentList" stripe class="inner-table">
|
||||
<el-table-column
|
||||
type="index"
|
||||
label="序号"
|
||||
min-width="56"
|
||||
align="center"
|
||||
header-align="center"
|
||||
:index="(index) => index + 1"
|
||||
<el-table :data="reagentList" stripe class="inner-table">
|
||||
<el-table-column
|
||||
type="index"
|
||||
label="序号"
|
||||
min-width="56"
|
||||
align="center"
|
||||
header-align="center"
|
||||
:index="(index) => index + 1"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="reagentCode"
|
||||
label="试剂编码"
|
||||
min-width="110"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="reagentName"
|
||||
label="试剂名称"
|
||||
min-width="120"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="spec"
|
||||
label="规格"
|
||||
min-width="100"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
label="标准用量"
|
||||
min-width="110"
|
||||
align="center"
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<el-input
|
||||
v-model="row.standardUsage"
|
||||
placeholder="用量"
|
||||
class="inner-cell-input inner-cell-input--usage"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="unit"
|
||||
label="单位"
|
||||
min-width="80"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
label="关键物料"
|
||||
min-width="90"
|
||||
align="center"
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<el-switch v-model="row.isKeyMaterial" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
label="备注"
|
||||
min-width="120"
|
||||
align="center"
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<el-input v-model="row.remark" placeholder="备注" class="inner-cell-input" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" min-width="80" align="center" header-align="center">
|
||||
<template #default="{ $index }">
|
||||
<el-button
|
||||
link
|
||||
type="danger"
|
||||
size="small"
|
||||
@click="reagentList.splice($index, 1)"
|
||||
>删除</el-button
|
||||
>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<el-empty
|
||||
v-if="reagentList.length === 0"
|
||||
description="暂无试剂,点击上方按钮添加"
|
||||
:image-size="48"
|
||||
/>
|
||||
<el-table-column prop="reagentCode" label="试剂编码" min-width="110" align="center" header-align="center" />
|
||||
<el-table-column prop="reagentName" label="试剂名称" min-width="120" align="center" header-align="center" />
|
||||
<el-table-column prop="spec" label="规格" min-width="100" align="center" header-align="center" />
|
||||
<el-table-column label="标准用量" min-width="110" align="center" header-align="center">
|
||||
<template #default="{ row }">
|
||||
<el-input
|
||||
v-model="row.standardUsage"
|
||||
placeholder="用量"
|
||||
class="inner-cell-input inner-cell-input--usage"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="unit" label="单位" min-width="80" align="center" header-align="center" />
|
||||
<el-table-column label="关键物料" min-width="90" align="center" header-align="center">
|
||||
<template #default="{ row }">
|
||||
<el-switch v-model="row.isKeyMaterial" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="备注" min-width="120" align="center" header-align="center">
|
||||
<template #default="{ row }">
|
||||
<el-input v-model="row.remark" placeholder="备注" class="inner-cell-input" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" min-width="80" align="center" header-align="center">
|
||||
<template #default="{ $index }">
|
||||
<el-button link type="danger" size="small" @click="reagentList.splice($index, 1)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<el-empty v-if="reagentList.length === 0" description="暂无试剂,点击上方按钮添加" :image-size="48" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -299,7 +363,12 @@
|
||||
<div class="section-title">
|
||||
<div class="section-title-left"><h3>耗材关联</h3></div>
|
||||
<div class="section-title-actions">
|
||||
<el-button class="btn-main" type="primary" size="small" @click="openAddConsumableDialog">
|
||||
<el-button
|
||||
class="btn-main"
|
||||
type="primary"
|
||||
size="small"
|
||||
@click="openAddConsumableDialog"
|
||||
>
|
||||
<el-icon><Plus /></el-icon>
|
||||
添加耗材
|
||||
</el-button>
|
||||
@@ -308,45 +377,89 @@
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
<el-table :data="consumableList" stripe class="inner-table">
|
||||
<el-table-column
|
||||
type="index"
|
||||
label="序号"
|
||||
min-width="56"
|
||||
align="center"
|
||||
header-align="center"
|
||||
:index="(index) => index + 1"
|
||||
<el-table :data="consumableList" stripe class="inner-table">
|
||||
<el-table-column
|
||||
type="index"
|
||||
label="序号"
|
||||
min-width="56"
|
||||
align="center"
|
||||
header-align="center"
|
||||
:index="(index) => index + 1"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="consumableCode"
|
||||
label="耗材编码"
|
||||
min-width="110"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="consumableName"
|
||||
label="耗材名称"
|
||||
min-width="120"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="specModel"
|
||||
label="规格型号"
|
||||
min-width="110"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
label="标准用量"
|
||||
min-width="110"
|
||||
align="center"
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<el-input
|
||||
v-model="row.standardUsage"
|
||||
placeholder="用量"
|
||||
class="inner-cell-input inner-cell-input--usage"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="unit"
|
||||
label="单位"
|
||||
min-width="80"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column label="必填" min-width="80" align="center" header-align="center">
|
||||
<template #default="{ row }">
|
||||
<el-switch v-model="row.isRequired" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
label="备注"
|
||||
min-width="120"
|
||||
align="center"
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<el-input v-model="row.remark" placeholder="备注" class="inner-cell-input" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" min-width="80" align="center" header-align="center">
|
||||
<template #default="{ $index }">
|
||||
<el-button
|
||||
link
|
||||
type="danger"
|
||||
size="small"
|
||||
@click="consumableList.splice($index, 1)"
|
||||
>删除</el-button
|
||||
>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<el-empty
|
||||
v-if="consumableList.length === 0"
|
||||
description="暂无耗材,点击上方按钮添加"
|
||||
:image-size="48"
|
||||
/>
|
||||
<el-table-column prop="consumableCode" label="耗材编码" min-width="110" align="center" header-align="center" />
|
||||
<el-table-column prop="consumableName" label="耗材名称" min-width="120" align="center" header-align="center" />
|
||||
<el-table-column prop="specModel" label="规格型号" min-width="110" align="center" header-align="center" />
|
||||
<el-table-column label="标准用量" min-width="110" align="center" header-align="center">
|
||||
<template #default="{ row }">
|
||||
<el-input
|
||||
v-model="row.standardUsage"
|
||||
placeholder="用量"
|
||||
class="inner-cell-input inner-cell-input--usage"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="unit" label="单位" min-width="80" align="center" header-align="center" />
|
||||
<el-table-column label="必填" min-width="80" align="center" header-align="center">
|
||||
<template #default="{ row }">
|
||||
<el-switch v-model="row.isRequired" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="备注" min-width="120" align="center" header-align="center">
|
||||
<template #default="{ row }">
|
||||
<el-input v-model="row.remark" placeholder="备注" class="inner-cell-input" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" min-width="80" align="center" header-align="center">
|
||||
<template #default="{ $index }">
|
||||
<el-button link type="danger" size="small" @click="consumableList.splice($index, 1)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<el-empty v-if="consumableList.length === 0" description="暂无耗材,点击上方按钮添加" :image-size="48" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -359,46 +472,84 @@
|
||||
添加产出物
|
||||
</el-button>
|
||||
</div>
|
||||
<el-table :data="outputList" stripe class="inner-table">
|
||||
<el-table-column label="产出物类型" min-width="150" align="center" header-align="center">
|
||||
<template #default="{ row }">
|
||||
<el-select
|
||||
v-model="row.outputTypeId"
|
||||
placeholder="选择类型"
|
||||
filterable
|
||||
class="inner-cell-select"
|
||||
>
|
||||
<el-option
|
||||
v-for="ot in outputTypeOptions"
|
||||
:key="ot.id"
|
||||
:label="ot.typeName || ot.name"
|
||||
:value="ot.id"
|
||||
<el-table :data="outputList" stripe class="inner-table">
|
||||
<el-table-column
|
||||
label="产出物类型"
|
||||
min-width="150"
|
||||
align="center"
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<el-select
|
||||
v-model="row.outputTypeId"
|
||||
placeholder="选择类型"
|
||||
filterable
|
||||
class="inner-cell-select"
|
||||
>
|
||||
<el-option
|
||||
v-for="ot in outputTypeOptions"
|
||||
:key="ot.id"
|
||||
:label="ot.typeName || ot.name"
|
||||
:value="ot.id"
|
||||
/>
|
||||
</el-select>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
label="产出物名称"
|
||||
min-width="150"
|
||||
align="center"
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<el-input
|
||||
v-model="row.outputName"
|
||||
placeholder="名称"
|
||||
class="inner-cell-input"
|
||||
/>
|
||||
</el-select>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="产出物名称" min-width="150" align="center" header-align="center">
|
||||
<template #default="{ row }">
|
||||
<el-input v-model="row.outputName" placeholder="名称" class="inner-cell-input" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="产出物条码" min-width="150" align="center" header-align="center">
|
||||
<template #default="{ row }">
|
||||
<el-input v-model="row.outputBarcode" placeholder="条码" class="inner-cell-input" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="备注" min-width="120" align="center" header-align="center">
|
||||
<template #default="{ row }">
|
||||
<el-input v-model="row.remark" placeholder="备注" class="inner-cell-input" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" min-width="80" align="center" header-align="center">
|
||||
<template #default="{ $index }">
|
||||
<el-button link type="danger" size="small" @click="outputList.splice($index, 1)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<el-empty v-if="outputList.length === 0" description="暂无产出物,点击上方按钮添加" :image-size="48" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
label="产出物条码"
|
||||
min-width="150"
|
||||
align="center"
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<el-input
|
||||
v-model="row.outputBarcode"
|
||||
placeholder="条码"
|
||||
class="inner-cell-input"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
label="备注"
|
||||
min-width="120"
|
||||
align="center"
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<el-input v-model="row.remark" placeholder="备注" class="inner-cell-input" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" min-width="80" align="center" header-align="center">
|
||||
<template #default="{ $index }">
|
||||
<el-button
|
||||
link
|
||||
type="danger"
|
||||
size="small"
|
||||
@click="outputList.splice($index, 1)"
|
||||
>删除</el-button
|
||||
>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<el-empty
|
||||
v-if="outputList.length === 0"
|
||||
description="暂无产出物,点击上方按钮添加"
|
||||
:image-size="48"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -409,76 +560,123 @@
|
||||
<h3>数据采集</h3>
|
||||
<span class="inner-table-sort-tip">拖拽左侧手柄可调整排序</span>
|
||||
</div>
|
||||
<el-button class="btn-main" type="primary" size="small" @click="addDataCollectionRow">
|
||||
<el-button
|
||||
class="btn-main"
|
||||
type="primary"
|
||||
size="small"
|
||||
@click="addDataCollectionRow"
|
||||
>
|
||||
<el-icon><Plus /></el-icon>
|
||||
添加采集项
|
||||
</el-button>
|
||||
</div>
|
||||
<el-table
|
||||
ref="dataCollectionTableRef"
|
||||
:data="dataCollectionList"
|
||||
row-key="_clientKey"
|
||||
stripe
|
||||
class="inner-table data-collection-sort-table"
|
||||
>
|
||||
<el-table-column label="" min-width="52" align="center" header-align="center" class-name="drag-col">
|
||||
<template #default>
|
||||
<el-icon class="data-collection-drag-handle" title="拖拽排序"><Rank /></el-icon>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
type="index"
|
||||
label="序号"
|
||||
min-width="56"
|
||||
align="center"
|
||||
header-align="center"
|
||||
:index="(index) => index + 1"
|
||||
/>
|
||||
<el-table-column label="采集项名称" min-width="180" align="center" header-align="center">
|
||||
<template #default="{ row }">
|
||||
<el-input v-model="row.collectionName" placeholder="请输入采集项名称" class="inner-cell-input" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="单位" min-width="150" align="center" header-align="center">
|
||||
<template #default="{ row }">
|
||||
<el-select
|
||||
v-model="row.unit"
|
||||
placeholder="请选择单位"
|
||||
clearable
|
||||
filterable
|
||||
remote
|
||||
reserve-keyword
|
||||
class="inner-cell-select"
|
||||
:loading="itemUnitOptionsLoading"
|
||||
:remote-method="searchItemUnitOptions"
|
||||
@visible-change="(visible) => visible && searchItemUnitOptions('')"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in itemUnitOptions"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
<el-table
|
||||
ref="dataCollectionTableRef"
|
||||
:data="dataCollectionList"
|
||||
row-key="_clientKey"
|
||||
stripe
|
||||
class="inner-table data-collection-sort-table"
|
||||
>
|
||||
<el-table-column
|
||||
label=""
|
||||
min-width="52"
|
||||
align="center"
|
||||
header-align="center"
|
||||
class-name="drag-col"
|
||||
>
|
||||
<template #default>
|
||||
<el-icon class="data-collection-drag-handle" title="拖拽排序"
|
||||
><Rank
|
||||
/></el-icon>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
type="index"
|
||||
label="序号"
|
||||
min-width="56"
|
||||
align="center"
|
||||
header-align="center"
|
||||
:index="(index) => index + 1"
|
||||
/>
|
||||
<el-table-column
|
||||
label="采集项名称"
|
||||
min-width="180"
|
||||
align="center"
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<el-input
|
||||
v-model="row.collectionName"
|
||||
placeholder="请输入采集项名称"
|
||||
class="inner-cell-input"
|
||||
/>
|
||||
</el-select>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="排序号" min-width="88" align="center" header-align="center">
|
||||
<template #default="{ $index }">
|
||||
<span class="data-collection-order-num">{{ $index + 1 }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="备注" min-width="120" align="center" header-align="center">
|
||||
<template #default="{ row }">
|
||||
<el-input v-model="row.remark" placeholder="备注" class="inner-cell-input" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" min-width="80" align="center" header-align="center">
|
||||
<template #default="{ $index }">
|
||||
<el-button link type="danger" size="small" @click="removeDataCollectionRow($index)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<el-empty v-if="dataCollectionList.length === 0" description="暂无采集项,点击上方按钮添加" :image-size="48" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
label="单位"
|
||||
min-width="150"
|
||||
align="center"
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<el-select
|
||||
v-model="row.unit"
|
||||
placeholder="请选择单位"
|
||||
clearable
|
||||
filterable
|
||||
remote
|
||||
reserve-keyword
|
||||
class="inner-cell-select"
|
||||
:loading="itemUnitOptionsLoading"
|
||||
:remote-method="searchItemUnitOptions"
|
||||
@visible-change="(visible) => visible && searchItemUnitOptions('')"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in itemUnitOptions"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
/>
|
||||
</el-select>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
label="排序号"
|
||||
min-width="88"
|
||||
align="center"
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ $index }">
|
||||
<span class="data-collection-order-num">{{ $index + 1 }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
label="备注"
|
||||
min-width="120"
|
||||
align="center"
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<el-input v-model="row.remark" placeholder="备注" class="inner-cell-input" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" min-width="80" align="center" header-align="center">
|
||||
<template #default="{ $index }">
|
||||
<el-button
|
||||
link
|
||||
type="danger"
|
||||
size="small"
|
||||
@click="removeDataCollectionRow($index)"
|
||||
>删除</el-button
|
||||
>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<el-empty
|
||||
v-if="dataCollectionList.length === 0"
|
||||
description="暂无采集项,点击上方按钮添加"
|
||||
:image-size="48"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</el-form>
|
||||
@@ -526,14 +724,65 @@
|
||||
row-key="id"
|
||||
@selection-change="handleReagentSelectionChange"
|
||||
>
|
||||
<el-table-column type="selection" min-width="48" align="center" header-align="center" :reserve-selection="true" />
|
||||
<el-table-column prop="reagentCode" label="试剂编码" min-width="120" align="center" header-align="center" />
|
||||
<el-table-column prop="reagentName" label="试剂名称" min-width="140" align="center" header-align="center" />
|
||||
<el-table-column prop="spec" label="规格" min-width="100" align="center" header-align="center" />
|
||||
<el-table-column prop="standardUsage" label="标准用量" min-width="110" align="center" header-align="center" show-overflow-tooltip />
|
||||
<el-table-column prop="unit" label="单位" min-width="80" align="center" header-align="center" />
|
||||
<el-table-column prop="remark" label="备注" min-width="120" align="center" header-align="center" show-overflow-tooltip />
|
||||
<el-table-column prop="description" label="描述" min-width="160" align="center" header-align="center" show-overflow-tooltip />
|
||||
<el-table-column
|
||||
type="selection"
|
||||
min-width="48"
|
||||
align="center"
|
||||
header-align="center"
|
||||
:reserve-selection="true"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="reagentCode"
|
||||
label="试剂编码"
|
||||
min-width="120"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="reagentName"
|
||||
label="试剂名称"
|
||||
min-width="140"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="spec"
|
||||
label="规格"
|
||||
min-width="100"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="standardUsage"
|
||||
label="标准用量"
|
||||
min-width="110"
|
||||
align="center"
|
||||
header-align="center"
|
||||
show-overflow-tooltip
|
||||
/>
|
||||
<el-table-column
|
||||
prop="unit"
|
||||
label="单位"
|
||||
min-width="80"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="remark"
|
||||
label="备注"
|
||||
min-width="120"
|
||||
align="center"
|
||||
header-align="center"
|
||||
show-overflow-tooltip
|
||||
/>
|
||||
<el-table-column
|
||||
prop="description"
|
||||
label="描述"
|
||||
min-width="160"
|
||||
align="center"
|
||||
header-align="center"
|
||||
show-overflow-tooltip
|
||||
/>
|
||||
</el-table>
|
||||
<template #footer>
|
||||
<el-button class="btn-ghost" @click="selectReagentVisible = false">取消</el-button>
|
||||
@@ -578,14 +827,65 @@
|
||||
row-key="id"
|
||||
@selection-change="handleConsumableSelectionChange"
|
||||
>
|
||||
<el-table-column type="selection" min-width="48" align="center" header-align="center" :reserve-selection="true" />
|
||||
<el-table-column prop="consumableCode" label="耗材编码" min-width="120" align="center" header-align="center" />
|
||||
<el-table-column prop="consumableName" label="耗材名称" min-width="140" align="center" header-align="center" />
|
||||
<el-table-column prop="specModel" label="规格型号" min-width="120" align="center" header-align="center" />
|
||||
<el-table-column prop="standardUsage" label="标准用量" min-width="110" align="center" header-align="center" show-overflow-tooltip />
|
||||
<el-table-column prop="unit" label="单位" min-width="80" align="center" header-align="center" />
|
||||
<el-table-column prop="remark" label="备注" min-width="120" align="center" header-align="center" show-overflow-tooltip />
|
||||
<el-table-column prop="description" label="描述" min-width="160" align="center" header-align="center" show-overflow-tooltip />
|
||||
<el-table-column
|
||||
type="selection"
|
||||
min-width="48"
|
||||
align="center"
|
||||
header-align="center"
|
||||
:reserve-selection="true"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="consumableCode"
|
||||
label="耗材编码"
|
||||
min-width="120"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="consumableName"
|
||||
label="耗材名称"
|
||||
min-width="140"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="specModel"
|
||||
label="规格型号"
|
||||
min-width="120"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="standardUsage"
|
||||
label="标准用量"
|
||||
min-width="110"
|
||||
align="center"
|
||||
header-align="center"
|
||||
show-overflow-tooltip
|
||||
/>
|
||||
<el-table-column
|
||||
prop="unit"
|
||||
label="单位"
|
||||
min-width="80"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="remark"
|
||||
label="备注"
|
||||
min-width="120"
|
||||
align="center"
|
||||
header-align="center"
|
||||
show-overflow-tooltip
|
||||
/>
|
||||
<el-table-column
|
||||
prop="description"
|
||||
label="描述"
|
||||
min-width="160"
|
||||
align="center"
|
||||
header-align="center"
|
||||
show-overflow-tooltip
|
||||
/>
|
||||
</el-table>
|
||||
<template #footer>
|
||||
<el-button class="btn-ghost" @click="selectConsumableVisible = false">取消</el-button>
|
||||
@@ -630,9 +930,28 @@
|
||||
<el-radio v-model="selectedPackId" :value="row.id" @click.stop />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="packName" label="耗材包名称" min-width="160" align="center" header-align="center" />
|
||||
<el-table-column prop="packCode" label="编码" min-width="120" align="center" header-align="center" />
|
||||
<el-table-column prop="usageScene" label="使用场景" min-width="140" align="center" header-align="center" show-overflow-tooltip />
|
||||
<el-table-column
|
||||
prop="packName"
|
||||
label="耗材包名称"
|
||||
min-width="160"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="packCode"
|
||||
label="编码"
|
||||
min-width="120"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="usageScene"
|
||||
label="使用场景"
|
||||
min-width="140"
|
||||
align="center"
|
||||
header-align="center"
|
||||
show-overflow-tooltip
|
||||
/>
|
||||
<el-table-column label="类型" min-width="130" align="center" header-align="center">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="packTypeTagType(row.packType)" size="small">
|
||||
@@ -647,7 +966,14 @@
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="remark" label="备注" min-width="120" align="center" header-align="center" show-overflow-tooltip />
|
||||
<el-table-column
|
||||
prop="remark"
|
||||
label="备注"
|
||||
min-width="120"
|
||||
align="center"
|
||||
header-align="center"
|
||||
show-overflow-tooltip
|
||||
/>
|
||||
</el-table>
|
||||
<template #footer>
|
||||
<el-button class="btn-ghost" @click="importPackVisible = false">取消</el-button>
|
||||
@@ -1001,7 +1327,10 @@
|
||||
return true
|
||||
}
|
||||
|
||||
function appendConsumableFromMaster(c: any, preset?: { standardUsage?: string; remark?: string }) {
|
||||
function appendConsumableFromMaster(
|
||||
c: any,
|
||||
preset?: { standardUsage?: string; remark?: string }
|
||||
) {
|
||||
if (isConsumableDuplicate(c.id || c.consumableId, c.consumableCode)) return false
|
||||
consumableList.value.push({
|
||||
consumableId: c.consumableId || c.id,
|
||||
@@ -1040,10 +1369,7 @@
|
||||
dialogLoading.value = true
|
||||
nextTick(() => formRef.value?.clearValidate?.())
|
||||
try {
|
||||
const [, detail] = await Promise.all([
|
||||
loadReferenceData(),
|
||||
fetchProcessDetail(row.id)
|
||||
])
|
||||
const [, detail] = await Promise.all([loadReferenceData(), fetchProcessDetail(row.id)])
|
||||
formData.processCode = detail.processCode || ''
|
||||
formData.processName = detail.processName || ''
|
||||
formData.categoryId = detail.categoryId || ''
|
||||
@@ -1160,7 +1486,9 @@
|
||||
const ids = selectedRows.value.map((r) => r.id)
|
||||
if (!ids.length) return
|
||||
try {
|
||||
await ElMessageBox.confirm(`确定删除选中的 ${ids.length} 个工艺吗?`, '提示', { type: 'warning' })
|
||||
await ElMessageBox.confirm(`确定删除选中的 ${ids.length} 个工艺吗?`, '提示', {
|
||||
type: 'warning'
|
||||
})
|
||||
} catch {
|
||||
return
|
||||
}
|
||||
@@ -1451,7 +1779,9 @@
|
||||
padding: 22px 24px;
|
||||
margin-bottom: 20px;
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.06);
|
||||
transition: transform 0.2s, box-shadow 0.2s;
|
||||
transition:
|
||||
transform 0.2s,
|
||||
box-shadow 0.2s;
|
||||
position: relative;
|
||||
background: #fff;
|
||||
|
||||
|
||||
@@ -15,10 +15,20 @@
|
||||
<el-form :model="searchForm" class="filter-form" @submit.prevent>
|
||||
<div class="filter-grid">
|
||||
<el-form-item label="关键词">
|
||||
<el-input v-model="searchForm.keyword" placeholder="任务号/所属工单号/协议号/样本编码" clearable @keyup.enter="handleSearch" />
|
||||
<el-input
|
||||
v-model="searchForm.keyword"
|
||||
placeholder="任务号/所属工单号/协议号/样本编码"
|
||||
clearable
|
||||
@keyup.enter="handleSearch"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="任务状态">
|
||||
<el-select v-model="searchForm.taskStatus" placeholder="全部" clearable style="width: 100%">
|
||||
<el-select
|
||||
v-model="searchForm.taskStatus"
|
||||
placeholder="全部"
|
||||
clearable
|
||||
style="width: 100%"
|
||||
>
|
||||
<el-option label="待配置" value="dpz" />
|
||||
<el-option label="进行中" value="jxz" />
|
||||
<el-option label="已完成" value="ywc" />
|
||||
@@ -37,10 +47,16 @@
|
||||
</el-form-item>
|
||||
</div>
|
||||
<div class="filter-actions-row">
|
||||
<span class="filter-hint">默认展示近 30 天内已开始的任务;清空日期可查看尚未开始的待配置任务。</span>
|
||||
<span class="filter-hint"
|
||||
>默认展示近 30 天内已开始的任务;清空日期可查看尚未开始的待配置任务。</span
|
||||
>
|
||||
<div class="filter-actions-end">
|
||||
<el-button class="btn-ghost" @click="handleReset"><el-icon><RefreshLeft /></el-icon>重置</el-button>
|
||||
<el-button class="btn-main" type="primary" @click="handleSearch"><el-icon><Search /></el-icon>搜索</el-button>
|
||||
<el-button class="btn-ghost" @click="handleReset"
|
||||
><el-icon><RefreshLeft /></el-icon>重置</el-button
|
||||
>
|
||||
<el-button class="btn-main" type="primary" @click="handleSearch"
|
||||
><el-icon><Search /></el-icon>搜索</el-button
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</el-form>
|
||||
@@ -54,7 +70,13 @@
|
||||
stripe
|
||||
@row-dblclick="handleViewDetail"
|
||||
>
|
||||
<el-table-column type="index" label="序号" min-width="68" align="center" :index="(i: number) => (pagination.page - 1) * pagination.pageSize + i + 1" />
|
||||
<el-table-column
|
||||
type="index"
|
||||
label="序号"
|
||||
min-width="68"
|
||||
align="center"
|
||||
:index="(i: number) => (pagination.page - 1) * pagination.pageSize + i + 1"
|
||||
/>
|
||||
<el-table-column prop="taskNo" label="任务号" min-width="150" align="center" />
|
||||
<el-table-column prop="workOrderNo" label="所属工单号" min-width="140" align="center" />
|
||||
<el-table-column prop="orderSource" label="来源" min-width="100" align="center">
|
||||
@@ -72,9 +94,20 @@
|
||||
<span v-if="shouldShowTempEmptyField(row, row.protocolStatus)">—</span>
|
||||
<el-tag
|
||||
v-else
|
||||
:type="protocolStatusTagTypeBySource(String(row.protocolStatus || ''), row.receptionSource || 'cell_storage')"
|
||||
:type="
|
||||
protocolStatusTagTypeBySource(
|
||||
String(row.protocolStatus || ''),
|
||||
row.receptionSource || 'cell_storage'
|
||||
)
|
||||
"
|
||||
size="small"
|
||||
>{{ protocolStatusLabelBySource(String(row.protocolStatus || ''), row.receptionSource || 'cell_storage') }}</el-tag>
|
||||
>{{
|
||||
protocolStatusLabelBySource(
|
||||
String(row.protocolStatus || ''),
|
||||
row.receptionSource || 'cell_storage'
|
||||
)
|
||||
}}</el-tag
|
||||
>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="sampleCode" label="样本编码" min-width="130" align="center">
|
||||
@@ -85,11 +118,25 @@
|
||||
</el-table-column>
|
||||
<el-table-column prop="taskType" label="任务类型" min-width="120" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="taskTypeTag(row.taskType, row.orderSource).type" size="small">{{ taskTypeTag(row.taskType, row.orderSource).label }}</el-tag>
|
||||
<el-tag :type="taskTypeTag(row.taskType, row.orderSource).type" size="small">{{
|
||||
taskTypeTag(row.taskType, row.orderSource).label
|
||||
}}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="taskName" label="任务名称" min-width="160" align="center" show-overflow-tooltip />
|
||||
<el-table-column prop="routeName" label="工艺路线" min-width="140" align="center" show-overflow-tooltip>
|
||||
<el-table-column
|
||||
prop="taskName"
|
||||
label="任务名称"
|
||||
min-width="160"
|
||||
align="center"
|
||||
show-overflow-tooltip
|
||||
/>
|
||||
<el-table-column
|
||||
prop="routeName"
|
||||
label="工艺路线"
|
||||
min-width="140"
|
||||
align="center"
|
||||
show-overflow-tooltip
|
||||
>
|
||||
<template #default="{ row }">{{ row.routeName || '—' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="receiverName" label="接收人" min-width="100" align="center">
|
||||
@@ -100,7 +147,9 @@
|
||||
</el-table-column>
|
||||
<el-table-column prop="taskStatus" label="任务状态" min-width="110" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="taskStatusTag(row.taskStatus).type" size="small">{{ taskStatusTag(row.taskStatus).label }}</el-tag>
|
||||
<el-tag :type="taskStatusTag(row.taskStatus).type" size="small">{{
|
||||
taskStatusTag(row.taskStatus).label
|
||||
}}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="conclusion" label="生产结论" min-width="100" align="center">
|
||||
@@ -123,12 +172,21 @@
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" min-width="400" fixed="right" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-button class="btn-op-item primary" @click="handleEnterExecution(row)"><el-icon><VideoPlay /></el-icon>进入执行</el-button>
|
||||
<el-button class="btn-op-item primary" @click="handleEnterExecution(row)"
|
||||
><el-icon><VideoPlay /></el-icon>进入执行</el-button
|
||||
>
|
||||
<el-button class="btn-op-item" @click="handleEditTask(row)">
|
||||
<el-icon><EditPen /></el-icon>编辑
|
||||
</el-button>
|
||||
<el-button class="btn-op-item" @click="handleViewDetail(row)"><el-icon><View /></el-icon>查看详情</el-button>
|
||||
<el-button v-if="row.taskStatus === 'dpz'" class="btn-op-item" @click="handleDelete(row)">删除</el-button>
|
||||
<el-button class="btn-op-item" @click="handleViewDetail(row)"
|
||||
><el-icon><View /></el-icon>查看详情</el-button
|
||||
>
|
||||
<el-button
|
||||
v-if="row.taskStatus === 'dpz'"
|
||||
class="btn-op-item"
|
||||
@click="handleDelete(row)"
|
||||
>删除</el-button
|
||||
>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
@@ -139,7 +197,9 @@
|
||||
:total="pagination.total"
|
||||
:page-sizes="[10, 20, 50]"
|
||||
layout="total, sizes, prev, pager, next"
|
||||
@size-change="(val: number) => ((pagination.page = 1), (pagination.pageSize = val), loadData())"
|
||||
@size-change="
|
||||
(val: number) => ((pagination.page = 1), (pagination.pageSize = val), loadData())
|
||||
"
|
||||
@current-change="(val: number) => ((pagination.page = val), loadData())"
|
||||
/>
|
||||
</div>
|
||||
@@ -169,7 +229,9 @@
|
||||
@keyup.enter="searchWorkOrders"
|
||||
>
|
||||
<template #append>
|
||||
<el-button @click="searchWorkOrders"><el-icon><Search /></el-icon></el-button>
|
||||
<el-button @click="searchWorkOrders"
|
||||
><el-icon><Search /></el-icon
|
||||
></el-button>
|
||||
</template>
|
||||
</el-input>
|
||||
</div>
|
||||
@@ -191,8 +253,14 @@
|
||||
</el-table-column>
|
||||
<el-table-column prop="receptionSource" label="来源" min-width="100" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="row.receptionSource === 'research_donation' ? 'warning' : 'success'" size="small">
|
||||
{{ row.receptionSourceLabel || (row.receptionSource === 'research_donation' ? '科研捐赠' : '细胞存储') }}
|
||||
<el-tag
|
||||
:type="row.receptionSource === 'research_donation' ? 'warning' : 'success'"
|
||||
size="small"
|
||||
>
|
||||
{{
|
||||
row.receptionSourceLabel ||
|
||||
(row.receptionSource === 'research_donation' ? '科研捐赠' : '细胞存储')
|
||||
}}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
@@ -205,11 +273,17 @@
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" min-width="100" align="center" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<el-button link type="primary" size="small" @click.stop="selectWorkOrder(row)">选择</el-button>
|
||||
<el-button link type="primary" size="small" @click.stop="selectWorkOrder(row)"
|
||||
>选择</el-button
|
||||
>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<el-empty v-if="!addSelectWoLoading && !addSelectWoList.length" description="暂无可选工单" :image-size="48" />
|
||||
<el-empty
|
||||
v-if="!addSelectWoLoading && !addSelectWoList.length"
|
||||
description="暂无可选工单"
|
||||
:image-size="48"
|
||||
/>
|
||||
</el-dialog>
|
||||
|
||||
<DispatchTaskDialog
|
||||
@@ -229,30 +303,40 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { RefreshLeft, Search, VideoPlay, View, Plus, Close, Check, EditPen } from '@element-plus/icons-vue'
|
||||
import {
|
||||
RefreshLeft,
|
||||
Search,
|
||||
VideoPlay,
|
||||
View,
|
||||
Plus,
|
||||
Close,
|
||||
Check,
|
||||
EditPen
|
||||
} from '@element-plus/icons-vue'
|
||||
import { fetchProductionTaskList, fetchDeleteProductionTask } from '@/api/production-task'
|
||||
import { fetchProductionWorkOrderList } from '@/api/production-work-order'
|
||||
import {
|
||||
protocolStatusLabel,
|
||||
protocolStatusTagType,
|
||||
donationStatusLabel,
|
||||
donationStatusTagType,
|
||||
donationStatusTagType
|
||||
} from '@/utils/status-tag-registry'
|
||||
import type { TagType } from '@/utils/status-tag-registry'
|
||||
import TaskExecutionDialog from '../production-work-order/TaskExecutionDialog.vue'
|
||||
import DispatchTaskDialog from '../production-work-order/DispatchTaskDialog.vue'
|
||||
import EditTaskInfoDialog from '../production-work-order/EditTaskInfoDialog.vue'
|
||||
import {
|
||||
productionConclusionLabel,
|
||||
productionConclusionTagType
|
||||
} from '@/utils/production-labels'
|
||||
import { productionConclusionLabel, productionConclusionTagType } from '@/utils/production-labels'
|
||||
|
||||
/** 根据来源类型返回协议状态标签 */
|
||||
function protocolStatusLabelBySource(status: string, source: string): string {
|
||||
return source === 'research_donation' ? donationStatusLabel(status) : protocolStatusLabel(status)
|
||||
return source === 'research_donation'
|
||||
? donationStatusLabel(status)
|
||||
: protocolStatusLabel(status)
|
||||
}
|
||||
function protocolStatusTagTypeBySource(status: string, source: string): TagType {
|
||||
return source === 'research_donation' ? donationStatusTagType(status) : protocolStatusTagType(status)
|
||||
return source === 'research_donation'
|
||||
? donationStatusTagType(status)
|
||||
: protocolStatusTagType(status)
|
||||
}
|
||||
|
||||
/** 临时工单无关联协议/样本时,空字段展示为 — */
|
||||
|
||||
+10
-2
@@ -11,7 +11,13 @@
|
||||
:append-to-body="true"
|
||||
@close="handleClose"
|
||||
>
|
||||
<el-form ref="formRef" :model="form" :rules="rules" label-position="top" class="biz-dialog-form">
|
||||
<el-form
|
||||
ref="formRef"
|
||||
:model="form"
|
||||
:rules="rules"
|
||||
label-position="top"
|
||||
class="biz-dialog-form"
|
||||
>
|
||||
<el-form-item label="生产结论" prop="conclusion">
|
||||
<el-radio-group v-model="form.conclusion">
|
||||
<el-radio value="cg">成功</el-radio>
|
||||
@@ -29,7 +35,9 @@
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button class="btn-ghost" @click="handleClose"><el-icon><Close /></el-icon>取消</el-button>
|
||||
<el-button class="btn-ghost" @click="handleClose"
|
||||
><el-icon><Close /></el-icon>取消</el-button
|
||||
>
|
||||
<el-button class="btn-main" type="primary" :loading="submitting" @click="handleSubmit">
|
||||
<el-icon><Check /></el-icon>{{ isCompletedEdit ? '保存' : '确认完成' }}
|
||||
</el-button>
|
||||
|
||||
@@ -298,7 +298,10 @@
|
||||
form.routeId = ''
|
||||
form.description = ''
|
||||
form.remark = ''
|
||||
if (props.workOrderRow?.workOrderType === 'temporary' && props.workOrderRow?.workOrderNo) {
|
||||
if (
|
||||
props.workOrderRow?.workOrderType === 'temporary' &&
|
||||
props.workOrderRow?.workOrderNo
|
||||
) {
|
||||
form.taskName = `${props.workOrderRow.workOrderNo}任务`
|
||||
}
|
||||
loadRouteOptions()
|
||||
|
||||
@@ -52,10 +52,7 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, computed, watch } from 'vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import {
|
||||
fetchProductionTaskDetail,
|
||||
fetchUpdateProductionTask
|
||||
} from '@/api/production-task'
|
||||
import { fetchProductionTaskDetail, fetchUpdateProductionTask } from '@/api/production-task'
|
||||
import type { FormInstance, FormRules } from 'element-plus'
|
||||
|
||||
const props = defineProps<{
|
||||
|
||||
@@ -136,7 +136,12 @@
|
||||
align="center"
|
||||
:index="(i: number) => i + 1"
|
||||
/>
|
||||
<el-table-column label="试剂编码" min-width="110" align="center" show-overflow-tooltip>
|
||||
<el-table-column
|
||||
label="试剂编码"
|
||||
min-width="110"
|
||||
align="center"
|
||||
show-overflow-tooltip
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<el-input
|
||||
v-if="!readonly"
|
||||
@@ -147,7 +152,12 @@
|
||||
<span v-else>{{ row.reagentCode || '—' }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="试剂名称" min-width="160" align="center" show-overflow-tooltip>
|
||||
<el-table-column
|
||||
label="试剂名称"
|
||||
min-width="160"
|
||||
align="center"
|
||||
show-overflow-tooltip
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<el-select
|
||||
v-if="!readonly"
|
||||
@@ -299,7 +309,12 @@
|
||||
align="center"
|
||||
:index="(i: number) => i + 1"
|
||||
/>
|
||||
<el-table-column label="耗材编码" min-width="110" align="center" show-overflow-tooltip>
|
||||
<el-table-column
|
||||
label="耗材编码"
|
||||
min-width="110"
|
||||
align="center"
|
||||
show-overflow-tooltip
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<el-input
|
||||
v-if="!readonly"
|
||||
@@ -310,7 +325,12 @@
|
||||
<span v-else>{{ row.consumableCode || '—' }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="耗材名称" min-width="160" align="center" show-overflow-tooltip>
|
||||
<el-table-column
|
||||
label="耗材名称"
|
||||
min-width="160"
|
||||
align="center"
|
||||
show-overflow-tooltip
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<el-select
|
||||
v-if="!readonly"
|
||||
@@ -336,7 +356,12 @@
|
||||
<span v-else>{{ row.consumableName || '—' }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="规格型号" min-width="110" align="center" show-overflow-tooltip>
|
||||
<el-table-column
|
||||
label="规格型号"
|
||||
min-width="110"
|
||||
align="center"
|
||||
show-overflow-tooltip
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<el-input
|
||||
v-if="!readonly"
|
||||
@@ -457,7 +482,12 @@
|
||||
align="center"
|
||||
:index="(i: number) => i + 1"
|
||||
/>
|
||||
<el-table-column label="产出类型" min-width="150" align="center" show-overflow-tooltip>
|
||||
<el-table-column
|
||||
label="产出类型"
|
||||
min-width="150"
|
||||
align="center"
|
||||
show-overflow-tooltip
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<el-select
|
||||
v-if="!readonly"
|
||||
@@ -552,7 +582,12 @@
|
||||
align="center"
|
||||
:index="(i: number) => i + 1"
|
||||
/>
|
||||
<el-table-column label="采集名称" min-width="180" align="center" show-overflow-tooltip>
|
||||
<el-table-column
|
||||
label="采集名称"
|
||||
min-width="180"
|
||||
align="center"
|
||||
show-overflow-tooltip
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<el-input
|
||||
v-if="!readonly"
|
||||
|
||||
@@ -1,329 +1,340 @@
|
||||
<template>
|
||||
<div class="task-execution-dialog-root">
|
||||
<el-dialog
|
||||
v-model="dialogVisible"
|
||||
:title="readonly ? `任务详情 — ${taskName}` : `任务执行 — ${taskName}`"
|
||||
width="96vw"
|
||||
class="dialog-form-layout execution-dialog"
|
||||
align-center
|
||||
destroy-on-close
|
||||
:close-on-click-modal="false"
|
||||
:append-to-body="appendToBody"
|
||||
@close="handleClose"
|
||||
>
|
||||
<!-- 顶部:任务基本信息一行展示 -->
|
||||
<div v-if="editor.taskDetail.value" class="execution-header">
|
||||
<div class="execution-header-row">
|
||||
<span class="execution-header-item"
|
||||
>任务号:<b>{{ editor.taskDetail.value.taskNo }}</b></span
|
||||
>
|
||||
<span class="execution-header-item"
|
||||
>工单号:{{ editor.taskDetail.value.workOrderNo || '—' }}</span
|
||||
>
|
||||
<span class="execution-header-item"
|
||||
>任务名称:{{ editor.taskDetail.value.taskName || '—' }}</span
|
||||
>
|
||||
<el-tag :type="editor.taskStatusTag(editor.taskDetail.value.taskStatus).type">
|
||||
{{ editor.taskStatusTag(editor.taskDetail.value.taskStatus).label }}
|
||||
</el-tag>
|
||||
<span class="execution-header-item execution-header-conclusion">
|
||||
生产结论:
|
||||
<el-tag
|
||||
v-if="productionConclusionTagType(executionTaskConclusion)"
|
||||
:type="productionConclusionTagType(executionTaskConclusion)"
|
||||
size="small"
|
||||
<el-dialog
|
||||
v-model="dialogVisible"
|
||||
:title="readonly ? `任务详情 — ${taskName}` : `任务执行 — ${taskName}`"
|
||||
width="96vw"
|
||||
class="dialog-form-layout execution-dialog"
|
||||
align-center
|
||||
destroy-on-close
|
||||
:close-on-click-modal="false"
|
||||
:append-to-body="appendToBody"
|
||||
@close="handleClose"
|
||||
>
|
||||
<!-- 顶部:任务基本信息一行展示 -->
|
||||
<div v-if="editor.taskDetail.value" class="execution-header">
|
||||
<div class="execution-header-row">
|
||||
<span class="execution-header-item"
|
||||
>任务号:<b>{{ editor.taskDetail.value.taskNo }}</b></span
|
||||
>
|
||||
{{ productionConclusionLabel(executionTaskConclusion) }}
|
||||
<span class="execution-header-item"
|
||||
>工单号:{{ editor.taskDetail.value.workOrderNo || '—' }}</span
|
||||
>
|
||||
<span class="execution-header-item"
|
||||
>任务名称:{{ editor.taskDetail.value.taskName || '—' }}</span
|
||||
>
|
||||
<el-tag :type="editor.taskStatusTag(editor.taskDetail.value.taskStatus).type">
|
||||
{{ editor.taskStatusTag(editor.taskDetail.value.taskStatus).label }}
|
||||
</el-tag>
|
||||
<template v-else>—</template>
|
||||
</span>
|
||||
<div v-if="!readonly" class="execution-header-actions">
|
||||
<el-button
|
||||
v-if="editor.taskDetail.value.taskStatus === 'dpz'"
|
||||
class="btn-main"
|
||||
type="primary"
|
||||
size="small"
|
||||
:loading="editor.taskActionLoading.value"
|
||||
@click="editor.handleStartTask"
|
||||
>
|
||||
<el-icon><VideoPlay /></el-icon>开始执行
|
||||
</el-button>
|
||||
<el-button
|
||||
v-if="editor.taskDetail.value.taskStatus === 'jxz' || editor.taskDetail.value.taskStatus === 'ywc'"
|
||||
class="btn-main"
|
||||
type="success"
|
||||
size="small"
|
||||
:loading="editor.taskActionLoading.value"
|
||||
@click="editor.handleCompleteTask"
|
||||
>
|
||||
<el-icon><Check /></el-icon>完成任务
|
||||
</el-button>
|
||||
<span class="execution-header-item execution-header-conclusion">
|
||||
生产结论:
|
||||
<el-tag
|
||||
v-if="productionConclusionTagType(executionTaskConclusion)"
|
||||
:type="productionConclusionTagType(executionTaskConclusion)"
|
||||
size="small"
|
||||
>
|
||||
{{ productionConclusionLabel(executionTaskConclusion) }}
|
||||
</el-tag>
|
||||
<template v-else>—</template>
|
||||
</span>
|
||||
<div v-if="!readonly" class="execution-header-actions">
|
||||
<el-button
|
||||
v-if="editor.taskDetail.value.taskStatus === 'dpz'"
|
||||
class="btn-main"
|
||||
type="primary"
|
||||
size="small"
|
||||
:loading="editor.taskActionLoading.value"
|
||||
@click="editor.handleStartTask"
|
||||
>
|
||||
<el-icon><VideoPlay /></el-icon>开始执行
|
||||
</el-button>
|
||||
<el-button
|
||||
v-if="
|
||||
editor.taskDetail.value.taskStatus === 'jxz' ||
|
||||
editor.taskDetail.value.taskStatus === 'ywc'
|
||||
"
|
||||
class="btn-main"
|
||||
type="success"
|
||||
size="small"
|
||||
:loading="editor.taskActionLoading.value"
|
||||
@click="editor.handleCompleteTask"
|
||||
>
|
||||
<el-icon><Check /></el-icon>完成任务
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="editor.taskDetail.value.remark" class="execution-header-remark">{{
|
||||
editor.taskDetail.value.remark
|
||||
}}</div>
|
||||
</div>
|
||||
<div v-if="editor.taskDetail.value.remark" class="execution-header-remark">{{
|
||||
editor.taskDetail.value.remark
|
||||
}}</div>
|
||||
</div>
|
||||
|
||||
<!-- 主体:可折叠工艺库 + 步骤列表 + 详情面板 -->
|
||||
<div class="execution-main">
|
||||
<!-- 可折叠工艺库侧栏(只读时隐藏) -->
|
||||
<template v-if="!readonly">
|
||||
<transition name="slide-lib">
|
||||
<div v-show="editor.processLibraryExpanded.value" class="execution-lib-panel">
|
||||
<div class="execution-lib-header">
|
||||
<h3>工艺库</h3>
|
||||
<el-input
|
||||
v-model="editor.processSearchKeyword.value"
|
||||
placeholder="搜索工序"
|
||||
clearable
|
||||
size="small"
|
||||
class="route-config-search"
|
||||
<!-- 主体:可折叠工艺库 + 步骤列表 + 详情面板 -->
|
||||
<div class="execution-main">
|
||||
<!-- 可折叠工艺库侧栏(只读时隐藏) -->
|
||||
<template v-if="!readonly">
|
||||
<transition name="slide-lib">
|
||||
<div v-show="editor.processLibraryExpanded.value" class="execution-lib-panel">
|
||||
<div class="execution-lib-header">
|
||||
<h3>工艺库</h3>
|
||||
<el-input
|
||||
v-model="editor.processSearchKeyword.value"
|
||||
placeholder="搜索工序"
|
||||
clearable
|
||||
size="small"
|
||||
class="route-config-search"
|
||||
/>
|
||||
</div>
|
||||
<el-collapse v-model="editor.expandedCategories.value" class="route-config-collapse">
|
||||
<el-collapse-item
|
||||
v-for="cat in editor.filteredProcessLibrary.value"
|
||||
:key="cat.categoryId"
|
||||
:name="cat.categoryId"
|
||||
>
|
||||
<template #title>
|
||||
<span class="route-config-cat-bar"></span>
|
||||
<span>{{ cat.categoryName }}</span>
|
||||
</template>
|
||||
<div
|
||||
v-for="(proc, procIndex) in cat.processes"
|
||||
:key="proc.id"
|
||||
class="route-config-process-card"
|
||||
draggable="true"
|
||||
@dragstart="editor.onDragStart($event, proc)"
|
||||
>
|
||||
<div class="route-config-process-info">
|
||||
<div class="route-config-process-name">
|
||||
<span class="route-config-process-seq">{{ procIndex + 1 }}</span
|
||||
>{{ proc.processName }}
|
||||
</div>
|
||||
<div class="route-config-process-code">{{ proc.processCode }}</div>
|
||||
</div>
|
||||
<el-icon class="route-config-process-drag"><Rank /></el-icon>
|
||||
</div>
|
||||
<el-empty
|
||||
v-if="!cat.processes.length"
|
||||
description="该分类暂无工序"
|
||||
:image-size="32"
|
||||
/>
|
||||
</el-collapse-item>
|
||||
</el-collapse>
|
||||
<el-empty
|
||||
v-if="!editor.filteredProcessLibrary.value.length"
|
||||
description="暂无工艺数据"
|
||||
:image-size="48"
|
||||
/>
|
||||
</div>
|
||||
<el-collapse v-model="editor.expandedCategories.value" class="route-config-collapse">
|
||||
<el-collapse-item
|
||||
v-for="cat in editor.filteredProcessLibrary.value"
|
||||
:key="cat.categoryId"
|
||||
:name="cat.categoryId"
|
||||
>
|
||||
<template #title>
|
||||
<span class="route-config-cat-bar"></span>
|
||||
<span>{{ cat.categoryName }}</span>
|
||||
</template>
|
||||
<div
|
||||
v-for="(proc, procIndex) in cat.processes"
|
||||
:key="proc.id"
|
||||
class="route-config-process-card"
|
||||
draggable="true"
|
||||
@dragstart="editor.onDragStart($event, proc)"
|
||||
>
|
||||
<div class="route-config-process-info">
|
||||
<div class="route-config-process-name">
|
||||
<span class="route-config-process-seq">{{ procIndex + 1 }}</span
|
||||
>{{ proc.processName }}
|
||||
</div>
|
||||
<div class="route-config-process-code">{{ proc.processCode }}</div>
|
||||
</div>
|
||||
<el-icon class="route-config-process-drag"><Rank /></el-icon>
|
||||
</div>
|
||||
<el-empty
|
||||
v-if="!cat.processes.length"
|
||||
description="该分类暂无工序"
|
||||
:image-size="32"
|
||||
/>
|
||||
</el-collapse-item>
|
||||
</el-collapse>
|
||||
<el-empty
|
||||
v-if="!editor.filteredProcessLibrary.value.length"
|
||||
description="暂无工艺数据"
|
||||
:image-size="48"
|
||||
/>
|
||||
</div>
|
||||
</transition>
|
||||
</transition>
|
||||
|
||||
<!-- 工艺库折叠把手 -->
|
||||
<!-- 工艺库折叠把手 -->
|
||||
<div
|
||||
class="execution-lib-toggle"
|
||||
@click="editor.processLibraryExpanded.value = !editor.processLibraryExpanded.value"
|
||||
>
|
||||
<span>{{ editor.processLibraryExpanded.value ? '‹' : '›' }}</span>
|
||||
<span class="execution-lib-toggle-text">工艺库</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- 步骤列表 -->
|
||||
<div
|
||||
class="execution-lib-toggle"
|
||||
@click="editor.processLibraryExpanded.value = !editor.processLibraryExpanded.value"
|
||||
class="execution-step-list"
|
||||
v-loading="editor.stepListLoading.value"
|
||||
@dragover.prevent
|
||||
@drop="editor.onDropToCenter"
|
||||
>
|
||||
<span>{{ editor.processLibraryExpanded.value ? '‹' : '›' }}</span>
|
||||
<span class="execution-lib-toggle-text">工艺库</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- 步骤列表 -->
|
||||
<div
|
||||
class="execution-step-list"
|
||||
v-loading="editor.stepListLoading.value"
|
||||
@dragover.prevent
|
||||
@drop="editor.onDropToCenter"
|
||||
>
|
||||
<div class="execution-step-list-header">
|
||||
<h3>工序步骤</h3>
|
||||
<span class="execution-progress-badge"
|
||||
>{{ editor.completedStepCount.value }}/{{ editor.stepList.value.length }}</span
|
||||
>
|
||||
</div>
|
||||
<div class="execution-step-list-body">
|
||||
<!-- 虚拟步骤:任务输入 -->
|
||||
<div
|
||||
class="virtual-step-card"
|
||||
:class="{ 'is-active': editor.selectedVirtualStep.value === 'input' }"
|
||||
@click="editor.selectVirtualStep('input')"
|
||||
>
|
||||
<div class="virtual-step-icon virtual-step-icon--input">📥</div>
|
||||
<div class="virtual-step-info">
|
||||
<div class="virtual-step-name">原材料(样本/细胞产品)</div>
|
||||
</div>
|
||||
<el-tag v-if="editor.virtualInputStatus.value === 'filled'" type="success" size="small"
|
||||
>已填写 {{ editor.taskInputs.value.length }} 项</el-tag
|
||||
<div class="execution-step-list-header">
|
||||
<h3>工序步骤</h3>
|
||||
<span class="execution-progress-badge"
|
||||
>{{ editor.completedStepCount.value }}/{{ editor.stepList.value.length }}</span
|
||||
>
|
||||
<el-tag v-else type="danger" size="small">未填写</el-tag>
|
||||
</div>
|
||||
|
||||
<!-- 虚拟步骤:任务输出 -->
|
||||
<div
|
||||
class="virtual-step-card"
|
||||
:class="{ 'is-active': editor.selectedVirtualStep.value === 'output' }"
|
||||
@click="editor.selectVirtualStep('output')"
|
||||
>
|
||||
<div class="virtual-step-icon virtual-step-icon--output">📤</div>
|
||||
<div class="virtual-step-info">
|
||||
<div class="virtual-step-name">产出(细胞产品)</div>
|
||||
</div>
|
||||
<el-tag v-if="editor.virtualOutputStatus.value === 'filled'" type="success" size="small"
|
||||
>已填写 {{ editor.taskOutputs.value.length }} 项</el-tag
|
||||
>
|
||||
<el-tag v-else type="info" size="small">暂无</el-tag>
|
||||
</div>
|
||||
|
||||
<!-- 分割线 -->
|
||||
<div class="step-divider"><span>工艺步骤</span></div>
|
||||
|
||||
<!-- 真实步骤列表 -->
|
||||
<draggable
|
||||
v-model="editor.stepList.value"
|
||||
:handle="readonly ? undefined : '.route-config-step-handle'"
|
||||
:animation="200"
|
||||
@end="editor.onStepDragEnd"
|
||||
>
|
||||
<div class="execution-step-list-body">
|
||||
<!-- 虚拟步骤:任务输入 -->
|
||||
<div
|
||||
v-for="(element, index) in editor.stepList.value"
|
||||
:key="element.id"
|
||||
class="route-config-step-card"
|
||||
:class="{
|
||||
'is-active': editor.selectedStepId.value === element.id,
|
||||
'is-done': element.stepStatus === 'ywc'
|
||||
}"
|
||||
@click="editor.selectStep(element)"
|
||||
class="virtual-step-card"
|
||||
:class="{ 'is-active': editor.selectedVirtualStep.value === 'input' }"
|
||||
@click="editor.selectVirtualStep('input')"
|
||||
>
|
||||
<div v-if="!readonly" class="route-config-step-handle"
|
||||
><el-icon><Rank /></el-icon
|
||||
></div>
|
||||
<div class="route-config-step-order" :class="stepSeqClass(element.stepStatus)">{{
|
||||
element.stepStatus === 'ywc' ? '✓' : index + 1
|
||||
}}</div>
|
||||
<div class="route-config-step-info">
|
||||
<div class="route-config-step-name">{{ element.processName || '未命名步骤' }}</div>
|
||||
<div class="route-config-step-desc">{{ element.description || '' }}</div>
|
||||
<div class="virtual-step-icon virtual-step-icon--input">📥</div>
|
||||
<div class="virtual-step-info">
|
||||
<div class="virtual-step-name">原材料(样本/细胞产品)</div>
|
||||
</div>
|
||||
<el-tag
|
||||
v-if="element.stepStatus"
|
||||
:type="editor.stepStatusTag(element.stepStatus).type"
|
||||
v-if="editor.virtualInputStatus.value === 'filled'"
|
||||
type="success"
|
||||
size="small"
|
||||
style="margin-right: 4px"
|
||||
>{{ editor.stepStatusTag(element.stepStatus).label }}</el-tag
|
||||
>已填写 {{ editor.taskInputs.value.length }} 项</el-tag
|
||||
>
|
||||
<template v-if="!readonly">
|
||||
<el-button
|
||||
link
|
||||
type="primary"
|
||||
size="small"
|
||||
title="复制步骤"
|
||||
@click.stop="editor.handleCopyStep(element)"
|
||||
>
|
||||
<el-icon><CopyDocument /></el-icon>
|
||||
</el-button>
|
||||
<el-button
|
||||
link
|
||||
type="danger"
|
||||
size="small"
|
||||
@click.stop="editor.handleDeleteStep(element)"
|
||||
>
|
||||
<el-icon><Delete /></el-icon>
|
||||
</el-button>
|
||||
</template>
|
||||
<el-tag v-else type="danger" size="small">未填写</el-tag>
|
||||
</div>
|
||||
</draggable>
|
||||
|
||||
<div v-if="!editor.stepList.value.length" class="execution-step-list-empty">
|
||||
<el-icon :size="32" color="#a7f3d0"><Plus /></el-icon>
|
||||
<p>从左侧拖拽工序到此处,添加任务步骤</p>
|
||||
<!-- 虚拟步骤:任务输出 -->
|
||||
<div
|
||||
class="virtual-step-card"
|
||||
:class="{ 'is-active': editor.selectedVirtualStep.value === 'output' }"
|
||||
@click="editor.selectVirtualStep('output')"
|
||||
>
|
||||
<div class="virtual-step-icon virtual-step-icon--output">📤</div>
|
||||
<div class="virtual-step-info">
|
||||
<div class="virtual-step-name">产出(细胞产品)</div>
|
||||
</div>
|
||||
<el-tag
|
||||
v-if="editor.virtualOutputStatus.value === 'filled'"
|
||||
type="success"
|
||||
size="small"
|
||||
>已填写 {{ editor.taskOutputs.value.length }} 项</el-tag
|
||||
>
|
||||
<el-tag v-else type="info" size="small">暂无</el-tag>
|
||||
</div>
|
||||
|
||||
<!-- 分割线 -->
|
||||
<div class="step-divider"><span>工艺步骤</span></div>
|
||||
|
||||
<!-- 真实步骤列表 -->
|
||||
<draggable
|
||||
v-model="editor.stepList.value"
|
||||
:handle="readonly ? undefined : '.route-config-step-handle'"
|
||||
:animation="200"
|
||||
@end="editor.onStepDragEnd"
|
||||
>
|
||||
<div
|
||||
v-for="(element, index) in editor.stepList.value"
|
||||
:key="element.id"
|
||||
class="route-config-step-card"
|
||||
:class="{
|
||||
'is-active': editor.selectedStepId.value === element.id,
|
||||
'is-done': element.stepStatus === 'ywc'
|
||||
}"
|
||||
@click="editor.selectStep(element)"
|
||||
>
|
||||
<div v-if="!readonly" class="route-config-step-handle"
|
||||
><el-icon><Rank /></el-icon
|
||||
></div>
|
||||
<div class="route-config-step-order" :class="stepSeqClass(element.stepStatus)">{{
|
||||
element.stepStatus === 'ywc' ? '✓' : index + 1
|
||||
}}</div>
|
||||
<div class="route-config-step-info">
|
||||
<div class="route-config-step-name">{{
|
||||
element.processName || '未命名步骤'
|
||||
}}</div>
|
||||
<div class="route-config-step-desc">{{ element.description || '' }}</div>
|
||||
</div>
|
||||
<el-tag
|
||||
v-if="element.stepStatus"
|
||||
:type="editor.stepStatusTag(element.stepStatus).type"
|
||||
size="small"
|
||||
style="margin-right: 4px"
|
||||
>{{ editor.stepStatusTag(element.stepStatus).label }}</el-tag
|
||||
>
|
||||
<template v-if="!readonly">
|
||||
<el-button
|
||||
link
|
||||
type="primary"
|
||||
size="small"
|
||||
title="复制步骤"
|
||||
@click.stop="editor.handleCopyStep(element)"
|
||||
>
|
||||
<el-icon><CopyDocument /></el-icon>
|
||||
</el-button>
|
||||
<el-button
|
||||
link
|
||||
type="danger"
|
||||
size="small"
|
||||
@click.stop="editor.handleDeleteStep(element)"
|
||||
>
|
||||
<el-icon><Delete /></el-icon>
|
||||
</el-button>
|
||||
</template>
|
||||
</div>
|
||||
</draggable>
|
||||
|
||||
<div v-if="!editor.stepList.value.length" class="execution-step-list-empty">
|
||||
<el-icon :size="32" color="#a7f3d0"><Plus /></el-icon>
|
||||
<p>从左侧拖拽工序到此处,添加任务步骤</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 右侧详情面板 -->
|
||||
<div class="execution-detail-panel">
|
||||
<!-- 任务输入详情 -->
|
||||
<template v-if="editor.selectedVirtualStep.value === 'input'">
|
||||
<div class="execution-detail-header">
|
||||
<h3>📥 原材料(样本/细胞产品)<span class="virtual-step-required">必填</span></h3>
|
||||
<div v-if="!readonly" class="execution-detail-actions">
|
||||
<el-button
|
||||
class="btn-main"
|
||||
type="primary"
|
||||
size="small"
|
||||
:loading="editor.inputSaving.value"
|
||||
@click="editor.saveTaskInputs"
|
||||
><el-icon><Check /></el-icon>保存</el-button
|
||||
>
|
||||
<!-- 右侧详情面板 -->
|
||||
<div class="execution-detail-panel">
|
||||
<!-- 任务输入详情 -->
|
||||
<template v-if="editor.selectedVirtualStep.value === 'input'">
|
||||
<div class="execution-detail-header">
|
||||
<h3>📥 原材料(样本/细胞产品)<span class="virtual-step-required">必填</span></h3>
|
||||
<div v-if="!readonly" class="execution-detail-actions">
|
||||
<el-button
|
||||
class="btn-main"
|
||||
type="primary"
|
||||
size="small"
|
||||
:loading="editor.inputSaving.value"
|
||||
@click="editor.saveTaskInputs"
|
||||
><el-icon><Check /></el-icon>保存</el-button
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="execution-detail-body">
|
||||
<TaskInputEnhancedTable
|
||||
:items="editor.taskInputs.value"
|
||||
:loading="false"
|
||||
:disabled="readonly"
|
||||
:sample-code="editor.taskDetail.value?.sampleCode || ''"
|
||||
:cell-product-options="editor.cellProductOptions.value"
|
||||
:cell-product-options-loading="editor.cellProductOptionsLoading.value"
|
||||
@update:items="(items: Record<string, any>[]) => (editor.taskInputs.value = items)"
|
||||
@search-cell-product="(q: string) => editor.searchCellProductOptions(q)"
|
||||
@save="editor.saveTaskInputs"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- 任务输出详情 -->
|
||||
<template v-else-if="editor.selectedVirtualStep.value === 'output'">
|
||||
<div class="execution-detail-header">
|
||||
<h3>📤 产出(细胞产品)</h3>
|
||||
<div v-if="!readonly" class="execution-detail-actions">
|
||||
<el-button
|
||||
class="btn-main"
|
||||
type="primary"
|
||||
size="small"
|
||||
:loading="editor.outputSaving.value"
|
||||
@click="editor.saveTaskOutputs"
|
||||
><el-icon><Check /></el-icon>保存</el-button
|
||||
>
|
||||
<div class="execution-detail-body">
|
||||
<TaskInputEnhancedTable
|
||||
:items="editor.taskInputs.value"
|
||||
:loading="false"
|
||||
:disabled="readonly"
|
||||
:sample-code="editor.taskDetail.value?.sampleCode || ''"
|
||||
:cell-product-options="editor.cellProductOptions.value"
|
||||
:cell-product-options-loading="editor.cellProductOptionsLoading.value"
|
||||
@update:items="(items: Record<string, any>[]) => (editor.taskInputs.value = items)"
|
||||
@search-cell-product="(q: string) => editor.searchCellProductOptions(q)"
|
||||
@save="editor.saveTaskInputs"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="execution-detail-body">
|
||||
<TaskOutputList
|
||||
:items="editor.taskOutputs.value"
|
||||
:loading="false"
|
||||
:product-type-options="editor.productTypeOptions.value"
|
||||
:spec-options="editor.specOptions.value"
|
||||
:storage-slot-options="editor.storageSlotOptions.value"
|
||||
:unit-options="editor.itemUnitOptions.value"
|
||||
:unit-options-loading="editor.itemUnitOptionsLoading.value"
|
||||
:disabled="readonly"
|
||||
@update:items="(items: Record<string, any>[]) => (editor.taskOutputs.value = items)"
|
||||
@search-unit="(q: string) => editor.searchItemUnitOptions(q)"
|
||||
@save="editor.saveTaskOutputs"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
<!-- 步骤详情 -->
|
||||
<template v-else>
|
||||
<StepDetailPanel :editor="editor" :readonly="readonly" />
|
||||
</template>
|
||||
<!-- 任务输出详情 -->
|
||||
<template v-else-if="editor.selectedVirtualStep.value === 'output'">
|
||||
<div class="execution-detail-header">
|
||||
<h3>📤 产出(细胞产品)</h3>
|
||||
<div v-if="!readonly" class="execution-detail-actions">
|
||||
<el-button
|
||||
class="btn-main"
|
||||
type="primary"
|
||||
size="small"
|
||||
:loading="editor.outputSaving.value"
|
||||
@click="editor.saveTaskOutputs"
|
||||
><el-icon><Check /></el-icon>保存</el-button
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div class="execution-detail-body">
|
||||
<TaskOutputList
|
||||
:items="editor.taskOutputs.value"
|
||||
:loading="false"
|
||||
:product-type-options="editor.productTypeOptions.value"
|
||||
:spec-options="editor.specOptions.value"
|
||||
:storage-slot-options="editor.storageSlotOptions.value"
|
||||
:unit-options="editor.itemUnitOptions.value"
|
||||
:unit-options-loading="editor.itemUnitOptionsLoading.value"
|
||||
:disabled="readonly"
|
||||
@update:items="(items: Record<string, any>[]) => (editor.taskOutputs.value = items)"
|
||||
@search-unit="(q: string) => editor.searchItemUnitOptions(q)"
|
||||
@save="editor.saveTaskOutputs"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- 步骤详情 -->
|
||||
<template v-else>
|
||||
<StepDetailPanel :editor="editor" :readonly="readonly" />
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</el-dialog>
|
||||
|
||||
<CompleteProductionTaskDialog
|
||||
v-model:visible="completeConclusionVisible"
|
||||
:submitting="taskActionLoading"
|
||||
:is-completed-edit="completeConclusionIsEdit"
|
||||
:initial-conclusion="completeConclusionInitial.conclusion"
|
||||
:initial-remark="completeConclusionInitial.remark"
|
||||
@submit="submitCompleteWithConclusion"
|
||||
/>
|
||||
<CompleteProductionTaskDialog
|
||||
v-model:visible="completeConclusionVisible"
|
||||
:submitting="taskActionLoading"
|
||||
:is-completed-edit="completeConclusionIsEdit"
|
||||
:initial-conclusion="completeConclusionInitial.conclusion"
|
||||
:initial-remark="completeConclusionInitial.remark"
|
||||
@submit="submitCompleteWithConclusion"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -335,10 +346,7 @@
|
||||
import TaskInputEnhancedTable from './TaskInputEnhancedTable.vue'
|
||||
import TaskOutputList from './TaskOutputList.vue'
|
||||
import CompleteProductionTaskDialog from './CompleteProductionTaskDialog.vue'
|
||||
import {
|
||||
productionConclusionLabel,
|
||||
productionConclusionTagType
|
||||
} from '@/utils/production-labels'
|
||||
import { productionConclusionLabel, productionConclusionTagType } from '@/utils/production-labels'
|
||||
|
||||
const props = defineProps<{
|
||||
visible: boolean
|
||||
|
||||
@@ -165,12 +165,7 @@
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
label="批次号"
|
||||
prop="batchNo"
|
||||
min-width="120"
|
||||
show-overflow-tooltip
|
||||
/>
|
||||
<el-table-column label="批次号" prop="batchNo" min-width="120" show-overflow-tooltip />
|
||||
<el-table-column label="规格" prop="specName" min-width="100" show-overflow-tooltip />
|
||||
<el-table-column
|
||||
label="预计细胞数量(亿)"
|
||||
|
||||
@@ -82,7 +82,7 @@
|
||||
<div class="list-body list-wrap">
|
||||
<el-table
|
||||
ref="workOrderListTableRef"
|
||||
class="list-table"
|
||||
:class="['list-table', { 'list-table--no-action-col list-table--all-tab': activeTypeTab === 'all' }]"
|
||||
:data="tableData"
|
||||
v-loading="loading"
|
||||
stripe
|
||||
@@ -96,6 +96,19 @@
|
||||
:index="(i) => (pagination.page - 1) * pagination.pageSize + i + 1"
|
||||
/>
|
||||
<el-table-column prop="workOrderNo" label="工单号" min-width="150" align="center" />
|
||||
<el-table-column
|
||||
v-if="activeTypeTab === 'outbound'"
|
||||
prop="workOrderStatus"
|
||||
label="工单状态"
|
||||
min-width="100"
|
||||
align="center"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="woStatusTag(row.workOrderStatus).type" size="small">
|
||||
{{ woStatusTag(row.workOrderStatus).label }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
v-if="activeTypeTab === 'all'"
|
||||
prop="workOrderType"
|
||||
@@ -248,7 +261,13 @@
|
||||
<el-table-column prop="remark" label="备注" min-width="140" align="center">
|
||||
<template #default="{ row }">{{ row.remark || '—' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="workOrderStatus" label="工单状态" min-width="100" align="center">
|
||||
<el-table-column
|
||||
v-if="activeTypeTab !== 'outbound'"
|
||||
prop="workOrderStatus"
|
||||
label="工单状态"
|
||||
min-width="100"
|
||||
align="center"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="woStatusTag(row.workOrderStatus).type" size="small">
|
||||
{{ woStatusTag(row.workOrderStatus).label }}
|
||||
@@ -1059,6 +1078,14 @@
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
/* 全部工单 Tab 无操作列,略增高行高便于阅读多列信息 */
|
||||
:deep(.list-table.list-table--all-tab) {
|
||||
td.el-table__cell > .cell {
|
||||
min-height: 36px;
|
||||
line-height: 36px;
|
||||
}
|
||||
}
|
||||
|
||||
.work-order-type-tabs {
|
||||
padding: 0 16px;
|
||||
margin-bottom: 4px;
|
||||
|
||||
@@ -946,7 +946,8 @@ export function useTaskStepEditor() {
|
||||
if (!taskDetail.value?.id || taskActionLoading.value) return
|
||||
const isCompleted = taskDetail.value.taskStatus === 'ywc'
|
||||
completeConclusionIsEdit.value = isCompleted
|
||||
completeConclusionInitial.conclusion = (taskDetail.value as { conclusion?: string }).conclusion || ''
|
||||
completeConclusionInitial.conclusion =
|
||||
(taskDetail.value as { conclusion?: string }).conclusion || ''
|
||||
completeConclusionInitial.remark = taskDetail.value.remark || ''
|
||||
if (!isCompleted) {
|
||||
const ioErr = validateTaskIoRequired()
|
||||
|
||||
@@ -82,7 +82,12 @@
|
||||
@selection-change="handleSelectionChange"
|
||||
@row-dblclick="handleView"
|
||||
>
|
||||
<el-table-column type="selection" min-width="55" align="center" header-align="center" />
|
||||
<el-table-column
|
||||
type="selection"
|
||||
min-width="55"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
type="index"
|
||||
label="序号"
|
||||
@@ -133,12 +138,22 @@
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column label="预警库存" min-width="100" align="center" header-align="center">
|
||||
<el-table-column
|
||||
label="预警库存"
|
||||
min-width="100"
|
||||
align="center"
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
{{ row.warningQty ?? 0 }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="库存状态" min-width="100" align="center" header-align="center">
|
||||
<el-table-column
|
||||
label="库存状态"
|
||||
min-width="100"
|
||||
align="center"
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="isStockLow(row) ? 'danger' : 'success'" size="small">
|
||||
{{ isStockLow(row) ? '库存预警' : '正常' }}
|
||||
@@ -199,7 +214,9 @@
|
||||
:total="pagination.total"
|
||||
:page-sizes="[10, 20, 50]"
|
||||
layout="total, sizes, prev, pager, next"
|
||||
@size-change="(val) => ((pagination.page = 1), (pagination.pageSize = val), loadData())"
|
||||
@size-change="
|
||||
(val) => ((pagination.page = 1), (pagination.pageSize = val), loadData())
|
||||
"
|
||||
@current-change="(val) => ((pagination.page = val), loadData())"
|
||||
/>
|
||||
</div>
|
||||
@@ -211,11 +228,7 @@
|
||||
<el-form :model="allLogFilter" class="filter-form" @submit.prevent>
|
||||
<div class="filter-grid">
|
||||
<el-form-item label="关键词">
|
||||
<el-input
|
||||
v-model="allLogFilter.keyword"
|
||||
placeholder="试剂编码/名称"
|
||||
clearable
|
||||
/>
|
||||
<el-input v-model="allLogFilter.keyword" placeholder="试剂编码/名称" clearable />
|
||||
</el-form-item>
|
||||
<el-form-item label="变动类型">
|
||||
<el-select v-model="allLogFilter.changeType" placeholder="全部" clearable>
|
||||
@@ -266,7 +279,9 @@
|
||||
min-width="68"
|
||||
align="center"
|
||||
header-align="center"
|
||||
:index="(index) => (allLogPagination.page - 1) * allLogPagination.pageSize + index + 1"
|
||||
:index="
|
||||
(index) => (allLogPagination.page - 1) * allLogPagination.pageSize + index + 1
|
||||
"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="materialCode"
|
||||
@@ -282,14 +297,24 @@
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column label="变动类型" min-width="100" align="center" header-align="center">
|
||||
<el-table-column
|
||||
label="变动类型"
|
||||
min-width="100"
|
||||
align="center"
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="getChangeTypeTagType(row)" size="small">
|
||||
{{ getChangeTypeLabel(row) }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="变动数量" min-width="120" align="center" header-align="center">
|
||||
<el-table-column
|
||||
label="变动数量"
|
||||
min-width="120"
|
||||
align="center"
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<span :class="row.changeQty < 0 ? 'qty-negative' : 'qty-positive'">
|
||||
{{ row.changeQty < 0 ? row.changeQty : `+${row.changeQty}` }}
|
||||
@@ -313,10 +338,20 @@
|
||||
<el-table-column label="工单号" min-width="140" align="center" header-align="center">
|
||||
<template #default="{ row }">{{ row.taskNo || '-' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="步骤名称" min-width="140" align="center" header-align="center">
|
||||
<el-table-column
|
||||
label="步骤名称"
|
||||
min-width="140"
|
||||
align="center"
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }">{{ row.stepName || '-' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="来源类型" min-width="120" align="center" header-align="center">
|
||||
<el-table-column
|
||||
label="来源类型"
|
||||
min-width="120"
|
||||
align="center"
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="sourceTypeTagType(row.sourceType)" size="small">
|
||||
{{ sourceTypeLabel(row.sourceType) }}
|
||||
@@ -326,10 +361,21 @@
|
||||
<el-table-column label="领用人" min-width="100" align="center" header-align="center">
|
||||
<template #default="{ row }">{{ displayLogReceiver(row) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="用途" min-width="120" align="center" header-align="center" show-overflow-tooltip>
|
||||
<el-table-column
|
||||
label="用途"
|
||||
min-width="120"
|
||||
align="center"
|
||||
header-align="center"
|
||||
show-overflow-tooltip
|
||||
>
|
||||
<template #default="{ row }">{{ displayLogPurpose(row) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="领用时间" min-width="170" align="center" header-align="center">
|
||||
<el-table-column
|
||||
label="领用时间"
|
||||
min-width="170"
|
||||
align="center"
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }">{{ displayLogUseTime(row) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
@@ -341,7 +387,9 @@
|
||||
show-overflow-tooltip
|
||||
/>
|
||||
<el-table-column label="操作人" min-width="100" align="center" header-align="center">
|
||||
<template #default="{ row }">{{ row.createByName || row.createBy || '-' }}</template>
|
||||
<template #default="{ row }">{{
|
||||
row.createByName || row.createBy || '-'
|
||||
}}</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="createTime"
|
||||
@@ -350,7 +398,13 @@
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column label="操作" min-width="160" fixed="right" align="center" header-align="center">
|
||||
<el-table-column
|
||||
label="操作"
|
||||
min-width="160"
|
||||
fixed="right"
|
||||
align="center"
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<el-button class="btn-op-item" @click="handleAllLogView(row)">
|
||||
<el-icon><View /></el-icon>查看
|
||||
@@ -366,7 +420,11 @@
|
||||
:page-sizes="[10, 20, 50, 100]"
|
||||
layout="total, sizes, prev, pager, next"
|
||||
@size-change="
|
||||
(val) => ((allLogPagination.page = 1), (allLogPagination.pageSize = val), loadAllLogs())
|
||||
(val) => (
|
||||
(allLogPagination.page = 1),
|
||||
(allLogPagination.pageSize = val),
|
||||
loadAllLogs()
|
||||
)
|
||||
"
|
||||
@current-change="(val) => ((allLogPagination.page = val), loadAllLogs())"
|
||||
/>
|
||||
@@ -418,7 +476,9 @@
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="预警库存">
|
||||
<template #label>预警库存<span class="form-label-tip">低于该值时列表显示库存预警</span></template>
|
||||
<template #label
|
||||
>预警库存<span class="form-label-tip">低于该值时列表显示库存预警</span></template
|
||||
>
|
||||
<el-input-number
|
||||
v-model="formData.warningQty"
|
||||
:min="0"
|
||||
@@ -438,12 +498,7 @@
|
||||
<el-input v-model="formData.description" placeholder="请输入描述" />
|
||||
</el-form-item>
|
||||
<el-form-item label="备注" class="form-col-span-2">
|
||||
<el-input
|
||||
v-model="formData.remark"
|
||||
type="textarea"
|
||||
:rows="3"
|
||||
placeholder="可选"
|
||||
/>
|
||||
<el-input v-model="formData.remark" type="textarea" :rows="3" placeholder="可选" />
|
||||
</el-form-item>
|
||||
</div>
|
||||
</el-form>
|
||||
@@ -488,12 +543,7 @@
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="备注">
|
||||
<el-input
|
||||
v-model="stockInForm.remark"
|
||||
type="textarea"
|
||||
:rows="3"
|
||||
placeholder="可选"
|
||||
/>
|
||||
<el-input v-model="stockInForm.remark" type="textarea" :rows="3" placeholder="可选" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
@@ -563,20 +613,10 @@
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="用途">
|
||||
<el-input
|
||||
v-model="stockOutForm.purpose"
|
||||
type="textarea"
|
||||
:rows="2"
|
||||
placeholder="可选"
|
||||
/>
|
||||
<el-input v-model="stockOutForm.purpose" type="textarea" :rows="2" placeholder="可选" />
|
||||
</el-form-item>
|
||||
<el-form-item label="备注">
|
||||
<el-input
|
||||
v-model="stockOutForm.remark"
|
||||
type="textarea"
|
||||
:rows="2"
|
||||
placeholder="可选"
|
||||
/>
|
||||
<el-input v-model="stockOutForm.remark" type="textarea" :rows="2" placeholder="可选" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
@@ -688,10 +728,20 @@
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column label="当前库存" min-width="100" align="center" header-align="center">
|
||||
<el-table-column
|
||||
label="当前库存"
|
||||
min-width="100"
|
||||
align="center"
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }">{{ row.stockQty }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="入库数量" min-width="130" align="center" header-align="center">
|
||||
<el-table-column
|
||||
label="入库数量"
|
||||
min-width="130"
|
||||
align="center"
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<el-input-number
|
||||
v-model="row.qty"
|
||||
@@ -705,22 +755,28 @@
|
||||
</el-table-column>
|
||||
<el-table-column label="备注" min-width="160" align="center" header-align="center">
|
||||
<template #default="{ row }">
|
||||
<el-input v-model="row.remark" placeholder="可选" clearable class="inner-cell-input" />
|
||||
<el-input
|
||||
v-model="row.remark"
|
||||
placeholder="可选"
|
||||
clearable
|
||||
class="inner-cell-input"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" min-width="80" align="center" header-align="center">
|
||||
<template #default="{ $index }">
|
||||
<el-button link type="danger" size="small" @click="batchStockInLines.splice($index, 1)">
|
||||
<el-button
|
||||
link
|
||||
type="danger"
|
||||
size="small"
|
||||
@click="batchStockInLines.splice($index, 1)"
|
||||
>
|
||||
移除
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<el-empty
|
||||
v-else
|
||||
description="请通过上方搜索框添加要入库的试剂"
|
||||
:image-size="56"
|
||||
/>
|
||||
<el-empty v-else description="请通过上方搜索框添加要入库的试剂" :image-size="56" />
|
||||
</div>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
@@ -861,10 +917,20 @@
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column label="当前库存" min-width="100" align="center" header-align="center">
|
||||
<el-table-column
|
||||
label="当前库存"
|
||||
min-width="100"
|
||||
align="center"
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }">{{ row.stockQty }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="出库数量" min-width="130" align="center" header-align="center">
|
||||
<el-table-column
|
||||
label="出库数量"
|
||||
min-width="130"
|
||||
align="center"
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<el-input-number
|
||||
v-model="row.qty"
|
||||
@@ -879,22 +945,28 @@
|
||||
</el-table-column>
|
||||
<el-table-column label="备注" min-width="160" align="center" header-align="center">
|
||||
<template #default="{ row }">
|
||||
<el-input v-model="row.remark" placeholder="可选" clearable class="inner-cell-input" />
|
||||
<el-input
|
||||
v-model="row.remark"
|
||||
placeholder="可选"
|
||||
clearable
|
||||
class="inner-cell-input"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" min-width="80" align="center" header-align="center">
|
||||
<template #default="{ $index }">
|
||||
<el-button link type="danger" size="small" @click="batchStockOutLines.splice($index, 1)">
|
||||
<el-button
|
||||
link
|
||||
type="danger"
|
||||
size="small"
|
||||
@click="batchStockOutLines.splice($index, 1)"
|
||||
>
|
||||
移除
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<el-empty
|
||||
v-else
|
||||
description="请通过上方搜索框添加要出库的试剂"
|
||||
:image-size="56"
|
||||
/>
|
||||
<el-empty v-else description="请通过上方搜索框添加要出库的试剂" :image-size="56" />
|
||||
</div>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
@@ -927,33 +999,32 @@
|
||||
<div class="filter-block" style="padding-bottom: 0">
|
||||
<el-form :inline="true" @submit.prevent>
|
||||
<el-form-item label="变动类型">
|
||||
<el-select v-model="stockLogFilter.changeType" placeholder="全部" clearable @change="loadStockLogs">
|
||||
<el-select
|
||||
v-model="stockLogFilter.changeType"
|
||||
placeholder="全部"
|
||||
clearable
|
||||
@change="loadStockLogs"
|
||||
>
|
||||
<el-option label="入库" value="in" />
|
||||
<el-option label="出库" value="out" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
<el-table
|
||||
class="list-table"
|
||||
:data="stockLogData"
|
||||
v-loading="stockLogLoading"
|
||||
stripe
|
||||
>
|
||||
<el-table class="list-table" :data="stockLogData" v-loading="stockLogLoading" stripe>
|
||||
<el-table-column
|
||||
type="index"
|
||||
label="序号"
|
||||
min-width="68"
|
||||
align="center"
|
||||
header-align="center"
|
||||
:index="(index) => (stockLogPagination.page - 1) * stockLogPagination.pageSize + index + 1"
|
||||
:index="
|
||||
(index) => (stockLogPagination.page - 1) * stockLogPagination.pageSize + index + 1
|
||||
"
|
||||
/>
|
||||
<el-table-column label="变动类型" min-width="100" align="center" header-align="center">
|
||||
<template #default="{ row }">
|
||||
<el-tag
|
||||
:type="row.changeType === 'in' ? 'success' : 'warning'"
|
||||
size="small"
|
||||
>
|
||||
<el-tag :type="row.changeType === 'in' ? 'success' : 'warning'" size="small">
|
||||
{{ row.changeType === 'in' ? '入库' : '出库' }}
|
||||
</el-tag>
|
||||
</template>
|
||||
@@ -1038,7 +1109,11 @@
|
||||
:page-sizes="[10, 20, 50]"
|
||||
layout="total, sizes, prev, pager, next"
|
||||
@size-change="
|
||||
(val) => ((stockLogPagination.page = 1), (stockLogPagination.pageSize = val), loadStockLogs())
|
||||
(val) => (
|
||||
(stockLogPagination.page = 1),
|
||||
(stockLogPagination.pageSize = val),
|
||||
loadStockLogs()
|
||||
)
|
||||
"
|
||||
@current-change="(val) => ((stockLogPagination.page = val), loadStockLogs())"
|
||||
/>
|
||||
@@ -1528,7 +1603,9 @@
|
||||
async function handleBatchDelete() {
|
||||
const ids = selectedRows.value.map((r) => r.id)
|
||||
if (!ids.length) return
|
||||
await ElMessageBox.confirm(`确定删除选中的 ${ids.length} 个试剂吗?`, '提示', { type: 'warning' })
|
||||
await ElMessageBox.confirm(`确定删除选中的 ${ids.length} 个试剂吗?`, '提示', {
|
||||
type: 'warning'
|
||||
})
|
||||
await withFullscreenLoading(async () => {
|
||||
for (const id of ids) {
|
||||
await fetchDeleteReagent(id)
|
||||
|
||||
@@ -22,8 +22,12 @@
|
||||
<div class="filter-actions-row">
|
||||
<span class="filter-hint">按编号、名称模糊查询。</span>
|
||||
<div class="filter-actions-end">
|
||||
<el-button class="btn-ghost" @click="handleReset"><el-icon><RefreshLeft /></el-icon>重置</el-button>
|
||||
<el-button class="btn-main" type="primary" @click="handleSearch"><el-icon><Search /></el-icon>搜索</el-button>
|
||||
<el-button class="btn-ghost" @click="handleReset"
|
||||
><el-icon><RefreshLeft /></el-icon>重置</el-button
|
||||
>
|
||||
<el-button class="btn-main" type="primary" @click="handleSearch"
|
||||
><el-icon><Search /></el-icon>搜索</el-button
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</el-form>
|
||||
@@ -68,17 +72,54 @@
|
||||
header-align="center"
|
||||
:index="(index) => (pagination.page - 1) * pagination.pageSize + index + 1"
|
||||
/>
|
||||
<el-table-column prop="sopCode" label="SOP编号" min-width="130" align="center" header-align="center" />
|
||||
<el-table-column prop="sopName" label="SOP文档名称" min-width="180" align="center" header-align="center" />
|
||||
<el-table-column prop="versionNo" label="版本号" min-width="100" align="center" header-align="center" />
|
||||
<el-table-column prop="remark" label="备注" min-width="160" align="center" header-align="center" show-overflow-tooltip />
|
||||
<el-table-column
|
||||
prop="sopCode"
|
||||
label="SOP编号"
|
||||
min-width="130"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="sopName"
|
||||
label="SOP文档名称"
|
||||
min-width="180"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="versionNo"
|
||||
label="版本号"
|
||||
min-width="100"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="remark"
|
||||
label="备注"
|
||||
min-width="160"
|
||||
align="center"
|
||||
header-align="center"
|
||||
show-overflow-tooltip
|
||||
/>
|
||||
<el-table-column label="附件数" min-width="90" align="center" header-align="center">
|
||||
<template #default="{ row }">
|
||||
{{ row.sopAttachments ? row.sopAttachments.split(',').length : 0 }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="createTime" label="创建时间" min-width="180" align="center" header-align="center" />
|
||||
<el-table-column label="操作" min-width="320" fixed="right" align="center" header-align="center">
|
||||
<el-table-column
|
||||
prop="createTime"
|
||||
label="创建时间"
|
||||
min-width="180"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
label="操作"
|
||||
min-width="320"
|
||||
fixed="right"
|
||||
align="center"
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<el-button class="btn-op-item" @click="handleView(row)"
|
||||
><el-icon><View /></el-icon>查看</el-button
|
||||
@@ -177,7 +218,9 @@
|
||||
</template>
|
||||
</div>
|
||||
<div class="attachment-footer">
|
||||
<span class="attachment-name" :title="file.fileName">{{ file.fileName }}</span>
|
||||
<span class="attachment-name" :title="file.fileName">{{
|
||||
file.fileName
|
||||
}}</span>
|
||||
<div class="attachment-actions">
|
||||
<el-icon
|
||||
class="action-btn"
|
||||
@@ -195,9 +238,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<span class="upload-hint"
|
||||
>支持图片、视频、PDF、Word、Excel等文件</span
|
||||
>
|
||||
<span class="upload-hint">支持图片、视频、PDF、Word、Excel等文件</span>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="备注" class="form-col-span-2">
|
||||
@@ -304,15 +345,23 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, onMounted, computed, nextTick } from 'vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { Plus, Close, Check, View, ScaleToOriginal, Document, Download, Delete, VideoPlay, Loading, RefreshLeft, Search } from '@element-plus/icons-vue'
|
||||
import {
|
||||
Plus,
|
||||
Close,
|
||||
Check,
|
||||
View,
|
||||
ScaleToOriginal,
|
||||
Document,
|
||||
Download,
|
||||
Delete,
|
||||
VideoPlay,
|
||||
Loading,
|
||||
RefreshLeft,
|
||||
Search
|
||||
} from '@element-plus/icons-vue'
|
||||
import SystemViewDialog from '@/components/business/system-view-dialog/index.vue'
|
||||
import { withFullscreenLoading, fullscreenLoadingText } from '@/utils/fullscreen-loading'
|
||||
import {
|
||||
fetchSopList,
|
||||
fetchCreateSop,
|
||||
fetchUpdateSop,
|
||||
fetchDeleteSop
|
||||
} from '@/api/production'
|
||||
import { fetchSopList, fetchCreateSop, fetchUpdateSop, fetchDeleteSop } from '@/api/production'
|
||||
import { fetchUploadAttachment } from '@/api/business'
|
||||
import { downloadAttachmentFile } from '@/utils/download-attachment'
|
||||
|
||||
@@ -441,7 +490,9 @@
|
||||
const viewSections = computed(() => {
|
||||
if (!viewRow.value) return []
|
||||
const row = viewRow.value
|
||||
const attachCount = row.sopAttachments ? row.sopAttachments.split(',').filter(Boolean).length : 0
|
||||
const attachCount = row.sopAttachments
|
||||
? row.sopAttachments.split(',').filter(Boolean).length
|
||||
: 0
|
||||
return [
|
||||
{
|
||||
title: '基本信息',
|
||||
@@ -460,8 +511,6 @@
|
||||
const file = uploadFile?.raw || uploadFile
|
||||
if (!file) return
|
||||
|
||||
|
||||
|
||||
const tempId = `temp_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`
|
||||
formData.attachments.push({
|
||||
id: tempId,
|
||||
@@ -621,7 +670,9 @@
|
||||
async function handleBatchDelete() {
|
||||
const ids = selectedRows.value.map((r) => r.id)
|
||||
if (!ids.length) return
|
||||
await ElMessageBox.confirm(`确定删除选中的 ${ids.length} 个SOP文档吗?`, '提示', { type: 'warning' })
|
||||
await ElMessageBox.confirm(`确定删除选中的 ${ids.length} 个SOP文档吗?`, '提示', {
|
||||
type: 'warning'
|
||||
})
|
||||
await withFullscreenLoading(async () => {
|
||||
for (const id of ids) {
|
||||
await fetchDeleteSop(id)
|
||||
|
||||
@@ -19,7 +19,12 @@
|
||||
<el-input v-model="searchForm.keyword" placeholder="编码/名称" clearable />
|
||||
</el-form-item>
|
||||
<el-form-item label="状态">
|
||||
<el-select v-model="searchForm.status" placeholder="全部" clearable style="width: 100%">
|
||||
<el-select
|
||||
v-model="searchForm.status"
|
||||
placeholder="全部"
|
||||
clearable
|
||||
style="width: 100%"
|
||||
>
|
||||
<el-option label="启用" value="1" />
|
||||
<el-option label="停用" value="0" />
|
||||
</el-select>
|
||||
@@ -27,8 +32,12 @@
|
||||
</div>
|
||||
<div class="filter-actions-row">
|
||||
<div class="filter-actions-end">
|
||||
<el-button class="btn-ghost" @click="handleReset"><el-icon><RefreshLeft /></el-icon>重置</el-button>
|
||||
<el-button class="btn-main" type="primary" @click="handleSearch"><el-icon><Search /></el-icon>搜索</el-button>
|
||||
<el-button class="btn-ghost" @click="handleReset"
|
||||
><el-icon><RefreshLeft /></el-icon>重置</el-button
|
||||
>
|
||||
<el-button class="btn-main" type="primary" @click="handleSearch"
|
||||
><el-icon><Search /></el-icon>搜索</el-button
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</el-form>
|
||||
@@ -36,8 +45,12 @@
|
||||
|
||||
<div class="list-toolbar">
|
||||
<div class="list-toolbar-row">
|
||||
<div class="list-toolbar-selected">已选 <span class="sel-num">{{ selectedRows.length }}</span> 项</div>
|
||||
<el-button class="btn-main" type="primary" @click="handleAdd"><el-icon><Plus /></el-icon>新增项目</el-button>
|
||||
<div class="list-toolbar-selected"
|
||||
>已选 <span class="sel-num">{{ selectedRows.length }}</span> 项</div
|
||||
>
|
||||
<el-button class="btn-main" type="primary" @click="handleAdd"
|
||||
><el-icon><Plus /></el-icon>新增项目</el-button
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -52,20 +65,40 @@
|
||||
@row-dblclick="handleView"
|
||||
>
|
||||
<el-table-column type="selection" min-width="55" align="center" />
|
||||
<el-table-column type="index" label="序号" min-width="68" align="center" :index="(i) => (pagination.page - 1) * pagination.pageSize + i + 1" />
|
||||
<el-table-column
|
||||
type="index"
|
||||
label="序号"
|
||||
min-width="68"
|
||||
align="center"
|
||||
:index="(i) => (pagination.page - 1) * pagination.pageSize + i + 1"
|
||||
/>
|
||||
<el-table-column prop="itemCode" label="项目编码" min-width="140" align="center" />
|
||||
<el-table-column prop="itemName" label="项目名称" min-width="160" align="center" />
|
||||
<el-table-column prop="description" label="描述" min-width="200" align="center" show-overflow-tooltip />
|
||||
<el-table-column
|
||||
prop="description"
|
||||
label="描述"
|
||||
min-width="200"
|
||||
align="center"
|
||||
show-overflow-tooltip
|
||||
/>
|
||||
<el-table-column prop="status" label="状态" min-width="90" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="row.status === '1' ? 'success' : 'info'" size="small">{{ row.status === '1' ? '启用' : '停用' }}</el-tag>
|
||||
<el-tag :type="row.status === '1' ? 'success' : 'info'" size="small">{{
|
||||
row.status === '1' ? '启用' : '停用'
|
||||
}}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" min-width="320" fixed="right" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-button class="btn-op-item" @click="handleView(row)"><el-icon><View /></el-icon>查看</el-button>
|
||||
<el-button class="btn-op-item primary" @click="handleEdit(row)"><el-icon><Edit /></el-icon>编辑</el-button>
|
||||
<el-button class="btn-op-item" @click="handleDelete(row)"><el-icon><Delete /></el-icon>删除</el-button>
|
||||
<el-button class="btn-op-item" @click="handleView(row)"
|
||||
><el-icon><View /></el-icon>查看</el-button
|
||||
>
|
||||
<el-button class="btn-op-item primary" @click="handleEdit(row)"
|
||||
><el-icon><Edit /></el-icon>编辑</el-button
|
||||
>
|
||||
<el-button class="btn-op-item" @click="handleDelete(row)"
|
||||
><el-icon><Delete /></el-icon>删除</el-button
|
||||
>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
@@ -82,8 +115,22 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<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">
|
||||
<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-3">
|
||||
<el-form-item label="项目编码" prop="itemCode">
|
||||
<el-input v-model="formData.itemCode" :disabled="isEdit" />
|
||||
@@ -106,54 +153,65 @@
|
||||
<el-input v-model="formData.remark" type="textarea" :rows="2" />
|
||||
</el-form-item>
|
||||
</div>
|
||||
<!-- 参数配置 -->
|
||||
<div class="form-section" style="margin-top: 8px">
|
||||
<div class="section-title">参数配置</div>
|
||||
<div class="param-row param-row-header">
|
||||
<span style="flex: 2">参数名称</span>
|
||||
<span style="flex: 1.5">参数编码</span>
|
||||
<span style="flex: 1">单位</span>
|
||||
<span style="flex: 1">类型</span>
|
||||
<span style="width: 40px; text-align: center">必填</span>
|
||||
<span style="width: 32px"></span>
|
||||
</div>
|
||||
<div v-if="itemParams.length === 0" style="text-align: center; padding: 8px 0; color: #94a3b8; font-size: 13px">
|
||||
暂无参数,点击下方按钮添加
|
||||
</div>
|
||||
<div v-for="(param, idx) in itemParams" :key="idx" class="param-row">
|
||||
<el-input v-model="param.paramName" placeholder="参数名称" style="flex: 2" />
|
||||
<el-input v-model="param.paramCode" placeholder="参数编码" style="flex: 1.5" />
|
||||
<el-select
|
||||
v-model="param.paramUnit"
|
||||
placeholder="单位"
|
||||
clearable
|
||||
filterable
|
||||
remote
|
||||
reserve-keyword
|
||||
:loading="unitOptionsLoading"
|
||||
:remote-method="searchUnitOptions"
|
||||
style="flex: 1"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in unitOptions"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
/>
|
||||
</el-select>
|
||||
<el-select v-model="param.paramType" style="flex: 1">
|
||||
<el-option label="文本" value="text" />
|
||||
<el-option label="数字" value="number" />
|
||||
</el-select>
|
||||
<el-switch v-model="param.requiredFlag" active-value="1" inactive-value="0" />
|
||||
<el-button class="btn-op-item" @click="removeParam(idx)"><el-icon><Delete /></el-icon></el-button>
|
||||
</div>
|
||||
<el-button type="primary" plain style="margin-top: 8px" @click="addParam"><el-icon><Plus /></el-icon>添加参数</el-button>
|
||||
<!-- 参数配置 -->
|
||||
<div class="form-section" style="margin-top: 8px">
|
||||
<div class="section-title">参数配置</div>
|
||||
<div class="param-row param-row-header">
|
||||
<span style="flex: 2">参数名称</span>
|
||||
<span style="flex: 1.5">参数编码</span>
|
||||
<span style="flex: 1">单位</span>
|
||||
<span style="flex: 1">类型</span>
|
||||
<span style="width: 40px; text-align: center">必填</span>
|
||||
<span style="width: 32px"></span>
|
||||
</div>
|
||||
<div
|
||||
v-if="itemParams.length === 0"
|
||||
style="text-align: center; padding: 8px 0; color: #94a3b8; font-size: 13px"
|
||||
>
|
||||
暂无参数,点击下方按钮添加
|
||||
</div>
|
||||
<div v-for="(param, idx) in itemParams" :key="idx" class="param-row">
|
||||
<el-input v-model="param.paramName" placeholder="参数名称" style="flex: 2" />
|
||||
<el-input v-model="param.paramCode" placeholder="参数编码" style="flex: 1.5" />
|
||||
<el-select
|
||||
v-model="param.paramUnit"
|
||||
placeholder="单位"
|
||||
clearable
|
||||
filterable
|
||||
remote
|
||||
reserve-keyword
|
||||
:loading="unitOptionsLoading"
|
||||
:remote-method="searchUnitOptions"
|
||||
style="flex: 1"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in unitOptions"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
/>
|
||||
</el-select>
|
||||
<el-select v-model="param.paramType" style="flex: 1">
|
||||
<el-option label="文本" value="text" />
|
||||
<el-option label="数字" value="number" />
|
||||
</el-select>
|
||||
<el-switch v-model="param.requiredFlag" active-value="1" inactive-value="0" />
|
||||
<el-button class="btn-op-item" @click="removeParam(idx)"
|
||||
><el-icon><Delete /></el-icon
|
||||
></el-button>
|
||||
</div>
|
||||
<el-button type="primary" plain style="margin-top: 8px" @click="addParam"
|
||||
><el-icon><Plus /></el-icon>添加参数</el-button
|
||||
>
|
||||
</div>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button class="btn-ghost" @click="dialogVisible = false"><el-icon><Close /></el-icon>取消</el-button>
|
||||
<el-button class="btn-main" type="primary" :loading="submitLoading" @click="handleSubmit"><el-icon><Check /></el-icon>确定</el-button>
|
||||
<el-button class="btn-ghost" @click="dialogVisible = false"
|
||||
><el-icon><Close /></el-icon>取消</el-button
|
||||
>
|
||||
<el-button class="btn-main" type="primary" :loading="submitLoading" @click="handleSubmit"
|
||||
><el-icon><Check /></el-icon>确定</el-button
|
||||
>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
@@ -171,8 +229,20 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, onMounted } from 'vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { Plus, Close, Check, Search, RefreshLeft, View, Edit, Delete, Notebook } from '@element-plus/icons-vue'
|
||||
import SystemViewDialog, { type ViewSection } from '@/components/business/system-view-dialog/index.vue'
|
||||
import {
|
||||
Plus,
|
||||
Close,
|
||||
Check,
|
||||
Search,
|
||||
RefreshLeft,
|
||||
View,
|
||||
Edit,
|
||||
Delete,
|
||||
Notebook
|
||||
} from '@element-plus/icons-vue'
|
||||
import SystemViewDialog, {
|
||||
type ViewSection
|
||||
} from '@/components/business/system-view-dialog/index.vue'
|
||||
import {
|
||||
fetchQcLabTestItemList,
|
||||
fetchCreateQcLabTestItem,
|
||||
@@ -233,7 +303,13 @@
|
||||
const formRef = ref()
|
||||
const editingId = ref<string | null>(null)
|
||||
const itemParams = ref<QcItemParam[]>([])
|
||||
const formData = reactive({ itemCode: '', itemName: '', description: '', status: '1', remark: '' })
|
||||
const formData = reactive({
|
||||
itemCode: '',
|
||||
itemName: '',
|
||||
description: '',
|
||||
status: '1',
|
||||
remark: ''
|
||||
})
|
||||
const viewVisible = ref(false)
|
||||
const viewItemName = ref('')
|
||||
const viewItemCode = ref('')
|
||||
@@ -285,7 +361,13 @@
|
||||
loadData()
|
||||
}
|
||||
function resetForm() {
|
||||
Object.assign(formData, { itemCode: '', itemName: '', description: '', status: '1', remark: '' })
|
||||
Object.assign(formData, {
|
||||
itemCode: '',
|
||||
itemName: '',
|
||||
description: '',
|
||||
status: '1',
|
||||
remark: ''
|
||||
})
|
||||
editingId.value = null
|
||||
itemParams.value = []
|
||||
}
|
||||
@@ -324,7 +406,11 @@
|
||||
if (!(await formRef.value?.validate?.().catch(() => false))) return
|
||||
submitLoading.value = true
|
||||
try {
|
||||
const payload = { ...formData, remark: formData.remark || undefined, description: formData.description || undefined }
|
||||
const payload = {
|
||||
...formData,
|
||||
remark: formData.remark || undefined,
|
||||
description: formData.description || undefined
|
||||
}
|
||||
let savedId = editingId.value
|
||||
if (isEdit.value && editingId.value) {
|
||||
await fetchUpdateQcLabTestItem(editingId.value, payload)
|
||||
@@ -350,7 +436,11 @@
|
||||
{
|
||||
title: '基本信息',
|
||||
fields: [
|
||||
{ label: '状态', value: row.status === '1' ? '启用' : '停用', tagType: row.status === '1' ? 'success' : 'info' }
|
||||
{
|
||||
label: '状态',
|
||||
value: row.status === '1' ? '启用' : '停用',
|
||||
tagType: row.status === '1' ? 'success' : 'info'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
|
||||
@@ -4,7 +4,9 @@
|
||||
<div class="page-container">
|
||||
<header class="page-head">
|
||||
<h1>质检物料变动记录</h1>
|
||||
<p class="page-desc">查看检测物料的使用与入库变动流水,支持按物料、变动类型和时间范围筛选。</p>
|
||||
<p class="page-desc"
|
||||
>查看检测物料的使用与入库变动流水,支持按物料、变动类型和时间范围筛选。</p
|
||||
>
|
||||
</header>
|
||||
|
||||
<section class="panel">
|
||||
@@ -121,12 +123,7 @@
|
||||
</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
label="变动前库存"
|
||||
min-width="120"
|
||||
align="center"
|
||||
header-align="center"
|
||||
>
|
||||
<el-table-column label="变动前库存" min-width="120" align="center" header-align="center">
|
||||
<template #default="{ row }">
|
||||
{{ displayStockBefore(row) }}
|
||||
</template>
|
||||
@@ -167,7 +164,13 @@
|
||||
{{ displayReceiver(row) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="用途" min-width="120" align="center" header-align="center" show-overflow-tooltip>
|
||||
<el-table-column
|
||||
label="用途"
|
||||
min-width="120"
|
||||
align="center"
|
||||
header-align="center"
|
||||
show-overflow-tooltip
|
||||
>
|
||||
<template #default="{ row }">
|
||||
{{ displayPurpose(row) }}
|
||||
</template>
|
||||
@@ -203,7 +206,13 @@
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column label="操作" min-width="160" fixed="right" align="center" header-align="center">
|
||||
<el-table-column
|
||||
label="操作"
|
||||
min-width="160"
|
||||
fixed="right"
|
||||
align="center"
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<el-button class="btn-op-item" @click="handleView(row)">
|
||||
<el-icon><View /></el-icon>查看
|
||||
@@ -238,188 +247,191 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, computed, onMounted } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
import { Search, RefreshLeft, View, Document } from '@element-plus/icons-vue'
|
||||
import { fetchQcMaterialUsageList } from '@/api/qc-material-usage'
|
||||
import type { QcMaterialUsage } from '@/api/qc-material-usage'
|
||||
import { fetchQcMaterialOptions } from '@/api/qc-material'
|
||||
import type { QcMaterialOption } from '@/api/qc-material'
|
||||
import SystemViewDialog from '@/components/business/system-view-dialog/index.vue'
|
||||
import { ref, reactive, computed, onMounted } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
import { Search, RefreshLeft, View, Document } from '@element-plus/icons-vue'
|
||||
import { fetchQcMaterialUsageList } from '@/api/qc-material-usage'
|
||||
import type { QcMaterialUsage } from '@/api/qc-material-usage'
|
||||
import { fetchQcMaterialOptions } from '@/api/qc-material'
|
||||
import type { QcMaterialOption } from '@/api/qc-material'
|
||||
import SystemViewDialog from '@/components/business/system-view-dialog/index.vue'
|
||||
|
||||
defineOptions({ name: 'QualityInspectionQcMaterialUsage' })
|
||||
defineOptions({ name: 'QualityInspectionQcMaterialUsage' })
|
||||
|
||||
const route = useRoute()
|
||||
const route = useRoute()
|
||||
|
||||
const searchForm = reactive<{
|
||||
materialId: string
|
||||
changeType: string
|
||||
}>({
|
||||
materialId: '',
|
||||
changeType: ''
|
||||
})
|
||||
const searchForm = reactive<{
|
||||
materialId: string
|
||||
changeType: string
|
||||
}>({
|
||||
materialId: '',
|
||||
changeType: ''
|
||||
})
|
||||
|
||||
const dateRange = ref<[string, string] | null>(null)
|
||||
const tableData = ref<QcMaterialUsage[]>([])
|
||||
const loading = ref(false)
|
||||
const pagination = reactive({ page: 1, pageSize: 10, total: 0 })
|
||||
const materialOptions = ref<QcMaterialOption[]>([])
|
||||
const dateRange = ref<[string, string] | null>(null)
|
||||
const tableData = ref<QcMaterialUsage[]>([])
|
||||
const loading = ref(false)
|
||||
const pagination = reactive({ page: 1, pageSize: 10, total: 0 })
|
||||
const materialOptions = ref<QcMaterialOption[]>([])
|
||||
|
||||
function isManualOutRow(row: QcMaterialUsage) {
|
||||
if (row.sourceType === 'manual_out') return true
|
||||
const remark = row.remark || ''
|
||||
return row.changeType === 'use' && (row.changeQty ?? 0) < 0 && remark.includes('领用人:')
|
||||
}
|
||||
|
||||
function parseFromRemark(remark: string, key: 'receiver' | 'purpose') {
|
||||
const prefix = key === 'receiver' ? '领用人:' : '用途:'
|
||||
for (const part of remark.split(';')) {
|
||||
const seg = part.trim()
|
||||
if (seg.startsWith(prefix)) return seg.slice(prefix.length).trim()
|
||||
function isManualOutRow(row: QcMaterialUsage) {
|
||||
if (row.sourceType === 'manual_out') return true
|
||||
const remark = row.remark || ''
|
||||
return row.changeType === 'use' && (row.changeQty ?? 0) < 0 && remark.includes('领用人:')
|
||||
}
|
||||
return ''
|
||||
}
|
||||
|
||||
function displayReceiver(row: QcMaterialUsage) {
|
||||
if (!isManualOutRow(row)) return '-'
|
||||
return (row.receiver || '').trim() || parseFromRemark(row.remark || '', 'receiver') || '-'
|
||||
}
|
||||
|
||||
function displayPurpose(row: QcMaterialUsage) {
|
||||
if (!isManualOutRow(row)) return '-'
|
||||
return (row.purpose || '').trim() || parseFromRemark(row.remark || '', 'purpose') || '-'
|
||||
}
|
||||
|
||||
function displayUseTime(row: QcMaterialUsage) {
|
||||
if (!isManualOutRow(row)) return '-'
|
||||
return (row.useTime || row.createTime || '').trim() || '-'
|
||||
}
|
||||
|
||||
function displayStockBefore(row: QcMaterialUsage) {
|
||||
const after = Number(row.stockAfter ?? 0)
|
||||
const qty = Number(row.changeQty ?? 0)
|
||||
return after - qty
|
||||
}
|
||||
|
||||
function getChangeTypeLabel(row: QcMaterialUsage) {
|
||||
if (row.changeType === 'in') return '入库'
|
||||
if (isManualOutRow(row)) return '出库'
|
||||
return '任务使用'
|
||||
}
|
||||
|
||||
function getChangeTypeTagType(row: QcMaterialUsage) {
|
||||
if (row.changeType === 'in') return 'success'
|
||||
if (isManualOutRow(row)) return 'warning'
|
||||
return 'primary'
|
||||
}
|
||||
|
||||
function getSourceTagType(sourceType: string) {
|
||||
if (sourceType === 'task_complete') return 'primary'
|
||||
if (sourceType === 'manual_in') return 'success'
|
||||
if (sourceType === 'manual_out') return 'warning'
|
||||
return 'info'
|
||||
}
|
||||
|
||||
function getSourceLabel(sourceType: string) {
|
||||
if (sourceType === 'task_complete') return '任务完成'
|
||||
if (sourceType === 'manual_in') return '手动入库'
|
||||
if (sourceType === 'manual_out') return '手动出库'
|
||||
return sourceType || '-'
|
||||
}
|
||||
|
||||
async function loadMaterialOptions() {
|
||||
try {
|
||||
const res = await fetchQcMaterialOptions()
|
||||
materialOptions.value = res || []
|
||||
} catch {
|
||||
materialOptions.value = []
|
||||
function parseFromRemark(remark: string, key: 'receiver' | 'purpose') {
|
||||
const prefix = key === 'receiver' ? '领用人:' : '用途:'
|
||||
for (const part of remark.split(';')) {
|
||||
const seg = part.trim()
|
||||
if (seg.startsWith(prefix)) return seg.slice(prefix.length).trim()
|
||||
}
|
||||
return ''
|
||||
}
|
||||
}
|
||||
|
||||
async function loadData() {
|
||||
loading.value = true
|
||||
try {
|
||||
const params: Record<string, any> = {
|
||||
page: pagination.page,
|
||||
pageSize: pagination.pageSize
|
||||
}
|
||||
if (searchForm.materialId) params.materialId = searchForm.materialId
|
||||
if (searchForm.changeType) params.changeType = searchForm.changeType
|
||||
if (dateRange.value && dateRange.value[0]) {
|
||||
params.startTime = dateRange.value[0]
|
||||
params.endTime = dateRange.value[1]
|
||||
}
|
||||
const res = await fetchQcMaterialUsageList(params)
|
||||
tableData.value = res.list || []
|
||||
pagination.total = res.total ?? 0
|
||||
} finally {
|
||||
loading.value = false
|
||||
function displayReceiver(row: QcMaterialUsage) {
|
||||
if (!isManualOutRow(row)) return '-'
|
||||
return (row.receiver || '').trim() || parseFromRemark(row.remark || '', 'receiver') || '-'
|
||||
}
|
||||
}
|
||||
|
||||
function handleSearch() {
|
||||
pagination.page = 1
|
||||
loadData()
|
||||
}
|
||||
function displayPurpose(row: QcMaterialUsage) {
|
||||
if (!isManualOutRow(row)) return '-'
|
||||
return (row.purpose || '').trim() || parseFromRemark(row.remark || '', 'purpose') || '-'
|
||||
}
|
||||
|
||||
function handleReset() {
|
||||
searchForm.materialId = ''
|
||||
searchForm.changeType = ''
|
||||
dateRange.value = null
|
||||
pagination.page = 1
|
||||
loadData()
|
||||
}
|
||||
function displayUseTime(row: QcMaterialUsage) {
|
||||
if (!isManualOutRow(row)) return '-'
|
||||
return (row.useTime || row.createTime || '').trim() || '-'
|
||||
}
|
||||
|
||||
const viewVisible = ref(false)
|
||||
const viewRow = ref<QcMaterialUsage | null>(null)
|
||||
function displayStockBefore(row: QcMaterialUsage) {
|
||||
const after = Number(row.stockAfter ?? 0)
|
||||
const qty = Number(row.changeQty ?? 0)
|
||||
return after - qty
|
||||
}
|
||||
|
||||
const viewSections = computed(() => {
|
||||
if (!viewRow.value) return []
|
||||
const row = viewRow.value
|
||||
return [
|
||||
{
|
||||
title: '变动信息',
|
||||
fields: [
|
||||
{ label: '物料编码', value: row.materialCode },
|
||||
{ label: '物料名称', value: row.materialName },
|
||||
{
|
||||
label: '变动类型',
|
||||
value: getChangeTypeLabel(row),
|
||||
tagType: getChangeTypeTagType(row) as any
|
||||
},
|
||||
{ label: '变动数量', value: row.changeQty < 0 ? String(row.changeQty) : `+${row.changeQty}` },
|
||||
{ label: '变动前库存', value: String(displayStockBefore(row)) },
|
||||
{ label: '变动后库存', value: String(row.stockAfter ?? 0) },
|
||||
{ label: '关联任务号', value: row.qcTaskNo || '-' },
|
||||
{
|
||||
label: '来源类型',
|
||||
value: getSourceLabel(row.sourceType),
|
||||
tagType: getSourceTagType(row.sourceType) as any
|
||||
},
|
||||
{
|
||||
label: '领用人',
|
||||
value: displayReceiver(row) === '-' ? '—' : displayReceiver(row)
|
||||
},
|
||||
{
|
||||
label: '用途',
|
||||
value: displayPurpose(row) === '-' ? '—' : displayPurpose(row)
|
||||
},
|
||||
{
|
||||
label: '领用时间',
|
||||
value: displayUseTime(row) === '-' ? '—' : displayUseTime(row)
|
||||
},
|
||||
{ label: '备注', value: row.remark || '—', span: true },
|
||||
{ label: '操作人', value: row.createByName || row.createBy || '-' },
|
||||
{ label: '变动时间', value: row.createTime || '—' }
|
||||
]
|
||||
function getChangeTypeLabel(row: QcMaterialUsage) {
|
||||
if (row.changeType === 'in') return '入库'
|
||||
if (isManualOutRow(row)) return '出库'
|
||||
return '任务使用'
|
||||
}
|
||||
|
||||
function getChangeTypeTagType(row: QcMaterialUsage) {
|
||||
if (row.changeType === 'in') return 'success'
|
||||
if (isManualOutRow(row)) return 'warning'
|
||||
return 'primary'
|
||||
}
|
||||
|
||||
function getSourceTagType(sourceType: string) {
|
||||
if (sourceType === 'task_complete') return 'primary'
|
||||
if (sourceType === 'manual_in') return 'success'
|
||||
if (sourceType === 'manual_out') return 'warning'
|
||||
return 'info'
|
||||
}
|
||||
|
||||
function getSourceLabel(sourceType: string) {
|
||||
if (sourceType === 'task_complete') return '任务完成'
|
||||
if (sourceType === 'manual_in') return '手动入库'
|
||||
if (sourceType === 'manual_out') return '手动出库'
|
||||
return sourceType || '-'
|
||||
}
|
||||
|
||||
async function loadMaterialOptions() {
|
||||
try {
|
||||
const res = await fetchQcMaterialOptions()
|
||||
materialOptions.value = res || []
|
||||
} catch {
|
||||
materialOptions.value = []
|
||||
}
|
||||
]
|
||||
})
|
||||
}
|
||||
|
||||
function handleView(row: QcMaterialUsage) {
|
||||
viewRow.value = row
|
||||
viewVisible.value = true
|
||||
}
|
||||
async function loadData() {
|
||||
loading.value = true
|
||||
try {
|
||||
const params: Record<string, any> = {
|
||||
page: pagination.page,
|
||||
pageSize: pagination.pageSize
|
||||
}
|
||||
if (searchForm.materialId) params.materialId = searchForm.materialId
|
||||
if (searchForm.changeType) params.changeType = searchForm.changeType
|
||||
if (dateRange.value && dateRange.value[0]) {
|
||||
params.startTime = dateRange.value[0]
|
||||
params.endTime = dateRange.value[1]
|
||||
}
|
||||
const res = await fetchQcMaterialUsageList(params)
|
||||
tableData.value = res.list || []
|
||||
pagination.total = res.total ?? 0
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function handleSearch() {
|
||||
pagination.page = 1
|
||||
loadData()
|
||||
}
|
||||
|
||||
function handleReset() {
|
||||
searchForm.materialId = ''
|
||||
searchForm.changeType = ''
|
||||
dateRange.value = null
|
||||
pagination.page = 1
|
||||
loadData()
|
||||
}
|
||||
|
||||
const viewVisible = ref(false)
|
||||
const viewRow = ref<QcMaterialUsage | null>(null)
|
||||
|
||||
const viewSections = computed(() => {
|
||||
if (!viewRow.value) return []
|
||||
const row = viewRow.value
|
||||
return [
|
||||
{
|
||||
title: '变动信息',
|
||||
fields: [
|
||||
{ label: '物料编码', value: row.materialCode },
|
||||
{ label: '物料名称', value: row.materialName },
|
||||
{
|
||||
label: '变动类型',
|
||||
value: getChangeTypeLabel(row),
|
||||
tagType: getChangeTypeTagType(row) as any
|
||||
},
|
||||
{
|
||||
label: '变动数量',
|
||||
value: row.changeQty < 0 ? String(row.changeQty) : `+${row.changeQty}`
|
||||
},
|
||||
{ label: '变动前库存', value: String(displayStockBefore(row)) },
|
||||
{ label: '变动后库存', value: String(row.stockAfter ?? 0) },
|
||||
{ label: '关联任务号', value: row.qcTaskNo || '-' },
|
||||
{
|
||||
label: '来源类型',
|
||||
value: getSourceLabel(row.sourceType),
|
||||
tagType: getSourceTagType(row.sourceType) as any
|
||||
},
|
||||
{
|
||||
label: '领用人',
|
||||
value: displayReceiver(row) === '-' ? '—' : displayReceiver(row)
|
||||
},
|
||||
{
|
||||
label: '用途',
|
||||
value: displayPurpose(row) === '-' ? '—' : displayPurpose(row)
|
||||
},
|
||||
{
|
||||
label: '领用时间',
|
||||
value: displayUseTime(row) === '-' ? '—' : displayUseTime(row)
|
||||
},
|
||||
{ label: '备注', value: row.remark || '—', span: true },
|
||||
{ label: '操作人', value: row.createByName || row.createBy || '-' },
|
||||
{ label: '变动时间', value: row.createTime || '—' }
|
||||
]
|
||||
}
|
||||
]
|
||||
})
|
||||
|
||||
function handleView(row: QcMaterialUsage) {
|
||||
viewRow.value = row
|
||||
viewVisible.value = true
|
||||
}
|
||||
// 输入检索条件时自动搜索(防抖 300ms)
|
||||
watchDebounced(
|
||||
searchForm,
|
||||
@@ -431,31 +443,28 @@ function handleView(row: QcMaterialUsage) {
|
||||
)
|
||||
|
||||
// 日期范围变更时自动搜索
|
||||
watch(
|
||||
dateRange,
|
||||
() => {
|
||||
pagination.page = 1
|
||||
loadData()
|
||||
}
|
||||
)
|
||||
watch(dateRange, () => {
|
||||
pagination.page = 1
|
||||
loadData()
|
||||
})
|
||||
|
||||
onMounted(async () => {
|
||||
await loadMaterialOptions()
|
||||
if (route.query.materialId) {
|
||||
searchForm.materialId = route.query.materialId as string
|
||||
}
|
||||
loadData()
|
||||
})
|
||||
onMounted(async () => {
|
||||
await loadMaterialOptions()
|
||||
if (route.query.materialId) {
|
||||
searchForm.materialId = route.query.materialId as string
|
||||
}
|
||||
loadData()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.qty-positive {
|
||||
color: var(--el-color-success);
|
||||
font-weight: 500;
|
||||
}
|
||||
.qty-positive {
|
||||
color: var(--el-color-success);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.qty-negative {
|
||||
color: var(--el-color-danger);
|
||||
font-weight: 500;
|
||||
}
|
||||
.qty-negative {
|
||||
color: var(--el-color-danger);
|
||||
font-weight: 500;
|
||||
}
|
||||
</style>
|
||||
@@ -4,7 +4,9 @@
|
||||
<div class="page-container">
|
||||
<header class="page-head">
|
||||
<h1>质检物料库</h1>
|
||||
<p class="page-desc">管理质检物料信息,支持入库、出库操作;质检任务完成时也会自动扣减库存。</p>
|
||||
<p class="page-desc"
|
||||
>管理质检物料信息,支持入库、出库操作;质检任务完成时也会自动扣减库存。</p
|
||||
>
|
||||
</header>
|
||||
|
||||
<section class="panel">
|
||||
@@ -28,8 +30,12 @@
|
||||
<div class="filter-actions-row">
|
||||
<span class="filter-hint">按编码、名称模糊查询,可按状态筛选。</span>
|
||||
<div class="filter-actions-end">
|
||||
<el-button class="btn-ghost" @click="handleReset"><el-icon><RefreshLeft /></el-icon>重置</el-button>
|
||||
<el-button class="btn-main" type="primary" @click="handleSearch"><el-icon><Search /></el-icon>搜索</el-button>
|
||||
<el-button class="btn-ghost" @click="handleReset"
|
||||
><el-icon><RefreshLeft /></el-icon>重置</el-button
|
||||
>
|
||||
<el-button class="btn-main" type="primary" @click="handleSearch"
|
||||
><el-icon><Search /></el-icon>搜索</el-button
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</el-form>
|
||||
@@ -137,15 +143,29 @@
|
||||
header-align="center"
|
||||
show-overflow-tooltip
|
||||
/>
|
||||
<el-table-column label="操作" min-width="480" fixed="right" align="center" header-align="center">
|
||||
<el-table-column
|
||||
label="操作"
|
||||
min-width="480"
|
||||
fixed="right"
|
||||
align="center"
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<el-button class="btn-op-item" @click="handleStockIn(row)"><el-icon><Upload /></el-icon>入库</el-button>
|
||||
<el-button class="btn-op-item" @click="handleStockOut(row)"><el-icon><Download /></el-icon>出库</el-button>
|
||||
<el-button class="btn-op-item" @click="handleStockIn(row)"
|
||||
><el-icon><Upload /></el-icon>入库</el-button
|
||||
>
|
||||
<el-button class="btn-op-item" @click="handleStockOut(row)"
|
||||
><el-icon><Download /></el-icon>出库</el-button
|
||||
>
|
||||
<el-button class="btn-op-item" @click="handleView(row)"
|
||||
><el-icon><View /></el-icon>查看</el-button
|
||||
>
|
||||
<el-button class="btn-op-item primary" @click="handleEdit(row)"><el-icon><Edit /></el-icon>编辑</el-button>
|
||||
<el-button class="btn-op-item" @click="handleDelete(row)"><el-icon><Delete /></el-icon>删除</el-button>
|
||||
<el-button class="btn-op-item primary" @click="handleEdit(row)"
|
||||
><el-icon><Edit /></el-icon>编辑</el-button
|
||||
>
|
||||
<el-button class="btn-op-item" @click="handleDelete(row)"
|
||||
><el-icon><Delete /></el-icon>删除</el-button
|
||||
>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
@@ -211,7 +231,9 @@
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="预警库存">
|
||||
<template #label>预警库存<span class="form-label-tip">低于该值时列表显示库存预警</span></template>
|
||||
<template #label
|
||||
>预警库存<span class="form-label-tip">低于该值时列表显示库存预警</span></template
|
||||
>
|
||||
<el-input-number
|
||||
v-model="formData.warningQty"
|
||||
:min="0"
|
||||
@@ -228,12 +250,7 @@
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="备注" class="form-col-span-2">
|
||||
<el-input
|
||||
v-model="formData.remark"
|
||||
type="textarea"
|
||||
:rows="3"
|
||||
placeholder="可选"
|
||||
/>
|
||||
<el-input v-model="formData.remark" type="textarea" :rows="3" placeholder="可选" />
|
||||
</el-form-item>
|
||||
</div>
|
||||
</el-form>
|
||||
@@ -287,12 +304,7 @@
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="备注">
|
||||
<el-input
|
||||
v-model="stockInForm.remark"
|
||||
type="textarea"
|
||||
:rows="3"
|
||||
placeholder="可选"
|
||||
/>
|
||||
<el-input v-model="stockInForm.remark" type="textarea" :rows="3" placeholder="可选" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
@@ -371,20 +383,10 @@
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="用途">
|
||||
<el-input
|
||||
v-model="stockOutForm.purpose"
|
||||
type="textarea"
|
||||
:rows="2"
|
||||
placeholder="可选"
|
||||
/>
|
||||
<el-input v-model="stockOutForm.purpose" type="textarea" :rows="2" placeholder="可选" />
|
||||
</el-form-item>
|
||||
<el-form-item label="备注">
|
||||
<el-input
|
||||
v-model="stockOutForm.remark"
|
||||
type="textarea"
|
||||
:rows="2"
|
||||
placeholder="可选"
|
||||
/>
|
||||
<el-input v-model="stockOutForm.remark" type="textarea" :rows="2" placeholder="可选" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
@@ -485,13 +487,41 @@
|
||||
header-align="center"
|
||||
:index="(index) => index + 1"
|
||||
/>
|
||||
<el-table-column prop="materialCode" label="物料编码" min-width="120" align="center" header-align="center" />
|
||||
<el-table-column prop="materialName" label="物料名称" min-width="140" align="center" header-align="center" />
|
||||
<el-table-column prop="unit" label="单位" min-width="80" align="center" header-align="center" />
|
||||
<el-table-column label="当前库存" min-width="100" align="center" header-align="center">
|
||||
<el-table-column
|
||||
prop="materialCode"
|
||||
label="物料编码"
|
||||
min-width="120"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="materialName"
|
||||
label="物料名称"
|
||||
min-width="140"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="unit"
|
||||
label="单位"
|
||||
min-width="80"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
label="当前库存"
|
||||
min-width="100"
|
||||
align="center"
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }">{{ row.stockQty }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="入库数量" min-width="130" align="center" header-align="center">
|
||||
<el-table-column
|
||||
label="入库数量"
|
||||
min-width="130"
|
||||
align="center"
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<el-input-number
|
||||
v-model="row.qty"
|
||||
@@ -505,22 +535,28 @@
|
||||
</el-table-column>
|
||||
<el-table-column label="备注" min-width="160" align="center" header-align="center">
|
||||
<template #default="{ row }">
|
||||
<el-input v-model="row.remark" placeholder="可选" clearable class="inner-cell-input" />
|
||||
<el-input
|
||||
v-model="row.remark"
|
||||
placeholder="可选"
|
||||
clearable
|
||||
class="inner-cell-input"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" min-width="80" align="center" header-align="center">
|
||||
<template #default="{ $index }">
|
||||
<el-button link type="danger" size="small" @click="batchStockInLines.splice($index, 1)">
|
||||
<el-button
|
||||
link
|
||||
type="danger"
|
||||
size="small"
|
||||
@click="batchStockInLines.splice($index, 1)"
|
||||
>
|
||||
移除
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<el-empty
|
||||
v-else
|
||||
description="请通过上方搜索框添加要入库的物料"
|
||||
:image-size="56"
|
||||
/>
|
||||
<el-empty v-else description="请通过上方搜索框添加要入库的物料" :image-size="56" />
|
||||
</div>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
@@ -649,13 +685,41 @@
|
||||
header-align="center"
|
||||
:index="(index) => index + 1"
|
||||
/>
|
||||
<el-table-column prop="materialCode" label="物料编码" min-width="120" align="center" header-align="center" />
|
||||
<el-table-column prop="materialName" label="物料名称" min-width="140" align="center" header-align="center" />
|
||||
<el-table-column prop="unit" label="单位" min-width="80" align="center" header-align="center" />
|
||||
<el-table-column label="当前库存" min-width="100" align="center" header-align="center">
|
||||
<el-table-column
|
||||
prop="materialCode"
|
||||
label="物料编码"
|
||||
min-width="120"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="materialName"
|
||||
label="物料名称"
|
||||
min-width="140"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="unit"
|
||||
label="单位"
|
||||
min-width="80"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
label="当前库存"
|
||||
min-width="100"
|
||||
align="center"
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }">{{ row.stockQty }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="出库数量" min-width="130" align="center" header-align="center">
|
||||
<el-table-column
|
||||
label="出库数量"
|
||||
min-width="130"
|
||||
align="center"
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<el-input-number
|
||||
v-model="row.qty"
|
||||
@@ -670,22 +734,28 @@
|
||||
</el-table-column>
|
||||
<el-table-column label="备注" min-width="160" align="center" header-align="center">
|
||||
<template #default="{ row }">
|
||||
<el-input v-model="row.remark" placeholder="可选" clearable class="inner-cell-input" />
|
||||
<el-input
|
||||
v-model="row.remark"
|
||||
placeholder="可选"
|
||||
clearable
|
||||
class="inner-cell-input"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" min-width="80" align="center" header-align="center">
|
||||
<template #default="{ $index }">
|
||||
<el-button link type="danger" size="small" @click="batchStockOutLines.splice($index, 1)">
|
||||
<el-button
|
||||
link
|
||||
type="danger"
|
||||
size="small"
|
||||
@click="batchStockOutLines.splice($index, 1)"
|
||||
>
|
||||
移除
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<el-empty
|
||||
v-else
|
||||
description="请通过上方搜索框添加要出库的物料"
|
||||
:image-size="56"
|
||||
/>
|
||||
<el-empty v-else description="请通过上方搜索框添加要出库的物料" :image-size="56" />
|
||||
</div>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
@@ -720,7 +790,19 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, onMounted, computed } from 'vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { Plus, Close, Check, View, Box, RefreshLeft, Search, Upload, Download, Edit, Delete } from '@element-plus/icons-vue'
|
||||
import {
|
||||
Plus,
|
||||
Close,
|
||||
Check,
|
||||
View,
|
||||
Box,
|
||||
RefreshLeft,
|
||||
Search,
|
||||
Upload,
|
||||
Download,
|
||||
Edit,
|
||||
Delete
|
||||
} from '@element-plus/icons-vue'
|
||||
import SystemViewDialog from '@/components/business/system-view-dialog/index.vue'
|
||||
import { fetchItemUnitList, fetchUserList } from '@/api/admin'
|
||||
import {
|
||||
|
||||
@@ -4,7 +4,9 @@
|
||||
<div class="page-container">
|
||||
<header class="page-head">
|
||||
<h1>质检任务</h1>
|
||||
<p class="page-desc">按检测项目跟进任务;实验室自检需开始后再填写结果,第三方在填写结果时一并上传检测材料。</p>
|
||||
<p class="page-desc"
|
||||
>按检测项目跟进任务;实验室自检需开始后再填写结果,第三方在填写结果时一并上传检测材料。</p
|
||||
>
|
||||
</header>
|
||||
|
||||
<section class="panel">
|
||||
@@ -23,20 +25,35 @@
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="业务类型">
|
||||
<el-select v-model="searchForm.qcBizType" placeholder="全部" clearable style="width: 100%">
|
||||
<el-select
|
||||
v-model="searchForm.qcBizType"
|
||||
placeholder="全部"
|
||||
clearable
|
||||
style="width: 100%"
|
||||
>
|
||||
<el-option label="入库质检" value="inbound" />
|
||||
<el-option label="出库质检" value="outbound" />
|
||||
<el-option label="临时质检" value="temporary" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="任务类型">
|
||||
<el-select v-model="searchForm.taskType" placeholder="全部" clearable style="width: 100%">
|
||||
<el-select
|
||||
v-model="searchForm.taskType"
|
||||
placeholder="全部"
|
||||
clearable
|
||||
style="width: 100%"
|
||||
>
|
||||
<el-option label="实验室自检" value="sysz" />
|
||||
<el-option label="第三方检测" value="dsfjc" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="任务状态">
|
||||
<el-select v-model="searchForm.taskStatus" placeholder="全部" clearable style="width: 100%">
|
||||
<el-select
|
||||
v-model="searchForm.taskStatus"
|
||||
placeholder="全部"
|
||||
clearable
|
||||
style="width: 100%"
|
||||
>
|
||||
<el-option label="待执行" value="dzx" />
|
||||
<el-option label="进行中" value="jxz" />
|
||||
<el-option label="已完成" value="ywc" />
|
||||
@@ -45,16 +62,32 @@
|
||||
</div>
|
||||
<div class="filter-actions-row">
|
||||
<div class="filter-actions-end">
|
||||
<el-button class="btn-ghost" @click="handleReset"><el-icon><RefreshLeft /></el-icon>重置</el-button>
|
||||
<el-button class="btn-main" type="primary" @click="handleSearch"><el-icon><Search /></el-icon>搜索</el-button>
|
||||
<el-button class="btn-ghost" @click="handleReset"
|
||||
><el-icon><RefreshLeft /></el-icon>重置</el-button
|
||||
>
|
||||
<el-button class="btn-main" type="primary" @click="handleSearch"
|
||||
><el-icon><Search /></el-icon>搜索</el-button
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</el-form>
|
||||
</div>
|
||||
|
||||
<div class="list-body list-wrap">
|
||||
<el-table class="list-table" :data="tableData" v-loading="loading" stripe @row-dblclick="handleView">
|
||||
<el-table-column type="index" label="序号" min-width="68" align="center" :index="(i) => (pagination.page - 1) * pagination.pageSize + i + 1" />
|
||||
<el-table
|
||||
class="list-table"
|
||||
:data="tableData"
|
||||
v-loading="loading"
|
||||
stripe
|
||||
@row-dblclick="handleView"
|
||||
>
|
||||
<el-table-column
|
||||
type="index"
|
||||
label="序号"
|
||||
min-width="68"
|
||||
align="center"
|
||||
:index="(i) => (pagination.page - 1) * pagination.pageSize + i + 1"
|
||||
/>
|
||||
<el-table-column prop="taskNo" label="任务号" min-width="150" align="center" />
|
||||
<el-table-column prop="qcBizTypeLabel" label="业务类型" min-width="100" align="center">
|
||||
<template #default="{ row }">
|
||||
@@ -71,7 +104,12 @@
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="receptionSourceLabel" label="样本来源" min-width="100" align="center">
|
||||
<el-table-column
|
||||
prop="receptionSourceLabel"
|
||||
label="样本来源"
|
||||
min-width="100"
|
||||
align="center"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<span v-if="shouldShowTempEmptyReceptionSource(row)">—</span>
|
||||
<el-tag
|
||||
@@ -97,7 +135,9 @@
|
||||
</el-table-column>
|
||||
<el-table-column prop="taskType" label="检测类型" min-width="110" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="row.taskType === 'sysz' ? 'success' : 'info'" size="small">{{ taskTypeLabel(row.taskType) }}</el-tag>
|
||||
<el-tag :type="row.taskType === 'sysz' ? 'success' : 'info'" size="small">{{
|
||||
taskTypeLabel(row.taskType)
|
||||
}}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="itemName" label="检测项目" min-width="160" align="center" />
|
||||
@@ -106,18 +146,10 @@
|
||||
</el-table-column>
|
||||
<el-table-column prop="conclusion" label="质检结论" min-width="100" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-tag
|
||||
v-if="row.conclusion === 'tg'"
|
||||
type="success"
|
||||
size="small"
|
||||
>
|
||||
<el-tag v-if="row.conclusion === 'tg'" type="success" size="small">
|
||||
{{ row.conclusionLabel || qcTaskConclusionLabel(row.conclusion) }}
|
||||
</el-tag>
|
||||
<el-tag
|
||||
v-else-if="row.conclusion === 'btg'"
|
||||
type="danger"
|
||||
size="small"
|
||||
>
|
||||
<el-tag v-else-if="row.conclusion === 'btg'" type="danger" size="small">
|
||||
{{ row.conclusionLabel || qcTaskConclusionLabel(row.conclusion) }}
|
||||
</el-tag>
|
||||
<el-tag v-else type="info" size="small">—</el-tag>
|
||||
@@ -125,24 +157,50 @@
|
||||
</el-table-column>
|
||||
<el-table-column prop="taskStatus" label="状态" min-width="100" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="row.taskStatus === 'ywc' ? 'success' : row.taskStatus === 'jxz' ? 'warning' : 'info'" size="small">
|
||||
<el-tag
|
||||
:type="
|
||||
row.taskStatus === 'ywc'
|
||||
? 'success'
|
||||
: row.taskStatus === 'jxz'
|
||||
? 'warning'
|
||||
: 'info'
|
||||
"
|
||||
size="small"
|
||||
>
|
||||
{{ taskStatusLabel(row.taskStatus) }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="remark" label="备注" min-width="140" align="center" show-overflow-tooltip>
|
||||
<el-table-column
|
||||
prop="remark"
|
||||
label="备注"
|
||||
min-width="140"
|
||||
align="center"
|
||||
show-overflow-tooltip
|
||||
>
|
||||
<template #default="{ row }">{{ row.remark || '—' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" min-width="320" fixed="right" align="center">
|
||||
<template #default="{ row }">
|
||||
<template v-if="row.taskType === 'sysz' && row.taskStatus === 'dzx'">
|
||||
<el-button class="btn-op-item primary" @click="handleStart(row)"><el-icon><VideoPlay /></el-icon>开始</el-button>
|
||||
<el-button class="btn-op-item primary" @click="handleStart(row)"
|
||||
><el-icon><VideoPlay /></el-icon>开始</el-button
|
||||
>
|
||||
</template>
|
||||
<template v-if="canFillResult(row)">
|
||||
<el-button class="btn-op-item primary" @click="openResult(row)"><el-icon><EditPen /></el-icon>填写结果</el-button>
|
||||
<el-button class="btn-op-item primary" @click="openResult(row)"
|
||||
><el-icon><EditPen /></el-icon>填写结果</el-button
|
||||
>
|
||||
</template>
|
||||
<el-button class="btn-op-item" @click="handleView(row)"><el-icon><View /></el-icon>查看</el-button>
|
||||
<el-button v-if="row.taskStatus === 'dzx'" class="btn-op-item" @click="handleDelete(row)">删除</el-button>
|
||||
<el-button class="btn-op-item" @click="handleView(row)"
|
||||
><el-icon><View /></el-icon>查看</el-button
|
||||
>
|
||||
<el-button
|
||||
v-if="row.taskStatus === 'dzx'"
|
||||
class="btn-op-item"
|
||||
@click="handleDelete(row)"
|
||||
>删除</el-button
|
||||
>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
@@ -167,142 +225,165 @@
|
||||
destroy-on-close
|
||||
:close-on-click-modal="false"
|
||||
>
|
||||
<DialogFormBody
|
||||
:loading="resultLoading"
|
||||
>
|
||||
<el-form label-position="top" class="biz-dialog-form">
|
||||
<div class="form-section">
|
||||
<div class="section-title">检测参数</div>
|
||||
<div
|
||||
v-if="!resultLoading && resultParams.length === 0"
|
||||
style="text-align: center; padding: 8px 0; color: #94a3b8; font-size: 13px"
|
||||
>
|
||||
该项目暂无配置参数,请先在实验室/第三方项目中维护检测参数
|
||||
</div>
|
||||
<div class="form-grid-3">
|
||||
<el-form-item
|
||||
v-for="(param, idx) in resultParams"
|
||||
:key="param.paramId || param.paramCode || idx"
|
||||
:label="param.paramName + (param.paramUnit ? `(${param.paramUnit})` : '')"
|
||||
<DialogFormBody :loading="resultLoading">
|
||||
<el-form label-position="top" class="biz-dialog-form">
|
||||
<div class="form-section">
|
||||
<div class="section-title">检测参数</div>
|
||||
<div
|
||||
v-if="!resultLoading && resultParams.length === 0"
|
||||
style="text-align: center; padding: 8px 0; color: #94a3b8; font-size: 13px"
|
||||
>
|
||||
<el-input v-if="param.paramType === 'text'" v-model="param.paramValue" placeholder="请输入" />
|
||||
<el-input-number
|
||||
v-else
|
||||
:model-value="toResultNumber(param.paramValue)"
|
||||
:precision="2"
|
||||
style="width: 100%"
|
||||
placeholder="请输入"
|
||||
@update:model-value="(v) => setResultNumber(param, v)"
|
||||
/>
|
||||
</el-form-item>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="resultTaskType !== 'dsfjc'" class="form-section material-usage-section">
|
||||
<div class="section-title material-usage-head">
|
||||
<div class="section-title-left"><h3>物料使用(选填)</h3></div>
|
||||
<el-button class="btn-main" type="primary" size="small" @click="addMaterialRow">
|
||||
<el-icon><Plus /></el-icon>添加物料
|
||||
</el-button>
|
||||
</div>
|
||||
<el-table
|
||||
v-if="materialUsages.length"
|
||||
:data="materialUsages"
|
||||
:row-key="(row) => row._rowKey"
|
||||
stripe
|
||||
class="list-table inner-table material-usage-table"
|
||||
max-height="280"
|
||||
>
|
||||
<el-table-column
|
||||
type="index"
|
||||
label="序号"
|
||||
min-width="56"
|
||||
align="center"
|
||||
header-align="center"
|
||||
:index="(index) => index + 1"
|
||||
/>
|
||||
<el-table-column label="物料名称" min-width="160" align="center" header-align="center">
|
||||
<template #default="{ row, $index }">
|
||||
<el-select
|
||||
v-model="row.materialId"
|
||||
filterable
|
||||
placeholder="搜索并选择物料"
|
||||
class="inner-cell-input"
|
||||
@change="(v) => onMaterialSelect($index, v)"
|
||||
>
|
||||
<el-option
|
||||
v-for="opt in materialOptions"
|
||||
:key="opt.id"
|
||||
:label="opt.label"
|
||||
:value="opt.id"
|
||||
:disabled="isMaterialOptionDisabled(opt.id, $index)"
|
||||
/>
|
||||
</el-select>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="规格型号" min-width="120" align="center" header-align="center">
|
||||
<template #default="{ row }">{{ row.specModel || '—' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="用量" min-width="120" align="center" header-align="center">
|
||||
<template #default="{ row }">
|
||||
<el-input-number
|
||||
v-model="row.qty"
|
||||
:min="0.0001"
|
||||
:precision="4"
|
||||
:step="1"
|
||||
controls-position="right"
|
||||
placeholder="用量"
|
||||
class="inner-cell-input"
|
||||
该项目暂无配置参数,请先在实验室/第三方项目中维护检测参数
|
||||
</div>
|
||||
<div class="form-grid-3">
|
||||
<el-form-item
|
||||
v-for="(param, idx) in resultParams"
|
||||
:key="param.paramId || param.paramCode || idx"
|
||||
:label="param.paramName + (param.paramUnit ? `(${param.paramUnit})` : '')"
|
||||
>
|
||||
<el-input
|
||||
v-if="param.paramType === 'text'"
|
||||
v-model="param.paramValue"
|
||||
placeholder="请输入"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="单位" min-width="72" align="center" header-align="center">
|
||||
<template #default="{ row }">{{ row.unit || '—' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="备注" min-width="140" align="center" header-align="center">
|
||||
<template #default="{ row }">
|
||||
<el-input v-model="row.remark" placeholder="选填" clearable class="inner-cell-input" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" min-width="96" align="center" header-align="center" fixed="right">
|
||||
<template #default="{ $index }">
|
||||
<el-button link type="danger" size="small" @click="removeMaterialRow($index)">
|
||||
删除
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<el-empty
|
||||
v-else
|
||||
description="暂无物料记录,可点击上方按钮添加"
|
||||
:image-size="56"
|
||||
/>
|
||||
</div>
|
||||
<div class="form-section result-conclusion-section">
|
||||
<div class="section-title">检测结论</div>
|
||||
<el-form-item label="结论" required>
|
||||
<el-radio-group v-model="resultConclusion">
|
||||
<el-radio value="tg">通过</el-radio>
|
||||
<el-radio value="btg">不通过</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="备注">
|
||||
<el-input v-model="resultRemark" type="textarea" :rows="3" placeholder="选填" />
|
||||
</el-form-item>
|
||||
<div class="result-attachment-block">
|
||||
<ProtocolFinanceConfirmFields
|
||||
v-model:finance-remark="resultRemark"
|
||||
v-model:attachments="resultAttachments"
|
||||
:show-remark="false"
|
||||
:attachment-label="resultAttachmentLabel"
|
||||
:upload-folder="resultUploadFolder"
|
||||
/>
|
||||
<el-input-number
|
||||
v-else
|
||||
:model-value="toResultNumber(param.paramValue)"
|
||||
:precision="2"
|
||||
style="width: 100%"
|
||||
placeholder="请输入"
|
||||
@update:model-value="(v) => setResultNumber(param, v)"
|
||||
/>
|
||||
</el-form-item>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-form>
|
||||
<div v-if="resultTaskType !== 'dsfjc'" class="form-section material-usage-section">
|
||||
<div class="section-title material-usage-head">
|
||||
<div class="section-title-left"><h3>物料使用(选填)</h3></div>
|
||||
<el-button class="btn-main" type="primary" size="small" @click="addMaterialRow">
|
||||
<el-icon><Plus /></el-icon>添加物料
|
||||
</el-button>
|
||||
</div>
|
||||
<el-table
|
||||
v-if="materialUsages.length"
|
||||
:data="materialUsages"
|
||||
:row-key="(row) => row._rowKey"
|
||||
stripe
|
||||
class="list-table inner-table material-usage-table"
|
||||
max-height="280"
|
||||
>
|
||||
<el-table-column
|
||||
type="index"
|
||||
label="序号"
|
||||
min-width="56"
|
||||
align="center"
|
||||
header-align="center"
|
||||
:index="(index) => index + 1"
|
||||
/>
|
||||
<el-table-column
|
||||
label="物料名称"
|
||||
min-width="160"
|
||||
align="center"
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row, $index }">
|
||||
<el-select
|
||||
v-model="row.materialId"
|
||||
filterable
|
||||
placeholder="搜索并选择物料"
|
||||
class="inner-cell-input"
|
||||
@change="(v) => onMaterialSelect($index, v)"
|
||||
>
|
||||
<el-option
|
||||
v-for="opt in materialOptions"
|
||||
:key="opt.id"
|
||||
:label="opt.label"
|
||||
:value="opt.id"
|
||||
:disabled="isMaterialOptionDisabled(opt.id, $index)"
|
||||
/>
|
||||
</el-select>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
label="规格型号"
|
||||
min-width="120"
|
||||
align="center"
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }">{{ row.specModel || '—' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="用量" min-width="120" align="center" header-align="center">
|
||||
<template #default="{ row }">
|
||||
<el-input-number
|
||||
v-model="row.qty"
|
||||
:min="0.0001"
|
||||
:precision="4"
|
||||
:step="1"
|
||||
controls-position="right"
|
||||
placeholder="用量"
|
||||
class="inner-cell-input"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="单位" min-width="72" align="center" header-align="center">
|
||||
<template #default="{ row }">{{ row.unit || '—' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="备注" min-width="140" align="center" header-align="center">
|
||||
<template #default="{ row }">
|
||||
<el-input
|
||||
v-model="row.remark"
|
||||
placeholder="选填"
|
||||
clearable
|
||||
class="inner-cell-input"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
label="操作"
|
||||
min-width="96"
|
||||
align="center"
|
||||
header-align="center"
|
||||
fixed="right"
|
||||
>
|
||||
<template #default="{ $index }">
|
||||
<el-button link type="danger" size="small" @click="removeMaterialRow($index)">
|
||||
删除
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<el-empty v-else description="暂无物料记录,可点击上方按钮添加" :image-size="56" />
|
||||
</div>
|
||||
<div class="form-section result-conclusion-section">
|
||||
<div class="section-title">检测结论</div>
|
||||
<el-form-item label="结论" required>
|
||||
<el-radio-group v-model="resultConclusion">
|
||||
<el-radio value="tg">通过</el-radio>
|
||||
<el-radio value="btg">不通过</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="备注">
|
||||
<el-input v-model="resultRemark" type="textarea" :rows="3" placeholder="选填" />
|
||||
</el-form-item>
|
||||
<div class="result-attachment-block">
|
||||
<ProtocolFinanceConfirmFields
|
||||
v-model:finance-remark="resultRemark"
|
||||
v-model:attachments="resultAttachments"
|
||||
:show-remark="false"
|
||||
:attachment-label="resultAttachmentLabel"
|
||||
:upload-folder="resultUploadFolder"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</el-form>
|
||||
</DialogFormBody>
|
||||
<template #footer>
|
||||
<el-button class="btn-ghost" @click="resultVisible = false"><el-icon><Close /></el-icon>取消</el-button>
|
||||
<el-button class="btn-main" type="primary" :loading="resultLoading" @click="submitResult"><el-icon><Check /></el-icon>{{ resultSubmitLabel }}</el-button>
|
||||
<el-button class="btn-ghost" @click="resultVisible = false"
|
||||
><el-icon><Close /></el-icon>取消</el-button
|
||||
>
|
||||
<el-button class="btn-main" type="primary" :loading="resultLoading" @click="submitResult"
|
||||
><el-icon><Check /></el-icon>{{ resultSubmitLabel }}</el-button
|
||||
>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
@@ -332,35 +413,40 @@
|
||||
<template #material-usages>
|
||||
<div v-if="viewMaterialUsages.length" class="view-subtable-wrap">
|
||||
<el-table :data="viewMaterialUsages" stripe class="list-table view-subtable">
|
||||
<el-table-column
|
||||
type="index"
|
||||
label="序号"
|
||||
min-width="56"
|
||||
align="center"
|
||||
header-align="center"
|
||||
:index="(index) => index + 1"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="materialName"
|
||||
label="物料名称"
|
||||
min-width="160"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column label="规格型号" min-width="120" align="center" header-align="center">
|
||||
<template #default="{ row }">{{ row.specModel || '—' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="用量" min-width="100" align="center" header-align="center">
|
||||
<template #default="{ row }">
|
||||
{{ row.qty != null && row.qty !== '' ? row.qty : '—' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="单位" min-width="72" align="center" header-align="center">
|
||||
<template #default="{ row }">{{ row.unit || '—' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="备注" min-width="140" align="center" header-align="center">
|
||||
<template #default="{ row }">{{ row.remark || '—' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
type="index"
|
||||
label="序号"
|
||||
min-width="56"
|
||||
align="center"
|
||||
header-align="center"
|
||||
:index="(index) => index + 1"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="materialName"
|
||||
label="物料名称"
|
||||
min-width="160"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
label="规格型号"
|
||||
min-width="120"
|
||||
align="center"
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }">{{ row.specModel || '—' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="用量" min-width="100" align="center" header-align="center">
|
||||
<template #default="{ row }">
|
||||
{{ row.qty != null && row.qty !== '' ? row.qty : '—' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="单位" min-width="72" align="center" header-align="center">
|
||||
<template #default="{ row }">{{ row.unit || '—' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="备注" min-width="140" align="center" header-align="center">
|
||||
<template #default="{ row }">{{ row.remark || '—' }}</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
<p v-else class="view-empty-hint">暂无物料使用记录</p>
|
||||
@@ -373,7 +459,6 @@
|
||||
/>
|
||||
</template>
|
||||
</SystemViewDialog>
|
||||
|
||||
</section>
|
||||
</div>
|
||||
</template>
|
||||
@@ -381,7 +466,17 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, computed, onMounted } from 'vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { Close, Check, Search, RefreshLeft, VideoPlay, View, Tickets, EditPen, Plus } from '@element-plus/icons-vue'
|
||||
import {
|
||||
Close,
|
||||
Check,
|
||||
Search,
|
||||
RefreshLeft,
|
||||
VideoPlay,
|
||||
View,
|
||||
Tickets,
|
||||
EditPen,
|
||||
Plus
|
||||
} from '@element-plus/icons-vue'
|
||||
import { formatMoneyDisplay } from '@/utils/money-format'
|
||||
import ProtocolFinanceConfirmFields from '@/components/business/protocol-finance-confirm-fields/index.vue'
|
||||
import ProtocolAttachmentReadonly from '@/views/cell-storage/protocol/ProtocolAttachmentReadonly.vue'
|
||||
@@ -424,16 +519,20 @@
|
||||
protocolStatusLabel,
|
||||
protocolStatusTagType,
|
||||
donationStatusLabel,
|
||||
donationStatusTagType,
|
||||
donationStatusTagType
|
||||
} from '@/utils/status-tag-registry'
|
||||
import type { TagType } from '@/utils/status-tag-registry'
|
||||
|
||||
/** 根据来源类型返回协议状态标签 */
|
||||
function protocolStatusLabelBySource(status: string, source: string): string {
|
||||
return source === 'research_donation' ? donationStatusLabel(status) : protocolStatusLabel(status)
|
||||
return source === 'research_donation'
|
||||
? donationStatusLabel(status)
|
||||
: protocolStatusLabel(status)
|
||||
}
|
||||
function protocolStatusTagTypeBySource(status: string, source: string): TagType {
|
||||
return source === 'research_donation' ? donationStatusTagType(status) : protocolStatusTagType(status)
|
||||
return source === 'research_donation'
|
||||
? donationStatusTagType(status)
|
||||
: protocolStatusTagType(status)
|
||||
}
|
||||
|
||||
/** 临时来源任务无关联样本,样本来源展示为 — */
|
||||
@@ -536,9 +635,7 @@
|
||||
const materialOptions = ref<QcMaterialOption[]>([])
|
||||
const materialUsages = ref<MaterialUsageRow[]>([])
|
||||
|
||||
const resultDialogWidth = computed(() =>
|
||||
resultTaskType.value === 'sysz' ? '970px' : '720px'
|
||||
)
|
||||
const resultDialogWidth = computed(() => (resultTaskType.value === 'sysz' ? '970px' : '720px'))
|
||||
|
||||
function canFillResult(row: QcTask) {
|
||||
if (row.taskType === 'sysz') {
|
||||
@@ -814,7 +911,10 @@
|
||||
{ label: '样本编码', value: r.sampleCode || d.sampleCode || '—' },
|
||||
{
|
||||
label: '样本来源',
|
||||
value: r.sourceTypeLabel || d.receptionSourceLabel || receptionSourceLabel(d.receptionSource || ''),
|
||||
value:
|
||||
r.sourceTypeLabel ||
|
||||
d.receptionSourceLabel ||
|
||||
receptionSourceLabel(d.receptionSource || ''),
|
||||
tagType: 'primary' as const
|
||||
},
|
||||
{ label: '样本类型', value: r.sampleTypeName || d.sampleTypeName || '—' }
|
||||
@@ -838,8 +938,7 @@
|
||||
{
|
||||
label: '结论',
|
||||
value:
|
||||
r.acceptConclusionLabel ||
|
||||
acceptConclusionLabel(String(r.acceptConclusion || ''))
|
||||
r.acceptConclusionLabel || acceptConclusionLabel(String(r.acceptConclusion || ''))
|
||||
},
|
||||
{ label: '检查项', value: formatCheckResults(r.checkResults), span: true },
|
||||
{ label: '备注', value: r.remark || '—', span: true }
|
||||
@@ -874,19 +973,40 @@
|
||||
title: '任务信息',
|
||||
fields: [
|
||||
{ label: '任务号', value: d.taskNo },
|
||||
{ label: '业务类型', value: d.qcBizTypeLabel || qcBizTypeLabel(d.qcBizType || ''), tagType: qcBizTypeTagType(d.qcBizType || '') },
|
||||
{
|
||||
label: '业务类型',
|
||||
value: d.qcBizTypeLabel || qcBizTypeLabel(d.qcBizType || ''),
|
||||
tagType: qcBizTypeTagType(d.qcBizType || '')
|
||||
},
|
||||
{ label: '工单号', value: d.workOrderNo },
|
||||
{
|
||||
label: '工单状态',
|
||||
value: d.workOrderStatus ? workOrderStatusLabel(d.workOrderStatus) : '—',
|
||||
tagType: d.workOrderStatus === 'ywc' ? 'success' : d.workOrderStatus === 'ypz' ? 'warning' : 'info'
|
||||
tagType:
|
||||
d.workOrderStatus === 'ywc'
|
||||
? 'success'
|
||||
: d.workOrderStatus === 'ypz'
|
||||
? 'warning'
|
||||
: 'info'
|
||||
},
|
||||
{
|
||||
label: '检测类型',
|
||||
value: taskTypeLabel(d.taskType),
|
||||
tagType: d.taskType === 'sysz' ? 'success' : 'info'
|
||||
},
|
||||
{ label: '检测类型', value: taskTypeLabel(d.taskType), tagType: d.taskType === 'sysz' ? 'success' : 'info' },
|
||||
{ label: '检测项目', value: d.itemName },
|
||||
{ label: '项目编码', value: d.itemCode || '—' },
|
||||
{ label: '第三方公司', value: d.companyName || '—' },
|
||||
{ label: '检测费用', value: d.feeAmount != null ? '¥' + formatMoneyDisplay(d.feeAmount) : '—' },
|
||||
{ label: '任务状态', value: taskStatusLabel(d.taskStatus), tagType: d.taskStatus === 'ywc' ? 'success' : d.taskStatus === 'jxz' ? 'warning' : 'info' },
|
||||
{
|
||||
label: '检测费用',
|
||||
value: d.feeAmount != null ? '¥' + formatMoneyDisplay(d.feeAmount) : '—'
|
||||
},
|
||||
{
|
||||
label: '任务状态',
|
||||
value: taskStatusLabel(d.taskStatus),
|
||||
tagType:
|
||||
d.taskStatus === 'ywc' ? 'success' : d.taskStatus === 'jxz' ? 'warning' : 'info'
|
||||
},
|
||||
{
|
||||
label: '检测结论',
|
||||
value: d.conclusionLabel || qcTaskConclusionLabel(d.conclusion || ''),
|
||||
@@ -1027,7 +1147,10 @@
|
||||
ElMessage.warning('请选择检测结论')
|
||||
return
|
||||
}
|
||||
if (resultTaskType.value === 'dsfjc' && !financeAttachmentsToUrlString(resultAttachments.value)) {
|
||||
if (
|
||||
resultTaskType.value === 'dsfjc' &&
|
||||
!financeAttachmentsToUrlString(resultAttachments.value)
|
||||
) {
|
||||
ElMessage.warning('请上传第三方检测材料')
|
||||
return
|
||||
}
|
||||
@@ -1065,7 +1188,8 @@
|
||||
.filter((p) => hasResultValue(p.paramValue))
|
||||
.map((p) => ({
|
||||
...p,
|
||||
paramValue: p.paramValue === undefined || p.paramValue === null ? '' : String(p.paramValue)
|
||||
paramValue:
|
||||
p.paramValue === undefined || p.paramValue === null ? '' : String(p.paramValue)
|
||||
}))
|
||||
}
|
||||
data.thirdPartyAttachments = financeAttachmentsToUrlString(resultAttachments.value)
|
||||
|
||||
@@ -0,0 +1,441 @@
|
||||
<!-- 出库质检工单 — 查看详情弹窗 -->
|
||||
<!-- eslint-disable vue/no-v-model-argument -->
|
||||
<template>
|
||||
<SystemViewDialog
|
||||
v-model:visible="dialogVisible"
|
||||
title="出库质检工单详情"
|
||||
width="1200px"
|
||||
:grid-columns="4"
|
||||
compact
|
||||
:loading="loading"
|
||||
:icon="Document"
|
||||
:meta="detail?.workOrder?.workOrderNo || ''"
|
||||
:sections="sections"
|
||||
>
|
||||
<template #diagnosis-attachments>
|
||||
<ProtocolAttachmentReadonly
|
||||
:files="diagnosisAttachmentFiles"
|
||||
@preview="handlePreview"
|
||||
@download="handleDownload"
|
||||
/>
|
||||
</template>
|
||||
<template v-for="task in tasksWithAttachments" #[taskAttachmentSlotName(task.id)]>
|
||||
<div :key="task.id" class="outbound-qc-task-attachments">
|
||||
<ProtocolAttachmentReadonly
|
||||
:files="task.attachmentFiles || []"
|
||||
@preview="handleTaskPreview"
|
||||
@download="handleTaskDownload"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</SystemViewDialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, watch } from 'vue'
|
||||
import { Document } from '@element-plus/icons-vue'
|
||||
import {
|
||||
fetchOutboundQcDetail,
|
||||
type OutboundQcDetail,
|
||||
type QcWorkOrderDetail
|
||||
} from '@/api/qc-work-order'
|
||||
import { fetchQcTaskResults, type QcTaskResultItem } from '@/api/qc-task-result'
|
||||
import {
|
||||
workOrderStatusLabel,
|
||||
taskStatusLabel,
|
||||
qcTaskConclusionLabel
|
||||
} from '@/utils/qc-labels'
|
||||
import {
|
||||
scheduleStatusLabel,
|
||||
scheduleStatusTagType,
|
||||
infusionStatusLabel,
|
||||
infusionStatusTagType,
|
||||
qcStatusLabel,
|
||||
qcStatusTagType,
|
||||
applicationStatusLabel,
|
||||
applicationStatusTagType,
|
||||
bizTypeLabel,
|
||||
bizTypeTagType,
|
||||
productStatusLabel,
|
||||
productStatusTagType
|
||||
} from '@/utils/status-tag-registry'
|
||||
import SystemViewDialog from '@/components/business/system-view-dialog/index.vue'
|
||||
import type { ViewField, ViewSection } from '@/components/business/system-view-dialog/index.vue'
|
||||
import ProtocolAttachmentReadonly from '@/views/cell-storage/protocol/ProtocolAttachmentReadonly.vue'
|
||||
import { downloadAttachmentFile } from '@/utils/download-attachment'
|
||||
import { formatMoneyDisplay } from '@/utils/money-format'
|
||||
|
||||
defineOptions({ name: 'OutboundQcViewDialog' })
|
||||
|
||||
const props = defineProps<{
|
||||
visible: boolean
|
||||
workOrderId: string
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'update:visible', v: boolean): void
|
||||
}>()
|
||||
|
||||
const dialogVisible = computed({
|
||||
get: () => props.visible,
|
||||
set: (v) => emit('update:visible', v)
|
||||
})
|
||||
|
||||
const loading = ref(false)
|
||||
const detail = ref<OutboundQcDetail | null>(null)
|
||||
|
||||
type TaskView = {
|
||||
id: string
|
||||
taskNo: string
|
||||
taskType: string
|
||||
itemCode?: string
|
||||
itemName?: string
|
||||
taskStatus: string
|
||||
conclusion?: string
|
||||
conclusionLabel?: string
|
||||
remark?: string
|
||||
completedTime?: string
|
||||
attachmentFiles?: Array<{ id?: string; fileUrl?: string; fileName?: string }>
|
||||
results?: QcTaskResultItem[]
|
||||
companyName?: string
|
||||
feeAmount?: number
|
||||
}
|
||||
|
||||
const tasksWithResults = ref<TaskView[]>([])
|
||||
|
||||
const minioBaseUrl = (import.meta.env.VITE_MINIO_BASE_URL || '').replace(/\/$/, '')
|
||||
|
||||
watch(
|
||||
() => props.visible,
|
||||
async (v) => {
|
||||
if (!v || !props.workOrderId) return
|
||||
loading.value = true
|
||||
tasksWithResults.value = []
|
||||
try {
|
||||
const d = await fetchOutboundQcDetail(props.workOrderId)
|
||||
detail.value = d
|
||||
const rawTasks = (d.workOrder?.tasks || d.tasks || []) as TaskView[]
|
||||
const enriched = await Promise.all(
|
||||
rawTasks.map(async (task) => {
|
||||
if (task.taskType !== 'sysz' || task.taskStatus !== 'ywc') {
|
||||
return { ...task, results: [] as QcTaskResultItem[] }
|
||||
}
|
||||
try {
|
||||
const results = await fetchQcTaskResults(task.id)
|
||||
return { ...task, results: results || [] }
|
||||
} catch {
|
||||
return { ...task, results: [] as QcTaskResultItem[] }
|
||||
}
|
||||
})
|
||||
)
|
||||
tasksWithResults.value = enriched
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
// ── URL helpers ──────────────────────────────────────────────────────────────
|
||||
|
||||
function fullUrl(url: string) {
|
||||
const u = String(url || '').trim()
|
||||
if (!u) return ''
|
||||
if (/^https?:\/\//i.test(u)) return u
|
||||
return minioBaseUrl ? `${minioBaseUrl}/${u.replace(/^\//, '')}` : u
|
||||
}
|
||||
|
||||
// ── Diagnosis attachments ────────────────────────────────────────────────────
|
||||
|
||||
const diagnosisAttachmentFiles = computed(() => {
|
||||
const urls = detail.value?.patient?.diagnosisAttachments || ''
|
||||
if (!urls.trim()) return []
|
||||
return urls
|
||||
.split(',')
|
||||
.map((u, i) => {
|
||||
const url = u.trim()
|
||||
if (!url) return null
|
||||
const name = url.includes('/') ? url.split('/').pop() || `附件${i + 1}` : url
|
||||
return { id: `url_${i}`, fileUrl: fullUrl(url), fileName: name }
|
||||
})
|
||||
.filter(Boolean) as Array<Record<string, unknown>>
|
||||
})
|
||||
|
||||
function handlePreview(file: Record<string, unknown>) {
|
||||
const url = fullUrl(String(file.fileUrl || ''))
|
||||
if (url) window.open(url, '_blank')
|
||||
}
|
||||
|
||||
function handleDownload(file: Record<string, unknown>) {
|
||||
downloadAttachmentFile(file, { fullFileUrl: fullUrl })
|
||||
}
|
||||
|
||||
// ── Task attachment helpers ─────────────────────────────────────────────────
|
||||
|
||||
const tasksWithAttachments = computed(() =>
|
||||
tasksWithResults.value.filter((t) => (t.attachmentFiles?.length || 0) > 0)
|
||||
)
|
||||
|
||||
function taskAttachmentSlotName(taskId: string) {
|
||||
return `task-att-${taskId}`
|
||||
}
|
||||
|
||||
function handleTaskPreview(file: Record<string, unknown>) {
|
||||
const url = fullUrl(String(file.fileUrl || ''))
|
||||
if (url) window.open(url, '_blank')
|
||||
}
|
||||
|
||||
function handleTaskDownload(file: Record<string, unknown>) {
|
||||
downloadAttachmentFile(file, { fullFileUrl: fullUrl })
|
||||
}
|
||||
|
||||
// ── Tag helpers ─────────────────────────────────────────────────────────────
|
||||
|
||||
function conclusionTagType(conclusion?: string) {
|
||||
if (conclusion === 'tg') return 'success' as const
|
||||
if (conclusion === 'btg') return 'danger' as const
|
||||
return 'info' as const
|
||||
}
|
||||
|
||||
function taskStatusTagType(status?: string) {
|
||||
if (status === 'ywc') return 'success' as const
|
||||
if (status === 'jxz') return 'warning' as const
|
||||
return 'info' as const
|
||||
}
|
||||
|
||||
function workOrderStatusTagType(status?: string) {
|
||||
if (status === 'ywc') return 'success' as const
|
||||
if (status === 'ypz') return 'warning' as const
|
||||
if (status === 'yzf') return 'danger' as const
|
||||
return 'info' as const
|
||||
}
|
||||
|
||||
function formatDetectMethods(wo?: Partial<QcWorkOrderDetail>) {
|
||||
const parts: string[] = []
|
||||
if (wo?.hasLab) parts.push('实验室')
|
||||
if (wo?.hasThirdParty) parts.push('第三方')
|
||||
return parts.length ? parts.join(' \\ ') : '—'
|
||||
}
|
||||
|
||||
// ── Build task type sections (same pattern as index.vue) ────────────────────
|
||||
|
||||
function buildTaskTypeSections(
|
||||
tasks: TaskView[],
|
||||
taskType: 'sysz' | 'dsfjc',
|
||||
sectionTitle: string
|
||||
): ViewSection[] {
|
||||
const filtered = tasks.filter((t) => t.taskType === taskType)
|
||||
if (!filtered.length) return []
|
||||
|
||||
return filtered.map((task, index) => {
|
||||
const title = filtered.length > 1 ? `${sectionTitle}(${index + 1})` : sectionTitle
|
||||
const fields: ViewField[] = [
|
||||
{ label: '检测项目', value: task.itemName || '—' },
|
||||
{ label: '项目编码', value: task.itemCode || '—' },
|
||||
{
|
||||
label: '任务状态',
|
||||
value: taskStatusLabel(task.taskStatus || ''),
|
||||
tagType: taskStatusTagType(task.taskStatus)
|
||||
},
|
||||
{
|
||||
label: '检测结论',
|
||||
value: task.conclusionLabel || qcTaskConclusionLabel(task.conclusion || '') || '—',
|
||||
tagType: conclusionTagType(task.conclusion)
|
||||
},
|
||||
{ label: '任务号', value: task.taskNo || '—' },
|
||||
{ label: '完成时间', value: task.completedTime || '—' }
|
||||
]
|
||||
if (taskType === 'dsfjc') {
|
||||
fields.splice(2, 0, { label: '第三方公司', value: task.companyName || '—' })
|
||||
if (task.feeAmount != null) {
|
||||
fields.push({ label: '费用(元)', value: String(task.feeAmount) })
|
||||
}
|
||||
}
|
||||
if (task.results?.length) {
|
||||
for (const item of task.results) {
|
||||
fields.push({
|
||||
label: item.paramName + (item.paramUnit ? `(${item.paramUnit})` : ''),
|
||||
value: item.paramValue || '—'
|
||||
})
|
||||
}
|
||||
} else if (taskType === 'sysz') {
|
||||
fields.push({ label: '检测参数', value: '—' })
|
||||
}
|
||||
fields.push({ label: '备注', value: task.remark || '—', span: true })
|
||||
|
||||
const section: ViewSection = { title, fields }
|
||||
if (task.attachmentFiles?.length) {
|
||||
section.slot = taskAttachmentSlotName(task.id)
|
||||
section.slotTitle = taskType === 'dsfjc' ? '检测材料' : '检测附件'
|
||||
section.slotTitleStyle = 'field'
|
||||
} else if (taskType === 'dsfjc') {
|
||||
fields.push({ label: '检测材料', value: '—' })
|
||||
} else {
|
||||
fields.push({ label: '检测附件', value: '—' })
|
||||
}
|
||||
return section
|
||||
})
|
||||
}
|
||||
|
||||
// ── Sections computed ────────────────────────────────────────────────────────
|
||||
|
||||
const sections = computed<ViewSection[]>(() => {
|
||||
if (!detail.value) return []
|
||||
const d = detail.value
|
||||
const wo: Partial<QcWorkOrderDetail> = d.workOrder || {}
|
||||
const app = d.application
|
||||
const patient = d.patient
|
||||
const plan = d.infusionPlan
|
||||
const schedule = d.infusionSchedule
|
||||
const products = d.products || []
|
||||
|
||||
const result: ViewSection[] = []
|
||||
|
||||
// 1. 工单基本信息
|
||||
result.push({
|
||||
title: '工单基本信息',
|
||||
fields: [
|
||||
{ label: '工单号', value: wo.workOrderNo || '—' },
|
||||
{ label: '工单类型', value: '出库', tagType: 'success' },
|
||||
{
|
||||
label: '业务类型',
|
||||
value: wo.bizTypeLabel || bizTypeLabel(wo.bizType || ''),
|
||||
tagType: bizTypeTagType(wo.bizType || '') as ViewField['tagType']
|
||||
},
|
||||
{
|
||||
label: '状态',
|
||||
value: workOrderStatusLabel(wo.workOrderStatus || ''),
|
||||
tagType: workOrderStatusTagType(wo.workOrderStatus)
|
||||
},
|
||||
{ label: '检测方式', value: formatDetectMethods(wo) },
|
||||
{ label: '配置时间', value: wo.configuredTime || '—' },
|
||||
{ label: '配置人', value: wo.configuredBy || '—' },
|
||||
{ label: '备注', value: wo.remark || '—', span: true },
|
||||
{ label: '创建时间', value: wo.createTime || '—' }
|
||||
]
|
||||
})
|
||||
|
||||
// 2. 中试服务申请单信息
|
||||
if (app) {
|
||||
result.push({
|
||||
title: '中试服务申请单信息',
|
||||
fields: [
|
||||
{ label: '申请单号', value: app.applicationNo || '—' },
|
||||
{ label: '申请单名称', value: app.applicationName || '—' },
|
||||
{
|
||||
label: '申请单状态',
|
||||
value: applicationStatusLabel(app.applicationStatus || ''),
|
||||
tagType: applicationStatusTagType(app.applicationStatus || '') as ViewField['tagType']
|
||||
},
|
||||
{ label: '渠道名称', value: app.channelName || '—' }
|
||||
]
|
||||
})
|
||||
}
|
||||
|
||||
// 3. 患者信息
|
||||
if (patient) {
|
||||
const patientFields: ViewField[] = [
|
||||
{ label: '患者姓名', value: patient.patientName || '—' },
|
||||
{ label: '性别', value: patient.sex || '—' },
|
||||
{ label: '年龄', value: patient.age || '—' },
|
||||
{ label: '联系电话', value: patient.phoneNo || '—' },
|
||||
{ label: '身份证号', value: patient.idCardNo || '—' },
|
||||
{ label: '紧急联系人', value: patient.emergencyContact || '—' },
|
||||
{ label: '紧急联系电话', value: patient.emergencyPhone || '—' },
|
||||
{ label: '临床诊断', value: patient.clinicalDiagnosis || '—', span: true }
|
||||
]
|
||||
result.push({
|
||||
title: '患者信息',
|
||||
fields: patientFields,
|
||||
slotTitle: '诊断附件',
|
||||
slotTitleStyle: 'field',
|
||||
slot: 'diagnosis-attachments'
|
||||
})
|
||||
}
|
||||
|
||||
// 4. 输注方案信息
|
||||
if (plan) {
|
||||
result.push({
|
||||
title: '输注方案信息',
|
||||
fields: [
|
||||
{ label: '细胞类型', value: plan.cellType || '—' },
|
||||
{ label: '规格', value: plan.specText || '—' },
|
||||
{ label: '总疗程数', value: plan.totalCourseCount != null ? String(plan.totalCourseCount) : '—' },
|
||||
{ label: '疗程间隔', value: plan.courseInterval || '—' },
|
||||
{ label: '输注途径', value: plan.infusionRoute || '—' },
|
||||
{ label: '输注开始日期', value: plan.infusionStartDate || '—' }
|
||||
]
|
||||
})
|
||||
}
|
||||
|
||||
// 5. 本次输注计划信息
|
||||
if (schedule) {
|
||||
const scheduleFields: ViewField[] = [
|
||||
{ label: '输注时间', value: schedule.infusionTime || '—' },
|
||||
{ label: '输注地点', value: schedule.infusionLocation || '—' },
|
||||
{ label: '输注方式', value: schedule.infusionMethod || '—' },
|
||||
{
|
||||
label: '计划状态',
|
||||
value: scheduleStatusLabel(schedule.scheduleStatus || ''),
|
||||
tagType: scheduleStatusTagType(schedule.scheduleStatus || '') as ViewField['tagType']
|
||||
},
|
||||
{
|
||||
label: '输注状态',
|
||||
value: infusionStatusLabel(schedule.infusionStatus || ''),
|
||||
tagType: infusionStatusTagType(schedule.infusionStatus || '') as ViewField['tagType']
|
||||
},
|
||||
{
|
||||
label: '质检状态',
|
||||
value: qcStatusLabel(schedule.qcStatus || ''),
|
||||
tagType: qcStatusTagType(schedule.qcStatus || '') as ViewField['tagType']
|
||||
}
|
||||
]
|
||||
if (schedule.cancelReason) {
|
||||
scheduleFields.push({ label: '取消原因', value: schedule.cancelReason, span: true })
|
||||
}
|
||||
result.push({ title: '本次输注计划信息', fields: scheduleFields })
|
||||
}
|
||||
|
||||
// 6. 产品明细
|
||||
if (products.length) {
|
||||
const productItems: string[] = products.map(
|
||||
(p) =>
|
||||
`${p.projectCode || '—'} / ${p.projectName || '—'} / ${p.cellTypeName || '—'} / ${p.specText || '—'} / 协议价:¥${p.agreementPrice != null ? formatMoneyDisplay(p.agreementPrice) : '—'} / 实际价:¥${p.actualPrice != null ? formatMoneyDisplay(p.actualPrice) : '—'} / 数量:${p.quantity ?? '—'} / ${productStatusLabel(p.productStatus || '')}`
|
||||
)
|
||||
result.push({
|
||||
title: '产品明细',
|
||||
fields: [{ label: '产品列表', value: '', span: true }],
|
||||
slot: 'product-table'
|
||||
})
|
||||
// Override: we'll use items instead of a separate slot — use span field with items list
|
||||
result[result.length - 1] = {
|
||||
title: '产品明细',
|
||||
fields: [
|
||||
{
|
||||
label: '产品列表',
|
||||
span: true,
|
||||
items: products.map(
|
||||
(p) =>
|
||||
`${p.projectCode || '—'} ${p.projectName || '—'} | ${p.cellTypeName || '—'} | ${p.specText || '—'} | 协议价 ¥${p.agreementPrice != null ? formatMoneyDisplay(p.agreementPrice) : '—'} | 实际价 ¥${p.actualPrice != null ? formatMoneyDisplay(p.actualPrice) : '—'} | 数量 ${p.quantity ?? '—'}`
|
||||
)
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
// 7. 检测结果 — lab tasks
|
||||
const labSections = buildTaskTypeSections(tasksWithResults.value, 'sysz', '实验室项目')
|
||||
result.push(...labSections)
|
||||
|
||||
// 8. 检测结果 — third-party tasks
|
||||
const thirdSections = buildTaskTypeSections(tasksWithResults.value, 'dsfjc', '第三方项目')
|
||||
result.push(...thirdSections)
|
||||
|
||||
return result
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.outbound-qc-task-attachments {
|
||||
margin-top: 0;
|
||||
}
|
||||
</style>
|
||||
@@ -19,7 +19,12 @@
|
||||
<el-input v-model="searchForm.keyword" placeholder="编码/名称" clearable />
|
||||
</el-form-item>
|
||||
<el-form-item label="状态">
|
||||
<el-select v-model="searchForm.status" placeholder="全部" clearable style="width: 100%">
|
||||
<el-select
|
||||
v-model="searchForm.status"
|
||||
placeholder="全部"
|
||||
clearable
|
||||
style="width: 100%"
|
||||
>
|
||||
<el-option label="启用" value="1" />
|
||||
<el-option label="停用" value="0" />
|
||||
</el-select>
|
||||
@@ -76,16 +81,54 @@
|
||||
header-align="center"
|
||||
:index="(index) => (pagination.page - 1) * pagination.pageSize + index + 1"
|
||||
/>
|
||||
<el-table-column prop="itemCode" label="检查项编码" min-width="140" align="center" header-align="center" />
|
||||
<el-table-column prop="itemName" label="检查项名称" min-width="160" align="center" header-align="center" />
|
||||
<el-table-column prop="orderNum" label="排序" min-width="80" align="center" header-align="center" />
|
||||
<el-table-column prop="status" label="状态" min-width="90" align="center" header-align="center">
|
||||
<el-table-column
|
||||
prop="itemCode"
|
||||
label="检查项编码"
|
||||
min-width="140"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="itemName"
|
||||
label="检查项名称"
|
||||
min-width="160"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="orderNum"
|
||||
label="排序"
|
||||
min-width="80"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="status"
|
||||
label="状态"
|
||||
min-width="90"
|
||||
align="center"
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="row.status === '1' ? 'success' : 'info'" size="small">{{ row.status === '1' ? '启用' : '停用' }}</el-tag>
|
||||
<el-tag :type="row.status === '1' ? 'success' : 'info'" size="small">{{
|
||||
row.status === '1' ? '启用' : '停用'
|
||||
}}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="createTime" label="创建时间" min-width="170" align="center" header-align="center" />
|
||||
<el-table-column label="操作" min-width="260" fixed="right" align="center" header-align="center">
|
||||
<el-table-column
|
||||
prop="createTime"
|
||||
label="创建时间"
|
||||
min-width="170"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
label="操作"
|
||||
min-width="260"
|
||||
fixed="right"
|
||||
align="center"
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<el-button class="btn-op-item primary" @click="handleEdit(row)">编辑</el-button>
|
||||
<el-button class="btn-op-item" @click="handleDelete(row)">删除</el-button>
|
||||
@@ -125,13 +168,22 @@
|
||||
>
|
||||
<div class="form-grid-2">
|
||||
<el-form-item label="检查项编码" prop="itemCode">
|
||||
<el-input v-model="formData.itemCode" placeholder="租户内唯一,如 packaging" :disabled="isEdit" />
|
||||
<el-input
|
||||
v-model="formData.itemCode"
|
||||
placeholder="租户内唯一,如 packaging"
|
||||
:disabled="isEdit"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="检查项名称" prop="itemName">
|
||||
<el-input v-model="formData.itemName" placeholder="如:包装" />
|
||||
</el-form-item>
|
||||
<el-form-item label="排序">
|
||||
<el-input-number v-model="formData.orderNum" :min="0" :max="9999" style="width: 100%" />
|
||||
<el-input-number
|
||||
v-model="formData.orderNum"
|
||||
:min="0"
|
||||
:max="9999"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="状态" prop="status">
|
||||
<el-radio-group v-model="formData.status">
|
||||
|
||||
@@ -131,50 +131,144 @@
|
||||
header-align="center"
|
||||
:index="(index) => (pagination.page - 1) * pagination.pageSize + index + 1"
|
||||
/>
|
||||
<el-table-column prop="protocolNo" label="协议号" min-width="130" align="center" header-align="center" />
|
||||
<el-table-column prop="protocolStatus" label="协议状态" min-width="100" align="center" header-align="center">
|
||||
<el-table-column
|
||||
prop="protocolNo"
|
||||
label="协议号"
|
||||
min-width="130"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="protocolStatus"
|
||||
label="协议状态"
|
||||
min-width="100"
|
||||
align="center"
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<el-tag
|
||||
:type="protocolStatusTagTypeBySource(String(row.protocolStatus || ''), row.sourceType || 'cell_storage')"
|
||||
:type="
|
||||
protocolStatusTagTypeBySource(
|
||||
String(row.protocolStatus || ''),
|
||||
row.sourceType || 'cell_storage'
|
||||
)
|
||||
"
|
||||
size="small"
|
||||
>{{ protocolStatusLabelBySource(String(row.protocolStatus || ''), row.sourceType || 'cell_storage') }}</el-tag>
|
||||
>{{
|
||||
protocolStatusLabelBySource(
|
||||
String(row.protocolStatus || ''),
|
||||
row.sourceType || 'cell_storage'
|
||||
)
|
||||
}}</el-tag
|
||||
>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="sourceTypeLabel" label="来源类型" min-width="100" align="center" header-align="center">
|
||||
<el-table-column
|
||||
prop="sourceTypeLabel"
|
||||
label="来源类型"
|
||||
min-width="100"
|
||||
align="center"
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="row.sourceType === 'research_donation' ? 'warning' : 'success'" size="small">
|
||||
<el-tag
|
||||
:type="row.sourceType === 'research_donation' ? 'warning' : 'success'"
|
||||
size="small"
|
||||
>
|
||||
{{ row.sourceTypeLabel || '细胞存储' }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="sampleCode" label="样本编码" min-width="120" align="center" header-align="center">
|
||||
<el-table-column
|
||||
prop="sampleCode"
|
||||
label="样本编码"
|
||||
min-width="120"
|
||||
align="center"
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }">{{ row.sampleCode || '-' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="sampleTypeName" label="样本类型" min-width="110" align="center" header-align="center">
|
||||
<el-table-column
|
||||
prop="sampleTypeName"
|
||||
label="样本类型"
|
||||
min-width="110"
|
||||
align="center"
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }">{{ row.sampleTypeName || '-' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="motherName" label="母亲姓名" min-width="100" align="center" header-align="center">
|
||||
<el-table-column
|
||||
prop="motherName"
|
||||
label="母亲姓名"
|
||||
min-width="100"
|
||||
align="center"
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }">{{ row.motherName || '-' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="newbornName" label="新生儿姓名" min-width="110" align="center" header-align="center">
|
||||
<el-table-column
|
||||
prop="newbornName"
|
||||
label="新生儿姓名"
|
||||
min-width="110"
|
||||
align="center"
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }">{{ row.newbornName || '-' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="etaTime" label="预计到达时间" min-width="170" align="center" header-align="center">
|
||||
<el-table-column
|
||||
prop="etaTime"
|
||||
label="预计到达时间"
|
||||
min-width="170"
|
||||
align="center"
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }">{{ row.etaTime || '-' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="transportType" label="运输方式" min-width="100" align="center" header-align="center">
|
||||
<template #default="{ row }">{{ transportTypeLabel(String(row.transportType || '')) }}</template>
|
||||
<el-table-column
|
||||
prop="transportType"
|
||||
label="运输方式"
|
||||
min-width="100"
|
||||
align="center"
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }">{{
|
||||
transportTypeLabel(String(row.transportType || ''))
|
||||
}}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="waybillNo" label="快递单号" min-width="140" align="center" header-align="center">
|
||||
<el-table-column
|
||||
prop="waybillNo"
|
||||
label="快递单号"
|
||||
min-width="140"
|
||||
align="center"
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }">{{ row.waybillNo || '-' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="expressCompanyName" label="快递公司" min-width="120" align="center" header-align="center">
|
||||
<el-table-column
|
||||
prop="expressCompanyName"
|
||||
label="快递公司"
|
||||
min-width="120"
|
||||
align="center"
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }">{{ row.expressCompanyName || '-' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="carrierName" label="携带人" min-width="100" align="center" header-align="center">
|
||||
<el-table-column
|
||||
prop="carrierName"
|
||||
label="携带人"
|
||||
min-width="100"
|
||||
align="center"
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }">{{ row.carrierName || '-' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="carrierPhone" label="携带人联系电话" min-width="130" align="center" header-align="center">
|
||||
<el-table-column
|
||||
prop="carrierPhone"
|
||||
label="携带人联系电话"
|
||||
min-width="130"
|
||||
align="center"
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }">{{ row.carrierPhone || '-' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
@@ -209,7 +303,13 @@
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" min-width="260" fixed="right" align="center" header-align="center">
|
||||
<el-table-column
|
||||
label="操作"
|
||||
min-width="260"
|
||||
fixed="right"
|
||||
align="center"
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<el-button
|
||||
v-if="activeTab === 'pending'"
|
||||
@@ -251,13 +351,39 @@
|
||||
<div v-if="currentRow" class="sample-reception-readonly">
|
||||
<h4 class="detail-section-title">样本信息</h4>
|
||||
<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><el-tag :type="protocolStatusTagTypeBySource(String(currentRow.protocolStatus || ''), currentRow.sourceType || 'cell_storage')" size="small">{{ protocolStatusLabelBySource(String(currentRow.protocolStatus || ''), currentRow.sourceType || 'cell_storage') }}</el-tag></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"
|
||||
><span class="label">协议号</span>{{ currentRow.protocolNo || '-' }}</div
|
||||
>
|
||||
<div class="readonly-item"
|
||||
><span class="label">协议状态</span
|
||||
><el-tag
|
||||
:type="
|
||||
protocolStatusTagTypeBySource(
|
||||
String(currentRow.protocolStatus || ''),
|
||||
currentRow.sourceType || 'cell_storage'
|
||||
)
|
||||
"
|
||||
size="small"
|
||||
>{{
|
||||
protocolStatusLabelBySource(
|
||||
String(currentRow.protocolStatus || ''),
|
||||
currentRow.sourceType || 'cell_storage'
|
||||
)
|
||||
}}</el-tag
|
||||
></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 readonly-col-span-2">
|
||||
<span class="label">客户</span>{{ currentRow.motherName || '-' }} / {{ currentRow.newbornName || '-' }}
|
||||
<span class="label">客户</span>{{ currentRow.motherName || '-' }} /
|
||||
{{ currentRow.newbornName || '-' }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -265,16 +391,27 @@
|
||||
<h4 class="detail-section-title">运输信息</h4>
|
||||
<div class="readonly-grid form-grid-3">
|
||||
<div class="readonly-item">
|
||||
<span class="label">运输方式</span>{{ transportTypeLabel(String(currentRow?.transportType || '')) }}
|
||||
<span class="label">运输方式</span
|
||||
>{{ transportTypeLabel(String(currentRow?.transportType || '')) }}
|
||||
</div>
|
||||
<div class="readonly-item"><span class="label">预计到达</span>{{ currentRow?.etaTime || '-' }}</div>
|
||||
<div class="readonly-item"
|
||||
><span class="label">预计到达</span>{{ currentRow?.etaTime || '-' }}</div
|
||||
>
|
||||
<template v-if="currentRow?.transportType === 'kdys'">
|
||||
<div class="readonly-item"><span class="label">快递公司</span>{{ currentRow?.expressCompanyName || '-' }}</div>
|
||||
<div class="readonly-item"><span class="label">快递单号</span>{{ currentRow?.waybillNo || '-' }}</div>
|
||||
<div class="readonly-item"
|
||||
><span class="label">快递公司</span>{{ currentRow?.expressCompanyName || '-' }}</div
|
||||
>
|
||||
<div class="readonly-item"
|
||||
><span class="label">快递单号</span>{{ currentRow?.waybillNo || '-' }}</div
|
||||
>
|
||||
</template>
|
||||
<template v-else-if="currentRow?.transportType === 'ryxd'">
|
||||
<div class="readonly-item"><span class="label">携带人</span>{{ currentRow?.carrierName || '-' }}</div>
|
||||
<div class="readonly-item"><span class="label">携带人电话</span>{{ currentRow?.carrierPhone || '-' }}</div>
|
||||
<div class="readonly-item"
|
||||
><span class="label">携带人</span>{{ currentRow?.carrierName || '-' }}</div
|
||||
>
|
||||
<div class="readonly-item"
|
||||
><span class="label">携带人电话</span>{{ currentRow?.carrierPhone || '-' }}</div
|
||||
>
|
||||
</template>
|
||||
</div>
|
||||
<div class="form-section-divider" />
|
||||
@@ -352,7 +489,9 @@
|
||||
{{ item.itemName }}
|
||||
</el-checkbox>
|
||||
</div>
|
||||
<p v-else class="check-empty-hint">暂无启用的检查项,请先在「接收检查项配置」中维护。</p>
|
||||
<p v-else class="check-empty-hint"
|
||||
>暂无启用的检查项,请先在「接收检查项配置」中维护。</p
|
||||
>
|
||||
</el-form-item>
|
||||
<ProtocolFinanceConfirmFields
|
||||
v-model:finance-remark="receiveForm.remark"
|
||||
@@ -366,7 +505,12 @@
|
||||
<el-button class="btn-ghost" @click="receiveDialogVisible = false">
|
||||
<el-icon><Close /></el-icon>取消
|
||||
</el-button>
|
||||
<el-button class="btn-main" type="primary" :loading="submitLoading" @click="submitReceive">
|
||||
<el-button
|
||||
class="btn-main"
|
||||
type="primary"
|
||||
:loading="submitLoading"
|
||||
@click="submitReceive"
|
||||
>
|
||||
<el-icon><Check /></el-icon>{{ receiveDialogMode === 'edit' ? '保存' : '提交' }}
|
||||
</el-button>
|
||||
</template>
|
||||
@@ -383,38 +527,90 @@
|
||||
<template v-if="viewRow">
|
||||
<h4 class="detail-section-title">样本信息</h4>
|
||||
<div class="readonly-grid form-grid-2">
|
||||
<div class="readonly-item"><span class="label">协议号</span>{{ viewRow.protocolNo || '-' }}</div>
|
||||
<div class="readonly-item"><span class="label">协议状态</span><el-tag :type="protocolStatusTagTypeBySource(String(viewRow.protocolStatus || ''), viewRow.sourceType || 'cell_storage')" size="small">{{ protocolStatusLabelBySource(String(viewRow.protocolStatus || ''), viewRow.sourceType || 'cell_storage') }}</el-tag></div>
|
||||
<div class="readonly-item"><span class="label">样本编码</span>{{ viewRow.sampleCode || '-' }}</div>
|
||||
<div class="readonly-item"><span class="label">样本类型</span>{{ viewRow.sampleTypeName || '-' }}</div>
|
||||
<div class="readonly-item"><span class="label">预计到达</span>{{ viewRow.etaTime || '-' }}</div>
|
||||
<div class="readonly-item"
|
||||
><span class="label">协议号</span>{{ viewRow.protocolNo || '-' }}</div
|
||||
>
|
||||
<div class="readonly-item"
|
||||
><span class="label">协议状态</span
|
||||
><el-tag
|
||||
:type="
|
||||
protocolStatusTagTypeBySource(
|
||||
String(viewRow.protocolStatus || ''),
|
||||
viewRow.sourceType || 'cell_storage'
|
||||
)
|
||||
"
|
||||
size="small"
|
||||
>{{
|
||||
protocolStatusLabelBySource(
|
||||
String(viewRow.protocolStatus || ''),
|
||||
viewRow.sourceType || 'cell_storage'
|
||||
)
|
||||
}}</el-tag
|
||||
></div
|
||||
>
|
||||
<div class="readonly-item"
|
||||
><span class="label">样本编码</span>{{ viewRow.sampleCode || '-' }}</div
|
||||
>
|
||||
<div class="readonly-item"
|
||||
><span class="label">样本类型</span>{{ viewRow.sampleTypeName || '-' }}</div
|
||||
>
|
||||
<div class="readonly-item"
|
||||
><span class="label">预计到达</span>{{ viewRow.etaTime || '-' }}</div
|
||||
>
|
||||
</div>
|
||||
<div class="form-section-divider" />
|
||||
<h4 class="detail-section-title">运输信息</h4>
|
||||
<div class="readonly-grid form-grid-2">
|
||||
<div class="readonly-item">
|
||||
<span class="label">运输方式</span>{{ transportTypeLabel(String(viewRow.transportType || '')) }}
|
||||
<span class="label">运输方式</span
|
||||
>{{ transportTypeLabel(String(viewRow.transportType || '')) }}
|
||||
</div>
|
||||
<div class="readonly-item"><span class="label">预计到达</span>{{ viewRow.etaTime || '-' }}</div>
|
||||
<div class="readonly-item"
|
||||
><span class="label">预计到达</span>{{ viewRow.etaTime || '-' }}</div
|
||||
>
|
||||
<template v-if="viewRow.transportType === 'kdys'">
|
||||
<div class="readonly-item"><span class="label">快递公司</span>{{ viewRow.expressCompanyName || '-' }}</div>
|
||||
<div class="readonly-item"><span class="label">快递单号</span>{{ viewRow.waybillNo || '-' }}</div>
|
||||
<div class="readonly-item"
|
||||
><span class="label">快递公司</span>{{ viewRow.expressCompanyName || '-' }}</div
|
||||
>
|
||||
<div class="readonly-item"
|
||||
><span class="label">快递单号</span>{{ viewRow.waybillNo || '-' }}</div
|
||||
>
|
||||
</template>
|
||||
<template v-else-if="viewRow.transportType === 'ryxd'">
|
||||
<div class="readonly-item"><span class="label">携带人</span>{{ viewRow.carrierName || '-' }}</div>
|
||||
<div class="readonly-item"><span class="label">携带人电话</span>{{ viewRow.carrierPhone || '-' }}</div>
|
||||
<div class="readonly-item"
|
||||
><span class="label">携带人</span>{{ viewRow.carrierName || '-' }}</div
|
||||
>
|
||||
<div class="readonly-item"
|
||||
><span class="label">携带人电话</span>{{ viewRow.carrierPhone || '-' }}</div
|
||||
>
|
||||
</template>
|
||||
</div>
|
||||
<div class="form-section-divider" />
|
||||
<h4 class="detail-section-title">接收信息</h4>
|
||||
<div class="readonly-grid form-grid-2">
|
||||
<div class="readonly-item"><span class="label">接收时间</span>{{ viewRow.receptionTime || '-' }}</div>
|
||||
<div class="readonly-item"><span class="label">经手人</span>{{ viewRow.handlerName || '-' }}</div>
|
||||
<div class="readonly-item"><span class="label">接收温度(℃)</span>{{ viewRow.receiveTemperature ? viewRow.receiveTemperature + '℃' : '-' }}</div>
|
||||
<div class="readonly-item"><span class="label">体积(ml)</span>{{ formatMetric(viewRow.volumeMl) }}</div>
|
||||
<div class="readonly-item"><span class="label">净重(g)</span>{{ formatMetric(viewRow.netWeightG) }}</div>
|
||||
<div class="readonly-item"
|
||||
><span class="label">接收时间</span>{{ viewRow.receptionTime || '-' }}</div
|
||||
>
|
||||
<div class="readonly-item"
|
||||
><span class="label">经手人</span>{{ viewRow.handlerName || '-' }}</div
|
||||
>
|
||||
<div class="readonly-item"
|
||||
><span class="label">接收温度(℃)</span
|
||||
>{{ viewRow.receiveTemperature ? viewRow.receiveTemperature + '℃' : '-' }}</div
|
||||
>
|
||||
<div class="readonly-item"
|
||||
><span class="label">体积(ml)</span>{{ formatMetric(viewRow.volumeMl) }}</div
|
||||
>
|
||||
<div class="readonly-item"
|
||||
><span class="label">净重(g)</span>{{ formatMetric(viewRow.netWeightG) }}</div
|
||||
>
|
||||
<div class="readonly-item">
|
||||
<span class="label">结论</span><el-tag :type="viewRow.acceptConclusion === 'js' ? 'success' : 'danger'" size="small">{{ acceptConclusionLabel(String(viewRow.acceptConclusion || '')) }}</el-tag>
|
||||
<span class="label">结论</span
|
||||
><el-tag
|
||||
:type="viewRow.acceptConclusion === 'js' ? 'success' : 'danger'"
|
||||
size="small"
|
||||
>{{ acceptConclusionLabel(String(viewRow.acceptConclusion || '')) }}</el-tag
|
||||
>
|
||||
</div>
|
||||
<div class="readonly-item form-col-span-2 check-items-readonly">
|
||||
<span class="label">检查项</span>
|
||||
@@ -470,19 +666,53 @@
|
||||
样本 {{ index + 1 }}:{{ entry.sampleCode || entry.protocolNo || '—' }}
|
||||
</h4>
|
||||
<div class="readonly-grid form-grid-2 batch-receive-card__meta">
|
||||
<div class="readonly-item"><span class="label">协议号</span>{{ entry.protocolNo || '—' }}</div>
|
||||
<div class="readonly-item"><span class="label">协议状态</span><el-tag :type="protocolStatusTagTypeBySource(String(entry.protocolStatus || ''), entry.sourceType || 'cell_storage')" size="small">{{ protocolStatusLabelBySource(String(entry.protocolStatus || ''), entry.sourceType || 'cell_storage') }}</el-tag></div>
|
||||
<div class="readonly-item"><span class="label">样本类型</span>{{ entry.sampleTypeName || '—' }}</div>
|
||||
<div class="readonly-item"
|
||||
><span class="label">协议号</span>{{ entry.protocolNo || '—' }}</div
|
||||
>
|
||||
<div class="readonly-item"
|
||||
><span class="label">协议状态</span
|
||||
><el-tag
|
||||
:type="
|
||||
protocolStatusTagTypeBySource(
|
||||
String(entry.protocolStatus || ''),
|
||||
entry.sourceType || 'cell_storage'
|
||||
)
|
||||
"
|
||||
size="small"
|
||||
>{{
|
||||
protocolStatusLabelBySource(
|
||||
String(entry.protocolStatus || ''),
|
||||
entry.sourceType || 'cell_storage'
|
||||
)
|
||||
}}</el-tag
|
||||
></div
|
||||
>
|
||||
<div class="readonly-item"
|
||||
><span class="label">样本类型</span>{{ entry.sampleTypeName || '—' }}</div
|
||||
>
|
||||
<div class="readonly-item form-col-span-2">
|
||||
<span class="label">客户</span>{{ entry.motherName || '—' }} / {{ entry.newbornName || '—' }}
|
||||
<span class="label">客户</span>{{ entry.motherName || '—' }} /
|
||||
{{ entry.newbornName || '—' }}
|
||||
</div>
|
||||
<div class="readonly-item">
|
||||
<span class="label">来源</span>
|
||||
<el-tag :type="entry.sourceType === 'research_donation' ? 'warning' : 'success'" size="small">
|
||||
<el-tag
|
||||
:type="entry.sourceType === 'research_donation' ? 'warning' : 'success'"
|
||||
size="small"
|
||||
>
|
||||
{{ entry.sourceType === 'research_donation' ? '科研捐赠' : '细胞存储' }}
|
||||
</el-tag>
|
||||
</div>
|
||||
<div class="readonly-item"><span class="label">运输方式</span>{{ entry.transportType === 'kdys' ? '快递运输' : entry.transportType === 'ryxd' ? '人员携带' : '-' }}</div>
|
||||
<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-3">
|
||||
@@ -551,7 +781,9 @@
|
||||
{{ item.itemName }}
|
||||
</el-checkbox>
|
||||
</div>
|
||||
<p v-else class="check-empty-hint">暂无启用的检查项,请先在「接收检查项配置」中维护。</p>
|
||||
<p v-else class="check-empty-hint"
|
||||
>暂无启用的检查项,请先在「接收检查项配置」中维护。</p
|
||||
>
|
||||
</el-form-item>
|
||||
<ProtocolFinanceConfirmFields
|
||||
v-model:finance-remark="entry.remark"
|
||||
@@ -567,7 +799,12 @@
|
||||
<el-button class="btn-ghost" @click="batchReceiveDialogVisible = false">
|
||||
<el-icon><Close /></el-icon>取消
|
||||
</el-button>
|
||||
<el-button class="btn-main" type="primary" :loading="submitLoading" @click="submitBatchReceive">
|
||||
<el-button
|
||||
class="btn-main"
|
||||
type="primary"
|
||||
:loading="submitLoading"
|
||||
@click="submitBatchReceive"
|
||||
>
|
||||
<el-icon><Check /></el-icon>提交全部
|
||||
</el-button>
|
||||
</template>
|
||||
@@ -607,16 +844,20 @@
|
||||
protocolStatusLabel,
|
||||
protocolStatusTagType,
|
||||
donationStatusLabel,
|
||||
donationStatusTagType,
|
||||
donationStatusTagType
|
||||
} from '@/utils/status-tag-registry'
|
||||
import type { TagType } from '@/utils/status-tag-registry'
|
||||
|
||||
/** 根据来源类型返回协议状态标签 */
|
||||
function protocolStatusLabelBySource(status: string, source: string): string {
|
||||
return source === 'research_donation' ? donationStatusLabel(status) : protocolStatusLabel(status)
|
||||
return source === 'research_donation'
|
||||
? donationStatusLabel(status)
|
||||
: protocolStatusLabel(status)
|
||||
}
|
||||
function protocolStatusTagTypeBySource(status: string, source: string): TagType {
|
||||
return source === 'research_donation' ? donationStatusTagType(status) : protocolStatusTagType(status)
|
||||
return source === 'research_donation'
|
||||
? donationStatusTagType(status)
|
||||
: protocolStatusTagType(status)
|
||||
}
|
||||
|
||||
defineOptions({ name: 'QualityInspectionSampleReception' })
|
||||
@@ -787,9 +1028,7 @@
|
||||
}
|
||||
|
||||
function applyCheckResultsFromDetail(results: SampleReceptionCheckResult[]) {
|
||||
const map = new Map(
|
||||
(results || []).map((r) => [String(r.checkItemId || ''), !!r.checked])
|
||||
)
|
||||
const map = new Map((results || []).map((r) => [String(r.checkItemId || ''), !!r.checked]))
|
||||
for (const item of activeCheckItems.value) {
|
||||
checkValues[item.id] = map.has(item.id) ? !!map.get(item.id) : false
|
||||
}
|
||||
@@ -801,13 +1040,11 @@
|
||||
receiveForm.handlerName = String(detail.handlerName || '')
|
||||
receiveForm.receiveTemperature = String(detail.receiveTemperature || '')
|
||||
const vol = detail.volumeMl
|
||||
receiveForm.volumeMl =
|
||||
vol === null || vol === undefined || vol === '' ? undefined : Number(vol)
|
||||
receiveForm.volumeMl = vol === null || vol === undefined || vol === '' ? undefined : Number(vol)
|
||||
const net = detail.netWeightG
|
||||
receiveForm.netWeightG =
|
||||
net === null || net === undefined || net === '' ? undefined : Number(net)
|
||||
receiveForm.acceptConclusion =
|
||||
String(detail.acceptConclusion || 'js') === 'bj' ? 'bj' : 'js'
|
||||
receiveForm.acceptConclusion = String(detail.acceptConclusion || 'js') === 'bj' ? 'bj' : 'js'
|
||||
receiveForm.remark = String(detail.remark || '')
|
||||
const checkList = detail.checkResults
|
||||
applyCheckResultsFromDetail(
|
||||
@@ -1011,12 +1248,10 @@
|
||||
try {
|
||||
await fetchSampleReceptionSubmit({
|
||||
sourceType: entry.sourceType,
|
||||
donationSampleId: entry.sourceType === 'research_donation'
|
||||
? entry.protocolSampleId
|
||||
: undefined,
|
||||
protocolSampleId: entry.sourceType !== 'research_donation'
|
||||
? entry.protocolSampleId
|
||||
: undefined,
|
||||
donationSampleId:
|
||||
entry.sourceType === 'research_donation' ? entry.protocolSampleId : undefined,
|
||||
protocolSampleId:
|
||||
entry.sourceType !== 'research_donation' ? entry.protocolSampleId : undefined,
|
||||
receptionTime: entry.receptionTime,
|
||||
handlerUserId: entry.handlerUserId,
|
||||
handlerName: entry.handlerName,
|
||||
@@ -1229,12 +1464,14 @@
|
||||
} else {
|
||||
await fetchSampleReceptionSubmit({
|
||||
sourceType: (currentRow.value.sourceType as string) || 'cell_storage',
|
||||
donationSampleId: currentRow.value.sourceType === 'research_donation'
|
||||
? String(currentRow.value.protocolSampleId)
|
||||
: undefined,
|
||||
protocolSampleId: currentRow.value.sourceType !== 'research_donation'
|
||||
? String(currentRow.value.protocolSampleId)
|
||||
: undefined,
|
||||
donationSampleId:
|
||||
currentRow.value.sourceType === 'research_donation'
|
||||
? String(currentRow.value.protocolSampleId)
|
||||
: undefined,
|
||||
protocolSampleId:
|
||||
currentRow.value.sourceType !== 'research_donation'
|
||||
? String(currentRow.value.protocolSampleId)
|
||||
: undefined,
|
||||
...payload
|
||||
})
|
||||
receiveDialogVisible.value = false
|
||||
|
||||
+8
-5
@@ -119,9 +119,7 @@
|
||||
return String(v)
|
||||
}
|
||||
|
||||
function formatCheckResults(
|
||||
items?: Array<{ itemName?: string; checked?: boolean }>
|
||||
) {
|
||||
function formatCheckResults(items?: Array<{ itemName?: string; checked?: boolean }>) {
|
||||
if (!items?.length) return '—'
|
||||
return items.map((x) => `${x.itemName || ''}${x.checked ? '(✓)' : '(×)'}`).join('、')
|
||||
}
|
||||
@@ -275,9 +273,14 @@
|
||||
{ label: '净重(g)', value: formatMetric(r.netWeightG) },
|
||||
{
|
||||
label: '结论',
|
||||
value: r.acceptConclusionLabel || acceptConclusionLabel(String(r.acceptConclusion || '')),
|
||||
value:
|
||||
r.acceptConclusionLabel || acceptConclusionLabel(String(r.acceptConclusion || '')),
|
||||
tagType:
|
||||
r.acceptConclusion === 'js' ? 'success' : r.acceptConclusion === 'jj' ? 'danger' : 'info'
|
||||
r.acceptConclusion === 'js'
|
||||
? 'success'
|
||||
: r.acceptConclusion === 'jj'
|
||||
? 'danger'
|
||||
: 'info'
|
||||
},
|
||||
{ label: '检查项', value: formatCheckResults(r.checkResults), span: true },
|
||||
{ label: '备注', value: r.remark || '—', span: true }
|
||||
|
||||
@@ -180,11 +180,7 @@
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<el-tag
|
||||
v-if="row.protocolStatus"
|
||||
:type="protocolStatusTagForRow(row)"
|
||||
size="small"
|
||||
>
|
||||
<el-tag v-if="row.protocolStatus" :type="protocolStatusTagForRow(row)" size="small">
|
||||
{{ protocolStatusLabelForRow(row) }}
|
||||
</el-tag>
|
||||
<span v-else>—</span>
|
||||
|
||||
@@ -19,7 +19,12 @@
|
||||
<el-input v-model="searchForm.keyword" placeholder="编码/名称" clearable />
|
||||
</el-form-item>
|
||||
<el-form-item label="状态">
|
||||
<el-select v-model="searchForm.status" placeholder="全部" clearable style="width: 100%">
|
||||
<el-select
|
||||
v-model="searchForm.status"
|
||||
placeholder="全部"
|
||||
clearable
|
||||
style="width: 100%"
|
||||
>
|
||||
<el-option label="启用" value="1" />
|
||||
<el-option label="停用" value="0" />
|
||||
</el-select>
|
||||
@@ -73,15 +78,25 @@
|
||||
<el-table-column prop="contactPhone" label="联系电话" min-width="120" align="center" />
|
||||
<el-table-column prop="status" label="状态" min-width="90" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="row.status === '1' ? 'success' : 'info'" size="small">{{ row.status === '1' ? '启用' : '停用' }}</el-tag>
|
||||
<el-tag :type="row.status === '1' ? 'success' : 'info'" size="small">{{
|
||||
row.status === '1' ? '启用' : '停用'
|
||||
}}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" min-width="400" fixed="right" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-button class="btn-op-item" @click="handleView(row)"><el-icon><View /></el-icon>查看</el-button>
|
||||
<el-button class="btn-op-item primary" @click="openItemDialog(row)"><el-icon><Setting /></el-icon>检测项目配置</el-button>
|
||||
<el-button class="btn-op-item" @click="handleEdit(row)"><el-icon><Edit /></el-icon>编辑</el-button>
|
||||
<el-button class="btn-op-item" @click="handleDelete(row)"><el-icon><Delete /></el-icon>删除</el-button>
|
||||
<el-button class="btn-op-item" @click="handleView(row)"
|
||||
><el-icon><View /></el-icon>查看</el-button
|
||||
>
|
||||
<el-button class="btn-op-item primary" @click="openItemDialog(row)"
|
||||
><el-icon><Setting /></el-icon>检测项目配置</el-button
|
||||
>
|
||||
<el-button class="btn-op-item" @click="handleEdit(row)"
|
||||
><el-icon><Edit /></el-icon>编辑</el-button
|
||||
>
|
||||
<el-button class="btn-op-item" @click="handleDelete(row)"
|
||||
><el-icon><Delete /></el-icon>删除</el-button
|
||||
>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
@@ -107,10 +122,20 @@
|
||||
destroy-on-close
|
||||
:close-on-click-modal="false"
|
||||
>
|
||||
<el-form ref="companyFormRef" :model="companyForm" :rules="companyFormRules" label-position="top" class="biz-dialog-form">
|
||||
<el-form
|
||||
ref="companyFormRef"
|
||||
:model="companyForm"
|
||||
:rules="companyFormRules"
|
||||
label-position="top"
|
||||
class="biz-dialog-form"
|
||||
>
|
||||
<div class="form-grid-3">
|
||||
<el-form-item label="公司编码" prop="companyCode">
|
||||
<el-input v-model="companyForm.companyCode" :disabled="companyIsEdit" placeholder="租户内唯一" />
|
||||
<el-input
|
||||
v-model="companyForm.companyCode"
|
||||
:disabled="companyIsEdit"
|
||||
placeholder="租户内唯一"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="公司名称" prop="companyName">
|
||||
<el-input v-model="companyForm.companyName" />
|
||||
@@ -160,7 +185,12 @@
|
||||
<el-button class="btn-ghost" @click="companyDialogVisible = false">
|
||||
<el-icon><Close /></el-icon>取消
|
||||
</el-button>
|
||||
<el-button class="btn-main" type="primary" :loading="companySubmitLoading" @click="submitCompany">
|
||||
<el-button
|
||||
class="btn-main"
|
||||
type="primary"
|
||||
:loading="companySubmitLoading"
|
||||
@click="submitCompany"
|
||||
>
|
||||
<el-icon><Check /></el-icon>确定
|
||||
</el-button>
|
||||
</template>
|
||||
@@ -204,18 +234,32 @@
|
||||
<el-table-column prop="itemCode" label="项目编码" min-width="120" align="center" />
|
||||
<el-table-column prop="itemName" label="项目名称" min-width="160" align="center" />
|
||||
<el-table-column prop="feeAmount" label="费用(元)" min-width="110" align="center">
|
||||
<template #default="{ row }">{{ row.feeAmount != null ? '¥' + formatMoneyDisplay(row.feeAmount) : '—' }}</template>
|
||||
<template #default="{ row }">{{
|
||||
row.feeAmount != null ? '¥' + formatMoneyDisplay(row.feeAmount) : '—'
|
||||
}}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="status" label="状态" min-width="90" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="row.status === '1' ? 'success' : 'info'" size="small">{{ row.status === '1' ? '启用' : '停用' }}</el-tag>
|
||||
<el-tag :type="row.status === '1' ? 'success' : 'info'" size="small">{{
|
||||
row.status === '1' ? '启用' : '停用'
|
||||
}}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="remark" label="备注" min-width="140" align="center" show-overflow-tooltip />
|
||||
<el-table-column
|
||||
prop="remark"
|
||||
label="备注"
|
||||
min-width="140"
|
||||
align="center"
|
||||
show-overflow-tooltip
|
||||
/>
|
||||
<el-table-column label="操作" min-width="260" fixed="right" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-button class="btn-op-item primary" @click="handleEditItem(row)"><el-icon><Edit /></el-icon>编辑</el-button>
|
||||
<el-button class="btn-op-item" @click="handleDeleteItem(row)"><el-icon><Delete /></el-icon>删除</el-button>
|
||||
<el-button class="btn-op-item primary" @click="handleEditItem(row)"
|
||||
><el-icon><Edit /></el-icon>编辑</el-button
|
||||
>
|
||||
<el-button class="btn-op-item" @click="handleDeleteItem(row)"
|
||||
><el-icon><Delete /></el-icon>删除</el-button
|
||||
>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
@@ -226,7 +270,9 @@
|
||||
:total="itemPagination.total"
|
||||
:page-sizes="[10, 20, 50]"
|
||||
layout="total, sizes, prev, pager, next"
|
||||
@size-change="(v) => ((itemPagination.page = 1), (itemPagination.pageSize = v), loadItems())"
|
||||
@size-change="
|
||||
(v) => ((itemPagination.page = 1), (itemPagination.pageSize = v), loadItems())
|
||||
"
|
||||
@current-change="(v) => ((itemPagination.page = v), loadItems())"
|
||||
/>
|
||||
</div>
|
||||
@@ -296,14 +342,32 @@
|
||||
header-align="center"
|
||||
:index="(i) => i + 1"
|
||||
/>
|
||||
<el-table-column label="参数名称" min-width="140" align="center" header-align="center">
|
||||
<el-table-column
|
||||
label="参数名称"
|
||||
min-width="140"
|
||||
align="center"
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<el-input v-model="row.paramName" placeholder="参数名称" class="inner-cell-input" />
|
||||
<el-input
|
||||
v-model="row.paramName"
|
||||
placeholder="参数名称"
|
||||
class="inner-cell-input"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="参数编码" min-width="120" align="center" header-align="center">
|
||||
<el-table-column
|
||||
label="参数编码"
|
||||
min-width="120"
|
||||
align="center"
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<el-input v-model="row.paramCode" placeholder="参数编码" class="inner-cell-input" />
|
||||
<el-input
|
||||
v-model="row.paramCode"
|
||||
placeholder="参数编码"
|
||||
class="inner-cell-input"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="单位" min-width="120" align="center" header-align="center">
|
||||
@@ -343,15 +407,13 @@
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" min-width="80" align="center" header-align="center">
|
||||
<template #default="{ $index }">
|
||||
<el-button link type="danger" size="small" @click="removeItemParam($index)">删除</el-button>
|
||||
<el-button link type="danger" size="small" @click="removeItemParam($index)"
|
||||
>删除</el-button
|
||||
>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<el-empty
|
||||
v-else
|
||||
description="暂无参数,点击上方按钮添加"
|
||||
:image-size="48"
|
||||
/>
|
||||
<el-empty v-else description="暂无参数,点击上方按钮添加" :image-size="48" />
|
||||
</div>
|
||||
|
||||
<div class="form-grid-2">
|
||||
@@ -364,7 +426,12 @@
|
||||
<el-button class="btn-ghost" @click="itemFormVisible = false">
|
||||
<el-icon><Close /></el-icon>取消
|
||||
</el-button>
|
||||
<el-button class="btn-main" type="primary" :loading="itemSubmitLoading" @click="submitItem">
|
||||
<el-button
|
||||
class="btn-main"
|
||||
type="primary"
|
||||
:loading="itemSubmitLoading"
|
||||
@click="submitItem"
|
||||
>
|
||||
<el-icon><Check /></el-icon>确定
|
||||
</el-button>
|
||||
</template>
|
||||
@@ -390,7 +457,18 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, onMounted } from 'vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { Plus, Close, Check, Search, RefreshLeft, View, Setting, Edit, Delete, OfficeBuilding } from '@element-plus/icons-vue'
|
||||
import {
|
||||
Plus,
|
||||
Close,
|
||||
Check,
|
||||
Search,
|
||||
RefreshLeft,
|
||||
View,
|
||||
Setting,
|
||||
Edit,
|
||||
Delete,
|
||||
OfficeBuilding
|
||||
} from '@element-plus/icons-vue'
|
||||
import SystemViewDialog from '@/components/business/system-view-dialog/index.vue'
|
||||
import type { ViewSection } from '@/components/business/system-view-dialog/index.vue'
|
||||
import {
|
||||
@@ -622,9 +700,13 @@
|
||||
}
|
||||
|
||||
async function handleDelete(row: QcThirdPartyCompany) {
|
||||
await ElMessageBox.confirm(`确定删除「${row.companyName}」?其下属检测项目将一并删除。`, '提示', {
|
||||
type: 'warning'
|
||||
})
|
||||
await ElMessageBox.confirm(
|
||||
`确定删除「${row.companyName}」?其下属检测项目将一并删除。`,
|
||||
'提示',
|
||||
{
|
||||
type: 'warning'
|
||||
}
|
||||
)
|
||||
await fetchDeleteQcThirdPartyCompany(row.id)
|
||||
ElMessage.success('已删除')
|
||||
loadData()
|
||||
@@ -647,7 +729,11 @@
|
||||
{ label: '联系人', value: row.contactName },
|
||||
{ label: '联系电话', value: row.contactPhone },
|
||||
{ label: '地址', value: row.address, span: true },
|
||||
{ label: '状态', value: row.status === '1' ? '启用' : '停用', tagType: row.status === '1' ? 'success' : 'info' },
|
||||
{
|
||||
label: '状态',
|
||||
value: row.status === '1' ? '启用' : '停用',
|
||||
tagType: row.status === '1' ? 'success' : 'info'
|
||||
},
|
||||
{ label: '备注', value: row.remark, span: true }
|
||||
]
|
||||
},
|
||||
|
||||
@@ -57,7 +57,9 @@
|
||||
</el-form-item>
|
||||
</div>
|
||||
<div class="filter-actions-row">
|
||||
<span class="filter-hint">列表为概要信息,详情见弹窗;默认展示近 30 天内创建的协议单。</span>
|
||||
<span class="filter-hint"
|
||||
>列表为概要信息,详情见弹窗;默认展示近 30 天内创建的协议单。</span
|
||||
>
|
||||
<div class="filter-actions-end">
|
||||
<el-button class="btn-ghost" @click="handleReset"
|
||||
><el-icon><RefreshLeft /></el-icon>重置</el-button
|
||||
@@ -152,9 +154,11 @@
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="donationStatusTagType(String(row.donationStatus || ''))" size="small">{{
|
||||
donationStatusLabel(String(row.donationStatus || ''))
|
||||
}}</el-tag>
|
||||
<el-tag
|
||||
:type="donationStatusTagType(String(row.donationStatus || ''))"
|
||||
size="small"
|
||||
>{{ donationStatusLabel(String(row.donationStatus || '')) }}</el-tag
|
||||
>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
|
||||
@@ -9,10 +9,7 @@
|
||||
destroy-on-close
|
||||
:close-on-click-modal="false"
|
||||
>
|
||||
<DialogFormBody
|
||||
:loading="dialogFormLoading"
|
||||
body-class="protocol-dialog-body min-h-[200px]"
|
||||
>
|
||||
<DialogFormBody :loading="dialogFormLoading" body-class="protocol-dialog-body min-h-[200px]">
|
||||
<div class="detail-tabs">
|
||||
<div
|
||||
v-for="tab in formTabs"
|
||||
@@ -670,9 +667,7 @@
|
||||
<div
|
||||
class="attachment-preview"
|
||||
@click="
|
||||
isImageFile(file) && file.fileUrl
|
||||
? handlePreviewAttachment(file)
|
||||
: undefined
|
||||
isImageFile(file) && file.fileUrl ? handlePreviewAttachment(file) : undefined
|
||||
"
|
||||
>
|
||||
<template v-if="isImageFile(file) && file.fileUrl">
|
||||
@@ -711,7 +706,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<span class="upload-hint">支持图片和 PDF 格式,可稍后补充</span>
|
||||
<span class="upload-hint">支持图片和 PDF 格式,可稍后补充</span>
|
||||
</div>
|
||||
|
||||
<!-- 其他附件 -->
|
||||
@@ -742,9 +737,7 @@
|
||||
<div
|
||||
class="attachment-preview"
|
||||
@click="
|
||||
isImageFile(file) && file.fileUrl
|
||||
? handlePreviewAttachment(file)
|
||||
: undefined
|
||||
isImageFile(file) && file.fileUrl ? handlePreviewAttachment(file) : undefined
|
||||
"
|
||||
>
|
||||
<template v-if="isImageFile(file) && file.fileUrl">
|
||||
@@ -783,7 +776,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<span class="upload-hint">支持图片、PDF、Word 格式</span>
|
||||
<span class="upload-hint">支持图片、PDF、Word 格式</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -933,7 +926,16 @@
|
||||
<script setup lang="ts">
|
||||
import DialogFormBody from '@/components/business/dialog-form-body/index.vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { Plus, Close, Check, Delete, Document, VideoPlay, Loading, Download } from '@element-plus/icons-vue'
|
||||
import {
|
||||
Plus,
|
||||
Close,
|
||||
Check,
|
||||
Delete,
|
||||
Document,
|
||||
VideoPlay,
|
||||
Loading,
|
||||
Download
|
||||
} from '@element-plus/icons-vue'
|
||||
import {
|
||||
fetchResearchDonationDetail,
|
||||
fetchCreateResearchDonation,
|
||||
@@ -1343,14 +1345,10 @@
|
||||
function applyDonationBundleToForm(bundle: Record<string, any>) {
|
||||
const donation = (bundle.donation ?? bundle.protocol) as Record<string, any> | null
|
||||
if (donation) {
|
||||
formData.collectorUserId = String(
|
||||
donation.collectorUserId || formData.collectorUserId || ''
|
||||
)
|
||||
formData.collectorUserId = String(donation.collectorUserId || formData.collectorUserId || '')
|
||||
formData.donationNo = String(donation.donationNo || formData.donationNo || '')
|
||||
ensureDonationProtocolNoInOptions(formData.donationNo)
|
||||
formData.donationStatus = String(
|
||||
donation.donationStatus || formData.donationStatus || 'dsx'
|
||||
)
|
||||
formData.donationStatus = String(donation.donationStatus || formData.donationStatus || 'dsx')
|
||||
formData.donationDate = String(donation.donationDate || formData.donationDate || '')
|
||||
formData.remark = String(donation.remark ?? formData.remark ?? '')
|
||||
}
|
||||
@@ -1400,9 +1398,9 @@
|
||||
if (consentAtt && consentAtt.length) {
|
||||
formData.consentAttachments = consentAtt.map((a) => ({ ...a })) as any
|
||||
}
|
||||
const otherAtt = (donation?.attachmentFiles ?? (bundle as any).attachmentFiles) as
|
||||
| Array<Record<string, any>>
|
||||
| null
|
||||
const otherAtt = (donation?.attachmentFiles ?? (bundle as any).attachmentFiles) as Array<
|
||||
Record<string, any>
|
||||
> | null
|
||||
if (otherAtt && otherAtt.length) {
|
||||
formData.otherAttachments = otherAtt.map((a) => ({ ...a })) as any
|
||||
}
|
||||
@@ -1493,9 +1491,7 @@
|
||||
if (!sampleRow.sampleCodeId) continue
|
||||
const sc = sampleCodeOptions.value.find((x) => x.id === sampleRow.sampleCodeId)
|
||||
if (sc?.usedInProtocol) {
|
||||
ElMessage.warning(
|
||||
`样本编码「${sampleRow.sampleCode || sc.sampleCode}」已被占用,请更换`
|
||||
)
|
||||
ElMessage.warning(`样本编码「${sampleRow.sampleCode || sc.sampleCode}」已被占用,请更换`)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -57,7 +57,9 @@
|
||||
</el-form-item>
|
||||
</div>
|
||||
<div class="filter-actions-row">
|
||||
<span class="filter-hint">列表为概要信息,详情见弹窗;默认展示近 30 天内创建的协议单。</span>
|
||||
<span class="filter-hint"
|
||||
>列表为概要信息,详情见弹窗;默认展示近 30 天内创建的协议单。</span
|
||||
>
|
||||
<div class="filter-actions-end">
|
||||
<el-button class="btn-ghost" @click="handleReset"
|
||||
><el-icon><RefreshLeft /></el-icon>重置</el-button
|
||||
@@ -120,9 +122,11 @@
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="donationStatusTagType(String(row.donationStatus || ''))" size="small">{{
|
||||
donationStatusLabel(String(row.donationStatus || ''))
|
||||
}}</el-tag>
|
||||
<el-tag
|
||||
:type="donationStatusTagType(String(row.donationStatus || ''))"
|
||||
size="small"
|
||||
>{{ donationStatusLabel(String(row.donationStatus || '')) }}</el-tag
|
||||
>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -22,8 +22,12 @@
|
||||
<div class="filter-actions-row">
|
||||
<span class="filter-hint">按组织名称模糊查询。</span>
|
||||
<div class="filter-actions-end">
|
||||
<el-button class="btn-ghost" @click="handleReset"><el-icon><RefreshLeft /></el-icon>重置</el-button>
|
||||
<el-button class="btn-main" type="primary" @click="handleSearch"><el-icon><Search /></el-icon>搜索</el-button>
|
||||
<el-button class="btn-ghost" @click="handleReset"
|
||||
><el-icon><RefreshLeft /></el-icon>重置</el-button
|
||||
>
|
||||
<el-button class="btn-main" type="primary" @click="handleSearch"
|
||||
><el-icon><Search /></el-icon>搜索</el-button
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</el-form>
|
||||
@@ -243,7 +247,15 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, computed, onMounted, nextTick } from 'vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { Plus, Close, Check, View, OfficeBuilding, RefreshLeft, Search } from '@element-plus/icons-vue'
|
||||
import {
|
||||
Plus,
|
||||
Close,
|
||||
Check,
|
||||
View,
|
||||
OfficeBuilding,
|
||||
RefreshLeft,
|
||||
Search
|
||||
} from '@element-plus/icons-vue'
|
||||
import SystemViewDialog from '@/components/business/system-view-dialog/index.vue'
|
||||
import {
|
||||
fetchDeptTree,
|
||||
@@ -472,7 +484,11 @@
|
||||
{ label: '组织名称', value: viewRow.value?.deptName },
|
||||
{ label: '上级组织', value: getParentDeptDisplay(viewRow.value?.parentId) },
|
||||
{ label: '组织类别', value: getDeptCategoryLabel(viewRow.value?.deptCategoryCode || '') },
|
||||
{ label: '状态', value: viewRow.value?.status === '1' ? '正常' : '停用', tagType: viewRow.value?.status === '1' ? 'success' : 'info' },
|
||||
{
|
||||
label: '状态',
|
||||
value: viewRow.value?.status === '1' ? '正常' : '停用',
|
||||
tagType: viewRow.value?.status === '1' ? 'success' : 'info'
|
||||
},
|
||||
{ label: '负责人', value: viewRow.value?.leader },
|
||||
{ label: '联系电话', value: viewRow.value?.leaderPhone },
|
||||
{ label: '备注', value: viewRow.value?.remark, span: true }
|
||||
|
||||
@@ -15,18 +15,18 @@
|
||||
<el-form :model="searchForm" class="filter-form" @submit.prevent>
|
||||
<div class="filter-grid">
|
||||
<el-form-item label="关键词">
|
||||
<el-input
|
||||
v-model="searchForm.keyword"
|
||||
placeholder="字典名称/类型编码"
|
||||
clearable
|
||||
/>
|
||||
<el-input v-model="searchForm.keyword" placeholder="字典名称/类型编码" clearable />
|
||||
</el-form-item>
|
||||
</div>
|
||||
<div class="filter-actions-row">
|
||||
<span class="filter-hint">按字典名称、类型编码模糊查询。</span>
|
||||
<div class="filter-actions-end">
|
||||
<el-button class="btn-ghost" @click="handleReset"><el-icon><RefreshLeft /></el-icon>重置</el-button>
|
||||
<el-button class="btn-main" type="primary" @click="handleSearch"><el-icon><Search /></el-icon>搜索</el-button>
|
||||
<el-button class="btn-ghost" @click="handleReset"
|
||||
><el-icon><RefreshLeft /></el-icon>重置</el-button
|
||||
>
|
||||
<el-button class="btn-main" type="primary" @click="handleSearch"
|
||||
><el-icon><Search /></el-icon>搜索</el-button
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</el-form>
|
||||
@@ -62,12 +62,7 @@
|
||||
@selection-change="handleSelectionChange"
|
||||
@row-dblclick="handleView"
|
||||
>
|
||||
<el-table-column
|
||||
type="selection"
|
||||
min-width="55"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column type="selection" min-width="55" align="center" header-align="center" />
|
||||
<el-table-column
|
||||
type="index"
|
||||
label="序号"
|
||||
@@ -115,18 +110,11 @@
|
||||
<el-button class="btn-op-item" @click="handleView(row)">
|
||||
<el-icon><View /></el-icon>查看
|
||||
</el-button>
|
||||
<el-button
|
||||
class="btn-op-item primary"
|
||||
@click="handleDictData(row)"
|
||||
>
|
||||
<el-button class="btn-op-item primary" @click="handleDictData(row)">
|
||||
字典数据配置
|
||||
</el-button>
|
||||
<el-button class="btn-op-item primary" @click="handleEdit(row)">
|
||||
编辑
|
||||
</el-button>
|
||||
<el-button class="btn-op-item" @click="handleDelete(row)">
|
||||
删除
|
||||
</el-button>
|
||||
<el-button class="btn-op-item primary" @click="handleEdit(row)"> 编辑 </el-button>
|
||||
<el-button class="btn-op-item" @click="handleDelete(row)"> 删除 </el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
@@ -137,9 +125,7 @@
|
||||
:total="pagination.total"
|
||||
:page-sizes="[10, 20, 50]"
|
||||
layout="total, sizes, prev, pager, next"
|
||||
@size-change="
|
||||
(val) => ((pagination.page = 1), (pagination.pageSize = val), loadData())
|
||||
"
|
||||
@size-change="(val) => ((pagination.page = 1), (pagination.pageSize = val), loadData())"
|
||||
@current-change="(val) => ((pagination.page = val), loadData())"
|
||||
/>
|
||||
</div>
|
||||
@@ -168,19 +154,10 @@
|
||||
<el-input v-model="formData.dictName" placeholder="字典名称" />
|
||||
</el-form-item>
|
||||
<el-form-item label="字典类型编码" prop="dictType">
|
||||
<el-input
|
||||
v-model="formData.dictType"
|
||||
placeholder="字典类型编码"
|
||||
:disabled="isEdit"
|
||||
/>
|
||||
<el-input v-model="formData.dictType" placeholder="字典类型编码" :disabled="isEdit" />
|
||||
</el-form-item>
|
||||
<el-form-item label="备注" style="grid-column: 1 / -1">
|
||||
<el-input
|
||||
v-model="formData.remark"
|
||||
type="textarea"
|
||||
:rows="2"
|
||||
placeholder="可选"
|
||||
/>
|
||||
<el-input v-model="formData.remark" type="textarea" :rows="2" placeholder="可选" />
|
||||
</el-form-item>
|
||||
</div>
|
||||
</el-form>
|
||||
@@ -189,12 +166,7 @@
|
||||
<el-icon><Close /></el-icon>
|
||||
取消
|
||||
</el-button>
|
||||
<el-button
|
||||
class="btn-main"
|
||||
type="primary"
|
||||
@click="handleSubmit"
|
||||
:loading="submitLoading"
|
||||
>
|
||||
<el-button class="btn-main" type="primary" @click="handleSubmit" :loading="submitLoading">
|
||||
<el-icon><Check /></el-icon>
|
||||
确定
|
||||
</el-button>
|
||||
@@ -292,8 +264,7 @@
|
||||
align="center"
|
||||
header-align="center"
|
||||
:index="
|
||||
(index) =>
|
||||
(dictDataPagination.page - 1) * dictDataPagination.pageSize + index + 1
|
||||
(index) => (dictDataPagination.page - 1) * dictDataPagination.pageSize + index + 1
|
||||
"
|
||||
/>
|
||||
<el-table-column
|
||||
@@ -326,15 +297,10 @@
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<el-button
|
||||
class="btn-op-item primary"
|
||||
@click="handleEditDictData(row)"
|
||||
>
|
||||
<el-button class="btn-op-item primary" @click="handleEditDictData(row)">
|
||||
编辑
|
||||
</el-button>
|
||||
<el-button class="btn-op-item" @click="handleDeleteDictData(row)">
|
||||
删除
|
||||
</el-button>
|
||||
<el-button class="btn-op-item" @click="handleDeleteDictData(row)"> 删除 </el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
@@ -352,9 +318,7 @@
|
||||
loadDictData()
|
||||
)
|
||||
"
|
||||
@current-change="
|
||||
(val) => ((dictDataPagination.page = val), loadDictData())
|
||||
"
|
||||
@current-change="(val) => ((dictDataPagination.page = val), loadDictData())"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -418,7 +382,15 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, onMounted, computed } from 'vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { Plus, Close, Check, View, Collection, RefreshLeft, Search } from '@element-plus/icons-vue'
|
||||
import {
|
||||
Plus,
|
||||
Close,
|
||||
Check,
|
||||
View,
|
||||
Collection,
|
||||
RefreshLeft,
|
||||
Search
|
||||
} from '@element-plus/icons-vue'
|
||||
import SystemViewDialog from '@/components/business/system-view-dialog/index.vue'
|
||||
import { withFullscreenLoading, fullscreenLoadingText } from '@/utils/fullscreen-loading'
|
||||
import {
|
||||
@@ -704,11 +676,9 @@
|
||||
}
|
||||
|
||||
async function handleDeleteDictData(row: Api.Dict.DictDataItem) {
|
||||
await ElMessageBox.confirm(
|
||||
`确定删除字典数据「${row.dictLabel}」吗?`,
|
||||
'提示',
|
||||
{ type: 'warning' }
|
||||
)
|
||||
await ElMessageBox.confirm(`确定删除字典数据「${row.dictLabel}」吗?`, '提示', {
|
||||
type: 'warning'
|
||||
})
|
||||
await fetchDeleteDictData(row.id)
|
||||
await loadDictData()
|
||||
}
|
||||
|
||||
@@ -22,8 +22,12 @@
|
||||
<div class="filter-actions-row">
|
||||
<span class="filter-hint">按快递公司名称、编码模糊查询。</span>
|
||||
<div class="filter-actions-end">
|
||||
<el-button class="btn-ghost" @click="handleReset"><el-icon><RefreshLeft /></el-icon>重置</el-button>
|
||||
<el-button class="btn-main" type="primary" @click="handleSearch"><el-icon><Search /></el-icon>搜索</el-button>
|
||||
<el-button class="btn-ghost" @click="handleReset"
|
||||
><el-icon><RefreshLeft /></el-icon>重置</el-button
|
||||
>
|
||||
<el-button class="btn-main" type="primary" @click="handleSearch"
|
||||
><el-icon><Search /></el-icon>搜索</el-button
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</el-form>
|
||||
@@ -68,10 +72,34 @@
|
||||
header-align="center"
|
||||
:index="(index) => (pagination.page - 1) * pagination.pageSize + index + 1"
|
||||
/>
|
||||
<el-table-column prop="expressCode" label="快递编码" min-width="120" align="center" header-align="center" />
|
||||
<el-table-column prop="expressName" label="快递名称" min-width="140" align="center" header-align="center" />
|
||||
<el-table-column prop="createTime" label="创建时间" min-width="180" align="center" header-align="center" />
|
||||
<el-table-column label="操作" min-width="320" fixed="right" align="center" header-align="center">
|
||||
<el-table-column
|
||||
prop="expressCode"
|
||||
label="快递编码"
|
||||
min-width="120"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="expressName"
|
||||
label="快递名称"
|
||||
min-width="140"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="createTime"
|
||||
label="创建时间"
|
||||
min-width="180"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
label="操作"
|
||||
min-width="320"
|
||||
fixed="right"
|
||||
align="center"
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<el-button class="btn-op-item" @click="handleView(row)"
|
||||
><el-icon><View /></el-icon>查看</el-button
|
||||
@@ -292,7 +320,9 @@
|
||||
}
|
||||
|
||||
async function handleDelete(row: Api.ExpressCompany.ExpressCompanyItem) {
|
||||
await ElMessageBox.confirm(`确定删除快递公司「${row.expressName}」吗?`, '提示', { type: 'warning' })
|
||||
await ElMessageBox.confirm(`确定删除快递公司「${row.expressName}」吗?`, '提示', {
|
||||
type: 'warning'
|
||||
})
|
||||
await fetchDeleteExpressCompany(row.id)
|
||||
ElMessage.success('删除成功')
|
||||
await loadData()
|
||||
@@ -301,7 +331,9 @@
|
||||
async function handleBatchDelete() {
|
||||
const ids = selectedRows.value.map((r) => r.id)
|
||||
if (!ids.length) return
|
||||
await ElMessageBox.confirm(`确定删除选中的 ${ids.length} 个快递公司吗?`, '提示', { type: 'warning' })
|
||||
await ElMessageBox.confirm(`确定删除选中的 ${ids.length} 个快递公司吗?`, '提示', {
|
||||
type: 'warning'
|
||||
})
|
||||
await withFullscreenLoading(async () => {
|
||||
for (const id of ids) {
|
||||
await fetchDeleteExpressCompany(id)
|
||||
@@ -331,6 +363,5 @@
|
||||
:deep(.el-form-item) {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
}
|
||||
</style>
|
||||
@@ -18,7 +18,12 @@
|
||||
<el-input v-model="filterForm.keyword" placeholder="搜索原因/类型" clearable />
|
||||
</el-form-item>
|
||||
<el-form-item label="状态">
|
||||
<el-select v-model="filterForm.status" placeholder="全部" clearable style="width: 120px">
|
||||
<el-select
|
||||
v-model="filterForm.status"
|
||||
placeholder="全部"
|
||||
clearable
|
||||
style="width: 120px"
|
||||
>
|
||||
<el-option label="草稿" value="draft" />
|
||||
<el-option label="审批中" value="pending" />
|
||||
<el-option label="已通过" value="approved" />
|
||||
@@ -29,8 +34,12 @@
|
||||
<div class="filter-actions-row">
|
||||
<span class="filter-hint">按原因、类型模糊查询。</span>
|
||||
<div class="filter-actions-end">
|
||||
<el-button class="btn-ghost" @click="handleReset"><el-icon><RefreshLeft /></el-icon>重置</el-button>
|
||||
<el-button class="btn-main" type="primary" @click="handleSearch"><el-icon><Search /></el-icon>搜索</el-button>
|
||||
<el-button class="btn-ghost" @click="handleReset"
|
||||
><el-icon><RefreshLeft /></el-icon>重置</el-button
|
||||
>
|
||||
<el-button class="btn-main" type="primary" @click="handleSearch"
|
||||
><el-icon><Search /></el-icon>搜索</el-button
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</el-form>
|
||||
@@ -55,23 +64,73 @@
|
||||
@selection-change="handleSelectionChange"
|
||||
>
|
||||
<el-table-column type="selection" min-width="55" align="center" header-align="center" />
|
||||
<el-table-column prop="leaveTypeLabel" label="请假类型" min-width="100" align="center" header-align="center" />
|
||||
<el-table-column prop="startDate" label="开始日期" min-width="120" align="center" header-align="center" />
|
||||
<el-table-column prop="endDate" label="结束日期" min-width="120" align="center" header-align="center" />
|
||||
<el-table-column prop="days" label="天数" min-width="80" align="center" header-align="center" />
|
||||
<el-table-column
|
||||
prop="leaveTypeLabel"
|
||||
label="请假类型"
|
||||
min-width="100"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="startDate"
|
||||
label="开始日期"
|
||||
min-width="120"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="endDate"
|
||||
label="结束日期"
|
||||
min-width="120"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="days"
|
||||
label="天数"
|
||||
min-width="80"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column prop="reason" label="请假原因" min-width="180" show-overflow-tooltip />
|
||||
<el-table-column prop="status" label="状态" min-width="100" align="center" header-align="center">
|
||||
<el-table-column
|
||||
prop="status"
|
||||
label="状态"
|
||||
min-width="100"
|
||||
align="center"
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="statusTagType(row.status)" size="small">{{ statusLabel(row.status) }}</el-tag>
|
||||
<el-tag :type="statusTagType(row.status)" size="small">{{
|
||||
statusLabel(row.status)
|
||||
}}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="createTime" label="创建时间" min-width="180" align="center" header-align="center" />
|
||||
<el-table-column label="操作" fixed="right" min-width="260" align="center" header-align="center">
|
||||
<el-table-column
|
||||
prop="createTime"
|
||||
label="创建时间"
|
||||
min-width="180"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
label="操作"
|
||||
fixed="right"
|
||||
min-width="260"
|
||||
align="center"
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<template v-if="row.status === 'draft' || row.status === 'rejected'">
|
||||
<el-button type="primary" link size="small" @click="handleEdit(row)">编辑</el-button>
|
||||
<el-button type="success" link size="small" @click="handleSubmit(row.id)">提交审批</el-button>
|
||||
<el-button type="danger" link size="small" @click="handleDelete(row.id)">删除</el-button>
|
||||
<el-button type="primary" link size="small" @click="handleEdit(row)"
|
||||
>编辑</el-button
|
||||
>
|
||||
<el-button type="success" link size="small" @click="handleSubmit(row.id)"
|
||||
>提交审批</el-button
|
||||
>
|
||||
<el-button type="danger" link size="small" @click="handleDelete(row.id)"
|
||||
>删除</el-button
|
||||
>
|
||||
</template>
|
||||
<template v-else-if="row.status === 'pending'">
|
||||
<el-tag type="warning" size="small">审批中</el-tag>
|
||||
@@ -154,7 +213,12 @@
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="请假原因" class="form-col-span-2">
|
||||
<el-input v-model="formData.reason" type="textarea" :rows="3" placeholder="请输入请假原因" />
|
||||
<el-input
|
||||
v-model="formData.reason"
|
||||
type="textarea"
|
||||
:rows="3"
|
||||
placeholder="请输入请假原因"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="备注" class="form-col-span-2">
|
||||
<el-input v-model="formData.remark" placeholder="选填" />
|
||||
@@ -176,144 +240,146 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { Close, Check, RefreshLeft, Search } from '@element-plus/icons-vue'
|
||||
import { leaveStatusLabel, leaveStatusTagType } from '@/utils/status-tag-registry'
|
||||
import {
|
||||
fetchLeaveList,
|
||||
fetchCreateLeave,
|
||||
fetchUpdateLeave,
|
||||
fetchDeleteLeave,
|
||||
fetchSubmitLeave
|
||||
} from '@/api/leave'
|
||||
import { Close, Check, RefreshLeft, Search } from '@element-plus/icons-vue'
|
||||
import { leaveStatusLabel, leaveStatusTagType } from '@/utils/status-tag-registry'
|
||||
import {
|
||||
fetchLeaveList,
|
||||
fetchCreateLeave,
|
||||
fetchUpdateLeave,
|
||||
fetchDeleteLeave,
|
||||
fetchSubmitLeave
|
||||
} from '@/api/leave'
|
||||
|
||||
defineOptions({ name: 'LeaveRequest' })
|
||||
defineOptions({ name: 'LeaveRequest' })
|
||||
|
||||
const loading = ref(false)
|
||||
const tableData = ref<Api.Leave.LeaveItem[]>([])
|
||||
const selectedRows = ref<Api.Leave.LeaveItem[]>([])
|
||||
const pagination = reactive({ page: 1, pageSize: 10, total: 0 })
|
||||
const loading = ref(false)
|
||||
const tableData = ref<Api.Leave.LeaveItem[]>([])
|
||||
const selectedRows = ref<Api.Leave.LeaveItem[]>([])
|
||||
const pagination = reactive({ page: 1, pageSize: 10, total: 0 })
|
||||
|
||||
const filterForm = reactive({ keyword: '', status: '' })
|
||||
const filterForm = reactive({ keyword: '', status: '' })
|
||||
|
||||
const dialogVisible = ref(false)
|
||||
const isEdit = ref(false)
|
||||
const editingId = ref('')
|
||||
const submitting = ref(false)
|
||||
const dialogVisible = ref(false)
|
||||
const isEdit = ref(false)
|
||||
const editingId = ref('')
|
||||
const submitting = ref(false)
|
||||
|
||||
const formData = reactive({
|
||||
leaveType: 'personal',
|
||||
startDate: '',
|
||||
endDate: '',
|
||||
days: 1,
|
||||
reason: '',
|
||||
remark: ''
|
||||
})
|
||||
const formData = reactive({
|
||||
leaveType: 'personal',
|
||||
startDate: '',
|
||||
endDate: '',
|
||||
days: 1,
|
||||
reason: '',
|
||||
remark: ''
|
||||
})
|
||||
|
||||
const statusLabel = leaveStatusLabel
|
||||
const statusTagType = leaveStatusTagType
|
||||
const statusLabel = leaveStatusLabel
|
||||
const statusTagType = leaveStatusTagType
|
||||
|
||||
function resetForm() {
|
||||
formData.leaveType = 'personal'
|
||||
formData.startDate = ''
|
||||
formData.endDate = ''
|
||||
formData.days = 1
|
||||
formData.reason = ''
|
||||
formData.remark = ''
|
||||
}
|
||||
|
||||
function handleSelectionChange(rows: Api.Leave.LeaveItem[]) {
|
||||
selectedRows.value = rows
|
||||
}
|
||||
|
||||
async function loadData() {
|
||||
loading.value = true
|
||||
try {
|
||||
const res = await fetchLeaveList({
|
||||
page: pagination.page,
|
||||
pageSize: pagination.pageSize,
|
||||
keyword: filterForm.keyword || undefined,
|
||||
status: filterForm.status || undefined
|
||||
})
|
||||
tableData.value = res.list || []
|
||||
pagination.total = res.total || 0
|
||||
} catch {
|
||||
// error shown by http util
|
||||
} finally {
|
||||
loading.value = false
|
||||
function resetForm() {
|
||||
formData.leaveType = 'personal'
|
||||
formData.startDate = ''
|
||||
formData.endDate = ''
|
||||
formData.days = 1
|
||||
formData.reason = ''
|
||||
formData.remark = ''
|
||||
}
|
||||
}
|
||||
|
||||
function handleSearch() {
|
||||
pagination.page = 1
|
||||
loadData()
|
||||
}
|
||||
|
||||
function handleReset() {
|
||||
filterForm.keyword = ''
|
||||
filterForm.status = ''
|
||||
pagination.page = 1
|
||||
loadData()
|
||||
}
|
||||
|
||||
function handleAdd() {
|
||||
isEdit.value = false
|
||||
editingId.value = ''
|
||||
resetForm()
|
||||
dialogVisible.value = true
|
||||
}
|
||||
|
||||
function handleEdit(row: Api.Leave.LeaveItem) {
|
||||
isEdit.value = true
|
||||
editingId.value = row.id
|
||||
formData.leaveType = row.leaveType
|
||||
formData.startDate = row.startDate
|
||||
formData.endDate = row.endDate
|
||||
formData.days = row.days
|
||||
formData.reason = row.reason
|
||||
formData.remark = row.remark || ''
|
||||
dialogVisible.value = true
|
||||
}
|
||||
|
||||
async function handleSave() {
|
||||
if (!formData.leaveType || !formData.startDate || !formData.endDate || !formData.days) {
|
||||
ElMessage.warning('请填写必填项')
|
||||
return
|
||||
function handleSelectionChange(rows: Api.Leave.LeaveItem[]) {
|
||||
selectedRows.value = rows
|
||||
}
|
||||
submitting.value = true
|
||||
try {
|
||||
if (isEdit.value) {
|
||||
await fetchUpdateLeave(editingId.value, { ...formData })
|
||||
} else {
|
||||
await fetchCreateLeave({ ...formData })
|
||||
|
||||
async function loadData() {
|
||||
loading.value = true
|
||||
try {
|
||||
const res = await fetchLeaveList({
|
||||
page: pagination.page,
|
||||
pageSize: pagination.pageSize,
|
||||
keyword: filterForm.keyword || undefined,
|
||||
status: filterForm.status || undefined
|
||||
})
|
||||
tableData.value = res.list || []
|
||||
pagination.total = res.total || 0
|
||||
} catch {
|
||||
// error shown by http util
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
dialogVisible.value = false
|
||||
loadData()
|
||||
} catch {
|
||||
// error shown by http util
|
||||
} finally {
|
||||
submitting.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function handleSubmit(id: string) {
|
||||
try {
|
||||
await ElMessageBox.confirm('确定提交审批?提交后将进入审批流程。', '提示', { type: 'warning' })
|
||||
await fetchSubmitLeave(id)
|
||||
function handleSearch() {
|
||||
pagination.page = 1
|
||||
loadData()
|
||||
} catch {
|
||||
// cancelled or error
|
||||
}
|
||||
}
|
||||
|
||||
async function handleDelete(id: string) {
|
||||
try {
|
||||
await ElMessageBox.confirm('确定删除此请假申请?', '提示', { type: 'warning' })
|
||||
await fetchDeleteLeave(id)
|
||||
function handleReset() {
|
||||
filterForm.keyword = ''
|
||||
filterForm.status = ''
|
||||
pagination.page = 1
|
||||
loadData()
|
||||
} catch {
|
||||
// cancelled or error
|
||||
}
|
||||
}
|
||||
|
||||
function handleAdd() {
|
||||
isEdit.value = false
|
||||
editingId.value = ''
|
||||
resetForm()
|
||||
dialogVisible.value = true
|
||||
}
|
||||
|
||||
function handleEdit(row: Api.Leave.LeaveItem) {
|
||||
isEdit.value = true
|
||||
editingId.value = row.id
|
||||
formData.leaveType = row.leaveType
|
||||
formData.startDate = row.startDate
|
||||
formData.endDate = row.endDate
|
||||
formData.days = row.days
|
||||
formData.reason = row.reason
|
||||
formData.remark = row.remark || ''
|
||||
dialogVisible.value = true
|
||||
}
|
||||
|
||||
async function handleSave() {
|
||||
if (!formData.leaveType || !formData.startDate || !formData.endDate || !formData.days) {
|
||||
ElMessage.warning('请填写必填项')
|
||||
return
|
||||
}
|
||||
submitting.value = true
|
||||
try {
|
||||
if (isEdit.value) {
|
||||
await fetchUpdateLeave(editingId.value, { ...formData })
|
||||
} else {
|
||||
await fetchCreateLeave({ ...formData })
|
||||
}
|
||||
dialogVisible.value = false
|
||||
loadData()
|
||||
} catch {
|
||||
// error shown by http util
|
||||
} finally {
|
||||
submitting.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function handleSubmit(id: string) {
|
||||
try {
|
||||
await ElMessageBox.confirm('确定提交审批?提交后将进入审批流程。', '提示', {
|
||||
type: 'warning'
|
||||
})
|
||||
await fetchSubmitLeave(id)
|
||||
loadData()
|
||||
} catch {
|
||||
// cancelled or error
|
||||
}
|
||||
}
|
||||
|
||||
async function handleDelete(id: string) {
|
||||
try {
|
||||
await ElMessageBox.confirm('确定删除此请假申请?', '提示', { type: 'warning' })
|
||||
await fetchDeleteLeave(id)
|
||||
loadData()
|
||||
} catch {
|
||||
// cancelled or error
|
||||
}
|
||||
}
|
||||
// 输入检索条件时自动搜索(防抖 300ms)
|
||||
watchDebounced(
|
||||
filterForm,
|
||||
@@ -324,5 +390,5 @@ async function handleDelete(id: string) {
|
||||
{ debounce: 300, deep: true }
|
||||
)
|
||||
|
||||
onMounted(() => loadData())
|
||||
onMounted(() => loadData())
|
||||
</script>
|
||||
@@ -21,8 +21,12 @@
|
||||
<div class="filter-actions-row">
|
||||
<span class="filter-hint">按菜单名称模糊查询。</span>
|
||||
<div class="filter-actions-end">
|
||||
<el-button class="btn-ghost" @click="handleReset"><el-icon><RefreshLeft /></el-icon>重置</el-button>
|
||||
<el-button class="btn-main" type="primary" @click="handleSearch"><el-icon><Search /></el-icon>搜索</el-button>
|
||||
<el-button class="btn-ghost" @click="handleReset"
|
||||
><el-icon><RefreshLeft /></el-icon>重置</el-button
|
||||
>
|
||||
<el-button class="btn-main" type="primary" @click="handleSearch"
|
||||
><el-icon><Search /></el-icon>搜索</el-button
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</el-form>
|
||||
@@ -56,12 +60,7 @@
|
||||
@selection-change="handleSelectionChange"
|
||||
@row-dblclick="handleView"
|
||||
>
|
||||
<el-table-column
|
||||
type="selection"
|
||||
min-width="55"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column type="selection" min-width="55" align="center" header-align="center" />
|
||||
<el-table-column
|
||||
prop="menuName"
|
||||
label="菜单名称"
|
||||
@@ -77,10 +76,7 @@
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<el-tag
|
||||
:type="menuTypeTagMap[row.menuType]?.type || 'info'"
|
||||
size="small"
|
||||
>
|
||||
<el-tag :type="menuTypeTagMap[row.menuType]?.type || 'info'" size="small">
|
||||
{{ menuTypeTagMap[row.menuType]?.label || row.menuType }}
|
||||
</el-tag>
|
||||
</template>
|
||||
@@ -144,13 +140,13 @@
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
label="操作"
|
||||
min-width="400"
|
||||
fixed="right"
|
||||
align="center"
|
||||
header-align="center"
|
||||
>
|
||||
<el-table-column
|
||||
label="操作"
|
||||
min-width="400"
|
||||
fixed="right"
|
||||
align="center"
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<el-button class="btn-op-item" @click="handleView(row)">
|
||||
<el-icon><View /></el-icon>查看
|
||||
@@ -230,12 +226,7 @@
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="备注" style="grid-column: 1 / -1">
|
||||
<el-input
|
||||
v-model="formData.remark"
|
||||
type="textarea"
|
||||
:rows="2"
|
||||
placeholder="可选"
|
||||
/>
|
||||
<el-input v-model="formData.remark" type="textarea" :rows="2" placeholder="可选" />
|
||||
</el-form-item>
|
||||
</div>
|
||||
</el-form>
|
||||
@@ -244,12 +235,7 @@
|
||||
<el-icon><Close /></el-icon>
|
||||
取消
|
||||
</el-button>
|
||||
<el-button
|
||||
class="btn-main"
|
||||
type="primary"
|
||||
@click="handleSubmit"
|
||||
:loading="submitLoading"
|
||||
>
|
||||
<el-button class="btn-main" type="primary" @click="handleSubmit" :loading="submitLoading">
|
||||
<el-icon><Check /></el-icon>
|
||||
确定
|
||||
</el-button>
|
||||
@@ -270,14 +256,17 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, computed, onMounted, nextTick } from 'vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { Plus, Close, Check, View, Menu as MenuIcon, RefreshLeft, Search } from '@element-plus/icons-vue'
|
||||
import SystemViewDialog from '@/components/business/system-view-dialog/index.vue'
|
||||
import {
|
||||
fetchMenuTree,
|
||||
fetchCreateMenu,
|
||||
fetchUpdateMenu,
|
||||
fetchDeleteMenu
|
||||
} from '@/api/admin'
|
||||
Plus,
|
||||
Close,
|
||||
Check,
|
||||
View,
|
||||
Menu as MenuIcon,
|
||||
RefreshLeft,
|
||||
Search
|
||||
} from '@element-plus/icons-vue'
|
||||
import SystemViewDialog from '@/components/business/system-view-dialog/index.vue'
|
||||
import { fetchMenuTree, fetchCreateMenu, fetchUpdateMenu, fetchDeleteMenu } from '@/api/admin'
|
||||
|
||||
defineOptions({ name: 'SystemMenu' })
|
||||
|
||||
@@ -343,13 +332,25 @@
|
||||
title: '基本信息',
|
||||
fields: [
|
||||
{ label: '菜单名称', value: viewRow.value?.menuName },
|
||||
{ label: '菜单类型', value: menuTypeTagMap[viewRow.value?.menuType || '']?.label || viewRow.value?.menuType, tagType: 'primary' },
|
||||
{
|
||||
label: '菜单类型',
|
||||
value: menuTypeTagMap[viewRow.value?.menuType || '']?.label || viewRow.value?.menuType,
|
||||
tagType: 'primary'
|
||||
},
|
||||
{ label: '路由路径', value: viewRow.value?.path },
|
||||
{ label: '组件路径', value: viewRow.value?.component },
|
||||
{ label: '权限标识', value: viewRow.value?.perms },
|
||||
{ label: '是否外链', value: viewRow.value?.isFrame === 1 ? '否' : '是' },
|
||||
{ label: '显示状态', value: viewRow.value?.visible === '1' ? '显示' : '隐藏', tagType: viewRow.value?.visible === '1' ? 'success' : 'info' },
|
||||
{ label: '状态', value: viewRow.value?.status === '1' ? '正常' : '停用', tagType: viewRow.value?.status === '1' ? 'success' : 'info' },
|
||||
{
|
||||
label: '显示状态',
|
||||
value: viewRow.value?.visible === '1' ? '显示' : '隐藏',
|
||||
tagType: viewRow.value?.visible === '1' ? 'success' : 'info'
|
||||
},
|
||||
{
|
||||
label: '状态',
|
||||
value: viewRow.value?.status === '1' ? '正常' : '停用',
|
||||
tagType: viewRow.value?.status === '1' ? 'success' : 'info'
|
||||
},
|
||||
{ label: '备注', value: viewRow.value?.remark, span: true }
|
||||
]
|
||||
}
|
||||
@@ -544,6 +545,5 @@
|
||||
:deep(.el-form-item) {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
}
|
||||
</style>
|
||||
+1227
-1017
File diff suppressed because it is too large.
Load diff
Loaded 100 of 1503 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user