提交
This commit is contained in:
1 parent
d08f03ab03
commit
cc843535f5
154 files changed
+17837
-5153
No files matched your search
@@ -46,7 +46,7 @@ export type QcProtocolPaymentSummary = {
|
||||
overallPayStatusTagType?: string
|
||||
installmentCount?: number | null
|
||||
paymentDetails?: QcProtocolPaymentDetailSummary[]
|
||||
/** 第 1 期多笔转账中最后一笔的转账时间(协议列表「首期缴费时间」优先用) */
|
||||
/** 第 1 期多笔转账中最早一笔的转账时间(协议列表「首期缴费时间」优先用) */
|
||||
firstPayTransferTime?: string
|
||||
}
|
||||
|
||||
|
||||
@@ -354,8 +354,8 @@
|
||||
useCommon().refresh()
|
||||
return
|
||||
}
|
||||
// 强制重新加载:先将目标组件从 KeepAlive 缓存中排除
|
||||
store.addKeepAliveExclude(item)
|
||||
// 强制重新加载:先将目标组件从 KeepAlive 缓存中排除(force:标签仍在 opened 中也要排除)
|
||||
store.addKeepAliveExclude(item, { force: true })
|
||||
// 等待 Vue 处理 exclude 变更、清除缓存后再导航
|
||||
nextTick(() => {
|
||||
router.push({
|
||||
|
||||
@@ -46,6 +46,7 @@ import { useCommon } from '@/hooks/core/useCommon'
|
||||
import { useMenuStore } from '@/store/modules/menu'
|
||||
import { useSettingStore } from '@/store/modules/setting'
|
||||
import { getFirstSecondLevelMenuPath, isWorkbenchRoute } from '@/utils/navigation/route'
|
||||
import { shouldAddKeepAliveExclude } from '@/utils/navigation/keep-alive-exclude'
|
||||
|
||||
interface WorktabState {
|
||||
current: Partial<WorkTab>
|
||||
@@ -428,14 +429,23 @@ export const useWorktabStore = defineStore(
|
||||
|
||||
/**
|
||||
* 将指定选项卡添加到 keepAlive 排除列表中
|
||||
* @param options.force 点击已打开标签切换时传 true,即使标签仍在 opened 中也排除缓存以强制重新挂载
|
||||
*/
|
||||
const addKeepAliveExclude = (tab: WorkTab): void => {
|
||||
if (!tab.keepAlive || !tab.name) return
|
||||
// 同 name 多开:仍有其它同名标签时勿排除,否则会清掉其它实例缓存
|
||||
if (opened.value.some((t) => t.name === tab.name)) return
|
||||
const addKeepAliveExclude = (tab: WorkTab, options?: { force?: boolean }): void => {
|
||||
const name = tab.name
|
||||
if (
|
||||
!shouldAddKeepAliveExclude({
|
||||
tabName: name,
|
||||
keepAlive: tab.keepAlive,
|
||||
openedHasSameName: opened.value.some((t) => t.name === name),
|
||||
force: options?.force
|
||||
})
|
||||
) {
|
||||
return
|
||||
}
|
||||
|
||||
if (!keepAliveExclude.value.includes(tab.name)) {
|
||||
keepAliveExclude.value.push(tab.name)
|
||||
if (name && !keepAliveExclude.value.includes(name)) {
|
||||
keepAliveExclude.value.push(name)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -8,3 +8,4 @@
|
||||
export * from './jump'
|
||||
export * from './worktab'
|
||||
export * from './route'
|
||||
export * from './keep-alive-exclude'
|
||||
@@ -0,0 +1,58 @@
|
||||
import { describe, it } from 'node:test'
|
||||
import assert from 'node:assert/strict'
|
||||
import { shouldAddKeepAliveExclude } from './keep-alive-exclude'
|
||||
|
||||
describe('shouldAddKeepAliveExclude', () => {
|
||||
it('returns false when tab has no name or keepAlive is off', () => {
|
||||
assert.equal(
|
||||
shouldAddKeepAliveExclude({
|
||||
tabName: undefined,
|
||||
keepAlive: true,
|
||||
openedHasSameName: false
|
||||
}),
|
||||
false
|
||||
)
|
||||
assert.equal(
|
||||
shouldAddKeepAliveExclude({
|
||||
tabName: 'CellStorageProtocolAudit',
|
||||
keepAlive: false,
|
||||
openedHasSameName: false
|
||||
}),
|
||||
false
|
||||
)
|
||||
})
|
||||
|
||||
it('skips exclude when same-name tab still open (close / worktabMulti guard)', () => {
|
||||
assert.equal(
|
||||
shouldAddKeepAliveExclude({
|
||||
tabName: 'CellStorageProtocolAudit',
|
||||
keepAlive: true,
|
||||
openedHasSameName: true
|
||||
}),
|
||||
false
|
||||
)
|
||||
})
|
||||
|
||||
it('allows exclude when no same-name tab remains open', () => {
|
||||
assert.equal(
|
||||
shouldAddKeepAliveExclude({
|
||||
tabName: 'ProductionPreTransitSample',
|
||||
keepAlive: true,
|
||||
openedHasSameName: false
|
||||
}),
|
||||
true
|
||||
)
|
||||
})
|
||||
|
||||
it('force=true excludes even when the tab is still in opened (tab click reload)', () => {
|
||||
assert.equal(
|
||||
shouldAddKeepAliveExclude({
|
||||
tabName: 'CellStorageProtocolAudit',
|
||||
keepAlive: true,
|
||||
openedHasSameName: true,
|
||||
force: true
|
||||
}),
|
||||
true
|
||||
)
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,17 @@
|
||||
/**
|
||||
* 判断是否应将组件名加入 KeepAlive exclude,以清除缓存并强制重新挂载。
|
||||
*
|
||||
* - 关闭标签:若仍有同名标签(worktabMulti),勿排除,避免清掉其它实例缓存
|
||||
* - 点击已打开标签切换:force=true,即使标签仍在 opened 中也要排除,以强制重新加载
|
||||
*/
|
||||
export function shouldAddKeepAliveExclude(params: {
|
||||
tabName?: string
|
||||
keepAlive?: boolean
|
||||
openedHasSameName: boolean
|
||||
force?: boolean
|
||||
}): boolean {
|
||||
if (!params.keepAlive || !params.tabName) return false
|
||||
if (params.force) return true
|
||||
if (params.openedHasSameName) return false
|
||||
return true
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
<!-- 客户管理 -->
|
||||
<!-- 客户基础信息 -->
|
||||
<!-- eslint-disable vue/no-v-model-argument -->
|
||||
<template>
|
||||
<div class="page-container">
|
||||
|
||||
@@ -27,9 +27,6 @@
|
||||
style="width: 100%"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="协议编号">
|
||||
<el-input v-model="form.protocolNo" placeholder="可选,转正式时可预占" clearable />
|
||||
</el-form-item>
|
||||
<el-form-item label="母亲姓名" required>
|
||||
<el-input v-model="form.motherName" placeholder="请输入母亲姓名" clearable />
|
||||
</el-form-item>
|
||||
@@ -828,7 +825,6 @@
|
||||
try {
|
||||
const payload: Record<string, unknown> = {
|
||||
orderDate: form.orderDate || undefined,
|
||||
protocolNo: form.protocolNo || undefined,
|
||||
motherName: form.motherName,
|
||||
expectedDeliveryDate: form.expectedDeliveryDate || undefined,
|
||||
regionId: form.regionId || undefined,
|
||||
|
||||
@@ -174,15 +174,15 @@
|
||||
<template #default="{ row }">{{ row.motherName || '—' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="protocolNo"
|
||||
label="协议编号"
|
||||
prop="sampleCode"
|
||||
label="样本编码"
|
||||
min-width="140"
|
||||
align="center"
|
||||
header-align="center"
|
||||
show-overflow-tooltip
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<span class="mono">{{ row.protocolNo || '—' }}</span>
|
||||
<span class="mono">{{ row.sampleCode || '—' }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
|
||||
@@ -325,10 +325,10 @@
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="母亲家庭住址" class="form-col-span-2">
|
||||
<el-input v-model="formData.motherAddress" placeholder="请输入" />
|
||||
</el-form-item>
|
||||
</div>
|
||||
<el-form-item label="母亲家庭住址">
|
||||
<el-input v-model="formData.motherAddress" placeholder="请输入" />
|
||||
</el-form-item>
|
||||
</div>
|
||||
|
||||
<!-- 父亲信息 -->
|
||||
@@ -2521,6 +2521,16 @@
|
||||
s.sampleStatus != null && String(s.sampleStatus).trim() !== ''
|
||||
? String(s.sampleStatus).trim()
|
||||
: null,
|
||||
transport: {
|
||||
transportType: transport?.transportType || '',
|
||||
expressCompanyId: transport?.expressCompanyId || '',
|
||||
waybillNo: transport?.waybillNo || '',
|
||||
destSiteId: transport?.destSiteId || '',
|
||||
expressFee: transport?.expressFee ?? null,
|
||||
etaTime: transport?.etaTime || '',
|
||||
carrierName: transport?.carrierName || '',
|
||||
carrierPhone: transport?.carrierPhone || ''
|
||||
},
|
||||
freightId: freight?.id || s.freightId || undefined,
|
||||
freightDraft: freightDraft || undefined
|
||||
}))
|
||||
|
||||
@@ -531,10 +531,22 @@
|
||||
}
|
||||
|
||||
.section-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin: 12px 0 8px;
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
color: var(--mint-text, #0f172a);
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
width: 3px;
|
||||
height: 14px;
|
||||
margin-right: 8px;
|
||||
flex-shrink: 0;
|
||||
border-radius: 2px;
|
||||
background: var(--mint-primary, #047857);
|
||||
}
|
||||
}
|
||||
|
||||
.action-btn--danger {
|
||||
|
||||
@@ -1,22 +1,5 @@
|
||||
<template>
|
||||
<div class="unified-sample-info-page">
|
||||
<div v-if="visibleTabs.length > 1" class="panel-pill-tabs panel-pill-tabs--lg">
|
||||
<div class="panel-pill-tabs__track" role="tablist" aria-label="来源">
|
||||
<button
|
||||
v-for="item in visibleTabs"
|
||||
:key="item.key"
|
||||
type="button"
|
||||
role="tab"
|
||||
class="panel-pill-tabs__btn"
|
||||
:class="{ 'is-active': activeSource === item.key }"
|
||||
:aria-selected="activeSource === item.key"
|
||||
@click="switchSource(item.key)"
|
||||
>
|
||||
{{ item.title }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="filter-block">
|
||||
<el-form
|
||||
:model="searchForm"
|
||||
@@ -497,7 +480,6 @@
|
||||
import DonationViewDialog from '@/views/research-donation/list/DonationViewDialog.vue'
|
||||
import SampleCodeWithSpecialDisplay from '@/components/business/sample-code-with-special-display/index.vue'
|
||||
import SampleInfoQuickRegisterDialog from './SampleInfoQuickRegisterDialog.vue'
|
||||
import { useMenuPerm } from '@/hooks/core/useMenuPerm'
|
||||
import {
|
||||
donationStatusDisplayLabel,
|
||||
donationStatusTagType,
|
||||
@@ -521,23 +503,8 @@
|
||||
defaultSource: 'cell_storage' | 'research_donation'
|
||||
}>()
|
||||
|
||||
const { hasMenuPerm } = useMenuPerm()
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
|
||||
type SourceKey = 'cell_storage' | 'research_donation'
|
||||
|
||||
const allTabs: Array<{ key: SourceKey; title: string; perm: string }> = [
|
||||
{ key: 'cell_storage', title: '细胞存储', perm: 'cell-storage:sample-info:list' },
|
||||
{
|
||||
key: 'research_donation',
|
||||
title: '科研捐赠',
|
||||
perm: 'research-donation:sample-info:list'
|
||||
}
|
||||
]
|
||||
|
||||
const visibleTabs = computed(() => allTabs.filter((t) => hasMenuPerm(t.perm)))
|
||||
const activeSource = ref<SourceKey>(props.defaultSource)
|
||||
/** 客户服务管理仅细胞存储;科研捐赠隐藏路由仍可通过 defaultSource 进入 */
|
||||
const activeSource = computed(() => props.defaultSource)
|
||||
const isCellStorage = computed(() => activeSource.value === 'cell_storage')
|
||||
|
||||
const searchForm = reactive({
|
||||
@@ -600,16 +567,6 @@
|
||||
return donationStatusTagType(String(row.donationStatus || ''))
|
||||
}
|
||||
|
||||
function switchSource(key: SourceKey) {
|
||||
activeSource.value = key
|
||||
protocolViewVisible.value = false
|
||||
donationViewVisible.value = false
|
||||
selectedRows.value = []
|
||||
router.replace({ query: { ...route.query, tab: key } })
|
||||
pagination.page = 1
|
||||
loadData()
|
||||
}
|
||||
|
||||
async function loadData() {
|
||||
loading.value = true
|
||||
try {
|
||||
@@ -731,15 +688,6 @@
|
||||
)
|
||||
|
||||
onMounted(() => {
|
||||
const tab = String(route.query.tab || '')
|
||||
const allowed = visibleTabs.value.map((t) => t.key)
|
||||
if (allowed.includes(tab as SourceKey)) {
|
||||
activeSource.value = tab as SourceKey
|
||||
} else if (allowed.includes(props.defaultSource)) {
|
||||
activeSource.value = props.defaultSource
|
||||
} else if (allowed.length) {
|
||||
activeSource.value = allowed[0]
|
||||
}
|
||||
loadData()
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
{ label: '用户管理', path: '/system/user' },
|
||||
{ label: '商品分类', path: '/system/category' },
|
||||
{ label: '租户管理', path: '/system/tenant' },
|
||||
{ label: '客户管理', path: '/basic-info/customer' }
|
||||
{ label: '客户基础信息', path: '/basic-info/customer' }
|
||||
]
|
||||
|
||||
const go = (path: string) => {
|
||||
|
||||
@@ -1451,6 +1451,16 @@
|
||||
s.sampleStatus != null && String(s.sampleStatus).trim() !== ''
|
||||
? String(s.sampleStatus).trim()
|
||||
: null,
|
||||
transport: {
|
||||
transportType: transport?.transportType || '',
|
||||
expressCompanyId: transport?.expressCompanyId || '',
|
||||
waybillNo: transport?.waybillNo || '',
|
||||
destSiteId: transport?.destSiteId || '',
|
||||
expressFee: transport?.expressFee ?? null,
|
||||
etaTime: transport?.etaTime || '',
|
||||
carrierName: transport?.carrierName || '',
|
||||
carrierPhone: transport?.carrierPhone || ''
|
||||
},
|
||||
freightId: freight?.id || s.freightId || undefined,
|
||||
freightDraft: freightDraft || undefined
|
||||
}))
|
||||
|
||||
@@ -247,10 +247,14 @@
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
v-if="row.freight?.id || row.freight?.transportMode"
|
||||
v-if="
|
||||
row.freight?.id ||
|
||||
row.freight?.transportMode ||
|
||||
row.transport?.etaTime
|
||||
"
|
||||
class="sample-card__section sample-card__section--transport"
|
||||
>
|
||||
<div class="sample-card__section-label">运费信息</div>
|
||||
<div class="sample-card__section-label">运输与运费</div>
|
||||
<div class="data-grid data-grid--4">
|
||||
<div class="data-item">
|
||||
<div class="data-label">运输方式</div>
|
||||
@@ -270,6 +274,10 @@
|
||||
}}</div
|
||||
>
|
||||
</div>
|
||||
<div class="data-item">
|
||||
<div class="data-label">预计到达</div>
|
||||
<div class="data-value">{{ dash(row.transport?.etaTime) }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="sample-card__section sample-card__section--reception">
|
||||
|
||||
Reference in new issue
Block a user