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,23 @@
export const isEmptyObj = function (obj) {
return JSON.stringify(obj) === '{}';
};
const changeNumToStr = function (arr) {
return arr.map(item => (typeof item === 'number' ? `${item}rpx` : item));
};
export const getMessageStyles = function (zIndex, offset, wrapTop) {
const arr = changeNumToStr(offset || [0, 0]);
const left = arr[1] || 0;
const right = arr[1] || 0;
const zIndexStyle = zIndex ? `z-index:${zIndex};` : '';
let styleOffset = '';
styleOffset += `top:${wrapTop}px;`;
styleOffset += `left:${left};`;
styleOffset += `right:${right};`;
return zIndexStyle + styleOffset;
};

View File

@@ -0,0 +1,40 @@
import { MessageType } from '../message/message.interface';
import { getInstance } from '../common/utils';
const showMessage = function (options, theme = MessageType.info) {
const { context, selector = '#t-message', ...otherOptions } = options;
const instance = getInstance(context, selector);
if (instance) {
instance.resetData(() => {
instance.setData({ theme, ...otherOptions }, instance.show.bind(instance));
});
return instance;
}
console.error('未找到组件,请确认 selector && context 是否正确');
};
export default {
info(options) {
return showMessage(options, MessageType.info);
},
success(options) {
return showMessage(options, MessageType.success);
},
warning(options) {
return showMessage(options, MessageType.warning);
},
error(options) {
return showMessage(options, MessageType.error);
},
hide(options) {
const { context, selector = '#t-message' } = { ...options };
const instance = getInstance(context, selector);
if (!instance) {
return;
}
instance.hide();
},
};

View File

@@ -0,0 +1,63 @@
.t-message {
position: fixed;
top: 0;
left: 0;
right: 0;
transition: opacity 0.3s, transform 0.4s, top 0.4s;
display: flex;
justify-content: flex-start;
height: 96rpx;
align-items: center;
z-index: 15000;
padding: 0 32rpx;
box-sizing: border-box;
border-radius: var(--td-message-border-radius, var(--td-radius-default, 12rpx));
line-height: 1;
background-color: var(--td-message-bg-color, var(--td-bg-color-container, var(--td-font-white-1, #ffffff)));
box-shadow: var(--td-message-box-shadow, var(--td-shadow-1, 0 1px 10px rgba(0, 0, 0, 0.05), 0 4px 5px rgba(0, 0, 0, 0.08), 0 2px 4px -1px rgba(0, 0, 0, 0.12)));
}
.t-message__text {
display: inline-block;
color: var(--td-message-content-color, var(--td-text-color-primary, var(--td-font-gray-1, rgba(0, 0, 0, 0.9))));
font: var(--td-font-body-medium, 28rpx / 44rpx var(--td-font-family, PingFang SC, Microsoft YaHei, Arial Regular));
}
.t-message__text-wrap {
flex: 1 1 auto;
overflow-x: hidden;
text-overflow: ellipsis;
}
.t-message__text-nowrap {
word-break: keep-all;
white-space: nowrap;
}
.t-message--info {
color: var(--td-message-info-color, var(--td-brand-color, var(--td-primary-color-7, #0052d9)));
}
.t-message--success {
color: var(--td-message-success-color, var(--td-success-color, var(--td-success-color-5, #2ba471)));
}
.t-message--warning {
color: var(--td-message-warning-color, var(--td-warning-color, var(--td-warning-color-5, #e37318)));
}
.t-message--error {
color: var(--td-message-error-color, var(--td-error-color, var(--td-error-color-6, #d54941)));
}
.t-message__icon--left,
.t-message__icon--right {
font-size: 44rpx;
}
.t-message__icon--left:not(:empty) {
margin-right: var(--td-spacer, 16rpx);
}
.t-message__icon--right {
color: var(--td-message-close-icon-color, var(--td-text-color-placeholder, var(--td-font-gray-3, rgba(0, 0, 0, 0.4))));
}
.t-message__icon--right:not(:empty),
.t-message__link {
flex: 0 0 auto;
margin-left: var(--td-spacer, 16rpx);
}
.t-message__fade {
opacity: 0;
transform: translateY(-100%);
}

View File

@@ -0,0 +1,353 @@
<template>
<view>
<view
v-if="visible"
:id="id || classPrefix"
:ref="id || classPrefix"
:class="classPrefix + ' ' + tClass + ' ' + classPrefix + '--' + theme + ' ' + fadeClass"
:style="tools._style([getMessageStyles(zIndex, offset, wrapTop), customStyle])"
:animation="showAnimation"
aria-role="alert"
>
<view :class="classPrefix + '__icon--left'">
<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="true"
:aria-label="_icon.ariaLabel"
:aria-role="_icon.ariaRole"
/>
</block>
</view>
<view
:id="classPrefix + '__text-wrap'"
:ref="classPrefix + '__text-wrap'"
:class="classPrefix + '__text-wrap ' + (marquee ? classPrefix + '__text-nowrap' : '')"
:style="'text-align: ' + align"
>
<view
:id="classPrefix + '__text'"
:ref="classPrefix + '__text'"
:class="classPrefix + '__text ' + tClassContent"
:animation="animation"
>
<block v-if="content">
{{ content }}
</block>
<slot name="content" />
<slot />
</view>
</view>
<t-link
v-if="_link && _link.content"
:t-class="classPrefix + '__link ' + tClassLink"
:custom-style="tools._style([_link.style, _link.customStyle])"
:disabled="_link.disabled || false"
:hover="_link.hover || true"
:theme="_link.theme || 'primary'"
:size="_link.size || 'medium'"
:prefix-icon="_link.prefixIcon || ''"
:suffix-icon="_link.suffixIcon || ''"
:underline="_link.underline || false"
:content="_link.content || ''"
:navigator-props="_link.navigatorProps || null"
@complete="handleLinkClick"
/>
<slot name="link" />
<view
:class="classPrefix + '__icon--right'"
@click="handleClose"
>
<slot name="close-btn" />
<block
v-if="_closeBtn"
name="icon"
>
<t-icon
:custom-style="_closeBtn.style || ''"
:t-class="tClassCloseBtn"
:prefix="_closeBtn.prefix"
:name="_closeBtn.name"
:size="_closeBtn.size"
:color="_closeBtn.color"
:aria-hidden="false"
:aria-label="_closeBtn.ariaLabel || '关闭'"
:aria-role="_closeBtn.ariaRole || 'button'"
/>
</block>
</view>
</view>
</view>
</template>
<script>
import TIcon from '../icon/icon';
import TLink from '../link/link';
import { uniComponent } from '../common/src/index';
import { prefix } from '../common/config';
import { getRect, unitConvert, calcIcon } from '../common/utils';
import { isObject } from '../common/validator';
import tools from '../common/utils.wxs';
import { getMessageStyles } from './computed.js';
import { messageDefaultData } from '../message/config';
const SHOW_DURATION = 400;
const name = `${prefix}-message`;
const THEME_ICON = {
info: 'info-circle-filled',
success: 'check-circle-filled',
warning: 'error-circle-filled',
error: 'error-circle-filled',
};
const rawData = {
prefix,
classPrefix: name,
loop: -1,
animation: [],
showAnimation: [],
wrapTop: -999,
fadeClass: '',
closeTimeoutContext: 0,
nextAnimationContext: 0,
resetAnimation: uni.createAnimation({
duration: 0,
timingFunction: 'linear',
}),
};
export default uniComponent({
name,
options: {
styleIsolation: 'shared',
},
externalClasses: [
`${prefix}-class`,
`${prefix}-class-content`,
`${prefix}-class-icon`,
`${prefix}-class-link`,
`${prefix}-class-close-btn`,
],
components: {
TIcon,
TLink,
},
props: {
},
emits: [
'duration-end',
'close-btn-click',
'link-click',
],
data() {
return {
...rawData,
...messageDefaultData,
animation: [],
tools,
};
},
computed: {
innerMarquee() {
const { marquee } = this;
if (JSON.stringify(marquee) === '{}' || JSON.stringify(marquee) === 'true') {
return {
speed: 50,
loop: -1,
delay: 0,
};
}
return this.marquee;
},
},
watch: {
theme: {
handler(theme) {
this._icon = calcIcon(this.icon, THEME_ICON[theme]);
},
immediate: true,
},
icon: {
handler(icon) {
this._icon = calcIcon(icon, THEME_ICON[this.theme]);
},
immediate: true,
},
link: {
handler(v) {
const _link = isObject(v) ? { ...v } : { content: v };
this._link = _link;
},
immediate: true,
},
closeBtn: {
handler(v) {
this._closeBtn = calcIcon(v, 'close');
},
immediate: true,
},
},
mounted() {
this.memoInitialData();
},
beforeUnMount() {
this.clearMessageAnimation();
},
methods: {
getMessageStyles,
/** 记录组件设置的项目 */
memoInitialData() {
this.initialData = {
...rawData,
...messageDefaultData,
animation: [],
};
},
resetData(cb) {
const { initialData = {} } = this;
Object.keys(initialData).forEach((key) => {
this[key] = initialData[key];
});
setTimeout(cb);
},
/** 检查是否需要开启一个新的动画循环 */
checkAnimation() {
const { innerMarquee } = this;
if (!innerMarquee || innerMarquee.loop === 0) {
return;
}
const speeding = innerMarquee.speed;
if (this.loop > 0) {
this.loop -= 1;
} else if (this.loop === 0) {
// 动画回到初始位置
this.animation = this.resetAnimation
.translateX(0)
.step()
.export();
return;
}
if (this.nextAnimationContext) {
this.clearMessageAnimation();
}
const warpID = `#${name}__text-wrap`;
const nodeID = `#${name}__text`;
Promise.all([getRect(this, nodeID), getRect(this, warpID)]).then(([nodeRect, wrapRect]) => {
this.animation = this.resetAnimation.translateX(wrapRect.width).step()
.export();
setTimeout(() => {
const durationTime = ((nodeRect.width + wrapRect.width) / speeding) * 1000;
const nextAnimation = uni
.createAnimation({
// 默认50px/s
duration: durationTime,
})
.translateX(-nodeRect.width)
.step()
.export();
// 这里就只能用 setTimeout/20, nextTick 没用
// 不用这个的话会出现reset动画没跑完就开始跑这个等的奇怪问题
setTimeout(() => {
this.nextAnimationContext = setTimeout(this.checkAnimation.bind(this), durationTime);
this.animation = nextAnimation;
}, 20);
});
});
},
/** 清理动画循环 */
clearMessageAnimation() {
clearTimeout(this.nextAnimationContext);
this.nextAnimationContext = 0;
},
show(offsetHeight = 0) {
const { duration, innerMarquee, offset, id } = this;
this.visible = true;
this.loop = innerMarquee.loop || this.loop;
this.fadeClass = `${name}__fade`;
this.wrapTop = unitConvert(offset[0]) + offsetHeight;
this.reset();
setTimeout(() => {
this.checkAnimation();
});
if (duration && duration > 0) {
this.closeTimeoutContext = setTimeout(() => {
this.hide();
this.$emit('duration-end', { self: this });
}, duration);
}
const wrapID = id ? `#${id}` : `#${name}`;
setTimeout(() => {
getRect(this, wrapID)
.then((wrapRect) => {
this.height = wrapRect.height;
setTimeout(() => {
this.fadeClass = '';
});
})
.catch(() => {
});
});
},
hide() {
this.reset();
this.fadeClass = `${name}__fade`;
setTimeout(() => {
this.visible = false;
this.animation = [];
}, SHOW_DURATION);
if (typeof this.onHide === 'function') {
this.onHide();
}
},
// 重置定时器
reset() {
if (this.nextAnimationContext) {
this.clearMessageAnimation();
}
clearTimeout(this.closeTimeoutContext);
this.closeTimeoutContext = 0;
},
handleClose() {
this.hide();
this.$emit('close-btn-click');
},
handleLinkClick() {
this.$emit('link-click');
},
},
});
</script>
<style scoped>
@import './message-item.css';
</style>