后台列表统一分页组件,工单/人员/账号改为服务端分页。
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -18,9 +18,13 @@ import {
|
|||||||
} from "/@/views/inquiry/account/account.api";
|
} from "/@/views/inquiry/account/account.api";
|
||||||
import ProtoIcon from "/@/views/inquiry/components/ProtoIcon.vue";
|
import ProtoIcon from "/@/views/inquiry/components/ProtoIcon.vue";
|
||||||
import AdminModal from "/@/views/inquiry/components/AdminModal.vue";
|
import AdminModal from "/@/views/inquiry/components/AdminModal.vue";
|
||||||
|
import AdminPagination from "/@/views/inquiry/components/AdminPagination.vue";
|
||||||
|
|
||||||
const rows = ref<AccountRow[]>([]);
|
const rows = ref<AccountRow[]>([]);
|
||||||
const loading = ref(false);
|
const loading = ref(false);
|
||||||
|
const total = ref(0);
|
||||||
|
const pageNo = ref(1);
|
||||||
|
const pageSize = ref(10);
|
||||||
const roleOptions = ref<{ value: string; label: string; tag: string }[]>([]);
|
const roleOptions = ref<{ value: string; label: string; tag: string }[]>([]);
|
||||||
const filters = reactive({ keyword: "", status: "" as "" | "active" | "disabled" });
|
const filters = reactive({ keyword: "", status: "" as "" | "active" | "disabled" });
|
||||||
const modalOpen = ref(false);
|
const modalOpen = ref(false);
|
||||||
@@ -35,15 +39,6 @@ const form = reactive({
|
|||||||
password: "",
|
password: "",
|
||||||
});
|
});
|
||||||
|
|
||||||
const filtered = computed(() =>
|
|
||||||
rows.value.filter((r) => {
|
|
||||||
const kw = filters.keyword.trim().toLowerCase();
|
|
||||||
if (kw && !`${r.username}${r.name}${r.wecomUserId}${r.phoneMasked}`.toLowerCase().includes(kw)) return false;
|
|
||||||
if (filters.status && r.status !== filters.status) return false;
|
|
||||||
return true;
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
const modalTitle = computed(() => (editing.value ? "编辑账号" : "新建账号"));
|
const modalTitle = computed(() => (editing.value ? "编辑账号" : "新建账号"));
|
||||||
const modalSub = computed(() =>
|
const modalSub = computed(() =>
|
||||||
editing.value ? `正在编辑:${editing.value.username}` : "创建账号并绑定角色",
|
editing.value ? `正在编辑:${editing.value.username}` : "创建账号并绑定角色",
|
||||||
@@ -67,8 +62,28 @@ async function loadList() {
|
|||||||
loading.value = true;
|
loading.value = true;
|
||||||
try {
|
try {
|
||||||
if (!roleOptions.value.length) await loadRoles();
|
if (!roleOptions.value.length) await loadRoles();
|
||||||
const res: any = await listUsers({ pageNo: 1, pageSize: 200 });
|
const kw = filters.keyword.trim();
|
||||||
const records = res?.records || res?.result?.records || [];
|
// Jeecg QueryGenerator:字段值包 * 走 LIKE。多字段 AND 过严,按关键字形态选一个字段。
|
||||||
|
const params: Record<string, any> = {
|
||||||
|
pageNo: pageNo.value,
|
||||||
|
pageSize: pageSize.value,
|
||||||
|
};
|
||||||
|
if (kw) {
|
||||||
|
if (/[\u4e00-\u9fff]/.test(kw)) {
|
||||||
|
params.realname = `*${kw}*`;
|
||||||
|
} else if (/^\d{5,}$/.test(kw)) {
|
||||||
|
params.phone = `*${kw}*`;
|
||||||
|
} else {
|
||||||
|
params.username = `*${kw}*`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (filters.status === "active") params.status = 1;
|
||||||
|
if (filters.status === "disabled") params.status = 2;
|
||||||
|
|
||||||
|
const res: any = await listUsers(params);
|
||||||
|
const page = res?.records ? res : res?.result || {};
|
||||||
|
const records = page.records || [];
|
||||||
|
total.value = Number(page.total || records.length || 0);
|
||||||
const mapped: AccountRow[] = [];
|
const mapped: AccountRow[] = [];
|
||||||
for (const u of records as any[]) {
|
for (const u of records as any[]) {
|
||||||
let roleId = "";
|
let roleId = "";
|
||||||
@@ -89,6 +104,11 @@ async function loadList() {
|
|||||||
mapped.push(mapUserToAccount(u, roleId, roleLabel, roleTag));
|
mapped.push(mapUserToAccount(u, roleId, roleLabel, roleTag));
|
||||||
}
|
}
|
||||||
rows.value = mapped;
|
rows.value = mapped;
|
||||||
|
const maxPage = Math.max(1, Math.ceil(total.value / pageSize.value) || 1);
|
||||||
|
if (pageNo.value > maxPage) {
|
||||||
|
pageNo.value = maxPage;
|
||||||
|
await loadList();
|
||||||
|
}
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
message.error(e?.message || "加载账号失败");
|
message.error(e?.message || "加载账号失败");
|
||||||
} finally {
|
} finally {
|
||||||
@@ -96,6 +116,20 @@ async function loadList() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let accountSearchTimer: ReturnType<typeof setTimeout> | null = null;
|
||||||
|
watch(
|
||||||
|
() => [filters.keyword, filters.status],
|
||||||
|
() => {
|
||||||
|
pageNo.value = 1;
|
||||||
|
if (accountSearchTimer) clearTimeout(accountSearchTimer);
|
||||||
|
accountSearchTimer = setTimeout(() => loadList(), 250);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
function onPageChange() {
|
||||||
|
loadList();
|
||||||
|
}
|
||||||
|
|
||||||
onMounted(() => loadList());
|
onMounted(() => loadList());
|
||||||
|
|
||||||
function openCreate() {
|
function openCreate() {
|
||||||
@@ -225,13 +259,6 @@ function removeAccount(row: AccountRow) {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
watch(
|
|
||||||
() => filters.keyword,
|
|
||||||
() => {
|
|
||||||
/* 本地过滤即可,账号量通常很小 */
|
|
||||||
},
|
|
||||||
);
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -250,7 +277,7 @@ watch(
|
|||||||
<div class="toolbar">
|
<div class="toolbar">
|
||||||
<div class="search">
|
<div class="search">
|
||||||
<ProtoIcon class="search-icon" name="search" :size="16" />
|
<ProtoIcon class="search-icon" name="search" :size="16" />
|
||||||
<input v-model="filters.keyword" placeholder="搜索账号、姓名、企微ID、手机号..." />
|
<input v-model="filters.keyword" placeholder="搜索账号 / 姓名 / 手机号..." />
|
||||||
</div>
|
</div>
|
||||||
<select v-model="filters.status">
|
<select v-model="filters.status">
|
||||||
<option value="">状态</option>
|
<option value="">状态</option>
|
||||||
@@ -276,10 +303,10 @@ watch(
|
|||||||
<tr v-if="loading">
|
<tr v-if="loading">
|
||||||
<td colspan="7" style="text-align: center; color: #94a3b8">加载中…</td>
|
<td colspan="7" style="text-align: center; color: #94a3b8">加载中…</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr v-else-if="!filtered.length">
|
<tr v-else-if="!rows.length">
|
||||||
<td colspan="7" style="text-align: center; color: #94a3b8">暂无账号</td>
|
<td colspan="7" style="text-align: center; color: #94a3b8">暂无账号</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr v-for="row in filtered" :key="row.id">
|
<tr v-for="row in rows" :key="row.id">
|
||||||
<td class="account-username">{{ row.username }}</td>
|
<td class="account-username">{{ row.username }}</td>
|
||||||
<td>{{ row.name }}</td>
|
<td>{{ row.name }}</td>
|
||||||
<td>
|
<td>
|
||||||
@@ -312,14 +339,14 @@ watch(
|
|||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
<div class="pagination">
|
<AdminPagination
|
||||||
<span>
|
v-model:page-no="pageNo"
|
||||||
共 <b style="color: #0f172a">{{ filtered.length }}</b> 个账号
|
v-model:page-size="pageSize"
|
||||||
</span>
|
:total="total"
|
||||||
<div class="pages">
|
unit="个账号"
|
||||||
<button class="is-active" type="button">1</button>
|
:disabled="loading"
|
||||||
</div>
|
@change="onPageChange"
|
||||||
</div>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<AdminModal
|
<AdminModal
|
||||||
|
|||||||
@@ -0,0 +1,172 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
/**
|
||||||
|
* 询价后台列表分页条:总数 + 每页条数 + 页码。
|
||||||
|
* 样式对齐 admin-fidelity.css 的 .pagination;翻页只发事件,不请求接口。
|
||||||
|
*/
|
||||||
|
import { computed } from "vue";
|
||||||
|
|
||||||
|
const props = withDefaults(
|
||||||
|
defineProps<{
|
||||||
|
total: number;
|
||||||
|
pageNo: number;
|
||||||
|
pageSize: number;
|
||||||
|
/** 文案:共 N {unit} */
|
||||||
|
unit?: string;
|
||||||
|
pageSizeOptions?: number[];
|
||||||
|
disabled?: boolean;
|
||||||
|
}>(),
|
||||||
|
{
|
||||||
|
unit: "条",
|
||||||
|
pageSizeOptions: () => [10, 20, 50, 100],
|
||||||
|
disabled: false,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
(e: "update:pageNo", v: number): void;
|
||||||
|
(e: "update:pageSize", v: number): void;
|
||||||
|
(e: "change"): void;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const totalPages = computed(() => Math.max(1, Math.ceil(Math.max(0, props.total) / Math.max(1, props.pageSize))));
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 页码按钮:两端保留,中间省略。
|
||||||
|
* 例:1 … 4 5 6 … 20
|
||||||
|
*/
|
||||||
|
const pageItems = computed(() => {
|
||||||
|
const total = totalPages.value;
|
||||||
|
const cur = Math.min(Math.max(1, props.pageNo), total);
|
||||||
|
if (total <= 7) {
|
||||||
|
return Array.from({ length: total }, (_, i) => i + 1) as Array<number | "ellipsis">;
|
||||||
|
}
|
||||||
|
const set = new Set<number>();
|
||||||
|
set.add(1);
|
||||||
|
set.add(total);
|
||||||
|
for (let p = cur - 1; p <= cur + 1; p++) {
|
||||||
|
if (p >= 1 && p <= total) set.add(p);
|
||||||
|
}
|
||||||
|
if (cur <= 3) {
|
||||||
|
set.add(2);
|
||||||
|
set.add(3);
|
||||||
|
set.add(4);
|
||||||
|
}
|
||||||
|
if (cur >= total - 2) {
|
||||||
|
set.add(total - 1);
|
||||||
|
set.add(total - 2);
|
||||||
|
set.add(total - 3);
|
||||||
|
}
|
||||||
|
const sorted = [...set].sort((a, b) => a - b);
|
||||||
|
const out: Array<number | "ellipsis"> = [];
|
||||||
|
let prev = 0;
|
||||||
|
for (const p of sorted) {
|
||||||
|
if (prev && p - prev > 1) out.push("ellipsis");
|
||||||
|
out.push(p);
|
||||||
|
prev = p;
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
});
|
||||||
|
|
||||||
|
function go(page: number) {
|
||||||
|
if (props.disabled) return;
|
||||||
|
const next = Math.min(Math.max(1, page), totalPages.value);
|
||||||
|
if (next === props.pageNo) return;
|
||||||
|
emit("update:pageNo", next);
|
||||||
|
emit("change");
|
||||||
|
}
|
||||||
|
|
||||||
|
function onSizeChange(e: Event) {
|
||||||
|
if (props.disabled) return;
|
||||||
|
const v = Number((e.target as HTMLSelectElement).value);
|
||||||
|
if (!v || v === props.pageSize) return;
|
||||||
|
emit("update:pageSize", v);
|
||||||
|
emit("update:pageNo", 1);
|
||||||
|
emit("change");
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="pagination admin-pagination">
|
||||||
|
<span class="admin-pagination__total">
|
||||||
|
共 <b>{{ total }}</b> {{ unit }}
|
||||||
|
<span v-if="total > 0" class="admin-pagination__range">
|
||||||
|
(第 {{ pageNo }} / {{ totalPages }} 页)
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
<div class="admin-pagination__right">
|
||||||
|
<label class="admin-pagination__size">
|
||||||
|
每页
|
||||||
|
<select :value="pageSize" :disabled="disabled" @change="onSizeChange">
|
||||||
|
<option v-for="n in pageSizeOptions" :key="n" :value="n">{{ n }}</option>
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
|
<div class="pages">
|
||||||
|
<button type="button" :disabled="disabled || pageNo <= 1" @click="go(pageNo - 1)">上一页</button>
|
||||||
|
<template v-for="(item, idx) in pageItems" :key="idx + '-' + item">
|
||||||
|
<span v-if="item === 'ellipsis'" class="admin-pagination__ellipsis">…</span>
|
||||||
|
<button
|
||||||
|
v-else
|
||||||
|
type="button"
|
||||||
|
:class="{ 'is-active': item === pageNo }"
|
||||||
|
:disabled="disabled"
|
||||||
|
@click="go(item)"
|
||||||
|
>
|
||||||
|
{{ item }}
|
||||||
|
</button>
|
||||||
|
</template>
|
||||||
|
<button type="button" :disabled="disabled || pageNo >= totalPages" @click="go(pageNo + 1)">下一页</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.admin-pagination__right {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
justify-content: flex-end;
|
||||||
|
}
|
||||||
|
|
||||||
|
.admin-pagination__size {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
color: #475467;
|
||||||
|
font-size: 11px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.admin-pagination__size select {
|
||||||
|
height: 28px;
|
||||||
|
border: 1px solid var(--line, #e4e7ec);
|
||||||
|
border-radius: 6px;
|
||||||
|
padding: 0 8px;
|
||||||
|
background: #fff;
|
||||||
|
font-size: 11px;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #344054;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.admin-pagination__range {
|
||||||
|
color: #98a2b3;
|
||||||
|
font-weight: 600;
|
||||||
|
margin-left: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.admin-pagination__ellipsis {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
min-width: 20px;
|
||||||
|
color: #98a2b3;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pages button:disabled {
|
||||||
|
opacity: 0.45;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -17,6 +17,7 @@ import {
|
|||||||
} from "/@/views/inquiry/staffRole/staffRole.api";
|
} from "/@/views/inquiry/staffRole/staffRole.api";
|
||||||
import ProtoIcon from "/@/views/inquiry/components/ProtoIcon.vue";
|
import ProtoIcon from "/@/views/inquiry/components/ProtoIcon.vue";
|
||||||
import AdminModal from "/@/views/inquiry/components/AdminModal.vue";
|
import AdminModal from "/@/views/inquiry/components/AdminModal.vue";
|
||||||
|
import AdminPagination from "/@/views/inquiry/components/AdminPagination.vue";
|
||||||
|
|
||||||
type StaffRole = "sales" | "air" | "sea" | "land";
|
type StaffRole = "sales" | "air" | "sea" | "land";
|
||||||
|
|
||||||
@@ -49,6 +50,9 @@ const roleLabelMap: Record<StaffRole, string> = {
|
|||||||
|
|
||||||
const rows = ref<StaffRow[]>([]);
|
const rows = ref<StaffRow[]>([]);
|
||||||
const loading = ref(false);
|
const loading = ref(false);
|
||||||
|
const total = ref(0);
|
||||||
|
const pageNo = ref(1);
|
||||||
|
const pageSize = ref(10);
|
||||||
const counts = ref({ all: 0, sales: 0, air: 0, sea: 0, land: 0 });
|
const counts = ref({ all: 0, sales: 0, air: 0, sea: 0, land: 0 });
|
||||||
const tab = ref<"all" | StaffRole>("all");
|
const tab = ref<"all" | StaffRole>("all");
|
||||||
const filters = reactive({
|
const filters = reactive({
|
||||||
@@ -134,11 +138,19 @@ async function loadList() {
|
|||||||
roleCode,
|
roleCode,
|
||||||
routes: filters.route || undefined,
|
routes: filters.route || undefined,
|
||||||
status: filters.status || undefined,
|
status: filters.status || undefined,
|
||||||
pageNo: 1,
|
pageNo: pageNo.value,
|
||||||
pageSize: 200,
|
pageSize: pageSize.value,
|
||||||
});
|
});
|
||||||
const records = res?.records || res?.result?.records || [];
|
const page = res?.records ? res : res?.result || {};
|
||||||
|
const records = page.records || [];
|
||||||
|
total.value = Number(page.total || records.length || 0);
|
||||||
rows.value = (records as any[]).map(mapRow);
|
rows.value = (records as any[]).map(mapRow);
|
||||||
|
const maxPage = Math.max(1, Math.ceil(total.value / pageSize.value) || 1);
|
||||||
|
if (pageNo.value > maxPage) {
|
||||||
|
pageNo.value = maxPage;
|
||||||
|
await loadList();
|
||||||
|
return;
|
||||||
|
}
|
||||||
await loadCounts();
|
await loadCounts();
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
message.error(e?.message || "加载员工失败");
|
message.error(e?.message || "加载员工失败");
|
||||||
@@ -151,11 +163,16 @@ let searchTimer: ReturnType<typeof setTimeout> | null = null;
|
|||||||
watch(
|
watch(
|
||||||
() => [tab.value, filters.keyword, filters.role, filters.route, filters.status],
|
() => [tab.value, filters.keyword, filters.role, filters.route, filters.status],
|
||||||
() => {
|
() => {
|
||||||
|
pageNo.value = 1;
|
||||||
if (searchTimer) clearTimeout(searchTimer);
|
if (searchTimer) clearTimeout(searchTimer);
|
||||||
searchTimer = setTimeout(() => loadList(), 200);
|
searchTimer = setTimeout(() => loadList(), 200);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
function onPageChange() {
|
||||||
|
loadList();
|
||||||
|
}
|
||||||
|
|
||||||
onMounted(() => loadList());
|
onMounted(() => loadList());
|
||||||
|
|
||||||
/** 企微根部门公司全称,下拉不展示 */
|
/** 企微根部门公司全称,下拉不展示 */
|
||||||
@@ -501,12 +518,14 @@ function routeTags(routes: string) {
|
|||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
<div class="pagination">
|
<AdminPagination
|
||||||
<span>共 <b style="color: #0f172a">{{ filtered.length }}</b> 名员工</span>
|
v-model:page-no="pageNo"
|
||||||
<div class="pages">
|
v-model:page-size="pageSize"
|
||||||
<button class="is-active" type="button">1</button>
|
:total="total"
|
||||||
</div>
|
unit="名员工"
|
||||||
</div>
|
:disabled="loading"
|
||||||
|
@change="onPageChange"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<AdminModal
|
<AdminModal
|
||||||
|
|||||||
@@ -791,6 +791,7 @@ body {
|
|||||||
background: #ffffff !important;
|
background: #ffffff !important;
|
||||||
font-size: 11px !important;
|
font-size: 11px !important;
|
||||||
color: #475467 !important;
|
color: #475467 !important;
|
||||||
|
flex-wrap: wrap !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.pagination button {
|
.pagination button {
|
||||||
@@ -799,6 +800,17 @@ body {
|
|||||||
border-radius: 6px !important;
|
border-radius: 6px !important;
|
||||||
font-size: 10.5px !important;
|
font-size: 10.5px !important;
|
||||||
font-weight: 700 !important;
|
font-weight: 700 !important;
|
||||||
|
border: 1px solid #e4e7ec !important;
|
||||||
|
background: #ffffff !important;
|
||||||
|
color: #344054 !important;
|
||||||
|
cursor: pointer !important;
|
||||||
|
padding: 0 8px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pagination .pages button.is-active {
|
||||||
|
border-color: #155eef !important;
|
||||||
|
background: #eff4ff !important;
|
||||||
|
color: #155eef !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.pagination .pages {
|
.pagination .pages {
|
||||||
@@ -807,6 +819,11 @@ body {
|
|||||||
gap: 4px;
|
gap: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.pagination b {
|
||||||
|
color: #0f172a;
|
||||||
|
font-weight: 800;
|
||||||
|
}
|
||||||
|
|
||||||
/* ---- section / 权限树 ---- */
|
/* ---- section / 权限树 ---- */
|
||||||
.section-card {
|
.section-card {
|
||||||
padding: 18px 20px !important;
|
padding: 18px 20px !important;
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import {
|
|||||||
} from "/@/views/inquiry/workOrder/workOrder.api";
|
} from "/@/views/inquiry/workOrder/workOrder.api";
|
||||||
import ProtoIcon from "/@/views/inquiry/components/ProtoIcon.vue";
|
import ProtoIcon from "/@/views/inquiry/components/ProtoIcon.vue";
|
||||||
import AdminModal from "/@/views/inquiry/components/AdminModal.vue";
|
import AdminModal from "/@/views/inquiry/components/AdminModal.vue";
|
||||||
|
import AdminPagination from "/@/views/inquiry/components/AdminPagination.vue";
|
||||||
|
|
||||||
type TicketQuote = { source: string; price: string; highlight?: boolean };
|
type TicketQuote = { source: string; price: string; highlight?: boolean };
|
||||||
type TicketAttach = { name: string; uploadedAt: string; uploader: string };
|
type TicketAttach = { name: string; uploadedAt: string; uploader: string };
|
||||||
@@ -52,6 +53,8 @@ type TicketRow = {
|
|||||||
const rows = ref<TicketRow[]>([]);
|
const rows = ref<TicketRow[]>([]);
|
||||||
const loading = ref(false);
|
const loading = ref(false);
|
||||||
const total = ref(0);
|
const total = ref(0);
|
||||||
|
const pageNo = ref(1);
|
||||||
|
const pageSize = ref(10);
|
||||||
const filters = reactive({
|
const filters = reactive({
|
||||||
keyword: "",
|
keyword: "",
|
||||||
status: "",
|
status: "",
|
||||||
@@ -291,13 +294,19 @@ async function loadList() {
|
|||||||
bizType: filters.bizType || undefined,
|
bizType: filters.bizType || undefined,
|
||||||
dateFrom: filters.dateFrom || undefined,
|
dateFrom: filters.dateFrom || undefined,
|
||||||
dateTo: filters.dateTo || undefined,
|
dateTo: filters.dateTo || undefined,
|
||||||
pageNo: 1,
|
pageNo: pageNo.value,
|
||||||
pageSize: 200,
|
pageSize: pageSize.value,
|
||||||
});
|
});
|
||||||
const page = res?.records ? res : res?.result || {};
|
const page = res?.records ? res : res?.result || {};
|
||||||
const records = page.records || [];
|
const records = page.records || [];
|
||||||
total.value = Number(page.total || records.length || 0);
|
total.value = Number(page.total || records.length || 0);
|
||||||
rows.value = (records as any[]).map((t) => mapTicket(t));
|
rows.value = (records as any[]).map((t) => mapTicket(t));
|
||||||
|
// 删减后当前页可能越界,回退到最后一页
|
||||||
|
const maxPage = Math.max(1, Math.ceil(total.value / pageSize.value) || 1);
|
||||||
|
if (pageNo.value > maxPage) {
|
||||||
|
pageNo.value = maxPage;
|
||||||
|
await loadList();
|
||||||
|
}
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
message.error(e?.message || "加载工单失败");
|
message.error(e?.message || "加载工单失败");
|
||||||
} finally {
|
} finally {
|
||||||
@@ -306,15 +315,21 @@ async function loadList() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let timer: ReturnType<typeof setTimeout> | null = null;
|
let timer: ReturnType<typeof setTimeout> | null = null;
|
||||||
|
/** 筛选变更:回到第 1 页再拉列表 */
|
||||||
watch(
|
watch(
|
||||||
() => ({ ...filters }),
|
() => ({ ...filters }),
|
||||||
() => {
|
() => {
|
||||||
|
pageNo.value = 1;
|
||||||
if (timer) clearTimeout(timer);
|
if (timer) clearTimeout(timer);
|
||||||
timer = setTimeout(() => loadList(), 250);
|
timer = setTimeout(() => loadList(), 250);
|
||||||
},
|
},
|
||||||
{ deep: true },
|
{ deep: true },
|
||||||
);
|
);
|
||||||
|
|
||||||
|
function onPageChange() {
|
||||||
|
loadList();
|
||||||
|
}
|
||||||
|
|
||||||
onMounted(() => loadList());
|
onMounted(() => loadList());
|
||||||
|
|
||||||
async function openDetail(row: TicketRow) {
|
async function openDetail(row: TicketRow) {
|
||||||
@@ -580,9 +595,14 @@ function refresh() {
|
|||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
<div class="pagination">
|
<AdminPagination
|
||||||
<span>共 <b style="color: #0f172a">{{ total }}</b> 条工单</span>
|
v-model:page-no="pageNo"
|
||||||
</div>
|
v-model:page-size="pageSize"
|
||||||
|
:total="total"
|
||||||
|
unit="条工单"
|
||||||
|
:disabled="loading"
|
||||||
|
@change="onPageChange"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user