提交 00476186 authored 作者: 王鹏飞's avatar 王鹏飞

feat: 添加实验复制功能,支持选择复制资源

上级 719a1abe
import httpRequest from '@/utils/axios'
import type { ExperimentCreateItem } from './types'
import type { ExperimentCopyResources, ExperimentCreateItem } from './types'
// 获取实验列表
export function getExperimentList(params?: { name?: string; page?: number; 'per-page'?: number }) {
......@@ -205,7 +205,20 @@ export function getScoreExamList(params: { experiment_id: string }) {
}
// 复制实验
export function copyExperiment(data: { experiment_id: string }) {
export function getExperimentCopyResources(params: { experiment_id: string }) {
return httpRequest.get<ExperimentCopyResources>('/api/resource/v1/backend/experiment/copy-resources', { params })
}
// 复制实验
export function copyExperiment(data: {
experiment_id: string
experiment_name?: string
sso_id?: string
is_copy_class?: number
case_ids?: string[]
book_ids?: string[]
video_ids?: string[]
}) {
return httpRequest.post('/api/resource/v1/backend/experiment/copy', data)
}
......
<script setup lang="ts">
import type { FormInstance } from 'element-plus'
import type { ExperimentItem } from '../types'
import type { ExperimentCopyResourceItem, ExperimentCopyResources, ExperimentItem } from '../types'
import { ElMessage } from 'element-plus'
import { copyExperiment } from '../api'
import { copyExperiment, getExperimentCopyResources } from '../api'
// import { useGetProjectList } from '@/composables/useGetProjectList'
import { useGetTeacherList } from '../composables/useGetTeacherList'
import { pickBy } from 'lodash-es'
......@@ -16,16 +16,62 @@ const emit = defineEmits<{
const formRef = ref<FormInstance>()
type CopyResourceType = keyof ExperimentCopyResources
type CopyResourceField = 'case_ids' | 'book_ids' | 'video_ids'
const form = reactive({
experiment_name: '',
organ_id: '',
sso_id: '',
is_copy_class: 0
is_copy_class: 0,
case_ids: [] as string[],
book_ids: [] as string[],
video_ids: [] as string[],
})
const copyResources = ref<ExperimentCopyResources>({
cases: [],
books: [],
videos: [],
})
const resourceLoading = ref(false)
const resourceSections = computed<
Array<{
type: CopyResourceType
field: CopyResourceField
title: string
emptyText: string
items: ExperimentCopyResourceItem[]
}>
>(() => [
{
type: 'cases',
field: 'case_ids',
title: '案例原文',
emptyText: '暂无可复制案例原文',
items: copyResources.value.cases,
},
{
type: 'books',
field: 'book_ids',
title: '案例指导书',
emptyText: '暂无可复制案例指导书',
items: copyResources.value.books,
},
{
type: 'videos',
field: 'video_ids',
title: '案例操作视频',
emptyText: '暂无可复制案例操作视频',
items: copyResources.value.videos,
},
])
onMounted(() => {
form.organ_id = props.data.organ_id
form.experiment_name = props.data.name + '(copy)'
fetchCopyResources()
// const [teacher] = props.data.teacher
// form.sso_id = teacher.sso_id
})
......@@ -46,6 +92,65 @@ watch(
// form.sso_id = ''
// }
async function fetchCopyResources() {
resourceLoading.value = true
try {
const { data } = await getExperimentCopyResources({ experiment_id: props.data.id })
copyResources.value = {
cases: data?.cases || [],
books: data?.books || [],
videos: data?.videos || [],
}
form.case_ids = copyResources.value.cases.map(item => item.id)
form.book_ids = copyResources.value.books.map(item => item.id)
form.video_ids = copyResources.value.videos.map(item => item.id)
} finally {
resourceLoading.value = false
}
}
function getSelectedCount(field: CopyResourceField) {
return form[field].length
}
function handleSelectAll(field: CopyResourceField, items: ExperimentCopyResourceItem[]) {
form[field] = items.map(item => item.id)
}
function handleClear(field: CopyResourceField) {
form[field] = []
}
function getResourceMeta(item: ExperimentCopyResourceItem, type: CopyResourceType) {
if (type === 'videos') {
const meta = [item.length_name, item.size_name].filter(Boolean)
return meta.join(' | ')
}
const meta = [formatResourceType(item.type), item.size_name].filter(Boolean)
return meta.join(' | ')
}
function formatResourceType(type?: string) {
if (!type) return ''
const typeMap: Record<string, string> = {
'application/vnd.openxmlformats-officedocument.wordprocessingml.document': 'DOCX',
'application/msword': 'DOC',
'application/pdf': 'PDF',
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet': 'XLSX',
'application/vnd.ms-excel': 'XLS',
'application/vnd.openxmlformats-officedocument.presentationml.presentation': 'PPTX',
'application/vnd.ms-powerpoint': 'PPT',
}
if (typeMap[type]) {
return typeMap[type]
}
const parts = type.split('/')
return (parts[1] || parts[0] || '').toUpperCase()
}
async function handleSubmit() {
formRef.value?.validate().then(async () => {
const params = { ...pickBy(form, item => item !== ''), experiment_id: props.data.id }
......@@ -58,7 +163,7 @@ async function handleSubmit() {
</script>
<template>
<el-dialog title="复制实验" width="600px" @update:modelValue="value => $emit('update:modelValue', value)">
<el-dialog title="复制实验" width="820px" class="copy-dialog" @update:modelValue="value => $emit('update:modelValue', value)">
<el-form ref="formRef" :model="form" label-width="130px">
<el-form-item label="实验名称" prop="experiment_name">
<el-input v-model="form.experiment_name" />
......@@ -82,6 +187,40 @@ async function handleSubmit() {
:inactive-value="0" />
</el-form-item>
</el-form>
<div class="copy-resource-panel" v-loading="resourceLoading">
<div class="copy-resource-panel__header">
<div>
<div class="copy-resource-panel__title">资源选择</div>
<div class="copy-resource-panel__desc">按资源精确选择复制内容,默认全选</div>
</div>
</div>
<div class="copy-resource-section" v-for="section in resourceSections" :key="section.type">
<div class="copy-resource-section__header">
<div class="copy-resource-section__title">
<span>{{ section.title }}</span>
<span class="copy-resource-section__count">已选 {{ getSelectedCount(section.field) }}/{{ section.items.length }}</span>
</div>
<div class="copy-resource-section__actions" v-if="section.items.length">
<el-button link type="primary" @click="handleSelectAll(section.field, section.items)">全选</el-button>
<el-button link @click="handleClear(section.field)">清空</el-button>
</div>
</div>
<el-empty v-if="!section.items.length && !resourceLoading" :description="section.emptyText" :image-size="70" />
<el-checkbox-group v-else v-model="form[section.field]" class="copy-resource-list">
<el-checkbox v-for="item in section.items" :key="item.id" :label="item.id" class="copy-resource-item">
<div class="copy-resource-item__content">
<div class="copy-resource-item__name" :title="item.name">{{ item.name }}</div>
<div class="copy-resource-item__meta" :title="getResourceMeta(item, section.type) || '无附加信息'">
{{ getResourceMeta(item, section.type) || '无附加信息' }}
</div>
</div>
<el-tag size="small" :type="item.status === '1' ? 'success' : 'info'">{{ item.status_name }}</el-tag>
</el-checkbox>
</el-checkbox-group>
</div>
</div>
<template #footer>
<el-row justify="center">
<el-button round auto-insert-space @click="$emit('update:modelValue', false)">关闭</el-button>
......@@ -90,3 +229,144 @@ async function handleSubmit() {
</template></el-dialog
>
</template>
<style scoped lang="scss">
:deep(.copy-dialog .el-dialog__body) {
max-height: calc(100vh - 180px);
overflow: hidden;
}
.copy-resource-panel {
margin-top: 8px;
border-top: 1px solid var(--el-border-color-lighter);
padding-top: 20px;
max-height: calc(100vh - 420px);
overflow-y: auto;
padding-right: 8px;
}
.copy-resource-panel__title {
font-size: 16px;
font-weight: 600;
color: var(--el-text-color-primary);
}
.copy-resource-panel__desc {
margin-top: 4px;
font-size: 13px;
color: var(--el-text-color-secondary);
}
.copy-resource-section {
margin-top: 16px;
padding: 12px;
border: 1px solid var(--el-border-color-lighter);
border-radius: 12px;
background: #fff;
}
.copy-resource-section__header {
display: flex;
align-items: center;
justify-content: space-between;
gap: 16px;
margin-bottom: 10px;
}
.copy-resource-section__title {
display: flex;
align-items: center;
gap: 10px;
font-size: 14px;
font-weight: 600;
color: var(--el-text-color-primary);
}
.copy-resource-section__count {
font-size: 12px;
font-weight: 400;
color: var(--el-text-color-secondary);
}
.copy-resource-list {
display: grid;
gap: 8px;
max-height: 220px;
overflow-y: auto;
padding-right: 6px;
}
.copy-resource-item {
display: flex;
align-items: center;
justify-content: space-between;
width: 100%;
box-sizing: border-box;
margin-right: 0;
min-height: 50px;
padding: 6px 12px;
border: 1px solid var(--el-border-color);
border-radius: 10px;
transition: border-color 0.2s ease, background-color 0.2s ease;
}
.copy-resource-item:hover {
border-color: var(--el-color-primary-light-5);
background: var(--el-color-primary-light-9);
}
.copy-resource-item :deep(.el-checkbox__input) {
flex-shrink: 0;
align-self: center;
}
.copy-resource-item :deep(.el-checkbox__label) {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
flex: 1;
min-width: 0;
padding-left: 12px;
overflow: hidden;
white-space: normal;
line-height: 1.2;
}
.copy-resource-item :deep(.el-tag) {
flex-shrink: 0;
}
.copy-resource-item__content {
min-width: 0;
}
.copy-resource-item__name {
font-size: 13px;
font-weight: 600;
color: var(--el-text-color-primary);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
line-height: 1.2;
}
.copy-resource-item__meta {
margin-top: 1px;
font-size: 11px;
color: var(--el-text-color-secondary);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
line-height: 1.2;
}
.copy-resource-list::-webkit-scrollbar {
width: 6px;
}
.copy-resource-list::-webkit-scrollbar-thumb {
background: var(--el-border-color-darker);
border-radius: 999px;
}
</style>
......@@ -131,3 +131,19 @@ export interface GroupItem {
course_name?: string
class_name?: string
}
export interface ExperimentCopyResourceItem {
id: string
name: string
status: string
status_name: string
size_name?: string
length_name?: string
type?: string
}
export interface ExperimentCopyResources {
cases: ExperimentCopyResourceItem[]
books: ExperimentCopyResourceItem[]
videos: ExperimentCopyResourceItem[]
}
Markdown 格式
0% 或
您添加了 0 人 到此讨论。请谨慎行事。
请先完成此评论的编辑!
请 注册 或者 后发表评论