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,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';
}