209 lines
7.7 KiB
Vue
209 lines
7.7 KiB
Vue
<script setup lang="ts">
|
||
/**
|
||
* 用户查询(列表页):旧 WebVue views/security/UserEnquiry.vue 的移动端等价物
|
||
*
|
||
* 保留的东西:三条筛选项(用户名/中文名/创建日期区间)、每页 20 条、
|
||
* 按创建日期倒序、行点击进详情、多选批量"无效"(旧页面也是循环 Delete,不是 Deletes)。
|
||
* 丢掉的东西:多列网格、列配置、打印——手机上没有对应交互。
|
||
*
|
||
* 页面只组数据与路由,列表/筛选/分页的机制全在 admin-list + useAdminList 里。
|
||
*/
|
||
import type { AdminColumn, AdminQuery, AdminRow } from "@/admin/admin-types";
|
||
import { useLocale } from "uview-pro";
|
||
import { computed, nextTick, reactive, ref, watch } from "vue";
|
||
import { fillCriteria, makeFilterOption } from "@/admin/admin-filter";
|
||
import { useAdminList, useAuthGuard } from "@/composables";
|
||
import { SecurityUserEntity } from "@/data/auto/securityUserEntity";
|
||
import { SecurityUserSearchCriteria } from "@/data/auto/securityUserSearchCriteria";
|
||
import { SearchFilterOperatorPolicy } from "@/data/custom/searchFilterOperatorPolicy";
|
||
import { SearchFilterTypePolicy } from "@/data/custom/searchFilterTypePolicy";
|
||
import { usePagePermission } from "@/permission/permission-context";
|
||
import { pageUrl } from "@/router/routes";
|
||
import securityUserService from "@/service/security/securityUserService";
|
||
|
||
const { t, currentLocale } = useLocale();
|
||
const perm = usePagePermission("userEnquiry");
|
||
|
||
/** 列名 -> i18n key:抽屉标签、卡片行标签都靠它,切换语言时就地刷新而不重建条件 */
|
||
const LABEL_KEYS: Record<string, string> = {
|
||
[SecurityUserEntity.DB_USER_NAME]: "UserPage.userName",
|
||
[SecurityUserEntity.DB_USER_LOCAL_NAME]: "UserPage.userLocalName",
|
||
[SecurityUserEntity.DB_CREATED_DATETIME]: "UserPage.createdDatetime",
|
||
};
|
||
|
||
// reactive 是必须的:useAdminList 的 filters 是 computed,源数组非响应式就永远不会失效,
|
||
// 改了条件却仍是旧筛选结果是这一层最容易踩的坑
|
||
const bindOptions = reactive([
|
||
makeFilterOption(
|
||
SecurityUserEntity.DB_USER_NAME,
|
||
t(LABEL_KEYS[SecurityUserEntity.DB_USER_NAME]),
|
||
SearchFilterTypePolicy.TEXT,
|
||
SearchFilterOperatorPolicy.CONTAIN,
|
||
),
|
||
makeFilterOption(
|
||
SecurityUserEntity.DB_USER_LOCAL_NAME,
|
||
t(LABEL_KEYS[SecurityUserEntity.DB_USER_LOCAL_NAME]),
|
||
SearchFilterTypePolicy.TEXT,
|
||
SearchFilterOperatorPolicy.CONTAIN,
|
||
),
|
||
makeFilterOption(
|
||
SecurityUserEntity.DB_CREATED_DATETIME,
|
||
t(LABEL_KEYS[SecurityUserEntity.DB_CREATED_DATETIME]),
|
||
SearchFilterTypePolicy.DATE,
|
||
SearchFilterOperatorPolicy.DATE_BETWEEN,
|
||
),
|
||
]);
|
||
|
||
/** 取数:criteria 每页重建(与旧实现一致),只在这里碰具体 service */
|
||
async function fetchData(query: AdminQuery) {
|
||
const criteria = new SecurityUserSearchCriteria();
|
||
criteria.GridColumns = [];
|
||
fillCriteria(criteria, query.filters, query);
|
||
const data = await securityUserService.FindBySearchCriteria(criteria);
|
||
return { rows: data?.Datas ?? [], total: data?.Total ?? 0 };
|
||
}
|
||
|
||
const state = useAdminList({
|
||
bindOptions,
|
||
loader: fetchData,
|
||
orderby: SecurityUserEntity.DB_CREATED_DATETIME,
|
||
orderbytag: "desc",
|
||
});
|
||
|
||
// 解构出来的是 ref/computed,交给模板自动 .value;方法仍走 state.xxx
|
||
const { filterCount, filterVisible, draft, searchToken } = state;
|
||
|
||
const columns = computed<AdminColumn[]>(() => [
|
||
{
|
||
label: t("UserPage.userLocalName"),
|
||
prop: SecurityUserEntity.USER_LOCAL_NAME,
|
||
},
|
||
{ label: t("UserPage.userName"), prop: SecurityUserEntity.USER_NAME },
|
||
{ label: t("UserPage.userMobile"), prop: SecurityUserEntity.USER_MOBILE },
|
||
{
|
||
label: t("UserPage.createdDatetime"),
|
||
prop: SecurityUserEntity.CREATED_DATETIME,
|
||
date: "datetime",
|
||
},
|
||
]);
|
||
|
||
const keyword = ref("");
|
||
const listRef = ref<any>(null);
|
||
|
||
/** 顶部搜索框:把关键字并进"用户名包含"这一条条件,复用同一套筛选链路 */
|
||
function onSearch(): void {
|
||
const option = bindOptions.find(
|
||
item => item.BindValue === SecurityUserEntity.DB_USER_NAME,
|
||
);
|
||
if (option) {
|
||
option.Value = keyword.value;
|
||
}
|
||
state.reload();
|
||
}
|
||
|
||
function onRowTap(row: AdminRow): void {
|
||
// 与旧页面一致:id 用 UserName,mode 决定详情页是只读还是编辑
|
||
// 不做 encodeURIComponent:见 composables/useAdminPick 的说明(H5 二次编码坑)
|
||
uni.navigateTo({
|
||
url: `${pageUrl("userOperation")}?id=${row.UserName}&mode=view`,
|
||
});
|
||
}
|
||
|
||
function onCreate(): void {
|
||
uni.navigateTo({ url: `${pageUrl("userOperation")}?mode=create` });
|
||
}
|
||
|
||
/** 批量作废:旧 UserEnquiry 也是逐条 Delete,Delete 语义最贴近后端 */
|
||
async function onDelete(rows: AdminRow[]): Promise<void> {
|
||
if (!rows.length) {
|
||
uni.showToast({ title: t("AdminPage.pickNoneTip"), icon: "none" });
|
||
return;
|
||
}
|
||
uni.showModal({
|
||
title: t("AdminPage.confirmDeleteTitle"),
|
||
success: async (res: any) => {
|
||
if (!res.confirm) {
|
||
return;
|
||
}
|
||
for (const row of rows) {
|
||
await securityUserService.Delete(String(row.UserName));
|
||
}
|
||
listRef.value?.clearSelection();
|
||
state.reload();
|
||
uni.showToast({ title: t("AdminPage.deletedTip"), icon: "none" });
|
||
},
|
||
});
|
||
}
|
||
|
||
// 语言切换后条件标签也要跟着换(值不动,只改显示名)
|
||
watch(currentLocale, () => {
|
||
for (const option of bindOptions) {
|
||
const key = LABEL_KEYS[option.BindValue];
|
||
if (key) {
|
||
option.DisplayName = t(key);
|
||
}
|
||
}
|
||
});
|
||
|
||
// 取数排在登录守卫之后:admin-list 的 auto 默认 false,首屏由这里手动触发一次
|
||
let booted = false;
|
||
useAuthGuard(() => {
|
||
if (booted) {
|
||
return;
|
||
}
|
||
booted = true;
|
||
nextTick(() => listRef.value?.refresh());
|
||
});
|
||
</script>
|
||
|
||
<template>
|
||
<view class="page">
|
||
<admin-list ref="listRef" v-model:keyword="keyword" :nav-title="t('UserPage.title')" :show-nav-back="false"
|
||
:columns="columns" :loader="state.query" :row-key="SecurityUserEntity.USER_NAME" title-key="UserLocalName"
|
||
:selectable="Boolean(perm.canDelete.value)" :filter-count="filterCount" :filter-visible="filterVisible"
|
||
:filter-items="draft" :search-token="searchToken" @search="onSearch" @open-filter="state.openFilter()"
|
||
@close-filter="state.closeFilter()" @apply-filter="state.applyFilter($event)"
|
||
@clear-filter="state.clearFiltersAndSearch()" @row-tap="onRowTap">
|
||
<template #toolbar>
|
||
<view v-if="perm.canCreate.value" class="create-entry" @click="onCreate">
|
||
<u-icon name="plus" size="24" color="#ffffff" />
|
||
<text class="create-text">
|
||
{{ t("AdminPage.createBut") }}
|
||
</text>
|
||
</view>
|
||
</template>
|
||
|
||
<template #actions="{ rows }">
|
||
<u-button v-if="perm.canDelete.value" type="error" shape="circle" size="mini"
|
||
:text="t('AdminPage.deleteBut')" @click="onDelete(rows)" />
|
||
</template>
|
||
</admin-list>
|
||
</view>
|
||
</template>
|
||
|
||
<style lang="scss" scoped>
|
||
.page {
|
||
width: 100%;
|
||
height: 100vh;
|
||
background-color: $u-bg-color;
|
||
}
|
||
|
||
/* 新增:翠绿实心胶囊,靠右落在次级工具行里 */
|
||
.create-entry {
|
||
display: flex;
|
||
flex-direction: row;
|
||
align-items: center;
|
||
padding: 12rpx 28rpx;
|
||
color: #ffffff;
|
||
background-color: $u-type-primary;
|
||
border-radius: 999rpx;
|
||
box-shadow: 0 6rpx 16rpx rgba(5, 150, 105, 0.24);
|
||
}
|
||
|
||
.create-text {
|
||
margin-left: 8rpx;
|
||
font-size: 26rpx;
|
||
font-weight: 500;
|
||
}
|
||
</style>
|