From 16793c82efe598cfad420283a679eb1024bb43fb Mon Sep 17 00:00:00 2001 From: chengqian Date: Wed, 12 Aug 2026 19:08:03 +0800 Subject: [PATCH] =?UTF-8?q?improve(ui):=E4=BC=98=E5=8C=96=E8=A1=A8?= =?UTF-8?q?=E6=A0=BC=E7=A7=BB=E5=8A=A8=E7=AB=AF=E6=98=BE=E7=A4=BA=E6=95=88?= =?UTF-8?q?=E6=9E=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/components/common/BasePagination.vue | 29 +- .../components/common/ResponsiveDataTable.vue | 254 ++++++++++++++++++ web/tenant/src/composables/useIsMobile.ts | 32 +++ web/tenant/src/views/finance/OrdersPage.vue | 11 +- web/tenant/src/views/finance/RedeemPage.vue | 10 +- .../src/views/finance/TransactionsPage.vue | 12 +- web/tenant/src/views/finance/WalletPage.vue | 10 +- web/tenant/src/views/service/ApiKeysPage.vue | 11 +- web/tenant/src/views/service/DocsPage.vue | 13 +- .../src/views/service/ModelComparisonPage.vue | 9 +- .../views/service/RequestAuditLogsPage.vue | 15 +- web/tenant/src/views/service/TaskLogsPage.vue | 14 +- .../src/views/service/UsageLogsPage.vue | 23 +- .../service/playground/ParamRefModal.vue | 7 +- .../src/views/settings/LoginHistoryPage.vue | 10 +- web/tenant/src/views/support/TicketsPage.vue | 19 +- web/tenant/src/views/team/InvitationsPage.vue | 11 +- web/tenant/src/views/team/MembersPage.vue | 21 +- .../src/views/team/ProjectDetailPage.vue | 22 +- web/tenant/src/views/team/ProjectsPage.vue | 20 +- 20 files changed, 474 insertions(+), 79 deletions(-) create mode 100644 web/tenant/src/components/common/ResponsiveDataTable.vue create mode 100644 web/tenant/src/composables/useIsMobile.ts diff --git a/web/tenant/src/components/common/BasePagination.vue b/web/tenant/src/components/common/BasePagination.vue index 751ea9e..770cf05 100644 --- a/web/tenant/src/components/common/BasePagination.vue +++ b/web/tenant/src/components/common/BasePagination.vue @@ -1,5 +1,6 @@ \ No newline at end of file +
+ 共 {{ total }} 条 + +
+ diff --git a/web/tenant/src/components/common/ResponsiveDataTable.vue b/web/tenant/src/components/common/ResponsiveDataTable.vue new file mode 100644 index 0000000..bd437e5 --- /dev/null +++ b/web/tenant/src/components/common/ResponsiveDataTable.vue @@ -0,0 +1,254 @@ + + + diff --git a/web/tenant/src/composables/useIsMobile.ts b/web/tenant/src/composables/useIsMobile.ts new file mode 100644 index 0000000..e626ec6 --- /dev/null +++ b/web/tenant/src/composables/useIsMobile.ts @@ -0,0 +1,32 @@ +import { ref, onMounted, onBeforeUnmount } from 'vue' + +/** + * 响应式断点检测:当前视口宽度 < breakpoint 时视为移动端。 + * 默认 768(对应 Tailwind 的 md 断点),超过则在桌面/移动之间实时切换。 + * + * 用法: + * const { isMobile } = useIsMobile() + * watch(isMobile, v => { ... }) + */ +export function useIsMobile(breakpoint = 768) { + const isMobile = ref(false) + let mql: MediaQueryList | null = null + + function update(e: MediaQueryListEvent | MediaQueryList) { + isMobile.value = e.matches + } + + onMounted(() => { + mql = window.matchMedia(`(max-width: ${breakpoint - 1}px)`) + update(mql) + // Safari < 14 需要 addListener,现代浏览器用 addEventListener + mql.addEventListener('change', update) + }) + + onBeforeUnmount(() => { + mql?.removeEventListener('change', update) + mql = null + }) + + return { isMobile } +} diff --git a/web/tenant/src/views/finance/OrdersPage.vue b/web/tenant/src/views/finance/OrdersPage.vue index 299a73d..a392603 100644 --- a/web/tenant/src/views/finance/OrdersPage.vue +++ b/web/tenant/src/views/finance/OrdersPage.vue @@ -3,6 +3,7 @@ import { ref, onMounted, computed, h } from 'vue' import type { DataTableColumns } from 'naive-ui' import { NButton } from 'naive-ui' import Icon from '@/components/common/Icon.vue' +import ResponsiveDataTable from '@/components/common/ResponsiveDataTable.vue' import { renderBadge, formatMoney, tableScrollX } from '@/utils/renderUtils' import request from '@/utils/request' import { useExport } from '@/composables/useExport' @@ -190,8 +191,9 @@ onMounted(() => {
- { :scroll-x="tableScrollX(columns)" :data="orders" :row-key="(row: any) => row.id" + card-title-key="order_no" + card-badge-key="status" + card-subtitle-key="created_at" + :card-fields="['order_type', 'final_amount']" + card-actions-key="actions" @update:page="fetchOrders" @update:page-size="handlePageSizeChange" > @@ -212,7 +219,7 @@ onMounted(() => {

您的订单记录将显示在这里

- + diff --git a/web/tenant/src/views/finance/RedeemPage.vue b/web/tenant/src/views/finance/RedeemPage.vue index 802f3bd..ec2d5df 100644 --- a/web/tenant/src/views/finance/RedeemPage.vue +++ b/web/tenant/src/views/finance/RedeemPage.vue @@ -3,6 +3,7 @@ import { ref, onMounted, computed, h } from 'vue' import type { DataTableColumns } from 'naive-ui' import { NInput } from 'naive-ui' import Icon from '@/components/common/Icon.vue' +import ResponsiveDataTable from '@/components/common/ResponsiveDataTable.vue' import { renderBadge, tableScrollX } from '@/utils/renderUtils' import request from '@/utils/request' @@ -145,12 +146,17 @@ onMounted(fetchHistory)

兑换记录

- - + diff --git a/web/tenant/src/views/finance/TransactionsPage.vue b/web/tenant/src/views/finance/TransactionsPage.vue index 41dcd19..20a7c8a 100644 --- a/web/tenant/src/views/finance/TransactionsPage.vue +++ b/web/tenant/src/views/finance/TransactionsPage.vue @@ -4,6 +4,7 @@ import type { DataTableColumns } from 'naive-ui' import { NInput, NInputNumber, NSelect } from 'naive-ui' import Icon from '@/components/common/Icon.vue' import DateRangePicker from '@/components/common/DateRangePicker.vue' +import ResponsiveDataTable from '@/components/common/ResponsiveDataTable.vue' import { renderBadge, tableScrollX } from '@/utils/renderUtils' import request from '@/utils/request' import { useExport } from '@/composables/useExport' @@ -182,7 +183,7 @@ onMounted(fetchTransactions)
@@ -249,8 +250,9 @@ onMounted(fetchTransactions)
- @@ -271,7 +277,7 @@ onMounted(fetchTransactions)

交易记录将在 API 调用和充值后展示

- +
diff --git a/web/tenant/src/views/finance/WalletPage.vue b/web/tenant/src/views/finance/WalletPage.vue index 392edd3..7f0a361 100644 --- a/web/tenant/src/views/finance/WalletPage.vue +++ b/web/tenant/src/views/finance/WalletPage.vue @@ -4,6 +4,7 @@ import type { DataTableColumns } from 'naive-ui' import { NInput, NInputNumber, NModal } from 'naive-ui' import { useRoute } from 'vue-router' import Icon from '@/components/common/Icon.vue' +import ResponsiveDataTable from '@/components/common/ResponsiveDataTable.vue' import { renderBadge, tableScrollX } from '@/utils/renderUtils' import request from '@/utils/request' import { dispatchPayment } from '@/utils/payment' @@ -672,12 +673,17 @@ onBeforeUnmount(() => {

兑换记录

- - + - + diff --git a/web/tenant/src/views/service/DocsPage.vue b/web/tenant/src/views/service/DocsPage.vue index 53f9175..76618cc 100644 --- a/web/tenant/src/views/service/DocsPage.vue +++ b/web/tenant/src/views/service/DocsPage.vue @@ -5,6 +5,7 @@ import { NTag } from 'naive-ui' import { renderBadge, tableScrollX } from '@/utils/renderUtils' import { useRouter } from 'vue-router' import Icon from '@/components/common/Icon.vue' +import ResponsiveDataTable from '@/components/common/ResponsiveDataTable.vue' const router = useRouter() const activeSection = ref('quickstart') @@ -1333,11 +1334,15 @@ const errorColumns = computed>(() => [
请求参数
-
@@ -1459,11 +1464,15 @@ const errorColumns = computed>(() => [

错误码一览

-
diff --git a/web/tenant/src/views/service/ModelComparisonPage.vue b/web/tenant/src/views/service/ModelComparisonPage.vue index 8e1e78f..282cb76 100644 --- a/web/tenant/src/views/service/ModelComparisonPage.vue +++ b/web/tenant/src/views/service/ModelComparisonPage.vue @@ -4,6 +4,7 @@ import type { DataTableColumns } from 'naive-ui' import request from '@/utils/request' import { tableScrollX } from '@/utils/renderUtils' import Icon from '@/components/common/Icon.vue' +import ResponsiveDataTable from '@/components/common/ResponsiveDataTable.vue' interface ModelItem { model_id: string; model_name: string; category: string } interface ComparisonItem { @@ -325,18 +326,22 @@ const columns = computed>(() => [

详细对比

- - +
diff --git a/web/tenant/src/views/service/RequestAuditLogsPage.vue b/web/tenant/src/views/service/RequestAuditLogsPage.vue index 14288d3..86613a0 100644 --- a/web/tenant/src/views/service/RequestAuditLogsPage.vue +++ b/web/tenant/src/views/service/RequestAuditLogsPage.vue @@ -4,6 +4,7 @@ import type { DataTableColumns } from 'naive-ui' import { NButton, NInput } from 'naive-ui' import { useRoute } from 'vue-router' import Icon from '@/components/common/Icon.vue' +import ResponsiveDataTable from '@/components/common/ResponsiveDataTable.vue' import request from '@/utils/request' import { tableScrollX } from '@/utils/renderUtils' import DateTimeRangePicker from '@/components/common/DateTimeRangePicker.vue' @@ -328,7 +329,7 @@ onMounted(() => {
@@ -362,8 +363,9 @@ onMounted(() => {
- { :scroll-x="tableScrollX(columns)" :data="logs" :row-key="(row: RequestLog) => row.id" + card-title-key="request_id" + card-badge-key="status_code" + card-subtitle-key="created_at" + :card-fields="[{ key: 'path', full: true }, 'user', 'method', 'latency_ms', 'first_token_ms', 'audit_level', 'task_id']" + card-actions-key="actions" + :row-click="(row: RequestLog) => fetchDetail(row.id)" @update:page="fetchRequestLogs" @update:page-size="handlePageSizeChange" > @@ -384,7 +392,7 @@ onMounted(() => {

API 调用的输入输出记录将显示在这里

- + @@ -501,4 +509,3 @@ onMounted(() => { - diff --git a/web/tenant/src/views/service/TaskLogsPage.vue b/web/tenant/src/views/service/TaskLogsPage.vue index b5b412f..ab4cb47 100644 --- a/web/tenant/src/views/service/TaskLogsPage.vue +++ b/web/tenant/src/views/service/TaskLogsPage.vue @@ -5,6 +5,7 @@ import { NButton, NTag, NInput } from 'naive-ui' import { useRoute } from 'vue-router' import Icon from '@/components/common/Icon.vue' import BaseModal from '@/components/common/BaseModal.vue' +import ResponsiveDataTable from '@/components/common/ResponsiveDataTable.vue' import BaseSelect from '../../components/common/BaseSelect.vue' import request from '@/utils/request' import { tableScrollX } from '@/utils/renderUtils' @@ -266,7 +267,7 @@ onMounted(() => {
@@ -306,8 +307,9 @@ onMounted(() => {
- { :scroll-x="tableScrollX(columns)" :data="tasks" :row-key="(row: TaskItem) => row.id" + card-title-key="public_task_id" + card-badge-key="status" + card-subtitle-key="submit_time" + :card-fields="['platform', 'model_name', 'cost', 'finish_time']" + card-actions-key="actions" + :row-click="openDetail" @update:page="fetchTasks" @update:page-size="handlePageSizeChange" > @@ -328,7 +336,7 @@ onMounted(() => {

异步生成任务的执行记录将显示在这里

- +
diff --git a/web/tenant/src/views/service/UsageLogsPage.vue b/web/tenant/src/views/service/UsageLogsPage.vue index 0449cad..a9ff4f1 100644 --- a/web/tenant/src/views/service/UsageLogsPage.vue +++ b/web/tenant/src/views/service/UsageLogsPage.vue @@ -6,6 +6,7 @@ import { useRouter } from 'vue-router' import Icon from '@/components/common/Icon.vue' import BaseModal from '@/components/common/BaseModal.vue' import DateTimeRangePicker from '@/components/common/DateTimeRangePicker.vue' +import ResponsiveDataTable from '@/components/common/ResponsiveDataTable.vue' import request from '@/utils/request' import { tableScrollX } from '@/utils/renderUtils' import { useExport } from '@/composables/useExport' @@ -469,7 +470,7 @@ onMounted(() => {
@@ -530,10 +531,11 @@ onMounted(() => {
- +
- { :scroll-x="tableScrollX(columns)" :data="logs" :row-key="(row: any) => row.id" + card-title-key="model" + card-badge-key="status" + card-subtitle-key="created_at" + :card-fields="[ + { key: 'user' }, + { key: 'api_key' }, + { key: 'type' }, + { key: 'token', full: true }, + { key: 'cost' }, + { key: 'latency' }, + ]" + card-actions-key="actions" + :row-click="openDetail" @update:page="fetchLogs" @update:page-size="handlePageSizeChange" > @@ -554,7 +569,7 @@ onMounted(() => {

日志将在 API 调用后展示

- + diff --git a/web/tenant/src/views/service/playground/ParamRefModal.vue b/web/tenant/src/views/service/playground/ParamRefModal.vue index 9207e67..9052157 100644 --- a/web/tenant/src/views/service/playground/ParamRefModal.vue +++ b/web/tenant/src/views/service/playground/ParamRefModal.vue @@ -3,6 +3,7 @@ import { ref, computed, watch, h } from 'vue' import type { DataTableColumns } from 'naive-ui' import { renderBadge, tableScrollX } from '@/utils/renderUtils' import BaseModal from '@/components/common/BaseModal.vue' +import ResponsiveDataTable from '@/components/common/ResponsiveDataTable.vue' import Icon from '@/components/common/Icon.vue' export interface ParamDef { @@ -203,11 +204,15 @@ const paramTableColumns = computed>(() => [
-
diff --git a/web/tenant/src/views/settings/LoginHistoryPage.vue b/web/tenant/src/views/settings/LoginHistoryPage.vue index 84d2ce3..bc23a34 100644 --- a/web/tenant/src/views/settings/LoginHistoryPage.vue +++ b/web/tenant/src/views/settings/LoginHistoryPage.vue @@ -3,6 +3,7 @@ import { ref, onMounted, computed, h } from 'vue' import type { DataTableColumns } from 'naive-ui' import { NInput } from 'naive-ui' import Icon from '@/components/common/Icon.vue' +import ResponsiveDataTable from '@/components/common/ResponsiveDataTable.vue' import request from '@/utils/request' import { renderBadge, tableScrollX } from '@/utils/renderUtils' import BaseSelect from '../../components/common/BaseSelect.vue' @@ -197,8 +198,9 @@ function handlePageSizeChange() {
- @@ -219,7 +225,7 @@ function handlePageSizeChange() {

当前没有任何登录历史记录

- + diff --git a/web/tenant/src/views/support/TicketsPage.vue b/web/tenant/src/views/support/TicketsPage.vue index c323ac9..b9f3feb 100644 --- a/web/tenant/src/views/support/TicketsPage.vue +++ b/web/tenant/src/views/support/TicketsPage.vue @@ -3,6 +3,7 @@ import { ref, reactive, onMounted, computed, h } from 'vue' import type { DataTableColumns } from 'naive-ui' import { NInput } from 'naive-ui' import BaseModal from '@/components/common/BaseModal.vue' +import ResponsiveDataTable from '@/components/common/ResponsiveDataTable.vue' import Icon from '@/components/common/Icon.vue' import BaseSelect from '../../components/common/BaseSelect.vue' import { renderBadge, tableScrollX } from '@/utils/renderUtils' @@ -227,13 +228,6 @@ const columns = computed>(() => [ }, ]) -function handleRowProps(row: Ticket) { - return { - style: 'cursor: pointer', - onClick: () => openDetail(row), - } -} - function handlePageSizeChange() { page.value = 1 fetchTickets() @@ -274,8 +268,9 @@ onMounted(() => {
- { :scroll-x="tableScrollX(columns)" :data="tickets" :row-key="(row: Ticket) => row.id" - :row-props="handleRowProps" + card-title-key="title" + card-badge-key="status" + card-subtitle-key="created_at" + :card-fields="['id', 'category', 'urgency', 'assigned_admin']" + :row-click="openDetail" @update:page="fetchTickets" @update:page-size="handlePageSizeChange" > @@ -301,7 +300,7 @@ onMounted(() => {
- + diff --git a/web/tenant/src/views/team/InvitationsPage.vue b/web/tenant/src/views/team/InvitationsPage.vue index a150a0d..920a92a 100644 --- a/web/tenant/src/views/team/InvitationsPage.vue +++ b/web/tenant/src/views/team/InvitationsPage.vue @@ -6,6 +6,7 @@ import { useRouter } from 'vue-router' import { useTenantAuthStore } from '@/stores/tenant-auth' import Icon from '@/components/common/Icon.vue' import TeamLockedBanner from '@/components/common/TeamLockedBanner.vue' +import ResponsiveDataTable from '@/components/common/ResponsiveDataTable.vue' import { renderBadge, tableScrollX } from '@/utils/renderUtils' import request from '@/utils/request' import { toast } from '@/utils/toast' @@ -163,8 +164,9 @@ onMounted(() => {
- { :scroll-x="tableScrollX(columns)" :data="invitations" :row-key="(row: InvitationItem) => row.id" + card-title-key="code" + card-badge-key="status" + card-subtitle-key="created_at" + :card-fields="['role', 'use_count', 'creator_name', { key: 'expires_at', full: true }]" + card-actions-key="actions" @update:page="fetchInvitations" @update:page-size="handlePageSizeChange" > @@ -185,7 +192,7 @@ onMounted(() => {

生成邀请链接后,记录会显示在这里

- + diff --git a/web/tenant/src/views/team/MembersPage.vue b/web/tenant/src/views/team/MembersPage.vue index 854ffa9..2aceb9b 100644 --- a/web/tenant/src/views/team/MembersPage.vue +++ b/web/tenant/src/views/team/MembersPage.vue @@ -5,6 +5,7 @@ import { NButton, NTag } from 'naive-ui' import { useRouter } from 'vue-router' import { useTenantAuthStore } from '@/stores/tenant-auth' import BaseModal from '@/components/common/BaseModal.vue' +import ResponsiveDataTable from '@/components/common/ResponsiveDataTable.vue' import TeamLockedBanner from '@/components/common/TeamLockedBanner.vue' import BaseSelect from '../../components/common/BaseSelect.vue' import TableFilterForm, { type FilterField } from '@/components/common/TableFilterForm.vue' @@ -376,14 +377,6 @@ const columns = computed>(() => [ }, ]) -// 整行点击跳详情 -function handleRowProps(row: Member) { - return { - style: 'cursor: pointer', - onClick: () => goDetail(row.id), - } -} - // pageSize 变化回第 1 页并刷新 function handlePageSizeChange() { page.value = 1 @@ -488,8 +481,9 @@ onMounted(() => {
- { :scroll-x="tableScrollX(columns)" :data="members" :row-key="(row: Member) => row.id" - :row-props="handleRowProps" + card-title-key="user" + card-badge-key="status" + card-subtitle-key="created_at" + :card-fields="['role', 'quota_type', 'model_count', 'month_cost', 'updated_at']" + card-actions-key="actions" + :row-click="(row: Member) => goDetail(row.id)" @update:page="fetchMembers" @update:page-size="handlePageSizeChange" > @@ -511,7 +510,7 @@ onMounted(() => {

邀请第一位团队成员吧

- + diff --git a/web/tenant/src/views/team/ProjectDetailPage.vue b/web/tenant/src/views/team/ProjectDetailPage.vue index 4aafb03..af2e026 100644 --- a/web/tenant/src/views/team/ProjectDetailPage.vue +++ b/web/tenant/src/views/team/ProjectDetailPage.vue @@ -5,6 +5,7 @@ import { NButton, NTag } from 'naive-ui' import { renderBadge, formatDate, formatTokens, formatMs, tableScrollX } from '@/utils/renderUtils' import { useRoute, useRouter } from 'vue-router' import BaseModal from '@/components/common/BaseModal.vue' +import ResponsiveDataTable from '@/components/common/ResponsiveDataTable.vue' import ApiKeyEditModal from '@/components/common/ApiKeyEditModal.vue' import type { ApiKeyData } from '@/components/common/ApiKeyEditModal.vue' import Icon from '@/components/common/Icon.vue' @@ -555,8 +556,8 @@ const usageLogsColumns = computed>(() => [
-
- + >(() => [ :scroll-x="tableScrollX(keysColumns)" :data="keys" :row-key="(row: ApiKey) => row.id" + card-title-key="name" + card-badge-key="status" + card-subtitle-key="created_at" + :card-fields="[{ key: 'key_prefix', full: true }, 'scope', { key: 'limit', full: true }, 'expires_at']" + card-actions-key="actions" @update:page="fetchKeys" @update:page-size="handleKeysPageSizeChange" > @@ -578,7 +584,7 @@ const usageLogsColumns = computed>(() => [

创建密钥以为此项目提供 AI 能力

- +
@@ -630,11 +636,11 @@ const usageLogsColumns = computed>(() => [ -
+

用量日志

- >(() => [ :scroll-x="tableScrollX(usageLogsColumns)" :data="usageLogs" :row-key="(row: any) => row.id" + card-title-key="model_name" + card-badge-key="status" + card-subtitle-key="created_at" + :card-fields="['relay_mode', 'input_tokens', 'output_tokens', 'total_cost', 'latency_ms']" @update:page="fetchUsageLogs" @update:page-size="handleUsageLogsPageSizeChange" > @@ -656,7 +666,7 @@ const usageLogsColumns = computed>(() => [

项目密钥调用后将在此显示用量数据

- +
diff --git a/web/tenant/src/views/team/ProjectsPage.vue b/web/tenant/src/views/team/ProjectsPage.vue index f588d92..774b764 100644 --- a/web/tenant/src/views/team/ProjectsPage.vue +++ b/web/tenant/src/views/team/ProjectsPage.vue @@ -5,6 +5,7 @@ import { NButton, NInput, NInputNumber } from 'naive-ui' import { useRouter } from 'vue-router' import Icon from '@/components/common/Icon.vue' import BaseModal from '@/components/common/BaseModal.vue' +import ResponsiveDataTable from '@/components/common/ResponsiveDataTable.vue' import { renderBadge, tableScrollX } from '@/utils/renderUtils' import request from '@/utils/request' import { useConfirm } from '@/composables/useConfirm' @@ -171,13 +172,6 @@ const columns = computed>(() => [ }, ]) -function handleRowProps(row: any) { - return { - style: 'cursor: pointer', - onClick: () => goToDetail(row), - } -} - function handlePageSizeChange() { page.value = 1 fetchProjects() @@ -198,8 +192,9 @@ function handlePageSizeChange() {
- @@ -221,7 +221,7 @@ function handlePageSizeChange() {

创建项目来组织你的 API Key 和资源

- +