mirror of
https://github.com/ant-design/ant-design-mini.git
synced 2024-10-23 08:44:21 +08:00
feat: 重构 noticebar (#1062)
This commit is contained in:
parent
d7e466f37b
commit
6444cba7cb
@ -6,3 +6,4 @@ src/.umi-production/**
|
||||
dist/**
|
||||
docs-dist/**
|
||||
node_modules
|
||||
compiled/
|
||||
|
44
.eslintrc
44
.eslintrc
@ -2,25 +2,25 @@
|
||||
"env": {
|
||||
"node": true
|
||||
},
|
||||
"globals": {
|
||||
"my": true,
|
||||
"App": true,
|
||||
"Component": true,
|
||||
"Page": true
|
||||
},
|
||||
"parser": "@typescript-eslint/parser",
|
||||
"plugins": [
|
||||
"@typescript-eslint"
|
||||
],
|
||||
"extends": [
|
||||
"eslint:recommended",
|
||||
"plugin:@typescript-eslint/eslint-recommended",
|
||||
"plugin:@typescript-eslint/recommended"
|
||||
],
|
||||
"rules": {
|
||||
"no-console": "warn",
|
||||
"@typescript-eslint/no-var-requires": "warn",
|
||||
"@typescript-eslint/no-empty-function": "warn",
|
||||
"@typescript-eslint/no-empty-interface": "warn"
|
||||
}
|
||||
}
|
||||
"globals": {
|
||||
"wx": true,
|
||||
"my": true,
|
||||
"App": true,
|
||||
"Component": true,
|
||||
"Page": true
|
||||
},
|
||||
"parser": "@typescript-eslint/parser",
|
||||
"plugins": ["@typescript-eslint"],
|
||||
"extends": [
|
||||
"eslint:recommended",
|
||||
"plugin:@typescript-eslint/eslint-recommended",
|
||||
"plugin:@typescript-eslint/recommended"
|
||||
],
|
||||
"rules": {
|
||||
"no-console": "warn",
|
||||
"@typescript-eslint/no-var-requires": "warn",
|
||||
"@typescript-eslint/no-empty-function": "warn",
|
||||
"@typescript-eslint/no-empty-interface": "warn",
|
||||
"@typescript-eslint/ban-ts-comment": "off"
|
||||
}
|
||||
}
|
||||
|
@ -36,6 +36,22 @@
|
||||
</block>
|
||||
</container>
|
||||
|
||||
<container title="可滚动通告栏(不循环)">
|
||||
<block
|
||||
a:for="{{ typeList }}"
|
||||
a:for-index="index"
|
||||
a:for-item="item">
|
||||
<notice
|
||||
type="{{ item }}"
|
||||
style="margin-bottom: 8px"
|
||||
enableMarquee="{{ true }}"
|
||||
onTap="handleTapLink"
|
||||
mode="link">
|
||||
文本溢出时,开启循环滚动。文字不够继续添加文字凑数。
|
||||
</notice>
|
||||
</block>
|
||||
</container>
|
||||
|
||||
<container title="自定义通告栏">
|
||||
<notice style="margin-bottom: 8px">
|
||||
不展示图标
|
||||
|
@ -3,20 +3,17 @@ Page({
|
||||
typeList: ['default', 'error', 'info', 'primary'],
|
||||
},
|
||||
handleTapAction() {
|
||||
my.showToast({
|
||||
content: `点击按钮`,
|
||||
duration: 1000,
|
||||
});
|
||||
this.showToast('点击按钮');
|
||||
},
|
||||
handleTapLink() {
|
||||
my.showToast({
|
||||
content: 'link 类型被点击了',
|
||||
duration: 1000,
|
||||
});
|
||||
this.showToast('link 类型被点击了');
|
||||
},
|
||||
handleClose() {
|
||||
this.showToast('点击关闭');
|
||||
},
|
||||
showToast(content) {
|
||||
my.showToast({
|
||||
content: `点击关闭`,
|
||||
content: content,
|
||||
duration: 1000,
|
||||
});
|
||||
},
|
||||
|
@ -16,9 +16,10 @@
|
||||
type="SoundOutline" />
|
||||
</view>
|
||||
</slot>
|
||||
<view class="ant-notice-bar-content ant-notice-bar-content-{{ $id }}">
|
||||
<view
|
||||
class="ant-notice-bar-content ant-notice-bar-content{{ $id ? '-' + $id : '' }}">
|
||||
<view
|
||||
class="ant-notice-bar-marquee ant-notice-bar-marquee-{{ $id }}"
|
||||
class="ant-notice-bar-marquee ant-notice-bar-marquee{{ $id ? '-' + $id : '' }}"
|
||||
style="{{ marqueeStyle }} display: {{ enableMarquee ? 'inline-block' : 'block' }}"
|
||||
onTransitionEnd="onTransitionEnd">
|
||||
<slot />
|
||||
|
@ -1,155 +1,126 @@
|
||||
import { NoticeBarDefaultProps } from './props';
|
||||
import { log } from '../_util/console';
|
||||
import { IBoundingClientRect } from '../_util/base';
|
||||
import {
|
||||
useState,
|
||||
useEffect,
|
||||
useEvent,
|
||||
usePageShow,
|
||||
} from 'functional-mini/component';
|
||||
import '../_util/assert-component2';
|
||||
import { IBoundingClientRect } from '../_util/base';
|
||||
import { mountComponent } from '../_util/component';
|
||||
import { useComponentEvent } from '../_util/hooks/useComponentEvent';
|
||||
import { useInstanceBoundingClientRect } from '../_util/hooks/useInstanceBoundingClientRect';
|
||||
import { INoticeBarProps, NoticeBarFunctionalProps } from './props';
|
||||
import { useEvent as useStableCallback } from '../_util/hooks/useEvent';
|
||||
|
||||
Component({
|
||||
props: NoticeBarDefaultProps,
|
||||
data: {
|
||||
show: true,
|
||||
marqueeStyle: '',
|
||||
animatedWidth: 0,
|
||||
overflowWidth: 0,
|
||||
duration: 0,
|
||||
viewWidth: 0,
|
||||
},
|
||||
didMount() {
|
||||
const { enableMarquee } = this.props;
|
||||
this.showError();
|
||||
const NoticeBar = (props: INoticeBarProps) => {
|
||||
const [marqueeStyle, setMarqueeStyle] = useState('');
|
||||
const [show, setShow] = useState(true);
|
||||
|
||||
if (enableMarquee) {
|
||||
this.measureText(this.startMarquee.bind(this));
|
||||
const { triggerEventOnly } = useComponentEvent(props);
|
||||
const startMarquee = useStableCallback((state) => {
|
||||
const { loop } = props;
|
||||
const leading = 500;
|
||||
const { duration, overflowWidth, viewWidth } = state;
|
||||
let marqueeScrollWidth = overflowWidth;
|
||||
if (loop) {
|
||||
marqueeScrollWidth = overflowWidth + viewWidth;
|
||||
}
|
||||
},
|
||||
const newMarqueeStyle = `transform: translate3d(${-marqueeScrollWidth}px, 0, 0); transition: ${duration}s all linear ${
|
||||
typeof leading === 'number' ? `${leading / 1000}s` : '0s'
|
||||
};`;
|
||||
setMarqueeStyle(newMarqueeStyle);
|
||||
});
|
||||
|
||||
didUpdate() {
|
||||
const { enableMarquee } = this.props;
|
||||
this.showError();
|
||||
// 这里更新处理的原因是防止notice内容在动画过程中发生改变。
|
||||
if (enableMarquee) {
|
||||
this.measureText(this.startMarquee.bind(this));
|
||||
}
|
||||
},
|
||||
const { getBoundingClientRectWithId } = useInstanceBoundingClientRect();
|
||||
function measureText(callback) {
|
||||
const fps = 40;
|
||||
const { loop } = props;
|
||||
// 计算文本所占据的宽度,计算需要滚动的宽度
|
||||
setTimeout(async () => {
|
||||
const marqueeSize: IBoundingClientRect | null =
|
||||
await getBoundingClientRectWithId('.ant-notice-bar-marquee');
|
||||
const contentSize: IBoundingClientRect | null =
|
||||
await getBoundingClientRectWithId('.ant-notice-bar-content');
|
||||
const overflowWidth =
|
||||
(marqueeSize && contentSize && marqueeSize.width - contentSize.width) ||
|
||||
0;
|
||||
|
||||
pageEvents: {
|
||||
onShow() {
|
||||
this.resetState();
|
||||
},
|
||||
},
|
||||
|
||||
methods: {
|
||||
resetState() {
|
||||
if (this.props.enableMarquee) {
|
||||
this.setData(
|
||||
{
|
||||
marqueeStyle: '',
|
||||
animatedWidth: 0,
|
||||
overflowWidth: 0,
|
||||
duration: 0,
|
||||
viewWidth: 0,
|
||||
},
|
||||
() => {
|
||||
this.resetMarquee();
|
||||
this.measureText(this.startMarquee.bind(this));
|
||||
}
|
||||
);
|
||||
}
|
||||
},
|
||||
showError() {
|
||||
const { actions } = this.props;
|
||||
if (!Array.isArray(actions) && typeof actions !== 'undefined') {
|
||||
log.warn(
|
||||
'NoticeBar',
|
||||
`当前定义的 actions 的类型为 ${typeof actions},不符合属性定义,应该为数组,如:actions="{{['值', '值']}}`
|
||||
);
|
||||
}
|
||||
},
|
||||
onTap() {
|
||||
const { mode, onTap } = this.props;
|
||||
if (mode === 'link' && typeof onTap === 'function') {
|
||||
return onTap();
|
||||
}
|
||||
if (mode === 'closeable' && typeof onTap === 'function') {
|
||||
this.setData({
|
||||
show: false,
|
||||
});
|
||||
return onTap();
|
||||
}
|
||||
},
|
||||
// 文本滚动的计算
|
||||
resetMarquee() {
|
||||
const { loop } = this.props;
|
||||
const { viewWidth } = this.data;
|
||||
let showMarqueeWidth = '0px';
|
||||
if (loop) {
|
||||
showMarqueeWidth = `${viewWidth}px`;
|
||||
}
|
||||
const marqueeStyle = `transform: translate3d(${showMarqueeWidth}, 0, 0); transition: 0s all linear;`;
|
||||
this.setData({
|
||||
marqueeStyle,
|
||||
});
|
||||
},
|
||||
startMarquee() {
|
||||
const { loop } = this.props;
|
||||
const leading = 500;
|
||||
const { duration, overflowWidth, viewWidth } = this.data;
|
||||
const viewWidth = contentSize?.width || 0;
|
||||
let marqueeScrollWidth = overflowWidth;
|
||||
if (loop) {
|
||||
marqueeScrollWidth = overflowWidth + viewWidth;
|
||||
}
|
||||
const marqueeStyle = `transform: translate3d(${-marqueeScrollWidth}px, 0, 0); transition: ${duration}s all linear ${
|
||||
typeof leading === 'number' ? `${leading / 1000}s` : '0s'
|
||||
};`;
|
||||
if (this.data.marqueeStyle !== marqueeStyle) {
|
||||
this.setData({
|
||||
marqueeStyle,
|
||||
if (overflowWidth > 0) {
|
||||
callback({
|
||||
overflowWidth,
|
||||
viewWidth,
|
||||
duration: marqueeScrollWidth / fps,
|
||||
});
|
||||
}
|
||||
},
|
||||
onTransitionEnd() {
|
||||
const { loop } = this.props;
|
||||
const trailing = 200;
|
||||
if (loop) {
|
||||
setTimeout(() => {
|
||||
this.resetMarquee();
|
||||
this.measureText(this.startMarquee.bind(this));
|
||||
}, trailing);
|
||||
}
|
||||
},
|
||||
measureText(callback) {
|
||||
const fps = 40;
|
||||
const { loop } = this.props;
|
||||
// 计算文本所占据的宽度,计算需要滚动的宽度
|
||||
}, 0);
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
const { enableMarquee } = props;
|
||||
if (enableMarquee) {
|
||||
measureText(startMarquee);
|
||||
}
|
||||
});
|
||||
|
||||
function resetMarquee(state) {
|
||||
const { loop } = props;
|
||||
const { viewWidth } = state;
|
||||
let showMarqueeWidth = '0px';
|
||||
if (loop) {
|
||||
showMarqueeWidth = `${viewWidth}px`;
|
||||
}
|
||||
const marqueeStyle = `transform: translate3d(${showMarqueeWidth}, 0, 0); transition: 0s all linear;`;
|
||||
setMarqueeStyle(marqueeStyle);
|
||||
}
|
||||
|
||||
useEvent('onTransitionEnd', () => {
|
||||
const { loop } = props;
|
||||
const trailing = 200;
|
||||
if (loop) {
|
||||
setTimeout(() => {
|
||||
my.createSelectorQuery()
|
||||
.select(`.ant-notice-bar-marquee-${this.$id}`)
|
||||
.boundingClientRect()
|
||||
.select(`.ant-notice-bar-content-${this.$id}`)
|
||||
.boundingClientRect()
|
||||
.exec((ret) => {
|
||||
// eslint-disable-next-line max-len
|
||||
const overflowWidth =
|
||||
(ret &&
|
||||
ret[0] &&
|
||||
ret[1] &&
|
||||
(<IBoundingClientRect>ret[0]).width -
|
||||
(<IBoundingClientRect>ret[1]).width) ||
|
||||
0;
|
||||
const viewWidth = (<IBoundingClientRect>ret[1])?.width || 0;
|
||||
let marqueeScrollWidth = overflowWidth;
|
||||
if (loop) {
|
||||
marqueeScrollWidth = overflowWidth + viewWidth;
|
||||
}
|
||||
if (overflowWidth > 0) {
|
||||
this.setData({
|
||||
overflowWidth,
|
||||
viewWidth,
|
||||
duration: marqueeScrollWidth / fps,
|
||||
});
|
||||
callback();
|
||||
}
|
||||
});
|
||||
}, 0);
|
||||
},
|
||||
},
|
||||
});
|
||||
measureText((state) => {
|
||||
resetMarquee(state);
|
||||
startMarquee(state);
|
||||
});
|
||||
}, trailing);
|
||||
}
|
||||
});
|
||||
|
||||
useEvent('onTap', () => {
|
||||
const { mode } = props;
|
||||
if (mode === 'link') {
|
||||
triggerEventOnly('tap');
|
||||
}
|
||||
if (mode === 'closeable') {
|
||||
if (typeof props.onTap !== 'function') {
|
||||
return;
|
||||
}
|
||||
setShow(false);
|
||||
triggerEventOnly('tap');
|
||||
}
|
||||
});
|
||||
|
||||
usePageShow(() => {
|
||||
if (props.enableMarquee) {
|
||||
setMarqueeStyle('');
|
||||
resetMarquee({
|
||||
overflowWidth: 0,
|
||||
duration: 0,
|
||||
viewWidth: 0,
|
||||
});
|
||||
measureText(startMarquee);
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
marqueeStyle,
|
||||
show,
|
||||
};
|
||||
};
|
||||
|
||||
mountComponent(NoticeBar, NoticeBarFunctionalProps);
|
||||
|
@ -7,10 +7,6 @@ import { IBaseProps } from '../_util/base';
|
||||
*/
|
||||
|
||||
export interface INoticeBarProps extends IBaseProps {
|
||||
/**
|
||||
* 是否展示
|
||||
*/
|
||||
show?: boolean;
|
||||
/**
|
||||
* @description 消息的展示
|
||||
*/
|
||||
@ -24,16 +20,7 @@ export interface INoticeBarProps extends IBaseProps {
|
||||
/**
|
||||
* @description 通告类型,link 表示连接,整行可点;closeable 表示点击 x 可以关闭;不填时表示你右侧没有图标
|
||||
*/
|
||||
mode: 'link' | 'closeable';
|
||||
/**
|
||||
* @description 行动点,最多两个行动点,action和mode可以同时搭配使用
|
||||
*/
|
||||
|
||||
actions: string[];
|
||||
/**
|
||||
* @description 滚动样式
|
||||
*/
|
||||
marqueeStyle?: boolean;
|
||||
mode?: 'link' | 'closeable';
|
||||
/**
|
||||
* @description 是否开启滚动动画
|
||||
* @default false
|
||||
@ -64,3 +51,11 @@ export const NoticeBarDefaultProps: Partial<INoticeBarProps> = {
|
||||
loop: false,
|
||||
type: 'default',
|
||||
};
|
||||
|
||||
export const NoticeBarFunctionalProps: Partial<INoticeBarProps> = {
|
||||
icon: '',
|
||||
type: 'default',
|
||||
mode: null,
|
||||
enableMarquee: false,
|
||||
loop: false,
|
||||
};
|
||||
|
@ -0,0 +1,22 @@
|
||||
import { useComponent } from 'functional-mini/component';
|
||||
import { getInstanceBoundingClientRect } from '../jsapi/get-instance-bounding-client-rect';
|
||||
|
||||
export const useInstanceBoundingClientRect = () => {
|
||||
const instance = useComponent();
|
||||
function getInstance() {
|
||||
if (instance.$id) {
|
||||
return my;
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
async function getBoundingClientRectWithId(prefix: string) {
|
||||
return await getInstanceBoundingClientRect(
|
||||
getInstance(),
|
||||
`${prefix}${instance.$id ? `-${instance.$id}` : ''}`
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
getBoundingClientRectWithId,
|
||||
};
|
||||
};
|
@ -1,5 +1,6 @@
|
||||
{
|
||||
"pages": [
|
||||
"demo/pages/NoticeBar/index",
|
||||
"demo/pages/ProgressCircle/index",
|
||||
"demo/pages/ProgressLine/index",
|
||||
"demo/pages/Empty/index",
|
||||
|
20
compiled/wechat/demo/pages/NoticeBar/index.js
Normal file
20
compiled/wechat/demo/pages/NoticeBar/index.js
Normal file
@ -0,0 +1,20 @@
|
||||
Page({
|
||||
data: {
|
||||
typeList: ['default', 'error', 'info', 'primary'],
|
||||
},
|
||||
handleTapAction: function () {
|
||||
this.showToast('点击按钮');
|
||||
},
|
||||
handleTapLink: function () {
|
||||
this.showToast('link 类型被点击了');
|
||||
},
|
||||
handleClose: function () {
|
||||
this.showToast('点击关闭');
|
||||
},
|
||||
showToast: function (content) {
|
||||
//@ts-ignore
|
||||
wx.showToast({
|
||||
title: content,
|
||||
});
|
||||
},
|
||||
});
|
8
compiled/wechat/demo/pages/NoticeBar/index.json
Normal file
8
compiled/wechat/demo/pages/NoticeBar/index.json
Normal file
@ -0,0 +1,8 @@
|
||||
{
|
||||
"navigationBarTitleText": "Notice",
|
||||
"usingComponents": {
|
||||
"notice": "../../../src/NoticeBar/index",
|
||||
"container": "../../../src/Container/index",
|
||||
"am-icon": "../../../src/Icon/index"
|
||||
}
|
||||
}
|
89
compiled/wechat/demo/pages/NoticeBar/index.wxml
Normal file
89
compiled/wechat/demo/pages/NoticeBar/index.wxml
Normal file
@ -0,0 +1,89 @@
|
||||
<container title="基础用法">
|
||||
<block
|
||||
wx:for="{{ typeList }}"
|
||||
wx:for-index="index"
|
||||
wx:for-item="item">
|
||||
<notice
|
||||
style="margin-bottom: 8px"
|
||||
type="{{ item }}">
|
||||
{{ item }}
|
||||
</notice>
|
||||
</block>
|
||||
</container>
|
||||
|
||||
<container title="可关闭通告栏">
|
||||
<notice
|
||||
bind:tap="handleClose"
|
||||
mode="closeable">
|
||||
这条通知可以关闭
|
||||
</notice>
|
||||
</container>
|
||||
|
||||
<container title="可滚动通告栏">
|
||||
<block
|
||||
wx:for="{{ typeList }}"
|
||||
wx:for-index="index"
|
||||
wx:for-item="item">
|
||||
<notice
|
||||
type="{{ item }}"
|
||||
style="margin-bottom: 8px"
|
||||
enableMarquee="{{ true }}"
|
||||
loop="{{ true }}"
|
||||
bind:tap="handleTapLink"
|
||||
mode="link">
|
||||
文本溢出时,开启循环滚动。文字不够继续添加文字凑数。
|
||||
</notice>
|
||||
</block>
|
||||
</container>
|
||||
|
||||
<container title="可滚动通告栏(不循环)">
|
||||
<block
|
||||
wx:for="{{ typeList }}"
|
||||
wx:for-index="index"
|
||||
wx:for-item="item">
|
||||
<notice
|
||||
type="{{ item }}"
|
||||
style="margin-bottom: 8px"
|
||||
enableMarquee="{{ true }}"
|
||||
bind:tap="handleTapLink"
|
||||
mode="link">
|
||||
文本溢出时,开启循环滚动。文字不够继续添加文字凑数。
|
||||
</notice>
|
||||
</block>
|
||||
</container>
|
||||
|
||||
<container title="自定义通告栏">
|
||||
<notice style="margin-bottom: 8px">
|
||||
不展示图标
|
||||
<view slot="icon" />
|
||||
</notice>
|
||||
|
||||
<notice
|
||||
type="primary"
|
||||
icon="GlobalOutline"
|
||||
style="margin-bottom: 8px"
|
||||
mode="link">
|
||||
自定义左侧图标
|
||||
</notice>
|
||||
|
||||
<notice
|
||||
type="primary"
|
||||
icon="https://gw.alipayobjects.com/mdn/rms_ce4c6f/afts/img/A*XMCgSYx3f50AAAAAAAAAAABkARQnAQ"
|
||||
style="margin-bottom: 8px"
|
||||
mode="link">
|
||||
自定义左侧图标图片
|
||||
</notice>
|
||||
|
||||
<notice
|
||||
mode="link"
|
||||
style="margin-bottom: 8px"
|
||||
bind:tap="handleTapLink">
|
||||
自定义右侧按钮
|
||||
<view
|
||||
slot="extra"
|
||||
class="extra">
|
||||
<view bind:tap="handleTapAction">不再提示</view>
|
||||
<view bind:tap="handleTapAction">查看详情</view>
|
||||
</view>
|
||||
</notice>
|
||||
</container>
|
7
compiled/wechat/demo/pages/NoticeBar/index.wxss
Normal file
7
compiled/wechat/demo/pages/NoticeBar/index.wxss
Normal file
@ -0,0 +1,7 @@
|
||||
.extra {
|
||||
display: flex;
|
||||
}
|
||||
.extra > view {
|
||||
white-space: nowrap;
|
||||
margin-left: 8rpx;
|
||||
}
|
@ -4,7 +4,7 @@
|
||||
"async-validator": "^4.0.7",
|
||||
"dayjs": "^1.11.10",
|
||||
"fast-deep-equal": "3.1.3",
|
||||
"functional-mini": "^0.16.0",
|
||||
"functional-mini": "^0.17.0",
|
||||
"tslib": "2.5.0"
|
||||
},
|
||||
"repository": "git@github.com:ant-design/ant-design-mini.git"
|
||||
|
148
compiled/wechat/src/NoticeBar/index.js
Normal file
148
compiled/wechat/src/NoticeBar/index.js
Normal file
@ -0,0 +1,148 @@
|
||||
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
||||
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
||||
return new (P || (P = Promise))(function (resolve, reject) {
|
||||
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
||||
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
||||
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
||||
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
||||
});
|
||||
};
|
||||
var __generator = (this && this.__generator) || function (thisArg, body) {
|
||||
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
|
||||
return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
|
||||
function verb(n) { return function (v) { return step([n, v]); }; }
|
||||
function step(op) {
|
||||
if (f) throw new TypeError("Generator is already executing.");
|
||||
while (g && (g = 0, op[0] && (_ = 0)), _) try {
|
||||
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
|
||||
if (y = 0, t) op = [op[0] & 2, t.value];
|
||||
switch (op[0]) {
|
||||
case 0: case 1: t = op; break;
|
||||
case 4: _.label++; return { value: op[1], done: false };
|
||||
case 5: _.label++; y = op[1]; op = [0]; continue;
|
||||
case 7: op = _.ops.pop(); _.trys.pop(); continue;
|
||||
default:
|
||||
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
|
||||
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
|
||||
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
|
||||
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
|
||||
if (t[2]) _.ops.pop();
|
||||
_.trys.pop(); continue;
|
||||
}
|
||||
op = body.call(thisArg, _);
|
||||
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
|
||||
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
|
||||
}
|
||||
};
|
||||
import { useState, useEffect, useEvent, usePageShow, } from 'functional-mini/component';
|
||||
import '../_util/assert-component2';
|
||||
import { mountComponent } from '../_util/component';
|
||||
import { useComponentEvent } from '../_util/hooks/useComponentEvent';
|
||||
import { useInstanceBoundingClientRect } from '../_util/hooks/useInstanceBoundingClientRect';
|
||||
import { NoticeBarFunctionalProps } from './props';
|
||||
import { useEvent as useStableCallback } from '../_util/hooks/useEvent';
|
||||
var NoticeBar = function (props) {
|
||||
var _a = useState(''), marqueeStyle = _a[0], setMarqueeStyle = _a[1];
|
||||
var _b = useState(true), show = _b[0], setShow = _b[1];
|
||||
var triggerEventOnly = useComponentEvent(props).triggerEventOnly;
|
||||
var startMarquee = useStableCallback(function (state) {
|
||||
var loop = props.loop;
|
||||
var leading = 500;
|
||||
var duration = state.duration, overflowWidth = state.overflowWidth, viewWidth = state.viewWidth;
|
||||
var marqueeScrollWidth = overflowWidth;
|
||||
if (loop) {
|
||||
marqueeScrollWidth = overflowWidth + viewWidth;
|
||||
}
|
||||
var newMarqueeStyle = "transform: translate3d(".concat(-marqueeScrollWidth, "px, 0, 0); transition: ").concat(duration, "s all linear ").concat(typeof leading === 'number' ? "".concat(leading / 1000, "s") : '0s', ";");
|
||||
setMarqueeStyle(newMarqueeStyle);
|
||||
});
|
||||
var getBoundingClientRectWithId = useInstanceBoundingClientRect().getBoundingClientRectWithId;
|
||||
function measureText(callback) {
|
||||
var _this = this;
|
||||
var fps = 40;
|
||||
var loop = props.loop;
|
||||
// 计算文本所占据的宽度,计算需要滚动的宽度
|
||||
setTimeout(function () { return __awaiter(_this, void 0, void 0, function () {
|
||||
var marqueeSize, contentSize, overflowWidth, viewWidth, marqueeScrollWidth;
|
||||
return __generator(this, function (_a) {
|
||||
switch (_a.label) {
|
||||
case 0: return [4 /*yield*/, getBoundingClientRectWithId('.ant-notice-bar-marquee')];
|
||||
case 1:
|
||||
marqueeSize = _a.sent();
|
||||
return [4 /*yield*/, getBoundingClientRectWithId('.ant-notice-bar-content')];
|
||||
case 2:
|
||||
contentSize = _a.sent();
|
||||
overflowWidth = (marqueeSize && contentSize && marqueeSize.width - contentSize.width) ||
|
||||
0;
|
||||
viewWidth = (contentSize === null || contentSize === void 0 ? void 0 : contentSize.width) || 0;
|
||||
marqueeScrollWidth = overflowWidth;
|
||||
if (loop) {
|
||||
marqueeScrollWidth = overflowWidth + viewWidth;
|
||||
}
|
||||
if (overflowWidth > 0) {
|
||||
callback({
|
||||
overflowWidth: overflowWidth,
|
||||
viewWidth: viewWidth,
|
||||
duration: marqueeScrollWidth / fps,
|
||||
});
|
||||
}
|
||||
return [2 /*return*/];
|
||||
}
|
||||
});
|
||||
}); }, 0);
|
||||
}
|
||||
useEffect(function () {
|
||||
var enableMarquee = props.enableMarquee;
|
||||
if (enableMarquee) {
|
||||
measureText(startMarquee);
|
||||
}
|
||||
});
|
||||
function resetMarquee(state) {
|
||||
var loop = props.loop;
|
||||
var viewWidth = state.viewWidth;
|
||||
var showMarqueeWidth = '0px';
|
||||
if (loop) {
|
||||
showMarqueeWidth = "".concat(viewWidth, "px");
|
||||
}
|
||||
var marqueeStyle = "transform: translate3d(".concat(showMarqueeWidth, ", 0, 0); transition: 0s all linear;");
|
||||
setMarqueeStyle(marqueeStyle);
|
||||
}
|
||||
useEvent('onTransitionEnd', function () {
|
||||
var loop = props.loop;
|
||||
var trailing = 200;
|
||||
if (loop) {
|
||||
setTimeout(function () {
|
||||
measureText(function (state) {
|
||||
resetMarquee(state);
|
||||
startMarquee(state);
|
||||
});
|
||||
}, trailing);
|
||||
}
|
||||
});
|
||||
useEvent('onTap', function () {
|
||||
var mode = props.mode;
|
||||
if (mode === 'link') {
|
||||
triggerEventOnly('tap');
|
||||
}
|
||||
if (mode === 'closeable') {
|
||||
setShow(false);
|
||||
triggerEventOnly('tap');
|
||||
}
|
||||
});
|
||||
usePageShow(function () {
|
||||
if (props.enableMarquee) {
|
||||
setMarqueeStyle('');
|
||||
resetMarquee({
|
||||
overflowWidth: 0,
|
||||
duration: 0,
|
||||
viewWidth: 0,
|
||||
});
|
||||
measureText(startMarquee);
|
||||
}
|
||||
});
|
||||
return {
|
||||
marqueeStyle: marqueeStyle,
|
||||
show: show,
|
||||
};
|
||||
};
|
||||
mountComponent(NoticeBar, NoticeBarFunctionalProps);
|
7
compiled/wechat/src/NoticeBar/index.json
Normal file
7
compiled/wechat/src/NoticeBar/index.json
Normal file
@ -0,0 +1,7 @@
|
||||
{
|
||||
"component": true,
|
||||
"usingComponents": {
|
||||
"icon": "../Icon/index",
|
||||
"image-icon": "../ImageIcon/index"
|
||||
}
|
||||
}
|
35
compiled/wechat/src/NoticeBar/index.md
Normal file
35
compiled/wechat/src/NoticeBar/index.md
Normal file
@ -0,0 +1,35 @@
|
||||
---
|
||||
nav:
|
||||
path: /components
|
||||
group:
|
||||
title: 引导提示
|
||||
order: 14
|
||||
toc: 'content'
|
||||
---
|
||||
# NoticeBar 通告栏
|
||||
|
||||
<code src="../../docs/components/compatibility.tsx" inline="true"></code>
|
||||
|
||||
展示一组消息通知
|
||||
## 何时使用
|
||||
用于当前页面内信息的通知,是一种较醒目的页面内通知方式
|
||||
|
||||
## 代码示例
|
||||
<code src='pages/NoticeBar/index'></code>
|
||||
|
||||
|
||||
## API
|
||||
| 属性 | 说明 | 类型 | 默认值 |
|
||||
| -----|-----|-----|-----|
|
||||
| className | 类名 | string | - |
|
||||
| enableMarquee | 是否开启滚动动画 | boolean | false |
|
||||
| extra | 自定义右侧内容 | slot | - |
|
||||
| icon | 左侧icon,支持所有内置 iconType 和自定义链接,也支持自定义slot | slot \| string | - |
|
||||
| loop | 是否循环滚动,enableMarquee 为 true 时有效 | boolean | false |
|
||||
| mode | 通告类型,`link` 表示连接,整行可点;`closeable` 表示点击 x 可以关闭;不填时表示你右侧没有图标 | string | - |
|
||||
| style | 样式 | string | - |
|
||||
| title | 标题 | string\|slot | - |
|
||||
| type | 类型,可选 `default`, `error`, `primary`, `info` | string | default |
|
||||
| onTap | 点击通知栏右侧的图标(箭头或者叉),触发回调 | ()=>void | - |
|
||||
|
||||
|
39
compiled/wechat/src/NoticeBar/index.wxml
Normal file
39
compiled/wechat/src/NoticeBar/index.wxml
Normal file
@ -0,0 +1,39 @@
|
||||
<view
|
||||
wx:if="{{ show }}"
|
||||
class="ant-notice-bar {{ className || '' }} {{ type ? 'ant-notice-bar-' + type : '' }}"
|
||||
style="{{ style }}">
|
||||
<view class="ant-notice-bar-icon">
|
||||
<image-icon
|
||||
wx:if="{{ icon }}"
|
||||
image="{{ icon }}"
|
||||
className="ant-notice-bar-icon-image" />
|
||||
<icon
|
||||
wx:elif="{{ type === 'error' }}"
|
||||
type="InformationCircleOutline" />
|
||||
<icon
|
||||
wx:else
|
||||
type="SoundOutline" />
|
||||
</view>
|
||||
<view
|
||||
class="ant-notice-bar-content ant-notice-bar-content{{ $id ? '-' + $id : '' }}">
|
||||
<view
|
||||
class="ant-notice-bar-marquee ant-notice-bar-marquee{{ $id ? '-' + $id : '' }}"
|
||||
style="{{ marqueeStyle }} display: {{ enableMarquee ? 'inline-block' : 'block' }}"
|
||||
onTransitionEnd="onTransitionEnd">
|
||||
<slot />
|
||||
</view>
|
||||
</view>
|
||||
<view class="ant-notice-bar-operation">
|
||||
<slot name="extra" />
|
||||
<icon
|
||||
wx:if="{{ mode === 'link' }}"
|
||||
className="ant-notice-bar-operation-icon"
|
||||
type="RightOutline"
|
||||
bind:tap="onTap" />
|
||||
<icon
|
||||
wx:if="{{ mode === 'closeable' }}"
|
||||
className="ant-notice-bar-operation-icon"
|
||||
type="CloseOutline"
|
||||
bind:tap="onTap" />
|
||||
</view>
|
||||
</view>
|
68
compiled/wechat/src/NoticeBar/index.wxss
Normal file
68
compiled/wechat/src/NoticeBar/index.wxss
Normal file
@ -0,0 +1,68 @@
|
||||
.ant-notice-bar {
|
||||
position: relative;
|
||||
display: flex;
|
||||
height: 37.5px;
|
||||
align-items: center;
|
||||
overflow: hidden;
|
||||
padding: 8px 12px;
|
||||
font-size: 15px;
|
||||
color: #ff6010;
|
||||
background-color: #fff9ed;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.ant-notice-bar-error {
|
||||
color: #ffffff;
|
||||
background-color: #ff3141;
|
||||
}
|
||||
.ant-notice-bar-error-scroll-left,
|
||||
.ant-notice-bar-error-scroll-right {
|
||||
background: #ff3141;
|
||||
}
|
||||
.ant-notice-bar-primary {
|
||||
color: #1677ff;
|
||||
background-color: #d0e4ff;
|
||||
}
|
||||
.ant-notice-bar-primary-scroll-left,
|
||||
.ant-notice-bar-primary-scroll-right {
|
||||
background: #d0e4ff;
|
||||
}
|
||||
.ant-notice-bar-info {
|
||||
color: #ffffff;
|
||||
background: #666666;
|
||||
}
|
||||
.ant-notice-bar-info-scroll-left,
|
||||
.ant-notice-bar-info-scroll-right {
|
||||
background: #666666;
|
||||
}
|
||||
.ant-notice-bar-icon {
|
||||
margin-right: 8px;
|
||||
font-size: 18px;
|
||||
}
|
||||
.ant-notice-bar-icon-image-image {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
}
|
||||
.ant-notice-bar-content {
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
flex: 1 100%;
|
||||
overflow: hidden;
|
||||
vertical-align: middle;
|
||||
line-height: 1.4;
|
||||
}
|
||||
.ant-notice-bar-marquee {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
vertical-align: middle;
|
||||
}
|
||||
.ant-notice-bar-operation {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
.ant-notice-bar-operation-icon {
|
||||
margin-left: 12px;
|
||||
}
|
||||
.ant-icon-size-x-small {
|
||||
font-size: 18px;
|
||||
}
|
12
compiled/wechat/src/NoticeBar/props.js
Normal file
12
compiled/wechat/src/NoticeBar/props.js
Normal file
@ -0,0 +1,12 @@
|
||||
export var NoticeBarDefaultProps = {
|
||||
enableMarquee: false,
|
||||
loop: false,
|
||||
type: 'default',
|
||||
};
|
||||
export var NoticeBarFunctionalProps = {
|
||||
icon: '',
|
||||
type: 'default',
|
||||
mode: null,
|
||||
enableMarquee: false,
|
||||
loop: false,
|
||||
};
|
0
compiled/wechat/src/NoticeBar/variable.wxss
Normal file
0
compiled/wechat/src/NoticeBar/variable.wxss
Normal file
@ -0,0 +1,60 @@
|
||||
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
||||
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
||||
return new (P || (P = Promise))(function (resolve, reject) {
|
||||
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
||||
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
||||
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
||||
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
||||
});
|
||||
};
|
||||
var __generator = (this && this.__generator) || function (thisArg, body) {
|
||||
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
|
||||
return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
|
||||
function verb(n) { return function (v) { return step([n, v]); }; }
|
||||
function step(op) {
|
||||
if (f) throw new TypeError("Generator is already executing.");
|
||||
while (g && (g = 0, op[0] && (_ = 0)), _) try {
|
||||
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
|
||||
if (y = 0, t) op = [op[0] & 2, t.value];
|
||||
switch (op[0]) {
|
||||
case 0: case 1: t = op; break;
|
||||
case 4: _.label++; return { value: op[1], done: false };
|
||||
case 5: _.label++; y = op[1]; op = [0]; continue;
|
||||
case 7: op = _.ops.pop(); _.trys.pop(); continue;
|
||||
default:
|
||||
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
|
||||
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
|
||||
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
|
||||
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
|
||||
if (t[2]) _.ops.pop();
|
||||
_.trys.pop(); continue;
|
||||
}
|
||||
op = body.call(thisArg, _);
|
||||
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
|
||||
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
|
||||
}
|
||||
};
|
||||
import { useComponent } from 'functional-mini/component';
|
||||
import { getInstanceBoundingClientRect } from '../jsapi/get-instance-bounding-client-rect';
|
||||
export var useInstanceBoundingClientRect = function () {
|
||||
var instance = useComponent();
|
||||
function getInstance() {
|
||||
if (instance.$id) {
|
||||
return my;
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
function getBoundingClientRectWithId(prefix) {
|
||||
return __awaiter(this, void 0, void 0, function () {
|
||||
return __generator(this, function (_a) {
|
||||
switch (_a.label) {
|
||||
case 0: return [4 /*yield*/, getInstanceBoundingClientRect(getInstance(), "".concat(prefix).concat(instance.$id ? "-".concat(instance.$id) : ''))];
|
||||
case 1: return [2 /*return*/, _a.sent()];
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
return {
|
||||
getBoundingClientRectWithId: getBoundingClientRectWithId,
|
||||
};
|
||||
};
|
@ -1,5 +1,6 @@
|
||||
{
|
||||
"pages": [
|
||||
"pages/NoticeBar",
|
||||
"pages/ProgressLine",
|
||||
"pages/ProgressCircle",
|
||||
"pages/Empty",
|
||||
@ -107,6 +108,7 @@
|
||||
"Pagination",
|
||||
"Badge",
|
||||
"TabBar",
|
||||
"Progress"
|
||||
"Progress",
|
||||
"NoticeBar"
|
||||
]
|
||||
}
|
||||
|
@ -33,6 +33,20 @@ export default ({ typeList }: InternalData) => (
|
||||
))}
|
||||
</Container>
|
||||
|
||||
<Container title="可滚动通告栏(不循环)">
|
||||
{typeList.map((item) => (
|
||||
<Notice
|
||||
type={item}
|
||||
style="margin-bottom: 8px"
|
||||
enableMarquee={true}
|
||||
onTap="handleTapLink"
|
||||
mode="link"
|
||||
>
|
||||
文本溢出时,开启循环滚动。文字不够继续添加文字凑数。
|
||||
</Notice>
|
||||
))}
|
||||
</Container>
|
||||
|
||||
<Container title="自定义通告栏">
|
||||
<Notice style="margin-bottom: 8px">
|
||||
不展示图标
|
||||
|
@ -3,21 +3,27 @@ Page({
|
||||
typeList: ['default', 'error', 'info', 'primary'],
|
||||
},
|
||||
handleTapAction() {
|
||||
my.showToast({
|
||||
content: `点击按钮`,
|
||||
duration: 1000,
|
||||
});
|
||||
this.showToast('点击按钮');
|
||||
},
|
||||
handleTapLink() {
|
||||
my.showToast({
|
||||
content: 'link 类型被点击了',
|
||||
duration: 1000,
|
||||
});
|
||||
this.showToast('link 类型被点击了');
|
||||
},
|
||||
handleClose() {
|
||||
this.showToast('点击关闭');
|
||||
},
|
||||
showToast(content: string) {
|
||||
/// #if ALIPAY
|
||||
my.showToast({
|
||||
content: `点击关闭`,
|
||||
content: content,
|
||||
duration: 1000,
|
||||
});
|
||||
/// #endif
|
||||
|
||||
/// #if WECHAT
|
||||
//@ts-ignore
|
||||
wx.showToast({
|
||||
title: content,
|
||||
});
|
||||
/// #endif
|
||||
},
|
||||
});
|
||||
|
@ -35,7 +35,7 @@
|
||||
"async-validator": "^4.0.7",
|
||||
"dayjs": "^1.11.3",
|
||||
"fast-deep-equal": "3.1.3",
|
||||
"functional-mini": "^0.16.0",
|
||||
"functional-mini": "^0.17.0",
|
||||
"tslib": "2.5.0"
|
||||
},
|
||||
"overrides": {
|
||||
@ -123,4 +123,4 @@
|
||||
],
|
||||
"license": "MIT",
|
||||
"homepage": "https://github.com/ant-design/ant-design-mini"
|
||||
}
|
||||
}
|
||||
|
@ -1,19 +1,22 @@
|
||||
import { Slot, TSXMLProps, View, Component } from 'tsxml';
|
||||
import { Slot, TSXMLProps, View, Component, InternalData } from 'tsxml';
|
||||
import Icon from '../Icon/index.axml';
|
||||
import ImageIcon from '../ImageIcon/index.axml';
|
||||
import { INoticeBarProps } from './props';
|
||||
|
||||
export default ({
|
||||
className,
|
||||
style,
|
||||
type,
|
||||
icon,
|
||||
mode,
|
||||
enableMarquee,
|
||||
show,
|
||||
marqueeStyle,
|
||||
$id,
|
||||
}: TSXMLProps<INoticeBarProps>) => (
|
||||
export default (
|
||||
{
|
||||
className,
|
||||
style,
|
||||
type,
|
||||
icon,
|
||||
mode,
|
||||
enableMarquee,
|
||||
|
||||
$id,
|
||||
}: TSXMLProps<INoticeBarProps>,
|
||||
|
||||
{ marqueeStyle, show }: InternalData
|
||||
) => (
|
||||
<Component>
|
||||
{show && (
|
||||
<View
|
||||
@ -22,7 +25,10 @@ export default ({
|
||||
}`}
|
||||
style={style}
|
||||
>
|
||||
{/* #if ALIPAY */}
|
||||
<Slot name="icon">
|
||||
{/* #endif */}
|
||||
|
||||
<View class="ant-notice-bar-icon">
|
||||
{icon ? (
|
||||
<ImageIcon image={icon} className="ant-notice-bar-icon-image" />
|
||||
@ -32,10 +38,18 @@ export default ({
|
||||
<Icon type="SoundOutline" />
|
||||
)}
|
||||
</View>
|
||||
{/* #if ALIPAY */}
|
||||
</Slot>
|
||||
<View class={`ant-notice-bar-content ant-notice-bar-content-${$id}`}>
|
||||
{/* #endif */}
|
||||
<View
|
||||
class={`ant-notice-bar-content ant-notice-bar-content${
|
||||
$id ? '-' + $id : ''
|
||||
}`}
|
||||
>
|
||||
<View
|
||||
class={`ant-notice-bar-marquee ant-notice-bar-marquee-${$id}`}
|
||||
class={`ant-notice-bar-marquee ant-notice-bar-marquee${
|
||||
$id ? '-' + $id : ''
|
||||
}`}
|
||||
style={`${marqueeStyle} display: ${
|
||||
enableMarquee ? 'inline-block' : 'block'
|
||||
}`}
|
||||
|
@ -1,155 +1,128 @@
|
||||
import { NoticeBarDefaultProps } from './props';
|
||||
import { log } from '../_util/console';
|
||||
import { IBoundingClientRect } from '../_util/base';
|
||||
import {
|
||||
useState,
|
||||
useEffect,
|
||||
useEvent,
|
||||
usePageShow,
|
||||
} from 'functional-mini/component';
|
||||
import '../_util/assert-component2';
|
||||
import { IBoundingClientRect } from '../_util/base';
|
||||
import { mountComponent } from '../_util/component';
|
||||
import { useComponentEvent } from '../_util/hooks/useComponentEvent';
|
||||
import { useInstanceBoundingClientRect } from '../_util/hooks/useInstanceBoundingClientRect';
|
||||
import { INoticeBarProps, NoticeBarFunctionalProps } from './props';
|
||||
import { useEvent as useStableCallback } from '../_util/hooks/useEvent';
|
||||
|
||||
Component({
|
||||
props: NoticeBarDefaultProps,
|
||||
data: {
|
||||
show: true,
|
||||
marqueeStyle: '',
|
||||
animatedWidth: 0,
|
||||
overflowWidth: 0,
|
||||
duration: 0,
|
||||
viewWidth: 0,
|
||||
},
|
||||
didMount() {
|
||||
const { enableMarquee } = this.props;
|
||||
this.showError();
|
||||
const NoticeBar = (props: INoticeBarProps) => {
|
||||
const [marqueeStyle, setMarqueeStyle] = useState('');
|
||||
const [show, setShow] = useState(true);
|
||||
|
||||
if (enableMarquee) {
|
||||
this.measureText(this.startMarquee.bind(this));
|
||||
const { triggerEventOnly } = useComponentEvent(props);
|
||||
const startMarquee = useStableCallback((state) => {
|
||||
const { loop } = props;
|
||||
const leading = 500;
|
||||
const { duration, overflowWidth, viewWidth } = state;
|
||||
let marqueeScrollWidth = overflowWidth;
|
||||
if (loop) {
|
||||
marqueeScrollWidth = overflowWidth + viewWidth;
|
||||
}
|
||||
},
|
||||
const newMarqueeStyle = `transform: translate3d(${-marqueeScrollWidth}px, 0, 0); transition: ${duration}s all linear ${
|
||||
typeof leading === 'number' ? `${leading / 1000}s` : '0s'
|
||||
};`;
|
||||
setMarqueeStyle(newMarqueeStyle);
|
||||
});
|
||||
|
||||
didUpdate() {
|
||||
const { enableMarquee } = this.props;
|
||||
this.showError();
|
||||
// 这里更新处理的原因是防止notice内容在动画过程中发生改变。
|
||||
if (enableMarquee) {
|
||||
this.measureText(this.startMarquee.bind(this));
|
||||
}
|
||||
},
|
||||
const { getBoundingClientRectWithId } = useInstanceBoundingClientRect();
|
||||
function measureText(callback) {
|
||||
const fps = 40;
|
||||
const { loop } = props;
|
||||
// 计算文本所占据的宽度,计算需要滚动的宽度
|
||||
setTimeout(async () => {
|
||||
const marqueeSize: IBoundingClientRect | null =
|
||||
await getBoundingClientRectWithId('.ant-notice-bar-marquee');
|
||||
const contentSize: IBoundingClientRect | null =
|
||||
await getBoundingClientRectWithId('.ant-notice-bar-content');
|
||||
const overflowWidth =
|
||||
(marqueeSize && contentSize && marqueeSize.width - contentSize.width) ||
|
||||
0;
|
||||
|
||||
pageEvents: {
|
||||
onShow() {
|
||||
this.resetState();
|
||||
},
|
||||
},
|
||||
|
||||
methods: {
|
||||
resetState() {
|
||||
if (this.props.enableMarquee) {
|
||||
this.setData(
|
||||
{
|
||||
marqueeStyle: '',
|
||||
animatedWidth: 0,
|
||||
overflowWidth: 0,
|
||||
duration: 0,
|
||||
viewWidth: 0,
|
||||
},
|
||||
() => {
|
||||
this.resetMarquee();
|
||||
this.measureText(this.startMarquee.bind(this));
|
||||
}
|
||||
);
|
||||
}
|
||||
},
|
||||
showError() {
|
||||
const { actions } = this.props;
|
||||
if (!Array.isArray(actions) && typeof actions !== 'undefined') {
|
||||
log.warn(
|
||||
'NoticeBar',
|
||||
`当前定义的 actions 的类型为 ${typeof actions},不符合属性定义,应该为数组,如:actions="{{['值', '值']}}`
|
||||
);
|
||||
}
|
||||
},
|
||||
onTap() {
|
||||
const { mode, onTap } = this.props;
|
||||
if (mode === 'link' && typeof onTap === 'function') {
|
||||
return onTap();
|
||||
}
|
||||
if (mode === 'closeable' && typeof onTap === 'function') {
|
||||
this.setData({
|
||||
show: false,
|
||||
});
|
||||
return onTap();
|
||||
}
|
||||
},
|
||||
// 文本滚动的计算
|
||||
resetMarquee() {
|
||||
const { loop } = this.props;
|
||||
const { viewWidth } = this.data;
|
||||
let showMarqueeWidth = '0px';
|
||||
if (loop) {
|
||||
showMarqueeWidth = `${viewWidth}px`;
|
||||
}
|
||||
const marqueeStyle = `transform: translate3d(${showMarqueeWidth}, 0, 0); transition: 0s all linear;`;
|
||||
this.setData({
|
||||
marqueeStyle,
|
||||
});
|
||||
},
|
||||
startMarquee() {
|
||||
const { loop } = this.props;
|
||||
const leading = 500;
|
||||
const { duration, overflowWidth, viewWidth } = this.data;
|
||||
const viewWidth = contentSize?.width || 0;
|
||||
let marqueeScrollWidth = overflowWidth;
|
||||
if (loop) {
|
||||
marqueeScrollWidth = overflowWidth + viewWidth;
|
||||
}
|
||||
const marqueeStyle = `transform: translate3d(${-marqueeScrollWidth}px, 0, 0); transition: ${duration}s all linear ${
|
||||
typeof leading === 'number' ? `${leading / 1000}s` : '0s'
|
||||
};`;
|
||||
if (this.data.marqueeStyle !== marqueeStyle) {
|
||||
this.setData({
|
||||
marqueeStyle,
|
||||
if (overflowWidth > 0) {
|
||||
callback({
|
||||
overflowWidth,
|
||||
viewWidth,
|
||||
duration: marqueeScrollWidth / fps,
|
||||
});
|
||||
}
|
||||
},
|
||||
onTransitionEnd() {
|
||||
const { loop } = this.props;
|
||||
const trailing = 200;
|
||||
if (loop) {
|
||||
setTimeout(() => {
|
||||
this.resetMarquee();
|
||||
this.measureText(this.startMarquee.bind(this));
|
||||
}, trailing);
|
||||
}
|
||||
},
|
||||
measureText(callback) {
|
||||
const fps = 40;
|
||||
const { loop } = this.props;
|
||||
// 计算文本所占据的宽度,计算需要滚动的宽度
|
||||
}, 0);
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
const { enableMarquee } = props;
|
||||
if (enableMarquee) {
|
||||
measureText(startMarquee);
|
||||
}
|
||||
});
|
||||
|
||||
function resetMarquee(state) {
|
||||
const { loop } = props;
|
||||
const { viewWidth } = state;
|
||||
let showMarqueeWidth = '0px';
|
||||
if (loop) {
|
||||
showMarqueeWidth = `${viewWidth}px`;
|
||||
}
|
||||
const marqueeStyle = `transform: translate3d(${showMarqueeWidth}, 0, 0); transition: 0s all linear;`;
|
||||
setMarqueeStyle(marqueeStyle);
|
||||
}
|
||||
|
||||
useEvent('onTransitionEnd', () => {
|
||||
const { loop } = props;
|
||||
const trailing = 200;
|
||||
if (loop) {
|
||||
setTimeout(() => {
|
||||
my.createSelectorQuery()
|
||||
.select(`.ant-notice-bar-marquee-${this.$id}`)
|
||||
.boundingClientRect()
|
||||
.select(`.ant-notice-bar-content-${this.$id}`)
|
||||
.boundingClientRect()
|
||||
.exec((ret) => {
|
||||
// eslint-disable-next-line max-len
|
||||
const overflowWidth =
|
||||
(ret &&
|
||||
ret[0] &&
|
||||
ret[1] &&
|
||||
(<IBoundingClientRect>ret[0]).width -
|
||||
(<IBoundingClientRect>ret[1]).width) ||
|
||||
0;
|
||||
const viewWidth = (<IBoundingClientRect>ret[1])?.width || 0;
|
||||
let marqueeScrollWidth = overflowWidth;
|
||||
if (loop) {
|
||||
marqueeScrollWidth = overflowWidth + viewWidth;
|
||||
}
|
||||
if (overflowWidth > 0) {
|
||||
this.setData({
|
||||
overflowWidth,
|
||||
viewWidth,
|
||||
duration: marqueeScrollWidth / fps,
|
||||
});
|
||||
callback();
|
||||
}
|
||||
});
|
||||
}, 0);
|
||||
},
|
||||
},
|
||||
});
|
||||
measureText((state) => {
|
||||
resetMarquee(state);
|
||||
startMarquee(state);
|
||||
});
|
||||
}, trailing);
|
||||
}
|
||||
});
|
||||
|
||||
useEvent('onTap', () => {
|
||||
const { mode } = props;
|
||||
if (mode === 'link') {
|
||||
triggerEventOnly('tap');
|
||||
}
|
||||
if (mode === 'closeable') {
|
||||
/// #if ALIPAY
|
||||
if (typeof props.onTap !== 'function') {
|
||||
return;
|
||||
}
|
||||
/// #endif
|
||||
setShow(false);
|
||||
triggerEventOnly('tap');
|
||||
}
|
||||
});
|
||||
|
||||
usePageShow(() => {
|
||||
if (props.enableMarquee) {
|
||||
setMarqueeStyle('');
|
||||
resetMarquee({
|
||||
overflowWidth: 0,
|
||||
duration: 0,
|
||||
viewWidth: 0,
|
||||
});
|
||||
measureText(startMarquee);
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
marqueeStyle,
|
||||
show,
|
||||
};
|
||||
};
|
||||
|
||||
mountComponent(NoticeBar, NoticeBarFunctionalProps);
|
||||
|
@ -7,10 +7,6 @@ import { IBaseProps } from '../_util/base';
|
||||
*/
|
||||
|
||||
export interface INoticeBarProps extends IBaseProps {
|
||||
/**
|
||||
* 是否展示
|
||||
*/
|
||||
show?: boolean;
|
||||
/**
|
||||
* @description 消息的展示
|
||||
*/
|
||||
@ -24,16 +20,7 @@ export interface INoticeBarProps extends IBaseProps {
|
||||
/**
|
||||
* @description 通告类型,link 表示连接,整行可点;closeable 表示点击 x 可以关闭;不填时表示你右侧没有图标
|
||||
*/
|
||||
mode: 'link' | 'closeable';
|
||||
/**
|
||||
* @description 行动点,最多两个行动点,action和mode可以同时搭配使用
|
||||
*/
|
||||
|
||||
actions: string[];
|
||||
/**
|
||||
* @description 滚动样式
|
||||
*/
|
||||
marqueeStyle?: boolean;
|
||||
mode?: 'link' | 'closeable';
|
||||
/**
|
||||
* @description 是否开启滚动动画
|
||||
* @default false
|
||||
@ -64,3 +51,11 @@ export const NoticeBarDefaultProps: Partial<INoticeBarProps> = {
|
||||
loop: false,
|
||||
type: 'default',
|
||||
};
|
||||
|
||||
export const NoticeBarFunctionalProps: Partial<INoticeBarProps> = {
|
||||
icon: '',
|
||||
type: 'default',
|
||||
mode: null,
|
||||
enableMarquee: false,
|
||||
loop: false,
|
||||
};
|
||||
|
22
src/_util/hooks/useInstanceBoundingClientRect.ts
Normal file
22
src/_util/hooks/useInstanceBoundingClientRect.ts
Normal file
@ -0,0 +1,22 @@
|
||||
import { useComponent } from 'functional-mini/component';
|
||||
import { getInstanceBoundingClientRect } from '../jsapi/get-instance-bounding-client-rect';
|
||||
|
||||
export const useInstanceBoundingClientRect = () => {
|
||||
const instance = useComponent();
|
||||
function getInstance() {
|
||||
if (instance.$id) {
|
||||
return my;
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
async function getBoundingClientRectWithId(prefix: string) {
|
||||
return await getInstanceBoundingClientRect(
|
||||
getInstance(),
|
||||
`${prefix}${instance.$id ? `-${instance.$id}` : ''}`
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
getBoundingClientRectWithId,
|
||||
};
|
||||
};
|
@ -13,4 +13,5 @@
|
||||
"header": "",
|
||||
"radius": false,
|
||||
},
|
||||
"rootEvents": {},
|
||||
}
|
@ -20,4 +20,5 @@
|
||||
"showDivider": true,
|
||||
"title": "",
|
||||
},
|
||||
"rootEvents": {},
|
||||
}
|
@ -50,13 +50,9 @@ describe('modal onClose', () => {
|
||||
});
|
||||
await sleep(30);
|
||||
expect(instance.getData()).toEqual({
|
||||
'animatedWidth': 0,
|
||||
'duration': 2.5,
|
||||
'marqueeStyle':
|
||||
'transform: translate3d(-100px, 0, 0); transition: 2.5s all linear 0.5s;',
|
||||
'overflowWidth': 100,
|
||||
'show': true,
|
||||
'viewWidth': 100,
|
||||
});
|
||||
handleQuery.mockImplementation((id: string, index: number) => {
|
||||
return {
|
||||
@ -71,13 +67,9 @@ describe('modal onClose', () => {
|
||||
});
|
||||
await sleep(30);
|
||||
expect(instance.getData()).toEqual({
|
||||
'animatedWidth': 0,
|
||||
'duration': 5,
|
||||
'marqueeStyle':
|
||||
'transform: translate3d(-200px, 0, 0); transition: 5s all linear 0.5s;',
|
||||
'overflowWidth': 200,
|
||||
'show': true,
|
||||
'viewWidth': 100,
|
||||
});
|
||||
});
|
||||
|
||||
@ -96,13 +88,9 @@ describe('modal onClose', () => {
|
||||
});
|
||||
await sleep(30);
|
||||
expect(instance.getData()).toEqual({
|
||||
'animatedWidth': 0,
|
||||
'duration': 5,
|
||||
'marqueeStyle':
|
||||
'transform: translate3d(-200px, 0, 0); transition: 5s all linear 0.5s;',
|
||||
'overflowWidth': 100,
|
||||
'show': true,
|
||||
'viewWidth': 100,
|
||||
});
|
||||
instance.callMethod('onTransitionEnd');
|
||||
handleQuery.mockImplementation(async (id: string, index: number) => {
|
||||
@ -116,23 +104,15 @@ describe('modal onClose', () => {
|
||||
});
|
||||
await sleep(250);
|
||||
expect(instance.getData()).toEqual({
|
||||
'animatedWidth': 0,
|
||||
'duration': 5,
|
||||
'marqueeStyle':
|
||||
'transform: translate3d(100px, 0, 0); transition: 0s all linear;',
|
||||
'overflowWidth': 100,
|
||||
'show': true,
|
||||
'viewWidth': 100,
|
||||
});
|
||||
await sleep(300);
|
||||
expect(instance.getData()).toEqual({
|
||||
'animatedWidth': 0,
|
||||
'duration': 5,
|
||||
'marqueeStyle':
|
||||
'transform: translate3d(-200px, 0, 0); transition: 5s all linear 0.5s;',
|
||||
'overflowWidth': 100,
|
||||
'show': true,
|
||||
'viewWidth': 100,
|
||||
});
|
||||
await sleep(500);
|
||||
expect(instance.getData()).toEqual({
|
||||
'marqueeStyle':
|
||||
'transform: translate3d(-200px, 0, 0); transition: 5s all linear 0.5s;',
|
||||
'show': true,
|
||||
});
|
||||
});
|
||||
|
||||
@ -151,12 +131,8 @@ describe('modal onClose', () => {
|
||||
});
|
||||
await sleep(30);
|
||||
expect(instance.getData()).toEqual({
|
||||
'animatedWidth': 0,
|
||||
'duration': 0,
|
||||
'marqueeStyle': '',
|
||||
'overflowWidth': 0,
|
||||
'show': true,
|
||||
'viewWidth': 0,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -13,4 +13,5 @@
|
||||
"message": "",
|
||||
"title": "",
|
||||
},
|
||||
"rootEvents": {},
|
||||
}
|
@ -23,4 +23,5 @@
|
||||
"size": "medium",
|
||||
"uncheckedText": "",
|
||||
},
|
||||
"rootEvents": {},
|
||||
}
|
Loading…
Reference in New Issue
Block a user