Commit 3c965caf authored by 黄韬's avatar 黄韬

[feat]进度条组件与排行榜

parent a3a88714
# 排行榜组件
---
## 何时使用
- 当某个页面需要展示累计邀请、分等级奖励进度条、累计签到等。
## 目录结构
`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
.scroll-container {
.wh(100%,100%);
max-height: 700px;
}
.text-overflow-el() {
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
}
.scroll-item {
.flex-row-space();
.wh(100%,66px);
background-color: #F7F0E6;
border: 1px solid #ECDBC0;
}
.scroll-item__rank {
width: 33%;
.flex-row-center();
.text-overflow-el();
position: relative;
}
.rank-icon {
position: absolute;
left: 50%;
top: 50%;
.wh(30px,30px);
transform: translate(-50px,-50%);
}
.rank-num {
// transform: translate(-40px,0);
font-size: 30px;
font-family: YouSheBiaoTiHei;
font-weight: 400;
color: #2D2B28;
font-weight: 600;
}
.scroll-item__user {
width: 34%;
.flex-row-center();
.text-overflow-el();
font-size: 26px;
font-family: SourceHanSansCN;
font-weight: 400;
color: #666666;
}
.scroll-item__score {
width: 33%;
.flex-row-center();
.text-overflow-el();
font-size: 28px;
font-family: WorkSans;
font-weight: 600;
color: #2D2B28;
}
\ No newline at end of file
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 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);
}
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