Commit 4a775d5e authored by mqf_0707's avatar mqf_0707

组件合并

parents 7ef34a85 3c965caf
......@@ -50,7 +50,10 @@ const apiList = {
method: 'GET',
isShowLoading: false,
toastError: false
}
},
getRotatePrizeListInfo:'getRotatePrizeListInfo',//获取奖品列表接口,暂未实现
drawRotatePrize:'drawRotatePrize',//大转盘抽奖接口,暂未实现
}
// 生成API
......
import React, { useState, useEffect } from "react";
import { View, Image, Text } from "@tarojs/components";
import LoadingBar,{LOAD_TYPE} from "../LoadingBar/LoadingBar";
import styles from './BrowserLoading.module.less'
import {BROWSE_COUNTDOWN_OBJ} from '@/config/browse.config'
const BrowserLoading = (props) => {
const {loadFlag,count} = props;
return (
<View className={styles["browse_loading"]}>
{loadFlag == BROWSE_COUNTDOWN_OBJ.NUMBER && (
<>
<View className={styles["count_img"]}>
<View className={styles["count_time"]}>{count}s</View>
</View>
</>
)}
{loadFlag == BROWSE_COUNTDOWN_OBJ.PICTURE && <LoadingBar type={LOAD_TYPE.TASK} />}
</View>
);
};
export default BrowserLoading;
.browse_loading {
width: 200px;
height: 200px;
position: fixed;
right: 20px;
top: 400px;
border: 1px solid cyan;
box-sizing: border-box;
}
.count_img {
width: 100%;
height: 100%;
border: 1px solid rgb(47, 131, 99);
box-sizing: border-box;
background-image: url("//yun.dui88.com/7f705da9-5f16-4cf1-a239-3903c5c23995.png");
.image-property(contain, center center);
}
.count_time {
width: 100px;
height: 60px;
line-height: 60px;
margin: 40px auto 0;
border: 1px solid cyan;
box-sizing: border-box;
font-size: 40px;
font-weight: 600;
color: #000;
text-align: center;
}
##### 是否展示浏览页中的倒计时
+ 入参
| 配置项 | 类型 | 描述 | 默认值 | 备注 |
| :-------- | :-----: | :--------: | :-----: | :---:|
| loadFlag | String | 倒计时类型"NUMBER","PICTURE" | 默认空 '' | NUMBER:数字倒计时 PICTURE:loading倒计时(默认15s,可在css中修改) |
| count | Number | 倒计时时间 | 15s | 默认倒计时15s(可改配置) |
+ 使用
```jsx
import BrowserLoading from '../BrowserLoading/BrowserLoading'
export const Index = () => {
return(
<BrowserLoading loadFlag={task.loadFlag} count={count} />
)
}
```
-------
###### loadingBar组件
+ 入参
| 配置项 | 类型 | 描述 | 默认值 | 备注 |
| :-------- | :-----: | :--------: | :-----: | :---:|
| bg | String | 背景loading | 'https://yun.duiba.com.cn/spark/assets/5a725b9e4ebf436a0a92c6317a5a4e47d87c8c1f.png' | / |
| bgTop | String | 上层loading | 'https://yun.duiba.com.cn/spark/assets/ccc405e28a5140d3aff9dff27b97fa5fb6bd7ff5.png' | / |
| type | String | "TASK","NORMAL" | 'NORMAL' | "TASK":自动loading倒计时 "NORMAL":静态的loading进度显示 |
| total | Number | loading总值 | / | type为"NORMAL"传入 |
| current | Number | 当前值 | 0 | type为"NORMAL"传入 |
+ 使用
```jsx
import LoadingBar,{LOAD_TYPE} from "../LoadingBar/LoadingBar";
export const Index = () => {
return(
<LoadingBar type={LOAD_TYPE.NORMAL} total={100} current={10} />
<LoadingBar type={LOAD_TYPE.TASK} />
)
}
```
\ No newline at end of file
import React, { useState } from 'react'
import React, { useState, useEffect, useRef } from 'react'
import { View, Image, ScrollView } from '@tarojs/components'
import classnames from 'classnames'
import tbccTs from 'tbcc-sdk-ts'
......
import React,{useState, useEffect} from 'react'
import {View,Image,Text} from '@tarojs/components'
import styles from './LoadingBar.module.less'
export const LOAD_TYPE = {
'TASK':"TASK",
'NORMAL':"NORMAL"
}
const LoadingBar = (props) => {
const {
bg='https://yun.duiba.com.cn/spark/assets/5a725b9e4ebf436a0a92c6317a5a4e47d87c8c1f.png',
bgTop='https://yun.duiba.com.cn/spark/assets/ccc405e28a5140d3aff9dff27b97fa5fb6bd7ff5.png',
type='',
total=0,
current=0
} = props;
const [percent,setPercent] = useState(0);
useEffect(()=>{
showCurrentLoading();
},[])
/**
* @description 显示当前进度(进度条上没有其他内容)
*/
const showCurrentLoading = () => {
if(type == LOAD_TYPE.NORMAL){
let cur = current >= total ? toal : current;
setPercent((cur / total * 100).toFixed(1))
}
}
return(
<View className={styles['load_container']} >
<View className={styles['load_content']} >
<View className={styles['load_bg']} style={{backgroundImage:`url(${bg})`}} ></View>
<View
className={`${styles['load_top']} ${type == LOAD_TYPE.TASK && 'loadAni'}`}
style={{
backgroundImage:`url(${bgTop})`,
clipPath:`${type==LOAD_TYPE.NORMAL && `inset(0% 0% 0% ${percent}%)`}`
}}
></View>
</View>
</View>
)
}
export default LoadingBar
\ No newline at end of file
.load_container{
.load_content{
width: 200px;
height: 80px;
margin: 0 auto;
overflow: hidden;
}
.load_bg,
.load_top{
width: 200px;
height: 60px;
position: absolute;
background-size: cover;
background-repeat: no-repeat;
}
.loadAni{
animation: move 15s linear forwards;
}
}
@keyframes move {
0%{
clip-path: inset(0% 0% 0% 0%);
}
99%{
opacity: 1;
}
100%{
clip-path: inset(0% 100% 0% 0%);
opacity: 0;
}
}
\ No newline at end of file
# 排行榜组件
---
## 何时使用
- 当某个页面需要展示累计邀请、分等级奖励进度条、累计签到等。
## 目录结构
`RankTitle` 排行榜头部
`RankList` 排行榜列表
## RankTitle API
| 参数 | 说明 | 类型 | 默认值 | 版本 |
| --------- | ------------ | ----------- | ------------------------------------------------------- | ---- |
| titleList | 表头 | ArrayString | - | |
| myRank | 个人排名信息 | Object | { rank: '11', userNick: '用户名', rankValue: '534443' } | |
## RankList API
| 参数 | 说明 | 类型 | 默认值 | 版本 |
| -------- | ----------- | ----------- | -------- | ---- |
| rankList | 排名信息 | ArrayObject | 见示例一 | |
| rankIcon | 前三名 Icon | Object | 见示例二 | |
## 示例一
```
[{
rank: 1,
userNick: '用户昵称',
hotValue: 98882892
}]
```
## 示例二
```
{
1: '//yun.duiba.com.cn/taobaomini/pike_call/icon_06.png',
2: '//yun.duiba.com.cn/taobaomini/pike_call/icon_07.png',
3: '//yun.duiba.com.cn/taobaomini/pike_call/icon_08.png'
}
```
import React ,{ memo } from 'react'
import { View, Image, ScrollView } from '@tarojs/components'
import { RANK_LIST } from '@/mock'
import styles from './RankList.module.less'
const RANK_ICON = {
1: '//yun.duiba.com.cn/taobaomini/pike_call/icon_06.png',
2: '//yun.duiba.com.cn/taobaomini/pike_call/icon_07.png',
3: '//yun.duiba.com.cn/taobaomini/pike_call/icon_08.png'
}
const RankList = memo((props) => {
const { rankList = RANK_LIST, rankIcon = RANK_ICON } = props
return (
<ScrollView scrollY className={styles['scroll-container']}>
{rankList.map((itm, i) => {
return (
<View
className={`${styles['scroll-item']} ${
styles['scroll-item' + (i + 1)]
}`}
>
<View className={styles['scroll-item__rank']}>
{i <= 2 && (
<Image className={styles['rank-icon']} src={rankIcon[i + 1]} />
)}
<View className={styles['rank-num']}>{itm.rank}</View>
</View>
<View className={styles['scroll-item__user']}>{itm.userNick}</View>
<View className={styles['scroll-item__score']}>
{itm.rankValue}
</View>
</View>
)
})}
</ScrollView>
)
})
export default RankList
import React, { memo } from 'react'
import { View } from '@tarojs/components'
import styles from './RankTitle.module.less'
const RankTitle = memo((props) => {
const {
titleList = ['排名', '用户名', '人气值'],
myRank = { rank: '11', userNick: '用户名', rankValue: '534443' }
} = props
return (
<View className={styles['rank-box']}>
<View className={styles['rank-box__title']}>
{titleList.map(val => {
return <View className={styles['box-itm']}>{val}</View>
})}
</View>
<View className={styles['rank-box__info']}>
<View className={styles['box-itm']}>{myRank.rank}</View>
<View className={styles['box-itm']}>{myRank.userNick}</View>
<View className={styles['box-itm']}>{myRank.rankValue}</View>
</View>
</View>
)
})
export default RankTitle
.rank-box {
.wh(100%,168px);
}
.rank-box__title {
.wh(100%,102px);
.flex-row-space();
.image('//yun.duiba.com.cn/taobaomini/pike_call/rank_title_bg.png');
}
.rank-box__info {
.wh(100%,66px);
.flex-row-space();
background: linear-gradient(-90deg, #FFDE8B 0%, #FFEAB5 100%);
}
.box-itm {
height: 66px;
width: 33%;
.flex-row-center();
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
}
.box-itm:first-child {
font-size: 30px;
font-family: YouSheBiaoTiHei;
font-weight: 600;
color: #2D2B28;
}
.box-itm:nth-child(2) {
width: 34%;
font-size: 26px;
font-family: SourceHanSansCN;
font-weight: 400;
color: #666666;
}
.box-itm:last-child {
font-size: 28px;
font-family: WorkSans;
font-weight: 600;
color: #2D2B28;
}
\ No newline at end of file
import { RankList, RankTitle } from '..'
const Demo = () => {
return (
<>
<RankTitle />
<RankList />
</>
)
}
export default Demo
export { default as RankList } from './RankList/RankList'
export { default as RankTitle } from './RankTitle/RankTitle'
import React, { useState } from 'react'
import { View, Image, ScrollView } from '@tarojs/components'
import classnames from 'classnames'
import { RANK_LIST } from '@/mock'
import styles from './RankList.module.less'
const RANK_ICON = {
1: '//yun.duiba.com.cn/taobaomini/pike_call/icon_06.png',
2: '//yun.duiba.com.cn/taobaomini/pike_call/icon_07.png',
3: '//yun.duiba.com.cn/taobaomini/pike_call/icon_08.png'
}
function RankList(props) {
const { rankList = RANK_LIST, minNum = 2 } = props
return (
<ScrollView scrollY className={styles['scroll-container']}>
{
rankList.map((itm,i) => {
return(
<View className={`${styles['scroll-item']} ${styles['scroll-item'+(i+1)]}`}>
<View className={styles['scroll-item__rank']}>
{i <= minNum && <Image className={styles['rank-icon']} src={RANK_ICON[i+1]} />}
<View className={styles['rank-num']}>{itm.rank}</View>
</View>
<View className={styles['scroll-item__user']}>{itm.userNick}</View>
<View className={styles['scroll-item__score']}>{itm.rankValue}</View>
</View>
)
})
}
</ScrollView>
)
}
export default RankList
\ No newline at end of file
import React, { useState } from 'react'
import { View, Image, ScrollView } from '@tarojs/components'
import classnames from 'classnames'
import styles from './RankTitle.module.less'
function RankTitle(props) {
const { titleList = ['排名','用户名','人气值'], myRank = { rank: '11', userNick: '用户名', hotValue: '534443' } } = props
return (
<View className={styles['rank-box']}>
<View className={styles['rank-box__title']}>
{
titleList.map(val => {
return(
<View className={styles['box-itm']}>{val}</View>
)
})
}
</View>
<View className={styles['rank-box__info']}>
<View className={styles['box-itm']}>{myRank.rank}</View>
<View className={styles['box-itm']}>{myRank.userNick}</View>
<View className={styles['box-itm']}>{myRank.hotValue}</View>
</View>
</View>
)
}
export default RankTitle
\ No newline at end of file
.rank-box {
.wh(100%,100px);
}
.rank-box__title,.rank-box__info {
.wh(100%,40px);
.flex-row-space();
}
.box-itm {
height: 100%;
width: 33%;
.flex-row-center();
}
.box-itm:nth-child(2) {
width: 34%;
}
.rank-box__info {
background-color: #000;
color: #ffffff;
}
\ No newline at end of file
import React,{useState, useEffect} from 'react'
import {View,Image,Text} from '@tarojs/components'
import styles from './RotateWheel.module.less'
import {useThrottle} from '@/hooks/useThrottle'
import API from '@/api'
import { prizeList } from '@/mock'
import Taro,{showToast,redirectTo,navigateTo} from '@tarojs/taro'
const oneTurn = 360;
const RotateWheel = (props) => {
const {
bg='https://yun.duiba.com.cn/spark/assets/8b6e920ffd09fab8f9ac2de09f9154879f4d0607.png',
ratio=0.65,
radius=300,
circles=4,
divideNum=8,
duration=5000,
timeFunction='ease-out',
nodeInfo={width:100,height:100},
imgInfo={width:80,height:80},
showWay="negative",
isShowPrizeName=true,
prizeNameWidthRatio=1.2,
callback=()=>{}
} = props;
/* 奖品列表 */
const [prizelist,setPrizelist] = useState(prizeList)
/* 平分角度 */
const [angle,setAngle] = useState(oneTurn / divideNum)
/* my.createAniamation */
const [myAnimation,setMyAnimation] = useState()
/* 展示奖品列表 */
const [show,setShow] = useState(false)
/* 转动一圈时的角度值 */
const [rotateInOneTurn,setRotateInOneTurn] = useState(0)
/* 动画参数 */
const [ani,setAni] = useState({startFlag:false,option:{duration:duration,timeFunction:timeFunction,rotate:0}})
useEffect(()=>{
computePosition()
},[prizeList])
/**
* @description 获取奖品列表
*/
// const getPrizeListInfo = async () => {
// const {success,data,code,message} = await API.getRotatePrizeListInfo().catch((res)=>{
// showToast({title:res?.message ? res?.message : '网络异常,请稍后再试'})
// })
// if(success && data){
// const {list} = data;
// // setPrizelist(list)
// computePosition(list)
// }
// }
/**
* @description 计算奖品位置,旋转角度
*/
const computePosition = () => {
if(!prizeList.length) return;
let centerX,centerY,color,rotate;
/* 获取每块奖品的中心位置 */
prizelist.forEach((ele, i)=>{
if (i % 2 == 0) {
color = '#527aff';
} else {
color = '#f13082'
}
// 当前奖品左上角位置
if(showWay === 'negative'){
centerX = radius - ratio * radius * (Math.sin((angle / 2 + angle * (i)) / 180 * Math.PI));
centerY = radius - ratio * radius * (Math.cos((angle / 2 + angle * (i)) / 180 * Math.PI));
rotate = -(angle / 2 + angle * i);
} else {
centerX = radius - ratio * radius * (Math.sin((angle / 2 + angle * (-i)) / 180 * Math.PI));
centerY = radius - ratio * radius * (Math.cos((angle / 2 + angle * (-i)) / 180 * Math.PI));
// 旋转角度
rotate = -(angle / 2 + angle * (-i));
}
ele.rotate = rotate
ele.color = color
// 奖品位置
let disleft = centerX - nodeInfo.width / 2;
let distop = centerY - nodeInfo.height / 2;
ele.centerX = disleft
ele.centerY = distop
ele.num = i;
// 测试:设置奖品Id
ele.prizeId = 100 + i;
})
console.log('prizelist',prizelist);
setPrizelist(prizelist)
setShow(true)
}
/**
* @description 抽奖
*/
const drawPrize = useThrottle( async() => {
// const {success,data,message,code} = await API.drawRotatePrize().catch((res)=>{
// showToast({title:res?.message ? res?.message : '网络异常,请稍后再试'})
// });
// if(success && data){
// const {prizeId,id} = data;
// }
// test
let prizeId = Math.floor(Math.random() * prizelist.length) + 100;
startRotation(prizeId)
setTimeout(() => {
// 弹出弹窗
callback && callback()//prizeInfo
}, duration + 1000);
},duration)
/**
* @description 根据奖品id开始旋转
* @param {*} prizeId 抽中的奖品id
*/
const startRotation = (prizeId) => {
console.log('start');
let rotateAngle;
let num;
let oneturnAngle;
prizelist.forEach((ele)=>{
if(ele.prizeId === prizeId){
num = ele.num;
}
})
// 旋转角度 ( 旋转圈数 + 对应位置(0,7) ) * 平分角度 + 平分角度 / 2
if(showWay === 'negative'){
rotateAngle = oneTurn * circles + num * angle + angle / 2;
oneturnAngle = num * angle + angle / 2;
} else {
rotateAngle = oneTurn * circles + (prizelist.length - num) * angle + angle / 2;
oneturnAngle = (prizelist.length - num) * angle + angle / 2
}
console.log('rotateAngle',rotateAngle, num, prizeId);
/* 执行动画 */
setAni({
startFlag:true,
option:{
...ani.option,
rotate:ani.option.rotate - rotateInOneTurn + rotateAngle
}
})
setRotateInOneTurn(oneturnAngle)
}
// my.createAnimation 旋转动画rot:旋转角度,timegap:旋转时间
// const animateRotation = (rot, timegap) => {
// var animation = my.createAnimation({
// transformOrigin: "center center",
// duration: 5000,
// timeFunction: "ease-out",
// delay: 0
// })
// console.log('animation',animation);
// animation.rotate(rot).step();
// setMyAnimation(animation.export())
// setTimeout(() => {
// setMyAnimation()
// }, 5000);
// }
return(
<>
<View className={`${styles['rotate_container']}`}
style={{
backgroundImage:`url(${bg})`,
transform:ani.startFlag ? `rotate(${ani.option.rotate}deg)` : '',
transition:ani.startFlag ? `all ${ani.option.timeFunction} ${ani.option.duration}ms` : ''
}}
// animation={myAnimation}
>
{
show && prizelist.length && prizelist.map((ele, i)=>{
return(
<View
className={styles['item']}
style={{
// backgroundColor:`${ele?.color}`,
width:`${nodeInfo.width/100}rem`,
height:`${nodeInfo.height/100}rem`,
transform:`rotate(${ele?.rotate}deg)`,
top:`${ele.centerY/100}rem`,
left:`${ele.centerX/100}rem`,
position:'absolute'
}}
key={'item'+i}>
{
isShowPrizeName &&
<View
className={styles['prize_name']}
style={{
width:`${prizeNameWidthRatio * 100}%`,
height:`${30/100}rem`,
}}
>{ele.name}</View>
}
<View className={styles['prize_img']}
style={{
backgroundImage:`url(${ele.image})`,
width:`${imgInfo.width/100}rem`,
height:`${imgInfo.height/100}rem`
}}
></View>
</View>
)
})
}
</View>
<View className={styles['circle']} onTap={drawPrize}>draw</View>
</>
)
}
export default RotateWheel
\ No newline at end of file
.rotate_container{
width: 100%;
height: 100%;
// border: 1px solid cyan;
// box-sizing: border-box;
position: relative;
border-radius: 50%;
display: flex;
justify-content: center;
align-items: center;
.image-property(cover, center center);
.item{
position: absolute;
display: flex;
justify-content: space-evenly;
align-items: center;
transform-origin: center;
flex-direction: column;
// border: 1px solid cyan;
// box-sizing: border-box;
.prize_name{
// margin: 0 auto;
// border: 1px solid cyan;
// box-sizing: border-box;
// display: flex;
// justify-content: center;
// align-items: center;
text-align: center;
white-space: nowrap;
font-size: 18px;
text-overflow: ellipsis;
overflow: hidden;
}
.prize_img{
.image-property(cover, center center);
margin: 0 auto;
}
}
}
.circle{
// border-radius: 50%;
width: 100px;
height: 100px;
background-color: rgb(78, 159, 165);
display: flex;
justify-content: center;
align-items: center;
position: absolute;
clip-path: polygon(50% 0%,0% 100%,100% 100%);
}
<mxfile host="65bd71144e">
<diagram id="aWOBYljMTH3HiLopJFIH" name="第 1 页">
<mxGraphModel dx="899" dy="594" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="827" pageHeight="1169" math="0" shadow="0">
<root>
<mxCell id="0"/>
<mxCell id="1" parent="0"/>
<mxCell id="3" value="" style="ellipse;whiteSpace=wrap;html=1;aspect=fixed;shadow=1;fillColor=#FFFFFF;" vertex="1" parent="1">
<mxGeometry x="200" y="160" width="300" height="300" as="geometry"/>
</mxCell>
<mxCell id="6" value="" style="endArrow=none;dashed=1;html=1;" edge="1" parent="1">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="200" y="310" as="sourcePoint"/>
<mxPoint x="500" y="310" as="targetPoint"/>
</mxGeometry>
</mxCell>
<mxCell id="8" value="" style="endArrow=none;dashed=1;html=1;entryX=0.5;entryY=0;entryDx=0;entryDy=0;exitX=0.5;exitY=1;exitDx=0;exitDy=0;fillColor=#FF9999;" edge="1" parent="1" source="3" target="3">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="430" y="330" as="sourcePoint"/>
<mxPoint x="480" y="280" as="targetPoint"/>
</mxGeometry>
</mxCell>
<mxCell id="9" value="" style="rounded=0;whiteSpace=wrap;html=1;shadow=0;fillColor=#FFCCCC;rotation=45;" vertex="1" parent="1">
<mxGeometry x="379" y="210" width="70" height="70" as="geometry"/>
</mxCell>
<mxCell id="10" value="" style="rounded=0;whiteSpace=wrap;html=1;shadow=0;fillColor=#FFCCCC;rotation=45;" vertex="1" parent="1">
<mxGeometry x="379" y="340" width="70" height="70" as="geometry"/>
</mxCell>
<mxCell id="11" value="" style="rounded=0;whiteSpace=wrap;html=1;shadow=0;fillColor=#FFCCCC;rotation=45;" vertex="1" parent="1">
<mxGeometry x="250" y="210" width="70" height="70" as="geometry"/>
</mxCell>
<mxCell id="12" value="" style="rounded=0;whiteSpace=wrap;html=1;shadow=0;fillColor=#FFCCCC;rotation=45;" vertex="1" parent="1">
<mxGeometry x="250" y="340" width="70" height="70" as="geometry"/>
</mxCell>
<mxCell id="13" value="1.找到每个奖品的中心位置&lt;br&gt;2.每个奖品旋转相应角度&lt;br&gt;3.给相应的奖品区域设置样式" style="text;html=1;strokeColor=none;fillColor=none;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;shadow=0;" vertex="1" parent="1">
<mxGeometry x="490" y="180" width="250" height="50" as="geometry"/>
</mxCell>
<mxCell id="16" value="旋转逻辑" style="rounded=0;whiteSpace=wrap;html=1;shadow=0;strokeColor=#000000;fillColor=#FFCCCC;" vertex="1" parent="1">
<mxGeometry x="80" y="510" width="120" height="60" as="geometry"/>
</mxCell>
<mxCell id="17" value="给每个奖品设置一个索引值,抽中的奖品id与奖品列表对应,则根据索引值旋转对应的角度。" style="text;html=1;strokeColor=none;fillColor=none;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;shadow=0;" vertex="1" parent="1">
<mxGeometry x="80" y="590" width="200" height="110" as="geometry"/>
</mxCell>
<mxCell id="18" value="" style="ellipse;whiteSpace=wrap;html=1;aspect=fixed;shadow=0;strokeColor=#000000;fillColor=#FFCCCC;" vertex="1" parent="1">
<mxGeometry x="310" y="270" width="80" height="80" as="geometry"/>
</mxCell>
</root>
</mxGraphModel>
</diagram>
</mxfile>
\ No newline at end of file
**记事本打开**
##### 大转盘配置项入参
| 配置项 | 类型 | 描述 | 默认值 | 备注 |
| :-------- | :-----: | :--------: | :-----: | :---:|
| bg | String | 转盘背景 | / | / |
| radius | Number | 转盘半径大小 | 300 | px |
| ratio | Number | 距离中心点的比例系数 | 0.65 | 小于1 |
| divideNum | Number | 转盘划分为几等分 | 8 | 最少2等分|
| circles | Number | 转盘基本转动圈数 | 4 | / |
| duration | Number | 转盘持续时间 | 4000 | 毫秒 |
| timeFunction | String | 缓动动画方式 | 'ease-out' | 'ease-in-out', cubic-bezier(xx,xx,xx,xx) |
| nodeInfo | Object | 单个奖品区域大小 | {width:100,height:100} | 宽高100px |
| imgInfo | Object | 奖品图片大小 | {width:80,height:80} | 宽高80px |
| showWay | String | 奖品排列方式 | 'negative' |negative逆时针排列 positive顺时针排列|
| isShowPrizeName | Boolean | 是否展示奖品名称 | true | true展示 false不展示 |
| prizeNameWidthRatio | Number | 奖品名称容器宽度与nodeInfo宽度的比值 | 1.2 | 超过容器宽度则...省略 |
##### 出参
| 配置项 | 类型 | 描述 | 默认值 | 备注 |
| :-------- | :-----: | :--------: | :-----: | :---:|
| callback | Function | 回调函数 | / |抽中奖品后的回调方法,后续的弹窗处理|
+ 使用
```jsx
import RotateWheel from '@/components/_tb_comps/RotateWheel/RotateWheel'
const rotateConfig = { //不传走默认配置
bg: 'https://yun.duiba.com.cn/spark/assets/8b6e920ffd09fab8f9ac2de09f9154879f4d0607.png',
ratio: 0.65,
radius: 300,
circles: 4,
divideNum: 8,
duration: 5000,
timeFunction:'ease-out',
nodeInfo:{
width:100,
height:100
},
imgInfo:{
width:80,
height:80
},
showWay:'negative',//positive/negative
isShowPrizeName:true
}
export const Index = () => {
return(
<RotateWheel {...rotateConfig} callback={()=>{}} />
)
}
```
\ No newline at end of file
......@@ -4,24 +4,69 @@ import Taro, { useShareAppMessage, useDidShow } from '@tarojs/taro'
import classnames from 'classnames'
import styles from './ScrollXView.module.less'
function ScrollXView(props) {
const { prizeList = [], marginRight = 26, width = 300, height = 300 } = props
const {
prizeList = [],
containerWidth = 300,
containerHeight = 300,
imgWidth=240,
imgHeight=240,
isShowPrizeName=true,
prizeInCenterNum=2,
marginRight=26,
backgroundColor='#E7E7EF'
} = props
const showPrize = classnames(styles['show-pize-box'],{
[`${styles['show-pize-box__two']}`]: prizeList.length === 1
[`${styles['show-pize-box__two']}`]: prizeList.length <= prizeInCenterNum,
[`${styles['show-pize-box__more']}`]: prizeList.length > prizeInCenterNum,
})
return (
<>
<ScrollView scrollX className={styles['scroll-x-box']}>
{<View className={showPrize} style={prizeList.length > 1?{width: (prizeList.length * width + (prizeList.length) * marginRight + 100) / 100 + 'rem'}: { width: '100%'}}>
{<View className={showPrize}>
{
prizeList.map(itm => {
prizeList.map((itm,i) => {
return(
<View style={{ width: width / 100 +'rem',height: height / 100 +'rem'}} className={styles['show-pirze__item']}>
<View className={styles['pirze__item-img']}>
<Image src={itm.image} className={styles['pirze__item-img-icon']} />
<View
key={'prize'+i}
className={styles['show-pirze__item']}
style={{
width:`${containerWidth/100}rem`,
height:`${containerHeight/100}rem`,
backgroundColor:backgroundColor,
marginRight:`${((prizeList.length > prizeInCenterNum) &&(prizeList.length - 1) !== i) ? `${marginRight/100}rem` : ''}`
}}
>
<View
className={styles['pirze__item-img']}
style={{
width:`${imgWidth/100}rem`,
height:`${imgHeight/100}rem`
}}
>
<Image
src={itm.image}
className={styles['pirze__item-img-icon']}
style={{
width:`${imgWidth/100}rem`,
height:`${imgHeight/100}rem`
}}
/>
</View>
<View className={styles['pirze__item-name']}>{itm.name}</View>
{
isShowPrizeName &&
<View
className={styles['pirze__item-name']}
style={{
height:`${60/100}rem`,
// lineHeight:`${60/100}rem`,
fontSize:`${24/100}rem`
}}
>{itm.name}</View>
}
</View>
)
})
......
##### ScrollXView滑动配置项
+ 入参
| 配置项 | 类型 | 描述 | 默认值 | 备注 |
| :-------- | :-----: | :--------: | :-----: | :---:|
| prizeList | Array | 滑动数据 | [...] | / |
| containerWidth | Number | 单个奖品容器的宽度 | 300 | / |
| containerHeight | Number | 单个奖品容器的高度 | 300 | / |
| imgWidth | Number | 奖品图片宽度 | 200 | / |
| imgHeight | Number | 奖品图片高度 | 200 | / |
| isShowPrizeName | Boolean | 是否展示奖品名称 | true | true:展示 false:不展示 |
| prizeInCenterNum | Number | 奖品少于n个时居中展示 | n=2 | 多于n个,可滑动查看 |
| marginRight | Number | 奖品容器之间间距 | 26 | px |
| backgroundColor | Number | 奖品容器背景颜色 | #E7E7EF | / |
+ prizeList数组格式
```json
[
{
"image":"",
"name":"xxx",
// ...
},
// ...
]
```
+ 使用
```jsx
import ScrollXView from '@/components/_tb_comps/ScrollXView/ScrollXView'
// 组件不一定在该目录下,举例说明一下
const SCROLLXVIEW_CONFIG = { //不传走默认配置
prizeList: [],
containerWidth : 300,
containerHeight : 300,
imgWidth:200,
imgHeight:200,
isShowPrizeName:true,
prizeInCenterNum:2,
marginRight:26,
backgroundColor:'#E7E7EF'
}
export const Index = () => {
return(
<View className="scroll">
<ScrollXView {...SCROLLXVIEW_CONFIG} prizeList={prizeList} />
</View>
)
}
```
\ No newline at end of file
......@@ -10,37 +10,58 @@
align-items: center;
}
.show-pize-box__two {
width: 100%;
display: flex;
justify-content: center;
justify-content: space-around;
align-items: center;
}
.show-pirze__item {
background: #E7E7EF;
// background: #E7E7EF;
border-radius: 25px;
margin-left: 26px;
}
.show-pirze__item:last-child {
margin-right: 26px;
// margin-left: 26px;
display: flex;
flex-direction: column;
justify-content: space-around;
align-items: center;
}
.show-pirze__item:first-child {
margin-left: 10px;
// .show-pirze__item:last-child {
// margin-right: 26px;
// }
// .show-pirze__item:first-child {
// margin-left: 10px;
// }
.show-pize-box__more{
// padding: 10px;
}
.pirze__item-img {
width: 224px;
height: 224px;
// width: 224px;
// height: 224px;
background: #FFFFFF;
border-radius: 25px;
margin: 16px auto 20px;
}
.pirze__item-img-icon {
width: 224px;
height: 224px;
// width: 224px;
// height: 224px;
border-radius: 25px;
.image-property(cover, center center);
}
.pirze__item-name {
font-size: 25px;
width: 90%;
font-family: FZLTHProGlobal;
font-weight: 400;
color: #1C1C1C;
text-align: center;
overflow: hidden;
text-overflow: ellipsis;
// 单行
// white-space: nowrap;
// 多行
// display: -webkit-box;
// -webkit-box-orient:vertical;
// -webkit-line-clamp: 2;
}
\ No newline at end of file
import React, { useState, useEffect,useRef } from "react";
import { View, Image, Text, Swiper, SwiperItem } from "@tarojs/components";
import './SwiperView.less'
import Taro,{showToast,redirectTo,navigateTo} from '@tarojs/taro'
export const EASING_WAYS = {
'DEFAULT':'default',
'LINEAR':'linear',
'EASEIN':'easeInCubic',
'EASEOUT':'easeOutCubic',
'EASEINOUT':'easeInOutCubic'
}
const SwiperView = (props) => {
/**
* @description swiperList
* {image,content,hasBtn}
*/
const {
swiperList=[],
isSpecial=true,
easingFunction=`${EASING_WAYS.LINEAR}`,
circular=true,
autoplay=false,
vertical=false,
duration=1000,
interval=1500,
disableTouch=false
} = props;
const [currentIndex, setCurrentIndex] = useState(0);
/**
* @description onchange事件
* @param {*} e
*/
const swiperChange = e => {
console.log("e", e);
setCurrentIndex(e.detail.current);
};
/**
* @description 入场/出场动画
* @param {*} index 数组索引值
* @returns 返回类名
*/
const useCustomAni = (index) => {
return `${currentIndex > 0 && currentIndex == index && 'move_in'} ${currentIndex - 1 == index && 'move_out'}`
}
/**
* @description 获取当前卡片详情之类
*/
const getCurrentDetail = (item) => {
showToast({
title:'111'
})
}
return (
<View className="scroll_container">
<Swiper
className="swiper_group"
current={currentIndex}
easingFunction={easingFunction}
onChange={swiperChange}
circular={circular}
vertical={vertical}
autoplay={autoplay}
duration={duration}
interval={interval}
disableTouch={disableTouch}
>
{swiperList.length &&
swiperList.map((item, i) => (
<SwiperItem>
<View
className={`scroll_item ${i === currentIndex && isSpecial ? "current_item" : ""} ${useCustomAni(i)}`}
>
{/* {i === currentIndex && (
<Image
className="checkPic"
src="https://yun.dui88.com/tebuXinYuan/checkGoods.png"
></Image>
)} */}
<View className="item_img_content">
{/* 单个swiper主要内容 */}
<Image
className="img"
style={{
width:`${400/100}rem`,
height:`${240/100}rem`
}}
src={item.image}></Image>
{
item?.content &&
<View
className='text_content'
style={{
width:'100%',
height:`${160/100}rem`
}}
>
<View className='text' >{item.content}</View>
</View>
}
{
item?.hasBtn &&
<View className='item_btn' >
<View className='right_btn' onTap={()=>getCurrentDetail(item)}>按钮{i}</View>
</View>
}
</View>
</View>
</SwiperItem>
))}
</Swiper>
</View>
);
};
export default SwiperView;
.scroll_container{
width: 450px;
margin: 300px auto 0;
overflow: hidden;
.swiper_group {
margin: 0 auto;
width: 450px;
overflow: visible;
position: relative;
swiper-item {
width: 450px;
display: flex;
justify-content: center;
align-items: center;
}
.scroll_item {
width: 400px;
margin: 0 auto;
position: relative;
border-radius: 20px;
.image-property(cover, top center);
.checkPic {
width: 60px;
height: 60px;
border-radius: 50%;
position: absolute;
right: 40px;
top: 40px;
}
.item_img_content {
width: 100%;
// padding: 20px;
.img {
margin: 20px auto 0;
border-radius: 20px;
background-repeat: no-repeat;
background-position: top center;
}
.text_content{
margin: 0 auto;
box-sizing: border-box;
padding: 16px;
display: flex;
justify-content: center;
align-items: center;
.text{
color: #232323;
text-align: center;
font-size: 24px;
padding: 16px;
}
}
.item_btn{
width: 100%;
.right_btn{
// background-image: url('https://yun.duiba.com.cn/spark/assets/33bc06a1be4bf250345e24900bc5719992328d4e.png');
// .image-property(cover, center center);
width: 200px;
height: 65px;
margin: 0 auto;
border-radius: 40px;
border: 2px solid rgb(79, 172, 118) ;
text-align: center;
line-height: 65px;
font-size: 26px;
// animation: scales 2s infinite;
}
}
}
&.current_item {
.item_img_content {
.img{}
.text_content{
.text{}
}
.item_btn{
.right_btn{}
}
}
}
// &.move_in{
// animation: movein 0.8s linear forwards;
// }
// &.move_out{
// animation: moveout 0.8s linear ;
// }
}
}
}
@keyframes movein {
from {
transform: perspective(1000px) translate3d(0,0,0) rotate3d(0, 1, 0, 25deg) scale(0.85);
transform-style: preserve-3d;
transform-origin: 45% center;
}
50%{
transform: perspective(1000px) translate3d(0,20px,0) rotate3d(0, 1, 0, 25deg) scale(0.85);
transform-style: preserve-3d;
transform-origin: 45% center;
}
to {
transform: perspective(1100px) translate3d(0,0,0) rotate3d(0, 1, 0, 0deg) scale(1);
transform-origin: 45% center;
transform-style: preserve-3d;
}
}
@keyframes moveout{
from {
transform: perspective(1000px) rotate3d(0, 1, 0, 0deg) scale(1);
transform-style: preserve-3d;
transform-origin: 45% center;
}
50%{
transform: perspective(1000px) rotate3d(0, 1, 0, 25deg) scale(1);
transform-style: preserve-3d;
transform-origin: 45% center;
}
to {
transform: perspective(1200px) rotate3d(0, 1, 0, 25deg) scale(0.85);
transform-origin: 45% center;
transform-style: preserve-3d;
}
}
@keyframes scales {
from{
transform: scale(1,1);
transform-origin: center;
}
50%{
transform: scale(1.1,1.1);
transform-origin: center;
}
to{
transform: scale(1,1);
transform-origin: center;
}
}
\ No newline at end of file
##### swiperView 滑动
| 配置项 | 类型 | 描述 | 默认值 | 备注 |
| :-------- | :-----: | :--------: | :-----: | :---:|
| swiperList | Array | 滑动数组 | [...] | / |
| disableTouch | Boolean | 是否可手动滑动 | false | true 可滑动 false 不可滑动 |
| autoplay | Boolean | 是否自动切换 | false |/|
| circular | Boolean | 是否采用衔接滑动| false |/|
| vertical | Boolean | 是否垂直切换| false |/|
| duration | Number | 滑动动画时长| 1000 |/|
| interval | Number | 自动切换时间间隔| 1500 |/|
| easingFunction|String | 缓动动画类型| linear |'default','linear' 'easeInCubic' 'easeOutCubic' 'easeInOutCubic'|
#### swiperList数据
```json
[
{
"image":"",//图片链接,图片宽高需要在less中修改
"content":"",//图片描述
"hasBtn":"",//注入数据(是否有按钮)
// 或者手动修改组件中的字段名称 ...
},
// ...
]
```
+ 使用
```jsx
import SwiperView from '@/components/_tb_comps/SwiperView/SwiperView'
const swiperConfig = { //不传走默认配置
swiperList:[],
isSpecial:true,
easingFunction:`linear`,
circular:true,
autoplay:false,
vertical:false,
duration:1000,
interval:1500,
disableTouch:false
}
export const Index = () => {
return(
<SwiperView {...swiperConfig} swiperList={swiperList} />
)
}
```
\ No newline at end of file
/**
* 全局配置
*/
import React from 'react'
export const BtnTexts = {
UNREACH: '',
REACH_UNRECEIVED: '',
REACH_RECEIVED: ''
}
export const ProgressGlobalConfig = {
type: 1,
maxLevel: 15,
width: 500,
BtnTexts
}
export const ProgressContext = React.createContext()
import { View, Image } from '@tarojs/components'
import { memo, useContext, useMemo } from 'react'
import classnames from 'classnames'
import { ProgressContext } from '../ProgressContext'
// css
import styles from './index.module.less'
const ProgressNode = memo(props => {
const {
descInfo,
iconInfo,
isReceived = false,
isReach,
levelValue,
levelPos,
id
} = props
const {
BtnTexts,
maxLevel,
nodeClassName,
iconImgClassName,
iconTxtClassName,
iconClassName,
descClassName,
btnClassName,
activeBtnClassName,
disableBtnClassName,
onReceviceClick
} = useMemo(() => useContext(ProgressContext), ProgressContext)
// 节点按钮点击
const btnClick = () => {
return isReach && !isReceived ? onReceviceClick(id) : null
}
// 判断方法
const checkFunc = fn => {
return typeof fn === 'function'
}
// 判断对象是否为空
const checkObject = obj => {
return obj instanceof Object && JSON.stringify(obj) !== '{}'
}
const getNodeStyle = classnames(styles['defualt-progress-node'], {
[nodeClassName]: nodeClassName
})
const getDescStyle = classnames(styles['defualt-progress-node-desc'], {
[descInfo?.className]: descInfo?.className,
[descClassName]: descClassName
})
const getIconStyle = classnames(styles['defualt-progress-node-icon'], {
[iconInfo?.className]: iconInfo?.className,
[iconClassName]: iconClassName
})
const getIconImgStyle = classnames(
styles['defualt-progress-node-icon-iconImg'],
{
[iconImgClassName]: iconImgClassName
}
)
const getIconTxtStyle = classnames(styles['defualt-progress-node-icon-txt'], {
[iconTxtClassName]: iconTxtClassName
})
const getBtnStyle = classnames(styles['defualt-progress-node-btn-txt'], {
[btnClassName]: !isReach && btnClassName,
[activeBtnClassName]: isReach && !isReceived && activeBtnClassName,
[disableBtnClassName]: isReach && isReceived && disableBtnClassName
})
return (
<View
className={getNodeStyle}
style={{
left: levelPos ? levelPos : (levelValue / maxLevel) * 100 + '%'
}}
>
{/* 描述 */}
{checkObject(descInfo) && (
<View className={getDescStyle}>{descInfo.descTxt}</View>
)}
{/* icon */}
{checkObject(iconInfo) && (
<View className={getIconStyle}>
{iconInfo?.iconUrl && (
<Image className={getIconImgStyle} src={iconInfo.iconUrl} />
)}
{iconInfo?.iconTxt && (
<View className={getIconTxtStyle}>{iconInfo.iconTxt}</View>
)}
</View>
)}
{/* 节点按钮 */}
{checkFunc(onReceviceClick) && (
<View className={getBtnStyle} onTap={btnClick}>
{isReach
? isReceived
? BtnTexts.REACH_RECEIVED
: BtnTexts.REACH_UNRECEIVED
: BtnTexts.UNREACH}
</View>
)}
</View>
)
})
export default ProgressNode
.defualt-progress-node {
display: flex;
flex-direction: column;
position: absolute;
z-index: 10;
font-size: 20px;
transform: translate(-100%, -100%);
white-space: nowrap;
}
// 描述
.defualt-progress-node-desc {
position: absolute;
right: -110px;
top: -54px;
z-index: 11;
}
// Icon
.defualt-progress-node-icon {
.wh(32px, 32px);
}
// Icon贴图
.defualt-progress-node-icon-iconImg {
.wh(155px, 151px);
position: absolute;
top: -54px;
}
// Icon文本
.defualt-progress-node-icon-txt {
font-size: 22px;
}
// 按钮
.defualt-progress-node-btn-txt {
position: absolute;
top: 80px;
left: 6px;
}
# 进度条组件
---
## 何时使用
- 当某个页面需要展示累计邀请、分等级奖励进度条、累计签到等。
## 目录结构
`ProgressContext` 进度条全局默认配置
`ProgressNode` 进度条节点
## API
| 参数 | 说明 | 类型 | 默认值 | 版本 |
| --- | --- | --- | --- | --- |
| levelList | 节点数据 | ArrayObject | 见下面示例一 | |
| currentValue | 当前进度值 | Numbver | 0 | |
| className | 进度条节点组样式 | Object | - | |
| maxLevel | 最大值 | Number | 15 | |
| width | 进度条长度 | Number | 500 | |
| BtnTexts | 进度按钮状态文本 | Object | {UNREACH: '待解锁',REACH_UNRECEIVED: '已解锁',REACH_RECEIVED: '已领取'} | |
| onReceviceClick | 点击进度按钮回调 | Function | (id)=>{} | |
| nodeClassName | 节点样式 | Object | - | |
| iconImgClassName | icon图片样式 | Object | - | |
| iconTxtClassName | icon文本样式 | Object | - | |
| descClassName | 描述样式 | Object | - | |
| btnClassName | 进度按钮样式 | Object | - | |
| activeBtnClassName | 进度按钮可点样式 | Object | - | |
| disableBtnClassName | 进度按钮不可用样式 | Object | - | |
## 示例一
```
[
{
id: 0, //节点id
levelValue: 0, //节点值
isReceived: true, //是否已领取
iconInfo: {
iconUrl: '//yun.dui88.com/tebuXinYuan/main-circle-tip.png',
iconTxt: ''
},
descInfo: {
descTxt: '等级1'
}
},
{
id: 1,
levelValue: 5,
isReceived: false,
iconInfo: {
iconUrl: '//yun.dui88.com/tebuXinYuan/main-circle-tip.png',
iconTxt: ''
},
descInfo: {
descTxt: '等级2'
}
}
]
```
import React, { useState } from 'react'
import { View } from '@tarojs/components'
import Progress from '../index'
import styles from './index.module.less'
const initLevelList = [
{
id: 0,
levelValue: 0,
isReceived: true,
iconInfo: {
iconUrl: '//yun.dui88.com/tebuXinYuan/main-circle-tip.png',
iconTxt: ''
},
descInfo: {
descTxt: '等级1'
}
},
{
id: 1,
levelValue: 5,
isReceived: false,
iconInfo: {
iconUrl: '//yun.dui88.com/tebuXinYuan/main-circle-tip.png',
iconTxt: ''
},
descInfo: {
descTxt: '等级2'
}
},
{
id: 2,
levelValue: 10,
isReceived: false,
iconInfo: {
iconUrl: '//yun.dui88.com/tebuXinYuan/main-circle-tip.png',
iconTxt: ''
},
descInfo: {
descTxt: '等级3'
}
},
{
id: 3,
levelValue: 15,
isReceived: false,
iconInfo: {
iconUrl: '//yun.dui88.com/tebuXinYuan/main-circle-tip.png',
iconTxt: ''
},
descInfo: {
descTxt: '等级4'
}
}
]
const Demo = props => {
const [levelList, setLevelList] = useState(initLevelList)
const [currentValue, setCurrentValue] = useState(10)
const onReceviceClick = id => {
console.log('点击:', id)
}
return (
<View className={styles['progress']}>
<Progress
levelList={levelList}
currentValue={currentValue}
btnClassName={styles['btnClassName']}
activeBtnClassName={styles['activeBtn']}
disableBtnClassName={styles['disableBtn']}
onReceviceClick={onReceviceClick}
/>
</View>
)
}
export default Demo
.progress {
margin-top: 100px;
display: flex;
justify-content: center;
}
.disableBtn {
.wh(108px, 42px);
.image('//yun.dui88.com/tebuXinYuan/main-received-btn.png');
}
.activeBtn {
.wh(108px, 42px);
.image('//yun.dui88.com/tebuXinYuan/main-waitReceive-btn.png');
}
.btnClassName {
.wh(108px, 42px);
.image('//yun.dui88.com/tebuXinYuan/main-waitUnlock-btn.png');
}
import { View } from '@tarojs/components'
import { memo, useMemo, useCallback } from 'react'
import classnames from 'classnames'
import ProgressNode from './ProgressNode'
import styles from './index.module.less'
import {
ProgressContext,
ProgressGlobalConfig
} from './ProgressContext'
const Progress = memo(props => {
const {
levelList = [],
currentValue = 0,
className,
maxLevel = ProgressGlobalConfig.maxLevel,
width = ProgressGlobalConfig.width
} = props
const LevelList = useMemo(() => {
return levelList
}, levelList)
// 合并全局配置
const mergeConfigToDefault = useCallback(config => {
return { ...ProgressGlobalConfig, ...config }
}, [])
/**
* 过滤props
* @param {*} props 过滤对象
* @param {*} filterPropArr 需要过滤掉的属性名称
* @returns
*/
const filterProps = (props, filterPropArr) => {
if (typeof props !== 'object' || !Array.isArray(filterPropArr)) {
throw new Error('参数格式不正确')
}
let obj = {}
Object.keys(props)
.filter(key => !filterPropArr.includes(key))
.map(key => {
obj[key] = props[key]
})
return obj
}
const getProgressStyle = classnames(styles['defualt-progress'], {
[className]: className
})
const getProItm = classnames(styles['progress-box-com'], {
[`${styles['progress-box-com-02']}`]: currentValue === maxLevel
})
return (
// 全局配置注入
<ProgressContext.Provider
value={mergeConfigToDefault(
filterProps(props, ['levelList', 'currentValue'])
)}
>
{/* 进度条-all */}
<View
className={styles['progress-box']}
style={{ width: width / 100 + 'rem' }}
>
{/* 进度条-current */}
<View
className={getProItm}
style={{
width:
currentValue >= maxLevel
? '100%'
: (currentValue / maxLevel) * 100 + '%'
}}
/>
{/* 进度条节点 */}
<View className={getProgressStyle}>
{LevelList.map((item, index) => {
return (
<ProgressNode
key={index}
{...item}
isReach={item.levelValue <= currentValue}
/>
)
})}
</View>
</View>
</ProgressContext.Provider>
)
})
export default Progress
.progress-box {
width: 692px;
height: 20px;
background: #371b15;
// border: 1px solid #371B15;
// box-shadow: 0px 0px 3px 0px #4D3328;
border-radius: 4px;
position: relative;
}
.defualt-progress {
}
.progress-box-com {
height: 20px;
background: linear-gradient(270deg, #ffde8b 0%, #ffce38 100%);
border-radius: 4px;
position: absolute;
top: 0;
left: 0;
transition: all 0.5s cubic-bezier(0, 0.64, 0.36, 1);
}
export default {
export const BROWSE_CONFIG = {
taskType: 'browseGoods',
type: '01',
bg: '//yun.dui88.com/taobaomini/clientCTest/goods_bg@2x.png',
......@@ -10,5 +10,17 @@ export default {
image: {
img: '//yun.duiba.com.cn/taobaomini/clientCTest/goods_img@2x.png'
},
color: '#181818'
color: '#181818',
countDown: ''
}
/*
浏览页倒计时标识
DEFAULT: 默认为空,不展示
PICTURE: loading进度条显示倒计时
NUMBER: 数字倒计时(15s)
*/
export const BROWSE_COUNTDOWN_OBJ = {
PICTURE:'PICTURE',
NUMBER:"NUMBER",
DEFAULT:""
}
\ No newline at end of file
......@@ -50,7 +50,7 @@ export const TASK_CONFIG = {
},
jumpType: 1, // 1 跳转浏览15s 才算完成 2 跳转即算完成
browseTime: 15, // 浏览时间
browseType: 3, // 1 浏览15s 才算完成 2 点击就算完成任务 3 浏览指定商品详情页 15s
browseType: 1, // 1 浏览15s 才算完成 2 点击就算完成任务 3 浏览指定商品详情页 15s
showTaskType: 1,// 1 弹窗 2 列表
orderType: 1, // 1 集合页下单 2 跳转店铺
}
\ No newline at end of file
......@@ -25,3 +25,13 @@ export const HELP_MODAL_SHOW = {
SHOW: 1, // 已弹
NOT_SHOW: 2, // 未弹
}
/* 背景音乐 应用:牧场消消乐有效,其他应用需要重新上传文件 */
export const BGMUSIC_URL = {
MUSIC:'cloud://FBE3C154661ACEEBA164E8B70B4D71E2/yoga bgm.mp3'
}
/* 点击音效 */
export const CLOUD_OBJ = {
'1':'cloud://0BD38A1B739CCF6A552396F3FE2D5A09/开始-音效.mp3',
'2':'cloud://93FBC274B6B43CE37B0A70DF8C1AFA52/答题-音效.mp3',
'3':'cloud://B8F0BA22BBA74F8FEA261E1CC9D55355/欢呼声.mp3'
}
\ No newline at end of file
/**
* @description 音乐
*/
import React, { useState, useEffect } from "react";
import { playBgMusic1 } from "@/utils/audio";
const app = getApp();
/**
* @description 一个页面不能同时create两个音频文件
* @param {*} url cloud文件链接
* @param {*} isBgMusic 是否自动调用(背景音乐自动调用),非背景音乐(点击音效)手动调用
* @returns
*/
export const useAudio = (url, isBgMusic=false) => {
const [musicUrl, setMusicUrl] = useState();
// 若需要控制所有的音效状态,app上自定义一个变量
const [openStatus,setOpenStatus] = useState(true);
useEffect(() => {
getMusicInfo();
}, []);
useEffect(()=>{
if(isBgMusic){
console.log('enter use audio function with bg music');
playAudio();
}
},[musicUrl,openStatus])
/**
* @description 获取音频url链接
*/
const getMusicInfo = async () => {
const resultSrc = await app.cloud.file.getTempFileURL({ fileId: url });
console.log('resultSrc--------hook',resultSrc);
if (resultSrc[0]){
setMusicUrl(resultSrc[0].url);
}
};
/**
* @description 播放音效
* @param {boolean} selfStatus 是否单独控制音效状态
* @param {function} callbackFn 播放之后的回调
*/
const playAudio = (callbackFn=()=>{}) => {
console.log('musicUrl',musicUrl,'selfStatus',openStatus)
musicUrl && playBgMusic1(openStatus, musicUrl, callbackFn);
};
return {
playAudio,
openStatus,
setOpenStatus,
musicUrl
};
};
##### useAudio
```jsx
const {playAudio, openStatus, setOpenStatus, musicUrl} = useAudio(url,isBgMusic)
```
##### 入参
| 入参 | 类型 | 描述 | 默认值 | 备注 |
| :--- | :---: | :---: |:---: | :---: |
| url | String | cloud开头的链接 | 'cloud:xxxx.mp3' | 当前应用云存储上的链s接才有效 |
| isBgMusic | Boolean | 是否自动加载背景音乐/音效 | false | / |
##### 出参
| 出参 | 类型 | 描述 | 默认值 | 备注 |
| :--- | :---: | :---: |:---: | :---: |
| playAudio | Function | 播放音乐/音效 | 有回调函数callback | callback默认为空函数 |
| openStatus | Boolean | 默认开启播放 | true | / |
| setOpenStatus | Function | 改变开始/暂停状态 | / | true/false都会调用playMusic1方法|
| musicUrl | String | cloud文件得到的音频url链接 | 'https:xxxx.mp3' | 音频文件url链接|
+ 使用
```jsx
import {useAudio} from '@/hooks/useAudio'
import {CLOUD_OBJ} from '@/const'
export const Index = () => {
const {playAudio,musicUrl,setOpenStatus,openStatus} = useAudio(CLOUD_OBJ['1'])
const gotoMainPage = () => {
setOpenStatus(!openStatus)
playAudio(()=>{
// ...场景:点击有音效,跳转的页面有背景音乐
navigateTo({url:`/pages/other/other`})
});
// TODO...
}
return(
<>
<View onTap={gotoMainPage}>点击音效button</View>
</>
)
}
import {useAudio} from '@/hooks/useAudio'
import {BGMUSIC_URL} from '@/const'
export const Other = () => {
// 自动播放背景音乐
const {playAudio,setOpenStatus,openStatus} = useAudio(CLOUD_OBJ['1'],true)
return(
<View>
other page
</View>
)
}
```
+ 所有音效状态统一, 则需要在useAudio中设置一个全局变量(app.openStatus)
+ 音效缓存,音效关闭,下次进入音效还是关闭
```js
let status = my.getStorageSync({ key: 'musicStatus' });
console.log('status',status);
if(status.data == undefined || status.data === null || status.data === ''){
app.openStatus = true;
my.setStorageSync({ key: 'musicStatus',data:true })
} else if(status?.data == true){
app.openStatus = true;
} else if(status?.data == false){
app.openStatus = false;
}
const {openStatus,setOpenStatus} = useAudio(CLOUD_OBJ['2'],true)
/**
* @description 切换音效状态
*/
const handleMusic = () => {
setOpenStatus(status => !status)
/* 切换音效状态 */
app.openStatus = !app.openStatus;
let musicStatus = my.getStorageSync({ key: 'musicStatus' }).data;
console.log('musicStatus-----初始值---->>>>>>>>>>',musicStatus);
my.setStorageSync({ key: 'musicStatus',data:!musicStatus });
let musics = my.getStorageSync({ key: 'musicStatus' }).data;
console.log('musicStatus----改变值----->>>>>>>>>>',musics);
}
```
\ No newline at end of file
This diff is collapsed.
......@@ -16,6 +16,15 @@ import { useEffect } from 'react'
const { navigateTo, openDetail, showSharePanel } = tbcc.tb
const { getImgShareUrl, checkIsMember, validateActivityStatus } = tbcc.utils
import {useAudio} from '@/hooks/useAudio'
import {CLOUD_OBJ, BGMUSIC_URL} from '@/const'
import ScrollXView from '@/components/_tb_comps/ScrollXView/ScrollXView'
import SwiperView from '@/components/_tb_comps/SwiperView/SwiperView'
import RotateWheel from '@/components/_tb_comps/RotateWheel/RotateWheel'
const INDEX_CONFIG = {
bg: '', // 背景 cloud://C4015B7CEC23CF013A78247E2852524F//火箭待机小球抖动.json
ruleButton: '', // 规则按钮
......@@ -24,6 +33,36 @@ const INDEX_CONFIG = {
taskButton: '' // 任务按钮
}
const rotateConfig = {
bg: 'https://yun.duiba.com.cn/spark/assets/8b6e920ffd09fab8f9ac2de09f9154879f4d0607.png',
ratio: 0.65,
radius: 300,
circles: 4,
divideNum: 8,
duration: 5000,
timeFunction:'ease-in-out',
nodeInfo:{
width:100,
height:100
},
imgInfo:{width:80,height:80},
showWay:'negative',//positive/negative
isShowPrizeName:true,
prizeNameWidthRatio:1.2
}
const SCROLLXVIEW_CONFIG = {
prizeList: [],
containerWidth : 300,
containerHeight : 300,
imgWidth:200,
imgHeight:200,
isShowPrizeName:true,
prizeInCenterNum:2,
marginRight:26,
backgroundColor:'#E7E7EF'
}
function Index() {
const app = getApp()
const { activityInfo, setActivityInfoAndStatus } = useActivityInfoModel()
......@@ -41,6 +80,16 @@ function Index() {
// 助力弹窗标识
const showHelp = useRef(false)
/* 背景音乐 */
const {playAudio,musicUrl,setOpenStatus,openStatus} = useAudio(CLOUD_OBJ['1'])
const playCurrentAudio = () => {
// setOpenStatus(!openStatus)
playAudio(()=>{
onHandleIndex('prize')
})
}
const fetchActivityInfo = async() => {
const { success, data } = await API.getActivityBaseInfoById()
if(success) {
......@@ -56,7 +105,7 @@ function Index() {
success && setUserInfo(data)
}
useEffect(() =>{
fetchActivityInfo()
// fetchActivityInfo()
// getImgShareUrl('cloud://CEFE74AE67921906B5AF842150646D35/share.png').then(url => {
// SHARE_IMG.current = url
// })
......@@ -64,7 +113,7 @@ function Index() {
// 授权登录完成
useLogin(async (info) => {
handleVisibleModal(info)
setUpdateFlag(1)
// setUpdateFlag(1)
})
// 查看是否有助力信息
const getShareInfo = async () => {
......@@ -128,7 +177,7 @@ function Index() {
updateActInfo && fetchActivityInfo()
},
'rule': () => setRuleModalVisible(true),
'prize': () => navigateTo('/pages/packagePrize/myPrize/myPrize'),
'prize': () => {navigateTo('/pages/packagePrize/myPrize/myPrize')},
'game': () => navigateTo('/pages/packageGame/game/game')
}
Fn[type]()
......@@ -142,6 +191,29 @@ function Index() {
<View className={styles.games} style={{ backgroundImage: `url(${INDEX_CONFIG.taskButton})` }} onClick={() =>onHandleIndex('game') }>游戏</View>
<View className={styles.bemember} onTap={() => setMemberVisible(true)}>入会</View>
{/* 奖品查看左右滑动 */}
{/* <ScrollXView {...SCROLLXVIEW_CONFIG} prizeList={prizeList} /> */}
{/* swiper左右滑动 */}
{/* <SwiperView swiperList={prizeList} /> */}
{/* 大转盘 */}
{/* <View className={styles['rotate']}
style={{
width:`${600/100}rem`,
height:`${600/100}rem`,
margin:`${100/100}rem auto`,
display:'flex',
justifyContent:'center',
alignItems:'center'
}} >
<RotateWheel {...rotateConfig} callback={()=>{}} />
</View> */}
<View className='view' onTap={playCurrentAudio}>play audio</View>
{
tasksModalVisible &&
<TasksModal
......
......@@ -8,6 +8,7 @@
left: 0;
right: 0;
bottom: 0;
background-image: linear-gradient(45deg,#fbc2eb 10%, #a6c1ee);
}
.index_button() {
.wh(150px, 46px);
......
......@@ -2,7 +2,6 @@ import React, { useEffect, useState, useRef } from 'react'
import { View } from '@tarojs/components'
import { useDidShow, useRouter, useDidHide, getApp } from '@tarojs/taro'
import API from '@/api'
import { TASK_CONFIG } from '@/const'
import { useLogin, useLoginFromShare } from '@/hooks/useLogin'
import { useActivityInfoModel, useLoginInfoModel } from '@/store'
import {
......
......@@ -3,31 +3,35 @@ import { View } from '@tarojs/components'
import ContainerFit from '@/components/_base/ContainerFit/ContainerFit'
import GoodsTitle from '@/components/_tb_comps/TitleImg/TitleImg'
import GoodsList from '@/components/_tb_comps/GoodsList/GoodsList'
import BrowserLoading from '@/components/_tb_comps/BrowserLoading/BrowserLoading'
import { useDidShow, useRouter, useDidHide } from '@tarojs/taro'
import API from '@/api'
import { commonToast } from 'tbcc-sdk-ts/lib/core/tb'
import styles from './browseGoods.module.less'
import { TASK_CONFIG, BROSE_GOOD_TYPE } from '@/config/task.config'
import BROWSE_CONFIG from '@/config/browse.config'
import { BROWSE_CONFIG } from '@/config/browse.config'
import { GOOD_LIST } from '@/mock'
function BrowseGoodsPage() {
const { params: { itemIds, keepTime } } = useRouter()
const { browseType, browseTime = 15 } = TASK_CONFIG
const [ startTimer, setStartTimer ] = useState(false)
const preBrowseTime = useRef(null)
const currentItemId = useRef('')
const usePercent = useRef(0)
const browserTimer = useRef(null)
const [goodsList, setGoodsList] = useState([])
const [accumulateTime, setAccumulateTime] = useState(+keepTime || 15)
const { browseGoodType, browseTime = 15 } = TASK_CONFIG
const { params: { itemIds, keepTime } } = useRouter()
const browserTimer = useRef(null)
const [goodsList, setGoodsList] = useState(GOOD_LIST)
useEffect(() => {
getGoodsList()
}, [])
// 浏览集合页15s
useEffect(() => {
if(browseGoodType === BROSE_GOOD_TYPE.PAGE) {
!browserTimer.current && goodsList.length && browseGoodsTimes()
if(browseType === BROSE_GOOD_TYPE.PAGE) {
(!browserTimer.current && goodsList.length) && browseGoodsTimes()
return () => browserTimer.current && clearInterval(browserTimer.current)
}
},[goodsList])
......@@ -39,11 +43,11 @@ function BrowseGoodsPage() {
useDidShow(() => {
// 重新回到页面开启倒计时
if(browseGoodType === BROSE_GOOD_TYPE.PAGE && startTimer && usePercent.current < +browseTime) {
if(browseType === BROSE_GOOD_TYPE.PAGE && startTimer && usePercent.current < +browseTime) {
browseGoodsTimes()
}
console.warn('回到该页面了')
if (browseGoodType === BROSE_GOOD_TYPE.DETAIL && preBrowseTime.current && ((Date.now() - preBrowseTime.current) / 1000) >= browseTime) {
if (browseType === BROSE_GOOD_TYPE.DETAIL && preBrowseTime.current && ((Date.now() - preBrowseTime.current) / 1000) >= browseTime) {
onCompleteTask()
currentItemId.current = ''
preBrowseTime.current = null
......@@ -53,7 +57,6 @@ function BrowseGoodsPage() {
preBrowseTime.current = null
}
})
useDidHide(() => {
// 页面关闭清除定时器
if(browserTimer.curren) {
......@@ -90,18 +93,21 @@ function BrowseGoodsPage() {
currentItemId.current = itemId
}
}
handleFn[browseGoodType]()
handleFn[browseType]()
}
// 浏览页面定时器
const browseGoodsTimes = () => {
browserTimer.current = setInterval(() => {
usePercent.current += 1
console.warn(usePercent.current)
const keepTime_ = browseTime
console.log('enter the browse time ');
usePercent.current += 1
setAccumulateTime(+keepTime_ - usePercent.current)
console.warn(usePercent.current,keepTime_)
if (usePercent.current >= keepTime_) {
onCompleteTask()
clearInterval(browserTimer.current)
usePercent.current = 0
setAccumulateTime(0)
}
}, 1000)
}
......@@ -113,8 +119,13 @@ function BrowseGoodsPage() {
<View className={styles['page-content__list']}>
<GoodsList goodsList={goodsList} task={BROWSE_CONFIG} onOpenDetail={(itemId,collected) => onOpenDetail(itemId,collected)} />
</View>
{
BROWSE_CONFIG.countDown &&
<BrowserLoading loadFlag={BROWSE_CONFIG.countDown} count={accumulateTime} />
}
</ContainerFit>
)
}
export default BrowseGoodsPage
......@@ -19,6 +19,7 @@ const enameStatus = { ..._enameStatus, ...commonStatus }
const { commonToast, navigateToOutside, setClipboard } = tbccTs.tb
const { getMyPrizeList } = API
import {useAudio} from '@/hooks/useAudio'
function Empty(blankTxt) {
return <View className={styles['my-prize-item__empty']}>{blankTxt}</View>
......@@ -32,6 +33,8 @@ function MyPrizeList() {
const [ memberVisible, setMemberVisible ] = useState(false)
const [ deliveryModalVisible, setDeliveryModalVisible ] = useState(false)
// const {openStatus,setOpenStatus} = useAudio(BGMUSIC_URL.MUSIC,true)
useEffect(() => {
fetchMyPriceList()
}, [])
......
import React, { useEffect, useState, useRef } from 'react'
import { View } from '@tarojs/components'
import { useDidShow, useRouter, useDidHide, getApp } from '@tarojs/taro'
import API from '@/api'
import { useLogin, useLoginFromShare } from '@/hooks/useLogin'
import { useActivityInfoModel, useLoginInfoModel } from '@/store'
import {useAudio} from '@/hooks/useAudio'
import {CLOUD_OBJ, BGMUSIC_URL} from '@/const'
import ScrollXView from '@/components/_tb_comps/ScrollXView/ScrollXView'
import SwiperView from '@/components/_tb_comps/SwiperView/SwiperView'
import RotateWheel from '@/components/_tb_comps/RotateWheel/RotateWheel'
export default function PreComp(props) {
return(
<View>
</View>
)
}
\ No newline at end of file
import { useState } from 'react'
import { createModel } from 'hox'
function useUserInfo() {
const [ activityInfo, setUserInfo ] = useState({
success: false,
gameTimes: 0
})
return {
activityInfo,
setUserInfo
}
}
export const useUserInfoModel = createModel(useUserInfo)
/**
* 创建音频类
*/
class AudioModel {
constructor(opts = {}) {
const { src } = opts;
if (!src) {
console.error("请输入音频链接");
return false;
}
try {
const innerAudioContext = my.createInnerAudioContext();
Object.keys(opts).forEach(item => {
innerAudioContext[item] = opts[item];
});
return innerAudioContext;
} catch (e) {
console.error("当前版本不支持音频");
}
}
}
/**
* 创建音频实例
* @param {object} opts 配置对象 例 { src: '音频链接', autoplay: false }
*/
export const createAudio = opts => {
const audioInstance = new AudioModel(opts);
return audioInstance;
};
/**
* 获取系统信息
* @returns {object} 小程序系统信息
*/
export const getSystemInfo = () => {
return new Promise((resolve, reject) => {
my.getSystemInfo({
success: res => {
resolve(res);
},
fail: err => {
reject(err);
}
});
});
};
let bgs = {};
export const playBgMusic1 = async (
isOn,
src,
callbackFn = () => {},
loop = false
) => {
//待测试
for (let k in bgs) {
if (bgs[k].isOn) {
bgs[k].stop();
}
// bgs[k].isOn = false; /// TODO 自己记一个开关,因为淘宝的玩意stop之后虽然听不到,但还是在播放,会触发loop播放和onEnded事件
}
const key = src;
console.log("---------->>>>>>>>>>>>>ison", isOn);
if (isOn) {
if (!bgs[key]) {
console.log("enter inner before the createAudio function", src, loop);
bgs[key] = createAudio({
src: src,
autoplay: true,
loop: loop /// TODO 暂时无效?2020.08.12 目前有效,但stop后还会跑数据,导致到音频结束到时间又自动播放
});
console.log("----------------src", src, bgs[key], "bgs[key]");
bgs[key].onPlay(() => {
bgs[key].isOn = true;
});
bgs[key].onStop(() => {
bgs[key].isOn = false;
});
const systemInfo = await getSystemInfo();
console.log(systemInfo, "systemInfo-------systemInfo");
/// TODO监听结束,因为loop无效 2020.08.12 loop有效,这个操作是为了解决stop之后还会跑音频数据的问题
bgs[key].onEnded(() => {
if (bgs[key].isOn) {
// 判断自己记录的开关
if (systemInfo?.platform.includes("Android")) {
// 安卓
bgs[key].play();
} else if (
systemInfo?.platform.includes("iOS") ||
systemInfo?.platform.includes("iPhone OS ")
) {
// ios
bgs[key].isOn = false;
}
delete bgs[key];
callbackFn && callbackFn();
}
});
bgs[key].play(); // 安卓有时候autoplay好像莫得用
// console.log(src)
// bgs[key].isOn = true; /// TODO 自己记一个开关,因为淘宝的玩意stop之后虽然听不到,但还是在播放,会触发loop播放和onEnded事件
} else {
console.log("---------->>>>>>>>>>>>>not enter ");
bgs[key].play();
// bgs[key].isOn = true; /// TODO 自己记一个开关,因为淘宝的玩意stop之后虽然听不到,但还是在播放,会触发loop播放和onEnded事件
callbackFn && callbackFn();
}
} else {
if (bgs[key]) {
// bgs[key].pause(); /// pause 垃圾,不能用
console.log("test stop function is work or not");
bgs[key].stop();
// bgs[key].isOn = false; /// TODO 自己记一个开关,因为淘宝的玩意stop之后虽然听不到,但还是在播放,会触发loop播放和onEnded事件
callbackFn && callbackFn();
} else {
callbackFn && callbackFn();
}
}
};
......@@ -166,6 +166,20 @@ export const getActivity = (sellerId) => {
});
})
}
/**
* 获取系统信息
* @returns {object} 小程序系统信息
*/
export const getSystemInfo = () => {
return new Promise((resolve, reject) => {
my.getSystemInfo({
success: res => { resolve(res); },
fail: err => { reject(err); }
});
});
};
// 查询商家会员等级
export const getGrade = (sellerId) => {
const memberBenefitPlugin = requirePlugin("memberBenefit")
......@@ -185,3 +199,4 @@ export const getGrade = (sellerId) => {
});
})
}
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment