Skip to content
项目
群组
代码片段
帮助
当前项目
正在载入...
登录 / 注册
切换导航面板
S
saas-lab
项目
项目
详情
活动
周期分析
仓库
仓库
文件
提交
分支
标签
贡献者
图表
比较
统计图
议题
0
议题
0
列表
看板
标记
里程碑
合并请求
0
合并请求
0
CI / CD
CI / CD
流水线
作业
日程
统计图
Wiki
Wiki
代码片段
代码片段
成员
成员
折叠边栏
关闭边栏
活动
图像
聊天
创建新问题
作业
提交
问题看板
Open sidebar
EzijingWeb
saas-lab
Commits
ac045689
提交
ac045689
authored
7月 01, 2026
作者:
王鹏飞
浏览文件
操作
浏览文件
下载
电子邮件补丁
差异文件
feat: 页面响应式设计
上级
608dd550
隐藏空白字符变更
内嵌
并排
正在显示
78 个修改的文件
包含
2163 行增加
和
424 行删除
+2163
-424
base.css
src/assets/styles/base.css
+15
-0
DragPanel.vue
src/components/DragPanel.vue
+26
-3
Preview.vue
src/components/Preview.vue
+57
-2
AppList.vue
src/components/base/AppList.vue
+132
-131
Aside.vue
src/components/layout/Aside.vue
+13
-0
Footer.vue
src/components/layout/Footer.vue
+3
-0
Header.vue
src/components/layout/Header.vue
+84
-1
Index.vue
src/components/layout/Index.vue
+25
-6
Main.vue
src/components/layout/Main.vue
+3
-0
ImportExamDialog.vue
...dules/admin/contest/check/components/ImportExamDialog.vue
+18
-4
ImportScoreDialog.vue
...ules/admin/contest/check/components/ImportScoreDialog.vue
+18
-4
ReportDialog.vue
src/modules/admin/contest/check/components/ReportDialog.vue
+46
-9
ScoreDialog.vue
src/modules/admin/contest/check/components/ScoreDialog.vue
+82
-2
ScoreViewPicturesDialog.vue
...dmin/contest/check/components/ScoreViewPicturesDialog.vue
+17
-1
SyncExamDialog.vue
...modules/admin/contest/check/components/SyncExamDialog.vue
+64
-4
ViewLiveDialog.vue
...modules/admin/contest/check/components/ViewLiveDialog.vue
+1
-0
Detail.vue
src/modules/admin/contest/check/views/Detail.vue
+33
-4
ImportDialog.vue
...les/admin/contest/contestants/components/ImportDialog.vue
+1
-1
Index.vue
src/modules/admin/contest/dashboard/views/Index.vue
+5
-3
DiscussItem.vue
src/modules/admin/contest/discuss/components/DiscussItem.vue
+7
-0
ImportDialog.vue
...modules/admin/contest/experts/components/ImportDialog.vue
+1
-1
FormDialog copy.vue
...odules/admin/contest/items/components/FormDialog copy.vue
+5
-5
FormDialog.vue
src/modules/admin/contest/items/components/FormDialog.vue
+5
-5
ViewExperimentFormDialog.vue
...min/contest/items/components/ViewExperimentFormDialog.vue
+1
-1
Edit.vue
src/modules/admin/contest/items/views/Edit.vue
+2
-1
View.vue
src/modules/admin/contest/items/views/View.vue
+2
-1
ScorePublishDialog.vue
...les/admin/contest/score/components/ScorePublishDialog.vue
+17
-5
index.ts
src/modules/admin/lab/book/index.ts
+0
-4
Index.vue
src/modules/admin/lab/book/views/Index.vue
+2
-0
Index.vue
src/modules/admin/lab/case-auth/views/Index.vue
+2
-2
index.ts
src/modules/admin/lab/case/index.ts
+0
-4
Index.vue
src/modules/admin/lab/case/views/Index.vue
+2
-0
History.vue
src/modules/admin/lab/dashboard/components/History.vue
+4
-4
Live.vue
src/modules/admin/lab/dashboard/components/Live.vue
+4
-4
DiscussItem.vue
src/modules/admin/lab/discuss/components/DiscussItem.vue
+7
-0
index.ts
src/modules/admin/lab/example/index.ts
+0
-4
CompetitionRuleDialog.vue
...admin/lab/experiment/components/CompetitionRuleDialog.vue
+82
-1
GradeRulesDialog.vue
...ules/admin/lab/experiment/components/GradeRulesDialog.vue
+136
-4
GroupQuestion.vue
...min/lab/experiment/components/Questions/GroupQuestion.vue
+2
-1
JourneyQuestion.vue
...n/lab/experiment/components/Questions/JourneyQuestion.vue
+2
-1
MaterialQuestion.vue
.../lab/experiment/components/Questions/MaterialQuestion.vue
+2
-1
TagQuestion.vue
...admin/lab/experiment/components/Questions/TagQuestion.vue
+2
-1
ReportDesignAttachment.vue
...dmin/lab/experiment/components/ReportDesignAttachment.vue
+4
-4
ReportDesignContent.vue
...s/admin/lab/experiment/components/ReportDesignContent.vue
+4
-4
ReportDesignPaper.vue
...les/admin/lab/experiment/components/ReportDesignPaper.vue
+4
-4
ReportDesignQuestion.vue
.../admin/lab/experiment/components/ReportDesignQuestion.vue
+4
-4
ReportPreview.vue
...modules/admin/lab/experiment/components/ReportPreview.vue
+10
-10
Questions.vue
src/modules/admin/lab/experiment/views/Questions.vue
+1
-0
Report.vue
src/modules/admin/lab/experiment/views/Report.vue
+5
-5
View.vue
src/modules/admin/lab/experiment/views/View.vue
+13
-0
ImportDialog.vue
src/modules/admin/lab/score/components/ImportDialog.vue
+1
-1
Question.vue
src/modules/admin/lab/score/components/Question.vue
+48
-1
ScoreDialog.vue
src/modules/admin/lab/score/components/ScoreDialog.vue
+119
-8
ScoreViewPicturesDialog.vue
...es/admin/lab/score/components/ScoreViewPicturesDialog.vue
+21
-1
Report.vue
src/modules/admin/lab/score/views/Report.vue
+94
-11
Index.vue
src/modules/admin/lab/video/views/Index.vue
+3
-0
Index.vue
src/modules/ai/views/Index.vue
+9
-3
Index.vue
src/modules/drawLots/views/Index.vue
+129
-4
AdminHome.vue
src/modules/home/components/AdminHome.vue
+24
-5
StudentHome.vue
src/modules/home/components/StudentHome.vue
+19
-5
TeacherHome.vue
src/modules/home/components/TeacherHome.vue
+24
-5
Total.vue
src/modules/home/components/Total.vue
+22
-0
Game.vue
src/modules/home/views/Game.vue
+16
-3
Index.vue
src/modules/register/views/Index.vue
+22
-9
DiscussItem.vue
src/modules/student/contest/components/DiscussItem.vue
+7
-0
Index.vue
src/modules/student/contest/views/Index.vue
+2
-0
Join.vue
src/modules/student/contest/views/Join.vue
+1
-1
Score.vue
src/modules/student/contest/views/Score.vue
+63
-5
BindCaptureDialog.vue
src/modules/student/lab/components/BindCaptureDialog.vue
+58
-1
DiscussItem.vue
src/modules/student/lab/components/DiscussItem.vue
+7
-0
Info.vue
src/modules/student/lab/components/Info.vue
+5
-5
ReportFilePreview.vue
src/modules/student/lab/components/ReportFilePreview.vue
+6
-6
ReportPreview.vue
src/modules/student/lab/components/ReportPreview.vue
+84
-10
Result.vue
src/modules/student/lab/components/Result.vue
+34
-0
ResultScoreDialog.vue
src/modules/student/lab/components/ResultScoreDialog.vue
+80
-7
Competition.vue
src/modules/student/lab/views/Competition.vue
+26
-1
Index.vue
src/modules/student/lab/views/Index.vue
+192
-65
Report.vue
src/modules/student/lab/views/Report.vue
+72
-11
没有找到文件。
src/assets/styles/base.css
浏览文件 @
ac045689
...
...
@@ -83,3 +83,18 @@ textarea:focus {
margin-right
:
0
!important
;
border-bottom
:
1px
solid
#e6e6e6
;
}
/* ========== 全局响应式 ========== */
img
,
video
{
max-width
:
100%
;
height
:
auto
;
}
@media
(
max-width
:
767px
)
{
.el-dialog
{
width
:
92%
!important
;
max-width
:
500px
;
margin-top
:
10vh
!important
;
}
.el-message-box
{
width
:
90%
!important
;
max-width
:
420px
;
}
.el-drawer
{
width
:
90%
!important
;
}
.el-form--inline
.el-form-item
{
margin-right
:
0
;
}
.el-table
{
font-size
:
13px
;
}
}
@media
(
min-width
:
768px
)
{
.hide-on-desktop
{
display
:
none
!important
;
}
}
@media
(
max-width
:
767px
)
{
.hide-on-mobile
{
display
:
none
!important
;
}
}
src/components/DragPanel.vue
浏览文件 @
ac045689
<
script
setup
lang=
"ts"
>
import
{
useStorage
}
from
'@vueuse/core'
import
{
use
MediaQuery
,
use
Storage
}
from
'@vueuse/core'
const
emit
=
defineEmits
<
{
(
e
:
'resize'
):
void
}
>
()
defineProps
<
{
isLeftShow
?:
boolean
}
>
()
const
isMobile
=
useMediaQuery
(
'(max-width: 767px)'
)
const
leftPanelVisible
=
$ref
<
boolean
>
(
true
)
const
leftPanelWidth
=
useStorage
(
'leftPanelWidth'
,
400
)
...
...
@@ -17,6 +18,7 @@ const leftPanelWidthText = $computed(() => {
// 因为鼠标移动文档和iframe影响不能move
let
dragFlag
=
$ref
(
false
)
function
useDrag
()
{
if
(
isMobile
.
value
)
return
const
dragDom
=
document
.
getElementById
(
'panel-resize'
)
if
(
dragDom
)
{
dragDom
.
addEventListener
(
'mousedown'
,
(
e
)
=>
{
...
...
@@ -47,8 +49,8 @@ onMounted(() => {
<div
class=
"drag-panel-left-content"
>
<slot
name=
"left"
></slot>
</div>
<div
class=
"panel-resize"
id=
"panel-resize"
></div>
<div
class=
"panel-icon"
@
click=
"leftPanelVisible = !leftPanelVisible"
>
<div
class=
"panel-resize"
id=
"panel-resize"
v-if=
"!isMobile"
></div>
<div
class=
"panel-icon"
@
click=
"leftPanelVisible = !leftPanelVisible"
v-if=
"!isMobile"
>
<svg
xmlns=
"http://www.w3.org/2000/svg"
viewBox=
"0 0 16 86"
aria-hidden=
"true"
width=
"16"
height=
"86"
>
<g
fill=
"none"
fill-rule=
"evenodd"
>
<path
...
...
@@ -141,4 +143,25 @@ onMounted(() => {
z-index
:
999
;
opacity
:
0
;
}
@media
(
max-width
:
767px
)
{
.drag-panel
{
flex-direction
:
column
;
height
:
auto
;
gap
:
12px
;
}
.drag-panel-left
{
width
:
100%
!
important
;
padding
:
12px
;
margin-right
:
0
;
}
.drag-panel-left-content
{
height
:
auto
;
overflow
:
visible
;
}
.drag-panel-right
{
width
:
100%
;
height
:
auto
;
min-width
:
0
;
}
}
</
style
>
src/components/Preview.vue
浏览文件 @
ac045689
<
script
setup
lang=
"ts"
>
import
{
useMediaQuery
}
from
'@vueuse/core'
interface
Props
{
url
:
string
}
const
props
=
defineProps
<
Props
>
()
const
isMobile
=
useMediaQuery
(
'(max-width: 767px)'
)
// 文件扩展名
const
fileExtensionName
=
$computed
(()
=>
{
return
props
.
url
.
split
(
'.'
).
pop
()
||
''
})
const
isOfficeFile
=
$computed
(()
=>
{
return
[
'pptx'
,
'doc'
,
'docx'
,
'xls'
,
'xlsx'
].
includes
(
fileExtensionName
)
})
// office文件
const
officeUrl
=
$computed
(()
=>
{
return
[
'pptx'
,
'doc'
,
'docx'
,
'xls'
,
'xlsx'
].
includes
(
fileExtensionName
)
return
isOfficeFile
?
`https://view.officeapps.live.com/op/view.aspx?src=
${
props
.
url
}
`
:
''
})
const
fileName
=
$computed
(()
=>
{
const
[
path
]
=
props
.
url
.
split
(
'?'
)
return
decodeURIComponent
(
path
.
split
(
'/'
).
pop
()
||
'文档'
)
})
</
script
>
<
template
>
<iframe
allowfullscreen
:src=
"officeUrl"
:key=
"officeUrl"
frameborder=
"0"
style=
"width: 100%; height: 100%"
v-if=
"officeUrl"
></iframe>
<div
v-if=
"isMobile && officeUrl"
class=
"preview-mobile-office"
>
<div
class=
"preview-mobile-office__title"
>
{{
fileName
}}
</div>
<div
class=
"preview-mobile-office__desc"
>
移动端不内嵌 Office 文档预览,点击后在新页面打开
</div>
<a
:href=
"officeUrl"
target=
"_blank"
rel=
"noopener noreferrer"
class=
"preview-mobile-office__link"
>
打开文档
</a>
</div>
<iframe
allowfullscreen
:src=
"officeUrl"
:key=
"officeUrl"
frameborder=
"0"
style=
"width: 100%; height: 100%"
v-else-if=
"officeUrl"
></iframe>
<object
:data=
"url"
style=
"width: 100%; height: 100%; object-fit: none"
v-else
></object>
</
template
>
<
style
lang=
"scss"
scoped
>
.preview-mobile-office
{
height
:
100%
;
min-height
:
240px
;
padding
:
24px
16px
;
background
:
#fff
;
border-radius
:
12px
;
display
:
flex
;
flex-direction
:
column
;
align-items
:
center
;
justify-content
:
center
;
text-align
:
center
;
}
.preview-mobile-office__title
{
font-size
:
16px
;
font-weight
:
600
;
line-height
:
24px
;
color
:
#333
;
word-break
:
break-word
;
}
.preview-mobile-office__desc
{
margin-top
:
8px
;
font-size
:
14px
;
line-height
:
22px
;
color
:
#666
;
}
.preview-mobile-office__link
{
margin-top
:
16px
;
min-width
:
120px
;
padding
:
10px
20px
;
border-radius
:
999px
;
background
:
var
(
--
main-color
);
color
:
#fff
;
line-height
:
20px
;
}
</
style
>
src/components/base/AppList.vue
浏览文件 @
ac045689
<
script
setup
lang=
"ts"
>
import
{
Search
,
RefreshLeft
}
from
'@element-plus/icons-vue'
import
{
useMediaQuery
}
from
'@vueuse/core'
const
isMobile
=
useMediaQuery
(
'(max-width: 767px)'
)
const
cardColumns
=
computed
(()
=>
props
.
columns
.
filter
((
c
)
=>
c
.
type
!==
'index'
&&
c
.
type
!==
'selection'
&&
c
.
slots
!==
'table-x'
))
const
hasSelectionColumn
=
computed
(()
=>
props
.
columns
.
some
((
c
)
=>
c
.
type
===
'selection'
))
const
hasActionsColumn
=
computed
(()
=>
props
.
columns
.
some
((
c
)
=>
c
.
slots
===
'table-x'
))
interface
IRemoteProps
{
params
?:
any
...
...
@@ -16,13 +23,17 @@ interface Props {
data
?:
any
[]
hasPagination
?:
boolean
limit
?:
number
hasLoading
?:
boolean
mobileCards
?:
boolean
}
const
props
=
withDefaults
(
defineProps
<
Props
>
(),
{
hasPagination
:
true
,
hasLoading
:
true
,
limit
:
10
,
filters
:
()
=>
[],
columns
:
()
=>
[],
data
:
()
=>
[]
data
:
()
=>
[],
mobileCards
:
true
,
})
const
filterFormRef
=
ref
()
...
...
@@ -32,97 +43,64 @@ const dataList = ref<any[]>([])
const
page
=
reactive
({
total
:
0
,
size
:
props
.
limit
,
currentPage
:
1
})
const
params
=
reactive
({
...
props
.
remote
?.
params
})
watchEffect
(()
=>
{
Object
.
assign
(
params
,
props
.
remote
?.
params
)
})
watchEffect
(()
=>
{
dataList
.
value
=
props
.
data
||
[]
})
watchEffect
(()
=>
{
Object
.
assign
(
params
,
props
.
remote
?.
params
)
})
watchEffect
(()
=>
{
dataList
.
value
=
props
.
data
||
[]
})
// 获取数据
const
fetchList
=
(
isReset
=
false
)
=>
{
/**
* @param function httpRequest api接口
* @param function beforeRequest 接口请求之前
* @param function callback 接口请求成功回调
*/
const
{
httpRequest
,
beforeRequest
,
callback
}
=
props
.
remote
||
{}
if
(
!
httpRequest
)
{
return
}
// 参数设置
if
(
!
httpRequest
)
return
let
requestParams
=
{
...
params
}
// 翻页参数设置
if
(
props
.
hasPagination
)
{
requestParams
.
page
=
page
.
currentPage
requestParams
[
'per-page'
]
=
page
.
size
}
// 接口请求之前
if
(
beforeRequest
)
{
requestParams
=
beforeRequest
(
requestParams
,
isReset
)
}
if
(
beforeRequest
)
requestParams
=
beforeRequest
(
requestParams
,
isReset
)
for
(
const
key
in
params
)
{
if
(
params
[
key
]
===
''
||
params
[
key
]
===
undefined
||
params
[
key
]
===
undefined
)
{
delete
params
[
key
]
}
if
(
params
[
key
]
===
''
||
params
[
key
]
===
undefined
)
delete
params
[
key
]
}
loading
.
value
=
true
return
(
httpRequest
(
requestParams
)
.
then
((
res
:
any
)
=>
{
const
{
list
=
[],
total
=
0
}
=
callback
?
callback
(
res
.
data
,
requestParams
)
:
res
.
data
||
{}
page
.
total
=
parseInt
(
total
)
dataList
.
value
=
list
})
// .catch(() => {
// page.total = 0
// dataList.value = []
// })
.
finally
(()
=>
{
loading
.
value
=
false
})
)
return
httpRequest
(
requestParams
)
.
then
((
res
:
any
)
=>
{
const
{
list
=
[],
total
=
0
}
=
callback
?
callback
(
res
.
data
,
requestParams
)
:
res
.
data
||
{}
page
.
total
=
parseInt
(
total
)
dataList
.
value
=
list
})
.
finally
(()
=>
{
loading
.
value
=
false
})
}
// 搜索
const
search
=
()
=>
{
page
.
currentPage
=
1
return
fetchList
()
}
const
search
=
()
=>
{
page
.
currentPage
=
1
;
return
fetchList
()
}
const
reset
=
()
=>
{
filterFormRef
.
value
?.
resetFields
();
page
.
currentPage
=
1
;
return
fetchList
(
true
)
}
const
refetch
=
(
isForce
=
false
)
=>
isForce
?
reset
()
:
fetchList
()
const
pageSizeChange
=
(
value
:
number
)
=>
{
page
.
currentPage
=
1
;
page
.
size
=
value
;
fetchList
()
}
// 重置
const
reset
=
()
=>
{
// 清空筛选条件
filterFormRef
.
value
?.
resetFields
()
// 初始化页码
page
.
currentPage
=
1
// 刷新列表
return
fetchList
(
true
)
// 移动端多选
const
mobileSelection
=
ref
<
any
[]
>
([])
const
mobileIsAllSelected
=
computed
(()
=>
dataList
.
value
.
length
>
0
&&
mobileSelection
.
value
.
length
===
dataList
.
value
.
length
)
function
toggleMobileSelectAll
()
{
mobileSelection
.
value
=
mobileIsAllSelected
.
value
?
[]
:
[...
dataList
.
value
]
emitSelectionChange
()
}
// 刷新
const
refetch
=
(
isForce
=
false
)
=>
{
return
isForce
?
reset
()
:
fetchList
()
function
toggleMobileSelectRow
(
row
:
any
)
{
const
idx
=
mobileSelection
.
value
.
findIndex
((
r
)
=>
r
===
row
)
if
(
idx
>
-
1
)
mobileSelection
.
value
.
splice
(
idx
,
1
)
else
mobileSelection
.
value
.
push
(
row
)
emitSelectionChange
()
}
// 页数改变
const
pageSizeChange
=
(
value
:
number
)
=>
{
page
.
currentPage
=
1
page
.
size
=
value
fetchList
()
function
isMobileRowSelected
(
row
:
any
)
{
return
mobileSelection
.
value
.
some
((
r
)
=>
r
===
row
)
}
function
emitSelectionChange
()
{
const
attrs
=
useAttrs
()
const
handler
=
(
attrs
as
Record
<
string
,
unknown
>
).
onSelectionChange
as
((
val
:
any
[])
=>
void
)
|
undefined
if
(
handler
)
handler
(
mobileSelection
.
value
)
}
onMounted
(()
=>
{
fetchList
()
})
onMounted
(()
=>
{
fetchList
()
})
defineExpose
({
refetch
,
tableRef
})
</
script
>
<
template
>
<div
class=
"table-list"
>
<div
class=
"table-list-hd"
>
<!-- 筛选 -->
<div
class=
"table-list-filter"
v-if=
"filters && filters.length"
>
<el-form
:inline=
"true"
:model=
"params"
v-bind=
"filterForm"
ref=
"filterFormRef"
@
submit
.
prevent
>
<template
v-for=
"item in filters"
:key=
"item.prop"
>
...
...
@@ -131,15 +109,10 @@ defineExpose({ refetch, tableRef })
<slot
:name=
"item.slots"
v-bind=
"
{ params }">
</slot>
</
template
>
<
template
v-else
>
<!-- input -->
<el-input
v-model=
"params[item.prop]"
v-bind=
"item"
clearable
@
change=
"search"
style=
"width: 200px"
v-if=
"item.type === 'input'"
/>
<!-- select -->
<el-select
v-model=
"params[item.prop]"
v-bind=
"item"
filterable
clearable
@
change=
"search"
v-if=
"item.type === 'select'"
>
<el-option
:label=
"option[item.labelKey] || option.label"
:value=
"option[item.valueKey] || option.value"
v-for=
"(option, index) in item.options"
:key=
"index"
/>
<el-option
:label=
"option[item.labelKey] || option.label"
:value=
"option[item.valueKey] || option.value"
v-for=
"(option, index) in item.options"
:key=
"index"
/>
</el-select>
</
template
>
</el-form-item>
...
...
@@ -150,14 +123,15 @@ defineExpose({ refetch, tableRef })
</el-form-item>
</el-form>
</div>
<!-- <div class="table-list-hd-aside"><slot name="header-aside" /></div> -->
</div>
<div
class=
"table-list-buttons"
><slot
name=
"header-buttons"
></slot></div>
<slot></slot>
<!-- 主体 -->
<div
class=
"table-list-bd"
>
<slot
name=
"body"
v-bind=
"{ data: dataList }"
>
<el-table
stripe
:header-cell-style=
"{ background: '#ededed' }"
:data=
"dataList"
v-loading=
"loading"
v-bind=
"$attrs"
ref=
"tableRef"
>
<!-- 桌面端表格 -->
<el-table
v-if=
"!isMobile || !mobileCards"
stripe
:header-cell-style=
"{ background: '#ededed' }"
:data=
"dataList"
v-loading=
"loading && hasLoading"
v-bind=
"$attrs"
ref=
"tableRef"
>
<el-table-column
align=
"center"
v-bind=
"item || {}"
v-for=
"item in columns"
:key=
"item.prop"
>
<
template
#
default=
"scope"
v-if=
"item.slots || item.computed"
>
<slot
:name=
"item.slots"
v-bind=
"scope"
v-if=
"item.slots"
></slot>
...
...
@@ -165,74 +139,101 @@ defineExpose({ refetch, tableRef })
</
template
>
</el-table-column>
</el-table>
<!-- 移动端卡片 -->
<div
v-if=
"isMobile && mobileCards"
class=
"mobile-card-list"
v-loading=
"loading && hasLoading"
>
<div
class=
"mobile-card-select-all"
v-if=
"hasSelectionColumn && dataList.length"
>
<el-checkbox
:model-value=
"mobileIsAllSelected"
@
change=
"toggleMobileSelectAll"
>
全选({{ mobileSelection.length }}/{{ dataList.length }})
</el-checkbox>
</div>
<div
class=
"mobile-card"
v-for=
"(row, rowIndex) in dataList"
:key=
"rowIndex"
:class=
"{ 'is-selected': isMobileRowSelected(row) }"
>
<div
class=
"mobile-card-body"
>
<div
class=
"mobile-card-checkbox"
v-if=
"hasSelectionColumn"
@
click
.
stop=
"toggleMobileSelectRow(row)"
>
<el-checkbox
:model-value=
"isMobileRowSelected(row)"
/>
</div>
<div
class=
"mobile-card-rows"
>
<div
class=
"mobile-card-row"
v-for=
"col in cardColumns"
:key=
"col.prop || col.label"
>
<span
class=
"mobile-card-label"
>
{{ col.label }}
</span>
<span
class=
"mobile-card-colon"
>
:
</span>
<span
class=
"mobile-card-value"
>
<slot
v-if=
"col.slots && $slots[col.slots]"
:name=
"col.slots"
v-bind=
"{ row, $index: rowIndex }"
></slot>
<div
v-html=
"col.computed({ row, $index: rowIndex })"
v-else-if=
"col.computed"
></div>
<
template
v-else
>
{{
row
[
col
.
prop
]
??
'-'
}}
</
template
>
</span>
</div>
</div>
</div>
<div
class=
"mobile-card-ft"
v-if=
"hasActionsColumn"
>
<slot
name=
"table-x"
v-bind=
"{ row, $index: rowIndex }"
></slot>
</div>
</div>
<el-empty
v-if=
"!dataList.length && !loading"
description=
"暂无数据"
/>
</div>
</slot>
</div>
<!-- 底部 -->
<div
class=
"table-list-ft"
>
<div>
<slot
name=
"footer"
></slot>
</div>
<el-pagination
class=
"table-list-pagination"
background
layout=
"total, sizes, prev, pager, next, jumper"
:page-sizes=
"[10, 20, 30, 50]"
:page-size=
"page.size"
:total=
"page.total"
v-model:currentPage=
"page.currentPage"
@
size-change=
"pageSizeChange"
@
current-change=
"fetchList()"
:hide-on-single-page=
"true"
v-if=
"hasPagination"
>
<div><slot
name=
"footer"
></slot></div>
<el-pagination
class=
"table-list-pagination"
background
:layout=
"isMobile ? 'prev, pager, next' : 'total, sizes, prev, pager, next, jumper'"
:page-sizes=
"[10, 20, 30, 50]"
:page-size=
"page.size"
:total=
"page.total"
:pager-count=
"isMobile ? 5 : 7"
v-model:currentPage=
"page.currentPage"
@
size-change=
"pageSizeChange"
@
current-change=
"fetchList()"
:hide-on-single-page=
"true"
v-if=
"hasPagination"
>
</el-pagination>
</div>
</div>
</template>
<
style
lang=
"scss"
>
// .table-list {
// height: 100%;
// display: flex;
// flex-direction: column;
// box-sizing: border-box;
// }
.table-list-hd
{
display
:
flex
;
margin-bottom
:
20px
;
&
:empty
{
display
:
none
;
}
}
.table-list-filter
{
flex
:
1
;
// padding: 30px 30px 10px;
// background: #f8f8f8;
// border-radius: 12px;
}
.table-list-buttons
{
margin-bottom
:
20px
;
&
:empty
{
display
:
none
;
}
.table-list-hd
{
display
:
flex
;
margin-bottom
:
20px
;
&
:empty
{
display
:
none
;
}
}
.table-list-filter
{
flex
:
1
;
}
.table-list-buttons
{
margin-bottom
:
20px
;
display
:
flex
;
flex-wrap
:
wrap
;
gap
:
8px
;
&
:empty
{
display
:
none
;
}
}
.table-list-ft
{
padding
:
10px
0
;
display
:
flex
;
align-items
:
center
;
justify-content
:
space-between
;
}
.table-list-pagination
{
text-align
:
right
;
}
.el-table-column--selection
.cell
{
padding
:
0
14px
!
important
;
}
.table-list-bd
.el-button
{
a
{
margin
:
-8px
-15px
;
padding
:
8px
15px
;
}
&
.is-text
{
padding
:
8px
;
}
&
.is-text
+
.is-text
{
margin-left
:
0
;
}
&
.is-plain
{
--el-button-border-color
:
var
(
--
el-button-bg-color
);
}
}
// .table-list-bd {
// flex: 1;
// }
.table-list-ft
{
padding
:
10px
0
;
display
:
flex
;
align-items
:
center
;
justify-content
:
space-between
;
// 移动端卡片
.mobile-card-list
{
min-height
:
120px
;
}
.mobile-card-select-all
{
padding
:
8px
0
;
.el-checkbox
{
font-size
:
13px
;
color
:
#666
;
}
}
.mobile-card
{
background
:
#fff
;
border-radius
:
8px
;
border
:
1px
solid
#eee
;
margin-bottom
:
12px
;
overflow
:
hidden
;
display
:
flex
;
flex-direction
:
column
;
&
.is-selected
{
border-color
:
var
(
--
main-color
);
background
:
#fef5f7
;
}
}
.table-list-pagination
{
text-align
:
right
;
.mobile-card-body
{
flex
:
1
;
padding
:
14px
16px
;
display
:
flex
;
align-items
:
flex-start
;
gap
:
10px
;
}
.mobile-card-checkbox
{
flex-shrink
:
0
;
padding-top
:
2px
;
}
.mobile-card-rows
{
flex
:
1
;
min-width
:
0
;
}
.mobile-card-row
{
display
:
flex
;
padding
:
5px
0
;
border-bottom
:
1px
solid
#f5f5f5
;
line-height
:
1
.6
;
&
:last-child
{
border-bottom
:
none
;
}
}
.el-table-column--selection
.cell
{
padding
:
0
14px
!
important
;
.mobile-card-label
{
flex-shrink
:
0
;
color
:
#999
;
font-size
:
14px
;
}
.mobile-card-colon
{
color
:
#999
;
font-size
:
14px
;
margin-right
:
6px
;
}
.mobile-card-value
{
flex
:
1
;
font-size
:
14px
;
color
:
#333
;
min-width
:
0
;
word-break
:
break-all
;
}
.mobile-card-ft
{
padding
:
8px
16px
;
display
:
flex
;
gap
:
2px
;
flex-wrap
:
wrap
;
background
:
#fafafa
;
border-top
:
1px
solid
#f0f0f0
;
.el-button.is-text
{
padding
:
4px
6px
;
font-size
:
12px
;
}
}
.el-button
a
{
margin
:
-8px
-15px
;
padding
:
8px
15px
;
// 移动端
@media
(
max-width
:
767px
)
{
.table-list-hd
.el-form-item__label
{
display
:
none
;
}
.table-list-filter
{
.el-form
,
.el-form--inline
{
width
:
100%
;
}
.el-form-item
{
width
:
100%
;
margin-right
:
0
;
margin-bottom
:
8px
;
}
.el-form-item__content
{
width
:
100%
;
}
.el-input
,
.el-select
,
.el-cascader
{
width
:
100%
!
important
;
}
.filter-buttons
{
width
:
auto
;
}
}
.table-list-ft
{
flex-direction
:
column
;
gap
:
8px
;
padding
:
8px
0
;
}
.table-list-pagination
.el-pagination
{
justify-content
:
center
;
}
.el-pager
.number
{
min-width
:
28px
;
height
:
28px
;
line-height
:
28px
;
font-size
:
13px
;
}
.btn-prev
,
.btn-next
{
height
:
28px
;
min-width
:
28px
;
}
}
</
style
>
src/components/layout/Aside.vue
浏览文件 @
ac045689
...
...
@@ -82,6 +82,13 @@ function handleClick(path: string) {
flex
:
0
0
200px
;
box-sizing
:
content-box
;
}
// 移动端 overlay
.app-aside--overlay
{
position
:
fixed
;
top
:
0
;
left
:
0
;
bottom
:
0
;
z-index
:
2000
;
width
:
220px
;
flex
:
none
;
transform
:
translateX
(
-100%
);
transition
:
transform
0
.3s
ease
;
&
.app-aside--visible
{
transform
:
translateX
(
0
);
}
}
.nav
{
position
:
fixed
;
width
:
200px
;
...
...
@@ -132,4 +139,10 @@ function handleClick(path: string) {
// border-radius: 8px;
// }
}
// 平板收缩
@media
(
max-width
:
1024px
)
{
.app-aside
:not
(
.app-aside--overlay
)
{
width
:
180px
;
flex
:
0
0
180px
;
}
.nav
{
width
:
180px
;
}
}
</
style
>
src/components/layout/Footer.vue
浏览文件 @
ac045689
...
...
@@ -17,4 +17,7 @@ export default { name: 'AppFooter' }
text-align
:
center
;
background-color
:
#fff
;
}
@media
(
max-width
:
767px
)
{
.app-footer
{
padding
:
8px
12px
;
font-size
:
12px
;
line-height
:
20px
;
}
}
</
style
>
src/components/layout/Header.vue
浏览文件 @
ac045689
...
...
@@ -4,7 +4,7 @@ export default { name: 'AppHeader' }
<
script
setup
lang=
"ts"
>
import
type
{
IMenuItem
}
from
'@/types'
import
{
CaretBottom
}
from
'@element-plus/icons-vue'
import
{
CaretBottom
,
Expand
}
from
'@element-plus/icons-vue'
import
{
useUserStore
}
from
'@/stores/user'
import
{
useMenuStore
}
from
'@/stores/menu'
import
{
useAppConfig
}
from
'@/composables/useAppConfig'
...
...
@@ -12,6 +12,10 @@ const appConfig = useAppConfig()
const
route
=
useRoute
()
const
router
=
useRouter
()
const
mobileMenuVisible
=
ref
(
false
)
const
toggleMobileMenu
=
()
=>
{
mobileMenuVisible
.
value
=
!
mobileMenuVisible
.
value
}
const
closeMobileMenu
=
()
=>
{
mobileMenuVisible
.
value
=
false
}
const
userStore
=
useUserStore
()
const
userInfo
=
userStore
.
user
...
...
@@ -60,6 +64,9 @@ function handleClick(path: string) {
<
template
>
<header
class=
"app-header"
>
<div
class=
"app-header-left"
>
<button
class=
"sidebar-toggle"
@
click=
"toggleMobileMenu"
>
<el-icon
:size=
"22"
><Expand
/></el-icon>
</button>
<template
v-if=
"appConfig.logo"
>
<div
class=
"logo"
>
<router-link
to=
"/"
><img
:src=
"appConfig.logo"
/></router-link>
...
...
@@ -125,6 +132,28 @@ function handleClick(path: string) {
</el-dropdown>
</div>
</header>
<el-drawer
v-model=
"mobileMenuVisible"
direction=
"ltr"
:size=
"260"
:with-header=
"false"
:z-index=
"2100"
>
<div
class=
"mobile-drawer"
>
<div
class=
"mobile-drawer-hd"
>
<img
v-if=
"appConfig.logo"
:src=
"appConfig.logo"
class=
"mobile-drawer-logo"
/>
<span
class=
"mobile-drawer-title"
>
{{ appConfig.title }}
</span>
</div>
<div
class=
"mobile-drawer-bd"
>
<
template
v-for=
"item in menus"
:key=
"item.path"
>
<div
class=
"mobile-nav-group"
v-permission=
"item.tag"
>
<div
class=
"mobile-nav-parent"
@
click=
"handleClick(item.path)"
>
{{
item
.
name
}}
</div>
<template
v-if=
"item.children"
>
<div
class=
"mobile-nav-child"
v-for=
"subitem in item.children"
:key=
"subitem.path"
v-permission=
"subitem.tag"
:class=
"
{ 'is-active': route.path.includes(subitem.path) }"
@click="handleClick(subitem.path); closeMobileMenu()">
{{
subitem
.
name
}}
</div>
</
template
>
</div>
</template>
</div>
</div>
</el-drawer>
</template>
<
style
lang=
"scss"
>
...
...
@@ -280,4 +309,58 @@ function handleClick(path: string) {
.app-header-user-buttons
{
padding-top
:
16px
;
}
// ========== 响应式 ==========
.sidebar-toggle
{
display
:
none
;
align-items
:
center
;
justify-content
:
center
;
width
:
36px
;
height
:
36px
;
margin-right
:
12px
;
border
:
none
;
border-radius
:
6px
;
background
:
transparent
;
color
:
#333
;
cursor
:
pointer
;
flex-shrink
:
0
;
&
:hover
{
background
:
#f0f0f0
;
}
}
// 平板 (≤1024px)
@media
(
max-width
:
1024px
)
{
.app-header
{
padding
:
0
12px
;
}
.app-header-left
.app-name
{
font-size
:
22px
;
}
.app-header-nav
{
margin
:
0
20px
;
--el-menu-item-font-size
:
14px
;
.el-menu--horizontal
>
.el-menu-item
{
margin-right
:
24px
;
}
}
}
// 移动端 (≤767px)
@media
(
max-width
:
767px
)
{
.sidebar-toggle
{
display
:
flex
;
}
.app-header
{
padding
:
0
12px
;
height
:
52px
;
}
.app-header-left
{
.logo
{
height
:
32px
;
}
.line
{
margin
:
0
10px
;
height
:
22px
;
}
.app-name
{
font-size
:
16px
;
line-height
:
22px
;
}
}
.app-header-nav
{
display
:
none
;
}
.app-header-right
.user
{
height
:
36px
;
p
{
display
:
none
;
}
}
.app-header-right
.avatar
{
width
:
32px
;
height
:
32px
;
}
}
// 移动端抽屉
.mobile-drawer
{
display
:
flex
;
flex-direction
:
column
;
height
:
100%
;
}
.mobile-drawer-hd
{
display
:
flex
;
align-items
:
center
;
gap
:
10px
;
padding
:
16px
;
border-bottom
:
1px
solid
#eee
;
}
.mobile-drawer-logo
{
height
:
36px
;
}
.mobile-drawer-title
{
font-size
:
18px
;
font-weight
:
600
;
color
:
#333
;
}
.mobile-drawer-bd
{
flex
:
1
;
overflow-y
:
auto
;
padding
:
8px
0
;
}
.mobile-nav-group
{
border-bottom
:
1px
solid
#f5f5f5
;
&
:last-child
{
border-bottom
:
none
;
}
}
.mobile-nav-parent
{
padding
:
14px
20px
;
font-size
:
15px
;
font-weight
:
600
;
color
:
#333
;
cursor
:
pointer
;
&
:active
{
background
:
#f5f5f5
;
}
}
.mobile-nav-child
{
padding
:
12px
20px
12px
36px
;
font-size
:
14px
;
color
:
#666
;
cursor
:
pointer
;
border-top
:
1px
solid
#fafafa
;
&
:active
{
background
:
#f5f5f5
;
}
&
.is-active
{
color
:
var
(
--
main-color
);
background
:
#fef5f7
;
}
}
</
style
>
src/components/layout/Index.vue
浏览文件 @
ac045689
...
...
@@ -7,20 +7,30 @@ import AppHeader from './Header.vue'
import
AppAside
from
'./Aside.vue'
import
AppMain
from
'./Main.vue'
import
AppFooter
from
'./Footer.vue'
import
{
useMediaQuery
}
from
'@vueuse/core'
withDefaults
(
defineProps
<
{
sidebar
?:
boolean
;
footer
?:
boolean
;
header
?:
boolean
}
>
(),
{
const
props
=
withDefaults
(
defineProps
<
{
sidebar
?:
boolean
;
footer
?:
boolean
;
header
?:
boolean
}
>
(),
{
header
:
true
,
sidebar
:
false
,
footer
:
false
})
const
isMobile
=
useMediaQuery
(
'(max-width: 767px)'
)
const
sidebarVisible
=
ref
(
false
)
const
closeSidebar
=
()
=>
{
sidebarVisible
.
value
=
false
}
const
showOverlaySidebar
=
computed
(()
=>
isMobile
.
value
&&
sidebarVisible
.
value
&&
props
.
sidebar
)
</
script
>
<
template
>
<div
class=
"app-layout"
>
<div
class=
"app-layout"
:class=
"
{ 'app-layout--mobile': isMobile }"
>
<AppHeader
v-if=
"header"
></AppHeader>
<div
class=
"app-layout-container"
>
<AppAside
v-if=
"sidebar"
></AppAside>
<AppMain></AppMain>
<div
v-if=
"showOverlaySidebar"
class=
"sidebar-overlay"
@
click=
"closeSidebar"
></div>
<AppAside
v-if=
"sidebar"
:class=
"
{ 'app-aside--overlay': isMobile, 'app-aside--visible': sidebarVisible }">
</AppAside>
<AppMain
@
click=
"closeSidebar"
></AppMain>
</div>
<AppFooter
v-if=
"footer"
></AppFooter>
</div>
...
...
@@ -28,7 +38,6 @@ withDefaults(defineProps<{ sidebar?: boolean; footer?: boolean; header?: boolean
<
style
lang=
"scss"
>
.app-layout
{
min-width
:
1000px
;
min-height
:
100vh
;
margin
:
0
auto
;
background-color
:
#ecf2f7
;
...
...
@@ -36,8 +45,18 @@ withDefaults(defineProps<{ sidebar?: boolean; footer?: boolean; header?: boolean
flex-direction
:
column
;
}
.app-layout-container
{
// min-height: calc(100vh - 66px);
flex
:
1
;
display
:
flex
;
position
:
relative
;
}
.sidebar-overlay
{
display
:
none
;
}
.app-layout--mobile
.sidebar-overlay
{
display
:
block
;
position
:
fixed
;
inset
:
0
;
background
:
rgba
(
0
,
0
,
0
,
0
.45
);
z-index
:
1999
;
}
</
style
>
src/components/layout/Main.vue
浏览文件 @
ac045689
...
...
@@ -16,6 +16,7 @@ export default { name: 'AppMain' }
flex
:
1
;
padding
:
20px
;
overflow
:
hidden
;
min-width
:
0
;
}
.app-main-inner
{
height
:
100%
;
...
...
@@ -26,4 +27,6 @@ export default { name: 'AppMain' }
display
:
table
;
clear
:
both
;
}
@media
(
max-width
:
1024px
)
{
.app-main
{
padding
:
16px
;
}
}
@media
(
max-width
:
767px
)
{
.app-main
{
padding
:
12px
;
}
}
</
style
>
src/modules/admin/contest/check/components/ImportExamDialog.vue
浏览文件 @
ac045689
...
...
@@ -39,12 +39,12 @@ watchEffect(() => {
<
template
>
<el-dialog
title=
"批量导入考试成绩"
:close-on-click-modal=
"false"
width=
"400px"
>
<el-form
label-width=
"70px"
>
<el-form
label-width=
"70px"
class=
"import-form"
>
<el-form-item
label=
"赛项名称"
>
<el-select
v-model=
"form.competition_id"
>
<el-select
v-model=
"form.competition_id"
class=
"import-form__select"
>
<el-option
v-for=
"item in competitions"
:key=
"item.id"
:label=
"item.name"
:value=
"item.id"
></el-option>
</el-select>
<a
:href=
"templateUrl"
download
style=
"margin-left: 10px
"
v-if=
"templateUrl"
>
下载模板
</a>
<a
:href=
"templateUrl"
download
class=
"import-form__link
"
v-if=
"templateUrl"
>
下载模板
</a>
</el-form-item>
<el-form-item>
<el-button
...
...
@@ -53,10 +53,24 @@ watchEffect(() => {
:icon=
"Upload"
@
click=
"handleImport"
:disabled=
"!form.competition_id"
style=
"width: 195px"
style=
"width: 1
00%; max-width: 1
95px"
>
本地上传
</el-button
>
</el-form-item>
</el-form>
</el-dialog>
</
template
>
<
style
scoped
>
@media
(
max-width
:
767px
)
{
.import-form
:deep
(
.el-form-item__content
)
{
display
:
flex
;
flex-direction
:
column
;
align-items
:
stretch
;
gap
:
8px
;
}
.import-form__select
{
width
:
100%
;
}
}
</
style
>
src/modules/admin/contest/check/components/ImportScoreDialog.vue
浏览文件 @
ac045689
...
...
@@ -39,12 +39,12 @@ watchEffect(() => {
<
template
>
<el-dialog
title=
"批量导入完整评分"
:close-on-click-modal=
"false"
width=
"400px"
>
<el-form
label-width=
"70px"
>
<el-form
label-width=
"70px"
class=
"import-form"
>
<el-form-item
label=
"赛项名称"
>
<el-select
v-model=
"form.competition_id"
>
<el-select
v-model=
"form.competition_id"
class=
"import-form__select"
>
<el-option
v-for=
"item in competitions"
:key=
"item.id"
:label=
"item.name"
:value=
"item.id"
></el-option>
</el-select>
<a
:href=
"templateUrl"
download
style=
"margin-left: 10px
"
v-if=
"templateUrl"
>
下载模板
</a>
<a
:href=
"templateUrl"
download
class=
"import-form__link
"
v-if=
"templateUrl"
>
下载模板
</a>
</el-form-item>
<el-form-item>
<el-button
...
...
@@ -53,10 +53,24 @@ watchEffect(() => {
:icon=
"Upload"
@
click=
"handleImport"
:disabled=
"!form.competition_id"
style=
"width: 195px"
style=
"width: 1
00%; max-width: 1
95px"
>
本地上传
</el-button
>
</el-form-item>
</el-form>
</el-dialog>
</
template
>
<
style
scoped
>
@media
(
max-width
:
767px
)
{
.import-form
:deep
(
.el-form-item__content
)
{
display
:
flex
;
flex-direction
:
column
;
align-items
:
stretch
;
gap
:
8px
;
}
.import-form__select
{
width
:
100%
;
}
}
</
style
>
src/modules/admin/contest/check/components/ReportDialog.vue
浏览文件 @
ac045689
...
...
@@ -26,15 +26,15 @@ const getUrl = (url: string) => {
<
template
>
<el-dialog
title=
"查看报告"
width=
"500px"
>
<ul>
<li
v-for=
"item in reportList"
:key=
"item.id"
style=
"line-height: 24px
"
>
<
a
:href=
"getUrl(item.url)"
target=
"_blank"
>
{{
item
.
name
}}
<
el-button
size=
"small"
type=
"primary"
style=
"margin-left: 10px"
>
查看
</el-button
>
</a
>
<el-button
size=
"small"
type=
"primary"
style=
"margin-left: 10px"
@
click=
"saveAs(item.url, item.name)"
>
下载
</el-button
>
<ul
class=
"report-list"
>
<li
v-for=
"item in reportList"
:key=
"item.id"
class=
"report-list__item
"
>
<
div
class=
"report-list__name"
>
{{
item
.
name
}}
</div
>
<div
class=
"report-list__actions"
>
<
a
:href=
"getUrl(item.url)"
target=
"_blank"
>
<el-button
size=
"small"
type=
"primary"
>
查看
</el-button
>
</a>
<el-button
size=
"small"
type=
"primary"
@
click=
"saveAs(item.url, item.name)"
>
下载
</el-button>
</div
>
</li>
</ul>
<template
#
footer
>
...
...
@@ -44,3 +44,40 @@ const getUrl = (url: string) => {
</
template
>
</el-dialog>
</template>
<
style
scoped
>
.report-list
{
display
:
flex
;
flex-direction
:
column
;
gap
:
12px
;
}
.report-list__item
{
display
:
flex
;
align-items
:
center
;
justify-content
:
space-between
;
gap
:
12px
;
padding
:
12px
14px
;
border
:
1px
solid
#ebeef5
;
border-radius
:
10px
;
}
.report-list__name
{
min-width
:
0
;
line-height
:
1.6
;
word-break
:
break-word
;
}
.report-list__actions
{
display
:
flex
;
gap
:
8px
;
flex-shrink
:
0
;
}
@media
(
max-width
:
767px
)
{
.report-list__item
{
flex-direction
:
column
;
align-items
:
stretch
;
}
.report-list__actions
{
justify-content
:
flex-end
;
flex-wrap
:
wrap
;
}
}
</
style
>
src/modules/admin/contest/check/components/ScoreDialog.vue
浏览文件 @
ac045689
...
...
@@ -2,10 +2,12 @@
import
{
ElMessage
}
from
'element-plus'
import
{
ArrowDown
}
from
'@element-plus/icons-vue'
import
{
usePrecision
}
from
'@vueuse/math'
import
{
useMediaQuery
}
from
'@vueuse/core'
import
type
{
ReportItem
}
from
'../types'
import
{
submitScore
}
from
'../api'
defineProps
<
{
reportList
:
ReportItem
[]
}
>
()
const
isMobile
=
useMediaQuery
(
'(max-width: 767px)'
)
const
emit
=
defineEmits
<
{
(
e
:
'update'
):
void
...
...
@@ -76,7 +78,7 @@ const handleScoreRule = function () {
<el-form-item
label=
"赛项名称"
>
{{
detail
.
competition_id_name
}}
</el-form-item>
<el-form-item
label=
"选手姓名"
>
{{
detail
.
student_name
}}
</el-form-item>
<el-form-item
label=
"选手ID"
>
{{
detail
.
login_id
}}
</el-form-item>
<div
style=
"display: flex; justify-content: space-between
"
>
<div
class=
"score-dialog-toolbar
"
>
<el-form-item
label=
"报告"
v-if=
"reportList.length"
>
<el-dropdown>
<el-button
text
>
...
...
@@ -94,7 +96,31 @@ const handleScoreRule = function () {
<el-button
type=
"primary"
style=
"margin-bottom: 20px"
@
click=
"handleScoreRule"
>
查看评分规则
</el-button>
</div>
</el-form>
<el-table
:data=
"tableList"
:header-cell-style=
"{ background: '#ededed' }"
>
<div
v-if=
"isMobile"
class=
"score-card-list"
>
<div
class=
"score-card"
v-for=
"row in tableList"
:key=
"row.id"
>
<div
class=
"score-card__row"
><span>
评分规则
</span><strong>
{{ row.name || '-' }}
</strong></div>
<div
class=
"score-card__row"
><span>
评分方法
</span><strong>
{{ row.type_name || '-' }}
</strong></div>
<div
class=
"score-card__row"
><span>
分值
</span><strong>
{{ row.old_score || '-' }}
</strong></div>
<div
class=
"score-card__row"
><span>
占比(%)
</span><strong>
{{ row.ratio || '-' }}
</strong></div>
<div
class=
"score-card__row score-card__row--field"
>
<span>
评分
</span>
<div
class=
"score-card__field"
>
<el-input-number
:disabled=
"row.type === '2'"
:controls=
"false"
v-model=
"row.score"
step-strictly
:step=
"0.1"
:min=
"0"
:max=
"row.old_score"
style=
"width: 100%"
placeholder=
"--"
/>
</div>
</div>
</div>
</div>
<el-table
v-else
:data=
"tableList"
:header-cell-style=
"{ background: '#ededed' }"
>
<el-table-column
label=
"评分规则"
prop=
"name"
align=
"center"
></el-table-column>
<el-table-column
label=
"评分方法"
prop=
"type_name"
align=
"center"
></el-table-column>
<el-table-column
label=
"分值"
prop=
"old_score"
align=
"center"
></el-table-column>
...
...
@@ -146,4 +172,58 @@ const handleScoreRule = function () {
color
:
var
(
--
main-color
);
}
}
.score-dialog-toolbar
{
display
:
flex
;
justify-content
:
space-between
;
gap
:
12px
;
}
.score-card-list
{
display
:
flex
;
flex-direction
:
column
;
gap
:
12px
;
}
.score-card
{
padding
:
14px
16px
;
border
:
1px
solid
#ebeef5
;
border-radius
:
10px
;
background
:
#fff
;
}
.score-card__row
{
display
:
flex
;
justify-content
:
space-between
;
gap
:
12px
;
padding
:
6px
0
;
border-bottom
:
1px
solid
#f3f4f6
;
}
.score-card__row
:last-of-type
{
border-bottom
:
none
;
}
.score-card__row
span
{
flex-shrink
:
0
;
color
:
#909399
;
}
.score-card__row
strong
{
min-width
:
0
;
text-align
:
right
;
word-break
:
break-word
;
}
.score-card__row--field
{
align-items
:
center
;
}
.score-card__field
{
width
:
120px
;
max-width
:
100%
;
}
@media
(
max-width
:
767px
)
{
.score-dialog-toolbar
{
flex-direction
:
column
;
align-items
:
flex-start
;
}
.result
{
padding
:
24px
0
;
span
{
font-size
:
30px
;
}
}
}
</
style
>
src/modules/admin/contest/check/components/ScoreViewPicturesDialog.vue
浏览文件 @
ac045689
<
script
setup
lang=
"ts"
>
import
type
{
RecordItem
,
FileItem
}
from
'../types'
import
{
useMediaQuery
}
from
'@vueuse/core'
interface
Props
{
data
:
RecordItem
}
const
props
=
defineProps
<
Props
>
()
const
isMobile
=
useMediaQuery
(
'(max-width: 767px)'
)
// 实验过程截图
const
pictures
=
$computed
<
FileItem
[]
>
(()
=>
{
...
...
@@ -24,7 +26,7 @@ const srcList = computed(() => {
<
template
>
<el-dialog
title=
"查看实验截图"
width=
"800px"
append-to-body
>
<ul
class=
"picture-list"
>
<ul
class=
"picture-list"
:class=
"
{ 'picture-list--mobile': isMobile }"
>
<li
v-for=
"(item, index) in pictures"
:key=
"item.url"
>
<p
class=
"t1"
>
<el-image
style=
"width: 100%"
:src=
"item.url"
:preview-src-list=
"srcList"
:initial-index=
"index"
/>
...
...
@@ -45,6 +47,7 @@ const srcList = computed(() => {
width
:
100%
;
li
{
line-height
:
30px
;
margin-bottom
:
16px
;
}
a
{
color
:
var
(
--
main-color
);
...
...
@@ -53,4 +56,17 @@ const srcList = computed(() => {
text-align
:
center
;
}
}
.picture-list--mobile
{
li
{
padding
:
12px
;
border
:
1px
solid
#ebeef5
;
border-radius
:
10px
;
background
:
#fff
;
}
.t2
{
margin-top
:
8px
;
font-size
:
13px
;
color
:
#909399
;
}
}
</
style
>
src/modules/admin/contest/check/components/SyncExamDialog.vue
浏览文件 @
ac045689
<
script
setup
lang=
"ts"
>
import
{
ElMessage
}
from
'element-plus'
import
{
useMediaQuery
}
from
'@vueuse/core'
import
{
useMapStore
}
from
'@/stores/map'
import
{
useFilterList
}
from
'../composables/useFilterList'
import
{
syncExam
}
from
'../api'
...
...
@@ -10,6 +11,7 @@ const emit = defineEmits<{
// 评分方法
const
scoreMethods
=
useMapStore
().
getMapValuesByKey
(
'scoring_manner'
)
const
isMobile
=
useMediaQuery
(
'(max-width: 767px)'
)
const
{
competitions
}
=
useFilterList
(
1
)
const
form
=
reactive
<
any
>
({
...
...
@@ -30,16 +32,25 @@ function scoreMethodText(value: string) {
<
template
>
<el-dialog
title=
"同步1+同步线上考试成绩"
:close-on-click-modal=
"false"
>
<el-form>
<el-form
class=
"sync-form"
>
<el-form-item
label=
"赛项名称"
>
<el-select
v-model=
"form.competition"
value-key=
"id"
>
<el-select
v-model=
"form.competition"
value-key=
"id"
class=
"sync-form__select"
>
<el-option
v-for=
"item in competitions"
:key=
"item.id"
:label=
"item.name"
:value=
"item"
></el-option>
</el-select>
<el-button
type=
"primary"
:disabled=
"!form.competition"
@
click=
"handleSync"
>
一键同步成绩
</el-button>
</el-form-item>
</el-form>
<el-table
:data=
"form.competition?.rules"
>
<div
v-if=
"isMobile"
class=
"sync-card-list"
>
<div
class=
"sync-card"
v-for=
"row in form.competition?.rules || []"
:key=
"row.id || row.exam_name"
>
<div
class=
"sync-card__row"
><span>
评分方法
</span><strong>
{{
scoreMethodText
(
row
.
type
)
}}
</strong></div>
<div
class=
"sync-card__row"
><span>
考试名称
</span><strong>
{{
row
.
exam_name
||
'-'
}}
</strong></div>
<div
class=
"sync-card__row"
><span>
实考人数/应考人数
</span><strong>
{{
row
.
finish_count
}}
/
{{
row
.
all_count
}}
</strong></div>
<div
class=
"sync-card__actions"
>
<el-button
round
type=
"primary"
@
click=
"handleSync(row)"
>
同步导入成绩
</el-button>
</div>
</div>
</div>
<el-table
v-else
:data=
"form.competition?.rules"
>
<el-table-column
label=
"评分方法"
prop=
"type"
align=
"center"
width=
"200"
>
<template
#
default=
"
{ row }">
{{
scoreMethodText
(
row
.
type
)
}}
</
template
>
</el-table-column>
...
...
@@ -55,3 +66,52 @@ function scoreMethodText(value: string) {
</el-table>
</el-dialog>
</template>
<
style
scoped
>
.sync-card-list
{
display
:
flex
;
flex-direction
:
column
;
gap
:
12px
;
}
.sync-card
{
padding
:
14px
16px
;
border
:
1px
solid
#ebeef5
;
border-radius
:
10px
;
background
:
#fff
;
}
.sync-card__row
{
display
:
flex
;
justify-content
:
space-between
;
gap
:
12px
;
padding
:
6px
0
;
border-bottom
:
1px
solid
#f3f4f6
;
}
.sync-card__row
:last-of-type
{
border-bottom
:
none
;
}
.sync-card__row
span
{
flex-shrink
:
0
;
color
:
#909399
;
}
.sync-card__row
strong
{
min-width
:
0
;
text-align
:
right
;
word-break
:
break-word
;
}
.sync-card__actions
{
display
:
flex
;
justify-content
:
flex-end
;
padding-top
:
10px
;
}
@media
(
max-width
:
767px
)
{
.sync-form
:deep
(
.el-form-item__content
)
{
display
:
flex
;
flex-direction
:
column
;
align-items
:
stretch
;
gap
:
10px
;
}
.sync-form__select
{
width
:
100%
;
}
}
</
style
>
src/modules/admin/contest/check/components/ViewLiveDialog.vue
浏览文件 @
ac045689
...
...
@@ -55,6 +55,7 @@ const listOptions = $computed(() => {
},
{
label
:
'操作'
,
slots
:
'table-x'
,
width
:
130
},
],
mobileCards
:
true
,
}
})
const
handleViewLive
=
function
(
row
:
any
)
{
...
...
src/modules/admin/contest/check/views/Detail.vue
浏览文件 @
ac045689
...
...
@@ -75,7 +75,7 @@ function handleResize() {
</
template
>
<
template
#
right
v-if=
"detail"
>
<AppCard>
<el-row
justify=
"space-between"
>
<el-row
justify=
"space-between"
class=
"detail-toolbar"
>
<div
class=
"info"
>
<p>
赛项名称:
<span>
{{
detail
.
competition_id_name
}}
</span>
...
...
@@ -87,11 +87,11 @@ function handleResize() {
选手ID:
<span>
{{
detail
.
login_id
}}
</span>
</p>
</div>
<div>
<el-select
v-model=
"platformUrl"
>
<div
class=
"detail-toolbar__actions"
>
<el-select
v-model=
"platformUrl"
class=
"detail-toolbar__select"
>
<el-option
v-for=
"item in platformList"
:key=
"item.id"
:label=
"item.name"
:value=
"item.url"
></el-option>
</el-select>
<el-dropdown
style=
"margin: 0 10px
"
>
<el-dropdown
class=
"detail-toolbar__dropdown
"
>
<el-button
plain
>
查看报告
<el-icon
class=
"el-icon--right"
><arrow-down
/></el-icon>
</el-button>
...
...
@@ -169,4 +169,33 @@ function handleResize() {
margin-right
:
40px
;
}
}
.detail-toolbar__actions
{
display
:
flex
;
align-items
:
center
;
gap
:
10px
;
flex-wrap
:
wrap
;
}
@media
(
max-width
:
767px
)
{
.detail-toolbar
{
gap
:
12px
;
}
.info
{
width
:
100%
;
p
{
margin-right
:
0
;
width
:
100%
;
line-height
:
1
.6
;
word-break
:
break-word
;
}
}
.detail-toolbar__actions
{
width
:
100%
;
}
.detail-toolbar__select
{
width
:
100%
;
}
.detail-toolbar__dropdown
{
margin
:
0
!
important
;
}
}
</
style
>
src/modules/admin/contest/contestants/components/ImportDialog.vue
浏览文件 @
ac045689
...
...
@@ -50,7 +50,7 @@ watchEffect(() => {
align-items
:
center
;
justify-content
:
center
;
.el-button
{
width
:
220px
;
max-width
:
220px
;
width
:
100%
;
}
p
{
color
:
#999
;
...
...
src/modules/admin/contest/dashboard/views/Index.vue
浏览文件 @
ac045689
...
...
@@ -234,11 +234,11 @@ function getModuleStatus(row: any, index: number) {
</li>
</ul>
<el-row
:gutter=
"20"
>
<el-col
:
span
=
"12"
>
<el-col
:
xs=
"24"
:sm=
"24"
:md
=
"12"
>
<h2
class=
"h2-title"
>
已提交选手
</h2>
<AppList
border
v-bind=
"submittedListOptions"
:data=
"submittedLis"
ref=
"appList"
></AppList
></el-col>
<el-col
:
span
=
"12"
>
<el-col
:
xs=
"24"
:sm=
"24"
:md
=
"12"
>
<h2
class=
"h2-title"
>
正在作答选手
</h2>
<AppList
border
v-bind=
"answeringListOptions"
:data=
"answeringList"
ref=
"appList"
></AppList
></el-col>
...
...
@@ -255,8 +255,9 @@ function getModuleStatus(row: any, index: number) {
justify-content
:
center
;
column-gap
:
140px
;
margin
:
90px
0
;
flex-wrap
:
wrap
;
row-gap
:
20px
;
li
{
width
:
212px
;
flex
:
0
0
212px
;
height
:
212px
;
background-color
:
rgba
(
247
,
247
,
247
,
1
);
border-radius
:
50%
;
...
...
@@ -353,4 +354,5 @@ function getModuleStatus(row: any, index: number) {
font-size
:
30px
;
}
}
@media
(
max-width
:
767px
)
{
.statistics
{
justify-content
:
center
;
margin
:
40px
0
;
}
.statistics
li
{
flex
:
0
0
140px
;
height
:
140px
;
}
}
</
style
>
src/modules/admin/contest/discuss/components/DiscussItem.vue
浏览文件 @
ac045689
...
...
@@ -53,6 +53,8 @@ defineEmits<{
.discuss-box-user
{
display
:
flex
;
align-items
:
center
;
flex-wrap
:
wrap
;
min-width
:
0
;
.button-comment
{
display
:
flex
;
align-items
:
center
;
...
...
@@ -112,6 +114,7 @@ defineEmits<{
font-weight
:
400
;
line-height
:
24px
;
color
:
#333333
;
word-break
:
break-word
;
}
.discuss-comment
{
...
...
@@ -134,12 +137,16 @@ defineEmits<{
margin-left
:
10px
;
color
:
#333
;
overflow
:
hidden
;
word-break
:
break-word
;
}
.discuss-comment__username
{
font-size
:
16px
;
line-height
:
20px
;
color
:
var
(
--
main-color
);
}
@media
(
max-width
:
767px
)
{
.discuss-item
{
padding
:
12px
8px
;
}
}
.discuss-comment-time
{
margin-top
:
10px
;
font-size
:
14px
;
...
...
src/modules/admin/contest/experts/components/ImportDialog.vue
浏览文件 @
ac045689
...
...
@@ -48,7 +48,7 @@ watchEffect(() => {
align-items
:
center
;
justify-content
:
center
;
.el-button
{
width
:
220px
;
max-width
:
220px
;
width
:
100%
;
}
p
{
color
:
#999
;
...
...
src/modules/admin/contest/items/components/FormDialog copy.vue
浏览文件 @
ac045689
...
...
@@ -435,8 +435,8 @@ onMounted(() => {
>
<div
style=
"display: flex; align-items: center"
>
<!--
<span
style=
"margin-right: 10px; width: 180px"
>
{{
item
.
name
}}
</span>
-->
<el-input
v-model=
"item.name"
style=
"margin-right: 10px; max-width: 130px"
/>
<el-input
v-model=
"item.url"
style=
"
width: 200px
"
/>
<el-input
v-model=
"item.name"
style=
"margin-right: 10px; max-width: 130px
; width: 100%
"
/>
<el-input
v-model=
"item.url"
style=
"
max-width: 200px; width: 100%
"
/>
</div>
</el-checkbox>
</el-form-item>
...
...
@@ -451,9 +451,9 @@ onMounted(() => {
>
<div
style=
"display: flex; align-items: center"
>
<!--
<span
style=
"margin-right: 10px; width: 180px"
>
{{
item
.
name
}}
</span>
-->
<el-input
v-model=
"item.name"
style=
"margin-right: 10px; max-width: 130px"
/>
<el-input
v-model=
"item.url"
v-if=
"item.type === '2'"
style=
"
width: 200px
"
/>
<el-select
v-model=
"item.exam_id"
filterable
style=
"
width: 200px
"
v-if=
"item.type === '1'"
>
<el-input
v-model=
"item.name"
style=
"margin-right: 10px; max-width: 130px
; width: 100%
"
/>
<el-input
v-model=
"item.url"
v-if=
"item.type === '2'"
style=
"
max-width: 200px; width: 100%
"
/>
<el-select
v-model=
"item.exam_id"
filterable
style=
"
max-width: 200px; width: 100%
"
v-if=
"item.type === '1'"
>
<el-option
v-for=
"item in examList"
:key=
"item.exam_id"
...
...
src/modules/admin/contest/items/components/FormDialog.vue
浏览文件 @
ac045689
...
...
@@ -432,8 +432,8 @@ onMounted(() => {
>
<div
style=
"display: flex; align-items: center"
>
<!--
<span
style=
"margin-right: 10px; width: 180px"
>
{{
item
.
name
}}
</span>
-->
<el-input
v-model=
"item.name"
style=
"margin-right: 10px; max-width: 130px"
/>
<el-input
v-model=
"item.url"
style=
"
width: 200px
"
/>
<el-input
v-model=
"item.name"
style=
"margin-right: 10px; max-width: 130px
; width: 100%
"
/>
<el-input
v-model=
"item.url"
style=
"
max-width: 200px; width: 100%
"
/>
</div>
</el-checkbox>
</el-form-item>
...
...
@@ -514,9 +514,9 @@ onMounted(() => {
>
<div
style=
"display: flex; align-items: center"
>
<!--
<span
style=
"margin-right: 10px; width: 180px"
>
{{
item
.
name
}}
</span>
-->
<el-input
v-model=
"item.name"
style=
"margin-right: 10px; max-width: 130px"
/>
<el-input
v-model=
"item.url"
v-if=
"item.type === '2'"
style=
"
width: 200px
"
/>
<el-select
v-model=
"item.exam_id"
filterable
style=
"
width: 200px
"
v-if=
"item.type === '1'"
>
<el-input
v-model=
"item.name"
style=
"margin-right: 10px; max-width: 130px
; width: 100%
"
/>
<el-input
v-model=
"item.url"
v-if=
"item.type === '2'"
style=
"
max-width: 200px; width: 100%
"
/>
<el-select
v-model=
"item.exam_id"
filterable
style=
"
max-width: 200px; width: 100%
"
v-if=
"item.type === '1'"
>
<el-option
v-for=
"item in examList"
:key=
"item.exam_id"
...
...
src/modules/admin/contest/items/components/ViewExperimentFormDialog.vue
浏览文件 @
ac045689
...
...
@@ -63,7 +63,7 @@ function handleSubmit() {
remote-show-suffix
:remote-method=
"fetchExamList"
:loading=
"loading"
style=
"width: 340px"
style=
"width:
100%; max-width:
340px"
>
<el-option
v-for=
"item in list"
:key=
"item.value"
:label=
"item.name"
:value=
"item.id"
/>
</el-select>
...
...
src/modules/admin/contest/items/views/Edit.vue
浏览文件 @
ac045689
...
...
@@ -41,7 +41,8 @@ onMounted(() => {
}
}
.top-cover
{
width
:
300px
;
max-width
:
300px
;
width
:
100%
;
margin-right
:
20px
;
p
{
font-weight
:
normal
;
...
...
src/modules/admin/contest/items/views/View.vue
浏览文件 @
ac045689
...
...
@@ -213,7 +213,8 @@ function handleExperts() {
}
}
.top-cover
{
width
:
300px
;
max-width
:
300px
;
width
:
100%
;
margin-right
:
20px
;
p
{
font-weight
:
normal
;
...
...
src/modules/admin/contest/score/components/ScorePublishDialog.vue
浏览文件 @
ac045689
...
...
@@ -32,9 +32,9 @@ function handleSubmit() {
</p>
<br
/>
<p>
<span>
报名人数:
{{
data
.
all_competitor_count
}}
</span>
<span>
完赛人数:
{{
data
.
complete_competitor_count
}}
</span>
<span>
已评分人数:
{{
data
.
checked_competitor_count
}}
</span>
<span
class=
"content__metric"
>
报名人数:
{{
data
.
all_competitor_count
}}
</span>
<span
class=
"content__metric"
>
完赛人数:
{{
data
.
complete_competitor_count
}}
</span>
<span
class=
"content__metric"
>
已评分人数:
{{
data
.
checked_competitor_count
}}
</span>
</p>
<br
/>
<p>
请确认是否发布成绩!
</p>
...
...
@@ -55,8 +55,20 @@ function handleSubmit() {
b
{
color
:
var
(
--
main-color
);
}
span
+
span
{
margin-left
:
40px
;
}
.content__metric
{
display
:
inline-block
;
}
.content__metric
+
.content__metric
{
margin-left
:
40px
;
}
@media
(
max-width
:
767px
)
{
.content__metric
{
display
:
block
;
margin
:
6px
0
;
}
.content__metric
+
.content__metric
{
margin-left
:
0
;
}
}
</
style
>
src/modules/admin/lab/book/index.ts
浏览文件 @
ac045689
...
...
@@ -2,10 +2,6 @@ import type { RouteRecordRaw } from 'vue-router'
import
AppLayout
from
'@/components/layout/Index.vue'
export
const
routes
:
Array
<
RouteRecordRaw
>
=
[
{
path
:
'/admin/lab'
,
redirect
:
'/admin/lab/book'
},
{
path
:
'/admin/lab/book'
,
component
:
AppLayout
,
...
...
src/modules/admin/lab/book/views/Index.vue
浏览文件 @
ac045689
...
...
@@ -85,4 +85,6 @@ function onUpdateSuccess() {
grid-template-columns
:
repeat
(
4
,
1fr
);
gap
:
20px
;
}
@media
(
max-width
:
1024px
)
{
.list-card
{
grid-template-columns
:
repeat
(
2
,
1fr
);
gap
:
16px
;
}
}
@media
(
max-width
:
767px
)
{
.list-card
{
grid-template-columns
:
1fr
;
gap
:
12px
;
}
}
</
style
>
src/modules/admin/lab/case-auth/views/Index.vue
浏览文件 @
ac045689
...
...
@@ -288,10 +288,10 @@ async function handleBatchAuthorize() {
<
template
>
<AppCard
title=
"案例授权"
>
<div
class=
"case-auth-toolbar"
>
<el-select
v-model=
"selectedCourse"
placeholder=
"请选择课程"
filterable
clearable
style=
"width: 200px"
>
<el-select
v-model=
"selectedCourse"
placeholder=
"请选择课程"
filterable
clearable
style=
"width:
100%; max-width:
200px"
>
<el-option
v-for=
"item in courses"
:key=
"item.id"
:label=
"item.name"
:value=
"item.id"
/>
</el-select>
<el-select
v-model=
"selectedExperiment"
placeholder=
"请选择实验"
filterable
clearable
style=
"width: 200px"
>
<el-select
v-model=
"selectedExperiment"
placeholder=
"请选择实验"
filterable
clearable
style=
"width:
100%; max-width:
200px"
>
<el-option
v-for=
"item in experiments"
:key=
"item.id"
:label=
"item.name"
:value=
"item.id"
/>
</el-select>
<el-button
type=
"primary"
:loading=
"submitting"
@
click=
"handleBatchAuthorize"
:disabled=
"!selectedCount"
...
...
src/modules/admin/lab/case/index.ts
浏览文件 @
ac045689
...
...
@@ -2,10 +2,6 @@ import type { RouteRecordRaw } from 'vue-router'
import
AppLayout
from
'@/components/layout/Index.vue'
export
const
routes
:
Array
<
RouteRecordRaw
>
=
[
{
path
:
'/admin/lab'
,
redirect
:
'/admin/lab/case'
},
{
path
:
'/admin/lab/case'
,
component
:
AppLayout
,
...
...
src/modules/admin/lab/case/views/Index.vue
浏览文件 @
ac045689
...
...
@@ -85,4 +85,6 @@ function onUpdateSuccess() {
grid-template-columns
:
repeat
(
4
,
1fr
);
gap
:
20px
;
}
@media
(
max-width
:
1024px
)
{
.list-card
{
grid-template-columns
:
repeat
(
2
,
1fr
);
gap
:
16px
;
}
}
@media
(
max-width
:
767px
)
{
.list-card
{
grid-template-columns
:
1fr
;
gap
:
12px
;
}
}
</
style
>
src/modules/admin/lab/dashboard/components/History.vue
浏览文件 @
ac045689
...
...
@@ -104,11 +104,10 @@ const listOptions = computed(() => {
align-items
:
center
;
justify-content
:
space-evenly
;
margin
:
90px
0
;
&
:empty
{
display
:
none
;
}
flex-wrap
:
wrap
;
row-gap
:
20px
;
&
:empty
{
display
:
none
;
}
li
{
width
:
212px
;
flex
:
0
0
212px
;
height
:
212px
;
background-color
:
rgba
(
247
,
247
,
247
,
1
);
border-radius
:
50%
;
...
...
@@ -147,4 +146,5 @@ const listOptions = computed(() => {
margin
:
20px
0
;
border-left
:
3px
solid
#aa1941
;
}
@media
(
max-width
:
767px
)
{
.statistics
{
margin
:
40px
0
;
}
.statistics
li
{
flex
:
0
0
140px
;
height
:
140px
;
}
}
</
style
>
src/modules/admin/lab/dashboard/components/Live.vue
浏览文件 @
ac045689
...
...
@@ -107,11 +107,10 @@ const listOptions = computed(() => {
align-items
:
center
;
justify-content
:
space-evenly
;
margin
:
90px
0
;
&
:empty
{
display
:
none
;
}
flex-wrap
:
wrap
;
row-gap
:
20px
;
&
:empty
{
display
:
none
;
}
li
{
width
:
212px
;
flex
:
0
0
212px
;
height
:
212px
;
background-color
:
rgba
(
247
,
247
,
247
,
1
);
border-radius
:
50%
;
...
...
@@ -150,4 +149,5 @@ const listOptions = computed(() => {
margin
:
20px
0
;
border-left
:
3px
solid
#aa1941
;
}
@media
(
max-width
:
767px
)
{
.statistics
{
margin
:
40px
0
;
}
.statistics
li
{
flex
:
0
0
140px
;
height
:
140px
;
}
}
</
style
>
src/modules/admin/lab/discuss/components/DiscussItem.vue
浏览文件 @
ac045689
...
...
@@ -53,6 +53,8 @@ defineEmits<{
.discuss-box-user
{
display
:
flex
;
align-items
:
center
;
flex-wrap
:
wrap
;
min-width
:
0
;
.button-comment
{
display
:
flex
;
align-items
:
center
;
...
...
@@ -112,6 +114,7 @@ defineEmits<{
font-weight
:
400
;
line-height
:
24px
;
color
:
#333333
;
word-break
:
break-word
;
}
.discuss-comment
{
...
...
@@ -134,12 +137,16 @@ defineEmits<{
margin-left
:
10px
;
color
:
#333
;
overflow
:
hidden
;
word-break
:
break-word
;
}
.discuss-comment__username
{
font-size
:
16px
;
line-height
:
20px
;
color
:
var
(
--
main-color
);
}
@media
(
max-width
:
767px
)
{
.discuss-item
{
padding
:
12px
8px
;
}
}
.discuss-comment-time
{
margin-top
:
10px
;
font-size
:
14px
;
...
...
src/modules/admin/lab/example/index.ts
浏览文件 @
ac045689
...
...
@@ -2,10 +2,6 @@ import type { RouteRecordRaw } from 'vue-router'
import
AppLayout
from
'@/components/layout/Index.vue'
export
const
routes
:
Array
<
RouteRecordRaw
>
=
[
{
path
:
'/admin/lab'
,
redirect
:
'/admin/lab/example'
,
},
{
path
:
'/admin/lab/example'
,
component
:
AppLayout
,
...
...
src/modules/admin/lab/experiment/components/CompetitionRuleDialog.vue
浏览文件 @
ac045689
<
script
setup
lang=
"ts"
>
import
{
ElMessage
}
from
'element-plus'
import
{
Plus
}
from
'@element-plus/icons-vue'
import
{
useMediaQuery
}
from
'@vueuse/core'
import
{
getCompetitionRule
,
saveCompetitionRule
}
from
'../api'
interface
Props
{
id
:
string
}
const
props
=
defineProps
<
Props
>
()
const
isMobile
=
useMediaQuery
(
'(max-width: 767px)'
)
const
dialogVisible
=
ref
(
false
)
...
...
@@ -226,7 +228,42 @@ function disabledMinSubmitDate(time: Date) {
<p>
成绩规则:
</p>
<el-button
type=
"primary"
:icon=
"Plus"
@
click=
"handleAdd"
></el-button>
</el-row>
<el-table
:data=
"form.config.rules"
>
<div
v-if=
"isMobile"
class=
"rule-card-list"
>
<div
class=
"rule-card"
v-for=
"(row, $index) in form.config.rules"
:key=
"`$
{row.type}-${$index}`">
<div
class=
"rule-card__field"
>
<span
class=
"rule-card__label"
>
评分项
</span>
<el-select
v-model=
"row.type"
style=
"width: 100%"
@
change=
"handleTypeChange(row)"
>
<el-option
v-for=
"item in currentRuleNames(row.type)"
:key=
"item.value"
v-bind=
"item"
></el-option>
</el-select>
</div>
<div
class=
"rule-card__field"
>
<span
class=
"rule-card__label"
>
分数
</span>
<div
class=
"rule-card__inline"
>
<el-input-number
v-model=
"row.percent"
:min=
"0"
:max=
"100"
@
change=
"handlePercentChange(row, $index)"
/>
分
</div>
</div>
<div
class=
"rule-card__field"
>
<span
class=
"rule-card__label"
>
评分方式
</span>
<el-radio-group
v-model=
"row.rule_mode"
size=
"small"
>
<el-radio
:label=
"1"
>
人工评分
</el-radio>
<el-radio
:label=
"2"
:disabled=
"[4, 5, 6].includes(row.type)"
>
自动评分
</el-radio>
</el-radio-group>
</div>
<div
class=
"rule-card__actions"
>
<el-button
style=
"padding: 0"
text
type=
"primary"
@
click=
"handleEdit(row)"
:disabled=
"[4, 5, 6].includes(row.type) || row.rule_mode === 1"
>
编辑
</el-button
>
<el-button
style=
"padding: 0"
text
type=
"primary"
@
click=
"handleRemove($index)"
>
删除
</el-button>
</div>
</div>
</div>
<el-table
v-else
:data=
"form.config.rules"
>
<el-table-column
prop=
"name"
width=
"170"
>
<template
#
default=
"
{ row }">
<el-select
v-model=
"row.type"
style=
"width: 100%"
@
change=
"handleTypeChange(row)"
>
...
...
@@ -305,4 +342,48 @@ function disabledMinSubmitDate(time: Date) {
padding
:
5px
10px
;
}
}
.rule-card-list
{
width
:
100%
;
display
:
flex
;
flex-direction
:
column
;
gap
:
12px
;
}
.rule-card
{
width
:
100%
;
padding
:
14px
16px
;
border
:
1px
solid
#ebeef5
;
border-radius
:
10px
;
background
:
#fff
;
box-sizing
:
border-box
;
}
.rule-card__field
+
.rule-card__field
{
margin-top
:
12px
;
}
.rule-card__label
{
display
:
block
;
margin-bottom
:
8px
;
color
:
#909399
;
font-size
:
13px
;
}
.rule-card__inline
{
display
:
flex
;
align-items
:
center
;
gap
:
8px
;
}
.rule-card__actions
{
display
:
flex
;
justify-content
:
flex-end
;
gap
:
12px
;
flex-wrap
:
wrap
;
padding-top
:
12px
;
}
@media
(
max-width
:
767px
)
{
.total
{
text-align
:
left
;
p
{
min-width
:
0
;
width
:
100%
;
}
}
}
</
style
>
src/modules/admin/lab/experiment/components/GradeRulesDialog.vue
浏览文件 @
ac045689
...
...
@@ -3,10 +3,12 @@ import type { FormInstance } from 'element-plus'
import
type
{
ExperimentItem
}
from
'../types'
import
{
Plus
,
Minus
}
from
'@element-plus/icons-vue'
import
{
ElMessage
}
from
'element-plus'
import
{
useMediaQuery
}
from
'@vueuse/core'
import
{
gradeRule
,
gradeRuleList
}
from
'@/utils/dictionary'
import
{
getExperimentGradeRule
,
updateExperimentGradeRule
,
getScoreExamList
}
from
'../api'
import
{
useAppConfig
}
from
'@/composables/useAppConfig'
const
appConfig
=
useAppConfig
()
const
isMobile
=
useMediaQuery
(
'(max-width: 767px)'
)
interface
Props
{
data
:
ExperimentItem
...
...
@@ -269,7 +271,7 @@ onMounted(() => {
<el-form
ref=
"formRef"
:model=
"form"
label-suffix=
":"
>
<el-form-item
label=
"实验名称"
>
{{
data
?.
name
}}
</el-form-item>
<el-row>
<el-col
:
span
=
"12"
>
<el-col
:
xs=
"24"
:sm=
"24"
:md
=
"12"
>
<el-form-item
label=
"是否允许查看成绩明细"
prop=
"is_show"
>
<el-radio-group
v-model=
"form.is_show"
>
<el-radio
label=
"1"
>
是
</el-radio>
...
...
@@ -277,7 +279,7 @@ onMounted(() => {
</el-radio-group>
</el-form-item>
</el-col>
<el-col
:
span
=
"12"
>
<el-col
:
xs=
"24"
:sm=
"24"
:md
=
"12"
>
<el-form-item
label=
"实验总成绩"
>
{{
data
?.
score
}}
</el-form-item>
</el-col>
</el-row>
...
...
@@ -289,7 +291,36 @@ onMounted(() => {
<el-button
type=
"primary"
:icon=
"Plus"
@
click=
"handleAddExamRule"
:disabled=
"form.exam_rules.length >= 1"
>
</el-button>
</el-row>
<el-table
:data=
"form.exam_rules"
row-key=
"id"
>
<div
v-if=
"isMobile"
class=
"rule-card-list"
>
<div
class=
"rule-card"
v-for=
"(row, $index) in form.exam_rules"
:key=
"row.id || $index"
>
<div
class=
"rule-card__field"
>
<span
class=
"rule-card__label"
>
理论考试
</span>
<el-select
v-model=
"row.exam_id"
style=
"width: 100%"
>
<el-option
v-for=
"item in examList"
:key=
"item.id"
:label=
"item.name"
:value=
"item.exam_id"
></el-option>
</el-select>
</div>
<div
class=
"rule-card__field"
>
<span
class=
"rule-card__label"
>
权重
</span>
<div
class=
"rule-card__inline"
>
<el-input-number
v-model=
"row.percent"
:min=
"0"
@
change=
"handlePercentChange(row, $index, 2)"
/>
%
</div>
</div>
<div
class=
"rule-card__field"
>
<span
class=
"rule-card__label"
>
评分方式
</span>
<el-radio-group
v-model=
"row.is_auto_scoring"
size=
"small"
>
<el-radio
label=
"1"
>
自动评分
</el-radio>
</el-radio-group>
</div>
<div
class=
"rule-card__row"
>
<span>
满分
</span>
<strong>
{{
rowScore
(
row
.
percent
)
}}
</strong>
</div>
<div
class=
"rule-card__actions"
>
<el-button
style=
"padding: 0"
text
type=
"primary"
@
click=
"handleRemoveExamRule($index)"
v-if=
"row.type !== 1"
>
删除
</el-button>
</div>
</div>
</div>
<el-table
v-else
:data=
"form.exam_rules"
row-key=
"id"
>
<el-table-column
prop=
"name"
width=
"170"
>
<template
#
default=
"
{ row }">
<el-select
v-model=
"row.exam_id"
style=
"width: 100%"
>
...
...
@@ -332,7 +363,47 @@ onMounted(() => {
<p>
实操成绩规则:
</p>
<el-button
type=
"primary"
:icon=
"Plus"
@
click=
"handleAdd"
></el-button>
</el-row>
<el-table
:data=
"form.rule_list"
row-key=
"id"
>
<div
v-if=
"isMobile"
class=
"rule-card-list"
>
<div
class=
"rule-card"
v-for=
"(row, $index) in form.rule_list"
:key=
"row.id || `${row.type}-${$index}`"
>
<div
class=
"rule-card__field"
>
<span
class=
"rule-card__label"
>
规则名称
</span>
<el-input
v-model=
"row.name"
:maxlength=
"20"
style=
"width: 100%"
v-if=
"row.type === 5"
/>
<el-select
v-model=
"row.type"
style=
"width: 100%"
@
change=
"handleTypeChange(row)"
v-else
>
<el-option
v-for=
"item in currentRuleNames(row.type)"
:key=
"item.value"
v-bind=
"item"
></el-option>
</el-select>
</div>
<div
class=
"rule-card__field"
>
<span
class=
"rule-card__label"
>
权重
</span>
<div
class=
"rule-card__inline"
>
<el-input-number
v-model=
"row.percent"
:min=
"0"
@
change=
"handlePercentChange(row, $index, 1)"
/>
%
</div>
</div>
<div
class=
"rule-card__field"
>
<span
class=
"rule-card__label"
>
评分方式
</span>
<el-radio-group
v-model=
"row.rule_mode"
size=
"small"
>
<el-radio
:label=
"1"
>
人工评分
</el-radio>
<el-radio
:label=
"2"
:disabled=
"row.type === 1 || row.type === 5 || row.type === 8"
>
自动评分
</el-radio>
</el-radio-group>
</div>
<div
class=
"rule-card__row"
>
<span>
满分
</span>
<strong>
{{ rowScore(row.percent) }}
</strong>
</div>
<div
class=
"rule-card__actions"
>
<el-button
:disabled=
"row.type === 1"
style=
"padding: 0"
text
type=
"primary"
@
click=
"handleEdit(row)"
v-if=
"row.type !== 1 && row.type !== 5"
>
编辑
</el-button
>
<el-button
style=
"padding: 0"
text
type=
"primary"
@
click=
"handleRemove($index)"
>
删除
</el-button>
</div>
</div>
</div>
<el-table
v-else
:data=
"form.rule_list"
row-key=
"id"
>
<el-table-column
prop=
"name"
width=
"170"
>
<
template
#
default=
"{ row }"
>
<el-input
v-model=
"row.name"
:maxlength=
"20"
style=
"width: 100%"
v-if=
"row.type === 5"
/>
...
...
@@ -400,6 +471,7 @@ onMounted(() => {
<
template
v-if=
"marketingShow"
>
<el-form-item
style=
"margin-bottom: 10px"
class=
"marketing-item"
label-width=
"150px"
:label=
"marketingType[item.type as '1']"
v-for=
"(item, index) in marketingForm.details"
...
...
@@ -452,4 +524,64 @@ onMounted(() => {
float
:
right
;
}
}
.rule-card-list
{
width
:
100%
;
display
:
flex
;
flex-direction
:
column
;
gap
:
12px
;
}
.rule-card
{
width
:
100%
;
padding
:
14px
16px
;
border
:
1px
solid
#ebeef5
;
border-radius
:
10px
;
background
:
#fff
;
box-sizing
:
border-box
;
}
.rule-card__field
+
.rule-card__field
{
margin-top
:
12px
;
}
.rule-card__label
{
display
:
block
;
margin-bottom
:
8px
;
color
:
#909399
;
font-size
:
13px
;
}
.rule-card__inline
{
display
:
flex
;
align-items
:
center
;
gap
:
8px
;
}
.rule-card__row
{
display
:
flex
;
justify-content
:
space-between
;
gap
:
12px
;
padding-top
:
12px
;
margin-top
:
12px
;
border-top
:
1px
solid
#f3f4f6
;
}
.rule-card__actions
{
display
:
flex
;
justify-content
:
flex-end
;
gap
:
12px
;
flex-wrap
:
wrap
;
padding-top
:
12px
;
}
@media
(
max-width
:
767px
)
{
.total
,
.total-c
{
padding
:
10px
0
;
p
{
margin-left
:
0
;
min-width
:
0
;
width
:
100%
;
float
:
none
;
}
}
.marketing-item
{
:deep
(
.el-form-item__content
)
{
width
:
100%
;
}
}
}
</
style
>
src/modules/admin/lab/experiment/components/Questions/GroupQuestion.vue
浏览文件 @
ac045689
...
...
@@ -94,7 +94,8 @@ defineExpose({ formRef })
.upload-box
{
display
:
flex
;
.upload-loading
{
width
:
300px
;
max-width
:
300px
;
width
:
100%
;
display
:
flex
;
align-items
:
center
;
&
:hover
{
...
...
src/modules/admin/lab/experiment/components/Questions/JourneyQuestion.vue
浏览文件 @
ac045689
...
...
@@ -61,7 +61,8 @@ defineExpose({ formRef })
.upload-box
{
display
:
flex
;
.upload-loading
{
width
:
300px
;
max-width
:
300px
;
width
:
100%
;
display
:
flex
;
align-items
:
center
;
&
:hover
{
...
...
src/modules/admin/lab/experiment/components/Questions/MaterialQuestion.vue
浏览文件 @
ac045689
...
...
@@ -119,7 +119,8 @@ defineExpose({ formRef })
.upload-box
{
display
:
flex
;
.upload-loading
{
width
:
300px
;
max-width
:
300px
;
width
:
100%
;
display
:
flex
;
align-items
:
center
;
&
:hover
{
...
...
src/modules/admin/lab/experiment/components/Questions/TagQuestion.vue
浏览文件 @
ac045689
...
...
@@ -93,7 +93,8 @@ defineExpose({ formRef })
.upload-box
{
display
:
flex
;
.upload-loading
{
width
:
300px
;
max-width
:
300px
;
width
:
100%
;
display
:
flex
;
align-items
:
center
;
&
:hover
{
...
...
src/modules/admin/lab/experiment/components/ReportDesignAttachment.vue
浏览文件 @
ac045689
...
...
@@ -43,7 +43,7 @@ function handleHasScoreChange(value?: number) {
<
template
>
<div
class=
"report-design-item"
>
<el-row
:gutter=
"40"
>
<el-col
:
span
=
"6"
>
<el-col
:
xs=
"12"
:sm=
"12"
:md
=
"6"
>
<el-form-item
label=
"附件组件名称"
label-width=
"130px"
...
...
@@ -52,12 +52,12 @@ function handleHasScoreChange(value?: number) {
<el-input
v-model=
"form.name"
placeholder=
"例:实验源代码程序包"
maxlength=
"20"
/>
</el-form-item>
</el-col>
<el-col
:
span
=
"6"
>
<el-col
:
xs=
"12"
:sm=
"12"
:md
=
"6"
>
<el-form-item
label=
"分值"
prop=
"score"
>
<el-input-number
:min=
"0"
v-model=
"form.score"
@
change=
"handleScoreChange"
/>
</el-form-item>
</el-col>
<el-col
:
span
=
"6"
>
<el-col
:
xs=
"12"
:sm=
"12"
:md
=
"6"
>
<el-form-item
label=
"评分规则"
prop=
"rule_mode"
>
<el-radio-group
v-model=
"form.rule_mode"
>
<el-radio
:label=
"1"
>
人工评分
</el-radio>
...
...
@@ -65,7 +65,7 @@ function handleHasScoreChange(value?: number) {
</el-radio-group>
</el-form-item>
</el-col>
<el-col
:
span
=
"6"
>
<el-col
:
xs=
"12"
:sm=
"12"
:md
=
"6"
>
<el-form-item
label=
"最低分"
prop=
"min_score"
>
<el-input-number
:min=
"0"
:max=
"form.score"
v-model=
"form.min_score"
v-if=
"form.rule_mode === 1"
/>
<template
v-else
>
...
...
src/modules/admin/lab/experiment/components/ReportDesignContent.vue
浏览文件 @
ac045689
...
...
@@ -27,7 +27,7 @@ function handleScoreChange(value?: number) {
<
template
>
<div
class=
"report-design-item"
>
<el-row
:gutter=
"40"
>
<el-col
:
span
=
"6"
>
<el-col
:
xs=
"12"
:sm=
"12"
:md
=
"6"
>
<el-form-item
label=
"内容组件名称"
label-width=
"130px"
...
...
@@ -36,12 +36,12 @@ function handleScoreChange(value?: number) {
<el-input
v-model=
"form.name"
placeholder=
"例:实验目的"
maxlength=
"20"
/>
</el-form-item>
</el-col>
<el-col
:
span
=
"6"
>
<el-col
:
xs=
"12"
:sm=
"12"
:md
=
"6"
>
<el-form-item
label=
"分值"
prop=
"score"
>
<el-input-number
:min=
"0"
v-model=
"form.score"
@
change=
"handleScoreChange"
/>
</el-form-item>
</el-col>
<el-col
:
span
=
"6"
>
<el-col
:
xs=
"12"
:sm=
"12"
:md
=
"6"
>
<el-form-item
label=
"评分规则"
prop=
"rule_mode"
>
<el-radio-group
v-model=
"form.rule_mode"
>
<el-radio
:label=
"1"
>
人工评分
</el-radio>
...
...
@@ -49,7 +49,7 @@ function handleScoreChange(value?: number) {
</el-radio-group>
</el-form-item>
</el-col>
<el-col
:
span
=
"6"
>
<el-col
:
xs=
"12"
:sm=
"12"
:md
=
"6"
>
<el-form-item
label=
"最低分"
prop=
"min_score"
>
<el-input-number
:min=
"0"
:max=
"form.score"
v-model=
"form.min_score"
/>
</el-form-item>
...
...
src/modules/admin/lab/experiment/components/ReportDesignPaper.vue
浏览文件 @
ac045689
...
...
@@ -17,17 +17,17 @@ watchEffect(() => {
<div
class=
"report-design-item"
>
<el-form
:model=
"form"
label-suffix=
":"
>
<el-row
:gutter=
"40"
>
<el-col
:
span
=
"6"
>
<el-col
:
xs=
"12"
:sm=
"12"
:md
=
"6"
>
<el-form-item
label=
"试卷组件名称"
prop=
"name"
label-width=
"130px"
>
<el-input
v-model=
"form.name"
placeholder=
"例:商业数据分析基础理论"
maxlength=
"20"
/>
</el-form-item>
</el-col>
<el-col
:
span
=
"6"
>
<el-col
:
xs=
"12"
:sm=
"12"
:md
=
"6"
>
<el-form-item
label=
"分值"
prop=
"score"
>
<el-input-number
:min=
"0"
v-model=
"form.score"
/>
</el-form-item>
</el-col>
<el-col
:
span
=
"6"
>
<el-col
:
xs=
"12"
:sm=
"12"
:md
=
"6"
>
<el-form-item
label=
"评分规则"
prop=
"rule_mode"
>
<el-radio-group
v-model=
"form.rule_mode"
>
<el-radio
:label=
"1"
>
人工评分
</el-radio>
...
...
@@ -35,7 +35,7 @@ watchEffect(() => {
</el-radio-group>
</el-form-item>
</el-col>
<el-col
:
span
=
"6"
>
<el-col
:
xs=
"12"
:sm=
"12"
:md
=
"6"
>
<el-form-item
label=
"最低分"
prop=
"min_score"
>
<el-input-number
:min=
"0"
v-model=
"form.min_score"
/>
</el-form-item>
...
...
src/modules/admin/lab/experiment/components/ReportDesignQuestion.vue
浏览文件 @
ac045689
...
...
@@ -27,7 +27,7 @@ function handleScoreChange(value?: number) {
<
template
>
<div
class=
"report-design-item"
>
<el-row
:gutter=
"40"
>
<el-col
:
span
=
"6"
>
<el-col
:
xs=
"12"
:sm=
"12"
:md
=
"6"
>
<el-form-item
label=
"思考题组件名称"
label-width=
"130px"
...
...
@@ -36,12 +36,12 @@ function handleScoreChange(value?: number) {
<el-input
v-model=
"form.name"
placeholder=
"例:实验扩展思考"
maxlength=
"20"
/>
</el-form-item>
</el-col>
<el-col
:
span
=
"6"
>
<el-col
:
xs=
"12"
:sm=
"12"
:md
=
"6"
>
<el-form-item
label=
"分值"
prop=
"score"
>
<el-input-number
:min=
"0"
v-model=
"form.score"
@
change=
"handleScoreChange"
/>
</el-form-item>
</el-col>
<el-col
:
span
=
"6"
>
<el-col
:
xs=
"12"
:sm=
"12"
:md
=
"6"
>
<el-form-item
label=
"评分规则"
prop=
"rule_mode"
>
<el-radio-group
v-model=
"form.rule_mode"
>
<el-radio
:label=
"1"
>
人工评分
</el-radio>
...
...
@@ -49,7 +49,7 @@ function handleScoreChange(value?: number) {
</el-radio-group>
</el-form-item>
</el-col>
<el-col
:
span
=
"6"
>
<el-col
:
xs=
"12"
:sm=
"12"
:md
=
"6"
>
<el-form-item
label=
"最低分"
prop=
"min_score"
>
<el-input-number
:min=
"0"
:max=
"form.score"
v-model=
"form.min_score"
/>
</el-form-item>
...
...
src/modules/admin/lab/experiment/components/ReportPreview.vue
浏览文件 @
ac045689
...
...
@@ -16,42 +16,42 @@ const teacherText = $computed(() => {
<h1>
实验报告
</h1>
<el-form
label-suffix=
":"
>
<el-row>
<el-col
:
span
=
"12"
>
<el-col
:
xs=
"24"
:sm=
"24"
:md
=
"12"
>
<el-form-item
label=
"课程名称"
>
{{
data
.
course_name
}}
</el-form-item>
</el-col>
<el-col
:
span
=
"12"
>
<el-col
:
xs=
"24"
:sm=
"24"
:md
=
"12"
>
<el-form-item
label=
"实验名称"
>
{{
data
.
name
}}
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col
:
span
=
"12"
>
<el-col
:
xs=
"24"
:sm=
"24"
:md
=
"12"
>
<el-form-item
label=
"实验地点"
></el-form-item>
</el-col>
<el-col
:
span
=
"12"
>
<el-col
:
xs=
"24"
:sm=
"24"
:md
=
"12"
>
<el-form-item
label=
"实验日期"
></el-form-item>
</el-col>
</el-row>
<el-row>
<el-col
:
span
=
"12"
>
<el-col
:
xs=
"24"
:sm=
"24"
:md
=
"12"
>
<el-form-item
label=
"实验类型"
>
{{
data
.
type_name
}}
</el-form-item>
</el-col>
<el-col
:
span
=
"12"
>
<el-col
:
xs=
"24"
:sm=
"24"
:md
=
"12"
>
<el-form-item
label=
"指导教师"
>
{{
teacherText
}}
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col
:
span
=
"12"
>
<el-col
:
xs=
"24"
:sm=
"24"
:md
=
"12"
>
<el-form-item
label=
"专业"
></el-form-item>
</el-col>
<el-col
:
span
=
"12"
>
<el-col
:
xs=
"24"
:sm=
"24"
:md
=
"12"
>
<el-form-item
label=
"班级"
></el-form-item>
</el-col>
</el-row>
<el-row>
<el-col
:
span
=
"12"
>
<el-col
:
xs=
"24"
:sm=
"24"
:md
=
"12"
>
<el-form-item
label=
"姓名"
></el-form-item>
</el-col>
<el-col
:
span
=
"12"
>
<el-col
:
xs=
"24"
:sm=
"24"
:md
=
"12"
>
<el-form-item
label=
"学号"
></el-form-item>
</el-col>
</el-row>
...
...
src/modules/admin/lab/experiment/views/Questions.vue
浏览文件 @
ac045689
...
...
@@ -143,6 +143,7 @@ const handleSubmit = async () => {
<
style
lang=
"scss"
scoped
>
.btn-box
{
display
:
flex
;
flex-wrap
:
wrap
;
gap
:
8px
;
margin-bottom
:
20px
;
.btn-r
{
margin-left
:
auto
;
...
...
src/modules/admin/lab/experiment/views/Report.vue
浏览文件 @
ac045689
...
...
@@ -107,21 +107,21 @@ function handlePreview() {
<AppCard
title=
"实验报告规则设置"
>
<el-form
ref=
"formRef"
:model=
"form"
label-suffix=
":"
hide-required-asterisk
v-if=
"detail"
>
<el-row>
<el-col
:
span
=
"8"
>
<el-col
:
xs=
"24"
:sm=
"12"
:md
=
"8"
>
<el-form-item
label=
"实验名称"
>
{{
detail
.
name
}}
</el-form-item>
</el-col>
<el-col
:
span
=
"8"
>
<el-col
:
xs=
"24"
:sm=
"12"
:md
=
"8"
>
<el-form-item
label=
"实验课程名称"
>
{{
detail
.
course_name
}}
</el-form-item>
</el-col>
<el-col
:
span
=
"8"
>
<el-col
:
xs=
"24"
:sm=
"12"
:md
=
"8"
>
<el-form-item
label=
"所属机构/学校"
>
{{
detail
.
organ_id_name
}}
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col
:
span
=
"8"
>
<el-col
:
xs=
"24"
:sm=
"12"
:md
=
"8"
>
<el-form-item
label=
"实验类型"
>
{{
detail
.
type_name
}}
</el-form-item>
</el-col>
<el-col
:
span
=
"8"
>
<el-col
:
xs=
"24"
:sm=
"12"
:md
=
"8"
>
<el-form-item
:span=
"3"
label=
"指导教师"
>
{{
teacherText
}}
</el-form-item>
</el-col>
</el-row>
...
...
src/modules/admin/lab/experiment/views/View.vue
浏览文件 @
ac045689
...
...
@@ -231,3 +231,16 @@ function handleUpdateGradeRules() {
:data=
"detail"
v-if=
"gradeRulesDialogVisible && detail"
></GradeRulesDialog>
</template>
<
style
lang=
"scss"
scoped
>
.btn-all
{
display
:
flex
;
flex-wrap
:
wrap
;
gap
:
8px
;
}
@media
(
max-width
:
767px
)
{
.btn-all
.el-button
{
width
:
100%
;
}
}
</
style
>
src/modules/admin/lab/score/components/ImportDialog.vue
浏览文件 @
ac045689
...
...
@@ -50,7 +50,7 @@ watchEffect(() => {
align-items
:
center
;
justify-content
:
center
;
.el-button
{
width
:
220px
;
max-width
:
220px
;
width
:
100%
;
}
p
{
color
:
#999
;
...
...
src/modules/admin/lab/score/components/Question.vue
浏览文件 @
ac045689
...
...
@@ -148,7 +148,7 @@ const handleSubmit = function () {
<el-button
type=
"primary"
@
click=
"updateScore(questionDetail)"
>
保存
</el-button>
</el-row>
</el-card>
<el-row
justify=
"center"
>
<el-row
justify=
"center"
class=
"question-actions"
>
<el-button
:disabled=
"!hasPrev"
type=
"primary"
@
click=
"handlePrev"
>
上一题
</el-button>
<el-button
:disabled=
"!hasNext"
type=
"primary"
@
click=
"handleNext"
>
下一题
</el-button>
<el-button
type=
"primary"
@
click=
"handleSubmit()"
>
提交
</el-button>
...
...
@@ -237,4 +237,51 @@ const handleSubmit = function () {
font-size
:
14px
;
}
}
.question-actions
{
gap
:
8px
;
}
@media
(
max-width
:
767px
)
{
.question-number
{
dd
{
display
:
flex
;
flex-wrap
:
wrap
;
gap
:
8px
;
span
{
margin-right
:
0
;
margin-bottom
:
0
;
}
}
}
.question-item__stem
,
.question-item__content
,
.question-item__tip
{
line-height
:
1
.6
;
word-break
:
break-word
;
}
.question-item__files-item
{
align-items
:
flex-start
;
}
.question-item__files-item__title
{
min-width
:
0
;
p
{
word-break
:
break-word
;
}
}
.question-item-score
{
flex-direction
:
column
;
gap
:
10px
;
span
{
margin-right
:
0
;
}
}
.question-actions
{
:deep
(
.el-button
)
{
margin-left
:
0
;
width
:
calc
(
50%
-
4px
);
}
:deep
(
.el-button
:last-child
)
{
width
:
100%
;
}
}
}
</
style
>
src/modules/admin/lab/score/components/ScoreDialog.vue
浏览文件 @
ac045689
<
script
setup
lang=
"ts"
>
import
type
{
RecordItem
,
FileItem
}
from
'../types'
import
{
ElMessage
,
ElMessageBox
}
from
'element-plus'
import
{
useMediaQuery
}
from
'@vueuse/core'
import
{
getExperimentScore
,
getExperimentScoreTemplate
,
updateExperimentScore
,
getExperimentReport
}
from
'../api'
import
{
useAppConfig
}
from
'@/composables/useAppConfig'
const
appConfig
=
useAppConfig
()
const
isMobile
=
useMediaQuery
(
'(max-width: 767px)'
)
const
ScoreViewPicturesDialog
=
defineAsyncComponent
(()
=>
import
(
'./ScoreViewPicturesDialog.vue'
))
const
ScoreViewPrepareDialog
=
defineAsyncComponent
(()
=>
import
(
'./ScoreViewPrepareDialog.vue'
))
...
...
@@ -171,33 +173,33 @@ function handleViewAuto(row: any) {
@
update:modelValue=
"(value) => $emit('update:modelValue', value)"
>
<el-form
label-width=
"120px"
label-suffix=
":"
v-if=
"detail"
>
<el-row>
<el-col
:
span
=
"12"
>
<el-col
:
xs=
"24"
:sm=
"24"
:md
=
"12"
>
<el-form-item
label=
"实验名称"
>
{{
data
.
experiment_name
}}
</el-form-item>
</el-col>
<el-col
:
span
=
"12"
>
<el-col
:
xs=
"24"
:sm=
"24"
:md
=
"12"
>
<el-form-item
label=
"实验课程名称"
>
{{
experiment
.
course
.
name
}}
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col
:
span
=
"12"
>
<el-col
:
xs=
"24"
:sm=
"24"
:md
=
"12"
>
<el-form-item
label=
"学生姓名"
>
{{
data
.
student_name
}}
</el-form-item>
</el-col>
<el-col
:
span
=
"12"
>
<el-col
:
xs=
"24"
:sm=
"24"
:md
=
"12"
>
<el-form-item
label=
"学生学号"
>
{{
data
.
sno_number
}}
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col
:
span
=
"12"
>
<el-col
:
xs=
"24"
:sm=
"24"
:md
=
"12"
>
<el-form-item
label=
"所属专业"
>
{{
data
.
specialty_name
}}
</el-form-item>
</el-col>
<el-col
:
span
=
"12"
>
<el-col
:
xs=
"24"
:sm=
"24"
:md
=
"12"
>
<el-form-item
label=
"所属班级"
>
{{
data
.
class_name
}}
</el-form-item>
</el-col>
</el-row>
<el-divider
/>
<el-form-item
label=
"理论成绩"
>
{{
data
.
theory_score
}}
</el-form-item>
<el-form-item
label=
"实验成绩"
>
<el-row
justify=
"end"
style=
"width: 100%"
>
<el-row
justify=
"end"
style=
"width: 100%"
class=
"score-action-row"
>
<el-button
type=
"primary"
@
click=
"prepareVisible = true"
>
查看实验准备
</el-button>
<el-button
type=
"primary"
@
click=
"resultVisible = true"
>
查看实验结果
</el-button>
<el-button
type=
"primary"
@
click=
"pictureVisible = true"
>
查看截图
</el-button>
...
...
@@ -208,11 +210,60 @@ function handleViewAuto(row: any) {
<p
class=
"t1"
>
{{
scoreValue
(
score
)
}}
</p>
<p
class=
"t2"
>
满分:
{{
experiment
.
score
}}
</p>
</div>
<div
v-if=
"isMobile"
class=
"score-detail-card-list"
>
<div
class=
"score-detail-card"
v-for=
"row in form.score_details"
:key=
"`$
{row.type}-${row.name}`">
<div
class=
"score-detail-card__row"
><span>
实验成绩组成项
</span><strong>
{{
row
.
name
||
'-'
}}
</strong></div>
<div
class=
"score-detail-card__row"
><span>
权重
</span><strong>
{{
row
.
percent
}}
%
</strong></div>
<div
class=
"score-detail-card__row"
><span>
满分
</span><strong>
{{
row
.
score
||
'-'
}}
</strong></div>
<div
class=
"score-detail-card__row score-detail-card__row--field"
>
<span>
得分
</span>
<div
class=
"score-detail-card__field"
>
<el-input-number
v-model=
"row.commit_score"
:min=
"0"
:max=
"100"
:controls=
"false"
:step=
"0.01"
step-strictly
placeholder=
"请输入成绩"
style=
"width: 100%"
v-if=
"!(row.type === 1 && experiment.report_upload_way === 2)"
></el-input-number>
<strong
v-else
>
{{
row
.
commit_score
}}
</strong>
</div>
</div>
<div
class=
"score-detail-card__actions"
>
<template
v-if=
"row.type === 1"
>
<el-button
link
type=
"primary"
v-if=
"experiment.report_upload_way === 2"
>
<a
:href=
"getOperationUrl(row.type)"
target=
"_blank"
>
批改
</a>
</el-button>
<template
v-if=
"experiment.report_upload_way === 1"
>
<el-button
link
type=
"primary"
v-if=
"file?.url"
>
<a
:href=
"file.url"
target=
"_blank"
>
查阅报告
</a>
</el-button>
<p
class=
"score-detail-card__warning"
v-else
>
未上传
</p>
</
template
>
</template>
<
template
v-if=
"[6, 7, 9, 11, 12].includes(row.type)"
>
<el-button
link
type=
"primary"
@
click=
"handleViewAuto(row)"
>
查看自动评分结果
</el-button>
</
template
>
<
template
v-if=
"[6, 7, 8, 9, 10, 11, 12].includes(row.type)"
>
<el-button
link
type=
"primary"
>
<a
:href=
"`/admin/lab/score/score?id=$
{props.data.experiment_id}
&
sid=${props.data.student_id}
&
type=${row.type}`"
target="_blank"
>查看原始结果
</a
>
</el-button>
</
template
>
</div>
</div>
</div>
<el-table
:data=
"form.score_details"
stripe
:header-cell-style=
"{ background: '#ededed' }"
style="margin-top: 20px">
style=
"margin-top: 20px"
v-else
>
<el-table-column
label=
"实验成绩组成项"
prop=
"name"
align=
"left"
></el-table-column>
<el-table-column
label=
"权重"
prop=
"percent"
align=
"center"
width=
"80"
>
<
template
#
default=
"{ row }"
>
{{
row
.
percent
}}
%
</
template
>
...
...
@@ -301,6 +352,7 @@ function handleViewAuto(row: any) {
display
:
flex
;
align-items
:
center
;
justify-content
:
center
;
gap
:
24px
;
}
.score-result__total
{
min-width
:
120px
;
...
...
@@ -311,4 +363,63 @@ function handleViewAuto(row: any) {
color
:
var
(
--
main-color
);
}
}
.score-detail-card-list
{
flex
:
1
;
display
:
flex
;
flex-direction
:
column
;
gap
:
12px
;
margin-top
:
20px
;
}
.score-detail-card
{
padding
:
14px
16px
;
border
:
1px
solid
#ebeef5
;
border-radius
:
10px
;
background
:
#fff
;
}
.score-detail-card__row
{
display
:
flex
;
justify-content
:
space-between
;
gap
:
12px
;
padding
:
6px
0
;
border-bottom
:
1px
solid
#f3f4f6
;
}
.score-detail-card__row
:last-of-type
{
border-bottom
:
none
;
}
.score-detail-card__row
span
{
flex-shrink
:
0
;
color
:
#909399
;
}
.score-detail-card__row
strong
{
min-width
:
0
;
text-align
:
right
;
word-break
:
break-word
;
}
.score-detail-card__row--field
{
align-items
:
center
;
}
.score-detail-card__field
{
width
:
140px
;
max-width
:
100%
;
}
.score-detail-card__actions
{
display
:
flex
;
justify-content
:
flex-end
;
flex-wrap
:
wrap
;
gap
:
8px
;
padding-top
:
8px
;
}
.score-detail-card__warning
{
color
:
red
;
}
@media
(
max-width
:
767px
)
{
.score-action-row
{
gap
:
8px
;
justify-content
:
flex-start
!
important
;
}
.score-result
{
flex-direction
:
column
;
align-items
:
stretch
;
}
}
</
style
>
src/modules/admin/lab/score/components/ScoreViewPicturesDialog.vue
浏览文件 @
ac045689
<
script
setup
lang=
"ts"
>
import
type
{
RecordItem
,
FileItem
}
from
'../types'
import
{
useMediaQuery
}
from
'@vueuse/core'
interface
Props
{
data
:
RecordItem
}
const
props
=
defineProps
<
Props
>
()
const
isMobile
=
useMediaQuery
(
'(max-width: 767px)'
)
// 实验过程截图
const
pictures
=
$computed
<
FileItem
[]
>
(()
=>
{
...
...
@@ -19,7 +21,7 @@ const pictures = $computed<FileItem[]>(() => {
<
template
>
<el-dialog
title=
"查看实验截图"
width=
"800px"
>
<ul
class=
"picture-list"
>
<ul
class=
"picture-list"
:class=
"
{ 'picture-list--mobile': isMobile }"
>
<li
v-for=
"item in pictures"
:key=
"item.url"
>
<p
class=
"t1"
>
<a
:href=
"item.url"
target=
"_blank"
>
{{
item
.
name
}}
</a>
...
...
@@ -42,6 +44,7 @@ const pictures = $computed<FileItem[]>(() => {
display
:
flex
;
justify-content
:
space-between
;
line-height
:
30px
;
gap
:
12px
;
}
a
{
color
:
var
(
--
main-color
);
...
...
@@ -53,4 +56,21 @@ const pictures = $computed<FileItem[]>(() => {
overflow
:
hidden
;
}
}
.picture-list--mobile
{
li
{
display
:
block
;
padding
:
12px
14px
;
border
:
1px
solid
#ebeef5
;
border-radius
:
10px
;
margin-bottom
:
12px
;
line-height
:
1
.7
;
}
.t1
{
white-space
:
normal
;
word-break
:
break-word
;
}
.t2
{
color
:
#909399
;
}
}
</
style
>
src/modules/admin/lab/score/views/Report.vue
浏览文件 @
ac045689
<
script
setup
>
import
{
ElMessage
}
from
'element-plus'
import
{
useMediaQuery
}
from
'@vueuse/core'
import
{
getExperimentScore
,
getExperimentReport
,
updateExperimentReport
,
getExperimentScoreTemplate
}
from
'../api'
import
{
reportScoreRule
}
from
'@/utils/dictionary'
const
route
=
useRoute
()
const
isMobile
=
useMediaQuery
(
'(max-width: 767px)'
)
let
experiment
=
$ref
()
let
report
=
$ref
()
...
...
@@ -122,42 +124,42 @@ function scoreValue(value) {
scroll-to-error
v-if=
"experiment && report"
>
<el-row>
<el-col
:
span
=
"12"
>
<el-col
:
xs=
"24"
:sm=
"24"
:md
=
"12"
>
<el-form-item
label=
"课程名称"
>
{{
experiment
.
course
.
name
}}
</el-form-item>
</el-col>
<el-col
:
span
=
"12"
>
<el-col
:
xs=
"24"
:sm=
"24"
:md
=
"12"
>
<el-form-item
label=
"实验名称"
>
{{
experiment
.
name
}}
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col
:
span
=
"12"
>
<el-col
:
xs=
"24"
:sm=
"24"
:md
=
"12"
>
<el-form-item
label=
"实验地点"
>
{{
report
.
experiment_address
}}
</el-form-item>
</el-col>
<el-col
:
span
=
"12"
>
<el-col
:
xs=
"24"
:sm=
"24"
:md
=
"12"
>
<el-form-item
label=
"实验日期"
>
{{
report
.
experiment_date
}}
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col
:
span
=
"12"
>
<el-col
:
xs=
"24"
:sm=
"24"
:md
=
"12"
>
<el-form-item
label=
"实验类型"
>
{{
experiment
.
type_level
}}
</el-form-item>
</el-col>
<el-col
:
span
=
"12"
>
<el-col
:
xs=
"24"
:sm=
"24"
:md
=
"12"
>
<el-form-item
label=
"指导教师"
>
{{
teacherText
}}
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col
:
span
=
"12"
>
<el-col
:
xs=
"24"
:sm=
"24"
:md
=
"12"
>
<el-form-item
label=
"专业"
>
{{
experiment
.
student
.
specialty
.
name
}}
</el-form-item>
</el-col>
<el-col
:
span
=
"12"
>
<el-col
:
xs=
"24"
:sm=
"24"
:md
=
"12"
>
<el-form-item
label=
"班级"
>
{{
classText
}}
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col
:
span
=
"12"
>
<el-col
:
xs=
"24"
:sm=
"24"
:md
=
"12"
>
<el-form-item
label=
"姓名"
>
{{
experiment
.
student
.
name
}}
</el-form-item>
</el-col>
<el-col
:
span
=
"12"
>
<el-col
:
xs=
"24"
:sm=
"24"
:md
=
"12"
>
<el-form-item
label=
"学号"
>
{{
experiment
.
student
.
sno_number
}}
</el-form-item>
</el-col>
</el-row>
...
...
@@ -195,7 +197,22 @@ function scoreValue(value) {
</
template
>
<!-- 附件 -->
<
template
v-if=
"item.type === 2"
>
<el-table
:data=
"item.files"
stripe
:header-cell-style=
"
{ background: '#ededed' }" style="margin-top: 20px">
<div
v-if=
"isMobile"
class=
"report-file-card-list"
>
<div
class=
"report-file-card"
v-for=
"(file, fileIndex) in item.files || []"
:key=
"file.id || file.url || fileIndex"
>
<div
class=
"report-file-card__row"
><span>
序号
</span><strong>
{{
fileIndex
+
1
}}
</strong></div>
<div
class=
"report-file-card__row"
>
<span>
附件类型
</span>
<strong>
{{
file
.
type
===
'1'
?
'本地上传'
:
'实验截图'
}}
</strong>
</div>
<div
class=
"report-file-card__row"
><span>
附件名称
</span><strong>
{{
file
.
name
||
'-'
}}
</strong></div>
<div
class=
"report-file-card__row"
><span>
更新时间
</span><strong>
{{
file
.
updated_time
||
'-'
}}
</strong></div>
<div
class=
"report-file-card__actions"
>
<el-button
text
type=
"primary"
><a
:href=
"file.url"
target=
"_blank"
>
查看
</a></el-button>
</div>
</div>
<el-empty
v-if=
"!item.files?.length"
description=
"暂无附件"
/>
</div>
<el-table
v-else
:data=
"item.files"
stripe
:header-cell-style=
"
{ background: '#ededed' }" style="margin-top: 20px">
<el-table-column
label=
"序号"
type=
"index"
width=
"80"
align=
"center"
></el-table-column>
<el-table-column
label=
"附件类型"
prop=
"type"
align=
"center"
>
<template
#
default=
"
{ row }">
...
...
@@ -314,6 +331,42 @@ function scoreValue(value) {
justify-content
:
space-between
;
line-height
:
32px
;
}
.report-file-card-list
{
display
:
flex
;
flex-direction
:
column
;
gap
:
12px
;
margin-top
:
20px
;
}
.report-file-card
{
padding
:
14px
16px
;
border
:
1px
solid
#ebeef5
;
border-radius
:
10px
;
background
:
#fff
;
}
.report-file-card__row
{
display
:
flex
;
justify-content
:
space-between
;
gap
:
12px
;
padding
:
6px
0
;
border-bottom
:
1px
solid
#f3f4f6
;
}
.report-file-card__row
:last-of-type
{
border-bottom
:
none
;
}
.report-file-card__row
span
{
flex-shrink
:
0
;
color
:
#909399
;
}
.report-file-card__row
strong
{
min-width
:
0
;
text-align
:
right
;
word-break
:
break-word
;
}
.report-file-card__actions
{
display
:
flex
;
justify-content
:
flex-end
;
padding-top
:
8px
;
}
.t2
{
margin
:
40px
0
20px
;
...
...
@@ -323,4 +376,34 @@ function scoreValue(value) {
text-align
:
center
;
line-height
:
40px
;
}
@media
(
max-width
:
767px
)
{
.report-title
{
padding
:
12px
0
20px
;
}
.report-score
{
margin
:
24px
0
;
text-align
:
center
;
font-size
:
20px
;
}
.report-form-item
{
margin-bottom
:
24px
;
.form-hd
{
flex-direction
:
column
;
gap
:
8px
;
align-items
:
flex-start
;
span
{
padding
:
0
;
}
}
}
.report-form-item-comment_hd
{
flex-direction
:
column
;
align-items
:
flex-start
;
gap
:
8px
;
}
.t2
{
margin
:
24px
0
16px
;
line-height
:
36px
;
}
}
</
style
>
src/modules/admin/lab/video/views/Index.vue
浏览文件 @
ac045689
...
...
@@ -85,4 +85,7 @@ function onUpdateSuccess() {
grid-template-columns
:
repeat
(
5
,
1fr
);
gap
:
20px
;
}
@media
(
max-width
:
1400px
)
{
.list-card
{
grid-template-columns
:
repeat
(
3
,
1fr
);
}
}
@media
(
max-width
:
1024px
)
{
.list-card
{
grid-template-columns
:
repeat
(
2
,
1fr
);
gap
:
16px
;
}
}
@media
(
max-width
:
767px
)
{
.list-card
{
grid-template-columns
:
1fr
;
gap
:
12px
;
}
}
</
style
>
src/modules/ai/views/Index.vue
浏览文件 @
ac045689
...
...
@@ -50,8 +50,8 @@ watch(messages.value, () => nextTick(() => scrollToBottom()))
:srcdoc=
"item.content"
frameborder=
"0"
v-if=
"item.type === 'html'"
width=
"
900
"
height=
"
5
00"
></iframe>
width=
"
100%
"
height=
"
4
00"
></iframe>
<div
v-html=
"item.content"
v-else
></div>
</div>
</div>
...
...
@@ -94,7 +94,8 @@ watch(messages.value, () => nextTick(() => scrollToBottom()))
display
:
flex
;
flex-direction
:
column
;
flex
:
1
;
width
:
1000px
;
max-width
:
1000px
;
width
:
100%
;
overflow
:
hidden
;
.ai-upload
{
margin
:
40px
0
;
...
...
@@ -106,6 +107,7 @@ watch(messages.value, () => nextTick(() => scrollToBottom()))
}
.ai-header-inner
{
max-width
:
1000px
;
width
:
100%
;
padding
:
18px
0
;
display
:
flex
;
align-items
:
center
;
...
...
@@ -235,4 +237,8 @@ watch(messages.value, () => nextTick(() => scrollToBottom()))
background-color
:
#000
;
}
}
@media
(
max-width
:
767px
)
{
.ai-header-inner
{
flex-direction
:
column
;
gap
:
8px
;
}
.ai-main
{
padding
:
12px
;
}
}
</
style
>
src/modules/drawLots/views/Index.vue
浏览文件 @
ac045689
...
...
@@ -106,6 +106,8 @@ function handlePrint() {
background
:
url(@/assets/images/drawLots/bg.png)
no-repeat
center
top
;
.inner
{
max-width
:
1100px
;
width
:
100%
;
padding
:
0
16px
;
margin
:
0
auto
;
}
h1
{
...
...
@@ -137,7 +139,8 @@ function handlePrint() {
.box
{
position
:
relative
;
z-index
:
100
;
width
:
800px
;
max-width
:
800px
;
width
:
100%
;
height
:
260px
;
background
:
#ab0a3d
;
border-radius
:
8px
;
...
...
@@ -152,7 +155,8 @@ function handlePrint() {
text-align
:
center
;
}
.box-main
{
width
:
760px
;
max-width
:
760px
;
width
:
100%
;
height
:
260px
;
background
:
#fff
url(@/assets/images/drawLots/bg3.png)
no-repeat
right
10px
center
;
border-radius
:
8px
;
...
...
@@ -175,7 +179,7 @@ function handlePrint() {
}
.box-info
{
position
:
relative
;
width
:
491px
;
max-width
:
491px
;
width
:
100%
;
height
:
167px
;
background
:
url(@/assets/images/drawLots/bg2.png)
no-repeat
;
display
:
flex
;
...
...
@@ -240,7 +244,8 @@ function handlePrint() {
position
:
absolute
;
top
:
0
;
left
:
-20px
;
width
:
840px
;
max-width
:
840px
;
width
:
100%
;
height
:
30px
;
background
:
#ab0a3d
;
border-radius
:
8px
;
...
...
@@ -272,4 +277,124 @@ function handlePrint() {
margin
:
40px
0
20px
;
}
}
@media
(
max-width
:
767px
)
{
.drawLots
{
background-size
:
cover
;
.inner
{
padding
:
0
12px
24px
;
}
h1
{
padding
:
40px
0
24px
;
font-size
:
28px
;
line-height
:
1
.4
;
}
.search
{
margin
:
0
0
24px
;
flex-wrap
:
wrap
;
gap
:
12px
;
.el-input
{
width
:
100%
;
height
:
48px
;
font-size
:
16px
;
}
.el-button
{
margin-left
:
0
;
width
:
100%
;
height
:
48px
;
font-size
:
16px
;
}
}
.box
{
height
:
auto
;
.box-title
{
padding
:
0
12px
;
font-size
:
18px
;
}
.box-main
{
flex-direction
:
column
;
height
:
auto
;
padding
:
20px
12px
;
gap
:
16px
;
background-position
:
right
bottom
;
background-size
:
120px
auto
;
}
.box-info
{
width
:
100%
;
max-width
:
100%
;
height
:
auto
;
background-size
:
cover
;
background-position
:
center
;
flex-direction
:
column
;
padding
:
16px
12px
;
box-sizing
:
border-box
;
.name
{
position
:
static
;
max-width
:
100%
;
margin-bottom
:
10px
;
white-space
:
normal
;
word-break
:
break-word
;
}
.col
{
width
:
100%
;
margin-top
:
0
;
margin-right
:
0
;
}
dl
{
font-size
:
14px
;
line-height
:
1
.5
;
dt
{
width
:
72px
;
}
dd
{
flex
:
1
;
min-width
:
0
;
word-break
:
break-word
;
}
&
+
dl
{
margin-top
:
12px
;
}
}
}
}
.footer
{
height
:
auto
;
margin-top
:
16px
;
background-size
:
cover
;
background-position
:
center
;
padding
:
28px
0
18px
;
.button
{
max-width
:
100%
;
width
:
100%
;
margin
:
0
auto
;
}
}
}
::v-deep
(
.drawLots-dialog
)
{
.bar
{
left
:
0
;
width
:
100%
;
height
:
20px
;
}
h2
{
margin
:
24px
0
;
font-size
:
22px
;
}
.el-form
{
margin-left
:
0
;
.el-form-item
{
margin-bottom
:
12px
;
}
p
{
font-size
:
18px
;
}
}
.el-button
{
width
:
100%
;
max-width
:
180px
;
height
:
48px
;
font-size
:
16px
;
margin
:
24px
0
12px
;
}
}
}
</
style
>
src/modules/home/components/AdminHome.vue
浏览文件 @
ac045689
...
...
@@ -51,33 +51,38 @@ function handleChange(id: string, type: number) {
margin
:
20px
auto
;
background
:
url(@/assets/images/home_expert_bg.png)
no-repeat
center
center
;
background-size
:
contain
;
width
:
1308px
;
width
:
100%
;
max-width
:
1308px
;
height
:
642px
;
.link1
{
position
:
absolute
;
bottom
:
100px
;
width
:
400px
;
width
:
100%
;
max-width
:
400px
;
height
:
300px
;
}
.link2
{
position
:
absolute
;
left
:
450px
;
bottom
:
50px
;
width
:
310px
;
width
:
100%
;
max-width
:
310px
;
height
:
250px
;
}
.link3
{
position
:
absolute
;
top
:
0
;
right
:
80px
;
width
:
400px
;
width
:
100%
;
max-width
:
400px
;
height
:
300px
;
}
.link4
{
position
:
absolute
;
bottom
:
0
;
right
:
0
;
width
:
400px
;
width
:
100%
;
max-width
:
400px
;
height
:
340px
;
}
}
...
...
@@ -94,4 +99,18 @@ function handleChange(id: string, type: number) {
margin-left
:
20px
;
}
}
@media
(
max-width
:
1024px
)
{
.bg
{
padding
:
30px
16px
;
}
}
@media
(
max-width
:
767px
)
{
.bg
{
padding
:
20px
12px
;
}
.link1
,
.link2
,
.link3
,
.link4
{
max-width
:
100%
;
margin-bottom
:
16px
;
}
}
</
style
>
src/modules/home/components/StudentHome.vue
浏览文件 @
ac045689
...
...
@@ -109,7 +109,8 @@ const learnURL = import.meta.env.VITE_SAAS_LEARN_URL
position
:
absolute
;
top
:
200px
;
right
:
80px
;
width
:
300px
;
width
:
100%
;
max-width
:
300px
;
height
:
300px
;
}
}
...
...
@@ -122,26 +123,30 @@ const learnURL = import.meta.env.VITE_SAAS_LEARN_URL
margin
:
20px
auto
;
background
:
url(@/assets/images/home_student_bg.png)
no-repeat
center
center
;
background-size
:
contain
;
width
:
1215px
;
width
:
100%
;
max-width
:
1215px
;
height
:
577px
;
.link1
{
position
:
absolute
;
top
:
160px
;
width
:
250px
;
width
:
100%
;
max-width
:
250px
;
height
:
250px
;
}
.link2
{
position
:
absolute
;
left
:
400px
;
bottom
:
0
;
width
:
300px
;
width
:
100%
;
max-width
:
300px
;
height
:
250px
;
}
.link3
{
position
:
absolute
;
top
:
200px
;
right
:
0
;
width
:
300px
;
width
:
100%
;
max-width
:
300px
;
height
:
300px
;
}
}
...
...
@@ -158,4 +163,13 @@ const learnURL = import.meta.env.VITE_SAAS_LEARN_URL
margin-left
:
20px
;
}
}
@media
(
max-width
:
767px
)
{
.bg
{
padding
:
20px
12px
;
}
.link1
,
.link2
,
.link3
,
.link4
{
max-width
:
100%
;
margin-bottom
:
16px
;
}
}
</
style
>
src/modules/home/components/TeacherHome.vue
浏览文件 @
ac045689
...
...
@@ -72,33 +72,38 @@ function handleChange(id: string, type: number) {
margin
:
20px
auto
;
background
:
url(@/assets/images/home_teacher_bg.png)
no-repeat
center
center
;
background-size
:
contain
;
width
:
1308px
;
width
:
100%
;
max-width
:
1308px
;
height
:
642px
;
.link1
{
position
:
absolute
;
bottom
:
100px
;
width
:
400px
;
width
:
100%
;
max-width
:
400px
;
height
:
300px
;
}
.link2
{
position
:
absolute
;
left
:
450px
;
bottom
:
50px
;
width
:
310px
;
width
:
100%
;
max-width
:
310px
;
height
:
250px
;
}
.link3
{
position
:
absolute
;
top
:
0
;
right
:
80px
;
width
:
400px
;
width
:
100%
;
max-width
:
400px
;
height
:
300px
;
}
.link4
{
position
:
absolute
;
bottom
:
0
;
right
:
0
;
width
:
400px
;
width
:
100%
;
max-width
:
400px
;
height
:
340px
;
}
}
...
...
@@ -115,4 +120,18 @@ function handleChange(id: string, type: number) {
margin-left
:
20px
;
}
}
@media
(
max-width
:
1024px
)
{
.bg
{
padding
:
30px
16px
;
}
}
@media
(
max-width
:
767px
)
{
.bg
{
padding
:
20px
12px
;
}
.link1
,
.link2
,
.link3
,
.link4
{
max-width
:
100%
;
margin-bottom
:
16px
;
}
}
</
style
>
src/modules/home/components/Total.vue
浏览文件 @
ac045689
...
...
@@ -63,6 +63,8 @@ onMounted(() => {
.total
{
display
:
flex
;
justify-content
:
space-between
;
flex-wrap
:
wrap
;
row-gap
:
16px
;
dl
{
text-align
:
left
;
}
...
...
@@ -92,4 +94,24 @@ onMounted(() => {
color
:
#999
;
}
}
@media
(
max-width
:
767px
)
{
.total
{
justify-content
:
flex-start
;
gap
:
8px
;
padding
:
0
8px
;
}
.total
.line
{
display
:
none
;
}
.total
dl
{
flex
:
0
0
calc
(
50%
-
4px
);
box-sizing
:
border-box
;
padding
:
12px
8px
;
background
:
#fff
;
border-radius
:
8px
;
text-align
:
center
;
}
.total
dt
span
{
font-size
:
22px
;
}
.total
dd
{
font-size
:
13px
;
}
}
</
style
>
src/modules/home/views/Game.vue
浏览文件 @
ac045689
...
...
@@ -66,12 +66,14 @@ const menus = computed(() => {
.card-list
{
height
:
100%
;
display
:
flex
;
flex-wrap
:
wrap
;
align-items
:
center
;
justify-content
:
center
;
gap
:
30px
;
}
.card
{
width
:
310px
;
width
:
100%
;
max-width
:
310px
;
height
:
520px
;
display
:
flex
;
flex-direction
:
column
;
...
...
@@ -79,7 +81,8 @@ const menus = computed(() => {
justify-content
:
center
;
background-color
:
#fff
;
.icon
{
width
:
246px
;
max-width
:
246px
;
width
:
100%
;
height
:
238px
;
img
{
width
:
100%
;
...
...
@@ -93,7 +96,8 @@ const menus = computed(() => {
color
:
#578ef9
;
}
.button
{
width
:
130px
;
max-width
:
130px
;
width
:
100%
;
height
:
40px
;
font-size
:
16px
;
line-height
:
40px
;
...
...
@@ -106,4 +110,13 @@ const menus = computed(() => {
}
}
}
@media
(
max-width
:
767px
)
{
.card-list
{
flex-direction
:
column
;
gap
:
16px
;
}
.card
{
max-width
:
100%
;
}
}
</
style
>
src/modules/register/views/Index.vue
浏览文件 @
ac045689
...
...
@@ -114,7 +114,7 @@ const goPage = function () {
<AppCard>
<div
style=
"position: relative; z-index: 9"
>
<h1>
2023年全国大学生商业数据分析与应用大赛
</h1>
<el-form
ref=
"formRef"
:model=
"form"
:rules=
"rules"
label-width=
"124px"
style=
"
width: 600px; margin: 0 auto
"
>
<el-form
ref=
"formRef"
:model=
"form"
:rules=
"rules"
label-width=
"124px"
style=
"
max-width: 600px; width: 100%; margin: 0 auto; padding: 0 16px;
"
>
<!--
<el-form-item
label=
"报名赛项"
>
<el-input
v-model=
"form.name"
disabled
/>
</el-form-item>
-->
...
...
@@ -209,14 +209,16 @@ const goPage = function () {
top
:
10%
;
left
:
50%
;
transform
:
translateX
(
-50%
);
width
:
202px
;
max-width
:
202px
;
width
:
100%
;
}
.pop
{
position
:
absolute
;
top
:
25%
;
left
:
50%
;
transform
:
translateX
(
-50%
);
width
:
366px
;
max-width
:
366px
;
width
:
100%
;
img
{
width
:
100%
;
}
...
...
@@ -226,7 +228,8 @@ const goPage = function () {
left
:
50%
;
transform
:
translateX
(
-50%
);
.btn1
{
width
:
170px
;
max-width
:
170px
;
width
:
100%
;
line-height
:
50px
;
background
:
#e92d55
;
border-radius
:
40px
;
...
...
@@ -236,7 +239,8 @@ const goPage = function () {
cursor
:
pointer
;
}
.btn2
{
width
:
170px
;
max-width
:
170px
;
width
:
100%
;
line-height
:
50px
;
border
:
1px
solid
#e92d55
;
border-radius
:
40px
;
...
...
@@ -281,7 +285,8 @@ const goPage = function () {
top
:
50%
;
left
:
50%
;
transform
:
translate
(
-50%
,
-50%
);
width
:
400px
;
width
:
100%
;
max-width
:
400px
;
height
:
450px
;
background
:
#fff
;
border-radius
:
40px
;
...
...
@@ -290,10 +295,12 @@ const goPage = function () {
top
:
-25px
;
left
:
50%
;
transform
:
translateX
(
-50%
);
width
:
276px
;
max-width
:
276px
;
width
:
100%
;
}
.text-box
{
width
:
300px
;
max-width
:
300px
;
width
:
100%
;
padding-top
:
187px
;
margin
:
0
auto
;
text-align
:
center
;
...
...
@@ -312,7 +319,8 @@ const goPage = function () {
.btn
{
text-align
:
center
;
margin
:
0
auto
;
width
:
240px
;
max-width
:
240px
;
width
:
100%
;
line-height
:
88px
;
background
:
#e92d55
;
box-shadow
:
0px
20px
30px
#ffd3dc
;
...
...
@@ -325,4 +333,9 @@ const goPage = function () {
}
}
}
@media
(
max-width
:
767px
)
{
.content
{
padding
:
20px
12px
;
}
}
</
style
>
src/modules/student/contest/components/DiscussItem.vue
浏览文件 @
ac045689
...
...
@@ -68,6 +68,8 @@ const commentVisible = $ref(false)
.discuss-box-user
{
display
:
flex
;
align-items
:
center
;
flex-wrap
:
wrap
;
min-width
:
0
;
}
.discuss-box-user__avatar
{
width
:
40px
;
...
...
@@ -112,6 +114,7 @@ const commentVisible = $ref(false)
font-weight
:
400
;
line-height
:
24px
;
color
:
#333333
;
word-break
:
break-word
;
}
.discuss-box-footer
{
display
:
flex
;
...
...
@@ -154,6 +157,7 @@ const commentVisible = $ref(false)
margin-left
:
10px
;
color
:
#333
;
overflow
:
hidden
;
word-break
:
break-word
;
}
.discuss-comment__username
{
font-size
:
16px
;
...
...
@@ -167,4 +171,7 @@ const commentVisible = $ref(false)
color
:
#999999
;
text-align
:
right
;
}
@media
(
max-width
:
767px
)
{
.discuss-item
{
padding
:
12px
8px
;
}
}
</
style
>
src/modules/student/contest/views/Index.vue
浏览文件 @
ac045689
...
...
@@ -44,4 +44,6 @@ onMounted(() => {
grid-template-columns
:
repeat
(
4
,
1fr
);
gap
:
20px
;
}
@media
(
max-width
:
1024px
)
{
.contest-list
{
grid-template-columns
:
repeat
(
2
,
1fr
);
gap
:
16px
;
}
}
@media
(
max-width
:
767px
)
{
.contest-list
{
grid-template-columns
:
1fr
;
gap
:
12px
;
}
}
</
style
>
src/modules/student/contest/views/Join.vue
浏览文件 @
ac045689
...
...
@@ -113,7 +113,7 @@ function handleCancel() {
<
template
>
<AppCard
title=
"大赛报名"
>
<el-form
ref=
"formRef"
:model=
"form"
:rules=
"rules"
label-width=
"124px"
style=
"
width: 600px; margin: 0 auto
"
>
<el-form
ref=
"formRef"
:model=
"form"
:rules=
"rules"
label-width=
"124px"
style=
"
max-width: 600px; width: 100%; margin: 0 auto; padding: 0 12px;
"
>
<el-form-item
label=
"报名赛项"
>
<el-input
v-model=
"form.name"
disabled
/>
</el-form-item>
...
...
src/modules/student/contest/views/Score.vue
浏览文件 @
ac045689
<
script
setup
lang=
"ts"
>
import
{
getScoreReport
,
getMyContestList
}
from
'../api'
import
{
useMediaQuery
}
from
'@vueuse/core'
const
isMobile
=
useMediaQuery
(
'(max-width: 767px)'
)
let
eventValue
=
$ref
(
''
)
let
eventOption
:
any
=
$ref
([])
...
...
@@ -28,15 +31,23 @@ const selectChange = function () {
<
template
>
<AppCard>
<h1>
2024 年湖北工匠杯成绩查询
</h1>
<div
style=
"display: flex; align-items: center; padding-top: 30px"
>
<div
style=
"display: flex; align-items: center; padding-top: 30px
; flex-wrap: wrap
"
>
<span
style=
"margin-right: 10px"
>
赛项
</span>
<el-select
style=
"width: 320px"
v-model=
"eventValue"
placeholder=
"请选择"
@
change=
"selectChange"
>
<el-select
style=
"width:
100%; max-width:
320px"
v-model=
"eventValue"
placeholder=
"请选择"
@
change=
"selectChange"
>
<el-option
v-for=
"item in eventOption"
:key=
"item.id"
:label=
"item.name"
:value=
"item.id"
/>
</el-select>
</div>
<h3>
考生基础信息
</h3>
<!--
{{
scoreInfo
?.
id
?
[
scoreInfo
]
:
[]
}}
-->
<el-table
:data=
"scoreInfo?.id ? [scoreInfo] : []"
stripe
:header-cell-style=
"
{ background: '#ededed' }">
<div
v-if=
"isMobile && scoreInfo?.id"
class=
"mobile-score-card"
>
<div
class=
"mobile-score-card__row"
><span>
选手姓名
</span><strong>
{{
scoreInfo
.
student
?.
name
||
'-'
}}
</strong></div>
<div
class=
"mobile-score-card__row"
><span>
参赛ID
</span><strong>
{{
scoreInfo
.
id
||
'-'
}}
</strong></div>
<div
class=
"mobile-score-card__row"
><span>
性别
</span><strong>
{{
scoreInfo
.
student
?.
gender
===
1
?
'男'
:
'女'
}}
</strong></div>
<div
class=
"mobile-score-card__row"
><span>
学校
</span><strong>
{{
scoreInfo
.
org
?.
name
||
'-'
}}
</strong></div>
<div
class=
"mobile-score-card__row"
><span>
所在专业
</span><strong>
{{
scoreInfo
.
specialty
?.
name
||
'-'
}}
</strong></div>
<div
class=
"mobile-score-card__row"
><span>
所在年级
</span><strong>
{{
scoreInfo
.
grade
||
'-'
}}
</strong></div>
<div
class=
"mobile-score-card__row"
><span>
联系电话
</span><strong>
{{
scoreInfo
.
student
?.
mobile
||
'-'
}}
</strong></div>
</div>
<el-table
v-else
:data=
"scoreInfo?.id ? [scoreInfo] : []"
stripe
:header-cell-style=
"
{ background: '#ededed' }">
<el-table-column
prop=
"student.name"
label=
"选手姓名"
/>
<el-table-column
prop=
"id"
label=
"参赛ID"
/>
<el-table-column
label=
"性别"
>
...
...
@@ -57,7 +68,21 @@ const selectChange = function () {
{ label: '占比(%)', prop: 'ratio' },
{ label: '实际得分', prop: 'score' }
] -->
<el-table
:data=
"scoreInfo?.score_detail"
stripe
:header-cell-style=
"{ background: '#ededed' }"
>
<div
v-if=
"isMobile"
class=
"mobile-score-list"
>
<div
class=
"mobile-score-card"
v-for=
"(item, index) in scoreInfo?.score_detail || []"
:key=
"index"
>
<div
class=
"mobile-score-card__row"
><span>
桌号
</span><strong>
{{ item.extension?.table_number || '-' }}
</strong></div>
<div
class=
"mobile-score-card__row"
><span>
考试账号
</span><strong>
{{ item.extension?.account || '-' }}
</strong></div>
<div
class=
"mobile-score-card__row"
>
<span>
考试类型
</span>
<strong>
{{ item.extension?.type === '2' ? '商业数据分析师实操考试' : '理论考试' }}
</strong>
</div>
<div
class=
"mobile-score-card__row"
><span>
分值
</span><strong>
{{ item.old_score || '-' }}
</strong></div>
<div
class=
"mobile-score-card__row"
><span>
占比(%)
</span><strong>
{{ item.ratio || '-' }}
</strong></div>
<div
class=
"mobile-score-card__row"
><span>
实际得分
</span><strong>
{{ item.score || '-' }}
</strong></div>
</div>
<el-empty
v-if=
"!scoreInfo?.score_detail?.length"
description=
"暂无成绩数据"
/>
</div>
<el-table
v-else
:data=
"scoreInfo?.score_detail"
stripe
:header-cell-style=
"{ background: '#ededed' }"
>
<el-table-column
prop=
"extension.table_number"
label=
"桌号"
/>
<el-table-column
prop=
"extension.account"
label=
"考试账号"
/>
<el-table-column
prop=
"extension.type"
label=
"考试类型"
>
...
...
@@ -90,4 +115,37 @@ h3 {
padding-top
:
40px
;
line-height
:
30px
;
}
.mobile-score-list
{
display
:
flex
;
flex-direction
:
column
;
gap
:
12px
;
}
.mobile-score-card
{
margin-top
:
12px
;
padding
:
14px
16px
;
background
:
#fff
;
border
:
1px
solid
#ebeef5
;
border-radius
:
10px
;
}
.mobile-score-card__row
{
display
:
flex
;
justify-content
:
space-between
;
gap
:
12px
;
padding
:
6px
0
;
border-bottom
:
1px
solid
#f3f4f6
;
font-size
:
14px
;
}
.mobile-score-card__row
:last-child
{
border-bottom
:
none
;
}
.mobile-score-card__row
span
{
flex-shrink
:
0
;
color
:
#909399
;
}
.mobile-score-card__row
strong
{
min-width
:
0
;
text-align
:
right
;
color
:
#303133
;
word-break
:
break-word
;
}
</
style
>
src/modules/student/lab/components/BindCaptureDialog.vue
浏览文件 @
ac045689
<
script
setup
>
import
{
getExperimentRecord
}
from
'../api'
import
{
useNow
,
useDateFormat
}
from
'@vueuse/core'
import
{
useMediaQuery
}
from
'@vueuse/core'
const
props
=
defineProps
({
experiment
:
Object
,
data
:
Object
})
const
isMobile
=
useMediaQuery
(
'(max-width: 767px)'
)
const
emit
=
defineEmits
([
'update'
,
'update:modelValue'
])
...
...
@@ -50,7 +52,24 @@ function handleRemove(row) {
<
template
>
<el-dialog
append-to-body
title=
"关联截图"
width=
"800px"
@
update:modelValue=
"value => $emit('update:modelValue', value)"
>
<el-table
:data=
"currentPictures"
stripe
>
<div
v-if=
"isMobile"
class=
"capture-card-list"
>
<div
class=
"capture-card"
v-for=
"row in currentPictures"
:key=
"row.url"
>
<div
class=
"capture-card__row"
>
<span>
截图名称
</span>
<el-link
type=
"primary"
:href=
"row.url"
target=
"_blank"
>
{{
row
.
name
}}
</el-link>
</div>
<div
class=
"capture-card__row"
>
<span>
上传时间
</span>
<strong>
{{
row
.
upload_time
||
'-'
}}
</strong>
</div>
<div
class=
"capture-card__actions"
>
<el-button
text
type=
"danger"
@
click=
"handleRemove(row)"
v-if=
"row.is_bind"
>
取消关联
</el-button>
<el-button
text
type=
"primary"
@
click=
"handleBind(row)"
v-else
>
关联截图
</el-button>
</div>
</div>
<el-empty
v-if=
"!currentPictures.length"
description=
"暂无截图"
/>
</div>
<el-table
v-else
:data=
"currentPictures"
stripe
>
<el-table-column
prop=
"name"
>
<template
#
default=
"
{ row }">
<el-link
type=
"primary"
:href=
"row.url"
target=
"_blank"
>
{{
row
.
name
}}
</el-link>
...
...
@@ -71,3 +90,41 @@ function handleRemove(row) {
</
template
>
</el-dialog>
</template>
<
style
scoped
>
.capture-card-list
{
display
:
flex
;
flex-direction
:
column
;
gap
:
12px
;
}
.capture-card
{
padding
:
14px
16px
;
border
:
1px
solid
#ebeef5
;
border-radius
:
10px
;
background
:
#fff
;
}
.capture-card__row
{
display
:
flex
;
justify-content
:
space-between
;
gap
:
12px
;
padding
:
6px
0
;
border-bottom
:
1px
solid
#f3f4f6
;
}
.capture-card__row
:last-of-type
{
border-bottom
:
none
;
}
.capture-card__row
span
{
flex-shrink
:
0
;
color
:
#909399
;
}
.capture-card__row
strong
{
min-width
:
0
;
text-align
:
right
;
word-break
:
break-word
;
}
.capture-card__actions
{
display
:
flex
;
justify-content
:
flex-end
;
padding-top
:
8px
;
}
</
style
>
src/modules/student/lab/components/DiscussItem.vue
浏览文件 @
ac045689
...
...
@@ -68,6 +68,8 @@ const commentVisible = $ref(false)
.discuss-box-user
{
display
:
flex
;
align-items
:
center
;
flex-wrap
:
wrap
;
min-width
:
0
;
}
.discuss-box-user__avatar
{
width
:
40px
;
...
...
@@ -112,6 +114,7 @@ const commentVisible = $ref(false)
font-weight
:
400
;
line-height
:
24px
;
color
:
#333333
;
word-break
:
break-word
;
}
.discuss-box-footer
{
display
:
flex
;
...
...
@@ -154,6 +157,7 @@ const commentVisible = $ref(false)
margin-left
:
10px
;
color
:
#333
;
overflow
:
hidden
;
word-break
:
break-word
;
}
.discuss-comment__username
{
font-size
:
16px
;
...
...
@@ -167,4 +171,7 @@ const commentVisible = $ref(false)
color
:
#999999
;
text-align
:
right
;
}
@media
(
max-width
:
767px
)
{
.discuss-item
{
padding
:
12px
8px
;
}
}
</
style
>
src/modules/student/lab/components/Info.vue
浏览文件 @
ac045689
...
...
@@ -3,7 +3,7 @@ const props = defineProps({ data: Object })
const
teacherText
=
$computed
(()
=>
{
if
(
!
props
.
data
)
return
''
return
props
.
data
.
teachers
.
map
(
item
=>
item
.
name
).
join
(
'、'
)
return
props
.
data
.
teachers
.
map
(
(
item
)
=>
item
.
name
).
join
(
'、'
)
})
</
script
>
...
...
@@ -20,10 +20,10 @@ const teacherText = $computed(() => {
</el-form>
<el-divider
/>
<el-form
label-suffix=
":"
label-position=
"top"
>
<el-form-item
label=
"实验目的"
>
{{
data
.
purpose
}}
</el-form-item>
<el-form-item
label=
"实验要求"
>
{{
data
.
requirements
}}
</el-form-item>
<el-form-item
label=
"实验内容及原理"
>
{{
data
.
content
}}
</el-form-item>
<el-form-item
label=
"实验步骤及过程"
>
{{
data
.
procedure
}}
</el-form-item>
<el-form-item
label=
"实验目的"
v-if=
"data.purpose"
>
{{
data
.
purpose
}}
</el-form-item>
<el-form-item
label=
"实验要求"
v-if=
"data.requirements"
>
{{
data
.
requirements
}}
</el-form-item>
<el-form-item
label=
"实验内容及原理"
v-if=
"data.content"
>
{{
data
.
content
}}
</el-form-item>
<el-form-item
label=
"实验步骤及过程"
v-if=
"data.procedure"
>
{{
data
.
procedure
}}
</el-form-item>
</el-form>
</
template
>
<el-empty
description=
"暂无数据"
v-else
/>
...
...
src/modules/student/lab/components/ReportFilePreview.vue
浏览文件 @
ac045689
...
...
@@ -18,26 +18,26 @@ const detail = $ref(inject<ExperimentRecord>('detail'))
<el-dialog
title=
"查看实验报告"
:close-on-click-modal=
"false"
width=
"600px"
>
<el-form
label-suffix=
":"
label-width=
"110px"
label-position=
"right"
>
<el-row>
<el-col
:
span
=
"12"
>
<el-col
:
xs=
"24"
:sm=
"24"
:md
=
"12"
>
<el-form-item
label=
"实验名称"
>
{{
data
.
name
}}
</el-form-item>
</el-col>
<el-col
:
span
=
"12"
>
<el-col
:
xs=
"24"
:sm=
"24"
:md
=
"12"
>
<el-form-item
label=
"实验课程名称"
>
{{
data
.
course
.
name
}}
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col
:
span
=
"12"
>
<el-col
:
xs=
"24"
:sm=
"24"
:md
=
"12"
>
<el-form-item
label=
"学生姓名"
>
{{
data
.
student
.
name
}}
</el-form-item>
</el-col>
<el-col
:
span
=
"12"
>
<el-col
:
xs=
"24"
:sm=
"24"
:md
=
"12"
>
<el-form-item
label=
"学生学号"
>
{{
data
.
student
.
sno_number
}}
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col
:
span
=
"12"
>
<el-col
:
xs=
"24"
:sm=
"24"
:md
=
"12"
>
<el-form-item
label=
"所属专业"
>
{{
data
.
student
.
specialty
.
name
}}
</el-form-item>
</el-col>
<el-col
:
span
=
"12"
>
<el-col
:
xs=
"24"
:sm=
"24"
:md
=
"12"
>
<el-form-item
label=
"所属班级"
>
{{
classText
}}
</el-form-item>
</el-col>
</el-row>
...
...
src/modules/student/lab/components/ReportPreview.vue
浏览文件 @
ac045689
<
script
setup
>
import
{
getExperimentReport
}
from
'../api'
import
{
useMediaQuery
}
from
'@vueuse/core'
const
props
=
defineProps
({
id
:
String
,
makePdf
:
Boolean
})
const
emit
=
defineEmits
([
'ready'
])
const
isMobile
=
useMediaQuery
(
'(max-width: 767px)'
)
let
experiment
=
$ref
()
let
report
=
$ref
()
...
...
@@ -62,42 +64,42 @@ function handleClose() {
<h1
class=
"report-title"
>
实验报告
</h1>
<el-form
label-suffix=
":"
hide-required-asterisk
v-if=
"experiment"
>
<el-row>
<el-col
:
span
=
"12"
>
<el-col
:
xs=
"24"
:sm=
"24"
:md
=
"12"
>
<el-form-item
label=
"课程名称"
>
{{
experiment
.
course
.
name
}}
</el-form-item>
</el-col>
<el-col
:
span
=
"12"
>
<el-col
:
xs=
"24"
:sm=
"24"
:md
=
"12"
>
<el-form-item
label=
"实验名称"
>
{{
experiment
.
name
}}
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col
:
span
=
"12"
>
<el-col
:
xs=
"24"
:sm=
"24"
:md
=
"12"
>
<el-form-item
label=
"实验地点"
>
{{
report
.
experiment_address
}}
</el-form-item>
</el-col>
<el-col
:
span
=
"12"
>
<el-col
:
xs=
"24"
:sm=
"24"
:md
=
"12"
>
<el-form-item
label=
"实验日期"
>
{{
report
.
experiment_date
}}
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col
:
span
=
"12"
>
<el-col
:
xs=
"24"
:sm=
"24"
:md
=
"12"
>
<el-form-item
label=
"实验类型"
>
{{
experiment
.
type_level
}}
</el-form-item>
</el-col>
<el-col
:
span
=
"12"
>
<el-col
:
xs=
"24"
:sm=
"24"
:md
=
"12"
>
<el-form-item
label=
"指导教师"
>
{{
teacherText
}}
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col
:
span
=
"12"
>
<el-col
:
xs=
"24"
:sm=
"24"
:md
=
"12"
>
<el-form-item
label=
"专业"
>
{{
experiment
.
student
.
specialty
.
name
}}
</el-form-item>
</el-col>
<el-col
:
span
=
"12"
>
<el-col
:
xs=
"24"
:sm=
"24"
:md
=
"12"
>
<el-form-item
label=
"班级"
>
{{
classText
}}
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col
:
span
=
"12"
>
<el-col
:
xs=
"24"
:sm=
"24"
:md
=
"12"
>
<el-form-item
label=
"姓名"
>
{{
experiment
.
student
.
name
}}
</el-form-item>
</el-col>
<el-col
:
span
=
"12"
>
<el-col
:
xs=
"24"
:sm=
"24"
:md
=
"12"
>
<el-form-item
label=
"学号"
>
{{
experiment
.
student
.
sno_number
}}
</el-form-item>
</el-col>
</el-row>
...
...
@@ -121,7 +123,23 @@ function handleClose() {
</
template
>
<!-- 附件 -->
<
template
v-if=
"item.type === 2"
>
<div
v-if=
"isMobile && !makePdf"
class=
"report-file-card-list"
>
<div
class=
"report-file-card"
v-for=
"(file, fileIndex) in item.files"
:key=
"file.id || file.url || fileIndex"
>
<div
class=
"report-file-card__row"
><span>
序号
</span><strong>
{{
fileIndex
+
1
}}
</strong></div>
<div
class=
"report-file-card__row"
>
<span>
附件类型
</span>
<strong>
{{
file
.
type
===
'1'
?
'本地上传'
:
'实验截图'
}}
</strong>
</div>
<div
class=
"report-file-card__row"
><span>
附件名称
</span><strong>
{{
file
.
name
||
'-'
}}
</strong></div>
<div
class=
"report-file-card__row"
><span>
更新时间
</span><strong>
{{
file
.
updated_time
||
'-'
}}
</strong></div>
<div
class=
"report-file-card__actions"
>
<el-button
text
type=
"primary"
><a
:href=
"file.url"
target=
"_blank"
>
查看
</a></el-button>
</div>
</div>
<el-empty
v-if=
"!item.files?.length"
description=
"暂无附件"
/>
</div>
<el-table
v-else
:data=
"item.files"
stripe
:header-cell-style=
"
{ background: '#ededed' }"
...
...
@@ -221,4 +239,60 @@ function handleClose() {
border
:
1px
solid
var
(
--
main-color
);
border-radius
:
6px
;
}
.report-file-card-list
{
display
:
flex
;
flex-direction
:
column
;
gap
:
12px
;
margin-top
:
20px
;
}
.report-file-card
{
padding
:
14px
16px
;
border
:
1px
solid
#ebeef5
;
border-radius
:
10px
;
background
:
#fff
;
}
.report-file-card__row
{
display
:
flex
;
justify-content
:
space-between
;
gap
:
12px
;
padding
:
6px
0
;
border-bottom
:
1px
solid
#f3f4f6
;
}
.report-file-card__row
:last-of-type
{
border-bottom
:
none
;
}
.report-file-card__row
span
{
flex-shrink
:
0
;
color
:
#909399
;
}
.report-file-card__row
strong
{
min-width
:
0
;
text-align
:
right
;
word-break
:
break-word
;
}
.report-file-card__actions
{
display
:
flex
;
justify-content
:
flex-end
;
padding-top
:
8px
;
}
@media
(
max-width
:
767px
)
{
.report-preview__inner
{
padding
:
20px
12px
;
}
.report-title
{
padding
:
12px
0
20px
;
}
.report-form-item
{
margin-bottom
:
24px
;
.form-hd
{
flex-direction
:
column
;
gap
:
6px
;
align-items
:
flex-start
;
}
}
.report-score
{
margin
:
24px
0
;
font-size
:
20px
;
}
}
</
style
>
src/modules/student/lab/components/Result.vue
浏览文件 @
ac045689
...
...
@@ -203,4 +203,38 @@ h3 {
}
}
}
@media
(
max-width
:
767px
)
{
.picture-list
{
li
{
height
:
auto
;
margin
:
16px
0
;
img
{
display
:
block
;
aspect-ratio
:
16
/
9
;
}
p
{
position
:
static
;
padding
:
8px
10px
0
;
color
:
#666
;
text-align
:
left
;
background
:
none
;
}
}
.cover
{
display
:
block
;
position
:
static
;
background
:
none
;
}
.cover-inner
{
flex-direction
:
row
;
justify-content
:
flex-start
;
gap
:
10px
;
padding
:
10px
;
.el-button
{
width
:
auto
;
margin
:
0
;
}
}
}
}
</
style
>
src/modules/student/lab/components/ResultScoreDialog.vue
浏览文件 @
ac045689
<
script
setup
lang=
"ts"
>
import
{
getExperimentScore
}
from
'../api'
import
{
useMediaQuery
}
from
'@vueuse/core'
import
{
useAppConfig
}
from
'@/composables/useAppConfig'
const
appConfig
=
useAppConfig
()
const
isMobile
=
useMediaQuery
(
'(max-width: 767px)'
)
const
ResultScoreViewAutoDialog
=
defineAsyncComponent
(()
=>
import
(
'./ResultScoreViewAutoDialog.vue'
))
...
...
@@ -95,30 +97,30 @@ function handleViewAuto(row: any) {
<el-form-item
label=
"实验名称"
>
{{
experiment
.
name
}}
</el-form-item>
<el-form-item
label=
"实验课程名称"
>
{{
experiment
.
course
.
name
}}
</el-form-item>
<el-row>
<el-col
:
span
=
"12"
>
<el-col
:
xs=
"24"
:sm=
"24"
:md
=
"12"
>
<el-form-item
label=
"学生姓名"
>
{{
experiment
.
student
.
name
}}
</el-form-item>
</el-col>
<el-col
:
span
=
"12"
>
<el-col
:
xs=
"24"
:sm=
"24"
:md
=
"12"
>
<el-form-item
label=
"学生学号"
>
{{
experiment
.
student
.
sno_number
}}
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col
:
span
=
"12"
>
<el-col
:
xs=
"24"
:sm=
"24"
:md
=
"12"
>
<el-form-item
label=
"所属专业"
>
{{
experiment
.
student
.
specialty
.
name
}}
</el-form-item>
</el-col>
<el-col
:
span
=
"12"
>
<el-col
:
xs=
"24"
:sm=
"24"
:md
=
"12"
>
<el-form-item
label=
"所属班级"
>
{{
classText
}}
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col
:
span
=
"12"
>
<el-col
:
xs=
"24"
:sm=
"24"
:md
=
"12"
>
<el-form-item
label=
"评分教师"
>
{{
detail
.
checker_sso_user
.
real_name
||
detail
.
checker_sso_user
.
nickname
||
detail
.
checker_sso_user
.
username
}}
</el-form-item>
</el-col>
<el-col
:
span
=
"12"
>
<el-col
:
xs=
"24"
:sm=
"24"
:md
=
"12"
>
<el-form-item
label=
"评分时间"
>
{{
detail
.
check_time
}}
</el-form-item>
</el-col>
</el-row>
...
...
@@ -127,11 +129,34 @@ function handleViewAuto(row: any) {
<p>
实验得分
</p>
<p
class=
"t1"
>
{{
detail
.
score
}}
</p>
</div>
<div
v-if=
"detail.is_show === '1' && isMobile"
class=
"score-detail-card-list"
>
<div
class=
"score-detail-card"
v-for=
"row in detail.score_details"
:key=
"`$
{row.type}-${row.name}`">
<div
class=
"score-detail-card__row"
><span>
实验成绩组成项
</span><strong>
{{
row
.
name
||
'-'
}}
</strong></div>
<div
class=
"score-detail-card__row"
><span>
权重
</span><strong>
{{
row
.
percent
}}
%
</strong></div>
<div
class=
"score-detail-card__row"
>
<span>
得分
</span>
<strong>
{{
row
.
commit_score
}}
<template
v-if=
"(row.type === 1 && experiment.report_upload_way === 2) || row.type === 8"
>
<el-button
text
type=
"primary"
>
<a
:href=
"getOperationUrl(row.type)"
target=
"_blank"
>
查看明细
</a>
</el-button>
</
template
>
</strong>
</div>
<div
class=
"score-detail-card__actions"
>
<el-button
type=
"primary"
link
@
click=
"handleViewAuto(row)"
v-if=
"[6, 7, 9, 11, 12].includes(row.type)"
>
查看标准答案
</el-button>
<el-button
link
type=
"primary"
>
<a
:href=
"getOperationUrl(row.type)"
target=
"_blank"
>
查看我的答案
</a>
</el-button>
</div>
</div>
</div>
<el-table
:data=
"detail.score_details"
stripe
:header-cell-style=
"{ background: '#ededed' }"
v-if="detail.is_show === '1'">
v-
else-
if=
"detail.is_show === '1'"
>
<el-table-column
label=
"实验成绩组成项"
prop=
"name"
align=
"center"
></el-table-column>
<el-table-column
label=
"权重"
prop=
"percent"
align=
"center"
>
<
template
#
default=
"{ row }"
>
{{
row
.
percent
}}
%
</
template
>
...
...
@@ -188,6 +213,7 @@ function handleViewAuto(row: any) {
display
:
flex
;
align-items
:
center
;
justify-content
:
center
;
gap
:
24px
;
}
.form-result__score
{
padding
:
40px
;
...
...
@@ -198,4 +224,51 @@ function handleViewAuto(row: any) {
color
:
var
(
--
main-color
);
}
}
.score-detail-card-list
{
flex
:
1
;
display
:
flex
;
flex-direction
:
column
;
gap
:
12px
;
}
.score-detail-card
{
padding
:
14px
16px
;
background
:
#fff
;
border
:
1px
solid
#ebeef5
;
border-radius
:
10px
;
}
.score-detail-card__row
{
display
:
flex
;
justify-content
:
space-between
;
gap
:
12px
;
padding
:
6px
0
;
border-bottom
:
1px
solid
#f3f4f6
;
}
.score-detail-card__row
:last-of-type
{
border-bottom
:
none
;
}
.score-detail-card__row
span
{
flex-shrink
:
0
;
color
:
#909399
;
}
.score-detail-card__row
strong
{
min-width
:
0
;
text-align
:
right
;
word-break
:
break-word
;
}
.score-detail-card__actions
{
display
:
flex
;
justify-content
:
flex-end
;
flex-wrap
:
wrap
;
gap
:
8px
;
padding-top
:
8px
;
}
@media
(
max-width
:
767px
)
{
.form-result
{
flex-direction
:
column
;
align-items
:
stretch
;
}
.form-result__score
{
padding
:
20px
;
}
}
</
style
>
src/modules/student/lab/views/Competition.vue
浏览文件 @
ac045689
...
...
@@ -185,11 +185,16 @@ useEventListener(window, 'message', async (event) => {
width
:
100%
;
margin-top
:
20px
;
display
:
flex
;
align-items
:
stretch
;
min-height
:
0
;
}
.iframe
{
display
:
block
;
flex
:
1
;
width
:
100%
;
height
:
100%
;
min-height
:
100%
;
}
.welcome-box
{
...
...
@@ -198,7 +203,8 @@ useEventListener(window, 'message', async (event) => {
.entry-btn
{
display
:
block
;
width
:
300px
;
max-width
:
300px
;
width
:
100%
;
margin
:
100px
auto
;
}
}
...
...
@@ -214,6 +220,7 @@ useEventListener(window, 'message', async (event) => {
.welcome-box-header-content
{
width
:
100%
;
max-width
:
1000px
;
padding
:
0
16px
;
margin
:
0
auto
;
color
:
#fff
;
padding
:
40px
0
;
...
...
@@ -253,4 +260,22 @@ useEventListener(window, 'message', async (event) => {
text-shadow
:
0px
2px
4px
rgba
(
0
,
0
,
0
,
0
.5
);
}
}
@media
(
max-width
:
767px
)
{
.iframe-box
{
margin-top
:
12px
;
display
:
block
;
height
:
74vh
;
min-height
:
74vh
;
overflow
:
hidden
;
}
.iframe
{
height
:
74vh
;
min-height
:
74vh
;
}
.welcome-box-header
{
padding
:
40px
16px
;
}
}
</
style
>
src/modules/student/lab/views/Index.vue
浏览文件 @
ac045689
...
...
@@ -2,6 +2,7 @@
import
type
{
CourseType
,
ExperimentInfo
,
ExperimentRecord
}
from
'../types'
import
{
HomeFilled
}
from
'@element-plus/icons-vue'
import
{
ElMessageBox
,
ElMessage
}
from
'element-plus'
import
{
useMediaQuery
}
from
'@vueuse/core'
import
DragPanel
from
'@/components/DragPanel.vue'
import
{
useGetCourseList
}
from
'../composables/useGetCourseList'
import
{
upload
}
from
'@/utils/upload'
...
...
@@ -32,6 +33,7 @@ const Exam = defineAsyncComponent(() => import('../components/Exam.vue'))
const
Example
=
defineAsyncComponent
(()
=>
import
(
'../components/Example.vue'
))
const
route
=
useRoute
()
const
isMobile
=
useMediaQuery
(
'(max-width: 767px)'
)
// 左侧
const
form
=
reactive
<
{
course_id
:
string
;
experiment_id
:
string
}
>
({
...
...
@@ -103,6 +105,19 @@ watchEffect(() => {
})
const
tabActive
=
ref
(
'info'
)
const
mobilePanelExpanded
=
ref
(
false
)
const
tabLabelMap
:
Record
<
string
,
string
>
=
{
info
:
'实验信息'
,
example
:
'实验案例'
,
case
:
'案例原文'
,
book
:
'实训指导'
,
video
:
'操作视频'
,
exam
:
'理论试题'
,
qa
:
'实操试题'
,
result
:
'过程与结果'
,
discuss
:
'讨论交流'
,
}
const
currentTabLabel
=
computed
(()
=>
tabLabelMap
[
tabActive
.
value
]
||
'实验面板'
)
const
examURL
=
ref
(
''
)
// 右侧
...
...
@@ -257,67 +272,75 @@ function handleGoToFunction(path: string) {
<
template
>
<DragPanel
@
resize=
"handleResize"
:isLeftShow=
"experimentInfo?.exam_status === 1"
>
<template
#
left
>
<div
class=
"lab-left"
>
<el-form
:model=
"form"
label-suffix=
":"
hide-required-asterisk
>
<el-form-item
label=
"请选择课程"
>
<el-select
:model-value=
"form.course_id"
@
change=
"handleCourseChange"
style=
"width: 100%"
>
<el-option
v-for=
"item in courses"
:key=
"item.id"
:label=
"item.name"
:value=
"item.id"
></el-option>
</el-select>
</el-form-item>
<el-form-item
label=
"请选择实验"
prop=
"experiment_id"
>
<el-select
:model-value=
"form.experiment_id"
@
change=
"handleExperimentChange"
style=
"width: 100%"
>
<el-option
v-for=
"item in experimentList"
:key=
"item.id"
:label=
"item.name"
:value=
"item.id"
></el-option>
</el-select>
</el-form-item>
</el-form>
<el-tabs
type=
"border-card"
v-model=
"tabActive"
v-if=
"experimentInfo"
>
<el-tab-pane
label=
"实验信息"
name=
"info"
>
<Info
:data=
"experimentInfo"
></Info>
</el-tab-pane>
<el-tab-pane
label=
"实验案例"
name=
"example"
v-if=
"!empty.includes('example')"
>
<Example
:experiment_id=
"form.experiment_id"
@
empty=
"handleEmpty('example')"
@
goToFunction=
"handleGoToFunction"
></Example>
</el-tab-pane>
<el-tab-pane
label=
"案例原文"
name=
"case"
v-if=
"!empty.includes('case')"
>
<Case
:course_id=
"form.course_id"
:experiment_id=
"form.experiment_id"
:key=
"resizeKey"
@
empty=
"handleEmpty('case')"
></Case>
</el-tab-pane>
<el-tab-pane
label=
"实训指导"
name=
"book"
v-if=
"!empty.includes('book')"
>
<Book
:course_id=
"form.course_id"
:experiment_id=
"form.experiment_id"
:key=
"resizeKey"
@
empty=
"handleEmpty('book')"
></Book>
</el-tab-pane>
<el-tab-pane
label=
"操作视频"
name=
"video"
v-if=
"!empty.includes('video')"
>
<Video
:course_id=
"form.course_id"
:experiment_id=
"form.experiment_id"
@
empty=
"handleEmpty('video')"
></Video>
</el-tab-pane>
<el-tab-pane
label=
"理论试题"
name=
"exam"
v-if=
"!empty.includes('exam')"
>
<Exam
v-model=
"examURL"
:experiment_id=
"form.experiment_id"
:examStatus=
"experimentInfo?.exam_status"
:showIframe=
"experimentInfo?.exam_status === 0"
@
empty=
"handleEmpty('exam')"
></Exam>
</el-tab-pane>
<el-tab-pane
label=
"实操试题"
name=
"qa"
v-if=
"!empty.includes('qa')"
>
<Question
:experiment_id=
"form.experiment_id"
@
empty=
"handleEmpty('qa')"
></Question>
</el-tab-pane>
<el-tab-pane
label=
"过程与结果"
name=
"result"
lazy
>
<Result
:data=
"experimentInfo"
@
update=
"fetchExperimentRecord"
></Result>
</el-tab-pane>
<el-tab-pane
label=
"讨论交流"
name=
"discuss"
lazy
>
<Discuss
:experiment_id=
"form.experiment_id"
></Discuss>
</el-tab-pane>
</el-tabs>
<div
class=
"lab-left"
>
<div
class=
"lab-left-body"
>
<el-form
:model=
"form"
label-suffix=
":"
hide-required-asterisk
>
<el-form-item
label=
"请选择课程"
>
<el-select
:model-value=
"form.course_id"
@
change=
"handleCourseChange"
style=
"width: 100%"
>
<el-option
v-for=
"item in courses"
:key=
"item.id"
:label=
"item.name"
:value=
"item.id"
></el-option>
</el-select>
</el-form-item>
<el-form-item
label=
"请选择实验"
prop=
"experiment_id"
>
<el-select
:model-value=
"form.experiment_id"
@
change=
"handleExperimentChange"
style=
"width: 100%"
>
<el-option
v-for=
"item in experimentList"
:key=
"item.id"
:label=
"item.name"
:value=
"item.id"
></el-option>
</el-select>
</el-form-item>
</el-form>
<div
class=
"mobile-panel-toggle"
v-if=
"isMobile && experimentInfo"
>
<span>
当前面板:
{{
currentTabLabel
}}
</span>
<el-button
text
type=
"primary"
@
click=
"mobilePanelExpanded = !mobilePanelExpanded"
>
{{
mobilePanelExpanded
?
'收起'
:
'展开'
}}
</el-button>
</div>
<el-tabs
type=
"border-card"
v-model=
"tabActive"
v-if=
"experimentInfo"
v-show=
"!isMobile || mobilePanelExpanded"
>
<el-tab-pane
label=
"实验信息"
name=
"info"
>
<Info
:data=
"experimentInfo"
></Info>
</el-tab-pane>
<el-tab-pane
label=
"实验案例"
name=
"example"
v-if=
"!empty.includes('example')"
>
<Example
:experiment_id=
"form.experiment_id"
@
empty=
"handleEmpty('example')"
@
goToFunction=
"handleGoToFunction"
></Example>
</el-tab-pane>
<el-tab-pane
label=
"案例原文"
name=
"case"
v-if=
"!empty.includes('case')"
>
<Case
:course_id=
"form.course_id"
:experiment_id=
"form.experiment_id"
:key=
"resizeKey"
@
empty=
"handleEmpty('case')"
></Case>
</el-tab-pane>
<el-tab-pane
label=
"实训指导"
name=
"book"
v-if=
"!empty.includes('book')"
>
<Book
:course_id=
"form.course_id"
:experiment_id=
"form.experiment_id"
:key=
"resizeKey"
@
empty=
"handleEmpty('book')"
></Book>
</el-tab-pane>
<el-tab-pane
label=
"操作视频"
name=
"video"
v-if=
"!empty.includes('video')"
>
<Video
:course_id=
"form.course_id"
:experiment_id=
"form.experiment_id"
@
empty=
"handleEmpty('video')"
></Video>
</el-tab-pane>
<el-tab-pane
label=
"理论试题"
name=
"exam"
v-if=
"!empty.includes('exam')"
>
<Exam
v-model=
"examURL"
:experiment_id=
"form.experiment_id"
:examStatus=
"experimentInfo?.exam_status"
:showIframe=
"experimentInfo?.exam_status === 0"
@
empty=
"handleEmpty('exam')"
></Exam>
</el-tab-pane>
<el-tab-pane
label=
"实操试题"
name=
"qa"
v-if=
"!empty.includes('qa')"
>
<Question
:experiment_id=
"form.experiment_id"
@
empty=
"handleEmpty('qa')"
></Question>
</el-tab-pane>
<el-tab-pane
label=
"过程与结果"
name=
"result"
lazy
>
<Result
:data=
"experimentInfo"
@
update=
"fetchExperimentRecord"
></Result>
</el-tab-pane>
<el-tab-pane
label=
"讨论交流"
name=
"discuss"
lazy
>
<Discuss
:experiment_id=
"form.experiment_id"
></Discuss>
</el-tab-pane>
</el-tabs>
</div>
</div>
</
template
>
<
template
#
right
>
...
...
@@ -326,14 +349,14 @@ function handleGoToFunction(path: string) {
<el-button
type=
"primary"
@
click=
"tabActive = 'exam'"
>
理论试题
</el-button>
<el-button
type=
"primary"
@
click=
"tabActive = 'qa'"
>
实操环境
</el-button>
</div>
<el-row
justify=
"space-between"
v-else
>
<div>
<el-row
justify=
"space-between"
v-else
class=
"lab-toolbar"
>
<div
class=
"lab-toolbar-group"
>
<el-button
type=
"primary"
:icon=
"HomeFilled"
:disabled=
"submitted"
@
click=
"handleBackHome"
>
返回首页
</el-button
>
<el-button
type=
"primary"
:disabled=
"disabled"
@
click=
"handleSubmit"
>
提交实验
</el-button>
</div>
<div>
<div
class=
"lab-toolbar-group"
>
<el-button
type=
"primary"
v-if=
"appConfig.system == 'default'"
>
<router-link
to=
"/ai"
target=
"_blank"
>
AI数据分析
</router-link>
</el-button>
...
...
@@ -392,7 +415,7 @@ function handleGoToFunction(path: string) {
class=
"iframe"
ref=
"iframeRef"
></iframe>
<div
style=
"padding: 10px; background-color: #fff; width: 300px"
style=
"padding: 10px; background-color: #fff; width:
100%; max-width:
300px"
v-if=
"experimentInfo?.exam_status === 1 && tabActive === 'qa'"
>
<Question
:experiment_id=
"form.experiment_id"
:exam_status=
"experimentInfo?.exam_status"
></Question>
</div>
...
...
@@ -459,15 +482,119 @@ function handleGoToFunction(path: string) {
overflow-y
:
auto
;
}
}
.lab-left-body
{
display
:
flex
;
flex
:
1
;
min-height
:
0
;
flex-direction
:
column
;
}
.mobile-panel-toggle
{
display
:
flex
;
align-items
:
center
;
justify-content
:
space-between
;
gap
:
12px
;
margin-bottom
:
12px
;
padding
:
10px
12px
;
background
:
#fff
;
border-radius
:
8px
;
span
{
flex
:
1
;
min-width
:
0
;
color
:
#606266
;
font-size
:
14px
;
line-height
:
20px
;
}
}
.lab-toolbar
{
gap
:
12px
;
}
.lab-toolbar-group
{
display
:
flex
;
flex-wrap
:
wrap
;
gap
:
12px
;
}
.exam-status
{
display
:
flex
;
flex-wrap
:
wrap
;
gap
:
12px
;
}
.lab-box
{
position
:
relative
;
flex
:
1
;
width
:
100%
;
margin-top
:
20px
;
display
:
flex
;
align-items
:
stretch
;
min-height
:
0
;
}
.iframe
{
display
:
block
;
flex
:
1
;
width
:
100%
;
height
:
100%
;
min-height
:
100%
;
}
@media
(
max-width
:
767px
)
{
.lab-left
{
:deep
(
.el-form-item
)
{
margin-bottom
:
12px
;
}
:deep
(
.el-tabs__header
)
{
overflow-x
:
auto
;
overflow-y
:
hidden
;
scrollbar-width
:
none
;
-ms-overflow-style
:
none
;
}
:deep
(
.el-tabs__header
::-webkit-scrollbar
)
{
display
:
none
;
}
:deep
(
.el-tabs__nav-wrap
)
{
overflow
:
visible
;
}
:deep
(
.el-tabs__nav
)
{
flex-wrap
:
nowrap
;
}
:deep
(
.el-tabs__item
)
{
flex-shrink
:
0
;
min-width
:
max-content
;
padding
:
0
12px
!
important
;
font-size
:
14px
;
}
:deep
(
.el-tabs__content
)
{
height
:
auto
;
max-height
:
52vh
;
padding
:
12px
;
overflow-y
:
auto
;
}
}
.lab-toolbar
{
flex-direction
:
column
;
align-items
:
stretch
;
}
.lab-toolbar-group
,
.exam-status
{
width
:
100%
;
}
.lab-toolbar-group
:deep
(
.el-button
),
.exam-status
:deep
(
.el-button
)
{
flex
:
1
1
calc
(
50%
-
6px
);
min-width
:
calc
(
50%
-
6px
);
margin-left
:
0
;
}
.lab-toolbar-group
:deep
(
.el-button
>
span
),
.exam-status
:deep
(
.el-button
>
span
)
{
justify-content
:
center
;
}
.lab-box
{
margin-top
:
12px
;
display
:
block
;
height
:
74vh
;
min-height
:
74vh
;
overflow
:
hidden
;
}
.iframe
{
height
:
74vh
;
min-height
:
74vh
;
}
}
</
style
>
src/modules/student/lab/views/Report.vue
浏览文件 @
ac045689
...
...
@@ -3,6 +3,7 @@ import AppEditor from '@/components/base/AppEditor.vue'
import
{
useMapStore
}
from
'@/stores/map'
import
{
ElMessage
,
ElMessageBox
}
from
'element-plus'
import
{
useFileDialog
}
from
'@vueuse/core'
import
{
useMediaQuery
}
from
'@vueuse/core'
import
{
upload
}
from
'@/utils/upload'
import
{
useNow
,
useDateFormat
}
from
'@vueuse/core'
import
{
...
...
@@ -15,6 +16,7 @@ import {
import
BindCaptureDialog
from
'../components/BindCaptureDialog.vue'
const
props
=
defineProps
({
id
:
String
})
const
isMobile
=
useMediaQuery
(
'(max-width: 767px)'
)
const
experimentAddress
=
useMapStore
().
getMapValuesByKey
(
'experiment_address'
)
...
...
@@ -163,48 +165,48 @@ function handleUpdateBindCapture(files) {
hide-required-asterisk
v-if=
"experiment"
>
<el-row>
<el-col
:
span
=
"12"
>
<el-col
:
xs=
"24"
:sm=
"24"
:md
=
"12"
>
<el-form-item
label=
"课程名称"
>
{{
experiment
.
course
.
name
}}
</el-form-item>
</el-col>
<el-col
:
span
=
"12"
>
<el-col
:
xs=
"24"
:sm=
"24"
:md
=
"12"
>
<el-form-item
label=
"实验名称"
>
{{
experiment
.
name
}}
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col
:
span
=
"12"
>
<el-col
:
xs=
"24"
:sm=
"24"
:md
=
"12"
>
<el-form-item
label=
"实验地点"
>
<el-select
v-model=
"form.experiment_address"
>
<el-option
v-for=
"item in experimentAddress"
v-bind=
"item"
:key=
"item.id"
></el-option>
</el-select>
</el-form-item>
</el-col>
<el-col
:
span
=
"12"
>
<el-col
:
xs=
"24"
:sm=
"24"
:md
=
"12"
>
<el-form-item
label=
"实验日期"
>
<el-date-picker
v-model=
"form.experiment_date"
value-format=
"YYYY-MM-DD"
></el-date-picker>
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col
:
span
=
"12"
>
<el-col
:
xs=
"24"
:sm=
"24"
:md
=
"12"
>
<el-form-item
label=
"实验类型"
>
{{
experiment
.
type_level
}}
</el-form-item>
</el-col>
<el-col
:
span
=
"12"
>
<el-col
:
xs=
"24"
:sm=
"24"
:md
=
"12"
>
<el-form-item
label=
"指导教师"
>
{{
teacherText
}}
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col
:
span
=
"12"
>
<el-col
:
xs=
"24"
:sm=
"24"
:md
=
"12"
>
<el-form-item
label=
"专业"
>
{{
experiment
.
student
.
specialty
.
name
}}
</el-form-item>
</el-col>
<el-col
:
span
=
"12"
>
<el-col
:
xs=
"24"
:sm=
"24"
:md
=
"12"
>
<el-form-item
label=
"班级"
>
{{
classText
}}
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col
:
span
=
"12"
>
<el-col
:
xs=
"24"
:sm=
"24"
:md
=
"12"
>
<el-form-item
label=
"姓名"
>
{{
experiment
.
student
.
name
}}
</el-form-item>
</el-col>
<el-col
:
span
=
"12"
>
<el-col
:
xs=
"24"
:sm=
"24"
:md
=
"12"
>
<el-form-item
label=
"学号"
>
{{
experiment
.
student
.
sno_number
}}
</el-form-item>
</el-col>
</el-row>
...
...
@@ -221,7 +223,23 @@ function handleUpdateBindCapture(files) {
<el-button
type=
"primary"
@
click=
"handleBindCapture(item)"
v-if=
"item.categories.includes(1)"
>
关联截图
</el-button
>
<el-table
:data=
"item.files"
stripe
:header-cell-style=
"
{ background: '#ededed' }" style="margin-top: 20px">
<div
v-if=
"isMobile"
class=
"report-file-card-list"
>
<div
class=
"report-file-card"
v-for=
"(file, fileIndex) in item.files || []"
:key=
"file.id || file.url || fileIndex"
>
<div
class=
"report-file-card__row"
><span>
序号
</span><strong>
{{
fileIndex
+
1
}}
</strong></div>
<div
class=
"report-file-card__row"
>
<span>
附件类型
</span>
<strong>
{{
file
.
type
===
'1'
?
'本地上传'
:
'实验截图'
}}
</strong>
</div>
<div
class=
"report-file-card__row"
><span>
附件名称
</span><strong>
{{
file
.
name
||
'-'
}}
</strong></div>
<div
class=
"report-file-card__row"
><span>
更新时间
</span><strong>
{{
file
.
updated_time
||
'-'
}}
</strong></div>
<div
class=
"report-file-card__actions"
>
<el-button
text
type=
"primary"
><a
:href=
"file.url"
target=
"_blank"
>
查看
</a></el-button>
<el-button
text
type=
"danger"
@
click=
"handleRemoveFile(item, fileIndex)"
>
删除
</el-button>
</div>
</div>
<el-empty
v-if=
"!item.files?.length"
description=
"暂无附件"
/>
</div>
<el-table
v-else
:data=
"item.files"
stripe
:header-cell-style=
"
{ background: '#ededed' }" style="margin-top: 20px">
<el-table-column
label=
"序号"
type=
"index"
width=
"80"
align=
"center"
></el-table-column>
<el-table-column
label=
"附件类型"
prop=
"type"
align=
"center"
>
<template
#
default=
"
{ row }">
...
...
@@ -274,4 +292,47 @@ function handleUpdateBindCapture(files) {
justify-content
:
flex-start
;
}
}
.report-file-card-list
{
display
:
flex
;
flex-direction
:
column
;
gap
:
12px
;
margin-top
:
20px
;
}
.report-file-card
{
padding
:
14px
16px
;
border
:
1px
solid
#ebeef5
;
border-radius
:
10px
;
background
:
#fff
;
}
.report-file-card__row
{
display
:
flex
;
justify-content
:
space-between
;
gap
:
12px
;
padding
:
6px
0
;
border-bottom
:
1px
solid
#f3f4f6
;
}
.report-file-card__row
:last-of-type
{
border-bottom
:
none
;
}
.report-file-card__row
span
{
flex-shrink
:
0
;
color
:
#909399
;
}
.report-file-card__row
strong
{
min-width
:
0
;
text-align
:
right
;
word-break
:
break-word
;
}
.report-file-card__actions
{
display
:
flex
;
justify-content
:
flex-end
;
flex-wrap
:
wrap
;
gap
:
8px
;
padding-top
:
8px
;
}
@media
(
max-width
:
767px
)
{
.report-title
{
padding
:
12px
0
20px
;
}
}
</
style
>
编写
预览
Markdown
格式
0%
重试
或
添加新文件
添加附件
取消
您添加了
0
人
到此讨论。请谨慎行事。
请先完成此评论的编辑!
取消
请
注册
或者
登录
后发表评论