优化系统

This commit is contained in:
2025-11-06 23:10:17 +08:00
parent f4244c09b9
commit 0cd0b9c705
23 changed files with 1086 additions and 129 deletions
+24
View File
@@ -0,0 +1,24 @@
import request from "@/utils/request";
/**
* 获取平台统计数据(平台用户使用)
* @returns {Promise}
*/
export function getPlatformStats() {
return request({
url: "/api/dashboard/platform-stats",
method: "get",
});
}
/**
* 获取租户统计数据(租户员工使用)
* @returns {Promise}
*/
export function getTenantStats() {
return request({
url: "/api/dashboard/tenant-stats",
method: "get",
});
}
+11
View File
@@ -112,4 +112,15 @@ export function addTag(data) {
method: "post",
data,
});
}
/**
* 获取知识库数量
* @returns {Promise}
*/
export function getKnowledgeCount() {
return request({
url: "/api/knowledge/count",
method: "get",
});
}
+4 -1
View File
@@ -2,11 +2,14 @@ import request from '@/utils/request';
/**
* 获取所有菜单权限列表(用于分配权限)
* @param {Object} params - 请求参数
* @param {number} params.roleId - 可选的角色ID,用于根据角色的default值过滤菜单
*/
export function getAllMenuPermissions() {
export function getAllMenuPermissions(params = {}) {
return request({
url: '/api/permissions/menus',
method: 'get',
params,
});
}
+6 -6
View File
@@ -108,7 +108,7 @@ const transformMenuData = (menus) => {
return [];
}
console.log('原始菜单数据:', menus);
// console.log('原始菜单数据:', menus);
// 功能页面路径关键词,这些菜单不应该显示在侧边栏
// 注意:只过滤真正的功能页面,不过滤包含这些关键词的父菜单
@@ -146,17 +146,17 @@ const transformMenuData = (menus) => {
.filter(menu => {
// 只显示页面菜单,不显示API权限菜单
if (menu.menuType !== 1 && menu.menuType !== undefined) {
console.log('过滤掉非页面菜单:', menu);
// console.log('过滤掉非页面菜单:', menu);
return false;
}
// 过滤掉功能页面(详情、新增、编辑、删除等)
if (isFunctionPage(menu.path)) {
console.log('过滤掉功能页面:', menu.path);
// console.log('过滤掉功能页面:', menu.path);
return false;
}
// 过滤掉需要隐藏的子菜单(如分类管理、标签管理)
if (isHiddenSubMenu(menu.path)) {
console.log('过滤掉隐藏的子菜单:', menu.path);
// console.log('过滤掉隐藏的子菜单:', menu.path);
return false;
}
return true;
@@ -172,7 +172,7 @@ const transformMenuData = (menus) => {
children: []
}));
console.log('过滤后的菜单数据:', allMenus);
// console.log('过滤后的菜单数据:', allMenus);
// 构建菜单映射表(只包含有效的页面菜单)
const menuMap = new Map();
@@ -205,7 +205,7 @@ const transformMenuData = (menus) => {
}
});
console.log('构建后的菜单树:', rootMenus);
// console.log('构建后的菜单树:', rootMenus);
// 按 order 排序(确保排序正确)
const sortMenus = (menus) => {
@@ -120,11 +120,13 @@ import { getKnowledgeDetail, createKnowledge, updateKnowledge, getCategoryList,
import { ElMessage, ElMessageBox } from "element-plus";
import type { FormInstance, FormRules } from "element-plus";
import WangEditor from '@/views/components/WangEditor.vue';
import { useAuthStore } from '@/stores/auth';
const formRef = ref<FormInstance>();
const router = useRouter();
const route = useRoute();
const authStore = useAuthStore();
const formData = reactive<{
title: string;
@@ -186,17 +188,44 @@ watch(
}
);
// 获取当前登录用户的显示名称
const getLoginUser = () => {
const userStr = localStorage.getItem("user");
if (userStr) {
try {
const user = JSON.parse(userStr);
return user.username || user.name || user.userName || "";
} catch (e) {
return "";
const user = authStore.user;
if (!user) {
// 如果 authStore 中没有,尝试从 localStorage 获取
const userStr = localStorage.getItem("userInfo");
if (userStr) {
try {
const userInfo = JSON.parse(userStr);
// 如果是员工登录,优先显示name
if (userInfo.type === 'employee' && userInfo.name) {
return userInfo.name;
}
// 如果是用户登录,优先显示nickname
if (userInfo.nickname) {
return userInfo.nickname;
}
// 最后显示username
return userInfo.username || "";
} catch (e) {
return "";
}
}
return "";
}
return "";
// 如果是员工登录,优先显示name
if (user.type === 'employee' && user.name) {
return user.name;
}
// 如果是用户登录,优先显示nickname
if (user.nickname) {
return user.nickname;
}
// 最后显示username
return user.username || "";
};
const fetchDetail = async () => {
+116 -20
View File
@@ -154,7 +154,7 @@
</template>
<script setup lang="ts">
import { ref, computed, onMounted } from "vue";
import { ref, computed, onMounted, markRaw } from "vue";
import { Chart, registerables } from "chart.js";
import {
Money,
@@ -165,7 +165,11 @@ import {
ArrowDown,
MoreFilled,
Plus,
Document,
} from "@element-plus/icons-vue";
import { getKnowledgeCount } from "@/api/knowledge";
import { getPlatformStats, getTenantStats } from "@/api/dashboard";
import { useAuthStore } from "@/stores/auth";
Chart.register(...registerables);
@@ -179,38 +183,111 @@ const currentDate = computed(() => {
return `${month}${day}${weekday}`;
});
// 统计数据
// 统计数据(使用 markRaw 避免图标组件被响应式化)
const stats = ref([
{
label: "本月收入",
value: "¥ 35,800",
change: 10.4,
icon: Money,
type: "income",
label: "知识库",
value: "0",
change: 0,
icon: markRaw(Document),
type: "knowledge",
},
{
label: "新用户",
value: "842",
change: 3.7,
icon: User,
value: "0",
change: 0,
icon: markRaw(User),
type: "users",
},
{
label: "订单量",
value: "1,376",
change: -1.6,
icon: ShoppingCart,
type: "orders",
label: "员工数",
value: "0",
change: 0,
icon: markRaw(ShoppingCart),
type: "employees",
},
{
label: "活跃率",
value: "27.3%",
change: 2.2,
icon: TrendCharts,
type: "active",
label: "租户数",
value: "0",
change: 0,
icon: markRaw(TrendCharts),
type: "tenants",
},
]);
const authStore = useAuthStore();
// 判断是否为租户员工登录
const isEmployee = computed(() => {
return authStore.user?.type === 'employee';
});
// 获取平台统计数据
const fetchPlatformStats = async () => {
try {
const res = await getPlatformStats();
if (res?.code === 0 || res?.success) {
const data = res.data || {};
// 知识库
if (data.knowledgeCount) {
stats.value[0].value = data.knowledgeCount.total?.toString() || "0";
stats.value[0].change = parseFloat((data.knowledgeCount.growthRate || 0).toFixed(1));
}
// 用户数
stats.value[1].label = "用户数";
stats.value[1].value = (data.userCount || 0).toString();
stats.value[1].change = 0;
// 员工数
stats.value[2].label = "员工数";
stats.value[2].value = (data.employeeCount || 0).toString();
stats.value[2].change = 0;
// 租户数
stats.value[3].label = "租户数";
stats.value[3].value = (data.tenantCount || 0).toString();
stats.value[3].change = 0;
}
} catch (e) {
// 静默失败,不影响页面显示
}
};
// 获取租户统计数据
const fetchTenantStats = async () => {
try {
const res = await getTenantStats();
if (res?.code === 0 || res?.success) {
const data = res.data || {};
// 知识库
if (data.knowledgeCount) {
stats.value[0].value = data.knowledgeCount.total?.toString() || "0";
stats.value[0].change = parseFloat((data.knowledgeCount.growthRate || 0).toFixed(1));
}
// 员工数
stats.value[1].label = "员工数";
stats.value[1].value = (data.employeeCount || 0).toString();
stats.value[1].change = 0;
// 部门数
stats.value[2].label = "部门数";
stats.value[2].value = (data.departmentCount || 0).toString();
stats.value[2].change = 0;
// 职位数
stats.value[3].label = "职位数";
stats.value[3].value = (data.positionCount || 0).toString();
stats.value[3].change = 0;
}
} catch (e) {
// 静默失败,不影响页面显示
}
};
// 任务列表
const tasks = ref([
{
@@ -272,6 +349,13 @@ const handleTaskChange = (task: any) => {
// 初始化图表
onMounted(() => {
// 根据登录类型加载不同的统计数据
if (isEmployee.value) {
fetchTenantStats();
} else {
fetchPlatformStats();
}
// 折线图
const lineChartEl = document.getElementById("lineChart") as HTMLCanvasElement | null;
if (!lineChartEl) {
@@ -486,6 +570,18 @@ onMounted(() => {
background: linear-gradient(135deg, #8b5cf6 0%, #a78bfa 100%);
}
&.knowledge .stat-icon-wrapper {
background: linear-gradient(135deg, #062da3 0%, #4f84ff 100%);
}
&.employees .stat-icon-wrapper {
background: linear-gradient(135deg, #10b981 0%, #34d399 100%);
}
&.tenants .stat-icon-wrapper {
background: linear-gradient(135deg, #8b5cf6 0%, #a78bfa 100%);
}
.stat-content {
flex: 1;
min-width: 0;
+35 -8
View File
@@ -160,17 +160,19 @@ import {
Select,
RefreshLeft,
} from '@element-plus/icons-vue';
import { getRoleByTenantId } from '@/api/role';
import { getRoleByTenantId, getAllRoles } from '@/api/role';
import {
getAllMenuPermissions,
getRolePermissions,
assignRolePermissions,
} from '@/api/permission';
import { useAuthStore } from '@/stores/auth';
// 角色相关
const roleList = ref([]);
const roleSearchQuery = ref('');
const selectedRole = ref(null);
const authStore = useAuthStore();
// 权限相关
const allMenus = ref([]);
@@ -200,33 +202,56 @@ const filteredRoles = computed(() => {
// 获取当前租户ID
const getCurrentTenantId = () => {
if (authStore.user && authStore.user.tenant_id) {
return authStore.user.tenant_id;
}
const userInfo = JSON.parse(localStorage.getItem('userInfo') || '{}');
return userInfo.tenant_id || 1;
return userInfo.tenant_id || userInfo.tenantId || 0;
};
// 判断是否为平台用户
const isPlatformUser = () => {
if (authStore.user && authStore.user.type) {
return authStore.user.type === 'user';
}
const userInfo = JSON.parse(localStorage.getItem('userInfo') || '{}');
return userInfo.type === 'user';
};
// 加载角色列表
const loadRoles = async () => {
try {
const tenantId = getCurrentTenantId();
const res = await getRoleByTenantId(tenantId);
// 如果是平台用户,使用 getAllRoles 获取所有角色(包括平台角色和租户角色)
// 如果是租户员工,使用 getRoleByTenantId 获取当前租户的角色
let res;
if (isPlatformUser()) {
res = await getAllRoles();
} else {
const tenantId = getCurrentTenantId();
res = await getRoleByTenantId(tenantId);
}
// 兼容两种响应格式:success 和 code
const isSuccess = res.success || res.code === 0;
if (isSuccess && res.data) {
roleList.value = res.data;
roleList.value = Array.isArray(res.data) ? res.data : [];
} else {
ElMessage.warning(res.message || '未获取到角色数据');
roleList.value = [];
}
} catch (error) {
console.error('加载角色列表失败:', error);
ElMessage.error('加载角色列表失败');
roleList.value = [];
}
};
// 加载所有菜单权限
const loadAllMenus = async () => {
// 加载所有菜单权限(根据选中的角色过滤)
const loadAllMenus = async (roleId = null) => {
try {
const res = await getAllMenuPermissions();
// 如果提供了roleId,传递给接口用于根据角色的default值过滤菜单
const params = roleId ? { roleId } : {};
const res = await getAllMenuPermissions(params);
if (res.success && res.data) {
allMenus.value = res.data;
@@ -267,6 +292,8 @@ const buildMenuTree = () => {
// 选择角色
const selectRole = async (role) => {
selectedRole.value = role;
// 重新加载菜单列表(根据角色的default值过滤)
await loadAllMenus(role.roleId);
await loadRolePermissions(role.roleId);
};
+30 -5
View File
@@ -96,7 +96,7 @@ import {
ElMessageBox,
} from "element-plus";
import { Plus, View, Edit, Delete, Refresh } from "@element-plus/icons-vue";
import { getRoleByTenantId, deleteRole } from "@/api/role";
import { getRoleByTenantId, getAllRoles, deleteRole } from "@/api/role";
import { useAuthStore } from "@/stores/auth";
import RoleEditDialog from "./components/edit.vue";
import RoleDetailDialog from "./components/detail.vue";
@@ -124,13 +124,30 @@ const getCurrentTenantId = () => {
if (userInfo) {
try {
const user = JSON.parse(userInfo);
return user.tenant_id || user.tenantId || null;
return user.tenant_id || user.tenantId || 0;
} catch (e) {
console.error('Failed to parse user info:', e);
}
}
return null;
return 0;
};
// 判断是否为平台用户
const isPlatformUser = () => {
if (authStore.user && authStore.user.type) {
return authStore.user.type === 'user';
}
const userInfo = localStorage.getItem('userInfo');
if (userInfo) {
try {
const user = JSON.parse(userInfo);
return user.type === 'user';
} catch (e) {
console.error('Failed to parse user info:', e);
}
}
return false;
};
// 判断是否为系统管理员角色(不允许删除)
@@ -160,8 +177,16 @@ async function fetchRoles() {
loading.value = true;
error.value = "";
try {
const tenantId = getCurrentTenantId();
const res = await getRoleByTenantId(tenantId);
// 如果是平台用户,使用 getAllRoles 获取所有角色(包括平台角色和租户角色)
// 如果是租户员工,使用 getRoleByTenantId 获取当前租户的角色
let res;
if (isPlatformUser()) {
res = await getAllRoles();
} else {
const tenantId = getCurrentTenantId();
res = await getRoleByTenantId(tenantId);
}
if (res.code === 0 && res.data) {
roles.value = Array.isArray(res.data) ? res.data : [];
} else {
+28 -3
View File
@@ -231,7 +231,7 @@ import {
getUserInfo,
changePassword,
} from "@/api/user";
import { getRoleByTenantId } from "@/api/role";
import { getRoleByTenantId, getAllRoles } from "@/api/role";
import { getTenantDepartments } from "@/api/department";
import { getTenantPositions, getPositionsByDepartment } from "@/api/position";
import { useAuthStore } from "@/stores/auth";
@@ -279,12 +279,37 @@ const getCurrentTenantId = () => {
return 0;
};
// 判断是否为平台用户
const isPlatformUser = () => {
if (authStore.user && authStore.user.type) {
return authStore.user.type === 'user';
}
const userInfo = localStorage.getItem('userInfo');
if (userInfo) {
try {
const user = JSON.parse(userInfo);
return user.type === 'user';
} catch (e) {
console.error('Failed to parse user info:', e);
}
}
return false;
};
// 获取角色列表
const fetchRoles = async () => {
loadingRoles.value = true;
try {
const tenantId = getCurrentTenantId();
const res = await getRoleByTenantId(tenantId);
// 如果是平台用户,使用 getAllRoles 获取所有角色(包括平台角色和租户角色)
// 如果是租户员工,使用 getRoleByTenantId 获取当前租户的角色
let res;
if (isPlatformUser()) {
res = await getAllRoles();
} else {
const tenantId = getCurrentTenantId();
res = await getRoleByTenantId(tenantId);
}
if (res.code === 0 && res.data) {
roleList.value = Array.isArray(res.data) ? res.data : [];
} else {