修复Vue Admin布局中RouterView未绑定唯一key的问题,解决RBAC系统资源页面切换后内容不刷新的ISSUE-001故障,新增routeViewKey工具函数区分路由实例 完善路由配置,新增admin空路径路由,修复路由命名空路径告警 新增媒体管理、运维发布/重置页面,补全全站路由注册与动态路由过滤逻辑 新增玩法、线索、管家、系统资源管理的全套工具函数、业务组件及单元测试用例 修复后端app/seed.py中媒体资源重复检测未考虑会话内pending资源的bug,新增对应测试验证修复逻辑 修正alembic迁移版本0015的依赖关系,删除废弃的旧迁移文件 优化后端系统接口的字典序列化逻辑,简化重复代码 添加QA验收报告与相关测试截图,完善项目测试覆盖
168 lines
9.2 KiB
Vue
168 lines
9.2 KiB
Vue
<script setup lang="ts">
|
|
import { computed, reactive, ref, watch } from "vue";
|
|
import type {
|
|
SystemDeptItem,
|
|
SystemMenuItem,
|
|
SystemRoleItem,
|
|
SystemUserItem,
|
|
} from "@/types";
|
|
|
|
export type SystemResourceName = "users" | "roles" | "menus" | "depts";
|
|
|
|
type EditorItem = SystemUserItem | SystemRoleItem | SystemMenuItem | SystemDeptItem;
|
|
|
|
export type SystemEditorSavePayload = {
|
|
resource: SystemResourceName;
|
|
mode: "create" | "edit";
|
|
input: Record<string, unknown>;
|
|
};
|
|
|
|
const props = defineProps<{
|
|
modelValue: boolean;
|
|
resource: SystemResourceName;
|
|
mode: "create" | "edit";
|
|
item: EditorItem | null;
|
|
roles: SystemRoleItem[];
|
|
menus: SystemMenuItem[];
|
|
depts: SystemDeptItem[];
|
|
}>();
|
|
|
|
const emit = defineEmits<{
|
|
(event: "update:modelValue", value: boolean): void;
|
|
(event: "save", payload: SystemEditorSavePayload): void;
|
|
}>();
|
|
|
|
type FormState = {
|
|
email: string;
|
|
name: string;
|
|
password: string;
|
|
isActive: boolean;
|
|
roleIds: string[];
|
|
deptIds: string[];
|
|
menuIds: string[];
|
|
code: string;
|
|
dataScope: SystemRoleItem["dataScope"];
|
|
description: string;
|
|
parentId: string | null;
|
|
type: SystemMenuItem["type"];
|
|
path: string;
|
|
componentKey: string;
|
|
permissionCode: string;
|
|
icon: string;
|
|
sortOrder: number;
|
|
isVisible: boolean;
|
|
};
|
|
|
|
function blankForm(): FormState {
|
|
return {
|
|
email: "",
|
|
name: "",
|
|
password: "",
|
|
isActive: true,
|
|
roleIds: [],
|
|
deptIds: [],
|
|
menuIds: [],
|
|
code: "",
|
|
dataScope: "self",
|
|
description: "",
|
|
parentId: null,
|
|
type: "page",
|
|
path: "",
|
|
componentKey: "",
|
|
permissionCode: "",
|
|
icon: "",
|
|
sortOrder: 0,
|
|
isVisible: true,
|
|
};
|
|
}
|
|
|
|
const form = reactive<FormState>(blankForm());
|
|
const formRef = ref<{ validate: () => Promise<boolean> } | null>(null);
|
|
const title = computed(() => `${props.mode === "create" ? "新增" : "编辑"}${({ users: "用户", roles: "角色", menus: "菜单", depts: "部门" } as Record<SystemResourceName, string>)[props.resource]}`);
|
|
const menuParents = computed(() => props.menus.filter((item) => item.id !== props.item?.id && item.type !== "button"));
|
|
|
|
function syncForm(item: EditorItem | null) {
|
|
Object.assign(form, blankForm());
|
|
if (!item) return;
|
|
if (props.resource === "users") {
|
|
const user = item as SystemUserItem;
|
|
Object.assign(form, { email: user.email, name: user.name, isActive: user.isActive !== false, roleIds: [...user.roleIds], deptIds: [...user.deptIds] });
|
|
} else if (props.resource === "roles") {
|
|
const role = item as SystemRoleItem;
|
|
Object.assign(form, { name: role.name, code: role.code, dataScope: role.dataScope, description: role.description || "", isActive: role.isActive, menuIds: [...role.menuIds], deptIds: [...role.deptIds] });
|
|
} else if (props.resource === "menus") {
|
|
const menu = item as SystemMenuItem;
|
|
Object.assign(form, { name: menu.name, parentId: menu.parentId || null, type: menu.type, path: menu.path || "", componentKey: menu.component || "", permissionCode: menu.permission || "", icon: menu.icon || "", sortOrder: menu.sortOrder, isVisible: menu.isVisible, isActive: menu.isActive });
|
|
} else {
|
|
const dept = item as SystemDeptItem;
|
|
Object.assign(form, { name: dept.name, parentId: dept.parentId || null, sortOrder: dept.sortOrder, isActive: dept.isActive });
|
|
}
|
|
}
|
|
|
|
watch(() => [props.item, props.resource, props.mode], () => syncForm(props.item), { immediate: true });
|
|
|
|
async function submit() {
|
|
if (formRef.value && !(await formRef.value.validate())) return;
|
|
const input: Record<string, unknown> = { ...form };
|
|
if (props.mode === "edit") {
|
|
delete input.email;
|
|
if (props.resource === "users" && !String(input.password || "").trim()) delete input.password;
|
|
}
|
|
if (props.resource === "menus") {
|
|
input.componentKey = form.componentKey || null;
|
|
input.permissionCode = form.permissionCode || null;
|
|
input.path = form.path || null;
|
|
input.parentId = form.parentId || null;
|
|
}
|
|
if (props.resource === "depts") input.parentId = form.parentId || null;
|
|
if (props.resource === "roles") input.description = form.description || null;
|
|
emit("save", { resource: props.resource, mode: props.mode, input });
|
|
}
|
|
</script>
|
|
|
|
<template>
|
|
<el-dialog :model-value="modelValue" :title="title" width="720px" destroy-on-close @update:model-value="emit('update:modelValue', $event)">
|
|
<el-form ref="formRef" :model="form" label-width="110px" class="system-editor-form">
|
|
<template v-if="resource === 'users'">
|
|
<el-form-item label="登录邮箱" prop="email" required><el-input v-model="form.email" :disabled="mode === 'edit'" /></el-form-item>
|
|
<el-form-item label="姓名" prop="name" required><el-input v-model="form.name" maxlength="80" /></el-form-item>
|
|
<el-form-item label="密码" prop="password" :required="mode === 'create'"><el-input v-model="form.password" type="password" show-password :placeholder="mode === 'edit' ? '留空表示不修改' : '至少 8 位'" /></el-form-item>
|
|
<el-form-item label="角色"><el-select v-model="form.roleIds" multiple filterable class="full-width"><el-option v-for="role in roles" :key="role.id" :label="`${role.name} (${role.code})`" :value="role.id" /></el-select></el-form-item>
|
|
<el-form-item label="部门"><el-select v-model="form.deptIds" multiple filterable class="full-width"><el-option v-for="dept in depts" :key="dept.id" :label="dept.name" :value="dept.id" /></el-select></el-form-item>
|
|
<el-form-item label="状态"><el-switch v-model="form.isActive" active-text="启用" inactive-text="停用" /></el-form-item>
|
|
</template>
|
|
|
|
<template v-else-if="resource === 'roles'">
|
|
<el-form-item label="角色名称" required><el-input v-model="form.name" maxlength="80" /></el-form-item>
|
|
<el-form-item label="角色编码" required><el-input v-model="form.code" maxlength="80" /></el-form-item>
|
|
<el-form-item label="数据范围"><el-select v-model="form.dataScope" class="full-width"><el-option label="全部数据" value="all" /><el-option label="当前部门" value="dept" /><el-option label="当前部门及子部门" value="dept_and_children" /><el-option label="自定义部门" value="custom_dept" /><el-option label="本人数据" value="self" /></el-select></el-form-item>
|
|
<el-form-item label="说明"><el-input v-model="form.description" type="textarea" :rows="3" maxlength="500" /></el-form-item>
|
|
<el-form-item label="菜单权限"><el-select v-model="form.menuIds" multiple filterable class="full-width"><el-option v-for="menu in menus" :key="menu.id" :label="`${menu.name} (${menu.permission || menu.type})`" :value="menu.id" /></el-select></el-form-item>
|
|
<el-form-item label="部门范围"><el-select v-model="form.deptIds" multiple filterable class="full-width"><el-option v-for="dept in depts" :key="dept.id" :label="dept.name" :value="dept.id" /></el-select></el-form-item>
|
|
<el-form-item label="状态"><el-switch v-model="form.isActive" active-text="启用" inactive-text="停用" /></el-form-item>
|
|
</template>
|
|
|
|
<template v-else-if="resource === 'menus'">
|
|
<el-form-item label="菜单名称" required><el-input v-model="form.name" maxlength="80" /></el-form-item>
|
|
<el-form-item label="菜单类型" required><el-select v-model="form.type" class="full-width"><el-option label="目录" value="directory" /><el-option label="页面" value="page" /><el-option label="按钮" value="button" /></el-select></el-form-item>
|
|
<el-form-item label="父级菜单"><el-select v-model="form.parentId" clearable class="full-width"><el-option v-for="menu in menuParents" :key="menu.id" :label="menu.name" :value="menu.id" /></el-select></el-form-item>
|
|
<el-form-item label="路径"><el-input v-model="form.path" placeholder="/example" /></el-form-item>
|
|
<el-form-item label="组件白名单"><el-input v-model="form.componentKey" placeholder="HomePage" /></el-form-item>
|
|
<el-form-item label="权限编码"><el-input v-model="form.permissionCode" placeholder="admin:example:read" /></el-form-item>
|
|
<el-form-item label="图标"><el-input v-model="form.icon" /></el-form-item>
|
|
<el-form-item label="排序"><el-input-number v-model="form.sortOrder" :min="0" /></el-form-item>
|
|
<el-form-item label="显示状态"><el-switch v-model="form.isVisible" active-text="显示" inactive-text="隐藏" /></el-form-item>
|
|
<el-form-item label="启用状态"><el-switch v-model="form.isActive" active-text="启用" inactive-text="停用" /></el-form-item>
|
|
</template>
|
|
|
|
<template v-else>
|
|
<el-form-item label="部门名称" required><el-input v-model="form.name" maxlength="120" /></el-form-item>
|
|
<el-form-item label="父级部门"><el-select v-model="form.parentId" clearable class="full-width"><el-option v-for="dept in depts.filter((item) => item.id !== props.item?.id)" :key="dept.id" :label="dept.name" :value="dept.id" /></el-select></el-form-item>
|
|
<el-form-item label="排序"><el-input-number v-model="form.sortOrder" :min="0" /></el-form-item>
|
|
<el-form-item label="状态"><el-switch v-model="form.isActive" active-text="启用" inactive-text="停用" /></el-form-item>
|
|
</template>
|
|
</el-form>
|
|
<template #footer><el-button @click="emit('update:modelValue', false)">取消</el-button><el-button type="primary" @click="submit">保存</el-button></template>
|
|
</el-dialog>
|
|
</template>
|