批量更新

This commit is contained in:
2025-11-06 16:40:53 +08:00
parent 6a9b5d413a
commit 873a2e297a
43 changed files with 229 additions and 3321 deletions
+1 -1
View File
@@ -108,7 +108,7 @@ async function refreshCache() {
// 触发菜单刷新事件,通知CommonAside组件刷新菜单
window.dispatchEvent(new CustomEvent('menu-cache-refreshed'));
ElMessage.success('菜单缓存和路由更新成功');
ElMessage.success('更新成功');
} catch (error) {
console.error('Failed to refresh cache', error);
ElMessage.error('更新缓存失败,请检查网络连接');
@@ -1,138 +0,0 @@
# OA 模块优化总结
## 优化完成时间
2024年
## 优化内容
### 1. 创建 OA 基础数据 Store (`pc/src/stores/oa.js`)
**功能特性:**
- ✅ 智能缓存机制(5分钟缓存)
- ✅ 并发请求控制
- ✅ 批量数据获取
- ✅ 统一数据管理
### 2. 重构的页面
#### ✅ 员工管理 (`employees/index.vue`)
- 使用 OA Store 获取部门、职位、角色数据
- 利用缓存机制,减少重复请求
- 数据更新后自动刷新缓存
#### ✅ 部门管理 (`departments/index.vue`)
- 使用 OA Store 管理部门数据
- 添加/编辑/删除后自动刷新缓存
- 使用响应式数据绑定
#### ✅ 职位管理 (`positions/index.vue`)
- 使用 OA Store 获取部门和职位数据
- 利用缓存机制优化性能
- 数据更新后自动刷新缓存
#### ✅ 组织架构 (`organization/index.vue`)
- 使用 OA Store 获取部门和职位数据
- 部门树和职位列表共享同一份数据
- 数据更新后自动刷新缓存
## 性能提升
### 优化前
- 每次进入页面都发起 3-4 个独立请求
- 无缓存机制,频繁切换页面产生大量重复请求
- 服务器压力大
### 优化后
- **首次访问**:并行请求所有基础数据(更快)
- **缓存有效期内**:只请求业务数据(员工列表等)
- **请求次数减少**75% 减少
- **响应速度提升**:缓存命中时,几乎瞬时响应
- **服务器压力降低**:减少 75% 的基础数据请求
## 缓存策略
### 缓存时间
- 默认:5 分钟
- 可配置:在 `oa.js` 中修改 `cacheTime` 常量
### 缓存管理
- 自动失效:缓存过期后自动刷新
- 手动刷新:调用 `refresh` 方法
- 数据更新后:自动刷新相关缓存
## 使用方式
### 在页面中使用
```javascript
import { useOAStore } from '@/stores/oa';
const oaStore = useOAStore();
// 页面初始化
onMounted(async () => {
// 使用批量获取,自动利用缓存
await oaStore.fetchAllBaseData();
// 然后获取业务数据
await fetchEmployees();
});
```
### 数据更新后刷新缓存
```javascript
// 添加/编辑/删除后
await addDepartment(data);
await oaStore.refreshDepartments(); // 刷新部门缓存
```
## 优化效果对比
| 指标 | 优化前 | 优化后 | 提升 |
|------|--------|--------|------|
| 请求次数(缓存命中) | 4 次 | 1 次 | **减少 75%** |
| 响应时间(缓存命中) | ~500ms | ~50ms | **提升 90%** |
| 服务器压力 | 高 | 低 | **降低 75%** |
| 代码复用性 | 低 | 高 | **提升** |
| 数据一致性 | 一般 | 优秀 | **提升** |
## 后续建议
1. **其他模块**:可以将类似的优化应用到其他模块(如用户管理、权限管理等)
2. **缓存时间**:根据业务需求调整缓存时间
3. **后端优化**:考虑提供批量接口,一次返回所有基础数据
4. **监控**:添加性能监控,跟踪缓存命中率
## 注意事项
1. **缓存时间**:默认 5 分钟,可根据业务需求调整
2. **数据一致性**:更新数据后记得调用 `refresh` 方法
3. **租户隔离**:缓存是基于当前租户的,不同租户数据不会混淆
4. **内存占用**:缓存数据存储在内存中,页面刷新后会清空
## 文件清单
### 新增文件
- `pc/src/stores/oa.js` - OA 基础数据 Store
- `pc/src/stores/README_OA.md` - Store 使用文档
- `pc/src/views/apps/oa/OPTIMIZATION_SUMMARY.md` - 优化总结(本文件)
### 重构文件
- `pc/src/views/apps/oa/employees/index.vue` - 员工管理页面
- `pc/src/views/apps/oa/departments/index.vue` - 部门管理页面
- `pc/src/views/apps/oa/positions/index.vue` - 职位管理页面
- `pc/src/views/apps/oa/organization/index.vue` - 组织架构页面
## 测试建议
1. **功能测试**:确保所有 CRUD 操作正常工作
2. **缓存测试**:验证缓存机制是否正常工作
3. **性能测试**:对比优化前后的性能指标
4. **并发测试**:验证并发请求控制是否正常
---
**优化完成!** 🎉
所有 OA 模块页面已成功使用统一的 Store 进行数据管理,大幅提升了性能和代码质量。
@@ -69,6 +69,7 @@
style="width: 100%"
:loading="loadingRoles"
clearable
:key="`role-${filteredRoleList.length}-${form.tenant_id}-${form.role}`"
>
<el-option
v-for="role in filteredRoleList"
@@ -83,7 +84,7 @@
</el-option>
</el-select>
<div v-if="filteredRoleList.length === 0 && !loadingRoles" style="color: #999; font-size: 12px; margin-top: 4px;">
暂无可用角色
暂无可用角色当前租户ID: {{ form.tenant_id }}
</div>
<div v-if="form.role && !hasValidRole" style="color: #f56c6c; font-size: 12px; margin-top: 4px;">
警告当前选择的角色ID ({{ form.role }}) 在角色列表中不存在
@@ -139,32 +140,79 @@ const props = defineProps<{
loadingRoles: boolean;
}>();
// 根据当前员工的 tenant_id 过滤角色列表
// 根据当前员工的 tenant_id 和角色的 default 字段过滤角色列表
const filteredRoleList = computed(() => {
if (!props.roleList || props.roleList.length === 0) {
return [];
}
// 获取当前员工的 tenant_id
const currentTenantId = props.formData?.tenant_id;
// 获取当前员工的 tenant_id(优先使用 form.value.tenant_id,因为表单数据可能已更新)
const currentTenantId = form.value.tenant_id !== null && form.value.tenant_id !== undefined
? form.value.tenant_id
: (props.formData?.tenant_id !== null && props.formData?.tenant_id !== undefined
? props.formData.tenant_id
: null);
// 调试日志
console.log('filteredRoleList - currentTenantId:', currentTenantId);
console.log('filteredRoleList - roleList length:', props.roleList.length);
if (currentTenantId === null || currentTenantId === undefined) {
// 如果没有 tenant_id,返回所有角色(不应该发生,但做个兼容)
console.log('filteredRoleList - 没有 tenant_id,返回所有角色');
return props.roleList;
}
// 过滤角色:显示当前租户的角色(tenant_id 匹配)和公共角色(tenant_id 为 0
return props.roleList.filter((role: any) => {
const currentTenantIdNum = Number(currentTenantId);
// 过滤角色:根据 default 字段和 tenant_id 进行筛选
// default = 1: 只匹配给 tenant_id = 1 的租户
// default = 2: 分配给所有租户使用
// default = 3: 租户单独设置的,给自己用(tenant_id 匹配当前租户)
const filtered = props.roleList.filter((role: any) => {
// 兼容不同的字段名:tenantId 或 tenant_id
const roleTenantId = role.tenantId !== undefined ? role.tenantId : (role.tenant_id !== undefined ? role.tenant_id : null);
const roleDefault = role.default !== undefined ? role.default : (role.Default !== undefined ? role.Default : 2); // 默认为2(所有租户可用)
// 转换为数字进行比较
const currentTenantIdNum = Number(currentTenantId);
const roleTenantIdNum = roleTenantId !== null && roleTenantId !== undefined ? Number(roleTenantId) : null;
const roleDefaultNum = Number(roleDefault);
// 显示当前租户的角色或公共角色(tenant_id 为 0
return roleTenantIdNum === currentTenantIdNum || roleTenantIdNum === 0;
let match = false;
// 情况1: default = 1,只给租户1使用
if (roleDefaultNum === 1) {
match = currentTenantIdNum === 1;
}
// 情况2: default = 2,所有租户可用
else if (roleDefaultNum === 2) {
match = true;
}
// 情况3: default = 3,租户专属(tenant_id 必须匹配)
else if (roleDefaultNum === 3) {
match = roleTenantIdNum === currentTenantIdNum;
}
// 兼容旧数据:如果没有 default 字段,使用旧的逻辑
else {
// 如果 tenant_id = 0,认为是公共角色,所有租户可用
// 如果 tenant_id 匹配,认为是租户专属角色
match = roleTenantIdNum === 0 || roleTenantIdNum === currentTenantIdNum;
}
if (match) {
console.log('filteredRoleList - 匹配角色:', {
id: role.roleId || role.id || role.role_id,
name: role.roleName || role.name || role.role_name,
roleTenantId: roleTenantIdNum,
roleDefault: roleDefaultNum,
currentTenantId: currentTenantIdNum
});
}
return match;
});
console.log('filteredRoleList - 过滤后数量:', filtered.length);
return filtered;
});
// 根据选择的部门过滤职位列表
@@ -267,11 +315,19 @@ watch(() => props.visible, async (val) => {
department_id: props.formData.department_id ? Number(props.formData.department_id) : null,
position_id: props.formData.position_id ? Number(props.formData.position_id) : null,
role: props.formData.role ? Number(props.formData.role) : null,
tenant_id: props.formData.tenant_id ? Number(props.formData.tenant_id) : null,
};
console.log('EmployeeEdit - 编辑模式,设置表单数据:', {
tenant_id: form.value.tenant_id,
role: form.value.role,
formData_tenant_id: props.formData.tenant_id
});
// 再等待一个tick,确保组件能正确渲染
await nextTick();
} else if (val && !props.isEdit) {
// 新增时重置表单
// 从 props.formData 获取 tenant_id(如果有的话)
const tenantId = props.formData?.tenant_id || null;
form.value = {
id: null,
employeeNo: "",
@@ -284,8 +340,11 @@ watch(() => props.visible, async (val) => {
bank_name: "",
bank_account: "",
status: 1,
tenant_id: null,
tenant_id: tenantId ? Number(tenantId) : null,
};
console.log('EmployeeEdit - 新增模式,设置表单数据:', {
tenant_id: form.value.tenant_id
});
}
});
@@ -63,7 +63,7 @@
width="180"
align="center"
/>
<el-table-column label="操作" width="280" align="center" fixed="right">
<el-table-column label="操作" width="340" align="center" fixed="right">
<template #default="scope">
<el-button size="small" @click="handleEdit(scope.row)">编辑</el-button>
<el-button
@@ -79,6 +79,7 @@
<el-button
size="small"
type="danger"
:disabled="scope.row.role === 5"
@click="handleDelete(scope.row)"
>删除</el-button>
</template>
@@ -164,15 +164,15 @@ const handleCollapseAll = () => {
gap: 8px;
margin-top: 8px;
.el-button {
padding: 0;
font-size: 13px;
color: var(--el-color-primary);
// .el-button {
// padding: 0;
// font-size: 13px;
// color: var(--el-color-primary);
&:hover {
color: var(--el-color-primary-light-3);
}
}
// &:hover {
// color: var(--el-color-primary-light-3);
// }
// }
}
}
+6
View File
@@ -458,6 +458,12 @@ const submitForm = async (formData: any) => {
};
const handleDelete = async (employee: Employee) => {
// 如果角色ID是5,不允许删除
if (employee.role === 5) {
ElMessage.warning("该员工的角色不允许删除");
return;
}
ElMessageBox.confirm("确认删除该员工?", "提示", {
confirmButtonText: "确定",
cancelButtonText: "取消",
+14 -2
View File
@@ -273,7 +273,13 @@ const handleTaskChange = (task: any) => {
// 初始化图表
onMounted(() => {
// 折线图
new Chart(document.getElementById("lineChart") as HTMLCanvasElement, {
const lineChartEl = document.getElementById("lineChart") as HTMLCanvasElement | null;
if (!lineChartEl) {
console.error("Line chart element not found");
return;
}
new Chart(lineChartEl, {
type: "line",
data: {
labels: ["1月", "2月", "3月", "4月", "5月", "6月", "7月"],
@@ -332,7 +338,13 @@ onMounted(() => {
});
// 柱状图
new Chart(document.getElementById("barChart") as HTMLCanvasElement, {
const barChartEl = document.getElementById("barChart") as HTMLCanvasElement | null;
if (!barChartEl) {
console.error("Bar chart element not found");
return;
}
new Chart(barChartEl, {
type: "bar",
data: {
labels: ["周一", "周二", "周三", "周四", "周五", "周六", "周日"],