mirror of
https://gitee.com/mirrors/AllinSSL.git
synced 2026-03-11 01:01:09 +08:00
【调整】监控页面整体优化
This commit is contained in:
@@ -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>
|
||||
}
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -1,6 +1,30 @@
|
||||
import type { DataTableColumns, DataTableInst, DataTableProps } from 'naive-ui'
|
||||
import type { Ref } from 'vue'
|
||||
|
||||
/** 列可见性配置接口 */
|
||||
export interface ColumnVisibility {
|
||||
/** 列的显示状态,key为列的key,value为是否显示 */
|
||||
[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
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user