first commit

This commit is contained in:
lingxiao865
2026-02-10 08:05:03 +08:00
commit c5af079d8c
1094 changed files with 97530 additions and 0 deletions

View File

@@ -0,0 +1,60 @@
:: BASE_DOC ::
## API
### ActionSheet Props
name | type | default | description | required
-- | -- | -- | -- | --
custom-style | Object | - | CSS(Cascading Style Sheets) | N
align | String | center | options: center/left | N
cancel-text | String | - | \- | N
count | Number | 8 | \- | N
description | String | - | \- | N
items | Array | [] | Typescript: `Array<string \| ActionSheetItem>` `interface ActionSheetItem { label: string; description?: string; color?: string; disabled?: boolean; icon?: string; suffixIcon?: string }`。[see more ts definition](https://github.com/tencent/tdesign-miniprogram/blob/develop/packages/uniapp-components/action-sheet/type.ts) | N
popup-props | Object | {} | Typescript: `PopupProps`[Popup API Documents](./popup?tab=api)。[see more ts definition](https://github.com/tencent/tdesign-miniprogram/blob/develop/packages/uniapp-components/action-sheet/type.ts) | N
show-cancel | Boolean | true | \- | N
show-overlay | Boolean | true | \- | N
theme | String | list | options: list/grid | N
using-custom-navbar | Boolean | false | \- | N
visible | Boolean | false | `v-model:visible` is supported | N
default-visible | Boolean | false | uncontrolled property | N
### ActionSheet Events
name | params | description
-- | -- | --
cancel | \- | \-
close | `(context: { trigger: ActionSheetTriggerSource })` | [see more ts definition](https://github.com/tencent/tdesign-miniprogram/blob/develop/packages/uniapp-components/action-sheet/type.ts)。<br/>`type ActionSheetTriggerSource = 'overlay' \| 'command' \| 'select' `<br/>
selected | `(context: { selected: ActionSheetItem \| string, index: number })` | \-
### ActionSheet Slots
name | Description
-- | --
\- | \-
### ActionSheet External Classes
className | Description
-- | --
t-class | \-
t-class-cancel | \-
t-class-content | \-
### CSS Variables
The component provides the following CSS variables, which can be used to customize styles.
Name | Default Value | Description
-- | -- | --
--td-action-sheet-border-color | @component-stroke | -
--td-action-sheet-border-radius | @radius-extraLarge | -
--td-action-sheet-cancel-color | @text-color-primary | -
--td-action-sheet-color | @text-color-primary | -
--td-action-sheet-description-color | @text-color-placeholder | -
--td-action-sheet-description-font | @font-body-medium | -
--td-action-sheet-disabled-color | @text-color-disabled | -
--td-action-sheet-dot-active-color | @brand-color | -
--td-action-sheet-dot-color | @text-color-disabled | -
--td-action-sheet-dot-size | 16rpx | -
--td-action-sheet-gap-color | @bg-color-page | -

View File

@@ -0,0 +1,131 @@
---
title: ActionSheet 动作面板
description: 由用户操作后触发的一种特定的模态弹出框 ,呈现一组与当前情境相关的两个或多个选项。
spline: data
isComponent: true
---
## 引入
可在 `main.ts` 或在需要使用的页面或组件中引入。
```js
import TActionSheet from '@tdesign/uniapp/action-sheet/action-sheet.vue';
```
### 组件类型
列表型动作面板
{{ list }}
宫格型动作面板
{{ grid }}
### 组件状态
宫格型动作面板
{{ status }}
### 组件样式
列表型对齐方式
{{ align }}
### 支持指令调用
```javascript
import ActionSheet, { ActionSheetTheme } from '@tdesign/uniapp/action-sheet/index';
// 指令调用不同于组件引用不需要传入visible
const basicListOption: ActionSheetShowOption = {
theme: ActionSheetTheme.List,
selector: '#t-action-sheet',
items: [
{
label: '默认选项',
},
{
label: '失效选项',
disabled: true,
},
{
label: '警告选项',
color: '#e34d59',
},
],
};
const handler = ActionSheet.show(basicListOption);
```
指令调用的关闭如下
```javascript
handler.close();
```
## API
### ActionSheet Props
名称 | 类型 | 默认值 | 描述 | 必传
-- | -- | -- | -- | --
custom-style | Object | - | 自定义样式 | N
align | String | center | 水平对齐方式。可选项center/left | N
cancel-text | String | - | 设置取消按钮的文本 | N
count | Number | 8 | 设置每页展示菜单的数量,仅当 type=grid 时有效 | N
description | String | - | 动作面板描述文字 | N
items | Array | [] | 菜单项。TS 类型:`Array<string \| ActionSheetItem>` `interface ActionSheetItem { label: string; description?: string; color?: string; disabled?: boolean; icon?: string; suffixIcon?: string }`。[详细类型定义](https://github.com/tencent/tdesign-miniprogram/blob/develop/packages/uniapp-components/action-sheet/type.ts) | N
popup-props | Object | {} | 透传 Popup 组件全部属性。TS 类型:`PopupProps`[Popup API Documents](./popup?tab=api)。[详细类型定义](https://github.com/tencent/tdesign-miniprogram/blob/develop/packages/uniapp-components/action-sheet/type.ts) | N
show-cancel | Boolean | true | 是否显示取消按钮 | N
show-overlay | Boolean | true | 是否显示遮罩层 | N
theme | String | list | 展示类型列表和表格形式展示。可选项list/grid | N
using-custom-navbar | Boolean | false | 是否使用了自定义导航栏 | N
visible | Boolean | false | 显示与隐藏。支持语法糖 `v-model:visible` | N
default-visible | Boolean | false | 显示与隐藏。非受控属性 | N
### ActionSheet Events
名称 | 参数 | 描述
-- | -- | --
cancel | \- | 点击取消按钮时触发
close | `(context: { trigger: ActionSheetTriggerSource })` | 关闭时触发。[详细类型定义](https://github.com/tencent/tdesign-miniprogram/blob/develop/packages/uniapp-components/action-sheet/type.ts)。<br/>`type ActionSheetTriggerSource = 'overlay' \| 'command' \| 'select' `<br/>
selected | `(context: { selected: ActionSheetItem \| string, index: number })` | 选择菜单项时触发
### ActionSheet Slots
名称 | 描述
-- | --
\- | 默认插槽,自定义内容区域内容
### ActionSheet External Classes
类名 | 描述
-- | --
t-class | 根节点样式类
t-class-cancel | 取消样式类
t-class-content | 内容样式类
### CSS Variables
组件提供了下列 CSS 变量,可用于自定义样式。
名称 | 默认值 | 描述
-- | -- | --
--td-action-sheet-border-color | @component-stroke | -
--td-action-sheet-border-radius | @radius-extraLarge | -
--td-action-sheet-cancel-color | @text-color-primary | -
--td-action-sheet-color | @text-color-primary | -
--td-action-sheet-description-color | @text-color-placeholder | -
--td-action-sheet-description-font | @font-body-medium | -
--td-action-sheet-disabled-color | @text-color-disabled | -
--td-action-sheet-dot-active-color | @brand-color | -
--td-action-sheet-dot-color | @text-color-disabled | -
--td-action-sheet-dot-size | 16rpx | -
--td-action-sheet-gap-color | @bg-color-page | -

View File

@@ -0,0 +1,162 @@
.t-action-sheet__content {
color: var(--td-action-sheet-color, var(--td-text-color-primary, var(--td-font-gray-1, rgba(0, 0, 0, 0.9))));
border-top-left-radius: var(--td-action-sheet-border-radius, var(--td-radius-extraLarge, 24rpx));
border-top-right-radius: var(--td-action-sheet-border-radius, var(--td-radius-extraLarge, 24rpx));
background-color: var(--td-bg-color-container, var(--td-font-white-1, #ffffff));
overflow: hidden;
}
.t-action-sheet__content:focus {
outline: 0;
}
.t-action-sheet--grid {
padding-top: var(--td-spacer, 16rpx);
}
.t-action-sheet--grid .t-action-sheet__description::after {
display: none;
}
.t-action-sheet--left .t-action-sheet__description {
text-align: left;
}
.t-action-sheet--left .t-action-sheet__list-item-content,
.t-action-sheet--left .t-action-sheet__list-item-desc {
justify-content: start;
}
.t-action-sheet--left .t-action-sheet__list-item-icon--suffix {
margin-left: auto;
}
.t-action-sheet__grid {
padding-bottom: 16rpx;
}
.t-action-sheet__grid--swiper {
padding-bottom: 48rpx;
}
.t-action-sheet__description {
color: var(--td-action-sheet-description-color, var(--td-text-color-placeholder, var(--td-font-gray-3, rgba(0, 0, 0, 0.4))));
font: var(--td-action-sheet-description-font, var(--td-font-body-medium, 28rpx / 44rpx var(--td-font-family, PingFang SC, Microsoft YaHei, Arial Regular)));
text-align: center;
padding: var(--td-spacer-1, 24rpx) var(--td-spacer-2, 32rpx);
position: relative;
}
.t-action-sheet__description:focus {
outline: 0;
}
.t-action-sheet__description::after {
content: '';
display: block;
position: absolute;
top: unset;
bottom: 0;
left: unset;
right: unset;
background-color: var(--td-action-sheet-border-color, var(--td-component-stroke, var(--td-gray-color-3, #e7e7e7)));
}
.t-action-sheet__description::after {
height: 1px;
left: 0;
right: 0;
transform: scaleY(0.5);
}
.t-action-sheet__list-item {
flex-direction: column;
padding: var(--td-spacer-2, 32rpx);
display: flex;
align-items: center;
justify-content: center;
}
.t-action-sheet__list-item:not(:last-child) {
position: relative;
}
.t-action-sheet__list-item:not(:last-child)::after {
content: '';
display: block;
position: absolute;
top: unset;
bottom: 0;
left: unset;
right: unset;
background-color: var(--td-action-sheet-border-color, var(--td-component-stroke, var(--td-gray-color-3, #e7e7e7)));
}
.t-action-sheet__list-item:not(:last-child)::after {
height: 1px;
left: 0;
right: 0;
transform: scaleY(0.5);
}
.t-action-sheet__list-item:focus {
outline: 0;
}
.t-action-sheet__list-item--disabled {
color: var(--td-action-sheet-disabled-color, var(--td-text-color-disabled, var(--td-font-gray-4, rgba(0, 0, 0, 0.26))));
}
.t-action-sheet__list-item-content,
.t-action-sheet__list-item-desc {
width: 100%;
display: flex;
align-items: center;
justify-content: center;
}
.t-action-sheet__list-item-text {
font: var(--td-font-body-large, 32rpx / 48rpx var(--td-font-family, PingFang SC, Microsoft YaHei, Arial Regular));
}
.t-action-sheet__list-item-desc {
font: var(--td-font-body-small, 24rpx / 40rpx var(--td-font-family, PingFang SC, Microsoft YaHei, Arial Regular));
color: var(--td-action-sheet-description-color, var(--td-text-color-placeholder, var(--td-font-gray-3, rgba(0, 0, 0, 0.4))));
margin-top: var(--td-spacer, 16rpx);
}
.t-action-sheet__list-item-text,
.t-action-sheet__list-item-desc {
word-wrap: normal;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.t-action-sheet__list-item-icon {
font-size: 48rpx;
margin-right: var(--td-spacer, 16rpx);
}
.t-action-sheet__list-item-icon--suffix {
margin-right: 0;
margin-left: var(--td-spacer, 16rpx);
}
.t-action-sheet__swiper-wrap {
margin-top: 8rpx;
position: relative;
}
.t-action-sheet__footer {
background-color: var(--td-bg-color-container, var(--td-font-white-1, #ffffff));
}
.t-action-sheet__gap-list {
height: 16rpx;
background-color: var(--td-action-sheet-gap-color, var(--td-bg-color-page, var(--td-gray-color-1, #f3f3f3)));
}
.t-action-sheet__gap-grid {
height: 1rpx;
background-color: var(--td-action-sheet-border-color, var(--td-component-stroke, var(--td-gray-color-3, #e7e7e7)));
}
.t-action-sheet__cancel {
padding: var(--td-spacer-1, 24rpx) var(--td-spacer-2, 32rpx);
font: var(--td-font-body-large, 32rpx / 48rpx var(--td-font-family, PingFang SC, Microsoft YaHei, Arial Regular));
color: var(--td-action-sheet-cancel-color, var(--td-text-color-primary, var(--td-font-gray-1, rgba(0, 0, 0, 0.9))));
display: flex;
align-items: center;
justify-content: center;
}
.t-action-sheet__dots {
position: absolute;
left: 50%;
bottom: var(--td-spacer-2, 32rpx);
transform: translateX(-50%);
display: flex;
flex-direction: row;
}
.t-action-sheet__dots-item {
width: var(--td-action-sheet-dot-size, 16rpx);
height: var(--td-action-sheet-dot-size, 16rpx);
background-color: var(--td-action-sheet-dot-color, var(--td-text-color-disabled, var(--td-font-gray-4, rgba(0, 0, 0, 0.26))));
border-radius: 50%;
margin: 0 var(--td-spacer, 16rpx);
transition: all 0.4s ease-in;
}
.t-action-sheet__dots-item.t-is-active {
background-color: var(--td-action-sheet-dot-active-color, var(--td-brand-color, var(--td-primary-color-7, #0052d9)));
}

View File

@@ -0,0 +1,350 @@
<template>
<view
:id="classPrefix"
:style="tools._style([rootCustomStyle])"
:class="tools.cls(classPrefix, [dataAlign, dataTheme, ['no-description', !description]]) + ' ' + tClass"
>
<t-popup
:visible="dataVisible"
placement="bottom"
:using-custom-navbar="dataUsingCustomNavbar"
:custom-navbar-height="customNavbarHeight"
:show-overlay="dataShowOverlay"
:z-index="(dataPopupProps && dataPopupProps.zIndex) || defaultPopUpzIndex"
:overlay-props="(dataPopupProps && dataPopupProps.overlayProps) || defaultPopUpProps"
@visible-change="onPopupVisibleChange"
>
<view
:class="classPrefix + '__content ' + tClassContent"
tabindex="0"
>
<view
v-if="dataDescription"
tabindex="0"
:class="classPrefix + '__description'"
>
{{ dataDescription }}
</view>
<block v-if="gridThemeItems.length">
<block v-if="gridThemeItems.length === 1">
<t-grid
align="center"
:t-class="classPrefix + '__grid'"
:column="dataCount / 2"
:class="classPrefix + '__single-wrap'"
>
<t-grid-item
v-for="(item, index) in gridThemeItems[0]"
:key="index"
:t-class="classPrefix + '__grid-item ' + classPrefix + '__square'"
:data-index="index"
:icon="{ name: item.icon, color: item.color }"
:text="item.label || ''"
:description="item.description || ''"
:image="item.image || ''"
:style="'--td-grid-item-text-color: ' + item.color"
@click="onSelect($event, { index })"
/>
</t-grid>
</block>
<block v-else-if="gridThemeItems.length > 1">
<view :class="classPrefix + '__swiper-wrap'">
<swiper
:style="heightStyle"
:autoplay="false"
:current="currentSwiperIndex"
@change="onSwiperChange"
>
<swiper-item
v-for="(item, index) in gridThemeItems"
:key="index"
>
<t-grid
align="center"
:t-class="classPrefix + '__grid ' + classPrefix + '__grid--swiper'"
:column="dataCount / 2"
:custom-style="gridStyle"
>
<t-grid-item
v-for="(item, index1) in item"
:key="index1"
:t-class="classPrefix + '__grid-item'"
:class="classPrefix + '__square'"
:data-index="index"
:icon="{ name: item.icon, color: item.color }"
:text="item.label || ''"
:description="item.description || ''"
:image="item.image || ''"
:style="'--td-grid-item-text-color: ' + item.color"
@click="onSelect($event, { index })"
/>
</t-grid>
</swiper-item>
</swiper>
<view :class="classPrefix + '__nav'">
<view :class="classPrefix + '__dots'">
<view
v-for="(item, index) in gridThemeItems.length"
:key="index"
:class="classPrefix + '__dots-item ' + (index === currentSwiperIndex ? prefix + '-is-active' : '')"
/>
</view>
</view>
</view>
</block>
</block>
<view
v-else-if="dataItems && dataItems.length"
:class="classPrefix + '__list'"
>
<block
v-for="(item, index) in dataItems"
:key="index"
>
<view
:data-index="index"
:style="item.color ? 'color: ' + item.color : ''"
:class="tools.cls(classPrefix + '__list-item', [['disabled', item.disabled]])"
:aria-role="ariaRole || 'button'"
:aria-label="item.label || item"
tabindex="0"
@click="() => onSelect(item, { index })"
>
<view :class="classPrefix + '__list-item-content'">
<t-icon
v-if="item.icon"
:name="item.icon"
:t-class="classPrefix + '__list-item-icon'"
:custom-style="iconCustomStyle"
/>
<view :class="classPrefix + '__list-item-text'">
{{ item.label || item }}
</view>
<t-icon
v-if="item.suffixIcon"
:name="item.suffixIcon"
:t-class="classPrefix + '__list-item-icon ' + classPrefix + '__list-item-icon--suffix'"
style="margin-left: auto;"
:custom-style="suffixIconCustomStyle"
/>
</view>
<view
v-if="item.description"
:class="classPrefix + '__list-item-desc'"
>
{{ item.description }}
</view>
</view>
</block>
</view>
</view>
<slot />
<view
v-if="dataShowCancel"
:class="classPrefix + '__footer'"
>
<view :class="classPrefix + '__gap-' + dataTheme" />
<view
:class="classPrefix + '__cancel ' + tClassCancel"
:hover-class="classPrefix + '__cancel--hover'"
hover-stay-time="70"
aria-role="button"
@click="onCancel"
>
{{ dataCancelText || '取消' }}
</view>
</view>
</t-popup>
</view>
</template>
<script>
import TIcon from '../icon/icon';
import TPopup from '../popup/popup';
import TGrid from '../grid/grid';
import TGridItem from '../grid-item/grid-item';
import { chunk } from '../common/utils';
import { uniComponent } from '../common/src/index';
import { prefix } from '../common/config';
import { actionSheetTheme } from './show';
import props from './props';
import useCustomNavbar from '../mixins/using-custom-navbar';
import tools from '../common/utils.wxs';
import { getFunctionalMixin } from '../common/functional/mixin';
const name = `${prefix}-action-sheet`;
export default uniComponent({
name,
options: {
styleIsolation: 'shared',
},
controlledProps: [{
key: 'visible',
event: 'visible-change',
}],
externalClasses: [
`${prefix}-class`,
`${prefix}-class-content`,
`${prefix}-class-cancel`,
],
mixins: [getFunctionalMixin(props), useCustomNavbar],
components: {
TIcon,
TPopup,
TGrid,
TGridItem,
},
props: {
...props,
},
emits: [
'visible-change',
'update:visible',
],
data() {
return {
prefix,
classPrefix: name,
gridThemeItems: [],
currentSwiperIndex: 0,
defaultPopUpProps: {},
defaultPopUpzIndex: 11500,
tools,
heightStyle: 'height: 456rpx;',
};
},
computed: {
rootCustomStyle() {
return tools._style([
this.customStyle,
this.dataTheme === 'grid' ? 'padding-bottom: 16rpx' : '',
]);
},
iconCustomStyle() {
return 'margin-right: 8px;';
},
suffixIconCustomStyle() {
return 'margin-right: 8px;margin-left: auto;';
},
gridStyle() {
return `${this.heightStyle}padding-bottom: 48rpx;`;
},
},
watch: {
dataVisible: {
handler(e) {
if (e) {
this.init();
}
},
immediate: true,
},
dataItems: {
handler() {
if (this.dataVisible) {
this.init();
}
},
immediate: true,
},
},
methods: {
init() {
this.memoInitialData();
this.splitGridThemeActions();
},
memoInitialData() {
this.initialData = {
};
},
splitGridThemeActions() {
if (this.dataTheme !== actionSheetTheme.Grid) return;
this.gridThemeItems = chunk(this.dataItems, this.dataCount);
},
/** 指令调用显示 */
show(options) {
const defaultOptions = [
'align',
'cancelText',
'count',
'description',
'items',
'popupProps',
'showCancel',
'showOverlay',
'theme',
'usingCustomNavbar',
].reduce((acc, key) => ({
...acc,
[key]: props[key].default,
}));
this.setData({
...defaultOptions,
...options,
visible: true,
});
this.splitGridThemeActions();
this.autoClose = true;
this._trigger('visible-change', { visible: true });
},
/** 指令调用隐藏 */
close() {
this.$emit('close', { trigger: 'command' });
this._trigger('visible-change', { visible: false });
},
/** 默认点击遮罩关闭 */
onPopupVisibleChange(detail) {
if (!detail.visible) {
this.$emit('close', { trigger: 'overlay' });
this._trigger('visible-change', { visible: false });
}
if (this.autoClose) {
this.dataVisible = false;
this.autoClose = false;
}
},
onSwiperChange(e) {
const { current } = e.detail;
this.currentSwiperIndex = current;
},
onSelect(event, { index }) {
const { currentSwiperIndex, dataItems, gridThemeItems, dataCount, dataTheme } = this;
const isSwiperMode = dataTheme === actionSheetTheme.Grid;
const item = isSwiperMode ? gridThemeItems[currentSwiperIndex][index] : dataItems[index];
const realIndex = isSwiperMode ? index + currentSwiperIndex * dataCount : index;
if (item) {
this.$emit('selected', { selected: item, index: realIndex });
if (!item.disabled) {
this.$emit('close', { trigger: 'select' });
this._trigger('visible-change', { visible: false });
}
}
},
onCancel() {
this.$emit('cancel');
if (this.autoClose) {
this.dataVisible = false;
this.autoClose = false;
}
},
},
});
</script>
<style scoped>
@import './action-sheet.css';
</style>

View File

@@ -0,0 +1,15 @@
export const getListThemeItemClass = function (props) {
const { classPrefix } = props;
const { item } = props;
const { prefix } = props;
const classList = [`${classPrefix}__list-item`];
if (item.disabled) {
classList.push(`${prefix}-is-disabled`);
}
return classList.join(' ');
};
export const isImage = function (name) {
return name.indexOf('/') !== -1;
};

View File

@@ -0,0 +1,11 @@
import type { ActionSheetItem, ActionSheetTheme, ActionSheetShowOption } from './show.d.ts';
export { ActionSheetItem, ActionSheetTheme, ActionSheetShowOption };
declare type Instance = any;
declare const Handler: {
show(options: ActionSheetShowOption): Instance;
close(options: ActionSheetShowOption): void;
};
export default Handler;

View File

@@ -0,0 +1,13 @@
import { show, close, ActionSheetTheme, actionSheetTheme } from './show';
export { ActionSheetTheme, actionSheetTheme };
export default {
show(options) {
return show(options);
},
close(options) {
return close(options);
},
};

View File

@@ -0,0 +1,86 @@
/* eslint-disable */
/**
* 该文件为脚本自动生成文件,请勿随意修改。如需修改请联系 PMC
* */
import type { TdActionSheetProps } from './type';
export default {
/** 水平对齐方式 */
align: {
type: String,
default: 'center' as TdActionSheetProps['align'],
validator(val: TdActionSheetProps['align']): boolean {
if (!val) return true;
return ['center', 'left'].includes(val);
},
},
/** 设置取消按钮的文本 */
cancelText: {
type: String,
default: '',
},
/** 设置每页展示菜单的数量,仅当 type=grid 时有效 */
count: {
type: Number,
default: 8,
},
/** 动作面板描述文字 */
description: {
type: String,
default: '',
},
/** 菜单项 */
items: {
type: Array,
default: (): TdActionSheetProps['items'] => [],
},
/** 透传 Popup 组件全部属性 */
popupProps: {
type: Object,
default: () => ({}),
},
/** 是否显示取消按钮 */
showCancel: {
type: Boolean,
default: true,
},
/** 是否显示遮罩层 */
showOverlay: {
type: Boolean,
default: true,
},
/** 展示类型,列表和表格形式展示 */
theme: {
type: String,
default: 'list' as TdActionSheetProps['theme'],
validator(val: TdActionSheetProps['theme']): boolean {
if (!val) return true;
return ['list', 'grid'].includes(val);
},
},
/** 是否使用了自定义导航栏 */
usingCustomNavbar: Boolean,
/** 显示与隐藏 */
visible: {
type: Boolean,
default: undefined,
},
/** 显示与隐藏,非受控属性 */
defaultVisible: Boolean,
/** 点击取消按钮时触发 */
onCancel: {
type: Function,
default: () => ({}),
},
/** 关闭时触发 */
onClose: {
type: Function,
default: () => ({}),
},
/** 选择菜单项时触发 */
onSelected: {
type: Function,
default: () => ({}),
},
};

View File

@@ -0,0 +1,28 @@
import { ActionSheetItem } from './type';
export { ActionSheetItem };
declare type Instance = any;
declare type Context = any;
export declare enum ActionSheetTheme {
List = 'list',
Grid = 'grid'
}
interface ActionSheetProps {
align: 'center' | 'left';
cancelText?: string;
count?: number;
description: string;
items: Array<string | ActionSheetItem>;
showCancel?: boolean;
theme?: ActionSheetTheme;
visible: boolean;
defaultVisible?: boolean;
}
export interface ActionSheetShowOption extends Omit<ActionSheetProps, 'visible'> {
context?: Context;
selector?: string;
}
export declare const show: (options: ActionSheetShowOption) => Instance;
export declare const close: (options: ActionSheetShowOption) => void;

View File

@@ -0,0 +1,33 @@
import { getInstance } from '../common/utils';
export const ActionSheetTheme = {
List: 'list',
Grid: 'grid',
};
export const actionSheetTheme = {
List: ActionSheetTheme.List,
Grid: ActionSheetTheme.Grid,
} ;
export const show = function (options) {
const { context, selector = '#t-action-sheet', ...otherOptions } = { ...options };
const instance = getInstance(context, selector);
if (instance) {
instance.show({
...otherOptions,
});
return instance;
}
console.error('未找到组件,请确认 selector && context 是否正确');
};
export const close = function (options) {
const { context, selector = '#t-action-sheet' } = { ...options };
const instance = getInstance(context, selector);
if (instance) {
instance.close();
}
};

View File

@@ -0,0 +1,93 @@
/* eslint-disable */
/**
* 该文件为脚本自动生成文件,请勿随意修改。如需修改请联系 PMC
* */
import type { TdPopupProps as PopupProps } from '../popup/type';
export interface TdActionSheetProps {
/**
* 水平对齐方式
* @default center
*/
align?: 'center' | 'left';
/**
* 设置取消按钮的文本
* @default ''
*/
cancelText?: string;
/**
* 设置每页展示菜单的数量,仅当 type=grid 时有效
* @default 8
*/
count?: number;
/**
* 动作面板描述文字
* @default ''
*/
description?: string;
/**
* 菜单项
* @default []
*/
items?: Array<string | ActionSheetItem>;
/**
* 透传 Popup 组件全部属性
* @default {}
*/
popupProps?: PopupProps;
/**
* 是否显示取消按钮
* @default true
*/
showCancel?: boolean;
/**
* 是否显示遮罩层
* @default true
*/
showOverlay?: boolean;
/**
* 展示类型,列表和表格形式展示
* @default list
*/
theme?: 'list' | 'grid';
/**
* 是否使用了自定义导航栏
* @default false
*/
usingCustomNavbar?: boolean;
/**
* 显示与隐藏
* @default false
*/
visible?: boolean;
/**
* 显示与隐藏,非受控属性
* @default false
*/
defaultVisible?: boolean;
/**
* 点击取消按钮时触发
*/
onCancel?: () => void;
/**
* 关闭时触发
*/
onClose?: (context: { trigger: ActionSheetTriggerSource }) => void;
/**
* 选择菜单项时触发
*/
onSelected?: (context: { selected: ActionSheetItem | string; index: number }) => void;
}
export interface ActionSheetItem {
label: string;
description?: string;
color?: string;
disabled?: boolean;
icon?: string;
suffixIcon?: string;
}
export type ActionSheetTriggerSource = 'overlay' | 'command' | 'select';

View File

@@ -0,0 +1,187 @@
.t-avatar-group {
display: inline-flex;
flex-wrap: wrap;
align-items: center;
}
.t-avatar-group-offset-left .t-avatar__wrapper,
.t-avatar-group-offset-right .t-avatar__wrapper {
padding: var(--td-avatar-group-line-spacing, 4rpx) 0;
}
.t-avatar-group-offset-left-small,
.t-avatar-group-offset-right-small {
--td-avatar-margin-left: var(--td-avatar-group-margin-left-small, -16rpx);
}
.t-avatar-group-offset-left-medium,
.t-avatar-group-offset-right-medium {
--td-avatar-margin-left: var(--td-avatar-group-margin-left-medium, -16rpx);
}
.t-avatar-group-offset-left-large,
.t-avatar-group-offset-right-large {
--td-avatar-margin-left: var(--td-avatar-group-margin-left-large, -16rpx);
}
.t-avatar-group-offset-left .t-avatar__wrapper:nth-child(1) {
z-index: calc(var(--td-avatar-group-init-z-index, 50) - 1);
}
.t-avatar-group-offset-left .t-avatar__wrapper:nth-child(2) {
z-index: calc(var(--td-avatar-group-init-z-index, 50) - 2);
}
.t-avatar-group-offset-left .t-avatar__wrapper:nth-child(3) {
z-index: calc(var(--td-avatar-group-init-z-index, 50) - 3);
}
.t-avatar-group-offset-left .t-avatar__wrapper:nth-child(4) {
z-index: calc(var(--td-avatar-group-init-z-index, 50) - 4);
}
.t-avatar-group-offset-left .t-avatar__wrapper:nth-child(5) {
z-index: calc(var(--td-avatar-group-init-z-index, 50) - 5);
}
.t-avatar-group-offset-left .t-avatar__wrapper:nth-child(6) {
z-index: calc(var(--td-avatar-group-init-z-index, 50) - 6);
}
.t-avatar-group-offset-left .t-avatar__wrapper:nth-child(7) {
z-index: calc(var(--td-avatar-group-init-z-index, 50) - 7);
}
.t-avatar-group-offset-left .t-avatar__wrapper:nth-child(8) {
z-index: calc(var(--td-avatar-group-init-z-index, 50) - 8);
}
.t-avatar-group-offset-left .t-avatar__wrapper:nth-child(9) {
z-index: calc(var(--td-avatar-group-init-z-index, 50) - 9);
}
.t-avatar-group-offset-left .t-avatar__wrapper:nth-child(10) {
z-index: calc(var(--td-avatar-group-init-z-index, 50) - 10);
}
.t-avatar-group-offset-left .t-avatar__wrapper:nth-child(11) {
z-index: calc(var(--td-avatar-group-init-z-index, 50) - 11);
}
.t-avatar-group-offset-left .t-avatar__wrapper:nth-child(12) {
z-index: calc(var(--td-avatar-group-init-z-index, 50) - 12);
}
.t-avatar-group-offset-left .t-avatar__wrapper:nth-child(13) {
z-index: calc(var(--td-avatar-group-init-z-index, 50) - 13);
}
.t-avatar-group-offset-left .t-avatar__wrapper:nth-child(14) {
z-index: calc(var(--td-avatar-group-init-z-index, 50) - 14);
}
.t-avatar-group-offset-left .t-avatar__wrapper:nth-child(15) {
z-index: calc(var(--td-avatar-group-init-z-index, 50) - 15);
}
.t-avatar-group-offset-left .t-avatar__wrapper:nth-child(16) {
z-index: calc(var(--td-avatar-group-init-z-index, 50) - 16);
}
.t-avatar-group-offset-left .t-avatar__wrapper:nth-child(17) {
z-index: calc(var(--td-avatar-group-init-z-index, 50) - 17);
}
.t-avatar-group-offset-left .t-avatar__wrapper:nth-child(18) {
z-index: calc(var(--td-avatar-group-init-z-index, 50) - 18);
}
.t-avatar-group-offset-left .t-avatar__wrapper:nth-child(19) {
z-index: calc(var(--td-avatar-group-init-z-index, 50) - 19);
}
.t-avatar-group-offset-left .t-avatar__wrapper:nth-child(20) {
z-index: calc(var(--td-avatar-group-init-z-index, 50) - 20);
}
.t-avatar-group-offset-left .t-avatar__wrapper:nth-child(21) {
z-index: calc(var(--td-avatar-group-init-z-index, 50) - 21);
}
.t-avatar-group-offset-left .t-avatar__wrapper:nth-child(22) {
z-index: calc(var(--td-avatar-group-init-z-index, 50) - 22);
}
.t-avatar-group-offset-left .t-avatar__wrapper:nth-child(23) {
z-index: calc(var(--td-avatar-group-init-z-index, 50) - 23);
}
.t-avatar-group-offset-left .t-avatar__wrapper:nth-child(24) {
z-index: calc(var(--td-avatar-group-init-z-index, 50) - 24);
}
.t-avatar-group-offset-left .t-avatar__wrapper:nth-child(25) {
z-index: calc(var(--td-avatar-group-init-z-index, 50) - 25);
}
.t-avatar-group-offset-left .t-avatar__wrapper:nth-child(26) {
z-index: calc(var(--td-avatar-group-init-z-index, 50) - 26);
}
.t-avatar-group-offset-left .t-avatar__wrapper:nth-child(27) {
z-index: calc(var(--td-avatar-group-init-z-index, 50) - 27);
}
.t-avatar-group-offset-left .t-avatar__wrapper:nth-child(28) {
z-index: calc(var(--td-avatar-group-init-z-index, 50) - 28);
}
.t-avatar-group-offset-left .t-avatar__wrapper:nth-child(29) {
z-index: calc(var(--td-avatar-group-init-z-index, 50) - 29);
}
.t-avatar-group-offset-left .t-avatar__wrapper:nth-child(30) {
z-index: calc(var(--td-avatar-group-init-z-index, 50) - 30);
}
.t-avatar-group-offset-left .t-avatar__wrapper:nth-child(31) {
z-index: calc(var(--td-avatar-group-init-z-index, 50) - 31);
}
.t-avatar-group-offset-left .t-avatar__wrapper:nth-child(32) {
z-index: calc(var(--td-avatar-group-init-z-index, 50) - 32);
}
.t-avatar-group-offset-left .t-avatar__wrapper:nth-child(33) {
z-index: calc(var(--td-avatar-group-init-z-index, 50) - 33);
}
.t-avatar-group-offset-left .t-avatar__wrapper:nth-child(34) {
z-index: calc(var(--td-avatar-group-init-z-index, 50) - 34);
}
.t-avatar-group-offset-left .t-avatar__wrapper:nth-child(35) {
z-index: calc(var(--td-avatar-group-init-z-index, 50) - 35);
}
.t-avatar-group-offset-left .t-avatar__wrapper:nth-child(36) {
z-index: calc(var(--td-avatar-group-init-z-index, 50) - 36);
}
.t-avatar-group-offset-left .t-avatar__wrapper:nth-child(37) {
z-index: calc(var(--td-avatar-group-init-z-index, 50) - 37);
}
.t-avatar-group-offset-left .t-avatar__wrapper:nth-child(38) {
z-index: calc(var(--td-avatar-group-init-z-index, 50) - 38);
}
.t-avatar-group-offset-left .t-avatar__wrapper:nth-child(39) {
z-index: calc(var(--td-avatar-group-init-z-index, 50) - 39);
}
.t-avatar-group-offset-left .t-avatar__wrapper:nth-child(40) {
z-index: calc(var(--td-avatar-group-init-z-index, 50) - 40);
}
.t-avatar-group-offset-left .t-avatar__wrapper:nth-child(41) {
z-index: calc(var(--td-avatar-group-init-z-index, 50) - 41);
}
.t-avatar-group-offset-left .t-avatar__wrapper:nth-child(42) {
z-index: calc(var(--td-avatar-group-init-z-index, 50) - 42);
}
.t-avatar-group-offset-left .t-avatar__wrapper:nth-child(43) {
z-index: calc(var(--td-avatar-group-init-z-index, 50) - 43);
}
.t-avatar-group-offset-left .t-avatar__wrapper:nth-child(44) {
z-index: calc(var(--td-avatar-group-init-z-index, 50) - 44);
}
.t-avatar-group-offset-left .t-avatar__wrapper:nth-child(45) {
z-index: calc(var(--td-avatar-group-init-z-index, 50) - 45);
}
.t-avatar-group-offset-left .t-avatar__wrapper:nth-child(46) {
z-index: calc(var(--td-avatar-group-init-z-index, 50) - 46);
}
.t-avatar-group-offset-left .t-avatar__wrapper:nth-child(47) {
z-index: calc(var(--td-avatar-group-init-z-index, 50) - 47);
}
.t-avatar-group-offset-left .t-avatar__wrapper:nth-child(48) {
z-index: calc(var(--td-avatar-group-init-z-index, 50) - 48);
}
.t-avatar-group-offset-left .t-avatar__wrapper:nth-child(49) {
z-index: calc(var(--td-avatar-group-init-z-index, 50) - 49);
}
.t-avatar-group-offset-left .t-avatar__wrapper:nth-child(50) {
z-index: calc(var(--td-avatar-group-init-z-index, 50) - 50);
}
.t-avatar-group__collapse--slot,
.t-avatar-group__collapse--default {
z-index: 0;
font-weight: 600;
}
.t-avatar-group__collapse--slot {
float: left;
}
.t-avatar-group__collapse--slot:not(:empty) + .t-avatar-group__collapse--default {
display: none;
float: left;
}
.t-avatar-group__collapse--slot:empty + .t-avatar-group__collapse--default {
display: block;
float: left;
}

View File

@@ -0,0 +1,121 @@
<template>
<view
:style="tools._style([customStyle])"
:class="className"
>
<slot />
<view :class="classPrefix + '__collapse--slot'">
<slot name="collapse-avatar" />
</view>
<view
v-if="max && max < length"
:class="classPrefix + '__collapse--default'"
@click="onCollapsedItemClick"
>
<t-avatar
:t-class-image="prefix + '-avatar--border ' + prefix + '-avatar--border-' + size + ' ' + tClassImage"
:t-class-content="tClassContent"
:size="size"
:shape="shape"
:icon="collapseAvatar ? '' : 'user-add'"
aria-role="none"
>
{{ collapseAvatar }}
</t-avatar>
</view>
</view>
</template>
<script>
import TAvatar from '../avatar/avatar';
import { uniComponent } from '../common/src/index';
import { prefix } from '../common/config';
import avatarGroupProps from './props';
import tools from '../common/utils.wxs';
import { ParentMixin, RELATION_MAP } from '../common/relation';
const name = `${prefix}-avatar-group`;
const AVATAR_GROUP_INIT_Z_INDEX = 50;
export default uniComponent({
name,
options: {
styleIsolation: 'shared',
},
externalClasses: [
`${prefix}-class`,
`${prefix}-class-content`,
`${prefix}-class-image`,
],
mixins: [ParentMixin(RELATION_MAP.Avatar)],
components: {
TAvatar,
},
props: {
...avatarGroupProps,
},
data() {
return {
prefix,
classPrefix: name,
hasChild: true,
length: 0,
className: '',
tools,
};
},
watch: {
cascading: 'setClass',
size: 'setClass',
},
mounted() {
this.setClass();
this.length = this.children?.length || 0;
if (this.length) {
this.handleMax();
}
},
methods: {
setClass() {
const { cascading, size } = this;
const direction = cascading.split('-')[0];
const classList = [
name,
this.tClass,
`${name}-offset-${direction}`,
`${name}-offset-${direction}-${size.indexOf('px') > -1 ? 'medium' : size || 'medium'}`,
];
this.className = classList.join(' ');
},
handleMax() {
const { max, cascading } = this;
const len = this.children.length;
if (!max || max > len) return;
const restAvatars = this.children.splice(max, len - max);
const isLeft = cascading === 'left-up';
this.children.forEach((child, index) => {
child.setStyle({
zIndex: isLeft && `calc(var(--td-avatar-group-init-z-index, ${AVATAR_GROUP_INIT_Z_INDEX}) - ${index})`,
padding: 'var(--td-avatar-group-line-spacing, 2px) 0',
});
});
restAvatars.forEach((child) => {
child.hide();
});
},
onCollapsedItemClick(e) {
this.$emit('collapsed-item-click', e);
},
},
});
</script>
<style scoped>
@import './avatar-group.css';
</style>

View File

@@ -0,0 +1,44 @@
/* eslint-disable */
/**
* 该文件为脚本自动生成文件,请勿随意修改。如需修改请联系 PMC
* */
import type { TdAvatarGroupProps } from './type';
export default {
/** 图片之间的层叠关系,可选值:左侧图片在上和右侧图片在上 */
cascading: {
type: String,
default: 'left-up' as TdAvatarGroupProps['cascading'],
validator(val: TdAvatarGroupProps['cascading']): boolean {
if (!val) return true;
return ['left-up', 'right-up'].includes(val);
},
},
/** 头像数量超出时,会出现一个头像折叠元素。该元素内容可自定义。默认为 `+N`。示例:`+5``...`, `更多` */
collapseAvatar: {
type: String,
},
/** 能够同时显示的最多头像数量 */
max: {
type: Number,
},
/** 形状。优先级低于 Avatar.shape */
shape: {
type: String,
validator(val: TdAvatarGroupProps['shape']): boolean {
if (!val) return true;
return ['circle', 'round'].includes(val);
},
},
/** 尺寸示例值small/medium/large/24px/38px 等。优先级低于 Avatar.size */
size: {
type: String,
default: '',
},
/** 点击头像折叠元素触发 */
onCollapsedItemClick: {
type: Function,
default: () => ({}),
},
};

View File

@@ -0,0 +1,38 @@
/* eslint-disable */
/**
* 该文件为脚本自动生成文件,请勿随意修改。如需修改请联系 PMC
* */
import type { ShapeEnum } from '../common/common';
export interface TdAvatarGroupProps {
/**
* 图片之间的层叠关系,可选值:左侧图片在上和右侧图片在上
* @default 'left-up'
*/
cascading?: CascadingValue;
/**
* 头像数量超出时,会出现一个头像折叠元素。该元素内容可自定义。默认为 `+N`。示例:`+5``...`, `更多`
*/
collapseAvatar?: string;
/**
* 能够同时显示的最多头像数量
*/
max?: number;
/**
* 形状。优先级低于 Avatar.shape
*/
shape?: ShapeEnum;
/**
* 尺寸示例值small/medium/large/24px/38px 等。优先级低于 Avatar.size
* @default ''
*/
size?: string;
/**
* 点击头像折叠元素触发
*/
onCollapsedItemClick?: (e: MouseEvent) => void;
}
export type CascadingValue = 'left-up' | 'right-up';

View File

@@ -0,0 +1,102 @@
:: BASE_DOC ::
## API
### Avatar Props
name | type | default | description | required
-- | -- | -- | -- | --
custom-style | Object | - | CSS(Cascading Style Sheets) | N
alt | String | - | show it when url is not valid | N
badge-props | Object | {} | Typescript`BadgeProps`[Badge API Documents](./badge?tab=api)。[see more ts definition](https://github.com/Tencent/tdesign-miniprogram/tree/develop/packages/uniapp-components/avatar/type.ts) | N
bordered | Boolean | false | \- | N
hide-on-load-failed | Boolean | false | hide image when loading image failed | N
icon | String / Object | - | \- | N
image | String | - | images url | N
image-props | Object | - | Typescript`ImageProps`[Image API Documents](./image?tab=api)。[see more ts definition](https://github.com/Tencent/tdesign-miniprogram/tree/develop/packages/uniapp-components/avatar/type.ts) | N
shape | String | - | shape。options: circle/round。Typescript`ShapeEnum`。[see more ts definition](https://github.com/Tencent/tdesign-miniprogram/tree/develop/packages/uniapp-components/common/common.ts) | N
size | String | - | size | N
### Avatar Events
name | params | description
-- | -- | --
error | `(e: Event)` | trigger on image load failed
### Avatar Slots
name | Description
-- | --
\- | \-
### Avatar External Classes
className | Description
-- | --
t-class | \-
t-class-alt | \-
t-class-content | \-
t-class-icon | \-
t-class-image | \-
### AvatarGroup Props
name | type | default | description | required
-- | -- | -- | -- | --
custom-style | Object | - | CSS(Cascading Style Sheets) | N
cascading | String | 'left-up' | multiple images cascading。options: left-up/right-up。Typescript`CascadingValue` `type CascadingValue = 'left-up' \| 'right-up'`。[see more ts definition](https://github.com/Tencent/tdesign-miniprogram/tree/develop/packages/uniapp-components/avatar-group/type.ts) | N
collapse-avatar | String | - | \- | N
max | Number | - | \- | N
shape | String | - | shape。options: circle/round。Typescript`ShapeEnum`。[see more ts definition](https://github.com/Tencent/tdesign-miniprogram/tree/develop/packages/uniapp-components/common/common.ts) | N
size | String | - | size | N
### AvatarGroup Events
name | params | description
-- | -- | --
collapsed-item-click | `(e: MouseEvent)` | \-
### AvatarGroup Slots
name | Description
-- | --
\- | \-
collapse-avatar | \-
### AvatarGroup External Classes
className | Description
-- | --
t-class | \-
t-class-content | \-
t-class-image | \-
### CSS Variables
The component provides the following CSS variables, which can be used to customize styles.
Name | Default Value | Description
-- | -- | --
--td-avatar-group-init-z-index | @avatar-group-init-zIndex | -
--td-avatar-group-line-spacing | 4rpx | -
--td-avatar-group-margin-left-large | -16rpx | -
--td-avatar-group-margin-left-medium | -16rpx | -
--td-avatar-group-margin-left-small | -16rpx | -
--td-avatar-bg-color | @brand-color-light-active | -
--td-avatar-border-color | #fff | -
--td-avatar-border-width-large | 6rpx | -
--td-avatar-border-width-medium | 4rpx | -
--td-avatar-border-width-small | 2rpx | -
--td-avatar-circle-border-radius | @radius-circle | -
--td-avatar-content-color | @brand-color | -
--td-avatar-icon-large-font-size | 64rpx | -
--td-avatar-icon-medium-font-size | 48rpx | -
--td-avatar-icon-small-font-size | 40rpx | -
--td-avatar-large-width | 128rpx | -
--td-avatar-margin-left | 0 | -
--td-avatar-medium-width | 96rpx | -
--td-avatar-round-border-radius | @radius-default | -
--td-avatar-small-width | 80rpx | -
--td-avatar-text-large-font-size | @font-size-xl | -
--td-avatar-text-medium-font-size | @font-size-m | -
--td-avatar-text-small-font-size | @font-size-base | -

View File

@@ -0,0 +1,152 @@
---
title: Avatar 头像
description: 用于展示用户头像信息,除了纯展示也可点击进入个人详情等操作。
spline: data
isComponent: true
---
## 引入
可在 `main.ts` 或在需要使用的页面或组件中引入。
```js
import TAvatar from '@tdesign/uniapp/avatar/avatar.vue';
import TAvatarGroup from '@tdesign/uniapp/avatar-group/avatar-group.vue';
```
### 头像类型
图片头像
{{ image-avatar }}
字符头像
{{ character-avatar }}
图标头像
{{ icon-avatar }}
徽标头像
{{ badge-avatar }}
### 组合头像
纯展示
{{ exhibition }}
带操作
{{ action }}
### 头像尺寸
头像 large/medium/small 尺寸
{{ size }}
## API
### Avatar Props
名称 | 类型 | 默认值 | 描述 | 必传
-- | -- | -- | -- | --
custom-style | Object | - | 自定义样式 | N
alt | String | - | 头像替换文本,仅当图片加载失败时有效 | N
badge-props | Object | {} | 头像右上角提示信息,继承 Badge 组件的全部特性。如小红点或者数字。TS 类型:`BadgeProps`[Badge API Documents](./badge?tab=api)。[详细类型定义](https://github.com/Tencent/tdesign-miniprogram/tree/develop/packages/uniapp-components/avatar/type.ts) | N
bordered | Boolean | false | 已废弃。是否显示外边框 | N
hide-on-load-failed | Boolean | false | 加载失败时隐藏图片 | N
icon | String / Object | - | 图标。值为字符串表示图标名称,值为 `Object` 类型,表示透传至 `icon` | N
image | String | - | 图片地址 | N
image-props | Object | - | 透传至 Image 组件。TS 类型:`ImageProps`[Image API Documents](./image?tab=api)。[详细类型定义](https://github.com/Tencent/tdesign-miniprogram/tree/develop/packages/uniapp-components/avatar/type.ts) | N
shape | String | - | 形状。优先级高于 AvatarGroup.shape 。Avatar 单独存在时,默认值为 circle。如果父组件 AvatarGroup 存在,默认值便由 AvatarGroup.shape 决定。可选项circle/round。TS 类型:`ShapeEnum`。[通用类型定义](https://github.com/Tencent/tdesign-miniprogram/tree/develop/packages/uniapp-components/common/common.ts) | N
size | String | - | 尺寸示例值small/medium/large/24px/38px 等。优先级高于 AvatarGroup.size 。Avatar 单独存在时,默认值为 medium。如果父组件 AvatarGroup 存在,默认值便由 AvatarGroup.size 决定 | N
### Avatar Events
名称 | 参数 | 描述
-- | -- | --
error | `(e: Event)` | 图片加载失败时触发
### Avatar Slots
名称 | 描述
-- | --
\- | 默认插槽,自定义内容区域内容
### Avatar External Classes
类名 | 描述
-- | --
t-class | 根节点样式类
t-class-alt | 替代文本样式类
t-class-content | 内容样式类
t-class-icon | 图标样式类
t-class-image | 图片样式类
### AvatarGroup Props
名称 | 类型 | 默认值 | 描述 | 必传
-- | -- | -- | -- | --
custom-style | Object | - | 自定义样式 | N
cascading | String | 'left-up' | 图片之间的层叠关系可选值左侧图片在上和右侧图片在上。可选项left-up/right-up。TS 类型:`CascadingValue` `type CascadingValue = 'left-up' \| 'right-up'`。[详细类型定义](https://github.com/Tencent/tdesign-miniprogram/tree/develop/packages/uniapp-components/avatar-group/type.ts) | N
collapse-avatar | String | - | 头像数量超出时,会出现一个头像折叠元素。该元素内容可自定义。默认为 `+N`。示例:`+5``...`, `更多` | N
max | Number | - | 能够同时显示的最多头像数量 | N
shape | String | - | 形状。优先级低于 Avatar.shape。可选项circle/round。TS 类型:`ShapeEnum`。[通用类型定义](https://github.com/Tencent/tdesign-miniprogram/tree/develop/packages/uniapp-components/common/common.ts) | N
size | String | - | 尺寸示例值small/medium/large/24px/38px 等。优先级低于 Avatar.size | N
### AvatarGroup Events
名称 | 参数 | 描述
-- | -- | --
collapsed-item-click | `(e: MouseEvent)` | 点击头像折叠元素触发
### AvatarGroup Slots
名称 | 描述
-- | --
\- | 默认插槽,自定义内容区域内容
collapse-avatar | 自定义 `collapse-avatar` 显示内容
### AvatarGroup External Classes
类名 | 描述
-- | --
t-class | 根节点样式类
t-class-content | 内容样式类
t-class-image | 图片样式类
### CSS Variables
组件提供了下列 CSS 变量,可用于自定义样式。
名称 | 默认值 | 描述
-- | -- | --
--td-avatar-group-init-z-index | @avatar-group-init-zIndex | -
--td-avatar-group-line-spacing | 4rpx | -
--td-avatar-group-margin-left-large | -16rpx | -
--td-avatar-group-margin-left-medium | -16rpx | -
--td-avatar-group-margin-left-small | -16rpx | -
--td-avatar-bg-color | @brand-color-light-active | -
--td-avatar-border-color | #fff | -
--td-avatar-border-width-large | 6rpx | -
--td-avatar-border-width-medium | 4rpx | -
--td-avatar-border-width-small | 2rpx | -
--td-avatar-circle-border-radius | @radius-circle | -
--td-avatar-content-color | @brand-color | -
--td-avatar-icon-large-font-size | 64rpx | -
--td-avatar-icon-medium-font-size | 48rpx | -
--td-avatar-icon-small-font-size | 40rpx | -
--td-avatar-large-width | 128rpx | -
--td-avatar-margin-left | 0 | -
--td-avatar-medium-width | 96rpx | -
--td-avatar-round-border-radius | @radius-default | -
--td-avatar-small-width | 80rpx | -
--td-avatar-text-large-font-size | @font-size-xl | -
--td-avatar-text-medium-font-size | @font-size-m | -
--td-avatar-text-small-font-size | @font-size-base | -

View File

@@ -0,0 +1,77 @@
.t-avatar {
display: flex;
align-items: center;
justify-content: center;
box-sizing: border-box;
background-color: var(--td-avatar-bg-color, var(--td-brand-color-light-active, var(--td-primary-color-2, #d9e1ff)));
color: var(--td-avatar-content-color, var(--td-brand-color, var(--td-primary-color-7, #0052d9)));
}
.t-avatar__wrapper {
display: inline-flex;
position: relative;
vertical-align: top;
margin-left: var(--td-avatar-margin-left, 0);
}
.t-avatar--large {
width: var(--td-avatar-large-width, 128rpx);
height: var(--td-avatar-large-width, 128rpx);
font-size: var(--td-avatar-text-large-font-size, var(--td-font-size-xl, 40rpx));
}
.t-avatar--large .t-avatar__icon {
font-size: var(--td-avatar-icon-large-font-size, 64rpx);
}
.t-avatar--medium {
width: var(--td-avatar-medium-width, 96rpx);
height: var(--td-avatar-medium-width, 96rpx);
font-size: var(--td-avatar-text-medium-font-size, var(--td-font-size-m, 32rpx));
}
.t-avatar--medium .t-avatar__icon {
font-size: var(--td-avatar-icon-medium-font-size, 48rpx);
}
.t-avatar--small {
width: var(--td-avatar-small-width, 80rpx);
height: var(--td-avatar-small-width, 80rpx);
font-size: var(--td-avatar-text-small-font-size, var(--td-font-size-base, 28rpx));
}
.t-avatar--small .t-avatar__icon {
font-size: var(--td-avatar-icon-small-font-size, 40rpx);
}
.t-avatar .t-image,
.t-avatar__image {
width: 100%;
height: 100%;
}
.t-avatar--circle {
border-radius: var(--td-avatar-circle-border-radius, var(--td-radius-circle, 50%));
overflow: hidden;
}
.t-avatar--round {
border-radius: var(--td-avatar-round-border-radius, var(--td-radius-default, 12rpx));
overflow: hidden;
}
.t-avatar__text,
.t-avatar__icon {
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
}
.t-avatar__text:empty,
.t-avatar__icon:empty {
width: 0;
height: 0;
}
.t-avatar--border {
border-color: var(--td-avatar-border-color, #fff);
border-style: solid;
}
.t-avatar--border-small {
border-width: var(--td-avatar-border-width-small, 2rpx);
}
.t-avatar--border-medium {
border-width: var(--td-avatar-border-width-medium, 4rpx);
}
.t-avatar--border-large {
border-width: var(--td-avatar-border-width-large, 6rpx);
}

View File

@@ -0,0 +1,200 @@
<template>
<view
:class="[
classPrefix + '__wrapper',
tClass
]"
:style="tools._style([utils.getStyles(isShow), customStyle, innerStyle])"
>
<t-badge
v-if="badgeProps"
:color="badgeProps.color || ''"
:content="badgeProps.content || ''"
:count="badgeProps.count || 0"
:dot="badgeProps.dot || false"
:max-count="badgeProps.maxCount || 99"
:offset="badgeProps.offset || []"
:shape="badgeProps.shape || 'circle'"
:show-zero="badgeProps.showZero || false"
:size="badgeProps.size || 'medium'"
:t-class="badgeProps.tClass"
:t-class-content="badgeProps.tClassContent"
:t-class-count="badgeProps.tClassCount"
>
<view
:class="[
utils.getClass(classPrefix, dataSize || 'medium', dataShape, dataBordered),
tClassImage
]"
:style="utils.getSize(dataSize, windowWidth)"
:aria-label="ariaLabel || alt || '头像'"
:aria-role="ariaRole || 'img'"
:aria-hidden="ariaHidden"
>
<t-image
v-if="image"
:t-class="prefix + '-image ' + classPrefix + '__image'"
:t-class-load="tClassAlt"
:custom-style="imageCustomStyle"
style="width: 100%;height: 100%;"
:src="image"
:mode="(imageProps && imageProps.mode) || 'aspectFill'"
:lazy="(imageProps && imageProps.lazy) || false"
:loading="(imageProps && imageProps.loading) || 'default'"
:shape="(imageProps && imageProps.shape) || 'round'"
:webp="(imageProps && imageProps.webp) || false"
:error="alt || 'default'"
@error="onLoadError"
/>
<block
v-else-if="iconName || tools.isNoEmptyObj(iconData)"
name="icon"
>
<t-icon
:t-class="classPrefix + '__icon ' + classPrefix + '__icon--' + (iconData.activeIdx == iconData.index ? 'active ' : ' ') + tClassIcon"
:prefix="iconData.prefix"
:name="iconName || iconData.name"
:size="iconData.size"
:color="iconData.color"
:aria-hidden="!!iconData.ariaHidden"
:aria-label="iconData.ariaLabel"
:aria-role="iconData.ariaRole"
:custom-style="iconCustomStyle"
@click="iconData.click || ''"
/>
</block>
<view
v-else
:class="[
classPrefix + '__text ',
tClassContent
]"
>
<slot />
</view>
</view>
</t-badge>
</view>
</template>
<script>
import TIcon from '../icon/icon';
import TBadge from '../badge/badge';
import TImage from '../image/image';
import { uniComponent } from '../common/src/index';
import { prefix } from '../common/config';
import avatarProps from './props';
import { setIcon, systemInfo } from '../common/utils';
import tools from '../common/utils.wxs';
import * as utils from './computed.js';
import { ChildrenMixin, RELATION_MAP } from '../common/relation';
const name = `${prefix}-avatar`;
export default uniComponent({
name,
options: {
styleIsolation: 'shared',
},
externalClasses: [
`${prefix}-class`,
`${prefix}-class-image`,
`${prefix}-class-icon`,
`${prefix}-class-alt`,
`${prefix}-class-content`,
],
mixins: [ChildrenMixin(RELATION_MAP.Avatar)],
components: {
TIcon,
TBadge,
TImage,
},
props: {
...avatarProps,
},
data() {
return {
prefix,
classPrefix: name,
isShow: true,
zIndex: 0,
windowWidth: systemInfo.windowWidth,
utils,
tools,
iconName: '',
iconData: {},
dataShape: this.shape,
dataSize: this.size,
dataBordered: this.bordered,
innerStyle: '',
};
},
computed: {
iconCustomStyle() {
const fontSize = {
small: 'var(--td-avatar-icon-small-font-size, 20px)',
medium: 'var(--td-avatar-icon-medium-font-size, 24px)',
large: 'var(--td-avatar-icon-large-font-size, 32px)',
};
if (!fontSize[this.dataSize]) return '';
return tools._style([
{
fontSize: fontSize[this.dataSize],
},
this.iconData.style || '',
]);
},
imageCustomStyle() {
return tools._style([
{
width: '100%',
height: '100%',
},
this.imageProps?.style || '',
]);
},
},
watch: {
icon: {
handler(t) {
const obj = setIcon('icon', t, '');
Object.keys(obj).forEach((key) => {
this[key] = obj[key];
});
},
immediate: true,
},
},
mounted() {
},
methods: {
innerAfterLinked() {
this.dataShape = this.shape || this[RELATION_MAP.Avatar]?.shape || 'circle';
this.dataSize = this.size || this[RELATION_MAP.Avatar]?.size;
this.dataBordered = true;
},
hide() {
this.isShow = false;
},
onLoadError(e) {
if (this.hideOnLoadFailed) {
this.isShow = false;
}
this.$emit('error', e && e.e);
},
setStyle(val = '') {
this.innerStyle = val;
},
},
});
</script>
<style scoped>
@import './avatar.css';
</style>

View File

@@ -0,0 +1,30 @@
import { getRegExp } from '../common/runtime/wxs-polyfill';
export function getClass(classPrefix, size, shape, bordered) {
const hasPx = (size || '').indexOf('px') > -1;
const borderSize = hasPx ? 'medium' : size;
const classNames = [
classPrefix,
classPrefix + (shape === 'round' ? '--round' : '--circle'),
bordered ? `${classPrefix}--border ${classPrefix}--border-${borderSize}` : '',
hasPx ? '' : `${classPrefix}--${size}`,
];
return classNames.join(' ');
}
export function getSize(size = 'medium', windowWidth) {
const res = getRegExp('^([0-9]+)(px|rpx)$').exec(size);
if (res && res.length >= 3) {
let px = res[1];
if (res[2] === 'rpx') {
px = Math.floor((windowWidth * res[1]) / 750);
}
return `width:${size};height:${size};font-size:${(px / 8) * 3 + 2}px`;
}
}
export function getStyles(isShow) {
return isShow ? '' : 'display: none;';
}

View File

@@ -0,0 +1,54 @@
/* eslint-disable */
/**
* 该文件为脚本自动生成文件,请勿随意修改。如需修改请联系 PMC
* */
import type { TdAvatarProps } from './type';
export default {
/** 头像替换文本,仅当图片加载失败时有效 */
alt: {
type: String,
default: '',
},
/** 头像右上角提示信息,继承 Badge 组件的全部特性。如:小红点,或者数字 */
badgeProps: {
type: Object,
default: () => ({}),
},
/** 已废弃。是否显示外边框 */
bordered: Boolean,
/** 加载失败时隐藏图片 */
hideOnLoadFailed: Boolean,
/** 图标。值为字符串表示图标名称,值为 `Object` 类型,表示透传至 `icon` */
icon: {
type: [String, Object],
},
/** 图片地址 */
image: {
type: String,
default: '',
},
/** 透传至 Image 组件 */
imageProps: {
type: Object,
},
/** 形状。优先级高于 AvatarGroup.shape 。Avatar 单独存在时,默认值为 circle。如果父组件 AvatarGroup 存在,默认值便由 AvatarGroup.shape 决定 */
shape: {
type: String,
validator(val: TdAvatarProps['shape']): boolean {
if (!val) return true;
return ['circle', 'round'].includes(val);
},
},
/** 尺寸示例值small/medium/large/24px/38px 等。优先级高于 AvatarGroup.size 。Avatar 单独存在时,默认值为 medium。如果父组件 AvatarGroup 存在,默认值便由 AvatarGroup.size 决定 */
size: {
type: String,
default: '',
},
/** 图片加载失败时触发 */
onError: {
type: Function,
default: () => ({}),
},
};

View File

@@ -0,0 +1,58 @@
/* eslint-disable */
/**
* 该文件为脚本自动生成文件,请勿随意修改。如需修改请联系 PMC
* */
import type { TdBadgeProps as BadgeProps } from '../badge/type';
import type { TdImageProps as ImageProps } from '../image/type';
import type { ShapeEnum } from '../common/common';
export interface TdAvatarProps {
/**
* 头像替换文本,仅当图片加载失败时有效
* @default ''
*/
alt?: string;
/**
* 头像右上角提示信息,继承 Badge 组件的全部特性。如:小红点,或者数字
* @default {}
*/
badgeProps?: BadgeProps;
/**
* 已废弃。是否显示外边框
* @default false
*/
bordered?: boolean;
/**
* 加载失败时隐藏图片
* @default false
*/
hideOnLoadFailed?: boolean;
/**
* 图标。值为字符串表示图标名称,值为 `Object` 类型,表示透传至 `icon`
*/
icon?: string | object;
/**
* 图片地址
* @default ''
*/
image?: string;
/**
* 透传至 Image 组件
*/
imageProps?: ImageProps;
/**
* 形状。优先级高于 AvatarGroup.shape 。Avatar 单独存在时,默认值为 circle。如果父组件 AvatarGroup 存在,默认值便由 AvatarGroup.shape 决定
*/
shape?: ShapeEnum;
/**
* 尺寸示例值small/medium/large/24px/38px 等。优先级高于 AvatarGroup.size 。Avatar 单独存在时,默认值为 medium。如果父组件 AvatarGroup 存在,默认值便由 AvatarGroup.size 决定
* @default ''
*/
size?: string;
/**
* 图片加载失败时触发
*/
onError?: (e: Event) => void;
}

View File

@@ -0,0 +1,49 @@
:: BASE_DOC ::
## API
### BackTop Props
name | type | default | description | required
-- | -- | -- | -- | --
custom-style | Object | - | CSS(Cascading Style Sheets) | N
fixed | Boolean | true | \- | N
icon | String / Boolean / Object | true | \- | N
scroll-top | Number | 0 | \- | N
text | String | '' | \- | N
theme | String | round | options: round/half-round/round-dark/half-round-dark | N
visibility-height | Number | 200 | \- | N
### BackTop Events
name | params | description
-- | -- | --
to-top | \- | \-
### BackTop Slots
name | Description
-- | --
\- | \-
icon | \-
### BackTop External Classes
className | Description
-- | --
t-class | \-
t-class-icon | \-
t-class-text | \-
### CSS Variables
The component provides the following CSS variables, which can be used to customize styles.
Name | Default Value | Description
-- | -- | --
--td-back-top-half-round-border-radius | @radius-round | -
--td-back-top-round-bg-color | @bg-color-container | -
--td-back-top-round-border-color | @component-border | -
--td-back-top-round-border-radius | @radius-circle | -
--td-back-top-round-color | @text-color-primary | -
--td-back-top-round-dark-bg-color | @gray-color-13 | -
--td-back-top-round-dark-color | @text-color-anti | -

View File

@@ -0,0 +1,67 @@
---
title: BackTop 返回顶部
description: 用于当页面过长往下滑动时,帮助用户快速回到页面顶部。
spline: navigation
isComponent: true
---
## 引入
可在 `main.ts` 或在需要使用的页面或组件中引入。
```js
import TBackTop from '@tdesign/uniapp/back-top/back-top.vue';
```
### 基础返回顶部
{{ base }}
## API
### BackTop Props
名称 | 类型 | 默认值 | 描述 | 必传
-- | -- | -- | -- | --
custom-style | Object | - | 自定义样式 | N
fixed | Boolean | true | 是否绝对定位固定到屏幕右下方 | N
icon | String / Boolean / Object | true | 图标。值为 `false` 表示不显示图标。不传表示使用默认图标 `'backtop'` | N
scroll-top | Number | 0 | 页面滚动距离 | N
text | String | '' | 文案 | N
theme | String | round | 预设的样式类型。可选项round/half-round/round-dark/half-round-dark | N
visibility-height | Number | 200 | 滚动高度达到此参数值才出现 | N
### BackTop Events
名称 | 参数 | 描述
-- | -- | --
to-top | \- | 点击触发
### BackTop Slots
名称 | 描述
-- | --
\- | 默认插槽,自定义内容区域内容
icon | 自定义图标内容
### BackTop External Classes
类名 | 描述
-- | --
t-class | 根节点样式类
t-class-icon | 图标样式类
t-class-text | 文本样式类
### CSS Variables
组件提供了下列 CSS 变量,可用于自定义样式。
名称 | 默认值 | 描述
-- | -- | --
--td-back-top-half-round-border-radius | @radius-round | -
--td-back-top-round-bg-color | @bg-color-container | -
--td-back-top-round-border-color | @component-border | -
--td-back-top-round-border-radius | @radius-circle | -
--td-back-top-round-color | @text-color-primary | -
--td-back-top-round-dark-bg-color | @gray-color-13 | -
--td-back-top-round-dark-color | @text-color-anti | -

View File

@@ -0,0 +1,65 @@
.t-back-top {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
background-color: transparent;
overflow: hidden;
box-sizing: border-box;
transition: height 0.2s;
height: auto;
}
.t-back-top--fixed {
position: fixed;
right: var(--td-spacer, 16rpx);
bottom: calc(var(--td-spacer-2, 32rpx) + env(safe-area-inset-bottom));
}
.t-back-top--round,
.t-back-top--round-dark {
width: 96rpx;
height: 96rpx;
border-radius: var(--td-back-top-round-border-radius, var(--td-radius-circle, 50%));
}
.t-back-top--round,
.t-back-top--half-round {
color: var(--td-back-top-round-color, var(--td-text-color-primary, var(--td-font-gray-1, rgba(0, 0, 0, 0.9))));
border: 1rpx solid var(--td-back-top-round-border-color, var(--td-component-border, var(--td-gray-color-4, #dcdcdc)));
background-color: var(--td-back-top-round-bg-color, var(--td-bg-color-container, var(--td-font-white-1, #ffffff)));
}
.t-back-top--round-dark,
.t-back-top--half-round-dark {
color: var(--td-back-top-round-dark-color, var(--td-text-color-anti, var(--td-font-white-1, #ffffff)));
background-color: var(--td-back-top-round-dark-bg-color, var(--td-gray-color-13, #242424));
}
.t-back-top--half-round,
.t-back-top--half-round-dark {
width: 120rpx;
height: 80rpx;
border-radius: 0;
border-top-left-radius: var(--td-back-top-half-round-border-radius, var(--td-radius-round, 999px));
border-bottom-left-radius: var(--td-back-top-half-round-border-radius, var(--td-radius-round, 999px));
flex-direction: row;
right: 0;
}
.t-back-top__text--round,
.t-back-top__text--round-dark,
.t-back-top__text--half-round,
.t-back-top__text--half-round-dark {
font-weight: 600;
font-size: var(--td-font-size, 20rpx);
line-height: 24rpx;
}
.t-back-top__text--half-round,
.t-back-top__text--half-round-dark {
width: 48rpx;
}
.t-back-top__icon:not(:empty) + .t-back-top__text--half-round,
.t-back-top__icon:not(:empty) + .t-back-top__text--half-round-dark {
margin-left: 8rpx;
}
.t-back-top__icon {
display: flex;
justify-content: center;
align-items: center;
font-size: 44rpx;
}

View File

@@ -0,0 +1,122 @@
<template>
<view
v-if="!hidden"
:style="tools._style([customStyle])"
:class="tClass + ' ' + tools.cls(classPrefix, [['fixed', fixed], theme])"
aria-role="button"
@click="toTop"
>
<view
:class="classPrefix + '__icon'"
aria-hidden
>
<slot name="icon" />
<block
v-if="_icon"
name="icon"
>
<t-icon
:custom-style="_icon.style || ''"
:t-class="tClassIcon"
:prefix="_icon.prefix"
:name="_icon.name"
:size="_icon.size"
:color="_icon.color"
:aria-hidden="!!_icon.ariaHidden"
:aria-label="_icon.ariaLabel"
:aria-role="_icon.ariaRole"
@click="_icon.click || ''"
/>
</block>
</view>
<view
v-if="!!text"
:class="classPrefix + '__text--' + theme + ' ' + tClassText"
>
{{ text }}
</view>
<slot />
</view>
</template>
<script>
import TIcon from '../icon/icon';
import { uniComponent } from '../common/src/index';
import { prefix } from '../common/config';
import props from './props';
import { calcIcon } from '../common/utils';
import tools from '../common/utils.wxs';
import { ChildrenMixin, RELATION_MAP } from '../common/relation';
const name = `${prefix}-back-top`;
export default uniComponent({
name,
options: {
styleIsolation: 'shared',
},
externalClasses: [
`${prefix}-class`,
`${prefix}-class-icon`,
`${prefix}-class-text`,
],
mixins: [
ChildrenMixin(RELATION_MAP.BackTop),
],
components: {
TIcon,
},
props: {
...props,
},
emits: [
'to-top',
],
watch: {
icon() {
this.setIcon();
},
scrollTop: {
handler(value) {
const { visibilityHeight } = this;
this.hidden = value < visibilityHeight;
},
immediate: true,
},
},
mounted() {
const { icon } = this;
this.setIcon(icon);
},
methods: {
setIcon(v) {
this._icon = calcIcon(v, 'backtop');
},
toTop() {
this.$emit('to-top');
if (this[RELATION_MAP.BackTop]) {
this[RELATION_MAP.BackTop]?.scrollToTop();
this.hidden = true;
} else {
uni.pageScrollTo({
scrollTop: 0,
duration: 300,
});
}
},
},
data() {
return {
prefix,
classPrefix: name,
_icon: null,
hidden: true,
tools,
};
},
});
</script>
<style scoped>
@import './back-top.css';
</style>

View File

@@ -0,0 +1,48 @@
/* eslint-disable */
/**
* 该文件为脚本自动生成文件,请勿随意修改。如需修改请联系 PMC
* */
import type { TdBackTopProps } from './type';
export default {
/** 是否绝对定位固定到屏幕右下方 */
fixed: {
type: Boolean,
default: true,
},
/** 图标。值为 `false` 表示不显示图标。不传表示使用默认图标 `'backtop'` */
icon: {
type: [String, Boolean, Object],
default: true as TdBackTopProps['icon'],
},
/** 页面滚动距离 */
scrollTop: {
type: Number,
default: 0,
},
/** 文案 */
text: {
type: String,
default: '',
},
/** 预设的样式类型 */
theme: {
type: String,
default: 'round' as TdBackTopProps['theme'],
validator(val: TdBackTopProps['theme']): boolean {
if (!val) return true;
return ['round', 'half-round', 'round-dark', 'half-round-dark'].includes(val);
},
},
/** 滚动高度达到此参数值才出现 */
visibilityHeight: {
type: Number,
default: 200,
},
/** 点击触发 */
onToTop: {
type: Function,
default: () => ({}),
},
};

View File

@@ -0,0 +1,42 @@
/* eslint-disable */
/**
* 该文件为脚本自动生成文件,请勿随意修改。如需修改请联系 PMC
* */
export interface TdBackTopProps {
/**
* 是否绝对定位固定到屏幕右下方
* @default true
*/
fixed?: boolean;
/**
* 图标。值为 `false` 表示不显示图标。不传表示使用默认图标 `'backtop'`
* @default true
*/
icon?: string | boolean | object;
/**
* 页面滚动距离
* @default 0
*/
scrollTop?: number;
/**
* 文案
* @default ''
*/
text?: string;
/**
* 预设的样式类型
* @default round
*/
theme?: 'round' | 'half-round' | 'round-dark' | 'half-round-dark';
/**
* 滚动高度达到此参数值才出现
* @default 200
*/
visibilityHeight?: number;
/**
* 点击触发
*/
onToTop?: () => void;
}

View File

@@ -0,0 +1,54 @@
:: BASE_DOC ::
## API
### Badge Props
name | type | default | description | required
-- | -- | -- | -- | --
custom-style | Object | - | CSS(Cascading Style Sheets) | N
color | String | - | \- | N
content | String | - | \- | N
count | String / Number | 0 | \- | N
dot | Boolean | false | \- | N
max-count | Number | 99 | \- | N
offset | Array | - | Typescript: `Array<string \| number>` | N
shape | String | circle | options: circle/square/bubble/ribbon/ribbon-right/ribbon-left/triangle-right/triangle-left | N
show-zero | Boolean | false | \- | N
size | String | medium | options: medium/large | N
### Badge Slots
name | Description
-- | --
\- | \-
count | \-
### Badge External Classes
className | Description
-- | --
t-class | \-
t-class-content | \-
t-class-count | \-
### CSS Variables
The component provides the following CSS variables, which can be used to customize styles.
Name | Default Value | Description
-- | -- | --
--td-badge-basic-height | 32rpx | -
--td-badge-basic-padding | 8rpx | -
--td-badge-basic-width | 32rpx | -
--td-badge-bg-color | @error-color | -
--td-badge-border-radius | 4rpx | -
--td-badge-bubble-border-radius | 20rpx 20rpx 20rpx 1px | -
--td-badge-content-text-color | @text-color-primary | -
--td-badge-dot-size | 16rpx | -
--td-badge-font | @font-mark-extraSmall | -
--td-badge-large-font | @font-mark-small | -
--td-badge-large-height | 40rpx | -
--td-badge-large-padding | 10rpx | -
--td-badge-text-color | @text-color-anti | -
--td-line-height-mark-extraSmall | 32rpx | -
--td-line-height-mark-small | 40rpx | -

View File

@@ -0,0 +1,85 @@
---
title: Badge 徽标
description: 用于告知用户,该区域的状态变化或者待处理任务的数量。
spline: data
isComponent: true
---
## 引入
可在 `main.ts` 或在需要使用的页面或组件中引入。
```js
import TBadge from '@tdesign/uniapp/badge/badge.vue';
```
### 组件类型
{{ base }}
### 组件样式
{{ theme }}
### 组件尺寸
{{ size }}
## FAQ
### 如何处理由 ribbon 徽标溢出导致页面出现横向滚动?
角标溢出问题建议从父容器组件处理。如 <a href="https://github.com/Tencent/tdesign-miniprogram/issues/3063" title="如 #3063 " target="_blank" rel="noopener noreferrer"> #3063 </a>,可以给父容器 `cell` 组件添加 `overflow: hidden`,处理溢出造成页面出现横向滚动的问题。
## API
### Badge Props
名称 | 类型 | 默认值 | 描述 | 必传
-- | -- | -- | -- | --
custom-style | Object | - | 自定义样式 | N
color | String | - | 颜色 | N
content | String | - | 徽标内容,示例:`content='自定义内容'`。也可以使用默认插槽定义 | N
count | String / Number | 0 | 徽标右上角内容。可以是数字,也可以是文字。如:'new'/3/99+。特殊:值为空表示使用插槽渲染 | N
dot | Boolean | false | 是否为红点 | N
max-count | Number | 99 | 封顶的数字值 | N
offset | Array | - | 设置状态点的位置偏移,示例:[-10, 20] 或 ['10em', '8rem']。TS 类型:`Array<string \| number>` | N
shape | String | circle | 徽标形状,其中 ribbon 和 ribbon-right 等效。可选项circle/square/bubble/ribbon/ribbon-right/ribbon-left/triangle-right/triangle-left | N
show-zero | Boolean | false | 当数值为 0 时,是否展示徽标 | N
size | String | medium | 尺寸。可选项medium/large | N
### Badge Slots
名称 | 描述
-- | --
\- | 默认插槽,自定义内容区域内容
count | 徽标右上角内容
### Badge External Classes
类名 | 描述
-- | --
t-class | 根节点样式类
t-class-content | 内容样式类
t-class-count | 计数样式类
### CSS Variables
组件提供了下列 CSS 变量,可用于自定义样式。
名称 | 默认值 | 描述
-- | -- | --
--td-badge-basic-height | 32rpx | -
--td-badge-basic-padding | 8rpx | -
--td-badge-basic-width | 32rpx | -
--td-badge-bg-color | @error-color | -
--td-badge-border-radius | 4rpx | -
--td-badge-bubble-border-radius | 20rpx 20rpx 20rpx 1px | -
--td-badge-content-text-color | @text-color-primary | -
--td-badge-dot-size | 16rpx | -
--td-badge-font | @font-mark-extraSmall | -
--td-badge-large-font | @font-mark-small | -
--td-badge-large-height | 40rpx | -
--td-badge-large-padding | 10rpx | -
--td-badge-text-color | @text-color-anti | -
--td-line-height-mark-extraSmall | 32rpx | -
--td-line-height-mark-small | 40rpx | -

View File

@@ -0,0 +1,140 @@
.t-badge {
position: relative;
display: inline-flex;
align-items: start;
}
.t-badge--basic {
z-index: 100;
padding: 0 var(--td-badge-basic-padding, 8rpx);
font: var(--td-badge-font, var(--td-font-mark-extraSmall, 600 20rpx / 32rpx var(--td-font-family, PingFang SC, Microsoft YaHei, Arial Regular)));
color: var(--td-badge-text-color, var(--td-text-color-anti, var(--td-font-white-1, #ffffff)));
background-color: var(--td-badge-bg-color, var(--td-error-color, var(--td-error-color-6, #d54941)));
text-align: center;
height: var(--td-badge-basic-height, 32rpx);
border-radius: var(--td-badge-border-radius, 4rpx);
}
.t-badge--dot {
height: var(--td-badge-dot-size, 16rpx);
border-radius: 50%;
min-width: var(--td-badge-dot-size, 16rpx);
padding: 0;
}
.t-badge--count {
min-width: var(--td-badge-basic-width, 32rpx);
white-space: nowrap;
box-sizing: border-box;
}
.t-badge--circle {
border-radius: calc(var(--td-badge-basic-height, 32rpx) / 2);
}
.t-badge__ribbon-outer,
.t-badge__ribbon-right-outer,
.t-badge__triangle-right-outer,
.t-badge__ribbon-left-outer,
.t-badge__triangle-left-outer {
position: absolute;
top: 0;
}
.t-badge__ribbon-outer,
.t-badge__ribbon-right-outer,
.t-badge__triangle-right-outer {
right: 0;
}
.t-badge__ribbon-left-outer,
.t-badge__triangle-left-outer {
left: 0;
}
.t-badge--bubble {
border-radius: var(--td-badge-bubble-border-radius, 20rpx 20rpx 20rpx 1px);
}
.t-badge--ribbon,
.t-badge--ribbon-right,
.t-badge--ribbon-left,
.t-badge--triangle-left,
.t-badge--triangle-right {
width: calc(var(--td-badge-basic-height, 32rpx) * 2);
height: calc(var(--td-badge-basic-height, 32rpx) * 2);
border-radius: 0;
padding: 0;
position: absolute;
top: 0;
display: flex;
align-items: center;
justify-content: center;
overflow: hidden;
}
.t-badge--ribbon,
.t-badge--ribbon-right {
background: linear-gradient(45deg, transparent 50%, var(--td-badge-bg-color, var(--td-error-color, var(--td-error-color-6, #d54941))) 50%, var(--td-badge-bg-color, var(--td-error-color, var(--td-error-color-6, #d54941))) 85%, transparent 85%);
}
.t-badge--triangle-right {
background: linear-gradient(45deg, transparent 50%, var(--td-badge-bg-color, var(--td-error-color, var(--td-error-color-6, #d54941))) 50%);
}
.t-badge--ribbon,
.t-badge--ribbon-right,
.t-badge--triangle-right {
right: 0;
}
.t-badge--ribbon .t-badge__count,
.t-badge--ribbon-right .t-badge__count,
.t-badge--triangle-right .t-badge__count {
transform: rotate(45deg) translateY(calc(-1 * var(--td-line-height-mark-extraSmall, 32rpx) / 2 + 1rpx));
}
.t-badge--ribbon-left {
background: linear-gradient(-45deg, transparent 50%, var(--td-badge-bg-color, var(--td-error-color, var(--td-error-color-6, #d54941))) 50%, var(--td-badge-bg-color, var(--td-error-color, var(--td-error-color-6, #d54941))) 85%, transparent 85%);
}
.t-badge--triangle-left {
background: linear-gradient(-45deg, transparent 50%, var(--td-badge-bg-color, var(--td-error-color, var(--td-error-color-6, #d54941))) 50%);
}
.t-badge--ribbon-left,
.t-badge--triangle-left {
left: 0;
}
.t-badge--ribbon-left .t-badge__count,
.t-badge--triangle-left .t-badge__count {
transform: rotate(-45deg) translateY(calc(-1 * var(--td-line-height-mark-extraSmall, 32rpx) / 2 + 1rpx));
}
.t-badge--large {
font: var(--td-badge-large-font, var(--td-font-mark-small, 600 24rpx / 40rpx var(--td-font-family, PingFang SC, Microsoft YaHei, Arial Regular)));
height: var(--td-badge-large-height, 40rpx);
min-width: var(--td-badge-large-height, 40rpx);
padding: 0 var(--td-badge-large-padding, 10rpx);
}
.t-badge--large.t-badge--circle {
border-radius: calc(var(--td-badge-large-height, 40rpx) / 2);
}
.t-badge--large.t-badge--ribbon,
.t-badge--large.t-badge--ribbon-right,
.t-badge--large.t-badge--ribbon-left,
.t-badge--large.t-badge--triangle-right,
.t-badge--large.t-badge--triangle-left {
width: calc(var(--td-badge-large-height, 40rpx) * 2);
height: calc(var(--td-badge-large-height, 40rpx) * 2);
padding: 0;
}
.t-badge--large.t-badge--ribbon .t-badge__count,
.t-badge--large.t-badge--ribbon-right .t-badge__count,
.t-badge--large.t-badge--triangle-right .t-badge__count {
transform: rotate(45deg) translateY(calc(-1 * var(--td-line-height-mark-small, 40rpx) / 2 + 3rpx));
}
.t-badge--large.t-badge--ribbon-left .t-badge__count,
.t-badge--large.t-badge--triangle-left .t-badge__count {
transform: rotate(-45deg) translateY(calc(-1 * var(--td-line-height-mark-small, 40rpx) / 2 + 3rpx));
}
.t-badge__content:not(:empty) + .t-badge--bubble.t-has-count,
.t-badge__content:not(:empty) + .t-badge--circle.t-has-count,
.t-badge__content:not(:empty) + .t-badge--square.t-has-count {
transform-origin: center center;
transform: translate(-50%, -50%);
position: absolute;
top: 0;
left: 100%;
}
.t-badge__content-text {
display: block;
font: var(--td-font-body-large, 32rpx / 48rpx var(--td-font-family, PingFang SC, Microsoft YaHei, Arial Regular));
color: var(--td-badge-content-text-color, var(--td-text-color-primary, var(--td-font-gray-1, rgba(0, 0, 0, 0.9))));
}
.t-badge__count:empty {
display: none;
}

View File

@@ -0,0 +1,134 @@
<template>
<view
:style="tools._style([customStyle])"
:class="classPrefix + ' ' + (useOuterClass ? classPrefix + '__' + shape + '-outer' : '') + tClass"
:aria-labelledby="labelID"
:aria-describedby="descriptionID"
:aria-role="ariaRole || 'option'"
>
<view
:id="labelID"
:class="tools.cls(classPrefix + '__content', [['empty', !content && !hasChild]]) + ' ' + tClassContent"
:aria-hidden="true"
>
<!-- #ifdef H5 -->
<slot
v-if="!content"
:class="classPrefix + '__content-slot'"
/>
<!-- #endif -->
<!-- 小程序下在 slot 下加 :class 属性会导致渲染失败 -->
<!-- #ifndef H5 -->
<slot
v-if="!content"
/>
<!-- #endif -->
<text
v-else
:class="classPrefix + '__content-text'"
>
{{ content }}
</text>
</view>
<view
v-if="isShowBadge({ dot, count, showZero }) || count === null"
:id="descriptionID"
:aria-hidden="true"
:aria-label="ariaLabel || tools.getBadgeAriaLabel({ dot, count, maxCount })"
:class="[
getBadgeInnerClass({ classPrefix, dot, size, shape, count }) + ' ' + prefix + '-has-count ',
tClassCount
]"
:style="tools._style([getBadgeStyles({ color, offset })])"
>
<view :class="classPrefix + '__count'">
<template v-if="isShowBadge({ dot, count, showZero })">
{{ getBadgeValue({ dot, count, maxCount }) }}
</template>
<slot
else
name="count"
/>
</view>
</view>
</view>
</template>
<script>
import { uniComponent } from '../common/src/index';
import { prefix } from '../common/config';
import props from './props';
import { uniqueFactory, getRect } from '../common/utils';
import tools from '../common/utils.wxs';
import {
getBadgeValue,
getBadgeStyles,
getBadgeInnerClass,
isShowBadge,
} from './computed.js';
const name = `${prefix}-badge`;
const getUniqueID = uniqueFactory('badge');
export default uniComponent({
name,
options: {
styleIsolation: 'shared',
},
externalClasses: [
`${prefix}-class`,
`${prefix}-class-count`,
`${prefix}-class-content`,
],
props: {
...props,
},
data() {
return {
prefix,
classPrefix: name,
value: '',
labelID: '',
descriptionID: '',
tools,
useOuterClass: false,
};
},
computed: {
hasChild() {
return !!this.$slots?.default;
},
},
mounted() {
const e = getUniqueID();
this.labelID = `${e}_label`;
this.descriptionID = `${e}_description`;
this.checkForActualContent();
},
methods: {
getBadgeValue,
getBadgeStyles,
getBadgeInnerClass,
isShowBadge,
checkForActualContent() {
const target = ['ribbon', 'ribbon-right', 'ribbon-left', 'triangle-right', 'triangle-left'];
if (this.content || !target.includes(this.shape)) {
this.useOuterClass = false;
return;
}
return getRect(this, `.${name}__content`).then((rect) => {
const hasSlotContent = rect.width > 0 || rect.height > 0;
this.useOuterClass = !hasSlotContent;
});
},
},
});
</script>
<style scoped>
@import './badge.css';
</style>

View File

@@ -0,0 +1,63 @@
export const getBadgeValue = function (props) {
if (props.dot) {
return '';
}
// eslint-disable-next-line no-restricted-globals
if (isNaN(props.count) || isNaN(props.maxCount)) {
return props.count;
}
return parseInt(props.count, 10) > props.maxCount ? `${props.maxCount}+` : props.count;
};
export const hasUnit = function (unit) {
return (
unit.indexOf('px') > 0
|| unit.indexOf('rpx') > 0
|| unit.indexOf('em') > 0
|| unit.indexOf('rem') > 0
|| unit.indexOf('%') > 0
|| unit.indexOf('vh') > 0
|| unit.indexOf('vm') > 0
);
};
export const getBadgeStyles = function (props) {
let styleStr = '';
if (props.color) {
styleStr += `background:${props.color};`;
}
if (props.offset?.[0]) {
styleStr
+= `left: calc(100% + ${hasUnit(props.offset[0].toString()) ? props.offset[0] : `${props.offset[0]}px`});`;
}
if (props.offset?.[1]) {
styleStr += `top:${hasUnit(props.offset[1].toString()) ? props.offset[1] : `${props.offset[1]}px`};`;
}
return styleStr;
};
export const getBadgeInnerClass = function (props) {
const baseClass = props.classPrefix;
const classNames = [
`${baseClass}--basic`,
props.dot ? `${baseClass}--dot` : '',
`${baseClass}--${props.size}`,
`${baseClass}--${props.shape}`,
!props.dot ? `${baseClass}--count` : '',
];
return classNames.join(' ');
};
export const isShowBadge = function (props) {
if (props.dot) {
return true;
}
// eslint-disable-next-line no-restricted-globals
if (!props.showZero && !isNaN(props.count) && parseInt(props.count, 10) === 0) {
return false;
}
if (props.count == null) return false;
return true;
};

View File

@@ -0,0 +1,55 @@
/* eslint-disable */
/**
* 该文件为脚本自动生成文件,请勿随意修改。如需修改请联系 PMC
* */
import type { TdBadgeProps } from './type';
export default {
/** 颜色 */
color: {
type: String,
default: '',
},
/** 徽标内容,示例:`content='自定义内容'`。也可以使用默认插槽定义 */
content: {
type: String,
default: '',
},
/** 徽标右上角内容。可以是数字,也可以是文字。如:'new'/3/99+。特殊:值为空表示使用插槽渲染 */
count: {
type: [String, Number],
default: 0 as TdBadgeProps['count'],
},
/** 是否为红点 */
dot: Boolean,
/** 封顶的数字值 */
maxCount: {
type: Number,
default: 99,
},
/** 设置状态点的位置偏移,示例:[-10, 20] 或 ['10em', '8rem'] */
offset: {
type: Array,
},
/** 徽标形状,其中 ribbon 和 ribbon-right 等效 */
shape: {
type: String,
default: 'circle' as TdBadgeProps['shape'],
validator(val: TdBadgeProps['shape']): boolean {
if (!val) return true;
return ['circle', 'square', 'bubble', 'ribbon', 'ribbon-right', 'ribbon-left', 'triangle-right', 'triangle-left'].includes(val);
},
},
/** 当数值为 0 时,是否展示徽标 */
showZero: Boolean,
/** 尺寸 */
size: {
type: String,
default: 'medium' as TdBadgeProps['size'],
validator(val: TdBadgeProps['size']): boolean {
if (!val) return true;
return ['medium', 'large'].includes(val);
},
},
};

View File

@@ -0,0 +1,60 @@
/* eslint-disable */
/**
* 该文件为脚本自动生成文件,请勿随意修改。如需修改请联系 PMC
* */
export interface TdBadgeProps {
/**
* 颜色
* @default ''
*/
color?: string;
/**
* 徽标内容,示例:`content='自定义内容'`。也可以使用默认插槽定义
* @default ''
*/
content?: string;
/**
* 徽标右上角内容。可以是数字,也可以是文字。如:'new'/3/99+。特殊:值为空表示使用插槽渲染
* @default 0
*/
count?: string | number;
/**
* 是否为红点
* @default false
*/
dot?: boolean;
/**
* 封顶的数字值
* @default 99
*/
maxCount?: number;
/**
* 设置状态点的位置偏移,示例:[-10, 20] 或 ['10em', '8rem']
*/
offset?: Array<string | number>;
/**
* 徽标形状,其中 ribbon 和 ribbon-right 等效
* @default circle
*/
shape?:
| 'circle'
| 'square'
| 'bubble'
| 'ribbon'
| 'ribbon-right'
| 'ribbon-left'
| 'triangle-right'
| 'triangle-left';
/**
* 当数值为 0 时,是否展示徽标
* @default false
*/
showZero?: boolean;
/**
* 尺寸
* @default medium
*/
size?: 'medium' | 'large';
}

View File

@@ -0,0 +1,238 @@
---
title: Button
description: Buttons are used to open a closed-loop task, such as "delete" an object, "buy" an item, etc.
spline: base
isComponent: true
---
## Usage
For global import, configure it in `app.json` in the root directory of the miniprogram. For local import, configure it in `index.json` of the page or component that needs to be imported.
```json
"usingComponents": {
"t-button": "tdesign-miniprogram/button/button"
}
```
## Code Demo
### 01 Component Type
#### Basic Buttons
{{ base }}
#### Icon Button
{{ icon-btn }}
#### Ghost Button
{{ ghost-btn }}
#### Combination Button
{{ group-btn }}
#### Banner Button
{{ block-btn }}
### 02 Component State
#### Buttons for different states
{{ disabled }}
### 03 Component Style
#### Different sizes of buttons
{{ size }}
#### Different shaped buttons
{{ shape }}
#### Different color theme buttons
{{ theme }}
## API
### Button Props
name | type | default | description | required
-- | -- | -- | -- | --
custom-style | Object | - | CSS(Cascading Style Sheets) | N
app-parameter | String | - | \- | N
block | Boolean | false | make button to be a block-level element | N
content | String | - | button's children elements | N
custom-dataset | String / Number / Boolean / Object / Array | {} | Typescript`string \| number \| boolean \| object \| Array<string \| number \| boolean \| object>` | N
disabled | Boolean | undefined | disable the button, make it can not be clicked | N
ghost | Boolean | false | make background-color to be transparent | N
hover-class | String | - | \- | N
hover-start-time | Number | 20 | \- | N
hover-stay-time | Number | 70 | \- | N
hover-stop-propagation | Boolean | false | \- | N
icon | String / Object | - | icon name | N
lang | String | - | message language。options: en/zh_CN/zh_TW | N
loading | Boolean | false | set button to be loading state | N
loading-props | Object | {} | Typescript`LoadingProps`[Loading API Documents](./loading?tab=api)。[see more ts definition](https://github.com/Tencent/tdesign-miniprogram/tree/develop/packages/uniapp-components/button/type.ts) | N
open-type | String | - | open type of button, [Miniprogram Button](https://developers.weixin.qq.com/miniprogram/dev/component/button.html)。options: contact/share/getPhoneNumber/getUserInfo/launchApp/openSetting/feedback/chooseAvatar/agreePrivacyAuthorization | N
phone-number-no-quota-toast | Boolean | true | \- | N
send-message-img | String | 截图 | \- | N
send-message-path | String | 当前分享路径 | \- | N
send-message-title | String | 当前标题 | \- | N
session-from | String | - | \- | N
shape | String | rectangle | button shape。options: rectangle/square/round/circle | N
show-message-card | Boolean | false | \- | N
size | String | medium | a button has four size。options: extra-small/small/medium/large | N
t-id | String | - | id | N
theme | String | default | button theme。options: default/primary/danger/light | N
type | String | - | type of button element, same as formType of Miniprogram。options: submit/reset | N
variant | String | base | variant of button。options: base/outline/dashed/text | N
### Button Events
name | params | description
-- | -- | --
agreeprivacyauthorization | \- | \-
chooseavatar | \- | \-
click | `(e: MouseEvent)` | trigger on click
contact | \- | \-
createliveactivity | \- | \-
error | \- | \-
getphonenumber | \- | \-
getrealtimephonenumber | \- | \-
getuserinfo | \- | \-
launchapp | \- | \-
opensetting | \- | \-
### Button Slots
name | Description
-- | --
\- | \-
content | button's children elements
suffix | \-
### Button External Classes
className | Description
-- | --
t-class | \-
t-class-icon | class name of icon
t-class-loading | class name of loading
### CSS Variables
The component provides the following CSS variables, which can be used to customize styles.
Name | Default Value | Description
-- | -- | --
--td-button-border-radius | @radius-default | -
--td-button-border-width | 4rpx | -
--td-button-danger-active-bg-color | @error-color-active | -
--td-button-danger-active-border-color | @error-color-active | -
--td-button-danger-bg-color | @error-color | -
--td-button-danger-border-color | @error-color | -
--td-button-danger-color | @text-color-anti | -
--td-button-danger-dashed-border-color | @button-danger-dashed-color | -
--td-button-danger-dashed-color | @error-color | -
--td-button-danger-dashed-disabled-color | @button-danger-disabled-color | -
--td-button-danger-disabled-bg | @error-color-3 | -
--td-button-danger-disabled-border-color | @error-color-3 | -
--td-button-danger-disabled-color | @font-white-1 | -
--td-button-danger-outline-active-bg-color | @bg-color-container-active | -
--td-button-danger-outline-active-border-color | @error-color-active | -
--td-button-danger-outline-border-color | @button-danger-outline-color | -
--td-button-danger-outline-color | @error-color | -
--td-button-danger-outline-disabled-color | @error-color-3 | -
--td-button-danger-text-active-bg-color | @bg-color-container-active | -
--td-button-danger-text-color | @error-color | -
--td-button-danger-text-disabled-color | @button-danger-disabled-color | -
--td-button-default-active-bg-color | @bg-color-component-active | -
--td-button-default-active-border-color | @bg-color-component-active | -
--td-button-default-bg-color | @bg-color-component | -
--td-button-default-border-color | @bg-color-component | -
--td-button-default-color | @text-color-primary | -
--td-button-default-disabled-bg | @bg-color-component-disabled | -
--td-button-default-disabled-border-color | @bg-color-component-disabled | -
--td-button-default-disabled-color | @text-color-disabled | -
--td-button-default-outline-active-bg-color | @bg-color-container-active | -
--td-button-default-outline-active-border-color | @component-border | -
--td-button-default-outline-border-color | @component-border | -
--td-button-default-outline-color | @text-color-primary | -
--td-button-default-outline-disabled-color | @component-border | -
--td-button-default-text-active-bg-color | @bg-color-container-active | -
--td-button-extra-small-font-size | @font-size-base | -
--td-button-extra-small-height | 56rpx | -
--td-button-extra-small-icon-size | 36rpx | -
--td-button-extra-small-padding-horizontal | 16rpx | -
--td-button-font-weight | 600 | -
--td-button-ghost-border-color | @button-ghost-color | -
--td-button-ghost-color | @text-color-anti | -
--td-button-ghost-danger-border-color | @error-color | -
--td-button-ghost-danger-color | @error-color | -
--td-button-ghost-danger-hover-color | @error-color-active | -
--td-button-ghost-disabled-color | @font-white-4 | -
--td-button-ghost-hover-color | @font-white-2 | -
--td-button-ghost-primary-border-color | @brand-color | -
--td-button-ghost-primary-color | @brand-color | -
--td-button-ghost-primary-hover-color | @brand-color-active | -
--td-button-icon-border-radius | 8rpx | -
--td-button-icon-spacer | @spacer | -
--td-button-large-font-size | @font-size-m | -
--td-button-large-height | 96rpx | -
--td-button-large-icon-size | 48rpx | -
--td-button-large-padding-horizontal | 40rpx | -
--td-button-light-active-bg-color | @brand-color-light-active | -
--td-button-light-active-border-color | @brand-color-light-active | -
--td-button-light-bg-color | @brand-color-light | -
--td-button-light-border-color | @brand-color-light | -
--td-button-light-color | @brand-color | -
--td-button-light-disabled-bg | @brand-color-light | -
--td-button-light-disabled-border-color | @brand-color-light | -
--td-button-light-disabled-color | @brand-color-disabled | -
--td-button-light-outline-active-bg-color | @brand-color-light-active | -
--td-button-light-outline-active-border-color | @brand-color-active | -
--td-button-light-outline-bg-color | @brand-color-light | -
--td-button-light-outline-border-color | @button-light-outline-color | -
--td-button-light-outline-color | @brand-color | -
--td-button-light-outline-disabled-color | @brand-color-disabled | -
--td-button-light-text-active-bg-color | @bg-color-container-active | -
--td-button-light-text-color | @brand-color | -
--td-button-medium-font-size | @font-size-m | -
--td-button-medium-height | 80rpx | -
--td-button-medium-icon-size | 40rpx | -
--td-button-medium-padding-horizontal | 32rpx | -
--td-button-primary-active-bg-color | @brand-color-active | -
--td-button-primary-active-border-color | @brand-color-active | -
--td-button-primary-bg-color | @brand-color | -
--td-button-primary-border-color | @brand-color | -
--td-button-primary-color | @text-color-anti | -
--td-button-primary-dashed-border-color | @button-primary-dashed-color | -
--td-button-primary-dashed-color | @brand-color | -
--td-button-primary-dashed-disabled-color | @brand-color-disabled | -
--td-button-primary-disabled-bg | @brand-color-disabled | -
--td-button-primary-disabled-border-color | @brand-color-disabled | -
--td-button-primary-disabled-color | @text-color-anti | -
--td-button-primary-outline-active-bg-color | @bg-color-container-active | -
--td-button-primary-outline-active-border-color | @brand-color-active | -
--td-button-primary-outline-border-color | @button-primary-outline-color | -
--td-button-primary-outline-color | @brand-color | -
--td-button-primary-outline-disabled-color | @brand-color-disabled | -
--td-button-primary-text-active-bg-color | @bg-color-container-active | -
--td-button-primary-text-color | @brand-color | -
--td-button-primary-text-disabled-color | @brand-color-disabled | -
--td-button-small-font-size | @font-size-base | -
--td-button-small-height | 64rpx | -
--td-button-small-icon-size | 36rpx | -
--td-button-small-padding-horizontal | 24rpx | -

View File

@@ -0,0 +1,229 @@
---
title: Button 按钮
description: 用于开启一个闭环的操作任务,如“删除”对象、“购买”商品等。
spline: base
isComponent: true
---
## 引入
可在 `main.ts` 或在需要使用的页面或组件中引入。
```js
import TButton from '@tdesign/uniapp/button/button.vue';
```
### 01 组件类型
#### 基础按钮
{{ base }}
#### 图标按钮
{{ icon-btn }}
#### 幽灵按钮
{{ ghost-btn }}
#### 组合按钮
{{ group-btn }}
#### 通栏按钮
{{ block-btn }}
### 02 组件状态
#### 按钮禁用态
{{ disabled }}
### 03 组件样式
#### 按钮尺寸
{{ size }}
#### 按钮形状
{{ shape }}
#### 按钮主题
{{ theme }}
## API
### Button Props
名称 | 类型 | 默认值 | 描述 | 必传
-- | -- | -- | -- | --
custom-style | Object | - | 自定义样式 | N
app-parameter | String | - | 打开 APP 时,向 APP 传递的参数open-type=launchApp时有效 | N
block | Boolean | false | 是否为块级元素 | N
content | String | - | 按钮内容 | N
custom-dataset | String / Number / Boolean / Object / Array | {} | 自定义 dataset可通过 event.currentTarget.dataset.custom 获取。TS 类型:`string \| number \| boolean \| object \| Array<string \| number \| boolean \| object>` | N
disabled | Boolean | undefined | 禁用状态。优先级Button.disabled > Form.disabled | N
ghost | Boolean | false | 是否为幽灵按钮(镂空按钮) | N
hover-class | String | - | 指定按钮按下去的样式类,按钮不为加载或禁用状态时有效。当 `hover-class="none"` 时,没有点击态效果 | N
hover-start-time | Number | 20 | 按住后多久出现点击态,单位毫秒 | N
hover-stay-time | Number | 70 | 手指松开后点击态保留时间,单位毫秒 | N
hover-stop-propagation | Boolean | false | 指定是否阻止本节点的祖先节点出现点击态 | N
icon | String / Object | - | 图标名称。值为字符串表示图标名称,值为 `Object` 类型,表示透传至 `icon` | N
lang | String | - | 指定返回用户信息的语言zh_CN 简体中文zh_TW 繁体中文en 英文。<br />具体释义:<br />`en` 英文;<br />`zh_CN` 简体中文;<br />`zh_TW` 繁体中文。<br />[小程序官方文档](https://developers.weixin.qq.com/miniprogram/dev/component/button.html)。可选项en/zh_CN/zh_TW | N
loading | Boolean | false | 是否显示为加载状态 | N
loading-props | Object | {} | 透传 Loading 组件全部属性。TS 类型:`LoadingProps`[Loading API Documents](./loading?tab=api)。[详细类型定义](https://github.com/Tencent/tdesign-miniprogram/tree/develop/packages/uniapp-components/button/type.ts) | N
open-type | String | - | 微信开放能力。<br />具体释义:<br />`contact` 打开客服会话,如果用户在会话中点击消息卡片后返回小程序,可以从 bindcontact 回调中获得具体信息,<a href="https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/customer-message/customer-message.html">具体说明</a> *鸿蒙 OS 暂不支持*<br />`liveActivity` 通过前端获取<a href="https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/subscribe-message-2.html">新的一次性订阅消息下发机制</a>使用的 code<br />`share` 触发用户转发,使用前建议先阅读<a href="https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/share.html#使用指引">使用指引</a><br />`getPhoneNumber` 获取用户手机号,可以从 bindgetphonenumber 回调中获取到用户信息,<a href="https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/getPhoneNumber.html">具体说明</a> *小程序插件中不能使用*<br />`getUserInfo` 获取用户信息,可以从 bindgetuserinfo 回调中获取到用户信息 *小程序插件中不能使用*<br />`launchApp` 打开APP可以通过 app-parameter 属性设定向 APP 传的参数<a href="https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/launchApp.html">具体说明</a><br />`openSetting` 打开授权设置页;<br />`feedback` 打开“意见反馈”页面,用户可提交反馈内容并上传<a href="https://developers.weixin.qq.com/miniprogram/dev/api/base/debug/wx.getLogManager.html">日志</a>,开发者可以登录<a href="https://mp.weixin.qq.com/">小程序管理后台</a>后进入左侧菜单“客服反馈”页面获取到反馈内容;<br />`chooseAvatar` 获取用户头像,可以从 bindchooseavatar 回调中获取到头像信息;<br />`agreePrivacyAuthorization`用户同意隐私协议按钮。用户点击一次此按钮后,所有隐私接口可以正常调用。可通过`bindagreeprivacyauthorization`监听用户同意隐私协议事件。隐私合规开发指南详情可见《<a href="https://developers.weixin.qq.com/miniprogram/dev/framework/user-privacy/PrivacyAuthorize.html">小程序隐私协议开发指南</a>》。<br />[小程序官方文档](https://developers.weixin.qq.com/miniprogram/dev/component/button.html)。可选项contact/share/getPhoneNumber/getUserInfo/launchApp/openSetting/feedback/chooseAvatar/agreePrivacyAuthorization | N
phone-number-no-quota-toast | Boolean | true | 原生按钮属性当手机号快速验证或手机号实时验证额度用尽时是否对用户展示“申请获取你的手机号但该功能使用次数已达当前小程序上限暂时无法使用”的提示默认展示open-type="getPhoneNumber" 或 open-type="getRealtimePhoneNumber" 时有效 | N
send-message-img | String | 截图 | 会话内消息卡片图片open-type="contact"时有效 | N
send-message-path | String | 当前分享路径 | 会话内消息卡片点击跳转小程序路径open-type="contact"时有效 | N
send-message-title | String | 当前标题 | 会话内消息卡片标题open-type="contact"时有效 | N
session-from | String | - | 会话来源open-type="contact"时有效 | N
shape | String | rectangle | 按钮形状,有 4 种长方形、正方形、圆角长方形、圆形。可选项rectangle/square/round/circle | N
show-message-card | Boolean | false | 是否显示会话内消息卡片,设置此参数为 true用户进入客服会话会在右下角显示"可能要发送的小程序"提示用户点击后可以快速发送小程序消息open-type="contact"时有效 | N
size | String | medium | 组件尺寸。可选项extra-small/small/medium/large | N
t-id | String | - | 按钮标签id | N
theme | String | default | 组件风格依次为品牌色、危险色。可选项default/primary/danger/light | N
type | String | - | 同小程序的 formType。可选项submit/reset | N
variant | String | base | 按钮形式基础、线框、虚线、文字。可选项base/outline/dashed/text | N
### Button Events
名称 | 参数 | 描述
-- | -- | --
agreeprivacyauthorization | \- | 原生按钮属性用户同意隐私协议事件回调open-type=agreePrivacyAuthorization时有效 Tips: 如果使用 onNeedPrivacyAuthorization 接口,需要在 bindagreeprivacyauthorization 触发后再调用 resolve({ event: "agree", buttonId })
chooseavatar | \- | 原生按钮属性,获取用户头像回调,`open-type=chooseAvatar` 时有效。返回 `e.detail.avatarUrl` 为头像临时文件链接
click | `(e: MouseEvent)` | 点击时触发
contact | \- | 原生按钮属性,客服消息回调,`open-type="contact"` 时有效
createliveactivity | \- | 新的一次性订阅消息下发机制回调,`open-type=liveActivity` 时有效
error | \- | 原生按钮属性,当使用开放能力时,发生错误的回调,`open-type=launchApp` 时有效
getphonenumber | \- | 原生按钮属性手机号快速验证回调open-type=getPhoneNumber时有效。Tips在触发 bindgetphonenumber 回调后应立即隐藏手机号按钮组件,或置为 disabled 状态,避免用户重复授权手机号产生额外费用
getrealtimephonenumber | \- | 原生按钮属性手机号实时验证回调open-type=getRealtimePhoneNumber 时有效。Tips在触发 bindgetrealtimephonenumber 回调后应立即隐藏手机号按钮组件,或置为 disabled 状态,避免用户重复授权手机号产生额外费用
getuserinfo | \- | 原生按钮属性用户点击该按钮时会返回获取到的用户信息回调的detail数据与wx.getUserInfo返回的一致open-type="getUserInfo"时有效
launchapp | \- | 打开 APP 成功的回调,`open-type=launchApp` 时有效
opensetting | \- | 原生按钮属性在打开授权设置页后回调open-type=openSetting时有效
### Button Slots
名称 | 描述
-- | --
\- | 默认插槽,作用同 `content` 插槽
content | 自定义 `content` 显示内容
suffix | 右侧内容,可用于定义右侧图标
### Button External Classes
类名 | 描述
-- | --
t-class | 根节点样式类
t-class-icon | 图标样式类
t-class-loading | 加载样式类
### CSS Variables
组件提供了下列 CSS 变量,可用于自定义样式。
名称 | 默认值 | 描述
-- | -- | --
--td-button-border-radius | @radius-default | -
--td-button-border-width | 4rpx | -
--td-button-danger-active-bg-color | @error-color-active | -
--td-button-danger-active-border-color | @error-color-active | -
--td-button-danger-bg-color | @error-color | -
--td-button-danger-border-color | @error-color | -
--td-button-danger-color | @text-color-anti | -
--td-button-danger-dashed-border-color | @button-danger-dashed-color | -
--td-button-danger-dashed-color | @error-color | -
--td-button-danger-dashed-disabled-color | @button-danger-disabled-color | -
--td-button-danger-disabled-bg | @error-color-3 | -
--td-button-danger-disabled-border-color | @error-color-3 | -
--td-button-danger-disabled-color | @font-white-1 | -
--td-button-danger-outline-active-bg-color | @bg-color-container-active | -
--td-button-danger-outline-active-border-color | @error-color-active | -
--td-button-danger-outline-border-color | @button-danger-outline-color | -
--td-button-danger-outline-color | @error-color | -
--td-button-danger-outline-disabled-color | @error-color-3 | -
--td-button-danger-text-active-bg-color | @bg-color-container-active | -
--td-button-danger-text-color | @error-color | -
--td-button-danger-text-disabled-color | @button-danger-disabled-color | -
--td-button-default-active-bg-color | @bg-color-component-active | -
--td-button-default-active-border-color | @bg-color-component-active | -
--td-button-default-bg-color | @bg-color-component | -
--td-button-default-border-color | @bg-color-component | -
--td-button-default-color | @text-color-primary | -
--td-button-default-disabled-bg | @bg-color-component-disabled | -
--td-button-default-disabled-border-color | @bg-color-component-disabled | -
--td-button-default-disabled-color | @text-color-disabled | -
--td-button-default-outline-active-bg-color | @bg-color-container-active | -
--td-button-default-outline-active-border-color | @component-border | -
--td-button-default-outline-border-color | @component-border | -
--td-button-default-outline-color | @text-color-primary | -
--td-button-default-outline-disabled-color | @component-border | -
--td-button-default-text-active-bg-color | @bg-color-container-active | -
--td-button-extra-small-font-size | @font-size-base | -
--td-button-extra-small-height | 56rpx | -
--td-button-extra-small-icon-size | 36rpx | -
--td-button-extra-small-padding-horizontal | 16rpx | -
--td-button-font-weight | 600 | -
--td-button-ghost-border-color | @button-ghost-color | -
--td-button-ghost-color | @text-color-anti | -
--td-button-ghost-danger-border-color | @error-color | -
--td-button-ghost-danger-color | @error-color | -
--td-button-ghost-danger-hover-color | @error-color-active | -
--td-button-ghost-disabled-color | @font-white-4 | -
--td-button-ghost-hover-color | @font-white-2 | -
--td-button-ghost-primary-border-color | @brand-color | -
--td-button-ghost-primary-color | @brand-color | -
--td-button-ghost-primary-hover-color | @brand-color-active | -
--td-button-icon-border-radius | 8rpx | -
--td-button-icon-spacer | @spacer | -
--td-button-large-font-size | @font-size-m | -
--td-button-large-height | 96rpx | -
--td-button-large-icon-size | 48rpx | -
--td-button-large-padding-horizontal | 40rpx | -
--td-button-light-active-bg-color | @brand-color-light-active | -
--td-button-light-active-border-color | @brand-color-light-active | -
--td-button-light-bg-color | @brand-color-light | -
--td-button-light-border-color | @brand-color-light | -
--td-button-light-color | @brand-color | -
--td-button-light-disabled-bg | @brand-color-light | -
--td-button-light-disabled-border-color | @brand-color-light | -
--td-button-light-disabled-color | @brand-color-disabled | -
--td-button-light-outline-active-bg-color | @brand-color-light-active | -
--td-button-light-outline-active-border-color | @brand-color-active | -
--td-button-light-outline-bg-color | @brand-color-light | -
--td-button-light-outline-border-color | @button-light-outline-color | -
--td-button-light-outline-color | @brand-color | -
--td-button-light-outline-disabled-color | @brand-color-disabled | -
--td-button-light-text-active-bg-color | @bg-color-container-active | -
--td-button-light-text-color | @brand-color | -
--td-button-medium-font-size | @font-size-m | -
--td-button-medium-height | 80rpx | -
--td-button-medium-icon-size | 40rpx | -
--td-button-medium-padding-horizontal | 32rpx | -
--td-button-primary-active-bg-color | @brand-color-active | -
--td-button-primary-active-border-color | @brand-color-active | -
--td-button-primary-bg-color | @brand-color | -
--td-button-primary-border-color | @brand-color | -
--td-button-primary-color | @text-color-anti | -
--td-button-primary-dashed-border-color | @button-primary-dashed-color | -
--td-button-primary-dashed-color | @brand-color | -
--td-button-primary-dashed-disabled-color | @brand-color-disabled | -
--td-button-primary-disabled-bg | @brand-color-disabled | -
--td-button-primary-disabled-border-color | @brand-color-disabled | -
--td-button-primary-disabled-color | @text-color-anti | -
--td-button-primary-outline-active-bg-color | @bg-color-container-active | -
--td-button-primary-outline-active-border-color | @brand-color-active | -
--td-button-primary-outline-border-color | @button-primary-outline-color | -
--td-button-primary-outline-color | @brand-color | -
--td-button-primary-outline-disabled-color | @brand-color-disabled | -
--td-button-primary-text-active-bg-color | @bg-color-container-active | -
--td-button-primary-text-color | @brand-color | -
--td-button-primary-text-disabled-color | @brand-color-disabled | -
--td-button-small-font-size | @font-size-base | -
--td-button-small-height | 64rpx | -
--td-button-small-icon-size | 36rpx | -
--td-button-small-padding-horizontal | 24rpx | -

View File

@@ -0,0 +1,514 @@
.t-button--size-extra-small {
font-size: var(--td-button-extra-small-font-size, var(--td-font-size-base, 28rpx));
padding-left: var(--td-button-extra-small-padding-horizontal, 16rpx);
padding-right: var(--td-button-extra-small-padding-horizontal, 16rpx);
height: var(--td-button-extra-small-height, 56rpx);
line-height: var(--td-button-extra-small-height, 56rpx);
}
.t-button--size-extra-small .t-button__icon {
font-size: var(--td-button-extra-small-icon-size, 36rpx);
}
.t-button--size-small {
font-size: var(--td-button-small-font-size, var(--td-font-size-base, 28rpx));
padding-left: var(--td-button-small-padding-horizontal, 24rpx);
padding-right: var(--td-button-small-padding-horizontal, 24rpx);
height: var(--td-button-small-height, 64rpx);
line-height: var(--td-button-small-height, 64rpx);
}
.t-button--size-small .t-button__icon {
font-size: var(--td-button-small-icon-size, 36rpx);
}
.t-button--size-medium {
font-size: var(--td-button-medium-font-size, var(--td-font-size-m, 32rpx));
padding-left: var(--td-button-medium-padding-horizontal, 32rpx);
padding-right: var(--td-button-medium-padding-horizontal, 32rpx);
height: var(--td-button-medium-height, 80rpx);
line-height: var(--td-button-medium-height, 80rpx);
}
.t-button--size-medium .t-button__icon {
font-size: var(--td-button-medium-icon-size, 40rpx);
}
.t-button--size-large {
font-size: var(--td-button-large-font-size, var(--td-font-size-m, 32rpx));
padding-left: var(--td-button-large-padding-horizontal, 40rpx);
padding-right: var(--td-button-large-padding-horizontal, 40rpx);
height: var(--td-button-large-height, 96rpx);
line-height: var(--td-button-large-height, 96rpx);
}
.t-button--size-large .t-button__icon {
font-size: var(--td-button-large-icon-size, 48rpx);
}
.t-button--default {
color: var(--td-button-default-color, var(--td-text-color-primary, var(--td-font-gray-1, rgba(0, 0, 0, 0.9))));
background-color: var(--td-button-default-bg-color, var(--td-bg-color-component, var(--td-gray-color-3, #e7e7e7)));
border-color: var(--td-button-default-border-color, var(--td-bg-color-component, var(--td-gray-color-3, #e7e7e7)));
}
.t-button--default::after {
border-width: var(--td-button-border-width, 4rpx);
border-color: var(--td-button-default-border-color, var(--td-bg-color-component, var(--td-gray-color-3, #e7e7e7)));
}
.t-button--default.t-button--hover {
z-index: 0;
}
.t-button--default.t-button--hover,
.t-button--default.t-button--hover::after {
background-color: var(--td-button-default-active-bg-color, var(--td-bg-color-component-active, var(--td-gray-color-6, #a6a6a6)));
border-color: var(--td-button-default-active-border-color, var(--td-bg-color-component-active, var(--td-gray-color-6, #a6a6a6)));
}
.t-button--default.t-button--disabled {
color: var(--td-button-default-disabled-color, var(--td-text-color-disabled, var(--td-font-gray-4, rgba(0, 0, 0, 0.26))));
background-color: var(--td-button-default-disabled-bg, var(--td-bg-color-component-disabled, var(--td-gray-color-2, #eeeeee)));
}
.t-button--default.t-button--disabled,
.t-button--default.t-button--disabled::after {
border-color: var(--td-button-default-disabled-border-color, var(--td-bg-color-component-disabled, var(--td-gray-color-2, #eeeeee)));
}
.t-button--primary {
color: var(--td-button-primary-color, var(--td-text-color-anti, var(--td-font-white-1, #ffffff)));
background-color: var(--td-button-primary-bg-color, var(--td-brand-color, var(--td-primary-color-7, #0052d9)));
border-color: var(--td-button-primary-border-color, var(--td-brand-color, var(--td-primary-color-7, #0052d9)));
}
.t-button--primary::after {
border-width: var(--td-button-border-width, 4rpx);
border-color: var(--td-button-primary-border-color, var(--td-brand-color, var(--td-primary-color-7, #0052d9)));
}
.t-button--primary.t-button--hover {
z-index: 0;
}
.t-button--primary.t-button--hover,
.t-button--primary.t-button--hover::after {
background-color: var(--td-button-primary-active-bg-color, var(--td-brand-color-active, var(--td-primary-color-8, #003cab)));
border-color: var(--td-button-primary-active-border-color, var(--td-brand-color-active, var(--td-primary-color-8, #003cab)));
}
.t-button--primary.t-button--disabled {
color: var(--td-button-primary-disabled-color, var(--td-text-color-anti, var(--td-font-white-1, #ffffff)));
background-color: var(--td-button-primary-disabled-bg, var(--td-brand-color-disabled, var(--td-primary-color-3, #b5c7ff)));
}
.t-button--primary.t-button--disabled,
.t-button--primary.t-button--disabled::after {
border-color: var(--td-button-primary-disabled-border-color, var(--td-brand-color-disabled, var(--td-primary-color-3, #b5c7ff)));
}
.t-button--light {
color: var(--td-button-light-color, var(--td-brand-color, var(--td-primary-color-7, #0052d9)));
background-color: var(--td-button-light-bg-color, var(--td-brand-color-light, var(--td-primary-color-1, #f2f3ff)));
border-color: var(--td-button-light-border-color, var(--td-brand-color-light, var(--td-primary-color-1, #f2f3ff)));
}
.t-button--light::after {
border-width: var(--td-button-border-width, 4rpx);
border-color: var(--td-button-light-border-color, var(--td-brand-color-light, var(--td-primary-color-1, #f2f3ff)));
}
.t-button--light.t-button--hover {
z-index: 0;
}
.t-button--light.t-button--hover,
.t-button--light.t-button--hover::after {
background-color: var(--td-button-light-active-bg-color, var(--td-brand-color-light-active, var(--td-primary-color-2, #d9e1ff)));
border-color: var(--td-button-light-active-border-color, var(--td-brand-color-light-active, var(--td-primary-color-2, #d9e1ff)));
}
.t-button--light.t-button--disabled {
color: var(--td-button-light-disabled-color, var(--td-brand-color-disabled, var(--td-primary-color-3, #b5c7ff)));
background-color: var(--td-button-light-disabled-bg, var(--td-brand-color-light, var(--td-primary-color-1, #f2f3ff)));
}
.t-button--light.t-button--disabled,
.t-button--light.t-button--disabled::after {
border-color: var(--td-button-light-disabled-border-color, var(--td-brand-color-light, var(--td-primary-color-1, #f2f3ff)));
}
.t-button--danger {
color: var(--td-button-danger-color, var(--td-text-color-anti, var(--td-font-white-1, #ffffff)));
background-color: var(--td-button-danger-bg-color, var(--td-error-color, var(--td-error-color-6, #d54941)));
border-color: var(--td-button-danger-border-color, var(--td-error-color, var(--td-error-color-6, #d54941)));
}
.t-button--danger::after {
border-width: var(--td-button-border-width, 4rpx);
border-color: var(--td-button-danger-border-color, var(--td-error-color, var(--td-error-color-6, #d54941)));
}
.t-button--danger.t-button--hover {
z-index: 0;
}
.t-button--danger.t-button--hover,
.t-button--danger.t-button--hover::after {
background-color: var(--td-button-danger-active-bg-color, var(--td-error-color-active, var(--td-error-color-7, #ad352f)));
border-color: var(--td-button-danger-active-border-color, var(--td-error-color-active, var(--td-error-color-7, #ad352f)));
}
.t-button--danger.t-button--disabled {
color: var(--td-button-danger-disabled-color, var(--td-font-white-1, #ffffff));
background-color: var(--td-button-danger-disabled-bg, var(--td-error-color-3, #ffb9b0));
}
.t-button--danger.t-button--disabled,
.t-button--danger.t-button--disabled::after {
border-color: var(--td-button-danger-disabled-border-color, var(--td-error-color-3, #ffb9b0));
}
.t-button {
display: inline-flex;
align-items: center;
justify-content: center;
position: relative;
white-space: nowrap;
text-align: center;
background-image: none;
transition: all 0.3s;
touch-action: manipulation;
border-radius: var(--td-button-border-radius, var(--td-radius-default, 12rpx));
outline: none;
font-family: PingFang SC, Microsoft YaHei, Arial Regular;
font-weight: var(--td-button-font-weight, 600);
vertical-align: top;
box-sizing: border-box;
cursor: pointer;
-webkit-tap-highlight-color: transparent;
-webkit-user-select: none;
user-select: none;
/* stylelint-disable-next-line */
-webkit-appearance: none;
}
.t-button::after {
border-radius: calc(var(--td-button-border-radius, var(--td-radius-default, 12rpx)) * 2);
}
.t-button--text {
color: var(--td-button-default-color, var(--td-text-color-primary, var(--td-font-gray-1, rgba(0, 0, 0, 0.9))));
background-color: transparent;
}
.t-button--text,
.t-button--text::after {
border: 0;
}
.t-button--text.t-button--hover,
.t-button--text.t-button--hover::after {
background-color: var(--td-button-default-text-active-bg-color, var(--td-bg-color-container-active, var(--td-gray-color-3, #e7e7e7)));
}
.t-button--text.t-button--primary {
color: var(--td-button-primary-text-color, var(--td-brand-color, var(--td-primary-color-7, #0052d9)));
background-color: transparent;
}
.t-button--text.t-button--primary.t-button--hover,
.t-button--text.t-button--primary.t-button--hover::after {
background-color: var(--td-button-primary-text-active-bg-color, var(--td-bg-color-container-active, var(--td-gray-color-3, #e7e7e7)));
}
.t-button--text.t-button--primary.t-button--disabled {
color: var(--td-button-primary-text-disabled-color, var(--td-brand-color-disabled, var(--td-primary-color-3, #b5c7ff)));
background-color: var(--td-bg-color-container, var(--td-font-white-1, #ffffff));
}
.t-button--text.t-button--danger {
color: var(--td-button-danger-text-color, var(--td-error-color, var(--td-error-color-6, #d54941)));
background-color: transparent;
}
.t-button--text.t-button--danger.t-button--hover,
.t-button--text.t-button--danger.t-button--hover::after {
background-color: var(--td-button-danger-text-active-bg-color, var(--td-bg-color-container-active, var(--td-gray-color-3, #e7e7e7)));
}
.t-button--text.t-button--danger.t-button--disabled {
color: var(--td-button-danger-text-disabled-color, var(--td-button-danger-disabled-color, var(--td-font-white-1, #ffffff)));
background-color: var(--td-bg-color-container, var(--td-font-white-1, #ffffff));
}
.t-button--text.t-button--light {
color: var(--td-button-light-text-color, var(--td-brand-color, var(--td-primary-color-7, #0052d9)));
background-color: transparent;
}
.t-button--text.t-button--light.t-button--hover,
.t-button--text.t-button--light.t-button--hover::after {
background-color: var(--td-button-light-text-active-bg-color, var(--td-bg-color-container-active, var(--td-gray-color-3, #e7e7e7)));
}
.t-button--text.t-button--disabled {
color: var(--td-button-default-disabled-color, var(--td-text-color-disabled, var(--td-font-gray-4, rgba(0, 0, 0, 0.26))));
}
.t-button--outline {
color: var(--td-button-default-outline-color, var(--td-text-color-primary, var(--td-font-gray-1, rgba(0, 0, 0, 0.9))));
background-color: var(--td-bg-color-container, var(--td-font-white-1, #ffffff));
}
.t-button--outline,
.t-button--outline::after {
border-color: var(--td-button-default-outline-border-color, var(--td-component-border, var(--td-gray-color-4, #dcdcdc)));
}
.t-button--outline.t-button--hover,
.t-button--outline.t-button--hover::after {
background-color: var(--td-button-default-outline-active-bg-color, var(--td-bg-color-container-active, var(--td-gray-color-3, #e7e7e7)));
border-color: var(--td-button-default-outline-active-border-color, var(--td-component-border, var(--td-gray-color-4, #dcdcdc)));
}
.t-button--outline.t-button--disabled {
color: var(--td-button-default-outline-disabled-color, var(--td-component-border, var(--td-gray-color-4, #dcdcdc)));
}
.t-button--outline.t-button--disabled,
.t-button--outline.t-button--disabled::after {
border-color: var(--td-button-default-outline-disabled-color, var(--td-component-border, var(--td-gray-color-4, #dcdcdc)));
}
.t-button--outline.t-button--primary {
color: var(--td-button-primary-outline-color, var(--td-brand-color, var(--td-primary-color-7, #0052d9)));
}
.t-button--outline.t-button--primary,
.t-button--outline.t-button--primary::after {
border-color: var(--td-button-primary-outline-border-color, var(--td-button-primary-outline-color, var(--td-brand-color, var(--td-primary-color-7, #0052d9))));
}
.t-button--outline.t-button--primary.t-button--hover {
color: var(--td-button-primary-outline-active-border-color, var(--td-brand-color-active, var(--td-primary-color-8, #003cab)));
}
.t-button--outline.t-button--primary.t-button--hover::after {
background-color: var(--td-button-primary-outline-active-bg-color, var(--td-bg-color-container-active, var(--td-gray-color-3, #e7e7e7)));
border-color: var(--td-button-primary-outline-active-border-color, var(--td-brand-color-active, var(--td-primary-color-8, #003cab)));
}
.t-button--outline.t-button--primary.t-button--disabled {
background-color: transparent;
color: var(--td-button-primary-outline-disabled-color, var(--td-brand-color-disabled, var(--td-primary-color-3, #b5c7ff)));
}
.t-button--outline.t-button--primary.t-button--disabled,
.t-button--outline.t-button--primary.t-button--disabled::after {
border-color: var(--td-button-primary-outline-disabled-color, var(--td-brand-color-disabled, var(--td-primary-color-3, #b5c7ff)));
}
.t-button--outline.t-button--danger {
color: var(--td-button-danger-outline-color, var(--td-error-color, var(--td-error-color-6, #d54941)));
}
.t-button--outline.t-button--danger,
.t-button--outline.t-button--danger::after {
border-color: var(--td-button-danger-outline-border-color, var(--td-button-danger-outline-color, var(--td-error-color, var(--td-error-color-6, #d54941))));
}
.t-button--outline.t-button--danger.t-button--hover {
color: var(--td-button-danger-outline-active-border-color, var(--td-error-color-active, var(--td-error-color-7, #ad352f)));
}
.t-button--outline.t-button--danger.t-button--hover::after {
background-color: var(--td-button-danger-outline-active-bg-color, var(--td-bg-color-container-active, var(--td-gray-color-3, #e7e7e7)));
border-color: var(--td-button-danger-outline-active-border-color, var(--td-error-color-active, var(--td-error-color-7, #ad352f)));
}
.t-button--outline.t-button--danger.t-button--disabled {
background-color: var(--td-bg-color-container, var(--td-font-white-1, #ffffff));
color: var(--td-button-danger-outline-disabled-color, var(--td-error-color-3, #ffb9b0));
}
.t-button--outline.t-button--danger.t-button--disabled,
.t-button--outline.t-button--danger.t-button--disabled::after {
border-color: var(--td-button-danger-outline-disabled-color, var(--td-error-color-3, #ffb9b0));
}
.t-button--outline.t-button--light {
color: var(--td-button-light-outline-color, var(--td-brand-color, var(--td-primary-color-7, #0052d9)));
background-color: var(--td-button-light-outline-bg-color, var(--td-brand-color-light, var(--td-primary-color-1, #f2f3ff)));
}
.t-button--outline.t-button--light,
.t-button--outline.t-button--light::after {
border-color: var(--td-button-light-outline-border-color, var(--td-button-light-outline-color, var(--td-brand-color, var(--td-primary-color-7, #0052d9))));
}
.t-button--outline.t-button--light.t-button--hover {
color: var(--td-button-light-outline-active-border-color, var(--td-brand-color-active, var(--td-primary-color-8, #003cab)));
}
.t-button--outline.t-button--light.t-button--hover,
.t-button--outline.t-button--light.t-button--hover::after {
background-color: var(--td-button-light-outline-active-bg-color, var(--td-brand-color-light-active, var(--td-primary-color-2, #d9e1ff)));
border-color: var(--td-button-light-outline-active-border-color, var(--td-brand-color-active, var(--td-primary-color-8, #003cab)));
}
.t-button--outline.t-button--light.t-button--disabled {
background-color: var(--td-bg-color-container, var(--td-font-white-1, #ffffff));
color: var(--td-button-light-outline-disabled-color, var(--td-brand-color-disabled, var(--td-primary-color-3, #b5c7ff)));
}
.t-button--outline.t-button--light.t-button--disabled,
.t-button--outline.t-button--light.t-button--disabled::after {
border-color: var(--td-button-light-outline-disabled-color, var(--td-brand-color-disabled, var(--td-primary-color-3, #b5c7ff)));
}
.t-button--dashed {
background-color: var(--td-bg-color-container, var(--td-font-white-1, #ffffff));
border-style: dashed;
border-width: 2rpx;
}
.t-button--dashed::after {
border: 0;
}
.t-button--dashed.t-button--hover,
.t-button--dashed.t-button--hover::after {
background-color: var(--td-button-default-outline-active-bg-color, var(--td-bg-color-container-active, var(--td-gray-color-3, #e7e7e7)));
border-color: var(--td-button-default-outline-active-border-color, var(--td-component-border, var(--td-gray-color-4, #dcdcdc)));
}
.t-button--dashed.t-button--primary {
color: var(--td-button-primary-dashed-color, var(--td-brand-color, var(--td-primary-color-7, #0052d9)));
}
.t-button--dashed.t-button--primary,
.t-button--dashed.t-button--primary::after {
border-color: var(--td-button-primary-dashed-border-color, var(--td-button-primary-dashed-color, var(--td-brand-color, var(--td-primary-color-7, #0052d9))));
}
.t-button--dashed.t-button--primary.t-button--disabled {
background-color: var(--td-bg-color-container, var(--td-font-white-1, #ffffff));
color: var(--td-button-primary-dashed-disabled-color, var(--td-brand-color-disabled, var(--td-primary-color-3, #b5c7ff)));
}
.t-button--dashed.t-button--primary.t-button--disabled,
.t-button--dashed.t-button--primary.t-button--disabled::after {
border-color: var(--td-button-primary-dashed-disabled-color, var(--td-brand-color-disabled, var(--td-primary-color-3, #b5c7ff)));
}
.t-button--dashed.t-button--danger {
color: var(--td-button-danger-dashed-color, var(--td-error-color, var(--td-error-color-6, #d54941)));
}
.t-button--dashed.t-button--danger,
.t-button--dashed.t-button--danger::after {
border-color: var(--td-button-danger-dashed-border-color, var(--td-button-danger-dashed-color, var(--td-error-color, var(--td-error-color-6, #d54941))));
}
.t-button--dashed.t-button--danger.t-button--disabled {
background-color: transparent;
color: var(--td-button-danger-dashed-disabled-color, var(--td-button-danger-disabled-color, var(--td-font-white-1, #ffffff)));
}
.t-button--dashed.t-button--danger.t-button--disabled::after {
border-color: var(--td-button-danger-dashed-disabled-color, var(--td-button-danger-disabled-color, var(--td-font-white-1, #ffffff)));
}
.t-button--ghost {
background-color: transparent;
color: var(--td-button-ghost-color, var(--td-text-color-anti, var(--td-font-white-1, #ffffff)));
}
.t-button--ghost,
.t-button--ghost::after {
border-color: var(--td-button-ghost-border-color, var(--td-button-ghost-color, var(--td-text-color-anti, var(--td-font-white-1, #ffffff))));
}
.t-button--ghost.t-button--default.t-button--hover {
color: var(--td-button-ghost-hover-color, var(--td-font-white-2, rgba(255, 255, 255, 0.55)));
}
.t-button--ghost.t-button--default.t-button--hover,
.t-button--ghost.t-button--default.t-button--hover::after {
background-color: transparent;
border-color: var(--td-button-ghost-hover-color, var(--td-font-white-2, rgba(255, 255, 255, 0.55)));
}
.t-button--ghost.t-button--primary {
color: var(--td-button-ghost-primary-color, var(--td-brand-color, var(--td-primary-color-7, #0052d9)));
}
.t-button--ghost.t-button--primary,
.t-button--ghost.t-button--primary::after {
border-color: var(--td-button-ghost-primary-border-color, var(--td-brand-color, var(--td-primary-color-7, #0052d9)));
}
.t-button--ghost.t-button--primary.t-button--hover {
color: var(--td-button-ghost-primary-hover-color, var(--td-brand-color-active, var(--td-primary-color-8, #003cab)));
}
.t-button--ghost.t-button--primary.t-button--hover,
.t-button--ghost.t-button--primary.t-button--hover::after {
background-color: transparent;
border-color: var(--td-button-ghost-primary-hover-color, var(--td-brand-color-active, var(--td-primary-color-8, #003cab)));
}
.t-button--ghost.t-button--primary.t-button--text.t-button--hover,
.t-button--ghost.t-button--primary.t-button--text.t-button--hover::after {
background-color: var(--td-gray-color-10, #4b4b4b);
}
.t-button--ghost.t-button--primary.t-button--disabled {
background-color: transparent;
color: var(--td-button-ghost-disabled-color, var(--td-font-white-4, rgba(255, 255, 255, 0.22)));
}
.t-button--ghost.t-button--primary.t-button--disabled,
.t-button--ghost.t-button--primary.t-button--disabled::after {
border-color: var(--td-button-ghost-disabled-color, var(--td-font-white-4, rgba(255, 255, 255, 0.22)));
}
.t-button--ghost.t-button--danger {
color: var(--td-button-ghost-danger-color, var(--td-error-color, var(--td-error-color-6, #d54941)));
}
.t-button--ghost.t-button--danger,
.t-button--ghost.t-button--danger::after {
border-color: var(--td-button-ghost-danger-border-color, var(--td-error-color, var(--td-error-color-6, #d54941)));
}
.t-button--ghost.t-button--danger.t-button--hover {
color: var(--td-button-ghost-danger-hover-color, var(--td-error-color-active, var(--td-error-color-7, #ad352f)));
}
.t-button--ghost.t-button--danger.t-button--hover,
.t-button--ghost.t-button--danger.t-button--hover::after {
background-color: transparent;
border-color: var(--td-button-ghost-danger-hover-color, var(--td-error-color-active, var(--td-error-color-7, #ad352f)));
}
.t-button--ghost.t-button--danger.t-button--text.t-button--hover,
.t-button--ghost.t-button--danger.t-button--text.t-button--hover::after {
background-color: var(--td-gray-color-10, #4b4b4b);
}
.t-button--ghost.t-button--danger.t-button--disabled {
background-color: transparent;
color: var(--td-button-ghost-disabled-color, var(--td-font-white-4, rgba(255, 255, 255, 0.22)));
}
.t-button--ghost.t-button--danger.t-button--disabled,
.t-button--ghost.t-button--danger.t-button--disabled::after {
border-color: var(--td-button-ghost-disabled-color, var(--td-font-white-4, rgba(255, 255, 255, 0.22)));
}
.t-button--ghost.t-button--default.t-button--text.t-button--hover,
.t-button--ghost.t-button--default.t-button--text.t-button--hover::after {
background-color: var(--td-gray-color-10, #4b4b4b);
}
.t-button--ghost.t-button--default.t-button--disabled {
background-color: transparent;
color: var(--td-button-ghost-disabled-color, var(--td-font-white-4, rgba(255, 255, 255, 0.22)));
}
.t-button--ghost.t-button--default.t-button--disabled,
.t-button--ghost.t-button--default.t-button--disabled::after {
border-color: var(--td-button-ghost-disabled-color, var(--td-font-white-4, rgba(255, 255, 255, 0.22)));
}
.t-button__loading + .t-button__content:not(:empty),
.t-button__icon + .t-button__content:not(:empty) {
margin-left: 8rpx;
}
.t-button__icon {
border-radius: var(--td-button-icon-border-radius, 8rpx);
}
.t-button--round.t-button--size-large {
border-radius: calc(var(--td-button-large-height, 96rpx) / 2);
}
.t-button--round.t-button--size-large::after {
border-radius: var(--td-button-large-height, 96rpx);
}
.t-button--round.t-button--size-medium {
border-radius: calc(var(--td-button-medium-height, 80rpx) / 2);
}
.t-button--round.t-button--size-medium::after {
border-radius: var(--td-button-medium-height, 80rpx);
}
.t-button--round.t-button--size-small {
border-radius: calc(var(--td-button-small-height, 64rpx) / 2);
}
.t-button--round.t-button--size-small::after {
border-radius: var(--td-button-small-height, 64rpx);
}
.t-button--round.t-button--size-extra-small {
border-radius: calc(var(--td-button-extra-small-height, 56rpx) / 2);
}
.t-button--round.t-button--size-extra-small::after {
border-radius: var(--td-button-extra-small-height, 56rpx);
}
.t-button--square {
padding: 0;
}
.t-button--square.t-button--size-large {
width: var(--td-button-large-height, 96rpx);
}
.t-button--square.t-button--size-medium {
width: var(--td-button-medium-height, 80rpx);
}
.t-button--square.t-button--size-small {
width: var(--td-button-small-height, 64rpx);
}
.t-button--square.t-button--size-extra-small {
width: var(--td-button-extra-small-height, 56rpx);
}
.t-button--circle {
padding: 0;
border-radius: 50%;
}
.t-button--circle.t-button--size-large {
width: var(--td-button-large-height, 96rpx);
}
.t-button--circle.t-button--size-large::after {
border-radius: 50%;
}
.t-button--circle.t-button--size-medium {
width: var(--td-button-medium-height, 80rpx);
}
.t-button--circle.t-button--size-medium::after {
border-radius: 50%;
}
.t-button--circle.t-button--size-small {
width: var(--td-button-small-height, 64rpx);
}
.t-button--circle.t-button--size-small::after {
border-radius: 50%;
}
.t-button--circle.t-button--size-extra-small {
width: var(--td-button-extra-small-height, 56rpx);
}
.t-button--circle.t-button--size-extra-small::after {
border-radius: 50%;
}
.t-button--block {
display: flex;
width: 100%;
}
.t-button--disabled {
cursor: not-allowed;
}
.t-button__loading--wrapper {
display: flex;
align-items: center;
justify-content: center;
}
.t-button.t-button--hover::after {
z-index: -1;
}

View File

@@ -0,0 +1,244 @@
<template>
<button
:id="tId"
:style="tools._style([customStyle])"
:data-custom="customDataset"
:class="className"
:form-type="disabled || loading ? '' : type"
:open-type="disabled || loading ? '' : openType"
:hover-stop-propagation="hoverStopPropagation"
:hover-start-time="hoverStartTime"
:hover-stay-time="hoverStayTime"
:lang="lang"
:session-from="sessionFrom"
:hover-class="disabled || loading ? '' : hoverClass || classPrefix + '--hover'"
:send-message-title="sendMessageTitle"
:send-message-path="sendMessagePath"
:send-message-img="sendMessageImg"
:app-parameter="appParameter"
:show-message-card="showMessageCard"
:aria-label="ariaLabel"
@click.stop.prevent="handleTap"
@getuserinfo="getuserinfo"
@contact="contact"
@getphonenumber="getphonenumber"
@error="error"
@opensetting="opensetting"
@launchapp="launchapp"
@chooseavatar="chooseavatar"
@agreeprivacyauthorization="agreeprivacyauthorization"
>
<block
v-if="_icon"
name="icon"
>
<t-icon
:custom-style="iconCustomStyle"
:t-class="classPrefix + '__icon ' + classPrefix + '__icon--' + (_icon.activeIdx == _icon.index ? 'active ' : ' ') + tClassIcon"
:prefix="_icon.prefix"
:name="_icon.name || ''"
:size="_icon.size"
:color="_icon.color"
@click="'handleClose' || ''"
/>
</block>
<t-loading
v-if="loading"
:delay="loadingProps.delay || 0"
:duration="loadingProps.duration || 800"
:indicator="loadingProps.indicator || true"
:inherit-color="loadingProps.inheritColor || true"
:layout="loadingProps.layout || 'horizontal'"
:pause="loadingProps.pause || false"
:progress="loadingProps.progress || 0"
:reverse="loadingProps.reverse || false"
:size="loadingProps.size || '40rpx'"
:text="loadingProps.text || ''"
:theme="loadingProps.theme || 'circular'"
loading
:t-class="classPrefix + '__loading ' + classPrefix + '__loading--wrapper'"
:t-class-indicator="classPrefix + '__loading--indicator ' + tClassLoading"
:custom-style="loadingCustomStyle"
/>
<view
:class="classPrefix + '__content '
+ ((_icon && _icon.name || loading) && content ? classPrefix + '__content--has-icon' : '')"
>
<slot name="content" />
<block v-if="content">
{{ content }}
</block>
<slot />
</view>
<slot name="suffix" />
</button>
</template>
<script>
import TIcon from '../icon/icon';
import TLoading from '../loading/loading';
import { uniComponent } from '../common/src/index';
import { prefix } from '../common/config';
import props from './props';
import { calcIcon } from '../common/utils';
import tools from '../common/utils.wxs';
const name = `${prefix}-button`;
export default uniComponent({
name,
options: {
styleIsolation: 'shared',
},
externalClasses: [
`${prefix}-class`,
`${prefix}-class-icon`,
`${prefix}-class-loading`,
],
components: {
TIcon,
TLoading,
},
props: {
...props,
},
emits: [
'click',
],
data() {
return {
tools,
prefix,
className: '',
classPrefix: name,
_icon: undefined,
};
},
computed: {
iconCustomStyle() {
const fontSize = {
'extra-small': 'var(--td-button-extra-small-icon-font-size, 18px)',
small: 'var(--td-button-small-icon-font-size, 18px)',
medium: 'var(--td-button-medium-icon-font-size, 20px)',
large: 'var(--td-button-large-icon-font-size, 24px)',
};
return tools._style([
{
fontSize: fontSize[this.size || 'medium'],
borderRadius: 'var(--td-button-icon-border-radius, 4px)',
},
this._icon.style || '',
]);
},
loadingCustomStyle() {
return tools._style({
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
});
},
},
watch: {
icon: {
handler(value) {
this._icon = calcIcon(value, '');
},
immediate: true,
},
theme: 'setClass',
size: 'setClass',
plain: 'setClass',
block: 'setClass',
shape: 'setClass',
disabled: 'setClass',
loading: 'setClass',
variant: 'setClass',
},
mounted() {
this.setClass();
},
methods: {
setClass() {
const t = [
name,
this.tClass,
`${name}--${this.variant || 'base'}`,
`${name}--${this.theme || 'default'}`,
`${name}--${this.shape || 'rectangle'}`,
`${name}--size-${this.size || 'medium'}`,
];
if (this.block) {
t.push(`${name}--block`);
}
if (this.disabled) {
t.push(`${name}--disabled`);
}
if (this.ghost) {
t.push(`${name}--ghost`);
}
this.className = t.join(' ');
},
getuserinfo(t) {
this.$emit('getuserinfo', t);
},
contact(t) {
this.$emit('contact', t);
},
getphonenumber(t) {
this.$emit('getphonenumber', t);
},
error(t) {
this.$emit('error', t);
},
opensetting(t) {
this.$emit('opensetting', t);
},
launchapp(t) {
this.$emit('launchapp', t);
},
chooseavatar(t) {
this.$emit('chooseavatar', t);
},
agreeprivacyauthorization(t) {
this.$emit('agreeprivacyauthorization', t);
},
handleTap(t) {
if (this.disabled || this.loading) return;
this.$emit('click', t);
},
},
});
</script>
<style scoped>
@import './button.css';
/* #ifdef MP-QQ */
/* 适配 qq 小程序 */
.t-button--outline {
border-style: solid;
border-width: 1px;
}
.t-button--ghost {
border-style: solid;
border-width: 1px;
}
/* #endif */
/* #ifdef H5 || MP-WEIXIN */
:deep(.t-button__loading) + .t-button__content:not(:empty) {
margin-left: 4px;
}
:deep(.t-button__icon) + .t-button__content:not(:empty) {
margin-left: 4px;
}
/* #endif */
.t-button {
/* support my-alipay */
margin-left: auto;
margin-right: auto;
}
</style>

View File

@@ -0,0 +1,207 @@
/* eslint-disable */
/**
* 该文件为脚本自动生成文件,请勿随意修改。如需修改请联系 PMC
* */
import type { TdButtonProps } from './type';
export default {
/** 打开 APP 时,向 APP 传递的参数open-type=launchApp时有效 */
appParameter: {
type: String,
default: '',
},
/** 是否为块级元素 */
block: Boolean,
/** 按钮内容 */
content: {
type: String,
},
/** 自定义 dataset可通过 event.currentTarget.dataset.custom 获取 */
customDataset: {
type: [String, Number, Boolean, Object, Array],
default: () => ({}) as TdButtonProps['customDataset'],
},
/** 禁用状态。优先级Button.disabled > Form.disabled */
disabled: {
type: Boolean,
default: undefined,
},
/** 是否为幽灵按钮(镂空按钮) */
ghost: Boolean,
/** 指定按钮按下去的样式类,按钮不为加载或禁用状态时有效。当 `hover-class="none"` 时,没有点击态效果 */
hoverClass: {
type: String,
default: '',
},
/** 按住后多久出现点击态,单位毫秒 */
hoverStartTime: {
type: Number,
default: 20,
},
/** 手指松开后点击态保留时间,单位毫秒 */
hoverStayTime: {
type: Number,
default: 70,
},
/** 指定是否阻止本节点的祖先节点出现点击态 */
hoverStopPropagation: Boolean,
/** 图标名称。值为字符串表示图标名称,值为 `Object` 类型,表示透传至 `icon` */
icon: {
type: [String, Object],
},
/** 指定返回用户信息的语言zh_CN 简体中文zh_TW 繁体中文en 英文。<br />具体释义:<br />`en` 英文;<br />`zh_CN` 简体中文;<br />`zh_TW` 繁体中文。<br />[小程序官方文档](https://developers.weixin.qq.com/miniprogram/dev/component/button.html) */
lang: {
type: String,
validator(val: TdButtonProps['lang']): boolean {
if (!val) return true;
return ['en', 'zh_CN', 'zh_TW'].includes(val);
},
},
/** 是否显示为加载状态 */
loading: Boolean,
/** 透传 Loading 组件全部属性 */
loadingProps: {
type: Object,
default: () => ({}),
},
/** 微信开放能力。<br />具体释义:<br />`contact` 打开客服会话,如果用户在会话中点击消息卡片后返回小程序,可以从 bindcontact 回调中获得具体信息,<a href="https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/customer-message/customer-message.html">具体说明</a> *鸿蒙 OS 暂不支持*<br />`liveActivity` 通过前端获取<a href="https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/subscribe-message-2.html">新的一次性订阅消息下发机制</a>使用的 code<br />`share` 触发用户转发,使用前建议先阅读<a href="https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/share.html#使用指引">使用指引</a><br />`getPhoneNumber` 获取用户手机号,可以从 bindgetphonenumber 回调中获取到用户信息,<a href="https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/getPhoneNumber.html">具体说明</a> *小程序插件中不能使用*<br />`getUserInfo` 获取用户信息,可以从 bindgetuserinfo 回调中获取到用户信息 *小程序插件中不能使用*<br />`launchApp` 打开APP可以通过 app-parameter 属性设定向 APP 传的参数<a href="https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/launchApp.html">具体说明</a><br />`openSetting` 打开授权设置页;<br />`feedback` 打开“意见反馈”页面,用户可提交反馈内容并上传<a href="https://developers.weixin.qq.com/miniprogram/dev/api/base/debug/wx.getLogManager.html">日志</a>,开发者可以登录<a href="https://mp.weixin.qq.com/">小程序管理后台</a>后进入左侧菜单“客服反馈”页面获取到反馈内容;<br />`chooseAvatar` 获取用户头像,可以从 bindchooseavatar 回调中获取到头像信息;<br />`agreePrivacyAuthorization`用户同意隐私协议按钮。用户点击一次此按钮后,所有隐私接口可以正常调用。可通过`bindagreeprivacyauthorization`监听用户同意隐私协议事件。隐私合规开发指南详情可见《<a href="https://developers.weixin.qq.com/miniprogram/dev/framework/user-privacy/PrivacyAuthorize.html">小程序隐私协议开发指南</a>》。<br />[小程序官方文档](https://developers.weixin.qq.com/miniprogram/dev/component/button.html) */
openType: {
type: String,
validator(val: TdButtonProps['openType']): boolean {
if (!val) return true;
return ['contact', 'share', 'getPhoneNumber', 'getUserInfo', 'launchApp', 'openSetting', 'feedback', 'chooseAvatar', 'agreePrivacyAuthorization'].includes(val);
},
},
/** 原生按钮属性当手机号快速验证或手机号实时验证额度用尽时是否对用户展示“申请获取你的手机号但该功能使用次数已达当前小程序上限暂时无法使用”的提示默认展示open-type="getPhoneNumber" 或 open-type="getRealtimePhoneNumber" 时有效 */
phoneNumberNoQuotaToast: {
type: Boolean,
default: true,
},
/** 会话内消息卡片图片open-type="contact"时有效 */
sendMessageImg: {
type: String,
default: '截图',
},
/** 会话内消息卡片点击跳转小程序路径open-type="contact"时有效 */
sendMessagePath: {
type: String,
default: '当前分享路径',
},
/** 会话内消息卡片标题open-type="contact"时有效 */
sendMessageTitle: {
type: String,
default: '当前标题',
},
/** 会话来源open-type="contact"时有效 */
sessionFrom: {
type: String,
default: '',
},
/** 按钮形状,有 4 种:长方形、正方形、圆角长方形、圆形 */
shape: {
type: String,
default: 'rectangle' as TdButtonProps['shape'],
validator(val: TdButtonProps['shape']): boolean {
if (!val) return true;
return ['rectangle', 'square', 'round', 'circle'].includes(val);
},
},
/** 是否显示会话内消息卡片,设置此参数为 true用户进入客服会话会在右下角显示"可能要发送的小程序"提示用户点击后可以快速发送小程序消息open-type="contact"时有效 */
showMessageCard: Boolean,
/** 组件尺寸 */
size: {
type: String,
default: 'medium' as TdButtonProps['size'],
validator(val: TdButtonProps['size']): boolean {
if (!val) return true;
return ['extra-small', 'small', 'medium', 'large'].includes(val);
},
},
/** 按钮标签id */
tId: {
type: String,
default: '',
},
/** 组件风格,依次为品牌色、危险色 */
theme: {
type: String,
default: 'default' as TdButtonProps['theme'],
validator(val: TdButtonProps['theme']): boolean {
if (!val) return true;
return ['default', 'primary', 'danger', 'light'].includes(val);
},
},
/** 同小程序的 formType */
type: {
type: String,
validator(val: TdButtonProps['type']): boolean {
if (!val) return true;
return ['submit', 'reset'].includes(val);
},
},
/** 按钮形式,基础、线框、虚线、文字 */
variant: {
type: String,
default: 'base' as TdButtonProps['variant'],
validator(val: TdButtonProps['variant']): boolean {
if (!val) return true;
return ['base', 'outline', 'dashed', 'text'].includes(val);
},
},
/** 原生按钮属性用户同意隐私协议事件回调open-type=agreePrivacyAuthorization时有效 Tips: 如果使用 onNeedPrivacyAuthorization 接口,需要在 bindagreeprivacyauthorization 触发后再调用 resolve({ event: "agree", buttonId }) */
onAgreeprivacyauthorization: {
type: Function,
default: () => ({}),
},
/** 原生按钮属性,获取用户头像回调,`open-type=chooseAvatar` 时有效。返回 `e.detail.avatarUrl` 为头像临时文件链接 */
onChooseavatar: {
type: Function,
default: () => ({}),
},
/** 点击时触发 */
onClick: {
type: Function,
default: () => ({}),
},
/** 原生按钮属性,客服消息回调,`open-type="contact"` 时有效 */
onContact: {
type: Function,
default: () => ({}),
},
/** 新的一次性订阅消息下发机制回调,`open-type=liveActivity` 时有效 */
onCreateliveactivity: {
type: Function,
default: () => ({}),
},
/** 原生按钮属性,当使用开放能力时,发生错误的回调,`open-type=launchApp` 时有效 */
onError: {
type: Function,
default: () => ({}),
},
/** 原生按钮属性手机号快速验证回调open-type=getPhoneNumber时有效。Tips在触发 bindgetphonenumber 回调后应立即隐藏手机号按钮组件,或置为 disabled 状态,避免用户重复授权手机号产生额外费用 */
onGetphonenumber: {
type: Function,
default: () => ({}),
},
/** 原生按钮属性手机号实时验证回调open-type=getRealtimePhoneNumber 时有效。Tips在触发 bindgetrealtimephonenumber 回调后应立即隐藏手机号按钮组件,或置为 disabled 状态,避免用户重复授权手机号产生额外费用 */
onGetrealtimephonenumber: {
type: Function,
default: () => ({}),
},
/** 原生按钮属性用户点击该按钮时会返回获取到的用户信息回调的detail数据与wx.getUserInfo返回的一致open-type="getUserInfo"时有效 */
onGetuserinfo: {
type: Function,
default: () => ({}),
},
/** 打开 APP 成功的回调,`open-type=launchApp` 时有效 */
onLaunchapp: {
type: Function,
default: () => ({}),
},
/** 原生按钮属性在打开授权设置页后回调open-type=openSetting时有效 */
onOpensetting: {
type: Function,
default: () => ({}),
},
};

View File

@@ -0,0 +1,192 @@
/* eslint-disable */
/**
* 该文件为脚本自动生成文件,请勿随意修改。如需修改请联系 PMC
* */
import type { TdLoadingProps as LoadingProps } from '../loading/type';
export interface TdButtonProps {
/**
* 打开 APP 时,向 APP 传递的参数open-type=launchApp时有效
* @default ''
*/
appParameter?: string;
/**
* 是否为块级元素
* @default false
*/
block?: boolean;
/**
* 按钮内容
*/
content?: string;
/**
* 自定义 dataset可通过 event.currentTarget.dataset.custom 获取
* @default {}
*/
customDataset?: string | number | boolean | object | Array<string | number | boolean | object>;
/**
* 禁用状态。优先级Button.disabled > Form.disabled
*/
disabled?: boolean;
/**
* 是否为幽灵按钮(镂空按钮)
* @default false
*/
ghost?: boolean;
/**
* 指定按钮按下去的样式类,按钮不为加载或禁用状态时有效。当 `hover-class="none"` 时,没有点击态效果
* @default ''
*/
hoverClass?: string;
/**
* 按住后多久出现点击态,单位毫秒
* @default 20
*/
hoverStartTime?: number;
/**
* 手指松开后点击态保留时间,单位毫秒
* @default 70
*/
hoverStayTime?: number;
/**
* 指定是否阻止本节点的祖先节点出现点击态
* @default false
*/
hoverStopPropagation?: boolean;
/**
* 图标名称。值为字符串表示图标名称,值为 `Object` 类型,表示透传至 `icon`
*/
icon?: string | object;
/**
* 指定返回用户信息的语言zh_CN 简体中文zh_TW 繁体中文en 英文。<br />具体释义:<br />`en` 英文;<br />`zh_CN` 简体中文;<br />`zh_TW` 繁体中文。<br />[小程序官方文档](https://developers.weixin.qq.com/miniprogram/dev/component/button.html)
*/
lang?: 'en' | 'zh_CN' | 'zh_TW';
/**
* 是否显示为加载状态
* @default false
*/
loading?: boolean;
/**
* 透传 Loading 组件全部属性
* @default {}
*/
loadingProps?: LoadingProps;
/**
* 微信开放能力。<br />具体释义:<br />`contact` 打开客服会话,如果用户在会话中点击消息卡片后返回小程序,可以从 bindcontact 回调中获得具体信息,<a href="https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/customer-message/customer-message.html">具体说明</a> *鸿蒙 OS 暂不支持*<br />`liveActivity` 通过前端获取<a href="https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/subscribe-message-2.html">新的一次性订阅消息下发机制</a>使用的 code<br />`share` 触发用户转发,使用前建议先阅读<a href="https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/share.html#使用指引">使用指引</a><br />`getPhoneNumber` 获取用户手机号,可以从 bindgetphonenumber 回调中获取到用户信息,<a href="https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/getPhoneNumber.html">具体说明</a> *小程序插件中不能使用*<br />`getUserInfo` 获取用户信息,可以从 bindgetuserinfo 回调中获取到用户信息 *小程序插件中不能使用*<br />`launchApp` 打开APP可以通过 app-parameter 属性设定向 APP 传的参数<a href="https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/launchApp.html">具体说明</a><br />`openSetting` 打开授权设置页;<br />`feedback` 打开“意见反馈”页面,用户可提交反馈内容并上传<a href="https://developers.weixin.qq.com/miniprogram/dev/api/base/debug/wx.getLogManager.html">日志</a>,开发者可以登录<a href="https://mp.weixin.qq.com/">小程序管理后台</a>后进入左侧菜单“客服反馈”页面获取到反馈内容;<br />`chooseAvatar` 获取用户头像,可以从 bindchooseavatar 回调中获取到头像信息;<br />`agreePrivacyAuthorization`用户同意隐私协议按钮。用户点击一次此按钮后,所有隐私接口可以正常调用。可通过`bindagreeprivacyauthorization`监听用户同意隐私协议事件。隐私合规开发指南详情可见《<a href="https://developers.weixin.qq.com/miniprogram/dev/framework/user-privacy/PrivacyAuthorize.html">小程序隐私协议开发指南</a>》。<br />[小程序官方文档](https://developers.weixin.qq.com/miniprogram/dev/component/button.html)
*/
openType?:
| 'contact'
| 'share'
| 'getPhoneNumber'
| 'getUserInfo'
| 'launchApp'
| 'openSetting'
| 'feedback'
| 'chooseAvatar'
| 'agreePrivacyAuthorization';
/**
* 原生按钮属性当手机号快速验证或手机号实时验证额度用尽时是否对用户展示“申请获取你的手机号但该功能使用次数已达当前小程序上限暂时无法使用”的提示默认展示open-type="getPhoneNumber" 或 open-type="getRealtimePhoneNumber" 时有效
* @default true
*/
phoneNumberNoQuotaToast?: boolean;
/**
* 会话内消息卡片图片open-type="contact"时有效
* @default 截图
*/
sendMessageImg?: string;
/**
* 会话内消息卡片点击跳转小程序路径open-type="contact"时有效
* @default 当前分享路径
*/
sendMessagePath?: string;
/**
* 会话内消息卡片标题open-type="contact"时有效
* @default 当前标题
*/
sendMessageTitle?: string;
/**
* 会话来源open-type="contact"时有效
* @default ''
*/
sessionFrom?: string;
/**
* 按钮形状,有 4 种:长方形、正方形、圆角长方形、圆形
* @default rectangle
*/
shape?: 'rectangle' | 'square' | 'round' | 'circle';
/**
* 是否显示会话内消息卡片,设置此参数为 true用户进入客服会话会在右下角显示"可能要发送的小程序"提示用户点击后可以快速发送小程序消息open-type="contact"时有效
* @default false
*/
showMessageCard?: boolean;
/**
* 组件尺寸
* @default medium
*/
size?: 'extra-small' | 'small' | 'medium' | 'large';
/**
* 按钮标签id
* @default ''
*/
tId?: string;
/**
* 组件风格,依次为品牌色、危险色
* @default default
*/
theme?: 'default' | 'primary' | 'danger' | 'light';
/**
* 同小程序的 formType
*/
type?: 'submit' | 'reset';
/**
* 按钮形式,基础、线框、虚线、文字
* @default base
*/
variant?: 'base' | 'outline' | 'dashed' | 'text';
/**
* 原生按钮属性用户同意隐私协议事件回调open-type=agreePrivacyAuthorization时有效 Tips: 如果使用 onNeedPrivacyAuthorization 接口,需要在 bindagreeprivacyauthorization 触发后再调用 resolve({ event: "agree", buttonId })
*/
onAgreeprivacyauthorization?: () => void;
/**
* 原生按钮属性,获取用户头像回调,`open-type=chooseAvatar` 时有效。返回 `e.detail.avatarUrl` 为头像临时文件链接
*/
onChooseavatar?: () => void;
/**
* 点击时触发
*/
onClick?: (e: MouseEvent) => void;
/**
* 原生按钮属性,客服消息回调,`open-type="contact"` 时有效
*/
onContact?: () => void;
/**
* 新的一次性订阅消息下发机制回调,`open-type=liveActivity` 时有效
*/
onCreateliveactivity?: () => void;
/**
* 原生按钮属性,当使用开放能力时,发生错误的回调,`open-type=launchApp` 时有效
*/
onError?: () => void;
/**
* 原生按钮属性手机号快速验证回调open-type=getPhoneNumber时有效。Tips在触发 bindgetphonenumber 回调后应立即隐藏手机号按钮组件,或置为 disabled 状态,避免用户重复授权手机号产生额外费用
*/
onGetphonenumber?: () => void;
/**
* 原生按钮属性手机号实时验证回调open-type=getRealtimePhoneNumber 时有效。Tips在触发 bindgetrealtimephonenumber 回调后应立即隐藏手机号按钮组件,或置为 disabled 状态,避免用户重复授权手机号产生额外费用
*/
onGetrealtimephonenumber?: () => void;
/**
* 原生按钮属性用户点击该按钮时会返回获取到的用户信息回调的detail数据与wx.getUserInfo返回的一致open-type="getUserInfo"时有效
*/
onGetuserinfo?: () => void;
/**
* 打开 APP 成功的回调,`open-type=launchApp` 时有效
*/
onLaunchapp?: () => void;
/**
* 原生按钮属性在打开授权设置页后回调open-type=openSetting时有效
*/
onOpensetting?: () => void;
}

View File

@@ -0,0 +1,63 @@
:: BASE_DOC ::
## API
### Calendar Props
name | type | default | description | required
-- | -- | -- | -- | --
custom-style | Object | - | CSS(Cascading Style Sheets) | N
allow-same-day | Boolean | false | \- | N
auto-close | Boolean | true | \- | N
confirm-btn | String / Object | '' | [see more ts definition](https://github.com/tencent/tdesign-miniprogram/blob/develop/packages/uniapp-components/calendar/type.ts) | N
first-day-of-week | Number | 0 | \- | N
format | Function | - | Typescript: `CalendarFormatType ` `type CalendarFormatType = (day: TDate) => TDate` `type TDateType = 'selected' \| 'disabled' \| 'start' \| 'start-end' \|'centre' \| 'end' \| ''` `interface TDate { date: Date; day: number; type: TDateType; className?: string; prefix?: string; suffix?: string;}`。[see more ts definition](https://github.com/tencent/tdesign-miniprogram/blob/develop/packages/uniapp-components/calendar/type.ts) | N
locale-text | Object | - | Typescript: `CalendarLocaleText` `interface CalendarLocaleText {title?: string; weekdays?: string[]; monthTitle?: string; months?: string[]; confirm?: string;}`。[see more ts definition](https://github.com/tencent/tdesign-miniprogram/blob/develop/packages/uniapp-components/calendar/type.ts) | N
max-date | Number | - | \- | N
min-date | Number | - | \- | N
readonly | Boolean | - | \- | N
switch-mode | String | none | options: none/month/year-month | N
title | String | - | \- | N
type | String | single | options: single/multiple/range | N
use-popup | Boolean | true | \- | N
using-custom-navbar | Boolean | false | \- | N
value | Number / Array | - | `v-model:value` is supported。Typescript: `number \| number[]` | N
default-value | Number / Array | - | uncontrolled property。Typescript: `number \| number[]` | N
visible | Boolean | false | \- | N
### Calendar Events
name | params | description
-- | -- | --
change | `(context: { value: number \| number[] })` | \-
close | `(context: { trigger: CalendarTrigger })` | [see more ts definition](https://github.com/tencent/tdesign-miniprogram/blob/develop/packages/uniapp-components/calendar/type.ts)。<br/>`type CalendarTrigger = 'close-btn' \| 'confirm-btn' \| 'overlay' \| 'auto-close'`<br/>
confirm | `(context: { value: number \| number[] })` | \-
panel-change | `(context: { year: number, month: number })` | \-
scroll | `(context: {scrollLeft: number, scrollTop: number, scrollHeight: number, scrollWidth: number, deltaX: number, deltaY: number})` | triggered when scrolling
select | `(context: { value: number \| number[] })` | \-
### Calendar Slots
name | Description
-- | --
confirm-btn | \-
title | \-
### CSS Variables
The component provides the following CSS variables, which can be used to customize styles.
Name | Default Value | Description
-- | -- | --
--td-calendar-active-color | @brand-color | -
--td-calendar-bg-color | @bg-color-container | -
--td-calendar-days-color | @text-color-secondary | -
--td-calendar-item-centre-color | @brand-color-light | -
--td-calendar-item-disabled-color | @text-color-disabled | -
--td-calendar-item-suffix-color | @text-color-placeholder | -
--td-calendar-radius | 24rpx | -
--td-calendar-selected-border-radius | @radius-default | -
--td-calendar-selected-color | @text-color-anti | -
--td-calendar-switch-mode-icon-color | @text-color-secondary | -
--td-calendar-switch-mode-icon-disabled-color | @text-color-disabled | -
--td-calendar-title-color | @text-color-primary | -
--td-calendar-title-font | @font-title-large | -

View File

@@ -0,0 +1,113 @@
---
title: Calendar 日历
description: 按照日历形式展示数据或日期的容器。
spline: form
isComponent: true
---
## 引入
可在 `main.ts` 或在需要使用的页面或组件中引入。
```js
import TCalendar from '@tdesign/uniapp/calendar/calendar.vue';
```
### 组件类型
#### 单个选择日历
{{ base }}
#### 多个选择日历
{{ multiple }}
#### 带单行/双行描述的日历
{{ custom-text }}
#### 带翻页功能的日历
{{ switch-mode }}
#### 可选择区间日期的日历
{{ range }}
### 组件样式
#### 国际化
{{ local-text }}
#### 含不可选的日历
{{ custom-range }}
#### 不使用 Popup
{{ without-popup }}
## API
### Calendar Props
名称 | 类型 | 默认值 | 描述 | 必传
-- | -- | -- | -- | --
custom-style | Object | - | 自定义样式 | N
allow-same-day | Boolean | false | 是否允许区间选择日历的起止时间相同,仅当 `type='range'` 时有效 | N
auto-close | Boolean | true | 自动关闭;在点击关闭按钮、确认按钮、遮罩层时自动关闭,不需要手动设置 visible | N
confirm-btn | String / Object | '' | 确认按钮。值为 null 则不显示确认按钮。值类型为字符串,则表示自定义按钮文本,值类型为 Object 则表示透传 Button 组件属性。[详细类型定义](https://github.com/tencent/tdesign-miniprogram/blob/develop/packages/uniapp-components/calendar/type.ts) | N
first-day-of-week | Number | 0 | 第一天从星期几开始,默认 0 = 周日 | N
format | Function | - | 用于格式化日期的函数。TS 类型:`CalendarFormatType ` `type CalendarFormatType = (day: TDate) => TDate` `type TDateType = 'selected' \| 'disabled' \| 'start' \| 'start-end' \|'centre' \| 'end' \| ''` `interface TDate { date: Date; day: number; type: TDateType; className?: string; prefix?: string; suffix?: string;}`。[详细类型定义](https://github.com/tencent/tdesign-miniprogram/blob/develop/packages/uniapp-components/calendar/type.ts) | N
locale-text | Object | - | 国际化文案。TS 类型:`CalendarLocaleText` `interface CalendarLocaleText {title?: string; weekdays?: string[]; monthTitle?: string; months?: string[]; confirm?: string;}`。[详细类型定义](https://github.com/tencent/tdesign-miniprogram/blob/develop/packages/uniapp-components/calendar/type.ts) | N
max-date | Number | - | 最大可选的日期,不传则默认半年后 | N
min-date | Number | - | 最小可选的日期,不传则默认今天 | N
readonly | Boolean | - | 是否只读,只读状态下不能选择日期 | N
switch-mode | String | none | 切换模式。 `none` 表示平铺展示所有月份; `month` 表示支持按月切换, `year-month` 表示既按年切换也支持按月切换。可选项none/month/year-month | N
title | String | - | 标题,不传默认为“请选择日期” | N
type | String | single | 日历的选择类型single = 单选multiple = 多选; range = 区间选择。可选项single/multiple/range | N
use-popup | Boolean | true | 是否使用弹出层包裹日历 | N
using-custom-navbar | Boolean | false | 是否使用了自定义导航栏 | N
value | Number / Array | - | 当前选择的日期,不传则选用 minDate 属性值或今天优先级minDate > today。当 type = multiple 或 range 时传入数组。支持语法糖 `v-model:value`。TS 类型:`number \| number[]` | N
default-value | Number / Array | - | 当前选择的日期,不传则选用 minDate 属性值或今天优先级minDate > today。当 type = multiple 或 range 时传入数组。非受控属性。TS 类型:`number \| number[]` | N
visible | Boolean | false | 是否显示日历;`usePopup` 为 true 时有效 | N
### Calendar Events
名称 | 参数 | 描述
-- | -- | --
change | `(context: { value: number \| number[] })` | 不显示 confirm-btn 时,完成选择时触发(暂不支持 type = multiple
close | `(context: { trigger: CalendarTrigger })` | 关闭按钮时触发。[详细类型定义](https://github.com/tencent/tdesign-miniprogram/blob/develop/packages/uniapp-components/calendar/type.ts)。<br/>`type CalendarTrigger = 'close-btn' \| 'confirm-btn' \| 'overlay' \| 'auto-close'`<br/>
confirm | `(context: { value: number \| number[] })` | 点击确认按钮时触发
panel-change | `(context: { year: number, month: number })` | 切换月或年时触发switch-mode 不为 none 时有效)
scroll | `(context: {scrollLeft: number, scrollTop: number, scrollHeight: number, scrollWidth: number, deltaX: number, deltaY: number})` | 滚动时触发
select | `(context: { value: number \| number[] })` | 点击日期时触发
### Calendar Slots
名称 | 描述
-- | --
confirm-btn | 确认按钮
title | 标题
### CSS Variables
组件提供了下列 CSS 变量,可用于自定义样式。
名称 | 默认值 | 描述
-- | -- | --
--td-calendar-active-color | @brand-color | -
--td-calendar-bg-color | @bg-color-container | -
--td-calendar-days-color | @text-color-secondary | -
--td-calendar-item-centre-color | @brand-color-light | -
--td-calendar-item-disabled-color | @text-color-disabled | -
--td-calendar-item-suffix-color | @text-color-placeholder | -
--td-calendar-radius | 24rpx | -
--td-calendar-selected-border-radius | @radius-default | -
--td-calendar-selected-color | @text-color-anti | -
--td-calendar-switch-mode-icon-color | @text-color-secondary | -
--td-calendar-switch-mode-icon-disabled-color | @text-color-disabled | -
--td-calendar-title-color | @text-color-primary | -
--td-calendar-title-font | @font-title-large | -

View File

@@ -0,0 +1,42 @@
export default {
tClass: {
type: String,
default: '',
},
classPrefix: {
type: String,
default: '',
},
switchMode: {
type: String,
default: '',
},
title: {
type: String,
default: '',
},
preYearBtnDisable: {
type: Boolean,
},
prevMonthBtnDisable: {
type: Boolean,
},
nextYearBtnDisable: {
type: Boolean,
},
nextMonthBtnDisable: {
type: Boolean,
},
tId: {
type: String,
default: '',
},
// realLocalText: {
// type: Object,
// default: () => ({}),
// },
// currentMonth: {
// type: Array,
// default: () => ([]),
// },
};

View File

@@ -0,0 +1,98 @@
<template>
<view
:id="tId"
:class="' ' + tClass + ' ' + classPrefix + ' '+(switchMode !== 'none' ? classPrefix + '__with-action' : '')"
>
<view
v-if="switchMode !== 'none'"
:class="classPrefix + '__action'"
>
<view
v-if="switchMode === 'year-month'"
:class="utils.cls(classPrefix + '__icon', [['disabled', preYearBtnDisable]])"
:data-disabled="preYearBtnDisable"
data-type="pre-year"
@click="handleSwitchModeChange"
>
<t-icon name="chevron-left-double" />
</view>
<view
:class="utils.cls(classPrefix + '__icon', [['disabled', prevMonthBtnDisable]])"
:data-disabled="prevMonthBtnDisable"
data-type="pre-month"
@click="handleSwitchModeChange"
>
<t-icon name="chevron-left" />
</view>
</view>
<view :class="classPrefix + '__title'">
{{ title }}
</view>
<view
v-if="switchMode !== 'none'"
:class="classPrefix + '__action'"
>
<view
:class="utils.cls(classPrefix + '__icon', [['disabled', nextMonthBtnDisable]])"
:data-disabled="nextMonthBtnDisable"
data-type="next-month"
@click="handleSwitchModeChange"
>
<t-icon name="chevron-right" />
</view>
<view
v-if="switchMode === 'year-month'"
:class="utils.cls(classPrefix + '__icon', [['disabled', nextYearBtnDisable]])"
:data-disabled="nextYearBtnDisable"
data-type="next-year"
@click="handleSwitchModeChange"
>
<t-icon name="chevron-right-double" />
</view>
</view>
</view>
</template>
<script>
import TIcon from '../icon/icon.vue';
import utils from '../common/utils.wxs';
import props from './calendar-header.props';
import { getMonthTitle } from './computed';
export default {
name: 'TCalendarHeader',
options: {
styleIsolation: 'shared',
},
components: {
TIcon,
},
props: {
...props,
},
emits: [
'handleSwitchModeChange',
],
data() {
return {
utils,
};
},
watch: {
},
mounted() {
},
methods: {
handleSwitchModeChange(...args) {
this.$emit('handleSwitchModeChange', ...args);
},
getMonthTitle,
},
};
</script>
<style scoped>
@import './calendar.css';
</style>

View File

@@ -0,0 +1,205 @@
.t-calendar {
width: inherit;
position: relative;
z-index: 9999;
background: var(--td-calendar-bg-color, var(--td-bg-color-container, var(--td-font-white-1, #ffffff)));
overflow-x: hidden;
}
.t-calendar--popup {
border-top-left-radius: var(--td-calendar-radius, 24rpx);
border-top-right-radius: var(--td-calendar-radius, 24rpx);
}
.t-calendar__title {
display: flex;
align-items: center;
justify-content: center;
font: var(--td-calendar-title-font, var(--td-font-title-large, 600 36rpx / 52rpx var(--td-font-family, PingFang SC, Microsoft YaHei, Arial Regular)));
color: var(--td-calendar-title-color, var(--td-text-color-primary, var(--td-font-gray-1, rgba(0, 0, 0, 0.9))));
padding: var(--td-spacer-2, 32rpx);
}
.t-calendar__title:focus {
outline: 0;
}
.t-calendar__close-btn {
position: absolute;
top: var(--td-spacer-2, 32rpx);
right: var(--td-spacer-2, 32rpx);
margin: calc(-1 * var(--td-spacer-1, 24rpx));
padding: var(--td-spacer-1, 24rpx);
color: var(--td-calendar-title-color, var(--td-text-color-primary, var(--td-font-gray-1, rgba(0, 0, 0, 0.9))));
}
.t-calendar__days {
display: grid;
grid-template-columns: repeat(7, 1fr);
grid-column-gap: 8rpx;
padding: 0 32rpx;
text-align: center;
line-height: 92rpx;
}
.t-calendar__days-item {
height: 92rpx;
font-size: 28rpx;
color: var(--td-calendar-days-color, var(--td-text-color-secondary, var(--td-font-gray-2, rgba(0, 0, 0, 0.6))));
}
.t-calendar__content {
min-height: 400rpx;
display: flex;
flex-direction: column;
}
.t-calendar__month {
font: var(--td-font-title-small, 600 28rpx / 44rpx var(--td-font-family, PingFang SC, Microsoft YaHei, Arial Regular));
color: var(--td-calendar-title-color, var(--td-text-color-primary, var(--td-font-gray-1, rgba(0, 0, 0, 0.9))));
padding: 32rpx 0 0;
}
.t-calendar__months {
height: 712rpx;
padding: 0 32rpx 32rpx;
box-sizing: border-box;
}
.t-calendar__months::-webkit-scrollbar {
display: none;
}
.t-calendar__dates {
flex: 1;
display: grid;
grid-template-columns: repeat(7, 1fr);
grid-column-gap: 8rpx;
}
.t-calendar__dates-item {
position: relative;
display: flex;
align-items: center;
justify-content: center;
font: var(--td-font-title-medium, 600 32rpx / 48rpx var(--td-font-family, PingFang SC, Microsoft YaHei, Arial Regular));
border-radius: var(--td-calendar-selected-border-radius, var(--td-radius-default, 12rpx));
height: 120rpx;
margin-top: 16rpx;
color: var(--td-calendar-title-color, var(--td-text-color-primary, var(--td-font-gray-1, rgba(0, 0, 0, 0.9))));
cursor: pointer;
-webkit-tap-highlight-color: transparent;
-webkit-user-select: none;
user-select: none;
}
.t-calendar__dates-item-prefix,
.t-calendar__dates-item-suffix {
position: absolute;
font: var(--td-font-body-extraSmall, 20rpx / 32rpx var(--td-font-family, PingFang SC, Microsoft YaHei, Arial Regular));
width: 100%;
text-align: center;
}
.t-calendar__dates-item-prefix {
top: 8rpx;
}
.t-calendar__dates-item-suffix {
bottom: 8rpx;
color: var(--td-calendar-item-suffix-color, var(--td-text-color-placeholder, var(--td-font-gray-3, rgba(0, 0, 0, 0.4))));
}
.t-calendar__dates-item-suffix--selected,
.t-calendar__dates-item-suffix--start,
.t-calendar__dates-item-suffix--end {
color: var(--td-calendar-selected-color, var(--td-text-color-anti, var(--td-font-white-1, #ffffff)));
}
.t-calendar__dates-item-suffix--disabled {
color: var(--td-calendar-item-disabled-color, var(--td-text-color-disabled, var(--td-font-gray-4, rgba(0, 0, 0, 0.26))));
}
.t-calendar__dates-item--selected,
.t-calendar__dates-item--start-end,
.t-calendar__dates-item--start,
.t-calendar__dates-item--end {
background: var(--td-calendar-active-color, var(--td-brand-color, var(--td-primary-color-7, #0052d9)));
color: var(--td-calendar-selected-color, var(--td-text-color-anti, var(--td-font-white-1, #ffffff)));
border-radius: var(--td-calendar-selected-border-radius, var(--td-radius-default, 12rpx));
}
.t-calendar__dates-item--start {
border-radius: var(--td-calendar-selected-border-radius, var(--td-radius-default, 12rpx)) 0 0 var(--td-calendar-selected-border-radius, var(--td-radius-default, 12rpx));
}
.t-calendar__dates-item--end {
border-radius: 0 var(--td-calendar-selected-border-radius, var(--td-radius-default, 12rpx)) var(--td-calendar-selected-border-radius, var(--td-radius-default, 12rpx)) 0;
}
.t-calendar__dates-item--start + .t-calendar__dates-item--end::before {
content: '';
display: block;
position: absolute;
top: 0;
width: 8rpx;
height: 100%;
background: var(--td-calendar-active-color, var(--td-brand-color, var(--td-primary-color-7, #0052d9)));
}
.t-calendar__dates-item--start + .t-calendar__dates-item--end:before {
left: -8rpx;
}
.t-calendar__dates-item--centre {
border-radius: 0;
background-color: var(--td-calendar-item-centre-color, var(--td-brand-color-light, var(--td-primary-color-1, #f2f3ff)));
}
.t-calendar__dates-item--centre::before,
.t-calendar__dates-item--centre::after {
content: '';
display: block;
position: absolute;
top: 0;
width: 8rpx;
height: 100%;
background-color: var(--td-calendar-item-centre-color, var(--td-brand-color-light, var(--td-primary-color-1, #f2f3ff)));
}
.t-calendar__dates-item--centre:before {
left: -8rpx;
}
.t-calendar__dates-item--centre:after {
right: -8rpx;
}
.t-calendar__dates-item--disabled {
color: var(--td-calendar-item-disabled-color, var(--td-text-color-disabled, var(--td-font-gray-4, rgba(0, 0, 0, 0.26))));
cursor: default;
}
.t-calendar__footer {
padding: 32rpx;
}
.t-calendar-switch-mode--none > .t-calendar__months {
height: 60vh;
}
.t-calendar-header {
display: flex;
justify-content: space-between;
align-items: center;
}
.t-calendar-header__with-action {
padding: 0rpx 32rpx 16rpx 32rpx;
box-sizing: border-box;
position: relative;
}
.t-calendar-header__with-action::after {
content: '';
display: block;
position: absolute;
top: unset;
bottom: 0;
left: unset;
right: unset;
background-color: var(--td-border-color, var(--td-gray-color-3, #e7e7e7));
}
.t-calendar-header__with-action::after {
height: 1px;
left: 0;
right: 0;
transform: scaleY(0.5);
}
.t-calendar-header__with-action .t-calendar-header__title {
flex: 1;
text-align: center;
font: var(--td-font-title-small, 600 28rpx / 44rpx var(--td-font-family, PingFang SC, Microsoft YaHei, Arial Regular));
}
.t-calendar-header__action {
display: flex;
font-size: 40rpx;
color: var(--td-calendar-switch-mode-icon-color, var(--td-text-color-secondary, var(--td-font-gray-2, rgba(0, 0, 0, 0.6))));
}
.t-calendar-header__icon {
padding: 16rpx;
}
.t-calendar-header__icon--disabled {
color: var(--td-calendar-switch-mode-icon-disabled-color, var(--td-text-color-disabled, var(--td-font-gray-4, rgba(0, 0, 0, 0.26))));
}
.t-calendar-header__title {
text-align: left;
}

View File

@@ -0,0 +1,454 @@
<template>
<view>
<t-popup
v-if="usePopup"
:visible="visible"
:using-custom-navbar="usingCustomNavbar"
:custom-navbar-height="customNavbarHeight"
placement="bottom"
@visible-change="onVisibleChange"
>
<CalendarTemplate
:class-prefix="classPrefix"
:use-popup="usePopup"
:switch-mode="switchMode"
:t-class="tClass"
:custom-style="tools._style([customStyle])"
:title="title"
:real-local-text="realLocalText"
:months="months"
:current-month="currentMonth"
:action-buttons="actionButtons"
:days="days"
:scroll-into-view="scrollIntoView"
:first-day-of-week="firstDayOfWeek"
:inner-confirm-btn="innerConfirmBtn"
@scroll="onScroll"
@close="handleClose"
@select="handleSelect"
@clickButton="onTplButtonTap"
@handleSwitchModeChange="handleSwitchModeChange"
>
<template #confirm-btn>
<slot name="confirm-btn" />
</template>
<template #title>
<slot name="title" />
</template>
</CalendarTemplate>
</t-popup>
<block v-else>
<CalendarTemplate
:class-prefix="classPrefix"
:use-popup="usePopup"
:switch-mode="switchMode"
:t-class="tClass"
:custom-style="tools._style([customStyle])"
:title="title"
:real-local-text="realLocalText"
:months="months"
:current-month="currentMonth"
:action-buttons="actionButtons"
:days="days"
:scroll-into-view="scrollIntoView"
:first-day-of-week="firstDayOfWeek"
:inner-confirm-btn="innerConfirmBtn"
@scroll="onScroll"
@close="handleClose"
@select="handleSelect"
@clickButton="onTplButtonTap"
@handleSwitchModeChange="handleSwitchModeChange"
>
<template #confirm-btn>
<slot name="confirm-btn" />
</template>
<template #title>
<slot name="title" />
</template>
</CalendarTemplate>
</block>
</view>
</template>
<script>
import TPopup from '../popup/popup';
import TButton from '../button/button';
import TIcon from '../icon/icon';
import CalendarTemplate from './template.vue';
import { uniComponent } from '../common/src/index';
import { prefix } from '../common/config';
import { coalesce } from '../common/utils';
import props from './props';
import TCalendar from '../common/shared/calendar/index';
import useCustomNavbar from '../mixins/using-custom-navbar';
import { getPrevMonth, getPrevYear, getNextMonth, getNextYear } from './utils';
import tools from '../common/utils.wxs';
import {
getMonthTitle,
getDateLabel,
isDateSelected,
} from './computed.js';
const name = `${prefix}-calendar`;
const defaultLocaleText = {
title: '请选择日期',
weekdays: ['日', '一', '二', '三', '四', '五', '六'],
monthTitle: '{year} 年 {month}',
months: ['1 月', '2 月', '3 月', '4 月', '5 月', '6 月', '7 月', '8 月', '9 月', '10 月', '11 月', '12 月'],
confirm: '确认',
};
export default uniComponent({
name,
options: {
styleIsolation: 'shared',
},
controlledProps: [
{
key: 'value',
event: 'confirm',
},
{
key: 'value',
event: 'change',
},
],
externalClasses: [
`${prefix}-class`,
],
mixins: [useCustomNavbar],
components: {
TPopup,
TButton,
TIcon,
CalendarTemplate,
},
props: {
...props,
},
emits: [
'update:visible',
],
data() {
return {
prefix,
classPrefix: name,
months: [],
scrollIntoView: '',
innerConfirmBtn: {},
realLocalText: {},
currentMonth: {},
actionButtons: {
preYearBtnDisable: false,
prevMonthBtnDisable: false,
nextMonthBtnDisable: false,
nextYearBtnDisable: false,
},
tools,
dataVisible: this.visible,
dataValue: coalesce(this.value, this.defaultValue),
days: [],
};
},
watch: {
type: {
handler(v) {
this.base.type = v;
},
},
allowSameDay(v) {
this.base.allowSameDay = v;
},
confirmBtn: {
handler(v) {
if (typeof v === 'string') {
this.innerConfirmBtn = v === 'slot' ? 'slot' : { content: v };
} else if (typeof v === 'object') {
this.innerConfirmBtn = v;
}
},
immediate: true,
},
firstDayOfWeek: 'onWatchMinMaxDate',
minDate: 'onWatchMinMaxDate',
maxDate: 'onWatchMinMaxDate',
value: {
handler(v) {
this.dataValue = v;
},
immediate: true,
deep: true,
},
visible: {
handler(v) {
this.dataVisible = v;
},
immediate: true,
},
dataValue: {
handler(v) {
this.base.value = v;
this.calcMonths();
this.updateCurrentMonth(Array.isArray(v) ? v[0] : v);
},
deep: true,
},
dataVisible: {
handler(v) {
if (v) {
this.onScrollIntoView();
this.base.value = this.dataValue;
this.calcMonths();
}
},
immediate: true,
},
format: {
handler(v) {
const { usePopup, dataVisible: visible } = this;
if (this.base) {
this.base.format = v;
}
if (!usePopup || visible) {
this.calcMonths();
}
},
immediate: true,
},
},
created() {
const values = Object.keys(props).reduce((acc, key) => ({
...acc,
[key]: this[key],
}));
this.base = new TCalendar(values);
},
mounted() {
const realLocalText = { ...defaultLocaleText, ...this.localeText };
this.initialValue();
this.onWatchMinMaxDate();
this.days = this.base.getDays(realLocalText.weekdays);
this.realLocalText = realLocalText;
this.calcMonths();
this.updateCurrentMonth();
if (!this.usePopup) {
this.onScrollIntoView();
}
},
methods: {
getMonthTitle,
getDateLabel,
isDateSelected,
initialValue() {
const { dataValue: value, type, minDate } = this;
if (!value) {
const today = new Date();
const now = minDate || new Date(today.getFullYear(), today.getMonth(), today.getDate()).getTime(); // 获取 0 点的时间戳
const initialValue = type === 'single' ? now : [now];
if (type === 'range') {
initialValue[1] = now + 24 * 3600 * 1000; // 第二天
}
this.dataValue = initialValue;
this.base.value = initialValue;
}
},
onScrollIntoView() {
const { dataValue: value } = this;
if (!value) return;
const date = new Date(Array.isArray(value) ? value[0] : value);
if (date) {
this.scrollIntoView = `year_${date.getFullYear()}_month_${date.getMonth()}`;
}
},
getCurrentYearAndMonth(v) {
const date = new Date(v);
return { year: date.getFullYear(), month: date.getMonth() };
},
updateActionButton(value) {
const _min = this.getCurrentYearAndMonth(this.base.minDate);
const _max = this.getCurrentYearAndMonth(this.base.maxDate);
const _value = this.getCurrentYearAndMonth(value);
const _minTimestamp = new Date(_min.year, _min.month, 1).getTime();
const _maxTimestamp = new Date(_max.year, _max.month, 1).getTime();
const _dateValue = new Date(_value.year, _value.month, 1);
const _prevYearTimestamp = getPrevYear(_dateValue).getTime();
const _prevMonthTimestamp = getPrevMonth(_dateValue).getTime();
const _nextMonthTimestamp = getNextMonth(_dateValue).getTime();
const _nextYearTimestamp = getNextYear(_dateValue).getTime();
const preYearBtnDisable = _prevYearTimestamp < _minTimestamp || _prevMonthTimestamp < _minTimestamp;
const prevMonthBtnDisable = _prevMonthTimestamp < _minTimestamp;
const nextYearBtnDisable = _nextMonthTimestamp > _maxTimestamp || _nextYearTimestamp > _maxTimestamp;
const nextMonthBtnDisable = _nextMonthTimestamp > _maxTimestamp;
this.actionButtons = {
preYearBtnDisable,
prevMonthBtnDisable,
nextYearBtnDisable,
nextMonthBtnDisable,
};
},
updateCurrentMonth(newValue) {
if (this.switchMode === 'none') return;
this.calcCurrentMonth(newValue);
},
calcCurrentMonth(newValue) {
const date = newValue || this.getCurrentDate();
const { year, month } = this.getCurrentYearAndMonth(date);
const currentMonth = this.months.filter(item => item.year === year && item.month === month);
this.updateActionButton(date);
this.currentMonth = currentMonth.length > 0 ? currentMonth : [this.months[0]];
},
calcMonths() {
if (!this.base) return;
const months = this.base.getMonths();
this.months = months;
},
close(trigger) {
if (this.autoClose) {
this.$emit('update:visible', false);
this.dataVisible = false;
}
this.$emit('close', { trigger });
},
onVisibleChange() {
this.close('overlay');
},
handleClose() {
this.close('close-btn');
},
handleSelect(e) {
const { readonly } = this;
const { date, year, month } = e.currentTarget.dataset;
if (date.type === 'disabled' || readonly) return;
const rawValue = this.base.select({ cellType: date.type, year, month, date: date.day });
const value = this.toTime(rawValue);
this.calcMonths();
this.updateCurrentMonth();
if (this.confirmBtn == null) {
// 不显示确认按钮,则选择完即关闭 popup
if (this.type === 'single' || rawValue.length === 2) {
this.dataVisible = false;
this._trigger('change', { value }); // 受控
}
}
this.$emit('select', { value });
},
onTplButtonTap() {
const rawValue = this.base.getTrimValue();
const value = this.toTime(rawValue);
this.close('confirm-btn');
this._trigger('confirm', { value });
},
toTime(val) {
if (!val) return null;
if (Array.isArray(val)) {
return val.map(item => item.getTime());
}
return val.getTime();
},
onScroll(e) {
this.$emit('scroll', e.detail);
},
getCurrentDate() {
let time = Array.isArray(this.base.value) ? this.base.value[0] : this.base.value;
if (this.currentMonth.length > 0) {
const year = this.currentMonth[0]?.year;
const month = this.currentMonth[0]?.month;
time = new Date(year, month, 1).getTime();
}
return time;
},
handleSwitchModeChange(e) {
const { type, disabled } = e.currentTarget.dataset;
if (disabled) return;
const date = this.getCurrentDate();
const funcMap = {
'pre-year': () => getPrevYear(date),
'pre-month': () => getPrevMonth(date),
'next-month': () => getNextMonth(date),
'next-year': () => getNextYear(date),
};
const newValue = funcMap[type]();
if (!newValue) return;
const { year, month } = this.getCurrentYearAndMonth(newValue);
this.$emit('panel-change', { year, month: month + 1 });
this.calcCurrentMonth(newValue);
},
onWatchMinMaxDate() {
const { firstDayOfWeek, minDate, maxDate } = this;
firstDayOfWeek && (this.base.firstDayOfWeek = firstDayOfWeek);
minDate && (this.base.minDate = minDate);
maxDate && (this.base.maxDate = maxDate);
this.calcMonths();
},
},
});
</script>
<style scoped>
@import './calendar.css';
</style>
<style scoped>
.t-calendar-switch-mode--none > .t-calendar__months {
/* support mp-alipay */
width: 100%;
}
</style>

View File

@@ -0,0 +1,42 @@
import { getRegExp } from '../common/runtime/wxs-polyfill';
export function isDateSelected(dateItem) {
return ['start', 'end', 'selected', 'centre'].indexOf(dateItem.type) >= 0;
}
export function getMonthTitle(year, month, pattern = '') {
// prettier-ignore
const REGEXP = getRegExp('\\{year\\}|\\{month\\}', 'g');
return pattern.replace(REGEXP, (match) => {
const replacements = {
'{year}': year,
'{month}': month < 10 ? `0${month}` : month,
};
return replacements[match] || match;
});
}
export function getDateLabel(monthItem, dateItem) {
const weekdayText = ['日', '一', '二', '三', '四', '五', '六'];
const weekday = (monthItem.weekdayOfFirstDay + dateItem.day - 1) % 7;
let label = `${monthItem.month + 1}${dateItem.day}日, 星期${weekdayText[weekday]}`;
if (dateItem.type === 'start') {
label = `开始日期:${label}`;
}
if (dateItem.type === 'end') {
label = `结束日期:${label}`;
}
if (isDateSelected(dateItem)) {
label = `已选中, ${label}`;
}
if (dateItem.prefix) {
label += `, ${dateItem.prefix}`;
}
if (dateItem.suffix) {
label += `, ${dateItem.suffix}`;
}
return label;
}

View File

@@ -0,0 +1,113 @@
/* eslint-disable */
/**
* 该文件为脚本自动生成文件,请勿随意修改。如需修改请联系 PMC
* */
import type { TdCalendarProps } from './type';
export default {
/** 是否允许区间选择日历的起止时间相同,仅当 `type='range'` 时有效 */
allowSameDay: Boolean,
/** 自动关闭;在点击关闭按钮、确认按钮、遮罩层时自动关闭,不需要手动设置 visible */
autoClose: {
type: Boolean,
default: true,
},
/** 确认按钮。值为 null 则不显示确认按钮。值类型为字符串,则表示自定义按钮文本,值类型为 Object 则表示透传 Button 组件属性 */
confirmBtn: {
type: [String, Object],
default: '' as TdCalendarProps['confirmBtn'],
},
/** 第一天从星期几开始,默认 0 = 周日 */
firstDayOfWeek: {
type: Number,
default: 0,
},
/** 用于格式化日期的函数 */
format: {
type: Function,
},
/** 国际化文案 */
localeText: {
type: Object,
},
/** 最大可选的日期,不传则默认半年后 */
maxDate: {
type: Number,
},
/** 最小可选的日期,不传则默认今天 */
minDate: {
type: Number,
},
/** 是否只读,只读状态下不能选择日期 */
readonly: Boolean,
/** 切换模式。 `none` 表示平铺展示所有月份; `month` 表示支持按月切换, `year-month` 表示既按年切换,也支持按月切换 */
switchMode: {
type: String,
default: 'none' as TdCalendarProps['switchMode'],
validator(val: TdCalendarProps['switchMode']): boolean {
if (!val) return true;
return ['none', 'month', 'year-month'].includes(val);
},
},
/** 标题,不传默认为“请选择日期” */
title: {
type: String,
},
/** 日历的选择类型single = 单选multiple = 多选; range = 区间选择 */
type: {
type: String,
default: 'single' as TdCalendarProps['type'],
validator(val: TdCalendarProps['type']): boolean {
if (!val) return true;
return ['single', 'multiple', 'range'].includes(val);
},
},
/** 是否使用弹出层包裹日历 */
usePopup: {
type: Boolean,
default: true,
},
/** 是否使用了自定义导航栏 */
usingCustomNavbar: Boolean,
/** 当前选择的日期,不传则选用 minDate 属性值或今天优先级minDate > today。当 type = multiple 或 range 时传入数组 */
value: {
type: [Number, Array],
},
/** 当前选择的日期,不传则选用 minDate 属性值或今天优先级minDate > today。当 type = multiple 或 range 时传入数组,非受控属性 */
defaultValue: {
type: [Number, Array],
},
/** 是否显示日历;`usePopup` 为 true 时有效 */
visible: Boolean,
/** 不显示 confirm-btn 时,完成选择时触发(暂不支持 type = multiple */
onChange: {
type: Function,
default: () => ({}),
},
/** 关闭按钮时触发 */
onClose: {
type: Function,
default: () => ({}),
},
/** 点击确认按钮时触发 */
onConfirm: {
type: Function,
default: () => ({}),
},
/** 切换月或年时触发switch-mode 不为 none 时有效) */
onPanelChange: {
type: Function,
default: () => ({}),
},
/** 滚动时触发 */
onScroll: {
type: Function,
default: () => ({}),
},
/** 点击日期时触发 */
onSelect: {
type: Function,
default: () => ({}),
},
};

View File

@@ -0,0 +1,57 @@
export default {
classPrefix: {
type: String,
default: '',
},
usePopup: {
type: Boolean,
},
switchMode: {
type: String,
default: '',
},
tClass: {
type: String,
default: '',
},
customStyle: {
type: [String, Object],
default: '',
},
title: {
type: String,
default: '',
},
realLocalText: {
type: Object,
default: () => ({}),
},
months: {
type: Array,
default: () => ([]),
},
currentMonth: {
type: [Array, Object],
default: () => ([]),
},
actionButtons: {
type: Object,
default: () => ({}),
},
days: {
type: Array,
default: () => ([]),
},
scrollIntoView: {
type: String,
default: '',
},
firstDayOfWeek: {
type: Number,
default: 0,
},
innerConfirmBtn: {
type: [Object, String],
default: '',
},
};

View File

@@ -0,0 +1,261 @@
<template>
<view
:class="utils.cls(classPrefix, [['popup', usePopup]]) + ' ' + classPrefix + '-switch-mode--' + switchMode + ' ' + tClass"
:style="customStyle"
>
<view
:class="classPrefix + '__title'"
tabindex="0"
>
<slot name="title" />
<text v-if="title || realLocalText.title">
{{ title || realLocalText.title }}
</text>
</view>
<t-icon
v-if="usePopup"
name="close"
:t-class="classPrefix + '__close-btn'"
size="48rpx"
aria-role="button"
aria-label="关闭"
:custom-style="closeBtnCustomStyle"
@click="handleClose"
/>
<block
v-if="switchMode !== 'none'"
name="calendar-header"
>
<calendar-header
:class-prefix="classPrefix + '-header'"
:switch-mode="switchMode"
:title="getMonthTitle(
currentMonth[0] && currentMonth[0].year,
realLocalText.months && currentMonth[0] && realLocalText.months[currentMonth[0].month],
realLocalText.monthTitle
)"
:pre-year-btn-disable="actionButtons.preYearBtnDisable"
:prev-month-btn-disable="actionButtons.prevMonthBtnDisable"
:next-year-btn-disable="actionButtons.nextYearBtnDisable"
:next-month-btn-disable="actionButtons.nextMonthBtnDisable"
@handleSwitchModeChange="handleSwitchModeChange"
/>
</block>
<view
aria-hidden
:class="classPrefix + '__days'"
>
<view
v-for="(item, index) in days"
:key="index"
:class="classPrefix + '__days-item'"
>
{{ item }}
</view>
</view>
<scroll-view
:class="classPrefix + '__months'"
:scroll-into-view="scrollIntoView"
scroll-y
enhanced
:show-scrollbar="false"
@scroll="onScroll"
>
<block
v-for="(item, index) in switchMode === 'none' ? months : currentMonth"
:key="index"
>
<block
v-if="switchMode === 'none'"
name="calendar-header"
>
<calendar-header
:t-class="classPrefix + '__month'"
:class-prefix="classPrefix + '-header'"
:switch-mode="switchMode"
:t-id="'year_' + item.year + '_month_' + item.month"
:title="getMonthTitle(item.year, realLocalText.months && realLocalText.months[item.month], realLocalText.monthTitle)"
:pre-year-btn-disable="actionButtons.preYearBtnDisable"
:prev-month-btn-disable="actionButtons.prevMonthBtnDisable"
:next-year-btn-disable="actionButtons.nextYearBtnDisable"
:next-month-btn-disable="actionButtons.nextMonthBtnDisable"
@handleSwitchModeChange="handleSwitchModeChange"
/>
</block>
<view :class="classPrefix + '__dates'">
<view
v-for="(item, index1) in (item.weekdayOfFirstDay - firstDayOfWeek + 7) % 7"
:key="index1"
/>
<block
v-for="(dateItem, dateIndex) in item.months"
:key="dateIndex"
>
<view
:class="classPrefix + '__dates-item ' + dateItem.className + ' ' + classPrefix + '__dates-item--' + dateItem.type"
:data-year="item.year"
:data-month="item.month"
:data-date="dateItem"
aria-role="button"
:aria-label="getDateLabel(item, dateItem)"
:aria-disabled="dateItem.type === 'disabled'"
@click="handleSelect"
>
<view
v-if="dateItem.prefix"
:class="classPrefix + '__dates-item-prefix'"
>
{{ dateItem.prefix }}
</view>
{{ dateItem.day }}
<view
v-if="dateItem.suffix"
:class="classPrefix + '__dates-item-suffix ' + classPrefix + '__dates-item-suffix--' + dateItem.type"
>
{{ dateItem.suffix }}
</view>
</view>
</block>
</view>
</block>
</scroll-view>
<view
v-if="innerConfirmBtn != null && usePopup"
:class="classPrefix + '__footer'"
>
<slot
v-if="innerConfirmBtn === 'slot'"
name="confirm-btn"
/>
<block v-else-if="innerConfirmBtn">
<t-button
:t-id="innerConfirmBtn.tId"
:custom-style="innerConfirmBtn.style"
:block="coalesce(innerConfirmBtn.block, true)"
:t-class="coalesce(coalesce(innerConfirmBtn.tClass, prefix + '-class-action'))"
:class="prefix + '-calendar__confirm-btn'"
:disabled="innerConfirmBtn.disabled"
:data-type="'action'"
:data-extra="innerConfirmBtn.dataExtra"
:custom-dataset="innerConfirmBtn.customDataset"
:content="innerConfirmBtn.content || realLocalText.confirm"
:icon="innerConfirmBtn.icon"
:loading="innerConfirmBtn.loading"
:loading-props="innerConfirmBtn.loadingProps"
:theme="coalesce(innerConfirmBtn.theme, 'primary')"
:ghost="innerConfirmBtn.ghost"
:shape="innerConfirmBtn.shape"
:size="innerConfirmBtn.size"
:variant="innerConfirmBtn.variant"
:open-type="innerConfirmBtn.openType"
:hover-class="innerConfirmBtn.hoverClass"
:hover-stop-propagation="innerConfirmBtn.hoverStopPropagation"
:hover-start-time="innerConfirmBtn.hoverStartTime"
:hover-stay-time="innerConfirmBtn.hoverStayTime"
:lang="innerConfirmBtn.lang"
:session-from="innerConfirmBtn.sessionFrom"
:send-message-title="innerConfirmBtn.sendMessageTitle"
:send-message-path="innerConfirmBtn.sendMessagePath"
:send-message-img="innerConfirmBtn.sendMessageImg"
:app-parameter="innerConfirmBtn.appParameter"
:show-message-card="innerConfirmBtn.showMessageCard"
:aria-label="innerConfirmBtn.ariaLabel"
@click="onTplButtonTap"
@getuserinfo="onTplButtonTap"
@contact="onTplButtonTap"
@getphonenumber="onTplButtonTap"
@error="onTplButtonTap"
@opensetting="onTplButtonTap"
@launchapp="onTplButtonTap"
@agreeprivacyauthorization="onTplButtonTap"
>
<slot v-if="innerConfirmBtn.useDefaultSlot" />
</t-button>
</block>
</view>
</view>
</template>
<script>
import TIcon from '../icon/icon.vue';
import TButton from '../button/button.vue';
import utils from '../common/utils.wxs';
import {
getDateLabel,
getMonthTitle,
} from './computed.js';
import CalendarHeader from './calendar-header.vue';
import { prefix } from '../common/config';
import { coalesce } from '../common/utils';
import props from './template.props';
export default {
name: 'TCalendarContent',
options: {
styleIsolation: 'shared',
},
components: {
CalendarHeader,
TIcon,
TButton,
},
props: {
...props,
},
emits: [
'clickButton',
'close',
'scroll',
'select',
'handleSwitchModeChange',
],
data() {
return {
prefix,
utils,
};
},
computed: {
closeBtnCustomStyle() {
return utils._style({
position: 'absolute',
top: '16px',
right: '16px',
margin: '-12px',
padding: '12px',
color: 'var(--td-calendar-title-color, var(--td-text-color-primary, var(--td-font-gray-1, rgba(0, 0, 0, .9))))',
});
},
},
watch: {
},
mounted() {
},
methods: {
coalesce,
getDateLabel,
getMonthTitle,
onTplButtonTap() {
this.$emit('clickButton');
},
handleSelect(...args) {
this.$emit('select', ...args);
},
handleClose() {
this.$emit('close');
},
onScroll(...args) {
this.$emit('scroll', ...args);
},
handleSwitchModeChange(...args) {
this.$emit('handleSwitchModeChange', ...args);
},
},
};
</script>
<style scoped>
@import './calendar.css';
</style>

View File

@@ -0,0 +1,141 @@
/* eslint-disable */
/**
* 该文件为脚本自动生成文件,请勿随意修改。如需修改请联系 PMC
* */
import type { TdButtonProps as ButtonProps } from '../button/type';
export interface TdCalendarProps {
/**
* 是否允许区间选择日历的起止时间相同,仅当 `type='range'` 时有效
* @default false
*/
allowSameDay?: boolean;
/**
* 自动关闭;在点击关闭按钮、确认按钮、遮罩层时自动关闭,不需要手动设置 visible
* @default true
*/
autoClose?: boolean;
/**
* 确认按钮。值为 null 则不显示确认按钮。值类型为字符串,则表示自定义按钮文本,值类型为 Object 则表示透传 Button 组件属性
* @default ''
*/
confirmBtn?: string | ButtonProps | null;
/**
* 第一天从星期几开始,默认 0 = 周日
* @default 0
*/
firstDayOfWeek?: number;
/**
* 用于格式化日期的函数
*/
format?: CalendarFormatType;
/**
* 国际化文案
*/
localeText?: CalendarLocaleText;
/**
* 最大可选的日期,不传则默认半年后
*/
maxDate?: number;
/**
* 最小可选的日期,不传则默认今天
*/
minDate?: number;
/**
* 是否只读,只读状态下不能选择日期
*/
readonly?: boolean;
/**
* 切换模式。 `none` 表示平铺展示所有月份; `month` 表示支持按月切换, `year-month` 表示既按年切换,也支持按月切换
* @default none
*/
switchMode?: 'none' | 'month' | 'year-month';
/**
* 标题,不传默认为“请选择日期”
*/
title?: string;
/**
* 日历的选择类型single = 单选multiple = 多选; range = 区间选择
* @default single
*/
type?: 'single' | 'multiple' | 'range';
/**
* 是否使用弹出层包裹日历
* @default true
*/
usePopup?: boolean;
/**
* 是否使用了自定义导航栏
* @default false
*/
usingCustomNavbar?: boolean;
/**
* 当前选择的日期,不传则选用 minDate 属性值或今天优先级minDate > today。当 type = multiple 或 range 时传入数组
*/
value?: number | number[];
/**
* 当前选择的日期,不传则选用 minDate 属性值或今天优先级minDate > today。当 type = multiple 或 range 时传入数组,非受控属性
*/
defaultValue?: number | number[];
/**
* 是否显示日历;`usePopup` 为 true 时有效
* @default false
*/
visible?: boolean;
/**
* 不显示 confirm-btn 时,完成选择时触发(暂不支持 type = multiple
*/
onChange?: (context: { value: number | number[] }) => void;
/**
* 关闭按钮时触发
*/
onClose?: (context: { trigger: CalendarTrigger }) => void;
/**
* 点击确认按钮时触发
*/
onConfirm?: (context: { value: number | number[] }) => void;
/**
* 切换月或年时触发switch-mode 不为 none 时有效)
*/
onPanelChange?: (context: { year: number; month: number }) => void;
/**
* 滚动时触发
*/
onScroll?: (context: {
scrollLeft: number;
scrollTop: number;
scrollHeight: number;
scrollWidth: number;
deltaX: number;
deltaY: number;
}) => void;
/**
* 点击日期时触发
*/
onSelect?: (context: { value: number | number[] }) => void;
}
export type CalendarFormatType = (day: TDate) => TDate;
export type TDateType = 'selected' | 'disabled' | 'start' | 'start-end' | 'centre' | 'end' | '';
export interface TDate {
date: Date;
day: number;
type: TDateType;
className?: string;
prefix?: string;
suffix?: string;
}
export interface CalendarLocaleText {
title?: string;
weekdays?: string[];
monthTitle?: string;
months?: string[];
confirm?: string;
}
export type CalendarTrigger = 'close-btn' | 'confirm-btn' | 'overlay' | 'auto-close';

View File

@@ -0,0 +1,16 @@
export function getMonthByOffset(date, offset) {
const _date = new Date(date);
_date.setMonth(_date.getMonth() + offset);
return _date;
}
export function getYearByOffset(date, offset) {
const _date = new Date(date);
_date.setFullYear(_date.getFullYear() + offset);
return _date;
}
export const getPrevMonth = date => getMonthByOffset(date, -1);
export const getNextMonth = date => getMonthByOffset(date, 1);
export const getPrevYear = date => getYearByOffset(date, -1);
export const getNextYear = date => getYearByOffset(date, 1);

View File

@@ -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 | -

View 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 | -

View 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;
}

View 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>

View 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: () => ({}),
},
};

View 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';

View File

@@ -0,0 +1,44 @@
.t-cell-group {
position: relative;
}
.t-cell-group__title {
font-family: PingFangSC-Regular;
font-size: var(--td-cell-group-title-font-size, 28rpx);
color: var(--td-cell-group-title-color, var(--td-text-color-placeholder, var(--td-font-gray-3, rgba(0, 0, 0, 0.4))));
text-align: left;
line-height: var(--td-cell-group-title-line-height, 90rpx);
background-color: var(--td-cell-group-title-bg-color, var(--td-bg-color-secondarycontainer, var(--td-gray-color-1, #f3f3f3)));
padding-left: var(--td-cell-group-title-padding-left, 32rpx);
}
.t-cell-group--bordered::before {
position: absolute;
box-sizing: border-box;
content: ' ';
pointer-events: none;
right: 0;
left: 0;
top: 0;
border-top: 1px solid var(--td-cell-group-border-color, var(--td-component-stroke, var(--td-gray-color-3, #e7e7e7)));
transform: scaleY(0.5);
transform-origin: 0 0;
transform-origin: top;
z-index: 1;
}
.t-cell-group--bordered::after {
position: absolute;
box-sizing: border-box;
content: ' ';
pointer-events: none;
right: 0;
left: 0;
bottom: 0;
border-bottom: 1px solid var(--td-cell-group-border-color, var(--td-component-stroke, var(--td-gray-color-3, #e7e7e7)));
transform: scaleY(0.5);
transform-origin: bottom;
z-index: 1;
}
.t-cell-group--card {
margin: 0 32rpx;
border-radius: var(--td-radius-large, 18rpx);
overflow: hidden;
}

View File

@@ -0,0 +1,69 @@
<template>
<view>
<view
v-if="title"
:class="[
classPrefix + '__title ',
tClassTitle
]"
>
{{ title }}
</view>
<view
:style="tools._style([customStyle])"
:class="[
tools.cls(classPrefix, [['bordered', bordered], theme]),
tClass
]"
>
<slot />
</view>
</view>
</template>
<script>
import { uniComponent } from '../common/src/index';
import { prefix } from '../common/config';
import props from './props';
import tools from '../common/utils.wxs';
import { ParentMixin, RELATION_MAP } from '../common/relation';
const name = `${prefix}-cell-group`;
export default uniComponent({
name,
options: {
styleIsolation: 'shared',
},
externalClasses: [`${prefix}-class`, `${prefix}-class-title`],
mixins: [ParentMixin(RELATION_MAP.Cell)],
props: {
...props,
},
data() {
return {
prefix,
classPrefix: name,
tools,
};
},
methods: {
innerAfterLinked() {
this.updateLastChid();
},
innerAfterUnLinked() {
this.updateLastChid();
},
updateLastChid() {
const { children } = this;
children.forEach((child, index) => {
child.isLastChild = index === children.length - 1;
});
},
},
});
</script>
<style scoped>
@import './cell-group.css';
</style>

View File

@@ -0,0 +1,25 @@
/* eslint-disable */
/**
* 该文件为脚本自动生成文件,请勿随意修改。如需修改请联系 PMC
* */
import type { TdCellGroupProps } from './type';
export default {
/** 是否显示组边框 */
bordered: Boolean,
/** 单元格组风格 */
theme: {
type: String,
default: 'default' as TdCellGroupProps['theme'],
validator(val: TdCellGroupProps['theme']): boolean {
if (!val) return true;
return ['default', 'card'].includes(val);
},
},
/** 单元格组标题 */
title: {
type: String,
default: '',
},
};

View File

@@ -0,0 +1,23 @@
/* eslint-disable */
/**
* 该文件为脚本自动生成文件,请勿随意修改。如需修改请联系 PMC
* */
export interface TdCellGroupProps {
/**
* 是否显示组边框
* @default false
*/
bordered?: boolean;
/**
* 单元格组风格
* @default default
*/
theme?: 'default' | 'card';
/**
* 单元格组标题
* @default ''
*/
title?: string;
}

View File

@@ -0,0 +1,116 @@
:: BASE_DOC ::
## API
### Cell Props
name | type | default | description | required
-- | -- | -- | -- | --
custom-style | Object | - | CSS(Cascading Style Sheets) | N
align | String | middle | options: top/middle/bottom | N
arrow | Boolean / Object | false | \- | N
bordered | Boolean | true | \- | N
description | String | - | \- | N
hover | Boolean | - | \- | N
image | String | - | \- | N
jump-type | String | navigateTo | options: switchTab/reLaunch/redirectTo/navigateTo | N
left-icon | String / Object | - | \- | N
note | String | - | \- | N
note-style | String / Object | - | \- | N
required | Boolean | false | \- | N
right-icon | String / Object | - | \- | N
right-icon-style | String / Object | - | \- | N
title | String | - | \- | N
title-style | String / Object | - | \- | N
url | String | - | \- | N
### Cell Events
name | params | description
-- | -- | --
click | `(e: MouseEvent)` | \-
### Cell Slots
name | Description
-- | --
description | \-
image | \-
left-icon | \-
note | \-
right-icon | \-
title | \-
### Cell External Classes
className | Description
-- | --
t-class | \-
t-class-center | \-
t-class-description | \-
t-class-hover | \-
t-class-image | \-
t-class-left | \-
t-class-left-icon | \-
t-class-note | \-
t-class-right | \-
t-class-right-icon | \-
t-class-title | \-
### CellGroup Props
name | type | default | description | required
-- | -- | -- | -- | --
custom-style | Object | - | CSS(Cascading Style Sheets) | N
bordered | Boolean | false | \- | N
theme | String | default | options: default/card | N
title | String | - | \- | N
### CellGroup Slots
name | Description
-- | --
\- | \-
### CellGroup External Classes
className | Description
-- | --
t-class | \-
t-class-title | \-
### CSS Variables
The component provides the following CSS variables, which can be used to customize styles.
Name | Default Value | Description
-- | -- | --
--td-cell-group-border-color | @component-stroke | -
--td-cell-group-title-bg-color | @bg-color-secondarycontainer | -
--td-cell-group-title-color | @text-color-placeholder | -
--td-cell-group-title-font-size | 28rpx | -
--td-cell-group-title-line-height | 90rpx | -
--td-cell-group-title-padding-left | 32rpx | -
--td-cell-bg-color | @bg-color-container | -
--td-cell-border-color | @component-stroke | -
--td-cell-border-left-space | @cell-horizontal-padding | -
--td-cell-border-right-space | 0 | -
--td-cell-border-width | 1px | -
--td-cell-description-color | @text-color-secondary | -
--td-cell-description-font | @font-body-medium | -
--td-cell-height | auto | -
--td-cell-horizontal-padding | 32rpx | -
--td-cell-hover-color | @bg-color-secondarycontainer | -
--td-cell-image-height | 96rpx | -
--td-cell-image-width | 96rpx | -
--td-cell-left-icon-color | @brand-color | -
--td-cell-left-icon-size | 48rpx | -
--td-cell-note-color | @text-color-placeholder | -
--td-cell-note-font-size | @font-size-m | -
--td-cell-required-color | @error-color | -
--td-cell-required-font-size | @font-size-m | -
--td-cell-right-icon-color | @text-color-placeholder | -
--td-cell-right-icon-size | 48rpx | -
--td-cell-title-color | @text-color-primary | -
--td-cell-title-font | @font-body-large | -
--td-cell-vertical-padding | 32rpx | -

View File

@@ -0,0 +1,147 @@
---
title: Cell 单元格
description: 用于各个类别行的信息展示。
spline: data
isComponent: true
---
## 引入
可在 `main.ts` 或在需要使用的页面或组件中引入。
```js
import TCell from '@tdesign/uniapp/cell/cell.vue';
import TCellGroup from '@tdesign/uniapp/cell-group/cell-group.vue';
```
### 类型
单行单元格
{{ base }}
多行单元格
{{ multiple }}
### 样式
卡片单元格
{{ theme }}
## API
### Cell Props
名称 | 类型 | 默认值 | 描述 | 必传
-- | -- | -- | -- | --
custom-style | Object | - | 自定义样式 | N
align | String | middle | 右侧内容的对齐方式默认居中对齐。可选项top/middle/bottom | N
arrow | Boolean / Object | false | 是否显示右侧箭头 | N
bordered | Boolean | true | 是否显示下边框 | N
description | String | - | 下方内容描述 | N
hover | Boolean | - | 是否开启点击反馈 | N
image | String | - | 主图 | N
jump-type | String | navigateTo | 链接跳转类型。可选项switchTab/reLaunch/redirectTo/navigateTo | N
left-icon | String / Object | - | 左侧图标,出现在单元格标题的左侧 | N
note | String | - | 和标题同行的说明文字 | N
note-style | String / Object | - | 说明文字自定义样式 | N
required | Boolean | false | 是否显示表单必填星号 | N
right-icon | String / Object | - | 最右侧图标 | N
right-icon-style | String / Object | - | 右侧图标自定义样式 | N
title | String | - | 标题 | N
title-style | String / Object | - | 标题自定义样式 | N
url | String | - | 点击后跳转链接地址。如果值为空,则表示不需要跳转 | N
### Cell Events
名称 | 参数 | 描述
-- | -- | --
click | `(e: MouseEvent)` | 右侧内容
### Cell Slots
名称 | 描述
-- | --
description | 自定义 `description` 显示内容
image | 自定义 `image` 显示内容
left-icon | 自定义 `left-icon` 显示内容
note | 自定义 `note` 显示内容
right-icon | 自定义 `right-icon` 显示内容
title | 自定义 `title` 显示内容
### Cell External Classes
类名 | 描述
-- | --
t-class | 根节点样式类
t-class-center | 中间(`title`, `description`)内容样式类
t-class-description | 下方描述内容样式类
t-class-hover | 悬停样式类
t-class-image | 图片样式类
t-class-left | 左侧内容样式类
t-class-left-icon | 左侧图标样式类
t-class-note | 右侧说明文字样式类
t-class-right | 右侧内容样式类
t-class-right-icon | 右侧图标样式类
t-class-title | 标题样式类
### CellGroup Props
名称 | 类型 | 默认值 | 描述 | 必传
-- | -- | -- | -- | --
custom-style | Object | - | 自定义样式 | N
bordered | Boolean | false | 是否显示组边框 | N
theme | String | default | 单元格组风格。可选项default/card | N
title | String | - | 单元格组标题 | N
### CellGroup Slots
名称 | 描述
-- | --
\- | 默认插槽,自定义内容区域内容
### CellGroup External Classes
类名 | 描述
-- | --
t-class | 根节点样式类
t-class-title | 标题样式类
### CSS Variables
组件提供了下列 CSS 变量,可用于自定义样式。
名称 | 默认值 | 描述
-- | -- | --
--td-cell-group-border-color | @component-stroke | -
--td-cell-group-title-bg-color | @bg-color-secondarycontainer | -
--td-cell-group-title-color | @text-color-placeholder | -
--td-cell-group-title-font-size | 28rpx | -
--td-cell-group-title-line-height | 90rpx | -
--td-cell-group-title-padding-left | 32rpx | -
--td-cell-bg-color | @bg-color-container | -
--td-cell-border-color | @component-stroke | -
--td-cell-border-left-space | @cell-horizontal-padding | -
--td-cell-border-right-space | 0 | -
--td-cell-border-width | 1px | -
--td-cell-description-color | @text-color-secondary | -
--td-cell-description-font | @font-body-medium | -
--td-cell-height | auto | -
--td-cell-horizontal-padding | 32rpx | -
--td-cell-hover-color | @bg-color-secondarycontainer | -
--td-cell-image-height | 96rpx | -
--td-cell-image-width | 96rpx | -
--td-cell-left-icon-color | @brand-color | -
--td-cell-left-icon-size | 48rpx | -
--td-cell-note-color | @text-color-placeholder | -
--td-cell-note-font-size | @font-size-m | -
--td-cell-required-color | @error-color | -
--td-cell-required-font-size | @font-size-m | -
--td-cell-right-icon-color | @text-color-placeholder | -
--td-cell-right-icon-size | 48rpx | -
--td-cell-title-color | @text-color-primary | -
--td-cell-title-font | @font-body-large | -
--td-cell-vertical-padding | 32rpx | -

View File

@@ -0,0 +1,94 @@
.t-cell {
position: relative;
display: flex;
box-sizing: border-box;
width: 100%;
padding: var(--td-cell-vertical-padding, 32rpx) var(--td-cell-horizontal-padding, 32rpx);
height: var(--td-cell-height, auto);
background-color: var(--td-cell-bg-color, var(--td-bg-color-container, var(--td-font-white-1, #ffffff)));
}
.t-cell--bordered::after {
position: absolute;
box-sizing: border-box;
content: ' ';
pointer-events: none;
right: 0;
left: 0;
bottom: 0;
border-bottom: var(--td-cell-border-width, 1px) solid var(--td-cell-border-color, var(--td-component-stroke, var(--td-gray-color-3, #e7e7e7)));
transform: scaleY(0.5);
transform-origin: bottom;
left: var(--td-cell-border-left-space, var(--td-cell-horizontal-padding, 32rpx));
right: var(--td-cell-border-right-space, 0);
}
.t-cell__description {
font: var(--td-cell-description-font, var(--td-font-body-medium, 28rpx / 44rpx var(--td-font-family, PingFang SC, Microsoft YaHei, Arial Regular)));
color: var(--td-cell-description-color, var(--td-text-color-secondary, var(--td-font-gray-2, rgba(0, 0, 0, 0.6))));
}
.t-cell__description-text {
margin-top: calc(var(--td-spacer, 16rpx) / 2);
}
.t-cell__note {
display: flex;
align-items: center;
justify-content: flex-end;
color: var(--td-cell-note-color, var(--td-text-color-placeholder, var(--td-font-gray-3, rgba(0, 0, 0, 0.4))));
font-size: var(--td-cell-note-font-size, var(--td-font-size-m, 32rpx));
}
.t-cell__title {
margin-right: var(--td-spacer-2, 32rpx);
}
.t-cell__title,
.t-cell__note {
flex: 1 1 auto;
}
.t-cell__title:empty,
.t-cell__note:empty {
display: none;
}
.t-cell__title-text {
display: flex;
font: var(--td-cell-title-font, var(--td-font-body-large, 32rpx / 48rpx var(--td-font-family, PingFang SC, Microsoft YaHei, Arial Regular)));
color: var(--td-cell-title-color, var(--td-text-color-primary, var(--td-font-gray-1, rgba(0, 0, 0, 0.9))));
}
.t-cell__left,
.t-cell__right {
align-self: stretch;
}
.t-cell__left:not(:empty) {
margin-right: var(--td-spacer-1, 24rpx);
}
.t-cell__left-icon {
color: var(--td-cell-left-icon-color, var(--td-brand-color, var(--td-primary-color-7, #0052d9)));
font-size: var(--td-cell-left-icon-size, 48rpx);
}
.t-cell__left-image {
height: var(--td-cell-image-height, 96rpx);
width: var(--td-cell-image-width, 96rpx);
}
.t-cell__note:not(:empty) + .t-cell__right {
margin-left: calc(var(--td-spacer, 16rpx) / 2);
}
.t-cell__right {
display: flex;
}
.t-cell__right-icon {
color: var(--td-cell-right-icon-color, var(--td-text-color-placeholder, var(--td-font-gray-3, rgba(0, 0, 0, 0.4))));
font-size: var(--td-cell-right-icon-size, 48rpx);
}
.t-cell__right--middle {
align-items: center;
}
.t-cell__right--top {
align-items: flex-start;
}
.t-cell__right--bottom {
align-items: flex-end;
}
.t-cell--hover {
background-color: var(--td-cell-hover-color, var(--td-bg-color-secondarycontainer, var(--td-gray-color-1, #f3f3f3)));
}
.t-cell--required {
font-size: var(--td-cell-required-font-size, var(--td-font-size-m, 32rpx));
color: var(--td-cell-required-color, var(--td-error-color, var(--td-error-color-6, #d54941)));
}

View File

@@ -0,0 +1,260 @@
<template>
<view
:style="tools._style([customStyle])"
:class="[
tClass,
tools.cls(classPrefix, [['bordered', bordered || isLastChild]])
]"
:hover-class="hover ? classPrefix + '--hover' : ''"
hover-stay-time="70"
:aria-role="ariaRole || (arrow ? 'button' : '')"
:aria-label="ariaLabel"
@click="onClick"
>
<view :class="classPrefix + '__left ' + tClassLeft">
<block
v-if="_leftIcon"
name="icon"
>
<t-icon
:custom-style="leftIconCustomStyle"
:t-class="classPrefix + '__left-icon ' + tClassLeftIcon"
:name="_leftIcon.name"
:size="_leftIcon.size"
:color="_leftIcon.color"
:aria-hidden="true"
:aria-label="_leftIcon.ariaLabel"
:aria-role="_leftIcon.ariaRole"
@click="'handleClose' || ''"
/>
</block>
<slot name="left-icon" />
<t-image
v-if="image"
shape="round"
:t-class="classPrefix + '__left-image ' + tClassImage"
:src="image"
:custom-style="leftImageCustomStyle"
/>
<slot name="image" />
</view>
<view :class="classPrefix + '__title ' + tClassCenter">
<view
:class="[
classPrefix + '__title-text ',
tClassTitle
]"
:style="tools._style(titleStyle)"
>
<block v-if="title">
{{ title }}
</block>
<slot name="title" />
<block v-if="required">
<text
decode
:class="classPrefix + '--required'"
>
&nbsp;*
</text>
</block>
</view>
<view
:class="[
classPrefix + '__description ',
tClassDescription
]"
>
<view
v-if="description"
:class="classPrefix + '__description-text'"
>
{{ description }}
</view>
<slot name="description" />
</view>
</view>
<view
:class="[
classPrefix + '__note ',
tClassNote
]"
:style="tools._style(noteStyle)"
>
<text v-if="note">
{{ note }}
</text>
<slot name="note" />
</view>
<view
:class="[
tools.cls(classPrefix + '__right', [align]),
tClassRight
]"
>
<t-icon
v-if="_arrow"
:custom-style="rightArrowCustomStyle"
:t-class=" classPrefix + '__right-icon ' + tClassRightIcon"
:name="_arrow.name || ''"
:size="_arrow.size"
:color="_arrow.color"
:aria-hidden="true"
:aria-label="_arrow.ariaLabel"
:aria-role="_arrow.ariaRole"
@click="'handleClose' || ''"
/>
<block v-else>
<block
v-if="_rightIcon"
name="icon"
>
<t-icon
:custom-style="rightIconCustomStyle"
:t-class=" classPrefix + '__right-icon ' + tClassRightIcon"
:name="_rightIcon.name"
:size="_rightIcon.size"
:color="_rightIcon.color || ''"
:aria-hidden="true"
:aria-label="_rightIcon.ariaLabel"
:aria-role="_rightIcon.ariaRole"
@click="'handleClose' || ''"
/>
</block>
<slot name="right-icon" />
</block>
</view>
</view>
</template>
<script>
import TIcon from '../icon/icon';
import TImage from '../image/image';
import { uniComponent } from '../common/src/index';
import { prefix } from '../common/config';
import props from './props';
import { calcIcon } from '../common/utils';
import tools from '../common/utils.wxs';
import { ChildrenMixin, RELATION_MAP } from '../common/relation';
const name = `${prefix}-cell`;
const COMMON_RIGHT_ICON_STYLE = {
color: 'var(--td-cell-right-icon-color, var(--td-text-color-placeholder, var(--td-font-gray-3, rgba(0, 0, 0, .4))))',
fontSize: 'var(--td-cell-right-icon-font-size, 24px)',
};
export default uniComponent({
name,
options: {
styleIsolation: 'shared',
},
externalClasses: [
`${prefix}-class`,
`${prefix}-class-title`,
`${prefix}-class-description`,
`${prefix}-class-note`,
`${prefix}-class-hover`,
`${prefix}-class-image`,
`${prefix}-class-left`,
`${prefix}-class-left-icon`,
`${prefix}-class-center`,
`${prefix}-class-right`,
`${prefix}-class-right-icon`,
],
mixins: [ChildrenMixin(RELATION_MAP.Cell)],
components: {
TIcon,
TImage,
},
props: {
...props,
},
emits: [
'click',
],
data() {
return {
prefix,
classPrefix: name,
_arrow: null,
_rightIcon: null,
_leftIcon: null,
isLastChild: false,
tools,
};
},
computed: {
rightArrowCustomStyle() {
return tools._style([
COMMON_RIGHT_ICON_STYLE,
this.rightIconStyle || '',
this._arrow.style || '',
]);
},
rightIconCustomStyle() {
return tools._style([
COMMON_RIGHT_ICON_STYLE,
this.rightIconStyle || '',
this._rightIcon.style || '',
]);
},
leftIconCustomStyle() {
return tools._style([
{
color: 'var(--td-cell-left-icon-color, var(--td-brand-color, var(--td-primary-color-7, #0052d9)))',
fontSize: 'var(--td-cell-left-icon-font-size, 24px)',
},
this._leftIcon.style || '',
]);
},
leftImageCustomStyle() {
return tools._style({
height: 'var(--td-cell-image-height, 48px)',
width: 'var(--td-cell-image-width, 48px)',
});
},
},
watch: {
leftIcon: {
handler(e) {
this.setIcon('_leftIcon', e, '');
},
immediate: true,
},
rightIcon: {
handler(e) {
this.setIcon('_rightIcon', e, '');
},
immediate: true,
},
arrow: {
handler(e) {
this.setIcon('_arrow', e, 'chevron-right');
},
immediate: true,
},
},
methods: {
setIcon(e, t, s) {
this[e] = calcIcon(t, s);
},
onClick(e) {
this.$emit('click', e);
this.jumpLink();
},
jumpLink(e = 'url', t = 'jumpType') {
const s = this[e];
const i = this[t];
if (s) {
uni[i]({
url: s,
});
}
},
},
});
</script>
<style scoped>
@import './cell.css';
</style>

View File

@@ -0,0 +1,87 @@
/* eslint-disable */
/**
* 该文件为脚本自动生成文件,请勿随意修改。如需修改请联系 PMC
* */
import type { TdCellProps } from './type';
export default {
/** 右侧内容的对齐方式,默认居中对齐 */
align: {
type: String,
default: 'middle' as TdCellProps['align'],
validator(val: TdCellProps['align']): boolean {
if (!val) return true;
return ['top', 'middle', 'bottom'].includes(val);
},
},
/** 是否显示右侧箭头 */
arrow: {
type: [Boolean, Object],
default: false as TdCellProps['arrow'],
},
/** 是否显示下边框 */
bordered: {
type: Boolean,
default: true,
},
/** 下方内容描述 */
description: {
type: String,
},
/** 是否开启点击反馈 */
hover: Boolean,
/** 主图 */
image: {
type: String,
},
/** 链接跳转类型 */
jumpType: {
type: String,
default: 'navigateTo' as TdCellProps['jumpType'],
validator(val: TdCellProps['jumpType']): boolean {
if (!val) return true;
return ['switchTab', 'reLaunch', 'redirectTo', 'navigateTo'].includes(val);
},
},
/** 左侧图标,出现在单元格标题的左侧 */
leftIcon: {
type: [String, Object],
},
/** 和标题同行的说明文字 */
note: {
type: String,
},
/** 说明文字自定义样式 */
noteStyle: {
type: [String, Object],
},
/** 是否显示表单必填星号 */
required: Boolean,
/** 最右侧图标 */
rightIcon: {
type: [String, Object],
},
/** 右侧图标自定义样式 */
rightIconStyle: {
type: [String, Object],
},
/** 标题 */
title: {
type: String,
},
/** 标题自定义样式 */
titleStyle: {
type: [String, Object],
},
/** 点击后跳转链接地址。如果值为空,则表示不需要跳转 */
url: {
type: String,
default: '',
},
/** 右侧内容 */
onClick: {
type: Function,
default: () => ({}),
},
};

View File

@@ -0,0 +1,82 @@
/* eslint-disable */
/**
* 该文件为脚本自动生成文件,请勿随意修改。如需修改请联系 PMC
* */
export interface TdCellProps {
/**
* 右侧内容的对齐方式,默认居中对齐
* @default middle
*/
align?: 'top' | 'middle' | 'bottom';
/**
* 是否显示右侧箭头
* @default false
*/
arrow?: boolean | object;
/**
* 是否显示下边框
* @default true
*/
bordered?: boolean;
/**
* 下方内容描述
*/
description?: string;
/**
* 是否开启点击反馈
*/
hover?: boolean;
/**
* 主图
*/
image?: string;
/**
* 链接跳转类型
* @default navigateTo
*/
jumpType?: 'switchTab' | 'reLaunch' | 'redirectTo' | 'navigateTo';
/**
* 左侧图标,出现在单元格标题的左侧
*/
leftIcon?: string | object;
/**
* 和标题同行的说明文字
*/
note?: string;
/**
* 说明文字自定义样式
*/
noteStyle?: string | object;
/**
* 是否显示表单必填星号
* @default false
*/
required?: boolean;
/**
* 最右侧图标
*/
rightIcon?: string | object;
/**
* 右侧图标自定义样式
*/
rightIconStyle?: string | object;
/**
* 标题
*/
title?: string;
/**
* 标题自定义样式
*/
titleStyle?: string | object;
/**
* 点击后跳转链接地址。如果值为空,则表示不需要跳转
* @default ''
*/
url?: string;
/**
* 右侧内容
*/
onClick?: (e: MouseEvent) => void;
}

View File

@@ -0,0 +1,197 @@
.t-tag {
display: inline-flex;
align-items: center;
border: 2rpx solid transparent;
box-sizing: border-box;
border-radius: var(--td-tag-square-border-radius, 8rpx);
user-select: none;
vertical-align: middle;
}
.t-tag__text {
word-wrap: normal;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.t-tag__icon {
display: flex;
align-items: center;
}
.t-tag__icon:not(:empty) + .t-tag__text:not(:empty) {
margin-left: 8rpx;
}
.t-tag--small {
padding: var(--td-tag-small-padding, 2rpx 10rpx);
font: var(--td-tag-small-font, var(--td-font-body-extraSmall, 20rpx / 32rpx var(--td-font-family, PingFang SC, Microsoft YaHei, Arial Regular)));
}
.t-tag--small .t-icon,
.t-tag--small .t-icon-close {
font-size: var(--td-tag-small-icon-size, 24rpx);
}
.t-tag--medium {
padding: var(--td-tag-medium-padding, 2rpx 14rpx);
font: var(--td-tag-medium-font, var(--td-font-body-small, 24rpx / 40rpx var(--td-font-family, PingFang SC, Microsoft YaHei, Arial Regular)));
}
.t-tag--medium .t-icon,
.t-tag--medium .t-icon-close {
font-size: var(--td-tag-medium-icon-size, 28rpx);
}
.t-tag--large {
padding: var(--td-tag-large-padding, 4rpx 14rpx);
font: var(--td-tag-large-font, var(--td-font-body-medium, 28rpx / 44rpx var(--td-font-family, PingFang SC, Microsoft YaHei, Arial Regular)));
}
.t-tag--large .t-icon,
.t-tag--large .t-icon-close {
font-size: var(--td-tag-large-icon-size, 32rpx);
}
.t-tag--extra-large {
padding: var(--td-tag-extra-large-padding, 16rpx 30rpx);
font: var(--td-tag-extra-large-font, var(--td-font-body-medium, 28rpx / 44rpx var(--td-font-family, PingFang SC, Microsoft YaHei, Arial Regular)));
}
.t-tag--extra-large .t-icon,
.t-tag--extra-large .t-icon-close {
font-size: var(--td-tag-extra-large-icon-size, 32rpx);
}
.t-tag.t-tag--square {
border-radius: var(--td-tag-square-border-radius, 8rpx);
}
.t-tag.t-tag--round {
border-radius: var(--td-tag-round-border-radius, 999px);
}
.t-tag.t-tag--mark {
border-radius: 0 var(--td-tag-mark-border-radius, var(--td-tag-round-border-radius, 999px)) var(--td-tag-mark-border-radius, var(--td-tag-round-border-radius, 999px)) 0;
}
.t-tag--dark.t-tag--default {
color: var(--td-text-color-anti, var(--td-font-white-1, #ffffff));
border-color: var(--td-tag-default-color, var(--td-bg-color-component, var(--td-gray-color-3, #e7e7e7)));
background-color: var(--td-tag-default-color, var(--td-bg-color-component, var(--td-gray-color-3, #e7e7e7)));
}
.t-tag--dark.t-tag--primary {
color: var(--td-text-color-anti, var(--td-font-white-1, #ffffff));
border-color: var(--td-tag-primary-color, var(--td-brand-color, var(--td-primary-color-7, #0052d9)));
background-color: var(--td-tag-primary-color, var(--td-brand-color, var(--td-primary-color-7, #0052d9)));
}
.t-tag--dark.t-tag--success {
color: var(--td-text-color-anti, var(--td-font-white-1, #ffffff));
border-color: var(--td-tag-success-color, var(--td-success-color, var(--td-success-color-5, #2ba471)));
background-color: var(--td-tag-success-color, var(--td-success-color, var(--td-success-color-5, #2ba471)));
}
.t-tag--dark.t-tag--warning {
color: var(--td-text-color-anti, var(--td-font-white-1, #ffffff));
border-color: var(--td-tag-warning-color, var(--td-warning-color, var(--td-warning-color-5, #e37318)));
background-color: var(--td-tag-warning-color, var(--td-warning-color, var(--td-warning-color-5, #e37318)));
}
.t-tag--dark.t-tag--danger {
color: var(--td-text-color-anti, var(--td-font-white-1, #ffffff));
border-color: var(--td-tag-danger-color, var(--td-error-color, var(--td-error-color-6, #d54941)));
background-color: var(--td-tag-danger-color, var(--td-error-color, var(--td-error-color-6, #d54941)));
}
.t-tag--dark.t-tag--default {
color: var(--td-tag-default-font-color, var(--td-text-color-primary, var(--td-font-gray-1, rgba(0, 0, 0, 0.9))));
}
.t-tag--outline.t-tag--default {
color: var(--td-tag-default-color, var(--td-bg-color-component, var(--td-gray-color-3, #e7e7e7)));
border-color: var(--td-tag-default-color, var(--td-bg-color-component, var(--td-gray-color-3, #e7e7e7)));
background-color: var(--td-tag-default-light-color, var(--td-bg-color-secondarycontainer, var(--td-gray-color-1, #f3f3f3)));
}
.t-tag--outline.t-tag--primary {
color: var(--td-tag-primary-color, var(--td-brand-color, var(--td-primary-color-7, #0052d9)));
border-color: var(--td-tag-primary-color, var(--td-brand-color, var(--td-primary-color-7, #0052d9)));
background-color: var(--td-tag-primary-light-color, var(--td-brand-color-light, var(--td-primary-color-1, #f2f3ff)));
}
.t-tag--outline.t-tag--success {
color: var(--td-tag-success-color, var(--td-success-color, var(--td-success-color-5, #2ba471)));
border-color: var(--td-tag-success-color, var(--td-success-color, var(--td-success-color-5, #2ba471)));
background-color: var(--td-tag-success-light-color, var(--td-success-color-1, #e3f9e9));
}
.t-tag--outline.t-tag--warning {
color: var(--td-tag-warning-color, var(--td-warning-color, var(--td-warning-color-5, #e37318)));
border-color: var(--td-tag-warning-color, var(--td-warning-color, var(--td-warning-color-5, #e37318)));
background-color: var(--td-tag-warning-light-color, var(--td-warning-color-1, #fff1e9));
}
.t-tag--outline.t-tag--danger {
color: var(--td-tag-danger-color, var(--td-error-color, var(--td-error-color-6, #d54941)));
border-color: var(--td-tag-danger-color, var(--td-error-color, var(--td-error-color-6, #d54941)));
background-color: var(--td-tag-danger-light-color, var(--td-error-color-1, #fff0ed));
}
.t-tag--outline.t-tag--default {
color: var(--td-tag-default-font-color, var(--td-text-color-primary, var(--td-font-gray-1, rgba(0, 0, 0, 0.9))));
}
.t-tag--outline.t-tag--default {
background-color: var(--td-tag-outline-bg-color, var(--td-bg-color-container, var(--td-font-white-1, #ffffff)));
}
.t-tag--outline.t-tag--primary {
background-color: var(--td-tag-outline-bg-color, var(--td-bg-color-container, var(--td-font-white-1, #ffffff)));
}
.t-tag--outline.t-tag--success {
background-color: var(--td-tag-outline-bg-color, var(--td-bg-color-container, var(--td-font-white-1, #ffffff)));
}
.t-tag--outline.t-tag--warning {
background-color: var(--td-tag-outline-bg-color, var(--td-bg-color-container, var(--td-font-white-1, #ffffff)));
}
.t-tag--outline.t-tag--danger {
background-color: var(--td-tag-outline-bg-color, var(--td-bg-color-container, var(--td-font-white-1, #ffffff)));
}
.t-tag--light.t-tag--default {
color: var(--td-tag-default-color, var(--td-bg-color-component, var(--td-gray-color-3, #e7e7e7)));
border-color: var(--td-tag-default-light-color, var(--td-bg-color-secondarycontainer, var(--td-gray-color-1, #f3f3f3)));
background-color: var(--td-tag-default-light-color, var(--td-bg-color-secondarycontainer, var(--td-gray-color-1, #f3f3f3)));
}
.t-tag--light.t-tag--primary {
color: var(--td-tag-primary-color, var(--td-brand-color, var(--td-primary-color-7, #0052d9)));
border-color: var(--td-tag-primary-light-color, var(--td-brand-color-light, var(--td-primary-color-1, #f2f3ff)));
background-color: var(--td-tag-primary-light-color, var(--td-brand-color-light, var(--td-primary-color-1, #f2f3ff)));
}
.t-tag--light.t-tag--success {
color: var(--td-tag-success-color, var(--td-success-color, var(--td-success-color-5, #2ba471)));
border-color: var(--td-tag-success-light-color, var(--td-success-color-1, #e3f9e9));
background-color: var(--td-tag-success-light-color, var(--td-success-color-1, #e3f9e9));
}
.t-tag--light.t-tag--warning {
color: var(--td-tag-warning-color, var(--td-warning-color, var(--td-warning-color-5, #e37318)));
border-color: var(--td-tag-warning-light-color, var(--td-warning-color-1, #fff1e9));
background-color: var(--td-tag-warning-light-color, var(--td-warning-color-1, #fff1e9));
}
.t-tag--light.t-tag--danger {
color: var(--td-tag-danger-color, var(--td-error-color, var(--td-error-color-6, #d54941)));
border-color: var(--td-tag-danger-light-color, var(--td-error-color-1, #fff0ed));
background-color: var(--td-tag-danger-light-color, var(--td-error-color-1, #fff0ed));
}
.t-tag--light.t-tag--default {
color: var(--td-tag-default-font-color, var(--td-text-color-primary, var(--td-font-gray-1, rgba(0, 0, 0, 0.9))));
}
.t-tag--light-outline.t-tag--default {
color: var(--td-tag-default-color, var(--td-bg-color-component, var(--td-gray-color-3, #e7e7e7)));
border-color: var(--td-tag-default-color, var(--td-bg-color-component, var(--td-gray-color-3, #e7e7e7)));
background-color: var(--td-tag-default-light-color, var(--td-bg-color-secondarycontainer, var(--td-gray-color-1, #f3f3f3)));
}
.t-tag--light-outline.t-tag--primary {
color: var(--td-tag-primary-color, var(--td-brand-color, var(--td-primary-color-7, #0052d9)));
border-color: var(--td-tag-primary-color, var(--td-brand-color, var(--td-primary-color-7, #0052d9)));
background-color: var(--td-tag-primary-light-color, var(--td-brand-color-light, var(--td-primary-color-1, #f2f3ff)));
}
.t-tag--light-outline.t-tag--success {
color: var(--td-tag-success-color, var(--td-success-color, var(--td-success-color-5, #2ba471)));
border-color: var(--td-tag-success-color, var(--td-success-color, var(--td-success-color-5, #2ba471)));
background-color: var(--td-tag-success-light-color, var(--td-success-color-1, #e3f9e9));
}
.t-tag--light-outline.t-tag--warning {
color: var(--td-tag-warning-color, var(--td-warning-color, var(--td-warning-color-5, #e37318)));
border-color: var(--td-tag-warning-color, var(--td-warning-color, var(--td-warning-color-5, #e37318)));
background-color: var(--td-tag-warning-light-color, var(--td-warning-color-1, #fff1e9));
}
.t-tag--light-outline.t-tag--danger {
color: var(--td-tag-danger-color, var(--td-error-color, var(--td-error-color-6, #d54941)));
border-color: var(--td-tag-danger-color, var(--td-error-color, var(--td-error-color-6, #d54941)));
background-color: var(--td-tag-danger-light-color, var(--td-error-color-1, #fff0ed));
}
.t-tag--light-outline.t-tag--default {
color: var(--td-tag-default-font-color, var(--td-text-color-primary, var(--td-font-gray-1, rgba(0, 0, 0, 0.9))));
border-color: var(--td-component-border, var(--td-gray-color-4, #dcdcdc));
}
.t-tag.t-tag--closable.t-tag--disabled {
cursor: not-allowed;
color: var(--td-tag-disabled-color, var(--td-text-color-disabled, var(--td-font-gray-4, rgba(0, 0, 0, 0.26))));
background-color: var(--td-tag-disabled-background-color, var(--td-bg-color-component-disabled, var(--td-gray-color-2, #eeeeee)));
border-color: var(--td-tag-disabled-border-color, var(--td-component-border, var(--td-gray-color-4, #dcdcdc)));
}

View File

@@ -0,0 +1,149 @@
<template>
<view
:style="tools._style([customStyle])"
:class="className + ' ' + tClass"
@click="onClick"
>
<view
:aria-hidden="true"
:class="classPrefix + '__icon'"
>
<block
v-if="_icon"
name="icon"
>
<t-icon
:custom-style="_icon.style || ''"
:t-class="prefix + '-icon'"
:prefix="_icon.prefix"
:name="_icon.name"
:size="_icon.size"
:color="_icon.color"
:aria-hidden="!!_icon.ariaHidden"
:aria-label="_icon.ariaLabel"
:aria-role="_icon.ariaRole"
/>
</block>
<slot name="icon" />
</view>
<view :class="classPrefix + '__text'">
<slot />
<slot name="content" />
<block v-if="tools.isArray(content) && content.length == 2">
{{ dataChecked ? content[0] : content[1] }}
</block>
<block v-else>
{{ content }}
</block>
</view>
<t-icon
v-if="closable"
:class="classPrefix + '__icon-close'"
:t-class="prefix + '-icon'"
name="close"
aria-role="button"
aria-label="关闭"
@click="onClose"
/>
</view>
</template>
<script>
import TIcon from '../icon/icon';
import { uniComponent } from '../common/src/index';
import { prefix } from '../common/config';
import props from './props';
import { classNames, calcIcon, coalesce } from '../common/utils';
import tools from '../common/utils.wxs';
const name = `${prefix}-tag`;
export default uniComponent({
name,
options: {
styleIsolation: 'shared',
},
controlledProps: [{
key: 'checked',
event: 'change',
}],
externalClasses: [
`${prefix}-class`,
],
components: {
TIcon,
},
props: {
...props,
},
data() {
return {
prefix,
classPrefix: name,
className: '',
tools,
_icon: null,
dataChecked: coalesce(this.checked, this.defaultChecked),
};
},
watch: {
size: 'setClass',
disabled: 'setClass',
dataChecked: 'setClass',
icon: {
handler(e) {
this._icon = calcIcon(e);
},
immediate: true,
},
checked: {
handler(value) {
this.dataChecked = value;
},
immediate: true,
},
},
mounted() {
this.setClass();
},
methods: {
setClass() {
const { classPrefix } = this;
const { size, variant, disabled, dataChecked, shape } = this;
const tagClass = [
classPrefix,
`${classPrefix}--checkable`,
disabled ? `${classPrefix}--disabled` : '',
dataChecked ? `${classPrefix}--checked` : '',
`${classPrefix}--${dataChecked ? 'primary' : 'default'}`,
`${classPrefix}--${size}`,
`${classPrefix}--${variant}`,
`${classPrefix}--${shape}`,
];
const className = classNames(tagClass);
this.className = className;
},
onClick() {
if (this.disabled) return;
const { dataChecked } = this;
this._trigger('click');
this._trigger('change', { checked: !dataChecked });
},
onClose(e) {
if (this.disabled) return;
this._trigger('close', e);
},
},
});
</script>
<style scoped>
@import './check-tag.css';
</style>

View File

@@ -0,0 +1,67 @@
/* eslint-disable */
/**
* 该文件为脚本自动生成文件,请勿随意修改。如需修改请联系 PMC
* */
import type { TdCheckTagProps } from './type';
export default {
/** 标签选中的状态默认风格theme=default才有选中态 */
checked: Boolean,
/** 标签选中的状态默认风格theme=default才有选中态非受控属性 */
defaultChecked: Boolean,
/** 标签是否可关闭 */
closable: Boolean,
/** 组件子元素;传入数组时:[选中内容,非选中内容] */
content: {
type: [String, Number, Array],
},
/** 标签禁用态失效标签不能触发事件。默认风格theme=default才有禁用态 */
disabled: Boolean,
/** 标签图标 */
icon: {
type: [String, Object],
},
/** 标签类型,有三种:方形、圆角方形、标记型 */
shape: {
type: String,
default: 'square' as TdCheckTagProps['shape'],
validator(val: TdCheckTagProps['shape']): boolean {
if (!val) return true;
return ['square', 'round', 'mark'].includes(val);
},
},
/** 标签尺寸 */
size: {
type: String,
default: 'medium' as TdCheckTagProps['size'],
validator(val: TdCheckTagProps['size']): boolean {
if (!val) return true;
return ['small', 'medium', 'large'].includes(val);
},
},
/** 标签风格变体 */
variant: {
type: String,
default: 'dark' as TdCheckTagProps['variant'],
validator(val: TdCheckTagProps['variant']): boolean {
if (!val) return true;
return ['dark', 'light', 'outline', 'light-outline'].includes(val);
},
},
/** 状态切换时触发 */
onChange: {
type: Function,
default: () => ({}),
},
/** 点击标签时触发 */
onClick: {
type: Function,
default: () => ({}),
},
/** 如果关闭按钮存在,点击关闭按钮时触发 */
onClose: {
type: Function,
default: () => ({}),
},
};

View File

@@ -0,0 +1,63 @@
/* eslint-disable */
/**
* 该文件为脚本自动生成文件,请勿随意修改。如需修改请联系 PMC
* */
import type { SizeEnum } from '../common/common';
export interface TdCheckTagProps {
/**
* 标签选中的状态默认风格theme=default才有选中态
*/
checked?: boolean;
/**
* 标签选中的状态默认风格theme=default才有选中态非受控属性
*/
defaultChecked?: boolean;
/**
* 标签是否可关闭
* @default false
*/
closable?: boolean;
/**
* 组件子元素;传入数组时:[选中内容,非选中内容]
*/
content?: string | number | string[];
/**
* 标签禁用态失效标签不能触发事件。默认风格theme=default才有禁用态
* @default false
*/
disabled?: boolean;
/**
* 标签图标
*/
icon?: string | object;
/**
* 标签类型,有三种:方形、圆角方形、标记型
* @default square
*/
shape?: 'square' | 'round' | 'mark';
/**
* 标签尺寸
* @default medium
*/
size?: SizeEnum;
/**
* 标签风格变体
* @default dark
*/
variant?: 'dark' | 'light' | 'outline' | 'light-outline';
/**
* 状态切换时触发
*/
onChange?: (context: { checked: boolean }) => void;
/**
* 点击标签时触发
*/
onClick?: () => void;
/**
* 如果关闭按钮存在,点击关闭按钮时触发
*/
onClose?: () => void;
}

View File

@@ -0,0 +1,261 @@
<template>
<view
:class="classPrefix + ' ' + tClass"
:style="tools._style([customStyle])"
>
<slot />
<t-checkbox
v-for="(item, index) in checkboxOptions"
:key="index"
:ref="prefix + '-checkbox-option'"
:class="prefix + '-checkbox-option'"
:data-item="item"
:label="item.label || item.text || ''"
:value="item.value == null ? '' : item.value"
:block="item.block || true"
:check-all="item.checkAll || false"
:checked="item.checked || false"
:content="item.content || ''"
:content-disabled="item.contentDisabled || false"
:icon="item.icon || 'circle'"
:indeterminate="item.indeterminate || false"
:disabled="item.disabled == null ? disabled : item.disabled"
:max-content-row="item.maxContentRow || 5"
:max-label-row="item.maxLabelRow || 3"
:name="item.name || ''"
:borderless="borderless"
:readonly="item.readonly || false"
:placement="item.placement || 'left'"
:relation-key="relationKey"
@change="({checked}) => handleInnerChildChange($event, { item, checked })"
/>
</view>
</template>
<script>
import TCheckbox from '../checkbox/checkbox';
import { uniComponent } from '../common/src/index';
import { prefix } from '../common/config';
import { coalesce } from '../common/utils';
import props from './props';
import tools from '../common/utils.wxs';
import { ParentMixin, RELATION_MAP } from '../common/relation';
const name = `${prefix}-checkbox-group`;
export default uniComponent({
name,
options: {
styleIsolation: 'shared',
},
controlledProps: [
{
key: 'value',
event: 'change',
},
],
externalClasses: [
`${prefix}-class`,
],
inject: {
[RELATION_MAP.FormKey]: {
default: null,
},
},
mixins: [ParentMixin(RELATION_MAP.Checkbox)],
components: {
TCheckbox,
},
props: {
...props,
},
data() {
return {
prefix,
classPrefix: name,
checkboxOptions: [],
tools,
dataValue: coalesce(this.value, this.defaultValue),
};
},
watch: {
value: {
handler(v) {
this.dataValue = v;
},
immediate: true,
deep: true,
},
dataValue: {
handler() {
this.updateChildren();
},
immediate: true,
deep: true,
},
options: {
handler() {
this.initWithOptions();
},
immediate: true,
deep: true,
},
disabled: {
handler(v) {
if (this.options?.length) {
this.initWithOptions();
return;
}
this.getChildren()?.forEach((item) => {
item.setDisabled(v);
});
},
immediate: true,
},
},
created() {
this.$checkAll = null;
},
mounted() {
this.setCheckall();
this.getChildren()?.forEach((item) => {
item.setDisabled(this.disabled);
});
setTimeout(() => {
this.updateChildren();
}, 33);
},
methods: {
getChildren() {
let items = this.children;
if (!items?.length) {
items = this.$refs[`${prefix}-checkbox-option`];
}
return items || [];
},
updateChildren() {
const items = this.getChildren();
const { dataValue } = this;
if (items.length > 0) {
items.forEach((item) => {
if (!item.checkAll) {
item.dataChecked = dataValue?.includes(item.value);
}
});
// 关联可全选项
if (items.some(item => item.checkAll)) {
this.setCheckall();
}
}
},
updateValue({ validChildren, trigger, value, checked, checkAll, item, indeterminate }) {
let { dataValue: newValue } = this;
const { max } = this;
if (validChildren !== false) {
const keySet = new Set(this.getChildren().map(item => item.value));
newValue = newValue.filter(value => keySet.has(value));
}
if (max && checked && newValue.length === max) return;
if (checkAll) {
const items = this.getChildren();
newValue = !checked && indeterminate
? items
.filter(data => !(data.disabled && !newValue.includes(data.value)))
.map(item => item.value)
: items
.filter((data) => {
if (data.disabled) {
return newValue.includes(data.value);
}
return checked && !data.checkAll;
})
.map(data => data.value);
} else if (checked) {
newValue = newValue.concat(value);
} else {
const index = newValue.findIndex(v => v === value);
newValue.splice(index, 1);
}
if (trigger !== 'init') {
this._trigger('change', { value: newValue, context: item });
this.onChange(newValue);
}
},
onChange(value) {
if (this[RELATION_MAP.FormKey]
&& this[RELATION_MAP.FormKey].onValueChange) {
this[RELATION_MAP.FormKey].onValueChange(value);
}
},
initWithOptions() {
const { options, dataValue: value, keys } = this;
if (!options?.length || !Array.isArray(options)) return;
const checkboxOptions = options.map((item) => {
const isLabel = ['number', 'string'].includes(typeof item);
return isLabel
? {
label: `${item}`,
value: item,
checked: value?.includes(item),
}
: {
...item,
label: item[coalesce(keys?.label, 'label')],
value: item[coalesce(keys?.value, 'value')],
checked: value?.includes(item[coalesce(keys?.value, 'value')]),
};
});
this.checkboxOptions = checkboxOptions;
},
handleInnerChildChange(tools, { item, checked }) {
const rect = {};
if (item.checkAll) {
rect.indeterminate = this.$checkAll?.indeterminate;
}
this.updateValue({ ...item, checked, item, ...rect });
},
setCheckall() {
const items = this.getChildren();
if (!this.$checkAll) {
this.$checkAll = items.find(item => item.checkAll);
}
if (!this.$checkAll) return;
const { dataValue } = this;
const valueSet = new Set(dataValue?.filter(val => val !== this.$checkAll.value));
const isCheckall = items.every(item => (item.checkAll ? true : valueSet.has(item.value)));
this.$checkAll.dataChecked = valueSet.size > 0;
this.$checkAll.dataIndeterminate = !isCheckall;
},
},
});
</script>
<style scoped>
</style>

View File

@@ -0,0 +1,58 @@
/* eslint-disable */
/**
* 该文件为脚本自动生成文件,请勿随意修改。如需修改请联系 PMC
* */
import type { TdCheckboxGroupProps } from './type';
export default {
/** 是否开启无边框模式。优先级低于 Checkbox.borderless */
borderless: Boolean,
/** 是否禁用组件。优先级Form.disabled < CheckboxGroup.disabled < Checkbox.disabled */
disabled: {
type: Boolean,
default: undefined,
},
/** 用来定义 value / label / disabled 在 `options` 中对应的字段别名 */
keys: {
type: Object,
},
/** 支持最多选中的数量 */
max: {
type: Number,
default: undefined,
},
/** 统一设置内部复选框 HTML 属性 */
name: {
type: String,
default: '',
},
/** 以配置形式设置子元素。示例1`['北京', '上海']` 示例2: `[{ label: '全选', checkAll: true }, { label: '上海', value: 'shanghai' }]`。checkAll 值为 true 表示当前选项为「全选选项」 */
options: {
type: Array,
default: (): TdCheckboxGroupProps['options'] => [],
},
/** 只读状态 */
readonly: {
type: Boolean,
default: undefined,
},
/** -1 时代表独立,不再寻找 parent用于头条小程序 */
relationKey: {
type: String,
default: '',
},
/** 选中值 */
value: {
type: Array,
},
/** 选中值,非受控属性 */
defaultValue: {
type: Array,
},
/** 值变化时触发。`context` 表示当前点击项内容 */
onChange: {
type: Function,
default: () => ({}),
},
};

View File

@@ -0,0 +1,72 @@
/* eslint-disable */
/**
* 该文件为脚本自动生成文件,请勿随意修改。如需修改请联系 PMC
* */
import type { KeysType } from '../common/common';
export interface TdCheckboxGroupProps<T = CheckboxGroupValue> {
/**
* 是否开启无边框模式。优先级低于 Checkbox.borderless
* @default false
*/
borderless?: boolean;
/**
* 是否禁用组件。优先级Form.disabled < CheckboxGroup.disabled < Checkbox.disabled
*/
disabled?: boolean;
/**
* 用来定义 value / label / disabled 在 `options` 中对应的字段别名
*/
keys?: KeysType;
/**
* 支持最多选中的数量
*/
max?: number;
/**
* 统一设置内部复选框 HTML 属性
* @default ''
*/
name?: string;
/**
* 以配置形式设置子元素。示例1`['北京', '上海']` 示例2: `[{ label: '全选', checkAll: true }, { label: '上海', value: 'shanghai' }]`。checkAll 值为 true 表示当前选项为「全选选项」
* @default []
*/
options?: Array<CheckboxOption>;
/**
* 只读状态
*/
readonly?: boolean;
/**
* -1 时代表独立,不再寻找 parent用于头条小程序
* @default ''
*/
relationKey?: string;
/**
* 选中值
*/
value?: T;
/**
* 选中值,非受控属性
*/
defaultValue?: T;
/**
* 值变化时触发。`context` 表示当前点击项内容
*/
onChange?: (context: {
value: CheckboxGroupValue;
context: { value: boolean | number | string; label: boolean | number | string };
}) => void;
}
export type CheckboxOption = string | number | CheckboxOptionObj;
export interface CheckboxOptionObj {
label?: string;
value?: string | number;
disabled?: boolean;
checkAll?: true;
}
export type CheckboxGroupValue = Array<string | number | boolean>;

View File

@@ -0,0 +1,103 @@
:: BASE_DOC ::
## API
### Checkbox Props
name | type | default | description | required
-- | -- | -- | -- | --
custom-style | Object | - | CSS(Cascading Style Sheets) | N
block | Boolean | true | \- | N
borderless | Boolean | undefined | \- | N
check-all | Boolean | false | \- | N
checked | Boolean | - | `v-model:checked` is supported | N
default-checked | Boolean | - | uncontrolled property | N
content | String | - | \- | N
content-disabled | Boolean | - | \- | N
disabled | Boolean | undefined | \- | N
icon | String / Array | 'circle' | Typescript`'circle' \| 'line' \| 'rectangle' \| string[]` | N
indeterminate | Boolean | false | \- | N
label | String | - | \- | N
max-content-row | Number | 5 | \- | N
max-label-row | Number | 3 | \- | N
name | String | - | \- | N
placement | String | left | options: left/right | N
readonly | Boolean | undefined | \- | N
relation-key | String | - | \- | N
value | String / Number / Boolean | - | value of checkbox。Typescript`string \| number \| boolean` | N
### Checkbox Events
name | params | description
-- | -- | --
change | `(context: { checked: boolean, context: { value: boolean\|number\|string, label: boolean\|number\|string }})` | \-
### Checkbox Slots
name | Description
-- | --
\- | \-
content | \-
label | \-
### Checkbox External Classes
className | Description
-- | --
t-class | \-
t-class-border | \-
t-class-content | \-
t-class-icon | \-
t-class-label | \-
### CheckboxGroup Props
name | type | default | description | required
-- | -- | -- | -- | --
custom-style | Object | - | CSS(Cascading Style Sheets) | N
borderless | Boolean | false | \- | N
disabled | Boolean | undefined | \- | N
keys | Object | - | Typescript`KeysType`。[see more ts definition](https://github.com/Tencent/tdesign-miniprogram/tree/develop/packages/uniapp-components/common/common.ts) | N
max | Number | undefined | \- | N
name | String | - | \- | N
options | Array | [] | Typescript`Array<CheckboxOption>` `type CheckboxOption = string \| number \| CheckboxOptionObj` `interface CheckboxOptionObj { label?: string; value?: string \| number; disabled?: boolean; checkAll?: true }`。[see more ts definition](https://github.com/Tencent/tdesign-miniprogram/tree/develop/packages/uniapp-components/checkbox-group/type.ts) | N
readonly | Boolean | undefined | \- | N
relation-key | String | - | \- | N
value | Array | - | `v-model:value` is supported。Typescript`T` `type CheckboxGroupValue = Array<string \| number \| boolean>`。[see more ts definition](https://github.com/Tencent/tdesign-miniprogram/tree/develop/packages/uniapp-components/checkbox-group/type.ts) | N
default-value | Array | - | uncontrolled property。Typescript`T` `type CheckboxGroupValue = Array<string \| number \| boolean>`。[see more ts definition](https://github.com/Tencent/tdesign-miniprogram/tree/develop/packages/uniapp-components/checkbox-group/type.ts) | N
### CheckboxGroup Events
name | params | description
-- | -- | --
change | `(context: { value: CheckboxGroupValue, context: { value: boolean\|number\|string, label: boolean\|number\|string }})` | \-
### CheckboxGroup Slots
name | Description
-- | --
\- | \-
### CSS Variables
The component provides the following CSS variables, which can be used to customize styles.
Name | Default Value | Description
-- | -- | --
--td-checkbox-bg-color | @bg-color-container | -
--td-checkbox-border-color | @component-stroke | -
--td-checkbox-description-color | @text-color-secondary | -
--td-checkbox-description-disabled-color | @text-color-disabled | -
--td-checkbox-description-font | @font-body-medium | -
--td-checkbox-icon-checked-color | @brand-color | -
--td-checkbox-icon-color | @component-border | -
--td-checkbox-icon-disabled-bg-color | @bg-color-component-disabled | -
--td-checkbox-icon-disabled-color | @brand-color-disabled | -
--td-checkbox-icon-size | 48rpx | -
--td-checkbox-tag-active-bg-color | @brand-color-light | -
--td-checkbox-tag-active-color | @brand-color | -
--td-checkbox-title-color | @text-color-primary | -
--td-checkbox-title-disabled-color | @text-color-disabled | -
--td-checkbox-title-font | @font-body-large | -
--td-checkbox-title-line-height | 48rpx | -
--td-checkbox-vertical-padding | @spacer-2 | -

View File

@@ -0,0 +1,158 @@
---
title: Checkbox 多选框
description: 用于预设的一组选项中执行多项选择,并呈现选择结果。
spline: form
isComponent: true
---
## 引入
可在 `main.ts` 或在需要使用的页面或组件中引入。
```js
import TCheckbox from '@tdesign/uniapp/checkbox/checkbox.vue';
import TCheckboxGroup from '@tdesign/uniapp/checkbox-group/checkbox-group.vue';
```
### 组件类型
纵向多选框
{{ base }}
横向多选框
{{ horizontal }}
带全选多选框
{{ all }}
### 组件状态
多选框状态
{{ status }}
### 组件样式
勾选样式
{{ type }}
勾选显示位置
{{ right }}
非通栏多选样式
{{ card }}
### 组件规格
多选框尺寸规格
{{ special }}
## API
### Checkbox Props
名称 | 类型 | 默认值 | 描述 | 必传
-- | -- | -- | -- | --
custom-style | Object | - | 自定义样式 | N
block | Boolean | true | 是否为块级元素 | N
borderless | Boolean | undefined | 是否开启无边框模式 | N
check-all | Boolean | false | 用于标识是否为「全选选项」。单独使用无效,需在 CheckboxGroup 中使用 | N
checked | Boolean | - | 是否选中。支持语法糖 `v-model:checked` | N
default-checked | Boolean | - | 是否选中。非受控属性 | N
content | String | - | 多选框内容 | N
content-disabled | Boolean | - | 是否禁用组件内容content触发选中 | N
disabled | Boolean | undefined | 是否禁用组件。如果父组件存在 CheckboxGroup默认值由 CheckboxGroup.disabled 控制。优先级Checkbox.disabled > CheckboxGroup.disabled > Form.disabled | N
icon | String / Array | 'circle' | 自定义选中图标和非选中图标。使用 Array 时表示:`[选中态图标,非选中态图标,半选中态图标]`。使用 String 时,值为 circle 表示填充圆形图标、值为 line 表示描边型图标、值为 rectangle 表示填充矩形图标。TS 类型:`'circle' \| 'line' \| 'rectangle' \| string[]` | N
indeterminate | Boolean | false | 是否为半选 | N
label | String | - | 主文案 | N
max-content-row | Number | 5 | 内容最大行数限制 | N
max-label-row | Number | 3 | 主文案最大行数限制 | N
name | String | - | HTML 元素原生属性 | N
placement | String | left | 多选框和内容相对位置。可选项left/right | N
readonly | Boolean | undefined | 只读状态 | N
relation-key | String | - | -1 时代表独立,不再寻找 parent用于头条小程序 | N
value | String / Number / Boolean | - | 多选框的值。TS 类型:`string \| number \| boolean` | N
### Checkbox Events
名称 | 参数 | 描述
-- | -- | --
change | `(context: { checked: boolean, context: { value: boolean\|number\|string, label: boolean\|number\|string }})` | 值变化时触发。`context` 表示当前点击项内容
### Checkbox Slots
名称 | 描述
-- | --
\- | 默认插槽,主文案
content | 自定义 `content` 显示内容
label | 自定义 `label` 显示内容
### Checkbox External Classes
类名 | 描述
-- | --
t-class | 根节点样式类
t-class-border | 边框样式类
t-class-content | 内容样式类
t-class-icon | 图标样式类
t-class-label | 标签样式类
### CheckboxGroup Props
名称 | 类型 | 默认值 | 描述 | 必传
-- | -- | -- | -- | --
custom-style | Object | - | 自定义样式 | N
borderless | Boolean | false | 是否开启无边框模式。优先级低于 Checkbox.borderless | N
disabled | Boolean | undefined | 是否禁用组件。优先级Form.disabled < CheckboxGroup.disabled < Checkbox.disabled | N
keys | Object | - | 用来定义 value / label / disabled `options` 中对应的字段别名TS 类型`KeysType`[通用类型定义](https://github.com/Tencent/tdesign-miniprogram/tree/develop/packages/uniapp-components/common/common.ts) | N
max | Number | undefined | 支持最多选中的数量 | N
name | String | - | 统一设置内部复选框 HTML 属性 | N
options | Array | [] | 以配置形式设置子元素示例1`['北京', '上海']` 示例2: `[{ label: '全选', checkAll: true }, { label: '上海', value: 'shanghai' }]`checkAll 值为 true 表示当前选项为全选选项」。TS 类型`Array<CheckboxOption>` `type CheckboxOption = string \| number \| CheckboxOptionObj` `interface CheckboxOptionObj { label?: string; value?: string \| number; disabled?: boolean; checkAll?: true }`[详细类型定义](https://github.com/Tencent/tdesign-miniprogram/tree/develop/packages/uniapp-components/checkbox-group/type.ts) | N
readonly | Boolean | undefined | 只读状态 | N
relation-key | String | - | -1 时代表独立不再寻找 parent用于头条小程序 | N
value | Array | - | 选中值支持语法糖 `v-model:value`TS 类型`T` `type CheckboxGroupValue = Array<string \| number \| boolean>`[详细类型定义](https://github.com/Tencent/tdesign-miniprogram/tree/develop/packages/uniapp-components/checkbox-group/type.ts) | N
default-value | Array | - | 选中值非受控属性TS 类型`T` `type CheckboxGroupValue = Array<string \| number \| boolean>`[详细类型定义](https://github.com/Tencent/tdesign-miniprogram/tree/develop/packages/uniapp-components/checkbox-group/type.ts) | N
### CheckboxGroup Events
名称 | 参数 | 描述
-- | -- | --
change | `(context: { value: CheckboxGroupValue, context: { value: boolean\|number\|string, label: boolean\|number\|string }})` | 值变化时触发`context` 表示当前点击项内容
### CheckboxGroup Slots
名称 | 描述
-- | --
\- | 默认插槽多选框组内容
### CSS Variables
组件提供了下列 CSS 变量可用于自定义样式
名称 | 默认值 | 描述
-- | -- | --
--td-checkbox-bg-color | @bg-color-container | -
--td-checkbox-border-color | @component-stroke | -
--td-checkbox-description-color | @text-color-secondary | -
--td-checkbox-description-disabled-color | @text-color-disabled | -
--td-checkbox-description-font | @font-body-medium | -
--td-checkbox-icon-checked-color | @brand-color | -
--td-checkbox-icon-color | @component-border | -
--td-checkbox-icon-disabled-bg-color | @bg-color-component-disabled | -
--td-checkbox-icon-disabled-color | @brand-color-disabled | -
--td-checkbox-icon-size | 48rpx | -
--td-checkbox-tag-active-bg-color | @brand-color-light | -
--td-checkbox-tag-active-color | @brand-color | -
--td-checkbox-title-color | @text-color-primary | -
--td-checkbox-title-disabled-color | @text-color-disabled | -
--td-checkbox-title-font | @font-body-large | -
--td-checkbox-title-line-height | 48rpx | -
--td-checkbox-vertical-padding | @spacer-2 | -

View File

@@ -0,0 +1,174 @@
.t-checkbox {
display: inline-flex;
vertical-align: middle;
position: relative;
background: var(--td-checkbox-bg-color, var(--td-bg-color-container, var(--td-font-white-1, #ffffff)));
}
.t-checkbox:focus {
outline: 0;
}
.t-checkbox--block {
display: flex;
padding: var(--td-checkbox-vertical-padding, var(--td-spacer-2, 32rpx));
}
.t-checkbox--right {
flex-direction: row-reverse;
}
.t-checkbox .limit-title-row {
display: -webkit-box;
-webkit-box-orient: vertical;
overflow: hidden;
}
.t-checkbox .image-center {
position: absolute;
top: 50%;
transform: translateY(-50%);
}
.t-checkbox__icon-left {
margin-right: 20rpx;
width: 40rpx;
}
.t-checkbox__icon-right {
right: 0px;
display: contents;
position: absolute;
top: 50%;
transform: translateY(-50%);
}
.t-checkbox__icon-image {
width: var(--td-checkbox-icon-size, 48rpx);
height: var(--td-checkbox-icon-size, 48rpx);
vertical-align: top;
}
.t-checkbox__icon {
position: relative;
display: block;
width: var(--td-checkbox-icon-size, 48rpx);
height: var(--td-checkbox-icon-size, 48rpx);
color: var(--td-checkbox-icon-color, var(--td-component-border, var(--td-gray-color-4, #dcdcdc)));
font-size: var(--td-checkbox-icon-size, 48rpx);
margin-top: calc((var(--td-checkbox-title-line-height, 48rpx) - var(--td-checkbox-icon-size, 48rpx)) / 2);
}
.t-checkbox__icon:empty {
display: none;
}
.t-checkbox__icon--checked {
color: var(--td-checkbox-icon-checked-color, var(--td-brand-color, var(--td-primary-color-7, #0052d9)));
}
.t-checkbox__icon--disabled {
cursor: not-allowed;
color: var(--td-checkbox-icon-disabled-color, var(--td-brand-color-disabled, var(--td-primary-color-3, #b5c7ff)));
}
.t-checkbox__icon--left {
margin-right: 16rpx;
}
.t-checkbox__icon-circle {
width: calc((var(--td-checkbox-icon-size, 48rpx) - 4rpx) * 2);
height: calc((var(--td-checkbox-icon-size, 48rpx) - 4rpx) * 2);
border: calc(4rpx * 2) solid var(--td-checkbox-icon-color, var(--td-component-border, var(--td-gray-color-4, #dcdcdc)));
border-radius: 50%;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%) scale(0.5);
box-sizing: border-box;
}
.t-checkbox__icon-circle--disabled {
background: var(--td-checkbox-icon-disabled-bg-color, var(--td-bg-color-component-disabled, var(--td-gray-color-2, #eeeeee)));
}
.t-checkbox__icon-rectangle {
width: calc((var(--td-checkbox-icon-size, 48rpx) - 4rpx * 2) * 2);
height: calc((var(--td-checkbox-icon-size, 48rpx) - 4rpx * 2) * 2);
border: calc(4rpx * 2) solid var(--td-checkbox-icon-color, var(--td-component-border, var(--td-gray-color-4, #dcdcdc)));
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%) scale(0.5);
box-sizing: border-box;
}
.t-checkbox__icon-rectangle--disabled {
background: var(--td-checkbox-icon-disabled-bg-color, var(--td-bg-color-component-disabled, var(--td-gray-color-2, #eeeeee)));
}
.t-checkbox__icon-line::before,
.t-checkbox__icon-line::after {
content: '';
display: block;
position: absolute;
width: 5rpx;
border-radius: 2rpx;
background: var(--td-checkbox-icon-checked-color, var(--td-brand-color, var(--td-primary-color-7, #0052d9)));
transform-origin: top center;
}
.t-checkbox__icon-line::before {
height: 16rpx;
left: 8rpx;
top: 22rpx;
transform: rotate(-45deg);
}
.t-checkbox__icon-line::after {
height: 26rpx;
right: 8rpx;
top: 14rpx;
transform: rotate(45deg);
}
.t-checkbox__icon-line--disabled::before,
.t-checkbox__icon-line--disabled::after {
background: var(--td-checkbox-icon-disabled-color, var(--td-brand-color-disabled, var(--td-primary-color-3, #b5c7ff)));
}
.t-checkbox__content {
flex: 1;
}
.t-checkbox__title {
color: var(--td-checkbox-title-color, var(--td-text-color-primary, var(--td-font-gray-1, rgba(0, 0, 0, 0.9))));
font: var(--td-checkbox-title-font, var(--td-font-body-large, 32rpx / 48rpx var(--td-font-family, PingFang SC, Microsoft YaHei, Arial Regular)));
line-height: var(--td-checkbox-title-line-height, 48rpx);
display: -webkit-box;
-webkit-box-orient: vertical;
overflow: hidden;
}
.t-checkbox__title--disabled {
color: var(--td-checkbox-title-disabled-color, var(--td-text-color-disabled, var(--td-font-gray-4, rgba(0, 0, 0, 0.26))));
}
.t-checkbox__description {
color: var(--td-checkbox-description-color, var(--td-text-color-secondary, var(--td-font-gray-2, rgba(0, 0, 0, 0.6))));
display: -webkit-box;
-webkit-box-orient: vertical;
overflow: hidden;
font: var(--td-checkbox-description-font, var(--td-font-body-medium, 28rpx / 44rpx var(--td-font-family, PingFang SC, Microsoft YaHei, Arial Regular)));
}
.t-checkbox__description--disabled {
color: var(--td-checkbox-description-disabled-color, var(--td-text-color-disabled, var(--td-font-gray-4, rgba(0, 0, 0, 0.26))));
}
.t-checkbox__title + .t-checkbox__description:not(:empty) {
margin-top: 8rpx;
}
.t-checkbox__border {
position: absolute;
bottom: 0;
left: 96rpx;
right: 0;
height: 1px;
background: var(--td-checkbox-border-color, var(--td-component-stroke, var(--td-gray-color-3, #e7e7e7)));
transform: scaleY(0.5);
}
.t-checkbox__border--right {
left: 32rpx;
}
.t-checkbox--tag {
font-size: 28rpx;
padding-top: 16rpx;
padding-bottom: 16rpx;
text-align: center;
background-color: var(--td-bg-color-secondarycontainer, var(--td-gray-color-1, #f3f3f3));
border-radius: 12rpx;
}
.t-checkbox--tag.t-checkbox--checked {
color: var(--td-checkbox-tag-active-color, var(--td-brand-color, var(--td-primary-color-7, #0052d9)));
background-color: var(--td-checkbox-tag-active-bg-color, var(--td-brand-color-light, var(--td-primary-color-1, #f2f3ff)));
}
.t-checkbox--tag .t-checkbox__title--checked {
color: var(--td-checkbox-tag-active-color, var(--td-brand-color, var(--td-primary-color-7, #0052d9)));
}
.t-checkbox--tag .t-checkbox__content {
margin-right: 0;
}

View File

@@ -0,0 +1,238 @@
<template>
<view
:id="tId"
:style="tools._style([customStyle])"
:class="tools.cls(classPrefix, [placement, theme, ['checked', dataChecked], ['block', block], ['disabled', _disabled]]) + ' ' + tClass"
aria-role="checkbox"
:aria-checked="dataChecked ? (dataIndeterminate ? 'mixed' : true) : false"
:aria-disabled="_disabled ? true : false"
@click.stop="handleTap"
>
<view
v-if="theme == 'default'"
:class="tools.cls(classPrefix + '__icon', [placement, ['checked', dataChecked], ['disabled', _disabled]]) + ' ' + tClassIcon"
>
<slot
v-if="icon === 'slot'"
name="icon"
/>
<view
v-else-if="tools.isArray(icon)"
:class="classPrefix + '__icon'"
>
<image
:src="dataChecked ? (dataIndeterminate && icon[2] ? icon[2] : icon[0]) : icon[1]"
:class="classPrefix + '__icon-image'"
/>
</view>
<block v-else>
<t-icon
v-if="dataChecked && (icon == 'circle' || icon == 'rectangle')"
:name="dataIndeterminate ? 'minus-' + icon + '-filled' : 'check-' + icon + '-filled'"
:class="tools.cls(classPrefix + '__icon-wrapper', [])"
/>
<t-icon
v-if="dataChecked && icon == 'line'"
:name="dataIndeterminate ? 'minus-' + icon + '-filled' : 'check'"
:class="tools.cls(classPrefix + '__icon-wrapper', [])"
/>
<view
v-else-if="!dataChecked && (icon == 'circle' || icon == 'rectangle')"
:class="tools.cls(classPrefix + '__icon-' + icon, [['disabled', _disabled]])"
/>
<view
v-if="!dataChecked && icon == 'line'"
class="placeholder"
/>
</block>
</view>
<view
:class="classPrefix + '__content'"
data-target="text"
@click.stop="handleTap"
>
<view
:class="
tools.cls(classPrefix + '__title', [
['disabled', _disabled],
['checked', dataChecked]
]) +
' ' +
tClassLabel
"
:style="'-webkit-line-clamp:' + maxLabelRow"
>
<block v-if="label">
{{ label }}
</block>
<slot />
<slot name="label" />
</view>
<view
:class="tools.cls(classPrefix + '__description', [['disabled', _disabled]]) + ' ' + tClassContent"
:style="'-webkit-line-clamp:' + maxContentRow"
>
<block v-if="content">
{{ content }}
</block>
<slot name="content" />
</view>
</view>
<view
v-if="theme == 'default' && !dataBorderless"
:class="tools.cls(classPrefix + '__border', [placement]) + ' ' + tClassBorder"
/>
</view>
</template>
<script>
import TIcon from '../icon/icon';
import { uniComponent } from '../common/src/index';
import { coalesce } from '../common/utils';
import { prefix, ISOLATED_RELATION_KEY } from '../common/config';
import props from './props';
import tools from '../common/utils.wxs';
import { ChildrenMixin, RELATION_MAP } from '../common/relation';
const name = `${prefix}-checkbox`;
export default uniComponent({
name,
options: {
styleIsolation: 'shared',
},
controlledProps: [
{
key: 'checked',
event: 'change',
},
],
externalClasses: [
`${prefix}-class`,
`${prefix}-class-label`,
`${prefix}-class-icon`,
`${prefix}-class-content`,
`${prefix}-class-border`,
],
mixins: [ChildrenMixin(RELATION_MAP.Checkbox)],
components: {
TIcon,
},
props: {
...props,
theme: {
type: String,
default: 'default',
},
tId: {
type: String,
},
},
data() {
return {
prefix,
classPrefix: name,
_disabled: false,
tools,
dataBorderless: this.borderless,
dataIndeterminate: this.indeterminate,
dataChecked: coalesce(this.checked, this.defaultChecked),
};
},
computed: {
isIsolated() {
return this.relationKey === ISOLATED_RELATION_KEY;
},
},
watch: {
borderless: {
handler(v) {
this.dataBorderless = v;
},
immediate: true,
},
indeterminate: {
handler(v) {
this.dataIndeterminate = v;
},
immediate: true,
},
checked: {
handler(v) {
this.dataChecked = v;
},
immediate: true,
},
disabled: {
handler(v) {
this._disabled = v;
},
immediate: true,
},
},
mounted() {
},
methods: {
innerAfterLinked() {
if (this.isIsolated) return;
const parent = this[RELATION_MAP.Checkbox];
const { value, disabled, borderless } = parent;
const { dataValue, checked, checkAll, item, dataIndeterminate } = this;
const valueSet = new Set(value);
const checkedFromParent = valueSet.has(this.value);
const data = {
_disabled: this.disabled == null ? disabled : this.disabled,
};
data.dataBorderless = !!(coalesce(this.borderless, borderless));
data.dataChecked = this.dataChecked || checkedFromParent;
if (this.dataChecked) {
parent.updateValue({
value: dataValue,
checked,
checkAll,
item,
indeterminate: dataIndeterminate,
trigger: 'init',
});
}
if (this.checkAll) {
data.dataChecked = valueSet.size > 0;
// data.indeterminate =
}
Object.keys(data).forEach((key) => {
this[key] = data[key];
});
},
handleTap(e) {
const { _disabled, readonly, contentDisabled } = this;
const { target } = e.currentTarget.dataset;
if (_disabled || readonly || (target === 'text' && contentDisabled)) return;
const { value, label, checkAll, dataIndeterminate } = this;
const checked = !this.dataChecked;
const parent = this[RELATION_MAP.Checkbox];
if (parent && !this.isIsolated) {
parent.updateValue({ value, checkAll, indeterminate: dataIndeterminate, checked, item: { label, value, checked } });
} else {
this._trigger('change', { context: { value, label }, checked });
}
},
setDisabled(disabled) {
this._disabled = this.disabled || disabled;
},
},
});
</script>
<style scoped>
@import './checkbox.css';
</style>

View File

@@ -0,0 +1,90 @@
/* eslint-disable */
/**
* 该文件为脚本自动生成文件,请勿随意修改。如需修改请联系 PMC
* */
import type { TdCheckboxProps } from './type';
export default {
/** 是否为块级元素 */
block: {
type: Boolean,
default: true,
},
/** 是否开启无边框模式 */
borderless: {
type: Boolean,
default: undefined,
},
/** 用于标识是否为「全选选项」。单独使用无效,需在 CheckboxGroup 中使用 */
checkAll: Boolean,
/** 是否选中 */
checked: Boolean,
/** 是否选中,非受控属性 */
defaultChecked: Boolean,
/** 多选框内容 */
content: {
type: String,
},
/** 是否禁用组件内容content触发选中 */
contentDisabled: Boolean,
/** 是否禁用组件。如果父组件存在 CheckboxGroup默认值由 CheckboxGroup.disabled 控制。优先级Checkbox.disabled > CheckboxGroup.disabled > Form.disabled */
disabled: {
type: Boolean,
default: undefined,
},
/** 自定义选中图标和非选中图标。使用 Array 时表示:`[选中态图标,非选中态图标,半选中态图标]`。使用 String 时,值为 circle 表示填充圆形图标、值为 line 表示描边型图标、值为 rectangle 表示填充矩形图标 */
icon: {
type: [String, Array],
default: 'circle' as TdCheckboxProps['icon'],
},
/** 是否为半选 */
indeterminate: Boolean,
/** 主文案 */
label: {
type: String,
},
/** 内容最大行数限制 */
maxContentRow: {
type: Number,
default: 5,
},
/** 主文案最大行数限制 */
maxLabelRow: {
type: Number,
default: 3,
},
/** HTML 元素原生属性 */
name: {
type: String,
default: '',
},
/** 多选框和内容相对位置 */
placement: {
type: String,
default: 'left' as TdCheckboxProps['placement'],
validator(val: TdCheckboxProps['placement']): boolean {
if (!val) return true;
return ['left', 'right'].includes(val);
},
},
/** 只读状态 */
readonly: {
type: Boolean,
default: undefined,
},
/** -1 时代表独立,不再寻找 parent用于头条小程序 */
relationKey: {
type: String,
default: '',
},
/** 多选框的值 */
value: {
type: [String, Number, Boolean],
},
/** 值变化时触发。`context` 表示当前点击项内容 */
onChange: {
type: Function,
default: () => ({}),
},
};

View File

@@ -0,0 +1,96 @@
/* eslint-disable */
/**
* 该文件为脚本自动生成文件,请勿随意修改。如需修改请联系 PMC
* */
export interface TdCheckboxProps {
/**
* 是否为块级元素
* @default true
*/
block?: boolean;
/**
* 是否开启无边框模式
*/
borderless?: boolean;
/**
* 用于标识是否为「全选选项」。单独使用无效,需在 CheckboxGroup 中使用
* @default false
*/
checkAll?: boolean;
/**
* 是否选中
*/
checked?: boolean;
/**
* 是否选中,非受控属性
*/
defaultChecked?: boolean;
/**
* 多选框内容
*/
content?: string;
/**
* 是否禁用组件内容content触发选中
*/
contentDisabled?: boolean;
/**
* 是否禁用组件。如果父组件存在 CheckboxGroup默认值由 CheckboxGroup.disabled 控制。优先级Checkbox.disabled > CheckboxGroup.disabled > Form.disabled
*/
disabled?: boolean;
/**
* 自定义选中图标和非选中图标。使用 Array 时表示:`[选中态图标,非选中态图标,半选中态图标]`。使用 String 时,值为 circle 表示填充圆形图标、值为 line 表示描边型图标、值为 rectangle 表示填充矩形图标
* @default 'circle'
*/
icon?: 'circle' | 'line' | 'rectangle' | string[];
/**
* 是否为半选
* @default false
*/
indeterminate?: boolean;
/**
* 主文案
*/
label?: string;
/**
* 内容最大行数限制
* @default 5
*/
maxContentRow?: number;
/**
* 主文案最大行数限制
* @default 3
*/
maxLabelRow?: number;
/**
* HTML 元素原生属性
* @default ''
*/
name?: string;
/**
* 多选框和内容相对位置
* @default left
*/
placement?: 'left' | 'right';
/**
* 只读状态
*/
readonly?: boolean;
/**
* -1 时代表独立,不再寻找 parent用于头条小程序
* @default ''
*/
relationKey?: string;
/**
* 多选框的值
*/
value?: string | number | boolean;
/**
* 值变化时触发。`context` 表示当前点击项内容
*/
onChange?: (context: {
checked: boolean;
context: { value: boolean | number | string; label: boolean | number | string };
}) => void;
}

View File

@@ -0,0 +1,31 @@
:: BASE_DOC ::
## API
### Col Props
name | type | default | description | required
-- | -- | -- | -- | --
custom-style | Object | - | CSS(Cascading Style Sheets) | N
offset | String / Number | - | \- | N
span | String / Number | - | \- | N
### Col Slots
name | Description
-- | --
\- | \-
### Row Props
name | type | default | description | required
-- | -- | -- | -- | --
custom-style | Object | - | CSS(Cascading Style Sheets) | N
gutter | String / Number | - | \- | N
### Row Slots
name | Description
-- | --
\- | \-

View File

@@ -0,0 +1,60 @@
---
title: Layout 布局
description: 以规则的网格阵列来指导和规范页面中的版面布局以及信息分布,提高界面内布局的一致性,节约成本。
spline: base
isComponent: true
---
## 引入
可在 `main.ts` 或在需要使用的页面或组件中引入。
```js
import TRow from '@tdesign/uniapp/row/row.vue';
import TCol from '@tdesign/uniapp/col/col.vue';
```
### 组件类型
基础
{{ base }}
增加间距
{{ offset }}
## API
### Col Props
名称 | 类型 | 默认值 | 描述 | 必传
-- | -- | -- | -- | --
custom-style | Object | - | 自定义样式 | N
offset | String / Number | - | 列的偏移量默认单位px | N
span | String / Number | - | 列的宽度默认单位px | N
### Col Slots
名称 | 描述
-- | --
\- | 默认插槽,列内容
### Row Props
名称 | 类型 | 默认值 | 描述 | 必传
-- | -- | -- | -- | --
custom-style | Object | - | 自定义样式 | N
gutter | String / Number | - | 列之间的间距默认单位px | N
### Row Slots
名称 | 描述
-- | --
\- | 默认插槽,行内容

View File

@@ -0,0 +1,149 @@
.t-col {
display: block;
box-sizing: border-box;
min-height: 1px;
}
.t-col--1 {
width: 4.16666667%;
}
.t-col--offset-1 {
margin-left: 4.16666667%;
}
.t-col--2 {
width: 8.33333333%;
}
.t-col--offset-2 {
margin-left: 8.33333333%;
}
.t-col--3 {
width: 12.5%;
}
.t-col--offset-3 {
margin-left: 12.5%;
}
.t-col--4 {
width: 16.66666667%;
}
.t-col--offset-4 {
margin-left: 16.66666667%;
}
.t-col--5 {
width: 20.83333333%;
}
.t-col--offset-5 {
margin-left: 20.83333333%;
}
.t-col--6 {
width: 25%;
}
.t-col--offset-6 {
margin-left: 25%;
}
.t-col--7 {
width: 29.16666667%;
}
.t-col--offset-7 {
margin-left: 29.16666667%;
}
.t-col--8 {
width: 33.33333333%;
}
.t-col--offset-8 {
margin-left: 33.33333333%;
}
.t-col--9 {
width: 37.5%;
}
.t-col--offset-9 {
margin-left: 37.5%;
}
.t-col--10 {
width: 41.66666667%;
}
.t-col--offset-10 {
margin-left: 41.66666667%;
}
.t-col--11 {
width: 45.83333333%;
}
.t-col--offset-11 {
margin-left: 45.83333333%;
}
.t-col--12 {
width: 50%;
}
.t-col--offset-12 {
margin-left: 50%;
}
.t-col--13 {
width: 54.16666667%;
}
.t-col--offset-13 {
margin-left: 54.16666667%;
}
.t-col--14 {
width: 58.33333333%;
}
.t-col--offset-14 {
margin-left: 58.33333333%;
}
.t-col--15 {
width: 62.5%;
}
.t-col--offset-15 {
margin-left: 62.5%;
}
.t-col--16 {
width: 66.66666667%;
}
.t-col--offset-16 {
margin-left: 66.66666667%;
}
.t-col--17 {
width: 70.83333333%;
}
.t-col--offset-17 {
margin-left: 70.83333333%;
}
.t-col--18 {
width: 75%;
}
.t-col--offset-18 {
margin-left: 75%;
}
.t-col--19 {
width: 79.16666667%;
}
.t-col--offset-19 {
margin-left: 79.16666667%;
}
.t-col--20 {
width: 83.33333333%;
}
.t-col--offset-20 {
margin-left: 83.33333333%;
}
.t-col--21 {
width: 87.5%;
}
.t-col--offset-21 {
margin-left: 87.5%;
}
.t-col--22 {
width: 91.66666667%;
}
.t-col--offset-22 {
margin-left: 91.66666667%;
}
.t-col--23 {
width: 95.83333333%;
}
.t-col--offset-23 {
margin-left: 95.83333333%;
}
.t-col--24 {
width: 100%;
}
.t-col--offset-24 {
margin-left: 100%;
}

View File

@@ -0,0 +1,58 @@
<template>
<view
:class="[
tClass,
tools.cls(classPrefix, [span]),
(offset ? classPrefix + '--offset-' + offset : '')
]"
:style="getColStyles(gutter, customStyle)"
>
<slot />
</view>
</template>
<script>
import { uniComponent } from '../common/src/index';
import { prefix } from '../common/config';
import props from './props';
import tools from '../common/utils.wxs';
import { getColStyles } from './computed.js';
import { ChildrenMixin, RELATION_MAP } from '../common/relation';
const name = `${prefix}-col`;
export default uniComponent({
name,
options: {
styleIsolation: 'shared',
virtualHost: true,
},
externalClasses: [`${prefix}-class`],
mixins: [ChildrenMixin(RELATION_MAP.Col)],
props: {
...props,
},
data() {
return {
prefix,
classPrefix: name,
tools,
gutter: '',
};
},
methods: {
getColStyles,
},
});
</script>
<style scoped>
@import './col.css';
</style>
<style scoped>
.t-col {
/* 适配 qq 小程序等 */
display: unset;
float: left;
}
</style>

View File

@@ -0,0 +1,14 @@
import utils from '../common/utils.wxs';
export function getColStyles(gutter, customStyle) {
let _style = '';
if (gutter) {
_style = utils._style({
'padding-right': utils.addUnit(gutter / 2),
'padding-left': utils.addUnit(gutter / 2),
});
}
return utils._style([customStyle]) + _style;
}

View File

@@ -0,0 +1,16 @@
/* eslint-disable */
/**
* 该文件为脚本自动生成文件,请勿随意修改。如需修改请联系 PMC
* */
export default {
/** 列的偏移量默认单位px */
offset: {
type: [String, Number],
},
/** 列的宽度默认单位px */
span: {
type: [String, Number],
},
};

View File

@@ -0,0 +1,16 @@
/* eslint-disable */
/**
* 该文件为脚本自动生成文件,请勿随意修改。如需修改请联系 PMC
* */
export interface TdColProps {
/**
* 列的偏移量默认单位px
*/
offset?: string | number;
/**
* 列的宽度默认单位px
*/
span?: string | number;
}

View File

@@ -0,0 +1,154 @@
.t-collapse-panel {
background-color: var(--td-collapse-panel-bg-color, var(--td-bg-color-container, var(--td-font-white-1, #ffffff)));
}
.t-collapse-panel--disabled {
pointer-events: none;
}
.t-collapse-panel--disabled .t-collapse-panel__content,
.t-collapse-panel--disabled .t-collapse-panel__header {
opacity: 0.3;
}
.t-collapse-panel--top {
display: flex;
flex-direction: column-reverse;
}
.t-collapse-panel__header {
position: relative;
display: flex;
justify-content: space-between;
align-items: center;
padding-left: var(--td-collapse-horizontal-padding, 32rpx);
height: var(--td-collapse-header-height, auto);
}
.t-collapse-panel__header--top {
position: relative;
}
.t-collapse-panel__header--top::after {
content: '';
display: block;
position: absolute;
top: 0;
bottom: unset;
left: unset;
right: unset;
background-color: var(--td-collapse-border-color, var(--td-border-level-1-color, var(--td-gray-color-3, #e7e7e7)));
}
.t-collapse-panel__header--top::after {
height: 1px;
left: 0;
right: 0;
transform: scaleY(0.5);
}
.t-collapse-panel__header--bottom {
position: relative;
}
.t-collapse-panel__header--bottom::after {
content: '';
display: block;
position: absolute;
top: unset;
bottom: 0;
left: unset;
right: unset;
background-color: var(--td-collapse-border-color, var(--td-border-level-1-color, var(--td-gray-color-3, #e7e7e7)));
}
.t-collapse-panel__header--bottom::after {
height: 1px;
left: 0;
right: 0;
transform: scaleY(0.5);
}
.t-collapse-panel__header::after {
left: var(--td-spacer-2, 32rpx);
}
.t-collapse-panel__extra {
font: var(--td-collapse-extra-font, var(--td-font-body-large, 32rpx / 48rpx var(--td-font-family, PingFang SC, Microsoft YaHei, Arial Regular)));
}
.t-collapse-panel__body {
position: relative;
}
.t-collapse-panel__body::after {
content: '';
display: block;
position: absolute;
top: unset;
bottom: 0;
left: unset;
right: unset;
background-color: var(--td-collapse-border-color, var(--td-border-level-1-color, var(--td-gray-color-3, #e7e7e7)));
}
.t-collapse-panel__body::after {
height: 1px;
left: 0;
right: 0;
transform: scaleY(0.5);
}
.t-collapse-panel__wrapper {
height: 0;
overflow: hidden;
}
.t-collapse-panel__content {
color: var(--td-collapse-content-text-color, var(--td-text-color-primary, var(--td-font-gray-1, rgba(0, 0, 0, 0.9))));
font: var(--td-collapse-content-font, var(--td-font-body-medium, 28rpx / 44rpx var(--td-font-family, PingFang SC, Microsoft YaHei, Arial Regular)));
padding: var(--td-collapse-content-padding, 32rpx);
}
.t-collapse-panel__content--disabled {
color: var(--td-collapse-disabled-color, var(--td-text-color-disabled, var(--td-font-gray-4, rgba(0, 0, 0, 0.26))));
}
.t-collapse-panel__content--expanded.t-collapse-panel__content--bottom {
position: relative;
}
.t-collapse-panel__content--expanded.t-collapse-panel__content--bottom::after {
content: '';
display: block;
position: absolute;
top: unset;
bottom: 0;
left: unset;
right: unset;
background-color: var(--td-collapse-border-color, var(--td-border-level-1-color, var(--td-gray-color-3, #e7e7e7)));
}
.t-collapse-panel__content--expanded.t-collapse-panel__content--bottom::after {
height: 1px;
left: 0;
right: 0;
transform: scaleY(0.5);
}
.t-collapse-panel__content--expanded.t-collapse-panel__content--top {
position: relative;
}
.t-collapse-panel__content--expanded.t-collapse-panel__content--top::after {
content: '';
display: block;
position: absolute;
top: 0;
bottom: unset;
left: unset;
right: unset;
background-color: var(--td-collapse-border-color, var(--td-border-level-1-color, var(--td-gray-color-3, #e7e7e7)));
}
.t-collapse-panel__content--expanded.t-collapse-panel__content--top::after {
height: 1px;
left: 0;
right: 0;
transform: scaleY(0.5);
}
.t-collapse-panel__arrow--top {
transform: rotate(180deg);
}
.class-title {
font: var(--td-collapse-title-font, var(--td-font-body-large, 32rpx / 48rpx var(--td-font-family, PingFang SC, Microsoft YaHei, Arial Regular)));
color: var(--td-collapse-header-text-color, var(--td-text-color-primary, var(--td-font-gray-1, rgba(0, 0, 0, 0.9))));
}
.class-left-icon {
color: var(--td-collapse-left-icon-color, var(--td-brand-color, var(--td-primary-color-7, #0052d9)));
}
.class-right-icon {
color: var(--td-collapse-icon-color, var(--td-text-color-placeholder, var(--td-font-gray-3, rgba(0, 0, 0, 0.4))));
}
.class-title--disabled,
.class-note--disabled,
.class-left-icon--disabled,
.class-right-icon--disabled {
color: var(--td-collapse-disabled-color, var(--td-text-color-disabled, var(--td-font-gray-4, rgba(0, 0, 0, 0.26))));
}

View File

@@ -0,0 +1,238 @@
<template>
<view
:style="tools._style([customStyle])"
:class="[
classPrefix + ' ' + classPrefix + '--' + placement,
tClass
]"
>
<view
:class="classPrefix + '__title'"
aria-role="button"
:aria-expanded="expanded"
:aria-disabled="ultimateDisabled"
@click="onClick"
>
<t-cell
:title="header"
:note="headerRightContent"
bordered
:left-icon="headerLeftIcon"
:right-icon="ultimateExpandIcon ? (expanded ? 'chevron-up' : 'chevron-down') : ''"
:t-class="tools.cls(classPrefix + '__header', [placement, ['expanded', expanded]]) + ' ' + tClassHeader"
:t-class-title="'class-title ' + (ultimateDisabled ? 'class-title--disabled' : '')"
:t-class-note="'class-note ' + (ultimateDisabled ? 'class-note--disabled' : '')"
:t-class-left-icon="'class-left-icon ' + (ultimateDisabled ? 'class-left-icon--disabled' : '')"
:t-class-right-icon="'class-right-icon ' + classPrefix + '__arrow--' + placement + ' ' + (ultimateDisabled ? 'class-right-icon--disabled' : '')"
t-class-hover="class-header-hover"
:title-style="titleCustomStyle"
:note-style="noteCustomStyle"
:right-icon-style="rightIconCustomStyle"
>
<template
#left-icon
>
<slot
name="header-left-icon"
/>
</template>
<template
#title
>
<slot
name="header"
/>
</template>
<template
#note
>
<slot
name="header-right-content"
/>
</template>
<template
#right-icon
>
<slot
name="expand-icon"
/>
</template>
</t-cell>
</view>
<view
:class="classPrefix + '__wrapper'"
:animation="animation"
:aria-hidden="expanded ? '' : true"
>
<view :class="tools.cls(classPrefix + '__content', [['disabled', ultimateDisabled], ['expanded', expanded], placement]) + ' ' + tClassContent">
{{ content }}
<slot />
<slot name="content" />
</view>
</view>
</view>
</template>
<script>
import TCell from '../cell/cell';
import { uniComponent } from '../common/src/index';
import { prefix } from '../common/config';
import props from './props';
import { getRect } from '../common/utils';
import tools from '../common/utils.wxs';
import { ChildrenMixin, RELATION_MAP } from '../common/relation';
const name = `${prefix}-collapse-panel`;
const DISABLED_COLOR = 'var(--td-text-color-disabled, var(--td-font-gray-4, rgba(0, 0, 0, .26)))';
export default uniComponent({
name,
options: {
styleIsolation: 'shared',
},
externalClasses: [
`${prefix}-class`,
`${prefix}-class-content`,
`${prefix}-class-header`,
],
mixins: [ChildrenMixin(RELATION_MAP.CollapsePanel)],
components: {
TCell,
},
props: {
...props,
},
data() {
return {
prefix,
expanded: false,
classPrefix: name,
classBasePrefix: prefix,
ultimateExpandIcon: false,
ultimateDisabled: false,
tools,
animation: null,
};
},
computed: {
titleCustomStyle() {
return tools._style({
fontSize: 'var(--td-collapse-title-font-size, var(--td-font-size-m, 16px))',
color: this.ultimateDisabled && DISABLED_COLOR,
});
},
noteCustomStyle() {
return tools._style({
color: this.ultimateDisabled && DISABLED_COLOR,
});
},
rightIconCustomStyle() {
return tools._style({
color: this.ultimateDisabled && DISABLED_COLOR,
});
},
},
watch: {
disabled: {
handler(e) {
this.ultimateDisabled = !!e;
},
immediate: true,
},
},
methods: {
updateExpanded(activeValues = []) {
if (!this[RELATION_MAP.CollapsePanel]) {
return;
}
const { value } = this;
const { defaultExpandAll } = this[RELATION_MAP.CollapsePanel];
const expanded = defaultExpandAll ? !this.expanded : activeValues?.includes(value);
if (expanded === this.expanded) return;
this.expanded = expanded;
this.updateStyle(expanded);
},
updateStyle(expanded) {
return getRect(this, `.${name}__content`)
.then(rect => rect.height)
.then((height) => {
const animation = uni.createAnimation({
duration: 0,
timingFunction: 'ease-in-out',
});
if (expanded) {
animation
.height(height)
.top(0)
.step({ duration: 300 })
.height('auto')
.step();
} else {
let doAnimation = false;
// #ifdef H5 || APP-PLUS
animation
.height(height)
.top(1)
.step({ duration: 17 })
.height(0)
.step({ duration: 300 });
doAnimation = true;
// #endif
if (!doAnimation) {
animation
.height(height)
.top(1)
.step({ duration: 1 })
.height(0)
.step({ duration: 300 });
}
}
this.animation = animation.export();
});
},
onClick() {
const { ultimateDisabled } = this;
const { value } = this;
if (ultimateDisabled) return;
if (this[RELATION_MAP.CollapsePanel].defaultExpandAll) {
this.updateExpanded();
} else {
this[RELATION_MAP.CollapsePanel].switch(value);
}
},
innerAfterLinked() {
const { dataValue, expandIcon, disabled } = this[RELATION_MAP.CollapsePanel];
this.ultimateExpandIcon = this.expandIcon == null ? expandIcon : this.expandIcon;
this.ultimateDisabled = this.disabled == null ? disabled : this.disabled;
let interval = 0;
// #ifdef APP-PLUS
interval = 33;
// #endif
setTimeout(() => {
this.updateExpanded(dataValue);
}, interval);
},
},
});
</script>
<style scoped>
@import './collapse-panel.css';
</style>

View File

@@ -0,0 +1,48 @@
/* eslint-disable */
/**
* 该文件为脚本自动生成文件,请勿随意修改。如需修改请联系 PMC
* */
import type { TdCollapsePanelProps } from './type';
export default {
/** 折叠面板内容 */
content: {
type: String,
},
/** 禁止当前面板展开,优先级大于 Collapse 的同名属性 */
disabled: {
type: Boolean,
default: undefined,
},
/** 当前折叠面板展开图标,优先级大于 Collapse 的同名属性 */
expandIcon: {
type: Boolean,
default: undefined as TdCollapsePanelProps['expandIcon'],
},
/** 面板头内容 */
header: {
type: String,
},
/** 面板头左侧图标 */
headerLeftIcon: {
type: String,
},
/** 面板头的右侧区域,一般用于呈现面板操作 */
headerRightContent: {
type: String,
},
/** 选项卡内容的位置 */
placement: {
type: String,
default: 'bottom' as TdCollapsePanelProps['placement'],
validator(val: TdCollapsePanelProps['placement']): boolean {
if (!val) return true;
return ['bottom', 'top'].includes(val);
},
},
/** 当前面板唯一标识,如果值为空则取当前面下标兜底作为唯一标识 */
value: {
type: [String, Number],
},
};

View File

@@ -0,0 +1,41 @@
/* eslint-disable */
/**
* 该文件为脚本自动生成文件,请勿随意修改。如需修改请联系 PMC
* */
export interface TdCollapsePanelProps {
/**
* 折叠面板内容
*/
content?: string;
/**
* 禁止当前面板展开,优先级大于 Collapse 的同名属性
*/
disabled?: boolean;
/**
* 当前折叠面板展开图标,优先级大于 Collapse 的同名属性
*/
expandIcon?: boolean;
/**
* 面板头内容
*/
header?: string;
/**
* 面板头左侧图标
*/
headerLeftIcon?: string;
/**
* 面板头的右侧区域,一般用于呈现面板操作
*/
headerRightContent?: string;
/**
* 选项卡内容的位置
* @default bottom
*/
placement?: 'bottom' | 'top';
/**
* 当前面板唯一标识,如果值为空则取当前面下标兜底作为唯一标识
*/
value?: string | number;
}

View File

@@ -0,0 +1,82 @@
:: BASE_DOC ::
## API
### Collapse Props
name | type | default | description | required
-- | -- | -- | -- | --
custom-style | Object | - | CSS(Cascading Style Sheets) | N
default-expand-all | Boolean | false | \- | N
disabled | Boolean | - | \- | N
expand-icon | Boolean | true | \- | N
expand-mutex | Boolean | false | \- | N
theme | String | default | options: default/card | N
value | Array | - | `v-model:value` is supported。Typescript`CollapseValue` `type CollapseValue = Array<string \| number>`。[see more ts definition](https://github.com/Tencent/tdesign-miniprogram/tree/develop/packages/uniapp-components/collapse/type.ts) | N
default-value | Array | - | uncontrolled property。Typescript`CollapseValue` `type CollapseValue = Array<string \| number>`。[see more ts definition](https://github.com/Tencent/tdesign-miniprogram/tree/develop/packages/uniapp-components/collapse/type.ts) | N
### Collapse Events
name | params | description
-- | -- | --
change | `(context: { value: CollapseValue, context: { e: MouseEvent }})` | \-
### Collapse Slots
name | Description
-- | --
\- | \-
### CollapsePanel Props
name | type | default | description | required
-- | -- | -- | -- | --
custom-style | Object | - | CSS(Cascading Style Sheets) | N
content | String | - | \- | N
disabled | Boolean | undefined | \- | N
expand-icon | Boolean | undefined | \- | N
header | String | - | \- | N
header-left-icon | String | - | \- | N
header-right-content | String | - | \- | N
placement | String | bottom | options: bottom/top | N
value | String / Number | - | \- | N
### CollapsePanel Slots
name | Description
-- | --
\- | \-
content | \-
expand-icon | \-
header | \-
header-left-icon | \-
header-right-content | \-
### CollapsePanel External Classes
className | Description
-- | --
t-class | \-
t-class-content | \-
t-class-header | \-
### CSS Variables
The component provides the following CSS variables, which can be used to customize styles.
Name | Default Value | Description
-- | -- | --
--td-collapse-border-color | @border-level-1-color | -
--td-collapse-content-font | @font-body-medium | -
--td-collapse-content-padding | 32rpx | -
--td-collapse-content-text-color | @text-color-primary | -
--td-collapse-disabled-color | @text-color-disabled | -
--td-collapse-extra-font | @font-body-large | -
--td-collapse-header-height | auto | -
--td-collapse-header-text-color | @text-color-primary | -
--td-collapse-header-text-disabled-color | @collapse-disabled-color | -
--td-collapse-horizontal-padding | 32rpx | -
--td-collapse-icon-color | @text-color-placeholder | -
--td-collapse-left-icon-color | @brand-color | -
--td-collapse-panel-bg-color | @bg-color-container | -
--td-collapse-title-font | @font-body-large | -

View File

@@ -0,0 +1,119 @@
---
title: Collapse 折叠面板
description: 用于对复杂区域进行分组和隐藏 常用于订单信息展示等
spline: data
isComponent: true
---
## 引入
可在 `main.ts` 或在需要使用的页面或组件中引入。
```js
import TCollapse from '@tdesign/uniapp/collapse/collapse.vue';
import TCollapsePanel from '@tdesign/uniapp/collapse-panel/collapse-panel.vue';
```
### 类型
基础折叠面板
{{ base }}
带操作说明
{{ action }}
手风琴模式
{{ accordion }}
### 样式
卡片折叠面板
{{ theme }}
## API
### Collapse Props
名称 | 类型 | 默认值 | 描述 | 必传
-- | -- | -- | -- | --
custom-style | Object | - | 自定义样式 | N
default-expand-all | Boolean | false | 默认是否展开全部 | N
disabled | Boolean | - | 是否禁用面板展开/收起操作 | N
expand-icon | Boolean | true | 展开图标 | N
expand-mutex | Boolean | false | 每个面板互斥展开,每次只展开一个面板 | N
theme | String | default | 折叠面板风格。可选项default/card | N
value | Array | - | 展开的面板集合。支持语法糖 `v-model:value`。TS 类型:`CollapseValue` `type CollapseValue = Array<string \| number>`。[详细类型定义](https://github.com/Tencent/tdesign-miniprogram/tree/develop/packages/uniapp-components/collapse/type.ts) | N
default-value | Array | - | 展开的面板集合。非受控属性。TS 类型:`CollapseValue` `type CollapseValue = Array<string \| number>`。[详细类型定义](https://github.com/Tencent/tdesign-miniprogram/tree/develop/packages/uniapp-components/collapse/type.ts) | N
### Collapse Events
名称 | 参数 | 描述
-- | -- | --
change | `(context: { value: CollapseValue, context: { e: MouseEvent }})` | 切换面板时触发,返回变化的值
### Collapse Slots
名称 | 描述
-- | --
\- | 默认插槽,自定义内容区域内容
### CollapsePanel Props
名称 | 类型 | 默认值 | 描述 | 必传
-- | -- | -- | -- | --
custom-style | Object | - | 自定义样式 | N
content | String | - | 折叠面板内容 | N
disabled | Boolean | undefined | 禁止当前面板展开,优先级大于 Collapse 的同名属性 | N
expand-icon | Boolean | undefined | 当前折叠面板展开图标,优先级大于 Collapse 的同名属性 | N
header | String | - | 面板头内容 | N
header-left-icon | String | - | 面板头左侧图标 | N
header-right-content | String | - | 面板头的右侧区域,一般用于呈现面板操作 | N
placement | String | bottom | 选项卡内容的位置。可选项bottom/top | N
value | String / Number | - | 当前面板唯一标识,如果值为空则取当前面下标兜底作为唯一标识 | N
### CollapsePanel Slots
名称 | 描述
-- | --
\- | 默认插槽,作用同 `content` 插槽
content | 自定义 `content` 显示内容
expand-icon | 自定义 `expand-icon` 显示内容
header | 自定义 `header` 显示内容
header-left-icon | 自定义 `header-left-icon` 显示内容
header-right-content | 自定义 `header-right-content` 显示内容
### CollapsePanel External Classes
类名 | 描述
-- | --
t-class | 根节点样式类
t-class-content | 内容样式类
t-class-header | 头部样式类
### CSS Variables
组件提供了下列 CSS 变量,可用于自定义样式。
名称 | 默认值 | 描述
-- | -- | --
--td-collapse-border-color | @border-level-1-color | -
--td-collapse-content-font | @font-body-medium | -
--td-collapse-content-padding | 32rpx | -
--td-collapse-content-text-color | @text-color-primary | -
--td-collapse-disabled-color | @text-color-disabled | -
--td-collapse-extra-font | @font-body-large | -
--td-collapse-header-height | auto | -
--td-collapse-header-text-color | @text-color-primary | -
--td-collapse-header-text-disabled-color | @collapse-disabled-color | -
--td-collapse-horizontal-padding | 32rpx | -
--td-collapse-icon-color | @text-color-placeholder | -
--td-collapse-left-icon-color | @brand-color | -
--td-collapse-panel-bg-color | @bg-color-container | -
--td-collapse-title-font | @font-body-large | -

View File

@@ -0,0 +1,5 @@
.t-collapse--card {
margin: 0 32rpx;
border-radius: var(--td-radius-large, 18rpx);
overflow: hidden;
}

View File

@@ -0,0 +1,119 @@
<template>
<view
:style="tools._style([customStyle])"
:class="[
tClass,
tools.cls(classPrefix, [['hairline--top-bottom', border], theme])
]"
>
<slot />
</view>
</template>
<script>
import { uniComponent } from '../common/src/index';
import { prefix } from '../common/config';
import { coalesce } from '../common/utils';
import props from './props';
import tools from '../common/utils.wxs';
import { ParentMixin, RELATION_MAP } from '../common/relation';
const name = `${prefix}-collapse`;
export default uniComponent({
name,
controlledProps: [
{
key: 'value',
event: 'change',
},
],
options: {
styleIsolation: 'shared',
},
externalClasses: [`${prefix}-class`],
mixins: [ParentMixin(RELATION_MAP.CollapsePanel)],
props: {
...props,
},
emits: [
'update:value',
],
data() {
return {
prefix,
classPrefix: name,
tools,
border: false,
dataValue: coalesce(this.value, this.defaultValue),
mounted: false,
};
},
watch: {
value: {
handler(value) {
this.dataValue = value;
},
immediate: true,
},
dataValue: {
handler() {
this.waitUntilMounted(this.updateExpanded);
},
immediate: true,
deep: true,
},
expandMutex: {
handler() {
this.waitUntilMounted(this.updateExpanded);
},
immediate: true,
},
},
mounted() {
let interval = 0;
// #ifdef APP-PLUS
interval = 33;
// #endif
setTimeout(() => {
this.mounted = true;
}, interval);
},
methods: {
waitUntilMounted(cb) {
if (this.mounted) {
cb.call(this);
return;
}
setTimeout(() => {
cb.call(this);
}, 33);
},
updateExpanded() {
this.children?.forEach((e) => {
e.updateExpanded(this.dataValue);
});
},
switch(panelValue) {
const { expandMutex, dataValue: activeValues } = this;
let value = [];
const hit = activeValues?.indexOf(panelValue);
if (hit > -1) {
value = activeValues.filter(item => item !== panelValue);
} else {
value = expandMutex ? [panelValue] : activeValues.concat(panelValue);
}
this._trigger('change', { value });
},
},
});
</script>
<style scoped>
@import './collapse.css';
</style>

View File

@@ -0,0 +1,42 @@
/* eslint-disable */
/**
* 该文件为脚本自动生成文件,请勿随意修改。如需修改请联系 PMC
* */
import type { TdCollapseProps } from './type';
export default {
/** 默认是否展开全部 */
defaultExpandAll: Boolean,
/** 是否禁用面板展开/收起操作 */
disabled: Boolean,
/** 展开图标 */
expandIcon: {
type: Boolean,
default: true,
},
/** 每个面板互斥展开,每次只展开一个面板 */
expandMutex: Boolean,
/** 折叠面板风格 */
theme: {
type: String,
default: 'default' as TdCollapseProps['theme'],
validator(val: TdCollapseProps['theme']): boolean {
if (!val) return true;
return ['default', 'card'].includes(val);
},
},
/** 展开的面板集合 */
value: {
type: Array,
},
/** 展开的面板集合,非受控属性 */
defaultValue: {
type: Array,
},
/** 切换面板时触发,返回变化的值 */
onChange: {
type: Function,
default: () => ({}),
},
};

View File

@@ -0,0 +1,46 @@
/* eslint-disable */
/**
* 该文件为脚本自动生成文件,请勿随意修改。如需修改请联系 PMC
* */
export interface TdCollapseProps {
/**
* 默认是否展开全部
* @default false
*/
defaultExpandAll?: boolean;
/**
* 是否禁用面板展开/收起操作
*/
disabled?: boolean;
/**
* 展开图标
* @default true
*/
expandIcon?: boolean;
/**
* 每个面板互斥展开,每次只展开一个面板
* @default false
*/
expandMutex?: boolean;
/**
* 折叠面板风格
* @default default
*/
theme?: 'default' | 'card';
/**
* 展开的面板集合
*/
value?: CollapseValue;
/**
* 展开的面板集合,非受控属性
*/
defaultValue?: CollapseValue;
/**
* 切换面板时触发,返回变化的值
*/
onChange?: (context: { value: CollapseValue; context: { e: MouseEvent } }) => void;
}
export type CollapseValue = Array<string | number>;

Some files were not shown because too many files have changed in this diff Show More