fix: 优化云端变量记录页面样式,对齐其他表格页面
- 移除ID列的font-mono text-xs样式,改用与其他页面一致的truncate样式 - 移除toolbar顶部多余的批量删除按钮,只保留BulkActions组件内的 - 搜索框和日期选择器尺寸对齐其他页面(h-8) - 日期格式化对齐其他页面(zh-CN格式) - 动态字段值显示改用truncate+title,替代pre标签 - 移除raw_data调试字段和诊断卡片
This commit is contained in:
@@ -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>
|
||||||
|
|||||||
Reference in New Issue
Block a user