refactor(composable): support server-paginated lists in usePagedList

Add an optional request handler so usePagedList can drive both
client-side filtering and server-paginated tables. Migrate the settings
list views (api, group, label, role, user, command, event) to consume
the composable, removing duplicated pagination/sort/search boilerplate.
This commit is contained in:
pnoker
2026-05-27 21:43:16 +08:00
parent d55e80fbd6
commit 93f0727e8e
11 changed files with 214 additions and 462 deletions
+43 -1
View File
@@ -16,7 +16,7 @@
import { reactive } from 'vue';
import type { Order } from '@/config/types';
import type { Order, PageQuery, PageResult } from '@/config/types';
export interface PagedListPage {
total: number;
@@ -37,6 +37,7 @@ export interface PagedListState<T, Q extends Record<string, any> = Record<string
export interface UsePagedListOptions<T, Q extends Record<string, any> = Record<string, any>> {
pageSize?: number;
sortColumn?: string;
request?: (query: PageQuery & Partial<Q>) => Promise<R<PageResult<T>>>;
filter?: (rows: T[], query: Partial<Q>) => T[];
sortValue?: (row: T) => string | number | null | undefined;
}
@@ -70,15 +71,42 @@ export const usePagedList = <T, Q extends Record<string, any> = Record<string, a
applyFilters();
};
const load = async () => {
if (!options.request) {
applyFilters();
return;
}
state.loading = true;
try {
const response = await options.request({ page: state.page, ...state.query } as PageQuery & Partial<Q>);
const data = response.data || ({ records: [], total: 0 } as PageResult<T>);
state.listData = data.records || [];
state.page.total = data.total || 0;
} catch {
// handled globally
} finally {
state.loading = false;
}
};
const search = (params?: Partial<Q>) => {
state.query = params || {};
state.page.current = 1;
if (options.request) {
void load();
return;
}
applyFilters();
};
const reset = () => {
state.query = {};
state.page.current = 1;
if (options.request) {
void load();
return;
}
applyFilters();
};
@@ -86,6 +114,11 @@ export const usePagedList = <T, Q extends Record<string, any> = Record<string, a
state.order = !state.order;
state.page.orders = [{ column: options.sortColumn ?? 'create_time', asc: state.order }];
if (options.request) {
void load();
return;
}
if (options.sortValue) {
const asc = state.order;
state.allData = [...state.allData].sort((a, b) => {
@@ -102,11 +135,19 @@ export const usePagedList = <T, Q extends Record<string, any> = Record<string, a
const sizeChange = (size: number) => {
state.page.size = size;
state.page.current = 1;
if (options.request) {
void load();
return;
}
applyFilters();
};
const currentChange = (current: number) => {
state.page.current = current;
if (options.request) {
void load();
return;
}
applyFilters();
};
@@ -123,6 +164,7 @@ export const usePagedList = <T, Q extends Record<string, any> = Record<string, a
state,
setAllData,
applyFilters,
load,
search,
reset,
sort,
+13 -57
View File
@@ -14,14 +14,15 @@
* limitations under the License.
*/
import { defineComponent, reactive } from 'vue';
import { defineComponent } from 'vue';
import { useI18n } from 'vue-i18n';
import { useRouter } from 'vue-router';
import { listApi } from '@/api/api';
import { usePagedList } from '@/composables/usePagedList';
import { timestampColumn } from '@/utils/dateUtil';
import type { ApiRecord, Order } from '@/config/types';
import type { ApiRecord } from '@/config/types';
import BlankCard from '@/components/card/blank/BlankCard.vue';
import EnableTag from '@/components/tag/EnableTag.vue';
@@ -41,71 +42,26 @@ export default defineComponent({
const { t } = useI18n();
const router = useRouter();
const reactiveData = reactive({
loading: false,
listData: [] as ApiRecord[],
query: {} as Record<string, unknown>,
order: false,
page: {
total: 0,
size: 12,
current: 1,
orders: [] as Order[],
},
const {
state: reactiveData,
load,
search,
reset,
sort,
sizeChange,
currentChange,
} = usePagedList<ApiRecord, Record<string, unknown>>({
request: (query) => listApi(query),
});
const load = () => {
reactiveData.loading = true;
listApi({ page: reactiveData.page, ...reactiveData.query })
.then((res) => {
const data = res.data || {};
reactiveData.listData = data.records || [];
reactiveData.page.total = data.total || 0;
})
.catch(() => {
// handled globally
})
.finally(() => {
reactiveData.loading = false;
});
};
const search = (params: Record<string, unknown>) => {
reactiveData.query = params || {};
reactiveData.page.current = 1;
load();
};
const reset = () => {
reactiveData.query = {};
reactiveData.page.current = 1;
load();
};
const refresh = () => load();
const sort = () => {
reactiveData.order = !reactiveData.order;
reactiveData.page.orders = [{ column: 'create_time', asc: reactiveData.order }];
load();
};
const openDetail = (row: ApiRecord) => {
router.push({ name: 'settingsApiDetail', query: { id: String(row.id) } }).catch(() => {
// handled globally
});
};
const sizeChange = (size: number) => {
reactiveData.page.size = size;
load();
};
const currentChange = (current: number) => {
reactiveData.page.current = current;
load();
};
load();
return {
+13 -34
View File
@@ -155,18 +155,23 @@
<script lang="ts" setup>
import { reactive, ref } from 'vue';
import { getCommandHistoryById, listCommandHistory } from '@/api/command';
import { usePagedList } from '@/composables/usePagedList';
import { timestampColumn, timestampLabel } from '@/utils/dateUtil';
import { prettyJson } from '@/utils/jsonUtil';
import type { CommandHistory, Order } from '@/config/types';
import type { CommandHistory } from '@/config/types';
import ToolCard from '@/components/card/tool/ToolCard.vue';
import BlankCard from '@/components/card/blank/BlankCard.vue';
import { cleanSearchParams, resetSearchForm } from '@/utils/searchParamUtil';
const reactiveData = reactive({
loading: false,
listData: [] as CommandHistory[],
query: {} as Record<string, unknown>,
page: { total: 0, size: 12, current: 1, orders: [] as Order[] },
const {
state: reactiveData,
load,
search,
reset,
sizeChange,
currentChange,
} = usePagedList<CommandHistory, Record<string, unknown>>({
request: (query) => listCommandHistory(query),
});
const formData = reactive<Record<string, string>>({});
@@ -175,43 +180,17 @@
const formatJson = (value: unknown) => prettyJson(value);
const load = () => {
reactiveData.loading = true;
listCommandHistory({ page: reactiveData.page, ...reactiveData.query })
.then((res) => {
const data = res.data || {};
reactiveData.listData = data.records || [];
reactiveData.page.total = data.total || 0;
})
.finally(() => {
reactiveData.loading = false;
});
};
const onSearch = (data: Record<string, string>) => {
reactiveData.query = cleanSearchParams(data);
reactiveData.page.current = 1;
load();
search(cleanSearchParams(data));
};
const onReset = () => {
resetSearchForm(formData, {});
reactiveData.query = {};
reactiveData.page.current = 1;
load();
reset();
};
const refresh = () => load();
const sizeChange = (size: number) => {
reactiveData.page.size = size;
load();
};
const currentChange = (current: number) => {
reactiveData.page.current = current;
load();
};
const openDetail = (row: CommandHistory) => {
getCommandHistoryById(row.recordId)
.then((res) => {
+24 -53
View File
@@ -99,7 +99,7 @@
</template>
<script lang="ts" setup>
import { computed, reactive, ref, watch } from 'vue';
import { computed, ref, watch } from 'vue';
import { useI18n } from 'vue-i18n';
import {
addCommand,
@@ -110,11 +110,12 @@
updateCommand,
updateCommandParam,
} from '@/api/command';
import { usePagedList } from '@/composables/usePagedList';
import { timestampLabel } from '@/utils/dateUtil';
import { failMessage, successMessage } from '@/utils/notificationUtil';
import { commandTimeoutLabel } from '@/utils/thingModelFormatUtil';
import { isNull } from '@/utils/validationUtil';
import type { CommandForm, CommandParamRecord, CommandRecord, Order } from '@/config/types';
import type { CommandForm, CommandParamRecord, CommandRecord } from '@/config/types';
import BlankCard from '@/components/card/blank/BlankCard.vue';
import SkeletonCard from '@/components/card/skeleton/SkeletonCard.vue';
import EnableTag from '@/components/tag/EnableTag.vue';
@@ -142,66 +143,46 @@
const canManage = computed(() => props.embedded === '' || props.embedded === 'edit');
const hasData = computed(() => !reactiveData.loading && reactiveData.listData.length < 1);
const reactiveData = reactive({
loading: false,
listData: [] as CommandRecord[],
detailVisible: false,
detailRecord: null as CommandRecord | null,
query: {} as Record<string, unknown>,
order: false,
page: {
total: 0,
size: 12,
current: 1,
orders: [] as Order[],
},
});
const withFixedQuery = (params: Record<string, unknown> = {}) => {
const q = { ...params };
if (!isNull(props.profileId)) q.profileId = props.profileId;
return q;
};
const {
state,
load,
search: searchList,
reset: resetList,
sort,
sizeChange,
currentChange,
} = usePagedList<CommandRecord, Record<string, unknown>>({
request: (query) => listCommand(withFixedQuery(query)),
});
const reactiveData = state as typeof state & {
detailVisible: boolean;
detailRecord: CommandRecord | null;
};
reactiveData.detailVisible = false;
reactiveData.detailRecord = null;
const withFixedProfile = (form: CommandForm) => {
const profileId = !isNull(props.profileId) ? props.profileId : form.profileId;
return isNull(profileId) ? { ...form } : { ...form, profileId };
};
const load = () => {
reactiveData.loading = true;
const query = withFixedQuery(reactiveData.query);
listCommand({ page: reactiveData.page, ...query })
.then((res) => {
const data = res.data || {};
reactiveData.listData = data.records || [];
reactiveData.page.total = data.total || 0;
})
.finally(() => {
reactiveData.loading = false;
});
};
const search = (params: Record<string, unknown>) => {
reactiveData.query = withFixedQuery(params || {});
reactiveData.page.current = 1;
load();
searchList(params || {});
};
const reset = () => {
reactiveData.query = withFixedQuery({});
reactiveData.page.current = 1;
load();
resetList();
};
const refresh = () => load();
const sort = () => {
reactiveData.order = !reactiveData.order;
reactiveData.page.orders = [{ column: 'create_time', asc: reactiveData.order }];
load();
};
const openAdd = () => editRef.value?.show(props.profileId);
const openDetail = (row: CommandRecord) => {
reactiveData.detailRecord = row;
@@ -299,16 +280,6 @@
});
};
const sizeChange = (size: number) => {
reactiveData.page.size = size;
load();
};
const currentChange = (current: number) => {
reactiveData.page.current = current;
load();
};
const preHandle = () => {
emit('pre-handle');
};
+13 -34
View File
@@ -135,19 +135,24 @@
<script lang="ts" setup>
import { reactive, ref } from 'vue';
import { getEventHistoryById, listEventHistory } from '@/api/event';
import { usePagedList } from '@/composables/usePagedList';
import { timestampColumn, timestampLabel } from '@/utils/dateUtil';
import { prettyJson } from '@/utils/jsonUtil';
import { eventLevelLabel, eventLevelTag, eventTypeLabel } from '@/utils/thingModelFormatUtil';
import type { EventHistory, Order } from '@/config/types';
import type { EventHistory } from '@/config/types';
import ToolCard from '@/components/card/tool/ToolCard.vue';
import BlankCard from '@/components/card/blank/BlankCard.vue';
import { cleanSearchParams, resetSearchForm } from '@/utils/searchParamUtil';
const reactiveData = reactive({
loading: false,
listData: [] as EventHistory[],
query: {} as Record<string, unknown>,
page: { total: 0, size: 12, current: 1, orders: [] as Order[] },
const {
state: reactiveData,
load,
search,
reset,
sizeChange,
currentChange,
} = usePagedList<EventHistory, Record<string, unknown>>({
request: (query) => listEventHistory(query),
});
const formData = reactive<Record<string, string>>({});
@@ -156,43 +161,17 @@
const formatJson = (value: unknown) => prettyJson(value);
const load = () => {
reactiveData.loading = true;
listEventHistory({ page: reactiveData.page, ...reactiveData.query })
.then((res) => {
const data = res.data || {};
reactiveData.listData = data.records || [];
reactiveData.page.total = data.total || 0;
})
.finally(() => {
reactiveData.loading = false;
});
};
const onSearch = (data: Record<string, string>) => {
reactiveData.query = cleanSearchParams(data);
reactiveData.page.current = 1;
load();
search(cleanSearchParams(data));
};
const onReset = () => {
resetSearchForm(formData, {});
reactiveData.query = {};
reactiveData.page.current = 1;
load();
reset();
};
const refresh = () => load();
const sizeChange = (size: number) => {
reactiveData.page.size = size;
load();
};
const currentChange = (current: number) => {
reactiveData.page.current = current;
load();
};
const openDetail = (row: EventHistory) => {
getEventHistoryById(row.recordId)
.then((res) => {
@@ -96,7 +96,7 @@
</template>
<script lang="ts" setup>
import { computed, reactive, ref, watch } from 'vue';
import { computed, ref, watch } from 'vue';
import { useI18n } from 'vue-i18n';
import {
addEvent,
@@ -107,11 +107,12 @@
updateEvent,
updateEventParam,
} from '@/api/event';
import { usePagedList } from '@/composables/usePagedList';
import { timestampLabel } from '@/utils/dateUtil';
import { failMessage, successMessage } from '@/utils/notificationUtil';
import { eventLevelLabel, eventTypeLabel } from '@/utils/thingModelFormatUtil';
import { isNull } from '@/utils/validationUtil';
import type { EventForm, EventParamRecord, EventRecord, Order } from '@/config/types';
import type { EventForm, EventParamRecord, EventRecord } from '@/config/types';
import BlankCard from '@/components/card/blank/BlankCard.vue';
import SkeletonCard from '@/components/card/skeleton/SkeletonCard.vue';
import EnableTag from '@/components/tag/EnableTag.vue';
@@ -139,66 +140,46 @@
const canManage = computed(() => props.embedded === '' || props.embedded === 'edit');
const hasData = computed(() => !reactiveData.loading && reactiveData.listData.length < 1);
const reactiveData = reactive({
loading: false,
listData: [] as EventRecord[],
detailVisible: false,
detailRecord: null as EventRecord | null,
query: {} as Record<string, unknown>,
order: false,
page: {
total: 0,
size: 12,
current: 1,
orders: [] as Order[],
},
});
const withFixedQuery = (params: Record<string, unknown> = {}) => {
const q = { ...params };
if (!isNull(props.profileId)) q.profileId = props.profileId;
return q;
};
const {
state,
load,
search: searchList,
reset: resetList,
sort,
sizeChange,
currentChange,
} = usePagedList<EventRecord, Record<string, unknown>>({
request: (query) => listEvent(withFixedQuery(query)),
});
const reactiveData = state as typeof state & {
detailVisible: boolean;
detailRecord: EventRecord | null;
};
reactiveData.detailVisible = false;
reactiveData.detailRecord = null;
const withFixedProfile = (form: EventForm) => {
const profileId = !isNull(props.profileId) ? props.profileId : form.profileId;
return isNull(profileId) ? { ...form } : { ...form, profileId };
};
const load = () => {
reactiveData.loading = true;
const query = withFixedQuery(reactiveData.query);
listEvent({ page: reactiveData.page, ...query })
.then((res) => {
const data = res.data || {};
reactiveData.listData = data.records || [];
reactiveData.page.total = data.total || 0;
})
.finally(() => {
reactiveData.loading = false;
});
};
const search = (params: Record<string, unknown>) => {
reactiveData.query = withFixedQuery(params || {});
reactiveData.page.current = 1;
load();
searchList(params || {});
};
const reset = () => {
reactiveData.query = withFixedQuery({});
reactiveData.page.current = 1;
load();
resetList();
};
const refresh = () => load();
const sort = () => {
reactiveData.order = !reactiveData.order;
reactiveData.page.orders = [{ column: 'create_time', asc: reactiveData.order }];
load();
};
const openAdd = () => editRef.value?.show(props.profileId);
const openDetail = (row: EventRecord) => {
reactiveData.detailRecord = row;
@@ -292,16 +273,6 @@
});
};
const sizeChange = (size: number) => {
reactiveData.page.size = size;
load();
};
const currentChange = (current: number) => {
reactiveData.page.current = current;
load();
};
const preHandle = () => {
emit('pre-handle');
};
+9 -57
View File
@@ -19,10 +19,10 @@ import { useI18n } from 'vue-i18n';
import { useRouter } from 'vue-router';
import { addGroup, deleteGroup, listGroup, updateGroup } from '@/api/group';
import { usePagedList } from '@/composables/usePagedList';
import { timestampColumn } from '@/utils/dateUtil';
import { successMessage } from '@/utils/notificationUtil';
import type { Order } from '@/config/types';
import type { GroupForm, GroupRecord } from '@/config/types/manager';
import BlankCard from '@/components/card/blank/BlankCard.vue';
@@ -43,20 +43,16 @@ export default defineComponent({
const router = useRouter();
const editRef = ref<InstanceType<typeof groupEditForm>>();
const reactiveData = reactive({
loading: false,
listData: [] as GroupRecord[],
groupOptions: [] as GroupRecord[],
query: {} as Record<string, unknown>,
order: false,
page: {
total: 0,
size: 12,
current: 1,
orders: [] as Order[],
},
const { state, load, search, reset, sort, sizeChange, currentChange } = usePagedList<
GroupRecord,
Record<string, unknown>
>({
request: (query) => listGroup(query),
});
const reactiveData = state as typeof state & { groupOptions: GroupRecord[] };
reactiveData.groupOptions = [];
const parentNameMap = reactive<Record<string, string>>({});
const loadOptions = () => {
@@ -74,50 +70,16 @@ export default defineComponent({
});
};
const load = () => {
reactiveData.loading = true;
listGroup({ page: reactiveData.page, ...reactiveData.query })
.then((res) => {
const data = res.data || {};
reactiveData.listData = data.records || [];
reactiveData.page.total = data.total || 0;
})
.catch(() => {
// handled globally
})
.finally(() => {
reactiveData.loading = false;
});
};
const parentName = (id: string | number | null | undefined) => {
if (!id || String(id) === '0') return t('settings.group.rootGroup');
return parentNameMap[String(id)] || String(id);
};
const search = (params: Record<string, unknown>) => {
reactiveData.query = params || {};
reactiveData.page.current = 1;
load();
};
const reset = () => {
reactiveData.query = {};
reactiveData.page.current = 1;
load();
};
const refresh = () => {
load();
loadOptions();
};
const sort = () => {
reactiveData.order = !reactiveData.order;
reactiveData.page.orders = [{ column: 'create_time', asc: reactiveData.order }];
load();
};
const openAdd = () => editRef.value?.show();
const openDetail = (row: GroupRecord) => {
router.push({ name: 'settingsGroupDetail', query: { id: String(row.id) } }).catch(() => {
@@ -161,16 +123,6 @@ export default defineComponent({
});
};
const sizeChange = (size: number) => {
reactiveData.page.size = size;
load();
};
const currentChange = (current: number) => {
reactiveData.page.current = current;
load();
};
load();
loadOptions();
+12 -57
View File
@@ -14,15 +14,15 @@
* limitations under the License.
*/
import { defineComponent, reactive, ref } from 'vue';
import { defineComponent, ref } from 'vue';
import { useI18n } from 'vue-i18n';
import { useRouter } from 'vue-router';
import { addLabel, deleteLabel, listLabel, updateLabel } from '@/api/label';
import { usePagedList } from '@/composables/usePagedList';
import { timestampColumn } from '@/utils/dateUtil';
import { successMessage } from '@/utils/notificationUtil';
import type { Order } from '@/config/types';
import type { LabelForm, LabelRecord } from '@/config/types/manager';
import BlankCard from '@/components/card/blank/BlankCard.vue';
@@ -43,55 +43,20 @@ export default defineComponent({
const router = useRouter();
const editRef = ref<InstanceType<typeof labelEditForm>>();
const reactiveData = reactive({
loading: false,
listData: [] as LabelRecord[],
query: {} as Record<string, unknown>,
order: false,
page: {
total: 0,
size: 12,
current: 1,
orders: [] as Order[],
},
const {
state: reactiveData,
load,
search,
reset,
sort,
sizeChange,
currentChange,
} = usePagedList<LabelRecord, Record<string, unknown>>({
request: (query) => listLabel(query),
});
const load = () => {
reactiveData.loading = true;
listLabel({ page: reactiveData.page, ...reactiveData.query })
.then((res) => {
const data = res.data || {};
reactiveData.listData = data.records || [];
reactiveData.page.total = data.total || 0;
})
.catch(() => {
// handled globally
})
.finally(() => {
reactiveData.loading = false;
});
};
const search = (params: Record<string, unknown>) => {
reactiveData.query = params || {};
reactiveData.page.current = 1;
load();
};
const reset = () => {
reactiveData.query = {};
reactiveData.page.current = 1;
load();
};
const refresh = () => load();
const sort = () => {
reactiveData.order = !reactiveData.order;
reactiveData.page.orders = [{ column: 'create_time', asc: reactiveData.order }];
load();
};
const openAdd = () => editRef.value?.show();
const openDetail = (row: LabelRecord) => {
router.push({ name: 'settingsLabelDetail', query: { id: String(row.id) } }).catch(() => {
@@ -135,16 +100,6 @@ export default defineComponent({
});
};
const sizeChange = (size: number) => {
reactiveData.page.size = size;
load();
};
const currentChange = (current: number) => {
reactiveData.page.current = current;
load();
};
load();
return {
+10 -58
View File
@@ -14,16 +14,16 @@
* limitations under the License.
*/
import { defineComponent, reactive, ref } from 'vue';
import { defineComponent, ref } from 'vue';
import { useI18n } from 'vue-i18n';
import { useRouter } from 'vue-router';
import { addRole, deleteRole, listRole, listRoleTree, updateRole } from '@/api/role';
import { addRoleResourceBind, deleteRoleResourceBind } from '@/api/roleResourceBind';
import { usePagedList } from '@/composables/usePagedList';
import { timestampColumn } from '@/utils/dateUtil';
import { successMessage } from '@/utils/notificationUtil';
import type { Order } from '@/config/types';
import type { RoleForm, RoleRecord } from '@/config/types/auth';
import BlankCard from '@/components/card/blank/BlankCard.vue';
@@ -48,20 +48,16 @@ export default defineComponent({
const editRef = ref<InstanceType<typeof roleEditForm>>();
const assignRef = ref<InstanceType<typeof roleAssignResources>>();
const reactiveData = reactive({
loading: false,
listData: [] as RoleRecord[],
roleTreeData: [] as RoleRecord[],
query: {} as Record<string, unknown>,
order: false,
page: {
total: 0,
size: 12,
current: 1,
orders: [] as Order[],
},
const { state, load, search, reset, sort, sizeChange, currentChange } = usePagedList<
RoleRecord,
Record<string, unknown>
>({
request: (query) => listRole(query),
});
const reactiveData = state as typeof state & { roleTreeData: RoleRecord[] };
reactiveData.roleTreeData = [];
const loadTree = () => {
listRoleTree()
.then((res) => {
@@ -72,42 +68,8 @@ export default defineComponent({
});
};
const load = () => {
reactiveData.loading = true;
listRole({ page: reactiveData.page, ...reactiveData.query })
.then((res) => {
const data = res.data || {};
reactiveData.listData = data.records || [];
reactiveData.page.total = data.total || 0;
})
.catch(() => {
// handled globally
})
.finally(() => {
reactiveData.loading = false;
});
};
const search = (params: Record<string, unknown>) => {
reactiveData.query = params || {};
reactiveData.page.current = 1;
load();
};
const reset = () => {
reactiveData.query = {};
reactiveData.page.current = 1;
load();
};
const refresh = () => load();
const sort = () => {
reactiveData.order = !reactiveData.order;
reactiveData.page.orders = [{ column: 'create_time', asc: reactiveData.order }];
load();
};
const openAdd = () => editRef.value?.show();
const openEdit = (row: RoleRecord) => editRef.value?.showEdit(row);
const openAssignResources = (row: RoleRecord) => assignRef.value?.show(row);
@@ -168,16 +130,6 @@ export default defineComponent({
});
};
const sizeChange = (size: number) => {
reactiveData.page.size = size;
load();
};
const currentChange = (current: number) => {
reactiveData.page.current = current;
load();
};
load();
loadTree();
+12 -57
View File
@@ -14,16 +14,16 @@
* limitations under the License.
*/
import { defineComponent, reactive, ref } from 'vue';
import { defineComponent, ref } from 'vue';
import { useI18n } from 'vue-i18n';
import { useRouter } from 'vue-router';
import { addUser, deleteUser, listUser, updateUser } from '@/api/user';
import { addRoleUserBind, deleteRoleUserBind } from '@/api/roleUserBind';
import { usePagedList } from '@/composables/usePagedList';
import { timestampColumn } from '@/utils/dateUtil';
import { successMessage } from '@/utils/notificationUtil';
import type { Order } from '@/config/types';
import type { UserForm, UserRecord } from '@/config/types/auth';
import userTool from './tool/UserTool.vue';
@@ -48,55 +48,20 @@ export default defineComponent({
const editRef = ref<InstanceType<typeof userEditForm>>();
const assignRef = ref<InstanceType<typeof userAssignRoles>>();
const reactiveData = reactive({
loading: false,
listData: [] as UserRecord[],
query: {} as Record<string, unknown>,
order: false,
page: {
total: 0,
size: 12,
current: 1,
orders: [] as Order[],
},
const {
state: reactiveData,
load,
search,
reset,
sort,
sizeChange,
currentChange,
} = usePagedList<UserRecord, Record<string, unknown>>({
request: (query) => listUser(query),
});
const load = () => {
reactiveData.loading = true;
listUser({ page: reactiveData.page, ...reactiveData.query })
.then((res) => {
const data = res.data || {};
reactiveData.listData = data.records || [];
reactiveData.page.total = data.total || 0;
})
.catch(() => {
// handled globally
})
.finally(() => {
reactiveData.loading = false;
});
};
const search = (params: Record<string, unknown>) => {
reactiveData.query = params || {};
reactiveData.page.current = 1;
load();
};
const reset = () => {
reactiveData.query = {};
reactiveData.page.current = 1;
load();
};
const refresh = () => load();
const sort = () => {
reactiveData.order = !reactiveData.order;
reactiveData.page.orders = [{ column: 'create_time', asc: reactiveData.order }];
load();
};
const openAdd = () => editRef.value?.show();
const openEdit = (row: UserRecord) => editRef.value?.showEdit(row);
const openAssignRoles = (row: UserRecord) => assignRef.value?.show(row);
@@ -154,16 +119,6 @@ export default defineComponent({
});
};
const sizeChange = (size: number) => {
reactiveData.page.size = size;
load();
};
const currentChange = (current: number) => {
reactiveData.page.current = current;
load();
};
load();
return {
+41 -1
View File
@@ -14,7 +14,7 @@
* limitations under the License.
*/
import { describe, expect, it } from 'vitest';
import { describe, expect, it, vi } from 'vitest';
import { usePagedList } from '@/composables/usePagedList';
@@ -146,4 +146,44 @@ describe('usePagedList', () => {
).rejects.toThrow('boom');
expect(state.loading).toBe(false);
});
it('loads server-paginated rows when a request handler is provided', async () => {
const request = vi.fn(() =>
Promise.resolve({
ok: true,
code: 'ok',
message: 'ok',
data: { records: sampleRows.slice(0, 2), total: sampleRows.length },
})
);
const { state, load, search, sort, sizeChange, currentChange } = usePagedList<Row, { keyword?: string }>({
request,
});
await load();
expect(state.listData).toEqual(sampleRows.slice(0, 2));
expect(state.page.total).toBe(sampleRows.length);
expect(request).toHaveBeenLastCalledWith(expect.objectContaining({ page: state.page }));
search({ keyword: 'Row 02' });
await Promise.resolve();
expect(request).toHaveBeenLastCalledWith(expect.objectContaining({ keyword: 'Row 02' }));
sort();
await Promise.resolve();
expect(request).toHaveBeenLastCalledWith(
expect.objectContaining({ page: expect.objectContaining({ orders: [{ column: 'create_time', asc: true }] }) })
);
sizeChange(24);
await Promise.resolve();
expect(state.page.current).toBe(1);
expect(request).toHaveBeenLastCalledWith(expect.objectContaining({ page: expect.objectContaining({ size: 24 }) }));
currentChange(2);
await Promise.resolve();
expect(request).toHaveBeenLastCalledWith(
expect.objectContaining({ page: expect.objectContaining({ current: 2 }) })
);
});
});