修复apiresult的问题;增加用户详情查看的逻辑

This commit is contained in:
BBIT-Kai
2026-05-21 14:55:08 +08:00
parent 40f1c27e71
commit 3506efe894
7 changed files with 503 additions and 33 deletions
+317 -2
View File
@@ -45,6 +45,96 @@
</div>
</section>
<n-modal
v-model:show="detailModal.visible"
preset="card"
title="用户详情"
:style="{ width: '920px', maxWidth: '92vw' }"
:mask-closable="false"
content-style="padding: 0; max-height: calc(85vh - 130px); overflow-y: auto"
>
<n-spin :show="detailModal.loading">
<template v-if="detailUser">
<div class="detail-shell">
<div class="detail-header">
<div>
<div class="detail-name">{{ detailUser.nickname || detailUser.realName || detailUser.username }}</div>
<div class="detail-username">{{ detailUser.username }}</div>
<div class="detail-tags">
<n-tag :type="statusTagType(detailUser.status)" size="small" round>
{{ detailUser.statusLabel || statusLabel(detailUser.status) }}
</n-tag>
<n-tag v-if="detailUser.orgName || detailUser.orgCode" size="small" round>
{{ detailUser.orgName || detailUser.orgCode }}
</n-tag>
</div>
</div>
<div class="detail-total">
<span>角色数</span>
<strong>{{ detailUser.roles?.length || 0 }}</strong>
</div>
</div>
<div class="detail-section">
<div class="detail-section-title">基础信息</div>
<div class="detail-grid">
<div v-for="item in baseDetailItems" :key="item.label" class="detail-item">
<span>{{ item.label }}</span>
<strong>{{ item.value }}</strong>
</div>
</div>
</div>
<div class="detail-section">
<div class="detail-section-title">角色信息</div>
<div class="role-tags">
<n-tag
v-for="role in detailUser.roles"
:key="role.id"
size="small"
round
type="info"
>
{{ role.name }}{{ role.code }}
</n-tag>
<span v-if="detailUser.roles.length === 0" class="empty-text">-</span>
</div>
</div>
<div class="detail-section">
<div class="detail-section-title">税务信息</div>
<div class="detail-grid">
<div v-for="item in taxDetailItems" :key="item.label" class="detail-item">
<span>{{ item.label }}</span>
<strong>{{ item.value }}</strong>
</div>
</div>
</div>
<div class="detail-section">
<div class="detail-section-title">银行与预设信息</div>
<div class="detail-grid">
<div v-for="item in presetDetailItems" :key="item.label" class="detail-item">
<span>{{ item.label }}</span>
<strong>{{ item.value }}</strong>
</div>
</div>
</div>
<div class="detail-section">
<div class="detail-section-title">系统信息</div>
<div class="detail-grid">
<div v-for="item in systemDetailItems" :key="item.label" class="detail-item">
<span>{{ item.label }}</span>
<strong>{{ item.value }}</strong>
</div>
</div>
</div>
</div>
</template>
</n-spin>
</n-modal>
<n-modal
v-model:show="editModal.visible"
preset="card"
@@ -169,7 +259,12 @@ import {
updateUserStatusApi
} from '@/api/system/user'
import { listRolesApi } from '@/api/system/role'
import type { CreateUserRequest, UpdateUserRequest, UserListItem } from '@/types/system/user'
import type {
CreateUserRequest,
UpdateUserRequest,
UserDetail,
UserListItem
} from '@/types/system/user'
const message = useMessage()
const authStore = useAuthStore()
@@ -227,6 +322,76 @@ const pagination = computed(() => ({
}))
const editFormRef = ref<FormInst | null>(null)
const detailModal = reactive({
visible: false,
loading: false
})
const detailUser = ref<UserDetail | null>(null)
const baseDetailItems = computed(() => {
const user = detailUser.value
if (!user) return []
return [
{ label: '用户ID', value: displayValue(user.id) },
{ label: '用户名', value: displayValue(user.username) },
{ label: '昵称', value: displayValue(user.nickname) },
{ label: '姓名', value: displayValue(user.realName) },
{ label: '手机号', value: displayValue(user.phone) },
{ label: '邮箱', value: displayValue(user.email) },
{ label: '组织', value: formatOrg(user) },
{ label: '状态', value: user.statusLabel || statusLabel(user.status) },
{ label: '头像', value: displayValue(user.avatar) },
{ label: 'API Key', value: displayValue(user.apiKey) }
]
})
const taxDetailItems = computed(() => {
const user = detailUser.value
if (!user) return []
return [
{ label: '纳税人识别号', value: displayValue(user.taxpayerNum) },
{ label: '数电账号', value: displayValue(user.account) },
{ label: '身份类型', value: displayValue(user.taxIdentityType) },
{ label: '联系人', value: displayValue(user.taxContactName) },
{ label: '联系人电话', value: displayValue(user.taxContactPhone) },
{ label: '联系人邮箱', value: displayValue(user.taxContactEmail) },
{ label: '法定代表人', value: displayValue(user.taxLegalPersonName) },
{ label: '企业名称', value: displayValue(user.taxEnterpriseName) },
{ label: '地区编码', value: displayValue(user.taxRegionCode) },
{ label: '城市', value: displayValue(user.taxCityName) },
{ label: '企业地址', value: displayValue(user.taxEnterpriseAddress) },
{ label: '税务登记证', value: displayValue(user.taxRegistrationCertificate) }
]
})
const presetDetailItems = computed(() => {
const user = detailUser.value
if (!user) return []
return [
{ label: '开户行', value: displayValue(user.bankName) },
{ label: '银行账号', value: displayValue(user.bankAccount) },
{ label: '预设地址', value: displayValue(user.presetAddress) },
{ label: '预设电话', value: displayValue(user.presetPhone) }
]
})
const systemDetailItems = computed(() => {
const user = detailUser.value
if (!user) return []
return [
{ label: 'Token版本', value: displayValue(user.tokenVersion) },
{ label: '数据版本', value: displayValue(user.version) },
{ label: '最后登录时间', value: displayValue(user.lastLoginAt) },
{ label: '最后登录IP', value: displayValue(user.lastLoginIp) },
{ label: '创建时间', value: displayValue(user.createdAt) },
{ label: '创建人', value: displayValue(user.createdBy) },
{ label: '更新时间', value: displayValue(user.updatedAt) },
{ label: '更新人', value: displayValue(user.updatedBy) },
{ label: '删除时间', value: displayValue(user.deletedAt) },
{ label: '删除人', value: displayValue(user.deletedBy) }
]
})
const editModal = reactive({
visible: false,
mode: 'create' as 'create' | 'edit',
@@ -373,6 +538,29 @@ function openCreate() {
editModal.visible = true
}
function displayValue(value: unknown) {
if (value === null || value === undefined || value === '') return '-'
return String(value)
}
function formatOrg(user: UserDetail) {
if (user.orgName && user.orgCode) return `${user.orgName}${user.orgCode}`
return user.orgName || user.orgCode || user.orgId || '-'
}
async function openDetail(row: UserListItem) {
detailModal.visible = true
detailModal.loading = true
detailUser.value = null
try {
detailUser.value = await getUserDetailApi(row.id)
} catch {
message.error('查询用户详情失败')
} finally {
detailModal.loading = false
}
}
async function openEdit(row: UserListItem) {
editModal.mode = 'edit'
editModal.title = `编辑用户 - ${row.username}`
@@ -485,13 +673,24 @@ const columns = computed<DataTableColumns<UserListItem>>(() => [
{
title: '操作',
key: 'actions',
width: 360,
width: 430,
render: (row) =>
h(
NSpace,
{ size: 6 },
{
default: () => [
h(
NButton,
{
size: 'small',
tertiary: true,
type: 'info',
class: 'action-btn',
onClick: () => openDetail(row)
},
{ default: () => '详情' }
),
authStore.hasPermission('system:user:update')
? h(
NButton,
@@ -560,3 +759,119 @@ const columns = computed<DataTableColumns<UserListItem>>(() => [
}
])
</script>
<style scoped>
.detail-shell {
padding: 0 20px 20px;
}
.detail-header {
display: flex;
justify-content: space-between;
gap: 16px;
padding: 20px 0 16px;
border-bottom: 1px solid #eef1f5;
}
.detail-name {
color: #111827;
font-size: 18px;
font-weight: 600;
}
.detail-username {
margin-top: 4px;
color: #6b7280;
font-family: ui-monospace, SFMono-Regular, monospace;
font-size: 13px;
}
.detail-tags {
display: flex;
gap: 8px;
margin-top: 10px;
flex-wrap: wrap;
}
.detail-total {
display: flex;
flex-direction: column;
align-items: flex-end;
gap: 4px;
}
.detail-total span {
color: #6b7280;
font-size: 12px;
}
.detail-total strong {
color: #111827;
font-size: 24px;
font-weight: 600;
}
.detail-section {
padding-top: 18px;
}
.detail-section-title {
margin-bottom: 12px;
color: #111827;
font-size: 15px;
font-weight: 600;
}
.detail-grid {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 12px;
}
.detail-item {
padding: 14px;
border: 1px solid #eef1f5;
border-radius: 8px;
background: #fafafa;
}
.detail-item span {
display: block;
margin-bottom: 6px;
color: #6b7280;
font-size: 12px;
}
.detail-item strong {
color: #111827;
font-size: 14px;
font-weight: 500;
word-break: break-all;
}
.role-tags {
display: flex;
gap: 8px;
flex-wrap: wrap;
}
.empty-text {
color: #9ca3af;
font-size: 13px;
}
@media (max-width: 960px) {
.detail-grid {
grid-template-columns: 1fr;
}
.detail-header {
flex-direction: column;
align-items: stretch;
}
.detail-total {
align-items: flex-start;
}
}
</style>