This commit is contained in:
owenchen committed 2026-10-17 16:39:54 +08:00
1 parent a8896cbfb2
commit 282d8ce583
20 files changed
+1256 -524

No files matched your search

@@ -2,20 +2,16 @@
/**
* 角色查询(ActorEnquiry):旧 WebVue views/security/ActorEnquiry.vue 的移动端等价物
*
* 它有两种打开方式,同一套列表:
* 1. 从菜单进(无 channel):普通查询页(点行看详情,右上可新增、长按多选可批量删除);
* 2. 被 user-operation 当"角色选择器"跳进来(带 ?channel=&multiple=1):
* 选完 uni.$emit 回传再 navigateBack(见 useAdminPick / useAdminPickTarget 协议)。
*
* 旧系统里这个选择能力是 Operation 用 <dragModal> 内嵌 Enquiry + provide/inject 实现的;
* 移动端页面之间没有 provide/inject,只能真跳一页再回传,所以这个页面本身就是 Enquiry。
* 普通查询页:点行看详情,右上可新增、多选可批量删除。
* 旧系统里 user-operation 用 <dragModal> 内嵌本页做"角色选择器";
* 移动端的弹窗选择已由 user-operation 内嵌 <admin-pick-popup> 实现,
* 本页不再承担选择器职责(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<void> {
});
}
onLoad((options) => {
pickTarget.readOptions(options);
isPick.value = String(options?.channel ?? "").trim().length > 0;
});
useAuthGuard(() => {
// 每次显示都重拉第一页:增/改/删后从详情返回能看到最新数据(navigateBack 不重新挂页面)
nextTick(() => listRef.value?.refresh());
@@ -135,23 +106,20 @@ useAuthGuard(() => {
<template>
<view class="page">
<admin-list ref="listRef" v-model:keyword="keyword" :nav-title="t('UserPage.actorEnquiryTitle')"
:show-nav-back="!isPick" :columns="columns" :loader="state.query" :row-key="SecurityActorEntity.ACTOR_NAME"
title-key="ActorLocalName" :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)"
:show-nav-back="true" :columns="columns" :loader="state.query" :row-key="SecurityActorEntity.ACTOR_NAME"
title-key="ActorLocalName" :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 v-if="!isPick && perm.canCreate.value" #toolbar-actions>
<template v-if="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)" />
<u-button v-if="perm.canDelete.value" type="error" shape="circle" size="mini" :text="t('AdminPage.deleteBut')"
@click="onDelete(rows)" />
</template>
</admin-list>
</view>
@@ -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 做选择;移动端用
* <admin-pick-popup> 底部弹窗(数据源见 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<SecurityActorGroupViewEntity>("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<SecurityUserActorViewEntity>("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(() => {
</view>
</template>
</admin-form>
<!-- 弹窗选择器 -->
<admin-pick-popup v-model:visible="showGroupPicker" :title="t('GroupPage.title')"
:columns="groupConfig.columns.value" :loader="groupConfig.loader" :row-key="groupConfig.rowKey"
:title-key="groupConfig.titleKey" :multiple="true" :fixed-filters="groupConfig.fixedFilters.value"
@confirm="onGroupPicked" @close="showGroupPicker = false" />
<admin-pick-popup v-model:visible="showUserPicker" :title="t('UserPage.title')" :columns="userConfig.columns.value"
:loader="userConfig.loader" :row-key="userConfig.rowKey" :title-key="userConfig.titleKey" :multiple="true"
:fixed-filters="userConfig.fixedFilters.value" @confirm="onUserPicked" @close="showUserPicker = false" />
</template>
<style lang="scss" scoped>
@@ -7,16 +7,15 @@
* data.SecurityFunctionDisplayDatas(不是普通的 data.Datas!),这里逐字照抄;
* - 中文名称筛选用 CN_CONTAIN(旧页面即如此),功能名称用 CONTAIN;
* - 菜单? 是 SINGLE_SELECTION + yesNo 候选,列显示 Y->是 / N->否。
* 行点击进详情(无 channel)/ 作为选择器回传(带 channel)。
* 行点击进详情。function-operation 的"选权限/上级/下级"弹窗已由调用方内嵌
* <admin-pick-popup> 实现,本页不再承担选择器职责(useAdminPick 协议已移除)。
*/
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 { SecurityFunctionDisplayData } from "@/data/auto/securityFunctionDisplayData";
@@ -29,9 +28,6 @@ import securityFunctionService from "@/service/security/securityFunctionService"
const { t, currentLocale } = useLocale();
const perm = usePagePermission("functionEnquiry");
const pickTarget = useAdminPickTarget();
const isPick = ref(false);
function yesNo() {
return [
@@ -116,13 +112,6 @@ function onSearch(): void {
}
function onRowTap(row: AdminRow): void {
if (isPick.value) {
if (pickTarget.multiple.value) {
return;
}
pickTarget.deliverOne(row);
return;
}
uni.navigateTo({
url: `${pageUrl("functionOperation")}?id=${row.FunctionName}&mode=view`,
});
@@ -132,14 +121,6 @@ function onCreate(): void {
uni.navigateTo({ url: `${pageUrl("functionOperation")}?mode=create` });
}
function onConfirm(rows: AdminRow[]): void {
if (!rows.length) {
uni.showToast({ title: t("AdminPage.pickNoneTip"), icon: "none" });
return;
}
pickTarget.deliver(rows);
}
async function onDelete(rows: AdminRow[]): Promise<void> {
if (!rows.length) {
uni.showToast({ title: t("AdminPage.pickNoneTip"), icon: "none" });
@@ -176,22 +157,6 @@ watch(currentLocale, () => {
}
});
onLoad((options) => {
pickTarget.readOptions(options);
isPick.value = String(options?.channel ?? "").trim().length > 0;
// function-operation 选上级目录时锁 FunctionIndc=N、选下级菜单时锁 Y:
// 用 ?indc= 传进来预置到菜单? 筛选,回传后按该类型过滤候选功能。
const indc = String(options?.indc ?? "").trim();
if (indc) {
const option = bindOptions.find(
item => item.BindValue === SecurityFunctionDisplayData.DB_FUNCTION_INDC,
);
if (option) {
option.Value = indc;
}
}
});
useAuthGuard(() => {
// 每次显示都重拉第一页:增/改/删后从详情返回能看到最新数据(navigateBack 不重新挂页面)
nextTick(() => listRef.value?.refresh());
@@ -200,24 +165,20 @@ useAuthGuard(() => {
<template>
<view class="page">
<admin-list ref="listRef" v-model:keyword="keyword" :nav-title="t('FunctionPage.title')"
:show-nav-back="!isPick" :columns="columns" :loader="state.query"
:row-key="SecurityFunctionDisplayData.FUNCTION_NAME" title-key="FunctionName"
: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>
<admin-list ref="listRef" v-model:keyword="keyword" :nav-title="t('FunctionPage.title')" :show-nav-back="true"
:columns="columns" :loader="state.query" :row-key="SecurityFunctionDisplayData.FUNCTION_NAME"
title-key="FunctionName" :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 v-if="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"
<u-button v-if="perm.canDelete.value" type="error" shape="circle" size="mini"
:text="t('AdminPage.deleteBut')" @click="onDelete(rows)" />
</template>
</admin-list>
@@ -4,9 +4,9 @@
*
* 对齐旧逻辑:
* - 主表 SecurityFunction(DisplayData 形状),字段含 菜单?(FunctionIndc Y/N)、权限名称(选择器)等;
* - 上级目录 SecurityFunctionMapParents:选 function-enquiry(单选、锁 FunctionIndc=N,
* - 上级目录 SecurityFunctionMapParents:弹窗选功能(单选、锁 FunctionIndc=N,
* 即只能挂在"目录"型功能下),最多一条(旧页面父为空时才显示新增);
* - 下级菜单 SecurityFunctionMapSons:选 function-enquiry(多选、锁 FunctionIndc=Y),
* - 下级菜单 SecurityFunctionMapSons:弹窗选功能(多选、锁 FunctionIndc=Y),
* 仅当本页 FunctionIndc=N(目录)才能加;可上下移动重排,重排后 Orderby=序号+1;
* - 菜单? 从 N 改成非 N 时清空下级菜单(旧 onFunctionIndcChange);
* - 保存按 CreatedBy 有无决定 Update / Insert(SecurityFunctionData)。
@@ -16,7 +16,8 @@ import type { AdminFormAction, AdminFormField, AdminFormSection } from "@/admin/
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 { useFunctionPickConfig, usePermissionPickConfig } from "@/composables/usePickerConfigs";
import { SecurityFunctionData } from "@/data/auto/securityFunctionData";
import { SecurityFunctionMapViewEntity } from "@/data/auto/securityFunctionMapViewEntity";
import { usePagePermission } from "@/permission/permission-context";
@@ -180,37 +181,49 @@ function onBack(): void {
smartBack("mineTab");
}
// 权限名称:单选 permission-enquiry,回传写进 FunctionName 之外不影响
const permissionPicker = useAdminPick("permission");
// 权限名称:单选弹窗
const permissionConfig = usePermissionPickConfig();
const showPermissionPicker = ref(false);
function pickPermission(): void {
if (readonly.value || !perm.canAmend.value) {
return;
}
permissionPicker.open({ page: "permissionEnquiry" }, (rows) => {
if (rows.length) {
func.value.PermissionName = String(rows[0].PermissionName ?? "");
}
});
showPermissionPicker.value = true;
}
// 上级目录:单选 function-enquiry(锁 FunctionIndc=N),仅当父为空时可加
const parentPicker = useAdminPick("parent");
function onPermissionPicked(rows: any[]): void {
if (rows.length) {
func.value.PermissionName = String(rows[0].PermissionName ?? "");
}
showPermissionPicker.value = false;
}
// 上级目录:单选弹窗(锁 FunctionIndc=N)
const parentConfig = useFunctionPickConfig();
const showParentPicker = ref(false);
function addParent(): void {
if (data.value.SecurityFunctionMapParents.length > 0) {
return;
}
parentPicker.open({ page: "functionEnquiry", params: { indc: "N" } }, (rows) => {
ensureLists();
if (!rows.length) {
return;
parentConfig.fixedFilters.value = { indc: "N" };
showParentPicker.value = true;
}
function onParentPicked(rows: any[]): void {
ensureLists();
if (rows.length) {
const parentName = String(rows[0].FunctionName ?? "");
// 防御:不能把自己设为自己的上级目录(旧跳页版也没挡,弹窗化顺手补上)
if (parentName && parentName !== func.value.FunctionName) {
const parent = new SecurityFunctionMapViewEntity();
parent.ParentFunctionName = parentName;
parent.ParentFunctionLocalName = String(rows[0].FunctionLocalName ?? "");
data.value.SecurityFunctionMapParents.push(parent);
}
const parent = new SecurityFunctionMapViewEntity();
parent.ParentFunctionName = String(rows[0].FunctionName ?? "");
parent.ParentFunctionLocalName = String(rows[0].FunctionLocalName ?? "");
data.value.SecurityFunctionMapParents.push(parent);
});
}
showParentPicker.value = false;
}
function deleteParent(parentFunctionName: string): void {
@@ -219,26 +232,32 @@ function deleteParent(parentFunctionName: string): void {
);
}
// 下级菜单:多选 function-enquiry(锁 FunctionIndc=Y),仅目录(FunctionIndc=N)可加
const sonPicker = useAdminPick("son");
// 下级菜单:多选弹窗(锁 FunctionIndc=Y),仅目录(FunctionIndc=N)可加
const sonConfig = useFunctionPickConfig();
const showSonPicker = ref(false);
function addSon(): void {
sonPicker.open({ page: "functionEnquiry", params: { multiple: "1", indc: "Y" } }, (rows) => {
ensureLists();
const exists = new Set(data.value.SecurityFunctionMapSons.map(item => item.SonFunctionName));
for (const row of rows) {
const sonName = String(row.FunctionName ?? "");
if (!sonName || exists.has(sonName)) {
continue;
}
const son = new SecurityFunctionMapViewEntity();
son.SonFunctionName = sonName;
son.SonFunctionLocalName = String(row.FunctionLocalName ?? "");
son.Orderby = data.value.SecurityFunctionMapSons.length + 1;
data.value.SecurityFunctionMapSons.push(son);
exists.add(sonName);
sonConfig.fixedFilters.value = { indc: "Y" };
showSonPicker.value = true;
}
function onSonPicked(rows: any[]): void {
ensureLists();
const exists = new Set(data.value.SecurityFunctionMapSons.map(item => item.SonFunctionName));
for (const row of rows) {
const sonName = String(row.FunctionName ?? "");
// 防御:跳过空、自身(不能把自己挂成自己的下级菜单)、以及已在列表里的
if (!sonName || sonName === func.value.FunctionName || exists.has(sonName)) {
continue;
}
});
const son = new SecurityFunctionMapViewEntity();
son.SonFunctionName = sonName;
son.SonFunctionLocalName = String(row.FunctionLocalName ?? "");
son.Orderby = data.value.SecurityFunctionMapSons.length + 1;
data.value.SecurityFunctionMapSons.push(son);
exists.add(sonName);
}
showSonPicker.value = false;
}
function deleteSon(sonFunctionName: string): void {
@@ -393,6 +412,22 @@ useAuthGuard(() => {
</view>
</template>
</admin-form>
<!-- 弹窗选择器 -->
<admin-pick-popup v-model:visible="showPermissionPicker" :title="t('PermissionPage.title')"
:columns="permissionConfig.columns.value" :loader="permissionConfig.loader" :row-key="permissionConfig.rowKey"
:title-key="permissionConfig.titleKey" :multiple="false" :fixed-filters="permissionConfig.fixedFilters.value"
@confirm="onPermissionPicked" @close="showPermissionPicker = false" />
<admin-pick-popup v-model:visible="showParentPicker" :title="t('FunctionPage.parentTitle')"
:columns="parentConfig.columns.value" :loader="parentConfig.loader" :row-key="parentConfig.rowKey"
:title-key="parentConfig.titleKey" :multiple="false" :fixed-filters="parentConfig.fixedFilters.value"
@confirm="onParentPicked" @close="showParentPicker = false" />
<admin-pick-popup v-model:visible="showSonPicker" :title="t('FunctionPage.sonTitle')"
:columns="sonConfig.columns.value" :loader="sonConfig.loader" :row-key="sonConfig.rowKey"
:title-key="sonConfig.titleKey" :multiple="true" :fixed-filters="sonConfig.fixedFilters.value"
@confirm="onSonPicked" @close="showSonPicker = false" />
</template>
<style lang="scss" scoped>
@@ -3,22 +3,20 @@
* 用户组查询(列表页):旧 WebVue views/security/GroupEnquiry.vue 的移动端等价物
*
* 保留:三条筛选(组别名称/中文名称 文本包含、创建日期 区间)、每页 20 条、
* 行点击进详情、多选批量删除(旧页面也是循环 Delete(GroupName))、多选选择器模式。
* 行点击进详情、多选批量删除(旧页面也是循环 Delete(GroupName))。
* 丢掉:多列网格、列配置、打印、桌面双击弹窗。
*
* 两种打开方式,同一套列表:
* 1. 从菜单/枢纽页进(无 channel):普通查询页,点行 -> group-operation 详情;
* 2. 被 actor-operation / group-operation 当"组别选择器"跳进来(带 ?channel=&multiple=1):
* 选完 uni.$emit 回传再 navigateBack(见 useAdminPick / useAdminPickTarget 协议)。
* 普通查询页:点行 -> group-operation 详情。
* 旧系统里 actor-operation / group-operation 用 <dragModal> 内嵌本页做"组别选择器";
* 移动端的弹窗选择已由调用方内嵌 <admin-pick-popup> 实现,
* 本页不再承担选择器职责(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, watch } from "vue";
import { fillCriteria, makeFilterOption } from "@/admin/admin-filter";
import {
useAdminList,
useAdminPickTarget,
useAuthGuard,
} from "@/composables";
import { SecurityGroupEntity } from "@/data/auto/securityGroupEntity";
@@ -31,10 +29,6 @@ import securityGroupService from "@/service/security/securityGroupService";
const { t, currentLocale } = useLocale();
const perm = usePagePermission("groupEnquiry");
const pickTarget = useAdminPickTarget();
/** 选择器模式:带 channel 进来时点行回传而非跳详情 */
const isPick = ref(false);
const LABEL_KEYS: Record<string, string> = {
[SecurityGroupEntity.DB_GROUP_NAME]: "GroupPage.groupName",
@@ -109,13 +103,6 @@ function onSearch(): void {
}
function onRowTap(row: AdminRow): void {
if (isPick.value) {
if (pickTarget.multiple.value) {
return;
}
pickTarget.deliverOne(row);
return;
}
uni.navigateTo({
url: `${pageUrl("groupOperation")}?id=${row.GroupName}&mode=view`,
});
@@ -125,14 +112,6 @@ function onCreate(): void {
uni.navigateTo({ url: `${pageUrl("groupOperation")}?mode=create` });
}
function onConfirm(rows: AdminRow[]): void {
if (!rows.length) {
uni.showToast({ title: t("AdminPage.pickNoneTip"), icon: "none" });
return;
}
pickTarget.deliver(rows);
}
async function onDelete(rows: AdminRow[]): Promise<void> {
if (!rows.length) {
uni.showToast({ title: t("AdminPage.pickNoneTip"), icon: "none" });
@@ -163,11 +142,6 @@ watch(currentLocale, () => {
}
});
onLoad((options) => {
pickTarget.readOptions(options);
isPick.value = String(options?.channel ?? "").trim().length > 0;
});
useAuthGuard(() => {
// 每次显示都重拉第一页:增/改/删后从详情返回能看到最新数据(navigateBack 不重新挂页面)
nextTick(() => listRef.value?.refresh());
@@ -176,23 +150,20 @@ useAuthGuard(() => {
<template>
<view class="page">
<admin-list ref="listRef" v-model:keyword="keyword" :nav-title="t('GroupPage.title')" :show-nav-back="!isPick"
<admin-list ref="listRef" v-model:keyword="keyword" :nav-title="t('GroupPage.title')" :show-nav-back="true"
:columns="columns" :loader="state.query" :row-key="SecurityGroupEntity.GROUP_NAME" title-key="GroupName"
: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()"
: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 v-if="!isPick && perm.canCreate.value" #toolbar-actions>
<template v-if="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"
<u-button v-if="perm.canDelete.value" type="error" shape="circle" size="mini"
:text="t('AdminPage.deleteBut')" @click="onDelete(rows)" />
</template>
</admin-list>
@@ -2,13 +2,13 @@
/**
* 组别详情(查看 / 新增 / 修改):旧 WebVue views/security/GroupOperation.vue 的移动端等价物
*
* 三个子表(旧页用 dragModal 内嵌 Enquiry + provide/inject,移动端改成真跳选择页再回传):
* 1. 权限明细 SecurityGroupPermissionViews —— 选 permission-enquiry(多选),
* 三个子表(旧页用 dragModal 内嵌 Enquiry + provide/inject,移动端改用 admin-pick-popup 底部弹窗):
* 1. 权限明细 SecurityGroupPermissionViews —— 弹窗选 permission(多选),
* 每行带 查看/新增/修改/删除 四个勾选(对齐旧 GroupPermissionActions 的四个 checkbox);
* 删除权限行会连同其扩展权限明细一起剔除(旧 deletePermission 同款)。
* 2. 扩展权限明细 SecurityGroupPermissionDependentViews —— 由权限行的"扩展名称"点开
* permission-dependent-enquiry(多选、按 dependent 过滤)补入;
* 3. 组别内角色群 SecurityActorGroups —— 选 actor-enquiry(多选)。
* 扩展权限弹窗(多选、按 dependent 过滤)补入;
* 3. 组别内角色群 SecurityActorGroups —— 弹窗选角色(多选)。
* 新增权限/扩展权限行时四个勾选全部置 false(旧页面 ent.IsAmend=...=false)。
* 保存按 CreatedBy 有无决定 Update / Insert(SecurityGroupData)。
*/
@@ -16,7 +16,8 @@ import type { AdminFormAction, AdminFormField, AdminFormSection } from "@/admin/
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 { useActorPickConfig, useDependentPickConfig, usePermissionPickConfig } from "@/composables/usePickerConfigs";
import { SecurityActorGroupViewEntity } from "@/data/auto/securityActorGroupViewEntity";
import { SecurityGroupData } from "@/data/auto/securityGroupData";
import { SecurityGroupPermissionDependentViewDisplayData } from "@/data/auto/securityGroupPermissionDependentViewDisplayData";
@@ -197,28 +198,32 @@ function actionLabel(key: string): string {
return map[key] ?? key;
}
// 权限选择:跳 permission-enquiry(多选),按 PermissionName 去重
const permissionPicker = useAdminPick("permission");
// 权限选择弹窗
const permissionConfig = usePermissionPickConfig();
const showPermissionPicker = ref(false);
function addPermission(): void {
permissionPicker.open({ page: "permissionEnquiry", params: { multiple: "1" } }, (rows) => {
ensureLists();
const exists = new Set(data.value.SecurityGroupPermissionViews.map(item => item.PermissionName));
for (const row of rows) {
const permissionName = String(row.PermissionName ?? "");
if (!permissionName || exists.has(permissionName)) {
continue;
}
const ent = new SecurityGroupPermissionViewDisplayData();
ent.GroupName = String(group.value.GroupName ?? "");
ent.PermissionName = permissionName;
ent.PermissionDescription = String(row.Description ?? "");
ent.Dependent = String(row.Dependent ?? "");
ent.IsView = ent.IsCreate = ent.IsAmend = ent.IsDelete = false;
data.value.SecurityGroupPermissionViews.push(ent);
exists.add(permissionName);
showPermissionPicker.value = true;
}
function onPermissionPicked(rows: any[]): void {
ensureLists();
const exists = new Set(data.value.SecurityGroupPermissionViews.map(item => item.PermissionName));
for (const row of rows) {
const permissionName = String(row.PermissionName ?? "");
if (!permissionName || exists.has(permissionName)) {
continue;
}
});
const ent = new SecurityGroupPermissionViewDisplayData();
ent.GroupName = String(group.value.GroupName ?? "");
ent.PermissionName = permissionName;
ent.PermissionDescription = String(row.Description ?? "");
ent.Dependent = String(row.Dependent ?? "");
ent.IsView = ent.IsCreate = ent.IsAmend = ent.IsDelete = false;
data.value.SecurityGroupPermissionViews.push(ent);
exists.add(permissionName);
}
showPermissionPicker.value = false;
}
function deletePermission(permissionName: string): void {
@@ -231,37 +236,39 @@ function deletePermission(permissionName: string): void {
);
}
// 扩展权限选择:跳 permission-dependent-enquiry(多选、按当前权限行的 dependent 过滤)
const dependentPicker = useAdminPick("dependent");
// 扩展权限选择弹窗
const dependentConfig = useDependentPickConfig();
const showDependentPicker = ref(false);
function addPermissionDependent(dependent: string): void {
if (readonly.value || !dependent) {
return;
}
dependentPicker.open(
{ page: "permissionDependentEnquiry", params: { multiple: "1", dependent } },
(rows) => {
ensureLists();
const exists = new Set(
data.value.SecurityGroupPermissionDependentViews.map(item => item.DependentDetailName),
);
for (const row of rows) {
const detailName = String(row.DependentDetailName ?? "");
if (!detailName || exists.has(detailName)) {
continue;
}
const ent = new SecurityGroupPermissionDependentViewDisplayData();
ent.GroupName = String(group.value.GroupName ?? "");
ent.PermissionName = String(row.PermissionName ?? "");
ent.Dependent = String(row.Dependent ?? "");
ent.DependentDetailName = detailName;
ent.DependentDetailDescription = String(row.DependentDetailDescription ?? "");
ent.IsView = ent.IsCreate = ent.IsAmend = ent.IsDelete = false;
data.value.SecurityGroupPermissionDependentViews.push(ent);
exists.add(detailName);
}
},
dependentConfig.fixedFilters.value = { dependent };
showDependentPicker.value = true;
}
function onDependentPicked(rows: any[]): void {
ensureLists();
const exists = new Set(
data.value.SecurityGroupPermissionDependentViews.map(item => item.DependentDetailName),
);
for (const row of rows) {
const detailName = String(row.DependentDetailName ?? "");
if (!detailName || exists.has(detailName)) {
continue;
}
const ent = new SecurityGroupPermissionDependentViewDisplayData();
ent.GroupName = String(group.value.GroupName ?? "");
ent.PermissionName = String(row.PermissionName ?? "");
ent.Dependent = String(row.Dependent ?? "");
ent.DependentDetailName = detailName;
ent.DependentDetailDescription = String(row.DependentDetailDescription ?? "");
ent.IsView = ent.IsCreate = ent.IsAmend = ent.IsDelete = false;
data.value.SecurityGroupPermissionDependentViews.push(ent);
exists.add(detailName);
}
showDependentPicker.value = false;
}
function deletePermissionDependent(detailName: string): void {
@@ -270,26 +277,30 @@ function deletePermissionDependent(detailName: string): void {
);
}
// 角色选择:跳 actor-enquiry(多选),按 ActorName 去重
const actorPicker = useAdminPick("actor");
// 角色选择弹窗
const actorConfig = useActorPickConfig();
const showActorPicker = ref(false);
function addActor(): void {
actorPicker.open({ page: "actorEnquiry", params: { multiple: "1" } }, (rows) => {
ensureLists();
const exists = new Set(data.value.SecurityActorGroups.map(item => item.ActorName));
for (const row of rows) {
const actorName = String(row.ActorName ?? "");
if (!actorName || exists.has(actorName)) {
continue;
}
const ent = new SecurityActorGroupViewEntity();
ent.GroupName = String(group.value.GroupName ?? "");
ent.ActorName = actorName;
ent.ActorLocalName = String(row.ActorLocalName ?? "");
data.value.SecurityActorGroups.push(ent);
exists.add(actorName);
showActorPicker.value = true;
}
function onActorPicked(rows: any[]): void {
ensureLists();
const exists = new Set(data.value.SecurityActorGroups.map(item => item.ActorName));
for (const row of rows) {
const actorName = String(row.ActorName ?? "");
if (!actorName || exists.has(actorName)) {
continue;
}
});
const ent = new SecurityActorGroupViewEntity();
ent.GroupName = String(group.value.GroupName ?? "");
ent.ActorName = actorName;
ent.ActorLocalName = String(row.ActorLocalName ?? "");
data.value.SecurityActorGroups.push(ent);
exists.add(actorName);
}
showActorPicker.value = false;
}
function deleteActor(actorName: string): void {
@@ -460,6 +471,22 @@ useAuthGuard(() => {
</view>
</template>
</admin-form>
<!-- 弹窗选择器 -->
<admin-pick-popup v-model:visible="showPermissionPicker" :title="t('PermissionPage.title')"
:columns="permissionConfig.columns.value" :loader="permissionConfig.loader" :row-key="permissionConfig.rowKey"
:title-key="permissionConfig.titleKey" :multiple="true" :fixed-filters="permissionConfig.fixedFilters.value"
@confirm="onPermissionPicked" @close="showPermissionPicker = false" />
<admin-pick-popup v-model:visible="showDependentPicker" :title="t('PermissionPage.dependentTitle')"
:columns="dependentConfig.columns.value" :loader="dependentConfig.loader" :row-key="dependentConfig.rowKey"
:title-key="dependentConfig.titleKey" :multiple="true" :fixed-filters="dependentConfig.fixedFilters.value"
@confirm="onDependentPicked" @close="showDependentPicker = false" />
<admin-pick-popup v-model:visible="showActorPicker" :title="t('UserPage.actorTitle')"
:columns="actorConfig.columns.value" :loader="actorConfig.loader" :row-key="actorConfig.rowKey"
:title-key="actorConfig.titleKey" :multiple="true" :fixed-filters="actorConfig.fixedFilters.value"
@confirm="onActorPicked" @close="showActorPicker = false" />
</template>
<style lang="scss" scoped>
@@ -7,17 +7,16 @@
* - "所属组"列存的是组主键(HelpFeatureGroupId),靠 FindAll 取回的组表翻译成组编码显示;
* - 点一条功能点进它【所属组】的维护页(旧 gotoGroup 语义:id=组编码);
* - 三条筛选:功能点名称 / 作用说明 / 图标 文本包含。
* help-function-operation 会把它当"功能点组选择器"跳进来(单选),回传后按组主键翻译成组编码。
* help-function-operation 的"选功能点组"弹窗已由调用方内嵌 <admin-pick-popup> 实现,
* 本页不再承担选择器职责(useAdminPick / useAdminPickTarget 协议已移除)。
*/
import type { AdminColumn, AdminQuery, AdminRow } from "@/admin/admin-types";
import type { SecurityHelpFeatureGroupEntity } from "@/data/auto/securityHelpFeatureGroupEntity";
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 { SecurityHelpFeatureEntity } from "@/data/auto/securityHelpFeatureEntity";
@@ -30,9 +29,6 @@ import securityHelpFunctionService from "@/service/security/securityHelpFunction
const { t, currentLocale } = useLocale();
const perm = usePagePermission("helpFeatureEnquiry");
const pickTarget = useAdminPickTarget();
const isPick = ref(false);
const LABEL_KEYS: Record<string, string> = {
[SecurityHelpFeatureEntity.DB_NAME]: "HelpFeaturePage.name",
@@ -132,13 +128,6 @@ function onSearch(): void {
}
function onRowTap(row: AdminRow): void {
if (isPick.value) {
if (pickTarget.multiple.value) {
return;
}
pickTarget.deliverOne(row);
return;
}
const groupCode = groupCodeOf(row.HelpFeatureGroupId);
if (!groupCode) {
uni.showToast({ title: t("HelpFeaturePage.noGroupTip"), icon: "none" });
@@ -153,14 +142,6 @@ function onCreate(): void {
uni.navigateTo({ url: `${pageUrl("helpFeatureOperation")}?mode=create` });
}
function onConfirm(rows: AdminRow[]): void {
if (!rows.length) {
uni.showToast({ title: t("AdminPage.pickNoneTip"), icon: "none" });
return;
}
pickTarget.deliver(rows);
}
watch(currentLocale, () => {
for (const option of bindOptions) {
const key = LABEL_KEYS[option.BindValue];
@@ -170,11 +151,6 @@ watch(currentLocale, () => {
}
});
onLoad((options) => {
pickTarget.readOptions(options);
isPick.value = String(options?.channel ?? "").trim().length > 0;
});
useAuthGuard(() => {
// 每次显示都重拉组表与第一页:增/改/删后从详情返回能看到最新数据(navigateBack 不重新挂页面)
loadGroups();
@@ -185,23 +161,16 @@ useAuthGuard(() => {
<template>
<view class="page">
<admin-list ref="listRef" v-model:keyword="keyword" :nav-title="t('HelpFeaturePage.title')"
:show-nav-back="!isPick" :columns="columns" :loader="state.query"
:row-key="SecurityHelpFeatureEntity.HELP_FEATURE_ID" title-key="Name"
:selectable="isPick && pickTarget.multiple.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>
:show-nav-back="true" :columns="columns" :loader="state.query"
:row-key="SecurityHelpFeatureEntity.HELP_FEATURE_ID" title-key="Name" :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 v-if="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)" />
</template>
</admin-list>
</view>
</template>
@@ -6,15 +6,15 @@
* - FindBySearchCriteria 的返回落在 data.Datas(SecurityHelpFunctionEntity[]);
* - 两条筛选:功能名称 / 功能说明 文本包含;
* - 作废走 DeleteByFunctionName(不是 Delete!旧页面即如此),行点击进详情。
* 本页从不作为选择器被跳入,选择器的弹窗能力已由调用方内嵌 <admin-pick-popup> 实现,
* 遗留的 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, watch } from "vue";
import { fillCriteria, makeFilterOption } from "@/admin/admin-filter";
import {
useAdminList,
useAdminPickTarget,
useAuthGuard,
} from "@/composables";
import { SecurityHelpFunctionEntity } from "@/data/auto/securityHelpFunctionEntity";
@@ -27,9 +27,6 @@ import securityHelpFunctionService from "@/service/security/securityHelpFunction
const { t, currentLocale } = useLocale();
const perm = usePagePermission("helpFunctionEnquiry");
const pickTarget = useAdminPickTarget();
const isPick = ref(false);
const LABEL_KEYS: Record<string, string> = {
[SecurityHelpFunctionEntity.DB_FUNCTION_NAME]: "HelpFunctionPage.functionName",
@@ -97,13 +94,6 @@ function onSearch(): void {
}
function onRowTap(row: AdminRow): void {
if (isPick.value) {
if (pickTarget.multiple.value) {
return;
}
pickTarget.deliverOne(row);
return;
}
uni.navigateTo({
url: `${pageUrl("helpFunctionOperation")}?id=${row.FunctionName}&mode=view`,
});
@@ -113,14 +103,6 @@ function onCreate(): void {
uni.navigateTo({ url: `${pageUrl("helpFunctionOperation")}?mode=create` });
}
function onConfirm(rows: AdminRow[]): void {
if (!rows.length) {
uni.showToast({ title: t("AdminPage.pickNoneTip"), icon: "none" });
return;
}
pickTarget.deliver(rows);
}
async function onDelete(rows: AdminRow[]): Promise<void> {
if (!rows.length) {
uni.showToast({ title: t("AdminPage.pickNoneTip"), icon: "none" });
@@ -151,11 +133,6 @@ watch(currentLocale, () => {
}
});
onLoad((options) => {
pickTarget.readOptions(options);
isPick.value = String(options?.channel ?? "").trim().length > 0;
});
useAuthGuard(() => {
// 每次显示都重拉第一页:增/改/删后从详情返回能看到最新数据(navigateBack 不重新挂页面)
nextTick(() => listRef.value?.refresh());
@@ -165,23 +142,20 @@ useAuthGuard(() => {
<template>
<view class="page">
<admin-list ref="listRef" v-model:keyword="keyword" :nav-title="t('HelpFunctionPage.title')"
:show-nav-back="!isPick" :columns="columns" :loader="state.query"
:show-nav-back="true" :columns="columns" :loader="state.query"
:row-key="SecurityHelpFunctionEntity.FUNCTION_NAME" title-key="FunctionName"
: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()"
: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 v-if="!isPick && perm.canCreate.value" #toolbar-actions>
<template v-if="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"
<u-button v-if="perm.canDelete.value" type="error" shape="circle" size="mini"
:text="t('AdminPage.deleteBut')" @click="onDelete(rows)" />
</template>
</admin-list>
@@ -4,10 +4,10 @@
*
* 对齐旧逻辑:
* - onLoad 读 id(=FunctionName)与 mode;FindByFunctionName 取 SecurityHelpFunctionData;
* - 主字段:功能名称(给哪个功能写帮助,新建时靠 function-enquiry 单选回填、之后锁死)、
* - 主字段:功能名称(给哪个功能写帮助,新建时靠功能选择弹窗单选回填、之后锁死)、
* 启用?(EnabledIndc Y/N switch)、功能说明(Intro textarea);
* - 子表1 操作步骤 SecurityHelpSteps:内联可编辑 Title/Description/Tips/Icon,可上下移动;
* - 子表2 引用的功能点组 SecurityHelpFunctionFeatures:GroupCode 靠 help-feature-enquiry 单选,
* - 子表2 引用的功能点组 SecurityHelpFunctionFeatures:GroupCode 靠功能点选择弹窗单选,
* 回传的功能点按 HelpFeatureGroupId 翻译成组编码写入;可上下移动;
* - 保存前按界面顺序重排 StepNo=i+1 / SortNo=i+1,调 SaveFunction(不是 Insert/Update!);
* 删除走 DeleteByFunctionName;新建默认 EnabledIndc='Y'。
@@ -19,7 +19,8 @@ import type { SecurityHelpFeatureGroupEntity } from "@/data/auto/securityHelpFea
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 { useFunctionPickConfig, useHelpFeaturePickConfig } from "@/composables/usePickerConfigs";
import { SecurityHelpFunctionData } from "@/data/auto/securityHelpFunctionData";
import { SecurityHelpFunctionFeatureEntity } from "@/data/auto/securityHelpFunctionFeatureEntity";
import { SecurityHelpStepEntity } from "@/data/auto/securityHelpStepEntity";
@@ -213,44 +214,52 @@ function onBack(): void {
smartBack("mineTab");
}
// 功能名称:新建时单选 function-enquiry 回填(旧页用 FindFunctionNameOptions 下拉,移动端退化成选择器)
const functionNamePicker = useAdminPick("functionName");
// 功能名称:新建时单选弹窗回填
const functionConfig = useFunctionPickConfig();
const showFunctionPicker = ref(false);
function pickFunctionName(): void {
if (readonly.value || !isNew.value || !perm.canAmend.value) {
return;
}
functionNamePicker.open({ page: "functionEnquiry" }, (rows) => {
if (rows.length) {
helpFunction.value.FunctionName = String(rows[0].FunctionName ?? "");
}
});
showFunctionPicker.value = true;
}
// 引用的功能点组:单选 help-feature-enquiry,回传功能点后翻译成组编码
const groupPicker = useAdminPick("group");
function onFunctionPicked(rows: any[]): void {
if (rows.length) {
helpFunction.value.FunctionName = String(rows[0].FunctionName ?? "");
}
showFunctionPicker.value = false;
}
// 引用的功能点组:单选弹窗,回传功能点后翻译成组编码
const featureConfig = useHelpFeaturePickConfig();
const showFeaturePicker = ref(false);
function addFeature(): void {
groupPicker.open({ page: "helpFeatureEnquiry" }, (rows) => {
ensureLists();
if (!rows.length) {
return;
}
const groupCode = groupCodeOf(rows[0].HelpFeatureGroupId);
if (!groupCode) {
uni.showToast({ title: t("HelpFeaturePage.noGroupTip"), icon: "none" });
return;
}
const exists = data.value.SecurityHelpFunctionFeatures.some(item => item.GroupCode === groupCode);
if (exists) {
return;
}
const row = new SecurityHelpFunctionFeatureEntity();
row.FunctionName = String(helpFunction.value.FunctionName ?? "");
row.GroupCode = groupCode;
row.SortNo = data.value.SecurityHelpFunctionFeatures.length + 1;
data.value.SecurityHelpFunctionFeatures.push(row);
});
showFeaturePicker.value = true;
}
function onFeaturePicked(rows: any[]): void {
ensureLists();
if (!rows.length) {
return;
}
const groupCode = groupCodeOf(rows[0].HelpFeatureGroupId);
if (!groupCode) {
uni.showToast({ title: t("HelpFeaturePage.noGroupTip"), icon: "none" });
return;
}
const exists = data.value.SecurityHelpFunctionFeatures.some(item => item.GroupCode === groupCode);
if (exists) {
return;
}
const row = new SecurityHelpFunctionFeatureEntity();
row.FunctionName = String(helpFunction.value.FunctionName ?? "");
row.GroupCode = groupCode;
row.SortNo = data.value.SecurityHelpFunctionFeatures.length + 1;
data.value.SecurityHelpFunctionFeatures.push(row);
showFeaturePicker.value = false;
}
function deleteFeature(index: number): void {
@@ -486,6 +495,17 @@ useAuthGuard(() => {
<help-doc-preview :visible="previewVisible" :title="heroTitle" :intro="helpFunction.Intro" :steps="previewSteps"
:features="previewFeatures" @close="previewVisible = false" />
<!-- 弹窗选择器 -->
<admin-pick-popup v-model:visible="showFunctionPicker" :title="t('FunctionPage.title')"
:columns="functionConfig.columns.value" :loader="functionConfig.loader" :row-key="functionConfig.rowKey"
:title-key="functionConfig.titleKey" :multiple="false" :fixed-filters="functionConfig.fixedFilters.value"
@confirm="onFunctionPicked" @close="showFunctionPicker = false" />
<admin-pick-popup v-model:visible="showFeaturePicker" :title="t('HelpFeaturePage.title')"
:columns="featureConfig.columns.value" :loader="featureConfig.loader" :row-key="featureConfig.rowKey"
:title-key="featureConfig.titleKey" :multiple="false" :fixed-filters="featureConfig.fixedFilters.value"
@confirm="onFeaturePicked" @close="showFeaturePicker = false" />
</template>
<style lang="scss" scoped>
@@ -1,20 +1,20 @@
<script setup lang="ts">
/**
* 扩展权限选择页:旧 WebVue views/security/PermissionDependentModal.vue 的移动端等价物
* 扩展权限查询页:旧 WebVue views/security/PermissionDependentModal.vue 的移动端等价物
*
* 旧页面就是一个纯"选择弹窗"(dragModal 内嵌),供 group-operation 挑扩展权限明细,
* 移动端改成独立选择页:用 FindDependentsBySearchCriteria,返回落在 data.Dependents;
* 唯一筛选是按"扩展名称"等值(EQUAL_TO)——由调用方通过 ?dependent= 传进来锁死,
* 旧页面就是一个纯"选择弹窗"(dragModal 内嵌),移动端弹窗选择已由 group-operation 内嵌
* <admin-pick-popup> 实现,本页保留为独立查询列表页:
* 用 FindDependentsBySearchCriteria,返回落在 data.Dependents;
* 唯一筛选是按"扩展名称"等值(EQUAL_TO)——可由 ?dependent= 传入锁定范围,
* 因为扩展权限明细必须归属某个权限的扩展名称之下。
*/
import type { AdminColumn, AdminQuery, AdminRow } from "@/admin/admin-types";
import type { AdminColumn, AdminQuery } 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 { SecurityPermissionDependentEntity } from "@/data/auto/securityPermissionDependentEntity";
@@ -24,7 +24,6 @@ import { SearchFilterTypePolicy } from "@/data/custom/searchFilterTypePolicy";
import securityPermissionService from "@/service/security/securityPermissionService";
const { t, currentLocale } = useLocale();
const pickTarget = useAdminPickTarget();
const LABEL_KEYS: Record<string, string> = {
[SecurityPermissionDependentEntity.DB_DEPENDENT]: "PermissionPage.dependent",
@@ -79,22 +78,6 @@ function onSearch(): void {
state.reload();
}
function onRowTap(row: AdminRow): void {
// 单选语义:直接回传;多选交给底部"确定"按钮统一交付
if (pickTarget.multiple.value) {
return;
}
pickTarget.deliverOne(row);
}
function onConfirm(rows: AdminRow[]): void {
if (!rows.length) {
uni.showToast({ title: t("AdminPage.pickNoneTip"), icon: "none" });
return;
}
pickTarget.deliver(rows);
}
watch(currentLocale, () => {
for (const option of bindOptions) {
const key = LABEL_KEYS[option.BindValue];
@@ -105,8 +88,7 @@ watch(currentLocale, () => {
});
onLoad((options) => {
pickTarget.readOptions(options);
// 调用方锁死的扩展名称:写进等值筛选,只列出该扩展名称下的子项目
// 可选的扩展名称过滤:?dependent= 传入时只列出该扩展名称下的子项目
const dependent = String(options?.dependent ?? "").trim();
if (dependent) {
const option = bindOptions.find(
@@ -127,16 +109,12 @@ useAuthGuard(() => {
<template>
<view class="page">
<admin-list ref="listRef" v-model:keyword="keyword" :nav-title="t('PermissionPage.dependentTitle')"
:show-nav-back="false" :columns="columns" :loader="state.query"
:show-nav-back="true" :columns="columns" :loader="state.query"
:row-key="SecurityPermissionDependentEntity.DEPENDENT_DETAIL_NAME" title-key="DependentDetailName"
:selectable="pickTarget.multiple.value" :filter-count="filterCount" :filter-visible="filterVisible"
:filter-items="draft" :search-token="searchToken" @search="onSearch" @open-filter="state.openFilter()"
: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 #actions="{ rows }">
<u-button type="primary" shape="circle" size="mini" :text="t('AdminPage.pickDoneBut')"
@click="onConfirm(rows)" />
</template>
@clear-filter="state.clearFiltersAndSearch()">
</admin-list>
</view>
</template>
@@ -5,17 +5,14 @@
* 与旧页面对齐的关键点:
* - findBySearchCriteria 的返回落在 data.Permissions(不是 Datas!),逐字照抄;
* - 四条筛选:权限名称/中文名称/扩展名称 文本包含、创建日期 区间;
* - 行点击进详情(无 channel)/ 作为选择器回传(带 channel)。
* group-operation、function-operation 会把它当"权限选择器"跳进来(多选 / 单选)。
* - 行点击进详情。
*/
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 { SecurityPermissionEntity } from "@/data/auto/securityPermissionEntity";
@@ -28,9 +25,6 @@ import securityPermissionService from "@/service/security/securityPermissionServ
const { t, currentLocale } = useLocale();
const perm = usePagePermission("permissionEnquiry");
const pickTarget = useAdminPickTarget();
const isPick = ref(false);
const LABEL_KEYS: Record<string, string> = {
[SecurityPermissionEntity.DB_PERMISSION_NAME]: "PermissionPage.permissionName",
@@ -116,13 +110,6 @@ function onSearch(): void {
}
function onRowTap(row: AdminRow): void {
if (isPick.value) {
if (pickTarget.multiple.value) {
return;
}
pickTarget.deliverOne(row);
return;
}
uni.navigateTo({
url: `${pageUrl("permissionOperation")}?id=${row.PermissionName}&mode=view`,
});
@@ -132,14 +119,6 @@ function onCreate(): void {
uni.navigateTo({ url: `${pageUrl("permissionOperation")}?mode=create` });
}
function onConfirm(rows: AdminRow[]): void {
if (!rows.length) {
uni.showToast({ title: t("AdminPage.pickNoneTip"), icon: "none" });
return;
}
pickTarget.deliver(rows);
}
async function onDelete(rows: AdminRow[]): Promise<void> {
if (!rows.length) {
uni.showToast({ title: t("AdminPage.pickNoneTip"), icon: "none" });
@@ -170,11 +149,6 @@ watch(currentLocale, () => {
}
});
onLoad((options) => {
pickTarget.readOptions(options);
isPick.value = String(options?.channel ?? "").trim().length > 0;
});
useAuthGuard(() => {
// 每次显示都重拉第一页:增/改/删后从详情返回能看到最新数据(navigateBack 不重新挂页面)
nextTick(() => listRef.value?.refresh());
@@ -183,24 +157,20 @@ useAuthGuard(() => {
<template>
<view class="page">
<admin-list ref="listRef" v-model:keyword="keyword" :nav-title="t('PermissionPage.title')"
:show-nav-back="!isPick" :columns="columns" :loader="state.query"
:row-key="SecurityPermissionEntity.PERMISSION_NAME" title-key="PermissionName"
: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>
<admin-list ref="listRef" v-model:keyword="keyword" :nav-title="t('PermissionPage.title')" :show-nav-back="true"
:columns="columns" :loader="state.query" :row-key="SecurityPermissionEntity.PERMISSION_NAME"
title-key="PermissionName" :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 v-if="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"
<u-button v-if="perm.canDelete.value" type="error" shape="circle" size="mini"
:text="t('AdminPage.deleteBut')" @click="onDelete(rows)" />
</template>
</admin-list>
@@ -7,13 +7,14 @@
* 丢掉的东西:多列网格、列配置、打印——手机上没有对应交互。
*
* 页面只组数据与路由,列表/筛选/分页的机制全在 admin-list + useAdminList 里。
* actor-operation 的"选用户"弹窗已由调用方内嵌 <admin-pick-popup> 实现,
* 本页不再承担选择器职责(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, watch } from "vue";
import { fillCriteria, makeFilterOption } from "@/admin/admin-filter";
import { useAdminList, useAdminPickTarget, useAuthGuard } from "@/composables";
import { useAdminList, useAuthGuard } from "@/composables";
import { SecurityUserEntity } from "@/data/auto/securityUserEntity";
import { SecurityUserSearchCriteria } from "@/data/auto/securityUserSearchCriteria";
import { SearchFilterOperatorPolicy } from "@/data/custom/searchFilterOperatorPolicy";
@@ -24,10 +25,6 @@ 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> = {
@@ -107,28 +104,13 @@ function onSearch(): void {
}
function onRowTap(row: AdminRow): void {
if (isPick.value) {
if (pickTarget.multiple.value) {
return;
}
pickTarget.deliverOne(row);
return;
}
// 与旧页面一致:id 用 UserName,mode 决定详情页是只读还是编辑
// 不做 encodeURIComponent:见 composables/useAdminPick 的说明(H5 二次编码坑)
// 不做 encodeURIComponent:URL 参数按原样透传,避免 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` });
}
@@ -165,11 +147,6 @@ watch(currentLocale, () => {
}
});
onLoad((options) => {
pickTarget.readOptions(options);
isPick.value = String(options?.channel ?? "").trim().length > 0;
});
// 取数排在登录守卫之后:admin-list 的 auto 默认 false,首屏由这里手动触发一次
useAuthGuard(() => {
// 每次显示都重拉第一页:增/改/删后从详情返回能看到最新数据(navigateBack 不重新挂页面)
@@ -179,23 +156,20 @@ useAuthGuard(() => {
<template>
<view class="page">
<admin-list ref="listRef" v-model:keyword="keyword" :nav-title="t('UserPage.title')" :show-nav-back="!isPick"
<admin-list ref="listRef" v-model:keyword="keyword" :nav-title="t('UserPage.title')" :show-nav-back="true"
: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()"
: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 v-if="!isPick && perm.canCreate.value" #toolbar-actions>
<template v-if="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"
<u-button v-if="perm.canDelete.value" type="error" shape="circle" size="mini"
:text="t('AdminPage.deleteBut')" @click="onDelete(rows)" />
</template>
</admin-list>
@@ -9,8 +9,8 @@
* - 密码编辑的是 SecurityUserDisplayData.OrgUserPassword(留空即不改);
* - 角色明细按 ActorName 去重合并,删除即从数组里剔除。
*
* 旧页面的"选角色"是弹窗内嵌 Enquiry + provide/inject;这里改成真跳 actor-enquiry 页
* 再回传(见 composables/useAdminPick),所以角色区放在 admin-form 的 #extra 插槽里自绘。
* 旧页面的"选角色"是弹窗内嵌 Enquiry + provide/inject;这里用 <admin-pick-popup>
* 底部弹窗(数据源见 composables/usePickerConfigs),角色区放在 admin-form 的 #extra 插槽里自绘。
*/
import type {
AdminFormAction,
@@ -20,7 +20,8 @@ import type {
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 { useActorPickConfig } from "@/composables/usePickerConfigs";
import { SecurityUserActorViewEntity } from "@/data/auto/securityUserActorViewEntity";
import { SecurityUserData } from "@/data/auto/securityUserData";
import { usePagePermission } from "@/permission/permission-context";
@@ -174,30 +175,31 @@ function onBack(): void {
smartBack("mineTab");
}
// 角色选择:跳 actor-enquiry(可多选),回传后按 ActorName 去重合并
const actorPicker = useAdminPick<SecurityUserActorViewEntity>("actor");
// 角色选择弹窗
const actorConfig = useActorPickConfig();
const showActorPicker = ref(false);
function onAddActor(): void {
actorPicker.open(
{ page: "actorEnquiry", params: { multiple: "1" } },
(rows) => {
const exists = new Set(
data.value.SecurityUserActors.map(item => item.ActorName),
);
for (const row of rows) {
const actorName = String(row.ActorName ?? "");
if (!actorName || exists.has(actorName)) {
continue;
}
const entity = new SecurityUserActorViewEntity();
entity.UserName = String(user.value.UserName ?? "");
entity.ActorName = actorName;
entity.ActorLocalName = String(row.ActorLocalName ?? "");
data.value.SecurityUserActors.push(entity);
exists.add(actorName);
}
},
showActorPicker.value = true;
}
function onActorPicked(rows: any[]): void {
const exists = new Set(
data.value.SecurityUserActors.map(item => item.ActorName),
);
for (const row of rows) {
const actorName = String(row.ActorName ?? "");
if (!actorName || exists.has(actorName)) {
continue;
}
const entity = new SecurityUserActorViewEntity();
entity.UserName = String(user.value.UserName ?? "");
entity.ActorName = actorName;
entity.ActorLocalName = String(row.ActorLocalName ?? "");
data.value.SecurityUserActors.push(entity);
exists.add(actorName);
}
showActorPicker.value = false;
}
function onDeleteActor(actorName: string): void {
@@ -266,6 +268,12 @@ useAuthGuard(() => {
</view>
</template>
</admin-form>
<!-- 弹窗选择器 -->
<admin-pick-popup v-model:visible="showActorPicker" :title="t('UserPage.actorTitle')"
:columns="actorConfig.columns.value" :loader="actorConfig.loader" :row-key="actorConfig.rowKey"
:title-key="actorConfig.titleKey" :multiple="true" :fixed-filters="actorConfig.fixedFilters.value"
@confirm="onActorPicked" @close="showActorPicker = false" />
</template>
<style lang="scss" scoped>
+1 -1
View File
@@ -336,7 +336,7 @@ function onRemoveGroup(familyName: string): void {
<text class="seg-bar__text">{{ t(seg.labelKey) }}</text>
</view>
</view>
<search-input v-if="showSearch" v-model="keyword" :placeholder="t('FilePage.searchPlaceholder')"
<search-input v-if="showSearch" v-model="keyword" :placeholder="t('MediaPage.searchPlaceholder')"
@search="onSearch" />
</view>
<view class="media-page__scroll-wrap">
+1 -1
View File
@@ -320,7 +320,7 @@ function groupDesc(group: FamilyGroupEntity): string {
<page-navbar :title="t('SharePage.title')" :show-back="false" :search-on="showSearch"
:show-transfer="false" :show-quick-toggle="false" @back="onBack"
@toggle-search="onToggleSearch" />
<search-input v-if="showSearch" v-model="keyword" :placeholder="t('FilePage.searchPlaceholder')"
<search-input v-if="showSearch" v-model="keyword" :placeholder="t('SharePage.searchPlaceholder')"
@search="onSearch" />
</view>
<view class="share-page__scroll-wrap">