Merge branch 'main' into zouyf_dev

This commit is contained in:
“zouyf” 2025-01-07 14:21:39 +08:00
commit 66853327cd
9 changed files with 617 additions and 165 deletions

View File

@ -11,15 +11,21 @@
:default-expanded-keys="defaultExpandedKeys" :current-node-key="curNode.data.id" highlight-current :default-expanded-keys="defaultExpandedKeys" :current-node-key="curNode.data.id" highlight-current
@node-click="handleNodeClick"> @node-click="handleNodeClick">
<template #default="{ node, data }"> <template #default="{ node, data }">
<div v-if="props.isClassTask && (data.bookId == '' || data.bookId == '0')"> <div v-if="props.isClassTask && (data.bookId == '' || data.bookId == '0')" class="tree-label-wrap">
<el-tooltip effect="light" placement="right" content="该单元章节无自主试题"> <el-tooltip effect="light" placement="right" >
<span class="tree-label" style="color: #A5B3CA"> <template #content> {{ node.label }}<br /><span style="color: red;">-该单元章节无自主试题-</span> </template>
<span class="tree-label" style="color: #A5B3CA" >
{{ node.label }} {{ node.label }}
</span> </span>
</el-tooltip> </el-tooltip>
</div> </div>
<div v-else> <div v-else class="tree-label-wrap">
<span class="tree-label">{{ node.label }}</span> <el-tooltip effect="light" placement="right" >
<template #content> {{ node.label }}</template>
<span class="tree-label">
{{ node.label }}
</span>
</el-tooltip>
</div> </div>
</template> </template>
</el-tree> </el-tree>
@ -334,7 +340,7 @@ onMounted( async () => {
} }
} }
.tree-label { .tree-label-wrap, .tree-label {
white-space: nowrap; white-space: nowrap;
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;

View File

@ -107,9 +107,13 @@ const getBackGroundV2 = async () => {
} }
}; };
const createOutlineV2 = async (data) => { const createOutlineV2 = async (params) => {
try { 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); console.log("createOutline response:", response);
return response.data; return response.data;

View File

@ -680,6 +680,7 @@ onMounted(async () => {
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;
gap: 15px; gap: 15px;
padding: 10px;
.content-body-right-item{ .content-body-right-item{
&:hover{ &:hover{
cursor: pointer; cursor: pointer;

View File

@ -1,30 +1,120 @@
<template> <template>
<div> <div>
<el-dialog class="ppt-dialog" v-model="model" :show-close="false" width="800" destroy-on-close :top="'3vh'"> <el-dialog class="ppt-dialog" v-model="model" title="生成PPT(试验版)"
<template #header="{ close, titleId, titleClass }"> :close-on-click-modal="false"
<div class="dialog-header"> :close-on-press-escape="false"
<h4 :id="titleId" :class="titleClass">生成PPT(试验版)</h4> :show-close="false" width="1000" destroy-on-close >
<i class="iconfont icon-guanbi" @click="close"></i> <!-- <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>
</template> <div class="ppt-dialog-prog-wrap" v-if="activeStep === 2">
<AiPptist @add-success="addAiPPT" :dataList="dataList"/> <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>
</el-dialog> </el-dialog>
</div> </div>
</template> </template>
<script setup> <script setup>
import AiPptist from './ai-pptistV2.vue'; 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 model = defineModel()
const emit = defineEmits(['addSuccess']) const emit = defineEmits(['addSuccess', 'close-dialogs'])
const backGroundList = ref([]);
const activeStep = ref(1);
const props = defineProps({ const props = defineProps({
dataList: { dataList: {
type: Array, type: Object,
default: () => [] default: () => {}
} }
}) })
const closeDialog = () => {
emit('close-dialogs')
}
const addAiPPT = (data) => { const addAiPPT = (data) => {
emit('addSuccess', 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> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@ -40,4 +130,50 @@
cursor: pointer; 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> </style>

View File

@ -9,19 +9,31 @@
<el-button type="primary" @click="createAi"> <el-button type="primary" @click="createAi">
<i class="iconfont icon-chuangzuo"></i>生成教学大纲 <i class="iconfont icon-chuangzuo"></i>生成教学大纲
</el-button> </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> <i class="iconfont icon-shanchu"></i>
删除大纲 删除大纲
</el-button> </el-button>
<el-button type="primary" @click="isEdit = true"> <el-button type="primary" @click="isEdit = true">
<i class="iconfont icon-bianji"></i>编辑大纲 <i class="iconfont icon-bianji"></i>编辑大纲
</el-button> </el-button>
</div> </div>
</div> </div>
<div class="center-con" v-loading="loading"> <div class="center-con" v-loading="loading">
<TypingEffect v-if="answer" :text="answer" :delay="10" :aiShow="aiShow"/> <!-- <TypingEffect v-if="answer" :text="answer" :delay="10" :aiShow="aiShow"/> -->
<el-empty v-if="!answer" description="请选择符合您需要的教学模式,生成教学大纲" /> <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>
</div> </div>
<EditDialog v-model="isEdit" :item="curItem" /> <EditDialog v-model="isEdit" :item="curItem" />
@ -36,7 +48,7 @@ import emitter from '@/utils/mitt'
import * as commUtils from '@/utils/comm.js' import * as commUtils from '@/utils/comm.js'
import { createChart, sendChart } from '@/api/ai/index' import { createChart, sendChart } from '@/api/ai/index'
import { completion, addSyllabus, syllabuss, removeSyllabus } from '@/api/mode/index.js' 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' import useUserStore from '@/store/modules/user'
const curMode = ref(2) const curMode = ref(2)
@ -64,8 +76,9 @@ emitter.on('selected', (data)=>{
// //
const curItem = reactive({}) const curItem = reactive({})
emitter.on('onShow', (data)=>{ emitter.on('onShow', (data)=>{
console.log(data)
aiShow.value = false aiShow.value = false
answer.value = getResult(data.outline) Object.assign(answer, JSON.parse(data.outline))
Object.assign(curItem, data) Object.assign(curItem, data)
curItem.answer = curItem.outline curItem.answer = curItem.outline
getDetails(data.id) getDetails(data.id)
@ -88,15 +101,14 @@ const params = reactive(
// //
const loading = ref(false) const loading = ref(false)
const answer = ref('') const answer = reactive({})
const createAi = async ()=>{ const createAi = async ()=>{
console.log(selectedData.value)
if(selectedData.value.length == 0){ if(selectedData.value.length == 0){
ElMessage.warning('请先选择教学环节后再生成教学大纲') ElMessage.warning('请先选择教学环节后再生成教学大纲')
return return
} }
let str = selectedData.value.map( item => item.name).join('、') let str = selectedData.value.map( item => item.name).join('、')
let bookV = curNode.roottitle.split('-')[1] + '版' let bookV = curNode.roottitle.split('-')[1] + '版'
loading.value = true loading.value = true
@ -119,17 +131,17 @@ const createAi = async ()=>{
// //
else { else {
const res = await completion(params) const res = await completion(params)
data = res.data data = res.data
} }
console.log(data) const res = await createOutlineV2({query: data.answer})
emitter.emit('onResult', data.answer) console.log(res)
answer.value = getResult(data.answer) emitter.emit('onResult', res)
Object.assign(answer, res.outline)
onSaveTemp(data.answer) onSaveTemp(JSON.stringify(res.outline))
} finally { } finally {
loading.value = false loading.value = false
} }
} }
@ -167,14 +179,9 @@ const delAnswer = () =>{
emitter.emit('resetSelect') emitter.emit('resetSelect')
}) })
.catch(() => {}) .catch(() => {})
} }
// ### **
let getResult = (str) => {
let newStr = str.replace(/#+|(\*\*)/g, '');
return newStr
}
// //
const conversation_id = ref('') const conversation_id = ref('')
@ -188,7 +195,7 @@ const getChartId = () => {
onUnmounted(()=>{ onUnmounted(()=>{
emitter.off('selected') emitter.off('selected')
emitter.off('onShow') emitter.off('onShow')
}) })
const curNode = reactive({}) const curNode = reactive({})
@ -227,7 +234,19 @@ onMounted(() => {
border-radius: 5px; border-radius: 5px;
text-align: left; text-align: left;
overflow-y: auto; 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;
}
}
} }
} }
</style> </style>

View File

@ -4,8 +4,11 @@
<span>教学模式</span> <span>教学模式</span>
<div> <div>
<el-button type="primary" link @click="resetSelect">重置</el-button> <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" <el-button type="primary" link @click="
></i>新增</el-button> addVisible = true,
addChild = false,
isEdit = false
"><i class="iconfont icon-jiahao"></i>新增</el-button>
</div> </div>
</div> </div>
<div class="left-list"> <div class="left-list">
@ -13,33 +16,34 @@
<div class="item-name flex" @mouseenter="item.isAdd = true" @mouseleave="item.isAdd = false" <div class="item-name flex" @mouseenter="item.isAdd = true" @mouseleave="item.isAdd = false"
@click="toggleParent(item)"> @click="toggleParent(item)">
<div class="flex"> <div class="flex">
<span>{{ item.name }}</span> <span>{{ item.name }}</span>
<!--个人教学模式才会有添加编辑--> <!--个人教学模式才会有添加编辑-->
<div v-if="!item.ex3" class="ml-3"> <div v-if="!item.ex3" class="ml-3">
<el-button type="primary" link @click.stop="addModeChild(item)">添加</el-button> <el-button type="primary" link @click.stop="addModeChild(item)">添加</el-button>
<el-button type="primary" link @click.stop="editMode(item)">编辑</el-button> <el-button type="primary" link @click.stop="editMode(item)">编辑</el-button>
</div> </div>
</div> </div>
<!--加号 数鼠标悬浮 显示--> <!--加号 数鼠标悬浮 显示-->
<i v-show="item.isAdd && !item.selected" class="iconfont icon-jiahao"></i> <i v-show="item.isAdd && !item.selected" class="iconfont icon-jiahao"></i>
<!--减号 选中之后显示--> <!--减号 选中之后显示-->
<i v-show="item.selected" class="iconfont icon-zuixiaohua"></i> <i v-show="item.selected" class="iconfont icon-zuixiaohua"></i>
</div> </div>
<div class="item-child flex" :class="child.selected ? 'act-child' : ''" v-for="child in item.children" :key="child.id" <div class="item-child flex" :class="child.selected ? 'act-child' : ''" v-for="child in item.children"
@mouseenter="child.isAdd = true" @mouseleave="child.isAdd = false" @click="toggleChild(item,child)"> :key="child.id" @mouseenter="child.isAdd = true" @mouseleave="child.isAdd = false"
@click="toggleChild(item, child)">
<div> <div>
<span>{{ child.name }}</span> <span>{{ child.name }}</span>
<!--个人教学模式才会有编辑--> <!--个人教学模式才会有编辑-->
<el-button v-if="!child.ex3" class="ml-3" type="primary" link @click.stop="editMode(child)">编辑</el-button> <el-button v-if="!child.ex3" class="ml-3" type="primary" link @click.stop="editMode(child)">编辑</el-button>
</div> </div>
<i v-show="child.isAdd && !child.selected" class="iconfont icon-jiahao"></i> <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> </div>
</div> </div>
<!--弹窗--> <!--弹窗-->
<el-dialog v-model="addVisible" append-to-body :show-close="false" width="550" :before-close="handleBeforeClose" <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> <template #header>
<div class="mode-dialog-header flex"> <div class="mode-dialog-header flex">
<span>{{ addChild ? '教学环节' : '教学模式' }}</span> <span>{{ addChild ? '教学环节' : '教学模式' }}</span>
@ -48,8 +52,8 @@
</template> </template>
<el-form ref="ruleFormRef" style="max-width: 600px" :model="ruleForm" :rules="rules" label-width="auto" <el-form ref="ruleFormRef" style="max-width: 600px" :model="ruleForm" :rules="rules" label-width="auto"
class="demo-ruleForm"> class="demo-ruleForm">
<el-form-item :label="`教学${ addChild ? '环节' : '模式'}名称`" prop="name"> <el-form-item :label="`教学${addChild ? '环节' : '模式'}名称`" prop="name">
<div class="flex" style="width: 100%;"> <div class="flex" style="width: 100%">
<el-input v-model="ruleForm.name" /> <el-input v-model="ruleForm.name" />
<el-button v-if="isEdit" link type="danger" class="ml-5 mr-3" @click="onDel">删除</el-button> <el-button v-if="isEdit" link type="danger" class="ml-5 mr-3" @click="onDel">删除</el-button>
</div> </div>
@ -64,7 +68,6 @@
</div> </div>
</el-dialog> </el-dialog>
</div> </div>
</template> </template>
<script setup> <script setup>
@ -73,131 +76,134 @@ import { modelList } from '@/api/mode/index'
import useUserStore from '@/store/modules/user' import useUserStore from '@/store/modules/user'
import { sessionStore } from '@/utils/store' import { sessionStore } from '@/utils/store'
import emitter from '@/utils/mitt' import emitter from '@/utils/mitt'
import { ElMessage, ElMessageBox } from 'element-plus' import { ElMessage, ElMessageBox } from 'element-plus'
import { addChildTemp, editChildTemp, removeChildTemp, syllabusList } from '@/api/mode' import { addChildTemp, editChildTemp, removeChildTemp, syllabusList } from '@/api/mode'
import { cloneDeep } from 'lodash' import { cloneDeep } from 'lodash'
const { user } = useUserStore() const { user } = useUserStore()
// //
const loading = ref(false) 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 templateList = ref([])
const getChildTemp = async () => { const getTemplate = async () => {
templateList.value = []
loading.value = true loading.value = true
const { rows } = await modelList({
createUser: user.userId,
model: 4,
pageNum: 1,
pageSize: 10000
})
loading.value = false
for (let item of list.value) { // type 1
try { let ary1 = rows.filter((item) => item.type === 1)
let { rows } = await modelList({ model: 4, type: 2, parentId: item.id }) templateList.value = ary1.map((parent) => {
templateList.value.push({ parent.children = rows.filter((child) => child.type === 2 && child.parentId === parent.id)
...item, return parent
children: rows })
})
} finally {
loading.value = false
}
}
getSyllabus() getSyllabus()
} }
// //
const getSyllabus = async () =>{ const getSyllabus = async () => {
const { rows } = await syllabusList() const { rows } = await syllabusList({
if(rows && rows.length){ createUserId: user.userId,
const idsAry = rows.at(-1).modelIds.split(',').map(item => Number(item)); 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) => { templateList.value.forEach((parent) => {
parent.children.forEach((child) => { parent.children.forEach((child) => {
child.selected = idsAry.includes(child.id); child.selected = idsAry.includes(child.id)
}); if (child.selected) {
ary.push(child)
}
})
// selected // 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('onShow', rows.at(-1))
emitter.emit('selected', ary)
} }
} }
// //
const resetSelect = () =>{ const resetSelect = () => {
templateList.value.forEach(item =>{ templateList.value.forEach((item) => {
item.selected = false item.selected = false
item.children.forEach( el=>{ item.children.forEach((el) => {
el.selected = false el.selected = false
}) })
}) })
emitter.emit('selected', []) emitter.emit('selected', [])
} }
emitter.on('resetSelect', () =>{ emitter.on('resetSelect', () => {
resetSelect() resetSelect()
}) })
// //
const toggleParent = (parent) => { const toggleParent = (parent) => {
parent.selected = !parent.selected; parent.selected = !parent.selected
parent.children.forEach(child => { parent.children.forEach((child) => {
child.selected = parent.selected; child.selected = parent.selected
}); })
const selectedData = templateList.value.map((item) => item.children.filter((child) => child.selected)).flat() const selectedData = templateList.value
// .flat() .map((item) => item.children.filter((child) => child.selected))
.flat()
// .flat()
emitter.emit('selected', selectedData) emitter.emit('selected', selectedData)
} }
// //
const toggleChild = (parent,child) =>{ const toggleChild = (parent, child) => {
child.selected = !child.selected; child.selected = !child.selected
updateParentSelection(parent) 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) emitter.emit('selected', selectedData)
} }
// ()() // ()()
const updateParentSelection = (parent) => { const updateParentSelection = (parent) => {
parent.selected = parent.children.every((child) => child.selected); parent.selected = parent.children.every((child) => child.selected)
} }
// //
const addVisible = ref(false) const addVisible = ref(false)
const ruleFormRef = ref() const ruleFormRef = ref()
const rules = reactive({ const rules = reactive({
name: [ name: [{ required: true, message: '不能为空', trigger: 'blur' }]
{ required: true, message: '不能为空', trigger: 'blur' },
]
}) })
const ruleForm = reactive({ const ruleForm = reactive({
name: '', name: '',
prompt: '' prompt: ''
}) })
// //
const onSubmit = async (formEl) =>{ const onSubmit = async (formEl) => {
if (!formEl) return if (!formEl) return
await formEl.validate(async (valid, fields) => { await formEl.validate(async (valid, fields) => {
if (valid) { if (valid) {
// //
if(isEdit.value){ if (isEdit.value) {
let data = cloneDeep(curEditItem) let data = cloneDeep(curEditItem)
data.name = ruleForm.name data.name = ruleForm.name
await editChildTemp(data) await editChildTemp(data)
} }
// //
else{ else {
let obj = { let obj = {
ex1: curNode.edustage, ex1: curNode.edustage,
ex2: curNode.edusubject, ex2: curNode.edusubject,
@ -207,13 +213,12 @@ const onSubmit = async (formEl) =>{
type: 1 type: 1
} }
// //
if(addChild.value){ if (addChild.value) {
obj.parentId = curEditItem.id obj.parentId = curEditItem.id
obj.prompt = ruleForm.prompt obj.prompt = ruleForm.prompt
obj.type = 2 obj.type = 2
} }
await addChildTemp(obj) await addChildTemp(obj)
} }
ElMessage.success('操作成功') ElMessage.success('操作成功')
resetForm() resetForm()
@ -231,16 +236,16 @@ const isEdit = ref(false)
const addChild = ref(false) const addChild = ref(false)
const curEditItem = reactive({}) const curEditItem = reactive({})
const addModeChild = (item) =>{ const addModeChild = (item) => {
isEdit.value = false isEdit.value = false
Object.assign(curEditItem, item) Object.assign(curEditItem, item)
addChild.value = true addChild.value = true
addVisible.value = true addVisible.value = true
} }
// //
const editMode = (item)=>{ const editMode = (item) => {
isEdit.value = true isEdit.value = true
addChild.value = false addChild.value = false
Object.assign(curEditItem, item) Object.assign(curEditItem, item)
@ -249,16 +254,12 @@ const editMode = (item)=>{
} }
// //
const onDel = () =>{ const onDel = () => {
ElMessageBox.confirm( ElMessageBox.confirm('确定要删除模板吗?', '温馨提示', {
'确定要删除模板吗?', confirmButtonText: '确定',
'温馨提示', cancelButtonText: '取消',
{ type: 'warning'
confirmButtonText: '确定', })
cancelButtonText: '取消',
type: 'warning',
}
)
.then(async () => { .then(async () => {
await removeChildTemp(curEditItem.id) await removeChildTemp(curEditItem.id)
ElMessage.success('操作成功') ElMessage.success('操作成功')
@ -266,36 +267,33 @@ const onDel = () =>{
addVisible.value = false addVisible.value = false
getTemplate() getTemplate()
}) })
.catch(() => {}) .catch(() => { })
} }
const closeDialog = () => {
const closeDialog = () =>{ handleBeforeClose(() => (addVisible.value = false))
handleBeforeClose(() => addVisible.value = false);
} }
const handleBeforeClose = (done) =>{ const handleBeforeClose = (done) => {
resetForm() resetForm()
done() done()
} }
const resetForm = () => { const resetForm = () => {
if (ruleFormRef.value) { if (ruleFormRef.value) {
ruleFormRef.value.resetFields(); ruleFormRef.value.resetFields()
} }
}; }
const curNode = reactive({}) const curNode = reactive({})
onMounted(() => { onMounted(() => {
let data = sessionStore.get('subject.curNode') let data = sessionStore.get('subject.curNode')
Object.assign(curNode, data); Object.assign(curNode, data)
getTemplate() getTemplate()
}) })
onUnmounted(() => {
onUnmounted(()=>{
emitter.off('resetSelect') emitter.off('resetSelect')
}) })
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@ -342,7 +340,7 @@ onUnmounted(()=>{
position: relative; position: relative;
&::after { &::after {
content: ""; content: '';
width: 5px; width: 5px;
height: 5px; height: 5px;
border-radius: 50%; border-radius: 50%;
@ -352,7 +350,6 @@ onUnmounted(()=>{
top: 50%; top: 50%;
transform: translate(0, -50%); transform: translate(0, -50%);
} }
} }
.iconfont { .iconfont {
@ -389,8 +386,9 @@ onUnmounted(()=>{
cursor: pointer; cursor: pointer;
} }
} }
.form-btn{
.form-btn {
text-align: right; text-align: right;
margin-bottom: 10px; margin-bottom: 10px;
} }
</style> </style>

View File

@ -1,6 +1,6 @@
<template> <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-progress type="dashboard" v-bind="$attrs.pg" />
</el-dialog> </el-dialog>
</template> </template>
@ -29,5 +29,10 @@ const dAttrs = computed(() => {
}) })
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
</style>
</style> <style>
.aix-progress-dialog .el-dialog__body{
display: flex;
justify-content: center;
}
</style>

View File

@ -4,22 +4,73 @@
<span>课件预览</span> <span>课件预览</span>
<div> <div>
<el-button type="danger" @click="onCreate">一键生成</el-button> <el-button type="danger" @click="onCreate">一键生成</el-button>
<el-button>编辑课件</el-button> <el-button :disabled="!result?.parentId" @click="openAiPPT">编辑课件</el-button>
</div> </div>
</div> </div>
<div class="right-con"> <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>
</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> </template>
<script setup> <script setup>
import { ref, onUnmounted } from 'vue' import {ref, onUnmounted, onMounted, reactive} from 'vue'
import { ElMessage } from 'element-plus' import { ElMessage } from 'element-plus'
import emitter from '@/utils/mitt' 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)=>{ emitter.on('onResult', (data)=>{
result.value = data result.value = data
if (!!result.value.parentId) {
listEntpcoursefileNew({parentid: result.value.parentId}).then(res=>{
pptSlides.value = res.rows
})
}
}) })
const onCreate = () =>{ const onCreate = () =>{
@ -27,8 +78,232 @@ const onCreate = () =>{
ElMessage.warning('请先进行研读') ElMessage.warning('请先进行研读')
return 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 resPPT
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(()=>{ onUnmounted(()=>{
emitter.off('onResult') emitter.off('onResult')
}) })
@ -51,7 +326,15 @@ onUnmounted(()=>{
background-color: #fff; background-color: #fff;
border-radius: 5px; border-radius: 5px;
overflow-y: auto; overflow-y: auto;
.right-con-item{
display: flex;
margin: 10px 0;
justify-content: space-around;
img{
width: 150px;
}
}
} }
} }
</style> </style>

View File

@ -1,25 +1,25 @@
<template> <template>
<div class="page-design"> <div class="page-design">
<!-- <div class="page-left"> <!-- <div class="page-left">
<left /> <left />
</div> </div>
<div class="page-right"> <div class="page-right">
<right /> <right />
</div> --> </div> -->
<el-row :gutter="20"> <el-row :gutter="20">
<el-col :span="5"> <el-col :span="5">
<Left/> <Left/>
</el-col> </el-col>
<el-col :span="13"> <el-col :span="15">
<Center/> <Center/>
</el-col> </el-col>
<el-col :span="6"> <el-col :span="4">
<Right/> <Right/>
</el-col> </el-col>
</el-row> </el-row>
</div> </div>
</template> </template>
@ -51,4 +51,4 @@ import Right from './container/right2.vue'
// width: 50%; // width: 50%;
// } // }
// } // }
</style> </style>