From 0d17d9e049b4750a03ca7c8eb7b435b823446def Mon Sep 17 00:00:00 2001 From: Liuyi <candymxq888@outlook.com> Date: 星期四, 26 九月 2024 18:33:31 +0800 Subject: [PATCH] 添加地址管理,新增地址页,导入tree树形组件 --- components/da-tree/readme.md | 310 +++++++ package-lock.json | 17 pages.json | 24 static/images/address/delete.png | 0 static/images/address/edit.png | 0 App.vue | 3 pages/address/index.vue | 147 +++ uni.scss | 1 components/da-tree/props.ts | 197 ++++ pages/addressAdd/index.vue | 198 ++++ pages/addressLocate/index.vue | 13 components/da-tree/utils.ts | 150 +++ pages/addressEdit/index.vue | 22 components/da-tree/changelog.md | 196 ++++ package.json | 33 uni_modules/uview-ui/LICENSE | 21 main.js | 2 components/da-tree/index.vue | 1151 +++++++++++++++++++++++++++ 18 files changed, 2,461 insertions(+), 24 deletions(-) diff --git a/App.vue b/App.vue index 1efba44..23ef92a 100644 --- a/App.vue +++ b/App.vue @@ -25,6 +25,5 @@ }); </script> -<style> - /*每个页面公共css */ +<style lang='scss'> </style> diff --git a/components/da-tree/changelog.md b/components/da-tree/changelog.md new file mode 100644 index 0000000..7117197 --- /dev/null +++ b/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. 支持多选 diff --git a/components/da-tree/index.vue b/components/da-tree/index.vue new file mode 100644 index 0000000..ead519d --- /dev/null +++ b/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,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') 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> diff --git a/components/da-tree/props.ts b/components/da-tree/props.ts new file mode 100644 index 0000000..2b80e0d --- /dev/null +++ b/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, + }, +} diff --git a/components/da-tree/readme.md b/components/da-tree/readme.md new file mode 100644 index 0000000..9b06ead --- /dev/null +++ b/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) diff --git a/components/da-tree/utils.ts b/components/da-tree/utils.ts new file mode 100644 index 0000000..f11bbb3 --- /dev/null +++ b/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) +} diff --git a/main.js b/main.js index d6eab51..0ecbff0 100644 --- a/main.js +++ b/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) diff --git a/package-lock.json b/package-lock.json index 6cd76f1..063845d 100644 --- a/package-lock.json +++ b/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==" + } + } } diff --git a/package.json b/package.json index c5861ff..815f9dd 100644 --- a/package.json +++ b/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": [ + "前端组件", + "通用组件" + ] + } +} \ No newline at end of file diff --git a/pages.json b/pages.json index 1f90b6f..a407d75 100644 --- a/pages.json +++ b/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": { diff --git a/pages/address/index.vue b/pages/address/index.vue index ce84ea2..cc4fbc5 100644 --- a/pages/address/index.vue +++ b/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> diff --git a/pages/addressAdd/index.vue b/pages/addressAdd/index.vue new file mode 100644 index 0000000..e7105be --- /dev/null +++ b/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> diff --git a/pages/addressEdit/index.vue b/pages/addressEdit/index.vue new file mode 100644 index 0000000..c060725 --- /dev/null +++ b/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> diff --git a/pages/addressLocate/index.vue b/pages/addressLocate/index.vue new file mode 100644 index 0000000..ddadfea --- /dev/null +++ b/pages/addressLocate/index.vue @@ -0,0 +1,13 @@ +<template> + <view> + + </view> +</template> + +<script setup> + +</script> + +<style> + +</style> diff --git a/static/images/address/delete.png b/static/images/address/delete.png new file mode 100644 index 0000000..9e23a21 --- /dev/null +++ b/static/images/address/delete.png Binary files differ diff --git a/static/images/address/edit.png b/static/images/address/edit.png new file mode 100644 index 0000000..a137e40 --- /dev/null +++ b/static/images/address/edit.png Binary files differ diff --git a/uni.scss b/uni.scss index b9249e9..d129e4f 100644 --- a/uni.scss +++ b/uni.scss @@ -74,3 +74,4 @@ $uni-font-size-subtitle:26px; $uni-color-paragraph: #3F536E; // 文章段落颜色 $uni-font-size-paragraph:15px; + diff --git a/uni_modules/uview-ui/LICENSE b/uni_modules/uview-ui/LICENSE new file mode 100644 index 0000000..4db40ef --- /dev/null +++ b/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. -- Gitblit v1.9.3