Commit 1dd3b96a by zhanghaozhe

ml

parent 9031f1bf
...@@ -24800,6 +24800,16 @@ ...@@ -24800,6 +24800,16 @@
"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz",
"integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68=" "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68="
}, },
"xterm": {
"version": "4.9.0",
"resolved": "https://registry.npmjs.org/xterm/-/xterm-4.9.0.tgz",
"integrity": "sha512-wGfqufmioctKr8VkbRuZbVDfjlXWGZZ1PWHy1yqqpGT3Nm6yaJx8lxDbSEBANtgaiVPTcKSp97sxOy5IlpqYfw=="
},
"xterm-addon-fit": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/xterm-addon-fit/-/xterm-addon-fit-0.4.0.tgz",
"integrity": "sha512-p4BESuV/g2L6pZzFHpeNLLnep9mp/DkF3qrPglMiucSFtD8iJxtMufEoEJbN8LZwB4i+8PFpFvVuFrGOSpW05w=="
},
"y18n": { "y18n": {
"version": "4.0.0", "version": "4.0.0",
"resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz", "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz",
......
...@@ -133,7 +133,10 @@ ...@@ -133,7 +133,10 @@
} }
}, },
"lint-staged": { "lint-staged": {
"*.(js|css|ts[x])": ["prettier --write", "git add ."] "*.(js|css|ts[x])": [
"prettier --write",
"git add ."
]
}, },
"eslintConfig": { "eslintConfig": {
"extends": "react-app", "extends": "react-app",
......
...@@ -17,7 +17,7 @@ import { ...@@ -17,7 +17,7 @@ import {
endFetchNoTrace, endFetchNoTrace,
} from "src/store/no-trace-validation/reducer" } from "src/store/no-trace-validation/reducer"
import { initialState } from "src/store/userReducer" import { initialState } from "src/store/userReducer"
import { withRouter } from "react-router-dom" import { withRouter, Link } from "react-router-dom"
import { compose } from "redux" import { compose } from "redux"
import { getParam, http, browser, loadScript, getTimestamp } from "src/utils" import { getParam, http, browser, loadScript, getTimestamp } from "src/utils"
import { Toast } from "antd-mobile" import { Toast } from "antd-mobile"
......
import React, { Component } from 'react';
import './index.scss'
class Catalog extends Component {
render() {
return (
<div className={'catalog'}>
<div className="icon">
<img src="https://julyedu-cdn.oss-cn-beijing.aliyuncs.com/interactive_tutorial/study/level1-title-icon.png"
alt=""/>
</div>
<div className="title">{this.props.title}</div>
</div>
);
}
}
export default Catalog;
\ No newline at end of file
import React, { Component } from "react"
import "./index.scss"
class Catalog extends Component<{ title: string }> {
render() {
return (
<div className={"catalog"}>
<div className="icon">
<img
src="https://julyedu-cdn.oss-cn-beijing.aliyuncs.com/interactive_tutorial/study/level1-title-icon.png"
alt=""
/>
</div>
<div className="title">{this.props.title}</div>
</div>
)
}
}
export default Catalog
import React, { Component } from 'react'; import React, { Component, ReactNode } from "react"
import './index.scss' import "./index.scss"
class Container extends Component { interface Props {
user?: string
content?: ReactNode
}
class Container extends Component<Props> {
render() { render() {
const {user, content, children} = this.props const { user, content, children } = this.props
return ( return (
<div className={'container'}> <div className={"container"}>
<div className="user"> <div className="user">
{ {user && <img className={"avatar"} src={user} alt="" />}
user && <img className={'avatar'} src={user} alt=""/>
}
</div> </div>
<div className="divide"></div> <div className="divide"></div>
<div className="content">{content ? content : children}</div> <div className="content">{content ? content : children}</div>
</div> </div>
); )
} }
} }
export default Container; export default Container
\ No newline at end of file
import React, { Component } from 'react'; import React, { Component } from "react"
import './index.scss' import "./index.scss"
import Container from './container' import Container from "./container"
import SingleAnswerQuestion from "@components/interactive-tutorial/single-answer-question" import SingleAnswerQuestion from "src/components/interactive-tutorial/single-answer-question"
import Program from "@components/interactive-tutorial/program" import Program from "src/components/interactive-tutorial/program"
import Project from './project' import Project from "./project"
import { getParam, http, getWXObject, isValidHttpUrl } from "@/utils" import { getParam, http, isValidHttpUrl } from "src/utils"
import { Toast } from 'antd-mobile' import { Toast } from "antd-mobile"
import { questionType, status } from './consts' import { questionType, status } from "./consts"
import Catalog from './catalog' import Catalog from "./catalog"
class InteractiveStudy extends Component { class InteractiveStudy extends Component {
container = null container = null
...@@ -21,15 +20,15 @@ class InteractiveStudy extends Component { ...@@ -21,15 +20,15 @@ class InteractiveStudy extends Component {
processContent: [], processContent: [],
processStatus: status.resumePractice, processStatus: status.resumePractice,
isComplete: false, isComplete: false,
avatar: '', avatar: "",
isFirst: true, isFirst: true,
isProgramShowed: 0, isProgramShowed: 0,
videoId: '', videoId: "",
} }
componentDidMount() { componentDidMount() {
this.getSchedule().then(res => { this.getSchedule().then((res) => {
const {code, msg, data} = res.data const { code, msg, data } = res.data
if (code === 200) { if (code === 200) {
const pageInfo = data.page_info const pageInfo = data.page_info
const page = pageInfo.current_page const page = pageInfo.current_page
...@@ -52,7 +51,7 @@ class InteractiveStudy extends Component { ...@@ -52,7 +51,7 @@ class InteractiveStudy extends Component {
getSchedule = () => { getSchedule = () => {
return http.post(`${API.home}/m/it/study/schedule`, { return http.post(`${API.home}/m/it/study/schedule`, {
course_id: getParam('id'), course_id: getParam("id"),
}) })
} }
...@@ -63,71 +62,94 @@ class InteractiveStudy extends Component { ...@@ -63,71 +62,94 @@ class InteractiveStudy extends Component {
} }
resetProcessStatus = () => { resetProcessStatus = () => {
this.setState({ this.setState(
processStatus: status.resumePractice, {
}, () => { processStatus: status.resumePractice,
// console.log(this.state.processStatus) },
}) () => {
// console.log(this.state.processStatus)
}
)
} }
showToast = (msg, type = 'info') => { showToast = (msg, type = "info") => {
Toast[type](msg, 2, null, false) Toast[type](msg, 2, null, false)
} }
getPageContent = (videoId, cb) => { getPageContent = (videoId, cb) => {
const {page, schedule} = this.state const { page, schedule } = this.state
if (page > schedule.page_info.total_pages) { if (page > schedule.page_info.total_pages) {
return return
} }
http.post(`${API.home}/m/it/study/syllabus?page=${this.state.isFirst ? 1 : this.state.page}`, { http
course_id: getParam('id'), .post(
video_id: videoId, `${API.home}/m/it/study/syllabus?page=${
type: this.state.isFirst ? 1 : 2, this.state.isFirst ? 1 : this.state.page
}).then(res => { }`,
const {code, msg, data} = res.data {
if (code === 200) { course_id: getParam("id"),
this.setState(state => { video_id: videoId,
const list = data.syllabus_list type: this.state.isFirst ? 1 : 2,
const progress = state.processContent.length }
? list.findIndex(item => item.syllabus_id === state.processContent[state.processContent.length - 1].syllabus_id) + 1 )
: 0 .then((res) => {
if (state.isFirst) { const { code, msg, data } = res.data
return { if (code === 200) {
processContent: state.processContent.concat(list), this.setState(
isFirst: false, (state) => {
const list = data.syllabus_list
const progress = state.processContent.length
? list.findIndex(
(item) =>
item.syllabus_id ===
state.processContent[state.processContent.length - 1]
.syllabus_id
) + 1
: 0
if (state.isFirst) {
return {
processContent: state.processContent.concat(list),
isFirst: false,
}
}
const cachedList = state.cachedList
.concat(list.slice(progress))
.reverse()
return {
processContent: state.processContent.length
? state.processContent
: [cachedList.pop()],
cachedList: cachedList,
page: state.page + 1,
pageData: data,
progress,
}
},
() => {
cb && cb()
} }
} )
return { } else {
cachedList: state.cachedList.reverse().concat(list.slice(progress)).reverse(), this.showToast(msg)
page: state.page + 1, }
pageData: data, })
progress,
}
}, () => {
cb && cb()
})
} else {
this.showToast(msg)
}
})
} }
saveSchedule = (syllabusId, unitInfoId) => { saveSchedule = (syllabusId, unitInfoId) => {
let data = {syllabus_id: syllabusId} let data = { syllabus_id: syllabusId }
if (unitInfoId) { if (unitInfoId) {
data.unit_info_id = unitInfoId data.unit_info_id = unitInfoId
} }
http.post(`${API.home}/m/it/user/saveSchedule`, data).then(res => { http.post(`${API.home}/m/it/user/saveSchedule`, data).then((res) => {
const {code, msg, data} = res.data const { code, msg } = res.data
if (code === 200) { if (code === 200) {
} else { } else {
this.showToast(msg, 'fail') this.showToast(msg, "fail")
} }
}) })
} }
savePractice = ({syllabus_id, type, answer, result, lines}) => { savePractice = ({ syllabus_id, type, answer, result, lines }) => {
let data = { let data = {
syllabus_id, syllabus_id,
type, type,
...@@ -137,116 +159,161 @@ class InteractiveStudy extends Component { ...@@ -137,116 +159,161 @@ class InteractiveStudy extends Component {
if (type === 2 && lines) { if (type === 2 && lines) {
data.lines = lines data.lines = lines
} }
http.post(`${API.home}/m/it/user/savePractice`, data).then(res => { http.post(`${API.home}/m/it/user/savePractice`, data).then((res) => {
const {code, msg, data} = res.data const { code, msg } = res.data
if (code === 200) { if (code === 200) {
} else { } else {
Toast.fail(msg, 2) Toast.fail(msg, 2)
} }
}) })
} }
process = () => { process = () => {
const {processStatus} = this.state const { processStatus } = this.state
const processContent = this.state.processContent.slice() const processContent = this.state.processContent.slice()
if (processStatus === status.practicingProgram || processStatus === status.practicingSingle) { if (
this.showToast('有其他正在进行的练习') processStatus === status.practicingProgram ||
processStatus === status.practicingSingle
) {
this.showToast("有其他正在进行的练习")
return return
} }
if (processStatus === status.nextSection) { if (processStatus === status.nextSection) {
console.log('下一关') console.log("下一关")
return return
} }
this.setState(state => { this.setState(
let nextQuestion, cachedList = state.cachedList, pageData = state.pageData, processStatus (state) => {
const length = cachedList.length let nextQuestion,
if (length) { cachedList = state.cachedList,
nextQuestion = cachedList.pop() pageData = state.pageData,
if (length < 3) { processStatus
this.getPageContent(state.videoId) const length = cachedList.length
if (length) {
nextQuestion = cachedList.pop()
if (length < 3) {
this.getPageContent(state.videoId)
}
}
if (!cachedList.length) {
return {
processStatus: status.nextSection,
}
}
processStatus =
nextQuestion.type === questionType.singleAnswer
? status.practicingSingle
: nextQuestion.type === questionType.program
? status.practicingProgram
: status.resumePractice
if (!nextQuestion.syllabus_id) {
nextQuestion.syllabus_id =
state.processContent[state.processContent.length - 1].syllabus_id
} }
}
if (!cachedList.length) {
return { return {
processStatus: status.nextSection, processContent: processContent.concat([nextQuestion]),
processStatus,
isProgramShowed:
processStatus === status.practicingProgram &&
state.isProgramShowed + 1,
cachedList,
pageData,
} }
},
() => {
const { processContent } = this.state
const latestItem = processContent[processContent.length - 1]
this.saveSchedule(latestItem.syllabus_id, latestItem.info_id)
this.updatePosition()
} }
processStatus = nextQuestion.type === questionType.singleAnswer )
? status.practicingSingle
: nextQuestion.type === questionType.program
? status.practicingProgram
: status.resumePractice
if (!nextQuestion.syllabus_id) {
nextQuestion.syllabus_id = state.processContent[state.processContent.length - 1].syllabus_id
}
return {
processContent: processContent.concat([nextQuestion]),
processStatus,
isProgramShowed: processStatus === status.practicingProgram && (state.isProgramShowed + 1),
cachedList,
pageData,
}
}, () => {
const {processContent} = this.state
const latestItem = processContent[processContent.length - 1]
this.saveSchedule(latestItem.syllabus_id, latestItem.info_id)
this.updatePosition()
})
} }
addCache = (ques) => { addCache = (ques) => {
this.setState(state => ({ this.setState((state) => ({
cachedList: state.cachedList.concat(ques), cachedList: state.cachedList.concat(ques),
})) }))
} }
render() { render() {
const {processStatus, processContent, avatar, isProgramShowed} = this.state const {
processStatus,
processContent,
avatar,
isProgramShowed,
} = this.state
return ( return (
<div id={'interactive-study'} ref={el => this.container = el}> <div id={"interactive-study"} ref={(el) => (this.container = el)}>
{ {!!processContent.length &&
!!processContent.length && processContent.map(item => { processContent.map((item) => {
if (item.catalogue === 1) { if (item.catalogue === 1) {
/* eslint-disable default-case */
switch (item.type) { switch (item.type) {
case questionType.text: case questionType.text:
return <Container user={avatar} content={item.content} key={item.syllabus_id}/> return (
<Container
user={avatar}
content={item.content}
key={item.syllabus_id}
/>
)
case questionType.image: case questionType.image:
return <Container user={avatar} key={item.syllabus_id}> return (
<img src={item.content} alt=""/> <Container user={avatar} key={item.syllabus_id}>
</Container> <img src={item.content} alt="" />
</Container>
)
case questionType.singleAnswer: case questionType.singleAnswer:
return <SingleAnswerQuestion user={avatar} topic={item.content} question={item} return (
key={item.syllabus_id} <SingleAnswerQuestion
updatePosition={this.updatePosition} user={avatar}
resetStatus={this.resetProcessStatus} saveSchedule={this.saveSchedule} topic={item.content}
addCache={this.addCache} savePractice={this.savePractice}/> question={item}
key={item.syllabus_id}
updatePosition={this.updatePosition}
resetStatus={this.resetProcessStatus}
saveSchedule={this.saveSchedule}
addCache={this.addCache}
savePractice={this.savePractice}
/>
)
case questionType.codeBlock: case questionType.codeBlock:
case questionType.program: case questionType.program:
return <Program question={item} key={item.syllabus_id} isProgramShowed={isProgramShowed}/> return (
<Program
question={item}
key={item.syllabus_id}
isProgramShowed={isProgramShowed}
/>
)
} }
/* eslint-enable default-case */
} else if (item.catalogue === 2) { } else if (item.catalogue === 2) {
return <Catalog title={item.content} key={item.syllabus_id}/> return <Catalog title={item.content} key={item.syllabus_id} />
} else { }
if (item.info_id) { if (item.info_id) {
if (isValidHttpUrl(item.content)) { if (isValidHttpUrl(item.content)) {
return <Container key={item.info_id}><img src={item.content} alt="" return (
onLoad={this.updatePosition}/></Container> <Container key={item.info_id}>
} <img
return <Container key={item.info_id} content={item.content}/> src={item.content}
alt=""
onLoad={this.updatePosition}
/>
</Container>
)
} }
return <Container key={item.syllabus_id}> return <Container key={item.info_id} content={item.content} />
<span className={'subtitle'}>{item.content}</span>
</Container>
} }
}) return (
} <Container key={item.syllabus_id}>
{ <span className={"subtitle"}>{item.content}</span>
processStatus === status.nextSection && <Project user={avatar}/> </Container>
} )
})}
{processStatus === status.nextSection && <Project user={avatar} />}
<div className="status-bar" onClick={this.process}> <div className="status-bar" onClick={this.process}>
{processStatus && <span className={'status'}>{processStatus}</span>} {processStatus && <span className={"status"}>{processStatus}</span>}
{/*<span className={'complete'}>已学完全部课时</span>*/} {/*<span className={'complete'}>已学完全部课时</span>*/}
{/*<div className="free-trial-end">*/} {/*<div className="free-trial-end">*/}
{/* <span>试学体验结束,389.1元学习全部课时</span>*/} {/* <span>试学体验结束,389.1元学习全部课时</span>*/}
...@@ -254,8 +321,8 @@ class InteractiveStudy extends Component { ...@@ -254,8 +321,8 @@ class InteractiveStudy extends Component {
{/*</div>*/} {/*</div>*/}
</div> </div>
</div> </div>
); )
} }
} }
export default InteractiveStudy; export default InteractiveStudy
\ No newline at end of file
body{ body {
background-color: #252529; background-color: #252529;
} }
...@@ -9,6 +9,10 @@ body{ ...@@ -9,6 +9,10 @@ body{
padding-bottom: 49px; padding-bottom: 49px;
padding-top: 20px; padding-top: 20px;
& + div {
display: none;
}
& + .year19-index { & + .year19-index {
display: none; display: none;
} }
...@@ -24,18 +28,19 @@ body{ ...@@ -24,18 +28,19 @@ body{
left: 0; left: 0;
width: 100%; width: 100%;
height: $height; height: $height;
background: #3E3F47; background: #3e3f47;
line-height: $height; line-height: $height;
text-align: center; text-align: center;
color: #CFDBE5; color: #cfdbe5;
z-index: 100;
.complete { .complete {
color: rgba(207, 219, 229, .2); color: rgba(207, 219, 229, 0.2);
font-size: 18px; font-size: 18px;
} }
.status { .status {
color: #CFDBE5; color: #cfdbe5;
font-size: 16px; font-size: 16px;
} }
...@@ -51,10 +56,10 @@ body{ ...@@ -51,10 +56,10 @@ body{
-webkit-appearance: none; -webkit-appearance: none;
border: none; border: none;
outline: 0; outline: 0;
background: #FA5C19; background: #fa5c19;
font-size: 14px; font-size: 14px;
color: #fff; color: #fff;
} }
} }
} }
} }
\ No newline at end of file
import React, { Component } from 'react'; import React, { Component } from "react"
import './index.scss' import "./index.scss"
import Container from '../container' import Container from "../container"
import Terminal from '../terminal' import Terminal from "../terminal"
import AceEditor from 'react-ace' import AceEditor from "react-ace"
import 'ace-builds/src-min-noconflict/theme-dracula' import "ace-builds/src-min-noconflict/theme-dracula"
import 'ace-builds/src-min-noconflict/mode-python' import "ace-builds/src-min-noconflict/mode-python"
import StatusBar from './status-bar' import StatusBar from "./status-bar"
import { http } from "@/utils" import { http } from "src/utils"
import { Toast } from "antd-mobile"; import { Toast } from "antd-mobile"
import { questionType } from '../consts' import { questionType } from "../consts"
/* eslint-disable-next-line no-unused-vars */
const {First, Normal, Pass, Error, InputTip, Skipped, Finished} = StatusBar const { First, Normal, Pass, Error, InputTip, Skipped, Finished } = StatusBar
class Program extends Component { class Program extends Component {
userEditor = null userEditor = null
icon = 'https://julyedu-cdn.oss-cn-beijing.aliyuncs.com/interactive_tutorial/study/program-icon.png' icon =
"https://julyedu-cdn.oss-cn-beijing.aliyuncs.com/interactive_tutorial/study/program-icon.png"
ws = null ws = null
state = { state = {
editorWidth: '', editorWidth: "",
showTerminal: false, showTerminal: false,
code: '', code: "",
answer: '', answer: "",
showAnswer: false, showAnswer: false,
result: '', result: "",
filename: '', filename: "",
executing: false, executing: false,
} }
componentDidMount() { componentDidMount() {
const contentElem = document.querySelector('.container .content') console.log(this.userEditor.session.getLength())
const contentElem = document.querySelector(".container .content")
const contentStyles = window.getComputedStyle(contentElem) const contentStyles = window.getComputedStyle(contentElem)
const contentWidth = contentElem.clientWidth const contentWidth = contentElem.clientWidth
const contentPaddingLeft = parseFloat(contentStyles.getPropertyValue('padding-left')) const contentPaddingLeft = parseFloat(
const {type, code_ques, content, user_answer} = this.props.question contentStyles.getPropertyValue("padding-left")
)
const { type, code_ques, content, user_answer } = this.props.question
let code = content let code = content
let answer = '', result = '', showTerminal = false let answer = "",
result = "",
showTerminal = false
if (type === questionType.program) { if (type === questionType.program) {
code = code_ques.code code = code_ques.code
answer = code_ques.answer answer = code_ques.answer
...@@ -54,13 +61,12 @@ class Program extends Component { ...@@ -54,13 +61,12 @@ class Program extends Component {
}) })
} }
onLoad = editor => { onLoad = (editor) => {
this.userEditor = editor this.userEditor = editor
} }
execute = () => { execute = () => {
// this.getAuthenticationData() this.uploadCode().then((filename) => {
this.uploadCode().then(filename => {
this.setState({ this.setState({
filename, filename,
executing: true, executing: true,
...@@ -70,74 +76,116 @@ class Program extends Component { ...@@ -70,74 +76,116 @@ class Program extends Component {
} }
uploadCode = () => { uploadCode = () => {
return http.post(`${API.home}/web/python/practice/file`, { return http
code: this.userEditor.getValue(), .post(`${API.home}/web/python/practice/file`, {
}).then(res => { code: this.userEditor.getValue(),
const {code, msg, data} = res.data })
if (code === 200) { .then((res) => {
return data.filename const { code, msg, data } = res.data
} else { if (code === 200) {
Toast.fail(msg, 2) return data.filename
} } else {
}) Toast.fail(msg, 2)
}
})
}
uploadPracticeResult = (result) => {
http
.post(`${API}/m/submit/python/practice`, {
syllabus_id: this.props.question.syllabus_id,
type: 2,
answer: this.userEditor.getValue(),
result,
lines: this.userEditor.session.getLength(),
})
.then((res) => {
const { code, msg, data } = res.data
if (code === 200) {
this.handleUploadedResult(data)
} else {
Toast.fail(msg, 2, null, false)
}
})
} }
handleUploadedResult = (data) => {}
render() { render() {
const {editorWidth, showTerminal, code, answer, showAnswer, result, executing, filename} = this.state const {
const {user, question: {type, user_answer, content}, isProgramShowed} = this.props editorWidth,
showTerminal,
code,
answer,
showAnswer,
result,
executing,
filename,
} = this.state
const {
question: { type, user_answer, content },
isProgramShowed,
} = this.props
return ( return (
<> <>
{ {content && type !== questionType.codeBlock && (
content && type !== questionType.codeBlock && <Container content={content}></Container> <Container content={content}></Container>
} )}
<Container <Container
user={this.icon} user={this.icon}
content={ content={
<div className="program"> <div className="program">
<AceEditor <AceEditor
name={'ace-editor'} name={"ace-editor"}
mode={'python'} mode={"python"}
theme={'dracula'} theme={"dracula"}
readOnly={true} readOnly={true}
value={code} value={code}
width={editorWidth} width={editorWidth}
height={'141px'} height={"141px"}
onLoad={this.onLoad} onLoad={this.onLoad}
/> />
{ {showAnswer && (
showAnswer && <AceEditor <AceEditor
name={'ace-editor'} name={"ace-editor"}
mode={'python'} mode={"python"}
theme={'dracula'} theme={"dracula"}
readOnly={true} readOnly={true}
value={answer} value={answer}
width={editorWidth} width={editorWidth}
height={'141px'} height={"141px"}
/>
)}
{type !== questionType.codeBlock && (
<ToolBar
isProgramShowed={isProgramShowed}
userAnswer={user_answer}
execute={this.execute}
/>
)}
{showTerminal && (
<Terminal
result={result}
executing={executing}
filename={filename}
/> />
} )}
{
type !== questionType.codeBlock &&
<ToolBar isProgramShowed={isProgramShowed} userAnswer={user_answer} execute={this.execute}/>
}
{
showTerminal && <Terminal result={result} executing={executing} filename={filename}/>
}
</div> </div>
} }
/> />
</> </>
); )
} }
} }
function ToolBar({isProgramShowed, userAnswer, isSuccessful, execute}) { function ToolBar({ isProgramShowed, userAnswer, isSuccessful, execute }) {
if (userAnswer && userAnswer.is_finish) { if (userAnswer && userAnswer.is_finish) {
if (userAnswer.is_jump) { if (userAnswer.is_jump) {
return <Skipped/> return <Skipped />
} }
return <Finished/> return <Finished />
} }
return isProgramShowed > 1 ? <Normal/> : <First execute={execute}/> return isProgramShowed > 1 ? <Normal /> : <First execute={execute} />
} }
export default Program; export default Program
\ No newline at end of file
...@@ -25,6 +25,14 @@ ...@@ -25,6 +25,14 @@
.ace_marker-layer .ace_active-line { .ace_marker-layer .ace_active-line {
background: rgba(207, 219, 229, .1); background: rgba(207, 219, 229, .1);
} }
.ace_gutter {
background: transparent;
}
.ace_gutter-active-line {
background: rgba(207, 219, 229, .1);
}
} }
.ace_scrollbar { .ace_scrollbar {
...@@ -71,13 +79,8 @@ ...@@ -71,13 +79,8 @@
color: #09f; color: #09f;
} }
.ace-dracula { .ace_dark > .ace_mobile-menu{
.ace_gutter { display: none;
background: transparent;
}
.ace_gutter-active-line {
background: rgba(207, 219, 229, .1);
}
} }
} }
\ No newline at end of file
import React, { Component } from 'react'; import React, { Component } from "react"
import './index.scss' import "./index.scss"
import Container from '../container' import Container from "../container"
import classnames from 'classnames' import classnames from "classnames"
import { html, isValidHttpUrl } from "@/utils" import { html, isValidHttpUrl } from "src/utils"
class SingleAnswerQuestion extends Component { class SingleAnswerQuestion extends Component {
icon =
icon = 'https://julyedu-cdn.oss-cn-beijing.aliyuncs.com/interactive_tutorial/study/single-answer-icon.png' "https://julyedu-cdn.oss-cn-beijing.aliyuncs.com/interactive_tutorial/study/single-answer-icon.png"
state = { state = {
correct: '', correct: "",
wrong: '', wrong: "",
replies: '', replies: "",
selectable: false, selectable: false,
} }
...@@ -20,37 +20,53 @@ class SingleAnswerQuestion extends Component { ...@@ -20,37 +20,53 @@ class SingleAnswerQuestion extends Component {
return return
} }
const {question: {unit_questions, type, syllabus_id}, updatePosition, resetStatus, saveSchedule, addCache, savePractice} = this.props const {
question: { unit_questions, syllabus_id },
updatePosition,
resetStatus,
saveSchedule,
addCache,
savePractice,
} = this.props
const userSelect = unit_questions[index] const userSelect = unit_questions[index]
const isCorrect = userSelect.is_ans const isCorrect = userSelect.is_ans
this.setState({ this.setState(
wrong: isCorrect ? '' : index, {
correct: isCorrect ? index : '', wrong: isCorrect ? "" : index,
replies: info.slice(0, 1), correct: isCorrect ? index : "",
selectable: false, replies: info.slice(0, 1),
}, () => { selectable: false,
updatePosition() },
resetStatus() () => {
addCache(info.slice(1)) updatePosition()
savePractice({ resetStatus()
type: 1, addCache(info.slice(1))
syllabus_id: syllabus_id, savePractice({
answer: unit_questions[index].unit_id, type: 1,
result: isCorrect, syllabus_id: syllabus_id,
}) answer: unit_questions[index].unit_id,
saveSchedule(syllabus_id, info[0].info_id) result: isCorrect,
}) })
saveSchedule(syllabus_id, info[0].info_id)
}
)
} }
componentDidMount() { componentDidMount() {
const {user_answer: userAnswer, unit_questions: options} = this.props.question const {
user_answer: userAnswer,
unit_questions: options,
} = this.props.question
if (userAnswer && userAnswer.answer) { if (userAnswer && userAnswer.answer) {
const userSelectId = userAnswer.answer const userSelectId = userAnswer.answer
const userSelectIndex = options.findIndex(item => item.unit_id == userSelectId) // eslint-disable-next-line eqeqeq
const userSelectIndex = options.findIndex(
(item) => item.unit_id == userSelectId
)
const isUserCorrect = options[userSelectIndex].is_ans const isUserCorrect = options[userSelectIndex].is_ans
this.setState({ this.setState({
correct: isUserCorrect ? userSelectIndex : '', correct: isUserCorrect ? userSelectIndex : "",
wrong: isUserCorrect ? '' : userSelectIndex, wrong: isUserCorrect ? "" : userSelectIndex,
replies: options[userSelectIndex].unit_info, replies: options[userSelectIndex].unit_info,
selectable: false, selectable: false,
}) })
...@@ -61,44 +77,67 @@ class SingleAnswerQuestion extends Component { ...@@ -61,44 +77,67 @@ class SingleAnswerQuestion extends Component {
} }
} }
render() { render() {
const {correct, wrong, replies} = this.state const { correct, wrong, replies } = this.state
const {user, topic, img, question: {unit_questions: options}, updatePosition} = this.props const {
topic,
img,
question: { unit_questions: options },
updatePosition,
} = this.props
return ( return (
<> <>
<Container <Container
user={this.icon} user={this.icon}
content={ content={
<div className={'single-answer-question'}> <div className={"single-answer-question"}>
<div className="topic" dangerouslySetInnerHTML={html(topic)}></div> <div
{img && <img src={img} alt=""/>} className="topic"
dangerouslySetInnerHTML={html(topic)}
></div>
{img && <img src={img} alt="" />}
<ul> <ul>
{ {options &&
options && !!options.length && options.map((item, index) => { !!options.length &&
return <li options.map((item, index) => {
className={classnames(['option', {'correct': index === correct, 'wrong': index === wrong}])} return (
key={index} <li
onClick={this.handleSelect.bind(this, item.unit_info, index)}>{item.des}</li> className={classnames([
}) "option",
} {
correct: index === correct,
wrong: index === wrong,
},
])}
key={index}
onClick={this.handleSelect.bind(
this,
item.unit_info,
index
)}
>
{item.des}
</li>
)
})}
</ul> </ul>
</div> </div>
} }
/> />
{ {!!replies.length &&
!!replies.length && replies.map(item => { replies.map((item) => {
if (isValidHttpUrl(item.content)) { if (isValidHttpUrl(item.content)) {
return <Container key={item.info_id}> return (
<img src={item.content} alt="" onLoad={updatePosition}/> <Container key={item.info_id}>
</Container> <img src={item.content} alt="" onLoad={updatePosition} />
</Container>
)
} }
return <Container content={item.content} key={item.info_id}/> return <Container content={item.content} key={item.info_id} />
}) })}
}
</> </>
); )
} }
} }
export default SingleAnswerQuestion; export default SingleAnswerQuestion
\ No newline at end of file
import React, { Component } from 'react';
import './index.scss'
import { Terminal } from "xterm"
import 'xterm/css/xterm.css'
import { FitAddon } from "xterm-addon-fit";
import { http } from "@/utils"
import { Toast } from "antd-mobile";
import axios from 'axios'
class TerminalInterface extends Component {
terminal = null
termElem = null
quit = '\u0003'
socket = null
state = {
terminalWidth: '',
}
componentDidMount() {
const content = document.querySelector('.container .content')
const contentStyles = window.getComputedStyle(content)
const contentWidth = content.clientWidth
const contentPaddingLeft = parseFloat(contentStyles.getPropertyValue('padding-left'))
this.setState({
terminalWidth: `${contentWidth - contentPaddingLeft}px`,
})
this.terminal = new Terminal()
const fitAddon = new FitAddon()
this.terminal.loadAddon(fitAddon)
this.terminal.open(this.termElem)
const {result, executing} = this.props
setTimeout(() => {
fitAddon.fit()
if (result) {
this.terminal && this.terminal.write(this.props.result)
} else if (executing) {
this.getAuthenticationData()
}
})
}
getAuthenticationData = () => {
// axios.post(`https://fwjizuub9f1tqlcb.julyedu.com`, {
axios.post(`http://47.93.119.175:8888/auth`, {
username: '3guh394h3fhj0f4',
password: 'okqdw029j038hrv3890cv',
}, {
withCredentials: false,
}).then(res => {
const {code, msg, data} = res.data
if (code === 0) {
this.getLinkId(data)
} else {
Toast.fail(msg, 2)
}
})
}
getLinkId = token => {
axios.post(`http://47.93.119.175:8888`, {}, {
headers: {
Token: token,
},
withCredentials: false,
}).then(res => {
const {id} = res.data
this.connectServer(id)
})
}
connectServer = (id) => {
this.socket = new WebSocket(`${API.ws}?id=${id}`)
this.socket.addEventListener('open', () => {
this.socket.send(JSON.stringify({
data: this.props.filename,
}))
})
this.socket.onmessage = event => {
const reader = new FileReader()
reader.onload = () => {
this.terminal.write(this.ab2str(reader.result))
}
reader.readAsArrayBuffer(event.data)
}
this.socket.onerror = (event) => {
console.log(event)
// this.connectServer(id)
}
this.socket.onclose = (event) => {
console.log('closed')
}
}
ab2str = buf => {
return String.fromCharCode.apply(null, new Uint8Array(buf));
}
render() {
const {terminalWidth} = this.state
return (
<div className={'terminal-container'}>
<div className={'terminal'} ref={el => this.termElem = el} style={{width: `${terminalWidth}`}}></div>
</div>
);
}
}
export default TerminalInterface;
\ No newline at end of file
.terminal-container {
.input {
position: relative;
height: 44px;
input {
width: 100%;
height: 100%;
-webkit-appearance: none;
background: transparent;
border: 0;
font-size: 14px;
line-height: 44px;
color: #cfdbe5;
&::placeholder {
font-size: 12px;
}
}
label{
position: absolute;
right: 10px;
top: 7px;
width: 53px;
height: 30px;
}
button{
width: 53px;
height: 30px;
border: 1px solid rgba(0, 153, 255, .8);
border-radius: 4px;
color: #09f;
background: transparent;
outline: 0;
-webkit-appearance: none;
}
}
}
.terminal { .terminal {
height: 141px; height: 141px;
padding: 5px; padding: 5px;
overflow: hidden;
background: #000; background: #000;
} }
\ No newline at end of file
import React, { Component } from "react"
import "./index.scss"
import { Terminal } from "xterm"
import "xterm/css/xterm.css"
import { FitAddon } from "xterm-addon-fit"
import { Toast } from "antd-mobile"
import axios from "axios"
interface Props {
result: string
executing: boolean
filename: string
}
class TerminalInterface extends Component<Props> {
terminal: Terminal | undefined
termElem: HTMLElement | null = null
quit = "\u0003"
socket: WebSocket | null = null
endFlag: string = "wrvgh38v3hc923hcv39sa"
state = {
terminalWidth: "",
userInput: "",
isEnding: false,
}
componentDidMount() {
const content = document.querySelector(".container .content")
const contentStyles = window.getComputedStyle(content!)
const contentWidth = content!.clientWidth
const contentPaddingLeft = parseFloat(
contentStyles.getPropertyValue("padding-left")
)
this.setState({
terminalWidth: `${contentWidth - contentPaddingLeft}px`,
})
this.terminal = new Terminal()
const fitAddon = new FitAddon()
this.terminal.loadAddon(fitAddon)
this.terminal.open(this.termElem!)
const { result, executing } = this.props
setTimeout(() => {
fitAddon.fit()
if (result) {
this.setState({
isEnding: true,
})
this.terminal && this.terminal.write(this.props.result)
} else if (executing) {
this.getAuthenticationData()
}
})
}
getAuthenticationData = () => {
axios
.post(
// `http://47.93.119.175:8888/auth`,
`http://webssh-test.julyedu.com:8888/auth`,
{
username: "3guh394h3fhj0f4",
password: "okqdw029j038hrv3890cv",
},
{
withCredentials: false,
}
)
.then((res) => {
const { code, msg, data } = res.data
if (code === 0) {
this.getLinkId(data)
} else {
Toast.fail(msg, 2)
}
})
}
getLinkId = (token: string) => {
axios
.post(
`http://webssh-test.julyedu.com:8888`,
{},
{
headers: {
Token: token,
},
withCredentials: false,
}
)
.then((res) => {
const { id } = res.data
this.connectServer(id)
})
}
connectServer = (id: string) => {
this.socket = new WebSocket(`${API.ws}?id=${id}`)
this.socket.onopen = () => {
this.sendMessage(this.props.filename)
this.sendMessage("\r")
}
this.socket.onmessage = (event) => {
const reader = new FileReader()
reader.onload = () => {
if (this.ab2str(reader.result).trim() === this.endFlag) {
return this.socket?.close()
}
this.terminal!.write(this.ab2str(reader.result))
}
reader.readAsArrayBuffer(event.data)
}
this.socket.onerror = (event) => {
console.log(event)
// this.connectServer(id)
}
this.socket.onclose = (event) => {
console.log("closed")
}
}
ab2str = (buf: any) => {
return String.fromCharCode.apply(null, new Uint8Array(buf) as any)
}
handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
this.setState({
userInput: e.target.value,
})
}
sendMessage = (message: string) => {
this.socket?.send(
JSON.stringify({
data: message,
})
)
}
render() {
const { terminalWidth, isEnding } = this.state
return (
<div className={"terminal-container"}>
<div
className={"terminal"}
ref={(el) => (this.termElem = el)}
style={{ width: `${terminalWidth}` }}
></div>
{!isEnding && (
<div className="input">
<input
type="text"
id={"terminal-input"}
placeholder={"输入完毕后请点击确认"}
onChange={this.handleChange}
/>
<label htmlFor="terminal-input">
<button
onClick={() => {
this.sendMessage(this.state.userInput)
}}
>
确认
</button>
</label>
</div>
)}
</div>
)
}
}
export default TerminalInterface
...@@ -536,7 +536,7 @@ export default [ ...@@ -536,7 +536,7 @@ export default [
path: "/interactive-tutorial", path: "/interactive-tutorial",
component: loadable(() => component: loadable(() =>
import( import(
/* webpackChunkName: 'interactive-tutorial' */ "@/components/interactive-tutorial" /* webpackChunkName: 'interactive-tutorial' */ "src/components/interactive-tutorial"
) )
), ),
}, },
......
...@@ -115,5 +115,6 @@ export { ...@@ -115,5 +115,6 @@ export {
isValidUrl, isValidUrl,
loadScript, loadScript,
getTimestamp, getTimestamp,
isValidHttpUrl,
} }
export { default as SendMessageToApp } from "./app" export { default as SendMessageToApp } from "./app"
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment