Liuyi
2024-09-26 0d17d9e049b4750a03ca7c8eb7b435b823446def
添加地址管理,新增地址页,导入tree树形组件
已修改7个文件
已添加11个文件
2485 ■■■■■ 文件已修改
App.vue 3 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
components/da-tree/changelog.md 196 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
components/da-tree/index.vue 1151 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
components/da-tree/props.ts 197 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
components/da-tree/readme.md 310 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
components/da-tree/utils.ts 150 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
main.js 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
package-lock.json 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
package.json 33 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pages.json 24 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pages/address/index.vue 147 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pages/addressAdd/index.vue 198 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pages/addressEdit/index.vue 22 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pages/addressLocate/index.vue 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
static/images/address/delete.png 补丁 | 查看 | 原始文档 | blame | 历史
static/images/address/edit.png 补丁 | 查看 | 原始文档 | blame | 历史
uni.scss 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
uni_modules/uview-ui/LICENSE 21 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
App.vue
@@ -25,6 +25,5 @@
    });
</script>
<style>
    /*每个页面公共css */
<style lang='scss'>
</style>
components/da-tree/changelog.md
对比新文件
@@ -0,0 +1,196 @@
# 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. 支持多选
components/da-tree/index.vue
对比新文件
@@ -0,0 +1,1151 @@
<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>
components/da-tree/props.ts
对比新文件
@@ -0,0 +1,197 @@
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,
  },
}
components/da-tree/readme.md
对比新文件
@@ -0,0 +1,310 @@
# 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)
components/da-tree/utils.ts
对比新文件
@@ -0,0 +1,150 @@
/** 未选 */
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)
}
main.js
@@ -1,7 +1,7 @@
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)
package-lock.json
@@ -1,6 +1,21 @@
{
  "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=="
    }
  }
}
package.json
@@ -1,16 +1,19 @@
{
  "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.json
@@ -1,5 +1,11 @@
{
    "pages": [ //pages数组中第一项表示应用启动页,参考:https://uniapp.dcloud.io/collocation/pages
        // {
        //     "path" : "pages/addressAdd/index",
        //     "style": {
        //         "navigationStyle":"custom"
        //     }
        // },
        {
            "path": "pages/index/index",
            "style": {
@@ -41,6 +47,24 @@
            "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": {
pages/address/index.vue
@@ -1,11 +1,57 @@
<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>
@@ -16,15 +62,106 @@
        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>
pages/addressAdd/index.vue
对比新文件
@@ -0,0 +1,198 @@
<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>
pages/addressEdit/index.vue
对比新文件
@@ -0,0 +1,22 @@
<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>
pages/addressLocate/index.vue
对比新文件
@@ -0,0 +1,13 @@
<template>
    <view>
    </view>
</template>
<script setup>
</script>
<style>
</style>
static/images/address/delete.png
static/images/address/edit.png
uni.scss
@@ -74,3 +74,4 @@
$uni-font-size-subtitle:26px;
$uni-color-paragraph: #3F536E; // 文章段落颜色
$uni-font-size-paragraph:15px;
uni_modules/uview-ui/LICENSE
对比新文件
@@ -0,0 +1,21 @@
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.