Merge pull request 'zdg_dev' (#75) from zdg_dev into main
Reviewed-on: #75
This commit is contained in:
commit
f9b80dd455
|
@ -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"
|
||||
}
|
||||
|
|
|
@ -26,6 +26,8 @@ import Mobile from './views/Mobile/index.vue'
|
|||
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()
|
||||
|
@ -68,19 +70,20 @@ interface Result {
|
|||
}
|
||||
// 获取参数
|
||||
const initLoad: Function = () => {
|
||||
const urlSearch = location.href.split('?')[1]
|
||||
const query = Object.fromEntries(new URLSearchParams(urlSearch))
|
||||
const id: String = query.id
|
||||
// 如果存在就获取pptx幻灯片内容
|
||||
if (!!id) return PPTApi.getSlideList(id)
|
||||
// 获取缓存的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()
|
||||
}
|
||||
|
||||
// 监听幻灯片内容变化
|
||||
watch(() => slidesStore.slides, (newVal, oldVal) => {
|
||||
// 更新幻灯片内容
|
||||
PPTApi.updateSlides(newVal, oldVal)
|
||||
},{ deep: true })
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
|
|
|
@ -51,7 +51,7 @@ export class PPTApi {
|
|||
// 获取所有幻灯片列表
|
||||
static getSlideList(parentid: (Number | String)): Promise<Boolean> {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
const params: object = { parentid }
|
||||
const params: object = { parentid, orderByColumn: 'fileidx', isAsc: 'asc', pageSize: 9999 }
|
||||
const res: Result = await API_entpcoursefile.listEntpcoursefileNew(params)
|
||||
if (res.code === 200) {
|
||||
const slides = (res.rows || []).map(o => {
|
||||
|
@ -111,7 +111,7 @@ export class PPTApi {
|
|||
static async updateSlides(newVal: object, oldVal: object) {
|
||||
const newData = toRaw(newVal)
|
||||
const oldData = toRaw(oldVal)
|
||||
console.log('监听幻灯片数据变化', newData, oldData)
|
||||
// console.log('监听幻灯片数据变化', newData, oldData)
|
||||
if (!(newData&&newData.length)) return // 新数据为空,不需要更新数据
|
||||
else if (!oldData.length) return // 初始加载,旧数据空不需要更新数据
|
||||
|
||||
|
|
|
@ -9,7 +9,6 @@ export default class {
|
|||
// 删除幻灯片
|
||||
static delSlide(id: string): Promise<Boolean> {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
console.log('delSlide', id)
|
||||
const res: Result = await API_entpcoursefile.delEntpcoursefile(id)
|
||||
if (res.code === 200) {
|
||||
resolve(true)
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
/**
|
||||
* @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')
|
||||
/**
|
||||
* @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)
|
||||
}
|
|
@ -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)
|
||||
})
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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')
|
||||
}
|
|
@ -93,6 +93,14 @@ export function batchUpdateNew(data) {
|
|||
data: data
|
||||
})
|
||||
}
|
||||
// zdg: 批量新增pptist - 新
|
||||
export function batchAddNew(data) {
|
||||
return request({
|
||||
url: '/education/entpcoursefile/batch/add',
|
||||
method: 'post',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
// 修改entpcoursefile
|
||||
export function updateFile2Redis(data) {
|
||||
|
@ -110,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) {
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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 };
|
|
@ -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 });
|
||||
}
|
||||
|
||||
// ============= 数学公式--相关 ===================
|
||||
/**
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
<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>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -55,6 +56,7 @@ import * as API_entpcourse from '@/api/education/entpcourse' // 相关api
|
|||
import * as API_entpcoursefile from '@/api/education/entpcoursefile' // 相关api
|
||||
// 组件引入
|
||||
import ChooseTextbook from '@/components/choose-textbook/index.vue'
|
||||
import { menusEvent } from '@/plugins/vue3-menus' // 右键菜单
|
||||
|
||||
const router = useRouter()
|
||||
const userStore = useUserStore() // 用户信息
|
||||
|
@ -70,6 +72,10 @@ const courseObj = reactive({
|
|||
})
|
||||
const dt = reactive({
|
||||
curRow: null, // 当前行数据
|
||||
menus: [ // 右键菜单
|
||||
{ label: '打开', click: (_, args) => handleAll('open', args) },
|
||||
{ label: '删除', click: (_, args) => handleAll('delete', args) },
|
||||
],
|
||||
})
|
||||
// ref定义
|
||||
const resourRef = ref() // 资源ref
|
||||
|
@ -91,12 +97,19 @@ const sourceOpt = reactive({
|
|||
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)
|
||||
},
|
||||
})
|
||||
|
||||
// 页面加载
|
||||
|
@ -220,7 +233,7 @@ const HTTP_SERVER_API = (type, params = {}) => {
|
|||
}
|
||||
// 事件回调
|
||||
const handleAll = async(type, row) =>{
|
||||
console.log(type)
|
||||
// console.log(type)
|
||||
switch (type) {
|
||||
case 'refresh': // 刷新
|
||||
getResourceList()
|
||||
|
@ -255,20 +268,46 @@ const handleAll = async(type, row) =>{
|
|||
break;
|
||||
}
|
||||
case 'open': { // 打开资源-pptist
|
||||
// console.log(row)
|
||||
if (row.filetype != 'aptist') return msgUtils.msgWarning('暂不支持该类型文件!')
|
||||
if (row.filetype != 'aptist') return msgUtils.msgWarning('暂不支持该类型文件操作!')
|
||||
sessionStore.set('curr.resource', row) // 缓存当前资源信息
|
||||
const query = { id: row.id }
|
||||
const queryUrl = new URLSearchParams(query).toString()
|
||||
console.log('打开资源 ', queryUrl)
|
||||
createWindow('open-win', { url: `/pptist?${queryUrl}` })
|
||||
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 处理
|
||||
const getIcon = o => {
|
||||
let icon = o.filetype
|
||||
if (['aptist','PPTX','pptList'].includes(o.filetype)) icon = 'pptx'
|
||||
// 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
|
||||
}
|
||||
|
||||
|
|
|
@ -59,11 +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
|
||||
|
@ -98,9 +111,43 @@ const params = reactive(
|
|||
}
|
||||
)
|
||||
|
||||
const addAiPPT = (res) => {
|
||||
const addAiPPT = async(res) => {
|
||||
let node = courseObj.node
|
||||
if (!node) return msgUtils.msgWarning('请选择章节?')
|
||||
//TODO res中有PPT地址
|
||||
console.log(res)
|
||||
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) {
|
||||
|
@ -155,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