Skip to content
项目
群组
代码片段
帮助
当前项目
正在载入...
登录 / 注册
切换导航面板
S
saas-lab
项目
项目
详情
活动
周期分析
仓库
仓库
文件
提交
分支
标签
贡献者
图表
比较
统计图
议题
0
议题
0
列表
看板
标记
里程碑
合并请求
0
合并请求
0
CI / CD
CI / CD
流水线
作业
日程
统计图
Wiki
Wiki
代码片段
代码片段
成员
成员
折叠边栏
关闭边栏
活动
图像
聊天
创建新问题
作业
提交
问题看板
Open sidebar
EzijingWeb
saas-lab
Commits
d88a1b98
提交
d88a1b98
authored
8月 24, 2022
作者:
王鹏飞
浏览文件
操作
浏览文件
下载
电子邮件补丁
差异文件
chore: update
上级
24a06d04
隐藏空白字符变更
内嵌
并排
正在显示
17 个修改的文件
包含
592 行增加
和
35 行删除
+592
-35
Index.vue
src/modules/admin/lab/book/views/Index.vue
+0
-1
api.ts
src/modules/admin/lab/discuss/api.ts
+10
-0
DiscussDialog.vue
src/modules/admin/lab/discuss/components/DiscussDialog.vue
+118
-0
DiscussItem.vue
src/modules/admin/lab/discuss/components/DiscussItem.vue
+100
-0
types.ts
src/modules/admin/lab/discuss/types.ts
+48
-0
Index.vue
src/modules/admin/lab/discuss/views/Index.vue
+27
-4
api.ts
src/modules/admin/lab/record/api.ts
+24
-1
ScoreDialog.vue
src/modules/admin/lab/record/components/ScoreDialog.vue
+168
-0
useFilterList.ts
src/modules/admin/lab/record/composables/useFilterList.ts
+0
-0
index.ts
src/modules/admin/lab/record/index.ts
+1
-1
types.ts
src/modules/admin/lab/record/types.ts
+27
-0
Index.vue
src/modules/admin/lab/record/views/Index.vue
+44
-5
Index.vue
src/modules/admin/lab/video/views/Index.vue
+0
-1
View.vue
src/modules/admin/lab/video/views/View.vue
+6
-5
Index.vue
src/modules/admin/system/experiment/views/Index.vue
+0
-1
Discuss.vue
src/modules/student/lab/components/Discuss.vue
+17
-14
menu.ts
src/stores/menu.ts
+2
-2
没有找到文件。
src/modules/admin/lab/book/views/Index.vue
浏览文件 @
d88a1b98
...
@@ -49,7 +49,6 @@ function handleUpdate(row: BookItem) {
...
@@ -49,7 +49,6 @@ function handleUpdate(row: BookItem) {
}
}
function
onUpdateSuccess
()
{
function
onUpdateSuccess
()
{
dialogVisible
=
false
appList
?.
refetch
()
appList
?.
refetch
()
}
}
</
script
>
</
script
>
...
...
src/modules/admin/lab/discuss/api.ts
浏览文件 @
d88a1b98
...
@@ -17,3 +17,13 @@ export function getDiscussList(params?: {
...
@@ -17,3 +17,13 @@ export function getDiscussList(params?: {
export
function
getFilterList
()
{
export
function
getFilterList
()
{
return
httpRequest
.
get
(
'/api/lab/v1/teacher/discussion/search-list'
)
return
httpRequest
.
get
(
'/api/lab/v1/teacher/discussion/search-list'
)
}
}
// 获取讨论交流详情
export
function
getDiscuss
(
params
:
{
id
:
string
;
page
?:
number
;
page_size
?:
number
})
{
return
httpRequest
.
get
(
'/api/lab/v1/teacher/discussion/view'
,
{
params
})
}
// 获取讨论交流详情
export
function
submitDiscussComment
(
data
:
{
id
:
string
;
content
:
string
})
{
return
httpRequest
.
post
(
'/api/lab/v1/teacher/discussion/comment'
,
data
)
}
src/modules/admin/lab/discuss/components/DiscussDialog.vue
0 → 100644
浏览文件 @
d88a1b98
<
script
setup
lang=
"ts"
>
import
type
{
FormInstance
,
FormRules
}
from
'element-plus'
import
type
{
DiscussListItem
,
DiscussInfo
,
DiscussCommentItem
}
from
'../types'
import
{
Loading
}
from
'@element-plus/icons-vue'
import
{
ElMessage
}
from
'element-plus'
import
{
useInfiniteScroll
}
from
'@vueuse/core'
import
DiscussItem
from
'./DiscussItem.vue'
import
{
getDiscuss
,
submitDiscussComment
}
from
'../api'
interface
Props
{
data
:
DiscussListItem
}
const
props
=
defineProps
<
Props
>
()
const
emit
=
defineEmits
<
{
(
e
:
'update'
):
void
(
e
:
'update:modelValue'
,
visible
:
boolean
):
void
}
>
()
const
params
=
reactive
({
id
:
props
.
data
.
id
,
page
:
1
,
'per-page'
:
10
})
let
hasMore
=
$ref
(
false
)
let
isLoading
=
$ref
(
false
)
const
detail
=
reactive
<
{
info
?:
DiscussInfo
;
list
:
DiscussCommentItem
[]
}
>
({
info
:
undefined
,
list
:
[]
})
function
fetchInfo
(
isForce
=
false
)
{
if
(
!
props
.
data
.
id
)
return
if
(
isForce
)
{
params
.
page
=
1
}
isLoading
=
true
getDiscuss
(
params
)
.
then
(
res
=>
{
detail
.
info
=
res
.
data
.
info
detail
.
list
=
isForce
?
res
.
data
.
list
:
[...
detail
.
list
,
...
res
.
data
.
list
]
hasMore
=
!!
res
.
data
.
list
.
length
})
.
finally
(()
=>
{
isLoading
=
false
})
}
watch
(
()
=>
props
.
data
.
id
,
()
=>
{
fetchInfo
(
true
)
},
{
immediate
:
true
}
)
// 滚动加载
const
scrollRef
=
ref
<
HTMLElement
>
()
useInfiniteScroll
(
scrollRef
,
()
=>
{
if
(
!
hasMore
||
isLoading
)
return
params
.
page
++
fetchInfo
()
},
{
distance
:
10
}
)
const
formRef
=
$ref
<
FormInstance
>
()
const
form
=
reactive
<
{
content
:
string
}
>
({
content
:
''
})
const
rules
=
ref
<
FormRules
>
({
content
:
[{
required
:
true
,
message
:
'请输入回复内容'
}]
})
// 提交
function
handleSubmit
()
{
formRef
?.
validate
().
then
(()
=>
{
const
params
=
{
...
form
,
id
:
props
.
data
.
id
}
submitDiscussComment
(
params
).
then
(()
=>
{
ElMessage
({
message
:
'回复成功'
,
type
:
'success'
})
emit
(
'update'
)
fetchInfo
(
true
)
formRef
?.
resetFields
()
})
})
}
</
script
>
<
template
>
<el-dialog
title=
"实验话题讨论"
:close-on-click-modal=
"false"
@
update:modelValue=
"$emit('update:modelValue')"
>
<div
class=
"discuss-scroll"
ref=
"scrollRef"
>
<DiscussItem
:info=
"detail.info"
:list=
"detail.list"
v-if=
"detail.info"
></DiscussItem>
<div
class=
"tips"
v-if=
"isLoading"
>
<el-icon
class=
"is-loading"
>
<Loading
/>
</el-icon>
加载中...
</div>
<div
class=
"tips"
v-if=
"!hasMore"
>
没有更多了
</div>
</div>
<el-form
ref=
"formRef"
:rules=
"rules"
:model=
"form"
style=
"margin-top: 40px"
>
<el-form-item
prop=
"content"
>
<el-input
type=
"textarea"
v-model=
"form.content"
:autosize=
"
{ minRows: 4, maxRows: 6 }" />
</el-form-item>
<el-row
justify=
"end"
>
<el-button
round
auto-insert-space
@
click=
"$emit('update:modelValue', false)"
>
取消
</el-button>
<el-button
type=
"primary"
round
auto-insert-space
@
click=
"handleSubmit"
>
保存回复
</el-button>
</el-row>
</el-form>
</el-dialog>
</
template
>
<
style
lang=
"scss"
scoped
>
.discuss-scroll
{
max-height
:
400px
;
overflow-y
:
auto
;
}
.tips
{
padding
:
40px
;
color
:
#555
;
text-align
:
center
;
}
.el-icon.is-loading
{
animation
:
rotating
2s
linear
infinite
;
}
</
style
>
src/modules/admin/lab/discuss/components/DiscussItem.vue
0 → 100644
浏览文件 @
d88a1b98
<
script
setup
lang=
"ts"
>
import
type
{
DiscussInfo
,
DiscussCommentItem
}
from
'../types'
import
{
ChatLineRound
}
from
'@element-plus/icons-vue'
interface
Props
{
info
:
DiscussInfo
list
:
DiscussCommentItem
[]
}
defineProps
<
Props
>
()
defineEmits
<
{
(
e
:
'update'
):
void
}
>
()
</
script
>
<
template
>
<div
class=
"discuss-item"
>
<div
class=
"discuss-box"
>
<div
class=
"discuss-box-user"
>
<img
:src=
"info.created_operator_avatar"
/>
<p>
{{
info
.
student_id_name
}}
</p>
</div>
<div
class=
"discuss-box-main"
>
<div
class=
"discuss-box-header"
>
<p
class=
"discuss-box-time"
>
{{
info
.
created_time
}}
</p>
<div
class=
"tools"
>
<el-icon><ChatLineRound></ChatLineRound></el-icon>
{{
info
.
replies_num
}}
</div>
</div>
<h3>
{{
info
.
title
}}
</h3>
<div
class=
"discuss-box-content"
v-html=
"info.content"
></div>
</div>
</div>
<div
class=
"discuss-box discuss-comment"
v-for=
"item in list"
:key=
"item.id"
>
<div
class=
"discuss-box-user"
>
<img
:src=
"item.sso_id_avatar"
/>
<p>
{{
item
.
sso_id_name
}}
</p>
</div>
<div
class=
"discuss-box-main"
>
<p
class=
"discuss-box-time"
>
{{
item
.
created_time
}}
</p>
<div
class=
"discuss-box-content"
v-html=
"item.content"
></div>
</div>
</div>
</div>
</
template
>
<
style
lang=
"scss"
scoped
>
.discuss-box
{
display
:
flex
;
margin
:
10px
0
;
padding
:
10px
;
font-size
:
12px
;
color
:
var
(
--
main-color
);
border-radius
:
10px
;
border
:
1px
solid
var
(
--
main-color
);
&
.discuss-comment
{
margin-left
:
40px
;
color
:
#016fa0
;
border
:
1px
solid
#016fa0
;
}
}
.discuss-box-user
{
img
{
width
:
50px
;
height
:
50px
;
border-radius
:
50%
;
overflow
:
hidden
;
object-fit
:
cover
;
}
p
{
text-align
:
center
;
}
}
.discuss-box-main
{
flex
:
1
;
padding-left
:
10px
;
h3
{
font-size
:
14px
;
font-weight
:
500
;
}
}
.discuss-box-content
{
padding
:
5px
0
;
color
:
#555
;
}
.discuss-box-header
{
display
:
flex
;
align-items
:
center
;
justify-content
:
space-between
;
.tools
{
display
:
flex
;
align-items
:
center
;
.el-icon
{
font-size
:
16px
;
margin-left
:
10px
;
margin-right
:
5px
;
color
:
#333333
;
}
}
}
</
style
>
src/modules/admin/lab/discuss/types.ts
0 → 100644
浏览文件 @
d88a1b98
export
interface
DiscussListItem
{
class_id
:
string
class_id_name
:
string
course_id
:
string
course_id_name
:
string
created_time
:
string
experiment_id
:
string
experiment_id_name
:
string
id
:
string
is_reply
:
string
is_reply_name
:
string
replies_num
:
number
sno_number
:
string
specialty_id
:
string
specialty_id_name
:
string
student_id
:
string
student_name
:
string
title
:
string
}
export
interface
DiscussInfo
{
content
:
string
created_operator
:
string
created_operator_avatar
:
string
created_time
:
string
delete_time
:
string
experiment_id
:
string
id
:
string
is_reply
:
string
replies_num
:
number
status
:
string
student_id
:
string
student_id_name
:
string
title
:
string
updated_operator
:
string
updated_time
:
string
}
export
interface
DiscussCommentItem
{
content
:
string
created_time
:
string
discussion_id
:
string
id
:
string
role
:
string
sso_id
:
string
sso_id_avatar
:
string
sso_id_name
:
string
}
src/modules/admin/lab/discuss/views/Index.vue
浏览文件 @
d88a1b98
<
script
setup
lang=
"ts"
>
<
script
setup
lang=
"ts"
>
import
type
{
DiscussListItem
}
from
'../types'
import
AppList
from
'@/components/base/AppList.vue'
import
{
getDiscussList
}
from
'../api'
import
{
getDiscussList
}
from
'../api'
import
{
useFilterList
}
from
'../composables/useFilterList'
import
{
useFilterList
}
from
'../composables/useFilterList'
const
DiscussDialog
=
defineAsyncComponent
(()
=>
import
(
'../components/DiscussDialog.vue'
))
const
{
courses
,
experiments
,
specialties
,
classes
}
=
useFilterList
()
const
{
courses
,
experiments
,
specialties
,
classes
}
=
useFilterList
()
const
appList
=
$ref
<
InstanceType
<
typeof
AppList
>
|
null
>
(
null
)
// 列表配置
// 列表配置
const
listOptions
=
$computed
(()
=>
{
const
listOptions
=
$computed
(()
=>
{
return
{
return
{
...
@@ -65,25 +71,42 @@ const listOptions = $computed(() => {
...
@@ -65,25 +71,42 @@ const listOptions = $computed(() => {
]
]
}
}
})
})
let
dialogVisible
=
$ref
(
false
)
const
rowData
=
ref
<
DiscussListItem
>
()
// 评论
function
handleComment
(
row
:
DiscussListItem
)
{
rowData
.
value
=
row
dialogVisible
=
true
}
function
onUpdateSuccess
()
{
appList
?.
refetch
()
}
</
script
>
</
script
>
<
template
>
<
template
>
<AppCard
title=
"实验讨论交流"
>
<AppCard
title=
"实验讨论交流"
>
<AppList
v-bind=
"listOptions"
>
<AppList
v-bind=
"listOptions"
ref=
"appList"
>
<template
#
table-replies-num=
"
{ row }">
<template
#
table-replies-num=
"
{ row }">
<span
:class=
"
{ 'is-info': !!row.replies_num }">
{{
row
.
replies_num
}}
</span>
<span
:class=
"
{ 'is-info': !!row.replies_num }">
{{
row
.
replies_num
}}
</span>
</
template
>
</
template
>
<
template
#
table-reply=
"{ row }"
>
<
template
#
table-reply=
"{ row }"
>
<span
:class=
"
{ 'is-success': row.is_reply === '1' }">
{{
row
.
is_reply_name
}}
</span>
<span
:class=
"
{ 'is-success': row.is_reply === '1' }">
{{
row
.
is_reply_name
}}
</span>
</
template
>
</
template
>
<
template
#
table-x
>
<
template
#
table-x
=
"{ row }"
>
<el-button
text
type=
"primary"
>
评论
</el-button>
<el-button
text
type=
"primary"
@
click=
"handleComment(row)"
>
评论
</el-button>
</
template
>
</
template
>
</AppList>
</AppList>
</AppCard>
</AppCard>
<DiscussDialog
v-model=
"dialogVisible"
:data=
"rowData"
@
update=
"onUpdateSuccess"
v-if=
"dialogVisible && rowData"
></DiscussDialog>
</template>
</template>
<
style
lang=
"scss"
>
<
style
lang=
"scss"
scoped
>
.is-success
{
.is-success
{
color
:
#63a103
;
color
:
#63a103
;
}
}
...
...
src/modules/admin/lab/
score
/api.ts
→
src/modules/admin/lab/
record
/api.ts
浏览文件 @
d88a1b98
import
httpRequest
from
'@/utils/axios'
import
httpRequest
from
'@/utils/axios'
// 获取实验
成绩
列表
// 获取实验
记录
列表
export
function
getExperimentRecordList
(
params
?:
{
export
function
getExperimentRecordList
(
params
?:
{
course_id
?:
string
course_id
?:
string
experiment_id
?:
string
experiment_id
?:
string
...
@@ -17,3 +17,26 @@ export function getExperimentRecordList(params?: {
...
@@ -17,3 +17,26 @@ export function getExperimentRecordList(params?: {
export
function
getFilterList
()
{
export
function
getFilterList
()
{
return
httpRequest
.
get
(
'/api/lab/v1/teacher/record/search-list'
)
return
httpRequest
.
get
(
'/api/lab/v1/teacher/record/search-list'
)
}
}
// 获取实验记录详情
export
function
getExperimentRecord
(
params
:
{
experiment_id
:
string
;
student_id
:
string
})
{
return
httpRequest
.
get
(
'/api/lab/v1/teacher/record/view'
,
{
params
})
}
// 实验记录评分
export
function
checkExperimentRecord
(
data
:
{
experiment_id
:
string
student_id
:
string
operate
:
number
result
:
number
file
:
number
})
{
return
httpRequest
.
post
(
'/api/lab/v1/teacher/record/check'
,
data
)
}
// 批量导入实验记录评分
export
function
uploadCheckExperimentRecord
(
data
:
{
file
:
File
})
{
return
httpRequest
.
post
(
'/api/lab/v1/teacher/record/check'
,
data
,
{
headers
:
{
'Content-Type'
:
'multipart/form-data'
}
})
}
src/modules/admin/lab/record/components/ScoreDialog.vue
0 → 100644
浏览文件 @
d88a1b98
<
script
setup
lang=
"ts"
>
import
type
{
FormInstance
,
FormRules
}
from
'element-plus'
import
type
{
RecordItem
,
FileItem
}
from
'../types'
import
{
Document
}
from
'@element-plus/icons-vue'
import
{
ElMessage
,
ElMessageBox
}
from
'element-plus'
import
{
getExperimentRecord
,
checkExperimentRecord
}
from
'../api'
interface
Props
{
data
:
RecordItem
}
const
props
=
defineProps
<
Props
>
()
const
emit
=
defineEmits
<
{
(
e
:
'update'
):
void
(
e
:
'update:modelValue'
,
visible
:
boolean
):
void
}
>
()
let
detail
=
$ref
<
RecordItem
>
()
function
fetchInfo
()
{
getExperimentRecord
({
experiment_id
:
props
.
data
.
experiment_id
,
student_id
:
props
.
data
.
student_id
}).
then
(
res
=>
{
detail
=
res
.
data
})
}
watchEffect
(()
=>
{
fetchInfo
()
})
// 实验报告文件
const
file
=
$computed
<
FileItem
>
(()
=>
{
try
{
return
detail
?.
file
?
JSON
.
parse
(
detail
.
file
)
:
null
}
catch
(
error
)
{
console
.
log
(
error
)
}
return
null
})
// 实验过程截图
const
pictures
=
$computed
<
FileItem
[]
>
(()
=>
{
try
{
return
detail
?.
pictures
?
JSON
.
parse
(
detail
.
pictures
)
:
[]
}
catch
(
error
)
{
console
.
log
(
error
)
}
return
[]
})
const
formRef
=
$ref
<
FormInstance
>
()
const
form
=
reactive
<
{
operate
?:
number
;
result
?:
number
;
file
?:
number
}
>
({
operate
:
undefined
,
result
:
undefined
,
file
:
undefined
})
const
score
=
$computed
<
number
>
(()
=>
{
const
result
=
((
form
.
operate
||
0
)
+
(
form
.
result
||
0
)
+
(
form
.
file
||
0
))
/
3
return
parseFloat
(
result
.
toFixed
(
2
))
})
const
rules
=
ref
<
FormRules
>
({
operate
:
[{
required
:
true
,
message
:
'请输入0~100数字'
}],
result
:
[{
required
:
true
,
message
:
'请输入0~100数字'
}],
file
:
[{
required
:
true
,
message
:
'请输入0~100数字'
}]
})
// 提交
function
handleSubmit
()
{
formRef
?.
validate
().
then
(()
=>
{
ElMessageBox
.
confirm
(
'成绩评分不能修改,确认要保存该成绩吗?'
,
'提示'
).
then
(()
=>
{
const
params
:
any
=
{
...
form
,
experiment_id
:
props
.
data
.
experiment_id
,
student_id
:
props
.
data
.
student_id
}
checkExperimentRecord
(
params
).
then
(()
=>
{
ElMessage
({
message
:
'保存成功'
,
type
:
'success'
})
emit
(
'update'
)
emit
(
'update:modelValue'
,
false
)
})
})
})
}
</
script
>
<
template
>
<el-dialog
title=
"学生实验评分"
:close-on-click-modal=
"false"
@
update:modelValue=
"$emit('update:modelValue')"
>
<el-form
:rules=
"rules"
label-width=
"120px"
label-suffix=
":"
v-if=
"detail"
>
<el-form-item
label=
"实验名称"
>
{{
detail
.
experiment_name
}}
</el-form-item>
<el-form-item
label=
"实验课程名称"
>
{{
detail
.
course_name
}}
</el-form-item>
<el-row>
<el-col
:span=
"12"
>
<el-form-item
label=
"学生姓名"
>
{{
detail
.
student_name
}}
</el-form-item>
</el-col>
<el-col
:span=
"12"
>
<el-form-item
label=
"学生学号"
>
{{
detail
.
sno_number
}}
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col
:span=
"12"
>
<el-form-item
label=
"所属专业"
>
{{
detail
.
specialty_name
}}
</el-form-item>
</el-col>
<el-col
:span=
"12"
>
<el-form-item
label=
"所属班级"
>
{{
detail
.
class_name
}}
</el-form-item>
</el-col>
</el-row>
<el-form-item
label=
"实验报告文件"
>
<p
v-if=
"file"
>
<a
:href=
"file.url"
target=
"_blank"
>
<el-icon><Document
/></el-icon>
{{
file
.
name
}}
</a>
</p>
</el-form-item>
<el-form-item
label=
"实验过程截图"
>
<ul
class=
"picture-list"
>
<li
v-for=
"item in pictures"
:key=
"item.url"
>
<p
class=
"t1"
>
<a
:href=
"item.url"
target=
"_blank"
>
{{
item
.
name
}}
</a>
</p>
<p
class=
"t2"
>
截图时间:
{{
item
.
upload_time
}}
</p>
</li>
</ul>
</el-form-item>
<el-form-item
label=
"实验成绩"
>
<el-form
ref=
"formRef"
:model=
"form"
:rules=
"rules"
hide-required-asterisk
inline
label-position=
"top"
style=
"padding: 5px 0 20px"
>
<el-form-item
label=
"实验操作"
prop=
"operate"
>
<el-input-number
:min=
"0"
:max=
"100"
:controls=
"false"
step-strictly
v-model=
"form.operate"
/>
</el-form-item>
<el-form-item
label=
"实验结果"
prop=
"result"
>
<el-input-number
:min=
"0"
:max=
"100"
:controls=
"false"
step-strictly
v-model=
"form.result"
/>
</el-form-item>
<el-form-item
label=
"实验报告"
prop=
"file"
>
<el-input-number
:min=
"0"
:max=
"100"
:controls=
"false"
step-strictly
v-model=
"form.file"
/>
</el-form-item>
<el-form-item
label=
"综合实验成绩"
>
<el-input-number
:min=
"0"
:max=
"100"
:controls=
"false"
disabled
v-model=
"score"
/>
</el-form-item>
</el-form>
</el-form-item>
<el-row
justify=
"center"
>
<el-button
type=
"primary"
round
auto-insert-space
@
click=
"handleSubmit"
>
保存
</el-button>
<el-button
round
auto-insert-space
@
click=
"$emit('update:modelValue', false)"
>
取消
</el-button>
</el-row>
</el-form>
</el-dialog>
</
template
>
<
style
lang=
"scss"
scoped
>
.picture-list
{
width
:
100%
;
li
{
display
:
flex
;
justify-content
:
space-between
;
}
a
{
color
:
blue
;
}
.t1
{
flex
:
1
;
white-space
:
nowrap
;
text-overflow
:
ellipsis
;
overflow
:
hidden
;
}
.t2
{
flex
:
1
;
}
}
</
style
>
src/modules/admin/lab/
score
/composables/useFilterList.ts
→
src/modules/admin/lab/
record
/composables/useFilterList.ts
浏览文件 @
d88a1b98
File moved
src/modules/admin/lab/
score
/index.ts
→
src/modules/admin/lab/
record
/index.ts
浏览文件 @
d88a1b98
...
@@ -3,7 +3,7 @@ import AppLayout from '@/components/layout/Index.vue'
...
@@ -3,7 +3,7 @@ import AppLayout from '@/components/layout/Index.vue'
export
const
routes
:
Array
<
RouteRecordRaw
>
=
[
export
const
routes
:
Array
<
RouteRecordRaw
>
=
[
{
{
path
:
'/admin/lab/
score
'
,
path
:
'/admin/lab/
record
'
,
component
:
AppLayout
,
component
:
AppLayout
,
children
:
[{
path
:
''
,
component
:
()
=>
import
(
'./views/Index.vue'
)
}]
children
:
[{
path
:
''
,
component
:
()
=>
import
(
'./views/Index.vue'
)
}]
}
}
...
...
src/modules/admin/lab/record/types.ts
0 → 100644
浏览文件 @
d88a1b98
export
interface
RecordItem
{
check_time
:
string
checker_id
?:
string
class_id
:
string
class_name
:
string
commit_time
:
string
course_name
?:
string
experiment_id
:
string
experiment_name
:
string
file
?:
string
pictures
?:
string
score
:
string
score_details
?:
string
sno_number
:
string
specialty_id
:
string
specialty_name
:
string
status
:
0
|
1
|
2
status_name
:
string
student_id
:
string
student_name
:
string
}
export
interface
FileItem
{
name
:
string
url
:
string
upload_time
:
string
}
src/modules/admin/lab/
score
/views/Index.vue
→
src/modules/admin/lab/
record
/views/Index.vue
浏览文件 @
d88a1b98
<
script
setup
lang=
"ts"
>
<
script
setup
lang=
"ts"
>
import
type
{
RecordItem
}
from
'../types'
import
{
Upload
,
Promotion
}
from
'@element-plus/icons-vue'
import
{
Upload
,
Promotion
}
from
'@element-plus/icons-vue'
import
{
getExperimentRecordList
}
from
'../api'
import
AppList
from
'@/components/base/AppList.vue'
import
{
getExperimentRecordList
,
uploadCheckExperimentRecord
}
from
'../api'
import
{
useFilterList
}
from
'../composables/useFilterList'
import
{
useFilterList
}
from
'../composables/useFilterList'
import
{
useFileDialog
}
from
'@vueuse/core'
const
ScoreDialog
=
defineAsyncComponent
(()
=>
import
(
'../components/ScoreDialog.vue'
))
const
{
courses
,
experiments
,
specialties
,
classes
}
=
useFilterList
()
const
{
courses
,
experiments
,
specialties
,
classes
}
=
useFilterList
()
const
LAB_URL
=
import
.
meta
.
env
.
VITE_LAB_URL
const
LAB_URL
=
import
.
meta
.
env
.
VITE_LAB_URL
const
appList
=
$ref
<
InstanceType
<
typeof
AppList
>
|
null
>
(
null
)
// 列表配置
// 列表配置
const
listOptions
=
$computed
(()
=>
{
const
listOptions
=
$computed
(()
=>
{
return
{
return
{
...
@@ -68,14 +74,41 @@ const listOptions = $computed(() => {
...
@@ -68,14 +74,41 @@ const listOptions = $computed(() => {
]
]
}
}
})
})
// 批量导入
const
{
files
,
open
}
=
useFileDialog
()
function
handleImport
()
{
open
({
accept
:
'.csv, application/vnd.openxmlformats-officedocument.spreadsheetml.sheet, application/vnd.ms-excel'
,
multiple
:
false
})
}
watchEffect
(()
=>
{
if
(
!
files
.
value
?.
length
)
return
const
[
file
]
=
files
.
value
uploadCheckExperimentRecord
({
file
}).
then
(()
=>
{
appList
?.
refetch
()
})
})
let
dialogVisible
=
$ref
(
false
)
const
rowData
=
ref
<
RecordItem
>
()
// 评分
function
handleScore
(
row
:
RecordItem
)
{
rowData
.
value
=
row
dialogVisible
=
true
}
function
onUpdateSuccess
()
{
appList
?.
refetch
()
}
</
script
>
</
script
>
<
template
>
<
template
>
<AppCard
title=
"实验成绩管理"
>
<AppCard
title=
"实验成绩管理"
>
<AppList
v-bind=
"listOptions"
>
<AppList
v-bind=
"listOptions"
ref=
"appList"
>
<template
#
header-buttons
>
<template
#
header-buttons
>
<el-row
justify=
"space-between"
>
<el-row
justify=
"space-between"
>
<el-button
type=
"primary"
round
:icon=
"Upload"
>
批量导入
</el-button>
<el-button
type=
"primary"
round
:icon=
"Upload"
@
click=
"handleImport"
>
批量导入
</el-button>
<a
:href=
"LAB_URL"
target=
"_blank"
>
<a
:href=
"LAB_URL"
target=
"_blank"
>
<el-button
type=
"primary"
round
:icon=
"Promotion"
>
进入实验室
</el-button>
<el-button
type=
"primary"
round
:icon=
"Promotion"
>
进入实验室
</el-button>
</a>
</a>
...
@@ -88,13 +121,19 @@ const listOptions = $computed(() => {
...
@@ -88,13 +121,19 @@ const listOptions = $computed(() => {
<span
:class=
"
{ 'is-info': row.score !== '--' }">
{{
row
.
score
}}
</span>
<span
:class=
"
{ 'is-info': row.score !== '--' }">
{{
row
.
score
}}
</span>
</
template
>
</
template
>
<
template
#
table-x=
"{ row }"
>
<
template
#
table-x=
"{ row }"
>
<el-button
text
type=
"primary"
v-if=
"row.status === 1"
>
打分
</el-button>
<el-button
text
type=
"primary"
v-if=
"row.status === 1"
@
click=
"handleScore(row)"
>
打分
</el-button>
</
template
>
</
template
>
</AppList>
</AppList>
</AppCard>
</AppCard>
<ScoreDialog
v-model=
"dialogVisible"
:data=
"rowData"
@
update=
"onUpdateSuccess"
v-if=
"dialogVisible && rowData"
></ScoreDialog>
</template>
</template>
<
style
lang=
"scss"
>
<
style
lang=
"scss"
scoped
>
.is-success
{
.is-success
{
color
:
#63a103
;
color
:
#63a103
;
}
}
...
...
src/modules/admin/lab/video/views/Index.vue
浏览文件 @
d88a1b98
...
@@ -49,7 +49,6 @@ function handleUpdate(row: VideoItem) {
...
@@ -49,7 +49,6 @@ function handleUpdate(row: VideoItem) {
}
}
function
onUpdateSuccess
()
{
function
onUpdateSuccess
()
{
dialogVisible
=
false
appList
?.
refetch
()
appList
?.
refetch
()
}
}
</
script
>
</
script
>
...
...
src/modules/admin/lab/video/views/View.vue
浏览文件 @
d88a1b98
...
@@ -36,11 +36,12 @@ onMounted(() => {
...
@@ -36,11 +36,12 @@ onMounted(() => {
<el-descriptions-item
label=
"创建人:"
>
{{
detail
.
created_operator_name
}}
</el-descriptions-item>
<el-descriptions-item
label=
"创建人:"
>
{{
detail
.
created_operator_name
}}
</el-descriptions-item>
<el-descriptions-item
label=
"创建时间:"
>
{{
detail
.
created_time
}}
</el-descriptions-item>
<el-descriptions-item
label=
"创建时间:"
>
{{
detail
.
created_time
}}
</el-descriptions-item>
</el-descriptions>
</el-descriptions>
<AppVideoPlayer
<div
style=
"height: 600px"
v-if=
"playUrl"
>
:options=
"
{ sources: [{ src: playUrl, type: 'application/x-mpegURL' }] }"
<AppVideoPlayer
style="width: 100%; height: 600px"
:options=
"
{ sources: [{ src: playUrl, type: 'application/x-mpegURL' }] }"
v-if="playUrl"
style="width: 100%; height: 100%"
>
</AppVideoPlayer>
>
</AppVideoPlayer>
</div>
</
template
>
</
template
>
</AppCard>
</AppCard>
</template>
</template>
src/modules/admin/system/experiment/views/Index.vue
浏览文件 @
d88a1b98
...
@@ -52,7 +52,6 @@ function handleUpdate(row: ExperimentItem) {
...
@@ -52,7 +52,6 @@ function handleUpdate(row: ExperimentItem) {
}
}
function
onUpdateSuccess
()
{
function
onUpdateSuccess
()
{
dialogVisible
=
false
appList
?.
refetch
()
appList
?.
refetch
()
}
}
</
script
>
</
script
>
...
...
src/modules/student/lab/components/Discuss.vue
浏览文件 @
d88a1b98
...
@@ -10,23 +10,32 @@ interface Props {
...
@@ -10,23 +10,32 @@ interface Props {
experiment_id
?:
string
experiment_id
?:
string
}
}
const
props
=
defineProps
<
Props
>
()
const
props
=
defineProps
<
Props
>
()
const
params
=
reactive
({
tag
:
1
,
page
:
0
,
'per-page'
:
10
})
const
params
=
reactive
({
tag
:
1
,
page
:
1
,
'per-page'
:
10
})
let
list
=
$ref
<
ExperimentDiscussType
[]
>
([])
let
list
=
$ref
<
ExperimentDiscussType
[]
>
([])
let
hasMore
=
$ref
(
false
)
let
hasMore
=
$ref
(
false
)
let
isLoading
=
$ref
(
false
)
let
isLoading
=
$ref
(
false
)
function
fetchInfo
()
{
function
fetchInfo
(
isForce
=
false
)
{
if
(
!
props
.
experiment_id
)
return
if
(
!
props
.
experiment_id
)
return
if
(
isForce
)
{
params
.
page
=
1
}
isLoading
=
true
isLoading
=
true
getExperimentDiscussList
({
...
params
,
experiment_id
:
props
.
experiment_id
})
getExperimentDiscussList
({
...
params
,
experiment_id
:
props
.
experiment_id
})
.
then
(
res
=>
{
.
then
(
res
=>
{
list
=
params
.
page
?
[...
list
,
...
res
.
data
.
list
]
:
res
.
data
.
list
list
=
isForce
?
res
.
data
.
list
:
[...
list
,
...
res
.
data
.
list
]
hasMore
=
!!
res
.
data
.
list
.
length
hasMore
=
!!
res
.
data
.
list
.
length
})
})
.
finally
(()
=>
{
.
finally
(()
=>
{
isLoading
=
false
isLoading
=
false
})
})
}
}
watch
(()
=>
props
.
experiment_id
,
handleRefetch
,
{
immediate
:
true
})
watch
(
()
=>
props
.
experiment_id
,
()
=>
{
fetchInfo
(
true
)
},
{
immediate
:
true
}
)
const
isEmpty
=
$computed
(()
=>
{
const
isEmpty
=
$computed
(()
=>
{
return
!
props
.
experiment_id
||
!
list
.
length
return
!
props
.
experiment_id
||
!
list
.
length
...
@@ -34,18 +43,12 @@ const isEmpty = $computed(() => {
...
@@ -34,18 +43,12 @@ const isEmpty = $computed(() => {
const
dialogVisible
=
$ref
(
false
)
const
dialogVisible
=
$ref
(
false
)
// 刷新
function
handleRefetch
()
{
params
.
page
=
0
fetchInfo
()
}
// 滚动加载
// 滚动加载
const
scrollRef
=
ref
<
HTMLElement
>
()
const
scrollRef
=
ref
<
HTMLElement
>
()
useInfiniteScroll
(
useInfiniteScroll
(
scrollRef
,
scrollRef
,
()
=>
{
()
=>
{
if
(
!
hasMore
)
return
if
(
!
hasMore
||
isLoading
)
return
params
.
page
++
params
.
page
++
fetchInfo
()
fetchInfo
()
},
},
...
@@ -55,7 +58,7 @@ useInfiniteScroll(
...
@@ -55,7 +58,7 @@ useInfiniteScroll(
<
template
>
<
template
>
<div
class=
"discuss"
>
<div
class=
"discuss"
>
<el-radio-group
v-model=
"params.tag"
@
change=
"
handleRefetch
"
>
<el-radio-group
v-model=
"params.tag"
@
change=
"
fetchInfo(true)
"
>
<el-radio
:label=
"1"
>
我发起的
</el-radio>
<el-radio
:label=
"1"
>
我发起的
</el-radio>
<el-radio
:label=
"2"
>
我回复的
</el-radio>
<el-radio
:label=
"2"
>
我回复的
</el-radio>
<el-radio
:label=
"3"
>
我的小组
</el-radio>
<el-radio
:label=
"3"
>
我的小组
</el-radio>
...
@@ -67,7 +70,7 @@ useInfiniteScroll(
...
@@ -67,7 +70,7 @@ useInfiniteScroll(
<el-empty
description=
"暂无数据"
v-if=
"isEmpty"
/>
<el-empty
description=
"暂无数据"
v-if=
"isEmpty"
/>
<template
v-else
>
<template
v-else
>
<div
class=
"discuss-scroll"
ref=
"scrollRef"
>
<div
class=
"discuss-scroll"
ref=
"scrollRef"
>
<DiscussItem
v-for=
"item in list"
:key=
"item.id"
:data=
"item"
@
update=
"
handleRefetch
"
></DiscussItem>
<DiscussItem
v-for=
"item in list"
:key=
"item.id"
:data=
"item"
@
update=
"
fetchInfo(true)
"
></DiscussItem>
<div
class=
"tips"
v-if=
"isLoading"
>
<div
class=
"tips"
v-if=
"isLoading"
>
<el-icon
class=
"is-loading"
>
<el-icon
class=
"is-loading"
>
<Loading
/>
<Loading
/>
...
@@ -82,7 +85,7 @@ useInfiniteScroll(
...
@@ -82,7 +85,7 @@ useInfiniteScroll(
<DiscussAddDialog
<DiscussAddDialog
v-model=
"dialogVisible"
v-model=
"dialogVisible"
:experiment_id=
"experiment_id"
:experiment_id=
"experiment_id"
@
update=
"
handleRefetch
"
@
update=
"
fetchInfo(true)
"
v-if=
"dialogVisible && experiment_id"
v-if=
"dialogVisible && experiment_id"
></DiscussAddDialog>
></DiscussAddDialog>
</template>
</template>
...
...
src/stores/menu.ts
浏览文件 @
d88a1b98
import
type
{
IMenuItem
}
from
'@/types'
import
type
{
IMenuItem
}
from
'@/types'
import
{
defineStore
}
from
'pinia'
import
{
defineStore
}
from
'pinia'
import
{
Notebook
,
Tickets
,
VideoCamera
,
ChatSquare
,
Finished
,
ScaleToOriginal
}
from
'@element-plus/icons-vue'
import
{
Notebook
,
VideoCamera
,
ChatSquare
,
Finished
,
ScaleToOriginal
}
from
'@element-plus/icons-vue'
interface
State
{
interface
State
{
studentMenus
:
IMenuItem
[]
studentMenus
:
IMenuItem
[]
...
@@ -42,7 +42,7 @@ const adminMenus: IMenuItem[] = [
...
@@ -42,7 +42,7 @@ const adminMenus: IMenuItem[] = [
{
{
icon
:
markRaw
(
Finished
),
icon
:
markRaw
(
Finished
),
name
:
'实验成绩管理'
,
name
:
'实验成绩管理'
,
path
:
'/admin/lab/
score
'
path
:
'/admin/lab/
record
'
}
}
]
]
},
},
...
...
编写
预览
Markdown
格式
0%
重试
或
添加新文件
添加附件
取消
您添加了
0
人
到此讨论。请谨慎行事。
请先完成此评论的编辑!
取消
请
注册
或者
登录
后发表评论