From 5df45cb43556d01032541558e9ac3f1f20a67ccf Mon Sep 17 00:00:00 2001 From: Echo Date: Wed, 24 Nov 2021 22:01:37 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E7=A9=BF=E6=A2=AD=E6=A1=86=E6=96=B0?= =?UTF-8?q?=E5=A2=9EtransferToSource=E7=AD=89=E4=BA=8B=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../transfer/common/use-transfer-base.ts | 18 +++- .../devui/transfer/common/use-transfer.ts | 38 ++++++-- .../devui/transfer/src/transfer-base.tsx | 82 +++++++++------- .../devui/transfer/src/transfer-operation.tsx | 2 +- .../devui/transfer/src/transfer.scss | 31 +++++-- .../devui-vue/devui/transfer/src/transfer.tsx | 93 ++++++++++++++----- packages/devui-vue/devui/transfer/types.ts | 24 +---- .../docs/components/transfer/index.md | 41 +++++--- 8 files changed, 216 insertions(+), 113 deletions(-) diff --git a/packages/devui-vue/devui/transfer/common/use-transfer-base.ts b/packages/devui-vue/devui/transfer/common/use-transfer-base.ts index ff58c152..4c3b6131 100644 --- a/packages/devui-vue/devui/transfer/common/use-transfer-base.ts +++ b/packages/devui-vue/devui/transfer/common/use-transfer-base.ts @@ -33,10 +33,14 @@ export const transferBaseProps = { type: Boolean, default: (): boolean => false }, - query: { + filter: { type: String, default: (): string => '' }, + height: { + type: String, + default: (): string => '320px' + }, alltargetState: { type: Boolean, default: (): boolean => false @@ -53,6 +57,14 @@ export const transferBaseProps = { type: Number, default: (): number => 0 }, + showTooltip: { + type: Boolean, + default: (): boolean => false + }, + tooltipPosition: { + type: String as PropType<'top' | 'right' | 'bottom' | 'left'>, + default: (): string => 'top' + }, scopedSlots: { type: Object }, @@ -109,7 +121,7 @@ export const initState = (props: TransferProps, type: string): TState => { allChecked: false, disabled: false, checkedNum: initModel.model.length, - query: '', + keyword: '', checkedValues: initModel.model, filterData: initModel.data } @@ -123,6 +135,6 @@ export const TransferBaseClass = (props: TransferOperationProps): ComputedRef => } export const Query = ((props: TransferOperationProps): ComputedRef => { - return computed(() => props.query) + return computed(() => props.filter) }) diff --git a/packages/devui-vue/devui/transfer/common/use-transfer.ts b/packages/devui-vue/devui/transfer/common/use-transfer.ts index 42eee67d..414d2dfc 100644 --- a/packages/devui-vue/devui/transfer/common/use-transfer.ts +++ b/packages/devui-vue/devui/transfer/common/use-transfer.ts @@ -1,5 +1,5 @@ import { ExtractPropTypes, PropType, SetupContext } from 'vue' -import { IItem, ITitles, IModel } from '../types' +import { IItem, ITitles, IModel, TState } from '../types' export const transferProps = { sourceOption: { @@ -26,30 +26,52 @@ export const transferProps = { }, height: { type: String, - default: '320px' + default: (): string => '320px' }, isSearch: { type: Boolean, - default: false + default: (): boolean => false }, isSourceDroppable: { type: Boolean, - default: false + default: (): boolean => false }, isTargetDroppable: { type: Boolean, - default: false + default: (): boolean => false }, disabled: { type: Boolean, - default: false + default: (): boolean => false }, - showOptionTitle: { + showTooltip: { type: Boolean, - default: false + default: (): boolean => false + }, + tooltipPosition: { + type: String as PropType<'top' | 'right' | 'bottom' | 'left'>, + default: (): string => 'top' + }, + beforeTransfer: { + type: Function as unknown as () => ((sourceOption: TState, targetOption: TState) => boolean | Promise) }, slots: { type: Object + }, + searching: { + type: Function as unknown as () => ((direction: string, keyword: string, targetOption: TState) => void) + }, + transferToSource: { + type: Function as unknown as () => ((sourceOption: TState, targetOption: TState) => void) + }, + transferToTarget: { + type: Function as unknown as () => ((sourceOption: TState, targetOption: TState) => void) + }, + transferring: { + type: Function as unknown as () => ((targetOption: TState) => void) + }, + afterTransfer: { + type: Function as unknown as () => ((targetOption: TState) => void) } } diff --git a/packages/devui-vue/devui/transfer/src/transfer-base.tsx b/packages/devui-vue/devui/transfer/src/transfer-base.tsx index 605057af..10ec9f60 100644 --- a/packages/devui-vue/devui/transfer/src/transfer-base.tsx +++ b/packages/devui-vue/devui/transfer/src/transfer-base.tsx @@ -1,35 +1,51 @@ import { defineComponent, computed } from 'vue' -import { transferBaseProps, TransferBaseClass , TransferBaseProps } from '../common/use-transfer-base' +import { transferBaseProps, TransferBaseClass, TransferBaseProps } from '../common/use-transfer-base' import DCheckbox from '../../checkbox/src/checkbox' import DCheckboxGroup from '../../checkbox/src/checkbox-group' import DSearch from '../../search/src/search' +import DTooltip from '../../tooltip/src/tooltip' export default defineComponent({ name: 'DTransferBase', components: { DSearch, DCheckboxGroup, - DCheckbox + DCheckbox, + DTooltip }, props: transferBaseProps, setup(props: TransferBaseProps, ctx) { /** data start **/ - const modelValues = computed(() => props.checkedValues) - const searchQuery = computed(() => props.query) + const modelValues = computed(() => props.checkedValues as Array) + const searchQuery = computed(() => props.filter) const baseClass = TransferBaseClass(props) /** data end **/ /** watch start **/ - /** watch start **/ + /** watch end **/ /** methods start **/ const updateSearchQuery = (val: string): void => ctx.emit('changeQuery', val) + + const renderCheckbox = (props, key, showTooltip = false, tooltipPosition = 'top') => { + const checkbox = + + return !showTooltip ? checkbox : {checkbox} + } /** methods start **/ return { baseClass, searchQuery, modelValues, - updateSearchQuery + updateSearchQuery, + renderCheckbox } }, render() { @@ -43,9 +59,13 @@ export default defineComponent({ updateSearchQuery, search, searchQuery, - modelValues + modelValues, + height, + showTooltip, + tooltipPosition, + renderCheckbox, } = this - + return (
{ @@ -61,33 +81,27 @@ export default defineComponent({
) } { - this.$slots.body ? this.$slots.body() :
- {search && } -
- { - sourceOption.length ? this.$emit('updateCheckeds', values)}> - { - sourceOption.map((item, idx) => { - return - - }) - } - : -
无数据
- } + this.$slots.body ? this.$slots.body() : +
+ {search && } +
+ { + sourceOption.length ? this.$emit('updateCheckeds', values)}> + { + sourceOption.map((item, idx) => { + return renderCheckbox(item, idx, showTooltip, tooltipPosition) + }) + } + : +
无数据
+ } +
-
}
) diff --git a/packages/devui-vue/devui/transfer/src/transfer-operation.tsx b/packages/devui-vue/devui/transfer/src/transfer-operation.tsx index 388024b4..4815a474 100644 --- a/packages/devui-vue/devui/transfer/src/transfer-operation.tsx +++ b/packages/devui-vue/devui/transfer/src/transfer-operation.tsx @@ -1,4 +1,4 @@ -import { defineComponent, computed } from 'vue'; +import { defineComponent } from 'vue'; import DButton from '../../button/src/button' import { transferOperationProps } from '../common/use-transfer-operation' diff --git a/packages/devui-vue/devui/transfer/src/transfer.scss b/packages/devui-vue/devui/transfer/src/transfer.scss index 9328c50e..8a6bb867 100644 --- a/packages/devui-vue/devui/transfer/src/transfer.scss +++ b/packages/devui-vue/devui/transfer/src/transfer.scss @@ -4,7 +4,6 @@ $devui-transfer-border-color: #adb0b8; $devui-transfer-border-radius: 2px; $devui-transfer-header-height: 40px; $devui-transfer-header-border-line-color:#dfe1e6; -$devui-transfer-body-search-height: 42px; $devui-transfer-body-list-item-height: 36px; .devui-transfer { @@ -33,15 +32,16 @@ $devui-transfer-body-list-item-height: 36px; } &-body { + display: flex; + flex-direction: column; height: 100%; &-search { - height: $devui-transfer-body-search-height; display: flex; justify-content: center; - width: calc(100% - 40px); align-items: center; - margin: 0 auto; + width: 100%; + padding: 7px 20px; .devui-search, .devui-input__wrap { @@ -51,13 +51,32 @@ $devui-transfer-body-list-item-height: 36px; &-list { overflow: auto; - height: calc(100% - #{$devui-transfer-header-height} - #{$devui-transfer-body-search-height}); width: 100%; + padding: 0 20px; &-item { - margin: 0 20px; height: $devui-transfer-body-list-item-height; line-height: $devui-transfer-body-list-item-height; + width: min-content; + max-width: 100%; + + label { + text-overflow: ellipsis; + overflow: hidden; + white-space: nowrap; + height: auto; + } + } + + &-tooltip { + // display: flex; + // max-width: calc(100% - 20px); + // overflow: hidden; + // text-overflow: ellipsis; + // white-space: nowrap; + .slotElement { + max-width: 100%; + } } &-empty { diff --git a/packages/devui-vue/devui/transfer/src/transfer.tsx b/packages/devui-vue/devui/transfer/src/transfer.tsx index e05273d0..e857d12b 100644 --- a/packages/devui-vue/devui/transfer/src/transfer.tsx +++ b/packages/devui-vue/devui/transfer/src/transfer.tsx @@ -17,14 +17,28 @@ export default defineComponent({ props: transferProps, setup(props: TransferProps, ctx: SetupContext) { /** data start **/ - const leftOptions = reactive(initState(props, 'source')) - const rightOptions = reactive(initState(props, 'target')) + let leftOptions = reactive(initState(props, 'source')) + let rightOptions = reactive(initState(props, 'target')) const origin = ref(null); /** data end **/ /** watch start **/ watch( - () => leftOptions.query, + () => props.sourceOption, + () => { + leftOptions = reactive(initState(props, 'source')) + } + ) + + watch( + () => props.targetOption, + () => { + rightOptions = reactive(initState(props, 'target')) + } + ) + + watch( + () => leftOptions.keyword, (nVal: string): void => { searchFilterData(leftOptions) } @@ -42,7 +56,7 @@ export default defineComponent({ ) watch( - () => rightOptions.query, + () => rightOptions.keyword, (nVal: string): void => { searchFilterData(rightOptions) }, @@ -70,19 +84,36 @@ export default defineComponent({ } } - const updateFilterData = (source: TState, target: TState): void => { - const newData = [] - source.data = source.data.filter(item => { - const hasInclues = source.checkedValues.includes(item.value) - hasInclues && newData.push(item) - return !hasInclues - }) - target.data = target.data.concat(newData) + const updateFilterData = async (source: TState, target: TState, direction: string): Promise => { + if (isFunction('beforeTransfer')) { + const res: boolean = await props.beforeTransfer.call(null, source, target) + if (typeof res === 'boolean' && res === false) { + return + } + } + + const hasToSource = isFunction('transferToSource') + const hasToTarget = isFunction('transferToTarget') + const hasTransfering = isFunction('transferring') + if (hasToSource || hasToTarget) { + direction === 'right' && props.transferToSource.call(null, source, target) + direction === 'left' && props.transferToTarget.call(null, source, target) + } else { + source.data = source.data.filter(item => { + const hasInclues = source.checkedValues.includes(item.value) + hasInclues && target.data.push(item) + return !hasInclues + }) + } + if (hasTransfering) { + props.transferring.call(null, target) + } source.checkedValues = [] target.disabled = !target.disabled searchFilterData(source, target) searchFilterData(target, source) setOrigin('click') + isFunction('afterTransfer') && props.afterTransfer.call(null, target) } const changeAllSource = (source: TState, value: boolean): void => { if (source.filterData.every(item => item.disabled)) return @@ -104,31 +135,43 @@ export default defineComponent({ setOrigin('change') } const searchFilterData = (source: TState, target?: TState): void => { - source.filterData = source.data.filter(item => item.key.indexOf(source.query) !== -1) + source.filterData = source.data.filter(item => item.key.indexOf(source.keyword) !== -1) if (target) { target.allChecked = false } } + const setOrigin = (value: string): void => { origin.value = value } + const changeQueryHandle = (source: TState, direction: string, value: string): void => { + if (props?.searching && typeof props.searching === 'function') { + props.searching.call(null, direction, value, source) + return + } + source.keyword = value + } + + const isFunction = (type: string): boolean => { + return props[type] && typeof props[type] === 'function' + } /** methods end **/ return () => { return
changeAllSource(leftOptions, value)} onUpdateCheckeds={updateLeftCheckeds} - onChangeQuery={(value) => leftOptions.query = value} + onChangeQuery={(value) => changeQueryHandle(leftOptions, 'left', value)} /> 0 ? false : true} targetDisabled={leftOptions.checkedNum > 0 ? false : true} - onUpdateSourceData={() => { updateFilterData(rightOptions, leftOptions) }} - onUpdateTargetData={() => { updateFilterData(leftOptions, rightOptions) }} + onUpdateSourceData={() => { updateFilterData(rightOptions, leftOptions, 'left') }} + onUpdateTargetData={() => { updateFilterData(leftOptions, rightOptions, 'right') }} /> changeAllSource(rightOptions, value)} onUpdateCheckeds={updateRightCheckeds} - onChangeQuery={(value) => rightOptions.query = value} + onChangeQuery={(value) => changeQueryHandle(rightOptions, 'right', value)} />
} diff --git a/packages/devui-vue/devui/transfer/types.ts b/packages/devui-vue/devui/transfer/types.ts index c527d74e..3846d0da 100644 --- a/packages/devui-vue/devui/transfer/types.ts +++ b/packages/devui-vue/devui/transfer/types.ts @@ -17,7 +17,7 @@ export interface TState { data: IItem[] allChecked: boolean checkedNum: number - query: string + keyword: string checkedValues: string[] filterData: IItem[] disabled: boolean @@ -30,25 +30,3 @@ export interface TResult { -// import { ComputedRef } from 'vue' -// export type TItem = { -// key: string -// value: string -// disabled: boolean -// checked?: boolean -// } - -// export type TState = { -// data: TItem[] -// allChecked: boolean -// // disabled: boolean // ComputedRef// -// checkedNum: number -// query: string -// checkedValues: string[] -// filterData: TItem[] -// } - -// export type TResult = { -// model: string[] -// data: TItem[] -// } diff --git a/packages/devui-vue/docs/components/transfer/index.md b/packages/devui-vue/docs/components/transfer/index.md index 304c507f..2a1ae140 100644 --- a/packages/devui-vue/docs/components/transfer/index.md +++ b/packages/devui-vue/docs/components/transfer/index.md @@ -26,9 +26,7 @@ import { defineComponent, reactive } from 'vue' export default defineComponent({ setup() { - const options = reactive({ - titles: ['sourceHeader', 'targetHeader'], - source: [ + const originSource = [ { key: '北京', value: '北京', @@ -79,8 +77,8 @@ export default defineComponent({ value: '重庆', disabled: false, }, - ], - target: [ + ] + const originTarget = [ { key: '南充', value: '南充', @@ -96,9 +94,16 @@ export default defineComponent({ value: '绵阳', disabled: false, }, - ], + ] + + const options = reactive({ + titles: ['sourceHeader', 'targetHeader'], + source: originSource, + target: originTarget, + originSource, + originTarget, isSearch: true, - modelValues: ['深圳', '成都', '绵阳'] + modelValues: ['深圳', '成都', '绵阳'], }) return { @@ -373,13 +378,23 @@ export default defineComponent({ ### API +d-transfer 参数 | **参数** | **类型** | **默认** | **说明** | **跳转 Demo** | | ------------------ | ------------------------------------------------------------ | ------------------------- | ------------------------------------------------------------ | ---------------------------- | -| sourceOption | Array | [] | 可选参数,穿梭框源数据 | [基本用法](#基本用法) | -| targetOption | Array | [] | 可选参数,穿梭框目标数据 | [基本用法](#基本用法) | -| titles | Array | [] | 可选参数,穿梭框标题 | [基本用法](#基本用法) | -| height | string | 320px | 可选参数,穿梭框高度 | [基本用法](#基本用法) | -| isSearch | boolean | true | 可选参数,是否可以搜索 | [基本用法](#基本用法) | -| disabled | boolean | false | 可选参数 穿梭框禁止使用 | [基本用法](#基本用法) | +| sourceOption | `Array` | [] | 可选参数,穿梭框源数据 | [基本用法](#基本用法) | +| targetOption | `Array` | [] | 可选参数,穿梭框目标数据 | [基本用法](#基本用法) | +| titles | `Array` | [] | 可选参数,穿梭框标题 | [基本用法](#基本用法) | +| height | `Array` | 320px | 可选参数,穿梭框高度 | [基本用法](#基本用法) | +| isSearch | `Array` | true | 可选参数,是否可以搜索 | [基本用法](#基本用法) | +| disabled | `Array` | false | 可选参数 穿梭框禁止使用 | [基本用法](#基本用法) | +| beforeTransfer | `(sourceOption, targetOption) => boolean \| Promise` |- | 可选参数 穿梭框禁止使用 | [基本用法](#基本用法) | +d-transfer 事件 +| **事件** | **类型** | **说明** | **跳转 Demo** | +| ------------------ | ------------------------------------------------------------ | ------------------------------------------------------------ | ---------------------------- | +| transferToSource | `EventEmitter<{sourceOption, targetOption}>` | 当点击右穿梭时,返回穿梭框源和目标数据; | [基本用法](#基本用法) | +| transferToTarget | `EventEmitter<{sourceOption, targetOption}>` | 当点击左穿梭时,返回穿梭框源和目标数据; | [基本用法](#基本用法) | +| searching | `EventEmitter<{direction, keyword}>` | 当搜索时触发,返回目标穿梭框和搜索文字,不设置此事件则会使用默认方法; | [基本用法](#基本用法) | +| transferring | `EventEmitter` | 当穿梭时触发,返回目标穿梭框,不设置此事件则会使用默认方法; | [基本用法](#基本用法) | +| afterTransfer | `EventEmitter` | 当穿梭完成后,返回目标穿梭框,不设置transferEvent才会触发; | [基本用法](#基本用法) | \ No newline at end of file -- Gitee