新增见面会开始结束,优化图片加载状态,失效状态显示,历史记录过期展示

main
king 2 years ago
parent 78c0c46125
commit f31ee9534b
  1. 1
      package.json
  2. 7
      pnpm-lock.yaml
  3. 2
      src/api/course.ts
  4. 21
      src/api/meeting.ts
  5. 1
      src/components/button/MyButton.tsx
  6. 34
      src/components/dateTimePicker/dateTimePicker.tsx
  7. 0
      src/components/dateTimePicker/index.scss
  8. 104
      src/components/dateTimePicker/utils.ts
  9. 7
      src/components/lineChart/lineChart.tsx
  10. 58
      src/components/video/video.tsx
  11. 6
      src/pages/business/videoInfo/components/catalogue.tsx
  12. 45
      src/pages/business/videoInfo/components/course.tsx
  13. 7
      src/pages/business/videoInfo/components/hours.tsx
  14. 9
      src/pages/business/videoInfo/videoInfo.tsx
  15. 1
      src/pages/index/components/videoList.tsx
  16. 5
      src/pages/manage/courseAdmin/components/search.tsx
  17. 6
      src/pages/manage/courseAdmin/courseAdmin.module.scss
  18. 2
      src/pages/manage/courseAdmin/courseAdmin.tsx
  19. 15
      src/pages/manage/meetings/meetings.tsx
  20. 15
      src/pages/manage/spotMeeting/spotMeeting.module.scss
  21. 165
      src/pages/manage/spotMeeting/spotMeeting.tsx
  22. BIN
      src/static/img/failure.png

@ -53,6 +53,7 @@
"@tarojs/runtime": "3.6.8", "@tarojs/runtime": "3.6.8",
"@tarojs/shared": "3.6.8", "@tarojs/shared": "3.6.8",
"@tarojs/taro": "3.6.8", "@tarojs/taro": "3.6.8",
"dayjs": "^1.11.9",
"react": "^18.0.0", "react": "^18.0.0",
"react-dom": "^18.0.0", "react-dom": "^18.0.0",
"react-refresh": "^0.11.0", "react-refresh": "^0.11.0",

@ -50,6 +50,9 @@ dependencies:
'@tarojs/taro': '@tarojs/taro':
specifier: 3.6.8 specifier: 3.6.8
version: 3.6.8(@types/react@18.0.0) version: 3.6.8(@types/react@18.0.0)
dayjs:
specifier: ^1.11.9
version: 1.11.9
react: react:
specifier: ^18.0.0 specifier: ^18.0.0
version: 18.0.0 version: 18.0.0
@ -6117,6 +6120,10 @@ packages:
whatwg-url: 12.0.1 whatwg-url: 12.0.1
dev: true dev: true
/dayjs@1.11.9:
resolution: {integrity: sha512-QvzAURSbQ0pKdIye2txOzNaHmxtUBXerpY0FJsFXUMKbIZeFm5ht1LS/jFsrncjnmtv8HsG0W2g6c0zUjZWmpA==}
dev: false
/debug@2.6.9: /debug@2.6.9:
resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==} resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==}
peerDependencies: peerDependencies:

@ -10,7 +10,7 @@ export interface CourseAllParam {
page: number page: number
page_size: number page_size: number
title?: string title?: string
dep_id: null | number dep_id: number
} }
export const courseApi = { export const courseApi = {

@ -12,6 +12,10 @@ export interface Meeting {
estimate_start_time: number; estimate_start_time: number;
/** 现场会标题 */ /** 现场会标题 */
name: string; name: string;
status: 0
| 1 // 已开始
| 2 // 进行中
| 3 // 结束中
} }
export const meetingAPi = { export const meetingAPi = {
@ -22,12 +26,21 @@ export const meetingAPi = {
return request<Meeting>(`/api/v1/meetings/meeting/${id}`, "GET") return request<Meeting>(`/api/v1/meetings/meeting/${id}`, "GET")
}, },
setList(page: number, page_size: number) { setList(page: number, page_size: number) {
return request<{data:Meeting[],total:number}>(`/api/v1/meetings/meeting?page=${page}&page_size=${page_size}`, "GET") return request<{
data: Meeting[],
total: number
}>(`/api/v1/meetings/meeting?page=${page}&page_size=${page_size}`, "GET")
}, },
exists() { exists() {
return request<Meeting>('/api/v1/meetings/exists', "GET") return request<Meeting>('/api/v1/meetings/exists', "GET")
}, },
del(id:number){ del(id: number) {
return request(`/api/v1/meetings/meeting/${id}`,"DELETE") return request(`/api/v1/meetings/meeting/${id}`, "DELETE")
} },
start(id: number) {
return request(`/api/v1/meetings/meeting/${id}/start`, "PUT")
},
end(id: number) {
return request(`/api/v1/meetings/meeting/${id}/end`, "PUT")
},
} }

@ -43,7 +43,6 @@ const MyButton: FC<Props> = (props) => {
return ( return (
<Button <Button
{...props} {...props}
loading={false}
style={buttonStyle()} style={buttonStyle()}
className={`${styles.Mybutton} ${props.className}`}> className={`${styles.Mybutton} ${props.className}`}>
{ {

@ -0,0 +1,34 @@
import {FC} from "react";
import {Picker, View} from "@tarojs/components";
interface Props {
children: JSX.Element
disabled?: boolean
}
const DateTimePicker: FC<Props> = (props) => {
function bindMultiPickerChange(e) {
let activityArray = JSON.parse(JSON.stringify(this.activityArray)),
{value} = e.detail,
_result = [];
for (let i = 0; i < value.length; i++) {
_result[i] = activityArray[i][value[i]].id;
}
this.$emit("result", _result);
}
return (
<Picker
mode="multiSelector"
range-key='name'
disabled={props.disabled}
onChange={bindMultiPickerChange}
>
{props.children}12
</Picker>
)
}
export default DateTimePicker

@ -0,0 +1,104 @@
interface DateTime {
year: number
month: number
day: number
hours: number
minutes: number
}
/**
*
* @param year
* @param month
*/
const getDaysInOneMonth = function (year: number, month: number): number {
let _month = parseInt(String(month), 10);
let d = new Date(year, _month, 0);
return d.getDate();
};
/**
*
* @param date
*/
const dateDate = function (date: Date): DateTime {
let year = date.getFullYear();
let month = date.getMonth() + 1;
let day = date.getDate();
let hours = date.getHours();
let minutes = date.getMinutes();
return {
year, month, day, hours, minutes
}
};
interface DateTimePicker {
name: string
id: number | string
}
/**
*
* date time 30
* @param startyear
* @param endyear
*/
const dateTimePicker = function (startyear: number, endyear: number) {
const years: DateTimePicker[] = [];
const months: DateTimePicker[] = [];
const hours: DateTimePicker[] = [];
const minutes: DateTimePicker[] = [];
for (let i = startyear - 20; i <= endyear + 20; i++) {
years.push({name: i + '年', id: i})
}
//获取月份
for (let i: string | number = 1; i <= 12; i = Number(i) + 1) {
if (i < 10) {
i = "0" + i;
}
months.push({name: i + '月', id: i});
}
//获取小时
for (let i: string | number = 0; i < 24; i = Number(i) + 1) {
if (i < 10) {
i = "0" + i
}
hours.push({name: i + '时', id: i})
}
//获取分钟
for (let i: string | number = 0; i < 60; i = Number(i) + 1) {
if (i < 10) {
i = "0" + i;
}
minutes.push({
name: i + '分',
id: i
});
}
return function (_year: number | string, _month: string | number) {
const days: DateTimePicker[] = [];
_year = parseInt(String(_year));
_month = parseInt(String(_month));
//获取日期
for (let i: string | number = 0; i <= getDaysInOneMonth(_year, _month); i = Number(i) + 1) {
if (i < 10) {
i = "0" + i;
}
days.push({
name: i + '日',
id: i
});
}
return [years, months, days, hours, minutes];
}
};
export {
dateTimePicker,
getDaysInOneMonth,
dateDate
}

@ -1,6 +1,7 @@
import {ScrollView, View} from "@tarojs/components"; import {ScrollView, View} from "@tarojs/components";
import {FC, useEffect, useState} from "react"; import {FC, useEffect, useState} from "react";
import style from './lineChart.module.scss' import style from './lineChart.module.scss'
import {formatMinute} from "@/utils/time";
export interface lineData { export interface lineData {
time: string time: string
@ -29,15 +30,15 @@ const LineChart: FC<Props> = ({data}) => {
return ( return (
<> <>
<ScrollView scrollX={!!maxHeight.value}> <ScrollView scrollX={!!maxHeight.value}>
<View>{maxHeight.time}{maxHeight.value}</View> <View>{maxHeight.time}{formatMinute(maxHeight.value)}</View>
<View className={style.lineChart}> <View className={style.lineChart}>
{!maxHeight.value && <View className={style.empty}></View>} {!maxHeight.value && <View className={style.empty}></View>}
{ {
lineChartList.map(d => <View key={d.time}> maxHeight.value && lineChartList.map(d => <View key={d.time}>
<View className={style.columnBox} style={{width: "100px"}}> <View className={style.columnBox} style={{width: "100px"}}>
<View className={style.line} <View className={style.line}
style={{height: height - 10 - (d.value / maxHeight.value * height) + "px"}}></View> style={{height: height - 10 - (d.value / maxHeight.value * height) + "px"}}></View>
<View>{d.value}</View> <View>{formatMinute(d.value)}</View>
<View className={style.column} style={{height: d.value / maxHeight.value * height + "px"}}> </View> <View className={style.column} style={{height: d.value / maxHeight.value * height + "px"}}> </View>
<View>{d.time}</View> <View>{d.time}</View>
</View> </View>

@ -23,24 +23,39 @@ const HVideo: FC<HVideoOptions> = (opt: HVideoOptions) => {
break break
} }
}) })
} catch (e) {} } catch (e) {
}
function onTimeUpdate(event: BaseEventOrig<VideoProps.onTimeUpdateEventDetail>) { function onTimeUpdate(event: BaseEventOrig<VideoProps.onTimeUpdateEventDetail>) {
// if (opt.preview) return;
const time = event.detail.currentTime const time = event.detail.currentTime
/** 前进回退 */ /** 前进回退 */
if (currentTime + deviation < time) { if (!opt.preview) {
video?.seek(currentTime) if (currentTime + deviation < time) {
return video?.seek(currentTime)
return
}
} }
setCurrentTime(time) setCurrentTime(time)
/** 判断是否进入断点 */ /** 判断是否进入断点 */
opt.breakpoint.forEach(d => { opt.breakpoint.forEach(d => {
if (time < d + deviation && time > d - deviation) { if (time < d + deviation && time > d - deviation) {
video?.exitFullScreen() video.exitFullScreen()
video?.pause() video?.pause()
video?.seek(d - deviation) video?.seek(d - deviation)
// if (opt.preview) {
// Taro.showModal({
// title: '是否答题',
// success({confirm, cancel}) {
// confirm && opt.onBreakpoint(d)
// cancel && video?.play()
// }
// })
// return;
// }
opt.onBreakpoint(d) opt.onBreakpoint(d)
return return
} }
@ -55,7 +70,6 @@ const HVideo: FC<HVideoOptions> = (opt: HVideoOptions) => {
}) })
function onEnded() { function onEnded() {
// if (opt.preview) return;
if (currentTime + 1 > opt.duration) { if (currentTime + 1 > opt.duration) {
opt.onEnded() opt.onEnded()
} else { } else {
@ -89,19 +103,23 @@ const HVideo: FC<HVideoOptions> = (opt: HVideoOptions) => {
return ( return (
<Video <>
id={'myVideo'} <Video
autoplay id={'myVideo'}
style={{width: '100%', height: '100%', position: "relative"}} autoplay
poster={opt?.poster || ''} style={{width: '100%', height: '100%', position: "relative"}}
src={opt.src} poster={opt?.poster || ''}
enableProgressGesture={false} src={opt.src}
direction={90} showRateBtn='swan'
onTimeUpdate={onTimeUpdate} enableProgressGesture={false}
onEnded={onEnded} direction={90}
onPlay={onPlay} onTimeUpdate={onTimeUpdate}
onPause={onPause} onEnded={onEnded}
/> onPlay={onPlay}
onPause={onPause}
/>
{/*<View style={{position: 'absolute', top: '100px', color: '#fff'}}>1221</View>*/}
</>
) )
} }

@ -99,7 +99,7 @@ const Catalogue: FC<Props> = ({data, setHors, id, playId,}) => {
if (flats[0].id === playId) { if (flats[0].id === playId) {
videoEvents.setVideoState('play') videoEvents.setVideoState('play')
} else { } else {
setHors(true, flats[0].id) setHors(!!data?.learn_hour_records?.length, flats[0].id)
} }
return return
} }
@ -114,7 +114,7 @@ const Catalogue: FC<Props> = ({data, setHors, id, playId,}) => {
if (next.id === playId) { if (next.id === playId) {
videoEvents.setVideoState('play') videoEvents.setVideoState('play')
} else { } else {
setHors(true, next.id) setHors(false, next.id)
} }
} }
} }
@ -134,7 +134,7 @@ const Catalogue: FC<Props> = ({data, setHors, id, playId,}) => {
{current === 1 && <View> {current === 1 && <View>
<View className='my-2'></View> <View className='my-2'></View>
{data?.chapters.length {data?.chapters.length
? Object.values(data?.chapters || {}).map((d, index) => <View> ? Object.values(data?.chapters || {}).map((d, index) => <View key={d.id}>
<Collapse title={`${index + 1}.${d.name}`}> <Collapse title={`${index + 1}.${d.name}`}>
<Hours <Hours
playId={playId} playId={playId}

@ -15,7 +15,7 @@ import {formatMinute} from "@/utils/time";
interface Props { interface Props {
id: number, id: number,
courseId: number courseId: number
preview?: boolean preview: boolean
curEnd: (test?: boolean) => void curEnd: (test?: boolean) => void
} }
@ -35,14 +35,28 @@ const Course: FC<Props> = ({id, courseId, preview, curEnd}: Props) => {
const [frequency, setFrequency] = useState<number>(1) // 次数 const [frequency, setFrequency] = useState<number>(1) // 次数
const [testId, setTestId] = useState<number | null>(null) const [testId, setTestId] = useState<number | null>(null)
const [startRecording, setStartRecording] = useState<CurEndParam | null>(null) // 视频开始记录 const [startRecording, setStartRecording] = useState<CurEndParam | null>(null) // 视频开始记录
const [topicRecord, setRecord] = useState<number[]>([]) // 考题正确记录
const {user} = Profile.useContainer() const {user} = Profile.useContainer()
async function onEnded() { async function onEnded() {
unique_ident.put(data?.duration, Date.now()) // 记录 unique_ident.put(data?.duration, Date.now()) // 记录
startRecording && await curriculum.curEnd(courseId, id, {...startRecording, duration: data?.duration!}) // 结束
startRecording && curriculum.curEnd(courseId, id, {...startRecording, duration: data?.duration!}) //结束
if (testId) { if (testId) {
if (preview) { // 预览
Taro.showModal({
title: "是否前往考试",
success({confirm}) {
if (confirm) {
curEnd(true)
Taro.navigateTo({url: `/pages/business/test/test?testId=${testId}`})
} else {
curEnd()
}
}
})
return
}
Taro.navigateTo({url: `/pages/business/test/test?testId=${testId}`}) Taro.navigateTo({url: `/pages/business/test/test?testId=${testId}`})
curEnd(true) curEnd(true)
} else { } else {
@ -70,6 +84,7 @@ const Course: FC<Props> = ({id, courseId, preview, curEnd}: Props) => {
setExamAll(res.hourExamQuestions || []) setExamAll(res.hourExamQuestions || [])
setFaultCount(res.fault_count?.[0] || {}) setFaultCount(res.fault_count?.[0] || {})
setTestId(res?.hour_test?.id || null) setTestId(res?.hour_test?.id || null)
setRecord([])
setStartRecording({ setStartRecording({
duration: 0, duration: 0,
@ -139,8 +154,9 @@ const Course: FC<Props> = ({id, courseId, preview, curEnd}: Props) => {
useEffect(() => { useEffect(() => {
if (!newRecord.length) return; if (!newRecord.length) return;
setFrequency(frequency - 1) setFrequency(frequency - 1) // 减少一次学习记录
const pass = newRecord.every(d => d) const pass = newRecord.every(d => d)
/** 考题正确 */
if (pass) { if (pass) {
const {id: question_id, question_type} = examAll?.[time]?.[0] const {id: question_id, question_type} = examAll?.[time]?.[0]
curriculum.answerRecord(id, { curriculum.answerRecord(id, {
@ -150,13 +166,19 @@ const Course: FC<Props> = ({id, courseId, preview, curEnd}: Props) => {
question_type, question_type,
question_id question_id
}).then() }).then()
/** 删除断点 */
const old: number[] = JSON.parse(JSON.stringify(breakpoint)) const old: number[] = JSON.parse(JSON.stringify(breakpoint))
const index = old.indexOf(time) const index = old.indexOf(time)
old.splice(index, 1) old.splice(index, 1)
setBreakpoint(old) setBreakpoint(old)
Taro.showToast({title: '答题正确', duration: 1000})
setRecord([...topicRecord, time])
seek(time) seek(time)
Taro.showToast({title: '答题正确', duration: 2000})
init() init()
} else { } else {
if ((frequency - 1) !== 0) { if ((frequency - 1) !== 0) {
Taro.showModal({ Taro.showModal({
@ -183,12 +205,11 @@ const Course: FC<Props> = ({id, courseId, preview, curEnd}: Props) => {
seek = fn seek = fn
} }
return ( return (
<> <>
<HVideo <HVideo
duration={data?.duration || 0} duration={data?.duration || 0}
preview={!!preview} preview={preview}
src={data?.url || ''} src={data?.url || ''}
onEnded={onEnded} onEnded={onEnded}
breakpoint={breakpoint} breakpoint={breakpoint}
@ -196,6 +217,7 @@ const Course: FC<Props> = ({id, courseId, preview, curEnd}: Props) => {
setTime={videoSeek} setTime={videoSeek}
/> />
<View> <View>
<CustomPageContainer <CustomPageContainer
show={show} show={show}
@ -244,13 +266,14 @@ const Course: FC<Props> = ({id, courseId, preview, curEnd}: Props) => {
<View className='upAndDown'> <View className='upAndDown'>
<View> <View>
{index !== 0 && <MyButton type='default' size='mini' width={150} {
onClick={() => setIndex(index - 1)}></MyButton>} index !== 0 &&
<MyButton type='default' size='mini' width={150} onClick={() => setIndex(index - 1)}></MyButton>
}
</View> </View>
<View> <View>
{ {
(index + 1) !== examAll?.[time]?.length (index + 1) !== examAll?.[time]?.length &&
&&
<MyButton size='mini' width={150} type='default' onClick={() => setIndex(index + 1)}></MyButton> <MyButton size='mini' width={150} type='default' onClick={() => setIndex(index + 1)}></MyButton>
} }
</View> </View>

@ -64,14 +64,15 @@ const Hours: FC<Props> = ({data, click, learn_hour_records, playId}) => {
return ( return (
<> <>
{data?.map((d, index) => {data?.map((d, index) =>
<> <View key={d.id}>
<View className={'hor' + ` ${complete(d.id) ? 'complete' : undefined}`} <View className={'hor' + ` ${complete(d.id) ? 'complete' : undefined}`}
key={index} 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) || playId === d.id) ? playOk : play} mode="scaleToFill" className='image'/> <Image src={(complete(d.id) || playId === d.id) ? playOk : play} mode="scaleToFill" className='image'/>
<View className='title'> <View className='title'>
<View className='text'> <View className='text'>
<View style={{wordBreak: 'break-all'}}>{playId === d.id && <Text className='text-center text-success'></Text>}{index + 1}. {d.title}</View> <View style={{wordBreak: 'break-all'}}>{playId === d.id &&
<Text className='text-center text-success'></Text>}{index + 1}. {d.title}</View>
<View className='font-26 text-muted mt-1'>{formatMinute(d.duration)}</View> <View className='font-26 text-muted mt-1'>{formatMinute(d.duration)}</View>
{complete(d.id) === 0 && <View className='font-26 text-danger'></View>} {complete(d.id) === 0 && <View className='font-26 text-danger'></View>}
</View> </View>
@ -82,7 +83,7 @@ const Hours: FC<Props> = ({data, click, learn_hour_records, playId}) => {
} }
</View> </View>
</View> </View>
</>)} </View>)}
</> </>
) )

@ -50,7 +50,7 @@ const VideoInfo: FC = () => {
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?.[flats.length - 1]?.id) { if (playId === flats?.[flats.length - 1]?.id) {
Taro.showModal({title: '当前课程结束'}) Taro.showModal({title: '当前课程结束'})
eventsIndex.trigger(id) eventsIndex.trigger({id: Number(id)})
return; return;
} }
for (const [index, flat] of flats.entries()) { for (const [index, flat] of flats.entries()) {
@ -79,7 +79,6 @@ const VideoInfo: FC = () => {
const courseHourRecordsFinish = data?.learn_hour_records.find(d => d.id === playId)?.courseHourRecordsFinish const courseHourRecordsFinish = data?.learn_hour_records.find(d => d.id === playId)?.courseHourRecordsFinish
if (typeof courseHourRecordsFinish === 'number') { if (typeof courseHourRecordsFinish === 'number') {
if (courseHourRecordsFinish === 1) { if (courseHourRecordsFinish === 1) {
console.log(playing)
if (!playing) { if (!playing) {
playNext() playNext()
} }
@ -125,11 +124,7 @@ const VideoInfo: FC = () => {
<Text>{((data?.learn_hour_records.length || 0) / (data?.course.class_hour || 1) * 100).toFixed(0)}%</Text> <Text>{((data?.learn_hour_records.length || 0) / (data?.course.class_hour || 1) * 100).toFixed(0)}%</Text>
</View> </View>
</View> </View>
<Catalogue <Catalogue data={data} setHors={setHors} id={id} playId={playId}/>
data={data}
setHors={setHors}
id={id}
playId={playId}/>
</View> </View>
</> </>
) )

@ -57,7 +57,6 @@ export const VideoList: FC<Props> = ({categoryKey, ready}) => {
case "is_finished": case "is_finished":
return <View></View> return <View></View>
} }
return (<View>sd</View>)
} }
useReachBottom(() => { useReachBottom(() => {

@ -15,7 +15,7 @@ interface Props {
export const Search: FC<Props> = ({param, setParam}) => { export const Search: FC<Props> = ({param, setParam}) => {
const [show, setShow] = useState(false) const [show, setShow] = useState(false)
const [deps, setDeps] = useState<Manage[]>([]) const [deps, setDeps] = useState<Manage[]>([])
const [depId, setDepId] = useState<number | null>(param.dep_id) const [depId, setDepId] = useState<number>(param.dep_id)
async function getDepList() { async function getDepList() {
try { try {
@ -27,7 +27,7 @@ export const Search: FC<Props> = ({param, setParam}) => {
const changeDepId = useCallback((id: number) => { const changeDepId = useCallback((id: number) => {
if (id === depId) { if (id === depId) {
setDepId(null) setDepId(0)
} else { } else {
setDepId(id) setDepId(id)
} }
@ -66,6 +66,7 @@ export const Search: FC<Props> = ({param, setParam}) => {
<> <>
{ {
deps.map(dep => <View deps.map(dep => <View
key={dep.id}
className={styles.radioBox} className={styles.radioBox}
onClick={() => changeDepId(dep.id)}> onClick={() => changeDepId(dep.id)}>
<Radio <Radio

@ -6,7 +6,7 @@
align-items: top; align-items: top;
} }
.searchInput{ .searchInput {
width: 570rpx; width: 570rpx;
height: 68rpx; height: 68rpx;
display: flex; display: flex;
@ -18,12 +18,12 @@
overflow: hidden; overflow: hidden;
} }
.radioBox{ .radioBox {
border-bottom: 1px solid #F5F8F7; border-bottom: 1px solid #F5F8F7;
padding: 20px 0; padding: 20px 0;
} }
.custom{ .custom {
max-height: 60vh; max-height: 60vh;
overflow: auto; overflow: auto;
padding: 20px 0; padding: 20px 0;

@ -8,7 +8,7 @@ const CourseAdmin: FC = () => {
page: 1, page: 1,
page_size: 10, page_size: 10,
title: '', title: '',
dep_id: null dep_id: 0
}) })
useEffect(() => { useEffect(() => {

@ -41,8 +41,11 @@ const MeetingsConfig: FC = () => {
} }
} }
function jumpInfo(id: number) { function jumpInfo(item: Meeting) {
Taro.navigateTo({url: `/pages/manage/spotMeeting/spotMeeting?id=${id}`}) if (Date.now() > item.estimate_end_time || item.status > 0) {
return
}
Taro.navigateTo({url: `/pages/manage/spotMeeting/spotMeeting?id=${item.id}`})
} }
useReachBottom(useCallback(() => { useReachBottom(useCallback(() => {
@ -61,10 +64,14 @@ const MeetingsConfig: FC = () => {
{ {
meeting.length ? meeting.length ?
meeting.map((d, index) => <View className={styles.meeting} key={d.id}> meeting.map((d, index) => <View className={styles.meeting} key={d.id}>
<View className={styles.title} onClick={() => Date.now() < d.estimate_end_time && jumpInfo(d.id)}> <View className={styles.title} onClick={() => jumpInfo(d)}>
<View className='font-weight mb-1'>{d.name}</View> <View className='font-weight mb-1'>{d.name}</View>
<View>{formatDate(new Date(d.estimate_start_time), "MM-dd")} {formatDate(new Date(d.estimate_start_time), "MM-dd")}</View> <View>{formatDate(new Date(d.estimate_start_time), "MM-dd")} {formatDate(new Date(d.estimate_start_time), "MM-dd")}</View>
{Date.now() > d.estimate_end_time && <View className={styles.overdue}></View>} {
(Date.now() > d.estimate_end_time || d.status > 1) && <View className={styles.overdue}>
{d.status > 1 ? '已结束' : "已过期"}
</View>
}
</View> </View>
<View className={styles.del} onClick={() => del(d.id, index)}></View> <View className={styles.del} onClick={() => del(d.id, index)}></View>
</View>) </View>)

@ -22,7 +22,19 @@
.code { .code {
width: 300rpx; width: 300rpx;
margin: 50rpx auto 70rpx; margin: 40rpx auto;
position: relative;
}
.failure{
width: 180rpx;
height: 180rpx;
position: absolute;
left: 0;
right: 0;
top: 0;
bottom: 0;
margin: auto;
} }
.choice { .choice {
@ -62,6 +74,7 @@
} }
.buttonDel { .buttonDel {
color: #fff;
margin: 30rpx auto; margin: 30rpx auto;
box-shadow: 0 0 10rpx rgba(#c94f4f, .5); box-shadow: 0 0 10rpx rgba(#c94f4f, .5);
} }

@ -8,6 +8,7 @@ import MyButton from "@/components/button/MyButton";
import styles from './spotMeeting.module.scss' import styles from './spotMeeting.module.scss'
import code from '@/static/img/code.png' import code from '@/static/img/code.png'
import Icon from "@/components/icon"; import Icon from "@/components/icon";
import failure from "@/static/img/failure.png"
const SpotMeeting: FC = () => { const SpotMeeting: FC = () => {
const path = encodeURIComponent("/pages/meeting/meeting") const path = encodeURIComponent("/pages/meeting/meeting")
@ -20,46 +21,50 @@ const SpotMeeting: FC = () => {
const [name, setName] = useState('') const [name, setName] = useState('')
const [description, setDescription] = useState('') const [description, setDescription] = useState('')
const [loading, setLoading] = useState(false) const [loading, setLoading] = useState(false)
const [status, setStatus] = useState(0) // 状态
const [id, setId] = useState<number | undefined>(params.id ? Number(params.id) : undefined)
function setData(res: Meeting | null) {
/**
* params.id
* onShow的时候 id === res.id
*/
if (params.id || id !== res?.id) {
setImgUrl('')
setDepid(res?.department_id || null)
setName(res?.name || '')
setDescription(res?.description || '')
setStatus(res?.status || 0)
setId(res?.id || 0)
if (!Array.isArray(res) && res) {
downUrl(res.id, false)
setEnd(formatDate(new Date(res.estimate_end_time), "YY-MM-dd 18:00:00"))
setStart(formatDate(new Date(res.estimate_start_time), "YY-MM-dd 08:00:00"))
}
} else {
setLoading(false)
}
}
useEffect(() => { useEffect(() => {
curriculum.department().then(res => { curriculum.department().then(res => {
setManages(res.data) setManages(res.data)
}) })
}, [])
Taro.useDidShow(() => {
setLoading(true) setLoading(true)
if (params.id != undefined) { if (params.id != undefined) {
meetingAPi.setMeeting(params.id).then(res => { meetingAPi.setMeeting(params.id).then(setData)
if (!Array.isArray(res)) {
setDepid(res.department_id)
setName(res.name)
setDescription(res.description)
setEnd(formatDate(new Date(res.estimate_end_time), "YY-MM-dd 18:00:00"))
setStart(formatDate(new Date(res.estimate_start_time), "YY-MM-dd 08:00:00"))
downUrl(res.id, false)
} else {
setLoading(false)
}
})
} else { } else {
meetingAPi.exists().then(res => { meetingAPi.exists().then(setData)
if (!Array.isArray(res)) {
setDepid(res.department_id)
setName(res.name)
setDescription(res.description)
setEnd(formatDate(new Date(res.estimate_end_time), "YY-MM-dd 18:00:00"))
setStart(formatDate(new Date(res.estimate_start_time), "YY-MM-dd 08:00:00"))
downUrl(res.id, false)
} else {
setLoading(false)
}
})
} }
}, []) })
const change = useCallback((e) => { const depChange = useCallback((e) => {
setDepid(() => manages[Number(e.detail.value)]?.id) setDepid(manages[Number(e.detail.value)]?.id)
}, [manages]) }, [manages])
const change_start = useCallback((time: string) => { const change_start = useCallback((time: string) => {
const mil = new Date(time).getTime() const mil = new Date(time).getTime()
const endMil = new Date(end).getTime() const endMil = new Date(end).getTime()
@ -69,7 +74,6 @@ const SpotMeeting: FC = () => {
setStart(time) setStart(time)
} }
}, [end]) }, [end])
const change_end = useCallback((time: string) => { const change_end = useCallback((time: string) => {
const mil = new Date(time).getTime() const mil = new Date(time).getTime()
const startMil = new Date(start).getTime() const startMil = new Date(start).getTime()
@ -80,6 +84,7 @@ const SpotMeeting: FC = () => {
} }
}, [start]) }, [start])
/** 下载二维码 */
const handleWriteFile = useCallback(() => { const handleWriteFile = useCallback(() => {
if (imgUrl == null) { if (imgUrl == null) {
Taro.showToast({title: '下载失败', icon: 'error'}) Taro.showToast({title: '下载失败', icon: 'error'})
@ -96,10 +101,9 @@ const SpotMeeting: FC = () => {
}) })
}, [imgUrl]) }, [imgUrl])
/** 查看保存权限 */
const handleSaveCode = useCallback(() => { const handleSaveCode = useCallback(() => {
if (process.env.TARO_ENV === 'h5') { if (process.env.TARO_ENV !== 'h5') {
Taro.showToast({title: '长按保存二维码', icon: 'error'})
} else {
getSetting({ getSetting({
success: function ({authSetting}) { success: function ({authSetting}) {
//没有权限则申请 //没有权限则申请
@ -119,6 +123,7 @@ const SpotMeeting: FC = () => {
} }
}, [imgUrl]); }, [imgUrl]);
/** 获取二维码地址 */
const downUrl = (id: number, down = true) => { const downUrl = (id: number, down = true) => {
Taro.showLoading({title: '加载二维码'}) Taro.showLoading({title: '加载二维码'})
const url = process.env.TARO_APP_API + '/official/qrcode?' + 'meeting_id=' + id + '&path=' + path const url = process.env.TARO_APP_API + '/official/qrcode?' + 'meeting_id=' + id + '&path=' + path
@ -135,8 +140,6 @@ const SpotMeeting: FC = () => {
fail() { fail() {
Taro.hideLoading() Taro.hideLoading()
Taro.showModal({title: '二维码生成失败'}) Taro.showModal({title: '二维码生成失败'})
},
complete() {
setLoading(false) setLoading(false)
} }
}) })
@ -147,23 +150,21 @@ const SpotMeeting: FC = () => {
if (down) { if (down) {
handleSaveCode() handleSaveCode()
} }
setLoading(false)
} }
} }
const submit = useCallback(async (e) => {
if (loading && status === 2) return;
async function submit(e) {
if (imgUrl) { // 已有 if (imgUrl) { // 已有
handleSaveCode() handleSaveCode()
return return
} }
const values: Meeting = e.detail.value const values: Meeting = e.detail.value
for (const [key, value] of Object.entries(values)) { if (!values.name) {
if (!value && key !== 'description') { Taro.showToast({title: '请填写会议名称', icon: 'error'})
Taro.showToast({title: '请认真填写', icon: 'error'}) return
return
}
} }
if (!depid) { if (!depid) {
Taro.showToast({title: '请选择部门', icon: 'error'}) Taro.showToast({title: '请选择部门', icon: 'error'})
@ -179,58 +180,83 @@ const SpotMeeting: FC = () => {
department_id: depid department_id: depid
}) })
downUrl(res.id) downUrl(res.id)
setStatus(0)
setId(res.id)
} catch (e) { } catch (e) {
setLoading(false)
} }
setLoading(false) }, [imgUrl, depid, end, start])
}
function jumpMeetings() { function imagLoad() {
Taro.navigateTo({url: '/pages/manage/meetings/meetings'})
}
function imageOnLoad() {
Taro.hideLoading() Taro.hideLoading()
setLoading(false)
} }
useEffect(() => { useEffect(() => {
if (imgUrl) { if (imgUrl) {
setImgUrl('') setImgUrl('')
setStatus(0)
setId(undefined)
} }
}, [start, end, name, depid, description]) }, [start, end, name, depid, description])
/** 开始 */
async function onStart() {
try {
await meetingAPi.start(id!)
setStatus(1)
Taro.showModal({
title: '现场会开始',
content: '扫描二维码可加入'
})
} catch (e) {
}
}
async function onEnd() {
try {
await meetingAPi.end(id!)
setImgUrl('')
setStatus(2)
} catch (e) {
}
}
return ( return (
<View className={styles.page}> <View className={styles.page}>
<View className={styles.box}> <View className={styles.box}>
<View></View> <View></View>
{process.env.TARO_ENV === 'h5' && <View className='text-center text-muted'></View>}
<View className={styles.code}> <View className={styles.code}>
{ {
imgUrl imgUrl
? <Image src={imgUrl} mode='aspectFit' onLoad={imageOnLoad}/> ? <Image src={imgUrl} mode='aspectFit' onLoad={imagLoad} onError={imagLoad} fadeIn/>
: <Image src={code} mode='aspectFit'/> : <Image src={code} mode='aspectFit'/>
} }
{status === 2 && <Image src={failure} className={styles.failure}/>}
</View> </View>
<View className={styles.choice}> <View className={styles.choice}>
<Form onSubmit={submit}> <Form onSubmit={submit}>
<PopPut <PopPut
title='会议标题' title='会议标题'
chevron
content={<Input content={<Input
className='input' className='input'
name='name' name='name'
value={name} value={name}
disabled={!!params.id} disabled={!!params.id || status > 0}
placeholder='请输入会议标题' placeholder='请输入会议标题'
onInput={(e) => setName(e.detail.value)} onInput={(e) => setName(e.detail.value)}
/>} />}
chevron
/> />
<Picker <Picker
mode="date" mode="date"
value={start} value={start}
disabled={!!params.id} disabled={!!params.id || status === 1}
onChange={(e) => change_start(e.detail.value + ' 8:00:00')} onChange={(e) => change_start(e.detail.value + ' 8:00:00')}
name='estimate_start_time'> name='estimate_start_time'>
<PopPut title='开始时间' content={start}/> <PopPut title='开始时间' content={start}/>
@ -238,7 +264,7 @@ const SpotMeeting: FC = () => {
<Picker <Picker
mode="date" mode="date"
value={end} value={end}
disabled={!!params.id} disabled={!!params.id || status === 1}
onChange={(e) => change_end(e.detail.value + ' 18:00:00')} onChange={(e) => change_end(e.detail.value + ' 18:00:00')}
name='estimate_end_time'> name='estimate_end_time'>
<PopPut title='结束时间' content={end}/> <PopPut title='结束时间' content={end}/>
@ -246,8 +272,8 @@ const SpotMeeting: FC = () => {
<Picker <Picker
mode='selector' mode='selector'
range={manages} range={manages}
onChange={change} onChange={depChange}
disabled={!!params.id} disabled={!!params.id || status === 1}
rangeKey='name' rangeKey='name'
name='department_id'> name='department_id'>
<PopPut title='选择部门' content={manages?.find(x => x.id == depid)?.name}/> <PopPut title='选择部门' content={manages?.find(x => x.id == depid)?.name}/>
@ -256,7 +282,7 @@ const SpotMeeting: FC = () => {
<Textarea <Textarea
value={description} value={description}
className='Textarea' className='Textarea'
disabled={!!params.id} disabled={!!params.id || status === 1}
placeholder='请输入描述' placeholder='请输入描述'
maxlength={255} maxlength={255}
name='description' name='description'
@ -264,6 +290,20 @@ const SpotMeeting: FC = () => {
style={{height: '60px'}}> style={{height: '60px'}}>
</Textarea> </Textarea>
{
id !== undefined && <>
{
status === 0 &&
<MyButton fillet type='warn' width={200} className={styles.buttonDel} onClick={onStart}></MyButton>
}
{
status === 1 &&
<MyButton fillet type='warn' width={200} className={styles.buttonDel} onClick={onEnd}></MyButton>
}
</>
}
<MyButton <MyButton
formType='submit' formType='submit'
@ -277,10 +317,15 @@ const SpotMeeting: FC = () => {
</Form> </Form>
</View> </View>
{!params.id && <View className='text-center text-muted my-3' onClick={jumpMeetings}> <Icon {
name='chevron-right'/></View>} !params.id
&& <View
className='text-center text-muted my-3'
onClick={() => Taro.navigateTo({url: '/pages/manage/meetings/meetings'})}>
<Icon name='chevron-right'/>
</View>
}
</View> </View>
</View> </View>
) )
} }

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Loading…
Cancel
Save