Compare commits

...

3 Commits

  1. 6
      src/api/brand.ts
  2. 12
      src/api/home.ts
  3. 12
      src/components/image/image.module.scss
  4. 26
      src/components/image/image.tsx
  5. 27
      src/components/videoList/videoList.module.scss
  6. 35
      src/components/videoList/videoList.tsx
  7. 1
      src/pages/business/videoInfo/components/catalogue.tsx
  8. 2
      src/pages/business/videoInfo/videoInfo.tsx
  9. 21
      src/pages/home/components/feature_recommended.tsx
  10. 7
      src/pages/preview/brand/article/article.module.scss
  11. 3
      src/pages/preview/brand/list/list.tsx
  12. 29
      src/pages/preview/health/health.tsx
  13. 9
      src/pages/preview/profession/profession.module.scss
  14. 32
      src/pages/preview/profession/profession.tsx
  15. 17
      src/pages/preview/videoFull/videoFull.module.scss
  16. 102
      src/pages/preview/videoFull/videoFull.tsx
  17. BIN
      src/static/img/brandSecond.png
  18. BIN
      src/static/img/healthShard.png
  19. BIN
      src/static/img/professionShard.png
  20. BIN
      src/static/img/组 498@2x.png
  21. 1
      types/curriculum.d.ts
  22. 17
      types/home.d.ts
  23. 5
      types/user.d.ts

@ -20,7 +20,7 @@ export type ArticleRecord = {
created_at: string
content: string
brands: BrandRecord[]
collect:boolean
collect: boolean
}
export const brandApi = {
@ -45,4 +45,8 @@ export const brandApi = {
articleInfo(id: number) {
return request<ArticleRecord>(`/home/v1/article/${id}`, "GET")
},
/** 品牌 & 健康详情 */
videoInfo(id: number | string) {
return request<VideList>(`/home/v1/health/${id}`, "GET")
}
}

@ -31,8 +31,8 @@ export interface AdwareType {
export interface HomeData {
adverts: AdwareType[]
skill: Kill[]
health: Health[]
skill: VideList[]
health: VideList[]
brand: {
list: Brand[]
}
@ -56,10 +56,10 @@ export const HomeApi = {
},
/** 健康管理 */
healthTop(count: number) {
return request<Health[]>('/home/v1/health/top', "GET", {count})
return request<VideList[]>('/home/v1/health/top', "GET", {count})
},
health(page: number, page_size: number) {
return request<{ data: Health[], total: number }>('/home/v1/health/index', "GET", {page, page_size})
return request<{ data: VideList[], total: number }>('/home/v1/health/index', "GET", {page, page_size})
},
/** 增加播放量 */
healthSetPlay(id) {
@ -71,13 +71,13 @@ export const HomeApi = {
},
/** 技能 */
skillTop(count: number) {
return request<Kill[]>('/home/v1/skill/top', "GET", {count})
return request<VideList[]>('/home/v1/skill/top', "GET", {count})
},
skillCategory() {
return request<Category[]>('/home/v1/skill/category', "GET")
},
skillList(categoryId: number, page: number, page_size: number) {
return request<{ data: Kill[], total: number }>('/home/v1/skill/index', "GET", {categoryId, page, page_size})
return request<{ data: VideList[], total: number }>('/home/v1/skill/index', "GET", {categoryId, page, page_size})
},
skillSetPlay(id: number) {
return request(`/home/v1/skill/set_play/${id}`, "PUT")

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

@ -1,15 +1,17 @@
import {FC, useEffect, useState} from "react";
import {Image, ImageProps, View} from "@tarojs/components";
import shard from '@/static/img/shard.png'
import styles from './image.module.scss'
import Taro from "@tarojs/taro";
import avatar from '@/static/img/avatar.png'
import healthShard from '@/static/img/healthShard.png'
import professionShard from '@/static/img/professionShard.png'
import brandSecond from '@/static/img/brandSecond.png'
interface Props extends ImageProps {
width?: number | string
height?: number | string
fallback?: string
errorType?: "acquiesce" | 'avatar'
errorType?: ImgErrType
}
const Img: FC<Props> = ({src, mode = 'aspectFill', width, height, fallback = shard, ...props}) => {
@ -34,6 +36,15 @@ const Img: FC<Props> = ({src, mode = 'aspectFill', width, height, fallback = sha
case "avatar":
setErrorUrl(avatar)
break
case 'health':
setErrorUrl(healthShard)
break
case 'profession':
setErrorUrl(professionShard)
break
case 'brand':
setErrorUrl(brandSecond)
break
}
}, [props.errorType])
@ -53,11 +64,12 @@ const Img: FC<Props> = ({src, mode = 'aspectFill', width, height, fallback = sha
return (
<View
style={{
width: `${width}rpx`,
height: `${height}rpx`,
overflow: 'hidden',
width: width ? `${width}rpx` : "100%",
height: height ? `${height}rpx` : "100%",
backgroundColor: (isError || !loading) ? 'transparent' : '#F8F8F8'
}}
className={`${props?.className} ${styles.imgBox}`}>
className={`${props?.className}`}>
{!isError &&
<View animation={animationData}>
<Image
@ -79,14 +91,12 @@ const Img: FC<Props> = ({src, mode = 'aspectFill', width, height, fallback = sha
{
isError && !loading &&
<Image
className={styles.imgError}
mode='aspectFill'
mode='widthFix'
src={errorUrl}
lazyLoad
fadeIn
style={{
width: "100%",
height: "100%",
}}/>
}
</View>

@ -0,0 +1,27 @@
.container {
width: 100%;
padding: 20rpx;
box-sizing: border-box;
columns: 2;
column-gap: 20rpx;
}
.health {
break-inside: avoid;
background: #fff;
border-radius: 10px;
overflow: hidden;
margin-bottom: 20rpx;
position: relative;
}
.play {
position: absolute;
min-height: 70rpx !important;
z-index: 9999;
width: 40rpx !important;
height: 40rpx !important;
top: 20rpx;
right: 20rpx;
background: transparent !important;
}

@ -0,0 +1,35 @@
import {FC} from "react";
import styles from "@/pages/preview/health/health.module.scss";
import {Image, Text, View} from "@tarojs/components";
import Img from "@/components/image/image";
import play from "@/static/img/play-back.png";
import {formatDate} from "@/utils/time";
import Taro from "@tarojs/taro";
interface Props {
data: VideList
errorType?: ImgErrType
}
const VideoList: FC<Props> = ({data, errorType}) => {
function jump() {
Taro.preload(data)
Taro.navigateTo({url: `/pages/preview/videoFull/videoFull?id=${data.id}`})
}
return (
<View key={data.id} className={styles.health} onClick={jump}>
<Img src={data.url_path} mode='widthFix' errorType={errorType}/>
<Image src={play} className={styles.play} mode='aspectFit'/>
<View className='p-1'>
<View className='text-ellipsis-2 text-dark'>{data.title}</View>
<View className='text-ellipsis-2 mt-1 font-26 text-secondary'>{data.introduction}</View>
<View className='font-24 text-muted my-2 flex justify-between'>
<Text>{formatDate(new Date(data.publish_time), "YY-MM-dd")}</Text>
<Text>{data.video_view}</Text>
</View>
</View>
</View>)
}
export default VideoList

@ -195,6 +195,7 @@ const Catalogue: FC<Props> = ({data, setHors, id, playId}) => {
<Collect
owner_id={id}
owner_type={3}
select={data?.course.collect}
styles={{flexDirection: 'column', justifyContent: 'center', padding: '20rpx'}}
stylesImage={{margin: '0 0 8rpx 0'}}/>
<View className='px-2' onClick={() => setShow(true)}>

@ -106,7 +106,7 @@ const VideoInfo: FC = () => {
{
playId
? <Course id={playId} courseId={id} curEnd={curEnd} preview={preview}/>
: <Img width={750} height={500} src={data?.course.thumb || ''} mode='aspectFill'/>
: <Img width={750} height={500} src={data?.course.thumb || ''} errorType='health'/>
}
</View>

@ -26,12 +26,13 @@ interface Data {
url: string
detailsUrl: string
data: DataContent[]
errorType: ImgErrType
type?: 'health' | 'kill'
}
interface Props {
skill: Kill[] // 技能
health: Health[] // 健康
skill: VideList[] // 技能
health: VideList[] // 健康
brand: Brand[] // 品牌
illness: Illness[] // 疾病
}
@ -42,27 +43,31 @@ const FeatureRecommended: FC<Props> = (props) => {
titleUrl: brandTop,
url: '/pages/preview/brand/list/list',
detailsUrl: '/pages/preview/brand/info/info',
data: []
data: [],
errorType: 'brand',
},
{
titleUrl: healthTop,
url: '/pages/preview/health/health',
detailsUrl: '/pages/preview/videoFull/videoFull',
data: [],
type: "health"
type: "health",
errorType: 'health'
},
{
titleUrl: professionTop,
url: '/pages/preview/profession/profession',
detailsUrl: '/pages/preview/videoFull/videoFull',
data: [],
type: 'kill'
type: 'kill',
errorType: 'profession'
},
{
titleUrl: illnessTop,
url: '/pages/preview/illness/sort/sort',
detailsUrl: '/pages/preview/illness/list/list',
data: []
data: [],
errorType: 'health'
},
])
@ -153,7 +158,7 @@ const FeatureRecommended: FC<Props> = (props) => {
<View className={styles.feature}>
<Swiper nextMargin='30px' style={{height: '390rpx'}} circular autoplay>
{
data.filter(d=>d.data.length === 3).map(d => <SwiperItem key={d.url}>
data.filter(d => d.data.length === 3).map(d => <SwiperItem key={d.url}>
<Image
mode='heightFix'
className={styles.featureTitle}
@ -166,7 +171,7 @@ const FeatureRecommended: FC<Props> = (props) => {
onClick={() => jump(d.detailsUrl + c.path, c.id, d.type)}>
<View style={{position: 'relative'}}>
<View className={styles.featureImage}>
<Img src={c.imageUrl} height={100} width={140}/>
<Img src={c.imageUrl} height={100} width={140} errorType={d.errorType}/>
</View>
<Image src={[first, second, third][index]} className={styles.ranking} mode='aspectFill'/>
</View>

@ -1,3 +1,7 @@
page{
background: #fff !important;
}
.fixedBox {
position: fixed;
z-index: 1000;
@ -74,8 +78,5 @@
}
.articleBox {
background: #fff;
min-height: calc(100vh - env(safe-area-inset-bottom) + 180rpx);
box-sizing: border-box;
padding: 30rpx 30rpx calc(env(safe-area-inset-bottom) + 150rpx) 30rpx;
}

@ -20,6 +20,7 @@ const BrandItem: FC<{ data: BrandRecord; onClick: VoidFunction }> = ({data, onCl
} else if (data.brand_album) {
media = <Img
height={320}
errorType='profession'
src={data.brand_album.split(",")[0]}
mode="aspectFill"
style={{background: '#ededed'}}
@ -36,7 +37,7 @@ const BrandItem: FC<{ data: BrandRecord; onClick: VoidFunction }> = ({data, onCl
height={76}
src={data.logo}
mode='aspectFill'
errorType='avatar'
errorType='brand'
className="rounded-10 clip"
style={{background: '#ededed'}}
/>

@ -1,24 +1,25 @@
import {FC, useEffect, useState} from "react";
import {Image, Text, View} from "@tarojs/components";
import {View} from "@tarojs/components";
import {HomeApi} from "@/api";
import Taro, {useReachBottom} from "@tarojs/taro";
import {useReachBottom} from "@tarojs/taro";
import styles from './health.module.scss'
import play from '@/static/img/play-back.png'
import Empty from "@/components/empty/empty";
import Spin from "@/components/spinner";
import Img from "@/components/image/image";
import {formatDate} from "@/utils/time";
import VideoList from "@/components/videoList/videoList";
const Health: FC = () => {
const [page, setPage] = useState(1)
const [data, setData] = useState<Health[]>([])
const [data, setData] = useState<VideList[]>([])
const [total, setTotal] = useState(0)
const [enable, setEnable] = useState(true)
async function getData(page: number) {
try {
const res = await HomeApi.health(page, 10)
setData(res.data)
setData([
...data,
...res.data
])
setTotal(res.total)
} catch (e) {
}
@ -33,10 +34,6 @@ const Health: FC = () => {
getData(page)
}, [page])
function jump(health: Health) {
HomeApi.healthSetPlay(health.id)
Taro.navigateTo({url: `/pages/preview/videoFull/videoFull?url=${health.resource.url}&poster=${health.url_path}&title=${health.title}`})
}
return (
<>
@ -45,15 +42,7 @@ const Health: FC = () => {
{
data.length > 0 ? <>
<View className={styles.container}>
{data.map(d => <View key={d.id} className={styles.health} onClick={() => jump(d)}>
<Img width={370} height={345} src={d.url_path} mode='widthFix'/>
<Image src={play} className={styles.play} mode='aspectFit'/>
<View className='text-ellipsis-2 m-2 text-dark'>{d.title}</View>
<View className='font-26 text-muted mx-2 mb-2 flex justify-between'>
<Text>{formatDate(new Date(d.publish_time), "YY-MM-dd")}</Text>
<Text>{d.video_view}</Text>
</View>
</View>)}
{data.map(d => <VideoList data={d} errorType='health'/>)}
</View>
<View className='text-center font-24 text-dark mt-2'></View>
</>

@ -1,3 +1,12 @@
.container {
width: 100%;
padding: 20rpx;
box-sizing: border-box;
columns: 2;
column-gap: 20rpx;
position: relative;
}
.height {
height: calc(100vh - 80rpx - env(safe-area-inset-bottom));
overflow: hidden;

@ -6,10 +6,10 @@ import Empty from "@/components/empty/empty";
import Taro from "@tarojs/taro";
import styles from './profession.module.scss'
import Spin from "@/components/spinner";
import Img from "@/components/image/image";
import VideoList from "@/components/videoList/videoList";
interface KillData {
data: Kill[]
data: VideList[]
total: number
page: number
}
@ -19,6 +19,7 @@ const Profession = () => {
const [categoryId, setCategoryId] = useState<number | null>(null)
const [data, setData] = useState<Map<number, KillData>>(new Map)
const [enable, setEnable] = useState(true)
const [loading, setLoading] = useState(false)
/**
* more
@ -35,6 +36,7 @@ const Profession = () => {
}
try {
setLoading(true)
const res = await HomeApi.skillList(categoryId!, page, 10)
const dataList = res.data.reduce((pre, cur) => {
const index = pre.findIndex(d => d.id === cur.id)
@ -54,6 +56,7 @@ const Profession = () => {
setData(oldData)
} catch (e) {
}
setLoading(false)
}
useEffect(() => {
@ -75,10 +78,6 @@ const Profession = () => {
setCategoryId(tab.tab?.value as number)
}
function jump(kill: Kill) {
HomeApi.skillSetPlay(kill.id)
Taro.navigateTo({url: `/pages/preview/videoFull/videoFull?url=${kill.resource.url}&poster=${kill.url_path}&title=${kill.resource.name}`})
}
function swiperChange(e) {
const categoryId = tabs[e.target.current].value
@ -89,21 +88,21 @@ const Profession = () => {
function KillList(data: KillData): JSX.Element {
if (!data?.data?.length) {
return <Empty name='暂无数据'/>
return (
<Empty name='暂无数据'/>
)
}
return (
<ScrollView
scrollY
onScrollToLower={() => getData(true)}
className={styles.height}>
{
data.data.map(d =>
<View className={styles.killBox} onClick={() => jump(d)}>
<Img width={320} height={180} src={d.url_path} mode='widthFix' className={styles.image}/>
<View className='text-ellipsis flex-1'>{d?.resource?.name}</View>
</View>
)
}
<View className={styles.container}>
{
data.data.map(d => <VideoList data={d} errorType='profession'/>)
}
</View>
<View className='text-center font-24 text-dark mt-2'></View>
</ScrollView>
)
@ -123,7 +122,8 @@ const Profession = () => {
className={styles.height}
style={{paddingTop: '10px'}}>
{
tabs.map(d => <SwiperItem key={d.title}>
tabs.map(d => <SwiperItem key={d.title} className='relative'>
<Spin enable={loading} block/>
{KillList(data.get(Number(d.value))!)}
</SwiperItem>)
}

@ -1,3 +1,8 @@
page {
background: #000 !important;
min-height: 100vh;
}
.video {
width: 100%;
height: 100vh;
@ -5,15 +10,19 @@
top: 0;
left: 0;
right: 0;
bottom: 0;
bottom: calc(env(safe-area-inset-bottom) + 250rpx);
margin: auto;
background: #000;
}
.title {
position: fixed;
z-index: 9999;
top: 20rpx;
left: 20rpx;
z-index: 10;
bottom: env(safe-area-inset-bottom);
width: 100%;
color: #fff;
padding: 0 30rpx;
box-sizing: border-box;
background: #000;
width: 100%;
}

@ -2,28 +2,52 @@ import {Video, View} from "@tarojs/components";
import {FC, useState} from "react";
import Taro from "@tarojs/taro";
import styles from './videoFull.module.scss'
import {brandApi} from "@/api";
import Collect from "@/components/collect/collect";
import Spin from "@/components/spinner";
interface Params {
url: string
poster?: string
title?: string
id: string
}
const VideoFull: FC = () => {
const params = Taro.useRouter().params as unknown as Params
const {id} = Taro.useRouter().params as unknown as Params
const video = Taro.createVideoContext('myVideo')
const [palying, setpalying] = useState(false)
const [data, setData] = useState<VideList | null>(null)
const [enable, setEnable] = useState<boolean>(!Taro.getCurrentInstance().preloadData)
Taro.useLoad(() => {
console.log(params)
if (!params.url) {
Taro.showModal({
title: '播放地址错',
success() {
Taro.navigateBack()
}
})
const preloadData: VideList = Taro.getCurrentInstance().preloadData as VideList
if (preloadData) {
setData(preloadData)
}
brandApi.videoInfo(id).then(res => {
if (!res?.resource?.url) {
Taro.showModal({
title: '加载资源失败',
confirmText: '退出',
showCancel: true,
success() {
Taro.navigateBack()
}
})
}
setData(res)
}).catch(() => {
if (!preloadData) {
Taro.showModal({
title: '加载资源失败',
confirmText: '退出',
showCancel: true,
success() {
Taro.navigateBack()
}
})
}
}).finally(() => {
setEnable(false)
})
})
function click() {
@ -46,28 +70,38 @@ const VideoFull: FC = () => {
}
return (
<>
{params.title && <View className={styles.title}>{params.title}</View>}
<Video
posterSize='100%'
id={'myVideo'}
onClick={click}
className={styles.video}
controls
// poster={params.poster}
src={params.url}
autoplay
showCenterPlayBtn
autoPauseIfOpenNative
autoPauseIfNavigate
playBtnPosition='center'
showFullscreenBtn={false}
enableProgressGesture={false}
onPlay={() => setpalying(true)}
onPause={() => setpalying(false)}
onError={onError}
/>
</>
<View>
{
data ? <>
<Video
posterSize='100%'
id={'myVideo'}
onClick={click}
className={styles.video}
controls
src={data?.resource?.url}
autoplay
showCenterPlayBtn
autoPauseIfOpenNative
autoPauseIfNavigate
playBtnPosition='center'
showFullscreenBtn={false}
enableProgressGesture={false}
onPlay={() => setpalying(true)}
onPause={() => setpalying(false)}
onError={onError}
/>
<View className={styles.title}>
<View className='flex'>
<View className='font-36 font-weight flex-1 pr-3 text-row1'>{data.title}</View>
<Collect owner_id={Number(id)} owner_type={2} styles={{color: '#fff'}} select={data.collects}/>
</View>
<View className='font-32 mt-1 text-ellipsis-2'>{data.introduction}</View>
</View>
</> : <Spin enable={enable} overlay/>
}
</View>
)
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 109 KiB

@ -8,6 +8,7 @@ interface Curriculum {
id: number;
title: string;
charge: number;
collect:boolean
/** 课时 */
class_hour: number;
created_at: string;

17
types/home.d.ts vendored

@ -1,4 +1,4 @@
interface Health {
interface VideList {
id: number
title: string
introduction: string
@ -7,6 +7,7 @@ interface Health {
/** 播放量 */
video_view: number
publish_time:string
collects:boolean
}
interface Brand {
@ -23,13 +24,13 @@ interface Resource {
url: string
}
interface Kill {
id: number
resource: Resource
introduction:string
title: string
url_path: string
}
// interface Kill {
// id: number
// resource: Resource
// introduction:string
// title: string
// url_path: string
// }
interface Illness {
id: number

5
types/user.d.ts vendored

@ -109,5 +109,10 @@ interface Create {
owner_type: CreateOwnerType
}
type ImgErrType = "acquiesce" // 默认
| 'avatar' // 头像
| 'health' // 健康
| 'profession' // 技能
| 'brand' // 品牌

Loading…
Cancel
Save