實現: 隨着向下滾動標簽出現並橫向滑動到某個tab相應標題選中,點擊tab標題滑動到相應內容
博客園不能錄視頻只能圖片模擬成品,如果想要看視頻效果留言給我
實現如下:
點擊查看代碼
// index.tsx文件
// 所涉及代碼
import React, { useEffect, useState } from 'react'
import { View, Image, Video, Swiper, SwiperItem, Button, ScrollView } from '@tarojs/components'
import { usePageScroll, createSelectorQuery, pageScrollTo, useReady, useRouter } from '@tarojs/taro'
import { useThrottleFn } from 'ahooks'
import c from 'classnames'
import Skeleton from 'taro-skeleton'
import MainLayout from '@/layout/MainLayout'
import { fetchDeatil, IResourceList } from '@/apis/detail'
import { projectArrs, ITaptaneTopArr } from './const'
import './index.scss'
const Detail: React.FC = () => {
const [projectDetail, setProjectDetail] = useState<any>({})
const [project, setProject] = useState({})
const [tabFixed, setTabFixed] = useState(false)
const [top, setTop] = useState(0)
const [tabScrollTop, setScrollTop] = useState(0)
const [taptaneTopArr, setTaptaneTopArr] = useState([])
const [projectArr, setProjectArr] = useState(projectArrs)
const [navScrollLeft, setNavScrollLeft] = useState(0)
const query = createSelectorQuery()
const router = useRouter()
const scrollHanlder = res => {
const { scrollTop } = res
setTop(scrollTop)
if (scrollTop > tabScrollTop) {
setTabFixed(true)
} else {
setTabFixed(false)
}
for (let i = 0; i < projectArr.length; i++) {
let currentTaptaneTop = 0
let nextTaptaneTop = 0
let currentItem = projectArr[i]
if (i != projectArr.length - 1) {
let nextItem = projectArr[i + 1]
for (const list of taptaneTopArr) {
if (currentItem.id === list.id) {
currentTaptaneTop = list.taptaneTop
}
if (nextItem.id === list.id) {
nextTaptaneTop = list.taptaneTop
}
}
if (scrollTop >= currentTaptaneTop - 47 - 15 && scrollTop < nextTaptaneTop - 47 - 15) {
if (i === 0) {
setNavScrollLeft(-16 + Math.random())
}
currentItem.isSelected = true
} else {
currentItem.isSelected = false
}
} else {
for (const list of taptaneTopArr) {
if (currentItem.id === list.id) {
currentTaptaneTop = list.taptaneTop
}
}
if (scrollTop >= currentTaptaneTop - 47 - 15) {
setNavScrollLeft(state => {
currentItem.isSelected = true
return 400 + Math.random()
})
} else {
currentItem.isSelected = false
}
}
}
setProjectArr([...projectArr])
}
const { run } = useThrottleFn(scrollHanlder, { wait: 1 })
usePageScroll(res => run(res))
useReady(() => {
// id是tabs距離頂部的距離
query
.select('#tabs')
.boundingClientRect(res => {
setScrollTop(res?.top)
})
.exec()
})
const { run: onTab } = useThrottleFn(
e => {
const { id } = e.target.dataset
pageScrollTo({
selector: `#${id}`,
offsetTop: -40,
// duration: 30
})
},
{ wait: 300 }
)
useEffect(() => {
const id = Number(router.params.id)
fetchDeatil(id).then((data: any) => {
setProjectDetail(data.projectDetail)
setProject(data.project)
setTimeout(() => {
const arr: ITaptaneTopArr[] = []
projectArr.map(item => {
query.select(`#${item.id}`).boundingClientRect(res => {
arr.push({ id: item.id, taptaneTop: res.top })
})
})
query.exec()
setTaptaneTopArr(arr)
}, 10)
})
}, [])
return (
<MainLayout className="detail">
{project?.auditState == 2 && (
<>
<ScrollView
className={c('scrollview', tabFixed ? 'tabtop' : '')}
id="tabs"
scrollX
enhanced
showScrollbar
refresherDefaultStyle={'white'}
scrollWithAnimation
scrollAnchoring
scrollLeft={navScrollLeft}
onClick={onTab}
>
<View className="wrapView" style={{ paddingRight: 1 }}>
{projectArr.map((list, index) => (
<View
key={list.id}
data-id={list.id}
data-index={index}
className={c('tabtaneCls', list.isSelected ? 'active' : '')}
>
{list.title}
</View>
))}
</View>
</ScrollView>
{projectArr.map(item => (
<Skeleton loading={false} title row={6} key={item.key}>
<View className="content" id={item.id}>
<View className="title">{item.title}</View>
<View className="table">
{item.lists.map(item => (
<View key={item.key} className="tr">
<View className="label">{item.label}</View>
<View className="value">{projectDetail[item.key]}</View>
</View>
))}
</View>
</View>
</Skeleton>
))}
<View className="paceHolderBom">
<View style={{ height: '150PX', background: '#fff' }} />
</View>
</>
)}
</MainLayout>
)
}
export default Detail
const.ts文件
type Txt = number | string;
export interface Idesc {
label: string;
key: string;
render?: (text: Txt) => Txt;
}
const salaryArr: Idesc[] = [
{
label: '薪資',
key: 'postSalary',
},
{
label: '工資計算',
key: 'salaryCalculationMethod',
},
{
label: '發薪時間',
key: 'payday',
},
{
label: '多久轉正',
key: 'howLongCorrection'
},
{
label: '發工資銀行卡',
key: 'bankCardName',
},
{
label: '其他津貼',
key: 'otherAllowances',
},
{
label: '多久可以離職',
key: 'howLongLeave',
},
{
label: '提前多久打離職報告',
key: 'advancePrintResignationReport',
},
{
label: '工作需繳納費用',
key: 'workDeposit',
},
// {
// label: '更新日期',
// key: 'updateTime',
// render(text) {
// return moment(text).format('YYYY-MM-DD');
// },
// },
{
label: '合同簽訂',
key: 'contractSigningMethod',
},
{
label: '企業福利',
key: 'enterpriseWelfare'
},
{
label: '是否有試用期',
key: 'isProbationPeriod',
},
{
label: '是否繳社保',
key: 'isSocialSecurity',
render(text) {
return text;
},
},
{
label: '是否好離職',
key: 'easyLeave',
},
{
label: '是否有培訓',
key: 'isTraining',
},
{
label: '自離是否有工資',
key: 'selfLeaveSalary',
},
{
label: '是否扣商報',
key: 'isDeductBusinessDaily',
},
];
const requestArr: Idesc[] = [
{
label: '年齡',
key: 'postSalary',
},
{
label: '性別',
key: 'salaryCalculationMethod',
},
{
label: '學歷',
key: 'payday',
},
{
label: '身高',
key: 'howLongCorrection'
},
{
label: '視力',
key: 'bankCardName',
},
{
label: '地域',
key: 'otherAllowances',
},
// {
// label: '民族',
// key: 'howLongLeave',
// },
{
label: '自離幾次是黑名單',
key: 'advancePrintResignationReport',
},
{
label: '前幾個月不能進廠',
key: 'workDeposit',
},
{
label: '超齡是否可以協調',
key: 'contractSigningMethod',
},
{
label: '殘疾人是否可以',
key: 'enterpriseWelfare'
},
{
label: '紋身煙疤是否可以',
key: 'isProbationPeriod',
},
{
label: '身份證過期是否可以',
key: 'isSocialSecurity',
render(text) {
return text;
},
}
];
const workArr: Idesc[] = [
{
label: '工作性質',
key: 'jobNature',
},
{
label: '工作內容',
key: 'workContent',
},
{
label: '車間環境',
key: 'workshopEnvironment',
},
{
label: '崗位',
key: 'whatPositions'
},
{
label: '工作服',
key: 'coverall',
},
{
label: '生產產品',
key: 'production',
},
{
label: '工作方式',
key: 'operationMode',
},
{
label: '上班形式',
key: 'workForm',
},
{
label: '月休息天數',
key: 'monthlyRestDays',
},
{
label: '工作時間',
key: 'workingHours',
},
{
label: '是否流水線',
key: 'isAssemblyLine'
},
{
label: '是否雙休',
key: 'isWeekend',
}
];
const envArr: Idesc[] = [
{
label: '餐食提供',
key: 'mealProvision',
},
{
label: '餐食標准',
key: 'mealStandard',
},
{
label: '餐食扣款',
key: 'mealDeductMethod',
},
{
label: '餐食補助',
key: 'mealAllowance'
},
{
label: '住宿',
key: 'accommodation',
},
{
label: '外宿補貼',
key: 'accommodationAllowance',
},
{
label: '班車費用',
key: 'shuttleBusCost',
},
{
label: '水電費',
key: 'waterAndElectricity',
},
{
label: '是否可以刷卡吃飯',
key: 'eatCard',
},
{
label: '非工作時廠區可否吃飯',
key: 'eatInFactory',
},
{
label: '是否有班車',
key: 'isShuttleBus'
}
];
const interviewArr: Idesc[] = [
{
label: '面試地點',
key: 'collectionPlace',
},
{
label: '面試時間',
key: 'interviewTime',
},
{
label: '面試后體檢時間',
key: 'physicalExaminationAfterInterview',
},
{
label: '面試后報到時間',
key: 'reportTimeAfterInterview'
},
{
label: '報到資料',
key: 'carryData',
},
{
label: '工人到達時間',
key: 'requiredArrivalTime',
},
{
label: '面試資料',
key: 'interviewInformation',
},
{
label: '面試項目',
key: 'interviewItems',
},
{
label: '體檢費用',
key: 'physicalExaminationCostMethod',
},
{
label: '體檢醫院',
key: 'physicalExaminationHospital',
},
{
label: '是否提供接站',
key: 'isPickUpStation'
},
{
label: '臨時身份證可否',
key: 'isTemporaryIdCard',
},
{
label: '身份證消磁可否',
key: 'isDegaussingIdCard'
},
{
label: '是否查學信網',
key: 'checkEducationNetwork',
},
{
label: '是否查紋身煙疤',
key: 'checkTattooScar'
},
{
label: '是否查案底',
key: 'checkCriminalRecord',
},
{
label: '是否需要社會工證明',
key: 'socialWorkerCertificate'
},
{
label: '是否安排臨時住宿',
key: 'temporaryAccommodation',
},
{
label: '是否需要體檢',
key: 'isPhysicalExamination'
},
{
label: '自帶體檢報告健康證可否',
key: 'selfPhysicalExaminationReport',
},
{
label: '是否需要健康證',
key: 'isHealthCertificate'
}
];
const firmArr: Idesc[] = [
{
label: '企業名稱',
key: '',
},
{
label: '企業性質',
key: 'enterpriseNature',
},
{
label: '企業規模',
key: 'enterpriseScale',
},
{
label: '生產產品',
key: 'enterpriseProduct'
},
{
label: '乘車路線',
key: 'busLine',
},
{
label: '企業簡介',
key: 'enterpriseIntroduction',
},
{
label: '周邊是否有商場',
key: 'nearbyMarket',
},
{
label: '廠區是否偏僻',
key: 'isFactoryRemote',
},
{
label: '附近是否有幼兒園',
key: 'nearbyKindergarten',
}
];
export const projectArrs = [
{
title: '薪資福利',
lists: salaryArr,
key: 'salary',
id: 'salary',
isSelected: false
},
{
title: '招聘要求',
lists: requestArr,
key: 'request',
id: 'request',
isSelected: false
},
{
title: '工作內容',
lists: workArr,
key: 'work',
id: 'work',
isSelected: false
},
{
title: '食宿環境',
lists: envArr,
key: 'environment',
id: 'environment',
isSelected: false
},
{
title: '面試體檢',
lists: interviewArr,
key: 'interview',
id: 'interview',
isSelected: false
},
{
title: '企業信息',
lists: firmArr,
key: 'firm',
id: 'firm',
isSelected: false
}
]
enum SalaryUnitType {
MOON = 1,
DAY,
HOUR
}
export const salaryUnit = {
[SalaryUnitType.MOON]: '/月',
[SalaryUnitType.DAY]: '/日',
[SalaryUnitType.HOUR]: '/時'
}
index.scss
// 所涉及的樣式
.nav {
display: none;
background-color: #F6F6F6;
border-top: 1PX solid rgba(0, 0, 0, 0.1);
border-bottom: 1PX solid rgba(0, 0, 0, 0.1);
.tabtaneCls{
position: relative;
font-size: 28px;
font-weight: 400;
color: #666666;
line-height: 20px;
&::after {
position: absolute;
right: 20px;
bottom: -8PX;
left: 20px;
border-bottom: 2px solid transparent;
transition: border-color .3s cubic-bezier(.645,.045,.355,1);
content: "";
}
}
.active {
color:#FF6600;
font-weight: bolder;
&::after {
border-bottom: 2PX solid #FF6600;
}
}
}
// }
.scrollview {
display: none;
height: 94px;
white-space: nowrap;
z-index: 9999;
// padding-left: 16PX;
background-color: #F6F6F6;
border-top: 1PX solid rgba(0, 0, 0, 0.1);
// border-bottom: 1PX solid rgba(0, 0, 0, 0.1);
.tabtaneCls {
display: inline-block;
line-height: 47PX;
position: relative;
font-size: 28px;
font-weight: 400;
color: #666666;
&::after {
position: absolute;
right: 20px;
bottom: 5PX;
left: 20px;
border-bottom: 2px solid transparent;
transition: border-color .3s cubic-bezier(.645,.045,.355,1);
content: "";
}
&:nth-child(1) {
margin-left: 16PX;
}
&:nth-child(n+2) {
margin-left: 20PX;
}
&:last-child{
margin-right: 20PX;
}
}
.active {
color:#FF6600;
font-weight: bolder;
&::after {
border-bottom: 2PX solid #FF6600;
}
}
}
.paceHolderBom {
background: #fff;
padding-bottom: calc(26PX + env(safe-area-inset-bottom))
}
.tabtop {
display: flex;
align-items: center;
justify-content: space-around;
margin-top: 6PX;
width: 100%;
height: 94px;
background: #FFFFFF;
box-shadow: 0px 1px 0px 0px #E9E9E9;
position: fixed;
top: 0;
margin-top: 0;
}
.wrapView::-webkit-scrollbar {
display: none!important;
}