| | |
| | | }); |
| | | </script> |
| | | |
| | | <style> |
| | | /*每个页面公共css */ |
| | | <style lang='scss'> |
| | | </style> |
对比新文件 |
| | |
| | | # 1.4.2 |
| | | |
| | | 新增 |
| | | |
| | | 1. 新增`filterValue`属性,支持通过此关键词来搜索并筛选树结构的内容 |
| | | |
| | | # 1.4.1 |
| | | |
| | | 修复 |
| | | |
| | | 1. 修复单选 onlyRadioLeaf 时末级节点无法选中的 bug |
| | | |
| | | # 1.4.0 |
| | | |
| | | 版本调整 |
| | | |
| | | 建议更新,但需要注意,异步数据的时候,后台需返回 leaf 字段来判断是否末项数据 |
| | | |
| | | 1. **调整数据项格式,新增 `leaf` 字段,来判断是否为末节点** |
| | | 2. **调整数据项格式,新增 `sort` 字段,来排序节点位置** |
| | | 3. **注意:异步加载数据,当为末项的时候,需要服务端数据返回 `leaf` 字段** |
| | | 4. 新增 `alwaysFirstLoad` ,即异步数据总会在第一次展开节点时,拉取一次后台数据,来比对是否一致 |
| | | 5. 拆分 `field` 属性,**注意: 1.5.0 版本后将移除 `field` 属性** |
| | | 6. 新增 `labelField` 同 `field.label`,指定节点对象中某个属性为**标签**字段,默认`label` |
| | | 7. 新增 `valueField` 同 `field.key`,指定节点对象中某个属性为**值**字段,默认`value` |
| | | 8. 新增 `childrenField` 同 `field.children`,指定节点对象中某个属性为**子树节点**字段,默认`children` |
| | | 9. 新增 `disabledField` 同 `field.disabled`,指定节点对象中某个属性为**禁用**字段,默认`disabled` |
| | | 10. 新增 `appendField` 同 `field.append`,指定节点对象中某个属性为**副标签**字段,默认`append` |
| | | 11. 新增 `leafField` 同 `field.label`,指定节点对象中某个属性为**末级节点**字段,默认`leaf` |
| | | 12. 新增 `sortField` 同 `field.label`,指定节点对象中某个属性为**排序**字段,默认`sort` |
| | | 13. 新增 `isLeafFn` ,用来自定义控制数据项的末项 |
| | | 14. 更多的项目示例 |
| | | 15. 支持单选取消选中 |
| | | 16. 修复节点展开时可能存在的 bug |
| | | 17. 修复节点选择可能存在的 bug |
| | | 18. 调整为子节点默认继承父节点禁用属性 |
| | | 19. `setExpandedKeys` 添加参数一为 `all` 即可支持一键展开/收起全部节点 |
| | | 20. 其它更多优化 |
| | | |
| | | # 1.3.4 |
| | | |
| | | 优化 |
| | | |
| | | 1. 优化图标字体命名 |
| | | |
| | | # 1.3.3 |
| | | |
| | | 优化 |
| | | |
| | | 1. 新增方法调用 |
| | | > - 新增`getUncheckedKeys`,返回未选的 key |
| | | > - 新增`getUncheckedNodes`,返回未选的节点 |
| | | > - 新增`getUnexpandedKeys`,返回未展开的 key |
| | | > - 新增`getUnexpandedNodes`,返回未展开的节点 |
| | | 2. 优化示例项目 |
| | | |
| | | # 1.3.2 |
| | | |
| | | 修复 |
| | | |
| | | 1. 修复在 APP 真机环境中的报错 |
| | | |
| | | # 1.3.1 |
| | | |
| | | 修复 |
| | | |
| | | 1. 修复方法`setExpandedKeys`没联动展开上级父子节点 |
| | | |
| | | # 1.3.0 |
| | | |
| | | 优化 |
| | | |
| | | 1. `field`新增字段 `append` 用于在标签后面显示小提示 |
| | | 2. 新增支持点击标签也能选中节点 |
| | | 3. 方法`setExpandedKeys`支持加载动态数据 |
| | | 4. 修复父节点禁用,则不能展开及图标展开显示 |
| | | 5. 修复动态加载数据时,末级节点的 `children` 为 `null` 时仍显示展开图标 |
| | | |
| | | # 1.2.6 |
| | | |
| | | 新增 |
| | | |
| | | 1. 新增支持主题换色 |
| | | 2. 支持单选的`onlyRadioLeaf`为`true`时可点父节点展开/收起 |
| | | 3. 优化`expandChecked`调整为不展开无子节点的节点 |
| | | |
| | | # 1.2.5 |
| | | |
| | | 新增 |
| | | |
| | | 1. 新增 `expandChecked`,控制选择时是否展开当前已选的所有下级节点 |
| | | |
| | | # 1.2.4 |
| | | |
| | | 修复 |
| | | |
| | | 1. 修复动态数据展开状态异常问题 |
| | | |
| | | # 1.2.3 |
| | | |
| | | 新增 |
| | | |
| | | 1. 新增 `checkedDisabled`,是否渲染禁用值 |
| | | 2. 新增 `packDisabledkey`,是否返回已禁用并选中的 key |
| | | 3. 修复选择父级时,子级已禁用但仍被选中的问题 |
| | | |
| | | # 1.2.2 |
| | | |
| | | 优化 |
| | | |
| | | 1. 调整动态数据载入处理方式 |
| | | 2. 修复节点数据因动态数据引起的状态异常 |
| | | 3. 修复初始节点数据默认选中 |
| | | |
| | | # 1.2.1 |
| | | |
| | | 修复 |
| | | |
| | | 1. 修复切换`选中状态`被重复选中问题 |
| | | 2. 修复动态数据引起的重复选择问题 |
| | | |
| | | # 1.2.0 |
| | | |
| | | 新增 |
| | | |
| | | 1. 新增方法调用 |
| | | > - 新增`setCheckedKeys`,方法设置指定 key 的节点选中状态 |
| | | > - 新增`setExpandedKeys`,方法设置指定 key 的节点展开状态 |
| | | 2. 修复小程序重复插槽一直刷报错问题 |
| | | 3. 优化展开时,会展开子级所以下级节点 |
| | | |
| | | # 1.1.1 |
| | | |
| | | 新增 |
| | | |
| | | 1. 新增`data`的`disabled`,支持节点禁用状态 |
| | | 2. 新增`field`的`disabled`,可自定`disabled`字段值 |
| | | |
| | | # 1.1.0 |
| | | |
| | | 新增 |
| | | |
| | | 1. 新增`loadMode`、`loadApi`,支持展开时加载异步数据 |
| | | 2. 新增方法调用 |
| | | > - 新增`getCheckedKeys`,方法返回已选的 key |
| | | > - 新增`getHalfCheckedKeys`,方法返回半选的 key |
| | | > - 新增`getExpandedKeys`,方法返回已展开的 key |
| | | > - 新增`getCheckedNodes`,方法返回已选的节点 |
| | | > - 新增`getHalfCheckedNodes`,方法返回半选的节点 |
| | | > - 新增`getExpandedNodes`,方法返回已展开的节点 |
| | | 3. 对代码进行重构,更易于后期拓展 |
| | | 4. 此次更新后,页面多个的 DaTee 组件间的数据不再关联 |
| | | |
| | | # 1.0.6 |
| | | |
| | | 新增 |
| | | |
| | | 1. 新增`checkStrictly`,多选模式下选中时是否父子不关联 |
| | | |
| | | # 1.0.5 |
| | | |
| | | 修复 |
| | | |
| | | 1. 修复多选时已选数据重复问题 |
| | | |
| | | # 1.0.4 |
| | | |
| | | 修复 |
| | | |
| | | 1. 修复 `change` 事件回调数据的问题 |
| | | |
| | | # 1.0.3 |
| | | |
| | | 优化 |
| | | |
| | | 1. 优化文档及示例说明 |
| | | |
| | | # 1.0.2 |
| | | |
| | | 新增 |
| | | |
| | | 1. 新增 `onlyRadioLeaf` ,单选时只允许选中末级 |
| | | 2. 优化默认展开及默认选择的展开问题 |
| | | |
| | | # 1.0.1 |
| | | |
| | | 新增 |
| | | |
| | | 1. 支持展开/收起回调事件`@expand` |
| | | |
| | | # 1.0.0 |
| | | |
| | | 初始版本 1.0.0,基于 Vue3 进行开发,支持单选、多选,兼容各大平台 |
| | | |
| | | 1. 支持单选 |
| | | 2. 支持多选 |
对比新文件 |
| | |
| | | <template> |
| | | <view class="da-tree" :style="{'--theme-color': themeColor}"> |
| | | <scroll-view class="da-tree-scroll" :scroll-y="true" :scroll-x="false"> |
| | | <view |
| | | class="da-tree-item" |
| | | :class="{'is-show': item.show}" |
| | | :style="{paddingLeft: item.level * indent + 'rpx'}" |
| | | v-for="item in datalist" |
| | | :key="item.key"> |
| | | <view |
| | | v-if="item.showArrow && !filterValue" |
| | | class="da-tree-item__icon" |
| | | @click="handleExpandedChange(item)"> |
| | | <view :class="['da-tree-item__icon--arr','is-loading']" v-if="loadLoading && item.loading"></view> |
| | | <view :class="['da-tree-item__icon--arr','is-expand', {'is-right':!item.expand}]" v-else></view> |
| | | </view> |
| | | <view v-else class="da-tree-item__icon"></view> |
| | | <view |
| | | class="da-tree-item__checkbox" |
| | | :class="[`da-tree-item__checkbox--${checkboxPlacement}`,{'is--disabled': item.disabled}]" |
| | | v-if="showCheckbox" |
| | | @click="handleCheckChange(item)"> |
| | | <view class="da-tree-item__checkbox--icon da-tree-checkbox-checked" v-if="item.checkedStatus === isCheckedStatus"></view> |
| | | <view class="da-tree-item__checkbox--icon da-tree-checkbox-indeterminate" v-else-if="item.checkedStatus === halfCheckedStatus"></view> |
| | | <view class="da-tree-item__checkbox--icon da-tree-checkbox-outline" v-else></view> |
| | | </view> |
| | | <view |
| | | class="da-tree-item__checkbox" |
| | | :class="[`da-tree-item__checkbox--${checkboxPlacement}`,{'is--disabled': item.disabled}]" |
| | | v-if="!showCheckbox && showRadioIcon" |
| | | @click="handleRadioChange(item)"> |
| | | <view class="da-tree-item__checkbox--icon da-tree-radio-checked" v-if="item.checkedStatus === isCheckedStatus"></view> |
| | | <view class="da-tree-item__checkbox--icon da-tree-radio-indeterminate" v-else-if="item.checkedStatus === halfCheckedStatus"></view> |
| | | <view class="da-tree-item__checkbox--icon da-tree-radio-outline" v-else></view> |
| | | </view> |
| | | <view class="da-tree-item__label" :class="'da-tree-item__label--'+item.checkedStatus" @click="handleLabelClick(item)">{{ item.label }} <text class="da-tree-item__label--append" v-if="item.append">{{ item.append }}</text></view> |
| | | </view> |
| | | </scroll-view> |
| | | </view> |
| | | </template> |
| | | |
| | | <script> |
| | | |
| | | import { defineComponent, ref, unref, watch } from 'vue' |
| | | |
| | | import { |
| | | unCheckedStatus, |
| | | halfCheckedStatus, |
| | | isCheckedStatus, |
| | | deepClone, |
| | | getAllNodeKeys, |
| | | getAllNodes, |
| | | logError, |
| | | isArray, |
| | | isString, |
| | | isNumber, |
| | | isFunction, |
| | | } from './utils' |
| | | import basicProps from './props' |
| | | |
| | | export default defineComponent({ |
| | | name: 'DaTree', |
| | | props: basicProps, |
| | | emits: ['change', 'expand'], |
| | | setup(props, { emit }) { |
| | | /** 原始的树数据 */ |
| | | const dataRef = ref([]) |
| | | /** 处理后的一维树项数据 */ |
| | | const datalist = ref([]) |
| | | /** 处理后的以key为键值的树项数据 */ |
| | | const datamap = ref({}) |
| | | /** 默认的展开数据 */ |
| | | const expandedKeys = ref([]) |
| | | /** 默认的已选数据 */ |
| | | const checkedKeys = ref(null) |
| | | /** 加载状态 */ |
| | | const loadLoading = ref(false) |
| | | let fieldMap = { |
| | | value: 'value', |
| | | label: 'label', |
| | | children: 'children', |
| | | disabled: 'disabled', |
| | | append: 'append', |
| | | leaf: 'leaf', |
| | | sort: 'sort', |
| | | } |
| | | |
| | | /** |
| | | * 初始化数据结构 |
| | | */ |
| | | function initData() { |
| | | fieldMap = { |
| | | value: props.field?.key || props.field?.value || props.valueField || 'value', |
| | | label: props.field?.label || props.labelField || 'label', |
| | | children: props.field?.children || props.childrenField || 'children', |
| | | disabled: props.field?.disabled || props.disabledField || 'disabled', |
| | | append: props.field?.append || props.appendField || 'append', |
| | | leaf: props.field?.leaf || props.leafField || 'leaf', |
| | | sort: props.field?.sort || props.sortField || 'sort', |
| | | } |
| | | |
| | | const data = deepClone(dataRef.value) |
| | | datalist.value = [] |
| | | datamap.value = {} |
| | | |
| | | // clean tree |
| | | handleTreeData(data) |
| | | // flat tree |
| | | datalist.value = checkInitData(datalist.value) |
| | | // console.log('init datalist', datalist.value) |
| | | // console.log('init datamap', datamap.value) |
| | | } |
| | | |
| | | /** |
| | | * 转换为节点数据 |
| | | * @param data |
| | | * @param parent |
| | | * @param level |
| | | */ |
| | | function handleTreeData(data = [], parent = null, level = 0, insertIndex = -1) { |
| | | return data.reduce((prev, cur, index) => { |
| | | const key = cur[fieldMap.value] |
| | | const children = cur[fieldMap.children] || null |
| | | const newItem = createNewItem(cur, index, parent, level) |
| | | if (insertIndex > -1) { |
| | | // 插入子项尾部 |
| | | const index = (parent.childrenKeys?.length || 0) + insertIndex + 1 |
| | | if (!parent?.childrenKeys?.includes(key)) { |
| | | datamap.value[key] = newItem |
| | | datalist.value.splice(index, 0, newItem) |
| | | parent.children.push(newItem) |
| | | if (newItem.parentKeys?.length) { |
| | | newItem.parentKeys.forEach(k => { |
| | | datamap.value[k].childrenKeys = [...datamap.value[k].childrenKeys, newItem.key] |
| | | }) |
| | | } |
| | | } |
| | | } else { |
| | | datamap.value[key] = newItem |
| | | datalist.value.push(newItem) |
| | | } |
| | | |
| | | const hasChildren = children && children.length > 0 |
| | | if (hasChildren) { |
| | | const childrenData = handleTreeData(children, newItem, level + 1) |
| | | // childrenData.sort((a, b) => a.sort - b.sort) |
| | | newItem.children = childrenData |
| | | const childrenKeys = childrenData.reduce((p, k) => { |
| | | const keys = k.childrenKeys |
| | | p.push(...keys, k.key) |
| | | return p |
| | | }, []) |
| | | newItem.childrenKeys = childrenKeys |
| | | } |
| | | prev.push(newItem) |
| | | return prev |
| | | }, []) |
| | | } |
| | | |
| | | /** |
| | | * 创建节点 |
| | | * @param item |
| | | * @param index |
| | | * @param parent |
| | | * @param level |
| | | */ |
| | | function createNewItem(item, index, parent, level) { |
| | | const key = item[fieldMap.value] |
| | | const label = item[fieldMap.label] |
| | | const sort = item[fieldMap.sort] || 0 |
| | | const children = item[fieldMap.children] || null |
| | | const append = item[fieldMap.append] || null |
| | | let disabled = item[fieldMap.disabled] || false |
| | | // 优先继承父级禁用属性 |
| | | disabled = parent?.disabled || disabled |
| | | let isLeaf = isFunction(props.isLeafFn) ? props.isLeafFn(item) : (item[fieldMap.leaf] || false) |
| | | // const hasChildren = children && children.length > 0 |
| | | const isEmptyChildren = children && children.length === 0 |
| | | let showArrow = true |
| | | // let isLeaf = !hasChildren |
| | | let expand = props.defaultExpandAll || false |
| | | // 是否异步加载模式 |
| | | const isLoadMode = props.loadMode && isFunction(props.loadApi) |
| | | |
| | | if (!children) { |
| | | expand = false |
| | | if (isLoadMode) { |
| | | showArrow = true |
| | | } else { |
| | | isLeaf = true |
| | | showArrow = false |
| | | } |
| | | } |
| | | |
| | | if (isEmptyChildren) { |
| | | expand = false |
| | | if (isLoadMode) { |
| | | showArrow = true |
| | | } else { |
| | | isLeaf = true |
| | | showArrow = false |
| | | } |
| | | } |
| | | |
| | | if (isLeaf) { |
| | | showArrow = false |
| | | expand = false |
| | | } else { |
| | | showArrow = true |
| | | } |
| | | |
| | | // onlyRadioLeaf 单选只能选择末级节点 |
| | | if (!props.showCheckbox) { |
| | | if (props.onlyRadioLeaf) { |
| | | if (!isLeaf) { |
| | | disabled = true |
| | | } else { |
| | | // 仍旧继承父类原始禁用状态 |
| | | disabled = parent?.originItem?.disabled || false |
| | | } |
| | | } |
| | | } |
| | | |
| | | if (disabled) { |
| | | if (isLeaf || !children || isEmptyChildren) { |
| | | expand = false |
| | | showArrow = false |
| | | } |
| | | } |
| | | |
| | | const parentKey = parent ? parent.key : null |
| | | const show = props.defaultExpandAll || level === 0 |
| | | |
| | | const newItem = { |
| | | key, |
| | | parentKey, |
| | | label, |
| | | append, |
| | | isLeaf, |
| | | showArrow, |
| | | level, |
| | | expand, |
| | | show, |
| | | sort, |
| | | disabled, |
| | | loaded: false, |
| | | loading: false, |
| | | indexs: [index], |
| | | checkedStatus: unCheckedStatus, |
| | | parentKeys: [], |
| | | childrenKeys: [], |
| | | children: [], |
| | | originItem: item, |
| | | } |
| | | |
| | | if (parent) { |
| | | newItem.parentKeys = [parent.key, ...parent.parentKeys] |
| | | newItem.indexs = [...parent.indexs, index] |
| | | } |
| | | |
| | | return newItem |
| | | } |
| | | |
| | | /** |
| | | * 处理初始化内容 |
| | | * @param list |
| | | */ |
| | | function checkInitData(list) { |
| | | let checkedKeyList = null |
| | | let expandedKeyList = [] |
| | | if (props.showCheckbox) { |
| | | checkedKeyList = [...new Set(checkedKeys.value || [])] |
| | | expandedKeyList = props.expandChecked ? ([...(checkedKeys.value || []), ...(expandedKeys.value || [])]) : expandedKeys.value |
| | | } else { |
| | | checkedKeyList = checkedKeys.value || null |
| | | expandedKeyList = props.expandChecked && checkedKeys.value ? ([checkedKeys.value, ...(expandedKeys.value || [])]) : expandedKeys.value |
| | | } |
| | | |
| | | handleCheckState(list, checkedKeyList, true) |
| | | |
| | | // 处理初始展开 |
| | | expandedKeyList = [...new Set(expandedKeyList)] |
| | | if (!props.defaultExpandAll) { |
| | | handleExpandState(list, expandedKeyList, true) |
| | | } |
| | | |
| | | list.sort((a, b) => { |
| | | if (a.sort === 0 && b.sort === 0) { |
| | | return 0 |
| | | } |
| | | |
| | | if (a.parentKey === b.parentKey) { |
| | | if (a.sort - b.sort > 0) { |
| | | return 1 |
| | | } else { |
| | | return -1 |
| | | } |
| | | } |
| | | |
| | | return 0 |
| | | }) |
| | | |
| | | return list |
| | | } |
| | | |
| | | /** |
| | | * 处理选中 |
| | | * @param list |
| | | * @param checkedKeyList |
| | | */ |
| | | function handleCheckState(list, checkedKeyList, checked = true) { |
| | | // 多选 |
| | | if (props.showCheckbox) { |
| | | if (checkedKeyList?.length) { |
| | | checkedKeyList.forEach(k => { |
| | | const item = datamap.value[k] |
| | | if (item) { |
| | | checkTheChecked(item, checked) |
| | | } |
| | | }) |
| | | } |
| | | |
| | | return |
| | | } |
| | | |
| | | // 单选 |
| | | for (let i = 0; i < list.length; i++) { |
| | | const item = list[i] |
| | | if (item.key === checkedKeyList) { |
| | | checkTheRadio(item, checked) |
| | | break |
| | | } |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 校验多选节点 |
| | | * @param item |
| | | * @param checked |
| | | */ |
| | | function checkTheChecked(item, checked = true) { |
| | | const { childrenKeys, parentKeys, disabled = false } = item |
| | | if (!props.checkedDisabled && disabled) return |
| | | |
| | | // 当前 |
| | | item.checkedStatus = checked ? isCheckedStatus : unCheckedStatus |
| | | |
| | | if (!props.checkStrictly) { |
| | | // 子类 |
| | | childrenKeys.forEach(k => { |
| | | const childrenItem = unref(datamap)[k] |
| | | childrenItem.checkedStatus = (!props.checkedDisabled && childrenItem.disabled) ? childrenItem.checkedStatus : item.checkedStatus |
| | | }) |
| | | |
| | | // 父类 |
| | | parentKeys.forEach(k => { |
| | | const parentItem = datamap.value[k] |
| | | parentItem.checkedStatus = getParentCheckedStatus(parentItem) |
| | | }) |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 校验单选节点 |
| | | * @param item |
| | | */ |
| | | function checkTheRadio(item, checked) { |
| | | const { parentKeys, isLeaf, disabled = false } = item |
| | | if (!props.checkedDisabled && disabled) return |
| | | |
| | | // 限制末节点选中,但当前非末节点 |
| | | if (props.onlyRadioLeaf && !isLeaf) { |
| | | logError(`限制了末节点选中,当前[${item.label}]非末节点`) |
| | | return |
| | | } |
| | | |
| | | if (datalist.value?.length) { |
| | | datalist.value.forEach(k => { |
| | | k.checkedStatus = unCheckedStatus |
| | | }) |
| | | } |
| | | |
| | | parentKeys.forEach(k => { |
| | | const parentItem = datamap.value[k] |
| | | parentItem.checkedStatus = checked ? getParentCheckedStatus(parentItem) : unCheckedStatus |
| | | }) |
| | | |
| | | // 当前 |
| | | item.checkedStatus = checked ? isCheckedStatus : unCheckedStatus |
| | | } |
| | | |
| | | /** |
| | | * 处理父节点展开 |
| | | * @param item |
| | | * @param expand |
| | | */ |
| | | // function handleExpandParentNode(item, expand = true) { |
| | | // if (!expand) return |
| | | |
| | | // if (item?.parentKeys?.length) { |
| | | // item.parentKeys.forEach(pk => { |
| | | // if (!datamap.value[pk].expand) { |
| | | // datamap.value[pk].expand = true |
| | | // } |
| | | // }) |
| | | // } |
| | | // } |
| | | |
| | | /** |
| | | * 处理节点展开 |
| | | * @param list |
| | | * @param expandedKeyList |
| | | * @param expand |
| | | */ |
| | | function handleExpandState(list, expandedKeyList, expand = true) { |
| | | // 收起 |
| | | if (expand === false) { |
| | | for (let i = 0; i < list.length; i++) { |
| | | const item = list[i] |
| | | if (expandedKeyList?.includes(item.key)) { |
| | | item.expand = false |
| | | if (item.childrenKeys?.length) { |
| | | item.childrenKeys.forEach(ck => { |
| | | datamap.value[ck].expand = false |
| | | datamap.value[ck].show = false |
| | | }) |
| | | } |
| | | } |
| | | } |
| | | return |
| | | } |
| | | // 展开 |
| | | for (let i = 0; i < list.length; i++) { |
| | | const item = list[i] |
| | | // 处理展开 |
| | | if (expandedKeyList?.includes(item.key)) { |
| | | // 父子 |
| | | item.expand = true |
| | | if (item.children?.length) { |
| | | item.children.forEach(k => { |
| | | const kItem = unref(datamap)[k.key] |
| | | kItem.show = true |
| | | }) |
| | | } |
| | | |
| | | // 族系 |
| | | if (item.parentKeys?.length) { |
| | | item.parentKeys.forEach(k => { |
| | | const kItem = unref(datamap)[k] |
| | | kItem.expand = true |
| | | if (kItem.children?.length) { |
| | | kItem.children.forEach(k => { |
| | | const skItem = unref(datamap)[k.key] |
| | | skItem.show = true |
| | | }) |
| | | } |
| | | }) |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 点击选框 |
| | | * @param item |
| | | */ |
| | | function handleCheckChange(item) { |
| | | const { childrenKeys, parentKeys, checkedStatus, isLeaf, disabled = false } = item |
| | | if (!props.showCheckbox) return |
| | | if (disabled) return |
| | | |
| | | // 当前 |
| | | item.checkedStatus = checkedStatus === isCheckedStatus ? unCheckedStatus : isCheckedStatus |
| | | |
| | | // 子类 |
| | | if (!props.checkStrictly) { |
| | | if (props.expandChecked) { |
| | | item.show = true |
| | | item.expand = childrenKeys?.length > 0 || isLeaf |
| | | } |
| | | |
| | | childrenKeys.forEach(k => { |
| | | const childrenItem = unref(datamap)[k] |
| | | childrenItem.checkedStatus = childrenItem.disabled ? childrenItem.checkedStatus : item.checkedStatus |
| | | |
| | | if (props.expandChecked) { |
| | | childrenItem.show = true |
| | | childrenItem.expand = childrenItem?.childrenKeys?.length > 0 || childrenItem.isLeaf |
| | | } |
| | | }) |
| | | } else { |
| | | if (props.expandChecked) { |
| | | logError(`多选时,当 checkStrictly 为 true 时,不支持选择自动展开子节点属性(expandChecked)`) |
| | | } |
| | | } |
| | | |
| | | // 父类 |
| | | if (!props.checkStrictly) { |
| | | parentKeys.forEach(k => { |
| | | const parentItem = datamap.value[k] |
| | | parentItem.checkedStatus = getParentCheckedStatus(parentItem) |
| | | }) |
| | | } |
| | | |
| | | const hasCheckedKeys = [] |
| | | for (let i = 0; i < datalist.value.length; i++) { |
| | | const k = datalist.value[i] |
| | | if (k.checkedStatus === isCheckedStatus) { |
| | | if ((props.packDisabledkey && k.disabled) || !k.disabled) { |
| | | hasCheckedKeys.push(k.key) |
| | | } |
| | | } |
| | | } |
| | | |
| | | checkedKeys.value = [...hasCheckedKeys] |
| | | |
| | | emit('change', hasCheckedKeys, item) |
| | | } |
| | | |
| | | /** |
| | | * 点击单选 |
| | | * @param item |
| | | */ |
| | | function handleRadioChange(item) { |
| | | const { parentKeys, checkedStatus, key, disabled = false, isLeaf } = item |
| | | if (props.showCheckbox) return |
| | | if (props.onlyRadioLeaf && !isLeaf) handleExpandedChange(item) |
| | | |
| | | if (disabled) return |
| | | |
| | | // 重置所有选择 |
| | | if (datalist.value?.length) { |
| | | for (let i = 0; i < datalist.value.length; i++) { |
| | | const k = datalist.value[i] |
| | | k.checkedStatus = unCheckedStatus |
| | | } |
| | | } |
| | | |
| | | parentKeys.forEach(k => { |
| | | const parentItem = datamap.value[k] |
| | | parentItem.checkedStatus = getParentCheckedStatus(parentItem) |
| | | }) |
| | | |
| | | // 当前 |
| | | item.checkedStatus = checkedStatus === isCheckedStatus ? unCheckedStatus : isCheckedStatus |
| | | |
| | | checkedKeys.value = key |
| | | emit('change', key, item) |
| | | } |
| | | |
| | | /** |
| | | * 点击标签 |
| | | */ |
| | | function handleLabelClick(item) { |
| | | if (props.showCheckbox) { |
| | | handleCheckChange(item) |
| | | } else { |
| | | handleRadioChange(item) |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 点击展开收起 |
| | | * @param item |
| | | */ |
| | | async function handleExpandedChange(item) { |
| | | if (props.filterValue) return |
| | | |
| | | const { expand, loading = false, disabled } = item |
| | | if (loadLoading.value && loading) return |
| | | |
| | | checkExpandedChange(item) |
| | | |
| | | // 异步 |
| | | item.expand = !expand |
| | | |
| | | let currentItem = null |
| | | if (!disabled) { |
| | | if (!props.showCheckbox && props.onlyRadioLeaf && props.loadMode) { |
| | | logError(`单选时,当 onlyRadioLeaf 为 true 时不支持动态数据`) |
| | | } else { |
| | | currentItem = await loadExpandNode(item) |
| | | } |
| | | } |
| | | |
| | | emit('expand', !expand, currentItem || item || null) |
| | | } |
| | | |
| | | /** |
| | | * 检查展开状态 |
| | | * @param item |
| | | */ |
| | | function checkExpandedChange(item) { |
| | | const { expand, childrenKeys, children = null } = item |
| | | |
| | | if (expand) { |
| | | if (childrenKeys?.length) { |
| | | childrenKeys.forEach(k => { |
| | | if (unref(datamap)[k]) { |
| | | unref(datamap)[k].show = false |
| | | unref(datamap)[k].expand = false |
| | | } |
| | | }) |
| | | } |
| | | } else { |
| | | if (children?.length) { |
| | | const childrenKeys = children.map(k => k.key) |
| | | childrenKeys.forEach(k => { |
| | | if (unref(datamap)[k]) { |
| | | unref(datamap)[k].show = true |
| | | } |
| | | }) |
| | | } |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 加载异步数据 |
| | | * @param item |
| | | */ |
| | | async function loadExpandNode(item) { |
| | | const { expand, key, loaded, children } = item |
| | | if (children?.length && !props.alwaysFirstLoad) { |
| | | return item |
| | | } |
| | | |
| | | if (expand && props.loadMode && !loaded) { |
| | | if (isFunction(props.loadApi)) { |
| | | expandedKeys.value.push(key) |
| | | loadLoading.value = true |
| | | item.loading = true |
| | | |
| | | const currentNode = deepClone(item) |
| | | const apiRes = await props.loadApi(currentNode) |
| | | |
| | | // 新增子项 |
| | | let newChildren = [...(item.originItem?.children || []), ...(apiRes || [])] |
| | | const newChildrenObj = {} |
| | | newChildren = newChildren.reduce((total, next) => { |
| | | newChildrenObj[next[fieldMap.value]] ? '' : newChildrenObj[next[fieldMap.value]] = true && total.push(next) |
| | | return total |
| | | }, []) |
| | | |
| | | item.originItem.children = newChildren || null |
| | | if (apiRes?.length) { |
| | | const insertIndex = datalist.value.findIndex(k => k.key === item.key) |
| | | handleTreeData(apiRes, item, item.level + 1, insertIndex) |
| | | datalist.value = checkInitData(datalist.value) |
| | | } else { |
| | | // 加载后无数据就移除展开图标 |
| | | item.expand = false |
| | | item.isLeaf = true |
| | | item.showArrow = false |
| | | } |
| | | |
| | | loadLoading.value = false |
| | | item.loading = false |
| | | item.loaded = true |
| | | } |
| | | } else { |
| | | const eki = expandedKeys.value.findIndex(k => k === key) |
| | | if (eki >= 0) { |
| | | expandedKeys.value.splice(eki, 1) |
| | | } |
| | | } |
| | | |
| | | return item |
| | | } |
| | | |
| | | /** |
| | | * 获取父类的选中状态 |
| | | * @param item |
| | | */ |
| | | function getParentCheckedStatus(item) { |
| | | if (!item) { |
| | | return unCheckedStatus |
| | | } |
| | | |
| | | if (!props.checkedDisabled && item.disabled) { |
| | | return item.checkedStatus || unCheckedStatus |
| | | } |
| | | |
| | | // 单选时,父类永远为半选 |
| | | if (!props.showCheckbox) { |
| | | return halfCheckedStatus |
| | | } |
| | | |
| | | const { children } = item |
| | | // 子类全选中 |
| | | const childrenCheckedAll = children.every(k => k.checkedStatus === isCheckedStatus) |
| | | if (childrenCheckedAll) { |
| | | return isCheckedStatus |
| | | } |
| | | |
| | | // 子类全不选中 |
| | | const childrenUncheckedAll = children.every(k => k.checkedStatus === unCheckedStatus) |
| | | if (childrenUncheckedAll) { |
| | | return unCheckedStatus |
| | | } |
| | | |
| | | return halfCheckedStatus |
| | | } |
| | | |
| | | function filterData() { |
| | | if (props.filterValue === '') { |
| | | datalist.value.forEach(k => { |
| | | k.show = true |
| | | }) |
| | | return |
| | | } |
| | | |
| | | datalist.value.forEach(k => { |
| | | if (k.label.indexOf(props.filterValue) > -1) { |
| | | k.show = true |
| | | k.parentKeys.forEach(k => { |
| | | datamap.value[k].show = true |
| | | }) |
| | | } else { |
| | | k.show = false |
| | | } |
| | | }) |
| | | |
| | | datalist.value.forEach(k => { |
| | | if (k.show) { |
| | | k.parentKeys.forEach(k => { |
| | | datamap.value[k].show = true |
| | | }) |
| | | } |
| | | }) |
| | | } |
| | | |
| | | /** |
| | | * 返回已选的 key |
| | | */ |
| | | const getCheckedKeys = () => getAllNodeKeys(datalist.value, 'checkedStatus', isCheckedStatus, props.packDisabledkey) |
| | | /** |
| | | * 根据key设置已选 |
| | | * @param keys 单选时为数字或者字符串,多选时为数组 |
| | | * @param checked 多选时为key的数组,单选时为key |
| | | */ |
| | | function setCheckedKeys(keys, checked = true) { |
| | | // 多选 |
| | | if (props.showCheckbox) { |
| | | if (!isArray(keys)) { |
| | | logError(`setCheckedKeys 第一个参数非数组,传入的是[${keys}]`) |
| | | return |
| | | } |
| | | |
| | | const list = datalist.value |
| | | |
| | | // 取消选择 |
| | | if (checked === false) { |
| | | let newCheckedKeys = [] |
| | | for (let i = 0; i < checkedKeys.value.length; i++) { |
| | | const ck = checkedKeys.value[i] |
| | | if (!keys.includes(ck)) { |
| | | newCheckedKeys.push(ck) |
| | | } |
| | | } |
| | | newCheckedKeys = [...new Set(newCheckedKeys)] |
| | | checkedKeys.value = newCheckedKeys |
| | | handleCheckState(list, keys, false) |
| | | |
| | | return |
| | | } |
| | | |
| | | // 选择 |
| | | const newCheckedKeys = [...checkedKeys.value, ...keys] |
| | | checkedKeys.value = [...new Set(newCheckedKeys)] |
| | | handleCheckState(list, checkedKeys.value, true) |
| | | |
| | | if (props.expandChecked && checked) { |
| | | expandedKeys.value = [...new Set([...(checkedKeys.value || []), ...(keys || [])])] |
| | | handleExpandState(list, keys, true) |
| | | } |
| | | return |
| | | } |
| | | |
| | | // 单选 |
| | | // 如果为数组则拿第一个 |
| | | if (isArray(keys)) { |
| | | keys = keys[0] |
| | | } |
| | | |
| | | if (!isString(keys) && !isNumber(keys)) { |
| | | logError('setCheckedKeys 第一个参数字符串或数字,传入的是==>', keys) |
| | | return |
| | | } |
| | | |
| | | const list = datalist.value |
| | | checkedKeys.value = checked ? keys : null |
| | | |
| | | if (props.expandChecked && checked) { |
| | | handleExpandState(list, [keys], true) |
| | | } |
| | | |
| | | handleCheckState(list, keys, !!checked) |
| | | } |
| | | /** |
| | | * 返回半选的 key |
| | | */ |
| | | const getHalfCheckedKeys = () => getAllNodeKeys(datalist.value, 'checkedStatus', halfCheckedStatus, props.packDisabledkey) |
| | | /** |
| | | * 返回未选的 key |
| | | */ |
| | | const getUncheckedKeys = () => getAllNodeKeys(datalist.value, 'checkedStatus', unCheckedStatus, props.packDisabledkey) |
| | | /** |
| | | * 返回已展开的 key |
| | | */ |
| | | const getExpandedKeys = () => getAllNodeKeys(datalist.value, 'expand', true) |
| | | /** |
| | | * 返回未展开的 key |
| | | */ |
| | | const getUnexpandedKeys = () => getAllNodeKeys(datalist.value, 'expand', false) |
| | | /** |
| | | * 根据key展开/收起 |
| | | * |
| | | * @param keys 数组,或字符串 all |
| | | * @param expand true为展开/false为收起 |
| | | */ |
| | | function setExpandedKeys(keys, expand = true) { |
| | | if (!Array.isArray(keys) && keys !== 'all') { |
| | | logError('setExpandedKeys 第一个参数非数组,传入的是===>', keys) |
| | | return |
| | | } |
| | | |
| | | const list = datalist.value |
| | | |
| | | // 展开/收起全部 |
| | | if (keys === 'all') { |
| | | list.forEach(k => { |
| | | k.expand = expand |
| | | if (k.level > 0) { |
| | | k.show = expand |
| | | } |
| | | }) |
| | | return |
| | | } |
| | | |
| | | // 收起 |
| | | if (expand === false) { |
| | | const newExpandedKeys = [] |
| | | for (let i = 0; i < expandedKeys.value.length; i++) { |
| | | const ek = expandedKeys.value[i] |
| | | if (!keys.includes(ek)) { |
| | | newExpandedKeys.push(ek) |
| | | } |
| | | } |
| | | expandedKeys.value = [...new Set(newExpandedKeys)] |
| | | handleExpandState(list, keys, false) |
| | | |
| | | return |
| | | } |
| | | |
| | | // 展开 |
| | | const newExpandedKeys = [] |
| | | for (let i = 0; i < list.length; i++) { |
| | | if (keys.includes(list[i].key)) { |
| | | newExpandedKeys.push(list[i].key) |
| | | } |
| | | } |
| | | expandedKeys.value = [...new Set(newExpandedKeys)] |
| | | handleExpandState(list, newExpandedKeys, true) |
| | | } |
| | | /** |
| | | * 返回已选的节点 |
| | | */ |
| | | const getCheckedNodes = () => getAllNodes(datalist.value, 'checkedStatus', isCheckedStatus, props.packDisabledkey) |
| | | /** |
| | | * 返回半选的节点 |
| | | */ |
| | | const getHalfCheckedNodes = () => getAllNodes(datalist.value, 'checkedStatus', halfCheckedStatus, props.packDisabledkey) |
| | | /** |
| | | * 返回未选的节点 |
| | | */ |
| | | const getUncheckedNodes = () => getAllNodes(datalist.value, 'checkedStatus', unCheckedStatus, props.packDisabledkey) |
| | | /** |
| | | * 返回已展开的节点 |
| | | */ |
| | | const getExpandedNodes = () => getAllNodes(datalist.value, 'expand', true) |
| | | /** |
| | | * 返回未展开的节点 |
| | | */ |
| | | const getUnexpandedNodes = () => getAllNodes(datalist.value, 'expand', false) |
| | | |
| | | watch( |
| | | () => props.defaultExpandedKeys, |
| | | (v) => { |
| | | if (v?.length) { |
| | | expandedKeys.value = v |
| | | } else { |
| | | expandedKeys.value = [] |
| | | } |
| | | |
| | | // if (v) checkInitData(datalist.value) |
| | | }, |
| | | { immediate: true } |
| | | ) |
| | | |
| | | watch( |
| | | () => props.defaultCheckedKeys, |
| | | (v) => { |
| | | if (props.showCheckbox) { |
| | | if (v?.length) { |
| | | checkedKeys.value = v |
| | | } else { |
| | | checkedKeys.value = [] |
| | | } |
| | | } else { |
| | | if (v || v === 0) { |
| | | checkedKeys.value = v |
| | | } else { |
| | | checkedKeys.value = null |
| | | } |
| | | } |
| | | // checkInitData(datalist.value) |
| | | }, |
| | | { immediate: true } |
| | | ) |
| | | |
| | | watch( |
| | | () => props.data, |
| | | (v) => { |
| | | dataRef.value = deepClone(v) |
| | | setTimeout(() => { |
| | | initData() |
| | | }, 36) |
| | | }, |
| | | { immediate: true, deep: true } |
| | | ) |
| | | |
| | | watch( |
| | | () => props.filterValue, |
| | | () => { |
| | | filterData() |
| | | }, |
| | | ) |
| | | |
| | | return { |
| | | datalist, |
| | | unCheckedStatus, |
| | | halfCheckedStatus, |
| | | isCheckedStatus, |
| | | handleCheckChange, |
| | | handleRadioChange, |
| | | handleLabelClick, |
| | | handleExpandedChange, |
| | | loadLoading, |
| | | |
| | | // updateChildrenByKey: () => {}, |
| | | // insertBeforeByKey: () => {}, |
| | | // insertAfterByKey: () => {}, |
| | | getCheckedKeys, |
| | | setCheckedKeys, |
| | | getHalfCheckedKeys, |
| | | getUncheckedKeys, |
| | | getExpandedKeys, |
| | | getUnexpandedKeys, |
| | | setExpandedKeys, |
| | | getCheckedNodes, |
| | | getHalfCheckedNodes, |
| | | getUncheckedNodes, |
| | | getExpandedNodes, |
| | | getUnexpandedNodes, |
| | | } |
| | | }, |
| | | }) |
| | | </script> |
| | | |
| | | <style lang="scss" scoped> |
| | | @font-face { |
| | | font-family: 'da-tree-iconfont'; /* Project id */ |
| | | src: url('data:application/octet-stream;base64,AAEAAAALAIAAAwAwR1NVQiCLJXoAAAE4AAAAVE9TLzI8GU+XAAABjAAAAGBjbWFwahLuHAAAAhQAAAIQZ2x5ZtAAFwYAAAQ8AAAEWGhlYWQkfWz8AAAA4AAAADZoaGVhB94DiwAAALwAAAAkaG10eCgAAAAAAAHsAAAAKGxvY2EE3AQOAAAEJAAAABZtYXhwAR0AoAAAARgAAAAgbmFtZRCjPLAAAAiUAAACZ3Bvc3TfNfUGAAAK/AAAALsAAQAAA4D/gABcBAAAAAAABAAAAQAAAAAAAAAAAAAAAAAAAAoAAQAAAAEAAJx55T9fDzz1AAsEAAAAAADgrxSAAAAAAOCvFIAAAP/VBAADKgAAAAgAAgAAAAAAAAABAAAACgCUAAkAAAAAAAIAAAAKAAoAAAD/AAAAAAAAAAEAAAAKADAAPgACREZMVAAObGF0bgAaAAQAAAAAAAAAAQAAAAQAAAAAAAAAAQAAAAFsaWdhAAgAAAABAAAAAQAEAAQAAAABAAgAAQAGAAAAAQAAAAQEAAGQAAUAAAKJAswAAACPAokCzAAAAesAMgEIAAACAAUDAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFBmRWQAwOYE7McDgP+AAAAD3ACAAAAAAQAAAAAAAAAAAAAAAAACBAAAAAQAAAAEAAAABAAAAAQAAAAEAAAABAAAAAQAAAAEAAAABAAAAAAAAAUAAAADAAAALAAAAAQAAAGUAAEAAAAAAI4AAwABAAAALAADAAoAAAGUAAQAYgAAABAAEAADAADmBOfx6k/q1evO7MXsx///AADmBOfx6k/q1OvO7MTsx///AAAAAAAAAAAAAAAAAAAAAQAQABAAEAAQABIAEgAUAAAAAQAIAAIAAwAEAAUABgAHAAkAAAEGAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwAAAAAAHwAAAAAAAAACQAA5gQAAOYEAAAAAQAA5/EAAOfxAAAACAAA6k8AAOpPAAAAAgAA6tQAAOrUAAAAAwAA6tUAAOrVAAAABAAA684AAOvOAAAABQAA7MQAAOzEAAAABgAA7MUAAOzFAAAABwAA7McAAOzHAAAACQAAAAAALgBgAIoArgDSAQIBJgH+AiwAAAABAAAAAANZAkoAGQAAATIeAQYHDgEHDgImJyYvAiYnLgE+ATM3AxsXHQkJEEB3Nw8pKigNHyFFQiAdDQgJGxa2AkoSHCQRR4g8EBEBDhAiI0dGIyAPIRsRAQAAAAMAAP/VA6sDKgAIABEAGgAAARQGIiY0NjIWAzI2ECYgBhAWEzIWEAYgJhA2AoBMaExMaEyAjMrK/ujKyoyw+vr+oPr6AYA0TExoTEz+dsoBGMrK/ujKAwD6/qD6+gFg+gAAAAACAAAAAAOAAwAABQAVAAAlAScBJwcBMhYVERQGIyEiJjURNDYzAaoBgDz+vJg8AlQkMjIk/awkMjIkqgGAPv68mDwBgDQi/awiNDQiAlQiNAAAAAACAAAAAAOAAwAADwATAAABMhYVERQGIyEiJjURNDYzBSERIQMqIjQ0Iv2sIjQ0IgJU/awCVAMANCL9rCI0NCICVCI0Vv2sAAACAAAAAAOAAwAAAwATAAABNSEVATIWFREUBiMhIiY1ETQ2MwLW/lQCACI0NCL9rCI0NCIBVlRUAao0Iv2sIjQ0IgJUIjQAAAADAAD/1QOrAyoACAARABoAACUyNhAmIAYQFhMyFhAGICYQNhcyFhQGIiY0NgIAjMrK/ujKyoyw+vr+oPr6sFh+frB+firKARjKyv7oygMA+v6g+voBYPrUfrB+frB+AAACAAD/1QOrAyoACAARAAAlMjYQJiAGEBYTMhYQBiAmEDYCAIzKyv7oysqMsPr6/qD6+irKARjKyv7oygMA+v6g+voBYPoAAAAJAAAAAANpAwEAHAA0AEgAWQBqAHUAfgCSAJMAAAEUFhcWFxYyNzY3Njc2NTQmJyYnJiIHBgcGBwYVBxQeARcWMzI+ATc2NTQuAScmIyIOAQcGExQWFx4BMj4CNCYnLgEiDgEHBhcUHgIyPgI0LgIiDgI3FBcWMzI3NjU0JyYjIgcGBzcGFjI2NCYiBw4BJxQWMjY0JiIGJxQWFxYzMjY3NjU0JicmIyIGBwYVASYUDxMUFTEVGQ4TBggUDxMUFTEVGQ4TBgimDh8SFBEUIx8HBw4fERUREyQfBghZDgsPHiceHQsNDA4fJx4dBAfyCxUdHx0VCwsVHR8dFAzMEhMcGhUTExMcGRYSAV8BIy8jIy8RCAkHGSMZGSMZVAUECQ0GDAQJBQQKDAYNAwkCixksDxMGCQkMDRMTFxYZLA8TBgkJDA0TExsT5BQkHgcIDx4SFRETJB4HCA8eEg7+6xQfDA4LDBsdJyALDwsNGw4WZxAdFQsLFR0fHRUMDBUdTBoVExMSHRkWExMWGakXIyIvIxEIFpMRGRkjGBhfBgwECQUECgwGDQMJBQQHDwAAAAABAAAAAALGAtkAGQAAATQ+ARYXHgEXHgIGBwYPAgYHDgEuATUnATYSHCQRR4g8EBEBDhAiI0dGIyAPIRsRAQKbFx0JCRBAdzcPKSooDR8hREMgHQ0ICRsWtgAAAAAAEgDeAAEAAAAAAAAAEwAAAAEAAAAAAAEACAATAAEAAAAAAAIABwAbAAEAAAAAAAMACAAiAAEAAAAAAAQACAAqAAEAAAAAAAUACwAyAAEAAAAAAAYACAA9AAEAAAAAAAoAKwBFAAEAAAAAAAsAEwBwAAMAAQQJAAAAJgCDAAMAAQQJAAEAEACpAAMAAQQJAAIADgC5AAMAAQQJAAMAEADHAAMAAQQJAAQAEADXAAMAAQQJAAUAFgDnAAMAAQQJAAYAEAD9AAMAAQQJAAoAVgENAAMAAQQJAAsAJgFjQ3JlYXRlZCBieSBpY29uZm9udGljb25mb250UmVndWxhcmljb25mb250aWNvbmZvbnRWZXJzaW9uIDEuMGljb25mb250R2VuZXJhdGVkIGJ5IHN2ZzJ0dGYgZnJvbSBGb250ZWxsbyBwcm9qZWN0Lmh0dHA6Ly9mb250ZWxsby5jb20AQwByAGUAYQB0AGUAZAAgAGIAeQAgAGkAYwBvAG4AZgBvAG4AdABpAGMAbwBuAGYAbwBuAHQAUgBlAGcAdQBsAGEAcgBpAGMAbwBuAGYAbwBuAHQAaQBjAG8AbgBmAG8AbgB0AFYAZQByAHMAaQBvAG4AIAAxAC4AMABpAGMAbwBuAGYAbwBuAHQARwBlAG4AZQByAGEAdABlAGQAIABiAHkAIABzAHYAZwAyAHQAdABmACAAZgByAG8AbQAgAEYAbwBuAHQAZQBsAGwAbwAgAHAAcgBvAGoAZQBjAHQALgBoAHQAdABwADoALwAvAGYAbwBuAHQAZQBsAGwAbwAuAGMAbwBtAAACAAAAAAAAAAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoBAgEDAQQBBQEGAQcBCAEJAQoBCwAIeGlhbmd4aWEGYWRqdXN0CGNoZWNrYm94FGNoZWNrYm94b3V0bGluZWJsYW5rFWluZGV0ZXJtaW5hdGVjaGVja2JveBJyYWRpb2J1dHRvbmNoZWNrZWQUcmFkaW9idXR0b251bmNoZWNrZWQHbG9hZGluZw14aWFuZ3hpYS1jb3B5AAAA') format('truetype'); |
| | | } |
| | | |
| | | .da-tree { |
| | | width: 100%; |
| | | height: 100%; |
| | | |
| | | &-scroll { |
| | | width: 100%; |
| | | height: 100%; |
| | | } |
| | | |
| | | &-item { |
| | | display: flex; |
| | | align-items: center; |
| | | height: 0; |
| | | padding: 0; |
| | | overflow: hidden; |
| | | font-size: 28rpx; |
| | | line-height: 1; |
| | | visibility: hidden; |
| | | opacity: 0; |
| | | transition: opacity 0.2s linear; |
| | | |
| | | &.is-show { |
| | | height: auto; |
| | | padding: 12rpx 24rpx; |
| | | visibility: visible; |
| | | opacity: 1; |
| | | } |
| | | |
| | | &__icon { |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | width: 40rpx; |
| | | height: 40rpx; |
| | | overflow: hidden; |
| | | |
| | | &--arr { |
| | | position: relative; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | width: 32rpx; |
| | | height: 32rpx; |
| | | |
| | | &::after { |
| | | position: relative; |
| | | z-index: 1; |
| | | overflow: hidden; |
| | | /* stylelint-disable-next-line font-family-no-missing-generic-family-keyword */ |
| | | font-family: 'da-tree-iconfont' !important; |
| | | font-size: 32rpx; |
| | | font-style: normal; |
| | | color: #999; |
| | | -webkit-font-smoothing: antialiased; |
| | | -moz-osx-font-smoothing: grayscale; |
| | | } |
| | | |
| | | &.is-expand { |
| | | &::after { |
| | | content: '\e604'; |
| | | } |
| | | } |
| | | |
| | | &.is-right { |
| | | transform: rotate(-90deg); |
| | | } |
| | | |
| | | &.is-loading { |
| | | animation: IconLoading 1s linear 0s infinite; |
| | | |
| | | &::after { |
| | | content: '\e7f1'; |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | &__checkbox { |
| | | width: 40rpx; |
| | | height: 40rpx; |
| | | overflow: hidden; |
| | | |
| | | &--left { |
| | | order: 0; |
| | | } |
| | | |
| | | &--right { |
| | | order: 1; |
| | | } |
| | | |
| | | &--icon { |
| | | position: relative; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | width: 40rpx; |
| | | height: 40rpx; |
| | | |
| | | &::after { |
| | | position: relative; |
| | | top: 0; |
| | | left: 0; |
| | | z-index: 1; |
| | | overflow: hidden; |
| | | /* stylelint-disable-next-line font-family-no-missing-generic-family-keyword */ |
| | | font-family: 'da-tree-iconfont' !important; |
| | | font-size: 32rpx; |
| | | font-style: normal; |
| | | -webkit-font-smoothing: antialiased; |
| | | -moz-osx-font-smoothing: grayscale; |
| | | } |
| | | |
| | | &.da-tree-checkbox-outline::after { |
| | | color: #bbb; |
| | | content: '\ead5'; |
| | | } |
| | | |
| | | &.da-tree-checkbox-checked::after { |
| | | color: var(--theme-color,#007aff); |
| | | content: '\ead4'; |
| | | } |
| | | |
| | | &.da-tree-checkbox-indeterminate::after { |
| | | color: var(--theme-color,#007aff); |
| | | content: '\ebce'; |
| | | } |
| | | |
| | | &.da-tree-radio-outline::after { |
| | | color: #bbb; |
| | | content: '\ecc5'; |
| | | } |
| | | |
| | | &.da-tree-radio-checked::after { |
| | | color: var(--theme-color,#007aff); |
| | | content: '\ecc4'; |
| | | } |
| | | |
| | | &.da-tree-radio-indeterminate::after { |
| | | color: var(--theme-color,#007aff); |
| | | content: '\ea4f'; |
| | | } |
| | | } |
| | | |
| | | &.is--disabled { |
| | | cursor: not-allowed; |
| | | opacity: 0.35; |
| | | } |
| | | } |
| | | |
| | | &__label { |
| | | flex: 1; |
| | | margin-left: 4rpx; |
| | | color: #555; |
| | | |
| | | &--2 { |
| | | color: var(--theme-color,#007aff); |
| | | } |
| | | |
| | | &--append { |
| | | font-size: 60%; |
| | | opacity: 0.6; |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | @keyframes IconLoading { |
| | | 0% { |
| | | transform: rotate(0deg); |
| | | } |
| | | |
| | | 100% { |
| | | transform: rotate(360deg); |
| | | } |
| | | } |
| | | </style> |
对比新文件 |
| | |
| | | export default { |
| | | /** |
| | | * 树的数据 |
| | | */ |
| | | data: { |
| | | type: Array, |
| | | default: () => [], |
| | | }, |
| | | /** |
| | | * 主题色 |
| | | */ |
| | | themeColor: { |
| | | type: String, |
| | | default: '#007aff', |
| | | }, |
| | | /** |
| | | * 是否开启多选,默认单选 |
| | | */ |
| | | showCheckbox: { |
| | | type: Boolean, |
| | | default: false, |
| | | }, |
| | | /** |
| | | * 默认选中的节点,注意单选时为单个key,多选时为key的数组 |
| | | */ |
| | | defaultCheckedKeys: { |
| | | type: [Array, String, Number], |
| | | default: null, |
| | | }, |
| | | /** |
| | | * 是否默认展开全部 |
| | | */ |
| | | defaultExpandAll: { |
| | | type: Boolean, |
| | | default: false, |
| | | }, |
| | | /** |
| | | * 默认展开的节点 |
| | | */ |
| | | defaultExpandedKeys: { |
| | | type: Array, |
| | | default: null, |
| | | }, |
| | | /** |
| | | * 筛选关键词 |
| | | */ |
| | | filterValue: { |
| | | type: String, |
| | | default: '', |
| | | }, |
| | | /** |
| | | * 是否自动展开到选中的节点,默认不展开 |
| | | */ |
| | | expandChecked: { |
| | | type: Boolean, |
| | | default: false, |
| | | }, |
| | | |
| | | /** |
| | | * (旧)字段对应内容,默认为 {label: 'label',key: 'key', children: 'children', disabled: 'disabled', append: 'append'} |
| | | * 注意:1.5.0版本后不再兼容 |
| | | */ |
| | | field: { |
| | | type: Object, |
| | | default: null, |
| | | }, |
| | | /** |
| | | * 标签字段(新,拆分了) |
| | | */ |
| | | labelField: { |
| | | type: String, |
| | | default: 'label', |
| | | }, |
| | | /** |
| | | * 值字段(新,拆分了) |
| | | */ |
| | | valueField: { |
| | | type: String, |
| | | default: 'value', |
| | | }, |
| | | /** |
| | | * 下级字段(新,拆分了) |
| | | */ |
| | | childrenField: { |
| | | type: String, |
| | | default: 'children', |
| | | }, |
| | | /** |
| | | * 禁用字段(新,拆分了) |
| | | */ |
| | | disabledField: { |
| | | type: String, |
| | | default: 'disabled', |
| | | }, |
| | | /** |
| | | * 末级节点字段(新,拆分了) |
| | | */ |
| | | leafField: { |
| | | type: String, |
| | | default: 'leaf', |
| | | }, |
| | | /** |
| | | * 副标签字段(新,拆分了) |
| | | */ |
| | | appendField: { |
| | | type: String, |
| | | default: 'append', |
| | | }, |
| | | /** |
| | | * 排序字段(新,拆分了) |
| | | */ |
| | | sortField: { |
| | | type: String, |
| | | default: 'sort', |
| | | }, |
| | | /** |
| | | * Api数据返回后的结果路径,支持嵌套如`data.list` |
| | | */ |
| | | resultField: { |
| | | type: String, |
| | | default: '', |
| | | }, |
| | | isLeafFn: { |
| | | type: Function, |
| | | default: null, |
| | | }, |
| | | /** |
| | | * 是否显示单选图标,默认显示 |
| | | */ |
| | | showRadioIcon: { |
| | | type: Boolean, |
| | | default: true, |
| | | }, |
| | | /** |
| | | * 单选时只允许选中末级,默认可随意选中 |
| | | */ |
| | | onlyRadioLeaf: { |
| | | type: Boolean, |
| | | default: false, |
| | | }, |
| | | /** |
| | | * 多选时,是否执行父子不关联的任意勾选,默认父子关联 |
| | | */ |
| | | checkStrictly: { |
| | | type: Boolean, |
| | | default: false, |
| | | }, |
| | | /** |
| | | * 为 true 时,空的 children 数组会显示展开图标 |
| | | */ |
| | | loadMode: { |
| | | type: Boolean, |
| | | default: false, |
| | | }, |
| | | /** |
| | | * 异步加载接口 |
| | | */ |
| | | loadApi: { |
| | | type: Function, |
| | | default: null, |
| | | }, |
| | | /** |
| | | * 是否总在首次的时候加载一下内容,来比对是否一致 |
| | | */ |
| | | alwaysFirstLoad: { |
| | | type: Boolean, |
| | | default: false, |
| | | }, |
| | | /** |
| | | * 是否渲染(操作)禁用值 |
| | | */ |
| | | checkedDisabled: { |
| | | type: Boolean, |
| | | default: false, |
| | | }, |
| | | /** |
| | | * 是否返回已禁用的但已选中的key |
| | | */ |
| | | packDisabledkey: { |
| | | type: Boolean, |
| | | default: true, |
| | | }, |
| | | /** |
| | | * 选择框的位置,可选 left/right |
| | | */ |
| | | checkboxPlacement: { |
| | | type: String, |
| | | default: 'left', |
| | | }, |
| | | /** |
| | | * 子项缩进距离,默认40,单位rpx |
| | | */ |
| | | indent: { |
| | | type: Number, |
| | | default: 40, |
| | | }, |
| | | } |
对比新文件 |
| | |
| | | # da-tree |
| | | |
| | | 一个基于 Vue3 的 tree(树)组件,同时支持主题换色,可能是最适合你的 tree(树)组件 |
| | | |
| | | 组件一直在更新,遇到问题可在下方讨论。 |
| | | |
| | | `同时更新 Vue2 版本,在此查看 ===>` **[Vue2 版](https://ext.dcloud.net.cn/plugin?id=12692)** |
| | | |
| | | ### 关于使用 |
| | | |
| | | 可在右侧的`使用 HBuilderX 导入插件`或`下载示例项目ZIP`,方便快速上手。 |
| | | |
| | | 可通过下方的示例及文档说明,进一步了解使用组件相关细节参数。 |
| | | |
| | | 插件地址:https://ext.dcloud.net.cn/plugin?id=12384 |
| | | |
| | | ### 组件示例 |
| | | |
| | | ```jsx |
| | | <template> |
| | | <view>多选</view> |
| | | <view><button @click="doCheckedTree(['2'],true)">全选</button></view> |
| | | <view><button @click="doCheckedTree(['2'],false)">取消全选</button></view> |
| | | <view><button @click="doCheckedTree(['211','222'],true)">选中指定节点</button></view> |
| | | <view><button @click="doCheckedTree(['211','222'],false)">取消选中指定节点</button></view> |
| | | <view><button @click="doExpandTree('all',true)">展开全部节点</button></view> |
| | | <view><button @click="doExpandTree('all',false)">收起全部节点</button></view> |
| | | <view><button @click="doExpandTree(['22','23'],true)">展开节点</button></view> |
| | | <view><button @click="doExpandTree(['22','23'],false)">收起节点</button></view> |
| | | <DaTree |
| | | ref="DaTreeRef" |
| | | :data="roomTreeData" |
| | | labelField="name" |
| | | valueField="id" |
| | | defaultExpandAll |
| | | showCheckbox |
| | | :defaultCheckedKeys="defaultCheckedKeysValue" |
| | | @change="handleTreeChange" |
| | | @expand="handleExpandChange"></DaTree> |
| | | <view>单选</view> |
| | | <DaTree |
| | | :data="roomTreeData" |
| | | labelField="name" |
| | | valueField="id" |
| | | defaultExpandAll |
| | | :defaultCheckedKeys="defaultCheckedKeysValue2" |
| | | @change="handleTreeChange" |
| | | @expand="handleExpandChange"></DaTree> |
| | | <view>默认展开指定节点</view> |
| | | <DaTree |
| | | :data="roomTreeData" |
| | | labelField="name" |
| | | valueField="id" |
| | | showCheckbox |
| | | :defaultExpandedKeys="defaultExpandKeysValue3" |
| | | @change="handleTreeChange" |
| | | @expand="handleExpandChange"></DaTree> |
| | | <view>异步加载数据</view> |
| | | <DaTree |
| | | :data="roomTreeData" |
| | | labelField="name" |
| | | valueField="id" |
| | | showCheckbox |
| | | loadMode |
| | | :loadApi="GetApiData" |
| | | defaultExpandAll |
| | | @change="handleTreeChange" |
| | | @expand="handleExpandChange"></DaTree> |
| | | </template> |
| | | ``` |
| | | |
| | | ```js |
| | | import { defineComponent, ref } from 'vue' |
| | | |
| | | /** |
| | | * 模拟创建一个接口数据 |
| | | */ |
| | | function GetApiData(currentNode) { |
| | | const { key } = currentNode |
| | | |
| | | return new Promise((resolve) => { |
| | | setTimeout(() => { |
| | | // 模拟返回空数据 |
| | | if (key.indexOf('-') > -1) { |
| | | return resolve(null) |
| | | // return resolve([]) |
| | | } |
| | | |
| | | return resolve([ |
| | | { |
| | | id: `${key}-1`, |
| | | name: `行政部X${key}-1`, |
| | | }, |
| | | { |
| | | id: `${key}-2`, |
| | | name: `财务部X${key}-2`, |
| | | append: '定义了末项数据', |
| | | leaf: true, |
| | | }, |
| | | { |
| | | id: `${key}-3`, |
| | | name: `资源部X${key}-3`, |
| | | }, |
| | | { |
| | | id: `${key}-4`, |
| | | name: `资源部X${key}-3`, |
| | | append: '被禁用,无展开图标', |
| | | disabled: true, |
| | | }, |
| | | ]) |
| | | }, 2000) |
| | | }) |
| | | } |
| | | |
| | | import DaTree from '@/components/da-tree/index.vue' |
| | | export default defineComponent({ |
| | | components: { DaTree }, |
| | | setup() { |
| | | const DaTreeRef = ref() |
| | | // key的类型必须对应树数据key的类型 |
| | | const defaultCheckedKeysValue = ref(['211', '222']) |
| | | const defaultCheckedKeysValue2 = ref('222') |
| | | const defaultExpandKeysValue3 = ref(['212', '231']) |
| | | const roomTreeData = ref([ |
| | | { |
| | | id: '2', |
| | | name: '行政中心', |
| | | children: [ |
| | | { |
| | | id: '21', |
| | | name: '行政部', |
| | | children: [ |
| | | { |
| | | id: '211', |
| | | name: '行政一部', |
| | | children: null, |
| | | }, |
| | | { |
| | | id: '212', |
| | | name: '行政二部', |
| | | children: [], |
| | | disabled: true, |
| | | }, |
| | | ], |
| | | }, |
| | | { |
| | | id: '22', |
| | | name: '财务部', |
| | | children: [ |
| | | { |
| | | id: '221', |
| | | name: '财务一部', |
| | | children: [], |
| | | disabled: true, |
| | | }, |
| | | { |
| | | id: '222', |
| | | name: '财务二部', |
| | | children: [], |
| | | }, |
| | | ], |
| | | }, |
| | | { |
| | | id: '23', |
| | | name: '人力资源部', |
| | | children: [ |
| | | { |
| | | id: '231', |
| | | name: '人力一部', |
| | | children: [], |
| | | }, |
| | | { |
| | | id: '232', |
| | | name: '人力二部', |
| | | append: '更多示例,请下载示例项目查看', |
| | | }, |
| | | ], |
| | | }, |
| | | ], |
| | | }, |
| | | ]) |
| | | function doExpandTree(keys, expand) { |
| | | DaTreeRef.value?.setExpandedKeys(keys, expand) |
| | | |
| | | const gek = DaTreeRef.value?.getExpandedKeys() |
| | | console.log('当前已展开的KEY ==>', gek) |
| | | } |
| | | function doCheckedTree(keys, checked) { |
| | | DaTreeRef.value?.setCheckedKeys(keys, checked) |
| | | |
| | | const gek = DaTreeRef.value?.getCheckedKeys() |
| | | console.log('当前已选中的KEY ==>', gek) |
| | | } |
| | | function handleTreeChange(allSelectedKeys, currentItem) { |
| | | console.log('handleTreeChange ==>', allSelectedKeys, currentItem) |
| | | } |
| | | function handleExpandChange(expand, currentItem) { |
| | | console.log('handleExpandChange ==>', expand, currentItem) |
| | | } |
| | | return { |
| | | DaTreeRef, |
| | | roomTreeData, |
| | | defaultCheckedKeysValue, |
| | | defaultCheckedKeysValue2, |
| | | defaultExpandKeysValue3, |
| | | handleTreeChange, |
| | | handleExpandChange, |
| | | GetApiData, |
| | | doExpandTree, |
| | | doCheckedTree, |
| | | } |
| | | }, |
| | | }) |
| | | ``` |
| | | |
| | | ** 更多示例请下载/导入示例项目 ZIP 查看 ** |
| | | |
| | | ### 组件参数 |
| | | |
| | | | 属性 | 类型 | 默认值 | 必填 | 说明 | |
| | | | :------------------ | :------------------------------ | :--------- | :--- | :--------------------------------------------------------------------------- | |
| | | | data | `Array` | - | 是 | 树的数据 | |
| | | | themeColor | `String` | `#007aff` | 否 | 主题色,十六进制 | |
| | | | defaultCheckedKeys | `Array` \| `Number` \| `String` | - | 否 | 默认选中的节点,单选为单个 key,多选为 key 的数组 | |
| | | | showCheckbox | `Boolean` | `false` | 否 | 是否开启多选,默认单选 | |
| | | | checkStrictly | `Boolean` | `false` | 否 | 多选时,是否执行父子不关联的任意勾选,默认父子关联 | |
| | | | showRadioIcon | `Boolean` | `true` | 否 | 是否显示单选图标,默认显示 | |
| | | | onlyRadioLeaf | `Boolean` | `true` | 否 | 单选时只允许选中末级,默认可随意选中 | |
| | | | defaultExpandAll | `Boolean` | `false` | 否 | 是否默认展开全部 | |
| | | | defaultExpandedKeys | `Array` | - | 否 | 默认展开的节点 | |
| | | | indent | `Number` | `40` | 否 | 子项缩进距离,单位 rpx | |
| | | | checkboxPlacement | `String` | `left` | 否 | 选择框的位置,可选 left/right | |
| | | | loadMode | `Boolean` | `false` | 否 | 为 true 时,空的 children 数组会显示展开图标 | |
| | | | loadApi | `Function` | - | 否 | 选择框的位置,可选 left/right | |
| | | | checkedDisabled | `Boolean` | `false` | 否 | 是否渲染禁用值,默认不渲染 | |
| | | | packDisabledkey | `Boolean` | `true` | 否 | 是否返回已禁用的但已选中的 key,默认返回禁用已选值 | |
| | | | expandChecked | `Boolean` | `false` | 否 | 是否自动展开到选中的节点,默认不展开 | |
| | | | alwaysFirstLoad | `Boolean` | `false` | 否 | 是否总在首次的时候加载一下内容,默认不加载,否则只有展开末级节点才会加载数据 | |
| | | | isLeafFn | `Function` | - | 否 | 自定义函数返回来控制数据项的末项 | |
| | | | field | `Object` | - | 否 | 字段对应内容,格式参考下方(1.5.0 后移除,请用单独的字段匹配) | |
| | | | labelField | `String` | `label` | 否 | 指定节点对象中某个属性为标签字段,默认`label` | |
| | | | valueField | `String` | `value` | 否 | 指定节点对象中某个属性为值字段,默认`value` | |
| | | | childrenField | `String` | `children` | 否 | 指定节点对象中某个属性为子树节点字段,默认`children` | |
| | | | disabledField | `String` | `disabled` | 否 | 指定节点对象中某个属性为禁用字段,默认`disabled` | |
| | | | appendField | `String` | `append` | 否 | 指定节点对象中某个属性为副标签字段,默认`append` | |
| | | | leafField | `String` | `leaf` | 否 | 指定节点对象中某个属性为末级节点字段,默认`leaf` | |
| | | | sortField | `String` | `sort` | 否 | 指定节点对象中某个属性为排序字段,默认`sort` | |
| | | | filterValue | `String` | - | 否 | 搜索筛选的关键词,通过输入关键词筛选内容 | |
| | | |
| | | **field 格式(1.5.0 后移除,请用单独的字段匹配)** |
| | | |
| | | ```js |
| | | { |
| | | label: 'label', |
| | | key: 'key', |
| | | children: 'children', |
| | | disabled: 'disabled', |
| | | append: 'append' |
| | | } |
| | | ``` |
| | | |
| | | ### 组件事件 |
| | | |
| | | | 事件名称 | 回调参数 | 说明 | |
| | | | :------- | :-------------------------------------- | :-------------- | |
| | | | change | `(allCheckedKeys, currentItem) => void` | 选中时回调 | |
| | | | expand | `(expandState, currentItem) => void` | 展开/收起时回调 | |
| | | |
| | | ### 组件方法 |
| | | |
| | | | 方法名称 | 参数 | 说明 | |
| | | | :------------------ | :--------------- | :------------------------------------------------------------------------------------------------ | |
| | | | setCheckedKeys | `(keys,checked)` | 设置指定 key 的节点选中/取消选中的状态。注: keys 单选时为 key,多选时为 key 的数组 | |
| | | | setExpandedKeys | `(keys,expand)` | 设置指定 key 的节点展开/收起的状态,当 keys 为 all 时即代表展开/收起全部。注:keys 为数组或 `all` | |
| | | | getCheckedKeys | - | 返回已选的 key | |
| | | | getHalfCheckedKeys | - | 返回半选的 key | |
| | | | getUncheckedKeys | - | 返回未选的 key | |
| | | | getCheckedNodes | - | 返回已选的节点 | |
| | | | getUncheckedNodes | - | 返回未选的节点 | |
| | | | getHalfCheckedNodes | - | 返回半选的节点 | |
| | | | getExpandedKeys | - | 返回已展开的 key | |
| | | | getUnexpandedKeys | - | 返回未展开的 key | |
| | | | getExpandedNodes | - | 返回已展开的节点 | |
| | | | getUnexpandedNodes | - | 返回未展开的节点 | |
| | | |
| | | ### 组件版本 |
| | | |
| | | v1.4.2 |
| | | |
| | | ### 差异化 |
| | | |
| | | 已通过测试 |
| | | |
| | | > - H5 页面 |
| | | > - 微信小程序 |
| | | > - 支付宝、钉钉小程序 |
| | | > - 字节跳动、抖音、今日头条小程序 |
| | | > - 百度小程序 |
| | | > - 飞书小程序 |
| | | > - QQ 小程序 |
| | | > - 京东小程序 |
| | | |
| | | 未测试 |
| | | |
| | | > - 快手小程序由于非企业用户暂无演示 |
| | | > - 快应用、360 小程序因 Vue3 支持的原因暂无演示 |
| | | |
| | | ### 开发组 |
| | | |
| | | [@CRLANG](https://crlang.com) |
对比新文件 |
| | |
| | | /** 未选 */ |
| | | export const unCheckedStatus = 0 |
| | | /** 半选 */ |
| | | export const halfCheckedStatus = 1 |
| | | /** 选中 */ |
| | | export const isCheckedStatus = 2 |
| | | |
| | | /** |
| | | * 深拷贝内容 |
| | | * @param originData 拷贝对象 |
| | | * @author crlang(https://crlang.com) |
| | | */ |
| | | export function deepClone(originData) { |
| | | const type = Object.prototype.toString.call(originData) |
| | | let data |
| | | if (type === '[object Array]') { |
| | | data = [] |
| | | for (let i = 0; i < originData.length; i++) { |
| | | data.push(deepClone(originData[i])) |
| | | } |
| | | } else if (type === '[object Object]') { |
| | | data = {} |
| | | for (const prop in originData) { |
| | | // eslint-disable-next-line no-prototype-builtins |
| | | if (originData.hasOwnProperty(prop)) { // 非继承属性 |
| | | data[prop] = deepClone(originData[prop]) |
| | | } |
| | | } |
| | | } else { |
| | | data = originData |
| | | } |
| | | return data |
| | | } |
| | | |
| | | /** |
| | | * 获取所有指定的节点 |
| | | * @param type |
| | | * @param value |
| | | * @author crlang(https://crlang.com) |
| | | */ |
| | | export function getAllNodes(list, type, value, packDisabledkey = true) { |
| | | if (!list || list.length === 0) { |
| | | return [] |
| | | } |
| | | |
| | | const res = [] |
| | | for (let i = 0; i < list.length; i++) { |
| | | const item = list[i] |
| | | if (item[type] === value) { |
| | | if ((packDisabledkey && item.disabled) || !item.disabled) { |
| | | res.push(item) |
| | | } |
| | | } |
| | | } |
| | | |
| | | return res |
| | | } |
| | | |
| | | /** |
| | | * 获取所有指定的key值 |
| | | * @param type |
| | | * @param value |
| | | * @author crlang(https://crlang.com) |
| | | */ |
| | | export function getAllNodeKeys(list, type, value, packDisabledkey = true) { |
| | | if (!list || list.length === 0) { |
| | | return null |
| | | } |
| | | |
| | | const res = [] |
| | | for (let i = 0; i < list.length; i++) { |
| | | const item = list[i] |
| | | if (item[type] === value) { |
| | | if ((packDisabledkey && item.disabled) || !item.disabled) { |
| | | res.push(item.key) |
| | | } |
| | | } |
| | | } |
| | | |
| | | return res.length ? res : null |
| | | } |
| | | |
| | | /** |
| | | * 错误输出 |
| | | * |
| | | * @param msg |
| | | */ |
| | | export function logError(msg, ...args) { |
| | | console.error(`DaTree: ${msg}`, ...args) |
| | | } |
| | | |
| | | const toString = Object.prototype.toString |
| | | |
| | | export function is(val, type) { |
| | | return toString.call(val) === `[object ${type}]` |
| | | } |
| | | |
| | | /** |
| | | * 是否对象(Object) |
| | | * @param val |
| | | |
| | | */ |
| | | export function isObject(val) { |
| | | return val !== null && is(val, 'Object') |
| | | } |
| | | |
| | | /** |
| | | * 是否数字(Number) |
| | | * @param val |
| | | |
| | | */ |
| | | export function isNumber(val) { |
| | | return is(val, 'Number') |
| | | } |
| | | |
| | | /** |
| | | * 是否字符串(String) |
| | | * @param val |
| | | |
| | | */ |
| | | export function isString(val) { |
| | | return is(val, 'String') |
| | | } |
| | | |
| | | /** |
| | | * 是否函数方法(Function) |
| | | * @param val |
| | | |
| | | */ |
| | | export function isFunction(val) { |
| | | return typeof val === 'function' |
| | | } |
| | | |
| | | /** |
| | | * 是否布尔(Boolean) |
| | | * @param val |
| | | |
| | | */ |
| | | export function isBoolean(val) { |
| | | return is(val, 'Boolean') |
| | | } |
| | | |
| | | /** |
| | | * 是否数组(Array) |
| | | * @param val |
| | | |
| | | */ |
| | | export function isArray(val) { |
| | | return val && Array.isArray(val) |
| | | } |
| | |
| | | import App from './App' |
| | | import navbar from './components/navbar/navbar.vue' |
| | | |
| | | import { createSSRApp } from 'vue' |
| | | |
| | | export function createApp() { |
| | | const app = createSSRApp(App) |
| | | app.component('navbar', navbar) |
| | |
| | | { |
| | | "name": "water-drinking-uniapp", |
| | | "version": "1.0.0", |
| | | "lockfileVersion": 3, |
| | | "requires": true, |
| | | "packages": {} |
| | | "packages": { |
| | | "": { |
| | | "name": "water-drinking-uniapp", |
| | | "version": "1.0.0", |
| | | "license": "ISC", |
| | | "dependencies": { |
| | | "@dcloudio/uni-ui": "^1.4.28" |
| | | } |
| | | }, |
| | | "node_modules/@dcloudio/uni-ui": { |
| | | "version": "1.5.6", |
| | | "resolved": "https://registry.npmmirror.com/@dcloudio/uni-ui/-/uni-ui-1.5.6.tgz", |
| | | "integrity": "sha512-jmb98PasFvZkrIDXGh94GbdWg2/jyhgs1HUG+bU8eyL7Ltias/5XBz4q8w9RXyWUfqepJRqapPA2IIQpLCuTIg==" |
| | | } |
| | | } |
| | | } |
| | |
| | | { |
| | | "name": "water-drinking-uniapp", |
| | | "version": "1.0.0", |
| | | "description": "", |
| | | "main": "main.js", |
| | | "dependencies":{ |
| | | "@dcloudio/uni-ui": "^1.4.28" |
| | | }, |
| | | "devDependencies": {}, |
| | | "scripts": { |
| | | "test": "echo \"Error: no test specified\" && exit 1" |
| | | }, |
| | | "keywords": [], |
| | | "author": "", |
| | | "license": "ISC" |
| | | } |
| | | "id": "da-tree", |
| | | "name": "da-tree 树组件(支持单选、多选、无限级、主题色,Vue3版) ", |
| | | "displayName": "da-tree 树组件(支持单选、多选、无限级、主题色,Vue3版) ", |
| | | "version": "1.4.2", |
| | | "description": "一个基于 Vue3 的tree(树)组件,支持主题换色,可能是最适合你的tree(树)组件", |
| | | "keywords": [ |
| | | "tree", |
| | | "树", |
| | | "树组件", |
| | | "da系列" |
| | | ], |
| | | "dcloudext": { |
| | | "category": [ |
| | | "前端组件", |
| | | "通用组件" |
| | | ] |
| | | } |
| | | } |
| | |
| | | { |
| | | "pages": [ //pages数组中第一项表示应用启动页,参考:https://uniapp.dcloud.io/collocation/pages |
| | | // { |
| | | // "path" : "pages/addressAdd/index", |
| | | // "style": { |
| | | // "navigationStyle":"custom" |
| | | // } |
| | | // }, |
| | | { |
| | | "path": "pages/index/index", |
| | | "style": { |
| | |
| | | "style": { |
| | | "navigationStyle":"custom" |
| | | } |
| | | }, |
| | | { |
| | | "path" : "pages/addressEdit/index", |
| | | "style": { |
| | | "navigationStyle":"custom" |
| | | } |
| | | }, |
| | | { |
| | | "path" : "pages/addressAdd/index", |
| | | "style": { |
| | | "navigationStyle":"custom" |
| | | } |
| | | }, |
| | | { |
| | | "path" : "pages/addressLocate/index", |
| | | "style": { |
| | | "navigationStyle":"custom" |
| | | } |
| | | } |
| | | ], |
| | | "globalStyle": { |
| | |
| | | <script setup> |
| | | |
| | | import { ref ,onMounted } from 'vue' |
| | | const addressList = ref([ |
| | | {name:'张大左',phone:'13512334002',address:'重庆 重庆市 北碚区 互联网产业生态园 2-5-1',checked:true,id:'12'}, |
| | | {name:'张大左',phone:'13512334002',address:'重庆 重庆市 北碚区 互联网产业生态园 2-5-1',checked:true,id:'12'}, |
| | | {name:'张大左',phone:'13512334002',address:'重庆 重庆市 北碚区 互联网产业生态园 2-5-1',checked:true,id:'12'}, |
| | | {name:'张大左',phone:'13512334002',address:'重庆 重庆市 北碚区 互联网产业生态园 2-5-1',checked:true,id:'12'}, |
| | | {name:'张大左',phone:'13512334002',address:'重庆 重庆市 北碚区 互联网产业生态园 2-5-1',checked:true,id:'12'}, |
| | | {name:'张大左',phone:'13512334002',address:'重庆 重庆市 北碚区 互联网产业生态园 2-5-1',checked:true,id:'12'}, |
| | | {name:'',phone:'',address:'',checked:false,id:''}, |
| | | {name:'',phone:'',address:'',checked:false,id:''}, |
| | | {name:'',phone:'',address:'',checked:false,id:''}, |
| | | ]) |
| | | function navTo(){ |
| | | let title = '新增地址' |
| | | let id = 0 |
| | | uni.navigateTo({ |
| | | url:'/pages/addressAdd/index' |
| | | }) |
| | | } |
| | | </script> |
| | | <template> |
| | | <view class="container"> |
| | | <navbar title = '地址管理'></navbar> |
| | | <view class="content"> |
| | | <view class="main"></view> |
| | | <view class="main"> |
| | | <block v-for="(item,index) in addressList"> |
| | | <view class="address-item"> |
| | | <view class="user-info"> |
| | | <text>{{item.name}}</text> |
| | | <text>{{item.phone}}</text> |
| | | </view> |
| | | <view class="address-info">{{item.address}}</view> |
| | | <view class="address-divide"></view> |
| | | <view class="handel"> |
| | | <view class="handel-left"> |
| | | <radio color = "#4996E3" :value="item.id" :checked="item.checked"></radio> |
| | | <text>设为默认</text> |
| | | </view> |
| | | <view class="handel-right"> |
| | | <view> |
| | | <image src="../../static/images/address/edit.png" alt=""></image> |
| | | <text>编辑</text> |
| | | </view> |
| | | <view> |
| | | <image src="../../static/images/address/delete.png" alt=""></image> |
| | | <text>删除</text> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | </block> |
| | | </view> |
| | | <view class="subBtn" @click="navTo()">新增收货地址</view> |
| | | </view> |
| | | </view> |
| | | </template> |
| | |
| | | height: 100vh; |
| | | .content{ |
| | | width: 100%; |
| | | height:calc(100vh - 176rpx); |
| | | height:calc(100vh - 176rpx - 20rpx); |
| | | background:linear-gradient(to top,#FFFFFF,#E8EFFF); |
| | | padding-top:20rpx; |
| | | .main{ |
| | | width: 686rpx; |
| | | height:1262rpx; |
| | | background: #ffaaff; |
| | | height:1162rpx; |
| | | margin:0 auto; |
| | | overflow-y: scroll; |
| | | // background-color: #a6ffd3; |
| | | .address-item{ |
| | | width:100%; |
| | | height:254rpx; |
| | | padding:20rpx 0 26rpx; |
| | | box-sizing: border-box; |
| | | margin-bottom:20rpx; |
| | | background: #FFFFFF; |
| | | display: flex; |
| | | flex-direction: column; |
| | | justify-content: space-between; |
| | | align-items:flex-start; |
| | | .user-info{ |
| | | width:100%; |
| | | padding: 0 65rpx; |
| | | box-sizing: border-box; |
| | | display: flex; |
| | | justify-content: space-between; |
| | | font-weight: 300; |
| | | font-size: 32rpx; |
| | | color: #000000; |
| | | } |
| | | .address-info{ |
| | | padding: 0 65rpx; |
| | | box-sizing: border-box; |
| | | font-weight: 300; |
| | | font-size: 28rpx; |
| | | color: #646464; |
| | | } |
| | | .address-divide{ |
| | | width: 100%; |
| | | height: 1rpx; |
| | | border-bottom:2rpx dashed #D5DDE0; |
| | | } |
| | | .handel{ |
| | | width: 100%; |
| | | padding: 0 65rpx; |
| | | box-sizing: border-box; |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items:flex-start; |
| | | .handel-left{ |
| | | radio{ |
| | | color: #000000; |
| | | } |
| | | text{ |
| | | font-weight: 300; |
| | | font-size: 32rpx; |
| | | color: #0088FF; |
| | | line-height:28rpx; |
| | | } |
| | | } |
| | | .handel-right{ |
| | | width:35%; |
| | | height:48rpx; |
| | | display: flex; |
| | | justify-content:space-between; |
| | | align-items:center; |
| | | view{ |
| | | display: flex; |
| | | align-items:center; |
| | | image{ |
| | | width:28rpx; |
| | | height:28rpx; |
| | | margin-right:10rpx; |
| | | } |
| | | text{ |
| | | font-weight: 300; |
| | | font-size: 24rpx; |
| | | color: #373737; |
| | | } |
| | | } |
| | | } |
| | | } |
| | | } |
| | | } |
| | | .subBtn{ |
| | | width:686rpx; |
| | | height: 98rpx; |
| | | // margin-top:100rpx; |
| | | padding:0 26rpx; |
| | | box-sizing:border-box; |
| | | background-color:#5EA1FA; |
| | | border-radius:50rpx; |
| | | font-weight: 300; |
| | | font-size: 36rpx; |
| | | color: #FFFFFF; |
| | | line-height:98rpx; |
| | | text-align: center; |
| | | letter-spacing: 2rpx; |
| | | margin: 100rpx auto 0; |
| | | } |
| | | } |
| | | } |
| | | </style> |
对比新文件 |
| | |
| | | <script setup> |
| | | import{ref} from 'vue' |
| | | const isDefault =ref(false) |
| | | const form = ref({ |
| | | name:'', |
| | | phone:'', |
| | | region:'', |
| | | address:'', |
| | | }) |
| | | function submit(){ |
| | | |
| | | } |
| | | const treeListData = ref([ |
| | | { |
| | | id: '2', |
| | | name: '行政中心', |
| | | children: [ |
| | | { |
| | | id: '21', |
| | | name: '行政部', |
| | | children: [ |
| | | { |
| | | id: '211', |
| | | name: '行政一部', |
| | | children: null, |
| | | }, |
| | | { |
| | | id: '212', |
| | | name: '行政二部', |
| | | children: [], |
| | | disabled: true, |
| | | }, |
| | | ], |
| | | }, |
| | | { |
| | | id: '22', |
| | | name: '财务部', |
| | | children: [ |
| | | { |
| | | id: '221', |
| | | name: '财务一部', |
| | | children: [], |
| | | disabled: true, |
| | | }, |
| | | { |
| | | id: '222', |
| | | name: '财务二部', |
| | | children: [], |
| | | }, |
| | | ], |
| | | }, |
| | | { |
| | | id: '23', |
| | | name: '人力资源部', |
| | | children: [ |
| | | { |
| | | id: '231', |
| | | name: '人力一部', |
| | | children: [], |
| | | }, |
| | | { |
| | | id: '232', |
| | | name: '人力二部', |
| | | append: '更多示例,请下载示例项目查看', |
| | | }, |
| | | ], |
| | | }, |
| | | ], |
| | | }, |
| | | ]) |
| | | function handleTreeChange(e){ |
| | | console.log(e) |
| | | } |
| | | function handleExpandChange(e){ |
| | | console.log(e) |
| | | } |
| | | const DaTreeRef = ref() |
| | | </script> |
| | | <template> |
| | | <view class="container"> |
| | | <navbar title ='新增地址'></navbar> |
| | | <view class="content"> |
| | | <view class="main"> |
| | | <view> |
| | | <text>联系人</text> |
| | | <input v-model="form.name" placeholder="请输入联系人" /> |
| | | </view> |
| | | <view> |
| | | <text>联系电话</text> |
| | | <input v-model="form.phpne" placeholder="请输入联系电话" /> |
| | | </view> |
| | | <view > |
| | | <text>送水区域</text> |
| | | <!-- <da-tree ref="DaTreeRef" |
| | | :data="treeListData" |
| | | labelField="name" |
| | | valueField="id" |
| | | defaultExpandAll |
| | | showCheckbox |
| | | @change="handleTreeChange" |
| | | @expand="handleExpandChange"></da-tree> --> |
| | | |
| | | <input v-model="form.phpne" placeholder="请选择区域" /> |
| | | </view> |
| | | <view> |
| | | <text>送水地址</text> |
| | | <input v-model="form.address" placeholder="请输入详细地址" /> |
| | | </view> |
| | | </view> |
| | | <view class="default"> |
| | | <view>设为默认收货地址</view> |
| | | <switch :checked = 'isDefault' color="#1890FF"/> |
| | | </view> |
| | | <view class="subBtn" @click="submit()">提交信息</view> |
| | | </view> |
| | | </view> |
| | | </template> |
| | | |
| | | <style lang="scss"> |
| | | .container{ |
| | | width: 100%; |
| | | height: 100vh; |
| | | .content{ |
| | | width: 100%; |
| | | height:calc(100vh - 176rpx - 20rpx); |
| | | background:linear-gradient(to top,#FFFFFF,#E8EFFF); |
| | | padding:94rpx 32rpx 0; |
| | | box-sizing: border-box; |
| | | .main{ |
| | | width: 100%; |
| | | height:565rpx; |
| | | // background: #E8EFFF; |
| | | view{ |
| | | width:100%; |
| | | height:93rpx; |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items:center; |
| | | margin-bottom:20rpx; |
| | | text{ |
| | | font-weight:light; |
| | | font-size: 32rpx; |
| | | color: #000000; |
| | | } |
| | | input{ |
| | | padding-left:36rpx; |
| | | box-sizing: border-box; |
| | | background: #FFFFFF; |
| | | border-radius:22rpx; |
| | | height:100%; |
| | | width:534rpx; |
| | | } |
| | | } |
| | | .tree{ |
| | | width:100%; |
| | | height:93rpx; |
| | | background: #FFFFFF; |
| | | border-radius:22rpx; |
| | | da-tree{ |
| | | width:543rpx; |
| | | height:93rpx; |
| | | background: #FFFFFF; |
| | | border-radius:22rpx; |
| | | } |
| | | } |
| | | } |
| | | .default{ |
| | | width:100%; |
| | | box-sizing: border-box; |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | height: 100rpx; |
| | | background: #FFFFFF; |
| | | border-radius:16rpx; |
| | | view{ |
| | | margin-left:24rpx; |
| | | font-weight: 300; |
| | | font-size: 32rpx; |
| | | color: #000000; |
| | | } |
| | | } |
| | | .subBtn{ |
| | | width: 100%; |
| | | height: 98rpx; |
| | | background-color:#5EA1FA; |
| | | border-radius: 50rpx; |
| | | font-weight: 300; |
| | | font-size: 36rpx; |
| | | color: #FFFFFF; |
| | | text-align: center; |
| | | line-height:98rpx; |
| | | margin-top:520rpx; |
| | | letter-spacing:2px; |
| | | } |
| | | } |
| | | } |
| | | </style> |
对比新文件 |
| | |
| | | <template> |
| | | <view> |
| | | <navbar title ='编辑地址'></navbar> |
| | | </view> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import {ref,onMounted} from 'vue' |
| | | import { onLoad } from "@dcloudio/uni-app" |
| | | |
| | | const title = ref() |
| | | const id = ref() |
| | | onLoad((option)=>{ |
| | | title.value = option.title |
| | | id.value = option.id |
| | | console.log('123',title.value,id.value) |
| | | }) |
| | | </script> |
| | | |
| | | <style> |
| | | |
| | | </style> |
对比新文件 |
| | |
| | | <template> |
| | | <view> |
| | | |
| | | </view> |
| | | </template> |
| | | |
| | | <script setup> |
| | | |
| | | </script> |
| | | |
| | | <style> |
| | | |
| | | </style> |
| | |
| | | $uni-font-size-subtitle:26px; |
| | | $uni-color-paragraph: #3F536E; // 文章段落颜色 |
| | | $uni-font-size-paragraph:15px; |
| | | |
对比新文件 |
| | |
| | | MIT License |
| | | |
| | | Copyright (c) 2023 www.uviewui.com |
| | | |
| | | Permission is hereby granted, free of charge, to any person obtaining a copy |
| | | of this software and associated documentation files (the "Software"), to deal |
| | | in the Software without restriction, including without limitation the rights |
| | | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
| | | copies of the Software, and to permit persons to whom the Software is |
| | | furnished to do so, subject to the following conditions: |
| | | |
| | | The above copyright notice and this permission notice shall be included in all |
| | | copies or substantial portions of the Software. |
| | | |
| | | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
| | | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
| | | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
| | | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
| | | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
| | | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
| | | SOFTWARE. |