Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
M
mr-julyedu
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
baiguangyao
mr-julyedu
Commits
1dd3b96a
Commit
1dd3b96a
authored
Sep 17, 2020
by
zhanghaozhe
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
ml
parent
9031f1bf
Hide whitespace changes
Inline
Side-by-side
Showing
16 changed files
with
694 additions
and
421 deletions
+694
-421
package-lock.json
+10
-0
package.json
+4
-1
src/App.js
+1
-1
src/components/interactive-tutorial/catalog/index.js
+0
-19
src/components/interactive-tutorial/catalog/index.tsx
+20
-0
src/components/interactive-tutorial/container/index.tsx
+13
-11
src/components/interactive-tutorial/index.js
+199
-133
src/components/interactive-tutorial/index.scss
+12
-8
src/components/interactive-tutorial/program/index.js
+113
-66
src/components/interactive-tutorial/program/index.scss
+12
-8
src/components/interactive-tutorial/single-answer-question/index.js
+95
-57
src/components/interactive-tutorial/terminal/index.js
+0
-114
src/components/interactive-tutorial/terminal/index.scss
+38
-2
src/components/interactive-tutorial/terminal/index.tsx
+175
-0
src/router/router-config.js
+1
-1
src/utils/index.js
+1
-0
No files found.
package-lock.json
View file @
1dd3b96a
...
...
@@ -24800,6 +24800,16 @@
"resolved"
:
"https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz"
,
"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"
:
{
"version"
:
"4.0.0"
,
"resolved"
:
"https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz"
,
...
...
package.json
View file @
1dd3b96a
...
...
@@ -133,7 +133,10 @@
}
},
"lint-staged"
:
{
"*.(js|css|ts[x])"
:
[
"prettier --write"
,
"git add ."
]
"*.(js|css|ts[x])"
:
[
"prettier --write"
,
"git add ."
]
},
"eslintConfig"
:
{
"extends"
:
"react-app"
,
...
...
src/App.js
View file @
1dd3b96a
...
...
@@ -17,7 +17,7 @@ import {
endFetchNoTrace
,
}
from
"src/store/no-trace-validation/reducer"
import
{
initialState
}
from
"src/store/userReducer"
import
{
withRouter
}
from
"react-router-dom"
import
{
withRouter
,
Link
}
from
"react-router-dom"
import
{
compose
}
from
"redux"
import
{
getParam
,
http
,
browser
,
loadScript
,
getTimestamp
}
from
"src/utils"
import
{
Toast
}
from
"antd-mobile"
...
...
src/components/interactive-tutorial/catalog/index.js
deleted
100644 → 0
View file @
9031f1bf
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
src/components/interactive-tutorial/catalog/index.tsx
0 → 100644
View file @
1dd3b96a
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
src/components/interactive-tutorial/container/index.
js
→
src/components/interactive-tutorial/container/index.
tsx
View file @
1dd3b96a
import
React
,
{
Component
}
from
'react'
;
import
'./index.scss'
import
React
,
{
Component
,
ReactNode
}
from
"react"
import
"./index.scss"
class
Container
extends
Component
{
interface
Props
{
user
?:
string
content
?:
ReactNode
}
class
Container
extends
Component
<
Props
>
{
render
()
{
const
{
user
,
content
,
children
}
=
this
.
props
const
{
user
,
content
,
children
}
=
this
.
props
return
(
<
div
className
=
{
'container'
}
>
<
div
className=
{
"container"
}
>
<
div
className=
"user"
>
{
user
&&
<
img
className
=
{
'avatar'
}
src
=
{
user
}
alt
=
""
/>
}
{
user
&&
<
img
className=
{
"avatar"
}
src=
{
user
}
alt=
""
/>
}
</
div
>
<
div
className=
"divide"
></
div
>
<
div
className=
"content"
>
{
content
?
content
:
children
}
</
div
>
</
div
>
)
;
)
}
}
export
default
Container
;
\ No newline at end of file
export
default
Container
src/components/interactive-tutorial/index.js
View file @
1dd3b96a
import
React
,
{
Component
}
from
'react'
;
import
'./index.scss'
import
Container
from
'./container'
import
SingleAnswerQuestion
from
"@components/interactive-tutorial/single-answer-question"
import
Program
from
"@components/interactive-tutorial/program"
import
Project
from
'./project'
import
{
getParam
,
http
,
getWXObject
,
isValidHttpUrl
}
from
"@/utils"
import
{
Toast
}
from
'antd-mobile'
import
{
questionType
,
status
}
from
'./consts'
import
Catalog
from
'./catalog'
import
React
,
{
Component
}
from
"react"
import
"./index.scss"
import
Container
from
"./container"
import
SingleAnswerQuestion
from
"src/components/interactive-tutorial/single-answer-question"
import
Program
from
"src/components/interactive-tutorial/program"
import
Project
from
"./project"
import
{
getParam
,
http
,
isValidHttpUrl
}
from
"src/utils"
import
{
Toast
}
from
"antd-mobile"
import
{
questionType
,
status
}
from
"./consts"
import
Catalog
from
"./catalog"
class
InteractiveStudy
extends
Component
{
container
=
null
...
...
@@ -21,15 +20,15 @@ class InteractiveStudy extends Component {
processContent
:
[],
processStatus
:
status
.
resumePractice
,
isComplete
:
false
,
avatar
:
''
,
avatar
:
""
,
isFirst
:
true
,
isProgramShowed
:
0
,
videoId
:
''
,
videoId
:
""
,
}
componentDidMount
()
{
this
.
getSchedule
().
then
(
res
=>
{
const
{
code
,
msg
,
data
}
=
res
.
data
this
.
getSchedule
().
then
(
(
res
)
=>
{
const
{
code
,
msg
,
data
}
=
res
.
data
if
(
code
===
200
)
{
const
pageInfo
=
data
.
page_info
const
page
=
pageInfo
.
current_page
...
...
@@ -52,7 +51,7 @@ class InteractiveStudy extends Component {
getSchedule
=
()
=>
{
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 {
}
resetProcessStatus
=
()
=>
{
this
.
setState
({
processStatus
:
status
.
resumePractice
,
},
()
=>
{
// console.log(this.state.processStatus)
})
this
.
setState
(
{
processStatus
:
status
.
resumePractice
,
},
()
=>
{
// console.log(this.state.processStatus)
}
)
}
showToast
=
(
msg
,
type
=
'info'
)
=>
{
showToast
=
(
msg
,
type
=
"info"
)
=>
{
Toast
[
type
](
msg
,
2
,
null
,
false
)
}
getPageContent
=
(
videoId
,
cb
)
=>
{
const
{
page
,
schedule
}
=
this
.
state
const
{
page
,
schedule
}
=
this
.
state
if
(
page
>
schedule
.
page_info
.
total_pages
)
{
return
}
http
.
post
(
`
${
API
.
home
}
/m/it/study/syllabus?page=
${
this
.
state
.
isFirst
?
1
:
this
.
state
.
page
}
`
,
{
course_id
:
getParam
(
'id'
),
video_id
:
videoId
,
type
:
this
.
state
.
isFirst
?
1
:
2
,
}).
then
(
res
=>
{
const
{
code
,
msg
,
data
}
=
res
.
data
if
(
code
===
200
)
{
this
.
setState
(
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
,
http
.
post
(
`
${
API
.
home
}
/m/it/study/syllabus?page=
${
this
.
state
.
isFirst
?
1
:
this
.
state
.
page
}
`
,
{
course_id
:
getParam
(
"id"
),
video_id
:
videoId
,
type
:
this
.
state
.
isFirst
?
1
:
2
,
}
)
.
then
((
res
)
=>
{
const
{
code
,
msg
,
data
}
=
res
.
data
if
(
code
===
200
)
{
this
.
setState
(
(
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
{
cachedList
:
state
.
cachedList
.
reverse
().
concat
(
list
.
slice
(
progress
)).
reverse
(),
page
:
state
.
page
+
1
,
pageData
:
data
,
progress
,
}
},
()
=>
{
cb
&&
cb
()
})
}
else
{
this
.
showToast
(
msg
)
}
})
)
}
else
{
this
.
showToast
(
msg
)
}
})
}
saveSchedule
=
(
syllabusId
,
unitInfoId
)
=>
{
let
data
=
{
syllabus_id
:
syllabusId
}
let
data
=
{
syllabus_id
:
syllabusId
}
if
(
unitInfoId
)
{
data
.
unit_info_id
=
unitInfoId
}
http
.
post
(
`
${
API
.
home
}
/m/it/user/saveSchedule`
,
data
).
then
(
res
=>
{
const
{
code
,
msg
,
data
}
=
res
.
data
http
.
post
(
`
${
API
.
home
}
/m/it/user/saveSchedule`
,
data
).
then
(
(
res
)
=>
{
const
{
code
,
msg
}
=
res
.
data
if
(
code
===
200
)
{
}
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
=
{
syllabus_id
,
type
,
...
...
@@ -137,116 +159,161 @@ class InteractiveStudy extends Component {
if
(
type
===
2
&&
lines
)
{
data
.
lines
=
lines
}
http
.
post
(
`
${
API
.
home
}
/m/it/user/savePractice`
,
data
).
then
(
res
=>
{
const
{
code
,
msg
,
data
}
=
res
.
data
http
.
post
(
`
${
API
.
home
}
/m/it/user/savePractice`
,
data
).
then
(
(
res
)
=>
{
const
{
code
,
msg
}
=
res
.
data
if
(
code
===
200
)
{
}
else
{
Toast
.
fail
(
msg
,
2
)
}
})
}
process
=
()
=>
{
const
{
processStatus
}
=
this
.
state
const
{
processStatus
}
=
this
.
state
const
processContent
=
this
.
state
.
processContent
.
slice
()
if
(
processStatus
===
status
.
practicingProgram
||
processStatus
===
status
.
practicingSingle
)
{
this
.
showToast
(
'有其他正在进行的练习'
)
if
(
processStatus
===
status
.
practicingProgram
||
processStatus
===
status
.
practicingSingle
)
{
this
.
showToast
(
"有其他正在进行的练习"
)
return
}
if
(
processStatus
===
status
.
nextSection
)
{
console
.
log
(
'下一关'
)
console
.
log
(
"下一关"
)
return
}
this
.
setState
(
state
=>
{
let
nextQuestion
,
cachedList
=
state
.
cachedList
,
pageData
=
state
.
pageData
,
processStatus
const
length
=
cachedList
.
length
if
(
length
)
{
nextQuestion
=
cachedList
.
pop
()
if
(
length
<
3
)
{
this
.
getPageContent
(
state
.
videoId
)
this
.
setState
(
(
state
)
=>
{
let
nextQuestion
,
cachedList
=
state
.
cachedList
,
pageData
=
state
.
pageData
,
processStatus
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
{
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
)
=>
{
this
.
setState
(
state
=>
({
this
.
setState
(
(
state
)
=>
({
cachedList
:
state
.
cachedList
.
concat
(
ques
),
}))
}
render
()
{
const
{
processStatus
,
processContent
,
avatar
,
isProgramShowed
}
=
this
.
state
const
{
processStatus
,
processContent
,
avatar
,
isProgramShowed
,
}
=
this
.
state
return
(
<
div
id
=
{
'interactive-study'
}
ref
=
{
el
=>
this
.
container
=
el
}
>
{
!!
processContent
.
length
&&
processContent
.
map
(
item
=>
{
<
div
id
=
{
"interactive-study"
}
ref
=
{(
el
)
=>
(
this
.
container
=
el
)
}
>
{
!!
processContent
.
length
&&
processContent
.
map
((
item
)
=>
{
if
(
item
.
catalogue
===
1
)
{
/* eslint-disable default-case */
switch
(
item
.
type
)
{
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
:
return
<
Container
user
=
{
avatar
}
key
=
{
item
.
syllabus_id
}
>
<
img
src
=
{
item
.
content
}
alt
=
""
/>
<
/Container
>
return
(
<
Container
user
=
{
avatar
}
key
=
{
item
.
syllabus_id
}
>
<
img
src
=
{
item
.
content
}
alt
=
""
/>
<
/Container
>
)
case
questionType
.
singleAnswer
:
return
<
SingleAnswerQuestion
user
=
{
avatar
}
topic
=
{
item
.
content
}
question
=
{
item
}
key
=
{
item
.
syllabus_id
}
updatePosition
=
{
this
.
updatePosition
}
resetStatus
=
{
this
.
resetProcessStatus
}
saveSchedule
=
{
this
.
saveSchedule
}
addCache
=
{
this
.
addCache
}
savePractice
=
{
this
.
savePractice
}
/
>
return
(
<
SingleAnswerQuestion
user
=
{
avatar
}
topic
=
{
item
.
content
}
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
.
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
)
{
return
<
Catalog
title
=
{
item
.
content
}
key
=
{
item
.
syllabus_id
}
/
>
}
else
{
if
(
item
.
info_id
)
{
if
(
isValidHttpUrl
(
item
.
content
))
{
return
<
Container
key
=
{
item
.
info_id
}
><
img
src
=
{
item
.
content
}
alt
=
""
onLoad
=
{
this
.
updatePosition
}
/></
Container
>
}
return
<
Container
key
=
{
item
.
info_id
}
content
=
{
item
.
content
}
/
>
return
<
Catalog
title
=
{
item
.
content
}
key
=
{
item
.
syllabus_id
}
/
>
}
if
(
item
.
info_id
)
{
if
(
isValidHttpUrl
(
item
.
content
))
{
return
(
<
Container
key
=
{
item
.
info_id
}
>
<
img
src
=
{
item
.
content
}
alt
=
""
onLoad
=
{
this
.
updatePosition
}
/
>
<
/Container
>
)
}
return
<
Container
key
=
{
item
.
syllabus_id
}
>
<
span
className
=
{
'subtitle'
}
>
{
item
.
content
}
<
/span
>
<
/Container
>
return
<
Container
key
=
{
item
.
info_id
}
content
=
{
item
.
content
}
/
>
}
})
}
{
processStatus
===
status
.
nextSection
&&
<
Project
user
=
{
avatar
}
/
>
}
return
(
<
Container
key
=
{
item
.
syllabus_id
}
>
<
span
className
=
{
"subtitle"
}
>
{
item
.
content
}
<
/span
>
<
/Container
>
)
})}
{
processStatus
===
status
.
nextSection
&&
<
Project
user
=
{
avatar
}
/>
}
<
div
className
=
"status-bar"
onClick
=
{
this
.
process
}
>
{
processStatus
&&
<
span
className
=
{
'status'
}
>
{
processStatus
}
<
/span>
}
{
processStatus
&&
<
span
className
=
{
"status"
}
>
{
processStatus
}
<
/span>
}
{
/*<span className={'complete'}>已学完全部课时</span>*/
}
{
/*<div className="free-trial-end">*/
}
{
/* <span>试学体验结束,389.1元学习全部课时</span>*/
}
...
...
@@ -254,8 +321,8 @@ class InteractiveStudy extends Component {
{
/*</div>*/
}
<
/div
>
<
/div
>
)
;
)
}
}
export
default
InteractiveStudy
;
\ No newline at end of file
export
default
InteractiveStudy
src/components/interactive-tutorial/index.scss
View file @
1dd3b96a
body
{
body
{
background-color
:
#252529
;
}
...
...
@@ -9,6 +9,10 @@ body{
padding-bottom
:
49px
;
padding-top
:
20px
;
&
+
div
{
display
:
none
;
}
&
+
.year19-index
{
display
:
none
;
}
...
...
@@ -24,18 +28,19 @@ body{
left
:
0
;
width
:
100%
;
height
:
$height
;
background
:
#3
E3F
47
;
background
:
#3
e3f
47
;
line-height
:
$height
;
text-align
:
center
;
color
:
#CFDBE5
;
color
:
#cfdbe5
;
z-index
:
100
;
.complete
{
color
:
rgba
(
207
,
219
,
229
,
.2
);
color
:
rgba
(
207
,
219
,
229
,
0
.2
);
font-size
:
18px
;
}
.status
{
color
:
#
CFDBE
5
;
color
:
#
cfdbe
5
;
font-size
:
16px
;
}
...
...
@@ -51,10 +56,10 @@ body{
-webkit-appearance
:
none
;
border
:
none
;
outline
:
0
;
background
:
#
FA5C
19
;
background
:
#
fa5c
19
;
font-size
:
14px
;
color
:
#fff
;
}
}
}
}
\ No newline at end of file
}
src/components/interactive-tutorial/program/index.js
View file @
1dd3b96a
import
React
,
{
Component
}
from
'react'
;
import
'./index.scss'
import
Container
from
'../container'
import
Terminal
from
'../terminal'
import
AceEditor
from
'react-ace'
import
'ace-builds/src-min-noconflict/theme-dracula'
import
'ace-builds/src-min-noconflict/mode-python'
import
StatusBar
from
'./status-bar'
import
{
http
}
from
"
@
/utils"
import
{
Toast
}
from
"antd-mobile"
;
import
{
questionType
}
from
'../consts'
import
React
,
{
Component
}
from
"react"
import
"./index.scss"
import
Container
from
"../container"
import
Terminal
from
"../terminal"
import
AceEditor
from
"react-ace"
import
"ace-builds/src-min-noconflict/theme-dracula"
import
"ace-builds/src-min-noconflict/mode-python"
import
StatusBar
from
"./status-bar"
import
{
http
}
from
"
src
/utils"
import
{
Toast
}
from
"antd-mobile"
import
{
questionType
}
from
"../consts"
const
{
First
,
Normal
,
Pass
,
Error
,
InputTip
,
Skipped
,
Finished
}
=
StatusBar
/* eslint-disable-next-line no-unused-vars */
const
{
First
,
Normal
,
Pass
,
Error
,
InputTip
,
Skipped
,
Finished
}
=
StatusBar
class
Program
extends
Component
{
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
state
=
{
editorWidth
:
''
,
editorWidth
:
""
,
showTerminal
:
false
,
code
:
''
,
answer
:
''
,
code
:
""
,
answer
:
""
,
showAnswer
:
false
,
result
:
''
,
filename
:
''
,
result
:
""
,
filename
:
""
,
executing
:
false
,
}
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
contentWidth
=
contentElem
.
clientWidth
const
contentPaddingLeft
=
parseFloat
(
contentStyles
.
getPropertyValue
(
'padding-left'
))
const
{
type
,
code_ques
,
content
,
user_answer
}
=
this
.
props
.
question
const
contentPaddingLeft
=
parseFloat
(
contentStyles
.
getPropertyValue
(
"padding-left"
)
)
const
{
type
,
code_ques
,
content
,
user_answer
}
=
this
.
props
.
question
let
code
=
content
let
answer
=
''
,
result
=
''
,
showTerminal
=
false
let
answer
=
""
,
result
=
""
,
showTerminal
=
false
if
(
type
===
questionType
.
program
)
{
code
=
code_ques
.
code
answer
=
code_ques
.
answer
...
...
@@ -54,13 +61,12 @@ class Program extends Component {
})
}
onLoad
=
editor
=>
{
onLoad
=
(
editor
)
=>
{
this
.
userEditor
=
editor
}
execute
=
()
=>
{
// this.getAuthenticationData()
this
.
uploadCode
().
then
(
filename
=>
{
this
.
uploadCode
().
then
((
filename
)
=>
{
this
.
setState
({
filename
,
executing
:
true
,
...
...
@@ -70,74 +76,116 @@ class Program extends Component {
}
uploadCode
=
()
=>
{
return
http
.
post
(
`
${
API
.
home
}
/web/python/practice/file`
,
{
code
:
this
.
userEditor
.
getValue
(),
}).
then
(
res
=>
{
const
{
code
,
msg
,
data
}
=
res
.
data
if
(
code
===
200
)
{
return
data
.
filename
}
else
{
Toast
.
fail
(
msg
,
2
)
}
})
return
http
.
post
(
`
${
API
.
home
}
/web/python/practice/file`
,
{
code
:
this
.
userEditor
.
getValue
(),
})
.
then
((
res
)
=>
{
const
{
code
,
msg
,
data
}
=
res
.
data
if
(
code
===
200
)
{
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
()
{
const
{
editorWidth
,
showTerminal
,
code
,
answer
,
showAnswer
,
result
,
executing
,
filename
}
=
this
.
state
const
{
user
,
question
:
{
type
,
user_answer
,
content
},
isProgramShowed
}
=
this
.
props
const
{
editorWidth
,
showTerminal
,
code
,
answer
,
showAnswer
,
result
,
executing
,
filename
,
}
=
this
.
state
const
{
question
:
{
type
,
user_answer
,
content
},
isProgramShowed
,
}
=
this
.
props
return
(
<>
{
content
&&
type
!==
questionType
.
codeBlock
&&
<
Container
content
=
{
content
}
><
/Container
>
}
{
content
&&
type
!==
questionType
.
codeBlock
&&
(
<
Container
content
=
{
content
}
><
/Container
>
)
}
<
Container
user
=
{
this
.
icon
}
content
=
{
<
div
className
=
"program"
>
<
AceEditor
name
=
{
'ace-editor'
}
mode
=
{
'python'
}
theme
=
{
'dracula'
}
name
=
{
"ace-editor"
}
mode
=
{
"python"
}
theme
=
{
"dracula"
}
readOnly
=
{
true
}
value
=
{
code
}
width
=
{
editorWidth
}
height
=
{
'141px'
}
height
=
{
"141px"
}
onLoad
=
{
this
.
onLoad
}
/
>
{
showAnswer
&&
<
AceEditor
name
=
{
'ace-editor'
}
mode
=
{
'python'
}
theme
=
{
'dracula'
}
{
showAnswer
&&
(
<
AceEditor
name
=
{
"ace-editor"
}
mode
=
{
"python"
}
theme
=
{
"dracula"
}
readOnly
=
{
true
}
value
=
{
answer
}
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
>
}
/
>
<
/
>
)
;
)
}
}
function
ToolBar
({
isProgramShowed
,
userAnswer
,
isSuccessful
,
execute
})
{
function
ToolBar
({
isProgramShowed
,
userAnswer
,
isSuccessful
,
execute
})
{
if
(
userAnswer
&&
userAnswer
.
is_finish
)
{
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
;
\ No newline at end of file
export
default
Program
src/components/interactive-tutorial/program/index.scss
View file @
1dd3b96a
...
...
@@ -25,6 +25,14 @@
.ace_marker-layer
.ace_active-line
{
background
:
rgba
(
207
,
219
,
229
,
.1
);
}
.ace_gutter
{
background
:
transparent
;
}
.ace_gutter-active-line
{
background
:
rgba
(
207
,
219
,
229
,
.1
);
}
}
.ace_scrollbar
{
...
...
@@ -71,13 +79,8 @@
color
:
#09f
;
}
.ace-dracula
{
.ace_gutter
{
background
:
transparent
;
}
.ace_gutter-active-line
{
background
:
rgba
(
207
,
219
,
229
,
.1
);
}
.ace_dark
>
.ace_mobile-menu
{
display
:
none
;
}
}
\ No newline at end of file
src/components/interactive-tutorial/single-answer-question/index.js
View file @
1dd3b96a
import
React
,
{
Component
}
from
'react'
;
import
'./index.scss'
import
Container
from
'../container'
import
classnames
from
'classnames'
import
{
html
,
isValidHttpUrl
}
from
"
@
/utils"
import
React
,
{
Component
}
from
"react"
import
"./index.scss"
import
Container
from
"../container"
import
classnames
from
"classnames"
import
{
html
,
isValidHttpUrl
}
from
"
src
/utils"
class
SingleAnswerQuestion
extends
Component
{
icon
=
'https://julyedu-cdn.oss-cn-beijing.aliyuncs.com/interactive_tutorial/study/single-answer-icon.png'
icon
=
"https://julyedu-cdn.oss-cn-beijing.aliyuncs.com/interactive_tutorial/study/single-answer-icon.png"
state
=
{
correct
:
''
,
wrong
:
''
,
replies
:
''
,
correct
:
""
,
wrong
:
""
,
replies
:
""
,
selectable
:
false
,
}
...
...
@@ -20,37 +20,53 @@ class SingleAnswerQuestion extends Component {
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
isCorrect
=
userSelect
.
is_ans
this
.
setState
({
wrong
:
isCorrect
?
''
:
index
,
correct
:
isCorrect
?
index
:
''
,
replies
:
info
.
slice
(
0
,
1
),
selectable
:
false
,
},
()
=>
{
updatePosition
()
resetStatus
()
addCache
(
info
.
slice
(
1
))
savePractice
({
type
:
1
,
syllabus_id
:
syllabus_id
,
answer
:
unit_questions
[
index
].
unit_id
,
result
:
isCorrect
,
})
saveSchedule
(
syllabus_id
,
info
[
0
].
info_id
)
})
this
.
setState
(
{
wrong
:
isCorrect
?
""
:
index
,
correct
:
isCorrect
?
index
:
""
,
replies
:
info
.
slice
(
0
,
1
),
selectable
:
false
,
},
()
=>
{
updatePosition
()
resetStatus
()
addCache
(
info
.
slice
(
1
))
savePractice
({
type
:
1
,
syllabus_id
:
syllabus_id
,
answer
:
unit_questions
[
index
].
unit_id
,
result
:
isCorrect
,
})
saveSchedule
(
syllabus_id
,
info
[
0
].
info_id
)
}
)
}
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
)
{
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
this
.
setState
({
correct
:
isUserCorrect
?
userSelectIndex
:
''
,
wrong
:
isUserCorrect
?
''
:
userSelectIndex
,
correct
:
isUserCorrect
?
userSelectIndex
:
""
,
wrong
:
isUserCorrect
?
""
:
userSelectIndex
,
replies
:
options
[
userSelectIndex
].
unit_info
,
selectable
:
false
,
})
...
...
@@ -61,44 +77,67 @@ class SingleAnswerQuestion extends Component {
}
}
render
()
{
const
{
correct
,
wrong
,
replies
}
=
this
.
state
const
{
user
,
topic
,
img
,
question
:
{
unit_questions
:
options
},
updatePosition
}
=
this
.
props
const
{
correct
,
wrong
,
replies
}
=
this
.
state
const
{
topic
,
img
,
question
:
{
unit_questions
:
options
},
updatePosition
,
}
=
this
.
props
return
(
<>
<
Container
user
=
{
this
.
icon
}
content
=
{
<
div
className
=
{
'single-answer-question'
}
>
<
div
className
=
"topic"
dangerouslySetInnerHTML
=
{
html
(
topic
)}
><
/div
>
{
img
&&
<
img
src
=
{
img
}
alt
=
""
/>
}
<
div
className
=
{
"single-answer-question"
}
>
<
div
className
=
"topic"
dangerouslySetInnerHTML
=
{
html
(
topic
)}
><
/div
>
{
img
&&
<
img
src
=
{
img
}
alt
=
""
/>
}
<
ul
>
{
options
&&
!!
options
.
length
&&
options
.
map
((
item
,
index
)
=>
{
return
<
li
className
=
{
classnames
([
'option'
,
{
'correct'
:
index
===
correct
,
'wrong'
:
index
===
wrong
}])}
key
=
{
index
}
onClick
=
{
this
.
handleSelect
.
bind
(
this
,
item
.
unit_info
,
index
)}
>
{
item
.
des
}
<
/li
>
})
}
{
options
&&
!!
options
.
length
&&
options
.
map
((
item
,
index
)
=>
{
return
(
<
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
>
<
/div
>
}
/
>
{
!!
replies
.
length
&&
replies
.
map
(
item
=>
{
{
!!
replies
.
length
&&
replies
.
map
((
item
)
=>
{
if
(
isValidHttpUrl
(
item
.
content
))
{
return
<
Container
key
=
{
item
.
info_id
}
>
<
img
src
=
{
item
.
content
}
alt
=
""
onLoad
=
{
updatePosition
}
/
>
<
/Container
>
return
(
<
Container
key
=
{
item
.
info_id
}
>
<
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
;
\ No newline at end of file
export
default
SingleAnswerQuestion
src/components/interactive-tutorial/terminal/index.js
deleted
100644 → 0
View file @
9031f1bf
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
src/components/interactive-tutorial/terminal/index.scss
View file @
1dd3b96a
.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
{
height
:
141px
;
padding
:
5px
;
overflow
:
hidden
;
background
:
#000
;
}
\ No newline at end of file
}
src/components/interactive-tutorial/terminal/index.tsx
0 → 100644
View file @
1dd3b96a
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
src/router/router-config.js
View file @
1dd3b96a
...
...
@@ -536,7 +536,7 @@ export default [
path
:
"/interactive-tutorial"
,
component
:
loadable
(()
=>
import
(
/* webpackChunkName: 'interactive-tutorial' */
"
@
/components/interactive-tutorial"
/* webpackChunkName: 'interactive-tutorial' */
"
src
/components/interactive-tutorial"
)
),
},
...
...
src/utils/index.js
View file @
1dd3b96a
...
...
@@ -115,5 +115,6 @@ export {
isValidUrl
,
loadScript
,
getTimestamp
,
isValidHttpUrl
,
}
export
{
default
as
SendMessageToApp
}
from
"./app"
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment