improve(ui):统一调整表格页面,将总数统计显示在左下角

This commit is contained in:
qian committed 2026-08-04 22:23:29 +08:00
1 parent 0e32e29ff6
commit a73cf411a5
44 files changed
+234 -60

No files matched your search

+6 -2
View File
@@ -9,7 +9,9 @@ type ChannelListReq struct {
PageSize int `json:"page_size" d:"20" v:"min:1|max:100" dc:"每页数量"`
Type int `json:"type" dc:"供应商类型筛选"`
Status string `json:"status" dc:"状态筛选:active/disabled/testing"`
Search string `json:"search" dc:"搜索关键词"`
Search string `json:"search" dc:"搜索关键词(渠道名称/备注)"`
ID int64 `json:"id" dc:"按渠道 ID 精确筛选"`
Model string `json:"model" dc:"按支持的模型名筛选(模糊匹配平台模型名/上游模型名)"`
}
// ChannelListRes 渠道列表响应
@@ -250,7 +252,9 @@ type ChannelExportReq struct {
Format string `json:"format" in:"query" d:"csv" v:"in:csv,xlsx" dc:"导出格式:csv / xlsx"`
Type int `json:"type" in:"query" dc:"供应商类型筛选"`
Status string `json:"status" in:"query" dc:"状态筛选:active/disabled/testing"`
Search string `json:"search" in:"query" dc:"搜索关键词"`
Search string `json:"search" in:"query" dc:"搜索关键词(渠道名称/备注)"`
ID int64 `json:"id" in:"query" dc:"按渠道 ID 精确筛选"`
Model string `json:"model" in:"query" dc:"按支持的模型名筛选(模糊匹配平台模型名/上游模型名)"`
}
type ChannelExportRes struct{}
+15
View File
@@ -58,6 +58,14 @@ func (s *sAdmin) ListChannels(ctx context.Context, req *v1.ChannelListReq) (*v1.
if req.Search != "" {
query = query.Where("chn_channels.name LIKE ? OR chn_channels.remark LIKE ?", "%"+req.Search+"%", "%"+req.Search+"%")
}
if req.ID > 0 {
query = query.Where("chn_channels.id", req.ID)
}
if req.Model != "" {
// 按支持的模型名筛选:渠道需具备匹配的模型能力(chn_abilities)。
// 用 EXISTS 相关子查询而非 JOIN,避免一个渠道命中多个模型时产生重复行、破坏分页与总数。
query = query.Where("EXISTS (SELECT 1 FROM chn_abilities WHERE chn_abilities.channel_id = chn_channels.id AND (chn_abilities.model_name LIKE ? OR chn_abilities.upstream_model LIKE ?))", "%"+req.Model+"%", "%"+req.Model+"%")
}
var total int
var channels []struct {
@@ -731,6 +739,13 @@ func (s *sAdmin) ExportChannels(ctx context.Context, req *v1.ChannelExportReq) (
if req.Search != "" {
query = query.Where("chn_channels.name LIKE ? OR chn_channels.remark LIKE ?", "%"+req.Search+"%", "%"+req.Search+"%")
}
if req.ID > 0 {
query = query.Where("chn_channels.id", req.ID)
}
if req.Model != "" {
// 与 ListChannels 保持一致:EXISTS 相关子查询避免 JOIN 重复行
query = query.Where("EXISTS (SELECT 1 FROM chn_abilities WHERE chn_abilities.channel_id = chn_channels.id AND (chn_abilities.model_name LIKE ? OR chn_abilities.upstream_model LIKE ?))", "%"+req.Model+"%", "%"+req.Model+"%")
}
var batch []struct {
ID int64 `json:"id"`
Name string `json:"name"`
+10
View File
@@ -449,6 +449,16 @@ body {
border-top: 1px solid var(--ta-border-light);
}
/* 统计总数从表格左上角移到底部后:footer 内存在统计栏时两端对齐(统计居左、分页居右),
无统计栏的 footer 保持右对齐不受影响(:has 特异性高于 scoped .table-footer,可统一覆盖各页脚注) */
.page-table .table-footer:has(.table-stats) {
justify-content: space-between;
}
/* 统计栏移入底部后,去掉全局样式的下边距,与分页栏垂直居中 */
.page-table .table-footer .table-stats {
margin-bottom: 0;
}
/* Tab 内嵌表格:让 Tab 容器与面板也参与纵向 flex,保证内部表格能撑满滚动 */
.page-table .arco-tabs {
flex: 1;
+108 -7
View File
@@ -1,7 +1,7 @@
<script setup lang="ts">
import { ref, reactive, onMounted, h } from 'vue'
import { useRouter } from 'vue-router'
import { Tag, Button, Space, Popconfirm, Message, Radio } from '@arco-design/web-vue'
import { Tag, Button, Space, Popconfirm, Message, Radio, InputNumber, Select } from '@arco-design/web-vue'
import { IconInfoCircle } from '@arco-design/web-vue/es/icon'
import type { TableColumnData } from '@arco-design/web-vue'
import type { FormInstance } from '@arco-design/web-vue'
@@ -26,6 +26,8 @@ const pagination = reactive({ current: 1, pageSize: 20, total: 0, showPageSize:
const filterType = ref<number | undefined>(undefined)
const filterStatus = ref<string | undefined>(undefined)
const filterSearch = ref('')
const filterID = ref<number | undefined>(undefined)
const filterModel = ref('')
const statusOptions = [
{ label: '全部状态', value: '' },
@@ -67,12 +69,42 @@ const columns: TableColumnData[] = [
title: '状态', dataIndex: 'status', width: 80,
render({ record }) { return h(Tag, { color: statusTagColor[record.status], size: 'small' }, () => statusLabel[record.status] || record.status) },
},
{ title: '优先级', dataIndex: 'priority', width: 70 },
{ title: '权重', dataIndex: 'weight', width: 70 },
{
title: '层级', dataIndex: 'tier', width: 80,
title: '权重', dataIndex: 'weight', width: 100,
render({ record }) {
return h(Tag, { color: tierTagColor[record.tier] || 'gray', size: 'small' }, () => tierLabel[record.tier] || record.tier || '-')
// 无编辑权限时只读展示
if (!hasPermission('channel:edit')) return h('span', {}, record.weight)
return h(InputNumber, {
modelValue: record.weight,
min: 0,
max: 100,
size: 'small',
disabled: record._saving,
style: 'width: 84px',
'onUpdate:modelValue': (val: number | undefined) => { record.weight = val ?? 0 },
onChange: (val: number | undefined) => onWeightChange(record, val),
})
},
},
{
title: '层级', dataIndex: 'tier', width: 120,
render({ record }) {
// 无编辑权限时只读展示
if (!hasPermission('channel:edit')) {
return h(Tag, { color: tierTagColor[record.tier] || 'gray', size: 'small' }, () => tierLabel[record.tier] || record.tier || '-')
}
return h(Select, {
modelValue: record.tier,
size: 'small',
style: 'width: 108px',
options: [
{ label: '首选', value: 'primary' },
{ label: '备用', value: 'secondary' },
{ label: '保底', value: 'reserve' },
],
'onUpdate:modelValue': (val: any) => { record.tier = val },
onChange: (val: any) => saveTier(record, val),
})
},
},
{
@@ -122,8 +154,12 @@ async function fetchData() {
if (filterType.value) params.type = filterType.value
if (filterStatus.value) params.status = filterStatus.value
if (filterSearch.value) params.search = filterSearch.value
if (filterID.value) params.id = filterID.value
if (filterModel.value) params.model = filterModel.value
const res: any = await request.get('/admin/channels', { params })
data.value = res.data?.data?.list || res.data?.list || []
// 记录本次加载时已持久化的权重/层级,供行内编辑失败回滚
data.value.forEach((r: any) => { r._savedWeight = r.weight; r._tierSaved = r.tier })
pagination.total = res.data?.data?.total || res.data?.total || 0
} catch {
data.value = []; pagination.total = 0
@@ -222,6 +258,61 @@ async function toggleStatus(record: any) {
}
}
// === 行内编辑(权重 / 层级) ===
// 行内编辑复用 PUT /admin/channels/{id}。后端 UpdateChannel 会无条件写入 priority/weight,
// 故每次提交必须携带当前 priority 与 weight,避免被重置为 0。
const weightTimers = new Map<number, any>()
function onWeightChange(record: any, val: number | undefined) {
if (val === undefined || val === null) {
// 清空字段时取消待提交的防抖任务,避免误提交旧值
const timer = weightTimers.get(record.id)
if (timer) { clearTimeout(timer); weightTimers.delete(record.id) }
return
}
const timer = weightTimers.get(record.id)
if (timer) clearTimeout(timer)
// 防抖:连续点击步进按钮或连续输入时,只提交最后一次值
weightTimers.set(record.id, setTimeout(() => saveWeight(record, val), 350))
}
async function saveWeight(record: any, val: number) {
if (record._saving || val === record._savedWeight) return
const prev = record._savedWeight
record._saving = true
try {
await request.put(`/admin/channels/${record.id}`, {
priority: record.priority,
weight: val,
})
record._savedWeight = val
message.success('权重已更新')
} catch {
record.weight = prev // 失败回滚显示值
} finally {
record._saving = false
}
}
async function saveTier(record: any, val: string) {
if (!val || record._saving || val === record._tierSaved) return
const prev = record._tierSaved
record._saving = true
try {
await request.put(`/admin/channels/${record.id}`, {
priority: record.priority,
weight: record.weight,
tier: val,
})
record._tierSaved = val
message.success('层级已更新')
} catch {
record.tier = prev // 失败回滚
} finally {
record._saving = false
}
}
onMounted(fetchData)
// === OAuth ===
@@ -292,6 +383,8 @@ const { exporting, exportFile } = useExport({
type: filterType.value,
status: filterStatus.value,
search: filterSearch.value,
id: filterID.value,
model: filterModel.value,
}),
})
</script>
@@ -356,15 +449,17 @@ const { exporting, exportFile } = useExport({
<ASelect v-model="filterType" :options="[{ label: '全部类型', value: '' }, ...providerTypeOptions]" placeholder="供应商类型" allow-clear allow-search style="width: 180px" @change="handleFilter" />
<ASelect v-model="filterStatus" :options="statusOptions" placeholder="状态" allow-clear style="width: 120px" @change="handleFilter" />
<AInput v-model="filterSearch" placeholder="搜索渠道名..." allow-clear style="width: 200px" @keydown.enter="handleFilter" @clear="handleFilter" />
<AInputNumber v-model="filterID" placeholder="渠道 ID" :min="1" allow-clear style="width: 120px" @change="handleFilter" @clear="handleFilter" />
<AInput v-model="filterModel" placeholder="搜索模型名..." allow-clear style="width: 160px" @keydown.enter="handleFilter" @clear="handleFilter" />
<AButton @click="handleFilter">搜索</AButton>
</ASpace>
</ACard>
<!-- Table -->
<ACard :bordered="false">
<TableStats :total="pagination.total" />
<ATable :columns="columns" :data="data" :loading="loading" :scroll="{ x: 1400 }" :bordered="false" :stripe="true" :pagination="false" row-key="id" />
<div class="table-footer">
<TableStats :total="pagination.total" />
<APagination v-model:current="pagination.current" v-model:page-size="pagination.pageSize" :total="pagination.total" :page-size-options="pagination.pageSizeOptions" show-page-size @change="fetchData" @page-size-change="(s: number) => { pagination.pageSize = s; pagination.current = 1; fetchData() }" />
</div>
</ACard>
@@ -522,11 +617,17 @@ const { exporting, exportFile } = useExport({
<style scoped>
.table-footer {
display: flex;
justify-content: flex-end;
align-items: center;
justify-content: space-between;
gap: 12px;
margin-top: 16px;
padding-top: 16px;
border-top: 1px solid var(--ta-border-light);
}
/* 统计栏移入底部后,去掉全局样式的下边距,与分页栏垂直居中 */
.table-footer :deep(.table-stats) {
margin-bottom: 0;
}
.scheduling-hint {
display: inline-flex;
align-items: center;
+8 -2
View File
@@ -272,7 +272,6 @@ onMounted(() => {
</ACard>
<ACard :bordered="false">
<TableStats :total="pagination.total" />
<ATable
:columns="columns"
:data="data"
@@ -284,6 +283,7 @@ onMounted(() => {
:scroll="{ x: 1200 }"
/>
<div class="table-footer">
<TableStats :total="pagination.total" />
<APagination
v-model:current="pagination.current"
v-model:page-size="pagination.pageSize"
@@ -347,9 +347,15 @@ onMounted(() => {
}
.table-footer {
display: flex;
justify-content: flex-end;
align-items: center;
justify-content: space-between;
gap: 12px;
margin-top: 16px;
}
/* 统计栏移入底部后,去掉全局样式的下边距,与分页栏垂直居中 */
.table-footer :deep(.table-stats) {
margin-bottom: 0;
}
.mb-4 {
margin-bottom: 16px;
}
+8 -2
View File
@@ -656,7 +656,6 @@ function resetImport() {
<!-- Table -->
<ACard :bordered="false">
<TableStats :total="pagination.total" />
<ATable
:columns="columns"
:data="data"
@@ -671,6 +670,7 @@ function resetImport() {
@selection-change="handleSelectionChange"
/>
<div class="table-footer">
<TableStats :total="pagination.total" />
<APagination
v-model:current="pagination.current"
v-model:page-size="pagination.pageSize"
@@ -833,11 +833,17 @@ function resetImport() {
<style scoped>
.table-footer {
display: flex;
justify-content: flex-end;
align-items: center;
justify-content: space-between;
gap: 12px;
margin-top: 16px;
padding-top: 16px;
border-top: 1px solid var(--ta-border-light);
}
/* 统计栏移入底部后,去掉全局样式的下边距,与分页栏垂直居中 */
.table-footer :deep(.table-stats) {
margin-bottom: 0;
}
.cap-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
+8 -2
View File
@@ -259,7 +259,6 @@ onMounted(() => {
<!-- Table -->
<ACard :bordered="false">
<TableStats :total="pagination.total" />
<ATable
:columns="columns"
:data="data"
@@ -271,6 +270,7 @@ onMounted(() => {
row-key="id"
/>
<div class="table-footer">
<TableStats :total="pagination.total" />
<APagination
v-model:current="pagination.current"
v-model:page-size="pagination.pageSize"
@@ -349,11 +349,17 @@ onMounted(() => {
<style scoped>
.table-footer {
display: flex;
justify-content: flex-end;
align-items: center;
justify-content: space-between;
gap: 12px;
margin-top: 16px;
padding-top: 16px;
border-top: 1px solid var(--ta-border-light);
}
/* 统计栏移入底部后,去掉全局样式的下边距,与分页栏垂直居中 */
.table-footer :deep(.table-stats) {
margin-bottom: 0;
}
.detail-section-title {
font-size: 14px;
+1 -1
View File
@@ -94,7 +94,6 @@ const { exporting, exportFile } = useExport({
</PageHeader>
<ACard :bordered="false" class="mt-4">
<TableStats :total="billPagination.total" />
<div class="mb-4">
<ASpace>
<AInput v-model="billFilterTenantId" placeholder="租户ID" allow-clear style="width: 120px" @keydown.enter="fetchBillingRecords" />
@@ -103,6 +102,7 @@ const { exporting, exportFile } = useExport({
</div>
<ATable :columns="billColumns" :data="billData" :loading="billLoading" :scroll="{ x: 1800 }" :bordered="false" :stripe="true" size="small" :pagination="false" row-key="id" />
<div class="table-footer">
<TableStats :total="billPagination.total" />
<APagination v-model:current="billPagination.current" v-model:page-size="billPagination.pageSize" :total="billPagination.total" :page-size-options="billPagination.pageSizeOptions" show-page-size @change="fetchBillingRecords" @page-size-change="(s: number) => { billPagination.pageSize = s; billPagination.current = 1; fetchBillingRecords() }" />
</div>
</ACard>
+1 -1
View File
@@ -139,9 +139,9 @@ const { exporting, exportFile } = useExport({
</ASpace>
</div>
</template>
<TableStats :total="pagination.total" />
<ATable :columns="columns" :data="orders" :loading="loading" row-key="id" :scroll="{ x: 1100 }" :pagination="false" />
<div class="table-footer">
<TableStats :total="pagination.total" />
<APagination v-model:current="pagination.current" v-model:page-size="pagination.pageSize" :total="pagination.total" :page-size-options="pagination.pageSizeOptions" show-page-size @change="fetchOrders" @page-size-change="(s: number) => { pagination.pageSize = s; pagination.current = 1; fetchOrders() }" />
</div>
</ACard>
+1 -1
View File
@@ -141,9 +141,9 @@ const { exporting, exportFile } = useExport({
<ASelect v-model="statusFilter" :options="statusOptions" style="width: 120px" allow-clear @change="() => { pagination.current = 1; fetchPlans() }" />
</div>
</template>
<TableStats :total="pagination.total" />
<ATable :columns="columns" :data="plans" :loading="loading" row-key="id" :scroll="{ x: 1200 }" :pagination="false" />
<div class="table-footer">
<TableStats :total="pagination.total" />
<APagination v-model:current="pagination.current" v-model:page-size="pagination.pageSize" :total="pagination.total" :page-size-options="pagination.pageSizeOptions" show-page-size @change="fetchPlans" @page-size-change="(s: number) => { pagination.pageSize = s; pagination.current = 1; fetchPlans() }" />
</div>
</ACard>
@@ -123,9 +123,9 @@ const { exporting, exportFile } = useExport({
</PageHeader>
<ACard :bordered="false">
<TableStats :total="pagination.total" />
<ATable :columns="columns" :data="promoCodes" :loading="loading" row-key="id" :scroll="{ x: 1200 }" :pagination="false" />
<div class="table-footer">
<TableStats :total="pagination.total" />
<APagination v-model:current="pagination.current" v-model:page-size="pagination.pageSize" :total="pagination.total" :page-size-options="pagination.pageSizeOptions" show-page-size @change="fetchList" @page-size-change="(s: number) => { pagination.pageSize = s; pagination.current = 1; fetchList() }" />
</div>
</ACard>
@@ -140,7 +140,6 @@ const { exporting, exportFile } = useExport({
<ATabs v-model:active-key="activeTab" @change="onTabChange">
<ATabPane key="codes" title="兑换码列表">
<div class="flex items-center justify-between mb-4">
<TableStats :total="pagination.total" />
<ASelect v-model="statusFilter" :options="[
{ label: '全部', value: '' }, { label: '可用', value: 'active' },
{ label: '已禁用', value: 'disabled' }, { label: '已过期', value: 'expired' },
@@ -148,14 +147,15 @@ const { exporting, exportFile } = useExport({
</div>
<ATable :columns="columns" :data="redemptions" :loading="loading" row-key="id" :scroll="{ x: 1100 }" :pagination="false" />
<div class="table-footer">
<TableStats :total="pagination.total" />
<APagination v-model:current="pagination.current" v-model:page-size="pagination.pageSize" :total="pagination.total" :page-size-options="pagination.pageSizeOptions" show-page-size @change="fetchRedemptions" @page-size-change="(s: number) => { pagination.pageSize = s; pagination.current = 1; fetchRedemptions() }" />
</div>
</ATabPane>
<ATabPane key="usages" title="使用记录">
<TableStats :total="usagePagination.total" />
<ATable :columns="usageColumns" :data="usages" :loading="usageLoading" row-key="id" :scroll="{ x: 900 }" :pagination="false" />
<div class="table-footer">
<TableStats :total="usagePagination.total" />
<APagination v-model:current="usagePagination.current" v-model:page-size="usagePagination.pageSize" :total="usagePagination.total" :page-size-options="usagePagination.pageSizeOptions" show-page-size @change="fetchUsages" @page-size-change="(s: number) => { usagePagination.pageSize = s; usagePagination.current = 1; fetchUsages() }" />
</div>
</ATabPane>
@@ -94,7 +94,6 @@ onMounted(fetchData)
<PageHeader title="交易流水" description="查看所有租户的钱包交易记录" />
<ACard :bordered="false" class="mt-4">
<TableStats :total="pagination.total" />
<div class="mb-4">
<ASpace wrap>
<AInput v-model="filterTenantId" placeholder="租户ID" allow-clear style="width: 120px" @keydown.enter="resetAndFetch" />
@@ -107,6 +106,7 @@ onMounted(fetchData)
</div>
<ATable :columns="columns" :data="data" :loading="loading" :scroll="{ x: 1200 }" :bordered="false" :stripe="true" size="small" :pagination="false" row-key="id" />
<div class="table-footer">
<TableStats :total="pagination.total" />
<APagination v-model:current="pagination.current" v-model:page-size="pagination.pageSize" :total="pagination.total" :page-size-options="pagination.pageSizeOptions" show-page-size @change="fetchData" @page-size-change="(s: number) => { pagination.pageSize = s; pagination.current = 1; fetchData() }" />
</div>
</ACard>
@@ -201,9 +201,9 @@ onUnmounted(() => {
</a-card>
<a-card :bordered="false">
<TableStats :total="total" />
<a-table :data="data" :columns="columns" :loading="loading" :pagination="false" row-key="id" />
<div class="table-footer">
<TableStats :total="total" />
<a-pagination :current="pagination.current" :page-size="pagination.pageSize" :total="total" @change="handlePageChange" />
</div>
</a-card>
@@ -237,9 +237,9 @@ onMounted(() => {
</a-card>
<a-card :bordered="false">
<TableStats :total="total" />
<a-table :data="data" :columns="columns" :loading="loading" :pagination="false" row-key="id" />
<div class="table-footer">
<TableStats :total="total" />
<a-pagination :current="pagination.current" :page-size="pagination.pageSize" :total="total" @change="handlePageChange" />
</div>
</a-card>
@@ -283,7 +283,6 @@ onMounted(() => {
<!-- Table -->
<ACard :bordered="false">
<TableStats :total="total" />
<ATable
:data="data"
:loading="loading"
@@ -293,6 +292,7 @@ onMounted(() => {
:scroll="{ x: 1400 }"
/>
<div class="table-footer">
<TableStats :total="total" />
<APagination
v-model:current="pagination.current"
v-model:page-size="pagination.pageSize"
@@ -263,7 +263,6 @@ onMounted(() => {
</a-card>
<a-card :bordered="false">
<TableStats :total="total" />
<a-table
:data="data"
:columns="columns"
@@ -272,6 +271,7 @@ onMounted(() => {
row-key="id"
/>
<div class="table-footer">
<TableStats :total="total" />
<a-pagination
v-model:current="pagination.current"
v-model:page-size="pagination.pageSize"
+3 -1
View File
@@ -228,7 +228,6 @@ onMounted(() => {
</PageHeader>
<a-card :bordered="false">
<TableStats :total="jobs.length" />
<a-table
:data="jobs"
:columns="columns"
@@ -263,6 +262,9 @@ onMounted(() => {
</div>
</template>
</a-table>
<div class="table-footer">
<TableStats :total="jobs.length" />
</div>
</a-card>
</div>
</template>
@@ -310,7 +310,6 @@ onMounted(() => {
<!-- Table -->
<a-card :bordered="false">
<TableStats :total="total" />
<a-table
:data="data"
:columns="columns"
@@ -322,6 +321,7 @@ onMounted(() => {
:row-selection="{ type: 'checkbox', showCheckedAll: true, selectedRowKeys: selectedKeys, onSelect: handleSelection }"
/>
<div class="table-footer">
<TableStats :total="total" />
<a-pagination
:current="pagination.current"
:page-size="pagination.pageSize"
@@ -120,7 +120,6 @@ onMounted(() => {
</PageHeader>
<a-card :bordered="false">
<TableStats :total="data.length" />
<a-table
:columns="columns"
:data="data"
@@ -130,6 +129,9 @@ onMounted(() => {
:scroll="{ x: 1200 }"
size="medium"
/>
<div class="table-footer">
<TableStats :total="data.length" />
</div>
</a-card>
</div>
</template>
@@ -267,9 +267,9 @@ onMounted(fetchList)
</div>
</div>
</template>
<TableStats :total="pagination.total" />
<ATable :columns="columns" :data="data" :loading="loading" row-key="id" :scroll="{ x: 1400 }" :pagination="false" />
<div class="table-footer">
<TableStats :total="pagination.total" />
<APagination v-model:current="pagination.current" v-model:page-size="pagination.pageSize" :total="pagination.total" :page-size-options="pagination.pageSizeOptions" show-page-size @change="fetchList" @page-size-change="(s: number) => { pagination.pageSize = s; pagination.current = 1; fetchList() }" />
</div>
</ACard>
@@ -212,9 +212,9 @@ onMounted(fetchList)
<ASelect v-model="statusFilter" :options="statusOptions" style="width: 120px" allow-clear placeholder="状态筛选" @change="() => { pagination.current = 1; fetchList() }" />
</div>
</template>
<TableStats :total="pagination.total" />
<ATable :columns="columns" :data="announcements" :loading="loading" row-key="id" :scroll="{ x: 1400 }" :pagination="false" />
<div class="table-footer">
<TableStats :total="pagination.total" />
<APagination v-model:current="pagination.current" v-model:page-size="pagination.pageSize" :total="pagination.total" :page-size-options="pagination.pageSizeOptions" show-page-size @change="fetchList" @page-size-change="(s: number) => { pagination.pageSize = s; pagination.current = 1; fetchList() }" />
</div>
</ACard>
@@ -183,9 +183,9 @@ onMounted(fetchList)
</div>
</div>
</template>
<TableStats :total="pagination.total" />
<ATable :columns="columns" :data="data" :loading="loading" row-key="id" :scroll="{ x: 1200 }" :pagination="false" />
<div class="table-footer">
<TableStats :total="pagination.total" />
<APagination v-model:current="pagination.current" v-model:page-size="pagination.pageSize" :total="pagination.total" :page-size-options="pagination.pageSizeOptions" show-page-size @change="fetchList" @page-size-change="(s: number) => { pagination.pageSize = s; pagination.current = 1; fetchList() }" />
</div>
</ACard>
@@ -200,7 +200,6 @@ onMounted(() => {
</a-card>
<a-card :bordered="false">
<TableStats :total="sendPagination.total" />
<a-table
:columns="sendColumns"
:data="sendData"
@@ -213,6 +212,7 @@ onMounted(() => {
row-key="id"
/>
<div class="table-footer">
<TableStats :total="sendPagination.total" />
<a-pagination
v-model:current="sendPagination.current"
v-model:page-size="sendPagination.pageSize"
@@ -246,7 +246,6 @@ onMounted(() => {
</a-card>
<a-card :bordered="false">
<TableStats :total="codePagination.total" />
<a-table
:columns="codeColumns"
:data="codeData"
@@ -259,6 +258,7 @@ onMounted(() => {
row-key="id"
/>
<div class="table-footer">
<TableStats :total="codePagination.total" />
<a-pagination
v-model:current="codePagination.current"
v-model:page-size="codePagination.pageSize"
@@ -228,9 +228,9 @@ onMounted(() => { fetchStats(); fetchList() })
</div>
</div>
</template>
<TableStats :total="pagination.total" />
<ATable :columns="columns" :data="data" :loading="loading" row-key="id" :scroll="{ x: 1300 }" :pagination="false" />
<div class="table-footer">
<TableStats :total="pagination.total" />
<APagination v-model:current="pagination.current" v-model:page-size="pagination.pageSize" :total="pagination.total" :page-size-options="pagination.pageSizeOptions" show-page-size @change="fetchList" @page-size-change="(s: number) => { pagination.pageSize = s; pagination.current = 1; fetchList() }" />
</div>
</ACard>
@@ -326,9 +326,9 @@ onMounted(fetchList)
<ASelect v-model="typeFilter" :options="typeOptions" style="width: 120px" allow-clear placeholder="类型筛选" @change="() => { pagination.current = 1; fetchList() }" />
</div>
</template>
<TableStats :total="pagination.total" />
<ATable :columns="columns" :data="messages" :loading="loading" row-key="id" :scroll="{ x: 1100 }" :pagination="false" />
<div class="table-footer">
<TableStats :total="pagination.total" />
<APagination v-model:current="pagination.current" v-model:page-size="pagination.pageSize" :total="pagination.total" :page-size-options="pagination.pageSizeOptions" show-page-size @change="fetchList" @page-size-change="(s: number) => { pagination.pageSize = s; pagination.current = 1; fetchList() }" />
</div>
</ACard>
@@ -151,9 +151,9 @@ onMounted(fetchTemplates)
</PageHeader>
<ACard :bordered="false">
<TableStats :total="pagination.total" />
<ATable :columns="columns" :data="templates" :loading="loading" row-key="code" :scroll="{ x: 1000 }" :pagination="false" />
<div class="table-footer">
<TableStats :total="pagination.total" />
<APagination v-model:current="pagination.current" v-model:page-size="pagination.pageSize" :total="pagination.total" :page-size-options="pagination.pageSizeOptions" show-page-size @change="fetchTemplates" @page-size-change="(s: number) => { pagination.pageSize = s; pagination.current = 1; fetchTemplates() }" />
</div>
</ACard>
@@ -288,9 +288,9 @@ onMounted(fetchTickets)
</ASpace>
</div>
</template>
<TableStats :total="pagination.total" />
<ATable :columns="columns" :data="tickets" :loading="loading" row-key="id" :scroll="{ x: 1300 }" :pagination="false" />
<div class="table-footer">
<TableStats :total="pagination.total" />
<APagination v-model:current="pagination.current" v-model:page-size="pagination.pageSize" :total="pagination.total" :page-size-options="pagination.pageSizeOptions" show-page-size @change="fetchTickets" @page-size-change="(s: number) => { pagination.pageSize = s; pagination.current = 1; fetchTickets() }" />
</div>
</ACard>
+2 -2
View File
@@ -218,7 +218,6 @@ const { exporting, exportFile } = useExport({
<!-- Tab 1: Operation Logs -->
<ATabPane key="logs" title="操作日志">
<ACard :bordered="false" class="mt-4">
<TableStats :total="opPagination.total" />
<div class="mb-4">
<ASpace wrap>
<AInput
@@ -255,6 +254,7 @@ const { exporting, exportFile } = useExport({
row-key="id"
/>
<div class="table-footer">
<TableStats :total="opPagination.total" />
<APagination
v-model:current="opPagination.current"
v-model:page-size="opPagination.pageSize"
@@ -271,7 +271,6 @@ const { exporting, exportFile } = useExport({
<!-- Tab 2: Sensitive Access Logs -->
<ATabPane key="sensitive" title="敏感访问日志">
<ACard :bordered="false" class="mt-4">
<TableStats :total="sensPagination.total" />
<div class="mb-4">
<ASpace wrap>
<AInput
@@ -308,6 +307,7 @@ const { exporting, exportFile } = useExport({
row-key="id"
/>
<div class="table-footer">
<TableStats :total="sensPagination.total" />
<APagination
v-model:current="sensPagination.current"
v-model:page-size="sensPagination.pageSize"
@@ -149,7 +149,6 @@ function onDateChange(dateString: string | undefined, type: 'start' | 'end') {
</ACard>
<ACard :bordered="false">
<TableStats :total="pagination.total" />
<ATable
:data="list"
:loading="loading"
@@ -193,6 +192,7 @@ function onDateChange(dateString: string | undefined, type: 'start' | 'end') {
</template>
</ATable>
<div class="table-footer">
<TableStats :total="pagination.total" />
<APagination
v-model:current="pagination.current"
v-model:page-size="pagination.pageSize"
@@ -58,9 +58,6 @@ onMounted(fetchPermissions)
</PageHeader>
<ACard :bordered="false">
<TableStats :total="groups.length">
<span>{{ groups.reduce((s, g) => s + g.permissions.length, 0) }} 个权限点</span>
</TableStats>
<ATable
:columns="columns"
:data="groups"
@@ -70,6 +67,11 @@ onMounted(fetchPermissions)
:pagination="false"
row-key="id"
/>
<div class="table-footer">
<TableStats :total="groups.length">
<span>{{ groups.reduce((s, g) => s + g.permissions.length, 0) }} 个权限点</span>
</TableStats>
</div>
</ACard>
</div>
</template>
@@ -346,7 +346,6 @@ onMounted(() => {
</a-card>
<a-card :bordered="false">
<TableStats :total="pagination.total" />
<a-table
:columns="columns"
:data="data"
@@ -359,6 +358,7 @@ onMounted(() => {
row-key="id"
/>
<div class="table-footer">
<TableStats :total="pagination.total" />
<a-pagination
v-model:current="pagination.current"
v-model:page-size="pagination.pageSize"
@@ -444,11 +444,17 @@ onMounted(() => {
<style scoped>
.table-footer {
display: flex;
justify-content: flex-end;
align-items: center;
justify-content: space-between;
gap: 12px;
margin-top: 16px;
padding-top: 16px;
border-top: 1px solid var(--color-border-light, #e5e6eb);
}
/* 统计栏移入底部后,去掉全局样式的下边距,与分页栏垂直居中 */
.table-footer :deep(.table-stats) {
margin-bottom: 0;
}
.audit-body {
background: var(--color-fill-2, #f7f8fa);
padding: 12px;
@@ -158,7 +158,6 @@ onMounted(() => {
</ACard>
<ACard :bordered="false">
<TableStats :total="pagination.total" />
<ATable
:columns="columns"
:data="data"
@@ -170,6 +169,7 @@ onMounted(() => {
:pagination="false"
/>
<div class="table-footer">
<TableStats :total="pagination.total" />
<APagination
v-model:current="pagination.current"
v-model:page-size="pagination.pageSize"
@@ -179,7 +179,6 @@ function onDateChange(dateString: string | undefined, type: 'start' | 'end') {
</ACard>
<ACard :bordered="false">
<TableStats :total="pagination.total" />
<ATable
:data="list"
:loading="loading"
@@ -224,6 +223,7 @@ function onDateChange(dateString: string | undefined, type: 'start' | 'end') {
</template>
</ATable>
<div class="table-footer">
<TableStats :total="pagination.total" />
<APagination
v-model:current="pagination.current"
v-model:page-size="pagination.pageSize"
+1 -1
View File
@@ -465,7 +465,6 @@ onMounted(() => {
<!-- 列表 -->
<ACard :bordered="false">
<TableStats :total="pagination.total" />
<ASpace wrap style="margin-bottom:16px">
<AInput v-model="filter.tenant_id" placeholder="租户ID" allow-clear style="width:110px" />
<AInput v-model="filter.user_id" placeholder="上传者ID" allow-clear style="width:110px" />
@@ -483,6 +482,7 @@ onMounted(() => {
:pagination="false" row-key="id"
/>
<div class="table-footer">
<TableStats :total="pagination.total" />
<APagination
v-model:current="pagination.current"
v-model:page-size="pagination.pageSize"
@@ -159,9 +159,9 @@ onMounted(() => { fetchCategories() })
</Space>
</div>
</template>
<TableStats :total="pagination.total" />
<ATable :columns="columns" :data="articles" :loading="loading" row-key="id" :scroll="{ x: 1300 }" :pagination="false" />
<div class="table-footer">
<TableStats :total="pagination.total" />
<APagination v-model:current="pagination.current" v-model:page-size="pagination.pageSize" :total="pagination.total" show-page-size @change="fetchList" @page-size-change="(s: number) => { pagination.pageSize = s; pagination.current = 1; fetchList() }" />
</div>
</ACard>
@@ -86,9 +86,9 @@ onMounted(fetchList)
</template>
</PageHeader>
<ACard :bordered="false">
<TableStats :total="pagination.total" />
<ATable :columns="columns" :data="categories" :loading="loading" row-key="id" :scroll="{ x: 1100 }" :pagination="false" />
<div class="table-footer">
<TableStats :total="pagination.total" />
<APagination v-model:current="pagination.current" v-model:page-size="pagination.pageSize" :total="pagination.total" show-page-size @change="fetchList" @page-size-change="(s: number) => { pagination.pageSize = s; pagination.current = 1; fetchList() }" />
</div>
</ACard>
+3 -1
View File
@@ -305,7 +305,6 @@ onMounted(() => {
<!-- Table -->
<ACard :bordered="false">
<TableStats :total="data.length" />
<ATable
:columns="columns"
:data="data"
@@ -319,6 +318,9 @@ onMounted(() => {
<AEmpty v-if="!loading && data.length === 0" style="margin-top: 40px">
<template #description>暂无已注册的插件</template>
</AEmpty>
<div class="table-footer">
<TableStats :total="data.length" />
</div>
</ACard>
<!-- Config Modal -->
+1 -1
View File
@@ -275,7 +275,6 @@ const { exporting, exportFile } = useExport({
<!-- Table Card -->
<ACard :bordered="false">
<TableStats :total="pagination.total" />
<ATable
:columns="columns"
:data="data"
@@ -287,6 +286,7 @@ const { exporting, exportFile } = useExport({
row-key="id"
/>
<div class="table-footer">
<TableStats :total="pagination.total" />
<APagination
v-model:current="pagination.current"
v-model:page-size="pagination.pageSize"
+1 -1
View File
@@ -376,7 +376,6 @@ const { exporting, exportFile } = useExport({
<!-- Table -->
<ACard :bordered="false">
<TableStats :total="pagination.total" />
<ATable
:columns="columns"
:data="data"
@@ -388,6 +387,7 @@ const { exporting, exportFile } = useExport({
row-key="id"
/>
<div class="table-footer">
<TableStats :total="pagination.total" />
<APagination
v-model:current="pagination.current"
v-model:page-size="pagination.pageSize"
@@ -792,7 +792,6 @@ onMounted(() => {
<template #extra>
<AButton type="primary" size="small" @click="openGroupModal">分配分组</AButton>
</template>
<TableStats :total="tenantGroups.length" />
<div v-if="tenantGroups.length === 0" style="color: var(--ta-text-tertiary)">
暂未分配模型分组,租户可通过分组获取可用模型
</div>
@@ -807,13 +806,15 @@ onMounted(() => {
row-key="group_id"
size="small"
/>
<div class="table-footer">
<TableStats :total="tenantGroups.length" />
</div>
</ACard>
<ACard :bordered="false" title="独立模型">
<template #extra>
<AButton type="primary" size="small" @click="openAssignModal">分配模型</AButton>
</template>
<TableStats :total="modelsData.length" />
<div v-if="modelsData.length === 0" style="color: var(--ta-text-tertiary)">
暂无独立分配的模型
</div>
@@ -828,6 +829,9 @@ onMounted(() => {
row-key="id"
:scroll="{ x: 1100 }"
/>
<div class="table-footer">
<TableStats :total="modelsData.length" />
</div>
</ACard>
</ATabPane>
@@ -143,8 +143,10 @@ onMounted(fetchLevels)
<ACard :bordered="false">
<template #title>等级配置列表</template>
<TableStats :total="levels.length" />
<ATable :columns="columns" :data="levels" :loading="loading" row-key="id" :scroll="{ x: 900 }" :pagination="false" />
<div class="table-footer">
<TableStats :total="levels.length" />
</div>
</ACard>
<ADrawer v-model:visible="showModal" :title="editingId ? '编辑等级' : '创建等级'" :width="520" :mask-closable="false" :footer="true" unmount-on-close>
+1 -1
View File
@@ -323,7 +323,6 @@ const { exporting, exportFile } = useExport({
<!-- Table -->
<ACard :bordered="false">
<TableStats :total="pagination.total" />
<ATable
:columns="columns"
:data="data"
@@ -335,6 +334,7 @@ const { exporting, exportFile } = useExport({
row-key="id"
/>
<div class="table-footer">
<TableStats :total="pagination.total" />
<APagination
v-model:current="pagination.current"
v-model:page-size="pagination.pageSize"
+8 -2
View File
@@ -521,7 +521,6 @@ const { exporting, exportFile } = useExport({
</a-card>
<a-card :bordered="false">
<TableStats :total="pagination.total" />
<a-table
:columns="columns"
:data="data"
@@ -534,6 +533,7 @@ const { exporting, exportFile } = useExport({
row-key="id"
/>
<div class="table-footer">
<TableStats :total="pagination.total" />
<a-pagination
v-model:current="pagination.current"
v-model:page-size="pagination.pageSize"
@@ -1317,9 +1317,15 @@ const { exporting, exportFile } = useExport({
.table-footer {
display: flex;
justify-content: flex-end;
align-items: center;
justify-content: space-between;
gap: 12px;
margin-top: 16px;
padding-top: 16px;
border-top: 1px solid var(--color-border-light, #e5e6eb);
}
/* 统计栏移入底部后,去掉全局样式的下边距,与分页栏垂直居中 */
.table-footer :deep(.table-stats) {
margin-bottom: 0;
}
</style>