diff --git a/docs/zh-CN/components/lottery.md b/docs/zh-CN/components/lottery.md new file mode 100644 index 00000000000..8f93f6f95ca --- /dev/null +++ b/docs/zh-CN/components/lottery.md @@ -0,0 +1,87 @@ +--- +title: Lottery 九宫格抽奖 +description: +type: 0 +group: ⚙ 组件 +menuName: Lottery 九宫格抽奖 +icon: +order: 27 +--- + +抽奖组件,可以配置奖品列表和中奖索引完成抽奖动画 + +```schema: scope="body" + { + type: 'lottery', + width: 300, + height: 300, + targetIndex: 1, + items: [ + { + name: "商品名称名称", + id: 1, + pictureUrl: + "//m.360buyimg.com/babel/s180x180_jfs/t1/174906/19/10256/188436/60a242afE89a800c9/801b64e5b80fde9a.jpg!q70.jpg", + }, + { + name: "没有中奖哦", + id: 2, + pictureUrl: + "https://img14.360buyimg.com/imagetools/jfs/t1/213369/13/5346/13899/619b60e5E2761162e/dca9b64e09bb2fed.png", + }, + { + name: "商品名称名称", + id: 3, + pictureUrl: + "//m.360buyimg.com/babel/s180x180_jfs/t1/17279/28/13940/140479/60b984f4E723b9981/d007711aa1cdc358.jpg!q70.jpg", + }, + { + name: "商品名称名称", + id: 4, + pictureUrl: + "//m.360buyimg.com/babel/s180x180_jfs/t1/190452/2/84/116077/608627ecEef11d11e/e0a93f09eca31ddf.jpg!q70.jpg", + }, + { + name: "商品名称名称", + id: 5, + pictureUrl: + "https://img10.360buyimg.com/n5/s54x54_jfs/t1/164065/10/8839/39628/603ee7edE9dee283f/e56acfa461919177.jpg", + }, + { + name: "祥禾饽饽铺京东自营旗舰店", + id: 6, + pictureUrl: + "//m.360buyimg.com/babel/s66x66_jfs/t1/195378/33/9432/145698/60d0400eE0520ca9f/2283995f6c6176e7.jpg!q50.jpg", + }, + { + name: "鲜花4+1束 鲜花速递 ", + id: 7, + pictureUrl: + "//m.360buyimg.com/babel/s180x180_jfs/t1/185809/36/6800/181830/60b4fdaaEa74ddfdf/7f3776e9a493ec20.jpg!q70.jpg", + }, + { + name: "大连萨米托爱心樱桃", + id: 8, + pictureUrl: + "//m.360buyimg.com/babel/s180x180_jfs/t1/191656/26/7699/116921/60c1ed9eE933be59e/5c77c8eabda19d0d.jpg!q70.jpg", + }, + ] + } +``` + +支持数据源 +```schema: scope="body" +{ + "type": "page", + "body": { + "type": "service", + "api": "/amis/api/mock2/sample?perPage=5", + "body": [ + { + "type": "lottery", + "source": "$rows", + } + ] + } +} +``` diff --git a/examples/components/Index.jsx b/examples/components/Index.jsx index e18938266d2..429a0cea4a6 100644 --- a/examples/components/Index.jsx +++ b/examples/components/Index.jsx @@ -1,16 +1,45 @@ +import Outline from '../../packages/amis-editor-core/static/outline.png'; export default { type: 'page', - title: '标题', - remark: { - title: '标题', - body: '这是一段描述问题,注意到了没,还可以设置标题。而且只有点击了才弹出来。', - icon: 'question-mark', - placement: 'right', - trigger: 'click', - rootClose: true - }, - body: '内容部分. 可以使用 \\${var} 获取变量。如: `\\$date`: ${date}', - aside: '边栏部分', - toolbar: '工具栏', - initApi: '/api/mock2/page/initData' + + body: { + type: 'lottery', + width: '300', + height: '300', + items: [ + { + name: "商品名称名称1", + pictureUrl: Outline, + }, + { + name: "没有中奖哦2", + pictureUrl: Outline, + }, + { + name: "商品名称名称3", + pictureUrl: Outline, + }, + { + name: "商品名称名称4", + pictureUrl: Outline, + }, + { + name: "商品名称名称5", + pictureUrl: Outline, + }, + { + name: "祥禾饽饽铺京东自营旗舰店6", + pictureUrl: Outline, + }, + { + name: "鲜花4+1束 鲜花速递7 ", + pictureUrl: Outline, + }, + { + name: "大连萨米托爱心樱桃8", + pictureUrl: Outline, + }, + ] + } + }; diff --git a/packages/amis-ui/scss/components/_lottery.scss b/packages/amis-ui/scss/components/_lottery.scss new file mode 100644 index 00000000000..7ac893fb8e8 --- /dev/null +++ b/packages/amis-ui/scss/components/_lottery.scss @@ -0,0 +1,91 @@ + +@use 'sass:color'; +@import 'node_modules/amis-ui/scss/mixins'; +@import 'node_modules/amis-ui/scss/functions'; +@import 'node_modules/amis-ui/scss/variables'; + + +.#{$ns}Lottery { + position: relative; + padding: px2rem(10px); + box-sizing: content-box; + background-color: #fdd130; + background-size: 100% 100%; + + .luckNineWrap { + box-sizing: content-box; + display: -webkit-flex; + display: flex; + justify-content: space-around; + align-content: space-around; + position: absolute; + background: #ffa00a; + padding: px2rem(5px); + left: 0; + right: 0; + top: 0; + bottom: 0; + margin: auto; + width: 90%; + height: 90%; + flex-wrap: wrap; + -webkit-flex-wrap: wrap; + border-radius: px2rem(15px); + overflow: hidden; + background-size: 100% 100%; + transition: all .05s ease; + + .active { + background-image: linear-gradient(#f69d7c, #f46c72) !important; + + .luckNineItem-title { + color: #fff !important; + } + } + + .luckNineItem { + border-radius: px2rem(10px); + padding: px2rem(5px); + background-image: linear-gradient(#ffefd1, #ffc8a4); + display: flex; + flex-direction: column; + justify-content: center; + width: 28%; + height: 28%; + align-items: center; + transition: all .05s ease; + + .luckNineItem-title { + margin-top: px2rem(5px); + font-size: px2rem(10px); + font-weight: bold; + white-space: nowrap; + text-overflow: ellipsis; + overflow: hidden; + width: px2rem(60px); + height: px2rem(15px); + text-align: center; + color: #be602b; + } + + .luckNineItem-img { + width: 60%; + height: 60%; + } + } + + .startBtn { + cursor: pointer; + background-image: linear-gradient(#ff733e, #ff4e57); + color: rgba(255, 212, 167, 1); + font-size: px2rem(30px); + font-weight: bold; + transition: all 1s ease-in; + } + + .startBtn:hover { + background-image: linear-gradient(#f69d7c, #f46c72); + transition: all 1s ease-in; + } + } +} diff --git a/packages/amis-ui/scss/themes/_common.scss b/packages/amis-ui/scss/themes/_common.scss index 7364fccf12b..1a5109abd87 100644 --- a/packages/amis-ui/scss/themes/_common.scss +++ b/packages/amis-ui/scss/themes/_common.scss @@ -150,3 +150,5 @@ @import '../components/verificationCode'; @import '../components/mobile-dev-tool'; + +@import '../components/lottery'; diff --git a/packages/amis-ui/src/components/Lottery.tsx b/packages/amis-ui/src/components/Lottery.tsx new file mode 100644 index 00000000000..dc221b1acac --- /dev/null +++ b/packages/amis-ui/src/components/Lottery.tsx @@ -0,0 +1,233 @@ +import React from 'react'; +import { + isPureVariable, + resolveVariableAndFilter, + themeable, + ThemeProps +} from 'amis-core'; +import {SchemaTokenizeableString} from 'amis/src/Schema'; + +interface SquareNineProps extends ThemeProps { + //宽度,默认300px + width?: string; + //高度,默认300px + height?: string; + //奖品列表 + items?: {name: string; pictureUrl: string; id: number}[]; + // 开始按钮 + children?: React.ReactNode; + //目标索引(中奖) + targetIndex?: number; + // 结束回调 + callback?: (index: number) => void; + // 数据源: 绑定当前环境变量, @default: '${items}' + source?: SchemaTokenizeableString; + //数据 + data: any; +} + +interface CallBackFn { + (now: number): number; +} + +interface SquareNineState { + off: 0 | 1; + now: number; + count: number; + speed: number; +} + +interface CustomElement extends HTMLElement { + start: (index: number, fn: CallBackFn) => void; + reset: () => void; + flag: 0 | 1; +} + +export class Lottery extends React.Component { + lightRef: React.RefObject = React.createRef(); + + list: Array = []; + constructor(props: SquareNineProps) { + super(props); + this.state = { + off: 1, + now: -1, + count: 0, + speed: 50 + }; + } + + start = (index: number, fn?: CallBackFn) => { + //开始抽奖 + if (!this.state.off) return; + this.setState({off: 0}); + this.changeFn(index, fn); + }; + + //重置抽奖状态 + reset = () => { + this.setState({off: 1}); + this.setState({now: -1}); + }; + + componentDidMount() { + if (this.lightRef.current) { + this.lightRef.current.start = this.start; + this.lightRef.current.reset = this.reset; + this.lightRef.current.flag = this.state.off; + } + } + + componentDidUpdate(prevProps: SquareNineProps, prevState: SquareNineState) { + if (this.lightRef.current && prevState.off !== this.state.off) { + this.lightRef.current.flag = this.state.off; + } + } + + //抽奖动画效果 + changeFn = (index: number, fn?: CallBackFn) => { + let now = this.state.now; + now = ++now % (this.list.length - 1); + this.setState({now: now}); + this.state.now == 0 && this.setState({count: this.state.count + 1}); + let timer = setTimeout(() => { + this.changeFn(index, fn); + }, this.state.speed); + if (this.state.count > 3) { + this.setState({speed: this.state.speed + 10}); + } + if (this.state.speed > 300 && this.state.now == index) { + timer && clearTimeout(timer); + this.setState({off: 1, count: 0, speed: 50}); + fn && fn(this.state.now); + this.props.callback && this.props.callback(this.state.now); + } + }; + + render() { + const {now} = this.state; + const {classnames: cx} = this.props; + const {source, items, data} = this.props; + const width = this.props.width || '300'; + const height = this.props.height || '300'; + + let list: any; + let value: any; + + if (typeof source === 'string' && isPureVariable(source)) { + list = resolveVariableAndFilter(source, data, '| raw') || undefined; + } else if (Array.isArray(items)) { + list = items; + } + + this.list = list; + + return ( +
+
+
+ 奖品图片 +
{this.list[0].name}
+
+
+ 奖品图片 +
{this.list[1].name}
+
+
+ 奖品图片 +
{this.list[2].name}
+
+
+ 奖品图片 +
{this.list[7].name}
+
+
this.start(this.props.targetIndex || 0)} + > + {this.props.children ? this.props.children : 开始} +
+
+ 奖品图片 +
{this.list[3].name}
+
+
+ 奖品图片 +
{this.list[6].name}
+
+
+ 奖品图片 +
{this.list[5].name}
+
+
+ 奖品图片 +
{this.list[5].name}
+
+
+
+ ); + } +} + +export default themeable(Lottery); diff --git a/packages/amis-ui/src/components/index.tsx b/packages/amis-ui/src/components/index.tsx index e57f4e38f88..e0fc6e1307a 100644 --- a/packages/amis-ui/src/components/index.tsx +++ b/packages/amis-ui/src/components/index.tsx @@ -144,6 +144,7 @@ import VerificationCode from './VerificationCode'; import Shape from './Shape'; import type {IShapeType} from './Shape'; import MobileDevTool from './MobileDevTool'; +import {Lottery} from './Lottery'; export { NotFound, @@ -287,5 +288,6 @@ export { VerificationCode, Shape, IShapeType, - MobileDevTool + MobileDevTool, + Lottery }; diff --git a/packages/amis/src/Schema.ts b/packages/amis/src/Schema.ts index 85b4d3f6ed8..970d0ab6b7d 100644 --- a/packages/amis/src/Schema.ts +++ b/packages/amis/src/Schema.ts @@ -130,12 +130,13 @@ import { SchemaClassName, SchemaExpression } from 'amis-core'; -import type {FormSchemaBase, TestIdBuilder} from 'amis-core'; +import type {FormSchemaBase} from 'amis-core'; import {MultilineTextSchema} from './renderers/MultilineText'; import {DateRangeSchema} from './renderers/DateRange'; import {PasswordSchema} from './renderers/Password'; import {WordsSchema} from './renderers/Words'; import {RadioControlSchema} from './renderers/Form/Radio'; +import {LotterySchema} from './renderers/Lottery'; // 每加个类型,这补充一下。 export type SchemaType = @@ -258,6 +259,7 @@ export type SchemaType = | 'input-signature' | 'input-verification-code' | 'shape' + | 'lottery' // editor 系列 | 'editor' @@ -498,6 +500,7 @@ export type SchemaObject = | DateRangeSchema | MultilineTextSchema | PasswordSchema + | LotterySchema | WordsSchema; export type SchemaCollection = diff --git a/packages/amis/src/minimal.ts b/packages/amis/src/minimal.ts index e28407e43ea..5cad502515a 100644 --- a/packages/amis/src/minimal.ts +++ b/packages/amis/src/minimal.ts @@ -820,6 +820,11 @@ registerRenderer({ type: 'pdf-viewer', getComponent: () => import('./renderers/PdfViewer') }); +// import './renderers/Lottery'; +registerRenderer({ + type: 'lottery', + getComponent: () => import('./renderers/Lottery') +}); // import './renderers/AMIS'; registerRenderer({ type: 'amis', diff --git a/packages/amis/src/renderers/Lottery.tsx b/packages/amis/src/renderers/Lottery.tsx new file mode 100644 index 00000000000..1bc6b84badf --- /dev/null +++ b/packages/amis/src/renderers/Lottery.tsx @@ -0,0 +1,61 @@ +import React from 'react'; +import { + anyChanged, + getPropValue, + IListStore, + isPureVariable, + Renderer, + RendererProps, + resolveVariableAndFilter +} from 'amis-core'; +import {Lottery as SquareNineComponent} from 'amis-ui'; +import {SchemaTokenizeableString} from '../Schema'; + +import type {BaseSchema} from 'amis'; +import {ListProps} from './List'; + +interface SquareNineProps extends RendererProps { + width?: number; + height?: number; + items: {name: string; pictureUrl: string; id: number}[]; + source: SchemaTokenizeableString; + children?: React.ReactNode; +} +/** + * Lottery 九宫格抽奖。 + * 文档:https://aisuda.bce.baidu.com/amis/zh-CN/components/lottery + */ + +export interface LotterySchema extends BaseSchema { + /** + * 指定为提示框类型 + */ + type: 'lottery'; + + //宽度,默认300px + width?: number; + //高度,默认300px + height?: number; + //奖品列表 + items: {name: string; pictureUrl: string; id: number}[]; + // 开始按钮 + children?: React.ReactNode; + //目标索引(中奖) + targetIndex?: number; + // 结束回调 + callback?: (index: number) => void; + // 数据源: 绑定当前环境变量, @default: '${items}' + source?: SchemaTokenizeableString; +} + +@Renderer({ + type: 'lottery' +}) +export class LotteryRenderer extends React.Component< + Omit & RendererProps +> { + render() { + const {items, ...rest} = this.props; + return ; + } +}