Compare commits
No commits in common. "6e29229e9547de56d933ca3e9fee5d014772564a" and "df84893509dff5181894ab20e08d4d2206dbe965" have entirely different histories.
6e29229e95
...
df84893509
|
@ -31,7 +31,7 @@
|
||||||
"electron-log": "^5.1.7",
|
"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",
|
||||||
"js-cookie": "^3.0.5",
|
"js-cookie": "^3.0.5",
|
||||||
"jsencrypt": "^3.3.2",
|
"jsencrypt": "^3.3.2",
|
||||||
"jsondiffpatch": "0.6.0",
|
"jsondiffpatch": "0.6.0",
|
||||||
|
|
|
@ -31,17 +31,17 @@ function createLoginWindow() {
|
||||||
preload: join(__dirname, '../preload/index.js'),
|
preload: join(__dirname, '../preload/index.js'),
|
||||||
sandbox: false,
|
sandbox: false,
|
||||||
nodeIntegration: true,
|
nodeIntegration: true,
|
||||||
contextIsolation: false // 沙箱取消
|
contextIsolation: false, // 沙箱取消
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
loginWindow.type = 'login' // 唯一标识
|
loginWindow.type = 'login' // 唯一标识
|
||||||
// handleUpdate(loginWindow,ipcMain)
|
// handleUpdate(loginWindow,ipcMain)
|
||||||
// const loginURL = is.dev ? `http://localhost:5173/#/login` : `file://${__dirname}/index.html/#/login`
|
// const loginURL = is.dev ? `http://localhost:5173/#/login` : `file://${__dirname}/index.html/#/login`
|
||||||
// loginWindow.loadURL(loginURL)
|
// loginWindow.loadURL(loginURL)
|
||||||
if (is.dev && process.env['ELECTRON_RENDERER_URL']) {
|
if (is.dev && process.env['ELECTRON_RENDERER_URL']) {
|
||||||
loginWindow.loadURL('http://localhost:5173/#/login')
|
loginWindow.loadURL('http://localhost:5173/#/login')
|
||||||
} else {
|
} else {
|
||||||
loginWindow.loadFile(join(__dirname, '../renderer/index.html'), { hash: 'login' })
|
loginWindow.loadFile(join(__dirname, '../renderer/index.html'), {hash: 'login'})
|
||||||
updateInit(loginWindow)
|
updateInit(loginWindow)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -70,18 +70,17 @@ function createMainWindow() {
|
||||||
preload: join(__dirname, '../preload/index.js'),
|
preload: join(__dirname, '../preload/index.js'),
|
||||||
sandbox: false,
|
sandbox: false,
|
||||||
// nodeIntegration: true,
|
// nodeIntegration: true,
|
||||||
nodeIntegration: true, // nodeApi调用
|
nodeIntegration: true, // nodeApi调用
|
||||||
contextIsolation: false // 沙箱取消
|
contextIsolation: false, // 沙箱取消
|
||||||
// webSecurity: false // 跨域关闭
|
// webSecurity: false // 跨域关闭
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
mainWindow.type = 'main' // 唯一标识
|
mainWindow.type = 'main' // 唯一标识
|
||||||
mainWindow.on('ready-to-show', () => {
|
mainWindow.on('ready-to-show', () => {
|
||||||
mainWindow.show()
|
mainWindow.show()
|
||||||
})
|
})
|
||||||
mainWindow.on('closed', () => {
|
mainWindow.on('closed', () => {
|
||||||
setTimeout(() => {
|
setTimeout(() => { // 延迟销毁
|
||||||
// 延迟销毁
|
|
||||||
mainWindow = null
|
mainWindow = null
|
||||||
}, 1000)
|
}, 1000)
|
||||||
// app.quit() // 主窗口关闭-结束所有进程
|
// app.quit() // 主窗口关闭-结束所有进程
|
||||||
|
@ -93,7 +92,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'))
|
||||||
}
|
}
|
||||||
|
@ -103,6 +102,7 @@ function createMainWindow() {
|
||||||
remote.enable(mainWindow.webContents)
|
remote.enable(mainWindow.webContents)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// 作业窗口相关-开发中
|
// 作业窗口相关-开发中
|
||||||
let linkWindow
|
let linkWindow
|
||||||
async function createLinkWin(data) {
|
async function createLinkWin(data) {
|
||||||
|
@ -120,17 +120,14 @@ async function createLinkWin(data) {
|
||||||
contextIsolation: true
|
contextIsolation: true
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
linkWindow.type = 'link' // 唯一标识
|
linkWindow.type = 'link' // 唯一标识
|
||||||
let cookieDetails = { ...data.cookieData }
|
let cookieDetails = { ...data.cookieData }
|
||||||
await linkWindow.webContents.session.cookies
|
await linkWindow.webContents.session.cookies.set(cookieDetails).then(()=>{
|
||||||
.set(cookieDetails)
|
console.log('Cookie is successful');
|
||||||
.then(() => {
|
}).catch( error =>{
|
||||||
console.log('Cookie is successful')
|
console.error('Cookie is error', error);
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
|
||||||
console.error('Cookie is error', error)
|
|
||||||
})
|
|
||||||
data.fullPath = data.fullPath.replaceAll('//', '/')
|
|
||||||
linkWindow.loadURL(data.fullPath)
|
linkWindow.loadURL(data.fullPath)
|
||||||
|
|
||||||
linkWindow.once('ready-to-show', () => {
|
linkWindow.once('ready-to-show', () => {
|
||||||
|
@ -172,10 +169,9 @@ app.on('ready', () => {
|
||||||
}
|
}
|
||||||
if (mainWindow) {
|
if (mainWindow) {
|
||||||
mainWindow.close() // 先发出这个关闭指令
|
mainWindow.close() // 先发出这个关闭指令
|
||||||
setTimeout(() => {
|
setTimeout(() => { //
|
||||||
//
|
|
||||||
mainWindow.destroy()
|
mainWindow.destroy()
|
||||||
}, 200)
|
}, 200);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -203,19 +199,21 @@ app.on('ready', () => {
|
||||||
createLinkWin(data)
|
createLinkWin(data)
|
||||||
})
|
})
|
||||||
// 新窗口创建-监听
|
// 新窗口创建-监听
|
||||||
ipcMain.on('new-window', (e, data) => {
|
ipcMain.on('new-window', (e,data) => {
|
||||||
const { id, type } = data
|
const { id, type } = data
|
||||||
const win = BrowserWindow.fromId(id)
|
const win = BrowserWindow.fromId(id)
|
||||||
win.type = type // 绑定独立标识
|
win.type = type // 绑定独立标识
|
||||||
remote.enable(win.webContents) // 开启远程服务
|
remote.enable(win.webContents) // 开启远程服务
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
// 打开-登录窗口
|
// 打开-登录窗口
|
||||||
createLoginWindow()
|
createLoginWindow()
|
||||||
|
|
||||||
app.on('activate', function () {
|
app.on('activate', function () {
|
||||||
if (BrowserWindow.getAllWindows().length === 0) createLoginWindow()
|
if (BrowserWindow.getAllWindows().length === 0) createLoginWindow()
|
||||||
})
|
})
|
||||||
|
|
||||||
})
|
})
|
||||||
|
|
||||||
// Quit when all windows are closed, except on macOS. There, it's common
|
// Quit when all windows are closed, except on macOS. There, it's common
|
||||||
|
|
|
@ -1,81 +0,0 @@
|
||||||
/**
|
|
||||||
* @description: v-drag
|
|
||||||
* @author zdg
|
|
||||||
* @date 2023-06-07
|
|
||||||
*/
|
|
||||||
// 工具包
|
|
||||||
const utils = {
|
|
||||||
// Creates an event handler that can be used in Vue code
|
|
||||||
// setup start moving end
|
|
||||||
vueDragEvent: (el, action) => {
|
|
||||||
el.dispatchEvent(new Event(`drag-${action}`))
|
|
||||||
},
|
|
||||||
dragStart: (el, target, axis, snap, e) => {
|
|
||||||
// el.style.cursor = 'move'
|
|
||||||
// el.onmousedown = function (e) {
|
|
||||||
// const disX = e.clientX - el.offsetLeft
|
|
||||||
// const disY = e.clientY - el.offsetTop
|
|
||||||
// document.onmousemove = function (e) {
|
|
||||||
// const left = e.clientX - disX
|
|
||||||
// const top= e.clientY - disY
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
},
|
|
||||||
}
|
|
||||||
// 首次向元素添加可拖动配置 | Add draggable configuration to element for the first time
|
|
||||||
const mountedHook = (el, binding) => {
|
|
||||||
console.log(el, binding)
|
|
||||||
const value = binding.value || {}
|
|
||||||
const handleSelector = value instanceof Object ? value.el : value // 获取元素
|
|
||||||
const isOpen = value instanceof Object ? value.open || true : true // 是否开启拖拽 默认:开启
|
|
||||||
const handleArray = [] // 拖拽元素
|
|
||||||
if (!isOpen) return false // 没有开启不加载后面的代码
|
|
||||||
let axis
|
|
||||||
// Store all the DOM elements that will be used as handles.
|
|
||||||
// They can be declared using a string with a CSS tag, class or id, or using Vue refs.
|
|
||||||
if (!!handleSelector) {
|
|
||||||
if (handleSelector instanceof HTMLElement) {
|
|
||||||
handleArray.push(handleSelector);
|
|
||||||
} else {
|
|
||||||
// handleArray.push(document.querySelectorAll(handleSelector));
|
|
||||||
document.querySelectorAll(handleSelector).forEach((child) => {
|
|
||||||
handleArray.push(child);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (handleArray.length !== 0) {
|
|
||||||
// Define move element and apply CSS class
|
|
||||||
// el.classList.add(window.data.class.usesHandle);
|
|
||||||
|
|
||||||
handleArray.forEach((grabElement) => {
|
|
||||||
// Apply CSS class to each grab element
|
|
||||||
// grabElement.classList.add(window.data.class.handle);
|
|
||||||
|
|
||||||
// Add events to start drag with handle
|
|
||||||
grabElement.onmousedown = (e) => utils.dragStart(grabElement, el, axis, e);
|
|
||||||
grabElement.ontouchstart = (e) => utils.dragStart(grabElement, el, axis, e);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
// Add events to start drag without handle
|
|
||||||
el.onmousedown = (e) => utils.dragStart(el, el, axis, e);
|
|
||||||
el.ontouchstart = (e) => utils.dragStart(el, el, axis, e);
|
|
||||||
}
|
|
||||||
// Vue event on setup
|
|
||||||
utils.vueDragEvent(el, 'setup')
|
|
||||||
}
|
|
||||||
export default {
|
|
||||||
// Hooks for Vue3
|
|
||||||
mounted(el, binding) {
|
|
||||||
mountedHook(el, binding)
|
|
||||||
},
|
|
||||||
// Hooks for Vue2
|
|
||||||
inserted(el, binding) {
|
|
||||||
mountedHook(el, binding)
|
|
||||||
},
|
|
||||||
|
|
||||||
update(el, binding){
|
|
||||||
},
|
|
||||||
updated(el, binding){
|
|
||||||
|
|
||||||
},
|
|
||||||
}
|
|
|
@ -1,15 +1,7 @@
|
||||||
<template>
|
<template>
|
||||||
<section class="app-main">
|
<section class="app-main">
|
||||||
<div class="app-main-left no-select">
|
|
||||||
<div v-for="(item, index) in title" :key="index" :class="item.active?'active':''" class="app-main-left-item" @click="active(index)">
|
|
||||||
<div class="app-main-left-item-icon">
|
|
||||||
<i :class="item.img"></i>
|
|
||||||
</div>
|
|
||||||
<div class="app-main-left-item-text">{{item.name}}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<transition mode="out-in" name="fade-transform">
|
<transition mode="out-in" name="fade-transform">
|
||||||
<div v-show="$route != null" style="height: 100%; flex: 1">
|
<div v-show="$route != null" style="height: 100%">
|
||||||
<router-view v-slot="{ Component, route }">
|
<router-view v-slot="{ Component, route }">
|
||||||
<keep-alive>
|
<keep-alive>
|
||||||
<component :is="Component" v-if="route.meta.keepAlive" :key="route.name" />
|
<component :is="Component" v-if="route.meta.keepAlive" :key="route.name" />
|
||||||
|
@ -21,170 +13,10 @@
|
||||||
</section>
|
</section>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup></script>
|
||||||
import { reactive, onMounted } from 'vue'
|
|
||||||
import { useRouter } from 'vue-router'
|
|
||||||
import routerStore from '@/store/modules/route'
|
|
||||||
const router = useRouter()
|
|
||||||
const title = reactive([
|
|
||||||
{
|
|
||||||
name: '教学工作台',
|
|
||||||
img: 'iconfont icon-gongzuotai',
|
|
||||||
id: 1,
|
|
||||||
child: [
|
|
||||||
{
|
|
||||||
name: '课程教学',
|
|
||||||
img: 'iconfont icon-PPT',
|
|
||||||
type: 'hash',
|
|
||||||
url: '/prepare',
|
|
||||||
child1: []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: '作业管理',
|
|
||||||
img: 'iconfont icon-36zuoyepingtai',
|
|
||||||
url: '/teaching/classtaskassign?titleName=作业布置',
|
|
||||||
child1: []
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: '教学研究室',
|
|
||||||
img: 'iconfont icon-yanjiushi',
|
|
||||||
id: 2,
|
|
||||||
child: [
|
|
||||||
{
|
|
||||||
name: '资源研究',
|
|
||||||
url: '/resource',
|
|
||||||
type: 'hash',
|
|
||||||
img: 'iconfont icon-business-report',
|
|
||||||
child1: []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: '课标分析',
|
|
||||||
url: '/teaching/chatwithstandard',
|
|
||||||
img: 'iconfont icon-kecheng',
|
|
||||||
child1: []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: '教材分析',
|
|
||||||
url: '/teaching/chatwithtextbook',
|
|
||||||
img: 'iconfont icon-yanjiushi',
|
|
||||||
child1: []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: '高考研究',
|
|
||||||
url: '/education/colentrance',
|
|
||||||
img: 'iconfont icon-icon_kaoshifenxi',
|
|
||||||
child1: []
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: '教学资源库',
|
|
||||||
img: 'iconfont icon-zhuanyeziyuanku',
|
|
||||||
id: 3,
|
|
||||||
child: [
|
|
||||||
{
|
|
||||||
name: '教学素材',
|
|
||||||
img: 'iconfont icon-sucai',
|
|
||||||
url: '/teaching/materialbank',
|
|
||||||
child1: []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: '课程资源',
|
|
||||||
img: 'iconfont icon-kechengziyuan',
|
|
||||||
url: '/teaching/coursewareresource',
|
|
||||||
child1: []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: '习题资源',
|
|
||||||
img: 'iconfont icon-iconku-zhuanqu-',
|
|
||||||
url: '/teaching/quesbank',
|
|
||||||
child1: []
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
])
|
|
||||||
const active = (index) => {
|
|
||||||
const route = routerStore()
|
|
||||||
title.forEach((item, i) => {
|
|
||||||
if (i === index) {
|
|
||||||
item.active = true
|
|
||||||
route.setNowRouter(item.child)
|
|
||||||
if (item.id !== 3) {
|
|
||||||
router.push(item.child[0].url)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
item.active = false
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
onMounted(()=>{
|
|
||||||
active(0)
|
|
||||||
})
|
|
||||||
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.no-select {
|
|
||||||
-webkit-user-select: none; /* Safari */
|
|
||||||
-moz-user-select: none; /* Firefox */
|
|
||||||
-ms-user-select: none; /* IE10+ */
|
|
||||||
user-select: none; /* Standard syntax */
|
|
||||||
}
|
|
||||||
.app-main {
|
.app-main {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
.app-main-left {
|
|
||||||
width: 80px;
|
|
||||||
height: 100%;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
.app-main-left-item {
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
flex-direction: column;
|
|
||||||
width: 65px;
|
|
||||||
height: 65px;
|
|
||||||
padding-top: 10px;
|
|
||||||
margin: 10px 0;
|
|
||||||
cursor: pointer;
|
|
||||||
&:hover {
|
|
||||||
background: #839ce0;
|
|
||||||
border-radius: 10px;
|
|
||||||
.app-main-left-item-icon {
|
|
||||||
background: #fff;
|
|
||||||
color: #758fd3;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
&.active {
|
|
||||||
background: #839ce0;
|
|
||||||
border-radius: 10px;
|
|
||||||
.app-main-left-item-icon {
|
|
||||||
background: #fff;
|
|
||||||
color: #758fd3;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.app-main-left-item-text {
|
|
||||||
font-size: 12px;
|
|
||||||
}
|
|
||||||
.app-main-left-item-icon {
|
|
||||||
width: 40px;
|
|
||||||
height: 40px;
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
border-radius: 10px;
|
|
||||||
background: #7c97e1;
|
|
||||||
color: #fff;
|
|
||||||
i {
|
|
||||||
font-size: 24px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -4,15 +4,9 @@
|
||||||
<h3 class="title" @click="changeTab">AIX智慧课堂</h3>
|
<h3 class="title" @click="changeTab">AIX智慧课堂</h3>
|
||||||
<div class="change-tab">
|
<div class="change-tab">
|
||||||
<ul class="flex">
|
<ul class="flex">
|
||||||
<li
|
<li v-for="item in menus" :key="item.path" class="flex"
|
||||||
v-for="(item, index) in routeHeader.nowRouter"
|
:class="currentRoute == item.path ? 'active-li' : ''" @click="changePage(item.path)">
|
||||||
:key="index"
|
<i class="iconfont" :class="item.icon"></i>
|
||||||
class="flex"
|
|
||||||
:style="{'color' : item.color}"
|
|
||||||
:class="currentRoute === item.url ? 'active-li' : ''"
|
|
||||||
@click="handleOutLink(item.url,item.type)"
|
|
||||||
>
|
|
||||||
<i :class="item.img"></i>
|
|
||||||
<span class="text">{{ item.name }}</span>
|
<span class="text">{{ item.name }}</span>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
@ -20,17 +14,13 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="right-section flex">
|
<div class="right-section flex">
|
||||||
<WindowTools />
|
<WindowTools/>
|
||||||
<div class="user flex">
|
<div class="user flex">
|
||||||
<div class="avatar-container">
|
<div class="avatar-container">
|
||||||
<el-dropdown
|
<el-dropdown @command="handleCommand" class="right-menu-item hover-effect" trigger="click">
|
||||||
class="right-menu-item hover-effect"
|
|
||||||
trigger="click"
|
|
||||||
@command="handleCommand"
|
|
||||||
>
|
|
||||||
<div class="avatar-wrapper">
|
<div class="avatar-wrapper">
|
||||||
<img :src="userStore.user.avatar" class="user-avatar" style="float: left" />
|
<img :src="userStore.user.avatar" class="user-avatar" style="float: left;" />
|
||||||
<div style="margin-top: 18px; font-size: 0.8em">{{ userStore.user.nickName }}</div>
|
<div style="margin-top: 18px; font-size: 0.8em;"> {{ userStore.user.nickName }}</div>
|
||||||
</div>
|
</div>
|
||||||
<template #dropdown>
|
<template #dropdown>
|
||||||
<el-dropdown-menu>
|
<el-dropdown-menu>
|
||||||
|
@ -54,31 +44,13 @@ import { useRouter } from 'vue-router'
|
||||||
import { ElMessageBox } from 'element-plus'
|
import { ElMessageBox } from 'element-plus'
|
||||||
import WindowTools from '@/components/window-tools/index.vue'
|
import WindowTools from '@/components/window-tools/index.vue'
|
||||||
import useUserStore from '@/store/modules/user'
|
import useUserStore from '@/store/modules/user'
|
||||||
import routerStore from '@/store/modules/route'
|
|
||||||
import outLink from '@/utils/linkConfig'
|
|
||||||
const routeHeader = routerStore()
|
|
||||||
const { ipcRenderer } = window.electron || {}
|
const { ipcRenderer } = window.electron || {}
|
||||||
const userStore = useUserStore()
|
const userStore = useUserStore()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const currentRoute = ref('')
|
const currentRoute = ref('')
|
||||||
|
|
||||||
const handleOutLink = (path, type) => {
|
const menus = ref([
|
||||||
if (!path) return
|
|
||||||
if (type === 'hash') {
|
|
||||||
router.push(path)
|
|
||||||
} else {
|
|
||||||
// key 对应的 linkConfig.js 外部链接配置
|
|
||||||
let configObj = outLink().getBaseData()
|
|
||||||
let fullPath = configObj.fullPath + path
|
|
||||||
fullPath = fullPath.replaceAll('//', '/')
|
|
||||||
// 通知主进程
|
|
||||||
ipcRenderer.send('openWindow', {
|
|
||||||
fullPath: fullPath,
|
|
||||||
cookieData: { ...configObj.data }
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/*const menus = ref([
|
|
||||||
{
|
{
|
||||||
icon: 'icon-zhuye2 icon-homepage',
|
icon: 'icon-zhuye2 icon-homepage',
|
||||||
name: '主页',
|
name: '主页',
|
||||||
|
@ -94,12 +66,12 @@ const handleOutLink = (path, type) => {
|
||||||
name: '备课',
|
name: '备课',
|
||||||
path: '/prepare'
|
path: '/prepare'
|
||||||
},
|
},
|
||||||
{
|
/*{
|
||||||
icon: 'icon-jiangke1 icon-teach',
|
icon: 'icon-jiangke1 icon-teach',
|
||||||
name: '授课',
|
name: '授课',
|
||||||
path: '/teach'
|
path: '/teach'
|
||||||
}
|
}*/
|
||||||
])*/
|
])
|
||||||
|
|
||||||
// 监听当前路由
|
// 监听当前路由
|
||||||
watch(
|
watch(
|
||||||
|
@ -110,19 +82,20 @@ watch(
|
||||||
{ immediate: true }
|
{ immediate: true }
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
const changePage = (url) => {
|
const changePage = (url) => {
|
||||||
router.push(url)
|
router.push(url)
|
||||||
}
|
}
|
||||||
function handleCommand(command) {
|
function handleCommand(command) {
|
||||||
switch (command) {
|
switch (command) {
|
||||||
case 'setLayout':
|
case "setLayout":
|
||||||
setLayout()
|
setLayout();
|
||||||
break
|
break;
|
||||||
case 'logout':
|
case "logout":
|
||||||
logout()
|
logout();
|
||||||
break
|
break;
|
||||||
default:
|
default:
|
||||||
break
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -131,25 +104,20 @@ function logout() {
|
||||||
confirmButtonText: '确定',
|
confirmButtonText: '确定',
|
||||||
cancelButtonText: '取消',
|
cancelButtonText: '取消',
|
||||||
type: 'warning'
|
type: 'warning'
|
||||||
})
|
}).then(() => {
|
||||||
.then(() => {
|
userStore.logOut().then(() => {
|
||||||
userStore
|
// router.replace('/login')
|
||||||
.logOut()
|
ipcRenderer && ipcRenderer.send('openLoginWindow')
|
||||||
.then(() => {
|
}).catch(()=>{
|
||||||
// router.replace('/login')
|
// router.replace('/login')
|
||||||
ipcRenderer && ipcRenderer.send('openLoginWindow')
|
ipcRenderer && ipcRenderer.send('openLoginWindow')
|
||||||
})
|
|
||||||
.catch(() => {
|
|
||||||
// router.replace('/login')
|
|
||||||
ipcRenderer && ipcRenderer.send('openLoginWindow')
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
.catch(() => {})
|
}).catch(() => { });
|
||||||
}
|
}
|
||||||
|
|
||||||
const emits = defineEmits(['setLayout'])
|
const emits = defineEmits(['setLayout'])
|
||||||
function setLayout() {
|
function setLayout() {
|
||||||
emits('setLayout')
|
emits('setLayout');
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@ -192,7 +160,7 @@ function setLayout() {
|
||||||
color: #f99b53;
|
color: #f99b53;
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-homepage {
|
.icon-homepage{
|
||||||
color: #0a84ff;
|
color: #0a84ff;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -211,7 +179,7 @@ function setLayout() {
|
||||||
|
|
||||||
.active-li {
|
.active-li {
|
||||||
background: #d3e3fb;
|
background: #d3e3fb;
|
||||||
color: #409eff;
|
color: #409EFF;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -235,6 +203,7 @@ function setLayout() {
|
||||||
padding-bottom: 5px;
|
padding-bottom: 5px;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|
||||||
|
|
||||||
.user {
|
.user {
|
||||||
.user-info {
|
.user-info {
|
||||||
padding-right: 5px;
|
padding-right: 5px;
|
||||||
|
|
|
@ -32,6 +32,6 @@ let uploaderStore = ref(uploaderState())
|
||||||
height: 80px;
|
height: 80px;
|
||||||
}
|
}
|
||||||
.el-main {
|
.el-main {
|
||||||
--el-main-padding: 0 20px 0 0;
|
--el-main-padding: 0 20px;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -0,0 +1,236 @@
|
||||||
|
/**
|
||||||
|
* @description: 点击事件
|
||||||
|
*/
|
||||||
|
import { FabricVue } from '@/plugins/fabric'
|
||||||
|
import * as TYPES from '@/constants/draw'
|
||||||
|
import * as fabricStore from '@/store/modules/draw'
|
||||||
|
|
||||||
|
export let updateInkHook = null // ((ink: IInk[]) => void) | null
|
||||||
|
|
||||||
|
export class CanvasClickEvent {
|
||||||
|
isMouseDown = false
|
||||||
|
isSpaceKeyDown = false
|
||||||
|
startPoint // fabric.Point | undefined
|
||||||
|
currentElement = null // The current mouse move draws the element|当前鼠标移动会绘制元素
|
||||||
|
|
||||||
|
mouseDownTime = 0
|
||||||
|
autoDrawInk = [[], [], []] // google auto draw ink Array<Array<number>>
|
||||||
|
isDrawBasic = false // 是否默认绘制
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.initClickEvent()
|
||||||
|
}
|
||||||
|
|
||||||
|
initClickEvent() {
|
||||||
|
const canvas = FabricVue.canvas
|
||||||
|
const useDrawStore = fabricStore.useDrawStore()
|
||||||
|
const useBoardStore = fabricStore.useBoardStore()
|
||||||
|
|
||||||
|
// 事件:按下鼠标
|
||||||
|
canvas?.on('mouse:down', (e) => {
|
||||||
|
console.log(222222222222222222222222222)
|
||||||
|
|
||||||
|
this.isMouseDown = true
|
||||||
|
if (this.isSpaceKeyDown) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.startPoint = e.absolutePointer
|
||||||
|
let currentElement = null
|
||||||
|
|
||||||
|
if (useBoardStore.mode === TYPES.ActionMode.DRAW) {
|
||||||
|
if (useBoardStore.drawType === TYPES.DrawType.Shape) {
|
||||||
|
// switch (useShapeStore.shapeStyle) {
|
||||||
|
// case ShapeStyle.Rect:
|
||||||
|
// currentElement = new RectShape(e.absolutePointer)
|
||||||
|
// break
|
||||||
|
// case ShapeStyle.Circle:
|
||||||
|
// currentElement = new CircleShape(e.absolutePointer)
|
||||||
|
// break
|
||||||
|
// case ShapeStyle.Line:
|
||||||
|
// currentElement = new LineShape(e.absolutePointer)
|
||||||
|
// break
|
||||||
|
// case ShapeStyle.Ellipse:
|
||||||
|
// currentElement = new EllipseShape(e.absolutePointer)
|
||||||
|
// break
|
||||||
|
// case ShapeStyle.Triangle:
|
||||||
|
// currentElement = new TriangleShape(e.absolutePointer)
|
||||||
|
// break
|
||||||
|
// case ShapeStyle.ArrowLine:
|
||||||
|
// currentElement = new ArrowLineShape(e.absolutePointer)
|
||||||
|
// break
|
||||||
|
// case ShapeStyle.ArrowOutline:
|
||||||
|
// currentElement = new ArrowOutlineShape(e.absolutePointer)
|
||||||
|
// break
|
||||||
|
// case ShapeStyle.Cloud:
|
||||||
|
// currentElement = new CloudShape(e.absolutePointer)
|
||||||
|
// break
|
||||||
|
// case ShapeStyle.Tooltips:
|
||||||
|
// currentElement = new TooltipsShape(e.absolutePointer)
|
||||||
|
// break
|
||||||
|
// case ShapeStyle.Lightning:
|
||||||
|
// currentElement = new LightningShape(e.absolutePointer)
|
||||||
|
// break
|
||||||
|
// case ShapeStyle.Close:
|
||||||
|
// currentElement = new CloseShape(e.absolutePointer)
|
||||||
|
// break
|
||||||
|
// case ShapeStyle.Check:
|
||||||
|
// currentElement = new CheckShap(e.absolutePointer)
|
||||||
|
// break
|
||||||
|
// case ShapeStyle.Info:
|
||||||
|
// currentElement = new InfoShape(e.absolutePointer)
|
||||||
|
// break
|
||||||
|
// case ShapeStyle.Backspace:
|
||||||
|
// currentElement = new BackspaceShape(e.absolutePointer)
|
||||||
|
// break
|
||||||
|
// case ShapeStyle.Block:
|
||||||
|
// currentElement = new BlockShap(e.absolutePointer)
|
||||||
|
// break
|
||||||
|
// case ShapeStyle.Speaker:
|
||||||
|
// currentElement = new SpeakerShape(e.absolutePointer)
|
||||||
|
// break
|
||||||
|
// case ShapeStyle.Search:
|
||||||
|
// currentElement = new SearchShape(e.absolutePointer)
|
||||||
|
// break
|
||||||
|
// case ShapeStyle.InfoOutline:
|
||||||
|
// currentElement = new InfoOutlineShape(e.absolutePointer)
|
||||||
|
// break
|
||||||
|
// case ShapeStyle.Heart:
|
||||||
|
// currentElement = new HeartShape(e.absolutePointer)
|
||||||
|
// break
|
||||||
|
// case ShapeStyle.Alert:
|
||||||
|
// currentElement = new AlertShape(e.absolutePointer)
|
||||||
|
// break
|
||||||
|
// default:
|
||||||
|
// break
|
||||||
|
// }
|
||||||
|
} else if (useBoardStore.drawType === TYPES.DrawType.FreeStyle) {
|
||||||
|
switch (useDrawStore.drawStyle) {
|
||||||
|
// case TYPES.DrawStyle.Shape:
|
||||||
|
// currentElement = new ShapeElement()
|
||||||
|
// break
|
||||||
|
// case TYPES.DrawStyle.Pixels:
|
||||||
|
// currentElement = new PixelsElement()
|
||||||
|
// break
|
||||||
|
// case TYPES.DrawStyle.Text:
|
||||||
|
// currentElement = new DrawTextElement()
|
||||||
|
// break
|
||||||
|
// case TYPES.DrawStyle.MultiLine:
|
||||||
|
// currentElement = new MultiLineElement()
|
||||||
|
// break
|
||||||
|
// case TYPES.DrawStyle.Reticulate:
|
||||||
|
// currentElement = new ReticulateElement()
|
||||||
|
// break
|
||||||
|
// case TYPES.DrawStyle.Rainbow:
|
||||||
|
// currentElement = new RainbowElement()
|
||||||
|
// break
|
||||||
|
// case TYPES.DrawStyle.Thorn:
|
||||||
|
// currentElement = new ThornElement()
|
||||||
|
// break
|
||||||
|
// case TYPES.DrawStyle.MultiPoint:
|
||||||
|
// currentElement = new MultiPointElement()
|
||||||
|
// break
|
||||||
|
// case TYPES.DrawStyle.Wiggle:
|
||||||
|
// currentElement = new WiggleElement()
|
||||||
|
// break
|
||||||
|
case TYPES.DrawStyle.Basic:
|
||||||
|
// if (useDrawStore.openAutoDraw) {
|
||||||
|
// autoDrawData.resetLoadedSVG()
|
||||||
|
// this.mouseDownTime = new Date().getTime()
|
||||||
|
// }
|
||||||
|
this.isDrawBasic = true
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.currentElement = currentElement
|
||||||
|
})
|
||||||
|
|
||||||
|
// 事件:移动鼠标
|
||||||
|
canvas?.on('mouse:move', (e) => {
|
||||||
|
console.log(222222222222222222222222222)
|
||||||
|
|
||||||
|
if (this.isMouseDown) {
|
||||||
|
// Press space, drag the canvas, stop drawing.
|
||||||
|
if (this.isSpaceKeyDown) {
|
||||||
|
canvas.relativePan(new fabric.Point(e.e.movementX, e.e.movementY))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// two touch disabled drawing on mobile
|
||||||
|
if (FabricVue.evnet?.touchEvent?.isTwoTouch) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
this.mouseDownTime &&
|
||||||
|
e.absolutePointer?.x &&
|
||||||
|
e.absolutePointer?.y
|
||||||
|
) {
|
||||||
|
this.autoDrawInk[0].push(e.absolutePointer?.x)
|
||||||
|
this.autoDrawInk[1].push(e.absolutePointer?.y)
|
||||||
|
this.autoDrawInk[2].push(new Date().getTime() - this.mouseDownTime)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
useBoardStore.mode === TYPES.ActionMode.DRAW && this.currentElement
|
||||||
|
) {
|
||||||
|
this.currentElement.addPosition(e.absolutePointer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// 事件:松开鼠标
|
||||||
|
canvas?.on('mouse:up', (e) => {
|
||||||
|
console.log(222222222222222222222222222)
|
||||||
|
|
||||||
|
this.isMouseDown = false
|
||||||
|
if (this.autoDrawInk?.[0]?.length > 3) {
|
||||||
|
autoDrawData.addInk([...this.autoDrawInk])
|
||||||
|
updateInkHook?.([...autoDrawData.inks])
|
||||||
|
this.autoDrawInk = [[], [], []]
|
||||||
|
this.mouseDownTime = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.currentElement) {
|
||||||
|
let isDestroy = false
|
||||||
|
if (this.startPoint && e.absolutePointer) {
|
||||||
|
const { x: startX, y: startY } = this.startPoint
|
||||||
|
const { x: endX, y: endY } = e.absolutePointer
|
||||||
|
if (startX === endX && startY === endY) {
|
||||||
|
this.currentElement.destroy()
|
||||||
|
isDestroy = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!isDestroy) {
|
||||||
|
if (
|
||||||
|
this.currentElement instanceof LineShape ||
|
||||||
|
this.currentElement instanceof ArrowLineShape
|
||||||
|
) {
|
||||||
|
this.currentElement?.mouseUp()
|
||||||
|
}
|
||||||
|
FabricVue.history?.saveState()
|
||||||
|
}
|
||||||
|
this.currentElement = null
|
||||||
|
}
|
||||||
|
// zdg: 基础画笔 保存数据
|
||||||
|
if (this.isDrawBasic) FabricVue.history?.saveState()
|
||||||
|
})
|
||||||
|
|
||||||
|
canvas?.on('mouse:dblclick', (e) => {
|
||||||
|
if (e?.absolutePointer) {
|
||||||
|
const { x, y } = e.absolutePointer
|
||||||
|
FabricVue.textElement?.loadText(x, y)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
setSpaceKeyDownState(isSpaceKeyDown) {
|
||||||
|
this.isSpaceKeyDown = isSpaceKeyDown
|
||||||
|
}
|
||||||
|
|
||||||
|
changeInkHookFn(fn) {
|
||||||
|
updateInkHook = fn
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,35 @@
|
||||||
|
import { CanvasClickEvent } from './clickEvent'
|
||||||
|
// import { ObjectEvent } from './objectEvent'
|
||||||
|
// import { CanvasTouchEvent } from './touchEvent'
|
||||||
|
// import { CanvasZoomEvent } from './zoomEvent'
|
||||||
|
// import { WindowEvent } from './windowEvent'
|
||||||
|
|
||||||
|
export class CanvasEvent {
|
||||||
|
clickEvent // CanvasClickEvent
|
||||||
|
zoomEvent // CanvasZoomEvent
|
||||||
|
objectEvent // ObjectEvent
|
||||||
|
windowEvent // WindowEvent
|
||||||
|
touchEvent // CanvasTouchEvent
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
const clickEvent = new CanvasClickEvent()
|
||||||
|
this.clickEvent = clickEvent
|
||||||
|
|
||||||
|
// const zoomEvent = new CanvasZoomEvent()
|
||||||
|
// this.zoomEvent = zoomEvent
|
||||||
|
|
||||||
|
// const objectEvent = new ObjectEvent()
|
||||||
|
// this.objectEvent = objectEvent
|
||||||
|
|
||||||
|
// const windowEvent = new WindowEvent()
|
||||||
|
// this.windowEvent = windowEvent
|
||||||
|
|
||||||
|
// const touchEvent = new CanvasTouchEvent()
|
||||||
|
// this.touchEvent = touchEvent
|
||||||
|
}
|
||||||
|
|
||||||
|
removeEvent() {
|
||||||
|
this.windowEvent.removeWindowEvent()
|
||||||
|
this.touchEvent.removeTouchEvent()
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,120 @@
|
||||||
|
/**
|
||||||
|
* 历史记录
|
||||||
|
*/
|
||||||
|
import { FabricVue } from '@/plugins/fabric'
|
||||||
|
import * as commUtils from '@/plugins/fabric/utils'
|
||||||
|
import * as bgUtils from '@/plugins/fabric/utils/bg'
|
||||||
|
import { diff, unpatch, patch } from 'jsondiffpatch'
|
||||||
|
import * as fabricStore from '@/store/modules/draw'
|
||||||
|
|
||||||
|
const initState = {}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Operation History
|
||||||
|
*/
|
||||||
|
export class History {
|
||||||
|
diffs = []
|
||||||
|
canvasData = {}
|
||||||
|
index = 0
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
const canvas = FabricVue.canvas
|
||||||
|
if (canvas) {
|
||||||
|
const canvasJson = commUtils.getCanvasJSON()
|
||||||
|
this.canvasData = cloneDeep(canvasJson ?? {})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 缓存相关数据json
|
||||||
|
saveState() {
|
||||||
|
const canvas = FabricVue?.canvas
|
||||||
|
if (canvas) {
|
||||||
|
const useFileStore = fabricStore.useFileStore()
|
||||||
|
this.diffs = this.diffs.slice(0, this.index)
|
||||||
|
const canvasJson = commUtils.getCanvasJSON()
|
||||||
|
const delta = diff(canvasJson, this.canvasData)
|
||||||
|
this.diffs.push(delta)
|
||||||
|
|
||||||
|
// More than 50 operations, remove initial state
|
||||||
|
if (this.diffs.length > 50) {
|
||||||
|
this.diffs.shift()
|
||||||
|
} else {
|
||||||
|
this.index++
|
||||||
|
}
|
||||||
|
this.canvasData = cloneDeep(canvasJson ?? {})
|
||||||
|
useFileStore.updateBoardData(canvasJson)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 撤销
|
||||||
|
undo() {
|
||||||
|
const canvas = FabricVue?.canvas
|
||||||
|
if (canvas && this.index > 0) {
|
||||||
|
const useFileStore = fabricStore.useFileStore()
|
||||||
|
const delta = this.diffs[this.index - 1]
|
||||||
|
this.index--
|
||||||
|
const canvasJson = patch(this.canvasData, delta)
|
||||||
|
canvas.loadFromJSON(canvasJson, () => {
|
||||||
|
commUtils.handleCanvasJSONLoaded(canvas)
|
||||||
|
|
||||||
|
canvas.requestRenderAll()
|
||||||
|
useFileStore.updateBoardData(canvasJson)
|
||||||
|
this.canvasData = cloneDeep(canvasJson ?? {})
|
||||||
|
FabricVue.triggerHook()
|
||||||
|
|
||||||
|
if ((delta)?.backgroundImage) {
|
||||||
|
handleBackgroundImageWhenCanvasSizeChange()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 退回|重做
|
||||||
|
redo() {
|
||||||
|
const canvas = FabricVue?.canvas
|
||||||
|
if (this.index < this.diffs.length && canvas) {
|
||||||
|
const useFileStore = fabricStore.useFileStore()
|
||||||
|
const delta = this.diffs[this.index]
|
||||||
|
this.index++
|
||||||
|
const canvasJson = unpatch(this.canvasData, delta)
|
||||||
|
canvas.loadFromJSON(canvasJson, () => {
|
||||||
|
handleCanvasJSONLoaded(canvas)
|
||||||
|
canvas.requestRenderAll()
|
||||||
|
|
||||||
|
useFileStore.updateBoardData(canvasJson)
|
||||||
|
this.canvasData = cloneDeep(canvasJson ?? {})
|
||||||
|
FabricVue.triggerHook()
|
||||||
|
|
||||||
|
if ((delta)?.backgroundImage) {
|
||||||
|
bgUtils.handleBackgroundImageWhenCanvasSizeChange()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
clean() {
|
||||||
|
const useFileStore = fabricStore.useFileStore()
|
||||||
|
const useBoardStore = fabricStore.useBoardStore()
|
||||||
|
FabricVue?.canvas?.clear()
|
||||||
|
this.index = 0
|
||||||
|
this.diffs = []
|
||||||
|
this.canvasData = {}
|
||||||
|
useFileStore.updateBoardData(initState)
|
||||||
|
useBoardStore.updateBackgroundColor('#ffffff')
|
||||||
|
useBoardStore.cleanBackgroundImage()
|
||||||
|
}
|
||||||
|
|
||||||
|
initHistory() {
|
||||||
|
const canvas = FabricVue.canvas
|
||||||
|
if (canvas) {
|
||||||
|
const canvasJson = commUtils.getCanvasJSON()
|
||||||
|
this.canvasData = canvasJson
|
||||||
|
this.index = 0
|
||||||
|
this.diffs = []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 对象克隆
|
||||||
|
function cloneDeep(obj = {}) {
|
||||||
|
return JSON.parse(JSON.stringify(obj))
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,27 @@
|
||||||
|
import { fabric } from 'fabric'
|
||||||
|
import { FabricVue } from '@/plugins/fabric'
|
||||||
|
import * as fabricUtils from '@/plugins/fabric/utils/draw'
|
||||||
|
import * as fabricStore from '@/store/modules/draw'
|
||||||
|
const useDrawStore = fabricStore.useDrawStore()
|
||||||
|
/**
|
||||||
|
* 基础 画笔
|
||||||
|
* @returns void
|
||||||
|
*/
|
||||||
|
export const renderPencilBrush = () => {
|
||||||
|
const canvas = FabricVue.canvas
|
||||||
|
if (!canvas) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const pencilBrush = new fabric.PencilBrush(canvas)
|
||||||
|
canvas.isDrawingMode = true
|
||||||
|
canvas.freeDrawingBrush = pencilBrush
|
||||||
|
canvas.freeDrawingBrush.width = fabricUtils.getDrawWidth()
|
||||||
|
canvas.freeDrawingBrush.color = useDrawStore.drawColors[0]
|
||||||
|
canvas.freeDrawingBrush.shadow = new fabric.Shadow({
|
||||||
|
blur: fabricUtils.getShadowWidth(),
|
||||||
|
offsetX: 0,
|
||||||
|
offsetY: 0,
|
||||||
|
color: useDrawStore.shadowColor
|
||||||
|
})
|
||||||
|
}
|
|
@ -0,0 +1,125 @@
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description: 材质
|
||||||
|
*/
|
||||||
|
import { FabricVue } from '@/plugins/fabric'
|
||||||
|
import * as TYPES from '@/constants/draw'
|
||||||
|
import * as fabricStore from '@/store/modules/draw'
|
||||||
|
|
||||||
|
export class Material {
|
||||||
|
initPromise = null // Initialize promise
|
||||||
|
crayonImage = null
|
||||||
|
carbonImage = null
|
||||||
|
clothImage = null
|
||||||
|
oilImage = null
|
||||||
|
crayonDarkImage = null
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.initMaterial()
|
||||||
|
}
|
||||||
|
|
||||||
|
async initMaterial() {
|
||||||
|
|
||||||
|
this.initPromise = Promise.all([
|
||||||
|
this.loadImage(TYPES.MATERIAL_TYPE.CRAYON),
|
||||||
|
this.loadImage(TYPES.MATERIAL_TYPE.CARBON),
|
||||||
|
this.loadImage(TYPES.MATERIAL_TYPE.CLOTH),
|
||||||
|
this.loadImage(TYPES.MATERIAL_TYPE.OIL),
|
||||||
|
this.loadImage(TYPES.MATERIAL_TYPE.CRAYON_DARK)
|
||||||
|
])
|
||||||
|
}
|
||||||
|
|
||||||
|
render({materialType, color}) {
|
||||||
|
const useDrawStore = fabricStore.useDrawStore()
|
||||||
|
materialType = materialType || useDrawStore.materialType
|
||||||
|
color = color || useDrawStore.drawColors[0]
|
||||||
|
this.initPromise?.then(() => {
|
||||||
|
switch (materialType) {
|
||||||
|
case TYPES.MATERIAL_TYPE.CRAYON:
|
||||||
|
this.renderMaterial(this.crayonImage, color)
|
||||||
|
break
|
||||||
|
case TYPES.MATERIAL_TYPE.CARBON:
|
||||||
|
this.renderMaterial(this.carbonImage, color)
|
||||||
|
break
|
||||||
|
case TYPES.MATERIAL_TYPE.CLOTH:
|
||||||
|
this.renderMaterial(this.clothImage, color)
|
||||||
|
break
|
||||||
|
case TYPES.MATERIAL_TYPE.OIL:
|
||||||
|
this.renderMaterial(this.oilImage, color)
|
||||||
|
break
|
||||||
|
case TYPES.MATERIAL_TYPE.CRAYON_DARK:
|
||||||
|
this.renderMaterial(this.crayonDarkImage, color)
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
renderMaterial(materialImg, color, opacity = 1) {
|
||||||
|
if (FabricVue.canvas) {
|
||||||
|
const useDrawStore = fabricStore.useDrawStore()
|
||||||
|
const patternBrush = new fabric.PatternBrush(FabricVue.canvas)
|
||||||
|
const patternCanvas = document.createElement('canvas')
|
||||||
|
patternCanvas.width = patternCanvas.height = 100
|
||||||
|
const context = patternCanvas.getContext('2d')
|
||||||
|
if (context) {
|
||||||
|
context.fillStyle = color
|
||||||
|
context.fillRect(0, 0, 100, 100)
|
||||||
|
if (materialImg) {
|
||||||
|
context.globalAlpha = opacity
|
||||||
|
context.drawImage(materialImg, 0, 0, 100, 100)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
patternBrush.getPatternSrc = () => {
|
||||||
|
return patternCanvas
|
||||||
|
}
|
||||||
|
patternBrush.getPatternSrcFunction = () => {
|
||||||
|
return patternCanvas
|
||||||
|
}
|
||||||
|
FabricVue.canvas.freeDrawingBrush = patternBrush
|
||||||
|
FabricVue.canvas.freeDrawingBrush.width = getDrawWidth()
|
||||||
|
FabricVue.canvas.freeDrawingBrush.shadow = new fabric.Shadow({
|
||||||
|
blur: getShadowWidth(),
|
||||||
|
offsetX: 0,
|
||||||
|
offsetY: 0,
|
||||||
|
color: useDrawStore.shadowColor
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
loadImage(imageName) {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
const img = new Image()
|
||||||
|
// img.src = formatPublicUrl(`pattern/${imageName}.png`)
|
||||||
|
img.src = `pattern/${imageName}.png`
|
||||||
|
img.onload = () => {
|
||||||
|
switch (imageName) {
|
||||||
|
case TYPES.MATERIAL_TYPE.CARBON:
|
||||||
|
this.carbonImage = img
|
||||||
|
break
|
||||||
|
case TYPES.MATERIAL_TYPE.CRAYON:
|
||||||
|
this.crayonImage = img
|
||||||
|
break
|
||||||
|
case TYPES.MATERIAL_TYPE.CLOTH:
|
||||||
|
this.clothImage = img
|
||||||
|
break
|
||||||
|
case TYPES.MATERIAL_TYPE.OIL:
|
||||||
|
this.oilImage = img
|
||||||
|
break
|
||||||
|
case TYPES.MATERIAL_TYPE.CRAYON_DARK:
|
||||||
|
this.crayonDarkImage = img
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
resolve(true)
|
||||||
|
}
|
||||||
|
img.onerror = () => {
|
||||||
|
resolve(false)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const material = new Material()
|
|
@ -0,0 +1,98 @@
|
||||||
|
/**
|
||||||
|
* 多个材质画刷
|
||||||
|
* @param {*} params
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
import { FabricVue } from '@/plugins/fabric'
|
||||||
|
import * as TYPES from '@/constants/draw'
|
||||||
|
import * as fabricUtils from '@/plugins/fabric/utils/draw'
|
||||||
|
import * as fabricStore from '@/store/modules/draw'
|
||||||
|
|
||||||
|
const COLOR_WIDTH = 5
|
||||||
|
|
||||||
|
export const renderMultiColor = (params={}) => {
|
||||||
|
const canvas = FabricVue.canvas
|
||||||
|
if (!canvas) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const useDrawStore = fabricStore.useDrawStore()
|
||||||
|
const colors = params?.colors ?? useDrawStore.drawColors
|
||||||
|
const type = params?.type ?? useDrawStore.multiColorType
|
||||||
|
|
||||||
|
const patternBrush = new fabric.PatternBrush(canvas)
|
||||||
|
const patternCanvas = document.createElement('canvas')
|
||||||
|
const context = patternCanvas.getContext('2d')
|
||||||
|
if (context) {
|
||||||
|
switch (type) {
|
||||||
|
case TYPES.MultiColorType.COL:
|
||||||
|
renderCol(patternCanvas, context, colors)
|
||||||
|
break
|
||||||
|
case TYPES.MultiColorType.ROW:
|
||||||
|
renderRow(patternCanvas, context, colors)
|
||||||
|
break
|
||||||
|
case TYPES.MultiColorType.CIRCLE:
|
||||||
|
renderCircle(patternCanvas, context, colors)
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
patternBrush.getPatternSrc = () => {
|
||||||
|
return patternCanvas
|
||||||
|
}
|
||||||
|
patternBrush.getPatternSrcFunction = () => {
|
||||||
|
return patternCanvas
|
||||||
|
}
|
||||||
|
|
||||||
|
canvas.isDrawingMode = true
|
||||||
|
canvas.freeDrawingBrush = patternBrush
|
||||||
|
canvas.freeDrawingBrush.width = fabricUtils.getDrawWidth()
|
||||||
|
canvas.freeDrawingBrush.shadow = new fabric.Shadow({
|
||||||
|
blur: fabricUtils.getShadowWidth(),
|
||||||
|
offsetX: 0,
|
||||||
|
offsetY: 0,
|
||||||
|
color: useDrawStore.shadowColor
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderCol(canvas, context, colors=[]) {
|
||||||
|
canvas.width = COLOR_WIDTH * colors.length
|
||||||
|
canvas.height = 20
|
||||||
|
colors.forEach((color, i) => {
|
||||||
|
context.fillStyle = color
|
||||||
|
context.fillRect(COLOR_WIDTH * i, 0, COLOR_WIDTH, 20)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderRow(canvas, context, colors=[]) {
|
||||||
|
canvas.width = 20
|
||||||
|
canvas.height = COLOR_WIDTH * colors.length
|
||||||
|
colors.forEach((color, i) => {
|
||||||
|
context.fillStyle = color
|
||||||
|
context.fillRect(0, COLOR_WIDTH * i, 20, COLOR_WIDTH)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderCircle(canvas, context, colors=[]) {
|
||||||
|
const radius = 10
|
||||||
|
const padding = 5
|
||||||
|
const n = colors.length
|
||||||
|
|
||||||
|
const canvasWidth = 2 * padding + n * radius * 2 + (n - 1) * padding
|
||||||
|
canvas.width = canvasWidth
|
||||||
|
canvas.height = radius * 2 + 2 * padding
|
||||||
|
|
||||||
|
let x = padding + radius
|
||||||
|
const y = padding + radius
|
||||||
|
|
||||||
|
// render multi circle
|
||||||
|
for (let i = 0; i < n; i++) {
|
||||||
|
context.beginPath()
|
||||||
|
context.fillStyle = colors[i]
|
||||||
|
context.arc(x, y, radius, 0, Math.PI * 2)
|
||||||
|
context.closePath()
|
||||||
|
context.fill()
|
||||||
|
x += 2 * radius + padding
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,223 @@
|
||||||
|
/**
|
||||||
|
* @description 封装fabric js
|
||||||
|
*/
|
||||||
|
import { fabric } from 'fabric'
|
||||||
|
|
||||||
|
// 当前使用到的常量|类型(枚举)
|
||||||
|
export class TYPES {
|
||||||
|
static ActionMode = {
|
||||||
|
DRAW: 'draw', // 画笔模式
|
||||||
|
ERASE: 'erase', // 橡皮擦模式
|
||||||
|
SELECT: 'select', // 选择模式
|
||||||
|
Board: 'board' // 画板模式
|
||||||
|
}
|
||||||
|
// 画笔类型
|
||||||
|
static DrawType = {
|
||||||
|
FreeStyle: 'freeStyle',
|
||||||
|
Shape: 'shape'
|
||||||
|
}
|
||||||
|
// 画笔样式
|
||||||
|
static DrawStyle = {
|
||||||
|
Basic: 'basic',
|
||||||
|
Rainbow: 'rainbow',
|
||||||
|
Shape: 'shape',
|
||||||
|
Material: 'material',
|
||||||
|
Pixels: 'pixels',
|
||||||
|
MultiColor: 'multiColor',
|
||||||
|
Text: 'text',
|
||||||
|
MultiLine: 'multiLine',
|
||||||
|
Reticulate: 'reticulate',
|
||||||
|
MultiPoint: 'multiPoint',
|
||||||
|
Wiggle: 'wiggle',
|
||||||
|
Thorn: 'thorn'
|
||||||
|
}
|
||||||
|
// 各种形状
|
||||||
|
static DrawShape = {
|
||||||
|
Bubble: 'bubble',
|
||||||
|
Star: 'star',
|
||||||
|
Love: 'love',
|
||||||
|
Butterfly: 'butterfly',
|
||||||
|
Snow: 'snow',
|
||||||
|
Music: 'music',
|
||||||
|
Sun: 'sun',
|
||||||
|
Moon: 'moon',
|
||||||
|
Leaf: 'leaf',
|
||||||
|
Flower: 'flower'
|
||||||
|
}
|
||||||
|
// 材质类型
|
||||||
|
static MATERIAL_TYPE = {
|
||||||
|
CRAYON: 'crayon',
|
||||||
|
CARBON: 'carbon',
|
||||||
|
CLOTH: 'cloth',
|
||||||
|
OIL: 'oil',
|
||||||
|
CRAYON_DARK: 'crayonDark'
|
||||||
|
}
|
||||||
|
// 多颜色类型
|
||||||
|
static MultiColorType = {
|
||||||
|
COL: 'col',
|
||||||
|
ROW: 'row',
|
||||||
|
CIRCLE: 'circle'
|
||||||
|
}
|
||||||
|
// 画笔元素
|
||||||
|
static FREESTYLE_ELEMENT_CUSTOM_TYPE = {
|
||||||
|
IMAGE: 'image',
|
||||||
|
I_TEXT: 'itext',
|
||||||
|
RAINBOW: 'rainbow',
|
||||||
|
SHAPE: 'shape',
|
||||||
|
PIXELS: 'pixels',
|
||||||
|
DRAW_TEXT: 'drawText',
|
||||||
|
MULTI_LINE: 'multiLine',
|
||||||
|
RETICULATE: 'reticulate',
|
||||||
|
MULTI_POINT: 'multiPoint',
|
||||||
|
WIGGLE: 'wiggle',
|
||||||
|
THORN: 'thorn'
|
||||||
|
}
|
||||||
|
// 形状元素
|
||||||
|
static SHAPE_ELEMENT_CUSTOM_TYPE = {
|
||||||
|
SHAPE_LINE: 'shapeLine',
|
||||||
|
SHAPE_RECT: 'shapeRect',
|
||||||
|
SHAPE_CIRCLE: 'shapeCircle',
|
||||||
|
SHAPE_ELLIPSE: 'shapeEllipse',
|
||||||
|
SHAPE_TRIANGLE: 'shapeTriangle',
|
||||||
|
SHAPE_ARROW_LINE: 'shapeArrowLine',
|
||||||
|
SHAPE_ARROW_OUTLINE: 'shapeArrowOutline',
|
||||||
|
SHAPE_CLOUD: 'shapeCloud',
|
||||||
|
SHAPE_TOOLTIPS: 'shapeTooltips',
|
||||||
|
SHAPE_LIGHTNING: 'shapeLightning',
|
||||||
|
SHAPE_CLOSE: 'shapeClose',
|
||||||
|
SHAPE_CHECK: 'shapeCheck',
|
||||||
|
SHAPE_INFO: 'shapeInfo',
|
||||||
|
SHAPE_BACKSPACE: 'shapeBackspace',
|
||||||
|
SHAPE_BLOCK: 'shapeBlock',
|
||||||
|
SHAPE_SPEAKER: 'shapeSpeaker',
|
||||||
|
SHAPE_SEARCH: 'shapeSearch',
|
||||||
|
SHAPE_INFO_OUTLINE: 'shapeInfoOutline',
|
||||||
|
SHAPE_HEART: 'shapeHeart',
|
||||||
|
SHAPE_ALERT: 'shapeAlert'
|
||||||
|
}
|
||||||
|
// 元素类型
|
||||||
|
static ELEMENT_CUSTOM_TYPE = {
|
||||||
|
...this.FREESTYLE_ELEMENT_CUSTOM_TYPE,
|
||||||
|
...this.SHAPE_ELEMENT_CUSTOM_TYPE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 自由绘画 FreeStyle
|
||||||
|
// export class FreeStyle {
|
||||||
|
// }
|
||||||
|
export const FreeStyle = {
|
||||||
|
renderPencilBrush: (canvas) => {
|
||||||
|
const pencilBrush = new fabric.PencilBrush(canvas)
|
||||||
|
canvas.isDrawingMode = true
|
||||||
|
canvas.freeDrawingBrush = pencilBrush
|
||||||
|
canvas.freeDrawingBrush.width = fabricUtils.getDrawWidth()
|
||||||
|
canvas.freeDrawingBrush.color = useDrawStore.drawColors[0]
|
||||||
|
canvas.freeDrawingBrush.shadow = new fabric.Shadow({
|
||||||
|
blur: fabricUtils.getShadowWidth(),
|
||||||
|
offsetX: 0,
|
||||||
|
offsetY: 0,
|
||||||
|
color: useDrawStore.shadowColor
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 封装fabric-canvas
|
||||||
|
export class fabricVue {
|
||||||
|
canvas = null // fabric-canvas 对象
|
||||||
|
evnet = null // 事件对象
|
||||||
|
history = null // 历史记录
|
||||||
|
textElement // 文本节点
|
||||||
|
hookFn = [] // 钩子
|
||||||
|
defConfig // 默认配置-canvas
|
||||||
|
drawConfig // 默认配置-画笔
|
||||||
|
boardConfig // 默认配置-画板
|
||||||
|
publicConfig // 公共属性-配置
|
||||||
|
|
||||||
|
/** 构造函数 */
|
||||||
|
constructor() {
|
||||||
|
// this.textElement = new TextElement() // 创建文本节点
|
||||||
|
const initLanguage = ['en', 'en-US', 'en-us'].includes(navigator.language) ? 'en' : 'zh'
|
||||||
|
this.drawConfig = { // 默认配置
|
||||||
|
drawWidth: 1,
|
||||||
|
drawColors: ['#000000'],
|
||||||
|
shadowWidth: 0,
|
||||||
|
shadowColor: '#000000',
|
||||||
|
drawTextValue: 'draw',
|
||||||
|
drawStyle: TYPES.DrawStyle.Basic,
|
||||||
|
drawShapeCount: 2,
|
||||||
|
materialType: TYPES.MATERIAL_TYPE.CRAYON,
|
||||||
|
drawShape: TYPES.DrawShape.Bubble,
|
||||||
|
eraserWidth: 20,
|
||||||
|
multiColorType: TYPES.MultiColorType.COL,
|
||||||
|
textFontFamily: 'Georgia',
|
||||||
|
openAutoDraw: false,
|
||||||
|
fontStyles: [],
|
||||||
|
}
|
||||||
|
this.boardConfig = { // 默认配置
|
||||||
|
mode: TYPES.ActionMode.DRAW,
|
||||||
|
drawType: TYPES.DrawType.FreeStyle,
|
||||||
|
language: initLanguage,
|
||||||
|
canvasWidth: 1,
|
||||||
|
canvasHeight: 1,
|
||||||
|
backgroundColor: 'rgba(255, 255, 255, 1)',
|
||||||
|
backgroundOpacity: 1,
|
||||||
|
hasBackgroundImage: false,
|
||||||
|
backgroundImageOpacity: 1,
|
||||||
|
isObjectCaching: true,
|
||||||
|
openGuideLine: false,
|
||||||
|
}
|
||||||
|
this.defConfig = { // 默认配置-canvas
|
||||||
|
// 它用于指定选中对象时显示的选择框的颜色
|
||||||
|
selectionColor: 'rgba(101, 204, 138, 0.3)',
|
||||||
|
// 用于控制在组合(group)对象时是否保留每个对象的堆叠顺序
|
||||||
|
preserveObjectStacking: true,
|
||||||
|
// 它用于控制是否在高分辨率显示器(例如 Retina 显示器)上启用图像的像素级缩放
|
||||||
|
enableRetinaScaling: true,
|
||||||
|
// 锁定背景,不受缩放影响 false
|
||||||
|
backgroundVpt: false,
|
||||||
|
// 默认全屏模式
|
||||||
|
width: window.innerWidth,
|
||||||
|
height: window.innerHeight,
|
||||||
|
}
|
||||||
|
this.publicConfig = { // 公共属性配置
|
||||||
|
Object: { // 默认配置-对象上
|
||||||
|
// 设置对象边框的颜色(Rect、Circle、Ellipse、Path)
|
||||||
|
borderColor: '#65CC8A',
|
||||||
|
// 设置对象角落的颜色(Rect、RoundRect、Circle 和 Ellipse 类型的对象)
|
||||||
|
cornerColor: '#65CC8A',
|
||||||
|
// 设置角落的形状(Rect、RoundRect、Circle 和 Ellipse 类型的对象)
|
||||||
|
// circle rect round [圆形,直角(默认),圆角]
|
||||||
|
cornerStyle: 'circle',
|
||||||
|
// 设置边框虚线的样式(Rect、Circle、Ellipse、Path)
|
||||||
|
borderDashArray: [3, 3],
|
||||||
|
// 矩形(Rect)、圆角矩形(RoundRect)、圆形(Circle)和椭圆形(Ellipse)对象的角落是否透明
|
||||||
|
transparentCorners: false
|
||||||
|
},
|
||||||
|
Line: {
|
||||||
|
// 设置边缘连接方式为 miter bevel round|尖角(默认) 斜角切割线 圆形连接
|
||||||
|
strokeLineJoin: 'round',
|
||||||
|
// 设置结束连接方式为 butt round square|直角(默认) 圆形 正方形
|
||||||
|
strokeLineCap: 'round'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 初始化canvas
|
||||||
|
* @param {*} canvasEl canvas元素
|
||||||
|
* @returns boolean 是否初始化成功
|
||||||
|
*/
|
||||||
|
initCanvas(canvasEl, option = {}) {
|
||||||
|
return new Promise(async(resolve) => {
|
||||||
|
if (!canvasEl) resolve(false)
|
||||||
|
this.canvas = new fabric.Canvas(canvasEl, {
|
||||||
|
...this.defConfig, ...option
|
||||||
|
})
|
||||||
|
// this.evnet = new CanvasEvent() // 创建相关事件
|
||||||
|
// this.history = new History() // 创建历史记录
|
||||||
|
// await this.initCanvasStorage()
|
||||||
|
resolve(true)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
export const FabricVue = new fabricVue()
|
||||||
|
export default FabricVue
|
||||||
|
|
|
@ -0,0 +1,77 @@
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 工具:背景相关的颜色、背景图片
|
||||||
|
*/
|
||||||
|
import { fabric } from 'fabric'
|
||||||
|
import { FabricVue } from '@/plugins/fabric'
|
||||||
|
import * as fabricStore from '@/store/modules/draw'
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 事件: 缩放变化-更新背景图片
|
||||||
|
*/
|
||||||
|
export const handleBackgroundImageWhenCanvasSizeChange = (isRender = true) => {
|
||||||
|
const backgroundImage = FabricVue?.canvas?.backgroundImage
|
||||||
|
if (backgroundImage) {
|
||||||
|
setBackgroundImage(backgroundImage)
|
||||||
|
if (isRender) {
|
||||||
|
FabricVue.canvas?.requestRenderAll()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 更新 fabric 加载画布背景图片
|
||||||
|
* @param {*} data string
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export const updateCanvasBackgroundImage = (data) => {
|
||||||
|
const canvas = FabricVue.canvas
|
||||||
|
if (!canvas) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fabric.Image.fromURL(
|
||||||
|
data,
|
||||||
|
(image) => {
|
||||||
|
setBackgroundImage(image)
|
||||||
|
|
||||||
|
canvas.setBackgroundImage(image, () => {
|
||||||
|
FabricVue.render()
|
||||||
|
})
|
||||||
|
},
|
||||||
|
{crossOrigin: 'anonymous'}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 设置-更新背景图片
|
||||||
|
* @param {*} image fabric.Image
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export const setBackgroundImage = (image) => {
|
||||||
|
const canvas = FabricVue?.canvas
|
||||||
|
if (!canvas) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const useBoardStore = fabricStore.useBoardStore()
|
||||||
|
const canvasWidth = canvas.getWidth()
|
||||||
|
const canvasHeight = canvas.getHeight()
|
||||||
|
|
||||||
|
const imgWidth = image.width
|
||||||
|
const imgHeight = image.height
|
||||||
|
|
||||||
|
const scaleWidth = canvasWidth / imgWidth
|
||||||
|
const scaleHeight = canvasHeight / imgHeight
|
||||||
|
|
||||||
|
const scale = Math.min(scaleWidth, scaleHeight)
|
||||||
|
image.scale(scale)
|
||||||
|
|
||||||
|
const imgLeft = canvasWidth / 2 - (imgWidth * scale) / 2
|
||||||
|
const imgTop = canvasHeight / 2 - (imgHeight * scale) / 2
|
||||||
|
|
||||||
|
image.set({
|
||||||
|
left: imgLeft,
|
||||||
|
top: imgTop,
|
||||||
|
originX: 'left',
|
||||||
|
originY: 'top',
|
||||||
|
opacity: useBoardStore.backgroundImageOpacity
|
||||||
|
})
|
||||||
|
}
|
|
@ -0,0 +1,82 @@
|
||||||
|
/**
|
||||||
|
* hexToRgba
|
||||||
|
* @param hex hexadecimal color
|
||||||
|
* @param alpha
|
||||||
|
* @returns rgba
|
||||||
|
*/
|
||||||
|
export function hexToRgba(hex, alpha = 1) {
|
||||||
|
const bigint = parseInt(hex.slice(1), 16)
|
||||||
|
const r = (bigint >> 16) & 255
|
||||||
|
const g = (bigint >> 8) & 255
|
||||||
|
const b = bigint & 255
|
||||||
|
return `rgba(${r}, ${g}, ${b}, ${alpha})`
|
||||||
|
}
|
||||||
|
|
||||||
|
export function rgbaToHex(rgba) {
|
||||||
|
if (!rgba) {
|
||||||
|
return rgba
|
||||||
|
}
|
||||||
|
|
||||||
|
const values = rgba.match(/\d+/g)
|
||||||
|
const hex = `#${(
|
||||||
|
(1 << 24) +
|
||||||
|
(parseInt(values[0]) << 16) +
|
||||||
|
(parseInt(values[1]) << 8) +
|
||||||
|
parseInt(values[2])
|
||||||
|
)
|
||||||
|
.toString(16)
|
||||||
|
.slice(1)}`
|
||||||
|
return hex
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* get rgba alpha
|
||||||
|
* @param rgbaString rgba color
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export function getAlphaFromRgba(rgbaString) {
|
||||||
|
const match = rgbaString.match(/rgba?\((\d+),\s*(\d+),\s*(\d+),\s*([\d.]+)\)/)
|
||||||
|
|
||||||
|
if (match) {
|
||||||
|
return parseFloat(match[4])
|
||||||
|
} else {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the rgba of the new alpha
|
||||||
|
* @param rgbaColor
|
||||||
|
* @param newAlpha
|
||||||
|
* @returns newRgbaColor
|
||||||
|
*/
|
||||||
|
export function changeAlpha(rgbaColor, newAlpha) {
|
||||||
|
const match = rgbaColor.match(/rgba?\((.*?)\)/)
|
||||||
|
if (!match) {
|
||||||
|
return rgbaColor
|
||||||
|
}
|
||||||
|
|
||||||
|
const rgbPart = match[1].split(',').map((i) => i.trim())
|
||||||
|
newAlpha = Math.min(1, Math.max(0, newAlpha))
|
||||||
|
|
||||||
|
const newRgbaColor = `rgba(${rgbPart[0]}, ${rgbPart[1]}, ${rgbPart[2]}, ${newAlpha})`
|
||||||
|
return newRgbaColor
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isHexColor(color) {
|
||||||
|
return /^#([0-9A-Fa-f]{3}){1,2}$/.test(color)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isRgbaColor(color) {
|
||||||
|
return /^rgba?\((\d+,\s*){2}\d+,\s*(0(\.\d+)?|1(\.0)?)\)$/.test(color)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getColorFormat(color) {
|
||||||
|
if (isHexColor(color)) {
|
||||||
|
return 'hex'
|
||||||
|
} else if (isRgbaColor(color)) {
|
||||||
|
return 'rgba'
|
||||||
|
} else {
|
||||||
|
return 'unknown'
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,33 @@
|
||||||
|
import * as fabricStore from '@/store/modules/draw'
|
||||||
|
import { FabricVue } from "@/plugins/fabric";
|
||||||
|
// import { v4 as uuidv4 } from 'uuid'
|
||||||
|
const useDrawStore = fabricStore.useDrawStore()
|
||||||
|
|
||||||
|
export const getDrawWidth = (width) => {
|
||||||
|
return (
|
||||||
|
(width ?? useDrawStore.drawWidth) /
|
||||||
|
(FabricVue.canvas?.getZoom() ?? 1)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getEraserWidth = (width) => {
|
||||||
|
return (
|
||||||
|
(width ?? useDrawStore.eraserWidth) /
|
||||||
|
(FabricVue.canvas?.getZoom() ?? 1)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getShadowWidth = (width) => {
|
||||||
|
return (
|
||||||
|
(width ?? useDrawStore.shadowWidth) /
|
||||||
|
(FabricVue.canvas?.getZoom() ?? 1)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// export const setObjectAttr = (obj, type) => {
|
||||||
|
// const id = uuidv4()
|
||||||
|
// obj.set({
|
||||||
|
// id,
|
||||||
|
// _customType: type
|
||||||
|
// })
|
||||||
|
// }
|
|
@ -0,0 +1,165 @@
|
||||||
|
/**
|
||||||
|
* 工具类
|
||||||
|
*/
|
||||||
|
import { FabricVue } from '@/plugins/fabric'
|
||||||
|
import * as TYPES from '@/constants/draw'
|
||||||
|
import * as lineUtils from '@/plugins/fabric/utils/shape/line'
|
||||||
|
import * as arrowLineUtils from '@/plugins/fabric/utils/shape/arrowLine'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* compare version
|
||||||
|
* @param v1
|
||||||
|
* @param v2
|
||||||
|
* @returns 0: v1 === v2; 1: v1 > v2; -1: v1 < v2
|
||||||
|
*/
|
||||||
|
export const compareVersion = (v1, v2) => {
|
||||||
|
const v1s = v1.split('.')
|
||||||
|
const v2s = v2.split('.')
|
||||||
|
const len = Math.max(v1s.length, v2s.length)
|
||||||
|
|
||||||
|
while (v1s.length < len) {
|
||||||
|
v1s.push('0')
|
||||||
|
}
|
||||||
|
while (v2s.length < len) {
|
||||||
|
v1s.push('0')
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let i = 0; i < len; i++) {
|
||||||
|
const num1 = parseInt(v1s[i])
|
||||||
|
const num2 = parseInt(v2s[i])
|
||||||
|
|
||||||
|
if (num1 > num2) {
|
||||||
|
return 1
|
||||||
|
} else if (num1 < num2) {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* get random integer
|
||||||
|
* @param min min range
|
||||||
|
* @param max max range
|
||||||
|
*/
|
||||||
|
export const getRandomInt = (min, max) => {
|
||||||
|
return Math.floor(Math.random() * (max - min + 1)) + min
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* get random float
|
||||||
|
* @param min min range
|
||||||
|
* @param max max range
|
||||||
|
*/
|
||||||
|
export const getRandomFloat = (min, max) => {
|
||||||
|
return Math.random() * (max - min) + min
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* handle static file paths in the public folder
|
||||||
|
* @param originUrl
|
||||||
|
* @returns publicDir + originUrl
|
||||||
|
*/
|
||||||
|
export const formatPublicUrl = (originUrl) => {
|
||||||
|
if (originUrl && typeof originUrl === 'string') {
|
||||||
|
return `${import.meta.env.BASE_URL}${originUrl}`
|
||||||
|
}
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* get the distance between two points
|
||||||
|
* @param start
|
||||||
|
* @param end
|
||||||
|
* @returns distance
|
||||||
|
*/
|
||||||
|
export const getDistance = (start, end) => {
|
||||||
|
return Math.sqrt(Math.pow(start.x - end.x, 2) + Math.pow(start.y - end.y, 2))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* is it mobile
|
||||||
|
*/
|
||||||
|
export const isMobile = () => {
|
||||||
|
return /phone|pad|pod|iPhone|iPod|ios|iPad|Android|Mobile|BlackBerry|IEMobile|MQQBrowser|JUC|Fennec|wOSBrowser|BrowserNG|WebOS|Symbian|Windows Phone|Mobi|Android|iPhone|iPad/i.test(
|
||||||
|
navigator.userAgent
|
||||||
|
)
|
||||||
|
}
|
||||||
|
// 生成UUID
|
||||||
|
export const getUUID = () => {
|
||||||
|
const timestamp = Date.now();
|
||||||
|
// 时间戳部分
|
||||||
|
const timestampPart = timestamp.toString(16).padStart(12, '0');
|
||||||
|
|
||||||
|
// 生成随机数
|
||||||
|
const rnd = crypto.getRandomValues(new Uint8Array(4));
|
||||||
|
const randomPart = rnd.map(byte => byte.toString(16).padStart(2, '0')).join('');
|
||||||
|
|
||||||
|
// 应用变体和版本标识符
|
||||||
|
const version = (timestamp >= 0x0100000000000000) ? (timestamp & 0x00000000FFFF0000) : 0x4000;
|
||||||
|
const variant = (rnd[0] & 0x0F) | 0x80;
|
||||||
|
|
||||||
|
// 合并所有部分并返回
|
||||||
|
return `${timestampPart}-${randomPart}-${version.toString(16).padStart(4, '0')}-${variant.toString(16).padStart(4, '0')}-${rnd[3].toString(16).padStart(4, '0')}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** loadCanvas */
|
||||||
|
export const getCanvasJSON = () => {
|
||||||
|
const canvas = FabricVue?.canvas
|
||||||
|
if (canvas) {
|
||||||
|
return (
|
||||||
|
// 返回额外属性
|
||||||
|
canvas.toDatalessJSON([
|
||||||
|
'id',
|
||||||
|
'_customType',
|
||||||
|
'perPixelTargetFind',
|
||||||
|
'objectCaching'
|
||||||
|
]) ?? {}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return {}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handling canvas json loaded data
|
||||||
|
* Used to initialize undo redo
|
||||||
|
* @param canvas fabric.Canvas
|
||||||
|
*/
|
||||||
|
export const handleCanvasJSONLoaded = (canvas) => {
|
||||||
|
canvas.getObjects().forEach((obj) => {
|
||||||
|
if (obj._customType === TYPES.ELEMENT_CUSTOM_TYPE.SHAPE_LINE) {
|
||||||
|
const points = obj.points
|
||||||
|
const lastControl = points.length - 1
|
||||||
|
obj.controls = points.reduce(function (acc, point, index) {
|
||||||
|
acc['p' + index] = new fabric.Control({
|
||||||
|
positionHandler: lineUtils.polygonPositionHandler,
|
||||||
|
actionHandler: lineUtils.anchorWrapper(
|
||||||
|
index > 0 ? index - 1 : lastControl,
|
||||||
|
lineUtils.actionHandler
|
||||||
|
),
|
||||||
|
actionName: 'polylineEndPoint',
|
||||||
|
pointIndex: index
|
||||||
|
})
|
||||||
|
return acc
|
||||||
|
}, {})
|
||||||
|
}
|
||||||
|
|
||||||
|
if (obj._customType === TYPES.ELEMENT_CUSTOM_TYPE.SHAPE_ARROW_LINE) {
|
||||||
|
const paths = (obj).path
|
||||||
|
obj.controls = paths
|
||||||
|
.slice(0, paths.length - 4)
|
||||||
|
.reduce(function (acc, point, index) {
|
||||||
|
acc['p' + index] = new fabric.Control({
|
||||||
|
positionHandler: arrowLineUtils.pathPositionHandler,
|
||||||
|
actionHandler: arrowLineUtils.anchorWrapper(
|
||||||
|
index > 0 ? index - 1 : paths.length - 5,
|
||||||
|
arrowLineUtils.actionHandler
|
||||||
|
),
|
||||||
|
actionName: 'pathEndPoint',
|
||||||
|
pointIndex: index
|
||||||
|
})
|
||||||
|
return acc
|
||||||
|
}, {})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
|
@ -0,0 +1,164 @@
|
||||||
|
import { fabric } from 'fabric'
|
||||||
|
|
||||||
|
export function calculateArrowSlidePath(
|
||||||
|
paths,
|
||||||
|
startX,
|
||||||
|
startY,
|
||||||
|
endX,
|
||||||
|
endY
|
||||||
|
) {
|
||||||
|
const angleRad = Math.atan2(endY - startY, endX - startX)
|
||||||
|
const arrowWidthHalf = 10
|
||||||
|
|
||||||
|
const arrowLeftX = endX - arrowWidthHalf * Math.cos(angleRad - Math.PI / 6)
|
||||||
|
const arrowLeftY = endY - arrowWidthHalf * Math.sin(angleRad - Math.PI / 6)
|
||||||
|
const arrowRightX = endX - arrowWidthHalf * Math.cos(angleRad + Math.PI / 6)
|
||||||
|
const arrowRightY = endY - arrowWidthHalf * Math.sin(angleRad + Math.PI / 6)
|
||||||
|
|
||||||
|
paths[paths.length - 4][1] = endX
|
||||||
|
paths[paths.length - 4][2] = endY
|
||||||
|
paths[paths.length - 3][1] = arrowLeftX
|
||||||
|
paths[paths.length - 3][2] = arrowLeftY
|
||||||
|
paths[paths.length - 2][1] = endX
|
||||||
|
paths[paths.length - 2][2] = endY
|
||||||
|
paths[paths.length - 1][1] = arrowRightX
|
||||||
|
paths[paths.length - 1][2] = arrowRightY
|
||||||
|
|
||||||
|
return paths
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {*} object fabric.Path
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export function getObjectSizeWithStroke(object) {
|
||||||
|
const stroke = new fabric.Point(
|
||||||
|
object.strokeUniform ? 1 / (object.scaleX) : 1,
|
||||||
|
object.strokeUniform ? 1 / (object.scaleY) : 1
|
||||||
|
).multiply(object.strokeWidth)
|
||||||
|
return new fabric.Point(
|
||||||
|
(object.width) + stroke.x,
|
||||||
|
(object.height) + stroke.y
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {*} this fabric.Control
|
||||||
|
* @param {*} dim
|
||||||
|
* @param {*} finalMatrix
|
||||||
|
* @param {*} fabricObject fabric.Path
|
||||||
|
* @returns fabric.Control['positionHandler']
|
||||||
|
*/
|
||||||
|
export const pathPositionHandler = function (
|
||||||
|
fabricControl,
|
||||||
|
dim,
|
||||||
|
finalMatrix,
|
||||||
|
fabricObject
|
||||||
|
) {
|
||||||
|
const paths = fabricObject.path
|
||||||
|
const x = paths[fabricControl.pointIndex][1] - fabricObject.pathOffset.x
|
||||||
|
const y = paths[fabricControl.pointIndex][2] - fabricObject.pathOffset.y
|
||||||
|
return fabric.util.transformPoint(
|
||||||
|
{ x, y },
|
||||||
|
fabric.util.multiplyTransformMatrices(
|
||||||
|
fabricObject.canvas?.viewportTransform,
|
||||||
|
fabricObject.calcTransformMatrix()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {*} eventData
|
||||||
|
* @param {*} transform
|
||||||
|
* @param {*} x
|
||||||
|
* @param {*} y
|
||||||
|
* @returns fabric.Control['actionHandler']
|
||||||
|
*/
|
||||||
|
export const actionHandler = function (
|
||||||
|
eventData,
|
||||||
|
transform,
|
||||||
|
x,
|
||||||
|
y
|
||||||
|
) {
|
||||||
|
const pathObj = transform.target,
|
||||||
|
currentControl = pathObj.controls[pathObj.__corner],
|
||||||
|
mouseLocalPosition = pathObj.toLocalPoint(
|
||||||
|
new fabric.Point(x, y),
|
||||||
|
'center',
|
||||||
|
'center'
|
||||||
|
),
|
||||||
|
pathBaseSize = getObjectSizeWithStroke(pathObj),
|
||||||
|
size = pathObj._getTransformedDimensions(0, 0),
|
||||||
|
finalPointPosition = {
|
||||||
|
x:
|
||||||
|
(mouseLocalPosition.x * pathBaseSize.x) / size.x + pathObj.pathOffset.x,
|
||||||
|
y: (mouseLocalPosition.y * pathBaseSize.y) / size.y + pathObj.pathOffset.y
|
||||||
|
}
|
||||||
|
|
||||||
|
const path = pathObj.path
|
||||||
|
path[currentControl.pointIndex][1] = finalPointPosition.x
|
||||||
|
path[currentControl.pointIndex][2] = finalPointPosition.y
|
||||||
|
|
||||||
|
if (
|
||||||
|
currentControl.pointIndex === path?.length - 5 ||
|
||||||
|
currentControl.pointIndex === path?.length - 6
|
||||||
|
) {
|
||||||
|
const point1 = path[path.length - 6]
|
||||||
|
const point2 = path[path.length - 5]
|
||||||
|
const newPath = calculateArrowSlidePath(
|
||||||
|
path,
|
||||||
|
point1[1],
|
||||||
|
point1[2],
|
||||||
|
point2[1],
|
||||||
|
point2[2]
|
||||||
|
)
|
||||||
|
pathObj._setPath(newPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {*} anchorIndex
|
||||||
|
* @param {*} fn fabric.Control['actionHandler']
|
||||||
|
* @returns MouseEvent fabric.Transform
|
||||||
|
*/
|
||||||
|
export function anchorWrapper(
|
||||||
|
anchorIndex,
|
||||||
|
fn
|
||||||
|
) {
|
||||||
|
return function (
|
||||||
|
eventData,
|
||||||
|
transform,
|
||||||
|
x,
|
||||||
|
y
|
||||||
|
) {
|
||||||
|
const fabricObject = transform.target
|
||||||
|
const paths = fabricObject.path
|
||||||
|
const absolutePoint = fabric.util.transformPoint(
|
||||||
|
{
|
||||||
|
x: paths[anchorIndex][1] - fabricObject.pathOffset.x,
|
||||||
|
y: paths[anchorIndex][2] - fabricObject.pathOffset.y
|
||||||
|
},
|
||||||
|
fabricObject.calcTransformMatrix()
|
||||||
|
)
|
||||||
|
const actionPerformed = fn(eventData, transform, x, y)
|
||||||
|
fabricObject._setPath(fabricObject.path)
|
||||||
|
const pathBaseSize = getObjectSizeWithStroke(fabricObject)
|
||||||
|
|
||||||
|
const newX =
|
||||||
|
(paths[anchorIndex][1] - fabricObject.pathOffset.x) / pathBaseSize.x
|
||||||
|
const newY =
|
||||||
|
(paths[anchorIndex][2] - fabricObject.pathOffset.y) / pathBaseSize.y
|
||||||
|
fabricObject.setPositionByOrigin(
|
||||||
|
absolutePoint,
|
||||||
|
(newX + 0.5),
|
||||||
|
(newY + 0.5)
|
||||||
|
)
|
||||||
|
return actionPerformed
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,109 @@
|
||||||
|
/**
|
||||||
|
* 线
|
||||||
|
*/
|
||||||
|
import { fabric } from 'fabric'
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {*} object fabric.Polyline
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export function getObjectSizeWithStroke(object) {
|
||||||
|
const stroke = new fabric.Point(
|
||||||
|
object.strokeUniform ? 1 / (object.scaleX) : 1,
|
||||||
|
object.strokeUniform ? 1 / (object.scaleY) : 1
|
||||||
|
).multiply(object.strokeWidth)
|
||||||
|
return new fabric.Point(
|
||||||
|
(object.width) + stroke.x,
|
||||||
|
(object.height) + stroke.y
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {*} this fabric.Control
|
||||||
|
* @param {*} dim
|
||||||
|
* @param {*} finalMatrix
|
||||||
|
* @param {*} fabricObject fabric.Polyline
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export const polygonPositionHandler = function (
|
||||||
|
fabricControl, dim, finalMatrix,fabricObject) {
|
||||||
|
const points = fabricObject.points
|
||||||
|
const x = points[fabricControl.pointIndex].x - fabricObject.pathOffset.x
|
||||||
|
const y = points[fabricControl.pointIndex].y - fabricObject.pathOffset.y
|
||||||
|
return fabric.util.transformPoint(
|
||||||
|
{ x, y },
|
||||||
|
fabric.util.multiplyTransformMatrices(
|
||||||
|
fabricObject.canvas?.viewportTransform,
|
||||||
|
fabricObject.calcTransformMatrix()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {*} eventData
|
||||||
|
* @param {*} transform
|
||||||
|
* @param {*} x
|
||||||
|
* @param {*} y
|
||||||
|
* @returns fabric.Control['actionHandler']
|
||||||
|
*/
|
||||||
|
export const actionHandler = function (
|
||||||
|
eventData, transform,x, y) {
|
||||||
|
const polygon = transform.target,
|
||||||
|
currentControl = polygon.controls[polygon.__corner],
|
||||||
|
mouseLocalPosition = polygon.toLocalPoint(
|
||||||
|
new fabric.Point(x, y),
|
||||||
|
'center',
|
||||||
|
'center'
|
||||||
|
),
|
||||||
|
polygonBaseSize = getObjectSizeWithStroke(polygon),
|
||||||
|
size = polygon._getTransformedDimensions(0, 0),
|
||||||
|
finalPointPosition = {
|
||||||
|
x:
|
||||||
|
(mouseLocalPosition.x * polygonBaseSize.x) / size.x +
|
||||||
|
polygon.pathOffset.x,
|
||||||
|
y:
|
||||||
|
(mouseLocalPosition.y * polygonBaseSize.y) / size.y +
|
||||||
|
polygon.pathOffset.y
|
||||||
|
}
|
||||||
|
|
||||||
|
const points = polygon.points
|
||||||
|
points[currentControl.pointIndex] = finalPointPosition
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {*} anchorIndex number
|
||||||
|
* @param {*} fn fabric.Control['actionHandler']
|
||||||
|
* @returns function (eventData, transform, x, y ) | MouseEvent fabric.Transform number number
|
||||||
|
*/
|
||||||
|
export function anchorWrapper(anchorIndex, fn) {
|
||||||
|
return function (eventData, transform, x, y ) {
|
||||||
|
const fabricObject = transform.target
|
||||||
|
const points = fabricObject.points
|
||||||
|
const absolutePoint = fabric.util.transformPoint(
|
||||||
|
{
|
||||||
|
x: points[anchorIndex].x - fabricObject.pathOffset.x,
|
||||||
|
y: points[anchorIndex].y - fabricObject.pathOffset.y
|
||||||
|
},
|
||||||
|
fabricObject.calcTransformMatrix()
|
||||||
|
)
|
||||||
|
const actionPerformed = fn(eventData, transform, x, y)
|
||||||
|
fabricObject._setPositionDimensions({})
|
||||||
|
const polygonBaseSize = getObjectSizeWithStroke(fabricObject)
|
||||||
|
|
||||||
|
const newX =
|
||||||
|
(points[anchorIndex].x - fabricObject.pathOffset.x) / polygonBaseSize.x
|
||||||
|
const newY =
|
||||||
|
(points[anchorIndex].y - fabricObject.pathOffset.y) / polygonBaseSize.y
|
||||||
|
fabricObject.setPositionByOrigin(
|
||||||
|
absolutePoint,
|
||||||
|
(newX + 0.5),
|
||||||
|
(newY + 0.5)
|
||||||
|
)
|
||||||
|
return actionPerformed
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,295 @@
|
||||||
|
/**
|
||||||
|
* @description fabric.js 缓存画板
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { defineStore } from "pinia";
|
||||||
|
import * as TYPES from "@/constants/draw";
|
||||||
|
import { FabricVue } from "@/plugins/fabric";
|
||||||
|
import * as fabricUtils from '@/plugins/fabric/utils/draw'
|
||||||
|
import * as colorUtils from '@/plugins/fabric/utils/color'
|
||||||
|
import * as bgUtils from '@/plugins/fabric/utils/bg'
|
||||||
|
import * as commUtils from '@/plugins/fabric/utils'
|
||||||
|
import { material } from '@/plugins/fabric/nodes/draw/material'
|
||||||
|
import { renderMultiColor } from '@/plugins/fabric/nodes/draw/multiColor'
|
||||||
|
|
||||||
|
// 画板模块-缓存 json相关
|
||||||
|
export const BOARD_VERSION = '1.0.0'
|
||||||
|
|
||||||
|
const getFileObj = (data) => ({
|
||||||
|
id: commUtils.getUUID(),
|
||||||
|
title: 'paint-board',
|
||||||
|
boardVersion: BOARD_VERSION,
|
||||||
|
boardData: {},
|
||||||
|
zoom: 1,
|
||||||
|
canvasWidth: 1,
|
||||||
|
canvasHeight: 1
|
||||||
|
, ...data
|
||||||
|
})
|
||||||
|
const initFileObj = getFileObj()
|
||||||
|
export const useFileStore = defineStore(
|
||||||
|
'file',
|
||||||
|
{
|
||||||
|
state() {
|
||||||
|
return {
|
||||||
|
currentId: initFileObj.id,
|
||||||
|
currentIndex: 0,
|
||||||
|
currentFile: initFileObj,
|
||||||
|
files: [ initFileObj ],
|
||||||
|
}
|
||||||
|
},
|
||||||
|
actions: {
|
||||||
|
// update file | 更新文件
|
||||||
|
updateFile(file = {}) {
|
||||||
|
if (file?.id != this.currentId) return
|
||||||
|
this.currentFile = file
|
||||||
|
this.files[this.currentIndex] = data
|
||||||
|
},
|
||||||
|
// update board data | 更新画板数据
|
||||||
|
updateBoardData(data) {
|
||||||
|
this.currentFile.boardData = data
|
||||||
|
this.files[this.currentIndex].boardData = data
|
||||||
|
},
|
||||||
|
// update board zoom | 更新画板缩放
|
||||||
|
updateBoardZoom(zoom){
|
||||||
|
this.currentFile.zoom = zoom
|
||||||
|
this.files[this.currentIndex].zoom = zoom
|
||||||
|
},
|
||||||
|
// update board field | 更新画板字段
|
||||||
|
updateBoardField(key, value) {
|
||||||
|
if(key == 'id') return
|
||||||
|
this.currentFile[key] = value
|
||||||
|
this.files[this.currentIndex][key] = value
|
||||||
|
},
|
||||||
|
// 创建新画板
|
||||||
|
createBoard(option = {}) {
|
||||||
|
delete option?.id
|
||||||
|
let fileObj = getFileObj(option)
|
||||||
|
this.currentFile = fileObj
|
||||||
|
this.currentId = fileObj.id
|
||||||
|
this.files.push(fileObj)
|
||||||
|
this.currentIndex = this.files.length - 1
|
||||||
|
return fileObj
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// 主模块-缓存
|
||||||
|
const initLanguage = ['en', 'en-US', 'en-us'].includes(navigator.language) ? 'en' : 'zh'
|
||||||
|
export const useBoardStore = defineStore(
|
||||||
|
'board',
|
||||||
|
{
|
||||||
|
state() {
|
||||||
|
return {
|
||||||
|
mode: TYPES.ActionMode.DRAW,
|
||||||
|
drawType: TYPES.DrawType.FreeStyle,
|
||||||
|
language: initLanguage,
|
||||||
|
canvasWidth: 1,
|
||||||
|
canvasHeight: 1,
|
||||||
|
backgroundColor: 'rgba(255, 255, 255, 1)',
|
||||||
|
backgroundOpacity: 1,
|
||||||
|
hasBackgroundImage: false,
|
||||||
|
backgroundImageOpacity: 1,
|
||||||
|
isObjectCaching: true,
|
||||||
|
openGuideLine: false,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
actions: {
|
||||||
|
// update mode | 更新模式
|
||||||
|
updateMode(mode) {
|
||||||
|
if (this.mode != mode) FabricVue.handleMode(mode)
|
||||||
|
this.mode = mode
|
||||||
|
},
|
||||||
|
// update draw type | 更新画笔类型
|
||||||
|
updateDrawType(drawType) {
|
||||||
|
if (this.drawType != drawType){
|
||||||
|
this.drawType = drawType
|
||||||
|
FabricVue.handleMode()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// update language | 更新语言
|
||||||
|
updateLanguage(language) {this.language = language},
|
||||||
|
updateCanvasWidth(width) {
|
||||||
|
if (this.canvasWidth !== width){
|
||||||
|
this.canvasHeight = width
|
||||||
|
FabricVue.updateCanvasWidth(width)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// init background | 初始化背景
|
||||||
|
initBackground() {
|
||||||
|
const backgroundColor = FabricVue?.canvas?.backgroundColor
|
||||||
|
console.log('xxxx', backgroundColor, FabricVue?.canvas)
|
||||||
|
if (backgroundColor && typeof backgroundColor === 'string') {
|
||||||
|
const type = colorUtils.getColorFormat(backgroundColor)
|
||||||
|
if (type === 'hex') {
|
||||||
|
const color = colorUtils.hexToRgba(backgroundColor)
|
||||||
|
const opacity = colorUtils.getAlphaFromRgba(color)
|
||||||
|
this.backgroundColor = color
|
||||||
|
this.backgroundOpacity = opacity
|
||||||
|
} else if (type === 'rgba') {
|
||||||
|
const opacity = colorUtils.getAlphaFromRgba(backgroundColor)
|
||||||
|
this.backgroundColor = backgroundColor
|
||||||
|
this.backgroundOpacity = opacity
|
||||||
|
}
|
||||||
|
} else if (FabricVue?.canvas) {
|
||||||
|
if (this.backgroundColor) {
|
||||||
|
FabricVue.canvas.backgroundColor = this.backgroundColor
|
||||||
|
} else {
|
||||||
|
FabricVue.canvas.backgroundColor = 'rgba(255, 255, 255, 1)'
|
||||||
|
this.backgroundColor = 'rgba(255, 255, 255, 1)'
|
||||||
|
this.backgroundOpacity = 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const backgroundImage = FabricVue?.canvas?.backgroundImage
|
||||||
|
if (backgroundImage) {
|
||||||
|
bgUtils.handleBackgroundImageWhenCanvasSizeChange()
|
||||||
|
this.hasBackgroundImage = true
|
||||||
|
this.backgroundOpacity = backgroundImage.opacity
|
||||||
|
} else {
|
||||||
|
this.hasBackgroundImage = true
|
||||||
|
this.backgroundOpacity = 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// 画笔模块-缓存
|
||||||
|
export const useDrawStore = defineStore(
|
||||||
|
'draw',
|
||||||
|
{
|
||||||
|
state() {
|
||||||
|
return {
|
||||||
|
drawWidth: 1,
|
||||||
|
drawColors: ['#000000'],
|
||||||
|
shadowWidth: 0,
|
||||||
|
shadowColor: '#000000',
|
||||||
|
drawTextValue: 'draw',
|
||||||
|
drawStyle: TYPES.DrawStyle.Basic,
|
||||||
|
drawShapeCount: 2,
|
||||||
|
materialType: TYPES.MATERIAL_TYPE.CRAYON,
|
||||||
|
drawShape: TYPES.DrawShape.Bubble,
|
||||||
|
eraserWidth: 20,
|
||||||
|
multiColorType: TYPES.MultiColorType.COL,
|
||||||
|
textFontFamily: 'Georgia',
|
||||||
|
openAutoDraw: false,
|
||||||
|
fontStyles: [],
|
||||||
|
}
|
||||||
|
},
|
||||||
|
actions: {
|
||||||
|
// update draw width | 更新画笔宽度
|
||||||
|
updateDrawWidth(w) {
|
||||||
|
const oldW = this.drawWidth
|
||||||
|
if (oldW != w && FabricVue.canvas) {
|
||||||
|
FabricVue.canvas.freeDrawingBrush.width = fabricUtils.getDrawWidth(w)
|
||||||
|
this.drawWidth = w
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// update draw colors | 更新画笔颜色
|
||||||
|
updateDrawColors(drawColors = []) {
|
||||||
|
const drawStyle = this.drawStyle
|
||||||
|
switch(drawStyle) {
|
||||||
|
case TYPES.DrawStyle.Basic:
|
||||||
|
if (FabricVue.canvas) {
|
||||||
|
FabricVue.canvas.freeDrawingBrush.color = drawColors[0]
|
||||||
|
}
|
||||||
|
break
|
||||||
|
case TYPES.DrawStyle.Material:
|
||||||
|
if (this.drawColors[0] != drawColors[0]) {
|
||||||
|
material.render({})
|
||||||
|
}
|
||||||
|
break
|
||||||
|
case TYPES.DrawStyle.MultiColor:
|
||||||
|
renderMultiColor({colors: drawColors})
|
||||||
|
break
|
||||||
|
}
|
||||||
|
this.drawColors = drawColors
|
||||||
|
},
|
||||||
|
// update shadow width | 更新画笔阴影宽度
|
||||||
|
updateShadowWidth(shadowWidth) {
|
||||||
|
if (FabricVue.canvas) {
|
||||||
|
(FabricVue.canvas.freeDrawingBrush.shadow).blur = fabricUtils.getShadowWidth(shadowWidth)
|
||||||
|
}
|
||||||
|
this.shadowWidth = shadowWidth
|
||||||
|
},
|
||||||
|
// update shadow color | 更新画笔阴影颜色
|
||||||
|
updateShadowColor(shadowColor) {
|
||||||
|
if (FabricVue.canvas) {
|
||||||
|
(FabricVue.canvas.freeDrawingBrush.shadow).color = shadowColor
|
||||||
|
}
|
||||||
|
this.shadowColor = shadowColor
|
||||||
|
},
|
||||||
|
// update shadow Shape | 更新画笔阴影形状
|
||||||
|
updateDrawShape(drawShape) {
|
||||||
|
this.drawShape = drawShape
|
||||||
|
},
|
||||||
|
// update draw style | 更新画笔阴影样式
|
||||||
|
updateDrawStyle(drawStyle) {
|
||||||
|
this.drawStyle = drawStyle
|
||||||
|
FabricVue.handleDrawStyle()
|
||||||
|
},
|
||||||
|
// update draw shape count | 更新画笔形状数量
|
||||||
|
updateDrawShapeCount(drawShapeCount){
|
||||||
|
this.drawShapeCount = drawShapeCount
|
||||||
|
},
|
||||||
|
// update draw text value | 更新画笔文字
|
||||||
|
updateDrawTextValue(drawTextValue){
|
||||||
|
this.drawTextValue = drawTextValue
|
||||||
|
},
|
||||||
|
// update material type | 更新材质类型
|
||||||
|
updateMaterialType(materialType) {
|
||||||
|
if(this.materialType != materialType) {
|
||||||
|
material.render({materialType})
|
||||||
|
this.materialType = materialType
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// update eraser width | 更新橡皮擦宽度
|
||||||
|
updateEraserWidth(eraserWidth) {
|
||||||
|
const oldW = this.drawWidth
|
||||||
|
if (oldW != eraserWidth && FabricVue.canvas) {
|
||||||
|
FabricVue.canvas.freeDrawingBrush.width = fabricUtils.getEraserWidth(eraserWidth)
|
||||||
|
this.eraserWidth = eraserWidth
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// update multi color type | 更新多色材质类型
|
||||||
|
updateMultiColorType(multiColorType) {
|
||||||
|
if(this.multiColorType != multiColorType) {
|
||||||
|
renderMultiColor({type: multiColorType})
|
||||||
|
this.multiColorType = multiColorType
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// update text font family | 更新文字字体
|
||||||
|
updateTextFontFamily(fontFamily){
|
||||||
|
this.textFontFamily = fontFamily
|
||||||
|
},
|
||||||
|
// update auto draw state | 更新自动绘图状态
|
||||||
|
updateAutoDrawState() {
|
||||||
|
this.openAutoDraw = !this.openAutoDraw
|
||||||
|
},
|
||||||
|
// update font styles | 更新字体样式
|
||||||
|
updateFontStyles(type) {
|
||||||
|
const ind = this.fontStyles.findIndex(item => item === type)
|
||||||
|
if (ind >= 0) this.fontStyles.splice(ind, 1)
|
||||||
|
else this.fontStyles.push(type)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// interface BoardState {
|
||||||
|
// mode: string // operating mode
|
||||||
|
// drawType: string // draw type
|
||||||
|
// language: string // i18n language 'zh' 'en'
|
||||||
|
// canvasWidth: number // canvas width 0.1 ~ 1
|
||||||
|
// canvasHeight: number // canvas height 0.1 ~ 1
|
||||||
|
// backgroundColor: string // canvas background color
|
||||||
|
// backgroundOpacity: number // canvas background color opacity
|
||||||
|
// hasBackgroundImage: boolean // canvas background image
|
||||||
|
// backgroundImageOpacity: number // canvas background Image opacity
|
||||||
|
// isObjectCaching: boolean // fabric objectCaching
|
||||||
|
// openGuideLine: boolean // does the guide line show
|
||||||
|
// }
|
|
@ -1,15 +0,0 @@
|
||||||
import { defineStore } from 'pinia'
|
|
||||||
|
|
||||||
const routerStore = defineStore('router', {
|
|
||||||
state: () => ({
|
|
||||||
nowRouter: []
|
|
||||||
}),
|
|
||||||
actions: {
|
|
||||||
setNowRouter(payload) {
|
|
||||||
this.nowRouter = payload
|
|
||||||
}
|
|
||||||
},
|
|
||||||
mutations: {},
|
|
||||||
persist: true
|
|
||||||
})
|
|
||||||
export default routerStore
|
|
|
@ -1,105 +1,101 @@
|
||||||
import { defineStore } from 'pinia'
|
import { defineStore } from "pinia"
|
||||||
import { login, logout, getInfo } from '@/api/login'
|
import { login, logout, getInfo } from '@/api/login'
|
||||||
import { getToken, setToken, removeToken } from '@/utils/auth'
|
import { getToken, setToken, removeToken } from '@/utils/auth'
|
||||||
import defAva from '@/assets/images/user.png'
|
import defAva from '@/assets/images/user.png'
|
||||||
|
|
||||||
const useUserStore = defineStore('user', {
|
const useUserStore = defineStore(
|
||||||
state: () => ({
|
'user',
|
||||||
token: getToken(),
|
{
|
||||||
id: '',
|
state: () => ({
|
||||||
name: '',
|
token: getToken(),
|
||||||
avatar: '',
|
id: '',
|
||||||
roles: [],
|
name: '',
|
||||||
permissions: [],
|
avatar: '',
|
||||||
user: {}
|
roles: [],
|
||||||
}),
|
permissions: [],
|
||||||
actions: {
|
user: {}
|
||||||
// 登录
|
}),
|
||||||
login(userInfo) {
|
actions: {
|
||||||
const username = userInfo.username.trim()
|
// 登录
|
||||||
const password = userInfo.password
|
login(userInfo) {
|
||||||
const code = userInfo.code
|
const username = userInfo.username.trim()
|
||||||
const uuid = userInfo.uuid
|
const password = userInfo.password
|
||||||
return new Promise((resolve, reject) => {
|
const code = userInfo.code
|
||||||
login(username, password, code, uuid)
|
const uuid = userInfo.uuid
|
||||||
.then((res) => {
|
return new Promise((resolve, reject) => {
|
||||||
|
login(username, password, code, uuid).then(res => {
|
||||||
setToken(res.token)
|
setToken(res.token)
|
||||||
this.token = res.token
|
this.token = res.token
|
||||||
resolve(res)
|
resolve(res)
|
||||||
})
|
}).catch(error => {
|
||||||
.catch((error) => {
|
|
||||||
reject(error)
|
reject(error)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
// 获取用户信息
|
// 获取用户信息
|
||||||
getInfo() {
|
getInfo() {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
getInfo()
|
getInfo().then(res => {
|
||||||
.then((res) => {
|
|
||||||
res.user.avatar = import.meta.env.VITE_APP_BASE_API + res.user.avatar
|
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;
|
||||||
|
|
||||||
if (res.roles && res.roles.length > 0) {
|
if (res.roles && res.roles.length > 0) { // 验证返回的roles是否是一个非空数组
|
||||||
// 验证返回的roles是否是一个非空数组
|
|
||||||
this.roles = res.roles
|
this.roles = res.roles
|
||||||
this.permissions = res.permissions
|
this.permissions = res.permissions
|
||||||
} else {
|
} else {
|
||||||
this.roles = ['ROLE_DEFAULT']
|
this.roles = ['ROLE_DEFAULT']
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
this.id = user.userId
|
this.id = user.userId
|
||||||
this.userName = user.userName
|
this.userName = user.userName
|
||||||
this.nickName = user.nickName
|
this.nickName = user.nickName;
|
||||||
this.avatar = avatar
|
this.avatar = avatar;
|
||||||
this.userType = user.userType
|
this.userType = user.userType;
|
||||||
this.deptId = user.deptId
|
this.deptId = user.deptId;
|
||||||
this.deptName = user.deptName
|
this.deptName = user.deptName;
|
||||||
this.deptLogo = user.deptLogo
|
this.deptLogo = user.deptLogo;
|
||||||
this.deptSlogan = user.deptSlogan
|
this.deptSlogan = user.deptSlogan;
|
||||||
this.activeDeptId = user.activeDeptId
|
this.activeDeptId = user.activeDeptId;
|
||||||
this.activeDeptName = user.activeDeptName
|
this.activeDeptName = user.activeDeptName;
|
||||||
this.parentDeptId = user.parentDeptId
|
this.parentDeptId = user.parentDeptId;
|
||||||
this.parentDeptName = user.parentDeptName
|
this.parentDeptName = user.parentDeptName;
|
||||||
this.edusubject = user.edusubject
|
this.edusubject = user.edusubject;
|
||||||
this.edudegree = user.edudegree
|
this.edudegree = user.edudegree;
|
||||||
this.edustage = user.edustage
|
this.edustage = user.edustage;
|
||||||
this.userType = user.userType
|
this.userType = user.userType;
|
||||||
this.studentId = user.studentId
|
this.studentId = user.studentId;
|
||||||
this.timUserId = user.timuserid
|
this.timUserId = user.timuserid;
|
||||||
this.plainpwd = user.plainpwd
|
this.plainpwd = user.plainpwd;
|
||||||
|
|
||||||
this.roles = res.roles
|
this.roles = res.roles;
|
||||||
|
|
||||||
resolve(res)
|
resolve(res)
|
||||||
})
|
}).catch(error => {
|
||||||
.catch((error) => {
|
|
||||||
reject(error)
|
reject(error)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
// 退出系统
|
// 退出系统
|
||||||
logOut() {
|
logOut() {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
logout(this.token)
|
logout(this.token).then(() => {
|
||||||
.then(() => {
|
|
||||||
this.token = ''
|
this.token = ''
|
||||||
this.roles = []
|
this.roles = []
|
||||||
this.permissions = []
|
this.permissions = []
|
||||||
localStorage.clear()
|
localStorage.clear()
|
||||||
removeToken()
|
removeToken()
|
||||||
resolve()
|
resolve()
|
||||||
})
|
}).catch(error => {
|
||||||
.catch((error) => {
|
|
||||||
removeToken() // zdg: 网络异常时,清除前端退出进入登录页
|
removeToken() // zdg: 网络异常时,清除前端退出进入登录页
|
||||||
reject(error)
|
reject(error)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
persist: true
|
persist: true
|
||||||
})
|
})
|
||||||
|
|
||||||
export default useUserStore
|
export default useUserStore
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
|
|
||||||
import useUserStore from '@/store/modules/user'
|
import useUserStore from '@/store/modules/user'
|
||||||
const baseConfig = () => {
|
|
||||||
const userStore = useUserStore()
|
const userStore = useUserStore()
|
||||||
return {
|
const baseConfig = {
|
||||||
// Electron 设置cookie
|
// Electron 设置cookie
|
||||||
url: import.meta.env.VITE_APP_BUILD_BASE_PATH,
|
url: import.meta.env.VITE_APP_BUILD_BASE_PATH,
|
||||||
// url: 'https://file.ysaix.com:7868',
|
// url: 'https://file.ysaix.com:7868',
|
||||||
|
@ -11,48 +12,52 @@ const baseConfig = () => {
|
||||||
value: userStore.token,
|
value: userStore.token,
|
||||||
// 域名
|
// 域名
|
||||||
domain: import.meta.env.VITE_APP_DOMAIN
|
domain: import.meta.env.VITE_APP_DOMAIN
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default () => {
|
// 作业布置
|
||||||
|
const homeWork = {
|
||||||
|
data: { ...baseConfig},
|
||||||
|
// 完整路径
|
||||||
|
fullPath: `${baseConfig.url}/teaching/classtaskassign?titleName=%E4%BD%9C%E4%B8%9A%E5%B8%83%E7%BD%AE`
|
||||||
|
}
|
||||||
|
|
||||||
|
// 作业反馈
|
||||||
|
const feedback = {
|
||||||
|
data: { ...baseConfig},
|
||||||
|
// 完整路径
|
||||||
|
fullPath: `${baseConfig.url}/teaching/classtaskassign?titleName=作业反馈`
|
||||||
|
}
|
||||||
|
|
||||||
|
// 高考研读
|
||||||
|
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`
|
||||||
|
}
|
||||||
|
|
||||||
|
const getBaseData = () => {
|
||||||
return {
|
return {
|
||||||
// 作业布置
|
data: { ...baseConfig},
|
||||||
homeWork: {
|
fullPath: `${baseConfig.url}`
|
||||||
data: { ...baseConfig() },
|
|
||||||
// 完整路径
|
|
||||||
fullPath: `${baseConfig().url}/teaching/classtaskassign?titleName=%E4%BD%9C%E4%B8%9A%E5%B8%83%E7%BD%AE`
|
|
||||||
},
|
|
||||||
|
|
||||||
// 作业反馈
|
|
||||||
feedback: {
|
|
||||||
data: { ...baseConfig() },
|
|
||||||
// 完整路径
|
|
||||||
fullPath: `${baseConfig().url}/teaching/classtaskassign?titleName=作业反馈`
|
|
||||||
},
|
|
||||||
|
|
||||||
// 高考研读
|
|
||||||
gk: {
|
|
||||||
data: { ...baseConfig() },
|
|
||||||
fullPath: `${baseConfig().url}/education/colentrance`
|
|
||||||
},
|
|
||||||
|
|
||||||
// 课标研读
|
|
||||||
standard: {
|
|
||||||
data: { ...baseConfig() },
|
|
||||||
fullPath: `${baseConfig().url}/teaching/chatwithstandard`
|
|
||||||
},
|
|
||||||
|
|
||||||
// 教学大模型
|
|
||||||
aiModel: {
|
|
||||||
data: { ...baseConfig() },
|
|
||||||
fullPath: `${baseConfig().url}/platofai`
|
|
||||||
},
|
|
||||||
|
|
||||||
getBaseData: () => {
|
|
||||||
return {
|
|
||||||
data: { ...baseConfig() },
|
|
||||||
fullPath: `${baseConfig().url}`
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default {
|
||||||
|
homeWork,
|
||||||
|
feedback,
|
||||||
|
gk,
|
||||||
|
standard,
|
||||||
|
aiModel,
|
||||||
|
getBaseData
|
||||||
|
}
|
||||||
|
|
|
@ -69,9 +69,9 @@ export const createWindow = async (type, data) => {
|
||||||
// parent: mainWin, // 父窗口
|
// parent: mainWin, // 父窗口
|
||||||
// autoClose: true, // 关闭窗口后自动关闭
|
// autoClose: true, // 关闭窗口后自动关闭
|
||||||
}
|
}
|
||||||
// data.isConsole = true // 是否开启控制台
|
|
||||||
data.option = {...defOption, ...option}
|
data.option = {...defOption, ...option}
|
||||||
const win = await toolWindow(data)
|
const win = await toolWindow(data)
|
||||||
|
win.setTitle('窗口标题: 我的自定义参数')
|
||||||
win.type = type // 唯一标识
|
win.type = type // 唯一标识
|
||||||
win.show()
|
win.show()
|
||||||
win.setFullScreen(true) // 设置窗口为全屏
|
win.setFullScreen(true) // 设置窗口为全屏
|
||||||
|
@ -110,7 +110,7 @@ export const createWindow = async (type, data) => {
|
||||||
* @author: zdg
|
* @author: zdg
|
||||||
* @date 2021-07-05 14:07:01
|
* @date 2021-07-05 14:07:01
|
||||||
*/
|
*/
|
||||||
export function toolWindow({url, isConsole, option={}}) {
|
export function toolWindow({url, isFile, isConsole, option={}}) {
|
||||||
// width = window.screen.width
|
// width = window.screen.width
|
||||||
let width = option?.width || 800
|
let width = option?.width || 800
|
||||||
let height = option?.height || 600
|
let height = option?.height || 600
|
||||||
|
@ -175,9 +175,9 @@ const eventHandles = (type, win) => {
|
||||||
const setIgnore = (_, ignore) => {win.setIgnoreMouseEvents(ignore, {forward: true})}
|
const setIgnore = (_, ignore) => {win.setIgnoreMouseEvents(ignore, {forward: true})}
|
||||||
Remote.ipcMain.on('tool-sphere:set:ignore', setIgnore)
|
Remote.ipcMain.on('tool-sphere:set:ignore', setIgnore)
|
||||||
// 关闭窗口
|
// 关闭窗口
|
||||||
Remote.ipcMain.once('tool-sphere:close', () => { win&&win.destroy() })
|
Remote.ipcMain.once('tool-sphere:close', () => { win.destroy() })
|
||||||
// 放大监听-测试
|
// 放大监听-测试
|
||||||
Remote.ipcMain.once('maximize-window', () => {win&&win.destroy()})
|
Remote.ipcMain.once('maximize-window', () => {win.destroy()})
|
||||||
const on = {
|
const on = {
|
||||||
onClosed: () => {Remote.ipcMain.off('tool-sphere:set:ignore', setIgnore)}
|
onClosed: () => {Remote.ipcMain.off('tool-sphere:set:ignore', setIgnore)}
|
||||||
}
|
}
|
||||||
|
@ -185,7 +185,8 @@ const eventHandles = (type, win) => {
|
||||||
break}
|
break}
|
||||||
case 'open-PDF': {
|
case 'open-PDF': {
|
||||||
// 最小化窗口 minimize()
|
// 最小化窗口 minimize()
|
||||||
Remote.ipcMain.once('open-PDF:minimize', () => {win&&win.destroy()})
|
Remote.ipcMain.once('open-PDF:minimize', () => {win.destroy()})
|
||||||
|
win.webContents.openDevTools()
|
||||||
publicMethods() // 加载公共方法
|
publicMethods() // 加载公共方法
|
||||||
break}
|
break}
|
||||||
default:
|
default:
|
||||||
|
|
|
@ -34,6 +34,9 @@
|
||||||
<div
|
<div
|
||||||
v-if="item.levelFirstName"
|
v-if="item.levelFirstName"
|
||||||
style="
|
style="
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
"
|
"
|
||||||
|
|
|
@ -13,23 +13,17 @@
|
||||||
<el-button class="btn" @click="handleOutLink('aiModel')">教学大模型</el-button>
|
<el-button class="btn" @click="handleOutLink('aiModel')">教学大模型</el-button>
|
||||||
</div>
|
</div>
|
||||||
<el-button type="primary" class="to-class-btn" @click="openLesson">
|
<el-button type="primary" class="to-class-btn" @click="openLesson">
|
||||||
<i class="iconfont icon-lingdang"></i>上课</el-button
|
<i class="iconfont icon-lingdang"></i>上课</el-button>
|
||||||
>
|
|
||||||
<div class="top-zoom-style"></div>
|
<div class="top-zoom-style"></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="prepare-body-header">
|
<div class="prepare-body-header">
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label style="font-size: 15px">共{{ currentFileList.length }}个文件</label>
|
<label style="font-size: 15px">共{{ currentFileList.length }}个文件</label>
|
||||||
<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>
|
||||||
|
@ -47,38 +41,20 @@
|
||||||
<el-button @click="handleOutLink('feedback')">作业反馈</el-button>
|
<el-button @click="handleOutLink('feedback')">作业反馈</el-button>
|
||||||
<el-button @click="handleOutLink('homeWork')">布置作业</el-button>
|
<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" @on-set="openSet">
|
||||||
>
|
|
||||||
<file-list-item
|
|
||||||
v-for="(item, index) in currentFileList"
|
|
||||||
:key="index"
|
|
||||||
:item="item"
|
|
||||||
:index="index"
|
|
||||||
@on-move="onMoveSingleFile"
|
|
||||||
@on-delete="deleteTalk"
|
|
||||||
@on-set="openSet"
|
|
||||||
>
|
|
||||||
<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" />
|
||||||
|
@ -111,15 +87,7 @@ const { ipcRenderer } = window.electron || {}
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'Prepare',
|
name: 'Prepare',
|
||||||
components: {
|
components: { ChooseTextbook, Refresh, uploadDialog, FileListItem, FileOperBatch, MoveFile, SetHomework },
|
||||||
ChooseTextbook,
|
|
||||||
Refresh,
|
|
||||||
uploadDialog,
|
|
||||||
FileListItem,
|
|
||||||
FileOperBatch,
|
|
||||||
MoveFile,
|
|
||||||
SetHomework
|
|
||||||
},
|
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
moveFile: [],
|
moveFile: [],
|
||||||
|
@ -158,13 +126,6 @@ export default {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
watch: {
|
|
||||||
$route(to) {
|
|
||||||
if (to.path != '/prepare' && this.timerId) {
|
|
||||||
clearInterval(this.timerId)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
created() {
|
created() {
|
||||||
this.userStore = useUserStore().user
|
this.userStore = useUserStore().user
|
||||||
ipcRenderer.removeAllListeners('copy-file-default-reply')
|
ipcRenderer.removeAllListeners('copy-file-default-reply')
|
||||||
|
@ -172,8 +133,10 @@ export default {
|
||||||
this.callback(param)
|
this.callback(param)
|
||||||
})
|
})
|
||||||
this.lastAsyncAllTime = localStorage.getItem('lastAsyncAllTime')
|
this.lastAsyncAllTime = localStorage.getItem('lastAsyncAllTime')
|
||||||
|
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
},
|
},
|
||||||
mounted() {},
|
|
||||||
activated() {
|
activated() {
|
||||||
if (this.uploadData.textbookId !== null) {
|
if (this.uploadData.textbookId !== null) {
|
||||||
this.asyncAllFile()
|
this.asyncAllFile()
|
||||||
|
@ -325,20 +288,16 @@ export default {
|
||||||
this.createTimer()
|
this.createTimer()
|
||||||
}
|
}
|
||||||
// key 对应的 linkConfig.js 外部链接配置
|
// key 对应的 linkConfig.js 外部链接配置
|
||||||
let configObj = outLink()[key]
|
let configObj = outLink[key]
|
||||||
// 通知主进程
|
// 通知主进程
|
||||||
ipcRenderer.send('openWindow', {
|
ipcRenderer.send('openWindow', {
|
||||||
fullPath: configObj.fullPath,
|
fullPath: configObj.fullPath,
|
||||||
cookieData: { ...configObj.data }
|
cookieData: { ...(configObj.data) }
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
// 根据教材章节单元ID 查询作业列表所需ID
|
// 根据教材章节单元ID 查询作业列表所需ID
|
||||||
getChapterId() {
|
getChapterId() {
|
||||||
return listEntpcourse({
|
return listEntpcourse({ evalid: this.uploadData.levelSecondId, edituserid: this.userStore.userId, pageSize: 500 })
|
||||||
evalid: this.uploadData.levelSecondId,
|
|
||||||
edituserid: this.userStore.userId,
|
|
||||||
pageSize: 500
|
|
||||||
})
|
|
||||||
},
|
},
|
||||||
//
|
//
|
||||||
createTimer() {
|
createTimer() {
|
||||||
|
@ -348,22 +307,20 @@ export default {
|
||||||
},
|
},
|
||||||
// 查询作业列表
|
// 查询作业列表
|
||||||
getHomeWorkList() {
|
getHomeWorkList() {
|
||||||
homeworklist({
|
homeworklist({ entpcourseid: this.entpcourseid, edituserid: this.userStore.userId, pageSize: 100 }).then(res => {
|
||||||
entpcourseid: this.entpcourseid,
|
|
||||||
edituserid: this.userStore.userId,
|
|
||||||
pageSize: 100
|
|
||||||
}).then((res) => {
|
|
||||||
//以下代码 参照AIx web端 作业布置
|
//以下代码 参照AIx web端 作业布置
|
||||||
let list = []
|
let list = []
|
||||||
for (var i = 0; i < res.rows.length; i++) {
|
for (var i = 0; i < res.rows.length; i++) {
|
||||||
res.rows[i].taskconfig = []
|
|
||||||
|
res.rows[i].taskconfig = [];
|
||||||
|
|
||||||
// 找child
|
// 找child
|
||||||
for (var j = 0; j < res.rows.length; j++) {
|
for (var j = 0; j < res.rows.length; j++) {
|
||||||
if (res.rows[j].parentid == res.rows[i].id) {
|
if (res.rows[j].parentid == res.rows[i].id) {
|
||||||
var ss = []
|
var ss = [];
|
||||||
if (res.rows[j].classworkdatastudentids != null) {
|
if (res.rows[j].classworkdatastudentids != null) {
|
||||||
ss = JSON.parse('[' + res.rows[j].classworkdatastudentids + ']')
|
ss = JSON.parse('[' + res.rows[j].classworkdatastudentids + ']');
|
||||||
}
|
}
|
||||||
var js = {
|
var js = {
|
||||||
id: res.rows[j].id,
|
id: res.rows[j].id,
|
||||||
|
@ -386,7 +343,7 @@ export default {
|
||||||
weights: res.rows[j].weights,
|
weights: res.rows[j].weights,
|
||||||
feedtype: res.rows[j].feedtype
|
feedtype: res.rows[j].feedtype
|
||||||
}
|
}
|
||||||
res.rows[i].taskconfig.push(js)
|
res.rows[i].taskconfig.push(js);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
res.rows[i].fileShowName = res.rows[i].uniquekey
|
res.rows[i].fileShowName = res.rows[i].uniquekey
|
||||||
|
@ -394,16 +351,15 @@ export default {
|
||||||
// 注意slideid>0的,这一些作业是添加到PPT页面的,所以在作业管理中不能出现
|
// 注意slideid>0的,这一些作业是添加到PPT页面的,所以在作业管理中不能出现
|
||||||
// 2024-05-15,酉阳,jackyshen
|
// 2024-05-15,酉阳,jackyshen
|
||||||
if (res.rows[i].classid == 0 && res.rows[i].slideid == 0) {
|
if (res.rows[i].classid == 0 && res.rows[i].slideid == 0) {
|
||||||
list.push(res.rows[i])
|
list.push(res.rows[i]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// 如果是习题训练任务,则检查一共有多少道
|
// 如果是习题训练任务,则检查一共有多少道
|
||||||
if (res.rows[i].entpcourseworklist != '') {
|
if (res.rows[i].entpcourseworklist != '') {
|
||||||
res.rows[i].entpcourseworklistarray = JSON.parse(
|
res.rows[i].entpcourseworklistarray = JSON.parse('[' + res.rows[i].entpcourseworklist + ']');
|
||||||
'[' + res.rows[i].entpcourseworklist + ']'
|
|
||||||
)
|
|
||||||
} else {
|
} else {
|
||||||
res.rows[i].entpcourseworklistarray = []
|
res.rows[i].entpcourseworklistarray = [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// 去重
|
// 去重
|
||||||
|
@ -414,11 +370,11 @@ export default {
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
// 打开布置作业窗口
|
// 打开布置作业窗口
|
||||||
openSet(row) {
|
openSet(row){
|
||||||
this.row = row
|
this.row = row
|
||||||
this.setDialog = true
|
this.setDialog = true
|
||||||
},
|
},
|
||||||
closeHomework() {
|
closeHomework(){
|
||||||
this.setDialog = false
|
this.setDialog = false
|
||||||
},
|
},
|
||||||
// 打开PDF-课件
|
// 打开PDF-课件
|
||||||
|
@ -429,6 +385,13 @@ export default {
|
||||||
openLesson() {
|
openLesson() {
|
||||||
createWindow('tool-sphere', { url: '/tool/sphere' })
|
createWindow('tool-sphere', { url: '/tool/sphere' })
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
$route(to) {
|
||||||
|
if (to.path != '/prepare' && this.timerId) {
|
||||||
|
clearInterval(this.timerId)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
@ -518,7 +481,7 @@ export default {
|
||||||
border-color: #ffffff;
|
border-color: #ffffff;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background: rgba(255, 255, 255, 0.3);
|
background: rgba(255, 255, 255, 0.3)
|
||||||
}
|
}
|
||||||
|
|
||||||
&:first-child {
|
&:first-child {
|
||||||
|
@ -555,6 +518,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 {
|
||||||
|
|
|
@ -4,37 +4,20 @@
|
||||||
</template>
|
</template>
|
||||||
<script setup>
|
<script setup>
|
||||||
// 功能说明:画板
|
// 功能说明:画板
|
||||||
import { ref, onMounted, watchEffect } from 'vue'
|
import { ref, onMounted } from 'vue'
|
||||||
import {FabricVue, TYPES} from '@/plugins/fabric'
|
import { FabricVue } from '@/plugins/fabric'
|
||||||
const canvasRef = ref(null) // 画布
|
import { useBoardStore, useDrawStore } from '@/store/modules/draw'
|
||||||
const isMouse = ref(true) // 鼠标是否在画布上
|
const canvasRef = ref(null)
|
||||||
const props = defineProps({
|
|
||||||
modelValue: String
|
|
||||||
})
|
|
||||||
// 画板初始化配置
|
|
||||||
onMounted(async() => {
|
onMounted(async() => {
|
||||||
if (canvasRef.value) {
|
if (canvasRef.value) {
|
||||||
FabricVue.drawConfig.drawColors = ['red']
|
useBoardStore().backgroundColor = 'transparent'
|
||||||
FabricVue.boardConfig.backgroundColor = 'transparent'
|
useDrawStore().drawColors = ['red']
|
||||||
const option = { freeDrawingCursor: 'default' }
|
const option = { freeDrawingCursor: 'default' }
|
||||||
await FabricVue.initCanvas(canvasRef.value, option)
|
await FabricVue.initCanvas(canvasRef.value, option)
|
||||||
}
|
// FabricVue.canvas.backgroundColor = 'transparent'
|
||||||
})
|
// FabricVue.canvas.setWidth(500)
|
||||||
// 监听
|
// FabricVue.canvas.setHeight(500)
|
||||||
watchEffect(() => {
|
|
||||||
// console.log('board 画板: ', props.modelValue)
|
|
||||||
isMouse.value = false
|
|
||||||
switch(props.modelValue) {
|
|
||||||
case 'select': // 选择模式
|
|
||||||
FabricVue.handleMode(TYPES.ActionMode.OTHER)
|
|
||||||
break
|
|
||||||
case 'brush': // 画笔模式
|
|
||||||
FabricVue.handleMode(TYPES.ActionMode.DRAW)
|
|
||||||
FabricVue.canvas.freeDrawingCursor = 'default'
|
|
||||||
break
|
|
||||||
case 'eraser': // 板擦模式
|
|
||||||
FabricVue.handleMode(TYPES.ActionMode.ERASE)
|
|
||||||
break
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -1,17 +1,12 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="warp-all">
|
<div class="warp-all">
|
||||||
<board-vue v-model="tabActive"></board-vue>
|
<board-vue></board-vue>
|
||||||
<!-- 底部工具栏 -->
|
<!-- 底部工具栏 -->
|
||||||
<div ref="tool" class="tool-bottom-all" :style="dataPos.style"
|
<el-row id="test" class="tool-bottom-all" @mouseenter="mouseChange(0)" @mouseleave="mouseChange(1)">
|
||||||
@mouseenter="mouseChange(0)" @mouseleave="mouseChange(1)">
|
<el-col :span="3" class="flex justify-center items-center">
|
||||||
<div @mousedown="e => dargHandle(e,'down')"
|
<div class="c-logo"><el-image :src="logo" @click="tabChange('close')" /></div>
|
||||||
@mousemove="e => dargHandle(e,'move')"
|
</el-col>
|
||||||
@mouseup="e => dargHandle(e,'up')">
|
<el-col :span="20">
|
||||||
<div class="c-logo" @click="logoHandle" title="拖动 | 折叠 | 展开">
|
|
||||||
<el-image :src="logo" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="tool-btns" v-show="!isFold">
|
|
||||||
<el-segmented class="c-btns" v-model="tabActive" :options="btnList" size="large" block
|
<el-segmented class="c-btns" v-model="tabActive" :options="btnList" size="large" block
|
||||||
@change="tabChange">
|
@change="tabChange">
|
||||||
<template #default="{item}">
|
<template #default="{item}">
|
||||||
|
@ -21,27 +16,22 @@
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</el-segmented>
|
</el-segmented>
|
||||||
</div>
|
</el-col>
|
||||||
</div>
|
</el-row>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
// 功能说明:electron 悬浮球
|
// 功能说明:electron 悬浮球
|
||||||
import { onMounted, ref, reactive } from 'vue'
|
import { onMounted, ref } from 'vue'
|
||||||
import logo from '@root/resources/icon.png' // logo
|
import logo from '@root/resources/icon.png' // logo
|
||||||
import boardVue from './components/board.vue' // 画板
|
import boardVue from './components/board.vue'
|
||||||
import vDrag from '@/directive/drag'
|
|
||||||
import { tryOnUnmounted } from '@vueuse/core'
|
|
||||||
// const Remote = require('@electron/remote') // remote对象
|
// const Remote = require('@electron/remote') // remote对象
|
||||||
const { ipcRenderer } = require('electron')
|
const { ipcRenderer } = require('electron')
|
||||||
|
|
||||||
const tool = ref()
|
|
||||||
const tabActive = ref('select') // 工具栏当前选中项
|
const tabActive = ref('select')
|
||||||
const isFold = ref(false) // 折叠工具栏
|
const btnList = [
|
||||||
const isDrag = ref(false) // 开始拖拽
|
|
||||||
const dataPos = reactive({style:{}}) // 对象属性
|
|
||||||
const btnList = [ // 工具栏按钮列表
|
|
||||||
{ label: '选择', value: 'select', icon: 'icon-mouse' },
|
{ label: '选择', value: 'select', icon: 'icon-mouse' },
|
||||||
{ label: '画笔', value: 'brush', icon: 'icon-huabi' },
|
{ label: '画笔', value: 'brush', icon: 'icon-huabi' },
|
||||||
{ label: '板擦', value: 'eraser', icon: 'icon-xiangpica' },
|
{ label: '板擦', value: 'eraser', icon: 'icon-xiangpica' },
|
||||||
|
@ -49,13 +39,14 @@ const btnList = [ // 工具栏按钮列表
|
||||||
{ label: '聚焦', value: 'focus', icon: 'icon-jujiao' },
|
{ label: '聚焦', value: 'focus', icon: 'icon-jujiao' },
|
||||||
{ label: '更多', value: 'more', icon: 'icon-xiazai9' },
|
{ label: '更多', value: 'more', icon: 'icon-xiazai9' },
|
||||||
]
|
]
|
||||||
let offsetX = 0, offsetY = 0, dragtime = 0
|
|
||||||
// ==== 方法 ===
|
// ==== 方法 ===
|
||||||
const tabChange = (val) => { // 切换tab-change
|
const tabChange = (val) => { // 切换tab-change
|
||||||
|
console.log('xxxx', val)
|
||||||
switch (val) {
|
switch (val) {
|
||||||
case 'brush': // 画笔
|
case 'brush':
|
||||||
break
|
break
|
||||||
case 'eraser': // 板擦
|
case 'eraser':
|
||||||
break
|
break
|
||||||
case 'interact':
|
case 'interact':
|
||||||
break
|
break
|
||||||
|
@ -70,48 +61,6 @@ const tabChange = (val) => { // 切换tab-change
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const logoHandle = (e,t) => { // logo 点击-事件 折叠|展开
|
|
||||||
if (Date.now() - dragtime < 200) {
|
|
||||||
isFold.value = !isFold.value
|
|
||||||
}
|
|
||||||
console.log('click', isDrag.value)
|
|
||||||
}
|
|
||||||
const dargHandle = (e, type) => { // 拖拽处理
|
|
||||||
e.preventDefault(); // 阻止默认的拖拽行为
|
|
||||||
if (type == 'down') {
|
|
||||||
dragtime = Date.now()
|
|
||||||
return isDrag.value = true
|
|
||||||
} else if (type == 'up') {
|
|
||||||
return isDrag.value = false
|
|
||||||
} else {
|
|
||||||
if (!isDrag.value) return false
|
|
||||||
if (!e.clientX&&!e.clientY){ // 最后一次松开坐标为0
|
|
||||||
offsetX = 0
|
|
||||||
offsetY = 0
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if (!offsetX&&!offsetY) { // 第一次, 获取元素坐标
|
|
||||||
setStyle()
|
|
||||||
} else {
|
|
||||||
const x = e.clientX - offsetX
|
|
||||||
const y = e.clientY - offsetY
|
|
||||||
setStyle(x, y)
|
|
||||||
}
|
|
||||||
offsetX = e.clientX
|
|
||||||
offsetY = e.clientY
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const setStyle = (x, y) => { // 拖拽-设置值
|
|
||||||
if (offsetX && offsetY) { // 有值
|
|
||||||
const {left, top} = dataPos.style
|
|
||||||
const ox = parseInt(left.replace('px',''))
|
|
||||||
const oy = parseInt(top.replace('px',''))
|
|
||||||
dataPos.style = {...dataPos.style, left: ox + x + 'px', top: oy + y + 'px'}
|
|
||||||
} else { // 初始值
|
|
||||||
const {left, top} = tool.value.getBoundingClientRect() // 获取元素位置
|
|
||||||
dataPos.style = {bottom: 'unset', transform:'unset', left: left+'px', top: top+'px'}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const mouseChange = (bool) => { // 鼠标移入工具栏 是否穿透
|
const mouseChange = (bool) => { // 鼠标移入工具栏 是否穿透
|
||||||
let resBool = false
|
let resBool = false
|
||||||
if (tabActive.value == 'select') resBool = !!bool
|
if (tabActive.value == 'select') resBool = !!bool
|
||||||
|
@ -125,8 +74,7 @@ const mouseChange = (bool) => { // 鼠标移入工具栏 是否穿透
|
||||||
}
|
}
|
||||||
// 底部工具栏
|
// 底部工具栏
|
||||||
.tool-bottom-all{
|
.tool-bottom-all{
|
||||||
// width: 45vw;
|
width: 45vw;
|
||||||
display: flex;
|
|
||||||
position: fixed;
|
position: fixed;
|
||||||
bottom: 3em;
|
bottom: 3em;
|
||||||
left: 50%;
|
left: 50%;
|
||||||
|
@ -141,11 +89,9 @@ const mouseChange = (bool) => { // 鼠标移入工具栏 是否穿透
|
||||||
height: 5rem;
|
height: 5rem;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
box-shadow: 0px 0px 8px rgb(0 0 0 / 40%);
|
box-shadow: 0px 0px 8px rgb(0 0 0 / 40%);
|
||||||
// &:hover{
|
position: absolute;
|
||||||
// .el-image{transform: scale(1.1);}
|
left: 0;
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
.tool-btns{margin: 0 35px 0 7px;}
|
|
||||||
.c-btn{
|
.c-btn{
|
||||||
i{font-size: 2rem;}
|
i{font-size: 2rem;}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,5 @@
|
||||||
<template>
|
<template>
|
||||||
<canvas ref="canvasRef" />
|
<canvas ref="canvasRef" />
|
||||||
<button @click="eraseTo">橡皮擦
|
|
||||||
<i class="iconfont icon-xiangpica"></i>
|
|
||||||
</button>
|
|
||||||
<button @click="close">销毁</button>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
|
@ -11,38 +7,23 @@
|
||||||
import { ref, onMounted } from 'vue'
|
import { ref, onMounted } from 'vue'
|
||||||
// import { FabricVue } from '@/plugins/fabric'
|
// import { FabricVue } from '@/plugins/fabric'
|
||||||
// import { useBoardStore } from '@/store/modules/draw'
|
// import { useBoardStore } from '@/store/modules/draw'
|
||||||
import {FabricVue, TYPES} from '@/plugins/fabric'
|
import FabricVue from '@/plugins/fabric/test'
|
||||||
let canvasRef = ref(null)
|
let canvasRef = ref(null)
|
||||||
let canvas = null
|
|
||||||
|
|
||||||
onMounted(async() => {
|
onMounted(async() => {
|
||||||
// console.log(canvasRef, FabricVue)
|
console.log(canvasRef, FabricVue)
|
||||||
// canvasRef.value = 123
|
|
||||||
if (canvasRef.value) {
|
if (canvasRef.value) {
|
||||||
// useBoardStore().backgroundColor = 'transparent'
|
// useBoardStore().backgroundColor = 'transparent'
|
||||||
const option = { freeDrawingCursor: 'default' }
|
const option = { freeDrawingCursor: 'default' }
|
||||||
await FabricVue.initCanvas(canvasRef.value, option)
|
// await FabricVue.initCanvas(canvasRef.value, option)
|
||||||
// FabricVue.canvas.setWidth(500)
|
// FabricVue.canvas.setWidth(500)
|
||||||
// FabricVue.canvas.setHeight(500)
|
// FabricVue.canvas.setHeight(500)
|
||||||
|
await FabricVue.initCanvas(canvasRef.value, option)
|
||||||
|
// const pencilBrush = new fabric.PencilBrush(canvas)
|
||||||
|
FabricVue.canvas.isDrawingMode = true
|
||||||
}
|
}
|
||||||
// if (canvasRef.value) {
|
|
||||||
// canvas = new fabric.Canvas(canvasRef.value,{
|
|
||||||
// isDrawingMode: true,
|
|
||||||
// freeDrawingCursor: 'default',
|
|
||||||
// backgroundColor: 'transparent',
|
|
||||||
// width: window.innerWidth,
|
|
||||||
// height: window.innerHeight
|
|
||||||
// })
|
|
||||||
// canvas.isDrawingMode = true
|
|
||||||
// }
|
|
||||||
})
|
})
|
||||||
const eraseTo = () => { // 橡皮擦
|
|
||||||
FabricVue.handleMode(TYPES.ActionMode.ERASE)
|
|
||||||
// FabricVue.removeCanvas()
|
|
||||||
// FabricVue.canvas.dispose()
|
|
||||||
// canvas.dispose()
|
|
||||||
}
|
|
||||||
const close = () => { FabricVue.removeCanvas() }
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
|
Loading…
Reference in New Issue