提交
This commit is contained in:
1 parent
a79aee2614
commit
427bcb1551
274 files changed
+59416
-38342
No files matched your search
@@ -55,10 +55,6 @@
|
||||
<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>
|
||||
<el-button class="btn-ghost" @click="openBatchGenerate">
|
||||
<el-icon><DocumentAdd /></el-icon>
|
||||
批量生成
|
||||
@@ -71,6 +67,10 @@
|
||||
<el-icon><Promotion /></el-icon>
|
||||
批量下发
|
||||
</el-button>
|
||||
<el-button class="btn-ghost" @click="handleAdd">
|
||||
<el-icon><Plus /></el-icon>
|
||||
样本手工编码
|
||||
</el-button>
|
||||
<el-button
|
||||
class="btn-ghost"
|
||||
:disabled="selectedRows.length === 0"
|
||||
|
||||
@@ -7,220 +7,256 @@
|
||||
<h2>细胞样本基础信息维护</h2>
|
||||
</div>
|
||||
|
||||
<el-tabs v-model="activeTab" class="panel-tabs">
|
||||
<el-tabs v-model="activeTab" class="panel-pill-tabs">
|
||||
<el-tab-pane label="细胞样本类型" name="type">
|
||||
<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>
|
||||
<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>
|
||||
</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="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 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>
|
||||
<el-button
|
||||
class="btn-ghost"
|
||||
type="danger"
|
||||
:disabled="selectedRows.length === 0"
|
||||
@click="handleBatchDelete"
|
||||
>
|
||||
批量删除
|
||||
</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>
|
||||
<el-button class="btn-main" type="primary" @click="handleAdd">
|
||||
<el-icon><Plus /></el-icon>
|
||||
新增类型
|
||||
</el-button>
|
||||
<el-button
|
||||
class="btn-ghost"
|
||||
type="danger"
|
||||
:disabled="selectedRows.length === 0"
|
||||
@click="handleBatchDelete"
|
||||
>
|
||||
批量删除
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="list-body list-wrap">
|
||||
<el-table
|
||||
class="list-table"
|
||||
:data="tableData"
|
||||
v-loading="loading"
|
||||
stripe
|
||||
row-key="id"
|
||||
@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="(index) => (pagination.page - 1) * pagination.pageSize + index + 1"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="typeCode"
|
||||
label="类型编码"
|
||||
min-width="140"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="typeName"
|
||||
label="类型名称"
|
||||
min-width="180"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="storageYears"
|
||||
label="存储年限(年)"
|
||||
min-width="120"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="storagePrice"
|
||||
label="存储价格(元)"
|
||||
min-width="120"
|
||||
align="center"
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
{{ row.storagePrice != null ? row.storagePrice : '—' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="createTime"
|
||||
label="创建时间"
|
||||
min-width="180"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
label="操作"
|
||||
min-width="120"
|
||||
fixed="right"
|
||||
align="center"
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<div style="display: flex; justify-content: center; gap: 8px">
|
||||
<el-button class="btn-op-item" @click="handleView(row)">
|
||||
<el-icon><View /></el-icon>查看
|
||||
</el-button>
|
||||
<el-dropdown trigger="hover" @command="(cmd: string) => handleDropdownCommand(cmd, row)">
|
||||
<el-button class="btn-op-item">
|
||||
<el-icon><More /></el-icon>
|
||||
<div class="list-body list-wrap">
|
||||
<div v-loading="loading" class="sample-type-card-grid">
|
||||
<div
|
||||
v-for="row in tableData"
|
||||
:key="row.id"
|
||||
class="sample-type-card"
|
||||
:class="{ 'is-selected': isRowSelected(row.id) }"
|
||||
@dblclick="handleView(row)"
|
||||
>
|
||||
<div class="sample-type-card__select" @click.stop>
|
||||
<el-checkbox
|
||||
:model-value="isRowSelected(row.id)"
|
||||
@change="(val) => toggleRowSelection(row, !!val)"
|
||||
/>
|
||||
</div>
|
||||
<div class="sample-type-card__media">
|
||||
<el-image
|
||||
v-if="row.typeLogo"
|
||||
:src="typeLogoUrl(row.typeLogo)"
|
||||
fit="cover"
|
||||
class="sample-type-card__logo"
|
||||
/>
|
||||
<div v-else class="sample-type-card__logo-placeholder">
|
||||
<el-icon><Collection /></el-icon>
|
||||
<span>暂无Logo</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="sample-type-card__body">
|
||||
<div class="sample-type-card__title" :title="row.typeName">{{
|
||||
row.typeName
|
||||
}}</div>
|
||||
<div class="sample-type-card__code">{{ row.typeCode }}</div>
|
||||
<div class="sample-type-card__meta">
|
||||
<span v-if="row.storageYears != null">存储 {{ row.storageYears }} 年</span>
|
||||
<span v-if="row.storagePrice != null"> · ¥{{ row.storagePrice }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="sample-type-card__footer">
|
||||
<el-button class="btn-op-item" @click.stop="handleView(row)">
|
||||
<el-icon><View /></el-icon>查看
|
||||
</el-button>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item command="edit">
|
||||
<el-icon><Edit /></el-icon>编辑
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item command="delete" divided>
|
||||
<el-icon><Delete /></el-icon>删除
|
||||
</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
<el-dropdown trigger="hover" @command="(cmd) => handleDropdownCommand(cmd, row)">
|
||||
<el-button class="btn-op-item" @click.stop>
|
||||
<el-icon><More /></el-icon>
|
||||
</el-button>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item command="edit">
|
||||
<el-icon><Edit /></el-icon>编辑
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item command="delete" divided>
|
||||
<el-icon><Delete /></el-icon>删除
|
||||
</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
</div>
|
||||
</div>
|
||||
</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="(val) => ((pagination.page = 1), (pagination.pageSize = val), loadData())"
|
||||
@current-change="(val) => ((pagination.page = val), loadData())"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<el-dialog
|
||||
v-model="dialogVisible"
|
||||
:title="dialogTitle"
|
||||
width="640px"
|
||||
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"
|
||||
scroll-to-error
|
||||
:scroll-into-view-options="{ behavior: 'smooth', block: 'center' }"
|
||||
>
|
||||
<div class="form-grid-2">
|
||||
<el-form-item label="类型编码" prop="typeCode">
|
||||
<el-input v-model="formData.typeCode" placeholder="租户内唯一" :disabled="isEdit" />
|
||||
</el-form-item>
|
||||
<el-form-item label="类型名称" prop="typeName">
|
||||
<el-input v-model="formData.typeName" placeholder="必填" />
|
||||
</el-form-item>
|
||||
<el-form-item label="存储年限(年)">
|
||||
<el-input
|
||||
type="number"
|
||||
step="any"
|
||||
v-model="formData.storageYears"
|
||||
:min="0"
|
||||
:max="200"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="存储价格(元)">
|
||||
<el-input
|
||||
type="number"
|
||||
step="any"
|
||||
v-model="formData.storagePrice"
|
||||
:min="0"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="备注" class="form-col-span-2">
|
||||
<el-input v-model="formData.remark" type="textarea" :rows="2" placeholder="可选" />
|
||||
</el-form-item>
|
||||
</div>
|
||||
<el-empty
|
||||
v-if="!loading && !tableData.length"
|
||||
description="暂无细胞样本类型"
|
||||
:image-size="80"
|
||||
/>
|
||||
</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" @click="handleSubmit" :loading="submitLoading">
|
||||
<el-icon><Check /></el-icon>
|
||||
确定
|
||||
</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<SystemViewDialog
|
||||
v-model:visible="viewVisible"
|
||||
:title="viewRow?.typeName || '类型详情'"
|
||||
:icon="Collection"
|
||||
:code="viewRow?.typeCode"
|
||||
code-label="类型编码"
|
||||
:sections="viewSections"
|
||||
/>
|
||||
<el-dialog
|
||||
v-model="dialogVisible"
|
||||
:title="dialogTitle"
|
||||
width="640px"
|
||||
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"
|
||||
scroll-to-error
|
||||
:scroll-into-view-options="{ behavior: 'smooth', block: 'center' }"
|
||||
>
|
||||
<div class="form-grid-2">
|
||||
<el-form-item label="类型编码" prop="typeCode">
|
||||
<el-input
|
||||
v-model="formData.typeCode"
|
||||
placeholder="租户内唯一"
|
||||
:disabled="isEdit"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="类型名称" prop="typeName">
|
||||
<el-input v-model="formData.typeName" placeholder="必填" />
|
||||
</el-form-item>
|
||||
<el-form-item label="类型Logo" style="grid-column: 1 / -1">
|
||||
<div class="sample-type-logo-row">
|
||||
<el-image
|
||||
v-if="formData.typeLogo"
|
||||
:src="typeLogoUrl(formData.typeLogo)"
|
||||
fit="cover"
|
||||
class="sample-type-logo-preview"
|
||||
/>
|
||||
<div v-else class="sample-type-logo-placeholder">暂无</div>
|
||||
<el-upload
|
||||
ref="typeLogoUploadRef"
|
||||
accept="image/*"
|
||||
:show-file-list="false"
|
||||
:auto-upload="false"
|
||||
:limit="1"
|
||||
:on-change="handleTypeLogoChange"
|
||||
:on-exceed="handleTypeLogoExceed"
|
||||
>
|
||||
<el-button type="primary" plain>
|
||||
<el-icon><Upload /></el-icon>上传Logo
|
||||
</el-button>
|
||||
</el-upload>
|
||||
<el-button
|
||||
v-if="formData.typeLogo"
|
||||
class="btn-ghost"
|
||||
@click="formData.typeLogo = ''"
|
||||
>
|
||||
<el-icon><Delete /></el-icon>移除
|
||||
</el-button>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="存储年限(年)">
|
||||
<el-input
|
||||
type="number"
|
||||
step="any"
|
||||
v-model="formData.storageYears"
|
||||
:min="0"
|
||||
:max="200"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="存储价格(元)">
|
||||
<el-input
|
||||
type="number"
|
||||
step="any"
|
||||
v-model="formData.storagePrice"
|
||||
:min="0"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item prop="orderNum">
|
||||
<template #label>排序<span class="form-label-tip">数值越小越靠前</span></template>
|
||||
<el-input-number
|
||||
v-model="formData.orderNum"
|
||||
:min="0"
|
||||
:max="9999"
|
||||
:step="1"
|
||||
controls-position="right"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="备注" class="form-col-span-2">
|
||||
<el-input
|
||||
v-model="formData.remark"
|
||||
type="textarea"
|
||||
:rows="2"
|
||||
placeholder="可选"
|
||||
/>
|
||||
</el-form-item>
|
||||
</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"
|
||||
@click="handleSubmit"
|
||||
:loading="submitLoading"
|
||||
>
|
||||
<el-icon><Check /></el-icon>
|
||||
确定
|
||||
</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<SystemViewDialog
|
||||
v-model:visible="viewVisible"
|
||||
:title="viewRow?.typeName || '类型详情'"
|
||||
:icon="Collection"
|
||||
:code="viewRow?.typeCode"
|
||||
code-label="类型编码"
|
||||
:sections="viewSections"
|
||||
>
|
||||
<template v-if="viewRow?.typeLogo" #type-logo>
|
||||
<dl class="sample-type-view-kv-grid">
|
||||
<div class="sample-type-view-kv">
|
||||
<dt>Logo</dt>
|
||||
<dd>
|
||||
<el-image
|
||||
:src="typeLogoUrl(viewRow.typeLogo)"
|
||||
fit="cover"
|
||||
class="sample-type-view-logo"
|
||||
:preview-src-list="[typeLogoUrl(viewRow.typeLogo)]"
|
||||
/>
|
||||
</dd>
|
||||
</div>
|
||||
</dl>
|
||||
</template>
|
||||
</SystemViewDialog>
|
||||
</el-tab-pane>
|
||||
|
||||
<el-tab-pane label="样本编码" name="sample-code" lazy>
|
||||
@@ -244,8 +280,10 @@
|
||||
Search,
|
||||
More,
|
||||
Edit,
|
||||
Delete
|
||||
Delete,
|
||||
Upload
|
||||
} from '@element-plus/icons-vue'
|
||||
import type { UploadFile } from 'element-plus'
|
||||
import { withFullscreenLoading, fullscreenLoadingText } from '@/utils/fullscreen-loading'
|
||||
import {
|
||||
fetchCellSampleTypeList,
|
||||
@@ -253,6 +291,7 @@
|
||||
fetchUpdateCellSampleType,
|
||||
fetchDeleteCellSampleType
|
||||
} from '@/api/admin'
|
||||
import { fetchUploadAttachment } from '@/api/business'
|
||||
import SystemViewDialog from '@/components/business/system-view-dialog/index.vue'
|
||||
import SampleCodeTab from './SampleCodeTab.vue'
|
||||
|
||||
@@ -260,11 +299,48 @@
|
||||
|
||||
const route = useRoute()
|
||||
const activeTab = ref('type')
|
||||
const typeLogoUploadRef = ref()
|
||||
const minioBaseUrl = (import.meta.env.VITE_MINIO_BASE_URL || '').replace(/\/$/, '')
|
||||
|
||||
function typeLogoUrl(url: string) {
|
||||
if (!url) return ''
|
||||
if (url.startsWith('http://') || url.startsWith('https://') || url.startsWith('/')) return url
|
||||
return `${minioBaseUrl}/${url}`
|
||||
}
|
||||
|
||||
async function uploadTypeLogoFile(file: File) {
|
||||
try {
|
||||
const fd = new FormData()
|
||||
fd.append('folder', 'cell-sample-type')
|
||||
fd.append('file', file)
|
||||
const res = await fetchUploadAttachment(fd)
|
||||
formData.typeLogo = res.fileUrl || ''
|
||||
ElMessage.success('Logo上传成功')
|
||||
} catch (e) {
|
||||
console.error('样本类型Logo上传失败:', e)
|
||||
ElMessage.error('Logo上传失败')
|
||||
} finally {
|
||||
typeLogoUploadRef.value?.clearFiles()
|
||||
}
|
||||
}
|
||||
|
||||
const handleTypeLogoChange = async (uploadFile: UploadFile) => {
|
||||
const file: any = (uploadFile as any).raw
|
||||
if (!file) return
|
||||
const status = (uploadFile as any).status
|
||||
if (status && status !== 'ready') return
|
||||
await uploadTypeLogoFile(file)
|
||||
}
|
||||
|
||||
const handleTypeLogoExceed = (files: File[]) => {
|
||||
const file = files[0]
|
||||
if (file) uploadTypeLogoFile(file)
|
||||
}
|
||||
|
||||
const searchForm = reactive<{ keyword: string }>({ keyword: '' })
|
||||
const tableData = ref<Api.BizCellSampleType.CellSampleTypeItem[]>([])
|
||||
const selectedRows = ref<Api.BizCellSampleType.CellSampleTypeItem[]>([])
|
||||
const loading = ref(false)
|
||||
const pagination = reactive({ page: 1, pageSize: 10, total: 0 })
|
||||
const dialogVisible = ref(false)
|
||||
const dialogTitle = ref('新增类型')
|
||||
const isEdit = ref(false)
|
||||
@@ -272,7 +348,6 @@
|
||||
const formRef = ref()
|
||||
const editingId = ref<string | null>(null)
|
||||
|
||||
// ---- 查看弹窗 ----
|
||||
const viewVisible = ref(false)
|
||||
const viewRow = ref<Api.BizCellSampleType.CellSampleTypeItem | null>(null)
|
||||
const viewSections = computed(() => {
|
||||
@@ -286,12 +361,34 @@
|
||||
{ label: '类型名称', value: row.typeName },
|
||||
{ label: '存储年限(年)', value: row.storageYears },
|
||||
{ label: '存储价格(元)', value: row.storagePrice },
|
||||
{ label: '排序', value: row.orderNum },
|
||||
{ label: '创建时间', value: row.createTime },
|
||||
{ label: '备注', value: row.remark, span: true }
|
||||
]
|
||||
}
|
||||
},
|
||||
row.typeLogo
|
||||
? {
|
||||
title: '类型Logo',
|
||||
fields: [],
|
||||
slot: 'type-logo'
|
||||
}
|
||||
: {
|
||||
title: '类型Logo',
|
||||
fields: [{ label: 'Logo', value: '无' }]
|
||||
}
|
||||
]
|
||||
})
|
||||
|
||||
const isRowSelected = (id: string) => selectedRows.value.some((r) => r.id === id)
|
||||
|
||||
const toggleRowSelection = (row: Api.BizCellSampleType.CellSampleTypeItem, checked: boolean) => {
|
||||
if (checked) {
|
||||
if (!isRowSelected(row.id)) selectedRows.value = [...selectedRows.value, row]
|
||||
return
|
||||
}
|
||||
selectedRows.value = selectedRows.value.filter((r) => r.id !== row.id)
|
||||
}
|
||||
|
||||
function handleView(row: Api.BizCellSampleType.CellSampleTypeItem) {
|
||||
viewRow.value = row
|
||||
viewVisible.value = true
|
||||
@@ -300,6 +397,7 @@
|
||||
const formData = reactive({
|
||||
typeCode: '',
|
||||
typeName: '',
|
||||
typeLogo: '',
|
||||
storageYears: null as number | null,
|
||||
storagePrice: null as number | null,
|
||||
orderNum: 0,
|
||||
@@ -311,39 +409,35 @@
|
||||
typeName: [{ required: true, message: '请输入类型名称', trigger: 'blur' }]
|
||||
}
|
||||
|
||||
const handleSelectionChange = (rows: Api.BizCellSampleType.CellSampleTypeItem[]) => {
|
||||
selectedRows.value = rows
|
||||
}
|
||||
|
||||
async function loadData() {
|
||||
loading.value = true
|
||||
try {
|
||||
const res = await fetchCellSampleTypeList({
|
||||
page: pagination.page,
|
||||
pageSize: pagination.pageSize,
|
||||
page: 1,
|
||||
pageSize: 100,
|
||||
keyword: searchForm.keyword || undefined
|
||||
})
|
||||
tableData.value = res.list || []
|
||||
pagination.total = res.total ?? 0
|
||||
const validIds = new Set(tableData.value.map((r) => r.id))
|
||||
selectedRows.value = selectedRows.value.filter((r) => validIds.has(r.id))
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function handleSearch() {
|
||||
pagination.page = 1
|
||||
loadData()
|
||||
}
|
||||
|
||||
function handleReset() {
|
||||
searchForm.keyword = ''
|
||||
pagination.page = 1
|
||||
loadData()
|
||||
}
|
||||
|
||||
function resetForm() {
|
||||
formData.typeCode = ''
|
||||
formData.typeName = ''
|
||||
formData.typeLogo = ''
|
||||
formData.storageYears = null
|
||||
formData.storagePrice = null
|
||||
formData.orderNum = 0
|
||||
@@ -365,6 +459,7 @@
|
||||
editingId.value = row.id
|
||||
formData.typeCode = row.typeCode || ''
|
||||
formData.typeName = row.typeName || ''
|
||||
formData.typeLogo = row.typeLogo || ''
|
||||
formData.storageYears = row.storageYears ?? null
|
||||
formData.storagePrice = row.storagePrice ?? null
|
||||
formData.orderNum = row.orderNum ?? 0
|
||||
@@ -380,6 +475,7 @@
|
||||
const payload = {
|
||||
typeCode: formData.typeCode.trim(),
|
||||
typeName: formData.typeName.trim(),
|
||||
typeLogo: formData.typeLogo?.trim() ?? '',
|
||||
storageYears: formData.storageYears,
|
||||
storagePrice: formData.storagePrice,
|
||||
orderNum: formData.orderNum,
|
||||
@@ -388,6 +484,7 @@
|
||||
if (isEdit.value && editingId.value) {
|
||||
await fetchUpdateCellSampleType(editingId.value, {
|
||||
typeName: payload.typeName,
|
||||
typeLogo: payload.typeLogo,
|
||||
storageYears: payload.storageYears,
|
||||
storagePrice: payload.storagePrice,
|
||||
orderNum: payload.orderNum,
|
||||
@@ -408,8 +505,12 @@
|
||||
|
||||
function handleDropdownCommand(command: string, row: any) {
|
||||
switch (command) {
|
||||
case 'edit': handleEdit(row); break
|
||||
case 'delete': handleDelete(row); break
|
||||
case 'edit':
|
||||
handleEdit(row)
|
||||
break
|
||||
case 'delete':
|
||||
handleDelete(row)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
@@ -433,11 +534,10 @@
|
||||
await loadData()
|
||||
}, fullscreenLoadingText.batchDelete)
|
||||
}
|
||||
// 输入检索条件时自动搜索(防抖 300ms)
|
||||
|
||||
watchDebounced(
|
||||
searchForm,
|
||||
() => {
|
||||
pagination.page = 1
|
||||
loadData()
|
||||
},
|
||||
{ debounce: 300, deep: true }
|
||||
@@ -458,4 +558,180 @@
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.sample-type-logo-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.sample-type-logo-preview {
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
border-radius: 8px;
|
||||
border: 1px solid var(--mint-border, #e2e8f0);
|
||||
}
|
||||
|
||||
.sample-type-logo-placeholder {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
border-radius: 8px;
|
||||
border: 1px dashed var(--mint-border, #e2e8f0);
|
||||
color: var(--mint-text-muted, #94a3b8);
|
||||
font-size: 12px;
|
||||
background: #f8fafc;
|
||||
}
|
||||
|
||||
.sample-type-card-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
|
||||
gap: 16px;
|
||||
min-height: 120px;
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.sample-type-card {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background: #fff;
|
||||
border: 1px solid var(--mint-border, #e2e8f0);
|
||||
border-radius: 12px;
|
||||
overflow: hidden;
|
||||
transition:
|
||||
border-color 0.2s,
|
||||
box-shadow 0.2s;
|
||||
|
||||
&:hover {
|
||||
border-color: #a8d4c4;
|
||||
box-shadow: 0 2px 12px rgba(4, 120, 87, 0.1);
|
||||
}
|
||||
|
||||
&.is-selected {
|
||||
border-color: var(--mint-primary, #10b981);
|
||||
box-shadow: 0 0 0 1px rgba(16, 185, 129, 0.25);
|
||||
}
|
||||
}
|
||||
|
||||
.sample-type-card__select {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
left: 10px;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.sample-type-card__media {
|
||||
width: 100%;
|
||||
height: 160px;
|
||||
flex-shrink: 0;
|
||||
background: #f1f5f9;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.sample-type-card__logo {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 160px;
|
||||
|
||||
:deep(.el-image__inner),
|
||||
:deep(img) {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
}
|
||||
|
||||
.sample-type-card__logo-placeholder {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 6px;
|
||||
width: 100%;
|
||||
height: 160px;
|
||||
color: var(--mint-text-muted, #94a3b8);
|
||||
font-size: 12px;
|
||||
background: #f1f5f9;
|
||||
|
||||
.el-icon {
|
||||
font-size: 28px;
|
||||
}
|
||||
}
|
||||
|
||||
.sample-type-card__body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
flex: 1;
|
||||
padding: 10px 16px 8px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.sample-type-card__title {
|
||||
font-size: 15px;
|
||||
font-weight: 600;
|
||||
color: var(--mint-text, #0f172a);
|
||||
line-height: 1.4;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.sample-type-card__code {
|
||||
font-size: 12px;
|
||||
color: var(--mint-text-muted, #64748b);
|
||||
}
|
||||
|
||||
.sample-type-card__meta {
|
||||
margin-top: 2px;
|
||||
font-size: 12px;
|
||||
color: var(--mint-text-muted, #94a3b8);
|
||||
}
|
||||
|
||||
.sample-type-card__footer {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
gap: 8px;
|
||||
padding: 10px 16px 14px;
|
||||
border-top: 1px solid var(--mint-border, #e2e8f0);
|
||||
}
|
||||
|
||||
.sample-type-view-kv-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: 12px 20px;
|
||||
}
|
||||
|
||||
.sample-type-view-kv {
|
||||
display: grid;
|
||||
grid-template-columns: 96px 1fr;
|
||||
gap: 8px 12px;
|
||||
font-size: 13px;
|
||||
align-items: start;
|
||||
|
||||
dt {
|
||||
margin: 0;
|
||||
color: #64748b;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
dd {
|
||||
margin: 0;
|
||||
color: #1e293b;
|
||||
word-break: break-word;
|
||||
}
|
||||
}
|
||||
|
||||
.sample-type-view-logo {
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
border-radius: 8px;
|
||||
border: 1px solid #e2e8f0;
|
||||
display: block;
|
||||
}
|
||||
</style>
|
||||
@@ -7,7 +7,7 @@
|
||||
<h2>细胞采集基础信息维护</h2>
|
||||
</div>
|
||||
|
||||
<el-tabs v-model="activeTab" class="panel-tabs">
|
||||
<el-tabs v-model="activeTab" class="panel-pill-tabs">
|
||||
<el-tab-pane label="采集地区及医院" name="region">
|
||||
<div class="filter-block">
|
||||
<el-form :model="searchForm" class="filter-form" @submit.prevent>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<div class="page-container">
|
||||
<section class="panel">
|
||||
<div class="panel-top">
|
||||
<h2>保险公司列表</h2>
|
||||
<h2>保险公司及险种</h2>
|
||||
</div>
|
||||
|
||||
<div class="filter-block">
|
||||
@@ -45,99 +45,118 @@
|
||||
</div>
|
||||
|
||||
<div class="list-body list-wrap">
|
||||
<el-table
|
||||
class="list-table"
|
||||
:data="tableData"
|
||||
v-loading="loading"
|
||||
stripe
|
||||
row-key="id"
|
||||
@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) => (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"
|
||||
<div v-loading="loading" class="insurance-company-card-grid">
|
||||
<div
|
||||
v-for="row in tableData"
|
||||
:key="row.id"
|
||||
class="insurance-company-card"
|
||||
:class="{ 'is-selected': isRowSelected(row.id) }"
|
||||
@dblclick="handleView(row)"
|
||||
>
|
||||
<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
|
||||
label="操作"
|
||||
min-width="160"
|
||||
fixed="right"
|
||||
align="center"
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<div style="display: flex; justify-content: center; gap: 8px">
|
||||
<el-button class="btn-op-item primary" @click="openItemDialog(row)">
|
||||
<el-icon><Setting /></el-icon>险种价格配置
|
||||
</el-button>
|
||||
<el-dropdown trigger="hover" @command="(cmd: string) => handleDropdownCommand(cmd, row)">
|
||||
<el-button class="btn-op-item">
|
||||
<el-icon><More /></el-icon>
|
||||
</el-button>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item command="view">
|
||||
<el-icon><View /></el-icon>查看
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item command="edit">
|
||||
<el-icon><Edit /></el-icon>编辑
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item command="delete" divided>
|
||||
<el-icon><Delete /></el-icon>删除
|
||||
</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
<div class="insurance-company-card__select" @click.stop>
|
||||
<el-checkbox
|
||||
:model-value="isRowSelected(row.id)"
|
||||
@change="(val) => toggleRowSelection(row, !!val)"
|
||||
/>
|
||||
</div>
|
||||
<div class="insurance-company-card__header">
|
||||
<div class="insurance-company-card__media">
|
||||
<el-image
|
||||
v-if="row.companyLogo"
|
||||
:src="companyLogoUrl(row.companyLogo)"
|
||||
fit="cover"
|
||||
class="insurance-company-card__logo"
|
||||
/>
|
||||
<div v-else class="insurance-company-card__logo-placeholder">
|
||||
<el-icon><OfficeBuilding /></el-icon>
|
||||
<span>暂无Logo</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<div class="insurance-company-card__summary">
|
||||
<div class="insurance-company-card__title-row">
|
||||
<div class="insurance-company-card__title" :title="row.companyName">
|
||||
{{ row.companyName }}
|
||||
</div>
|
||||
<el-tag :type="row.status == '1' ? 'success' : 'info'" size="small">
|
||||
{{ row.status == '1' ? '正常' : '停用' }}
|
||||
</el-tag>
|
||||
</div>
|
||||
<div class="insurance-company-card__address" :title="row.companyAddress || ''">
|
||||
{{ row.companyAddress || ' ' }}
|
||||
</div>
|
||||
<div class="insurance-company-card__contact">
|
||||
<template v-if="row.contactName || row.contactPhone">
|
||||
<span v-if="row.contactName">{{ row.contactName }}</span>
|
||||
<span v-if="row.contactName && row.contactPhone"> · </span>
|
||||
<span v-if="row.contactPhone">{{ row.contactPhone }}</span>
|
||||
</template>
|
||||
<span v-else> </span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="insurance-company-card__projects">
|
||||
<div class="insurance-company-card__projects-head">
|
||||
<span class="insurance-company-card__projects-title">险种</span>
|
||||
<span class="insurance-company-card__projects-count">
|
||||
{{ (row.projectPrices || []).length }} 项
|
||||
</span>
|
||||
</div>
|
||||
<div class="insurance-company-card__project-body">
|
||||
<div
|
||||
v-for="item in (row.projectPrices || []).slice(0, 1)"
|
||||
:key="item.id"
|
||||
class="insurance-company-card__project-item"
|
||||
>
|
||||
<span class="insurance-company-card__project-name" :title="item.projectName">
|
||||
{{ item.projectName }}
|
||||
</span>
|
||||
<span class="insurance-company-card__project-fee">
|
||||
{{
|
||||
item.projectFee != null ? '¥' + formatMoneyDisplay(item.projectFee) : '—'
|
||||
}}
|
||||
</span>
|
||||
<el-tag :type="item.status == '1' ? 'success' : 'info'" size="small">
|
||||
{{ item.status == '1' ? '正常' : '停用' }}
|
||||
</el-tag>
|
||||
</div>
|
||||
<div
|
||||
v-if="!(row.projectPrices || []).length"
|
||||
class="insurance-company-card__project-empty"
|
||||
>
|
||||
暂无险种,请点击下方配置
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="insurance-company-card__footer">
|
||||
<el-button class="btn-op-item primary" @click.stop="openItemDialog(row)">
|
||||
<el-icon><Setting /></el-icon>险种价格配置
|
||||
</el-button>
|
||||
<el-dropdown trigger="hover" @command="(cmd) => handleDropdownCommand(cmd, row)">
|
||||
<el-button class="btn-op-item" @click.stop>
|
||||
<el-icon><More /></el-icon>
|
||||
</el-button>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item command="view">
|
||||
<el-icon><View /></el-icon>查看
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item command="edit">
|
||||
<el-icon><Edit /></el-icon>编辑
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item command="delete" divided>
|
||||
<el-icon><Delete /></el-icon>删除
|
||||
</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<el-empty
|
||||
v-if="!loading && !tableData.length"
|
||||
description="暂无保险公司数据"
|
||||
:image-size="80"
|
||||
/>
|
||||
<div class="pagination list-pager-bar">
|
||||
<el-pagination
|
||||
:current-page="pagination.page"
|
||||
@@ -172,6 +191,37 @@
|
||||
<el-form-item label="保险公司名称" prop="companyName">
|
||||
<el-input v-model="companyForm.companyName" placeholder="请输入保险公司名称" />
|
||||
</el-form-item>
|
||||
<el-form-item label="公司Logo" class="form-col-span-2">
|
||||
<div class="company-logo-row">
|
||||
<el-image
|
||||
v-if="companyForm.companyLogo"
|
||||
:src="companyLogoUrl(companyForm.companyLogo)"
|
||||
fit="cover"
|
||||
class="company-logo-preview"
|
||||
/>
|
||||
<div v-else class="company-logo-placeholder">暂无</div>
|
||||
<el-upload
|
||||
ref="companyLogoUploadRef"
|
||||
accept="image/*"
|
||||
:show-file-list="false"
|
||||
:auto-upload="false"
|
||||
:limit="1"
|
||||
:on-change="handleCompanyLogoChange"
|
||||
:on-exceed="handleCompanyLogoExceed"
|
||||
>
|
||||
<el-button type="primary" plain>
|
||||
<el-icon><Upload /></el-icon>上传Logo
|
||||
</el-button>
|
||||
</el-upload>
|
||||
<el-button
|
||||
v-if="companyForm.companyLogo"
|
||||
class="btn-ghost"
|
||||
@click="companyForm.companyLogo = ''"
|
||||
>
|
||||
<el-icon><Delete /></el-icon>移除
|
||||
</el-button>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="公司地址">
|
||||
<el-input v-model="companyForm.companyAddress" placeholder="请输入公司地址" />
|
||||
</el-form-item>
|
||||
@@ -402,7 +452,23 @@
|
||||
:icon="OfficeBuilding"
|
||||
:meta="viewCompanyName"
|
||||
:sections="viewSections"
|
||||
/>
|
||||
>
|
||||
<template v-if="viewCompanyLogo" #company-logo>
|
||||
<dl class="company-view-kv-grid">
|
||||
<div class="company-view-kv">
|
||||
<dt>Logo</dt>
|
||||
<dd>
|
||||
<el-image
|
||||
:src="companyLogoUrl(viewCompanyLogo)"
|
||||
fit="cover"
|
||||
class="company-view-logo"
|
||||
:preview-src-list="[companyLogoUrl(viewCompanyLogo)]"
|
||||
/>
|
||||
</dd>
|
||||
</div>
|
||||
</dl>
|
||||
</template>
|
||||
</SystemViewDialog>
|
||||
</section>
|
||||
</div>
|
||||
</template>
|
||||
@@ -419,8 +485,10 @@
|
||||
Edit,
|
||||
Delete,
|
||||
More,
|
||||
OfficeBuilding
|
||||
OfficeBuilding,
|
||||
Upload
|
||||
} from '@element-plus/icons-vue'
|
||||
import type { UploadFile } from 'element-plus'
|
||||
import SystemViewDialog from '@/components/business/system-view-dialog/index.vue'
|
||||
import type { ViewSection } from '@/components/business/system-view-dialog/index.vue'
|
||||
import {
|
||||
@@ -437,12 +505,62 @@
|
||||
fetchUpdateInsuranceProjectPrice,
|
||||
fetchDeleteInsuranceProjectPrice
|
||||
} from '@/api/admin'
|
||||
import { fetchUploadAttachment } from '@/api/business'
|
||||
import { withFullscreenLoading, fullscreenLoadingText } from '@/utils/fullscreen-loading'
|
||||
import AmountInput from '@/components/common/amount-input/index.vue'
|
||||
import { formatMoneyDisplay } from '@/utils/money-format'
|
||||
|
||||
defineOptions({ name: 'BasicInfoInsuranceCompany' })
|
||||
|
||||
const companyLogoUploadRef = ref()
|
||||
const minioBaseUrl = (import.meta.env.VITE_MINIO_BASE_URL || '').replace(/\/$/, '')
|
||||
|
||||
function companyLogoUrl(url: string) {
|
||||
if (!url) return ''
|
||||
if (url.startsWith('http://') || url.startsWith('https://') || url.startsWith('/')) return url
|
||||
return `${minioBaseUrl}/${url}`
|
||||
}
|
||||
|
||||
async function uploadCompanyLogoFile(file: File) {
|
||||
try {
|
||||
const fd = new FormData()
|
||||
fd.append('folder', 'insurance-company')
|
||||
fd.append('file', file)
|
||||
const res = await fetchUploadAttachment(fd)
|
||||
companyForm.companyLogo = res.fileUrl || ''
|
||||
ElMessage.success('Logo上传成功')
|
||||
} catch (e) {
|
||||
console.error('保险公司Logo上传失败:', e)
|
||||
ElMessage.error('Logo上传失败')
|
||||
} finally {
|
||||
companyLogoUploadRef.value?.clearFiles()
|
||||
}
|
||||
}
|
||||
|
||||
const handleCompanyLogoChange = async (uploadFile: UploadFile) => {
|
||||
const file: any = (uploadFile as any).raw
|
||||
if (!file) return
|
||||
const status = (uploadFile as any).status
|
||||
if (status && status !== 'ready') return
|
||||
await uploadCompanyLogoFile(file)
|
||||
}
|
||||
|
||||
const handleCompanyLogoExceed = (files: File[]) => {
|
||||
const file = files[0]
|
||||
if (file) uploadCompanyLogoFile(file)
|
||||
}
|
||||
|
||||
const isRowSelected = (id: string) => selectedRows.value.some((r) => r.id === id)
|
||||
|
||||
const toggleRowSelection = (row: any, checked: boolean) => {
|
||||
if (checked) {
|
||||
if (!isRowSelected(row.id)) selectedRows.value = [...selectedRows.value, row]
|
||||
return
|
||||
}
|
||||
selectedRows.value = selectedRows.value.filter((r) => r.id !== row.id)
|
||||
}
|
||||
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 搜索 & 主表
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -463,6 +581,7 @@
|
||||
const editingId = ref<string | null>(null)
|
||||
const companyForm = reactive({
|
||||
companyName: '',
|
||||
companyLogo: '',
|
||||
companyAddress: '',
|
||||
contactName: '',
|
||||
contactPhone: '',
|
||||
@@ -512,6 +631,7 @@
|
||||
// ---------------------------------------------------------------------------
|
||||
const viewVisible = ref(false)
|
||||
const viewCompanyName = ref('')
|
||||
const viewCompanyLogo = ref('')
|
||||
const viewSections = ref<ViewSection[]>([])
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -528,6 +648,8 @@
|
||||
})
|
||||
tableData.value = (res as any)?.list || []
|
||||
pagination.total = (res as any)?.total ?? 0
|
||||
const validIds = new Set(tableData.value.map((r) => r.id))
|
||||
selectedRows.value = selectedRows.value.filter((r) => validIds.has(r.id))
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
@@ -545,15 +667,12 @@
|
||||
loadData()
|
||||
}
|
||||
|
||||
function handleSelectionChange(rows: any[]) {
|
||||
selectedRows.value = rows
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 保险公司弹窗操作
|
||||
// ---------------------------------------------------------------------------
|
||||
function resetCompanyForm() {
|
||||
companyForm.companyName = ''
|
||||
companyForm.companyLogo = ''
|
||||
companyForm.companyAddress = ''
|
||||
companyForm.contactName = ''
|
||||
companyForm.contactPhone = ''
|
||||
@@ -578,6 +697,7 @@
|
||||
companyDialogTitle.value = '编辑保险公司'
|
||||
editingId.value = row.id
|
||||
companyForm.companyName = row.companyName || ''
|
||||
companyForm.companyLogo = row.companyLogo || ''
|
||||
companyForm.companyAddress = row.companyAddress || ''
|
||||
companyForm.contactName = row.contactName || ''
|
||||
companyForm.contactPhone = row.contactPhone || ''
|
||||
@@ -615,6 +735,7 @@
|
||||
try {
|
||||
const payload = {
|
||||
companyName: companyForm.companyName.trim(),
|
||||
companyLogo: companyForm.companyLogo?.trim() ?? '',
|
||||
companyAddress: companyForm.companyAddress?.trim() || undefined,
|
||||
contactName: companyForm.contactName?.trim() || undefined,
|
||||
contactPhone: companyForm.contactPhone?.trim() || undefined,
|
||||
@@ -693,6 +814,7 @@
|
||||
// ---------------------------------------------------------------------------
|
||||
async function handleView(row: any) {
|
||||
viewCompanyName.value = row.companyName || ''
|
||||
viewCompanyLogo.value = row.companyLogo || ''
|
||||
const sections: ViewSection[] = [
|
||||
{
|
||||
title: '基本信息',
|
||||
@@ -710,6 +832,27 @@
|
||||
]
|
||||
}
|
||||
]
|
||||
if (row.companyLogo) {
|
||||
sections.push({
|
||||
title: '公司Logo',
|
||||
fields: [],
|
||||
slot: 'company-logo'
|
||||
})
|
||||
}
|
||||
const projectPrices = row.projectPrices || []
|
||||
if (projectPrices.length) {
|
||||
sections.push({
|
||||
title: '险种价格',
|
||||
fields: projectPrices.map((item: any) => ({
|
||||
label: item.projectName,
|
||||
value:
|
||||
(item.projectFee != null ? '¥' + formatMoneyDisplay(item.projectFee) : '—') +
|
||||
' · ' +
|
||||
(item.status == '1' ? '正常' : '停用'),
|
||||
tagType: item.status == '1' ? 'success' : 'info'
|
||||
}))
|
||||
})
|
||||
}
|
||||
// 加载开票信息
|
||||
try {
|
||||
const invRes = await fetchInsuranceCompanyInvoices(row.id)
|
||||
@@ -763,6 +906,7 @@
|
||||
currentCompanyId.value = ''
|
||||
currentCompanyName.value = ''
|
||||
itemTable.value = []
|
||||
loadData()
|
||||
}
|
||||
|
||||
function onItemSelectionChange(rows: any[]) {
|
||||
@@ -852,6 +996,275 @@
|
||||
}
|
||||
}
|
||||
|
||||
.company-logo-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.company-logo-preview {
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
border-radius: 8px;
|
||||
border: 1px solid var(--mint-border, #e2e8f0);
|
||||
}
|
||||
|
||||
.company-logo-placeholder {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
border-radius: 8px;
|
||||
border: 1px dashed var(--mint-border, #e2e8f0);
|
||||
color: var(--mint-text-muted, #94a3b8);
|
||||
font-size: 12px;
|
||||
background: #f8fafc;
|
||||
}
|
||||
|
||||
.insurance-company-card-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(360px, 1fr));
|
||||
gap: 16px;
|
||||
min-height: 120px;
|
||||
padding: 16px;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.insurance-company-card {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
min-height: 268px;
|
||||
background: #fff;
|
||||
border: 1px solid var(--mint-border, #e2e8f0);
|
||||
border-radius: 12px;
|
||||
overflow: hidden;
|
||||
transition:
|
||||
border-color 0.2s,
|
||||
box-shadow 0.2s;
|
||||
|
||||
&:hover {
|
||||
border-color: #a8d4c4;
|
||||
box-shadow: 0 2px 12px rgba(4, 120, 87, 0.1);
|
||||
}
|
||||
|
||||
&.is-selected {
|
||||
border-color: var(--mint-primary, #10b981);
|
||||
box-shadow: 0 0 0 1px rgba(16, 185, 129, 0.25);
|
||||
}
|
||||
}
|
||||
|
||||
.insurance-company-card__select {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
left: 10px;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.insurance-company-card__header {
|
||||
display: flex;
|
||||
gap: 14px;
|
||||
min-height: 108px;
|
||||
box-sizing: border-box;
|
||||
padding: 16px 16px 12px 40px;
|
||||
background: #f8fafc;
|
||||
border-bottom: 1px solid var(--mint-border, #e2e8f0);
|
||||
}
|
||||
|
||||
.insurance-company-card__media {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.insurance-company-card__logo {
|
||||
display: block;
|
||||
width: 72px;
|
||||
height: 72px;
|
||||
border-radius: 10px;
|
||||
border: 1px solid var(--mint-border, #e2e8f0);
|
||||
background: #fff;
|
||||
|
||||
:deep(.el-image__inner),
|
||||
:deep(img) {
|
||||
width: 72px;
|
||||
height: 72px;
|
||||
object-fit: cover;
|
||||
}
|
||||
}
|
||||
|
||||
.insurance-company-card__logo-placeholder {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 4px;
|
||||
width: 72px;
|
||||
height: 72px;
|
||||
border-radius: 10px;
|
||||
border: 1px dashed var(--mint-border, #e2e8f0);
|
||||
color: var(--mint-text-muted, #94a3b8);
|
||||
font-size: 11px;
|
||||
background: #fff;
|
||||
|
||||
.el-icon {
|
||||
font-size: 24px;
|
||||
}
|
||||
}
|
||||
|
||||
.insurance-company-card__summary {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.insurance-company-card__title-row {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
gap: 8px;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
.insurance-company-card__title {
|
||||
min-height: 42px;
|
||||
font-size: 15px;
|
||||
font-weight: 600;
|
||||
color: var(--mint-text, #0f172a);
|
||||
line-height: 1.4;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
}
|
||||
|
||||
.insurance-company-card__address,
|
||||
.insurance-company-card__contact {
|
||||
min-height: 18px;
|
||||
font-size: 12px;
|
||||
color: var(--mint-text-muted, #64748b);
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.insurance-company-card__address {
|
||||
margin-bottom: 4px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.insurance-company-card__projects {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-direction: column;
|
||||
min-height: 92px;
|
||||
padding: 12px 16px;
|
||||
}
|
||||
|
||||
.insurance-company-card__projects-head {
|
||||
display: flex;
|
||||
flex-shrink: 0;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
height: 20px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.insurance-company-card__projects-title {
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
color: var(--mint-text, #0f172a);
|
||||
}
|
||||
|
||||
.insurance-company-card__projects-count {
|
||||
font-size: 12px;
|
||||
color: var(--mint-text-muted, #94a3b8);
|
||||
}
|
||||
|
||||
.insurance-company-card__project-body {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
align-items: center;
|
||||
min-height: 40px;
|
||||
}
|
||||
|
||||
.insurance-company-card__project-item {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1fr) auto auto;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
gap: 8px;
|
||||
padding: 8px 10px;
|
||||
border-radius: 8px;
|
||||
background: #f8fafc;
|
||||
}
|
||||
|
||||
.insurance-company-card__project-name {
|
||||
font-size: 13px;
|
||||
color: var(--mint-text, #0f172a);
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.insurance-company-card__project-fee {
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
color: #047857;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.insurance-company-card__project-empty {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
height: 40px;
|
||||
padding: 0;
|
||||
text-align: center;
|
||||
font-size: 12px;
|
||||
color: var(--mint-text-muted, #94a3b8);
|
||||
}
|
||||
|
||||
.insurance-company-card__footer {
|
||||
display: flex;
|
||||
flex-shrink: 0;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
gap: 8px;
|
||||
height: 52px;
|
||||
box-sizing: border-box;
|
||||
padding: 10px 16px 14px;
|
||||
border-top: 1px solid var(--mint-border, #e2e8f0);
|
||||
}
|
||||
|
||||
.company-view-kv-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: 12px 24px;
|
||||
}
|
||||
|
||||
.company-view-kv {
|
||||
dt {
|
||||
margin-bottom: 6px;
|
||||
font-size: 12px;
|
||||
color: var(--mint-text-muted, #64748b);
|
||||
}
|
||||
|
||||
dd {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.company-view-logo {
|
||||
width: 96px;
|
||||
height: 96px;
|
||||
border-radius: 10px;
|
||||
border: 1px solid var(--mint-border, #e2e8f0);
|
||||
}
|
||||
|
||||
.item-dialog-head {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user