添加bar组件

This commit is contained in:
2025-11-05 17:34:23 +08:00
parent 7314f23529
commit 3cb63ffca7
223 changed files with 23185 additions and 2 deletions

View File

@@ -0,0 +1,112 @@
@import '~@/uni_modules/lime-style/index.scss';
/* #ifdef uniVersion >= 4.75 */
$use-css-var: true;
/* #endif */
$badge-size: create-var(badge-size , $spacer);
$badge-color: create-var(badge-color , white);
// $badge-padding: create-var(badge-padding, 0 4px);
$badge-padding-x: create-var(badge-padding-x, $spacer-tn); // 水平方向(左右)
$badge-padding-y: create-var(badge-padding-y, 0); // 垂直方向(上下)
$badge-font-size: create-var(badge-font-size, $font-size-sm);
$badge-font-weight: create-var(badge-font-weight, bold);
$badge-border-width: create-var(badge-border-width, 1rpx);
$badge-border-color: create-var(badge-border-color, white);
$badge-bg-color: create-var(badge-bg-color, $error-color);
$badge-dot-color: create-var(badge-dot-color, $error-color);
$badge-dot-size: create-var(badge-dot-size, 8px);
$badge-font: create-var(badge-font, -apple-system-font, helvetica neue, arial, sans-serif);
$badge-border-radius: create-var(badge-border-radius, $border-radius-hg);
.l-badge {
/* #ifndef UNI-APP-X */
display: inline-block;
/* #endif */
/* #ifndef APP-ANDROID || APP-IOS || APP-HARMONY */
min-width: $badge-size;
/* #endif */
box-sizing: border-box;
// padding: $badge-padding;
@include padding($badge-padding-y $badge-padding-x);
// box-sizing: content-box;
color: $badge-color;
font-weight: $badge-font-weight;
font-size: $badge-font-size;
font-family: $badge-font;
line-height: 1.2;
white-space: nowrap;
text-align: center;
background-color: $badge-bg-color;
// border: $badge-border-width solid $badge-border-color;
border-width: $badge-border-width;
border-style: solid;
border-color: $badge-border-color;
// border-radius: $badge-border-radius;
@include border-radius($badge-border-radius);
overflow: visible;
&--fixed {
position: absolute;
transform-origin: 100%;
z-index: 1
}
&--offscreen {
position: fixed !important;
opacity: 0;
}
&--top-left {
top: 0;
left: 0;
transform: translate(-50%, -50%);
}
&--top-right {
top: 0;
right: 0;
transform: translate(50%, -50%);
}
&--bottom-left {
bottom: 0;
left: 0;
transform: translate(-50%, 50%);
}
&--bottom-right {
bottom: 0;
right: 0;
transform: translate(50%, 50%);
}
&--dot {
width: $badge-dot-size;
min-width: 0;
height: $badge-dot-size;
background: $badge-dot-color;
border-radius: 99px;
// border: none;
border-width: 0;
padding: 0;
overflow: visible;
}
&__wrapper {
position: relative;
overflow: visible;
/* #ifndef UNI-APP-X */
display: inline-block;
/* #endif */
/* #ifdef UNI-APP-X */
// align-self: flex-start;
/* #endif */
}
}

View File

@@ -0,0 +1,83 @@
@import '~@/uni_modules/lime-style/index.scss';
$badge-size: create-var(badge-size , 16px);
$badge-color: create-var(badge-color , white);
$badge-padding: create-var(badge-padding, 0 3px);
$badge-font-size: create-var(badge-font-size, 12px);
$badge-font-weight: create-var(badge-font-weight, bold);
$badge-border-width: create-var(badge-border-width, 1px);
$badge-border-color: create-var(badge-border-color, white);
$badge-background: create-var(badge-background, $error-color);
$badge-dot-color: create-var(badge-dot-color, $error-color);
$badge-dot-size: create-var(badge-dot-size, 8px);
$badge-font: create-var(badge-font, -apple-system-font, helvetica neue, arial, sans-serif);
$badge-border-radius: create-var(badge-border-radius, 999px);
.l-badge {
display: inline-block;
box-sizing: border-box;
min-width: $badge-size;
padding: $badge-padding;
color: $badge-color;
font-weight: $badge-font-weight;
font-size: $badge-font-size;
font-family: $badge-font;
line-height: 1.2;
text-align: center;
background-color: $badge-background;
border: $badge-border-width solid $badge-border-color;
border-radius: $badge-border-radius;
&--fixed {
position: absolute;
transform-origin: 100%;
}
&--top-left {
top: 0;
left: 0;
transform: translate(-50%, -50%);
}
&--top-right {
top: 0;
right: 0;
transform: translate(50%, -50%);
}
&--bottom-left {
bottom: 0;
left: 0;
transform: translate(-50%, 50%);
}
&--bottom-right {
bottom: 0;
right: 0;
transform: translate(50%, 50%);
}
&--dot {
width: $badge-dot-size;
min-width: 0;
height: $badge-dot-size;
background: $badge-dot-color;
border-radius: 100%;
// border: none;
border-width: 0;
padding: 0;
}
&__wrapper {
position: relative;
/* #ifndef UNI-APP-X */
display: inline-block;
/* #endif */
/* #ifdef UNI-APP-X */
// display: inline-block;
width: fit-content;
align-items: flex-start;
/* #endif */
}
}

View File

@@ -0,0 +1,148 @@
<template>
<view class="l-badge__wrapper" v-if="$slots['default'] != null">
<slot></slot>
<text v-if="hasContent || dot" class="l-badge" ref="textRef" :class="classes" :style="[styles]">
<slot name="content">{{renderContent}}</slot>
</text>
<!-- #ifdef APP-HARMONY -->
<text v-if="hasContent || dot" class="l-badge l-badge--offscreen" ref="offscreenRef" >
<slot name="content">{{renderContent}}</slot>
</text>
<!-- #endif -->
</view>
<text v-else-if="hasContent || dot" class="l-badge" :class="classes" :style="[styles]">
<slot name="content">{{renderContent}}</slot>
</text>
</template>
<script lang="uts" setup>
/**
* Badge 徽标组件
* @description 用于展示状态标记、消息数量等提示信息,支持多种形态和定位方式
* <br> 插件类型LBadgeComponentPublicInstance
* @tutorial https://ext.dcloud.net.cn/plugin?name=lime-badge
*
* @property {string} [color] 徽标背景色支持CSS颜色值
* @property {number | string | any} content 显示内容(数字/文字)
* @property {boolean} dot 是否显示为小红点优先级高于content
* @property {number} max 数字最大值(超出显示为${max}+
* @property {Array<string | number> | any[]} offset 位置偏移量([x, y]
* @property {'top-right' | 'top-left' | 'bottom-right' | 'bottom-left'} position 定位位置
* @property {'circle' | 'square' | 'bubble' | 'ribbon'} [shape] 形状(当前版本未实现)
* @property {boolean} showZero 数值为0时是否显示
* @property {'medium' | 'large'} [size] 尺寸(当前版本未实现)
* @property {string | number} [content] 支持字符串模板(例:'${count}条'
* @property {Array<string | number>} offset 支持单位(例:['-10rpx', '20px']
*/
import { isNumeric } from '@/uni_modules/lime-shared/isNumeric'
import { isNumber } from '@/uni_modules/lime-shared/isNumber'
import { addUnit } from '@/uni_modules/lime-shared/addUnit'
import { toBoolean } from '@/uni_modules/lime-shared/toBoolean'
import { getOffsetWithMinusString } from './utils'
import { BadgeProps } from './type'
const name = 'l-badge'
const props = withDefaults(defineProps<BadgeProps>(), {
dot: false,
max: 99,
showZero: false,
// #ifdef APP-ANDROID
// offset: [] as any[],
// #endif
// #ifndef APP-ANDROID
// offset: [] as (string | number)[],
// #endif
position: 'top-right'
})
const context = getCurrentInstance()!
const classes = computed(():Map<string, boolean>=>{
const cls = new Map<string, boolean>()
cls.set(`${name}--fixed`, toBoolean(context.slots['default']));
cls.set(`${name}--dot`, props.dot);
cls.set(`${name}--${props.position}`, context.slots['default'] != null);
return cls
})
const styles = computed(():Map<string, any|null>=>{
const style = new Map<string, any|null>()
if(toBoolean(props.color)) {
style.set('background-color', props.color!)
}
const positions = props.position.split('-');
const offset = props.offset;
if(offset != null && offset.length == 2) {
const x = offset[0];
const y = offset[1];
if(context.slots['default'] != null) {
if(positions.length == 2) {
const offsetY = positions[0], offsetX = positions[1];
if(isNumber(y)) {
const _y = y as number
style.set(offsetY, addUnit(offsetY == 'top' ? _y : -_y))
} else {
style.set(offsetY, offsetY == 'top' ? addUnit(y) : getOffsetWithMinusString(`${y}`))
}
if(isNumber(x)) {
const _x = x as number
style.set(offsetX, addUnit(offsetX == 'left' ? _x : -_x))
} else {
style.set(offsetY, offsetY == 'left' ? addUnit(x) : getOffsetWithMinusString(`${x}`))
}
}
} else {
style.set('margin-top', addUnit(y))
style.set('margin-left', addUnit(x))
}
}
return style
});
const hasContent = computed<boolean>(():boolean => {
if(toBoolean(context.slots['content'])) {
return true
}
const content = props.content;
return (content != '' && content != null && (props.showZero || content !== '0'));
});
const renderContent = computed<string>(():string=>{
const dot = props.dot
const max = props.max
const content = props.content
if(!dot && hasContent.value) {
if(max != 0 && isNumeric(content) && parseFloat(content.toString()) > max) {
return `${max}+`
}
}
if(dot) {
return ''
}
return `${content ?? ""}`
})
// #ifdef APP-HARMONY
// 鸿蒙BUG 显示不存
// 暂时先绕一下
const textRef = ref<UniTextElement|null>(null)
const offscreenRef = ref<UniTextElement|null>(null)
const resizeObserver = new UniResizeObserver((entries : Array<UniResizeObserverEntry>) => {
offscreenRef.value?.getBoundingClientRectAsync()?.then(res=>{
// const width = entries[0].target.getBoundingClientRect().width
textRef.value!.style.setProperty('width', res.width*1.05)
})
})
const stopWatch = watch(offscreenRef, (el:UniElement|null) => {
if(el== null) return
resizeObserver.observe(el)
})
onUnmounted(()=>{
stopWatch()
resizeObserver.disconnect()
})
// #endif
</script>
<style lang="scss">
@import './index-u';
</style>

View File

@@ -0,0 +1,117 @@
<template>
<view class="l-badge__wrapper" v-if="$slots.default">
<slot></slot>
<view v-if="hasContent || props.dot" class="l-badge" :class="classes" :style="[styles]">
<slot v-if="$slots.content" name="content"></slot>
<block v-else-if="renderContent">{{ renderContent }}</block>
</view>
</view>
<view v-else-if="hasContent || props.dot" class="l-badge" :class="classes" :style="[styles]">
<slot v-if="$slots.content" name="content"></slot>
<block v-else-if="renderContent">{{ renderContent }}</block>
</view>
</template>
<script lang="ts">
// @ts-nocheck
/**
* Badge 徽标组件
* @description 用于展示状态标记、消息数量等提示信息,支持多种形态和定位方式
* @tutorial https://ext.dcloud.net.cn/plugin?name=lime-badge
*
* @property {string} [color] 徽标背景色支持CSS颜色值
* @property {number | string | any} content 显示内容(数字/文字)
* @property {boolean} dot 是否显示为小红点优先级高于content
* @property {number} max 数字最大值(超出显示为${max}+
* @property {Array<string | number> | any[]} offset 位置偏移量([x, y]
* @property {'top-right' | 'top-left' | 'bottom-right' | 'bottom-left'} position 定位位置
* @property {'circle' | 'square' | 'bubble' | 'ribbon'} [shape] 形状(当前版本未实现)
* @property {boolean} showZero 数值为0时是否显示
* @property {'medium' | 'large'} [size] 尺寸(当前版本未实现)
* @property {string | number} [content] 支持字符串模板(例:'${count}条'
* @property {Array<string | number>} offset 支持单位(例:['-10rpx', '20px']
*/
import { computed, defineComponent, getCurrentInstance, onMounted } from '@/uni_modules/lime-shared/vue'
import badgeProps from './props'
import { isNumeric } from '@/uni_modules/lime-shared/isNumeric'
import { isNumber } from '@/uni_modules/lime-shared/isNumber'
import { addUnit } from '@/uni_modules/lime-shared/addUnit'
import { isDef } from '@/uni_modules/lime-shared/isDef'
import { getClassStr } from '@/uni_modules/lime-shared/getClassStr'
import { getOffsetWithMinusString } from './utils'
const name = 'l-badge'
interface CSSProperties {
[key : string] : string | number | undefined;
}
export default defineComponent({
name,
props: badgeProps,
setup(props) {
const context = getCurrentInstance()
// vue2 setup 解构的 slots 为空
const classes = computed(() => {
return getClassStr({
[`${name}--fixed`]: context.slots.default,
[`${name}--dot`]: props.dot,
[`${name}--${props.position}`]: Boolean(context.slots['default'])
})
})
const styles = computed(() => {
const style : CSSProperties = {
background: props.color,
};
if (props.offset) {
const [x, y] = props.offset;
const { position } = props;
const [offsetY, offsetX] = `${position}`.split('-') as ['top' | 'bottom', 'left' | 'right'];
if (context.slots.default) {
if (isNumber(y)) {
style[offsetY] = addUnit(offsetY === 'top' ? y : -y);
} else {
style[offsetY] = offsetY === 'top' ? addUnit(y) : getOffsetWithMinusString(`${y}`);
}
if (isNumber(x)) {
style[offsetX] = addUnit(offsetX === 'left' ? x : -x);
} else {
style[offsetX] = offsetX === 'left' ? addUnit(x) : getOffsetWithMinusString(`${x}`);
}
} else {
style.marginTop = addUnit(y);
style.marginLeft = addUnit(x);
}
}
return style
})
const hasContent = computed(() => {
if (Boolean(context.slots.content)) {
return !0
}
const { content, showZero } = props;
return (isDef(content) && content !== '' && (showZero || (content !== 0 && content !== '0')));
})
const renderContent = computed(() => {
const { dot, max, content } = props;
if (!dot && hasContent.value) {
//@ts-ignore
if (isDef(max) && max != 0 && isDef(content) && isNumeric(content!) && +content > +max) {
return `${max}+`
}
}
return content
})
return {
props,
classes,
styles,
hasContent,
renderContent
}
}
})
</script>
<style lang="scss">
@import './index-u';
</style>

View File

@@ -0,0 +1,20 @@
// @ts-nocheck
import type {PropType} from '@/uni_modules/lime-shared/vue'
export type BadgePosition =
| 'top-left'
| 'top-right'
| 'bottom-left'
| 'bottom-right';
type Numeric = string | number
export default {
dot: Boolean,
max: Number,
color: String,
offset: Array as unknown as PropType<[Numeric, Numeric]>,
content: [Number , String],
showZero: Boolean,
position: {
type: String as PropType<BadgePosition>,
default: 'top-right'
}
}

View File

@@ -0,0 +1,50 @@
// @ts-nocheck
export interface BadgeProps {
/**
* 颜色
* @default ''
*/
color?: string;
/**
* 徽标内容
*/
// #ifndef APP-ANDROID
content?: string|number;
// #endif
// #ifdef APP-ANDROID
content?: any;
// #endif
/**
* 是否为红点
*/
dot: boolean;
/**
* 封顶的数字值
* @default 99
*/
max: number;
/**
* 设置状态点的位置偏移,示例:[-10, 20] 或 ['10rpx', '8rpx']
*/
// #ifndef APP-ANDROID
offset?: Array<string | number>;
// #endif
// #ifdef APP-ANDROID
offset?: any[];
// #endif
position: string;
/**
* 形状 未实现
* @default circle
*/
shape?: 'circle' | 'square' | 'bubble' | 'ribbon';
/**
* 当数值为 0 时,是否展示徽标
*/
showZero: boolean;
/**
* 尺寸 未实现
* @default medium
*/
size?: 'medium' | 'large';
}

View File

@@ -0,0 +1,3 @@
export function getOffsetWithMinusString(val : string):string {
return val.startsWith('-') ? val.replace('-', '') : `-${val}`
};

View File

@@ -0,0 +1,172 @@
<template>
<view class="demo-block">
<text class="demo-block__title-text ultra">Badge 徽标</text>
<text class="demo-block__desc-text">用于告知用户该区域的状态变化或者待处理任务的数量</text>
<view class="demo-block__body">
<view class="demo-block card">
<text class="demo-block__title-text large">基础</text>
<view class="demo-block__body">
<view class="preview" >
<l-badge content="500">
<view class="child"/>
</l-badge>
<l-badge :content="10">
<view class="child" />
</l-badge>
<l-badge content="Hot">
<view class="child" />
</l-badge>
<l-badge :dot="true">
<view class="child" />
</l-badge>
</view>
</view>
</view>
<view class="demo-block card">
<text class="demo-block__title-text large">最大值</text>
<view class="demo-block__body">
<view class="preview">
<l-badge content="20" :max="9">
<view class="child" />
</l-badge>
<l-badge content="50" :max="20">
<view class="child" />
</l-badge>
<l-badge content="200" :max="99">
<view class="child" />
</l-badge>
</view>
</view>
</view>
<view class="demo-block card">
<text class="demo-block__title-text large">徽标颜色</text>
<view class="demo-block__body">
<view class="preview">
<l-badge content="5" color="#1989fa">
<view class="child" />
</l-badge>
<l-badge content="10" color="#1989fa">
<view class="child" />
</l-badge>
<l-badge :dot="true" color="#1989fa">
<view class="child" />
</l-badge>
</view>
</view>
</view>
<view class="demo-block card">
<text class="demo-block__title-text large">徽标内容</text>
<view class="demo-block__body">
<view class="preview">
<l-badge>
<view class="child" />
<template #content>你好</template>
</l-badge>
<l-badge>
<view class="child" />
<template #content>999</template>
</l-badge>
</view>
</view>
</view>
<view class="demo-block card">
<text class="demo-block__title-text large">徽标位置</text>
<view class="demo-block__body">
<view class="preview">
<l-badge content="10" position="top-left">
<view class="child" />
</l-badge>
<l-badge content="10" position="bottom-left">
<view class="child" />
</l-badge>
<l-badge content="10" position="bottom-right">
<view class="child" />
</l-badge>
</view>
</view>
</view>
<view class="demo-block card">
<text class="demo-block__title-text large">独立展示</text>
<view class="demo-block__body">
<view class="preview">
<l-badge content="20" />
<l-badge content="200" :max="88" />
</view>
</view>
</view>
</view>
</view>
</template>
<script>
</script>
<style lang="scss">
$card-bg-color: var(--doc-card-bg-color, white);
// $page-bg-color: var(--doc-page-bg-color, #f5f5f5);
$title-color: var(--doc-title-color, #000000E6);
$card-summary-color: var(--doc-card-summary-color, #0000000F);
$summary-color: var(--doc-summary-color, #00000099);
.preview {
padding-top: 20px;
flex-direction: row;
justify-content: space-around;
overflow: visible;
// padding-left: 20px;
}
.child {
width: 50px;
height: 50px;
background-color: $card-summary-color;
border-radius: 4px;
}
.demo-block {
margin: 32px 10px 0;
overflow: visible;
&.card{
padding: 30rpx;
background-color: $card-bg-color;
transition-property: background-color;
// transition-duration: 300ms;
margin-bottom: 20rpx !important;
}
&__title {
margin: 0;
margin-top: 8px;
&-text {
color: $summary-color;
font-weight: 400;
font-size: 14px;
line-height: 16px;
&.large {
color: $title-color;
font-size: 18px;
font-weight: 700;
line-height: 26px;
}
&.ultra {
color: $title-color;
font-size: 24px;
font-weight: 700;
line-height: 32px;
}
}
}
&__desc-text {
color: $summary-color;
margin: 8px 16px 0 0;
font-size: 14px;
line-height: 22px;
}
&__body {
margin: 16px 0;
overflow: visible;
.demo-block {
// margin-top: 0px;
margin: 0;
}
}
}
</style>