Files
WonderQ-Project/WonderQ-Admin-UI-Vue/src/components/system/SystemResourceEditor.vue
T
duanshuwen efab6d1398 feat(admin): 新增全套运营管理功能,修复核心路由bug及后端seed问题
修复Vue Admin布局中RouterView未绑定唯一key的问题,解决RBAC系统资源页面切换后内容不刷新的ISSUE-001故障,新增routeViewKey工具函数区分路由实例
完善路由配置,新增admin空路径路由,修复路由命名空路径告警
新增媒体管理、运维发布/重置页面,补全全站路由注册与动态路由过滤逻辑
新增玩法、线索、管家、系统资源管理的全套工具函数、业务组件及单元测试用例
修复后端app/seed.py中媒体资源重复检测未考虑会话内pending资源的bug,新增对应测试验证修复逻辑
修正alembic迁移版本0015的依赖关系,删除废弃的旧迁移文件
优化后端系统接口的字典序列化逻辑,简化重复代码
添加QA验收报告与相关测试截图,完善项目测试覆盖
2026-08-26 18:48:48 +08:00

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>