Files
Docker/uniapp/youjiala/src/pages/security/user-enquiry.vue
T
2026-10-15 19:26:18 +08:00

226 lines
8.5 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<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 { onLoad } from "@dcloudio/uni-app";
import { useLocale } from "uview-pro";
import { computed, nextTick, reactive, ref, watch } from "vue";
import { fillCriteria, makeFilterOption } from "@/admin/admin-filter";
import { useAdminList, useAdminPickTarget, 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");
const pickTarget = useAdminPickTarget();
/** 选择器模式:带 channel 进来(如 actor-operation 选用户)时点行回传而非跳详情 */
const isPick = ref(false);
/** 列名 -> 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 {
if (isPick.value) {
if (pickTarget.multiple.value) {
return;
}
pickTarget.deliverOne(row);
return;
}
// 与旧页面一致:id 用 UserName,mode 决定详情页是只读还是编辑
// 不做 encodeURIComponent:见 composables/useAdminPick 的说明(H5 二次编码坑)
uni.navigateTo({
url: `${pageUrl("userOperation")}?id=${row.UserName}&mode=view`,
});
}
function onConfirm(rows: AdminRow[]): void {
if (!rows.length) {
uni.showToast({ title: t("AdminPage.pickNoneTip"), icon: "none" });
return;
}
pickTarget.deliver(rows);
}
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);
}
}
});
onLoad((options) => {
pickTarget.readOptions(options);
isPick.value = String(options?.channel ?? "").trim().length > 0;
});
// 取数排在登录守卫之后:admin-list 的 auto 默认 false,首屏由这里手动触发一次
useAuthGuard(() => {
// 每次显示都重拉第一页:增/改/删后从详情返回能看到最新数据(navigateBack 不重新挂页面)
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="!isPick"
:columns="columns" :loader="state.query" :row-key="SecurityUserEntity.USER_NAME" title-key="UserLocalName"
:selectable="isPick ? pickTarget.multiple.value : Boolean(perm.canDelete.value)" :filter-count="filterCount"
:filter-visible="filterVisible" :filter-items="draft" :search-token="searchToken"
:keep-selection-on-reload="isPick" @search="onSearch" @open-filter="state.openFilter()"
@close-filter="state.closeFilter()" @apply-filter="state.applyFilter($event)"
@clear-filter="state.clearFiltersAndSearch()" @row-tap="onRowTap">
<template v-if="!isPick && perm.canCreate.value" #toolbar-actions>
<view class="create-entry" @click="onCreate">
<u-icon name="plus" size="40" />
</view>
</template>
<template #actions="{ rows }">
<u-button v-if="isPick" type="primary" shape="circle" size="mini" :text="t('AdminPage.pickDoneBut')"
@click="onConfirm(rows)" />
<u-button v-else-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;
justify-content: center;
width: 64rpx;
height: 64rpx;
margin-left: 12rpx;
color: $u-type-primary;
background-color: $u-bg-color;
border-radius: 16rpx;
}
</style>