610 lines
17 KiB
JavaScript
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,
|
|
};
|
|
});
|
|
|