@@ -632,6 +677,8 @@
class="list-body list-wrap protocol-price-list-wrap"
>
-
- 删除
+
+
+ 删除
@@ -861,23 +914,22 @@
append-to-body
:close-on-click-modal="false"
>
-
-
-
-
-
-
-
+
+ 选择项目价格体系
+
+
+
+
-
+
@@ -921,10 +978,11 @@
-
+
确认添加({{ priceSelectorSelected.length }})
@@ -988,6 +1046,7 @@
import ChannelViewDialog from './ChannelViewDialog.vue'
import { withFullscreenLoading, fullscreenLoadingText } from '@/utils/fullscreen-loading'
import { fetchUploadAttachment } from '@/api/business'
+ import { downloadAttachmentFile } from '@/utils/download-attachment'
import {
fetchChannelList,
fetchCreateChannel,
@@ -1119,6 +1178,25 @@
}>
}
+ function resolveChannelAttachments(row: any) {
+ if (Array.isArray(row?.attachmentFiles) && row.attachmentFiles.length) {
+ return row.attachmentFiles.map((a: any, i: number) => ({
+ id: a.id || `att_${i}`,
+ fileName: a.fileName || '',
+ fileUrl: a.fileUrl || '',
+ fileSize: a.fileSize ?? 0,
+ fileType: a.fileType || ''
+ }))
+ }
+ if (Array.isArray(row?.attachments) && row.attachments.length) {
+ return row.attachments.map((a: any) => ({ ...a }))
+ }
+ if (Array.isArray(row?.attachmentList) && row.attachmentList.length) {
+ return row.attachmentList.map((a: any) => ({ ...a }))
+ }
+ return protocolUrlsToAttachments(String(row?.protocolAttachments || ''))
+ }
+
const agreementTypeTagMap: Record = {
kyxy: { label: '框架协议', type: 'primary' },
zsxy: { label: '中试协议', type: 'success' },
@@ -1248,6 +1326,11 @@
const agreementFormStatus = ref('dsx')
const agreementSubmitLoading = ref(false)
const agreementActivateLoading = ref(false)
+ const agreementOperatingId = ref(null)
+ const agreementOperatingAction = ref<'activate' | 'void' | 'delete' | null>(null)
+ const deletingInvoiceId = ref(null)
+ const deletingMailId = ref(null)
+ const priceConfirmLoading = ref(false)
const agreementFormRef = ref()
const agreementFormData = reactive({
agreementType: '' as string,
@@ -1278,6 +1361,48 @@
const priceSelectorTitle = ref('从价格体系添加')
const priceSelectorKeyword = ref('')
const priceSelectorLoading = ref(false)
+ const deletingAgreementPriceId = ref(null)
+ const deletingAgreementPriceAgreementId = ref(null)
+
+ function isAgreementPriceTableLoading(agreementId: string) {
+ return (
+ deletingAgreementPriceAgreementId.value === agreementId &&
+ deletingAgreementPriceId.value != null
+ )
+ }
+
+ function removeAgreementPriceFromMap(agreementId: string, priceId: string) {
+ const list = agreementPricesMap.value[agreementId] || []
+ agreementPricesMap.value = {
+ ...agreementPricesMap.value,
+ [agreementId]: list.filter((item) => item.id !== priceId)
+ }
+ }
+
+ /** 协议项目信息唯一键:项目编码 + 细胞类型 + 规格 */
+ function buildAgreementPriceItemKey(item: Record) {
+ const code = String(item.projectCode || item.projectName || '')
+ .trim()
+ .toLowerCase()
+ const cellType = String(item.cellTypeCode || item.cellTypeName || '')
+ .trim()
+ .toLowerCase()
+ const spec = String(item.specText || '')
+ .trim()
+ .toLowerCase()
+ return `${code}|${cellType}|${spec}`
+ }
+
+ function getAgreementPriceKeySet(agreementId: string | null) {
+ if (!agreementId) return new Set()
+ const list = agreementPricesMap.value[agreementId] || []
+ return new Set(list.map((item: any) => buildAgreementPriceItemKey(item)))
+ }
+
+ function priceSelectorRowSelectable(row: any) {
+ if (!currentAgreementId.value) return true
+ return !getAgreementPriceKeySet(currentAgreementId.value).has(buildAgreementPriceItemKey(row))
+ }
const filteredPriceSelectorList = computed(() => {
const kw = priceSelectorKeyword.value.trim().toLowerCase()
@@ -1380,13 +1505,7 @@
formData.contactName = row.contactName || ''
formData.contactPhone = row.contactPhone || ''
formData.contactEmail = row.contactEmail || ''
- if (Array.isArray(row.attachments) && row.attachments.length) {
- formData.attachments = row.attachments.map((a: any) => ({ ...a }))
- } else if (Array.isArray(row.attachmentList) && row.attachmentList.length) {
- formData.attachments = row.attachmentList.map((a: any) => ({ ...a }))
- } else {
- formData.attachments = protocolUrlsToAttachments(String(row.protocolAttachments || ''))
- }
+ formData.attachments = resolveChannelAttachments(row)
formData.remark = row.remark || ''
activeTab.value = 'basic'
dialogVisible.value = true
@@ -1468,10 +1587,16 @@
// 主表删除
// ---------------------------------------------------------------------------
async function handleDelete(row: any) {
- await ElMessageBox.confirm('确定要删除该渠道吗?关联的子表数据也将清除。', '提示', { type: 'warning' })
- await fetchDeleteChannel(row.id)
- ElMessage.success('删除成功')
- await loadData()
+ try {
+ await ElMessageBox.confirm('确定要删除该渠道吗?关联的子表数据也将清除。', '提示', { type: 'warning' })
+ } catch {
+ return
+ }
+ await withFullscreenLoading(async () => {
+ await fetchDeleteChannel(row.id)
+ ElMessage.success('删除成功')
+ await loadData()
+ }, fullscreenLoadingText.delete)
}
async function handleBatchDelete() {
@@ -1580,15 +1705,25 @@
}
async function handleDeleteInvoice(row: any) {
- await ElMessageBox.confirm('确定删除该开票信息吗?', '提示', { type: 'warning' })
- if (editingId.value) {
+ try {
+ await ElMessageBox.confirm('确定删除该开票信息吗?', '提示', { type: 'warning' })
+ } catch {
+ return
+ }
+ if (!editingId.value) {
+ const idx = invoiceList.value.findIndex((r: any) => r.id === row.id)
+ if (idx !== -1) invoiceList.value.splice(idx, 1)
+ return
+ }
+ deletingInvoiceId.value = row.id
+ try {
await fetchDeleteChannelInvoice(row.id)
ElMessage.success('删除成功')
await loadSubTables(editingId.value)
- } else {
- // 本地模式:从本地数组中移除
- const idx = invoiceList.value.findIndex((r: any) => r.id === row.id)
- if (idx !== -1) invoiceList.value.splice(idx, 1)
+ } catch {
+ /* 全局已提示 */
+ } finally {
+ deletingInvoiceId.value = null
}
}
@@ -1663,14 +1798,25 @@
}
async function handleDeleteMail(row: any) {
- await ElMessageBox.confirm('确定删除该邮寄地址吗?', '提示', { type: 'warning' })
- if (editingId.value) {
+ try {
+ await ElMessageBox.confirm('确定删除该邮寄地址吗?', '提示', { type: 'warning' })
+ } catch {
+ return
+ }
+ if (!editingId.value) {
+ const idx = mailList.value.findIndex((r: any) => r.id === row.id)
+ if (idx !== -1) mailList.value.splice(idx, 1)
+ return
+ }
+ deletingMailId.value = row.id
+ try {
await fetchDeleteChannelMailAddress(row.id)
ElMessage.success('删除成功')
await loadSubTables(editingId.value)
- } else {
- const idx = mailList.value.findIndex((r: any) => r.id === row.id)
- if (idx !== -1) mailList.value.splice(idx, 1)
+ } catch {
+ /* 全局已提示 */
+ } finally {
+ deletingMailId.value = null
}
}
@@ -1786,17 +1932,43 @@
}
async function handleDeleteAgreement(row: any) {
- await ElMessageBox.confirm('确定删除该协议吗?', '提示', { type: 'warning' })
- await fetchDeleteChannelAgreement(row.id)
- ElMessage.success('删除成功')
- await loadAgreementsForDialog(agreementChannelId.value!)
+ try {
+ await ElMessageBox.confirm('确定删除该协议吗?', '提示', { type: 'warning' })
+ } catch {
+ return
+ }
+ agreementOperatingId.value = row.id
+ agreementOperatingAction.value = 'delete'
+ try {
+ await fetchDeleteChannelAgreement(row.id)
+ ElMessage.success('删除成功')
+ await loadAgreementsForDialog(agreementChannelId.value!)
+ } catch {
+ /* 全局已提示 */
+ } finally {
+ agreementOperatingId.value = null
+ agreementOperatingAction.value = null
+ }
}
async function handleActivateAgreement(row: any) {
- await ElMessageBox.confirm('确定将该协议设为生效中吗?', '协议生效', { type: 'info' })
- await fetchActivateChannelAgreement(row.id)
- ElMessage.success('生效成功')
- await loadAgreementsForDialog(agreementChannelId.value!)
+ try {
+ await ElMessageBox.confirm('确定将该协议设为生效中吗?', '协议生效', { type: 'info' })
+ } catch {
+ return
+ }
+ agreementOperatingId.value = row.id
+ agreementOperatingAction.value = 'activate'
+ try {
+ await fetchActivateChannelAgreement(row.id)
+ ElMessage.success('生效成功')
+ await loadAgreementsForDialog(agreementChannelId.value!)
+ } catch {
+ /* 全局已提示 */
+ } finally {
+ agreementOperatingId.value = null
+ agreementOperatingAction.value = null
+ }
}
async function handleActivateAgreementFromForm() {
@@ -1825,10 +1997,23 @@
}
async function handleVoidAgreement(row: any) {
- await ElMessageBox.confirm('确定作废该协议吗?作废后不可恢复。', '提示', { type: 'warning' })
- await fetchVoidChannelAgreement(row.id)
- ElMessage.success('操作成功')
- await loadAgreementsForDialog(agreementChannelId.value!)
+ try {
+ await ElMessageBox.confirm('确定作废该协议吗?作废后不可恢复。', '提示', { type: 'warning' })
+ } catch {
+ return
+ }
+ agreementOperatingId.value = row.id
+ agreementOperatingAction.value = 'void'
+ try {
+ await fetchVoidChannelAgreement(row.id)
+ ElMessage.success('操作成功')
+ await loadAgreementsForDialog(agreementChannelId.value!)
+ } catch {
+ /* 全局已提示 */
+ } finally {
+ agreementOperatingId.value = null
+ agreementOperatingAction.value = null
+ }
}
// ===========================================================================
@@ -1867,10 +2052,25 @@
}
async function handleDeletePriceInline(priceRow: any, agreement: any) {
- await ElMessageBox.confirm('确定删除该价格明细吗?', '提示', { type: 'warning' })
- await fetchDeleteChannelAgreementPrice(priceRow.id)
- ElMessage.success('删除成功')
- await refreshAgreementPrices(agreement.id)
+ try {
+ await ElMessageBox.confirm('确定删除该价格明细吗?', '提示', { type: 'warning' })
+ } catch {
+ return
+ }
+ const agreementId = agreement.id
+ const priceId = priceRow.id
+ deletingAgreementPriceId.value = priceId
+ deletingAgreementPriceAgreementId.value = agreementId
+ try {
+ await fetchDeleteChannelAgreementPrice(priceId)
+ removeAgreementPriceFromMap(agreementId, priceId)
+ ElMessage.success('删除成功')
+ } catch {
+ /* 全局已提示 */
+ } finally {
+ deletingAgreementPriceId.value = null
+ deletingAgreementPriceAgreementId.value = null
+ }
}
function handleAddPriceCommand(command: string) {
@@ -1881,7 +2081,6 @@
} else if (command === 'fromProjectSystem') {
priceSelectorMode.value = 'project'
priceSelectorTitle.value = '从项目价格体系添加'
- selectedProjectSystemId.value = ''
openProjectPriceSelector()
}
}
@@ -1918,6 +2117,11 @@
try {
const res = await fetchProjectSystemList({ page: 1, pageSize: 100 })
projectSystemList.value = (res as any)?.list || []
+ const first = projectSystemList.value[0]
+ if (first?.id) {
+ selectedProjectSystemId.value = first.id
+ await handleProjectSystemChange(first.id)
+ }
} catch {
projectSystemList.value = []
}
@@ -1953,37 +2157,85 @@
async function handleConfirmPriceSelector() {
if (!currentAgreementId.value) return
- const existingList = agreementPricesMap.value[currentAgreementId.value] || []
- const existingIds = new Set(existingList.map((item: any) => String(item.sourcePriceId || '')).filter(Boolean))
+ const existingKeys = getAgreementPriceKeySet(currentAgreementId.value)
+ const batchKeys = new Set()
+ const toAdd: any[] = []
+ let skippedCount = 0
+
for (const row of priceSelectorSelected.value) {
- if (existingIds.has(row.id)) continue
- try {
- await fetchCreateChannelAgreementPrice(currentAgreementId.value, {
- projectName: row.projectName || '',
- projectCode: row.projectCode || '',
- cellTypeCode: row.cellTypeCode || undefined,
- cellTypeName: row.cellTypeName || '',
- specText: row.specText || '',
- originalPrice: row.unitPrice ?? null,
- agreementPrice: null,
- remark: row.remark || undefined
- })
- } catch {
- /* */
+ const key = buildAgreementPriceItemKey(row)
+ if (existingKeys.has(key) || batchKeys.has(key)) {
+ skippedCount += 1
+ continue
}
+ batchKeys.add(key)
+ toAdd.push(row)
}
- priceSelectorVisible.value = false
- if (currentAgreementId.value) {
- await refreshAgreementPrices(currentAgreementId.value)
+
+ if (!toAdd.length) {
+ ElMessage.warning(
+ skippedCount > 0 ? '所选项目均已存在,不可重复添加' : '请选择要添加的项目'
+ )
+ return
+ }
+
+ priceConfirmLoading.value = true
+ let successCount = 0
+ try {
+ for (const row of toAdd) {
+ try {
+ await fetchCreateChannelAgreementPrice(currentAgreementId.value, {
+ projectName: row.projectName || '',
+ projectCode: row.projectCode || '',
+ cellTypeCode: row.cellTypeCode || undefined,
+ cellTypeName: row.cellTypeName || '',
+ specText: row.specText || '',
+ originalPrice: row.unitPrice ?? null,
+ agreementPrice: null,
+ remark: row.remark || undefined
+ })
+ successCount += 1
+ } catch {
+ /* */
+ }
+ }
+
+ if (successCount > 0) {
+ ElMessage.success(`成功添加 ${successCount} 条项目`)
+ }
+ if (skippedCount > 0) {
+ ElMessage.warning(`已跳过 ${skippedCount} 条重复项目`)
+ }
+ priceSelectorVisible.value = false
+ if (currentAgreementId.value) {
+ await refreshAgreementPrices(currentAgreementId.value)
+ }
+ } finally {
+ priceConfirmLoading.value = false
}
}
async function handleDeletePrice(row: any) {
- await ElMessageBox.confirm('确定删除该价格明细吗?', '提示', { type: 'warning' })
- await fetchDeleteChannelAgreementPrice(row.id)
- ElMessage.success('删除成功')
- if (currentAgreementId.value) {
- await refreshAgreementPrices(currentAgreementId.value)
+ try {
+ await ElMessageBox.confirm('确定删除该价格明细吗?', '提示', { type: 'warning' })
+ } catch {
+ return
+ }
+ const agreementId = currentAgreementId.value
+ if (!agreementId) return
+ const priceId = row.id
+ deletingAgreementPriceId.value = priceId
+ deletingAgreementPriceAgreementId.value = agreementId
+ try {
+ await fetchDeleteChannelAgreementPrice(priceId)
+ removeAgreementPriceFromMap(agreementId, priceId)
+ priceList.value = priceList.value.filter((item) => item.id !== priceId)
+ ElMessage.success('删除成功')
+ } catch {
+ /* 全局已提示 */
+ } finally {
+ deletingAgreementPriceId.value = null
+ deletingAgreementPriceAgreementId.value = null
}
}
@@ -2083,15 +2335,7 @@
}
function handleDownloadAttachment(file: any) {
- const url = fullFileUrl(file.fileUrl)
- if (!url) return
- const a = document.createElement('a')
- a.href = url
- a.download = file.fileName || ''
- a.target = '_blank'
- document.body.appendChild(a)
- a.click()
- document.body.removeChild(a)
+ downloadAttachmentFile(file, { fullFileUrl })
}
// ---------------------------------------------------------------------------
@@ -2648,6 +2892,24 @@
}
}
+ .price-selector-system-row {
+ align-items: center;
+
+ .price-selector-system-label {
+ flex-shrink: 0;
+ font-size: 14px;
+ font-weight: 500;
+ color: var(--mint-text, #334155);
+ line-height: 36px;
+ white-space: nowrap;
+ }
+
+ .price-selector-system-select {
+ width: 280px;
+ flex-shrink: 0;
+ }
+ }
+
@media (max-width: 900px) {
.form-grid {
grid-template-columns: repeat(2, 1fr);
diff --git a/web-admin/src/views/pilot-service/checkup-institution/CheckupPackagePanel.vue b/web-admin/src/views/pilot-service/checkup-institution/CheckupPackagePanel.vue
index 0df6aa29..c767a8ae 100644
--- a/web-admin/src/views/pilot-service/checkup-institution/CheckupPackagePanel.vue
+++ b/web-admin/src/views/pilot-service/checkup-institution/CheckupPackagePanel.vue
@@ -227,6 +227,7 @@
fetchDeleteCheckupInstitutionPackage,
fetchCheckupInstitutionProjects
} from '@/api/admin'
+ import { withFullscreenLoading, fullscreenLoadingText } from '@/utils/fullscreen-loading'
const props = defineProps<{
institutionId: string
@@ -374,10 +375,16 @@
}
async function handleDelete(row: any) {
- await ElMessageBox.confirm(`确定删除套餐「${row.packageName}」?`, '提示', { type: 'warning' })
- await fetchDeleteCheckupInstitutionPackage(row.id)
- ElMessage.success('已删除')
- await loadPackages()
+ try {
+ await ElMessageBox.confirm(`确定删除套餐「${row.packageName}」?`, '提示', { type: 'warning' })
+ } catch {
+ return
+ }
+ await withFullscreenLoading(async () => {
+ await fetchDeleteCheckupInstitutionPackage(row.id)
+ ElMessage.success('已删除')
+ await loadPackages()
+ }, fullscreenLoadingText.delete)
}
async function openProjectSelector() {
diff --git a/web-admin/src/views/pilot-service/checkup-institution/index.vue b/web-admin/src/views/pilot-service/checkup-institution/index.vue
index a7f94bf4..78cf7c86 100644
--- a/web-admin/src/views/pilot-service/checkup-institution/index.vue
+++ b/web-admin/src/views/pilot-service/checkup-institution/index.vue
@@ -332,6 +332,7 @@
fetchUpdateCheckupInstitutionProject,
fetchDeleteCheckupInstitutionProject
} from '@/api/admin'
+ import { withFullscreenLoading, fullscreenLoadingText } from '@/utils/fullscreen-loading'
defineOptions({ name: 'PilotCheckupInstitution' })
@@ -555,12 +556,18 @@
}
async function handleDelete(row: any) {
- await ElMessageBox.confirm(`确定删除「${row.institutionName}」?其下属项目价格将一并删除。`, '提示', {
- type: 'warning'
- })
- await fetchDeleteCheckupInstitution(row.id)
- ElMessage.success('已删除')
- loadData()
+ try {
+ await ElMessageBox.confirm(`确定删除「${row.institutionName}」?其下属项目价格将一并删除。`, '提示', {
+ type: 'warning'
+ })
+ } catch {
+ return
+ }
+ await withFullscreenLoading(async () => {
+ await fetchDeleteCheckupInstitution(row.id)
+ ElMessage.success('已删除')
+ await loadData()
+ }, fullscreenLoadingText.delete)
}
// ---------------------------------------------------------------------------
@@ -693,10 +700,16 @@
}
async function handleDeleteItem(row: any) {
- await ElMessageBox.confirm(`确定删除「${row.projectName}」?`, '提示', { type: 'warning' })
- await fetchDeleteCheckupInstitutionProject(row.id)
- ElMessage.success('已删除')
- loadItems()
+ try {
+ await ElMessageBox.confirm(`确定删除「${row.projectName}」?`, '提示', { type: 'warning' })
+ } catch {
+ return
+ }
+ await withFullscreenLoading(async () => {
+ await fetchDeleteCheckupInstitutionProject(row.id)
+ ElMessage.success('已删除')
+ await loadItems()
+ }, fullscreenLoadingText.delete)
}
onMounted(() => loadData())
diff --git a/web-admin/src/views/pilot-service/project-price-system/index.vue b/web-admin/src/views/pilot-service/project-price-system/index.vue
index 41a5bd98..220ac975 100644
--- a/web-admin/src/views/pilot-service/project-price-system/index.vue
+++ b/web-admin/src/views/pilot-service/project-price-system/index.vue
@@ -523,10 +523,16 @@
// --- Delete ---
async function handleDelete(row: any) {
- await ElMessageBox.confirm('确定要删除该体系吗?删除后关联的价格明细也将清除。', '提示', { type: 'warning' })
- await fetchDeleteProjectSystem(row.id)
- ElMessage.success('删除成功')
- await loadData()
+ try {
+ await ElMessageBox.confirm('确定要删除该体系吗?删除后关联的价格明细也将清除。', '提示', { type: 'warning' })
+ } catch {
+ return
+ }
+ await withFullscreenLoading(async () => {
+ await fetchDeleteProjectSystem(row.id)
+ ElMessage.success('删除成功')
+ await loadData()
+ }, fullscreenLoadingText.delete)
}
// --- Batch delete ---
diff --git a/web-admin/src/views/production/consumable-pack/index.vue b/web-admin/src/views/production/consumable-pack/index.vue
index 309593fb..87c0f181 100644
--- a/web-admin/src/views/production/consumable-pack/index.vue
+++ b/web-admin/src/views/production/consumable-pack/index.vue
@@ -61,6 +61,7 @@
stripe
row-key="id"
@selection-change="handleSelectionChange"
+ @row-dblclick="handleView"
>
-
+
+
+ 查看
+
编辑
删除
@@ -145,11 +149,90 @@