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

chore: update

上级 43bde8c6
...@@ -42,9 +42,6 @@ ...@@ -42,9 +42,6 @@
"isReactive": true, "isReactive": true,
"isReadonly": true, "isReadonly": true,
"isRef": true, "isRef": true,
"logicAnd": true,
"logicNot": true,
"logicOr": true,
"makeDestructurable": true, "makeDestructurable": true,
"markRaw": true, "markRaw": true,
"nextTick": true, "nextTick": true,
...@@ -105,6 +102,14 @@ ...@@ -105,6 +102,14 @@
"unrefElement": true, "unrefElement": true,
"until": true, "until": true,
"useActiveElement": true, "useActiveElement": true,
"useArrayEvery": true,
"useArrayFilter": true,
"useArrayFind": true,
"useArrayFindIndex": true,
"useArrayJoin": true,
"useArrayMap": true,
"useArrayReduce": true,
"useArraySome": true,
"useAsyncQueue": true, "useAsyncQueue": true,
"useAsyncState": true, "useAsyncState": true,
"useAttrs": true, "useAttrs": true,
...@@ -115,7 +120,6 @@ ...@@ -115,7 +120,6 @@
"useBroadcastChannel": true, "useBroadcastChannel": true,
"useBrowserLocation": true, "useBrowserLocation": true,
"useCached": true, "useCached": true,
"useClamp": true,
"useClipboard": true, "useClipboard": true,
"useColorMode": true, "useColorMode": true,
"useConfirmDialog": true, "useConfirmDialog": true,
...@@ -191,6 +195,7 @@ ...@@ -191,6 +195,7 @@
"usePreferredColorScheme": true, "usePreferredColorScheme": true,
"usePreferredDark": true, "usePreferredDark": true,
"usePreferredLanguages": true, "usePreferredLanguages": true,
"usePreferredReducedMotion": true,
"useRafFn": true, "useRafFn": true,
"useRefHistory": true, "useRefHistory": true,
"useResizeObserver": true, "useResizeObserver": true,
...@@ -210,8 +215,10 @@ ...@@ -210,8 +215,10 @@
"useStorage": true, "useStorage": true,
"useStorageAsync": true, "useStorageAsync": true,
"useStyleTag": true, "useStyleTag": true,
"useSupported": true,
"useSwipe": true, "useSwipe": true,
"useTemplateRefsList": true, "useTemplateRefsList": true,
"useTextDirection": true,
"useTextSelection": true, "useTextSelection": true,
"useTextareaAutosize": true, "useTextareaAutosize": true,
"useThrottle": true, "useThrottle": true,
...@@ -223,6 +230,8 @@ ...@@ -223,6 +230,8 @@
"useTimeoutPoll": true, "useTimeoutPoll": true,
"useTimestamp": true, "useTimestamp": true,
"useTitle": true, "useTitle": true,
"useToNumber": true,
"useToString": true,
"useToggle": true, "useToggle": true,
"useTransition": true, "useTransition": true,
"useUrlSearchParams": true, "useUrlSearchParams": true,
......
...@@ -43,9 +43,6 @@ declare global { ...@@ -43,9 +43,6 @@ declare global {
const isReactive: typeof import('vue')['isReactive'] const isReactive: typeof import('vue')['isReactive']
const isReadonly: typeof import('vue')['isReadonly'] const isReadonly: typeof import('vue')['isReadonly']
const isRef: typeof import('vue')['isRef'] const isRef: typeof import('vue')['isRef']
const logicAnd: typeof import('@vueuse/core')['logicAnd']
const logicNot: typeof import('@vueuse/core')['logicNot']
const logicOr: typeof import('@vueuse/core')['logicOr']
const makeDestructurable: typeof import('@vueuse/core')['makeDestructurable'] const makeDestructurable: typeof import('@vueuse/core')['makeDestructurable']
const markRaw: typeof import('vue')['markRaw'] const markRaw: typeof import('vue')['markRaw']
const nextTick: typeof import('vue')['nextTick'] const nextTick: typeof import('vue')['nextTick']
...@@ -106,6 +103,14 @@ declare global { ...@@ -106,6 +103,14 @@ declare global {
const unrefElement: typeof import('@vueuse/core')['unrefElement'] const unrefElement: typeof import('@vueuse/core')['unrefElement']
const until: typeof import('@vueuse/core')['until'] const until: typeof import('@vueuse/core')['until']
const useActiveElement: typeof import('@vueuse/core')['useActiveElement'] const useActiveElement: typeof import('@vueuse/core')['useActiveElement']
const useArrayEvery: typeof import('@vueuse/core')['useArrayEvery']
const useArrayFilter: typeof import('@vueuse/core')['useArrayFilter']
const useArrayFind: typeof import('@vueuse/core')['useArrayFind']
const useArrayFindIndex: typeof import('@vueuse/core')['useArrayFindIndex']
const useArrayJoin: typeof import('@vueuse/core')['useArrayJoin']
const useArrayMap: typeof import('@vueuse/core')['useArrayMap']
const useArrayReduce: typeof import('@vueuse/core')['useArrayReduce']
const useArraySome: typeof import('@vueuse/core')['useArraySome']
const useAsyncQueue: typeof import('@vueuse/core')['useAsyncQueue'] const useAsyncQueue: typeof import('@vueuse/core')['useAsyncQueue']
const useAsyncState: typeof import('@vueuse/core')['useAsyncState'] const useAsyncState: typeof import('@vueuse/core')['useAsyncState']
const useAttrs: typeof import('vue')['useAttrs'] const useAttrs: typeof import('vue')['useAttrs']
...@@ -116,7 +121,6 @@ declare global { ...@@ -116,7 +121,6 @@ declare global {
const useBroadcastChannel: typeof import('@vueuse/core')['useBroadcastChannel'] const useBroadcastChannel: typeof import('@vueuse/core')['useBroadcastChannel']
const useBrowserLocation: typeof import('@vueuse/core')['useBrowserLocation'] const useBrowserLocation: typeof import('@vueuse/core')['useBrowserLocation']
const useCached: typeof import('@vueuse/core')['useCached'] const useCached: typeof import('@vueuse/core')['useCached']
const useClamp: typeof import('@vueuse/core')['useClamp']
const useClipboard: typeof import('@vueuse/core')['useClipboard'] const useClipboard: typeof import('@vueuse/core')['useClipboard']
const useColorMode: typeof import('@vueuse/core')['useColorMode'] const useColorMode: typeof import('@vueuse/core')['useColorMode']
const useConfirmDialog: typeof import('@vueuse/core')['useConfirmDialog'] const useConfirmDialog: typeof import('@vueuse/core')['useConfirmDialog']
...@@ -192,6 +196,7 @@ declare global { ...@@ -192,6 +196,7 @@ declare global {
const usePreferredColorScheme: typeof import('@vueuse/core')['usePreferredColorScheme'] const usePreferredColorScheme: typeof import('@vueuse/core')['usePreferredColorScheme']
const usePreferredDark: typeof import('@vueuse/core')['usePreferredDark'] const usePreferredDark: typeof import('@vueuse/core')['usePreferredDark']
const usePreferredLanguages: typeof import('@vueuse/core')['usePreferredLanguages'] const usePreferredLanguages: typeof import('@vueuse/core')['usePreferredLanguages']
const usePreferredReducedMotion: typeof import('@vueuse/core')['usePreferredReducedMotion']
const useRafFn: typeof import('@vueuse/core')['useRafFn'] const useRafFn: typeof import('@vueuse/core')['useRafFn']
const useRefHistory: typeof import('@vueuse/core')['useRefHistory'] const useRefHistory: typeof import('@vueuse/core')['useRefHistory']
const useResizeObserver: typeof import('@vueuse/core')['useResizeObserver'] const useResizeObserver: typeof import('@vueuse/core')['useResizeObserver']
...@@ -211,8 +216,10 @@ declare global { ...@@ -211,8 +216,10 @@ declare global {
const useStorage: typeof import('@vueuse/core')['useStorage'] const useStorage: typeof import('@vueuse/core')['useStorage']
const useStorageAsync: typeof import('@vueuse/core')['useStorageAsync'] const useStorageAsync: typeof import('@vueuse/core')['useStorageAsync']
const useStyleTag: typeof import('@vueuse/core')['useStyleTag'] const useStyleTag: typeof import('@vueuse/core')['useStyleTag']
const useSupported: typeof import('@vueuse/core')['useSupported']
const useSwipe: typeof import('@vueuse/core')['useSwipe'] const useSwipe: typeof import('@vueuse/core')['useSwipe']
const useTemplateRefsList: typeof import('@vueuse/core')['useTemplateRefsList'] const useTemplateRefsList: typeof import('@vueuse/core')['useTemplateRefsList']
const useTextDirection: typeof import('@vueuse/core')['useTextDirection']
const useTextSelection: typeof import('@vueuse/core')['useTextSelection'] const useTextSelection: typeof import('@vueuse/core')['useTextSelection']
const useTextareaAutosize: typeof import('@vueuse/core')['useTextareaAutosize'] const useTextareaAutosize: typeof import('@vueuse/core')['useTextareaAutosize']
const useThrottle: typeof import('@vueuse/core')['useThrottle'] const useThrottle: typeof import('@vueuse/core')['useThrottle']
...@@ -224,6 +231,8 @@ declare global { ...@@ -224,6 +231,8 @@ declare global {
const useTimeoutPoll: typeof import('@vueuse/core')['useTimeoutPoll'] const useTimeoutPoll: typeof import('@vueuse/core')['useTimeoutPoll']
const useTimestamp: typeof import('@vueuse/core')['useTimestamp'] const useTimestamp: typeof import('@vueuse/core')['useTimestamp']
const useTitle: typeof import('@vueuse/core')['useTitle'] const useTitle: typeof import('@vueuse/core')['useTitle']
const useToNumber: typeof import('@vueuse/core')['useToNumber']
const useToString: typeof import('@vueuse/core')['useToString']
const useToggle: typeof import('@vueuse/core')['useToggle'] const useToggle: typeof import('@vueuse/core')['useToggle']
const useTransition: typeof import('@vueuse/core')['useTransition'] const useTransition: typeof import('@vueuse/core')['useTransition']
const useUrlSearchParams: typeof import('@vueuse/core')['useUrlSearchParams'] const useUrlSearchParams: typeof import('@vueuse/core')['useUrlSearchParams']
......
差异被折叠。
...@@ -17,10 +17,12 @@ ...@@ -17,10 +17,12 @@
"@vueuse/core": "^9.1.0", "@vueuse/core": "^9.1.0",
"axios": "^0.27.2", "axios": "^0.27.2",
"blueimp-md5": "^2.19.0", "blueimp-md5": "^2.19.0",
"dayjs": "^1.11.5",
"element-plus": "^2.2.12", "element-plus": "^2.2.12",
"lodash-es": "^4.17.21", "lodash-es": "^4.17.21",
"pinia": "^2.0.17", "pinia": "^2.0.17",
"qs": "^6.11.0", "qs": "^6.11.0",
"video.js": "^7.20.2",
"vue": "^3.2.37", "vue": "^3.2.37",
"vue-router": "^4.1.3" "vue-router": "^4.1.3"
}, },
...@@ -29,6 +31,7 @@ ...@@ -29,6 +31,7 @@
"@types/blueimp-md5": "^2.18.0", "@types/blueimp-md5": "^2.18.0",
"@types/node": "^16.11.45", "@types/node": "^16.11.45",
"@types/qs": "^6.9.7", "@types/qs": "^6.9.7",
"@types/video.js": "^7.3.45",
"@vitejs/plugin-vue": "^3.0.1", "@vitejs/plugin-vue": "^3.0.1",
"@vue/eslint-config-typescript": "^11.0.0", "@vue/eslint-config-typescript": "^11.0.0",
"@vue/tsconfig": "^0.1.3", "@vue/tsconfig": "^0.1.3",
......
...@@ -12,7 +12,7 @@ interface Props { ...@@ -12,7 +12,7 @@ interface Props {
} }
const props = withDefaults(defineProps<Props>(), { const props = withDefaults(defineProps<Props>(), {
prefix: 'upload/saas-learn/' prefix: 'upload/saas-lab/'
}) })
const emit = defineEmits(['update:modelValue', 'success']) const emit = defineEmits(['update:modelValue', 'success'])
...@@ -117,7 +117,7 @@ const handlePreview: UploadProps['onPreview'] = uploadFile => { ...@@ -117,7 +117,7 @@ const handlePreview: UploadProps['onPreview'] = uploadFile => {
<el-icon><Plus /></el-icon> <el-icon><Plus /></el-icon>
</template> </template>
<template v-else> <template v-else>
<el-button round>本地文件</el-button> <el-button type="primary" plain round>本地文件</el-button>
</template> </template>
</template> </template>
<div class="avatar-uploader" v-else> <div class="avatar-uploader" v-else>
......
<script lang="ts">
const DEFAULT_OPTIONS = {
controls: true,
autoplay: false,
controlBar: {
pictureInPictureToggle: false
},
// fluid: true,
responsive: true
// playbackRates: [0.5, 1, 1.5, 2]
}
const DEFAULT_EVENTS = [
'abort',
'canplay',
'canplaythrough',
'durationchange',
'emptied',
'ended',
'error',
'loadeddata',
'loadedmetadata',
'pause',
'play',
'playing',
'progress',
'ratechange',
'resize',
'seeked',
'seeking',
'stalled',
'suspend',
'timeupdate',
'volumechange',
'waiting'
]
</script>
<script setup lang="ts">
import videojs from 'video.js'
import type { VideoJsPlayerOptions, VideoJsPlayer } from 'video.js'
import 'video.js/dist/video-js.css'
interface Props {
src?: string | { src: string; type?: string }
options?: VideoJsPlayerOptions
}
const props = defineProps<Props>()
const emit = defineEmits(['ready', ...DEFAULT_EVENTS])
let player = $ref<VideoJsPlayer | null>()
const videoRef = $ref<HTMLVideoElement>()
const videoOptions = $computed<VideoJsPlayerOptions>(() => {
return Object.assign({}, DEFAULT_OPTIONS, props.options)
})
watch(
() => props.src,
src => {
src && changeSrc(src)
},
{ deep: true, immediate: true }
)
// 初始化播放器
function initPlayer() {
if (!videoRef) return
if (player) {
player.dispose()
player = null
}
player = videojs(videoRef, videoOptions, function onPlayerReady() {
props.src && changeSrc(props.src)
// 注册事件
DEFAULT_EVENTS.forEach(eventName => {
this.on(eventName, (...arg) => {
// console.log(eventName, ...arg)
emit(eventName, ...arg)
})
})
emit('ready', this)
})
return player
}
function changeSrc(src: string | { src: string; type?: string }) {
if (!player || !src) return
// if (!player.paused()) {
// console.log(2)
// player.pause()
// }
player.src(src)
// player.load()
// player.play()
}
onMounted(() => {
initPlayer()
})
onUnmounted(() => {
player && player.dispose()
})
</script>
<template>
<video class="video-js vjs-default-skin vjs-big-play-centered" ref="videoRef"></video>
</template>
<style>
.video-js {
font-size: 12px;
}
</style>
...@@ -36,3 +36,23 @@ export function addExperimentDiscuss(data: { experiment_id: string; title: strin ...@@ -36,3 +36,23 @@ export function addExperimentDiscuss(data: { experiment_id: string; title: strin
export function addExperimentDiscussComment(data: { discussion_id: string; content: string }) { export function addExperimentDiscussComment(data: { discussion_id: string; content: string }) {
return httpRequest.post('/api/student/v1/student/experiment-topic/comment', data) return httpRequest.post('/api/student/v1/student/experiment-topic/comment', data)
} }
// 获取实验记录
export function getExperimentRecord(params: { experiment_id: string }) {
return httpRequest.get('/api/student/v1/student/experiment-record/detail', { params })
}
// 截图
export function uploadExperimentPicture(data: { experiment_id: string; pictures: string }) {
return httpRequest.post('/api/student/v1/student/experiment-record/upload-pictures', data)
}
// 上传实验报告
export function uploadExperimentReport(data: { experiment_id: string; file: string }) {
return httpRequest.post('/api/student/v1/student/experiment-record/upload-report', data)
}
// 提交实验记录
export function submitExperimentRecord(data: { experiment_id: string }) {
return httpRequest.post('/api/student/v1/student/experiment-record/submit', data)
}
...@@ -10,7 +10,7 @@ interface Props { ...@@ -10,7 +10,7 @@ interface Props {
experiment_id?: string experiment_id?: string
} }
const props = defineProps<Props>() const props = defineProps<Props>()
const params = reactive({ tag: 3, page: 0, 'per-page': 10 }) const params = reactive({ tag: 1, page: 0, 'per-page': 10 })
let list = $ref<ExperimentDiscussType[]>([]) let list = $ref<ExperimentDiscussType[]>([])
let hasMore = $ref(false) let hasMore = $ref(false)
let isLoading = $ref(false) let isLoading = $ref(false)
...@@ -26,9 +26,7 @@ function fetchInfo() { ...@@ -26,9 +26,7 @@ function fetchInfo() {
isLoading = false isLoading = false
}) })
} }
onMounted(() => { watch(() => props.experiment_id, handleRefetch, { immediate: true })
fetchInfo()
})
const isEmpty = $computed(() => { const isEmpty = $computed(() => {
return !props.experiment_id || !list.length return !props.experiment_id || !list.length
......
<script setup lang="ts"> <script setup lang="ts">
import type { FormInstance, FormRules } from 'element-plus' import type { FormInstance, FormRules } from 'element-plus'
// import { ElMessage } from 'element-plus' import type { ExperimentRecord } from '../types'
import { ElMessage } from 'element-plus'
import dayjs from 'dayjs'
import { uploadExperimentReport } from '../api'
interface Props { interface Props {
data?: any experiment_id: string
} }
const props = defineProps<Props>() const props = defineProps<Props>()
defineEmits<{ const detail = $ref<ExperimentRecord>(inject('detail'))
const emit = defineEmits<{
(e: 'update'): void (e: 'update'): void
(e: 'update:modelValue', visible: boolean): void (e: 'update:modelValue', visible: boolean): void
}>() }>()
const formRef = $ref<FormInstance>() const formRef = $ref<FormInstance>()
const form = reactive({ files: [] }) const form = reactive<any>({ files: [] })
watchEffect(() => { watchEffect(() => {
Object.assign(form, props.data) if (detail?.file) {
form.files = [detail.file]
}
}) })
const rules = ref<FormRules>({ const rules = ref<FormRules>({
files: [{ required: true, message: '请输入话题描述', trigger: 'blur' }] files: [{ required: true, message: '请上传实验报告文件', trigger: 'blur' }]
}) })
// 提交 // 提交
function handleSubmit() { function handleSubmit() {
...@@ -27,16 +34,25 @@ function handleSubmit() { ...@@ -27,16 +34,25 @@ function handleSubmit() {
} }
// 修改 // 修改
const update = () => { const update = () => {
// submitSuggestion(form).then(() => { const [file] = form.files
// ElMessage({ message: '提交成功', type: 'success' }) uploadExperimentReport({
// emit('update') experiment_id: props.experiment_id,
// formRef?.resetFields() file: JSON.stringify({ name: file.name, url: file.url, upload_time: dayjs().format('YYYY-MM-DD HH:mm:ss') })
// }) }).then(() => {
ElMessage({ message: '上传成功', type: 'success' })
emit('update')
emit('update:modelValue', false)
})
} }
</script> </script>
<template> <template>
<el-dialog title="上传实验报告" :close-on-click-modal="false" width="600px"> <el-dialog
title="上传实验报告"
:close-on-click-modal="false"
width="600px"
@update:modelValue="$emit('update:modelValue')"
>
<el-form ref="formRef" :model="form" :rules="rules"> <el-form ref="formRef" :model="form" :rules="rules">
<el-form-item label="实验报告文件" prop="files"> <el-form-item label="实验报告文件" prop="files">
<AppUpload v-model="form.files"> <AppUpload v-model="form.files">
......
<script setup lang="ts"> <script setup lang="ts">
import type { ExperimentRecord } from '../types'
interface Props { interface Props {
experiment_id?: string experiment_id?: string
} }
defineProps<Props>() const props = defineProps<Props>()
const detail = $ref<ExperimentRecord>(inject('detail'))
const isEmpty = $computed(() => {
return !props.experiment_id || !detail
})
</script> </script>
<template> <template>
<div>result</div> <el-empty description="暂无数据" v-if="isEmpty" />
<template v-else>
<h2>我的成绩</h2>
<h2>实验过程</h2>
<ul class="picture-list">
<li v-for="item in detail.pictures" :key="item.url">
<img :src="item.url" />
<p>截图时间:{{ item.upload_time }}</p>
</li>
</ul>
</template>
</template> </template>
<style lang="scss" scoped></style> <style lang="scss" scoped>
.picture-list {
li {
position: relative;
height: 200px;
margin: 20px 0;
img {
width: 100%;
height: 100%;
object-fit: cover;
}
p {
position: absolute;
left: 0;
right: 0;
bottom: 0;
padding: 0 10px;
font-size: 14px;
color: #fff;
line-height: 30px;
text-align: right;
background-color: rgba(0, 0, 0, 0.5);
}
}
}
</style>
<script setup lang="ts"> <script setup lang="ts">
import type { ExperimentVideoType, PlayInfo } from '../types' import type { ExperimentVideoType, PlayInfo } from '../types'
import AppVideoPlayer from '@/components/base/AppVideoPlayer.vue'
import { getExperimentVideoPlayInfo } from '../api' import { getExperimentVideoPlayInfo } from '../api'
interface Props { interface Props {
data: ExperimentVideoType data: ExperimentVideoType
...@@ -11,7 +12,9 @@ function fetchInfo() { ...@@ -11,7 +12,9 @@ function fetchInfo() {
playList = res.data.play_info_list playList = res.data.play_info_list
}) })
} }
console.log(playList) const playUrl = $computed(() => {
return playList[0]?.PlayURL || ''
})
onMounted(() => { onMounted(() => {
fetchInfo() fetchInfo()
}) })
...@@ -19,16 +22,22 @@ onMounted(() => { ...@@ -19,16 +22,22 @@ onMounted(() => {
<template> <template>
<div class="video-item"> <div class="video-item">
<h2>{{ data.name }}</h2> <h2>{{ data.name }}</h2>
<img :src="data.cover" /> <!-- <img :src="data.cover" /> -->
<AppVideoPlayer
:options="{ sources: [{ src: playUrl, type: 'application/x-mpegURL' }] }"
style="width: 100%"
v-if="playUrl"
></AppVideoPlayer>
</div> </div>
</template> </template>
<style lang="scss" scoped> <style lang="scss" scoped>
.video-item { .video-item {
h2 { h2 {
font-size: 16px; font-size: 14px;
font-weight: 500;
color: #333; color: #333;
margin-bottom: 10px; padding: 10px 0;
text-align: center; text-align: center;
} }
img { img {
......
...@@ -83,3 +83,45 @@ export interface UserType { ...@@ -83,3 +83,45 @@ export interface UserType {
real_name: string real_name: string
username: string username: string
} }
export interface ExperimentRecord {
experiment_id: string
student_id: string
commit_time: string
pictures: ExperimentRecordFile[]
file: ExperimentRecordFile
checker_id: string
check_time: string
score_details: string
score: string
status: 0 | 1 | 2
checker_user: UserType
experiment: {
id: string
name: string
score: number
length: number
}
course: CourseType
student: ExperimentRecordStudent
}
export interface ExperimentRecordFile {
url: string
name: string
upload_time: string
}
export interface ExperimentRecordStudent {
id: string
name: string
specialty: {
id: string
name: string
}
classes: [
{
id: string
name: string
}
]
}
<script setup lang="ts"> <script setup lang="ts">
import type { CourseType } from '../types' import type { CourseType, ExperimentRecord } from '../types'
import { HomeFilled, Select, UploadFilled, FullScreen } from '@element-plus/icons-vue' import { HomeFilled, Select, UploadFilled, FullScreen } from '@element-plus/icons-vue'
import { useGetCourseList } from '../composables/useGetCourseList' import { useGetCourseList } from '../composables/useGetCourseList'
import { upload } from '@/utils/upload'
import { getExperimentRecord, uploadExperimentPicture, submitExperimentRecord } from '../api'
import dayjs from 'dayjs'
const Book = defineAsyncComponent(() => import('../components/Book.vue')) const Book = defineAsyncComponent(() => import('../components/Book.vue'))
const Video = defineAsyncComponent(() => import('../components/Video.vue')) const Video = defineAsyncComponent(() => import('../components/Video.vue'))
...@@ -20,6 +23,19 @@ const { courses } = useGetCourseList() ...@@ -20,6 +23,19 @@ const { courses } = useGetCourseList()
const experimentList = $computed(() => { const experimentList = $computed(() => {
return form.course?.experiments || [] return form.course?.experiments || []
}) })
let detail = $ref<ExperimentRecord>()
provide('detail', $$(detail))
function fetchInfo() {
if (!form.experiment_id) return
getExperimentRecord({ experiment_id: form.experiment_id }).then(res => {
detail = Array.isArray(res.data.data) ? undefined : res.data.data
})
}
watchEffect(() => {
fetchInfo()
})
// 右侧 // 右侧
const LAB_URL = import.meta.env.VITE_LAB_URL const LAB_URL = import.meta.env.VITE_LAB_URL
let iframeKey = $ref(Date.now()) let iframeKey = $ref(Date.now())
...@@ -30,7 +46,13 @@ function handleBackHome() { ...@@ -30,7 +46,13 @@ function handleBackHome() {
const reportDialogVisible = $ref(false) const reportDialogVisible = $ref(false)
// 是否已经提交 // 是否已经提交
const submitted = $ref(false) const submitted = $computed(() => {
return detail ? detail.status !== 0 : false
})
// 是否禁用
const disabled = $computed(() => {
return submitted || !form.experiment_id
})
const iframeRef = $ref<HTMLIFrameElement>() const iframeRef = $ref<HTMLIFrameElement>()
let screenshotLoading = $ref(false) let screenshotLoading = $ref(false)
...@@ -48,11 +70,9 @@ function handleCapture() { ...@@ -48,11 +70,9 @@ function handleCapture() {
function handleCaptureCallback(event: MessageEvent) { function handleCaptureCallback(event: MessageEvent) {
const { data } = event const { data } = event
if (data.action === 'screenshot' && data.timestamp === screenshotTimestamp) { if (data.action === 'screenshot' && data.timestamp === screenshotTimestamp) {
console.log(data) upload(data.blob).then(url => {
const img = new Image() uploadPicture(url)
img.src = data.dataURL })
document.body.appendChild(img)
screenshotLoading = false
} }
} }
onMounted(() => { onMounted(() => {
...@@ -61,6 +81,24 @@ onMounted(() => { ...@@ -61,6 +81,24 @@ onMounted(() => {
onUnmounted(() => { onUnmounted(() => {
window.removeEventListener('message', handleCaptureCallback, false) window.removeEventListener('message', handleCaptureCallback, false)
}) })
// 上传截图
function uploadPicture(url: string) {
const pictures = detail?.pictures || []
pictures.unshift({ url, name: 'screenshot.png', upload_time: dayjs().format('YYYY-MM-DD HH:mm:ss') })
uploadExperimentPicture({ experiment_id: form.experiment_id, pictures: JSON.stringify(pictures) }).then(() => {
screenshotLoading = false
if (!detail) {
fetchInfo()
}
})
}
// 提交实验
function handleSubmit() {
submitExperimentRecord({ experiment_id: form.experiment_id }).then(() => {
fetchInfo()
})
}
</script> </script>
<template> <template>
...@@ -100,14 +138,14 @@ onUnmounted(() => { ...@@ -100,14 +138,14 @@ onUnmounted(() => {
>返回首页</el-button >返回首页</el-button
> >
<div> <div>
<el-button type="primary" :icon="Select" :disabled="submitted">提交</el-button> <el-button type="primary" :icon="Select" :disabled="disabled" @click="handleSubmit">提交</el-button>
<el-button type="primary" :icon="UploadFilled" :disabled="submitted" @click="reportDialogVisible = true" <el-button type="primary" :icon="UploadFilled" :disabled="disabled" @click="reportDialogVisible = true"
>上传报告</el-button >上传报告</el-button
> >
<el-button <el-button
type="primary" type="primary"
:icon="FullScreen" :icon="FullScreen"
:disabled="submitted" :disabled="disabled"
:loading="screenshotLoading" :loading="screenshotLoading"
@click="handleCapture" @click="handleCapture"
>截图</el-button >截图</el-button
...@@ -122,7 +160,12 @@ onUnmounted(() => { ...@@ -122,7 +160,12 @@ onUnmounted(() => {
</div> </div>
</section> </section>
<!-- 上传报告 --> <!-- 上传报告 -->
<ReportDialog v-model="reportDialogVisible" v-if="reportDialogVisible"></ReportDialog> <ReportDialog
v-model="reportDialogVisible"
:experiment_id="form.experiment_id"
@update="fetchInfo"
v-if="reportDialogVisible && form.experiment_id"
></ReportDialog>
</template> </template>
<style lang="scss" scoped> <style lang="scss" scoped>
......
import md5 from 'blueimp-md5'
import { getSignature, uploadFile } from '@/api/base'
export async function upload(blob: Blob) {
const key = 'upload/saas-lab/' + md5(new Date().getTime() + Math.random().toString(36).slice(-8)) + '.png'
const response: any = await getSignature()
const params = {
key,
OSSAccessKeyId: response.accessid,
policy: response.policy,
signature: response.signature,
success_action_status: '200',
file: blob,
url: `${response.host}/${key}`
}
await uploadFile(params)
return params.url
}
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论