修改页面级加载 和 图片加载

v2
king 1 year ago
parent 1eb468a699
commit 20564f42fb
  1. 12
      src/components/image/image.module.scss
  2. 51
      src/components/image/image.tsx
  3. 50
      src/components/spinner/index.tsx
  4. 14
      src/components/spinner/style.scss
  5. 6
      src/pages/business/videoInfo/videoInfo.scss
  6. 24
      src/pages/business/videoInfo/videoInfo.tsx
  7. 4
      src/pages/home/components/feature_recommended.tsx
  8. 2
      src/pages/home/home.module.scss
  9. 13
      src/pages/preview/health/health.tsx
  10. 35
      src/pages/preview/illness/sort/sort.tsx
  11. 23
      src/pages/preview/profession/profession.tsx

@ -0,0 +1,12 @@
.imgBox {
position: relative;
}
.imgError {
position: absolute;
top: 0;
left: 0;
bottom: 0;
right: 0;
margin: auto;
}

@ -1,7 +1,8 @@
import {FC, useEffect, useState} from "react"; import {FC, useEffect, useState} from "react";
import {Image, ImageProps, View} from "@tarojs/components"; import {Image, ImageProps, View} from "@tarojs/components";
import {AtActivityIndicator} from "taro-ui";
import shard from '@/static/img/shard.png' import shard from '@/static/img/shard.png'
import styles from './image.module.scss'
import Taro from "@tarojs/taro";
interface Props extends ImageProps { interface Props extends ImageProps {
width: number width: number
@ -13,14 +14,12 @@ const Img: FC<Props> = ({src, mode = 'aspectFill', width, height, fallback = sha
const [isError, setIsError] = useState(false) const [isError, setIsError] = useState(false)
const [loading, setLoading] = useState(true) const [loading, setLoading] = useState(true)
const imgAnimation = Taro.createAnimation({duration: 0}).opacity(0).step()
const [animationData, setAnimationData] = useState<TaroGeneral.IAnyObject>(imgAnimation.export())
useEffect(() => { useEffect(() => {
if (!src) { setIsError(!src)
setIsError(true) setLoading(!!src)
setLoading(false)
} else {
setIsError(false)
setLoading(false)
}
}, [src]) }, [src])
// 图片加载失败 // 图片加载失败
@ -32,25 +31,37 @@ const Img: FC<Props> = ({src, mode = 'aspectFill', width, height, fallback = sha
function onLoadHandler() { function onLoadHandler() {
setLoading(false) setLoading(false)
setIsError(false) setIsError(false)
imgAnimation.opacity(1).step({duration: 200})
setAnimationData(imgAnimation.export())
} }
return ( return (
<View style={{width: `${width}rpx`, height: `${height}rpx`, backgroundColor: '#eee'}}> <View
style={{width: `${width}rpx`, height: `${height}rpx`, backgroundColor: '#F8F8F8'}}
className={`${props.className} ${styles.imgBox}`}>
{!isError && {!isError &&
<Image <View animation={animationData}>
{...props} <Image
src={src} {...props}
mode={mode} src={src}
lazyLoad mode={mode}
style={{width: `${width}rpx`, height: `${height}rpx`}} lazyLoad
onError={onErrorHandler} fadeIn
onLoad={onLoadHandler}> defaultSource={fallback}
</Image> style={{width: `${width}rpx`, height: `${height}rpx`}}
onError={onErrorHandler}
onLoad={onLoadHandler}/>
</View>
} }
{loading && <AtActivityIndicator mode="center"/>}
{ {
isError && !loading && isError && !loading &&
<Image mode={'aspectFill'} src={fallback} style={{width: `${width}rpx`, height: `${height}rpx`}}/> <Image
className={styles.imgError}
mode='aspectFit'
src={fallback}
lazyLoad
fadeIn
style={{width: `${width * .7}rpx`, height: `${height * .7}rpx`, margin: 'auto'}}/>
} }
</View> </View>
) )

@ -1,6 +1,6 @@
import Taro from "@tarojs/taro"; import Taro from "@tarojs/taro";
import { View, Image } from '@tarojs/components' import {View, Image} from '@tarojs/components'
import { Component, ReactNode } from "react"; import {Component, ReactNode} from "react";
import indicator from './loading.svg' import indicator from './loading.svg'
import './style.scss' import './style.scss'
@ -12,8 +12,9 @@ type Status =
| 'completed' | 'completed'
interface Props { interface Props {
enable?: boolean enable?: boolean // 控制显现
overlay?: boolean overlay?: boolean // 页面覆盖
block?: boolean // 块级
} }
interface State { interface State {
@ -30,8 +31,8 @@ type Controller = {
} }
function createController(setState: StateSetter): Controller { function createController(setState: StateSetter): Controller {
const background = Taro.createAnimation({ duration: 600 }) const background = Taro.createAnimation({duration: 600})
const rotation = Taro.createAnimation({ duration: 600 }) const rotation = Taro.createAnimation({duration: 600})
let rotateTimer: ReturnType<typeof setTimeout> | undefined let rotateTimer: ReturnType<typeof setTimeout> | undefined
let status: Status | undefined let status: Status | undefined
@ -49,8 +50,8 @@ function createController(setState: StateSetter): Controller {
} }
// 清空动画 // 清空动画
background.export() background.step().export()
rotation.export() rotation.step().export()
// 通知 UI 刷新 // 通知 UI 刷新
if (notify) { if (notify) {
@ -66,7 +67,7 @@ function createController(setState: StateSetter): Controller {
// 旋转动画定时器 // 旋转动画定时器
const rotate = () => { const rotate = () => {
rotation.opacity(opacity).rotate(360).step({ duration: 600 }) rotation.opacity(opacity).rotate(360).step({duration: 600})
notifyListener() notifyListener()
rotateTimer = setTimeout(rotate, 600) rotateTimer = setTimeout(rotate, 600)
} }
@ -80,23 +81,20 @@ function createController(setState: StateSetter): Controller {
const onFinish = (opacity: number, nextStatus: Status) => { const onFinish = (opacity: number, nextStatus: Status) => {
const lockStatus = status const lockStatus = status
setTimeout(() => { if (lockStatus === status) {
if (lockStatus === status) { background.backgroundColor(`rgba(255,255,255,${opacity})`).step({duration: 0})
background.backgroundColor(`rgba(255,255,255,${opacity})`).step({ duration: 0 }) if (nextStatus === 'dismissed') {
if (nextStatus === 'dismissed') { clearAnimation()
clearAnimation()
}
status = nextStatus
notifyListener()
} }
}, 600) status = nextStatus
notifyListener()
}
} }
const setStatus = (newStatus: Status, opacity: number) => { const setStatus = (newStatus: Status, opacity: number) => {
if (status !== newStatus) { if (status !== newStatus) {
status = newStatus status = newStatus
setAnimation(opacity) setAnimation(opacity)
if (status === 'reverse') { if (status === 'reverse') {
onFinish(0, 'dismissed') onFinish(0, 'dismissed')
} else if (status === 'forward') { } else if (status === 'forward') {
@ -140,17 +138,16 @@ export default class Spin extends Component<Props, State> {
this.setState((s) => ({...s, ...state})) this.setState((s) => ({...s, ...state}))
} }
componentDidMount(): void { // componentDidMount(): void {
console.log(this.props.enable) // this.controller.setTick(this.props.enable)
this.controller.setTick(this.props.enable) // }
}
componentDidUpdate(): void { componentDidUpdate(): void {
this.controller.setTick(this.props.enable) this.controller.setTick(this.props.enable)
} }
componentWillUnmount(): void { componentWillUnmount(): void {
this.controller.clear() this.controller.clear()
} }
shouldComponentUpdate(nextProps: Readonly<Props>, nextState: Readonly<State>): boolean { shouldComponentUpdate(nextProps: Readonly<Props>, nextState: Readonly<State>): boolean {
@ -161,9 +158,10 @@ export default class Spin extends Component<Props, State> {
render(): ReactNode { render(): ReactNode {
return ( return (
<View className={`spinner-wrapper ${this.state.status} ${this.props.overlay ? 'is-fixed' : ''}`}> <View
className={`spinner-wrapper ${this.state.status} ${this.props.overlay && 'is-fixed'} ${this.props.block && 'is-block'}`}>
<View className={`spinner ${this.state.status}`}> <View className={`spinner ${this.state.status}`}>
<Image className="spinner-icon" src={indicator} /> <Image className="spinner-icon" src={indicator}/>
</View> </View>
</View> </View>
) )

@ -1,8 +1,17 @@
.spinner-wrapper { .spinner-wrapper {
background-color: rgba( #fff, 1.0);
transition: background-color 1200ms ease-out; transition: background-color 1200ms ease-out;
&.is-block {
width: auto;
position: absolute;
z-index: 99999;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
}
&.is-fixed { &.is-fixed {
background-color: rgba(#fff, 1.0);
z-index: 99999; z-index: 99999;
position: fixed; position: fixed;
width: 100%; width: 100%;
@ -15,7 +24,7 @@
&.reverse, &.reverse,
&.dismissed { &.dismissed {
background-color: rgba( #fff, 0.0); background-color: rgba(#fff, 0.0);
} }
&.dismissed { &.dismissed {
@ -36,6 +45,7 @@
flex-direction: column; flex-direction: column;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
width: 100%;
} }
.spinner { .spinner {

@ -7,11 +7,7 @@
height: 500rpx; height: 500rpx;
} }
.image {
width: 100%;
height: 100%;
display: block;
}
.header { .header {
margin-bottom: 10px; margin-bottom: 10px;

@ -1,4 +1,4 @@
import {Image, Text, View} from "@tarojs/components"; import {Text, View} from "@tarojs/components";
import {FC, useCallback, useEffect, useState} from "react"; import {FC, useCallback, useEffect, useState} from "react";
import {CourseDepData, curriculum} from "@/api"; import {CourseDepData, curriculum} from "@/api";
import './videoInfo.scss' import './videoInfo.scss'
@ -9,6 +9,8 @@ import eventsIndex from "@/hooks/eventsIndex";
import {formatMinute} from "@/utils/time"; import {formatMinute} from "@/utils/time";
import videoEvents from "@/hooks/videoEvents"; import videoEvents from "@/hooks/videoEvents";
import unique_ident from "@/hooks/unique_ident"; import unique_ident from "@/hooks/unique_ident";
import Spin from "@/components/spinner";
import Img from "@/components/image/image";
const VideoInfo: FC = () => { const VideoInfo: FC = () => {
const {id, depId} = Taro.getCurrentInstance()?.router?.params as any const {id, depId} = Taro.getCurrentInstance()?.router?.params as any
@ -16,15 +18,16 @@ const VideoInfo: FC = () => {
const [playId, setPlayId] = useState<number | null>(null) const [playId, setPlayId] = useState<number | null>(null)
const [preview, setPreview] = useState(false) // 预览 const [preview, setPreview] = useState(false) // 预览
const [playing, setPlaying] = useState(false) // 学习中 const [playing, setPlaying] = useState(false) // 学习中
const [enable, setEnable] = useState(true)
const getData = useCallback(async (playing: boolean) => { const getData = useCallback(async (playing: boolean) => {
const res = await curriculum.courseDep(id, depId) try {
if (res) { const res = await curriculum.courseDep(id, depId)
setData(res) res && setData(res)
} playId != null && currentVideo(res, playing) // 用于自动播放 判断当前课程是否完成
if (playId != null) { // 用于自动播放 判断当前课程是否完成 } catch (e) {
currentVideo(res, playing)
} }
setEnable(false)
}, [playing, playId]) }, [playing, playId])
const curEnd = (test?: boolean) => { const curEnd = (test?: boolean) => {
@ -73,9 +76,7 @@ const VideoInfo: FC = () => {
} }
}, [playId, data, preview]) }, [playId, data, preview])
/** /** 判断当前课程是否完成 */
*
*/
const currentVideo = useCallback((data: CourseDepData, playing: boolean) => { const currentVideo = useCallback((data: CourseDepData, playing: boolean) => {
const courseHourRecordsFinish = data?.learn_hour_records.find(d => d.id === playId)?.courseHourRecordsFinish const courseHourRecordsFinish = data?.learn_hour_records.find(d => d.id === playId)?.courseHourRecordsFinish
if (typeof courseHourRecordsFinish === 'number') { if (typeof courseHourRecordsFinish === 'number') {
@ -106,12 +107,13 @@ const VideoInfo: FC = () => {
}) })
return ( return (
<> <>
<Spin enable={enable} overlay/>
<View className='content'> <View className='content'>
<View className='content-video'> <View className='content-video'>
{ {
playId playId
? <Course id={playId} courseId={id} curEnd={curEnd} preview={preview}/> ? <Course id={playId} courseId={id} curEnd={curEnd} preview={preview}/>
: <Image src={data?.course.thumb || ''} className='image' mode='aspectFill'/> : <Img width={750} height={500} src={data?.course.thumb || ''} mode='aspectFill'/>
} }
</View> </View>

@ -150,8 +150,8 @@ const FeatureRecommended: FC<Props> = (props) => {
} }
return ( return (
<View className={styles.feature} style={{height: '263px'}}> <View className={styles.feature}>
<Swiper nextMargin='30px' style={{height: '263px'}}> <Swiper nextMargin='30px' style={{height: '225px'}}>
{ {
data.map(d => <SwiperItem key={d.url}> data.map(d => <SwiperItem key={d.url}>
<Image <Image

@ -51,9 +51,9 @@
} }
.adware { .adware {
width: 100%;
border-radius: 16rpx; border-radius: 16rpx;
overflow: hidden; overflow: hidden;
background: #eee;
margin-bottom: 40rpx; margin-bottom: 40rpx;
} }

@ -5,16 +5,22 @@ import Taro, {useReachBottom} from "@tarojs/taro";
import styles from './health.module.scss' import styles from './health.module.scss'
import play from '@/static/img/play.png' import play from '@/static/img/play.png'
import Empty from "@/components/empty/empty"; import Empty from "@/components/empty/empty";
import Spin from "@/components/spinner";
const Health: FC = () => { const Health: FC = () => {
const [page, setPage] = useState(1) const [page, setPage] = useState(1)
const [data, setData] = useState<Health[]>([]) const [data, setData] = useState<Health[]>([])
const [total, setTotal] = useState(0) const [total, setTotal] = useState(0)
const [enable, setEnable] = useState(true)
async function getData(page: number) { async function getData(page: number) {
const res = await HomeApi.health(page, 10) try {
setData(res.data) const res = await HomeApi.health(page, 10)
setTotal(res.total) setData(res.data)
setTotal(res.total)
} catch (e) {
}
setEnable(false)
} }
useReachBottom(() => { useReachBottom(() => {
@ -32,6 +38,7 @@ const Health: FC = () => {
return ( return (
<ScrollView> <ScrollView>
<Spin enable={enable} overlay/>
<View className={styles.container}> <View className={styles.container}>
{ {
data.length > 0 data.length > 0

@ -7,6 +7,7 @@ import Tabs, {TabList} from "@/components/tabs/tabs";
import {illnessApi} from "@/api/illness"; import {illnessApi} from "@/api/illness";
import Empty from "@/components/empty/empty"; import Empty from "@/components/empty/empty";
import leftArrow from "@/static/img/leftArrow.png" import leftArrow from "@/static/img/leftArrow.png"
import Spin from "@/components/spinner";
const prefix = 'SORT' const prefix = 'SORT'
const Sort: FC = () => { const Sort: FC = () => {
@ -14,16 +15,24 @@ const Sort: FC = () => {
const [firstId, setFirstId] = useState<number | undefined>(undefined) // 一级分类 const [firstId, setFirstId] = useState<number | undefined>(undefined) // 一级分类
const [secondId, setSecondId] = useState<number | undefined>(undefined) // 二级分类 const [secondId, setSecondId] = useState<number | undefined>(undefined) // 二级分类
const [list, setList] = useState<any[]>([]) const [list, setList] = useState<any[]>([])
const [enable, setEnable] = useState(true)
const [loading, setLoading] = useState(false)
const globalData = Taro.getApp().globalData const globalData = Taro.getApp().globalData
const menu = Taro.getMenuButtonBoundingClientRect() const menu = Taro.getMenuButtonBoundingClientRect()
async function getData() { async function getData() {
const res = await HomeApi.category(3) try {
setData(res)
if (res.length) { const res = await HomeApi.category(3)
setFirstId(res[0].id) setData(res)
setSecondId(res[0]?.resource_category?.[0].id) if (res.length) {
setFirstId(res[0].id)
setSecondId(res[0]?.resource_category?.[0].id)
}
} catch (e) {
setLoading(false)
} }
setEnable(false)
} }
function jump(id: number) { function jump(id: number) {
@ -54,17 +63,16 @@ const Sort: FC = () => {
useEffect(() => { useEffect(() => {
if (secondId) { if (secondId) {
Taro.showLoading({title: '加载中'}) setLoading(true)
illnessApi.list(secondId, 1, 100).then(res => { illnessApi.list(secondId, 1, 100).then(res => {
setList(res.list) setList(res.list)
}).finally(() => {
Taro.hideLoading()
}).catch(() => { }).catch(() => {
setList([]) setList([])
}) })
} else { } else {
setList([]) setList([])
} }
setLoading(false)
}, [secondId]) }, [secondId])
function swiperChange(e) { function swiperChange(e) {
@ -76,6 +84,7 @@ const Sort: FC = () => {
return ( return (
<View> <View>
<Spin enable={enable} overlay/>
<View style={headerStyles}> <View style={headerStyles}>
<Image src={leftArrow} <Image src={leftArrow}
mode='widthFix' mode='widthFix'
@ -119,9 +128,13 @@ const Sort: FC = () => {
className={styles.tree} className={styles.tree}
scrollWithAnimation> scrollWithAnimation>
{ {
list.length ? loading ? <Spin enable={loading}/> : <>
list.map(d => <View className={styles.list} onClick={() => jump(d.id)}>{d.name}</View>) {
: <Empty name='暂无数据'/> list.length ?
list.map(d => <View className={styles.list} onClick={() => jump(d.id)}>{d.name}</View>)
: <Empty name='暂无数据'/>
}
</>
} }
</ScrollView> </ScrollView>
</SwiperItem>) </SwiperItem>)

@ -5,6 +5,7 @@ import Tabs, {OnChangOpt, TabList} from "@/components/tabs/tabs";
import Empty from "@/components/empty/empty"; import Empty from "@/components/empty/empty";
import Taro from "@tarojs/taro"; import Taro from "@tarojs/taro";
import styles from './profession.module.scss' import styles from './profession.module.scss'
import Spin from "@/components/spinner";
interface KillData { interface KillData {
data: Kill[] data: Kill[]
@ -16,6 +17,8 @@ const Profession = () => {
const [tabs, setTabs] = useState<TabList[]>([]) const [tabs, setTabs] = useState<TabList[]>([])
const [categoryId, setCategoryId] = useState<number | null>(null) const [categoryId, setCategoryId] = useState<number | null>(null)
const [data, setData] = useState<Map<number, KillData>>(new Map) const [data, setData] = useState<Map<number, KillData>>(new Map)
const [enable, setEnable] = useState(true)
const [loading, setLoading] = useState(false)
/** /**
* more * more
@ -32,9 +35,7 @@ const Profession = () => {
} }
try { try {
if (!data.has(categoryId)) { setLoading(true)
Taro.showLoading()
}
const res = await HomeApi.skillList(categoryId!, page, 10) const res = await HomeApi.skillList(categoryId!, page, 10)
const dataList = res.data.reduce((pre, cur) => { const dataList = res.data.reduce((pre, cur) => {
const index = pre.findIndex(d => d.id === cur.id) const index = pre.findIndex(d => d.id === cur.id)
@ -54,7 +55,7 @@ const Profession = () => {
setData(oldData) setData(oldData)
} catch (e) { } catch (e) {
} }
Taro.hideLoading() setLoading(false)
} }
useEffect(() => { useEffect(() => {
@ -62,10 +63,14 @@ const Profession = () => {
}, [categoryId]) }, [categoryId])
async function getCategory() { async function getCategory() {
const res = await HomeApi.skillCategory() try {
const newTabs = res.map<TabList>(d => ({title: d.name, value: d.id})) const res = await HomeApi.skillCategory()
setTabs(newTabs) const newTabs = res.map<TabList>(d => ({title: d.name, value: d.id}))
setCategoryId(newTabs[0].value as number) setTabs(newTabs)
setCategoryId(newTabs[0].value as number)
} catch (e) {
}
setEnable(false)
} }
function tabsChange(tab: OnChangOpt) { function tabsChange(tab: OnChangOpt) {
@ -101,6 +106,7 @@ const Profession = () => {
</View> </View>
) )
} }
<Spin enable={loading}/>
</ScrollView> </ScrollView>
) )
} }
@ -108,6 +114,7 @@ const Profession = () => {
return ( return (
<> <>
<Spin enable={enable} overlay/>
<View className='bg-white'> <View className='bg-white'>
<Tabs tabList={tabs} onChange={tabsChange} current={categoryId!}/> <Tabs tabList={tabs} onChange={tabsChange} current={categoryId!}/>
</View> </View>

Loading…
Cancel
Save