zdg #72

Merged
zhangxuelin merged 21 commits from zdg into main 2024-07-26 14:39:20 +08:00
32 changed files with 1163 additions and 434 deletions
Showing only changes of commit e6a854b1a2 - Show all commits

View File

@ -1,3 +1,3 @@
provider: generic provider: generic
url: https://example.com/auto-updates url: http://localhost:3000/
updaterCacheDirName: electron-app-updater updaterCacheDirName: electron-app-updater

47
electron-builder-test.yml Normal file
View File

@ -0,0 +1,47 @@
appId: com.electron.app
productName: AIx
directories:
buildResources: build
files:
- '!**/.vscode/*'
- '!src/*'
- '!electron.vite.config.{js,ts,mjs,cjs}'
- '!{.eslintignore,.eslintrc.cjs,.prettierignore,.prettierrc.yaml,dev-app-update.yml,CHANGELOG.md,README.md}'
- '!{.env,.env.*,.npmrc,pnpm-lock.yaml}'
asarUnpack:
- resources/**
win:
executableName: AIx
icon: resources/logo2.ico
nsis:
oneClick: false
allowToChangeInstallationDirectory: true
artifactName: ${name}-${version}-setup.${ext}
shortcutName: ${productName}
uninstallDisplayName: ${productName}
createDesktopShortcut: always
mac:
entitlementsInherit: build/entitlements.mac.plist
extendInfo:
- NSCameraUsageDescription: Application requests access to the device's camera.
- NSMicrophoneUsageDescription: Application requests access to the device's microphone.
- NSDocumentsFolderUsageDescription: Application requests access to the user's Documents folder.
- NSDownloadsFolderUsageDescription: Application requests access to the user's Downloads folder.
notarize: false
dmg:
artifactName: ${name}-${version}.${ext}
linux:
target:
- AppImage
- snap
- deb
maintainer: electronjs.org
category: Utility
appImage:
artifactName: ${name}-${version}.${ext}
npmRebuild: false
publish:
provider: generic
url: http://localhost:3000
electronDownload:
mirror: https://npmmirror.com/mirrors/electron/

View File

@ -12,6 +12,7 @@ asarUnpack:
- resources/** - resources/**
win: win:
executableName: AIx executableName: AIx
icon: resources/logo2.ico
nsis: nsis:
oneClick: false oneClick: false
allowToChangeInstallationDirectory: true allowToChangeInstallationDirectory: true
@ -41,6 +42,6 @@ appImage:
npmRebuild: false npmRebuild: false
publish: publish:
provider: generic provider: generic
url: https://example.com/auto-updates url: https://file.ysaix.com:7868/src/assets/smarttalk/
electronDownload: electronDownload:
mirror: https://npmmirror.com/mirrors/electron/ mirror: https://npmmirror.com/mirrors/electron/

View File

@ -28,12 +28,7 @@ export default defineConfig({
// target: 'http://192.168.2.52:7863', // target: 'http://192.168.2.52:7863',
changeOrigin: true, changeOrigin: true,
rewrite: (p) => p.replace(/^\/dev-api/, '') rewrite: (p) => p.replace(/^\/dev-api/, '')
}, }
'/profile': {
target: 'http://192.168.2.52:7863',
ws: true,
changeOrigin: true
},
}, },
}, },
plugins: [vue(), WindiCSS()], plugins: [vue(), WindiCSS()],

View File

@ -13,7 +13,8 @@
"build": "electron-vite build", "build": "electron-vite build",
"postinstall": "electron-builder install-app-deps", "postinstall": "electron-builder install-app-deps",
"build:unpack": "npm run build && electron-builder --dir", "build:unpack": "npm run build && electron-builder --dir",
"build:win": "npm run build && electron-builder --win", "build:test": "npm run build && electron-builder --win --config ./electron-builder-test.yml",
"build:prod": "npm run build && electron-builder --win --config ./electron-builder.yml",
"build:mac": "npm run build && electron-builder --mac", "build:mac": "npm run build && electron-builder --mac",
"build:linux": "npm run build && electron-builder --linux" "build:linux": "npm run build && electron-builder --linux"
}, },
@ -24,6 +25,7 @@
"@vueuse/core": "^10.11.0", "@vueuse/core": "^10.11.0",
"crypto-js": "^4.2.0", "crypto-js": "^4.2.0",
"electron-dl-manager": "^3.0.0", "electron-dl-manager": "^3.0.0",
"electron-log": "^5.1.7",
"electron-updater": "^6.1.7", "electron-updater": "^6.1.7",
"element-plus": "^2.7.6", "element-plus": "^2.7.6",
"fabric": "5.3.0", "fabric": "5.3.0",
@ -31,8 +33,10 @@
"jsencrypt": "^3.3.2", "jsencrypt": "^3.3.2",
"jsondiffpatch": "0.6.0", "jsondiffpatch": "0.6.0",
"pdfjs-dist": "^4.4.168", "pdfjs-dist": "^4.4.168",
"lodash": "^4.17.21",
"pinia": "^2.1.7", "pinia": "^2.1.7",
"pinia-plugin-persistedstate": "^3.2.1", "pinia-plugin-persistedstate": "^3.2.1",
"spark-md5": "^3.0.2",
"vue-cropper": "^1.0.3", "vue-cropper": "^1.0.3",
"vue-router": "^4.4.0" "vue-router": "^4.4.0"
}, },

BIN
resources/logo.ico Normal file

Binary file not shown.

Before

Width:  |  Height:  |  Size: 57 KiB

After

Width:  |  Height:  |  Size: 264 KiB

BIN
resources/logo2.ico Normal file

Binary file not shown.

Before

Width:  |  Height:  |  Size: 59 KiB

After

Width:  |  Height:  |  Size: 264 KiB

View File

@ -1,16 +1,105 @@
import CryptoJS from 'crypto-js' import SparkMD5 from 'spark-md5'
const fs = require('fs') const fs = require('fs')
const path = require('path') const path = require('path')
import { ElectronDownloadManager } from 'electron-dl-manager' import { ElectronDownloadManager } from 'electron-dl-manager'
import { dialog } from 'electron' import { dialog } from 'electron'
import axios from 'axios' import axios from 'axios'
const uploadUrl = import.meta.env.VITE_APP_UPLOAD_API + '/smarttalk/file/upload' const uploadUrl = import.meta.env.VITE_APP_UPLOAD_API + '/smarttalk/file/upload'
const asyncUploadUrl = import.meta.env.VITE_APP_UPLOAD_API + '/smarttalk/file/asyncUpload'
const manager = new ElectronDownloadManager() const manager = new ElectronDownloadManager()
export default async function ({ app, shell, BrowserWindow, ipcMain }) { export default async function ({ app, shell, BrowserWindow, ipcMain }) {
const userDataPath = app.getPath('userData') const userDataPath = app.getPath('userData')
const appRootFilePath = userDataPath + '\\selfFile\\' const appRootFilePath = userDataPath + '\\selfFile\\'
const appTempFilePath = userDataPath + '\\tempFile\\' const appTempFilePath = userDataPath + '\\tempFile\\'
let Spark = new SparkMD5.ArrayBuffer()
ipcMain.on('upload-file-change', (e, { id, fileNewName, cookie, fileType }) => {
let filePath = appRootFilePath + fileNewName
//执行更新,上传文件
let formData = new FormData()
formData.append('id', id)
uploadFileByFS({
url: asyncUploadUrl,
path: filePath,
name: fileNewName,
cookie,
fileType,
formData,
success: (response) => {
e.reply('upload-file-change-success' + fileNewName, {
data: response.data,
md5: formData.md5
})
},
error: (err) => {
console.error('Error uploading file:', err)
}
})
})
/*监听文件改变,如果有改变则返回触发*/
ipcMain.on('listen-file-change', (e, { id, fileNewName, md5, cookie, fileType }) => {
let filePath = appRootFilePath + fileNewName
let uploadId = null
let isOn = false
setInterval(() => {
getFileMD5(filePath).then((md5New) => {
if (md5New !== md5) {
md5 = md5New
if (uploadId) {
clearTimeout(uploadId)
}
if (isOn === false) {
e.reply('listen-file-change-on' + fileNewName)
isOn = true
}
//倒数十秒提交更改,十秒之内有继续修改则重置倒数
uploadId = setTimeout(() => {
//执行更新,上传文件
let formData = new FormData()
formData.append('id', id)
uploadFileByFS({
url: asyncUploadUrl,
path: filePath,
name: fileNewName,
cookie,
fileType,
formData,
success: (response) => {
e.reply('listen-file-change-success' + fileNewName, {
data: response.data,
md5: formData.md5
})
clearTimeout(uploadId)
isOn = false
},
error: (err) => {
console.error('Error uploading file:', err)
}
})
}, 5000)
}
})
}, 1000)
})
function getFileMD5(path) {
return new Promise((resolve, reject) => {
fs.readFile(path, (err, dataFile) => {
if (err) {
reject(err)
return console.error(err)
}
Spark.append(dataFile)
let md5 = Spark.end()
resolve(md5)
})
})
}
/*
* 判断是否有本地文件
* */
ipcMain.on('is-have-local-file', (e, fileNewName) => { ipcMain.on('is-have-local-file', (e, fileNewName) => {
let filePath = appRootFilePath + fileNewName let filePath = appRootFilePath + fileNewName
fs.access(filePath, fs.constants.F_OK, (err) => { fs.access(filePath, fs.constants.F_OK, (err) => {
@ -21,10 +110,39 @@ export default async function ({ app, shell, BrowserWindow, ipcMain }) {
e.reply('is-have-local-file-reply' + fileNewName, true) e.reply('is-have-local-file-reply' + fileNewName, true)
}) })
}) })
/*
* 判断是需要同步本地文件
* */
ipcMain.on('is-async-local-file', (e, { fileNewName, lastModifyTime, md5 }) => {
let filePath = appRootFilePath + fileNewName
fs.access(filePath, fs.constants.F_OK, (err) => {
if (err) {
e.reply('is-async-local-file-reply' + fileNewName, { isAsync: true, type: 'down' })
return
}
getFileMD5(filePath).then((localMd5) => {
if (localMd5 === md5) {
e.reply('is-async-local-file-reply' + fileNewName, { isAsync: false, type: '' })
} else {
const stats = fs.statSync(filePath)
//如果线上时间大于线下时间,就需要从线上下载,否则则需要上传
let time = new Date(lastModifyTime)
if (time > stats.mtime.getTime()) {
e.reply('is-async-local-file-reply' + fileNewName, { isAsync: true, type: 'down' })
} else if (time < stats.mtime.getTime()) {
e.reply('is-async-local-file-reply' + fileNewName, { isAsync: true, type: 'upload' })
}
}
})
})
})
//默认浏览器打开url //默认浏览器打开url
ipcMain.on('open-url-browser', (e, url) => { ipcMain.on('open-url-browser', (e, url) => {
shell.openPath(url) shell.openPath(url)
}) })
//使用默认应用打开本地文件 //使用默认应用打开本地文件
ipcMain.on('open-path-app', (e, destination) => { ipcMain.on('open-path-app', (e, destination) => {
let path = appRootFilePath + destination let path = appRootFilePath + destination
@ -40,65 +158,74 @@ export default async function ({ app, shell, BrowserWindow, ipcMain }) {
}) })
}) })
//复制文件 //导出文件
ipcMain.on('export-file-default', (e, list) => { ipcMain.on('export-file-default', (e, list) => {
exportFile(list, (res) => { exportFile(list, (res) => {
e.reply('export-file-default-reply', res) e.reply('export-file-default-reply', res)
}) })
}) })
function getFileMD5(file) { function uploadFileByFS({ url, path, name, cookie, fileType, formData, success, error }) {
return new Promise((resolve, reject) => { fs.readFile(path, (err, data) => {
const fileReader = new FileReader() if (err) {
fileReader.onload = (e) => { return console.error(err)
const buffer = e.target.result
let md5 = CryptoJS.MD5(buffer).toString()
resolve(md5)
} }
fileReader.readAsArrayBuffer(file) // 配置上传的请求
const config = {
headers: {
'Content-Type': 'multipart/form-data', // 或者其他适合上传文件的Content-Type
Authorization: 'Bearer ' + cookie
}
}
Spark.append(data)
let md5 = Spark.end()
// 使用axios上传文件
let file = new File([data], name, {
type: fileType
})
const stats = fs.statSync(path)
formData.append('file', file)
formData.append('md5', md5)
formData.append('lastModifyTime', stats.mtime.toLocaleString())
axios
.post(url, formData, config)
.then((response) => {
success(response)
})
.catch((errorMsg) => {
error(errorMsg)
})
}) })
} }
/*创建新的ppt文件*/
ipcMain.on('creat-file-default', (e, { name, uploadData, cookie }) => { ipcMain.on('creat-file-default', (e, { name, uploadData, cookie }) => {
createFolder('tempFile').then(() => { createFolder('tempFile').then(() => {
let path = appTempFilePath + name let path = appTempFilePath + name
fs.writeFileSync(path, '', 'utf-8') fs.writeFileSync(path, '', 'utf-8')
// 读取文件 let fileType = 'application/vnd.openxmlformats-officedocument.presentationml.presentation'
fs.readFile(path, (err, data) => { let formData = new FormData()
if (err) { for (let key in uploadData) {
return console.error(err) if (Object.prototype.hasOwnProperty.call(uploadData, key)) {
// 检查是否是对象自身的属性
formData.append(key, uploadData[key])
} }
// 配置上传的请求 }
const config = { formData.append('fileFlag', '教案')
headers: { uploadFileByFS({
'Content-Type': 'multipart/form-data', // 或者其他适合上传文件的Content-Type url: uploadUrl,
Authorization: 'Bearer ' + cookie path,
} name,
cookie,
fileType,
formData,
success: (response) => {
e.reply('creat-file-default-reply', response.data)
console.log('File uploaded successfully:', response.data)
},
error: (err) => {
console.error('Error uploading file:', err)
} }
let md5 = CryptoJS.MD5(data).toString()
let formData = new FormData()
// 使用axios上传文件
let file = new File([data], name, {
type: 'application/vnd.openxmlformats-officedocument.presentationml.presentation'
})
formData.append('file', file)
formData.append('md5',md5)
for (let key in uploadData) {
if (uploadData.hasOwnProperty(key)) { // 检查是否是对象自身的属性
formData.append(key,uploadData[key])
}
}
formData.append("fileFlag","教案")
axios
.post(uploadUrl, formData, config)
.then((response) => {
e.reply('creat-file-default-reply', response.data)
console.log('File uploaded successfully:', response.data)
})
.catch((error) => {
console.error('Error uploading file:', error)
})
}) })
}) })
}) })
@ -191,6 +318,7 @@ export default async function ({ app, shell, BrowserWindow, ipcMain }) {
}) })
}) })
/*导出文件*/
function exportFile(list, callback) { function exportFile(list, callback) {
let win = BrowserWindow.getFocusedWindow() let win = BrowserWindow.getFocusedWindow()
//通过扩展名识别文件类型 //通过扩展名识别文件类型
@ -220,10 +348,12 @@ export default async function ({ app, shell, BrowserWindow, ipcMain }) {
}) })
} }
/*文件是否已经存在*/
function isHaveFile(path) { function isHaveFile(path) {
return fs.existsSync(path) return fs.existsSync(path)
} }
/*判断是否已经存在这个名字的文件,如果已经存在则递增导出*/
function filterCopyFile(path, index = 0) { function filterCopyFile(path, index = 0) {
if (isHaveFile(path) === true) { if (isHaveFile(path) === true) {
index++ index++
@ -234,6 +364,7 @@ export default async function ({ app, shell, BrowserWindow, ipcMain }) {
} }
} }
/*复制文件*/
function copyRelFile(source, destination, callback) { function copyRelFile(source, destination, callback) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const readStream = fs.createReadStream(source) const readStream = fs.createReadStream(source)
@ -256,6 +387,7 @@ export default async function ({ app, shell, BrowserWindow, ipcMain }) {
}) })
} }
/*复制文件*/
function copyFile(source, destination, callback) { function copyFile(source, destination, callback) {
let path = appRootFilePath + destination let path = appRootFilePath + destination
createFolder('selfFile').then(() => { createFolder('selfFile').then(() => {
@ -276,6 +408,7 @@ export default async function ({ app, shell, BrowserWindow, ipcMain }) {
}) })
} }
/*创建文件夹*/
function createFolder(folderName) { function createFolder(folderName) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const folderPath = path.join(userDataPath, folderName) const folderPath = path.join(userDataPath, folderName)

View File

@ -8,11 +8,10 @@ import File from './file'
import remote from '@electron/remote/main' import remote from '@electron/remote/main'
// 第二步: 初始化remote // 第二步: 初始化remote
remote.initialize() remote.initialize()
import updateInit from './update'
File({ app, shell, BrowserWindow, ipcMain }) File({ app, shell, BrowserWindow, ipcMain })
process.env['ELECTRON_DISABLE_SECURITY_WARNINGS'] = 'true'
let mainWindow, loginWindow let mainWindow, loginWindow
//登录窗口 //登录窗口
@ -24,6 +23,7 @@ function createLoginWindow() {
show: false, show: false,
frame: false, frame: false,
autoHideMenuBar: true, autoHideMenuBar: true,
icon: join(__dirname, '../../resources/logo2.ico'),
...(process.platform === 'linux' ? { icon } : {}), ...(process.platform === 'linux' ? { icon } : {}),
webPreferences: { webPreferences: {
preload: join(__dirname, '../preload/index.js'), preload: join(__dirname, '../preload/index.js'),
@ -31,9 +31,17 @@ function createLoginWindow() {
nodeIntegration: true nodeIntegration: true
} }
}) })
const loginURL = is.dev ? `http://localhost:5173/#/login` : `file://${__dirname}/index.html/login` // handleUpdate(loginWindow,ipcMain)
loginWindow.loadURL(loginURL) // const loginURL = is.dev ? `http://localhost:5173/#/login` : `file://${__dirname}/index.html/#/login`
// loginWindow.loadURL(loginURL)
if (is.dev && process.env['ELECTRON_RENDERER_URL']) {
loginWindow.loadURL('http://localhost:5173/#/login')
} else {
loginWindow.loadFile(join(__dirname, '../renderer/index.html'), {hash: 'login'})
updateInit(loginWindow)
}
// loginWindow.webContents.openDevTools()
loginWindow.once('ready-to-show', () => { loginWindow.once('ready-to-show', () => {
loginWindow.show() loginWindow.show()
}) })
@ -48,8 +56,9 @@ function createMainWindow() {
width: 1200, width: 1200,
height: 700, height: 700,
show: false, show: false,
frame: false, frame: false, // 无边框
autoHideMenuBar: true, autoHideMenuBar: true,
icon: join(__dirname, '../../resources/logo2.ico'),
...(process.platform === 'linux' ? { icon } : {}), ...(process.platform === 'linux' ? { icon } : {}),
webPreferences: { webPreferences: {
preload: join(__dirname, '../preload/index.js'), preload: join(__dirname, '../preload/index.js'),
@ -74,7 +83,7 @@ function createMainWindow() {
mainWindow.webContents.openDevTools() mainWindow.webContents.openDevTools()
if (is.dev && process.env['ELECTRON_RENDERER_URL']) { if (is.dev && process.env['ELECTRON_RENDERER_URL']) {
mainWindow.loadURL(process.env['ELECTRON_RENDERER_URL']) mainWindow.loadURL(process.env['ELECTRON_RENDERER_URL'] )
} else { } else {
mainWindow.loadFile(join(__dirname, '../renderer/index.html')) mainWindow.loadFile(join(__dirname, '../renderer/index.html'))
} }
@ -84,47 +93,41 @@ function createMainWindow() {
remote.enable(mainWindow.webContents) remote.enable(mainWindow.webContents)
} }
// 作业窗口相关-开发中 // 作业窗口相关-开发中
let workWindow let linkWindow
function createWork(data) { async function createLinkWin(data) {
if (workWindow) return if (linkWindow) return
workWindow = new BrowserWindow({ linkWindow = new BrowserWindow({
width: 650, width: 650,
height: 500, height: 500,
show: false, show: false,
frame: true, frame: true,
maximizable: true,
autoHideMenuBar: true, autoHideMenuBar: true,
...(process.platform === 'linux' ? { icon } : {}), ...(process.platform === 'linux' ? { icon } : {}),
webPreferences: { webPreferences: {
sandbox: false, sandbox: false,
nodeIntegration: true nodeIntegration: true,
worldSafeExecuteJavaScript: true,
contextIsolation: true
} }
}) })
let cookieDetails = { ...data.cookieData }
workWindow.webContents.session.cookies.set( await linkWindow.webContents.session.cookies.set(cookieDetails).then(()=>{
{ console.log('Cookie is successful');
url: 'https://file.ysaix.com:7868', }).catch( error =>{
name: 'Admin-Token', console.error('Cookie is error', error);
value: data
},
function (error) {
if (error) {
console.error('Set cookie failed:', error)
} else {
console.log('Cookie set successfully.')
}
}
)
workWindow.loadURL(
'https://file.ysaix.com:7868/teaching/classtaskassign?titleName=%E4%BD%9C%E4%B8%9A%E5%B8%83%E7%BD%AE'
)
workWindow.once('ready-to-show', () => {
workWindow.show()
}) })
workWindow.on('closed', () => {
workWindow = null linkWindow.loadURL(data.fullPath)
linkWindow.once('ready-to-show', () => {
linkWindow.show()
linkWindow.maximize()
})
linkWindow.on('closed', () => {
linkWindow = null
}) })
} }
@ -175,17 +178,19 @@ app.on('ready', () => {
createLoginWindow() createLoginWindow()
} }
mainWindow.destroy() mainWindow.destroy()
mainWindow = null
loginWindow.show() loginWindow.show()
loginWindow.focus() loginWindow.focus()
}) })
//打开作业窗口 //打开作业窗口
ipcMain.on('openWork', (e, data) => { ipcMain.on('openWindow', (e, data) => {
createWork(data) createLinkWin(data)
}) })
// 打开-登录窗口 // 打开-登录窗口
createLoginWindow() createLoginWindow()
app.on('activate', function () { app.on('activate', function () {
if (BrowserWindow.getAllWindows().length === 0) createLoginWindow() if (BrowserWindow.getAllWindows().length === 0) createLoginWindow()
}) })

61
src/main/update.js Normal file
View File

@ -0,0 +1,61 @@
import { dialog } from 'electron'
import logger from 'electron-log'
const updateURL = 'http://27.128.240.72:3000/zhuhao/AIx_Smarttalk/releases/tag/V1.0.0%28%E6%B5%8B%E8%AF%95%E7%89%88%29/'
// 主进程中的更新检查
const { autoUpdater } = require('electron-updater')
const updateInit = (win) => {
logger.info('进来了')
// 检查更新
autoUpdater.checkForUpdates()
// 自动下载
autoUpdater.autoDownload = false
// 设置版本更新服务器地址
// autoUpdater.setFeedURL(updateURL)
//监听更新事件
autoUpdater.on('update-available', (info) => {
logger.info('发现新版本')
dialog
.showMessageBox(win,{
type: 'info',
title: '新版本可用',
message: '有一个可用的新版本,要更新吗',
buttons: ['是', '否']
})
.then((result) => {
if (result.response === 0) {
// 用户选择更新,触发下载和安装
autoUpdater.downloadUpdate()
}
})
})
// 没有新版本
autoUpdater.on('update-not-available', () => {
logger.info('没有新版本')
})
// 更新发生错误
autoUpdater.on('error', () => {
logger.error('检查更新失败')
})
// 跟新下载完毕
autoUpdater.on('update-downloaded', () => {
dialog
.showMessageBox({
type: 'info',
title: '更新下载完成',
message: '点击确定重启获取最新内容',
buttons: ['确定']
})
.then(() => {
// 调用 quitAndInstall 来安装更新
autoUpdater.quitAndInstall()
})
})
}
export default updateInit

View File

@ -9,6 +9,13 @@ export const getSmarttalkPage = (params) => {
}) })
} }
export const getPrepareById = (id) => {
return request({
url: '/smarttalk/file/' + id,
method: 'get'
})
}
export function deleteSmarttalk(id) { export function deleteSmarttalk(id) {
return request({ return request({
url: '/smarttalk/file/' + id, url: '/smarttalk/file/' + id,

View File

@ -64,6 +64,8 @@ const defaultProps = {
const curBookId = ref(-1) const curBookId = ref(-1)
// //
const curBookName = ref('') const curBookName = ref('')
//
const curBookImg = ref('')
// //
const volumeOne = ref([]) const volumeOne = ref([])
// //
@ -110,9 +112,10 @@ const getSubjectContent = async () => {
} }
// //
const changeBook = ({ id, itemtitle }) => { const changeBook = ({ id, itemtitle, avartar }) => {
curBookId.value = id curBookId.value = id
curBookName.value = itemtitle curBookName.value = itemtitle
curBookImg.value = BaseUrl + avartar
getTreeData() getTreeData()
setTimeout(() => { setTimeout(() => {
dialogVisible.value = false dialogVisible.value = false
@ -144,7 +147,8 @@ const emitChangeBook = () => {
const data = { const data = {
textBook: { textBook: {
curBookId: curBookId.value, curBookId: curBookId.value,
curBookName: curBookName.value curBookName: curBookName.value,
curBookImg: curBookImg.value
}, },
node: curNode node: curNode
} }
@ -236,6 +240,7 @@ const getSubject = async () => {
// //
curBookName.value = subjectList.value[0].itemtitle curBookName.value = subjectList.value[0].itemtitle
curBookId.value = subjectList.value[0].id curBookId.value = subjectList.value[0].id
curBookImg.value = BaseUrl + subjectList.value[0].avartar
} }
@ -264,7 +269,8 @@ const handleNodeClick = (data, node) => {
let curData = { let curData = {
textBook: { textBook: {
curBookId: curBookId.value, curBookId: curBookId.value,
curBookName: curBookName.value curBookName: curBookName.value,
curBookImg: curBookImg.value
}, },
node: toRaw(currentNode) node: toRaw(currentNode)
} }

View File

@ -1,97 +1,213 @@
<template> <template>
<div class="canvasitem"> <div class="canvasitem">
<canvas v-for="(item,index) in pdfObj.numberOfPdf" :key="index" :id="'pdf-canvas'+(index+1)" :ref="'canvasPdf'+(index+1)" class="pdf-canvas" style="height:100vh;padding-right:10px"></canvas> <div class="pdfAdnFabric">
<canvas id="pdf-fabric"></canvas>
<canvas id="pdf-fabric1" v-if="props.pdfObj.numberOfPdf == 2"></canvas>
</div> </div>
</div>
</template> </template>
<script setup > <script setup >
import { ref, onMounted, watch, reactive,defineProps ,defineExpose,nextTick,defineEmits } from 'vue'; import {
ref,
onMounted,
watch,
reactive,
defineProps,
defineExpose,
nextTick,
defineEmits
} from 'vue'
import { fabric } from 'fabric'
import { ElMessage } from 'element-plus'
import { handleevent, savecanvsStore, initcanvasdata, displayData } from '@/utils/pdfAndFabric'
const props = defineProps({ const props = defineProps({
pdfObj: { pdfObj: {
type: Object, type: Object,
default: { default: {
numberOfPdf:2, //pdf 1 2 numberOfPdf: 2, //pdf 1 2
pdfUrl:null, pdfUrl: null,
numPages:1 numPages: 1
}
} }
}
}) })
const numPages=ref(0) // canvas
const canvsStore = reactive({
id: 'xxxx',
pageArr: []
})
const fabriccanvas = ref(null)
const fabriccanvas1 = ref(null)
//
const numPagesTotal = ref(0)
const imgarr = ref([])
// pdf // pdf
const canvasNumbsValue = ref([]); const canvasNumbsValue = ref([])
const emit = defineEmits(['update:numPages']) const emit = defineEmits(['update:numPagesTotal'])
const renderPage = async (canvasobj) => { const renderPage = async (canvasobj) => {
if(canvasobj.page>numPages.value) return if (canvasobj.page > numPagesTotal.value) return
const pdf = await pdfjsLib.getDocument(props.pdfObj.pdfUrl).promise; const pdf = await pdfjsLib.getDocument(props.pdfObj.pdfUrl).promise
// //
const page1 = await pdf.getPage(canvasobj.page); const page = await pdf.getPage(canvasobj.page)
const viewport1 = page1.getViewport({ scale: 1 }); const viewport = page.getViewport({ scale: 1 })
const canvasElement1 = canvasobj.canvas;
canvasElement1.width = viewport1.width;
canvasElement1.height = viewport1.height;
const renderContext1 = { const canvasElement = canvasobj.canvas
canvasContext:canvasobj.context, canvasElement.width = viewport.width
viewport: viewport1, canvasElement.height = viewport.height
}; const renderContext = {
await page1.render(renderContext1).promise; canvasContext: canvasobj.context,
viewport: viewport
}; }
const updatePage = (canvasobj) => { page.render(renderContext).promise.then((res) => {
renderPage(canvasobj); const img = document.createElement('img')
}; img.src = canvasobj.canvas.toDataURL('image/png')
const loadPdf = async (canvasobj) => { canvasobj.canvas.remove()
imgarr.value.push({ src: img.src, page: canvasobj.page, JSONdata: {}, index: canvasobj.index })
updatePage(canvasobj); img.onload = () => {
//
}; // pdf fabric
const initPdf = async() => { if (props.pdfObj.numberOfPdf == 2) {
// canvas if (canvasobj.index == 0) {
canvasNumbsValue.value.forEach((canvasObj) => { fabriccanvas.value.setWidth(img.width)
const context = canvasObj.context; fabriccanvas.value.setHeight(img.height)
context.clearRect(0, 0, canvasObj.canvas.width, canvasObj.canvas.height); displayData(fabriccanvas, canvsStore, canvasobj, fabric, img)
}); } else {
canvasNumbsValue.value=[] fabriccanvas1.value.setWidth(img.width)
if(props.pdfObj.pdfUrl){ fabriccanvas1.value.setHeight(img.height)
await nextTick(); // DOM displayData(fabriccanvas1, canvsStore, canvasobj, fabric, img)
if(props.pdfObj.numberOfPdf==1){
canvasNumbsValue.value=[{}]
canvasNumbsValue.value[0].canvas = document.getElementById('pdf-canvas1');
canvasNumbsValue.value[0].context = canvasNumbsValue.value[0].canvas.getContext('2d');
canvasNumbsValue.value[0].page =Number(props.pdfObj.numPages)*props.pdfObj.numberOfPdf
await loadPdf(canvasNumbsValue.value[0]);
}else{
for(var i=0;i<props.pdfObj.numberOfPdf;i++){
canvasNumbsValue.value[i] = {};
canvasNumbsValue.value[i].canvas = document.getElementById('pdf-canvas'+(i+1));
canvasNumbsValue.value[i].context = canvasNumbsValue.value[i].canvas.getContext('2d');
if(i==0){
canvasNumbsValue.value[i].page =Number(props.pdfObj.numPages)*props.pdfObj.numberOfPdf-1
}else{
canvasNumbsValue.value[i].page =Number(props.pdfObj.numPages)*props.pdfObj.numberOfPdf
}
await loadPdf(canvasNumbsValue.value[i]);
} }
} } else {
fabriccanvas.value.setWidth(img.width)
fabriccanvas.value.setHeight(img.height)
displayData(fabriccanvas, canvsStore, canvasobj, fabric, img)
}
// console.log(imgarr.value)
img.remove()
} }
// imgarrJSONdatacanvsStore.pageArr
canvsStore.pageArr.forEach((item) => {
if (item.page == canvasobj.page) {
imgarr.value.forEach((img) => {
if (img.page == canvasobj.page) {
img.JSONdata = item.JSONdata
}
})
}
})
})
} }
onMounted( async() => { const updatePage = (canvasobj) => {
const pdf = await pdfjsLib.getDocument(props.pdfObj.pdfUrl).promise; renderPage(canvasobj)
numPages.value = pdf.numPages; }
emit('update:numPages', pdf.numPages); const loadPdf = async (canvasobj) => {
updatePage(canvasobj)
}
const initPdf = async (type = 'default') => {
//
savecanvsStore(imgarr, canvsStore)
// initcanvasdata(fabriccanvas)
// initcanvasdata(fabriccanvas1)
//
if (type == 'restone') {
// canvas
fabriccanvas1.value.clear()
// canvas
fabriccanvas1.value.dispose()
}
// canvas
canvasNumbsValue.value.forEach((canvasObj) => {
const context = canvasObj.context
context.clearRect(0, 0, canvasObj.canvas.width, canvasObj.canvas.height)
})
//
imgarr.value = []
canvasNumbsValue.value = []
if (props.pdfObj.pdfUrl) {
await nextTick() // DOM
if (props.pdfObj.numberOfPdf == 1) {
canvasNumbsValue.value = [{}]
const canvasElement = document.createElement('canvas')
canvasNumbsValue.value[0].canvas = canvasElement
canvasNumbsValue.value[0].context = canvasNumbsValue.value[0].canvas.getContext('2d')
canvasNumbsValue.value[0].page = props.pdfObj.numPages
canvasNumbsValue.value[0].index = 0
await loadPdf(canvasNumbsValue.value[0])
} else {
for (var i = 0; i < props.pdfObj.numberOfPdf; i++) {
canvasNumbsValue.value[i] = {}
const canvasElement = document.createElement('canvas')
canvasNumbsValue.value[i].canvas = canvasElement
canvasNumbsValue.value[i].context = canvasNumbsValue.value[i].canvas.getContext('2d')
//
if (i == 0) {
canvasNumbsValue.value[i].page = props.pdfObj.numPages
} else {
canvasNumbsValue.value[i].page = props.pdfObj.numPages + 1
}
canvasNumbsValue.value[i].index = i
// FabricVue
await loadPdf(canvasNumbsValue.value[i])
}
}
}
}
const initPdfone = async () => {
setTimeout(() => {
fabriccanvas1.value = new fabric.Canvas('pdf-fabric1')
fabriccanvas1.value.isDrawingMode = true
fabriccanvas1.value.freeDrawingBrush.color = 'red'
fabriccanvas1.value.setWidth(595)
}, 0)
initPdf('addOnePage')
}
onMounted(async () => {
try {
const pdf = await pdfjsLib.getDocument(props.pdfObj.pdfUrl).promise
numPagesTotal.value = pdf.numPages
// console.log(pdf)
// fabriccanvas
fabriccanvas.value = new fabric.Canvas('pdf-fabric')
fabriccanvas.value.setWidth(595)
fabriccanvas.value.isDrawingMode = true
fabriccanvas.value.freeDrawingBrush.color = 'red'
fabriccanvas1.value = new fabric.Canvas('pdf-fabric1')
fabriccanvas1.value.isDrawingMode = true
fabriccanvas1.value.freeDrawingBrush.color = 'red'
fabriccanvas1.value.setWidth(595)
emit('update:numPagesTotal', pdf.numPages)
initPdf() initPdf()
}); } catch (error) {
console.log(error)
ElMessage.error('pdf文件错误')
}
// 2canvas
handleevent(fabriccanvas.value, imgarr)
handleevent(fabriccanvas1.value, imgarr, 'two')
})
defineExpose({ defineExpose({
initPdf initPdf,
}); initPdfone
})
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.canvasitem{ .canvasitem {
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;
width: 100%; width: 100%;
justify-content: center justify-content: center;
}
.pdfAdnFabric {
position: relative;
display: flex;
height: 100vh;
align-items: center;
:deep(> div:nth-of-type(1)) {
margin-right: 10px;
}
} }
</style> </style>

View File

@ -79,10 +79,6 @@ const subjectList = ref([])
const allSubject = ref([]) const allSubject = ref([])
const dialogVisible = ref(false) const dialogVisible = ref(false)
watch(() => props.modelValue, (newVal) => {
dialogVisible.value = newVal
})
// //
const changeGrade = ()=>{ const changeGrade = ()=>{
// //
@ -120,8 +116,13 @@ const editUserInfo = async () =>{
emit('onSuccess') emit('onSuccess')
} }
watch(() => props.modelValue, (newVal) => {
dialogVisible.value = newVal
if(newVal){
getSubject()
}
})
onMounted(getSubject)
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@ -138,11 +139,13 @@ onMounted(getSubject)
.dialog-content { .dialog-content {
padding: 30px 20px 10px 30px; padding: 30px 20px 10px 30px;
-webkit-app-region: no-drag;
} }
.dialog-footer{ .dialog-footer{
text-align: center; text-align: center;
padding-bottom: 10px; padding-bottom: 10px;
-webkit-app-region: no-drag;
} }

View File

@ -4,11 +4,12 @@
<el-form> <el-form>
<el-form-item label="文件"> <el-form-item label="文件">
<div class="create-item file-item flex"> <div class="create-item file-item flex">
<el-upload :file-list="fileList" :show-file-list="false" :auto-upload="false" multiple <el-upload :file-list="fileList" :limit="limit" :show-file-list="false" :auto-upload="false" multiple
:on-change="hanleFileChange"> :on-change="hanleFileChange">
<el-button slot="trigger">选择文件</el-button> <el-button slot="trigger">选择文件</el-button>
</el-upload> </el-upload>
<span class="upload-desc">说明一次最多上传5个文件单个文件大小不能大于100M</span> <span class="upload-desc">说明一次最多上传{{ limit }}个文件单个文件大小不能大于100M</span>
<span class="upload-desc">仅支持图片音频视频wordpptpdftxtzip文件</span>
</div> </div>
</el-form-item> </el-form-item>
<el-form-item> <el-form-item>
@ -16,14 +17,15 @@
<div class="file-list-item flex" v-for="(item, index) in fileList" :key="item.uid"> <div class="file-list-item flex" v-for="(item, index) in fileList" :key="item.uid">
<div class="file-name"> <div class="file-name">
<span class="name">标题</span> <span class="name">标题</span>
<FileImage :fileName="item.name" :size="50"/> <FileImage :fileName="item.name" :size="50" />
<el-input class="file-input" v-model="item.fileData.name" placeholder="请输入文件名" /> <el-input class="file-input" v-model="item.fileData.name" placeholder="请输入文件名" />
<span>.{{ getFileSuffix(item.name) }}</span> <span>.{{ getFileSuffix(item.name) }}</span>
</div> </div>
<div class="flex-type flex"> <div class="flex-type flex">
<span class="name">类别</span> <span class="name">类别</span>
<el-select v-model="item.fileData.fileFlag" placeholder="Select" style="width: 100px"> <el-select v-model="item.fileData.fileFlag" placeholder="Select" style="width: 100px">
<el-option v-for="item in resourceType" :key="item.alue" :label="item.label" :value="item.value" /> <el-option v-for="el in resourceType" :key="el.alue" :label="el.label" :value="el.value"
:disabled="checkFile(el, item)" />
</el-select> </el-select>
</div> </div>
@ -58,6 +60,7 @@ const props = defineProps({
}, },
}) })
const dialogValue = ref(false) const dialogValue = ref(false)
const limit = ref(5)
// emit // emit
const emit = defineEmits(['update:modelValue', 'submitFile']) const emit = defineEmits(['update:modelValue', 'submitFile'])
// //
@ -81,9 +84,9 @@ const hanleFileChange = (file) => {
// pdf // pdf
const pdfTypes = ['application/pdf'] const pdfTypes = ['application/pdf']
// zip // zip
const zipTypes = ['application/x-zip-compressed','application/x-compressed'] const zipTypes = ['application/x-zip-compressed', 'application/x-compressed']
// //
const imgTypes = ['image/jpeg','image/gif', 'image/png'] const imgTypes = ['image/jpeg', 'image/gif', 'image/png']
// text // text
const textTypes = ['text/plain'] const textTypes = ['text/plain']
@ -93,6 +96,8 @@ const hanleFileChange = (file) => {
return false return false
} }
// //
// B < KB < MB < GB
// file.raw.size B
const fileSize = file.raw.size / 1024 / 1024 > 100 const fileSize = file.raw.size / 1024 / 1024 > 100
if (fileSize) { if (fileSize) {
ElMessage.error('文件大小错误! 请上传小于100M的文件!') ElMessage.error('文件大小错误! 请上传小于100M的文件!')
@ -102,13 +107,28 @@ const hanleFileChange = (file) => {
// fileData // fileData
file.fileData = { file.fileData = {
fileFlag: '课件', fileFlag: '素材',
name: getFileName(file.name), name: getFileName(file.name),
} }
fileList.value.push(file) fileList.value.push(file)
console.log(fileList.value)
} }
} }
//
const checkFile = (item, file) => {
const type = file.raw.type
const pptTypes = ['application/vnd.ms-powerpoint', 'application/vnd.openxmlformats-officedocument.presentationml.presentation']
let isPpt = pptTypes.includes(type)
if(!isPpt && item.value == '教案'){
return true
}
else{
return false
}
}
// //
const delFile = (index) => { const delFile = (index) => {
fileList.value.splice(index, 1) fileList.value.splice(index, 1)
@ -133,7 +153,7 @@ const submitFile = () => {
item.fileData.fileShowName = item.fileData.name + '.' + suffix item.fileData.fileShowName = item.fileData.name + '.' + suffix
delete item.fileData.name delete item.fileData.name
}) })
emit('submitFile',fileList.value) emit('submitFile', fileList.value)
closeDialog() closeDialog()
} }
@ -203,24 +223,28 @@ const submitFile = () => {
} }
} }
} }
.file-input { .file-input {
border-bottom: solid #dfdfdf 1px; border-bottom: solid #dfdfdf 1px;
&:hover { &:hover {
border-color: #409EFF; border-color: #409EFF;
} }
&:focus{
&:focus {
border-color: #409EFF; border-color: #409EFF;
} }
} }
:deep(.el-input__wrapper){ :deep(.el-input__wrapper) {
box-shadow: none
}
:deep(.el-input__wrapper.is-focus){
box-shadow: none
}
:deep(.el-input__wrapper:hover){
box-shadow: none box-shadow: none
} }
:deep(.el-input__wrapper.is-focus) {
box-shadow: none
}
:deep(.el-input__wrapper:hover) {
box-shadow: none
}
</style> </style>

View File

@ -113,9 +113,6 @@ const emits = defineEmits(['setLayout'])
function setLayout() { function setLayout() {
emits('setLayout'); emits('setLayout');
} }
watch(()=> userStore.avatar,() => {
userImg.value = userStore.avatar;
},{deep:true})
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>

View File

@ -24,7 +24,8 @@
<div class="prepare-body-main-item"> <div class="prepare-body-main-item">
<div class="prepare-uploader-progress" :style="{ width: item.percentage + '%' }"></div> <div class="prepare-uploader-progress" :style="{ width: item.percentage + '%' }"></div>
<div class="prepare-body-main-item-icon"> <div class="prepare-body-main-item-icon">
<svg <FileImage :size="50" :file-name="item.raw.name" />
<!-- <svg
class="icon" class="icon"
aria-hidden="true" aria-hidden="true"
font-size="50px" font-size="50px"
@ -32,7 +33,7 @@
style="margin: auto" style="margin: auto"
> >
<use xlink:href="#icon-ppt"></use> <use xlink:href="#icon-ppt"></use>
</svg> </svg>-->
</div> </div>
<div class="prepare-body-main-item-info"> <div class="prepare-body-main-item-info">
<div class="prepare-item-info-title">{{ item.raw.name }}</div> <div class="prepare-item-info-title">{{ item.raw.name }}</div>
@ -59,12 +60,14 @@
<script> <script>
import uploaderState from '@/store/modules/uploader' import uploaderState from '@/store/modules/uploader'
import { getToken } from '@/utils/auth' import { getToken } from '@/utils/auth'
import CryptoJS from 'crypto-js' // import CryptoJS from 'crypto-js/md5'
import SparkMD5 from 'spark-md5'
import { DeleteFilled } from '@element-plus/icons-vue' import { DeleteFilled } from '@element-plus/icons-vue'
import FileImage from '@/components/file-image/index.vue'
const { ipcRenderer } = window.electron || {} const { ipcRenderer } = window.electron || {}
export default { export default {
name: 'Uploader', name: 'Uploader',
components: { DeleteFilled }, components: { DeleteFilled, FileImage },
data() { data() {
return { return {
timer: null, timer: null,
@ -147,13 +150,14 @@ export default {
}, },
getFileMD5(file) { getFileMD5(file) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const fileReader = new FileReader() let fileReader = new FileReader()
fileReader.onload = (e) => { let Spark = new SparkMD5.ArrayBuffer()
const buffer = e.target.result fileReader.readAsArrayBuffer(file)
let md5 = CryptoJS.MD5(buffer).toString() fileReader.onload = function (e) {
Spark.append(e.target.result)
let md5 = Spark.end()
resolve(md5) resolve(md5)
} }
fileReader.readAsArrayBuffer(file)
}) })
}, },
removeUploadFile(uid) { removeUploadFile(uid) {

View File

@ -28,6 +28,8 @@ export class CanvasClickEvent {
// 事件:按下鼠标 // 事件:按下鼠标
canvas?.on('mouse:down', (e) => { canvas?.on('mouse:down', (e) => {
console.log(222222222222222222222222222)
this.isMouseDown = true this.isMouseDown = true
if (this.isSpaceKeyDown) { if (this.isSpaceKeyDown) {
return return
@ -147,6 +149,8 @@ export class CanvasClickEvent {
// 事件:移动鼠标 // 事件:移动鼠标
canvas?.on('mouse:move', (e) => { canvas?.on('mouse:move', (e) => {
console.log(222222222222222222222222222)
if (this.isMouseDown) { if (this.isMouseDown) {
// Press space, drag the canvas, stop drawing. // Press space, drag the canvas, stop drawing.
if (this.isSpaceKeyDown) { if (this.isSpaceKeyDown) {
@ -179,6 +183,8 @@ export class CanvasClickEvent {
// 事件:松开鼠标 // 事件:松开鼠标
canvas?.on('mouse:up', (e) => { canvas?.on('mouse:up', (e) => {
console.log(222222222222222222222222222)
this.isMouseDown = false this.isMouseDown = false
if (this.autoDrawInk?.[0]?.length > 3) { if (this.autoDrawInk?.[0]?.length > 3) {
autoDrawData.addInk([...this.autoDrawInk]) autoDrawData.addInk([...this.autoDrawInk])

View File

@ -36,6 +36,7 @@ const useUserStore = defineStore(
getInfo() { getInfo() {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
getInfo().then(res => { getInfo().then(res => {
res.user.avatar = import.meta.env.VITE_APP_BASE_API + res.user.avatar
const user = res.user const user = res.user
this.user = user this.user = user
const avatar = (user.avatar == "" || user.avatar == null) ? defAva : user.avatar; const avatar = (user.avatar == "" || user.avatar == null) ? defAva : user.avatar;

View File

@ -0,0 +1,18 @@
import useUserStore from '@/store/modules/user'
import array from 'lodash/array'
export const hasPermission = (value, def = true) => {
// 不传值,默认视为有权限,不做鉴权
if (!value) {
return def
}
const allCodeList = useUserStore().roles
// 如果不是数组直接判断pinia里的权限数组有没有相同的元素即可
if (!Array.isArray(value)) {
return allCodeList.includes(value)
}
// intersection是lodash提供的一个方法用于返回一个所有给定数组都存在的元素组成的数组
return array.intersection(value, allCodeList).length > 0
}

View File

@ -0,0 +1,47 @@
import useUserStore from '@/store/modules/user'
const userStore = useUserStore()
const baseConfig = {
// Electron 设置cookie
url: 'https://file.ysaix.com:7868',
//cookie 名称 这里为 token
name: 'Admin-Token',
//cookie 值
value: userStore.token,
// 域名
domain: 'file.ysaix.com',
}
// 作业
const homeWork = {
data: { ...baseConfig},
// 完整路径
fullPath: `${baseConfig.url}/teaching/classtaskassign?titleName=%E4%BD%9C%E4%B8%9A%E5%B8%83%E7%BD%AE`
}
// 高考研读
const gk = {
data: { ...baseConfig},
fullPath: `${baseConfig.url}/education/colentrance`
}
// 课标研读
const standard = {
data: { ...baseConfig},
fullPath: `${baseConfig.url}/teaching/chatwithstandard`
}
// 教学大模型
const aiModel = {
data: { ...baseConfig},
fullPath: `${baseConfig.url}/platofai`
}
export default {
homeWork,
gk,
standard,
aiModel
}

View File

@ -0,0 +1,129 @@
// 所有事件
export function handleevent(canvas, imgarr, type = 'defalut') {
// 鼠标按下
canvas.on('mouse:down', function (e) {})
// // 监听鼠标移动事件
// canvas.on('mouse:move', (options) => {
// console.log('Mouse move event:', options);
// });
// 监听鼠标释放事件
canvas.on('mouse:up', (options) => {
//判断是点击的哪一个
if (type == 'defalut') {
if (imgarr.value[0].index == 0) {
imgarr.value[0].JSONdata = canvas.toJSON()
}
if (imgarr.value[1].index == 0) {
imgarr.value[1].JSONdata = canvas.toJSON()
}
} else {
if (imgarr.value[0].index == 1) {
imgarr.value[0].JSONdata = canvas.toJSON()
}
if (imgarr.value[1].index == 1) {
imgarr.value[1].JSONdata = canvas.toJSON()
}
}
})
}
// 保存数据
export function savecanvsStore(imgarr, canvsStore) {
canvsStore.pageArr = mergeAndReplace(canvsStore.pageArr, imgarr.value)
// console.log(canvsStore.pageArr,22222222222222222222+'存入')
}
// 重显数据
export function displayData(canvas, canvsStore, canvasobj, fabric, img) {
// 初始化
if (!canvsStore.pageArr.length) {
fabric.Image.fromURL(img.src, (img) => {
img.set({
left: 0,
top: 0,
scaleX: canvas.value.width / img.width,
scaleY: canvas.value.height / img.height
})
canvas.value.setBackgroundImage(img, canvas.value.renderAll.bind(canvas.value))
})
return
}
canvsStore.pageArr.forEach((item) => {
//初始化
if (item.page == canvasobj.page) {
canvas.value.clear() // 清除 Canvas
// console.log(item.JSONdata, '找到一样的数据')
canvas.value.loadFromJSON(item.JSONdata, () => {
// 在所有对象加载完成后重新渲染画布
requestAnimationFrame(() => {
// 渲染所有对象
canvas.value.renderAll.bind(canvas.value)
canvas.value.renderAll()
})
})
} else {
// 使用 requestAnimationFrame 来更新画布,确保在下一帧进行重绘
canvas.value.clear() // 清除 Canvas
requestAnimationFrame(function () {
fabric.Image.fromURL(img.src, (img) => {
img.set({
left: 0,
top: 0,
scaleX: canvas.value.width / img.width,
scaleY: canvas.value.height / img.height
})
canvas.value.setBackgroundImage(img, canvas.value.renderAll.bind(canvas.value))
})
// 渲染所有对象
canvas.value.renderAll.bind(canvas.value)
})
}
})
}
//page 一样替换
const mergeAndReplace = (arr1, arr2) => {
// // 用于存储替换后的数组
// const resultArray = array1.map(item1 => {
// // 在 array2 中查找 page 相同的对象
// const replacement = array2.find(item2 => item2.page == item1.page);
// // 如果找到替换对象,则返回替换对象,否则返回原对象
// return replacement ? replacement : item1;
// });
// // 将 array2 中 page 不在 array1 中的对象追加到结果数组中
// array2.forEach(item2 => {
// const existsInArray1 = array1.some(item1 => item1.page == item2.page);
// if (!existsInArray1) {
// resultArray.push(item2);
// }
// });
// return resultArray;
// 创建一个映射,将 arr2 中的对象按 page 属性存储
let map = new Map(arr2.map((item) => [item.page, item]))
// 使用 map 替换 arr1 中相应 page 的对象,并添加 arr2 中的对象
arr1 = arr1.map((item) => (map.has(item.page) ? map.get(item.page) : item))
// 将 map 中存在但 arr1 中不存在的对象添加到 arr1
for (let [page, obj] of map) {
if (!arr1.some((item) => item.page === page)) {
arr1.push(obj)
}
}
return arr1
}
// 初始化数据
export function initcanvasdata(canvas) {
canvas.value.clear() // 清除 Canvas
// 设置画布的背景色或其他属性
canvas.value.backgroundColor = 'rgba(255, 255, 255, 1)' // 白色背景
// 使用 requestAnimationFrame 来更新画布,确保在下一帧进行重绘
requestAnimationFrame(function () {
// 渲染所有对象
canvas.value.renderAll.bind(canvas.value)
})
}

View File

@ -1,5 +1,49 @@
const { ipcRenderer } = window.electron || {} const { ipcRenderer } = window.electron || {}
export const asyncLocalFile = (item) => {
return new Promise((resolve) => {
//判断是否需要从线上拿新的文件
isAsyncLocalFile(item.fileNewName, item.lastModifyTime, item.fileMd5).then(
({ isAsync, type }) => {
item.async = !isAsync
if (isAsync === true) {
item.async = 'on'
if (type === 'down') {
ipcRenderer.send('download-file-default', {
url: item.fileFullPath,
fileName: item.fileNewName
})
ipcRenderer.once('download-file-default' + item.fileNewName, (e, isSuccess) => {
item.async = isSuccess
resolve()
})
}
if (type === 'upload') {
let cookie = localStorage.getItem('Admin-Token')
ipcRenderer.send('upload-file-change', {
id: item.id,
fileNewName: item.fileNewName,
cookie,
fileType: item.fileType
})
ipcRenderer.once(
'upload-file-change-success' + item.fileNewName,
(e, { data, md5 }) => {
item.fileSize = data.fileSize
item.md5 = md5
item.async = true
resolve()
}
)
}
} else {
resolve()
}
}
)
})
}
export const isHaveLocalFile = async (fileNewName) => { export const isHaveLocalFile = async (fileNewName) => {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
ipcRenderer.send('is-have-local-file', fileNewName) ipcRenderer.send('is-have-local-file', fileNewName)
@ -9,6 +53,15 @@ export const isHaveLocalFile = async (fileNewName) => {
}) })
} }
export const isAsyncLocalFile = async (fileNewName, lastModifyTime, md5) => {
return new Promise((resolve, reject) => {
ipcRenderer.send('is-async-local-file', { fileNewName, lastModifyTime, md5 })
ipcRenderer.once('is-async-local-file-reply' + fileNewName, (e, { isAsync, type }) => {
resolve({ isAsync, type })
})
})
}
export const parseCataByNode = (node) => { export const parseCataByNode = (node) => {
if (node.parentNode) { if (node.parentNode) {
let arr = parseCataByNode(node.parentNode) let arr = parseCataByNode(node.parentNode)
@ -31,9 +84,12 @@ export const exportFile = async (list) => {
export const creatPPT = (name, uploadData) => { export const creatPPT = (name, uploadData) => {
JSON.parse(JSON.stringify(uploadData)) JSON.parse(JSON.stringify(uploadData))
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
let cookie = localStorage.getItem('Admin-Token'); let cookie = localStorage.getItem('Admin-Token')
console.log(cookie) ipcRenderer.send('creat-file-default', {
ipcRenderer.send('creat-file-default', { name, uploadData:JSON.parse(JSON.stringify(uploadData)), cookie }) name,
uploadData: JSON.parse(JSON.stringify(uploadData)),
cookie
})
ipcRenderer.once('creat-file-default-reply', (e, res) => { ipcRenderer.once('creat-file-default-reply', (e, res) => {
resolve(res) resolve(res)
}) })

View File

@ -1,78 +1,89 @@
<template> <template>
<div class="pdfbox" ref="pdfbox"> <div class="pdfbox" ref="pdfbox">
<pdfCanvas :pdfObj="pdfObj" ref="pdfCanvaslist" @update:numPages="handleUpdate" /> <pdfCanvas :pdfObj="pdfObj" ref="pdfCanvaslist" @update:numPagesTotal="handleUpdate" />
<div class="pdf-btn"> <div class="pdf-btn">
<el-button style=" border-top-left-radius: 8px;" @click="switchPageMode"> <el-button
<i class="iconfont icon-danyemoban" v-if="pdfObj.numberOfPdf==2"></i> style="border-top-left-radius: 8px"
<i class="iconfont icon-shuangye" v-if="pdfObj.numberOfPdf==1"></i> @click="switchPageMode"
<span class="texts">{{ pdfObj.numberOfPdf==1?'双页':'单页' }}</span> :disabled="numPagesTotal == 1"
</el-button> >
<el-button @click="navtopage('up')" :disabled="pdfObj.numPages==1"> <i class="iconfont icon-danyemoban" v-if="pdfObj.numberOfPdf == 2"></i>
<i class="iconfont icon-shangyiye"></i> <i class="iconfont icon-shuangye" v-if="pdfObj.numberOfPdf == 1"></i>
<span class="texts">上一页</span> <span class="texts">{{ pdfObj.numberOfPdf == 1 ? '双页' : '单页' }}</span>
</el-button> </el-button>
<el-button @click="navtopage('lower')" :disabled="pdfObj.numPages >= numPages/pdfObj.numberOfPdf" > <el-button @click="navtopage('up')" :disabled="pdfObj.numPages == 1 || numPagesTotal == 1">
<i class="iconfont icon-xiayiye"></i> <i class="iconfont icon-shangyiye"></i>
<span class="texts">下一页</span> <span class="texts">上一页</span>
</el-button> </el-button>
</div> <el-button
@click="navtopage('lower')"
:disabled="
pdfObj.numberOfPdf == 1
? pdfObj.numPages >= numPagesTotal
: pdfObj.numPages >= numPagesTotal - 1 || numPagesTotal == 1
"
>
<i class="iconfont icon-xiayiye"></i>
<span class="texts">下一页</span>
</el-button>
</div> </div>
</div>
</template> </template>
<script setup> <script setup>
import { ref, onMounted, watch, reactive } from 'vue'; import { ref, onMounted, watch, reactive } from 'vue'
import * as pdfjsLib from 'pdfjs-dist/legacy/build/pdf'; import * as pdfjsLib from 'pdfjs-dist/legacy/build/pdf'
pdfjsLib.GlobalWorkerOptions.workerSrc = '/lib/build/pdf.worker.mjs'; pdfjsLib.GlobalWorkerOptions.workerSrc = '/lib/build/pdf.worker.mjs'
import pdfCanvas from '@/components/pdf/index.vue'; import pdfCanvas from '@/components/pdf/index.vue'
// //
const pdfObj = reactive({ const pdfObj = reactive({
numberOfPdf:2, // numberOfPdf: 2, //
pdfUrl:'/aaa.pdf', //url pdfUrl: '/aaa.pdf', //url
numPages:1// numPages: 1 //
}); })
const numPages=ref(0) //
const numPagesTotal = ref(0)
const pdfCanvaslist=ref(null); const pdfCanvaslist = ref(null)
const navtopage=(type)=>{ const navtopage = (type) => {
const maxpage=numPages.value/pdfObj.numberOfPdf let num = 1
if(type=='up'){ if (pdfObj.numberOfPdf == 2) {
pdfObj.numPages--; num = 2
}else{ }
pdfObj.numPages++; if (type == 'up') {
} pdfObj.numPages -= num
if(pdfObj.numPages>maxpage) return } else {
pdfCanvaslist.value.initPdf() pdfObj.numPages += num
}
if (pdfObj.numPages > numPagesTotal.value) return
pdfCanvaslist.value.initPdf()
} }
const handleUpdate=(data)=>{ const handleUpdate = (data) => {
numPages.value=data numPagesTotal.value = data
} if (numPagesTotal.value == 1) {
const divideWithRemainder=(num1, num2)=> { pdfObj.numberOfPdf = 1
// pdfCanvaslist.value.initPdf('restone')
const quotient = Math.floor(num1 / num2); }
const remainder = num1 % num2;
// 1
return remainder === 0 ? quotient : quotient + 1;
} }
// //
const switchPageMode=()=>{ const switchPageMode = () => {
// //
if(pdfObj.numberOfPdf==1){ if (pdfObj.numberOfPdf == 1) {
pdfObj.numberOfPdf=2; // 1
pdfObj.numPages= divideWithRemainder(pdfObj.numPages,pdfObj.numberOfPdf) if (pdfObj.numPages == numPagesTotal.value) {
}else{ pdfObj.numPages = pdfObj.numPages - 1
//
pdfObj.numberOfPdf=1;
pdfObj.numPages=pdfObj.numPages*2-1
} }
pdfObj.numberOfPdf = 2
pdfCanvaslist.value.initPdf() // pdf canvas
pdfCanvaslist.value.initPdfone()
} else {
//
pdfObj.numberOfPdf = 1
pdfCanvaslist.value.initPdf('restone')
}
} }
onMounted(async () => { onMounted(async () => {})
});
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@ -82,31 +93,31 @@ onMounted(async () => {
justify-content: center; justify-content: center;
background: #f0f0f0; background: #f0f0f0;
flex-wrap: wrap; flex-wrap: wrap;
.pdf-btn{ .pdf-btn {
position: absolute; position: absolute;
right: 0; right: 0;
bottom: 0; bottom: 0;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.5); box-shadow: 0 4px 6px rgba(0, 0, 0, 0.5);
border-radius: 5px 0 0 0; border-radius: 5px 0 0 0;
.texts{ .texts {
display: block !important; display: block !important;
margin-top: 10px; margin-top: 10px;
} }
button{ button {
margin-left: 0; margin-left: 0;
border: none; border: none;
font-size: 16px; font-size: 16px;
// padding: 4px 7px; // padding: 4px 7px;
border-radius: 0; border-radius: 0;
width: 80px; width: 80px;
height: 70px; height: 70px;
:deep(>span) { :deep(> span) {
display: block !important; display: block !important;
} }
.iconfont{ .iconfont {
font-size: 26px; font-size: 26px;
margin-bottom: 10px; margin-bottom: 10px;
} }
} }
} }
} }

View File

@ -31,7 +31,7 @@
</div> </div>
</div> </div>
<!--选择学科--> <!--选择学科-->
<SelectSubject v-model="isSubject" v-if="isSubject" class="select-subject" @onSuccess="successEditSubject" /> <SelectSubject v-model="isSubject" @onSuccess="successEditSubject" />
</template> </template>
<script setup> <script setup>
import { onMounted, reactive, ref } from 'vue' import { onMounted, reactive, ref } from 'vue'
@ -202,7 +202,5 @@ onMounted(()=>{
.el-form-item { .el-form-item {
margin-bottom: 40px; margin-bottom: 40px;
} }
.select-subject{
-webkit-app-region: drag;
}
</style> </style>

View File

@ -100,10 +100,10 @@ import { Check, UploadFilled, Switch } from '@element-plus/icons-vue'
</script> </script>
<script> <script>
import FileImage from '@/components/file-image/index.vue' import FileImage from '@/components/file-image/index.vue'
import { isHaveLocalFile } from '@/utils/talkFile' import { asyncLocalFile } from '@/utils/talkFile'
import { toTimeText } from '@/utils/date' import { toTimeText } from '@/utils/date'
import { ElMessage, ElMessageBox } from 'element-plus' import { ElMessage, ElMessageBox } from 'element-plus'
import { deleteSmarttalk, updateSmarttalk } from '@/api/file' import { deleteSmarttalk, updateSmarttalk, getPrepareById } from '@/api/file'
const { ipcRenderer } = window.electron || {} const { ipcRenderer } = window.electron || {}
export default { export default {
@ -112,18 +112,23 @@ export default {
props: { props: {
item: { item: {
type: Object, type: Object,
default: function() { default: function () {
return {} return {}
} }
}, },
index: { index: {
type: Number, type: Number,
default: function() { default: function () {
return 0 return 0
} }
} }
}, },
emits: { 'on-move': null, 'on-delete': null }, emits: { 'on-move': null, 'on-delete': null },
data() {
return {
listenList: []
}
},
methods: { methods: {
editTalk(item) { editTalk(item) {
ElMessageBox.prompt('请输入新的名称', '重命名', { ElMessageBox.prompt('请输入新的名称', '重命名', {
@ -142,8 +147,7 @@ export default {
} }
}) })
}) })
.catch(() => { .catch(() => {})
})
}, },
downloadFile(item) { downloadFile(item) {
ipcRenderer.send('save-as', item.fileFullPath, item.fileShowName) ipcRenderer.send('save-as', item.fileFullPath, item.fileShowName)
@ -156,7 +160,6 @@ export default {
}) })
}, },
closePopver(index) { closePopver(index) {
console.log(this.$refs['popover_' + index])
this.$refs['popover_' + index].hide() this.$refs['popover_' + index].hide()
}, },
moveSmarttalkFun(item) { moveSmarttalkFun(item) {
@ -179,20 +182,31 @@ export default {
return temp + 'GB' return temp + 'GB'
} }
}, },
openFileWin(item) { openFileWin(items) {
isHaveLocalFile(item.fileNewName).then((res) => { getPrepareById(items.id).then((item) => {
if (res === true) { Object.assign(items, item)
asyncLocalFile(items).then(() => {
ipcRenderer.send('open-path-app', item.fileNewName) ipcRenderer.send('open-path-app', item.fileNewName)
} else { if (this.listenList.indexOf(item.fileNewName) === -1) {
item.async = 'on' this.listenList.push(item.fileNewName)
ipcRenderer.once('download-file-default' + item.fileNewName, (e, isSuccess) => { let cookie = localStorage.getItem('Admin-Token')
item.async = isSuccess ipcRenderer.send('listen-file-change', {
}) id: item.id,
ipcRenderer.send('download-file-default', { fileNewName: item.fileNewName,
url: item.fileFullPath, md5: item.fileMd5,
fileName: item.fileNewName cookie,
}) fileType: item.fileType
} })
ipcRenderer.on('listen-file-change-on' + item.fileNewName, () => {
item.async = 'on'
})
ipcRenderer.on('listen-file-change-success' + item.fileNewName, (e, { data, md5 }) => {
item.fileSize = data.fileSize
item.md5 = md5
item.async = true
})
}
})
}) })
} }
} }

View File

@ -2,19 +2,27 @@
<div v-loading="isLoading" class="page-resource flex"> <div v-loading="isLoading" class="page-resource flex">
<ChooseTextbook @change-book="nodeClick" @node-click="nodeClick" /> <ChooseTextbook @change-book="nodeClick" @node-click="nodeClick" />
<div class="page-right"> <div class="page-right">
<div class="header-top flex">
<div class="textbook-img">
<el-image style="width: 80px; height: 110px" :src="curBookImg" />
</div>
<div class="top-item">
<el-button class="btn" @click="handleOutLink('standard')">课标研读</el-button>
<el-button class="btn" >电子课本</el-button>
<el-button class="btn" @click="handleOutLink('gk')">高考研读</el-button>
<el-button class="btn" @click="handleOutLink('aiModel')">教学大模型</el-button>
</div>
<el-button type="primary" class="to-class-btn">
<i class="iconfont icon-lingdang"></i>上课</el-button>
</div>
<div class="prepare-body-header"> <div class="prepare-body-header">
<div> <div>
<label style="font-size: 15px">{{ currentFileList.length }}个文件</label>&nbsp; <label style="font-size: 15px">{{ currentFileList.length }}个文件</label>&nbsp;
<el-popover placement="top-start" :width="250" trigger="hover"> <el-popover placement="top-start" :width="250" trigger="hover">
<template #default> <template #default>
<div> <div>
<el-button <el-button v-if="lastAsyncAllTime" type="success" size="small" :icon="Check" circle />
v-if="lastAsyncAllTime"
type="success"
size="small"
:icon="Check"
circle
/>
{{ lastAsyncAllTime ? toTimeText(lastAsyncAllTime) + '同步成功' : '' }} {{ lastAsyncAllTime ? toTimeText(lastAsyncAllTime) + '同步成功' : '' }}
</div> </div>
</template> </template>
@ -29,38 +37,22 @@
</el-popover> </el-popover>
</div> </div>
<div style="display: flex"> <div style="display: flex">
<el-button @click="handleOutLink('homeWork')">布置作业</el-button>
<el-button @click="isDialogOpen = true">上传资料</el-button> <el-button @click="isDialogOpen = true">上传资料</el-button>
<el-button type="primary" style="margin-left: 10px" @click="createFile" <el-button type="primary" style="margin-left: 10px" @click="createFile">新建课件</el-button>
>新建课件</el-button
>
</div> </div>
</div> </div>
<el-checkbox-group <el-checkbox-group v-model="checkFileList" class="prepare-body-main"
v-model="checkFileList" :style="{ 'margin-bottom': checkFileList.length > 0 ? '40px' : '0' }">
class="prepare-body-main" <file-list-item v-for="(item, index) in currentFileList" :key="index" :item="item" :index="index"
:style="{ 'margin-bottom': checkFileList.length > 0 ? '40px' : '0' }" @on-move="onMoveSingleFile" @on-delete="deleteTalk">
>
<file-list-item
v-for="(item, index) in currentFileList"
:key="index"
:item="item"
:index="index"
@on-move="onMoveSingleFile"
@on-delete="deleteTalk"
>
<el-checkbox label="" :value="item" /> <el-checkbox label="" :value="item" />
</file-list-item> </file-list-item>
</el-checkbox-group> </el-checkbox-group>
<file-oper-batch <file-oper-batch v-show="checkFileList.length > 0"
v-show="checkFileList.length > 0"
:indeterminate="checkFileList.length > 0 && checkFileList.length < currentFileList.length" :indeterminate="checkFileList.length > 0 && checkFileList.length < currentFileList.length"
:choose="checkFileList" :choose="checkFileList" :check-all="isCheckAll" @click-delete="clickDelete" @click-move="clickMove"
:check-all="isCheckAll" @cancel="checkFileList = []" @click-choose="clickChoose"></file-oper-batch>
@click-delete="clickDelete"
@click-move="clickMove"
@cancel="checkFileList = []"
@click-choose="clickChoose"
></file-oper-batch>
</div> </div>
<MoveFile v-model="isMoveDialogOpen" @on-submit="chooseMoveCata" /> <MoveFile v-model="isMoveDialogOpen" @on-submit="chooseMoveCata" />
<uploadDialog v-model="isDialogOpen" @submit-file="submitFile" /> <uploadDialog v-model="isDialogOpen" @submit-file="submitFile" />
@ -79,9 +71,12 @@ import FileListItem from '@/views/prepare/container/file-list-item.vue'
import { getSmarttalkPage, moveSmarttalk } from '@/api/file' import { getSmarttalkPage, moveSmarttalk } from '@/api/file'
import { toTimeText } from '@/utils/date' import { toTimeText } from '@/utils/date'
import { ElMessage } from 'element-plus' import { ElMessage } from 'element-plus'
import { isHaveLocalFile, parseCataByNode, creatPPT } from '@/utils/talkFile' import { parseCataByNode, creatPPT, asyncLocalFile } from '@/utils/talkFile'
import FileOperBatch from '@/views/prepare/container/file-oper-batch.vue' import FileOperBatch from '@/views/prepare/container/file-oper-batch.vue'
import outLink from '@/utils/linkConfig'
const { ipcRenderer } = window.electron || {} const { ipcRenderer } = window.electron || {}
export default { export default {
name: 'Prepare', name: 'Prepare',
components: { ChooseTextbook, Refresh, uploadDialog, FileListItem, FileOperBatch, MoveFile }, components: { ChooseTextbook, Refresh, uploadDialog, FileListItem, FileOperBatch, MoveFile },
@ -104,7 +99,9 @@ export default {
levelSecondId: null, levelSecondId: null,
fileSource: '个人', fileSource: '个人',
fileRoot: '备课' fileRoot: '备课'
} },
//
curBookImg: '',
} }
}, },
computed: { computed: {
@ -120,21 +117,13 @@ export default {
this.callback(param) this.callback(param)
}) })
this.lastAsyncAllTime = localStorage.getItem('lastAsyncAllTime') this.lastAsyncAllTime = localStorage.getItem('lastAsyncAllTime')
}, },
mounted() { mounted() {
// const destination = '0901(A)-PPT.pptx'
// ipcRenderer.send('open-path-app',this.filePath)
// const source = 'D:\\edufile\\0901(A)-PPT.pptx'
// ipcRenderer.send('copy-file-default',{ source, destination })
// ipcRenderer.send('download-file-default',this.fileUrl)
// getSmarttalkPage({nowPage:1,pageSize:2}).then(res=>{
// console.log(res)
// })
// let filePath = window.rootTalkFilePath + item.fileNewName
}, },
methods: { methods: {
createFile() { createFile() {
creatPPT('新建ppt文档.pptx',this.uploadData).then((res) => { creatPPT(this.currentNode.label + '.pptx', this.uploadData).then((res) => {
this.currentFileList.unshift(res.resData) this.currentFileList.unshift(res.resData)
}) })
}, },
@ -160,35 +149,6 @@ export default {
clickChoose(value) { clickChoose(value) {
this.checkFileList = value ? this.currentFileList : [] this.checkFileList = value ? this.currentFileList : []
}, },
async asyncAllFile() {
this.lastAsyncAllTime = new Date()
localStorage.setItem('lastAsyncAllTime', this.lastAsyncAllTime)
this.asyncAllFileVisiable = true
const test = (item) => {
return new Promise((resolve) => {
isHaveLocalFile(item.fileNewName).then((res) => {
item.async = res
if (res === false) {
ipcRenderer.send('download-file-default', {
url: item.fileFullPath,
fileName: item.fileNewName
})
item.async = 'on'
ipcRenderer.once('download-file-default' + item.fileNewName, (e, isSuccess) => {
item.async = isSuccess
resolve()
})
} else {
resolve()
}
})
})
}
for (let i = 0; i < this.currentFileList.length; i++) {
await test(this.currentFileList[i])
}
this.asyncAllFileVisiable = false
},
deleteTalk(item) { deleteTalk(item) {
let index = this.currentFileList.indexOf(item) let index = this.currentFileList.indexOf(item)
this.currentFileList.splice(index, 1) this.currentFileList.splice(index, 1)
@ -254,15 +214,7 @@ export default {
} }
console.log('File copied to:', filePath) console.log('File copied to:', filePath)
}, },
nodeClick(data) { asyncAllFile() {
if (this.currentNode.id === data.node.id) return
this.checkFileList = []
let cata = parseCataByNode(data.node)
this.currentNode = data.node
this.uploadData.levelFirstId = cata[0]
this.uploadData.levelSecondId = cata[1]
this.uploadData.levelThirdId = cata[2]
this.uploadData.textbookId = data.textBook.curBookId
this.isLoading = true this.isLoading = true
getSmarttalkPage({ getSmarttalkPage({
...this.uploadData, ...this.uploadData,
@ -270,20 +222,44 @@ export default {
isAsc: 'desc', isAsc: 'desc',
pageSize: 500 pageSize: 500
}) })
.then((res) => { .then(async (res) => {
this.currentFileList = [...res.rows] this.currentFileList = [...res.rows]
this.isLoading = false this.isLoading = false
this.currentFileList.filter((item) => { this.lastAsyncAllTime = new Date()
isHaveLocalFile(item.fileNewName).then((res) => { localStorage.setItem('lastAsyncAllTime', this.lastAsyncAllTime)
item.async = res this.asyncAllFileVisiable = true
}) for (let i = 0; i < this.currentFileList.length; i++) {
}) let item = this.currentFileList[i]
await asyncLocalFile(item)
}
this.asyncAllFileVisiable = false
}) })
.catch((res) => { .catch(() => {
console.log(res)
this.isLoading = false this.isLoading = false
}) })
} },
nodeClick(data) {
if (this.currentNode.id === data.node.id) return
this.curBookImg = data.textBook.curBookImg
this.checkFileList = []
let cata = parseCataByNode(data.node)
this.currentNode = data.node
this.uploadData.levelFirstId = cata[0]
this.uploadData.levelSecondId = cata[1]
this.uploadData.levelThirdId = cata[2]
this.uploadData.textbookId = data.textBook.curBookId
this.asyncAllFile()
},
//
handleOutLink(key){
// key linkConfig.js
let configObj = outLink[key]
//
ipcRenderer.send('openWindow', {
fullPath: configObj.fullPath,
cookieData: {...(configObj.data)}
})
},
} }
} }
</script> </script>
@ -321,6 +297,7 @@ export default {
height: 100%; height: 100%;
.page-right { .page-right {
overflow: hidden;
position: relative; position: relative;
min-width: 0; min-width: 0;
flex: 1; flex: 1;
@ -332,6 +309,55 @@ export default {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
.header-top {
height: 150px;
align-items: center;
justify-content: center;
background: linear-gradient(#97c4ed, #7aa8e5);
padding-right: 20px;
.textbook-img{
height: 120px;
background-color: #ffffff;
padding: 5px;
border-radius: 6px;
overflow: hidden;
margin-right: 20px;
}
.top-item{
width: 230px;
flex-wrap: wrap;
.btn{
width: 102px;
background: none;
color: #ffffff;
border-width: 2px;
border-color: #ffffff;
&:hover{
background: rgba(255, 255, 255, 0.3)
}
&:first-child{
margin-left: 12px;
margin-bottom: 15px;
}
&:nth-child(2){
margin-bottom: 15px;
}
}
}
.to-class-btn{
width: 130px;
height: 80px;
margin-left: 25px;
font-size: 18px;
.icon-lingdang{
margin-right: 5px;
color: #ffffff;
font-size: 20px;
}
}
}
.prepare-body-header { .prepare-body-header {
height: 60px; height: 60px;
width: 100%; width: 100%;
@ -340,6 +366,7 @@ export default {
flex-wrap: wrap; flex-wrap: wrap;
justify-content: space-between; justify-content: space-between;
padding: 0 20px; padding: 0 20px;
} }
.prepare-body-main { .prepare-body-main {

View File

@ -99,6 +99,7 @@ const state = reactive({
function getUser() { function getUser() {
getUserProfile().then((response) => { getUserProfile().then((response) => {
response.data.avatar = import.meta.env.VITE_APP_BASE_API + response.data.avatar
state.user = response.data state.user = response.data
state.roleGroup = response.roleGroup state.roleGroup = response.roleGroup
state.postGroup = response.postGroup state.postGroup = response.postGroup

View File

@ -84,7 +84,7 @@ const title = ref('修改头像')
// //
const options = reactive({ const options = reactive({
img: userStore.avatar, // img: userStore.user.avatar, //
autoCrop: true, // autoCrop: true, //
autoCropWidth: 200, // autoCropWidth: 200, //
autoCropHeight: 200, // autoCropHeight: 200, //
@ -155,7 +155,7 @@ function uploadImg() {
uploadAvatar(formData).then((response) => { uploadAvatar(formData).then((response) => {
open.value = false open.value = false
options.img = import.meta.env.VITE_APP_BASE_API + response.imgUrl options.img = import.meta.env.VITE_APP_BASE_API + response.imgUrl
userStore.avatar = options.img userStore.user.avatar = options.img
ElMessage({ ElMessage({
message: '上传成功', message: '上传成功',
type: 'success', type: 'success',

View File

@ -8,12 +8,13 @@
}}</el-button> }}</el-button>
</el-col> </el-col>
<el-col :span="12" class="search-box flex"> <el-col :span="12" class="search-box flex">
<el-input v-model="sourceStore.query.fileName" @input="sourceStore.changeName" style="width: 240px" placeholder="请输入关键词" /> <el-input v-model="sourceStore.query.fileName" @input="sourceStore.changeName" style="width: 240px"
placeholder="请输入关键词" />
</el-col> </el-col>
</el-row> </el-row>
<el-row class="resoure-btns"> <el-row class="resoure-btns">
<el-col :span="24" class="query-row flex"> <el-col :span="24" class="query-row flex">
<div class="flex row-left"> <el-select v-model="sourceStore.query.fileSuffix" @change="sourceStore.changeSuffix" <div class="flex row-left"> <el-select v-model="sourceStore.query.fileSuffix" @change="sourceStore.changeSuffix"
style="width: 110px"> style="width: 110px">
<el-option v-for="item in sourceStore.resourceFormatList" :key="item.value" :label="item.label" <el-option v-for="item in sourceStore.resourceFormatList" :key="item.value" :label="item.label"
:value="item.value" /> :value="item.value" />
@ -22,7 +23,7 @@
<el-button v-for="item in sourceStore.resourceTypeList" :key="item.id" <el-button v-for="item in sourceStore.resourceTypeList" :key="item.id"
:type="sourceStore.query.fileFlag == item.value ? 'primary' : ''" round :type="sourceStore.query.fileFlag == item.value ? 'primary' : ''" round
@click="sourceStore.changeType(item.value)">{{ @click="sourceStore.changeType(item.value)">{{
item.label }}</el-button> item.label }}</el-button>
</div> </div>
<div> <div>
<slot name="add" /> <slot name="add" />
@ -58,7 +59,8 @@ const sourceStore = useResoureStore()
.query-row { .query-row {
justify-content: space-between; justify-content: space-between;
.row-left{
.row-left {
align-items: center; align-items: center;
} }
} }
@ -74,7 +76,8 @@ const sourceStore = useResoureStore()
} }
} }
.el-button.is-round{
.el-button.is-round {
padding: 3px 15px; padding: 3px 15px;
font-size: 13px; font-size: 13px;
} }

View File

@ -6,7 +6,7 @@
<div class="page-right"> <div class="page-right">
<!-- 搜索 --> <!-- 搜索 -->
<ResoureSearch #add> <ResoureSearch #add>
<el-button type="primary" round @click="openDialog" class="create-btn"> <el-button v-if="sourceStore.isCreate" type="primary" round @click="openDialog" class="create-btn">
<i class="iconfont icon-jiahao"></i> <i class="iconfont icon-jiahao"></i>
新建资源</el-button> 新建资源</el-button>
</ResoureSearch> </ResoureSearch>
@ -28,7 +28,8 @@ import ResoureList from './container/resoure-list.vue'
import uploadDialog from '@/components/upload-dialog/index.vue' import uploadDialog from '@/components/upload-dialog/index.vue'
import uploaderState from '@/store/modules/uploader' import uploaderState from '@/store/modules/uploader'
import { createWindow } from '@/utils/tool' import { createWindow } from '@/utils/tool'
import { hasPermission } from '@/utils/hasPermission'
//
const sourceStore = useResoureStore() const sourceStore = useResoureStore()
const isDialogOpen = ref(false) const isDialogOpen = ref(false)
@ -74,24 +75,27 @@ const submitFile = (data) => {
let fileList = toRaw(data) let fileList = toRaw(data)
const { textbookId, levelFirstId, levelSecondId, fileSource, fileRoot } = sourceStore.query const { textbookId, levelFirstId, levelSecondId, fileSource, fileRoot } = sourceStore.query
// //
let fileData = { textbookId, levelFirstId, levelSecondId, fileSource, fileRoot }
fileList.forEach(item => { fileList.forEach(item => {
let fileData = { textbookId, levelFirstId, levelSecondId, fileSource, fileRoot }
fileData.fileShowName = item.fileData.fileShowName fileData.fileShowName = item.fileData.fileShowName
fileData.fileFlag = item.fileData.fileFlag fileData.fileFlag = item.fileData.fileFlag
item.fileData = fileData item.fileData = fileData
item.callback = fileCallBack item.callback = fileCallBack
}) })
// console.log(fileList)
uploaderState().pushFile(fileList) uploaderState().pushFile(fileList)
} }
const fileCallBack = (res) => { const fileCallBack = (res) => {
console.log(res)
if (res.code == 200) { if (res.code == 200) {
sourceStore.handleQuery() sourceStore.handleQuery()
} }
} }
onMounted(()=>{
sourceStore.getCreate()
})
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>

View File

@ -2,6 +2,7 @@ import { defineStore } from 'pinia'
import { getSmarttalkPage } from '@/api/file/index' import { getSmarttalkPage } from '@/api/file/index'
import { tabs, resourceType, resourceFormat } from '@/utils/resourceDict' import { tabs, resourceType, resourceFormat } from '@/utils/resourceDict'
import useUserStore from '@/store/modules/user' import useUserStore from '@/store/modules/user'
import { hasPermission } from '@/utils/hasPermission'
const userStore = useUserStore() const userStore = useUserStore()
@ -21,10 +22,10 @@ const resourceFormatList = [
] ]
// 校本资源为学校ID // 校本资源为学校ID
tabs.forEach(item =>{ tabs.forEach((item) => {
if( item.label == "校本资源"){ if (item.label == '校本资源') {
item.value = userStore.user.deptId item.value = userStore.user.deptId
} }
}) })
const structQuery = { const structQuery = {
@ -41,12 +42,14 @@ export default defineStore('resource', {
searchKey: '', searchKey: '',
//节点数据 //节点数据
nodeData:{}, nodeData: {},
loading: false, loading: false,
//
isCreate: true,
//查询条件 //查询条件
query: { query: {
textbookId: '', textbookId: '',
fileSource: '平台', fileSource: tabs[0].value,
//资源格式 mp3 ppt ... //资源格式 mp3 ppt ...
fileSuffix: -1, fileSuffix: -1,
// 资源类型 课件 素材 教案 // 资源类型 课件 素材 教案
@ -66,8 +69,8 @@ export default defineStore('resource', {
handleQuery() { handleQuery() {
try { try {
this.loading = true this.loading = true
let data = {...this.query} let data = { ...this.query }
if(data.fileSuffix == -1){ if (data.fileSuffix == -1) {
data.fileSuffix = '' data.fileSuffix = ''
} }
getSmarttalkPage(data).then((res) => { getSmarttalkPage(data).then((res) => {
@ -80,20 +83,28 @@ export default defineStore('resource', {
}, },
changeTab(val) { changeTab(val) {
this.query.fileSource = val this.query.fileSource = val
this.getCreate()
this.handleQuery() this.handleQuery()
}, },
changeType(val) { changeType(val) {
this.query.fileFlag = val this.query.fileFlag = val
this.handleQuery() this.handleQuery()
}, },
changeSuffix(val){ changeSuffix(val) {
this.query.fileSuffix = val this.query.fileSuffix = val
this.handleQuery() this.handleQuery()
}, },
// 关键词搜索 // 关键词搜索
changeName(){ changeName() {
console.log(this.query.fileName)
this.handleQuery() this.handleQuery()
}, },
getCreate(){
if(this.query.fileSource == '平台'){
this.isCreate = hasPermission(['platformmanager'])
}
else{
this.isCreate = hasPermission(['schoolteacher','headmaster'])
}
}
} }
}) })