【调整】监控页面整体优化

This commit is contained in:
chenzhihua
2025-07-17 11:04:36 +08:00
parent 624fc06f3e
commit 260601055f
81 changed files with 3316 additions and 925 deletions

View File

@@ -2,16 +2,20 @@ import { ref, shallowRef, ShallowRef, Ref, effectScope, watch, onUnmounted, isRe
import {
type DataTableProps,
type DataTableSlots,
type DataTableColumns,
type PaginationProps,
type PaginationSlots,
NDataTable,
NPagination,
NButton,
NDropdown,
NCheckbox,
NIcon,
} from 'naive-ui'
import { translation, TranslationModule, type TranslationLocale } from '../locals/translation'
import { useMessage } from './useMessage'
import type { UseTableOptions, TableInstanceWithComponent, TableResponse } from '../types/table'
import type { UseTableOptions, TableInstanceWithComponent, TableResponse, ColumnVisibility } from '../types/table'
// 获取当前语言
const currentLocale = localStorage.getItem('locale-active') || 'zhCN'
@@ -66,6 +70,67 @@ const savePageSizeToStorage = (storage: string, size: number): void => {
console.warn('保存pageSize到本地存储失败:', error)
}
}
/**
* 从本地存储获取列可见性配置的纯函数
* @param storage 存储的key
* @param columns 表格列配置
* @returns 列可见性配置
*/
const getStoredColumnVisibility = (storage: string, columns: any[]): ColumnVisibility => {
try {
if (!storage) return getDefaultColumnVisibility(columns)
const stored = localStorage.getItem(`table-column-settings-${storage}`)
if (stored) {
const parsedVisibility = JSON.parse(stored) as ColumnVisibility
// 验证存储的配置是否与当前列配置匹配
const defaultVisibility = getDefaultColumnVisibility(columns)
const mergedVisibility: ColumnVisibility = {}
// 合并默认配置和存储配置,确保新增的列默认显示
Object.keys(defaultVisibility).forEach((key) => {
mergedVisibility[key] = Object.prototype.hasOwnProperty.call(parsedVisibility, key)
? parsedVisibility[key]
: defaultVisibility[key]
})
return mergedVisibility
}
} catch (error) {
console.warn('读取本地存储列设置失败:', error)
}
return getDefaultColumnVisibility(columns)
}
/**
* 保存列可见性配置到本地存储的纯函数
* @param storage 存储的key
* @param visibility 列可见性配置
*/
const saveColumnVisibilityToStorage = (storage: string, visibility: ColumnVisibility): void => {
try {
if (storage) localStorage.setItem(`table-column-settings-${storage}`, JSON.stringify(visibility))
} catch (error) {
console.warn('保存列设置到本地存储失败:', error)
}
}
/**
* 获取默认列可见性配置的纯函数
* @param columns 表格列配置
* @returns 默认列可见性配置
*/
const getDefaultColumnVisibility = (columns: any[]): ColumnVisibility => {
const visibility: ColumnVisibility = {}
columns.forEach((column) => {
// 使用类型断言来访问 key 属性
const col = column as any
if (col.key) {
visibility[col.key] = true // 默认所有列都显示
}
})
return visibility
}
/**
* 表格钩子函数
* @param options 表格配置选项
@@ -92,6 +157,70 @@ export default function useTable<T = Record<string, any>, Z extends Record<strin
const props = shallowRef({}) as ShallowRef<DataTableProps> // 表格属性
const { error: errorMsg } = useMessage()
// 列设置状态
const columnVisibility = ref<ColumnVisibility>(getStoredColumnVisibility(storage, config))
// 计算过滤后的列配置
const filteredColumns = computed(() => {
return config.filter((column) => {
const col = column as any
if (!col.key) return true // 没有key的列始终显示
return columnVisibility.value[col.key] !== false
})
})
// 计算可见列的详细宽度信息
const visibleColumnsWidth = computed(() => {
let normalColumnsWidth = 0
let fixedColumnsWidth = 0
let totalWidth = 0
filteredColumns.value.forEach((column) => {
const col = column as any
if (col.width) {
// 处理数字和字符串类型的宽度
const width = typeof col.width === 'string' ? parseInt(col.width) : col.width
if (!isNaN(width)) {
totalWidth += width
if (col.fixed) {
fixedColumnsWidth += width
} else {
normalColumnsWidth += width
}
}
}
})
return {
totalWidth,
normalColumnsWidth,
fixedColumnsWidth,
}
})
// 精确计算动态 scroll-x 值
const dynamicScrollX = computed(() => {
const { totalWidth, normalColumnsWidth, fixedColumnsWidth } = visibleColumnsWidth.value
if (totalWidth <= 0) {
return undefined
}
// 精确的表格补偿计算
// 基于 Naive UI DataTable 的实际渲染需求
const TABLE_BORDER = 2 // 表格边框 (左右各1px)
const TABLE_PADDING = 16 // 表格内边距 (Naive UI 默认)
const SCROLL_COMPENSATION = 4 // 滚动区域补偿
// 总补偿宽度:保守且精确
const totalCompensation = TABLE_BORDER + TABLE_PADDING + SCROLL_COMPENSATION
// 最终宽度 = 实际列宽度 + 精确补偿
const preciseWidth = totalWidth + totalCompensation
return preciseWidth
})
// 分页相关状态
const { page, pageSize } = alias
const pageSizeOptionsRef = ref([10, 20, 50, 100, 200]) // 分页选项
@@ -107,7 +236,6 @@ export default function useTable<T = Record<string, any>, Z extends Record<strin
if ((param.value as Record<string, unknown>)[pageSize]) {
;(param.value as Record<string, unknown>)[pageSize] = getStoredPageSize(storage, 10, pageSizeOptionsRef.value) // 每页条数
console.log('初始化每页条数', (param.value as Record<string, unknown>)[pageSize])
}
/**
@@ -135,6 +263,29 @@ export default function useTable<T = Record<string, any>, Z extends Record<strin
fetchData()
}
/**
* 切换列可见性
* @param columnKey 列的key
*/
const toggleColumnVisibility = (columnKey: string) => {
columnVisibility.value = {
...columnVisibility.value,
[columnKey]: !columnVisibility.value[columnKey],
}
// 保存到本地存储
saveColumnVisibilityToStorage(storage, columnVisibility.value)
}
/**
* 重置列设置
*/
const resetColumnSettings = () => {
const defaultVisibility = getDefaultColumnVisibility(config)
columnVisibility.value = defaultVisibility
// 保存到本地存储
saveColumnVisibilityToStorage(storage, defaultVisibility)
}
/**
* 获取表格数据
*/
@@ -172,19 +323,34 @@ export default function useTable<T = Record<string, any>, Z extends Record<strin
const component = (props: DataTableProps, context: { slots?: DataTableSlots }) => {
const { slots, ...attrs } = props as any
const s2 = context
// 合并动态 scroll-x 值
const mergedProps = {
...props,
...attrs,
}
// 精确的 scroll-x 处理:确保容器宽度与内容宽度完全匹配
if (dynamicScrollX.value) {
// 始终使用动态计算的精确宽度,确保无浏览器自动拉伸
mergedProps.scrollX = dynamicScrollX.value
}
return (
<NDataTable
remote
ref={example}
loading={loading.value}
data={data.value.list}
columns={columns.value}
{...props}
{...attrs}
columns={filteredColumns.value}
scrollbar-props={{
xPlacement: 'top',
}}
{...mergedProps}
>
{{
empty: () => (slots?.empty || s2?.slots?.empty ? slots?.empty() || s2?.slots?.empty() : null),
loading: () => (slots?.loading || s2?.slots?.loading ? slots?.loading() || s2?.slots?.loading() : null),
empty: () => (slots?.empty || s2?.slots?.empty ? slots?.empty?.() || s2?.slots?.empty?.() : null),
loading: () => (slots?.loading || s2?.slots?.loading ? slots?.loading?.() || s2?.slots?.loading?.() : null),
}}
</NDataTable>
)
@@ -215,6 +381,82 @@ export default function useTable<T = Record<string, any>, Z extends Record<strin
)
}
/**
* 渲染列设置组件
*/
const columnSettingsComponent = () => {
// 生成下拉选项
const dropdownOptions = [
{
key: 'header',
type: 'render',
render: () => (
<div style="padding: 8px 12px; font-weight: 500; color: var(--n-text-color);">
{hookT('columnSettings')}
</div>
),
},
{
key: 'divider1',
type: 'divider',
},
...config
.filter((column) => (column as any).key)
.map((column) => {
const col = column as any
return {
key: col.key,
type: 'render',
render: () => (
<div
style="padding: 4px 12px; cursor: pointer; display: flex; align-items: center;"
onClick={(e: Event) => {
e.stopPropagation()
toggleColumnVisibility(col.key)
}}
>
<NCheckbox
checked={columnVisibility.value[col.key] !== false}
onUpdateChecked={() => toggleColumnVisibility(col.key)}
style="pointer-events: none;"
/>
<span style="margin-left: 8px; flex: 1;">{col.title || col.key}</span>
</div>
),
}
}),
{
key: 'divider2',
type: 'divider',
},
{
key: 'reset',
type: 'render',
render: () => (
<div
style="padding: 8px 12px; cursor: pointer; color: var(--n-color-target);"
onClick={() => resetColumnSettings()}
>
{hookT('resetColumns')}
</div>
),
},
]
return (
<NDropdown options={dropdownOptions} trigger="click" placement="bottom-end" showArrow={false}>
<NButton quaternary circle size="small" title={hookT('columnSettings')}>
<NIcon size={16}>
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M3 4H21V6H3V4ZM3 11H15V13H3V11ZM3 18H21V20H3V18Z" fill="currentColor" />
<path d="M16 11H18V13H16V11ZM19 11H21V13H19V11Z" fill="currentColor" />
</svg>
</NIcon>
</NButton>
</NDropdown>
)
}
// 检测到参数变化时,重新请求数据
if (Array.isArray(watchValue)) {
// 只监听指定的字段
@@ -225,10 +467,8 @@ export default function useTable<T = Record<string, any>, Z extends Record<strin
// 检查是否刚刚有直接请求,如果是则跳过此次 watch 触发的请求
const timeSinceLastDirectRequest = Date.now() - lastDirectRequestTime.value
if (timeSinceLastDirectRequest < REQUEST_DEBOUNCE_DELAY) {
console.log('跳过 watch 触发的重复请求,距离上次直接请求:', timeSinceLastDirectRequest, 'ms')
return
}
console.log('watch 触发请求,距离上次直接请求:', timeSinceLastDirectRequest, 'ms')
fetchData()
},
{ deep: true },
@@ -251,12 +491,19 @@ export default function useTable<T = Record<string, any>, Z extends Record<strin
fetch: fetchData<T>,
TableComponent: component,
PageComponent: paginationComponent,
ColumnSettingsComponent: columnSettingsComponent,
config: columns,
props,
storage,
handlePageChange,
handlePageSizeChange,
pageSizeOptions: pageSizeOptionsRef,
columnVisibility,
toggleColumnVisibility,
resetColumnSettings,
filteredColumns,
visibleColumnsWidth,
dynamicScrollX,
}
}) as TableInstanceWithComponent<T, Z>
}

View File

@@ -27,6 +27,11 @@ type TranslationTemplate = {
}
useTable: {
operation: string
columnSettings: string
showColumn: string
hideColumn: string
resetColumns: string
allColumns: string
}
}
@@ -80,6 +85,11 @@ export const translation = {
useTable: {
operation: '操作',
total: (total: number) => formatString('共 {} 条', total),
columnSettings: '列设置',
showColumn: '显示列',
hideColumn: '隐藏列',
resetColumns: '重置列设置',
allColumns: '全部列',
},
}),
zhTW: createTranslation({
@@ -142,6 +152,11 @@ export const translation = {
useTable: {
operation: 'Operation',
total: (total: number) => formatString('Total {} items', total),
columnSettings: 'Column Settings',
showColumn: 'Show Column',
hideColumn: 'Hide Column',
resetColumns: 'Reset Columns',
allColumns: 'All Columns',
},
}),
jaJP: createTranslation({

View File

@@ -1,6 +1,30 @@
import type { DataTableColumns, DataTableInst, DataTableProps } from 'naive-ui'
import type { Ref } from 'vue'
/** 列可见性配置接口 */
export interface ColumnVisibility {
/** 列的显示状态key为列的keyvalue为是否显示 */
[columnKey: string]: boolean
}
/** 列设置状态接口 */
export interface ColumnSettingsState {
/** 列可见性配置 */
visibility: ColumnVisibility
/** 表格唯一标识 */
tableId: string
}
/** 列设置组件属性接口 */
export interface ColumnSettingsProps {
/** 表格列配置 */
columns: DataTableColumns
/** 列可见性状态 */
visibility: ColumnVisibility
/** 可见性变更回调 */
onVisibilityChange: (visibility: ColumnVisibility) => void
}
/** 表格请求参数接口 */
export interface TableRequestParams {
/** 其他可能的查询参数 */
@@ -40,6 +64,8 @@ export interface TableInstanceWithComponent<T = Record<string, unknown>, Z = Rec
TableComponent: (props: Record<string, unknown>, context: Record<string, unknown>) => JSX.Element
/** 分页渲染组件用于渲染分页组件的Vue组件 */
PageComponent: (props: Record<string, unknown>, context: Record<string, unknown>) => JSX.Element
/** 列设置渲染组件用于渲染列设置下拉组件的Vue组件 */
ColumnSettingsComponent: () => JSX.Element
loading: Ref<boolean> // 加载状态
tableAlias: Ref<{ total: string; list: string }> // 表格别名
data: Ref<{ list: T[]; total: number }> // 表格数据引用
@@ -53,6 +79,12 @@ export interface TableInstanceWithComponent<T = Record<string, unknown>, Z = Rec
handlePageChange: (currentPage: number) => void // 分页改变
handlePageSizeChange: (size: number) => void // 分页大小改变
pageSizeOptions: Ref<number[]> // 分页大小选项
/** 列可见性状态 */
columnVisibility: Ref<ColumnVisibility>
/** 切换列可见性 */
toggleColumnVisibility: (columnKey: string) => void
/** 重置列设置 */
resetColumnSettings: () => void
}
/**

View File

@@ -2,6 +2,7 @@ import { defineComponent, ref, computed } from 'vue'
import { NSpace, NTabs, NTabPane, NBackTop, NButton } from 'naive-ui'
import TableDemo from './tabs/TableDemo'
import FormDemo from './tabs/FormDemo'
import ColumnSettingsDemo from './tabs/ColumnSettingsDemo'
import { useModal } from '../hooks/useModal'
// import FormBuilder from '../components/FormBuilder'
@@ -14,6 +15,8 @@ export default defineComponent({
return '动态表格'
} else if (tabName.value === 'form') {
return '动态表单'
} else if (tabName.value === 'column-settings') {
return '列设置功能'
} else if (tabName.value === 'builder') {
return '表单构建器'
}
@@ -41,6 +44,9 @@ export default defineComponent({
<NTabPane name="form" tab="动态表单">
<FormDemo />
</NTabPane>
<NTabPane name="column-settings" tab="列设置功能">
<ColumnSettingsDemo />
</NTabPane>
<NTabPane name="builder" tab="表单构建器">
{/* <FormBuilder /> */}
</NTabPane>