hookehuyr

fix 树型选择器操作逻辑完善,和生成和校验逻辑调整

<!--
* @Date: 2022-08-29 14:31:20
* @LastEditors: hookehuyr hookehuyr@gmail.com
* @LastEditTime: 2024-06-03 13:15:47
* @FilePath: /data-table/src/components/TreeField/MyComponent.vue
* @Description: 树形组件
-->
<template>
<div class="tree-field-page">
<div class="select-tree-box" @click="openTree">
<div class="select-tree-item" v-for="(dept) in emitCheckedGroup.dept" :key="dept.id">
{{ dept.name }}
</div>
<div class="select-tree-item" v-for="(role) in emitCheckedGroup.role" :key="role.id">
{{ role.name }}
</div>
<div class="select-tree-item" v-for="(user) in emitCheckedGroup.user" :key="user.id">
{{ user.name }}
</div>
</div>
<van-popup
v-model:show="showPopover"
position="bottom"
:close-on-click-overlay="false"
:style="{ height: '90vh' }"
>
<div v-if="!is_search" class="search-box" @click="onSearchFocus">
<van-icon name="search" size="1.1rem" />&nbsp;点击搜索
</div>
<van-field
v-else
ref="searchInputRef"
v-model="searchValue"
placeholder="可通过名称,手机号或邮箱查询"
:border="false"
@blur="onSearchBlur"
@focus="onSearchFocus"
>
<template #button>
<van-button size="small" type="primary" @click="onCloseSearch">关闭</van-button>
</template>
</van-field>
<div class="select-box">
<div class="select-item" v-for="(dept) in checkedGroup.dept" :key="dept.id">
{{ dept.name }}&nbsp;<van-icon @click="onRemoveDeptTag(dept)" name="close" />
</div>
<div class="select-item" v-for="(role) in checkedGroup.role" :key="role.id">
{{ role.name }}&nbsp;<van-icon @click="onRemoveRoleTag(role)" name="close" />
</div>
<div class="select-item" v-for="(user) in checkedGroup.user" :key="user.id">
{{ user.name }}&nbsp;<van-icon @click="onRemoveUserTag(user)" name="close" />
</div>
</div>
<div v-show="!is_search" class="tab-tree-container">
<van-tabs ref="tabRef" :color="styleColor.baseColor" v-model:active="tabActive" @click-tab="onClickTab" style="margin-bottom: 1rem;">
<van-tab title="组织结构" :name="0"></van-tab>
<van-tab title="角色" :name="1"></van-tab>
<van-tab title="成员" :name="2"></van-tab>
</van-tabs>
<div v-show="tabActive === 0" style="padding: 0 0 1rem 1rem;">
<Vtree
ref="deptTreeRef"
v-model="select_dept_value"
checkable
titleField="name"
keyField="id"
:expandOnFilter="false"
:showCheckedButton="false"
:showFooter="false"
:cascade="false"
:defaultExpandAll="false"
@checked-change="deptTreeCheckedChange"
style=" height: 55vh; overflow: scroll;"
>
<span slot="empty">暂无数据</span>
</Vtree>
</div>
<div v-if="tabActive === 1" style="padding: 0 0 1rem 1rem;">
<van-checkbox-group
v-model="role_checked"
@change="roleChangeMethod"
>
<van-checkbox
v-for="(role, index) in roleList"
:key="index"
:name="role.id"
shape="square"
icon-size="13px"
:checked-color="styleColor.baseColor"
style="margin-bottom: 0.5rem;"
>{{ role.name }}</van-checkbox>
</van-checkbox-group>
</div>
<div v-if="tabActive === 2" style="padding: 0 0 0 1rem;">
<van-row gutter="">
<van-col span="10" style="border-right: 1px solid #eee; height: 55vh; overflow: scroll;">
<Vtree
ref="userTreeRef"
v-model="select_user_value"
selectable
titleField="name"
keyField="id"
:expandOnFilter="false"
:showCheckedButton="false"
@update:modelValue="() => {}"
@click="onUserTreeClick"
>
<span slot="empty">暂无数据</span>
</Vtree>
</van-col>
<van-col span="14">
<van-checkbox-group
v-model="user_checked"
style="padding: 0 0 1rem 1rem;"
@change="onUserChange"
>
<van-checkbox
@click="onCheckUserChange(user, $event)"
v-for="(user, index) in userList"
:id="user.id"
:type="user.type"
:text="user.name"
:key="index"
:name="user.id"
shape="square"
icon-size="13px"
:checked-color="styleColor.baseColor" style="margin-bottom: 0.5rem;">{{ user.name }}</van-checkbox>
</van-checkbox-group>
</van-col>
</van-row>
</div>
</div>
<div v-show="is_search" class="search-container">
<van-checkbox-group
v-model="search_result_checked"
style="padding: 0 0 1rem 1rem;"
@change="onSearchResultChange"
>
<div>
<p>部门</p>
<div>
<van-checkbox
@click="onSearchResultClick(dept, $event)"
v-for="(dept) in user_dept_role.dept"
:id="dept.id"
:type="dept.type"
:text="dept.name"
:key="dept.id"
:name="dept.id"
shape="square" icon-size="13px" :checked-color="styleColor.baseColor" style="margin-bottom: 0.5rem;">
{{ dept.name }}
</van-checkbox>
</div>
</div>
<div>
<p>角色</p>
<div>
<van-checkbox
@click="onSearchResultClick(role, $event)"
v-for="(role) in user_dept_role.role"
:id="role.id"
:type="role.type"
:text="role.name"
:key="role.id"
:name="role.id"
shape="square" icon-size="13px" :checked-color="styleColor.baseColor" style="margin-bottom: 0.8rem;">
{{ role.name }}
</van-checkbox>
</div>
</div>
<div>
<p>成员</p>
<div>
<van-checkbox
@click="onSearchResultClick(user, $event)"
v-for="(user) in user_dept_role.user"
:id="user.id"
:type="user.type"
:text="user.name"
:key="user.id"
:name="user.id"
shape="square" icon-size="13px" :checked-color="styleColor.baseColor" style="margin-bottom: 0.5rem;">
{{ user.name }}
</van-checkbox>
</div>
</div>
{{ search_result_checked }}
</van-checkbox-group>
</div>
<div style="position: fixed; bottom: 0; left: 0; width: 100%;">
<van-row gutter="0">
<van-col span="12">
<van-button block type="default" @click="onCancelClick">取消</van-button>
</van-col>
<van-col span="12">
<van-button block type="primary" @click="onConfirmClick">确定</van-button>
</van-col>
</van-row>
</div>
</van-popup>
</div>
</template>
<script setup>
import { inject, ref } from 'vue'
import { useCustomFieldValue } from '@vant/use';
import { styleColor } from "@/constant.js";
// 大家可以根据需要是否引入VTreeNode, VTreeSearch, VTreeDrop
import Vtree, { VTreeNode, VTreeSearch, VTreeDrop } from '@wsfe/vue-tree'
import '@wsfe/vue-tree/style.css';
import role_list from './flow_role_list.json'
import dept_list from './flow_dept_list.json'
import $ from 'jquery';
import _ from 'lodash';
// 获取父组件传值
const props = inject('props');
console.log("🚀 ~ file: MyComponent.vue:227 ~ props:", props);
const emit = defineEmits(["active"]);
onMounted(() => {
// props.item.value = props.item?.component_props.default;
// TODO:获取已选中数据
// emitCheckedGroup.value = {
// dept: [{
// "id": 107691,
// "name": "插花组",
// "type": "dept"
// }],
// role: [{
// "id": 137902,
// "name": "大道大商营员组长",
// "type": "role"
// }],
// user: [{
// "id": 107707,
// "name": "场地组长",
// "type": "user"
// }]
// }
});
const openTree = () => {
showPopover.value = true;
// TODO:获取数据
nextTick(() => {
getDeptTreeData();
// 获取已选择的数据
checkedGroup.value = _.cloneDeep(emitCheckedGroup.value);
syncResultToList(); // 同步勾选状态
});
}
const emitCheckedGroup = ref({
dept: [], // 组织结构
role: [], // 角色
user: [] // 成员
});
const tree_select_value = ref([]);
const onCancelClick = () => {
showPopover.value = false;
}
const onConfirmClick = () => {
showPopover.value = false;
if (is_search.value) {
onCloseSearch()
}
//
emitCheckedGroup.value = _.cloneDeep(checkedGroup.value);
// 发送到表单数据
tree_select_value.value = [].concat(...Object.values(emitCheckedGroup.value));
}
/******* 搜索输入项 *******/
const showPopover = ref(false); // 显示/隐藏弹框
const searchInputRef = ref(null);
const searchValue = ref('');
const is_search = ref(false); // 默认不显示搜索框
/**
* 搜索选中结果集
* @param {Number} id
*/
const search_result_checked = ref([]);
const onSearchBlur = () => { // 搜索框失去焦点
}
const onSearchFocus = () => { // 搜索框获取焦点回调
is_search.value = true; // 打开搜索状态
// 自动选中搜索框
nextTick(() => {
searchInputRef.value.focus();
});
// 如果选中框有值,点击搜索框后把结果选中到搜索结果集里面
// TODO:待实现 真实情况应该是请求搜索结果后做
handleSelectToSearch();
}
const handleSelectToSearch = () => { // 把选中结果集同步,到搜索结果集上勾中显示
let dept = checkedGroup.value.dept.map(item => item.id);
let role = checkedGroup.value.role.map(item => item.id);
let user = checkedGroup.value.user.map(item => item.id);
search_result_checked.value = [...dept, ...role, ...user]; // 搜索选中结果集
}
const syncResultToList = () => { // 把弹框结果集同步到树形选择树的勾选状态
// 组织结构,勾选状态还原
deptTreeRef.value?.setCheckedKeys(checkedGroup.value.dept.map(item => item.id), true);
// 角色选中,勾选状态还原
role_checked.value = checkedGroup.value.role.map(item => item.id);
// 成员选中,勾选状态还原
user_checked.value = checkedGroup.value.user.map(item => item.id)
}
const onCloseSearch = () => { // 点击搜索关闭按钮回调
tabActive.value = 0; // 默认选中组织结构
is_search.value = false; // 关闭搜索状态
syncResultToList(); // 同步勾选状态
}
/****************************** END ********************************/
/**
* 中间通用显示勾选结果集
*/
const checkedGroup = ref({
dept: [], // 组织结构
role: [], // 角色
user: [] // 成员
});
const onRemoveDeptTag = (dept) => { // 移除部门标签
// 移除选中框显示
const index = checkedGroup.value.dept.findIndex(item => JSON.stringify(item) === JSON.stringify(dept));
checkedGroup.value.dept.splice(index, 1);
// 组织结构移除对应ID
deptTreeRef.value?.setChecked(dept.id, false);
// 移除搜索结果选中显示
const idx = search_result_checked.value.findIndex(item => JSON.stringify(item) === JSON.stringify(dept));
search_result_checked.value.splice(idx, 1);
}
const onRemoveRoleTag = (role) => { // 移除角色标签
const index = checkedGroup.value.role.findIndex(item => JSON.stringify(item) === JSON.stringify(role));
checkedGroup.value.role.splice(index, 1);
//
const idx = role_checked.value.findIndex(item => JSON.stringify(item) === JSON.stringify(role));
role_checked.value.splice(index, 1);
// 移除搜索结果选中显示
const i = search_result_checked.value.findIndex(item => JSON.stringify(item) === JSON.stringify(role));
search_result_checked.value.splice(i, 1);
}
const onRemoveUserTag = (user) => { // 移除成员标签
const index = checkedGroup.value.user.findIndex(item => JSON.stringify(item) === JSON.stringify(user));
checkedGroup.value.user.splice(index, 1);
//
const idx = user_checked.value.findIndex(item => JSON.stringify(item) === JSON.stringify(user));
user_checked.value.splice(index, 1);
// 移除搜索结果选中显示
const i = search_result_checked.value.findIndex(item => JSON.stringify(item) === JSON.stringify(user));
search_result_checked.value.splice(i, 1);
}
/*************** Tab 功能模块 ****************/
const tabRef = ref(null);
const tabActive = ref(0);
const deptTreeRef = ref();
const userList = ref([]);
const onClickTab = ({ title }) => { // tab点击事件
nextTick(() => {
if (title === '组织结构') {
deptListReset();
}
if (title === '角色') {
roleListReset();
}
if (title === '成员') {
userListReset();
}
});
};
const deptListReset = () => { // 组织重置列表
deptTreeRef.value.setData(role_list);
deptTreeRef.value.setExpand(35697, true)
}
const roleListReset = () => { // 角色重置列表
roleList.value = dept_list;
}
const userListReset = () => { // 成员重置列表
userTreeRef.value.setData(role_list);
userTreeRef.value.setExpand(35697, true)
}
/**************** END *****************/
/************* 组织结构模块 ***************/
const select_dept_value = ref(); // 组织结构树形选中值
const getDeptTreeData = () => { // 获取组织结构数据
deptTreeRef.value.setData(role_list);
// 默认展开第一个
deptTreeRef.value.setExpand(35697, true);
}
const deptTreeCheckedChange = (arr) => { // 组织结构勾选回调
checkedGroup.value.dept = arr.map((item) => {
return {
id: item.id,
name: item.name,
type: 'dept'
}
});
}
/**************** END *****************/
/************* 角色模块 ***************/
const role_checked = ref([]); // 角色多选选中值
const roleList = ref([]);
const roleChangeMethod = (val) => { // 角色多选组点击回调
let result = val.map(id => roleList.value.find(obj => obj.id === id));
// 过滤掉未找到的项(即返回undefined的项)
checkedGroup.value.role = result.filter(item => item !== undefined);
}
/**************** END *****************/
/************* 成员模块 ***************/
const userTreeRef = ref();
const select_user_value = ref(); // 成员树形选中值
const user_checked = ref([]); // 成员多选选中值
const onUserTreeClick = (node) => { // 点击成员树形回调
userList.value = node.user;
user_checked.value = checkedGroup.value.user.map(item => item.id)
}
const onUserChange = (val) => { // 成员多选组点击回调
}
const onCheckUserChange = (val, evt) => {
nextTick(() => {
let checked = false;
let id = '';
let name = '';
let type = '';
if ($(evt.target).attr('aria-checked') === undefined) {
checked = $(evt.target).parents('.van-checkbox').attr('aria-checked');
id = $(evt.target).parents('.van-checkbox').attr('id');
name = $(evt.target).parents('.van-checkbox').attr('text');
type = $(evt.target).parents('.van-checkbox').attr('type');
} else {
checked = $(evt.target).attr('aria-checked');
id = $(evt.target).attr('id');
name = $(evt.target).attr('text');
type = $(evt.target).attr('type');
}
let obj = {
id: +id,
name,
type
}
checkedGroup.value.user.push(obj);
checkedGroup.value.user = _.uniqBy(checkedGroup.value.user, 'id');
//
if (checked === 'false') {
if (val.type === 'user') {
const index = checkedGroup.value.user.indexOf(val);
checkedGroup.value.user.splice(index, 1);
}
}
})
}
/**************** END *****************/
/***************** 搜索结果集模块 ********************/
// 模拟数据
const user_dept_role = ref({
"dept": [
{
"type": "dept",
"name": "男10组",
"id": 137571
}, {
"type": "dept",
"name": "主持组",
"id": 107700
}
],
"role": [
{
"type": "role",
"name": "八关斋戒",
"id": 624337
},
{
"id": 82983,
"name": "场地管理",
"type": "role"
}
],
"user": [
{
"id": 137918,
"name": "10组寝室长",
"type": "user"
},
{
"id": 137919,
"name": "11组寝室长",
"type": "user"
}
]
});
const onSearchResultChange = (val) => { // 监听搜索结果集点击回调,结果集为选中项
}
const onSearchResultClick = (val, evt) => { // 搜索结果集项点击回调
nextTick(() => {
let checked = false;
let id = '';
let name = '';
let type = '';
if ($(evt.target).attr('aria-checked') === undefined) { // 点击子元素
checked = $(evt.target).parents('.van-checkbox').attr('aria-checked');
id = $(evt.target).parents('.van-checkbox').attr('id');
name = $(evt.target).parents('.van-checkbox').attr('text');
type = $(evt.target).parents('.van-checkbox').attr('type');
} else { // 点击父元素
checked = $(evt.target).attr('aria-checked');
id = $(evt.target).attr('id');
name = $(evt.target).attr('text');
type = $(evt.target).attr('type');
}
let obj = { // 点击元素属性
id: +id,
name,
type
}
// 对应类型添加到选中组
checkedGroup.value[type].push(obj);
checkedGroup.value[type] = _.uniqBy(checkedGroup.value[type], 'id');
// 取消选中处理
if (checked === 'false') {
// 移除对应的数据集
const index = checkedGroup.value[type].findIndex(item => item.id === obj.id);
checkedGroup.value[type].splice(index, 1);
if (type === 'dept') {
// 树形 组织结构移除对应ID
deptTreeRef.value?.setChecked(obj.id, false);
}
}
});
}
// 此处传入的值会替代 Field 组件内部的 value
useCustomFieldValue(() => tree_select_value.value);
// defineExpose({ handleReset, show_control });
</script>
<style lang="less" scoped>
.tree-field-page {
.select-tree-box {
height: 4rem;
width: calc(100vw - 5rem);
border: 1px dashed #dfdfdf;
margin: 1rem 0;
overflow: scroll;
border-radius: 5px;
padding: 0.5rem;
display: flex;
flex-wrap: wrap;
.select-tree-item {
margin-right: 5px;
margin-bottom: 5px;
font-size: 0.85rem;
padding: 5px 8px;
background-color: #C2915F;
color: #fff;
height: 1.2rem;
display: flex;
justify-content: center;
align-items: center;
}
}
}
:deep(.van-field__body) {
border: 1px solid #eaeaea;
border-radius: 0.25rem;
padding: 0.25rem 0.5rem;
}
.search-box {
display: flex;
align-items: center;
justify-content: center;
background-color: #eee;
margin: 1rem;
border-radius: 3px;
padding: 0.6rem;
font-size: 0.9rem;
}
:deep(.ctree-tree-node__checkbox_checked) {
border-color: #C2915F;
background-color: #C2915F;
}
:deep(.ctree-tree-node__title_selected) {
background-color: #f8e2cb;
}
// :deep(.ctree-tree__scroll-area) {
// margin-bottom: 2rem;
// }
.select-box {
height: 4rem;
border: 1px dashed #dfdfdf;
margin: 0 1rem;
overflow: scroll;
border-radius: 5px;
padding: 0.5rem;
display: flex;
flex-wrap: wrap;
.select-item {
margin-right: 5px;
margin-bottom: 5px;
font-size: 0.85rem;
padding: 5px 8px;
background-color: #C2915F;
color: #fff;
height: 1.2rem;
display: flex;
justify-content: center;
align-items: center;
}
}
</style>
<!--
* @Date: 2022-08-29 14:31:20
* @LastEditors: hookehuyr hookehuyr@gmail.com
* @LastEditTime: 2024-05-31 18:05:23
* @LastEditTime: 2024-06-03 13:29:18
* @FilePath: /data-table/src/components/TreeField/index.vue
* @Description: 树形组件
-->
......@@ -12,224 +12,25 @@
{{ item.component_props.label }}
</div>
<div class="select-tree-box" @click="openTree">
<div class="select-tree-item" v-for="(dept) in emitCheckedGroup.dept" :key="dept.id">
{{ dept.name }}
</div>
<div class="select-tree-item" v-for="(role) in emitCheckedGroup.role" :key="role.id">
{{ role.name }}
</div>
<div class="select-tree-item" v-for="(user) in emitCheckedGroup.user" :key="user.id">
{{ user.name }}
</div>
</div>
<van-popup
v-model:show="showPopover"
position="bottom"
:close-on-click-overlay="false"
:style="{ height: '90vh' }"
>
<div v-if="!is_search" class="search-box" @click="onSearchFocus">
<van-icon name="search" size="1.1rem" />&nbsp;点击搜索
</div>
<van-field
v-else
ref="searchInputRef"
v-model="searchValue"
placeholder="可通过名称,手机号或邮箱查询"
:border="false"
@blur="onSearchBlur"
@focus="onSearchFocus"
>
<template #button>
<van-button size="small" type="primary" @click="onCloseSearch">关闭</van-button>
</template>
</van-field>
<div class="select-box">
<div class="select-item" v-for="(dept) in checkedGroup.dept" :key="dept.id">
{{ dept.name }}&nbsp;<van-icon @click="onRemoveDeptTag(dept)" name="close" />
</div>
<div class="select-item" v-for="(role) in checkedGroup.role" :key="role.id">
{{ role.name }}&nbsp;<van-icon @click="onRemoveRoleTag(role)" name="close" />
</div>
<div class="select-item" v-for="(user) in checkedGroup.user" :key="user.id">
{{ user.name }}&nbsp;<van-icon @click="onRemoveUserTag(user)" name="close" />
</div>
</div>
<div v-show="!is_search" class="tab-tree-container">
<van-tabs ref="tabRef" :color="styleColor.baseColor" v-model:active="tabActive" @click-tab="onClickTab" style="margin-bottom: 1rem;">
<van-tab title="组织结构" :name="0"></van-tab>
<van-tab title="角色" :name="1"></van-tab>
<van-tab title="成员" :name="2"></van-tab>
</van-tabs>
<div v-show="tabActive === 0" style="padding: 0 0 1rem 1rem;">
<Vtree
ref="deptTreeRef"
v-model="select_dept_value"
checkable
titleField="name"
keyField="id"
:expandOnFilter="false"
:showCheckedButton="false"
:showFooter="false"
:cascade="false"
:defaultExpandAll="false"
@checked-change="deptTreeCheckedChange"
style=" height: 55vh; overflow: scroll;"
>
<span slot="empty">暂无数据</span>
</Vtree>
</div>
<div v-if="tabActive === 1" style="padding: 0 0 1rem 1rem;">
<van-checkbox-group
v-model="role_checked"
@change="roleChangeMethod"
>
<van-checkbox
v-for="(role, index) in roleList"
:key="index"
:name="role.id"
shape="square"
icon-size="13px"
:checked-color="styleColor.baseColor"
style="margin-bottom: 0.5rem;"
>{{ role.name }}</van-checkbox>
</van-checkbox-group>
</div>
<div v-if="tabActive === 2" style="padding: 0 0 0 1rem;">
<van-row gutter="">
<van-col span="10" style="border-right: 1px solid #eee; height: 55vh; overflow: scroll;">
<Vtree
ref="userTreeRef"
v-model="select_user_value"
selectable
titleField="name"
keyField="id"
:expandOnFilter="false"
:showCheckedButton="false"
@update:modelValue="() => {}"
@click="onUserTreeClick"
>
<span slot="empty">暂无数据</span>
</Vtree>
</van-col>
<van-col span="14">
<van-checkbox-group
v-model="user_checked"
style="padding: 0 0 1rem 1rem;"
@change="onUserChange"
>
<van-checkbox
@click="onCheckUserChange(user, $event)"
v-for="(user, index) in userList"
:id="user.id"
:type="user.type"
:text="user.name"
:key="index"
:name="user.id"
shape="square"
icon-size="13px"
:checked-color="styleColor.baseColor" style="margin-bottom: 0.5rem;">{{ user.name }}</van-checkbox>
</van-checkbox-group>
</van-col>
</van-row>
</div>
</div>
<div v-show="is_search" class="search-container">
<van-checkbox-group
v-model="search_result_checked"
style="padding: 0 0 1rem 1rem;"
@change="onSearchResultChange"
>
<div>
<p>部门</p>
<div>
<van-checkbox
@click="onSearchResultClick(dept, $event)"
v-for="(dept) in user_dept_role.dept"
:id="dept.id"
:type="dept.type"
:text="dept.name"
:key="dept.id"
:name="dept.id"
shape="square" icon-size="13px" :checked-color="styleColor.baseColor" style="margin-bottom: 0.5rem;">
{{ dept.name }}
</van-checkbox>
</div>
</div>
<div>
<p>角色</p>
<div>
<van-checkbox
@click="onSearchResultClick(role, $event)"
v-for="(role) in user_dept_role.role"
:id="role.id"
:type="role.type"
:text="role.name"
:key="role.id"
:name="role.id"
shape="square" icon-size="13px" :checked-color="styleColor.baseColor" style="margin-bottom: 0.8rem;">
{{ role.name }}
</van-checkbox>
</div>
</div>
<div>
<p>成员</p>
<div>
<van-checkbox
@click="onSearchResultClick(user, $event)"
v-for="(user) in user_dept_role.user"
:id="user.id"
:type="user.type"
:text="user.name"
:key="user.id"
:name="user.id"
shape="square" icon-size="13px" :checked-color="styleColor.baseColor" style="margin-bottom: 0.5rem;">
{{ user.name }}
</van-checkbox>
</div>
</div>
{{ search_result_checked }}
</van-checkbox-group>
</div>
<div style="position: fixed; bottom: 0; left: 0; width: 100%;">
<van-row gutter="0">
<van-col span="12">
<van-button block type="default" @click="onCancelClick">取消</van-button>
</van-col>
<van-col span="12">
<van-button block type="primary" @click="onConfirmClick">确定</van-button>
</van-col>
</van-row>
</div>
</van-popup>
<van-field :name="item.key" :rules="rules" style="padding: 0 1rem;">
<template #input>
<my-component ref="refComponent" />
</template>
</van-field>
</div>
</template>
<script setup>
import { styleColor } from "@/constant.js";
// 大家可以根据需要是否引入VTreeNode, VTreeSearch, VTreeDrop
import Vtree, { VTreeNode, VTreeSearch, VTreeDrop } from '@wsfe/vue-tree'
import '@wsfe/vue-tree/style.css';
import role_list from './flow_role_list.json'
import dept_list from './flow_dept_list.json'
import $ from 'jquery';
import _ from 'lodash';
import MyComponent from './MyComponent.vue';
const props = defineProps({
item: Object,
});
// 注入子组件属性
provide('props', props.item);
const refComponent = ref(null)
// 隐藏显示
const HideShow = computed(() => {
......@@ -242,324 +43,42 @@ const isGroup = computed(() => {
});
onMounted(() => {
props.item.value = props.item.component_props.default;
});
const openTree = () => {
showPopover.value = true;
// TODO:获取数据
nextTick(() => {
getDeptTreeData();
})
}
const emitCheckedGroup = ref({
dept: [], // 组织结构
role: [], // 角色
user: [] // 成员
});
const onCancelClick = () => {
showPopover.value = false;
}
const onConfirmClick = () => {
showPopover.value = false;
//
emitCheckedGroup.value = _.cloneDeep(checkedGroup.value);
}
/******* 搜索输入项 *******/
const showPopover = ref(false); // 显示/隐藏弹框
const searchInputRef = ref(null);
const searchValue = ref('');
const is_search = ref(false); // 默认不显示搜索框
/**
* 搜索选中结果集
* @param {Number} id
*/
const search_result_checked = ref([]);
const onSearchBlur = () => { // 搜索框失去焦点
}
const onSearchFocus = () => { // 搜索框获取焦点回调
is_search.value = true; // 打开搜索状态
// 自动选中搜索框
nextTick(() => {
searchInputRef.value.focus();
});
// 如果选中框有值,点击搜索框后把结果选中到搜索结果集里面
// TODO:待实现 真实情况应该是请求搜索结果后做
handleSelectToSearch();
}
const handleSelectToSearch = () => { // 把选中结果集同步,到搜索结果集上勾中显示
let dept = checkedGroup.value.dept.map(item => item.id);
let role = checkedGroup.value.role.map(item => item.id);
let user = checkedGroup.value.user.map(item => item.id);
search_result_checked.value = [...dept, ...role, ...user]; // 搜索选中结果集
}
const onCloseSearch = () => { // 点击搜索关闭按钮回调
tabActive.value = 0; // 默认选中组织结构
is_search.value = false; // 关闭搜索状态
// 组织结构,勾选状态还原
deptTreeRef.value?.setCheckedKeys(checkedGroup.value.dept.map(item => item.id), true);
// 角色选中,勾选状态还原
role_checked.value = checkedGroup.value.role.map(item => item.id);
// 成员选中,勾选状态还原
user_checked.value = checkedGroup.value.user.map(item => item.id)
}
/****************************** END ********************************/
/**
* 中间通用显示勾选结果集
*/
const checkedGroup = ref({
dept: [], // 组织结构
role: [], // 角色
user: [] // 成员
// props.item.value = props.item.component_props.default;
// TODO:获取已选中数据
// emitCheckedGroup.value = {
// dept: [{
// "id": 107691,
// "name": "插花组",
// "type": "dept"
// }],
// role: [{
// "id": 137902,
// "name": "大道大商营员组长",
// "type": "role"
// }],
// user: [{
// "id": 107707,
// "name": "场地组长",
// "type": "user"
// }]
});
const onRemoveDeptTag = (dept) => { // 移除部门标签
// 移除选中框显示
const index = checkedGroup.value.dept.indexOf(dept);
checkedGroup.value.dept.splice(index, 1);
// 组织结构移除对应ID
deptTreeRef.value?.setChecked(dept.id, false);
// 移除搜索结果选中显示
const idx = search_result_checked.value.indexOf(dept);
search_result_checked.value.splice(idx, 1);
}
const onRemoveRoleTag = (role) => { // 移除角色标签
const index = checkedGroup.value.role.indexOf(role);
checkedGroup.value.role.splice(index, 1);
//
const idx = role_checked.value.indexOf(role);
role_checked.value.splice(index, 1);
// 移除搜索结果选中显示
const i = search_result_checked.value.indexOf(role);
search_result_checked.value.splice(i, 1);
}
const onRemoveUserTag = (user) => { // 移除成员标签
const index = checkedGroup.value.user.indexOf(user);
checkedGroup.value.user.splice(index, 1);
//
const idx = user_checked.value.indexOf(user);
user_checked.value.splice(index, 1);
// 移除搜索结果选中显示
const i = search_result_checked.value.indexOf(user);
search_result_checked.value.splice(i, 1);
}
/*************** Tab 功能模块 ****************/
const tabRef = ref(null);
const tabActive = ref(0);
const deptTreeRef = ref();
const userList = ref([]);
const onClickTab = ({ title }) => { // tab点击事件
nextTick(() => {
if (title === '组织结构') {
deptListReset();
}
if (title === '角色') {
roleListReset();
}
if (title === '成员') {
userListReset();
}
});
// 规则校验
const required = props.item.component_props.required;
const validator = (val) => {
if (required && !val.length) {
return false;
} else {
return true;
}
};
const deptListReset = () => { // 组织重置列表
deptTreeRef.value.setData(role_list);
deptTreeRef.value.setExpand(35697, true)
}
const roleListReset = () => { // 角色重置列表
roleList.value = dept_list;
}
const userListReset = () => { // 成员重置列表
userTreeRef.value.setData(role_list);
userTreeRef.value.setExpand(35697, true)
}
/**************** END *****************/
/************* 组织结构模块 ***************/
const select_dept_value = ref(); // 组织结构树形选中值
const getDeptTreeData = () => { // 获取组织结构数据
deptTreeRef.value.setData(role_list);
// 默认展开第一个
deptTreeRef.value.setExpand(35697, true);
}
const deptTreeCheckedChange = (arr) => { // 组织结构勾选回调
checkedGroup.value.dept = arr.map((item) => {
return {
id: item.id,
name: item.name,
type: 'dept'
}
});
}
/**************** END *****************/
/************* 角色模块 ***************/
const role_checked = ref([]); // 角色多选选中值
const roleList = ref([]);
const roleChangeMethod = (val) => { // 角色多选组点击回调
let result = val.map(id => roleList.value.find(obj => obj.id === id));
// 过滤掉未找到的项(即返回undefined的项)
checkedGroup.value.role = result.filter(item => item !== undefined);
}
/**************** END *****************/
/************* 成员模块 ***************/
const userTreeRef = ref();
const select_user_value = ref(); // 成员树形选中值
const user_checked = ref([]); // 成员多选选中值
const onUserTreeClick = (node) => { // 点击成员树形回调
userList.value = node.user;
user_checked.value = checkedGroup.value.user.map(item => item.id)
}
const onUserChange = (val) => { // 成员多选组点击回调
}
const onCheckUserChange = (val, evt) => {
nextTick(() => {
let checked = false;
let id = '';
let name = '';
let type = '';
if ($(evt.target).attr('aria-checked') === undefined) {
checked = $(evt.target).parents('.van-checkbox').attr('aria-checked');
id = $(evt.target).parents('.van-checkbox').attr('id');
name = $(evt.target).parents('.van-checkbox').attr('text');
type = $(evt.target).parents('.van-checkbox').attr('type');
} else {
checked = $(evt.target).attr('aria-checked');
id = $(evt.target).attr('id');
name = $(evt.target).attr('text');
type = $(evt.target).attr('type');
}
let obj = {
id: +id,
name,
type
}
checkedGroup.value.user.push(obj);
checkedGroup.value.user = _.uniqBy(checkedGroup.value.user, 'id');
//
if (checked === 'false') {
if (val.type === 'user') {
const index = checkedGroup.value.user.indexOf(val);
checkedGroup.value.user.splice(index, 1);
}
}
})
}
/**************** END *****************/
/***************** 搜索结果集模块 ********************/
// 模拟数据
const user_dept_role = ref({
"dept": [
{
"type": "dept",
"name": "男10组",
"id": 137571
}, {
"type": "dept",
"name": "主持组",
"id": 107700
}
],
"role": [
{
"type": "role",
"name": "八关斋戒",
"id": 624337
},
{
"id": 82983,
"name": "场地管理",
"type": "role"
}
],
"user": [
{
"id": 137918,
"name": "10组寝室长",
"type": "user"
},
{
"id": 137919,
"name": "11组寝室长",
"type": "user"
}
]
});
const onSearchResultChange = (val) => { // 监听搜索结果集点击回调,结果集为选中项
}
const onSearchResultClick = (val, evt) => { // 搜索结果集项点击回调
nextTick(() => {
let checked = false;
let id = '';
let name = '';
let type = '';
if ($(evt.target).attr('aria-checked') === undefined) { // 点击子元素
checked = $(evt.target).parents('.van-checkbox').attr('aria-checked');
id = $(evt.target).parents('.van-checkbox').attr('id');
name = $(evt.target).parents('.van-checkbox').attr('text');
type = $(evt.target).parents('.van-checkbox').attr('type');
} else { // 点击父元素
checked = $(evt.target).attr('aria-checked');
id = $(evt.target).attr('id');
name = $(evt.target).attr('text');
type = $(evt.target).attr('type');
}
let obj = { // 点击元素属性
id: +id,
name,
type
}
// 对应类型添加到选中组
checkedGroup.value[type].push(obj);
checkedGroup.value[type] = _.uniqBy(checkedGroup.value[type], 'id');
// 取消选中处理
if (checked === 'false') {
// 移除对应的数据集
const index = checkedGroup.value[type].findIndex(item => item.id === obj.id);
checkedGroup.value[type].splice(index, 1);
if (type === 'dept') {
// 树形 组织结构移除对应ID
deptTreeRef.value?.setChecked(obj.id, false);
}
}
});
}
// 错误提示文案
const validatorMessage = (val, rule) => {
if (required && !val.length) {
return "选择不能为空";
}
};
const rules = [{ validator, message: validatorMessage }];
</script>
<style lang="less" scoped>
......@@ -586,81 +105,5 @@ const onSearchResultClick = (val, evt) => { // 搜索结果集项点击回调
color: red;
}
}
.select-tree-box {
height: 4rem;
border: 1px dashed #dfdfdf;
margin: 1rem;
overflow: scroll;
border-radius: 5px;
padding: 0.5rem;
display: flex;
flex-wrap: wrap;
.select-tree-item {
margin-right: 5px;
margin-bottom: 5px;
font-size: 0.85rem;
padding: 5px 8px;
background-color: #C2915F;
color: #fff;
height: 1.2rem;
display: flex;
justify-content: center;
align-items: center;
}
}
}
:deep(.van-field__body) {
border: 1px solid #eaeaea;
border-radius: 0.25rem;
padding: 0.25rem 0.5rem;
}
.search-box {
display: flex;
align-items: center;
justify-content: center;
background-color: #eee;
margin: 1rem;
border-radius: 3px;
padding: 0.6rem;
font-size: 0.9rem;
}
:deep(.ctree-tree-node__checkbox_checked) {
border-color: #C2915F;
background-color: #C2915F;
}
:deep(.ctree-tree-node__title_selected) {
background-color: #f8e2cb;
}
// :deep(.ctree-tree__scroll-area) {
// margin-bottom: 2rem;
// }
.select-box {
height: 4rem;
border: 1px dashed #dfdfdf;
margin: 0 1rem;
overflow: scroll;
border-radius: 5px;
padding: 0.5rem;
display: flex;
flex-wrap: wrap;
.select-item {
margin-right: 5px;
margin-bottom: 5px;
font-size: 0.85rem;
padding: 5px 8px;
background-color: #C2915F;
color: #fff;
height: 1.2rem;
display: flex;
justify-content: center;
align-items: center;
}
}
</style>
......