|
@@ -0,0 +1,457 @@
|
|
|
|
|
+<template>
|
|
|
|
|
+ <div class="sub-account-form-container">
|
|
|
|
|
+ <!-- 头部:返回按钮和标题 -->
|
|
|
|
|
+ <div class="header-section">
|
|
|
|
|
+ <el-button :icon="ArrowLeft" text @click="handleBack"> 返回 </el-button>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 表单内容 -->
|
|
|
|
|
+ <div class="form-content">
|
|
|
|
|
+ <el-form ref="subAccountFormRef" :model="subAccountForm" :rules="subAccountFormRules" label-width="120px">
|
|
|
|
|
+ <!-- 基本信息 -->
|
|
|
|
|
+ <div class="form-section">
|
|
|
|
|
+ <h3 class="section-title">基本信息</h3>
|
|
|
|
|
+ <el-form-item label="账号名称" prop="accountName">
|
|
|
|
|
+ <el-input v-model="subAccountForm.accountName" placeholder="请输入" maxlength="50" clearable />
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+ <el-form-item label="手机号" prop="phone">
|
|
|
|
|
+ <el-input v-model="subAccountForm.phone" placeholder="请输入" maxlength="11" clearable />
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 权限 -->
|
|
|
|
|
+ <div class="form-section">
|
|
|
|
|
+ <h3 class="section-title">权限</h3>
|
|
|
|
|
+ <el-form-item label="选择角色" prop="roleId">
|
|
|
|
|
+ <el-select
|
|
|
|
|
+ v-model="subAccountForm.roleId"
|
|
|
|
|
+ placeholder="请选择角色"
|
|
|
|
|
+ clearable
|
|
|
|
|
+ style="width: 100%"
|
|
|
|
|
+ @change="handleRoleChange"
|
|
|
|
|
+ >
|
|
|
|
|
+ <el-option
|
|
|
|
|
+ v-for="role in roleList"
|
|
|
|
|
+ :key="role.roleId || role.id"
|
|
|
|
|
+ :label="role.roleName"
|
|
|
|
|
+ :value="role.roleId || role.id"
|
|
|
|
|
+ />
|
|
|
|
|
+ </el-select>
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 权限列表 -->
|
|
|
|
|
+ <el-form-item label="权限" prop="permissions">
|
|
|
|
|
+ <div class="permission-list">
|
|
|
|
|
+ <template v-if="permissionList.length > 0">
|
|
|
|
|
+ <PermissionItem v-for="permission in permissionList" :key="permission.id" :permission="permission" :level="0" />
|
|
|
|
|
+ </template>
|
|
|
|
|
+ <div v-else class="permission-empty">
|
|
|
|
|
+ <span class="empty-text">暂无权限</span>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </el-form>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 底部按钮 -->
|
|
|
|
|
+ <div class="form-footer">
|
|
|
|
|
+ <el-button @click="handleCancel"> 取消 </el-button>
|
|
|
|
|
+ <el-button type="primary" @click="handleSave" :loading="saveLoading"> 确定 </el-button>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+</template>
|
|
|
|
|
+
|
|
|
|
|
+<script setup lang="ts" name="subAccountCreate">
|
|
|
|
|
+import { ref, reactive, onMounted, computed } from "vue";
|
|
|
|
|
+import { useRouter, useRoute } from "vue-router";
|
|
|
|
|
+import { ElMessage, type FormInstance, type FormRules } from "element-plus";
|
|
|
|
|
+import { ArrowLeft } from "@element-plus/icons-vue";
|
|
|
|
|
+import {
|
|
|
|
|
+ getAllNormalRoles,
|
|
|
|
|
+ getRolePermissionTable,
|
|
|
|
|
+ type RolePermissionItem,
|
|
|
|
|
+ type RoleItem,
|
|
|
|
|
+ createAccountAndAssignRole
|
|
|
|
|
+} from "@/api/modules/accountRoleManagement";
|
|
|
|
|
+import { localGet } from "@/utils";
|
|
|
|
|
+import PermissionItem from "./PermissionItem.vue";
|
|
|
|
|
+// 路由
|
|
|
|
|
+const router = useRouter();
|
|
|
|
|
+const route = useRoute();
|
|
|
|
|
+
|
|
|
|
|
+// 判断是编辑还是创建
|
|
|
|
|
+const accountId = computed(() => route.params.id as string | undefined);
|
|
|
|
|
+const isEdit = computed(() => !!accountId.value);
|
|
|
|
|
+
|
|
|
|
|
+// 表单引用
|
|
|
|
|
+const subAccountFormRef = ref<FormInstance>();
|
|
|
|
|
+
|
|
|
|
|
+// 表单数据
|
|
|
|
|
+const subAccountForm = reactive({
|
|
|
|
|
+ accountName: "",
|
|
|
|
|
+ phone: "",
|
|
|
|
|
+ roleId: undefined as number | undefined,
|
|
|
|
|
+ permissions: [] as number[]
|
|
|
|
|
+});
|
|
|
|
|
+
|
|
|
|
|
+// 表单验证规则
|
|
|
|
|
+const subAccountFormRules: FormRules = {
|
|
|
|
|
+ accountName: [{ required: true, message: "请输入账号名称", trigger: "blur" }],
|
|
|
|
|
+ phone: [
|
|
|
|
|
+ { required: true, message: "请输入手机号", trigger: "blur" },
|
|
|
|
|
+ { pattern: /^1[3-9]\d{9}$/, message: "请输入正确的手机号", trigger: "blur" }
|
|
|
|
|
+ ],
|
|
|
|
|
+ roleId: [{ required: true, message: "请选择角色", trigger: "change" }]
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+// 角色列表
|
|
|
|
|
+const roleList = ref<RoleItem[]>([]);
|
|
|
|
|
+
|
|
|
|
|
+// 权限项接口
|
|
|
|
|
+interface PermissionItem {
|
|
|
|
|
+ id: number;
|
|
|
|
|
+ name: string;
|
|
|
|
|
+ checked: boolean;
|
|
|
|
|
+ expanded: boolean;
|
|
|
|
|
+ children?: PermissionItem[];
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 权限列表
|
|
|
|
|
+const permissionList = ref<PermissionItem[]>([]);
|
|
|
|
|
+
|
|
|
|
|
+// 保存加载状态
|
|
|
|
|
+const saveLoading = ref(false);
|
|
|
|
|
+
|
|
|
|
|
+// 加载角色列表
|
|
|
|
|
+const loadRoleList = async () => {
|
|
|
|
|
+ try {
|
|
|
|
|
+ const storeId = localGet("createdId") || "";
|
|
|
|
|
+ const res = await getAllNormalRoles({ storeId });
|
|
|
|
|
+ const code = typeof res.code === "string" ? parseInt(res.code) : res.code;
|
|
|
|
|
+ if (code === 200) {
|
|
|
|
|
+ // 处理角色数据,确保有 id 或 roleId 字段
|
|
|
|
|
+ const roles = (res.data || []).map((role: any) => ({
|
|
|
|
|
+ ...role,
|
|
|
|
|
+ // 如果后端返回的是 roleId,也确保 id 字段存在
|
|
|
|
|
+ id: role.roleId || role.id,
|
|
|
|
|
+ // 如果后端返回的是 id,也确保 roleId 字段存在
|
|
|
|
|
+ roleId: role.roleId || role.id
|
|
|
|
|
+ }));
|
|
|
|
|
+ roleList.value = roles;
|
|
|
|
|
+ } else {
|
|
|
|
|
+ ElMessage.error(res.msg || "获取角色列表失败");
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ console.error("获取角色列表失败:", error);
|
|
|
|
|
+ ElMessage.error("获取角色列表失败,请重试");
|
|
|
|
|
+ }
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+// 将扁平格式的权限数据转换为树形结构
|
|
|
|
|
+const transformFlatPermissionsToTree = (permissionList: RolePermissionItem[]): PermissionItem[] => {
|
|
|
|
|
+ const level1Map = new Map<string, PermissionItem>();
|
|
|
|
|
+ const level2Map = new Map<string, PermissionItem>();
|
|
|
|
|
+ const level3Map = new Map<string, PermissionItem>();
|
|
|
|
|
+ let idCounter = 1;
|
|
|
|
|
+
|
|
|
|
|
+ // 遍历所有权限项,构建树形结构
|
|
|
|
|
+ permissionList.forEach(item => {
|
|
|
|
|
+ // 处理一级权限
|
|
|
|
|
+ if (item.level1Permission) {
|
|
|
|
|
+ let level1 = level1Map.get(item.level1Permission);
|
|
|
|
|
+
|
|
|
|
|
+ if (!level1) {
|
|
|
|
|
+ level1 = {
|
|
|
|
|
+ id: idCounter++,
|
|
|
|
|
+ name: item.level1Permission,
|
|
|
|
|
+ checked: false,
|
|
|
|
|
+ expanded: true, // 一级权限默认展开
|
|
|
|
|
+ children: []
|
|
|
|
|
+ };
|
|
|
|
|
+ level1Map.set(item.level1Permission, level1);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 处理二级权限
|
|
|
|
|
+ if (item.level2Permission) {
|
|
|
|
|
+ const level2Key = `${item.level1Permission}-${item.level2Permission}`;
|
|
|
|
|
+ let level2 = level2Map.get(level2Key);
|
|
|
|
|
+
|
|
|
|
|
+ if (!level2) {
|
|
|
|
|
+ level2 = {
|
|
|
|
|
+ id: idCounter++,
|
|
|
|
|
+ name: item.level2Permission,
|
|
|
|
|
+ checked: false,
|
|
|
|
|
+ expanded: false,
|
|
|
|
|
+ children: []
|
|
|
|
|
+ };
|
|
|
|
|
+ level2Map.set(level2Key, level2);
|
|
|
|
|
+
|
|
|
|
|
+ // 确保一级权限的children数组存在并添加二级权限
|
|
|
|
|
+ if (!level1.children) {
|
|
|
|
|
+ level1.children = [];
|
|
|
|
|
+ }
|
|
|
|
|
+ // 检查是否已存在,避免重复添加
|
|
|
|
|
+ const existingLevel2 = level1.children.find(child => child.id === level2!.id);
|
|
|
|
|
+ if (!existingLevel2) {
|
|
|
|
|
+ level1.children.push(level2);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 处理三级权限
|
|
|
|
|
+ if (item.level3Permission) {
|
|
|
|
|
+ // 重新获取 level2,确保它存在
|
|
|
|
|
+ const currentLevel2 = level2Map.get(level2Key);
|
|
|
|
|
+
|
|
|
|
|
+ if (currentLevel2) {
|
|
|
|
|
+ const level3Key = `${level2Key}-${item.level3Permission}`;
|
|
|
|
|
+ let level3 = level3Map.get(level3Key);
|
|
|
|
|
+
|
|
|
|
|
+ if (!level3) {
|
|
|
|
|
+ level3 = {
|
|
|
|
|
+ id: idCounter++,
|
|
|
|
|
+ name: item.level3Permission,
|
|
|
|
|
+ checked: false,
|
|
|
|
|
+ expanded: false
|
|
|
|
|
+ };
|
|
|
|
|
+ level3Map.set(level3Key, level3);
|
|
|
|
|
+
|
|
|
|
|
+ // 确保二级权限的children数组存在并添加三级权限
|
|
|
|
|
+ if (!currentLevel2.children) {
|
|
|
|
|
+ currentLevel2.children = [];
|
|
|
|
|
+ }
|
|
|
|
|
+ // 检查是否已存在,避免重复添加
|
|
|
|
|
+ const existingLevel3 = currentLevel2.children.find(child => child.id === level3!.id);
|
|
|
|
|
+ if (!existingLevel3) {
|
|
|
|
|
+ currentLevel2.children.push(level3);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ // 返回所有一级权限
|
|
|
|
|
+ return Array.from(level1Map.values());
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+// 加载权限列表
|
|
|
|
|
+const loadPermissionList = async () => {
|
|
|
|
|
+ if (!subAccountForm.roleId) {
|
|
|
|
|
+ permissionList.value = [];
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ try {
|
|
|
|
|
+ const roleId = Number(subAccountForm.roleId);
|
|
|
|
|
+ if (!roleId || isNaN(roleId)) {
|
|
|
|
|
+ permissionList.value = [];
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const res = await getRolePermissionTable({ roleId });
|
|
|
|
|
+ const code = typeof res.code === "string" ? parseInt(res.code) : res.code;
|
|
|
|
|
+ if (code === 200 && res.data) {
|
|
|
|
|
+ permissionList.value = transformFlatPermissionsToTree(res.data);
|
|
|
|
|
+ } else {
|
|
|
|
|
+ ElMessage.error(res.msg || "获取权限列表失败");
|
|
|
|
|
+ permissionList.value = [];
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ console.error("获取权限列表失败:", error);
|
|
|
|
|
+ ElMessage.error("获取权限列表失败,请重试");
|
|
|
|
|
+ permissionList.value = [];
|
|
|
|
|
+ }
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+// 递归收集所有选中的权限ID
|
|
|
|
|
+const collectCheckedPermissions = (permission: PermissionItem): number[] => {
|
|
|
|
|
+ const permissions: number[] = [];
|
|
|
|
|
+ if (permission.checked) {
|
|
|
|
|
+ permissions.push(permission.id);
|
|
|
|
|
+ }
|
|
|
|
|
+ if (permission.children) {
|
|
|
|
|
+ permission.children.forEach(child => {
|
|
|
|
|
+ permissions.push(...collectCheckedPermissions(child));
|
|
|
|
|
+ });
|
|
|
|
|
+ }
|
|
|
|
|
+ return permissions;
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+// 重置权限列表选中状态
|
|
|
|
|
+const resetPermissions = () => {
|
|
|
|
|
+ const resetItem = (item: PermissionItem) => {
|
|
|
|
|
+ item.checked = false;
|
|
|
|
|
+ if (item.children) {
|
|
|
|
|
+ item.children.forEach(child => resetItem(child));
|
|
|
|
|
+ }
|
|
|
|
|
+ };
|
|
|
|
|
+ permissionList.value.forEach(item => resetItem(item));
|
|
|
|
|
+ subAccountForm.permissions = [];
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+// 初始化数据
|
|
|
|
|
+onMounted(() => {
|
|
|
|
|
+ loadRoleList();
|
|
|
|
|
+ // 不在这里加载权限列表,等用户选择角色后再加载
|
|
|
|
|
+
|
|
|
|
|
+ if (isEdit.value) {
|
|
|
|
|
+ // 编辑模式:加载子账号数据
|
|
|
|
|
+ loadSubAccountData();
|
|
|
|
|
+ }
|
|
|
|
|
+});
|
|
|
|
|
+
|
|
|
|
|
+// 加载子账号数据(编辑时)
|
|
|
|
|
+const loadSubAccountData = () => {
|
|
|
|
|
+ // TODO: 调用接口获取子账号详情
|
|
|
|
|
+ // 模拟数据
|
|
|
|
|
+ const mockAccountData = {
|
|
|
|
|
+ id: Number(accountId.value),
|
|
|
|
|
+ accountName: "测试账号",
|
|
|
|
|
+ phone: "13800138000",
|
|
|
|
|
+ roleId: 1,
|
|
|
|
|
+ permissions: [1, 11, 111, 2]
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ subAccountForm.accountName = mockAccountData.accountName;
|
|
|
|
|
+ subAccountForm.phone = mockAccountData.phone;
|
|
|
|
|
+ subAccountForm.roleId = mockAccountData.roleId;
|
|
|
|
|
+ subAccountForm.permissions = mockAccountData.permissions;
|
|
|
|
|
+
|
|
|
|
|
+ // 设置权限列表的选中状态
|
|
|
|
|
+ setPermissionsChecked(mockAccountData.permissions);
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+// 设置权限选中状态
|
|
|
|
|
+const setPermissionsChecked = (permissionIds: number[]) => {
|
|
|
|
|
+ const setItemChecked = (item: PermissionItem) => {
|
|
|
|
|
+ if (permissionIds.includes(item.id)) {
|
|
|
|
|
+ item.checked = true;
|
|
|
|
|
+ }
|
|
|
|
|
+ if (item.children) {
|
|
|
|
|
+ item.children.forEach(child => setItemChecked(child));
|
|
|
|
|
+ }
|
|
|
|
|
+ };
|
|
|
|
|
+ permissionList.value.forEach(item => setItemChecked(item));
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+// 返回
|
|
|
|
|
+const handleBack = () => {
|
|
|
|
|
+ router.back();
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+// 取消
|
|
|
|
|
+const handleCancel = () => {
|
|
|
|
|
+ router.back();
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+// 保存
|
|
|
|
|
+const handleSave = async () => {
|
|
|
|
|
+ if (!subAccountFormRef.value) return;
|
|
|
|
|
+
|
|
|
|
|
+ await subAccountFormRef.value.validate(async valid => {
|
|
|
|
|
+ if (!valid) return;
|
|
|
|
|
+
|
|
|
|
|
+ saveLoading.value = true;
|
|
|
|
|
+ try {
|
|
|
|
|
+ // TODO: 调用创建/编辑子账号接口
|
|
|
|
|
+ const createdId = localGet("createdId") || localGet("geeker-user")?.userInfo?.storeId || "";
|
|
|
|
|
+
|
|
|
|
|
+ const createAccountDto = {
|
|
|
|
|
+ accountName: subAccountForm.accountName,
|
|
|
|
|
+ phone: subAccountForm.phone,
|
|
|
|
|
+ roleId: subAccountForm.roleId,
|
|
|
|
|
+ storeId: createdId
|
|
|
|
|
+ };
|
|
|
|
|
+ const res = await createAccountAndAssignRole(createAccountDto);
|
|
|
|
|
+ if (res.code === 200) {
|
|
|
|
|
+ ElMessage.success(isEdit.value ? "编辑成功" : "创建成功");
|
|
|
|
|
+ setTimeout(() => {
|
|
|
|
|
+ router.push("/accountRoleManagement/subAccountManagement");
|
|
|
|
|
+ }, 1000);
|
|
|
|
|
+ } else {
|
|
|
|
|
+ ElMessage.error(res.msg || "创建失败");
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ ElMessage.error(isEdit.value ? "编辑失败,请重试" : "创建失败,请重试");
|
|
|
|
|
+ } finally {
|
|
|
|
|
+ saveLoading.value = false;
|
|
|
|
|
+ }
|
|
|
|
|
+ });
|
|
|
|
|
+};
|
|
|
|
|
+// 处理角色选择变化
|
|
|
|
|
+const handleRoleChange = (value: number | null | undefined) => {
|
|
|
|
|
+ subAccountForm.roleId = value as number | undefined;
|
|
|
|
|
+ permissionList.value = [];
|
|
|
|
|
+ if (value) {
|
|
|
|
|
+ loadPermissionList();
|
|
|
|
|
+ } else {
|
|
|
|
|
+ // 如果清空了角色选择,重置权限列表
|
|
|
|
|
+ resetPermissions();
|
|
|
|
|
+ }
|
|
|
|
|
+};
|
|
|
|
|
+</script>
|
|
|
|
|
+
|
|
|
|
|
+<style lang="scss" scoped>
|
|
|
|
|
+.sub-account-form-container {
|
|
|
|
|
+ min-height: calc(100vh - 84px);
|
|
|
|
|
+ padding: 20px;
|
|
|
|
|
+ background: #ffffff;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 头部区域
|
|
|
|
|
+.header-section {
|
|
|
|
|
+ padding-bottom: 16px;
|
|
|
|
|
+ margin-bottom: 24px;
|
|
|
|
|
+ border-bottom: 1px solid #e4e7ed;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 表单内容
|
|
|
|
|
+.form-content {
|
|
|
|
|
+ max-width: 800px;
|
|
|
|
|
+ padding: 24px 0;
|
|
|
|
|
+ .form-section {
|
|
|
|
|
+ margin-bottom: 32px;
|
|
|
|
|
+ .section-title {
|
|
|
|
|
+ margin: 0 0 24px;
|
|
|
|
|
+ font-size: 16px;
|
|
|
|
|
+ font-weight: 600;
|
|
|
|
|
+ color: var(--el-text-color-primary);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ :deep(.el-form-item) {
|
|
|
|
|
+ margin-bottom: 24px;
|
|
|
|
|
+ .el-form-item__label {
|
|
|
|
|
+ font-weight: 500;
|
|
|
|
|
+ color: var(--el-text-color-regular);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 权限列表
|
|
|
|
|
+ .permission-list {
|
|
|
|
|
+ width: 100%;
|
|
|
|
|
+ .permission-empty {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ justify-content: center;
|
|
|
|
|
+ padding: 40px 0;
|
|
|
|
|
+ color: var(--el-text-color-placeholder);
|
|
|
|
|
+ .empty-text {
|
|
|
|
|
+ font-size: 14px;
|
|
|
|
|
+ color: var(--el-text-color-secondary);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 底部按钮
|
|
|
|
|
+.form-footer {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ gap: 12px;
|
|
|
|
|
+ justify-content: center;
|
|
|
|
|
+ padding: 24px 0;
|
|
|
|
|
+ margin-top: 24px;
|
|
|
|
|
+ border-top: 1px solid #e4e7ed;
|
|
|
|
|
+}
|
|
|
|
|
+</style>
|