commit
19e01cf623
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,11 @@ |
||||
import {request} from "@/api/request"; |
||||
|
||||
export const SearchApi = { |
||||
/** 品牌列表 */ |
||||
list(page: number , page_size: number, name: string) { |
||||
return request<{ |
||||
data: any[], |
||||
total: number |
||||
}>(`/home/v1/search/home?page=${page}&page_size=${page_size}&keywords=${name}&sort_type=1&sort=0` , "GET") |
||||
}, |
||||
} |
@ -0,0 +1,12 @@ |
||||
.imgBox { |
||||
position: relative; |
||||
} |
||||
|
||||
.imgError { |
||||
position: absolute; |
||||
top: 0; |
||||
left: 0; |
||||
bottom: 0; |
||||
right: 0; |
||||
margin: auto; |
||||
} |
@ -0,0 +1,77 @@ |
||||
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"; |
||||
|
||||
interface Props extends ImageProps { |
||||
width?: number | string |
||||
height?: number | string |
||||
fallback?: string |
||||
} |
||||
|
||||
const Img: FC<Props> = ({src, mode = 'aspectFill', width, height, fallback = shard, ...props}) => { |
||||
const [isError, setIsError] = useState(false) |
||||
const [loading, setLoading] = useState(true) |
||||
|
||||
const imgAnimation = Taro.createAnimation({duration: 0}).opacity(0).step() |
||||
const [animationData, setAnimationData] = useState<TaroGeneral.IAnyObject>(imgAnimation.export()) |
||||
|
||||
useEffect(() => { |
||||
setIsError(!src) |
||||
setLoading(!!src) |
||||
}, [src]) |
||||
|
||||
// 图片加载失败
|
||||
function onErrorHandler() { |
||||
setLoading(false) |
||||
setIsError(true) |
||||
} |
||||
|
||||
function onLoadHandler() { |
||||
setLoading(false) |
||||
setIsError(false) |
||||
imgAnimation.opacity(1).step({duration: 200}) |
||||
setAnimationData(imgAnimation.export()) |
||||
} |
||||
|
||||
return ( |
||||
<View |
||||
style={{ |
||||
width: `${width}rpx`, |
||||
height: `${height}rpx`, |
||||
backgroundColor: '#F8F8F8' |
||||
}} |
||||
className={`${props?.className} ${styles.imgBox}`}> |
||||
{!isError && |
||||
<View animation={animationData}> |
||||
<Image |
||||
{...props} |
||||
src={src} |
||||
mode={mode} |
||||
lazyLoad |
||||
fadeIn |
||||
defaultSource={fallback} |
||||
style={{width: width ? `${width}rpx` : "100%", height: height ? `${height}rpx` : "100%"}} |
||||
onError={onErrorHandler} |
||||
onLoad={onLoadHandler}/> |
||||
</View> |
||||
} |
||||
{ |
||||
isError && !loading && |
||||
<Image |
||||
className={styles.imgError} |
||||
mode='aspectFill' |
||||
src={fallback} |
||||
lazyLoad |
||||
fadeIn |
||||
style={{ |
||||
width: "100%", |
||||
height: "100%", |
||||
}}/> |
||||
} |
||||
</View> |
||||
) |
||||
} |
||||
|
||||
export default Img |
@ -0,0 +1,47 @@ |
||||
.content { |
||||
position: relative; |
||||
width: 100%; |
||||
display: flex; |
||||
flex-direction: column; |
||||
justify-content: center; |
||||
align-items: center; |
||||
|
||||
image { |
||||
width: 320rpx; |
||||
height: 208rpx; |
||||
} |
||||
|
||||
.title { |
||||
font-size: 28rpx; |
||||
font-weight: bold; |
||||
color: #323635; |
||||
} |
||||
|
||||
.label { |
||||
font-size: 24rpx; |
||||
font-weight: 500; |
||||
color: #909795; |
||||
line-height: 24rpx; |
||||
margin: 20rpx 0 ; |
||||
} |
||||
|
||||
.button { |
||||
padding: 0 60rpx; |
||||
height: 76rpx; |
||||
background: #45D4A8; |
||||
border-radius: 38rpx 38rpx 38rpx 38rpx; |
||||
color: #fff; |
||||
line-height: 76rpx; |
||||
text-align: center; |
||||
font-size: 32rpx; |
||||
font-weight: 500; |
||||
} |
||||
|
||||
.nextLabel { |
||||
font-size: 24rpx; |
||||
font-weight: 500; |
||||
color: #909795; |
||||
line-height: 24rpx; |
||||
margin-top: 15rpx; |
||||
} |
||||
} |
@ -0,0 +1,75 @@ |
||||
import {CSSProperties, FC, useState} from "react"; |
||||
import {View, Image} from "@tarojs/components"; |
||||
import styles from './index.module.scss' |
||||
import NoLogin from '@/static/img/noLogin.png' |
||||
import {Profile} from "@/store"; |
||||
import Taro from "@tarojs/taro"; |
||||
import {userApi} from "@/api"; |
||||
|
||||
interface Props { |
||||
tips?: string |
||||
height?: number |
||||
paddingTop?: number |
||||
style?: CSSProperties |
||||
offImage?: boolean |
||||
onSuccess?: VoidFunction |
||||
} |
||||
|
||||
const LoginView: FC<Props> = (props) => { |
||||
const [isLoading, setLoading] = useState(false) |
||||
const {setUser, setToken, setCompany} = Profile.useContainer() |
||||
const sizeStyle: CSSProperties = { |
||||
height: `${props.height || 1000}rpx`, |
||||
paddingTop: `${props.paddingTop || 0}rpx`, |
||||
...props.style, |
||||
} |
||||
|
||||
function login() { |
||||
if (isLoading) return; |
||||
Taro.showLoading({title: '微信授权中...'}) |
||||
setLoading(true) |
||||
Taro.login({ |
||||
success: async (res) => { |
||||
try { |
||||
const {catch_key, user, token, company} = await userApi.login(res.code) |
||||
Taro.hideLoading() |
||||
if (token) { |
||||
Taro.showToast({title: '授权成功', duration: 1500, icon: 'success', mask: true}) |
||||
setTimeout(() => { |
||||
setUser(user) |
||||
setToken(token) |
||||
setCompany(company) |
||||
setLoading(false) |
||||
if (props.onSuccess) { |
||||
props.onSuccess() |
||||
} else { |
||||
Taro.switchTab({url: '/pages/home/home'}) |
||||
} |
||||
}, 1500) |
||||
} else { |
||||
Taro.setStorageSync('openid', catch_key) |
||||
Taro.reLaunch({url: '/pages/check/check'}) |
||||
} |
||||
} catch (e) { |
||||
Taro.hideLoading() |
||||
} |
||||
setLoading(false) |
||||
} |
||||
}) |
||||
} |
||||
|
||||
return ( |
||||
<View className={styles.content} style={sizeStyle}> |
||||
{ |
||||
!props.offImage && <> |
||||
<Image src={NoLogin}/> |
||||
<View className={styles.title}>暂未登录</View> |
||||
</> |
||||
} |
||||
<View className={styles.label}>点击按钮完成微信授权</View> |
||||
<View onClick={login} className={styles.button}>立即登录</View> |
||||
</View> |
||||
) |
||||
} |
||||
|
||||
export default LoginView |
@ -0,0 +1,33 @@ |
||||
.navigation { |
||||
position: sticky; |
||||
top: 0; |
||||
left: 0; |
||||
width: 730rpx; |
||||
padding-left: 20rpx; |
||||
z-index: 10; |
||||
overflow: hidden; |
||||
background: #fff; |
||||
} |
||||
|
||||
.leftNode { |
||||
position: absolute; |
||||
display: flex; |
||||
left: 0; |
||||
bottom: 0; |
||||
align-items: center; |
||||
} |
||||
|
||||
.text { |
||||
position: absolute; |
||||
left: 0; |
||||
right: 0; |
||||
margin: auto; |
||||
display: flex; |
||||
justify-content: center; |
||||
align-items: center; |
||||
} |
||||
|
||||
.arrow { |
||||
width: 32px; |
||||
height: 32px; |
||||
} |
@ -0,0 +1,56 @@ |
||||
import {Image, View} from "@tarojs/components"; |
||||
import React, {FC, ReactNode, useMemo} from "react"; |
||||
import styles from './navigationBar.module.scss' |
||||
import Taro from "@tarojs/taro"; |
||||
import leftArrow from '@/static/img/leftArrow.png' |
||||
|
||||
interface Props { |
||||
// 文本
|
||||
text?: string | ReactNode |
||||
children?: ReactNode | ReactNode[] |
||||
// 左边节点
|
||||
leftNode?: string | ReactNode | ReactNode[] |
||||
// 字体颜色
|
||||
color?: string |
||||
// 背景颜色
|
||||
backgroundColor?: string |
||||
// 取消返回按钮
|
||||
cancelBack?: boolean |
||||
// 跟随页面滚动
|
||||
inherit?: boolean |
||||
className?: string |
||||
} |
||||
|
||||
const NavigationBar: FC<Props> = (props) => { |
||||
const globalData = Taro.getApp().globalData |
||||
const navigationBarStyle = useMemo((): React.CSSProperties => ({ |
||||
background: props.backgroundColor, |
||||
position: props.inherit ? 'inherit' : "sticky", |
||||
paddingTop: globalData.statusBarHeight + 'px', |
||||
height: globalData.textBarHeight + globalData.statusBarHeight + 'px', |
||||
boxSizing: 'border-box', |
||||
}), [props]) |
||||
|
||||
|
||||
const navigationTextStyle = useMemo((): React.CSSProperties => ({ |
||||
color: props.color, |
||||
height: globalData.textBarHeight + 'px', |
||||
}), [props]) |
||||
|
||||
return ( |
||||
<View className={`${props.className} ${styles.navigation}`} style={navigationBarStyle}> |
||||
<View style={navigationTextStyle} className={styles.leftNode}> |
||||
{!props.cancelBack && <View className="flex flex-column justify-center align-center" style={{ |
||||
width: globalData.textBarHeight + 'px', |
||||
height: globalData.textBarHeight + 'px', |
||||
}} onClick={() => Taro.navigateBack()}> |
||||
<Image src={leftArrow} mode='aspectFill' className={styles.arrow}/> |
||||
</View>} |
||||
{props.leftNode} |
||||
</View> |
||||
<View style={navigationTextStyle} className={styles.text}>{props.children || props.text}</View> |
||||
</View> |
||||
) |
||||
} |
||||
|
||||
export default NavigationBar |
@ -0,0 +1,169 @@ |
||||
import Taro from "@tarojs/taro"; |
||||
import {View, Image} from '@tarojs/components' |
||||
import {Component, ReactNode} from "react"; |
||||
import indicator from './loading.svg' |
||||
import './style.scss' |
||||
|
||||
// 动画状态
|
||||
type Status = |
||||
| 'dismissed' |
||||
| 'forward' |
||||
| 'reverse' |
||||
| 'completed' |
||||
|
||||
interface Props { |
||||
enable?: boolean // 控制显现
|
||||
overlay?: boolean // 页面覆盖
|
||||
block?: boolean // 块级
|
||||
} |
||||
|
||||
interface State { |
||||
status?: Status |
||||
background?: Record<string, unknown>, |
||||
rotation?: Record<string, unknown>, |
||||
} |
||||
|
||||
type StateSetter = (state: State) => void |
||||
|
||||
type Controller = { |
||||
setTick: (enabled: boolean | undefined) => void |
||||
clear: () => void |
||||
} |
||||
|
||||
function createController(setState: StateSetter): Controller { |
||||
const background = Taro.createAnimation({duration: 600}) |
||||
const rotation = Taro.createAnimation({duration: 600}) |
||||
let rotateTimer: ReturnType<typeof setTimeout> | undefined |
||||
let status: Status | undefined |
||||
|
||||
const notifyListener = () => setState({ |
||||
status, |
||||
background: background.export(), |
||||
rotation: rotation.export(), |
||||
}) |
||||
|
||||
const clearAnimation = (notify = true) => { |
||||
// 清空旋转动画定时器
|
||||
if (rotateTimer != null) { |
||||
clearTimeout(rotateTimer) |
||||
rotateTimer = undefined |
||||
} |
||||
|
||||
// 清空动画
|
||||
background.step().export() |
||||
rotation.step().export() |
||||
|
||||
// 通知 UI 刷新
|
||||
if (notify) { |
||||
notifyListener() |
||||
} |
||||
} |
||||
|
||||
const setAnimation = (opacity: number) => { |
||||
// 停止旋转动画
|
||||
if (rotateTimer != null) { |
||||
clearTimeout(rotateTimer) |
||||
} |
||||
|
||||
// 旋转动画定时器
|
||||
const rotate = () => { |
||||
rotation.opacity(opacity).rotate(360).step({duration: 600}) |
||||
notifyListener() |
||||
rotateTimer = setTimeout(rotate, 600) |
||||
} |
||||
|
||||
// 背景动画
|
||||
background.backgroundColor(`rgba(255,255,255,${opacity})`).step() |
||||
|
||||
// 启动旋转动画
|
||||
rotate() |
||||
} |
||||
|
||||
const onFinish = (opacity: number, nextStatus: Status) => { |
||||
const lockStatus = status |
||||
if (lockStatus === status) { |
||||
background.backgroundColor(`rgba(255,255,255,${opacity})`).step({duration: 0}) |
||||
if (nextStatus === 'dismissed') { |
||||
clearAnimation() |
||||
} |
||||
status = nextStatus |
||||
notifyListener() |
||||
} |
||||
} |
||||
|
||||
const setStatus = (newStatus: Status, opacity: number) => { |
||||
if (status !== newStatus) { |
||||
status = newStatus |
||||
setAnimation(opacity) |
||||
if (status === 'reverse') { |
||||
onFinish(0, 'dismissed') |
||||
} else if (status === 'forward') { |
||||
onFinish(1, 'completed') |
||||
} |
||||
} |
||||
} |
||||
|
||||
const setTick = (enabled?: boolean) => { |
||||
if (enabled !== true) { |
||||
if (status === 'dismissed') { |
||||
clearAnimation() |
||||
} else if (status !== 'reverse') { |
||||
setStatus('reverse', 0) |
||||
} |
||||
} else if (status === 'completed') { |
||||
// ignore
|
||||
} else if (status !== 'forward') { |
||||
setStatus('forward', 1) |
||||
} |
||||
} |
||||
|
||||
return { |
||||
setTick, |
||||
clear() { |
||||
clearAnimation(false) |
||||
}, |
||||
} |
||||
} |
||||
|
||||
export default class Spin extends Component<Props, State> { |
||||
constructor(props) { |
||||
super(props) |
||||
this.controller = createController(this.onAnimation) |
||||
this.state = {} |
||||
} |
||||
|
||||
controller: Controller |
||||
|
||||
onAnimation = (state: State) => { |
||||
this.setState((s) => ({...s, ...state})) |
||||
} |
||||
|
||||
// componentDidMount(): void {
|
||||
// this.controller.setTick(this.props.enable)
|
||||
// }
|
||||
|
||||
componentDidUpdate(): void { |
||||
this.controller.setTick(this.props.enable) |
||||
} |
||||
|
||||
componentWillUnmount(): void { |
||||
this.controller.clear() |
||||
} |
||||
|
||||
shouldComponentUpdate(nextProps: Readonly<Props>, nextState: Readonly<State>): boolean { |
||||
return nextProps.enable !== this.props.enable |
||||
|| nextProps.overlay !== this.props.overlay |
||||
|| nextState.status != this.state.status |
||||
} |
||||
|
||||
render(): ReactNode { |
||||
return ( |
||||
<View |
||||
className={`spinner-wrapper ${this.state.status} ${this.props.overlay && 'is-fixed'} ${this.props.block && 'is-block'}`}> |
||||
<View className={`spinner ${this.state.status}`}> |
||||
<Image className="spinner-icon" src={indicator}/> |
||||
</View> |
||||
</View> |
||||
) |
||||
} |
||||
} |
After Width: | Height: | Size: 813 B |
@ -0,0 +1,76 @@ |
||||
.spinner-wrapper { |
||||
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 { |
||||
background-color: rgba(#fff, 1.0); |
||||
z-index: 99999; |
||||
position: fixed; |
||||
width: 100%; |
||||
height: 100%; |
||||
top: 0; |
||||
left: 0; |
||||
right: 0; |
||||
bottom: 0; |
||||
} |
||||
|
||||
&.reverse, |
||||
&.dismissed { |
||||
background-color: rgba(#fff, 0.0); |
||||
} |
||||
|
||||
&.dismissed { |
||||
position: fixed; |
||||
width: 0; |
||||
height: 0; |
||||
left: -10000px; |
||||
top: -10000px; |
||||
right: auto; |
||||
bottom: auto; |
||||
overflow: hidden; |
||||
} |
||||
} |
||||
|
||||
.spinner-wrapper, |
||||
.spinner { |
||||
display: inline-flex; |
||||
flex-direction: column; |
||||
justify-content: center; |
||||
align-items: center; |
||||
width: 100%; |
||||
} |
||||
|
||||
.spinner { |
||||
padding: 10rpx; |
||||
opacity: 1; |
||||
transition: opacity 1200ms ease-out; |
||||
|
||||
&.reverse, |
||||
&.dismissed { |
||||
opacity: 0; |
||||
} |
||||
} |
||||
|
||||
.spinner-icon { |
||||
width: 38px; |
||||
height: 38px; |
||||
animation: spinner-rotation 600ms linear infinite; |
||||
} |
||||
|
||||
@keyframes spinner-rotation { |
||||
from { |
||||
transform: rotate(0); |
||||
} |
||||
|
||||
to { |
||||
transform: rotate(360deg); |
||||
} |
||||
} |
@ -0,0 +1,40 @@ |
||||
|
||||
.singleCover { |
||||
position: absolute; |
||||
bottom: 0; |
||||
top: 0; |
||||
margin: auto; |
||||
height: 220rpx; |
||||
right: 20rpx; |
||||
max-width: 50%; |
||||
min-width: 300rpx; |
||||
padding: 25rpx; |
||||
background: rgba(#000, .7); |
||||
border-radius: 20rpx; |
||||
line-height: 40rpx; |
||||
display: flex; |
||||
flex-direction: column; |
||||
justify-content: space-between; |
||||
|
||||
view:first-child { |
||||
font-weight: bold; |
||||
color: #fff; |
||||
} |
||||
|
||||
view:not(:first-child) { |
||||
background: #fff; |
||||
border-radius: 10rpx; |
||||
padding: 15rpx; |
||||
} |
||||
} |
||||
|
||||
|
||||
.correct { |
||||
background: #bdf6c1 !important; |
||||
color: #fff !important; |
||||
} |
||||
|
||||
.mistake { |
||||
background: #e7abab !important; |
||||
color: #fff !important; |
||||
} |
@ -0,0 +1,106 @@ |
||||
import {FC, useEffect, useMemo, useState} from "react"; |
||||
import {View} from "@tarojs/components"; |
||||
import Taro from "@tarojs/taro"; |
||||
import styles from './single.module.scss' |
||||
|
||||
interface Props { |
||||
full: boolean |
||||
topic?: ShareSubject |
||||
examination: (result: boolean) => void |
||||
} |
||||
|
||||
type AnswerType = "true" | 'false' |
||||
|
||||
export const Single: FC<Props> = (props) => { |
||||
let timer: NodeJS.Timeout |
||||
const [lastState, setLastState] = useState<0 | 1>(0) // 0为竖屏,1为横屏
|
||||
const [result, setResult] = useState<AnswerType | undefined>(undefined) |
||||
let lastTime = Date.now() |
||||
Taro.startAccelerometer() |
||||
Taro.onAccelerometerChange((res) => { |
||||
const now = Date.now(); |
||||
if (now - lastTime < 500) return; |
||||
|
||||
lastTime = now; |
||||
let nowState; |
||||
const Roll = Math.atan2(-res.x, Math.sqrt(res.y * res.y + res.z * res.z)) * 57.3; |
||||
const Pitch = Math.atan2(res.y, res.z) * 57.3; |
||||
|
||||
// 横屏状态
|
||||
if (Roll > 50) { |
||||
if ((Pitch > -180 && Pitch < -60) || (Pitch > 130)) { |
||||
nowState = 1; |
||||
} else { |
||||
nowState = lastState; |
||||
} |
||||
|
||||
} else if ((Roll > 0 && Roll < 30) || (Roll < 0 && Roll > -30)) { |
||||
let absPitch = Math.abs(Pitch); |
||||
// 如果手机平躺,保持原状态不变,40容错率
|
||||
if ((absPitch > 140 || absPitch < 40)) { |
||||
nowState = lastState; |
||||
} else if (Pitch < 0) { |
||||
// 收集竖向正立的情况
|
||||
nowState = 0; |
||||
} else { |
||||
nowState = lastState; |
||||
} |
||||
} else { |
||||
nowState = lastState; |
||||
} |
||||
|
||||
// 状态变化时,触发
|
||||
if (nowState !== lastState) { |
||||
setLastState(nowState); |
||||
} |
||||
}) |
||||
|
||||
useEffect(() => { |
||||
timer = setTimeout(() => { |
||||
props.examination(false) |
||||
}, 4000) |
||||
}, [props.topic]) |
||||
|
||||
const style: React.CSSProperties = useMemo(() => ({ |
||||
transform: lastState === 1 && props.full ? "rotate(90deg)" : 'none' |
||||
}), [lastState, props.full]) |
||||
|
||||
function examination(answer: AnswerType) { |
||||
if (result) return; |
||||
clearTimeout(timer) |
||||
setResult(answer) |
||||
|
||||
setTimeout(() => { |
||||
props.examination(props.topic?.right_value === result) |
||||
setResult(undefined) |
||||
}, 2000) |
||||
} |
||||
|
||||
function judgment(answer: AnswerType): string { |
||||
if (result !== answer) return '' |
||||
if (props.topic?.right_answer === answer && result === answer) { |
||||
return styles.correct |
||||
} |
||||
return styles.mistake |
||||
} |
||||
|
||||
return ( |
||||
<> |
||||
{ |
||||
props.topic && <View className={styles.singleCover} style={style}> |
||||
<View>{props.topic.question}</View> |
||||
<View className={judgment("true")} |
||||
onClick={() => examination("true")}> |
||||
{props.topic.right_value} |
||||
</View> |
||||
<View className={judgment("false")} |
||||
onClick={() => examination("false")}> |
||||
{props.topic.error_value} |
||||
</View> |
||||
</View> |
||||
} |
||||
</> |
||||
) |
||||
} |
||||
|
||||
export default Single |
@ -1,3 +0,0 @@ |
||||
export default definePageConfig({ |
||||
navigationBarTitleText: '分类' |
||||
}) |
@ -1,21 +0,0 @@ |
||||
import {FC} from "react"; |
||||
import {View} from "@tarojs/components"; |
||||
import {useRouter} from "@tarojs/taro"; |
||||
|
||||
type SortType = '' |
||||
|
||||
interface Params { |
||||
id: number |
||||
type: SortType |
||||
jumpUrl: (data:Record<string, any>) => void |
||||
} |
||||
|
||||
const Sort: FC = () => { |
||||
const params = useRouter().params as unknown as Params |
||||
|
||||
return ( |
||||
<View>ds</View> |
||||
) |
||||
} |
||||
|
||||
export default Sort |
@ -1,13 +1,19 @@ |
||||
import {FC} from "react"; |
||||
import {Text, View} from "@tarojs/components"; |
||||
import {View} from "@tarojs/components"; |
||||
import styles from "../home.module.scss"; |
||||
import Icon from "@/components/icon"; |
||||
import Taro from "@tarojs/taro"; |
||||
|
||||
|
||||
export const Search: FC = () => { |
||||
|
||||
function jump(){ |
||||
Taro.navigateTo({url:'/pages/preview/search/search/index'}) |
||||
} |
||||
return ( |
||||
<View className={styles.search}> |
||||
<Icon name='search' size={18}/> |
||||
<Text className='ml-1'>搜索课程</Text> |
||||
<View className={styles.search} onClick={jump}> |
||||
<Icon name='search' size={18} color='#808080'/> |
||||
<View className='ml-1'>点击搜索</View> |
||||
</View> |
||||
) |
||||
} |
||||
|
@ -1,7 +1,7 @@ |
||||
export default definePageConfig({ |
||||
navigationBarTitleText: '康一诺', |
||||
// navigationStyle: 'custom',
|
||||
navigationStyle: 'custom', |
||||
navigationBarBackgroundColor: '#92ecc5', |
||||
navigationBarTextStyle: 'white', |
||||
// navigationBarTextStyle: 'white',
|
||||
onReachBottomDistance: 50 |
||||
}) |
||||
|
@ -1,5 +1,3 @@ |
||||
export default definePageConfig({ |
||||
navigationBarTitleText: '医学道', |
||||
navigationBarBackgroundColor: '#92ecc5', |
||||
navigationBarTextStyle: 'white', |
||||
navigationStyle: 'custom', |
||||
}) |
||||
|
@ -1,4 +1,3 @@ |
||||
export default definePageConfig({ |
||||
navigationStyle: 'custom', |
||||
navigationBarTitleText: '登录' |
||||
}) |
||||
|
@ -1,4 +1,4 @@ |
||||
export default definePageConfig({ |
||||
navigationBarTitleText: '', |
||||
onReachBottomDistance: 30 |
||||
onReachBottomDistance: 50 |
||||
}) |
||||
|
@ -1,4 +1,4 @@ |
||||
export default definePageConfig({ |
||||
navigationBarTitleText: '疾病知识列表', |
||||
onReachBottomDistance: 30 |
||||
navigationBarTitleText: '', |
||||
onReachBottomDistance: 50 |
||||
}) |
||||
|
@ -0,0 +1,13 @@ |
||||
import {FC} from "react"; |
||||
import {Text, View} from "@tarojs/components"; |
||||
import styles from "../sort.module.scss"; |
||||
import Icon from "@/components/icon"; |
||||
|
||||
export const Search: FC = () => { |
||||
return ( |
||||
<View className={styles.search}> |
||||
<Icon name='search' size={18}/> |
||||
<Text className='ml-1'>搜索课程</Text> |
||||
</View> |
||||
) |
||||
} |
@ -1,3 +1,4 @@ |
||||
export default definePageConfig({ |
||||
navigationBarTitleText: '疾病知识', |
||||
navigationStyle: "custom", |
||||
}) |
||||
|
@ -0,0 +1,53 @@ |
||||
.box { |
||||
margin-left:30rpx; |
||||
margin-bottom: 24rpx; |
||||
width:690rpx; |
||||
display: flex; |
||||
margin-bottom: 20rpx; |
||||
background-color: #fff; |
||||
border-radius: 16rpx; |
||||
padding: 24rpx; |
||||
box-sizing: border-box; |
||||
position: relative; |
||||
} |
||||
.image{ |
||||
background-color: #ededed; |
||||
border-radius: 8rpx; |
||||
} |
||||
.play { |
||||
position: absolute; |
||||
z-index: 9999; |
||||
width: 50rpx !important; |
||||
height: 50rpx !important; |
||||
top: 99rpx; |
||||
left: 99rpx; |
||||
background: transparent !important; |
||||
} |
||||
.rightBox{ |
||||
padding-left: 24rpx; |
||||
box-sizing: border-box; |
||||
flex: 1; |
||||
} |
||||
.articleLeftBox{ |
||||
padding-right: 24rpx; |
||||
box-sizing: border-box; |
||||
flex: 1; |
||||
} |
||||
.videoRightBox{ |
||||
padding-left: 24rpx; |
||||
box-sizing: border-box; |
||||
flex: 1; |
||||
} |
||||
.desc{ |
||||
font-size: 24rpx; |
||||
font-weight: 500; |
||||
color: #909795; |
||||
line-height: 34rpx; |
||||
display: -webkit-box; |
||||
word-break: break-all; |
||||
text-overflow: ellipsis; |
||||
overflow: hidden; |
||||
-webkit-box-orient:vertical; |
||||
-webkit-line-clamp:2; |
||||
} |
||||
|
@ -0,0 +1,167 @@ |
||||
import {FC, useCallback, useEffect, useState} from "react"; |
||||
import {Image, ScrollView, View} from "@tarojs/components"; |
||||
import styles from './list.module.scss' |
||||
import Taro from "@tarojs/taro"; |
||||
import Empty from "@/components/empty/empty"; |
||||
import Img from "@/components/image/image"; |
||||
import { SearchApi } from "@/api/search"; |
||||
import { AtLoadMore } from 'taro-ui' |
||||
import {rfc33392time} from "@/utils/day"; |
||||
import play from "@/static/img/play.png"; |
||||
|
||||
type Props = { |
||||
name:string |
||||
clear:boolean |
||||
} |
||||
const SearchList: FC<Props> = ({name,clear}) => { |
||||
console.log(name,'name') |
||||
const globalData = Taro.getApp().globalData |
||||
const [page, setPage] = useState(1) |
||||
const [brands, setBrands] = useState<any[]>([]) |
||||
const [total, setTotal] = useState(0) |
||||
const [text, setText] = useState('') |
||||
const [loading, setLoading] = useState(true) |
||||
|
||||
useEffect(()=>{ |
||||
if(!clear){ |
||||
setBrands([]) |
||||
setLoading(true) |
||||
} |
||||
},[clear]) |
||||
useEffect(() => { |
||||
if(name && clear){ |
||||
getData() |
||||
} |
||||
}, [page,name,clear]) |
||||
|
||||
const getData = useCallback(async () => { |
||||
try { |
||||
const data = await SearchApi.list(page, 10,name) |
||||
if (page === 1) { |
||||
if (data.data.length < 10) { |
||||
setText('没有更多了~') |
||||
} else { |
||||
setText('上拉加载更多~') |
||||
} |
||||
setBrands([ |
||||
...data.data |
||||
]) |
||||
}else{ |
||||
setBrands([ |
||||
...brands, |
||||
...data.data |
||||
]) |
||||
} |
||||
setTotal(data.total) |
||||
|
||||
} catch (e) { |
||||
} |
||||
setLoading(false) |
||||
}, [page,name]) |
||||
|
||||
|
||||
function jumpInfo(id: number,type:string,health:any) { |
||||
console.log(type,'type') |
||||
let url = '' |
||||
switch (type){ |
||||
case 'brand': |
||||
url = '/pages/preview/brand/info/info'; |
||||
break; |
||||
case 'illness': |
||||
url = '/pages/preview/illness/list/list'; |
||||
break; |
||||
case 'article': |
||||
url = '/pages/preview/illness/article/article' |
||||
break |
||||
case 'video_records': |
||||
return Taro.navigateTo({url: `/pages/preview/videoFull/videoFull?url=${health.resources.url}&poster=${health.url_path}&title=${health.title}`}) |
||||
case 'courses': |
||||
url = '/pages/business/videoInfo/videoInfo' |
||||
break |
||||
} |
||||
Taro.navigateTo({url: `${url}?id=${id}`}) |
||||
|
||||
} |
||||
|
||||
function onScrollToLower(){ |
||||
if (brands?.length < total) { |
||||
setPage(page + 1) |
||||
getData().then() |
||||
} else { |
||||
setText('没有更多了~') |
||||
} |
||||
} |
||||
|
||||
return ( |
||||
<ScrollView |
||||
className='scrollview' |
||||
scrollY |
||||
scrollWithAnimation |
||||
style={{height:`${globalData.windowHeight-60}px`,backgroundColor:`#f5f5f5`}} |
||||
onScrollToLower={onScrollToLower} |
||||
> |
||||
{ loading && <AtLoadMore status={'loading'}/>} |
||||
{ |
||||
brands.length >= 1 && !loading && |
||||
<> |
||||
{brands.map((d) => |
||||
<View onClick={() => jumpInfo(d.data.id,d.data.table,d.data['@data'])} className={styles.box} key={d.data.id}> |
||||
{ |
||||
d.data.table === 'brand' && |
||||
<> |
||||
<Img width={128} height={128} src={d.data['@data'].logo} mode='aspectFill' className={styles.image}/> |
||||
<View className={styles.rightBox}> |
||||
<View className='font-weight mb-2 font-28'>{d.data.title}</View> |
||||
<View className={styles.desc}>{d.data.content || '暂无品牌简介'}</View> |
||||
</View> |
||||
</> |
||||
} |
||||
{ |
||||
d.data.table === 'article' && |
||||
<> |
||||
<View className={styles.articleLeftBox}> |
||||
<View className='font-weight mb-2 font-28 lh-40'>{d.data['@data'].title}</View> |
||||
<View className={styles.desc}>{rfc33392time(d.data['@data'].created_at)} {d.data['@data'].page_view}阅读</View> |
||||
</View> |
||||
<Img width={172} height={128} src={d.data['@data'].logo} mode='aspectFill' className={styles.image}/> |
||||
</> |
||||
} |
||||
{ |
||||
d.data.table === 'video_records' && |
||||
<> |
||||
<Img width={192} height={192} src={d.data['@data'].url_path} mode='aspectFill' className={styles.image}/> |
||||
<Image src={play} className={styles.play} mode='aspectFit'/> |
||||
<View className={styles.videoRightBox}> |
||||
<View className='font-weight mb-2 font-28 lh-40'>{d.data['@data'].title}</View> |
||||
<View className={styles.desc}>{d.data['@data'].introduction}</View> |
||||
<View className={`${styles.desc} mt-2`}>播放量: {d.data['@data'].video_view}</View> |
||||
</View> |
||||
</> |
||||
} |
||||
{ |
||||
d.data.table === 'courses' && |
||||
<> |
||||
<Img width={192} height={138} src={d.data['@data'].thumb} mode='aspectFill' className={styles.image}/> |
||||
<View className={styles.articleLeftBox}> |
||||
<View className='font-weight mb-2 font-28 lh-40'>{d.data['@data'].title}</View> |
||||
<View className={styles.desc}>课时:{d.data['@data'].class_hour}节 销量:{d.data['@data'].charge}</View> |
||||
</View> |
||||
</> |
||||
} |
||||
|
||||
|
||||
|
||||
|
||||
</View> |
||||
) |
||||
} |
||||
<View style={{width: '750rpx', textAlign: 'center', color: '#999'}} className="font-28 mt-3 mb-3">{text}</View> |
||||
</> |
||||
} |
||||
{ !loading && brands.length === 0 && <Empty name='空空如也'/>} |
||||
</ScrollView> |
||||
) |
||||
} |
||||
|
||||
export default SearchList |
||||
|
@ -0,0 +1,5 @@ |
||||
export default definePageConfig({ |
||||
navigationBarTitleText: '搜索', |
||||
navigationBarBackgroundColor:'#F2F8F6', |
||||
onReachBottomDistance: 50 |
||||
}) |
@ -0,0 +1,50 @@ |
||||
page{ |
||||
background-color:#F2F8F6; |
||||
padding-left: 30rpx; |
||||
.searchBox{ |
||||
width: 690rpx; |
||||
height: 68rpx; |
||||
background: #FFFFFF; |
||||
border-radius: 32rpx 32rpx 32rpx 32rpx; |
||||
box-sizing: border-box; |
||||
display: flex; |
||||
align-items: center; |
||||
padding:0 24rpx; |
||||
.input{ |
||||
font-size: 28rpx; |
||||
font-weight: 500; |
||||
margin-left: 24rpx; |
||||
flex:1; |
||||
} |
||||
} |
||||
.titleBox{ |
||||
margin-top: 60rpx; |
||||
width: 100%; |
||||
display: flex; |
||||
box-sizing: border-box; |
||||
padding-right: 30rpx; |
||||
justify-content: space-between; |
||||
.title{ |
||||
font-size: 28rpx; |
||||
font-weight: bold; |
||||
color: #323635; |
||||
} |
||||
} |
||||
.contentBox{ |
||||
display: flex; |
||||
flex-wrap: wrap; |
||||
.items{ |
||||
display: inline-block; |
||||
background: #FFFFFF; |
||||
border-radius:32rpx; |
||||
padding: 10rpx 24rpx; |
||||
font-size: 24rpx; |
||||
font-weight: 500; |
||||
color: #323635; |
||||
line-height: 24rpx; |
||||
margin-top: 24rpx; |
||||
margin-right: 24rpx; |
||||
} |
||||
} |
||||
} |
||||
|
@ -0,0 +1,116 @@ |
||||
import {Input, View, Text, PageContainer} from "@tarojs/components"; |
||||
import {FC, useEffect, useState} from "react"; |
||||
import { AtIcon } from 'taro-ui' |
||||
import styles from './index.module.scss' |
||||
import Taro from "@tarojs/taro"; |
||||
import { useReady, useDidShow } from '@tarojs/taro' |
||||
import SearchList from './components/list' |
||||
|
||||
|
||||
const Search:FC = () => { |
||||
const [value, setValue] = useState('') |
||||
const [recentSearch, setRecentSearch] = useState<string[]>([]) |
||||
const [hotSearch,setHotSearch] = useState<any[]>([]) |
||||
const [show, setShow] = useState(false) |
||||
|
||||
useReady(()=>{ |
||||
console.log('onReady') |
||||
}) |
||||
useDidShow(()=>{ |
||||
getRecentSearch() |
||||
}) |
||||
|
||||
useEffect(()=>{ |
||||
if(!show){ |
||||
getRecentSearch() |
||||
} |
||||
},[show]) |
||||
|
||||
function inpFn(e){ |
||||
setValue(e.detail.value) |
||||
} |
||||
|
||||
function clearSearch(){ |
||||
Taro.removeStorageSync('recentSearch') |
||||
getRecentSearch() |
||||
} |
||||
|
||||
function getRecentSearch () { |
||||
setRecentSearch(Taro.getStorageSync('recentSearch')) |
||||
} |
||||
function getSearchItem(value) { |
||||
setValue(value) |
||||
// Taro.navigateTo({
|
||||
// url:`/pages/preview/search/list/index?name=${value}`
|
||||
// })
|
||||
setShow(true) |
||||
} |
||||
|
||||
function searchInput(){ |
||||
if (value === "") return; |
||||
getSearchItem(value) |
||||
//记录最近搜索
|
||||
let recentSearch = Taro.getStorageSync('recentSearch') || []; |
||||
recentSearch.unshift(value); |
||||
Taro.setStorageSync('recentSearch', [...new Set(recentSearch)]) |
||||
} |
||||
|
||||
|
||||
|
||||
return ( |
||||
<View className="flex flex-column"> |
||||
<View className={styles.searchBox}> |
||||
<AtIcon value='search' size='20' color='#ccc'></AtIcon> |
||||
{ show ? |
||||
<Text className={styles.input} >{value}</Text>: |
||||
<Input className={styles.input} placeholder="输入关键字搜索" type={'text'} confirmType={'search'} onInput={inpFn} onConfirm={searchInput} /> |
||||
} |
||||
</View> |
||||
|
||||
{ |
||||
recentSearch.length >= 1 && !show && |
||||
<> |
||||
<View className={styles.titleBox} > |
||||
<Text className={styles.title}>最近搜索</Text> |
||||
<AtIcon onClick={clearSearch} value='trash' size='16' color='#909795'></AtIcon> |
||||
</View> |
||||
<View className={styles.contentBox}> |
||||
{ |
||||
recentSearch.length && |
||||
recentSearch?.map(d => |
||||
<View className={styles.items}> |
||||
<View onClick={()=>{getSearchItem(d)}} className="font-28" >{d}</View> |
||||
</View>) |
||||
} |
||||
</View> |
||||
</> |
||||
} |
||||
{ |
||||
hotSearch.length >= 1 && !show && |
||||
<> |
||||
<View className={`flex justify-between ${styles.titleBox}`} > |
||||
|
||||
<Text className="font-32 fwb">热门搜索</Text> |
||||
</View> |
||||
<View className="wid100 pt-3 pl-3 box-b"> |
||||
<View className="wid100 flex flex-wrap"> |
||||
{ |
||||
hotSearch.length && |
||||
hotSearch.map(d => |
||||
<View className="py-1 px-2 rounded-40 mr-2 mb-2 bg-warning text-white"> |
||||
<View onClick={()=>{getSearchItem(d)}} className="font-28" >{d}</View> |
||||
</View>) |
||||
} |
||||
</View> |
||||
</View> |
||||
</> |
||||
} |
||||
|
||||
<PageContainer onClickOverlay={()=>{ setShow(false)}} show={show} round={true} overlay={true} overlayStyle={'background:rgba(0,0,0,0)'} > |
||||
<SearchList name={value} clear={show} /> |
||||
</PageContainer> |
||||
|
||||
</View> |
||||
) |
||||
} |
||||
export default Search |
@ -0,0 +1,4 @@ |
||||
export default definePageConfig({ |
||||
navigationBarTitleText: '链接', |
||||
onReachBottomDistance: 30 |
||||
}) |
@ -0,0 +1,9 @@ |
||||
import {FC} from "react"; |
||||
import {WebView} from "@tarojs/components"; |
||||
import {useRouter} from "@tarojs/taro"; |
||||
|
||||
const Web:FC = () => { |
||||
const {url} = useRouter().params as unknown as { url: string } |
||||
return <WebView src={url}></WebView> |
||||
} |
||||
export default Web |
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue