improve(ui):优化租户端表格分页组件
This commit is contained in:
1 parent
657c452ea1
commit
ec4a4682e5
15 files changed
+159
-128
No files matched your search
@@ -1,20 +1,33 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, ref } from 'vue'
|
||||
|
||||
const props = defineProps<{
|
||||
const props = withDefaults(defineProps<{
|
||||
modelValue: number
|
||||
pageSize: number
|
||||
total: number
|
||||
}>()
|
||||
pageSizeOptions?: number[]
|
||||
showSizeChanger?: boolean
|
||||
}>(), {
|
||||
pageSizeOptions: () => [10, 20, 50, 100],
|
||||
showSizeChanger: true,
|
||||
})
|
||||
|
||||
const emit = defineEmits<{
|
||||
'update:modelValue': [page: number]
|
||||
'update:pageSize': [size: number]
|
||||
change: [page: number]
|
||||
}>()
|
||||
|
||||
const jumpPage = ref('')
|
||||
const totalPages = computed(() => Math.max(1, Math.ceil(props.total / props.pageSize)))
|
||||
|
||||
// 当前 pageSize 不在候选列表时兜底追加,避免 select 找不到选中项
|
||||
const sizeOptions = computed(() => {
|
||||
const opts = [...props.pageSizeOptions]
|
||||
if (!opts.includes(props.pageSize)) opts.push(props.pageSize)
|
||||
return opts.sort((a, b) => a - b)
|
||||
})
|
||||
|
||||
const pageItems = computed(() => {
|
||||
const count = totalPages.value
|
||||
if (count <= 7) {
|
||||
@@ -47,6 +60,14 @@ function goToPage(page: number) {
|
||||
emit('change', target)
|
||||
}
|
||||
|
||||
function changePageSize(size: number) {
|
||||
if (!Number.isFinite(size) || size === props.pageSize) return
|
||||
emit('update:pageSize', size)
|
||||
// 切换每页条数后回到第一页并触发刷新(即使原本就在第一页也要重新拉取)
|
||||
emit('update:modelValue', 1)
|
||||
emit('change', 1)
|
||||
}
|
||||
|
||||
function submitJump() {
|
||||
const target = Number(jumpPage.value)
|
||||
if (!Number.isFinite(target)) return
|
||||
@@ -57,7 +78,20 @@ function submitJump() {
|
||||
|
||||
<template>
|
||||
<div v-if="total > 0" class="table-pagination">
|
||||
<span>共 {{ total }} 条记录</span>
|
||||
<div class="pagination-meta">
|
||||
<div v-if="showSizeChanger" class="pagination-size">
|
||||
<span>每页</span>
|
||||
<select
|
||||
class="pagination-size-select"
|
||||
:value="pageSize"
|
||||
aria-label="每页条数"
|
||||
@change="changePageSize(Number(($event.target as HTMLSelectElement).value))"
|
||||
>
|
||||
<option v-for="opt in sizeOptions" :key="opt" :value="opt">{{ opt }} 条/页</option>
|
||||
</select>
|
||||
</div>
|
||||
<span>共 {{ total }} 条记录</span>
|
||||
</div>
|
||||
|
||||
<div class="pagination-controls">
|
||||
<button
|
||||
@@ -114,6 +148,44 @@ function submitJump() {
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.pagination-meta {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
min-width: 0;
|
||||
font-size: 0.75rem;
|
||||
color: #64748b;
|
||||
}
|
||||
|
||||
.pagination-size {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.375rem;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.pagination-size-select {
|
||||
height: 2rem;
|
||||
border: 1px solid rgba(203, 213, 225, 0.82);
|
||||
border-radius: 0.5rem;
|
||||
background: rgba(255, 255, 255, 0.72);
|
||||
padding: 0 0.5rem;
|
||||
font-size: 0.75rem;
|
||||
color: #334155;
|
||||
outline: none;
|
||||
cursor: pointer;
|
||||
transition: border-color 160ms ease, box-shadow 160ms ease;
|
||||
}
|
||||
|
||||
.pagination-size-select:hover {
|
||||
border-color: rgba(20, 184, 166, 0.45);
|
||||
}
|
||||
|
||||
.pagination-size-select:focus {
|
||||
border-color: #14b8a6;
|
||||
box-shadow: 0 0 0 3px rgba(20, 184, 166, 0.12);
|
||||
}
|
||||
|
||||
.pagination-controls,
|
||||
.pagination-pages,
|
||||
.pagination-jump {
|
||||
@@ -209,6 +281,10 @@ function submitJump() {
|
||||
}
|
||||
|
||||
@media (max-width: 640px) {
|
||||
.pagination-meta {
|
||||
order: 1;
|
||||
}
|
||||
|
||||
.pagination-controls,
|
||||
.pagination-jump {
|
||||
width: 100%;
|
||||
|
||||
@@ -8,7 +8,7 @@ import { useExport } from '@/composables/useExport'
|
||||
const loading = ref(false)
|
||||
const orders = ref<any[]>([])
|
||||
const page = ref(1)
|
||||
const pageSize = 20
|
||||
const pageSize = ref(20)
|
||||
const total = ref(0)
|
||||
const statusFilter = ref('')
|
||||
|
||||
@@ -60,7 +60,7 @@ const statusFilterOptions = [
|
||||
async function fetchOrders() {
|
||||
loading.value = true
|
||||
try {
|
||||
const params: any = { page: page.value, page_size: pageSize }
|
||||
const params: any = { page: page.value, page_size: pageSize.value }
|
||||
if (statusFilter.value) params.status = statusFilter.value
|
||||
const res = await request.get('/tenant/orders', { params })
|
||||
const raw = res.data?.data
|
||||
@@ -199,7 +199,7 @@ onMounted(() => {
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<BasePagination v-model="page" :page-size="pageSize" :total="total" @change="fetchOrders" />
|
||||
<BasePagination v-model="page" v-model:page-size="pageSize" :total="total" @change="fetchOrders" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -9,7 +9,7 @@ import { useExport } from '@/composables/useExport'
|
||||
const loading = ref(false)
|
||||
const transactions = ref<any[]>([])
|
||||
const page = ref(1)
|
||||
const pageSize = 20
|
||||
const pageSize = ref(20)
|
||||
const total = ref(0)
|
||||
|
||||
// Filter state
|
||||
@@ -80,7 +80,7 @@ function formatAmount(amount: number): string {
|
||||
async function fetchTransactions() {
|
||||
loading.value = true
|
||||
try {
|
||||
const params: any = { page: page.value, page_size: pageSize }
|
||||
const params: any = { page: page.value, page_size: pageSize.value }
|
||||
if (filterType.value) params.type = filterType.value
|
||||
if (filterStartDate.value) params.start_date = filterStartDate.value
|
||||
if (filterEndDate.value) params.end_date = filterEndDate.value
|
||||
@@ -229,7 +229,7 @@ onMounted(fetchTransactions)
|
||||
<p class="empty-state-description">交易记录将在 API 调用和充值后展示</p>
|
||||
</div>
|
||||
|
||||
<BasePagination v-model="page" :page-size="pageSize" :total="total" @change="fetchTransactions" />
|
||||
<BasePagination v-model="page" v-model:page-size="pageSize" :total="total" @change="fetchTransactions" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -1,6 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, computed } from 'vue'
|
||||
import Icon from '@/components/common/Icon.vue'
|
||||
import BasePagination from '@/components/common/BasePagination.vue'
|
||||
import MarkdownRenderer from '@/components/common/MarkdownRenderer.vue'
|
||||
import request from '@/utils/request'
|
||||
|
||||
@@ -48,9 +49,9 @@ const articleDetail = ref<ArticleDetail | null>(null)
|
||||
|
||||
const searchQuery = ref('')
|
||||
const isSearchMode = ref(false)
|
||||
const searchPage = ref(1)
|
||||
const searchTotal = ref(0)
|
||||
const pageSize = 20
|
||||
const listPage = ref(1)
|
||||
const listTotal = ref(0)
|
||||
const pageSize = ref(20)
|
||||
|
||||
const categoryColors = [
|
||||
{ bg: 'bg-primary-50', text: 'text-primary-600', dot: 'bg-primary-500' },
|
||||
@@ -122,25 +123,41 @@ async function fetchCategories() {
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchArticles(slug: string) {
|
||||
async function loadList() {
|
||||
articlesLoading.value = true
|
||||
activeCategorySlug.value = slug
|
||||
activeArticleSlug.value = null
|
||||
articleDetail.value = null
|
||||
isSearchMode.value = false
|
||||
try {
|
||||
const res: any = await request.get(`/tenant/help/categories/${slug}/articles`, {
|
||||
params: { page: 1, page_size: pageSize },
|
||||
})
|
||||
const raw = res.data?.data
|
||||
let raw: any = null
|
||||
if (isSearchMode.value) {
|
||||
const res: any = await request.get('/tenant/help/search', {
|
||||
params: { q: searchQuery.value.trim(), page: listPage.value, page_size: pageSize.value },
|
||||
})
|
||||
raw = res.data?.data
|
||||
} else if (activeCategorySlug.value) {
|
||||
const res: any = await request.get(`/tenant/help/categories/${activeCategorySlug.value}/articles`, {
|
||||
params: { page: listPage.value, page_size: pageSize.value },
|
||||
})
|
||||
raw = res.data?.data
|
||||
}
|
||||
articles.value = Array.isArray(raw) ? raw : (raw?.data || raw?.list || [])
|
||||
// 分类列表若未返回 total,按当前条数兜底(不足一页时不展示翻页)
|
||||
listTotal.value = raw?.total ?? (isSearchMode.value ? 0 : articles.value.length)
|
||||
} catch {
|
||||
articles.value = []
|
||||
listTotal.value = 0
|
||||
} finally {
|
||||
articlesLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchArticles(slug: string) {
|
||||
activeCategorySlug.value = slug
|
||||
activeArticleSlug.value = null
|
||||
isSearchMode.value = false
|
||||
listPage.value = 1
|
||||
await loadList()
|
||||
}
|
||||
|
||||
async function fetchArticleDetail(slug: string) {
|
||||
articleLoading.value = true
|
||||
activeArticleSlug.value = slug
|
||||
@@ -160,22 +177,13 @@ async function handleSearch() {
|
||||
isSearchMode.value = true
|
||||
activeCategorySlug.value = null
|
||||
activeArticleSlug.value = null
|
||||
articleDetail.value = null
|
||||
articlesLoading.value = true
|
||||
searchPage.value = 1
|
||||
try {
|
||||
const res: any = await request.get('/tenant/help/search', {
|
||||
params: { q, page: 1, page_size: pageSize },
|
||||
})
|
||||
const raw = res.data?.data
|
||||
articles.value = Array.isArray(raw) ? raw : (raw?.data || raw?.list || [])
|
||||
searchTotal.value = raw?.total || 0
|
||||
} catch {
|
||||
articles.value = []
|
||||
searchTotal.value = 0
|
||||
} finally {
|
||||
articlesLoading.value = false
|
||||
}
|
||||
listPage.value = 1
|
||||
await loadList()
|
||||
}
|
||||
|
||||
// 翻页 / 切换每页条数时重新加载(listPage 已由分页组件 v-model 更新)
|
||||
function handleListPageChange() {
|
||||
loadList()
|
||||
}
|
||||
|
||||
function goBackToList() {
|
||||
@@ -390,7 +398,7 @@ onMounted(fetchCategories)
|
||||
<template v-else-if="articles.length > 0">
|
||||
<div v-if="isSearchMode" class="flex items-center gap-2 text-sm text-gray-500 mb-4">
|
||||
<Icon name="search" size="sm" class="text-gray-400" />
|
||||
搜索 "{{ searchQuery }}" 找到 <span class="font-medium text-gray-700">{{ searchTotal }}</span> 条结果
|
||||
搜索 "{{ searchQuery }}" 找到 <span class="font-medium text-gray-700">{{ listTotal }}</span> 条结果
|
||||
</div>
|
||||
<div v-else-if="activeCategorySlug" class="flex items-center gap-2 text-sm text-gray-500 mb-4">
|
||||
<span class="w-2 h-2 rounded-full" :class="catColor(activeColorIndex).dot" />
|
||||
@@ -425,6 +433,8 @@ onMounted(fetchCategories)
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<BasePagination v-model="listPage" v-model:page-size="pageSize" :total="listTotal" @change="handleListPageChange" />
|
||||
</template>
|
||||
|
||||
<!-- Empty search results -->
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, computed } from 'vue'
|
||||
import { ref, onMounted } from 'vue'
|
||||
import Icon from '@/components/common/Icon.vue'
|
||||
import BasePagination from '@/components/common/BasePagination.vue'
|
||||
import request from '@/utils/request'
|
||||
import { useExport } from '@/composables/useExport'
|
||||
import { useNotificationCount } from '@/composables/useNotificationCount'
|
||||
@@ -18,7 +19,7 @@ interface Notification {
|
||||
const notifications = ref<Notification[]>([])
|
||||
const loading = ref(false)
|
||||
const page = ref(1)
|
||||
const pageSize = 20
|
||||
const pageSize = ref(20)
|
||||
const total = ref(0)
|
||||
const activeTab = ref<'all' | 'unread'>('all')
|
||||
|
||||
@@ -29,8 +30,6 @@ const { exporting, exportFile } = useExport({
|
||||
|
||||
const { unreadCount, decrement: decrementUnread, reset: resetUnread } = useNotificationCount()
|
||||
|
||||
const totalPages = computed(() => Math.ceil(total.value / pageSize))
|
||||
|
||||
const typeLabel: Record<string, string> = {
|
||||
security: '安全',
|
||||
billing: '账单',
|
||||
@@ -50,7 +49,7 @@ const typeBadgeClass: Record<string, string> = {
|
||||
async function fetchNotifications() {
|
||||
loading.value = true
|
||||
try {
|
||||
const params: any = { page: page.value, page_size: pageSize }
|
||||
const params: any = { page: page.value, page_size: pageSize.value }
|
||||
if (activeTab.value === 'unread') params.is_read = false
|
||||
const res: any = await request.get('/tenant/notifications', { params })
|
||||
const raw = res.data?.data
|
||||
@@ -110,11 +109,6 @@ function switchTab(tab: 'all' | 'unread') {
|
||||
fetchNotifications()
|
||||
}
|
||||
|
||||
function handlePageChange(newPage: number) {
|
||||
page.value = newPage
|
||||
fetchNotifications()
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
fetchNotifications()
|
||||
fetchUnreadCount()
|
||||
@@ -232,26 +226,7 @@ onMounted(() => {
|
||||
</div>
|
||||
|
||||
<!-- Pagination -->
|
||||
<div v-if="totalPages > 1" class="flex items-center justify-between px-1 pt-3">
|
||||
<span class="text-xs text-gray-500">共 {{ total }} 条通知</span>
|
||||
<div class="flex items-center gap-2">
|
||||
<button
|
||||
class="btn btn-ghost btn-sm"
|
||||
:disabled="page <= 1"
|
||||
@click="handlePageChange(page - 1)"
|
||||
>
|
||||
上一页
|
||||
</button>
|
||||
<span class="text-sm text-gray-600">{{ page }} / {{ totalPages }}</span>
|
||||
<button
|
||||
class="btn btn-ghost btn-sm"
|
||||
:disabled="page >= totalPages"
|
||||
@click="handlePageChange(page + 1)"
|
||||
>
|
||||
下一页
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<BasePagination v-model="page" v-model:page-size="pageSize" :total="total" @change="fetchNotifications" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -33,7 +33,7 @@ interface ApiKey {
|
||||
const keys = ref<ApiKey[]>([])
|
||||
const loading = ref(false)
|
||||
const page = ref(1)
|
||||
const pageSize = 20
|
||||
const pageSize = ref(20)
|
||||
const total = ref(0)
|
||||
|
||||
const showExportDropdown = ref(false)
|
||||
@@ -88,7 +88,7 @@ async function fetchKeys() {
|
||||
loading.value = true
|
||||
try {
|
||||
const res: any = await request.get('/tenant/api-keys', {
|
||||
params: { page: page.value, page_size: pageSize, key_type: 'personal' },
|
||||
params: { page: page.value, page_size: pageSize.value, key_type: 'personal' },
|
||||
})
|
||||
const raw = res.data?.data
|
||||
keys.value = Array.isArray(raw) ? raw : (raw?.data || raw?.list || [])
|
||||
@@ -256,7 +256,7 @@ onMounted(() => {
|
||||
<p class="empty-state-description">创建第一个密钥以开始使用 AI 模型</p>
|
||||
</div>
|
||||
|
||||
<BasePagination v-model="page" :page-size="pageSize" :total="total" @change="fetchKeys" />
|
||||
<BasePagination v-model="page" v-model:page-size="pageSize" :total="total" @change="fetchKeys" />
|
||||
</div>
|
||||
|
||||
<!-- Create Modal -->
|
||||
|
||||
@@ -25,7 +25,7 @@ interface RequestLog {
|
||||
const logs = ref<RequestLog[]>([])
|
||||
const logsLoading = ref(false)
|
||||
const logPage = ref(1)
|
||||
const logPageSize = 20
|
||||
const logPageSize = ref(20)
|
||||
const logTotal = ref(0)
|
||||
|
||||
const logFilter = reactive({
|
||||
@@ -117,7 +117,7 @@ async function fetchRequestLogs() {
|
||||
try {
|
||||
const params: Record<string, any> = {
|
||||
page: logPage.value,
|
||||
page_size: logPageSize,
|
||||
page_size: logPageSize.value,
|
||||
}
|
||||
if (logFilter.username) params.username = logFilter.username
|
||||
if (logFilter.request_id) params.request_id = logFilter.request_id
|
||||
@@ -307,7 +307,7 @@ onMounted(() => {
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<BasePagination v-model="logPage" :page-size="logPageSize" :total="logTotal" @change="fetchRequestLogs" />
|
||||
<BasePagination v-model="logPage" v-model:page-size="logPageSize" :total="logTotal" @change="fetchRequestLogs" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@ interface TaskItem {
|
||||
const loading = ref(false)
|
||||
const tasks = ref<TaskItem[]>([])
|
||||
const page = ref(1)
|
||||
const pageSize = 20
|
||||
const pageSize = ref(20)
|
||||
const total = ref(0)
|
||||
|
||||
const filterStatus = ref('')
|
||||
@@ -94,7 +94,7 @@ async function fetchTasks() {
|
||||
try {
|
||||
const params: Record<string, any> = {
|
||||
page: page.value,
|
||||
page_size: pageSize,
|
||||
page_size: pageSize.value,
|
||||
}
|
||||
if (filterStatus.value) params.status = filterStatus.value
|
||||
if (filterPlatform.value) params.platform = filterPlatform.value
|
||||
@@ -263,7 +263,7 @@ onMounted(() => {
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<BasePagination v-model="page" :page-size="pageSize" :total="total" @change="fetchTasks" />
|
||||
<BasePagination v-model="page" v-model:page-size="pageSize" :total="total" @change="fetchTasks" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ import { useExport } from '@/composables/useExport'
|
||||
const loading = ref(false)
|
||||
const logs = ref<any[]>([])
|
||||
const page = ref(1)
|
||||
const pageSize = 20
|
||||
const pageSize = ref(20)
|
||||
const total = ref(0)
|
||||
|
||||
const filterUsername = ref('')
|
||||
@@ -103,7 +103,7 @@ const billingSourceLabel: Record<string, string> = {
|
||||
async function fetchLogs() {
|
||||
loading.value = true
|
||||
try {
|
||||
const params: any = { page: page.value, page_size: pageSize }
|
||||
const params: any = { page: page.value, page_size: pageSize.value }
|
||||
if (filterUsername.value) params.username = filterUsername.value
|
||||
if (filterModel.value) params.model = filterModel.value
|
||||
if (filterStatus.value) params.status = filterStatus.value
|
||||
@@ -295,7 +295,6 @@ onMounted(() => {
|
||||
<th class="min-w-50">用户/项目</th>
|
||||
<th class="min-w-40">API Key</th>
|
||||
<th class="min-w-45">模型</th>
|
||||
<th class="min-w-40">模型显示名称</th>
|
||||
<th class="min-w-30">类型</th>
|
||||
<th class="min-w-30">Token</th>
|
||||
<th class="min-w-20">费用</th>
|
||||
@@ -326,10 +325,6 @@ onMounted(() => {
|
||||
</div>
|
||||
<span v-else class="font-medium text-gray-900">{{ log.model_name }}</span>
|
||||
</td>
|
||||
<!-- 模型显示名称 -->
|
||||
<td>
|
||||
<span class="text-sm text-gray-700">{{ log.model_display_name || '-' }}</span>
|
||||
</td>
|
||||
|
||||
<!-- 请求类型 -->
|
||||
<td>
|
||||
@@ -441,7 +436,7 @@ onMounted(() => {
|
||||
<p class="empty-state-description">日志将在 API 调用后展示</p>
|
||||
</div>
|
||||
|
||||
<BasePagination v-model="page" :page-size="pageSize" :total="total" @change="fetchLogs" />
|
||||
<BasePagination v-model="page" v-model:page-size="pageSize" :total="total" @change="fetchLogs" />
|
||||
</div>
|
||||
|
||||
<!-- Token Tooltip -->
|
||||
|
||||
@@ -8,7 +8,7 @@ import BaseSelect from '../../components/common/BaseSelect.vue'
|
||||
const list = ref<any[]>([])
|
||||
const loading = ref(false)
|
||||
const page = ref(1)
|
||||
const pageSize = 20
|
||||
const pageSize = ref(20)
|
||||
const total = ref(0)
|
||||
|
||||
const filterUsername = ref('')
|
||||
@@ -27,7 +27,7 @@ async function fetchData() {
|
||||
try {
|
||||
const params: Record<string, any> = {
|
||||
page: page.value,
|
||||
page_size: pageSize,
|
||||
page_size: pageSize.value,
|
||||
}
|
||||
if (filterUsername.value) params.username = filterUsername.value
|
||||
if (filterIpAddress.value) params.ip_address = filterIpAddress.value
|
||||
@@ -180,7 +180,7 @@ const methodLabel: Record<string, string> = {
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<BasePagination v-model="page" :page-size="pageSize" :total="total" @change="fetchData" />
|
||||
<BasePagination v-model="page" v-model:page-size="pageSize" :total="total" @change="fetchData" />
|
||||
</div>
|
||||
|
||||
<!-- Empty state -->
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, onMounted, computed } from 'vue'
|
||||
import BaseModal from '@/components/common/BaseModal.vue'
|
||||
import BasePagination from '@/components/common/BasePagination.vue'
|
||||
import Icon from '@/components/common/Icon.vue'
|
||||
import BaseSelect from '../../components/common/BaseSelect.vue'
|
||||
import request from '@/utils/request'
|
||||
@@ -22,7 +23,7 @@ interface FeedbackItem {
|
||||
const feedbacks = ref<FeedbackItem[]>([])
|
||||
const loading = ref(false)
|
||||
const page = ref(1)
|
||||
const pageSize = 20
|
||||
const pageSize = ref(20)
|
||||
const total = ref(0)
|
||||
|
||||
const filterStatus = ref('')
|
||||
@@ -85,15 +86,13 @@ const categoryIcon: Record<string, string> = {
|
||||
complaint: 'exclamationCircle',
|
||||
}
|
||||
|
||||
const totalPages = computed(() => Math.ceil(total.value / pageSize))
|
||||
|
||||
const filterStatusOptions = computed(() => [{value:'',label:'全部状态'}, ...statusOptions])
|
||||
const filterCategoryOptions = computed(() => [{value:'',label:'全部类型'}, ...categoryOptions])
|
||||
|
||||
async function fetchFeedbacks() {
|
||||
loading.value = true
|
||||
try {
|
||||
const params: any = { page: page.value, page_size: pageSize }
|
||||
const params: any = { page: page.value, page_size: pageSize.value }
|
||||
if (filterStatus.value) params.status = filterStatus.value
|
||||
if (filterCategory.value) params.category = filterCategory.value
|
||||
const res: any = await request.get('/tenant/feedbacks', { params })
|
||||
@@ -143,11 +142,6 @@ async function openDetail(fb: FeedbackItem) {
|
||||
}
|
||||
}
|
||||
|
||||
function handlePageChange(newPage: number) {
|
||||
page.value = newPage
|
||||
fetchFeedbacks()
|
||||
}
|
||||
|
||||
function handleFilter() {
|
||||
page.value = 1
|
||||
fetchFeedbacks()
|
||||
@@ -258,26 +252,7 @@ onMounted(() => {
|
||||
</div>
|
||||
|
||||
<!-- Pagination -->
|
||||
<div v-if="totalPages > 1" class="card flex items-center justify-between px-6 py-3">
|
||||
<span class="text-xs text-gray-500">共 {{ total }} 条记录</span>
|
||||
<div class="flex items-center gap-2">
|
||||
<button
|
||||
class="btn btn-ghost btn-sm"
|
||||
:disabled="page <= 1"
|
||||
@click="handlePageChange(page - 1)"
|
||||
>
|
||||
上一页
|
||||
</button>
|
||||
<span class="text-sm text-gray-600">{{ page }} / {{ totalPages }}</span>
|
||||
<button
|
||||
class="btn btn-ghost btn-sm"
|
||||
:disabled="page >= totalPages"
|
||||
@click="handlePageChange(page + 1)"
|
||||
>
|
||||
下一页
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<BasePagination v-model="page" v-model:page-size="pageSize" :total="total" @change="fetchFeedbacks" />
|
||||
</div>
|
||||
|
||||
<!-- Create Feedback Modal -->
|
||||
|
||||
@@ -31,7 +31,7 @@ interface Reply {
|
||||
const tickets = ref<Ticket[]>([])
|
||||
const loading = ref(false)
|
||||
const page = ref(1)
|
||||
const pageSize = 20
|
||||
const pageSize = ref(20)
|
||||
const total = ref(0)
|
||||
|
||||
const showExportDropdown = ref(false)
|
||||
@@ -111,7 +111,7 @@ async function fetchTickets() {
|
||||
loading.value = true
|
||||
try {
|
||||
const res: any = await request.get('/tenant/tickets', {
|
||||
params: { page: page.value, page_size: pageSize },
|
||||
params: { page: page.value, page_size: pageSize.value },
|
||||
})
|
||||
const raw = res.data?.data
|
||||
tickets.value = Array.isArray(raw) ? raw : (raw?.data || raw?.list || [])
|
||||
@@ -306,7 +306,7 @@ onMounted(() => {
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<BasePagination v-model="page" :page-size="pageSize" :total="total" @change="fetchTickets" />
|
||||
<BasePagination v-model="page" v-model:page-size="pageSize" :total="total" @change="fetchTickets" />
|
||||
</div>
|
||||
|
||||
<!-- Create Ticket Modal -->
|
||||
|
||||
@@ -29,7 +29,7 @@ const invitations = ref<InvitationItem[]>([])
|
||||
const loading = ref(false)
|
||||
const total = ref(0)
|
||||
const page = ref(1)
|
||||
const pageSize = 20
|
||||
const pageSize = ref(20)
|
||||
|
||||
const roleBadgeClass: Record<string, string> = {
|
||||
owner: 'badge-primary',
|
||||
@@ -64,7 +64,7 @@ async function fetchInvitations() {
|
||||
loading.value = true
|
||||
try {
|
||||
const res: any = await request.get('/tenant/members/invitations', {
|
||||
params: { page: page.value, page_size: pageSize },
|
||||
params: { page: page.value, page_size: pageSize.value },
|
||||
})
|
||||
const raw = res.data?.data
|
||||
invitations.value = raw?.list || []
|
||||
@@ -183,7 +183,7 @@ onMounted(() => {
|
||||
<p class="empty-state-description">生成邀请链接后,记录会显示在这里</p>
|
||||
</div>
|
||||
|
||||
<BasePagination v-model="page" :page-size="pageSize" :total="total" @change="fetchInvitations" />
|
||||
<BasePagination v-model="page" v-model:page-size="pageSize" :total="total" @change="fetchInvitations" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -38,7 +38,7 @@ const members = ref<Member[]>([])
|
||||
const loading = ref(false)
|
||||
const total = ref(0)
|
||||
const page = ref(1)
|
||||
const pageSize = 20
|
||||
const pageSize = ref(20)
|
||||
const keyword = ref('')
|
||||
|
||||
const showInviteModal = ref(false)
|
||||
@@ -95,7 +95,7 @@ async function fetchMembers() {
|
||||
loading.value = true
|
||||
try {
|
||||
const res: any = await request.get('/tenant/members', {
|
||||
params: { page: page.value, page_size: pageSize, keyword: keyword.value },
|
||||
params: { page: page.value, page_size: pageSize.value, keyword: keyword.value },
|
||||
})
|
||||
const raw = res.data?.data; members.value = Array.isArray(raw) ? raw : (raw?.data || raw?.list || [])
|
||||
total.value = raw?.total || 0
|
||||
@@ -341,7 +341,7 @@ onMounted(() => {
|
||||
<p class="empty-state-description">邀请第一位团队成员吧</p>
|
||||
</div>
|
||||
|
||||
<BasePagination v-model="page" :page-size="pageSize" :total="total" @change="fetchMembers" />
|
||||
<BasePagination v-model="page" v-model:page-size="pageSize" :total="total" @change="fetchMembers" />
|
||||
</div>
|
||||
|
||||
<!-- Invite Modal -->
|
||||
|
||||
@@ -13,7 +13,7 @@ const router = useRouter()
|
||||
const loading = ref(false)
|
||||
const projects = ref<any[]>([])
|
||||
const page = ref(1)
|
||||
const pageSize = 20
|
||||
const pageSize = ref(20)
|
||||
const total = ref(0)
|
||||
|
||||
const statusLabels: Record<string, string> = {
|
||||
@@ -85,7 +85,7 @@ async function fetchProjects() {
|
||||
loading.value = true
|
||||
try {
|
||||
const res: any = await request.get('/tenant/projects', {
|
||||
params: { page: page.value, page_size: pageSize }
|
||||
params: { page: page.value, page_size: pageSize.value }
|
||||
})
|
||||
const raw = res.data?.data
|
||||
projects.value = Array.isArray(raw) ? raw : (raw?.data || raw?.list || [])
|
||||
@@ -170,7 +170,7 @@ function goToDetail(item: any) {
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<BasePagination v-model="page" :page-size="pageSize" :total="total" @change="fetchProjects" />
|
||||
<BasePagination v-model="page" v-model:page-size="pageSize" :total="total" @change="fetchProjects" />
|
||||
</div>
|
||||
|
||||
<!-- Create/Edit Modal -->
|
||||
|
||||
Reference in new issue
Block a user