Merge branch 'main' into zouyf_dev

This commit is contained in:
“zouyf” 2024-12-05 14:37:13 +08:00
commit 35c088c25a
36 changed files with 1767 additions and 267 deletions

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,8 +4,10 @@
* @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
import * as useStore from '../store' // pptist-状态管理
import { sessionStore } from '@/utils/store' // electron-store 状态管理
import useUserStore from '@/store/modules/user' // 外部-用户信息
@ -53,6 +55,7 @@ export class PPTApi {
return new Promise(async (resolve, reject) => {
const params: object = { parentid, orderByColumn: 'fileidx', isAsc: 'asc', pageSize: 9999 }
const res: Result = await API_entpcoursefile.listEntpcoursefileNew(params)
console.log(res.rows,'res.rows');
if (res.code === 200) {
const slides = (res.rows || []).map(o => {
if (!!o.datacontent) {
@ -63,8 +66,15 @@ export class PPTApi {
// 如果没有数据,默认空白页
return {id: o.id,elements:[],background:{type:"solid",color:"#fff"}}
})
// 活动列表处理
const workList = (res.rows || []).map(o => o.activityContent)
const workItem = [...res.rows]
slidesStore.updateSlideIndex(0) // 下标0 为第一页
slidesStore.setSlides(slides) // 写入数据
// 写入作业列表数据
slidesStore.setWorkList(workList)
// 获取所有的pptlist的数据
slidesStore.setWorkItem(workItem)
resolve(true)
} else msgUtils.msgError(res.msg || '获取数据失败');resolve(false)
})
@ -166,6 +176,14 @@ export class PPTApi {
} else msgUtils.msgError(res.msg || '删除失败');resolve(false)
})
}
// 更新-备课资源 标题
static updateSmarttalk(data: object): Promise<Boolean> {
return API_smarttalk.updateSmarttalk(data).then(res => {
if (res.code === 200) return true
else msgUtils.msgError(res.msg || '更新失败');return false
})
}
}
export default PPTApi

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

@ -7,7 +7,8 @@ import { PPTApi } from './index'
import * as store from '../store'
import { sessionStore } from '@/utils/store' // electron-store 状态管理
const slidesStore = store.useSlidesStore()
const resource = sessionStore.get('curr.resource')
const resource = sessionStore.get('curr.resource') // apt 资源
const smarttalk = sessionStore.get('curr.smarttalk') // 备课资源
/**
* @description
*/
@ -28,4 +29,11 @@ const updatePPT = async (data) => {
data.id = resource.id
await PPTApi.updateSlide(data) // 更新ppt内容
sessionStore.set('curr.resource.title', data.title)
// 更新smarttalk内容
if (!!smarttalk && !!data.title) {
const {id, fileFlag} = smarttalk
const params = { id, fileShowName: `${data.title}.${fileFlag}` }
await PPTApi.updateSmarttalk(params) // 更新ppt内容
sessionStore.set('curr.smarttalk.fileShowName', params.fileShowName)
}
}

View File

@ -31,6 +31,8 @@ export interface SlidesState {
slideIndex: number
viewportSize: number
viewportRatio: number
workList:Object[],
workItem:Object[],
}
export const useSlidesStore = defineStore('slides', {
@ -41,6 +43,8 @@ export const useSlidesStore = defineStore('slides', {
slideIndex: 0, // 当前页面索引
viewportSize: 1000, // 可视区域宽度基数
viewportRatio: 0.5625, // 可视区域比例默认16:9
workList:[],// 活动的列表
workItem:[],// 获取到的所有pptlist
}),
getters: {
@ -131,6 +135,13 @@ export const useSlidesStore = defineStore('slides', {
setSlides(slides: Slide[]) {
this.slides = slides
},
// 更新活动列表
setWorkList(list: Object[]) {
this.workList = list
},
setWorkItem(list: Object[]) {
this.workItem = list
},
addSlide(slide: Slide | Slide[]) {
const slides = Array.isArray(slide) ? slide : [slide]

View File

@ -6,4 +6,5 @@ export const enum ToolbarStates {
SLIDE_DESIGN = 'slideDesign',
SLIDE_ANIMATION = 'slideAnimation',
MULTI_POSITION = 'multiPosition',
EL_ACTIVE = 'elActive',
}

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

@ -66,6 +66,9 @@
</Draggable>
<div class="page-number">幻灯片 {{slideIndex + 1}} / {{slides.length}}</div>
<!-- 引入活动的列表页面 -->
<Active ref="activeRef" v-show="false"/>
</div>
</template>
@ -85,12 +88,13 @@ import ThumbnailSlide from '../../../views/components/ThumbnailSlide/index.vue'
import LayoutPool from './LayoutPool.vue'
import Popover from '../../../components/Popover.vue'
import Draggable from 'vuedraggable'
import Active from '../Toolbar/ElementStylePanel/Active/index.vue'
const mainStore = useMainStore()
const slidesStore = useSlidesStore()
const keyboardStore = useKeyboardStore()
const { selectedSlidesIndex: _selectedSlidesIndex, thumbnailsFocus } = storeToRefs(mainStore)
const { slides, slideIndex, currentSlide } = storeToRefs(slidesStore)
const { slides, slideIndex, currentSlide, workList, workItem } = storeToRefs(slidesStore)
const { ctrlKeyState, shiftKeyState } = storeToRefs(keyboardStore)
const { slidesLoadLimit } = useLoadSlides()
@ -123,6 +127,8 @@ const {
updateSectionTitle,
} = useSectionHandler()
const activeRef = ref()
//
const thumbnailsRef = ref<InstanceType<typeof Draggable>>()
watch(() => slideIndex.value, () => {
@ -145,6 +151,9 @@ watch(() => slideIndex.value, () => {
//
const changeSlideIndex = (index: number) => {
console.log(workItem.value[index],'hasSection');
activeRef.value.clickPPTList(workItem.value[index])
mainStore.setActiveElementIdList([])
if (slideIndex.value === index) return

View File

@ -0,0 +1,291 @@
<template>
<div>
<div style="display: flex;flex-wrap: wrap;">
<el-button size="small" title="活动引用" text style="height: 54px" @click="openList()">
<div class="buttonDiv">
<svg width="26" height="26" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" fill="#646473"><path d="M133.36576 308.12842667v407.74314666c0 96.39253333 78.31552 174.76266667 174.76266667 174.76266667h407.74314666c96.39253333 0 174.76266667-78.31552 174.76266667-174.76266667V308.12842667c0-96.39253333-78.31552-174.76266667-174.76266667-174.76266667H308.12842667a174.87189333 174.87189333 0 0 0-174.76266667 174.76266667zM75.09333333 308.12842667A233.19893333 233.19893333 0 0 1 308.12842667 75.09333333h407.74314666A233.19893333 233.19893333 0 0 1 948.90666667 308.12842667v407.74314666A233.19893333 233.19893333 0 0 1 715.87157333 948.90666667H308.12842667A233.19893333 233.19893333 0 0 1 75.09333333 715.87157333V308.12842667z m706.9696 192.07509333c0 24.46677333-5.67978667 48.22357333-16.98474666 71.21578667-11.35957333 22.99221333-26.76053333 43.52682667-46.25749334 61.54922666-19.44234667 18.0224-42.05226667 32.44032-67.61130666 43.25376a207.20298667 207.20298667 0 0 1-81.21002667 16.16554667h-26.54208c-10.37653333 0-21.62688 0.10922667-33.64181333 0.27306667-12.01493333 0.21845333-23.86602667 0.32768-35.66250667 0.32768h-31.9488c-13.1072 0-23.53834667 1.25610667-31.23882667 3.71370666-7.70048 2.51221333-11.35957333 7.91893333-10.92266666 16.16554667 0 6.22592-0.10922667 13.38026667-0.32768 21.46304-0.21845333 8.08277333-0.32768 15.45557333-0.32768 22.06378667 0 10.81344-3.16757333 17.74933333-9.50272 20.86229333-6.33514667 3.11296-14.7456 0.92842667-25.12213334-6.5536-10.92266667-7.42741333-23.37450667-16.27477333-37.41013333-26.43285333a36528.56490667 36528.56490667 0 0 0-126.42986667-91.09504c-10.37653333-7.42741333-15.72864-14.47253333-15.94709333-21.13536-0.27306667-6.60821333 4.36906667-13.65333333 13.9264-21.13536 9.93962667-7.48202667 21.62688-16.27477333 34.95253333-26.43285334 13.38026667-10.15808 27.30666667-20.58922667 41.83381334-31.40266666l42.81685333-31.67573334c14.03562667-10.37653333 26.48746667-19.71541333 37.41013333-28.01664 11.74186667-9.12042667 21.62688-12.61568 29.4912-10.54037333 7.97354667 2.07530667 11.96032 8.73813333 11.96032 19.87925333 0 3.2768 0.10922667 7.3728 0.32768 12.12416a2794.40042667 2794.40042667 0 0 1 1.69301334 43.85450667c0 7.86432 2.62144 12.72490667 7.80970666 14.58176 5.24288 1.85685333 12.34261333 2.83989333 21.40842667 2.83989333 20.42538667-0.43690667 43.30837333-0.65536 68.64896-0.65536h70.66965333c10.43114667 0 20.86229333-2.18453333 31.29344-6.5536 10.37653333-4.36906667 19.93386667-10.10346667 28.56277334-17.36704 8.57429333-7.26357333 15.61941333-15.67402667 21.02613333-25.17674666 5.46133333-9.55733333 8.192-19.6608 8.192-30.47424v-43.52682667c0-16.60245333-0.10922667-32.65877333-0.32768-48.22357333a2840.00256 2840.00256 0 0 1-0.38229333-40.68693334V341.6064c0-9.12042667 3.05834667-16.65706667 9.17504-22.66453333 6.11669333-6.00746667 13.81717333-10.59498667 23.10144-13.70794667 9.28426667-3.11296 19.38773333-4.64213333 30.25578666-4.64213333 10.92266667 0 20.97152 1.41994667 30.25578667 4.36906666 9.28426667 2.83989333 16.98474667 7.09973333 23.10144 12.72490667 6.11669333 5.57056 9.17504 12.56106667 9.17504 20.80768v46.03904c0 10.75882667 0.10922667 22.28224 0.32768 34.51562667 0.27306667 12.23338667 0.38229333 23.92064 0.38229333 35.11637333V500.20352z" p-id="7882"></path></svg>
<div style="margin-top: 10px">活动引用</div>
</div>
</el-button>
<el-button size="small" title="习题训练" text style="height: 54px" @click="showDialog('习题训练')">
<div class="buttonDiv">
<svg width="26" height="26" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" fill="#646473"><path d="M473.6 204.8c21.2 0 38.4 17.2 38.4 38.4s-17.2 38.4-38.4 38.4H243.2c-21.2 0-38.4-17.2-38.4-38.4s17.2-38.4 38.4-38.4h230.4z m-51.2 153.6c21.2 0 38.4 17.2 38.4 38.4s-17.2 38.4-38.4 38.4H243.2c-21.2 0-38.4-17.2-38.4-38.4s17.2-38.4 38.4-38.4h179.2zM371.2 512c21.2 0 38.4 17.2 38.4 38.4s-17.2 38.4-38.4 38.4h-128c-21.2 0-38.4-17.2-38.4-38.4S222 512 243.2 512h128z m192-384H153.6c-12.6 0-23 9.1-25.2 21l-0.4 4.6v512c0 12.6 9.1 23 21 25.2l4.6 0.4h257.1c-0.7-8.5-1.1-17-1.1-25.6 0-124 73.5-230.8 179.2-279.4V153.6c0-12.6-9.1-23-21-25.2l-4.6-0.4zM768 460.8H665.6v153.6H512v102.4h153.6v153.6H768V716.8h153.6V614.4H768V460.8zM563.2 51.2c56.6 0 102.4 45.8 102.4 102.4v209c16.6-2.8 33.7-4.2 51.2-4.2 169.7 0 307.2 137.5 307.2 307.2S886.5 972.8 716.8 972.8c-133.7 0-247.5-85.5-289.7-204.8H153.6C97 768 51.2 722.2 51.2 665.6v-512C51.2 97 97 51.2 153.6 51.2h409.6z" p-id="13225"></path></svg>
<div style="margin-top: 10px">习题训练</div>
</div>
</el-button>
<el-button size="small" title="课堂展示" text style="height: 54px" @click="showDialog('课堂展示')">
<div class="buttonDiv">
<svg width="26" height="26" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" fill="#646473"><path d="M741.75298 604.605763l3.836774 0.697596 17.43988 3.767014c154.656857 35.089039 260.133252 103.732407 260.133252 194.070985 0 88.455072-101.360583 156.261326-251.134274 191.838682l-9.068737 2.092785-17.370121 3.836774-17.788678 3.487976c-23.927515 4.39485-48.831664 8.092104-74.642686 11.022004l-19.462907 2.023026-24.69487 2.092786-5.022685 0.348797-20.090742 1.325431-10.18489 0.488317-10.25465 0.418557-20.579058 0.697595c-10.324409 0.209279-20.788337 0.348798-31.322025 0.348798l-15.695892-0.139519-15.556373-0.279038-20.648818-0.627836-10.18489-0.418557-10.18489-0.488317-20.160501-1.325431-9.975612-0.697595-19.741944-1.743988a1100.665713 1100.665713 0 0 1-75.968118-9.905852l-18.137475-3.139178-17.788678-3.487976-17.37012-3.767014C105.406635 961.983786 0 893.410178 0 803.141358c0-88.594591 101.360583-156.261326 251.134273-191.9782l8.998979-2.092785 17.43988-3.767014 3.767014-0.697596v93.756796l-10.952245 2.581102-15.207575 3.906533-14.6495 4.046052-13.951904 4.185572c-75.340282 23.857756-121.660604 61.946454-121.660603 89.989781 0 27.206213 43.250903 61.527897 114.196335 85.036855l7.394509 2.371824 14.021663 4.255331 14.6495 4.046052 15.207575 3.906533c8.580421 2.092786 17.43988 4.115812 26.578377 5.999319l13.951904 2.790381 17.021323 3.139178 8.7897 1.39519 17.788678 2.790381 9.068737 1.255672 18.556033 2.302064 14.161182 1.604469 14.370462 1.39519 19.532665 1.53471a1139.731044 1139.731044 0 0 0 142.867498 1.255671l19.881463-1.255671 19.532666-1.53471c8.580421-0.767355 17.021323-1.674228 25.392466-2.581102l12.417194-1.53471 18.276995-2.441583 17.858437-2.790381 8.71994-1.39519 17.091082-3.139178c9.417535-1.813748 18.486273-3.697255 27.415492-5.720281l13.11479-3.069419 15.207575-3.906533 14.649499-4.046052 13.951904-4.185572c75.340282-23.857756 121.590844-59.365352 121.590845-87.408679 0-27.206213-43.250903-64.178759-114.196335-87.617957l-7.39451-2.371824-14.021663-4.255331-14.649499-4.115811-15.207576-3.836774-10.952245-2.581102V604.605763zM451.204578 588.072757l121.660604 69.82928-131.078139 86.153008 9.417535-155.982288z m280.921589-484.131072l121.660603 69.82928-257.552149 443.810069-121.590844-69.899039 257.48239-443.74031zM842.904285 6.278357l40.530281 23.22992a46.459841 46.459841 0 0 1 17.160842 63.690442l-23.439198 40.321003L755.565365 63.620683l23.36944-40.321003a46.948157 46.948157 0 0 1 63.96948-17.021323z" p-id="59383"></path></svg>
<div style="margin-top: 10px">课堂展示</div>
</div>
</el-button>
<el-button size="small" title="常规作业" text style="height: 54px;margin-left: 0" @click="showDialog('常规作业')">
<div class="buttonDiv">
<svg width="26" height="26" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" fill="#646473"><path d="M901.705143 511.926857h-55.954286a8.045714 8.045714 0 0 1-8.045714-8.045714V183.881143H181.686857v656.091428H501.76c4.388571 0 8.045714 3.510857 8.045714 7.899429v56.027429c0 4.388571-3.657143 8.045714-8.045714 8.045714H141.750857a31.963429 31.963429 0 0 1-32.036571-32.036572V143.872c0-17.627429 14.336-31.963429 32.036571-31.963429H877.714286c17.700571 0 32.036571 14.336 32.036571 31.963429V503.954286c0 4.388571-3.657143 8.045714-8.045714 8.045714zM731.428571 911.945143a36.571429 36.571429 0 0 1-36.571428-36.571429v-109.714285H585.142857a36.571429 36.571429 0 0 1 0-73.142858h109.714286v-109.714285a36.571429 36.571429 0 0 1 73.142857 0v109.714285H877.714286a36.571429 36.571429 0 1 1 0 73.142858h-109.714286v109.714285a36.571429 36.571429 0 0 1-36.571429 36.571429z" p-id="22184"></path></svg>
<div style="margin-top: 10px">常规作业</div>
</div>
</el-button>
</div>
<Divider />
<!-- 作业列表 -->
<div class="c-apt-r">
<!-- 显示-作业内容 -->
<template v-for="(item, index) in workList">
<div class="item">
<div class="item-title">
<el-tag :type="getTagType(item.worktype) || 'primary'">{{item.worktype}}</el-tag>
<el-tooltip :content="item.title||item.uniquekey" placement="top">
<div class="tt">{{item.title||item.uniquekey}}</div>
</el-tooltip>
<el-button class="btn-del" type="danger" link @click="handleRemoveDemoActivityClassWork(item)">删除</el-button>
</div>
</div>
</template>
</div>
<!-- // -->
<el-dialog v-model="dialogVisible" append-to-body :show-close="false" width="80%">
<NewClassTsakAssign :currentCourse='currentCourse'/>
</el-dialog>
<!-- 活动引用 -->
<el-dialog
v-loading="tasklist_loading"
v-model="activeVisible"
append-to-body
:show-close="false"
width="40%"
@selection-change="handleSelectionChange">
<el-table :data="taskList" style="width: 100%" height="500">
<el-table-column type="selection" :selectable="selectable" width="55" />
<el-table-column prop="evaltitle" label="活动名称" width="150" />
<el-table-column prop="worktype" label="活动类型" width="120" sortable>
<template #default="scope">
<el-tag :type="getTagType(scope.row.worktype) || 'primary'">{{ scope.row.worktype }}</el-tag>
</template>
</el-table-column>
<el-table-column prop="timestamp" label="创建时间" sortable/>
</el-table>
<template #footer>
<el-button @click="activeVisible = false"> </el-button>
<el-button type="primary" @click="save"> </el-button>
</template>
</el-dialog>
</div>
</template>
<script setup>
import { ref, reactive, onMounted, onBeforeMount, defineExpose } from 'vue'
import Divider from '../../../../../components/Divider.vue'
import {listEntpcoursefile} from '@/api/education/entpcoursefile'
import {homeworklist} from '@/api/teaching/classwork'
import { processList } from "@/hooks/useProcessList";
import { listEntpcoursework } from "@/api/classTask/index";
import { ElMessageBox } from 'element-plus'
import NewClassTsakAssign from '@/views/classTask/newClassTaskAssign/index.vue'
import { sessionStore } from '@/utils/store'
import { useGetHomework } from '@/hooks/useGetHomework'
const currentCourse = reactive({
textbookId:0,
levelFirstId:0,
levelSecondId:0,
coursetitle:'',
node:{},
id:1,
worktype:'',
})
const dataList = ref([])
const dialogVisible = ref(false)
const tasklist_loading = ref(false)
//
const taskList = ref([])
//
const activeVisible = ref(false)
const params = reactive({
parentid:14766,
pageSize:500,
orderby:'fileidx'
})
const type = ref([
{
label:'习题训练',
value:'danger'
},
{
label:'课堂展示',
value:'success'
},
{
label:'常规作业',
value:'primary'
},
])
//
const workList = ref([])
const selectable = (row,index) => {
console.log(row,index,'row,index');
return true
}
const clickPPTList = (item) => {
console.log(item,'点击了')
workList.value = []
let datacontent = item.datacontent;
let pptJson = "";
if(typeof datacontent === 'string') pptJson = JSON.parse(datacontent)
if(pptJson&&pptJson[0]&&pptJson[0].classworkList) {
homeworklist({ids:pptJson[0].classworkList, pageSize: 100}).then( async res => {
await formatClassWorkFile(res.rows)
})
}
}
const formatClassWorkFile = async (postData) => {
return new Promise(async (resolve, reject)=>{
for (let i = 0; i < postData.length; i++) {
let item = postData[i];
switch (item.worktype) {
case '框架梳理': {
}
break;
case '习题训练': {
item.entpcourseworklistarray = item.entpcourseworklist?JSON.parse('['+item.entpcourseworklist+']'):[];
let workIds = item.entpcourseworklistarray.map(items=>items.id).join(',')
let ress = await listEntpcoursework({ids:workIds})
processList(ress.rows)
item.workShowList = ress.rows
}
break;
case '课堂展示': {
item.base64 = JSON.parse(item.workcodes).base64
}
break;
case '常规作业': {
item.prevData = JSON.parse(item.workcodes)
}
}
workList.value.push(item)
}
resolve()
})
}
//
const handleRemoveDemoActivityClassWork = (item) => {
ElMessageBox.confirm('是否确认删除?')
.then(function () {
workList.value.splice(workList.value.indexOf(item), 1);
})
.catch(() => {});
}
// tag
const getTagType = (worktype) => {
return type.value.find(item => item.label == worktype).value
}
//
const initHomeWork = async()=> {
tasklist_loading.value = true;
const { res, chapterId } = await useGetHomework(sessionStore.get('subject.curNode'));
taskList.value = res;
tasklist_loading.value = false;
}
//
const handleSelectionChange = (val) => {
console.log(val,'多选')
}
//
const showDialog = (item) => {
currentCourse.worktype = item
dialogVisible.value = true
}
const openList = () => {
activeVisible.value = true
initHomeWork()
}
//
const save = () => {
console.log('添加了')
activeVisible.value = false
}
onMounted(() => {
// console.log(sessionStore.get('subject.curBook'),'curBook');
// console.log(sessionStore.get('subject.subjectTree'),'subjectTree');
// console.log(sessionStore.get('subject.bookList'),'bookList');
console.log(sessionStore.get('subject.curNode'),'curNode');
const curNode = sessionStore.get('subject.curNode')
currentCourse.textbookId = curNode.rootid
currentCourse.levelFirstId = curNode.parentNode.id
currentCourse.levelSecondId = curNode.id
currentCourse.coursetitle = curNode.itemtitle,
currentCourse.node = curNode
listEntpcoursefile(params).then((res) => {
dataList.value = [...res.rows]
})
})
defineExpose({
clickPPTList
})
</script>
<style scoped lang="scss">
.buttonDiv{
margin-top: 4px;
display: flex;
align-items: center;
justify-content: space-between;
flex-direction: column;
}
// (apt)-
.c-apt-r{
flex:1;
overflow: auto;
.item{
position: relative;
margin-top: 5px;
.item-title{
display: flex;
align-items: center;
.tt{
flex: 1;
padding-left: 10px;
cursor: default;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
font-size: 13px;
color: #606266;
}
.btn-del{
margin-right: 10px;
}
}
.item-body{
position: relative;
border: 1px solid silver;
padding: 10px;
background: #fff;
border-radius: 3px;
margin: 5px 10px;
cursor: default;
&:hover{
border-color: var(--el-color-primary);
.el-text{
--el-text-color: var(--el-color-primary);
}
.el-tag{
--el-tag-bg-color: var(--el-color-primary);
--el-tag-border-color: var(--el-color-primary);
--el-tag-text-color: var(--el-color-white);
}
}
.c-icon-info{
position: absolute;
top: -5px;
right: -5px;
}
}
.item-divider{
margin: 5px 0;
margin-left: 10px;
width: calc(100% - 20px);
--el-border-color: var(--current-color);
}
}
}
</style>

View File

@ -25,6 +25,8 @@ import SlideDesignPanel from './SlideDesignPanel.vue'
import SlideAnimationPanel from './SlideAnimationPanel.vue'
import MultiPositionPanel from './MultiPositionPanel.vue'
import SymbolPanel from './SymbolPanel.vue'
//
import SymbolActivePanel from './ElementStylePanel/Active/index.vue'
import Tabs from '../../../components/Tabs.vue'
interface ElementTabs {
@ -54,6 +56,7 @@ const slideTabs = [
{ label: '设计', key: ToolbarStates.SLIDE_DESIGN },
{ label: '切换', key: ToolbarStates.SLIDE_ANIMATION },
{ label: '动画', key: ToolbarStates.EL_ANIMATION },
{ label: '活动', key: ToolbarStates.EL_ACTIVE },
]
const multiSelectTabs = [
{ label: '样式', key: ToolbarStates.EL_STYLE },
@ -86,6 +89,7 @@ const currentPanelComponent = computed(() => {
[ToolbarStates.SLIDE_ANIMATION]: SlideAnimationPanel,
[ToolbarStates.MULTI_POSITION]: MultiPositionPanel,
[ToolbarStates.SYMBOL]: SymbolPanel,
[ToolbarStates.EL_ACTIVE]: SymbolActivePanel,//
}
return panelMap[toolbarState.value] || null
})

View File

@ -0,0 +1,81 @@
import axios from 'axios'
import request from '@/utils/request'
import { getToken } from "@/utils/auth";
// 文生图片
export function convertTextToPicture(data) {
return axios({
url: 'https://ai.ysaix.com:7853/prompt',
method: 'post',
headers: {
'Content-Type': 'application/json',
'Accept': '*/*'
},
data: data
})
}
// 获取任务列表
export function getQueue() {
return axios({
url: `https://ai.ysaix.com:7853/queue`,
method: 'get',
})
}
// 获取生图任务id
export function getPromptId(id) {
return axios({
url: `https://ai.ysaix.com:7853/history/${id}`,
method: 'get',
})
}
// 获取生成图片路径
export function getPicture(data) {
return axios({
url: 'https://ai.ysaix.com:7853/view',
method: 'get',
params: data
})
}
// 大模型对话生成prompt模板
export function chattoprompt(dataset_id,prompt) {
return axios({
url: '/api/v1/parse/docs',
method: 'post',
headers: {
'Authorization': 'Bearer ragflow-IwMDI1MGU2YTU3NjExZWZiNWEzMDI0Mm',
},
data: {
'dataset_id': dataset_id,
'prompt': prompt
}
})
}
// prompt敏感词校验
export function textSensitiveWord(data) {
return request({
url: '/verify/text',
method: 'post',
data: {
'text' : data
}
})
}
// 图片上传资源库
export function uploadPicture(data) {
return axios({
url: '/dev-api/smarttalk/file/upload',
method: 'post',
headers: {
'Accept': '*/*',
'Content-Type': 'multipart/form-data',
'Authorization': "Bearer " + getToken()
},
data: data
})
}

View File

@ -0,0 +1,627 @@
<template>
<div>
<el-row :gutter="40" style="height:auto">
<el-col :span="8">
<div>
<div style="text-align: left">反面提示词</div>
<br>
<el-input v-model="formData.prompt[12].inputs.negative_prompt" placeholder="试试在这里输入你不想其进入图画的词"
type="textarea"></el-input>
</div>
<br>
<div>
<div style="text-align: left">可选提示词</div>
<br>
<div style="font-size: 14px;text-align: left">风格特点</div>
<el-checkbox-group v-model="checkList">
<el-checkbox v-for="(prompt, index) in promptOption1" :key="index" :label="prompt" :value="prompt" />
</el-checkbox-group>
</div>
<div>
<div style="font-size: 14px;text-align: left">主体对象</div>
<el-checkbox-group v-model="checkList">
<el-checkbox v-for="(prompt, index) in promptOption2" :key="index" :label="prompt" :value="prompt" />
</el-checkbox-group>
</div>
<div>
<div style="font-size: 14px;text-align: left">场景</div>
<el-checkbox-group v-model="checkList">
<el-checkbox v-for="(prompt, index) in promptOption3" :key="index" :label="prompt" :value="prompt" />
</el-checkbox-group>
</div>
<div style="text-align: right">
<el-button type="primary" @click="addCheckListToPrompt">添加</el-button>
<el-button type="primary" @click="deletePrompt">清空</el-button>
</div>
<!-- 具体参数相关内容 -->
<el-form label-position="top" size="small">
<el-form-item label="图片尺寸" style=" margin-bottom: 0px">
<el-row class="ratio-options" :gutter="10" style="margin-bottom: -15px;margin-top: -25px">
<el-col v-for="ratio in ratioOptions" :key="ratio.value" :span="4">
<el-button :type="form.ratio === ratio.value ? 'primary' : 'default'" @click="setRatio(ratio.value)"
class="ratio-option" block>
{{ ratio.label }}
</el-button>
</el-col>
</el-row>
</el-form-item>
<el-form-item label="图片比例" style=" margin-bottom: 0px">
<el-row class="ratio-options" :gutter="10" style="margin-bottom: -15px;margin-top: -25px">
<el-col v-for="ratio in ratioOptions2" :key="ratio.value" :span="4">
<el-button :type="form.ratios2 === ratio.value ? 'primary' : 'default'" @click="setRatio2(ratio.value)"
class="ratio-option" block>
{{ ratio.label }}
</el-button>
</el-col>
</el-row>
</el-form-item>
<el-form-item label="图片数量" style=" margin-bottom: 0px">
<el-row style="margin-bottom: -15px;margin-top: -25px">
<el-button style="width: 10%" :type="num_picture === 1 ? 'primary' : 'default'" @click="changePicture(1)">
1
</el-button>
<el-button style="width: 10%" :type="num_picture === 2 ? 'primary' : 'default'" @click="changePicture(2)">
2
</el-button>
<el-button style="width: 10%" :type="num_picture === 3 ? 'primary' : 'default'" @click="changePicture(3)">
3
</el-button>
<el-button style="width: 10%" :type="num_picture === 4 ? 'primary' : 'default'" @click="changePicture(4)">
4
</el-button>
</el-row>
</el-form-item>
<el-form-item label="随机种子">
<el-slider v-model="formData.prompt[14].inputs.seed" :min="1" :max="10000000" show-input />
</el-form-item>
<el-form-item label="cfg (数值越高,生图过程与提示词越相关)">
<el-slider v-model="formData.prompt[14].inputs.cfg" :max="20" show-input />
</el-form-item>
</el-form>
</el-col>
<el-col :span="16">
<div
:style="{ height: divHeight + 'px', 'overflow-y': 'auto', 'margin-bottom': '10px', 'background-color': '#f5f5f5', 'padding': '10px' }">
<div v-for="(resultItem, resultIndex) in resultList" :key="resultIndex">
<div style="display: flex; flex-wrap: wrap; justify-content: flex-end;">
<el-card style="max-width: 50%; margin-right: 10px; display: inline-block;background-color: lightblue;">
<div>
<p style="word-wrap: break-word; overflow-wrap: break-word;text-align: left">{{ resultItem }}</p>
</div>
</el-card>
</div>
<el-row :gutter="10" justify="center">
<el-col :span="6" v-for="(url, index) in pictureurl[resultIndex]" :key="index">
<el-card style="margin-bottom: 5%;height:95%">
<el-image style="width: 100%;" fit="cover" :src="url" :preview-src-list="pictureurl"
:initial-index="index" class="equal-size-image"></el-image>
<div style="text-align: center; margin-top: 15px">
<el-button :disabled="buttonStates[resultIndex][index].disabled" size="small" type="primary"
@click="saveImage(resultIndex, index, url, resultItem)">{{ buttonStates[resultIndex][index].text
}}</el-button>
</div>
</el-card>
</el-col>
</el-row>
</div>
<el-row :gutter="10" justify="center">
<el-col v-if="pictureLoading" :span="6" v-for="n in generateArray(this.skeletonNumber)" :key="n">
<el-card>
<el-skeleton class="custom-skeleton-item" animated>
<template #template>
<el-skeleton-item variant="image" class="custom-skeleton-item"></el-skeleton-item>
</template>
</el-skeleton>
</el-card>
</el-col>
</el-row>
<el-row :gutter="10" justify="center">
<el-col :span="12">
<el-card v-for="(item, index) in resultData" :key="index"
style="display: flex;flex-direction: column;align-items: center;margin-bottom: 10px" justify="center"
@click="handleCardClick(item)" :class="{ 'card-hover': !pictureLoading }">
<p style="word-wrap: break-word; overflow-wrap: break-word;">{{ item }}</p>
</el-card>
</el-col>
</el-row>
</div>
<div style="text-align: center">
<el-input style="width: 70%;" v-model="promptData" placeholder="试试输入你心中的画面,尽量描述具体点哦,可以按照这个格式来写: 提示词=主体+风格+场景"
type="textarea">
</el-input>
<el-button type="primary" :disabled="pictureLoading" @click="fetchData">
{{ !pictureLoading ? "生成图片" : "请等待" }}
</el-button>
</div>
</el-col>
</el-row>
</div>
</template>
<script>
import { convertTextToPicture, getQueue, getPromptId, getPicture, chattoprompt, textSensitiveWord, uploadPicture } from "@/api/aiGeneratedImage/index.js";
import CryptoJS from 'crypto-js'
import { useRoute } from 'vue-router'
export default {
data() {
return {
form: {
ratio: "512",
ratios2: "1/1",
},
ratioOptions: [
{ value: "512", label: "512" },
{ value: "768", label: "768" },
{ value: "1024", label: "1024" },
{ value: "1280", label: "1280" },
{ value: "2048", label: "2048" },
],
ratioOptions2: [
{ value: "1/1", label: "1:1" },
{ value: "4/3", label: "4:3" },
{ value: "3/4", label: "3:4" },
{ value: "9/16", label: "9:16" },
{ value: "16/9", label: "16:9" },
],
//
imageData: {
imageUrls: [],
prompt: [],
time: [],
id: [],
},
promptOption1: ["赛博朋克", "水墨风", "莫奈风格", "二次元", "中国风", "写实风格", "水彩风格", "工笔画", "素描风", "未来主义", "超现实主义", "映像派"],
promptOption2: ["男生", "女生", "老年人", "船舶", "蝴蝶", "狮子", "兔子", "飞机", "中年人", "大树", "长江", "坦克"],
promptOption3: ["雨林", "沙漠", "湖泊", "天空", "城市", "乡村", "太空", "教室"],
promptData: "",
//
formData: {
client_id: "533ef3a3-39c0-4e39-9ced-37d290f371f8",
prompt: {
3: {
inputs: {
images: ["10", 0],
},
class_type: "PreviewImage",
_meta: {
title: "Preview Image",
},
},
6: {
inputs: {
model: "Kwai-Kolors/Kolors",
precision: "fp16",
},
class_type: "DownloadAndLoadKolorsModel",
_meta: {
title: "(Down)load Kolors Model",
},
},
10: {
inputs: {
samples: ["14", 0],
vae: ["11", 0],
},
class_type: "VAEDecode",
_meta: {
title: "VAE Decode",
},
},
11: {
inputs: {
vae_name: "sdxl.vae.safetensors",
},
class_type: "VAELoader",
_meta: {
title: "Load VAE",
},
},
12: {
inputs: {
prompt: "",
negative_prompt: "",
num_images_per_prompt: 1,
chatglm3_model: ["13", 0],
},
class_type: "KolorsTextEncode",
_meta: {
title: "Kolors Text Encode",
},
},
13: {
inputs: {
precision: "quant8",
},
class_type: "DownloadAndLoadChatGLM3",
_meta: {
title: "(Down)load ChatGLM3 Model",
},
},
14: {
inputs: {
width: 1024,
height: 1024,
seed: 1000102404233412,
steps: 25,
cfg: 5,
scheduler: "EulerDiscreteScheduler",
denoise_strength: 1,
kolors_model: ["6", 0],
kolors_embeds: ["12", 0],
},
class_type: "KolorsSampler",
_meta: {
title: "Kolors Sampler",
},
},
},
},
picture: {
filename: "",
type: "temp",
subfolder: "",
preview: "",
channel: "",
},
buttonStates: [], //
resultList: [],
pictureurl: [],
percentage: 50,
num_picture: 1,
skeletonNumber: 0,
cfg_value: 5,
pictureLoading: false,
QueueNumber: 0, //
userIdInComponent: '',
activeName: '1',
checkList: [], //prompt
totalData: 0,
// deleteImageSrc: deletebutton,
timer: null,
isTimerPaused: false,
resultData: [],
divHeight: 0,
dataset_id: '',
courseName: '',
levelFirstId: '',
levelSecondId: '',
textbookId: '',
};
},
methods: {
//
generateArray(length) {
return Array.from({ length }, (_, index) => index);
},
//
Randomseed() {
this.formData.prompt[14].inputs.seed = Math.floor(
Math.random() * 10000001
);
},
sleep(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
},
//
async fetchData() {
// prompt
if (this.promptData == "") {
this.$message.error("prompt不能为空");
return;
}
const prompt = this.promptData
//
await textSensitiveWord(prompt)
this.setRatio(this.form.ratio)
this.changeSize()
this.resultList.push(this.promptData)
this.skeletonNumber = this.num_picture
const skeletonItemNumber = this.num_picture
// const userId = this.userIdInComponent
// const model = 'kolors'
// prompt
let temp = "";
if (skeletonItemNumber > 1) {
for (let i = 1; i < skeletonItemNumber; i++) {
temp += "|" + this.promptData;
}
this.formData.prompt[12].inputs.prompt = this.promptData + temp;
} else {
this.formData.prompt[12].inputs.prompt = this.promptData;
}
// json
const transformedData = {
client_id: this.formData.client_id,
prompt: Object.fromEntries(
Object.entries(this.formData.prompt).map(([key, value]) => [
key,
{
inputs: value.inputs,
class_type: value.class_type,
_meta: value._meta,
},
])
),
};
const jsonData = JSON.stringify(transformedData, null, 2);
//
try {
this.pictureLoading = true;
const response = await convertTextToPicture(jsonData); //
const pictureId = response.data.prompt_id;
const queue_Number = response.data.number
let queue;
do {
queue = await getQueue(); //
if (this.hasId(queue, pictureId)) {
this.QueueNumber = queue_Number - queue.data.queue_running[0][0] //
await this.sleep(500);
}
} while (this.hasId(queue, pictureId));
const pictureData = await getPromptId(pictureId); //id
const jsonString = JSON.parse(pictureData.request.responseText);
const urls = []
const buttonState = [];
for (let i = 0; i < skeletonItemNumber; i++) {
this.picture.filename = jsonString[pictureId].outputs[3].images[i].filename;
const pictureURL = await getPicture(this.picture); //url
const url0 = pictureURL.request.responseURL;
urls.push(url0)
buttonState.push({
disabled: false,
text: "插入本课素材资源库",
})
}
this.skeletonNumber = 0
this.pictureurl.push(urls)
this.buttonStates.push(buttonState)
this.pictureLoading = false;
} catch (error) {
this.pictureLoading = false;
console.error("Error fetching data:", error);
}
},
//
hasId(obj, id) {
if (typeof obj !== "object" || obj === null) {
return false;
}
if (Object.values(obj).includes(id)) {
return true;
}
return Object.values(obj).some((value) => this.hasId(value, id));
},
setRatio(ratio) {
this.form.ratio = ratio;
switch (ratio) {
case "512":
this.formData.prompt[14].inputs.width = this.formData.prompt[14].inputs.height = 512;
break;
case "768":
this.formData.prompt[14].inputs.width = this.formData.prompt[14].inputs.height = 768;
break;
case "1024":
this.formData.prompt[14].inputs.width = this.formData.prompt[14].inputs.height = 1024;
break;
case "1280":
this.formData.prompt[14].inputs.width = this.formData.prompt[14].inputs.height = 1280;
break;
case "2048":
this.formData.prompt[14].inputs.width = this.formData.prompt[14].inputs.height = 2048;
break;
default:
this.formData.prompt[14].inputs.width = this.formData.prompt[14].inputs.height = 512;
}
},
setRatio2(ratio) {
this.form.ratios2 = ratio;
},
changeSize() {
const a = this.form.ratios2
if (a == "1/1") {
this.formData.prompt[14].inputs.width = this.formData.prompt[14].inputs.height
} else if (a == "4/3") {
this.formData.prompt[14].inputs.height = (this.formData.prompt[14].inputs.width) * 3 / 4
} else if (a == "3/4") {
this.formData.prompt[14].inputs.width = (this.formData.prompt[14].inputs.height) * 3 / 4
} else if (a == "9/16") {
this.formData.prompt[14].inputs.width = (this.formData.prompt[14].inputs.height) * 9 / 16
} else {
this.formData.prompt[14].inputs.height = (this.formData.prompt[14].inputs.width) * 9 / 16
}
},
changePicture(num) {
this.num_picture = num;
},
//
async createPrompt() {
const dataset_id = this.dataset_id
const courseName = this.courseName
const prompt = `结合${courseName}给我三句详细的提示词用于生成图片以1.2.3的形式`
const response = await chattoprompt(dataset_id, prompt)
const resultData = response.data.answer
const promptData = []
for (let i = 1; i <= 3; i++) {
const startIndex = resultData.indexOf(`${i}.`) + `${i}.`.length;
const endIndex = resultData.indexOf("。", startIndex);
promptData.push(resultData.substring(startIndex, endIndex).trim());
}
this.resultData = promptData
},
handleCardClick(item) {
this.promptData = item
this.fetchData()
},
//
async saveImage(resultIndex, index, url, resultItem) {
this.buttonStates[resultIndex][index].disabled = true;
this.buttonStates[resultIndex][index].text = "正在保存...";
const numberIndex = url.indexOf('filename=');
const path = url.substring(numberIndex + 9);
const pngIndex = path.indexOf('.png');
const finalPath = path.substring(0, pngIndex + 4);
try {
const blob = await this.getImageBlob(`https://ai.ysaix.com:7853/view?filename=${finalPath}&type=temp`);
const hash = CryptoJS.MD5(blob).toString();
const formData = new FormData();
let file = new File([blob], `${resultItem}.png`, {
type: 'image/png'
})
//
formData.append('md5', hash);
formData.append('file', file);
formData.append('textbookId', this.textbookId);
formData.append('levelFirstId', this.levelFirstId);
formData.append('levelSecondId', this.levelSecondId);
formData.append('fileSource', '个人');
formData.append('fileRoot', '备课');
formData.append('fileShowName', `${resultItem}.png`);
formData.append('fileFlag', '素材');
const responsedata = uploadPicture(formData);
responsedata.then((response) => {
if (response.data && response.data.code === 200) {
//
this.buttonStates[resultIndex][index].text = "已保存";
this.buttonStates[resultIndex][index].disabled = true;
}
}).catch((error) => {
console.error(error);
this.buttonStates[resultIndex][index].disabled = false;
this.buttonStates[resultIndex][index].text = "插入本课素材资源库";
});
} catch (error) {
console.error(error);
}
},
getImageBlob(url) {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.open('GET', url, true);
xhr.responseType = 'blob';
xhr.onload = function () {
if (this.status === 200) {
resolve(this.response);
} else {
reject(new Error(`图片获取失败,状态码:${this.status}`));
}
};
xhr.onerror = function () {
reject(new Error('图片获取发生网络错误'));
};
xhr.send();
});
},
// convertImageToBase64(url) {
// return request({
// url: '/common/convertImageToBase64',
// method: 'get',
// params: {
// url: url
// }
// })
// },
// handleSaveImage(url) {
// this.convertImageToBase64(url).then(res => {
// this.$emit("saveImage", res)
// }, err => {
// this.$message.error(err);
// })
// },
//prompt
addCheckListToPrompt() {
const joinedString = this.checkList.join(',');
this.promptData = ''
this.promptData += joinedString;
},
//prompt
deletePrompt() {
this.promptData = '',
this.checkList = []
},
},
mounted() {
const route = useRoute();
this.dataset_id = route.query.datasetId;
this.courseName = route.query.coursetitle;
this.levelFirstId = route.query.levelFirstId;
this.levelSecondId = route.query.levelSecondId;
this.textbookId = route.query.textbookId;
this.createPrompt()
this.Randomseed();
this.divHeight = window.innerHeight * 0.80;
window.addEventListener('resize', () => {
this.divHeight = window.innerHeight * 0.85;
});
},
beforeDestroy() {
window.removeEventListener('resize', () => {
this.divHeight = window.innerHeight * 0.85;
});
}
};
</script>
<style scoped>
.el-row {
padding: 20px
}
.count-option,
.ratio-option {
width: 100%;
}
.custom-skeleton-item {
width: 100%;
height: 200px;
}
.time-display p {
font-size: 9px;
}
.equal-size-image img {
position: absolute;
top: 0;
}
.card-hover:hover {
background-color: #f0f0f0;
cursor: pointer;
}
.disabled-cursor {
cursor: not-allowed !important;
}
</style>

View File

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

View File

@ -34,7 +34,8 @@ const getFileTypeIcon = () => {
gif: 'icon-gif',
txt: 'icon-txt',
rar: 'icon-rar',
apt: 'icon-A'
apt: 'icon-A',
aptist: 'icon-A',
}
if (iconObj[name]) {
return '#' + iconObj[name]

View File

@ -26,7 +26,7 @@
</div>
</div>
<!--List-->
<div class="container-right-list">
<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">
@ -52,7 +52,7 @@
<i class="iconfont icon-ai"></i>
</div>
<div class="item-answer">
<TypingEffect :text="item.oldAnswer" :delay="10" :aiShow="item.aiShow" @complete="onSaveTemp(item)" />
<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">
@ -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,8 +434,6 @@ onUnmounted(() => {
padding: 5px 15px;
box-sizing: border-box;
.template-item {
background: #fff;
padding: 10px;

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

@ -85,6 +85,12 @@ export const constantRoutes = [
name: 'questionUpload',
meta: { title: '习题上传' }
},
{
path: 'aiKolors',
component: () => import('@/components/ai-kolors/index.vue'),
name: 'aiKolors',
meta: { title: '文生图片' }
},
]
},

View File

@ -395,5 +395,6 @@ export const dataSetJson = {
"课标-高中-英语": "e889fcac9fd011efb22a0242ac140006",
"课标-高中-数学": "e03aa4fe9fd011ef91270242ac140006",
"课标-高中-地理": "270516829fd111efb13c0242ac140006",
"课标-高中-政治": "a7df2b01aafd11ef8bb40242ac140002",
"鉴权": "ragflow-IwMDI1MGU2YTU3NjExZWZiNWEzMDI0Mm"
}

View File

@ -1,6 +1,6 @@
<template>
<div class="page">
<div class="page-top">
<div class="page-top" v-if="!isShow">
<div class="page-top-left">
<el-button type="danger" :icon="Delete" @click="handleDelete">删除</el-button>
<el-button type="success" @click="handleTaskAssignToAllClass()">批量推送</el-button>
@ -10,7 +10,7 @@
</div>
</div>
<div class="page-resource">
<div class="page-left">
<div class="page-left" v-if="!isShow">
<el-table
ref="taskTable"
v-loading="tasklist_loading"
@ -154,7 +154,9 @@ const route = useRoute();
const router = useRouter()
const { proxy } = getCurrentInstance()
const props = defineProps({
currentCourse: Object,
})
const isShow = ref(false)
const propsQueryCourseObj = route.query.courseObj;//
const courseObj = reactive({
@ -186,16 +188,48 @@ const fileLoading = ref(false); // 常规作业loading
onMounted(() => {
currentRow.value = {id:0};
console.log('propsQueryCourseObj', JSON.parse(propsQueryCourseObj));
if(propsQueryCourseObj&&JSON.parse(propsQueryCourseObj)){
if(propsQueryCourseObj){
if(JSON.parse(propsQueryCourseObj)){
courseObj.textbookId = JSON.parse(propsQueryCourseObj).bookObj //
courseObj.levelFirstId = JSON.parse(propsQueryCourseObj).levelFirstId //
courseObj.levelSecondId = JSON.parse(propsQueryCourseObj).levelSecondId //
courseObj.coursetitle = JSON.parse(propsQueryCourseObj).coursetitle // (/)
courseObj.node = JSON.parse(propsQueryCourseObj).node; //
}
}else{
if(props.currentCourse){
courseObj.textbookId = props.currentCourse.textbookId //
courseObj.levelFirstId = props.currentCourse.levelFirstId //
courseObj.levelSecondId = props.currentCourse.levelSecondId //
courseObj.coursetitle = props.currentCourse.coursetitle // (/)
courseObj.node = props.currentCourse.node; //
classWorkForm.worktype = props.currentCourse.worktype
currentRow.value = {
id:props.currentCourse.id,
worktype:props.currentCourse.worktype
}
isShow.value = true;
}else{
isShow.value = false;
}
}
initHomeWork();
})
watch(() => props.currentCourse, (newVal, oldVal) => {
if(newVal){
courseObj.textbookId = newVal.textbookId //
courseObj.levelFirstId = newVal.levelFirstId //
courseObj.levelSecondId = newVal.levelSecondId //
courseObj.coursetitle = newVal.coursetitle // (/)
courseObj.node = newVal.node; //
classWorkForm.worktype = newVal.worktype
currentRow.value = {
id:props.currentCourse.id,
worktype:props.currentCourse.worktype
}
}
console.log(newVal,'newval');
},{deep:true})
//------------
const handleItemClick = (itemName) => {
console.log('itemName', itemName);

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,15 +128,14 @@ 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,
@ -127,11 +155,11 @@ const curNode = reactive({})
const fileList = ref([])
const curFile = reactive({})
const getList = () =>{
const getList = () => {
docList({
userId: userInfo.userId,
dataset_id: dataset_id.value
}).then( res =>{
}).then(res => {
fileList.value = [...res.rows]
Object.assign(curFile, fileList.value[0])
})
@ -140,12 +168,16 @@ const getList = () =>{
const clickItem = (index, item) => {
activeIndex.value = index
Object.assign(curFile, item)
emitter.emit('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

@ -13,6 +13,7 @@
<el-button type="success" @click="openPPTist">打开PPTist</el-button>
<el-button type="info" @click="onchange('/model/examination')">考试分析</el-button>
<el-button type="primary" v-menus="dt.menus">测试</el-button>
<el-button type="success" @click="onchange('/model/aiKolors')">文生图片</el-button>
</div>
</div>
</div>
@ -51,9 +52,12 @@ import { useRouter } from 'vue-router'
import { Plus, Refresh, Upload, Files, UploadFilled } from '@element-plus/icons-vue'
import useUserStore from '@/store/modules/user' //
import msgUtils from '@/plugins/modal' //
import { createWindow, sessionStore } from '@/utils/tool' //
import { createWindow } from '@/utils/tool' //
import * as API_smarttalk from '@/api/file' // api
import * as API_entpcourse from '@/api/education/entpcourse' // api
import * as API_entpcoursefile from '@/api/education/entpcoursefile' // api
import { dataSetJson } from '@/utils/comm' // id
import { sessionStore } from '@/utils/store' //
//
import ChooseTextbook from '@/components/choose-textbook/index.vue'
import { menusEvent } from '@/plugins/vue3-menus' //
@ -156,6 +160,21 @@ const onchange = (path) => {
if (path == '/model/newClassTaskAssign') {
//
router.push({ path, query: { courseObj: JSON.stringify(courseObj) } })
} else if (path == '/model/aiKolors') {
// ai
let subjectdata = sessionStore.get('subject.curNode')
let datasubject = `课标-${subjectdata.edustage}-${subjectdata.edusubject}`
console.log(subjectdata)
router.push({
path,
query: {
datasetId: dataSetJson[datasubject],
coursetitle: courseObj.coursetitle,
levelFirstId: subjectdata.parentid,
levelSecondId: subjectdata.id,
textbookId: subjectdata.rootid,
}
});
} else {
router.push(path)
}

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

@ -59,14 +59,16 @@
<!-- 手机登录 -->
<template #item_mobile>
<div>
<div>开始新的课堂需要点击先创建课堂才能显示手机二维码</div>
<div v-if="myClassActive.filetype=='apt'">开始新的课堂需要点击先创建课堂才能显示手机二维码</div>
<div v-else>开始新的课堂需要点击先创建课堂</div>
<el-button type="warning" :loading="dt.loading" @click="createClasscourse">创建课堂</el-button>
</div>
</template>
<!-- 故障备用 -->
<template #item_backup>
<div>
<div>如果手机扫码后进入课堂但本页面没自动跳转请点击下面按钮</div>
<div v-if="myClassActive.filetype=='apt'">如果手机扫码后进入课堂但本页面没自动跳转请点击下面按钮</div>
<div v-else>本页面没自动跳转请点击下面按钮</div>
<el-button type="primary" plain @click="classTeachingStart">开始上课</el-button>
</div>
</template>
@ -81,7 +83,7 @@
<script setup>
//
import { onMounted, reactive, ref, watchEffect, watch, nextTick } from 'vue' // vue
import { onMounted, reactive, ref, watchEffect, watch, nextTick, toRaw } from 'vue' // vue
import { Refresh } from '@element-plus/icons-vue'
import { ElMessage, ElMessageBox } from 'element-plus' // ui:
import vueQr from 'vue-qr/src/packages/vue-qr.vue' // :
@ -149,9 +151,9 @@ const open = async (id, classObj) => {
teacherForm.form.classcourseid = classObj.id
}
// im-chat
nextTick(async() => {
chat = await imChatRef.value?.initImChat()
})
// nextTick(async() => {
// chat = await imChatRef.value?.initImChat()
// })
}
}
//
@ -259,7 +261,15 @@ const createClasscourse = async () => {
dt.loading = false
// getClasscourseList('update') //
ElMessage.success('创建课程-成功')
// -pptList
if (myClassActive.value.filetype == 'aptist') {
const msgEl = ElMessage.warning({message:'正在打开公屏,请稍后...',duration: 0})
setTimeout(() => {
msgEl.close()
const classcourse = {...params, id: teacherForm.form.classcourseid}
openPublicScreen(classcourse)
}, 1500);
}
}
//
const removeClasscourse = async () => {
@ -323,6 +333,21 @@ const getQrUrl = async() => {
}
teacherForm.form.qrUrl = baseUrl + qrCodeUrl
}
//
const openPublicScreen = (classcourse) => {
const resource = toRaw(myClassActive.value)
sessionStore.set('curr.resource', resource) //
sessionStore.set('curr.classcourse', classcourse) //
createWindow('open-win', {
url: '/pptist', //
close: () => {
sessionStore.set('curr.resource', null) //
sessionStore.set('curr.classcourse', null) //
}
})
}
//
// ================== =======================

View File

@ -341,6 +341,8 @@ export default {
cookieData: { ...configObj.data }
})
return
} else if(items.fileFlag === 'aptist') { // aptist PPT-List
return this.$emit('change', 'click', items)
}
if (!items||!items.fileSuffix) return;
getPrepareById(items.id).then((item) => {

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

@ -175,8 +175,9 @@ import { updateClasscourse } from '@/api/teaching/classcourse'
import { getClassInfo, getSelfReserv, endClass } from '@/api/classManage'
import { useGetHomework } from '@/hooks/useGetHomework'
import { editListItem } from '@/hooks/useClassTask'
import { addEntpcoursefileReturnId } from '@/api/education/entpcoursefile'
import { addEntpcoursefileReturnId, getEntpcoursefile } from '@/api/education/entpcoursefile'
import ClassReserv from '@/views/classManage/classReserv.vue'
import TreeLog from '@/views/prepare/components/treeLog.vue'
import classStart from './container/class-start.vue' //
import MsgEnum from '@/plugins/imChat/msgEnum' // im
import Chat from '@/utils/chat' // im
@ -239,7 +240,9 @@ export default {
activeClass: null,
pptDialog: false,
//
treelogRef:null
treelogRef:null,
// Entpcourse
entp: null
}
},
computed: {
@ -249,10 +252,12 @@ export default {
)
},
currentKJFileList() {
return this.currentFileList.filter((item) => item.fileFlag === 'apt' || item.fileFlag === '课件')
// return this.currentFileList.filter((item) => item.fileFlag === 'apt' || item.fileFlag === '')
return this.currentFileList.filter((item) => ['apt','aptist','课件'].includes(item.fileFlag))
},
currentSCFileList() {
return this.currentFileList.filter((item) => item.fileFlag !== 'apt' && item.fileFlag !== '课件')
// return this.currentFileList.filter((item) => item.fileFlag !== 'apt' && item.fileFlag !== '')
return this.currentFileList.filter((item) => !['apt','aptist','课件'].includes(item.fileFlag))
}
},
@ -305,8 +310,8 @@ export default {
//
startClass(item, classObj) {
// ()
const id = sessionStore.has('activeClass.id') ? sessionStore.get('activeClass.id') : null
if (id && id == item.id) return ElMessage.warning('当前正在上课,请勿重复操作')
// const id = sessionStore.has('activeClass.id') ? sessionStore.get('activeClass.id') : null
// if (id && id == item.id) return ElMessage.warning('')
// -store
sessionStore.set('activeClass', item)
this.activeClass = item
@ -316,6 +321,9 @@ export default {
if(item.fileFlag === 'apt') {
this.$refs.calssRef.open(item.fileId, classObj)
}
if(item.fileFlag === 'aptist') {
this.$refs.calssRef.open(item.fileId, classObj)
}
},
// -apt
async changeClass(type, row, other) {
@ -370,6 +378,28 @@ export default {
}, 1000)
break
}
case 'click': { // --aptist
if (row.fileFlag === 'aptist' && !!row.fileId) {
const res = await getEntpcoursefile(row.fileId)
if (res && res.code === 200) {
sessionStore.set('curr.resource', res.data) //
sessionStore.set('curr.smarttalk', row) // smarttalk
createWindow('open-win', {
url: '/pptist', //
close: () => {
sessionStore.set('curr.resource', null) //
sessionStore.set('curr.smarttalk', null) //
this.asyncAllFile() //
}
})
} else {
ElMessage.warning(res.msg||'文件获取异常!')
}
return
}
ElMessage.warning('该功能暂未开放!')
break
}
default:
break
}
@ -623,6 +653,7 @@ export default {
for (let i = 0; i < this.currentFileList.length; i++) {
let item = this.currentFileList[i]
if (item.fileFlag === 'apt') continue;
if (item.fileFlag === 'aptist') continue;
await asyncLocalFile(item)
}
this.asyncAllFileVisiable = false
@ -651,6 +682,11 @@ export default {
toolStore.curSubjectNode.querySearch = this.uploadData
this.initHomeWork()
await this.asyncAllFile()
//
const params = { evalid: this.currentNode.id, edituserid: this.userStore.userId, pageSize: 1 }
const res = await listEntpcourse(params)
this.entp = res?.rows?.[0] || null
sessionStore.set('curr.entp', this.entp) //
},
//
async initHomeWork() {

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