diff --git a/uniapp/youjiala/src/components/admin-pick-popup/admin-pick-popup.vue b/uniapp/youjiala/src/components/admin-pick-popup/admin-pick-popup.vue
new file mode 100644
index 0000000..44b725c
--- /dev/null
+++ b/uniapp/youjiala/src/components/admin-pick-popup/admin-pick-popup.vue
@@ -0,0 +1,465 @@
+
+
+
+
+
+
+
+
+
diff --git a/uniapp/youjiala/src/composables/usePickerConfigs.ts b/uniapp/youjiala/src/composables/usePickerConfigs.ts
new file mode 100644
index 0000000..a144947
--- /dev/null
+++ b/uniapp/youjiala/src/composables/usePickerConfigs.ts
@@ -0,0 +1,380 @@
+/**
+ * 安全域"弹窗选择器"配置工厂:为 admin-pick-popup 提供 columns / loader / rowKey / titleKey。
+ *
+ * 每个工厂对应一种被选实体,内部封装 service 调用与 criteria 构建。
+ * fixedFilters 是一个响应式 ref,调用方在打开弹窗前动态设值(如 { dependent: "xxx" }),
+ * loader 会将其作为等值条件注入 criteria.Filter。
+ *
+ * keyword 搜索统一映射到各实体的"主名称字段"(CONTAIN 模糊)。
+ */
+import type { PickColumn } from "@/components/admin-pick-popup/admin-pick-popup.vue";
+import { useLocale } from "uview-pro";
+import { computed, ref } from "vue";
+import { SearchFilter } from "@/data/auto/searchFilter";
+import { SecurityActorEntity } from "@/data/auto/securityActorEntity";
+import { SecurityActorSearchCriteria } from "@/data/auto/securityActorSearchCriteria";
+import { SecurityFunctionDisplayData } from "@/data/auto/securityFunctionDisplayData";
+import { SecurityFunctionSearchCriteria } from "@/data/auto/securityFunctionSearchCriteria";
+import { SecurityGroupEntity } from "@/data/auto/securityGroupEntity";
+import { SecurityGroupSearchCriteria } from "@/data/auto/securityGroupSearchCriteria";
+import { SecurityHelpFeatureEntity } from "@/data/auto/securityHelpFeatureEntity";
+import { SecurityHelpFunctionSearchCriteria } from "@/data/auto/securityHelpFunctionSearchCriteria";
+import { SecurityPermissionDependentEntity } from "@/data/auto/securityPermissionDependentEntity";
+import { SecurityPermissionEntity } from "@/data/auto/securityPermissionEntity";
+import { SecurityPermissionSearchCriteria } from "@/data/auto/securityPermissionSearchCriteria";
+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 securityActorService from "@/service/security/securityActorService";
+import securityFunctionService from "@/service/security/securityFunctionService";
+import securityGroupService from "@/service/security/securityGroupService";
+import securityHelpFunctionService from "@/service/security/securityHelpFunctionService";
+import securityPermissionService from "@/service/security/securityPermissionService";
+import securityUserService from "@/service/security/securityUserService";
+
+/** 构建一条文本 CONTAIN 过滤 */
+function containFilter(column: string, value: string): SearchFilter {
+ const f = new SearchFilter();
+ f.Column = column;
+ f.Type = SearchFilterTypePolicy.TEXT;
+ f.Operator = SearchFilterOperatorPolicy.CONTAIN;
+ f.Value = value;
+ return f;
+}
+
+/** 构建一条等值过滤 */
+function equalFilter(column: string, value: string): SearchFilter {
+ const f = new SearchFilter();
+ f.Column = column;
+ f.Type = SearchFilterTypePolicy.TEXT;
+ f.Operator = SearchFilterOperatorPolicy.EQUAL_TO;
+ f.Value = value;
+ return f;
+}
+
+/** 通用返回值 */
+export interface PickerConfig {
+ columns: PickColumn[];
+ loader: (
+ page: number,
+ pageSize: number,
+ keyword: string,
+ fixedFilters: Record,
+ ) => Promise<{ rows: any[]; total: number }>;
+ rowKey: string;
+ titleKey: string;
+ fixedFilters: Record;
+}
+
+// ===== Permission =====
+export function usePermissionPickConfig() {
+ const { t } = useLocale();
+ const fixedFilters = ref>({});
+
+ const columns = computed(() => [
+ {
+ label: t("PermissionPage.permissionName"),
+ prop: SecurityPermissionEntity.PERMISSION_NAME,
+ },
+ {
+ label: t("PermissionPage.description"),
+ prop: SecurityPermissionEntity.DESCRIPTION,
+ },
+ {
+ label: t("PermissionPage.dependent"),
+ prop: SecurityPermissionEntity.DEPENDENT,
+ },
+ ]);
+
+ async function loader(page: number, pageSize: number, keyword: string) {
+ const criteria = new SecurityPermissionSearchCriteria();
+ criteria.GridColumns = [];
+ criteria.page = page;
+ criteria.limit = pageSize;
+ criteria.orderby = SecurityPermissionEntity.DB_PERMISSION_NAME;
+ const filters: SearchFilter[] = [];
+ if (keyword) {
+ filters.push(
+ containFilter(SecurityPermissionEntity.DB_PERMISSION_NAME, keyword),
+ );
+ }
+ criteria.Filter = filters;
+ const data = await securityPermissionService.FindBySearchCriteria(criteria);
+ return { rows: data?.Permissions ?? [], total: data?.Total ?? 0 };
+ }
+
+ return {
+ columns,
+ loader,
+ rowKey: SecurityPermissionEntity.PERMISSION_NAME,
+ titleKey: SecurityPermissionEntity.PERMISSION_NAME,
+ fixedFilters,
+ };
+}
+
+// ===== Permission Dependent =====
+export function useDependentPickConfig() {
+ const { t } = useLocale();
+ const fixedFilters = ref>({});
+
+ const columns = computed(() => [
+ {
+ label: t("PermissionPage.dependentDetailName"),
+ prop: SecurityPermissionDependentEntity.DEPENDENT_DETAIL_NAME,
+ },
+ {
+ label: t("PermissionPage.dependentDetailDescription"),
+ prop: SecurityPermissionDependentEntity.DEPENDENT_DETAIL_DESCRIPTION,
+ },
+ ]);
+
+ async function loader(
+ page: number,
+ pageSize: number,
+ keyword: string,
+ filters: Record,
+ ) {
+ const criteria = new SecurityPermissionSearchCriteria();
+ criteria.GridColumns = [];
+ criteria.page = page;
+ criteria.limit = pageSize;
+ criteria.orderby =
+ SecurityPermissionDependentEntity.DB_DEPENDENT_DETAIL_NAME;
+ const filterList: SearchFilter[] = [];
+ // 固定过滤:按 dependent 等值(由调用方设定)
+ if (filters.dependent) {
+ filterList.push(
+ equalFilter(
+ SecurityPermissionDependentEntity.DB_DEPENDENT,
+ filters.dependent,
+ ),
+ );
+ }
+ if (keyword) {
+ filterList.push(
+ containFilter(
+ SecurityPermissionDependentEntity.DB_DEPENDENT_DETAIL_NAME,
+ keyword,
+ ),
+ );
+ }
+ criteria.Filter = filterList;
+ const data =
+ await securityPermissionService.FindDependentsBySearchCriteria(criteria);
+ return { rows: data?.Dependents ?? [], total: data?.Total ?? 0 };
+ }
+
+ return {
+ columns,
+ loader,
+ rowKey: SecurityPermissionDependentEntity.DEPENDENT_DETAIL_NAME,
+ titleKey: SecurityPermissionDependentEntity.DEPENDENT_DETAIL_NAME,
+ fixedFilters,
+ };
+}
+
+// ===== Actor =====
+export function useActorPickConfig() {
+ const { t } = useLocale();
+ const fixedFilters = ref>({});
+
+ const columns = computed(() => [
+ {
+ label: t("UserPage.actorLocalName"),
+ prop: SecurityActorEntity.ACTOR_LOCAL_NAME,
+ },
+ { label: t("UserPage.actorName"), prop: SecurityActorEntity.ACTOR_NAME },
+ ]);
+
+ async function loader(page: number, pageSize: number, keyword: string) {
+ const criteria = new SecurityActorSearchCriteria();
+ criteria.GridColumns = [];
+ criteria.page = page;
+ criteria.limit = pageSize;
+ const filters: SearchFilter[] = [];
+ if (keyword) {
+ filters.push(
+ containFilter(SecurityActorEntity.DB_ACTOR_LOCAL_NAME, keyword),
+ );
+ }
+ criteria.Filter = filters;
+ const data = await securityActorService.FindBySearchCriteria(criteria);
+ return { rows: data?.Datas ?? [], total: data?.Total ?? 0 };
+ }
+
+ return {
+ columns,
+ loader,
+ rowKey: SecurityActorEntity.ACTOR_NAME,
+ titleKey: SecurityActorEntity.ACTOR_LOCAL_NAME,
+ fixedFilters,
+ };
+}
+
+// ===== Group =====
+export function useGroupPickConfig() {
+ const { t } = useLocale();
+ const fixedFilters = ref>({});
+
+ const columns = computed(() => [
+ { label: t("GroupPage.groupName"), prop: SecurityGroupEntity.GROUP_NAME },
+ {
+ label: t("GroupPage.groupLocalName"),
+ prop: SecurityGroupEntity.GROUP_LOCAL_NAME,
+ },
+ ]);
+
+ async function loader(page: number, pageSize: number, keyword: string) {
+ const criteria = new SecurityGroupSearchCriteria();
+ criteria.GridColumns = [];
+ criteria.page = page;
+ criteria.limit = pageSize;
+ criteria.orderby = SecurityGroupEntity.DB_GROUP_NAME;
+ const filters: SearchFilter[] = [];
+ if (keyword) {
+ filters.push(containFilter(SecurityGroupEntity.DB_GROUP_NAME, keyword));
+ }
+ criteria.Filter = filters;
+ const data = await securityGroupService.FindBySearchCriteria(criteria);
+ return { rows: data?.Datas ?? [], total: data?.Total ?? 0 };
+ }
+
+ return {
+ columns,
+ loader,
+ rowKey: SecurityGroupEntity.GROUP_NAME,
+ titleKey: SecurityGroupEntity.GROUP_NAME,
+ fixedFilters,
+ };
+}
+
+// ===== User =====
+export function useUserPickConfig() {
+ const { t } = useLocale();
+ const fixedFilters = ref>({});
+
+ const columns = computed(() => [
+ {
+ label: t("UserPage.userLocalName"),
+ prop: SecurityUserEntity.USER_LOCAL_NAME,
+ },
+ { label: t("UserPage.userName"), prop: SecurityUserEntity.USER_NAME },
+ ]);
+
+ async function loader(page: number, pageSize: number, keyword: string) {
+ const criteria = new SecurityUserSearchCriteria();
+ criteria.GridColumns = [];
+ criteria.page = page;
+ criteria.limit = pageSize;
+ const filters: SearchFilter[] = [];
+ if (keyword) {
+ filters.push(containFilter(SecurityUserEntity.DB_USER_NAME, keyword));
+ }
+ criteria.Filter = filters;
+ const data = await securityUserService.FindBySearchCriteria(criteria);
+ return { rows: data?.Datas ?? [], total: data?.Total ?? 0 };
+ }
+
+ return {
+ columns,
+ loader,
+ rowKey: SecurityUserEntity.USER_NAME,
+ titleKey: SecurityUserEntity.USER_LOCAL_NAME,
+ fixedFilters,
+ };
+}
+
+// ===== Function =====
+export function useFunctionPickConfig() {
+ const { t } = useLocale();
+ const fixedFilters = ref>({});
+
+ const columns = computed(() => [
+ {
+ label: t("FunctionPage.functionName"),
+ prop: SecurityFunctionDisplayData.FUNCTION_NAME,
+ },
+ {
+ label: t("FunctionPage.functionLocalName"),
+ prop: SecurityFunctionDisplayData.FUNCTION_LOCAL_NAME,
+ },
+ ]);
+
+ async function loader(
+ page: number,
+ pageSize: number,
+ keyword: string,
+ filters: Record,
+ ) {
+ const criteria = new SecurityFunctionSearchCriteria();
+ criteria.GridColumns = [];
+ criteria.page = page;
+ criteria.limit = pageSize;
+ criteria.orderby = SecurityFunctionDisplayData.DB_FUNCTION_NAME;
+ const filterList: SearchFilter[] = [];
+ if (keyword) {
+ filterList.push(
+ containFilter(SecurityFunctionDisplayData.DB_FUNCTION_NAME, keyword),
+ );
+ }
+ // 固定过滤:按 FunctionIndc 等值(如 indc=N 只要目录、indc=Y 只要功能)
+ if (filters.indc) {
+ filterList.push(
+ equalFilter(SecurityFunctionDisplayData.DB_FUNCTION_INDC, filters.indc),
+ );
+ }
+ criteria.Filter = filterList;
+ const data = await securityFunctionService.FindBySearchCriteria(criteria);
+ return {
+ rows: data?.SecurityFunctionDisplayDatas ?? [],
+ total: data?.Total ?? 0,
+ };
+ }
+
+ return {
+ columns,
+ loader,
+ rowKey: SecurityFunctionDisplayData.FUNCTION_NAME,
+ titleKey: SecurityFunctionDisplayData.FUNCTION_NAME,
+ fixedFilters,
+ };
+}
+
+// ===== Help Feature =====
+export function useHelpFeaturePickConfig() {
+ const { t } = useLocale();
+ const fixedFilters = ref>({});
+
+ const columns = computed(() => [
+ { label: t("HelpFeaturePage.name"), prop: SecurityHelpFeatureEntity.NAME },
+ {
+ label: t("HelpFeaturePage.description"),
+ prop: SecurityHelpFeatureEntity.DESCRIPTION,
+ },
+ ]);
+
+ async function loader(page: number, pageSize: number, keyword: string) {
+ const criteria = new SecurityHelpFunctionSearchCriteria();
+ criteria.GridColumns = [];
+ criteria.page = page;
+ criteria.limit = pageSize;
+ criteria.orderby = SecurityHelpFeatureEntity.DB_NAME;
+ const filters: SearchFilter[] = [];
+ if (keyword) {
+ filters.push(containFilter(SecurityHelpFeatureEntity.DB_NAME, keyword));
+ }
+ criteria.Filter = filters;
+ const data =
+ await securityHelpFunctionService.FindFeatureBySearchCriteria(criteria);
+ return { rows: data?.Features ?? [], total: data?.Total ?? 0 };
+ }
+
+ return {
+ columns,
+ loader,
+ rowKey: SecurityHelpFeatureEntity.NAME,
+ titleKey: SecurityHelpFeatureEntity.NAME,
+ fixedFilters,
+ };
+}
diff --git a/uniapp/youjiala/src/locale/lang/en-US.json b/uniapp/youjiala/src/locale/lang/en-US.json
index a2208b6..2020f2b 100644
--- a/uniapp/youjiala/src/locale/lang/en-US.json
+++ b/uniapp/youjiala/src/locale/lang/en-US.json
@@ -545,6 +545,7 @@
},
"SharePage": {
"title": "Family Groups",
+ "searchPlaceholder": "Search group name",
"createTitle": "Create Group",
"noAccess": "This group is private and you are not a member",
"unnamed": "Unnamed",
@@ -599,6 +600,7 @@
},
"MediaPage": {
"title": "Media",
+ "searchPlaceholder": "Search album name",
"createTitle": "New Album",
"segPhoto": "Photos",
"segAudio": "Music",
@@ -690,7 +692,9 @@
"menuSection": "Features",
"menuEmptyText": "The server returned no authorized function",
"loadingTip": "Loading…",
- "savingTip": "Saving…"
+ "savingTip": "Saving…",
+ "pickLoadingMore": "Pull up for more…",
+ "pickNoMore": "— No more —"
},
"UserPage": {
"title": "User Query",
diff --git a/uniapp/youjiala/src/locale/lang/zh-CN.json b/uniapp/youjiala/src/locale/lang/zh-CN.json
index d4c18e0..f7bc086 100644
--- a/uniapp/youjiala/src/locale/lang/zh-CN.json
+++ b/uniapp/youjiala/src/locale/lang/zh-CN.json
@@ -545,6 +545,7 @@
},
"SharePage": {
"title": "家庭群组",
+ "searchPlaceholder": "搜索群组名称",
"createTitle": "创建群组",
"noAccess": "该群组为私密,您还不是成员",
"unnamed": "未命名",
@@ -599,6 +600,7 @@
},
"MediaPage": {
"title": "多媒体",
+ "searchPlaceholder": "搜索专辑名称",
"createTitle": "新建专辑",
"segPhoto": "相册",
"segAudio": "音乐",
@@ -690,7 +692,9 @@
"menuSection": "功能菜单",
"menuEmptyText": "后端没有返回你有权限的功能",
"loadingTip": "加载中…",
- "savingTip": "保存中…"
+ "savingTip": "保存中…",
+ "pickLoadingMore": "上拉加载更多…",
+ "pickNoMore": "— 没有更多了 —"
},
"UserPage": {
"title": "用户查询",
diff --git a/uniapp/youjiala/src/md/admin-pages.md b/uniapp/youjiala/src/md/admin-pages.md
index aaeca49..3a6bcfb 100644
--- a/uniapp/youjiala/src/md/admin-pages.md
+++ b/uniapp/youjiala/src/md/admin-pages.md
@@ -14,15 +14,15 @@
- **保留** `bindOption` 数据模型(后端列名 / `Type` / `Operator` / `SelectOptions`)与查询语义(分页、条件、`orderby`),因为后端 `FindBySearchCriteria` 吃的就是这个形状;
- **砍掉** 多列网格、列配置、打印、排序表头——手机没有对应交互;
-- **重写** 列表为卡片流、详情为单列卡、选择为"真跳一页再回传"。
+- **重写** 列表为卡片流、详情为单列卡、选择为"页内底部弹窗"(`admin-pick-popup`,见 §3.3)。
复用分三层,边界清楚,换域移植(Group/Function/Dms/Publish…)时机械部分零改动:
| 层 | 文件 | 认识什么 | 不认识什么 |
| --- | --- | --- | --- |
| 纯函数 | `admin/admin-filter.ts`、`admin-date.ts`、`admin-column.ts`、`admin-types.ts` | SearchFilter / BindOption / 日期口径 | vue、uni、service |
-| composable | `useAdminList`、`useAdminPick`、`useAdminMenu` | 上面那层 + vue 响应式 | z-paging、service、权限名 |
-| 组件 | `admin-list`、`admin-form`、`admin-filter-drawer` | z-paging + uview-pro | service、**权限** |
+| composable | `useAdminList`、`useAdminPick`、`usePickerConfigs`、`useAdminMenu` | 上面那层 + vue 响应式 | z-paging、service、权限名 |
+| 组件 | `admin-list`、`admin-form`、`admin-filter-drawer`、`admin-pick-popup` | z-paging / u-popup + uview-pro | service、**权限** |
**组件不判权限**:按钮由页面按 `canCreate/canAmend/canDelete` 组好数组(详情)或用 `v-if`(列表工具条)传进来。这样公共组件不需要知道任何一个域的权限名。
@@ -34,7 +34,7 @@
| --- | --- | --- | --- |
| `userEnquiry` | `pages/security/user-enquiry` | `UserEnquiry.vue` | 用户列表:三条筛选(用户名/中文名/创建日期区间)、每页 20、按创建日期倒序、行点进详情、多选批量作废 |
| `userOperation` | `pages/security/user-operation` | `UserOperation.vue` | 用户详情:查看/新增/修改,`?id=&mode=`;角色明细在 `#extra` 插槽自绘 |
-| `actorEnquiry` | `pages/security/actor-enquiry` | `ActorEnquiry.vue` | 角色查询,**双用途**(见 §3.3) |
+| `actorEnquiry` | `pages/security/actor-enquiry` | `ActorEnquiry.vue` | 角色查询,纯列表页(选角色改由调用方弹窗,见 §3.3) |
| `dashboard` | `pages/dashboard/dashboard` | `layout` 菜单 | 入口九宫格,`FindMenuByUsername` 驱动(见 §5) |
列表页写法(`user-enquiry.vue` 为准):`reactive(bindOptions)` + 一个只碰具体 service 的 `fetchData(query)` 交给 `useAdminList`;解构出的 `filterCount/filterVisible/draft/searchToken` 是 ref/computed,模板自动 unwrap,方法仍走 `state.xxx`。取数排在 `useAuthGuard` 之后(`admin-list` 的 `auto` 默认 false,首屏手动 `listRef.refresh()` 一次)。
@@ -53,16 +53,20 @@
### 3.2 user-operation 的取数与保存
`onLoad` 读 `id`(=`UserName`)与 `mode`;`FindByKey` 取 `SecurityUserData`,取不到提示"记录不存在"。保存按有无 `CreatedBy` 决定 `Update`/`Insert`,成功后用后端回写的档案刷新、回填主键、退回只读。密码编辑的是 `SecurityUserDisplayData.OrgUserPassword`(留空即不改),只在编辑态出现。
-### 3.3 选择页回传协议(useAdminPick / useAdminPickTarget)
-旧系统的"选角色"是 Operation 用 `` 内嵌 Enquiry + `provide/inject`。移动端改成**真跳一页再回传**:
+### 3.3 选择器:`admin-pick-popup` 页内底部弹窗
+旧系统的"选角色/选权限/选功能"是 Operation 用 `` 内嵌 Enquiry + `provide/inject`。移动端第一版改成"真跳一页再回传",但多数选择页 `show-nav-back=false` 且没有取消按钮,用户进去就退不出来。现已改为**页内弹窗**:
-- 调用方 `useAdminPick(channel).open({ page, params }, onPicked)`——先退订再订阅,`onUnmounted` 兜底清理;
-- 目标页(`actor-enquiry`)`useAdminPickTarget()` 在 `onLoad` 里 `readOptions(options)` 存下 `channel`/`multiple`,确定时 `deliver(rows)`(`uni.$emit('admin:pick:'+channel, rows)` + `navigateBack`);
-- `actor-enquiry` 因此**同一个列表两种用法**:菜单进入(无 `channel`)是普通查询;被带 `?channel=&multiple=1` 打开就是选择器,单选点行即回传、多选底部"完成"批量回传。
+- 组件 `components/admin-pick-popup/admin-pick-popup.vue`:`u-popup mode="bottom"`(80vh)+ `scroll-view` 手动分页(**不能用 z-paging**:它默认 `position:fixed` 撑满视口,在弹层内会打架);
+- Props `visible / title / columns / loader / rowKey / titleKey / multiple / fixedFilters / pageSize`,Emits `update:visible / close / confirm(rows)`;
+- 数据源在 `composables/usePickerConfigs.ts`:每个被选数据一个工厂函数(`usePermissionPickConfig`、`useDependentPickConfig`、`useActorPickConfig`、`useGroupPickConfig`、`useUserPickConfig`、`useFunctionPickConfig`、`useHelpFeaturePickConfig`),返回 `{ columns, loader, rowKey, titleKey, fixedFilters }`;
+- 调用方只需 `const xxxConfig = useXxxPickConfig(); const showXxxPicker = ref(false);`,点入口置 `true`,`@confirm` 里做原来的去重合并逻辑。需要动态过滤(如扩展权限按 `dependent`)时写 `xxxConfig.fixedFilters.value = { dependent }`;
+- 单选点行即 `confirm` + 关闭;多选勾选后点确定;X / 遮罩 / 取消三条退路都通。
-**协议参数一律不做 `encodeURIComponent`**:H5 侧 uni/vue-router 拼 URL 用 `encodeURI`,预编码会把 `%` 再编成 `%25`。`channel` 与 `params` 的值只允许字母/数字/下划线/连字符/点/斜杠。
+**security 域的 enquiry 页因此回归纯列表页**:`isPick` / `pickTarget` / `onLoad(channel)` / `deliver()` 全部删除,`:show-nav-back="true"` 恒定,`+` 新增入口不再被选择器态隐藏。
-> 命名订正:这个页面就叫 `actor-enquiry`(旧系统本是 `ActorEnquiry.vue`),早期草稿里的 `actor-pick` 是造错的概念——"选择"只是它带 `channel` 打开时的一种模式,不是另一张页面。
+`useAdminPick` / `useAdminPickTarget` 机制本身**保留**:`family-person-enquiry` 仍作为「族员选择器」被首页 dashboard 以 `?channel=person` 调起。那条老路子的协议参数一律不做 `encodeURIComponent`(H5 侧 uni/vue-router 拼 URL 用 `encodeURI`,预编码会把 `%` 再编成 `%25`)。
+
+> 命名订正:`actor-enquiry` 就是旧 `ActorEnquiry.vue` 的移植,早期草稿里的 `actor-pick` 是造错的概念——它现在只是普通查询页,选择能力在调用方的弹窗里。
---
diff --git a/uniapp/youjiala/src/pages/security/actor-enquiry.vue b/uniapp/youjiala/src/pages/security/actor-enquiry.vue
index b8e1d24..123810c 100644
--- a/uniapp/youjiala/src/pages/security/actor-enquiry.vue
+++ b/uniapp/youjiala/src/pages/security/actor-enquiry.vue
@@ -2,20 +2,16 @@
/**
* 角色查询(ActorEnquiry):旧 WebVue views/security/ActorEnquiry.vue 的移动端等价物
*
- * 它有两种打开方式,同一套列表:
- * 1. 从菜单进(无 channel):普通查询页(点行看详情,右上可新增、长按多选可批量删除);
- * 2. 被 user-operation 当"角色选择器"跳进来(带 ?channel=&multiple=1):
- * 选完 uni.$emit 回传再 navigateBack(见 useAdminPick / useAdminPickTarget 协议)。
- *
- * 旧系统里这个选择能力是 Operation 用 内嵌 Enquiry + provide/inject 实现的;
- * 移动端页面之间没有 provide/inject,只能真跳一页再回传,所以这个页面本身就是 Enquiry。
+ * 普通查询页:点行看详情,右上可新增、多选可批量删除。
+ * 旧系统里 user-operation 用 内嵌本页做"角色选择器";
+ * 移动端的弹窗选择已由 user-operation 内嵌 实现,
+ * 本页不再承担选择器职责(useAdminPick / useAdminPickTarget 协议已移除)。
*/
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 } from "vue";
import { fillCriteria, makeFilterOption } from "@/admin/admin-filter";
-import { useAdminList, useAdminPickTarget, useAuthGuard } from "@/composables";
+import { useAdminList, useAuthGuard } from "@/composables";
import { SecurityActorEntity } from "@/data/auto/securityActorEntity";
import { SecurityActorSearchCriteria } from "@/data/auto/securityActorSearchCriteria";
import { SearchFilterOperatorPolicy } from "@/data/custom/searchFilterOperatorPolicy";
@@ -25,12 +21,8 @@ import { pageUrl } from "@/router/routes";
import securityActorService from "@/service/security/securityActorService";
const { t } = useLocale();
-const pickTarget = useAdminPickTarget();
const perm = usePagePermission("actorEnquiry");
-/** 选择器模式:带 channel 进来时点行回传;否则作为普通菜单页点行进详情 */
-const isPick = ref(false);
-
const bindOptions = reactive([
makeFilterOption(
SecurityActorEntity.DB_ACTOR_LOCAL_NAME,
@@ -73,28 +65,12 @@ function onSearch(): void {
}
function onRowTap(row: AdminRow): void {
- // 选择器模式:多选态交给勾选,单选态点一行即选中回传
- if (isPick.value) {
- if (pickTarget.multiple.value) {
- return;
- }
- pickTarget.deliverOne(row);
- return;
- }
- // 菜单模式:进角色详情(旧页面双击行开编辑弹窗,移动端改点行进详情页)
+ // 进角色详情(旧页面双击行开编辑弹窗,移动端改点行进详情页)
uni.navigateTo({
url: `${pageUrl("actorOperation")}?id=${row.ActorName}&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("actorOperation")}?mode=create` });
}
@@ -121,11 +97,6 @@ async function onDelete(rows: AdminRow[]): Promise {
});
}
-onLoad((options) => {
- pickTarget.readOptions(options);
- isPick.value = String(options?.channel ?? "").trim().length > 0;
-});
-
useAuthGuard(() => {
// 每次显示都重拉第一页:增/改/删后从详情返回能看到最新数据(navigateBack 不重新挂页面)
nextTick(() => listRef.value?.refresh());
@@ -135,23 +106,20 @@ useAuthGuard(() => {
-
+
-
-
+
diff --git a/uniapp/youjiala/src/pages/security/actor-operation.vue b/uniapp/youjiala/src/pages/security/actor-operation.vue
index 79bde61..309ef95 100644
--- a/uniapp/youjiala/src/pages/security/actor-operation.vue
+++ b/uniapp/youjiala/src/pages/security/actor-operation.vue
@@ -6,17 +6,19 @@
* - onLoad 读 id(=ActorName)与 mode(view|create|amend);
* - FindByKey 取 SecurityActorData,取不到提示"记录不存在";
* - 保存按 CreatedBy 有无决定 Update / Insert,成功后回填主键、退回只读;
- * - 两个子表:组别明细(SecurityActorGroups,选 group-enquiry 多选)、
- * 用户明细(SecurityUserActors,选 user-enquiry 多选),均按主键去重合并、删除即剔除。
+ * - 两个子表:组别明细(SecurityActorGroups,弹窗多选组别)、
+ * 用户明细(SecurityUserActors,弹窗多选用户),均按主键去重合并、删除即剔除。
*
- * 旧页面用 dragModal 内嵌 Enquiry + provide/inject 做选择;移动端改成真跳选择页再回传
- * (见 composables/useAdminPick),所以两个子表放在 admin-form 的 #extra 插槽里自绘。
+ * 旧页面用 dragModal 内嵌 Enquiry + provide/inject 做选择;移动端用
+ * 底部弹窗(数据源见 composables/usePickerConfigs),
+ * 两个子表放在 admin-form 的 #extra 插槽里自绘。
*/
import type { AdminFormAction, AdminFormField, AdminFormSection } from "@/admin/admin-types";
import { onLoad } from "@dcloudio/uni-app";
import { useLocale } from "uview-pro";
import { computed, ref } from "vue";
-import { useAdminPick, useAuthGuard } from "@/composables";
+import { useAuthGuard } from "@/composables";
+import { useGroupPickConfig, useUserPickConfig } from "@/composables/usePickerConfigs";
import { SecurityActorData } from "@/data/auto/securityActorData";
import { SecurityActorGroupViewEntity } from "@/data/auto/securityActorGroupViewEntity";
import { SecurityUserActorViewEntity } from "@/data/auto/securityUserActorViewEntity";
@@ -167,25 +169,29 @@ function onBack(): void {
smartBack("mineTab");
}
-// 组别选择:跳 group-enquiry(多选),回传后按 GroupName 去重合并
-const groupPicker = useAdminPick("group");
+// 组别选择弹窗
+const groupConfig = useGroupPickConfig();
+const showGroupPicker = ref(false);
function onAddGroup(): void {
- groupPicker.open({ page: "groupEnquiry", params: { multiple: "1" } }, (rows) => {
- const exists = new Set(data.value.SecurityActorGroups.map(item => item.GroupName));
- for (const row of rows) {
- const groupName = String(row.GroupName ?? "");
- if (!groupName || exists.has(groupName)) {
- continue;
- }
- const entity = new SecurityActorGroupViewEntity();
- entity.ActorName = String(actor.value.ActorName ?? "");
- entity.GroupName = groupName;
- entity.GroupLocalName = String(row.GroupLocalName ?? "");
- data.value.SecurityActorGroups.push(entity);
- exists.add(groupName);
+ showGroupPicker.value = true;
+}
+
+function onGroupPicked(rows: any[]): void {
+ const exists = new Set(data.value.SecurityActorGroups.map(item => item.GroupName));
+ for (const row of rows) {
+ const groupName = String(row.GroupName ?? "");
+ if (!groupName || exists.has(groupName)) {
+ continue;
}
- });
+ const entity = new SecurityActorGroupViewEntity();
+ entity.ActorName = String(actor.value.ActorName ?? "");
+ entity.GroupName = groupName;
+ entity.GroupLocalName = String(row.GroupLocalName ?? "");
+ data.value.SecurityActorGroups.push(entity);
+ exists.add(groupName);
+ }
+ showGroupPicker.value = false;
}
function onDeleteGroup(groupName: string): void {
@@ -194,25 +200,29 @@ function onDeleteGroup(groupName: string): void {
);
}
-// 用户选择:跳 user-enquiry(多选),回传后按 UserName 去重合并
-const userPicker = useAdminPick("user");
+// 用户选择弹窗
+const userConfig = useUserPickConfig();
+const showUserPicker = ref(false);
function onAddUser(): void {
- userPicker.open({ page: "userEnquiry", params: { multiple: "1" } }, (rows) => {
- const exists = new Set(data.value.SecurityUserActors.map(item => item.UserName));
- for (const row of rows) {
- const userName = String(row.UserName ?? "");
- if (!userName || exists.has(userName)) {
- continue;
- }
- const entity = new SecurityUserActorViewEntity();
- entity.UserName = userName;
- entity.UserLocalName = String(row.UserLocalName ?? "");
- entity.ActorName = String(actor.value.ActorName ?? "");
- data.value.SecurityUserActors.push(entity);
- exists.add(userName);
+ showUserPicker.value = true;
+}
+
+function onUserPicked(rows: any[]): void {
+ const exists = new Set(data.value.SecurityUserActors.map(item => item.UserName));
+ for (const row of rows) {
+ const userName = String(row.UserName ?? "");
+ if (!userName || exists.has(userName)) {
+ continue;
}
- });
+ const entity = new SecurityUserActorViewEntity();
+ entity.UserName = userName;
+ entity.UserLocalName = String(row.UserLocalName ?? "");
+ entity.ActorName = String(actor.value.ActorName ?? "");
+ data.value.SecurityUserActors.push(entity);
+ exists.add(userName);
+ }
+ showUserPicker.value = false;
}
function onDeleteUser(userName: string): void {
@@ -317,6 +327,16 @@ useAuthGuard(() => {
+
+
+
+
+