Merge branch 'main' into zouyf_dev
This commit is contained in:
commit
5a49cde417
|
@ -35,7 +35,7 @@ export default defineConfig({
|
|||
target: 'http://27.128.240.72:7865',
|
||||
// target: 'https://prev.ysaix.com:7868/prod-api/',
|
||||
// target: 'http://36.134.181.164:7863',
|
||||
// target: 'http://192.168.0.102:7865',
|
||||
// target: 'http://192.168.2.237:7865',
|
||||
changeOrigin: true,
|
||||
rewrite: (p) => p.replace(/^\/dev-api/, '')
|
||||
},
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "aix-win-ws",
|
||||
"version": "2.5.14",
|
||||
"version": "2.5.15",
|
||||
"description": "",
|
||||
"main": "./out/main/index.js",
|
||||
"author": "上海交大重庆人工智能研究院",
|
||||
|
@ -35,12 +35,14 @@
|
|||
"@electron/remote": "^2.1.2",
|
||||
"@element-plus/icons-vue": "^2.3.1",
|
||||
"@icon-park/vue-next": "^1.4.2",
|
||||
"@kangc/v-md-editor": "^2.3.18",
|
||||
"@tinymce/tinymce-vue": "5.1.1",
|
||||
"@vitejs/plugin-vue-jsx": "^4.0.0",
|
||||
"@vue-office/docx": "^1.6.2",
|
||||
"@vue-office/excel": "^1.7.11",
|
||||
"@vue-office/pdf": "^2.0.2",
|
||||
"@vueuse/core": "^10.11.0",
|
||||
"aix-plugins-aitools": "^1.1.0",
|
||||
"animate.css": "^4.1.1",
|
||||
"circular-json": "^0.5.9",
|
||||
"clipboard": "^2.0.11",
|
||||
|
|
|
@ -359,6 +359,11 @@ export default async function ({ app, shell, BrowserWindow, ipcMain }) {
|
|||
|
||||
//下载文件
|
||||
ipcMain.on('download-file-default', (e, { url, fileName }) => {
|
||||
console.log(url, fileName)
|
||||
if (!url) {
|
||||
e.reply('download-file-default' + fileName, false)
|
||||
return;
|
||||
}
|
||||
createFolder('selfFile')
|
||||
.then(async () => {
|
||||
const browserWindow = BrowserWindow.getFocusedWindow()
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
http-equiv="Content-Security-Policy"
|
||||
content="default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:"
|
||||
/> -->
|
||||
<meta http-equiv="Content-Security-Policy" content="connect-src * blob: data:; frame-src 'self' *; default-src 'self' https://wzyzoss.eos-chongqing-3.cmecloud.cn/; script-src 'self' 'unsafe-eval' http://www.wiris.net 'unsafe-inline'; style-src 'self' 'unsafe-inline' http://www.wiris.net; media-src * blob:;img-src * 'self' data: blob:;font-src 'self' http://www.wiris.net;" />
|
||||
<meta http-equiv="Content-Security-Policy" content="connect-src * blob: data:; frame-src 'self' *; default-src 'self' https://wzyzoss.eos-chongqing-3.cmecloud.cn/; script-src 'self' 'unsafe-eval' http://www.wiris.net 'unsafe-inline'; style-src 'self' 'unsafe-inline' http://www.wiris.net; media-src * blob:;img-src * 'self' data: blob:;font-src 'self' http://www.wiris.net data:;" />
|
||||
|
||||
</head>
|
||||
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
<svg style="width: 1em;height: 1em;vertical-align: middle;fill: currentColor;overflow: hidden;" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="6511">
|
||||
<path d="M512 34.133333c263.466667 0 477.866667 214.4 477.866667 477.866667S775.466667 989.866667 512 989.866667 34.133333 775.466667 34.133333 512 248.533333 34.133333 512 34.133333M512 0C229.333333 0 0 229.333333 0 512s229.333333 512 512 512 512-229.333333 512-512S794.666667 0 512 0z" fill="#87C1FF" p-id="6512"></path><path d="M505.173333 611.62666667c100.053333 0 181.333333-80.64 181.333334-180.053333 0-99.2-81.28-180.053333-181.333334-180.053334-100.053333 0-181.333333 80.64-181.333333 180.053334s81.28 180.053333 181.333333 180.053333zM579.84 661.54666667h-135.893333c-126.293333 0-229.12 102.4-229.12 228.053333v13.44c0 65.706667 101.12 65.706667 229.12 65.706667h135.893333c122.88 0 229.12 0 229.12-65.706667v-13.44c0-125.653333-102.826667-228.053333-229.12-228.053333z" fill="#87C1FF" p-id="6513"></path>
|
||||
</svg>
|
After Width: | Height: | Size: 1006 B |
|
@ -5,14 +5,21 @@
|
|||
</header>
|
||||
<div class="flex material-list" v-loading="loading">
|
||||
<div class="flex material-item" v-for="item in list" :key="item.id" >
|
||||
<div class="flex">
|
||||
<el-image :src="fileUrl(item)" class="img" />
|
||||
<el-text truncated>{{ item.fileShowName }}</el-text>
|
||||
<div class="flex material-item">
|
||||
<el-image v-if="item.fileType.indexOf('image')!=-1" :src="fileUrl(item)" class="img" />
|
||||
<svg v-if="item.fileType.indexOf('video')!=-1" class="icon file-icon" aria-hidden="true" :style="{ 'font-size': 100 + 'px' }">
|
||||
<use :xlink:href="'#icon-video'"></use>
|
||||
</svg>
|
||||
<svg class="icon file-icon" v-if="item.fileType.indexOf('audio')!=-1" aria-hidden="true" :style="{ 'font-size': 100 + 'px' }">
|
||||
<use :xlink:href="'#icon-mp'"></use>
|
||||
</svg>
|
||||
<div class="texts">{{ item.fileShowName }}</div>
|
||||
<!-- <el-text truncated>{{ item.fileShowName }}</el-text> -->
|
||||
</div>
|
||||
<el-button type="primary" @click="onInsert(item)">插入</el-button>
|
||||
<el-button style="margin-left: 10px;" type="primary" @click="onInsert(item)">插入</el-button>
|
||||
</div>
|
||||
<el-empty description="暂无素材" v-if="!list.length" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
|
@ -37,7 +44,7 @@ let params = {
|
|||
pageSize: 500
|
||||
}
|
||||
|
||||
const suffixAry = [ 'jpeg','jpg','png','gif','mp3','mp4','avi','mov']
|
||||
const suffixAry = [ 'jpeg','jpg','png','gif','mp3','mp4','avi','mov',"wav"]
|
||||
const videoSuffix = ['mp3','mp4','avi','mov']
|
||||
const list = ref([])
|
||||
const loading = ref(false)
|
||||
|
@ -48,6 +55,7 @@ const init = () => {
|
|||
if(res.rows && res.rows.length){
|
||||
// 过滤出图片和视频
|
||||
list.value = res.rows.filter( item => suffixAry.indexOf(getFileSuffix(item.fileShowName)) != -1)
|
||||
console.log(list.value)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -64,16 +72,19 @@ const fileUrl = computed(() => (item) =>{
|
|||
// 插入
|
||||
const onInsert = async (item) =>{
|
||||
loading.value = true
|
||||
const res = await fetch(item.fileFullPath)
|
||||
const bolb = await res.blob()
|
||||
const file = commUtils.blobToFile(bolb, item.fileShowName)
|
||||
|
||||
// const res = await fetch(item.fileFullPath)
|
||||
// const bolb = await res.blob()
|
||||
// const file = commUtils.blobToFile(bolb, item.fileShowName)
|
||||
const data=item.fileFullPath
|
||||
try {
|
||||
const data = await PPTApi.toRousrceUrl(file)
|
||||
console.log('item', item)
|
||||
// const data = await PPTApi.toRousrceUrl(file)
|
||||
if(videoSuffix.indexOf(getFileSuffix(item.fileShowName)) != -1){
|
||||
emit('insertMaterial',{ type: 'video', data })
|
||||
}
|
||||
else{
|
||||
emit('insertMaterial',{ type: 'video', data })
|
||||
}else if(item.fileType.indexOf('audio') != -1){
|
||||
emit('insertMaterial',{ type: 'audio', data })
|
||||
}else{
|
||||
emit('insertMaterial',{ type: 'img', data })
|
||||
}
|
||||
} finally {
|
||||
|
@ -125,11 +136,17 @@ onMounted(() => {
|
|||
align-items: center;
|
||||
margin-bottom: 10px;
|
||||
font-size: 14px;
|
||||
justify-content: space-between;
|
||||
|
||||
.img{
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
margin-right: 20px;
|
||||
}
|
||||
}
|
||||
.texts{
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
max-width: 600px;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -287,6 +287,8 @@ const insertMaterial = async (item: MaterialParams) =>{
|
|||
const { type, data } = item
|
||||
if(type == 'video'){
|
||||
createVideoElement(data)
|
||||
}else if(type == 'audio'){
|
||||
createAudioElement(data)
|
||||
}
|
||||
else{
|
||||
createImageElement(data)
|
||||
|
|
|
@ -47,9 +47,10 @@
|
|||
<IconListView class="tool-btn" v-tooltip="'演讲者视图'" @click="changeViewMode('presenter')" />
|
||||
<IconOffScreenOne class="tool-btn" v-tooltip="'退出全屏'" v-if="fullscreenState" @click="manualExitFullscreen()" />
|
||||
<IconFullScreenOne class="tool-btn" v-tooltip="'进入全屏'" v-else @click="enterFullscreen()" />
|
||||
<Hands class="tool-btn" v-if="classcourse" v-tooltip="'课堂点名'" @click="ToolHandle('named')" />
|
||||
<Share class="tool-btn" v-if="classcourse" v-tooltip="'分享'" @click="ShareCode()" />
|
||||
<IconPower class="tool-btn" v-if="!classcourse" v-tooltip="'结束放映'" @click="exitScreening()" />
|
||||
<IconPower class="tool-btn" v-else v-tooltip="'结束课堂'" @click="exitCourse()" size="30" fill="#d0021b" strokeLinecap="butt" />
|
||||
<Share class="tool-btn" v-if="classcourse" v-tooltip="'分享'" @click="ShareCode()" />
|
||||
</div>
|
||||
<div :class="['tools-icon',{opacity:iconHide}]" @click.stop="toolTrigger('icon')">
|
||||
<circle-double-down v-if="rightToolsVisible" theme="outline" size="30" fill="#409EFF"/>
|
||||
|
@ -76,8 +77,8 @@ import WritingBoardTool from './WritingBoardTool.vue'
|
|||
import CountdownTimer from './CountdownTimer.vue'
|
||||
import emitter from '@/utils/mitt';
|
||||
import Chat from '../../api/chat' // 聊天
|
||||
import { CircleDoubleDown, CircleDoubleUp, Share } from '@icon-park/vue-next' // icon-park 图标库
|
||||
import { ShareCode } from '@/utils/ppt' // ppt相关
|
||||
import { CircleDoubleDown, CircleDoubleUp, Share, Hands } from '@icon-park/vue-next' // icon-park 图标库
|
||||
import { ShareCode, ToolHandle } from '@/utils/ppt' // ppt相关
|
||||
|
||||
const props = defineProps<{
|
||||
changeViewMode: (mode: 'base' | 'presenter') => void
|
||||
|
|
|
@ -10,7 +10,10 @@
|
|||
<IconOffScreenOne class="tool-icon" v-else />
|
||||
<span>{{ fullscreenState ? '退出全屏' : '全屏' }}</span>
|
||||
</div>
|
||||
<div class="tool-btn" @click="ShareCode()"><Share class="tool-icon" /><span>分享</span></div>
|
||||
<template v-if="classcourse">
|
||||
<div class="tool-btn" @click="ToolHandle('named')"><Hands class="tool-icon" /><span>课堂点名</span></div>
|
||||
<div class="tool-btn" @click="ShareCode()"><Share class="tool-icon" /><span>分享</span></div>
|
||||
</template>
|
||||
<Divider class="divider" />
|
||||
<div class="tool-btn" v-if="!classcourse" @click="exitScreening()"><IconPower class="tool-icon" /><span>结束放映</span></div>
|
||||
<div class="tool-btn" v-else @click="exitCourse()" size="30" fill="#d0021b" strokeLinecap="butt"><IconPower class="tool-icon" /><span>结束课堂</span></div>
|
||||
|
@ -79,7 +82,7 @@
|
|||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { Share } from '@icon-park/vue-next' // icon-park 图标库
|
||||
import { Share, Hands } from '@icon-park/vue-next' // icon-park 图标库
|
||||
import { computed, nextTick, onMounted, onUnmounted, ref, watch } from 'vue'
|
||||
import { storeToRefs } from 'pinia'
|
||||
import { useSlidesStore, useClasscourseStore } from '../../store'
|
||||
|
@ -99,7 +102,7 @@ import CountdownTimer from './CountdownTimer.vue'
|
|||
import Divider from '../../components/Divider.vue'
|
||||
import emitter from '@/utils/mitt';
|
||||
import Chat from '../../api/chat' // 聊天
|
||||
import { ShareCode } from '@/utils/ppt' // ppt相关
|
||||
import { ShareCode, ToolHandle } from '@/utils/ppt' // ppt相关
|
||||
|
||||
const props = defineProps<{
|
||||
changeViewMode: (mode: 'base' | 'presenter') => void
|
||||
|
|
|
@ -4,8 +4,7 @@
|
|||
<PresenterView :changeViewMode="changeViewMode" v-else-if="viewMode === 'presenter'" />
|
||||
<!-- 点赞组件 -->
|
||||
<upvote-vue ref="upvoteRef" type="2"></upvote-vue>
|
||||
<!-- <div style="z-index: 999;position: absolute;top:10px">
|
||||
</div> -->
|
||||
|
||||
<!-- 推图上屏弹窗 -->
|
||||
<el-dialog
|
||||
v-model="dialogVisible"
|
||||
|
@ -15,7 +14,7 @@
|
|||
:show-close="false"
|
||||
>
|
||||
<grid-pic ref="gridPicRef" style="height:100%;" @clear="clearchidrenPic"></grid-pic>
|
||||
</el-dialog>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
|
|
@ -79,3 +79,19 @@ export const getModelInfo = (params) => {
|
|||
params
|
||||
})
|
||||
}
|
||||
// 上传tts语音
|
||||
export const aitts = (data) => {
|
||||
return request({
|
||||
url: '/aitts/createTts',
|
||||
method: 'post',
|
||||
data
|
||||
})
|
||||
}
|
||||
// 语音关联课
|
||||
export const addFileToSC = (params) => {
|
||||
return request({
|
||||
url: '/smarttalk/file/addFileToSC',
|
||||
method: 'post',
|
||||
params
|
||||
})
|
||||
}
|
|
@ -114,4 +114,16 @@ export function getShareCode(id) {
|
|||
data: { id }
|
||||
})
|
||||
}
|
||||
/**
|
||||
* 课堂点名
|
||||
* @param {*} timgroupid 群id
|
||||
* @returns
|
||||
*/
|
||||
export function rollCall(timgroupid) {
|
||||
return request({
|
||||
url: '/education/classcourse/roll/call',
|
||||
method: 'post',
|
||||
data: { timgroupid }
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,240 @@
|
|||
<template>
|
||||
<div>
|
||||
<div style="height: auto; display: flex">
|
||||
<div style="flex: 1">
|
||||
<div class="audio-container">
|
||||
<aiAudio
|
||||
@saveClick="saveClick"
|
||||
ref="audioRef"
|
||||
:audioSrc="audioSrc"
|
||||
@auditionClick="auditionClick"
|
||||
></aiAudio>
|
||||
</div>
|
||||
</div>
|
||||
<div style="background: #fff; height: 85vh; min-width: 364px; margin-left: 20px;overflow: hidden;">
|
||||
<div style="padding: 0 20px; height: 100%;">
|
||||
<el-tabs v-model="activeTab" class="prepare-tabs">
|
||||
<el-tab-pane label="素材" name="素材">
|
||||
<div class="prepare-body-header">
|
||||
<div>
|
||||
<label style="font-size: 15px"
|
||||
>共{{
|
||||
currentFileList.filter(
|
||||
(ite) => ite.fileFlag !== 'apt' && ite.fileFlag !== '课件'
|
||||
).length
|
||||
}}个文件</label
|
||||
>
|
||||
<!-- <el-popover placement="top-start" :width="250" trigger="hover">
|
||||
<template #default>
|
||||
<div>
|
||||
<el-button
|
||||
v-if="lastAsyncAllTime"
|
||||
type="success"
|
||||
size="small"
|
||||
:icon="Check"
|
||||
circle
|
||||
/>
|
||||
{{ lastAsyncAllTime ? toTimeText(lastAsyncAllTime) + '同步成功' : '' }}
|
||||
</div>
|
||||
</template>
|
||||
<template #reference>
|
||||
<el-button size="small" text @click="asyncAllFile">
|
||||
<el-icon v-loading="asyncAllFileVisiable">
|
||||
<Refresh />
|
||||
</el-icon>
|
||||
{{ asyncAllFileVisiable ? '同步中' : '云同步' }}
|
||||
</el-button>
|
||||
</template>
|
||||
</el-popover> -->
|
||||
<!-- <el-button size="small" @click="isDialogOpen = true">上传资料</el-button>
|
||||
<el-button size="small" @click="reloadFiles">资源重载</el-button> -->
|
||||
</div>
|
||||
</div>
|
||||
<el-checkbox-group
|
||||
v-model="checkFileList"
|
||||
class="prepare-body-main"
|
||||
:style="{ 'margin-bottom': checkFileList.length > 0 ? '40px' : '0' }"
|
||||
>
|
||||
<file-list-item
|
||||
v-for="(item, index) in currentSCFileList"
|
||||
:key="index"
|
||||
:item="item"
|
||||
:index="index"
|
||||
@on-delete="deleteSuccess"
|
||||
>
|
||||
</file-list-item>
|
||||
</el-checkbox-group>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { useRoute } from 'vue-router'
|
||||
import { getSmarttalkPage, moveSmarttalk, creatAPT } from '@/api/file'
|
||||
import FileListItem from '@/views/prepare/container/file-list-item.vue'
|
||||
import { parseCataByNode, creatPPT, asyncLocalFile, removeLocalFiles } from '@/utils/talkFile'
|
||||
import { uploadPicture } from '@/api/aiGeneratedImage/index.js'
|
||||
import { aitts, addFileToSC } from '@/api/file/index.js'
|
||||
export default {
|
||||
components: {
|
||||
FileListItem
|
||||
},
|
||||
props: {},
|
||||
data() {
|
||||
return {
|
||||
activeTab: '素材',
|
||||
currentFileList: [],
|
||||
uploadData: {},
|
||||
checkFileList: [],
|
||||
isLoading: false,
|
||||
lastAsyncAllTime: '',
|
||||
audioSrc: null,
|
||||
filedata: null
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
isCheckAll() {
|
||||
return (
|
||||
this.checkFileList.length > 0 && this.checkFileList.length === this.currentSCFileList.length
|
||||
)
|
||||
},
|
||||
currentSCFileList() {
|
||||
// return this.currentFileList.filter((item) => item.fileFlag !== 'apt' && item.fileFlag !== '课件')
|
||||
return this.currentFileList.filter((item) => !['apt', 'aippt'].includes(item.fileFlag))
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
deleteSuccess(item) {
|
||||
this.asyncAllFile() // 刷新资源列表
|
||||
},
|
||||
auditionClick(requestData) {
|
||||
this.$refs.audioRef.startLoading = true
|
||||
aitts(requestData)
|
||||
.then((res) => {
|
||||
console.log(res)
|
||||
if (res.code == 200) {
|
||||
this.audioSrc = res.data.fullUrl
|
||||
this.filedata = res.data
|
||||
this.$refs.audioRef.startLoading = false
|
||||
} else {
|
||||
this.$message.error(res.msg)
|
||||
this.$refs.audioRef.startLoading = false
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
this.$refs.audioRef.startLoading = false
|
||||
})
|
||||
},
|
||||
saveClick(requestData) {
|
||||
this.$refs.audioRef.saveLoading = true
|
||||
aitts(requestData)
|
||||
.then((res) => {
|
||||
if (res.code == 200) {
|
||||
this.filedata = res.data
|
||||
const saveObj = {
|
||||
textbookId: this.uploadData.textbookId,
|
||||
levelFirstId: this.uploadData.levelFirstId,
|
||||
levelSecondId: this.uploadData.levelSecondId,
|
||||
fileSource: this.uploadData.fileSource,
|
||||
fileRoot: this.uploadData.fileRoot,
|
||||
fileShowName: this.filedata.uploadTime+'音频.' + this.filedata.fileSuffix,
|
||||
fileFlag: '素材',
|
||||
fileId: this.filedata.id
|
||||
}
|
||||
addFileToSC(saveObj)
|
||||
.then((resone) => {
|
||||
this.$message.success('上传成功')
|
||||
this.asyncAllFile()
|
||||
this.$refs.audioRef.saveLoading = false
|
||||
})
|
||||
.catch((err) => {
|
||||
this.$refs.audioRef.saveLoading = false
|
||||
})
|
||||
} else {
|
||||
this.$message.error(res.msg)
|
||||
this.$refs.audioRef.saveLoading = false
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log(err)
|
||||
this.$refs.audioRef.saveLoading = false
|
||||
})
|
||||
|
||||
},
|
||||
asyncAllFile() {
|
||||
this.isLoading = true
|
||||
return getSmarttalkPage({
|
||||
...this.uploadData,
|
||||
orderByColumn: 'createTime',
|
||||
isAsc: 'desc',
|
||||
pageSize: 500
|
||||
})
|
||||
.then(async (res) => {
|
||||
this.currentFileList = [...res.rows].filter((item) => item.fileType === 'audio/wav')
|
||||
this.isLoading = false
|
||||
this.lastAsyncAllTime = new Date()
|
||||
localStorage.setItem('lastAsyncAllTime', this.lastAsyncAllTime)
|
||||
this.asyncAllFileVisiable = true
|
||||
for (let i = 0; i < this.currentFileList.length; i++) {
|
||||
let item = this.currentFileList[i]
|
||||
if (item.fileFlag === 'apt') continue
|
||||
if (item.fileFlag === 'aippt') continue
|
||||
await asyncLocalFile(item)
|
||||
}
|
||||
this.asyncAllFileVisiable = false
|
||||
return Promise.resolve()
|
||||
})
|
||||
.catch(() => {
|
||||
this.isLoading = false
|
||||
return Promise.resolve()
|
||||
})
|
||||
},
|
||||
onMoveSingleFile(item) {
|
||||
this.moveFile = [item]
|
||||
this.isMoveDialogOpen = true
|
||||
},
|
||||
deleteTalk(item) {
|
||||
let index = this.currentFileList.indexOf(item)
|
||||
this.currentFileList.splice(index, 1)
|
||||
},
|
||||
// 打开布置作业窗口
|
||||
openSet(row) {
|
||||
// 布置推送:row 这里是单个,转为list
|
||||
this.rows = [row]
|
||||
this.setDialog = true
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
const route = useRoute()
|
||||
this.uploadData = route.query
|
||||
// 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.asyncAllFile() // 刷新资源列表
|
||||
},
|
||||
beforeDestroy() {}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped scss>
|
||||
:deep(.prepare-item-info-title) {
|
||||
line-height: 30px;
|
||||
}
|
||||
.prepare-body-main{
|
||||
overflow-y: auto;
|
||||
height: 74vh;
|
||||
}
|
||||
:deep(.audio-container .content-main){
|
||||
height: 85vh !important ;
|
||||
}
|
||||
:deep(.audio-container .content-main div:nth-of-type(1)){
|
||||
display: flex;
|
||||
flex: 1;
|
||||
}
|
||||
</style>
|
|
@ -26,6 +26,7 @@ const getFileTypeIcon = () => {
|
|||
doc: 'icon-word',
|
||||
docx: 'icon-word',
|
||||
mp4: 'icon-video',
|
||||
wav: 'icon-mp',
|
||||
mov: 'icon-mov',
|
||||
avi: 'icon-avi',
|
||||
jpeg: 'icon-jpeg',
|
||||
|
|
|
@ -10,6 +10,15 @@ import './assets/iconfont/iconfont'
|
|||
import 'virtual:windi.css'
|
||||
import request from "@/utils/request";
|
||||
|
||||
//v-md-editor
|
||||
import VMdPreview from '@kangc/v-md-editor/lib/preview';
|
||||
import '@kangc/v-md-editor/lib/style/preview.css';
|
||||
// 引入你所使用的主题 此处以 github 主题为例
|
||||
import githubTheme from '@kangc/v-md-editor/lib/theme/github';
|
||||
import '@kangc/v-md-editor/lib/theme/style/github.css';
|
||||
// highlightjs
|
||||
import hljs from 'highlight.js';
|
||||
|
||||
import { store } from '@/store'
|
||||
import App from './App.vue'
|
||||
import router from './router'
|
||||
|
@ -17,6 +26,9 @@ import log from 'electron-log/renderer' // 渲染进程日志-文件记录
|
|||
import customComponent from '@/components/common' // 自定义组件
|
||||
import plugins from './plugins' // plugins插件
|
||||
import useUserStore from '@/store/modules/user'
|
||||
import aiAudio from 'aix-plugins-aitools' // 文字转语音插件
|
||||
import '../../../node_modules/aix-plugins-aitools/aitools.css'
|
||||
|
||||
|
||||
if(process.env.NODE_ENV != 'development') { // 非开发环境,将日志打印到日志文件
|
||||
Object.assign(console, log.functions) // 渲染进程日志-控制台替换
|
||||
|
@ -39,6 +51,10 @@ app.config.globalProperties.$requestGetJYW = (url,config)=>{
|
|||
import Icon from '@/AixPPTist/src/plugins/icon'
|
||||
import Directive from '@/AixPPTist/src/plugins/directive'
|
||||
|
||||
VMdPreview.use(githubTheme, {
|
||||
Hljs: hljs,
|
||||
});
|
||||
|
||||
app.use(router)
|
||||
.use(store)
|
||||
.use(ElementPlus, { locale: zhLocale })
|
||||
|
@ -46,6 +62,8 @@ app.use(router)
|
|||
.use(plugins)
|
||||
.use(Icon)
|
||||
.use(Directive)
|
||||
.use(aiAudio)
|
||||
.use(VMdPreview)
|
||||
.mount('#app')
|
||||
|
||||
const isStadium = (user) => {
|
||||
|
|
|
@ -96,6 +96,12 @@ export const constantRoutes = [
|
|||
name: 'aiKolors',
|
||||
meta: { title: '文生图片', showBread: true }
|
||||
},
|
||||
{
|
||||
path: 'aiVoice',
|
||||
component: () => import('@/components/ai-voice/index.vue'),
|
||||
name: 'aiVoice',
|
||||
meta: { title: '语音生成', showBread: true }
|
||||
},
|
||||
]
|
||||
},
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/**
|
||||
* ppt 转换为图片
|
||||
* ppt 相关方法
|
||||
*/
|
||||
import { h, render, getCurrentInstance } from 'vue'
|
||||
import { toPng, toJpeg } from 'html-to-image' // 引入html-to-image库
|
||||
|
@ -8,6 +8,7 @@ import ThumbnailSlide from '@/AixPPTist/src/views/components/ThumbnailSlide/inde
|
|||
import { useSlidesStore } from '@/AixPPTist/src/store'
|
||||
import * as ElementPlus from 'element-plus'
|
||||
import { sessionStore } from '@/utils/store' // electron-store 状态管理
|
||||
import { getStaticUrl } from '@/utils/tool' // 工具类
|
||||
import * as Http_Classcourse from '@/api/teaching/classcourse' // api接口
|
||||
|
||||
// 延时
|
||||
|
@ -129,3 +130,81 @@ export const ShareCode = async(code, cb) => {
|
|||
}
|
||||
}).catch(() => {})
|
||||
}
|
||||
|
||||
/**
|
||||
* 提示框
|
||||
* @param {*} msg 内容
|
||||
* @param {*} title 标题
|
||||
* @param {*} option 配置
|
||||
* @param {*} cb 关闭前回调 ({ h, instance, action, done }, done)
|
||||
* @returns
|
||||
*/
|
||||
export const Alert = async (msg, title, option, cb) => {
|
||||
try {
|
||||
if (typeof msg == 'function') msg = await msg(h)
|
||||
return await ElementPlus.ElMessageBox.alert(msg, title, {
|
||||
confirmButtonText: '确认',
|
||||
cancelButtonText: '关闭',
|
||||
showCancelButton: true,
|
||||
beforeClose: (action, instance, done) => {
|
||||
if (action == 'confirm') { // 确认
|
||||
if (!!cb) { // 回调
|
||||
cb({ h, instance, action, done }, done) && done()
|
||||
} else { // 默认确认
|
||||
done()
|
||||
}
|
||||
} else done()
|
||||
},
|
||||
...option,
|
||||
})
|
||||
} catch { }
|
||||
}
|
||||
/**
|
||||
* 课堂工具栏-钩子
|
||||
*/
|
||||
export const ToolType = {
|
||||
/** 分享码 */
|
||||
SHARE_CODE: 'shareCode',
|
||||
/** 课堂点名 */
|
||||
NAMED: 'named',
|
||||
}
|
||||
export const ToolHandle = async(type, data, cb) => {
|
||||
const classcourse = sessionStore.get('curr.classcourse') // 课堂信息
|
||||
switch (type) {
|
||||
case ToolType.SHARE_CODE:
|
||||
return ShareCode(data, cb)
|
||||
case ToolType.NAMED: {
|
||||
if (!classcourse) return ElementPlus.ElMessage.warning('没有课堂信息!')
|
||||
const avatar = getStaticUrl('/icon/avatar.svg')
|
||||
const timgroupid = classcourse.timgroupid
|
||||
if (!timgroupid) return ElementPlus.ElMessage.warning('没有课堂群信息!')
|
||||
// 课堂点名
|
||||
const res = await Http_Classcourse.rollCall(timgroupid)
|
||||
if (!(res && res.code == 200 && res.data)) return ElementPlus.ElMessage.warning('点名失败!')
|
||||
const userList = res?.data || []
|
||||
const refresh = () => { ToolHandle(ToolType.NAMED); return true }
|
||||
Alert(h => {
|
||||
const attr_0 = {style:{display:'flex',gap:'10px', cursor:'pointer'}}
|
||||
const attr_1 = { style:{display:'inline-flex',alignItems:'center',gap:'10px',padding:'5px 10px'}}
|
||||
const attr_2 = { src: avatar, style: { width: '50px', height: '50px' }}
|
||||
const attr_3 = { style: { fontSize: '20px', fontWeight: 'bold', color: '#409EFF' }}
|
||||
const attr_4 = { style: { fontSize: '12px' }}
|
||||
const userList_H = userList.map(o => {
|
||||
attr_1.title = o.name
|
||||
attr_4.style.color = o.online ? '#67C23A' : '#F56C6C'
|
||||
return h('div', attr_1, [
|
||||
h('img', attr_2),
|
||||
h('div', [
|
||||
h('p', attr_3, o.name),
|
||||
h('span', attr_4, o.online?'在线':'离线')
|
||||
])
|
||||
])
|
||||
})
|
||||
return h('div', attr_0, userList_H)
|
||||
}, '课堂点名', { confirmButtonText: '刷新' }, refresh)
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
|
@ -153,7 +153,7 @@ const tools = reactive([{
|
|||
img: 'airobot'
|
||||
},{
|
||||
name: '语音生成',
|
||||
path: '',
|
||||
path: '/model/aiVoice',
|
||||
img: 'aiyuyin'
|
||||
},{
|
||||
name: '文生图片',
|
||||
|
@ -211,7 +211,20 @@ const gotoRoute = (item) => {
|
|||
if (item.path) {
|
||||
if (item.path === '/model/aiKolors') {
|
||||
gotoAiKolors(item.path)
|
||||
}else {
|
||||
}else if(item.path == '/model/aiVoice'){
|
||||
console.log('aiVoice',uploadData.value)
|
||||
const path = '/model/aiVoice'
|
||||
let subjectdata = sessionStore.get('subject.curNode')
|
||||
let datasubject = `课标-${subjectdata.edustage}-${subjectdata.edusubject}`
|
||||
router.push({
|
||||
path,
|
||||
query: {
|
||||
datasetId: dataSetJson[datasubject],
|
||||
coursetitle: currentNode.value.itemtitle,
|
||||
...uploadData.value
|
||||
}
|
||||
});
|
||||
}else {
|
||||
router.push(item.path)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -70,7 +70,8 @@
|
|||
const percentage = ref(0);
|
||||
const outlineCreatePPT = () => {
|
||||
const newOutlineData = { ...outlineData.value, };
|
||||
newOutlineData.outline = props.dataList.outline;
|
||||
let outline = JSON.parse(props.dataList.outline).json
|
||||
newOutlineData.outline = JSON.stringify(outline);
|
||||
newOutlineData.query = "通过传入大纲帮我生成相应的PPT课件"
|
||||
createPPTLoading.value = true;
|
||||
createPptByOutline(newOutlineData).then((res) => {
|
||||
|
|
|
@ -16,31 +16,13 @@
|
|||
</div>
|
||||
</div>
|
||||
<div class="center-con" v-loading="loading">
|
||||
<template v-if="answer.title">
|
||||
<div class="flex justify-between">
|
||||
<span style="font-size: 18px;color: #409eff;">封面页</span>
|
||||
<el-button type="primary" link @click="onEdit(item, -1)">编辑</el-button>
|
||||
</div>
|
||||
<div class="con-item mb-5">
|
||||
<div class="item-name">标题:{{ answer.title }}</div>
|
||||
<div class="item-name">副标题:{{ answer.subTitle }}</div>
|
||||
</div>
|
||||
<div style="font-size: 18px;color: #409eff;">目录页</div>
|
||||
<div class="con-item" v-for="(item, index) in answer.chapters">
|
||||
<div class="item-name">
|
||||
<span>{{ index + 1 }}:{{ item.chapterTitle }}</span>
|
||||
<el-button type="primary" link @click="onEdit(item, index)">编辑</el-button>
|
||||
</div>
|
||||
<div class="item-text">
|
||||
<p v-for="(el, i) in item.chapterContents">{{ index + 1 }} - {{ i + 1 }} : {{ el.chapterTitle }}</p>
|
||||
</div>
|
||||
<!-- <v-md-editor v-if="markeDownAnswer" :model-value="markeDownAnswer" mode="preview"></v-md-editor> -->
|
||||
<v-md-preview v-if="markeDownAnswer" :text="markeDownAnswer"></v-md-preview>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
<el-empty v-else description="请选择符合您需要的教学模式,生成教学大纲" />
|
||||
</div>
|
||||
</div>
|
||||
<EditDialog v-model="isEdit" :item="editItem" :index="editIndex" />
|
||||
<!-- <EditDialog v-model="isEdit" :item="editItem" :index="editIndex" /> -->
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
|
@ -51,7 +33,7 @@ import EditDialog from './edit-dialog.vue'
|
|||
import emitter from '@/utils/mitt'
|
||||
import * as commUtils from '@/utils/comm.js'
|
||||
import { createChart, sendChart } from '@/api/ai/index'
|
||||
import { completion, addSyllabus, removeSyllabus, editSyllabus } from '@/api/mode/index.js'
|
||||
import { completion, addSyllabus, removeSyllabus, editSyllabus, modelList } from '@/api/mode/index.js'
|
||||
import { createOutlineV2 } from '@/utils/ppt-request.js'
|
||||
import useUserStore from '@/store/modules/user'
|
||||
import { cloneDeep } from 'lodash'
|
||||
|
@ -60,7 +42,6 @@ const curMode = ref(2)
|
|||
const isEdit = ref(false)
|
||||
|
||||
const { user } = useUserStore()
|
||||
const aiShow = ref(false)
|
||||
|
||||
const modeOptions = ref([
|
||||
{
|
||||
|
@ -81,12 +62,10 @@ emitter.on('selected', (data) => {
|
|||
// 回显大纲
|
||||
const curItem = reactive({})
|
||||
emitter.on('onShow', (data) => {
|
||||
aiShow.value = false
|
||||
Object.assign(answer, JSON.parse(data.outline))
|
||||
|
||||
let outline = cloneDeep(JSON.parse(data.outline))
|
||||
markeDownAnswer.value = outline.markdown
|
||||
emitter.emit('onResult', cloneDeep(data))
|
||||
Object.assign(curItem, data)
|
||||
curItem.outline = JSON.parse(curItem.outline)
|
||||
emitter.emit('onResult',curItem)
|
||||
})
|
||||
|
||||
|
||||
|
@ -100,7 +79,8 @@ const params = reactive(
|
|||
|
||||
// 研读
|
||||
const loading = ref(false)
|
||||
const answer = reactive({})
|
||||
// const answer = reactive({})
|
||||
const markeDownAnswer = ref('')
|
||||
|
||||
const createAi = async () => {
|
||||
if (selectedData.value.length == 0) {
|
||||
|
@ -109,12 +89,13 @@ const createAi = async () => {
|
|||
}
|
||||
|
||||
let str = selectedData.value.map( item => item.name).join('、')
|
||||
let bookV = curNode.roottitle.split('-')[1] + '版'
|
||||
// let bookV = curNode.roottitle.split('-')[1] + '版'
|
||||
loading.value = true
|
||||
aiShow.value = true
|
||||
try {
|
||||
|
||||
params.prompt = `针对${curNode.edustage}${curNode.edusubject}${bookV}${curNode.itemtitle}这一课,根据以下教学环节:${str},进行课件教学PPT内容设计`
|
||||
try {
|
||||
params.prompt = prompt.value.replace(/{模板名称}/g, str)
|
||||
|
||||
//params.prompt = `针对${curNode.edustage}${curNode.edusubject}${bookV}${curNode.itemtitle}这一课,根据以下教学环节:${str},进行课件教学PPT内容设计,按照幻灯片1、幻灯片2、...的格式`
|
||||
// params.template = item.prompt
|
||||
|
||||
// 教学大模型
|
||||
|
@ -133,65 +114,73 @@ const createAi = async () => {
|
|||
|
||||
data = res.data
|
||||
}
|
||||
|
||||
|
||||
const res = await createOutlineV2({ query: data.answer })
|
||||
|
||||
Object.assign(answer, res.outline)
|
||||
curItem.outline = res.outline
|
||||
markeDownAnswer.value = data.answer
|
||||
let outline = JSON.stringify({
|
||||
json: res.outline,
|
||||
markdown: data.answer
|
||||
})
|
||||
|
||||
|
||||
Object.assign(curItem, {...curItem, outline})
|
||||
|
||||
emitter.emit('onResult', curItem)
|
||||
onSaveTemp(JSON.stringify(res.outline))
|
||||
onSaveTemp(outline)
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 编辑大纲
|
||||
const editItem = reactive({})
|
||||
const editIndex = ref(0)
|
||||
const onEdit = (item, index)=>{
|
||||
// const editItem = reactive({})
|
||||
// const editIndex = ref(0)
|
||||
// const onEdit = (item, index)=>{
|
||||
|
||||
let obj = null
|
||||
if(index == -1){
|
||||
obj = {
|
||||
title: answer.title,
|
||||
subTitle: answer.subTitle
|
||||
}
|
||||
}
|
||||
else{
|
||||
obj = cloneDeep(item)
|
||||
}
|
||||
editIndex.value = index
|
||||
isEdit.value = true
|
||||
Object.assign(editItem, obj)
|
||||
}
|
||||
// let obj = null
|
||||
// if(index == -1){
|
||||
// obj = {
|
||||
// title: answer.title,
|
||||
// subTitle: answer.subTitle
|
||||
// }
|
||||
// }
|
||||
// else{
|
||||
// obj = cloneDeep(item)
|
||||
// }
|
||||
// editIndex.value = index
|
||||
// isEdit.value = true
|
||||
// Object.assign(editItem, obj)
|
||||
// }
|
||||
|
||||
emitter.on('editItem', (item) =>{
|
||||
if(editIndex.value == -1){
|
||||
answer.title = item.title
|
||||
answer.subTitle = item.subTitle
|
||||
}else{
|
||||
answer.chapters[editIndex.value] = item
|
||||
}
|
||||
let data = cloneDeep(curItem)
|
||||
// emitter.on('editItem', (item) =>{
|
||||
// if(editIndex.value == -1){
|
||||
// answer.title = item.title
|
||||
// answer.subTitle = item.subTitle
|
||||
// }else{
|
||||
// answer.chapters[editIndex.value] = item
|
||||
// }
|
||||
// let data = cloneDeep(curItem)
|
||||
|
||||
data.outline = JSON.stringify(cloneDeep(answer))
|
||||
loading.value = true
|
||||
// data.outline = JSON.stringify(cloneDeep(answer))
|
||||
// loading.value = true
|
||||
|
||||
editSyllabus(data).then( res =>{
|
||||
curItem.outline = answer
|
||||
emitter.emit('onResult', curItem)
|
||||
ElMessage.success('操作成功')
|
||||
}).finally( ()=>{
|
||||
loading.value = false
|
||||
})
|
||||
})
|
||||
// editSyllabus(data).then( res =>{
|
||||
// curItem.outline = answer
|
||||
// emitter.emit('onResult', curItem)
|
||||
// ElMessage.success('操作成功')
|
||||
// }).finally( ()=>{
|
||||
// loading.value = false
|
||||
// })
|
||||
// })
|
||||
|
||||
// 保存模板
|
||||
const onSaveTemp = async (answer) => {
|
||||
if (answer == '') return
|
||||
const onSaveTemp = async (outline) => {
|
||||
let modelIds = selectedData.value.map(item => item.id).join(',')
|
||||
const data = {
|
||||
eduId: curNode.id,
|
||||
outline: answer,
|
||||
outline,
|
||||
outlineType: curMode.value == 1 ? 0 : 1,
|
||||
modelIds,
|
||||
sourceType: 1,
|
||||
|
@ -199,10 +188,12 @@ const onSaveTemp = async (answer) => {
|
|||
createUserName: user.nickName
|
||||
}
|
||||
await addSyllabus(data)
|
||||
emitter.emit('getLastInfo')
|
||||
}
|
||||
|
||||
// 删除大纲
|
||||
const delAnswer = () => {
|
||||
if(!curItem.id) return
|
||||
ElMessageBox.confirm(
|
||||
'确定要删除大纲吗?',
|
||||
'温馨提示',
|
||||
|
@ -215,8 +206,11 @@ const delAnswer = () => {
|
|||
.then(async () => {
|
||||
await removeSyllabus(curItem.id)
|
||||
ElMessage.success('操作成功')
|
||||
answer.value = ''
|
||||
markeDownAnswer.value = ''
|
||||
|
||||
emitter.emit('resetSelect')
|
||||
|
||||
// window.location.reload();
|
||||
})
|
||||
.catch(() => {})
|
||||
|
||||
|
@ -232,13 +226,24 @@ const getChartId = () => {
|
|||
})
|
||||
}
|
||||
|
||||
onUnmounted(() => {
|
||||
emitter.off('selected')
|
||||
emitter.off('onShow')
|
||||
emitter.off('editItem')
|
||||
// 查询prompt 替换
|
||||
const prompt = ref('')
|
||||
const getPrompt = async () => {
|
||||
const { rows } = await modelList({ model: 5 })
|
||||
|
||||
let str = rows.find(item => item.name.indexOf('框架设计') != -1).prompt
|
||||
|
||||
str = str.replace('{学段}', curNode.edustage)
|
||||
str = str.replace('{学科}', curNode.edusubject)
|
||||
let bookV = curNode.roottitle + '版'
|
||||
str = str.replace('{教材版本}', bookV)
|
||||
str = str.replace('{课程名称}', `《${curNode.itemtitle}》`)
|
||||
|
||||
prompt.value = str
|
||||
}
|
||||
|
||||
|
||||
|
||||
})
|
||||
|
||||
const curNode = reactive({})
|
||||
onMounted(() => {
|
||||
|
@ -254,6 +259,16 @@ onMounted(() => {
|
|||
getChartId();
|
||||
}
|
||||
|
||||
getPrompt()
|
||||
|
||||
})
|
||||
|
||||
|
||||
onUnmounted(() => {
|
||||
emitter.off('selected')
|
||||
emitter.off('onShow')
|
||||
emitter.off('editItem')
|
||||
|
||||
})
|
||||
</script>
|
||||
|
||||
|
|
|
@ -150,6 +150,10 @@ const resetSelect = () => {
|
|||
|
||||
emitter.on('resetSelect', () => {
|
||||
resetSelect()
|
||||
getSyllabus()
|
||||
})
|
||||
emitter.on('getLastInfo',() =>{
|
||||
getSyllabus()
|
||||
})
|
||||
|
||||
// 点击教学模式
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
<template>
|
||||
<div class="container-right flex">
|
||||
<div class="right-header flex">
|
||||
<span>课件预览</span>
|
||||
<span>预览</span>
|
||||
<div>
|
||||
<el-button type="danger" @click="onCreate">一键生成</el-button>
|
||||
<el-button :disabled="!result?.parentId" @click="openAiPPT">编辑课件</el-button>
|
||||
<el-button :disabled="!result?.outline" type="danger" @click="onCreate">一键生成</el-button>
|
||||
<el-button :disabled="!result?.parentId" @click="openAiPPT">编辑</el-button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="right-con">
|
||||
|
@ -64,6 +64,7 @@ const pgDialog = reactive({ // 弹窗-进度条
|
|||
const pptSlides = ref([])
|
||||
|
||||
emitter.on('onResult', (data)=>{
|
||||
console.log(data)
|
||||
result.value = data
|
||||
if (!!result.value.parentId) {
|
||||
listEntpcoursefileNew({parentid: result.value.parentId}).then(res=>{
|
||||
|
@ -152,8 +153,12 @@ const openAiPPT = async () =>{
|
|||
let parentid = result.value.parentId
|
||||
const res = await getEntpcoursefile(parentid)
|
||||
if (res && res.code === 200) {
|
||||
const smarttalk = getSmarttalkPage({fileId: parentid})
|
||||
openPublicScreen('edit', res.data, smarttalk.resData) // 打开公屏-窗口
|
||||
const smarttalk = await getSmarttalkPage({fileId: parentid})
|
||||
if (smarttalk && smarttalk.rows.length>0) {
|
||||
openPublicScreen('edit', res.data, smarttalk.rows[0]) // 打开公屏-窗口
|
||||
}else {
|
||||
ElMessage.warning(res.msg||'没有查询到课件!')
|
||||
}
|
||||
} else {
|
||||
ElMessage.warning(res.msg||'文件获取异常!')
|
||||
}
|
||||
|
|
|
@ -4,10 +4,10 @@
|
|||
<el-col :span="5">
|
||||
<Left />
|
||||
</el-col>
|
||||
<el-col :span="14">
|
||||
<el-col :span="15">
|
||||
<Center />
|
||||
</el-col>
|
||||
<el-col :span="5">
|
||||
<el-col :span="4">
|
||||
<Right />
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
|
Loading…
Reference in New Issue