提交代码

This commit is contained in:
zlwu committed 2025-07-21 20:15:31 +08:00
1 parent 225a268e32
commit 7ce20647f0
28 files changed
+1451 -420

No files matched your search

+34
View File
@@ -61,3 +61,37 @@ export const delDormManager = (id: string | number | Array<string | number>) =>
method: 'delete'
});
};
/**
* 分配管理员数据范围
* @param managerId 管理员ID
* @param authType 授权类型(1全校级 2校区级 3宿舍区级 4楼栋级)
* @param authSourceIds 授权来源ID列表
*/
export const assignScope = (managerId: string | number, authType: number, authSourceIds: (string | number)[]) => {
return request({
url: '/glysqgx/dormManagerSource/assignScope',
method: 'post',
params: {
managerId,
authType
},
data: authSourceIds
});
};
/**
* 获取管理员授权范围名称
* @param managerId 管理员ID
* @param managerType 管理员类型
*/
export const getManagerScopeNames = (managerId: string | number, managerType: number) => {
return request({
url: '/glysqgx/dormManagerSource/getScopeNames',
method: 'get',
params: {
managerId,
managerType
}
});
};
+5
View File
@@ -49,6 +49,11 @@ export interface DormManagerVO {
*/
remark: string;
/**
* 授权范围名称(逗号分隔的字符串)
*/
scopeNames?: string;
}
export interface DormManagerForm extends BaseEntity {
+12 -1
View File
@@ -1,6 +1,6 @@
import request from '@/utils/request';
import { AxiosPromise } from 'axios';
import { DormBuildingVO, DormBuildingForm, DormBuildingQuery } from '@/api/sslxx/dormBuilding/types';
import { DormBuildingVO, DormBuildingForm, DormBuildingQuery, BuildingTreeSelectVo } from '@/api/sslxx/dormBuilding/types';
/**
* 查询宿舍楼信息列表
@@ -83,3 +83,14 @@ export const listAreaTreeData = (campusId?: string | number): AxiosPromise<any>
params: { campusId }
});
};
/**
* 获取宿舍楼树形结构数据(校区-宿舍区-宿舍楼三级结构)
* 校区和宿舍区节点不可选,只有宿舍楼节点可以选择
*/
export const getBuildingTree = (): AxiosPromise<BuildingTreeSelectVo[]> => {
return request({
url: '/sslxx/dormBuilding/buildingTree',
method: 'get'
});
};
+24 -103
View File
@@ -1,125 +1,46 @@
export interface DormBuildingVO {
/**
* 主键ID
*/
id: string | number;
/**
* 宿舍楼编码
*/
buildingCode: string;
/**
* 宿舍楼名称
*/
buildingName: string;
/**
* 所属宿舍区ID
*/
areaId: string | number;
/**
* 所属校区ID
*/
campusId: string | number;
/**
* 宿舍楼类型(1男生楼 2女生楼 3混合楼)
*/
buildingType: string;
/**
* 宿舍楼描述
*/
buildingDescription: string;
/**
* 排序号
*/
sortOrder: number;
createTime: string;
createBy: string;
updateTime: string;
updateBy: string;
remark: string;
areaName?: string;
campusName?: string;
}
export interface DormBuildingForm extends BaseEntity {
/**
* 主键ID
*/
id?: string | number;
export interface DormBuildingForm extends Omit<DormBuildingVO, 'createTime' | 'createBy' | 'updateTime' | 'updateBy' | 'areaName' | 'campusName'> {
}
/**
* 宿舍楼编码
*/
export interface DormBuildingQuery {
buildingCode?: string;
/**
* 宿舍楼名称
*/
buildingName?: string;
/**
* 所属宿舍区ID
*/
areaId?: string | number;
/**
* 所属校区ID
*/
campusId?: string | number;
/**
* 宿舍楼类型(1男生楼 2女生楼 3混合楼)
*/
buildingType?: string;
/**
* 宿舍楼描述
*/
buildingDescription?: string;
/**
* 排序号
*/
sortOrder?: number;
params?: Record<string, any>;
}
export interface DormBuildingQuery extends PageQuery {
/**
* 宿舍楼编码
*/
buildingCode?: string;
/**
* 宿舍楼名称
*/
buildingName?: string;
/**
* 所属宿舍区ID
*/
areaId?: string | number;
/**
* 所属校区ID
*/
campusId?: string | number;
/**
* 宿舍楼类型(1男生楼 2女生楼 3混合楼)
*/
buildingType?: string;
/**
* 排序号
*/
sortOrder?: number;
/**
* 日期范围参数
*/
params?: any;
/**
* 宿舍楼树形结构节点
*/
export interface BuildingTreeSelectVo {
id: number;
label: string;
nodeType: 'campus' | 'area' | 'building';
disabled: boolean;
children?: BuildingTreeSelectVo[];
createBy?: number;
createTime?: string;
updateBy?: number;
updateTime?: string;
}
+12 -1
View File
@@ -1,6 +1,6 @@
import request from '@/utils/request';
import { AxiosPromise } from 'axios';
import { DormAreaVO, DormAreaForm, DormAreaQuery } from '@/api/ssqxx/dormArea/types';
import { DormAreaVO, DormAreaForm, DormAreaQuery, TreeSelectVo } from '@/api/ssqxx/dormArea/types';
/**
* 查询宿舍区信息列表
@@ -61,3 +61,14 @@ export const delDormArea = (id: string | number | Array<string | number>) => {
method: 'delete'
});
};
/**
* 获取宿舍区树形结构数据
* 以校区为根节点,宿舍区为子节点的树形结构
*/
export const getAreaTree = (): AxiosPromise<TreeSelectVo[]> => {
return request({
url: '/ssqxx/dormArea/tree',
method: 'get'
});
};
+16
View File
@@ -87,5 +87,21 @@ export interface DormAreaQuery extends PageQuery {
params?: any;
}
/**
* 树形选择数据类型
*/
export interface TreeSelectVo {
/** 节点ID */
id: number;
/** 节点名称 */
label: string;
/** 节点类型(campus:校区, area:宿舍区) */
type: string;
/** 是否禁用选择 */
disabled: boolean;
/** 子节点 */
children?: TreeSelectVo[];
}
+328 -41
View File
@@ -5,18 +5,70 @@
<span>{{ admin?.name }}</span>
</el-form-item>
<el-form-item label="管理范围" prop="scope">
<!-- 校区类型使用原有的多选下拉框 -->
<el-select
v-if="type === 'campus'"
v-model="form.scope"
placeholder="请选择管理范围"
style="width: 100%"
clearable
filterable
:multiple="isCampusType"
multiple
collapse-tags
default-expand-all
collapse-tags-tooltip
>
<el-option v-for="item in scopeOptions" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
<!-- 宿舍区类型使用树形选择器 -->
<el-tree-select
v-else-if="type === 'area'"
v-model="form.scope"
placeholder="请选择宿舍区"
style="width: 100%"
clearable
filterable
multiple
collapse-tags
collapse-tags-tooltip
default-expand-all
:data="treeData"
:props="{
children: 'children',
label: 'label',
disabled: 'disabled',
value: 'id'
}"
check-strictly
/>
<!-- 宿舍楼类型使用新的树形选择器 -->
<el-tree-select
v-else-if="type === 'building'"
v-model="form.scope"
placeholder="请选择宿舍楼"
style="width: 100%"
clearable
filterable
multiple
collapse-tags
collapse-tags-tooltip
default-expand-all
:data="buildingTreeData"
:props="{
children: 'children',
label: 'label',
disabled: 'disabled',
value: 'id'
}"
check-strictly
/>
<!-- 其他类型保持原有的单选下拉框 -->
<el-select v-else v-model="form.scope" placeholder="请选择管理范围" style="width: 100%" clearable filterable>
<el-option v-for="item in scopeOptions" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</el-form-item>
</el-form>
<template #footer>
@@ -31,6 +83,10 @@
<script lang="ts">
import { defineComponent, ref, reactive, computed, watch, onMounted } from 'vue';
import { listDormCampus } from '@/api/xqxx/dormCampus';
import { getAreaTree } from '@/api/ssqxx/dormArea';
import { getBuildingTree } from '@/api/sslxx/dormBuilding';
import type { TreeSelectVo } from '@/api/ssqxx/dormArea/types';
import type { BuildingTreeSelectVo } from '@/api/sslxx/dormBuilding/types';
export default defineComponent({
name: 'ScopeDialog',
@@ -59,47 +115,231 @@ export default defineComponent({
// 判断是否为校区类型的计算属性
const isCampusType = computed(() => props.type === 'campus');
// 判断是否为宿舍区类型的计算属性
const isAreaType = computed(() => props.type === 'area');
// 判断是否为宿舍楼类型的计算属性
const isBuildingType = computed(() => props.type === 'building');
const formRef = ref();
const form = reactive({
scope: isCampusType.value ? [] : ''
scope: isCampusType.value || isAreaType.value || isBuildingType.value ? [] : ''
});
// 选项数据
// 下拉选项数据
const scopeOptions = ref<any[]>([]);
// 宿舍区树形数据
const treeData = ref<TreeSelectVo[]>([]);
// 宿舍楼树形数据
const buildingTreeData = ref<BuildingTreeSelectVo[]>([]);
// 标记是否完成数据加载的状态
const dataLoaded = ref(false);
// 处理scope字符串为数组的辅助函数(校区使用)
const parseScopeString = (scopeString: string) => {
if (!scopeString) return [];
// 尝试按逗号分隔并提取ID (假设格式为: "东校区,西校区")
return scopeString
.split(',')
.map((item) => {
// 在校区选项中查找匹配的校区名称
const option = scopeOptions.value.find((opt) => opt.label === item.trim());
return option ? option.value : null;
})
.filter((id) => id !== null); // 过滤掉未找到的项
};
// 处理宿舍区scope字符串为数组的辅助函数
const parseAreaScopeString = (scopeString: string) => {
if (!scopeString) return [];
// 尝试按逗号分隔宿舍区名称
const areaNames = scopeString.split(',').map((item) => item.trim());
// 在树形数据中查找匹配的宿舍区ID
const findAreaIds = (nodes: TreeSelectVo[]): number[] => {
if (!nodes || nodes.length === 0) return [];
let result: number[] = [];
for (const node of nodes) {
// 如果节点标签匹配任何一个宿舍区名称,添加该节点ID
if (areaNames.includes(node.label)) {
result.push(node.id);
}
// 如果有子节点,递归查找
if (node.children && node.children.length > 0) {
result = [...result, ...findAreaIds(node.children)];
}
}
return result;
};
return findAreaIds(treeData.value);
};
// 处理宿舍楼scope字符串为数组的辅助函数
const parseBuildingScopeString = (scopeString: string) => {
if (!scopeString) return [];
// 尝试按逗号分隔宿舍楼名称
const buildingNames = scopeString.split(',').map((item) => item.trim());
// 在树形数据中查找匹配的宿舍楼ID
const findBuildingIds = (nodes: BuildingTreeSelectVo[]): number[] => {
if (!nodes || nodes.length === 0) return [];
let result: number[] = [];
for (const node of nodes) {
// 如果是宿舍楼节点,且名称匹配,添加该节点ID
if (node.nodeType === 'building' && buildingNames.includes(node.label)) {
result.push(node.id);
}
// 如果有子节点,递归查找
if (node.children && node.children.length > 0) {
result = [...result, ...findBuildingIds(node.children)];
}
}
return result;
};
return findBuildingIds(buildingTreeData.value);
};
// 根据类型加载选项
const loadScopeOptions = async () => {
dataLoaded.value = false;
if (isCampusType.value) {
// 调用API获取校区列表
const res = await listDormCampus();
// 适配el-select格式
console.log(res);
scopeOptions.value = (res.rows || []).map((item: any) => ({
value: item.id,
label: item.campusName
}));
// 校区支持多选,初始化为数组
form.scope = Array.isArray(form.scope) ? form.scope : [];
} else if (props.type === 'area') {
scopeOptions.value = [
{ value: '1号宿舍区', label: '1号宿舍区' },
{ value: '2号宿舍区', label: '2号宿舍区' },
{ value: '3号宿舍区', label: '3号宿舍区' }
];
// 非校区为单选,初始化为字符串
form.scope = Array.isArray(form.scope) ? '' : form.scope;
try {
const res = await listDormCampus();
// 适配el-select格式
console.log('校区数据加载成功:', res);
scopeOptions.value = (res.rows || []).map((item: any) => ({
value: item.id,
label: item.campusName
}));
// 校区支持多选,初始化为数组
form.scope = Array.isArray(form.scope) ? form.scope : [];
dataLoaded.value = true;
// 数据加载完成后,处理当前管理员的scope
handleAdminScope();
} catch (error) {
console.error('加载校区数据失败:', error);
dataLoaded.value = true;
}
} else if (isAreaType.value) {
// 宿舍区类型时加载树形数据
try {
const res = await getAreaTree();
if (res && res.data) {
treeData.value = res.data;
console.log('宿舍区树形数据加载成功:', treeData.value);
}
// 宿舍区使用树形选择,支持多选
form.scope = Array.isArray(form.scope) ? form.scope : [];
dataLoaded.value = true;
// 数据加载完成后,处理当前管理员的scope
handleAdminScope();
} catch (error) {
console.error('加载树形数据失败:', error);
dataLoaded.value = true;
}
} else if (isBuildingType.value) {
// 宿舍楼类型时加载树形数据
try {
const res = await getBuildingTree();
if (res && res.data) {
buildingTreeData.value = res.data;
console.log('宿舍楼树形数据加载成功:', buildingTreeData.value);
}
// 宿舍楼使用树形选择,支持多选
form.scope = Array.isArray(form.scope) ? form.scope : [];
dataLoaded.value = true;
// 数据加载完成后,处理当前管理员的scope
handleAdminScope();
} catch (error) {
console.error('加载宿舍楼树形数据失败:', error);
dataLoaded.value = true;
}
} else if (props.type === 'building') {
scopeOptions.value = [
{ value: '1号楼', label: '1号楼' },
{ value: '2号楼', label: '2号楼' },
{ value: '3号楼', label: '3号楼' },
{ value: '4号楼', label: '4号楼' }
];
// 非校区为单选,初始化为字符串
form.scope = Array.isArray(form.scope) ? '' : form.scope;
// 这段代码已被上面的isBuildingType.value条件替代,保留此处以兼容旧代码
// 将在下一次重构时移除
dataLoaded.value = true;
// 数据加载完成后,处理当前管理员的scope
handleAdminScope();
} else {
scopeOptions.value = [];
dataLoaded.value = true;
// 数据加载完成后,处理当前管理员的scope
handleAdminScope();
}
};
// 创建单独的函数处理admin的scope,确保在数据加载完成后执行
const handleAdminScope = () => {
const admin = props.admin;
if (!admin || !dataLoaded.value) return;
// 根据类型决定如何设置scope值
if (isCampusType.value) {
// 校区类型 - 支持多选
if (admin.scope) {
// 判断scope的类型
if (Array.isArray(admin.scope)) {
// 如果已经是数组,直接使用
form.scope = admin.scope;
} else if (typeof admin.scope === 'string') {
// 如果是字符串,需要解析
form.scope = parseScopeString(admin.scope);
} else {
// 其他情况,初始化为空数组
form.scope = [];
}
console.log('校区管理员回显数据:', form.scope);
} else {
form.scope = [];
}
} else if (isAreaType.value) {
// 宿舍区类型 - 支持多选
if (admin.scope) {
if (Array.isArray(admin.scope)) {
// 如果已经是数组,直接使用
form.scope = admin.scope;
} else if (typeof admin.scope === 'string') {
// 如果是字符串,需要解析为树形选择器所需的ID数组
form.scope = parseAreaScopeString(admin.scope);
} else {
form.scope = [];
}
console.log('宿舍区管理员回显数据:', form.scope);
} else {
form.scope = [];
}
} else if (isBuildingType.value) {
// 宿舍楼类型 - 支持多选
if (admin.scope) {
if (Array.isArray(admin.scope)) {
// 如果已经是数组,直接使用
form.scope = admin.scope;
} else if (typeof admin.scope === 'string') {
// 如果是字符串,需要解析为树形选择器所需的ID数组
form.scope = parseBuildingScopeString(admin.scope);
} else {
form.scope = [];
}
console.log('宿舍楼管理员回显数据:', form.scope);
} else {
form.scope = [];
}
} else {
// 其他类型 - 单选
form.scope = admin.scope || '';
}
};
@@ -112,35 +352,82 @@ export default defineComponent({
{ immediate: true }
);
// 监听对话框可见性,确保每次打开时都重新加载数据
watch(
() => dialogVisible.value,
(newVal) => {
if (newVal) {
// 对话框打开时,确保重新加载数据
loadScopeOptions();
}
}
);
// 监听admin变化
watch(
() => props.admin,
(newVal) => {
if (newVal && newVal.scope) {
// 根据类型决定如何设置scope值
if (isCampusType.value) {
// 校区支持多选,确保scope是数组
form.scope = Array.isArray(newVal.scope) ? newVal.scope : [newVal.scope];
} else {
// 其他类型是单选
form.scope = newVal.scope;
}
() => {
// 如果数据已加载完成,则立即处理admin的scope
if (dataLoaded.value) {
handleAdminScope();
}
// 否则,会在loadScopeOptions完成后自动调用handleAdminScope
},
{ deep: true, immediate: true }
{ deep: true }
);
const submitForm = () => {
emit('submit', form.scope);
// 将form.scope转换为API所需的格式
let result;
if (Array.isArray(form.scope)) {
// 对于多选,发送选中的ID数组
result = {
sourceIds: form.scope
};
} else {
// 对于单选,将字符串ID包装在数组中
result = {
sourceIds: form.scope ? [form.scope] : []
};
}
emit('submit', result);
dialogVisible.value = false;
};
// 在对话框打开时,重置数据状态,防止旧数据影响新的显示
watch(
() => dialogVisible.value,
(newVal) => {
if (newVal) {
// 对话框打开时,先清空数据,确保重新加载
scopeOptions.value = [];
treeData.value = [];
buildingTreeData.value = [];
dataLoaded.value = false;
// 根据类型设置默认值
if (isCampusType.value || isAreaType.value || isBuildingType.value) {
form.scope = [];
} else {
form.scope = '';
}
// 然后加载新数据
loadScopeOptions();
}
}
);
return {
dialogVisible,
form,
formRef,
scopeOptions,
treeData,
buildingTreeData,
submitForm,
isCampusType
isCampusType,
type: computed(() => props.type)
};
}
});
+125 -44
View File
@@ -218,11 +218,17 @@
import { ref, reactive, onMounted } from 'vue';
import AdminDialog from './components/AdminDialog.vue';
import ScopeDialog from './components/ScopeDialog.vue';
import axios from 'axios';
import { listDormManager } from '@/api/ssglyxx/dormManager';
import { delDormManager } from '@/api/ssglyxx/dormManager';
import request from '@/utils/request';
import { ElMessage, ElMessageBox } from 'element-plus';
// API路径
const apiUrl = {
list: '/ssglyxx/dormManager/list',
delete: '/ssglyxx/dormManager/',
assignScope: '/glysqgx/dormManagerSource/assignScope',
getScopeNames: '/glysqgx/dormManagerSource/getScopeNames'
};
const activeTab = ref('school');
// 各tab独立检索表单
@@ -287,38 +293,80 @@ async function fetchAdmins(type: string) {
Object.keys(params).forEach((key) => {
if (params[key] === '' || params[key] === undefined) delete params[key];
});
// const res = await axios.get('/ssglyxx/dormManager/list', { params });
const res = await listDormManager(params);
// 兼容TableDataInfo和R结构
let data = [];
if (Array.isArray(res.data)) {
data = res.data;
} else if (res.rows) {
data = res?.rows;
}
// console.log(data);
const mappedData = data.map((item: any) => ({
id: item.id,
name: item.managerName, // 映射
phone: item.phone,
sex: typeof item.gender === 'number' ? item.gender : Number(item.gender), // gender转数字
remark: item.remark
// 其他字段按需添加
}));
// console.log(mappedData);
switch (type) {
case 'school':
schoolAdmins.value = mappedData;
break;
case 'campus':
campusAdmins.value = mappedData;
break;
case 'area':
areaAdmins.value = mappedData;
break;
case 'building':
buildingAdmins.value = mappedData;
break;
try {
// 获取管理员列表
const res = await request({
url: apiUrl.list,
method: 'get',
params
});
// 兼容TableDataInfo和R结构
let data = [];
if (Array.isArray(res.data)) {
data = res.data;
} else if (res.rows) {
data = res?.rows;
}
// 预处理管理员数据
const mappedData = await Promise.all(
data.map(async (item: any) => {
// 基础字段映射
const manager = {
id: item.id,
name: item.managerName, // 映射
phone: item.phone,
sex: typeof item.gender === 'number' ? item.gender : Number(item.gender), // gender转数字
remark: item.remark,
scope: '' // 初始化scope字段
};
// 非全校级管理员需要获取授权范围
if (managerType > 1) {
try {
// 获取授权范围名称
const scopeRes = await request({
url: apiUrl.getScopeNames,
method: 'get',
params: {
managerId: item.id,
managerType: managerType
}
});
console.log(scopeRes.msg);
if (scopeRes) {
manager.scope = scopeRes.msg;
}
} catch (error) {
console.error('获取管理员授权范围失败:', error);
}
}
return manager;
})
);
// 更新对应类型的管理员数据
switch (type) {
case 'school':
schoolAdmins.value = mappedData;
break;
case 'campus':
campusAdmins.value = mappedData;
break;
case 'area':
areaAdmins.value = mappedData;
break;
case 'building':
buildingAdmins.value = mappedData;
break;
}
} catch (error) {
console.error('获取管理员列表失败:', error);
ElMessage.error('获取管理员列表失败');
}
}
@@ -336,8 +384,6 @@ function onAdd(type: string) {
dialogVisible.value = true;
}
function onEdit(type: string, admin: any) {
console.log('onEdit=============');
console.log(admin);
dialogMode.value = 'edit';
currentAdmin.value = { ...admin };
activeTab.value = type; // 确保当前标签页与编辑的管理员类型一致
@@ -352,7 +398,10 @@ async function onDelete(type: string, admin: any) {
})
.then(async () => {
try {
await delDormManager(admin.id);
await request({
url: apiUrl.delete + admin.id,
method: 'delete'
});
ElMessage.success('删除成功');
fetchAdmins(type); // 删除后刷新
} catch (e) {
@@ -380,12 +429,44 @@ function onDialogSubmit(admin: any) {
}
dialogVisible.value = false;
}
function onScopeSubmit(scope: string) {
// TODO: 分配范围时调用API,成功后刷新
const list = getListByType(scopeType.value);
const idx = list.value.findIndex((a: any) => a.id === currentAdmin.value.id);
if (idx !== -1) list.value[idx].scope = scope;
scopeDialogVisible.value = false;
async function onScopeSubmit(scope: any) {
try {
// 将scope类型转换为数字
let authType = 1;
switch (scopeType.value) {
case 'school':
authType = 1;
break;
case 'campus':
authType = 2;
break;
case 'area':
authType = 3;
break;
case 'building':
authType = 4;
break;
}
// 调用API
await request({
url: apiUrl.assignScope,
method: 'post',
params: {
managerId: currentAdmin.value.id,
authType: authType
},
data: scope.sourceIds
});
// 成功后刷新数据并提示
ElMessage.success('管理范围分配成功');
fetchAdmins(scopeType.value);
scopeDialogVisible.value = false;
} catch (error) {
ElMessage.error('管理范围分配失败');
console.error(error);
}
}
function getListByType(type: string) {
switch (type) {