123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489 |
- <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"
- v-model="checkedKeys"
- :load-data="onLoadData"
- :tree-data="treeDataComputed"
- checkable
- :checked-keys="[...selectedKeys, ...targetKeys]"
- :default-expand-parent="true"
- :default-expanded-keys="expandedKeys"
- :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"
- />
- </div>
- <a-menu slot="overlay" @click="menuClick">
- <a-menu-item key="1" :disabled="!selectedNodeHasSelectableChild">
- <a-icon type="check" />选中下一级节点1
- </a-menu-item>
- <a-menu-item key="2" :disabled="!selectedNodeHasSelectableChild">
- <a-icon type="close" />取消下一级节点2
- </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 auditPermissionTreeService from '../audit-permission-tree-service'
- import icTreeService from './ic-tree-service'
- import components from './_import-components/ic-picker-source-tree-import'
- import pickerSourceMixin from './picker-source-mixin'
- import IcPickerLabel from './ic-picker-label'
- 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: 'IcPickerSourceTree',
- 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,
- },
- versionId: {
- type: String,
- default: '',
- },
- orgId: {
- type: String,
- default: '',
- },
- selecttype: {
- type: String,
- default: 'control',
- },
- types: {
- type: String,
- default: 'NK',
- },
- isAbandonment: {
- type: String,
- default: 'NO',
- },
- // moduleId: {
- // type: String,
- // default: undefined,
- // },
- },
- data() {
- return {
- treeData: null,
- searchValueDebounce: '',
- searchedData: [],
- expandedKeys: [],
- showMenu: false,
- selectedNode: undefined,
- dataList: [], // 数组dataList,搜索要用
- checkedKeys: { checked: [], halfChecked: [] }, // 选中的节点数据
- halfCheckedKeys: [],
- isendids: [],
- }
- },
- 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) {
- this.doSearch(text)
- },
- },
- created() {
- this.loadDataForRoot()
- },
- methods: {
- transformData(data) {
- return data.map((d) => {
- const { children, checkable, selectable, isLeaf, ...rest } = d
- return {
- ...rest,
- checkable: checkable ?? true,
- selectable: selectable ?? false,
- isLeaf: isLeaf ?? true,
- key: d[this.optionValue],
- title: <IcPickerLabel vnodes={this.render(d, 'left')} />,
- children: children && this.transformData(children),
- originalValue: rest,
- code: rest.code,
- }
- })
- },
- loadDataForRoot() {
- Promise.resolve(this.loadTreeData())
- .then((data) => {
- if (this.selecttype === 'cross') {
- data.forEach((i) => {
- if (i.children) {
- i.children.forEach((c) => {
- if (c.props.isEnd === '0') {
- c.isLeaf = false
- c.cat = true
- } else {
- c.cat = false
- this.isendids.push(c.id.toString())
- }
- })
- }
- })
- }
- 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) 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))
- if (this.selecttype === 'cross') {
- this.halfCheckedKeys = _.filter((i) => this.isendids.indexOf(i) === -1)
- // this.halfCheckedKeys = _.filter((i) => i.indexOf('1000') === -1)
- } else {
- this.halfCheckedKeys = _.filter((i) => i.indexOf('1000') === -1)
- }
- }
- },
- 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) {
- const params = {
- auditOrgId: parseInt(this.orgId),
- versionId: parseInt(this.versionId),
- isAbandonment: this.isAbandonment,
- }
- if (this.selecttype === 'cross') {
- return icTreeService
- .getCategoryTree(treeNode.dataRef.id, params, this.types)
- .then((res) => {
- if (treeNode.dataRef.children !== null && treeNode.dataRef.children.length > 0) {
- console.log('有子节点,不请求')
- } else {
- res.data.forEach((item) => {
- item.code = item.id.toString()
- if (item.props.isEnd === '0') {
- item.isLeaf = false
- item.cat = true
- } else {
- item.cat = false
- this.isendids.push(item.id.toString())
- }
- })
- treeNode.dataRef.children = this.transformData(res.data)
- this.treeData = [...this.treeData]
- }
- })
- } else {
- return icTreeService.getMeasureTree(treeNode.dataRef.id, params, this.types).then((res) => {
- if (treeNode.dataRef.children !== null && treeNode.dataRef.children.length > 0) {
- console.log('有子节点,不请求')
- } else {
- res.data.forEach((item) => {
- item.code = item.id.toString()
- if (item.props.disabled) {
- item.isLeaf = false
- item.cat = true
- } else {
- item.cat = false
- }
- })
- treeNode.dataRef.children = this.transformData(res.data)
- this.treeData = [...this.treeData]
- }
- })
- }
- },
- // 初始化地址树
- initTreeData(depId) {
- const topDepId = -1
- const params = {
- auditOrgId: 521,
- versionId: 341,
- }
- icTreeService.getCategoryTree(topDepId, params).then((res1) => {
- this.spinning = false
- const treeNode = res1.data
- this.treeData = this.transformData(treeNode)
- this.expandedKeys = this.defaultTreeExpandedKeys
- this.generateList(this.treeData)
- this.empty = false
- this.dataLoaded = true
- })
- },
- // 处理搜索用的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;
- overflow: hidden;
- text-overflow: ellipsis;
- width: 200px;
- display: block;
- }
- }
- .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>
|