微信公众号对接 && 弹窗 && 按钮 && 详情立即学习

main
king 1 year ago
parent 7f4092cc8a
commit 8e34ddb5af
  1. 6
      config/index.js
  2. 2
      pnpm-lock.yaml
  3. 2
      project.config.json
  4. 2
      project.tt.json
  5. 12
      src/api/login.ts
  6. 49
      src/api/request.ts
  7. 16
      src/api/user.ts
  8. 3
      src/app.config.ts
  9. 12
      src/app.tsx
  10. 40
      src/components/button/MyButton.tsx
  11. 10
      src/components/button/myButton.module.scss
  12. 27
      src/components/custom-page-container/custom-page-container.module.scss
  13. 59
      src/components/custom-page-container/custom-page-container.tsx
  14. 14
      src/components/empty/empty.module.scss
  15. 4
      src/components/empty/empty.tsx
  16. 8
      src/components/popPut/popPut.tsx
  17. 3
      src/components/tabs/tabs.scss
  18. 1
      src/components/tabs/tabs.tsx
  19. 2
      src/components/topic/judge.tsx
  20. 2
      src/components/topic/multi.tsx
  21. 2
      src/components/topic/shortAnswer.tsx
  22. 8
      src/components/video/video.tsx
  23. 44
      src/components/videoCover/videoCover.scss
  24. 6
      src/components/videoCover/videoCover.tsx
  25. 2
      src/index.html
  26. 9
      src/pages/business/curHistory/curHistory.tsx
  27. 4
      src/pages/business/history/history.tsx
  28. 59
      src/pages/business/videoInfo/components/catalogue.tsx
  29. 16
      src/pages/business/videoInfo/components/course.tsx
  30. 5
      src/pages/business/videoInfo/components/hours.tsx
  31. 38
      src/pages/business/videoInfo/videoInfo.scss
  32. 27
      src/pages/business/videoInfo/videoInfo.tsx
  33. 4
      src/pages/check/check.config.ts
  34. 68
      src/pages/check/check.module.scss
  35. 90
      src/pages/check/check.tsx
  36. 8
      src/pages/index/components/videoList.tsx
  37. 1
      src/pages/index/index.config.ts
  38. 19
      src/pages/index/index.tsx
  39. 1
      src/pages/login/login.config.ts
  40. 10
      src/pages/login/login.module.scss
  41. 89
      src/pages/login/login.tsx
  42. 8
      src/pages/manage/addStudent/addStudent.tsx
  43. 2
      src/pages/manage/bingUser/bingUser.config.ts
  44. 104
      src/pages/manage/bingUser/bingUser.tsx
  45. 7
      src/pages/manage/depAdmin/depAdmin.tsx
  46. 111
      src/pages/manage/offline/offline.tsx
  47. 2
      src/pages/my/components/header/header.tsx
  48. 4
      src/pages/my/components/header/service.tsx
  49. 4
      src/pages/my/my.module.scss
  50. 12
      src/static/css/module.scss
  51. 2
      src/store/profile.ts

@ -30,9 +30,6 @@ const config = {
postcss: { postcss: {
pxtransform: { pxtransform: {
enable: true, enable: true,
config: {
selectorBlackList: ['nut-']
}
}, },
url: { url: {
enable: true, enable: true,
@ -58,9 +55,6 @@ const config = {
postcss: { postcss: {
pxtransform: { pxtransform: {
enable: true, enable: true,
config: {
selectorBlackList: ['nut-']
}
}, },
autoprefixer: { autoprefixer: {
enable: true, enable: true,

@ -8001,7 +8001,7 @@ packages:
/history@5.3.0: /history@5.3.0:
resolution: {integrity: sha512-ZqaKwjjrAYUYfLG+htGaIIZ4nioX2L70ZUMIFysS3xvBsSG4x/n1V6TXV3N8ZYNuFGlDirFg32T7B6WOUPDYcQ==} resolution: {integrity: sha512-ZqaKwjjrAYUYfLG+htGaIIZ4nioX2L70ZUMIFysS3xvBsSG4x/n1V6TXV3N8ZYNuFGlDirFg32T7B6WOUPDYcQ==}
dependencies: dependencies:
'@babel/runtime': 7.7.7 '@babel/runtime': 7.22.5
/hls.js@1.4.7: /hls.js@1.4.7:
resolution: {integrity: sha512-dvwJXLlYES6wb7DR42uuTrio5sUTsIoWbuNeQS4xHMqfVBZ0KAlJlBmjFAo4s20/0XRhsMjWf5bx0kq5Lgvv1w==} resolution: {integrity: sha512-dvwJXLlYES6wb7DR42uuTrio5sUTsIoWbuNeQS4xHMqfVBZ0KAlJlBmjFAo4s20/0XRhsMjWf5bx0kq5Lgvv1w==}

@ -2,7 +2,7 @@
"miniprogramRoot": "./dist", "miniprogramRoot": "./dist",
"projectname": "video", "projectname": "video",
"description": "", "description": "",
"appid": "wx703940a70f0f1be7", "appid": "wxd8ae2141559dcf5b",
"setting": { "setting": {
"urlCheck": true, "urlCheck": true,
"es6": false, "es6": false,

@ -2,7 +2,7 @@
"miniprogramRoot": "Progress/", "miniprogramRoot": "Progress/",
"projectname": "video", "projectname": "video",
"description": "", "description": "",
"appid": "wx703940a70f0f1be7", "appid": "wxd8ae2141559dcf5b",
"setting": { "setting": {
"urlCheck": true, "urlCheck": true,
"es6": false, "es6": false,

@ -0,0 +1,12 @@
import {request} from "@/api/request";
export interface LoginParams {
appid: string
route: string
}
export const loginApi = {
getParams() {
return request<LoginParams>("/api/v1/auth/login/app", "GET");
}
}

@ -44,11 +44,11 @@ export const ERROR_STATUS: Record<number | string, string> = {
'OVERSTEP': '请求越界~' 'OVERSTEP': '请求越界~'
} }
const whitelist = [ // const whitelist = [
'/api/v1/auth/login/code', // '/api/v1/auth/login/code',
'/api/v1/auth/login/checkout', // '/api/v1/auth/login/checkout',
'/api/v1/auth/login/wechat' // '/api/v1/auth/login/wechat'
] // ]
export function request<T = unknown>( export function request<T = unknown>(
url: string, url: string,
@ -68,16 +68,17 @@ export function request<T = unknown>(
if (token) { if (token) {
option.header ??= {} option.header ??= {}
option.header['Authorization'] = `Bearer ${token}` option.header['Authorization'] = `Bearer ${token}`
} else {
/** 登录页面白名单 */
if (Taro.getCurrentInstance().router?.path === '/pages/login/login') {
if (!whitelist.includes(url)) {
return new Promise((_, reject) => reject())
}
} else {
return new Promise((_, reject) => reject())
}
} }
// else {
/** 登录页面白名单 */
// if (Taro.getCurrentInstance().router?.path === '/pages/login/login') {
// if (!whitelist.includes(url)) {
// return new Promise((_, reject) => reject())
// }
// } else {
// return new Promise((_, reject) => reject())
// }
// }
if (method === 'GET' && data) { if (method === 'GET' && data) {
let parameter = '' let parameter = ''
Object.entries(data).forEach(([key, value], index) => { Object.entries(data).forEach(([key, value], index) => {
@ -88,7 +89,6 @@ export function request<T = unknown>(
data && (option.data = data) data && (option.data = data)
return new Promise<T>((resolve, reject) => { return new Promise<T>((resolve, reject) => {
Taro.request<T>({ Taro.request<T>({
...option, ...option,
success(res) { success(res) {
@ -97,23 +97,24 @@ export function request<T = unknown>(
if (data?.code === 0 && res.statusCode === 200) { if (data?.code === 0 && res.statusCode === 200) {
resolve(data.data || []) resolve(data.data || [])
} else if (res.statusCode === 401) { } else if (res.statusCode === 401) {
Taro.showModal({ // Taro.showModal({
title: "登录过期,需重新登陆", // title: "登录过期,需重新登陆",
showCancel: false, // showCancel: false,
success({confirm}) { // success({confirm}) {
confirm && Taro.reLaunch({url: '/pages/login/login'}) // confirm && Taro.reLaunch({url: '/pages/login/login'})
} // }
}) // })
} else { } else {
Taro.showToast({title: data.msg || ERROR_STATUS[res.statusCode] || '请求错误~', icon: 'error'})
reject(null) reject(null)
Taro.showToast({title: data.msg || data.message || ERROR_STATUS[res.statusCode] || '请求错误~', icon: 'error'})
} }
} catch (e) { } catch (e) {
reject(null) reject(null)
} }
}, },
fail(err) { fail(err) {
const errMsg = err.errMsg const errMsg = err?.errMsg
console.log(err)
Taro.showToast({title: ERROR_STATUS[errMsg] || ERROR_STATUS['DEFAULT'], icon: 'error'}) Taro.showToast({title: ERROR_STATUS[errMsg] || ERROR_STATUS['DEFAULT'], icon: 'error'})
reject(null) reject(null)
} }

@ -4,7 +4,7 @@ interface Code {
image: string image: string
} }
interface Login { export interface LoginData {
access_token: string access_token: string
code?: Code code?: Code
company: Company company: Company
@ -40,15 +40,15 @@ interface CourseRecord {
data: Record<number, userRecord> data: Record<number, userRecord>
} }
interface HourCourse{ interface HourCourse {
courseHour:Record<number, Hour>, courseHour: Record<number, Hour>,
date:Record<number, { start:number,end:number} > date: Record<number, { start: number, end: number }>
duration:Record<number, number> duration: Record<number, number>
} }
export const userApi = { export const userApi = {
login(code: string) { login(code: string) {
return request<Login>('/api/v1/auth/login/wechat', 'POST', {code}) return request<LoginData>('/api/v1/auth/login/wechat', 'POST', {code})
}, },
checkout(data: CheckoutBody) { checkout(data: CheckoutBody) {
return request<CheckoutData>('/api/v1/auth/login/checkout', 'POST', data) return request<CheckoutData>('/api/v1/auth/login/checkout', 'POST', data)
@ -78,5 +78,9 @@ export const userApi = {
}, },
hourCourse(course_id: number, unique_ident: number) { hourCourse(course_id: number, unique_ident: number) {
return request<HourCourse>(`/api/v1/course/${course_id}/info/${unique_ident}`, "GET") return request<HourCourse>(`/api/v1/course/${course_id}/info/${unique_ident}`, "GET")
},
meetingSave(data: any) {
return request<LoginData>(`/api/v1/user/meeting/save`, "POST", data)
} }
} }

@ -1,7 +1,8 @@
export default defineAppConfig({ export default defineAppConfig({
pages: [ pages: [
'pages/index/index',
'pages/login/login', 'pages/login/login',
'pages/check/check',
'pages/index/index',
'pages/my/my' 'pages/my/my'
], ],
window: { window: {

@ -4,7 +4,7 @@ import {CustomWrapper} from "@tarojs/components";
import unique_ident from "@/hooks/unique_ident"; import unique_ident from "@/hooks/unique_ident";
function updateApp() { function updateApp() {
if (Taro.canIUse('getUpdateManager')) { if (Taro.canIUse('getUpdateManager.onCheckForUpdate')) {
const updateManager = Taro.getUpdateManager() const updateManager = Taro.getUpdateManager()
updateManager.onCheckForUpdate((res) => { updateManager.onCheckForUpdate((res) => {
console.log('新版本', res.hasUpdate) console.log('新版本', res.hasUpdate)
@ -35,10 +35,10 @@ function App(props) {
Taro.useLaunch(() => { Taro.useLaunch(() => {
updateApp() updateApp()
const token = JSON.parse(Taro.getStorageSync('profile') || '{}')?.token // const token = JSON.parse(Taro.getStorageSync('profile') || '{}')?.token
if (!token) { // if (!token) {
Taro.reLaunch({url: '/pages/login/login'}) // Taro.reLaunch({url: '/pages/login/login'})
} // }
unique_ident.put() unique_ident.put()
unique_ident.del() unique_ident.del()
}) })
@ -47,7 +47,7 @@ function App(props) {
Taro.getSystemInfo({ Taro.getSystemInfo({
success(res) { success(res) {
Taro.getApp().globalData = { Taro.getApp().globalData = {
statusBarHeight: res.statusBarHeight, statusBarHeight: res.statusBarHeight || 0,
screenWidth: res.screenWidth, screenWidth: res.screenWidth,
screenHeight: res.screenHeight, screenHeight: res.screenHeight,
safeArea: res.safeArea, safeArea: res.safeArea,

@ -0,0 +1,40 @@
import {CSSProperties, FC} from "react";
import {Button} from "@tarojs/components";
import styles from './myButton.module.scss'
import {ButtonProps} from "@tarojs/components/types/Button";
interface Props extends ButtonProps {
fillet?: boolean
loading?: boolean
}
const MyButton: FC<Props> = (props) => {
const buttonStyle = (): CSSProperties => {
const style: CSSProperties = {}
switch (props.type) {
case 'default':
style.background = '#f5f8f7'
break
case 'warn':
style.background = '#c94f4f'
break
}
if (props.fillet) {
style.borderRadius = '100px'
}
return style
}
return (
<Button
{...props}
style={buttonStyle()}
className={styles.Mybutton}>
{props.children}
</Button>
)
}
export default MyButton

@ -0,0 +1,10 @@
.Mybutton {
line-height: 76rpx;
background: #45D4A8;
border-radius: 10rpx;
color: #fff;
font-size: 32rpx;
border: none !important;
outline: none !important;
position: sticky;
}

@ -0,0 +1,27 @@
.customPageContainer {
position: fixed;
top: 0;
right: 0;
left: 0;
bottom: 0;
display: flex;
align-items: center;
justify-content: center;
}
.overlay {
background: rgba(#000, .5);
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
}
.content {
width: 100%;
background: #fff;
position: absolute;
padding: 0 30px env(safe-area-inset-bottom);
box-sizing: border-box;
}

@ -0,0 +1,59 @@
import {PageContainer, PageContainerProps, View} from "@tarojs/components";
import {CSSProperties, FC, useEffect, useState} from "react";
import styles from './custom-page-container.module.scss'
const PageContainerInner: FC<PageContainerProps> = (props) => {
const [visible, setVisible] = useState(props.show)
useEffect(() => {
if (props.show != visible) {
setVisible(props.show);
// todo 目前仅仅支持 after
if (!props.show) {
(props.onBeforeLeave as Function)?.();
(props.onAfterLeave as Function)?.();
}
}
}, [props.show])
if (!props.show) {
return null
}
const contentStyle = (): CSSProperties => {
let style: CSSProperties = {}
switch (props.position) {
case "top":
style.borderRadius = '0 0 30px 30px'
style.top = 0
break
case 'bottom':
style.borderRadius = '30px 30px 0 0'
style.bottom = 0
break
case 'center':
style.borderRadius = '30px'
break
}
return style
}
return (
<View className={styles.customPageContainer}>
<View className={styles.overlay} onClick={props.onClickOverlay}></View>
<View className={styles.content} style={contentStyle()}>{props.children}</View>
</View>
)
}
const CustomPageContainer: FC<PageContainerProps> = (props) => {
if (process.env.TARO_ENV !== 'h5') {
return (<PageContainer {...props} />)
}
return (<PageContainerInner {...props} />);
}
export default CustomPageContainer

@ -3,19 +3,19 @@
text-align: center; text-align: center;
color: #6c757d; color: #6c757d;
position: relative; position: relative;
}
Image { .image {
display: block; display: block;
margin: auto; margin: auto;
width: 400px; width: 600rpx;
margin-bottom: 10px; }
}
View { .name {
font-size: 30rpx;
position: absolute; position: absolute;
left: 0; left: 0;
right: 0; right: 0;
bottom: 40px; bottom: 50rpx;
margin: auto; margin: auto;
}
} }

@ -10,8 +10,8 @@ interface Props {
const Empty: FC<Props> = ({name}) => { const Empty: FC<Props> = ({name}) => {
return ( return (
<View className={styles.empty}> <View className={styles.empty}>
<Image src={emptyImg} mode='widthFix'/> <Image src={emptyImg} mode='widthFix' className={styles.image}/>
<View>{name}</View> <View className={styles.name}>{name}</View>
</View> </View>
) )
} }

@ -1,6 +1,7 @@
import {FC, ReactNode, useEffect, useState} from "react"; import {FC, ReactNode, useEffect, useState} from "react";
import {View, Image, Text, PageContainer} from "@tarojs/components"; import {View, Image, Text} from "@tarojs/components";
import Icon from "@/components/icon"; import Icon from "@/components/icon";
import CustomPageContainer from "@/components/custom-page-container/custom-page-container";
interface Props { interface Props {
@ -57,11 +58,10 @@ const PopPut: FC<Props> = ({title, chevron, content, image, isProp, children, sh
</View> </View>
{ {
isProp isProp
&& <PageContainer show={PageShow} position='bottom' round onBeforeLeave={() => setShow(false)}> && <CustomPageContainer show={PageShow} position='bottom' round onBeforeLeave={() => setShow(false)}>
{children} {children}
</PageContainer> </CustomPageContainer>
} }
</> </>
) )
} }

@ -6,6 +6,7 @@ View::-webkit-scrollbar {
width: 100%; width: 100%;
height: 100%; height: 100%;
overflow: hidden; overflow: hidden;
font-size: 30rpx;
.tabs-scroll { .tabs-scroll {
@ -38,7 +39,7 @@ View::-webkit-scrollbar {
} }
.tabs-item { .tabs-item {
padding: 25rpx; padding: 20rpx;
} }
} }

@ -51,6 +51,7 @@ const Tabs: FC<TabsProps> = (opt: TabsProps) => {
<ScrollView scrollX scrollLeft={left} scrollWithAnimation showScrollbar={false}> <ScrollView scrollX scrollLeft={left} scrollWithAnimation showScrollbar={false}>
<View className={'tabs-scroll ' + (opt.tabList.length < 5 ? 'justify-around' : '')}> <View className={'tabs-scroll ' + (opt.tabList.length < 5 ? 'justify-around' : '')}>
{opt.tabList.map((d, index) => <View {opt.tabList.map((d, index) => <View
key={index}
className={'tabs-item ' + (is_current(d.value, index) ? 'current' : null)} className={'tabs-item ' + (is_current(d.value, index) ? 'current' : null)}
onClick={(event) => onChange(event, index, d)}> onClick={(event) => onChange(event, index, d)}>
{d.title} {d.title}

@ -9,7 +9,7 @@ interface Props {
index: number index: number
validate: boolean validate: boolean
frequency?: number frequency?: number
end: boolean end?: boolean
} }
const Judge: FC<Props> = ({data, onAnswer, onUpAndDown, index, validate, frequency, end}) => { const Judge: FC<Props> = ({data, onAnswer, onUpAndDown, index, validate, frequency, end}) => {

@ -9,7 +9,7 @@ interface Props {
index: number index: number
validate: boolean validate: boolean
frequency?: number frequency?: number
end: boolean end?: boolean
} }
const Multi: FC<Props> = ({data, onAnswer, onUpAndDown, index, validate, frequency, end}) => { const Multi: FC<Props> = ({data, onAnswer, onUpAndDown, index, validate, frequency, end}) => {

@ -10,7 +10,7 @@ interface Props {
index: number index: number
validate: boolean validate: boolean
frequency?: number frequency?: number
end:boolean end?:boolean
} }
const ShortAnswer: FC<Props> = ({data, onAnswer, onUpAndDown, index, validate, frequency,end}) => { const ShortAnswer: FC<Props> = ({data, onAnswer, onUpAndDown, index, validate, frequency,end}) => {

@ -3,18 +3,15 @@ import {HVideoOptions} from "@/components/video/type";
import Taro from "@tarojs/taro"; import Taro from "@tarojs/taro";
import {FC, useState} from "react"; import {FC, useState} from "react";
import unique_ident from "@/hooks/unique_ident"; import unique_ident from "@/hooks/unique_ident";
// import {Profile} from '@/store'
const deviation: number = 0.5 const deviation: number = 1
const HVideo: FC<HVideoOptions> = (opt: HVideoOptions) => { const HVideo: FC<HVideoOptions> = (opt: HVideoOptions) => {
// const {user} = Profile.useContainer()
const video = Taro.createVideoContext('myVideo') const video = Taro.createVideoContext('myVideo')
const [currentTime, setCurrentTime] = useState(0) const [currentTime, setCurrentTime] = useState(0)
function onTimeUpdate(event: BaseEventOrig<VideoProps.onTimeUpdateEventDetail>) { function onTimeUpdate(event: BaseEventOrig<VideoProps.onTimeUpdateEventDetail>) {
// if (opt.preview) return; // if (opt.preview) return;
// if (user?.role_type === 2) return;
const time = event.detail.currentTime const time = event.detail.currentTime
/** 前进回退 */ /** 前进回退 */
if (currentTime + deviation < time) { if (currentTime + deviation < time) {
@ -42,7 +39,6 @@ const HVideo: FC<HVideoOptions> = (opt: HVideoOptions) => {
function onEnded() { function onEnded() {
// if (opt.preview) return; // if (opt.preview) return;
// if (user?.role_type === 2) return;
if (currentTime + 1 > opt.duration) { if (currentTime + 1 > opt.duration) {
opt.onEnded() opt.onEnded()
} else { } else {
@ -60,7 +56,7 @@ const HVideo: FC<HVideoOptions> = (opt: HVideoOptions) => {
<Video <Video
id={'myVideo'} id={'myVideo'}
autoplay autoplay
style={{width: '100%', height: '100%'}} style={{width: '100%', height: '100%',position:"relative"}}
poster={opt?.poster || ''} poster={opt?.poster || ''}
src={opt.src} src={opt.src}
enableProgressGesture={false} enableProgressGesture={false}

@ -10,13 +10,17 @@
background: #fff; background: #fff;
border-radius: 10rpx; border-radius: 10rpx;
overflow: hidden; overflow: hidden;
.upper {
overflow: hidden;
position: relative; position: relative;
height: 180rpx;
.content { .content {
position: absolute; position: absolute;
color: #fff; color: #fff;
left: 0; left: 0;
top: 172rpx; bottom: 0;
width: 100%; width: 100%;
line-height: 48rpx; line-height: 48rpx;
font-size: 24rpx; font-size: 24rpx;
@ -27,20 +31,6 @@
text-align: center; text-align: center;
} }
.box {
padding: 20rpx;
.title{
width: 100%;
font-size: 28rpx;
word-break: break-all;
overflow: hidden;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
}
}
.marker { .marker {
position: absolute; position: absolute;
background: rgba(#000, .5); background: rgba(#000, .5);
@ -53,12 +43,28 @@
Image { Image {
width: 100%; width: 100%;
height: 220rpx; height: 100px;
display: block; }
}
.box {
box-sizing: border-box;
padding: 15rpx;
.title {
width: 100%;
font-size: 28rpx;
word-break: break-all;
overflow: hidden;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
}
} }
.videoButton{ .videoButton {
margin-top: 20rpx; margin-top: 10rpx;
color: #909795; color: #909795;
font-size: 22rpx; font-size: 22rpx;
} }

@ -26,9 +26,9 @@ const VideoCover: FC<VideoCoverProps> = (opt: VideoCoverProps) => {
return ( return (
<View className='videoBox'> <View className='videoBox'>
<View className='video'> <View className='video' onClick={jump}>
<View onClick={jump}> <View className='upper'>
<Image src={opt.thumb} mode='scaleToFill'/> <Image src={opt.thumb} mode='aspectFit'/>
{opt.content && <View className='content'>{opt.content}</View>} {opt.content && <View className='content'>{opt.content}</View>}
{opt.marker && <View className='marker'>{opt.marker}</View>} {opt.marker && <View className='marker'>{opt.marker}</View>}
</View> </View>

@ -8,7 +8,7 @@
<meta name="format-detection" content="telephone=no,address=no"> <meta name="format-detection" content="telephone=no,address=no">
<meta name="apple-mobile-web-app-status-bar-style" content="white"> <meta name="apple-mobile-web-app-status-bar-style" content="white">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" > <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" >
<title>video</title> <title>医学道</title>
<script><%= htmlWebpackPlugin.options.script %></script> <script><%= htmlWebpackPlugin.options.script %></script>
</head> </head>
<body> <body>

@ -1,9 +1,10 @@
import {Image, PageContainer, Progress, View} from "@tarojs/components"; import {Image, Progress, View} from "@tarojs/components";
import Taro from "@tarojs/taro"; import Taro from "@tarojs/taro";
import styles from './curHistory.module.scss' import styles from './curHistory.module.scss'
import {useState} from "react"; import {useState} from "react";
import {userApi} from "@/api"; import {userApi} from "@/api";
import {formatDateTime, formatMinute} from "@/utils/time"; import {formatDateTime, formatMinute} from "@/utils/time";
import CustomPageContainer from "@/components/custom-page-container/custom-page-container";
const CurHistory = () => { const CurHistory = () => {
const [show, setShow] = useState(false) const [show, setShow] = useState(false)
@ -56,7 +57,7 @@ const CurHistory = () => {
<View className={styles.record}> <View className={styles.record}>
{data.map(d => <View onClick={() => setHour(d.unique_ident)} className={styles.recordItem}> {data.map(d => <View key={d.id} onClick={() => setHour(d.unique_ident)} className={styles.recordItem}>
<View>{formatDateTime(new Date(d.start_at), "MM/dd HH:ss")} - {formatDateTime(new Date(d.end_at), "MM/dd HH:ss")}</View> <View>{formatDateTime(new Date(d.start_at), "MM/dd HH:ss")} - {formatDateTime(new Date(d.end_at), "MM/dd HH:ss")}</View>
<View>{formatMinute(d.duration)}</View> <View>{formatMinute(d.duration)}</View>
<Progress <Progress
@ -71,7 +72,7 @@ const CurHistory = () => {
</View>)} </View>)}
</View> </View>
<PageContainer <CustomPageContainer
show={show} show={show}
round round
onAfterLeave={() => setShow(false)}> onAfterLeave={() => setShow(false)}>
@ -96,7 +97,7 @@ const CurHistory = () => {
) )
} }
</View>} </View>}
</PageContainer> </CustomPageContainer>
</View> </View>
) )
} }

@ -1,6 +1,5 @@
import {Image, View} from "@tarojs/components"; import {Image, View} from "@tarojs/components";
import CategoryTabs from "@/pages/business/history/components/CategoryTabs"; import CategoryTabs from "@/pages/business/history/components/CategoryTabs";
// import {useState} from "react";
import styles from './history.module.scss' import styles from './history.module.scss'
import Taro from "@tarojs/taro"; import Taro from "@tarojs/taro";
import {userApi} from "@/api"; import {userApi} from "@/api";
@ -26,8 +25,7 @@ const History = () => {
<CategoryTabs changeTabs={getData}/> <CategoryTabs changeTabs={getData}/>
<View className='mt-3'> <View className='mt-3'>
{data.length ? data.map(d => <View className={styles.category} {data.length ? data.map((d, index) => <View key={index} className={styles.category} onClick={() => jump(d.course_id, d.course.title)}>
onClick={() => jump(d.course_id, d.course.title)}>
<View className={styles.thumb}> <View className={styles.thumb}>
<Image src={d.course.thumb}/> <Image src={d.course.thumb}/>
<View>{d.total_hour_count}/{d.finished_count}</View> <View>{d.total_hour_count}/{d.finished_count}</View>

@ -1,10 +1,14 @@
import {FC, useState} from "react"; import {FC, useState} from "react";
import Tabs, {OnChangOpt} from "@/components/tabs/tabs"; import Tabs, {OnChangOpt} from "@/components/tabs/tabs";
import {Button, View} from "@tarojs/components"; import {Image, View} from "@tarojs/components";
import {CourseDepData} from "@/api"; import {CourseDepData} from "@/api";
import Collapse from "@/components/collapse/collapse"; import Collapse from "@/components/collapse/collapse";
import Hours from "@/pages/business/videoInfo/components/hours"; import Hours from "@/pages/business/videoInfo/components/hours";
import Taro from "@tarojs/taro"; import Taro from "@tarojs/taro";
import CustomPageContainer from "@/components/custom-page-container/custom-page-container";
import MyButton from "@/components/button/MyButton";
import curRecord from '@/static/img/curRecord.png'
import hourRecord from '@/static/img/hourRecord.png'
interface Props { interface Props {
data: CourseDepData | null data: CourseDepData | null
@ -21,6 +25,7 @@ const tabList = [
const Catalogue: FC<Props> = ({data, setHors, id}) => { const Catalogue: FC<Props> = ({data, setHors, id}) => {
const [current, setCurrent] = useState(1) const [current, setCurrent] = useState(1)
const [show, setShow] = useState(false)
function jumCurHistory() { function jumCurHistory() {
Taro.preload({course_id: id, name: data?.course.title}) Taro.preload({course_id: id, name: data?.course.title})
@ -67,6 +72,29 @@ const Catalogue: FC<Props> = ({data, setHors, id}) => {
} }
} }
function learning() {
const flats: Hour[] = Object.values(data?.hours || {}).flat(Infinity) as Hour[]
if (data?.learn_record?.finished_count === data?.learn_record?.hour_count && flats.length) {
setHors(true, flats[0].id)
return
}
if (data?.learn_hour_records.length) {
const lastTimeId = data.learn_hour_records[data.learn_hour_records.length - 1].id
for (const [index, flat] of flats.entries()) {
if (flat.id === lastTimeId) {
const next = flats[index + 1]
if (next) {
setHors(true, lastTimeId)
}
}
}
} else {
Taro.showToast({title: "无播放视频", icon: 'error'})
}
}
return ( return (
<> <>
<View className='catalogue'> <View className='catalogue'>
@ -74,7 +102,7 @@ const Catalogue: FC<Props> = ({data, setHors, id}) => {
<View className='py-2 hours'> <View className='py-2 hours'>
{current === 0 && <View className='short_desc'>{data?.course.short_desc || '无'}</View>} {current === 0 && <View className='short_desc'>{data?.course.short_desc || '无'}</View>}
{current === 1 && <View> {current === 1 && <View>
<View className='font-weight mb-2'></View> <View className='font-weight'></View>
{data?.chapters.length ? Object.values(data?.chapters || {}).map((d, index) => <View> {data?.chapters.length ? Object.values(data?.chapters || {}).map((d, index) => <View>
<Collapse title={`${index + 1}.${d.name}`}> <Collapse title={`${index + 1}.${d.name}`}>
<Hours <Hours
@ -91,10 +119,33 @@ const Catalogue: FC<Props> = ({data, setHors, id}) => {
</View> </View>
<View className='Videobutton'> <View className='Videobutton'>
<Button className='button' onClick={jumCurHistory}></Button> <MyButton onClick={learning}></MyButton>
<View className='px-3' onClick={() => setShow(true)}>...</View>
</View>
<CustomPageContainer
show={show}
position='bottom'
onClickOverlay={() => setShow(false)}>
<View className='more'>
<View></View>
<View className='flex justify-around'>
<View onClick={() => Taro.navigateTo({url: '/pages/business/history/history'})}>
<Image src={curRecord} className='image'/>
</View>
<View onClick={jumCurHistory}>
<Image src={hourRecord} className='image'/>
</View>
</View>
<MyButton onClick={() => setShow(false)} type='default' fillet></MyButton>
</View> </View>
</CustomPageContainer>
</> </>
) );
} }
export default Catalogue export default Catalogue

@ -1,4 +1,4 @@
import {Button, PageContainer, ScrollView, Swiper, SwiperItem, View} from "@tarojs/components"; import {Button, ScrollView, Swiper, SwiperItem, View} from "@tarojs/components";
import {FC, useEffect, useState} from "react"; import {FC, useEffect, useState} from "react";
import HVideo from "@/components/video/video"; import HVideo from "@/components/video/video";
import {CurEndParam, curriculum, HourPlayData} from "@/api"; import {CurEndParam, curriculum, HourPlayData} from "@/api";
@ -8,6 +8,7 @@ import Multi from "@/components/topic/multi";
import Judge from "@/components/topic/judge"; import Judge from "@/components/topic/judge";
import ShortAnswer from "@/components/topic/shortAnswer"; import ShortAnswer from "@/components/topic/shortAnswer";
import unique_ident from "@/hooks/unique_ident"; import unique_ident from "@/hooks/unique_ident";
import CustomPageContainer from "@/components/custom-page-container/custom-page-container";
interface Props { interface Props {
id: number, id: number,
@ -40,17 +41,10 @@ const Course: FC<Props> = ({id, courseId, preview, curEnd}: Props) => {
startRecording && curriculum.curEnd(courseId, id, {...startRecording, duration: data?.duration!}) //结束 startRecording && curriculum.curEnd(courseId, id, {...startRecording, duration: data?.duration!}) //结束
if (testId) { if (testId) {
Taro.navigateTo({ Taro.navigateTo({url: `/pages/business/test/test?testId=${testId}`})
url: `/pages/business/test/test?testId=${testId}`
})
} else { } else {
Taro.showModal({
title: "学习完成",
success() {
curEnd() curEnd()
} }
})
}
} }
/** 进入断点 */ /** 进入断点 */
@ -186,7 +180,7 @@ const Course: FC<Props> = ({id, courseId, preview, curEnd}: Props) => {
/> />
<view> <view>
<PageContainer <CustomPageContainer
show={show} show={show}
position='bottom' position='bottom'
round round
@ -244,7 +238,7 @@ const Course: FC<Props> = ({id, courseId, preview, curEnd}: Props) => {
{frequency > 0 && validate && <Button className='button' onClick={onceMore}></Button>} {frequency > 0 && validate && <Button className='button' onClick={onceMore}></Button>}
{frequency === 0 && validate && <Button className='button' onClick={again}></Button>} {frequency === 0 && validate && <Button className='button' onClick={again}></Button>}
</View> </View>
</PageContainer> </CustomPageContainer>
</view> </view>
</> </>

@ -57,7 +57,7 @@ const Hours: FC<Props> = ({data, click, learn_hour_records}) => {
return return
} }
console.log(id)
click(is_complete !== undefined, id) click(is_complete !== undefined, id)
} }
@ -66,8 +66,9 @@ const Hours: FC<Props> = ({data, click, learn_hour_records}) => {
{ {
data?.map((d, index) => data?.map((d, index) =>
<View className={'hor' + ` ${complete(d.id) ? 'complete' : null}`} <View className={'hor' + ` ${complete(d.id) ? 'complete' : null}`}
key={index}
onClick={() => onClick(d.id, complete(d.id), d, data?.[index - 1]?.id,)}> onClick={() => onClick(d.id, complete(d.id), d, data?.[index - 1]?.id,)}>
<Image src={complete(d.id) ? playOk : play} mode='aspectFit'/> <Image src={complete(d.id) ? playOk : play} mode="scaleToFill" className='image'/>
<View> <View>
<View>{index + 1}.{d.title}</View> <View>{index + 1}.{d.title}</View>
<View className='font-26'>{formatMinute(d.duration)}</View> <View className='font-26'>{formatMinute(d.duration)}</View>

@ -1,7 +1,8 @@
.content { .content {
&-video { .content-video {
height: 480rpx; width: 100%;
height: 500rpx;
} }
.image { .image {
@ -15,19 +16,16 @@
border-radius: 0 0 40rpx 40rpx; border-radius: 0 0 40rpx 40rpx;
padding: 30rpx 30rpx; padding: 30rpx 30rpx;
background: #fff; background: #fff;
margin-bottom: 30px;
} }
} }
.catalogue { .catalogue {
background: #fff; background: #fff;
border-radius: 40rpx; border-radius: 40rpx;
padding: 24px; padding: 0 24px 10px 24px;
padding-bottom: env(safe-area-inset-bottom);
margin-top: 20rpx; margin-top: 20rpx;
margin-bottom: env(safe-area-inset-bottom);
.hours {
padding-bottom: 80px;
}
.short_desc { .short_desc {
color: #606563; color: #606563;
@ -40,10 +38,10 @@
padding: 20px 0; padding: 20px 0;
display: flex; display: flex;
Image { .image {
width: 40rpx; width: 40rpx;
height: 40rpx; height: 40rpx;
margin-top: 6px; margin: 10px;
} }
& > View { & > View {
@ -64,11 +62,29 @@
} }
.Videobutton { .Videobutton {
display: flex;
background: #fff; background: #fff;
position: fixed; position: fixed;
width: 100%; width: 100%;
left: 0; left: 0;
bottom: 0; bottom: 0;
padding-bottom: env(safe-area-inset-bottom); box-sizing: border-box;
padding: 10px 20px env(safe-area-inset-bottom);
}
.more {
height: 50vh;
padding: 20px 0;
text-align: center;
display: flex;
flex-direction: column;
justify-content: space-between;
.image {
width: 100px;
height: 100px;
display: block;
margin: auto;
}
} }

@ -1,24 +1,23 @@
import {Image, Text, View} from "@tarojs/components"; import {Image, Text, View} from "@tarojs/components";
import {FC, useState} from "react"; import {FC, useEffect, useState} from "react";
import {CourseDepData, curriculum} from "@/api"; import {CourseDepData, curriculum} from "@/api";
import './videoInfo.scss' import './videoInfo.scss'
import {Profile} from '@/store' import {Profile} from '@/store'
import Catalogue from "./components/catalogue"; import Catalogue from "./components/catalogue";
import Course from "./components/course"; import Course from "./components/course";
import Taro from "@tarojs/taro"; import Taro from "@tarojs/taro";
import eventsIndex from "@/hooks/eventsIndex"; // import eventsIndex from "@/hooks/eventsIndex";
const VideoInfo: FC = () => { const VideoInfo: FC = () => {
const {id, depId} = Taro.getCurrentInstance().preloadData as { id: number, depId: number | null } const {id, depId} = Taro.getCurrentInstance()?.router?.params as any
const [data, setData] = useState<CourseDepData | null>(null) const [data, setData] = useState<CourseDepData | null>(null)
const [playId, setPlayId] = useState<number | null>(null) const [playId, setPlayId] = useState<number | null>(null)
const [preview, setPreview] = useState(false) const [preview, setPreview] = useState(false)
eventsIndex.trigger(12) // eventsIndex.trigger(12)
const getData = async () => {
async function getData() {
const res = await curriculum.courseDep(id, depId) const res = await curriculum.courseDep(id, depId)
if (res) { if (res) {
setData(res) setData(res)
@ -34,14 +33,14 @@ const VideoInfo: FC = () => {
setPlayId(play_id) setPlayId(play_id)
} }
Taro.useLoad(() => { useEffect(() => {
getData() getData()
}) }, [])
/** 播放下一个视频 */ /** 播放下一个视频 */
function playNext() { function playNext() {
const flats: Hour[] = Object.values(data?.hours || {}).flat(Infinity) as Hour[] const flats: Hour[] = Object.values(data?.hours || {}).flat(Infinity) as Hour[]
if (playId === flats.at(-1)?.id) { if (playId === flats?.[flats.length - 1]?.id) {
Taro.showModal({title: '当前课程结束'}) Taro.showModal({title: '当前课程结束'})
return; return;
} }
@ -50,7 +49,7 @@ const VideoInfo: FC = () => {
const next = flats[index + 1] const next = flats[index + 1]
if (next) { if (next) {
Taro.showModal({ Taro.showModal({
title: '是否播放下一个视频', title: '继续播放下个视频',
success({confirm}) { success({confirm}) {
if (confirm) { if (confirm) {
setPlayId(next.id) setPlayId(next.id)
@ -86,9 +85,7 @@ const VideoInfo: FC = () => {
} }
Taro.useDidShow(() => { Taro.useDidShow(() => {
if (data) { data && getData()
getData().then()
}
}) })
@ -106,14 +103,14 @@ const VideoInfo: FC = () => {
<Text className='font-34 text-warning'>{data?.is_required ? '必修' : '选修'}</Text> <Text className='font-34 text-warning'>{data?.is_required ? '必修' : '选修'}</Text>
<Text>{data?.course.class_hour}</Text> <Text>{data?.course.class_hour}</Text>
</View> </View>
<View className='font-weight font-40 my-4'>{data?.course.title}</View> <View className='font-weight font-40 my-3'>{data?.course.title}</View>
<View className='text-muted font-26'> <View className='text-muted font-26'>
<Text>{data?.learn_hour_records.length || 0}/{data?.course.class_hour}</Text> <Text>{data?.learn_hour_records.length || 0}/{data?.course.class_hour}</Text>
</View> </View>
</View> </View>
<Catalogue data={data} setHors={setHors} id={id}/> <Catalogue data={data} setHors={setHors} id={id}/>
</View> </View>
</Profile.Provider> </Profile.Provider>
) )
} }

@ -0,0 +1,4 @@
export default definePageConfig({
navigationStyle: 'custom',
navigationBarTitleText: '验证'
})

@ -0,0 +1,68 @@
.container {
position: relative;
}
.navbar,
.brand {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
.navbar {
position: relative;
line-height: 1;
font-size: 28px;
}
.brand {
width: 140px;
height: 140px;
background: #fff;
border-radius: 20px;
margin: 250px auto 145px;
image {
width: 100px;
height: 100px;
}
}
.loginTips {
margin: 24px;
text-align: center;
}
.submit {
display: flex;
justify-content: center;
align-items: center;
gap: 12px;
margin: 0 auto;
a {
color: #fff;
}
}
.errorTips {
position: fixed;
top: 10%;
left: 24px;
right: 24px;
background: red;
color: white;
padding: 24px;
border-radius: 20px;
display: flex;
align-items: center;
gap: 12px;
}
.bing {
height: 50vh;
padding: 50px 30px 0;
}

@ -0,0 +1,90 @@
import {FC, useRef, useState} from "react";
import {Button, Form, Image, Input, View} from "@tarojs/components";
import {Profile} from "@/store";
import {userApi} from "@/api";
import Taro, {useRouter} from "@tarojs/taro";
import {regexTel} from "@/utils/regu";
import styles from './check.module.scss'
const uuid = () => {
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
const r = Math.random() * 16 | 0
const v = c === 'x' ? r : (r & 0x3 | 0x8)
return v.toString(16)
}).replace(/-/g, '')
}
const Bing: FC = () => {
const form = useRef<HTMLFormElement | null>(null)
const [loading, setLoading] = useState(false)
const {setUser, setToken, setCompany} = Profile.useContainer()
const [captchaKey, setCaptchaKey] = useState(uuid())
const router = useRouter()
async function Submit(data) {
setLoading(true)
const value = data.target.value
if (!regexTel.exec(value.phone_number)) {
Taro.showToast({title: '手机号错误', icon: 'error'})
setLoading(false)
return
}
try {
const res = await userApi.checkout({
...value,
catch_key: captchaKey,
openid: router.params.openid,
})
if (res) {
setCompany(res.company)
setUser(res.user)
setToken(res.token)
Taro.switchTab({url: '/pages/index/index'})
}
} catch (e) {
setCaptchaKey(uuid)
}
setLoading(false)
}
return (
<View className='h-10 pt-6 px-3 bg-white'>
<Form className='form' onSubmit={Submit} ref={form}>
<View className='item'>
<View></View>
<Input name='phone_number' placeholder={'请输入手机号'} value='18708100736'/>
</View>
<View className='item'>
<View></View>
<View className='flex align-center flex-1'>
<Input name='code' className='flex-1' placeholder={'请输入验证码'}/>
<Image className='w-2 ml-1' style='height:28px'
src={process.env.TARO_APP_API + '/api/v1/captcha?key=' + captchaKey}
onClick={() => setCaptchaKey(uuid)}/>
</View>
</View>
<Button
className={'button ' + styles.submit}
style={{margin: '30px auto'}}
formType='submit'
disabled={loading}
>
</Button>
</Form>
</View>
)
}
const Index: FC = () => {
return (
<Profile.Provider>
<Bing/>
</Profile.Provider>
);
}
export default Index;

@ -7,7 +7,6 @@ import styles from '../index.module.scss'
import {formatMinute} from "@/utils/time"; import {formatMinute} from "@/utils/time";
import {userApi} from "@/api"; import {userApi} from "@/api";
import Empty from "@/components/empty/empty"; import Empty from "@/components/empty/empty";
// import eventsIndex from "@/hooks/eventsIndex";
interface Props { interface Props {
categoryKey: CoursesKey categoryKey: CoursesKey
@ -49,14 +48,14 @@ export const VideoList: FC<Props> = ({categoryKey}) => {
case "is_not_finished": case "is_not_finished":
const find = records.find(d => d?.course_id === id) const find = records.find(d => d?.course_id === id)
if (find) { if (find) {
if(class_hour === find.finished_count){ if (class_hour === find.finished_count) {
return <View className='text-danger'></View> return <View></View>
} }
return (<View>{`${class_hour}节/已学${find.finished_count}`}</View>) return (<View>{`${class_hour}节/已学${find.finished_count}`}</View>)
} }
return (<View>{`${class_hour}节/已学0节`}</View>) return (<View>{`${class_hour}节/已学0节`}</View>)
case "is_finished": case "is_finished":
return (<View>{`${class_hour}节/已学${class_hour}`}</View>) return <View></View>
} }
return (<View>sd</View>) return (<View>sd</View>)
} }
@ -88,6 +87,7 @@ export const VideoList: FC<Props> = ({categoryKey}) => {
title={c.title} title={c.title}
id={c.id} id={c.id}
depId={c.id} depId={c.id}
key={c.id}
time={formatMinute(c.course_duration)} time={formatMinute(c.course_duration)}
content={rateOfLearning(c.id, c.class_hour)} content={rateOfLearning(c.id, c.class_hour)}
/>) : <Empty name='暂无数据'/>} />) : <Empty name='暂无数据'/>}

@ -1,4 +1,5 @@
export default definePageConfig({ export default definePageConfig({
navigationStyle: 'custom', navigationStyle: 'custom',
navigationBarTitleText:'医学道',
onReachBottomDistance: 30 onReachBottomDistance: 30
}) })

@ -1,16 +1,17 @@
import {FC, useState} from "react"; import {FC, useEffect, useState} from "react";
import {View} from "@tarojs/components"; import {View} from "@tarojs/components";
import styles from './index.module.scss' import styles from './index.module.scss'
import {Profile} from '@/store' import {Profile} from '@/store'
// import {Search} from "@/pages/index/components/search";
import {VideoList} from "@/pages/index/components/videoList"; import {VideoList} from "@/pages/index/components/videoList";
import Tabs, {OnChangOpt, TabList} from "@/components/tabs/tabs"; import Tabs, {OnChangOpt, TabList} from "@/components/tabs/tabs";
import {CoursesKey} from "@/api/public"; import {CoursesKey} from "@/api/public";
import Taro from '@tarojs/taro' import Taro, {useRouter} from '@tarojs/taro'
// import {Search} from "@/pages/index/components/search";
const Index: FC = () => { const Index: FC = () => {
const globalData = Taro.getApp().globalData const globalData = Taro.getApp().globalData
const router = useRouter();
const category: TabList[] = [ const category: TabList[] = [
{title: "必修", value: 'is_required'}, {title: "必修", value: 'is_required'},
{title: "选修", value: 'is_not_required'}, {title: "选修", value: 'is_not_required'},
@ -19,16 +20,22 @@ const Index: FC = () => {
] ]
const [categoryKey, setCategoryKey] = useState<CoursesKey>('is_required') const [categoryKey, setCategoryKey] = useState<CoursesKey>('is_required')
function tabChange(data: OnChangOpt) { function tabChange(data: OnChangOpt) {
setCategoryKey(data.tab?.value as CoursesKey) setCategoryKey(data.tab?.value as CoursesKey)
} }
useEffect(() => {
if (router.params.d) {
Taro.setStorageSync("profile", decodeURIComponent(router.params.d))
Taro.reLaunch({url:"/pages/index/index"})
}
}, [])
return ( return (
<Profile.Provider> <Profile.Provider>
<View className={styles.content} style={`paddingTop:${globalData.statusBarHeight}px`}> <View className={styles.content} style={`paddingTop:${globalData.statusBarHeight}px`}>
<View className='text-center font-weight font-34 mt-3'></View> {process.env.TARO_ENV !== "h5" && <View className='text-center font-weight font-34 mt-3'></View>}
{/*<Search/>*/} {/*<Search/>*/}
<Tabs tabList={category} onChange={tabChange} current={categoryKey}/> <Tabs tabList={category} onChange={tabChange} current={categoryKey}/>
<VideoList categoryKey={categoryKey}/> <VideoList categoryKey={categoryKey}/>

@ -1,3 +1,4 @@
export default definePageConfig({ export default definePageConfig({
navigationStyle: 'custom', navigationStyle: 'custom',
navigationBarTitleText: '登录'
}) })

@ -40,12 +40,18 @@
align-items: center; align-items: center;
gap: 12px; gap: 12px;
margin: 0 auto; margin: 0 auto;
padding: 25px 0;
a {
color: #fff;
}
} }
.errorTips { .errorTips {
position: absolute; position: fixed;
top: 100%; top: 10%;
left: 24px; left: 24px;
right: 24px; right: 24px;
background: red; background: red;

@ -1,12 +1,14 @@
import {FC, useEffect, useRef, useState} from "react"; import {FC, useEffect, useRef, useState} from "react";
import {Profile} from "@/store"; import {Profile} from "@/store";
import {Button, Form, Image, Input, PageContainer, Text, View} from "@tarojs/components"; import {Button, Form, Image, Input, Text, View} from "@tarojs/components";
import Taro from "@tarojs/taro"; import Taro from "@tarojs/taro";
import styles from './login.module.scss' import styles from './login.module.scss'
import Loading from "@/components/loading"; import Loading from "@/components/loading";
import Icon from "@/components/icon"; import Icon from "@/components/icon";
import {userApi} from "@/api"; import {LoginData, userApi} from "@/api";
import {regexTel} from "@/utils/regu"; import {regexTel} from "@/utils/regu";
import CustomPageContainer from "@/components/custom-page-container/custom-page-container";
import {loginApi, LoginParams} from "@/api/login";
interface BingProps { interface BingProps {
code: string code: string
@ -79,22 +81,74 @@ const Bing: FC<BingProps> = ({code, catch_key}: BingProps) => {
) )
} }
const Login = () => { function getMenuButtonBoundingClientRect() {
if (process.env.TARO_ENV === 'h5') {
return {top: 0, bottom: 44}
}
return Taro.getMenuButtonBoundingClientRect()
}
const Login: FC = () => {
const {statusBarHeight = 0} = Taro.getSystemInfoSync() const {statusBarHeight = 0} = Taro.getSystemInfoSync()
const bbc = Taro.getMenuButtonBoundingClientRect(); const bbc = getMenuButtonBoundingClientRect();
const navHeight = bbc.bottom + (bbc.top - statusBarHeight) - statusBarHeight const navHeight = bbc.bottom + (bbc.top - statusBarHeight) - statusBarHeight
const [isLoading, setLoading] = useState(false) const [isLoading, setLoading] = useState(false)
const [error, setError] = useState<string | null>(null) const [error, setError] = useState<string | null>(null)
const [validateCode, setCode] = useState<string | null>(null) const [validateCode, setCode] = useState<string | null>(null)
const [catch_key, setCatch_key] = useState<string | null>(null) const [catch_key, setCatch_key] = useState<string | null>(null)
const {setUser, setToken, setCompany} = Profile.useContainer() const {setUser, setToken, setCompany} = Profile.useContainer()
const [h5params, setH5Params] = useState<LoginParams | null>(null)
const params = Taro.getCurrentInstance()?.router?.params as unknown as { data: LoginData }
useEffect(() => {
console.log(params)
if (params?.data) {
if (!params.data?.code) {
setUser(params.data.user)
setToken(params.data.token)
setCompany(params.data.company)
setLoading(false)
Taro.switchTab({url: '/pages/index/index'})
return
}
setCatch_key(catch_key)
setCode(params.data.code.image)
return
}
if (process.env.TARO_ENV === 'h5') {
setLoading(true);
loginApi.getParams().then((res) => {
setH5Params(res);
setError(null);
}).catch(e => {
setError(e?.errMsg ?? '系统内部错误')
}).finally(() => {
setLoading(false);
})
}
}, [])
function login() { function login() {
if (isLoading) return; if (isLoading) return;
if (h5params == null) {
setError('页面参数错误,请刷新页面!')
return;
}
setLoading(true) setLoading(true)
if (process.env.TARO_ENV === 'h5') {
location.href = "https://open.weixin.qq.com/connect/oauth2/authorize?" +
"appid=" + h5params!.appid +
"&redirect_uri=" + encodeURIComponent(h5params!.route) +
"&response_type=code" +
"&scope=snsapi_userinfo" +
"#wechat_redirect";
} else {
Taro.login({ Taro.login({
success: async (res) => { success: async (res) => {
try { try {
@ -103,6 +157,7 @@ const Login = () => {
setUser(user) setUser(user)
setToken(token) setToken(token)
setCompany(company) setCompany(company)
setLoading(false)
Taro.switchTab({url: '/pages/index/index'}) Taro.switchTab({url: '/pages/index/index'})
return return
} }
@ -118,12 +173,22 @@ const Login = () => {
}, },
}) })
} }
}
return ( return (
<View className={styles.container}> <View className={styles.container}>
<PageContainer show={!!validateCode} position='bottom' round onBeforeLeave={() => setCode(null)}> <CustomPageContainer show={!!validateCode} position='bottom' onBeforeLeave={() => setCode(null)}>
{validateCode && <Bing code={validateCode!} catch_key={catch_key!}/>} {validateCode && <Bing code={validateCode!} catch_key={catch_key!}/>}
</PageContainer> </CustomPageContainer>
<View className={styles.brand}>
<Image mode={'scaleToFill'} src="https://playedu.yaojiankang.top/favicon.ico"/>
</View>
<View className={styles.loginTips}>
<Text>使</Text>
</View>
<Button className={'button ' + styles.submit} onClick={login} disabled={isLoading}>
{isLoading ? <Loading/> : null}
<View className={styles.navbar} style={`height:${navHeight}px;margin-top:${statusBarHeight}px`}> <View className={styles.navbar} style={`height:${navHeight}px;margin-top:${statusBarHeight}px`}>
<Text></Text> <Text></Text>
{error ? <View className={styles.errorTips}> {error ? <View className={styles.errorTips}>
@ -133,15 +198,6 @@ const Login = () => {
</View> </View>
</View> : null} </View> : null}
</View> </View>
<View className={styles.brand}>
<Image mode={'scaleToFill'} src="https://admin.playedu.xyz/favicon.ico"/>
</View>
<View className={styles.loginTips}>
<Text>使</Text>
</View>
<Button className={'button ' + styles.submit} onClick={login} disabled={isLoading}>
{isLoading ? <Loading/> : null}
<Text></Text>
</Button> </Button>
</View> </View>
) )
@ -154,4 +210,5 @@ const Index: FC = () => {
</Profile.Provider> </Profile.Provider>
) )
} }
export default Index export default Index

@ -1,4 +1,4 @@
import {Button, Form, Input, PageContainer, View} from "@tarojs/components"; import {Button, Form, Input, View} from "@tarojs/components";
import {FC, useEffect, useState} from "react"; import {FC, useEffect, useState} from "react";
import {ManageApi, Student} from "@/api/manage"; import {ManageApi, Student} from "@/api/manage";
import Icon from "@/components/icon"; import Icon from "@/components/icon";
@ -7,13 +7,13 @@ import {curriculum} from "@/api";
import './addStudent.scss' import './addStudent.scss'
import {getCurrentInstance} from "@tarojs/runtime"; import {getCurrentInstance} from "@tarojs/runtime";
import {Profile} from '@/store' import {Profile} from '@/store'
import CustomPageContainer from "@/components/custom-page-container/custom-page-container";
interface Department { interface Department {
id: number id: number
title: string title: string
} }
const AddStudent = () => { const AddStudent = () => {
const [userInfo, setUerInfo] = useState<Student | null>(null) const [userInfo, setUerInfo] = useState<Student | null>(null)
const [department, setDepartment] = useState<Department[]>([]) const [department, setDepartment] = useState<Department[]>([])
@ -127,7 +127,7 @@ const AddStudent = () => {
<Button className='add button' formType='submit' disabled={disable}></Button> <Button className='add button' formType='submit' disabled={disable}></Button>
</Form> </Form>
<PageContainer show={show} round> <CustomPageContainer show={show} round>
<View className='px-2 pt-1' style='text-align:right' onClick={() => setShow(false)}></View> <View className='px-2 pt-1' style='text-align:right' onClick={() => setShow(false)}></View>
<View className='h-4 p-2 flex flex-wrap align-start'> <View className='h-4 p-2 flex flex-wrap align-start'>
{department?.map(item => { {department?.map(item => {
@ -139,7 +139,7 @@ const AddStudent = () => {
) )
})} })}
</View> </View>
</PageContainer> </CustomPageContainer>
</View> </View>
) )
} }

@ -1,3 +1,3 @@
export default definePageConfig({ export default definePageConfig({
navigationBarTitleText: '注册', navigationBarTitleText: '登记',
}) })

@ -1,49 +1,103 @@
import {FC} from "react"; import {FC} from "react";
import {Button, Form, Input, View} from "@tarojs/components"; import {Button, Form, Input, View} from "@tarojs/components";
import {getCurrentInstance} from "@tarojs/runtime"; import Taro, {useLoad, useRouter} from "@tarojs/taro";
import Taro from "@tarojs/taro"; import {userApi} from "@/api";
import {Profile} from '@/store'
const BingUser: FC = () => { const BingUser: FC = () => {
const {company_id, department_id, start_time, end_time} = getCurrentInstance()?.router?.params as unknown as Offline // const {depid, start_time, end_time} = getCurrentInstance()?.router?.params as unknown as Offline
const {setUser, setToken, setCompany} = Profile.useContainer()
Taro.useLoad(() => { const router = useRouter()
const time = Date.now()
if (!company_id
|| !department_id // Taro.useLoad(() => {
|| !start_time // const time = Date.now()
|| !end_time // if (!depid
|| time > new Date(end_time).getTime() // || !start_time
|| time < new Date(start_time).getTime()) { // || !end_time
Taro.showModal({ // || time > new Date(end_time).getTime()
title: '二维码已过期', // || time < new Date(start_time).getTime()) {
success() { // Taro.showModal({
Taro.reLaunch({url: '/pages/login/login'}) // title: '二维码已过期',
} // success() {
// Taro.reLaunch({url: '/pages/login/login'})
// }
// })
// return
// }
// })
function submit(e) {
const value = e.detail.value
// if (!value.user_name || !value.phone_number) {
// Taro.showToast({title: '请认真填写', icon: "error"})
// return
// }
//
// if (!regexTel.exec(value.phone_number)) {
// Taro.showToast({title: '手机号错误', icon: 'error'})
// return
// }
Taro.showLoading()
Taro.login({
success: async (res) => {
const data = await userApi.meetingSave({
...value,
code: res.code,
ticket: router.params.ticket,
}) })
return setCompany(data.company)
setUser(data.user)
setToken(data.token)
Taro.switchTab({url: '/pages/index/index'})
},
fail: () => {
Taro.showToast({title: '获取微信登录失败', icon: "error"})
},
complete() {
Taro.hideLoading()
} }
}) })
}
return ( return (
<View className='h-10 bg-white p-2'> <View className='h-10 bg-white p-2'>
<Form className='form'> <Form className='form' onSubmit={submit}>
<View className='item'> <View className='item'>
<View></View> <View></View>
<Input placeholder='请输入用户名' focus/> <Input placeholder='请输入用户名' focus name='user_name'/>
</View> </View>
<View className='item'> <View className='item'>
<View></View> <View></View>
<Input type='number' placeholder='请输入手机号'/> <Input type='number' placeholder='请输入手机号' name='phone_number'/>
</View> </View>
<Button className='button mt-3' formType='submit'></Button> <Button className='button mt-3' formType='submit'></Button>
</Form> </Form>
</View> </View>
) )
} }
export default BingUser const BingUserIndex = () => {
const router = useRouter()
useLoad(() => {
if (!router.params.ticket) {
Taro.reLaunch({
url: "/pages/index/index"
})
}
})
return (
<Profile.Provider>
<BingUser/>
</Profile.Provider>
)
}
export default BingUserIndex

@ -1,12 +1,13 @@
import {FC, useEffect, useState} from "react"; import {FC, useEffect, useState} from "react";
import {AddDepProps, ManageApi} from "@/api/manage"; import {AddDepProps, ManageApi} from "@/api/manage";
import {Button, View, PageContainer, Input, Form} from "@tarojs/components"; import {Button, View, Input, Form} from "@tarojs/components";
import Taro from "@tarojs/taro"; import Taro from "@tarojs/taro";
import {Profile} from '@/store' import {Profile} from '@/store'
import './depAdmin.scss' import './depAdmin.scss'
import PopPut from "@/components/popPut/popPut"; import PopPut from "@/components/popPut/popPut";
import folder from '@/static/img/folder.png' import folder from '@/static/img/folder.png'
import {getCurrentInstance} from "@tarojs/runtime"; import {getCurrentInstance} from "@tarojs/runtime";
import CustomPageContainer from "@/components/custom-page-container/custom-page-container";
interface ChangeDataProps { interface ChangeDataProps {
putCompany: Manage | null putCompany: Manage | null
@ -255,11 +256,11 @@ const DepAdmin: FC = () => {
</View> </View>
</View> </View>
<PageContainer show={show} round onAfterLeave={() => setShow(false)}> <CustomPageContainer show={show} round onAfterLeave={() => setShow(false)}>
<View> <View>
{show && <ChangeData getDeps={getData} putCompany={putCompany} parent_id={Number(params.id)}/>} {show && <ChangeData getDeps={getData} putCompany={putCompany} parent_id={Number(params.id)}/>}
</View> </View>
</PageContainer> </CustomPageContainer>
</Profile.Provider> </Profile.Provider>
) )
} }

@ -1,4 +1,4 @@
import {FC, useCallback, useState} from "react"; import {FC, useCallback, useEffect, useState} from "react";
import {Button, Image, Picker, View} from "@tarojs/components"; import {Button, Image, Picker, View} from "@tarojs/components";
import styles from './offline.module.scss' import styles from './offline.module.scss'
import Taro from "@tarojs/taro"; import Taro from "@tarojs/taro";
@ -7,13 +7,14 @@ import {formatDate} from "@/utils/time";
import {getSetting, authorize} from "@tarojs/taro"; import {getSetting, authorize} from "@tarojs/taro";
import PopPut from "@/components/popPut/popPut"; import PopPut from "@/components/popPut/popPut";
const Offline: FC = () => { const Offline: FC = () => {
const [manages, setManages] = useState<Manage[]>([]) const [manages, setManages] = useState<Manage[]>([])
const [start, setStart] = useState<string>(formatDate(new Date(), "YY-MM-dd")) const [start, setStart] = useState<string>(formatDate(new Date(), "YY-MM-dd 08:00:00"))
const [end, setEnd] = useState<string>(formatDate(new Date(), "YY-MM-dd")) const [end, setEnd] = useState<string>(formatDate(new Date(), "YY-MM-dd 18:00:00"))
const [tempFilePath, setTempFilePath] = useState<string | null>(null); const [tempFilePath, setTempFilePath] = useState<string | null>(null);
const [error, setError] = useState<string | null>(null)
const [depid, setDepid] = useState<number | null>(null)
const [isDownloading, setDownloading] = useState(false)
Taro.useLoad(() => { Taro.useLoad(() => {
curriculum.department().then(res => { curriculum.department().then(res => {
@ -21,78 +22,53 @@ const Offline: FC = () => {
}) })
}) })
async function change(e) { useEffect(() => {
const depName = e.detail.value if (!depid) {
if (!depName) { // Taro.showToast({title: '请选择部门', icon: 'error'})
Taro.showToast({title: '请选择部门', icon: 'error'}) return
return null }
if (isDownloading) {
return;
} }
setDownloading(true)
const startTime = new Date(start).getTime() const startTime = new Date(start).getTime()
const endTime = new Date(end).getTime() const endTime = new Date(end).getTime()
const depid = manages.find(d => d.name === depName)?.id const path = encodeURIComponent("/pages/meeting/meeting")
const path = encodeURIComponent("/pages/meeting/index")
const qrcodeUrl = `${process.env.TARO_APP_API}/wechat/link?depid=${depid}&start_time=${startTime}&end_time=${endTime}&path=${path}` const qrcodeUrl = `${process.env.TARO_APP_API}/official/qrcode?depid=${depid}&start_time=${startTime}&end_time=${endTime}&path=${path}&t=${Date.now()}`
if (process.env.TARO_ENV === 'h5') {
setTempFilePath(qrcodeUrl)
} else {
Taro.downloadFile({ Taro.downloadFile({
url: qrcodeUrl, url: qrcodeUrl,
success(res) { success(res) {
console.log({res})
setTempFilePath(res.tempFilePath) setTempFilePath(res.tempFilePath)
}, },
fail() { fail(err) {
Taro.showToast({title: '二维码生产失败', icon: 'error'}) console.log({err})
} setError("请求失败");
// Taro.showToast({title: '操作失败', icon: 'error'})
},
complete() {
setDownloading(false)
} }
) })
} }
}, [depid])
function change(e) {
// const paintingCanvas = () => { setDepid(() => manages[Number(e.detail.value)]?.id)
// Taro.showLoading() }
// try {
// Taro.createSelectorQuery().select('#canvasId').fields({node: true, size: true}).exec((res) => {
// const width = res[0].width
// const height = res[0].height
//
// const canvas = res[0].node
//
// Mycanvas = canvas
// const ctx = canvas.getContext('2d')
//
// const dpr = Taro.getSystemInfoSync().pixelRatio
// canvas.width = width * dpr
// canvas.height = height * dpr
// ctx.scale(dpr, dpr)
//
// ctx.fillStyle = '#ffffff'
// ctx.fillRect(0, 0, width, height)
//
// ctx.font = '16px Microsoft YaiHei'
// ctx.fillStyle = 'black'
// ctx.textAlign = 'center';
// ctx.fillText(`${start === end ? `有效期${start}` : `开始:${start} 结束:${end}`}`, width / 2, 280)
//
// Taro.getImageInfo({
// src: 'http://81.69.44.74:39200/kynyxd/images/ILBNTCk3PhC3xEV1zCDAsggudZzkiHJi.png',
// success(res) {
// const img = canvas.createImage()
// img.src = res.path
// img.onload = () => {
// ctx.drawImage(img, (width - 200) / 2, 20, 200, 200)
// }
// }
// })
// })
// } catch (e) {
// Taro.showToast({title: '二维码生成失败', icon: 'error'})
// }
// Taro.hideLoading()
// }
const handleWriteFile = useCallback(() => { const handleWriteFile = useCallback(() => {
if (tempFilePath) { if (tempFilePath == null) {
Taro.showToast({title: '下载失败', icon: 'error'})
} else if (process.env.TARO_ENV !== 'h5') {
Taro.saveImageToPhotosAlbum({ Taro.saveImageToPhotosAlbum({
filePath: tempFilePath, filePath: tempFilePath,
success() { success() {
@ -103,7 +79,7 @@ const Offline: FC = () => {
} }
}) })
} else { } else {
Taro.showToast({title: '下载失败', icon: 'error'}) Taro.showToast({title: '请截屏', icon: 'error'})
} }
}, [tempFilePath]) }, [tempFilePath])
@ -153,12 +129,19 @@ const Offline: FC = () => {
<View> <View>
<Picker mode='selector' range={manages} onChange={change} rangeKey='name'> <Picker mode='selector' range={manages} onChange={change} rangeKey='name'>
<PopPut title='部门'/> <PopPut title='部门' content={manages?.find(x => x.id === depid)?.name}/>
</Picker> </Picker>
</View> </View>
{tempFilePath && <View className='text-center'> {tempFilePath && <View className='text-center'>
<Image src={tempFilePath} mode='widthFix'/> <Image
src={tempFilePath}
mode='widthFix'
onLoad={() => setError(null)}
onError={(e) => setError(e.detail.errMsg)}
style={{width: '80%'}}
/>
{error && <View>{error}</View>}`
<Button className='button' onClick={handleSaveCode}></Button> <Button className='button' onClick={handleSaveCode}></Button>
</View>} </View>}
</View> </View>

@ -9,7 +9,7 @@ const Header = () => {
return ( return (
<View className={styles.header}> <View className={styles.header}>
<View className='flex'> <View className='flex'>
<Image src={avatar}/> <Image src={avatar} className={styles.avatar}/>
<View className='flex-1'> <View className='flex-1'>
<View className='font-32 font-weight'>{user?.name}</View> <View className='font-32 font-weight'>{user?.name}</View>
<View className='login font-24 mt-2 text-secondary flex justify-between content-start'> <View className='login font-24 mt-2 text-secondary flex justify-between content-start'>

@ -27,7 +27,7 @@ const Service = () => {
oldList.unshift(...[ oldList.unshift(...[
{title: '部门管理', src: dep, router: '/pages/manage/depAdmin/depAdmin'}, {title: '部门管理', src: dep, router: '/pages/manage/depAdmin/depAdmin'},
{title: '课程购买', src: buy, router: '/pages/manage/curriculum/curriculum'}, {title: '课程购买', src: buy, router: '/pages/manage/curriculum/curriculum'},
{title: '见面会', src: buy, router: '/pages/manage/offline/offline'}, {title: '现场会', src: buy, router: '/pages/manage/offline/offline'},
]) ])
setList(oldList) setList(oldList)
} }
@ -44,7 +44,7 @@ const Service = () => {
{ {
list.map(d => { list.map(d => {
return ( return (
<View onClick={() => jump(d.router)}> <View onClick={() => jump(d.router)} key={d.title}>
<Image src={d.src} mode='aspectFit' className={styles.serviceImage}/> <Image src={d.src} mode='aspectFit' className={styles.serviceImage}/>
<View>{d.title}</View> <View>{d.title}</View>
</View> </View>

@ -35,10 +35,10 @@ page {
} }
.header { .header {
padding: 130px 20px 0; padding: 130px 20px 0;
font-size: 10rpx;
Image { .avatar {
width: 100px; width: 100px;
height: 100px; height: 100px;
margin-right: 30px; margin-right: 30px;

@ -1,7 +1,12 @@
page { page,
background-color: #efeff7; .taro_router .taro_page {
background-color: #efeff7 !important;
font-family: PingFang SC-Bold, PingFang SC; font-family: PingFang SC-Bold, PingFang SC;
-webkit-overflow-scrolling: touch -webkit-overflow-scrolling: touch;
}
body {
font-size: 32rpx;
} }
.input { .input {
@ -64,7 +69,6 @@ page {
} }
.button { .button {
width: 690rpx;
line-height: 76rpx; line-height: 76rpx;
background: #45D4A8; background: #45D4A8;
border-radius: 10rpx; border-radius: 10rpx;

@ -22,7 +22,7 @@ function useProfile() {
setUser(null) setUser(null)
setToken(null) setToken(null)
Taro.removeStorageSync('profile') Taro.removeStorageSync('profile')
Taro.reLaunch({url: '/pages/login/login'}) // Taro.reLaunch({url: '/pages/login/login'})
} }
useEffect(() => { useEffect(() => {

Loading…
Cancel
Save