feat(admin): 新增全套运营管理功能,修复核心路由bug及后端seed问题
修复Vue Admin布局中RouterView未绑定唯一key的问题,解决RBAC系统资源页面切换后内容不刷新的ISSUE-001故障,新增routeViewKey工具函数区分路由实例 完善路由配置,新增admin空路径路由,修复路由命名空路径告警 新增媒体管理、运维发布/重置页面,补全全站路由注册与动态路由过滤逻辑 新增玩法、线索、管家、系统资源管理的全套工具函数、业务组件及单元测试用例 修复后端app/seed.py中媒体资源重复检测未考虑会话内pending资源的bug,新增对应测试验证修复逻辑 修正alembic迁移版本0015的依赖关系,删除废弃的旧迁移文件 优化后端系统接口的字典序列化逻辑,简化重复代码 添加QA验收报告与相关测试截图,完善项目测试覆盖
This commit is contained in:
1 parent
fa6fd2a8eb
commit
efab6d1398
73 files changed
+3963
-788
No files matched your search
@@ -0,0 +1,167 @@
|
||||
<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>
|
||||
Reference in new issue
Block a user