Merge branch 'main' into lyc-dev

This commit is contained in:
lyc 2024-09-03 10:41:57 +08:00
commit a6bda302af
20 changed files with 322 additions and 121 deletions

View File

@ -1,6 +1,6 @@
{ {
"name": "aix-win", "name": "aix-win",
"version": "1.1.1", "version": "1.1.6",
"description": "An Electron application with Vue", "description": "An Electron application with Vue",
"main": "./out/main/index.js", "main": "./out/main/index.js",
"author": "example.com", "author": "example.com",

BIN
resources/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

View File

@ -3,15 +3,17 @@ import { join } from 'path'
import { electronApp, optimizer, is } from '@electron-toolkit/utils' import { electronApp, optimizer, is } from '@electron-toolkit/utils'
import icon from '../../resources/icon.png?asset' import icon from '../../resources/icon.png?asset'
import File from './file' import File from './file'
import chat from './chat' // chat封装 import Logger from './logger' // 日志封装
import Store from './store' // Store封装 import chat from './chat' // chat封装
import Store from './store' // Store封装
import updateInit from './update' import updateInit from './update'
// 代理 electron/remote // 代理 electron/remote
// 第一步引入remote // 第一步引入remote
import remote from '@electron/remote/main' import remote from '@electron/remote/main'
// 第二步: 初始化remote // 第二步: 初始化remote
remote.initialize() remote.initialize()
// 日志配置-初始化(日志直接绑定到console上)
if(!is.dev) Logger.initialize()
// 持久化数据-初始化 // 持久化数据-初始化
Store.initialize() Store.initialize()
@ -153,6 +155,7 @@ async function createLinkWin(data) {
// 初始化完成 // 初始化完成
app.on('ready', () => { app.on('ready', () => {
appWatchError() // 监听app错误
process.env.LANG = 'en_US.UTF-8' process.env.LANG = 'en_US.UTF-8'
// 设置应用程序用户模型标识符 // 设置应用程序用户模型标识符
electronApp.setAppUserModelId('com.electron') electronApp.setAppUserModelId('com.electron')
@ -259,3 +262,34 @@ function handleAll() {
win.webContents.send('pinia-state-set', storeName, jsonStr) win.webContents.send('pinia-state-set', storeName, jsonStr)
}) })
} }
// app 崩溃监听器
function appWatchError() {
// 渲染进程崩溃
app.on('renderer-process-crashed', (event, webContents, killed) => {
console.error(
`APP-ERROR:renderer-process-crashed; event: ${JSON.stringify(event)}; webContents:${JSON.stringify(
webContents
)}; killed:${JSON.stringify(killed)}`
)
})
// GPU进程崩溃
app.on('gpu-process-crashed', (event, killed) => {
console.error(`APP-ERROR:gpu-process-crashed; event: ${JSON.stringify(event)}; killed: ${JSON.stringify(killed)}`)
})
// 渲染进程结束
app.on('render-process-gone', async (event, webContents, details) => {
console.error(
`APP-ERROR:render-process-gone; event: ${JSON.stringify(event)}; webContents:${JSON.stringify(
webContents
)}; details:${JSON.stringify(details)}`
)
})
// 子进程结束
app.on('child-process-gone', async (event, details) => {
console.error(`APP-ERROR:child-process-gone; event: ${JSON.stringify(event)}; details:${JSON.stringify(details)}`)
})
}

52
src/main/logger.js Normal file
View File

@ -0,0 +1,52 @@
/**
* @description 日志配置
* @author zdg
* @date 2021-07-05 14:07:01
*/
// import log from 'electron-log'
import log from 'electron-log/main'
import { app } from 'electron'
import path from 'path'
// 关闭控制台打印
// 日志控制台等级默认值false
log.transports.console.level = false
// log.transports.console.level = 'info'
// 日志文件等级默认值false
log.transports.file.level = 'info'
// 日志文件名默认main.log
// log.transports.file.fileName = 'main.log';
// 日志大小默认10485761M达到最大上限后备份文件并重命名为main.old.log有且仅有一个备份文件
log.transports.file.maxSize = 10 * 1024 * 1024; // 文件最大不超过 10M
// 自定义日志文件滚动策略
log.transports.file.rollSize = 10 * 1024 * 1024; // 10MB
// 日志格式,默认:[{y}-{m}-{d} {h}:{i}:{s}.{ms}] [{level}]{scope} {text}
log.transports.file.format = '[{y}-{m}-{d} {h}:{i}:{s}.{ms}] [{level}]{scope} {text}'
let date = new Date()
let dateStr = date.getFullYear() + '-' + (date.getMonth() + 1) + '-' + date.getDate()
// 文件位置及命名方式
// 默认位置为C:\Users\[user]\AppData\Roaming\[appname]\electron_log\
// 文件名为:年-月-日.log
// 自定义文件保存位置为安装目录下 \log\年-月-日.log
// log.transports.file.resolvePathFn = () => 'logs\\' + dateStr+ '.log';
log.transports.file.resolvePathFn = () => path.join(app.getPath('userData'), `logs/${dateStr}.log`)
// 有六个日志级别error, warn, info, verbose, debug, silly。默认是silly
export const logger = {
error: (...args) => log.error(...args),
warn: (...args) => log.warn(...args),
info: (...args) => log.info(...args),
verbose: (...args) => log.verbose(...args),
debug: (...args) => log.debug(...args),
silly: (...args) => log.silly(...args)
}
export function initialize(bool = true, type = 'all') {
log.initialize() // 为渲染器进行初始化
if (bool) { // 是否替换默认的console
if (type == 'all') Object.assign(console, log.functions)
else { // 替换指定类型
console[type] = log[type]
}
}
}
export default { initialize }

View File

@ -8,25 +8,54 @@ Store.initRenderer()
// 默认共享数据 // 默认共享数据
const defaultData = { const defaultData = {
model: 'select', // 悬浮球-当前模式 session: { // 缓存(临时sessionStorage)
showBoardAll: false, // 全屏画板-是否显示 model: 'select', // 悬浮球-当前模式
isPdfWin: false, // pdf窗口是否打开 showBoardAll: false, // 全屏画板-是否显示
isToolWin: false, // 工具窗口是否打开 isPdfWin: false, // pdf窗口是否打开
curSubjectNode: { isToolWin: false, // 工具窗口是否打开
data: {}, // 当前教材节点 (包含当前教材 单元) curSubjectNode: {
querySearch: {} // 查询资源所需参数 data: {}, // 当前教材节点 (包含当前教材 单元)
} querySearch: {} // 查询资源所需参数
}
},
local: { // 本地(永久localStorage)
},
} }
// 初始化 // 初始化
export function initialize(){ export function initialize(){
const store = new Store({ // 缓存数据-sessionStore
name: 'cache-store', // 存储文件名 const sessionStore = new Store({
name: 'session-store', // 存储文件名
fileExtension: 'ini', // 文件后缀名 fileExtension: 'ini', // 文件后缀名
encryptionKey: 'Eihrjwi7h104h2Kub423' // 数据加密-防止用户直接改配置 encryptionKey: 'BvPLmgCC4DSIG0KkTec5', // 数据加密-防止用户直接改配置
beforeEachMigration: (store, context) => { // 版本迁移回调
console.log(`[session-store] 迁移从 ${context.fromVersion}${context.toVersion}`);
},
migrations: { // 版本变化
'0.0.0': store => {
// store.set('debugPhase', true);
}
}
}) })
store.clear() // 先清除-所有缓存数据 sessionStore.clear() // 先清除-所有缓存数据
store.set(defaultData) // 初始化-默认数据 sessionStore.set(defaultData.session) // 初始化-默认数据
return store
// 缓存数据-localStore
const localStore = new Store({
name: 'local-store', // 存储文件名
fileExtension: 'ini', // 文件后缀名
encryptionKey: '6CyoHQmUaPmLzvVsh', // 数据加密-防止用户直接改配置
beforeEachMigration: (store, context) => { // 版本迁移回调
console.log(`[local-store] 迁移从 ${context.fromVersion}${context.toVersion}`);
},
migrations: { // 版本变化
'0.0.0': store => {
// store.set('debugPhase', true);
}
}
})
localStore.set(defaultData.local) // 初始化-默认数据
return {sessionStore, localStore}
} }
export default { initialize } export default { initialize }

View File

@ -383,16 +383,7 @@ defineExpose({
savaDataStore savaDataStore
}) })
watchEffect(() => { watchEffect(() => {
setTimeout(() => {
console.log(toolState,'监听')
}, 300)
if(toolState.isPdfWin){ if(toolState.isPdfWin){
// if(toolState.isToolWin){
// ispointer.value=false
// }else{
// ispointer.value=true
// }
watchToolState() // watchToolState() //
} }
}) })

View File

@ -12,10 +12,14 @@ import 'virtual:windi.css'
import { store } from '@/store' import { store } from '@/store'
import App from './App.vue' import App from './App.vue'
import router from './router' import router from './router'
import log from 'electron-log/renderer' // 渲染进程日志-文件记录
if(process.env.NODE_ENV != 'development') { // 非开发环境,将日志打印到日志文件
Object.assign(console, log.functions) // 渲染进程日志-控制台替换
}
const app = createApp(App) const app = createApp(App)
app.use(router) app.use(router)
.use(store) .use(store)
.use(ElementPlus, { locale: zhLocale }).mount('#app') .use(ElementPlus, { locale: zhLocale }).mount('#app')

View File

@ -3,13 +3,14 @@
*/ */
const isNode = typeof require !== 'undefined' // 是否支持node函数 const isNode = typeof require !== 'undefined' // 是否支持node函数
const { ipcRenderer } = isNode?require('electron'):{} // app使用 const { ipcRenderer } = isNode?require('electron'):{} // app使用
import { sessionStore } from '@/utils/tool'
// const Remote = isNode?require('@electron/remote'):{} // 远程模块 // const Remote = isNode?require('@electron/remote'):{} // 远程模块
export function shareStorePlugin({store}) { export function shareStorePlugin({store}) {
store.$subscribe((mutation, state) => { // 自动同步 store.$subscribe((mutation, state) => { // 自动同步
// mutation 变量包含了变化前后的状态 // mutation 变量包含了变化前后的状态
// mutation.events: key newValue target oldValue oldTarget // mutation.events: key newValue target oldValue oldTarget
// state 是变化后的状态 // state 是变化后的状态
// console.log('store.$subscribe', mutation) // console.log('store.$subscribe', mutation, state, store)
// 在存储变化的时候执行 // 在存储变化的时候执行
// const storeName = store.$id // const storeName = store.$id
const storeName = mutation.storeId const storeName = mutation.storeId
@ -19,13 +20,15 @@ export function shareStorePlugin({store}) {
const { storeId: storeName, payload, events, type } = mutation // direct const { storeId: storeName, payload, events, type } = mutation // direct
// if (!Object.keys(payload).length) return // if (!Object.keys(payload).length) return
if (type != 'direct' || !events || Array.isArray(events) || !events.key) return if (type != 'direct' || !events || Array.isArray(events) || !events.key) return
stateSync(storeName, events.key, events.newValue) // 需要同步 stateSync(storeName, events.key, events.newValue, state) // 需要同步
} }
}) })
// 暴露方法-手动同步 // 暴露方法-手动同步
store.stateSync = (storeName, key, value) => { store.stateSync = (storeName, key, value) => {
if (!storeName && !!key && !!value) stateSync(storeName, key, value) const state = store.$state
else stateSyncAll(store) if (!storeName && !!key && !!value) stateSync(storeName, key, value, state)
else stateSyncAll(store, state)
} }
// 暴露方法-发送当前状态-新窗口 // 暴露方法-发送当前状态-新窗口
store.stateSyncInit = wid => stateSyncInit(wid, store) store.stateSyncInit = wid => stateSyncInit(wid, store)
@ -34,14 +37,16 @@ export function shareStorePlugin({store}) {
} }
// 同步数据-发送给主线程-单独 // 同步数据-发送给主线程-单独
function stateSync(storeName, key, value) { function stateSync(storeName, key, value, state) {
// console.log('state-change', storeName, key, value) // console.log('state-change', storeName, key, value)
try { try {
let jsonStr = '' const { data, keystr } = filterByKey(state, key, value)
if (typeof key === 'string') jsonStr = JSON.stringify({[key]:value}) const jsonStr = JSON.stringify(data) // 从新组装-json数据
else if (typeof value === 'object') jsonStr = JSON.stringify(key) // 更新本地数据-session
sessionStore.set(keystr, value)
// 通知主线程更新 // 通知主线程更新
ipcRenderer?.invoke('pinia-state-change', storeName, jsonStr) ipcRenderer?.invoke('pinia-state-change', storeName, jsonStr)
// console.log('======',keystr, data )
} catch (error) { } catch (error) {
console.log('state-change-error', error) console.log('state-change-error', error)
} }
@ -95,3 +100,27 @@ const circularSafeStringify = (obj) => {
}); });
} }
// 过滤对象
const filterByKey = (obj, key, value) => {
let res = { data:{}, keystr:'' }
for (let k in obj) {
if (obj.hasOwnProperty(k)) {
const isEqual = JSON.stringify(obj[k]) === JSON.stringify(value) // 值是否相同
if (k === key && isEqual) {
// 如果匹配,则添加到新对象中
res.data[k] = obj[k];
res.keystr = k;
} else {
if (obj[k] !== null && typeof obj[k] === 'object') {
// 如果是对象,则递归处理
const {data, keystr} = filterByKey(obj[k], key, value)
res.data[k] = data
res.keystr = keystr ? `${k}.${keystr}`: key
}
}
}
}
return res;
}
// 对象克隆
const objClone = (obj) => JSON.parse(JSON.stringify(obj))

View File

@ -2,6 +2,10 @@
* 工具类-窗口-状态管理 * 工具类-窗口-状态管理
*/ */
import { defineStore } from 'pinia' import { defineStore } from 'pinia'
import { sessionStore } from '@/utils/tool'
// 默认数据
const defData = sessionStore.store || {}
export const useToolState = defineStore('tool', { export const useToolState = defineStore('tool', {
state: () => ({ state: () => ({
@ -10,9 +14,10 @@ export const useToolState = defineStore('tool', {
isPdfWin: false, // pdf窗口是否打开 isPdfWin: false, // pdf窗口是否打开
isToolWin: false, // 工具窗口是否打开 isToolWin: false, // 工具窗口是否打开
curSubjectNode: { curSubjectNode: {
data: {}, // 当前教材节点 (包含当前教材 单元) data: {}, // 当前教材节点 (包含当前教材 单元)
querySearch: {} // 查询资源所需参数 querySearch: {} // 查询资源所需参数
} },
...defData // 默认数据-覆盖上面的配置(不要删除, 会导致新窗口-获取状态失败)
}), }),
actions: { actions: {
} }

View File

@ -11,22 +11,31 @@ const path = isNode?require('path'):{}
const Remote = isNode?require('@electron/remote'):{} const Remote = isNode?require('@electron/remote'):{}
const { ipcRenderer } = isNode?require('electron'):window.electron || {} const { ipcRenderer } = isNode?require('electron'):window.electron || {}
const API = isNode?window.api:{} // preload-api const API = isNode?window.api:{} // preload-api
import { useToolState } from '@/store/modules/tool' // 获取store状态 // import { useToolState } from '@/store/modules/tool' // 获取store状态
const Store = isNode?require('electron-store'):null // 持久化存储 const Store = isNode?require('electron-store'):null // 持久化存储
// 常用变量 // 常用变量
const BaseUrl = isNode?process.env['ELECTRON_RENDERER_URL']+'/#':'' const BaseUrl = isNode?process.env['ELECTRON_RENDERER_URL']+'/#':''
const isDev = isNode?process.env.NODE_ENV !== 'production':'' const isDev = isNode?process.env.NODE_ENV !== 'production':''
const toolState = useToolState() // 获取store状态 // const toolState = useToolState() // 获取store状态
// 暴露Remote中的属性 // 暴露Remote中的属性
export const ipcMain = Remote?.ipcMain || {} export const ipcMain = Remote?.ipcMain || {}
// 暴露Store存储对象
export const store = Store ? new Store({ // 暴露sessionStore存储对象
name: 'cache-store', // 存储文件名 export const sessionStore = Store ? new Store({
fileExtension: 'ini', // 文件后缀名 name: 'session-store', // 存储文件名
encryptionKey: 'Eihrjwi7h104h2Kub423' // 数据加密-防止用户直接改配置 fileExtension: 'ini', // 文件后缀名
encryptionKey: 'BvPLmgCC4DSIG0KkTec5' // 数据加密-防止用户直接改配置
}) : {} }) : {}
// 暴露localStore存储对象
export const localStore = Store ? new Store({
name: 'local-store', // 存储文件名
fileExtension: 'ini', // 文件后缀名
encryptionKey: '6CyoHQmUaPmLzvVsh' // 数据加密-防止用户直接改配置
}) : {}
/** /**
* 获取静态资源开发和生产环境 * 获取静态资源开发和生产环境
* @param {*} url * @param {*} url
@ -128,7 +137,7 @@ export const createWindow = async (type, data) => {
winPdf.restore(); winPdf.restore();
} else{ } else{
winPdf.focus(); winPdf.focus();
toolState.isPdfWin=true // toolState.isPdfWin=true
} }
return return
@ -195,9 +204,9 @@ export function toolWindow({url, isConsole, isWeb=true, option={}}) {
mainWin.once('closed', () => { win.destroy()}) mainWin.once('closed', () => { win.destroy()})
// 内部监听器 // 内部监听器
win.webContents.on('did-finish-load', () => { win.webContents.on('did-finish-load', () => {
setTimeout(() => { // setTimeout(() => {
toolState.stateSyncInit(win.id) // 同步状态 // toolState.stateSyncInit(win.id) // 同步状态
}, 200); // }, 200);
}) })
// 内部监听器-是否打印 // 内部监听器-是否打印
if (!!isConsole) { if (!!isConsole) {

View File

@ -104,9 +104,12 @@ const switchPageMode = () => {
} }
} }
onMounted(async () => { onMounted(async () => {
const isDev = process.env.NODE_ENV == 'development'
toolState.isPdfWin=true //pdf toolState.isPdfWin=true //pdf
pdfObj.pdfUrl = getStaticUrl(route.query.path, 'user', 'selfFile', true) //线 if (isDev)
// pdfObj.pdfUrl = getStaticUrl('aaa.pdf', 'user', 'selfFile', true) // pdfObj.pdfUrl = getStaticUrl('aaa.pdf', 'user', 'selfFile', true) //
else
pdfObj.pdfUrl = getStaticUrl(route.query.path, 'user', 'selfFile', true) //线
textbookId.value = route.query.textbookId textbookId.value = route.query.textbookId
pdfObj.bookId=textbookId.value pdfObj.bookId=textbookId.value
// //

View File

@ -1,10 +1,10 @@
<template> <template>
<el-card style="width: 100%;height: 100%"> <el-card style="width: 100%;height: 100%">
<template #header> <!-- <template #header>-->
<div class="card-header" style="text-align: left"> <!-- <div class="card-header" style="text-align: left">-->
<el-button type="primary" @click="addGroup">新建分组</el-button> <!-- <el-button type="primary" @click="addGroup">新建分组</el-button>-->
</div> <!-- </div>-->
</template> <!-- </template>-->
<template v-if="groupList.length > 0"> <template v-if="groupList.length > 0">
<div style="font-size: 16px;font-weight: bold;color: #000;text-align: left;margin-bottom: 5px">可用分组</div> <div style="font-size: 16px;font-weight: bold;color: #000;text-align: left;margin-bottom: 5px">可用分组</div>
<div class="groupList"> <div class="groupList">

View File

@ -1,10 +1,10 @@
<template> <template>
<el-card style="width: 100%;height: 100%"> <el-card style="width: 100%;height: 100%">
<template #header> <!-- <template #header>-->
<div style="text-align: left"> <!-- <div style="text-align: left">-->
<el-button type="danger" @click="deleteClassRoom">删除班级</el-button> <!-- <el-button type="danger" @click="deleteClassRoom">删除班级</el-button>-->
</div> <!-- </div>-->
</template> <!-- </template>-->
<el-descriptions :column="1"> <el-descriptions :column="1">
<el-descriptions-item label="班级名称">{{ classInfo.caption }}</el-descriptions-item> <el-descriptions-item label="班级名称">{{ classInfo.caption }}</el-descriptions-item>
<el-descriptions-item label="教师"> <el-descriptions-item label="教师">

View File

@ -6,11 +6,12 @@
<div :style="{'max-height': (viewportHeight - 120) + 'px','overflow-y': 'auto'}"> <div :style="{'max-height': (viewportHeight - 120) + 'px','overflow-y': 'auto'}">
<Aside :menuItems="menuItems" :classList="classList" @handleSelect="handleSelect"></Aside> <Aside :menuItems="menuItems" :classList="classList" @handleSelect="handleSelect"></Aside>
</div> </div>
<template #footer> <!-- 隐藏操作按钮-->
<div> <!-- <template #footer>-->
<el-button @click="addClass" type="primary" :icon="Plus" >新增班级</el-button> <!-- <div>-->
</div> <!-- <el-button @click="addClass" type="primary" :icon="Plus" >新增班级</el-button>-->
</template> <!-- </div>-->
<!-- </template>-->
</el-card> </el-card>
</el-aside> </el-aside>
<el-main :style="{'min-height': (viewportHeight - 160) + 'px'}"> <el-main :style="{'min-height': (viewportHeight - 160) + 'px'}">

View File

@ -3,10 +3,10 @@
<el-card style="width: 100%;height: 100%;overflow-y: auto"> <el-card style="width: 100%;height: 100%;overflow-y: auto">
<template #header> <template #header>
<div style="text-align: left;display: flex;justify-content: space-between"> <div style="text-align: left;display: flex;justify-content: space-between">
<div> <!-- <div>-->
<el-button type="primary" @click="addStudent(0)">新增学生</el-button> <!-- <el-button type="primary" @click="addStudent(0)">新增学生</el-button>-->
<el-button type="primary" @click="importStudent()">导入学生</el-button> <!-- <el-button type="primary" @click="importStudent()">导入学生</el-button>-->
</div> <!-- </div>-->
<el-text class="mx-1">点击学生头像查看学生信息</el-text> <el-text class="mx-1">点击学生头像查看学生信息</el-text>
</div> </div>
</template> </template>
@ -54,27 +54,27 @@
<el-form-item label="电话" prop="parentmobile"> <el-form-item label="电话" prop="parentmobile">
<el-input v-model="studentForm.parentmobile" placeholder="请输入电话" style="width: 50%"/> <el-input v-model="studentForm.parentmobile" placeholder="请输入电话" style="width: 50%"/>
</el-form-item> </el-form-item>
<div> <!-- <div>-->
<el-row :gutter="4"> <!-- <el-row :gutter="4">-->
<el-col :span="12"> <!-- <el-col :span="12">-->
<el-form-item label-width="100px" label="平台登录账号"> <!-- <el-form-item label-width="100px" label="平台登录账号">-->
系统自动创建 <!-- 系统自动创建-->
</el-form-item> <!-- </el-form-item>-->
</el-col> <!-- </el-col>-->
<el-col :span="12"> <!-- <el-col :span="12">-->
<el-form-item label-width="100px" label="平台登录密码"> <!-- <el-form-item label-width="100px" label="平台登录密码">-->
系统自动创建默认为123123 <!-- 系统自动创建默认为123123-->
</el-form-item> <!-- </el-form-item>-->
</el-col> <!-- </el-col>-->
</el-row> <!-- </el-row>-->
</div> <!-- </div>-->
</el-form> </el-form>
<template #footer> <!-- <template #footer>-->
<el-button type="warning" v-show="studentForm.id > 0" @click="delStudent(1)">移出班级</el-button> <!-- <el-button type="warning" v-show="studentForm.id > 0" @click="delStudent(1)">移出班级</el-button>-->
<el-button type="danger" v-show="studentForm.id > 0 && studentForm.editoruserid == userStore.userId" @click="delStudent(2)">删除学生</el-button> <!-- <el-button type="danger" v-show="studentForm.id > 0 && studentForm.editoruserid == userStore.userId" @click="delStudent(2)">删除学生</el-button>-->
<el-button @click="studentVisible = false"> </el-button> <!-- <el-button @click="studentVisible = false"> </el-button>-->
<el-button type="primary" @click="btnStudentSave"> </el-button> <!-- <el-button type="primary" @click="btnStudentSave"> </el-button>-->
</template> <!-- </template>-->
</el-dialog> </el-dialog>
<!-- 学生导入--> <!-- 学生导入-->
<el-dialog title="学生导入" v-model="importVisiable" :width="600" append-to-body> <el-dialog title="学生导入" v-model="importVisiable" :width="600" append-to-body>

View File

@ -144,12 +144,13 @@ 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 SetHomework from '@/components/set-homework/index.vue' import SetHomework from '@/components/set-homework/index.vue'
import outLink from '@/utils/linkConfig' import outLink from '@/utils/linkConfig'
import { createWindow } from '@/utils/tool' import { createWindow, sessionStore } from '@/utils/tool'
import { cloneDeep } from 'lodash' import { cloneDeep } from 'lodash'
import { delClasswork } from '@/api/teaching/classwork' import { delClasswork } from '@/api/teaching/classwork'
import { getSelfReserv, startClass } from '@/api/classManage' import { getSelfReserv, startClass } from '@/api/classManage'
import { useGetHomework } from '@/hooks/useGetHomework' import { useGetHomework } from '@/hooks/useGetHomework'
const toolStore = useToolState() const toolStore = useToolState()
const fs = require('fs') const fs = require('fs')
const { ipcRenderer } = window.electron || {} const { ipcRenderer } = window.electron || {}
@ -423,7 +424,8 @@ export default {
this.uploadData.levelThirdId = cata[2] this.uploadData.levelThirdId = cata[2]
this.uploadData.textbookId = data.textBook.curBookId this.uploadData.textbookId = data.textBook.curBookId
toolStore.curSubjectNode.data = data toolStore.curSubjectNode.data = data
toolStore.curSubjectNode.querySearch = this.uploadData //
this.$nextTick(() =>{ toolStore.curSubjectNode.querySearch = this.uploadData })
this.initHomeWork() this.initHomeWork()
await this.asyncAllFile() await this.asyncAllFile()
}, },

View File

@ -29,7 +29,7 @@ onMounted(async() => {
const handleMode = (newVal, oldVal) => { const handleMode = (newVal, oldVal) => {
if(toolStore.isPdfWin){ if(toolStore.isPdfWin){
if(newVal=='clear'){ if(newVal=='clear'){
emit('update:modelValue', oldVal) setTimeout(() => emit('update:modelValue', oldVal), 10)
} }
return return
} }
@ -47,7 +47,7 @@ const handleMode = (newVal, oldVal) => {
case 'clear': // case 'clear': //
if(oldVal){ if(oldVal){
FabricVue.history?.clean() FabricVue.history?.clean()
emit('update:modelValue', oldVal) setTimeout(() => emit('update:modelValue', oldVal), 10)
} }
break break

View File

@ -2,17 +2,19 @@
<div class="warp" ref="btnRef" :style="isFold?'min-height:auto;':''"> <div class="warp" ref="btnRef" :style="isFold?'min-height:auto;':''">
<slot name="start"></slot> <slot name="start"></slot>
<!-- 工具按钮 --> <!-- 工具按钮 -->
<el-space direction="vertical" v-show="!isFold"> <transition name="el-zoom-in-bottom">
<template v-for="(item,index) in list"> <el-space direction="vertical" v-show="!isFold">
<slot :name="item.prop" :item="item" :index="index"> <template v-for="(item,index) in list">
<div class="c-btn flex flex-col items-center gap-2 p-2" @click.stop="clickHandel(item,$event)"> <slot :name="item.prop" :item="item" :index="index">
<i class="iconfont" :class="item.icon" :style="item.style" /> <div class="c-btn flex flex-col items-center gap-2 p-2" @click.stop="clickHandel(item,$event)">
<span>{{item.label||item.text||item.name}}</span> <i class="iconfont" :class="item.icon" :style="item.style" />
</div> <span>{{item.label||item.text||item.name}}</span>
</slot> </div>
</template> </slot>
<slot name="append"></slot> </template>
</el-space> <slot name="append"></slot>
</el-space>
</transition>
<slot name="end"> <slot name="end">
<span class="fold" @click="isFold=!isFold" :style="isFold?'margin: 5px;':''"> <span class="fold" @click="isFold=!isFold" :style="isFold?'margin: 5px;':''">
{{isFold?'<<<':'>>>'}} {{isFold?'<<<':'>>>'}}

View File

@ -36,6 +36,7 @@ class Drag {
const {cx, cy} = this.getMousePos(e) const {cx, cy} = this.getMousePos(e)
this.x = cx this.x = cx
this.y = cy this.y = cy
this.getCurPos() // 被拖拽元素初始坐标
// 手动-触发事件 v-drag-start // 手动-触发事件 v-drag-start
this.el.dispatchEvent(new CustomEvent('v-drag-start', {detail:{drag: this}})) this.el.dispatchEvent(new CustomEvent('v-drag-start', {detail:{drag: this}}))
} }
@ -85,12 +86,20 @@ class Drag {
} }
// 获取移动后坐标 // 获取移动后坐标
getPos(x, y) { getPos(x, y) {
const w = this.max.w - this.toRound(this.el.clientWidth) // 边界控制:图标元素
const h = this.max.h - this.toRound(this.el.clientHeight) // const w = this.max.w - this.toRound(this.el.clientWidth)
// const h = this.max.h - this.toRound(this.el.clientHeight)
// 边界控制:整个工具
const w = this.max.w - this.toRound(this.handle.clientWidth)
const h = this.max.h - this.toRound(this.handle.clientHeight)
x = x < 0 ? 0 : x > w ? w : x x = x < 0 ? 0 : x > w ? w : x
y = y < 0 ? 0 : y > h ? h : y y = y < 0 ? 0 : y > h ? h : y
return { x, y } return { x, y }
} }
getCurPos(dom) {
const pos = this[dom||'handle']?.getBoundingClientRect()
this.data = {left:this.toRound(pos.left), top:this.toRound(pos.top)}
}
// 小数转整数 // 小数转整数
toRound = v => Math.round(v) toRound = v => Math.round(v)
} }

View File

@ -20,17 +20,19 @@
<el-image :src="logo" draggable="false" /> <el-image :src="logo" draggable="false" />
</div> </div>
</div> </div>
<div class="tool-btns" v-show="!isFold"> <transition name="a-fade">
<el-segmented class="c-btns" v-model="tabActive" :options="btnList" size="large" block <div class="tool-btns" v-if="!isFold">
@change="tabChange"> <el-segmented class="c-btns" v-model="tabActive" :options="btnList" size="large" block
<template #default="{item}"> @change="tabChange">
<div class="c-btn flex flex-col items-center gap-2 p-2"> <template #default="{item}">
<i class="iconfont" :class="item.icon" :style="item.style" /> <div class="c-btn flex flex-col items-center gap-2 p-2">
<span>{{item.label}}</span> <i class="iconfont" :class="item.icon" :style="item.style" />
</div> <span>{{item.label}}</span>
</template> </div>
</el-segmented> </template>
</div> </el-segmented>
</div>
</transition>
</div> </div>
</div> </div>
</template> </template>
@ -41,7 +43,7 @@ import { onMounted, ref, reactive, watchEffect } from 'vue'
import { useRoute } from 'vue-router'; import { useRoute } from 'vue-router';
import { ElMessageBox, ElMessage, ElLoading } from 'element-plus' import { ElMessageBox, ElMessage, ElLoading } from 'element-plus'
import * as classManageApi from '@/api/classManage' import * as classManageApi from '@/api/classManage'
import logo from '@root/resources/icon.png' // logo import logo from '@root/resources/logo.png' // logo
import boardVue from './components/board.vue' // - import boardVue from './components/board.vue' // -
import sideVue from './components/side.vue' // - import sideVue from './components/side.vue' // -
import upvoteVue from './components/upvote.vue' // - import upvoteVue from './components/upvote.vue' // -
@ -49,8 +51,9 @@ import imChat from './components/imChat.vue' // im-chat-子组件
import vDrag from './directive/drag' // - import vDrag from './directive/drag' // -
import vIgnore from './directive/ignore' // -穿 import vIgnore from './directive/ignore' // -穿
import { useToolState } from '@/store/modules/tool' // - import { useToolState } from '@/store/modules/tool' // -
import { ipcMsgSend, ipcHandle, ipcMain, ipcMsgInvoke } from '@/utils/tool' // import { ipcMsgSend, ipcMain, sessionStore } from '@/utils/tool' //
import MsgEnum from '@/plugins/imChat/msgEnum' // -(nuem) import MsgEnum from '@/plugins/imChat/msgEnum' // -(nuem)
const route = useRoute(); const route = useRoute();
const tabActive = ref('select') // const tabActive = ref('select') //
const isFold = ref(false) // const isFold = ref(false) //
@ -80,6 +83,7 @@ const btnList = [ // 工具栏按钮列表
// === === // === ===
onMounted(async() => { onMounted(async() => {
if (!electron) return // if (!electron) return //
// console.log(sessionStore)
getClassInfo() // ex3 getClassInfo() // ex3
setTimeout(() => { setTimeout(() => {
resetStatus() // - resetStatus() // -
@ -102,13 +106,29 @@ const getClassInfo = async () => {
const tabChange = (val) => { const tabChange = (val) => {
const bool = !toolStore.isPdfWin && !toolStore.showBoardAll const bool = !toolStore.isPdfWin && !toolStore.showBoardAll
if(bool) toolStore.showBoardAll = true if(bool) toolStore.showBoardAll = true
// ipcMsgSend('tool-sphere:close')
toolStore.model = val // tab toolStore.model = val // tab
} }
// logo - | // logo - |
const logoHandle = (e,t) => { const logoHandle = (e,t) => {
if (Date.now() - dragtime.value < 200) { if (Date.now() - dragtime.value < 200) {
isFold.value = !isFold.value isFold.value = !isFold.value
setTimeout(() => {
// :
const dom = document.querySelector('.tool-bottom-all')
const { x } = dom.getBoundingClientRect()
const w = window.innerWidth - (470 || 80)
// if (x > w) dom.style.left = `${w}px`
if (x > w) { //
let left = x
const animatFn = () => {
left-=30
if (left < w) left == w
dom.style.left = `${left}px`
if (left > w) requestAnimationFrame(animatFn)
}
requestAnimationFrame(animatFn)
}
}, 20);
} }
} }
// -穿 // -穿
@ -262,4 +282,15 @@ watchEffect(() => {
} }
} }
} }
.a-fade-leave-active,.a-fade-enter-active{
transition: all .3s;
}
.a-fade-enter-from,.a-fade-leave-to{
width: 0;
opacity: 0;
}
.a-fade-enter-to,.a-fade-leave-from{
width: 350px;
opacity: 1;
}
</style> </style>