yws_dev #99

Merged
yangws merged 6 commits from yws_dev into main 2024-12-09 17:26:10 +08:00
22 changed files with 567 additions and 259 deletions
Showing only changes of commit 0e18d74bb9 - Show all commits

View File

@ -34,7 +34,7 @@ export default defineConfig({
'/dev-api': {
target: 'http://27.128.240.72:7865',
// target: 'http://36.134.181.164:7863',
// target: 'http://192.168.2.52:7863',
// target: 'http://192.168.0.102:7865',
changeOrigin: true,
rewrite: (p) => p.replace(/^\/dev-api/, '')
},

View File

@ -57,6 +57,7 @@
"file-saver": "^2.0.5",
"hfmath": "^0.0.2",
"html-to-image": "^1.11.11",
"html2canvas": "^1.4.1",
"im_electron_sdk": "^8.0.5904",
"js-cookie": "^3.0.5",
"jsencrypt": "^3.3.2",

View File

@ -8,7 +8,7 @@
http-equiv="Content-Security-Policy"
content="default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:"
/> -->
<meta http-equiv="Content-Security-Policy" content="connect-src * blob: data:; default-src 'self'; script-src 'self' 'unsafe-eval' http://www.wiris.net 'unsafe-inline'; style-src 'self' 'unsafe-inline' http://www.wiris.net; media-src * blob:;img-src * 'self' data: blob:;font-src 'self' http://www.wiris.net;" />
<meta http-equiv="Content-Security-Policy" content="connect-src * blob: data:; default-src 'self' https://wzyzoss.eos-chongqing-3.cmecloud.cn/; script-src 'self' 'unsafe-eval' http://www.wiris.net 'unsafe-inline'; style-src 'self' 'unsafe-inline' http://www.wiris.net; media-src * blob:;img-src * 'self' data: blob:;font-src 'self' http://www.wiris.net;" />
</head>

View File

@ -60,14 +60,6 @@ window.addEventListener('unload', () => {
const newDiscardedDB = JSON.stringify(discardedDBList)
localStorage.setItem(LOCALSTORAGE_KEY_DISCARDED_DB, newDiscardedDB)
})
/** 接口类型 */
interface Result {
code?: number,
msg?: string,
data?: any
rows?: Array<any>,
total?: number
}
//
const initLoad: Function = () => {
// ppt

View File

@ -4,6 +4,7 @@
* @date 2024-11-26
*/
import { toRaw } from 'vue'
import type { Result } from './types' // 接口类型
import msgUtils from '@/plugins/modal' // 消息工具
import * as API_entpcoursefile from '@/api/education/entpcoursefile' // 相关api
import * as API_smarttalk from '@/api/file' // 相关api

View File

@ -2,6 +2,7 @@
* @description api store循环引用
* @author zdg
*/
import type { Result } from './types' // 接口类型
import msgUtils from '@/plugins/modal' // 消息工具
import * as API_entpcoursefile from '@/api/education/entpcoursefile' // 相关api

View File

@ -0,0 +1,8 @@
/** 返回-接口类型 */
export interface Result {
code?: number,
msg?: string,
data?: any
rows?: Array<any>,
total?: number
}

View File

@ -81,6 +81,7 @@
</template>
<IconVideoTwo class="handler-item" v-tooltip="'插入音视频'" />
</Popover>
<IconPreviewOpen class="handler-item" v-tooltip="'插入试题'" @click="classWorkTaskVisible = true" />
</div>
<div class="right-handler">
@ -110,6 +111,18 @@
@update="data => { createLatexElement(data); latexEditorVisible = false }"
/>
</Modal>
<Modal
v-model:visible="classWorkTaskVisible"
:width="880"
>
<QuestToPPTist
class="class-work-task-modal"
@close="classWorkTaskVisible = false"
@update="data => { onhtml2canvas(data); classWorkTaskVisible = false }"
/>
</Modal>
</div>
</template>
@ -135,6 +148,7 @@ import Modal from '../../../components/Modal.vue'
import Divider from '../../../components/Divider.vue'
import Popover from '../../../components/Popover.vue'
import PopoverMenuItem from '../../../components/PopoverMenuItem.vue'
import QuestToPPTist from '@/views/classTask/newClassTaskAssign/questToPPTist/index.vue'
const mainStore = useMainStore()
const { creatingElement, creatingCustomShape, showSelectPanel, showSearchPanel, showNotesPanel } = storeToRefs(mainStore)
@ -172,12 +186,17 @@ const insertImageElement = (files: FileList) => {
getImageDataURL(imageFile).then(dataURL => createImageElement(dataURL))
}
const onhtml2canvas = (imgbs64: string) => {
createImageElement(imgbs64)
}
const shapePoolVisible = ref(false)
const linePoolVisible = ref(false)
const chartPoolVisible = ref(false)
const tableGeneratorVisible = ref(false)
const mediaInputVisible = ref(false)
const latexEditorVisible = ref(false)
const classWorkTaskVisible = ref(false)
const textTypeSelectVisible = ref(false)
const shapeMenuVisible = ref(false)
const moreVisible = ref(false)
@ -343,6 +362,9 @@ const toggleNotesPanel = () => {
font-size: 13px;
}
}
.class-work-task-modal{
height: 80vh;
}
@media screen and (width <= 1200px) {
.right-handler .text {

View File

@ -142,13 +142,19 @@ const handleNodeClick = (data) => {
//label label
nodeData.label = nodeData.itemtitle
// null
let parent = {
id: nodeData.parentid,
label: nodeData.parenttitle,
itemtitle: nodeData.parenttitle
}
const parentNode = nodeData.parentid ? parent : null
let parentNode
// children
if(nodeData.children){
//
parentNode = null
}
else{
parentNode = {
id: nodeData.parentid,
label: nodeData.parenttitle,
itemtitle: nodeData.parenttitle
}
}
nodeData.parentNode = parentNode
let curData = {
textBook: {

View File

@ -26,52 +26,52 @@
</div>
</div>
<!--List-->
<div class="container-right-list">
<template v-for="(item, index) in childTempList">
<div class="template-item" v-loading="item.loading">
<div class="item-header">
<div>
<span class="blue">#</span>{{ item.name }}
</div>
<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)">编辑</el-button>
<el-button type="primary" link @click="removeItem(item, true)">移除</el-button>
</template>
</el-popover>
</div>
<div class="item-text">
{{ item.prompt }}
</div>
<div class="item-text text-answer" v-if="item.answer">
<div class="item-icon">
<i class="iconfont icon-ai"></i>
</div>
<div class="item-answer">
<TypingEffect :text="item.oldAnswer" :delay="10" :aiShow="item.aiShow" @complete="onSaveTemp(item)" />
</div>
</div>
<div class="ai-btn" v-if="item.answer">
<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>
</template>
<el-empty v-if="!childTempList.length" description="暂无模板数据" />
<div class="container-right-list" ref="listRef">
<template v-for="(item, index) in childTempList">
<div class="template-item" v-loading="item.loading">
<div class="item-header">
<div>
<span class="blue">#</span>{{ item.name }}
</div>
<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)">编辑</el-button>
<el-button type="primary" link @click="removeItem(item, true)">移除</el-button>
</template>
</el-popover>
</div>
<div class="item-text">
{{ item.prompt }}
</div>
<div class="item-text text-answer" v-if="item.answer">
<div class="item-icon">
<i class="iconfont icon-ai"></i>
</div>
<div class="item-answer">
<TypingEffect v-if="isStarted[index]" :text="item.answer" :delay="10" :aiShow="item.aiShow" @complete="handleCompleteText($event,index)" @updateScroll="scrollToBottom($event,index)" />
</div>
</div>
<div class="ai-btn" v-if="item.answer">
<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>
</template>
<el-empty v-if="!childTempList.length" description="暂无模板数据" />
</div>
</div>
<!--编辑结果-->
@ -83,7 +83,7 @@
</template>
<script setup>
import { ref, reactive, onMounted, watch, onUnmounted } from 'vue'
import { ref, reactive, onMounted, watch, onUnmounted, nextTick } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { tempSave, completion, modelList, removeChildTemp, tempResult, editTempResult } from '@/api/mode/index'
import { sessionStore } from '@/utils/store'
@ -146,14 +146,16 @@ const getChildTemplate = () => {
tempLoading.value = true
modelList({ model: props.type, type: 2, parentId: curTemplate.id }).then(res => {
childTempList.value = res.rows
if(childTempList.value.length){
childTempList.value.forEach(item => item.answer = '')
}
getTempResult()
}).finally(() => {
tempLoading.value = false
})
}
const isStarted = ref([]);
const listRef = ref()
//
const getTempResult = () => {
tempResult({ mainModelId: curTemplate.id, pageNum: 1, pageSize: 10000 }).then(res => {
@ -161,14 +163,40 @@ const getTempResult = () => {
childTempList.value.forEach(item => {
rows.forEach(el => {
if (item.id == el.modelId) {
item.answer = el.content
item.answer = getResult(el.content)
item.resultId = el.id
}
})
})
if(rows.length > 0){
isStarted.value = new Array(rows.length).fill(true)
}
})
}
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
}
}
}
//
const changeTemplate = (val) => {
ElMessageBox.confirm(
@ -203,11 +231,11 @@ const removeItem = async (item, isChild) => {
).then(() => {
removeChildTemp(item.id).then(res => {
ElMessage.success('操作成功')
if(isChild){
if (isChild) {
//
getChildTemplate()
}
else{
else {
//
getTemplateList()
}
@ -215,7 +243,7 @@ const removeItem = async (item, isChild) => {
})
}
else {
editKeyWord(item,!isChild)
editKeyWord(item, !isChild)
}
}
@ -240,19 +268,18 @@ const onEdit = (index, item) => {
const modeType = ref('课标')
watch(() => props.type, (newVal) => {
if (newVal == 1){
if (newVal == 1) {
modeType.value = '课标'
}
if (newVal == 2){
if (newVal == 2) {
modeType.value = '教材'
}
if (newVal == 2){
if (newVal == 3) {
modeType.value = '考试'
}
}, { immediate: false })
//
const params = reactive(
{
@ -260,76 +287,102 @@ const params = reactive(
dataset_id: ''
}
)
//
const isAgain = ref(false)
const againResult = async (index, item) => {
isAgain.value = true
isStarted.value[index] = false
childTempList.value[index].answer = ''
if(index == 0){
listRef.value.scrollTop = 0
}else{
scrollToBottom(50, index)
}
try {
await nextTick()
childTempList.value[index].loading = true
item.aiShow = true
childTempList.value[index].oldAnswer = ''
params.prompt = `按照${item.name}的要求,针对${curNode.edustage}${curNode.edusubject}${modeType.value}${curNode.itemtitle}进行教学分析`
const { data } = await completion(params)
let answer = data.answer
childTempList.value[index].oldAnswer = answer
childTempList.value[index].answer = getResult(answer);
// onEditSave(item)
childTempList.value[index].answer = getResult(data.answer);
isStarted.value[index] = true
} finally {
childTempList.value[index].loading = false
}
}
//
const getCompletion = async () => {
isStarted.value = new Array(childTempList.length).fill(false)
isStarted.value[0] = true
childTempList.value.forEach(item =>{
if(item.answer){
item.answer = ''
}
})
for (let item of childTempList.value) {
try {
item.loading = true
item.aiShow = true
params.prompt = `按照${item.name}的要求,针对${curNode.edustage}${curNode.edusubject}${modeType.value}${curNode.itemtitle}进行教学分析`
const { data } = await completion(params)
let answer = data.answer
item.oldAnswer = answer
item.answer = getResult(answer);
item.answer = getResult(data.answer)
onSaveTemp(item)
} finally {
item.loading = false
}
}
}
const handleCompleteText = async (answer, index) =>{
if (index < childTempList.value.length - 1) {
isStarted.value[index + 1] = true; //
}
if(isAgain.value){
try{
await editTempResult({ id: childTempList.value[index].resultId, content: answer })
}finally{
isAgain.value = false
}
}
}
//
emitter.on('onSaveAdjust', (item) => {
childTempList.value[curIndex.value].oldAnswer = item
let answer = getResult(item);
childTempList.value[curIndex.value].oldAnswer = item
childTempList.value[curIndex.value].answer = answer
childTempList.value[curIndex.value].answer = item
onEditSave(childTempList.value[curIndex.value])
})
//
const onEditSave = async (item) =>{
const { res } = await editTempResult({id: item.resultId, content: item.oldAnswer})
const onEditSave = async (item) => {
const { res } = await editTempResult({ id: item.resultId, content: item.answer })
ElMessage.success(res)
getChildTemplate()
}
//
const onSaveTemp = (item) => {
if(item.oldAnswer == '') return
if (item.answer == '') return
const data = {
mainModelId: curTemplate.id,
modelId: item.id,
examDocld: '',
content: item.oldAnswer
content: item.answer
}
tempSave(data).then(res => {})
tempSave(data).then(res => { })
}
//
let getResult = (text) => {
text = text.replace(/^\n\n(.*?)\n\n$/s, '<div>$1</div>');
text = text.replace(/^\n(.*?)\n$/s, '<p>$1</p>');
text = text.replace(/\*\*(.*?)\*\*/g, "<div class='text-tit'>$1</div>");
text = text.replace(/(\d+\..*?)\n/g, "<div class='text-num'>$1</div>\n");
return text
// ### **
let getResult = (str) => {
let newStr = str.replace(/#+|(\*\*)/g, '');
return newStr
}
//
@ -381,87 +434,85 @@ onUnmounted(() => {
padding: 5px 15px;
box-sizing: border-box;
.template-item {
background: #fff;
padding: 10px;
margin-top: 10px;
border-radius: 5px;
.template-item {
background: #fff;
padding: 10px;
margin-top: 10px;
border-radius: 5px;
.item-header {
display: flex;
align-items: center;
font-size: 16px;
font-weight: bold;
color: #000;
justify-content: space-between;
.item-header {
display: flex;
align-items: center;
font-size: 16px;
font-weight: bold;
color: #000;
justify-content: space-between;
.blue {
font-size: 22px;
color: #409eff;
margin-right: 5px;
}
}
.item-text {
display: flex;
margin-top: 10px;
font-size: 14px;
text-align: left;
color: #606266;
.item-icon {
width: 30px;
height: 30px;
line-height: 30px;
text-align: center;
background: #F6F6F6;
border-radius: 50%;
margin-right: 10px;
flex-shrink: 0;
}
.item-answer {
flex-direction: column;
padding-top: 5px;
width: 100%;
:deep(.text-tit) {
font-weight: bold;
margin: 10px 0;
}
:deep(.text-num) {
padding-left: 2em;
}
}
}
.text-answer {
.blue {
font-size: 22px;
color: #409eff;
}
.ai-btn {
margin-top: 10px;
display: flex;
justify-content: flex-end;
.iconfont {
margin-right: 3px;
}
:deep(.el-button) {
font-size: 13px;
}
.icon-ai1 {
font-size: 18px;
}
margin-right: 5px;
}
}
.item-text {
display: flex;
margin-top: 10px;
font-size: 14px;
text-align: left;
color: #606266;
.item-icon {
width: 30px;
height: 30px;
line-height: 30px;
text-align: center;
background: #F6F6F6;
border-radius: 50%;
margin-right: 10px;
flex-shrink: 0;
}
.item-answer {
flex-direction: column;
padding-top: 5px;
width: 100%;
:deep(.text-tit) {
font-weight: bold;
margin: 10px 0;
}
:deep(.text-num) {
padding-left: 2em;
}
}
}
.text-answer {
color: #409eff;
}
.ai-btn {
margin-top: 10px;
display: flex;
justify-content: flex-end;
.iconfont {
margin-right: 3px;
}
:deep(.el-button) {
font-size: 13px;
}
.icon-ai1 {
font-size: 18px;
}
}
}
}
}
</style>

View File

@ -1,5 +1,5 @@
<template>
<div class="typing-effect">
<div class="typing-effect" ref="typingEffectRef">
<!-- <span v-html="displayedText"></span> -->
<el-input
v-model="displayedText"
@ -14,11 +14,11 @@
</template>
<script setup>
import { ref, onMounted, watch } from 'vue';
import { ref, onMounted, watch, nextTick } from 'vue';
const props = defineProps({
text: {
type: String,
type: [String, Object],
required: true
},
delay: {
@ -26,35 +26,48 @@ const props = defineProps({
default: 100 //
},
aiShow: {
type: [Boolean]
type: [Boolean] // true
}
});
const emit = defineEmits(['complete']);
const typingEffectRef = ref(null);
const emit = defineEmits(['complete', 'updateScroll']);
const displayedText = ref('');
const index = ref(0);
const type = () => {
if(!props.aiShow) return
const type = async () => {
await nextTick()
if(!props.aiShow) {
displayedText.value = props.text
return
}
if (index.value <= props.text.length) {
displayedText.value += props.text.charAt(index.value);
index.value++;
setTimeout(() => type(), props.delay);
setTimeout(() => {
type();
emit('updateScroll', typingEffectRef.value.clientHeight); //
}, props.delay);
} else {
// complete
emit('complete');
emit('complete',displayedText.value);
}
};
onMounted(() => {
type();
resetAndType();
});
// props 便 text delay
watch([() => props.text, () => props.delay], () => {
const resetAndType = () =>{
displayedText.value = '';
index.value = 0;
type();
});
}
// props 便 text delay
watch([() => props.text, () => props.delay], resetAndType);
</script>
<style scoped>

View File

@ -40,7 +40,7 @@
<el-button @click="handleQueryParamFromEntpCourseWork(1)"><el-icon><Search /></el-icon> </el-button>
</el-col>
<el-col :span="5">
<el-button type="primary" @click="goToQuestUpload()">添加习题</el-button>
<el-button v-if="!props.isHtml2canvas" type="primary" @click="goToQuestUpload()">添加习题</el-button>
</el-col>
</el-row>
<!-- 习题表格 -->
@ -58,9 +58,9 @@
</div>
</template>
<template #default="scope">
<div @click="showExamAnalyseDrawer(scope.row)">
<div style="overflow: hidden; text-overflow: ellipsis" v-html="scope.row.titleFormat"></div>
<div style="overflow: hidden; text-overflow: ellipsis; font-size: 0.9em; margin-top: 6px;" v-html="scope.row.workdescFormat"></div>
<div @click="showExamAnalyseDrawer(scope.row)" :id=" `screenshot-target-${scope.row.id}` " style="padding: 5px;">
<div style="overflow: hidden; text-overflow: ellipsis; padding: 2px;" v-html="scope.row.titleFormat"></div>
<div style="overflow: hidden; text-overflow: ellipsis; font-size: 0.9em; margin-top: 6px; padding: 2px;" v-html="scope.row.workdescFormat"></div>
<el-col :span="24" style="display: flex">
<div style="font-size: 1em; color: silver; padding-top: 5px">{{ scope.row.entpname }} {{ scope.row.editusername }}</div>
<div style="margin-left: 30px; font-size: 1em; color: silver; padding-top: 5px">{{ scope.row.worktag }}</div>
@ -70,7 +70,8 @@
</el-table-column>
<el-table-column width="100">
<template #default="scope">
<div>
<el-button v-if="props.isHtml2canvas" type="primary" @click="captureScreenshot(scope.row.id)">选取该题</el-button>
<div v-else>
<el-button type="primary" @click="handleClassWorkQuizAdd('entpcourseworklist', scope.row.id)">添加</el-button>
<div style="padding: 2px;"></div>
<el-button type="warning" @click="handleImportSingleDlg(scope.row)">纠错</el-button>
@ -117,6 +118,7 @@
import { Search } from '@element-plus/icons-vue'
import { onMounted, ref,watch, reactive, getCurrentInstance,nextTick } from 'vue'
import { useRouter, useRoute } from 'vue-router'
import html2canvas from 'html2canvas';
import { listEntpcoursework, listEntpcourseworkLocal } from '@/api/education/entpCourseWork'
import { listEvaluationclue } from '@/api/classTask'
@ -136,7 +138,7 @@ const router = useRouter()
// emit
const emit = defineEmits(['addQuiz'])
let emit = defineEmits(['addQuiz', 'addQuizImgBs64'])
const { proxy } = getCurrentInstance()
const userStore = useUserStore().user
const {
@ -150,6 +152,10 @@ const props = defineProps({
type: Object,
default: () => ({})
},
isHtml2canvas: {//
type: Boolean,
default: () => false
},
})
const knowledgePointProps = ref({value: 'thirdId', label: 'title'});
@ -440,6 +446,22 @@ const handleDelete = async(item, index) => {
// ElMessage('')
// }
};
/**
* 把该题区域id 获取为截屏区域
* @param id 试题id
*/
const captureScreenshot = (id) => {
const targetElement = document.getElementById('screenshot-target-' + id);
html2canvas(targetElement).then(canvas => {
// canvasURL
const screenshotUrl = canvas.toDataURL('image/png');
//
// console.log(screenshotUrl);
emit('addQuizImgBs64', screenshotUrl);
});
}
//
const debounceQueryData = debounce(() => {
console.log("防抖 加载数据中...")

View File

@ -0,0 +1,115 @@
<template>
<div class="page">
<div class="page-resource">
<div class="page-center">
<el-tabs v-model="activeAptTab" style="height: 100%;">
<el-tab-pane label="自主搜题" name="自主搜题" class="prepare-center-zzst">
<SearchQuestion :bookobj="courseObj" :isHtml2canvas="true" @addQuizImgBs64="handleaddQuizImgBs64" />
</el-tab-pane>
<el-tab-pane label="校本题库" name="校本题库" class="prepare-center-xbtk">
<SchoolQuestion />
</el-tab-pane>
<el-tab-pane label="个人题库" name="个人题库" class="prepare-center-grst">
<MyQuestion :bookobj="courseObj" :isHtml2canvas="true" @addQuizImgBs64="handleaddQuizImgBs64"/>
</el-tab-pane>
</el-tabs>
</div>
</div>
</div>
</template>
<script setup>
import { onMounted, ref, watch, reactive, getCurrentInstance, nextTick } from 'vue'
import MyQuestion from '@/views/classTask/newClassTaskAssign/myQuestion/index.vue'
import SchoolQuestion from '@/views/classTask/newClassTaskAssign/schoolQuestion/index.vue'
import SearchQuestion from '@/views/classTask/newClassTaskAssign/searchQuestion/index.vue'
import { sessionStore } from '@/utils/store'
import { useRouter, useRoute } from 'vue-router'
import useUserStore from '@/store/modules/user'
const userStore = useUserStore().user
const emit = defineEmits(['update']);
const courseObj = reactive({
// : id,id,id,
textbookId: '',
levelFirstId: '',
levelSecondId: '',
coursetitle:'',
node: null, //
//
})
const activeAptTab = ref("自主搜题");
onMounted(() => {
const curNode = sessionStore.get('subject.curNode')
courseObj.textbookId = curNode.rootid
courseObj.levelFirstId = curNode.parentNode.id
courseObj.levelSecondId = curNode.id
courseObj.coursetitle = curNode.itemtitle,
courseObj.node = curNode
})
const handleaddQuizImgBs64 = (quizbs64) => {
emit('update', quizbs64)
}
defineExpose({
})
</script>
<style scoped lang="scss">
.page {
height: 100%;
.page-resource {
user-select: none;
height: calc(100% - 55px);
display: flex;
flex-direction: row;
flex-wrap: nowrap;
flex: 1;
:deep(.el-tabs__nav) {
.el-tabs__item{
font-weight: bold;
font-size: 18px;
}
}
}
.page-center{
flex: 1;
//min-width: calc(100% - 675px);
height: 100%;
padding: 0 5px;
margin: 0 5px;
overflow: hidden;
border-radius: 10px;
background-color: white;
.prepare-center-zzst{
height: 100%;
display: flex;
flex-direction: column;
}
.prepare-center-xbtk{
height: 100%;
}
.prepare-center-grst{
height: 100%;
}
.upload-homework{
padding: 20px;
box-sizing: border-box;
}
}
}
</style>

View File

@ -71,9 +71,9 @@
</div>
</template>
<template #default="scope">
<div @click="showExamAnalyseDrawer(scope.row)">
<div style="overflow: hidden; text-overflow: ellipsis" v-html="scope.row.titleFormat"></div>
<div style="overflow: hidden; text-overflow: ellipsis; font-size: 0.9em; margin-top: 6px;" v-html="scope.row.workdescFormat"></div>
<div @click="showExamAnalyseDrawer(scope.row)" :id=" `screenshot-target-${scope.row.id}` " style="padding: 5px;">
<div style="overflow: hidden; text-overflow: ellipsis; padding: 2px;" v-html="scope.row.titleFormat"></div>
<div style="overflow: hidden; text-overflow: ellipsis; font-size: 0.9em; margin-top: 6px; padding: 2px;" v-html="scope.row.workdescFormat"></div>
<el-col :span="24" style="display: flex">
<div style="font-size: 1em; color: silver; padding-top: 5px">{{ scope.row.entpname }} {{ scope.row.editusername }}</div>
<div style="margin-left: 30px; font-size: 1em; color: silver; padding-top: 5px">{{ scope.row.worktag }}</div>
@ -83,7 +83,8 @@
</el-table-column>
<el-table-column align="left" width="100">
<template #default="scope">
<el-button type="primary" @click="handleClassWorkQuizAdd('entpcourseworklist', scope.row.id)">添加</el-button>
<el-button v-if="props.isHtml2canvas" type="primary" @click="captureScreenshot(scope.row.id)">选取该题</el-button>
<el-button v-else type="primary" @click="handleClassWorkQuizAdd('entpcourseworklist', scope.row.id)">添加</el-button>
</template>
</el-table-column>
</el-table>
@ -104,7 +105,7 @@
</template>
<script setup>
import { Search } from '@element-plus/icons-vue'
import html2canvas from 'html2canvas';
import { onMounted, ref,watch, reactive, getCurrentInstance,nextTick } from 'vue'
import {listEntpcoursework, listEntpcourseworkNew, getEntpcoursework} from '@/api/education/entpCourseWork'
@ -122,7 +123,7 @@ import useUserStore from '@/store/modules/user'
import useClassTaskStore from '@/store/modules/classTask'
// emit
const emit = defineEmits(['addQuiz'])
let emit = defineEmits(['addQuiz', 'addQuizImgBs64'])
const { proxy } = getCurrentInstance()
const userStore = useUserStore().user
const {
@ -136,6 +137,10 @@ const props = defineProps({
type: Object,
default: () => ({})
},
isHtml2canvas: {//
type: Boolean,
default: () => false
},
})
const entpCourseWorkPointList = ref([
@ -427,6 +432,20 @@ const getPaginationList = ( page, limit ) => {
// ElMessage('')
// }
};
/**
* 把该题区域id 获取为截屏区域
* @param id 试题id
*/
const captureScreenshot = (id) => {
const targetElement = document.getElementById('screenshot-target-' + id);
html2canvas(targetElement).then(canvas => {
// canvasURL
const screenshotUrl = canvas.toDataURL('image/png');
//
// console.log(screenshotUrl);
emit('addQuizImgBs64', screenshotUrl);
});
}
//

View File

@ -1,5 +1,5 @@
<template>
<el-dialog v-model="isDialog" :show-close="false" width="900" destroy-on-close>
<el-dialog v-model="isDialog" :show-close="false" width="900" append-to-body destroy-on-close>
<template #header>
<div class="custom-header flex">
<span>选择{{ title }}</span>
@ -15,8 +15,10 @@
</div>
<div class="content-list">
<ul>
<li v-for="(item, index) in fileList" :class="activeIndex == index ? 'li-active' : ''" @click="clickItem(index, item)">
<li v-for="(item, index) in fileList" :class="activeIndex == index ? 'li-active' : ''"
@click="clickItem(index, item)">
<el-image class="img" :src="url" />
<el-button type="primary" class="prev-btn" @click.stop="onPrevItem(item)">预览</el-button>
<el-text truncated>{{ item.fileName }}</el-text>
</li>
</ul>
@ -24,8 +26,8 @@
</div>
<template #footer>
<div class="dialog-footer">
<el-upload class="upload-demo" :action="uploadFileUrl" :limit="1" :show-file-list="false"
:headers="headers" :on-success="onSuccess">
<el-upload class="upload-demo" :action="uploadFileUrl" :limit="1" :show-file-list="false" :headers="headers"
:on-success="onSuccess">
<el-button type="primary">上传</el-button>
</el-upload>
<div>
@ -37,6 +39,31 @@
</div>
</template>
</el-dialog>
<el-dialog v-model="prevVisible" fullscreen :show-close="false" class="prev-dialog">
<template #header>
<div class="custom-header flex">
<span>预览</span>
<i class="iconfont icon-guanbi" @click="prevVisible = false"></i>
</div>
</template>
<div style="height: calc(100vh - 120px);">
<template v-if="getFileSuffix(prevItem.fileUrl) == 'pdf'">
<iframe :src="prevItem.fileUrl"
frameborder="0" width="100%" height="100%"></iframe>
</template>
<template v-else>
<el-image :src="prevItem.fileUrl" style="height:100%"/>
</template>
</div>
<template #footer>
<div class="dialog-footer">
<div></div>
<el-button type="primary" @click="prevVisible = false">
关闭
</el-button>
</div>
</template>
</el-dialog>
</template>
<script setup>
@ -47,6 +74,7 @@ import { sessionStore } from '@/utils/store'
import { dataSetJson } from '@/utils/comm.js'
import { ElMessage } from 'element-plus'
import useUserStore from '@/store/modules/user'
import { getFileSuffix } from '@/utils/ruoyi.js'
import emitter from '@/utils/mitt';
const userInfo = useUserStore().user
@ -56,6 +84,7 @@ const headers = ref({ Authorization: "Bearer " + getToken() });
const url = 'https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fsafe-img.xhscdn.com%2Fbw1%2F11044b08-04c1-41a0-a453-1fd20b58a614%3FimageView2%2F2%2Fw%2F1080%2Fformat%2Fjpg&refer=http%3A%2F%2Fsafe-img.xhscdn.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=auto?sec=1732953359&t=7ab1d1b3a903db85b1149914407aea35'
const isDialog = defineModel()
const prevVisible = ref(false)
const props = defineProps({
modeType: {
@ -99,21 +128,20 @@ const activeIndex = ref(0)
const dataset_id = ref('')
//
const onSuccess = async (response) =>{
console.log(response,'response')
const onSuccess = async (response) => {
let data = {
url: response.url,
dataset_id: dataset_id.value
}
const res = await completion(data)
if(res.data.code != 200) return
if (res.data.code != 200) return
let docData = {
fileUrl: response.url,
fileId: response.file.id,
fileName: response.file.fileName,
filesize: response.file.fileSize,
datasetId: dataset_id.value,
datasetId: dataset_id.value,
docId: res.data.document_id,
edustage: curNode.edustage,
edusubject: curNode.edusubject
@ -127,25 +155,29 @@ const curNode = reactive({})
const fileList = ref([])
const curFile = reactive({})
const getList = () =>{
const getList = () => {
docList({
userId: userInfo.userId,
dataset_id: dataset_id.value
}).then( res =>{
dataset_id: dataset_id.value
}).then(res => {
fileList.value = [...res.rows]
Object.assign(curFile, fileList.value[0])
Object.assign(curFile, fileList.value[0])
})
}
const clickItem = (index, item) => {
activeIndex.value = index
Object.assign(curFile, item)
emitter.emit('curFile',item)
Object.assign(curFile, item)
emitter.emit('curFile', item)
}
const prevItem = reactive({})
const onPrevItem = (item) => {
Object.assign(prevItem, item)
prevVisible.value = true
}
onMounted(() =>{
onMounted(() => {
let data = sessionStore.get('subject.curNode')
Object.assign(curNode, data);
@ -155,7 +187,6 @@ onMounted(() =>{
})
</script>
<style lang="scss" scoped>
.custom-header {
justify-content: space-between;
@ -192,9 +223,11 @@ onMounted(() =>{
overflow: hidden;
margin-right: 20px;
margin-bottom: 10px;
position: relative;
overflow: hidden;
.img {
width: 100px;
width: 100%;
height: 130px;
border: solid #ccc 1px;
margin-bottom: 10px;
@ -203,6 +236,10 @@ onMounted(() =>{
&:hover {
background: #E0EAFF;
}
&:hover .prev-btn {
transform: translate(-50%, -50%)
}
}
.li-active {
@ -212,9 +249,20 @@ onMounted(() =>{
}
}
}
.dialog-footer{
.dialog-footer {
display: flex;
align-items: center;
justify-content: space-between;
}
.prev-btn {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%) translateY(-110px);
/* 按钮初始位置在容器外 */
transition: transform 0.3s ease-in-out;
/* 设置过渡效果 */
}
</style>

View File

@ -14,8 +14,14 @@ const pdfUrl = ref('')
onMounted(async () =>{
await nextTick()
const { fileurl } = sessionStore.get('subject.curBook')
pdfUrl.value = import.meta.env.VITE_APP_RES_FILE_PATH + fileurl.replace('.txt','.pdf')
let data = sessionStore.get('subject.curBook')
let fileurl = data.fileurl
if(fileurl == ''){
fileurl = `${data.edustage}-${data.edusubject}-课标.txt`
}
pdfUrl.value = import.meta.env.VITE_APP_RES_FILE_PATH + fileurl.replace('.txt', '.pdf')
})
</script>

View File

@ -197,19 +197,6 @@ const getResourceList = async () => {
// HTTP
const HTTP_SERVER_API = (type, params = {}) => {
switch (type) {
case 'addSmarttalk': { //
const def = {
fileId: '', // id - Entpcoursefile id
fileFlag: 'aptist',
fileShowName: courseObj.coursetitle + '.aptist',
textbookId: courseObj.textbookId,
levelFirstId: courseObj.levelFirstId,
levelSecondId: courseObj.levelSecondId,
fileSource: '个人',
fileRoot: '备课'
}
return API_smarttalk.creatAPT({...def, ...params})
}
case 'addEntpcourse': { //
const node = courseObj.node || {}
if (!node) return msgUtils.msgWarning('请选择章节?')
@ -292,8 +279,6 @@ const handleAll = async(type, row) =>{
}
// ppt-(slide)
await HTTP_SERVER_API('addEntpcoursefile', params)
// -Smarttalk
await HTTP_SERVER_API('addSmarttalk',{fileId: id})
//
await getResourceList()
} else {
@ -372,7 +357,7 @@ const getIcon = (o, type) => {
justify-content: flex-start;
.el-button {
flex: 1 1 15%;
flex: 1 1 15%;
max-width: 15%;
min-width: 15%;
box-sizing: border-box;
@ -387,4 +372,4 @@ const getIcon = (o, type) => {
}
}
}
</style>
</style>

View File

@ -57,11 +57,11 @@
</el-row>
<div>
<el-button style="margin-bottom: 5px;" type="primary" @click="activeStep = 0">上一步</el-button>
<el-button style="margin-bottom: 5px;" type="primary" @click="outlineCreatePPT()">生成PPT</el-button>
<el-button style="margin-bottom: 5px;" type="primary" v-loading="createPPTLoading" @click="outlineCreatePPT()">生成PPT</el-button>
</div>
</el-card>
<el-card v-if="activeStep === 2">
<el-progress :percentage="30" type="circle" v-if="percentage === 30"></el-progress>
<el-progress :percentage="percentage" type="circle"></el-progress>
</el-card>
</div>
</div>
@ -79,6 +79,7 @@ import {
import CryptoJS from "crypto-js"
import { getSignature } from "@/utils/index.js";
import {sessionStore} from "@/utils/store";
let appId = "01ec9aa3";
let secret = "M2QxMDAxMjYyYTEzODMwMGRkZTQ4NmUy";
@ -98,8 +99,8 @@ let secondArray = ref([]); //大纲的文字部分
const backGroundList = ref([]);
const inputTheme = ref("高中语文《沁园春雪》的授课课件"); //
let subjectdata = sessionStore.get('subject.curNode')
const inputTheme = ref(subjectdata.edustage + subjectdata.edusubject + "《" + subjectdata.itemtitle + "》的授课课件"); //
const inputRequire = ref("") //
const activeStep = ref(0); //
const combined = ref('') // ppt
@ -109,6 +110,8 @@ const status = ref("init");
const percentage = ref(0);
const createPPTLoading = ref(false);
const getBackgrounds = () => {
treeData.value = [];
getBackGroundV2().then((res) => {
@ -126,6 +129,8 @@ const outlineData = ref({
// templateId: 'auto', // ppt
author: 'AIX平台',
isFigure: false, //
search: true,
language: "cn"
}
)
@ -145,20 +150,17 @@ function updateStagingData(role, newData) {
const outlineCreatePPT = () => {
const newOutlineData = { ...outlineData.value, };
newOutlineData.query = outputText.value;
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 = response.process;
if (response && response.pptUrl && response.pptUrl.length > 4) {
console.log('PPT',response)
// window.location.href = response.data.pptUrl;
//URLURL
// let url = "https://bjcdn.openstorage.cn/xinghuo-privatedata/%2Ftmp/apiTempFiledf28bf990a4c40ffb7477ed4b65392c27232357022409613439/%E3%80%8A%E9%9D%99%E5%A5%B3%E3%80%8B%E6%B7%B1%E5%BA%A6%E8%A7%A3%E8%AF%BB%E4%B8%8E%E7%A0%94%E7%A9%B6.pptx"
emit('addSuccess',res)
getProgressV2(res.sid).then(response => {
percentage.value = Math.round(response?.donePages/response?.totalPages)*100;
if (response.pptStatus === "done") {
emit('addSuccess',{...res,url:response.pptUrl})
ElMessage.success("生成成功");
} else {
const sleepTime = 2000;

View File

@ -332,10 +332,6 @@ export default {
const { id, rootid } = sessionStore.get('subject.curNode')
const path="/teaching/aptindex?id="+items.fileId + "&unitId=" + id + "&bookId=" + rootid;
let configObj = outLink().getBaseData()
configObj.fullPath = 'https://localhost:7860/'
configObj.data.url = 'https://localhost:7860/'
configObj.data.domain = 'localhost'
console.log(configObj)
let fullPath = configObj.fullPath + path
fullPath = fullPath.replaceAll('//', '/')
//

View File

@ -13,7 +13,7 @@
</template>
<script setup>
import AiPptist from './ai-pptist.vue';
import AiPptist from './ai-pptistV2.vue';
const model = defineModel()
const emit = defineEmits(['addSuccess'])
const props = defineProps({

View File

@ -5,7 +5,7 @@
<div class="info">
<div class="info-name">{{ state.user.nickName }}</div>
<div class="infomation">
<selectClass/>
<selectClass v-if="!isSubject"/>
</div>
</div>
</div>
@ -46,8 +46,10 @@ const state = reactive({
postGroup: {}
})
const isSubject = ref(false)
async function getUser() {
getUserProfile().then((response) => {
isSubject.value = response.roleGroup.indexOf('场馆管理员') != -1
// response.data.avatar = import.meta.env.VITE_APP_BASE_API + response.data.avatar
Object.assign(state.user,response.data)
state.roleGroup = response.roleGroup

View File

@ -68,6 +68,7 @@ import {PPTXFileToJson} from '@/AixPPTist/src/hooks/useImport' // ppt转json
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_smarttalk from '@/api/file' // api
import msgUtils from '@/plugins/modal' //
const userStore = useUserStore()
@ -136,6 +137,8 @@ const addAiPPT = async(res) => {
const p_params = {parentContent: JSON.stringify(content)}
const parentid = await HTTP_SERVER_API('addEntpcoursefile', p_params)
if (!!parentid??null) { //
// -Smarttalk
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: '', slides: resSlides }
@ -206,6 +209,20 @@ emitter.on('changeResult', (item) => {
// HTTP
const HTTP_SERVER_API = (type, params = {}) => {
switch (type) {
case 'addSmarttalk': { //
const node = courseObj.node || {}
const def = {
fileId: '', // id - Entpcoursefile id
fileFlag: 'aptist',
fileShowName: node.itemtitle + '.aptist',
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('请选择章节?')
@ -298,6 +315,7 @@ const toRousrceUrl = async(o) => {
const curNode = reactive({})
onMounted(() => {
let data = sessionStore.get('subject.curNode')
console.log('data', sessionStore)
Object.assign(curNode, data);
courseObj.node = data