forked from opentiny/tiny-vue
232 lines
6.2 KiB
Vue
232 lines
6.2 KiB
Vue
<template>
|
||
<div class="tiny-demo">
|
||
<tiny-button @click="visible = !visible">{{ `${visible ? '关闭' : '打开'}窗口` }}</tiny-button>
|
||
<tiny-dialog-select
|
||
ref="dialogSelect"
|
||
class="tiny-demo-tree-multi"
|
||
:visible="visible"
|
||
@update:visible="visible = $event"
|
||
popseletor="tree"
|
||
multi
|
||
:dialog-op="dialogOp"
|
||
:tree-op="treeOp"
|
||
:selected-box-op="selectedBoxOp"
|
||
:lookup-method="lookupMethod"
|
||
:before-close="beforeClose"
|
||
@change="onDialogSelectChange"
|
||
value-field="id"
|
||
text-field="label"
|
||
:main-height="290"
|
||
>
|
||
<template #option="{ option, mouseenter, mouseleave }">
|
||
<div class="tiny-demo-tree-multi-option" @mouseenter="mouseenter" @mouseleave="mouseleave">
|
||
<span class="title">{{ option._$title }}</span>
|
||
<span class="sub-text" v-if="option._$auxi">({{ option._$auxi }})</span>
|
||
</div>
|
||
</template>
|
||
<template #close="{ option, close }">
|
||
<div class="tiny-demo-tree-multi-close">
|
||
<span @click="close(option)">
|
||
<tiny-icon-close></tiny-icon-close>
|
||
</span>
|
||
</div>
|
||
</template>
|
||
</tiny-dialog-select>
|
||
</div>
|
||
</template>
|
||
|
||
<script>
|
||
import { DialogSelect, Button, Modal } from '@opentiny/vue'
|
||
import { iconClose } from '@opentiny/vue-icon'
|
||
import Sortable from 'sortablejs'
|
||
|
||
// 模拟服务侧数据
|
||
const datas = [
|
||
{ id: 1, pid: null, label: '一级 1' },
|
||
{ id: 2, pid: null, label: '一级 2' },
|
||
{ id: 3, pid: null, label: '一级 3', isLeaf: true, children: [] },
|
||
{ id: 4, pid: 1, label: '二级 4' },
|
||
{ id: 5, pid: 1, label: '二级 5', isLeaf: true, children: [] },
|
||
{ id: 6, pid: 2, label: '二级 6', isLeaf: true, children: [] },
|
||
{ id: 7, pid: 2, label: '二级 7', isLeaf: true, children: [] },
|
||
{ id: 8, pid: 4, label: '三级 8', isLeaf: true, children: [] },
|
||
{ id: 9, pid: 4, label: '三级 9', isLeaf: true, children: [] }
|
||
]
|
||
|
||
// 接口1:根据pid查询直接子节点
|
||
const queryChildrenByPid = (pid) => datas.filter((record) => record.pid === pid)
|
||
|
||
// 接口2:根据搜索文本找到匹配节点的所有父级节点id
|
||
// (支持通过节点id搜索只是为了简化示例代码,业务使用时不必支持,可把接口3实现为批量接口提高查询效率)
|
||
const queryPidsBySearchFn = (search) => {
|
||
const ids = []
|
||
|
||
// 递归记录节点id
|
||
const ff = (node, path) => {
|
||
path.unshift(node.id)
|
||
|
||
if (!node.pid) return
|
||
|
||
const p = datas.find((record) => record.id === node.pid)
|
||
|
||
ff(p, path)
|
||
}
|
||
|
||
if (search) {
|
||
datas
|
||
.filter(
|
||
(record) =>
|
||
(typeof search === 'string' && ~record.label.indexOf(search)) ||
|
||
(typeof search === 'number' && record.id === search)
|
||
)
|
||
.forEach((node) => {
|
||
const path = []
|
||
|
||
ff(node, path)
|
||
|
||
ids.splice(ids.length, 0, ...path.slice(0, path.length - 1))
|
||
})
|
||
}
|
||
|
||
return dedup(ids)
|
||
}
|
||
|
||
// 接口3:根据一组节点id找到匹配节点的所有父级节点id
|
||
// (使用了接口2只是为了简化示例代码,实现为批量接口可提高查询效率)
|
||
const queryPidsByIdsFn = (ids, pids = []) => {
|
||
let allPids = [...pids]
|
||
|
||
ids.forEach((id) => {
|
||
allPids = allPids.concat(queryPidsBySearchFn(id))
|
||
})
|
||
|
||
return dedup(allPids)
|
||
}
|
||
|
||
// 接口4:根据一组节点id查询这组节点
|
||
const queryNodesByIds = (ids) => datas.filter((row) => ~ids.indexOf(row.id))
|
||
|
||
// 去重
|
||
const dedup = (ids, tmp = []) => {
|
||
const res = [...tmp, ...ids]
|
||
|
||
return Array.from(new Set(res))
|
||
}
|
||
|
||
export default {
|
||
components: {
|
||
TinyDialogSelect: DialogSelect,
|
||
TinyButton: Button,
|
||
TinyIconClose: iconClose()
|
||
},
|
||
data() {
|
||
return {
|
||
visible: false,
|
||
dialogOp: {
|
||
top: '20vh',
|
||
width: '800px',
|
||
title: '选择公司',
|
||
beforeClose: this.beforeClose,
|
||
dialogClass: 'custom-dialog-class'
|
||
},
|
||
selectedBoxOp: {
|
||
config: {
|
||
pkField: 'id',
|
||
pkFieldType: 'number',
|
||
showField: ['label'],
|
||
plugin: Sortable
|
||
}
|
||
},
|
||
treeOp: {
|
||
nodeKey: 'id',
|
||
pathSplit: '/',
|
||
props: { children: 'children', label: 'label', isLeaf: 'isLeaf' },
|
||
checkStrictly: true,
|
||
baseIndent: 0,
|
||
load: this.remoteSearch,
|
||
queryPidsBySearch: this.queryPidsBySearch,
|
||
queryPidsByIds: this.queryPidsByIds,
|
||
defaultCheckedKeys: [1, 4, 8]
|
||
}
|
||
}
|
||
},
|
||
methods: {
|
||
beforeClose() {
|
||
return true
|
||
},
|
||
remoteSearch(node, callback) {
|
||
setTimeout(() => {
|
||
const res = queryChildrenByPid(node.level === 0 ? null : node.data.id)
|
||
|
||
callback(res)
|
||
}, 300)
|
||
},
|
||
queryPidsBySearch(search, callback) {
|
||
setTimeout(() => {
|
||
const res = queryPidsBySearchFn(search)
|
||
callback(res)
|
||
}, 300)
|
||
},
|
||
queryPidsByIds(ids, callback) {
|
||
setTimeout(() => {
|
||
const res = queryPidsByIdsFn(ids)
|
||
callback(res)
|
||
}, 300)
|
||
},
|
||
lookupMethod(values) {
|
||
return new Promise((resolve) => {
|
||
setTimeout(() => {
|
||
const res = queryNodesByIds(values)
|
||
// 序列化是为了模拟每次返回的都是新对象
|
||
const copy = JSON.parse(JSON.stringify(res))
|
||
// 已选栏在插槽中使用字段_$title和_$auxi做定制显示,所以要在lookup接口内设置;如果使用其它字段,就不用设置
|
||
copy.forEach((row) => {
|
||
row._$title = row.label
|
||
row._$auxi = '辅助文本'
|
||
})
|
||
resolve(copy)
|
||
}, 300)
|
||
})
|
||
},
|
||
onDialogSelectChange(values, texts, selectedDatas) {
|
||
Modal.message({
|
||
message: `values:${values},texts:${texts},selectedDatas:${JSON.stringify(selectedDatas)}`,
|
||
status: 'info'
|
||
})
|
||
}
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<style scoped>
|
||
.tiny-demo-tree-multi .tiny-demo-tree-multi-option {
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
white-space: nowrap;
|
||
height: 28px;
|
||
}
|
||
|
||
.tiny-demo-tree-multi .tiny-demo-tree-multi-option .title {
|
||
color: #161e26;
|
||
position: relative;
|
||
top: 4px;
|
||
}
|
||
|
||
.tiny-demo-tree-multi .tiny-demo-tree-multi-option .sub-text {
|
||
color: #8d959e;
|
||
position: relative;
|
||
top: 4px;
|
||
margin-left: 8px;
|
||
}
|
||
|
||
.tiny-demo-tree-multi-close {
|
||
height: 28px;
|
||
}
|
||
|
||
.tiny-demo-tree-multi-close > span {
|
||
position: relative;
|
||
top: 2px;
|
||
left: 12px;
|
||
}
|
||
</style>
|