优化tenant模式

This commit is contained in:
李志强 2026-03-09 18:27:12 +08:00
parent b8761ad3e4
commit 108ec50978
8 changed files with 239 additions and 58 deletions

View File

@ -7,6 +7,13 @@ export function getModulesList() {
}); });
} }
export function getTenantList() {
return request({
url: '/admin/modules/getTenantList',
method: 'get',
});
}
export function getModuleDetail(id) { export function getModuleDetail(id) {
return request({ return request({
url: `/admin/modules/${id}`, url: `/admin/modules/${id}`,

View File

@ -224,7 +224,7 @@ const handleCommand = async (command) => {
// localStorage // localStorage
for (let i = 0; i < localStorage.length; i++) { for (let i = 0; i < localStorage.length; i++) {
const key = localStorage.key(i); const key = localStorage.key(i);
if (key && key.startsWith('menu_cache_')) { if (key && key.startsWith('menu_')) {
menuCacheKeys.push(key); menuCacheKeys.push(key);
} }
} }
@ -237,7 +237,7 @@ const handleCommand = async (command) => {
const sessionMenuCacheKeys: string[] = []; const sessionMenuCacheKeys: string[] = [];
for (let i = 0; i < sessionStorage.length; i++) { for (let i = 0; i < sessionStorage.length; i++) {
const key = sessionStorage.key(i); const key = sessionStorage.key(i);
if (key && key.startsWith('menu_cache_')) { if (key && key.startsWith('menu_')) {
sessionMenuCacheKeys.push(key); sessionMenuCacheKeys.push(key);
} }
} }

View File

@ -21,7 +21,7 @@ export const useMenuStore = defineStore('menu', () => {
try { try {
const userInfo = JSON.parse(localStorage.getItem('userInfo') || '{}'); const userInfo = JSON.parse(localStorage.getItem('userInfo') || '{}');
const loginType = userInfo.type || 'user'; const loginType = userInfo.type || 'user';
const roleId = userInfo.role || 0; const roleId = userInfo.group_id || 0;
return `menu_cache_${loginType}_${roleId}`; return `menu_cache_${loginType}_${roleId}`;
} catch (e) { } catch (e) {
return 'menu_cache_default'; return 'menu_cache_default';
@ -113,7 +113,7 @@ export const useMenuStore = defineStore('menu', () => {
try { try {
const userInfo = getUserInfo(); const userInfo = getUserInfo();
const loginType = userInfo.type || 'user'; const loginType = userInfo.type || 'user';
const roleId = userInfo.role || 0; const roleId = userInfo.group_id || 0;
let res; let res;

View File

@ -1,7 +1,9 @@
<template> <template>
<div class="category-manager"> <div class="category-manager">
<div class="category-list" v-loading="loading"> <div class="category-list" v-loading="loading">
<div v-if="filteredCategories.length === 0" class="empty-state"></div> <div v-if="filteredCategories.length === 0" class="empty-state">
<el-empty description="暂无文章分类" />
</div>
<div v-else class="category-tree"> <div v-else class="category-tree">
<category-node <category-node

View File

@ -0,0 +1,112 @@
<template>
<el-dialog v-model="visible" title="添加用户" width="600px" @closed="handleClosed"
destroy-on-close>
<el-form ref="formRef" :model="formData" :rules="rules" label-width="100px" v-loading="loading"
style="padding: 20px">
<el-form-item label="用户名" prop="account">
<el-input v-model="formData.account" placeholder="请输入用户名" />
</el-form-item>
<el-form-item label="密码" prop="password">
<el-input v-model="formData.password" type="password" placeholder="请输入密码" show-password />
</el-form-item>
<el-form-item label="昵称" prop="name">
<el-input v-model="formData.name" placeholder="请输入昵称" />
</el-form-item>
<el-form-item label="手机号" prop="phone">
<el-input v-model="formData.phone" placeholder="请输入手机号" />
</el-form-item>
<el-form-item label="邮箱" prop="email">
<el-input v-model="formData.email" placeholder="请输入邮箱" />
</el-form-item>
<el-form-item label="状态" prop="status">
<el-radio-group v-model="formData.status">
<el-radio :label="1">启用</el-radio>
<el-radio :label="0">禁用</el-radio>
</el-radio-group>
</el-form-item>
</el-form>
<template #footer>
<el-button @click="visible = false">取消</el-button>
<el-button type="primary" :loading="submitting" @click="submitForm">确定</el-button>
</template>
</el-dialog>
</template>
<script setup lang="ts">
import { ref, reactive } from 'vue';
import { ElMessage } from 'element-plus';
import { addUser } from '@/api/user';
const emit = defineEmits(['success']);
const visible = ref(false);
const loading = ref(false);
const submitting = ref(false);
const formRef = ref();
const currentTenantId = ref<number | null>(null);
const formData = reactive({
account: '',
password: '',
name: '',
phone: '',
email: '',
status: 1
});
const rules = {
account: [{ required: true, message: '请输入用户名', trigger: 'blur' }],
password: [{ required: true, message: '请输入密码', trigger: 'blur', min: 6 }],
phone: [
{ pattern: /^1[3-9]\d{9}$/, message: '请输入正确的手机号', trigger: 'blur' }
],
email: [
{ type: 'email', message: '请输入正确的邮箱格式', trigger: 'blur' }
]
};
const open = (tenantId: number) => {
visible.value = true;
currentTenantId.value = tenantId;
Object.assign(formData, {
account: '',
password: '',
name: '',
phone: '',
email: '',
status: 1
});
};
const submitForm = async () => {
if (!formRef.value) return;
await formRef.value.validate();
submitting.value = true;
try {
const submitData = {
...formData,
tenant_id: currentTenantId.value
};
const res = await addUser(submitData);
if (res.code === 200) {
ElMessage.success('添加成功');
visible.value = false;
emit('success');
}
} catch (error) {
console.error('提交失败', error);
} finally {
submitting.value = false;
}
};
const handleClosed = () => {
formRef.value?.resetFields();
};
defineExpose({ open });
</script>

View File

@ -62,9 +62,10 @@
<el-table-column label="操作" width="280" align="center" fixed="right"> <el-table-column label="操作" width="280" align="center" fixed="right">
<template #default="scope"> <template #default="scope">
<!-- <el-button size="small" @click="handleQualification(scope.row)">资质</el-button> --> <!-- <el-button size="small" @click="handleQualification(scope.row)">资质</el-button> -->
<el-button size="small" @click="handlePreview(scope.row)">查看</el-button> <el-button text size="small" @click="handleAddUser(scope.row)">增加用户</el-button>
<el-button size="small" @click="editRef.open(scope.row.id)">编辑</el-button> <el-button text size="small" @click="handlePreview(scope.row)">查看</el-button>
<el-button size="small" type="danger" @click="handleDelete(scope.row)">删除</el-button> <el-button text size="small" @click="editRef.open(scope.row.id)">编辑</el-button>
<el-button text size="small" type="danger" @click="handleDelete(scope.row)">删除</el-button>
</template> </template>
</el-table-column> </el-table-column>
</el-table> </el-table>
@ -72,6 +73,7 @@
<EditModal ref="editRef" @success="refresh" /> <EditModal ref="editRef" @success="refresh" />
<DetailDrawer ref="detailRef" /> <DetailDrawer ref="detailRef" />
<Qualification ref="qualificationRef" /> <Qualification ref="qualificationRef" />
<AddUser ref="addUserRef" />
<!-- 分页 --> <!-- 分页 -->
<div class="pagination-bar"> <div class="pagination-bar">
@ -88,6 +90,7 @@ import { useRouter } from 'vue-router';
import EditModal from './components/edit.vue'; import EditModal from './components/edit.vue';
import DetailDrawer from './components/detail.vue'; import DetailDrawer from './components/detail.vue';
import Qualification from './components/qualification.vue'; import Qualification from './components/qualification.vue';
import AddUser from './components/adduser.vue';
const total = ref(0); const total = ref(0);
const page = ref(1); const page = ref(1);
@ -98,6 +101,7 @@ const tenants = ref([]);
const editRef = ref(); const editRef = ref();
const detailRef = ref(); const detailRef = ref();
const addUserRef = ref();
const qualificationRef = ref(); const qualificationRef = ref();
// //
@ -119,6 +123,11 @@ const handlePreview = (row: any) => {
detailRef.value.open(row.id); detailRef.value.open(row.id);
}; };
//
const handleAddUser = (row: any) => {
addUserRef.value.open(row.id);
};
// //
const handleQualification = (row: any) => { const handleQualification = (row: any) => {
qualificationRef.value.open(row.id); qualificationRef.value.open(row.id);

View File

@ -146,7 +146,7 @@ import {
Moon, Moon,
} from "@element-plus/icons-vue"; } from "@element-plus/icons-vue";
import { getModulesList } from "@/api/modules"; import { getTenantList } from "@/api/modules";
import { useAuthStore } from "@/stores/auth"; import { useAuthStore } from "@/stores/auth";
import { useMenuStore } from "@/stores/menu"; import { useMenuStore } from "@/stores/menu";
@ -271,7 +271,7 @@ async function handleLogout() {
// //
async function loadModules() { async function loadModules() {
try { try {
const res = await getModulesList(); const res = await getTenantList();
if (res.code === 200) { if (res.code === 200) {
const list = res.data?.list || []; const list = res.data?.list || [];
const filteredList = list const filteredList = list

View File

@ -43,22 +43,38 @@
element-loading-text="正在加载..." element-loading-text="正在加载..."
:tree-props="{ :tree-props="{
children: 'children', children: 'children',
hasChildren: 'hasChildren' hasChildren: 'hasChildren',
}" }"
@row-click="handleRowClick" @row-click="handleRowClick"
> >
<el-table-column prop="title" label="菜单名称" width="200"> <el-table-column prop="title" label="菜单名称" width="200">
<template #default="scope"> <template #default="scope">
<div class="menu-item"> <div class="menu-item">
<i v-if="scope.row.icon" :class="scope.row.icon" class="menu-icon"></i> <i
v-if="scope.row.icon"
:class="scope.row.icon"
class="menu-icon"
></i>
<span>{{ scope.row.title }}</span> <span>{{ scope.row.title }}</span>
</div> </div>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column
prop="id"
width="60"
label="ID"
align="center"
></el-table-column>
<el-table-column prop="path" label="路由地址"></el-table-column> <el-table-column prop="path" label="路由地址"></el-table-column>
<el-table-column prop="MenuType" label="菜单类型" width="120" align="center"> <el-table-column
prop="MenuType"
label="菜单类型"
width="120"
align="center"
>
<template #default="scope"> <template #default="scope">
<el-tag :type="getMenuTypeTagType(scope.row.type)"> <el-tag :type="getMenuTypeTagType(scope.row.type)">
{{ getMenuTypeTitle(scope.row.type) }} {{ getMenuTypeTitle(scope.row.type) }}
@ -66,7 +82,12 @@
</template> </template>
</el-table-column> </el-table-column>
<el-table-column prop="sort" label="排序" width="80" align="center"></el-table-column> <el-table-column
prop="sort"
label="排序"
width="80"
align="center"
></el-table-column>
<el-table-column prop="status" label="状态" width="100" align="center"> <el-table-column prop="status" label="状态" width="100" align="center">
<template #default="scope"> <template #default="scope">
@ -83,7 +104,12 @@
<el-table-column label="操作" width="280" fixed="right" align="center"> <el-table-column label="操作" width="280" fixed="right" align="center">
<template #default="scope"> <template #default="scope">
<el-button size="small" text @click.stop="handleAddSubMenu(scope.row)" :disabled="scope.row.type === 3"> <el-button
size="small"
text
@click.stop="handleAddSubMenu(scope.row)"
:disabled="scope.row.type === 3"
>
<el-icon> <el-icon>
<CirclePlus /> <CirclePlus />
</el-icon> </el-icon>
@ -97,7 +123,12 @@
<span>编辑</span> <span>编辑</span>
</el-button> </el-button>
<el-button size="small" text type="danger" @click.stop="handleDeleteMenu(scope.row)"> <el-button
size="small"
text
type="danger"
@click.stop="handleDeleteMenu(scope.row)"
>
<el-icon> <el-icon>
<Delete /> <Delete />
</el-icon> </el-icon>
@ -108,16 +139,37 @@
</el-table> </el-table>
<!-- 引入编辑组件 --> <!-- 引入编辑组件 -->
<MenuEdit v-model:visible="dialogVisible" :menu="dialogMenu" :parent-menu-options="parentMenuOptions" <MenuEdit
:dialog-type="dialogType" :parent-title="dialogParentTitle" @save="handleMenuSave" @cancel="handleMenuCancel" /> v-model:visible="dialogVisible"
:menu="dialogMenu"
:parent-menu-options="parentMenuOptions"
:dialog-type="dialogType"
:parent-title="dialogParentTitle"
@save="handleMenuSave"
@cancel="handleMenuCancel"
/>
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref, onMounted, watch } from "vue"; import { ref, onMounted, watch } from "vue";
import { ElMessage, ElMessageBox, ElForm } from "element-plus"; import { ElMessage, ElMessageBox, ElForm } from "element-plus";
import { Plus, CirclePlus, Edit, Delete, Refresh, FolderOpened, Folder } from "@element-plus/icons-vue"; import {
import { getAllMenus, updateMenuStatus, createMenu, updateMenu, deleteMenu } from "@/api/menu"; Plus,
CirclePlus,
Edit,
Delete,
Refresh,
FolderOpened,
Folder,
} from "@element-plus/icons-vue";
import {
getAllMenus,
updateMenuStatus,
createMenu,
updateMenu,
deleteMenu,
} from "@/api/menu";
import MenuEdit from "./components/edit.vue"; import MenuEdit from "./components/edit.vue";
// //
@ -146,10 +198,8 @@ const tableRef = ref<any>(null);
// //
const dialogVisible = ref(false); const dialogVisible = ref(false);
const dialogMenu = ref<Partial<Menu> | null>(null); const dialogMenu = ref<Partial<Menu> | null>(null);
const dialogType = ref<'add' | 'edit' | 'addSub'>('add'); const dialogType = ref<"add" | "edit" | "addSub">("add");
const dialogParentTitle = ref(''); const dialogParentTitle = ref("");
// //
const parentMenuOptions = ref<Menu[]>([]); const parentMenuOptions = ref<Menu[]>([]);
@ -196,9 +246,9 @@ async function refresh() {
loading.value = true; loading.value = true;
try { try {
await fetchMenus(); await fetchMenus();
ElMessage.success('刷新成功'); ElMessage.success("刷新成功");
} catch (error) { } catch (error) {
ElMessage.error('刷新失败'); ElMessage.error("刷新失败");
} finally { } finally {
loading.value = false; loading.value = false;
} }
@ -283,30 +333,28 @@ const handleStatusChange = async (menu: Menu) => {
} }
}; };
// //
const handleAddSubMenu = (parentMenu: Menu) => { const handleAddSubMenu = (parentMenu: Menu) => {
dialogType.value = 'addSub'; dialogType.value = "addSub";
dialogParentTitle.value = parentMenu.title; dialogParentTitle.value = parentMenu.title;
dialogMenu.value = { dialogMenu.value = {
id: 0, id: 0,
pid: parentMenu.id, pid: parentMenu.id,
title: '', title: "",
path: '', path: "",
component_path: '', component_path: "",
icon: '', icon: "",
sort: 0, sort: 0,
status: 1, status: 1,
type: parentMenu.type === 2 ? 1 : parentMenu.type, type: parentMenu.type === 2 ? 1 : parentMenu.type,
permission: '', permission: "",
}; };
dialogVisible.value = true; dialogVisible.value = true;
}; };
// //
const handleEditMenu = (menu: Menu) => { const handleEditMenu = (menu: Menu) => {
dialogType.value = 'edit'; dialogType.value = "edit";
dialogMenu.value = { ...menu }; dialogMenu.value = { ...menu };
dialogVisible.value = true; dialogVisible.value = true;
}; };
@ -320,7 +368,7 @@ const handleDeleteMenu = (menu: Menu) => {
confirmButtonText: "确定", confirmButtonText: "确定",
cancelButtonText: "取消", cancelButtonText: "取消",
type: "warning", type: "warning",
} },
).then(async () => { ).then(async () => {
try { try {
const result = await deleteMenu(menu.id); const result = await deleteMenu(menu.id);
@ -338,7 +386,7 @@ const handleDeleteMenu = (menu: Menu) => {
// //
const handleAddMenu = () => { const handleAddMenu = () => {
dialogType.value = 'add'; dialogType.value = "add";
dialogMenu.value = null; dialogMenu.value = null;
dialogVisible.value = true; dialogVisible.value = true;
}; };
@ -357,7 +405,7 @@ const handleMenuSave = async (menu: Partial<Menu>) => {
} }
// //
if (pidValue === null || pidValue === undefined || pidValue === '') { if (pidValue === null || pidValue === undefined || pidValue === "") {
payload.pid = 0; payload.pid = 0;
} else { } else {
const parsedPid = parseInt(String(pidValue), 10); const parsedPid = parseInt(String(pidValue), 10);
@ -370,20 +418,22 @@ const handleMenuSave = async (menu: Partial<Menu>) => {
// payload.pid // payload.pid
if (Array.isArray(payload.pid)) { if (Array.isArray(payload.pid)) {
payload.pid = Array.isArray(payload.pid) && payload.pid.length > 0 payload.pid =
Array.isArray(payload.pid) && payload.pid.length > 0
? parseInt(String(payload.pid[payload.pid.length - 1]), 10) || 0 ? parseInt(String(payload.pid[payload.pid.length - 1]), 10) || 0
: 0; : 0;
} }
// //
if (typeof payload.pid !== 'number') { if (typeof payload.pid !== "number") {
payload.pid = parseInt(String(payload.pid), 10) || 0; payload.pid = parseInt(String(payload.pid), 10) || 0;
} }
if (menu.id === 0) { if (menu.id === 0) {
// //
const result = await createMenu(payload as Menu); const result = await createMenu(payload as Menu);
if (result.code === 200) { // code success if (result.code === 200) {
// code success
ElMessage.success(result.msg || "菜单添加成功"); ElMessage.success(result.msg || "菜单添加成功");
dialogVisible.value = false; dialogVisible.value = false;
await fetchMenus(); await fetchMenus();
@ -393,7 +443,8 @@ const handleMenuSave = async (menu: Partial<Menu>) => {
} else { } else {
// //
const result = await updateMenu(menu.id!, payload as Menu); const result = await updateMenu(menu.id!, payload as Menu);
if (result.code === 200) { // code success if (result.code === 200) {
// code success
ElMessage.success(result.msg || "更新成功"); ElMessage.success(result.msg || "更新成功");
dialogVisible.value = false; dialogVisible.value = false;
await fetchMenus(); await fetchMenus();