first commit
This commit is contained in:
@@ -0,0 +1,56 @@
|
||||
:: BASE_DOC ::
|
||||
|
||||
## API
|
||||
|
||||
### Cascader Props
|
||||
|
||||
name | type | default | description | required
|
||||
-- | -- | -- | -- | --
|
||||
custom-style | Object | - | CSS(Cascading Style Sheets) | N
|
||||
check-strictly | Boolean | false | \- | N
|
||||
close-btn | Boolean | true | \- | N
|
||||
keys | Object | - | Typescript: `CascaderKeysType` `type CascaderKeysType = TreeKeysType`。[see more ts definition](https://github.com/tencent/tdesign-miniprogram/blob/develop/packages/uniapp-components/common/common.ts)。[see more ts definition](https://github.com/tencent/tdesign-miniprogram/blob/develop/packages/uniapp-components/cascader/type.ts) | N
|
||||
options | Array | [] | Typescript: `Array<CascaderOption>` | N
|
||||
placeholder | String | 选择选项 | \- | N
|
||||
sub-titles | Array | [] | Typescript: `Array<string>` | N
|
||||
theme | String | step | options: step/tab | N
|
||||
title | String | - | \- | N
|
||||
value | String / Number | - | `v-model:value` is supported | N
|
||||
default-value | String / Number | - | uncontrolled property | N
|
||||
visible | Boolean | false | \- | N
|
||||
|
||||
### Cascader Events
|
||||
|
||||
name | params | description
|
||||
-- | -- | --
|
||||
change | `(context: { value: string \| number, selectedOptions: string[] })` | \-
|
||||
close | `(context: { trigger: CascaderTriggerSource })` | [see more ts definition](https://github.com/tencent/tdesign-miniprogram/blob/develop/packages/uniapp-components/cascader/type.ts)。<br/>`type CascaderTriggerSource = 'overlay' \| 'close-btn' \| 'finish'`<br/>
|
||||
pick | `(context: { value: string \| number, label: string, index: number, level: number })` | \-
|
||||
|
||||
### Cascader Slots
|
||||
|
||||
name | Description
|
||||
-- | --
|
||||
close-btn | \-
|
||||
header | \-
|
||||
middle-content | \-
|
||||
title | \-
|
||||
|
||||
### CSS Variables
|
||||
|
||||
The component provides the following CSS variables, which can be used to customize styles.
|
||||
Name | Default Value | Description
|
||||
-- | -- | --
|
||||
--td-cascader-active-color | @brand-color | -
|
||||
--td-cascader-bg-color | @bg-color-container | -
|
||||
--td-cascader-border-color | @component-stroke | -
|
||||
--td-cascader-content-height | 78vh | -
|
||||
--td-cascader-disabled-color | @text-color-disabled | -
|
||||
--td-cascader-options-height | calc(100% - @cascader-step-height) | -
|
||||
--td-cascader-options-title-color | @text-color-placeholder | -
|
||||
--td-cascader-step-arrow-color | @text-color-placeholder | -
|
||||
--td-cascader-step-dot-size | 16rpx | -
|
||||
--td-cascader-step-height | 88rpx | -
|
||||
--td-cascader-title-color | @text-color-primary | -
|
||||
--td-cascader-title-font | @font-title-large | -
|
||||
--td-cascader-title-padding | @spacer-2 | -
|
||||
96
uni_modules/tdesign-uniapp/components/cascader/README.md
Normal file
96
uni_modules/tdesign-uniapp/components/cascader/README.md
Normal file
@@ -0,0 +1,96 @@
|
||||
---
|
||||
title: Cascader 级联选择器
|
||||
description: 级联选择器适用于有清晰层级结构的数据集合,用户可以通过逐级查看并选择。
|
||||
spline: form
|
||||
isComponent: true
|
||||
---
|
||||
|
||||
|
||||
## 引入
|
||||
|
||||
可在 `main.ts` 或在需要使用的页面或组件中引入。
|
||||
|
||||
```js
|
||||
import TCascader from '@tdesign/uniapp/cascader/cascader.vue';
|
||||
```
|
||||
|
||||
### 基础用法
|
||||
|
||||
{{ base }}
|
||||
|
||||
### 选项卡风格
|
||||
|
||||
{{ theme-tab }}
|
||||
|
||||
### 进阶
|
||||
|
||||
#### 带初始值
|
||||
|
||||
{{ with-value }}
|
||||
|
||||
#### 自定义 keys
|
||||
|
||||
{{ keys }}
|
||||
|
||||
#### 使用次级标题
|
||||
|
||||
{{ with-title }}
|
||||
|
||||
#### 选择任意一项
|
||||
|
||||
{{ check-strictly }}
|
||||
|
||||
## API
|
||||
|
||||
### Cascader Props
|
||||
|
||||
名称 | 类型 | 默认值 | 描述 | 必传
|
||||
-- | -- | -- | -- | --
|
||||
custom-style | Object | - | 自定义样式 | N
|
||||
check-strictly | Boolean | false | 父子节点选中状态不再关联,可各自选中或取消 | N
|
||||
close-btn | Boolean | true | 关闭按钮 | N
|
||||
keys | Object | - | 用来定义 value / label / children / disabled 在 `options` 中对应的字段别名。TS 类型:`CascaderKeysType` `type CascaderKeysType = TreeKeysType`。[通用类型定义](https://github.com/tencent/tdesign-miniprogram/blob/develop/packages/uniapp-components/common/common.ts)。[详细类型定义](https://github.com/tencent/tdesign-miniprogram/blob/develop/packages/uniapp-components/cascader/type.ts) | N
|
||||
options | Array | [] | 可选项数据源。TS 类型:`Array<CascaderOption>` | N
|
||||
placeholder | String | 选择选项 | 未选中时的提示文案 | N
|
||||
sub-titles | Array | [] | 每级展示的次标题。TS 类型:`Array<string>` | N
|
||||
theme | String | step | 展示风格。可选项:step/tab | N
|
||||
title | String | - | 标题 | N
|
||||
value | String / Number | - | 选项值。支持语法糖 `v-model:value` | N
|
||||
default-value | String / Number | - | 选项值。非受控属性 | N
|
||||
visible | Boolean | false | 是否展示 | N
|
||||
|
||||
### Cascader Events
|
||||
|
||||
名称 | 参数 | 描述
|
||||
-- | -- | --
|
||||
change | `(context: { value: string \| number, selectedOptions: string[] })` | 值发生变更时触发
|
||||
close | `(context: { trigger: CascaderTriggerSource })` | 关闭时触发。[详细类型定义](https://github.com/tencent/tdesign-miniprogram/blob/develop/packages/uniapp-components/cascader/type.ts)。<br/>`type CascaderTriggerSource = 'overlay' \| 'close-btn' \| 'finish'`<br/>
|
||||
pick | `(context: { value: string \| number, label: string, index: number, level: number })` | 选择后触发
|
||||
|
||||
### Cascader Slots
|
||||
|
||||
名称 | 描述
|
||||
-- | --
|
||||
close-btn | 自定义 `close-btn` 显示内容
|
||||
header | 头部
|
||||
middle-content | 中间内容
|
||||
title | 自定义 `title` 显示内容
|
||||
|
||||
### CSS Variables
|
||||
|
||||
组件提供了下列 CSS 变量,可用于自定义样式。
|
||||
名称 | 默认值 | 描述
|
||||
-- | -- | --
|
||||
--td-cascader-active-color | @brand-color | -
|
||||
--td-cascader-bg-color | @bg-color-container | -
|
||||
--td-cascader-border-color | @component-stroke | -
|
||||
--td-cascader-content-height | 78vh | -
|
||||
--td-cascader-disabled-color | @text-color-disabled | -
|
||||
--td-cascader-options-height | calc(100% - @cascader-step-height) | -
|
||||
--td-cascader-options-title-color | @text-color-placeholder | -
|
||||
--td-cascader-step-arrow-color | @text-color-placeholder | -
|
||||
--td-cascader-step-dot-size | 16rpx | -
|
||||
--td-cascader-step-height | 88rpx | -
|
||||
--td-cascader-title-color | @text-color-primary | -
|
||||
--td-cascader-title-font | @font-title-large | -
|
||||
--td-cascader-title-padding | @spacer-2 | -
|
||||
102
uni_modules/tdesign-uniapp/components/cascader/cascader.css
Normal file
102
uni_modules/tdesign-uniapp/components/cascader/cascader.css
Normal file
@@ -0,0 +1,102 @@
|
||||
.t-cascader {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background-color: var(--td-cascader-bg-color, var(--td-bg-color-container, var(--td-font-white-1, #ffffff)));
|
||||
color: var(--td-cascader-title-color, var(--td-text-color-primary, var(--td-font-gray-1, rgba(0, 0, 0, 0.9))));
|
||||
border-radius: var(--td-radius-extraLarge, 24rpx) var(--td-radius-extraLarge, 24rpx) 0 0;
|
||||
--td-radio-icon-checked-color: var(--td-cascader-active-color, var(--td-brand-color, var(--td-primary-color-7, #0052d9)));
|
||||
--td-tab-item-active-color: var(--td-cascader-active-color, var(--td-brand-color, var(--td-primary-color-7, #0052d9)));
|
||||
--td-tab-track-color: var(--td-cascader-active-color, var(--td-brand-color, var(--td-primary-color-7, #0052d9)));
|
||||
}
|
||||
.t-cascader__close-btn {
|
||||
right: var(--td-spacer-2, 32rpx);
|
||||
top: var(--td-spacer-2, 32rpx);
|
||||
position: absolute;
|
||||
}
|
||||
.t-cascader__title {
|
||||
position: relative;
|
||||
font: var(--td-cascader-title-font, var(--td-font-title-large, 600 36rpx / 52rpx var(--td-font-family, PingFang SC, Microsoft YaHei, Arial Regular)));
|
||||
text-align: center;
|
||||
padding: var(--td-cascader-title-padding, var(--td-spacer-2, 32rpx));
|
||||
}
|
||||
.t-cascader__content {
|
||||
width: 100%;
|
||||
height: var(--td-cascader-content-height, 78vh);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.t-cascader__options {
|
||||
width: 100vw;
|
||||
}
|
||||
.t-cascader__options-title {
|
||||
color: var(--td-cascader-options-title-color, var(--td-text-color-placeholder, var(--td-font-gray-3, rgba(0, 0, 0, 0.4))));
|
||||
font: var(--td-font-body-medium, 28rpx / 44rpx var(--td-font-family, PingFang SC, Microsoft YaHei, Arial Regular));
|
||||
padding-top: 40rpx;
|
||||
padding-left: var(--td-spacer-2, 32rpx);
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.t-cascader__options-container {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
transition: all ease 0.3s;
|
||||
}
|
||||
.t-cascader__step {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: var(--td-cascader-step-height, 88rpx);
|
||||
}
|
||||
.t-cascader__steps {
|
||||
padding: 0 32rpx 10rpx;
|
||||
position: relative;
|
||||
}
|
||||
.t-cascader__steps::after {
|
||||
content: '';
|
||||
display: block;
|
||||
position: absolute;
|
||||
top: unset;
|
||||
bottom: 0;
|
||||
left: unset;
|
||||
right: unset;
|
||||
background-color: var(--td-cascader-border-color, var(--td-component-stroke, var(--td-gray-color-3, #e7e7e7)));
|
||||
}
|
||||
.t-cascader__steps::after {
|
||||
height: 1px;
|
||||
left: 0;
|
||||
right: 0;
|
||||
transform: scaleY(0.5);
|
||||
}
|
||||
.t-cascader__step-dot {
|
||||
position: relative;
|
||||
width: var(--td-cascader-step-dot-size, 16rpx);
|
||||
height: var(--td-cascader-step-dot-size, 16rpx);
|
||||
border-radius: 50%;
|
||||
border: 2rpx solid var(--td-cascader-active-color, var(--td-brand-color, var(--td-primary-color-7, #0052d9)));
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.t-cascader__step-dot:not(.t-cascader__step-dot--last)::after {
|
||||
content: '';
|
||||
display: block;
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: calc(var(--td-cascader-step-dot-size, 16rpx) + 14rpx);
|
||||
height: 36rpx;
|
||||
width: 2rpx;
|
||||
background: var(--td-cascader-active-color, var(--td-brand-color, var(--td-primary-color-7, #0052d9)));
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
.t-cascader__step-dot--active {
|
||||
background: var(--td-cascader-active-color, var(--td-brand-color, var(--td-primary-color-7, #0052d9)));
|
||||
border-color: var(--td-cascader-active-color, var(--td-brand-color, var(--td-primary-color-7, #0052d9)));
|
||||
}
|
||||
.t-cascader__step-label {
|
||||
padding-left: var(--td-spacer-2, 32rpx);
|
||||
font: var(--td-font-body-medium, 28rpx / 44rpx var(--td-font-family, PingFang SC, Microsoft YaHei, Arial Regular));
|
||||
}
|
||||
.t-cascader__step-label--active {
|
||||
color: var(--td-cascader-active-color, var(--td-brand-color, var(--td-primary-color-7, #0052d9)));
|
||||
font-weight: 600;
|
||||
}
|
||||
.t-cascader__step-arrow {
|
||||
color: var(--td-cascader-step-arrow-color, var(--td-text-color-placeholder, var(--td-font-gray-3, rgba(0, 0, 0, 0.4))));
|
||||
margin-left: auto;
|
||||
}
|
||||
519
uni_modules/tdesign-uniapp/components/cascader/cascader.vue
Normal file
519
uni_modules/tdesign-uniapp/components/cascader/cascader.vue
Normal file
@@ -0,0 +1,519 @@
|
||||
<template>
|
||||
<view>
|
||||
<TPopup
|
||||
:class="tClass"
|
||||
:visible="dataVisible"
|
||||
placement="bottom"
|
||||
@visible-change="onVisibleChange"
|
||||
>
|
||||
<view
|
||||
:style="tools._style([customStyle])"
|
||||
:class="name"
|
||||
>
|
||||
<view :class="name + '__title'">
|
||||
<slot name="title" />
|
||||
{{ title }}
|
||||
</view>
|
||||
<view
|
||||
:class="name + '__close-btn'"
|
||||
@click="onClose"
|
||||
>
|
||||
<slot name="close-btn" />
|
||||
<TIcon
|
||||
v-if="closeBtn"
|
||||
size="48rpx"
|
||||
name="close"
|
||||
/>
|
||||
</view>
|
||||
|
||||
<slot name="header" />
|
||||
|
||||
<view :class="name + '__content'">
|
||||
<block v-if="steps && steps.length">
|
||||
<view
|
||||
v-if="theme == 'step'"
|
||||
:class="name + '__steps'"
|
||||
>
|
||||
<view
|
||||
v-for="(item, index) in steps"
|
||||
:key="index"
|
||||
:class="name + '__step'"
|
||||
:data-index="index"
|
||||
@click="() => onStepClick(index)"
|
||||
>
|
||||
<view
|
||||
:class="
|
||||
name +
|
||||
'__step-dot ' +
|
||||
name +
|
||||
'__step-dot--' +
|
||||
(item !== placeholder ? 'active' : '') +
|
||||
' ' +
|
||||
name +
|
||||
'__step-dot--' +
|
||||
(index === steps.length - 1 ? 'last' : '')
|
||||
"
|
||||
/>
|
||||
|
||||
<view :class="name + '__step-label ' + name + '__step-label--' + (index === stepIndex ? 'active' : '')">
|
||||
{{ item }}
|
||||
</view>
|
||||
|
||||
<TIcon
|
||||
name="chevron-right"
|
||||
size="44rpx"
|
||||
:t-class="name + '__step-arrow'"
|
||||
:custom-style="stepArrowCustomStyle"
|
||||
style="margin-left: auto"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<TTabs
|
||||
v-if="theme == 'tab'"
|
||||
ref="tabs"
|
||||
:value="stepIndex"
|
||||
:space-evenly="false"
|
||||
@change="({value}) => onTabChange(value)"
|
||||
>
|
||||
<TTabPanel
|
||||
v-for="(item, index) in steps"
|
||||
:key="index"
|
||||
:ref="`tab-${index}`"
|
||||
:value="index"
|
||||
:label="item"
|
||||
/>
|
||||
</TTabs>
|
||||
</block>
|
||||
|
||||
<slot name="middle-content" />
|
||||
|
||||
<view
|
||||
v-if="subTitles && subTitles[stepIndex]"
|
||||
:class="name + '__options-title'"
|
||||
>
|
||||
{{ subTitles[stepIndex] }}
|
||||
</view>
|
||||
|
||||
<view
|
||||
:class="name + '__options-container'"
|
||||
:style="'width: ' + (items.length + 1) + '00vw; transform: translateX(-' + stepIndex + '00vw)'"
|
||||
>
|
||||
<scroll-view
|
||||
v-for="(options, index) in items"
|
||||
:key="index"
|
||||
:class="name + '__options'"
|
||||
scroll-y
|
||||
:scroll-top="scrollTopList[index]"
|
||||
type="list"
|
||||
:style="'height: ' + _optionsHeight + 'px'"
|
||||
>
|
||||
<view :class="'cascader-radio-group-' + index">
|
||||
<TRadioGroup
|
||||
:value="selectedValue[index]"
|
||||
:keys="keys"
|
||||
:options="options"
|
||||
:data-level="index"
|
||||
placement="right"
|
||||
icon="line"
|
||||
borderless
|
||||
@change="({ value }) => handleSelect($event, { level: index, value })"
|
||||
/>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</TPopup>
|
||||
</view>
|
||||
</template>
|
||||
<script>
|
||||
import TIcon from '../icon/icon';
|
||||
import TPopup from '../popup/popup';
|
||||
import TTabs from '../tabs/tabs';
|
||||
import TTabPanel from '../tab-panel/tab-panel';
|
||||
import TRadioGroup from '../radio-group/radio-group';
|
||||
import { uniComponent } from '../common/src/index';
|
||||
import { prefix } from '../common/config';
|
||||
import props from './props';
|
||||
import { getRect, coalesce, nextTick } from '../common/utils';
|
||||
|
||||
import tools from '../common/utils.wxs';
|
||||
|
||||
|
||||
const name = `${prefix}-cascader`;
|
||||
|
||||
function parseOptions(options, keys) {
|
||||
const label = coalesce(keys?.label, 'label');
|
||||
const value = coalesce(keys?.value, 'value');
|
||||
const disabled = coalesce(keys?.disabled, 'disabled');
|
||||
|
||||
return options.map(item => ({
|
||||
[label]: item[label],
|
||||
[value]: item[value],
|
||||
[disabled]: item[disabled],
|
||||
}));
|
||||
}
|
||||
|
||||
const defaultState = {
|
||||
contentHeight: 0,
|
||||
stepHeight: 0,
|
||||
tabsHeight: 0,
|
||||
subTitlesHeight: 0,
|
||||
stepsInitHeight: 0,
|
||||
};
|
||||
|
||||
export default uniComponent({
|
||||
name,
|
||||
options: {
|
||||
styleIsolation: 'shared',
|
||||
},
|
||||
controlledProps: [
|
||||
{
|
||||
key: 'value',
|
||||
event: 'change',
|
||||
},
|
||||
],
|
||||
externalClasses: [
|
||||
`${prefix}-class`,
|
||||
],
|
||||
components: {
|
||||
TIcon,
|
||||
TPopup,
|
||||
TTabs,
|
||||
TTabPanel,
|
||||
TRadioGroup,
|
||||
},
|
||||
props: {
|
||||
...props,
|
||||
},
|
||||
emits: [
|
||||
'update:visible',
|
||||
],
|
||||
data() {
|
||||
return {
|
||||
prefix,
|
||||
name,
|
||||
stepIndex: 0,
|
||||
selectedIndexes: [],
|
||||
selectedValue: [],
|
||||
scrollTopList: [],
|
||||
steps: [],
|
||||
_optionsHeight: 0,
|
||||
tools,
|
||||
|
||||
dataVisible: this.visible,
|
||||
dataValue: coalesce(this.value, this.defaultValue),
|
||||
items: [],
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
stepArrowCustomStyle() {
|
||||
return tools._style({
|
||||
color: 'var(--td-cascader-step-arrow-color, var(--td-text-color-placeholder, var(--td-font-gray-3, rgba(0, 0, 0, .4))))',
|
||||
marginLeft: 'auto',
|
||||
});
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
visible: {
|
||||
handler(v) {
|
||||
this.dataVisible = v;
|
||||
},
|
||||
immediate: true,
|
||||
},
|
||||
dataVisible: {
|
||||
handler(v) {
|
||||
if (v) {
|
||||
nextTick().then(() => {
|
||||
const $tabs = this.$refs.tabs;
|
||||
$tabs?.setTrack();
|
||||
$tabs?.getTabHeight().then((res) => {
|
||||
this.state.tabsHeight = res.height;
|
||||
});
|
||||
});
|
||||
|
||||
// 不能使用 this.$nextTick,在头条小程序下会报错
|
||||
nextTick().then(() => {
|
||||
this.initOptionsHeight(this.steps.length);
|
||||
this.updateScrollTop();
|
||||
this.initWithValue();
|
||||
});
|
||||
} else {
|
||||
this.state = { ...defaultState };
|
||||
}
|
||||
},
|
||||
immediate: true,
|
||||
},
|
||||
|
||||
value: {
|
||||
handler(v) {
|
||||
this.dataValue = v;
|
||||
},
|
||||
immediate: true,
|
||||
},
|
||||
|
||||
dataValue: {
|
||||
handler() {
|
||||
this.initWithValue();
|
||||
},
|
||||
immediate: true,
|
||||
},
|
||||
|
||||
options: {
|
||||
handler() {
|
||||
const { selectedValue, steps, items } = this.genItems();
|
||||
|
||||
this.steps = steps;
|
||||
this.items = items;
|
||||
this.selectedValue = selectedValue;
|
||||
this.stepIndex = items.length - 1;
|
||||
this.setTabParent();
|
||||
},
|
||||
immediate: true,
|
||||
deep: true,
|
||||
},
|
||||
selectedIndexes: {
|
||||
handler() {
|
||||
const { visible, theme } = this;
|
||||
const { selectedValue, steps, items } = this.genItems();
|
||||
|
||||
this.steps = steps;
|
||||
this.setTabParent();
|
||||
this.selectedValue = selectedValue;
|
||||
this.stepIndex = items.length - 1;
|
||||
|
||||
if (JSON.stringify(items) !== JSON.stringify(this.items)) {
|
||||
this.items = items;
|
||||
}
|
||||
|
||||
|
||||
if (visible && theme === 'step') {
|
||||
this.updateOptionsHeight(steps.length);
|
||||
}
|
||||
},
|
||||
immediate: true,
|
||||
deep: true,
|
||||
},
|
||||
|
||||
stepIndex: {
|
||||
handler() {
|
||||
const { dataVisible: visible } = this;
|
||||
|
||||
if (visible) {
|
||||
this.updateScrollTop();
|
||||
}
|
||||
},
|
||||
immediate: true,
|
||||
deep: true,
|
||||
},
|
||||
},
|
||||
created() {
|
||||
this.state = {
|
||||
...defaultState,
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
|
||||
},
|
||||
methods: {
|
||||
setTabParent() {
|
||||
// #ifdef MP-TOUTIAO
|
||||
nextTick().then(() => {
|
||||
const tabsRef = this.$refs.tabs;
|
||||
this.steps.forEach((tools, index) => {
|
||||
const tabRef = this.$refs[`tab-${index}`];
|
||||
tabRef?.[0]?.setParent(tabsRef);
|
||||
});
|
||||
});
|
||||
// #endif
|
||||
},
|
||||
updateOptionsHeight(steps) {
|
||||
const { contentHeight, stepsInitHeight, stepHeight, subTitlesHeight } = this.state;
|
||||
this._optionsHeight = contentHeight - stepsInitHeight - subTitlesHeight - (steps - 1) * stepHeight;
|
||||
},
|
||||
|
||||
async initOptionsHeight(steps) {
|
||||
const { theme, subTitles } = this;
|
||||
|
||||
const { height } = await getRect(this, `.${name}__content`);
|
||||
this.state.contentHeight = height;
|
||||
|
||||
if (theme === 'step') {
|
||||
await Promise.all([
|
||||
getRect(this, `.${name}__steps`),
|
||||
getRect(this, `.${name}__step`),
|
||||
])
|
||||
.then(([stepsRect, stepRect]) => {
|
||||
this.state.stepsInitHeight = stepsRect.height - (steps - 1) * stepRect.height;
|
||||
this.state.stepHeight = stepRect.height;
|
||||
})
|
||||
.catch(() => {
|
||||
});
|
||||
}
|
||||
|
||||
if (subTitles.length > 0) {
|
||||
const { height } = await getRect(this, `.${name}__options-title`);
|
||||
this.state.subTitlesHeight = height;
|
||||
}
|
||||
|
||||
const optionsInitHeight = this.state.contentHeight - this.state.subTitlesHeight;
|
||||
this._optionsHeight = theme === 'step'
|
||||
? optionsInitHeight - this.state.stepsInitHeight - (steps - 1) * this.state.stepHeight
|
||||
: optionsInitHeight - this.state.tabsHeight;
|
||||
},
|
||||
|
||||
initWithValue() {
|
||||
if (this.dataValue != null && this.dataValue !== '') {
|
||||
const selectedIndexes = this.getIndexesByValue(this.options, this.dataValue);
|
||||
|
||||
if (selectedIndexes) {
|
||||
this.selectedIndexes = selectedIndexes;
|
||||
}
|
||||
} else {
|
||||
this.selectedIndexes = [];
|
||||
}
|
||||
},
|
||||
getIndexesByValue(options, value) {
|
||||
const { keys } = this;
|
||||
|
||||
for (let i = 0, size = options.length; i < size; i += 1) {
|
||||
const opt = options[i];
|
||||
if (opt[coalesce(keys?.value, 'value')] === value) {
|
||||
return [i];
|
||||
}
|
||||
if (opt[coalesce(keys?.children, 'children')]) {
|
||||
const res = this.getIndexesByValue(opt[coalesce(keys?.children, 'children')], value);
|
||||
if (res) {
|
||||
return [i, ...res];
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
updateScrollTop() {
|
||||
const { dataVisible: visible, items, selectedIndexes, stepIndex } = this;
|
||||
|
||||
if (visible) {
|
||||
getRect(this, '.cascader-radio-group-0').then((rect) => {
|
||||
const eachRadioHeight = rect.height / items[0]?.length;
|
||||
|
||||
this[`scrollTopList[${stepIndex}]`] = eachRadioHeight * selectedIndexes[stepIndex];
|
||||
})
|
||||
.catch(() => {
|
||||
});
|
||||
}
|
||||
},
|
||||
hide(trigger) {
|
||||
this.dataVisible = false;
|
||||
this.$emit('close', { trigger });
|
||||
this.$emit('update:visible', false);
|
||||
},
|
||||
onVisibleChange() {
|
||||
this.hide('overlay');
|
||||
},
|
||||
onClose() {
|
||||
if (this.checkStrictly) {
|
||||
this.triggerChange();
|
||||
}
|
||||
this.hide('close-btn');
|
||||
},
|
||||
onStepClick(index) {
|
||||
this.stepIndex = index;
|
||||
},
|
||||
onTabChange(value) {
|
||||
this.stepIndex = value;
|
||||
},
|
||||
genItems() {
|
||||
const { options, selectedIndexes, keys, placeholder } = this;
|
||||
const selectedValue = [];
|
||||
const steps = [];
|
||||
const items = [parseOptions(options, keys)];
|
||||
|
||||
if (options.length > 0) {
|
||||
let current = options;
|
||||
for (let i = 0, size = selectedIndexes.length; i < size; i += 1) {
|
||||
const index = selectedIndexes[i];
|
||||
const next = current[index];
|
||||
current = next[coalesce(keys?.children, 'children')];
|
||||
|
||||
selectedValue.push(next[coalesce(keys?.value, 'value')]);
|
||||
steps.push(next[coalesce(keys?.label, 'label')]);
|
||||
|
||||
if (next[coalesce(keys?.children, 'children')]) {
|
||||
items.push(parseOptions(next[coalesce(keys?.children, 'children')], keys));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (steps.length < items.length) {
|
||||
steps.push(placeholder);
|
||||
}
|
||||
|
||||
return {
|
||||
selectedValue,
|
||||
steps,
|
||||
items,
|
||||
};
|
||||
},
|
||||
handleSelect(tools, { level, value }) {
|
||||
const { checkStrictly } = this;
|
||||
const { selectedIndexes, items, keys, options, selectedValue } = this;
|
||||
|
||||
const index = items[level].findIndex(item => item[coalesce(keys?.value, 'value')] === value);
|
||||
|
||||
let item = selectedIndexes.slice(0, level).reduce((acc, item, index) => {
|
||||
if (index === 0) {
|
||||
return acc[item];
|
||||
}
|
||||
return acc[coalesce(keys?.children, 'children')][item];
|
||||
}, options);
|
||||
|
||||
|
||||
if (level === 0) {
|
||||
item = item[index];
|
||||
} else {
|
||||
item = item[coalesce(keys?.children, 'children')][index];
|
||||
}
|
||||
|
||||
if (item[coalesce(keys?.disabled, 'disabled')]) {
|
||||
return;
|
||||
}
|
||||
this.$emit('pick', {
|
||||
value: item[coalesce(keys?.value, 'value')],
|
||||
label: item[coalesce(keys?.label, 'label')],
|
||||
index,
|
||||
level,
|
||||
});
|
||||
selectedIndexes[level] = index;
|
||||
if (checkStrictly && selectedValue.includes(String(value))) {
|
||||
selectedIndexes.length = level;
|
||||
this.selectedIndexes = selectedIndexes;
|
||||
return;
|
||||
}
|
||||
selectedIndexes.length = level + 1;
|
||||
|
||||
const { items: newItems } = this.genItems();
|
||||
if (item?.[coalesce(keys?.children, 'children')]?.length >= 0) {
|
||||
this.selectedIndexes = selectedIndexes;
|
||||
this[`items[${level + 1}]`] = newItems[level + 1];
|
||||
} else {
|
||||
// setCascaderValue(item.value);
|
||||
this.selectedIndexes = selectedIndexes;
|
||||
setTimeout(this.triggerChange);
|
||||
|
||||
this.hide('finish');
|
||||
}
|
||||
},
|
||||
triggerChange() {
|
||||
const { items, selectedValue, selectedIndexes } = this;
|
||||
this._trigger('change', {
|
||||
value: coalesce(selectedValue[selectedValue.length - 1], ''),
|
||||
selectedOptions: items.map((item, index) => item[selectedIndexes[index]]).filter(Boolean),
|
||||
});
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
||||
<style scoped>
|
||||
@import './cascader.css';
|
||||
</style>
|
||||
73
uni_modules/tdesign-uniapp/components/cascader/props.ts
Normal file
73
uni_modules/tdesign-uniapp/components/cascader/props.ts
Normal file
@@ -0,0 +1,73 @@
|
||||
/* eslint-disable */
|
||||
|
||||
/**
|
||||
* 该文件为脚本自动生成文件,请勿随意修改。如需修改请联系 PMC
|
||||
* */
|
||||
|
||||
import type { TdCascaderProps } from './type';
|
||||
export default {
|
||||
/** 父子节点选中状态不再关联,可各自选中或取消 */
|
||||
checkStrictly: Boolean,
|
||||
/** 关闭按钮 */
|
||||
closeBtn: {
|
||||
type: Boolean,
|
||||
default: true as TdCascaderProps['closeBtn'],
|
||||
},
|
||||
/** 用来定义 value / label / children / disabled 在 `options` 中对应的字段别名 */
|
||||
keys: {
|
||||
type: Object,
|
||||
},
|
||||
/** 可选项数据源 */
|
||||
options: {
|
||||
type: Array,
|
||||
default: (): TdCascaderProps['options'] => [],
|
||||
},
|
||||
/** 未选中时的提示文案 */
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: '选择选项',
|
||||
},
|
||||
/** 每级展示的次标题 */
|
||||
subTitles: {
|
||||
type: Array,
|
||||
default: (): TdCascaderProps['subTitles'] => [],
|
||||
},
|
||||
/** 展示风格 */
|
||||
theme: {
|
||||
type: String,
|
||||
default: 'step' as TdCascaderProps['theme'],
|
||||
validator(val: TdCascaderProps['theme']): boolean {
|
||||
if (!val) return true;
|
||||
return ['step', 'tab'].includes(val);
|
||||
},
|
||||
},
|
||||
/** 标题 */
|
||||
title: {
|
||||
type: String,
|
||||
},
|
||||
/** 选项值 */
|
||||
value: {
|
||||
type: [String, Number],
|
||||
},
|
||||
/** 选项值,非受控属性 */
|
||||
defaultValue: {
|
||||
type: [String, Number],
|
||||
},
|
||||
/** 是否展示 */
|
||||
visible: Boolean,
|
||||
/** 值发生变更时触发 */
|
||||
onChange: {
|
||||
type: Function,
|
||||
default: () => ({}),
|
||||
},
|
||||
/** 关闭时触发 */
|
||||
onClose: {
|
||||
type: Function,
|
||||
default: () => ({}),
|
||||
},
|
||||
/** 选择后触发 */
|
||||
onPick: {
|
||||
type: Function,
|
||||
default: () => ({}),
|
||||
},
|
||||
};
|
||||
77
uni_modules/tdesign-uniapp/components/cascader/type.ts
Normal file
77
uni_modules/tdesign-uniapp/components/cascader/type.ts
Normal file
@@ -0,0 +1,77 @@
|
||||
/* eslint-disable */
|
||||
|
||||
/**
|
||||
* 该文件为脚本自动生成文件,请勿随意修改。如需修改请联系 PMC
|
||||
* */
|
||||
|
||||
import type { TreeOptionData, TreeKeysType } from '../common/common';
|
||||
|
||||
export interface TdCascaderProps<CascaderOption extends TreeOptionData = TreeOptionData> {
|
||||
/**
|
||||
* 父子节点选中状态不再关联,可各自选中或取消
|
||||
* @default false
|
||||
*/
|
||||
checkStrictly?: boolean;
|
||||
/**
|
||||
* 关闭按钮
|
||||
* @default true
|
||||
*/
|
||||
closeBtn?: boolean;
|
||||
/**
|
||||
* 用来定义 value / label / children / disabled 在 `options` 中对应的字段别名
|
||||
*/
|
||||
keys?: CascaderKeysType;
|
||||
/**
|
||||
* 可选项数据源
|
||||
* @default []
|
||||
*/
|
||||
options?: Array<CascaderOption>;
|
||||
/**
|
||||
* 未选中时的提示文案
|
||||
* @default 选择选项
|
||||
*/
|
||||
placeholder?: string;
|
||||
/**
|
||||
* 每级展示的次标题
|
||||
* @default []
|
||||
*/
|
||||
subTitles?: Array<string>;
|
||||
/**
|
||||
* 展示风格
|
||||
* @default step
|
||||
*/
|
||||
theme?: 'step' | 'tab';
|
||||
/**
|
||||
* 标题
|
||||
*/
|
||||
title?: string;
|
||||
/**
|
||||
* 选项值
|
||||
*/
|
||||
value?: string | number;
|
||||
/**
|
||||
* 选项值,非受控属性
|
||||
*/
|
||||
defaultValue?: string | number;
|
||||
/**
|
||||
* 是否展示
|
||||
* @default false
|
||||
*/
|
||||
visible?: boolean;
|
||||
/**
|
||||
* 值发生变更时触发
|
||||
*/
|
||||
onChange?: (context: { value: string | number; selectedOptions: string[] }) => void;
|
||||
/**
|
||||
* 关闭时触发
|
||||
*/
|
||||
onClose?: (context: { trigger: CascaderTriggerSource }) => void;
|
||||
/**
|
||||
* 选择后触发
|
||||
*/
|
||||
onPick?: (context: { value: string | number; label: string; index: number; level: number }) => void;
|
||||
}
|
||||
|
||||
export type CascaderKeysType = TreeKeysType;
|
||||
|
||||
export type CascaderTriggerSource = 'overlay' | 'close-btn' | 'finish';
|
||||
Reference in New Issue
Block a user