提交 a5393832 authored 作者: lihuihui's avatar lihuihui

课程开发

上级 4b439fa9
...@@ -3,6 +3,13 @@ import Editor from '@tinymce/tinymce-vue' ...@@ -3,6 +3,13 @@ import Editor from '@tinymce/tinymce-vue'
import md5 from 'blueimp-md5' import md5 from 'blueimp-md5'
import { getSignature, uploadFile } from '@/api/base' import { getSignature, uploadFile } from '@/api/base'
const props = defineProps({
height: {
type: Number,
default: 600
}
})
const ImageUploadHandler = (blobInfo: any) => const ImageUploadHandler = (blobInfo: any) =>
new Promise((resolve, reject) => { new Promise((resolve, reject) => {
const file = blobInfo.blob() const file = blobInfo.blob()
...@@ -35,7 +42,7 @@ const ImageUploadHandler = (blobInfo: any) => ...@@ -35,7 +42,7 @@ const ImageUploadHandler = (blobInfo: any) =>
const init = { const init = {
language: 'zh-Hans', language: 'zh-Hans',
height: 600, height: props.height,
menubar: false, menubar: false,
statusbar: false, statusbar: false,
plugins: 'table charmap fullscreen lists link code preview quickbars', plugins: 'table charmap fullscreen lists link code preview quickbars',
......
import httpRequest from '@/utils/axios'
// 讲师搜索
export function searchLecturer(params: { name: string; 'per-page'?: string; }) {
return httpRequest.get('/api/resource/v1/course/course/search-lecturer', { params })
}
// 直播搜索
export function searchLive(params: { name: string; 'per-page'?: string; }) {
return httpRequest.get('/api/resource/v1/course/course/search-live', { params })
}
// 试卷搜索
export function searchExam(data: { nonce: string; timestamp: string; signature: string, paper_title: string; limit: string; is_all: number; project_prefix: string; }) {
return httpRequest.post('/api/qbs/api/v1/question-papers/search', data)
}
// 新建课件
export function createCourse(data: any) {
return httpRequest.post('/api/resource/v1/course/course/create', data)
}
// 获取封面列表
export function getCoverList() {
return httpRequest.get('/api/resource/v1/util/get-cover-list')
}
// 获取视频详情
export function getVideoDetails(params: { id: string }) {
return httpRequest.get('/api/resource/v1/resource/video/view', { params })
}
// 更新视频
export function updateVideo(data: { id: string; name: string; classification: string; knowledge_points?: string; cover?: string }) {
return httpRequest.post('/api/resource/v1/resource/video/update', data)
}
// 部门共享
export function setDepartment(data: { id: string }) {
return httpRequest.post('/api/resource/v1/resource/video/set-department', data)
}
// 平台共享
export function setPlatform(data: { id: string }) {
return httpRequest.post('/api/resource/v1/resource/video/set-platform', data)
}
// 上下线
export function setStatus(data: { id: string }) {
return httpRequest.post('/api/resource/v1/resource/video/set-status', data)
}
// 更改负责人
export function setBelong(data: { id: string; belong_operator: string }) {
return httpRequest.post('/api/resource/v1/resource/video/set-belong', data)
}
// 获取视频详情
export function getVideoPpt(params: { id: string }) {
return httpRequest.get('/api/resource/v1/resource/video/get-ppt', { params })
}
// 创建课件
export function createPpt(data: { id: string; ppts: string }) {
return httpRequest.post('/api/resource/v1/resource/video/create-ppt', data)
}
// 编辑课件
export function updatePpt(data: { id: string; ppt_id: string, name: string, point: string, url: string }) {
return httpRequest.post('/api/resource/v1/resource/video/update-ppt', data)
}
// 编辑课件
export function deletePpt(data: { id: string; ppt_id: string }) {
return httpRequest.post('/api/resource/v1/resource/video/delete-ppt', data)
}
<script setup lang="ts">
import { getCoverList } from '../api'
import { PictureFilled, ArrowLeftBold, ArrowRightBold } from '@element-plus/icons-vue'
import UploadImg from './UploadImg.vue'
const emit = defineEmits<{
(e: 'update:dialogVisible', dialogVisible: false): void
(e: 'change', url: string): void
}>()
defineProps({
dialogVisible: {
type: Boolean,
required: true
}
})
// 关闭弹框
const handleCancel = () => {
emit('update:dialogVisible', false)
}
// 选择封面
const changeCover = () => {
emit('update:dialogVisible', false)
emit('change', courseCover.value)
}
// 封面
const courseCover = ref('')
// 课程封面图轮播
let swiperCovers: [{ id: string; url: string }[]] = $ref([[]])
// 获取swiper 自定义左右切换按钮
let swiper = ref()
const swiperChange = (type?: string) => {
type === 'prev' ? swiper.value.prev() : swiper.value.next()
}
// 获取封面
getCoverList().then(res => {
const filtersData = res.data.list.filter((i: any) => i.type === '1')
let index = 0
while (index < filtersData.length) {
swiperCovers.push(filtersData.slice(index, (index += 8)))
}
swiperCovers.forEach((item: any, index: number) => {
if (item.length === 0) {
swiperCovers.splice(index, 1)
}
})
console.log(swiperCovers, '123')
})
let isSwiperBtn = $ref(0)
const watchSwiper = (index: number) => {
if (!index) {
isSwiperBtn = 0
} else {
swiperCovers.length === index + 1 ? (isSwiperBtn = 1) : (isSwiperBtn = 2)
}
}
const swiperItemHandle = (url: string) => {
courseCover.value = url
}
</script>
<template>
<div class="add-cover">
<el-dialog :model-value="dialogVisible" :before-close="handleCancel" title="选择封面" width="770px">
<div class="video-cover">
<div class="img-box">
<el-icon :size="50" color="#ccc" v-if="courseCover == ''">
<PictureFilled></PictureFilled>
</el-icon>
<div v-else class="cover-img" :style="`background-image:url(${courseCover})`"></div>
</div>
<div class="video-cover_right">
<UploadImg accept=".jpg,.jpeg,.gif,.png" v-model="courseCover"></UploadImg>
<div class="list">
<div class="item">
<img
src="https://webapp-pub.oss-cn-beijing.aliyuncs.com/center_resource/upload-video-icon.png"
class="icon"
/>
<div class="text">该图片作为视频的封面图,用于视频封面显示</div>
</div>
<div class="item">
<img
src="https://webapp-pub.oss-cn-beijing.aliyuncs.com/center_resource/upload-video-icon.png"
class="icon"
/>
<div class="text">支持jpg/jpeg/gif/png格式</div>
</div>
<div class="item">
<img
src="https://webapp-pub.oss-cn-beijing.aliyuncs.com/center_resource/upload-video-icon.png"
class="icon"
/>
<div class="text">支持小于4M,最好宽240*高175*及以上尺寸</div>
</div>
<div class="item">
<img
src="https://webapp-pub.oss-cn-beijing.aliyuncs.com/center_resource/upload-video-icon.png"
class="icon"
/>
<div class="text">可以自己上传图片,也可以从下面这些图片中选择使用</div>
</div>
</div>
</div>
</div>
<div class="swiper-box">
<div class="block text-center" v-if="swiperCovers.length">
<el-carousel
@change="watchSwiper"
height="202px"
:autoplay="false"
arrow="never"
trigger="click"
ref="swiper"
>
<el-carousel-item v-for="(item, index) in swiperCovers" :key="index" class="cover-list">
<div
@click="swiperItemHandle(cItem.url)"
:key="cItem.id"
v-for="cItem in item"
:style="`background-image:url(${cItem.url})`"
class="cover-list_item"
></div>
</el-carousel-item>
</el-carousel>
</div>
<el-icon v-if="isSwiperBtn != 0" class="arrow left" @click="swiperChange('prev')"><ArrowLeftBold /></el-icon>
<el-icon v-if="isSwiperBtn != 1" class="arrow right" @click="swiperChange('next')"><ArrowRightBold /></el-icon>
</div>
<template #footer>
<span class="dialog-footer">
<el-button @click="handleCancel">取消</el-button>
<el-button type="primary" @click="changeCover">确认</el-button>
</span>
</template>
</el-dialog>
</div>
</template>
<style lang="scss">
.video-cover {
display: flex;
.img-box {
width: 208px;
height: 130px;
border-radius: 4px;
background: #f7f7f7;
font-size: 20px;
display: flex;
align-items: center;
justify-content: center;
// img {
// width: 100%;
// // height: 100%;
// display: block;
// }
.cover-img {
width: 208px;
height: 130px;
background-size: cover;
}
}
.video-cover_right {
margin-left: 20px;
.list {
.item {
display: flex;
align-items: center;
margin-top: 12px;
img {
width: 9px;
display: block;
margin-right: 5px;
}
.text {
font-size: 12px;
color: #999999;
line-height: 100%;
}
}
}
}
}
.swiper-box {
min-width: 660px;
margin-top: 20px;
padding: 0 40px;
position: relative;
.arrow {
position: absolute;
top: 50%;
transform: translateY(-50%);
font-size: 30px;
color: #d5d5d5;
cursor: pointer;
&.left {
left: 0;
}
&.right {
right: 10px;
}
}
.cover-list {
display: flex;
flex-wrap: wrap;
.cover-list_item {
display: block;
width: 155px;
height: 96px;
margin: 0 10px 10px 0;
border-radius: 4px;
cursor: pointer;
background-size: cover;
}
}
}
:deep(.el-carousel__indicators--horizontal) {
display: none;
}
</style>
<script setup lang="ts">
import { searchExam } from '../api'
const emit = defineEmits(['change'])
let lecturerList: any = ref([])
const listOptions = computed(() => {
return {
columns: [
// { label: '', slots: 'table-avatar', align: 'center' },
{ label: '试卷名称', prop: 'paper_title', align: 'center' },
{
label: '组卷模式',
prop: 'paper_type',
align: 'center',
computed({ row }: any) {
const json = { 1: '选题组卷', 2: '自动组卷' }
return json[parseInt(row.paper_type)]
}
},
{
label: '试卷用途',
prop: 'paper_uses',
align: 'center',
computed({ row }: any) {
const json = { 1: '考试', 2: '课后作业', 3: '课程测试' }
return json[parseInt(row.paper_uses)]
}
},
{ label: '总分', prop: 'paper_total_score', align: 'center' },
{ label: '及格分数', prop: 'pass_score', align: 'center' },
{ label: '操作', slots: 'table-operate', align: 'center' }
],
data: lecturerList.value
}
})
// 试卷弹窗
const dialogVisible = ref(false)
interface ListItem {
paper_title: string
id: string
}
const options = ref<ListItem[]>([])
const loading = ref(false)
// 所有被搜索出来的值
const allLecturers: any = ref([])
// 考试选中的值
const lecturerValue = ref([])
// 远程搜索
const remoteMethod = (query: string) => {
if (query) {
loading.value = true
searchExam({
paper_title: query,
limit: '100',
is_all: 1,
project_prefix: 'x1',
signature: 'UG7wBenexQhiuD2wpCwuxkU0jqcj006d',
timestamp: '32322323',
nonce: '3232'
}).then((res: any) => {
loading.value = false
options.value = res.data.data.filter((item: any) => {
return !lecturerValue.value.find((id: string) => id === item.id)
})
options.value.forEach((item: any) => {
const findItem = allLecturers.value.find((cItem: any) => cItem.id === item.id)
if (!findItem) {
allLecturers.value.push(item)
}
})
})
} else {
options.value = []
}
}
const changeValue = () => {
lecturerList.value = lecturerValue.value.reduce((a: any, b: any) => {
a.push(allLecturers.value.find((item: any) => item.id === b))
return a
}, [])
}
const changeLecturer = () => {
changeValue()
emit('change', lecturerValue.value)
dialogVisible.value = false
}
const dialogShow = () => {
options.value = []
dialogVisible.value = true
}
// 删除考试
const removeLectuter = (id: string) => {
const index = lecturerValue.value.findIndex((ids: string) => ids === id)
lecturerValue.value.splice(index, 1)
changeValue()
emit('change', lecturerValue.value)
}
</script>
<template>
<div>
<el-button type="primary" @click="dialogShow">添加课程考试</el-button>
<AppList v-bind="listOptions" ref="appList">
<template #table-avatar="{ row }">
<img :src="row.avatar" style="width: 50px; height: 50px; display: block; margin: 0 auto" />
</template>
<template #table-summarize="{ row }">
<div v-html="row.summarize"></div>
</template>
<template #table-operate="{ row }">
<el-button plain @click="removeLectuter(row.id)">删除</el-button>
</template>
</AppList>
<el-dialog v-model="dialogVisible" width="400px" title="添加考试">
<div style="display: flex; justify-content: center">
<el-select
v-model="lecturerValue"
multiple
filterable
remote
reserve-keyword
placeholder="请输入试卷名称"
:remote-method="remoteMethod"
:loading="loading"
>
<el-option v-for="item in options" :key="item.id" :label="item.paper_title" :value="item.id" />
</el-select>
</div>
<template #footer>
<span class="dialog-footer">
<el-button @click="dialogVisible = false">取消</el-button>
<el-button type="primary" @click="changeLecturer">确认</el-button>
</span>
</template>
</el-dialog>
</div>
</template>
<style lang="scss"></style>
<script setup lang="ts">
import { searchLecturer } from '../api'
const emit = defineEmits(['change'])
let lecturerList: any = ref([])
const listOptions = computed(() => {
return {
columns: [
{ label: '头像', slots: 'table-avatar', align: 'center' },
{ label: '姓名', prop: 'name', align: 'center' },
{ label: '职位', prop: 'title', align: 'center' },
{ label: '机构', prop: 'office', align: 'center' },
{ label: '简介', slots: 'table-summarize', align: 'center' },
{ label: '操作', slots: 'table-operate', align: 'center' }
],
data: lecturerList.value
}
})
// 讲师弹窗
const dialogVisible = ref(false)
interface ListItem {
name: string
id: string
}
const options = ref<ListItem[]>([])
const loading = ref(false)
// 所有被搜索出来的值
const allLecturers: any = ref([])
// 讲师选中的值
const lecturerValue = ref([])
// 远程搜索
const remoteMethod = (query: string) => {
if (query) {
loading.value = true
searchLecturer({ name: query, 'per-page': '100' }).then((res: any) => {
loading.value = false
options.value = res.data.list.filter((item: any) => {
return !lecturerValue.value.find((id: string) => id === item.id)
})
options.value.forEach((item: any) => {
const findItem = allLecturers.value.find((cItem: any) => cItem.id === item.id)
if (!findItem) {
allLecturers.value.push(item)
}
})
})
} else {
options.value = []
}
}
const changeValue = () => {
lecturerList.value = lecturerValue.value.reduce((a: any, b: any) => {
a.push(allLecturers.value.find((item: any) => item.id === b))
return a
}, [])
}
const changeLecturer = () => {
changeValue()
emit('change', lecturerValue.value)
dialogVisible.value = false
}
const dialogShow = () => {
options.value = []
dialogVisible.value = true
}
// 删除讲师
const removeLectuter = (id: string) => {
const index = lecturerValue.value.findIndex((ids: string) => ids === id)
lecturerValue.value.splice(index, 1)
changeValue()
emit('change', lecturerValue.value)
}
</script>
<template>
<div>
<el-button type="primary" @click="dialogShow">添加讲师</el-button>
<AppList v-bind="listOptions" ref="appList">
<template #table-avatar="{ row }">
<img :src="row.avatar" style="width: 50px; height: 50px; display: block; margin: 0 auto" />
</template>
<template #table-summarize="{ row }">
<div v-html="row.summarize"></div>
</template>
<template #table-operate="{ row }">
<el-button plain @click="removeLectuter(row.id)">删除</el-button>
</template>
</AppList>
<el-dialog v-model="dialogVisible" width="400px" title="添加讲师">
<div style="display: flex; justify-content: center">
<el-select
v-model="lecturerValue"
multiple
filterable
remote
reserve-keyword
placeholder="请输入讲师姓名"
:remote-method="remoteMethod"
:loading="loading"
>
<el-option v-for="item in options" :key="item.id" :label="item.name" :value="item.id" />
</el-select>
</div>
<template #footer>
<span class="dialog-footer">
<el-button @click="dialogVisible = false">取消</el-button>
<el-button type="primary" @click="changeLecturer">确认</el-button>
</span>
</template>
</el-dialog>
</div>
</template>
<style lang="scss"></style>
<script setup lang="ts">
import { searchLive } from '../api'
const emit = defineEmits(['change'])
let lecturerList: any = ref([])
const listOptions = computed(() => {
return {
columns: [
{ label: '会议code', prop: 'meeting_code', align: 'center' },
{ label: '主题', prop: 'subject', align: 'center' },
{ label: '开始时间', prop: 'start_time', align: 'center' },
{ label: '结束时间', prop: 'end_time', align: 'center' },
{ label: '操作', slots: 'table-operate', align: 'center' }
],
data: lecturerList.value
}
})
// 直播弹窗
const dialogVisible = ref(false)
interface ListItem {
subject: string
id: string
}
const options = ref<ListItem[]>([])
const loading = ref(false)
// 所有被搜索出来的值
const allLecturers: any = ref([])
// 直播选中的值
const lecturerValue = ref([])
// 远程搜索
const remoteMethod = (query: string) => {
if (query) {
loading.value = true
searchLive({ name: query, 'per-page': '100' }).then((res: any) => {
loading.value = false
options.value = res.data.list.filter((item: any) => {
return !lecturerValue.value.find((id: string) => id === item.id)
})
options.value.forEach((item: any) => {
const findItem = allLecturers.value.find((cItem: any) => cItem.id === item.id)
if (!findItem) {
allLecturers.value.push(item)
}
})
})
} else {
options.value = []
}
}
const changeValue = () => {
lecturerList.value = lecturerValue.value.reduce((a: any, b: any) => {
a.push(allLecturers.value.find((item: any) => item.id === b))
return a
}, [])
}
const changeLecturer = () => {
changeValue()
emit('change', lecturerValue.value)
dialogVisible.value = false
}
const dialogShow = () => {
options.value = []
dialogVisible.value = true
}
// 删除直播
const removeLectuter = (id: string) => {
const index = lecturerValue.value.findIndex((ids: string) => ids === id)
lecturerValue.value.splice(index, 1)
changeValue()
emit('change', lecturerValue.value)
}
</script>
<template>
<div>
<el-button type="primary" @click="dialogShow">添加周期性直播</el-button>
<AppList v-bind="listOptions" ref="appList">
<template #table-avatar="{ row }">
<img :src="row.avatar" style="width: 50px; height: 50px; display: block; margin: 0 auto" />
</template>
<template #table-summarize="{ row }">
<div v-html="row.summarize"></div>
</template>
<template #table-operate="{ row }">
<el-button plain @click="removeLectuter(row.id)">删除</el-button>
</template>
</AppList>
<el-dialog v-model="dialogVisible" width="400px" title="添加直播">
<div style="display: flex; justify-content: center">
<el-select
v-model="lecturerValue"
multiple
filterable
remote
reserve-keyword
placeholder="会议主题或者会议code"
:remote-method="remoteMethod"
:loading="loading"
>
<el-option v-for="item in options" :key="item.id" :label="item.subject" :value="item.id" />
</el-select>
</div>
<template #footer>
<span class="dialog-footer">
<el-button @click="dialogVisible = false">取消</el-button>
<el-button type="primary" @click="changeLecturer">确认</el-button>
</span>
</template>
</el-dialog>
</div>
</template>
<style lang="scss"></style>
<script setup lang="ts">
import { createCourse } from '../api'
import { useGetCategoryList } from '@/composables/useGetCategoryList'
import { useMapStore } from '@/stores/map'
import VEditor from '@/components/tinymce/Index.vue'
// 课程封面
import AddCourseCover from './AddCourseCover.vue'
// 添加讲师
import AddLecturer from './AddLecturer.vue'
// 添加试卷
import AddExam from './AddExam.vue'
// 添加直播
import AddLive from './AddLive.vue'
const store = useMapStore()
interface ICourseList {
label: string,
value: string
}
// 课程类型
const courseType = computed<ICourseList[]>(() => {
return store.getMapValuesByKey('system_online_type')
})
// 课程类型
const electiveType = computed<ICourseList[]>(() => {
return store.getMapValuesByKey('system_elective_type')
})
// 下拉选择tree 视频分类
let { list: selectTree } = useGetCategoryList()
const defaultProps = {
children: 'children',
label: 'category_name',
value: 'id'
}
// form
const form = reactive<Record<string, any>>({
source: '2',
cover: 'https://webapp-pub.oss-cn-beijing.aliyuncs.com/center_resource/course-cover.png'
})
// 表单验证
const rules = {
name: [{ required: true, message: '请输入标题' }],
online_type: [{ required: true, message: '请选择类型' }],
elective_type: [{ required: true, message: '请选择类型' }],
classification: [{ required: true, message: '请选择分类' }],
credit: [{ required: true, message: '请输入学分' }],
lecturer_id: [{ required: true, message: '' }],
exam_id: [{ required: true, message: '' }],
live_id: [{ required: true, message: '' }]
}
// 课程封面dialog
const dialogVisible = ref(false)
// 选择封面
const coverChange = (url: string) => {
form.cover = url
}
// 选择讲师
const changeLecturer = (data: any) => {
form.lecturer_id = data.toString()
}
// 选择考试
const changeExam = (data: any) => {
form.exam_id = data.toString()
}
// 选择直播
const changeLive = (data: any) => {
form.live_id = data.toString()
}
// 第一步保存后的id
const stepOneId = ref()
// 新建课件
const createCourseForm = () => {
createCourse(form).then((res: any) => {
if (res.code === 0) {
// 操作第二部
stepOneId.value = res.data.id
}
})
}
</script>
<template>
<!-- 基本信息 -->
<div class="update-course-info">
<div style="display: flex">
<div class="cover" :style="`background-image: url(${form.cover})`">
<div class="upload-btn" @click="dialogVisible = true">添加封面</div>
</div>
<el-form ref="ruleFormRef" :model="form" :rules="rules" style="width: 50%">
<el-form-item label="课程名称:" prop="name">
<el-input v-model="form.name" maxlength="40" />
</el-form-item>
<el-form-item label="课程类型:" prop="online_type">
<el-radio-group v-model="form.online_type">
<el-radio :key="item.value" :label="item.value" v-for="item in courseType">{{ item.label }}</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="选课类型:" prop="elective_type">
<el-radio-group v-model="form.elective_type">
<el-radio :key="item.value" :label="item.value" v-for="item in electiveType">{{ item.label }}</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="课程分类:" prop="classification">
<el-tree-select
:render-after-expand="false"
:props="defaultProps"
style="width: 100%"
v-model="form.classification"
:data="selectTree"
/>
</el-form-item>
<el-form-item label="课程学分:" prop="credit">
<el-input v-model="form.credit" maxlength="40" />
</el-form-item>
</el-form>
</div>
<el-form ref="ruleFormRef" label-position="top" :model="form" :rules="rules" style="width: 100%; margin-top: 30px">
<el-form-item label="课程讲师:" prop="lecturer_id">
<!-- 添加讲师 -->
<AddLecturer @change="changeLecturer" style="width: 100%"></AddLecturer>
</el-form-item>
<el-form-item label="课程简介与描述:">
<v-editor v-model="form.represent" class="editor" :height="200"></v-editor>
</el-form-item>
<el-form-item label="课程小论文:">
<v-editor v-model="form.essay" class="editor" :height="200"></v-editor>
</el-form-item>
<el-form-item label="前期准备与预备知识:">
<v-editor v-model="form.previous_preparation" class="editor" :height="200"></v-editor>
</el-form-item>
<el-form-item label="授课目标:">
<v-editor v-model="form.target" class="editor" :height="200"></v-editor>
</el-form-item>
<el-form-item label="课程考试:" prop="exam_id">
<!-- 添加考试 -->
<AddExam @change="changeExam" style="width: 100%"></AddExam>
</el-form-item>
<el-form-item label="课程考试:" prop="live_id">
<!-- 添加直播 -->
<AddLive @change="changeLive" style="width: 100%"></AddLive>
</el-form-item>
</el-form>
</div>
<div class="btn-box" style="display: flex;justify-content: center;">
<el-button type="primary" @click="createCourseForm">下一步</el-button>
</div>
<!-- 添加封面 -->
<AddCourseCover @change="coverChange" v-model:dialogVisible="dialogVisible"></AddCourseCover>
</template>
<style lang="scss">
.update-course-info {
.cover {
width: 430px;
height: 270px;
background-size: cover;
background-image: url('https://iph.href.lu/430x270');
margin-right: 20px;
position: relative;
.upload-btn {
position: absolute;
bottom: 10px;
right: 10px;
padding: 5px 10px;
font-size: 14px;
border-radius: 25px;
text-align: center;
background: rgba(0, 0, 0, 0.3);
color: #fff;
cursor: pointer;
}
}
}
</style>
<script lang="ts" setup>
import { ElMessage } from 'element-plus'
// import { Plus } from '@element-plus/icons-vue'
import type { UploadProps, UploadUserFile } from 'element-plus'
import md5 from 'blueimp-md5'
import { getSignature } from '@/api/base'
const props = withDefaults(defineProps<{ modelValue: string | []; prefix?: string }>(), {
prefix: 'upload/admin/'
})
const emit = defineEmits(['update:modelValue'])
const uploadData = ref()
const fileList = ref<UploadUserFile[]>([])
watch(
() => props.modelValue,
value => {
fileList.value = Array.isArray(value) ? [...value] : []
}
)
const showFileList = computed(() => {
return Array.isArray(props.modelValue)
})
// 上传之前
const handleBeforeUpload = async (file: any) => {
const fileName = file.name
const key = props.prefix + md5(fileName + new Date().getTime()) + fileName.substr(fileName.lastIndexOf('.'))
const response: Record<string, any> = await getSignature()
uploadData.value = {
key,
OSSAccessKeyId: response.accessid,
policy: response.policy,
signature: response.signature,
success_action_status: '200',
url: `${response.host}/${key}`
}
file.url = `${response.host}/${key}`
}
// 上传成功
const handleSuccess = (response: any, file: any, files: any) => {
if (!files.every((item: any) => item.status === 'success')) return
if (showFileList.value) {
emit(
'update:modelValue',
files.map((item: any) => {
return { name: item.name, url: item.url || item.raw.url }
})
)
} else {
emit('update:modelValue', file.raw.url)
}
}
// 上传限制
const handleExceed: UploadProps['onExceed'] = () => {
ElMessage.warning('文件超出个数限制')
}
// 删除
const handleRemove: UploadProps['onRemove'] = (file, files) => {
emit(
'update:modelValue',
files.map((item: any) => {
return { name: item.name, url: item.url || item.raw.url }
})
)
}
// 预览
const handlePreview: UploadProps['onPreview'] = uploadFile => {
console.log(uploadFile)
}
</script>
<template>
<el-upload
action="https://webapp-pub.oss-cn-beijing.aliyuncs.com"
:data="uploadData"
:show-file-list="false"
:before-upload="handleBeforeUpload"
:on-exceed="handleExceed"
:on-remove="handleRemove"
:on-preview="handlePreview"
:on-success="handleSuccess"
:file-list="fileList"
class="uploader"
>
<el-button type="primary">选择本地图片</el-button>
</el-upload>
</template>
<style lang="scss">
.uploader {
flex: 1;
}
.avatar-uploader {
width: 178px;
height: 178px;
border: 1px dashed var(--el-border-color);
border-radius: 6px;
cursor: pointer;
position: relative;
overflow: hidden;
transition: var(--el-transition-duration-fast);
.el-image {
width: 100%;
height: 100%;
}
}
.avatar-uploader:hover {
border-color: var(--el-color-primary);
}
.avatar-uploader-icon {
font-size: 28px;
color: #8c939d;
width: 100%;
height: 100%;
text-align: center;
}
</style>
import type { RouteRecordRaw } from 'vue-router'
import AppLayout from '@/components/layout/Index.vue'
export const routes: Array<RouteRecordRaw> = [
{
path: '/course/create',
component: AppLayout,
children: [
{ path: '', component: () => import('./views/Create.vue') }
]
}
]
<script setup lang="ts">
// 第一步
import StepOne from '../components/StepOne.vue'
// is 编辑 新建
const route = useRoute()
const isUpdate = $computed(() => {
return !!route.query.id
})
</script>
<template>
<AppCard :title="isUpdate ? '编辑课程' : '新建课程'">
<StepOne></StepOne>
</AppCard>
</template>
<style lang="scss">
</style>
...@@ -26,6 +26,11 @@ export default defineConfig(({ mode }) => ({ ...@@ -26,6 +26,11 @@ export default defineConfig(({ mode }) => ({
cert: fs.readFileSync(path.join(__dirname, './https/dev.ezijing.com.pem')) cert: fs.readFileSync(path.join(__dirname, './https/dev.ezijing.com.pem'))
}, },
proxy: { proxy: {
'/api/qbs': {
target: 'https://question-api.ezijing.com',
changeOrigin: true,
rewrite: path => path.replace(/^\/api\/qbs/, '')
},
'/api': 'https://resource-center.ezijing.com' '/api': 'https://resource-center.ezijing.com'
} }
}, },
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论