短通知 + 上部消息

This commit is contained in:
limqhz
2022-12-12 18:05:49 +08:00
parent 44df516337
commit 0efe64d1bb
34 changed files with 1262 additions and 58 deletions

View File

@@ -0,0 +1,74 @@
---
title: Message 消息通知
description: 用于轻量级反馈或提示,不会打断用户操作。
spline: message
isComponent: true
---
<span class="coverages-badge" style="margin-right: 10px"><img src="https://img.shields.io/badge/coverages%3A%20lines-94%25-blue" /></span><span class="coverages-badge" style="margin-right: 10px"><img src="https://img.shields.io/badge/coverages%3A%20functions-89%25-blue" /></span><span class="coverages-badge" style="margin-right: 10px"><img src="https://img.shields.io/badge/coverages%3A%20statements-94%25-blue" /></span><span class="coverages-badge" style="margin-right: 10px"><img src="https://img.shields.io/badge/coverages%3A%20branches-86%25-blue" /></span>
## 引入
全局引入,在 miniprogram 根目录下的`app.json`中配置,局部引入,在需要引入的页面或组件的`index.json`中配置。
```json
"usingComponents": {
"t-message": "tdesign-miniprogram/message/message"
}
```
### 引入 API
若以 API 形式调用 Message则需在页面 `page.js` 中引入组件 API
```js
import Message from 'tdesign-miniprogram/message/index';
```
## 代码演示
### 基础消息通知
弹窗内容为纯文本、标题和副标题、带输入框,用 API `Message.info` 方法调用反馈类对话框。
{{ base }}
### 不同状态的消息通知
消息通知类型为普通info、警示warning、成功success、错误error
{{ status-message }}
### 自定义导航栏
当设置了 `navigationStyle = custom`,可以通过 offetset 来调整显示位置:
{{ custom-navigation }}
## API
### Message Props
| 名称 | 类型 | 默认值 | 说明 | 必传|
| -- | -- | -- | -- | -- |
| action | String / Slot | - | 操作 | N |
| align | String | left | 文本对齐方式。可选项left/center | N |
| close-btn | String / Boolean / Slot | undefined | 关闭按钮,可以自定义。值为 true 显示默认关闭按钮,值为 false 不显示关闭按钮。值类型为 string 则直接显示值,如:“关闭”。也可以完全自定义按钮 | N |
| content | String / Slot | - | 用于自定义消息弹出内容 | N |
| duration | Number | 3000 | 消息内置计时器,计时到达时会触发 duration-end 事件。单位:毫秒。值为 0 则表示没有计时器。 | N |
| external-classes | Array | - | 样式类名,分别用于设置 组件外层、消息内容、左侧图标、操作按钮、关闭按钮等元素类名。`['t-class', 't-class-content', 't-class-icon', 't-class-action', 't-class-close-btn']` | N |
| icon | String / Boolean / Slot | true | 消息提醒前面的图标。值为 true 则根据 theme 显示对应的图标,值为 false 则不显示图标。值为 'info' 或 'bell' 则显示组件内置图标。也可以完全自定义图标节点。TS 类型:`boolean | 'info' | 'bell'` | N |
| marquee | Boolean / Object | false | 跑马灯效果。speed 指速度控制loop 指循环播放次数,值为 -1 表示循环播放,值为 0 表示不循环播放delay 表示延迟多久开始播放。TS 类型:`boolean | DrawMarquee`。[详细类型定义](https://github.com/Tencent/tdesign-miniprogram/tree/develop/src/message/type.ts) | N |
| offset | Array | - | 相对于 placement 的偏移量,示例:[-10, 20] 或 ['10rpx', '8rpx']。TS 类型:`Array<string | number>` | N |
| theme | String | info | 消息组件风格。可选项info/success/warning/error。TS 类型:`MessageThemeList`。[详细类型定义](https://github.com/Tencent/tdesign-miniprogram/tree/develop/src/message/type.ts) | N |
| visible | Boolean | false | 是否显示,隐藏时默认销毁组件 | N |
| z-index | Number | 15000 | 组件层级,样式默认为 15000 | N |
### Message Events
| 名称 | 参数 | 描述 |
| ---------------- | ---- | ---------------------------------------- |
| action-btn-click | - | 当操作按钮存在时,用户点击操作按钮时触发 |
| close-btn-click | - | 当关闭按钮存在时,用户点击关闭按钮触发 |
| duration-end | - | 计时结束后触发 |

100
components/message/api.md Normal file
View File

@@ -0,0 +1,100 @@
# Message
消息组件
### 特性及兼容性
## 引入
### 引入组件
`app.json``page.json` 中引入组件:
```json
"usingComponents": {
"t-message": "tdesign-miniprogram/message/message"
}
```
### 引入 API
若以 API 形式调用 Message则需在页面 `page.js` 中引入组件 API
```js
import Message from 'tdesign-miniprogram/message/index';
```
## 用法
### 消息提示
用 API `Message.info` 方法调用反馈类对话框
```html
<!-- page.wxml -->
<t-message id="t-message" />
```
```js
// page.js
Message.info({
content: '消息内容',
});
```
## API
### `<Message>` 组件
组件路径:`tdesign-miniprogram/message/message`
#### Props
| 属性 | 值类型 | 默认值 | 说明 |
| --------------- | ----------------------- | --------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------- | --------------- |
| visible | Boolean | false | 是否显示,隐藏时默认销毁组件 | N |
| content | String / Slot | - | 用于自定义消息弹出内容。 | N |
| theme | String | info | 消息组件风格。可选值info/success/warning/error。 | N |
| duration | Number | 3000 | 消息内置计时器,计时到达时会触发 duration-end 事件。单位:毫秒。值为 0 则表示没有计时器。 | N |
| offset | `Object` | - | 偏移量,默认[0,0] |
| closeBtn | String / Boolean /Slot | undefined | 关闭按钮,可以自定义。值为 true 显示默认关闭按钮,值为 false 不显示关闭按钮。值类型为 string 则直接显示值,如:“关闭”。也可以完全自定义按钮。 | N |
| externalClasses | Array | - | 样式类名,分别用于设置 组件外层、消息内容、左侧图标、操作按钮、关闭按钮等元素类名。`['t-class', 't-class-content', 't-class-icon', 't-class-action', 't-class-close-btn']` | N |
| icon | String / Boolean / Slot | true | 消息提醒前面的图标。值为 true 则根据 theme 显示对应的图标,值为 false 则不显示图标。值为 'info' 或 'bell' 则显示组件内置图标。也可以完全自定义图标节点。TS 类型:`boolean | 'warning_fill' | 'sound_fill'` N |
| marquee | Object | - | 跑马灯效果,delay 动画延迟时间 (s);speed 滚动速率 (px/s)。N |
| zIndex | Number | - | 元素层级,样式默认为 5000 | N |
### Event
| 事件名 | 说明 | 参数 |
| -------------- | ---------------------------------------- | ---------------- |
| closeBtnClick | 点击关闭 icon 时触发 | - |
| actionBtnClick | 点击按钮时触发, 可通过 self 拿到组件实例 | `{ self: this }` |
### Slot
| 事件名 | 说明 |
| -------- | ------------------- |
| icon | 左侧 icon 处 |
| action | 右侧操作按钮。 N |
| closeBtn | 按钮(关闭 icon处 |
### 外部样式
| class | 说明 |
| ----------------- | ----------- |
| t-class | 根节点 |
| t-class-content | 文本内容 |
| t-class-icon | 左侧 icon |
| t-class-action | 右侧 button |
| t-class-close-btn | 右侧 icon |
### 编程式调用
| 方法 | type | 返回值 | 说明 |
| -------------------------- | ------- | ------ | ------------------------------------------------------------------------------------------------------------ |
| Message.info(`options`) | info | `void` | 弹出消息,参数参考 Props,额外可指定`{context: WechatMiniprogram.Component.TrivialInstance,selector: string}` |
| Message.success(`options`) | success | `void` | 弹出消息,参数参考 Props,额外可指定`{context: WechatMiniprogram.Component.TrivialInstance,selector: string}` |
| Message.warning(`options`) | warning | `void` | 弹出消息,参数参考 Props,额外可指定`{context: WechatMiniprogram.Component.TrivialInstance,selector: string}` |
| Message.error(`options`) | error | `void` | 弹出消息,参数参考 Props,额外可指定`{context: WechatMiniprogram.Component.TrivialInstance,selector: string}` |

17
components/message/index.d.ts vendored Normal file
View File

@@ -0,0 +1,17 @@
/// <reference types="miniprogram-api-typings" />
/// <reference types="miniprogram-api-typings" />
/// <reference types="miniprogram-api-typings" />
import { MessageProps } from './message.interface';
declare type Context = WechatMiniprogram.Page.TrivialInstance | WechatMiniprogram.Component.TrivialInstance;
interface MessageActionOptionsType extends Optional<MessageProps> {
context?: Context;
selector?: string;
}
declare const _default: {
info(options: MessageActionOptionsType): WechatMiniprogram.Component.TrivialInstance;
success(options: MessageActionOptionsType): WechatMiniprogram.Component.TrivialInstance;
warning(options: MessageActionOptionsType): WechatMiniprogram.Component.TrivialInstance;
error(options: MessageActionOptionsType): WechatMiniprogram.Component.TrivialInstance;
hide(options: MessageActionOptionsType): void;
};
export default _default;

View File

@@ -0,0 +1,46 @@
var __rest = (this && this.__rest) || function (s, e) {
var t = {};
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
t[p] = s[p];
if (s != null && typeof Object.getOwnPropertySymbols === "function")
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
t[p[i]] = s[p[i]];
}
return t;
};
import { MessageType } from './message.interface';
import { getInstance } from '../common/utils';
const showMessage = function (options, theme = MessageType.info) {
const { context, selector = '#t-message' } = options, otherOptions = __rest(options, ["context", "selector"]);
const instance = getInstance(context, selector);
if (instance) {
instance.resetData(() => {
instance.setData(Object.assign({ 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' } = Object.assign({}, options);
const instance = getInstance(context, selector);
if (!instance) {
return;
}
instance.hide();
},
};

38
components/message/message.d.ts vendored Normal file
View File

@@ -0,0 +1,38 @@
/// <reference types="miniprogram-api-typings" />
import { SuperComponent, ComponentsOptionsType } from '../common/src/index';
import { MessageProps } from './message.interface';
export default class Message extends SuperComponent {
externalClasses: string[];
options: ComponentsOptionsType;
properties: MessageProps;
data: {
prefix: string;
classPrefix: string;
visible: boolean;
loop: number;
animation: any[];
showAnimation: any[];
iconName: string;
wrapTop: number;
};
observers: {
marquee(val: any): void;
};
closeTimeoutContext: number;
nextAnimationContext: number;
resetAnimation: WechatMiniprogram.Animation;
showAnimation: WechatMiniprogram.AnimationExportResult;
hideAnimation: WechatMiniprogram.AnimationExportResult;
ready(): void;
memoInitalData(): void;
resetData(cb: () => void): void;
detached(): void;
setIcon(icon?: string | boolean): void;
checkAnimation(): void;
clearMessageAnimation(): void;
show(): void;
hide(): void;
reset(): void;
handleClose(): void;
handleBtnClick(): void;
}

View File

@@ -0,0 +1,24 @@
export declare enum MessageType {
info = "info",
success = "success",
warning = "warning",
error = "error"
}
export interface MessageMarquee {
speed?: number;
loop?: number;
delay?: number;
}
export interface MessageProps {
visible?: boolean;
content: string;
align?: string;
theme?: MessageType;
icon?: boolean | string;
closeBtn?: boolean;
action?: string;
marquee?: MessageMarquee;
offset?: object;
duration?: number;
zIndex?: number;
}

View File

@@ -0,0 +1,7 @@
export var MessageType;
(function (MessageType) {
MessageType["info"] = "info";
MessageType["success"] = "success";
MessageType["warning"] = "warning";
MessageType["error"] = "error";
})(MessageType || (MessageType = {}));

View File

@@ -0,0 +1,180 @@
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
import { SuperComponent, wxComponent } from '../common/src/index';
import config from '../common/config';
import props from './props';
import { getRect } from '../common/utils';
const { prefix } = config;
const name = `${prefix}-message`;
const SHOW_DURATION = 500;
let Message = class Message extends SuperComponent {
constructor() {
super(...arguments);
this.externalClasses = ['t-class', 't-class-content', 't-class-icon', 't-class-action', 't-class-close-btn'];
this.options = {
styleIsolation: 'apply-shared',
multipleSlots: true,
};
this.properties = Object.assign({}, props);
this.data = {
prefix,
classPrefix: name,
visible: false,
loop: -1,
animation: [],
showAnimation: [],
iconName: '',
wrapTop: -92,
};
this.observers = {
marquee(val) {
if (JSON.stringify(val) === '{}') {
this.setData({
marquee: {
speed: 50,
loop: -1,
delay: 5000,
},
});
}
},
};
this.closeTimeoutContext = 0;
this.nextAnimationContext = 0;
this.resetAnimation = wx.createAnimation({
duration: 0,
timingFunction: 'linear',
});
this.showAnimation = wx.createAnimation({ duration: SHOW_DURATION, timingFunction: 'ease' }).translateY(0).step().export();
this.hideAnimation = wx
.createAnimation({ duration: SHOW_DURATION, timingFunction: 'ease' })
.translateY(this.data.wrapTop)
.step()
.export();
}
ready() {
this.memoInitalData();
this.setIcon();
}
memoInitalData() {
this.initalData = Object.assign(Object.assign({}, this.properties), this.data);
}
resetData(cb) {
this.setData(Object.assign({}, this.initalData), cb);
}
detached() {
this.clearMessageAnimation();
}
setIcon(icon = this.properties.icon) {
if (!icon) {
this.setData({ iconName: '' });
return;
}
if (typeof icon === 'string') {
this.setData({
iconName: `${icon}`,
});
return;
}
if (icon) {
let nextValue = 'notification';
const { theme } = this.properties;
const themeMessage = {
info: 'error-circle',
success: 'check-circle',
warning: 'error-circle',
error: 'error-circle',
};
nextValue = themeMessage[theme];
this.setData({ iconName: nextValue });
}
}
checkAnimation() {
if (!this.properties.marquee) {
return;
}
const speeding = this.properties.marquee.speed;
if (this.data.loop > 0) {
this.data.loop -= 1;
}
else if (this.data.loop === 0) {
this.setData({ 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.setData({
animation: this.resetAnimation.translateX(wrapRect.width).step().export(),
}, () => {
const durationTime = ((nodeRect.width + wrapRect.width) / speeding) * 1000;
const nextAnimation = wx
.createAnimation({
duration: durationTime,
})
.translateX(-nodeRect.width)
.step()
.export();
setTimeout(() => {
this.nextAnimationContext = setTimeout(this.checkAnimation.bind(this), durationTime);
this.setData({ animation: nextAnimation });
}, 20);
});
});
}
clearMessageAnimation() {
clearTimeout(this.nextAnimationContext);
this.nextAnimationContext = 0;
}
show() {
const { duration, icon, marquee } = this.properties;
this.setData({ visible: true, loop: marquee.loop });
this.reset();
this.setIcon(icon);
this.checkAnimation();
if (duration && duration > 0) {
this.closeTimeoutContext = setTimeout(() => {
this.hide();
this.triggerEvent('durationEnd', { self: this });
}, duration);
}
const wrapID = `#${name}`;
getRect(this, wrapID).then((wrapRect) => {
this.setData({ wrapTop: -wrapRect.height }, () => {
this.setData({ showAnimation: this.showAnimation });
});
});
}
hide() {
this.reset();
this.setData({ showAnimation: this.hideAnimation });
setTimeout(() => {
this.setData({ visible: false, animation: [] });
}, SHOW_DURATION);
}
reset() {
if (this.nextAnimationContext) {
this.clearMessageAnimation();
}
clearTimeout(this.closeTimeoutContext);
this.closeTimeoutContext = 0;
}
handleClose() {
this.hide();
this.triggerEvent('closeBtnClick');
}
handleBtnClick() {
this.triggerEvent('actionBtnClick', { self: this });
}
};
Message = __decorate([
wxComponent()
], Message);
export default Message;

View File

@@ -0,0 +1,7 @@
{
"component": true,
"usingComponents": {
"t-icon": "../icon/icon",
"t-button": "../button/button"
}
}

View File

@@ -0,0 +1,51 @@
<wxs src="./message.wxs" module="this"></wxs>
<block wx:if="{{visible}}">
<view
class="{{classPrefix}} {{prefix}}-class {{classPrefix}}--{{theme}}"
style="z-index: {{zIndex}};{{this.getMessageStylesOffset({ offset })}};transform: translateY({{wrapTop}}px)"
animation="{{showAnimation}}"
id="{{classPrefix}}"
>
<t-icon
wx:if="{{iconName}}"
name="{{iconName}}"
class="{{classPrefix}}__icon--left {{prefix}}-class-icon"
size="44rpx"
/>
<slot name="icon" />
<view
class="{{classPrefix}}__text-wrap {{marquee ? '{{classPrefix}}__text-nowrap' : ''}}"
style="text-align: {{align}}"
id="{{classPrefix}}__text-wrap"
>
<view
class="{{classPrefix}}__text {{prefix}}-class-content"
id="{{classPrefix}}__text"
animation="{{animation}}"
>
<view wx:if="{{content}}">{{content}}</view>
<slot name="content"></slot>
</view>
</view>
<t-button
wx:if="{{action}}"
t-class="{{classPrefix}}__btn--right {{prefix}}-class-action"
size="small"
bind:tap="handleBtnClick"
>{{action}}</t-button
>
<slot name="action" />
<t-icon
wx:if="{{closeBtn}}"
class="{{classPrefix}}__icon--right {{prefix}}-class-close-btn"
name="close"
size="20px"
bind:tap="handleClose"
/>
<slot name="closeBtn" />
</view>
</block>

View File

@@ -0,0 +1,16 @@
var changeNumToStr = function (arr) {
return arr.map(function (item) {
return typeof item === 'number' ? item + 'rpx' : item;
});
};
var getMessageStylesOffset = function (props) {
var arr = changeNumToStr(props.offset);
var styleOffset = '';
styleOffset += 'top:' + arr[0] + ';';
styleOffset += 'right:' + arr[1] + ';';
styleOffset += 'left:' + arr[1] + ';';
return styleOffset;
};
module.exports.getMessageStylesOffset = getMessageStylesOffset;

View File

@@ -0,0 +1,89 @@
.t-float-left {
float: left;
}
.t-float-right {
float: right;
}
@keyframes tdesign-fade-out {
from {
opacity: 1;
}
to {
opacity: 0;
}
}
.hotspot-expanded.relative {
position: relative;
}
.hotspot-expanded::after {
content: '';
display: block;
position: absolute;
left: 0;
top: 0;
right: 0;
bottom: 0;
transform: scale(1.5);
}
.t-message {
position: fixed;
top: 0;
left: 0;
right: 0;
display: flex;
justify-content: flex-start;
align-items: center;
z-index: 15000;
color: #0052d9;
padding: 24rpx 32rpx;
box-sizing: border-box;
border-radius: 8rpx;
font-size: 28rpx;
line-height: 1;
background: #ffffff;
box-shadow: 0px 1px 4px 0px rgba(0, 0, 0, 0.2);
}
.t-message__text {
display: inline-block;
}
.t-message__text-wrap {
flex: 1 1 auto;
overflow-x: hidden;
text-overflow: ellipsis;
line-height: 44rpx;
}
.t-message__text-nowrap {
word-break: keep-all;
white-space: nowrap;
}
.t-message--info {
color: #0052d9;
}
.t-message--success {
color: #00a870;
}
.t-message--warning {
color: #ed7b2f;
}
.t-message--error {
color: #e34d59;
}
.t-message .t-message__icon--left {
margin-right: 16rpx;
}
.t-message .t-message__icon--right,
.t-message .t-message__btn--right {
flex: 0 0 auto;
margin-left: 16rpx;
}
.t-message .t-message__btn--right {
font-size: inherit;
line-height: inherit;
height: 44rpx;
line-height: 44rpx;
border-radius: 8rpx;
border-color: inherit;
color: inherit;
min-height: 0;
background: transparent;
}

3
components/message/props.d.ts vendored Normal file
View File

@@ -0,0 +1,3 @@
import { TdMessageProps } from './type';
declare const props: TdMessageProps;
export default props;

View File

@@ -0,0 +1,49 @@
const props = {
action: {
type: String,
},
align: {
type: String,
value: 'left',
},
closeBtn: {
type: String,
optionalTypes: [Boolean],
value: undefined,
},
content: {
type: String,
},
duration: {
type: Number,
value: 3000,
},
externalClasses: {
type: Array,
},
icon: {
type: String,
optionalTypes: [Boolean],
value: true,
},
marquee: {
type: null,
value: false,
},
offset: {
type: Array,
},
theme: {
type: String,
value: 'info',
},
visible: {
type: Boolean,
value: false,
},
zIndex: {
type: Number,
value: 15000,
},
};
export default props;

70
components/message/type.d.ts vendored Normal file
View File

@@ -0,0 +1,70 @@
export interface TdMessageProps {
action?: {
type: StringConstructor;
value?: string;
required?: boolean;
};
align?: {
type: StringConstructor;
value?: 'left' | 'center';
required?: boolean;
};
closeBtn?: {
type: StringConstructor;
optionalTypes: Array<BooleanConstructor>;
value?: string | boolean;
required?: boolean;
};
content?: {
type: StringConstructor;
value?: string;
required?: boolean;
};
duration?: {
type: NumberConstructor;
value?: number;
required?: boolean;
};
externalClasses?: {
type: ArrayConstructor;
value?: ['t-class', 't-class-content', 't-class-icon', 't-class-action', 't-class-close-btn'];
required?: boolean;
};
icon?: {
type: StringConstructor;
optionalTypes: Array<BooleanConstructor>;
value?: boolean | 'info' | 'bell';
required?: boolean;
};
marquee?: {
type: null;
value?: DrawMarquee | boolean;
required?: boolean;
};
offset?: {
type: ArrayConstructor;
value?: Array<string | number>;
required?: boolean;
};
theme?: {
type: StringConstructor;
value?: MessageThemeList;
required?: boolean;
};
visible?: {
type: BooleanConstructor;
value?: boolean;
required?: boolean;
};
zIndex?: {
type: NumberConstructor;
value?: number;
required?: boolean;
};
}
export interface DrawMarquee {
speed?: number;
loop?: number;
delay?: number;
}
export declare type MessageThemeList = 'info' | 'success' | 'warning' | 'error';

View File

@@ -0,0 +1 @@
export {};