123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450 |
- <template>
- <div
- :class="[
- $style.wrapper,
- {
- [$style.single]: single,
- },
- ]"
- >
- <a-spin ref="scroll" :spinning="!dataLoaded" :class="$style.spin">
- <a-dropdown
- v-if="dataLoaded"
- v-model="showMenu"
- :get-popup-container="() => this.$refs.scroll.$el"
- :trigger="['contextmenu']"
- :disabled="single"
- @visibleChange="menuVisibleChange"
- >
- <div>
- <a-tree
- v-if="treeData"
- :key="key"
- :load-data="onLoadData"
- :tree-data="treeDataComputed"
- checkable
- check-strictly
- :checked-keys="[...selectedKeys, ...targetKeys]"
- :default-expand-parent="true"
- :default-expanded-keys="expandedKeys"
- :default-expand-all="defaultExpandAll"
- :selected-keys="[selectedNode && selectedNode.eventKey]"
- @click="treeClick"
- @click.native="treeClickNative"
- @dblclick="treeDblClick"
- @rightClick="treeRightClick"
- @contextmenu.native.capture="treeRightClickNative"
- @check="
- (_, props) => {
- onChecked(_, props, [...selectedKeys, ...targetKeys], itemSelect)
- }
- "
- @select="onSelect"
- >
- </a-tree>
- </div>
- <a-menu slot="overlay" @click="menuClick">
- <a-menu-item key="1" :disabled="!selectedNodeHasSelectableChild">
- <a-icon type="check" />选中下一级节点
- </a-menu-item>
- <a-menu-item key="2" :disabled="!selectedNodeHasSelectableChild">
- <a-icon type="close" />取消下一级节点
- </a-menu-item>
- </a-menu>
- </a-dropdown>
- <!-- 空span撑开加载条 -->
- <span></span>
- </a-spin>
- <a-empty v-if="dataLoaded && treeData.length === 0" :image="simpleImage" />
- </div>
- </template>
- <script>
- import debounce from 'lodash.debounce'
- import cloneDeep from 'lodash.clonedeep'
- import lawCaseTreeService from './law-case-tree-service'
- import components from './_import-components/law-case-picker-source-tree-import'
- import pickerSourceMixin from './picker-source-mixin'
- import LawCasePickerLabel from './law-case-picker-label.vue'
- function addDisableProp(data, targetKeys = []) {
- data.forEach((item) => {
- item.disabled = targetKeys.includes(item.key)
- if (item.children) {
- addDisableProp(item.children, targetKeys)
- }
- })
- return data
- }
- function isChecked(selectedKeys, eventKey) {
- return selectedKeys.indexOf(eventKey) !== -1
- }
- /**
- * SdValuePicker的数据源:树结构
- * @displayName SdPickerSourceTree 数据源-树结构
- */
- export default {
- name: 'LawCasePickerSourceTree',
- components,
- mixins: [pickerSourceMixin],
- props: {
- /**
- * 根节点{code:'200000',name:'西安分公司'}
- */
- rootNode: {
- type: [Object, Array],
- default: undefined,
- },
- /**
- * <p>获取数据的回调,获取根节点时,parentId为undefined</p>
- * <pre>(parentId,parent) => childrenItems</pre>
- */
- loadTreeData: {
- type: Function,
- required: true,
- },
- /**
- * <p>搜索数据的回调</p>
- * <pre>(searchText) => items</pre>
- * @since 8.0.2
- */
- searchTreeData: {
- type: Function,
- default: undefined,
- },
- /**
- * 默认展开指定的树节点
- * @since 8.0.7
- */
- defaultExpandedKeys: {
- type: Array,
- default: undefined,
- },
- moduleId: {
- type: String,
- default: undefined,
- },
- // 特殊树不需要获取节点
- selectchecked: {
- type: Boolean,
- default: false,
- },
- // 加载子节点
- loadChildrenData: {
- type: Function,
- default: undefined,
- },
- },
- data() {
- return {
- treeData: null,
- searchValueDebounce: '',
- searchedData: [],
- expandedKeys: [],
- showMenu: false,
- selectedNode: undefined,
- dataList: [], // 数组dataList,搜索要用
- defaultExpandAll: false,
- key: 0,
- }
- },
- computed: {
- listOrTreeData() {
- return this.searchValueDebounce ? this.searchedData : this.treeData
- },
- treeDataComputed() {
- // targetKeys和disabledKeys中的都变成禁用状态
- return addDisableProp(this.listOrTreeData, this.targetKeys.concat(this.disabledKeys))
- },
- selectedNodeHasSelectableChild() {
- return this.selectedNode?.dataRef.children?.some((item) => item.checkable && !item.disabled)
- },
- },
- watch: {
- listOrTreeData(data) {
- // 把treeData中所有selectable的节点,转成一个列表,给父组件作为dataSource
- const result = []
- function flatten(list, that) {
- list = list ?? []
- list.forEach((item) => {
- if (item.checkable) {
- result.push(item.originalValue)
- }
- flatten(item.children, that)
- })
- }
- flatten(data, this)
- this.updateDataSource(result)
- },
- searchValue(text) {
- if (text) {
- this.defaultExpandAll = true
- } else {
- this.key++
- this.defaultExpandAll = false
- }
- this.doSearch(text)
- },
- },
- created() {
- // 传了searchTreeData函数,就显示搜索框
- // this.updateShowSearch(!!this.searchTreeData)
- this.loadDataForRoot()
- // // 用户输入搜索条件时,延迟500ms
- this.doSearch = debounce(this.doSearch, 500, {
- leading: false,
- trailing: true,
- })
- },
- methods: {
- transformData(data) {
- return data.map((d) => {
- if (d.children && d.children.length > 0) {
- d.isLeaf = d.leaf
- }
- d.code = d.id.toString()
- const { children, checkable, selectable, ...rest } = d
- let isLeaf
- if (d.isLeaf !== undefined) {
- isLeaf = d.isLeaf
- }
- if (d.isleaf !== undefined) {
- isLeaf = d.isleaf
- }
- return {
- ...rest,
- checkable: checkable ?? true,
- selectable: selectable ?? false,
- isLeaf: isLeaf ?? true,
- key: d[this.optionValue],
- title: <LawCasePickerLabel vnodes={this.render(d, 'left')} />,
- children: children && this.transformData(children),
- originalValue: rest,
- code: rest.code,
- }
- })
- },
- loadDataForRoot() {
- Promise.resolve(this.loadTreeData())
- .then((data) => {
- data[0].isLeaf = false
- this.treeData = this.transformData(data)
- if (this.defaultExpandedKeys) {
- this.expandedKeys = this.defaultExpandedKeys
- } else {
- // 没传展开的keys,根节点只有一个的话,展开它
- if (this.treeData.length === 1) {
- this.expandedKeys = [this.treeData[0].key]
- }
- }
- })
- .finally(() => (this.dataLoaded = true))
- },
- loadData(treeNode) {
- return new Promise((resolve) => {
- if (treeNode.dataRef.children) {
- // 有值了直接渲染
- resolve()
- return
- }
- Promise.resolve(
- this.loadTreeData(treeNode.dataRef.oldId, cloneDeep(treeNode.dataRef.originalValue))
- ).then((data) => {
- treeNode.dataRef.children = this.transformData(data)
- if (this.searchValueDebounce) {
- this.searchedData = [...this.searchedData]
- } else {
- this.treeData = [...this.treeData]
- }
- resolve()
- })
- })
- },
- doSearch(text) {
- this.searchValueDebounce = text
- if (!text) {
- this.searchTreeData(text)
- return
- }
- this.dataLoaded = false
- this.searchedData = []
- Promise.resolve(this.searchTreeData(text))
- .then((data) => {
- // 返回一个列表,转换成树的格式
- this.searchedData = this.transformData(data)
- })
- .finally(() => (this.dataLoaded = true))
- },
- onChecked(_, e, checkedKeys, itemSelect) {
- const { eventKey } = e.node
- if (!this.single) {
- // 单选模式,取消已经选中的,再选中当前的
- if (!isChecked(checkedKeys, eventKey)) {
- itemSelect(checkedKeys[0], false)
- itemSelect(eventKey, true)
- }
- } else {
- itemSelect(eventKey, !isChecked(checkedKeys, eventKey))
- }
- },
- treeClick(evt, treeNode) {
- const nodeData = treeNode.dataRef
- if (nodeData.disabled) return
- // 非叶子节点 + 不可选中,展开节点
- if (!nodeData.isLeaf && !nodeData.checkable && !nodeData.selectable)
- treeNode.vcTree.onNodeExpand(evt, treeNode)
- },
- treeDblClick(evt, treeNode) {
- const nodeData = treeNode.dataRef
- if (nodeData.disabled) return
- if (nodeData.checkable) {
- this.itemDblClick(evt, nodeData.key)
- }
- },
- treeClickNative() {
- // 点击树的其他区域,隐藏菜单
- if (this.single) return
- this.hideContextMemu()
- },
- treeRightClick({ node }) {
- // 记录当前点击的节点
- if (this.single) return
- this.selectedNode = node
- },
- hideContextMemu() {
- this.showMenu = false
- this.menuVisibleChange(false)
- },
- menuVisibleChange(visible) {
- if (!visible) this.selectedNode = undefined
- },
- treeRightClickNative(evt) {
- // 右键如果没有点击到树节点,不触发右键菜单
- if (this.single) return
- if (evt.target.nodeName !== 'SPAN') evt.stopPropagation()
- },
- menuClick(menu) {
- this.selectedNode.dataRef.children?.forEach((item) => {
- if (item.checkable && !item.disabled) this.itemSelect(item.key, menu.key === '1')
- })
- this.hideContextMemu()
- },
- onSelect(selectedKeys, evt) {
- /**
- * 树节点的选中事件
- * @property {Object} treeItem 选中的节点
- * @property {Object} evt 其他信息{selected: bool, selectedNodes, node, event}
- */
- this.$emit('treeItemSelect', { ...evt.node.dataRef.originalValue }, evt)
- },
- // 点击地址树展开时调用
- onLoadData(treeNode) {
- return new Promise((resolve) => {
- if (treeNode.dataRef.children) {
- // 有值了直接渲染
- resolve()
- return
- }
- Promise.resolve(lawCaseTreeService.getSelectCaseNode(treeNode.dataRef.id)).then((res) => {
- res.data.forEach((item) => {
- item.name = item.text
- item.code = item.id.toString()
- item.type = 'Group'
- item.checkable = true
- })
- treeNode.dataRef.children = this.transformData(res.data)
- this.treeData = [...this.treeData]
- resolve()
- })
- })
- },
- // 处理搜索用的dataList
- generateList(data) {
- for (let i = 0; i < data.length; i++) {
- const node = data[i]
- const key = node.id
- const title = node.text
- const props = node.props
- this.dataList.push({ key, id: key, title: title, props })
- if (node.children) {
- this.generateList(node.children)
- }
- }
- },
- },
- }
- </script>
- <style module lang="scss">
- @use '@/common/design' as *;
- @import './source.scss';
- .wrapper {
- :global .ant-tree-title {
- user-select: none;
- }
- :global(.ant-tree-child-tree) {
- li {
- width: 100px;
- }
- }
- :global(.searchTitle) {
- color: #1890ff;
- }
- }
- .single {
- :global .ant-tree-checkbox {
- .ant-tree-checkbox-inner {
- border-radius: 100px;
- &::after {
- position: absolute;
- top: 3px;
- left: 3px;
- display: table;
- width: 8px;
- height: 8px;
- content: ' ';
- background-color: $primary-color;
- border: 0;
- border-radius: 8px;
- opacity: 0;
- transition: all 0.3s cubic-bezier(0.78, 0.14, 0.15, 0.86);
- transform: scale(0);
- }
- }
- &.ant-tree-checkbox-checked {
- &::after {
- position: absolute;
- top: 16.67%;
- left: 0;
- width: 100%;
- height: 66.67%;
- content: '';
- border: 1px solid #1890ff;
- border-radius: 50%;
- animation: antRadioEffect 0.36s ease-in-out;
- animation-fill-mode: both;
- }
- .ant-tree-checkbox-inner {
- background-color: $white;
- &::after {
- opacity: 1;
- transition: all 0.3s cubic-bezier(0.78, 0.14, 0.15, 0.86);
- transform: scale(1);
- }
- }
- }
- }
- }
- </style>
- <docs>
- 需要作为 [SdValuePicker](#sdvaluepicker) 的子节点使用,样例请参考 [SdValuePicker](#sdvaluepicker)
- </docs>
|