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

598 lines
21 KiB
Vue

<script setup lang="ts">
/**
* 组别详情(查看 / 新增 / 修改):旧 WebVue views/security/GroupOperation.vue 的移动端等价物
*
* 三个子表(旧页用 dragModal 内嵌 Enquiry + provide/inject,移动端改成真跳选择页再回传):
* 1. 权限明细 SecurityGroupPermissionViews —— 选 permission-enquiry(多选),
* 每行带 查看/新增/修改/删除 四个勾选(对齐旧 GroupPermissionActions 的四个 checkbox);
* 删除权限行会连同其扩展权限明细一起剔除(旧 deletePermission 同款)。
* 2. 扩展权限明细 SecurityGroupPermissionDependentViews —— 由权限行的"扩展名称"点开
* permission-dependent-enquiry(多选、按 dependent 过滤)补入;
* 3. 组别内角色群 SecurityActorGroups —— 选 actor-enquiry(多选)。
* 新增权限/扩展权限行时四个勾选全部置 false(旧页面 ent.IsAmend=...=false)。
* 保存按 CreatedBy 有无决定 Update / Insert(SecurityGroupData)。
*/
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 { SecurityActorGroupViewEntity } from "@/data/auto/securityActorGroupViewEntity";
import { SecurityGroupData } from "@/data/auto/securityGroupData";
import { SecurityGroupPermissionDependentViewDisplayData } from "@/data/auto/securityGroupPermissionDependentViewDisplayData";
import { SecurityGroupPermissionViewDisplayData } from "@/data/auto/securityGroupPermissionViewDisplayData";
import { usePagePermission } from "@/permission/permission-context";
import { smartBack } from "@/router/routes";
import securityGroupService from "@/service/security/securityGroupService";
const { t } = useLocale();
const perm = usePagePermission("groupOperation");
const data = ref<SecurityGroupData>(new SecurityGroupData());
const group = computed(() => data.value.SecurityGroup);
const readonly = ref(true);
const routeId = ref("");
const isNew = computed(() => !String(group.value.CreatedBy ?? "").trim());
const heroTitle = computed(
() => String(group.value.GroupLocalName || group.value.GroupName || ""),
);
const heroChips = computed(() => {
const chips: string[] = [];
if (group.value.GroupName) {
chips.push(`${t("GroupPage.groupName")}: ${group.value.GroupName}`);
}
return chips;
});
const sections = computed<AdminFormSection[]>(() => {
const fields: AdminFormField[] = [
{
label: t("GroupPage.groupName"),
prop: "GroupName",
required: true,
lock: routeId.value !== "",
},
{ label: t("GroupPage.groupLocalName"), prop: "GroupLocalName", required: true },
{ label: t("GroupPage.groupEngName"), prop: "GroupEngName" },
];
return [{ title: t("GroupPage.detailTitle"), fields }];
});
const actions = computed<AdminFormAction[]>(() => {
const list: AdminFormAction[] = [];
if (perm.canAmend.value) {
list.push({ key: "amend", label: t("AdminPage.amendBut"), when: "view" });
}
if (perm.canDelete.value) {
list.push({
key: "delete",
label: t("AdminPage.deleteBut"),
tone: "error",
confirm: true,
when: "view",
});
}
list.push({ key: "save", label: t("AdminPage.saveBut"), when: "edit" });
list.push({ key: "cancel", label: t("AdminPage.cancelBut"), when: "edit" });
return list;
});
function ensureLists(): void {
if (!data.value.SecurityGroupPermissionViews) {
data.value.SecurityGroupPermissionViews = [];
}
if (!data.value.SecurityGroupPermissionDependentViews) {
data.value.SecurityGroupPermissionDependentViews = [];
}
if (!data.value.SecurityActorGroups) {
data.value.SecurityActorGroups = [];
}
}
async function load(): Promise<void> {
const result = await securityGroupService.FindByKey(routeId.value);
if (!result?.SecurityGroup?.GroupName) {
uni.showToast({ title: t("AdminPage.notExistTip"), icon: "none" });
return;
}
data.value = result;
ensureLists();
}
function onFieldChange(prop: string, value: unknown): void {
(group.value as Record<string, any>)[prop] = value;
}
async function onSave(): Promise<void> {
if (!String(group.value.GroupName ?? "").trim()) {
uni.showToast({ title: t("GroupPage.nameRequired"), icon: "none" });
return;
}
if (!String(group.value.GroupLocalName ?? "").trim()) {
uni.showToast({ title: t("GroupPage.localNameRequired"), icon: "none" });
return;
}
ensureLists();
const isUpdate = !isNew.value;
const result = await (isUpdate
? securityGroupService.Update(data.value)
: securityGroupService.Insert(data.value));
if (result?.SecurityGroup) {
data.value = result;
ensureLists();
}
routeId.value = String(group.value.GroupName ?? "");
readonly.value = true;
uni.showToast({ title: t("AdminPage.savedTip"), icon: "none" });
}
async function onDelete(): Promise<void> {
await securityGroupService.Delete(routeId.value);
uni.showToast({ title: t("AdminPage.deletedTip"), icon: "none" });
setTimeout(() => smartBack("mineTab"), 600);
}
function onAction(action: AdminFormAction): void {
switch (action.key) {
case "amend":
readonly.value = false;
break;
case "save":
onSave();
break;
case "delete":
onDelete();
break;
case "cancel":
if (isNew.value || !routeId.value) {
smartBack("mineTab");
return;
}
readonly.value = true;
load();
break;
default:
break;
}
}
function onBack(): void {
smartBack("mineTab");
}
// 权限勾选:四个 boolean 直接落在 DisplayData 上(与旧 GroupPermissionActions 一致)
function toggleAction(record: SecurityGroupPermissionViewDisplayData, key: "IsView" | "IsCreate" | "IsAmend" | "IsDelete"): void {
if (readonly.value || !perm.canAmend.value) {
return;
}
record[key] = !record[key];
}
function toggleDependentAction(
record: SecurityGroupPermissionDependentViewDisplayData,
key: "IsView" | "IsCreate" | "IsAmend" | "IsDelete",
): void {
if (readonly.value || !perm.canAmend.value) {
return;
}
record[key] = !record[key];
}
const ACTION_KEYS: Array<"IsView" | "IsCreate" | "IsAmend" | "IsDelete"> = [
"IsView",
"IsCreate",
"IsAmend",
"IsDelete",
];
function actionLabel(key: string): string {
const map: Record<string, string> = {
IsView: t("GroupPage.actionView"),
IsCreate: t("GroupPage.actionCreate"),
IsAmend: t("GroupPage.actionAmend"),
IsDelete: t("GroupPage.actionDelete"),
};
return map[key] ?? key;
}
// 权限选择:跳 permission-enquiry(多选),按 PermissionName 去重
const permissionPicker = useAdminPick("permission");
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);
}
});
}
function deletePermission(permissionName: string): void {
data.value.SecurityGroupPermissionViews = data.value.SecurityGroupPermissionViews.filter(
item => item.PermissionName !== permissionName,
);
// 旧 deletePermission:删权限行时连带删掉它下面的扩展权限明细
data.value.SecurityGroupPermissionDependentViews = data.value.SecurityGroupPermissionDependentViews.filter(
item => item.PermissionName !== permissionName,
);
}
// 扩展权限选择:跳 permission-dependent-enquiry(多选、按当前权限行的 dependent 过滤)
const dependentPicker = useAdminPick("dependent");
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);
}
},
);
}
function deletePermissionDependent(detailName: string): void {
data.value.SecurityGroupPermissionDependentViews = data.value.SecurityGroupPermissionDependentViews.filter(
item => item.DependentDetailName !== detailName,
);
}
// 角色选择:跳 actor-enquiry(多选),按 ActorName 去重
const actorPicker = useAdminPick("actor");
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);
}
});
}
function deleteActor(actorName: string): void {
data.value.SecurityActorGroups = data.value.SecurityActorGroups.filter(
item => item.ActorName !== actorName,
);
}
onLoad((options) => {
routeId.value = String(options?.id ?? "");
const mode = String(options?.mode ?? "view");
readonly.value = mode !== "create" && mode !== "amend";
});
let booted = false;
useAuthGuard(() => {
if (booted) {
return;
}
booted = true;
if (routeId.value) {
load();
}
});
</script>
<template>
<admin-form :nav-title="t('GroupPage.detailTitle')" :show-nav-back="true" :hero-title="heroTitle"
:hero-chips="heroChips" :sections="sections" :model="group" :readonly="readonly" :actions="actions"
@back="onBack" @action="onAction" @field-change="onFieldChange">
<template #extra="{ readonly: ro }">
<!-- 权限明细 -->
<view class="group">
<view class="group-head">
<text class="group-title">
{{ t("GroupPage.permissionTitle") }}
</text>
<view v-if="!ro && perm.canAmend.value" class="group-add" @click="addPermission">
<u-icon name="plus" size="24" color="#059669" />
<text class="group-add-text">
{{ t("GroupPage.addPermissionBut") }}
</text>
</view>
</view>
<view class="group-card">
<view v-if="!data.SecurityGroupPermissionViews.length" class="group-empty">
<text>
{{ t("GroupPage.permissionEmpty") }}
</text>
</view>
<view v-for="record in data.SecurityGroupPermissionViews" :key="record.PermissionName"
class="perm-row">
<view class="perm-head">
<view class="perm-text">
<text class="perm-name">
{{ record.PermissionName || t("GroupPage.unnamedPermission") }}
</text>
<text v-if="record.PermissionDescription" class="perm-sub">
{{ record.PermissionDescription }}
</text>
</view>
<u-icon v-if="!ro && perm.canAmend.value" name="trash" size="34" color="#c4c8cf"
@click="deletePermission(record.PermissionName)" />
</view>
<view class="dep-entry" @click="addPermissionDependent(record.Dependent)">
<text class="dep-entry-label">
{{ t("GroupPage.dependentLabel") }}
</text>
<text class="dep-entry-value">
{{ record.Dependent || "-" }}
</text>
</view>
<view class="perm-actions">
<view v-for="key in ACTION_KEYS" :key="key" class="perm-action"
:class="{ 'perm-action-disabled': ro || !perm.canAmend.value }"
@click="toggleAction(record, key)">
<u-icon :name="record[key] ? 'checkmark-circle-fill' : 'checkmark-circle'" size="34"
:color="record[key] ? '#059669' : '#c4c8cf'" />
<text class="perm-action-label">
{{ actionLabel(key) }}
</text>
</view>
</view>
</view>
</view>
</view>
<!-- 扩展权限明细 -->
<view class="group">
<view class="group-head">
<text class="group-title">
{{ t("GroupPage.dependentViewTitle") }}
</text>
</view>
<view class="group-card">
<view v-if="!data.SecurityGroupPermissionDependentViews.length" class="group-empty">
<text>
{{ t("GroupPage.dependentViewEmpty") }}
</text>
</view>
<view v-for="record in data.SecurityGroupPermissionDependentViews" :key="record.DependentDetailName"
class="perm-row">
<view class="perm-head">
<view class="perm-text">
<text class="perm-name">
{{ record.DependentDetailName || t("GroupPage.unnamedDependent") }}
</text>
<text v-if="record.DependentDetailDescription" class="perm-sub">
{{ record.DependentDetailDescription }}
</text>
</view>
<u-icon v-if="!ro && perm.canAmend.value" name="trash" size="34" color="#c4c8cf"
@click="deletePermissionDependent(record.DependentDetailName)" />
</view>
<view class="perm-actions">
<view v-for="key in ACTION_KEYS" :key="key" class="perm-action"
:class="{ 'perm-action-disabled': ro || !perm.canAmend.value }"
@click="toggleDependentAction(record, key)">
<u-icon :name="record[key] ? 'checkmark-circle-fill' : 'checkmark-circle'" size="34"
:color="record[key] ? '#059669' : '#c4c8cf'" />
<text class="perm-action-label">
{{ actionLabel(key) }}
</text>
</view>
</view>
</view>
</view>
</view>
<!-- 组别内角色群 -->
<view class="group">
<view class="group-head">
<text class="group-title">
{{ t("GroupPage.actorTitle") }}
</text>
<view v-if="!ro && perm.canAmend.value" class="group-add" @click="addActor">
<u-icon name="plus" size="24" color="#059669" />
<text class="group-add-text">
{{ t("GroupPage.addActorBut") }}
</text>
</view>
</view>
<view class="group-card">
<view v-if="!data.SecurityActorGroups.length" class="group-empty">
<text>
{{ t("GroupPage.actorEmpty") }}
</text>
</view>
<view v-for="record in data.SecurityActorGroups" :key="record.ActorName" class="actor-row">
<view class="perm-text">
<text class="perm-name">
{{ record.ActorLocalName || record.ActorName || t("GroupPage.unnamedActor") }}
</text>
<text v-if="record.ActorLocalName" class="perm-sub">
{{ record.ActorName }}
</text>
</view>
<u-icon v-if="!ro && perm.canAmend.value" name="trash" size="34" color="#c4c8cf"
@click="deleteActor(record.ActorName)" />
</view>
</view>
</view>
</template>
</admin-form>
</template>
<style lang="scss" scoped>
.group {
margin-bottom: 28rpx;
}
.group-head {
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
padding: 0 12rpx 12rpx;
}
.group-title {
font-size: 24rpx;
color: $u-tips-color;
}
.group-add {
display: flex;
flex-direction: row;
align-items: center;
}
.group-add-text {
margin-left: 4rpx;
font-size: 24rpx;
color: $u-type-primary;
}
.group-card {
padding: 0 28rpx;
overflow: hidden;
background-color: #ffffff;
border-radius: 20rpx;
}
.group-empty {
padding: 40rpx 0;
font-size: 24rpx;
color: $u-tips-color;
text-align: center;
}
.perm-row {
padding: 20rpx 0;
border-bottom: 1rpx solid $u-border-color;
&:last-child {
border-bottom: none;
}
}
.perm-head {
display: flex;
flex-direction: row;
align-items: flex-start;
justify-content: space-between;
}
.perm-text {
display: flex;
flex-direction: column;
min-width: 0;
}
.perm-name {
font-size: 28rpx;
color: $u-main-color;
}
.perm-sub {
margin-top: 4rpx;
font-size: 22rpx;
color: $u-tips-color;
}
.dep-entry {
display: flex;
flex-direction: row;
align-items: center;
margin-top: 10rpx;
}
.dep-entry-label {
margin-right: 12rpx;
font-size: 22rpx;
color: $u-tips-color;
}
.dep-entry-value {
font-size: 24rpx;
color: $u-type-primary;
}
.perm-actions {
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
margin-top: 16rpx;
}
.perm-action {
display: flex;
flex-direction: column;
align-items: center;
}
.perm-action-disabled {
opacity: 0.7;
}
.perm-action-label {
margin-top: 4rpx;
font-size: 22rpx;
color: $u-content-color;
}
.actor-row {
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
min-height: 92rpx;
padding: 16rpx 0;
border-bottom: 1rpx solid $u-border-color;
&:last-child {
border-bottom: none;
}
}
</style>