fix: 优化云端变量记录页面样式,对齐其他表格页面

- 移除ID列的font-mono text-xs样式,改用与其他页面一致的truncate样式
- 移除toolbar顶部多余的批量删除按钮,只保留BulkActions组件内的
- 搜索框和日期选择器尺寸对齐其他页面(h-8)
- 日期格式化对齐其他页面(zh-CN格式)
- 动态字段值显示改用truncate+title,替代pre标签
- 移除raw_data调试字段和诊断卡片
This commit is contained in:
2026-05-08 09:31:47 +08:00
parent 375fd46224
commit 29fb448d04
2 changed files with 17 additions and 59 deletions
-1
View File
@@ -755,7 +755,6 @@ func handleGetCloudVariableRecords(c *gin.Context) {
record := gin.H{ record := gin.H{
"id": r.ID, "id": r.ID,
"data": parsedData, "data": parsedData,
"raw_data": r.Data,
"created_at": r.CreatedAt, "created_at": r.CreatedAt,
} }
if r.AppUserID != nil { if r.AppUserID != nil {
@@ -1,7 +1,7 @@
<script setup lang="ts"> <script setup lang="ts">
import type { ColumnDef } from '@tanstack/vue-table' import type { ColumnDef } from '@tanstack/vue-table'
import { List, Loader2, Search, Trash2 } from 'lucide-vue-next' import { List, Search, Trash2 } from 'lucide-vue-next'
import { computed, h, onMounted, ref, watch } from 'vue' import { computed, h, onMounted, ref, watch } from 'vue'
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
import { useRoute, useRouter } from 'vue-router' import { useRoute, useRouter } from 'vue-router'
@@ -30,7 +30,6 @@ interface Variable {
interface VariableRecord { interface VariableRecord {
id: number id: number
data: any data: any
raw_data?: string
user_id?: number user_id?: number
user?: { user?: {
id: number id: number
@@ -40,16 +39,7 @@ interface VariableRecord {
} }
function parseRecordData(record: VariableRecord): Record<string, any> { function parseRecordData(record: VariableRecord): Record<string, any> {
let rawData = record.data const rawData = record.data
if ((rawData == null || rawData === '') && record.raw_data) {
try {
rawData = JSON.parse(record.raw_data)
}
catch {
rawData = record.raw_data
}
}
if (rawData == null || rawData === '') return {} if (rawData == null || rawData === '') return {}
@@ -118,24 +108,13 @@ const dynamicKeys = computed(() => {
return Array.from(keys).sort() return Array.from(keys).sort()
}) })
const debugInfo = computed(() => {
if (records.value.length === 0 || dynamicKeys.value.length > 0) return null
const first = records.value[0]
return {
dataType: first.data === null ? 'null' : first.data === undefined ? 'undefined' : typeof first.data,
dataValue: first.data === null ? 'null' : first.data === undefined ? 'undefined' : typeof first.data === 'object' ? JSON.stringify(first.data) : String(first.data),
rawDataField: first.raw_data === undefined ? '(字段不存在)' : first.raw_data === '' ? '(空字符串)' : first.raw_data || '(falsy)',
parsedKeys: (() => { const p = parseRecordData(first); return p && typeof p === 'object' ? Object.keys(p) : [] })(),
}
})
const columns = computed<ColumnDef<VariableRecord>[]>(() => { const columns = computed<ColumnDef<VariableRecord>[]>(() => {
const cols: ColumnDef<VariableRecord>[] = [SelectColumn as ColumnDef<VariableRecord>] const cols: ColumnDef<VariableRecord>[] = [SelectColumn as ColumnDef<VariableRecord>]
cols.push({ cols.push({
accessorKey: 'id', accessorKey: 'id',
header: () => 'ID', header: () => 'ID',
cell: ({ row }) => h('span', { class: 'text-muted-foreground font-mono text-xs' }, String(row.original.id)), cell: ({ row }) => h('div', { class: 'max-w-xs truncate' }, String(row.original.id)),
size: 80, size: 80,
}) })
@@ -169,12 +148,12 @@ const columns = computed<ColumnDef<VariableRecord>[]>(() => {
return h('span', { class: 'text-muted-foreground' }, '-') return h('span', { class: 'text-muted-foreground' }, '-')
} }
if (typeof value === 'object') { if (typeof value === 'object') {
return h('pre', { class: 'text-xs bg-muted px-2 py-1 rounded max-w-xs overflow-auto max-h-20' }, JSON.stringify(value, null, 2)) return h('div', { class: 'max-w-xs truncate', title: JSON.stringify(value) }, JSON.stringify(value))
} }
if (typeof value === 'boolean') { if (typeof value === 'boolean') {
return h(Badge, { variant: value ? 'default' : 'secondary' }, () => String(value)) return h(Badge, { variant: value ? 'default' : 'secondary' }, () => String(value))
} }
return h('span', { class: 'truncate max-w-xs block', title: String(value) }, String(value)) return h('div', { class: 'max-w-xs truncate', title: String(value) }, String(value))
}, },
size: 150, size: 150,
}) })
@@ -189,7 +168,7 @@ const columns = computed<ColumnDef<VariableRecord>[]>(() => {
if (!data || Object.keys(data).length === 0) { if (!data || Object.keys(data).length === 0) {
return h('span', { class: 'text-muted-foreground' }, '-') return h('span', { class: 'text-muted-foreground' }, '-')
} }
return h('pre', { class: 'text-xs bg-muted px-2 py-1 rounded max-w-md overflow-auto max-h-20' }, JSON.stringify(data, null, 2)) return h('div', { class: 'max-w-md truncate', title: JSON.stringify(data) }, JSON.stringify(data))
}, },
}) })
} }
@@ -203,7 +182,13 @@ const columns = computed<ColumnDef<VariableRecord>[]>(() => {
try { try {
const date = new Date(createdAt) const date = new Date(createdAt)
if (Number.isNaN(date.getTime())) return '-' if (Number.isNaN(date.getTime())) return '-'
return h('span', { class: 'text-muted-foreground text-xs' }, date.toLocaleString()) return date.toLocaleString('zh-CN', {
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
})
} }
catch { catch {
return '-' return '-'
@@ -440,35 +425,11 @@ onMounted(async () => {
</UiCard> </UiCard>
</div> </div>
<div v-if="debugInfo" class="border border-dashed border-yellow-500/50 bg-yellow-500/5 rounded-md p-4 text-sm">
<p class="font-medium text-yellow-600 mb-2">数据诊断 (Data Debug)</p>
<div class="grid grid-cols-1 gap-2 text-xs font-mono">
<div>data 类型: <span class="text-yellow-700">{{ debugInfo.dataType }}</span></div>
<div>data : <span class="text-yellow-700 break-all">{{ debugInfo.dataValue.substring(0, 500) }}</span></div>
<div>raw_data (数据库原始): <span class="text-yellow-700 break-all">{{ debugInfo.rawDataField.substring(0, 500) }}</span></div>
<div>解析后 keys: <span class="text-yellow-700">{{ debugInfo.parsedKeys.length > 0 ? debugInfo.parsedKeys.join(', ') : '(空)' }}</span></div>
</div>
</div>
<UiCard> <UiCard>
<UiCardContent class="p-6"> <UiCardContent class="p-6">
<DataTable :columns="columns" :data="records" :loading="loading" :table="table" :server-pagination="serverPagination" @refresh="fetchRecords"> <DataTable :columns="columns" :data="records" :loading="loading" :table="table" :server-pagination="serverPagination" @refresh="fetchRecords">
<template #toolbar> <template #toolbar>
<div class="space-y-3"> <div class="space-y-3">
<div class="flex items-center justify-end">
<UiButton
variant="destructive"
size="sm"
:disabled="table.getSelectedRowModel().rows.length === 0"
@click="openBatchDeleteDialog"
>
<Trash2 class="h-4 w-4 mr-1" />
{{ t('admin.cloudVariables.records.batchDelete') }}
<span v-if="table.getSelectedRowModel().rows.length > 0" class="ml-1">
({{ table.getSelectedRowModel().rows.length }})
</span>
</UiButton>
</div>
<BulkActions :table="table" entity-name="records"> <BulkActions :table="table" entity-name="records">
<UiButton variant="destructive" size="sm" @click="openBatchDeleteDialog"> <UiButton variant="destructive" size="sm" @click="openBatchDeleteDialog">
<Trash2 class="h-4 w-4 mr-1" /> <Trash2 class="h-4 w-4 mr-1" />
@@ -482,23 +443,21 @@ onMounted(async () => {
<Input <Input
v-model="searchQuery" v-model="searchQuery"
:placeholder="t('admin.cloudVariables.records.searchPlaceholder')" :placeholder="t('admin.cloudVariables.records.searchPlaceholder')"
class="pl-8 w-[250px] h-9" class="pl-8 h-8 w-[150px] lg:w-[250px]"
@keydown.enter="handleSearch" @keydown.enter="handleSearch"
/> />
</div> </div>
<Input <Input
v-model="startDate" v-model="startDate"
type="date" type="date"
class="w-36 h-9" class="h-8 w-[140px]"
:placeholder="t('admin.cloudVariables.records.startDate')"
/> />
<Input <Input
v-model="endDate" v-model="endDate"
type="date" type="date"
class="w-36 h-9" class="h-8 w-[140px]"
:placeholder="t('admin.cloudVariables.records.endDate')"
/> />
<Button variant="outline" size="sm" class="h-9" @click="handleSearch"> <Button variant="outline" size="sm" class="h-8" @click="handleSearch">
<Search class="h-4 w-4 mr-1" /> <Search class="h-4 w-4 mr-1" />
{{ t('admin.cloudVariables.records.search') }} {{ t('admin.cloudVariables.records.search') }}
</Button> </Button>