Merge branch 'feature/datatable-enhancements'
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<Modal :show="show" :title="t('admin.accounts.editAccount')" size="lg" @close="handleClose">
|
||||
<Modal :show="show" :title="t('admin.accounts.editAccount')" size="xl" @close="handleClose">
|
||||
<form v-if="account" @submit.prevent="handleSubmit" class="space-y-5">
|
||||
<div>
|
||||
<label class="input-label">{{ t('common.name') }}</label>
|
||||
|
||||
@@ -15,7 +15,8 @@
|
||||
:key="column.key"
|
||||
scope="col"
|
||||
:class="[
|
||||
'sticky-header-cell px-6 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500 dark:text-dark-400',
|
||||
'sticky-header-cell py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500 dark:text-dark-400',
|
||||
getAdaptivePaddingClass(),
|
||||
{ 'cursor-pointer hover:bg-gray-100 dark:hover:bg-dark-700': column.sortable },
|
||||
getStickyColumnClass(column, index)
|
||||
]"
|
||||
@@ -81,7 +82,7 @@
|
||||
<tbody class="table-body divide-y divide-gray-200 bg-white dark:divide-dark-700 dark:bg-dark-900">
|
||||
<!-- Loading skeleton -->
|
||||
<tr v-if="loading" v-for="i in 5" :key="i">
|
||||
<td v-for="column in columns" :key="column.key" class="whitespace-nowrap px-6 py-4">
|
||||
<td v-for="column in columns" :key="column.key" :class="['whitespace-nowrap py-4', getAdaptivePaddingClass()]">
|
||||
<div class="animate-pulse">
|
||||
<div class="h-4 w-3/4 rounded bg-gray-200 dark:bg-dark-700"></div>
|
||||
</div>
|
||||
@@ -92,7 +93,7 @@
|
||||
<tr v-else-if="!data || data.length === 0">
|
||||
<td
|
||||
:colspan="columns.length"
|
||||
class="px-6 py-12 text-center text-gray-500 dark:text-dark-400"
|
||||
:class="['py-12 text-center text-gray-500 dark:text-dark-400', getAdaptivePaddingClass()]"
|
||||
>
|
||||
<slot name="empty">
|
||||
<div class="flex flex-col items-center">
|
||||
@@ -128,7 +129,8 @@
|
||||
v-for="(column, colIndex) in columns"
|
||||
:key="column.key"
|
||||
:class="[
|
||||
'whitespace-nowrap px-6 py-4 text-sm text-gray-900 dark:text-gray-100',
|
||||
'whitespace-nowrap py-4 text-sm text-gray-900 dark:text-gray-100',
|
||||
getAdaptivePaddingClass(),
|
||||
getStickyColumnClass(column, colIndex)
|
||||
]"
|
||||
>
|
||||
@@ -165,24 +167,46 @@ const checkScrollable = () => {
|
||||
const checkActionsColumnWidth = () => {
|
||||
if (!tableWrapperRef.value) return
|
||||
|
||||
// 查找操作列的表头单元格
|
||||
const actionsHeader = tableWrapperRef.value.querySelector('th:has(button[title*="Expand"], button[title*="展开"])')
|
||||
if (!actionsHeader) return
|
||||
|
||||
// 查找第一行的操作列单元格
|
||||
const firstActionCell = tableWrapperRef.value.querySelector('tbody tr:first-child td:last-child')
|
||||
if (!firstActionCell) return
|
||||
|
||||
// 获取操作列内容的实际宽度
|
||||
const actionsContent = firstActionCell.querySelector('div')
|
||||
if (!actionsContent) return
|
||||
// 查找操作列内容的容器div
|
||||
const actionsContainer = firstActionCell.querySelector('div')
|
||||
if (!actionsContainer) return
|
||||
|
||||
// 比较内容宽度和单元格宽度
|
||||
const contentWidth = actionsContent.scrollWidth
|
||||
const cellWidth = (firstActionCell as HTMLElement).clientWidth
|
||||
// 临时展开以测量完整宽度
|
||||
const wasExpanded = actionsExpanded.value
|
||||
actionsExpanded.value = true
|
||||
|
||||
// 如果内容宽度超过单元格宽度,说明需要展开
|
||||
actionsColumnNeedsExpanding.value = contentWidth > cellWidth
|
||||
// 等待DOM更新
|
||||
nextTick(() => {
|
||||
// 测量所有按钮的总宽度
|
||||
const buttons = actionsContainer.querySelectorAll('button')
|
||||
if (buttons.length <= 2) {
|
||||
actionsColumnNeedsExpanding.value = false
|
||||
actionsExpanded.value = wasExpanded
|
||||
return
|
||||
}
|
||||
|
||||
// 计算所有按钮的总宽度(包括gap)
|
||||
let totalWidth = 0
|
||||
buttons.forEach((btn, index) => {
|
||||
totalWidth += (btn as HTMLElement).offsetWidth
|
||||
if (index < buttons.length - 1) {
|
||||
totalWidth += 4 // gap-1 = 4px
|
||||
}
|
||||
})
|
||||
|
||||
// 获取单元格可用宽度(减去padding)
|
||||
const cellWidth = (firstActionCell as HTMLElement).clientWidth - 32 // 减去左右padding
|
||||
|
||||
// 如果总宽度超过可用宽度,需要展开功能
|
||||
actionsColumnNeedsExpanding.value = totalWidth > cellWidth
|
||||
|
||||
// 恢复原来的展开状态
|
||||
actionsExpanded.value = wasExpanded
|
||||
})
|
||||
}
|
||||
|
||||
// 监听尺寸变化
|
||||
@@ -219,6 +243,7 @@ interface Props {
|
||||
stickyFirstColumn?: boolean
|
||||
stickyActionsColumn?: boolean
|
||||
expandableActions?: boolean
|
||||
actionsCount?: number // 操作按钮总数,用于判断是否需要展开功能
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
@@ -232,9 +257,10 @@ const sortKey = ref<string>('')
|
||||
const sortOrder = ref<'asc' | 'desc'>('asc')
|
||||
const actionsExpanded = ref(false)
|
||||
|
||||
// 数据/列/展开状态变化时重新检查滚动状态
|
||||
// 数据/列变化时重新检查滚动状态
|
||||
// 注意:不能监听 actionsExpanded,因为 checkActionsColumnWidth 会临时修改它,会导致无限循环
|
||||
watch(
|
||||
[() => props.data.length, () => props.columns, actionsExpanded],
|
||||
[() => props.data.length, () => props.columns],
|
||||
async () => {
|
||||
await nextTick()
|
||||
checkScrollable()
|
||||
@@ -243,6 +269,12 @@ watch(
|
||||
{ flush: 'post' }
|
||||
)
|
||||
|
||||
// 单独监听展开状态变化,只更新滚动状态
|
||||
watch(actionsExpanded, async () => {
|
||||
await nextTick()
|
||||
checkScrollable()
|
||||
})
|
||||
|
||||
const handleSort = (key: string) => {
|
||||
if (sortKey.value === key) {
|
||||
sortOrder.value = sortOrder.value === 'asc' ? 'desc' : 'asc'
|
||||
@@ -268,6 +300,12 @@ const sortedData = computed(() => {
|
||||
|
||||
// 检查是否有可展开的操作列
|
||||
const hasExpandableActions = computed(() => {
|
||||
// 如果明确指定了actionsCount,使用它来判断
|
||||
if (props.actionsCount !== undefined) {
|
||||
return props.expandableActions && props.columns.some((col) => col.key === 'actions') && props.actionsCount > 2
|
||||
}
|
||||
|
||||
// 否则使用原来的检测逻辑
|
||||
return (
|
||||
props.expandableActions &&
|
||||
props.columns.some((col) => col.key === 'actions') &&
|
||||
@@ -312,6 +350,22 @@ const getStickyColumnClass = (column: Column, index: number) => {
|
||||
|
||||
return classes.join(' ')
|
||||
}
|
||||
|
||||
// 根据列数自适应调整内边距
|
||||
const getAdaptivePaddingClass = () => {
|
||||
const columnCount = props.columns.length
|
||||
|
||||
// 列数越多,内边距越小
|
||||
if (columnCount >= 10) {
|
||||
return 'px-2' // 8px
|
||||
} else if (columnCount >= 7) {
|
||||
return 'px-3' // 12px
|
||||
} else if (columnCount >= 5) {
|
||||
return 'px-4' // 16px
|
||||
} else {
|
||||
return 'px-6' // 24px (原始值)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
@@ -1172,9 +1172,9 @@ export default {
|
||||
batchAdd: 'Quick Add',
|
||||
batchInput: 'Proxy List',
|
||||
batchInputPlaceholder:
|
||||
"Enter one proxy per line in the following formats:\nsocks5://user:pass{'@'}192.168.1.1:1080\nhttp://192.168.1.1:8080\nhttps://user:pass{'@'}proxy.example.com:443",
|
||||
"Enter one proxy per line in the following formats:\nsocks5://user:pass@192.168.1.1:1080\nhttp://192.168.1.1:8080\nhttps://user:pass@proxy.example.com:443",
|
||||
batchInputHint:
|
||||
"Supports http, https, socks5 protocols. Format: protocol://[user:pass{'@'}]host:port",
|
||||
"Supports http, https, socks5 protocols. Format: protocol://[user:pass@]host:port",
|
||||
parsedCount: '{count} valid',
|
||||
invalidCount: '{count} invalid',
|
||||
duplicateCount: '{count} duplicate',
|
||||
@@ -1351,12 +1351,12 @@ export default {
|
||||
port: 'SMTP Port',
|
||||
portPlaceholder: '587',
|
||||
username: 'SMTP Username',
|
||||
usernamePlaceholder: 'your-email@gmail.com',
|
||||
usernamePlaceholder: "your-email{'@'}gmail.com",
|
||||
password: 'SMTP Password',
|
||||
passwordPlaceholder: '********',
|
||||
passwordHint: 'Leave empty to keep existing password',
|
||||
fromEmail: 'From Email',
|
||||
fromEmailPlaceholder: 'noreply@example.com',
|
||||
fromEmailPlaceholder: "noreply{'@'}example.com",
|
||||
fromName: 'From Name',
|
||||
fromNamePlaceholder: 'Sub2API',
|
||||
useTls: 'Use TLS',
|
||||
@@ -1366,7 +1366,7 @@ export default {
|
||||
title: 'Send Test Email',
|
||||
description: 'Send a test email to verify your SMTP configuration',
|
||||
recipientEmail: 'Recipient Email',
|
||||
recipientEmailPlaceholder: 'test@example.com',
|
||||
recipientEmailPlaceholder: "test{'@'}example.com",
|
||||
sendTestEmail: 'Send Test Email',
|
||||
sending: 'Sending...',
|
||||
enterRecipientHint: 'Please enter a recipient email address'
|
||||
|
||||
@@ -1321,8 +1321,8 @@ export default {
|
||||
batchAdd: '快捷添加',
|
||||
batchInput: '代理列表',
|
||||
batchInputPlaceholder:
|
||||
"每行输入一个代理,支持以下格式:\nsocks5://user:pass{'@'}192.168.1.1:1080\nhttp://192.168.1.1:8080\nhttps://user:pass{'@'}proxy.example.com:443",
|
||||
batchInputHint: "支持 http、https、socks5 协议,格式:协议://[用户名:密码{'@'}]主机:端口",
|
||||
"每行输入一个代理,支持以下格式:\nsocks5://user:pass@192.168.1.1:1080\nhttp://192.168.1.1:8080\nhttps://user:pass@proxy.example.com:443",
|
||||
batchInputHint: "支持 http、https、socks5 协议,格式:协议://[用户名:密码@]主机:端口",
|
||||
parsedCount: '有效 {count} 个',
|
||||
invalidCount: '无效 {count} 个',
|
||||
duplicateCount: '重复 {count} 个',
|
||||
@@ -1549,12 +1549,12 @@ export default {
|
||||
port: 'SMTP 端口',
|
||||
portPlaceholder: '587',
|
||||
username: 'SMTP 用户名',
|
||||
usernamePlaceholder: 'your-email@gmail.com',
|
||||
usernamePlaceholder: "your-email{'@'}gmail.com",
|
||||
password: 'SMTP 密码',
|
||||
passwordPlaceholder: '********',
|
||||
passwordHint: '留空以保留现有密码',
|
||||
fromEmail: '发件人邮箱',
|
||||
fromEmailPlaceholder: 'noreply@example.com',
|
||||
fromEmailPlaceholder: "noreply{'@'}example.com",
|
||||
fromName: '发件人名称',
|
||||
fromNamePlaceholder: 'Sub2API',
|
||||
useTls: '使用 TLS',
|
||||
@@ -1564,7 +1564,7 @@ export default {
|
||||
title: '发送测试邮件',
|
||||
description: '发送测试邮件以验证 SMTP 配置',
|
||||
recipientEmail: '收件人邮箱',
|
||||
recipientEmailPlaceholder: 'test@example.com',
|
||||
recipientEmailPlaceholder: "test{'@'}example.com",
|
||||
sendTestEmail: '发送测试邮件',
|
||||
sending: '发送中...',
|
||||
enterRecipientHint: '请输入收件人邮箱地址'
|
||||
|
||||
@@ -165,7 +165,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<DataTable :columns="columns" :data="accounts" :loading="loading">
|
||||
<DataTable :columns="columns" :data="accounts" :loading="loading" :actions-count="6">
|
||||
<template #cell-select="{ row }">
|
||||
<input
|
||||
type="checkbox"
|
||||
|
||||
@@ -85,7 +85,7 @@
|
||||
|
||||
<!-- Users Table -->
|
||||
<template #table>
|
||||
<DataTable :columns="columns" :data="users" :loading="loading">
|
||||
<DataTable :columns="columns" :data="users" :loading="loading" :actions-count="7">
|
||||
<template #cell-email="{ value }">
|
||||
<div class="flex items-center gap-2">
|
||||
<div
|
||||
|
||||
Reference in New Issue
Block a user