Merge branch 'main' into zouyf_dev
This commit is contained in:
commit
66853327cd
|
@ -11,15 +11,21 @@
|
|||
:default-expanded-keys="defaultExpandedKeys" :current-node-key="curNode.data.id" highlight-current
|
||||
@node-click="handleNodeClick">
|
||||
<template #default="{ node, data }">
|
||||
<div v-if="props.isClassTask && (data.bookId == '' || data.bookId == '0')">
|
||||
<el-tooltip effect="light" placement="right" content="该单元章节无自主试题">
|
||||
<span class="tree-label" style="color: #A5B3CA">
|
||||
<div v-if="props.isClassTask && (data.bookId == '' || data.bookId == '0')" class="tree-label-wrap">
|
||||
<el-tooltip effect="light" placement="right" >
|
||||
<template #content> {{ node.label }}<br /><span style="color: red;">-该单元章节无自主试题-</span> </template>
|
||||
<span class="tree-label" style="color: #A5B3CA" >
|
||||
{{ node.label }}
|
||||
</span>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
<div v-else>
|
||||
<span class="tree-label">{{ node.label }}</span>
|
||||
<div v-else class="tree-label-wrap">
|
||||
<el-tooltip effect="light" placement="right" >
|
||||
<template #content> {{ node.label }}</template>
|
||||
<span class="tree-label">
|
||||
{{ node.label }}
|
||||
</span>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
</template>
|
||||
</el-tree>
|
||||
|
@ -334,7 +340,7 @@ onMounted( async () => {
|
|||
}
|
||||
}
|
||||
|
||||
.tree-label {
|
||||
.tree-label-wrap, .tree-label {
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
|
|
|
@ -107,9 +107,13 @@ const getBackGroundV2 = async () => {
|
|||
}
|
||||
};
|
||||
|
||||
const createOutlineV2 = async (data) => {
|
||||
const createOutlineV2 = async (params) => {
|
||||
try {
|
||||
const response = await req("/api/aipptV2/createOutlineV2", "POST", data);
|
||||
const response = await request({
|
||||
url:"/api/aipptV2/createOutlineV2",
|
||||
method: "POST",
|
||||
params
|
||||
});
|
||||
console.log("createOutline response:", response);
|
||||
|
||||
return response.data;
|
||||
|
|
|
@ -680,6 +680,7 @@ onMounted(async () => {
|
|||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 15px;
|
||||
padding: 10px;
|
||||
.content-body-right-item{
|
||||
&:hover{
|
||||
cursor: pointer;
|
||||
|
|
|
@ -1,30 +1,120 @@
|
|||
<template>
|
||||
<div>
|
||||
<el-dialog class="ppt-dialog" v-model="model" :show-close="false" width="800" destroy-on-close :top="'3vh'">
|
||||
<template #header="{ close, titleId, titleClass }">
|
||||
<div class="dialog-header">
|
||||
<h4 :id="titleId" :class="titleClass">生成PPT(试验版)</h4>
|
||||
<i class="iconfont icon-guanbi" @click="close"></i>
|
||||
<el-dialog class="ppt-dialog" v-model="model" title="生成PPT(试验版)"
|
||||
:close-on-click-modal="false"
|
||||
:close-on-press-escape="false"
|
||||
:show-close="false" width="1000" destroy-on-close >
|
||||
<!-- <AiPptist @add-success="addAiPPT" :dataList="dataList"/>-->
|
||||
<div class="ppt-dialog-content">
|
||||
<div v-if="activeStep === 1" class="ppt-dialog-cover-wrap">
|
||||
<div v-for="(item,index) in backGroundList"
|
||||
:key="item.templateIndexId"
|
||||
@click="outlineData.templateId = item.templateIndexId"
|
||||
:class="outlineData.templateId === item.templateIndexId?'active-mode':''"
|
||||
class="ppt-dialog-cover-item">
|
||||
<div class="icon-select">
|
||||
<Select />
|
||||
</div>
|
||||
<img :src="getBackGroundImg(item.detailImage)">
|
||||
</div>
|
||||
</div>
|
||||
<div class="ppt-dialog-prog-wrap" v-if="activeStep === 2">
|
||||
<el-progress :percentage="percentage" type="circle"></el-progress>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ppt-dialog-footer" v-if="activeStep === 1">
|
||||
<el-button @click="closeDialog">关闭</el-button>
|
||||
<el-button type="primary" :loading="createPPTLoading" @click="createPPT">生成课件</el-button>
|
||||
</div>
|
||||
</template>
|
||||
<AiPptist @add-success="addAiPPT" :dataList="dataList"/>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import AiPptist from './ai-pptistV2.vue';
|
||||
import {Select} from "@element-plus/icons-vue";
|
||||
import {ref, defineEmits, onMounted} from "vue";
|
||||
import {createPPTV2, getBackGroundV2, getProgressV2} from "@/utils/ppt-request";
|
||||
import {ElMessage} from "element-plus";
|
||||
const model = defineModel()
|
||||
const emit = defineEmits(['addSuccess'])
|
||||
const emit = defineEmits(['addSuccess', 'close-dialogs'])
|
||||
const backGroundList = ref([]);
|
||||
const activeStep = ref(1);
|
||||
const props = defineProps({
|
||||
dataList: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
type: Object,
|
||||
default: () => {}
|
||||
}
|
||||
})
|
||||
const closeDialog = () => {
|
||||
emit('close-dialogs')
|
||||
}
|
||||
const addAiPPT = (data) => {
|
||||
emit('addSuccess', data)
|
||||
}
|
||||
const getBackGroundImg = (imgUrlStr) => {
|
||||
return JSON.parse(imgUrlStr).titleCoverImage
|
||||
};
|
||||
|
||||
const createPPTLoading = ref(false);
|
||||
const outlineData = ref({
|
||||
query: '', // 用户要求(最多8000字)
|
||||
templateId: '', // ppt生成主题
|
||||
author: 'AIX平台',
|
||||
isFigure: false, // 是否自动配图
|
||||
search: true,
|
||||
language: "cn"
|
||||
})
|
||||
const percentage = ref(0);
|
||||
const outlineCreatePPT = () => {
|
||||
const newOutlineData = { ...outlineData.value, };
|
||||
newOutlineData.query = props.dataList.outline;
|
||||
createPPTLoading.value = true;
|
||||
createPPTV2(newOutlineData).then((res) => {
|
||||
console.log(res, "正在生成中");
|
||||
createPPTLoading.value = false;
|
||||
activeStep.value = 2
|
||||
|
||||
const checkProgress = () => {
|
||||
getProgressV2(res.sid).then(response => {
|
||||
percentage.value = Math.round(response?.donePages*100/response?.totalPages);
|
||||
if (response.pptStatus === "done") {
|
||||
emit('addSuccess',{...res,url:response.pptUrl})
|
||||
ElMessage.success("生成成功");
|
||||
} else {
|
||||
const sleepTime = 2000;
|
||||
let remainingTime = sleepTime;
|
||||
const intervalId = setInterval(() => {
|
||||
remainingTime -= 100;
|
||||
if (remainingTime <= 0) {
|
||||
clearInterval(intervalId);
|
||||
checkProgress();
|
||||
}
|
||||
}, 100);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
checkProgress();
|
||||
}).finally(()=>{
|
||||
createPPTLoading.value = false
|
||||
})
|
||||
};
|
||||
|
||||
const createPPT = () => {
|
||||
if (outlineData.value.templateId) {
|
||||
outlineCreatePPT()
|
||||
}else {
|
||||
ElMessage.warning("请选择模板");
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
// emit('addSuccess',{url:'https://bjcdn.openstorage.cn/xinghuo-privatedata/%2Ftmp/apiTempFileba724e0344f74e1480535eedf3ebec661601807661085006275/%E9%87%91%E9%A9%AC%E5%A5%96%E5%B0%B4%E5%B0%AC%E4%BA%8B%E4%BB%B6%E5%88%86%E6%9E%90%E4%B8%8E%E5%BA%94%E5%AF%B9%E7%AD%96%E7%95%A5.pptx'})
|
||||
getBackGroundV2().then((res) => {
|
||||
backGroundList.value = res.records;
|
||||
});
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
@ -40,4 +130,50 @@
|
|||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
.ppt-dialog-footer{
|
||||
padding-top: 20px;
|
||||
text-align: right;
|
||||
}
|
||||
.ppt-dialog-content{
|
||||
height: 500px;
|
||||
overflow: auto;
|
||||
.ppt-dialog-prog-wrap{
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 100%;
|
||||
}
|
||||
.ppt-dialog-cover-wrap{
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: space-around;
|
||||
.ppt-dialog-cover-item{
|
||||
margin-bottom: 10px;
|
||||
border: 10px solid #ccc;
|
||||
border-radius: 10px;
|
||||
cursor: pointer;
|
||||
img{
|
||||
width: 200px;
|
||||
}
|
||||
.icon-select{
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
.active-mode{
|
||||
border: 10px solid #5e24d0;
|
||||
position: relative;
|
||||
.icon-select{
|
||||
position: absolute;
|
||||
bottom: 10px;
|
||||
right: 10px;
|
||||
height: 20px;
|
||||
width: 20px;
|
||||
color: #fff;
|
||||
background: #5e24d0;
|
||||
border-radius: 5px;
|
||||
display: block!important;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
<el-button type="primary" @click="createAi">
|
||||
<i class="iconfont icon-chuangzuo"></i>生成教学大纲
|
||||
</el-button>
|
||||
<el-button type="danger" :disabled="curItem.parentId" @click="delAnswer">
|
||||
<el-button type="danger" :disabled="!!curItem.parentId" @click="delAnswer">
|
||||
<i class="iconfont icon-shanchu"></i>
|
||||
删除大纲
|
||||
</el-button>
|
||||
|
@ -20,8 +20,20 @@
|
|||
</div>
|
||||
</div>
|
||||
<div class="center-con" v-loading="loading">
|
||||
<TypingEffect v-if="answer" :text="answer" :delay="10" :aiShow="aiShow"/>
|
||||
<el-empty v-if="!answer" description="请选择符合您需要的教学模式,生成教学大纲" />
|
||||
<!-- <TypingEffect v-if="answer" :text="answer" :delay="10" :aiShow="aiShow"/> -->
|
||||
<div style="font-size: 18px;color: #409eff;">封面页</div>
|
||||
<div class="con-item mb-5">
|
||||
<div class="item-name">标题:{{ answer.title }}</div>
|
||||
<div class="item-name">副标题:{{answer.subTitle }}</div>
|
||||
</div>
|
||||
<div style="font-size: 18px;color: #409eff;">目录页</div>
|
||||
<div class="con-item" v-for="(item,index) in answer.chapters">
|
||||
<div class="item-name">{{index + 1}}:{{ item.chapterTitle }}</div>
|
||||
<div class="item-text">
|
||||
<p v-for="(el,i) in item.chapterContents">{{ index + 1 }} - {{ i + 1}} : {{ el.chapterTitle }}</p>
|
||||
</div>
|
||||
</div>
|
||||
<el-empty v-if="!answer.title" description="请选择符合您需要的教学模式,生成教学大纲" />
|
||||
</div>
|
||||
</div>
|
||||
<EditDialog v-model="isEdit" :item="curItem" />
|
||||
|
@ -36,7 +48,7 @@ import emitter from '@/utils/mitt'
|
|||
import * as commUtils from '@/utils/comm.js'
|
||||
import { createChart, sendChart } from '@/api/ai/index'
|
||||
import { completion, addSyllabus, syllabuss, removeSyllabus } from '@/api/mode/index.js'
|
||||
import TypingEffect from '@/components/typing-effect/index.vue'
|
||||
import { createOutlineV2 } from '@/utils/ppt-request.js'
|
||||
import useUserStore from '@/store/modules/user'
|
||||
|
||||
const curMode = ref(2)
|
||||
|
@ -64,8 +76,9 @@ emitter.on('selected', (data)=>{
|
|||
// 回显大纲
|
||||
const curItem = reactive({})
|
||||
emitter.on('onShow', (data)=>{
|
||||
console.log(data)
|
||||
aiShow.value = false
|
||||
answer.value = getResult(data.outline)
|
||||
Object.assign(answer, JSON.parse(data.outline))
|
||||
Object.assign(curItem, data)
|
||||
curItem.answer = curItem.outline
|
||||
getDetails(data.id)
|
||||
|
@ -88,10 +101,9 @@ const params = reactive(
|
|||
|
||||
// 研读
|
||||
const loading = ref(false)
|
||||
const answer = ref('')
|
||||
const answer = reactive({})
|
||||
|
||||
const createAi = async ()=>{
|
||||
console.log(selectedData.value)
|
||||
if(selectedData.value.length == 0){
|
||||
ElMessage.warning('请先选择教学环节后再生成教学大纲')
|
||||
return
|
||||
|
@ -119,17 +131,17 @@ const createAi = async ()=>{
|
|||
// 知识库模型
|
||||
else {
|
||||
const res = await completion(params)
|
||||
|
||||
data = res.data
|
||||
}
|
||||
console.log(data)
|
||||
emitter.emit('onResult', data.answer)
|
||||
answer.value = getResult(data.answer)
|
||||
|
||||
onSaveTemp(data.answer)
|
||||
const res = await createOutlineV2({query: data.answer})
|
||||
console.log(res)
|
||||
emitter.emit('onResult', res)
|
||||
Object.assign(answer, res.outline)
|
||||
onSaveTemp(JSON.stringify(res.outline))
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
@ -170,11 +182,6 @@ const delAnswer = () =>{
|
|||
|
||||
}
|
||||
|
||||
// 去掉字符串中的 ### **
|
||||
let getResult = (str) => {
|
||||
let newStr = str.replace(/#+|(\*\*)/g, '');
|
||||
return newStr
|
||||
}
|
||||
|
||||
// 千帆创建对话
|
||||
const conversation_id = ref('')
|
||||
|
@ -227,6 +234,18 @@ onMounted(() => {
|
|||
border-radius: 5px;
|
||||
text-align: left;
|
||||
overflow-y: auto;
|
||||
padding: 15px;
|
||||
.con-item{
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin-top: 15px;
|
||||
.item-text{
|
||||
background: #F2F2F2;
|
||||
padding: 15px;
|
||||
border-radius: 5px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -4,8 +4,11 @@
|
|||
<span>教学模式</span>
|
||||
<div>
|
||||
<el-button type="primary" link @click="resetSelect">重置</el-button>
|
||||
<el-button type="primary" link @click="addVisible = true; addChild = false; isEdit = false"><i class="iconfont icon-jiahao"
|
||||
></i>新增</el-button>
|
||||
<el-button type="primary" link @click="
|
||||
addVisible = true,
|
||||
addChild = false,
|
||||
isEdit = false
|
||||
"><i class="iconfont icon-jiahao"></i>新增</el-button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="left-list">
|
||||
|
@ -25,21 +28,22 @@
|
|||
<!--减号 选中之后显示-->
|
||||
<i v-show="item.selected" class="iconfont icon-zuixiaohua"></i>
|
||||
</div>
|
||||
<div class="item-child flex" :class="child.selected ? 'act-child' : ''" v-for="child in item.children" :key="child.id"
|
||||
@mouseenter="child.isAdd = true" @mouseleave="child.isAdd = false" @click="toggleChild(item,child)">
|
||||
<div class="item-child flex" :class="child.selected ? 'act-child' : ''" v-for="child in item.children"
|
||||
:key="child.id" @mouseenter="child.isAdd = true" @mouseleave="child.isAdd = false"
|
||||
@click="toggleChild(item, child)">
|
||||
<div>
|
||||
<span>{{ child.name }}</span>
|
||||
<!--个人教学模式才会有编辑-->
|
||||
<el-button v-if="!child.ex3" class="ml-3" type="primary" link @click.stop="editMode(child)">编辑</el-button>
|
||||
</div>
|
||||
<i v-show="child.isAdd && !child.selected" class="iconfont icon-jiahao"></i>
|
||||
<i v-show="child.selected" class="iconfont icon-zuixiaohua "></i>
|
||||
<i v-show="child.selected" class="iconfont icon-zuixiaohua"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!--弹窗-->
|
||||
<el-dialog v-model="addVisible" append-to-body :show-close="false" width="550" :before-close="handleBeforeClose"
|
||||
style="border-radius: 10px; padding: 10px 15px;">
|
||||
style="border-radius: 10px; padding: 10px 15px">
|
||||
<template #header>
|
||||
<div class="mode-dialog-header flex">
|
||||
<span>{{ addChild ? '教学环节' : '教学模式' }}</span>
|
||||
|
@ -48,8 +52,8 @@
|
|||
</template>
|
||||
<el-form ref="ruleFormRef" style="max-width: 600px" :model="ruleForm" :rules="rules" label-width="auto"
|
||||
class="demo-ruleForm">
|
||||
<el-form-item :label="`教学${ addChild ? '环节' : '模式'}名称`" prop="name">
|
||||
<div class="flex" style="width: 100%;">
|
||||
<el-form-item :label="`教学${addChild ? '环节' : '模式'}名称`" prop="name">
|
||||
<div class="flex" style="width: 100%">
|
||||
<el-input v-model="ruleForm.name" />
|
||||
<el-button v-if="isEdit" link type="danger" class="ml-5 mr-3" @click="onDel">删除</el-button>
|
||||
</div>
|
||||
|
@ -64,7 +68,6 @@
|
|||
</div>
|
||||
</el-dialog>
|
||||
</div>
|
||||
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
|
@ -77,127 +80,130 @@ import { ElMessage, ElMessageBox } from 'element-plus'
|
|||
import { addChildTemp, editChildTemp, removeChildTemp, syllabusList } from '@/api/mode'
|
||||
import { cloneDeep } from 'lodash'
|
||||
|
||||
|
||||
const { user } = useUserStore()
|
||||
|
||||
// 获取主模板
|
||||
// 获取模板
|
||||
const loading = ref(false)
|
||||
let list = ref([])
|
||||
const getTemplate = async (id) => {
|
||||
loading.value = true
|
||||
const { rows } = await modelList({ createUser: user.userId, model: 4, type: 1, pageNum: 1, pageSize: 10000 })
|
||||
loading.value = false
|
||||
list.value = rows
|
||||
if (list.value.length) {
|
||||
getChildTemp(id)
|
||||
}
|
||||
}
|
||||
|
||||
// 获取子模板
|
||||
const templateList = ref([])
|
||||
const getChildTemp = async () => {
|
||||
templateList.value = []
|
||||
const getTemplate = async () => {
|
||||
loading.value = true
|
||||
|
||||
for (let item of list.value) {
|
||||
try {
|
||||
let { rows } = await modelList({ model: 4, type: 2, parentId: item.id })
|
||||
templateList.value.push({
|
||||
...item,
|
||||
children: rows
|
||||
const { rows } = await modelList({
|
||||
createUser: user.userId,
|
||||
model: 4,
|
||||
pageNum: 1,
|
||||
pageSize: 10000
|
||||
})
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 过滤出 type 为 1 的项
|
||||
let ary1 = rows.filter((item) => item.type === 1)
|
||||
templateList.value = ary1.map((parent) => {
|
||||
parent.children = rows.filter((child) => child.type === 2 && child.parentId === parent.id)
|
||||
return parent
|
||||
})
|
||||
getSyllabus()
|
||||
}
|
||||
|
||||
// 查询生成大纲记录
|
||||
const getSyllabus = async () =>{
|
||||
const { rows } = await syllabusList()
|
||||
if(rows && rows.length){
|
||||
const idsAry = rows.at(-1).modelIds.split(',').map(item => Number(item));
|
||||
const getSyllabus = async () => {
|
||||
const { rows } = await syllabusList({
|
||||
createUserId: user.userId,
|
||||
eduId: curNode.id,
|
||||
sourceType: 1,
|
||||
pageSize: 1,
|
||||
orderByColumn: 'createTime',
|
||||
isAsc: 'desc'
|
||||
})
|
||||
if (rows && rows.length) {
|
||||
const idsAry = rows
|
||||
.at(-1)
|
||||
.modelIds.split(',')
|
||||
.map((item) => Number(item))
|
||||
// 设置选中
|
||||
let ary = []
|
||||
templateList.value.forEach((parent) => {
|
||||
parent.children.forEach((child) => {
|
||||
child.selected = idsAry.includes(child.id);
|
||||
});
|
||||
child.selected = idsAry.includes(child.id)
|
||||
if (child.selected) {
|
||||
ary.push(child)
|
||||
}
|
||||
})
|
||||
// 更新父项的 selected 状态
|
||||
parent.selected = parent.children.every((child) => child.selected);
|
||||
});
|
||||
parent.selected = parent.children.every((child) => child.selected)
|
||||
})
|
||||
// 回显大纲数据
|
||||
emitter.emit('onShow', rows.at(-1))
|
||||
emitter.emit('selected', ary)
|
||||
}
|
||||
}
|
||||
|
||||
// 重置选中
|
||||
const resetSelect = () =>{
|
||||
templateList.value.forEach(item =>{
|
||||
const resetSelect = () => {
|
||||
templateList.value.forEach((item) => {
|
||||
item.selected = false
|
||||
item.children.forEach( el=>{
|
||||
item.children.forEach((el) => {
|
||||
el.selected = false
|
||||
})
|
||||
})
|
||||
emitter.emit('selected', [])
|
||||
}
|
||||
|
||||
emitter.on('resetSelect', () =>{
|
||||
emitter.on('resetSelect', () => {
|
||||
resetSelect()
|
||||
})
|
||||
|
||||
// 点击教学模式
|
||||
const toggleParent = (parent) => {
|
||||
parent.selected = !parent.selected;
|
||||
parent.children.forEach(child => {
|
||||
child.selected = parent.selected;
|
||||
});
|
||||
const selectedData = templateList.value.map((item) => item.children.filter((child) => child.selected)).flat()
|
||||
parent.selected = !parent.selected
|
||||
parent.children.forEach((child) => {
|
||||
child.selected = parent.selected
|
||||
})
|
||||
const selectedData = templateList.value
|
||||
.map((item) => item.children.filter((child) => child.selected))
|
||||
.flat()
|
||||
// .flat()将二维数组扁平化为一维数组
|
||||
emitter.emit('selected', selectedData)
|
||||
}
|
||||
|
||||
// 点击教学环节
|
||||
const toggleChild = (parent,child) =>{
|
||||
child.selected = !child.selected;
|
||||
const toggleChild = (parent, child) => {
|
||||
child.selected = !child.selected
|
||||
updateParentSelection(parent)
|
||||
const selectedData = templateList.value.map((item) => item.children.filter((child) => child.selected)).flat()
|
||||
const selectedData = templateList.value
|
||||
.map((item) => item.children.filter((child) => child.selected))
|
||||
.flat()
|
||||
emitter.emit('selected', selectedData)
|
||||
}
|
||||
|
||||
// 检查父项(教学模式)的所有子项(教学环节)是否都被选中,如果是,则设置父项为选中状态,否则取消选中
|
||||
const updateParentSelection = (parent) => {
|
||||
parent.selected = parent.children.every((child) => child.selected);
|
||||
parent.selected = parent.children.every((child) => child.selected)
|
||||
}
|
||||
|
||||
|
||||
// 弹窗
|
||||
const addVisible = ref(false)
|
||||
|
||||
const ruleFormRef = ref()
|
||||
const rules = reactive({
|
||||
name: [
|
||||
{ required: true, message: '不能为空', trigger: 'blur' },
|
||||
]
|
||||
name: [{ required: true, message: '不能为空', trigger: 'blur' }]
|
||||
})
|
||||
const ruleForm = reactive({
|
||||
name: '',
|
||||
prompt: ''
|
||||
})
|
||||
// 提交
|
||||
const onSubmit = async (formEl) =>{
|
||||
const onSubmit = async (formEl) => {
|
||||
if (!formEl) return
|
||||
await formEl.validate(async (valid, fields) => {
|
||||
if (valid) {
|
||||
|
||||
// 修改
|
||||
if(isEdit.value){
|
||||
if (isEdit.value) {
|
||||
let data = cloneDeep(curEditItem)
|
||||
data.name = ruleForm.name
|
||||
await editChildTemp(data)
|
||||
}
|
||||
// 新增
|
||||
else{
|
||||
else {
|
||||
let obj = {
|
||||
ex1: curNode.edustage,
|
||||
ex2: curNode.edusubject,
|
||||
|
@ -207,13 +213,12 @@ const onSubmit = async (formEl) =>{
|
|||
type: 1
|
||||
}
|
||||
// 新增教学环节
|
||||
if(addChild.value){
|
||||
if (addChild.value) {
|
||||
obj.parentId = curEditItem.id
|
||||
obj.prompt = ruleForm.prompt
|
||||
obj.type = 2
|
||||
}
|
||||
await addChildTemp(obj)
|
||||
|
||||
}
|
||||
ElMessage.success('操作成功')
|
||||
resetForm()
|
||||
|
@ -231,7 +236,7 @@ const isEdit = ref(false)
|
|||
const addChild = ref(false)
|
||||
|
||||
const curEditItem = reactive({})
|
||||
const addModeChild = (item) =>{
|
||||
const addModeChild = (item) => {
|
||||
isEdit.value = false
|
||||
Object.assign(curEditItem, item)
|
||||
addChild.value = true
|
||||
|
@ -240,7 +245,7 @@ const addModeChild = (item) =>{
|
|||
|
||||
//
|
||||
|
||||
const editMode = (item)=>{
|
||||
const editMode = (item) => {
|
||||
isEdit.value = true
|
||||
addChild.value = false
|
||||
Object.assign(curEditItem, item)
|
||||
|
@ -249,16 +254,12 @@ const editMode = (item)=>{
|
|||
}
|
||||
|
||||
// 删除 教学模式 教学环节
|
||||
const onDel = () =>{
|
||||
ElMessageBox.confirm(
|
||||
'确定要删除模板吗?',
|
||||
'温馨提示',
|
||||
{
|
||||
const onDel = () => {
|
||||
ElMessageBox.confirm('确定要删除模板吗?', '温馨提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
}
|
||||
)
|
||||
type: 'warning'
|
||||
})
|
||||
.then(async () => {
|
||||
await removeChildTemp(curEditItem.id)
|
||||
ElMessage.success('操作成功')
|
||||
|
@ -266,36 +267,33 @@ const onDel = () =>{
|
|||
addVisible.value = false
|
||||
getTemplate()
|
||||
})
|
||||
.catch(() => {})
|
||||
.catch(() => { })
|
||||
}
|
||||
|
||||
|
||||
const closeDialog = () =>{
|
||||
handleBeforeClose(() => addVisible.value = false);
|
||||
const closeDialog = () => {
|
||||
handleBeforeClose(() => (addVisible.value = false))
|
||||
}
|
||||
|
||||
const handleBeforeClose = (done) =>{
|
||||
const handleBeforeClose = (done) => {
|
||||
resetForm()
|
||||
done()
|
||||
}
|
||||
const resetForm = () => {
|
||||
if (ruleFormRef.value) {
|
||||
ruleFormRef.value.resetFields();
|
||||
ruleFormRef.value.resetFields()
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const curNode = reactive({})
|
||||
onMounted(() => {
|
||||
let data = sessionStore.get('subject.curNode')
|
||||
Object.assign(curNode, data);
|
||||
Object.assign(curNode, data)
|
||||
getTemplate()
|
||||
})
|
||||
|
||||
|
||||
onUnmounted(()=>{
|
||||
onUnmounted(() => {
|
||||
emitter.off('resetSelect')
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
@ -342,7 +340,7 @@ onUnmounted(()=>{
|
|||
position: relative;
|
||||
|
||||
&::after {
|
||||
content: "";
|
||||
content: '';
|
||||
width: 5px;
|
||||
height: 5px;
|
||||
border-radius: 50%;
|
||||
|
@ -352,7 +350,6 @@ onUnmounted(()=>{
|
|||
top: 50%;
|
||||
transform: translate(0, -50%);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.iconfont {
|
||||
|
@ -389,7 +386,8 @@ onUnmounted(()=>{
|
|||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
.form-btn{
|
||||
|
||||
.form-btn {
|
||||
text-align: right;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
|
||||
<template>
|
||||
<el-dialog v-model="open" v-bind="dAttrs">
|
||||
<el-dialog class="aix-progress-dialog" v-model="open" v-bind="dAttrs">
|
||||
<el-progress type="dashboard" v-bind="$attrs.pg" />
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
@ -29,5 +29,10 @@ const dAttrs = computed(() => {
|
|||
})
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
|
||||
</style>
|
||||
<style>
|
||||
.aix-progress-dialog .el-dialog__body{
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
</style>
|
|
@ -4,22 +4,73 @@
|
|||
<span>课件预览</span>
|
||||
<div>
|
||||
<el-button type="danger" @click="onCreate">一键生成</el-button>
|
||||
<el-button>编辑课件</el-button>
|
||||
<el-button :disabled="!result?.parentId" @click="openAiPPT">编辑课件</el-button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="right-con">
|
||||
<el-empty v-if="!result" description="请先生成教学大纲,再生成教学课件" />
|
||||
<el-empty v-if="!result?.parentId" description="请先生成教学大纲,再生成教学课件" />
|
||||
<div v-for="(item,index) in pptSlides" class="right-con-item">
|
||||
<div>{{index+1}}</div><img :src="item.fileurl">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<PptDialog @close-dialogs="pptDialog = false" @add-success="addAiPPT" :dataList="result" v-model="pptDialog" />
|
||||
<progress-dialog v-model:visible="pgDialog.visible" v-bind="pgDialog" />
|
||||
</template>
|
||||
<script setup>
|
||||
import { ref, onUnmounted } from 'vue'
|
||||
import {ref, onUnmounted, onMounted, reactive} from 'vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import emitter from '@/utils/mitt'
|
||||
import progressDialog from './progress-dialog.vue'
|
||||
import PptDialog from "@/views/prepare/container/pptist-dialog.vue";
|
||||
import msgUtils from "@/plugins/modal";
|
||||
import {PPTXFileToJson} from "@/AixPPTist/src/hooks/useImport";
|
||||
import {slidesToImg} from "@/utils/ppt";
|
||||
import {getEntpcoursefile, listEntpcoursefileNew} from "@/api/education/entpcoursefile";
|
||||
import {sessionStore} from "@/utils/store";
|
||||
import * as API_smarttalk from "@/api/file";
|
||||
import * as API_entpcourse from "@/api/education/entpcourse";
|
||||
import * as API_entpcoursefile from "@/api/education/entpcoursefile";
|
||||
import * as commUtils from '@/utils/comm.js'
|
||||
import * as Api_server from '@/api/apiService' // 相关api
|
||||
import useUserStore from '@/store/modules/user'
|
||||
import {createWindow} from "@/utils/tool";
|
||||
import {editSyllabus} from "@/api/mode";
|
||||
import { getSmarttalkPage } from "@/api/file"
|
||||
const userStore = useUserStore()
|
||||
|
||||
const pptDialog = ref(false)
|
||||
const result = ref(null)
|
||||
const courseObj = reactive({
|
||||
node: null, // 选择的课程节点
|
||||
})
|
||||
const curNode = reactive({})
|
||||
const pgDialog = reactive({ // 弹窗-进度条
|
||||
visible: false,
|
||||
title: 'PPT解析中...',
|
||||
width: 300,
|
||||
showClose: false,
|
||||
draggable: true,
|
||||
beforeClose: done => { }, // 阻止-弹窗事件
|
||||
pg: { // 进度条-参数
|
||||
percentage: 0, // 百分比
|
||||
color: [
|
||||
{ color: '#1989fa', percentage: 50 }, // 蓝色
|
||||
{ color: '#e6a23c', percentage: 80 }, // 橙色
|
||||
{ color: '#5cb87a', percentage: 100 }, // 绿色
|
||||
]
|
||||
}
|
||||
})
|
||||
|
||||
const pptSlides = ref([])
|
||||
|
||||
const result = ref('')
|
||||
emitter.on('onResult', (data)=>{
|
||||
result.value = data
|
||||
if (!!result.value.parentId) {
|
||||
listEntpcoursefileNew({parentid: result.value.parentId}).then(res=>{
|
||||
pptSlides.value = res.rows
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
const onCreate = () =>{
|
||||
|
@ -27,8 +78,232 @@ const onCreate = () =>{
|
|||
ElMessage.warning('请先进行研读')
|
||||
return
|
||||
}
|
||||
pptDialog.value = true
|
||||
}
|
||||
|
||||
const addAiPPT = async (res) => {
|
||||
// res = { url: 'https://bjcdn.openstorage.cn/xinghuo-privatedata/%2Ftmp/apiTempFileba724e0344f74e1480535eedf3ebec661601807661085006275/%E9%87%91%E9%A9%AC%E5%A5%96%E5%B0%B4%E5%B0%AC%E4%BA%8B%E4%BB%B6%E5%88%86%E6%9E%90%E4%B8%8E%E5%BA%94%E5%AF%B9%E7%AD%96%E7%95%A5.pptx' }
|
||||
let node = courseObj.node
|
||||
pptDialog.value = false;
|
||||
if (!node) return msgUtils.msgWarning('请选择章节?')
|
||||
pgDialog.visible = true
|
||||
pgDialog.pg.percentage = 0
|
||||
//TODO res中有PPT地址
|
||||
const params = { evalid: node.id, edituserid: userStore.id, pageSize: 1 }
|
||||
const resEnpt = await HTTP_SERVER_API('getCourseList', params)
|
||||
if (!(resEnpt?.rows?.[0] || null)) { // 创建
|
||||
const resid = await HTTP_SERVER_API('addEntpcourse')
|
||||
courseObj.entp.id = resid
|
||||
} else courseObj.entp = resEnpt?.rows?.[0] || null
|
||||
// 下载PPT 并解析json转换到我们自己数据库
|
||||
fetch(res.url)
|
||||
.then(res => res.arrayBuffer())
|
||||
.then(async buffer => {
|
||||
const resPptJson = await PPTXFileToJson(buffer)
|
||||
const { def, slides, ...content } = resPptJson
|
||||
// 生成缩略图
|
||||
const thumbnails = await slidesToImg(slides, content.width)
|
||||
// 转换图片|音频|视频 为线上地址
|
||||
let completed = 0
|
||||
const total = slides.length
|
||||
for (let o of slides) {
|
||||
completed++
|
||||
await toRousrceUrl(o)
|
||||
// 设置进度条
|
||||
pgDialog.pg.percentage = Math.floor(completed / total * 100)
|
||||
}
|
||||
pgDialog.pg.percentage = 0
|
||||
pgDialog.visible = false
|
||||
// 生成ppt课件-父级
|
||||
const p_params = { parentContent: JSON.stringify(content) }
|
||||
const parentid = await HTTP_SERVER_API('addEntpcoursefile', p_params)
|
||||
if (!!parentid ?? null) { // 生成内容幻灯片
|
||||
// 生成备课资源-Smarttalk
|
||||
const smarttalk = await HTTP_SERVER_API('addSmarttalk', { fileId: parentid })
|
||||
if (slides.length > 0) {
|
||||
const resSlides = slides.map(({ id, ...slide }) => JSON.stringify(slide))
|
||||
const params = { parentid, filetype: 'slide', title: '', thumbnails, slides: resSlides }
|
||||
const res_3 = await HTTP_SERVER_API('batchAddNew', params)
|
||||
if (res_3 && res_3.code == 200) {
|
||||
msgUtils.msgSuccess('生成PPT课件成功')
|
||||
//TODO 打开生成的课件
|
||||
updateGen(parentid)
|
||||
listEntpcoursefileNew({parentid: parentid}).then(res=>{
|
||||
pptSlides.value = res.rows
|
||||
})
|
||||
const res = await getEntpcoursefile(parentid)
|
||||
if (res && res.code === 200) {
|
||||
openPublicScreen('edit', res.data, smarttalk.resData) // 打开公屏-窗口
|
||||
} else {
|
||||
ElMessage.warning(res.msg||'文件获取异常!')
|
||||
}
|
||||
} else {
|
||||
msgUtils.msgWarning('生成PPT课件失败')
|
||||
}
|
||||
}
|
||||
}
|
||||
}).finally(()=>{
|
||||
pgDialog.visible = false
|
||||
})
|
||||
}
|
||||
|
||||
const openAiPPT = async () =>{
|
||||
let parentid = result.value.parentId
|
||||
const res = await getEntpcoursefile(parentid)
|
||||
if (res && res.code === 200) {
|
||||
const smarttalk = getSmarttalkPage({fileId: parentid})
|
||||
openPublicScreen('edit', res.data, smarttalk.resData) // 打开公屏-窗口
|
||||
} else {
|
||||
ElMessage.warning(res.msg||'文件获取异常!')
|
||||
}
|
||||
}
|
||||
|
||||
const updateGen = (parentid)=>{
|
||||
result.value.parentId = parentid
|
||||
editSyllabus(result.value)
|
||||
}
|
||||
|
||||
const openPublicScreen = (type, resource, currData)=> {
|
||||
sessionStore.set('curr.resource', resource) // 缓存当前资源信息
|
||||
if (type=='edit') sessionStore.set('curr.smarttalk', currData) // 缓存当前文件smarttalk
|
||||
else sessionStore.set('curr.classcourse', currData) // 缓存当前当前上课
|
||||
createWindow('open-win', {
|
||||
url: '/pptist', // 窗口关闭时,清除缓存
|
||||
close: () => {
|
||||
sessionStore.set('curr.resource', null) // 清除缓存
|
||||
if (type=='edit') {
|
||||
sessionStore.set('curr.smarttalk', null) // 清除缓存
|
||||
} else sessionStore.set('curr.classcourse', null) // 清除缓存
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 图片|音频|视频 转换为在线地址
|
||||
const toRousrceUrl = async (o) => {
|
||||
if (!!o.src) { // 如果有src就转换
|
||||
const isBase64 = /^data:image\/(\w+);base64,/.test(o.src)
|
||||
const isBlobUrl = /^blob:/.test(o.src)
|
||||
console.log('isBase64', o, isBase64)
|
||||
if (isBase64) {
|
||||
const bolb = commUtils.base64ToBlob(o.src)
|
||||
const fileName = Date.now() + '.png'
|
||||
const file = commUtils.blobToFile(bolb, fileName)
|
||||
// o.src = fileName
|
||||
// console.log('file', file)
|
||||
const formData = new FormData()
|
||||
formData.append('file', file)
|
||||
const res = await Api_server.Other.uploadFile(formData)
|
||||
if (res && res.code == 200) {
|
||||
const url = res?.url
|
||||
url && (o.src = url)
|
||||
}
|
||||
} else if (isBlobUrl) { // 视频和音频
|
||||
const res = await fetch(o.src)
|
||||
const blob = await res.blob()
|
||||
const fileName = o.type == 'video' ? Date.now() + '.mp4' : Date.now() + '.mp3'
|
||||
const file = commUtils.blobToFile(blob, fileName)
|
||||
// o.src = fileName
|
||||
// console.log('file', file)
|
||||
const formData = new FormData()
|
||||
formData.append('file', file)
|
||||
const ress = await Api_server.Other.uploadFile(formData)
|
||||
if (ress && ress.code == 200) {
|
||||
const url = ress?.url
|
||||
url && (o.src = url)
|
||||
}
|
||||
}
|
||||
}
|
||||
if (o?.background?.image) await toRousrceUrl(o.background.image)
|
||||
if (o?.elements) {
|
||||
for (let element of o.elements) {
|
||||
await toRousrceUrl(element);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const HTTP_SERVER_API = (type, params = {}) => {
|
||||
switch (type) {
|
||||
case 'addSmarttalk': { // 获取课程
|
||||
const node = courseObj.node || {}
|
||||
const def = {
|
||||
fileId: '', // 文件id - Entpcoursefile 对应id
|
||||
fileFlag: 'aippt',
|
||||
fileShowName: node.itemtitle + '.aippt',
|
||||
textbookId: node.rootid,
|
||||
levelFirstId: node.parentid || node.id,
|
||||
levelSecondId: node.parentid && node.id,
|
||||
fileSource: '个人',
|
||||
fileRoot: '备课'
|
||||
}
|
||||
return API_smarttalk.creatAPT({ ...def, ...params })
|
||||
}
|
||||
case 'addEntpcourse': { // 添加课程
|
||||
const node = courseObj.node || {}
|
||||
if (!node) return msgUtils.msgWarning('请选择章节?')
|
||||
const def = { // 默认参数
|
||||
entpid: userStore.user.deptId, // 部门id
|
||||
level: 1, // 层级
|
||||
parentid: 0, // 父级id
|
||||
dictid: 0, // 字典id
|
||||
evalid: node.id, // 章节id
|
||||
evalparentid: node.parentid, // 单元id(父级id)
|
||||
edusubject: node.edusubject, // 学科
|
||||
edudegree: node.edudegree, // 年级
|
||||
edustage: node.edustage, // 阶段
|
||||
coursetype: '课标学科', // 课程类型
|
||||
coursetitle: node.itemtitle, // 课程名称
|
||||
coursedesc: '', // 课程描述
|
||||
status: '', // 状态
|
||||
dflag: 0, // 状态
|
||||
edituserid: userStore.id, // 编辑人id
|
||||
createblankfile: 'no', // 创建空白文件
|
||||
}
|
||||
courseObj.entp = def
|
||||
return API_entpcourse.addEntpcourse(def)
|
||||
}
|
||||
case 'addEntpcoursefile': { // 添加课程文件
|
||||
params = getDefParams(params)
|
||||
return API_entpcoursefile.addEntpcoursefileReturnId(params)
|
||||
}
|
||||
case 'batchAddNew': { // 批量添加课程文件
|
||||
params = getDefParams(params)
|
||||
return API_entpcoursefile.batchAddNew(params)
|
||||
}
|
||||
case 'getCourseList': { // 获取课程列表
|
||||
return API_entpcourse.listEntpcourse(params)
|
||||
}
|
||||
case 'getCourseFileList': { // 获取课程文件列表
|
||||
return API_entpcoursefile.listEntpcoursefileNew(params)
|
||||
}
|
||||
}
|
||||
}
|
||||
// 获取默认参数
|
||||
const getDefParams = (params) => {
|
||||
const enpt = courseObj.entp
|
||||
const def = {
|
||||
parentid: 0,
|
||||
entpid: userStore.user.deptId,
|
||||
entpcourseid: enpt.id,
|
||||
ppttype: 'file',
|
||||
title: enpt.coursetitle,
|
||||
fileurl: '',
|
||||
filetype: 'aippt',
|
||||
datacontent: '',
|
||||
filekey: '',
|
||||
filetag: '',
|
||||
fileidx: 0,
|
||||
dflag: 0,
|
||||
status: '',
|
||||
edituserid: userStore.id
|
||||
}
|
||||
return Object.assign(def, params)
|
||||
}
|
||||
onMounted(()=>{
|
||||
let data = sessionStore.get('subject.curNode')
|
||||
Object.assign(curNode, data);
|
||||
courseObj.node = data
|
||||
})
|
||||
|
||||
onUnmounted(()=>{
|
||||
emitter.off('onResult')
|
||||
})
|
||||
|
@ -51,6 +326,14 @@ onUnmounted(()=>{
|
|||
background-color: #fff;
|
||||
border-radius: 5px;
|
||||
overflow-y: auto;
|
||||
.right-con-item{
|
||||
display: flex;
|
||||
margin: 10px 0;
|
||||
justify-content: space-around;
|
||||
img{
|
||||
width: 150px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -12,10 +12,10 @@
|
|||
<el-col :span="5">
|
||||
<Left/>
|
||||
</el-col>
|
||||
<el-col :span="13">
|
||||
<el-col :span="15">
|
||||
<Center/>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-col :span="4">
|
||||
<Right/>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
|
Loading…
Reference in New Issue