| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257 |
- <template>
- <view
- :class="['nana-badge', standalone ? 'standalone' : '']"
- :style="containerStyle"
- >
- <slot />
- <SimpleTransition name="badge" :show="showBadge" :duration="3000">
- <template #show="{ classNames }">
- <view :class="['nana-badge-inner',...classNames]" :style="currentStyle">
- <VerticalScrollText v-if="anim" center :innerStyle="textStyle" :numberString="content2" />
- <Text v-else :innerStyle="textStyle" :text="content2" />
- </view>
- </template>
- </SimpleTransition>
- </view>
- </template>
- <script setup lang="ts">
- import { computed } from 'vue';
- import { propGetThemeVar, useTheme, type TextStyle, type ViewStyle } from '../theme/ThemeDefine';
- import { selectStyleType } from '../theme/ThemeTools';
- import VerticalScrollText from '../typography/VerticalScrollText.vue';
- import Text from '../basic/Text.vue';
- import SimpleTransition from '../anim/SimpleTransition.vue';
- type BadgePositionTypes = 'topRight'|'topLeft'|'bottomRight'|'bottomLeft';
- export interface BadgeProps {
- /**
- * 控制是否显示
- * @default true
- */
- show?: boolean,
- /**
- * 独立显示
- */
- standalone?: boolean;
- /**
- * 徽标内容
- */
- content?: string|number,
- /**
- * 徽标数字最大值,如果徽标内容是数字,并且超过最大值,则显示 xx+。
- */
- maxCount?: number,
- /**
- * 徽标颜色
- * @default danger
- */
- color?: string,
- /**
- * 是否增加描边
- * @default false
- */
- border?: boolean;
- /**
- * 描边宽度
- * @default 2
- */
- borderWidth?: number;
- /**
- * 描边颜色
- * @default white
- */
- borderColor?: string;
- /**
- * 徽标在父级所处位置
- * @default 'top-right'
- */
- position?: BadgePositionTypes;
- /**
- * 是否在切换时有动画效果
- * @default false
- */
- anim?: boolean,
- /**
- * 徽标定位偏移
- */
- offset?: { x: number, y: number };
- /**
- * 徽标自定义样式
- */
- badgeStyle?: ViewStyle;
- /**
- * 徽标文字自定义样式
- */
- textStyle?: TextStyle;
- /**
- * 外层容器样式
- */
- containerStyle?: ViewStyle;
- /**
- * 徽标无文字情况下的徽标大小
- * @default 10
- */
- badgeSize?: number;
- /**
- * 字号
- * @default 12.5
- */
- fontSize?: number,
- /**
- * 圆角的大小
- * @default 20
- */
- radius?: number;
- /**
- * 徽标有文字情况下的垂直内边距
- * @default 2
- */
- paddingVertical?: number,
- /**
- * 徽标有文字情况下的水平内边距
- * @default 4
- */
- paddingHorizontal?: number,
- /**
- * 如果 content===0 是否隐藏红点
- * @default true
- */
- hiddenIfZero?: boolean;
- }
- const themeContext = useTheme();
- const props = withDefaults(defineProps<BadgeProps>(), {
- color: 'danger',
- show: true,
- anim: () => propGetThemeVar('BadgeAnim', false),
- content: '',
- border: () => propGetThemeVar('BadgeBorder', false),
- borderWidth: () => propGetThemeVar('BadgeBorderWidth', 4),
- borderColor: () => propGetThemeVar('BadgeBorderColor', 'white'),
- position: () => propGetThemeVar('BadgePosition', 'topRight'),
- offset: () => propGetThemeVar('BadgeOffset', { x: 0, y: 0 }),
- badgeSize: () => propGetThemeVar('BadgeSize', 18),
- radius: () => propGetThemeVar('BadgeRadius', 20),
- fontSize: () => propGetThemeVar('BadgeFontSize', 24),
- paddingVertical: () => propGetThemeVar('BadgePaddingVertical', 4),
- paddingHorizontal: () => propGetThemeVar('BadgePaddingHorizontal', 8),
- hiddenIfZero: true,
- });
- //样式生成
- const currentStyle = computed(() => {
- const size = themeContext.resolveThemeSize(props.badgeSize);
- return {
- backgroundColor: themeContext.resolveThemeColor(props.color),
- borderRadius: themeContext.resolveThemeSize(props.radius),
- border: props.border ? `${themeContext.resolveThemeSize(props.borderWidth)} solid ${themeContext.resolveThemeColor(props.borderColor)}` : undefined,
- minWidth: props.content ? themeContext.resolveThemeSize(props.fontSize) : undefined,
- padding: `${themeContext.resolveThemeSize(props.paddingVertical)} ${themeContext.resolveThemeSize(props.paddingHorizontal)}`,
- ...props.badgeStyle,
- ...(props.standalone ? {} : selectStyleType<TextStyle, BadgePositionTypes>(props.position, 'topRight', {
- topRight: {
- transform: `translate(50%, -50%)`,
- top: themeContext.resolveSize(props.offset.y),
- right: themeContext.resolveSize(props.offset.x),
- },
- topLeft: {
- transform: `translate(-50%, -50%)`,
- top: themeContext.resolveSize(props.offset.y),
- left: themeContext.resolveSize(props.offset.x),
- },
- bottomRight: {
- transform: `translate(50%, 50%)`,
- bottom: themeContext.resolveSize(props.offset.y),
- right: themeContext.resolveSize(props.offset.x),
- },
- bottomLeft: {
- transform: `translate(-50%, 50%)`,
- bottom: themeContext.resolveSize(props.offset.y),
- left: themeContext.resolveSize(props.offset.x),
- },
- })),
- ...(
- props.content ? {} : {
- width: size,
- height: size,
- borderRadius: '50%',
- padding: undefined,
- }
- ),
- }
- });
- const textStyle = computed(() => {
- const fontSize = themeContext.resolveThemeSize(props.fontSize, 24);
- return {
- fontSize: fontSize,
- color: themeContext.resolveThemeColor('BadgeTextColor', 'white'),
- textAlign: 'center',
- ...props.textStyle,
- }
- })
- const content2 = computed(() => {
- if (typeof props.content !== 'undefined') {
- if (typeof props.content === 'number') {
- return (props.maxCount && props.content > props.maxCount) ? `${props.maxCount}+` : props.content.toString();
- } else
- return props.content;
- }
- return '';
- });
- const showBadge = computed(() =>
- props.show && (!props.hiddenIfZero ||
- (props.hiddenIfZero && props.content !== 0 && props.content !== '0'))
- );
- </script>
- <style lang="scss">
- .nana-badge {
- position: relative;
- display: inline-flex;
- width: auto;
- height: auto;
- overflow: visible;
- .badge-enter-active,
- .badge-leave-active {
- opacity: 1;
- transform: scale(1);
- transition: all 0.3s ease-in-out;
- }
- .badge-enter-from,
- .badge-leave-to {
- transform: scale(0);
- opacity: 0;
- }
- &.standalone {
- position: relative;
- .nana-badge-inner {
- position: relative;
- z-index: 100;
- }
- }
- }
- .nana-badge-inner {
- position: absolute;
- z-index: 100;
- display: flex;
- justify-content: center;
- align-items: center;
- text-align: center;
- align-self: flex-start;
- overflow: hidden;
- flex-shrink: 0;
- }
- </style>
|