first commit
This commit is contained in:
@@ -0,0 +1,6 @@
|
||||
export function getStyles(top, zIndex) {
|
||||
const topStyle = top ? `top:${top}px;` : '';
|
||||
const zIndexStyle = zIndex ? `z-index:${zIndex};` : '';
|
||||
return topStyle + zIndexStyle;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
/** dropdown-item */
|
||||
.t-dropdown-item {
|
||||
position: fixed;
|
||||
right: 0;
|
||||
left: 0;
|
||||
top: 0;
|
||||
overflow: hidden;
|
||||
bottom: 0;
|
||||
}
|
||||
.t-dropdown-item__content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
z-index: 11600;
|
||||
overflow: hidden;
|
||||
}
|
||||
.t-dropdown-item__popup-host {
|
||||
display: block;
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
}
|
||||
.t-dropdown-item__body {
|
||||
flex: 1;
|
||||
background: var(--td-dropdown-menu-bg-color, var(--td-bg-color-container, var(--td-font-white-1, #ffffff)));
|
||||
overflow: auto;
|
||||
max-height: var(--td-dropdown-body-max-height, 560rpx);
|
||||
}
|
||||
.t-dropdown-item__body--tree {
|
||||
display: flex;
|
||||
overflow: hidden;
|
||||
}
|
||||
.t-dropdown-item__body--multi {
|
||||
padding-top: var(--td-spacer, 16rpx);
|
||||
padding-bottom: var(--td-spacer, 16rpx);
|
||||
overflow-y: auto;
|
||||
}
|
||||
.t-dropdown-item__scroll {
|
||||
max-height: var(--td-dropdown-body-max-height, 560rpx);
|
||||
}
|
||||
.t-dropdown-item__footer {
|
||||
display: flex;
|
||||
background: var(--td-dropdown-menu-bg-color, var(--td-bg-color-container, var(--td-font-white-1, #ffffff)));
|
||||
padding: 32rpx;
|
||||
position: relative;
|
||||
}
|
||||
.t-dropdown-item__footer::after {
|
||||
content: '';
|
||||
display: block;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: unset;
|
||||
left: unset;
|
||||
right: unset;
|
||||
background-color: var(--td-component-border, var(--td-gray-color-4, #dcdcdc));
|
||||
}
|
||||
.t-dropdown-item__footer::after {
|
||||
height: 1px;
|
||||
left: 0;
|
||||
right: 0;
|
||||
transform: scaleY(0.5);
|
||||
}
|
||||
:deep(.t-dropdown-item__footer-btn) {
|
||||
flex: 1;
|
||||
}
|
||||
:deep(.t-dropdown-item__footer-btn + .t-dropdown-item__footer-btn) {
|
||||
margin-left: 32rpx;
|
||||
}
|
||||
.t-dropdown-item__body:empty,
|
||||
.t-dropdown-item__footer:empty {
|
||||
display: none;
|
||||
}
|
||||
.t-dropdown-item__radio,
|
||||
.t-dropdown-item__checkbox {
|
||||
width: 100%;
|
||||
overflow: scroll;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.t-dropdown-item__radio-group,
|
||||
.t-dropdown-item__checkbox-group {
|
||||
display: grid;
|
||||
grid-gap: 24rpx;
|
||||
}
|
||||
.t-dropdown-item__radio-group {
|
||||
display: grid;
|
||||
grid-gap: 0rpx;
|
||||
}
|
||||
.t-dropdown-item__checkbox-group {
|
||||
padding: 32rpx;
|
||||
}
|
||||
.t-dropdown-item__mask {
|
||||
position: fixed;
|
||||
width: 100vh;
|
||||
top: 0;
|
||||
left: 0;
|
||||
}
|
||||
@@ -0,0 +1,403 @@
|
||||
<template>
|
||||
<view
|
||||
v-if="wrapperVisible"
|
||||
:class="classPrefix + ' ' + tClass"
|
||||
:style="tools._style([getStyles(top, zIndex), customStyle])"
|
||||
>
|
||||
<view
|
||||
v-if="show"
|
||||
:class="classPrefix + '__mask'"
|
||||
:style="tools._style(['height:' + maskHeight + 'px', customStyle])"
|
||||
@click="handleMaskClick"
|
||||
@touchmove.stop.prevent="closeDropdown"
|
||||
/>
|
||||
<t-popup
|
||||
:visible="show"
|
||||
:z-index="zIndex + 1"
|
||||
:duration="duration"
|
||||
:show-overlay="showOverlay"
|
||||
custom-style="position: absolute"
|
||||
:overlay-props="{ style: 'position: absolute' }"
|
||||
:t-class="classPrefix + '__popup-host'"
|
||||
:t-class-content="classPrefix + '__content ' + tClassContent"
|
||||
@leaved="onLeaved"
|
||||
@visible-change="handleMaskClick"
|
||||
>
|
||||
<view :class="classPrefix + '__body'">
|
||||
<scroll-view
|
||||
v-if="!multiple && options && options.length > 0"
|
||||
:class="classPrefix + '__scroll'"
|
||||
scroll-y
|
||||
:scroll-into-view="'id_' + dataValue"
|
||||
>
|
||||
<t-radio-group
|
||||
:t-class="classPrefix + '__radio ' + classPrefix + '__radio-group ' + tClassColumn"
|
||||
:custom-style="radioGroupCustomStyle"
|
||||
:value="dataValue"
|
||||
@change="handleRadioChange"
|
||||
>
|
||||
<view
|
||||
v-for="(item, index) in options"
|
||||
:id="'id_' + item[valueAlias]"
|
||||
:key="index"
|
||||
>
|
||||
<t-radio
|
||||
:placement="placement"
|
||||
tabindex="0"
|
||||
:checked="dataValue === item[valueAlias]"
|
||||
icon="line"
|
||||
:t-class="classPrefix + '__radio-item radio ' + tClassColumnItem"
|
||||
:t-class-label="tClassColumnItemLabel"
|
||||
:value="item[valueAlias]"
|
||||
:label="item[labelAlias]"
|
||||
:disabled="item.disabled"
|
||||
@change="e => onRadioChange(e, item[valueAlias])"
|
||||
/>
|
||||
</view>
|
||||
</t-radio-group>
|
||||
</scroll-view>
|
||||
<scroll-view
|
||||
v-if="multiple && options && options.length > 0"
|
||||
:class="classPrefix + '__scroll'"
|
||||
scroll-y
|
||||
:scroll-into-view="'id_' + firstCheckedValue"
|
||||
>
|
||||
<t-checkbox-group
|
||||
ref="checkboxGroupRef"
|
||||
:t-class="classPrefix + '__checkbox ' + classPrefix + '__checkbox-group ' + tClassColumn"
|
||||
:custom-style="checkboxGroupCustomStyle"
|
||||
:value="dataValue ? dataValue : []"
|
||||
@change="handleRadioChange"
|
||||
>
|
||||
<view
|
||||
v-for="(item, index) in options"
|
||||
:id="'id_' + item[valueAlias]"
|
||||
:key="index"
|
||||
>
|
||||
<t-checkbox
|
||||
tabindex="0"
|
||||
:t-class="classPrefix + '__checkbox-item ' + tClassColumnItem"
|
||||
theme="tag"
|
||||
:value="item[valueAlias]"
|
||||
:label="item[labelAlias]"
|
||||
:disabled="item.disabled"
|
||||
:checked="dataValue.indexOf(item[valueAlias]) > -1"
|
||||
@change="e => onCheckboxChange(e, item[valueAlias])"
|
||||
/>
|
||||
</view>
|
||||
</t-checkbox-group>
|
||||
</scroll-view>
|
||||
<slot />
|
||||
</view>
|
||||
<view :class="classPrefix + '__footer ' + tClassFooter">
|
||||
<slot name="footer" />
|
||||
<block v-if="multiple">
|
||||
<t-button
|
||||
block
|
||||
:t-class="footerBtnTClass"
|
||||
:class="footerBtnClass"
|
||||
theme="light"
|
||||
content="重置"
|
||||
:disabled="dataValue.length == 0"
|
||||
@click="handleReset"
|
||||
/>
|
||||
<t-button
|
||||
block
|
||||
:t-class="confirmBtnTClass"
|
||||
:class="confirmBtnClass"
|
||||
theme="primary"
|
||||
content="确定"
|
||||
:disabled="dataValue.length == 0"
|
||||
@click="handleConfirm"
|
||||
/>
|
||||
</block>
|
||||
</view>
|
||||
</t-popup>
|
||||
</view>
|
||||
</template>
|
||||
<script>
|
||||
import TButton from '../button/button';
|
||||
import TRadio from '../radio/radio';
|
||||
import TRadioGroup from '../radio-group/radio-group';
|
||||
import TCheckbox from '../checkbox/checkbox';
|
||||
import TCheckboxGroup from '../checkbox-group/checkbox-group';
|
||||
import TPopup from '../popup/popup';
|
||||
|
||||
import { uniComponent } from '../common/src/index';
|
||||
import { prefix } from '../common/config';
|
||||
import { coalesce, getRect, getWindowInfo } from '../common/utils';
|
||||
import props from './props';
|
||||
import menuProps from '../dropdown-menu/props';
|
||||
import tools from '../common/utils.wxs';
|
||||
import { getStyles } from './computed';
|
||||
import { ChildrenMixin, RELATION_MAP } from '../common/relation';
|
||||
import { canUseVirtualHost } from '../common/version';
|
||||
|
||||
|
||||
const name = `${prefix}-dropdown-item`;
|
||||
|
||||
|
||||
export default uniComponent({
|
||||
name,
|
||||
options: {
|
||||
styleIsolation: 'shared',
|
||||
},
|
||||
controlledProps: [{
|
||||
key: 'value',
|
||||
event: 'change',
|
||||
}],
|
||||
externalClasses: [
|
||||
`${prefix}-class`,
|
||||
`${prefix}-class-content`,
|
||||
`${prefix}-class-column`,
|
||||
`${prefix}-class-column-item`,
|
||||
`${prefix}-class-column-item-label`,
|
||||
`${prefix}-class-footer`,
|
||||
],
|
||||
mixins: [ChildrenMixin(RELATION_MAP.DropdownItem)],
|
||||
components: {
|
||||
TButton,
|
||||
TRadio,
|
||||
TRadioGroup,
|
||||
TCheckbox,
|
||||
TCheckboxGroup,
|
||||
TPopup,
|
||||
},
|
||||
props: {
|
||||
...props,
|
||||
},
|
||||
emits: ['close', 'closed', 'change', 'reset', 'confirm', 'opened', 'open'],
|
||||
data() {
|
||||
return {
|
||||
prefix,
|
||||
classPrefix: name,
|
||||
show: false,
|
||||
top: 0,
|
||||
maskHeight: 0,
|
||||
initValue: null,
|
||||
hasChanged: false,
|
||||
duration: menuProps.duration.default,
|
||||
zIndex: menuProps.zIndex.default,
|
||||
overlay: menuProps.showOverlay.default,
|
||||
labelAlias: 'label',
|
||||
valueAlias: 'value',
|
||||
computedLabel: '',
|
||||
firstCheckedValue: '',
|
||||
|
||||
dataValue: coalesce(this.value, this.defaultValue),
|
||||
|
||||
wrapperVisible: false,
|
||||
tools,
|
||||
|
||||
windowTop: 0,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
footerBtnTClass() {
|
||||
return canUseVirtualHost() ? this.footerBtnRealClass : '';
|
||||
},
|
||||
footerBtnClass() {
|
||||
return !canUseVirtualHost() ? this.footerBtnRealClass : '';
|
||||
},
|
||||
footerBtnRealClass() {
|
||||
const { classPrefix } = this;
|
||||
return `${classPrefix}__footer-btn ${classPrefix}__reset-btn`;
|
||||
},
|
||||
|
||||
confirmBtnTClass() {
|
||||
return canUseVirtualHost() ? this.confirmBtnRealClass : '';
|
||||
},
|
||||
confirmBtnClass() {
|
||||
return !canUseVirtualHost() ? this.confirmBtnRealClass : '';
|
||||
},
|
||||
confirmBtnRealClass() {
|
||||
const { classPrefix } = this;
|
||||
return `${classPrefix}__footer-btn ${classPrefix}__confirm-btn`;
|
||||
},
|
||||
|
||||
radioGroupCustomStyle() {
|
||||
return tools._style([
|
||||
{
|
||||
width: '100%',
|
||||
overflow: 'scroll',
|
||||
boxSizing: 'border-box',
|
||||
|
||||
display: 'grid',
|
||||
gridGap: '0px',
|
||||
|
||||
gridTemplateColumns: `repeat(${this.optionsColumns}, 1fr)`,
|
||||
},
|
||||
]);
|
||||
},
|
||||
checkboxGroupCustomStyle() {
|
||||
return tools._style([
|
||||
{
|
||||
width: '100%',
|
||||
overflow: 'scroll',
|
||||
boxSizing: 'border-box',
|
||||
|
||||
display: 'grid',
|
||||
gridGap: '12px',
|
||||
|
||||
gridTemplateColumns: `repeat(${this.optionsColumns}, 1fr)`,
|
||||
padding: '16px',
|
||||
},
|
||||
]);
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
keys: {
|
||||
handler(obj) {
|
||||
this.labelAlias = obj?.label || 'label';
|
||||
this.valueAlias = obj?.value || 'value';
|
||||
},
|
||||
immediate: true,
|
||||
},
|
||||
value: {
|
||||
handler(value) {
|
||||
this.dataValue = value;
|
||||
},
|
||||
immediate: true,
|
||||
},
|
||||
dataValue: {
|
||||
handler(v) {
|
||||
const { options, labelAlias, valueAlias } = this;
|
||||
|
||||
if (this.multiple) {
|
||||
if (v && !Array.isArray(v)) throw TypeError('应传入数组类型的 value');
|
||||
}
|
||||
|
||||
const target = options.find(item => item[valueAlias] === v);
|
||||
|
||||
if (target) {
|
||||
this.computedLabel = target[labelAlias];
|
||||
}
|
||||
},
|
||||
immediate: true,
|
||||
},
|
||||
label: 'getParentAllItems',
|
||||
computedLabel: 'getParentAllItems',
|
||||
disabled: 'getParentAllItems',
|
||||
show: {
|
||||
handler(visible) {
|
||||
if (visible) {
|
||||
this.getParentBottom(() => {
|
||||
this.wrapperVisible = true;
|
||||
});
|
||||
}
|
||||
},
|
||||
immediate: true,
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
const { windowTop } = getWindowInfo();
|
||||
this.windowTop = windowTop || 0;
|
||||
},
|
||||
methods: {
|
||||
getStyles,
|
||||
innerAfterLinked(target) {
|
||||
const { zIndex, duration, showOverlay } = target;
|
||||
|
||||
this.zIndex = zIndex;
|
||||
this.duration = duration;
|
||||
this.showOverlay = showOverlay;
|
||||
},
|
||||
getParentAllItems() {
|
||||
this[RELATION_MAP.DropdownItem]?.getAllItems();
|
||||
},
|
||||
closeDropdown() {
|
||||
this[RELATION_MAP.DropdownItem].activeIdx = -1;
|
||||
this.show = false;
|
||||
this.$emit('close');
|
||||
},
|
||||
|
||||
getParentBottom(cb) {
|
||||
getRect(this[RELATION_MAP.DropdownItem], `#${prefix}-bar`).then((rect) => {
|
||||
this.top = rect.bottom + (this.windowTop || 0);
|
||||
this.maskHeight = rect.top;
|
||||
|
||||
setTimeout(() => {
|
||||
cb();
|
||||
}, 20);
|
||||
});
|
||||
},
|
||||
|
||||
handleTreeClick(e) {
|
||||
const { level, value: itemValue } = e.currentTarget.dataset;
|
||||
const { dataValue } = this;
|
||||
|
||||
dataValue[level] = itemValue;
|
||||
this._trigger('change', { value: dataValue });
|
||||
},
|
||||
|
||||
onRadioChange(e, curValue) {
|
||||
this.handleRadioChange({ value: curValue });
|
||||
},
|
||||
onCheckboxChange(e) {
|
||||
const { checkAll, dataIndeterminate } = this.$refs.checkboxGroupRef;
|
||||
const { context: { value, label }, checked } = e;
|
||||
this.$refs.checkboxGroupRef.updateValue({
|
||||
value,
|
||||
checkAll,
|
||||
indeterminate: dataIndeterminate,
|
||||
checked,
|
||||
item: { label, value, checked },
|
||||
validChildren: false,
|
||||
});
|
||||
},
|
||||
|
||||
handleRadioChange(e) {
|
||||
const { value } = e;
|
||||
|
||||
this._trigger('change', { value });
|
||||
|
||||
if (!this.multiple) {
|
||||
this.closeDropdown();
|
||||
} else {
|
||||
const firstChecked = this.options.find(item => value.includes(item.value));
|
||||
if (firstChecked) {
|
||||
this.firstCheckedValue = firstChecked.value;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
handleMaskClick() {
|
||||
if (this[RELATION_MAP.DropdownItem]?.closeOnClickOverlay) {
|
||||
this.closeDropdown();
|
||||
}
|
||||
},
|
||||
|
||||
handleReset() {
|
||||
this._trigger('change', { value: [] });
|
||||
this._trigger('reset');
|
||||
},
|
||||
|
||||
handleConfirm() {
|
||||
this._trigger('confirm', { value: this.dataValue });
|
||||
this.closeDropdown();
|
||||
// 在关闭 popup 后才自动滚动到首个选项
|
||||
// this.firstCheckedValue = this.firstCheckedValue;
|
||||
},
|
||||
|
||||
onLeaved() {
|
||||
this.wrapperVisible = false;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
</script>
|
||||
<style scoped>
|
||||
@import './dropdown-item.css';
|
||||
</style>
|
||||
<style scoped>
|
||||
.t-dropdown-item__scroll {
|
||||
width: 100%;
|
||||
}
|
||||
:deep(.t-dropdown-item__footer-btn) {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
:deep(.t-dropdown-item__footer-btn + .t-dropdown-item__footer-btn) {
|
||||
margin-left: 32rpx;
|
||||
}
|
||||
</style>
|
||||
71
uni_modules/tdesign-uniapp/components/dropdown-item/props.ts
Normal file
71
uni_modules/tdesign-uniapp/components/dropdown-item/props.ts
Normal file
@@ -0,0 +1,71 @@
|
||||
/* eslint-disable */
|
||||
|
||||
/**
|
||||
* 该文件为脚本自动生成文件,请勿随意修改。如需修改请联系 PMC
|
||||
* */
|
||||
|
||||
import type { TdDropdownItemProps } from './type';
|
||||
export default {
|
||||
/** 是否禁用操作项 */
|
||||
disabled: Boolean,
|
||||
/** 用来定义 value / label / disabled 在 `options` 中对应的字段别名 */
|
||||
keys: {
|
||||
type: Object,
|
||||
},
|
||||
/** 标题 */
|
||||
label: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
/** 是否多选 */
|
||||
multiple: Boolean,
|
||||
/** 选项数据 */
|
||||
options: {
|
||||
type: Array,
|
||||
default: (): TdDropdownItemProps['options'] => [],
|
||||
},
|
||||
/** 选项分栏(1-3) */
|
||||
optionsColumns: {
|
||||
type: [String, Number],
|
||||
default: 1 as TdDropdownItemProps['optionsColumns'],
|
||||
},
|
||||
/** 复选框和内容相对位置,仅单选菜单栏有效 */
|
||||
placement: {
|
||||
type: String,
|
||||
default: 'left' as TdDropdownItemProps['placement'],
|
||||
validator(val: TdDropdownItemProps['placement']): boolean {
|
||||
if (!val) return true;
|
||||
return ['left', 'right'].includes(val);
|
||||
},
|
||||
},
|
||||
/** 选中值 */
|
||||
value: {
|
||||
type: [String, Number, Array],
|
||||
default: undefined as TdDropdownItemProps['value'],
|
||||
},
|
||||
/** 选中值,非受控属性 */
|
||||
defaultValue: {
|
||||
type: [String, Number, Array],
|
||||
default: undefined as TdDropdownItemProps['defaultValue'],
|
||||
},
|
||||
/** 值改变时触发 */
|
||||
onChange: {
|
||||
type: Function,
|
||||
default: () => ({}),
|
||||
},
|
||||
/** 关闭时触发 */
|
||||
onClose: {
|
||||
type: Function,
|
||||
default: () => ({}),
|
||||
},
|
||||
/** 点击确认时触发 */
|
||||
onConfirm: {
|
||||
type: Function,
|
||||
default: () => ({}),
|
||||
},
|
||||
/** 点击重置时触发 */
|
||||
onReset: {
|
||||
type: Function,
|
||||
default: () => ({}),
|
||||
},
|
||||
};
|
||||
76
uni_modules/tdesign-uniapp/components/dropdown-item/type.ts
Normal file
76
uni_modules/tdesign-uniapp/components/dropdown-item/type.ts
Normal file
@@ -0,0 +1,76 @@
|
||||
/* eslint-disable */
|
||||
|
||||
/**
|
||||
* 该文件为脚本自动生成文件,请勿随意修改。如需修改请联系 PMC
|
||||
* */
|
||||
|
||||
import type { KeysType } from '../common/common';
|
||||
|
||||
export interface TdDropdownItemProps {
|
||||
/**
|
||||
* 是否禁用操作项
|
||||
* @default false
|
||||
*/
|
||||
disabled?: boolean;
|
||||
/**
|
||||
* 用来定义 value / label / disabled 在 `options` 中对应的字段别名
|
||||
*/
|
||||
keys?: KeysType;
|
||||
/**
|
||||
* 标题
|
||||
* @default ''
|
||||
*/
|
||||
label?: string;
|
||||
/**
|
||||
* 是否多选
|
||||
* @default false
|
||||
*/
|
||||
multiple?: boolean;
|
||||
/**
|
||||
* 选项数据
|
||||
* @default []
|
||||
*/
|
||||
options?: Array<DropdownOption>;
|
||||
/**
|
||||
* 选项分栏(1-3)
|
||||
* @default 1
|
||||
*/
|
||||
optionsColumns?: string | number;
|
||||
/**
|
||||
* 复选框和内容相对位置,仅单选菜单栏有效
|
||||
* @default left
|
||||
*/
|
||||
placement?: 'left' | 'right';
|
||||
/**
|
||||
* 选中值
|
||||
*/
|
||||
value?: DropdownValue;
|
||||
/**
|
||||
* 选中值,非受控属性
|
||||
*/
|
||||
defaultValue?: DropdownValue;
|
||||
/**
|
||||
* 值改变时触发
|
||||
*/
|
||||
onChange?: (context: { value: DropdownValue }) => void;
|
||||
/**
|
||||
* 关闭时触发
|
||||
*/
|
||||
onClose?: () => void;
|
||||
/**
|
||||
* 点击确认时触发
|
||||
*/
|
||||
onConfirm?: (context: { value: DropdownValue }) => void;
|
||||
/**
|
||||
* 点击重置时触发
|
||||
*/
|
||||
onReset?: () => void;
|
||||
}
|
||||
|
||||
export interface DropdownOption {
|
||||
label: string;
|
||||
disabled: boolean;
|
||||
value: DropdownValue;
|
||||
}
|
||||
|
||||
export type DropdownValue = string | number | Array<DropdownValue>;
|
||||
Reference in New Issue
Block a user