直接将一个下拉框做成一个组件
<template>
<el-select
v-model="value"
@change="deptSelectChange"
placeholder="请输入或点击选择"
default-first-option
placement="bottom"
filterable
remote
:multiple="type == 'check'"
style="width: 100%; max-height: 150px; overflow: auto"
:disabled="disabled"
:readonly="readonly"
>
<el-option
v-for="item in deptOptions"
:key="item.value"
:label="item.label"
:value="item.value"
style="z-index: 999"
/>
</el-select>
<el-icon
v-if="!view"
size="small"
@click="selectDeptDialog()"
style="margin-left: -30px; width: 25px; height: 25px; cursor: pointer"
>
<MoreFilled />
</el-icon>
<!-- 部门组件 -->
<DeptDialog
:type="type"
:deptIds="deptIds"
@deptList="selectDeptList"
:nodeTitle="nodeTitle"
ref="deptDialogRef"
></DeptDialog>
</template>
<script setup>
import { ref, onMounted, watch } from 'vue';
import { getDeptTree } from '@/api/system/dept';
const props = defineProps({
// 内容
modelValue: {
type: [String, Array],
default: '',
},
view: {
type: Boolean,
default: false,
},
disabled: {
type: Boolean,
default: false,
},
readonly: {
type: Boolean,
default: false,
},
type: {
type: String,
default: 'radio',
},
nodeTitle: {
type: String,
default: '',
},
});
const emits = defineEmits(['update:modelValue', 'change']);
const value = ref(props.modelValue);
watch(
() => props.type,
newType => {
// 当类型从 'radio' 变为 'check' 时,将 modelValue 转换为数组
if (newType === 'check' && !Array.isArray(value.value)) {
value.value = [value.value];
}
// 当类型从 'check' 变为 'radio' 时,尝试将 modelValue 转换为字符串
else if (newType === 'radio' && Array.isArray(value.value)) {
value.value = value.value[0] || '';
}
}
);
// 监听props.modelValue的变化,以便更新内部状态
watch(
() => props.modelValue,
newValue => {
value.value = newValue;
}
);
// 监听内部value的变化,并通知父组件更新
watch(value, newValue => {
emits('update:modelValue', newValue);
});
//选择部门组件ref
const deptDialogRef = ref();
//部门id用于回显
const deptIds = ref('');
//部门列表
const deptlist = ref([]);
//部门名称下拉数据
const deptOptions = ref([]);
//获取部门名称模糊搜索数据
const deptNameMessage = async () => {
try {
const res = await getDeptTree();
let result = [];
if (props.nodeTitle) {
result = findAllNodesUnderStationSection(res.data.data, props.nodeTitle);
} else {
result = res.data.data;
}
const flattenedData = flattenTreeData(result);
deptOptions.value = flattenedData;
deptlist.value = flattenedData;
} catch (error) {
console.error('获取部门树失败:', error);
}
};
//打开选择部门组件弹框
const selectDeptDialog = () => {
setTimeout(() => {
deptDialogRef.value.openDialog();
}, 200);
};
//弹框组件选择部门回调
const selectDeptList = list => {
if (props.type == 'check') {
value.value = list.map(item => item.id);
deptIds.value = list.map(item => item.id).join(',');
} else {
value.value = list[0].id;
deptIds.value = list[0].id;
}
emits('update:modelValue', value.value);
emits('change', value.value);
};
// 下拉选择部门
const deptSelectChange = item => {
if (props.type === 'check') {
value.value = item;
deptIds.value = item.join(',');
} else {
value.value = item;
deptIds.value = item;
}
emits('update:modelValue', value.value);
emits('change', value.value);
};
// 扁平化树形数据
const flattenTreeData = (nodes, flattenedList = []) => {
nodes.forEach(node => {
const { title: label, id: value, children } = node;
const newNode = { label, value }; // 仅保留label和value
flattenedList.push(newNode); // 添加当前节点到结果数组
if (children && children.length > 0) {
flattenTreeData(children, flattenedList); // 递归处理子节点
}
});
return flattenedList;
};
// 查找所有“站段”节点
const findAllNodesUnderStationSection = (nodes, nodeKey, result = []) => {
nodes.forEach(node => {
// 检查当前节点是否名称为“站段”
if (node.title == nodeKey && node.parentId == 0) {
// 如果是,将当前节点添加到结果中
result.push(node);
}
// 如果当前节点有子节点,递归遍历子节点
if (node.children && node.children.length > 0) {
findAllNodesUnderStationSection(node.children, result);
}
});
return result;
};
onMounted(() => {
deptNameMessage();
});
</script>
<style lang="scss" scoped></style>
弹框组件
<template>
<el-dialog
@close="selectCancel"
append-to-body
title="选择部门"
v-model="platformBox"
width="344"
>
<div style="margin-right: 5px">
<el-input
placeholder="搜索部门分类"
style="width: 304px; margin-bottom: 5px"
v-model="inputValue"
></el-input>
</div>
<div class="title-style" style="position: sticky">
<span class="tree-title">部门分类</span>
<el-button
@click="dialogChink"
class="add-button"
icon="CirclePlus"
slot="append"
v-if="userInfo.role_name.includes('admin')"
>
<span style="margin-left: 1px">添加</span>
</el-button>
</div>
<div style="height: 20vh; overflow: auto">
<el-tree
:current-node-key="checkedNodeKeys"
:data="dataSource"
:default-checked-keys="checkedKeys"
:filter-node-method="filterNode"
:show-checkbox="showCheckbox"
@node-click="nodeClick"
check-strictly
class="filter-tree"
default-expand-all
icon="ArrowRightBold"
node-key="id"
ref="treeRef"
@check-change="handleCheckChange"
>
<template v-slot="{ data }">
<span class="custom-node">{{ data.title }}</span>
</template>
</el-tree>
</div>
<template #footer>
<span class="dialog-footer">
<el-button @click="selectData" type="primary"> 确定 </el-button
><el-button @click="selectCancel">取消</el-button>
</span>
</template>
<!--crud-->
<el-dialog
destroy-on-close
:title="title"
v-model="dialogFormVisible"
:before-close="resetForm"
width="400px"
append-to-body
draggable
>
<el-form
:model="form"
:rules="rules"
ref="ruleFormRef"
label-position="right"
status-icon
label-width="auto"
>
<el-form-item label="机构名称" prop="deptName">
<el-input v-model="form.deptName" placeholder="请输入机构名称" />
</el-form-item>
<el-form-item label="机构全称" prop="fullName">
<el-input v-model="form.fullName" placeholder="请输入机构全称" />
</el-form-item>
<el-form-item label="上级机构">
<el-tree-select
v-model="form.parentId"
:data="dataSource"
:render-after-expand="false"
style="width: 100%"
placeholder="请选择上级机构"
>
</el-tree-select>
</el-form-item>
<el-form-item label="机构类型" prop="deptCategory">
<el-select v-model="form.deptCategory" placeholder="请选择机构类型" style="width: 100%">
<el-option
v-for="item in orgCategoryList"
:key="item.dictKey"
:label="item.dictValue"
:value="item.dictKey"
>
</el-option>
</el-select>
</el-form-item>
<el-form-item label="排序" prop="sort" required>
<el-input-number
:max="10"
:min="1"
controls-position="right"
placeholder="请输入排序"
style="text-align: left; width: 100%"
v-model="form.sort"
/>
</el-form-item>
<el-form-item label="备注" prop="remark">
<el-input
:row="5"
placeholder="请输入备注"
type="textarea"
v-model="form.remark"
maxlength="200"
/>
</el-form-item>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="submitForm(form)" type="primary" v-if="deptAdd"> 确定 </el-button>
<el-button @click="updateForm(form)" type="primary" v-else> 修改 </el-button>
<el-button @click="resetForm()">取消</el-button>
</span>
</template>
</el-dialog>
</el-dialog>
</template>
<script>
import { add, getDeptTree, update } from '@/api/system/dept';
import { mapGetters } from 'vuex';
import { getDictionary } from '@/api/system/dictbiz';
import rulesPublic from '@/utils/biz/rules';
export default {
props: {
deptIds: {
//用于回显’,‘号隔开
type: String,
},
type: {
//类型 radio.单选,check.多选
type: String,
},
nodeTitle: {
type: String,
default: '',
},
},
data() {
return {
plusBtnType: true, //默认显示 加号
platformBox: false,
treeDeptId: '',
value: '',
dataSource: [], //数据集合
deptList: [], //已选择的数据
checkedKeys: [], // 初始选中项(多选)
checkedNodeKeys: '', // 初始选中项(单选)
inputValue: '',
showCheckbox: false,
dialogFormVisible: false, //新增
deptAdd: false, //新增
orgCategoryList: [], //机构类型
form: {
sort: '1',
},
// 弹框标题
title: '',
// 表单校验
rules: {
deptName: rulesPublic.MaxiMumNumber(true, '请输入机构名称', 1, 20),
fullName: rulesPublic.MaxiMumNumber(true, '请输入机构全称', 1, 20),
deptCategory: [
{
required: true,
message: '请输入机构类型',
trigger: 'blur',
},
],
sort: [
{
required: true,
message: '请输入排序',
trigger: 'blur',
},
],
},
};
},
computed: {
...mapGetters(['userInfo', 'permission']),
permissionList() {
return {
addBtn: this.validData(this.permission.param_add, false),
viewBtn: this.validData(this.permission.param_view, false),
delBtn: this.validData(this.permission.param_delete, false),
editBtn: this.validData(this.permission.param_edit, false),
};
},
},
watch: {
//监听树搜索框
inputValue(val) {
this.$refs.treeRef.filter(val);
},
},
methods: {
handleCheckChange(data, checked, indeterminate) {
// 单选
if (checked && this.type === 'radio') {
this.$refs.treeRef.setCheckedKeys([data.id]);
}
},
//打开弹框
openDialog() {
this.showCheckbox = true;
if (this.deptIds) {
let videoTrackListIds = this.deptIds.split(',');
setTimeout(() => {
this.checkedKeys = videoTrackListIds;
}, 300);
}
this.platformBox = true;
this.getListTree();
//查询机构类型
getDictionary({ code: 'org_category' }).then(res => {
this.orgCategoryList = res.data.data;
});
},
// 定义一个递归函数来遍历树形结构
findAllNodesUnderStationSection(nodes, nodeTitle, result = []) {
nodes.forEach(node => {
// 检查当前节点是否名称为“站段”
if (node.title == this.nodeTitle && node.parentId == 0) {
// 如果是,将当前节点添加到结果中
result.push(node);
}
// 如果当前节点有子节点,递归遍历子节点
if (node.children && node.children.length > 0) {
this.findAllNodesUnderStationSection(node.children, result);
}
});
return result;
},
//获取树数据
getListTree() {
getDeptTree().then(data => {
let res = [];
if (this.nodeTitle) {
res = this.findAllNodesUnderStationSection(data.data.data, this.nodeTitle);
} else {
res = data.data.data;
}
res.map(item => {
item.label = item.title;
if (item.children) {
this.finMyChildren(item);
}
});
this.dataSource = res;
});
},
//查找树形的子节点
finMyChildren(data) {
data.children.forEach(item => {
item.label = item.title;
if (item.children && item.children.length > 0) {
this.finMyChildren(item);
}
});
},
//组件传参
nodeClick(data) {
let key = this.$refs.treeRef.getCheckedKeys();
if (key) {
if (key.includes(data.id)) {
key.map((item, index) => {
if (item === data.id) {
key.splice(index, 1);
}
});
} else {
key.push(data.id);
}
this.$refs.treeRef.setCheckedKeys(key);
}
this.deptList = [];
this.deptList.push(data);
},
//过滤树节点
filterNode(value, data) {
if (!value) return true;
return data.title.includes(value);
},
//选择传递数据
selectData() {
if (this.showCheckbox) {
let list = this.$refs.treeRef.getCheckedNodes();
this.$emit('deptList', list);
} else {
this.$emit('deptList', this.deptList);
}
this.checkedKeys = [];
this.checkedNodeKeys = '';
this.platformBox = false;
},
//关闭弹框
selectCancel() {
this.checkedKeys = [];
this.checkedNodeKeys = '';
this.platformBox = false;
},
dialogChink() {
//如果是第一次新增
this.title = '新增';
this.deptAdd = true;
this.form.sort = '1';
this.form.deptCategory = '1';
//打开新增弹框
this.dialogFormVisible = true;
},
//新增
submitForm() {
this.$refs.ruleFormRef.validate(valid => {
if (valid) {
add(this.form).then(res => {
if (res.data.data) {
this.$message({
type: 'success',
message: '新增成功!',
});
}
this.form = {};
this.dialogFormVisible = false;
setTimeout(() => {
this.getListTree();
}, 300);
});
} else {
return false;
}
});
},
//修改
updateForm() {
this.$refs.ruleFormRef.validate(valid => {
if (valid) {
update(this.form)
.then(res => {
if (res.data.data) {
this.$alert('修改失败,修改的分类名称已存在!', '系统提示', {
confirmButtonText: '确定',
type: 'warning',
});
} else {
this.$message({
type: 'success',
message: '修改成功!',
});
}
})
.catch(error => {
this.ruleForm = this.rowData;
});
setTimeout(() => {
this.getList();
}, 300);
this.dialogFormVisible = false;
} else {
return false;
}
});
},
//取消
resetForm() {
this.form = {};
this.dialogFormVisible = false;
},
nodeClickTree(row) {
this.form.parentId = row.id;
this.form.parentName = row.title;
this.form.value = row.title;
setTimeout(() => {
//当前行子表全选
this.form.parentName = row.title;
}, 100);
},
},
};
</script>
<style scoped>
.el-dialog-dept {
width: 340px;
height: 300px;
}
.el-dialog-dept .el-dialog__body {
padding: 0 0 0;
}
/*标题行样式*/
.title-style {
margin-top: 5px;
display: flex;
justify-content: space-between;
padding-left: 7px;
}
/*标题样式*/
.tree-title {
line-height: 30px;
font-size: 13px;
color: #3d3d3d;
font-weight: bold;
}
/*添加分类按钮样式*/
.add-button {
background-color: white;
color: #409eff;
border: none;
height: 30px;
}
</style>