Merge pull request 'lyc-dev' (#207) from lyc-dev into main

This commit is contained in:
lyc 2025-01-07 16:57:54 +08:00
commit 284ae40519
10 changed files with 618 additions and 1975 deletions

View File

@ -136,14 +136,6 @@ export function syllabusList(params) {
}) })
} }
// 大纲详情
export function syllabuss(id) {
return request({
url: '/education/generate/' + id,
method: 'get',
})
}
// 删除大纲 // 删除大纲
export function removeSyllabus(id) { export function removeSyllabus(id) {
return request({ return request({

View File

@ -1,282 +0,0 @@
<template>
<el-dialog v-model="isDialog" :show-close="false" width="800" destroy-on-close>
<template #header>
<div class="custom-header flex">
<span>{{ item.name }}</span>
<i class="iconfont icon-guanbi" @click="isDialog = false"></i>
</div>
</template>
<div class="dialog-content">
<el-scrollbar height="400px">
<div class="chart-con flex">
<template v-for="item in msgList">
<div class="flex-end flex" v-if="item.type == 'user'">
<div class="chart-item user">{{ item.msg }}</div>
</div>
<div class="flex-start flex" v-else>
<div class="flex" v-loading="!item.msg">
<div class="chart-item robot">{{ item.msg }}</div>
</div>
<div class="flex flex-end replace-item">
<span @click="saveAdjust(item)"><i class="iconfont icon-tihuan"></i>替换分析结果</span>
</div>
</div>
<div v-if="loaded" class="chart-loading">
<div></div>
<div></div>
<div></div>
</div>
</template>
</div>
</el-scrollbar>
<div class="input-box flex">
<el-input v-model="textarea" @keyup.enter="send" :disabled="loaded" />
<div class="ipt-icon" @click="send">
<i class="iconfont icon-fasong"></i>
</div>
</div>
</div>
</el-dialog>
</template>
<script setup>
import { ref, reactive, onMounted } from 'vue'
import { completion } from '@/api/mode/index'
import { dataSetJson } from '@/utils/comm.js'
import emitter from '@/utils/mitt';
import { sessionStore } from '@/utils/store'
import { sendChart } from '@/api/ai/index'
const textarea = ref('')
const isDialog = defineModel()
const props = defineProps({
item: {
type: Object,
default: () => {
return { name: '11' }
}
},
curMode:{
type: Number,
default: 1
},
conversation_id: {
type: [Number, String],
default: ''
}
})
const emit = defineEmits(['saveEdit'])
const loaded = ref(false)
const msgList = ref([])
const send = () => {
if (loaded.value) return
msgList.value.push({
type: 'user',
msg: textarea.value
})
loaded.value = true
getConversation(textarea.value)
textarea.value = ''
}
const curNode = reactive({})
const params = reactive(
{
prompt: '',
dataset_id: '',
template: ''
}
)
//
const getConversation = async (val) => {
try {
params.prompt = `按照${val}的要求,针对${curNode.edustage}${curNode.edusubject}课标,对${curNode.itemtitle}进行教学分析`
params.template = props.item.prompt
let data = null;
//
if(props.curMode == 1){
const res = await sendChart({
content: params.prompt,
conversationId: props.conversation_id,
stream: false
})
data = res.data
}
else{
//
const res = await completion(params)
data = res.data
}
msgList.value.push({
type: 'robot',
msg: data.answer,
})
} finally {
loaded.value = false
}
}
const saveAdjust = (item) => {
isDialog.value = false
emitter.emit('onSaveAdjust', item.msg)
}
onMounted(() => {
let data = sessionStore.get('subject.curNode')
Object.assign(curNode, data);
// dataset_id
let jsonKey = `课标-${data.edustage}-${data.edusubject}`
params.dataset_id = dataSetJson[jsonKey]
})
</script>
<style lang="scss" scoped>
.custom-header {
justify-content: space-between;
align-items: center;
.icon-guanbi {
cursor: pointer;
font-weight: bold;
}
}
.dialog-content {
padding-top: 10px;
padding-bottom: 20px;
.chart-con {
flex-direction: column;
align-items: flex-start;
.flex-end {
width: 100%;
justify-content: flex-end;
}
.flex-start {
width: 100%;
justify-content: flex-start;
flex-direction: column;
}
.chart-item {
border-radius: 5px;
padding: 5px;
text-align: left;
}
.user {
background: #F2F2F2;
margin-bottom: 10px;
}
.robot {
background: #409EFF;
color: #FFF;
}
.replace-item {
font-size: 12px;
color: #409EFF;
align-items: center;
cursor: pointer;
}
}
.input-box {
position: relative;
.ipt-icon {
cursor: pointer;
padding: 0 5px;
.icon-fasong {
font-size: 26px;
color: #409EFF;
}
}
}
}
.chart-loading,
.chart-loading>div {
position: relative;
box-sizing: border-box;
}
.chart-loading {
display: block;
font-size: 0;
color: #66b1ff;
}
.chart-loading.la-dark {
color: #66b1ff;
}
.chart-loading>div {
display: inline-block;
float: none;
background-color: currentColor;
border: 0 solid currentColor;
}
.chart-loading {
width: 54px;
height: 18px;
display: flex;
align-items: center;
justify-content: center;
}
.chart-loading>div:nth-child(1) {
animation-delay: -200ms;
}
.chart-loading>div:nth-child(2) {
animation-delay: -100ms;
}
.chart-loading>div:nth-child(3) {
animation-delay: 0ms;
}
.chart-loading>div {
width: 8px;
height: 8px;
border-radius: 100%;
margin-right: 4px;
animation: ball-pulse 1s ease infinite;
}
@keyframes ball-pulse {
0%,
60%,
100% {
opacity: 1;
transform: scale(1);
}
30% {
opacity: 0.1;
transform: scale(0.01);
}
}
</style>

View File

@ -13,30 +13,34 @@
<i class="iconfont icon-shanchu"></i> <i class="iconfont icon-shanchu"></i>
删除大纲 删除大纲
</el-button> </el-button>
<el-button type="primary" @click="isEdit = true">
<i class="iconfont icon-bianji"></i>编辑大纲
</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"/> --> <template v-if="answer.title">
<div style="font-size: 18px;color: #409eff;">封面页</div> <div class="flex justify-between">
<span style="font-size: 18px;color: #409eff;">封面页</span>
<el-button type="primary" link @click="onEdit(item, -1)">编辑</el-button>
</div>
<div class="con-item mb-5"> <div class="con-item mb-5">
<div class="item-name">标题{{ answer.title }}</div> <div class="item-name">标题{{ answer.title }}</div>
<div class="item-name">副标题{{ answer.subTitle }}</div> <div class="item-name">副标题{{ answer.subTitle }}</div>
</div> </div>
<div style="font-size: 18px;color: #409eff;">目录页</div> <div style="font-size: 18px;color: #409eff;">目录页</div>
<div class="con-item" v-for="(item, index) in answer.chapters"> <div class="con-item" v-for="(item, index) in answer.chapters">
<div class="item-name">{{index + 1}}{{ item.chapterTitle }}</div> <div class="item-name">
<span>{{ index + 1 }}{{ item.chapterTitle }}</span>
<el-button type="primary" link @click="onEdit(item, index)">编辑</el-button>
</div>
<div class="item-text"> <div class="item-text">
<p v-for="(el, i) in item.chapterContents">{{ index + 1 }} - {{ i + 1 }} : {{ el.chapterTitle }}</p> <p v-for="(el, i) in item.chapterContents">{{ index + 1 }} - {{ i + 1 }} : {{ el.chapterTitle }}</p>
</div> </div>
</div> </div>
<el-empty v-if="!answer.title" description="请选择符合您需要的教学模式,生成教学大纲" /> </template>
<el-empty v-else description="请选择符合您需要的教学模式,生成教学大纲" />
</div> </div>
</div> </div>
<EditDialog v-model="isEdit" :item="curItem" /> <EditDialog v-model="isEdit" :item="editItem" :index="editIndex" />
</template> </template>
<script setup> <script setup>
@ -47,9 +51,10 @@ import EditDialog from './edit-dialog.vue'
import emitter from '@/utils/mitt' 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, removeSyllabus, editSyllabus } from '@/api/mode/index.js'
import { createOutlineV2 } from '@/utils/ppt-request.js' import { createOutlineV2 } from '@/utils/ppt-request.js'
import useUserStore from '@/store/modules/user' import useUserStore from '@/store/modules/user'
import { cloneDeep } from 'lodash'
const curMode = ref(2) const curMode = ref(2)
const isEdit = ref(false) const isEdit = ref(false)
@ -76,20 +81,14 @@ 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
Object.assign(answer, JSON.parse(data.outline)) Object.assign(answer, JSON.parse(data.outline))
Object.assign(curItem, data) Object.assign(curItem, data)
curItem.answer = curItem.outline curItem.outline = JSON.parse(curItem.outline)
getDetails(data.id) emitter.emit('onResult',curItem)
}) })
const getDetails = (id) =>{
syllabuss(id).then( res =>{
Object.assign(curItem, res.data)
emitter.emit('onResult', res.data)
})
}
const params = reactive( const params = reactive(
{ {
@ -135,15 +134,56 @@ const createAi = async ()=>{
data = res.data data = res.data
} }
const res = await createOutlineV2({ query: data.answer }) const res = await createOutlineV2({ query: data.answer })
console.log(res)
emitter.emit('onResult', res)
Object.assign(answer, res.outline) Object.assign(answer, res.outline)
curItem.outline = res.outline
emitter.emit('onResult', curItem)
onSaveTemp(JSON.stringify(res.outline)) onSaveTemp(JSON.stringify(res.outline))
} finally { } finally {
loading.value = false loading.value = false
} }
} }
//
const editItem = reactive({})
const editIndex = ref(0)
const onEdit = (item, index)=>{
let obj = null
if(index == -1){
obj = {
title: answer.title,
subTitle: answer.subTitle
}
}
else{
obj = cloneDeep(item)
}
editIndex.value = index
isEdit.value = true
Object.assign(editItem, obj)
}
emitter.on('editItem', (item) =>{
if(editIndex.value == -1){
answer.title = item.title
answer.subTitle = item.subTitle
}else{
answer.chapters[editIndex.value] = item
}
let data = cloneDeep(curItem)
data.outline = JSON.stringify(cloneDeep(answer))
loading.value = true
editSyllabus(data).then( res =>{
curItem.outline = answer
emitter.emit('onResult', curItem)
ElMessage.success('操作成功')
}).finally( ()=>{
loading.value = false
})
})
// //
const onSaveTemp = async (answer) => { const onSaveTemp = async (answer) => {
@ -195,6 +235,8 @@ const getChartId = () => {
onUnmounted(() => { onUnmounted(() => {
emitter.off('selected') emitter.off('selected')
emitter.off('onShow') emitter.off('onShow')
emitter.off('editItem')
}) })
@ -220,13 +262,16 @@ onMounted(() => {
height: 100%; height: 100%;
font-size: 15px; font-size: 15px;
flex-direction: column; flex-direction: column;
.center-header { .center-header {
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
.icon-jiahao { .icon-jiahao {
font-size: 12px; font-size: 12px;
} }
} }
.center-con { .center-con {
flex: 1; flex: 1;
margin-top: 5px; margin-top: 5px;
@ -235,10 +280,15 @@ onMounted(() => {
text-align: left; text-align: left;
overflow-y: auto; overflow-y: auto;
padding: 15px; padding: 15px;
.con-item { .con-item {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
margin-top: 15px; margin-top: 15px;
.item-name{
display: flex;
justify-content: space-between;
}
.item-text { .item-text {
background: #F2F2F2; background: #F2F2F2;
padding: 15px; padding: 15px;

View File

@ -7,11 +7,28 @@
</div> </div>
</template> </template>
<div class="dialog-content" v-loading="loading"> <div class="dialog-content" v-loading="loading">
<el-row> <template v-if="props.index == -1">
<el-col :span="24"> <div class="flex mb-5">
<el-input v-model="textarea" :autosize="{ minRows: 5, maxRows: 15 }" type="textarea" /> <span class="name">标题</span>
</el-col> <el-input v-model="editItem.title" />
</el-row> </div>
<div class="flex mb-5">
<span class="name">副标题</span>
<el-input v-model="editItem.subTitle" />
</div>
</template>
<template v-else>
<div class="flex mb-5">
<span class="name">标题</span>
<el-input v-model="editItem.chapterTitle" />
</div>
<div class="flex">
<span class="name">内容</span>
<div class="flex edit-con">
<el-input class="mb-3" v-model="item.chapterTitle" v-for="item in editItem.chapterContents" />
</div>
</div>
</template>
</div> </div>
<template #footer> <template #footer>
@ -26,13 +43,13 @@
</template> </template>
<script setup> <script setup>
import { ref, watch} from 'vue' import { reactive, ref, watch} from 'vue'
import { ElMessage } from 'element-plus' import { ElMessage } from 'element-plus'
import { editSyllabus } from '@/api/mode/index.js' import { editSyllabus } from '@/api/mode/index.js'
import { cloneDeep } from 'lodash'; import { cloneDeep } from 'lodash';
import emitter from '@/utils/mitt'; import emitter from '@/utils/mitt';
const textarea = ref('')
const isDialog = defineModel() const isDialog = defineModel()
const loading = ref(false) const loading = ref(false)
@ -40,20 +57,28 @@ const loading = ref(false)
const props = defineProps({ const props = defineProps({
item: { item: {
type: Object, type: Object,
default: () => {
return { name: '11' } },
} index: {
type: [Number, String]
} }
}) })
watch(() => props.item.answer, (newVal) => { const editItem = reactive({})
watch(() => isDialog.value, (newVal) => {
if(newVal){ if(newVal){
textarea.value = newVal let data = cloneDeep(props.item)
Object.assign(editItem, data)
} }
},{ deep: true }) },{ deep: true })
const emit = defineEmits(['saveEdit']) const emit = defineEmits(['saveEdit'])
const onSave = () =>{ const onSave = () =>{
emitter.emit('editItem', editItem)
isDialog.value = false
return
loading.value = true loading.value = true
let data = cloneDeep(props.item) let data = cloneDeep(props.item)
data.outline = textarea.value data.outline = textarea.value
@ -83,6 +108,12 @@ const onSave = () =>{
.dialog-content { .dialog-content {
padding-top: 10px; padding-top: 10px;
.name{
flex-shrink: 0;
}
.edit-con{
width: 100%;
flex-direction: column;
}
} }
</style> </style>

View File

@ -1,159 +0,0 @@
<template>
<el-dialog v-model="mode" :show-close="false" width="600" append-to-body destroy-on-close>
<template #header>
<div class="custom-header flex">
<span>{{ item.ex3 == '1' ? '请输入新的模板名称' : item.isAdd ? '添加提示词' : '编辑提示词' }}</span>
<i class="iconfont icon-guanbi" @click="mode = false"></i>
</div>
</template>
<div class="dialog-content" v-loading="loading">
<p class="small-tip" v-if="item && item.ex3 == '1'">*当前模板为系统预设不支持直接操作需要复制一份为自己的然后再操作</p>
<el-form :model="form" label-width="auto">
<el-form-item label="名称">
<el-input v-model="form.name" />
</el-form-item>
<el-form-item label="提示词" v-if="item.ex3 == '1' ? false : true">
<el-input v-model="form.prompt" type="textarea" />
</el-form-item>
</el-form>
</div>
<template #footer>
<div class="dialog-footer">
<el-button @click="mode = false">取消</el-button>
<el-button type="primary" @click="saveAdd">
确定
</el-button>
</div>
</template>
</el-dialog>
</template>
<script setup>
import { ref, reactive, watch } from 'vue'
import { ElMessage } from 'element-plus'
import emitter from '@/utils/mitt';
import { addKeyWords, addChildTemp, editChildTemp } from '@/api/mode/index'
const mode = defineModel()
const props = defineProps({
modeType: {
type: Number,
default: 1
},
item: { //
type: Object,
default: () => {
return { ex3: '' }
}
},
})
const form = reactive({
name: '',
prompt: '',
})
watch(() => mode.value, (newVal) => {
if(newVal){
if (props.item.isAdd) {
form.name = ''
form.prompt = ''
}
else{
form.name = props.item?.name
form.prompt = props.item?.prompt
}
}
},{ deep: true})
const loading = ref(false)
const saveAdd = async () => {
loading.value = true
if (props.item.ex3 == '1') {
let id; // id id
if (props.item.isAdd) {
id = props.item.id
}
else{
// item item.parentId
id = props.item.parentId
}
try {
// copy
const { msg } = await addKeyWords({ name: form.name, id })
emitter.emit('onGetMain', props.item)
ElMessage.success(msg)
mode.value = false
} finally {
loading.value = false
}
} else {
if (props.item.isAdd) {
onAddChildTemp(props.item.id)
}
else {
try {
let data = JSON.parse(JSON.stringify(props.item))
data.name = form.name;
data.prompt = form.prompt
const { msg } = await editChildTemp(data)
emitter.emit('onGetChild', data )
ElMessage.success(msg)
mode.value = false
} finally {
loading.value = false
}
}
}
}
//
const onAddChildTemp = async (parentId) => {
//
let obj = {
name: form.name,
type: 2, // 2
sortNum: 1,
parentId,
lmType: 1,
model: props.modeType,
prompt: form.prompt,
ex1: props.item.ex1, //
ex2: props.item.ex2, //
ex3: '', //
}
try {
var { msg } = await addChildTemp(obj)
emitter.emit('onGetChild')
ElMessage.success(msg)
mode.value = false
} finally {
loading.value = false
}
}
</script>
<style lang="scss" scoped>
.custom-header {
justify-content: space-between;
align-items: center;
.icon-guanbi {
cursor: pointer;
font-weight: bold;
}
}
.small-tip {
text-align: left;
font-size: 12px;
margin-bottom: 15px;
color: #F56C6C;
}
</style>

View File

@ -1,193 +1,394 @@
<template> <template>
<div class="container-left flex"> <div class="container-left flex" v-loading="loading">
<div class="left-header flex">教学模式</div> <div class="left-header flex">
<div class="left-con" v-loading="loading"> <span>教学模式</span>
<el-empty v-if="!(tempList.length)" description="暂无数据" /> <div>
<div class="con-item" v-for="item in tempList" :key="item.id" :class=" actId == item.id ? 'item-act' : ''"> <el-button type="primary" link @click="resetSelect">重置</el-button>
<div class="item-header flex"> <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">
<div class="item" v-for="item in templateList" :key="item.id">
<div class="item-name flex" @mouseenter="item.isAdd = true" @mouseleave="item.isAdd = false"
@click="toggleParent(item)">
<div class="flex">
<span>{{ item.name }}</span> <span>{{ item.name }}</span>
<el-button type="primary" link @click="onSelect(item)">选择模式</el-button> <!--个人教学模式才会有添加编辑-->
<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="editMode(item)">编辑</el-button>
</div> </div>
<div class="content-list">
<div class="item-list flex" >
<el-card class="item-card" shadow="never" v-for="el in item.child" :key="el.id">
<p class="card-name">
<el-text line-clamp="1" :title="el.name">
{{ el.name }}
</el-text>
</p>
<div class="card-text">
<el-text line-clamp="4" :title="el.prompt">
{{ el.prompt }}
</el-text>
</div> </div>
</el-card> <!--加号 数鼠标悬浮 显示-->
<i v-show="item.isAdd && !item.selected" class="iconfont icon-jiahao"></i>
<!--减号 选中之后显示-->
<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>
<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>
</div> </div>
</div> </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">
<template #header>
<div class="mode-dialog-header flex">
<span>{{ addChild ? '教学环节' : '教学模式' }}</span>
<i class="iconfont icon-guanbi" @click="addVisible = false"></i>
</div> </div>
</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-input v-model="ruleForm.name" />
<el-button v-if="isEdit" link type="danger" class="ml-5 mr-3" @click="onDel">删除</el-button>
</div>
</el-form-item>
<el-form-item label="说明" v-if="addChild" prop="prompt">
<el-input v-model="ruleForm.prompt" type="textarea" />
</el-form-item>
</el-form>
<div class="mt-10 form-btn">
<el-button @click="closeDialog">关闭</el-button>
<el-button type="primary" @click="onSubmit(ruleFormRef)">保存</el-button>
</div>
</el-dialog>
</div> </div>
</template> </template>
<script setup> <script setup>
import { ref, reactive, onMounted, onUnmounted } from 'vue'; import { ref, reactive, onMounted, onUnmounted } from 'vue'
import emitter from '@/utils/mitt';
import { modelList } from '@/api/mode/index' 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 { ElMessage, ElMessageBox } from 'element-plus'
import { addChildTemp, editChildTemp, removeChildTemp, syllabusList } from '@/api/mode'
import { cloneDeep } from 'lodash'
const { user } = useUserStore() const { user } = useUserStore()
// //
let list = ref([])
const getTemplate = async (id) => {
const { rows } = await modelList({ createUser: user.userId, model: 4, type: 1, pageNum: 1, pageSize: 10000})
list.value = rows
if(list.value.length){
getChildTemp(id)
}
}
//
const actId = ref('')
const loading = ref(false) const loading = ref(false)
const tempList = ref([]) const templateList = ref([])
const getChildTemp = async (parentId) => { const getTemplate = async () => {
tempList.value.length = 0
loading.value = true loading.value = true
for (let item of list.value) { const { rows } = await modelList({
try { createUser: user.userId,
const { rows } = await modelList({ model: 4, type: 2, parentId: item.id }) model: 4,
tempList.value.push({ pageNum: 1,
id: item.id, pageSize: 10000
name: item.name,
child: rows
}) })
} finally {
loading.value = false 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()
} }
actId.value = tempList.value[0].id
//
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)
if (child.selected) {
ary.push(child)
} }
if(parentId){ })
const item = tempList.value.find(item => item.id == parentId) // selected
emitter.emit('changeMode', item) parent.selected = parent.children.every((child) => child.selected)
})
//
emitter.emit('onShow', rows.at(-1))
emitter.emit('selected', ary)
} }
}
//
const resetSelect = () => {
templateList.value.forEach((item) => {
item.selected = false
item.children.forEach((el) => {
el.selected = false
})
})
emitter.emit('selected', [])
}
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()
// .flat()
emitter.emit('selected', selectedData)
}
//
const toggleChild = (parent, child) => {
child.selected = !child.selected
updateParentSelection(parent)
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)
}
//
const addVisible = ref(false)
const ruleFormRef = ref()
const rules = reactive({
name: [{ required: true, message: '不能为空', trigger: 'blur' }]
})
const ruleForm = reactive({
name: '',
prompt: ''
})
//
const onSubmit = async (formEl) => {
if (!formEl) return
await formEl.validate(async (valid, fields) => {
if (valid) {
//
if (isEdit.value) {
let data = cloneDeep(curEditItem)
data.name = ruleForm.name
await editChildTemp(data)
}
//
else { else {
emitter.emit('changeMode', tempList.value[0]) let obj = {
ex1: curNode.edustage,
ex2: curNode.edusubject,
model: 4,
name: ruleForm.name,
sortNum: 1,
type: 1
} }
//
if (addChild.value) {
obj.parentId = curEditItem.id
obj.prompt = ruleForm.prompt
obj.type = 2
}
await addChildTemp(obj)
}
ElMessage.success('操作成功')
resetForm()
addVisible.value = false
getTemplate()
} else {
console.log('error submit!', fields)
}
})
} }
//
const isEdit = ref(false)
// false true
const addChild = ref(false)
// const curEditItem = reactive({})
emitter.on('onGetChild', async (data) => { const addModeChild = (item) => {
isEdit.value = false
await getTemplate(data.parentId) Object.assign(curEditItem, item)
addChild.value = true
}) addVisible.value = true
//
emitter.on('onGetMain', (item) => {
getTemplate(item.parentId)
})
//
const emit = defineEmits([''])
const onSelect = (item) =>{
actId.value = item.id
item.child.forEach(el =>{
el.aiShow = false
})
emitter.emit('changeMode', item)
} }
//
const editMode = (item) => {
isEdit.value = true
addChild.value = false
Object.assign(curEditItem, item)
ruleForm.name = item.name
addVisible.value = true
}
//
const onDel = () => {
ElMessageBox.confirm('确定要删除模板吗?', '温馨提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
})
.then(async () => {
await removeChildTemp(curEditItem.id)
ElMessage.success('操作成功')
resetForm()
addVisible.value = false
getTemplate()
})
.catch(() => { })
}
const closeDialog = () => {
handleBeforeClose(() => (addVisible.value = false))
}
const handleBeforeClose = (done) => {
resetForm()
done()
}
const resetForm = () => {
if (ruleFormRef.value) {
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('onGetMain');
emitter.off('onGetChild');
}) })
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.container-left { .container-left {
width: 100%;
height: 100%; height: 100%;
font-size: 15px;
flex-direction: column; flex-direction: column;
.left-header { .left-header {
height: 45px; height: 32px;
background: #F6F6F6;
border-radius: 5px 0 0 0;
align-items: center;
font-size: 14px;
font-weight: bold;
text-indent: 2em;
}
.left-con {
flex: 1;
background: #fff;
border-radius: 0 0 0 5px;
padding: 15px;
box-sizing: border-box;
font-size: 14px;
overflow: auto;
.item-header {
justify-content: space-between; justify-content: space-between;
margin-bottom: 5px; align-items: center;
}
.con-item {
margin-bottom: 20px;
display: flex;
flex-direction: column;
padding: 10px;
}
.item-list{
padding: 10px; .icon-jiahao {
font-size: 12px;
} }
.item-act{ }
border: solid 1px #409eff;
.left-list {
flex: 1;
margin-top: 5px;
background-color: #fff;
border-radius: 5px; border-radius: 5px;
}
.item-card {
width: 130px;
font-size: 13px;
padding: 10px;
margin-right: 20px;
flex-shrink: 0;
border-radius: 10px;
:deep(.el-card__body) {
padding: 0 !important;
}
.card-name {
text-align: center;
font-size: 14px;
}
.card-text {
text-align: left; text-align: left;
padding: 0 10px;
line-height: 30px;
overflow-y: auto;
.el-text{ .item-name {
font-size: 12px !important; line-height: 40px;
font-size: 14px;
cursor: pointer;
justify-content: space-between;
padding-right: 10px;
}
.item-child {
padding-left: 3em;
justify-content: space-between;
cursor: pointer;
align-items: center;
padding-right: 10px;
margin-bottom: 3px;
position: relative;
&::after {
content: '';
width: 5px;
height: 5px;
border-radius: 50%;
background-color: #666;
position: absolute;
left: 2em;
top: 50%;
transform: translate(0, -50%);
}
}
.iconfont {
font-weight: bold;
font-size: 18px;
}
.icon-jiahao {
color: #409eff;
}
.icon-zuixiaohua {
color: #f56c6c;
}
.act-child {
border-radius: 5px;
background: #eaf3ff;
&::after {
background-color: #409eff;
} }
} }
} }
} }
.mode-dialog-header {
justify-content: space-between;
font-size: 15px;
font-weight: bold;
.icon-guanbi {
font-size: 20px;
cursor: pointer;
} }
.content-list{
overflow-x: auto
} }
.content-list::-webkit-scrollbar {
height: 8px; .form-btn {
text-align: right;
margin-bottom: 10px;
} }
</style> </style>

View File

@ -1,394 +0,0 @@
<template>
<div class="container-left flex" v-loading="loading">
<div class="left-header flex">
<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>
</div>
</div>
<div class="left-list">
<div class="item" v-for="item in templateList" :key="item.id">
<div class="item-name flex" @mouseenter="item.isAdd = true" @mouseleave="item.isAdd = false"
@click="toggleParent(item)">
<div class="flex">
<span>{{ item.name }}</span>
<!--个人教学模式才会有添加编辑-->
<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="editMode(item)">编辑</el-button>
</div>
</div>
<!--加号 数鼠标悬浮 显示-->
<i v-show="item.isAdd && !item.selected" class="iconfont icon-jiahao"></i>
<!--减号 选中之后显示-->
<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>
<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>
</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">
<template #header>
<div class="mode-dialog-header flex">
<span>{{ addChild ? '教学环节' : '教学模式' }}</span>
<i class="iconfont icon-guanbi" @click="addVisible = false"></i>
</div>
</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-input v-model="ruleForm.name" />
<el-button v-if="isEdit" link type="danger" class="ml-5 mr-3" @click="onDel">删除</el-button>
</div>
</el-form-item>
<el-form-item label="说明" v-if="addChild" prop="prompt">
<el-input v-model="ruleForm.prompt" type="textarea" />
</el-form-item>
</el-form>
<div class="mt-10 form-btn">
<el-button @click="closeDialog">关闭</el-button>
<el-button type="primary" @click="onSubmit(ruleFormRef)">保存</el-button>
</div>
</el-dialog>
</div>
</template>
<script setup>
import { ref, reactive, onMounted, onUnmounted } from 'vue'
import { modelList } from '@/api/mode/index'
import useUserStore from '@/store/modules/user'
import { sessionStore } from '@/utils/store'
import emitter from '@/utils/mitt'
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)
const templateList = ref([])
const getTemplate = async () => {
loading.value = true
const { rows } = await modelList({
createUser: user.userId,
model: 4,
pageNum: 1,
pageSize: 10000
})
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({
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)
if (child.selected) {
ary.push(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) => {
item.selected = false
item.children.forEach((el) => {
el.selected = false
})
})
emitter.emit('selected', [])
}
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()
// .flat()
emitter.emit('selected', selectedData)
}
//
const toggleChild = (parent, child) => {
child.selected = !child.selected
updateParentSelection(parent)
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)
}
//
const addVisible = ref(false)
const ruleFormRef = ref()
const rules = reactive({
name: [{ required: true, message: '不能为空', trigger: 'blur' }]
})
const ruleForm = reactive({
name: '',
prompt: ''
})
//
const onSubmit = async (formEl) => {
if (!formEl) return
await formEl.validate(async (valid, fields) => {
if (valid) {
//
if (isEdit.value) {
let data = cloneDeep(curEditItem)
data.name = ruleForm.name
await editChildTemp(data)
}
//
else {
let obj = {
ex1: curNode.edustage,
ex2: curNode.edusubject,
model: 4,
name: ruleForm.name,
sortNum: 1,
type: 1
}
//
if (addChild.value) {
obj.parentId = curEditItem.id
obj.prompt = ruleForm.prompt
obj.type = 2
}
await addChildTemp(obj)
}
ElMessage.success('操作成功')
resetForm()
addVisible.value = false
getTemplate()
} else {
console.log('error submit!', fields)
}
})
}
//
const isEdit = ref(false)
// false true
const addChild = ref(false)
const curEditItem = reactive({})
const addModeChild = (item) => {
isEdit.value = false
Object.assign(curEditItem, item)
addChild.value = true
addVisible.value = true
}
//
const editMode = (item) => {
isEdit.value = true
addChild.value = false
Object.assign(curEditItem, item)
ruleForm.name = item.name
addVisible.value = true
}
//
const onDel = () => {
ElMessageBox.confirm('确定要删除模板吗?', '温馨提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
})
.then(async () => {
await removeChildTemp(curEditItem.id)
ElMessage.success('操作成功')
resetForm()
addVisible.value = false
getTemplate()
})
.catch(() => { })
}
const closeDialog = () => {
handleBeforeClose(() => (addVisible.value = false))
}
const handleBeforeClose = (done) => {
resetForm()
done()
}
const resetForm = () => {
if (ruleFormRef.value) {
ruleFormRef.value.resetFields()
}
}
const curNode = reactive({})
onMounted(() => {
let data = sessionStore.get('subject.curNode')
Object.assign(curNode, data)
getTemplate()
})
onUnmounted(() => {
emitter.off('resetSelect')
})
</script>
<style lang="scss" scoped>
.container-left {
height: 100%;
font-size: 15px;
flex-direction: column;
.left-header {
height: 32px;
justify-content: space-between;
align-items: center;
.icon-jiahao {
font-size: 12px;
}
}
.left-list {
flex: 1;
margin-top: 5px;
background-color: #fff;
border-radius: 5px;
text-align: left;
padding: 0 10px;
line-height: 30px;
overflow-y: auto;
.item-name {
line-height: 40px;
font-size: 14px;
cursor: pointer;
justify-content: space-between;
padding-right: 10px;
}
.item-child {
padding-left: 3em;
justify-content: space-between;
cursor: pointer;
align-items: center;
padding-right: 10px;
margin-bottom: 3px;
position: relative;
&::after {
content: '';
width: 5px;
height: 5px;
border-radius: 50%;
background-color: #666;
position: absolute;
left: 2em;
top: 50%;
transform: translate(0, -50%);
}
}
.iconfont {
font-weight: bold;
font-size: 18px;
}
.icon-jiahao {
color: #409eff;
}
.icon-zuixiaohua {
color: #f56c6c;
}
.act-child {
border-radius: 5px;
background: #eaf3ff;
&::after {
background-color: #409eff;
}
}
}
}
.mode-dialog-header {
justify-content: space-between;
font-size: 15px;
font-weight: bold;
.icon-guanbi {
font-size: 20px;
cursor: pointer;
}
}
.form-btn {
text-align: right;
margin-bottom: 10px;
}
</style>

View File

@ -1,107 +1,49 @@
<template> <template>
<div class="container-right flex"> <div class="container-right flex">
<div class="right-header flex"> <div class="right-header flex">
<div class="header-left"> <span>课件预览</span>
<!-- <el-button type="primary" link> <div>
<i class="iconfont icon-jiahao"></i>新活动 <el-button type="danger" @click="onCreate">一键生成</el-button>
</el-button> <el-button :disabled="!result?.parentId" @click="openAiPPT">编辑课件</el-button>
<el-button type="primary" link>
<i class="iconfont icon-baocun"></i>保存为教学模式
</el-button> -->
</div>
<div class="header-right">
<el-select v-model="curMode" placeholder="Select" class="mr-4 w-30">
<el-option v-for="item in modeOptions" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
<el-button type="primary" :disabled="!(resultList.length)" @click="getCompletion">一键研读</el-button>
<el-button type="danger" @click="onCreate">生成PPT</el-button>
</div> </div>
</div> </div>
<div class="right-con flex" ref="listRef"> <div class="right-con">
<el-empty v-if="!(resultList.length)" description="暂无数据" /> <el-empty v-if="!result?.parentId" description="请先生成教学大纲,再生成教学课件" />
<div class="con-item flex" v-for="(item, index) in resultList" :key="item.id" v-loading="item.loading"> <div v-for="(item,index) in pptSlides" class="right-con-item">
<div class="item-top flex"> <div>{{index+1}}</div><img :src="item.fileurl">
<span>{{ item.name }}</span>
<el-popover placement="bottom-end" trigger="hover" popper-class="template-custom-popover">
<template #reference>
<el-button link type="primary">
<i class="iconfont icon-shenglvehao"></i></el-button>
</template>
<template #default>
<el-button type="primary" link @click="editKeyWord(item, false)">编辑</el-button>
<el-button type="primary" link @click="removeItem(item, true)">移除</el-button>
</template>
</el-popover>
</div>
<div class="item-bom">
<div class="item-prompt">{{ item.prompt }}</div>
<div class="item-answer" v-if="item.answer">
<div class="answer-text">
<TypingEffect v-if="isStarted[index]" :text="item.answer" :delay="10" :aiShow="item.aiShow"
@complete="handleCompleteText($event, index)" @updateScroll="scrollToBottom($event, index)" />
</div>
<div class="item-btn flex">
<el-button type="primary" link @click="againResult(index, item)">
<i class="iconfont icon-ai1"></i>
重新生成
</el-button>
<el-button type="primary" link @click="onAdjust(index, item)">
<i class="iconfont icon-duihua"></i>
AI对话调整
</el-button>
<el-button type="primary" link @click="onEdit(index, item)">
<i class="iconfont icon-bianji1"></i>
手动编辑结果
</el-button>
</div> </div>
</div> </div>
</div> </div>
<PptDialog @close-dialogs="pptDialog = false" @add-success="addAiPPT" :dataList="result" v-model="pptDialog" />
</div>
</div>
</div>
<EditDialog v-model="isEdit" :item="curItem" />
<AdjustDialog v-model="isAdjust" :item="curItem" :curMode="curMode" :conversation_id="conversation_id" />
<PptDialog @add-success="addAiPPT" :dataList="resultList" v-model="pptDialog" />
<progress-dialog v-model:visible="pgDialog.visible" v-bind="pgDialog" /> <progress-dialog v-model:visible="pgDialog.visible" v-bind="pgDialog" />
<!--添加编辑提示词-->
<keywordDialog v-model="isWordDialog" :item="curItem" />
</template> </template>
<script setup> <script setup>
import { ref, onMounted, onUnmounted, reactive, nextTick } from 'vue' import {ref, onUnmounted, onMounted, reactive} from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus' import { ElMessage } from 'element-plus'
import { sessionStore } from '@/utils/store'
import emitter from '@/utils/mitt' import emitter from '@/utils/mitt'
import EditDialog from './edit-dialog.vue'
import AdjustDialog from './adjust-dialog.vue'
import progressDialog from './progress-dialog.vue' import progressDialog from './progress-dialog.vue'
import { completion, tempResult, tempSave, removeChildTemp, editTempResult, modelList } from '@/api/mode/index.js' import PptDialog from "@/views/prepare/container/pptist-dialog.vue";
import { createChart, sendChart } from '@/api/ai/index' import msgUtils from "@/plugins/modal";
// import { dataSetJson } from '@/utils/comm.js' 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 commUtils from '@/utils/comm.js'
import PptDialog from '@/views/prepare/container/pptist-dialog.vue'
import keywordDialog from './keyword-dialog.vue'
import TypingEffect from '@/components/typing-effect/index.vue'
import { cloneDeep } from 'lodash'
import useUserStore from '@/store/modules/user'
import { PPTXFileToJson } from '@/AixPPTist/src/hooks/useImport' // pptjson
import * as API_entpcourse from '@/api/education/entpcourse' // api
import * as API_entpcoursefile from '@/api/education/entpcoursefile' // api
import * as Api_server from '@/api/apiService' // api import * as Api_server from '@/api/apiService' // api
import * as API_smarttalk from '@/api/file' // api import {createWindow} from "@/utils/tool";
import msgUtils from '@/plugins/modal' import {editSyllabus} from "@/api/mode";
import { getEntpcoursefile } from '@/api/education/entpcoursefile' import { getSmarttalkPage } from "@/api/file"
import { createWindow } from '@/utils/tool' // import useUserStore from '@/store/modules/user'
import { slidesToImg } from '@/utils/ppt' // ppt
const userStore = useUserStore() const userStore = useUserStore()
const pptDialog = ref(false) const pptDialog = ref(false)
const resultList = ref([]) const result = ref(null)
const courseObj = reactive({ const courseObj = reactive({
node: null, // node: null, //
}) })
const curNode = reactive({})
const pgDialog = reactive({ // - const pgDialog = reactive({ // -
visible: false, visible: false,
title: 'PPT解析中...', title: 'PPT解析中...',
@ -119,214 +61,27 @@ const pgDialog = reactive({ // 弹窗-进度条
} }
}) })
const curMode = ref(2) const pptSlides = ref([])
const modeOptions = ref([
{
label: '教学大模型',
value: 1
},
{
label: '知识库模型',
value: 2
}
])
emitter.on('changeMode', (item) => { emitter.on('onResult', (data)=>{
resultList.value = item.child result.value = data
getTempResult(item.id) if (!!result.value.parentId) {
listEntpcoursefileNew({parentid: result.value.parentId}).then(res=>{
pptSlides.value = res.rows
})
}else {
pptSlides.value = []
}
}) })
const onCreate = () =>{ const onCreate = () =>{
let isAnswer = resultList.value.every(item => !item.answer) if(!result.value){
if(isAnswer){
ElMessage.warning('请先进行研读') ElMessage.warning('请先进行研读')
return return
} }
pptDialog.value = true pptDialog.value = true
} }
//
const getCompletion = async () => {
isStarted.value = new Array(resultList.length).fill(false)
isStarted.value[0] = true
resultList.value.forEach(item => {
if (item.answer) {
item.answer = ''
}
})
for (let item of resultList.value) {
try {
item.loading = true
item.aiShow = true
let str = cloneDeep(prompt.value)
str = str.replace(/{模板名称}/g, item.name)
params.prompt = str
params.template = item.prompt
//
let data = null
if (curMode.value == 1) {
const res = await sendChart({
content: params.prompt,
conversationId: conversation_id.value,
stream: false
})
data = res.data
}
//
else {
const res = await completion(params)
data = res.data
}
item.answer = getResult(data.answer)
onSaveTemp(item)
} finally {
item.loading = false
}
}
}
const handleCompleteText = async (answer, index) => {
if (index < resultList.value.length - 1) {
isStarted.value[index + 1] = true; //
}
if (isAgain.value) {
try {
await editTempResult({ id: resultList.value[index].resultId, content: answer })
} finally {
isAgain.value = false
}
}
}
//
const onSaveTemp = async (item) => {
if (item.answer == '') return
const data = {
mainModelId: item.parentId,
modelId: item.id,
examDocld: '',
content: item.answer,
ex1: curNode.id
}
const res = await tempSave(data)
if(!item.resultId){
item.resultId = res.data
}
}
const isWordDialog = ref(false)
const editKeyWord = (item, val) => {
/**
* isAdd: 子模板中的移除 为编辑false 头部删除 添加提示词为新增 true
*/
Object.assign(curItem, item)
curItem.isAdd = val
isWordDialog.value = true
}
//
const removeItem = async (item, isChild) => {
/**
* item: 当前操作的模板
* isChild: 子模板中的移除为 true
*/
if (item.ex3 != '1') {
ElMessageBox.confirm(
'确认是否移除?',
'提示',
{
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
}
).then(() => {
removeChildTemp(item.id).then(res => {
ElMessage.success('操作成功')
if (isChild) {
//
emitter.emit('onGetChild', item)
}
else {
//
// getTemplateList()
}
})
})
}
else {
// editKeyWord(item, !isChild)
}
}
const listRef = ref()
//
const isStarted = ref([]);
const getTempResult = (id) => {
tempResult({ mainModelId: id, pageNum: 1, pageSize: 10000, ex1: curNode.id }).then(res => {
let rows = res.rows
if (rows.length > 0) {
isStarted.value = new Array(rows.length).fill(true)
resultList.value.forEach(item => {
rows.forEach(el => {
if (item.id == el.modelId) {
item.answer = getResult(el.content)
item.resultId = el.id
}
})
})
}
})
}
const scrollToBottom = (height, index) => {
if (listRef.value) {
let sum = 0
let listDom = listRef.value.children
if (index == 0) {
// 220
let screenHeight = window.innerHeight - 220
if (height > screenHeight) {
listRef.value.scrollTop = (height - screenHeight + 50)
}
}
else {
for (let i = 0; i < index; i++) {
sum += listDom[i].clientHeight
}
listRef.value.scrollTop = sum + height
}
}
}
// ### **
let getResult = (str) => {
let newStr = str.replace(/#+|(\*\*)/g, '');
return newStr
}
const params = reactive(
{
prompt: '',
dataset_id: '',
template: ''
}
)
const prompt = ref('')
const addAiPPT = async (res) => { 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' } // 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 let node = courseObj.node
@ -373,6 +128,10 @@ const addAiPPT = async (res) => {
if (res_3 && res_3.code == 200) { if (res_3 && res_3.code == 200) {
msgUtils.msgSuccess('生成PPT课件成功') msgUtils.msgSuccess('生成PPT课件成功')
//TODO //TODO
updateGen(parentid)
listEntpcoursefileNew({parentid: parentid}).then(res=>{
pptSlides.value = res.rows
})
const res = await getEntpcoursefile(parentid) const res = await getEntpcoursefile(parentid)
if (res && res.code === 200) { if (res && res.code === 200) {
openPublicScreen('edit', res.data, smarttalk.resData) // - openPublicScreen('edit', res.data, smarttalk.resData) // -
@ -384,9 +143,27 @@ const addAiPPT = async (res) => {
} }
} }
} }
}).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)=> { const openPublicScreen = (type, resource, currData)=> {
sessionStore.set('curr.resource', resource) // sessionStore.set('curr.resource', resource) //
if (type=='edit') sessionStore.set('curr.smarttalk', currData) // smarttalk if (type=='edit') sessionStore.set('curr.smarttalk', currData) // smarttalk
@ -401,92 +178,50 @@ const openPublicScreen = (type, resource, currData)=> {
} }
}) })
} }
const isEdit = ref(false)
//
const curIndex = ref(-1)
// item
const curItem = reactive({})
// // || 线
const isAgain = ref(false) const toRousrceUrl = async (o) => {
const againResult = async (index, item) => { if (!!o.src) { // src
isAgain.value = true const isBase64 = /^data:image\/(\w+);base64,/.test(o.src)
isStarted.value[index] = false const isBlobUrl = /^blob:/.test(o.src)
resultList.value[index].answer = '' console.log('isBase64', o, isBase64)
if (index == 0) { if (isBase64) {
listRef.value.scrollTop = 0 const bolb = commUtils.base64ToBlob(o.src)
const fileName = Date.now() + '.png'
} else { const file = commUtils.blobToFile(bolb, fileName)
scrollToBottom(50, index) // 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) { //
try { const res = await fetch(o.src)
await nextTick() const blob = await res.blob()
resultList.value[index].loading = true const fileName = o.type == 'video' ? Date.now() + '.mp4' : Date.now() + '.mp3'
item.aiShow = true const file = commUtils.blobToFile(blob, fileName)
// o.src = fileName
let str = cloneDeep(prompt.value) // console.log('file', file)
str = str.replace(/{模板名称}/g, item.name) const formData = new FormData()
params.prompt = str formData.append('file', file)
params.template = item.prompt const ress = await Api_server.Other.uploadFile(formData)
if (ress && ress.code == 200) {
let data = null; const url = ress?.url
// url && (o.src = url)
if (curMode.value == 1) { }
const res = await sendChart({ }
content: params.prompt, }
conversationId: conversation_id.value, if (o?.background?.image) await toRousrceUrl(o.background.image)
stream: false if (o?.elements) {
}) for (let element of o.elements) {
data = res.data await toRousrceUrl(element);
} else {
//
const res = await completion(params)
data = res.data
} }
resultList.value[index].answer = getResult(data.answer)
isStarted.value[index] = true
} finally {
resultList.value[index].loading = false
} }
} }
//
const isAdjust = ref(false)
const onAdjust = (index, item) => {
curIndex.value = index
Object.assign(curItem, item)
isAdjust.value = true
}
//
emitter.on('onSaveAdjust', (item) => {
resultList.value[curIndex.value].answer = item
onEditSave(resultList.value[curIndex.value])
})
//
const onEditSave = async (item) => {
const { msg } = await editTempResult({ id: item.resultId, content: item.answer })
ElMessage.success(msg)
getChildTemplate()
}
//
const onEdit = (index, item) => {
curIndex.value = index
Object.assign(curItem, item)
isEdit.value = true
}
emitter.on('changeResult', (item) => {
resultList.value[curIndex.value].answer = item
})
// ======== zdg start ============
// HTTP
const HTTP_SERVER_API = (type, params = {}) => { const HTTP_SERVER_API = (type, params = {}) => {
switch (type) { switch (type) {
case 'addSmarttalk': { // case 'addSmarttalk': { //
@ -564,214 +299,43 @@ const getDefParams = (params) => {
} }
return Object.assign(def, params) return Object.assign(def, params)
} }
// || 线
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);
}
}
}
// ======== zdg end ============
//
const conversation_id = ref('')
const getChartId = () => {
createChart({ app_id: '712ff0df-ed6b-470f-bf87-8cfbaf757be5' }).then(res => {
localStorage.setItem("conversation_id", res.data.conversation_id);
conversation_id.value = res.data.conversation_id;
})
}
// prompt
const getPrompt = async () => {
const { rows } = await modelList({ model: 5 })
let str = rows.find(item => item.name.indexOf('框架设计') != -1).prompt
str = str.replace('{学段}', curNode.edustage)
str = str.replace('{学科}', curNode.edusubject)
let bookV = curNode.roottitle.split('-')[1] + '版本'
str = str.replace('{教材版本}', bookV)
str = str.replace('{课程名称}', `${curNode.itemtitle}`)
prompt.value = str
}
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);
courseObj.node = data courseObj.node = data
// dataset_id
let jsonKey = `课标-${data.edustage}-${data.edusubject}`
params.dataset_id = commUtils.dataSetJson[jsonKey]
// ID
conversation_id.value = localStorage.getItem('conversation_id')
if (!conversation_id.value) {
getChartId();
}
// prompt
getPrompt()
}) })
//
onUnmounted(()=>{ onUnmounted(()=>{
emitter.off('changeMode') emitter.off('onResult')
emitter.off('changeResult')
emitter.off('changeAdjust')
emitter.off('onSaveAdjust');
}) })
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.container-right{ .container-right{
flex-direction: column;
height: 100%; height: 100%;
font-size: 15px;
flex-direction: column;
.right-header{ .right-header{
height: 45px;
background: #fff;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
padding: 0 20px; .icon-jiahao{
border-radius: 0 5px 0 0; font-size: 12px;
}
} }
.right-con{ .right-con{
flex: 1; flex: 1;
background: #F6F6F6; margin-top: 5px;
padding: 15px; background-color: #fff;
flex-direction: column; border-radius: 5px;
overflow-y: auto; overflow-y: auto;
.right-con-item{
.con-item { display: flex;
width: 100%; margin: 10px 0;
flex-direction: column; justify-content: space-around;
padding-bottom: 20px; img{
position: relative; width: 150px;
padding-left: 15px;
box-sizing: border-box;
&::after {
content: '';
width: 15px;
height: 15px;
border-radius: 50%;
background: #409eff;
position: absolute;
left: -8px;
top: 5px;
} }
&::before {
content: '';
width: 2px;
height: 100%;
background: #409eff;
position: absolute;
left: -1px;
top: 5px;
} }
&:last-child {
&::before {
content: '';
width: 0
} }
} }
.item-top {
justify-content: space-between;
align-items: center;
margin-bottom: 5px;
.icon-shenglvehao {
font-weight: bold
}
}
.item-bom {
background: #fff;
padding: 15px;
border-radius: 5px;
.item-prompt {
text-align: left;
margin-bottom: 5px;
font-size: 13px;
}
.item-answer {
padding: 10px;
background: #F2F2F2;
border-radius: 5px;
.answer-text {
background: #fff;
border-radius: 5px;
margin-bottom: 10px;
text-align: left;
font-size: 13px;
padding: 8px;
}
.item-btn {
justify-content: flex-end;
.iconfont {
margin-right: 3px;
}
.icon-ai1 {
font-size: 18px;
}
}
}
}
}
}
}
</style>
<style>
.template-custom-popover {
width: 110px !important;
min-width: 110px !important;
}
</style> </style>

View File

@ -1,341 +0,0 @@
<template>
<div class="container-right flex">
<div class="right-header flex">
<span>课件预览</span>
<div>
<el-button type="danger" @click="onCreate">一键生成</el-button>
<el-button :disabled="!result?.parentId" @click="openAiPPT">编辑课件</el-button>
</div>
</div>
<div class="right-con">
<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, 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 {createWindow} from "@/utils/tool";
import {editSyllabus} from "@/api/mode";
import { getSmarttalkPage } from "@/api/file"
import useUserStore from '@/store/modules/user'
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([])
emitter.on('onResult', (data)=>{
result.value = data
if (!!result.value.parentId) {
listEntpcoursefileNew({parentid: result.value.parentId}).then(res=>{
pptSlides.value = res.rows
})
}else {
pptSlides.value = []
}
})
const onCreate = () =>{
if(!result.value){
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 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(()=>{
emitter.off('onResult')
})
</script>
<style lang="scss" scoped>
.container-right{
height: 100%;
font-size: 15px;
flex-direction: column;
.right-header{
justify-content: space-between;
align-items: center;
.icon-jiahao{
font-size: 12px;
}
}
.right-con{
flex: 1;
margin-top: 5px;
background-color: #fff;
border-radius: 5px;
overflow-y: auto;
.right-con-item{
display: flex;
margin: 10px 0;
justify-content: space-around;
img{
width: 150px;
}
}
}
}
</style>

View File

@ -1,54 +1,35 @@
<template> <template>
<div class="page-design"> <div class="page-design">
<!-- <div class="page-left">
<left />
</div>
<div class="page-right">
<right />
</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="15"> <el-col :span="14">
<Center /> <Center />
</el-col> </el-col>
<el-col :span="4"> <el-col :span="5">
<Right /> <Right />
</el-col> </el-col>
</el-row> </el-row>
</div> </div>
</template> </template>
<script setup> <script setup>
// import left from './container/left.vue'; import Left from './container/left.vue'
// import right from './container/right.vue';
import Left from './container/left2.vue'
import Center from './container/center.vue' import Center from './container/center.vue'
import Right from './container/right2.vue' import Right from './container/right.vue'
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.page-design { .page-design {
height: 100%; height: 100%;
.el-row { .el-row {
height: 100%; height: 100%;
.el-col { .el-col {
height: 100%; height: 100%;
} }
} }
} }
// .page-design {
// height: 100%;
// .page-left{
// width: 50%;
// }
// .page-right{
// width: 50%;
// }
// }
</style> </style>