Compare commits
46 Commits
5b49eef8e8
...
8d03c927b9
Author | SHA1 | Date |
---|---|---|
小杨 | 8d03c927b9 | |
小杨 | edabc0336f | |
zhengdegang | c22b360e0f | |
zdg | 3b851887ca | |
zdg | e6333e49f0 | |
CYS | cee81102da | |
cys | 59bc2fba86 | |
zouyf | 051aa6bd56 | |
“zouyf” | 330a9acddb | |
“zouyf” | 90dff4d5c7 | |
baigl | e64311e8a4 | |
白了个白 | 7c304650ad | |
lyc | 1a267e5696 | |
lyc | cd6ca2e4e7 | |
“zouyf” | 99c90bf9a3 | |
白了个白 | 40053c6c79 | |
zhengdegang | f9b80dd455 | |
zdg | 482cab5cd3 | |
“zouyf” | c3620a11ee | |
zdg | 0d68e6efa7 | |
zdg | d432d97507 | |
zdg | a9443035c2 | |
lyc | 7b4e54cb16 | |
白了个白 | eb428eaddf | |
“zouyf” | e30e3fd42d | |
“zouyf” | 81f704b2a5 | |
lyc | 58e80d590e | |
lyc | 8a1c5d7186 | |
zdg | 7c3f3ea8fb | |
zdg | f2b1a097bb | |
朱浩 | a6e6f450c5 | |
白了个白 | 944dff1b00 | |
zdg | 64e7c27a44 | |
zdg | c555de5fe5 | |
zhengdegang | a4f276908e | |
zdg | a10d3b078b | |
zdg | c1bba6e443 | |
lyc | 231a7b6dce | |
lyc | 310d9d3a2b | |
lyc | fae380f9c6 | |
lyc | a68fb21203 | |
朱浩 | 2c6ccd893b | |
朱浩 | 540e5f0179 | |
朱浩 | 8a8f90ce16 | |
朱浩 | 050f87a84a | |
yangws | c338f34127 |
|
@ -130,7 +130,6 @@
|
|||
"typescript": "~5.3.0",
|
||||
"vite": "^5.3.1",
|
||||
"vite-plugin-windicss": "^1.9.3",
|
||||
"vue": "^3.4.30",
|
||||
"vue-tsc": "^1.8.25",
|
||||
"windicss": "^3.5.6"
|
||||
}
|
||||
|
|
|
@ -23,7 +23,9 @@ const defaultData = {
|
|||
curNode: null, // 当前选中的节点
|
||||
defaultExpandedKeys: [], //展开的节点
|
||||
subjectTree: [] // "树结构" 章节
|
||||
}
|
||||
},
|
||||
env: {}, // 不走同步 Pinia - 变量
|
||||
curr: {} // 不走同步 Pinia - 当前信息
|
||||
},
|
||||
local: { // 本地(永久localStorage)
|
||||
},
|
||||
|
|
|
@ -1,15 +1,20 @@
|
|||
<template>
|
||||
<template v-if="loading">
|
||||
加载中...
|
||||
</template>
|
||||
<template v-else>
|
||||
<Screen v-if="screening" />
|
||||
<Editor v-else-if="_isPC" />
|
||||
<Mobile v-else />
|
||||
</template>
|
||||
</template>
|
||||
|
||||
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { onMounted } from 'vue'
|
||||
import { ref, onMounted, watch, onBeforeMount } from 'vue'
|
||||
import { storeToRefs } from 'pinia'
|
||||
import { useScreenStore, useMainStore, useSnapshotStore } from './store'
|
||||
import { useScreenStore, useMainStore, useSnapshotStore, useSlidesStore } from './store'
|
||||
import { LOCALSTORAGE_KEY_DISCARDED_DB } from './configs/storage'
|
||||
import { deleteDiscardedDB } from './utils/database'
|
||||
import { isPC } from './utils/common'
|
||||
|
@ -17,10 +22,19 @@ import Editor from './views/Editor/index.vue'
|
|||
import Screen from './views/Screen/index.vue'
|
||||
import Mobile from './views/Mobile/index.vue'
|
||||
|
||||
// zdg
|
||||
import msgUtils from '@/plugins/modal' // 消息工具
|
||||
import * as API_entpcoursefile from '@/api/education/entpcoursefile' // 相关api
|
||||
import { PPTApi } from './api'
|
||||
import { sessionStore } from '@/utils/store' // electron-store 状态管理
|
||||
import './api/watcher' // 监听
|
||||
|
||||
const loading = ref(true)
|
||||
const _isPC = isPC()
|
||||
|
||||
const mainStore = useMainStore()
|
||||
const snapshotStore = useSnapshotStore()
|
||||
const slidesStore = useSlidesStore()
|
||||
const { databaseId } = storeToRefs(mainStore)
|
||||
const { screening } = storeToRefs(useScreenStore())
|
||||
|
||||
|
@ -29,9 +43,11 @@ if (import.meta.env.MODE !== 'development') {
|
|||
}
|
||||
|
||||
onMounted(async () => {
|
||||
await initLoad()
|
||||
await deleteDiscardedDB()
|
||||
snapshotStore.initSnapshotDatabase()
|
||||
mainStore.setAvailableFonts()
|
||||
loading.value = false // 加载完毕
|
||||
})
|
||||
|
||||
// 应用注销时向 localStorage 中记录下本次 indexedDB 的数据库ID,用于之后清除数据库
|
||||
|
@ -44,6 +60,30 @@ 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 资源数据
|
||||
const resource = sessionStore.get('curr.resource')
|
||||
if (!!resource) { // 有ppt 资源数据缓存
|
||||
slidesStore.setTitle(resource.title)
|
||||
if (!!resource.parentContent) { // 有全局配置项
|
||||
const opt = JSON.parse(resource.parentContent)
|
||||
!!(opt.width??null) && slidesStore.setViewportSize(opt.width) // 有宽度配置项
|
||||
!!(opt.ratio??null) && slidesStore.setViewportRatio(opt.ratio)// 有比例配置项
|
||||
}
|
||||
return PPTApi.getSlideList(resource.id)
|
||||
}
|
||||
return Promise.resolve()
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
|
|
|
@ -0,0 +1,188 @@
|
|||
/**
|
||||
* @description ppt幻灯片相关的请求接口-集中处理
|
||||
* @author zdg
|
||||
* @date 2024-11-26
|
||||
*/
|
||||
import { toRaw } from 'vue'
|
||||
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' // 外部-用户信息
|
||||
|
||||
const slidesStore = useStore.useSlidesStore()
|
||||
const userStore = useUserStore()
|
||||
|
||||
/** 工具类 */
|
||||
export class Utils {
|
||||
static mxData: any = {
|
||||
throTime: 0, // 节流时间
|
||||
}
|
||||
/**
|
||||
* 节流-防抖
|
||||
* @param {*} func
|
||||
* @param {*} delay
|
||||
* @param {*} type 1-节流 2-防抖
|
||||
* mxThrottle(() => {xxxx}, 200)
|
||||
*/
|
||||
static mxThrottle(func, delay = 200, type = 1) {
|
||||
if (type == 1) { // 节流(第一次触发后面的事件不触发)
|
||||
const cur = Date.now()
|
||||
const lastExecuted = this.mxData.throTime
|
||||
if (cur - lastExecuted > delay) {
|
||||
this.mxData.throTime = cur
|
||||
return func.apply(this)
|
||||
}
|
||||
} else { // 防抖(只触发最后一次事件)
|
||||
const timer = this.mxData.throTime
|
||||
!!timer && clearTimeout(timer)
|
||||
this.mxData.throTime = setTimeout(() => {
|
||||
return func.apply(this)
|
||||
}, delay)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** ppt相关后端接口处理 */
|
||||
export class PPTApi {
|
||||
// 变量
|
||||
static isUpdate = true // 是否更新数据
|
||||
|
||||
// 获取所有幻灯片列表
|
||||
static getSlideList(parentid: (Number | String)): Promise<Boolean> {
|
||||
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) {
|
||||
const json = JSON.parse(o.datacontent)
|
||||
!!json && (json.id = o.id)
|
||||
return json
|
||||
}
|
||||
// 如果没有数据,默认空白页
|
||||
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)
|
||||
})
|
||||
}
|
||||
|
||||
// 新增幻灯片
|
||||
static addSlide(data: object): Promise<Boolean> {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
const enpt = sessionStore.get('curr.entp')||{}
|
||||
const resource = sessionStore.get('curr.resource')||{}
|
||||
const {id, ...content} = data
|
||||
const params = {
|
||||
parentid: resource.id,
|
||||
entpid: userStore.user.deptId,
|
||||
entpcourseid: enpt.id,
|
||||
ppttype: 'file',
|
||||
title: '',
|
||||
fileurl: '',
|
||||
filetype: 'slide',
|
||||
datacontent: JSON.stringify(content),
|
||||
filekey: '',
|
||||
filetag: '',
|
||||
fileidx: 0,
|
||||
dflag: 0,
|
||||
status: '',
|
||||
edituserid: userStore.id
|
||||
}
|
||||
const rid = await API_entpcoursefile.addEntpcoursefileReturnId(params)
|
||||
if (!!rid) {
|
||||
data.id = rid
|
||||
slidesStore.updateSlide(data)
|
||||
// msgUtils.msgSuccess('新增成功')
|
||||
this.isUpdate = false // 新增后会触发监听,不再更新数据
|
||||
resolve(true)
|
||||
} else msgUtils.msgError('新增失败');resolve(false)
|
||||
})
|
||||
}
|
||||
/**
|
||||
* @description 监听幻灯片数据变化,更新数据
|
||||
* @param newVal
|
||||
* @param oldVal
|
||||
* @returns
|
||||
*/
|
||||
static async updateSlides(newVal: object, oldVal: object) {
|
||||
const newData = toRaw(newVal)
|
||||
const oldData = toRaw(oldVal)
|
||||
// console.log('监听幻灯片数据变化', newData, oldData)
|
||||
if (!(newData&&newData.length)) return // 新数据为空,不需要更新数据
|
||||
else if (!oldData.length) return // 初始加载,旧数据空不需要更新数据
|
||||
|
||||
const currentSlide = toRaw(slidesStore.currentSlide)
|
||||
const isAdd = !/^\d+$/.test(currentSlide.id) // 是否新增
|
||||
if (isAdd) { // 新增的幻灯片(id 为非数字,说明是新增的幻灯片)
|
||||
const bool = await this.addSlide(currentSlide)
|
||||
bool && this.batchUpdateSlides(newData, true) // 批量更新-排序
|
||||
} else { // 防抖-更新
|
||||
if (!this.isUpdate) return this.isUpdate = true // 下次更新数据
|
||||
const params = {
|
||||
id: currentSlide.id,
|
||||
datacontent: JSON.stringify(currentSlide),
|
||||
}
|
||||
Utils.mxThrottle(() => {this.updateSlide(params)}, 1000, 2)
|
||||
}
|
||||
}
|
||||
// 更新幻灯片
|
||||
static updateSlide(data: object): Promise<Boolean> {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
const res: Result = await API_entpcoursefile.updateEntpcoursefileNew(data)
|
||||
if (res.code === 200) {
|
||||
resolve(true)
|
||||
} else msgUtils.msgError(res.msg || '更新失败');resolve(false)
|
||||
})
|
||||
}
|
||||
/**
|
||||
* @description 批量更新 数据|排序
|
||||
* @param list 数据
|
||||
* @param sort 是否只更新排序
|
||||
* @returns
|
||||
*/
|
||||
static batchUpdateSlides(list: Slide[], sort: boolean): Promise<Boolean> {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
const data: object[] = list.map((o, index) => ({
|
||||
id: o.id,
|
||||
fileidx: index,
|
||||
datacontent: sort?null:JSON.stringify(o),
|
||||
}))
|
||||
const res: Result = await API_entpcoursefile.batchUpdateNew(data)
|
||||
if (res.code === 200) {
|
||||
resolve(true)
|
||||
} else msgUtils.msgError(res.msg || '更新失败');resolve(false)
|
||||
})
|
||||
}
|
||||
// 删除幻灯片
|
||||
static delSlide(id: string): Promise<Boolean> {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
const res: Result = await API_entpcoursefile.delEntpcoursefile(id)
|
||||
if (res.code === 200) {
|
||||
resolve(true)
|
||||
} 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
|
|
@ -0,0 +1,18 @@
|
|||
/**
|
||||
* @description api 无store循环引用
|
||||
* @author zdg
|
||||
*/
|
||||
import msgUtils from '@/plugins/modal' // 消息工具
|
||||
import * as API_entpcoursefile from '@/api/education/entpcoursefile' // 相关api
|
||||
|
||||
export default class {
|
||||
// 删除幻灯片
|
||||
static delSlide(id: string): Promise<Boolean> {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
const res: Result = await API_entpcoursefile.delEntpcoursefile(id)
|
||||
if (res.code === 200) {
|
||||
resolve(true)
|
||||
} else msgUtils.msgError(res.msg || '删除失败');resolve(false)
|
||||
})
|
||||
}
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
/**
|
||||
* @description 公共监听器
|
||||
*/
|
||||
|
||||
import { watch } from 'vue'
|
||||
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') // apt 资源
|
||||
const smarttalk = sessionStore.get('curr.smarttalk') // 备课资源
|
||||
/**
|
||||
* @description 监听器
|
||||
*/
|
||||
|
||||
// 监听幻灯片内容变化
|
||||
watch(() => slidesStore.slides, (newVal, oldVal) => {
|
||||
PPTApi.updateSlides(newVal, oldVal) // 更新幻灯片内容
|
||||
},{ deep: true })
|
||||
|
||||
// 监听标题变化
|
||||
watch(() => slidesStore.title, (newVal, oldVal) => {
|
||||
if (oldVal == '未命名演示文稿') return // 初始加载,不需要更新数据
|
||||
updatePPT({title: newVal})
|
||||
})
|
||||
|
||||
const updatePPT = async (data) => {
|
||||
if (!resource) return
|
||||
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)
|
||||
}
|
||||
}
|
|
@ -28,12 +28,12 @@ const convertFontSizePtToPx = (html: string, ratio: number) => {
|
|||
})
|
||||
}
|
||||
|
||||
export default () => {
|
||||
const slidesStore = useSlidesStore()
|
||||
const { theme } = storeToRefs(useSlidesStore())
|
||||
|
||||
const { addSlidesFromData } = useAddSlidesOrElements()
|
||||
const { isEmptySlide } = useSlideHandler()
|
||||
export default () => {
|
||||
|
||||
const exporting = ref(false)
|
||||
|
||||
|
@ -486,9 +486,413 @@ export default () => {
|
|||
reader.readAsArrayBuffer(file)
|
||||
}
|
||||
|
||||
|
||||
|
||||
return {
|
||||
importSpecificFile,
|
||||
importPPTXFile,
|
||||
PPTXFileToJson,
|
||||
exporting,
|
||||
}
|
||||
}
|
||||
|
||||
// 导入PPTX 返回 json
|
||||
export const PPTXFileToJson = (data: File|ArrayBuffer) => {
|
||||
return new Promise(async(resolve, reject) => {
|
||||
if (!data) return
|
||||
let fileArrayBuffer: ArrayBuffer = null
|
||||
let resData = {} // 返回的数据
|
||||
|
||||
const shapeList: ShapePoolItem[] = []
|
||||
for (const item of SHAPE_LIST) {
|
||||
shapeList.push(...item.children)
|
||||
}
|
||||
// 获取文件的 ArrayBuffer
|
||||
const getArrayBuffer = () => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const reader = new FileReader()
|
||||
reader.onload = () => {
|
||||
resolve(reader.result)
|
||||
}
|
||||
reader.onerror = reject
|
||||
reader.readAsArrayBuffer(data)
|
||||
})
|
||||
}
|
||||
if (data instanceof File) { // 文件
|
||||
fileArrayBuffer = await getArrayBuffer()
|
||||
} else if (data instanceof ArrayBuffer) { // ArrayBuffer
|
||||
fileArrayBuffer = data
|
||||
} else {
|
||||
throw new Error('Invalid data type')
|
||||
}
|
||||
|
||||
// 开始解析
|
||||
const json = await parse(fileArrayBuffer)
|
||||
|
||||
const ratio = 96 / 72
|
||||
const width = json.size.width
|
||||
|
||||
resData.def = json // 保留原始数据
|
||||
resData.width = width * ratio
|
||||
resData.ratio = slidesStore.viewportRatio
|
||||
|
||||
const slides: Slide[] = []
|
||||
for (const item of json.slides) {
|
||||
const { type, value } = item.fill
|
||||
let background: SlideBackground
|
||||
if (type === 'image') {
|
||||
background = {
|
||||
type: 'image',
|
||||
image: {
|
||||
src: value.picBase64,
|
||||
size: 'cover',
|
||||
},
|
||||
}
|
||||
}
|
||||
else if (type === 'gradient') {
|
||||
background = {
|
||||
type: 'gradient',
|
||||
gradient: {
|
||||
type: 'linear',
|
||||
colors: value.colors.map(item => ({
|
||||
...item,
|
||||
pos: parseInt(item.pos),
|
||||
})),
|
||||
rotate: value.rot,
|
||||
},
|
||||
}
|
||||
}
|
||||
else {
|
||||
background = {
|
||||
type: 'solid',
|
||||
color: value,
|
||||
}
|
||||
}
|
||||
|
||||
const slide: Slide = {
|
||||
id: nanoid(10),
|
||||
elements: [],
|
||||
background,
|
||||
}
|
||||
|
||||
const parseElements = (elements: Element[]) => {
|
||||
for (const el of elements) {
|
||||
const originWidth = el.width || 1
|
||||
const originHeight = el.height || 1
|
||||
const originLeft = el.left
|
||||
const originTop = el.top
|
||||
|
||||
el.width = el.width * ratio
|
||||
el.height = el.height * ratio
|
||||
el.left = el.left * ratio
|
||||
el.top = el.top * ratio
|
||||
|
||||
if (el.type === 'text') {
|
||||
const textEl: PPTTextElement = {
|
||||
type: 'text',
|
||||
id: nanoid(10),
|
||||
width: el.width,
|
||||
height: el.height,
|
||||
left: el.left,
|
||||
top: el.top,
|
||||
rotate: el.rotate,
|
||||
defaultFontName: theme.value.fontName,
|
||||
defaultColor: theme.value.fontColor,
|
||||
content: convertFontSizePtToPx(el.content, ratio),
|
||||
lineHeight: 1,
|
||||
outline: {
|
||||
color: el.borderColor,
|
||||
width: el.borderWidth,
|
||||
style: el.borderType,
|
||||
},
|
||||
fill: el.fillColor,
|
||||
vertical: el.isVertical,
|
||||
}
|
||||
if (el.shadow) {
|
||||
textEl.shadow = {
|
||||
h: el.shadow.h * ratio,
|
||||
v: el.shadow.v * ratio,
|
||||
blur: el.shadow.blur * ratio,
|
||||
color: el.shadow.color,
|
||||
}
|
||||
}
|
||||
slide.elements.push(textEl)
|
||||
}
|
||||
else if (el.type === 'image') {
|
||||
slide.elements.push({
|
||||
type: 'image',
|
||||
id: nanoid(10),
|
||||
src: el.src,
|
||||
width: el.width,
|
||||
height: el.height,
|
||||
left: el.left,
|
||||
top: el.top,
|
||||
fixedRatio: true,
|
||||
rotate: el.rotate,
|
||||
flipH: el.isFlipH,
|
||||
flipV: el.isFlipV,
|
||||
})
|
||||
}
|
||||
else if (el.type === 'audio') {
|
||||
slide.elements.push({
|
||||
type: 'audio',
|
||||
id: nanoid(10),
|
||||
src: el.blob,
|
||||
width: el.width,
|
||||
height: el.height,
|
||||
left: el.left,
|
||||
top: el.top,
|
||||
rotate: 0,
|
||||
fixedRatio: false,
|
||||
color: theme.value.themeColor,
|
||||
loop: false,
|
||||
autoplay: false,
|
||||
})
|
||||
}
|
||||
else if (el.type === 'video') {
|
||||
slide.elements.push({
|
||||
type: 'video',
|
||||
id: nanoid(10),
|
||||
src: (el.blob || el.src)!,
|
||||
width: el.width,
|
||||
height: el.height,
|
||||
left: el.left,
|
||||
top: el.top,
|
||||
rotate: 0,
|
||||
autoplay: false,
|
||||
})
|
||||
}
|
||||
else if (el.type === 'shape') {
|
||||
if (el.shapType === 'line' || /Connector/.test(el.shapType)) {
|
||||
const lineElement = parseLineElement(el)
|
||||
slide.elements.push(lineElement)
|
||||
}
|
||||
else {
|
||||
const shape = shapeList.find(item => item.pptxShapeType === el.shapType)
|
||||
|
||||
const vAlignMap: { [key: string]: ShapeTextAlign } = {
|
||||
'mid': 'middle',
|
||||
'down': 'bottom',
|
||||
'up': 'top',
|
||||
}
|
||||
|
||||
const element: PPTShapeElement = {
|
||||
type: 'shape',
|
||||
id: nanoid(10),
|
||||
width: el.width,
|
||||
height: el.height,
|
||||
left: el.left,
|
||||
top: el.top,
|
||||
viewBox: [200, 200],
|
||||
path: 'M 0 0 L 200 0 L 200 200 L 0 200 Z',
|
||||
fill: el.fillColor || 'none',
|
||||
fixedRatio: false,
|
||||
rotate: el.rotate,
|
||||
outline: {
|
||||
color: el.borderColor,
|
||||
width: el.borderWidth,
|
||||
style: el.borderType,
|
||||
},
|
||||
text: {
|
||||
content: convertFontSizePtToPx(el.content, ratio),
|
||||
defaultFontName: theme.value.fontName,
|
||||
defaultColor: theme.value.fontColor,
|
||||
align: vAlignMap[el.vAlign] || 'middle',
|
||||
},
|
||||
flipH: el.isFlipH,
|
||||
flipV: el.isFlipV,
|
||||
}
|
||||
if (el.shadow) {
|
||||
element.shadow = {
|
||||
h: el.shadow.h * ratio,
|
||||
v: el.shadow.v * ratio,
|
||||
blur: el.shadow.blur * ratio,
|
||||
color: el.shadow.color,
|
||||
}
|
||||
}
|
||||
|
||||
if (shape) {
|
||||
element.path = shape.path
|
||||
element.viewBox = shape.viewBox
|
||||
|
||||
if (shape.pathFormula) {
|
||||
element.pathFormula = shape.pathFormula
|
||||
element.viewBox = [el.width, el.height]
|
||||
|
||||
const pathFormula = SHAPE_PATH_FORMULAS[shape.pathFormula]
|
||||
if ('editable' in pathFormula && pathFormula.editable) {
|
||||
element.path = pathFormula.formula(el.width, el.height, pathFormula.defaultValue)
|
||||
element.keypoints = pathFormula.defaultValue
|
||||
}
|
||||
else element.path = pathFormula.formula(el.width, el.height)
|
||||
}
|
||||
}
|
||||
if (el.shapType === 'custom') {
|
||||
if (el.path!.indexOf('NaN') !== -1) element.path = ''
|
||||
else {
|
||||
element.special = true
|
||||
element.path = el.path!
|
||||
|
||||
const { maxX, maxY } = getSvgPathRange(element.path)
|
||||
element.viewBox = [maxX || originWidth, maxY || originHeight]
|
||||
}
|
||||
}
|
||||
|
||||
if (element.path) slide.elements.push(element)
|
||||
}
|
||||
}
|
||||
else if (el.type === 'table') {
|
||||
const row = el.data.length
|
||||
const col = el.data[0].length
|
||||
|
||||
const style: TableCellStyle = {
|
||||
fontname: theme.value.fontName,
|
||||
color: theme.value.fontColor,
|
||||
}
|
||||
const data: TableCell[][] = []
|
||||
for (let i = 0; i < row; i++) {
|
||||
const rowCells: TableCell[] = []
|
||||
for (let j = 0; j < col; j++) {
|
||||
const cellData = el.data[i][j]
|
||||
|
||||
let textDiv: HTMLDivElement | null = document.createElement('div')
|
||||
textDiv.innerHTML = cellData.text
|
||||
const p = textDiv.querySelector('p')
|
||||
const align = p?.style.textAlign || 'left'
|
||||
|
||||
const span = textDiv.querySelector('span')
|
||||
const fontsize = span?.style.fontSize ? (parseInt(span?.style.fontSize) * ratio).toFixed(1) + 'px' : ''
|
||||
const fontname = span?.style.fontFamily || ''
|
||||
const color = span?.style.color || cellData.fontColor
|
||||
|
||||
rowCells.push({
|
||||
id: nanoid(10),
|
||||
colspan: cellData.colSpan || 1,
|
||||
rowspan: cellData.rowSpan || 1,
|
||||
text: textDiv.innerText,
|
||||
style: {
|
||||
...style,
|
||||
align: ['left', 'right', 'center'].includes(align) ? (align as 'left' | 'right' | 'center') : 'left',
|
||||
fontsize,
|
||||
fontname,
|
||||
color,
|
||||
bold: cellData.fontBold,
|
||||
backcolor: cellData.fillColor,
|
||||
},
|
||||
})
|
||||
textDiv = null
|
||||
}
|
||||
data.push(rowCells)
|
||||
}
|
||||
|
||||
const colWidths: number[] = new Array(col).fill(1 / col)
|
||||
|
||||
slide.elements.push({
|
||||
type: 'table',
|
||||
id: nanoid(10),
|
||||
width: el.width,
|
||||
height: el.height,
|
||||
left: el.left,
|
||||
top: el.top,
|
||||
colWidths,
|
||||
rotate: 0,
|
||||
data,
|
||||
outline: {
|
||||
width: el.borderWidth || 2,
|
||||
style: el.borderType,
|
||||
color: el.borderColor || '#eeece1',
|
||||
},
|
||||
cellMinHeight: 36,
|
||||
})
|
||||
}
|
||||
else if (el.type === 'chart') {
|
||||
let labels: string[]
|
||||
let legends: string[]
|
||||
let series: number[][]
|
||||
|
||||
if (el.chartType === 'scatterChart' || el.chartType === 'bubbleChart') {
|
||||
labels = el.data[0].map((item, index) => `坐标${index + 1}`)
|
||||
legends = ['X', 'Y']
|
||||
series = el.data
|
||||
}
|
||||
else {
|
||||
const data = el.data as ChartItem[]
|
||||
labels = Object.values(data[0].xlabels)
|
||||
legends = data.map(item => item.key)
|
||||
series = data.map(item => item.values.map(v => v.y))
|
||||
}
|
||||
|
||||
const options: ChartOptions = {}
|
||||
|
||||
let chartType: ChartType = 'bar'
|
||||
|
||||
switch (el.chartType) {
|
||||
case 'barChart':
|
||||
case 'bar3DChart':
|
||||
chartType = 'bar'
|
||||
if (el.barDir === 'bar') chartType = 'column'
|
||||
if (el.grouping === 'stacked' || el.grouping === 'percentStacked') options.stack = true
|
||||
break
|
||||
case 'lineChart':
|
||||
case 'line3DChart':
|
||||
if (el.grouping === 'stacked' || el.grouping === 'percentStacked') options.stack = true
|
||||
chartType = 'line'
|
||||
break
|
||||
case 'areaChart':
|
||||
case 'area3DChart':
|
||||
if (el.grouping === 'stacked' || el.grouping === 'percentStacked') options.stack = true
|
||||
chartType = 'area'
|
||||
break
|
||||
case 'scatterChart':
|
||||
case 'bubbleChart':
|
||||
chartType = 'scatter'
|
||||
break
|
||||
case 'pieChart':
|
||||
case 'pie3DChart':
|
||||
chartType = 'pie'
|
||||
break
|
||||
case 'radarChart':
|
||||
chartType = 'radar'
|
||||
break
|
||||
case 'doughnutChart':
|
||||
chartType = 'ring'
|
||||
break
|
||||
default:
|
||||
}
|
||||
|
||||
slide.elements.push({
|
||||
type: 'chart',
|
||||
id: nanoid(10),
|
||||
chartType: chartType,
|
||||
width: el.width,
|
||||
height: el.height,
|
||||
left: el.left,
|
||||
top: el.top,
|
||||
rotate: 0,
|
||||
themeColors: [theme.value.themeColor],
|
||||
textColor: theme.value.fontColor,
|
||||
data: {
|
||||
labels,
|
||||
legends,
|
||||
series,
|
||||
},
|
||||
options,
|
||||
})
|
||||
}
|
||||
else if (el.type === 'group' || el.type === 'diagram') {
|
||||
const elements = el.elements.map(_el => ({
|
||||
..._el,
|
||||
left: _el.left + originLeft,
|
||||
top: _el.top + originTop,
|
||||
}))
|
||||
parseElements(elements)
|
||||
}
|
||||
}
|
||||
}
|
||||
parseElements(item.elements)
|
||||
slides.push(slide)
|
||||
}
|
||||
resData.slides = slides
|
||||
resolve(resData)
|
||||
})
|
||||
}
|
|
@ -1,186 +1,187 @@
|
|||
import type { Slide } from '../types/slides'
|
||||
|
||||
export const slides: Slide[] = [
|
||||
{
|
||||
id: 'test-slide-1',
|
||||
elements: [
|
||||
{
|
||||
type: 'shape',
|
||||
id: '4cbRxp',
|
||||
left: 0,
|
||||
top: 200,
|
||||
width: 546,
|
||||
height: 362.5,
|
||||
viewBox: [200, 200],
|
||||
path: 'M 0 0 L 0 200 L 200 200 Z',
|
||||
fill: '#5b9bd5',
|
||||
fixedRatio: false,
|
||||
opacity: 0.7,
|
||||
rotate: 0
|
||||
},
|
||||
{
|
||||
type: 'shape',
|
||||
id: 'ookHrf',
|
||||
left: 0,
|
||||
top: 0,
|
||||
width: 300,
|
||||
height: 320,
|
||||
viewBox: [200, 200],
|
||||
path: 'M 0 0 L 0 200 L 200 200 Z',
|
||||
fill: '#5b9bd5',
|
||||
fixedRatio: false,
|
||||
flipV: true,
|
||||
rotate: 0
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
id: 'idn7Mx',
|
||||
left: 355,
|
||||
top: 65.25,
|
||||
width: 450,
|
||||
height: 188,
|
||||
lineHeight: 1.2,
|
||||
content: '<p><strong><span style=\"font-size: 112px;\">PPTist</span></strong></p>',
|
||||
rotate: 0,
|
||||
defaultFontName: 'Microsoft Yahei',
|
||||
defaultColor: '#333'
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
id: '7stmVP',
|
||||
left: 355,
|
||||
top: 253.25,
|
||||
width: 585,
|
||||
height: 56,
|
||||
content: '<p><span style=\"font-size: 24px;\">基于 Vue 3.x + TypeScript 的在线演示文稿应用</span></p>',
|
||||
rotate: 0,
|
||||
defaultFontName: 'Microsoft Yahei',
|
||||
defaultColor: '#333'
|
||||
},
|
||||
{
|
||||
type: 'line',
|
||||
id: 'FnpZs4',
|
||||
left: 361,
|
||||
top: 238,
|
||||
start: [0, 0],
|
||||
end: [549, 0],
|
||||
points: ['', ''],
|
||||
color: '#5b9bd5',
|
||||
style: 'solid',
|
||||
width: 2,
|
||||
},
|
||||
],
|
||||
background: {
|
||||
type: 'solid',
|
||||
color: '#ffffff',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'test-slide-2',
|
||||
elements: [
|
||||
{
|
||||
type: 'text',
|
||||
id: 'ptNnUJ',
|
||||
left: 145,
|
||||
top: 148,
|
||||
width: 711,
|
||||
height: 77,
|
||||
lineHeight: 1.2,
|
||||
content: '<p style=\"text-align: center;\"><strong><span style=\"font-size: 48px;\">在此处添加标题</span></strong></p>',
|
||||
rotate: 0,
|
||||
defaultFontName: 'Microsoft Yahei',
|
||||
defaultColor: '#333',
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
id: 'mRHvQN',
|
||||
left: 207.50000000000003,
|
||||
top: 249.84259259259264,
|
||||
width: 585,
|
||||
height: 56,
|
||||
content: '<p style=\"text-align: center;\"><span style=\"font-size: 24px;\">在此处添加副标题</span></p>',
|
||||
rotate: 0,
|
||||
defaultFontName: 'Microsoft Yahei',
|
||||
defaultColor: '#333',
|
||||
},
|
||||
{
|
||||
type: 'line',
|
||||
id: '7CQDwc',
|
||||
left: 323.09259259259267,
|
||||
top: 238.33333333333334,
|
||||
start: [0, 0],
|
||||
end: [354.8148148148148, 0],
|
||||
points: ['', ''],
|
||||
color: '#5b9bd5',
|
||||
style: 'solid',
|
||||
width: 4
|
||||
},
|
||||
{
|
||||
type: 'shape',
|
||||
id: '09wqWw',
|
||||
left: -27.648148148148138,
|
||||
top: 432.73148148148147,
|
||||
width: 1056.2962962962963,
|
||||
height: 162.96296296296296,
|
||||
viewBox: [200, 200],
|
||||
path: 'M 0 20 C 40 -40 60 60 100 20 C 140 -40 160 60 200 20 L 200 180 C 140 240 160 140 100 180 C 40 240 60 140 0 180 L 0 20 Z',
|
||||
fill: '#5b9bd5',
|
||||
fixedRatio: false,
|
||||
rotate: 0
|
||||
}
|
||||
],
|
||||
background: {
|
||||
type: 'solid',
|
||||
color: '#fff',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'test-slide-3',
|
||||
elements: [
|
||||
{
|
||||
type: 'shape',
|
||||
id: 'vSheCJ',
|
||||
left: 183.5185185185185,
|
||||
top: 175.5092592592593,
|
||||
width: 605.1851851851851,
|
||||
height: 185.18518518518516,
|
||||
viewBox: [200, 200],
|
||||
path: 'M 0 0 L 200 0 L 200 200 L 0 200 Z',
|
||||
fill: '#5b9bd5',
|
||||
fixedRatio: false,
|
||||
rotate: 0
|
||||
},
|
||||
{
|
||||
type: 'shape',
|
||||
id: 'Mpwv7x',
|
||||
left: 211.29629629629628,
|
||||
top: 201.80555555555557,
|
||||
width: 605.1851851851851,
|
||||
height: 185.18518518518516,
|
||||
viewBox: [200, 200],
|
||||
path: 'M 0 0 L 200 0 L 200 200 L 0 200 Z',
|
||||
fill: '#5b9bd5',
|
||||
fixedRatio: false,
|
||||
rotate: 0,
|
||||
opacity: 0.7
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
id: 'WQOTAp',
|
||||
left: 304.9074074074074,
|
||||
top: 198.10185185185182,
|
||||
width: 417.9629629629629,
|
||||
height: 140,
|
||||
content: '<p style=\"text-align: center;\"><strong><span style=\"font-size: 80px;\"><span style=\"color: rgb(255, 255, 255);\">感谢观看</span></span></strong></p>',
|
||||
rotate: 0,
|
||||
defaultFontName: 'Microsoft Yahei',
|
||||
defaultColor: '#333',
|
||||
wordSpace: 5
|
||||
}
|
||||
],
|
||||
background: {
|
||||
type: 'solid',
|
||||
color: '#fff',
|
||||
},
|
||||
},
|
||||
// {
|
||||
// id: 'test-slide-1',
|
||||
// elements: [
|
||||
// {
|
||||
// type: 'shape',
|
||||
// id: '4cbRxp',
|
||||
// left: 0,
|
||||
// top: 200,
|
||||
// width: 546,
|
||||
// height: 362.5,
|
||||
// viewBox: [200, 200],
|
||||
// path: 'M 0 0 L 0 200 L 200 200 Z',
|
||||
// fill: '#5b9bd5',
|
||||
// fixedRatio: false,
|
||||
// opacity: 0.7,
|
||||
// rotate: 0
|
||||
// },
|
||||
// {
|
||||
// type: 'shape',
|
||||
// id: 'ookHrf',
|
||||
// left: 0,
|
||||
// top: 0,
|
||||
// width: 300,
|
||||
// height: 320,
|
||||
// viewBox: [200, 200],
|
||||
// path: 'M 0 0 L 0 200 L 200 200 Z',
|
||||
// fill: '#5b9bd5',
|
||||
// fixedRatio: false,
|
||||
// flipV: true,
|
||||
// rotate: 0
|
||||
// },
|
||||
// {
|
||||
// type: 'text',
|
||||
// id: 'idn7Mx',
|
||||
// left: 355,
|
||||
// top: 65.25,
|
||||
// width: 450,
|
||||
// height: 188,
|
||||
// lineHeight: 1.2,
|
||||
// content: '<p><strong><span style=\"font-size: 112px;\">PPTist</span></strong></p>',
|
||||
// rotate: 0,
|
||||
// defaultFontName: 'Microsoft Yahei',
|
||||
// defaultColor: '#333'
|
||||
// },
|
||||
// {
|
||||
// type: 'text',
|
||||
// id: '7stmVP',
|
||||
// left: 355,
|
||||
// top: 253.25,
|
||||
// width: 585,
|
||||
// height: 56,
|
||||
// content: '<p><span style=\"font-size: 24px;\">基于 Vue 3.x + TypeScript 的在线演示文稿应用</span></p>',
|
||||
// rotate: 0,
|
||||
// defaultFontName: 'Microsoft Yahei',
|
||||
// defaultColor: '#333'
|
||||
// },
|
||||
// {
|
||||
// type: 'line',
|
||||
// id: 'FnpZs4',
|
||||
// left: 361,
|
||||
// top: 238,
|
||||
// start: [0, 0],
|
||||
// end: [549, 0],
|
||||
// points: ['', ''],
|
||||
// color: '#5b9bd5',
|
||||
// style: 'solid',
|
||||
// width: 2,
|
||||
// },
|
||||
// ],
|
||||
// background: {
|
||||
// type: 'solid',
|
||||
// color: '#ffffff',
|
||||
// },
|
||||
// },
|
||||
// {
|
||||
// id: 'test-slide-2',
|
||||
// elements: [
|
||||
// {
|
||||
// type: 'text',
|
||||
// id: 'ptNnUJ',
|
||||
// left: 145,
|
||||
// top: 148,
|
||||
// width: 711,
|
||||
// height: 77,
|
||||
// lineHeight: 1.2,
|
||||
// content: '<p style=\"text-align: center;\"><strong><span style=\"font-size: 48px;\">在此处添加标题</span></strong></p>',
|
||||
// rotate: 0,
|
||||
// defaultFontName: 'Microsoft Yahei',
|
||||
// defaultColor: '#333',
|
||||
// },
|
||||
// {
|
||||
// type: 'text',
|
||||
// id: 'mRHvQN',
|
||||
// left: 207.50000000000003,
|
||||
// top: 249.84259259259264,
|
||||
// width: 585,
|
||||
// height: 56,
|
||||
// content: '<p style=\"text-align: center;\"><span style=\"font-size: 24px;\">在此处添加副标题</span></p>',
|
||||
// rotate: 0,
|
||||
// defaultFontName: 'Microsoft Yahei',
|
||||
// defaultColor: '#333',
|
||||
// },
|
||||
// {
|
||||
// type: 'line',
|
||||
// id: '7CQDwc',
|
||||
// left: 323.09259259259267,
|
||||
// top: 238.33333333333334,
|
||||
// start: [0, 0],
|
||||
// end: [354.8148148148148, 0],
|
||||
// points: ['', ''],
|
||||
// color: '#5b9bd5',
|
||||
// style: 'solid',
|
||||
// width: 4
|
||||
// },
|
||||
// {
|
||||
// type: 'shape',
|
||||
// id: '09wqWw',
|
||||
// left: -27.648148148148138,
|
||||
// top: 432.73148148148147,
|
||||
// width: 1056.2962962962963,
|
||||
// height: 162.96296296296296,
|
||||
// viewBox: [200, 200],
|
||||
// path: 'M 0 20 C 40 -40 60 60 100 20 C 140 -40 160 60 200 20 L 200 180 C 140 240 160 140 100 180 C 40 240 60 140 0 180 L 0 20 Z',
|
||||
// fill: '#5b9bd5',
|
||||
// fixedRatio: false,
|
||||
// rotate: 0
|
||||
// }
|
||||
// ],
|
||||
// background: {
|
||||
// type: 'solid',
|
||||
// color: '#fff',
|
||||
// },
|
||||
// },
|
||||
// {
|
||||
// id: 'test-slide-3',
|
||||
// elements: [
|
||||
// {
|
||||
// type: 'shape',
|
||||
// id: 'vSheCJ',
|
||||
// left: 183.5185185185185,
|
||||
// top: 175.5092592592593,
|
||||
// width: 605.1851851851851,
|
||||
// height: 185.18518518518516,
|
||||
// viewBox: [200, 200],
|
||||
// path: 'M 0 0 L 200 0 L 200 200 L 0 200 Z',
|
||||
// fill: '#5b9bd5',
|
||||
// fixedRatio: false,
|
||||
// rotate: 0
|
||||
// },
|
||||
// {
|
||||
// type: 'shape',
|
||||
// id: 'Mpwv7x',
|
||||
// left: 211.29629629629628,
|
||||
// top: 201.80555555555557,
|
||||
// width: 605.1851851851851,
|
||||
// height: 185.18518518518516,
|
||||
// viewBox: [200, 200],
|
||||
// path: 'M 0 0 L 200 0 L 200 200 L 0 200 Z',
|
||||
// fill: '#5b9bd5',
|
||||
// fixedRatio: false,
|
||||
// rotate: 0,
|
||||
// opacity: 0.7
|
||||
// },
|
||||
// {
|
||||
// type: 'text',
|
||||
// id: 'WQOTAp',
|
||||
// left: 304.9074074074074,
|
||||
// top: 198.10185185185182,
|
||||
// width: 417.9629629629629,
|
||||
// height: 140,
|
||||
// content: '<p style=\"text-align: center;\"><strong><span style=\"font-size: 80px;\"><span style=\"color: rgb(255, 255, 255);\">感谢观看</span></span></strong></p>',
|
||||
// rotate: 0,
|
||||
// defaultFontName: 'Microsoft Yahei',
|
||||
// defaultColor: '#333',
|
||||
// wordSpace: 5
|
||||
// }
|
||||
// ],
|
||||
// background: {
|
||||
// type: 'solid',
|
||||
// color: '#fff',
|
||||
// },
|
||||
// },
|
||||
// {"id":"-3hMiOtk8M","elements":[],"background":{"type":"solid","color":"#fff"}},
|
||||
]
|
|
@ -6,6 +6,8 @@ import { slides } from '../mocks/slides'
|
|||
import { theme } from '../mocks/theme'
|
||||
import { layouts } from '../mocks/layout'
|
||||
|
||||
import PPTApi from '../api/store'
|
||||
|
||||
interface RemovePropData {
|
||||
id: string
|
||||
propName: string | string[]
|
||||
|
@ -29,6 +31,8 @@ export interface SlidesState {
|
|||
slideIndex: number
|
||||
viewportSize: number
|
||||
viewportRatio: number
|
||||
workList:Object[],
|
||||
workItem:Object[],
|
||||
}
|
||||
|
||||
export const useSlidesStore = defineStore('slides', {
|
||||
|
@ -39,6 +43,8 @@ export const useSlidesStore = defineStore('slides', {
|
|||
slideIndex: 0, // 当前页面索引
|
||||
viewportSize: 1000, // 可视区域宽度基数
|
||||
viewportRatio: 0.5625, // 可视区域比例,默认16:9
|
||||
workList:[],// 活动的列表
|
||||
workItem:[],// 获取到的所有pptlist
|
||||
}),
|
||||
|
||||
getters: {
|
||||
|
@ -129,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]
|
||||
|
@ -155,14 +168,13 @@ export const useSlidesStore = defineStore('slides', {
|
|||
this.slides = slides
|
||||
},
|
||||
|
||||
deleteSlide(slideId: string | string[]) {
|
||||
async deleteSlide(slideId: string | string[]) {
|
||||
const slidesId = Array.isArray(slideId) ? slideId : [slideId]
|
||||
const slides: Slide[] = JSON.parse(JSON.stringify(this.slides))
|
||||
|
||||
const deleteSlidesIndex = []
|
||||
for (const deletedId of slidesId) {
|
||||
const index = slides.findIndex(item => item.id === deletedId)
|
||||
deleteSlidesIndex.push(index)
|
||||
|
||||
const deletedSlideSection = slides[index].sectionTag
|
||||
if (deletedSlideSection) {
|
||||
|
@ -172,9 +184,14 @@ export const useSlidesStore = defineStore('slides', {
|
|||
slides[index + 1].sectionTag = deletedSlideSection
|
||||
}
|
||||
}
|
||||
|
||||
// 后端先删除,再更新页面数据
|
||||
const isDel = await PPTApi.delSlide(deletedId)
|
||||
if (isDel) {
|
||||
// 后端删除成功,更新页面数据
|
||||
deleteSlidesIndex.push(index)
|
||||
slides.splice(index, 1)
|
||||
}
|
||||
}
|
||||
let newIndex = Math.min(...deleteSlidesIndex)
|
||||
|
||||
const maxIndex = slides.length - 1
|
||||
|
|
|
@ -6,4 +6,5 @@ export const enum ToolbarStates {
|
|||
SLIDE_DESIGN = 'slideDesign',
|
||||
SLIDE_ANIMATION = 'slideAnimation',
|
||||
MULTI_POSITION = 'multiPosition',
|
||||
EL_ACTIVE = 'elActive',
|
||||
}
|
|
@ -57,9 +57,12 @@
|
|||
<div class="menu-item" v-tooltip="'导出'" @click="setDialogForExport('pptx')">
|
||||
<IconDownload class="icon" />
|
||||
</div>
|
||||
<a class="github-link" v-tooltip="'Copyright © 2020-PRESENT pipipi-pikachu'" href="https://github.com/pipipi-pikachu/PPTist" target="_blank">
|
||||
<div class="menu-item" v-tooltip="`${userStore.user.parentDeptName}-${userStore.user.nickName}`">
|
||||
<el-avatar size="small" :src="avatar" />
|
||||
</div>
|
||||
<!-- <a class="github-link" v-tooltip="'Copyright © 2020-PRESENT pipipi-pikachu'" href="https://github.com/pipipi-pikachu/PPTist" target="_blank">
|
||||
<div class="menu-item"><IconGithub class="icon" /></div>
|
||||
</a>
|
||||
</a> -->
|
||||
</div>
|
||||
|
||||
<Drawer
|
||||
|
@ -92,6 +95,9 @@ import Input from '../../../components/Input.vue'
|
|||
import Popover from '../../../components/Popover.vue'
|
||||
import PopoverMenuItem from '../../../components/PopoverMenuItem.vue'
|
||||
|
||||
import useUserStore from '@/store/modules/user' // 外部-用户信息
|
||||
|
||||
const userStore = useUserStore() // 外部-用户信息
|
||||
const mainStore = useMainStore()
|
||||
const slidesStore = useSlidesStore()
|
||||
const { title } = storeToRefs(slidesStore)
|
||||
|
@ -104,7 +110,7 @@ const hotkeyDrawerVisible = ref(false)
|
|||
const editingTitle = ref(false)
|
||||
const titleInputRef = ref<InstanceType<typeof Input>>()
|
||||
const titleValue = ref('')
|
||||
|
||||
const avatar = ref(import.meta.env.VITE_APP_BASE_API+userStore.avatar) // 用户头像
|
||||
const startEditTitle = () => {
|
||||
titleValue.value = title.value
|
||||
editingTitle.value = true
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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>
|
|
@ -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
|
||||
})
|
||||
|
|
|
@ -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
|
||||
})
|
||||
}
|
||||
|
|
@ -51,3 +51,9 @@ export class school {
|
|||
static checkSchool = data => ApiService.publicHttp(`/smarttalk/audit/checkSchool`,data,'post')
|
||||
|
||||
}
|
||||
|
||||
export class Other {
|
||||
static baseUrl = "/common/upload"
|
||||
// 测试
|
||||
static uploadFile = data => ApiService.publicHttp(this.baseUrl, data, 'post', null, 'file')
|
||||
}
|
|
@ -9,6 +9,16 @@ export function listEntpcoursework(query) {
|
|||
})
|
||||
}
|
||||
|
||||
|
||||
// 查询entpcoursework列表
|
||||
export function listEntpcourseworkLocal(query) {
|
||||
return request({
|
||||
url: '/education/entpcoursework/local/list',
|
||||
method: 'get',
|
||||
params: query
|
||||
})
|
||||
}
|
||||
|
||||
// 查询entpcoursework详细
|
||||
export function getEntpcoursework(id) {
|
||||
return request({
|
||||
|
|
|
@ -0,0 +1,54 @@
|
|||
import request from '@/utils/request'
|
||||
|
||||
// 查询entpcourse列表
|
||||
export function listEntpcourse(query) {
|
||||
return request({
|
||||
url: '/education/entpcourse/list',
|
||||
method: 'get',
|
||||
params: query
|
||||
})
|
||||
}
|
||||
|
||||
// 查询entpcourse详细
|
||||
export function getEntpcourse(id) {
|
||||
return request({
|
||||
url: '/education/entpcourse/' + id,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
// 新增entpcourse
|
||||
export function addEntpcourse(data) {
|
||||
return request({
|
||||
url: '/education/entpcourse',
|
||||
method: 'post',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
// 修改entpcourse
|
||||
export function updateEntpcourse(data) {
|
||||
return request({
|
||||
url: '/education/entpcourse',
|
||||
method: 'put',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
// 删除entpcourse
|
||||
export function delEntpcourse(id) {
|
||||
return request({
|
||||
url: '/education/entpcourse/' + id,
|
||||
method: 'delete'
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
|
||||
export function lpmChat(query) {
|
||||
return request({
|
||||
url: '/llm/chatToSD',
|
||||
method: 'POST',
|
||||
params: query
|
||||
})
|
||||
}
|
|
@ -85,6 +85,22 @@ export function updateFileByArray(data) {
|
|||
data: data
|
||||
})
|
||||
}
|
||||
// zdg: 批量更新pptist - 新
|
||||
export function batchUpdateNew(data) {
|
||||
return request({
|
||||
url: '/education/entpcoursefile/batch/update',
|
||||
method: 'post',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
// zdg: 批量新增pptist - 新
|
||||
export function batchAddNew(data) {
|
||||
return request({
|
||||
url: '/education/entpcoursefile/batch/add',
|
||||
method: 'post',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
// 修改entpcoursefile
|
||||
export function updateFile2Redis(data) {
|
||||
|
@ -102,6 +118,14 @@ export function delEntpcoursefile(id) {
|
|||
method: 'delete'
|
||||
})
|
||||
}
|
||||
// 删除entpcoursefile - new
|
||||
export function delEntpcoursefileNew(id) {
|
||||
return request({
|
||||
url: '/education/entpcoursefile/delete',
|
||||
method: 'get',
|
||||
params: {id}
|
||||
})
|
||||
}
|
||||
|
||||
// 保存base64图片,返回url
|
||||
export function saveEntpCourseBase64File(data) {
|
||||
|
|
|
@ -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>
|
|
@ -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]
|
||||
|
|
|
@ -41,7 +41,7 @@
|
|||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button @click.stop="cloneDialog(ruleFormRef)">取消</el-button>
|
||||
<el-button type="primary" @click.stop="onSubmit(ruleFormRef)"> 确定 </el-button>
|
||||
<el-button :loading="setLoading" type="primary" @click.stop="onSubmit(ruleFormRef)"> 确定 </el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<template>
|
||||
<el-dialog v-model="isDialog" :show-close="false" width="800" destroy-on-close>
|
||||
<el-dialog v-model="isDialog" :show-close="false" width="800" append-to-body destroy-on-close>
|
||||
<template #header>
|
||||
<div class="custom-header flex">
|
||||
<span>{{ item.name }}</span>
|
||||
|
@ -40,8 +40,8 @@
|
|||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive, onMounted } from 'vue'
|
||||
import { conversation, completion } from '@/api/mode/index'
|
||||
import { ref, reactive, onMounted, watch } from 'vue'
|
||||
import { completion } from '@/api/mode/index'
|
||||
import { sessionStore } from '@/utils/store'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { dataSetJson } from '@/utils/comm.js'
|
||||
|
@ -58,7 +58,7 @@ const props = defineProps({
|
|||
return { name: '11' }
|
||||
}
|
||||
},
|
||||
modeType: {
|
||||
type: {
|
||||
type: Number,
|
||||
default: 1
|
||||
}
|
||||
|
@ -77,7 +77,7 @@ const send = () =>{
|
|||
msg: textarea.value
|
||||
})
|
||||
loaded.value = true
|
||||
getConversation(textarea.value)
|
||||
getCompletion(textarea.value)
|
||||
textarea.value = ''
|
||||
}
|
||||
const curNode = reactive({})
|
||||
|
@ -87,16 +87,11 @@ const params = reactive(
|
|||
dataset_id: ''
|
||||
}
|
||||
)
|
||||
// 获取会话ID
|
||||
const getConversation = (val) => {
|
||||
|
||||
getCompletion(val)
|
||||
}
|
||||
// 大模型对话
|
||||
const getCompletion = async (val) => {
|
||||
try {
|
||||
|
||||
params.prompt = `根据${curNode.edustage}${curNode.edusubject}课标${props.item.name},${val}`
|
||||
params.prompt = `按照${val}的要求,针对${curNode.edustage}${curNode.edusubject}${modeType.value} 对${curNode.itemtitle}进行教学分析`
|
||||
const { data } = await completion(params)
|
||||
let answer = data.answer
|
||||
msgList.value.push({
|
||||
|
@ -110,20 +105,31 @@ const getCompletion = async (val) => {
|
|||
}
|
||||
|
||||
const saveAdjust = (item) =>{
|
||||
|
||||
isDialog.value = false
|
||||
ElMessage.success('操作成功')
|
||||
emitter.on('saveAdjust', item.msg)
|
||||
|
||||
emitter.emit('onSaveAdjust', item.msg)
|
||||
}
|
||||
|
||||
const modeType = ref('课标')
|
||||
watch(() => props.type, (newVal) => {
|
||||
if (newVal == 1){
|
||||
modeType.value = '课标'
|
||||
}
|
||||
if (newVal == 2){
|
||||
modeType.value = '教材'
|
||||
}
|
||||
if (newVal == 2){
|
||||
modeType.value = '考试'
|
||||
}
|
||||
|
||||
}, { immediate: false })
|
||||
|
||||
|
||||
onMounted(() => {
|
||||
let data = sessionStore.get('subject.curNode')
|
||||
|
||||
Object.assign(curNode, data);
|
||||
let text = props.modeType == 1||props.modeType == 2 ? '课标' : '考试'
|
||||
let jsonKey = `${text}-${data.edustage}-${data.edusubject}`
|
||||
console.log(jsonKey)
|
||||
let jsonKey = `${modeType.value}-${data.edustage}-${data.edusubject}`
|
||||
params.dataset_id = dataSetJson[jsonKey]
|
||||
|
||||
})
|
||||
|
|
|
@ -52,7 +52,7 @@ watch(() => props.item.answer, (newVal) => {
|
|||
|
||||
|
||||
const onSave = () =>{
|
||||
editTempResult({id: props.item.reultId, content: textarea.value}).then( res =>{
|
||||
editTempResult({id: props.item.resultId, content: textarea.value}).then( res =>{
|
||||
isDialog.value = false
|
||||
ElMessage.success('操作成功')
|
||||
emitter.emit('onGetChild', textarea.value)
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
|
||||
<script setup>
|
||||
import { ref, onMounted, nextTick } from 'vue'
|
||||
import { sessionStore } from '@/utils/store'
|
||||
import PDF from '@/components/PdfJs/index.vue'
|
||||
import LeftDialog from './left-dialog.vue'
|
||||
|
||||
|
@ -31,7 +32,11 @@ const onClick = () => {
|
|||
const pdfUrl = ref('')
|
||||
onMounted(async () => {
|
||||
await nextTick()
|
||||
const { fileurl } = props.curNode
|
||||
let data = sessionStore.get('subject.curBook')
|
||||
let fileurl = data.fileurl
|
||||
if(props.type == 1){
|
||||
fileurl = `${data.edustage}-${data.edusubject}-课标.txt`
|
||||
}
|
||||
pdfUrl.value = import.meta.env.VITE_APP_RES_FILE_PATH + fileurl.replace('.txt', '.pdf')
|
||||
})
|
||||
</script>
|
||||
|
|
|
@ -51,7 +51,9 @@
|
|||
<div class="item-icon">
|
||||
<i class="iconfont icon-ai"></i>
|
||||
</div>
|
||||
<div class="item-answer" v-html="item.answer"></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)">
|
||||
|
@ -68,7 +70,6 @@
|
|||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</template>
|
||||
<el-empty v-if="!childTempList.length" description="暂无模板数据" />
|
||||
</div>
|
||||
|
@ -76,18 +77,20 @@
|
|||
<!--编辑结果-->
|
||||
<EditDialog v-model="isEdit" :item="editItem" />
|
||||
<!--AI 对话调整-->
|
||||
<AdjustDialog v-model="isAdjust" :modeType="type" :item="editItem" />
|
||||
<AdjustDialog v-model="isAdjust" :type="type" :item="editItem" />
|
||||
<!--添加、编辑提示词-->
|
||||
<keywordDialog v-model="isWordDialog" :isAdd="isAdd" :item="editItem" />
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive, onMounted, computed, onUnmounted } from 'vue'
|
||||
import { ref, reactive, onMounted, watch, onUnmounted } from 'vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { tempSave, completion, modelList, removeChildTemp, tempResult } from '@/api/mode/index'
|
||||
import { tempSave, completion, modelList, removeChildTemp, tempResult, editTempResult } from '@/api/mode/index'
|
||||
import { sessionStore } from '@/utils/store'
|
||||
import keywordDialog from './keyword-dialog.vue';
|
||||
import AdjustDialog from './adjust-dialog.vue'
|
||||
import EditDialog from './edit-dialog.vue'
|
||||
import TypingEffect from '@/components/typing-effect/index.vue'
|
||||
import emitter from '@/utils/mitt';
|
||||
import { dataSetJson } from '@/utils/comm.js'
|
||||
|
||||
|
@ -108,7 +111,6 @@ const onAdd = () => {
|
|||
isAdd.value = true
|
||||
Object.assign(editItem, curTemplate)
|
||||
isWordDialog.value = true
|
||||
|
||||
}
|
||||
|
||||
const editKeyWord = (item, val) => {
|
||||
|
@ -154,13 +156,13 @@ const getChildTemplate = () => {
|
|||
|
||||
// 查询模板结果
|
||||
const getTempResult = () => {
|
||||
tempResult({ mainModelId: curTemplate.id }).then(res => {
|
||||
tempResult({ mainModelId: curTemplate.id, pageNum: 1, pageSize: 10000 }).then(res => {
|
||||
let rows = res.rows
|
||||
childTempList.value.forEach(item => {
|
||||
rows.forEach(el => {
|
||||
if (item.id == el.modelId) {
|
||||
item.answer = el.content
|
||||
item.reultId = el.id
|
||||
item.resultId = el.id
|
||||
}
|
||||
})
|
||||
})
|
||||
|
@ -187,7 +189,7 @@ const changeTemplate = (val) => {
|
|||
const removeItem = async (item, isChild) => {
|
||||
/**
|
||||
* item: 当前操作的模板
|
||||
* isChild: 子模板中的移除为 true 否则为false
|
||||
* isChild: 子模板中的移除为 true
|
||||
*/
|
||||
if (item.ex3 != '1') {
|
||||
ElMessageBox.confirm(
|
||||
|
@ -213,15 +215,11 @@ const removeItem = async (item, isChild) => {
|
|||
})
|
||||
}
|
||||
else {
|
||||
|
||||
|
||||
editKeyWord(item,!isChild)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
// Ai对话调整
|
||||
const curIndex = ref(-1)
|
||||
const isAdjust = ref(false)
|
||||
|
@ -240,11 +238,21 @@ const onEdit = (index, item) => {
|
|||
}
|
||||
|
||||
|
||||
const modeType = computed(() => {
|
||||
if (props.type == 1) return '课标';
|
||||
if (props.type == 2) return '教材';
|
||||
if (props.type == 3) return '考试';
|
||||
})
|
||||
const modeType = ref('课标')
|
||||
watch(() => props.type, (newVal) => {
|
||||
if (newVal == 1){
|
||||
modeType.value = '课标'
|
||||
}
|
||||
if (newVal == 2){
|
||||
modeType.value = '教材'
|
||||
}
|
||||
if (newVal == 2){
|
||||
modeType.value = '考试'
|
||||
}
|
||||
|
||||
}, { immediate: false })
|
||||
|
||||
|
||||
// 重新研读
|
||||
const params = reactive(
|
||||
{
|
||||
|
@ -256,12 +264,14 @@ const params = reactive(
|
|||
const againResult = async (index, item) => {
|
||||
try {
|
||||
childTempList.value[index].loading = true
|
||||
params.prompt = `按照${item.name}的要求,针对${props.curNode.edustage}${props.curNode.edusubject}${modeType} 对${props.curNode.itemtitle}进行教学分析`
|
||||
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);
|
||||
onSaveTemp(item)
|
||||
// onEditSave(item)
|
||||
} finally {
|
||||
childTempList.value[index].loading = false
|
||||
}
|
||||
|
@ -271,40 +281,48 @@ const getCompletion = async () => {
|
|||
for (let item of childTempList.value) {
|
||||
try {
|
||||
item.loading = true
|
||||
params.prompt = `按照${item.name}的要求,针对${props.curNode.edustage}${props.curNode.edusubject}${modeType} 对${props.curNode.itemtitle}进行教学分析`
|
||||
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);
|
||||
onSaveTemp(item)
|
||||
|
||||
} finally {
|
||||
item.loading = 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
|
||||
onEditSave(childTempList.value[curIndex.value])
|
||||
})
|
||||
|
||||
|
||||
// 保存 重新研读后的结果
|
||||
const onEditSave = async (item) =>{
|
||||
const { res } = await editTempResult({id: item.resultId, content: item.oldAnswer})
|
||||
ElMessage.success(res)
|
||||
getChildTemplate()
|
||||
}
|
||||
|
||||
// 保存模板
|
||||
const onSaveTemp = (item) => {
|
||||
if(item.oldAnswer == '') return
|
||||
const data = {
|
||||
mainModelId: props.tempId,
|
||||
mainModelId: curTemplate.id,
|
||||
modelId: item.id,
|
||||
examDocld: '',
|
||||
content: item.oldAnswer
|
||||
}
|
||||
tempSave(data).then(res => {
|
||||
console.log(res)
|
||||
})
|
||||
tempSave(data).then(res => {})
|
||||
}
|
||||
|
||||
|
||||
|
||||
// 替换分析结果
|
||||
emitter.on('saveAdjust', (item) => {
|
||||
childTempList.value[curIndex.value].oldAnswer = item
|
||||
let answer = getResult(item);
|
||||
childTempList.value[curIndex.value].answer = answer
|
||||
})
|
||||
|
||||
// 分析获取课标对话结果
|
||||
let getResult = (text) => {
|
||||
text = text.replace(/^\n\n(.*?)\n\n$/s, '<div>$1</div>');
|
||||
|
@ -324,17 +342,20 @@ emitter.on('onGetMain', () => {
|
|||
})
|
||||
|
||||
|
||||
|
||||
const curNode = reactive({})
|
||||
onMounted(() => {
|
||||
getTemplateList()
|
||||
let jsonKey = `${modeType}-${props.curNode.edustage}-${props.curNode.edusubject}`
|
||||
let data = sessionStore.get('subject.curNode')
|
||||
Object.assign(curNode, data);
|
||||
let jsonKey = `${modeType.value}-${data.edustage}-${data.edusubject}`
|
||||
params.dataset_id = dataSetJson[jsonKey]
|
||||
})
|
||||
|
||||
// 解绑
|
||||
onUnmounted(() => {
|
||||
emitter.off('onGetMain');
|
||||
emitter.off('onGetChild');
|
||||
emitter.off('saveAdjust');
|
||||
emitter.off('onSaveAdjust');
|
||||
})
|
||||
</script>
|
||||
|
||||
|
@ -404,6 +425,7 @@ onUnmounted(() => {
|
|||
.item-answer {
|
||||
flex-direction: column;
|
||||
padding-top: 5px;
|
||||
width: 100%;
|
||||
|
||||
:deep(.text-tit) {
|
||||
font-weight: bold;
|
||||
|
|
|
@ -284,23 +284,23 @@ const init = reactive({
|
|||
formData.append("filetype", "image");
|
||||
formData.append("suffix", "image");
|
||||
formData.append("status", '1');
|
||||
if(userStore.deptId && userStore.deptId != null){
|
||||
if(userStore.deptId != null){
|
||||
formData.append("entpid", userStore.deptId);
|
||||
}
|
||||
if(userStore.userId && userStore.userId != null){
|
||||
if(userStore.userId != null){
|
||||
formData.append("userid", userStore.userId);
|
||||
}
|
||||
if(userStore.edudegree && userStore.edudegree != null){
|
||||
if(userStore.edudegree && userStore.edudegree != ''){
|
||||
let edudegree = userStore.edudegree.toString();
|
||||
if(edudegree != '' && edudegree.indexOf('年级') == -1){
|
||||
edudegree += '年级';
|
||||
}
|
||||
formData.append("edudegree", edudegree);
|
||||
}
|
||||
if(userStore.edusubject && userStore.edusubject != null){
|
||||
if(userStore.edusubject && userStore.edusubject != ''){
|
||||
formData.append("edusubject", userStore.edusubject);
|
||||
}
|
||||
if(userStore.edustage && userStore.edustage != null){
|
||||
if(userStore.edustage && userStore.edustage != ''){
|
||||
formData.append("edustage", userStore.edustage);
|
||||
}
|
||||
if(props.upFileParams?.hasOwnProperty('lessionId')){
|
||||
|
|
|
@ -0,0 +1,67 @@
|
|||
<template>
|
||||
<div class="typing-effect">
|
||||
<!-- <span v-html="displayedText"></span> -->
|
||||
<el-input
|
||||
v-model="displayedText"
|
||||
:autosize="{ minRows: 2 }"
|
||||
type="textarea"
|
||||
readonly
|
||||
resize="none"
|
||||
style="width: 100%;"
|
||||
input-style="border:none;outline: none;box-shadow:none;color:000;fontSize:15px"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted, watch } from 'vue';
|
||||
|
||||
const props = defineProps({
|
||||
text: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
delay: {
|
||||
type: Number,
|
||||
default: 100 // 默认每个字符出现的延迟时间,单位是毫秒
|
||||
},
|
||||
aiShow: {
|
||||
type: [Boolean]
|
||||
}
|
||||
});
|
||||
const emit = defineEmits(['complete']);
|
||||
const displayedText = ref('');
|
||||
const index = ref(0);
|
||||
|
||||
const type = () => {
|
||||
if(!props.aiShow) return
|
||||
if (index.value <= props.text.length) {
|
||||
displayedText.value += props.text.charAt(index.value);
|
||||
index.value++;
|
||||
setTimeout(() => type(), props.delay);
|
||||
} else {
|
||||
// 当所有字符都显示完毕时,触发 complete 事件
|
||||
emit('complete');
|
||||
}
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
type();
|
||||
});
|
||||
|
||||
// 监听 props 的变化,以便当传入的 text 或 delay 发生改变时重新开始打字机效果
|
||||
watch([() => props.text, () => props.delay], () => {
|
||||
displayedText.value = '';
|
||||
index.value = 0;
|
||||
type();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.typing-effect {
|
||||
font-family: monospace;
|
||||
}
|
||||
:deep(.el-textarea__inner:hover){
|
||||
box-shadow: none;
|
||||
}
|
||||
</style>
|
|
@ -8,9 +8,10 @@
|
|||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { ElMessageBox } from 'element-plus'
|
||||
import useUserStore from '@/store/modules/user'
|
||||
const Remote = require('@electron/remote')
|
||||
|
||||
const userStore = useUserStore()
|
||||
const { ipcRenderer } = window.electron || {}
|
||||
|
@ -47,6 +48,11 @@ const closeWindow = () => {
|
|||
})
|
||||
}).catch(() => { });
|
||||
}
|
||||
|
||||
onMounted(() =>{
|
||||
isMaxSize.value = Remote.getCurrentWindow().isMaximized()
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
<el-breadcrumb :separator-icon="ArrowRight">
|
||||
<el-breadcrumb-item v-for="item in breadList"> {{ item.title }} </el-breadcrumb-item>
|
||||
</el-breadcrumb>
|
||||
<span class="ml-5">《{{ curNode.itemtitle }}》</span>
|
||||
</div>
|
||||
<div class="header-center" v-else>
|
||||
AI文枢{{ version }}
|
||||
|
@ -20,11 +21,13 @@
|
|||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, watch } from 'vue'
|
||||
import { reactive, ref, watch } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { ArrowRight } from '@element-plus/icons-vue'
|
||||
import WindowTools from '@/components/window-tools/index.vue'
|
||||
import pkc from "../../../../../package.json"
|
||||
import { sessionStore } from '@/utils/store'
|
||||
|
||||
const version = ref(pkc.version)
|
||||
|
||||
// 返回
|
||||
|
@ -33,6 +36,8 @@ const onBack = () => {
|
|||
router.go(-1)
|
||||
}
|
||||
|
||||
const curNode = reactive({itemtitle: ''})
|
||||
|
||||
// 监听当前路由
|
||||
const isShowBack = ref(false)
|
||||
const breadList = ref([])
|
||||
|
@ -44,7 +49,8 @@ watch(
|
|||
if (path.includes('/model') && path !== ('/model/index')) {
|
||||
isShowBack.value = true
|
||||
breadList.value = newValue.matched.map(item => item.meta)
|
||||
|
||||
let data = sessionStore.get('subject.curNode')
|
||||
Object.assign(curNode, data)
|
||||
}
|
||||
else {
|
||||
isShowBack.value = false
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
// import tab from './tab'
|
||||
// import auth from './auth'
|
||||
// import cache from './cache'
|
||||
import modal from './modal'
|
||||
// import download from './download'
|
||||
|
||||
import modal from './modal'
|
||||
// import './vue3-menus'
|
||||
import vue3Menus from './vue3-menus'
|
||||
// console.log('vue3Menus', defineComponent)
|
||||
export default function installPlugins(app){
|
||||
// 页签操作
|
||||
// app.config.globalProperties.$tab = tab
|
||||
|
@ -15,4 +17,6 @@ export default function installPlugins(app){
|
|||
app.config.globalProperties.$modal = modal
|
||||
// 下载文件
|
||||
// app.config.globalProperties.$download = download
|
||||
// 右键菜单 支持组件|指令|函数 三种方式使用
|
||||
vue3Menus(app)
|
||||
}
|
||||
|
|
|
@ -10,7 +10,7 @@ import _ from 'lodash'
|
|||
// import { diff } from 'jsondiffpatch'
|
||||
// const Remote = isNode?require('@electron/remote'):{} // 远程模块
|
||||
|
||||
const exArrs = ['subject'] // 不需要同步key-排除
|
||||
const exArrs = ['subject','env','curr'] // 不需要同步key-排除
|
||||
|
||||
export function shareStorePlugin({store}) {
|
||||
store.$subscribe((mutation, state) => { // 自动同步
|
||||
|
|
|
@ -0,0 +1,437 @@
|
|||
import { defineComponent, getCurrentInstance, ref, computed, watch, nextTick, createVNode, Teleport, Transition, render } from 'vue';
|
||||
|
||||
function styleInject(css, ref) {
|
||||
if ( ref === void 0 ) ref = {};
|
||||
var insertAt = ref.insertAt;
|
||||
|
||||
if (!css || typeof document === 'undefined') { return; }
|
||||
|
||||
var head = document.head || document.getElementsByTagName('head')[0];
|
||||
var style = document.createElement('style');
|
||||
style.type = 'text/css';
|
||||
|
||||
if (insertAt === 'top') {
|
||||
if (head.firstChild) {
|
||||
head.insertBefore(style, head.firstChild);
|
||||
} else {
|
||||
head.appendChild(style);
|
||||
}
|
||||
} else {
|
||||
head.appendChild(style);
|
||||
}
|
||||
|
||||
if (style.styleSheet) {
|
||||
style.styleSheet.cssText = css;
|
||||
} else {
|
||||
style.appendChild(document.createTextNode(css));
|
||||
}
|
||||
}
|
||||
|
||||
var css_248z = ".menus-fade-enter-active,\n.menus-fade-leave-active {\n transition: opacity 0.2s ease-in-out;\n}\n.menus-fade-enter-from,\n.menus-fade-leave-to {\n opacity: 0;\n}\n\n.v3-menus {\n position: fixed;\n box-shadow: 0 2px 4px rgba(0, 0, 0, 0.12), 0 0 6px rgba(0, 0, 0, 0.04);\n background: #fff;\n border-radius: 4px;\n padding: 8px 0;\n user-select: none;\n box-sizing: border-box;\n}\n\n.v3-menus-body {\n display: block;\n}\n\n.v3-menus-item {\n display: flex;\n line-height: 2rem;\n padding: 0 1rem;\n margin: 0;\n font-size: 0.8rem;\n outline: 0;\n align-items: center;\n transition: 0.2s;\n box-sizing: border-box;\n list-style: none;\n border-bottom: 1px solid #00000000;\n}\n\n.v3-menus-divided {\n border-bottom-color: #ebeef5;\n}\n\n.v3-menus-icon {\n display: flex;\n margin-right: 0.6rem;\n width: 1rem;\n}\n\n.v3-menus-label {\n flex: 1;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n.v3-menus-suffix {\n margin-left: 1.5rem;\n font-size: 0.39rem;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n.v3-menus-available {\n color: #606266;\n cursor: pointer;\n}\n\n.v3-menus-available:hover {\n background: #ecf5ff;\n color: #409eff;\n}\n\n.v3-menus-disabled {\n color: #c0c4cc;\n cursor: not-allowed;\n}\n\n.v3-menus-active {\n background: #ecf5ff;\n color: #409eff;\n}\n\n.v3-menus-tip {\n font-size: 9px;\n color: #999;\n}\n";
|
||||
styleInject(css_248z);
|
||||
|
||||
const props = {
|
||||
menus: {
|
||||
type: Array,
|
||||
required: true
|
||||
},
|
||||
menusClass: {
|
||||
type: String,
|
||||
default: null
|
||||
},
|
||||
itemClass: {
|
||||
type: String,
|
||||
default: null
|
||||
},
|
||||
event: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
minWidth: {
|
||||
type: [Number, String],
|
||||
default: 'none'
|
||||
},
|
||||
maxWidth: {
|
||||
type: [Number, String],
|
||||
default: 'none'
|
||||
},
|
||||
zIndex: {
|
||||
type: Number,
|
||||
default: 3
|
||||
},
|
||||
direction: {
|
||||
type: String,
|
||||
default: 'right'
|
||||
},
|
||||
open: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
args: {
|
||||
type: [Object, Function, Array, Boolean, String],
|
||||
default: {}
|
||||
}
|
||||
};
|
||||
const vue3MenusComponent = defineComponent({
|
||||
name: 'vue3-menus',
|
||||
inheritAttrs: false,
|
||||
props,
|
||||
|
||||
setup(props, {
|
||||
slots,
|
||||
attrs
|
||||
}) {
|
||||
const windowWidth = globalThis.document.documentElement.clientWidth;
|
||||
const windowHeight = globalThis.document.documentElement.clientHeight;
|
||||
const {
|
||||
proxy
|
||||
} = getCurrentInstance();
|
||||
const show = ref(props.open);
|
||||
const self = {};
|
||||
const menusRef = ref(null);
|
||||
const activeIndex = ref(-1);
|
||||
const left = ref(0);
|
||||
const top = ref(0);
|
||||
let direction = props.direction;
|
||||
const hasIcon = computed(() => {
|
||||
for (let index = 0; index < props.menus.length; index++) {
|
||||
const menu = props.menus[index];
|
||||
|
||||
if (menu.icon !== undefined) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
});
|
||||
const position = computed(() => {
|
||||
return {
|
||||
x: props.event.clientX,
|
||||
y: props.event.clientY,
|
||||
width: props.event.width || 0,
|
||||
height: props.event.height || 0
|
||||
};
|
||||
});
|
||||
const style = computed(() => {
|
||||
return {
|
||||
left: `${left.value}px`,
|
||||
top: `${top.value}px`,
|
||||
minWidth: `${props.minWidth}px`,
|
||||
maxWidth: props.maxWidth == 'none' ? props.maxWidth : `${props.maxWidth}px`,
|
||||
zIndex: props.zIndex
|
||||
};
|
||||
});
|
||||
|
||||
function leftOpen(menusWidth) {
|
||||
left.value = position.value.x - menusWidth;
|
||||
direction = 'left';
|
||||
|
||||
if (left.value < 0) {
|
||||
direction = 'right';
|
||||
|
||||
if (position.value.width === 0 || position.value.width === undefined) {
|
||||
left.value = 0;
|
||||
} else {
|
||||
left.value = position.value.x + position.value.width;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function rightOpen(windowWidth, menusWidth) {
|
||||
left.value = position.value.x + position.value.width;
|
||||
direction = 'right';
|
||||
|
||||
if (left.value + menusWidth > windowWidth) {
|
||||
direction = 'left';
|
||||
|
||||
if (position.value.width === 0 || position.value.width === undefined) {
|
||||
left.value = windowWidth - menusWidth;
|
||||
} else {
|
||||
left.value = position.value.x - menusWidth;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function closeEvent() {
|
||||
activeIndex.value = -1;
|
||||
show.value = false;
|
||||
|
||||
if (self && self.instance) {
|
||||
self.instance.close.bind(self.instance)();
|
||||
self.instance = null;
|
||||
self.index = null; // @ts-ignore
|
||||
|
||||
if (proxy.closeAll) {
|
||||
// @ts-ignore
|
||||
proxy.closeAll();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
watch(() => props.open, newVal => show.value = newVal);
|
||||
watch(show, newVal => {
|
||||
if (newVal) {
|
||||
nextTick(() => {
|
||||
const menusWidth = menusRef.value.offsetWidth;
|
||||
const menusHeight = menusRef.value.offsetHeight;
|
||||
|
||||
if (direction === 'left') {
|
||||
leftOpen(menusWidth);
|
||||
} else {
|
||||
rightOpen(windowWidth, menusWidth);
|
||||
}
|
||||
|
||||
top.value = position.value.y;
|
||||
|
||||
if (position.value.y + menusHeight > windowHeight) {
|
||||
if (position.value.height === 0 || position.value.height === undefined) {
|
||||
top.value = position.value.y - menusHeight;
|
||||
} else {
|
||||
top.value = windowHeight - menusHeight;
|
||||
}
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
globalThis.document.addEventListener('click', closeEvent);
|
||||
globalThis.document.addEventListener('contextmenu', closeEvent);
|
||||
globalThis.document.addEventListener('wheel', closeEvent);
|
||||
}, 0);
|
||||
});
|
||||
} else {
|
||||
activeIndex.value = -1;
|
||||
globalThis.document.removeEventListener('click', closeEvent);
|
||||
globalThis.document.removeEventListener('contextmenu', closeEvent);
|
||||
globalThis.document.removeEventListener('wheel', closeEvent);
|
||||
}
|
||||
}, {
|
||||
immediate: true
|
||||
});
|
||||
|
||||
function mouseEnter(event, menu, index) {
|
||||
event.preventDefault();
|
||||
activeIndex.value = index;
|
||||
|
||||
if (!menu || menu.disabled || menu.hidden) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (self.instance) {
|
||||
if (self.index === index) {
|
||||
return;
|
||||
}
|
||||
|
||||
self.instance.close.bind(self.instance)();
|
||||
self.instance = null;
|
||||
self.index = null;
|
||||
}
|
||||
|
||||
if (!menu.children) {
|
||||
return;
|
||||
}
|
||||
|
||||
const enter = menu.enter && typeof menu.enter === 'function' ? menu.enter : null;
|
||||
|
||||
if (enter) {
|
||||
const val = enter(menu, props.args);
|
||||
|
||||
if (val === false || val === null) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const menuItemClientRect = event.target.getBoundingClientRect();
|
||||
const vm = createVNode(vue3MenusComponent, { ...props,
|
||||
menus: menu.children,
|
||||
direction: direction,
|
||||
event: {
|
||||
clientX: menuItemClientRect.x + 3,
|
||||
clientY: menuItemClientRect.y - 8,
|
||||
width: menuItemClientRect.width - 2 * 3,
|
||||
height: menuItemClientRect.width
|
||||
},
|
||||
open: false
|
||||
}, slots);
|
||||
const container = globalThis.document.createElement('div');
|
||||
render(vm, container);
|
||||
vm.component.props.open = true; // @ts-ignore
|
||||
|
||||
vm.component.proxy.close = close;
|
||||
self.instance = vm.component.proxy;
|
||||
self.instance.container = container;
|
||||
self.instance.props = vm.component.props;
|
||||
self.index = index;
|
||||
}
|
||||
|
||||
function mouseClick(event, menu) {
|
||||
event.preventDefault();
|
||||
|
||||
if (!menu || menu.disabled) {
|
||||
event.stopPropagation();
|
||||
return;
|
||||
}
|
||||
|
||||
const click = menu.click && typeof menu.click === 'function' ? menu.click : null;
|
||||
|
||||
if (click) {
|
||||
const val = click(menu, props.args);
|
||||
|
||||
if (val === false || val === null) {
|
||||
event.stopPropagation();
|
||||
}
|
||||
}
|
||||
|
||||
if (menu.children) {
|
||||
event.stopPropagation();
|
||||
}
|
||||
}
|
||||
|
||||
function close() {
|
||||
this.show = false;
|
||||
|
||||
if (this.self && this.self.instance) {
|
||||
this.self.instance.close();
|
||||
}
|
||||
|
||||
nextTick(() => {
|
||||
render(null, this.container);
|
||||
});
|
||||
}
|
||||
|
||||
const {
|
||||
default: $default,
|
||||
label,
|
||||
icon,
|
||||
suffix
|
||||
} = slots;
|
||||
const $class = ['v3-menus', attrs.class, props.menusClass];
|
||||
return () => createVNode(Teleport, {
|
||||
"to": 'body'
|
||||
}, {
|
||||
default: () => [createVNode(Transition, {
|
||||
"name": 'menus-fade'
|
||||
}, {
|
||||
default: () => [!show.value ? null : createVNode("div", {
|
||||
"ref": menusRef,
|
||||
"class": $class,
|
||||
"style": style.value,
|
||||
"onWheel": e => e.preventDefault(),
|
||||
"onContextmenu": e => e.preventDefault()
|
||||
}, [createVNode("div", {
|
||||
"class": 'v3-menus-body'
|
||||
}, [props.menus.map((menu, index) => {
|
||||
if (menu.hidden) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ($default) {
|
||||
return createVNode("div", {
|
||||
"onContextmenu": $event => mouseClick($event, menu),
|
||||
"onClick": $event => mouseClick($event, menu),
|
||||
"onMouseenter": $event => mouseEnter($event, menu, index)
|
||||
}, [$default({
|
||||
menu,
|
||||
activeIndex: activeIndex.value,
|
||||
index
|
||||
})]);
|
||||
} else {
|
||||
let $class = [props.itemClass, 'v3-menus-item', menu.disabled ? 'v3-menus-disabled' : 'v3-menus-available'];
|
||||
$class = $class.concat([menu.divided ? 'v3-menus-divided' : null, !menu.disabled && activeIndex.value === index ? 'v3-menus-active' : null]);
|
||||
return createVNode("div", {
|
||||
"style": menu.style,
|
||||
"class": $class.join(' '),
|
||||
"onClick": $event => mouseClick($event, menu),
|
||||
"onMouseenter": $event => mouseEnter($event, menu, index),
|
||||
"onContextmenu": $event => mouseClick($event, menu)
|
||||
}, [hasIcon.value ? createVNode("div", {
|
||||
"class": 'v3-menus-icon '
|
||||
}, [icon ? icon({
|
||||
menu,
|
||||
activeIndex: activeIndex.value,
|
||||
index
|
||||
}) : createVNode("span", {
|
||||
"innerHTML": menu.icon
|
||||
}, null)]) : null, label ? createVNode("span", {
|
||||
"class": 'v3-menus-label'
|
||||
}, [label({
|
||||
menu,
|
||||
activeIndex: activeIndex.value,
|
||||
index
|
||||
})]) : createVNode("span", {
|
||||
"class": 'v3-menus-label'
|
||||
}, [menu.label]), menu.children || menu.tip ? createVNode("div", {
|
||||
"class": 'v3-menus-suffix'
|
||||
}, [suffix ? suffix({
|
||||
menu,
|
||||
activeIndex: activeIndex.value,
|
||||
index
|
||||
}) : menu.children ? '▶' : menu.tip ? createVNode("span", {
|
||||
"class": 'v3-menus-tip'
|
||||
}, [menu.tip]) : null]) : null]);
|
||||
}
|
||||
})])])]
|
||||
})]
|
||||
});
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
function mouseEvent(menus, args, event) {
|
||||
let props = {};
|
||||
if (Array.isArray(menus)) {
|
||||
props = {
|
||||
menus,
|
||||
event,
|
||||
args,
|
||||
open: false,
|
||||
};
|
||||
} else {
|
||||
props = {
|
||||
...menus,
|
||||
args,
|
||||
event,
|
||||
open: false
|
||||
};
|
||||
}
|
||||
const vNode = createVNode(vue3MenusComponent, props);
|
||||
const container = globalThis.document.createElement('div');
|
||||
render(vNode, container);
|
||||
vNode.component.props.open = true;
|
||||
vNode.component.proxy.closeAll = () => {
|
||||
nextTick(() => {
|
||||
render(null, container);
|
||||
});
|
||||
};
|
||||
if (props.prevent == undefined || props.prevent) {
|
||||
event.preventDefault();
|
||||
}
|
||||
}
|
||||
|
||||
const directive = {
|
||||
mounted(el, { value, arg }) {
|
||||
const vnode = el.__vnode || {};
|
||||
if (arg === undefined || arg === 'right') {
|
||||
el.addEventListener("contextmenu", mouseEvent.bind(el, value, vnode.props || {}));
|
||||
} else if (arg === 'left') {
|
||||
el.addEventListener("click", mouseEvent.bind(el, value, vnode.props || {}));
|
||||
} else if (arg === 'all') {
|
||||
el.addEventListener("contextmenu", mouseEvent.bind(el, value, vnode.props || {}));
|
||||
el.addEventListener("click", mouseEvent.bind(el, value, vnode.props || {}));
|
||||
}
|
||||
},
|
||||
unmounted(el) {
|
||||
el.removeEventListener("contextmenu", mouseEvent);
|
||||
el.removeEventListener("click", mouseEvent);
|
||||
}
|
||||
};
|
||||
|
||||
const install = function (app, options = {}) {
|
||||
app.component(options.name || vue3MenusComponent.name, vue3MenusComponent);
|
||||
app.directive('menus', directive);
|
||||
app.config.globalProperties.$menusEvent = (event, menus, args) => mouseEvent(menus, args || {}, event);
|
||||
};
|
||||
|
||||
const menusEvent = (event, menus, args) => mouseEvent(menus, args || {}, event);
|
||||
|
||||
function index (app) {
|
||||
app.use(install);
|
||||
}
|
||||
|
||||
export { vue3MenusComponent as Vue3Menus, index as default, directive, menusEvent };
|
|
@ -85,6 +85,12 @@ export const constantRoutes = [
|
|||
name: 'questionUpload',
|
||||
meta: { title: '习题上传' }
|
||||
},
|
||||
{
|
||||
path: 'aiKolors',
|
||||
component: () => import('@/components/ai-kolors/index.vue'),
|
||||
name: 'aiKolors',
|
||||
meta: { title: '文生图片' }
|
||||
},
|
||||
]
|
||||
},
|
||||
|
||||
|
|
|
@ -16,14 +16,11 @@ const useClassTaskStore = defineStore('classTask',{
|
|||
{value: 6, label: "复合题"},
|
||||
], // 习题查询条件 - 题型
|
||||
entpCourseWorkGroupList: [{
|
||||
Key: -1,
|
||||
Key: 0,
|
||||
Value: '不限',
|
||||
}, {
|
||||
Key: 1,
|
||||
Value: '真题',
|
||||
}, {
|
||||
Key: 0,
|
||||
Value: '非真题',
|
||||
}
|
||||
], // 习题查询条件 - 题源
|
||||
entpCourseWorkYearList: [
|
||||
|
|
|
@ -6,7 +6,7 @@ import { sessionStore } from '@/utils/store'
|
|||
|
||||
// 默认数据
|
||||
const defData = sessionStore.store || {}
|
||||
const exArrs = ['subject']
|
||||
const exArrs = ['subject','env','curr']
|
||||
exArrs.forEach(k => Object.keys(defData).includes(k) && (delete defData[k]))
|
||||
|
||||
// 延时
|
||||
|
|
|
@ -21,6 +21,29 @@ export function getFiles() {
|
|||
}
|
||||
return new Promise(cb)
|
||||
}
|
||||
/**
|
||||
* base64 转 blob
|
||||
*/
|
||||
export function base64ToBlob(base64Data) {
|
||||
const contentType = base64Data?.match(/^data:([^;]+);base64,/)?.[1]||'image/png'
|
||||
// 去除Base64编码数据中的前缀(如"data:image/png;base64,")
|
||||
const byteCharacters = atob(base64Data.split(',')[1]);
|
||||
const byteArrays = [];
|
||||
for (let i = 0; i < byteCharacters.length; i++) {
|
||||
byteArrays.push(byteCharacters.charCodeAt(i));
|
||||
}
|
||||
return new Blob([new Uint8Array(byteArrays)], { type: contentType });
|
||||
}
|
||||
|
||||
export function arrayBufferToBlob(arrayBuffer, contentType) {
|
||||
return new Blob([new Uint8Array(arrayBuffer)], { type: contentType });
|
||||
}
|
||||
|
||||
export function blobToFile(blob, fileName, contentType) {
|
||||
fileName = fileName || 'file'
|
||||
contentType = contentType || blob.type ||'image/png'
|
||||
return new File([blob], fileName, { type: contentType });
|
||||
}
|
||||
|
||||
// ============= 数学公式--相关 ===================
|
||||
/**
|
||||
|
@ -372,5 +395,6 @@ export const dataSetJson = {
|
|||
"课标-高中-英语": "e889fcac9fd011efb22a0242ac140006",
|
||||
"课标-高中-数学": "e03aa4fe9fd011ef91270242ac140006",
|
||||
"课标-高中-地理": "270516829fd111efb13c0242ac140006",
|
||||
"课标-高中-政治": "a7df2b01aafd11ef8bb40242ac140002",
|
||||
"鉴权": "ragflow-IwMDI1MGU2YTU3NjExZWZiNWEzMDI0Mm"
|
||||
}
|
|
@ -97,4 +97,34 @@ const getProgress = async (id) => {
|
|||
}
|
||||
};
|
||||
|
||||
export { createOutline, getBackGround, createPPT, getProgress, createByOutline };
|
||||
const getBackGroundV2 = async () => {
|
||||
try {
|
||||
const response = await req("/api/aipptV2/themeListV2", "GET");
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error("请求失败:", error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
const createPPTV2 = async (data) => {
|
||||
try {
|
||||
const response = await req("/api/aipptV2/createV2", "POST", data);
|
||||
console.log("createOutline response:", response);
|
||||
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error("请求失败:", error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
const getProgressV2 = async (id) => {
|
||||
try {
|
||||
const response = await req(`/api/aipptV2/progressV2?sid=${id}`, "GET");
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error("请求失败:", error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
export { createOutline, getBackGround, createPPT, getProgress, getBackGroundV2, createPPTV2, getProgressV2, createByOutline };
|
||||
|
|
|
@ -216,7 +216,11 @@ export const createWindow = async (type, data) => {
|
|||
win.maximize();
|
||||
// win.setFullScreen(true) // 设置窗口为全屏
|
||||
if (import.meta.env.VITE_SHOW_DEV_TOOLS === 'true') win.webContents.openDevTools() // 打开调试工具
|
||||
eventHandles(type, win) // 事件监听处理
|
||||
let events = {} // 事件处理函数对象
|
||||
Object.keys(data)
|
||||
.filter(k => typeof data[k] === 'function')
|
||||
.forEach(k => events[k] = data[k])
|
||||
eventHandles(type, win, events) // 事件监听处理
|
||||
break
|
||||
}
|
||||
default:
|
||||
|
@ -286,17 +290,20 @@ export function toolWindow(type, {url, isConsole, isWeb=true, option={}}) {
|
|||
* 窗口创建-事件处理
|
||||
* @param {*} type 事件类型
|
||||
* @param {*} win 窗口对象
|
||||
* @param {*} events 事件对象
|
||||
*/
|
||||
const eventHandles = (type, win) => {
|
||||
const eventHandles = (type, win, events) => {
|
||||
const toolState = useToolState() // 获取store状态
|
||||
const winAll = Remote.BrowserWindow.getAllWindows()
|
||||
const mainWin = winAll.find(o => o.type == 'main') // 主窗口对象
|
||||
// 公共方法
|
||||
const publicMethods = ({onClosed}={}) => {
|
||||
const publicMethods = ({onClosed, closed, close}={}) => {
|
||||
// 监听主窗口-关闭事件
|
||||
mainWin.once('close', () => {winPdf=null;win.destroy();})
|
||||
win.on('closed', () => {
|
||||
if(onClosed) onClosed() // 自定义关闭事件
|
||||
if(!!onClosed) onClosed() // 自定义关闭事件
|
||||
if(!!closed) closed() // 自定义关闭事件
|
||||
if(!!close) close() // 自定义关闭事件
|
||||
win = null
|
||||
wins_tool = null
|
||||
winChild=null
|
||||
|
@ -385,8 +392,7 @@ const eventHandles = (type, win) => {
|
|||
win&&win.destroy()
|
||||
});
|
||||
const on = {
|
||||
onClosed: () => {
|
||||
}
|
||||
...events
|
||||
}
|
||||
publicMethods(on) // 加载公共方法
|
||||
break
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -118,7 +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 { listEntpcoursework } from '@/api/education/entpCourseWork'
|
||||
import { listEntpcoursework, listEntpcourseworkLocal } from '@/api/education/entpCourseWork'
|
||||
import { listEvaluationclue } from '@/api/classTask'
|
||||
import { delEntpcoursework, updateEntpcoursework } from "@/api/education/entpCourseWork";
|
||||
|
||||
|
@ -249,27 +249,28 @@ const t = function(name, time) {
|
|||
return new Promise(resolve => {
|
||||
const queryForm = {
|
||||
// 题类
|
||||
worktype: entpCourseWorkQueryParams.worktype.label == '不限' ? '' : entpCourseWorkQueryParams.worktype.label,
|
||||
worktype: entpCourseWorkQueryParams.worktype.label,
|
||||
// 题源 TODO 估计后端没做相应的查询处理 web端也没有返回
|
||||
// workgroup: entpCourseWorkQueryParams.workgroup,
|
||||
workgroup: entpCourseWorkQueryParams.workgroup,
|
||||
// 年份 TODO 估计后端没做相应的查询处理 web端也没有返回
|
||||
// yearStr: entpCourseWorkQueryParams.yearStr !== '-1' ? entpCourseWorkQueryParams.yearStr:'',
|
||||
yearStr: entpCourseWorkQueryParams.yearStr !== '-1' ? entpCourseWorkQueryParams.yearStr:'',
|
||||
// 关键字
|
||||
title: entpCourseWorkQueryParams.keyWord && entpCourseWorkQueryParams.keyWord !== '' ? entpCourseWorkQueryParams.keyWord:'',
|
||||
keyword: entpCourseWorkQueryParams.keyWord && entpCourseWorkQueryParams.keyWord !== '' ? entpCourseWorkQueryParams.keyWord:'',
|
||||
// 课程相关参数
|
||||
edustage: userStore.edustage, // this.userStore.edustage,
|
||||
edusubject: userStore.edusubject, // this.userStore.edusubject,
|
||||
eid: props.bookobj.levelSecondId, // this.activeParams.lession.id,
|
||||
status: "1",
|
||||
editUserId: userStore.userId,
|
||||
//orderby: 'concat(worktype,timestamp) DESC',
|
||||
|
||||
// 分页参数
|
||||
pageNum: paginationParams.pageNum,
|
||||
pageSize: paginationParams.pageSize,
|
||||
// 课程相关参数
|
||||
edustage: userStore.edustage, // this.userStore.edustage,
|
||||
edusubject: userStore.edusubject, // this.userStore.edusubject,
|
||||
evalid: props.bookobj.levelSecondId, // this.activeParams.lession.id,
|
||||
status: "1",
|
||||
edituserid: userStore.userId,
|
||||
orderby: 'concat(worktype,timestamp) DESC',
|
||||
|
||||
}
|
||||
const entpcourseworkres = listEntpcoursework(queryForm);
|
||||
//const entpcourseworkres = listEntpcoursework(queryForm);
|
||||
const entpcourseworkres = listEntpcourseworkLocal(queryForm);
|
||||
|
||||
|
||||
resolve(entpcourseworkres);
|
||||
})
|
||||
|
|
|
@ -152,7 +152,7 @@
|
|||
</el-form-item>
|
||||
</div>
|
||||
<div class="item-cropper-btn">
|
||||
<el-button v-show="isCropper" circle @click="cropperFormItem('workdesc')"></el-button>
|
||||
<el-button v-show="isCropper" circle @click="cropperFormItem('workdesc')"><el-icon><Search /></el-icon></el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@ -252,8 +252,7 @@
|
|||
<el-tag v-else type="danger" style=" margin-left: 10px ">温馨提示:这里 - 号删除的是最后一道题目哟!</el-tag>
|
||||
</el-form-item>
|
||||
<div class="item-cropper-btn-multi">
|
||||
<!-- <el-button v-show="isCropper" circle icon="Search" @click="cropperFormItem('workdesc')"></el-button> -->
|
||||
<el-button v-show="isCropper" circle @click="cropperFormItem('workdesc')">识别</el-button>
|
||||
<el-button v-show="isCropper" circle @click="cropperFormItem('workdesc')"><el-icon><Search /></el-icon></el-button>
|
||||
</div>
|
||||
|
||||
|
||||
|
|
|
@ -113,7 +113,7 @@ const getConversation = (val) => {
|
|||
const getCompletion = async (val) => {
|
||||
try {
|
||||
|
||||
params.prompt = `根据${curNode.edustage}${curNode.edusubject},分析${props.item.name},${val}`
|
||||
params.prompt = `按照${val}的要求,针对${curNode.edustage}${curNode.edusubject}考试 对${curNode.itemtitle}进行教学分析`
|
||||
const { data } = await completion(params)
|
||||
let answer = data.answer
|
||||
msgList.value.push({
|
||||
|
@ -133,8 +133,6 @@ const saveAdjust = (item) =>{
|
|||
}
|
||||
|
||||
|
||||
|
||||
|
||||
const curFile = reactive({})
|
||||
const dataset_id = ref('')
|
||||
const fileList = ref([])
|
||||
|
@ -143,15 +141,15 @@ const getList = () =>{
|
|||
userId: userInfo.userId,
|
||||
dataset_id: dataset_id.value
|
||||
}).then( res =>{
|
||||
fileList.value = [{fileId: '',fileName: '选择文件', id:-1},...res.rows]
|
||||
fileList.value = res.rows
|
||||
Object.assign(curFile, fileList.value[0])
|
||||
params.document_ids = fileList.value[0].docId
|
||||
})
|
||||
}
|
||||
emitter.on('curFile', (item) =>{
|
||||
changeFile(item)
|
||||
})
|
||||
const changeFile = (val) =>{
|
||||
|
||||
Object.assign(curFile, val);
|
||||
params.document_ids = val.docId
|
||||
}
|
||||
|
|
|
@ -51,7 +51,7 @@ watch(() => props.item.answer, (newVal) => {
|
|||
|
||||
const emit = defineEmits(['saveEdit'])
|
||||
const onSave = () =>{
|
||||
editTempResult({id: props.item.reultId, content: textarea.value}).then( res =>{
|
||||
editTempResult({id: props.item.resultId, content: textarea.value}).then( res =>{
|
||||
isDialog.value = false
|
||||
ElMessage.success('操作成功')
|
||||
emit('saveEdit', textarea.value)
|
||||
|
|
|
@ -67,7 +67,7 @@ import AdjustDialog from './adjust-dialog.vue'
|
|||
import keywordDialog from './keyword-dialog.vue';
|
||||
import { sessionStore } from '@/utils/store'
|
||||
import useUserStore from '@/store/modules/user'
|
||||
import { tempSave, completion, modelList, removeChildTemp, tempResult } from '@/api/mode/index'
|
||||
import { tempSave, completion, modelList, removeChildTemp, tempResult, editTempResult } from '@/api/mode/index'
|
||||
import { dataSetJson } from '@/utils/comm.js'
|
||||
import emitter from '@/utils/mitt';
|
||||
|
||||
|
@ -109,7 +109,7 @@ const getTempResult = () => {
|
|||
rows.forEach(el => {
|
||||
if (item.id == el.modelId) {
|
||||
item.answer = el.content
|
||||
item.reultId = el.id
|
||||
item.resultId = el.id
|
||||
}
|
||||
})
|
||||
})
|
||||
|
@ -143,7 +143,7 @@ const getCompletion = async () => {
|
|||
for (let item of childTempList.value) {
|
||||
try {
|
||||
item.loading = true
|
||||
params.prompt = `根据${curNode.edustage}${curNode.edusubject},提炼出${item.prompt}`
|
||||
params.prompt = `按照${item.prompt}的要求,针对${curNode.edustage}${curNode.edusubject}考试 对${curNode.itemtitle}进行教学分析`
|
||||
const { data } = await completion(params)
|
||||
let answer = data.answer
|
||||
item.oldAnswer = answer
|
||||
|
@ -174,16 +174,24 @@ const onSaveTemp = (item) => {
|
|||
const againResult = async (index, item) => {
|
||||
try {
|
||||
childTempList.value[index].loading = true
|
||||
params.prompt = `根据${curNode.edustage}${curNode.edusubject}课标,提炼出${item.name}`
|
||||
params.prompt = `按照${item.name}的要求,针对${curNode.edustage}${curNode.edusubject}考试 对${curNode.itemtitle}进行教学分析`
|
||||
const { data } = await completion(params)
|
||||
let answer = data.answer
|
||||
childTempList.value[index].oldAnswer = answer
|
||||
childTempList.value[index].answer = getResult(answer);
|
||||
onEditSave(childTempList.value[index])
|
||||
} finally {
|
||||
childTempList.value[index].loading = false
|
||||
}
|
||||
}
|
||||
|
||||
// 保存 重新研读后的结果
|
||||
const onEditSave = async (item) =>{
|
||||
const { res } = await editTempResult({id: item.resultId, content: item.oldAnswer})
|
||||
ElMessage.success(res)
|
||||
getChildTemplate()
|
||||
}
|
||||
|
||||
|
||||
// 分析获取课标对话结果
|
||||
let getResult = (text) => {
|
||||
|
|
|
@ -12,6 +12,8 @@
|
|||
<el-button type="info" @click="onchange('/model/design')">教学框架设计</el-button>
|
||||
<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>
|
||||
|
@ -24,13 +26,21 @@
|
|||
<div class="flex justify-between pb-2">
|
||||
<h3>教师资源</h3>
|
||||
<span class="c-btns">
|
||||
<el-button size="small" text :icon="Refresh" @click="handleAll('refresh')">刷新</el-button>
|
||||
<el-button size="small" text :icon="Files" @click="handleAll('resource')">资源库</el-button>
|
||||
<el-button size="small" text :icon="UploadFilled" @click="handleAll('upload')">上传</el-button>
|
||||
<el-button size="small" text :icon="Plus" @click="handleAll('add')">添加</el-button>
|
||||
<template v-for="item in resourBtns">
|
||||
<el-button :size="item.size" text :icon="item.icon" @click="handleAll(item.prop)">{{ item.name }}</el-button>
|
||||
</template>
|
||||
</span>
|
||||
</div>
|
||||
<c-table ref="resourRef" v-bind="sourceOpt" t-class="rounded"></c-table>
|
||||
<c-table ref="resourRef" v-bind="sourceOpt" t-class="rounded">
|
||||
<template #title="{row,value}">
|
||||
<el-link :underline="false" @click="handleAll('open', row)">
|
||||
<svg class="icon svg-icon" aria-hidden="true">
|
||||
<use :xlink:href="`#icon-${getIcon(row)}`"></use>
|
||||
</svg>
|
||||
<b class="ml-1">{{ value }}</b>
|
||||
</el-link>
|
||||
</template>
|
||||
</c-table>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
@ -40,12 +50,20 @@
|
|||
import { onMounted, ref, watch, reactive } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { Plus, Refresh, Upload, Files, UploadFilled } from '@element-plus/icons-vue'
|
||||
import { createWindow } from '@/utils/tool' // 相关工具
|
||||
import * as entpcoursefile from '@/api/education/entpcoursefile' // 相关api
|
||||
import useUserStore from '@/store/modules/user' // 用户信息
|
||||
import msgUtils from '@/plugins/modal' // 消息工具
|
||||
import { createWindow, sessionStore } 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' // 右键菜单
|
||||
|
||||
const router = useRouter()
|
||||
const userStore = useUserStore() // 用户信息
|
||||
|
||||
const courseObj = reactive({
|
||||
// 课程相关参数: 教材id,单元id,章节id,课程名称
|
||||
|
@ -54,39 +72,58 @@ const courseObj = reactive({
|
|||
levelSecondId: '',
|
||||
coursetitle: '',
|
||||
node: null, // 选择的课程节点
|
||||
entp: null, // 当前课程
|
||||
})
|
||||
const dt = reactive({
|
||||
curRow: null, // 当前行数据
|
||||
menus: [ // 右键菜单
|
||||
{ label: '打开', click: (_, args) => handleAll('open', args) },
|
||||
{ label: '删除', click: (_, args) => handleAll('delete', args) },
|
||||
],
|
||||
})
|
||||
// ref定义
|
||||
const resourRef = ref() // 资源ref
|
||||
// 资源按钮配置
|
||||
const resourBtns = [
|
||||
{ name: '刷新', prop: 'refresh', size: 'small', icon: Refresh },
|
||||
{ name: '资源库', prop:'resource', size:'small', icon: Files },
|
||||
{ name: '上传', prop:'upload', size:'small', icon: UploadFilled },
|
||||
{ name: '添加', prop:'add', size:'small', icon: Plus },
|
||||
]
|
||||
// 资源相关配置-cTable
|
||||
const sourceOpt = reactive({
|
||||
data: [], // 数据
|
||||
option: [ // 列配置
|
||||
{ label: '名称', prop: 'title', align: 'left' },
|
||||
{ label: '类型', prop: 'type' },
|
||||
{ label: '时间', prop: 'createTime', width: 160, sortable: true },
|
||||
{ label: '类型', prop: 'filetype', width: 80, },
|
||||
{ label: '时间', prop: 'timestamp', width: 160, sortable: true },
|
||||
],
|
||||
noPage: true, // 不显示分页
|
||||
isMain: false, // 主界面
|
||||
highlightCurrentRow: true, // 高亮当前行
|
||||
rowClick: (r, c, e) => { // 行点击事件
|
||||
rowClick: (r, c, e) => { // 行点击事件-处理高亮(再次点击取消选中)
|
||||
if (dt.curRow == r) { // 重复点击-取消选中
|
||||
resourRef.value.$refs.table.setCurrentRow()
|
||||
dt.curRow = null
|
||||
} else dt.curRow = r
|
||||
}
|
||||
},
|
||||
rowContextmenu: (r, c, e) => { // 行—右键菜单事件
|
||||
dt.menus.forEach(item => {
|
||||
if(item.label == '打开') item.icon = getIcon(r, 'svg')
|
||||
else if(item.label == '删除') item.icon = getIcon('icon-shanchu', 'class')
|
||||
})
|
||||
menusEvent(e, dt.menus, r)
|
||||
},
|
||||
})
|
||||
|
||||
// 页面加载
|
||||
onMounted(() => {
|
||||
})
|
||||
sourceOpt.data = [
|
||||
{ title: '测试学校' },
|
||||
{ title: '测试学校2' },
|
||||
{ title: '测试学校3' },
|
||||
]
|
||||
|
||||
// 相关方法-methods
|
||||
// 教材选中
|
||||
const changeBook = (data) => {
|
||||
const changeBook = async(data) => {
|
||||
// console.log(data)
|
||||
const { textBook, node } = data
|
||||
let textbookId = textBook.curBookId
|
||||
let levelSecondId = node.id
|
||||
|
@ -106,6 +143,13 @@ const changeBook = (data) => {
|
|||
|
||||
// 头部 教材分析打开外部链接需要当前章节ID
|
||||
localStorage.setItem('unitId', JSON.stringify({ levelFirstId, levelSecondId }))
|
||||
// 获取当前章节对应的课程信息
|
||||
const params = { evalid: node.id, edituserid: userStore.id, pageSize: 1 }
|
||||
const res = await HTTP_SERVER_API('getCourseList', params)
|
||||
courseObj.entp = res?.rows?.[0] || null
|
||||
sessionStore.set('curr.entp', courseObj.entp) // 缓存当前课程信息
|
||||
// 更新-资源列表
|
||||
getResourceList()
|
||||
}
|
||||
|
||||
const openPPTist = () => {
|
||||
|
@ -116,25 +160,191 @@ 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)
|
||||
}
|
||||
}
|
||||
// 获取资源列表
|
||||
const getResourceList = async () => {
|
||||
const entpcourseidarray = courseObj?.entp?.id
|
||||
if (!entpcourseidarray) return msgUtils.msgWarning('请选择章节?')
|
||||
const params = {
|
||||
pageSize: 100, parentid: 0, entpcourseidarray,
|
||||
orderByColumn: 'timestamp', isAsc: 'desc',
|
||||
}
|
||||
const res = await HTTP_SERVER_API('getCourseFileList', params)
|
||||
if (res?.code == 200) {
|
||||
sourceOpt.data = res?.rows || []
|
||||
} else {
|
||||
msgUtils.msgWarning('获取资源列表, 请重试')
|
||||
}
|
||||
}
|
||||
// 统一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('请选择章节?')
|
||||
const def = { // 默认参数
|
||||
entpid: userStore.user.deptId, // 部门id
|
||||
level: 1, // 层级
|
||||
parentid: 0, // 父级id
|
||||
dictid: 0, // 字典id
|
||||
evalid: node.id, // 章节id
|
||||
evalparentid: node.parentid, // 单元id(父级id)
|
||||
edusubject: node.edusubject, // 学科
|
||||
edudegree: node.edudegree, // 年级
|
||||
edustage: node.edustage, // 阶段
|
||||
coursetype: '课标学科', // 课程类型
|
||||
coursetitle: node.itemtitle, // 课程名称
|
||||
coursedesc: '', // 课程描述
|
||||
status: '', // 状态
|
||||
dflag: 0, // 状态
|
||||
edituserid: userStore.id, // 编辑人id
|
||||
createblankfile: 'no', // 创建空白文件
|
||||
}
|
||||
courseObj.entp = def
|
||||
return API_entpcourse.addEntpcourse(def)
|
||||
}
|
||||
case 'addEntpcoursefile': { // 添加课程文件
|
||||
const enpt = courseObj.entp
|
||||
const def = {
|
||||
parentid: 0,
|
||||
entpid: userStore.user.deptId,
|
||||
entpcourseid: enpt.id,
|
||||
ppttype: 'file',
|
||||
title: enpt.coursetitle,
|
||||
fileurl: '',
|
||||
filetype: 'aptist',
|
||||
datacontent: '',
|
||||
filekey: '',
|
||||
filetag: '',
|
||||
fileidx: 0,
|
||||
dflag: 0,
|
||||
status: '',
|
||||
edituserid: userStore.id
|
||||
}
|
||||
// return Promise.resolve(1)
|
||||
return API_entpcoursefile.addEntpcoursefileReturnId({...def,...params})
|
||||
}
|
||||
case 'getCourseList': { // 获取课程列表
|
||||
return API_entpcourse.listEntpcourse(params)
|
||||
}
|
||||
case 'getCourseFileList':{ // 获取课程文件列表
|
||||
return API_entpcoursefile.listEntpcoursefileNew(params)
|
||||
}
|
||||
}
|
||||
}
|
||||
// 事件回调
|
||||
const handleAll = (type) =>{
|
||||
console.log(type)
|
||||
const handleAll = async(type, row) =>{
|
||||
// console.log(type)
|
||||
switch (type) {
|
||||
case 'refresh': // 刷新
|
||||
getResourceList()
|
||||
break;
|
||||
case 'resource': // 资源库
|
||||
break;
|
||||
case 'upload': // 上传
|
||||
break;
|
||||
case 'add':{ // 添加
|
||||
case 'add':{ // 添加PPT-list - 课程文件
|
||||
const enpt = courseObj.entp // 获取课程信息
|
||||
if (!enpt) { // 如果没有,就新增课程
|
||||
const resid = await HTTP_SERVER_API('addEntpcourse')
|
||||
courseObj.entp.id = resid
|
||||
}
|
||||
// 生成ppt课件-父级
|
||||
const p_params = {parentContent: '{"width":1000,"ratio":0.5625}'}
|
||||
const id = await HTTP_SERVER_API('addEntpcoursefile', p_params)
|
||||
if (!!id??null) { // 生成第一个幻灯片
|
||||
const params = {
|
||||
parentid: id,
|
||||
title: '第一页',
|
||||
filetype: 'slide',
|
||||
datacontent: '{"elements":[],"background":{"type":"solid","color":"#fff"}}' // json内容
|
||||
}
|
||||
// 生成ppt课件-子级(slide)
|
||||
await HTTP_SERVER_API('addEntpcoursefile', params)
|
||||
// 生成备课资源-Smarttalk
|
||||
await HTTP_SERVER_API('addSmarttalk',{fileId: id})
|
||||
// 刷新资源列表
|
||||
await getResourceList()
|
||||
} else {
|
||||
msgUtils.msgWarning('添加失败!')
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'open': { // 打开资源-pptist
|
||||
if (row.filetype != 'aptist') return msgUtils.msgWarning('暂不支持该类型文件操作!')
|
||||
sessionStore.set('curr.resource', row) // 缓存当前资源信息
|
||||
createWindow('open-win', {
|
||||
url: '/pptist', // 窗口关闭时,清除缓存
|
||||
close: () => {
|
||||
sessionStore.set('curr.resource', null) // 清除缓存
|
||||
getResourceList() // 刷新资源列表
|
||||
}
|
||||
})
|
||||
break
|
||||
}
|
||||
case 'delete':{ // 删除资源
|
||||
if (!(row && row.id)) return msgUtils.msgWarning('请选择要删除的资源!')
|
||||
await msgUtils.confirm(`是否确认删除【${row.title}】课程课件?`)
|
||||
await API_entpcoursefile.delEntpcoursefileNew(row.id)
|
||||
msgUtils.msgSuccess('删除成功!')
|
||||
// 刷新资源列表
|
||||
await getResourceList()
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
// icons 处理 type 代表传递svg
|
||||
const getIcon = (o, type) => {
|
||||
let icon = typeof o == 'string' ? o : o?.filetype
|
||||
if (['aptist'].includes(o?.filetype)) icon = 'pptx'
|
||||
if (!!type) { // 其他格式icon
|
||||
switch(type) {
|
||||
case 'svg': // 返回svg格式
|
||||
return `<svg class="icon svg-icon" aria-hidden="true">
|
||||
<use xlink:href="#icon-${icon}"></use>
|
||||
</svg>`
|
||||
case 'class': // class
|
||||
return `<span class="icon iconfont ${icon}"></span>`
|
||||
case 'unicode': // unicode
|
||||
return `<span class="icon iconfont">${icon}</span>`
|
||||
default: // 返回icon-class
|
||||
return `icon-${icon}`
|
||||
}
|
||||
}
|
||||
return icon
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
|
|
@ -0,0 +1,380 @@
|
|||
<template>
|
||||
<div class="ai-container">
|
||||
<el-steps style="max-width:100% " :active="activeStep" align-center>
|
||||
<el-step title="生成大纲" />
|
||||
<el-step title="选择模板" />
|
||||
<el-step title="制作PPT" />
|
||||
</el-steps>
|
||||
<div class="card-box">
|
||||
<el-card class="card2" v-if="activeStep === 0">
|
||||
<div class="paragraphs">
|
||||
{{ outputText }}
|
||||
</div>
|
||||
<el-button style="margin-bottom: 5px;" type="primary" @click="addMessage">从新生成</el-button>
|
||||
<el-button style="margin-bottom: 5px;" type="primary" @click="activeStep = 1">下一步</el-button>
|
||||
</el-card>
|
||||
<el-card v-if="activeStep === 1">
|
||||
<div style="padding-bottom: 10px">ppt模板选择</div>
|
||||
<div class="themes">
|
||||
<div v-for="item in backGroundList" :key="item.key" :style="{
|
||||
padding: '20px',
|
||||
paddingRight: '30px',
|
||||
paddingLeft: '30px',
|
||||
margin: '10px',
|
||||
backgroundColor: getBackgroundColor(item.key),
|
||||
borderRadius: '10px',
|
||||
borderBlock: '10px solid #e6e6e6'
|
||||
}" @click="chooseBackground(item.key)" @mouseenter="changeCursor('pointer')" @mouseleave="changeCursor('default')">
|
||||
{{ item.name }}
|
||||
<br />
|
||||
<img style="width: 150px; height: auto" :src="item.thumbnail" alt="" />
|
||||
</div>
|
||||
</div>
|
||||
<el-row class="el-row">
|
||||
<el-col :span="6" class="el-col">
|
||||
<div class="grid-content-1">
|
||||
<div>自动配图</div>
|
||||
<el-switch v-model="outlineData.is_figure" />
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="6" class="el-col">
|
||||
<div class="grid-content-2">
|
||||
<div>PPT作者名:</div>
|
||||
<el-input v-model="outlineData.author" style="width: 50%" />
|
||||
</div>
|
||||
</el-col>
|
||||
</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>
|
||||
</div>
|
||||
</el-card>
|
||||
<el-card v-if="activeStep === 2">
|
||||
<el-progress :percentage="percentage" type="circle" ></el-progress>
|
||||
</el-card>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted } from "vue";
|
||||
import { creatAIPPT } from '@/utils/talkFile'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import {
|
||||
getBackGround,
|
||||
createPPT,
|
||||
getProgress,
|
||||
} from "@/utils/ppt-request.js";
|
||||
import CryptoJS from "crypto-js"
|
||||
|
||||
import { getSignature } from "@/utils/index.js";
|
||||
|
||||
let appId = "01ec9aa3";
|
||||
let secret = "M2QxMDAxMjYyYTEzODMwMGRkZTQ4NmUy";
|
||||
let apikey = "39d05b269fa229f431a56c21794a8ea5"
|
||||
let timestamp = Math.floor(Date.now() / 1000);
|
||||
let signature = getSignature(appId, secret, timestamp);
|
||||
const { ipcRenderer } = window.electron || {}
|
||||
|
||||
|
||||
const outputText = ref(""); // 用于展示的大纲数据
|
||||
const stagingData = ref([]); //储存的对话数据,用于多轮对话
|
||||
const stagOutputText = ref(""); // 暂存大纲用于拆分
|
||||
let extractedParts = ref([]) // 初步拆分
|
||||
|
||||
let firstArray = ref([]); //大纲的大纲等级数字部分
|
||||
let secondArray = ref([]); //大纲的文字部分
|
||||
|
||||
|
||||
const backGroundList = ref([]);
|
||||
|
||||
const inputTheme = ref("高中语文《沁园春雪》的授课课件"); // 输入的主题
|
||||
const inputRequire = ref("") // 输入的需求
|
||||
const activeStep = ref(0); // 上方进度条
|
||||
const combined = ref('') // 修改完毕的大纲数据,准备传入ppt生成模型
|
||||
|
||||
const treeData = ref([]);
|
||||
const status = ref("init");
|
||||
|
||||
const percentage = ref(0);
|
||||
|
||||
const getBackground = () => {
|
||||
treeData.value = [];
|
||||
getBackGround().then((res) => {
|
||||
console.log(res);
|
||||
backGroundList.value = res;
|
||||
});
|
||||
};
|
||||
|
||||
const getBackgroundColor = (key) => {
|
||||
return outlineData.value.theme === key ? '#83e2b6' : '#f5f5f5';
|
||||
};
|
||||
|
||||
const outlineData = ref({
|
||||
query: '', // 用户要求(最多8000字)
|
||||
theme: 'auto', // ppt生成主题
|
||||
author: 'AIX平台',
|
||||
is_card_note: false, // 是否自动生成ppt演讲备注
|
||||
is_cover_img: false, // 是否自动生成封面
|
||||
is_figure: false, // 是否自动配图
|
||||
}
|
||||
)
|
||||
|
||||
const emit = defineEmits(['addSuccess'])
|
||||
const props = defineProps({
|
||||
dataList: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
}
|
||||
})
|
||||
|
||||
// 将输入数据或返回数据存入记忆中
|
||||
function updateStagingData(role, newData) {
|
||||
stagingData.value.push({ role: role, content: newData });
|
||||
}
|
||||
//大纲直接生成ppt
|
||||
const outlineCreatePPT = () => {
|
||||
const newOutlineData = { ...outlineData.value, };
|
||||
newOutlineData.query = outputText.value;
|
||||
|
||||
createPPT(newOutlineData).then((res) => {
|
||||
console.log(res, "正在生成中");
|
||||
activeStep.value = 2
|
||||
|
||||
const checkProgress = () => {
|
||||
getProgress(res.sid).then((response) => {
|
||||
percentage.value = response.process;
|
||||
if (response && response.pptUrl && response.pptUrl.length > 4) {
|
||||
// window.location.href = response.data.pptUrl;
|
||||
//发消息到主进程,携带名称和URL,将URL下载下来后复制到文件列表并上传到服务
|
||||
// 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)
|
||||
ElMessage.success("生成成功");
|
||||
} else {
|
||||
const sleepTime = 2000;
|
||||
let remainingTime = sleepTime;
|
||||
const intervalId = setInterval(() => {
|
||||
remainingTime -= 100;
|
||||
if (remainingTime <= 0) {
|
||||
clearInterval(intervalId);
|
||||
checkProgress();
|
||||
}
|
||||
}, 100);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
checkProgress();
|
||||
})
|
||||
};
|
||||
|
||||
//初次对话
|
||||
const addMessage = () => {
|
||||
const themeValue = inputTheme.value;
|
||||
const requireValue = inputRequire.value;
|
||||
firstArray.value = []
|
||||
secondArray.value = []
|
||||
extractedParts.value = []
|
||||
stagOutputText.value = ''
|
||||
const combinedString = `请帮我生成一个ppt大纲,主题为:${themeValue}。具体内容要求为:${requireValue}。注意,用三个等级大纲展示,如1. 1.1 1.1.2 2. 2.1这种类型,且按照这种顺序,不要有完全相同数字等级的大纲,不要有目录`
|
||||
updateStagingData("user", combinedString);
|
||||
connectWebSocket(stagingData.value);
|
||||
};
|
||||
|
||||
let ttsWS
|
||||
function connectWebSocket(data) {
|
||||
outputText.value = ""; //清楚展示部分内容
|
||||
status.value = "ttsing";
|
||||
return getWebsocketUrl().then((url) => {
|
||||
ttsWS = new WebSocket(url);
|
||||
ttsWS.onopen = () => {
|
||||
webSocketSend(ttsWS, data);
|
||||
};
|
||||
ttsWS.onmessage = (e) => {
|
||||
result1(e.data);
|
||||
};
|
||||
ttsWS.onerror = (e) => {
|
||||
status.value = "error";
|
||||
console.log("WebSocket error:", e);
|
||||
};
|
||||
ttsWS.onclose = () => {
|
||||
status.value = "init";
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
function getWebsocketUrl() {
|
||||
return new Promise((resolve, reject) => {
|
||||
var apiKey = apikey;
|
||||
var apiSecret = secret;
|
||||
var url = "wss://spark-api.xf-yun.com/v4.0/chat";
|
||||
|
||||
var host = "spark-api.xf-yun.com";
|
||||
var date = new Date().toGMTString();
|
||||
var algorithm = "hmac-sha256";
|
||||
var headers = "host date request-line";
|
||||
var signatureOrigin = `host: ${host}\ndate: ${date}\nGET /v4.0/chat HTTP/1.1`;
|
||||
var signatureSha = CryptoJS.HmacSHA256(signatureOrigin, apiSecret);
|
||||
var signature = CryptoJS.enc.Base64.stringify(signatureSha);
|
||||
|
||||
var authorizationOrigin = `api_key="${apiKey}", algorithm="${algorithm}", headers="${headers}", signature="${signature}"`;
|
||||
var authorization = CryptoJS.enc.Base64.stringify(
|
||||
CryptoJS.enc.Utf8.parse(authorizationOrigin)
|
||||
);
|
||||
|
||||
url = `${url}?authorization=${authorization}&date=${date}&host=${host}`;
|
||||
console.log(url);
|
||||
resolve(url);
|
||||
});
|
||||
}
|
||||
function webSocketSend(ws, data) {
|
||||
const params = {
|
||||
header: {
|
||||
app_id: appId,
|
||||
},
|
||||
parameter: {
|
||||
chat: {
|
||||
domain: "4.0Ultra",
|
||||
temperature: 0.5,
|
||||
max_tokens: 1024,
|
||||
},
|
||||
},
|
||||
payload: {
|
||||
message: {
|
||||
text: data,
|
||||
},
|
||||
},
|
||||
};
|
||||
ws.send(JSON.stringify(params));
|
||||
}
|
||||
|
||||
function result1(resultData) {
|
||||
let jsonData = JSON.parse(resultData);
|
||||
outputText.value += jsonData.payload.choices.text[0].content;
|
||||
const div = document.querySelector('.paragraphs');
|
||||
if (div) {
|
||||
div.scrollTop = div.scrollHeight;
|
||||
}
|
||||
if (jsonData.payload && jsonData.payload.usage) {
|
||||
startExtraction() // 返回完毕后开始拆分大纲
|
||||
console.log(firstArray.value, secondArray.value)
|
||||
activeStep.value = 2
|
||||
updateStagingData("assistant", outputText.value) //返回数据存入记忆池
|
||||
}
|
||||
if (jsonData.header.code !== 0) {
|
||||
alert(`提问失败: ${jsonData.header.code}:${jsonData.header.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
const chooseBackground = (data) => {
|
||||
outlineData.value.theme = data
|
||||
}
|
||||
|
||||
const changeCursor = (cursorStyle) => {
|
||||
document.documentElement.style.cursor = cursorStyle;
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
// let url = "https://bjcdn.openstorage.cn/xinghuo-privatedata/%2Ftmp/apiTempFileba724e0344f74e1480535eedf3ebec661601807661085006275/%E9%87%91%E9%A9%AC%E5%A5%96%E5%B0%B4%E5%B0%AC%E4%BA%8B%E4%BB%B6%E5%88%86%E6%9E%90%E4%B8%8E%E5%BA%94%E5%AF%B9%E7%AD%96%E7%95%A5.pptx"
|
||||
// creatAIPPT(props.currentNode.itemtitle + '.pptx',url, props.uploadData).then((res) => {
|
||||
// emit('addSuccess',res)
|
||||
// })
|
||||
// connectWebSocket("");
|
||||
let url = "https://bjcdn.openstorage.cn/xinghuo-privatedata/%2Ftmp/apiTempFilea2e0342f406e4f89b7524bf421d3fef26331634651754729404/%E9%AB%98%E4%B8%AD%E8%AF%AD%E6%96%87%E3%80%8A%E6%B2%81%E5%9B%AD%E6%98%A5%C2%B7%E9%9B%AA%E3%80%8B%E6%8E%88%E8%AF%BE%E8%A7%A3%E6%9E%90.pptx";
|
||||
emit('addSuccess',{url})
|
||||
props.dataList.filter(item => {
|
||||
inputRequire.value += item.answer
|
||||
})
|
||||
getBackground();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.ai-container {
|
||||
width: 100%;
|
||||
background-color: #f5f7f6;
|
||||
padding: 20px
|
||||
}
|
||||
|
||||
.card-box {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.card1 {
|
||||
padding: 0;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.paragraphs {
|
||||
white-space: pre-wrap;
|
||||
text-align: left;
|
||||
max-height: 60vh;
|
||||
overflow-y: auto;
|
||||
border: 1px solid #409EFF;
|
||||
padding: 10px;
|
||||
margin: 5px
|
||||
}
|
||||
|
||||
.themes {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
height: 250px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.outline {
|
||||
white-space: pre-wrap;
|
||||
text-align: left;
|
||||
|
||||
border: 1px solid #409EFF;
|
||||
padding: 10px;
|
||||
outline-style: none;
|
||||
/* margin: 5px */
|
||||
}
|
||||
|
||||
.outline-row {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.outline-row>.el-col {
|
||||
display: flex;
|
||||
flex-direction: column
|
||||
}
|
||||
|
||||
.outline-row>.el-col>div,
|
||||
.outline-row>.el-col>div>.el-input {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 3px;
|
||||
}
|
||||
|
||||
.item-with-dash {
|
||||
margin-left: 100px
|
||||
}
|
||||
|
||||
.item-with-dash::after {
|
||||
content: "";
|
||||
border-bottom: 1px dashed #000;
|
||||
flex-grow: 1;
|
||||
margin-left: 4px;
|
||||
}
|
||||
|
||||
.grid-content-1 {
|
||||
border-radius: 4px;
|
||||
background-color: #c2dbf3;
|
||||
}
|
||||
|
||||
.grid-content-2 {
|
||||
border-radius: 4px;
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
.el-row {
|
||||
padding: 20px
|
||||
}
|
||||
:deep(.el-card__body){
|
||||
padding: 10px 15px;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,391 @@
|
|||
<template>
|
||||
<div class="ai-container">
|
||||
<el-steps style="max-width:100% " :active="activeStep" align-center>
|
||||
<el-step title="生成大纲" />
|
||||
<el-step title="选择模板" />
|
||||
<el-step title="制作PPT" />
|
||||
</el-steps>
|
||||
<div class="card-box">
|
||||
<el-card class="card2" v-if="activeStep === 0">
|
||||
<div class="paragraphs">
|
||||
{{ outputText }}
|
||||
</div>
|
||||
<el-button style="margin-bottom: 5px;" type="primary" @click="addMessage">从新生成</el-button>
|
||||
<el-button style="margin-bottom: 5px;" type="primary" @click="activeStep = 1">下一步</el-button>
|
||||
</el-card>
|
||||
<el-card v-if="activeStep === 1">
|
||||
<div style="padding-bottom: 10px">ppt模板选择</div>
|
||||
<div class="themes">
|
||||
<div v-for="item in backGroundList" :key="item.templateIndexId" :style="{
|
||||
padding: '5px',
|
||||
paddingRight: '5px',
|
||||
paddingLeft: '5px',
|
||||
margin: '5px',
|
||||
backgroundColor: getBackgroundColor(item.templateIndexId),
|
||||
borderRadius: '10px',
|
||||
borderBlock: '10px solid #e6e6e6'
|
||||
}" @click="chooseBackground(item.templateIndexId)" @mouseenter="changeCursor('pointer')" @mouseleave="changeCursor('default')">
|
||||
{{ item.name }}
|
||||
<img style="width: 150px; height: auto" :src="getBackGroundImg(item.detailImage)" alt="" />
|
||||
</div>
|
||||
</div>
|
||||
<el-row class="el-row">
|
||||
<!-- <el-col :span="6" class="el-col">
|
||||
<div class="grid-content-1">
|
||||
<div>演讲备注</div>
|
||||
<el-switch v-model="outlineData.is_card_note" />
|
||||
</div>
|
||||
</el-col>-->
|
||||
<!-- <el-col :span="6" class="el-col">
|
||||
<div class="grid-content-2">
|
||||
<div>生成封面</div>
|
||||
<el-switch v-model="outlineData.is_cover_img" />
|
||||
</div>
|
||||
</el-col>-->
|
||||
<el-col :span="6" class="el-col">
|
||||
<div class="grid-content-1">
|
||||
<div>自动配图</div>
|
||||
<el-switch v-model="outlineData.isFigure" />
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="6" class="el-col">
|
||||
<div class="grid-content-2">
|
||||
<div>PPT作者名:</div>
|
||||
<el-input v-model="outlineData.author" style="width: 50%" />
|
||||
</div>
|
||||
</el-col>
|
||||
</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>
|
||||
</div>
|
||||
</el-card>
|
||||
<el-card v-if="activeStep === 2">
|
||||
<el-progress :percentage="30" type="circle" v-if="percentage === 30"></el-progress>
|
||||
</el-card>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted } from "vue";
|
||||
import { ElMessage } from 'element-plus'
|
||||
import {
|
||||
getBackGroundV2,
|
||||
createPPTV2,
|
||||
getProgressV2,
|
||||
} from "@/utils/ppt-request.js";
|
||||
import CryptoJS from "crypto-js"
|
||||
|
||||
import { getSignature } from "@/utils/index.js";
|
||||
|
||||
let appId = "01ec9aa3";
|
||||
let secret = "M2QxMDAxMjYyYTEzODMwMGRkZTQ4NmUy";
|
||||
let apikey = "39d05b269fa229f431a56c21794a8ea5"
|
||||
let timestamp = Math.floor(Date.now() / 1000);
|
||||
let signature = getSignature(appId, secret, timestamp);
|
||||
const { ipcRenderer } = window.electron || {}
|
||||
|
||||
|
||||
const outputText = ref(""); // 用于展示的大纲数据
|
||||
const stagingData = ref([]); //储存的对话数据,用于多轮对话
|
||||
const stagOutputText = ref(""); // 暂存大纲用于拆分
|
||||
let extractedParts = ref([]) // 初步拆分
|
||||
|
||||
let firstArray = ref([]); //大纲的大纲等级数字部分
|
||||
let secondArray = ref([]); //大纲的文字部分
|
||||
|
||||
|
||||
const backGroundList = ref([]);
|
||||
|
||||
const inputTheme = ref("高中语文《沁园春雪》的授课课件"); // 输入的主题
|
||||
const inputRequire = ref("") // 输入的需求
|
||||
const activeStep = ref(0); // 上方进度条
|
||||
const combined = ref('') // 修改完毕的大纲数据,准备传入ppt生成模型
|
||||
|
||||
const treeData = ref([]);
|
||||
const status = ref("init");
|
||||
|
||||
const percentage = ref(0);
|
||||
|
||||
const getBackgrounds = () => {
|
||||
treeData.value = [];
|
||||
getBackGroundV2().then((res) => {
|
||||
console.log(res);
|
||||
backGroundList.value = res.records;
|
||||
});
|
||||
};
|
||||
|
||||
const getBackGroundImg = (imgUrlStr) => {
|
||||
return JSON.parse(imgUrlStr).titleCoverImage
|
||||
};
|
||||
|
||||
const outlineData = ref({
|
||||
query: '', // 用户要求(最多8000字)
|
||||
// templateId: 'auto', // ppt生成主题
|
||||
author: 'AIX平台',
|
||||
isFigure: false, // 是否自动配图
|
||||
}
|
||||
)
|
||||
|
||||
const emit = defineEmits(['addSuccess'])
|
||||
const props = defineProps({
|
||||
dataList: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
}
|
||||
})
|
||||
|
||||
// 将输入数据或返回数据存入记忆中
|
||||
function updateStagingData(role, newData) {
|
||||
stagingData.value.push({ role: role, content: newData });
|
||||
}
|
||||
//大纲直接生成ppt
|
||||
const outlineCreatePPT = () => {
|
||||
const newOutlineData = { ...outlineData.value, };
|
||||
newOutlineData.query = outputText.value;
|
||||
|
||||
createPPTV2(newOutlineData).then((res) => {
|
||||
console.log(res, "正在生成中");
|
||||
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;
|
||||
//发消息到主进程,携带名称和URL,将URL下载下来后复制到文件列表并上传到服务
|
||||
// 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)
|
||||
ElMessage.success("生成成功");
|
||||
} else {
|
||||
const sleepTime = 2000;
|
||||
let remainingTime = sleepTime;
|
||||
const intervalId = setInterval(() => {
|
||||
remainingTime -= 100;
|
||||
if (remainingTime <= 0) {
|
||||
clearInterval(intervalId);
|
||||
checkProgress();
|
||||
}
|
||||
}, 100);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
checkProgress();
|
||||
})
|
||||
};
|
||||
|
||||
//初次对话
|
||||
const addMessage = () => {
|
||||
const themeValue = inputTheme.value;
|
||||
const requireValue = inputRequire.value;
|
||||
firstArray.value = []
|
||||
secondArray.value = []
|
||||
extractedParts.value = []
|
||||
stagOutputText.value = ''
|
||||
const combinedString = `请帮我生成一个ppt大纲,主题为:${themeValue}。具体内容要求为:${requireValue}。注意,用三个等级大纲展示,如1. 1.1 1.1.2 2. 2.1这种类型,且按照这种顺序,不要有完全相同数字等级的大纲,不要有目录`
|
||||
updateStagingData("user", combinedString);
|
||||
connectWebSocket(stagingData.value);
|
||||
// activeStep.value = 3
|
||||
};
|
||||
|
||||
let ttsWS
|
||||
function connectWebSocket(data) {
|
||||
outputText.value = ""; //清楚展示部分内容
|
||||
status.value = "ttsing";
|
||||
return getWebsocketUrl().then((url) => {
|
||||
ttsWS = new WebSocket(url);
|
||||
ttsWS.onopen = () => {
|
||||
webSocketSend(ttsWS, data);
|
||||
};
|
||||
ttsWS.onmessage = (e) => {
|
||||
result1(e.data);
|
||||
};
|
||||
ttsWS.onerror = (e) => {
|
||||
status.value = "error";
|
||||
console.log("WebSocket error:", e);
|
||||
};
|
||||
ttsWS.onclose = () => {
|
||||
status.value = "init";
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
const getBackgroundColor = (key) => {
|
||||
return outlineData.value.templateId === key ? '#83e2b6' : '#f5f5f5';
|
||||
};
|
||||
|
||||
function getWebsocketUrl() {
|
||||
return new Promise((resolve, reject) => {
|
||||
var apiKey = apikey;
|
||||
var apiSecret = secret;
|
||||
var url = "wss://spark-api.xf-yun.com/v4.0/chat";
|
||||
|
||||
var host = "spark-api.xf-yun.com";
|
||||
var date = new Date().toGMTString();
|
||||
var algorithm = "hmac-sha256";
|
||||
var headers = "host date request-line";
|
||||
var signatureOrigin = `host: ${host}\ndate: ${date}\nGET /v4.0/chat HTTP/1.1`;
|
||||
var signatureSha = CryptoJS.HmacSHA256(signatureOrigin, apiSecret);
|
||||
var signature = CryptoJS.enc.Base64.stringify(signatureSha);
|
||||
|
||||
var authorizationOrigin = `api_key="${apiKey}", algorithm="${algorithm}", headers="${headers}", signature="${signature}"`;
|
||||
var authorization = CryptoJS.enc.Base64.stringify(
|
||||
CryptoJS.enc.Utf8.parse(authorizationOrigin)
|
||||
);
|
||||
|
||||
url = `${url}?authorization=${authorization}&date=${date}&host=${host}`;
|
||||
console.log(url);
|
||||
resolve(url);
|
||||
});
|
||||
}
|
||||
function webSocketSend(ws, data) {
|
||||
const params = {
|
||||
header: {
|
||||
app_id: appId,
|
||||
},
|
||||
parameter: {
|
||||
chat: {
|
||||
domain: "4.0Ultra",
|
||||
temperature: 0.5,
|
||||
max_tokens: 1024,
|
||||
},
|
||||
},
|
||||
payload: {
|
||||
message: {
|
||||
text: data,
|
||||
},
|
||||
},
|
||||
};
|
||||
ws.send(JSON.stringify(params));
|
||||
}
|
||||
|
||||
function result1(resultData) {
|
||||
let jsonData = JSON.parse(resultData);
|
||||
console.log(jsonData)
|
||||
outputText.value += jsonData.payload.choices.text[0].content;
|
||||
const div = document.querySelector('.paragraphs');
|
||||
if (div) {
|
||||
div.scrollTop = div.scrollHeight;
|
||||
}
|
||||
if (jsonData.payload && jsonData.payload.usage) {
|
||||
updateStagingData("assistant", outputText.value) //返回数据存入记忆池
|
||||
}
|
||||
if (jsonData.header.code !== 0) {
|
||||
alert(`提问失败: ${jsonData.header.code}:${jsonData.header.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
const chooseBackground = (data) => {
|
||||
outlineData.value.templateId = data
|
||||
}
|
||||
|
||||
const changeCursor = (cursorStyle) => {
|
||||
document.documentElement.style.cursor = cursorStyle;
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
// let url = "https://bjcdn.openstorage.cn/xinghuo-privatedata/%2Ftmp/apiTempFileba724e0344f74e1480535eedf3ebec661601807661085006275/%E9%87%91%E9%A9%AC%E5%A5%96%E5%B0%B4%E5%B0%AC%E4%BA%8B%E4%BB%B6%E5%88%86%E6%9E%90%E4%B8%8E%E5%BA%94%E5%AF%B9%E7%AD%96%E7%95%A5.pptx"
|
||||
// creatAIPPT(props.currentNode.itemtitle + '.pptx',url, props.uploadData).then((res) => {
|
||||
// emit('addSuccess',res)
|
||||
// })
|
||||
// connectWebSocket("init");
|
||||
props.dataList.filter(item => {
|
||||
inputRequire.value += item.answer
|
||||
})
|
||||
getBackgrounds();
|
||||
// addMessage()
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.ai-container {
|
||||
width: 100%;
|
||||
background-color: #f5f7f6;
|
||||
padding: 20px
|
||||
}
|
||||
|
||||
.card-box {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.card1 {
|
||||
padding: 0;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.paragraphs {
|
||||
white-space: pre-wrap;
|
||||
text-align: left;
|
||||
max-height: 60vh;
|
||||
overflow-y: auto;
|
||||
border: 1px solid #409EFF;
|
||||
padding: 10px;
|
||||
margin: 5px
|
||||
}
|
||||
|
||||
.themes {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
height: 250px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.outline {
|
||||
white-space: pre-wrap;
|
||||
text-align: left;
|
||||
|
||||
border: 1px solid #409EFF;
|
||||
padding: 10px;
|
||||
outline-style: none;
|
||||
/* margin: 5px */
|
||||
}
|
||||
|
||||
.outline-row {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.outline-row>.el-col {
|
||||
display: flex;
|
||||
flex-direction: column
|
||||
}
|
||||
|
||||
.outline-row>.el-col>div,
|
||||
.outline-row>.el-col>div>.el-input {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 3px;
|
||||
}
|
||||
|
||||
.item-with-dash {
|
||||
margin-left: 100px
|
||||
}
|
||||
|
||||
.item-with-dash::after {
|
||||
content: "";
|
||||
border-bottom: 1px dashed #000;
|
||||
flex-grow: 1;
|
||||
margin-left: 4px;
|
||||
}
|
||||
|
||||
.grid-content-1 {
|
||||
border-radius: 4px;
|
||||
background-color: #c2dbf3;
|
||||
}
|
||||
|
||||
.grid-content-2 {
|
||||
border-radius: 4px;
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
.el-row {
|
||||
padding: 20px
|
||||
}
|
||||
:deep(.el-card__body){
|
||||
padding: 10px 15px;
|
||||
}
|
||||
</style>
|
|
@ -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) // 清除缓存
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 定时器监听
|
||||
|
||||
// ================== 监听 =======================
|
||||
|
|
|
@ -332,6 +332,10 @@ 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('//', '/')
|
||||
// 通知主进程
|
||||
|
@ -341,6 +345,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) => {
|
||||
|
|
|
@ -0,0 +1,43 @@
|
|||
<template>
|
||||
<div>
|
||||
<el-dialog class="ppt-dialog" v-model="model" :show-close="false" width="800" destroy-on-close :top="'3vh'">
|
||||
<template #header="{ close, titleId, titleClass }">
|
||||
<div class="dialog-header">
|
||||
<h4 :id="titleId" :class="titleClass">生成PPT(试验版)</h4>
|
||||
<i class="iconfont icon-guanbi" @click="close"></i>
|
||||
</div>
|
||||
</template>
|
||||
<AiPptist @add-success="addAiPPT" :dataList="dataList"/>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import AiPptist from './ai-pptist.vue';
|
||||
const model = defineModel()
|
||||
const emit = defineEmits(['addSuccess'])
|
||||
const props = defineProps({
|
||||
dataList: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
}
|
||||
})
|
||||
const addAiPPT = (data) => {
|
||||
emit('addSuccess', data)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
:deep(.ppt-dialog){
|
||||
-webkit-app-region: no-drag;
|
||||
}
|
||||
.dialog-header{
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
.icon-guanbi {
|
||||
font-size: 20px;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -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() {
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
</div>
|
||||
<div class="header-right">
|
||||
<el-button type="primary">生成大纲</el-button>
|
||||
<el-button type="danger">生成PPT</el-button>
|
||||
<el-button type="danger" @click="pptDialog = true">生成PPT</el-button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="right-con flex">
|
||||
|
@ -49,6 +49,7 @@
|
|||
</div>
|
||||
<EditDialog v-model="isEdit" :item="curItem" />
|
||||
<AdjustDialog v-model="isAdjust" :item="curItem" />
|
||||
<PptDialog @add-success="addAiPPT" :dataList="resultList" v-model="pptDialog"/>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
|
@ -58,10 +59,24 @@ import emitter from '@/utils/mitt'
|
|||
import EditDialog from './edit-dialog.vue'
|
||||
import AdjustDialog from './adjust-dialog.vue'
|
||||
import { completion, tempResult } from '@/api/mode/index.js'
|
||||
import { dataSetJson } from '@/utils/comm.js'
|
||||
// import { dataSetJson } from '@/utils/comm.js'
|
||||
import * as commUtils from '@/utils/comm.js'
|
||||
import PptDialog from '@/views/prepare/container/pptist-dialog.vue'
|
||||
|
||||
import useUserStore from '@/store/modules/user'
|
||||
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 msgUtils from '@/plugins/modal' // 消息工具
|
||||
|
||||
const userStore = useUserStore()
|
||||
const pptDialog = ref(false)
|
||||
const resultList = ref([])
|
||||
const courseObj = reactive({
|
||||
node: null, // 选择的课程节点
|
||||
})
|
||||
|
||||
emitter.on('changeMode', (item) => {
|
||||
console.log(item, 'item')
|
||||
resultList.value = item.child
|
||||
|
@ -95,6 +110,45 @@ const params = reactive(
|
|||
dataset_id: ''
|
||||
}
|
||||
)
|
||||
|
||||
const addAiPPT = async(res) => {
|
||||
let node = courseObj.node
|
||||
if (!node) return msgUtils.msgWarning('请选择章节?')
|
||||
//TODO res中有PPT地址
|
||||
const params = { evalid: node.id, edituserid: userStore.id, pageSize: 1 }
|
||||
const resEnpt = await HTTP_SERVER_API('getCourseList', params)
|
||||
if (!(resEnpt?.rows?.[0] || null)) { // 创建
|
||||
const resid = await HTTP_SERVER_API('addEntpcourse')
|
||||
courseObj.entp.id = resid
|
||||
} else courseObj.entp = resEnpt?.rows?.[0] || null
|
||||
// 下载PPT 并解析json转换到我们自己数据库
|
||||
fetch(res.url)
|
||||
.then(res => res.arrayBuffer())
|
||||
.then(async buffer => {
|
||||
const resPptJson = await PPTXFileToJson(buffer)
|
||||
const { def, slides, ...content } = resPptJson
|
||||
// 转换图片|音频|视频 为线上地址
|
||||
for( let o of slides ) {
|
||||
await toRousrceUrl(o)
|
||||
}
|
||||
// return
|
||||
// 生成ppt课件-父级
|
||||
const p_params = {parentContent: JSON.stringify(content)}
|
||||
const parentid = await HTTP_SERVER_API('addEntpcoursefile', p_params)
|
||||
if (!!parentid??null) { // 生成内容幻灯片
|
||||
if (slides.length > 0) {
|
||||
const resSlides = slides.map(({id, ...slide}) => JSON.stringify(slide))
|
||||
const params = {parentid, filetype: 'slide', title: '', slides: resSlides }
|
||||
const res_3 = await HTTP_SERVER_API('batchAddNew', params)
|
||||
if (res_3 && res_3.code == 200) {
|
||||
msgUtils.msgSuccess('生成PPT课件成功')
|
||||
} else {
|
||||
msgUtils.msgWarning('生成PPT课件失败')
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
const conversation = async () => {
|
||||
for (let item of resultList.value) {
|
||||
item.loading = true
|
||||
|
@ -148,15 +202,107 @@ emitter.on('changeResult', (item) => {
|
|||
resultList.value[curIndex.value].answer = item
|
||||
})
|
||||
|
||||
// ======== zdg start ============
|
||||
// 统一HTTP处理
|
||||
const HTTP_SERVER_API = (type, params = {}) => {
|
||||
switch (type) {
|
||||
case 'addEntpcourse': { // 添加课程
|
||||
const node = courseObj.node || {}
|
||||
if (!node) return msgUtils.msgWarning('请选择章节?')
|
||||
const def = { // 默认参数
|
||||
entpid: userStore.user.deptId, // 部门id
|
||||
level: 1, // 层级
|
||||
parentid: 0, // 父级id
|
||||
dictid: 0, // 字典id
|
||||
evalid: node.id, // 章节id
|
||||
evalparentid: node.parentid, // 单元id(父级id)
|
||||
edusubject: node.edusubject, // 学科
|
||||
edudegree: node.edudegree, // 年级
|
||||
edustage: node.edustage, // 阶段
|
||||
coursetype: '课标学科', // 课程类型
|
||||
coursetitle: node.itemtitle, // 课程名称
|
||||
coursedesc: '', // 课程描述
|
||||
status: '', // 状态
|
||||
dflag: 0, // 状态
|
||||
edituserid: userStore.id, // 编辑人id
|
||||
createblankfile: 'no', // 创建空白文件
|
||||
}
|
||||
courseObj.entp = def
|
||||
return API_entpcourse.addEntpcourse(def)
|
||||
}
|
||||
case 'addEntpcoursefile': { // 添加课程文件
|
||||
params = getDefParams(params)
|
||||
return API_entpcoursefile.addEntpcoursefileReturnId(params)
|
||||
}
|
||||
case 'batchAddNew': { // 批量添加课程文件
|
||||
params = getDefParams(params)
|
||||
return API_entpcoursefile.batchAddNew(params)
|
||||
}
|
||||
case 'getCourseList': { // 获取课程列表
|
||||
return API_entpcourse.listEntpcourse(params)
|
||||
}
|
||||
case 'getCourseFileList':{ // 获取课程文件列表
|
||||
return API_entpcoursefile.listEntpcoursefileNew(params)
|
||||
}
|
||||
}
|
||||
}
|
||||
// 获取默认参数
|
||||
const getDefParams = (params) => {
|
||||
const enpt = courseObj.entp
|
||||
const def = {
|
||||
parentid: 0,
|
||||
entpid: userStore.user.deptId,
|
||||
entpcourseid: enpt.id,
|
||||
ppttype: 'file',
|
||||
title: enpt.coursetitle,
|
||||
fileurl: '',
|
||||
filetype: 'aptist',
|
||||
datacontent: '',
|
||||
filekey: '',
|
||||
filetag: '',
|
||||
fileidx: 0,
|
||||
dflag: 0,
|
||||
status: '',
|
||||
edituserid: userStore.id
|
||||
}
|
||||
return Object.assign(def, params)
|
||||
}
|
||||
// 图片|音频|视频 转换为在线地址
|
||||
const toRousrceUrl = async(o) => {
|
||||
if (!!o.src) { // 如果有src就转换
|
||||
const isBase64 = /^data:image\/(\w+);base64,/.test(o.src)
|
||||
const isBlobUrl = /^blob:/.test(o.src)
|
||||
console.log('isBase64', o, isBase64)
|
||||
if (isBase64) {
|
||||
const bolb = commUtils.base64ToBlob(o.src)
|
||||
const fileName = Date.now() + '.png'
|
||||
const file = commUtils.blobToFile(bolb, fileName)
|
||||
// o.src = fileName
|
||||
// console.log('file', file)
|
||||
const formData = new FormData()
|
||||
formData.append('file', file)
|
||||
const res = await Api_server.Other.uploadFile(formData)
|
||||
if (res && res.code == 200){
|
||||
const url = res?.url
|
||||
url &&(o.src = url)
|
||||
}
|
||||
} else if (isBlobUrl) { // 视频和音频
|
||||
|
||||
}
|
||||
}
|
||||
if (o?.background?.image) await toRousrceUrl(o.background.image)
|
||||
if (o?.elements) o.elements.forEach(async o => {await toRousrceUrl(o)})
|
||||
}
|
||||
// ======== zdg end ============
|
||||
|
||||
const curNode = reactive({})
|
||||
onMounted(() => {
|
||||
let data = sessionStore.get('subject.curNode')
|
||||
Object.assign(curNode, data);
|
||||
courseObj.node = data
|
||||
|
||||
let jsonKey = `课标-${data.edustage}-${data.edusubject}`
|
||||
params.dataset_id = dataSetJson[jsonKey]
|
||||
params.dataset_id = commUtils.dataSetJson[jsonKey]
|
||||
})
|
||||
|
||||
|
||||
|
|
Loading…
Reference in New Issue