From 29cc15f526b9474175594da6125c2b0b06151721 Mon Sep 17 00:00:00 2001 From: lzm <254311563@qq.com> Date: Sun, 19 Sep 2021 08:53:46 +0800 Subject: [PATCH 1/3] =?UTF-8?q?feat:=20tree-select=E7=BB=93=E6=9E=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- devui/tree-select/index.ts | 18 ++++++++++++++++++ devui/tree-select/src/tree-select-types.ts | 9 +++++++++ devui/tree-select/src/tree-select.scss | 3 +++ devui/tree-select/src/tree-select.tsx | 18 ++++++++++++++++++ sites/components/tree-select/index.md | 0 5 files changed, 48 insertions(+) create mode 100644 devui/tree-select/index.ts create mode 100644 devui/tree-select/src/tree-select-types.ts create mode 100644 devui/tree-select/src/tree-select.scss create mode 100644 devui/tree-select/src/tree-select.tsx create mode 100644 sites/components/tree-select/index.md diff --git a/devui/tree-select/index.ts b/devui/tree-select/index.ts new file mode 100644 index 00000000..4a3ed884 --- /dev/null +++ b/devui/tree-select/index.ts @@ -0,0 +1,18 @@ +import type { App } from 'vue' +import TreeSelect from './src/tree-select' + +TreeSelect.install = function(app: App): void { + app.component(TreeSelect.name, TreeSelect) +} + +export { TreeSelect } + +export default { + title: 'TreeSelect 树形选择框', + category: '数据录入', + status: undefined, // TODO: 组件若开发完成则填入"已完成",并删除该注释 + install(app: App): void { + + app.use(TreeSelect as any) + } +} diff --git a/devui/tree-select/src/tree-select-types.ts b/devui/tree-select/src/tree-select-types.ts new file mode 100644 index 00000000..1b426cb6 --- /dev/null +++ b/devui/tree-select/src/tree-select-types.ts @@ -0,0 +1,9 @@ +import type { PropType, ExtractPropTypes } from 'vue' + +export const treeSelectProps = { + /* test: { + type: Object as PropType<{ xxx: xxx }> + } */ +} as const + +export type TreeSelectProps = ExtractPropTypes diff --git a/devui/tree-select/src/tree-select.scss b/devui/tree-select/src/tree-select.scss new file mode 100644 index 00000000..28076567 --- /dev/null +++ b/devui/tree-select/src/tree-select.scss @@ -0,0 +1,3 @@ +.d-tree-select { + // +} diff --git a/devui/tree-select/src/tree-select.tsx b/devui/tree-select/src/tree-select.tsx new file mode 100644 index 00000000..5c2952df --- /dev/null +++ b/devui/tree-select/src/tree-select.tsx @@ -0,0 +1,18 @@ +import './tree-select.scss' + +import { defineComponent } from 'vue' +import { treeSelectProps, TreeSelectProps } from './tree-select-types' + +export default defineComponent({ + name: 'DTreeSelect', + props: treeSelectProps, + emits: [], + setup(props: TreeSelectProps, ctx) { + return {} + }, + render() { + const {} = this + + return
+ } +}) diff --git a/sites/components/tree-select/index.md b/sites/components/tree-select/index.md new file mode 100644 index 00000000..e69de29b -- Gitee From 18e84e172bd8ce5ac4ed605c4b2af56ad6755bde Mon Sep 17 00:00:00 2001 From: pandeng <657524896@qq.com> Date: Sat, 9 Oct 2021 15:10:47 +0800 Subject: [PATCH 2/3] =?UTF-8?q?feat:=20=E5=A2=9E=E5=8A=A0tree-select?= =?UTF-8?q?=E5=9F=BA=E6=9C=AC=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- devui/tree-select/src/tree-select-types.ts | 66 +++++++++- devui/tree-select/src/tree-select.scss | 140 ++++++++++++++++++++- devui/tree-select/src/tree-select.tsx | 111 +++++++++++++++- devui/tree-select/src/utils.ts | 19 +++ docs/components/tree-select/index.md | 77 ++++++++++++ 5 files changed, 403 insertions(+), 10 deletions(-) create mode 100644 devui/tree-select/src/utils.ts diff --git a/devui/tree-select/src/tree-select-types.ts b/devui/tree-select/src/tree-select-types.ts index 1b426cb6..fcb84dcc 100644 --- a/devui/tree-select/src/tree-select-types.ts +++ b/devui/tree-select/src/tree-select-types.ts @@ -1,9 +1,69 @@ import type { PropType, ExtractPropTypes } from 'vue' +export interface TreeItem { + id?: number | string + label?: string + data?: any + parent?: TreeItem | null + children?: Array | null + level?: number + loading?: boolean + isOpen?: boolean + isChecked?: boolean + disabled?: boolean + + [prop: string]: any +} + +export type TreeData = Array + +export type ModelValue = number | string | Array; + export const treeSelectProps = { - /* test: { - type: Object as PropType<{ xxx: xxx }> - } */ + modelValue: { + type: [String, Number, Array] as PropType, + default: '', + }, + treeData: { + type: Array as PropType, + default: () => [], + }, + placeholder: { + type: String, + default: '请选择', + }, + disabled: { + type: Boolean, + default: false + }, + expandTree: { + type: Boolean, + default: false + }, + multiple: { + type: Boolean, + default: false, + }, + leafOnly: { + type: Boolean, + default: false, + }, + searchable: { + type: Boolean, + default: false, + }, + allowClear: { + type: Boolean, + default: false + }, + onToggleChange: { + type: Function as PropType<(bool: boolean) => void>, + default: undefined, + }, + onValueChange: { + type: Function as PropType<(item: TreeItem, index: number) => void>, + default: undefined, + }, } as const export type TreeSelectProps = ExtractPropTypes diff --git a/devui/tree-select/src/tree-select.scss b/devui/tree-select/src/tree-select.scss index 28076567..71ed5d4f 100644 --- a/devui/tree-select/src/tree-select.scss +++ b/devui/tree-select/src/tree-select.scss @@ -1,3 +1,139 @@ -.d-tree-select { - // +@import '../../style/mixins/index'; +@import '../../style/theme/color'; +@import '../../style/theme/corner'; + +$select-arrow-width: 28px; +$select-dropdown-max-height: 300px; +$select-item-min-height: 36px; + +.devui-tree-select { + position: relative; + width: 100%; +} + +// .devui-tree-select-disabled { +// cursor: not-allowed; +// background-color: $devui-disabled-bg; +// border-color: $devui-disabled-line; +// color: $devui-disabled-text; + +// .devui-tree-select-input { +// cursor: not-allowed; +// background-color: $devui-disabled-bg; +// border-color: $devui-disabled-line; +// color: $devui-disabled-text; +// } + +// .devui-tree-select-arrow { +// cursor: not-allowed; +// color: $devui-disabled-text; +// } +// } +.devui-tree-select-open { + .devui-tree-select-arrow { + transform: rotate3d(0, 0, 1, 180deg); + } +} + +// .devui-tree-select-selection { +// position: relative; +// cursor: pointer; +// } +.devui-tree-select-input { + cursor: pointer; + width: 100%; + height: 28px; + padding: 4px $select-arrow-width 4px 10px; + color: $devui-text; + vertical-align: middle; + border: 1px solid $devui-form-control-line; + border-radius: $devui-border-radius; + outline: none; + background-color: $devui-base-bg; + // @include border-transition(); + + // &:not([disabled]):not(.disabled) { + // &:hover { + // border-color: $devui-form-control-line-hover; + // } + + // &:focus { + // border-color: $devui-form-control-line-active; + // } + // } + + // &[disabled], + // &.disabled { + // &:hover { + // cursor: not-allowed; + // background-color: $devui-disabled-bg; + // border-color: $devui-disabled-line; + // color: $devui-disabled-text; + // } + // } +} + +.devui-tree-select-dropdown { + overflow: auto; + margin: 5px 0; + border-radius: $devui-border-radius; + background: $devui-base-bg; + box-shadow: 0 2px 5px 0 $devui-shadow; +} + +.devui-tree-select-dropdown-list { + max-height: $select-dropdown-max-height; + width: 100%; + overflow-y: auto; + padding: 0; + margin: 0; +} + +.devui-scrollbar { + &::-webkit-scrollbar { + width: 8px; + height: 8px; + } + + &::-webkit-scrollbar-corner { + background-color: transparent; + } + + &::-webkit-scrollbar-thumb { + border-radius: 8px; + background-color: var(--devui-line, #adb0b8); + } + + &::-webkit-scrollbar-track { + background-color: transparent; + } +} + +.devui-tree-select-clearable:hover { + .devui-tree-select-clear { + display: inline-flex; + } + + .devui-tree-select-arrow { + display: none; + } +} + +.devui-tree-select-clear, +.devui-tree-select-arrow { + position: absolute; + right: 0; + height: 100%; + width: $select-arrow-width; + display: inline-flex; + justify-content: center; + align-items: center; +} + +.devui-tree-select-clear { + display: none; + + &:hover { + color: $devui-icon-fill-active; + } } diff --git a/devui/tree-select/src/tree-select.tsx b/devui/tree-select/src/tree-select.tsx index 5c2952df..55bfa64d 100644 --- a/devui/tree-select/src/tree-select.tsx +++ b/devui/tree-select/src/tree-select.tsx @@ -1,18 +1,119 @@ import './tree-select.scss' -import { defineComponent } from 'vue' +import { defineComponent, ref, reactive, toRefs, computed } from 'vue' import { treeSelectProps, TreeSelectProps } from './tree-select-types' +import { className } from './utils' export default defineComponent({ name: 'DTreeSelect', props: treeSelectProps, - emits: [], + emits: ['toggleChange', 'update:modelValue'], setup(props: TreeSelectProps, ctx) { - return {} + + const visible = ref(false) + const origin = ref() + const position = reactive({ + originX: 'left', + originY: 'bottom', + overlayX: 'left', + overlayY: 'top' + }) + + const { treeData } = toRefs(props) + + const inputValue = computed(() => { + return '' + }) + + function toggleChange() { + if(props.disabled) return + visible.value = !visible.value + ctx.emit('toggleChange', visible.value) + } + + function handleClear(e: MouseEvent) { + e.preventDefault() + e.stopPropagation() + if (props.multiple) { + ctx.emit('update:modelValue', []) + } else { + ctx.emit('update:modelValue', '') + } + } + + return { + visible, + origin, + position, + treeData, + inputValue, + handleClear, + toggleChange, + } }, render() { - const {} = this + const { + origin, + position, + treeData, + inputValue, + placeholder, + disabled, + handleClear, + toggleChange, + } = this + + const renderNode = (item) => ( +
+ { item.children ? : {'\u00A0\u00A0\u00A0'}} + {item.label} +
+ ) + + const renderTree = (treeData) => { + return treeData.map(item => { + if (item.children) { + return ( + <> + { renderNode(item) } + { renderTree(item.children) } + + ) + } + return renderNode(item) + }) + } + + const treeSelectCls = className('devui-tree-select', { + 'devui-tree-select-open': this.visible, + 'devui-tree-select-disabled': disabled, + }) - return
+ return ( +
+
+ + + + + + + +
+ +
+
    {renderTree(treeData)}
+
+ {/* {renderTree(treeData)} */} +
+
+ ) } }) diff --git a/devui/tree-select/src/utils.ts b/devui/tree-select/src/utils.ts new file mode 100644 index 00000000..3092da77 --- /dev/null +++ b/devui/tree-select/src/utils.ts @@ -0,0 +1,19 @@ +/** + * 动态获取class字符串 + * @param classStr 是一个字符串,固定的class名 + * @param classOpt 是一个对象,key表示class名,value为布尔值,true则添加,否则不添加 + * @returns 最终的class字符串 + */ +export function className( + classStr: string, + classOpt?: { [key: string]: boolean; } +): string { + let classname = classStr; + if (typeof classOpt === 'object') { + Object.keys(classOpt).forEach((key) => { + classOpt[key] && (classname += ` ${key}`); + }); + } + + return classname; +} diff --git a/docs/components/tree-select/index.md b/docs/components/tree-select/index.md index e69de29b..d5d09684 100644 --- a/docs/components/tree-select/index.md +++ b/docs/components/tree-select/index.md @@ -0,0 +1,77 @@ +# TreeSelect 树形选择框 + +一种从列表中选择嵌套结构数据的组件。 + +### 基本用法 + +:::demo + +```vue + + +``` + +::: \ No newline at end of file -- Gitee From 19b3f0a47202c0c187cf292c1f366a13700230d4 Mon Sep 17 00:00:00 2001 From: pandeng <657524896@qq.com> Date: Sat, 9 Oct 2021 15:11:47 +0800 Subject: [PATCH 3/3] =?UTF-8?q?feat:=20=E5=A2=9E=E5=8A=A0=E7=A6=81?= =?UTF-8?q?=E7=94=A8=20=E5=8F=AF=E6=B8=85=E7=A9=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- devui/tree-select/src/tree-select.scss | 127 +++++++++--------- devui/tree-select/src/tree-select.tsx | 60 ++++++--- docs/components/tree-select/index.md | 174 ++++++++++++++++++++++++- 3 files changed, 278 insertions(+), 83 deletions(-) diff --git a/devui/tree-select/src/tree-select.scss b/devui/tree-select/src/tree-select.scss index 71ed5d4f..9cae0512 100644 --- a/devui/tree-select/src/tree-select.scss +++ b/devui/tree-select/src/tree-select.scss @@ -2,110 +2,95 @@ @import '../../style/theme/color'; @import '../../style/theme/corner'; -$select-arrow-width: 28px; -$select-dropdown-max-height: 300px; -$select-item-min-height: 36px; +$tree-select-input-height: 28px; +$tree-select-dropdown-max-height: 300px; +$tree-select-item-min-height: 36px; +$tree-select-item-font-size: 16px; .devui-tree-select { position: relative; width: 100%; } -// .devui-tree-select-disabled { -// cursor: not-allowed; -// background-color: $devui-disabled-bg; -// border-color: $devui-disabled-line; -// color: $devui-disabled-text; - -// .devui-tree-select-input { -// cursor: not-allowed; -// background-color: $devui-disabled-bg; -// border-color: $devui-disabled-line; -// color: $devui-disabled-text; -// } - -// .devui-tree-select-arrow { -// cursor: not-allowed; -// color: $devui-disabled-text; -// } -// } +.devui-tree-select-disabled { + cursor: not-allowed; + background-color: $devui-disabled-bg; + border-color: $devui-disabled-line; + color: $devui-disabled-text; + + .devui-tree-select-input { + cursor: not-allowed; + background-color: $devui-disabled-bg; + border-color: $devui-disabled-line; + color: $devui-disabled-text; + } + + .devui-tree-select-arrow { + cursor: not-allowed; + color: $devui-disabled-text; + } +} + .devui-tree-select-open { .devui-tree-select-arrow { transform: rotate3d(0, 0, 1, 180deg); } } -// .devui-tree-select-selection { -// position: relative; -// cursor: pointer; -// } .devui-tree-select-input { cursor: pointer; width: 100%; - height: 28px; - padding: 4px $select-arrow-width 4px 10px; + height: $tree-select-input-height; + padding: 4px $tree-select-input-height 4px 10px; color: $devui-text; vertical-align: middle; border: 1px solid $devui-form-control-line; border-radius: $devui-border-radius; outline: none; background-color: $devui-base-bg; - // @include border-transition(); - - // &:not([disabled]):not(.disabled) { - // &:hover { - // border-color: $devui-form-control-line-hover; - // } - - // &:focus { - // border-color: $devui-form-control-line-active; - // } - // } - - // &[disabled], - // &.disabled { - // &:hover { - // cursor: not-allowed; - // background-color: $devui-disabled-bg; - // border-color: $devui-disabled-line; - // color: $devui-disabled-text; - // } - // } } .devui-tree-select-dropdown { - overflow: auto; - margin: 5px 0; border-radius: $devui-border-radius; background: $devui-base-bg; box-shadow: 0 2px 5px 0 $devui-shadow; } .devui-tree-select-dropdown-list { - max-height: $select-dropdown-max-height; - width: 100%; + max-height: $tree-select-dropdown-max-height; overflow-y: auto; padding: 0; margin: 0; } -.devui-scrollbar { - &::-webkit-scrollbar { - width: 8px; - height: 8px; - } +.devui-tree-select-item { + font-size: $tree-select-item-font-size; + display: block; + min-height: $tree-select-item-min-height; + line-height: 1.5; + width: 100%; + padding: 10px; + clear: both; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + border: 0; + color: $devui-text; + cursor: pointer; - &::-webkit-scrollbar-corner { - background-color: transparent; + &:hover:not(.active):not(.disabled) { + color: $devui-list-item-hover-text; + background-color: $devui-list-item-hover-bg; } +} - &::-webkit-scrollbar-thumb { - border-radius: 8px; - background-color: var(--devui-line, #adb0b8); +.devui-tree-select-clearable:hover { + .devui-tree-select-clear { + display: inline-flex; } - &::-webkit-scrollbar-track { - background-color: transparent; + .devui-tree-select-arrow { + display: none; } } @@ -124,7 +109,7 @@ $select-item-min-height: 36px; position: absolute; right: 0; height: 100%; - width: $select-arrow-width; + width: $tree-select-input-height; display: inline-flex; justify-content: center; align-items: center; @@ -134,6 +119,18 @@ $select-item-min-height: 36px; display: none; &:hover { + cursor: pointer; color: $devui-icon-fill-active; } } + +.devui-tree-select-arrow-expand { + display: inline-flex; + justify-content: center; + align-items: center; + transform: rotate3d(0, 0, 1, 270deg); +} + +.devui-tree-select-arrow-open { + transform: rotate3d(0, 0, 1, 0deg); +} diff --git a/devui/tree-select/src/tree-select.tsx b/devui/tree-select/src/tree-select.tsx index 55bfa64d..ed99e34b 100644 --- a/devui/tree-select/src/tree-select.tsx +++ b/devui/tree-select/src/tree-select.tsx @@ -7,7 +7,7 @@ import { className } from './utils' export default defineComponent({ name: 'DTreeSelect', props: treeSelectProps, - emits: ['toggleChange', 'update:modelValue'], + emits: ['toggleChange', 'valueChange', 'update:modelValue'], setup(props: TreeSelectProps, ctx) { const visible = ref(false) @@ -18,19 +18,31 @@ export default defineComponent({ overlayX: 'left', overlayY: 'top' }) + const inputValue = ref('') const { treeData } = toRefs(props) - const inputValue = computed(() => { - return '' + const mergeClearable = computed(() => { + return !props.disabled && props.allowClear && inputValue.value.length > 0; }) function toggleChange() { - if(props.disabled) return + if (props.disabled) return visible.value = !visible.value ctx.emit('toggleChange', visible.value) } + function valueChange(data) { + if (data.isOpen !== undefined) { + data.isOpen = !data.isOpen + } else { + inputValue.value = data.label + visible.value = false + ctx.emit('update:modelValue', data.label) + ctx.emit('toggleChange', visible.value) + } + } + function handleClear(e: MouseEvent) { e.preventDefault() e.stopPropagation() @@ -38,6 +50,7 @@ export default defineComponent({ ctx.emit('update:modelValue', []) } else { ctx.emit('update:modelValue', '') + inputValue.value = '' } } @@ -45,27 +58,46 @@ export default defineComponent({ visible, origin, position, - treeData, inputValue, + mergeClearable, + treeData, handleClear, toggleChange, + valueChange, } }, render() { const { origin, position, - treeData, inputValue, + mergeClearable, + treeData, placeholder, disabled, handleClear, toggleChange, + valueChange } = this + const treeSelectCls = className('devui-tree-select', { + 'devui-tree-select-open': this.visible, + 'devui-tree-select-disabled': disabled, + }) + const renderNode = (item) => ( -
- { item.children ? : {'\u00A0\u00A0\u00A0'}} +
{ + e.preventDefault() + e.stopPropagation() + valueChange(item) + }}> + { item.children ? + + + : {'\u00A0\u00A0\u00A0'}} {item.label}
) @@ -76,7 +108,7 @@ export default defineComponent({ return ( <> { renderNode(item) } - { renderTree(item.children) } + { item.isOpen && renderTree(item.children) } ) } @@ -84,14 +116,9 @@ export default defineComponent({ }) } - const treeSelectCls = className('devui-tree-select', { - 'devui-tree-select-open': this.visible, - 'devui-tree-select-disabled': disabled, - }) - return (
-
+
-
    {renderTree(treeData)}
+
    {renderTree(treeData)}
- {/* {renderTree(treeData)} */}
) diff --git a/docs/components/tree-select/index.md b/docs/components/tree-select/index.md index d5d09684..03a6a904 100644 --- a/docs/components/tree-select/index.md +++ b/docs/components/tree-select/index.md @@ -8,7 +8,7 @@ ```vue +``` + +::: + +### 禁用 + +:::demo + +```vue + + +``` + +::: + +### 可清空 + +:::demo + +```vue + +