diff --git a/public/react/src/App.css b/public/react/src/App.css index 50bccfb60..cb36f7d63 100644 --- a/public/react/src/App.css +++ b/public/react/src/App.css @@ -63,4 +63,14 @@ html, body { border-left: 1px solid rgb(221, 221, 221); /* 某些情况下,被cm盖住了 */ z-index: 99; +} + + + +/* antd扩展 */ +.formItemInline.ant-form-item { + display: flex; +} +.formItemInline .ant-form-item-control-wrapper { + flex: 1; } \ No newline at end of file diff --git a/public/react/src/common/context/ThemeContext.js b/public/react/src/common/context/ThemeContext.js index db639729e..2a922b58b 100644 --- a/public/react/src/common/context/ThemeContext.js +++ b/public/react/src/common/context/ThemeContext.js @@ -5,7 +5,8 @@ export const themes = { foreground: '#000000', background: '#eeeeee', foreground_select: '#4CACFF', - foreground_tip: '#333' + foreground_orange1: '#FF6800', + foreground_tip: '#333', }, dark: { diff --git a/public/react/src/modules/user/usersInfo/common/HeadlessModal.js b/public/react/src/modules/user/usersInfo/common/HeadlessModal.js new file mode 100644 index 000000000..c58157722 --- /dev/null +++ b/public/react/src/modules/user/usersInfo/common/HeadlessModal.js @@ -0,0 +1,46 @@ +import React, { useState, useEffect, useContext, useRef, memo } from 'react'; +import {Link} from 'react-router-dom'; + +import { getUrl2, isDev, ThemeContext } from 'educoder' +import { Modal } from 'antd' + + +function HeadlessModal (props) { + // const [ visible, setVisible ] = useState(false) + const theme = useContext(ThemeContext); + const { category, visible, setVisible, className, width } = props; + + + useEffect(() => { + + }, []) + + return ( + <Modal + visible={visible} + className={`headless ${className}`} + title={null} + footer={null} + width={width} + > + <style>{` + .headless .ant-modal-close { + display:none; + } + .headless .ant-modal-body { + padding: 0px; + } + .headless .closeBtn { + position: absolute; + top: -15px; + right: -9px; + color: ${theme.foreground_select} + } + `}</style> + <i className="iconfont icon-htmal5icon19 closeBtn" onClick={ () => setVisible(false) }></i> + {props.children} + </Modal> + ) +} + +export default HeadlessModal diff --git a/public/react/src/modules/user/usersInfo/common/InfoTab.js b/public/react/src/modules/user/usersInfo/common/InfoTab.js new file mode 100644 index 000000000..615f84ea5 --- /dev/null +++ b/public/react/src/modules/user/usersInfo/common/InfoTab.js @@ -0,0 +1,32 @@ +import React, { useState, useEffect, useContext, useRef, memo } from 'react'; +import {Link} from 'react-router-dom'; + +import { getUrl2, isDev, ThemeContext } from 'educoder' +import axios from 'axios' + + +function InfoTab (props) { + + const theme = useContext(ThemeContext); + const { category, changeCategory, categories } = props; + const username = props.match.params.username + + useEffect(() => { + + }, []) + + return ( + <div className="white-panel edu-back-white pt25 pb25 clearfix "> + {categories && categories.map(item => { + return ( + <li key={item.key} className={category == item.key ? "active" : ''}><a href="javascript:void(0)" onClick={()=>changeCategory(item.key)}>{item.name}</a></li> + ) + })} + {/* <li className={category ? "" : "active"}><a href="javascript:void(0)" onClick={()=>this.changeCategory()}>全部</a></li> + <li className={category=="manage" ? "active" : ""}><a href="javascript:void(0)" onClick={()=>this.changeCategory("manage")}>{is_current ? "我":"TA"}管理的</a></li> + <li className={category=="study" ? "active" : ""}><a href="javascript:void(0)" onClick={()=>this.changeCategory("study")}>{is_current ? "我":"TA"}学习的</a></li> */} + </div> + ) +} + +export default InfoTab diff --git a/public/react/src/modules/user/usersInfo/video/EditVideoModal.js b/public/react/src/modules/user/usersInfo/video/EditVideoModal.js index 44d21d09c..01b0eb92d 100644 --- a/public/react/src/modules/user/usersInfo/video/EditVideoModal.js +++ b/public/react/src/modules/user/usersInfo/video/EditVideoModal.js @@ -6,10 +6,12 @@ import axios from 'axios' function EditVideoModal (props) { const modalEl = useRef(null); const theme = useContext(ThemeContext); - const { history, id, cover_url, title, created_at, isReview, onEditVideo, visible, setVisible, - form } = props; + const { history, videoId, cover_url, title, created_at, isReview, onEditVideo, visible, setVisible, + form, editSuccess } = props; const getFieldDecorator = form.getFieldDecorator const username = props.match.params.username + const _title = form.getFieldsValue().title; + function toList() { history.push(`/users/${username}/videoes`) } @@ -20,8 +22,17 @@ function EditVideoModal (props) { form.validateFieldsAndScroll((err, values) => { if (!err) { - - + const url = `/users/${username}/videos/${videoId}.json` + axios.put(url, { + title: _title + }).then((response) => { + if (response.data) { + onCancel() + editSuccess() + } + }).catch((e) => { + + }) } else { // $("html").animate({ scrollTop: $('html').scrollTop() - 100 }) } @@ -36,7 +47,11 @@ function EditVideoModal (props) { useEffect(() => { modalEl.current.setVisible(visible) }, [visible]) - const _title = form.getFieldsValue().title; + useEffect(() => { + visible && form.setFieldsValue({ + title, + }) + }, [visible]) return ( <ModalWrapper ref={modalEl} @@ -49,7 +64,7 @@ function EditVideoModal (props) { > <Form.Item label="视频标题" - className="title " + className="title formItemInline" > {getFieldDecorator('title', { diff --git a/public/react/src/modules/user/usersInfo/video/InfosVideo.js b/public/react/src/modules/user/usersInfo/video/InfosVideo.js index ac20d86ff..262d127f4 100644 --- a/public/react/src/modules/user/usersInfo/video/InfosVideo.js +++ b/public/react/src/modules/user/usersInfo/video/InfosVideo.js @@ -1,11 +1,13 @@ import React, { useState, useEffect, useContext, useRef, memo } from 'react'; import {Link} from 'react-router-dom'; - +import { Pagination } from 'antd' import { getUrl2, isDev, ThemeContext } from 'educoder' import axios from 'axios' import VideoInReviewItem from './VideoInReviewItem' import EditVideoModal from './EditVideoModal' import './InfosVideo.css' +import InfoTab from '../common/InfoTab' +import HeadlessModal from '../common/HeadlessModal' function useModal(initValue) { const [visible, setVisible] = useState(initValue) @@ -15,30 +17,59 @@ function useModal(initValue) { setVisible } } +function useCategory(initValue) { + const [category, setCategory] = useState(initValue) + function changeCategory(key) { + setCategory(key) + } + return { + category, + changeCategory + } +} +function usePagination() { + const [page, setPage] = useState(1) + function onPageChange(page) { + setPage(page) + } + return { + current: page, + onChange: onPageChange + } +} +const PAGE_SIZE = 3 +let videoId = {}; -const PAGE_SIZE = 12 function InfoVideo (props) { const [videoes, setVideoes] = useState([]) const [reviewVideoes, setReviewVideoes] = useState([]) + const [count, setCount] = useState(0) const editModalObj = useModal(false) + const videoModalObj = useModal(false) + const categoryObj = useCategory('all') + const pageObj = usePagination() const theme = useContext(ThemeContext); const editModalEl = useRef(null); + const videoEl = useRef(null); const username = props.match.params.username - let cVideo = {}; function fetchVideoes() { const fetchUrl = `/users/${username}/videos.json` axios.get(fetchUrl, { params: { - per_page: PAGE_SIZE + page: pageObj.current, + per_page: PAGE_SIZE, + // sort_by + // sort_direction } }) .then((response) => { if (response.data.videos) { setVideoes(response.data.videos) + setCount(response.data.count) } }).catch(() => { @@ -62,20 +93,68 @@ function InfoVideo (props) { useEffect(() => { fetchVideoes() - fetchReviewVideoes() - }, []) + }, [pageObj.current]) - function onEditVideo(video) { - cVideo = video + useEffect(() => { + if (categoryObj.category == 'all') { + fetchVideoes() + } else { + fetchReviewVideoes() + } + }, [categoryObj.category]) + + useEffect(() => { + if (videoModalObj.visible == false) { + // 关闭视频 + videoEl.current && videoEl.current.pause() + } else { + } + }, [videoModalObj.visible]) + + function editSuccess() { + fetchVideoes() + } + + function onEditVideo(item) { + videoId = { + videoId: item.id, + title: item.title + } editModalObj.setVisible(true) // editModalEl.current.toList(true, video); // this.refs['editVideoModal'].setVisible(true, video); } - + function onMaskClick(item) { + videoId = { + videoId: item.id, + title: item.title, + file_url: item.file_url + } + videoModalObj.setVisible(true) + } return ( <div className="educontent infoVideo"> - <EditVideoModal ref={editModalEl} {...props} {...cVideo} {...editModalObj}></EditVideoModal> + <EditVideoModal {...props} {...editModalObj} + editSuccess={editSuccess} + {...videoId} + ></EditVideoModal> + + <HeadlessModal + {...videoModalObj} + className="showVideoModal" + width={800 - 1} + > + <video + ref="videoEl" + src={videoId.file_url} controls="true" controlslist="nodownload"> + 您的浏览器不支持 video 标签。 + </video> + </HeadlessModal> <style>{` + .showVideoModal video{ + width: 800px; + height: 450px; + } /* item */ .videoPublishSuccess .section { @@ -96,33 +175,69 @@ function InfoVideo (props) { } `}</style> + <InfoTab + {...props} + categories={[{ + key: 'all', + name: '全部视频' + }, { + key: 'review', + name: '待审核视频' + }]} + {...categoryObj} + ></InfoTab> + + <div className="toolbarRow mt20"> + <span> + 共 + <span style={{color: theme.foreground_orange1}}> {count} </span> + 个视频 + </span> - <div className="itemWrap"> - {reviewVideoes.map((item, index) => { + + </div> + + {categoryObj.category == 'all' ? + <div className="itemWrap"> + {videoes.map((item, index) => { return (<VideoInReviewItem {...props} {...item} key={item.id} - isReview={true} + onEditVideo={onEditVideo} + onMaskClick={onMaskClick} + > </VideoInReviewItem>) })} </div> - - gogogo + : <div className="itemWrap"> - {videoes.map((item, index) => { + {reviewVideoes.map((item, index) => { return (<VideoInReviewItem {...props} {...item} key={item.id} - onEditVideo={onEditVideo} + isReview={true} > </VideoInReviewItem>) })} </div> + } + + + + { + categoryObj.category == 'all' && count > PAGE_SIZE && + <div className="mt30 mb50 edu-txt-center"> + <Pagination showQuickJumper total={count} pageSize={PAGE_SIZE} + {...pageObj} + /> + </div> + } + <Link to={`/users/${username}/videoes/upload`}>to upload</Link> </div> ) diff --git a/public/react/src/modules/user/usersInfo/video/VideoInReviewItem.js b/public/react/src/modules/user/usersInfo/video/VideoInReviewItem.js index 7c2af5070..27035c341 100644 --- a/public/react/src/modules/user/usersInfo/video/VideoInReviewItem.js +++ b/public/react/src/modules/user/usersInfo/video/VideoInReviewItem.js @@ -16,7 +16,8 @@ updated_at: "2019-08-12 17:17:09" */ function VideoInReviewItem (props) { const theme = useContext(ThemeContext); - const { history, cover_url, title, created_at, isReview, onEditVideo } = props; + const { history, cover_url, title, created_at, isReview + , onEditVideo, onMaskClick } = props; const username = props.match.params.username function toList() { @@ -30,7 +31,7 @@ function VideoInReviewItem (props) { <img className="cover" src={cover_url || "http://video.educoder.net/e7d18970482a46d2a6f0e951b504256c/snapshots/491e113950d74f1dab276097dae287dd-00005.jpg"} ></img> - {!isReview && <div className="mask"> + {!isReview && <div className="mask" onClick={() => onMaskClick(props)}> <img className="play" src={playIcon}></img> </div>} <div className="square-main">