Files
platforms/frontend/platform_admin/src/views/resource/ResourceDetailContent.vue

215 lines
5.9 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!--
功能以响应式信息卡和子表页签展示标准资源详情
版本v1.1.0
-->
<template>
<div class="detail-stack">
<a-card title="基本信息" :bordered="false" class="detail-card">
<div class="detail-grid">
<div v-for="entry in entries" :key="entry.key" class="detail-item" :class="{ 'detail-item-wide': entry.wide }">
<span class="detail-label">{{ entry.label }}</span>
<pre v-if="entry.objectValue" class="json-value">{{ entry.value }}</pre>
<IdentityText v-else-if="entry.key === 'identity'" :value="String(entry.value)" />
<span v-else class="detail-value">{{ entry.value }}</span>
</div>
</div>
</a-card>
<a-card v-if="collections.length" title="关联记录" :bordered="false" class="detail-card">
<a-tabs>
<a-tab-pane v-for="collection in collections" :key="collection.key" :title="collection.title">
<a-table :data="collection.rows" :pagination="false" size="small" table-layout-fixed>
<template #columns>
<a-table-column
v-for="column in collection.columns"
:key="column"
:title="resourceFieldLabel(definition, column)"
:width="column.includes('identity') ? 220 : 160"
ellipsis
tooltip
>
<template #cell="{ record }">
<IdentityText v-if="column.includes('identity') && record[column]" :value="String(record[column])" />
<template v-else>{{ displayRawValue(column, record[column]) }}</template>
</template>
</a-table-column>
</template>
</a-table>
</a-tab-pane>
</a-tabs>
</a-card>
</div>
</template>
<script setup lang="ts">
import { computed } from 'vue';
import IdentityText from '@/components/IdentityText.vue';
import {
displayRawValue,
displayResourceField,
isEmptyDeletedAt,
primaryRecord,
resourceFieldLabel,
} from '@/api/resource-display';
import type { ResourceUiDefinition } from '@/api/resources';
import type { ResourceRow } from '@/api/resource-page-rules';
const props = defineProps<{
definition: ResourceUiDefinition;
detail: ResourceRow;
relationOptions: Record<string, ResourceRow[]>;
accountSummary: boolean;
}>();
type DetailEntry = {
key: string;
label: string;
value: string;
objectValue: boolean;
wide: boolean;
};
const entries = computed<DetailEntry[]>(() => {
const row = primaryRecord(props.detail);
const excluded = new Set(['id', 'password', 'password_hash', 'avatar']);
if (props.accountSummary) {
for (const key of ['username', 'identity', 'created_at']) excluded.add(key);
}
const preferred = [
'identity',
'status',
...props.definition.fields.map((field) => field.key),
'created_at',
'updated_at',
];
const keys = [...new Set([...preferred, ...Object.keys(row)])];
return keys.flatMap((key) => {
const maskedKey = `${key}_masked`;
const actualKey = Object.prototype.hasOwnProperty.call(row, key)
? key
: Object.prototype.hasOwnProperty.call(row, maskedKey)
? maskedKey
: '';
if (!actualKey || excluded.has(key) || key.endsWith('_id')) return [];
const value = row[actualKey];
if (Array.isArray(value)) return [];
if (['deleted_at', 'DeletedAt'].includes(key) && isEmptyDeletedAt(value))
return [];
const field = props.definition.fields.find((item) => item.key === key);
const display = field
? displayResourceField(field, row, props.relationOptions)
: displayRawValue(actualKey, value);
const objectValue = typeof value === 'object' && value !== null;
return [
{
key,
label: resourceFieldLabel(props.definition, actualKey),
value: display,
objectValue,
wide:
objectValue ||
/(address|terms|content|body|remark|reason|params|args)$/.test(key),
},
];
});
});
const collections = computed(() =>
Object.entries(props.detail)
.filter(([, value]) => Array.isArray(value) && value.length > 0)
.map(([key, value]) => {
const rows = value as ResourceRow[];
const columns = [
...new Set(
rows.flatMap((row) =>
Object.keys(row).filter(
(column) => column !== 'id' && !column.endsWith('_id'),
),
),
),
].slice(0, 8);
return {
key,
title: resourceFieldLabel(props.definition, key),
rows,
columns,
};
}),
);
</script>
<style scoped lang="less">
.detail-stack {
display: grid;
gap: 12px;
}
.detail-card {
border-radius: 10px;
}
.detail-card :deep(.arco-card-header) {
height: 48px;
padding: 0 24px;
}
.detail-card :deep(.arco-card-body) {
padding: 20px 24px;
}
.detail-grid {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 8px 42px;
padding: 0;
}
.detail-item {
display: grid;
grid-template-columns: 120px minmax(0, 1fr);
align-items: start;
min-height: 38px;
padding: 8px 0;
border-bottom: 1px solid var(--color-border-1);
}
.detail-item-wide {
grid-column: 1 / -1;
}
.detail-label {
color: var(--color-text-3);
text-align: right;
padding-right: 18px;
}
.detail-value {
color: var(--color-text-1);
white-space: pre-wrap;
word-break: break-word;
}
.json-value {
max-height: 360px;
margin: 0;
padding: 12px;
overflow: auto;
color: var(--color-text-1);
background: var(--color-fill-1);
border-radius: 6px;
white-space: pre-wrap;
}
@media (max-width: 1280px) {
.detail-grid {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
}
@media (max-width: 760px) {
.detail-card :deep(.arco-card-header),
.detail-card :deep(.arco-card-body) {
padding-right: 16px;
padding-left: 16px;
}
.detail-grid {
grid-template-columns: 1fr;
gap: 4px;
}
.detail-item,
.detail-item-wide {
grid-column: auto;
grid-template-columns: 100px minmax(0, 1fr);
}
}
</style>