diff --git a/.merge_file_a17580 b/.merge_file_a17580 new file mode 100644 index 000000000..47e471767 --- /dev/null +++ b/.merge_file_a17580 @@ -0,0 +1,529 @@ +import React, {Component} from 'react'; +import logo from './logo.svg'; +import './App.css'; +import {LocaleProvider} from 'antd' +import zhCN from 'antd/lib/locale-provider/zh_CN'; +import { + BrowserRouter as Router, + Route, + Switch +} from 'react-router-dom'; +import axios from 'axios'; +import '@icedesign/base/dist/ICEDesignBase.css'; + +import '@icedesign/base/index.scss'; + +import LoginDialog from './modules/login/LoginDialog'; +import Notcompletedysl from './modules/user/Notcompletedysl'; +import Trialapplicationysl from './modules/login/Trialapplicationysl'; +import Trialapplicationreview from './modules/user/Trialapplicationreview'; +import Addcourses from "./modules/courses/coursesPublic/Addcourses"; +import AccountProfile from"./modules/user/AccountProfile"; + + +import Trialapplication from './modules/login/Trialapplication' + +import NotFoundPage from './NotFoundPage' + +import Loading from './Loading' + +import Loadable from 'react-loadable'; + + +import moment from 'moment' + +import {MuiThemeProvider, createMuiTheme} from 'material-ui/styles'; + +// import './AppConfig' + +import history from './history'; + +import {SnackbarHOC} from 'educoder' +import {initAxiosInterceptors} from './AppConfig' + + +// !!!tpi需要这个来加载css +import {TPMIndexHOC} from './modules/tpm/TPMIndexHOC'; + + +const theme = createMuiTheme({ + palette: { + primary: { + main: '#4CACFF', + contrastText: 'rgba(255, 255, 255, 0.87)' + }, + secondary: {main: '#4CACFF'}, // #11cb5f This is just green.A700 as hex. + }, +}); +// +// const Trialapplication= Loadable({ +// loader: () =>import('./modules/login/Trialapplication'), +// loading:Loading, +// }) +//登入 +const EducoderLogin = Loadable({ + loader: () => import('./modules/login/EducoderLogin'), + loading: Loading, +}) +const TestIndex = Loadable({ + loader: () => import('./modules/test'), + loading: Loading, +}) + +const IndexWrapperComponent = Loadable({ + loader: () => import('./modules/page/IndexWrapper'), + loading: Loading, +}) + +const CommentComponent = Loadable({ + loader: () => import('./modules/comment/CommentContainer'), + loading: Loading, +}) + +const TestMaterialDesignComponent = Loadable({ + loader: () => import('./modules/test/md/TestMaterialDesign'), + loading: Loading, +}) +const TestCodeMirrorComponent = Loadable({ + loader: () => import('./modules/test/codemirror/TestCodeMirror'), + loading: Loading, +}) + +const TestComponent = Loadable({ + loader: () => import('./modules/test/TestRC'), + loading: Loading, +}) +const TestUrlQueryComponent = Loadable({ + loader: () => import('./modules/test/urlquery/TestUrlQuery'), + loading: Loading, +}) + +const TPMIndexComponent = Loadable({ + loader: () => import('./modules/tpm/TPMIndex'), + loading: Loading, +}) +const TPMShixunsIndexComponent = Loadable({ + loader: () => import('./modules/tpm/shixuns/ShixunsIndex'), + loading: Loading, +}) + +//实训课程(原实训路径) +const ShixunPaths = Loadable({ + loader: () => import('./modules/paths/Index'), + loading: Loading, +}) + +//在线课堂 +const CoursesIndex = Loadable({ + loader: () => import('./modules/courses/Index'), + loading: Loading, +}) +const SearchPage = Loadable({ + loader: () => import('./search/SearchPage'), + loading: Loading, +}) + +//教学案例 +const MoopCases = Loadable({ + loader: () => import('./modules/moop_cases/index'), + loading: Loading, +}) + +// 课堂讨论 +// const BoardIndex = Loadable({ +// loader: () => import('./modules/courses/boards/BoardIndex'), +// loading:Loading, +// }) + +// //课堂普通作业&分组作业 +// const CoursesWorkIndex = Loadable({ +// loader: () => import('./modules/courses/busyWork/Index'), +// loading:Loading, +// }) +// + +// const TPMShixunchildIndexComponent = Loadable({ +// loader: () => import('./modules/tpm/shixunchild/ShixunChildIndex'), +// loading: Loading, +// }) + + +// const TPMshixunfork_listIndexComponent = Loadable({ +// loader: () => import('./modules/tpm/shixunchild/Shixunfork_list'), +// loading: Loading, +// }) + + +const ForumsIndexComponent = Loadable({ + loader: () => import('./modules/forums/ForumsIndex'), + loading: Loading, +}) + +// trustie plus forum +// const TPForumsIndexComponent = Loadable({ +// loader: () => import('./modules/tp-forums/TPForumsIndex'), +// loading: Loading, +// }) + + +// const TestPageComponent = Loadable({ +// loader: () => import('./modules/page/Index'), +// loading: Loading, +// }) + + +//新建实训 +const Newshixuns = Loadable({ + loader: () => import('./modules/tpm/newshixuns/Newshixuns'), + loading: Loading, +}) + + +//实训首页 +const ShixunsHome = Loadable({ + loader: () => import('./modules/home/shixunsHome'), + loading: Loading, +}) + + +const CompatibilityPageLoadable = Loadable({ + loader: () => import('./modules/common/CompatibilityPage'), + loading: Loading, +}) + +//403页面 +const Shixunauthority = Loadable({ + loader: () => import('./modules/403/Shixunauthority'), + loading: Loading, +}) + + +//404页面 +const Shixunnopage = Loadable({ + loader: () => import('./modules/404/Shixunnopage'), + loading: Loading, +}) + +//500页面 +const http500 = Loadable({ + loader: () => import('./modules/500/http500'), + loading: Loading, +}) + +// 登录注册 +const LoginRegisterPage = Loadable({ + loader: () => import('./modules/user/LoginRegisterPage'), + loading: Loading, +}) +const AccountPage = Loadable({ + loader: () => import('./modules/user/AccountPage'), + loading: Loading, +}) + +// 个人主页 +const UsersInfo = Loadable({ + loader: () => import('./modules/user/usersInfo/Infos'), + loading: Loading, +}) + +// 兴趣页面 +const Interestpage = Loadable({ + loader: () => import('./modules/login/EducoderInteresse'), + loading: Loading, +}) + +//众包创新 +const ProjectPackages=Loadable({ + loader: () => import('./modules/projectPackages/ProjectPackageIndex'), + loading: Loading, +}) + +class App extends Component { + constructor(props) { + super(props) + // this.state = { + // isRenders:false, + // } + + } + + componentDidMount() { + // force an update if the URL changes + history.listen(() => { + this.forceUpdate() + const $ = window.$ + // https://www.trustie.net/issues/21919 可能会有问题 + $("html").animate({ scrollTop: $('html').scrollTop() - 0 }) + }); + + initAxiosInterceptors(this.props) + + // + // axios.interceptors.response.use((response) => { + // // console.log("response"+response); + // if(response!=undefined) + // // console.log("response"+response.data.statu); + // if (response&&response.data.status === 407) { + // this.setState({ + // isRenders: true, + // }) + // } + // return response; + // }, (error) => { + // //TODO 这里如果样式变了会出现css不加载的情况 + // }); + } + //修改登录方法 + Modifyloginvalue=()=>{ + this.setState({ + isRender:false, + }) + } + + render() { + + + return ( + + + + + this.Modifyloginvalue()}> + + + + + + {/*{*/} + {/* isRender === true?*/} + {/* : ""*/} + {/*}*/} + + {/*{*/} + {/* isRenders === true?*/} + {/**/} + {/*:""*/} + {/*}*/} + + + + {/**/} + + {/*众包创新*/} + + {/*认证*/} + + + {/*403*/} + + + + + {/*404*/} + + + + + + () + }> + {/**/} + + + + + + + + + + + + {/*列表页*/} + + + {/* + + + */} + + {/**/} + {/**/} + + + {/*实训课程(原实训路径)*/} + + + () + } + > + + {/*课堂*/} + + + {/* 课堂讨论 */} + {/* */} + + {/* + */} + + {/* */} + {/* 兴趣页面*/} + {/**/} + + + + + + + + + {/* 教学案例 */} + () + }/> + + {/* */} + {/*列表页*/} + {/**/} + {/*首页*/} + + + + {/**/} + + + + + + ); + } +} + +// moment国际化,设置为中文 +moment.defineLocale('zh-cn', { + months: '一月_二月_三月_四月_五月_六月_七月_八月_九月_十月_十一月_十二月'.split('_'), + monthsShort: '1月_2月_3月_4月_5月_6月_7月_8月_9月_10月_11月_12月'.split('_'), + weekdays: '星期日_星期一_星期二_星期三_星期四_星期五_星期六'.split('_'), + weekdaysShort: '周日_周一_周二_周三_周四_周五_周六'.split('_'), + weekdaysMin: '日_一_二_三_四_五_六'.split('_'), + longDateFormat: { + LT: 'Ah点mm分', + LTS: 'Ah点m分s秒', + L: 'YYYY-MM-DD', + LL: 'YYYY年MMMD日', + LLL: 'YYYY年MMMD日Ah点mm分', + LLLL: 'YYYY年MMMD日ddddAh点mm分', + l: 'YYYY-MM-DD', + ll: 'YYYY年MMMD日', + lll: 'YYYY年MMMD日Ah点mm分', + llll: 'YYYY年MMMD日ddddAh点mm分' + }, + meridiemParse: /凌晨|早上|上午|中午|下午|晚上/, + meridiemHour: function (hour, meridiem) { + if (hour === 12) { + hour = 0; + } + if (meridiem === '凌晨' || meridiem === '早上' || + meridiem === '上午') { + return hour; + } else if (meridiem === '下午' || meridiem === '晚上') { + return hour + 12; + } else { + // '中午' + return hour >= 11 ? hour : hour + 12; + } + }, + meridiem: function (hour, minute, isLower) { + var hm = hour * 100 + minute; + if (hm < 600) { + return '凌晨'; + } else if (hm < 900) { + return '早上'; + } else if (hm < 1130) { + return '上午'; + } else if (hm < 1230) { + return '中午'; + } else if (hm < 1800) { + return '下午'; + } else { + return '晚上'; + } + }, + calendar: { + sameDay: function () { + return this.minutes() === 0 ? '[今天]Ah[点整]' : '[今天]LT'; + }, + nextDay: function () { + return this.minutes() === 0 ? '[明天]Ah[点整]' : '[明天]LT'; + }, + lastDay: function () { + return this.minutes() === 0 ? '[昨天]Ah[点整]' : '[昨天]LT'; + }, + nextWeek: function () { + var startOfWeek, prefix; + startOfWeek = moment().startOf('week'); + prefix = this.unix() - startOfWeek.unix() >= 7 * 24 * 3600 ? '[下]' : '[本]'; + return this.minutes() === 0 ? prefix + 'dddAh点整' : prefix + 'dddAh点mm'; + }, + lastWeek: function () { + var startOfWeek, prefix; + startOfWeek = moment().startOf('week'); + prefix = this.unix() < startOfWeek.unix() ? '[上]' : '[本]'; + return this.minutes() === 0 ? prefix + 'dddAh点整' : prefix + 'dddAh点mm'; + }, + sameElse: 'LL' + }, + ordinalParse: /\d{1,2}(日|月|周)/, + ordinal: function (number, period) { + switch (period) { + case 'd': + case 'D': + case 'DDD': + return number + '日'; + case 'M': + return number + '月'; + case 'w': + case 'W': + return number + '周'; + default: + return number; + } + }, + relativeTime: { + future: '%s内', + past: '%s前', + s: '几秒', + m: '1分钟', + mm: '%d分钟', + h: '1小时', + hh: '%d小时', + d: '1天', + dd: '%d天', + M: '1个月', + MM: '%d个月', + y: '1年', + yy: '%d年' + }, + week: { + // GB/T 7408-1994《数据元和交换格式·信息交换·日期和时间表示法》与ISO 8601:1988等效 + dow: 1, // Monday is the first day of the week. + doy: 4 // The week that contains Jan 4th is the first week of the year. + } +}); +export default SnackbarHOC()(App); \ No newline at end of file diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 7aa89dbd7..024c7c151 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -190,7 +190,7 @@ class ApplicationController < ActionController::Base def check_account if !current_user.profile_completed? info_url = '/account/profile' - tip_exception(402, info_url) + tip_exception(402, '') end end diff --git a/app/controllers/myshixuns_controller.rb b/app/controllers/myshixuns_controller.rb index 7ae9df5e3..c36ad09b6 100644 --- a/app/controllers/myshixuns_controller.rb +++ b/app/controllers/myshixuns_controller.rb @@ -265,10 +265,10 @@ class MyshixunsController < ApplicationController # params[:evaluate] 实训评测时更新必须给的参数,需要依据该参数做性能统计,其它类型的更新可以跳过 # 自动保存的时候evaluate为0;点评测的时候为1 if params[:evaluate] == 1 - #exec_time = game.challenge.try(:exec_time) + exec_time = game.challenge.try(:exec_time) @sec_key = generate_identifier(EvaluateRecord, 12) record = EvaluateRecord.create!(:user_id => current_user.id, :shixun_id => @myshixun.shixun_id, :game_id => game_id, - :identifier => @sec_key) + :identifier => @sec_key, :exec_time => exec_time) uid_logger("-- game build: file update #{@sec_key}, record id is #{record.id}, time is **** #{Time.now.strftime("%Y-%m-%d %H:%M:%S.%L")}") end unless @hide_code diff --git a/public/react/config/webpack.config.dev.js b/public/react/config/webpack.config.dev.js index 12d5e437f..15e7e6bef 100644 --- a/public/react/config/webpack.config.dev.js +++ b/public/react/config/webpack.config.dev.js @@ -29,7 +29,7 @@ const env = getClientEnvironment(publicUrl); module.exports = { // You may want 'eval' instead if you prefer to see the compiled output in DevTools. // See the discussion in https://github.com/facebookincubator/create-react-app/issues/343.s - devtool: "eval", // 开启调试 + devtool: "source-map", // 开启调试 // These are the "entry points" to our application. // This means they will be the "root" imports that are included in JS bundle. // The first two entry points enable "hot" CSS and auto-refreshes for JS. diff --git a/public/react/src/App.js b/public/react/src/App.js index 46522d2b9..e71ce4a9a 100644 --- a/public/react/src/App.js +++ b/public/react/src/App.js @@ -218,6 +218,12 @@ const UsersInfo = Loadable({ loading: Loading, }) +// 教学案例 +const MoopCases = Loadable({ + loader: () => import('./modules/moop_cases/index'), + loading: Loading, +}) + // 兴趣页面 const Interestpage = Loadable({ loader: () => import('./modules/login/EducoderInteresse'), @@ -351,14 +357,19 @@ class App extends Component { {/*课堂*/} - + {/* + */} + {/* 教学案例 */} + () + }/> + () } > - diff --git a/public/react/src/common/course/ActionBtn.js b/public/react/src/common/course/ActionBtn.js index 03da0d3e7..e5eeb11a4 100644 --- a/public/react/src/common/course/ActionBtn.js +++ b/public/react/src/common/course/ActionBtn.js @@ -1,7 +1,7 @@ import React, { Component } from 'react'; import {Link} from 'react-router-dom' -const map={"blue":"blueFull","greyBack":"greyBack","grey":"greyWidthFixed","green":"greenBack", +const map={"blue":"blueFull","greyBack":"greyBack","grey":"greyWidthFixed","green":"greenBack",'greyLine':"greyLine", 'colorBlue': 'colorBlue', // 蓝字白底 } class ActionBtn extends Component { diff --git a/public/react/src/images/moop_cases/success.png b/public/react/src/images/moop_cases/success.png new file mode 100644 index 000000000..9282ef4d0 Binary files /dev/null and b/public/react/src/images/moop_cases/success.png differ diff --git a/public/react/src/images/moop_cases/teach_ex.jpg b/public/react/src/images/moop_cases/teach_ex.jpg new file mode 100644 index 000000000..eef1f7ce6 Binary files /dev/null and b/public/react/src/images/moop_cases/teach_ex.jpg differ diff --git a/public/react/src/modules/courses/css/Courses.css b/public/react/src/modules/courses/css/Courses.css index 2408e95bb..47db2f595 100644 --- a/public/react/src/modules/courses/css/Courses.css +++ b/public/react/src/modules/courses/css/Courses.css @@ -683,8 +683,9 @@ a.white-btn.use_scope-btn:hover{ } .colorBlue { + padding: 0px 7px; background-color: #fff; - color: #4CACFF; + color: #4CACFF!important; border: 1px solid #4CACFF; } .greyBack{ @@ -701,6 +702,12 @@ a.white-btn.use_scope-btn:hover{ .Actionbtn.middle { padding: 0px 18px; } +.greyLine{ + background: #fff; + border:1px solid #eaeaea; + color: #999!important; + padding:0px 10px; +} .colorFF6800{ diff --git a/public/react/src/modules/forums/MemoDetail.js b/public/react/src/modules/forums/MemoDetail.js index f2d50f9bb..8441a8ac9 100644 --- a/public/react/src/modules/forums/MemoDetail.js +++ b/public/react/src/modules/forums/MemoDetail.js @@ -14,7 +14,7 @@ import moment from 'moment' import Comments from '../comment/Comments' import update from 'immutability-helper' -import Tooltip from 'material-ui/Tooltip'; +// import Tooltip from 'material-ui/Tooltip'; import RewardDialog from '../common/RewardDialog'; import {ImageLayerOfCommentHOC} from '../page/layers/ImageLayerOfCommentHOC' @@ -23,6 +23,7 @@ import MemoDetailKEEditor from './MemoDetailKEEditor' import MemoDetailMDEditor from './MemoDetailMDEditor' import { bytesToSize } from 'educoder' +import { Tooltip } from 'antd' const $ = window.$ function urlStringify(params) { let noParams = true; @@ -691,15 +692,23 @@ class MemoDetail extends Component {
- {memo.subject} - { memo.sticky && 置顶} - { !!memo.reward && - - {memo.reward} - } - -
+ {/* overflowHidden1 */} + {memo.subject + memo.subject} + { memo.sticky && 置顶} + { !!memo.reward && + + + + {memo.reward} + + + } + +
{ _current_user && (_current_user.admin === true || _current_user.user_id === author_info.user_id) &&
diff --git a/public/react/src/modules/forums/MemoList.js b/public/react/src/modules/forums/MemoList.js index 26cad1448..96af68767 100644 --- a/public/react/src/modules/forums/MemoList.js +++ b/public/react/src/modules/forums/MemoList.js @@ -17,16 +17,22 @@ import PostItem from './PostItem' import ForumsNavTab from './ForumsNavTab' -import { queryString } from 'educoder' +import { queryString, ThemeContext } from 'educoder' class MemoList extends Component { render() { const { match, history, currentPage, memo_count ,memo_list, renderMemoList, onPaginationChange } = this.props + let theme = this.context; return (
+
{!memo_list || memo_list.length === 0 ?
@@ -49,5 +55,6 @@ class MemoList extends Component { ); } } +MemoList.contextType = ThemeContext; export default ( MemoList ); diff --git a/public/react/src/modules/forums/PostItem.js b/public/react/src/modules/forums/PostItem.js index 02d9fa273..43b63c3fa 100644 --- a/public/react/src/modules/forums/PostItem.js +++ b/public/react/src/modules/forums/PostItem.js @@ -7,10 +7,12 @@ import PropTypes from 'prop-types'; import classNames from 'classnames'; -import { getImageUrl, toPath } from 'educoder'; +import { getImageUrl, toPath, ThemeContext } from 'educoder'; import moment from 'moment'; +import { Tooltip } from 'antd' + class PostItem extends Component { _toTenThousand(num) { @@ -31,17 +33,19 @@ class PostItem extends Component {

{/* target="_blank" */} - + 46 ? memo.subject : ''} + className="clearfix task-hide item_name fl" style={{maxWidth: '600px'}} > {memo.subject} { memo.sticky && 置顶 } { memo.reward && - - {memo.reward} - + + + {memo.reward} + + }

@@ -101,4 +105,6 @@ class PostItem extends Component { ); } } +PostItem.contextType = ThemeContext; + export default PostItem diff --git a/public/react/src/modules/moop_cases/CaseDetail.js b/public/react/src/modules/moop_cases/CaseDetail.js new file mode 100644 index 000000000..193961240 --- /dev/null +++ b/public/react/src/modules/moop_cases/CaseDetail.js @@ -0,0 +1,165 @@ +import React,{ Component } from "react"; +import './css/moopCases.css' +import '../courses/css/Courses.css' + +import { getImageUrl , MarkdownToHtml , ActionBtn } from 'educoder'; + +import Tags from './CaseTags' + +import axios from 'axios'; +import Modals from '../modals/Modals' + +class CaseDetail extends Component{ + constructor(props){ + super(props); + this.state={ + modalsType:"", + modalsTopval:"", + modalsBottomval:"", + modalCancel:"", + } + } + + componentDidMount =()=>{ + let caseID = this.props.match.params.caseID; + this.props.getDetail(caseID); + } + // 是否删除 + delCases=()=>{ + this.setState({ + modalsType:true, + modalsTopval:"是否确认删除?", + modalsBottomval:"" + }) + } + + // 取消删除 + cancelDelClasses=()=>{ + this.setState({ + modalsType:false, + modalsTopval:"", + modalsBottomval:"" + }) + } + // 确定删除 + sureDelClasses=()=>{ + let caseID = this.props.match.params.caseID; + let url =`/libraries/${caseID}.json`; + axios.delete(url).then((result)=>{ + if(result){ + this.props.showNotification("删除成功"); + this.props.history.push("/moop_cases"); + } + }).catch((error)=>{ + console.log(error); + }) + } + + + render(){ + let { CaseDetail , praise_count , creator , operation , user_praised , tags , attachments }=this.props + let { + modalsType, + modalsTopval, + modalsBottomval, + } = this.state; + return( +
+ + +

+ 教学案例 > { CaseDetail && CaseDetail.title} +

+

+ + { CaseDetail && CaseDetail.title} + + + + { + CaseDetail && CaseDetail.status == "pending" && 草稿 + } + + 返回 +

+
+
+
+ 82274?1563067098 +
+
  • + {creator && creator.name} + { + operation && operation.can_deletable ? 删除:"" + } + + { + operation && operation.can_editable ? 编辑:"" + } +
  • +
  • + {creator && creator.school_name} + + 编码:{CaseDetail && CaseDetail.uuid} + 发布时间:{CaseDetail && CaseDetail.published_at} + +
  • +
    +
    +
    + 作者:{CaseDetail && CaseDetail.author_name}/{CaseDetail && CaseDetail.author_school_name} +
    + +
    + { CaseDetail && CaseDetail.content && } +
    + { attachments && +
    + { + attachments.map((item,key)=>{ + return( +

    + + + + {item.title} + {item.filesize} +

    + ) + }) + } +
    + } +
    + { + user_praised ? +

    + 已赞 + {praise_count} +

    + : +

    this.props.praisePoint(this.props.match.params.caseID)} className="pointsBtn"> + + {praise_count} +

    + } +
    +
    +
    +
    + ) + } +} +export default CaseDetail; \ No newline at end of file diff --git a/public/react/src/modules/moop_cases/CaseItem.js b/public/react/src/modules/moop_cases/CaseItem.js new file mode 100644 index 000000000..a5086133b --- /dev/null +++ b/public/react/src/modules/moop_cases/CaseItem.js @@ -0,0 +1,48 @@ +import React,{ Component } from "react"; +import './css/moopCases.css' +import '../courses/css/Courses.css' + +import { getUrl } from 'educoder'; + +import Tags from './CaseTags' + +class CaseItem extends Component{ + constructor(props){ + super(props); + } + + render(){ + let { libraries } = this.props; + console.log(libraries) + return( +
    + { + libraries && libraries.map((item,key)=>{ + return( +
  • + {item.id} +
    +

    + {item.title} + +

    +

    + {item.author_name} + {item.author_school_name} + + {item.visited_count} 浏览 + {item.praise_count} 赞 + {item.download_count} 下载 + +

    +
    +
  • + ) + }) + } + +
    + ) + } +} +export default CaseItem; \ No newline at end of file diff --git a/public/react/src/modules/moop_cases/CaseList.js b/public/react/src/modules/moop_cases/CaseList.js new file mode 100644 index 000000000..b968cc930 --- /dev/null +++ b/public/react/src/modules/moop_cases/CaseList.js @@ -0,0 +1,134 @@ +import React,{ Component } from "react"; +import { Input , Spin , Pagination } from "antd"; +import './css/moopCases.css' +import '../courses/css/Courses.css' + +import { ActionBtn } from 'educoder'; + +import axios from 'axios' + +import NoneData from '../courses/coursesPublic/NoneData' + +import mainImg from '../../images/moop_cases/teach_ex.jpg' +import CaseItem from './CaseItem' + + +const Search = Input.Search; +class CaseList extends Component{ + constructor(props){ + super(props); + this.state={ + type:0, + search:undefined, + page:1, + pageSize:20, + libraries:undefined, + totalCount:undefined + } + } + + componentDidMount = () =>{ + let { type , search , page , pageSize } = this.state; + this.InitList(type,search,page,pageSize); + } + + // 列表 + InitList = (type,search,page,pageSize) =>{ + let url = `/libraries.json`; + axios.get(url,{params:{ + type:type == 0 ? undefined : "mine", + keyword:search, + page, + per_page:pageSize + }}).then((result)=>{ + if(result){ + this.setState({ + libraries:result.data.libraries, + totalCount:result.data.count + }) + } + }).catch((error)=>{ + console.log(error); + }) + } + + // tab切换 + changeType = (type) =>{ + this.setState({ + type, + page:1 + }) + let { search , page , pageSize } = this.state; + this.InitList(type,search,page,pageSize); + } + + // 输入搜索内容 + inputStudent=(e)=>{ + this.setState({ + search:e.target.value + }) + } + + // 搜索 + searchInfo = () =>{ + let { type , search , pageSize } = this.state; + this.InitList( type , search , 1 , pageSize ); + } + + // 切换分页 + onChangePage =(pageNumber)=>{ + this.setState({ + page:pageNumber + }) + let { type , search , pageSize } = this.state; + this.InitList( type , search , pageNumber , pageSize ); + } + + render(){ + let { type , search ,libraries , totalCount ,pageSize ,page } = this.state; + return( + + +
    +
    +

    + 教学案例 + this.addQuestion(null, Q_TYPE_SINGLE)}>发布案例 +

    +
    +
      +
    • this.changeType(0)}> + 全部 +
    • +
    • this.changeType(1)}> + 我的 +
    • +
    +
    + +
    +
    +
    + { + libraries && libraries.length > 0 && + } + { + libraries && libraries.length == 0 &&
    + } + { + totalCount && totalCount > pageSize && +
    + +
    + } +
    +
    + ) + } +} +export default CaseList \ No newline at end of file diff --git a/public/react/src/modules/moop_cases/CaseNew.js b/public/react/src/modules/moop_cases/CaseNew.js new file mode 100644 index 000000000..e855b5b71 --- /dev/null +++ b/public/react/src/modules/moop_cases/CaseNew.js @@ -0,0 +1,433 @@ +import React,{ Component } from "react"; +import './css/moopCases.css' +import '../courses/css/Courses.css' +import { Form , Input , Upload , Button , Icon , message , Tooltip } from "antd"; + +import { getImageUrl , MarkdownToHtml , ActionBtn , appendFileSizeToUploadFile , appendFileSizeToUploadFileAll , getUrl , getUploadActionUrl } from 'educoder'; + +import Tags from './CaseTags' + +import axios from 'axios'; +import TPMMDEditor from '../tpm/challengesnew/TPMMDEditor'; +import _ from 'lodash' +const { Dragger } = Upload; +function beforeUpload(file) { + const isJpgOrPng = file.type === 'image/jpeg' || file.type === 'image/png'; + if (!isJpgOrPng) { + message.error('You can only upload JPG/PNG file!'); + } + const isLt2M = file.size / 1024 / 1024 < 2; + if (!isLt2M) { + message.error('Image must smaller than 2MB!'); + } + return isJpgOrPng && isLt2M; +} +function getBase64(img, callback) { + const reader = new FileReader(); + reader.addEventListener('load', () => callback(reader.result)); + reader.readAsDataURL(img); +} + const $ = window.$; +class CaseNew extends Component{ + constructor(props){ + super(props); + this.DescMdRef = React.createRef(); + + this.state={ + casesTags:[], + contentFileList:[], + filesID:[], + imageUrl:undefined, + loading: false, + checkTag:false, + checkFile:false, + coverID:undefined + } + } + + // 上传附件-删除确认框 + onAttachmentRemove = (file, stateName) => { + this.props.confirm({ + content: '是否确认删除?', + onOk: () => { + this.deleteAttachment(file, stateName) + }, + onCancel() { + console.log('Cancel'); + }, + }); + return false; + } + + // 上传附件-确认删除 + deleteAttachment = (file, stateName) => { + // 初次上传不能直接取uid + const url = `/attachments/${file.response ? file.response.id : file.uid}.json` + axios.delete(url, { + }).then((response) => { + if (response.data) { + const { status } = response.data; + if (status == 0) { + console.log('--- success') + + this.setState((state) => { + const index = state[stateName].indexOf(file); + const newFileList = state[stateName].slice(); + newFileList.splice(index, 1); + return { + [stateName]: newFileList, + }; + }); + } + } + }) + .catch(function (error) { + console.log(error); + }); + } + // 上传附件-change + handleContentUploadChange = (info) => { + let contentFileList = info.fileList; + this.setState({ contentFileList: appendFileSizeToUploadFileAll(contentFileList)}); + let list = appendFileSizeToUploadFileAll(contentFileList); + let arr = list.map(item=>{ + return ( item.response && item.response.id ) + }) + this.setState({ + filesID:arr, + checkFile:arr.length > 0 ? false : true + }) + console.log("fujian"); + console.log(list.map(item=>{ + return ( item.response && item.response.id ) + })); + } + + // 上传封面图-change + handleChange = (info) => { + if (info.file.status === 'uploading') { + this.setState({ loading: true }); + return; + } + if (info.file.status === 'done') { + // Get this url from response in real world. + getBase64(info.file.originFileObj, imageUrl => + this.setState({ + imageUrl, + loading: false + }), + ); + console.log(info.file); + this.setState({ + coverID:info.file.response && info.file.response.id + }) + } + }; + + // 编辑时加载数据 + componentDidMount=()=>{ + if(this.props.match.params.caseID){ + this.InitEditData(); + } + } + + componentDidUpdate=(prevState)=>{ + if(this.props.CaseDetail && prevState.CaseDetail != this.props.CaseDetail){ + this.props.form.setFieldsValue({ + caseTitle:this.props.CaseDetail.title, + userName:this.props.CaseDetail.author_name, + userUnit:this.props.CaseDetail.author_school_name, + }) + this.setState({ + contentFileList:this.props.CaseDetail.attachments.map(item => { + return { + id: item.id, + uid: item.id, + name: appendFileSizeToUploadFile(item), + url: item.url, + filesize: item.filesize, + status: 'done' + } + }), + coverID:this.props.cover && this.props.cover.id, + imageUrl:this.props.CaseDetail.cover && getImageUrl(this.props.CaseDetail.cover.url), + casesTags:this.props.tags.map(item=>{ + return (item.id); + }) + }) + } + } + + InitEditData=()=>{ + let caseID = this.props.match.params.caseID; + this.props.getDetail(caseID); + } + + // 申请提交和保存 + handleSubmit = (type) => { + let caseID = this.props.match.params.caseID; + console.log(type); + this.props.form.validateFieldsAndScroll((err, values) => { + let { casesTags , filesID } = this.state; + if(casesTags.length == 0){ + $("html").animate({ scrollTop: $("#tagFormItem").offset().top - 100 }); + this.setState({ + checkTag:true + }) + return; + } + if(filesID.length == 0){ + $("html").animate({ scrollTop: $("#fileFormItem").offset().top - 100 }); + this.setState({ + checkFile:true + }) + return; + } + //const mdContnet = this.DescMdRef.current.getValue().trim(); + console.log(values); + let url = caseID ? `/libraries/${caseID}.json`: `/libraries.json`; + if(caseID){ + axios.put((url),{ + title:values.caseTitle, + author_name:values.userName, + author_school_name:values.userUnit, + content:values.description, + attachment_ids:this.state.filesID, + tag_ids:this.state.casesTags, + cover_id:this.state.coverID, + publish:type == 'save' ? false : true + }).then((result)=>{ + if(result){ + this.props.showNotification(type == 'save' ? `案例保存成功!`: `提交成功!`); + this.props.history.push(type == 'save' ? `/moop_cases/${result.data.id}` : `/moop_cases/${result.data.id}/publish_success`); + } + }).catch((error)=>{ + console.log(error); + }) + }else{ + axios.post((url),{ + title:values.caseTitle, + author_name:values.userName, + author_school_name:values.userUnit, + content:values.description, + attachment_ids:this.state.filesID, + tag_ids:this.state.casesTags, + cover_id:this.state.coverID, + publish:type == 'save' ? false : true + }).then((result)=>{ + if(result){ + this.props.showNotification(type == 'save' ? `案例保存成功!`: `提交成功!`); + this.props.history.push(type == 'save' ? `/moop_cases/${result.data.id}` : `/moop_cases/${result.data.id}/publish_success`); + } + }).catch((error)=>{ + console.log(error); + }) + } + + }) + } + + // 选择标签 + changeType=(type)=>{ + let tags = []; + if(this.state.casesTags.indexOf(type) > -1){ + tags = this.state.casesTags.filter(item => item != type); + }else{ + tags = this.state.casesTags.concat(type); + } + const tagUniqed = _.uniq(tags); + this.setState({ + casesTags: tagUniqed, + checkTag:tags.length > 0 ? false : true + }) + } + + render(){ + let { caseID } = this.props.match.params; + let { CaseDetail } = this.props; + let { casesTags , contentFileList , imageUrl , checkTag , checkFile } = this.state; + const {getFieldDecorator} = this.props.form; + + + // 上传附件点击事件 + const uploadProps = { + width: 600, + multiple: true, + fileList:contentFileList, + action: `${getUploadActionUrl()}`, + onChange: this.handleContentUploadChange, + onRemove: (file) => this.onAttachmentRemove(file, 'contentFileList'), + beforeUpload: (file) => { + const isLt150M = file.size / 1024 / 1024 < 150; + if (!isLt150M) { + message.error('文件大小必须小于150MB!'); + } + return isLt150M; + } + }; + // 上传封面图-html + const uploadButton = ( +
    + +
    + ); + // 上传封面图点击事件 + const uploadCover = { + listType:"picture-card", + className:"avatar-uploader", + showUploadList:false, + action:`${getUploadActionUrl()}`, + onChange:this.handleChange, + } + return( +
    + +

    + 教学案例 > { caseID ? "编辑" : "新建" } +

    +

    上传教学案例

    +
    +
    + + {getFieldDecorator('caseTitle', { + rules: [{required: true, message: "案例标题不能为空"}], + })( + + )} + 简明扼要介绍文档/视频所包含的主要的内容 + +
    + + {getFieldDecorator('userName', { + rules: [{required: true, message: "请输入作者姓名"}], + })( + + )} + + + {getFieldDecorator('userUnit', { + rules: [{required: true, message: "请输入作者单位名称"}], + })( + + )} + +
    +
    + 标签 +
      +
    • -1 ? "active" :"" } onClick={()=>this.changeType(1)}>获奖案例
    • +
    • -1 ? "active" :"" } onClick={()=>this.changeType(2)}>入库案例
    • +
    + { + checkTag &&
    请选择标签
    + } +
    + + {getFieldDecorator('description', { + rules: [{ + required: true, message: '请输入描述内容' + }], + })( + + )} + +
    + +

    上传附件

    +

    从我的电脑选择要上传的文档:按住CTRL可以上传多份文档。单个文件最大限制:150MB

    +
    + { + checkFile &&
    请先上传附件
    + } +
    +

    + 封面图(上传尺寸:120*90 px) +

    +
    + + { imageUrl ? + + avatar + + : + + {uploadButton} + + } + +
    +
    +
    +
  • 审核说明
  • +
      +
    • 平台管理员将对每天新上传的文档进行审核,审核通过的文档将公开显示,否则将私有化或移除
    • +
    +
    +
    +
  • 温馨提示
  • +
      +
    • 1.请勿上传已设置加密口令的文档资源;
    • +
    • 2.可以上传符合教学案例标准的文档资料,如 + 案例入库标准、 + 案例使用说明书以及其他资料等,上传支持的文件最大容量:100MB;
    • +
    • 3.请确保上传内容无侵权或违反国家关于互联网政策的不良行为;
    • +
    • 4.请使用Chrome,Firefox,Safari,IE11(及以上版本)浏览器;
    • +
    +
    + +
    + { + !caseID && + } + this.handleSubmit("save")}>保存 +
    +
    + +
    + ) + } +} +const WrappedCoursesNewApp = Form.create({name: 'CaseNew'})(CaseNew); +export default WrappedCoursesNewApp; \ No newline at end of file diff --git a/public/react/src/modules/moop_cases/CaseTags.js b/public/react/src/modules/moop_cases/CaseTags.js new file mode 100644 index 000000000..4b4f0670c --- /dev/null +++ b/public/react/src/modules/moop_cases/CaseTags.js @@ -0,0 +1,26 @@ +import React,{ Component } from "react"; +import './css/moopCases.css' +import '../courses/css/Courses.css' + +class CaseTags extends Component{ + constructor(props){ + super(props); + } + + render(){ + let { tags } = this.props; + return( + + { + tags && tags.map((item,key)=>{ + return( + {item.name} + ) + }) + } + + + ) + } +} +export default CaseTags; \ No newline at end of file diff --git a/public/react/src/modules/moop_cases/Success.js b/public/react/src/modules/moop_cases/Success.js new file mode 100644 index 000000000..d612758c8 --- /dev/null +++ b/public/react/src/modules/moop_cases/Success.js @@ -0,0 +1,28 @@ +import React,{ Component } from "react"; +import './css/moopCases.css' +import '../courses/css/Courses.css' +import { getImageUrl } from 'educoder'; +import success from '../../images/moop_cases/success.png' + +class Success extends Component{ + constructor(props){ + super(props); + } + render(){ + return( + + ) + } +} +export default Success; \ No newline at end of file diff --git a/public/react/src/modules/moop_cases/css/moopCases.css b/public/react/src/modules/moop_cases/css/moopCases.css new file mode 100644 index 000000000..01668b637 --- /dev/null +++ b/public/react/src/modules/moop_cases/css/moopCases.css @@ -0,0 +1,155 @@ +.winput-300-35{ + width: 300px; + height: 35px; + padding: 5px; + box-sizing: border-box; +} +.library_nav li { + float: left; + cursor: pointer; + margin-right: 30px; + position: relative; + color: #05101A; + height: 40px; + line-height: 20px; + font-size: 16px; +} +.library_nav li.active a, .library_nav li:hover a{ + color: #4cacff!important; +} +.library_list { + margin-bottom: 30px; +} +.library_list_item:hover { + box-shadow: 0px 4px 8px rgba(158,158,158,0.16); +} +.library_list_item { + background: #fff; + padding: 20px 30px; + margin-bottom: 30px; + display: flex; +} +.library_list_item .library_l_name { + max-width: 600px; + float: left; +} + +.edu-activity-red { + background-color: #FC2B6A; + color: #fff!important; + cursor: pointer; + border: 1px solid #FC2B6A; + line-height: 17px; +} +.edu-activity-green { + background-color: green; + color: #fff!important; + cursor: pointer; + border: 1px solid green; + line-height: 17px; +} +.edu-activity-blue { + background-color: #4CACFF; + color: #fff!important; + cursor: pointer; + border: 1px solid #4CACFF; + line-height: 17px; +} + +.pointsBtn { + width: 70px; + height: 70px; + background-color: #4cacff; + border-radius: 50%; + color: #fff; + text-align: center; + margin: 0 auto; + -webkit-box-sizing: border-box; + box-sizing: border-box; + padding: 2px 0; + cursor: pointer; + line-height: 22px; + padding-top: 12px; +} +.pointedBtn{ + background: #BCD1E3; + cursor: default +} +.pointsBtn span{ + display: block; +} +.upload_Title { + position: relative; + margin-right: 20px; + float: left; + line-height: 35px; + font-size: 16px; + width: 56px; + color:rgba(0, 0, 0, 0.85); + text-align: center +} +.upload_Title.must:before { + display: inline-block; + margin-right: 4px; + color: #f5222d; + font-size: 14px; + font-family: SimSun, sans-serif; + line-height: 1; + content: '*'; +} +.upload_Title:after{ + content: ':'; + position: relative; + top: -0.5px; + margin: 0 8px 0 2px; +} +.libraries_tab li { + width: 120px; + height: 35px; + line-height: 33px; + border-radius: 18px; + border: 1px solid #4C98FF; + color: #4C98FF; + cursor: pointer; + margin-right: 30px; + float: left; + text-align: center; +} +.libraries_tab li.active { + background: #4C98FF; + color: #fff; +} +.librariesField .ant-upload{ + width: 100%; + background: #F2F9FF; + justify-content: center; + align-items: center; + display: -webkit-flex; + text-align: center; + height: 120px!important; + border-radius: 4px; + border: 1px dashed #4cacff!important; + display: block; + cursor: pointer; +} +.uploadImage .ant-upload.ant-upload-select-picture-card{ + width:120px; + height: 90px; +} +.uploadImage .ant-upload.ant-upload-select-picture-card > .ant-upload{ + padding:0px!important; +} +.successPage { + justify-content: center; + align-items: center; + display: -webkit-flex; + height: 570px; + text-align: center; + margin-bottom: 50px; +} +.changebtn { + width:166px; + font-size: 16px; + height: 45px; + line-height: 45px; +} \ No newline at end of file diff --git a/public/react/src/modules/moop_cases/index.js b/public/react/src/modules/moop_cases/index.js new file mode 100644 index 000000000..cc8ae68b9 --- /dev/null +++ b/public/react/src/modules/moop_cases/index.js @@ -0,0 +1,130 @@ +import React,{ Component } from "react"; +import './css/moopCases.css' +import '../courses/css/Courses.css' + +import { SnackbarHOC } from 'educoder'; + +import { TPMIndexHOC } from '../tpm/TPMIndexHOC'; +import { CNotificationHOC } from '../courses/common/CNotificationHOC' + +import {BrowserRouter as Router,Route,Switch} from 'react-router-dom'; +import Loading from '../../Loading'; +import Loadable from 'react-loadable'; + +import axios from 'axios'; + +const CaseList = Loadable({ + loader: () => import('./CaseList'), + loading:Loading, +}) +const CaseDetail = Loadable({ + loader: () => import('./CaseDetail'), + loading:Loading, +}) +const CaseNew = Loadable({ + loader: () => import('./CaseNew'), + loading:Loading, +}) +const CaseSuccess = Loadable({ + loader: () => import('./Success'), + loading:Loading, +}) + + + +class Index extends Component{ + constructor(props){ + super(props); + this.state={ + praise_count:0, + CaseDetail:undefined, + cover:undefined, + creator:undefined, + operation:undefined, + tags:undefined, + attachments:undefined, + user_praised:true, + } + } + // 获取案例详情 + getDetail = (caseID) =>{ + let url=`/libraries/${caseID}.json` + axios.get(url).then((result)=>{ + if(result){ + this.setState({ + CaseDetail:result.data, + praise_count:result.data.praise_count, + cover:result.data.cover, + creator:result.data.creator, + operation:result.data.operation, + user_praised:result.data.operation.user_praised, + tags:result.data.tags, + attachments:result.data.attachments + }) + } + }).catch((error)=>{ + console.log(error); + }) + } + // 点赞 + praisePoint=(caseID)=>{ + let { praise_count }=this.state; + let url =`/praise_tread/like.json`; + axios.post(url,{ + object_id:caseID, + object_type:"library" + }).then((result)=>{ + if(result){ + this.setState({ + praise_count: parseInt(praise_count)+1, + user_praised:true + }) + } + }).catch((error)=>{ + console.log(error); + }) + } + + render(){ + + return( +
    + + + () + } + > + + () + } + > + + () + } + > + + + () + } + > + + () + } + > + + +
    + ) + } +} +export default CNotificationHOC() ( SnackbarHOC() ( TPMIndexHOC(Index) )); \ No newline at end of file