Skip to content
项目
群组
代码片段
帮助
当前项目
正在载入...
登录 / 注册
切换导航面板
C
center-resource
项目
项目
详情
活动
周期分析
仓库
仓库
文件
提交
分支
标签
贡献者
图表
比较
统计图
议题
0
议题
0
列表
看板
标记
里程碑
合并请求
0
合并请求
0
CI / CD
CI / CD
流水线
作业
日程
统计图
Wiki
Wiki
代码片段
代码片段
成员
成员
折叠边栏
关闭边栏
活动
图像
聊天
创建新问题
作业
提交
问题看板
Open sidebar
EzijingWeb
center-resource
Commits
48826ec2
提交
48826ec2
authored
3月 20, 2026
作者:
王鹏飞
浏览文件
操作
浏览文件
下载
电子邮件补丁
差异文件
feat: 新增案例授权
上级
151ff894
显示空白字符变更
内嵌
并排
正在显示
9 个修改的文件
包含
1027 行增加
和
1 行删除
+1027
-1
Aside.vue
src/components/layout/Aside.vue
+1
-1
api.ts
src/modules/case-auth/api.ts
+76
-0
useCaseAuthorization.ts
src/modules/case-auth/composables/useCaseAuthorization.ts
+499
-0
useCaseLibrary.ts
src/modules/case-auth/composables/useCaseLibrary.ts
+20
-0
useGetCourseList.ts
src/modules/case-auth/composables/useGetCourseList.ts
+17
-0
useGetExperimentList.ts
src/modules/case-auth/composables/useGetExperimentList.ts
+23
-0
index.ts
src/modules/case-auth/index.ts
+10
-0
types.ts
src/modules/case-auth/types.ts
+77
-0
Index.vue
src/modules/case-auth/views/Index.vue
+304
-0
没有找到文件。
src/components/layout/Aside.vue
浏览文件 @
48826ec2
...
...
@@ -43,7 +43,7 @@ function handleClick(path: string) {
</
script
>
<
template
>
<aside
class=
"app-aside"
>
<aside
class=
"app-aside"
v-if=
"menuList.length > 0"
>
<nav
class=
"nav"
>
<el-menu
:default-active=
"defaultActive"
class=
"app-menu"
>
<template
v-for=
"item in menuList"
:key=
"item.path"
>
...
...
src/modules/case-auth/api.ts
0 → 100644
浏览文件 @
48826ec2
import
{
getCreateAuth
,
updateAuth
}
from
'@/api/base'
import
httpRequest
from
'@/utils/axios'
import
type
{
BookAuthorizeCreateItem
,
CaseAuthorizeCreateItem
,
ResourceDocumentCreateItem
,
ResourceVideoCreateItem
,
VideoAuthorizeCreateItem
,
VideoUploadAuthParams
,
VideoUploadRefreshParams
,
}
from
'./types'
// 获取课程列表
export
function
getCourseList
()
{
return
httpRequest
.
get
(
'/api/lab/v1/teacher/course/list'
)
}
// 获取实验列表
export
function
getExperimentList
(
params
:
{
course_id
:
string
})
{
return
httpRequest
.
get
(
'/api/resource/v1/backend/experiment/experiments'
,
{
params
})
}
// 创建案例原文
export
function
createCase
(
data
:
CaseAuthorizeCreateItem
)
{
return
httpRequest
.
post
(
'/api/lab/v1/teacher/experiment-cases/create'
,
data
)
}
// 创建指导书
export
function
createBook
(
data
:
BookAuthorizeCreateItem
)
{
return
httpRequest
.
post
(
'/api/lab/v1/teacher/book/create'
,
data
)
}
// 创建操作视频
export
function
createVideo
(
data
:
VideoAuthorizeCreateItem
)
{
return
httpRequest
.
post
(
'/api/lab/v1/teacher/video/create'
,
data
)
}
// 获取上传视频凭证
export
function
getUploadVideoAuth
(
data
:
VideoUploadAuthParams
)
{
return
httpRequest
.
post
(
'/api/lab/v1/teacher/video/auth-create'
,
data
)
}
// 刷新上传视频地址凭证
export
function
updateUploadVideoAuth
(
data
:
VideoUploadRefreshParams
)
{
return
httpRequest
.
post
(
'/api/lab/v1/teacher/video/create-auth'
,
data
)
}
// 统一资源平台视频上传凭证
export
function
getResourceUploadVideoAuth
(
data
:
VideoUploadAuthParams
)
{
return
getCreateAuth
(
data
)
}
// 统一资源平台刷新上传视频地址凭证
export
function
updateResourceUploadVideoAuth
(
data
:
VideoUploadRefreshParams
)
{
return
updateAuth
(
data
)
}
// 创建统一资源平台视频
export
function
createResourceVideo
(
data
:
ResourceVideoCreateItem
)
{
return
httpRequest
.
post
(
'/api/resource/v1/resource/video/create'
,
data
)
}
// 创建统一资源平台课件
export
function
createResourceCourseware
(
data
:
ResourceDocumentCreateItem
)
{
return
httpRequest
.
post
(
'/api/resource/v1/resource/courseware/create'
,
data
)
}
// 创建统一资源平台教案
export
function
createResourceLessonPlan
(
data
:
ResourceDocumentCreateItem
)
{
return
httpRequest
.
post
(
'/api/resource/v1/resource/lesson-plan/create'
,
data
)
}
// 创建统一资源平台其他资料
export
function
createResourceOther
(
data
:
ResourceDocumentCreateItem
)
{
return
httpRequest
.
post
(
'/api/resource/v1/resource/other-information/create'
,
data
)
}
src/modules/case-auth/composables/useCaseAuthorization.ts
0 → 100644
浏览文件 @
48826ec2
import
axios
from
'axios'
import
{
ElMessage
}
from
'element-plus'
import
{
useGetCategoryList
}
from
'@/composables/useGetCategoryList'
import
{
upload
}
from
'@/utils/upload'
import
{
createBook
,
createCase
,
createResourceCourseware
,
createResourceLessonPlan
,
createResourceOther
,
createResourceVideo
,
createVideo
,
getResourceUploadVideoAuth
,
updateResourceUploadVideoAuth
,
}
from
'../api'
import
type
{
AuthorizePlatform
,
CaseFileSelectionType
,
CaseLibraryFile
,
CaseLibraryItem
}
from
'../types'
import
{
useCaseLibrary
}
from
'./useCaseLibrary'
import
{
useGetCourseList
}
from
'./useGetCourseList'
import
{
useGetExperimentList
}
from
'./useGetExperimentList'
const
caseFileTypeMap
:
Record
<
CaseFileSelectionType
,
string
>
=
{
case
:
'1'
,
ppt
:
'2'
,
book
:
'3'
,
video
:
'4'
,
dataset
:
'5'
,
}
const
caseFileSectionConfig
:
Array
<
{
key
:
CaseFileSelectionType
title
:
string
emptyText
:
string
}
>
=
[
{
key
:
'case'
,
title
:
'案例原文'
,
emptyText
:
'暂无案例原文'
},
{
key
:
'ppt'
,
title
:
'案例PPT'
,
emptyText
:
'暂无案例PPT'
},
{
key
:
'book'
,
title
:
'案例指导书'
,
emptyText
:
'暂无案例指导书'
},
{
key
:
'video'
,
title
:
'案例操作视频'
,
emptyText
:
'暂无案例操作视频'
},
{
key
:
'dataset'
,
title
:
'案例数据集'
,
emptyText
:
'暂无案例数据集'
},
]
const
experimentSupportedTypes
=
new
Set
<
CaseFileSelectionType
>
([
'case'
,
'book'
,
'video'
])
const
resourceSupportedTypes
=
new
Set
<
CaseFileSelectionType
>
([
'ppt'
,
'book'
,
'video'
,
'dataset'
])
function
createEmptySelections
():
Record
<
CaseFileSelectionType
,
string
[]
>
{
return
{
case
:
[],
ppt
:
[],
book
:
[],
video
:
[],
dataset
:
[],
}
}
function
getMimeType
(
fileName
:
string
)
{
const
ext
=
fileName
.
split
(
'.'
).
pop
()?.
toLowerCase
()
||
''
const
mimeMap
:
Record
<
string
,
string
>
=
{
doc
:
'application/msword'
,
docx
:
'application/vnd.openxmlformats-officedocument.wordprocessingml.document'
,
pdf
:
'application/pdf'
,
ppt
:
'application/vnd.ms-powerpoint'
,
pptx
:
'application/vnd.openxmlformats-officedocument.presentationml.presentation'
,
xls
:
'application/vnd.ms-excel'
,
xlsx
:
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
,
csv
:
'text/csv'
,
mp4
:
'video/mp4'
,
}
return
mimeMap
[
ext
]
||
'application/octet-stream'
}
function
getFileExtension
(
fileName
:
string
)
{
return
fileName
.
split
(
'.'
).
pop
()?.
toLowerCase
()
||
''
}
function
getFileNameWithoutExt
(
fileName
:
string
)
{
return
fileName
.
replace
(
/
\.[^
.
]
+$/
,
''
)
}
function
getFilesByType
(
files
:
CaseLibraryItem
[
'files'
],
type
:
string
)
{
return
files
.
filter
((
file
)
=>
file
.
type
===
type
)
}
function
buildSummaryText
(
stats
:
{
experiment
:
{
case
:
number
;
book
:
number
;
video
:
number
}
resource
:
{
courseware
:
number
;
lessonPlan
:
number
;
video
:
number
;
other
:
number
}
})
{
const
parts
:
string
[]
=
[]
if
(
stats
.
experiment
.
case
||
stats
.
experiment
.
book
||
stats
.
experiment
.
video
)
{
parts
.
push
(
`实操平台:案例原文
${
stats
.
experiment
.
case
}
个,指导书
${
stats
.
experiment
.
book
}
个,视频
${
stats
.
experiment
.
video
}
个`
,
)
}
if
(
stats
.
resource
.
courseware
||
stats
.
resource
.
lessonPlan
||
stats
.
resource
.
video
||
stats
.
resource
.
other
)
{
parts
.
push
(
`统一资源平台:课件
${
stats
.
resource
.
courseware
}
个,教案
${
stats
.
resource
.
lessonPlan
}
个,视频
${
stats
.
resource
.
video
}
个,其他资料
${
stats
.
resource
.
other
}
个`
,
)
}
return
parts
.
join
(
';'
)
}
export
function
useCaseAuthorization
()
{
const
{
list
,
loading
,
fetchList
}
=
useCaseLibrary
()
const
{
courses
,
updateCourses
}
=
useGetCourseList
()
const
{
experiments
,
updateExperiments
}
=
useGetExperimentList
()
const
{
list
:
categoryList
}
=
useGetCategoryList
()
const
platformSelections
=
ref
<
AuthorizePlatform
[]
>
([
'experiment'
])
const
selectedCourse
=
ref
(
''
)
const
selectedExperiment
=
ref
(
''
)
const
resourceClassification
=
ref
(
''
)
const
selectedCaseId
=
ref
(
''
)
const
fileSelections
=
ref
<
Record
<
CaseFileSelectionType
,
string
[]
>>
(
createEmptySelections
())
const
submitting
=
ref
(
false
)
const
progress
=
ref
(
0
)
const
progressText
=
ref
(
''
)
updateCourses
()
fetchList
()
watch
(
selectedCourse
,
(
val
)
=>
{
selectedExperiment
.
value
=
''
updateExperiments
(
val
)
})
watch
(
selectedCaseId
,
()
=>
{
fileSelections
.
value
=
createEmptySelections
()
})
function
isPlatformSelected
(
platform
:
AuthorizePlatform
)
{
return
platformSelections
.
value
.
includes
(
platform
)
}
function
isTypeSelectable
(
type
:
CaseFileSelectionType
)
{
return
(
(
isPlatformSelected
(
'experiment'
)
&&
experimentSupportedTypes
.
has
(
type
))
||
(
isPlatformSelected
(
'resource'
)
&&
resourceSupportedTypes
.
has
(
type
))
)
}
watch
(
platformSelections
,
()
=>
{
const
nextSelections
=
createEmptySelections
()
for
(
const
section
of
caseFileSectionConfig
)
{
nextSelections
[
section
.
key
]
=
isTypeSelectable
(
section
.
key
)
?
[...
fileSelections
.
value
[
section
.
key
]]
:
[]
}
fileSelections
.
value
=
nextSelections
},
{
deep
:
true
},
)
const
selectedCase
=
computed
(()
=>
list
.
value
.
find
((
item
)
=>
item
.
id
===
selectedCaseId
.
value
)
||
null
)
const
groupedFiles
=
computed
(()
=>
{
const
files
=
selectedCase
.
value
?.
files
||
[]
return
{
case
:
getFilesByType
(
files
,
caseFileTypeMap
.
case
),
ppt
:
getFilesByType
(
files
,
caseFileTypeMap
.
ppt
),
book
:
getFilesByType
(
files
,
caseFileTypeMap
.
book
),
video
:
getFilesByType
(
files
,
caseFileTypeMap
.
video
),
dataset
:
getFilesByType
(
files
,
caseFileTypeMap
.
dataset
),
}
})
const
selectedCount
=
computed
(()
=>
caseFileSectionConfig
.
reduce
((
total
,
section
)
=>
total
+
fileSelections
.
value
[
section
.
key
].
length
,
0
),
)
const
selectedItems
=
computed
(()
=>
{
if
(
!
selectedCase
.
value
)
return
[]
const
result
:
Array
<
{
row
:
CaseLibraryItem
type
:
CaseFileSelectionType
file
:
CaseLibraryFile
authorizeToExperiment
:
boolean
authorizeToResource
:
boolean
}
>
=
[]
for
(
const
section
of
caseFileSectionConfig
)
{
for
(
const
key
of
fileSelections
.
value
[
section
.
key
])
{
const
file
=
selectedCase
.
value
.
files
.
find
(
(
item
)
=>
`
${
item
.
type
}
-
${
item
.
url
}
`
===
key
&&
item
.
type
===
caseFileTypeMap
[
section
.
key
],
)
if
(
!
file
)
continue
result
.
push
({
row
:
selectedCase
.
value
,
type
:
section
.
key
,
file
,
authorizeToExperiment
:
isPlatformSelected
(
'experiment'
)
&&
experimentSupportedTypes
.
has
(
section
.
key
),
authorizeToResource
:
isPlatformSelected
(
'resource'
)
&&
resourceSupportedTypes
.
has
(
section
.
key
),
})
}
}
return
result
})
function
handleCaseChange
(
caseId
:
string
)
{
selectedCaseId
.
value
=
caseId
}
function
getFileKey
(
file
:
CaseLibraryFile
)
{
return
`
${
file
.
type
}
-
${
file
.
url
}
`
}
function
getAllChecked
(
type
:
CaseFileSelectionType
)
{
const
files
=
groupedFiles
.
value
[
type
]
return
!!
files
.
length
&&
fileSelections
.
value
[
type
].
length
===
files
.
length
}
function
handleToggleAll
(
type
:
CaseFileSelectionType
,
checked
:
boolean
)
{
if
(
!
isTypeSelectable
(
type
))
{
fileSelections
.
value
[
type
]
=
[]
return
}
fileSelections
.
value
[
type
]
=
checked
?
groupedFiles
.
value
[
type
].
map
((
file
)
=>
getFileKey
(
file
))
:
[]
}
function
setStepProgress
(
index
:
number
,
total
:
number
,
ratio
:
number
,
label
:
string
)
{
const
safeRatio
=
Math
.
max
(
0
,
Math
.
min
(
1
,
ratio
))
progress
.
value
=
Math
.
round
(((
index
+
safeRatio
)
/
total
)
*
100
)
progressText
.
value
=
label
}
async
function
downloadRemoteFile
(
url
:
string
,
fileName
:
string
,
index
:
number
,
total
:
number
)
{
const
response
=
await
axios
.
get
(
url
,
{
responseType
:
'blob'
,
onDownloadProgress
:
(
event
)
=>
{
if
(
!
event
.
total
)
{
setStepProgress
(
index
,
total
,
0.15
,
`正在下载:
${
fileName
}
`
)
return
}
const
ratio
=
event
.
loaded
/
event
.
total
setStepProgress
(
index
,
total
,
ratio
*
0.45
,
`正在下载
${
Math
.
round
(
ratio
*
100
)}
%:
${
fileName
}
`
)
},
})
return
new
File
([
response
.
data
],
fileName
,
{
type
:
response
.
data
.
type
||
getMimeType
(
fileName
),
})
}
async
function
uploadDocumentFromCase
(
url
:
string
,
fileName
:
string
,
index
:
number
,
total
:
number
)
{
const
file
=
await
downloadRemoteFile
(
url
,
fileName
,
index
,
total
)
setStepProgress
(
index
,
total
,
0.55
,
`正在上传文件:
${
fileName
}
`
)
const
uploadedUrl
=
await
upload
(
file
)
return
{
file
,
url
:
uploadedUrl
,
}
}
async
function
uploadVideoFromCase
(
url
:
string
,
fileName
:
string
,
index
:
number
,
total
:
number
,
)
{
const
file
=
await
downloadRemoteFile
(
url
,
fileName
,
index
,
total
)
return
new
Promise
<
string
>
((
resolve
,
reject
)
=>
{
const
uploader
=
new
(
window
as
any
).
AliyunUpload
.
Vod
({
userId
:
'1303984639806000'
,
region
:
'cn-shanghai'
,
partSize
:
1048576
,
parallel
:
5
,
retryCount
:
3
,
retryDuration
:
2
,
onUploadstarted
(
uploadInfo
:
any
)
{
setStepProgress
(
index
,
total
,
0.55
,
`正在上传视频:
${
uploadInfo
.
file
.
name
}
`
)
getResourceUploadVideoAuth
({
title
:
uploadInfo
.
file
.
name
,
file_name
:
uploadInfo
.
file
.
name
})
.
then
((
res
:
any
)
=>
{
uploader
.
setUploadAuthAndAddress
(
uploadInfo
,
res
.
data
.
upload_auth
,
res
.
data
.
upload_address
,
res
.
data
.
source_id
,
)
})
.
catch
(
reject
)
},
onUploadSucceed
(
uploadInfo
:
any
)
{
setStepProgress
(
index
,
total
,
0.95
,
`视频上传完成:
${
uploadInfo
.
file
.
name
}
`
)
resolve
(
uploadInfo
.
videoId
)
},
onUploadFailed
(
_uploadInfo
:
any
,
code
:
number
,
message
:
string
)
{
reject
(
new
Error
(
message
||
`视频上传失败:
${
code
}
`
))
},
onUploadProgress
(
_uploadInfo
:
any
,
_totalSize
:
number
,
loadedPercent
:
number
)
{
setStepProgress
(
index
,
total
,
0.55
+
loadedPercent
*
0.4
,
`正在上传视频
${
Math
.
round
(
loadedPercent
*
100
)}
%:
${
fileName
}
`
,
)
},
onUploadTokenExpired
(
uploadInfo
:
any
)
{
updateResourceUploadVideoAuth
({
source_id
:
uploadInfo
.
videoId
})
.
then
((
res
:
any
)
=>
{
uploader
.
resumeUploadWithAuth
(
res
.
data
.
UploadAuth
||
res
.
data
.
upload_auth
||
res
.
UploadAuth
)
})
.
catch
(
reject
)
},
})
uploader
.
addFile
(
file
,
null
,
null
,
null
,
'{"Vod":{}}'
)
uploader
.
startUpload
()
})
}
async
function
submitAuthorize
()
{
if
(
!
platformSelections
.
value
.
length
)
{
ElMessage
.
warning
(
'请选择授权平台'
)
return
}
if
(
isPlatformSelected
(
'experiment'
)
&&
!
selectedExperiment
.
value
)
{
ElMessage
.
warning
(
'授权到实操平台时请选择课程和实验'
)
return
}
if
(
isPlatformSelected
(
'resource'
)
&&
!
resourceClassification
.
value
)
{
ElMessage
.
warning
(
'授权到统一资源平台时请选择分类'
)
return
}
if
(
!
selectedItems
.
value
.
length
)
{
ElMessage
.
warning
(
'请勾选需要授权的案例文件'
)
return
}
const
actionableItems
=
selectedItems
.
value
.
filter
((
item
)
=>
item
.
authorizeToExperiment
||
item
.
authorizeToResource
)
if
(
!
actionableItems
.
length
)
{
ElMessage
.
warning
(
'当前授权平台下没有可授权的案例文件'
)
return
}
const
successStats
=
{
experiment
:
{
case
:
0
,
book
:
0
,
video
:
0
},
resource
:
{
courseware
:
0
,
lessonPlan
:
0
,
video
:
0
,
other
:
0
},
}
submitting
.
value
=
true
progress
.
value
=
0
progressText
.
value
=
`准备处理 0/
${
actionableItems
.
length
}
`
try
{
for
(
let
index
=
0
;
index
<
actionableItems
.
length
;
index
+=
1
)
{
const
item
=
actionableItems
[
index
]
const
{
type
,
file
,
authorizeToExperiment
,
authorizeToResource
}
=
item
setStepProgress
(
index
,
actionableItems
.
length
,
0.05
,
`准备处理
${
index
+
1
}
/
${
actionableItems
.
length
}
:
${
file
.
name
}
`
)
if
(
type
===
'video'
)
{
const
sourceId
=
await
uploadVideoFromCase
(
file
.
url
,
file
.
name
,
index
,
actionableItems
.
length
)
if
(
authorizeToExperiment
)
{
setStepProgress
(
index
,
actionableItems
.
length
,
0.98
,
`正在创建实验操作视频:
${
file
.
name
}
`
)
await
createVideo
({
experiment_id
:
selectedExperiment
.
value
,
name
:
getFileNameWithoutExt
(
file
.
name
),
source_id
:
sourceId
,
status
:
'1'
,
})
successStats
.
experiment
.
video
+=
1
}
if
(
authorizeToResource
)
{
setStepProgress
(
index
,
actionableItems
.
length
,
0.98
,
`正在创建统一资源视频:
${
file
.
name
}
`
)
await
createResourceVideo
({
name
:
getFileNameWithoutExt
(
file
.
name
),
source
:
'2'
,
classification
:
resourceClassification
.
value
,
knowledge_points
:
''
,
cover
:
''
,
source_id
:
sourceId
,
})
successStats
.
resource
.
video
+=
1
}
progress
.
value
=
Math
.
round
(((
index
+
1
)
/
actionableItems
.
length
)
*
100
)
continue
}
const
uploadedFile
=
await
uploadDocumentFromCase
(
file
.
url
,
file
.
name
,
index
,
actionableItems
.
length
)
if
(
type
===
'case'
&&
authorizeToExperiment
)
{
setStepProgress
(
index
,
actionableItems
.
length
,
0.95
,
`正在创建实验案例原文:
${
file
.
name
}
`
)
await
createCase
({
experiment_id
:
selectedExperiment
.
value
,
name
:
getFileNameWithoutExt
(
file
.
name
),
type
:
getMimeType
(
file
.
name
),
url
:
uploadedFile
.
url
,
size
:
(
uploadedFile
.
file
.
size
/
1024
/
1024
).
toString
(),
status
:
'1'
,
})
successStats
.
experiment
.
case
+=
1
}
if
(
type
===
'ppt'
&&
authorizeToResource
)
{
setStepProgress
(
index
,
actionableItems
.
length
,
0.95
,
`正在创建统一资源课件:
${
file
.
name
}
`
)
await
createResourceCourseware
({
name
:
getFileNameWithoutExt
(
file
.
name
),
source
:
'2'
,
classification
:
resourceClassification
.
value
,
knowledge_points
:
''
,
url
:
uploadedFile
.
url
,
type
:
getFileExtension
(
file
.
name
),
size
:
`
${
uploadedFile
.
file
.
size
}
`
,
})
successStats
.
resource
.
courseware
+=
1
}
if
(
type
===
'book'
)
{
if
(
authorizeToExperiment
)
{
setStepProgress
(
index
,
actionableItems
.
length
,
0.95
,
`正在创建实验案例指导书:
${
file
.
name
}
`
)
await
createBook
({
experiment_id
:
selectedExperiment
.
value
,
name
:
getFileNameWithoutExt
(
file
.
name
),
type
:
getMimeType
(
file
.
name
),
url
:
uploadedFile
.
url
,
size
:
(
uploadedFile
.
file
.
size
/
1024
/
1024
).
toString
(),
status
:
'1'
,
})
successStats
.
experiment
.
book
+=
1
}
if
(
authorizeToResource
)
{
setStepProgress
(
index
,
actionableItems
.
length
,
0.97
,
`正在创建统一资源教案:
${
file
.
name
}
`
)
await
createResourceLessonPlan
({
name
:
getFileNameWithoutExt
(
file
.
name
),
source
:
'2'
,
classification
:
resourceClassification
.
value
,
knowledge_points
:
''
,
url
:
uploadedFile
.
url
,
type
:
getFileExtension
(
file
.
name
),
size
:
`
${
uploadedFile
.
file
.
size
}
`
,
})
successStats
.
resource
.
lessonPlan
+=
1
}
}
if
(
type
===
'dataset'
&&
authorizeToResource
)
{
setStepProgress
(
index
,
actionableItems
.
length
,
0.95
,
`正在创建统一资源其他资料:
${
file
.
name
}
`
)
await
createResourceOther
({
name
:
getFileNameWithoutExt
(
file
.
name
),
source
:
'2'
,
classification
:
resourceClassification
.
value
,
knowledge_points
:
''
,
url
:
uploadedFile
.
url
,
type
:
getFileExtension
(
file
.
name
),
size
:
`
${
uploadedFile
.
file
.
size
}
`
,
})
successStats
.
resource
.
other
+=
1
}
progress
.
value
=
Math
.
round
(((
index
+
1
)
/
actionableItems
.
length
)
*
100
)
}
const
summaryText
=
buildSummaryText
(
successStats
)
if
(
!
summaryText
)
{
ElMessage
.
warning
(
'当前授权平台下没有可授权的案例文件'
)
return
}
ElMessage
.
success
(
`授权完成:
${
summaryText
}
`
)
progressText
.
value
=
`授权完成
${
actionableItems
.
length
}
/
${
actionableItems
.
length
}
`
}
catch
(
error
)
{
console
.
error
(
error
)
ElMessage
.
error
(
'批量授权失败'
)
progressText
.
value
=
'授权失败'
}
finally
{
submitting
.
value
=
false
}
}
return
{
caseFileSectionConfig
,
caseFileTypeMap
,
categoryList
,
courses
,
experiments
,
fileSelections
,
groupedFiles
,
list
,
loading
,
platformSelections
,
progress
,
progressText
,
resourceClassification
,
selectedCase
,
selectedCaseId
,
selectedCount
,
selectedCourse
,
selectedExperiment
,
submitting
,
getAllChecked
,
getFileKey
,
getFilesByType
,
handleCaseChange
,
handleToggleAll
,
isTypeSelectable
,
submitAuthorize
,
}
}
src/modules/case-auth/composables/useCaseLibrary.ts
0 → 100644
浏览文件 @
48826ec2
import
axios
from
'axios'
import
type
{
CaseLibraryItem
}
from
'../types'
export
function
useCaseLibrary
()
{
const
list
=
ref
<
CaseLibraryItem
[]
>
([])
const
loading
=
ref
(
false
)
function
fetchList
()
{
loading
.
value
=
true
return
axios
.
get
(
`https://webapp-pub.ezijing.com/case_library/case.json?_t=
${
Date
.
now
()}
`
)
.
then
((
res
)
=>
{
list
.
value
=
res
.
data
})
.
finally
(()
=>
{
loading
.
value
=
false
})
}
return
{
list
,
loading
,
fetchList
}
}
src/modules/case-auth/composables/useGetCourseList.ts
0 → 100644
浏览文件 @
48826ec2
import
{
getCourseList
}
from
'../api'
export
interface
CourseType
{
id
:
string
name
:
string
}
const
courses
=
ref
<
CourseType
[]
>
([])
export
function
useGetCourseList
()
{
function
updateCourses
()
{
getCourseList
().
then
((
res
:
any
)
=>
{
courses
.
value
=
res
.
data
})
}
return
{
courses
,
updateCourses
}
}
src/modules/case-auth/composables/useGetExperimentList.ts
0 → 100644
浏览文件 @
48826ec2
import
{
getExperimentList
}
from
'../api'
export
interface
ExperimentType
{
id
:
string
name
:
string
}
export
function
useGetExperimentList
()
{
const
experiments
=
ref
<
ExperimentType
[]
>
([])
function
updateExperiments
(
courseId
?:
string
)
{
if
(
!
courseId
)
{
experiments
.
value
=
[]
return
}
getExperimentList
({
course_id
:
courseId
}).
then
((
res
:
any
)
=>
{
experiments
.
value
=
res
.
data
}).
catch
((
err
)
=>
{
console
.
error
(
'获取实验列表失败'
,
err
)
experiments
.
value
=
[]
})
}
return
{
experiments
,
updateExperiments
}
}
src/modules/case-auth/index.ts
0 → 100644
浏览文件 @
48826ec2
import
type
{
RouteRecordRaw
}
from
'vue-router'
import
AppLayout
from
'@/components/layout/Index.vue'
export
const
routes
:
Array
<
RouteRecordRaw
>
=
[
{
path
:
'/case-auth'
,
component
:
AppLayout
,
children
:
[{
path
:
''
,
component
:
()
=>
import
(
'./views/Index.vue'
)
}],
},
]
src/modules/case-auth/types.ts
0 → 100644
浏览文件 @
48826ec2
export
interface
CaseLibraryFile
{
type
:
string
type_name
:
string
name
:
string
url
:
string
size
?:
string
source_id
?:
string
created_at
:
string
updated_at
:
string
}
export
interface
CaseLibraryItem
{
id
:
string
name
:
string
description
:
string
files
:
CaseLibraryFile
[]
operator
:
{
id
:
string
;
name
:
string
}
created_at
:
string
updated_at
:
string
}
export
type
CaseFileSelectionType
=
'case'
|
'ppt'
|
'book'
|
'video'
|
'dataset'
export
type
AuthorizePlatform
=
'resource'
|
'experiment'
export
interface
CaseAuthorizeCreateItem
{
experiment_id
:
string
status
:
string
name
:
string
type
:
string
url
:
string
size
:
string
}
export
interface
BookAuthorizeCreateItem
{
experiment_id
:
string
status
:
string
name
:
string
type
:
string
url
:
string
size
:
string
}
export
interface
VideoAuthorizeCreateItem
{
experiment_id
:
string
status
:
string
name
:
string
source_id
:
string
}
export
interface
VideoUploadAuthParams
{
title
:
string
file_name
:
string
}
export
interface
VideoUploadRefreshParams
{
source_id
:
string
}
export
interface
ResourceDocumentCreateItem
{
name
:
string
source
:
string
classification
:
string
knowledge_points
?:
string
url
:
string
type
:
string
size
:
string
}
export
interface
ResourceVideoCreateItem
{
name
:
string
source
:
string
classification
:
string
knowledge_points
:
string
cover
:
string
source_id
:
string
}
src/modules/case-auth/views/Index.vue
0 → 100644
浏览文件 @
48826ec2
<
script
setup
lang=
"ts"
>
import
{
useCaseAuthorization
}
from
'../composables/useCaseAuthorization'
const
defaultProps
=
{
children
:
'children'
,
label
:
'category_name'
,
value
:
'id'
,
}
const
{
caseFileSectionConfig
,
caseFileTypeMap
,
categoryList
,
courses
,
experiments
,
fileSelections
,
groupedFiles
,
list
,
loading
,
platformSelections
,
progress
,
progressText
,
resourceClassification
,
selectedCase
,
selectedCaseId
,
selectedCount
,
selectedCourse
,
selectedExperiment
,
submitting
,
getAllChecked
,
getFileKey
,
getFilesByType
,
handleCaseChange
,
handleToggleAll
,
isTypeSelectable
,
submitAuthorize
,
}
=
useCaseAuthorization
()
</
script
>
<
template
>
<AppCard
title=
"案例授权"
>
<div
class=
"case-auth-toolbar"
>
<el-checkbox-group
v-model=
"platformSelections"
>
<el-checkbox
label=
"resource"
>
统一资源平台
</el-checkbox>
<el-checkbox
label=
"experiment"
>
实操平台
</el-checkbox>
</el-checkbox-group>
<el-tree-select
v-if=
"platformSelections.includes('resource')"
v-model=
"resourceClassification"
:data=
"categoryList"
:props=
"defaultProps"
node-key=
"id"
clearable
check-strictly
filterable
style=
"width: 240px"
placeholder=
"请选择统一资源分类"
/>
<el-select
v-if=
"platformSelections.includes('experiment')"
v-model=
"selectedCourse"
placeholder=
"请选择课程"
filterable
clearable
style=
"width: 200px"
>
<el-option
v-for=
"item in courses"
:key=
"item.id"
:label=
"item.name"
:value=
"item.id"
/>
</el-select>
<el-select
v-if=
"platformSelections.includes('experiment')"
v-model=
"selectedExperiment"
placeholder=
"请选择实验"
filterable
clearable
style=
"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=
"submitAuthorize"
:disabled=
"!selectedCount"
>
批量授权
</el-button>
</div>
<el-alert
type=
"warning"
:closable=
"false"
class=
"case-auth-alert"
>
<div
class=
"case-auth-alert__content"
>
<div
class=
"case-auth-alert__title"
>
重要提示:
</div>
<div>
1、授权到实操平台前请先创建课程和实验
</div>
<div>
2、授权到统一资源平台时必须选择分类
</div>
<div
class=
"case-auth-alert__title"
>
授权资源映射说明:
</div>
<div>
1、案例原文 -> 实操平台:实验案例原文
</div>
<div>
2、案例指导书 -> 实操平台:实验案例指导书;统一资源平台:教案
</div>
<div>
3、案例PPT -> 统一资源平台:课件
</div>
<div>
4、案例操作视频 -> 统一资源平台:视频;实操平台:实验操作视频
</div>
<div>
5、案例数据集 -> 统一资源平台:其他资料
</div>
</div>
</el-alert>
<div
v-if=
"selectedCount || submitting || progress"
class=
"case-auth-progress"
>
<div
class=
"case-auth-progress__header"
>
<span>
{{
progressText
||
`准备处理 0/${selectedCount
}
`
}}
<
/span
>
<
span
>
{{
progress
}}
%<
/span
>
<
/div
>
<
el
-
progress
:
percentage
=
"progress"
:
show
-
text
=
"false"
/>
<
/div
>
<
div
v
-
loading
=
"loading"
class
=
"case-list"
>
<
div
v
-
for
=
"item in list"
:
key
=
"item.id"
class
=
"case-card"
:
class
=
"{ 'case-card--active': selectedCaseId === item.id
}
"
@
click
=
"handleCaseChange(item.id)"
>
<
div
class
=
"case-card__top"
>
<
div
>
<
div
class
=
"case-card__name"
>
{{
item
.
name
}}
<
/div
>
<
div
class
=
"case-card__id"
>
{{
item
.
id
}}
<
/div
>
<
/div
>
<
el
-
radio
:
model
-
value
=
"selectedCaseId === item.id"
:
label
=
"true"
>
选择
<
/el-radio
>
<
/div
>
<
div
class
=
"case-card__desc"
>
{{
item
.
description
||
'暂无案例简介'
}}
<
/div
>
<
div
class
=
"case-card__meta"
>
<
el
-
tag
v
-
for
=
"section in caseFileSectionConfig"
:
key
=
"section.key"
size
=
"small"
:
type
=
"getFilesByType(item.files, caseFileTypeMap[section.key]).length ? 'success' : 'info'"
>
{{
section
.
title
}}
{{
getFilesByType
(
item
.
files
,
caseFileTypeMap
[
section
.
key
]).
length
}}
<
/el-tag
>
<
/div
>
<
/div
>
<
/div
>
<
div
v
-
if
=
"selectedCase"
class
=
"case-auth-sections"
>
<
div
v
-
for
=
"section in caseFileSectionConfig"
:
key
=
"section.key"
class
=
"case-auth-section"
:
class
=
"{ 'case-auth-section--disabled': !isTypeSelectable(section.key)
}
"
>
<
div
class
=
"case-auth-section__header"
>
<
div
class
=
"case-auth-section__title"
>
{{
section
.
title
}}
<
/div
>
<
el
-
checkbox
:
model
-
value
=
"getAllChecked(section.key)"
:
disabled
=
"!groupedFiles[section.key].length || !isTypeSelectable(section.key)"
@
update
:
model
-
value
=
"(value) => handleToggleAll(section.key, !!value)"
>
全选
<
/el-checkbox
>
<
/div
>
<
el
-
checkbox
-
group
v
-
model
=
"fileSelections[section.key]"
class
=
"case-auth-options"
>
<
el
-
checkbox
v
-
for
=
"file in groupedFiles[section.key]"
:
key
=
"getFileKey(file)"
:
label
=
"getFileKey(file)"
:
disabled
=
"!isTypeSelectable(section.key)"
>
{{
file
.
name
}}
<
/el-checkbox
>
<
/el-checkbox-group
>
<
el
-
empty
v
-
if
=
"!groupedFiles[section.key].length"
:
description
=
"section.emptyText"
:
image
-
size
=
"72"
/>
<
/div
>
<
/div
>
<
/AppCard
>
<
/template
>
<
style
lang
=
"scss"
scoped
>
.
case
-
auth
-
toolbar
{
display
:
flex
;
align
-
items
:
center
;
flex
-
wrap
:
wrap
;
gap
:
10
px
;
margin
-
bottom
:
16
px
;
}
.
case
-
auth
-
alert
{
margin
-
bottom
:
16
px
;
}
.
case
-
auth
-
alert__title
{
font
-
weight
:
600
;
}
.
case
-
auth
-
alert__content
{
display
:
flex
;
flex
-
direction
:
column
;
gap
:
6
px
;
line
-
height
:
1.6
;
}
.
case
-
auth
-
progress
{
margin
-
bottom
:
16
px
;
padding
:
12
px
16
px
;
background
:
var
(
--
el
-
fill
-
color
-
light
);
border
-
radius
:
8
px
;
}
.
case
-
auth
-
progress__header
{
display
:
flex
;
justify
-
content
:
space
-
between
;
gap
:
12
px
;
margin
-
bottom
:
8
px
;
font
-
size
:
14
px
;
color
:
var
(
--
el
-
text
-
color
-
regular
);
}
.
case
-
list
{
display
:
grid
;
grid
-
template
-
columns
:
repeat
(
3
,
minmax
(
0
,
1
fr
));
gap
:
16
px
;
}
.
case
-
card
{
padding
:
16
px
;
border
:
1
px
solid
var
(
--
el
-
border
-
color
);
border
-
radius
:
10
px
;
background
:
#
fff
;
cursor
:
pointer
;
transition
:
border
-
color
0.2
s
ease
,
box
-
shadow
0.2
s
ease
;
}
.
case
-
card
:
hover
,
.
case
-
card
--
active
{
border
-
color
:
var
(
--
el
-
color
-
primary
);
box
-
shadow
:
0
0
0
2
px
rgb
(
64
158
255
/
12
%
);
}
.
case
-
card__top
{
display
:
flex
;
align
-
items
:
flex
-
start
;
justify
-
content
:
space
-
between
;
gap
:
12
px
;
margin
-
bottom
:
10
px
;
}
.
case
-
card__name
{
font
-
size
:
16
px
;
font
-
weight
:
600
;
color
:
var
(
--
el
-
text
-
color
-
primary
);
}
.
case
-
card__id
{
margin
-
top
:
4
px
;
font
-
size
:
12
px
;
color
:
var
(
--
el
-
text
-
color
-
secondary
);
}
.
case
-
card__desc
{
min
-
height
:
44
px
;
font
-
size
:
14
px
;
line
-
height
:
1.6
;
color
:
var
(
--
el
-
text
-
color
-
regular
);
}
.
case
-
card__meta
{
display
:
flex
;
flex
-
wrap
:
wrap
;
gap
:
8
px
;
margin
-
top
:
12
px
;
}
.
case
-
auth
-
sections
{
display
:
grid
;
grid
-
template
-
columns
:
repeat
(
3
,
minmax
(
0
,
1
fr
));
gap
:
16
px
;
margin
-
top
:
16
px
;
}
.
case
-
auth
-
section
{
padding
:
16
px
;
background
:
var
(
--
el
-
fill
-
color
-
light
);
border
-
radius
:
8
px
;
min
-
height
:
180
px
;
}
.
case
-
auth
-
section
--
disabled
{
opacity
:
0.65
;
}
.
case
-
auth
-
section__header
{
display
:
flex
;
align
-
items
:
center
;
justify
-
content
:
space
-
between
;
gap
:
12
px
;
margin
-
bottom
:
12
px
;
}
.
case
-
auth
-
section__title
{
font
-
size
:
15
px
;
font
-
weight
:
600
;
color
:
var
(
--
el
-
text
-
color
-
primary
);
}
.
case
-
auth
-
options
{
display
:
flex
;
flex
-
direction
:
column
;
gap
:
10
px
;
}
@
media
(
max
-
width
:
1200
px
)
{
.
case
-
list
,
.
case
-
auth
-
sections
{
grid
-
template
-
columns
:
1
fr
;
}
}
<
/style
>
编写
预览
Markdown
格式
0%
重试
或
添加新文件
添加附件
取消
您添加了
0
人
到此讨论。请谨慎行事。
请先完成此评论的编辑!
取消
请
注册
或者
登录
后发表评论