ow
This commit is contained in:
1 parent
3c0d389fcd
commit
32f237c4bf
22 files changed
+3351
-24
No files matched your search
@@ -0,0 +1,208 @@
|
||||
<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>
|
||||
Reference in new issue
Block a user