diff --git a/src/configs/defaultElement.ts b/src/configs/defaultElement.ts
index a8bf51be..d03f922f 100644
--- a/src/configs/defaultElement.ts
+++ b/src/configs/defaultElement.ts
@@ -1,4 +1,4 @@
-const DEFAULT_COLOR = '#888'
+const DEFAULT_COLOR = '#41464b'
export const DEFAULT_TEXT = {
type: 'text',
@@ -6,12 +6,10 @@ export const DEFAULT_TEXT = {
top: 0,
width: 300,
height: 0,
- padding: 5,
opacity: 1,
lineHeight: 1.5,
segmentSpacing: 5,
- textType: 'content',
- content: '
“单击此处添加文本”
',
+ content: '请输入内容',
}
export const DEFAULT_IMAGE = {
@@ -27,25 +25,6 @@ export const DEFAULT_SHAPE = {
lockRatio: false,
}
-export const DEFAULT_SHAPE_LINE = {
- type: 'shape',
- borderStyle: 'solid',
- borderWidth: 2,
- borderColor: DEFAULT_COLOR,
- fill: 'rgba(0, 0, 0, 0)',
- lockRatio: false,
-}
-
-export const DEFAULT_ICON = {
- type: 'icon',
- left: 0,
- top: 0,
- width: 80,
- height: 80,
- color: DEFAULT_COLOR,
- lockRatio: true,
-}
-
export const DEFAULT_LINE = {
type: 'line',
style: 'solid',
@@ -62,14 +41,6 @@ export const DEFAULT_CHART = {
height: 500,
}
-export const DEFAULT_IFRAME = {
- type: 'iframe',
- left: 0,
- top: 0,
- width: 800,
- height: 480,
-}
-
export const DEFAULT_TABLE = {
type: 'table',
left: 0,
diff --git a/src/mocks/index.ts b/src/mocks/index.ts
index f20b7aa8..e4bdcf43 100644
--- a/src/mocks/index.ts
+++ b/src/mocks/index.ts
@@ -18,12 +18,10 @@ export const slides: Slide[] = [
borderColor: '#5b7d89',
fill: 'rgba(220,220,220,0.8)',
shadow: '1px 1px 3px rgba(10,10,10,.5)',
- padding: 10,
opacity: 1,
lineHeight: 1.5,
segmentSpacing: 10,
isLock: false,
- textType: 'title',
content: '一段测试文字,字号固定为28px
',
},
{
@@ -79,12 +77,10 @@ export const slides: Slide[] = [
width: 220,
height: 188,
rotate: 0,
- padding: 10,
opacity: 1,
lineHeight: 1.5,
segmentSpacing: 10,
isLock: false,
- textType: 'content',
content: '😀 😐 😶 😜 🔔 ⭐ ⚡ 🔥 👍 💡 🔰 🎀 🎁 🥇 🏅 🏆 🎈 🎉 💎 🚧 ⛔ 📢 ⌛ ⏰ 🕒 🧩 🎵 📎 🔒 🔑 ⛳ 📌 📍 💬 📅 📈 📋 📜 📁 📱 💻 💾 🌏 🚚 🚡 🚢💧 🌐 🧭 💰 💳 🛒
',
},
],
diff --git a/src/store/mutations.ts b/src/store/mutations.ts
index 2d385cef..c7e812ba 100644
--- a/src/store/mutations.ts
+++ b/src/store/mutations.ts
@@ -2,7 +2,7 @@ import { MutationTypes } from './constants'
import { State } from './state'
import { Slide, PPTElement } from '@/types/slides'
import { FONT_NAMES } from '@/configs/fontName'
-import { isSupportFontFamily } from '@/utils/index'
+import { isSupportFontFamily } from '@/utils/fontFamily'
interface AddSlidesData {
index?: number;
diff --git a/src/types/slides.ts b/src/types/slides.ts
index 1e903ff4..171dee25 100644
--- a/src/types/slides.ts
+++ b/src/types/slides.ts
@@ -1,3 +1,5 @@
+export type ElementType = 'text' | 'image' | 'shape' | 'line' | 'chart' | 'table'
+
export interface PPTElementBaseProps {
elId: string;
isLock: boolean;
@@ -51,8 +53,6 @@ export interface PPTShapeElement extends PPTElementBaseProps, PPTElementSizeProp
rotate?: number;
opacity?: number;
shadow?: string;
- text?: string;
- textAlign?: string;
}
export interface PPTLineElement extends PPTElementBaseProps {
@@ -73,7 +73,7 @@ export interface PPTChartElement extends PPTElementBaseProps, PPTElementSizeProp
data: Object;
}
-export interface TableCell {
+export interface TableElementCell {
colspan: number;
rowspan: number;
content: string;
@@ -85,7 +85,7 @@ export interface PPTTableElement extends PPTElementBaseProps, PPTElementSizeProp
theme: string;
rowSizes: number[];
colSizes: number[];
- data: TableCell[][];
+ data: TableElementCell[][];
}
export type PPTElement = PPTTextElement |
diff --git a/src/utils/clipboard.ts b/src/utils/clipboard.ts
new file mode 100644
index 00000000..ec8a6782
--- /dev/null
+++ b/src/utils/clipboard.ts
@@ -0,0 +1,35 @@
+import Clipboard from 'clipboard'
+
+// 复制文本到剪贴板
+export const copyText = (text: string) => {
+ return new Promise((resolve, reject) => {
+ const fakeElement = document.createElement('button')
+ const clipboard = new Clipboard(fakeElement, {
+ text: () => text,
+ action: () => 'copy',
+ container: document.body,
+ })
+ clipboard.on('success', e => {
+ clipboard.destroy()
+ resolve(e)
+ })
+ clipboard.on('error', e => {
+ clipboard.destroy()
+ reject(e)
+ })
+ document.body.appendChild(fakeElement)
+ fakeElement.click()
+ document.body.removeChild(fakeElement)
+ })
+}
+
+// 读取剪贴板
+export const readClipboard = () => {
+ if(navigator.clipboard) {
+ navigator.clipboard.readText().then(text => {
+ if(!text) return { err: '剪贴板为空或者不包含文本' }
+ return { text }
+ })
+ }
+ return { err: '浏览器不支持或禁止访问剪贴板' }
+}
\ No newline at end of file
diff --git a/src/utils/common.ts b/src/utils/common.ts
new file mode 100644
index 00000000..3f20f2ca
--- /dev/null
+++ b/src/utils/common.ts
@@ -0,0 +1,18 @@
+import padStart from 'lodash/padStart'
+
+// 生成随机码
+export const createRandomCode = (len = 6) => {
+ const charset = `_0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz`
+ const maxLen = charset.length
+ let ret = ''
+ for(let i = 0; i < len; i++) {
+ const randomIndex = Math.floor(Math.random() * maxLen)
+ ret += charset[randomIndex]
+ }
+ return ret
+}
+
+// 数字补足位数,例如将6补足3位 -> 003
+export const fillDigit = (digit: number, len: number) => {
+ return padStart('' + digit, len, '0')
+}
\ No newline at end of file
diff --git a/src/utils/crypto.ts b/src/utils/crypto.ts
new file mode 100644
index 00000000..45986ef5
--- /dev/null
+++ b/src/utils/crypto.ts
@@ -0,0 +1,14 @@
+import CryptoJS from 'crypto-js'
+
+const CRYPTO_KEY = 'zxc_ppt_online_editor'
+
+// 加密函数
+export const encrypt = (msg: string) => {
+ return CryptoJS.AES.encrypt(msg, CRYPTO_KEY).toString()
+}
+
+// 解密函数
+export const decrypt = (ciphertext: string) => {
+ const bytes = CryptoJS.AES.decrypt(ciphertext, CRYPTO_KEY)
+ return bytes.toString(CryptoJS.enc.Utf8)
+}
\ No newline at end of file
diff --git a/src/utils/fontFamily.ts b/src/utils/fontFamily.ts
new file mode 100644
index 00000000..6e2ae095
--- /dev/null
+++ b/src/utils/fontFamily.ts
@@ -0,0 +1,31 @@
+// 判断用户的操作系统是否安装了某字体
+export const isSupportFontFamily = (fontFamily: string) => {
+ if(typeof fontFamily !== 'string') return false
+ const arial = 'Arial'
+ if(fontFamily.toLowerCase() === arial.toLowerCase()) return true
+ const a = 'a'
+ const size = 100
+ const width = 100
+ const height = 100
+
+ const canvas = document.createElement('canvas')
+ const ctx = canvas.getContext('2d')
+
+ if(!ctx) return false
+
+ canvas.width = width
+ canvas.height = height
+ ctx.textAlign = 'center'
+ ctx.fillStyle = 'black'
+ ctx.textBaseline = 'middle'
+
+ const getDotArray = (_fontFamily: string) => {
+ ctx.clearRect(0, 0, width, height)
+ ctx.font = `${size}px ${_fontFamily}, ${arial}`
+ ctx.fillText(a, width / 2, height / 2)
+ const imageData = ctx.getImageData(0, 0, width, height).data
+ return [].slice.call(imageData).filter(item => item !== 0)
+ }
+
+ return getDotArray(arial).join('') !== getDotArray(fontFamily).join('')
+}
\ No newline at end of file
diff --git a/src/utils/fullscreen.ts b/src/utils/fullscreen.ts
new file mode 100644
index 00000000..c0c56690
--- /dev/null
+++ b/src/utils/fullscreen.ts
@@ -0,0 +1,8 @@
+// 进入全屏
+export const enterFullscreen = document.documentElement.requestFullscreen
+
+// 退出全屏
+export const exitFullscreen = document.exitFullscreen
+
+// 判断是否全屏
+export const isFullscreen = () => document.fullscreenEnabled
\ No newline at end of file
diff --git a/src/utils/image.ts b/src/utils/image.ts
new file mode 100644
index 00000000..c4c41bfe
--- /dev/null
+++ b/src/utils/image.ts
@@ -0,0 +1,42 @@
+interface ImageSize {
+ width: number;
+ height: number;
+}
+
+// 获取图片的原始宽高
+export const getImageSize = (imgUrl: string): Promise => {
+ return new Promise(resolve => {
+ const img = document.createElement('img')
+ img.src = imgUrl
+ img.style.opacity = '0'
+ document.body.appendChild(img)
+
+ img.onload = () => {
+ const imgWidth = img.clientWidth
+ const imgHeight = img.clientHeight
+
+ img.onload = null
+ img.onerror = null
+
+ document.body.removeChild(img)
+
+ resolve({ width: imgWidth, height: imgHeight })
+ }
+
+ img.onerror = () => {
+ img.onload = null
+ img.onerror = null
+ }
+ })
+}
+
+// 获取图片文件的dataURL
+export const getImageDataURL = (file: File): Promise => {
+ return new Promise(resolve => {
+ const reader = new FileReader()
+ reader.addEventListener('load', () => {
+ resolve(reader.result as string)
+ })
+ reader.readAsDataURL(file)
+ })
+}
\ No newline at end of file
diff --git a/src/utils/index.ts b/src/utils/index.ts
deleted file mode 100644
index b8cb8113..00000000
--- a/src/utils/index.ts
+++ /dev/null
@@ -1,209 +0,0 @@
-import padStart from 'lodash/padStart'
-import Clipboard from 'clipboard'
-import CryptoJS from 'crypto-js'
-
-const CRYPTO_KEY = 'zxc_ppt_online_editor'
-
-// 生成随机数
-export const createRandomNumber = (min: number, max: number) => {
- return Math.floor(min + Math.random() * (max - min))
-}
-
-// 生成随机码
-export const createRandomCode = (len = 6) => {
- const charset = `_0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz`
- const maxLen = charset.length
- let ret = ''
- for(let i = 0; i < len; i++) {
- const randomIndex = Math.floor(Math.random() * maxLen)
- ret += charset[randomIndex]
- }
- return ret
-}
-
-// 生成uuid
-export const createUUID = () => {
- const url = URL.createObjectURL(new Blob())
- const uuid = url.toString()
- URL.revokeObjectURL(url)
- return uuid.substr(uuid.lastIndexOf('/') + 1)
-}
-
-// 获取当前日期字符串
-export const getDateTime = (format = 'yyyy-MM-dd hh:mm:ss') => {
- const date = new Date()
-
- const formatMap = {
- 'y+': date.getFullYear(),
- 'M+': date.getMonth() + 1,
- 'd+': date.getDate(),
- 'h+': date.getHours(),
- 'm+': date.getMinutes(),
- 's+': date.getSeconds(),
- }
-
- for(const item of Object.keys(formatMap)) {
- if(new RegExp('(' + item + ')').test(format)) {
- const formated = (formatMap[item] + '').length < RegExp.$1.length ? padStart('' + formatMap[item], RegExp.$1.length, '0') : formatMap[item]
- format = format.replace(RegExp.$1, formated)
- }
- }
- return format
-}
-
-// 数字转中文,如1049 -> 一千零四十九
-export const digitalToChinese = (n: number) => {
- const str = n + ''
- const len = str.length - 1
- const idxs = ['', '十', '百', '千']
- const num = ['零', '一', '二', '三', '四', '五', '六', '七', '八', '九']
- return str.replace(/([1-9]|0+)/g, ($, $1, idx) => {
- const pos = len - idx
- if($1 !== 0) {
- if(idx === 0 && $1 === 1 && idxs[pos] === '十') return idxs[pos]
- return num[$1] + idxs[pos]
- }
- if(idx + $1.length >= str.length) return ''
- return '零'
- })
-}
-
-// 数字补足位数,例如将6补足3位 -> 003
-export const fillDigit = (digit: number, len: number) => {
- return padStart('' + digit, len, '0')
-}
-
-// 进入全屏
-export const enterFullscreen = () => {
- const docElm = document.documentElement
- docElm.requestFullscreen()
-}
-
-// 退出全屏
-export const exitFullscreen = document.exitFullscreen
-
-// 判断是否全屏
-export const isFullscreen = () => document.fullscreenEnabled
-
-// 判断用户的操作系统是否安装了某字体
-export const isSupportFontFamily = (fontFamily: string) => {
- if(typeof fontFamily !== 'string') return false
- const arial = 'Arial'
- if(fontFamily.toLowerCase() === arial.toLowerCase()) return true
- const a = 'a'
- const size = 100
- const width = 100
- const height = 100
-
- const canvas = document.createElement('canvas')
- const ctx = canvas.getContext('2d')
-
- if(!ctx) return false
-
- canvas.width = width
- canvas.height = height
- ctx.textAlign = 'center'
- ctx.fillStyle = 'black'
- ctx.textBaseline = 'middle'
-
- const getDotArray = (_fontFamily: string) => {
- ctx.clearRect(0, 0, width, height)
- ctx.font = `${size}px ${_fontFamily}, ${arial}`
- ctx.fillText(a, width / 2, height / 2)
- const imageData = ctx.getImageData(0, 0, width, height).data
- return [].slice.call(imageData).filter(item => item !== 0)
- }
-
- return getDotArray(arial).join('') !== getDotArray(fontFamily).join('')
-}
-
-// 获取图片的原始宽高
-export const getImageSize = (imgUrl: string) => {
- return new Promise((resolve, reject) => {
- const img = document.createElement('img')
- img.src = imgUrl
- img.style.opacity = '0'
- document.body.appendChild(img)
-
- img.onload = () => {
- const imgWidth = img.clientWidth
- const imgHeight = img.clientHeight
-
- img.onload = null
- img.onerror = null
-
- document.body.removeChild(img)
-
- resolve({ imgWidth, imgHeight })
- }
-
- img.onerror = () => {
- img.onload = null
- img.onerror = null
-
- reject('图片加载失败')
- }
- })
-}
-
-// 复制文本到剪贴板
-export const copyText = (text: string) => {
- return new Promise((resolve, reject) => {
- const fakeElement = document.createElement('button')
- const clipboard = new Clipboard(fakeElement, {
- text: () => text,
- action: () => 'copy',
- container: document.body,
- })
- clipboard.on('success', e => {
- clipboard.destroy()
- resolve(e)
- })
- clipboard.on('error', e => {
- clipboard.destroy()
- reject(e)
- })
- document.body.appendChild(fakeElement)
- fakeElement.click()
- document.body.removeChild(fakeElement)
- })
-}
-
-// 读取剪贴板
-export const readClipboard = () => {
- if(navigator.clipboard) {
- navigator.clipboard.readText().then(text => {
- if(!text) return { err: '剪贴板为空或者不包含文本' }
- return { text }
- })
- }
- return { err: '浏览器不支持或禁止访问剪贴板' }
-}
-
-// 加密函数
-export const encrypt = (msg: string) => {
- return CryptoJS.AES.encrypt(msg, CRYPTO_KEY).toString()
-}
-
-// 解密函数
-export const decrypt = (ciphertext: string) => {
- const bytes = CryptoJS.AES.decrypt(ciphertext, CRYPTO_KEY)
- return bytes.toString(CryptoJS.enc.Utf8)
-}
-
-// 获取DOM节点样式
-export const getStyle = (el: HTMLElement, style: string) => {
- if(!el) return null
- return window.getComputedStyle(el, null).getPropertyValue(style)
-}
-
-// 检查元素是否处在可视区域内
-export const checkElementInViewport = (el: HTMLElement) => {
- const rect = el.getBoundingClientRect()
- return (
- rect.top >= 0 &&
- rect.left >= 0 &&
- rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) &&
- rect.right <= (window.innerWidth || document.documentElement.clientWidth)
- )
-}
\ No newline at end of file
diff --git a/src/views/Editor/Canvas/index.vue b/src/views/Editor/Canvas/index.vue
index bdb6afde..4180be11 100644
--- a/src/views/Editor/Canvas/index.vue
+++ b/src/views/Editor/Canvas/index.vue
@@ -46,6 +46,7 @@ import { State } from '@/store/state'
import { MutationTypes } from '@/store/constants'
import { ContextmenuItem } from '@/components/Contextmenu/types'
import { VIEWPORT_SIZE, VIEWPORT_ASPECT_RATIO } from '@/configs/canvas'
+import { getImageDataURL } from '@/utils/image'
import useDropImage from '@/hooks/useDropImage'
@@ -67,7 +68,11 @@ export default defineComponent({
const dropImageFile = useDropImage(viewportRef)
watch(dropImageFile, () => {
- console.log(dropImageFile.value)
+ if(dropImageFile.value) {
+ getImageDataURL(dropImageFile.value).then(dataURL => {
+ console.log(dataURL)
+ })
+ }
})
const viewportStyles = reactive({
diff --git a/src/views/Editor/Canvas/utils/createElement.ts b/src/views/Editor/Canvas/utils/createElement.ts
new file mode 100644
index 00000000..9f901abf
--- /dev/null
+++ b/src/views/Editor/Canvas/utils/createElement.ts
@@ -0,0 +1,120 @@
+import { createRandomCode } from '@/utils/common'
+import { getImageSize } from '@/utils/image'
+import { VIEWPORT_SIZE, VIEWPORT_ASPECT_RATIO } from '@/configs/canvas'
+import { TableElementCell } from '@/types/slides'
+import {
+ DEFAULT_IMAGE,
+ DEFAULT_TEXT,
+ DEFAULT_SHAPE,
+ DEFAULT_LINE,
+ DEFAULT_CHART,
+ DEFAULT_TABLE,
+} from '@/configs/defaultElement'
+
+interface CommonElementPosition {
+ top: number;
+ left: number;
+ width: number;
+ height: number;
+}
+
+interface LineElementPosition {
+ top: number;
+ left: number;
+ start: [number, number];
+ end: [number, number];
+}
+
+export const insertImage = (imgUrl: string) => {
+ getImageSize(imgUrl).then(({ width, height }) => {
+ const scale = width / height
+
+ if(scale < VIEWPORT_ASPECT_RATIO && width > VIEWPORT_SIZE) {
+ width = VIEWPORT_SIZE
+ height = width * scale
+ }
+ else if(height > VIEWPORT_SIZE * VIEWPORT_ASPECT_RATIO) {
+ height = VIEWPORT_SIZE * VIEWPORT_ASPECT_RATIO
+ width = height / scale
+ }
+
+ return {
+ ...DEFAULT_IMAGE,
+ elId: createRandomCode(),
+ imgUrl,
+ width,
+ height,
+ }
+ })
+}
+
+export const insertChart = (chartType: string, data: Object) => {
+ return {
+ ...DEFAULT_CHART,
+ elId: createRandomCode(),
+ chartType,
+ data,
+ }
+}
+
+export const insertTable = (rowCount: number, colCount: number) => {
+ const row: TableElementCell[] = new Array(colCount).fill({ colspan: 1, rowspan: 1, content: '' })
+ const data: TableElementCell[][] = new Array(rowCount).fill(row)
+
+ const DEFAULT_CELL_WIDTH = 80
+ const DEFAULT_CELL_HEIGHT = 35
+ const DEFAULT_BORDER_WIDTH = 2
+
+ const colSizes: number[] = new Array(colCount).fill(DEFAULT_CELL_WIDTH)
+ const rowSizes: number[] = new Array(rowCount).fill(DEFAULT_CELL_HEIGHT)
+
+ return {
+ ...DEFAULT_TABLE,
+ elId: createRandomCode(),
+ width: colCount * DEFAULT_CELL_WIDTH + DEFAULT_BORDER_WIDTH,
+ height: rowCount * DEFAULT_CELL_HEIGHT + DEFAULT_BORDER_WIDTH,
+ colSizes,
+ rowSizes,
+ data,
+ }
+}
+
+export const insertText = (position: CommonElementPosition) => {
+ const { left, top, width, height } = position
+ return {
+ ...DEFAULT_TEXT,
+ elId: createRandomCode(),
+ left,
+ top,
+ width,
+ height,
+ }
+}
+
+export const insertShape = (position: CommonElementPosition, svgCode: string) => {
+ const { left, top, width, height } = position
+ return {
+ ...DEFAULT_SHAPE,
+ elId: createRandomCode(),
+ left,
+ top,
+ width,
+ height,
+ svgCode,
+ }
+}
+
+export const insertLine = (position: LineElementPosition, marker: [string, string], lineType: string) => {
+ const { left, top, start, end } = position
+
+ return {
+ ...DEFAULT_LINE,
+ elId: createRandomCode(),
+ left,
+ top,
+ start,
+ end,
+ marker,
+ lineType,
+ }
+}
\ No newline at end of file
diff --git a/src/views/Editor/Canvas/utils/elementCombine.ts b/src/views/Editor/Canvas/utils/elementCombine.ts
index 38fbcbc4..c2fa5fd1 100644
--- a/src/views/Editor/Canvas/utils/elementCombine.ts
+++ b/src/views/Editor/Canvas/utils/elementCombine.ts
@@ -1,4 +1,4 @@
-import { createRandomCode } from '@/utils/index'
+import { createRandomCode } from '@/utils/common'
import { PPTElement } from '@/types/slides'
// 组合元素(为当前所有激活元素添加一个相同的groupId)
diff --git a/src/views/Editor/index.vue b/src/views/Editor/index.vue
index ed0d6bb6..f966f4a4 100644
--- a/src/views/Editor/index.vue
+++ b/src/views/Editor/index.vue
@@ -17,7 +17,8 @@ import { computed, defineComponent, onMounted, onUnmounted, ref } from 'vue'
import { useStore } from 'vuex'
import { State } from '@/store/state'
import { KEYCODE } from '@/configs/keyCode'
-import { decrypt } from '@/utils/index'
+import { decrypt } from '@/utils/crypto'
+import { getImageDataURL } from '@/utils/image'
import { message } from 'ant-design-vue'
@@ -151,7 +152,9 @@ export default defineComponent({
}
const pasteImageFile = (imageFile: File) => {
- console.log(imageFile)
+ getImageDataURL(imageFile).then(dataURL => {
+ console.log(dataURL)
+ })
}
const pasteText = (text: string) => {