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

No files matched your search

@@ -0,0 +1,465 @@
<script setup lang="ts">
/**
* 通用"弹窗选择器"(底部抽屉):替代旧的跳页选择(useAdminPick → 全页面 enquiry → navigateBack)。
*
* 对齐旧 WebVue 的 <dragModal> + 内嵌 Enquiry 组件模式:
* 在调用方页面内弹出,选完 confirm 回传行数据,不离开当前编辑上下文。
* 用户可通过 X / 遮罩 / 取消按钮随时关闭,彻底解决"进去无法返回"的问题。
*
* 列表用 scroll-view + 手动分页(触底加载更多),避免 z-paging 的 fixed 定位在弹层内冲突。
*/
import type { PropType } from "vue";
import type { AdminRow } from "@/admin/admin-types";
import { useLocale } from "uview-pro";
import { computed, ref, watch } from "vue";
/** 列定义:精简版,第一列为 title,其余为副信息 */
export interface PickColumn {
label: string;
prop: string;
}
/** 取数函数签名 */
type PickLoader = (
page: number,
pageSize: number,
keyword: string,
fixedFilters: Record<string, string>,
) => Promise<{ rows: AdminRow[]; total: number }>;
const props = defineProps({
/** 弹层是否展开(v-model:visible) */
visible: { type: Boolean, default: false },
/** 弹窗标题 */
title: { type: String, default: "" },
/** 显示列:第一列为卡片主标题,其余为副字段 */
columns: { type: Array as PropType<PickColumn[]>, required: true },
/** 取数函数 */
loader: { type: Function as PropType<PickLoader>, required: true },
/** 行唯一标识字段 */
rowKey: { type: String, required: true },
/** 主标题字段(优先于 columns[0].prop) */
titleKey: { type: String, default: "" },
/** 是否多选 */
multiple: { type: Boolean, default: false },
/** 固定过滤条件(如 { dependent: "xxx" }),透传给 loader */
fixedFilters: { type: Object as PropType<Record<string, string>>, default: () => ({}) },
/** 每页条数 */
pageSize: { type: Number, default: 20 },
});
const emit = defineEmits<{
(e: "update:visible", value: boolean): void;
(e: "close"): void;
(e: "confirm", rows: AdminRow[]): void;
}>();
const { t } = useLocale();
// ===== 列表状态 =====
const rows = ref<AdminRow[]>([]);
const total = ref(0);
const page = ref(1);
const loading = ref(false);
const keyword = ref("");
// ===== 选择状态 =====
const selectedKeys = ref<string[]>([]);
const selectedPool = ref<Record<string, AdminRow>>({});
const titleProp = computed(() => props.titleKey || props.columns[0]?.prop || "");
const hasMore = computed(() => rows.value.length < total.value);
const selectedRows = computed(() =>
selectedKeys.value.map(key => selectedPool.value[key]).filter(Boolean),
);
function keyOf(row: AdminRow): string {
return String(row[props.rowKey] ?? "");
}
function isSelected(row: AdminRow): boolean {
return selectedKeys.value.includes(keyOf(row));
}
function getTitle(row: AdminRow): string {
return String(row[titleProp.value] ?? "");
}
function getSubtitles(): PickColumn[] {
return props.columns.filter(col => col.prop !== titleProp.value);
}
// ===== 数据加载 =====
async function loadData(reset = false): Promise<void> {
if (loading.value) {
return;
}
loading.value = true;
if (reset) {
page.value = 1;
rows.value = [];
total.value = 0;
}
try {
const result = await props.loader(page.value, props.pageSize, keyword.value, props.fixedFilters);
if (reset) {
rows.value = result.rows;
}
else {
rows.value = [...rows.value, ...result.rows];
}
total.value = result.total;
}
catch {
// 错误由 http.interceptor 统一提示
}
finally {
loading.value = false;
}
}
function loadMore(): void {
if (!hasMore.value || loading.value) {
return;
}
page.value += 1;
loadData();
}
function onSearch(): void {
loadData(true);
}
// ===== 选择操作 =====
function toggleSelect(row: AdminRow): void {
const key = keyOf(row);
if (!props.multiple) {
// 单选:直接确认
emit("confirm", [row]);
closePopup();
return;
}
const idx = selectedKeys.value.indexOf(key);
const pool = { ...selectedPool.value };
if (idx >= 0) {
selectedKeys.value = selectedKeys.value.filter(k => k !== key);
delete pool[key];
}
else {
selectedKeys.value = [...selectedKeys.value, key];
pool[key] = row;
}
selectedPool.value = pool;
}
function toggleSelectAll(): void {
if (!props.multiple) {
return;
}
const visibleKeys = rows.value.map(row => keyOf(row));
const pool = { ...selectedPool.value };
if (selectedKeys.value.length >= rows.value.length && rows.value.length > 0) {
// 取消全选(仅当前可见)
selectedKeys.value = selectedKeys.value.filter(k => !visibleKeys.includes(k));
for (const key of visibleKeys) {
delete pool[key];
}
}
else {
for (const row of rows.value) {
pool[keyOf(row)] = row;
}
selectedKeys.value = [...new Set([...selectedKeys.value, ...visibleKeys])];
}
selectedPool.value = pool;
}
function onConfirm(): void {
if (!selectedRows.value.length) {
uni.showToast({ title: t("AdminPage.pickNoneTip"), icon: "none" });
return;
}
emit("confirm", selectedRows.value);
closePopup();
}
// ===== 生命周期 =====
function resetState(): void {
rows.value = [];
total.value = 0;
page.value = 1;
keyword.value = "";
selectedKeys.value = [];
selectedPool.value = {};
loading.value = false;
}
function closePopup(): void {
emit("update:visible", false);
emit("close");
}
// 每次展开重置并加载
watch(
() => props.visible,
(on) => {
if (on) {
resetState();
loadData(true);
}
},
);
</script>
<template>
<u-popup :model-value="props.visible" mode="bottom" round="24" :safe-area-inset-bottom="true"
:close-on-click-overlay="true" @close="closePopup">
<view class="pick-popup">
<!-- 头部:标题 + 关闭 -->
<view class="pick-popup__header">
<text class="pick-popup__title">
{{ props.title }}
</text>
<view class="pick-popup__close" @tap="closePopup">
<u-icon name="close-circle-fill" size="40" color="#9aa0a6" />
</view>
</view>
<!-- 搜索栏 -->
<view class="pick-popup__search">
<u-search v-model="keyword" :placeholder="t('AdminPage.searchPlaceholder')" :show-action="false"
:animation="false" @search="onSearch" @clear="onSearch" />
</view>
<!-- 列表 -->
<scroll-view scroll-y class="pick-popup__list" @scrolltolower="loadMore">
<!-- 加载中占位(首次) -->
<view v-if="loading && !rows.length" class="pick-popup__loading">
<u-loading mode="circle" />
</view>
<!-- 空态 -->
<view v-else-if="!rows.length" class="pick-popup__empty">
<text>{{ t("AdminPage.emptyText") }}</text>
</view>
<!-- 数据行 -->
<template v-else>
<view v-for="row in rows" :key="keyOf(row)" class="pick-popup__row"
:class="{ 'is-selected': isSelected(row) }" @tap="toggleSelect(row)">
<!-- 多选勾选框 -->
<view v-if="props.multiple" class="pick-popup__check" :class="{ 'is-on': isSelected(row) }">
<u-icon v-if="isSelected(row)" name="checkbox-mark" size="24" color="#ffffff" />
</view>
<view class="pick-popup__content">
<text class="pick-popup__main">
{{ getTitle(row) }}
</text>
<view v-for="col in getSubtitles()" :key="col.prop" class="pick-popup__sub">
<text class="pick-popup__sub-label">{{ col.label }}</text>
<text class="pick-popup__sub-value">{{ row[col.prop] ?? "" }}</text>
</view>
</view>
</view>
<!-- 底部加载状态 -->
<view class="pick-popup__footer-hint">
<text v-if="loading">{{ t("AdminPage.pickLoadingMore") }}</text>
<text v-else-if="!hasMore">{{ t("AdminPage.pickNoMore") }}</text>
</view>
</template>
</scroll-view>
<!-- 底部操作栏(多选时) -->
<view v-if="props.multiple" class="pick-popup__actions">
<view class="pick-popup__action-left" @tap="toggleSelectAll">
<text class="pick-popup__toggle">
{{ selectedKeys.length >= rows.length && rows.length > 0 ? t("AdminPage.unselectAllBut") :
t("AdminPage.selectAllBut") }}
</text>
<text class="pick-popup__count">
{{ t("AdminPage.selectedText", { count: selectedKeys.length }) }}
</text>
</view>
<view class="pick-popup__action-btns">
<view class="pick-popup__btn pick-popup__btn--cancel" @tap="closePopup">
<text>{{ t("AdminPage.cancelBut") }}</text>
</view>
<view class="pick-popup__btn pick-popup__btn--confirm"
:class="{ 'is-disabled': !selectedKeys.length }" @tap="onConfirm">
<text>{{ t("AdminPage.confirmBut") }}</text>
</view>
</view>
</view>
</view>
</u-popup>
</template>
<style lang="scss" scoped>
$brand-green: #2f7d5c;
$brand-green-light: #47a678;
.pick-popup {
display: flex;
flex-direction: column;
height: 80vh;
background-color: #f7f8fa;
}
.pick-popup__header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 28rpx 32rpx 16rpx;
background-color: #ffffff;
border-radius: 24rpx 24rpx 0 0;
}
.pick-popup__title {
font-size: 32rpx;
font-weight: 600;
color: #1f2329;
}
.pick-popup__close {
padding: 8rpx;
}
.pick-popup__search {
padding: 12rpx 24rpx;
background-color: #ffffff;
}
.pick-popup__list {
flex: 1;
min-height: 0;
padding: 16rpx 24rpx;
}
.pick-popup__loading,
.pick-popup__empty {
display: flex;
align-items: center;
justify-content: center;
padding: 80rpx 0;
font-size: 26rpx;
color: #9aa0a6;
}
.pick-popup__row {
display: flex;
align-items: center;
gap: 16rpx;
margin-bottom: 12rpx;
padding: 24rpx 28rpx;
border-radius: 16rpx;
background-color: #ffffff;
}
.pick-popup__row.is-selected {
background-color: rgba(47, 125, 92, 0.06);
border: 2rpx solid $brand-green;
}
.pick-popup__check {
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
width: 40rpx;
height: 40rpx;
border: 2rpx solid #c4c8cf;
border-radius: 8rpx;
}
.pick-popup__check.is-on {
background-color: $brand-green;
border-color: $brand-green;
}
.pick-popup__content {
flex: 1;
min-width: 0;
}
.pick-popup__main {
font-size: 28rpx;
font-weight: 500;
color: #1f2329;
}
.pick-popup__sub {
display: flex;
gap: 8rpx;
margin-top: 6rpx;
font-size: 24rpx;
}
.pick-popup__sub-label {
color: #9aa0a6;
}
.pick-popup__sub-value {
color: #606266;
}
.pick-popup__footer-hint {
display: flex;
justify-content: center;
padding: 24rpx 0;
font-size: 24rpx;
color: #9aa0a6;
}
.pick-popup__actions {
display: flex;
align-items: center;
justify-content: space-between;
padding: 20rpx 32rpx;
border-top: 2rpx solid #eee;
background-color: #ffffff;
}
.pick-popup__action-left {
display: flex;
flex-direction: column;
gap: 4rpx;
}
.pick-popup__toggle {
font-size: 26rpx;
color: $brand-green;
}
.pick-popup__count {
font-size: 22rpx;
color: #9aa0a6;
}
.pick-popup__action-btns {
display: flex;
gap: 20rpx;
}
.pick-popup__btn {
padding: 16rpx 40rpx;
border-radius: 40rpx;
font-size: 28rpx;
}
.pick-popup__btn--cancel {
color: #606266;
background-color: #f2f3f5;
}
.pick-popup__btn--confirm {
color: #ffffff;
background: linear-gradient(135deg, $brand-green-light, $brand-green);
}
.pick-popup__btn--confirm.is-disabled {
opacity: 0.5;
}
</style>
@@ -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<string, string>,
) => Promise<{ rows: any[]; total: number }>;
rowKey: string;
titleKey: string;
fixedFilters: Record<string, string>;
}
// ===== Permission =====
export function usePermissionPickConfig() {
const { t } = useLocale();
const fixedFilters = ref<Record<string, string>>({});
const columns = computed<PickColumn[]>(() => [
{
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<Record<string, string>>({});
const columns = computed<PickColumn[]>(() => [
{
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<string, string>,
) {
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<Record<string, string>>({});
const columns = computed<PickColumn[]>(() => [
{
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<Record<string, string>>({});
const columns = computed<PickColumn[]>(() => [
{ 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<Record<string, string>>({});
const columns = computed<PickColumn[]>(() => [
{
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<Record<string, string>>({});
const columns = computed<PickColumn[]>(() => [
{
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<string, string>,
) {
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<Record<string, string>>({});
const columns = computed<PickColumn[]>(() => [
{ 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,
};
}
+5 -1
View File
@@ -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",
+5 -1
View File
@@ -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": "用户查询",
+15 -11
View File
@@ -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 用 `<dragModal>` 内嵌 Enquiry + `provide/inject`。移动端改成**真跳一页再回传**:
### 3.3 选择器:`admin-pick-popup` 页内底部弹窗
旧系统的"选角色/选权限/选功能"是 Operation 用 `<dragModal>` 内嵌 Enquiry + `provide/inject`。移动端第一版改成"真跳一页再回传",但多数选择页 `show-nav-back=false` 且没有取消按钮,用户进去就退不出来。现已改为**页内弹窗**:
- 调用方 `useAdminPick<T>(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` 是造错的概念——它现在只是普通查询页,选择能力在调用方的弹窗里。
---
@@ -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,11 +169,15 @@ 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) => {
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 ?? "");
@@ -185,7 +191,7 @@ function onAddGroup(): void {
data.value.SecurityActorGroups.push(entity);
exists.add(groupName);
}
});
showGroupPicker.value = false;
}
function onDeleteGroup(groupName: string): void {
@@ -194,11 +200,15 @@ 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) => {
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 ?? "");
@@ -212,7 +222,7 @@ function onAddUser(): void {
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) => {
showPermissionPicker.value = true;
}
function onPermissionPicked(rows: any[]): void {
if (rows.length) {
func.value.PermissionName = String(rows[0].PermissionName ?? "");
}
});
showPermissionPicker.value = false;
}
// 上级目录:单选 function-enquiry(锁 FunctionIndc=N),仅当父为空时可加
const parentPicker = useAdminPick("parent");
// 上级目录:单选弹窗(锁 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) => {
parentConfig.fixedFilters.value = { indc: "N" };
showParentPicker.value = true;
}
function onParentPicked(rows: any[]): void {
ensureLists();
if (!rows.length) {
return;
}
if (rows.length) {
const parentName = String(rows[0].FunctionName ?? "");
// 防御:不能把自己设为自己的上级目录(旧跳页版也没挡,弹窗化顺手补上)
if (parentName && parentName !== func.value.FunctionName) {
const parent = new SecurityFunctionMapViewEntity();
parent.ParentFunctionName = String(rows[0].FunctionName ?? "");
parent.ParentFunctionName = parentName;
parent.ParentFunctionLocalName = String(rows[0].FunctionLocalName ?? "");
data.value.SecurityFunctionMapParents.push(parent);
});
}
}
showParentPicker.value = false;
}
function deleteParent(parentFunctionName: string): void {
@@ -219,16 +232,22 @@ 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) => {
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 || exists.has(sonName)) {
// 防御:跳过空、自身(不能把自己挂成自己的下级菜单)、以及已在列表里的
if (!sonName || sonName === func.value.FunctionName || exists.has(sonName)) {
continue;
}
const son = new SecurityFunctionMapViewEntity();
@@ -238,7 +257,7 @@ function addSon(): void {
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,11 +198,15 @@ 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) => {
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) {
@@ -218,7 +223,7 @@ function addPermission(): void {
data.value.SecurityGroupPermissionViews.push(ent);
exists.add(permissionName);
}
});
showPermissionPicker.value = false;
}
function deletePermission(permissionName: string): void {
@@ -231,16 +236,19 @@ 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) => {
dependentConfig.fixedFilters.value = { dependent };
showDependentPicker.value = true;
}
function onDependentPicked(rows: any[]): void {
ensureLists();
const exists = new Set(
data.value.SecurityGroupPermissionDependentViews.map(item => item.DependentDetailName),
@@ -260,8 +268,7 @@ function addPermissionDependent(dependent: string): void {
data.value.SecurityGroupPermissionDependentViews.push(ent);
exists.add(detailName);
}
},
);
showDependentPicker.value = false;
}
function deletePermissionDependent(detailName: string): void {
@@ -270,11 +277,15 @@ 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) => {
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) {
@@ -289,7 +300,7 @@ function addActor(): void {
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,25 +214,33 @@ 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) => {
showFunctionPicker.value = true;
}
function onFunctionPicked(rows: any[]): void {
if (rows.length) {
helpFunction.value.FunctionName = String(rows[0].FunctionName ?? "");
}
});
showFunctionPicker.value = false;
}
// 引用的功能点组:单选 help-feature-enquiry,回传功能点后翻译成组编码
const groupPicker = useAdminPick("group");
// 引用的功能点组:单选弹窗,回传功能点后翻译成组编码
const featureConfig = useHelpFeaturePickConfig();
const showFeaturePicker = ref(false);
function addFeature(): void {
groupPicker.open({ page: "helpFeatureEnquiry" }, (rows) => {
showFeaturePicker.value = true;
}
function onFeaturePicked(rows: any[]): void {
ensureLists();
if (!rows.length) {
return;
@@ -250,7 +259,7 @@ function addFeature(): void {
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,13 +175,15 @@ 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) => {
showActorPicker.value = true;
}
function onActorPicked(rows: any[]): void {
const exists = new Set(
data.value.SecurityUserActors.map(item => item.ActorName),
);
@@ -196,8 +199,7 @@ function onAddActor(): void {
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">