Files
yunzer_go/pc/src/stores/oa.js
T
2025-11-06 15:56:29 +08:00

610 lines
17 KiB
JavaScript

import { defineStore } from 'pinia';
import { ref, computed } from 'vue';
import {
getTenantDepartments,
getDepartmentInfo
} from '@/api/department';
import {
getTenantPositions,
getPositionsByDepartment
} from '@/api/position';
import { getRoleByTenantId } from '@/api/role';
import { getOABaseData } from '@/api/oa';
import { useAuthStore } from '@/stores/auth';
/**
* OA 基础数据 Store
* 统一管理部门、职位、角色等基础数据,并提供缓存机制
*/
export const useOAStore = defineStore('oa', () => {
// ========== 状态定义 ==========
const departments = ref([]); // 部门列表(扁平结构)
const departmentTree = ref([]); // 部门树结构
const positions = ref([]); // 职位列表
const roles = ref([]); // 角色列表
// 缓存时间戳(毫秒)
const cacheTime = 5 * 60 * 1000; // 5分钟缓存
const departmentsCacheTime = ref(0);
const positionsCacheTime = ref(0);
const rolesCacheTime = ref(0);
// 加载状态
const loadingDepartments = ref(false);
const loadingPositions = ref(false);
const loadingRoles = ref(false);
// ========== 工具函数 ==========
const authStore = useAuthStore();
// 获取当前租户ID
const getCurrentTenantId = () => {
if (authStore.user && authStore.user.tenant_id) {
return authStore.user.tenant_id;
}
const userInfo = localStorage.getItem('userInfo');
if (userInfo) {
try {
const user = JSON.parse(userInfo);
return user.tenant_id || user.tenantId || 0;
} catch (e) {
console.error('Failed to parse user info:', e);
}
}
return 0;
};
// 检查缓存是否有效
const isCacheValid = (cacheTimeValue) => {
return Date.now() - cacheTimeValue < cacheTime;
};
// 构建部门树
const buildDepartmentTree = (deptList, tenantId) => {
if (!deptList || deptList.length === 0) {
return [];
}
// 先按tenant_id过滤
let filteredList = deptList;
if (tenantId !== undefined && tenantId !== null) {
filteredList = deptList.filter((dept) => {
const deptTenantId = dept.tenant_id || dept.tenantId;
return deptTenantId === tenantId || deptTenantId === 0;
});
}
const tree = [];
const map = new Map();
// 第一遍:创建所有节点的映射
filteredList.forEach((dept) => {
if (!dept.id) return;
map.set(dept.id, {
...dept,
children: [],
});
});
// 第二遍:构建树结构
filteredList.forEach((dept) => {
if (!dept.id) return;
const node = map.get(dept.id);
if (!node) return;
const parentId = dept.parent_id || 0;
if (parentId === 0 || parentId === null || parentId === undefined) {
tree.push(node);
} else {
const parent = map.get(parentId);
if (parent) {
parent.children.push(node);
} else {
tree.push(node);
}
}
});
// 排序
const sortTree = (nodes) => {
nodes.sort((a, b) => (a.sort_order || 0) - (b.sort_order || 0));
nodes.forEach((node) => {
if (node.children && node.children.length > 0) {
sortTree(node.children);
}
});
};
sortTree(tree);
return tree;
};
// 解析接口返回的数据
const parseResponse = (res) => {
if (Array.isArray(res)) {
return res;
} else if (res?.data && Array.isArray(res.data)) {
return res.data;
} else if (res?.data?.data && Array.isArray(res.data.data)) {
return res.data.data;
} else if (res?.data) {
return res.data;
}
return [];
};
// ========== 部门相关方法 ==========
/**
* 获取部门列表(带缓存)
* @param {boolean} forceRefresh 是否强制刷新,忽略缓存
* @returns {Promise}
*/
const fetchDepartments = async (forceRefresh = false) => {
// 如果缓存有效且不强制刷新,直接返回缓存数据
if (!forceRefresh && isCacheValid(departmentsCacheTime.value) && departments.value.length > 0) {
return departments.value;
}
// 如果正在加载,等待加载完成
if (loadingDepartments.value) {
return new Promise((resolve) => {
const checkInterval = setInterval(() => {
if (!loadingDepartments.value) {
clearInterval(checkInterval);
resolve(departments.value);
}
}, 100);
});
}
loadingDepartments.value = true;
try {
const tenantId = getCurrentTenantId();
const res = await getTenantDepartments(tenantId);
const deptList = parseResponse(res);
departments.value = deptList;
departmentTree.value = buildDepartmentTree(deptList, tenantId);
departmentsCacheTime.value = Date.now();
return deptList;
} catch (error) {
console.error('获取部门列表失败:', error);
// 如果出错且没有缓存数据,返回空数组
if (departments.value.length === 0) {
departments.value = [];
departmentTree.value = [];
}
throw error;
} finally {
loadingDepartments.value = false;
}
};
/**
* 获取部门树(计算属性)
*/
const getDepartmentTree = computed(() => departmentTree.value);
/**
* 根据ID获取部门信息
*/
const getDepartmentById = (id) => {
const findInTree = (nodes) => {
for (const node of nodes) {
if (node.id === id) return node;
if (node.children && node.children.length > 0) {
const found = findInTree(node.children);
if (found) return found;
}
}
return null;
};
return findInTree(departmentTree.value) || departments.value.find(d => d.id === id);
};
// ========== 职位相关方法 ==========
/**
* 获取职位列表(带缓存)
* @param {number|null} departmentId 部门ID,如果提供则只获取该部门的职位
* @param {boolean} forceRefresh 是否强制刷新
* @returns {Promise}
*/
const fetchPositions = async (departmentId = null, forceRefresh = false) => {
// 如果指定了部门ID,先尝试从已有数据中过滤
if (departmentId) {
// 如果已加载所有职位且缓存有效,直接过滤返回
// 注意:即使某个部门没有职位,只要已加载所有职位,也应该直接返回空数组
if (!forceRefresh && isCacheValid(positionsCacheTime.value)) {
// 如果已加载所有职位(positions.value.length > 0 或已加载过),直接过滤
const filteredPositions = positions.value.filter((pos) => {
return Number(pos.department_id) === Number(departmentId);
});
// 直接返回过滤结果(即使为空数组,也说明该部门确实没有职位)
return filteredPositions;
}
// 如果缓存中没有数据或需要刷新,请求该部门的职位
try {
loadingPositions.value = true;
const res = await getPositionsByDepartment(departmentId);
const posList = parseResponse(res);
// 将获取到的职位合并到职位列表中(去重)
if (posList && posList.length > 0) {
const uniquePositions = new Map();
// 先添加现有职位
positions.value.forEach((pos) => {
uniquePositions.set(Number(pos.id), pos);
});
// 再添加新获取的职位
posList.forEach((pos) => {
const posId = Number(pos.id);
if (posId && !uniquePositions.has(posId)) {
uniquePositions.set(posId, pos);
}
});
positions.value = Array.from(uniquePositions.values());
}
// 更新缓存时间(即使返回空数组也要更新,表示已查询过)
positionsCacheTime.value = Date.now();
return posList || [];
} catch (error) {
console.error('获取职位列表失败:', error);
throw error;
} finally {
loadingPositions.value = false;
}
}
// 如果缓存有效且不强制刷新,直接返回缓存数据
if (!forceRefresh && isCacheValid(positionsCacheTime.value) && positions.value.length > 0) {
return positions.value;
}
// 如果正在加载,等待加载完成
if (loadingPositions.value) {
return new Promise((resolve) => {
const checkInterval = setInterval(() => {
if (!loadingPositions.value) {
clearInterval(checkInterval);
resolve(positions.value);
}
}, 100);
});
}
loadingPositions.value = true;
try {
const tenantId = getCurrentTenantId();
const res = await getTenantPositions(tenantId);
const posList = parseResponse(res);
// 去重
const uniquePositions = new Map();
posList.forEach((pos) => {
const posId = Number(pos.id);
if (posId && !uniquePositions.has(posId)) {
uniquePositions.set(posId, pos);
}
});
positions.value = Array.from(uniquePositions.values());
positionsCacheTime.value = Date.now();
return positions.value;
} catch (error) {
console.error('获取职位列表失败:', error);
if (positions.value.length === 0) {
positions.value = [];
}
throw error;
} finally {
loadingPositions.value = false;
}
};
/**
* 根据ID获取职位信息
*/
const getPositionById = (id) => {
return positions.value.find(p => p.id === id);
};
// ========== 角色相关方法 ==========
/**
* 获取角色列表(带缓存)
* @param {boolean} forceRefresh 是否强制刷新
* @returns {Promise}
*/
const fetchRoles = async (forceRefresh = false) => {
// 如果缓存有效且不强制刷新,直接返回缓存数据
if (!forceRefresh && isCacheValid(rolesCacheTime.value) && roles.value.length > 0) {
return roles.value;
}
// 如果正在加载,等待加载完成
if (loadingRoles.value) {
return new Promise((resolve) => {
const checkInterval = setInterval(() => {
if (!loadingRoles.value) {
clearInterval(checkInterval);
resolve(roles.value);
}
}, 100);
});
}
loadingRoles.value = true;
try {
const tenantId = getCurrentTenantId();
const res = await getRoleByTenantId(tenantId);
// 兼容接口返回的数据结构
if (res?.data && Array.isArray(res.data)) {
roles.value = res.data;
} else if (res?.data?.data && Array.isArray(res.data.data)) {
roles.value = res.data.data;
} else if (Array.isArray(res)) {
roles.value = res;
} else {
roles.value = [];
}
rolesCacheTime.value = Date.now();
return roles.value;
} catch (error) {
console.error('获取角色列表失败:', error);
if (roles.value.length === 0) {
roles.value = [];
}
throw error;
} finally {
loadingRoles.value = false;
}
};
/**
* 根据ID获取角色信息
*/
const getRoleById = (id) => {
return roles.value.find(r => r.roleId === id || r.id === id);
};
// ========== 批量获取方法 ==========
/**
* 批量获取所有基础数据(用于页面初始化)
* 使用合并接口,一次性获取所有数据,减少网络请求次数
* @param {boolean} forceRefresh 是否强制刷新
* @returns {Promise}
*/
const fetchAllBaseData = async (forceRefresh = false) => {
// 如果缓存有效且不强制刷新,直接返回
if (!forceRefresh &&
isCacheValid(departmentsCacheTime.value) &&
isCacheValid(positionsCacheTime.value) &&
isCacheValid(rolesCacheTime.value) &&
departments.value.length > 0 &&
positions.value.length > 0 &&
roles.value.length > 0) {
return;
}
// 如果正在加载,等待加载完成
if (loadingDepartments.value || loadingPositions.value || loadingRoles.value) {
return new Promise((resolve) => {
const checkInterval = setInterval(() => {
if (!loadingDepartments.value && !loadingPositions.value && !loadingRoles.value) {
clearInterval(checkInterval);
resolve();
}
}, 100);
});
}
// 设置加载状态
loadingDepartments.value = true;
loadingPositions.value = true;
loadingRoles.value = true;
try {
const tenantId = getCurrentTenantId();
// 调用合并接口,一次性获取所有数据
const res = await getOABaseData(tenantId);
// 解析返回的数据
let baseData;
if (res?.data) {
if (res.data.data) {
baseData = res.data.data;
} else {
baseData = res.data;
}
} else {
baseData = res;
}
// 更新部门数据
if (baseData.departments && Array.isArray(baseData.departments)) {
departments.value = baseData.departments;
departmentTree.value = buildDepartmentTree(baseData.departments, tenantId);
departmentsCacheTime.value = Date.now();
}
// 更新职位数据
if (baseData.positions && Array.isArray(baseData.positions)) {
// 去重
const uniquePositions = new Map();
baseData.positions.forEach((pos) => {
const posId = Number(pos.id);
if (posId && !uniquePositions.has(posId)) {
uniquePositions.set(posId, pos);
}
});
positions.value = Array.from(uniquePositions.values());
positionsCacheTime.value = Date.now();
}
// 更新角色数据
if (baseData.roles && Array.isArray(baseData.roles)) {
roles.value = baseData.roles;
rolesCacheTime.value = Date.now();
}
} catch (error) {
console.error('批量获取基础数据失败:', error);
// 如果合并接口失败,回退到分别请求
console.warn('合并接口失败,回退到分别请求');
try {
await Promise.all([
fetchDepartments(forceRefresh),
fetchPositions(null, forceRefresh),
fetchRoles(forceRefresh),
]);
} catch (fallbackError) {
console.error('回退请求也失败:', fallbackError);
throw fallbackError;
}
} finally {
loadingDepartments.value = false;
loadingPositions.value = false;
loadingRoles.value = false;
}
};
// ========== 缓存管理方法 ==========
/**
* 清除所有缓存
*/
const clearCache = () => {
departmentsCacheTime.value = 0;
positionsCacheTime.value = 0;
rolesCacheTime.value = 0;
};
/**
* 清除部门缓存
*/
const clearDepartmentsCache = () => {
departmentsCacheTime.value = 0;
};
/**
* 清除职位缓存
*/
const clearPositionsCache = () => {
positionsCacheTime.value = 0;
};
/**
* 清除角色缓存
*/
const clearRolesCache = () => {
rolesCacheTime.value = 0;
};
/**
* 临时添加部门到列表(用于显示不在当前租户列表中的部门)
*/
const addTemporaryDepartment = (deptData) => {
if (!deptData || !deptData.id) return;
// 检查是否已存在
const exists = departments.value.some((dept) => dept.id === deptData.id);
if (!exists) {
departments.value.push(deptData);
// 重新构建部门树
const tenantId = getCurrentTenantId();
departmentTree.value = buildDepartmentTree(departments.value, tenantId);
}
};
/**
* 临时添加职位到列表(用于显示不在当前租户列表中的职位)
*/
const addTemporaryPosition = (posData) => {
if (!posData || !posData.id) return;
// 检查是否已存在
const exists = positions.value.some((pos) => pos.id === posData.id);
if (!exists) {
positions.value.push(posData);
}
};
/**
* 临时添加角色到列表(用于显示不在当前租户列表中的角色)
*/
const addTemporaryRole = (roleData) => {
if (!roleData) return;
// 获取角色ID(兼容不同的字段名)
const roleId = roleData.roleId || roleData.id || roleData.role_id;
if (!roleId) return;
// 检查是否已存在
const exists = roles.value.some((role) => {
const rId = role.roleId || role.id || role.role_id;
return Number(rId) === Number(roleId);
});
if (!exists) {
roles.value.push(roleData);
}
};
/**
* 刷新指定数据(清除缓存并重新获取)
*/
const refreshDepartments = () => fetchDepartments(true);
const refreshPositions = () => fetchPositions(null, true);
const refreshRoles = () => fetchRoles(true);
const refreshAll = () => fetchAllBaseData(true);
return {
// 状态
departments,
departmentTree,
positions,
roles,
loadingDepartments,
loadingPositions,
loadingRoles,
// 计算属性
getDepartmentTree,
// 方法
fetchDepartments,
fetchPositions,
fetchRoles,
fetchAllBaseData,
getDepartmentById,
getPositionById,
getRoleById,
// 缓存管理
clearCache,
clearDepartmentsCache,
clearPositionsCache,
clearRolesCache,
refreshDepartments,
refreshPositions,
refreshRoles,
refreshAll,
// 临时数据添加
addTemporaryDepartment,
addTemporaryPosition,
addTemporaryRole,
};
});