Compare commits
No commits in common. "09469888b4bae66a2cc5891d218260db14510378" and "b63306890dde3eda7e163df704b62be3b881f9ad" have entirely different histories.
09469888b4
...
b63306890d
@ -276,120 +276,6 @@ const mockValuationDetails = valuationRecords.map((record) => ({
|
|||||||
...record,
|
...record,
|
||||||
}))
|
}))
|
||||||
|
|
||||||
const mockAppUsers = [
|
|
||||||
{
|
|
||||||
id: 11111111,
|
|
||||||
phone: '15021982682',
|
|
||||||
wechat: 'f1498480844',
|
|
||||||
created_at: '2024-01-15T10:30:00Z',
|
|
||||||
notes: '测试用户1',
|
|
||||||
remaining_count: 1,
|
|
||||||
user_type: '体验用户',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 11111112,
|
|
||||||
phone: '13800138002',
|
|
||||||
wechat: 'wx_limming2024',
|
|
||||||
created_at: '2024-02-20T14:20:00Z',
|
|
||||||
notes: '付费用户',
|
|
||||||
remaining_count: 5,
|
|
||||||
user_type: '付费用户',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 11111113,
|
|
||||||
phone: '13800138003',
|
|
||||||
wechat: null,
|
|
||||||
created_at: '2024-03-10T08:45:00Z',
|
|
||||||
notes: null,
|
|
||||||
remaining_count: 0,
|
|
||||||
user_type: '体验用户',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 11111114,
|
|
||||||
phone: '13800138004',
|
|
||||||
wechat: 'chenjun_vip',
|
|
||||||
created_at: '2024-04-05T11:30:00Z',
|
|
||||||
notes: 'VIP用户',
|
|
||||||
remaining_count: 10,
|
|
||||||
user_type: 'VIP',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 11111115,
|
|
||||||
phone: '13800138005',
|
|
||||||
wechat: 'liuxia888',
|
|
||||||
created_at: '2024-05-12T16:15:00Z',
|
|
||||||
notes: '体验用户',
|
|
||||||
remaining_count: 3,
|
|
||||||
user_type: '体验用户',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 11111116,
|
|
||||||
phone: '13800138006',
|
|
||||||
wechat: null,
|
|
||||||
created_at: '2024-06-18T09:00:00Z',
|
|
||||||
notes: '新注册用户',
|
|
||||||
remaining_count: 2,
|
|
||||||
user_type: '体验用户',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 11111117,
|
|
||||||
phone: '13800138007',
|
|
||||||
wechat: 'zhaolei2024',
|
|
||||||
created_at: '2024-07-22T12:45:00Z',
|
|
||||||
notes: null,
|
|
||||||
remaining_count: 0,
|
|
||||||
user_type: '体验用户',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 11111118,
|
|
||||||
phone: '13800138008',
|
|
||||||
wechat: 'sunmei_user',
|
|
||||||
created_at: '2024-08-30T15:20:00Z',
|
|
||||||
notes: '活跃用户',
|
|
||||||
remaining_count: 7,
|
|
||||||
user_type: 'VIP',
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
const defaultInvoiceHeaders = [
|
|
||||||
{
|
|
||||||
company_name: '成都文创科技有限公司',
|
|
||||||
tax_number: '91510100MA7XYZ1234',
|
|
||||||
register_address: '四川省成都市高新区天府三街666号',
|
|
||||||
register_phone: '028-66666666',
|
|
||||||
bank_name: '招商银行成都分行',
|
|
||||||
bank_account: '6225 6666 8888 0000',
|
|
||||||
email: 'finance@scwenchuang.com',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
company_name: '天府文化发展有限公司',
|
|
||||||
tax_number: '91510100678912345K',
|
|
||||||
register_address: '四川省成都市武侯区科华北路88号',
|
|
||||||
register_phone: '028-12345678',
|
|
||||||
bank_name: '中国工商银行成都分行',
|
|
||||||
bank_account: '6212 8888 0000 9999',
|
|
||||||
email: 'invoice@tfculture.com',
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
const defaultOperationLogs = [
|
|
||||||
{
|
|
||||||
time: '2025-10-31 18:30:30',
|
|
||||||
operator: 'admin',
|
|
||||||
records: ['剩余估值次数:0 -> 1', '类型:付费估值', '备注:新用户'],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
time: '2025-10-31 18:30:30',
|
|
||||||
operator: 'admin',
|
|
||||||
records: ['剩余估值次数:2 -> 1', '类型:付费估值', '备注:退款'],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
time: '2025-10-31 18:30:30',
|
|
||||||
operator: 'admin',
|
|
||||||
records: ['用户备注:111111111111 -> 22222222222222222222'],
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
login: (data) => request.post('/base/access_token', data, { noNeedToken: true }),
|
login: (data) => request.post('/base/access_token', data, { noNeedToken: true }),
|
||||||
getUserInfo: () => request.get('/base/userinfo'),
|
getUserInfo: () => request.get('/base/userinfo'),
|
||||||
@ -431,8 +317,76 @@ export default {
|
|||||||
getAuditLogList: (params = {}) => request.get('/auditlog/list', { params }),
|
getAuditLogList: (params = {}) => request.get('/auditlog/list', { params }),
|
||||||
// app users (客户端用户管理) - 使用现有的后端接口
|
// app users (客户端用户管理) - 使用现有的后端接口
|
||||||
getAppUserList: (params = {}) => {
|
getAppUserList: (params = {}) => {
|
||||||
|
// Mock 数据
|
||||||
|
const mockUsers = [
|
||||||
|
{
|
||||||
|
id: 11111111,
|
||||||
|
phone: '15021982682',
|
||||||
|
wechat: 'f1498480844',
|
||||||
|
created_at: '2024-01-15T10:30:00Z',
|
||||||
|
notes: '测试用户1',
|
||||||
|
remaining_count: 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 11111112,
|
||||||
|
phone: '13800138002',
|
||||||
|
wechat: 'wx_limming2024',
|
||||||
|
created_at: '2024-02-20T14:20:00Z',
|
||||||
|
notes: '付费用户',
|
||||||
|
remaining_count: 5
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 11111113,
|
||||||
|
phone: '13800138003',
|
||||||
|
wechat: null,
|
||||||
|
created_at: '2024-03-10T08:45:00Z',
|
||||||
|
notes: null,
|
||||||
|
remaining_count: 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 11111114,
|
||||||
|
phone: '13800138004',
|
||||||
|
wechat: 'chenjun_vip',
|
||||||
|
created_at: '2024-04-05T11:30:00Z',
|
||||||
|
notes: 'VIP用户',
|
||||||
|
remaining_count: 10
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 11111115,
|
||||||
|
phone: '13800138005',
|
||||||
|
wechat: 'liuxia888',
|
||||||
|
created_at: '2024-05-12T16:15:00Z',
|
||||||
|
notes: '体验用户',
|
||||||
|
remaining_count: 3
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 11111116,
|
||||||
|
phone: '13800138006',
|
||||||
|
wechat: null,
|
||||||
|
created_at: '2024-06-18T09:00:00Z',
|
||||||
|
notes: '新注册用户',
|
||||||
|
remaining_count: 2
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 11111117,
|
||||||
|
phone: '13800138007',
|
||||||
|
wechat: 'zhaolei2024',
|
||||||
|
created_at: '2024-07-22T12:45:00Z',
|
||||||
|
notes: null,
|
||||||
|
remaining_count: 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 11111118,
|
||||||
|
phone: '13800138008',
|
||||||
|
wechat: 'sunmei_user',
|
||||||
|
created_at: '2024-08-30T15:20:00Z',
|
||||||
|
notes: '活跃用户',
|
||||||
|
remaining_count: 7
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
// 模拟分页和搜索
|
// 模拟分页和搜索
|
||||||
let filteredUsers = [...mockAppUsers]
|
let filteredUsers = [...mockUsers]
|
||||||
|
|
||||||
// 手机号搜索
|
// 手机号搜索
|
||||||
if (params.phone) {
|
if (params.phone) {
|
||||||
@ -461,32 +415,13 @@ export default {
|
|||||||
resolve({
|
resolve({
|
||||||
data: paginatedUsers,
|
data: paginatedUsers,
|
||||||
total: filteredUsers.length,
|
total: filteredUsers.length,
|
||||||
page,
|
page: page,
|
||||||
page_size: pageSize,
|
page_size: pageSize
|
||||||
})
|
})
|
||||||
}, 300)
|
}, 300) // 模拟网络延迟
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
getAppUserById: (params = {}) =>
|
getAppUserById: (params = {}) => request.get('/app-user/detail', { params }),
|
||||||
new Promise((resolve) => {
|
|
||||||
const id = Number(params.id)
|
|
||||||
const user = mockAppUsers.find((item) => item.id === id) || {}
|
|
||||||
setTimeout(() => {
|
|
||||||
resolve({
|
|
||||||
baseInfo: {
|
|
||||||
id: user.id,
|
|
||||||
phone: user.phone,
|
|
||||||
wechat: user.wechat,
|
|
||||||
register_time: user.created_at,
|
|
||||||
notes: user.notes,
|
|
||||||
remaining_count: user.remaining_count,
|
|
||||||
user_type: user.user_type || '体验用户',
|
|
||||||
},
|
|
||||||
invoiceHeaders: defaultInvoiceHeaders,
|
|
||||||
operationLogs: defaultOperationLogs,
|
|
||||||
})
|
|
||||||
}, 300)
|
|
||||||
}),
|
|
||||||
createAppUser: (data = {}) => request.post('/app-user/register', data),
|
createAppUser: (data = {}) => request.post('/app-user/register', data),
|
||||||
updateAppUser: (data = {}) => request.post('/app-user/update', data),
|
updateAppUser: (data = {}) => request.post('/app-user/update', data),
|
||||||
deleteAppUser: (params = {}) => request.delete('/app-user/delete', { params }),
|
deleteAppUser: (params = {}) => request.delete('/app-user/delete', { params }),
|
||||||
|
|||||||
@ -1,222 +0,0 @@
|
|||||||
<script setup>
|
|
||||||
import { computed, ref, watch, h } from 'vue'
|
|
||||||
import { NModal, NButton, NTabs, NTabPane, NDataTable, NSpin } from 'naive-ui'
|
|
||||||
|
|
||||||
const props = defineProps({
|
|
||||||
visible: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false,
|
|
||||||
},
|
|
||||||
data: {
|
|
||||||
type: Object,
|
|
||||||
default: () => ({}),
|
|
||||||
},
|
|
||||||
loading: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
const emit = defineEmits(['update:visible'])
|
|
||||||
|
|
||||||
const activeTab = ref('basic')
|
|
||||||
|
|
||||||
watch(
|
|
||||||
() => props.visible,
|
|
||||||
(show) => {
|
|
||||||
if (!show) {
|
|
||||||
activeTab.value = 'basic'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
const baseInfo = computed(() => props.data?.baseInfo || {})
|
|
||||||
const invoiceHeaders = computed(() => props.data?.invoiceHeaders || [])
|
|
||||||
const operationLogs = computed(() => props.data?.operationLogs || [])
|
|
||||||
|
|
||||||
const invoiceColumns = [
|
|
||||||
{ title: '公司名称', key: 'company_name', ellipsis: { tooltip: true } },
|
|
||||||
{ title: '公司税号', key: 'tax_number', ellipsis: { tooltip: true } },
|
|
||||||
{ title: '注册地址', key: 'register_address', ellipsis: { tooltip: true } },
|
|
||||||
{ title: '注册电话', key: 'register_phone' },
|
|
||||||
{ title: '开户银行', key: 'bank_name' },
|
|
||||||
{ title: '银行账号', key: 'bank_account', ellipsis: { tooltip: true } },
|
|
||||||
{ title: '邮箱', key: 'email', ellipsis: { tooltip: true } },
|
|
||||||
]
|
|
||||||
|
|
||||||
const logColumns = [
|
|
||||||
{ title: '操作时间', key: 'time', width: 160 },
|
|
||||||
{ title: '操作人', key: 'operator', width: 100 },
|
|
||||||
{
|
|
||||||
title: '操作记录',
|
|
||||||
key: 'records',
|
|
||||||
render: (row) =>
|
|
||||||
h(
|
|
||||||
'div',
|
|
||||||
{ class: 'log-record' },
|
|
||||||
row.records?.map((item, idx) => h('div', { key: idx }, item))
|
|
||||||
),
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
function handleClose() {
|
|
||||||
emit('update:visible', false)
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<NModal
|
|
||||||
:show="visible"
|
|
||||||
preset="card"
|
|
||||||
title="用户信息"
|
|
||||||
style="width: 780px"
|
|
||||||
:bordered="false"
|
|
||||||
size="huge"
|
|
||||||
:mask-closable="false"
|
|
||||||
@update:show="$emit('update:visible', $event)"
|
|
||||||
>
|
|
||||||
<NSpin :show="loading">
|
|
||||||
<div class="user-detail-modal">
|
|
||||||
<NTabs v-model:value="activeTab" type="card" size="large" class="detail-tabs">
|
|
||||||
<NTabPane name="basic" tab="基础信息">
|
|
||||||
<div class="basic-info">
|
|
||||||
<div class="info-row">
|
|
||||||
<span class="label">ID:</span>
|
|
||||||
<span class="value">{{ baseInfo.id || '-' }}</span>
|
|
||||||
<span class="label">手机号:</span>
|
|
||||||
<span class="value">{{ baseInfo.phone || '-' }}</span>
|
|
||||||
</div>
|
|
||||||
<div class="info-row">
|
|
||||||
<span class="label">微信号:</span>
|
|
||||||
<span class="value">{{ baseInfo.wechat || '-' }}</span>
|
|
||||||
<span class="label">注册时间:</span>
|
|
||||||
<span class="value">{{ baseInfo.register_time || '-' }}</span>
|
|
||||||
</div>
|
|
||||||
<div class="info-row">
|
|
||||||
<span class="label">剩余次数:</span>
|
|
||||||
<span class="value">{{ baseInfo.remaining_count ?? '-' }}</span>
|
|
||||||
<span class="label">用户类型:</span>
|
|
||||||
<span class="value">{{ baseInfo.user_type || '-' }}</span>
|
|
||||||
</div>
|
|
||||||
<div class="info-row notes">
|
|
||||||
<span class="label">备注:</span>
|
|
||||||
<div class="notes-content">{{ baseInfo.notes || '暂无备注' }}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</NTabPane>
|
|
||||||
<NTabPane name="invoice" tab="发票抬头">
|
|
||||||
<NDataTable
|
|
||||||
class="section-table"
|
|
||||||
:columns="invoiceColumns"
|
|
||||||
:data="invoiceHeaders"
|
|
||||||
:pagination="false"
|
|
||||||
:bordered="false"
|
|
||||||
:single-line="false"
|
|
||||||
>
|
|
||||||
<template #empty>
|
|
||||||
<div class="empty">暂无发票抬头信息</div>
|
|
||||||
</template>
|
|
||||||
</NDataTable>
|
|
||||||
</NTabPane>
|
|
||||||
<NTabPane name="logs" tab="操作记录">
|
|
||||||
<NDataTable
|
|
||||||
class="section-table"
|
|
||||||
:columns="logColumns"
|
|
||||||
:data="operationLogs"
|
|
||||||
:pagination="false"
|
|
||||||
:bordered="false"
|
|
||||||
:single-line="false"
|
|
||||||
>
|
|
||||||
<template #empty>
|
|
||||||
<div class="empty">暂无操作记录</div>
|
|
||||||
</template>
|
|
||||||
</NDataTable>
|
|
||||||
</NTabPane>
|
|
||||||
</NTabs>
|
|
||||||
|
|
||||||
<div class="action-buttons">
|
|
||||||
<NButton @click="handleClose">取消</NButton>
|
|
||||||
<NButton type="primary" @click="handleClose">确定</NButton>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</NSpin>
|
|
||||||
</NModal>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.user-detail-modal {
|
|
||||||
padding: 12px 0 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.detail-tabs :deep(.n-tabs-nav-scroll-content) {
|
|
||||||
justify-content: flex-start;
|
|
||||||
}
|
|
||||||
|
|
||||||
.basic-info {
|
|
||||||
padding: 12px 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.info-row {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
margin-bottom: 16px;
|
|
||||||
font-size: 14px;
|
|
||||||
color: #1f2329;
|
|
||||||
}
|
|
||||||
|
|
||||||
.info-row.notes {
|
|
||||||
align-items: flex-start;
|
|
||||||
}
|
|
||||||
|
|
||||||
.label {
|
|
||||||
min-width: 80px;
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
|
|
||||||
.value {
|
|
||||||
flex: 1;
|
|
||||||
margin-right: 40px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.notes-content {
|
|
||||||
flex: 1;
|
|
||||||
min-height: 60px;
|
|
||||||
padding: 12px;
|
|
||||||
border: 1px solid #ebedf0;
|
|
||||||
border-radius: 4px;
|
|
||||||
background: #fafafa;
|
|
||||||
}
|
|
||||||
|
|
||||||
.section-table {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.section-table :deep(.n-data-table-th) {
|
|
||||||
background: #f6f7fb;
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
|
|
||||||
.section-table :deep(.n-data-table-th),
|
|
||||||
.section-table :deep(.n-data-table-td) {
|
|
||||||
font-size: 14px;
|
|
||||||
padding: 12px;
|
|
||||||
color: #333;
|
|
||||||
}
|
|
||||||
|
|
||||||
.section-table .empty {
|
|
||||||
text-align: center;
|
|
||||||
color: #999;
|
|
||||||
padding: 24px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.log-record div + div {
|
|
||||||
margin-top: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.action-buttons {
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
gap: 16px;
|
|
||||||
margin-top: 20px;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@ -16,7 +16,6 @@ import QueryBarItem from '@/components/query-bar/QueryBarItem.vue'
|
|||||||
import CrudModal from '@/components/table/CrudModal.vue'
|
import CrudModal from '@/components/table/CrudModal.vue'
|
||||||
import CrudTable from '@/components/table/CrudTable.vue'
|
import CrudTable from '@/components/table/CrudTable.vue'
|
||||||
import LimitSettingModal from './LimitSettingModal.vue'
|
import LimitSettingModal from './LimitSettingModal.vue'
|
||||||
import UserDetailModal from './UserDetailModal.vue'
|
|
||||||
|
|
||||||
import { formatDate, renderIcon } from '@/utils'
|
import { formatDate, renderIcon } from '@/utils'
|
||||||
import { useCRUD } from '@/composables'
|
import { useCRUD } from '@/composables'
|
||||||
@ -33,14 +32,6 @@ const vPermission = resolveDirective('permission')
|
|||||||
const limitModalVisible = ref(false)
|
const limitModalVisible = ref(false)
|
||||||
const currentUser = ref(null)
|
const currentUser = ref(null)
|
||||||
|
|
||||||
const detailModalVisible = ref(false)
|
|
||||||
const detailLoading = ref(false)
|
|
||||||
const userDetail = ref({
|
|
||||||
baseInfo: {},
|
|
||||||
invoiceHeaders: [],
|
|
||||||
operationLogs: [],
|
|
||||||
})
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
modalVisible,
|
modalVisible,
|
||||||
modalTitle,
|
modalTitle,
|
||||||
@ -165,30 +156,9 @@ const columns = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
// 查看用户详情
|
// 查看用户详情
|
||||||
async function handleViewDetail(row) {
|
function handleViewDetail(row) {
|
||||||
detailModalVisible.value = true
|
// 这里可以跳转到详情页面或打开详情弹窗
|
||||||
detailLoading.value = true
|
$message.info('查看用户详情功能待实现')
|
||||||
try {
|
|
||||||
const detail = await api.getAppUserById({ id: row.id })
|
|
||||||
const baseInfoFromServer = detail?.baseInfo || {}
|
|
||||||
userDetail.value = {
|
|
||||||
baseInfo: {
|
|
||||||
...baseInfoFromServer,
|
|
||||||
id: row.id,
|
|
||||||
phone: row.phone,
|
|
||||||
wechat: row.wechat,
|
|
||||||
register_time: row.created_at ? formatDate(row.created_at) : '-',
|
|
||||||
notes: row.notes,
|
|
||||||
remaining_count: row.remaining_count,
|
|
||||||
},
|
|
||||||
invoiceHeaders: detail?.invoiceHeaders || [],
|
|
||||||
operationLogs: detail?.operationLogs || [],
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
$message.error('获取用户详情失败')
|
|
||||||
} finally {
|
|
||||||
detailLoading.value = false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 次数设置
|
// 次数设置
|
||||||
@ -301,12 +271,6 @@ const validateForm = {
|
|||||||
:user-data="currentUser"
|
:user-data="currentUser"
|
||||||
@save="handleSaveLimitSetting"
|
@save="handleSaveLimitSetting"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<!-- 用户详情弹窗 -->
|
|
||||||
<UserDetailModal
|
|
||||||
v-model:visible="detailModalVisible"
|
|
||||||
:data="userDetail"
|
|
||||||
:loading="detailLoading"
|
|
||||||
/>
|
|
||||||
</CommonPage>
|
</CommonPage>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { computed, ref, watch, h } from 'vue'
|
import { computed, ref, watch } from 'vue'
|
||||||
import {
|
import {
|
||||||
NButton,
|
NButton,
|
||||||
NTag,
|
NTag,
|
||||||
@ -8,12 +8,10 @@ import {
|
|||||||
NSpin,
|
NSpin,
|
||||||
NImage,
|
NImage,
|
||||||
NImageGroup,
|
NImageGroup,
|
||||||
NDataTable,
|
|
||||||
} from 'naive-ui'
|
} from 'naive-ui'
|
||||||
|
|
||||||
import { formatDate } from '@/utils'
|
import { formatDate } from '@/utils'
|
||||||
import TheIcon from '@/components/icon/TheIcon.vue'
|
import TheIcon from '@/components/icon/TheIcon.vue'
|
||||||
import CertificateModal from './CertificateModal.vue'
|
|
||||||
|
|
||||||
import { getStatusConfig } from '../constants'
|
import { getStatusConfig } from '../constants'
|
||||||
import {
|
import {
|
||||||
@ -37,11 +35,6 @@ const emit = defineEmits(['back', 'approve', 'reject'])
|
|||||||
|
|
||||||
const activeDetailTab = ref('audit')
|
const activeDetailTab = ref('audit')
|
||||||
|
|
||||||
// 证书弹窗相关状态
|
|
||||||
const certificateModalVisible = ref(false)
|
|
||||||
const certificateModalMode = ref('upload') // 'upload' 或 'view'
|
|
||||||
const certificateData = ref({})
|
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => props.detailData?.id,
|
() => props.detailData?.id,
|
||||||
() => {
|
() => {
|
||||||
@ -52,23 +45,20 @@ watch(
|
|||||||
const detailSections = computed(() => {
|
const detailSections = computed(() => {
|
||||||
const detail = props.detailData
|
const detail = props.detailData
|
||||||
if (!detail) return []
|
if (!detail) return []
|
||||||
|
return [
|
||||||
const sections = [
|
|
||||||
{
|
{
|
||||||
key: 'basic',
|
key: 'basic',
|
||||||
title: '基础信息',
|
title: '基础信息',
|
||||||
fields: [
|
columns: [
|
||||||
{ label: '资产名称', type: 'text', value: detail.asset_name || '-' },
|
{ label: '资产名称', type: 'text', value: detail.asset_name || '-' },
|
||||||
{ label: '所属机构', type: 'text', value: detail.institution || '-' },
|
{ label: '所属机构', type: 'text', value: detail.institution || '-' },
|
||||||
{ label: '所属行业', type: 'text', value: detail.industry || '-' },
|
{ label: '所属行业', type: 'text', value: detail.industry || '-' },
|
||||||
{ label: '企业简介', type: 'text', value: detail.company_profile || '-' },
|
|
||||||
{ label: '业务简介', type: 'text', value: detail.business_profile || '-' },
|
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'finance',
|
key: 'finance',
|
||||||
title: '财务状况',
|
title: '财务状况',
|
||||||
fields: [
|
columns: [
|
||||||
{ label: '近12个月机构营收/万元', type: 'text', value: formatNumberValue(detail.annual_revenue) },
|
{ label: '近12个月机构营收/万元', type: 'text', value: formatNumberValue(detail.annual_revenue) },
|
||||||
{ label: '近12个月机构研发投入/万元', type: 'text', value: formatNumberValue(detail.rd_investment) },
|
{ label: '近12个月机构研发投入/万元', type: 'text', value: formatNumberValue(detail.rd_investment) },
|
||||||
{ label: '近三年机构收益/万元', type: 'list', value: formatThreeYearIncome(detail.three_year_income) },
|
{ label: '近三年机构收益/万元', type: 'list', value: formatThreeYearIncome(detail.three_year_income) },
|
||||||
@ -78,7 +68,7 @@ const detailSections = computed(() => {
|
|||||||
{
|
{
|
||||||
key: 'tech',
|
key: 'tech',
|
||||||
title: '非遗等级与技术',
|
title: '非遗等级与技术',
|
||||||
fields: [
|
columns: [
|
||||||
{ label: '非遗传承人等级', type: 'text', value: detail.inheritor_level || '-' },
|
{ label: '非遗传承人等级', type: 'text', value: detail.inheritor_level || '-' },
|
||||||
{ label: '非遗传承人年龄水平及数量', type: 'list', value: formatAgeDistribution(detail.inheritor_age_count) },
|
{ label: '非遗传承人年龄水平及数量', type: 'list', value: formatAgeDistribution(detail.inheritor_age_count) },
|
||||||
{ label: '非遗传承人等级证书', type: 'images', value: detail.inheritor_certificates || [] },
|
{ label: '非遗传承人等级证书', type: 'images', value: detail.inheritor_certificates || [] },
|
||||||
@ -95,7 +85,7 @@ const detailSections = computed(() => {
|
|||||||
{
|
{
|
||||||
key: 'promotion',
|
key: 'promotion',
|
||||||
title: '非遗应用与推广',
|
title: '非遗应用与推广',
|
||||||
fields: [
|
columns: [
|
||||||
{ label: '非遗资产应用成熟度', type: 'text', value: detail.application_maturity || detail.implementation_stage || '-' },
|
{ label: '非遗资产应用成熟度', type: 'text', value: detail.application_maturity || detail.implementation_stage || '-' },
|
||||||
{ label: '非遗资产应用覆盖范围', type: 'text', value: detail.application_coverage || detail.coverage_area || '-' },
|
{ label: '非遗资产应用覆盖范围', type: 'text', value: detail.application_coverage || detail.coverage_area || '-' },
|
||||||
{ label: '非遗资产跨界合作深度', type: 'text', value: detail.cooperation_depth || detail.collaboration_type || '-' },
|
{ label: '非遗资产跨界合作深度', type: 'text', value: detail.cooperation_depth || detail.collaboration_type || '-' },
|
||||||
@ -106,7 +96,7 @@ const detailSections = computed(() => {
|
|||||||
{
|
{
|
||||||
key: 'products',
|
key: 'products',
|
||||||
title: '非遗资产衍生商品信息',
|
title: '非遗资产衍生商品信息',
|
||||||
fields: [
|
columns: [
|
||||||
{ label: '代表产品近12个月销售数量', type: 'text', value: formatNumberValue(detail.sales_volume) },
|
{ label: '代表产品近12个月销售数量', type: 'text', value: formatNumberValue(detail.sales_volume) },
|
||||||
{ label: '商品链接浏览量', type: 'text', value: formatNumberValue(detail.link_views) },
|
{ label: '商品链接浏览量', type: 'text', value: formatNumberValue(detail.link_views) },
|
||||||
{ label: '发行量', type: 'text', value: detail.circulation || detail.scarcity_level || '-' },
|
{ label: '发行量', type: 'text', value: detail.circulation || detail.scarcity_level || '-' },
|
||||||
@ -116,101 +106,9 @@ const detailSections = computed(() => {
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
// 为每个 section 生成 NDataTable 需要的 columns 和 data
|
|
||||||
return sections.map(section => {
|
|
||||||
const columns = [
|
|
||||||
{
|
|
||||||
title: '字段名',
|
|
||||||
key: 'fieldName',
|
|
||||||
width: 120,
|
|
||||||
align: 'center',
|
|
||||||
fixed: 'left',
|
|
||||||
},
|
|
||||||
...section.fields.map(field => ({
|
|
||||||
title: field.label,
|
|
||||||
key: field.label,
|
|
||||||
width: 200,
|
|
||||||
ellipsis: {
|
|
||||||
tooltip: true,
|
|
||||||
},
|
|
||||||
render: (row) => {
|
|
||||||
const fieldData = row[field.label]
|
|
||||||
if (!fieldData) return '-'
|
|
||||||
|
|
||||||
if (fieldData.type === 'list') {
|
|
||||||
if (fieldData.value && fieldData.value.length) {
|
|
||||||
return h('div', { class: 'cell-multi' },
|
|
||||||
fieldData.value.map(item => h('span', item))
|
|
||||||
)
|
|
||||||
}
|
|
||||||
return '-'
|
|
||||||
} else if (fieldData.type === 'images') {
|
|
||||||
if (fieldData.value && fieldData.value.length) {
|
|
||||||
return h(NImageGroup, {}, () =>
|
|
||||||
fieldData.value.map(img =>
|
|
||||||
h(NImage, {
|
|
||||||
src: img,
|
|
||||||
width: 72,
|
|
||||||
height: 48,
|
|
||||||
objectFit: 'cover',
|
|
||||||
style: 'margin-right: 8px;'
|
|
||||||
})
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
return '-'
|
|
||||||
} else {
|
|
||||||
return fieldData.value || '-'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})),
|
|
||||||
]
|
|
||||||
|
|
||||||
const data = [
|
|
||||||
{
|
|
||||||
fieldName: '用户输入',
|
|
||||||
...section.fields.reduce((acc, field) => {
|
|
||||||
acc[field.label] = field
|
|
||||||
return acc
|
|
||||||
}, {}),
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
return {
|
|
||||||
...section,
|
|
||||||
columns,
|
|
||||||
data,
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
|
|
||||||
const calcFlow = computed(() => props.detailData?.calculation_result?.flow || [])
|
const calcFlow = computed(() => props.detailData?.calculation_result?.flow || [])
|
||||||
|
|
||||||
// 证书相关功能
|
|
||||||
const handleUploadCertificate = () => {
|
|
||||||
certificateModalMode.value = 'upload'
|
|
||||||
certificateData.value = {}
|
|
||||||
certificateModalVisible.value = true
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleViewCertificate = () => {
|
|
||||||
certificateModalMode.value = 'view'
|
|
||||||
// 这里可以从 props.detailData 中获取已上传的证书数据
|
|
||||||
certificateData.value = {
|
|
||||||
title: '非遗传承人等级证书',
|
|
||||||
description: '非遗传承人等级证书相关文件',
|
|
||||||
files: props.detailData?.certificates || []
|
|
||||||
}
|
|
||||||
certificateModalVisible.value = true
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleCertificateConfirm = (data) => {
|
|
||||||
console.log('证书数据:', data)
|
|
||||||
// 这里可以调用 API 保存证书数据
|
|
||||||
$message?.success('证书上传成功')
|
|
||||||
certificateModalVisible.value = false
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@ -221,7 +119,7 @@ const handleCertificateConfirm = (data) => {
|
|||||||
<TheIcon icon="mdi:arrow-left" :size="16" class="mr-4" />
|
<TheIcon icon="mdi:arrow-left" :size="16" class="mr-4" />
|
||||||
返回审核列表
|
返回审核列表
|
||||||
</button>
|
</button>
|
||||||
<!-- <div class="detail-title">
|
<div class="detail-title">
|
||||||
<h2>{{ detailData?.asset_name || '审核详情' }}</h2>
|
<h2>{{ detailData?.asset_name || '审核详情' }}</h2>
|
||||||
<NTag size="small" :type="getStatusConfig(detailData?.status).type">
|
<NTag size="small" :type="getStatusConfig(detailData?.status).type">
|
||||||
{{ getStatusConfig(detailData?.status).text }}
|
{{ getStatusConfig(detailData?.status).text }}
|
||||||
@ -232,16 +130,21 @@ const handleCertificateConfirm = (data) => {
|
|||||||
<span>微信号:{{ detailData?.wechat || '-' }}</span>
|
<span>微信号:{{ detailData?.wechat || '-' }}</span>
|
||||||
<span>提交时间:{{ formatDate(detailData?.created_at) }}</span>
|
<span>提交时间:{{ formatDate(detailData?.created_at) }}</span>
|
||||||
<span>审核时间:{{ detailData?.reviewed_at ? formatDate(detailData?.reviewed_at) : '-' }}</span>
|
<span>审核时间:{{ detailData?.reviewed_at ? formatDate(detailData?.reviewed_at) : '-' }}</span>
|
||||||
</p> -->
|
</p>
|
||||||
|
</div>
|
||||||
|
<div v-if="mode === 'approve' && detailData?.status === 'pending'" class="detail-actions">
|
||||||
|
<NButton tertiary type="error" @click="emit('reject')">
|
||||||
|
<TheIcon icon="mdi:close-circle-outline" :size="16" class="mr-4" />
|
||||||
|
拒绝
|
||||||
|
</NButton>
|
||||||
|
<NButton type="primary" @click="emit('approve')">
|
||||||
|
<TheIcon icon="mdi:check-circle-outline" :size="16" class="mr-4" />
|
||||||
|
通过
|
||||||
|
</NButton>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<NTabs
|
<NTabs v-model:value="activeDetailTab">
|
||||||
v-model:value="activeDetailTab"
|
|
||||||
type="line"
|
|
||||||
size="large"
|
|
||||||
class="audit-tabs"
|
|
||||||
>
|
|
||||||
<NTabPane name="audit" tab="审核信息">
|
<NTabPane name="audit" tab="审核信息">
|
||||||
<NSpin :show="loading">
|
<NSpin :show="loading">
|
||||||
<div v-for="section in detailSections" :key="section.key" class="detail-section">
|
<div v-for="section in detailSections" :key="section.key" class="detail-section">
|
||||||
@ -249,17 +152,47 @@ const handleCertificateConfirm = (data) => {
|
|||||||
<span class="dot" />
|
<span class="dot" />
|
||||||
<span>{{ section.title }}</span>
|
<span>{{ section.title }}</span>
|
||||||
</div>
|
</div>
|
||||||
<NDataTable
|
<div class="table-wrapper">
|
||||||
:columns="section.columns"
|
<table class="info-table">
|
||||||
:data="section.data"
|
<thead>
|
||||||
:bordered="true"
|
<tr>
|
||||||
:single-line="false"
|
<th class="first-col">字段名</th>
|
||||||
:scroll-x="section.fields.length * 200 + 120"
|
<th v-for="column in section.columns" :key="column.label">
|
||||||
>
|
{{ column.label }}
|
||||||
<template #empty>
|
</th>
|
||||||
<span>暂无数据</span>
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td class="first-col">用户输入</td>
|
||||||
|
<td v-for="column in section.columns" :key="column.label">
|
||||||
|
<div v-if="column.type === 'list'" class="cell-multi">
|
||||||
|
<template v-if="column.value && column.value.length">
|
||||||
|
<span v-for="(item, idx) in column.value" :key="idx">{{ item }}</span>
|
||||||
</template>
|
</template>
|
||||||
</NDataTable>
|
<span v-else>-</span>
|
||||||
|
</div>
|
||||||
|
<div v-else-if="column.type === 'images'">
|
||||||
|
<template v-if="column.value && column.value.length">
|
||||||
|
<NImageGroup>
|
||||||
|
<NImage
|
||||||
|
v-for="(img, idx) in column.value"
|
||||||
|
:key="idx"
|
||||||
|
width="72"
|
||||||
|
height="48"
|
||||||
|
:src="img"
|
||||||
|
object-fit="cover"
|
||||||
|
/>
|
||||||
|
</NImageGroup>
|
||||||
|
</template>
|
||||||
|
<span v-else>-</span>
|
||||||
|
</div>
|
||||||
|
<span v-else>{{ column.value || '-' }}</span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</NSpin>
|
</NSpin>
|
||||||
</NTabPane>
|
</NTabPane>
|
||||||
@ -297,34 +230,6 @@ const handleCertificateConfirm = (data) => {
|
|||||||
</NSpin>
|
</NSpin>
|
||||||
</NTabPane>
|
</NTabPane>
|
||||||
</NTabs>
|
</NTabs>
|
||||||
|
|
||||||
<!-- 证书按钮 -->
|
|
||||||
<div class="certificate-actions">
|
|
||||||
<NButton
|
|
||||||
v-if="mode === 'approve'"
|
|
||||||
type="primary"
|
|
||||||
@click="handleUploadCertificate"
|
|
||||||
>
|
|
||||||
<TheIcon icon="mdi:upload" :size="16" class="mr-4" />
|
|
||||||
上传证书
|
|
||||||
</NButton>
|
|
||||||
<NButton
|
|
||||||
v-else
|
|
||||||
type="info"
|
|
||||||
@click="handleViewCertificate"
|
|
||||||
>
|
|
||||||
<TheIcon icon="mdi:eye" :size="16" class="mr-4" />
|
|
||||||
查看证书
|
|
||||||
</NButton>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 证书弹窗 -->
|
|
||||||
<CertificateModal
|
|
||||||
v-model:visible="certificateModalVisible"
|
|
||||||
:mode="certificateModalMode"
|
|
||||||
:certificate-data="certificateData"
|
|
||||||
@confirm="handleCertificateConfirm"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -333,20 +238,13 @@ const handleCertificateConfirm = (data) => {
|
|||||||
background: #fff;
|
background: #fff;
|
||||||
border-radius: 12px;
|
border-radius: 12px;
|
||||||
padding: 24px;
|
padding: 24px;
|
||||||
.certificate-actions {
|
|
||||||
margin-top: 20px;
|
|
||||||
width: 100%;
|
|
||||||
display: flex;
|
|
||||||
justify-content: flex-end;
|
|
||||||
z-index: 100;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.detail-header {
|
.detail-header {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
gap: 16px;
|
gap: 16px;
|
||||||
margin: -16px 0px 10px ;
|
margin-bottom: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.back-btn {
|
.back-btn {
|
||||||
@ -392,62 +290,51 @@ const handleCertificateConfirm = (data) => {
|
|||||||
margin-bottom: 24px;
|
margin-bottom: 24px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.audit-tabs :deep(.n-tabs-nav) {
|
|
||||||
margin-bottom: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.audit-tabs :deep(.n-tabs-tab) {
|
|
||||||
font-size: 16px;
|
|
||||||
font-weight: 500;
|
|
||||||
color: #1d2129;
|
|
||||||
padding: 0 24px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.audit-tabs :deep(.n-tabs-tab.n-tabs-tab--active) {
|
|
||||||
color: #ff6f3b;
|
|
||||||
}
|
|
||||||
|
|
||||||
.audit-tabs :deep(.n-tabs-nav__line) {
|
|
||||||
background-color: #ff6f3b;
|
|
||||||
height: 3px;
|
|
||||||
border-radius: 999px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.section-title {
|
.section-title {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 12px;
|
gap: 8px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
margin-bottom: 12px;
|
margin-bottom: 12px;
|
||||||
font-size: 18px;
|
|
||||||
color: #1d2129;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.section-title .dot {
|
.section-title .dot {
|
||||||
width: 10px;
|
width: 8px;
|
||||||
height: 18px;
|
height: 8px;
|
||||||
border-radius: 4px;
|
border-radius: 50%;
|
||||||
background: #3b82f6;
|
background: #409eff;
|
||||||
}
|
}
|
||||||
|
|
||||||
.detail-section :deep(.n-data-table) {
|
.table-wrapper {
|
||||||
|
overflow-x: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-table {
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
background: #f9fafe;
|
background: #f9fafe;
|
||||||
|
table-layout: fixed;
|
||||||
}
|
}
|
||||||
|
|
||||||
.detail-section :deep(.n-data-table-th) {
|
.info-table th,
|
||||||
|
.info-table td {
|
||||||
|
border: 1px solid #e5e6eb;
|
||||||
|
padding: 12px;
|
||||||
|
text-align: left;
|
||||||
|
min-width: 140px;
|
||||||
|
word-break: break-word;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-table .first-col {
|
||||||
|
width: 120px;
|
||||||
|
text-align: center;
|
||||||
background: #f1f2f5;
|
background: #f1f2f5;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
|
|
||||||
.detail-section :deep(.n-data-table-th:first-child) {
|
.info-table th:not(.first-col),
|
||||||
background: #f1f2f5;
|
.info-table td:not(.first-col) {
|
||||||
text-align: center;
|
width: 180px;
|
||||||
}
|
|
||||||
|
|
||||||
.detail-section :deep(.n-data-table-td:first-child) {
|
|
||||||
background: #f1f2f5;
|
|
||||||
text-align: center;
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.cell-multi {
|
.cell-multi {
|
||||||
@ -532,6 +419,4 @@ const handleCertificateConfirm = (data) => {
|
|||||||
color: #999;
|
color: #999;
|
||||||
padding: 40px 0;
|
padding: 40px 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@ -1,485 +0,0 @@
|
|||||||
<script setup>
|
|
||||||
import { ref, watch, computed } from 'vue'
|
|
||||||
import {
|
|
||||||
NModal,
|
|
||||||
NCard,
|
|
||||||
NButton,
|
|
||||||
NUpload,
|
|
||||||
NText,
|
|
||||||
NImage
|
|
||||||
} from 'naive-ui'
|
|
||||||
// 临时移除图标导入以解决模块解析问题
|
|
||||||
// import { DownloadIcon } from '@vicons/tabler'
|
|
||||||
|
|
||||||
const props = defineProps({
|
|
||||||
visible: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false,
|
|
||||||
},
|
|
||||||
certificateData: {
|
|
||||||
type: Object,
|
|
||||||
default: () => ({}),
|
|
||||||
},
|
|
||||||
mode: {
|
|
||||||
type: String,
|
|
||||||
default: 'upload', // 'upload' 或 'view'
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
const emit = defineEmits(['update:visible', 'confirm'])
|
|
||||||
|
|
||||||
const formData = ref({
|
|
||||||
reportFiles: [],
|
|
||||||
certificateFiles: [],
|
|
||||||
})
|
|
||||||
|
|
||||||
const reportFileList = ref([])
|
|
||||||
const certificateFileList = ref([])
|
|
||||||
|
|
||||||
// 监听弹窗打开,初始化数据
|
|
||||||
watch(
|
|
||||||
() => props.visible,
|
|
||||||
(val) => {
|
|
||||||
if (val) {
|
|
||||||
if (props.mode === 'view') {
|
|
||||||
// 查看模式,加载已有数据
|
|
||||||
reportFileList.value = props.certificateData?.reportFiles || []
|
|
||||||
certificateFileList.value = props.certificateData?.certificateFiles || []
|
|
||||||
} else {
|
|
||||||
// 上传模式,清空数据
|
|
||||||
formData.value = {
|
|
||||||
reportFiles: [],
|
|
||||||
certificateFiles: [],
|
|
||||||
}
|
|
||||||
reportFileList.value = []
|
|
||||||
certificateFileList.value = []
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
// 关闭弹窗
|
|
||||||
const handleClose = () => {
|
|
||||||
emit('update:visible', false)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 确认操作
|
|
||||||
const handleConfirm = () => {
|
|
||||||
emit('confirm', {
|
|
||||||
reportFiles: formData.value.reportFiles,
|
|
||||||
certificateFiles: formData.value.certificateFiles,
|
|
||||||
})
|
|
||||||
handleClose()
|
|
||||||
}
|
|
||||||
|
|
||||||
// 文件上传前的处理
|
|
||||||
const beforeUpload = (data) => {
|
|
||||||
const { file } = data
|
|
||||||
const isImage = file.type.startsWith('image/')
|
|
||||||
const isPdf = file.type === 'application/pdf'
|
|
||||||
const isWord = file.type.includes('word') || file.type.includes('document')
|
|
||||||
const isVideo = file.type.startsWith('video/')
|
|
||||||
|
|
||||||
if (!isImage && !isPdf && !isWord && !isVideo) {
|
|
||||||
$message.error('只能上传图片、PDF、Word文档或视频文件')
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
const isLt50M = file.size / 1024 / 1024 < 50
|
|
||||||
if (!isLt50M) {
|
|
||||||
$message.error('文件大小不能超过50MB')
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// 报告文件上传变化
|
|
||||||
const handleReportUploadChange = ({ fileList: newFileList }) => {
|
|
||||||
reportFileList.value = newFileList
|
|
||||||
formData.value.reportFiles = newFileList.map(file => ({
|
|
||||||
id: file.id,
|
|
||||||
name: file.name,
|
|
||||||
url: file.url,
|
|
||||||
type: file.type
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
|
|
||||||
// 证书文件上传变化
|
|
||||||
const handleCertificateUploadChange = ({ fileList: newFileList }) => {
|
|
||||||
certificateFileList.value = newFileList
|
|
||||||
formData.value.certificateFiles = newFileList.map(file => ({
|
|
||||||
id: file.id,
|
|
||||||
name: file.name,
|
|
||||||
url: file.url,
|
|
||||||
type: file.type
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
|
|
||||||
// 移除文件
|
|
||||||
const handleRemove = () => {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// 移除证书文件
|
|
||||||
const removeCertificateFile = (index) => {
|
|
||||||
certificateFileList.value.splice(index, 1)
|
|
||||||
formData.value.certificateFiles = certificateFileList.value.map(file => ({
|
|
||||||
id: file.id,
|
|
||||||
name: file.name,
|
|
||||||
url: file.url || '',
|
|
||||||
type: file.type || ''
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// 下载报告
|
|
||||||
const handleDownloadReport = () => {
|
|
||||||
// 这里实现下载原版报告的逻辑
|
|
||||||
console.log('下载原版报告')
|
|
||||||
// TODO: 实现实际的下载功能
|
|
||||||
}
|
|
||||||
|
|
||||||
// 文件预览
|
|
||||||
const handlePreview = (file) => {
|
|
||||||
// 图片和PDF可以直接预览
|
|
||||||
if (file.type?.startsWith('image/') || file.type === 'application/pdf') {
|
|
||||||
window.open(file.url || '', '_blank')
|
|
||||||
} else {
|
|
||||||
console.log('此文件类型不支持预览')
|
|
||||||
// TODO: 添加用户提示
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const modalTitle = computed(() => {
|
|
||||||
return props.mode === 'upload' ? '上传' : '查看'
|
|
||||||
})
|
|
||||||
|
|
||||||
const isUploadMode = computed(() => props.mode === 'upload')
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<NModal
|
|
||||||
:show="visible"
|
|
||||||
:mask-closable="false"
|
|
||||||
preset="card"
|
|
||||||
:title="modalTitle"
|
|
||||||
class="certificate-modal"
|
|
||||||
style="width: 700px"
|
|
||||||
@update:show="handleClose"
|
|
||||||
>
|
|
||||||
<!-- 上传模式 -->
|
|
||||||
<div v-if="isUploadMode" class="certificate-content">
|
|
||||||
<!-- 报告上传部分 -->
|
|
||||||
<div class="upload-section">
|
|
||||||
<div class="section-title">报告:</div>
|
|
||||||
<div class="upload-content">
|
|
||||||
<div class="download-section">
|
|
||||||
<NButton text type="primary" @click="handleDownloadReport">
|
|
||||||
点击下载原版报告
|
|
||||||
</NButton>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<NUpload
|
|
||||||
v-model:file-list="reportFileList"
|
|
||||||
:max="5"
|
|
||||||
list-type="image-card"
|
|
||||||
:before-upload="beforeUpload"
|
|
||||||
@change="handleReportUploadChange"
|
|
||||||
@remove="handleRemove"
|
|
||||||
>
|
|
||||||
</NUpload>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 证书上传部分 -->
|
|
||||||
<div class="upload-section">
|
|
||||||
<div class="section-title">证书:</div>
|
|
||||||
<div class="upload-content">
|
|
||||||
<NUpload
|
|
||||||
v-model:file-list="certificateFileList"
|
|
||||||
:max="5"
|
|
||||||
list-type="image-card"
|
|
||||||
:before-upload="beforeUpload"
|
|
||||||
@change="handleCertificateUploadChange"
|
|
||||||
@remove="handleRemove"
|
|
||||||
>
|
|
||||||
</NUpload>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 查看模式 -->
|
|
||||||
<div v-else class="certificate-content">
|
|
||||||
<!-- 报告查看部分 -->
|
|
||||||
<div class="view-section">
|
|
||||||
<div class="section-title">报告:</div>
|
|
||||||
<div class="view-content">
|
|
||||||
<div class="download-area">
|
|
||||||
<NButton text type="primary" @click="handleDownloadReport">
|
|
||||||
<!-- 临时移除图标 -->
|
|
||||||
<!-- <template #icon>
|
|
||||||
<NIcon :component="DownloadIcon" />
|
|
||||||
</template> -->
|
|
||||||
点击下载原版报告
|
|
||||||
</NButton>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div v-if="reportFileList.length > 0" class="file-info">
|
|
||||||
<NText>{{ reportFileList[0]?.name }} 下载</NText>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 证书查看部分 -->
|
|
||||||
<div class="view-section">
|
|
||||||
<div class="section-title">证书:</div>
|
|
||||||
<div class="view-content">
|
|
||||||
<div v-if="certificateFileList.length === 0" class="empty-state">
|
|
||||||
<NText depth="3">暂无证书文件</NText>
|
|
||||||
</div>
|
|
||||||
<div v-else class="certificate-display">
|
|
||||||
<div
|
|
||||||
v-for="file in certificateFileList"
|
|
||||||
:key="file.id"
|
|
||||||
class="certificate-image"
|
|
||||||
@click="handlePreview(file)"
|
|
||||||
>
|
|
||||||
<NImage
|
|
||||||
v-if="file.type?.startsWith('image/')"
|
|
||||||
:src="file.url"
|
|
||||||
width="120"
|
|
||||||
height="120"
|
|
||||||
objectFit="cover"
|
|
||||||
preview-disabled
|
|
||||||
/>
|
|
||||||
<div v-else class="file-icon">
|
|
||||||
{{ file.name?.split('.').pop()?.toUpperCase() || 'FILE' }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<template #footer>
|
|
||||||
<div class="modal-footer">
|
|
||||||
<NButton @click="handleClose">取消</NButton>
|
|
||||||
<NButton v-if="isUploadMode" type="primary" @click="handleConfirm">
|
|
||||||
上传并通知
|
|
||||||
</NButton>
|
|
||||||
<NButton v-else type="primary" @click="handleConfirm">
|
|
||||||
确定
|
|
||||||
</NButton>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</NModal>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.certificate-modal {
|
|
||||||
max-width: 90vw;
|
|
||||||
}
|
|
||||||
|
|
||||||
.certificate-content {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 24px;
|
|
||||||
padding: 16px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 上传模式样式 */
|
|
||||||
.upload-section {
|
|
||||||
margin-bottom: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.section-title {
|
|
||||||
font-size: 14px;
|
|
||||||
color: #666;
|
|
||||||
margin-bottom: 12px;
|
|
||||||
font-weight: normal;
|
|
||||||
}
|
|
||||||
|
|
||||||
.upload-content {
|
|
||||||
min-height: 60px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.report-upload-area,
|
|
||||||
.certificate-upload-area {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 16px;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.download-section {
|
|
||||||
flex: 0 0 auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.file-section {
|
|
||||||
flex: 1;
|
|
||||||
min-width: 200px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.uploaded-file {
|
|
||||||
padding: 6px 12px;
|
|
||||||
background: #f5f5f5;
|
|
||||||
border-radius: 4px;
|
|
||||||
display: inline-block;
|
|
||||||
margin-bottom: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
.file-name {
|
|
||||||
font-size: 14px;
|
|
||||||
color: #333;
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
|
|
||||||
.certificate-preview {
|
|
||||||
width: 60px;
|
|
||||||
height: 60px;
|
|
||||||
border-radius: 6px;
|
|
||||||
overflow: hidden;
|
|
||||||
position: relative;
|
|
||||||
border: 1px solid #e8e8e8;
|
|
||||||
flex: 0 0 auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.file-preview-icon {
|
|
||||||
width: 60px;
|
|
||||||
height: 60px;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
background: #f5f5f5;
|
|
||||||
color: #666;
|
|
||||||
font-size: 12px;
|
|
||||||
font-weight: 600;
|
|
||||||
border-radius: 6px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.remove-button {
|
|
||||||
position: absolute;
|
|
||||||
top: -8px;
|
|
||||||
right: -8px;
|
|
||||||
width: 20px;
|
|
||||||
height: 20px;
|
|
||||||
background: #ff4d4f;
|
|
||||||
color: white;
|
|
||||||
border-radius: 50%;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
cursor: pointer;
|
|
||||||
font-size: 12px;
|
|
||||||
line-height: 1;
|
|
||||||
border: 2px solid white;
|
|
||||||
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
.remove-button:hover {
|
|
||||||
background: #ff7875;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 查看模式样式 */
|
|
||||||
.view-section {
|
|
||||||
margin-bottom: 24px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.view-content {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.download-area {
|
|
||||||
display: flex;
|
|
||||||
justify-content: flex-start;
|
|
||||||
}
|
|
||||||
|
|
||||||
.file-info {
|
|
||||||
padding: 8px 12px;
|
|
||||||
background: #f5f5f5;
|
|
||||||
border-radius: 4px;
|
|
||||||
display: inline-block;
|
|
||||||
}
|
|
||||||
|
|
||||||
.certificate-display {
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
gap: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.certificate-image {
|
|
||||||
cursor: pointer;
|
|
||||||
border-radius: 8px;
|
|
||||||
overflow: hidden;
|
|
||||||
border: 1px solid #e8e8e8;
|
|
||||||
transition: all 0.3s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.certificate-image:hover {
|
|
||||||
border-color: #1890ff;
|
|
||||||
box-shadow: 0 4px 12px rgba(24, 144, 255, 0.15);
|
|
||||||
}
|
|
||||||
|
|
||||||
.empty-state {
|
|
||||||
padding: 40px 20px;
|
|
||||||
text-align: center;
|
|
||||||
color: #999;
|
|
||||||
font-size: 14px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.file-icon {
|
|
||||||
width: 120px;
|
|
||||||
height: 120px;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
background: #f5f5f5;
|
|
||||||
color: #666;
|
|
||||||
font-size: 24px;
|
|
||||||
font-weight: 600;
|
|
||||||
border-radius: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 底部按钮 */
|
|
||||||
.modal-footer {
|
|
||||||
display: flex;
|
|
||||||
justify-content: flex-end;
|
|
||||||
gap: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 缩略图上传组件样式 */
|
|
||||||
:deep(.n-upload) {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
:deep(.n-upload-file-list) {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(auto-fill, minmax(100px, 1fr));
|
|
||||||
gap: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
:deep(.n-upload-file-card) {
|
|
||||||
aspect-ratio: 1;
|
|
||||||
border-radius: 6px;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 响应式调整 */
|
|
||||||
@media (max-width: 768px) {
|
|
||||||
.certificate-modal {
|
|
||||||
width: 95vw !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.certificate-display {
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.certificate-image {
|
|
||||||
flex: 0 0 auto;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
Loading…
x
Reference in New Issue
Block a user