实现: 随着向下滚动标签出现并横向滑动到某个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;
  }
posted on 2022-04-11 14:09  sandy.simple  阅读(1174)  评论(0编辑  收藏  举报