347 lines
11 KiB
Vue
347 lines
11 KiB
Vue
<script setup lang="ts">
|
||
/**
|
||
* 用户详情(查看 / 新增 / 修改):旧 WebVue views/security/UserOperation.vue 的移动端等价物
|
||
*
|
||
* 逐条对齐旧逻辑:
|
||
* - onLoad 读 id(=UserName)与 mode(view|create|amend);
|
||
* - FindByKey 取 SecurityUserData,取不到提示"记录不存在";
|
||
* - 保存按 CreatedBy 有无决定 Update / Insert,成功后把主键回填、退回只读;
|
||
* - 密码编辑的是 SecurityUserDisplayData.OrgUserPassword(留空即不改);
|
||
* - 角色明细按 ActorName 去重合并,删除即从数组里剔除。
|
||
*
|
||
* 旧页面的"选角色"是弹窗内嵌 Enquiry + provide/inject;这里改成真跳 actor-enquiry 页
|
||
* 再回传(见 composables/useAdminPick),所以角色区放在 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 { SecurityUserActorViewEntity } from "@/data/auto/securityUserActorViewEntity";
|
||
import { SecurityUserData } from "@/data/auto/securityUserData";
|
||
import { usePagePermission } from "@/permission/permission-context";
|
||
import { smartBack } from "@/router/routes";
|
||
import securityUserService from "@/service/security/securityUserService";
|
||
|
||
const { t } = useLocale();
|
||
const perm = usePagePermission("userOperation");
|
||
|
||
const data = ref<SecurityUserData>(new SecurityUserData());
|
||
const user = computed(() => data.value.SecurityUser);
|
||
const readonly = ref(true);
|
||
const routeId = ref("");
|
||
|
||
/** 新增态判定:没有 CreatedBy 就说明是还没落库的新记录 */
|
||
const isNew = computed(() => !String(user.value.CreatedBy ?? "").trim());
|
||
|
||
const heroTitle = computed(
|
||
() => String(user.value.UserLocalName || user.value.UserName || ""),
|
||
);
|
||
const heroChips = computed(() => {
|
||
const chips: string[] = [];
|
||
if (user.value.UserName) {
|
||
chips.push(`${t("UserPage.account")}: ${user.value.UserName}`);
|
||
}
|
||
if (user.value.UserMobile) {
|
||
chips.push(`${t("UserPage.userMobile")}: ${user.value.UserMobile}`);
|
||
}
|
||
return chips;
|
||
});
|
||
|
||
const sections = computed<AdminFormSection[]>(() => {
|
||
const fields: AdminFormField[] = [
|
||
{
|
||
label: t("UserPage.userName"),
|
||
prop: "UserName",
|
||
required: true,
|
||
// 已有记录时主键锁死:与旧页面"编辑态 UserName 只读"一致
|
||
lock: routeId.value !== "",
|
||
},
|
||
{ label: t("UserPage.userLocalName"), prop: "UserLocalName", required: true },
|
||
{ label: t("UserPage.userEngName"), prop: "UserEngName" },
|
||
{ label: t("UserPage.userMobile"), prop: "UserMobile" },
|
||
{ label: t("UserPage.createdDatetime"), prop: "CreatedDatetime", date: "datetime", lock: true },
|
||
];
|
||
// 密码只在编辑态出现:查看态没必要显示(即便是掩码)
|
||
if (!readonly.value) {
|
||
fields.push({
|
||
label: t("UserPage.userPassword"),
|
||
prop: "OrgUserPassword",
|
||
type: "password",
|
||
placeholder: t("UserPage.passwordTip"),
|
||
});
|
||
}
|
||
return [{ title: t("UserPage.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;
|
||
});
|
||
|
||
async function load(): Promise<void> {
|
||
const result = await securityUserService.FindByKey(routeId.value);
|
||
if (!result?.SecurityUser) {
|
||
uni.showToast({ title: t("AdminPage.notExistTip"), icon: "none" });
|
||
return;
|
||
}
|
||
data.value = result;
|
||
// 子表数组统一兑底:服务端 FindByKey/保存返回可能缺字段,否则模板读 .length 会报 undefined
|
||
if (!data.value.SecurityUserActors) {
|
||
data.value.SecurityUserActors = [];
|
||
}
|
||
}
|
||
|
||
/** 字段改写:admin-form 不直接改 props.model,统一回抛到这里 */
|
||
function onFieldChange(prop: string, value: unknown): void {
|
||
(user.value as Record<string, any>)[prop] = value;
|
||
}
|
||
|
||
async function onSave(): Promise<void> {
|
||
if (!String(user.value.UserName ?? "").trim()) {
|
||
uni.showToast({ title: t("UserPage.userNameRequired"), icon: "none" });
|
||
return;
|
||
}
|
||
if (!String(user.value.UserLocalName ?? "").trim()) {
|
||
uni.showToast({ title: t("UserPage.localNameRequired"), icon: "none" });
|
||
return;
|
||
}
|
||
const isUpdate = !isNew.value;
|
||
const result = await (isUpdate
|
||
? securityUserService.Update(data.value)
|
||
: securityUserService.Insert(data.value));
|
||
// 后端回写后的档案才是准的(新记录拿到了 CreatedBy)
|
||
if (result?.SecurityUser) {
|
||
data.value = result;
|
||
}
|
||
if (!data.value.SecurityUserActors) {
|
||
data.value.SecurityUserActors = [];
|
||
}
|
||
routeId.value = String(user.value.UserName ?? "");
|
||
readonly.value = true;
|
||
uni.showToast({ title: t("AdminPage.savedTip"), icon: "none" });
|
||
}
|
||
|
||
async function onDelete(): Promise<void> {
|
||
await securityUserService.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");
|
||
}
|
||
|
||
// 角色选择:跳 actor-enquiry(可多选),回传后按 ActorName 去重合并
|
||
const actorPicker = useAdminPick<SecurityUserActorViewEntity>("actor");
|
||
|
||
function onAddActor(): void {
|
||
actorPicker.open(
|
||
{ page: "actorEnquiry", params: { multiple: "1" } },
|
||
(rows) => {
|
||
const exists = new Set(
|
||
data.value.SecurityUserActors.map(item => item.ActorName),
|
||
);
|
||
for (const row of rows) {
|
||
const actorName = String(row.ActorName ?? "");
|
||
if (!actorName || exists.has(actorName)) {
|
||
continue;
|
||
}
|
||
const entity = new SecurityUserActorViewEntity();
|
||
entity.UserName = String(user.value.UserName ?? "");
|
||
entity.ActorName = actorName;
|
||
entity.ActorLocalName = String(row.ActorLocalName ?? "");
|
||
data.value.SecurityUserActors.push(entity);
|
||
exists.add(actorName);
|
||
}
|
||
},
|
||
);
|
||
}
|
||
|
||
function onDeleteActor(actorName: string): void {
|
||
data.value.SecurityUserActors = data.value.SecurityUserActors.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('UserPage.detailTitle')" :show-nav-back="true" :hero-title="heroTitle"
|
||
:hero-chips="heroChips" :sections="sections" :model="user" :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("UserPage.actorTitle") }}
|
||
</text>
|
||
<view v-if="!ro && perm.canAmend.value" class="group-add" @click="onAddActor">
|
||
<u-icon name="plus" size="24" color="#059669" />
|
||
<text class="group-add-text">
|
||
{{ t("UserPage.addActorBut") }}
|
||
</text>
|
||
</view>
|
||
</view>
|
||
|
||
<view class="group-card">
|
||
<view v-if="!data.SecurityUserActors.length" class="group-empty">
|
||
<text>
|
||
{{ t("UserPage.actorEmpty") }}
|
||
</text>
|
||
</view>
|
||
|
||
<view v-for="actor in data.SecurityUserActors" :key="actor.ActorName" class="actor-row">
|
||
<view class="actor-text">
|
||
<text class="actor-name">
|
||
{{ actor.ActorLocalName || actor.ActorName || t("UserPage.unnamedActor") }}
|
||
</text>
|
||
<text v-if="actor.ActorLocalName" class="actor-sub">
|
||
{{ actor.ActorName }}
|
||
</text>
|
||
</view>
|
||
<u-icon v-if="!ro && perm.canAmend.value" name="trash" size="34" color="#c4c8cf"
|
||
@click="onDeleteActor(actor.ActorName)" />
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</template>
|
||
</admin-form>
|
||
</template>
|
||
|
||
<style lang="scss" scoped>
|
||
/* 角色明细:与 admin-form 的分组卡同一套视觉(细标题 + 白卡 + 行间细线) */
|
||
.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;
|
||
}
|
||
|
||
.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;
|
||
}
|
||
}
|
||
|
||
.actor-text {
|
||
display: flex;
|
||
flex-direction: column;
|
||
min-width: 0;
|
||
}
|
||
|
||
.actor-name {
|
||
font-size: 28rpx;
|
||
color: $u-main-color;
|
||
}
|
||
|
||
.actor-sub {
|
||
margin-top: 4rpx;
|
||
font-size: 22rpx;
|
||
color: $u-tips-color;
|
||
}
|
||
</style>
|