mirror of
https://github.com/pipipi-pikachu/PPTist.git
synced 2025-04-15 02:20:00 +08:00
refactor: script setup 语法重构
This commit is contained in:
parent
1c4ad8eebd
commit
16acbc6333
@ -6,6 +6,7 @@ module.exports = {
|
|||||||
root: true,
|
root: true,
|
||||||
env: {
|
env: {
|
||||||
node: true,
|
node: true,
|
||||||
|
'vue/setup-compiler-macros': true,
|
||||||
},
|
},
|
||||||
extends: [
|
extends: [
|
||||||
'plugin:vue/vue3-essential',
|
'plugin:vue/vue3-essential',
|
||||||
@ -60,6 +61,7 @@ module.exports = {
|
|||||||
'no-useless-return': 'error',
|
'no-useless-return': 'error',
|
||||||
'array-bracket-spacing': 'error',
|
'array-bracket-spacing': 'error',
|
||||||
'no-useless-escape': 'off',
|
'no-useless-escape': 'off',
|
||||||
|
'no-unused-vars': 'off',
|
||||||
'no-eval': 'error',
|
'no-eval': 'error',
|
||||||
'no-var': 'error',
|
'no-var': 'error',
|
||||||
'no-with': 'error',
|
'no-with': 'error',
|
||||||
@ -73,6 +75,10 @@ module.exports = {
|
|||||||
'{}': false,
|
'{}': false,
|
||||||
},
|
},
|
||||||
}],
|
}],
|
||||||
|
'@typescript-eslint/no-unused-vars': 'off',
|
||||||
|
'@typescript-eslint/no-non-null-assertion': 'off',
|
||||||
|
'vue/multi-word-component-names': 'off',
|
||||||
|
'vue/no-reserved-component-names': 'off',
|
||||||
},
|
},
|
||||||
overrides: [
|
overrides: [
|
||||||
{
|
{
|
||||||
|
117
package-lock.json
generated
117
package-lock.json
generated
@ -7007,37 +7007,112 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"eslint-plugin-vue": {
|
"eslint-plugin-vue": {
|
||||||
"version": "7.20.0",
|
"version": "9.1.0",
|
||||||
"resolved": "https://registry.npmmirror.com/eslint-plugin-vue/download/eslint-plugin-vue-7.20.0.tgz?cache=0&sync_timestamp=1635570504787&other_urls=https%3A%2F%2Fregistry.npmmirror.com%2Feslint-plugin-vue%2Fdownload%2Feslint-plugin-vue-7.20.0.tgz",
|
"resolved": "https://registry.npmmirror.com/eslint-plugin-vue/-/eslint-plugin-vue-9.1.0.tgz",
|
||||||
"integrity": "sha1-mMIYhaa/3wcTw6kpV6Wv6q7tklM=",
|
"integrity": "sha512-EPCeInPicQ/YyfOWJDr1yfEeSNoFCMzUus107lZyYi37xejdOolNzS5MXGXp8+9bkoKZMdv/1AcZzQebME6r+g==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"eslint-utils": "^2.1.0",
|
"eslint-utils": "^3.0.0",
|
||||||
"natural-compare": "^1.4.0",
|
"natural-compare": "^1.4.0",
|
||||||
"semver": "^6.3.0",
|
"nth-check": "^2.0.1",
|
||||||
"vue-eslint-parser": "^7.10.0"
|
"postcss-selector-parser": "^6.0.9",
|
||||||
|
"semver": "^7.3.5",
|
||||||
|
"vue-eslint-parser": "^9.0.1",
|
||||||
|
"xml-name-validator": "^4.0.0"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"eslint-utils": {
|
"acorn": {
|
||||||
"version": "2.1.0",
|
"version": "8.7.1",
|
||||||
"resolved": "https://registry.nlark.com/eslint-utils/download/eslint-utils-2.1.0.tgz",
|
"resolved": "https://registry.npmmirror.com/acorn/-/acorn-8.7.1.tgz",
|
||||||
"integrity": "sha1-0t5eA0JOcH3BDHQGjd7a5wh0Gyc=",
|
"integrity": "sha512-Xx54uLJQZ19lKygFXOWsscKUbsBZW0CPykPhVQdhIeIwrbPmJzqeASDInc8nKBnp/JT6igTs82qPXz069H8I/A==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"debug": {
|
||||||
|
"version": "4.3.4",
|
||||||
|
"resolved": "https://registry.npmmirror.com/debug/-/debug-4.3.4.tgz",
|
||||||
|
"integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"eslint-visitor-keys": "^1.1.0"
|
"ms": "2.1.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"eslint-scope": {
|
||||||
|
"version": "7.1.1",
|
||||||
|
"resolved": "https://registry.npmmirror.com/eslint-scope/-/eslint-scope-7.1.1.tgz",
|
||||||
|
"integrity": "sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"esrecurse": "^4.3.0",
|
||||||
|
"estraverse": "^5.2.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"eslint-visitor-keys": {
|
"eslint-visitor-keys": {
|
||||||
"version": "1.3.0",
|
"version": "3.3.0",
|
||||||
"resolved": "https://registry.npmmirror.com/eslint-visitor-keys/download/eslint-visitor-keys-1.3.0.tgz?cache=0&sync_timestamp=1636378420914&other_urls=https%3A%2F%2Fregistry.npmmirror.com%2Feslint-visitor-keys%2Fdownload%2Feslint-visitor-keys-1.3.0.tgz",
|
"resolved": "https://registry.npmmirror.com/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz",
|
||||||
"integrity": "sha1-MOvR73wv3/AcOk8VEESvJfqwUj4=",
|
"integrity": "sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"semver": {
|
"espree": {
|
||||||
"version": "6.3.0",
|
"version": "9.3.2",
|
||||||
"resolved": "https://registry.npm.taobao.org/semver/download/semver-6.3.0.tgz?cache=0&sync_timestamp=1616463550093&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fsemver%2Fdownload%2Fsemver-6.3.0.tgz",
|
"resolved": "https://registry.npmmirror.com/espree/-/espree-9.3.2.tgz",
|
||||||
"integrity": "sha1-7gpkyK9ejO6mdoexM3YeG+y9HT0=",
|
"integrity": "sha512-D211tC7ZwouTIuY5x9XnS0E9sWNChB7IYKX/Xp5eQj3nFXhqmiUDB9q27y76oFl8jTg3pXcQx/bpxMfs3CIZbA==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"acorn": "^8.7.1",
|
||||||
|
"acorn-jsx": "^5.3.2",
|
||||||
|
"eslint-visitor-keys": "^3.3.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"estraverse": {
|
||||||
|
"version": "5.3.0",
|
||||||
|
"resolved": "https://registry.npmmirror.com/estraverse/-/estraverse-5.3.0.tgz",
|
||||||
|
"integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==",
|
||||||
"dev": true
|
"dev": true
|
||||||
|
},
|
||||||
|
"nth-check": {
|
||||||
|
"version": "2.1.1",
|
||||||
|
"resolved": "https://registry.npmmirror.com/nth-check/-/nth-check-2.1.1.tgz",
|
||||||
|
"integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"boolbase": "^1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"postcss-selector-parser": {
|
||||||
|
"version": "6.0.10",
|
||||||
|
"resolved": "https://registry.npmmirror.com/postcss-selector-parser/-/postcss-selector-parser-6.0.10.tgz",
|
||||||
|
"integrity": "sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"cssesc": "^3.0.0",
|
||||||
|
"util-deprecate": "^1.0.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"vue-eslint-parser": {
|
||||||
|
"version": "9.0.2",
|
||||||
|
"resolved": "https://registry.npmmirror.com/vue-eslint-parser/-/vue-eslint-parser-9.0.2.tgz",
|
||||||
|
"integrity": "sha512-uCPQwTGjOtAYrwnU+76pYxalhjsh7iFBsHwBqDHiOPTxtICDaraO4Szw54WFTNZTAEsgHHzqFOu1mmnBOBRzDA==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"debug": "^4.3.4",
|
||||||
|
"eslint-scope": "^7.1.1",
|
||||||
|
"eslint-visitor-keys": "^3.3.0",
|
||||||
|
"espree": "^9.3.1",
|
||||||
|
"esquery": "^1.4.0",
|
||||||
|
"lodash": "^4.17.21",
|
||||||
|
"semver": "^7.3.6"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"semver": {
|
||||||
|
"version": "7.3.7",
|
||||||
|
"resolved": "https://registry.npmmirror.com/semver/-/semver-7.3.7.tgz",
|
||||||
|
"integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"lru-cache": "^6.0.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -17370,6 +17445,12 @@
|
|||||||
"async-limiter": "~1.0.0"
|
"async-limiter": "~1.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"xml-name-validator": {
|
||||||
|
"version": "4.0.0",
|
||||||
|
"resolved": "https://registry.npmmirror.com/xml-name-validator/-/xml-name-validator-4.0.0.tgz",
|
||||||
|
"integrity": "sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"xtend": {
|
"xtend": {
|
||||||
"version": "4.0.2",
|
"version": "4.0.2",
|
||||||
"resolved": "https://registry.nlark.com/xtend/download/xtend-4.0.2.tgz",
|
"resolved": "https://registry.nlark.com/xtend/download/xtend-4.0.2.tgz",
|
||||||
|
@ -73,7 +73,7 @@
|
|||||||
"@vue/test-utils": "^2.0.0-0",
|
"@vue/test-utils": "^2.0.0-0",
|
||||||
"babel-plugin-import": "^1.13.3",
|
"babel-plugin-import": "^1.13.3",
|
||||||
"eslint": "^6.7.2",
|
"eslint": "^6.7.2",
|
||||||
"eslint-plugin-vue": "^7.1.0",
|
"eslint-plugin-vue": "^9.1.0",
|
||||||
"husky": "^7.0.2",
|
"husky": "^7.0.2",
|
||||||
"less": "^4.1.1",
|
"less": "^4.1.1",
|
||||||
"less-loader": "^7.1.0",
|
"less-loader": "^7.1.0",
|
||||||
|
43
src/App.vue
43
src/App.vue
@ -1,11 +1,11 @@
|
|||||||
<template>
|
<template>
|
||||||
<Screen v-if="screening" />
|
<Screen v-if="screening" />
|
||||||
<Editor v-else-if="isPC" />
|
<Editor v-else-if="_isPC" />
|
||||||
<Mobile v-else />
|
<Mobile v-else />
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts" setup>
|
||||||
import { defineComponent, onMounted } from 'vue'
|
import { onMounted } from 'vue'
|
||||||
import { storeToRefs } from 'pinia'
|
import { storeToRefs } from 'pinia'
|
||||||
import { useScreenStore, useMainStore, useSnapshotStore } from '@/store'
|
import { useScreenStore, useMainStore, useSnapshotStore } from '@/store'
|
||||||
import { LOCALSTORAGE_KEY_DISCARDED_DB } from '@/configs/storage'
|
import { LOCALSTORAGE_KEY_DISCARDED_DB } from '@/configs/storage'
|
||||||
@ -15,30 +15,24 @@ import Editor from './views/Editor/index.vue'
|
|||||||
import Screen from './views/Screen/index.vue'
|
import Screen from './views/Screen/index.vue'
|
||||||
import Mobile from './views/Mobile/index.vue'
|
import Mobile from './views/Mobile/index.vue'
|
||||||
|
|
||||||
export default defineComponent({
|
const _isPC = isPC()
|
||||||
name: 'app',
|
|
||||||
components: {
|
|
||||||
Editor,
|
|
||||||
Screen,
|
|
||||||
Mobile,
|
|
||||||
},
|
|
||||||
setup() {
|
|
||||||
const mainStore = useMainStore()
|
|
||||||
const snapshotStore = useSnapshotStore()
|
|
||||||
const { databaseId } = storeToRefs(mainStore)
|
|
||||||
const { screening } = storeToRefs(useScreenStore())
|
|
||||||
|
|
||||||
if (process.env.NODE_ENV === 'production') {
|
const mainStore = useMainStore()
|
||||||
|
const snapshotStore = useSnapshotStore()
|
||||||
|
const { databaseId } = storeToRefs(mainStore)
|
||||||
|
const { screening } = storeToRefs(useScreenStore())
|
||||||
|
|
||||||
|
if (process.env.NODE_ENV === 'production') {
|
||||||
window.onbeforeunload = () => false
|
window.onbeforeunload = () => false
|
||||||
}
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
snapshotStore.initSnapshotDatabase()
|
snapshotStore.initSnapshotDatabase()
|
||||||
mainStore.setAvailableFonts()
|
mainStore.setAvailableFonts()
|
||||||
})
|
})
|
||||||
|
|
||||||
// 应用注销时向 localStorage 中记录下本次 indexedDB 的数据库ID,用于之后清除数据库
|
// 应用注销时向 localStorage 中记录下本次 indexedDB 的数据库ID,用于之后清除数据库
|
||||||
window.addEventListener('unload', () => {
|
window.addEventListener('unload', () => {
|
||||||
const discardedDB = localStorage.getItem(LOCALSTORAGE_KEY_DISCARDED_DB)
|
const discardedDB = localStorage.getItem(LOCALSTORAGE_KEY_DISCARDED_DB)
|
||||||
const discardedDBList: string[] = discardedDB ? JSON.parse(discardedDB) : []
|
const discardedDBList: string[] = discardedDB ? JSON.parse(discardedDB) : []
|
||||||
|
|
||||||
@ -46,13 +40,6 @@ export default defineComponent({
|
|||||||
|
|
||||||
const newDiscardedDB = JSON.stringify(discardedDBList)
|
const newDiscardedDB = JSON.stringify(discardedDBList)
|
||||||
localStorage.setItem(LOCALSTORAGE_KEY_DISCARDED_DB, newDiscardedDB)
|
localStorage.setItem(LOCALSTORAGE_KEY_DISCARDED_DB, newDiscardedDB)
|
||||||
})
|
|
||||||
|
|
||||||
return {
|
|
||||||
screening,
|
|
||||||
isPC: isPC(),
|
|
||||||
}
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@ -4,17 +4,12 @@
|
|||||||
</button>
|
</button>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts" setup>
|
||||||
import { defineComponent } from 'vue'
|
defineProps({
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
name: 'checkbox-button',
|
|
||||||
props: {
|
|
||||||
checked: {
|
checked: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false,
|
default: false,
|
||||||
},
|
},
|
||||||
},
|
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@ -4,12 +4,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts" setup>
|
||||||
import { defineComponent } from 'vue'
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
name: 'checkbox-button-group',
|
|
||||||
})
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
@ -16,34 +16,32 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts" setup>
|
||||||
import { computed, defineComponent, onUnmounted, PropType, ref } from 'vue'
|
import { computed, onUnmounted, PropType, ref } from 'vue'
|
||||||
|
|
||||||
import Checkboard from './Checkboard.vue'
|
import Checkboard from './Checkboard.vue'
|
||||||
import { ColorFormats } from 'tinycolor2'
|
import { ColorFormats } from 'tinycolor2'
|
||||||
|
|
||||||
export default defineComponent({
|
const props = defineProps({
|
||||||
name: 'alpha',
|
|
||||||
components: {
|
|
||||||
Checkboard,
|
|
||||||
},
|
|
||||||
emits: ['colorChange'],
|
|
||||||
props: {
|
|
||||||
value: {
|
value: {
|
||||||
type: Object as PropType<ColorFormats.RGBA>,
|
type: Object as PropType<ColorFormats.RGBA>,
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
},
|
})
|
||||||
setup(props, { emit }) {
|
|
||||||
const color = computed(() => props.value)
|
|
||||||
|
|
||||||
const gradientColor = computed(() => {
|
const emit = defineEmits<{
|
||||||
|
(event: 'colorChange', payload: ColorFormats.RGBA): void
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const color = computed(() => props.value)
|
||||||
|
|
||||||
|
const gradientColor = computed(() => {
|
||||||
const rgbaStr = [color.value.r, color.value.g, color.value.b].join(',')
|
const rgbaStr = [color.value.r, color.value.g, color.value.b].join(',')
|
||||||
return `linear-gradient(to right, rgba(${rgbaStr}, 0) 0%, rgba(${rgbaStr}, 1) 100%)`
|
return `linear-gradient(to right, rgba(${rgbaStr}, 0) 0%, rgba(${rgbaStr}, 1) 100%)`
|
||||||
})
|
})
|
||||||
|
|
||||||
const alphaRef = ref<HTMLElement>()
|
const alphaRef = ref<HTMLElement>()
|
||||||
const handleChange = (e: MouseEvent) => {
|
const handleChange = (e: MouseEvent) => {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
if (!alphaRef.value) return
|
if (!alphaRef.value) return
|
||||||
const containerWidth = alphaRef.value.clientWidth
|
const containerWidth = alphaRef.value.clientWidth
|
||||||
@ -63,28 +61,18 @@ export default defineComponent({
|
|||||||
a: a,
|
a: a,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const unbindEventListeners = () => {
|
const unbindEventListeners = () => {
|
||||||
window.removeEventListener('mousemove', handleChange)
|
window.removeEventListener('mousemove', handleChange)
|
||||||
window.removeEventListener('mouseup', unbindEventListeners)
|
window.removeEventListener('mouseup', unbindEventListeners)
|
||||||
}
|
}
|
||||||
const handleMouseDown = (e: MouseEvent) => {
|
const handleMouseDown = (e: MouseEvent) => {
|
||||||
handleChange(e)
|
handleChange(e)
|
||||||
window.addEventListener('mousemove', handleChange)
|
window.addEventListener('mousemove', handleChange)
|
||||||
window.addEventListener('mouseup', unbindEventListeners)
|
window.addEventListener('mouseup', unbindEventListeners)
|
||||||
}
|
}
|
||||||
|
onUnmounted(unbindEventListeners)
|
||||||
onUnmounted(unbindEventListeners)
|
|
||||||
|
|
||||||
return {
|
|
||||||
alphaRef,
|
|
||||||
gradientColor,
|
|
||||||
handleMouseDown,
|
|
||||||
color,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
})
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
@ -2,8 +2,23 @@
|
|||||||
<div class="checkerboard" :style="bgStyle"></div>
|
<div class="checkerboard" :style="bgStyle"></div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts" setup>
|
||||||
import { computed, defineComponent } from 'vue'
|
import { computed } from 'vue'
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
size: {
|
||||||
|
type: Number,
|
||||||
|
default: 8,
|
||||||
|
},
|
||||||
|
white: {
|
||||||
|
type: String,
|
||||||
|
default: '#fff',
|
||||||
|
},
|
||||||
|
grey: {
|
||||||
|
type: String,
|
||||||
|
default: '#e6e6e6',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
const checkboardCache = {}
|
const checkboardCache = {}
|
||||||
|
|
||||||
@ -32,33 +47,9 @@ const getCheckboard = (white: string, grey: string, size: number) => {
|
|||||||
return checkboard
|
return checkboard
|
||||||
}
|
}
|
||||||
|
|
||||||
export default defineComponent({
|
const bgStyle = computed(() => {
|
||||||
name: 'checkboard',
|
|
||||||
emits: ['colorChange'],
|
|
||||||
props: {
|
|
||||||
size: {
|
|
||||||
type: Number,
|
|
||||||
default: 8,
|
|
||||||
},
|
|
||||||
white: {
|
|
||||||
type: String,
|
|
||||||
default: '#fff',
|
|
||||||
},
|
|
||||||
grey: {
|
|
||||||
type: String,
|
|
||||||
default: '#e6e6e6',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
setup(props) {
|
|
||||||
const bgStyle = computed(() => {
|
|
||||||
const checkboard = getCheckboard(props.white, props.grey, props.size)
|
const checkboard = getCheckboard(props.white, props.grey, props.size)
|
||||||
return { backgroundImage: `url(${checkboard})` }
|
return { backgroundImage: `url(${checkboard})` }
|
||||||
})
|
|
||||||
|
|
||||||
return {
|
|
||||||
bgStyle,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@ -8,38 +8,32 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts" setup>
|
||||||
import { computed, defineComponent, PropType } from 'vue'
|
import { computed, PropType } from 'vue'
|
||||||
import tinycolor, { ColorFormats } from 'tinycolor2'
|
import tinycolor, { ColorFormats } from 'tinycolor2'
|
||||||
|
|
||||||
export default defineComponent({
|
const props = defineProps({
|
||||||
name: 'editable-input',
|
|
||||||
emits: ['colorChange'],
|
|
||||||
props: {
|
|
||||||
value: {
|
value: {
|
||||||
type: Object as PropType<ColorFormats.RGBA>,
|
type: Object as PropType<ColorFormats.RGBA>,
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
},
|
})
|
||||||
setup(props, { emit }) {
|
|
||||||
const val = computed(() => {
|
const emit = defineEmits<{
|
||||||
|
(event: 'colorChange', payload: ColorFormats.RGBA): void
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const val = computed(() => {
|
||||||
let _hex = ''
|
let _hex = ''
|
||||||
if (props.value.a < 1) _hex = tinycolor(props.value).toHex8String().toUpperCase()
|
if (props.value.a < 1) _hex = tinycolor(props.value).toHex8String().toUpperCase()
|
||||||
else _hex = tinycolor(props.value).toHexString().toUpperCase()
|
else _hex = tinycolor(props.value).toHexString().toUpperCase()
|
||||||
return _hex.replace('#', '')
|
return _hex.replace('#', '')
|
||||||
})
|
})
|
||||||
|
|
||||||
const handleInput = (e: Event) => {
|
const handleInput = (e: Event) => {
|
||||||
const value = (e.target as HTMLInputElement).value
|
const value = (e.target as HTMLInputElement).value
|
||||||
if (value.length >= 6) emit('colorChange', tinycolor(value).toRgb())
|
if (value.length >= 6) emit('colorChange', tinycolor(value).toRgb())
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
|
||||||
val,
|
|
||||||
handleInput,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
})
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
@ -15,14 +15,11 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts" setup>
|
||||||
import { computed, defineComponent, onUnmounted, PropType, ref, watch } from 'vue'
|
import { computed, onUnmounted, PropType, ref, watch } from 'vue'
|
||||||
import tinycolor, { ColorFormats } from 'tinycolor2'
|
import tinycolor, { ColorFormats } from 'tinycolor2'
|
||||||
|
|
||||||
export default defineComponent({
|
const props = defineProps({
|
||||||
name: 'hue',
|
|
||||||
emits: ['colorChange'],
|
|
||||||
props: {
|
|
||||||
value: {
|
value: {
|
||||||
type: Object as PropType<ColorFormats.RGBA>,
|
type: Object as PropType<ColorFormats.RGBA>,
|
||||||
required: true,
|
required: true,
|
||||||
@ -31,32 +28,36 @@ export default defineComponent({
|
|||||||
type: Number,
|
type: Number,
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
},
|
})
|
||||||
setup(props, { emit }) {
|
|
||||||
const oldHue = ref(0)
|
|
||||||
const pullDirection = ref('')
|
|
||||||
|
|
||||||
const color = computed(() => {
|
const emit = defineEmits<{
|
||||||
|
(event: 'colorChange', payload: ColorFormats.HSLA): void
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const oldHue = ref(0)
|
||||||
|
const pullDirection = ref('')
|
||||||
|
|
||||||
|
const color = computed(() => {
|
||||||
const hsla = tinycolor(props.value).toHsl()
|
const hsla = tinycolor(props.value).toHsl()
|
||||||
if (props.hue !== -1) hsla.h = props.hue
|
if (props.hue !== -1) hsla.h = props.hue
|
||||||
return hsla
|
return hsla
|
||||||
})
|
})
|
||||||
|
|
||||||
const pointerLeft = computed(() => {
|
const pointerLeft = computed(() => {
|
||||||
if (color.value.h === 0 && pullDirection.value === 'right') return '100%'
|
if (color.value.h === 0 && pullDirection.value === 'right') return '100%'
|
||||||
return color.value.h * 100 / 360 + '%'
|
return color.value.h * 100 / 360 + '%'
|
||||||
})
|
})
|
||||||
|
|
||||||
watch(() => props.value, () => {
|
watch(() => props.value, () => {
|
||||||
const hsla = tinycolor(props.value).toHsl()
|
const hsla = tinycolor(props.value).toHsl()
|
||||||
const h = hsla.s === 0 ? props.hue : hsla.h
|
const h = hsla.s === 0 ? props.hue : hsla.h
|
||||||
if (h !== 0 && h - oldHue.value > 0) pullDirection.value = 'right'
|
if (h !== 0 && h - oldHue.value > 0) pullDirection.value = 'right'
|
||||||
if (h !== 0 && h - oldHue.value < 0) pullDirection.value = 'left'
|
if (h !== 0 && h - oldHue.value < 0) pullDirection.value = 'left'
|
||||||
oldHue.value = h
|
oldHue.value = h
|
||||||
})
|
})
|
||||||
|
|
||||||
const hueRef = ref<HTMLElement>()
|
const hueRef = ref<HTMLElement>()
|
||||||
const handleChange = (e: MouseEvent) => {
|
const handleChange = (e: MouseEvent) => {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
if (!hueRef.value) return
|
if (!hueRef.value) return
|
||||||
|
|
||||||
@ -79,27 +80,18 @@ export default defineComponent({
|
|||||||
a: color.value.a,
|
a: color.value.a,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const unbindEventListeners = () => {
|
const unbindEventListeners = () => {
|
||||||
window.removeEventListener('mousemove', handleChange)
|
window.removeEventListener('mousemove', handleChange)
|
||||||
window.removeEventListener('mouseup', unbindEventListeners)
|
window.removeEventListener('mouseup', unbindEventListeners)
|
||||||
}
|
}
|
||||||
const handleMouseDown = (e: MouseEvent) => {
|
const handleMouseDown = (e: MouseEvent) => {
|
||||||
handleChange(e)
|
handleChange(e)
|
||||||
window.addEventListener('mousemove', handleChange)
|
window.addEventListener('mousemove', handleChange)
|
||||||
window.addEventListener('mouseup', unbindEventListeners)
|
window.addEventListener('mouseup', unbindEventListeners)
|
||||||
}
|
}
|
||||||
|
onUnmounted(unbindEventListeners)
|
||||||
onUnmounted(unbindEventListeners)
|
|
||||||
|
|
||||||
return {
|
|
||||||
hueRef,
|
|
||||||
handleMouseDown,
|
|
||||||
pointerLeft,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
})
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
@ -18,15 +18,12 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts" setup>
|
||||||
import { computed, defineComponent, onUnmounted, PropType, ref } from 'vue'
|
import { computed, onUnmounted, PropType, ref } from 'vue'
|
||||||
import tinycolor, { ColorFormats } from 'tinycolor2'
|
import tinycolor, { ColorFormats } from 'tinycolor2'
|
||||||
import { throttle, clamp } from 'lodash'
|
import { throttle, clamp } from 'lodash'
|
||||||
|
|
||||||
export default defineComponent({
|
const props = defineProps({
|
||||||
name: 'saturation',
|
|
||||||
emits: ['colorChange'],
|
|
||||||
props: {
|
|
||||||
value: {
|
value: {
|
||||||
type: Object as PropType<ColorFormats.RGBA>,
|
type: Object as PropType<ColorFormats.RGBA>,
|
||||||
required: true,
|
required: true,
|
||||||
@ -35,24 +32,28 @@ export default defineComponent({
|
|||||||
type: Number,
|
type: Number,
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
},
|
})
|
||||||
setup(props, { emit }) {
|
|
||||||
const color = computed(() => {
|
const emit = defineEmits<{
|
||||||
|
(event: 'colorChange', payload: ColorFormats.HSVA): void
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const color = computed(() => {
|
||||||
const hsva = tinycolor(props.value).toHsv()
|
const hsva = tinycolor(props.value).toHsv()
|
||||||
if (props.hue !== -1) hsva.h = props.hue
|
if (props.hue !== -1) hsva.h = props.hue
|
||||||
return hsva
|
return hsva
|
||||||
})
|
})
|
||||||
|
|
||||||
const bgColor = computed(() => `hsl(${color.value.h}, 100%, 50%)`)
|
const bgColor = computed(() => `hsl(${color.value.h}, 100%, 50%)`)
|
||||||
const pointerTop = computed(() => (-(color.value.v * 100) + 1) + 100 + '%')
|
const pointerTop = computed(() => (-(color.value.v * 100) + 1) + 100 + '%')
|
||||||
const pointerLeft = computed(() => color.value.s * 100 + '%')
|
const pointerLeft = computed(() => color.value.s * 100 + '%')
|
||||||
|
|
||||||
const emitChangeEvent = throttle(function(param) {
|
const emitChangeEvent = throttle(function(param: ColorFormats.HSVA) {
|
||||||
emit('colorChange', param)
|
emit('colorChange', param)
|
||||||
}, 20, { leading: true, trailing: false })
|
}, 20, { leading: true, trailing: false })
|
||||||
|
|
||||||
const saturationRef = ref<HTMLElement>()
|
const saturationRef = ref<HTMLElement>()
|
||||||
const handleChange = (e: MouseEvent) => {
|
const handleChange = (e: MouseEvent) => {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
if (!saturationRef.value) return
|
if (!saturationRef.value) return
|
||||||
|
|
||||||
@ -71,30 +72,19 @@ export default defineComponent({
|
|||||||
v: bright,
|
v: bright,
|
||||||
a: color.value.a,
|
a: color.value.a,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
const unbindEventListeners = () => {
|
const unbindEventListeners = () => {
|
||||||
window.removeEventListener('mousemove', handleChange)
|
window.removeEventListener('mousemove', handleChange)
|
||||||
window.removeEventListener('mouseup', unbindEventListeners)
|
window.removeEventListener('mouseup', unbindEventListeners)
|
||||||
}
|
}
|
||||||
const handleMouseDown = (e: MouseEvent) => {
|
const handleMouseDown = (e: MouseEvent) => {
|
||||||
handleChange(e)
|
handleChange(e)
|
||||||
window.addEventListener('mousemove', handleChange)
|
window.addEventListener('mousemove', handleChange)
|
||||||
window.addEventListener('mouseup', unbindEventListeners)
|
window.addEventListener('mouseup', unbindEventListeners)
|
||||||
}
|
}
|
||||||
|
onUnmounted(unbindEventListeners)
|
||||||
onUnmounted(unbindEventListeners)
|
|
||||||
|
|
||||||
return {
|
|
||||||
saturationRef,
|
|
||||||
bgColor,
|
|
||||||
handleMouseDown,
|
|
||||||
pointerTop,
|
|
||||||
pointerLeft,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
})
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
@ -72,8 +72,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts" setup>
|
||||||
import { computed, defineComponent, onMounted, ref, watch } from 'vue'
|
import { computed, onMounted, ref, watch } from 'vue'
|
||||||
import tinycolor, { ColorFormats } from 'tinycolor2'
|
import tinycolor, { ColorFormats } from 'tinycolor2'
|
||||||
import { debounce } from 'lodash'
|
import { debounce } from 'lodash'
|
||||||
import { toCanvas } from 'html-to-image'
|
import { toCanvas } from 'html-to-image'
|
||||||
@ -86,6 +86,17 @@ import EditableInput from './EditableInput.vue'
|
|||||||
|
|
||||||
import { message } from 'ant-design-vue'
|
import { message } from 'ant-design-vue'
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
modelValue: {
|
||||||
|
type: String,
|
||||||
|
default: '#e86b99',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
(event: 'update:modelValue', payload: string): void
|
||||||
|
}>()
|
||||||
|
|
||||||
const RECENT_COLORS = 'RECENT_COLORS'
|
const RECENT_COLORS = 'RECENT_COLORS'
|
||||||
|
|
||||||
const presetColorConfig = [
|
const presetColorConfig = [
|
||||||
@ -132,27 +143,10 @@ const getPresetColors = () => {
|
|||||||
const themeColors = ['#000000', '#ffffff', '#eeece1', '#1e497b', '#4e81bb', '#e2534d', '#9aba60', '#8165a0', '#47acc5', '#f9974c']
|
const themeColors = ['#000000', '#ffffff', '#eeece1', '#1e497b', '#4e81bb', '#e2534d', '#9aba60', '#8165a0', '#47acc5', '#f9974c']
|
||||||
const standardColors = ['#c21401', '#ff1e02', '#ffc12a', '#ffff3a', '#90cf5b', '#00af57', '#00afee', '#0071be', '#00215f', '#72349d']
|
const standardColors = ['#c21401', '#ff1e02', '#ffc12a', '#ffff3a', '#90cf5b', '#00af57', '#00afee', '#0071be', '#00215f', '#72349d']
|
||||||
|
|
||||||
export default defineComponent({
|
const hue = ref(-1)
|
||||||
name: 'color-picker',
|
const recentColors = ref<string[]>([])
|
||||||
components: {
|
|
||||||
Alpha,
|
|
||||||
Checkboard,
|
|
||||||
Hue,
|
|
||||||
Saturation,
|
|
||||||
EditableInput,
|
|
||||||
},
|
|
||||||
emits: ['update:modelValue'],
|
|
||||||
props: {
|
|
||||||
modelValue: {
|
|
||||||
type: String,
|
|
||||||
default: '#e86b99',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
setup(props, { emit }) {
|
|
||||||
const hue = ref(-1)
|
|
||||||
const recentColors = ref<string[]>([])
|
|
||||||
|
|
||||||
const color = computed({
|
const color = computed({
|
||||||
get() {
|
get() {
|
||||||
return tinycolor(props.modelValue).toRgb()
|
return tinycolor(props.modelValue).toRgb()
|
||||||
},
|
},
|
||||||
@ -160,21 +154,21 @@ export default defineComponent({
|
|||||||
const rgbaString = `rgba(${[rgba.r, rgba.g, rgba.b, rgba.a].join(',')})`
|
const rgbaString = `rgba(${[rgba.r, rgba.g, rgba.b, rgba.a].join(',')})`
|
||||||
emit('update:modelValue', rgbaString)
|
emit('update:modelValue', rgbaString)
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
const presetColors = getPresetColors()
|
const presetColors = getPresetColors()
|
||||||
|
|
||||||
const currentColor = computed(() => {
|
const currentColor = computed(() => {
|
||||||
return `rgba(${[color.value.r, color.value.g, color.value.b, color.value.a].join(',')})`
|
return `rgba(${[color.value.r, color.value.g, color.value.b, color.value.a].join(',')})`
|
||||||
})
|
})
|
||||||
|
|
||||||
const selectPresetColor = (colorString: string) => {
|
const selectPresetColor = (colorString: string) => {
|
||||||
hue.value = tinycolor(colorString).toHsl().h
|
hue.value = tinycolor(colorString).toHsl().h
|
||||||
emit('update:modelValue', colorString)
|
emit('update:modelValue', colorString)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 每次选择非预设颜色时,需要将该颜色加入到最近使用列表中
|
// 每次选择非预设颜色时,需要将该颜色加入到最近使用列表中
|
||||||
const updateRecentColorsCache = debounce(function() {
|
const updateRecentColorsCache = debounce(function() {
|
||||||
const _color = tinycolor(color.value).toRgbString()
|
const _color = tinycolor(color.value).toRgbString()
|
||||||
if (!recentColors.value.includes(_color)) {
|
if (!recentColors.value.includes(_color)) {
|
||||||
recentColors.value = [_color, ...recentColors.value]
|
recentColors.value = [_color, ...recentColors.value]
|
||||||
@ -184,19 +178,19 @@ export default defineComponent({
|
|||||||
recentColors.value = recentColors.value.slice(0, maxLength)
|
recentColors.value = recentColors.value.slice(0, maxLength)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, 300, { trailing: true })
|
}, 300, { trailing: true })
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
const recentColorsCache = localStorage.getItem(RECENT_COLORS)
|
const recentColorsCache = localStorage.getItem(RECENT_COLORS)
|
||||||
if (recentColorsCache) recentColors.value = JSON.parse(recentColorsCache)
|
if (recentColorsCache) recentColors.value = JSON.parse(recentColorsCache)
|
||||||
})
|
})
|
||||||
|
|
||||||
watch(recentColors, () => {
|
watch(recentColors, () => {
|
||||||
const recentColorsCache = JSON.stringify(recentColors.value)
|
const recentColorsCache = JSON.stringify(recentColors.value)
|
||||||
localStorage.setItem(RECENT_COLORS, recentColorsCache)
|
localStorage.setItem(RECENT_COLORS, recentColorsCache)
|
||||||
})
|
})
|
||||||
|
|
||||||
const changeColor = (value: ColorFormats.RGBA | ColorFormats.HSLA | ColorFormats.HSVA) => {
|
const changeColor = (value: ColorFormats.RGBA | ColorFormats.HSLA | ColorFormats.HSVA) => {
|
||||||
if ('h' in value) {
|
if ('h' in value) {
|
||||||
hue.value = value.h
|
hue.value = value.h
|
||||||
color.value = tinycolor(value).toRgb()
|
color.value = tinycolor(value).toRgb()
|
||||||
@ -207,9 +201,9 @@ export default defineComponent({
|
|||||||
}
|
}
|
||||||
|
|
||||||
updateRecentColorsCache()
|
updateRecentColorsCache()
|
||||||
}
|
}
|
||||||
|
|
||||||
const pickColor = () => {
|
const pickColor = () => {
|
||||||
const targetRef: HTMLElement | null = document.querySelector('.canvas')
|
const targetRef: HTMLElement | null = document.querySelector('.canvas')
|
||||||
if (!targetRef) return
|
if (!targetRef) return
|
||||||
|
|
||||||
@ -280,22 +274,7 @@ export default defineComponent({
|
|||||||
message.error('取色吸管初始化失败')
|
message.error('取色吸管初始化失败')
|
||||||
document.body.removeChild(maskRef)
|
document.body.removeChild(maskRef)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
|
||||||
themeColors,
|
|
||||||
standardColors,
|
|
||||||
presetColors,
|
|
||||||
color,
|
|
||||||
hue,
|
|
||||||
currentColor,
|
|
||||||
changeColor,
|
|
||||||
selectPresetColor,
|
|
||||||
recentColors,
|
|
||||||
pickColor,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
})
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
@ -30,13 +30,11 @@
|
|||||||
</ul>
|
</ul>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts" setup>
|
||||||
import { PropType, defineComponent } from 'vue'
|
import { PropType } from 'vue'
|
||||||
import { ContextmenuItem } from './types'
|
import { ContextmenuItem } from './types'
|
||||||
|
|
||||||
export default defineComponent({
|
defineProps({
|
||||||
name: 'menu-content',
|
|
||||||
props: {
|
|
||||||
menus: {
|
menus: {
|
||||||
type: Array as PropType<ContextmenuItem[]>,
|
type: Array as PropType<ContextmenuItem[]>,
|
||||||
required: true,
|
required: true,
|
||||||
@ -45,7 +43,6 @@ export default defineComponent({
|
|||||||
type: Function,
|
type: Function,
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
},
|
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@ -20,18 +20,13 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts" setup>
|
||||||
import { computed, defineComponent, PropType } from 'vue'
|
import { computed, PropType } from 'vue'
|
||||||
import { ContextmenuItem, Axis } from './types'
|
import { ContextmenuItem, Axis } from './types'
|
||||||
|
|
||||||
import MenuContent from './MenuContent.vue'
|
import MenuContent from './MenuContent.vue'
|
||||||
|
|
||||||
export default defineComponent({
|
const props = defineProps({
|
||||||
name: 'contextmenu',
|
|
||||||
components: {
|
|
||||||
MenuContent,
|
|
||||||
},
|
|
||||||
props: {
|
|
||||||
axis: {
|
axis: {
|
||||||
type: Object as PropType<Axis>,
|
type: Object as PropType<Axis>,
|
||||||
required: true,
|
required: true,
|
||||||
@ -48,9 +43,9 @@ export default defineComponent({
|
|||||||
type: Function,
|
type: Function,
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
},
|
})
|
||||||
setup(props) {
|
|
||||||
const style = computed(() => {
|
const style = computed(() => {
|
||||||
const MENU_WIDTH = 170
|
const MENU_WIDTH = 170
|
||||||
const MENU_HEIGHT = 30
|
const MENU_HEIGHT = 30
|
||||||
const DIVIDER_HEIGHT = 11
|
const DIVIDER_HEIGHT = 11
|
||||||
@ -70,21 +65,14 @@ export default defineComponent({
|
|||||||
left: screenWidth <= x + menuWidth ? x - menuWidth : x,
|
left: screenWidth <= x + menuWidth ? x - menuWidth : x,
|
||||||
top: screenHeight <= y + menuHeight ? y - menuHeight : y,
|
top: screenHeight <= y + menuHeight ? y - menuHeight : y,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const handleClickMenuItem = (item: ContextmenuItem) => {
|
const handleClickMenuItem = (item: ContextmenuItem) => {
|
||||||
if (item.disable) return
|
if (item.disable) return
|
||||||
if (item.children && !item.handler) return
|
if (item.children && !item.handler) return
|
||||||
if (item.handler) item.handler(props.el)
|
if (item.handler) item.handler(props.el)
|
||||||
props.removeContextmenu()
|
props.removeContextmenu()
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
|
||||||
style,
|
|
||||||
handleClickMenuItem,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
})
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
|
@ -12,38 +12,31 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts" setup>
|
||||||
import { defineComponent, ref } from 'vue'
|
import { ref } from 'vue'
|
||||||
|
|
||||||
export default defineComponent({
|
const props = defineProps({
|
||||||
name: 'file-input',
|
|
||||||
emits: ['change'],
|
|
||||||
props: {
|
|
||||||
accept: {
|
accept: {
|
||||||
type: String,
|
type: String,
|
||||||
default: 'image/*',
|
default: 'image/*',
|
||||||
},
|
},
|
||||||
},
|
})
|
||||||
setup(props, { emit }) {
|
|
||||||
const inputRef = ref<HTMLInputElement>()
|
|
||||||
|
|
||||||
const handleClick = () => {
|
const emit = defineEmits<{
|
||||||
|
(event: 'change', payload: FileList): void
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const inputRef = ref<HTMLInputElement>()
|
||||||
|
|
||||||
|
const handleClick = () => {
|
||||||
if (!inputRef.value) return
|
if (!inputRef.value) return
|
||||||
inputRef.value.value = ''
|
inputRef.value.value = ''
|
||||||
inputRef.value.click()
|
inputRef.value.click()
|
||||||
}
|
}
|
||||||
const handleChange = (e: Event) => {
|
const handleChange = (e: Event) => {
|
||||||
const files = (e.target as HTMLInputElement).files
|
const files = (e.target as HTMLInputElement).files
|
||||||
if (files) emit('change', files)
|
if (files) emit('change', files)
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
|
||||||
handleClick,
|
|
||||||
handleChange,
|
|
||||||
inputRef,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
})
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
@ -2,12 +2,8 @@
|
|||||||
<div class="fullscreen-spin" v-if="loading"><Spin :tip="tip" size="large" /></div>
|
<div class="fullscreen-spin" v-if="loading"><Spin :tip="tip" size="large" /></div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts" setup>
|
||||||
import { defineComponent } from 'vue'
|
const props = defineProps({
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
name: 'fullscreen-spin',
|
|
||||||
props: {
|
|
||||||
loading: {
|
loading: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false,
|
default: false,
|
||||||
@ -16,7 +12,6 @@ export default defineComponent({
|
|||||||
type: String,
|
type: String,
|
||||||
default: '',
|
default: '',
|
||||||
},
|
},
|
||||||
},
|
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@ -19,13 +19,11 @@
|
|||||||
</svg>
|
</svg>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts" setup>
|
||||||
import { computed, defineComponent, ref, watch } from 'vue'
|
import { computed, ref, watch } from 'vue'
|
||||||
import { hfmath } from './hfmath'
|
import { hfmath } from './hfmath'
|
||||||
|
|
||||||
export default defineComponent({
|
const props = defineProps({
|
||||||
name: 'formula-content',
|
|
||||||
props: {
|
|
||||||
latex: {
|
latex: {
|
||||||
type: String,
|
type: String,
|
||||||
required: true,
|
required: true,
|
||||||
@ -38,18 +36,18 @@ export default defineComponent({
|
|||||||
type: Number,
|
type: Number,
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
},
|
})
|
||||||
setup(props) {
|
|
||||||
const box = ref({ x: 0, y: 0, w: 0, h: 0 })
|
|
||||||
const pathd = ref('')
|
|
||||||
|
|
||||||
watch(() => props.latex, () => {
|
const box = ref({ x: 0, y: 0, w: 0, h: 0 })
|
||||||
|
const pathd = ref('')
|
||||||
|
|
||||||
|
watch(() => props.latex, () => {
|
||||||
const eq = new hfmath(props.latex)
|
const eq = new hfmath(props.latex)
|
||||||
pathd.value = eq.pathd({})
|
pathd.value = eq.pathd({})
|
||||||
box.value = eq.box({})
|
box.value = eq.box({})
|
||||||
}, { immediate: true })
|
}, { immediate: true })
|
||||||
|
|
||||||
const scale = computed(() => {
|
const scale = computed(() => {
|
||||||
const boxW = box.value.w + 32
|
const boxW = box.value.w + 32
|
||||||
const boxH = box.value.h + 32
|
const boxH = box.value.h + 32
|
||||||
|
|
||||||
@ -58,14 +56,6 @@ export default defineComponent({
|
|||||||
return props.height / boxH
|
return props.height / boxH
|
||||||
}
|
}
|
||||||
return 1
|
return 1
|
||||||
})
|
|
||||||
|
|
||||||
return {
|
|
||||||
box,
|
|
||||||
pathd,
|
|
||||||
scale,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@ -2,30 +2,22 @@
|
|||||||
<div class="symbol-content" v-html="svg"></div>
|
<div class="symbol-content" v-html="svg"></div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts" setup>
|
||||||
import { computed, defineComponent } from 'vue'
|
import { computed } from 'vue'
|
||||||
import { hfmath } from './hfmath'
|
import { hfmath } from './hfmath'
|
||||||
|
|
||||||
export default defineComponent({
|
const props = defineProps({
|
||||||
name: 'symbol-content',
|
|
||||||
props: {
|
|
||||||
latex: {
|
latex: {
|
||||||
type: String,
|
type: String,
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
},
|
})
|
||||||
setup(props) {
|
|
||||||
const svg = computed(() => {
|
const svg = computed(() => {
|
||||||
const eq = new hfmath(props.latex)
|
const eq = new hfmath(props.latex)
|
||||||
return eq.svg({
|
return eq.svg({
|
||||||
SCALE_X: 10,
|
SCALE_X: 10,
|
||||||
SCALE_Y: 10,
|
SCALE_Y: 10,
|
||||||
})
|
})
|
||||||
})
|
|
||||||
|
|
||||||
return {
|
|
||||||
svg,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
@ -65,8 +65,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts" setup>
|
||||||
import { computed, defineComponent, onMounted, ref } from 'vue'
|
import { computed, onMounted, ref } from 'vue'
|
||||||
import { hfmath } from './hfmath'
|
import { hfmath } from './hfmath'
|
||||||
import { FORMULA_LIST, SYMBOL_LIST } from '@/configs/latex'
|
import { FORMULA_LIST, SYMBOL_LIST } from '@/configs/latex'
|
||||||
|
|
||||||
@ -83,35 +83,43 @@ const tabs: Tab[] = [
|
|||||||
{ label: '预置公式', value: 'formula' },
|
{ label: '预置公式', value: 'formula' },
|
||||||
]
|
]
|
||||||
|
|
||||||
export default defineComponent({
|
interface LatexResult {
|
||||||
name: 'latex-editor',
|
latex: string
|
||||||
emits: ['update', 'close'],
|
path: string
|
||||||
components: {
|
w: number
|
||||||
FormulaContent,
|
h: number
|
||||||
SymbolContent,
|
}
|
||||||
},
|
|
||||||
props: {
|
const props = defineProps({
|
||||||
value: {
|
value: {
|
||||||
type: String,
|
type: String,
|
||||||
default: '',
|
default: '',
|
||||||
},
|
},
|
||||||
},
|
})
|
||||||
setup(props, { emit }) {
|
|
||||||
const latex = ref('')
|
|
||||||
const toolbarState = ref<'symbol' | 'formula'>('symbol')
|
|
||||||
const textAreaRef = ref<HTMLTextAreaElement>()
|
|
||||||
|
|
||||||
const selectedSymbolKey = ref(SYMBOL_LIST[0].type)
|
const emit = defineEmits<{
|
||||||
const symbolPool = computed(() => {
|
(event: 'update', payload: LatexResult): void
|
||||||
|
(event: 'close'): void
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const formulaList = FORMULA_LIST
|
||||||
|
const symbolList = SYMBOL_LIST
|
||||||
|
|
||||||
|
const latex = ref('')
|
||||||
|
const toolbarState = ref<'symbol' | 'formula'>('symbol')
|
||||||
|
const textAreaRef = ref<HTMLTextAreaElement>()
|
||||||
|
|
||||||
|
const selectedSymbolKey = ref(SYMBOL_LIST[0].type)
|
||||||
|
const symbolPool = computed(() => {
|
||||||
const selectedSymbol = SYMBOL_LIST.find(item => item.type === selectedSymbolKey.value)
|
const selectedSymbol = SYMBOL_LIST.find(item => item.type === selectedSymbolKey.value)
|
||||||
return selectedSymbol?.children || []
|
return selectedSymbol?.children || []
|
||||||
})
|
})
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
if (props.value) latex.value = props.value
|
if (props.value) latex.value = props.value
|
||||||
})
|
})
|
||||||
|
|
||||||
const update = () => {
|
const update = () => {
|
||||||
if (!latex.value) return
|
if (!latex.value) return
|
||||||
|
|
||||||
const eq = new hfmath(latex.value)
|
const eq = new hfmath(latex.value)
|
||||||
@ -124,31 +132,15 @@ export default defineComponent({
|
|||||||
w: box.w + 32,
|
w: box.w + 32,
|
||||||
h: box.h + 32,
|
h: box.h + 32,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const close = () => emit('close')
|
const close = () => emit('close')
|
||||||
|
|
||||||
const insertSymbol = (latex: string) => {
|
const insertSymbol = (latex: string) => {
|
||||||
if (!textAreaRef.value) return
|
if (!textAreaRef.value) return
|
||||||
textAreaRef.value.focus()
|
textAreaRef.value.focus()
|
||||||
document.execCommand('insertText', false, latex)
|
document.execCommand('insertText', false, latex)
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
|
||||||
tabs,
|
|
||||||
latex,
|
|
||||||
toolbarState,
|
|
||||||
selectedSymbolKey,
|
|
||||||
formulaList: FORMULA_LIST,
|
|
||||||
symbolList: SYMBOL_LIST,
|
|
||||||
symbolPool,
|
|
||||||
textAreaRef,
|
|
||||||
update,
|
|
||||||
close,
|
|
||||||
insertSymbol,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
})
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
@ -55,13 +55,11 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts" setup>
|
||||||
import { computed, defineComponent, onMounted, onUnmounted, PropType, ref, watch } from 'vue'
|
import { computed, onMounted, onUnmounted, PropType, ref, watch } from 'vue'
|
||||||
import { throttle } from 'lodash'
|
import { throttle } from 'lodash'
|
||||||
|
|
||||||
export default defineComponent({
|
const props = defineProps({
|
||||||
name: 'writing-board',
|
|
||||||
props: {
|
|
||||||
color: {
|
color: {
|
||||||
type: String,
|
type: String,
|
||||||
default: '#ffcc00',
|
default: '#ffcc00',
|
||||||
@ -74,55 +72,55 @@ export default defineComponent({
|
|||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false,
|
default: false,
|
||||||
},
|
},
|
||||||
},
|
})
|
||||||
setup(props) {
|
|
||||||
let ctx: CanvasRenderingContext2D | null = null
|
|
||||||
const writingBoardRef = ref<HTMLElement>()
|
|
||||||
const canvasRef = ref<HTMLCanvasElement>()
|
|
||||||
|
|
||||||
const penSize = ref(6)
|
let ctx: CanvasRenderingContext2D | null = null
|
||||||
const rubberSize = ref(80)
|
const writingBoardRef = ref<HTMLElement>()
|
||||||
const markSize = ref(24)
|
const canvasRef = ref<HTMLCanvasElement>()
|
||||||
|
|
||||||
let lastPos = {
|
const penSize = ref(6)
|
||||||
|
const rubberSize = ref(80)
|
||||||
|
const markSize = ref(24)
|
||||||
|
|
||||||
|
let lastPos = {
|
||||||
x: 0,
|
x: 0,
|
||||||
y: 0,
|
y: 0,
|
||||||
}
|
}
|
||||||
let isMouseDown = false
|
let isMouseDown = false
|
||||||
let lastTime = 0
|
let lastTime = 0
|
||||||
let lastLineWidth = -1
|
let lastLineWidth = -1
|
||||||
|
|
||||||
// 鼠标位置坐标:用于画笔或橡皮位置跟随
|
// 鼠标位置坐标:用于画笔或橡皮位置跟随
|
||||||
const mouse = ref({
|
const mouse = ref({
|
||||||
x: 0,
|
x: 0,
|
||||||
y: 0,
|
y: 0,
|
||||||
})
|
})
|
||||||
|
|
||||||
// 鼠标是否处在画布范围内:处在范围内才会显示画笔或橡皮
|
// 鼠标是否处在画布范围内:处在范围内才会显示画笔或橡皮
|
||||||
const mouseInCanvas = ref(false)
|
const mouseInCanvas = ref(false)
|
||||||
|
|
||||||
// 监听更新canvas尺寸
|
// 监听更新canvas尺寸
|
||||||
const canvasWidth = ref(0)
|
const canvasWidth = ref(0)
|
||||||
const canvasHeight = ref(0)
|
const canvasHeight = ref(0)
|
||||||
|
|
||||||
const widthScale = computed(() => canvasRef.value ? canvasWidth.value / canvasRef.value.width : 1)
|
const widthScale = computed(() => canvasRef.value ? canvasWidth.value / canvasRef.value.width : 1)
|
||||||
const heightScale = computed(() => canvasRef.value ? canvasHeight.value / canvasRef.value.height : 1)
|
const heightScale = computed(() => canvasRef.value ? canvasHeight.value / canvasRef.value.height : 1)
|
||||||
|
|
||||||
const updateCanvasSize = () => {
|
const updateCanvasSize = () => {
|
||||||
if (!writingBoardRef.value) return
|
if (!writingBoardRef.value) return
|
||||||
canvasWidth.value = writingBoardRef.value.clientWidth
|
canvasWidth.value = writingBoardRef.value.clientWidth
|
||||||
canvasHeight.value = writingBoardRef.value.clientHeight
|
canvasHeight.value = writingBoardRef.value.clientHeight
|
||||||
}
|
}
|
||||||
const resizeObserver = new ResizeObserver(updateCanvasSize)
|
const resizeObserver = new ResizeObserver(updateCanvasSize)
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
if (writingBoardRef.value) resizeObserver.observe(writingBoardRef.value)
|
if (writingBoardRef.value) resizeObserver.observe(writingBoardRef.value)
|
||||||
})
|
})
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
if (writingBoardRef.value) resizeObserver.unobserve(writingBoardRef.value)
|
if (writingBoardRef.value) resizeObserver.unobserve(writingBoardRef.value)
|
||||||
})
|
})
|
||||||
|
|
||||||
// 初始化画布
|
// 初始化画布
|
||||||
const initCanvas = () => {
|
const initCanvas = () => {
|
||||||
if (!canvasRef.value || !writingBoardRef.value) return
|
if (!canvasRef.value || !writingBoardRef.value) return
|
||||||
|
|
||||||
ctx = canvasRef.value.getContext('2d')
|
ctx = canvasRef.value.getContext('2d')
|
||||||
@ -133,11 +131,11 @@ export default defineComponent({
|
|||||||
|
|
||||||
ctx.lineCap = 'round'
|
ctx.lineCap = 'round'
|
||||||
ctx.lineJoin = 'round'
|
ctx.lineJoin = 'round'
|
||||||
}
|
}
|
||||||
onMounted(initCanvas)
|
onMounted(initCanvas)
|
||||||
|
|
||||||
// 切换画笔模式时,更新 canvas ctx 配置
|
// 切换画笔模式时,更新 canvas ctx 配置
|
||||||
const updateCtx = () => {
|
const updateCtx = () => {
|
||||||
if (!ctx) return
|
if (!ctx) return
|
||||||
if (props.model === 'mark') {
|
if (props.model === 'mark') {
|
||||||
ctx.globalCompositeOperation = 'xor'
|
ctx.globalCompositeOperation = 'xor'
|
||||||
@ -147,11 +145,11 @@ export default defineComponent({
|
|||||||
ctx.globalCompositeOperation = 'source-over'
|
ctx.globalCompositeOperation = 'source-over'
|
||||||
ctx.globalAlpha = 1
|
ctx.globalAlpha = 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
watch(() => props.model, updateCtx)
|
watch(() => props.model, updateCtx)
|
||||||
|
|
||||||
// 绘制画笔墨迹方法
|
// 绘制画笔墨迹方法
|
||||||
const draw = (posX: number, posY: number, lineWidth: number) => {
|
const draw = (posX: number, posY: number, lineWidth: number) => {
|
||||||
if (!ctx) return
|
if (!ctx) return
|
||||||
|
|
||||||
const lastPosX = lastPos.x
|
const lastPosX = lastPos.x
|
||||||
@ -164,10 +162,10 @@ export default defineComponent({
|
|||||||
ctx.lineTo(posX, posY)
|
ctx.lineTo(posX, posY)
|
||||||
ctx.stroke()
|
ctx.stroke()
|
||||||
ctx.closePath()
|
ctx.closePath()
|
||||||
}
|
}
|
||||||
|
|
||||||
// 擦除墨迹方法
|
// 擦除墨迹方法
|
||||||
const erase = (posX: number, posY: number) => {
|
const erase = (posX: number, posY: number) => {
|
||||||
if (!ctx || !canvasRef.value) return
|
if (!ctx || !canvasRef.value) return
|
||||||
const lastPosX = lastPos.x
|
const lastPosX = lastPos.x
|
||||||
const lastPosY = lastPos.y
|
const lastPosY = lastPos.y
|
||||||
@ -198,17 +196,17 @@ export default defineComponent({
|
|||||||
ctx.clip()
|
ctx.clip()
|
||||||
ctx.clearRect(0, 0, canvasRef.value.width, canvasRef.value.height)
|
ctx.clearRect(0, 0, canvasRef.value.width, canvasRef.value.height)
|
||||||
ctx.restore()
|
ctx.restore()
|
||||||
}
|
}
|
||||||
|
|
||||||
// 计算鼠标两次移动之间的距离
|
// 计算鼠标两次移动之间的距离
|
||||||
const getDistance = (posX: number, posY: number) => {
|
const getDistance = (posX: number, posY: number) => {
|
||||||
const lastPosX = lastPos.x
|
const lastPosX = lastPos.x
|
||||||
const lastPosY = lastPos.y
|
const lastPosY = lastPos.y
|
||||||
return Math.sqrt((posX - lastPosX) * (posX - lastPosX) + (posY - lastPosY) * (posY - lastPosY))
|
return Math.sqrt((posX - lastPosX) * (posX - lastPosX) + (posY - lastPosY) * (posY - lastPosY))
|
||||||
}
|
}
|
||||||
|
|
||||||
// 根据鼠标两次移动之间的距离s和时间t计算绘制速度,速度越快,墨迹越细
|
// 根据鼠标两次移动之间的距离s和时间t计算绘制速度,速度越快,墨迹越细
|
||||||
const getLineWidth = (s: number, t: number) => {
|
const getLineWidth = (s: number, t: number) => {
|
||||||
const maxV = 10
|
const maxV = 10
|
||||||
const minV = 0.1
|
const minV = 0.1
|
||||||
const maxWidth = penSize.value
|
const maxWidth = penSize.value
|
||||||
@ -222,10 +220,10 @@ export default defineComponent({
|
|||||||
|
|
||||||
if (lastLineWidth === -1) return lineWidth
|
if (lastLineWidth === -1) return lineWidth
|
||||||
return lineWidth * 1 / 3 + lastLineWidth * 2 / 3
|
return lineWidth * 1 / 3 + lastLineWidth * 2 / 3
|
||||||
}
|
}
|
||||||
|
|
||||||
// 路径操作
|
// 路径操作
|
||||||
const handleMove = (x: number, y: number) => {
|
const handleMove = (x: number, y: number) => {
|
||||||
const time = new Date().getTime()
|
const time = new Date().getTime()
|
||||||
|
|
||||||
if (props.model === 'pen') {
|
if (props.model === 'pen') {
|
||||||
@ -241,21 +239,21 @@ export default defineComponent({
|
|||||||
|
|
||||||
lastPos = { x, y }
|
lastPos = { x, y }
|
||||||
lastTime = new Date().getTime()
|
lastTime = new Date().getTime()
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取鼠标在canvas中的相对位置
|
// 获取鼠标在canvas中的相对位置
|
||||||
const getMouseOffsetPosition = (e: MouseEvent | TouchEvent) => {
|
const getMouseOffsetPosition = (e: MouseEvent | TouchEvent) => {
|
||||||
if (!canvasRef.value) return [0, 0]
|
if (!canvasRef.value) return [0, 0]
|
||||||
const event = e instanceof MouseEvent ? e : e.changedTouches[0]
|
const event = e instanceof MouseEvent ? e : e.changedTouches[0]
|
||||||
const canvasRect = canvasRef.value.getBoundingClientRect()
|
const canvasRect = canvasRef.value.getBoundingClientRect()
|
||||||
const x = event.pageX - canvasRect.x
|
const x = event.pageX - canvasRect.x
|
||||||
const y = event.pageY - canvasRect.y
|
const y = event.pageY - canvasRect.y
|
||||||
return [x, y]
|
return [x, y]
|
||||||
}
|
}
|
||||||
|
|
||||||
// 处理鼠标(触摸)事件
|
// 处理鼠标(触摸)事件
|
||||||
// 准备开始绘制/擦除墨迹(落笔)
|
// 准备开始绘制/擦除墨迹(落笔)
|
||||||
const handleMousedown = (e: MouseEvent | TouchEvent) => {
|
const handleMousedown = (e: MouseEvent | TouchEvent) => {
|
||||||
const [mouseX, mouseY] = getMouseOffsetPosition(e)
|
const [mouseX, mouseY] = getMouseOffsetPosition(e)
|
||||||
const x = mouseX / widthScale.value
|
const x = mouseX / widthScale.value
|
||||||
const y = mouseY / heightScale.value
|
const y = mouseY / heightScale.value
|
||||||
@ -268,10 +266,10 @@ export default defineComponent({
|
|||||||
mouse.value = { x: mouseX, y: mouseY }
|
mouse.value = { x: mouseX, y: mouseY }
|
||||||
mouseInCanvas.value = true
|
mouseInCanvas.value = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 开始绘制/擦除墨迹(移动)
|
// 开始绘制/擦除墨迹(移动)
|
||||||
const handleMousemove = (e: MouseEvent | TouchEvent) => {
|
const handleMousemove = (e: MouseEvent | TouchEvent) => {
|
||||||
const [mouseX, mouseY] = getMouseOffsetPosition(e)
|
const [mouseX, mouseY] = getMouseOffsetPosition(e)
|
||||||
const x = mouseX / widthScale.value
|
const x = mouseX / widthScale.value
|
||||||
const y = mouseY / heightScale.value
|
const y = mouseY / heightScale.value
|
||||||
@ -279,37 +277,37 @@ export default defineComponent({
|
|||||||
mouse.value = { x: mouseX, y: mouseY }
|
mouse.value = { x: mouseX, y: mouseY }
|
||||||
|
|
||||||
if (isMouseDown) handleMove(x, y)
|
if (isMouseDown) handleMove(x, y)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 结束绘制/擦除墨迹(停笔)
|
// 结束绘制/擦除墨迹(停笔)
|
||||||
const handleMouseup = () => {
|
const handleMouseup = () => {
|
||||||
if (!isMouseDown) return
|
if (!isMouseDown) return
|
||||||
isMouseDown = false
|
isMouseDown = false
|
||||||
}
|
}
|
||||||
|
|
||||||
// 清空画布
|
// 清空画布
|
||||||
const clearCanvas = () => {
|
const clearCanvas = () => {
|
||||||
if (!ctx || !canvasRef.value) return
|
if (!ctx || !canvasRef.value) return
|
||||||
ctx.clearRect(0, 0, canvasRef.value.width, canvasRef.value.height)
|
ctx.clearRect(0, 0, canvasRef.value.width, canvasRef.value.height)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取 DataURL
|
// 获取 DataURL
|
||||||
const getImageDataURL = () => {
|
const getImageDataURL = () => {
|
||||||
return canvasRef.value?.toDataURL()
|
return canvasRef.value?.toDataURL()
|
||||||
}
|
}
|
||||||
|
|
||||||
// 设置 DataURL(绘制图片到 canvas)
|
// 设置 DataURL(绘制图片到 canvas)
|
||||||
const setImageDataURL = (imageDataURL: string) => {
|
const setImageDataURL = (imageDataURL: string) => {
|
||||||
const img = new Image()
|
const img = new Image()
|
||||||
img.src = imageDataURL
|
img.src = imageDataURL
|
||||||
img.onload = () => {
|
img.onload = () => {
|
||||||
if (!ctx) return
|
if (!ctx) return
|
||||||
ctx.drawImage(img, 0, 0)
|
ctx.drawImage(img, 0, 0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 滚动鼠标滚轮,调整笔触大小
|
// 滚动鼠标滚轮,调整笔触大小
|
||||||
const mousewheelListener = throttle(function(e: WheelEvent) {
|
const mousewheelListener = throttle(function(e: WheelEvent) {
|
||||||
if (props.model === 'eraser') {
|
if (props.model === 'eraser') {
|
||||||
if (e.deltaY < 0 && rubberSize.value < 200) rubberSize.value += 20
|
if (e.deltaY < 0 && rubberSize.value < 200) rubberSize.value += 20
|
||||||
else if (e.deltaY > 0 && rubberSize.value > 20) rubberSize.value -= 20
|
else if (e.deltaY > 0 && rubberSize.value > 20) rubberSize.value -= 20
|
||||||
@ -322,27 +320,12 @@ export default defineComponent({
|
|||||||
if (e.deltaY < 0 && markSize.value < 40) markSize.value += 4
|
if (e.deltaY < 0 && markSize.value < 40) markSize.value += 4
|
||||||
else if (e.deltaY > 0 && markSize.value > 16) markSize.value -= 4
|
else if (e.deltaY > 0 && markSize.value > 16) markSize.value -= 4
|
||||||
}
|
}
|
||||||
}, 300, { leading: true, trailing: false })
|
}, 300, { leading: true, trailing: false })
|
||||||
|
|
||||||
return {
|
defineExpose({
|
||||||
mouse,
|
|
||||||
mouseInCanvas,
|
|
||||||
penSize,
|
|
||||||
rubberSize,
|
|
||||||
markSize,
|
|
||||||
writingBoardRef,
|
|
||||||
canvasRef,
|
|
||||||
canvasWidth,
|
|
||||||
canvasHeight,
|
|
||||||
handleMousedown,
|
|
||||||
handleMousemove,
|
|
||||||
handleMouseup,
|
|
||||||
clearCanvas,
|
clearCanvas,
|
||||||
getImageDataURL,
|
getImageDataURL,
|
||||||
setImageDataURL,
|
setImageDataURL,
|
||||||
mousewheelListener,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@ -62,7 +62,7 @@ export default () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 导入pptist文件
|
// 导入pptist文件
|
||||||
const importSpecificFile = (files: File[], cover = false) => {
|
const importSpecificFile = (files: FileList, cover = false) => {
|
||||||
const file = files[0]
|
const file = files[0]
|
||||||
|
|
||||||
const reader = new FileReader()
|
const reader = new FileReader()
|
||||||
|
2
src/shims-vue.d.ts
vendored
2
src/shims-vue.d.ts
vendored
@ -1,3 +1,5 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
|
|
||||||
declare module '*.vue' {
|
declare module '*.vue' {
|
||||||
import type { DefineComponent } from 'vue'
|
import type { DefineComponent } from 'vue'
|
||||||
const component: DefineComponent<{}, {}, any>
|
const component: DefineComponent<{}, {}, any>
|
||||||
|
@ -4,13 +4,11 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts" setup>
|
||||||
import { computed, PropType, defineComponent } from 'vue'
|
import { computed, PropType } from 'vue'
|
||||||
import { AlignmentLineAxis } from '@/types/edit'
|
import { AlignmentLineAxis } from '@/types/edit'
|
||||||
|
|
||||||
export default defineComponent({
|
const props = defineProps({
|
||||||
name: 'alignment-line',
|
|
||||||
props: {
|
|
||||||
type: {
|
type: {
|
||||||
type: String as PropType<'vertical' | 'horizontal'>,
|
type: String as PropType<'vertical' | 'horizontal'>,
|
||||||
required: true,
|
required: true,
|
||||||
@ -27,24 +25,16 @@ export default defineComponent({
|
|||||||
type: Number,
|
type: Number,
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
},
|
})
|
||||||
setup(props) {
|
|
||||||
// 吸附对齐线的位置
|
|
||||||
const left = computed(() => props.axis.x * props.canvasScale + 'px')
|
|
||||||
const top = computed(() => props.axis.y * props.canvasScale + 'px')
|
|
||||||
|
|
||||||
// 吸附对齐线的长度
|
// 吸附对齐线的位置
|
||||||
const sizeStyle = computed(() => {
|
const left = computed(() => props.axis.x * props.canvasScale + 'px')
|
||||||
|
const top = computed(() => props.axis.y * props.canvasScale + 'px')
|
||||||
|
|
||||||
|
// 吸附对齐线的长度
|
||||||
|
const sizeStyle = computed(() => {
|
||||||
if (props.type === 'vertical') return { height: props.length * props.canvasScale + 'px' }
|
if (props.type === 'vertical') return { height: props.length * props.canvasScale + 'px' }
|
||||||
return { width: props.length * props.canvasScale + 'px' }
|
return { width: props.length * props.canvasScale + 'px' }
|
||||||
})
|
|
||||||
|
|
||||||
return {
|
|
||||||
left,
|
|
||||||
top,
|
|
||||||
sizeStyle,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@ -16,8 +16,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts" setup>
|
||||||
import { computed, defineComponent, PropType } from 'vue'
|
import { computed, PropType } from 'vue'
|
||||||
import { ElementTypes, PPTElement } from '@/types/slides'
|
import { ElementTypes, PPTElement } from '@/types/slides'
|
||||||
import { ContextmenuItem } from '@/components/Contextmenu/types'
|
import { ContextmenuItem } from '@/components/Contextmenu/types'
|
||||||
|
|
||||||
@ -41,9 +41,7 @@ import LatexElement from '@/views/components/element/LatexElement/index.vue'
|
|||||||
import VideoElement from '@/views/components/element/VideoElement/index.vue'
|
import VideoElement from '@/views/components/element/VideoElement/index.vue'
|
||||||
import AudioElement from '@/views/components/element/AudioElement/index.vue'
|
import AudioElement from '@/views/components/element/AudioElement/index.vue'
|
||||||
|
|
||||||
export default defineComponent({
|
const props = defineProps({
|
||||||
name: 'editable-element',
|
|
||||||
props: {
|
|
||||||
elementInfo: {
|
elementInfo: {
|
||||||
type: Object as PropType<PPTElement>,
|
type: Object as PropType<PPTElement>,
|
||||||
required: true,
|
required: true,
|
||||||
@ -64,9 +62,9 @@ export default defineComponent({
|
|||||||
type: Function as PropType<() => void>,
|
type: Function as PropType<() => void>,
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
},
|
})
|
||||||
setup(props) {
|
|
||||||
const currentElementComponent = computed(() => {
|
const currentElementComponent = computed(() => {
|
||||||
const elementTypeMap = {
|
const elementTypeMap = {
|
||||||
[ElementTypes.IMAGE]: ImageElement,
|
[ElementTypes.IMAGE]: ImageElement,
|
||||||
[ElementTypes.TEXT]: TextElement,
|
[ElementTypes.TEXT]: TextElement,
|
||||||
@ -79,17 +77,17 @@ export default defineComponent({
|
|||||||
[ElementTypes.AUDIO]: AudioElement,
|
[ElementTypes.AUDIO]: AudioElement,
|
||||||
}
|
}
|
||||||
return elementTypeMap[props.elementInfo.type] || null
|
return elementTypeMap[props.elementInfo.type] || null
|
||||||
})
|
})
|
||||||
|
|
||||||
const { orderElement } = useOrderElement()
|
const { orderElement } = useOrderElement()
|
||||||
const { alignElementToCanvas } = useAlignElementToCanvas()
|
const { alignElementToCanvas } = useAlignElementToCanvas()
|
||||||
const { combineElements, uncombineElements } = useCombineElement()
|
const { combineElements, uncombineElements } = useCombineElement()
|
||||||
const { deleteElement } = useDeleteElement()
|
const { deleteElement } = useDeleteElement()
|
||||||
const { lockElement, unlockElement } = useLockElement()
|
const { lockElement, unlockElement } = useLockElement()
|
||||||
const { copyElement, pasteElement, cutElement } = useCopyAndPasteElement()
|
const { copyElement, pasteElement, cutElement } = useCopyAndPasteElement()
|
||||||
const { selectAllElement } = useSelectAllElement()
|
const { selectAllElement } = useSelectAllElement()
|
||||||
|
|
||||||
const contextmenus = (): ContextmenuItem[] => {
|
const contextmenus = (): ContextmenuItem[] => {
|
||||||
if (props.elementInfo.lock) {
|
if (props.elementInfo.lock) {
|
||||||
return [{
|
return [{
|
||||||
text: '解锁',
|
text: '解锁',
|
||||||
@ -180,12 +178,5 @@ export default defineComponent({
|
|||||||
handler: deleteElement,
|
handler: deleteElement,
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
|
||||||
currentElementComponent,
|
|
||||||
contextmenus,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
})
|
|
||||||
</script>
|
</script>
|
@ -25,36 +25,37 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts" setup>
|
||||||
import { computed, defineComponent, onMounted, ref } from 'vue'
|
import { computed, onMounted, ref } from 'vue'
|
||||||
import { storeToRefs } from 'pinia'
|
import { storeToRefs } from 'pinia'
|
||||||
import { useMainStore, useKeyboardStore } from '@/store'
|
import { useMainStore, useKeyboardStore } from '@/store'
|
||||||
|
import { CreateElementSelectionData } from '@/types/edit'
|
||||||
|
|
||||||
export default defineComponent({
|
const emit = defineEmits<{
|
||||||
name: 'element-create-selection',
|
(event: 'created', payload: CreateElementSelectionData): void
|
||||||
emits: ['created'],
|
}>()
|
||||||
setup(props, { emit }) {
|
|
||||||
const mainStore = useMainStore()
|
|
||||||
const { creatingElement } = storeToRefs(mainStore)
|
|
||||||
const { ctrlOrShiftKeyActive } = storeToRefs(useKeyboardStore())
|
|
||||||
|
|
||||||
const start = ref<[number, number]>()
|
const mainStore = useMainStore()
|
||||||
const end = ref<[number, number]>()
|
const { creatingElement } = storeToRefs(mainStore)
|
||||||
|
const { ctrlOrShiftKeyActive } = storeToRefs(useKeyboardStore())
|
||||||
|
|
||||||
const selectionRef = ref<HTMLElement>()
|
const start = ref<[number, number]>()
|
||||||
const offset = ref({
|
const end = ref<[number, number]>()
|
||||||
|
|
||||||
|
const selectionRef = ref<HTMLElement>()
|
||||||
|
const offset = ref({
|
||||||
x: 0,
|
x: 0,
|
||||||
y: 0,
|
y: 0,
|
||||||
})
|
})
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
if (!selectionRef.value) return
|
if (!selectionRef.value) return
|
||||||
const { x, y } = selectionRef.value.getBoundingClientRect()
|
const { x, y } = selectionRef.value.getBoundingClientRect()
|
||||||
offset.value = { x, y }
|
offset.value = { x, y }
|
||||||
})
|
})
|
||||||
|
|
||||||
// 鼠标拖动创建元素生成位置大小
|
// 鼠标拖动创建元素生成位置大小
|
||||||
// 获取范围的起始位置和终点位置
|
// 获取范围的起始位置和终点位置
|
||||||
const createSelection = (e: MouseEvent) => {
|
const createSelection = (e: MouseEvent) => {
|
||||||
let isMouseDown = true
|
let isMouseDown = true
|
||||||
|
|
||||||
const startPageX = e.pageX
|
const startPageX = e.pageX
|
||||||
@ -120,8 +121,8 @@ export default defineComponent({
|
|||||||
(Math.abs(endPageX - startPageX) >= minSize || Math.abs(endPageY - startPageY) >= minSize)
|
(Math.abs(endPageX - startPageX) >= minSize || Math.abs(endPageY - startPageY) >= minSize)
|
||||||
) {
|
) {
|
||||||
emit('created', {
|
emit('created', {
|
||||||
start: start.value,
|
start: start.value!,
|
||||||
end: end.value,
|
end: end.value!,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
else if (
|
else if (
|
||||||
@ -129,8 +130,8 @@ export default defineComponent({
|
|||||||
(Math.abs(endPageX - startPageX) >= minSize && Math.abs(endPageY - startPageY) >= minSize)
|
(Math.abs(endPageX - startPageX) >= minSize && Math.abs(endPageY - startPageY) >= minSize)
|
||||||
) {
|
) {
|
||||||
emit('created', {
|
emit('created', {
|
||||||
start: start.value,
|
start: start.value!,
|
||||||
end: end.value,
|
end: end.value!,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
@ -147,10 +148,10 @@ export default defineComponent({
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 绘制线条的路径相关数据(仅当绘制元素类型为线条时使用)
|
// 绘制线条的路径相关数据(仅当绘制元素类型为线条时使用)
|
||||||
const lineData = computed(() => {
|
const lineData = computed(() => {
|
||||||
if (!start.value || !end.value) return null
|
if (!start.value || !end.value) return null
|
||||||
if (!creatingElement.value || creatingElement.value.type !== 'line') return null
|
if (!creatingElement.value || creatingElement.value.type !== 'line') return null
|
||||||
|
|
||||||
@ -180,10 +181,10 @@ export default defineComponent({
|
|||||||
endY,
|
endY,
|
||||||
path,
|
path,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
// 根据生成范围的起始位置和终点位置,计算元素创建时的位置和大小
|
// 根据生成范围的起始位置和终点位置,计算元素创建时的位置和大小
|
||||||
const position = computed(() => {
|
const position = computed(() => {
|
||||||
if (!start.value || !end.value) return {}
|
if (!start.value || !end.value) return {}
|
||||||
|
|
||||||
const [startX, startY] = start.value
|
const [startX, startY] = start.value
|
||||||
@ -202,18 +203,6 @@ export default defineComponent({
|
|||||||
width: width + 'px',
|
width: width + 'px',
|
||||||
height: height + 'px',
|
height: height + 'px',
|
||||||
}
|
}
|
||||||
})
|
|
||||||
|
|
||||||
return {
|
|
||||||
selectionRef,
|
|
||||||
start,
|
|
||||||
end,
|
|
||||||
creatingElement,
|
|
||||||
createSelection,
|
|
||||||
lineData,
|
|
||||||
position,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@ -13,33 +13,30 @@
|
|||||||
</svg>
|
</svg>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts" setup>
|
||||||
import { defineComponent, computed } from 'vue'
|
import { computed } from 'vue'
|
||||||
import tinycolor from 'tinycolor2'
|
import tinycolor from 'tinycolor2'
|
||||||
import { storeToRefs } from 'pinia'
|
import { storeToRefs } from 'pinia'
|
||||||
import { useMainStore, useSlidesStore } from '@/store'
|
import { useMainStore, useSlidesStore } from '@/store'
|
||||||
import { VIEWPORT_SIZE } from '@/configs/canvas'
|
import { VIEWPORT_SIZE } from '@/configs/canvas'
|
||||||
import { SlideBackground } from '@/types/slides'
|
import { SlideBackground } from '@/types/slides'
|
||||||
|
|
||||||
export default defineComponent({
|
const { canvasScale } = storeToRefs(useMainStore())
|
||||||
name: 'grid-lines',
|
const { currentSlide, viewportRatio } = storeToRefs(useSlidesStore())
|
||||||
setup() {
|
|
||||||
const { canvasScale } = storeToRefs(useMainStore())
|
|
||||||
const { currentSlide, viewportRatio } = storeToRefs(useSlidesStore())
|
|
||||||
|
|
||||||
const background = computed<SlideBackground | undefined>(() => currentSlide.value?.background)
|
const background = computed<SlideBackground | undefined>(() => currentSlide.value?.background)
|
||||||
|
|
||||||
// 计算网格线的颜色,避免与背景的颜色太接近
|
// 计算网格线的颜色,避免与背景的颜色太接近
|
||||||
const gridColor = computed(() => {
|
const gridColor = computed(() => {
|
||||||
const bgColor = background.value?.color || '#fff'
|
const bgColor = background.value?.color || '#fff'
|
||||||
const colorList = ['#000', '#fff']
|
const colorList = ['#000', '#fff']
|
||||||
return tinycolor.mostReadable(bgColor, colorList, { includeFallbackColors: true }).setAlpha(.5).toRgbString()
|
return tinycolor.mostReadable(bgColor, colorList, { includeFallbackColors: true }).setAlpha(.5).toRgbString()
|
||||||
})
|
})
|
||||||
|
|
||||||
const gridSize = 50
|
const gridSize = 50
|
||||||
|
|
||||||
// 计算网格路径
|
// 计算网格路径
|
||||||
const getPath = () => {
|
const getPath = () => {
|
||||||
const maxX = VIEWPORT_SIZE
|
const maxX = VIEWPORT_SIZE
|
||||||
const maxY = VIEWPORT_SIZE * viewportRatio.value
|
const maxY = VIEWPORT_SIZE * viewportRatio.value
|
||||||
|
|
||||||
@ -51,17 +48,9 @@ export default defineComponent({
|
|||||||
path += `M${i * gridSize} 0 L${i * gridSize} ${maxY} `
|
path += `M${i * gridSize} 0 L${i * gridSize} ${maxY} `
|
||||||
}
|
}
|
||||||
return path
|
return path
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
const path = getPath()
|
||||||
canvasScale,
|
|
||||||
gridColor,
|
|
||||||
width: VIEWPORT_SIZE,
|
|
||||||
height: VIEWPORT_SIZE * viewportRatio.value,
|
|
||||||
path: getPath(),
|
|
||||||
}
|
|
||||||
},
|
|
||||||
})
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
@ -37,8 +37,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts" setup>
|
||||||
import { computed, defineComponent, onMounted, ref } from 'vue'
|
import { computed, onMounted, ref } from 'vue'
|
||||||
import { storeToRefs } from 'pinia'
|
import { storeToRefs } from 'pinia'
|
||||||
import { useMainStore, useSlidesStore } from '@/store'
|
import { useMainStore, useSlidesStore } from '@/store'
|
||||||
import { PPTElementLink } from '@/types/slides'
|
import { PPTElementLink } from '@/types/slides'
|
||||||
@ -52,47 +52,44 @@ interface TabItem {
|
|||||||
label: string
|
label: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export default defineComponent({
|
const emit = defineEmits<{
|
||||||
name: 'link-dialog',
|
(event: 'close'): void
|
||||||
emits: ['close'],
|
}>()
|
||||||
components: {
|
|
||||||
ThumbnailSlide,
|
|
||||||
},
|
|
||||||
setup(props, { emit }) {
|
|
||||||
const { handleElement } = storeToRefs(useMainStore())
|
|
||||||
const { slides } = storeToRefs(useSlidesStore())
|
|
||||||
|
|
||||||
const type = ref<TypeKey>('web')
|
const { handleElement } = storeToRefs(useMainStore())
|
||||||
const address = ref('')
|
const { slides } = storeToRefs(useSlidesStore())
|
||||||
const slideId = ref('')
|
|
||||||
|
|
||||||
slideId.value = slides.value[0].id
|
const type = ref<TypeKey>('web')
|
||||||
|
const address = ref('')
|
||||||
|
const slideId = ref('')
|
||||||
|
|
||||||
const selectedSlide = computed(() => {
|
slideId.value = slides.value[0].id
|
||||||
|
|
||||||
|
const selectedSlide = computed(() => {
|
||||||
if (!slideId.value) return null
|
if (!slideId.value) return null
|
||||||
|
|
||||||
return slides.value.find(item => item.id === slideId.value) || null
|
return slides.value.find(item => item.id === slideId.value) || null
|
||||||
})
|
})
|
||||||
|
|
||||||
const tabs: TabItem[] = [
|
const tabs: TabItem[] = [
|
||||||
{ key: 'web', label: '网页链接' },
|
{ key: 'web', label: '网页链接' },
|
||||||
{ key: 'slide', label: '幻灯片页面' },
|
{ key: 'slide', label: '幻灯片页面' },
|
||||||
]
|
]
|
||||||
|
|
||||||
const { setLink } = useLink()
|
const { setLink } = useLink()
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
if (handleElement.value?.link) {
|
if (handleElement.value?.link) {
|
||||||
if (handleElement.value.link.type === 'web') address.value = handleElement.value.link.target
|
if (handleElement.value.link.type === 'web') address.value = handleElement.value.link.target
|
||||||
else if (handleElement.value.link.type === 'slide') slideId.value = handleElement.value.link.target
|
else if (handleElement.value.link.type === 'slide') slideId.value = handleElement.value.link.target
|
||||||
|
|
||||||
type.value = handleElement.value.link.type
|
type.value = handleElement.value.link.type
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const close = () => emit('close')
|
const close = () => emit('close')
|
||||||
|
|
||||||
const save = () => {
|
const save = () => {
|
||||||
const link: PPTElementLink = {
|
const link: PPTElementLink = {
|
||||||
type: type.value,
|
type: type.value,
|
||||||
target: type.value === 'web' ? address.value : slideId.value,
|
target: type.value === 'web' ? address.value : slideId.value,
|
||||||
@ -102,20 +99,7 @@ export default defineComponent({
|
|||||||
if (success) close()
|
if (success) close()
|
||||||
else address.value = ''
|
else address.value = ''
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
|
||||||
slides,
|
|
||||||
tabs,
|
|
||||||
type,
|
|
||||||
address,
|
|
||||||
slideId,
|
|
||||||
selectedSlide,
|
|
||||||
close,
|
|
||||||
save,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
})
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
@ -9,12 +9,8 @@
|
|||||||
></div>
|
></div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts" setup>
|
||||||
import { defineComponent } from 'vue'
|
defineProps({
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
name: 'mouse-selection',
|
|
||||||
props: {
|
|
||||||
top: {
|
top: {
|
||||||
type: Number,
|
type: Number,
|
||||||
required: true,
|
required: true,
|
||||||
@ -38,7 +34,6 @@ export default defineComponent({
|
|||||||
return [1, 2, 3, 4].includes(value)
|
return [1, 2, 3, 4].includes(value)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@ -2,13 +2,11 @@
|
|||||||
<div :class="['border-line', type, { 'wide': isWide }]"></div>
|
<div :class="['border-line', type, { 'wide': isWide }]"></div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts" setup>
|
||||||
import { defineComponent, PropType } from 'vue'
|
import { PropType } from 'vue'
|
||||||
import { OperateBorderLines } from '@/types/edit'
|
import { OperateBorderLines } from '@/types/edit'
|
||||||
|
|
||||||
export default defineComponent({
|
defineProps({
|
||||||
name: 'border-line',
|
|
||||||
props: {
|
|
||||||
type: {
|
type: {
|
||||||
type: String as PropType<OperateBorderLines>,
|
type: String as PropType<OperateBorderLines>,
|
||||||
required: true,
|
required: true,
|
||||||
@ -17,7 +15,6 @@ export default defineComponent({
|
|||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false,
|
default: false,
|
||||||
},
|
},
|
||||||
},
|
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@ -28,7 +28,13 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { computed, defineComponent, PropType } from 'vue'
|
export default {
|
||||||
|
inheritAttrs: false,
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { computed, PropType } from 'vue'
|
||||||
import { storeToRefs } from 'pinia'
|
import { storeToRefs } from 'pinia'
|
||||||
import { useMainStore } from '@/store'
|
import { useMainStore } from '@/store'
|
||||||
import { PPTShapeElement, PPTVideoElement, PPTLatexElement, PPTAudioElement } from '@/types/slides'
|
import { PPTShapeElement, PPTVideoElement, PPTLatexElement, PPTAudioElement } from '@/types/slides'
|
||||||
@ -41,15 +47,7 @@ import BorderLine from './BorderLine.vue'
|
|||||||
|
|
||||||
type PPTElement = PPTShapeElement | PPTVideoElement | PPTLatexElement | PPTAudioElement
|
type PPTElement = PPTShapeElement | PPTVideoElement | PPTLatexElement | PPTAudioElement
|
||||||
|
|
||||||
export default defineComponent({
|
const props = defineProps({
|
||||||
name: 'common-element-operate',
|
|
||||||
inheritAttrs: false,
|
|
||||||
components: {
|
|
||||||
RotateHandler,
|
|
||||||
ResizeHandler,
|
|
||||||
BorderLine,
|
|
||||||
},
|
|
||||||
props: {
|
|
||||||
elementInfo: {
|
elementInfo: {
|
||||||
type: Object as PropType<PPTElement>,
|
type: Object as PropType<PPTElement>,
|
||||||
required: true,
|
required: true,
|
||||||
@ -66,22 +64,13 @@ export default defineComponent({
|
|||||||
type: Function as PropType<(e: MouseEvent, element: PPTElement, command: OperateResizeHandlers) => void>,
|
type: Function as PropType<(e: MouseEvent, element: PPTElement, command: OperateResizeHandlers) => void>,
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
},
|
|
||||||
setup(props) {
|
|
||||||
const { canvasScale } = storeToRefs(useMainStore())
|
|
||||||
|
|
||||||
const scaleWidth = computed(() => props.elementInfo.width * canvasScale.value)
|
|
||||||
const scaleHeight = computed(() => props.elementInfo.height * canvasScale.value)
|
|
||||||
const { resizeHandlers, borderLines } = useCommonOperate(scaleWidth, scaleHeight)
|
|
||||||
|
|
||||||
const cannotRotate = computed(() => ['video', 'audio'].includes(props.elementInfo.type))
|
|
||||||
|
|
||||||
return {
|
|
||||||
scaleWidth,
|
|
||||||
resizeHandlers,
|
|
||||||
borderLines,
|
|
||||||
cannotRotate,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const { canvasScale } = storeToRefs(useMainStore())
|
||||||
|
|
||||||
|
const scaleWidth = computed(() => props.elementInfo.width * canvasScale.value)
|
||||||
|
const scaleHeight = computed(() => props.elementInfo.height * canvasScale.value)
|
||||||
|
const { resizeHandlers, borderLines } = useCommonOperate(scaleWidth, scaleHeight)
|
||||||
|
|
||||||
|
const cannotRotate = computed(() => ['video', 'audio'].includes(props.elementInfo.type))
|
||||||
</script>
|
</script>
|
@ -27,7 +27,13 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { computed, defineComponent, PropType } from 'vue'
|
export default {
|
||||||
|
inheritAttrs: false,
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { computed, PropType } from 'vue'
|
||||||
import { storeToRefs } from 'pinia'
|
import { storeToRefs } from 'pinia'
|
||||||
import { useMainStore } from '@/store'
|
import { useMainStore } from '@/store'
|
||||||
import { PPTImageElement } from '@/types/slides'
|
import { PPTImageElement } from '@/types/slides'
|
||||||
@ -38,15 +44,7 @@ import RotateHandler from './RotateHandler.vue'
|
|||||||
import ResizeHandler from './ResizeHandler.vue'
|
import ResizeHandler from './ResizeHandler.vue'
|
||||||
import BorderLine from './BorderLine.vue'
|
import BorderLine from './BorderLine.vue'
|
||||||
|
|
||||||
export default defineComponent({
|
const props = defineProps({
|
||||||
name: 'image-element-operate',
|
|
||||||
inheritAttrs: false,
|
|
||||||
components: {
|
|
||||||
RotateHandler,
|
|
||||||
ResizeHandler,
|
|
||||||
BorderLine,
|
|
||||||
},
|
|
||||||
props: {
|
|
||||||
elementInfo: {
|
elementInfo: {
|
||||||
type: Object as PropType<PPTImageElement>,
|
type: Object as PropType<PPTImageElement>,
|
||||||
required: true,
|
required: true,
|
||||||
@ -63,24 +61,15 @@ export default defineComponent({
|
|||||||
type: Function as PropType<(e: MouseEvent, element: PPTImageElement, command: OperateResizeHandlers) => void>,
|
type: Function as PropType<(e: MouseEvent, element: PPTImageElement, command: OperateResizeHandlers) => void>,
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
},
|
|
||||||
setup(props) {
|
|
||||||
const { canvasScale, clipingImageElementId } = storeToRefs(useMainStore())
|
|
||||||
|
|
||||||
const isCliping = computed(() => clipingImageElementId.value === props.elementInfo.id)
|
|
||||||
|
|
||||||
const scaleWidth = computed(() => props.elementInfo.width * canvasScale.value)
|
|
||||||
const scaleHeight = computed(() => props.elementInfo.height * canvasScale.value)
|
|
||||||
const { resizeHandlers, borderLines } = useCommonOperate(scaleWidth, scaleHeight)
|
|
||||||
|
|
||||||
return {
|
|
||||||
isCliping,
|
|
||||||
scaleWidth,
|
|
||||||
resizeHandlers,
|
|
||||||
borderLines,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const { canvasScale, clipingImageElementId } = storeToRefs(useMainStore())
|
||||||
|
|
||||||
|
const isCliping = computed(() => clipingImageElementId.value === props.elementInfo.id)
|
||||||
|
|
||||||
|
const scaleWidth = computed(() => props.elementInfo.width * canvasScale.value)
|
||||||
|
const scaleHeight = computed(() => props.elementInfo.height * canvasScale.value)
|
||||||
|
const { resizeHandlers, borderLines } = useCommonOperate(scaleWidth, scaleHeight)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
@ -34,7 +34,13 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { computed, defineComponent, PropType } from 'vue'
|
export default {
|
||||||
|
inheritAttrs: false,
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { computed, PropType } from 'vue'
|
||||||
import { storeToRefs } from 'pinia'
|
import { storeToRefs } from 'pinia'
|
||||||
import { useMainStore } from '@/store'
|
import { useMainStore } from '@/store'
|
||||||
import { PPTLineElement } from '@/types/slides'
|
import { PPTLineElement } from '@/types/slides'
|
||||||
@ -42,13 +48,7 @@ import { OperateLineHandlers } from '@/types/edit'
|
|||||||
|
|
||||||
import ResizeHandler from './ResizeHandler.vue'
|
import ResizeHandler from './ResizeHandler.vue'
|
||||||
|
|
||||||
export default defineComponent({
|
const props = defineProps({
|
||||||
name: 'line-element-operate',
|
|
||||||
inheritAttrs: false,
|
|
||||||
components: {
|
|
||||||
ResizeHandler,
|
|
||||||
},
|
|
||||||
props: {
|
|
||||||
elementInfo: {
|
elementInfo: {
|
||||||
type: Object as PropType<PPTLineElement>,
|
type: Object as PropType<PPTLineElement>,
|
||||||
required: true,
|
required: true,
|
||||||
@ -61,14 +61,14 @@ export default defineComponent({
|
|||||||
type: Function as PropType<(e: MouseEvent, element: PPTLineElement, command: OperateLineHandlers) => void>,
|
type: Function as PropType<(e: MouseEvent, element: PPTLineElement, command: OperateLineHandlers) => void>,
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
},
|
})
|
||||||
setup(props) {
|
|
||||||
const { canvasScale } = storeToRefs(useMainStore())
|
|
||||||
|
|
||||||
const svgWidth = computed(() => Math.max(props.elementInfo.start[0], props.elementInfo.end[0]))
|
const { canvasScale } = storeToRefs(useMainStore())
|
||||||
const svgHeight = computed(() => Math.max(props.elementInfo.start[1], props.elementInfo.end[1]))
|
|
||||||
|
|
||||||
const resizeHandlers = computed(() => {
|
const svgWidth = computed(() => Math.max(props.elementInfo.start[0], props.elementInfo.end[0]))
|
||||||
|
const svgHeight = computed(() => Math.max(props.elementInfo.start[1], props.elementInfo.end[1]))
|
||||||
|
|
||||||
|
const resizeHandlers = computed(() => {
|
||||||
const handlers = [
|
const handlers = [
|
||||||
{
|
{
|
||||||
handler: OperateLineHandlers.START,
|
handler: OperateLineHandlers.START,
|
||||||
@ -116,15 +116,6 @@ export default defineComponent({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return handlers
|
return handlers
|
||||||
})
|
|
||||||
|
|
||||||
return {
|
|
||||||
svgWidth,
|
|
||||||
svgHeight,
|
|
||||||
canvasScale,
|
|
||||||
resizeHandlers,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@ -10,16 +10,14 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts" setup>
|
||||||
import { computed, defineComponent, PropType } from 'vue'
|
import { computed, PropType } from 'vue'
|
||||||
import { storeToRefs } from 'pinia'
|
import { storeToRefs } from 'pinia'
|
||||||
import { useMainStore } from '@/store'
|
import { useMainStore } from '@/store'
|
||||||
import { PPTElement, PPTElementLink } from '@/types/slides'
|
import { PPTElement, PPTElementLink } from '@/types/slides'
|
||||||
import useLink from '@/hooks/useLink'
|
import useLink from '@/hooks/useLink'
|
||||||
|
|
||||||
export default defineComponent({
|
const props = defineProps({
|
||||||
name: 'link-handler',
|
|
||||||
props: {
|
|
||||||
elementInfo: {
|
elementInfo: {
|
||||||
type: Object as PropType<PPTElement>,
|
type: Object as PropType<PPTElement>,
|
||||||
required: true,
|
required: true,
|
||||||
@ -32,21 +30,11 @@ export default defineComponent({
|
|||||||
type: Function as PropType<() => void>,
|
type: Function as PropType<() => void>,
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
},
|
|
||||||
setup(props) {
|
|
||||||
const { canvasScale } = storeToRefs(useMainStore())
|
|
||||||
|
|
||||||
const { removeLink } = useLink()
|
|
||||||
|
|
||||||
const height = computed(() => props.elementInfo.type === 'line' ? 0 : props.elementInfo.height)
|
|
||||||
|
|
||||||
return {
|
|
||||||
canvasScale,
|
|
||||||
height,
|
|
||||||
removeLink,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const { canvasScale } = storeToRefs(useMainStore())
|
||||||
|
const { removeLink } = useLink()
|
||||||
|
const height = computed(() => props.elementInfo.type === 'line' ? 0 : props.elementInfo.height)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
@ -20,8 +20,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts" setup>
|
||||||
import { computed, defineComponent, ref, PropType, watchEffect } from 'vue'
|
import { computed, ref, PropType, watchEffect } from 'vue'
|
||||||
import { storeToRefs } from 'pinia'
|
import { storeToRefs } from 'pinia'
|
||||||
import { useMainStore } from '@/store'
|
import { useMainStore } from '@/store'
|
||||||
import { PPTElement } from '@/types/slides'
|
import { PPTElement } from '@/types/slides'
|
||||||
@ -32,13 +32,7 @@ import useCommonOperate from '../hooks/useCommonOperate'
|
|||||||
import ResizeHandler from './ResizeHandler.vue'
|
import ResizeHandler from './ResizeHandler.vue'
|
||||||
import BorderLine from './BorderLine.vue'
|
import BorderLine from './BorderLine.vue'
|
||||||
|
|
||||||
export default defineComponent({
|
const props = defineProps({
|
||||||
name: 'multi-select-operate',
|
|
||||||
components: {
|
|
||||||
ResizeHandler,
|
|
||||||
BorderLine,
|
|
||||||
},
|
|
||||||
props: {
|
|
||||||
elementList: {
|
elementList: {
|
||||||
type: Array as PropType<PPTElement[]>,
|
type: Array as PropType<PPTElement[]>,
|
||||||
required: true,
|
required: true,
|
||||||
@ -47,33 +41,33 @@ export default defineComponent({
|
|||||||
type: Function as PropType<(e: MouseEvent, range: MultiSelectRange, command: OperateResizeHandlers) => void>,
|
type: Function as PropType<(e: MouseEvent, range: MultiSelectRange, command: OperateResizeHandlers) => void>,
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
},
|
})
|
||||||
setup(props) {
|
|
||||||
const { activeElementIdList, canvasScale } = storeToRefs(useMainStore())
|
|
||||||
|
|
||||||
const localActiveElementList = computed(() => props.elementList.filter(el => activeElementIdList.value.includes(el.id)))
|
const { activeElementIdList, canvasScale } = storeToRefs(useMainStore())
|
||||||
|
|
||||||
const range = ref({
|
const localActiveElementList = computed(() => props.elementList.filter(el => activeElementIdList.value.includes(el.id)))
|
||||||
|
|
||||||
|
const range = ref({
|
||||||
minX: 0,
|
minX: 0,
|
||||||
maxX: 0,
|
maxX: 0,
|
||||||
minY: 0,
|
minY: 0,
|
||||||
maxY: 0,
|
maxY: 0,
|
||||||
})
|
})
|
||||||
|
|
||||||
// 根据多选元素整体在画布中的范围,计算边框线和缩放点的位置信息
|
// 根据多选元素整体在画布中的范围,计算边框线和缩放点的位置信息
|
||||||
const width = computed(() => (range.value.maxX - range.value.minX) * canvasScale.value)
|
const width = computed(() => (range.value.maxX - range.value.minX) * canvasScale.value)
|
||||||
const height = computed(() => (range.value.maxY - range.value.minY) * canvasScale.value)
|
const height = computed(() => (range.value.maxY - range.value.minY) * canvasScale.value)
|
||||||
const { resizeHandlers, borderLines } = useCommonOperate(width, height)
|
const { resizeHandlers, borderLines } = useCommonOperate(width, height)
|
||||||
|
|
||||||
// 计算多选元素整体在画布中的范围
|
// 计算多选元素整体在画布中的范围
|
||||||
const setRange = () => {
|
const setRange = () => {
|
||||||
const { minX, maxX, minY, maxY } = getElementListRange(localActiveElementList.value)
|
const { minX, maxX, minY, maxY } = getElementListRange(localActiveElementList.value)
|
||||||
range.value = { minX, maxX, minY, maxY }
|
range.value = { minX, maxX, minY, maxY }
|
||||||
}
|
}
|
||||||
watchEffect(setRange)
|
watchEffect(setRange)
|
||||||
|
|
||||||
// 禁用多选状态下缩放:仅未旋转的图片和形状可以在多选状态下缩放
|
// 禁用多选状态下缩放:仅未旋转的图片和形状可以在多选状态下缩放
|
||||||
const disableResize = computed(() => {
|
const disableResize = computed(() => {
|
||||||
return localActiveElementList.value.some(item => {
|
return localActiveElementList.value.some(item => {
|
||||||
if (
|
if (
|
||||||
(item.type === 'image' || item.type === 'shape') &&
|
(item.type === 'image' || item.type === 'shape') &&
|
||||||
@ -81,16 +75,6 @@ export default defineComponent({
|
|||||||
) return false
|
) return false
|
||||||
return true
|
return true
|
||||||
})
|
})
|
||||||
})
|
|
||||||
|
|
||||||
return {
|
|
||||||
range,
|
|
||||||
canvasScale,
|
|
||||||
borderLines,
|
|
||||||
disableResize,
|
|
||||||
resizeHandlers,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@ -2,13 +2,11 @@
|
|||||||
<div :class="['resize-handler', rotateClassName, type]"></div>
|
<div :class="['resize-handler', rotateClassName, type]"></div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts" setup>
|
||||||
import { computed, defineComponent, PropType } from 'vue'
|
import { computed, PropType } from 'vue'
|
||||||
import { OperateResizeHandlers } from '@/types/edit'
|
import { OperateResizeHandlers } from '@/types/edit'
|
||||||
|
|
||||||
export default defineComponent({
|
const props = defineProps({
|
||||||
name: 'resize-handler',
|
|
||||||
props: {
|
|
||||||
type: {
|
type: {
|
||||||
type: String as PropType<OperateResizeHandlers>,
|
type: String as PropType<OperateResizeHandlers>,
|
||||||
default: '',
|
default: '',
|
||||||
@ -17,9 +15,9 @@ export default defineComponent({
|
|||||||
type: Number,
|
type: Number,
|
||||||
default: 0,
|
default: 0,
|
||||||
},
|
},
|
||||||
},
|
})
|
||||||
setup(props) {
|
|
||||||
const rotateClassName = computed(() => {
|
const rotateClassName = computed(() => {
|
||||||
const prefix = 'rotate-'
|
const prefix = 'rotate-'
|
||||||
const rotate = props.rotate
|
const rotate = props.rotate
|
||||||
if (rotate > -22.5 && rotate <= 22.5) return prefix + 0
|
if (rotate > -22.5 && rotate <= 22.5) return prefix + 0
|
||||||
@ -31,12 +29,6 @@ export default defineComponent({
|
|||||||
else if (rotate > -112.5 && rotate <= -67.5) return prefix + 90
|
else if (rotate > -112.5 && rotate <= -67.5) return prefix + 90
|
||||||
else if (rotate > -67.5 && rotate <= -22.5) return prefix + 135
|
else if (rotate > -67.5 && rotate <= -22.5) return prefix + 135
|
||||||
return prefix + 0
|
return prefix + 0
|
||||||
})
|
|
||||||
|
|
||||||
return {
|
|
||||||
rotateClassName,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@ -2,10 +2,8 @@
|
|||||||
<div class="rotate-handler"></div>
|
<div class="rotate-handler"></div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts" setup>
|
||||||
export default {
|
|
||||||
name: 'rotate-handler',
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
@ -27,7 +27,13 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { computed, defineComponent, PropType } from 'vue'
|
export default {
|
||||||
|
inheritAttrs: false,
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { computed, PropType } from 'vue'
|
||||||
import { storeToRefs } from 'pinia'
|
import { storeToRefs } from 'pinia'
|
||||||
import { useMainStore } from '@/store'
|
import { useMainStore } from '@/store'
|
||||||
import { PPTShapeElement } from '@/types/slides'
|
import { PPTShapeElement } from '@/types/slides'
|
||||||
@ -38,15 +44,7 @@ import RotateHandler from './RotateHandler.vue'
|
|||||||
import ResizeHandler from './ResizeHandler.vue'
|
import ResizeHandler from './ResizeHandler.vue'
|
||||||
import BorderLine from './BorderLine.vue'
|
import BorderLine from './BorderLine.vue'
|
||||||
|
|
||||||
export default defineComponent({
|
const props = defineProps({
|
||||||
name: 'shape-element-operate',
|
|
||||||
inheritAttrs: false,
|
|
||||||
components: {
|
|
||||||
RotateHandler,
|
|
||||||
ResizeHandler,
|
|
||||||
BorderLine,
|
|
||||||
},
|
|
||||||
props: {
|
|
||||||
elementInfo: {
|
elementInfo: {
|
||||||
type: Object as PropType<PPTShapeElement>,
|
type: Object as PropType<PPTShapeElement>,
|
||||||
required: true,
|
required: true,
|
||||||
@ -63,19 +61,11 @@ export default defineComponent({
|
|||||||
type: Function as PropType<(e: MouseEvent, element: PPTShapeElement, command: OperateResizeHandlers) => void>,
|
type: Function as PropType<(e: MouseEvent, element: PPTShapeElement, command: OperateResizeHandlers) => void>,
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
},
|
|
||||||
setup(props) {
|
|
||||||
const { canvasScale } = storeToRefs(useMainStore())
|
|
||||||
|
|
||||||
const scaleWidth = computed(() => props.elementInfo.width * canvasScale.value)
|
|
||||||
const scaleHeight = computed(() => props.elementInfo.height * canvasScale.value)
|
|
||||||
const { resizeHandlers, borderLines } = useCommonOperate(scaleWidth, scaleHeight)
|
|
||||||
|
|
||||||
return {
|
|
||||||
scaleWidth,
|
|
||||||
resizeHandlers,
|
|
||||||
borderLines,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const { canvasScale } = storeToRefs(useMainStore())
|
||||||
|
|
||||||
|
const scaleWidth = computed(() => props.elementInfo.width * canvasScale.value)
|
||||||
|
const scaleHeight = computed(() => props.elementInfo.height * canvasScale.value)
|
||||||
|
const { resizeHandlers, borderLines } = useCommonOperate(scaleWidth, scaleHeight)
|
||||||
</script>
|
</script>
|
@ -27,7 +27,13 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { computed, defineComponent, PropType } from 'vue'
|
export default {
|
||||||
|
inheritAttrs: false,
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { computed, PropType } from 'vue'
|
||||||
import { storeToRefs } from 'pinia'
|
import { storeToRefs } from 'pinia'
|
||||||
import { useMainStore } from '@/store'
|
import { useMainStore } from '@/store'
|
||||||
import { PPTTableElement } from '@/types/slides'
|
import { PPTTableElement } from '@/types/slides'
|
||||||
@ -38,15 +44,7 @@ import RotateHandler from './RotateHandler.vue'
|
|||||||
import ResizeHandler from './ResizeHandler.vue'
|
import ResizeHandler from './ResizeHandler.vue'
|
||||||
import BorderLine from './BorderLine.vue'
|
import BorderLine from './BorderLine.vue'
|
||||||
|
|
||||||
export default defineComponent({
|
const props = defineProps({
|
||||||
name: 'table-element-operate',
|
|
||||||
inheritAttrs: false,
|
|
||||||
components: {
|
|
||||||
RotateHandler,
|
|
||||||
ResizeHandler,
|
|
||||||
BorderLine,
|
|
||||||
},
|
|
||||||
props: {
|
|
||||||
elementInfo: {
|
elementInfo: {
|
||||||
type: Object as PropType<PPTTableElement>,
|
type: Object as PropType<PPTTableElement>,
|
||||||
required: true,
|
required: true,
|
||||||
@ -63,22 +61,14 @@ export default defineComponent({
|
|||||||
type: Function as PropType<(e: MouseEvent, element: PPTTableElement, command: OperateResizeHandlers) => void>,
|
type: Function as PropType<(e: MouseEvent, element: PPTTableElement, command: OperateResizeHandlers) => void>,
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
},
|
|
||||||
setup(props) {
|
|
||||||
const { canvasScale } = storeToRefs(useMainStore())
|
|
||||||
|
|
||||||
const outlineWidth = computed(() => props.elementInfo.outline.width || 1)
|
|
||||||
|
|
||||||
const scaleWidth = computed(() => (props.elementInfo.width + outlineWidth.value) * canvasScale.value)
|
|
||||||
const scaleHeight = computed(() => props.elementInfo.height * canvasScale.value)
|
|
||||||
|
|
||||||
const { textElementResizeHandlers, borderLines } = useCommonOperate(scaleWidth, scaleHeight)
|
|
||||||
|
|
||||||
return {
|
|
||||||
scaleWidth,
|
|
||||||
textElementResizeHandlers,
|
|
||||||
borderLines,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const { canvasScale } = storeToRefs(useMainStore())
|
||||||
|
|
||||||
|
const outlineWidth = computed(() => props.elementInfo.outline.width || 1)
|
||||||
|
|
||||||
|
const scaleWidth = computed(() => (props.elementInfo.width + outlineWidth.value) * canvasScale.value)
|
||||||
|
const scaleHeight = computed(() => props.elementInfo.height * canvasScale.value)
|
||||||
|
|
||||||
|
const { textElementResizeHandlers, borderLines } = useCommonOperate(scaleWidth, scaleHeight)
|
||||||
</script>
|
</script>
|
@ -27,7 +27,13 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { computed, defineComponent, PropType } from 'vue'
|
export default {
|
||||||
|
inheritAttrs: false,
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { computed, PropType } from 'vue'
|
||||||
import { storeToRefs } from 'pinia'
|
import { storeToRefs } from 'pinia'
|
||||||
import { useMainStore } from '@/store'
|
import { useMainStore } from '@/store'
|
||||||
import { PPTTextElement } from '@/types/slides'
|
import { PPTTextElement } from '@/types/slides'
|
||||||
@ -38,15 +44,7 @@ import RotateHandler from './RotateHandler.vue'
|
|||||||
import ResizeHandler from './ResizeHandler.vue'
|
import ResizeHandler from './ResizeHandler.vue'
|
||||||
import BorderLine from './BorderLine.vue'
|
import BorderLine from './BorderLine.vue'
|
||||||
|
|
||||||
export default defineComponent({
|
const props = defineProps({
|
||||||
name: 'text-element-operate',
|
|
||||||
inheritAttrs: false,
|
|
||||||
components: {
|
|
||||||
RotateHandler,
|
|
||||||
ResizeHandler,
|
|
||||||
BorderLine,
|
|
||||||
},
|
|
||||||
props: {
|
|
||||||
elementInfo: {
|
elementInfo: {
|
||||||
type: Object as PropType<PPTTextElement>,
|
type: Object as PropType<PPTTextElement>,
|
||||||
required: true,
|
required: true,
|
||||||
@ -63,20 +61,12 @@ export default defineComponent({
|
|||||||
type: Function as PropType<(e: MouseEvent, element: PPTTextElement, command: OperateResizeHandlers) => void>,
|
type: Function as PropType<(e: MouseEvent, element: PPTTextElement, command: OperateResizeHandlers) => void>,
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
},
|
|
||||||
setup(props) {
|
|
||||||
const { canvasScale } = storeToRefs(useMainStore())
|
|
||||||
|
|
||||||
const scaleWidth = computed(() => props.elementInfo.width * canvasScale.value)
|
|
||||||
const scaleHeight = computed(() => props.elementInfo.height * canvasScale.value)
|
|
||||||
|
|
||||||
const { textElementResizeHandlers, borderLines } = useCommonOperate(scaleWidth, scaleHeight)
|
|
||||||
|
|
||||||
return {
|
|
||||||
scaleWidth,
|
|
||||||
textElementResizeHandlers,
|
|
||||||
borderLines,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const { canvasScale } = storeToRefs(useMainStore())
|
||||||
|
|
||||||
|
const scaleWidth = computed(() => props.elementInfo.width * canvasScale.value)
|
||||||
|
const scaleHeight = computed(() => props.elementInfo.height * canvasScale.value)
|
||||||
|
|
||||||
|
const { textElementResizeHandlers, borderLines } = useCommonOperate(scaleWidth, scaleHeight)
|
||||||
</script>
|
</script>
|
@ -36,8 +36,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts" setup>
|
||||||
import { defineComponent, PropType, computed } from 'vue'
|
import { PropType, computed } from 'vue'
|
||||||
import { storeToRefs } from 'pinia'
|
import { storeToRefs } from 'pinia'
|
||||||
import { useMainStore, useSlidesStore } from '@/store'
|
import { useMainStore, useSlidesStore } from '@/store'
|
||||||
import { ElementTypes, PPTElement, PPTLineElement, PPTVideoElement, PPTAudioElement } from '@/types/slides'
|
import { ElementTypes, PPTElement, PPTLineElement, PPTVideoElement, PPTAudioElement } from '@/types/slides'
|
||||||
@ -51,12 +51,7 @@ import TableElementOperate from './TableElementOperate.vue'
|
|||||||
import CommonElementOperate from './CommonElementOperate.vue'
|
import CommonElementOperate from './CommonElementOperate.vue'
|
||||||
import LinkHandler from './LinkHandler.vue'
|
import LinkHandler from './LinkHandler.vue'
|
||||||
|
|
||||||
export default defineComponent({
|
const props = defineProps({
|
||||||
name: 'operate',
|
|
||||||
components: {
|
|
||||||
LinkHandler,
|
|
||||||
},
|
|
||||||
props: {
|
|
||||||
elementInfo: {
|
elementInfo: {
|
||||||
type: Object as PropType<PPTElement>,
|
type: Object as PropType<PPTElement>,
|
||||||
required: true,
|
required: true,
|
||||||
@ -93,12 +88,12 @@ export default defineComponent({
|
|||||||
type: Function as PropType<() => void>,
|
type: Function as PropType<() => void>,
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
},
|
})
|
||||||
setup(props) {
|
|
||||||
const { canvasScale, toolbarState } = storeToRefs(useMainStore())
|
|
||||||
const { formatedAnimations } = storeToRefs(useSlidesStore())
|
|
||||||
|
|
||||||
const currentOperateComponent = computed(() => {
|
const { canvasScale, toolbarState } = storeToRefs(useMainStore())
|
||||||
|
const { formatedAnimations } = storeToRefs(useSlidesStore())
|
||||||
|
|
||||||
|
const currentOperateComponent = computed(() => {
|
||||||
const elementTypeMap = {
|
const elementTypeMap = {
|
||||||
[ElementTypes.IMAGE]: ImageElementOperate,
|
[ElementTypes.IMAGE]: ImageElementOperate,
|
||||||
[ElementTypes.TEXT]: TextElementOperate,
|
[ElementTypes.TEXT]: TextElementOperate,
|
||||||
@ -111,30 +106,19 @@ export default defineComponent({
|
|||||||
[ElementTypes.AUDIO]: CommonElementOperate,
|
[ElementTypes.AUDIO]: CommonElementOperate,
|
||||||
}
|
}
|
||||||
return elementTypeMap[props.elementInfo.type] || null
|
return elementTypeMap[props.elementInfo.type] || null
|
||||||
})
|
})
|
||||||
|
|
||||||
const elementIndexListInAnimation = computed(() => {
|
const elementIndexListInAnimation = computed(() => {
|
||||||
const indexList = []
|
const indexList = []
|
||||||
for (let i = 0; i < formatedAnimations.value.length; i++) {
|
for (let i = 0; i < formatedAnimations.value.length; i++) {
|
||||||
const elIds = formatedAnimations.value[i].animations.map(item => item.elId)
|
const elIds = formatedAnimations.value[i].animations.map(item => item.elId)
|
||||||
if (elIds.includes(props.elementInfo.id)) indexList.push(i)
|
if (elIds.includes(props.elementInfo.id)) indexList.push(i)
|
||||||
}
|
}
|
||||||
return indexList
|
return indexList
|
||||||
})
|
|
||||||
|
|
||||||
const rotate = computed(() => 'rotate' in props.elementInfo ? props.elementInfo.rotate : 0)
|
|
||||||
const height = computed(() => 'height' in props.elementInfo ? props.elementInfo.height : 0)
|
|
||||||
|
|
||||||
return {
|
|
||||||
currentOperateComponent,
|
|
||||||
canvasScale,
|
|
||||||
toolbarState,
|
|
||||||
elementIndexListInAnimation,
|
|
||||||
rotate,
|
|
||||||
height,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const rotate = computed(() => 'rotate' in props.elementInfo ? props.elementInfo.rotate : 0)
|
||||||
|
const height = computed(() => 'height' in props.elementInfo ? props.elementInfo.height : 0)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
@ -36,8 +36,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts" setup>
|
||||||
import { computed, defineComponent, PropType } from 'vue'
|
import { computed, PropType } from 'vue'
|
||||||
import { storeToRefs } from 'pinia'
|
import { storeToRefs } from 'pinia'
|
||||||
import { useMainStore } from '@/store'
|
import { useMainStore } from '@/store'
|
||||||
|
|
||||||
@ -48,25 +48,17 @@ interface ViewportStyles {
|
|||||||
height: number
|
height: number
|
||||||
}
|
}
|
||||||
|
|
||||||
export default defineComponent({
|
const props = defineProps({
|
||||||
props: {
|
|
||||||
viewportStyles: {
|
viewportStyles: {
|
||||||
type: Object as PropType<ViewportStyles>,
|
type: Object as PropType<ViewportStyles>,
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
},
|
})
|
||||||
setup(props) {
|
|
||||||
const { canvasScale } = storeToRefs(useMainStore())
|
|
||||||
|
|
||||||
const markerSize = computed(() => {
|
const { canvasScale } = storeToRefs(useMainStore())
|
||||||
|
|
||||||
|
const markerSize = computed(() => {
|
||||||
return props.viewportStyles.width * canvasScale.value / 10
|
return props.viewportStyles.width * canvasScale.value / 10
|
||||||
})
|
|
||||||
|
|
||||||
return {
|
|
||||||
canvasScale,
|
|
||||||
markerSize,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@ -7,32 +7,19 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts" setup>
|
||||||
import { computed, defineComponent } from 'vue'
|
import { computed } from 'vue'
|
||||||
import { storeToRefs } from 'pinia'
|
import { storeToRefs } from 'pinia'
|
||||||
import { useMainStore, useSlidesStore } from '@/store'
|
import { useMainStore, useSlidesStore } from '@/store'
|
||||||
import { SlideBackground } from '@/types/slides'
|
import { SlideBackground } from '@/types/slides'
|
||||||
import GridLines from './GridLines.vue'
|
import GridLines from './GridLines.vue'
|
||||||
import useSlideBackgroundStyle from '@/hooks/useSlideBackgroundStyle'
|
import useSlideBackgroundStyle from '@/hooks/useSlideBackgroundStyle'
|
||||||
|
|
||||||
export default defineComponent({
|
const { showGridLines } = storeToRefs(useMainStore())
|
||||||
name: 'viewport-background',
|
const { currentSlide } = storeToRefs(useSlidesStore())
|
||||||
components: {
|
const background = computed<SlideBackground | undefined>(() => currentSlide.value?.background)
|
||||||
GridLines,
|
|
||||||
},
|
|
||||||
setup() {
|
|
||||||
const { showGridLines } = storeToRefs(useMainStore())
|
|
||||||
const { currentSlide } = storeToRefs(useSlidesStore())
|
|
||||||
const background = computed<SlideBackground | undefined>(() => currentSlide.value?.background)
|
|
||||||
|
|
||||||
const { backgroundStyle } = useSlideBackgroundStyle(background)
|
const { backgroundStyle } = useSlideBackgroundStyle(background)
|
||||||
|
|
||||||
return {
|
|
||||||
showGridLines,
|
|
||||||
backgroundStyle,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
})
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
@ -91,8 +91,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts" setup>
|
||||||
import { defineComponent, onMounted, provide, ref, watch, watchEffect } from 'vue'
|
import { onMounted, provide, ref, watch, watchEffect } from 'vue'
|
||||||
import { throttle } from 'lodash'
|
import { throttle } from 'lodash'
|
||||||
import { storeToRefs } from 'pinia'
|
import { storeToRefs } from 'pinia'
|
||||||
import { useMainStore, useSlidesStore, useKeyboardStore } from '@/store'
|
import { useMainStore, useSlidesStore, useKeyboardStore } from '@/store'
|
||||||
@ -130,22 +130,8 @@ import MultiSelectOperate from './Operate/MultiSelectOperate.vue'
|
|||||||
import Operate from './Operate/index.vue'
|
import Operate from './Operate/index.vue'
|
||||||
import LinkDialog from './LinkDialog.vue'
|
import LinkDialog from './LinkDialog.vue'
|
||||||
|
|
||||||
export default defineComponent({
|
const mainStore = useMainStore()
|
||||||
name: 'editor-canvas',
|
const {
|
||||||
components: {
|
|
||||||
EditableElement,
|
|
||||||
MouseSelection,
|
|
||||||
ViewportBackground,
|
|
||||||
AlignmentLine,
|
|
||||||
Ruler,
|
|
||||||
ElementCreateSelection,
|
|
||||||
MultiSelectOperate,
|
|
||||||
Operate,
|
|
||||||
LinkDialog,
|
|
||||||
},
|
|
||||||
setup() {
|
|
||||||
const mainStore = useMainStore()
|
|
||||||
const {
|
|
||||||
activeElementIdList,
|
activeElementIdList,
|
||||||
activeGroupElementId,
|
activeGroupElementId,
|
||||||
handleElementId,
|
handleElementId,
|
||||||
@ -154,53 +140,53 @@ export default defineComponent({
|
|||||||
showRuler,
|
showRuler,
|
||||||
creatingElement,
|
creatingElement,
|
||||||
canvasScale,
|
canvasScale,
|
||||||
} = storeToRefs(mainStore)
|
} = storeToRefs(mainStore)
|
||||||
const { currentSlide } = storeToRefs(useSlidesStore())
|
const { currentSlide } = storeToRefs(useSlidesStore())
|
||||||
const { ctrlKeyState, spaceKeyState } = storeToRefs(useKeyboardStore())
|
const { ctrlKeyState, spaceKeyState } = storeToRefs(useKeyboardStore())
|
||||||
|
|
||||||
const viewportRef = ref<HTMLElement>()
|
const viewportRef = ref<HTMLElement>()
|
||||||
const alignmentLines = ref<AlignmentLineProps[]>([])
|
const alignmentLines = ref<AlignmentLineProps[]>([])
|
||||||
|
|
||||||
const linkDialogVisible = ref(false)
|
const linkDialogVisible = ref(false)
|
||||||
const openLinkDialog = () => linkDialogVisible.value = true
|
const openLinkDialog = () => linkDialogVisible.value = true
|
||||||
|
|
||||||
watch(handleElementId, () => {
|
watch(handleElementId, () => {
|
||||||
mainStore.setActiveGroupElementId('')
|
mainStore.setActiveGroupElementId('')
|
||||||
})
|
})
|
||||||
|
|
||||||
const elementList = ref<PPTElement[]>([])
|
const elementList = ref<PPTElement[]>([])
|
||||||
const setLocalElementList = () => {
|
const setLocalElementList = () => {
|
||||||
elementList.value = currentSlide.value ? JSON.parse(JSON.stringify(currentSlide.value.elements)) : []
|
elementList.value = currentSlide.value ? JSON.parse(JSON.stringify(currentSlide.value.elements)) : []
|
||||||
}
|
}
|
||||||
watchEffect(setLocalElementList)
|
watchEffect(setLocalElementList)
|
||||||
|
|
||||||
const canvasRef = ref<HTMLElement>()
|
const canvasRef = ref<HTMLElement>()
|
||||||
const { dragViewport, viewportStyles } = useViewportSize(canvasRef)
|
const { dragViewport, viewportStyles } = useViewportSize(canvasRef)
|
||||||
|
|
||||||
useDropImageOrText(canvasRef)
|
useDropImageOrText(canvasRef)
|
||||||
|
|
||||||
const { mouseSelection, mouseSelectionVisible, mouseSelectionQuadrant, updateMouseSelection } = useMouseSelection(elementList, viewportRef)
|
const { mouseSelection, mouseSelectionVisible, mouseSelectionQuadrant, updateMouseSelection } = useMouseSelection(elementList, viewportRef)
|
||||||
|
|
||||||
const { dragElement } = useDragElement(elementList, alignmentLines, canvasScale)
|
const { dragElement } = useDragElement(elementList, alignmentLines, canvasScale)
|
||||||
const { dragLineElement } = useDragLineElement(elementList)
|
const { dragLineElement } = useDragLineElement(elementList)
|
||||||
const { selectElement } = useSelectElement(elementList, dragElement)
|
const { selectElement } = useSelectElement(elementList, dragElement)
|
||||||
const { scaleElement, scaleMultiElement } = useScaleElement(elementList, alignmentLines, canvasScale)
|
const { scaleElement, scaleMultiElement } = useScaleElement(elementList, alignmentLines, canvasScale)
|
||||||
const { rotateElement } = useRotateElement(elementList, viewportRef)
|
const { rotateElement } = useRotateElement(elementList, viewportRef)
|
||||||
|
|
||||||
const { selectAllElement } = useSelectAllElement()
|
const { selectAllElement } = useSelectAllElement()
|
||||||
const { deleteAllElements } = useDeleteElement()
|
const { deleteAllElements } = useDeleteElement()
|
||||||
const { pasteElement } = useCopyAndPasteElement()
|
const { pasteElement } = useCopyAndPasteElement()
|
||||||
const { enterScreeningFromStart } = useScreening()
|
const { enterScreeningFromStart } = useScreening()
|
||||||
const { updateSlideIndex } = useSlideHandler()
|
const { updateSlideIndex } = useSlideHandler()
|
||||||
|
|
||||||
// 组件渲染时,如果存在元素焦点,需要清除
|
// 组件渲染时,如果存在元素焦点,需要清除
|
||||||
// 这种情况存在于:有焦点元素的情况下进入了放映模式,再退出时,需要清除原先的焦点(因为可能已经切换了页面)
|
// 这种情况存在于:有焦点元素的情况下进入了放映模式,再退出时,需要清除原先的焦点(因为可能已经切换了页面)
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
if (activeElementIdList.value.length) mainStore.setActiveElementIdList([])
|
if (activeElementIdList.value.length) mainStore.setActiveElementIdList([])
|
||||||
})
|
})
|
||||||
|
|
||||||
// 点击画布的空白区域:清空焦点元素、设置画布焦点、清除文字选区
|
// 点击画布的空白区域:清空焦点元素、设置画布焦点、清除文字选区
|
||||||
const handleClickBlankArea = (e: MouseEvent) => {
|
const handleClickBlankArea = (e: MouseEvent) => {
|
||||||
mainStore.setActiveElementIdList([])
|
mainStore.setActiveElementIdList([])
|
||||||
|
|
||||||
if (!spaceKeyState.value) updateMouseSelection(e)
|
if (!spaceKeyState.value) updateMouseSelection(e)
|
||||||
@ -208,19 +194,19 @@ export default defineComponent({
|
|||||||
|
|
||||||
if (!editorAreaFocus.value) mainStore.setEditorareaFocus(true)
|
if (!editorAreaFocus.value) mainStore.setEditorareaFocus(true)
|
||||||
removeAllRanges()
|
removeAllRanges()
|
||||||
}
|
}
|
||||||
|
|
||||||
// 移除画布编辑区域焦点
|
// 移除画布编辑区域焦点
|
||||||
const removeEditorAreaFocus = () => {
|
const removeEditorAreaFocus = () => {
|
||||||
if (editorAreaFocus.value) mainStore.setEditorareaFocus(false)
|
if (editorAreaFocus.value) mainStore.setEditorareaFocus(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 滚动鼠标
|
// 滚动鼠标
|
||||||
const { scaleCanvas } = useScaleCanvas()
|
const { scaleCanvas } = useScaleCanvas()
|
||||||
const throttleScaleCanvas = throttle(scaleCanvas, 100, { leading: true, trailing: false })
|
const throttleScaleCanvas = throttle(scaleCanvas, 100, { leading: true, trailing: false })
|
||||||
const throttleUpdateSlideIndex = throttle(updateSlideIndex, 300, { leading: true, trailing: false })
|
const throttleUpdateSlideIndex = throttle(updateSlideIndex, 300, { leading: true, trailing: false })
|
||||||
|
|
||||||
const handleMousewheelCanvas = (e: WheelEvent) => {
|
const handleMousewheelCanvas = (e: WheelEvent) => {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
|
|
||||||
// 按住Ctrl键时:缩放画布
|
// 按住Ctrl键时:缩放画布
|
||||||
@ -233,22 +219,22 @@ export default defineComponent({
|
|||||||
if (e.deltaY > 0) throttleUpdateSlideIndex(KEYS.DOWN)
|
if (e.deltaY > 0) throttleUpdateSlideIndex(KEYS.DOWN)
|
||||||
else if (e.deltaY < 0) throttleUpdateSlideIndex(KEYS.UP)
|
else if (e.deltaY < 0) throttleUpdateSlideIndex(KEYS.UP)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 开关网格线
|
// 开关网格线
|
||||||
const toggleGridLines = () => {
|
const toggleGridLines = () => {
|
||||||
mainStore.setGridLinesState(!showGridLines.value)
|
mainStore.setGridLinesState(!showGridLines.value)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 开关标尺
|
// 开关标尺
|
||||||
const toggleRuler = () => {
|
const toggleRuler = () => {
|
||||||
mainStore.setRulerState(!showRuler.value)
|
mainStore.setRulerState(!showRuler.value)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 在鼠标绘制的范围插入元素
|
// 在鼠标绘制的范围插入元素
|
||||||
const { insertElementFromCreateSelection } = useInsertFromCreateSelection(viewportRef)
|
const { insertElementFromCreateSelection } = useInsertFromCreateSelection(viewportRef)
|
||||||
|
|
||||||
const contextmenus = (): ContextmenuItem[] => {
|
const contextmenus = (): ContextmenuItem[] => {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
text: '粘贴',
|
text: '粘贴',
|
||||||
@ -281,41 +267,9 @@ export default defineComponent({
|
|||||||
handler: enterScreeningFromStart,
|
handler: enterScreeningFromStart,
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
provide(injectKeySlideScale, canvasScale)
|
provide(injectKeySlideScale, canvasScale)
|
||||||
|
|
||||||
return {
|
|
||||||
elementList,
|
|
||||||
activeElementIdList,
|
|
||||||
handleElementId,
|
|
||||||
activeGroupElementId,
|
|
||||||
canvasRef,
|
|
||||||
viewportRef,
|
|
||||||
viewportStyles,
|
|
||||||
canvasScale,
|
|
||||||
mouseSelection,
|
|
||||||
mouseSelectionVisible,
|
|
||||||
mouseSelectionQuadrant,
|
|
||||||
creatingElement,
|
|
||||||
alignmentLines,
|
|
||||||
linkDialogVisible,
|
|
||||||
spaceKeyState,
|
|
||||||
showRuler,
|
|
||||||
openLinkDialog,
|
|
||||||
handleClickBlankArea,
|
|
||||||
removeEditorAreaFocus,
|
|
||||||
insertElementFromCreateSelection,
|
|
||||||
selectElement,
|
|
||||||
rotateElement,
|
|
||||||
scaleElement,
|
|
||||||
dragLineElement,
|
|
||||||
scaleMultiElement,
|
|
||||||
handleMousewheelCanvas,
|
|
||||||
contextmenus,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
})
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
@ -14,26 +14,18 @@
|
|||||||
</ul>
|
</ul>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts" setup>
|
||||||
import { PresetChartType } from '@/types/slides'
|
import { PresetChartType } from '@/types/slides'
|
||||||
import { defineComponent } from 'vue'
|
|
||||||
|
|
||||||
export default defineComponent({
|
const emit = defineEmits<{
|
||||||
name: 'chart-pool',
|
(event: 'select', payload: PresetChartType): void
|
||||||
emits: ['select'],
|
}>()
|
||||||
setup(props, { emit }) {
|
|
||||||
const chartList: PresetChartType[] = ['bar', 'horizontalBar', 'line', 'area', 'scatter', 'pie', 'ring']
|
|
||||||
|
|
||||||
const selectChart = (chart: PresetChartType) => {
|
const chartList: PresetChartType[] = ['bar', 'horizontalBar', 'line', 'area', 'scatter', 'pie', 'ring']
|
||||||
|
|
||||||
|
const selectChart = (chart: PresetChartType) => {
|
||||||
emit('select', chart)
|
emit('select', chart)
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
|
||||||
chartList,
|
|
||||||
selectChart,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
})
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="line-pool">
|
<div class="line-pool">
|
||||||
<div class="category" v-for="(item, i) in lineList" :key="item.type">
|
<div class="category" v-for="(item, i) in LINE_LIST" :key="item.type">
|
||||||
<div class="category-name">{{item.type}}</div>
|
<div class="category-name">{{item.type}}</div>
|
||||||
<div class="line-list">
|
<div class="line-list">
|
||||||
<div class="line-item" v-for="(line, j) in item.children" :key="j">
|
<div class="line-item" v-for="(line, j) in item.children" :key="j">
|
||||||
@ -48,31 +48,18 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts" setup>
|
||||||
import { defineComponent } from 'vue'
|
|
||||||
import { LINE_LIST, LinePoolItem } from '@/configs/lines'
|
import { LINE_LIST, LinePoolItem } from '@/configs/lines'
|
||||||
|
|
||||||
import LinePointMarker from '@/views/components/element/LineElement/LinePointMarker.vue'
|
import LinePointMarker from '@/views/components/element/LineElement/LinePointMarker.vue'
|
||||||
|
|
||||||
export default defineComponent({
|
const emit = defineEmits<{
|
||||||
name: 'line-pool',
|
(event: 'select', payload: LinePoolItem): void
|
||||||
emits: ['select'],
|
}>()
|
||||||
components: {
|
|
||||||
LinePointMarker,
|
|
||||||
},
|
|
||||||
setup(props, { emit }) {
|
|
||||||
const lineList = LINE_LIST
|
|
||||||
|
|
||||||
const selectLine = (line: LinePoolItem) => {
|
const selectLine = (line: LinePoolItem) => {
|
||||||
emit('select', line)
|
emit('select', line)
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
|
||||||
lineList,
|
|
||||||
selectLine,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
})
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
@ -28,8 +28,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts" setup>
|
||||||
import { defineComponent, ref } from 'vue'
|
import { ref } from 'vue'
|
||||||
import { message } from 'ant-design-vue'
|
import { message } from 'ant-design-vue'
|
||||||
|
|
||||||
type TypeKey = 'video' | 'audio'
|
type TypeKey = 'video' | 'audio'
|
||||||
@ -38,43 +38,33 @@ interface TabItem {
|
|||||||
label: string
|
label: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export default defineComponent({
|
const emit = defineEmits<{
|
||||||
name: 'media-input',
|
(event: 'insertVideo', payload: string): void
|
||||||
emits: ['insertVideo', 'insertAudio', 'close'],
|
(event: 'insertAudio', payload: string): void
|
||||||
setup(props, { emit }) {
|
(event: 'close'): void
|
||||||
const type = ref<TypeKey>('video')
|
}>()
|
||||||
|
|
||||||
const videoSrc = ref('https://mazwai.com/videvo_files/video/free/2019-01/small_watermarked/181004_04_Dolphins-Whale_06_preview.webm')
|
const type = ref<TypeKey>('video')
|
||||||
const audioSrc = ref('https://freesound.org/data/previews/614/614107_11861866-lq.mp3')
|
|
||||||
|
|
||||||
const tabs: TabItem[] = [
|
const videoSrc = ref('https://mazwai.com/videvo_files/video/free/2019-01/small_watermarked/181004_04_Dolphins-Whale_06_preview.webm')
|
||||||
|
const audioSrc = ref('https://freesound.org/data/previews/614/614107_11861866-lq.mp3')
|
||||||
|
|
||||||
|
const tabs: TabItem[] = [
|
||||||
{ key: 'video', label: '视频' },
|
{ key: 'video', label: '视频' },
|
||||||
{ key: 'audio', label: '音频' },
|
{ key: 'audio', label: '音频' },
|
||||||
]
|
]
|
||||||
|
|
||||||
const insertVideo = () => {
|
const insertVideo = () => {
|
||||||
if (!videoSrc.value) return message.error('请先输入正确的视频地址')
|
if (!videoSrc.value) return message.error('请先输入正确的视频地址')
|
||||||
emit('insertVideo', videoSrc.value)
|
emit('insertVideo', videoSrc.value)
|
||||||
}
|
}
|
||||||
|
|
||||||
const insertAudio = () => {
|
const insertAudio = () => {
|
||||||
if (!audioSrc.value) return message.error('请先输入正确的音频地址')
|
if (!audioSrc.value) return message.error('请先输入正确的音频地址')
|
||||||
emit('insertAudio', audioSrc.value)
|
emit('insertAudio', audioSrc.value)
|
||||||
}
|
}
|
||||||
|
|
||||||
const close = () => emit('close')
|
const close = () => emit('close')
|
||||||
|
|
||||||
return {
|
|
||||||
type,
|
|
||||||
videoSrc,
|
|
||||||
audioSrc,
|
|
||||||
tabs,
|
|
||||||
insertVideo,
|
|
||||||
insertAudio,
|
|
||||||
close,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
})
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="shape-pool">
|
<div class="shape-pool">
|
||||||
<div class="category" v-for="item in shapeList" :key="item.type">
|
<div class="category" v-for="item in SHAPE_LIST" :key="item.type">
|
||||||
<div class="category-name">{{item.type}}</div>
|
<div class="category-name">{{item.type}}</div>
|
||||||
<div class="shape-list">
|
<div class="shape-list">
|
||||||
<div class="shape-item" v-for="(shape, index) in item.children" :key="index">
|
<div class="shape-item" v-for="(shape, index) in item.children" :key="index">
|
||||||
@ -33,26 +33,16 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts" setup>
|
||||||
import { defineComponent } from 'vue'
|
|
||||||
import { SHAPE_LIST, ShapePoolItem } from '@/configs/shapes'
|
import { SHAPE_LIST, ShapePoolItem } from '@/configs/shapes'
|
||||||
|
|
||||||
export default defineComponent({
|
const emit = defineEmits<{
|
||||||
name: 'shape-pool',
|
(event: 'select', payload: ShapePoolItem): void
|
||||||
emits: ['select'],
|
}>()
|
||||||
setup(props, { emit }) {
|
|
||||||
const shapeList = SHAPE_LIST
|
|
||||||
|
|
||||||
const selectShape = (shape: ShapePoolItem) => {
|
const selectShape = (shape: ShapePoolItem) => {
|
||||||
emit('select', shape)
|
emit('select', shape)
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
|
||||||
shapeList,
|
|
||||||
selectShape,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
})
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
@ -51,49 +51,42 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts" setup>
|
||||||
import { defineComponent, ref } from 'vue'
|
import { ref } from 'vue'
|
||||||
|
|
||||||
import { message } from 'ant-design-vue'
|
import { message } from 'ant-design-vue'
|
||||||
|
|
||||||
export default defineComponent({
|
interface InsertData {
|
||||||
name: 'table-generator',
|
row: number
|
||||||
emits: ['insert', 'close'],
|
col: number
|
||||||
setup(props, { emit }) {
|
}
|
||||||
const endCell = ref<number[]>([])
|
|
||||||
const customRow = ref(3)
|
|
||||||
const customCol = ref(3)
|
|
||||||
const isCustom = ref(false)
|
|
||||||
|
|
||||||
const handleClickTable = () => {
|
const emit = defineEmits<{
|
||||||
|
(event: 'insert', payload: InsertData): void
|
||||||
|
(event: 'close'): void
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const endCell = ref<number[]>([])
|
||||||
|
const customRow = ref(3)
|
||||||
|
const customCol = ref(3)
|
||||||
|
const isCustom = ref(false)
|
||||||
|
|
||||||
|
const handleClickTable = () => {
|
||||||
if (!endCell.value.length) return
|
if (!endCell.value.length) return
|
||||||
const [row, col] = endCell.value
|
const [row, col] = endCell.value
|
||||||
emit('insert', { row, col })
|
emit('insert', { row, col })
|
||||||
}
|
}
|
||||||
|
|
||||||
const insertCustomTable = () => {
|
const insertCustomTable = () => {
|
||||||
if (customRow.value < 1 || customRow.value > 20) return message.warning('行数/列数必须在0~20之间!')
|
if (customRow.value < 1 || customRow.value > 20) return message.warning('行数/列数必须在0~20之间!')
|
||||||
if (customCol.value < 1 || customCol.value > 20) return message.warning('行数/列数必须在0~20之间!')
|
if (customCol.value < 1 || customCol.value > 20) return message.warning('行数/列数必须在0~20之间!')
|
||||||
emit('insert', { row: customRow.value, col: customCol.value })
|
emit('insert', { row: customRow.value, col: customCol.value })
|
||||||
isCustom.value = false
|
isCustom.value = false
|
||||||
}
|
}
|
||||||
|
|
||||||
const close = () => {
|
const close = () => {
|
||||||
emit('close')
|
emit('close')
|
||||||
isCustom.value = false
|
isCustom.value = false
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
|
||||||
endCell,
|
|
||||||
customRow,
|
|
||||||
customCol,
|
|
||||||
handleClickTable,
|
|
||||||
insertCustomTable,
|
|
||||||
isCustom,
|
|
||||||
close,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
})
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
@ -106,8 +106,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts" setup>
|
||||||
import { defineComponent, ref } from 'vue'
|
import { ref } from 'vue'
|
||||||
import { storeToRefs } from 'pinia'
|
import { storeToRefs } from 'pinia'
|
||||||
import { useMainStore, useSnapshotStore } from '@/store'
|
import { useMainStore, useSnapshotStore } from '@/store'
|
||||||
import { getImageDataURL } from '@/utils/image'
|
import { getImageDataURL } from '@/utils/image'
|
||||||
@ -124,115 +124,73 @@ import TableGenerator from './TableGenerator.vue'
|
|||||||
import MediaInput from './MediaInput.vue'
|
import MediaInput from './MediaInput.vue'
|
||||||
import LaTeXEditor from '@/components/LaTeXEditor/index.vue'
|
import LaTeXEditor from '@/components/LaTeXEditor/index.vue'
|
||||||
|
|
||||||
export default defineComponent({
|
const mainStore = useMainStore()
|
||||||
name: 'canvas-tool',
|
const { creatingElement } = storeToRefs(mainStore)
|
||||||
components: {
|
const { canUndo, canRedo } = storeToRefs(useSnapshotStore())
|
||||||
ShapePool,
|
|
||||||
LinePool,
|
|
||||||
ChartPool,
|
|
||||||
TableGenerator,
|
|
||||||
MediaInput,
|
|
||||||
LaTeXEditor,
|
|
||||||
},
|
|
||||||
setup() {
|
|
||||||
const mainStore = useMainStore()
|
|
||||||
const { creatingElement } = storeToRefs(mainStore)
|
|
||||||
const { canUndo, canRedo } = storeToRefs(useSnapshotStore())
|
|
||||||
|
|
||||||
const { redo, undo } = useHistorySnapshot()
|
const { redo, undo } = useHistorySnapshot()
|
||||||
|
|
||||||
const {
|
const {
|
||||||
scaleCanvas,
|
scaleCanvas,
|
||||||
setCanvasScalePercentage,
|
setCanvasScalePercentage,
|
||||||
resetCanvas,
|
resetCanvas,
|
||||||
canvasScalePercentage,
|
canvasScalePercentage,
|
||||||
} = useScaleCanvas()
|
} = useScaleCanvas()
|
||||||
|
|
||||||
const canvasScalePresetList = [200, 150, 100, 80, 50]
|
const canvasScalePresetList = [200, 150, 100, 80, 50]
|
||||||
const canvasScaleVisible = ref(false)
|
const canvasScaleVisible = ref(false)
|
||||||
|
|
||||||
const applyCanvasPresetScale = (value: number) => {
|
const applyCanvasPresetScale = (value: number) => {
|
||||||
setCanvasScalePercentage(value)
|
setCanvasScalePercentage(value)
|
||||||
canvasScaleVisible.value = false
|
canvasScaleVisible.value = false
|
||||||
}
|
}
|
||||||
|
|
||||||
const {
|
const {
|
||||||
createImageElement,
|
createImageElement,
|
||||||
createChartElement,
|
createChartElement,
|
||||||
createTableElement,
|
createTableElement,
|
||||||
createLatexElement,
|
createLatexElement,
|
||||||
createVideoElement,
|
createVideoElement,
|
||||||
createAudioElement,
|
createAudioElement,
|
||||||
} = useCreateElement()
|
} = useCreateElement()
|
||||||
|
|
||||||
const insertImageElement = (files: File[]) => {
|
const insertImageElement = (files: FileList) => {
|
||||||
const imageFile = files[0]
|
const imageFile = files[0]
|
||||||
if (!imageFile) return
|
if (!imageFile) return
|
||||||
getImageDataURL(imageFile).then(dataURL => createImageElement(dataURL))
|
getImageDataURL(imageFile).then(dataURL => createImageElement(dataURL))
|
||||||
}
|
}
|
||||||
|
|
||||||
const shapePoolVisible = ref(false)
|
const shapePoolVisible = ref(false)
|
||||||
const linePoolVisible = ref(false)
|
const linePoolVisible = ref(false)
|
||||||
const chartPoolVisible = ref(false)
|
const chartPoolVisible = ref(false)
|
||||||
const tableGeneratorVisible = ref(false)
|
const tableGeneratorVisible = ref(false)
|
||||||
const mediaInputVisible = ref(false)
|
const mediaInputVisible = ref(false)
|
||||||
const latexEditorVisible = ref(false)
|
const latexEditorVisible = ref(false)
|
||||||
|
|
||||||
// 绘制文字范围
|
// 绘制文字范围
|
||||||
const drawText = () => {
|
const drawText = () => {
|
||||||
mainStore.setCreatingElement({
|
mainStore.setCreatingElement({
|
||||||
type: 'text',
|
type: 'text',
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// 绘制形状范围
|
// 绘制形状范围
|
||||||
const drawShape = (shape: ShapePoolItem) => {
|
const drawShape = (shape: ShapePoolItem) => {
|
||||||
mainStore.setCreatingElement({
|
mainStore.setCreatingElement({
|
||||||
type: 'shape',
|
type: 'shape',
|
||||||
data: shape,
|
data: shape,
|
||||||
})
|
})
|
||||||
shapePoolVisible.value = false
|
shapePoolVisible.value = false
|
||||||
}
|
}
|
||||||
|
|
||||||
// 绘制线条路径
|
// 绘制线条路径
|
||||||
const drawLine = (line: LinePoolItem) => {
|
const drawLine = (line: LinePoolItem) => {
|
||||||
mainStore.setCreatingElement({
|
mainStore.setCreatingElement({
|
||||||
type: 'line',
|
type: 'line',
|
||||||
data: line,
|
data: line,
|
||||||
})
|
})
|
||||||
linePoolVisible.value = false
|
linePoolVisible.value = false
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
|
||||||
scaleCanvas,
|
|
||||||
resetCanvas,
|
|
||||||
canvasScalePercentage,
|
|
||||||
canvasScaleVisible,
|
|
||||||
canvasScalePresetList,
|
|
||||||
applyCanvasPresetScale,
|
|
||||||
canUndo,
|
|
||||||
canRedo,
|
|
||||||
redo,
|
|
||||||
undo,
|
|
||||||
insertImageElement,
|
|
||||||
shapePoolVisible,
|
|
||||||
linePoolVisible,
|
|
||||||
chartPoolVisible,
|
|
||||||
tableGeneratorVisible,
|
|
||||||
mediaInputVisible,
|
|
||||||
latexEditorVisible,
|
|
||||||
creatingElement,
|
|
||||||
drawText,
|
|
||||||
drawShape,
|
|
||||||
drawLine,
|
|
||||||
createChartElement,
|
|
||||||
createTableElement,
|
|
||||||
createLatexElement,
|
|
||||||
createVideoElement,
|
|
||||||
createAudioElement,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
})
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="hotkey-doc">
|
<div class="hotkey-doc">
|
||||||
<template v-for="item in hotkeys" :key="item.type">
|
<template v-for="item in HOTKEY_DOC" :key="item.type">
|
||||||
<div class="title">{{item.type}}</div>
|
<div class="title">{{item.type}}</div>
|
||||||
<div class="hotkey-item" v-for="hotkey in item.children" :key="hotkey.label">
|
<div class="hotkey-item" v-for="hotkey in item.children" :key="hotkey.label">
|
||||||
<div class="label">{{hotkey.label}}</div>
|
<div class="label">{{hotkey.label}}</div>
|
||||||
@ -10,18 +10,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts" setup>
|
||||||
import { defineComponent } from 'vue'
|
|
||||||
import { HOTKEY_DOC } from '@/configs/hotkey'
|
import { HOTKEY_DOC } from '@/configs/hotkey'
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
name: 'hotkey-doc',
|
|
||||||
setup() {
|
|
||||||
return {
|
|
||||||
hotkeys: HOTKEY_DOC,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
})
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
@ -78,8 +78,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts" setup>
|
||||||
import { defineComponent, ref } from 'vue'
|
import { ref } from 'vue'
|
||||||
import { storeToRefs } from 'pinia'
|
import { storeToRefs } from 'pinia'
|
||||||
import { useMainStore } from '@/store'
|
import { useMainStore } from '@/store'
|
||||||
import useScreening from '@/hooks/useScreening'
|
import useScreening from '@/hooks/useScreening'
|
||||||
@ -89,55 +89,29 @@ import useExport from '@/hooks/useExport'
|
|||||||
|
|
||||||
import HotkeyDoc from './HotkeyDoc.vue'
|
import HotkeyDoc from './HotkeyDoc.vue'
|
||||||
|
|
||||||
export default defineComponent({
|
const mainStore = useMainStore()
|
||||||
name: 'editor-header',
|
const { showGridLines, showRuler } = storeToRefs(mainStore)
|
||||||
components: {
|
|
||||||
HotkeyDoc,
|
|
||||||
},
|
|
||||||
setup() {
|
|
||||||
const mainStore = useMainStore()
|
|
||||||
const { showGridLines, showRuler } = storeToRefs(mainStore)
|
|
||||||
|
|
||||||
const { enterScreening, enterScreeningFromStart } = useScreening()
|
const { enterScreening, enterScreeningFromStart } = useScreening()
|
||||||
const { createSlide, deleteSlide, resetSlides } = useSlideHandler()
|
const { createSlide, deleteSlide, resetSlides } = useSlideHandler()
|
||||||
const { redo, undo } = useHistorySnapshot()
|
const { redo, undo } = useHistorySnapshot()
|
||||||
const { importSpecificFile } = useExport()
|
const { importSpecificFile } = useExport()
|
||||||
|
|
||||||
const setDialogForExport = mainStore.setDialogForExport
|
const setDialogForExport = mainStore.setDialogForExport
|
||||||
|
|
||||||
const toggleGridLines = () => {
|
const toggleGridLines = () => {
|
||||||
mainStore.setGridLinesState(!showGridLines.value)
|
mainStore.setGridLinesState(!showGridLines.value)
|
||||||
}
|
}
|
||||||
|
|
||||||
const toggleRuler = () => {
|
const toggleRuler = () => {
|
||||||
mainStore.setRulerState(!showRuler.value)
|
mainStore.setRulerState(!showRuler.value)
|
||||||
}
|
}
|
||||||
|
|
||||||
const hotkeyDrawerVisible = ref(false)
|
const hotkeyDrawerVisible = ref(false)
|
||||||
|
|
||||||
const goIssues = () => {
|
const goIssues = () => {
|
||||||
window.open('https://github.com/pipipi-pikachu/PPTist/issues')
|
window.open('https://github.com/pipipi-pikachu/PPTist/issues')
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
|
||||||
redo,
|
|
||||||
undo,
|
|
||||||
showGridLines,
|
|
||||||
showRuler,
|
|
||||||
hotkeyDrawerVisible,
|
|
||||||
importSpecificFile,
|
|
||||||
setDialogForExport,
|
|
||||||
enterScreening,
|
|
||||||
enterScreeningFromStart,
|
|
||||||
createSlide,
|
|
||||||
deleteSlide,
|
|
||||||
toggleGridLines,
|
|
||||||
toggleRuler,
|
|
||||||
resetSlides,
|
|
||||||
goIssues,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
})
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
@ -75,62 +75,44 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts" setup>
|
||||||
import { computed, defineComponent, ref } from 'vue'
|
import { computed, ref } from 'vue'
|
||||||
import { storeToRefs } from 'pinia'
|
import { storeToRefs } from 'pinia'
|
||||||
import { useSlidesStore } from '@/store'
|
import { useSlidesStore } from '@/store'
|
||||||
import useExport from '@/hooks/useExport'
|
import useExport from '@/hooks/useExport'
|
||||||
|
|
||||||
import ThumbnailSlide from '@/views/components/ThumbnailSlide/index.vue'
|
import ThumbnailSlide from '@/views/components/ThumbnailSlide/index.vue'
|
||||||
|
|
||||||
export default defineComponent({
|
const emit = defineEmits<{
|
||||||
name: 'export-img-dialog',
|
(event: 'close'): void
|
||||||
components: {
|
}>()
|
||||||
ThumbnailSlide,
|
|
||||||
},
|
|
||||||
setup(props, { emit }) {
|
|
||||||
const { slides, currentSlide } = storeToRefs(useSlidesStore())
|
|
||||||
|
|
||||||
const imageThumbnailsRef = ref<HTMLElement>()
|
const { slides, currentSlide } = storeToRefs(useSlidesStore())
|
||||||
const rangeType = ref<'all' | 'current' | 'custom'>('all')
|
|
||||||
const range = ref<[number, number]>([1, slides.value.length])
|
|
||||||
const format = ref<'jpeg' | 'png'>('jpeg')
|
|
||||||
const quality = ref(1)
|
|
||||||
const ignoreWebfont = ref(true)
|
|
||||||
|
|
||||||
const renderSlides = computed(() => {
|
const imageThumbnailsRef = ref<HTMLElement>()
|
||||||
|
const rangeType = ref<'all' | 'current' | 'custom'>('all')
|
||||||
|
const range = ref<[number, number]>([1, slides.value.length])
|
||||||
|
const format = ref<'jpeg' | 'png'>('jpeg')
|
||||||
|
const quality = ref(1)
|
||||||
|
const ignoreWebfont = ref(true)
|
||||||
|
|
||||||
|
const renderSlides = computed(() => {
|
||||||
if (rangeType.value === 'all') return slides.value
|
if (rangeType.value === 'all') return slides.value
|
||||||
if (rangeType.value === 'current') return [currentSlide.value]
|
if (rangeType.value === 'current') return [currentSlide.value]
|
||||||
return slides.value.filter((item, index) => {
|
return slides.value.filter((item, index) => {
|
||||||
const [min, max] = range.value
|
const [min, max] = range.value
|
||||||
return index >= min - 1 && index <= max - 1
|
return index >= min - 1 && index <= max - 1
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
const close = () => emit('close')
|
const close = () => emit('close')
|
||||||
|
|
||||||
const { exportImage, exporting } = useExport()
|
const { exportImage, exporting } = useExport()
|
||||||
|
|
||||||
const expImage = () => {
|
const expImage = () => {
|
||||||
if (!imageThumbnailsRef.value) return
|
if (!imageThumbnailsRef.value) return
|
||||||
exportImage(imageThumbnailsRef.value, format.value, quality.value, ignoreWebfont.value)
|
exportImage(imageThumbnailsRef.value, format.value, quality.value, ignoreWebfont.value)
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
|
||||||
imageThumbnailsRef,
|
|
||||||
slides,
|
|
||||||
rangeType,
|
|
||||||
range,
|
|
||||||
format,
|
|
||||||
quality,
|
|
||||||
ignoreWebfont,
|
|
||||||
renderSlides,
|
|
||||||
exporting,
|
|
||||||
expImage,
|
|
||||||
close,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
})
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
@ -11,28 +11,18 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts" setup>
|
||||||
import { defineComponent } from 'vue'
|
|
||||||
import { storeToRefs } from 'pinia'
|
import { storeToRefs } from 'pinia'
|
||||||
import { useSlidesStore } from '@/store'
|
import { useSlidesStore } from '@/store'
|
||||||
import useExport from '@/hooks/useExport'
|
import useExport from '@/hooks/useExport'
|
||||||
|
|
||||||
export default defineComponent({
|
const emit = defineEmits<{
|
||||||
name: 'export-json-dialog',
|
(event: 'close'): void
|
||||||
setup(props, { emit }) {
|
}>()
|
||||||
const close = () => emit('close')
|
|
||||||
|
|
||||||
const { slides } = storeToRefs(useSlidesStore())
|
const { slides } = storeToRefs(useSlidesStore())
|
||||||
|
const { exportJSON } = useExport()
|
||||||
const { exportJSON } = useExport()
|
const close = () => emit('close')
|
||||||
|
|
||||||
return {
|
|
||||||
slides,
|
|
||||||
exportJSON,
|
|
||||||
close,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
})
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
@ -60,30 +60,28 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts" setup>
|
||||||
import { defineComponent, ref } from 'vue'
|
import { ref } from 'vue'
|
||||||
import { storeToRefs } from 'pinia'
|
import { storeToRefs } from 'pinia'
|
||||||
import { useSlidesStore } from '@/store'
|
import { useSlidesStore } from '@/store'
|
||||||
import { print } from '@/utils/print'
|
import { print } from '@/utils/print'
|
||||||
|
|
||||||
import ThumbnailSlide from '@/views/components/ThumbnailSlide/index.vue'
|
import ThumbnailSlide from '@/views/components/ThumbnailSlide/index.vue'
|
||||||
|
|
||||||
export default defineComponent({
|
const emit = defineEmits<{
|
||||||
name: 'export-pdf-dialog',
|
(event: 'close'): void
|
||||||
components: {
|
}>()
|
||||||
ThumbnailSlide,
|
|
||||||
},
|
|
||||||
setup(props, { emit }) {
|
|
||||||
const { slides, currentSlide } = storeToRefs(useSlidesStore())
|
|
||||||
|
|
||||||
const pdfThumbnailsRef = ref<HTMLElement>()
|
const { slides, currentSlide } = storeToRefs(useSlidesStore())
|
||||||
const rangeType = ref<'all' | 'current'>('all')
|
|
||||||
const count = ref(1)
|
|
||||||
const padding = ref(true)
|
|
||||||
|
|
||||||
const close = () => emit('close')
|
const pdfThumbnailsRef = ref<HTMLElement>()
|
||||||
|
const rangeType = ref<'all' | 'current'>('all')
|
||||||
|
const count = ref(1)
|
||||||
|
const padding = ref(true)
|
||||||
|
|
||||||
const expPDF = () => {
|
const close = () => emit('close')
|
||||||
|
|
||||||
|
const expPDF = () => {
|
||||||
if (!pdfThumbnailsRef.value) return
|
if (!pdfThumbnailsRef.value) return
|
||||||
const pageSize = {
|
const pageSize = {
|
||||||
width: 1600,
|
width: 1600,
|
||||||
@ -91,20 +89,7 @@ export default defineComponent({
|
|||||||
margin: padding.value ? 50 : 0,
|
margin: padding.value ? 50 : 0,
|
||||||
}
|
}
|
||||||
print(pdfThumbnailsRef.value, pageSize)
|
print(pdfThumbnailsRef.value, pageSize)
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
|
||||||
pdfThumbnailsRef,
|
|
||||||
slides,
|
|
||||||
currentSlide,
|
|
||||||
rangeType,
|
|
||||||
count,
|
|
||||||
padding,
|
|
||||||
expPDF,
|
|
||||||
close,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
})
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
@ -39,46 +39,34 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts" setup>
|
||||||
import { computed, defineComponent, ref } from 'vue'
|
import { computed, ref } from 'vue'
|
||||||
import { storeToRefs } from 'pinia'
|
import { storeToRefs } from 'pinia'
|
||||||
import { useSlidesStore } from '@/store'
|
import { useSlidesStore } from '@/store'
|
||||||
import useExport from '@/hooks/useExport'
|
import useExport from '@/hooks/useExport'
|
||||||
|
|
||||||
export default defineComponent({
|
const emit = defineEmits<{
|
||||||
name: 'export-pptx-dialog',
|
(event: 'close'): void
|
||||||
setup(props, { emit }) {
|
}>()
|
||||||
const { slides, currentSlide } = storeToRefs(useSlidesStore())
|
|
||||||
|
|
||||||
const rangeType = ref<'all' | 'current' | 'custom'>('all')
|
const { slides, currentSlide } = storeToRefs(useSlidesStore())
|
||||||
const range = ref<[number, number]>([1, slides.value.length])
|
|
||||||
const masterOverwrite = ref(true)
|
|
||||||
|
|
||||||
const selectedSlides = computed(() => {
|
const { exportPPTX, exporting } = useExport()
|
||||||
|
|
||||||
|
const rangeType = ref<'all' | 'current' | 'custom'>('all')
|
||||||
|
const range = ref<[number, number]>([1, slides.value.length])
|
||||||
|
const masterOverwrite = ref(true)
|
||||||
|
|
||||||
|
const selectedSlides = computed(() => {
|
||||||
if (rangeType.value === 'all') return slides.value
|
if (rangeType.value === 'all') return slides.value
|
||||||
if (rangeType.value === 'current') return [currentSlide.value]
|
if (rangeType.value === 'current') return [currentSlide.value]
|
||||||
return slides.value.filter((item, index) => {
|
return slides.value.filter((item, index) => {
|
||||||
const [min, max] = range.value
|
const [min, max] = range.value
|
||||||
return index >= min - 1 && index <= max - 1
|
return index >= min - 1 && index <= max - 1
|
||||||
})
|
})
|
||||||
})
|
|
||||||
|
|
||||||
const close = () => emit('close')
|
|
||||||
|
|
||||||
const { exportPPTX, exporting } = useExport()
|
|
||||||
|
|
||||||
return {
|
|
||||||
slides,
|
|
||||||
rangeType,
|
|
||||||
range,
|
|
||||||
masterOverwrite,
|
|
||||||
exporting,
|
|
||||||
selectedSlides,
|
|
||||||
exportPPTX,
|
|
||||||
close,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const close = () => emit('close')
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
@ -34,43 +34,33 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts" setup>
|
||||||
import { computed, defineComponent, ref } from 'vue'
|
import { computed, ref } from 'vue'
|
||||||
import { storeToRefs } from 'pinia'
|
import { storeToRefs } from 'pinia'
|
||||||
import { useSlidesStore } from '@/store'
|
import { useSlidesStore } from '@/store'
|
||||||
import useExport from '@/hooks/useExport'
|
import useExport from '@/hooks/useExport'
|
||||||
|
|
||||||
export default defineComponent({
|
const emit = defineEmits<{
|
||||||
name: 'export-pptist-dialog',
|
(event: 'close'): void
|
||||||
setup(props, { emit }) {
|
}>()
|
||||||
const { slides, currentSlide } = storeToRefs(useSlidesStore())
|
|
||||||
|
|
||||||
const rangeType = ref<'all' | 'current' | 'custom'>('all')
|
const { slides, currentSlide } = storeToRefs(useSlidesStore())
|
||||||
const range = ref<[number, number]>([1, slides.value.length])
|
|
||||||
|
|
||||||
const selectedSlides = computed(() => {
|
const { exportSpecificFile } = useExport()
|
||||||
|
|
||||||
|
const rangeType = ref<'all' | 'current' | 'custom'>('all')
|
||||||
|
const range = ref<[number, number]>([1, slides.value.length])
|
||||||
|
|
||||||
|
const selectedSlides = computed(() => {
|
||||||
if (rangeType.value === 'all') return slides.value
|
if (rangeType.value === 'all') return slides.value
|
||||||
if (rangeType.value === 'current') return [currentSlide.value]
|
if (rangeType.value === 'current') return [currentSlide.value]
|
||||||
return slides.value.filter((item, index) => {
|
return slides.value.filter((item, index) => {
|
||||||
const [min, max] = range.value
|
const [min, max] = range.value
|
||||||
return index >= min - 1 && index <= max - 1
|
return index >= min - 1 && index <= max - 1
|
||||||
})
|
})
|
||||||
})
|
|
||||||
|
|
||||||
const close = () => emit('close')
|
|
||||||
|
|
||||||
const { exportSpecificFile } = useExport()
|
|
||||||
|
|
||||||
return {
|
|
||||||
slides,
|
|
||||||
rangeType,
|
|
||||||
range,
|
|
||||||
selectedSlides,
|
|
||||||
exportSpecificFile,
|
|
||||||
close,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const close = () => emit('close')
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
@ -15,8 +15,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts" setup>
|
||||||
import { computed, defineComponent } from 'vue'
|
import { computed } from 'vue'
|
||||||
import { storeToRefs } from 'pinia'
|
import { storeToRefs } from 'pinia'
|
||||||
import { useMainStore } from '@/store'
|
import { useMainStore } from '@/store'
|
||||||
import { DialogForExportTypes } from '@/types/export'
|
import { DialogForExportTypes } from '@/types/export'
|
||||||
@ -32,23 +32,20 @@ interface TabItem {
|
|||||||
label: string
|
label: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export default defineComponent({
|
const mainStore = useMainStore()
|
||||||
name: 'export-dialog',
|
const { dialogForExport } = storeToRefs(mainStore)
|
||||||
setup() {
|
|
||||||
const mainStore = useMainStore()
|
|
||||||
const { dialogForExport } = storeToRefs(mainStore)
|
|
||||||
|
|
||||||
const setDialogForExport = mainStore.setDialogForExport
|
const setDialogForExport = mainStore.setDialogForExport
|
||||||
|
|
||||||
const tabs: TabItem[] = [
|
const tabs: TabItem[] = [
|
||||||
{ key: 'pptist', label: '导出 pptist 文件' },
|
{ key: 'pptist', label: '导出 pptist 文件' },
|
||||||
{ key: 'pptx', label: '导出 PPTX' },
|
{ key: 'pptx', label: '导出 PPTX' },
|
||||||
{ key: 'image', label: '导出图片' },
|
{ key: 'image', label: '导出图片' },
|
||||||
{ key: 'json', label: '导出 JSON' },
|
{ key: 'json', label: '导出 JSON' },
|
||||||
{ key: 'pdf', label: '打印 / 导出 PDF' },
|
{ key: 'pdf', label: '打印 / 导出 PDF' },
|
||||||
]
|
]
|
||||||
|
|
||||||
const currentDialogComponent = computed(() => {
|
const currentDialogComponent = computed(() => {
|
||||||
const dialogMap = {
|
const dialogMap = {
|
||||||
'image': ExportImage,
|
'image': ExportImage,
|
||||||
'json': ExportJSON,
|
'json': ExportJSON,
|
||||||
@ -57,15 +54,6 @@ export default defineComponent({
|
|||||||
'pptist': ExportSpecificFile,
|
'pptist': ExportSpecificFile,
|
||||||
}
|
}
|
||||||
return dialogMap[dialogForExport.value] || null
|
return dialogMap[dialogForExport.value] || null
|
||||||
})
|
|
||||||
|
|
||||||
return {
|
|
||||||
currentDialogComponent,
|
|
||||||
tabs,
|
|
||||||
dialogForExport,
|
|
||||||
setDialogForExport,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@ -12,32 +12,33 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts" setup>
|
||||||
import { computed, defineComponent } from 'vue'
|
import { computed } from 'vue'
|
||||||
import { storeToRefs } from 'pinia'
|
import { storeToRefs } from 'pinia'
|
||||||
import { useSlidesStore } from '@/store'
|
import { useSlidesStore } from '@/store'
|
||||||
|
|
||||||
export default defineComponent({
|
const props = defineProps({
|
||||||
name: 'remark',
|
|
||||||
emits: ['update:height'],
|
|
||||||
props: {
|
|
||||||
height: {
|
height: {
|
||||||
type: Number,
|
type: Number,
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
},
|
})
|
||||||
setup(props, { emit }) {
|
|
||||||
const slidesStore = useSlidesStore()
|
|
||||||
const { currentSlide } = storeToRefs(slidesStore)
|
|
||||||
|
|
||||||
const remark = computed(() => currentSlide.value?.remark || '')
|
const emit = defineEmits<{
|
||||||
|
(event: 'update:height', payload: number): void
|
||||||
|
}>()
|
||||||
|
|
||||||
const handleInput = (e: Event) => {
|
const slidesStore = useSlidesStore()
|
||||||
|
const { currentSlide } = storeToRefs(slidesStore)
|
||||||
|
|
||||||
|
const remark = computed(() => currentSlide.value?.remark || '')
|
||||||
|
|
||||||
|
const handleInput = (e: Event) => {
|
||||||
const value = (e.target as HTMLTextAreaElement).value
|
const value = (e.target as HTMLTextAreaElement).value
|
||||||
slidesStore.updateSlide({ remark: value })
|
slidesStore.updateSlide({ remark: value })
|
||||||
}
|
}
|
||||||
|
|
||||||
const resize = (e: MouseEvent) => {
|
const resize = (e: MouseEvent) => {
|
||||||
let isMouseDown = true
|
let isMouseDown = true
|
||||||
const startPageY = e.pageY
|
const startPageY = e.pageY
|
||||||
const originHeight = props.height
|
const originHeight = props.height
|
||||||
@ -61,15 +62,7 @@ export default defineComponent({
|
|||||||
document.onmousemove = null
|
document.onmousemove = null
|
||||||
document.onmouseup = null
|
document.onmouseup = null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
|
||||||
remark,
|
|
||||||
handleInput,
|
|
||||||
resize,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
})
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
@ -11,33 +11,22 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts" setup>
|
||||||
import { defineComponent } from 'vue'
|
|
||||||
import { storeToRefs } from 'pinia'
|
import { storeToRefs } from 'pinia'
|
||||||
import { useSlidesStore } from '@/store'
|
import { useSlidesStore } from '@/store'
|
||||||
import { Slide } from '@/types/slides'
|
import { Slide } from '@/types/slides'
|
||||||
|
|
||||||
import ThumbnailSlide from '@/views/components/ThumbnailSlide/index.vue'
|
import ThumbnailSlide from '@/views/components/ThumbnailSlide/index.vue'
|
||||||
|
|
||||||
export default defineComponent({
|
const emit = defineEmits<{
|
||||||
name: 'layout-pool',
|
(event: 'select', payload: Slide): void
|
||||||
emits: ['select'],
|
}>()
|
||||||
components: {
|
|
||||||
ThumbnailSlide,
|
|
||||||
},
|
|
||||||
setup(props, { emit }) {
|
|
||||||
const { layouts } = storeToRefs(useSlidesStore())
|
|
||||||
|
|
||||||
const selectSlideTemplate = (slide: Slide) => {
|
const { layouts } = storeToRefs(useSlidesStore())
|
||||||
|
|
||||||
|
const selectSlideTemplate = (slide: Slide) => {
|
||||||
emit('select', slide)
|
emit('select', slide)
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
|
||||||
layouts,
|
|
||||||
selectSlideTemplate,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
})
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
@ -43,8 +43,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts" setup>
|
||||||
import { computed, defineComponent, ref } from 'vue'
|
import { computed, ref } from 'vue'
|
||||||
import { storeToRefs } from 'pinia'
|
import { storeToRefs } from 'pinia'
|
||||||
import { useMainStore, useSlidesStore, useKeyboardStore } from '@/store'
|
import { useMainStore, useSlidesStore, useKeyboardStore } from '@/store'
|
||||||
import { fillDigit } from '@/utils/common'
|
import { fillDigit } from '@/utils/common'
|
||||||
@ -57,28 +57,20 @@ import Draggable from 'vuedraggable'
|
|||||||
import ThumbnailSlide from '@/views/components/ThumbnailSlide/index.vue'
|
import ThumbnailSlide from '@/views/components/ThumbnailSlide/index.vue'
|
||||||
import LayoutPool from './LayoutPool.vue'
|
import LayoutPool from './LayoutPool.vue'
|
||||||
|
|
||||||
export default defineComponent({
|
const mainStore = useMainStore()
|
||||||
name: 'thumbnails',
|
const slidesStore = useSlidesStore()
|
||||||
components: {
|
const keyboardStore = useKeyboardStore()
|
||||||
Draggable,
|
const { selectedSlidesIndex: _selectedSlidesIndex, thumbnailsFocus } = storeToRefs(mainStore)
|
||||||
ThumbnailSlide,
|
const { slides, slideIndex } = storeToRefs(slidesStore)
|
||||||
LayoutPool,
|
const { ctrlKeyState, shiftKeyState } = storeToRefs(keyboardStore)
|
||||||
},
|
|
||||||
setup() {
|
|
||||||
const mainStore = useMainStore()
|
|
||||||
const slidesStore = useSlidesStore()
|
|
||||||
const keyboardStore = useKeyboardStore()
|
|
||||||
const { selectedSlidesIndex: _selectedSlidesIndex, thumbnailsFocus } = storeToRefs(mainStore)
|
|
||||||
const { slides, slideIndex } = storeToRefs(slidesStore)
|
|
||||||
const { ctrlKeyState, shiftKeyState } = storeToRefs(keyboardStore)
|
|
||||||
|
|
||||||
const { slidesLoadLimit } = useLoadSlides()
|
const { slidesLoadLimit } = useLoadSlides()
|
||||||
|
|
||||||
const selectedSlidesIndex = computed(() => [..._selectedSlidesIndex.value, slideIndex.value])
|
const selectedSlidesIndex = computed(() => [..._selectedSlidesIndex.value, slideIndex.value])
|
||||||
|
|
||||||
const presetLayoutPopoverVisible = ref(false)
|
const presetLayoutPopoverVisible = ref(false)
|
||||||
|
|
||||||
const {
|
const {
|
||||||
copySlide,
|
copySlide,
|
||||||
pasteSlide,
|
pasteSlide,
|
||||||
createSlide,
|
createSlide,
|
||||||
@ -87,18 +79,18 @@ export default defineComponent({
|
|||||||
deleteSlide,
|
deleteSlide,
|
||||||
cutSlide,
|
cutSlide,
|
||||||
selectAllSlide,
|
selectAllSlide,
|
||||||
} = useSlideHandler()
|
} = useSlideHandler()
|
||||||
|
|
||||||
// 切换页面
|
// 切换页面
|
||||||
const changSlideIndex = (index: number) => {
|
const changSlideIndex = (index: number) => {
|
||||||
mainStore.setActiveElementIdList([])
|
mainStore.setActiveElementIdList([])
|
||||||
|
|
||||||
if (slideIndex.value === index) return
|
if (slideIndex.value === index) return
|
||||||
slidesStore.updateSlideIndex(index)
|
slidesStore.updateSlideIndex(index)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 点击缩略图
|
// 点击缩略图
|
||||||
const handleClickSlideThumbnail = (e: MouseEvent, index: number) => {
|
const handleClickSlideThumbnail = (e: MouseEvent, index: number) => {
|
||||||
const isMultiSelected = selectedSlidesIndex.value.length > 1
|
const isMultiSelected = selectedSlidesIndex.value.length > 1
|
||||||
|
|
||||||
if (isMultiSelected && selectedSlidesIndex.value.includes(index) && e.button !== 0) return
|
if (isMultiSelected && selectedSlidesIndex.value.includes(index) && e.button !== 0) return
|
||||||
@ -146,18 +138,18 @@ export default defineComponent({
|
|||||||
mainStore.updateSelectedSlidesIndex([])
|
mainStore.updateSelectedSlidesIndex([])
|
||||||
changSlideIndex(index)
|
changSlideIndex(index)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 设置缩略图工具栏聚焦状态(只有聚焦状态下,该部分的快捷键才能生效)
|
// 设置缩略图工具栏聚焦状态(只有聚焦状态下,该部分的快捷键才能生效)
|
||||||
const setThumbnailsFocus = (focus: boolean) => {
|
const setThumbnailsFocus = (focus: boolean) => {
|
||||||
if (thumbnailsFocus.value === focus) return
|
if (thumbnailsFocus.value === focus) return
|
||||||
mainStore.setThumbnailsFocus(focus)
|
mainStore.setThumbnailsFocus(focus)
|
||||||
|
|
||||||
if (!focus) mainStore.updateSelectedSlidesIndex([])
|
if (!focus) mainStore.updateSelectedSlidesIndex([])
|
||||||
}
|
}
|
||||||
|
|
||||||
// 拖拽调整顺序后进行数据的同步
|
// 拖拽调整顺序后进行数据的同步
|
||||||
const handleDragEnd = (eventData: { newIndex: number; oldIndex: number }) => {
|
const handleDragEnd = (eventData: { newIndex: number; oldIndex: number }) => {
|
||||||
const { newIndex, oldIndex } = eventData
|
const { newIndex, oldIndex } = eventData
|
||||||
if (oldIndex === newIndex) return
|
if (oldIndex === newIndex) return
|
||||||
|
|
||||||
@ -167,11 +159,11 @@ export default defineComponent({
|
|||||||
_slides.splice(newIndex, 0, _slide)
|
_slides.splice(newIndex, 0, _slide)
|
||||||
slidesStore.setSlides(_slides)
|
slidesStore.setSlides(_slides)
|
||||||
slidesStore.updateSlideIndex(newIndex)
|
slidesStore.updateSlideIndex(newIndex)
|
||||||
}
|
}
|
||||||
|
|
||||||
const { enterScreening, enterScreeningFromStart } = useScreening()
|
const { enterScreening, enterScreeningFromStart } = useScreening()
|
||||||
|
|
||||||
const contextmenusThumbnails = (): ContextmenuItem[] => {
|
const contextmenusThumbnails = (): ContextmenuItem[] => {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
text: '粘贴',
|
text: '粘贴',
|
||||||
@ -194,9 +186,9 @@ export default defineComponent({
|
|||||||
handler: enterScreeningFromStart,
|
handler: enterScreeningFromStart,
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
const contextmenusThumbnailItem = (): ContextmenuItem[] => {
|
const contextmenusThumbnailItem = (): ContextmenuItem[] => {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
text: '剪切',
|
text: '剪切',
|
||||||
@ -241,25 +233,7 @@ export default defineComponent({
|
|||||||
handler: enterScreening,
|
handler: enterScreening,
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
|
||||||
slides,
|
|
||||||
slideIndex,
|
|
||||||
selectedSlidesIndex,
|
|
||||||
presetLayoutPopoverVisible,
|
|
||||||
slidesLoadLimit,
|
|
||||||
createSlide,
|
|
||||||
createSlideByTemplate,
|
|
||||||
setThumbnailsFocus,
|
|
||||||
handleClickSlideThumbnail,
|
|
||||||
contextmenusThumbnails,
|
|
||||||
contextmenusThumbnailItem,
|
|
||||||
fillDigit,
|
|
||||||
handleDragEnd,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
})
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
@ -30,9 +30,9 @@
|
|||||||
<div
|
<div
|
||||||
class="animation-box"
|
class="animation-box"
|
||||||
:class="[
|
:class="[
|
||||||
`${prefix}animated`,
|
`${ANIMATION_CLASS_PREFIX}animated`,
|
||||||
`${prefix}fast`,
|
`${ANIMATION_CLASS_PREFIX}fast`,
|
||||||
hoverPreviewAnimation === item.value && `${prefix}${item.value}`,
|
hoverPreviewAnimation === item.value && `${ANIMATION_CLASS_PREFIX}${item.value}`,
|
||||||
]"
|
]"
|
||||||
>{{item.name}}</div>
|
>{{item.name}}</div>
|
||||||
</div>
|
</div>
|
||||||
@ -113,8 +113,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts" setup>
|
||||||
import { computed, defineComponent, ref, watch } from 'vue'
|
import { computed, ref, watch } from 'vue'
|
||||||
import { nanoid } from 'nanoid'
|
import { nanoid } from 'nanoid'
|
||||||
import { storeToRefs } from 'pinia'
|
import { storeToRefs } from 'pinia'
|
||||||
import { useMainStore, useSlidesStore } from '@/store'
|
import { useMainStore, useSlidesStore } from '@/store'
|
||||||
@ -157,34 +157,28 @@ interface TabItem {
|
|||||||
|
|
||||||
const animationTypes: AnimationType[] = ['in', 'out', 'attention']
|
const animationTypes: AnimationType[] = ['in', 'out', 'attention']
|
||||||
|
|
||||||
export default defineComponent({
|
const slidesStore = useSlidesStore()
|
||||||
name: 'element-animation-panel',
|
const { handleElement, handleElementId } = storeToRefs(useMainStore())
|
||||||
components: {
|
const { currentSlide, formatedAnimations, currentSlideAnimations } = storeToRefs(slidesStore)
|
||||||
Draggable,
|
|
||||||
},
|
|
||||||
setup() {
|
|
||||||
const slidesStore = useSlidesStore()
|
|
||||||
const { handleElement, handleElementId } = storeToRefs(useMainStore())
|
|
||||||
const { currentSlide, formatedAnimations, currentSlideAnimations } = storeToRefs(slidesStore)
|
|
||||||
|
|
||||||
const tabs: TabItem[] = [
|
const tabs: TabItem[] = [
|
||||||
{ key: 'in', label: '入场' },
|
{ key: 'in', label: '入场' },
|
||||||
{ key: 'out', label: '退场' },
|
{ key: 'out', label: '退场' },
|
||||||
{ key: 'attention', label: '强调' },
|
{ key: 'attention', label: '强调' },
|
||||||
]
|
]
|
||||||
const activeTab = ref('in')
|
const activeTab = ref('in')
|
||||||
|
|
||||||
watch(() => handleElementId.value, () => {
|
watch(() => handleElementId.value, () => {
|
||||||
animationPoolVisible.value = false
|
animationPoolVisible.value = false
|
||||||
})
|
})
|
||||||
|
|
||||||
const hoverPreviewAnimation = ref('')
|
const hoverPreviewAnimation = ref('')
|
||||||
const animationPoolVisible = ref(false)
|
const animationPoolVisible = ref(false)
|
||||||
|
|
||||||
const { addHistorySnapshot } = useHistorySnapshot()
|
const { addHistorySnapshot } = useHistorySnapshot()
|
||||||
|
|
||||||
// 当前页面的动画列表
|
// 当前页面的动画列表
|
||||||
const animationSequence = computed(() => {
|
const animationSequence = computed(() => {
|
||||||
const animationSequence = []
|
const animationSequence = []
|
||||||
for (let i = 0; i < formatedAnimations.value.length; i++) {
|
for (let i = 0; i < formatedAnimations.value.length; i++) {
|
||||||
const item = formatedAnimations.value[i]
|
const item = formatedAnimations.value[i]
|
||||||
@ -204,24 +198,24 @@ export default defineComponent({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
return animationSequence
|
return animationSequence
|
||||||
})
|
})
|
||||||
|
|
||||||
// 当前选中元素的入场动画信息
|
// 当前选中元素的入场动画信息
|
||||||
const handleElementAnimation = computed(() => {
|
const handleElementAnimation = computed(() => {
|
||||||
const animations = currentSlideAnimations.value
|
const animations = currentSlideAnimations.value
|
||||||
const animation = animations.filter(item => item.elId === handleElementId.value)
|
const animation = animations.filter(item => item.elId === handleElementId.value)
|
||||||
return animation || []
|
return animation || []
|
||||||
})
|
})
|
||||||
|
|
||||||
// 删除元素动画
|
// 删除元素动画
|
||||||
const deleteAnimation = (id: string) => {
|
const deleteAnimation = (id: string) => {
|
||||||
const animations = currentSlideAnimations.value.filter(item => item.id !== id)
|
const animations = currentSlideAnimations.value.filter(item => item.id !== id)
|
||||||
slidesStore.updateSlide({ animations })
|
slidesStore.updateSlide({ animations })
|
||||||
addHistorySnapshot()
|
addHistorySnapshot()
|
||||||
}
|
}
|
||||||
|
|
||||||
// 拖拽修改动画顺序后同步数据
|
// 拖拽修改动画顺序后同步数据
|
||||||
const handleDragEnd = (eventData: { newIndex: number; oldIndex: number }) => {
|
const handleDragEnd = (eventData: { newIndex: number; oldIndex: number }) => {
|
||||||
const { newIndex, oldIndex } = eventData
|
const { newIndex, oldIndex } = eventData
|
||||||
if (oldIndex === newIndex) return
|
if (oldIndex === newIndex) return
|
||||||
|
|
||||||
@ -232,10 +226,10 @@ export default defineComponent({
|
|||||||
|
|
||||||
slidesStore.updateSlide({ animations })
|
slidesStore.updateSlide({ animations })
|
||||||
addHistorySnapshot()
|
addHistorySnapshot()
|
||||||
}
|
}
|
||||||
|
|
||||||
// 执行动画预览
|
// 执行动画预览
|
||||||
const runAnimation = (elId: string, effect: string, duration: number) => {
|
const runAnimation = (elId: string, effect: string, duration: number) => {
|
||||||
const elRef = document.querySelector(`#editable-element-${elId} [class^=editable-element-]`)
|
const elRef = document.querySelector(`#editable-element-${elId} [class^=editable-element-]`)
|
||||||
if (elRef) {
|
if (elRef) {
|
||||||
const animationName = `${ANIMATION_CLASS_PREFIX}${effect}`
|
const animationName = `${ANIMATION_CLASS_PREFIX}${effect}`
|
||||||
@ -248,10 +242,10 @@ export default defineComponent({
|
|||||||
}
|
}
|
||||||
elRef.addEventListener('animationend', handleAnimationEnd, { once: true })
|
elRef.addEventListener('animationend', handleAnimationEnd, { once: true })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 修改元素动画持续时间
|
// 修改元素动画持续时间
|
||||||
const updateElementAnimationDuration = (id: string, duration: number) => {
|
const updateElementAnimationDuration = (id: string, duration: number) => {
|
||||||
if (duration < 100 || duration > 5000) return
|
if (duration < 100 || duration > 5000) return
|
||||||
|
|
||||||
const animations = currentSlideAnimations.value.map(item => {
|
const animations = currentSlideAnimations.value.map(item => {
|
||||||
@ -260,20 +254,20 @@ export default defineComponent({
|
|||||||
})
|
})
|
||||||
slidesStore.updateSlide({ animations })
|
slidesStore.updateSlide({ animations })
|
||||||
addHistorySnapshot()
|
addHistorySnapshot()
|
||||||
}
|
}
|
||||||
|
|
||||||
// 修改触发方式
|
// 修改触发方式
|
||||||
const updateElementAnimationTrigger = (id: string, trigger: 'click' | 'meantime' | 'auto') => {
|
const updateElementAnimationTrigger = (id: string, trigger: 'click' | 'meantime' | 'auto') => {
|
||||||
const animations = currentSlideAnimations.value.map(item => {
|
const animations = currentSlideAnimations.value.map(item => {
|
||||||
if (item.id === id) return { ...item, trigger }
|
if (item.id === id) return { ...item, trigger }
|
||||||
return item
|
return item
|
||||||
})
|
})
|
||||||
slidesStore.updateSlide({ animations })
|
slidesStore.updateSlide({ animations })
|
||||||
addHistorySnapshot()
|
addHistorySnapshot()
|
||||||
}
|
}
|
||||||
|
|
||||||
// 修改元素动画,并执行一次预览
|
// 修改元素动画,并执行一次预览
|
||||||
const updateElementAnimation = (type: AnimationType, effect: string) => {
|
const updateElementAnimation = (type: AnimationType, effect: string) => {
|
||||||
const animations = currentSlideAnimations.value.map(item => {
|
const animations = currentSlideAnimations.value.map(item => {
|
||||||
if (item.id === handleAnimationId.value) return { ...item, type, effect }
|
if (item.id === handleAnimationId.value) return { ...item, type, effect }
|
||||||
return item
|
return item
|
||||||
@ -286,11 +280,11 @@ export default defineComponent({
|
|||||||
const duration = animationItem?.duration || ANIMATION_DEFAULT_DURATION
|
const duration = animationItem?.duration || ANIMATION_DEFAULT_DURATION
|
||||||
|
|
||||||
runAnimation(handleElementId.value, effect, duration)
|
runAnimation(handleElementId.value, effect, duration)
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleAnimationId = ref('')
|
const handleAnimationId = ref('')
|
||||||
// 添加元素动画,并执行一次预览
|
// 添加元素动画,并执行一次预览
|
||||||
const addAnimation = (type: AnimationType, effect: string) => {
|
const addAnimation = (type: AnimationType, effect: string) => {
|
||||||
if (handleAnimationId.value) {
|
if (handleAnimationId.value) {
|
||||||
updateElementAnimation(type, effect)
|
updateElementAnimation(type, effect)
|
||||||
return
|
return
|
||||||
@ -310,51 +304,28 @@ export default defineComponent({
|
|||||||
addHistorySnapshot()
|
addHistorySnapshot()
|
||||||
|
|
||||||
runAnimation(handleElementId.value, effect, ANIMATION_DEFAULT_DURATION)
|
runAnimation(handleElementId.value, effect, ANIMATION_DEFAULT_DURATION)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 动画选择面板打开600ms后再移除遮罩层,否则打开面板后迅速滑入鼠标预览会导致抖动
|
// 动画选择面板打开600ms后再移除遮罩层,否则打开面板后迅速滑入鼠标预览会导致抖动
|
||||||
const popoverMaskHide = ref(false)
|
const popoverMaskHide = ref(false)
|
||||||
const handlePopoverVisibleChange = (visible: boolean) => {
|
const handlePopoverVisibleChange = (visible: boolean) => {
|
||||||
if (visible) {
|
if (visible) {
|
||||||
setTimeout(() => popoverMaskHide.value = true, 600)
|
setTimeout(() => popoverMaskHide.value = true, 600)
|
||||||
}
|
}
|
||||||
else popoverMaskHide.value = false
|
else popoverMaskHide.value = false
|
||||||
}
|
}
|
||||||
|
|
||||||
const openAnimationPool = (elementId: string) => {
|
const openAnimationPool = (elementId: string) => {
|
||||||
animationPoolVisible.value = true
|
animationPoolVisible.value = true
|
||||||
handleAnimationId.value = elementId
|
handleAnimationId.value = elementId
|
||||||
handlePopoverVisibleChange(true)
|
handlePopoverVisibleChange(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
const animations = {
|
||||||
tabs,
|
|
||||||
activeTab,
|
|
||||||
handleAnimationId,
|
|
||||||
handleElement,
|
|
||||||
animationPoolVisible,
|
|
||||||
animationSequence,
|
|
||||||
hoverPreviewAnimation,
|
|
||||||
handleElementAnimation,
|
|
||||||
popoverMaskHide,
|
|
||||||
animations: {
|
|
||||||
in: ENTER_ANIMATIONS,
|
in: ENTER_ANIMATIONS,
|
||||||
out: EXIT_ANIMATIONS,
|
out: EXIT_ANIMATIONS,
|
||||||
attention: ATTENTION_ANIMATIONS,
|
attention: ATTENTION_ANIMATIONS,
|
||||||
},
|
}
|
||||||
prefix: ANIMATION_CLASS_PREFIX,
|
|
||||||
animationTypes,
|
|
||||||
addAnimation,
|
|
||||||
deleteAnimation,
|
|
||||||
handleDragEnd,
|
|
||||||
runAnimation,
|
|
||||||
updateElementAnimationDuration,
|
|
||||||
updateElementAnimationTrigger,
|
|
||||||
handlePopoverVisibleChange,
|
|
||||||
openAnimationPool,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
})
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
@ -2,12 +2,12 @@
|
|||||||
<div class="element-positopn-panel">
|
<div class="element-positopn-panel">
|
||||||
<div class="title">层级:</div>
|
<div class="title">层级:</div>
|
||||||
<ButtonGroup class="row">
|
<ButtonGroup class="row">
|
||||||
<Button style="flex: 1;" @click="orderElement(handleElement, ElementOrderCommands.TOP)"><IconSendToBack class="btn-icon" /> 置于顶层</Button>
|
<Button style="flex: 1;" @click="orderElement(handleElement!, ElementOrderCommands.TOP)"><IconSendToBack class="btn-icon" /> 置于顶层</Button>
|
||||||
<Button style="flex: 1;" @click="orderElement(handleElement, ElementOrderCommands.BOTTOM)"><IconBringToFrontOne class="btn-icon" /> 置于底层</Button>
|
<Button style="flex: 1;" @click="orderElement(handleElement!, ElementOrderCommands.BOTTOM)"><IconBringToFrontOne class="btn-icon" /> 置于底层</Button>
|
||||||
</ButtonGroup>
|
</ButtonGroup>
|
||||||
<ButtonGroup class="row">
|
<ButtonGroup class="row">
|
||||||
<Button style="flex: 1;" @click="orderElement(handleElement, ElementOrderCommands.UP)"><IconBringToFront class="btn-icon" /> 上移一层</Button>
|
<Button style="flex: 1;" @click="orderElement(handleElement!, ElementOrderCommands.UP)"><IconBringToFront class="btn-icon" /> 上移一层</Button>
|
||||||
<Button style="flex: 1;" @click="orderElement(handleElement, ElementOrderCommands.DOWN)"><IconSentToBack class="btn-icon" /> 下移一层</Button>
|
<Button style="flex: 1;" @click="orderElement(handleElement!, ElementOrderCommands.DOWN)"><IconSentToBack class="btn-icon" /> 下移一层</Button>
|
||||||
</ButtonGroup>
|
</ButtonGroup>
|
||||||
|
|
||||||
<Divider />
|
<Divider />
|
||||||
@ -61,7 +61,7 @@
|
|||||||
<div style="flex: 4;" class="label">Y</div>
|
<div style="flex: 4;" class="label">Y</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<template v-if="handleElement.type !== 'line'">
|
<template v-if="handleElement!.type !== 'line'">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div style="flex: 3;">大小:</div>
|
<div style="flex: 3;">大小:</div>
|
||||||
<InputNumber
|
<InputNumber
|
||||||
@ -72,7 +72,7 @@
|
|||||||
@change="value => updateWidth(value as number)"
|
@change="value => updateWidth(value as number)"
|
||||||
style="flex: 4;"
|
style="flex: 4;"
|
||||||
/>
|
/>
|
||||||
<template v-if="['image', 'shape', 'audio'].includes(handleElement.type)">
|
<template v-if="['image', 'shape', 'audio'].includes(handleElement!.type)">
|
||||||
<Tooltip :mouseLeaveDelay="0" :mouseEnterDelay="0.5" title="解除宽高比锁定" v-if="fixedRatio">
|
<Tooltip :mouseLeaveDelay="0" :mouseEnterDelay="0.5" title="解除宽高比锁定" v-if="fixedRatio">
|
||||||
<IconLock style="flex: 1;" class="icon-btn" @click="updateFixedRatio(false)" />
|
<IconLock style="flex: 1;" class="icon-btn" @click="updateFixedRatio(false)" />
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
@ -85,7 +85,7 @@
|
|||||||
:min="minSize"
|
:min="minSize"
|
||||||
:max="800"
|
:max="800"
|
||||||
:step="5"
|
:step="5"
|
||||||
:disabled="handleElement.type === 'text'"
|
:disabled="handleElement!.type === 'text'"
|
||||||
:value="height"
|
:value="height"
|
||||||
@change="value => updateHeight(value as number)"
|
@change="value => updateHeight(value as number)"
|
||||||
style="flex: 4;"
|
style="flex: 4;"
|
||||||
@ -99,7 +99,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template v-if="!['line', 'video', 'audio'].includes(handleElement.type)">
|
<template v-if="!['line', 'video', 'audio'].includes(handleElement!.type)">
|
||||||
<Divider />
|
<Divider />
|
||||||
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
@ -131,37 +131,33 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts" setup>
|
||||||
import { computed, defineComponent, Ref, ref, watch } from 'vue'
|
import { computed, ref, watch } from 'vue'
|
||||||
import { round } from 'lodash'
|
import { round } from 'lodash'
|
||||||
import { storeToRefs } from 'pinia'
|
import { storeToRefs } from 'pinia'
|
||||||
import { useMainStore, useSlidesStore } from '@/store'
|
import { useMainStore, useSlidesStore } from '@/store'
|
||||||
import { PPTElement } from '@/types/slides'
|
|
||||||
import { ElementAlignCommands, ElementOrderCommands } from '@/types/edit'
|
import { ElementAlignCommands, ElementOrderCommands } from '@/types/edit'
|
||||||
import { MIN_SIZE } from '@/configs/element'
|
import { MIN_SIZE } from '@/configs/element'
|
||||||
import useOrderElement from '@/hooks/useOrderElement'
|
import useOrderElement from '@/hooks/useOrderElement'
|
||||||
import useAlignElementToCanvas from '@/hooks/useAlignElementToCanvas'
|
import useAlignElementToCanvas from '@/hooks/useAlignElementToCanvas'
|
||||||
import useHistorySnapshot from '@/hooks/useHistorySnapshot'
|
import useHistorySnapshot from '@/hooks/useHistorySnapshot'
|
||||||
|
|
||||||
export default defineComponent({
|
const slidesStore = useSlidesStore()
|
||||||
name: 'element-positopn-panel',
|
const { handleElement, handleElementId } = storeToRefs(useMainStore())
|
||||||
setup() {
|
|
||||||
const slidesStore = useSlidesStore()
|
|
||||||
const { handleElement, handleElementId } = storeToRefs(useMainStore())
|
|
||||||
|
|
||||||
const left = ref(0)
|
const left = ref(0)
|
||||||
const top = ref(0)
|
const top = ref(0)
|
||||||
const width = ref(0)
|
const width = ref(0)
|
||||||
const height = ref(0)
|
const height = ref(0)
|
||||||
const rotate = ref(0)
|
const rotate = ref(0)
|
||||||
const fixedRatio = ref(false)
|
const fixedRatio = ref(false)
|
||||||
|
|
||||||
const minSize = computed(() => {
|
const minSize = computed(() => {
|
||||||
if (!handleElement.value) return 20
|
if (!handleElement.value) return 20
|
||||||
return MIN_SIZE[handleElement.value.type] || 20
|
return MIN_SIZE[handleElement.value.type] || 20
|
||||||
})
|
})
|
||||||
|
|
||||||
watch(handleElement, () => {
|
watch(handleElement, () => {
|
||||||
if (!handleElement.value) return
|
if (!handleElement.value) return
|
||||||
|
|
||||||
left.value = round(handleElement.value.left, 1)
|
left.value = round(handleElement.value.left, 1)
|
||||||
@ -174,51 +170,51 @@ export default defineComponent({
|
|||||||
height.value = round(handleElement.value.height, 1)
|
height.value = round(handleElement.value.height, 1)
|
||||||
rotate.value = 'rotate' in handleElement.value && handleElement.value.rotate !== undefined ? round(handleElement.value.rotate, 1) : 0
|
rotate.value = 'rotate' in handleElement.value && handleElement.value.rotate !== undefined ? round(handleElement.value.rotate, 1) : 0
|
||||||
}
|
}
|
||||||
}, { deep: true, immediate: true })
|
}, { deep: true, immediate: true })
|
||||||
|
|
||||||
const { orderElement } = useOrderElement()
|
const { orderElement } = useOrderElement()
|
||||||
const { alignElementToCanvas } = useAlignElementToCanvas()
|
const { alignElementToCanvas } = useAlignElementToCanvas()
|
||||||
|
|
||||||
const { addHistorySnapshot } = useHistorySnapshot()
|
const { addHistorySnapshot } = useHistorySnapshot()
|
||||||
|
|
||||||
// 设置元素位置
|
// 设置元素位置
|
||||||
const updateLeft = (value: number) => {
|
const updateLeft = (value: number) => {
|
||||||
const props = { left: value }
|
const props = { left: value }
|
||||||
slidesStore.updateElement({ id: handleElementId.value, props })
|
slidesStore.updateElement({ id: handleElementId.value, props })
|
||||||
addHistorySnapshot()
|
addHistorySnapshot()
|
||||||
}
|
}
|
||||||
const updateTop = (value: number) => {
|
const updateTop = (value: number) => {
|
||||||
const props = { top: value }
|
const props = { top: value }
|
||||||
slidesStore.updateElement({ id: handleElementId.value, props })
|
slidesStore.updateElement({ id: handleElementId.value, props })
|
||||||
addHistorySnapshot()
|
addHistorySnapshot()
|
||||||
}
|
}
|
||||||
|
|
||||||
// 设置元素宽度、高度、旋转角度
|
// 设置元素宽度、高度、旋转角度
|
||||||
const updateWidth = (value: number) => {
|
const updateWidth = (value: number) => {
|
||||||
const props = { width: value }
|
const props = { width: value }
|
||||||
slidesStore.updateElement({ id: handleElementId.value, props })
|
slidesStore.updateElement({ id: handleElementId.value, props })
|
||||||
addHistorySnapshot()
|
addHistorySnapshot()
|
||||||
}
|
}
|
||||||
const updateHeight = (value: number) => {
|
const updateHeight = (value: number) => {
|
||||||
const props = { height: value }
|
const props = { height: value }
|
||||||
slidesStore.updateElement({ id: handleElementId.value, props })
|
slidesStore.updateElement({ id: handleElementId.value, props })
|
||||||
addHistorySnapshot()
|
addHistorySnapshot()
|
||||||
}
|
}
|
||||||
const updateRotate = (value: number) => {
|
const updateRotate = (value: number) => {
|
||||||
const props = { rotate: value }
|
const props = { rotate: value }
|
||||||
slidesStore.updateElement({ id: handleElementId.value, props })
|
slidesStore.updateElement({ id: handleElementId.value, props })
|
||||||
addHistorySnapshot()
|
addHistorySnapshot()
|
||||||
}
|
}
|
||||||
|
|
||||||
// 固定元素的宽高比
|
// 固定元素的宽高比
|
||||||
const updateFixedRatio = (value: boolean) => {
|
const updateFixedRatio = (value: boolean) => {
|
||||||
const props = { fixedRatio: value }
|
const props = { fixedRatio: value }
|
||||||
slidesStore.updateElement({ id: handleElementId.value, props })
|
slidesStore.updateElement({ id: handleElementId.value, props })
|
||||||
addHistorySnapshot()
|
addHistorySnapshot()
|
||||||
}
|
}
|
||||||
|
|
||||||
// 将元素旋转45度(顺时针或逆时针)
|
// 将元素旋转45度(顺时针或逆时针)
|
||||||
const updateRotate45 = (command: '+' | '-') => {
|
const updateRotate45 = (command: '+' | '-') => {
|
||||||
let _rotate = Math.floor(rotate.value / 45) * 45
|
let _rotate = Math.floor(rotate.value / 45) * 45
|
||||||
if (command === '+') _rotate = _rotate + 45
|
if (command === '+') _rotate = _rotate + 45
|
||||||
else if (command === '-') _rotate = _rotate - 45
|
else if (command === '-') _rotate = _rotate - 45
|
||||||
@ -229,31 +225,7 @@ export default defineComponent({
|
|||||||
const props = { rotate: _rotate }
|
const props = { rotate: _rotate }
|
||||||
slidesStore.updateElement({ id: handleElementId.value, props })
|
slidesStore.updateElement({ id: handleElementId.value, props })
|
||||||
addHistorySnapshot()
|
addHistorySnapshot()
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
|
||||||
handleElement: handleElement as Ref<PPTElement>,
|
|
||||||
orderElement,
|
|
||||||
alignElementToCanvas,
|
|
||||||
left,
|
|
||||||
top,
|
|
||||||
width,
|
|
||||||
height,
|
|
||||||
rotate,
|
|
||||||
fixedRatio,
|
|
||||||
minSize,
|
|
||||||
updateLeft,
|
|
||||||
updateTop,
|
|
||||||
updateWidth,
|
|
||||||
updateHeight,
|
|
||||||
updateRotate,
|
|
||||||
updateFixedRatio,
|
|
||||||
updateRotate45,
|
|
||||||
ElementOrderCommands,
|
|
||||||
ElementAlignCommands,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
})
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
@ -5,11 +5,11 @@
|
|||||||
<Popover trigger="click">
|
<Popover trigger="click">
|
||||||
<template #content>
|
<template #content>
|
||||||
<ColorPicker
|
<ColorPicker
|
||||||
:modelValue="handleElement.color"
|
:modelValue="handleAudioElement.color"
|
||||||
@update:modelValue="value => updateAudio({ color: value })"
|
@update:modelValue="value => updateAudio({ color: value })"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
<ColorButton :color="handleElement.color" style="flex: 3;" />
|
<ColorButton :color="handleAudioElement.color" style="flex: 3;" />
|
||||||
</Popover>
|
</Popover>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -17,7 +17,7 @@
|
|||||||
<div style="flex: 2;">自动播放:</div>
|
<div style="flex: 2;">自动播放:</div>
|
||||||
<div class="switch-wrapper" style="flex: 3;">
|
<div class="switch-wrapper" style="flex: 3;">
|
||||||
<Switch
|
<Switch
|
||||||
:checked="handleElement.autoplay"
|
:checked="handleAudioElement.autoplay"
|
||||||
@change="checked => updateAudio({ autoplay: checked as boolean })"
|
@change="checked => updateAudio({ autoplay: checked as boolean })"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@ -27,7 +27,7 @@
|
|||||||
<div style="flex: 2;">循环播放:</div>
|
<div style="flex: 2;">循环播放:</div>
|
||||||
<div class="switch-wrapper" style="flex: 3;">
|
<div class="switch-wrapper" style="flex: 3;">
|
||||||
<Switch
|
<Switch
|
||||||
:checked="handleElement.loop"
|
:checked="handleAudioElement.loop"
|
||||||
@change="checked => updateAudio({ loop: checked as boolean })"
|
@change="checked => updateAudio({ loop: checked as boolean })"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@ -35,8 +35,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts" setup>
|
||||||
import { defineComponent, Ref } from 'vue'
|
import { Ref } from 'vue'
|
||||||
import { storeToRefs } from 'pinia'
|
import { storeToRefs } from 'pinia'
|
||||||
import { useMainStore, useSlidesStore } from '@/store'
|
import { useMainStore, useSlidesStore } from '@/store'
|
||||||
import { PPTAudioElement } from '@/types/slides'
|
import { PPTAudioElement } from '@/types/slides'
|
||||||
@ -44,29 +44,18 @@ import useHistorySnapshot from '@/hooks/useHistorySnapshot'
|
|||||||
|
|
||||||
import ColorButton from '../common/ColorButton.vue'
|
import ColorButton from '../common/ColorButton.vue'
|
||||||
|
|
||||||
export default defineComponent({
|
const slidesStore = useSlidesStore()
|
||||||
name: 'audio-style-panel',
|
const { handleElement } = storeToRefs(useMainStore())
|
||||||
components: {
|
|
||||||
ColorButton,
|
|
||||||
},
|
|
||||||
setup() {
|
|
||||||
const slidesStore = useSlidesStore()
|
|
||||||
const { handleElement } = storeToRefs(useMainStore())
|
|
||||||
|
|
||||||
const { addHistorySnapshot } = useHistorySnapshot()
|
const handleAudioElement = handleElement as Ref<PPTAudioElement>
|
||||||
|
|
||||||
const updateAudio = (props: Partial<PPTAudioElement>) => {
|
const { addHistorySnapshot } = useHistorySnapshot()
|
||||||
|
|
||||||
|
const updateAudio = (props: Partial<PPTAudioElement>) => {
|
||||||
if (!handleElement.value) return
|
if (!handleElement.value) return
|
||||||
slidesStore.updateElement({ id: handleElement.value.id, props })
|
slidesStore.updateElement({ id: handleElement.value.id, props })
|
||||||
addHistorySnapshot()
|
addHistorySnapshot()
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
|
||||||
handleElement: handleElement as Ref<PPTAudioElement>,
|
|
||||||
updateAudio,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
@ -54,31 +54,33 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts" setup>
|
||||||
import { computed, defineComponent, onMounted, onUnmounted, PropType, ref } from 'vue'
|
import { computed, onMounted, onUnmounted, PropType, ref } from 'vue'
|
||||||
import { ChartData } from '@/types/slides'
|
import { ChartData } from '@/types/slides'
|
||||||
import { KEYS } from '@/configs/hotkey'
|
import { KEYS } from '@/configs/hotkey'
|
||||||
import { pasteCustomClipboardString, pasteExcelClipboardString } from '@/utils/clipboard'
|
import { pasteCustomClipboardString, pasteExcelClipboardString } from '@/utils/clipboard'
|
||||||
|
|
||||||
const CELL_WIDTH = 100
|
const props = defineProps({
|
||||||
const CELL_HEIGHT = 32
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
name: 'chart-data-editor',
|
|
||||||
emits: ['save', 'close'],
|
|
||||||
props: {
|
|
||||||
data: {
|
data: {
|
||||||
type: Object as PropType<ChartData>,
|
type: Object as PropType<ChartData>,
|
||||||
required: true,
|
required: true,
|
||||||
}
|
}
|
||||||
},
|
})
|
||||||
setup(props, { emit }) {
|
|
||||||
const selectedRange = ref([0, 0])
|
|
||||||
const tempRangeSize = ref({ width: 0, height: 0 })
|
|
||||||
const focusCell = ref<[number, number] | null>(null)
|
|
||||||
|
|
||||||
// 当前选区的边框线条位置
|
const emit = defineEmits<{
|
||||||
const rangeLines = computed(() => {
|
(event: 'save', payload: ChartData): void
|
||||||
|
(event: 'close'): void
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const CELL_WIDTH = 100
|
||||||
|
const CELL_HEIGHT = 32
|
||||||
|
|
||||||
|
const selectedRange = ref([0, 0])
|
||||||
|
const tempRangeSize = ref({ width: 0, height: 0 })
|
||||||
|
const focusCell = ref<[number, number] | null>(null)
|
||||||
|
|
||||||
|
// 当前选区的边框线条位置
|
||||||
|
const rangeLines = computed(() => {
|
||||||
const width = selectedRange.value[0] * CELL_WIDTH
|
const width = selectedRange.value[0] * CELL_WIDTH
|
||||||
const height = selectedRange.value[1] * CELL_HEIGHT
|
const height = selectedRange.value[1] * CELL_HEIGHT
|
||||||
return [
|
return [
|
||||||
@ -87,17 +89,17 @@ export default defineComponent({
|
|||||||
{ type: 'l', style: {height: height + 'px'} },
|
{ type: 'l', style: {height: height + 'px'} },
|
||||||
{ type: 'r', style: {left: width + 'px', height: height + 'px'} },
|
{ type: 'r', style: {left: width + 'px', height: height + 'px'} },
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
|
|
||||||
// 当前选区的缩放点位置
|
// 当前选区的缩放点位置
|
||||||
const resizablePointStyle = computed(() => {
|
const resizablePointStyle = computed(() => {
|
||||||
const width = selectedRange.value[0] * CELL_WIDTH
|
const width = selectedRange.value[0] * CELL_WIDTH
|
||||||
const height = selectedRange.value[1] * CELL_HEIGHT
|
const height = selectedRange.value[1] * CELL_HEIGHT
|
||||||
return { left: width + 'px', top: height + 'px' }
|
return { left: width + 'px', top: height + 'px' }
|
||||||
})
|
})
|
||||||
|
|
||||||
// 初始化图表数据:将数据格式化并填充到DOM
|
// 初始化图表数据:将数据格式化并填充到DOM
|
||||||
const initData = () => {
|
const initData = () => {
|
||||||
const _data: string[][] = []
|
const _data: string[][] = []
|
||||||
|
|
||||||
const { labels, legends, series } = props.data
|
const { labels, legends, series } = props.data
|
||||||
@ -122,33 +124,33 @@ export default defineComponent({
|
|||||||
}
|
}
|
||||||
|
|
||||||
selectedRange.value = [colCount + 1, rowCount + 1]
|
selectedRange.value = [colCount + 1, rowCount + 1]
|
||||||
}
|
}
|
||||||
|
|
||||||
onMounted(initData)
|
onMounted(initData)
|
||||||
|
|
||||||
// 快捷键监听:回车移动焦点到下一行
|
// 快捷键监听:回车移动焦点到下一行
|
||||||
const moveNextRow = () => {
|
const moveNextRow = () => {
|
||||||
if (!focusCell.value) return
|
if (!focusCell.value) return
|
||||||
|
|
||||||
const [rowIndex, colIndex] = focusCell.value
|
const [rowIndex, colIndex] = focusCell.value
|
||||||
const inputRef = document.querySelector(`#cell-${rowIndex + 1}-${colIndex}`) as HTMLInputElement
|
const inputRef = document.querySelector(`#cell-${rowIndex + 1}-${colIndex}`) as HTMLInputElement
|
||||||
inputRef && inputRef.focus()
|
inputRef && inputRef.focus()
|
||||||
}
|
}
|
||||||
|
|
||||||
const keyboardListener = (e: KeyboardEvent) => {
|
const keyboardListener = (e: KeyboardEvent) => {
|
||||||
const key = e.key.toUpperCase()
|
const key = e.key.toUpperCase()
|
||||||
if (key === KEYS.ENTER) moveNextRow()
|
if (key === KEYS.ENTER) moveNextRow()
|
||||||
}
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
document.addEventListener('keydown', keyboardListener)
|
document.addEventListener('keydown', keyboardListener)
|
||||||
})
|
})
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
document.removeEventListener('keydown', keyboardListener)
|
document.removeEventListener('keydown', keyboardListener)
|
||||||
})
|
})
|
||||||
|
|
||||||
// 获取当前图表DOM中的数据,整理格式化后传递出去
|
// 获取当前图表DOM中的数据,整理格式化后传递出去
|
||||||
const getTableData = () => {
|
const getTableData = () => {
|
||||||
const [col, row] = selectedRange.value
|
const [col, row] = selectedRange.value
|
||||||
|
|
||||||
const labels: string[] = []
|
const labels: string[] = []
|
||||||
@ -183,10 +185,10 @@ export default defineComponent({
|
|||||||
}
|
}
|
||||||
|
|
||||||
emit('save', { labels, legends, series })
|
emit('save', { labels, legends, series })
|
||||||
}
|
}
|
||||||
|
|
||||||
// 清空表格数据
|
// 清空表格数据
|
||||||
const clear = () => {
|
const clear = () => {
|
||||||
for (let rowIndex = 1; rowIndex < 31; rowIndex++) {
|
for (let rowIndex = 1; rowIndex < 31; rowIndex++) {
|
||||||
for (let colIndex = 1; colIndex < 7; colIndex++) {
|
for (let colIndex = 1; colIndex < 7; colIndex++) {
|
||||||
const inputRef = document.querySelector(`#cell-${rowIndex}-${colIndex}`) as HTMLInputElement
|
const inputRef = document.querySelector(`#cell-${rowIndex}-${colIndex}`) as HTMLInputElement
|
||||||
@ -194,10 +196,10 @@ export default defineComponent({
|
|||||||
inputRef.value = ''
|
inputRef.value = ''
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 自定义粘贴事件(尝试读取剪贴板中的表格数据)
|
// 自定义粘贴事件(尝试读取剪贴板中的表格数据)
|
||||||
const handlePaste = (e: ClipboardEvent, rowIndex: number, colIndex: number) => {
|
const handlePaste = (e: ClipboardEvent, rowIndex: number, colIndex: number) => {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
|
|
||||||
if (!e.clipboardData) return
|
if (!e.clipboardData) return
|
||||||
@ -223,13 +225,13 @@ export default defineComponent({
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 关闭图表数据编辑器
|
// 关闭图表数据编辑器
|
||||||
const closeEditor = () => emit('close')
|
const closeEditor = () => emit('close')
|
||||||
|
|
||||||
// 鼠标拖拽修改选中的数据范围
|
// 鼠标拖拽修改选中的数据范围
|
||||||
const changeSelectRange = (e: MouseEvent) => {
|
const changeSelectRange = (e: MouseEvent) => {
|
||||||
let isMouseDown = true
|
let isMouseDown = true
|
||||||
|
|
||||||
const startPageX = e.pageX
|
const startPageX = e.pageX
|
||||||
@ -278,22 +280,7 @@ export default defineComponent({
|
|||||||
selectedRange.value = [col, row]
|
selectedRange.value = [col, row]
|
||||||
tempRangeSize.value = { width: 0, height: 0 }
|
tempRangeSize.value = { width: 0, height: 0 }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
|
||||||
tempRangeSize,
|
|
||||||
rangeLines,
|
|
||||||
resizablePointStyle,
|
|
||||||
selectedRange,
|
|
||||||
focusCell,
|
|
||||||
changeSelectRange,
|
|
||||||
getTableData,
|
|
||||||
closeEditor,
|
|
||||||
clear,
|
|
||||||
handlePaste,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
})
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
@ -6,7 +6,7 @@
|
|||||||
|
|
||||||
<Divider />
|
<Divider />
|
||||||
|
|
||||||
<template v-if="handleElement.chartType === 'line'">
|
<template v-if="handleChartElement.chartType === 'line'">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<Checkbox
|
<Checkbox
|
||||||
@change="e => updateOptions({ showArea: e.target.checked })"
|
@change="e => updateOptions({ showArea: e.target.checked })"
|
||||||
@ -26,7 +26,7 @@
|
|||||||
>使用平滑曲线</Checkbox>
|
>使用平滑曲线</Checkbox>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<div class="row" v-if="handleElement.chartType === 'bar'">
|
<div class="row" v-if="handleChartElement.chartType === 'bar'">
|
||||||
<Checkbox
|
<Checkbox
|
||||||
@change="e => updateOptions({ horizontalBars: e.target.checked })"
|
@change="e => updateOptions({ horizontalBars: e.target.checked })"
|
||||||
:checked="horizontalBars"
|
:checked="horizontalBars"
|
||||||
@ -36,7 +36,7 @@
|
|||||||
:checked="stackBars"
|
:checked="stackBars"
|
||||||
>堆叠样式</Checkbox>
|
>堆叠样式</Checkbox>
|
||||||
</div>
|
</div>
|
||||||
<div class="row" v-if="handleElement.chartType === 'pie'">
|
<div class="row" v-if="handleChartElement.chartType === 'pie'">
|
||||||
<Checkbox
|
<Checkbox
|
||||||
@change="e => updateOptions({ donut: e.target.checked })"
|
@change="e => updateOptions({ donut: e.target.checked })"
|
||||||
:checked="donut"
|
:checked="donut"
|
||||||
@ -143,7 +143,7 @@
|
|||||||
destroyOnClose
|
destroyOnClose
|
||||||
>
|
>
|
||||||
<ChartDataEditor
|
<ChartDataEditor
|
||||||
:data="handleElement.data"
|
:data="handleChartElement.data"
|
||||||
@close="chartDataEditorVisible = false"
|
@close="chartDataEditorVisible = false"
|
||||||
@save="value => updateData(value)"
|
@save="value => updateData(value)"
|
||||||
/>
|
/>
|
||||||
@ -151,8 +151,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts" setup>
|
||||||
import { defineComponent, onUnmounted, Ref, ref, watch } from 'vue'
|
import { onUnmounted, Ref, ref, watch } from 'vue'
|
||||||
import { storeToRefs } from 'pinia'
|
import { storeToRefs } from 'pinia'
|
||||||
import { useMainStore, useSlidesStore } from '@/store'
|
import { useMainStore, useSlidesStore } from '@/store'
|
||||||
import { ChartData, ChartOptions, PPTChartElement } from '@/types/slides'
|
import { ChartData, ChartOptions, PPTChartElement } from '@/types/slides'
|
||||||
@ -178,39 +178,33 @@ const presetChartThemes = [
|
|||||||
['#8a7ca8', '#e098c7', '#8fd3e8', '#71669e', '#cc70af', '#7cb4cc'],
|
['#8a7ca8', '#e098c7', '#8fd3e8', '#71669e', '#cc70af', '#7cb4cc'],
|
||||||
]
|
]
|
||||||
|
|
||||||
export default defineComponent({
|
const mainStore = useMainStore()
|
||||||
name: 'chart-style-panel',
|
const slidesStore = useSlidesStore()
|
||||||
components: {
|
const { handleElement, handleElementId } = storeToRefs(mainStore)
|
||||||
ElementOutline,
|
const { theme } = storeToRefs(slidesStore)
|
||||||
ChartDataEditor,
|
|
||||||
ColorButton,
|
|
||||||
},
|
|
||||||
setup() {
|
|
||||||
const mainStore = useMainStore()
|
|
||||||
const slidesStore = useSlidesStore()
|
|
||||||
const { handleElement, handleElementId } = storeToRefs(mainStore)
|
|
||||||
const { theme } = storeToRefs(slidesStore)
|
|
||||||
|
|
||||||
const chartDataEditorVisible = ref(false)
|
const handleChartElement = handleElement as Ref<PPTChartElement>
|
||||||
const presetThemesVisible = ref(false)
|
|
||||||
const presetThemeColorHoverIndex = ref<[number, number]>([-1, -1])
|
|
||||||
|
|
||||||
const { addHistorySnapshot } = useHistorySnapshot()
|
const chartDataEditorVisible = ref(false)
|
||||||
|
const presetThemesVisible = ref(false)
|
||||||
|
const presetThemeColorHoverIndex = ref<[number, number]>([-1, -1])
|
||||||
|
|
||||||
const fill = ref<string>('#000')
|
const { addHistorySnapshot } = useHistorySnapshot()
|
||||||
|
|
||||||
const themeColor = ref<string[]>([])
|
const fill = ref<string>('#000')
|
||||||
const gridColor = ref('')
|
|
||||||
const legend = ref('')
|
|
||||||
|
|
||||||
const lineSmooth = ref(true)
|
const themeColor = ref<string[]>([])
|
||||||
const showLine = ref(true)
|
const gridColor = ref('')
|
||||||
const showArea = ref(false)
|
const legend = ref('')
|
||||||
const horizontalBars = ref(false)
|
|
||||||
const donut = ref(false)
|
|
||||||
const stackBars = ref(false)
|
|
||||||
|
|
||||||
watch(handleElement, () => {
|
const lineSmooth = ref(true)
|
||||||
|
const showLine = ref(true)
|
||||||
|
const showArea = ref(false)
|
||||||
|
const horizontalBars = ref(false)
|
||||||
|
const donut = ref(false)
|
||||||
|
const stackBars = ref(false)
|
||||||
|
|
||||||
|
watch(handleElement, () => {
|
||||||
if (!handleElement.value || handleElement.value.type !== 'chart') return
|
if (!handleElement.value || handleElement.value.type !== 'chart') return
|
||||||
fill.value = handleElement.value.fill || '#fff'
|
fill.value = handleElement.value.fill || '#fff'
|
||||||
|
|
||||||
@ -235,108 +229,81 @@ export default defineComponent({
|
|||||||
themeColor.value = handleElement.value.themeColor
|
themeColor.value = handleElement.value.themeColor
|
||||||
gridColor.value = handleElement.value.gridColor || '#333'
|
gridColor.value = handleElement.value.gridColor || '#333'
|
||||||
legend.value = handleElement.value.legend || ''
|
legend.value = handleElement.value.legend || ''
|
||||||
}, { deep: true, immediate: true })
|
}, { deep: true, immediate: true })
|
||||||
|
|
||||||
const updateElement = (props: Partial<PPTChartElement>) => {
|
const updateElement = (props: Partial<PPTChartElement>) => {
|
||||||
slidesStore.updateElement({ id: handleElementId.value, props })
|
slidesStore.updateElement({ id: handleElementId.value, props })
|
||||||
addHistorySnapshot()
|
addHistorySnapshot()
|
||||||
}
|
}
|
||||||
|
|
||||||
// 设置图表数据
|
// 设置图表数据
|
||||||
const updateData = (data: ChartData) => {
|
const updateData = (data: ChartData) => {
|
||||||
chartDataEditorVisible.value = false
|
chartDataEditorVisible.value = false
|
||||||
updateElement({ data })
|
updateElement({ data })
|
||||||
}
|
}
|
||||||
|
|
||||||
// 设置填充色
|
// 设置填充色
|
||||||
const updateFill = (value: string) => {
|
const updateFill = (value: string) => {
|
||||||
updateElement({ fill: value })
|
updateElement({ fill: value })
|
||||||
}
|
}
|
||||||
|
|
||||||
// 设置其他选项:柱状图转条形图、折线图转面积图、折线图转散点图、饼图转环形图、折线图开关平滑曲线
|
// 设置其他选项:柱状图转条形图、折线图转面积图、折线图转散点图、饼图转环形图、折线图开关平滑曲线
|
||||||
const updateOptions = (optionProps: ChartOptions) => {
|
const updateOptions = (optionProps: ChartOptions) => {
|
||||||
const _handleElement = handleElement.value as PPTChartElement
|
const _handleElement = handleElement.value as PPTChartElement
|
||||||
|
|
||||||
const newOptions = { ..._handleElement.options, ...optionProps }
|
const newOptions = { ..._handleElement.options, ...optionProps }
|
||||||
updateElement({ options: newOptions })
|
updateElement({ options: newOptions })
|
||||||
}
|
}
|
||||||
|
|
||||||
// 设置主题色
|
// 设置主题色
|
||||||
const updateTheme = (color: string, index: number) => {
|
const updateTheme = (color: string, index: number) => {
|
||||||
const props = {
|
const props = {
|
||||||
themeColor: themeColor.value.map((c, i) => i === index ? color : c),
|
themeColor: themeColor.value.map((c, i) => i === index ? color : c),
|
||||||
}
|
}
|
||||||
updateElement(props)
|
updateElement(props)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 添加主题色
|
// 添加主题色
|
||||||
const addThemeColor = () => {
|
const addThemeColor = () => {
|
||||||
const props = {
|
const props = {
|
||||||
themeColor: [...themeColor.value, theme.value.themeColor],
|
themeColor: [...themeColor.value, theme.value.themeColor],
|
||||||
}
|
}
|
||||||
updateElement(props)
|
updateElement(props)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 使用预置主题配色
|
// 使用预置主题配色
|
||||||
const applyPresetTheme = (colors: string[], index: number) => {
|
const applyPresetTheme = (colors: string[], index: number) => {
|
||||||
const themeColor = colors.slice(0, index + 1)
|
const themeColor = colors.slice(0, index + 1)
|
||||||
updateElement({ themeColor })
|
updateElement({ themeColor })
|
||||||
presetThemesVisible.value = false
|
presetThemesVisible.value = false
|
||||||
}
|
}
|
||||||
|
|
||||||
// 删除主题色
|
// 删除主题色
|
||||||
const deleteThemeColor = (index: number) => {
|
const deleteThemeColor = (index: number) => {
|
||||||
const props = {
|
const props = {
|
||||||
themeColor: themeColor.value.filter((c, i) => i !== index),
|
themeColor: themeColor.value.filter((c, i) => i !== index),
|
||||||
}
|
}
|
||||||
updateElement(props)
|
updateElement(props)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 设置网格颜色
|
// 设置网格颜色
|
||||||
const updateGridColor = (gridColor: string) => {
|
const updateGridColor = (gridColor: string) => {
|
||||||
updateElement({ gridColor })
|
updateElement({ gridColor })
|
||||||
}
|
}
|
||||||
|
|
||||||
// 设置图例位置/不显示
|
// 设置图例位置/不显示
|
||||||
const updateLegend = (legend: '' | 'top' | 'bottom') => {
|
const updateLegend = (legend: '' | 'top' | 'bottom') => {
|
||||||
updateElement({ legend })
|
updateElement({ legend })
|
||||||
}
|
}
|
||||||
|
|
||||||
const openDataEditor = () => chartDataEditorVisible.value = true
|
const openDataEditor = () => chartDataEditorVisible.value = true
|
||||||
|
|
||||||
emitter.on(EmitterEvents.OPEN_CHART_DATA_EDITOR, openDataEditor)
|
emitter.on(EmitterEvents.OPEN_CHART_DATA_EDITOR, openDataEditor)
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
emitter.off(EmitterEvents.OPEN_CHART_DATA_EDITOR, openDataEditor)
|
emitter.off(EmitterEvents.OPEN_CHART_DATA_EDITOR, openDataEditor)
|
||||||
})
|
|
||||||
|
|
||||||
return {
|
|
||||||
chartDataEditorVisible,
|
|
||||||
presetThemesVisible,
|
|
||||||
presetThemeColorHoverIndex,
|
|
||||||
handleElement: handleElement as Ref<PPTChartElement>,
|
|
||||||
updateData,
|
|
||||||
fill,
|
|
||||||
updateFill,
|
|
||||||
lineSmooth,
|
|
||||||
showLine,
|
|
||||||
showArea,
|
|
||||||
horizontalBars,
|
|
||||||
donut,
|
|
||||||
stackBars,
|
|
||||||
updateOptions,
|
|
||||||
themeColor,
|
|
||||||
gridColor,
|
|
||||||
legend,
|
|
||||||
updateTheme,
|
|
||||||
addThemeColor,
|
|
||||||
deleteThemeColor,
|
|
||||||
updateGridColor,
|
|
||||||
updateLegend,
|
|
||||||
presetChartThemes,
|
|
||||||
applyPresetTheme,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// handleElement: handleElement as Ref<PPTChartElement>,
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
<div class="image-style-panel">
|
<div class="image-style-panel">
|
||||||
<div
|
<div
|
||||||
class="origin-image"
|
class="origin-image"
|
||||||
:style="{ backgroundImage: `url(${handleElement.src})` }"
|
:style="{ backgroundImage: `url(${handleImageElement.src})` }"
|
||||||
></div>
|
></div>
|
||||||
|
|
||||||
<ElementFlip />
|
<ElementFlip />
|
||||||
@ -57,8 +57,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts" setup>
|
||||||
import { defineComponent, Ref, ref } from 'vue'
|
import { Ref, ref } from 'vue'
|
||||||
import { storeToRefs } from 'pinia'
|
import { storeToRefs } from 'pinia'
|
||||||
import { useMainStore, useSlidesStore } from '@/store'
|
import { useMainStore, useSlidesStore } from '@/store'
|
||||||
import { PPTImageElement, SlideBackground } from '@/types/slides'
|
import { PPTImageElement, SlideBackground } from '@/types/slides'
|
||||||
@ -105,32 +105,25 @@ const ratioClipOptions = [
|
|||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
export default defineComponent({
|
const mainStore = useMainStore()
|
||||||
name: 'image-style-panel',
|
const slidesStore = useSlidesStore()
|
||||||
components: {
|
const { handleElement, handleElementId } = storeToRefs(mainStore)
|
||||||
ElementOutline,
|
const { currentSlide } = storeToRefs(slidesStore)
|
||||||
ElementShadow,
|
|
||||||
ElementFlip,
|
|
||||||
ElementFilter,
|
|
||||||
},
|
|
||||||
setup() {
|
|
||||||
const mainStore = useMainStore()
|
|
||||||
const slidesStore = useSlidesStore()
|
|
||||||
const { handleElement, handleElementId } = storeToRefs(mainStore)
|
|
||||||
const { currentSlide } = storeToRefs(slidesStore)
|
|
||||||
|
|
||||||
const clipPanelVisible = ref(false)
|
const handleImageElement = handleElement as Ref<PPTImageElement>
|
||||||
|
|
||||||
const { addHistorySnapshot } = useHistorySnapshot()
|
const clipPanelVisible = ref(false)
|
||||||
|
|
||||||
// 打开自由裁剪
|
const { addHistorySnapshot } = useHistorySnapshot()
|
||||||
const clipImage = () => {
|
|
||||||
|
// 打开自由裁剪
|
||||||
|
const clipImage = () => {
|
||||||
mainStore.setClipingImageElementId(handleElementId.value)
|
mainStore.setClipingImageElementId(handleElementId.value)
|
||||||
clipPanelVisible.value = false
|
clipPanelVisible.value = false
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取原始图片的位置大小
|
// 获取原始图片的位置大小
|
||||||
const getImageElementDataBeforeClip = () => {
|
const getImageElementDataBeforeClip = () => {
|
||||||
const _handleElement = handleElement.value as PPTImageElement
|
const _handleElement = handleElement.value as PPTImageElement
|
||||||
|
|
||||||
// 图片当前的位置大小和裁剪范围
|
// 图片当前的位置大小和裁剪范围
|
||||||
@ -152,10 +145,10 @@ export default defineComponent({
|
|||||||
originLeft,
|
originLeft,
|
||||||
originTop,
|
originTop,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 预设裁剪
|
// 预设裁剪
|
||||||
const presetImageClip = (shape: string, ratio = 0) => {
|
const presetImageClip = (shape: string, ratio = 0) => {
|
||||||
const _handleElement = handleElement.value as PPTImageElement
|
const _handleElement = handleElement.value as PPTImageElement
|
||||||
|
|
||||||
const {
|
const {
|
||||||
@ -204,10 +197,10 @@ export default defineComponent({
|
|||||||
}
|
}
|
||||||
clipImage()
|
clipImage()
|
||||||
addHistorySnapshot()
|
addHistorySnapshot()
|
||||||
}
|
}
|
||||||
|
|
||||||
// 替换图片(保持当前的样式)
|
// 替换图片(保持当前的样式)
|
||||||
const replaceImage = (files: File[]) => {
|
const replaceImage = (files: FileList) => {
|
||||||
const imageFile = files[0]
|
const imageFile = files[0]
|
||||||
if (!imageFile) return
|
if (!imageFile) return
|
||||||
getImageDataURL(imageFile).then(dataURL => {
|
getImageDataURL(imageFile).then(dataURL => {
|
||||||
@ -215,10 +208,10 @@ export default defineComponent({
|
|||||||
slidesStore.updateElement({ id: handleElementId.value, props })
|
slidesStore.updateElement({ id: handleElementId.value, props })
|
||||||
})
|
})
|
||||||
addHistorySnapshot()
|
addHistorySnapshot()
|
||||||
}
|
}
|
||||||
|
|
||||||
// 重置图片:清除全部样式
|
// 重置图片:清除全部样式
|
||||||
const resetImage = () => {
|
const resetImage = () => {
|
||||||
const _handleElement = handleElement.value as PPTImageElement
|
const _handleElement = handleElement.value as PPTImageElement
|
||||||
|
|
||||||
if (_handleElement.clip) {
|
if (_handleElement.clip) {
|
||||||
@ -245,10 +238,10 @@ export default defineComponent({
|
|||||||
propName: ['clip', 'outline', 'flip', 'shadow', 'filters'],
|
propName: ['clip', 'outline', 'flip', 'shadow', 'filters'],
|
||||||
})
|
})
|
||||||
addHistorySnapshot()
|
addHistorySnapshot()
|
||||||
}
|
}
|
||||||
|
|
||||||
// 将图片设置为背景
|
// 将图片设置为背景
|
||||||
const setBackgroundImage = () => {
|
const setBackgroundImage = () => {
|
||||||
const _handleElement = handleElement.value as PPTImageElement
|
const _handleElement = handleElement.value as PPTImageElement
|
||||||
|
|
||||||
const background: SlideBackground = {
|
const background: SlideBackground = {
|
||||||
@ -259,21 +252,7 @@ export default defineComponent({
|
|||||||
}
|
}
|
||||||
slidesStore.updateSlide({ background })
|
slidesStore.updateSlide({ background })
|
||||||
addHistorySnapshot()
|
addHistorySnapshot()
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
|
||||||
clipPanelVisible,
|
|
||||||
shapeClipPathOptions,
|
|
||||||
ratioClipOptions,
|
|
||||||
handleElement: handleElement as Ref<PPTImageElement>,
|
|
||||||
clipImage,
|
|
||||||
presetImageClip,
|
|
||||||
replaceImage,
|
|
||||||
resetImage,
|
|
||||||
setBackgroundImage,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
})
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
@ -9,11 +9,11 @@
|
|||||||
<Popover trigger="click">
|
<Popover trigger="click">
|
||||||
<template #content>
|
<template #content>
|
||||||
<ColorPicker
|
<ColorPicker
|
||||||
:modelValue="handleElement.color"
|
:modelValue="handleLatexElement.color"
|
||||||
@update:modelValue="value => updateLatex({ color: value })"
|
@update:modelValue="value => updateLatex({ color: value })"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
<ColorButton :color="handleElement.color" style="flex: 3;" />
|
<ColorButton :color="handleLatexElement.color" style="flex: 3;" />
|
||||||
</Popover>
|
</Popover>
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
@ -21,7 +21,7 @@
|
|||||||
<InputNumber
|
<InputNumber
|
||||||
:min="1"
|
:min="1"
|
||||||
:max="3"
|
:max="3"
|
||||||
:value="handleElement.strokeWidth"
|
:value="handleLatexElement.strokeWidth"
|
||||||
@change="value => updateLatex({ strokeWidth: value as number })"
|
@change="value => updateLatex({ strokeWidth: value as number })"
|
||||||
style="flex: 3;"
|
style="flex: 3;"
|
||||||
/>
|
/>
|
||||||
@ -35,7 +35,7 @@
|
|||||||
destroyOnClose
|
destroyOnClose
|
||||||
>
|
>
|
||||||
<LaTeXEditor
|
<LaTeXEditor
|
||||||
:value="handleElement.latex"
|
:value="handleLatexElement.latex"
|
||||||
@close="latexEditorVisible = false"
|
@close="latexEditorVisible = false"
|
||||||
@update="data => { updateLatexData(data); latexEditorVisible = false }"
|
@update="data => { updateLatexData(data); latexEditorVisible = false }"
|
||||||
/>
|
/>
|
||||||
@ -43,8 +43,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts" setup>
|
||||||
import { defineComponent, onUnmounted, Ref, ref } from 'vue'
|
import { onUnmounted, Ref, ref } from 'vue'
|
||||||
import { storeToRefs } from 'pinia'
|
import { storeToRefs } from 'pinia'
|
||||||
import { useMainStore, useSlidesStore } from '@/store'
|
import { useMainStore, useSlidesStore } from '@/store'
|
||||||
import { PPTLatexElement } from '@/types/slides'
|
import { PPTLatexElement } from '@/types/slides'
|
||||||
@ -54,27 +54,22 @@ import useHistorySnapshot from '@/hooks/useHistorySnapshot'
|
|||||||
import ColorButton from '../common/ColorButton.vue'
|
import ColorButton from '../common/ColorButton.vue'
|
||||||
import LaTeXEditor from '@/components/LaTeXEditor/index.vue'
|
import LaTeXEditor from '@/components/LaTeXEditor/index.vue'
|
||||||
|
|
||||||
export default defineComponent({
|
const slidesStore = useSlidesStore()
|
||||||
name: 'latex-style-panel',
|
const { handleElement } = storeToRefs(useMainStore())
|
||||||
components: {
|
|
||||||
ColorButton,
|
|
||||||
LaTeXEditor,
|
|
||||||
},
|
|
||||||
setup() {
|
|
||||||
const slidesStore = useSlidesStore()
|
|
||||||
const { handleElement } = storeToRefs(useMainStore())
|
|
||||||
|
|
||||||
const latexEditorVisible = ref(false)
|
const handleLatexElement = handleElement as Ref<PPTLatexElement>
|
||||||
|
|
||||||
const { addHistorySnapshot } = useHistorySnapshot()
|
const latexEditorVisible = ref(false)
|
||||||
|
|
||||||
const updateLatex = (props: Partial<PPTLatexElement>) => {
|
const { addHistorySnapshot } = useHistorySnapshot()
|
||||||
|
|
||||||
|
const updateLatex = (props: Partial<PPTLatexElement>) => {
|
||||||
if (!handleElement.value) return
|
if (!handleElement.value) return
|
||||||
slidesStore.updateElement({ id: handleElement.value.id, props })
|
slidesStore.updateElement({ id: handleElement.value.id, props })
|
||||||
addHistorySnapshot()
|
addHistorySnapshot()
|
||||||
}
|
}
|
||||||
|
|
||||||
const updateLatexData = (data: { path: string; latex: string; w: number; h: number; }) => {
|
const updateLatexData = (data: { path: string; latex: string; w: number; h: number; }) => {
|
||||||
updateLatex({
|
updateLatex({
|
||||||
path: data.path,
|
path: data.path,
|
||||||
latex: data.latex,
|
latex: data.latex,
|
||||||
@ -82,22 +77,13 @@ export default defineComponent({
|
|||||||
height: data.h,
|
height: data.h,
|
||||||
viewBox: [data.w, data.h],
|
viewBox: [data.w, data.h],
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const openLatexEditor = () => latexEditorVisible.value = true
|
const openLatexEditor = () => latexEditorVisible.value = true
|
||||||
|
|
||||||
emitter.on(EmitterEvents.OPEN_LATEX_EDITOR, openLatexEditor)
|
emitter.on(EmitterEvents.OPEN_LATEX_EDITOR, openLatexEditor)
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
emitter.off(EmitterEvents.OPEN_LATEX_EDITOR, openLatexEditor)
|
emitter.off(EmitterEvents.OPEN_LATEX_EDITOR, openLatexEditor)
|
||||||
})
|
|
||||||
|
|
||||||
return {
|
|
||||||
handleElement: handleElement as Ref<PPTLatexElement>,
|
|
||||||
latexEditorVisible,
|
|
||||||
updateLatex,
|
|
||||||
updateLatexData,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
<div style="flex: 2;">线条样式:</div>
|
<div style="flex: 2;">线条样式:</div>
|
||||||
<Select
|
<Select
|
||||||
style="flex: 3;"
|
style="flex: 3;"
|
||||||
:value="handleElement.style"
|
:value="handleLineElement.style"
|
||||||
@change="value => updateLine({ style: value as 'solid' | 'dashed' })"
|
@change="value => updateLine({ style: value as 'solid' | 'dashed' })"
|
||||||
>
|
>
|
||||||
<SelectOption value="solid">实线</SelectOption>
|
<SelectOption value="solid">实线</SelectOption>
|
||||||
@ -16,17 +16,17 @@
|
|||||||
<Popover trigger="click">
|
<Popover trigger="click">
|
||||||
<template #content>
|
<template #content>
|
||||||
<ColorPicker
|
<ColorPicker
|
||||||
:modelValue="handleElement.color"
|
:modelValue="handleLineElement.color"
|
||||||
@update:modelValue="value => updateLine({ color: value })"
|
@update:modelValue="value => updateLine({ color: value })"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
<ColorButton :color="handleElement.color" style="flex: 3;" />
|
<ColorButton :color="handleLineElement.color" style="flex: 3;" />
|
||||||
</Popover>
|
</Popover>
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div style="flex: 2;">线条宽度:</div>
|
<div style="flex: 2;">线条宽度:</div>
|
||||||
<InputNumber
|
<InputNumber
|
||||||
:value="handleElement.width"
|
:value="handleLineElement.width"
|
||||||
@change="value => updateLine({ width: value as number })"
|
@change="value => updateLine({ width: value as number })"
|
||||||
style="flex: 3;"
|
style="flex: 3;"
|
||||||
/>
|
/>
|
||||||
@ -36,8 +36,8 @@
|
|||||||
<div style="flex: 2;">起点样式:</div>
|
<div style="flex: 2;">起点样式:</div>
|
||||||
<Select
|
<Select
|
||||||
style="flex: 3;"
|
style="flex: 3;"
|
||||||
:value="handleElement.points[0]"
|
:value="handleLineElement.points[0]"
|
||||||
@change="value => updateLine({ points: [value as 'arrow' | 'dot', handleElement.points[1]] })"
|
@change="value => updateLine({ points: [value as 'arrow' | 'dot', handleLineElement.points[1]] })"
|
||||||
>
|
>
|
||||||
<SelectOption value="">无</SelectOption>
|
<SelectOption value="">无</SelectOption>
|
||||||
<SelectOption value="arrow">箭头</SelectOption>
|
<SelectOption value="arrow">箭头</SelectOption>
|
||||||
@ -48,8 +48,8 @@
|
|||||||
<div style="flex: 2;">终点样式:</div>
|
<div style="flex: 2;">终点样式:</div>
|
||||||
<Select
|
<Select
|
||||||
style="flex: 3;"
|
style="flex: 3;"
|
||||||
:value="handleElement.points[1]"
|
:value="handleLineElement.points[1]"
|
||||||
@change="value => updateLine({ points: [handleElement.points[0], value as 'arrow' | 'dot'] })"
|
@change="value => updateLine({ points: [handleLineElement.points[0], value as 'arrow' | 'dot'] })"
|
||||||
>
|
>
|
||||||
<SelectOption value="">无</SelectOption>
|
<SelectOption value="">无</SelectOption>
|
||||||
<SelectOption value="arrow">箭头</SelectOption>
|
<SelectOption value="arrow">箭头</SelectOption>
|
||||||
@ -62,8 +62,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts" setup>
|
||||||
import { defineComponent, Ref } from 'vue'
|
import { Ref } from 'vue'
|
||||||
import { storeToRefs } from 'pinia'
|
import { storeToRefs } from 'pinia'
|
||||||
import { useMainStore, useSlidesStore } from '@/store'
|
import { useMainStore, useSlidesStore } from '@/store'
|
||||||
import { PPTLineElement } from '@/types/slides'
|
import { PPTLineElement } from '@/types/slides'
|
||||||
@ -72,30 +72,18 @@ import useHistorySnapshot from '@/hooks/useHistorySnapshot'
|
|||||||
import ElementShadow from '../common/ElementShadow.vue'
|
import ElementShadow from '../common/ElementShadow.vue'
|
||||||
import ColorButton from '../common/ColorButton.vue'
|
import ColorButton from '../common/ColorButton.vue'
|
||||||
|
|
||||||
export default defineComponent({
|
const slidesStore = useSlidesStore()
|
||||||
name: 'line-style-panel',
|
const { handleElement } = storeToRefs(useMainStore())
|
||||||
components: {
|
|
||||||
ElementShadow,
|
|
||||||
ColorButton,
|
|
||||||
},
|
|
||||||
setup() {
|
|
||||||
const slidesStore = useSlidesStore()
|
|
||||||
const { handleElement } = storeToRefs(useMainStore())
|
|
||||||
|
|
||||||
const { addHistorySnapshot } = useHistorySnapshot()
|
const handleLineElement = handleElement as Ref<PPTLineElement>
|
||||||
|
|
||||||
const updateLine = (props: Partial<PPTLineElement>) => {
|
const { addHistorySnapshot } = useHistorySnapshot()
|
||||||
|
|
||||||
|
const updateLine = (props: Partial<PPTLineElement>) => {
|
||||||
if (!handleElement.value) return
|
if (!handleElement.value) return
|
||||||
slidesStore.updateElement({ id: handleElement.value.id, props })
|
slidesStore.updateElement({ id: handleElement.value.id, props })
|
||||||
addHistorySnapshot()
|
addHistorySnapshot()
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
|
||||||
handleElement: handleElement as Ref<PPTLineElement>,
|
|
||||||
updateLine,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
@ -62,7 +62,7 @@
|
|||||||
</SelectOption>
|
</SelectOption>
|
||||||
</SelectOptGroup>
|
</SelectOptGroup>
|
||||||
<SelectOptGroup label="在线字体">
|
<SelectOptGroup label="在线字体">
|
||||||
<SelectOption v-for="font in webFonts" :key="font.value" :value="font.value">
|
<SelectOption v-for="font in WEB_FONTS" :key="font.value" :value="font.value">
|
||||||
<span>{{font.label}}</span>
|
<span>{{font.label}}</span>
|
||||||
</SelectOption>
|
</SelectOption>
|
||||||
</SelectOptGroup>
|
</SelectOptGroup>
|
||||||
@ -141,8 +141,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts" setup>
|
||||||
import { defineComponent, ref } from 'vue'
|
import { ref } from 'vue'
|
||||||
import { storeToRefs } from 'pinia'
|
import { storeToRefs } from 'pinia'
|
||||||
import { useMainStore, useSlidesStore } from '@/store'
|
import { useMainStore, useSlidesStore } from '@/store'
|
||||||
import { PPTElement, PPTElementOutline, TableCell } from '@/types/slides'
|
import { PPTElement, PPTElementOutline, TableCell } from '@/types/slides'
|
||||||
@ -152,39 +152,31 @@ import useHistorySnapshot from '@/hooks/useHistorySnapshot'
|
|||||||
|
|
||||||
import ColorButton from '../common/ColorButton.vue'
|
import ColorButton from '../common/ColorButton.vue'
|
||||||
|
|
||||||
const webFonts = WEB_FONTS
|
const slidesStore = useSlidesStore()
|
||||||
|
const { richTextAttrs, availableFonts, activeElementList } = storeToRefs(useMainStore())
|
||||||
|
|
||||||
export default defineComponent({
|
const { addHistorySnapshot } = useHistorySnapshot()
|
||||||
name: 'multi-style-panel',
|
|
||||||
components: {
|
|
||||||
ColorButton,
|
|
||||||
},
|
|
||||||
setup() {
|
|
||||||
const slidesStore = useSlidesStore()
|
|
||||||
const { richTextAttrs, availableFonts, activeElementList } = storeToRefs(useMainStore())
|
|
||||||
|
|
||||||
const { addHistorySnapshot } = useHistorySnapshot()
|
const updateElement = (id: string, props: Partial<PPTElement>) => {
|
||||||
|
|
||||||
const updateElement = (id: string, props: Partial<PPTElement>) => {
|
|
||||||
slidesStore.updateElement({ id, props })
|
slidesStore.updateElement({ id, props })
|
||||||
addHistorySnapshot()
|
addHistorySnapshot()
|
||||||
}
|
}
|
||||||
|
|
||||||
const fontSizeOptions = [
|
const fontSizeOptions = [
|
||||||
'12px', '14px', '16px', '18px', '20px', '22px', '24px', '28px', '32px',
|
'12px', '14px', '16px', '18px', '20px', '22px', '24px', '28px', '32px',
|
||||||
'36px', '40px', '44px', '48px', '54px', '60px', '66px', '72px', '76px',
|
'36px', '40px', '44px', '48px', '54px', '60px', '66px', '72px', '76px',
|
||||||
'80px', '88px', '96px', '104px', '112px', '120px',
|
'80px', '88px', '96px', '104px', '112px', '120px',
|
||||||
]
|
]
|
||||||
|
|
||||||
const fill = ref('#fff')
|
const fill = ref('#fff')
|
||||||
const outline = ref<PPTElementOutline>({
|
const outline = ref<PPTElementOutline>({
|
||||||
width: 0,
|
width: 0,
|
||||||
color: '#fff',
|
color: '#fff',
|
||||||
style: 'solid',
|
style: 'solid',
|
||||||
})
|
})
|
||||||
|
|
||||||
// 批量修改填充色(表格元素为单元格填充、音频元素为图标颜色)
|
// 批量修改填充色(表格元素为单元格填充、音频元素为图标颜色)
|
||||||
const updateFill = (value: string) => {
|
const updateFill = (value: string) => {
|
||||||
for (const el of activeElementList.value) {
|
for (const el of activeElementList.value) {
|
||||||
if (
|
if (
|
||||||
el.type === 'text' ||
|
el.type === 'text' ||
|
||||||
@ -206,10 +198,10 @@ export default defineComponent({
|
|||||||
if (el.type === 'audio') updateElement(el.id, { color: value })
|
if (el.type === 'audio') updateElement(el.id, { color: value })
|
||||||
}
|
}
|
||||||
fill.value = value
|
fill.value = value
|
||||||
}
|
}
|
||||||
|
|
||||||
// 修改边框/线条样式
|
// 修改边框/线条样式
|
||||||
const updateOutline = (outlineProps: Partial<PPTElementOutline>) => {
|
const updateOutline = (outlineProps: Partial<PPTElementOutline>) => {
|
||||||
|
|
||||||
for (const el of activeElementList.value) {
|
for (const el of activeElementList.value) {
|
||||||
if (
|
if (
|
||||||
@ -227,10 +219,10 @@ export default defineComponent({
|
|||||||
if (el.type === 'line') updateElement(el.id, outlineProps)
|
if (el.type === 'line') updateElement(el.id, outlineProps)
|
||||||
}
|
}
|
||||||
outline.value = { ...outline.value, ...outlineProps }
|
outline.value = { ...outline.value, ...outlineProps }
|
||||||
}
|
}
|
||||||
|
|
||||||
// 修改文字样式
|
// 修改文字样式
|
||||||
const updateFontStyle = (command: string, value: string) => {
|
const updateFontStyle = (command: string, value: string) => {
|
||||||
for (const el of activeElementList.value) {
|
for (const el of activeElementList.value) {
|
||||||
if (el.type === 'text' || (el.type === 'shape' && el.text?.content)) {
|
if (el.type === 'text' || (el.type === 'shape' && el.text?.content)) {
|
||||||
emitter.emit(EmitterEvents.RICH_TEXT_COMMAND, { target: el.id, action: { command, value } })
|
emitter.emit(EmitterEvents.RICH_TEXT_COMMAND, { target: el.id, action: { command, value } })
|
||||||
@ -249,21 +241,7 @@ export default defineComponent({
|
|||||||
updateElement(el.id, { color: value })
|
updateElement(el.id, { color: value })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
|
||||||
webFonts,
|
|
||||||
richTextAttrs,
|
|
||||||
availableFonts,
|
|
||||||
fontSizeOptions,
|
|
||||||
fill,
|
|
||||||
outline,
|
|
||||||
updateFill,
|
|
||||||
updateOutline,
|
|
||||||
updateFontStyle,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
@ -71,7 +71,7 @@
|
|||||||
<ElementFlip />
|
<ElementFlip />
|
||||||
<Divider />
|
<Divider />
|
||||||
|
|
||||||
<template v-if="handleElement?.text?.content">
|
<template v-if="handleShapeElement.text?.content">
|
||||||
<InputGroup compact class="row">
|
<InputGroup compact class="row">
|
||||||
<Select
|
<Select
|
||||||
style="flex: 3;"
|
style="flex: 3;"
|
||||||
@ -85,7 +85,7 @@
|
|||||||
</SelectOption>
|
</SelectOption>
|
||||||
</SelectOptGroup>
|
</SelectOptGroup>
|
||||||
<SelectOptGroup label="在线字体">
|
<SelectOptGroup label="在线字体">
|
||||||
<SelectOption v-for="font in webFonts" :key="font.value" :value="font.value">
|
<SelectOption v-for="font in WEB_FONTS" :key="font.value" :value="font.value">
|
||||||
<span>{{font.label}}</span>
|
<span>{{font.label}}</span>
|
||||||
</SelectOption>
|
</SelectOption>
|
||||||
</SelectOptGroup>
|
</SelectOptGroup>
|
||||||
@ -222,8 +222,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts" setup>
|
||||||
import { defineComponent, Ref, ref, watch } from 'vue'
|
import { Ref, ref, watch } from 'vue'
|
||||||
import { storeToRefs } from 'pinia'
|
import { storeToRefs } from 'pinia'
|
||||||
import { useMainStore, useSlidesStore } from '@/store'
|
import { useMainStore, useSlidesStore } from '@/store'
|
||||||
import { PPTShapeElement, ShapeGradient, ShapeText } from '@/types/slides'
|
import { PPTShapeElement, ShapeGradient, ShapeText } from '@/types/slides'
|
||||||
@ -237,69 +237,59 @@ import ElementShadow from '../common/ElementShadow.vue'
|
|||||||
import ElementFlip from '../common/ElementFlip.vue'
|
import ElementFlip from '../common/ElementFlip.vue'
|
||||||
import ColorButton from '../common/ColorButton.vue'
|
import ColorButton from '../common/ColorButton.vue'
|
||||||
|
|
||||||
const webFonts = WEB_FONTS
|
const mainStore = useMainStore()
|
||||||
|
const slidesStore = useSlidesStore()
|
||||||
|
const { handleElement, handleElementId, richTextAttrs, availableFonts } = storeToRefs(mainStore)
|
||||||
|
|
||||||
export default defineComponent({
|
const handleShapeElement = handleElement as Ref<PPTShapeElement>
|
||||||
name: 'shape-style-panel',
|
|
||||||
components: {
|
|
||||||
ElementOpacity,
|
|
||||||
ElementOutline,
|
|
||||||
ElementShadow,
|
|
||||||
ElementFlip,
|
|
||||||
ColorButton,
|
|
||||||
},
|
|
||||||
setup() {
|
|
||||||
const mainStore = useMainStore()
|
|
||||||
const slidesStore = useSlidesStore()
|
|
||||||
const { handleElement, handleElementId, richTextAttrs, availableFonts } = storeToRefs(mainStore)
|
|
||||||
|
|
||||||
const fill = ref<string>('#000')
|
const fill = ref<string>('#000')
|
||||||
const gradient = ref<ShapeGradient>({
|
const gradient = ref<ShapeGradient>({
|
||||||
type: 'linear',
|
type: 'linear',
|
||||||
rotate: 0,
|
rotate: 0,
|
||||||
color: ['#fff', '#fff'],
|
color: ['#fff', '#fff'],
|
||||||
})
|
})
|
||||||
const fillType = ref('fill')
|
const fillType = ref('fill')
|
||||||
const textAlign = ref('middle')
|
const textAlign = ref('middle')
|
||||||
|
|
||||||
watch(handleElement, () => {
|
watch(handleElement, () => {
|
||||||
if (!handleElement.value || handleElement.value.type !== 'shape') return
|
if (!handleElement.value || handleElement.value.type !== 'shape') return
|
||||||
|
|
||||||
fill.value = handleElement.value.fill || '#fff'
|
fill.value = handleElement.value.fill || '#fff'
|
||||||
gradient.value = handleElement.value.gradient || { type: 'linear', rotate: 0, color: [fill.value, '#fff'] }
|
gradient.value = handleElement.value.gradient || { type: 'linear', rotate: 0, color: [fill.value, '#fff'] }
|
||||||
fillType.value = handleElement.value.gradient ? 'gradient' : 'fill'
|
fillType.value = handleElement.value.gradient ? 'gradient' : 'fill'
|
||||||
textAlign.value = handleElement.value?.text?.align || 'middle'
|
textAlign.value = handleElement.value?.text?.align || 'middle'
|
||||||
}, { deep: true, immediate: true })
|
}, { deep: true, immediate: true })
|
||||||
|
|
||||||
const { addHistorySnapshot } = useHistorySnapshot()
|
const { addHistorySnapshot } = useHistorySnapshot()
|
||||||
|
|
||||||
const updateElement = (props: Partial<PPTShapeElement>) => {
|
const updateElement = (props: Partial<PPTShapeElement>) => {
|
||||||
slidesStore.updateElement({ id: handleElementId.value, props })
|
slidesStore.updateElement({ id: handleElementId.value, props })
|
||||||
addHistorySnapshot()
|
addHistorySnapshot()
|
||||||
}
|
}
|
||||||
|
|
||||||
// 设置填充类型:渐变、纯色
|
// 设置填充类型:渐变、纯色
|
||||||
const updateFillType = (type: 'gradient' | 'fill') => {
|
const updateFillType = (type: 'gradient' | 'fill') => {
|
||||||
if (type === 'fill') {
|
if (type === 'fill') {
|
||||||
slidesStore.removeElementProps({ id: handleElementId.value, propName: 'gradient' })
|
slidesStore.removeElementProps({ id: handleElementId.value, propName: 'gradient' })
|
||||||
addHistorySnapshot()
|
addHistorySnapshot()
|
||||||
}
|
}
|
||||||
else updateElement({ gradient: gradient.value })
|
else updateElement({ gradient: gradient.value })
|
||||||
}
|
}
|
||||||
|
|
||||||
// 设置渐变填充
|
// 设置渐变填充
|
||||||
const updateGradient = (gradientProps: Partial<ShapeGradient>) => {
|
const updateGradient = (gradientProps: Partial<ShapeGradient>) => {
|
||||||
if (!gradient.value) return
|
if (!gradient.value) return
|
||||||
const _gradient: ShapeGradient = { ...gradient.value, ...gradientProps }
|
const _gradient: ShapeGradient = { ...gradient.value, ...gradientProps }
|
||||||
updateElement({ gradient: _gradient })
|
updateElement({ gradient: _gradient })
|
||||||
}
|
}
|
||||||
|
|
||||||
// 设置填充色
|
// 设置填充色
|
||||||
const updateFill = (value: string) => {
|
const updateFill = (value: string) => {
|
||||||
updateElement({ fill: value })
|
updateElement({ fill: value })
|
||||||
}
|
}
|
||||||
|
|
||||||
const updateTextAlign = (align: 'top' | 'middle' | 'bottom') => {
|
const updateTextAlign = (align: 'top' | 'middle' | 'bottom') => {
|
||||||
const _handleElement = handleElement.value as PPTShapeElement
|
const _handleElement = handleElement.value as PPTShapeElement
|
||||||
|
|
||||||
const defaultText: ShapeText = {
|
const defaultText: ShapeText = {
|
||||||
@ -310,36 +300,17 @@ export default defineComponent({
|
|||||||
}
|
}
|
||||||
const _text = _handleElement.text || defaultText
|
const _text = _handleElement.text || defaultText
|
||||||
updateElement({ text: { ..._text, align } })
|
updateElement({ text: { ..._text, align } })
|
||||||
}
|
}
|
||||||
|
|
||||||
const fontSizeOptions = [
|
const fontSizeOptions = [
|
||||||
'12px', '14px', '16px', '18px', '20px', '22px', '24px', '28px', '32px',
|
'12px', '14px', '16px', '18px', '20px', '22px', '24px', '28px', '32px',
|
||||||
'36px', '40px', '44px', '48px', '54px', '60px', '66px', '72px', '76px',
|
'36px', '40px', '44px', '48px', '54px', '60px', '66px', '72px', '76px',
|
||||||
'80px', '88px', '96px', '104px', '112px', '120px',
|
'80px', '88px', '96px', '104px', '112px', '120px',
|
||||||
]
|
]
|
||||||
|
|
||||||
const emitRichTextCommand = (command: string, value?: string) => {
|
const emitRichTextCommand = (command: string, value?: string) => {
|
||||||
emitter.emit(EmitterEvents.RICH_TEXT_COMMAND, { action: { command, value } })
|
emitter.emit(EmitterEvents.RICH_TEXT_COMMAND, { action: { command, value } })
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
|
||||||
fill,
|
|
||||||
gradient,
|
|
||||||
fillType,
|
|
||||||
textAlign,
|
|
||||||
richTextAttrs,
|
|
||||||
availableFonts,
|
|
||||||
fontSizeOptions,
|
|
||||||
webFonts,
|
|
||||||
handleElement: handleElement as Ref<PPTShapeElement>,
|
|
||||||
emitRichTextCommand,
|
|
||||||
updateFillType,
|
|
||||||
updateFill,
|
|
||||||
updateGradient,
|
|
||||||
updateTextAlign,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
})
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
@ -13,7 +13,7 @@
|
|||||||
</SelectOption>
|
</SelectOption>
|
||||||
</SelectOptGroup>
|
</SelectOptGroup>
|
||||||
<SelectOptGroup label="在线字体">
|
<SelectOptGroup label="在线字体">
|
||||||
<SelectOption v-for="font in webFonts" :key="font.value" :value="font.value">
|
<SelectOption v-for="font in WEB_FONTS" :key="font.value" :value="font.value">
|
||||||
<span>{{font.label}}</span>
|
<span>{{font.label}}</span>
|
||||||
</SelectOption>
|
</SelectOption>
|
||||||
</SelectOptGroup>
|
</SelectOptGroup>
|
||||||
@ -185,8 +185,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts" setup>
|
||||||
import { computed, defineComponent, onMounted, ref, watch } from 'vue'
|
import { computed, onMounted, ref, watch } from 'vue'
|
||||||
import { storeToRefs } from 'pinia'
|
import { storeToRefs } from 'pinia'
|
||||||
import { nanoid } from 'nanoid'
|
import { nanoid } from 'nanoid'
|
||||||
import { useMainStore, useSlidesStore } from '@/store'
|
import { useMainStore, useSlidesStore } from '@/store'
|
||||||
@ -197,24 +197,15 @@ import useHistorySnapshot from '@/hooks/useHistorySnapshot'
|
|||||||
import ElementOutline from '../common/ElementOutline.vue'
|
import ElementOutline from '../common/ElementOutline.vue'
|
||||||
import ColorButton from '../common/ColorButton.vue'
|
import ColorButton from '../common/ColorButton.vue'
|
||||||
|
|
||||||
const webFonts = WEB_FONTS
|
const slidesStore = useSlidesStore()
|
||||||
|
const { handleElement, handleElementId, selectedTableCells: selectedCells, availableFonts } = storeToRefs(useMainStore())
|
||||||
|
const themeColor = computed(() => slidesStore.theme.themeColor)
|
||||||
|
|
||||||
export default defineComponent({
|
const fontSizeOptions = [
|
||||||
name: 'table-style-panel',
|
|
||||||
components: {
|
|
||||||
ElementOutline,
|
|
||||||
ColorButton,
|
|
||||||
},
|
|
||||||
setup() {
|
|
||||||
const slidesStore = useSlidesStore()
|
|
||||||
const { handleElement, handleElementId, selectedTableCells: selectedCells, availableFonts } = storeToRefs(useMainStore())
|
|
||||||
const themeColor = computed(() => slidesStore.theme.themeColor)
|
|
||||||
|
|
||||||
const fontSizeOptions = [
|
|
||||||
'12px', '14px', '16px', '18px', '20px', '22px', '24px', '28px', '32px',
|
'12px', '14px', '16px', '18px', '20px', '22px', '24px', '28px', '32px',
|
||||||
]
|
]
|
||||||
|
|
||||||
const textAttrs = ref({
|
const textAttrs = ref({
|
||||||
bold: false,
|
bold: false,
|
||||||
em: false,
|
em: false,
|
||||||
underline: false,
|
underline: false,
|
||||||
@ -224,16 +215,16 @@ export default defineComponent({
|
|||||||
fontsize: '12px',
|
fontsize: '12px',
|
||||||
fontname: '微软雅黑',
|
fontname: '微软雅黑',
|
||||||
align: 'left',
|
align: 'left',
|
||||||
})
|
})
|
||||||
|
|
||||||
const theme = ref<TableTheme>()
|
const theme = ref<TableTheme>()
|
||||||
const hasTheme = ref(false)
|
const hasTheme = ref(false)
|
||||||
const rowCount = ref(0)
|
const rowCount = ref(0)
|
||||||
const colCount = ref(0)
|
const colCount = ref(0)
|
||||||
const minRowCount = ref(0)
|
const minRowCount = ref(0)
|
||||||
const minColCount = ref(0)
|
const minColCount = ref(0)
|
||||||
|
|
||||||
watch(handleElement, () => {
|
watch(handleElement, () => {
|
||||||
if (!handleElement.value || handleElement.value.type !== 'table') return
|
if (!handleElement.value || handleElement.value.type !== 'table') return
|
||||||
|
|
||||||
theme.value = handleElement.value.theme
|
theme.value = handleElement.value.theme
|
||||||
@ -244,12 +235,12 @@ export default defineComponent({
|
|||||||
|
|
||||||
minRowCount.value = handleElement.value.data.length
|
minRowCount.value = handleElement.value.data.length
|
||||||
minColCount.value = handleElement.value.data[0].length
|
minColCount.value = handleElement.value.data[0].length
|
||||||
}, { deep: true, immediate: true })
|
}, { deep: true, immediate: true })
|
||||||
|
|
||||||
const { addHistorySnapshot } = useHistorySnapshot()
|
const { addHistorySnapshot } = useHistorySnapshot()
|
||||||
|
|
||||||
// 更新当前选中单元格的文本样式状态
|
// 更新当前选中单元格的文本样式状态
|
||||||
const updateTextAttrState = () => {
|
const updateTextAttrState = () => {
|
||||||
if (!handleElement.value || handleElement.value.type !== 'table') return
|
if (!handleElement.value || handleElement.value.type !== 'table') return
|
||||||
|
|
||||||
let rowIndex = 0
|
let rowIndex = 0
|
||||||
@ -287,21 +278,21 @@ export default defineComponent({
|
|||||||
align: style.align || 'left',
|
align: style.align || 'left',
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
if (selectedCells.value.length) updateTextAttrState()
|
if (selectedCells.value.length) updateTextAttrState()
|
||||||
})
|
})
|
||||||
|
|
||||||
watch(selectedCells, updateTextAttrState)
|
watch(selectedCells, updateTextAttrState)
|
||||||
|
|
||||||
const updateElement = (props: Partial<PPTTableElement>) => {
|
const updateElement = (props: Partial<PPTTableElement>) => {
|
||||||
slidesStore.updateElement({ id: handleElementId.value, props })
|
slidesStore.updateElement({ id: handleElementId.value, props })
|
||||||
addHistorySnapshot()
|
addHistorySnapshot()
|
||||||
}
|
}
|
||||||
|
|
||||||
// 设置单元格内容文本样式
|
// 设置单元格内容文本样式
|
||||||
const updateTextAttrs = (textAttrProp: Partial<TableCellStyle>) => {
|
const updateTextAttrs = (textAttrProp: Partial<TableCellStyle>) => {
|
||||||
const _handleElement = handleElement.value as PPTTableElement
|
const _handleElement = handleElement.value as PPTTableElement
|
||||||
|
|
||||||
const data: TableCell[][] = JSON.parse(JSON.stringify(_handleElement.data))
|
const data: TableCell[][] = JSON.parse(JSON.stringify(_handleElement.data))
|
||||||
@ -316,17 +307,17 @@ export default defineComponent({
|
|||||||
}
|
}
|
||||||
updateElement({ data })
|
updateElement({ data })
|
||||||
updateTextAttrState()
|
updateTextAttrState()
|
||||||
}
|
}
|
||||||
|
|
||||||
// 更新表格主题:主题色、标题行、汇总行、第一列、最后一列
|
// 更新表格主题:主题色、标题行、汇总行、第一列、最后一列
|
||||||
const updateTheme = (themeProp: Partial<TableTheme>) => {
|
const updateTheme = (themeProp: Partial<TableTheme>) => {
|
||||||
if (!theme.value) return
|
if (!theme.value) return
|
||||||
const _theme = { ...theme.value, ...themeProp }
|
const _theme = { ...theme.value, ...themeProp }
|
||||||
updateElement({ theme: _theme })
|
updateElement({ theme: _theme })
|
||||||
}
|
}
|
||||||
|
|
||||||
// 开启/关闭表格主题
|
// 开启/关闭表格主题
|
||||||
const toggleTheme = (checked: boolean) => {
|
const toggleTheme = (checked: boolean) => {
|
||||||
if (checked) {
|
if (checked) {
|
||||||
const props = {
|
const props = {
|
||||||
theme: {
|
theme: {
|
||||||
@ -343,10 +334,10 @@ export default defineComponent({
|
|||||||
slidesStore.removeElementProps({ id: handleElementId.value, propName: 'theme' })
|
slidesStore.removeElementProps({ id: handleElementId.value, propName: 'theme' })
|
||||||
addHistorySnapshot()
|
addHistorySnapshot()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 设置表格行数
|
// 设置表格行数
|
||||||
const setTableRow = (value: number) => {
|
const setTableRow = (value: number) => {
|
||||||
const _handleElement = handleElement.value as PPTTableElement
|
const _handleElement = handleElement.value as PPTTableElement
|
||||||
const rowCount = _handleElement.data.length
|
const rowCount = _handleElement.data.length
|
||||||
|
|
||||||
@ -363,10 +354,10 @@ export default defineComponent({
|
|||||||
const tableCells: TableCell[][] = _handleElement.data.slice(0, value)
|
const tableCells: TableCell[][] = _handleElement.data.slice(0, value)
|
||||||
updateElement({ data: tableCells })
|
updateElement({ data: tableCells })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 设置表格列数
|
// 设置表格列数
|
||||||
const setTableCol = (value: number) => {
|
const setTableCol = (value: number) => {
|
||||||
const _handleElement = handleElement.value as PPTTableElement
|
const _handleElement = handleElement.value as PPTTableElement
|
||||||
const colCount = _handleElement.data[0].length
|
const colCount = _handleElement.data[0].length
|
||||||
|
|
||||||
@ -397,27 +388,7 @@ export default defineComponent({
|
|||||||
colWidths,
|
colWidths,
|
||||||
}
|
}
|
||||||
updateElement(props)
|
updateElement(props)
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
|
||||||
availableFonts,
|
|
||||||
fontSizeOptions,
|
|
||||||
textAttrs,
|
|
||||||
updateTextAttrs,
|
|
||||||
theme,
|
|
||||||
rowCount,
|
|
||||||
colCount,
|
|
||||||
minRowCount,
|
|
||||||
minColCount,
|
|
||||||
hasTheme,
|
|
||||||
toggleTheme,
|
|
||||||
updateTheme,
|
|
||||||
setTableRow,
|
|
||||||
setTableCol,
|
|
||||||
webFonts,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
})
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
@ -25,7 +25,7 @@
|
|||||||
</SelectOption>
|
</SelectOption>
|
||||||
</SelectOptGroup>
|
</SelectOptGroup>
|
||||||
<SelectOptGroup label="在线字体">
|
<SelectOptGroup label="在线字体">
|
||||||
<SelectOption v-for="font in webFonts" :key="font.value" :value="font.value">
|
<SelectOption v-for="font in WEB_FONTS" :key="font.value" :value="font.value">
|
||||||
<span>{{font.label}}</span>
|
<span>{{font.label}}</span>
|
||||||
</SelectOption>
|
</SelectOption>
|
||||||
</SelectOptGroup>
|
</SelectOptGroup>
|
||||||
@ -270,8 +270,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts" setup>
|
||||||
import { defineComponent, ref, watch } from 'vue'
|
import { ref, watch } from 'vue'
|
||||||
import { storeToRefs } from 'pinia'
|
import { storeToRefs } from 'pinia'
|
||||||
import { useMainStore, useSlidesStore } from '@/store'
|
import { useMainStore, useSlidesStore } from '@/store'
|
||||||
import { PPTTextElement } from '@/types/slides'
|
import { PPTTextElement } from '@/types/slides'
|
||||||
@ -358,34 +358,23 @@ const presetStyles = [
|
|||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
const webFonts = WEB_FONTS
|
const slidesStore = useSlidesStore()
|
||||||
|
const { handleElement, handleElementId, richTextAttrs, availableFonts } = storeToRefs(useMainStore())
|
||||||
|
|
||||||
export default defineComponent({
|
const { addHistorySnapshot } = useHistorySnapshot()
|
||||||
name: 'text-style-panel',
|
|
||||||
components: {
|
|
||||||
ElementOpacity,
|
|
||||||
ElementOutline,
|
|
||||||
ElementShadow,
|
|
||||||
ColorButton,
|
|
||||||
},
|
|
||||||
setup() {
|
|
||||||
const slidesStore = useSlidesStore()
|
|
||||||
const { handleElement, handleElementId, richTextAttrs, availableFonts } = storeToRefs(useMainStore())
|
|
||||||
|
|
||||||
const { addHistorySnapshot } = useHistorySnapshot()
|
const updateElement = (props: Partial<PPTTextElement>) => {
|
||||||
|
|
||||||
const updateElement = (props: Partial<PPTTextElement>) => {
|
|
||||||
slidesStore.updateElement({ id: handleElementId.value, props })
|
slidesStore.updateElement({ id: handleElementId.value, props })
|
||||||
addHistorySnapshot()
|
addHistorySnapshot()
|
||||||
}
|
}
|
||||||
|
|
||||||
const fill = ref<string>('#000')
|
const fill = ref<string>('#000')
|
||||||
const lineHeight = ref<number>()
|
const lineHeight = ref<number>()
|
||||||
const wordSpace = ref<number>()
|
const wordSpace = ref<number>()
|
||||||
const textIndent = ref<number>()
|
const textIndent = ref<number>()
|
||||||
const paragraphSpace = ref<number>()
|
const paragraphSpace = ref<number>()
|
||||||
|
|
||||||
watch(handleElement, () => {
|
watch(handleElement, () => {
|
||||||
if (!handleElement.value || handleElement.value.type !== 'text') return
|
if (!handleElement.value || handleElement.value.type !== 'text') return
|
||||||
|
|
||||||
fill.value = handleElement.value.fill || '#fff'
|
fill.value = handleElement.value.fill || '#fff'
|
||||||
@ -393,101 +382,71 @@ export default defineComponent({
|
|||||||
wordSpace.value = handleElement.value.wordSpace || 0
|
wordSpace.value = handleElement.value.wordSpace || 0
|
||||||
textIndent.value = handleElement.value.textIndent || 0
|
textIndent.value = handleElement.value.textIndent || 0
|
||||||
paragraphSpace.value = handleElement.value.paragraphSpace === undefined ? 5 : handleElement.value.paragraphSpace
|
paragraphSpace.value = handleElement.value.paragraphSpace === undefined ? 5 : handleElement.value.paragraphSpace
|
||||||
}, { deep: true, immediate: true })
|
}, { deep: true, immediate: true })
|
||||||
|
|
||||||
const fontSizeOptions = [
|
const fontSizeOptions = [
|
||||||
'12px', '14px', '16px', '18px', '20px', '22px', '24px', '28px', '32px',
|
'12px', '14px', '16px', '18px', '20px', '22px', '24px', '28px', '32px',
|
||||||
'36px', '40px', '44px', '48px', '54px', '60px', '66px', '72px', '76px',
|
'36px', '40px', '44px', '48px', '54px', '60px', '66px', '72px', '76px',
|
||||||
'80px', '88px', '96px', '104px', '112px', '120px',
|
'80px', '88px', '96px', '104px', '112px', '120px',
|
||||||
]
|
]
|
||||||
const lineHeightOptions = [0.9, 1.0, 1.15, 1.2, 1.4, 1.5, 1.8, 2.0, 2.5, 3.0]
|
const lineHeightOptions = [0.9, 1.0, 1.15, 1.2, 1.4, 1.5, 1.8, 2.0, 2.5, 3.0]
|
||||||
const wordSpaceOptions = [0, 1, 2, 3, 4, 5, 6, 8, 10]
|
const wordSpaceOptions = [0, 1, 2, 3, 4, 5, 6, 8, 10]
|
||||||
const textIndentOptions = [0, 48, 96, 144, 192, 240, 288, 336]
|
const textIndentOptions = [0, 48, 96, 144, 192, 240, 288, 336]
|
||||||
const paragraphSpaceOptions = [0, 5, 10, 15, 20, 25, 30, 40, 50, 80]
|
const paragraphSpaceOptions = [0, 5, 10, 15, 20, 25, 30, 40, 50, 80]
|
||||||
|
|
||||||
// 设置行高
|
// 设置行高
|
||||||
const updateLineHeight = (value: number) => {
|
const updateLineHeight = (value: number) => {
|
||||||
updateElement({ lineHeight: value })
|
updateElement({ lineHeight: value })
|
||||||
}
|
}
|
||||||
|
|
||||||
// 设置段间距
|
// 设置段间距
|
||||||
const updateParagraphSpace = (value: number) => {
|
const updateParagraphSpace = (value: number) => {
|
||||||
updateElement({ paragraphSpace: value })
|
updateElement({ paragraphSpace: value })
|
||||||
}
|
}
|
||||||
|
|
||||||
// 设置字间距
|
// 设置字间距
|
||||||
const updateWordSpace = (value: number) => {
|
const updateWordSpace = (value: number) => {
|
||||||
updateElement({ wordSpace: value })
|
updateElement({ wordSpace: value })
|
||||||
}
|
}
|
||||||
|
|
||||||
// 设置首行缩进
|
// 设置首行缩进
|
||||||
const updateTextIndent = (value: number) => {
|
const updateTextIndent = (value: number) => {
|
||||||
updateElement({ textIndent: value })
|
updateElement({ textIndent: value })
|
||||||
}
|
}
|
||||||
|
|
||||||
// 设置文本框填充
|
// 设置文本框填充
|
||||||
const updateFill = (value: string) => {
|
const updateFill = (value: string) => {
|
||||||
updateElement({ fill: value })
|
updateElement({ fill: value })
|
||||||
}
|
}
|
||||||
|
|
||||||
// 发射富文本设置命令
|
// 发射富文本设置命令
|
||||||
const emitRichTextCommand = (command: string, value?: string) => {
|
const emitRichTextCommand = (command: string, value?: string) => {
|
||||||
emitter.emit(EmitterEvents.RICH_TEXT_COMMAND, { action: { command, value } })
|
emitter.emit(EmitterEvents.RICH_TEXT_COMMAND, { action: { command, value } })
|
||||||
}
|
}
|
||||||
|
|
||||||
// 发射富文本设置命令(批量)
|
// 发射富文本设置命令(批量)
|
||||||
const emitBatchRichTextCommand = (action: RichTextAction[]) => {
|
const emitBatchRichTextCommand = (action: RichTextAction[]) => {
|
||||||
emitter.emit(EmitterEvents.RICH_TEXT_COMMAND, { action })
|
emitter.emit(EmitterEvents.RICH_TEXT_COMMAND, { action })
|
||||||
}
|
}
|
||||||
|
|
||||||
// 设置富文本超链接
|
// 设置富文本超链接
|
||||||
const link = ref('')
|
const link = ref('')
|
||||||
const linkPopoverVisible = ref(false)
|
const linkPopoverVisible = ref(false)
|
||||||
|
|
||||||
watch(richTextAttrs, () => linkPopoverVisible.value = false)
|
watch(richTextAttrs, () => linkPopoverVisible.value = false)
|
||||||
|
|
||||||
const openLinkPopover = () => {
|
const openLinkPopover = () => {
|
||||||
link.value = richTextAttrs.value.link
|
link.value = richTextAttrs.value.link
|
||||||
linkPopoverVisible.value = true
|
linkPopoverVisible.value = true
|
||||||
}
|
}
|
||||||
const updateLink = (link?: string) => {
|
const updateLink = (link?: string) => {
|
||||||
if (link) {
|
if (link) {
|
||||||
const linkRegExp = /^(https?):\/\/[\w\-]+(\.[\w\-]+)+([\w\-.,@?^=%&:\/~+#]*[\w\-@?^=%&\/~+#])?$/
|
const linkRegExp = /^(https?):\/\/[\w\-]+(\.[\w\-]+)+([\w\-.,@?^=%&:\/~+#]*[\w\-@?^=%&\/~+#])?$/
|
||||||
if (!linkRegExp.test(link)) return message.error('不是正确的网页链接地址')
|
if (!linkRegExp.test(link)) return message.error('不是正确的网页链接地址')
|
||||||
}
|
}
|
||||||
emitRichTextCommand('link', link)
|
emitRichTextCommand('link', link)
|
||||||
linkPopoverVisible.value = false
|
linkPopoverVisible.value = false
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
|
||||||
fill,
|
|
||||||
lineHeight,
|
|
||||||
wordSpace,
|
|
||||||
textIndent,
|
|
||||||
paragraphSpace,
|
|
||||||
richTextAttrs,
|
|
||||||
availableFonts,
|
|
||||||
webFonts,
|
|
||||||
fontSizeOptions,
|
|
||||||
lineHeightOptions,
|
|
||||||
wordSpaceOptions,
|
|
||||||
textIndentOptions,
|
|
||||||
paragraphSpaceOptions,
|
|
||||||
updateLineHeight,
|
|
||||||
updateParagraphSpace,
|
|
||||||
updateWordSpace,
|
|
||||||
updateTextIndent,
|
|
||||||
updateFill,
|
|
||||||
emitRichTextCommand,
|
|
||||||
emitBatchRichTextCommand,
|
|
||||||
presetStyles,
|
|
||||||
link,
|
|
||||||
linkPopoverVisible,
|
|
||||||
openLinkPopover,
|
|
||||||
updateLink,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
})
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
<div class="background-image-wrapper">
|
<div class="background-image-wrapper">
|
||||||
<FileInput @change="files => setVideoPoster(files)">
|
<FileInput @change="files => setVideoPoster(files)">
|
||||||
<div class="background-image">
|
<div class="background-image">
|
||||||
<div class="content" :style="{ backgroundImage: `url(${handleElement.poster})` }">
|
<div class="content" :style="{ backgroundImage: `url(${handleVideoElement.poster})` }">
|
||||||
<IconPlus />
|
<IconPlus />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -14,42 +14,33 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts" setup>
|
||||||
import { defineComponent } from 'vue'
|
import { Ref } from 'vue'
|
||||||
import { storeToRefs } from 'pinia'
|
import { storeToRefs } from 'pinia'
|
||||||
import { useMainStore, useSlidesStore } from '@/store'
|
import { useMainStore, useSlidesStore } from '@/store'
|
||||||
import { PPTVideoElement } from '@/types/slides'
|
import { PPTVideoElement } from '@/types/slides'
|
||||||
import { getImageDataURL } from '@/utils/image'
|
import { getImageDataURL } from '@/utils/image'
|
||||||
import useHistorySnapshot from '@/hooks/useHistorySnapshot'
|
import useHistorySnapshot from '@/hooks/useHistorySnapshot'
|
||||||
|
|
||||||
export default defineComponent({
|
const slidesStore = useSlidesStore()
|
||||||
name: 'video-style-panel',
|
const { handleElement } = storeToRefs(useMainStore())
|
||||||
setup() {
|
|
||||||
const slidesStore = useSlidesStore()
|
|
||||||
const { handleElement } = storeToRefs(useMainStore())
|
|
||||||
|
|
||||||
const { addHistorySnapshot } = useHistorySnapshot()
|
const handleVideoElement = handleElement as Ref<PPTVideoElement>
|
||||||
|
|
||||||
const updateVideo = (props: Partial<PPTVideoElement>) => {
|
const { addHistorySnapshot } = useHistorySnapshot()
|
||||||
|
|
||||||
|
const updateVideo = (props: Partial<PPTVideoElement>) => {
|
||||||
if (!handleElement.value) return
|
if (!handleElement.value) return
|
||||||
slidesStore.updateElement({ id: handleElement.value.id, props })
|
slidesStore.updateElement({ id: handleElement.value.id, props })
|
||||||
addHistorySnapshot()
|
addHistorySnapshot()
|
||||||
}
|
}
|
||||||
|
|
||||||
// 设置视频预览封面
|
// 设置视频预览封面
|
||||||
const setVideoPoster = (files: File[]) => {
|
const setVideoPoster = (files: FileList) => {
|
||||||
const imageFile = files[0]
|
const imageFile = files[0]
|
||||||
if (!imageFile) return
|
if (!imageFile) return
|
||||||
getImageDataURL(imageFile).then(dataURL => updateVideo({ poster: dataURL }))
|
getImageDataURL(imageFile).then(dataURL => updateVideo({ poster: dataURL }))
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
|
||||||
handleElement,
|
|
||||||
updateVideo,
|
|
||||||
setVideoPoster,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
@ -4,8 +4,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts" setup>
|
||||||
import { computed, defineComponent } from 'vue'
|
import { computed } from 'vue'
|
||||||
import { storeToRefs } from 'pinia'
|
import { storeToRefs } from 'pinia'
|
||||||
import { useMainStore } from '@/store'
|
import { useMainStore } from '@/store'
|
||||||
import { ElementTypes } from '@/types/slides'
|
import { ElementTypes } from '@/types/slides'
|
||||||
@ -33,12 +33,9 @@ const panelMap = {
|
|||||||
[ElementTypes.AUDIO]: AudioStylePanel,
|
[ElementTypes.AUDIO]: AudioStylePanel,
|
||||||
}
|
}
|
||||||
|
|
||||||
export default defineComponent({
|
const { activeElementIdList, activeElementList, handleElement, activeGroupElementId } = storeToRefs(useMainStore())
|
||||||
name: 'element-style-panel',
|
|
||||||
setup() {
|
|
||||||
const { activeElementIdList, activeElementList, handleElement, activeGroupElementId } = storeToRefs(useMainStore())
|
|
||||||
|
|
||||||
const currentPanelComponent = computed(() => {
|
const currentPanelComponent = computed(() => {
|
||||||
if (activeElementIdList.value.length > 1) {
|
if (activeElementIdList.value.length > 1) {
|
||||||
if (!activeGroupElementId.value) return MultiStylePanel
|
if (!activeGroupElementId.value) return MultiStylePanel
|
||||||
|
|
||||||
@ -47,12 +44,5 @@ export default defineComponent({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return handleElement.value ? (panelMap[handleElement.value.type] || null) : null
|
return handleElement.value ? (panelMap[handleElement.value.type] || null) : null
|
||||||
})
|
|
||||||
|
|
||||||
return {
|
|
||||||
handleElement,
|
|
||||||
currentPanelComponent,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
@ -36,42 +36,25 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts" setup>
|
||||||
import { defineComponent } from 'vue'
|
|
||||||
import { ElementAlignCommands } from '@/types/edit'
|
import { ElementAlignCommands } from '@/types/edit'
|
||||||
import useCombineElement from '@/hooks/useCombineElement'
|
import useCombineElement from '@/hooks/useCombineElement'
|
||||||
import useAlignActiveElement from '@/hooks/useAlignActiveElement'
|
import useAlignActiveElement from '@/hooks/useAlignActiveElement'
|
||||||
import useAlignElementToCanvas from '@/hooks/useAlignElementToCanvas'
|
import useAlignElementToCanvas from '@/hooks/useAlignElementToCanvas'
|
||||||
import useUniformDisplayElement from '@/hooks/useUniformDisplayElement'
|
import useUniformDisplayElement from '@/hooks/useUniformDisplayElement'
|
||||||
|
|
||||||
export default defineComponent({
|
const { canCombine, combineElements, uncombineElements } = useCombineElement()
|
||||||
name: 'multi-position-panel',
|
const { alignActiveElement } = useAlignActiveElement()
|
||||||
setup() {
|
const { alignElementToCanvas } = useAlignElementToCanvas()
|
||||||
const { canCombine, combineElements, uncombineElements } = useCombineElement()
|
const { displayItemCount, uniformHorizontalDisplay, uniformVerticalDisplay } = useUniformDisplayElement()
|
||||||
const { alignActiveElement } = useAlignActiveElement()
|
|
||||||
const { alignElementToCanvas } = useAlignElementToCanvas()
|
|
||||||
const { displayItemCount, uniformHorizontalDisplay, uniformVerticalDisplay } = useUniformDisplayElement()
|
|
||||||
|
|
||||||
// 多选元素对齐,需要先判断当前所选中的元素状态:
|
// 多选元素对齐,需要先判断当前所选中的元素状态:
|
||||||
// 如果所选元素为一组组合元素,则将它对齐到画布;
|
// 如果所选元素为一组组合元素,则将它对齐到画布;
|
||||||
// 如果所选元素不是组合元素或不止一组元素(即当前为可组合状态),则将这多个(多组)元素相互对齐。
|
// 如果所选元素不是组合元素或不止一组元素(即当前为可组合状态),则将这多个(多组)元素相互对齐。
|
||||||
const alignElement = (command: ElementAlignCommands) => {
|
const alignElement = (command: ElementAlignCommands) => {
|
||||||
if (canCombine.value) alignActiveElement(command)
|
if (canCombine.value) alignActiveElement(command)
|
||||||
else alignElementToCanvas(command)
|
else alignElementToCanvas(command)
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
|
||||||
canCombine,
|
|
||||||
displayItemCount,
|
|
||||||
combineElements,
|
|
||||||
uncombineElements,
|
|
||||||
uniformHorizontalDisplay,
|
|
||||||
uniformVerticalDisplay,
|
|
||||||
alignElement,
|
|
||||||
ElementAlignCommands,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
})
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
@ -16,8 +16,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts" setup>
|
||||||
import { computed, defineComponent } from 'vue'
|
import { computed } from 'vue'
|
||||||
import { storeToRefs } from 'pinia'
|
import { storeToRefs } from 'pinia'
|
||||||
import { useSlidesStore } from '@/store'
|
import { useSlidesStore } from '@/store'
|
||||||
import { TurningMode } from '@/types/slides'
|
import { TurningMode } from '@/types/slides'
|
||||||
@ -28,32 +28,29 @@ interface Animations {
|
|||||||
value: TurningMode
|
value: TurningMode
|
||||||
}
|
}
|
||||||
|
|
||||||
export default defineComponent({
|
const slidesStore = useSlidesStore()
|
||||||
name: 'slide-animation-panel',
|
const { slides, currentSlide } = storeToRefs(slidesStore)
|
||||||
setup() {
|
|
||||||
const slidesStore = useSlidesStore()
|
|
||||||
const { slides, currentSlide } = storeToRefs(slidesStore)
|
|
||||||
|
|
||||||
const currentTurningMode = computed(() => currentSlide.value.turningMode || 'slideY')
|
const currentTurningMode = computed(() => currentSlide.value.turningMode || 'slideY')
|
||||||
|
|
||||||
const animations: Animations[] = [
|
const animations: Animations[] = [
|
||||||
{ label: '无', value: 'no' },
|
{ label: '无', value: 'no' },
|
||||||
{ label: '淡入淡出', value: 'fade' },
|
{ label: '淡入淡出', value: 'fade' },
|
||||||
{ label: '左右推移', value: 'slideX' },
|
{ label: '左右推移', value: 'slideX' },
|
||||||
{ label: '上下推移', value: 'slideY' },
|
{ label: '上下推移', value: 'slideY' },
|
||||||
]
|
]
|
||||||
|
|
||||||
const { addHistorySnapshot } = useHistorySnapshot()
|
const { addHistorySnapshot } = useHistorySnapshot()
|
||||||
|
|
||||||
// 修改播放时的切换页面方式
|
// 修改播放时的切换页面方式
|
||||||
const updateTurningMode = (mode: TurningMode) => {
|
const updateTurningMode = (mode: TurningMode) => {
|
||||||
if (mode === currentTurningMode.value) return
|
if (mode === currentTurningMode.value) return
|
||||||
slidesStore.updateSlide({ turningMode: mode })
|
slidesStore.updateSlide({ turningMode: mode })
|
||||||
addHistorySnapshot()
|
addHistorySnapshot()
|
||||||
}
|
}
|
||||||
|
|
||||||
// 将当前页的切换页面方式应用到全部页面
|
// 将当前页的切换页面方式应用到全部页面
|
||||||
const applyAllSlide = () => {
|
const applyAllSlide = () => {
|
||||||
const newSlides = slides.value.map(slide => {
|
const newSlides = slides.value.map(slide => {
|
||||||
return {
|
return {
|
||||||
...slide,
|
...slide,
|
||||||
@ -62,16 +59,7 @@ export default defineComponent({
|
|||||||
})
|
})
|
||||||
slidesStore.setSlides(newSlides)
|
slidesStore.setSlides(newSlides)
|
||||||
addHistorySnapshot()
|
addHistorySnapshot()
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
|
||||||
currentTurningMode,
|
|
||||||
animations,
|
|
||||||
updateTurningMode,
|
|
||||||
applyAllSlide,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
})
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
@ -122,7 +122,7 @@
|
|||||||
</SelectOption>
|
</SelectOption>
|
||||||
</SelectOptGroup>
|
</SelectOptGroup>
|
||||||
<SelectOptGroup label="在线字体">
|
<SelectOptGroup label="在线字体">
|
||||||
<SelectOption v-for="font in webFonts" :key="font.value" :value="font.value">
|
<SelectOption v-for="font in WEB_FONTS" :key="font.value" :value="font.value">
|
||||||
<span>{{font.label}}</span>
|
<span>{{font.label}}</span>
|
||||||
</SelectOption>
|
</SelectOption>
|
||||||
</SelectOptGroup>
|
</SelectOptGroup>
|
||||||
@ -171,7 +171,7 @@
|
|||||||
<div class="theme-list" v-if="showPresetThemes">
|
<div class="theme-list" v-if="showPresetThemes">
|
||||||
<div
|
<div
|
||||||
class="theme-item"
|
class="theme-item"
|
||||||
v-for="(item, index) in themes"
|
v-for="(item, index) in PRESET_THEMES"
|
||||||
:key="index"
|
:key="index"
|
||||||
:style="{ backgroundColor: item.background }"
|
:style="{ backgroundColor: item.background }"
|
||||||
@click="updateTheme({
|
@click="updateTheme({
|
||||||
@ -191,8 +191,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts" setup>
|
||||||
import { computed, defineComponent, ref } from 'vue'
|
import { computed, ref } from 'vue'
|
||||||
import { storeToRefs } from 'pinia'
|
import { storeToRefs } from 'pinia'
|
||||||
import { useMainStore, useSlidesStore } from '@/store'
|
import { useMainStore, useSlidesStore } from '@/store'
|
||||||
import { Slide, SlideBackground, SlideTheme } from '@/types/slides'
|
import { Slide, SlideBackground, SlideTheme } from '@/types/slides'
|
||||||
@ -203,20 +203,11 @@ import useHistorySnapshot from '@/hooks/useHistorySnapshot'
|
|||||||
import ColorButton from './common/ColorButton.vue'
|
import ColorButton from './common/ColorButton.vue'
|
||||||
import { getImageDataURL } from '@/utils/image'
|
import { getImageDataURL } from '@/utils/image'
|
||||||
|
|
||||||
const themes = PRESET_THEMES
|
const slidesStore = useSlidesStore()
|
||||||
const webFonts = WEB_FONTS
|
const { availableFonts } = storeToRefs(useMainStore())
|
||||||
|
const { slides, currentSlide, viewportRatio, theme } = storeToRefs(slidesStore)
|
||||||
|
|
||||||
export default defineComponent({
|
const background = computed(() => {
|
||||||
name: 'slide-design-panel',
|
|
||||||
components: {
|
|
||||||
ColorButton,
|
|
||||||
},
|
|
||||||
setup() {
|
|
||||||
const slidesStore = useSlidesStore()
|
|
||||||
const { availableFonts } = storeToRefs(useMainStore())
|
|
||||||
const { slides, currentSlide, viewportRatio, theme } = storeToRefs(slidesStore)
|
|
||||||
|
|
||||||
const background = computed(() => {
|
|
||||||
if (!currentSlide.value.background) {
|
if (!currentSlide.value.background) {
|
||||||
return {
|
return {
|
||||||
type: 'solid',
|
type: 'solid',
|
||||||
@ -224,12 +215,12 @@ export default defineComponent({
|
|||||||
} as SlideBackground
|
} as SlideBackground
|
||||||
}
|
}
|
||||||
return currentSlide.value.background
|
return currentSlide.value.background
|
||||||
})
|
})
|
||||||
|
|
||||||
const { addHistorySnapshot } = useHistorySnapshot()
|
const { addHistorySnapshot } = useHistorySnapshot()
|
||||||
|
|
||||||
// 设置背景模式:纯色、图片、渐变色
|
// 设置背景模式:纯色、图片、渐变色
|
||||||
const updateBackgroundType = (type: 'solid' | 'image' | 'gradient') => {
|
const updateBackgroundType = (type: 'solid' | 'image' | 'gradient') => {
|
||||||
if (type === 'solid') {
|
if (type === 'solid') {
|
||||||
const newBackground: SlideBackground = {
|
const newBackground: SlideBackground = {
|
||||||
...background.value,
|
...background.value,
|
||||||
@ -258,23 +249,23 @@ export default defineComponent({
|
|||||||
slidesStore.updateSlide({ background: newBackground })
|
slidesStore.updateSlide({ background: newBackground })
|
||||||
}
|
}
|
||||||
addHistorySnapshot()
|
addHistorySnapshot()
|
||||||
}
|
}
|
||||||
|
|
||||||
// 设置背景图片
|
// 设置背景图片
|
||||||
const updateBackground = (props: Partial<SlideBackground>) => {
|
const updateBackground = (props: Partial<SlideBackground>) => {
|
||||||
slidesStore.updateSlide({ background: { ...background.value, ...props } })
|
slidesStore.updateSlide({ background: { ...background.value, ...props } })
|
||||||
addHistorySnapshot()
|
addHistorySnapshot()
|
||||||
}
|
}
|
||||||
|
|
||||||
// 上传背景图片
|
// 上传背景图片
|
||||||
const uploadBackgroundImage = (files: File[]) => {
|
const uploadBackgroundImage = (files: FileList) => {
|
||||||
const imageFile = files[0]
|
const imageFile = files[0]
|
||||||
if (!imageFile) return
|
if (!imageFile) return
|
||||||
getImageDataURL(imageFile).then(dataURL => updateBackground({ image: dataURL }))
|
getImageDataURL(imageFile).then(dataURL => updateBackground({ image: dataURL }))
|
||||||
}
|
}
|
||||||
|
|
||||||
// 应用当前页背景到全部页面
|
// 应用当前页背景到全部页面
|
||||||
const applyBackgroundAllSlide = () => {
|
const applyBackgroundAllSlide = () => {
|
||||||
const newSlides = slides.value.map(slide => {
|
const newSlides = slides.value.map(slide => {
|
||||||
return {
|
return {
|
||||||
...slide,
|
...slide,
|
||||||
@ -283,15 +274,15 @@ export default defineComponent({
|
|||||||
})
|
})
|
||||||
slidesStore.setSlides(newSlides)
|
slidesStore.setSlides(newSlides)
|
||||||
addHistorySnapshot()
|
addHistorySnapshot()
|
||||||
}
|
}
|
||||||
|
|
||||||
// 设置主题
|
// 设置主题
|
||||||
const updateTheme = (themeProps: Partial<SlideTheme>) => {
|
const updateTheme = (themeProps: Partial<SlideTheme>) => {
|
||||||
slidesStore.setTheme(themeProps)
|
slidesStore.setTheme(themeProps)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 将当前主题应用到全部页面
|
// 将当前主题应用到全部页面
|
||||||
const applyThemeAllSlide = () => {
|
const applyThemeAllSlide = () => {
|
||||||
const newSlides: Slide[] = JSON.parse(JSON.stringify(slides.value))
|
const newSlides: Slide[] = JSON.parse(JSON.stringify(slides.value))
|
||||||
const { themeColor, backgroundColor, fontColor, fontName } = theme.value
|
const { themeColor, backgroundColor, fontColor, fontName } = theme.value
|
||||||
|
|
||||||
@ -334,38 +325,18 @@ export default defineComponent({
|
|||||||
}
|
}
|
||||||
slidesStore.setSlides(newSlides)
|
slidesStore.setSlides(newSlides)
|
||||||
addHistorySnapshot()
|
addHistorySnapshot()
|
||||||
}
|
}
|
||||||
|
|
||||||
// 是否显示预设主题
|
// 是否显示预设主题
|
||||||
const showPresetThemes = ref(true)
|
const showPresetThemes = ref(true)
|
||||||
const togglePresetThemesVisible = () => {
|
const togglePresetThemesVisible = () => {
|
||||||
showPresetThemes.value = !showPresetThemes.value
|
showPresetThemes.value = !showPresetThemes.value
|
||||||
}
|
}
|
||||||
|
|
||||||
// 设置画布尺寸(宽高比例)
|
// 设置画布尺寸(宽高比例)
|
||||||
const updateViewportRatio = (value: number) => {
|
const updateViewportRatio = (value: number) => {
|
||||||
slidesStore.setViewportRatio(value)
|
slidesStore.setViewportRatio(value)
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
|
||||||
availableFonts,
|
|
||||||
background,
|
|
||||||
updateBackgroundType,
|
|
||||||
updateBackground,
|
|
||||||
uploadBackgroundImage,
|
|
||||||
applyBackgroundAllSlide,
|
|
||||||
themes,
|
|
||||||
theme,
|
|
||||||
webFonts,
|
|
||||||
updateTheme,
|
|
||||||
applyThemeAllSlide,
|
|
||||||
viewportRatio,
|
|
||||||
updateViewportRatio,
|
|
||||||
showPresetThemes,
|
|
||||||
togglePresetThemesVisible,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
})
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
<div
|
<div
|
||||||
class="tab"
|
class="tab"
|
||||||
:class="{ 'active': selectedSymbolKey === item.key }"
|
:class="{ 'active': selectedSymbolKey === item.key }"
|
||||||
v-for="item in symbolPoolList"
|
v-for="item in SYMBOL_LIST"
|
||||||
:key="item.key"
|
:key="item.key"
|
||||||
@click="selectedSymbolKey = item.key"
|
@click="selectedSymbolKey = item.key"
|
||||||
>{{item.label}}</div>
|
>{{item.label}}</div>
|
||||||
@ -17,34 +17,20 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts" setup>
|
||||||
import { computed, defineComponent, ref } from 'vue'
|
import { computed, ref } from 'vue'
|
||||||
import { SYMBOL_LIST } from '@/configs/symbol'
|
import { SYMBOL_LIST } from '@/configs/symbol'
|
||||||
import emitter, { EmitterEvents } from '@/utils/emitter'
|
import emitter, { EmitterEvents } from '@/utils/emitter'
|
||||||
|
|
||||||
const symbolPoolList = SYMBOL_LIST
|
const selectedSymbolKey = ref(SYMBOL_LIST[0].key)
|
||||||
|
const symbolPool = computed(() => {
|
||||||
export default defineComponent({
|
const selectedSymbol = SYMBOL_LIST.find(item => item.key === selectedSymbolKey.value)
|
||||||
name: 'symbol-panel',
|
|
||||||
setup() {
|
|
||||||
const selectedSymbolKey = ref(symbolPoolList[0].key)
|
|
||||||
const symbolPool = computed(() => {
|
|
||||||
const selectedSymbol = symbolPoolList.find(item => item.key === selectedSymbolKey.value)
|
|
||||||
return selectedSymbol?.children || []
|
return selectedSymbol?.children || []
|
||||||
})
|
|
||||||
|
|
||||||
const selectSymbol = (value: string) => {
|
|
||||||
emitter.emit(EmitterEvents.RICH_TEXT_COMMAND, { action: { command: 'insert', value } })
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
symbolPoolList,
|
|
||||||
symbolPool,
|
|
||||||
selectedSymbolKey,
|
|
||||||
selectSymbol,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const selectSymbol = (value: string) => {
|
||||||
|
emitter.emit(EmitterEvents.RICH_TEXT_COMMAND, { action: { command: 'insert', value } })
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
@ -7,17 +7,12 @@
|
|||||||
</Button>
|
</Button>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts" setup>
|
||||||
import { defineComponent } from 'vue'
|
defineProps({
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
name: 'color-button',
|
|
||||||
props: {
|
|
||||||
color: {
|
color: {
|
||||||
type: String,
|
type: String,
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
},
|
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@ -25,8 +25,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts" setup>
|
||||||
import { defineComponent, ref, watch } from 'vue'
|
import { ref, watch } from 'vue'
|
||||||
import { storeToRefs } from 'pinia'
|
import { storeToRefs } from 'pinia'
|
||||||
import { useMainStore, useSlidesStore } from '@/store'
|
import { useMainStore, useSlidesStore } from '@/store'
|
||||||
import { PPTImageElement } from '@/types/slides'
|
import { PPTImageElement } from '@/types/slides'
|
||||||
@ -52,18 +52,15 @@ const defaultFilters: FilterOption[] = [
|
|||||||
{ label: '不透明度', key: 'opacity', default: 100, value: 100, unit: '%', max: 100, step: 5 },
|
{ label: '不透明度', key: 'opacity', default: 100, value: 100, unit: '%', max: 100, step: 5 },
|
||||||
]
|
]
|
||||||
|
|
||||||
export default defineComponent({
|
const slidesStore = useSlidesStore()
|
||||||
name: 'element-filter',
|
const { handleElement, handleElementId } = storeToRefs(useMainStore())
|
||||||
setup() {
|
|
||||||
const slidesStore = useSlidesStore()
|
|
||||||
const { handleElement, handleElementId } = storeToRefs(useMainStore())
|
|
||||||
|
|
||||||
const filterOptions = ref<FilterOption[]>(JSON.parse(JSON.stringify(defaultFilters)))
|
const filterOptions = ref<FilterOption[]>(JSON.parse(JSON.stringify(defaultFilters)))
|
||||||
const hasFilters = ref(false)
|
const hasFilters = ref(false)
|
||||||
|
|
||||||
const { addHistorySnapshot } = useHistorySnapshot()
|
const { addHistorySnapshot } = useHistorySnapshot()
|
||||||
|
|
||||||
watch(handleElement, () => {
|
watch(handleElement, () => {
|
||||||
if (!handleElement.value || handleElement.value.type !== 'image') return
|
if (!handleElement.value || handleElement.value.type !== 'image') return
|
||||||
|
|
||||||
const filters = handleElement.value.filters
|
const filters = handleElement.value.filters
|
||||||
@ -78,19 +75,19 @@ export default defineComponent({
|
|||||||
filterOptions.value = JSON.parse(JSON.stringify(defaultFilters))
|
filterOptions.value = JSON.parse(JSON.stringify(defaultFilters))
|
||||||
hasFilters.value = false
|
hasFilters.value = false
|
||||||
}
|
}
|
||||||
}, { deep: true, immediate: true })
|
}, { deep: true, immediate: true })
|
||||||
|
|
||||||
// 设置滤镜
|
// 设置滤镜
|
||||||
const updateFilter = (filter: FilterOption, value: number) => {
|
const updateFilter = (filter: FilterOption, value: number) => {
|
||||||
const _handleElement = handleElement.value as PPTImageElement
|
const _handleElement = handleElement.value as PPTImageElement
|
||||||
|
|
||||||
const originFilters = _handleElement.filters || {}
|
const originFilters = _handleElement.filters || {}
|
||||||
const filters = { ...originFilters, [filter.key]: `${value}${filter.unit}` }
|
const filters = { ...originFilters, [filter.key]: `${value}${filter.unit}` }
|
||||||
slidesStore.updateElement({ id: handleElementId.value, props: { filters } })
|
slidesStore.updateElement({ id: handleElementId.value, props: { filters } })
|
||||||
addHistorySnapshot()
|
addHistorySnapshot()
|
||||||
}
|
}
|
||||||
|
|
||||||
const toggleFilters = (checked: boolean) => {
|
const toggleFilters = (checked: boolean) => {
|
||||||
if (!handleElement.value) return
|
if (!handleElement.value) return
|
||||||
if (checked) {
|
if (checked) {
|
||||||
slidesStore.updateElement({ id: handleElement.value.id, props: { filters: {} } })
|
slidesStore.updateElement({ id: handleElement.value.id, props: { filters: {} } })
|
||||||
@ -99,16 +96,7 @@ export default defineComponent({
|
|||||||
slidesStore.removeElementProps({ id: handleElement.value.id, propName: 'filters' })
|
slidesStore.removeElementProps({ id: handleElement.value.id, propName: 'filters' })
|
||||||
}
|
}
|
||||||
addHistorySnapshot()
|
addHistorySnapshot()
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
|
||||||
filterOptions,
|
|
||||||
hasFilters,
|
|
||||||
toggleFilters,
|
|
||||||
updateFilter,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
})
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
@ -15,44 +15,33 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts" setup>
|
||||||
import { defineComponent, ref, watch } from 'vue'
|
import { ref, watch } from 'vue'
|
||||||
import { storeToRefs } from 'pinia'
|
import { storeToRefs } from 'pinia'
|
||||||
import { useMainStore, useSlidesStore } from '@/store'
|
import { useMainStore, useSlidesStore } from '@/store'
|
||||||
import { ImageOrShapeFlip } from '@/types/slides'
|
import { ImageOrShapeFlip } from '@/types/slides'
|
||||||
import useHistorySnapshot from '@/hooks/useHistorySnapshot'
|
import useHistorySnapshot from '@/hooks/useHistorySnapshot'
|
||||||
|
|
||||||
export default defineComponent({
|
const slidesStore = useSlidesStore()
|
||||||
name: 'element-flip',
|
const { handleElement } = storeToRefs(useMainStore())
|
||||||
setup() {
|
|
||||||
const slidesStore = useSlidesStore()
|
|
||||||
const { handleElement } = storeToRefs(useMainStore())
|
|
||||||
|
|
||||||
const flipH = ref(false)
|
const flipH = ref(false)
|
||||||
const flipV = ref(false)
|
const flipV = ref(false)
|
||||||
|
|
||||||
watch(handleElement, () => {
|
watch(handleElement, () => {
|
||||||
if (handleElement.value && (handleElement.value.type === 'image' || handleElement.value.type === 'shape')) {
|
if (handleElement.value && (handleElement.value.type === 'image' || handleElement.value.type === 'shape')) {
|
||||||
flipH.value = !!handleElement.value.flipH
|
flipH.value = !!handleElement.value.flipH
|
||||||
flipV.value = !!handleElement.value.flipV
|
flipV.value = !!handleElement.value.flipV
|
||||||
}
|
}
|
||||||
}, { deep: true, immediate: true })
|
}, { deep: true, immediate: true })
|
||||||
|
|
||||||
const { addHistorySnapshot } = useHistorySnapshot()
|
const { addHistorySnapshot } = useHistorySnapshot()
|
||||||
|
|
||||||
const updateFlip = (flipProps: ImageOrShapeFlip) => {
|
const updateFlip = (flipProps: ImageOrShapeFlip) => {
|
||||||
if (!handleElement.value) return
|
if (!handleElement.value) return
|
||||||
slidesStore.updateElement({ id: handleElement.value.id, props: flipProps })
|
slidesStore.updateElement({ id: handleElement.value.id, props: flipProps })
|
||||||
addHistorySnapshot()
|
addHistorySnapshot()
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
|
||||||
flipH,
|
|
||||||
flipV,
|
|
||||||
updateFlip,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
})
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
@ -14,40 +14,30 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts" setup>
|
||||||
import { defineComponent, ref, watch } from 'vue'
|
import { ref, watch } from 'vue'
|
||||||
import { storeToRefs } from 'pinia'
|
import { storeToRefs } from 'pinia'
|
||||||
import { useMainStore, useSlidesStore } from '@/store'
|
import { useMainStore, useSlidesStore } from '@/store'
|
||||||
import useHistorySnapshot from '@/hooks/useHistorySnapshot'
|
import useHistorySnapshot from '@/hooks/useHistorySnapshot'
|
||||||
|
|
||||||
export default defineComponent({
|
const slidesStore = useSlidesStore()
|
||||||
name: 'element-opacity',
|
const { handleElement } = storeToRefs(useMainStore())
|
||||||
setup() {
|
|
||||||
const slidesStore = useSlidesStore()
|
|
||||||
const { handleElement } = storeToRefs(useMainStore())
|
|
||||||
|
|
||||||
const opacity = ref<number>(1)
|
const opacity = ref<number>(1)
|
||||||
|
|
||||||
watch(handleElement, () => {
|
watch(handleElement, () => {
|
||||||
if (!handleElement.value) return
|
if (!handleElement.value) return
|
||||||
opacity.value = 'opacity' in handleElement.value && handleElement.value.opacity !== undefined ? handleElement.value.opacity : 1
|
opacity.value = 'opacity' in handleElement.value && handleElement.value.opacity !== undefined ? handleElement.value.opacity : 1
|
||||||
}, { deep: true, immediate: true })
|
}, { deep: true, immediate: true })
|
||||||
|
|
||||||
const { addHistorySnapshot } = useHistorySnapshot()
|
const { addHistorySnapshot } = useHistorySnapshot()
|
||||||
|
|
||||||
const updateOpacity = (value: number) => {
|
const updateOpacity = (value: number) => {
|
||||||
if (!handleElement.value) return
|
if (!handleElement.value) return
|
||||||
const props = { opacity: value }
|
const props = { opacity: value }
|
||||||
slidesStore.updateElement({ id: handleElement.value.id, props })
|
slidesStore.updateElement({ id: handleElement.value.id, props })
|
||||||
addHistorySnapshot()
|
addHistorySnapshot()
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
|
||||||
opacity,
|
|
||||||
updateOpacity,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
})
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
@ -45,8 +45,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts" setup>
|
||||||
import { defineComponent, ref, watch } from 'vue'
|
import { ref, watch } from 'vue'
|
||||||
import { storeToRefs } from 'pinia'
|
import { storeToRefs } from 'pinia'
|
||||||
import { useMainStore, useSlidesStore } from '@/store'
|
import { useMainStore, useSlidesStore } from '@/store'
|
||||||
import { PPTElementOutline } from '@/types/slides'
|
import { PPTElementOutline } from '@/types/slides'
|
||||||
@ -54,40 +54,35 @@ import useHistorySnapshot from '@/hooks/useHistorySnapshot'
|
|||||||
|
|
||||||
import ColorButton from './ColorButton.vue'
|
import ColorButton from './ColorButton.vue'
|
||||||
|
|
||||||
export default defineComponent({
|
defineProps({
|
||||||
name: 'element-outline',
|
|
||||||
components: {
|
|
||||||
ColorButton,
|
|
||||||
},
|
|
||||||
props: {
|
|
||||||
fixed: {
|
fixed: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false,
|
default: false,
|
||||||
},
|
},
|
||||||
},
|
})
|
||||||
setup() {
|
|
||||||
const slidesStore = useSlidesStore()
|
|
||||||
const { handleElement } = storeToRefs(useMainStore())
|
|
||||||
|
|
||||||
const outline = ref<PPTElementOutline>()
|
const slidesStore = useSlidesStore()
|
||||||
const hasOutline = ref(false)
|
const { handleElement } = storeToRefs(useMainStore())
|
||||||
|
|
||||||
watch(handleElement, () => {
|
const outline = ref<PPTElementOutline>()
|
||||||
|
const hasOutline = ref(false)
|
||||||
|
|
||||||
|
watch(handleElement, () => {
|
||||||
if (!handleElement.value) return
|
if (!handleElement.value) return
|
||||||
outline.value = 'outline' in handleElement.value ? handleElement.value.outline : undefined
|
outline.value = 'outline' in handleElement.value ? handleElement.value.outline : undefined
|
||||||
hasOutline.value = !!outline.value
|
hasOutline.value = !!outline.value
|
||||||
}, { deep: true, immediate: true })
|
}, { deep: true, immediate: true })
|
||||||
|
|
||||||
const { addHistorySnapshot } = useHistorySnapshot()
|
const { addHistorySnapshot } = useHistorySnapshot()
|
||||||
|
|
||||||
const updateOutline = (outlineProps: Partial<PPTElementOutline>) => {
|
const updateOutline = (outlineProps: Partial<PPTElementOutline>) => {
|
||||||
if (!handleElement.value) return
|
if (!handleElement.value) return
|
||||||
const props = { outline: { ...outline.value, ...outlineProps } }
|
const props = { outline: { ...outline.value, ...outlineProps } }
|
||||||
slidesStore.updateElement({ id: handleElement.value.id, props })
|
slidesStore.updateElement({ id: handleElement.value.id, props })
|
||||||
addHistorySnapshot()
|
addHistorySnapshot()
|
||||||
}
|
}
|
||||||
|
|
||||||
const toggleOutline = (checked: boolean) => {
|
const toggleOutline = (checked: boolean) => {
|
||||||
if (!handleElement.value) return
|
if (!handleElement.value) return
|
||||||
if (checked) {
|
if (checked) {
|
||||||
const _outline: PPTElementOutline = { width: 2, color: '#000', style: 'solid' }
|
const _outline: PPTElementOutline = { width: 2, color: '#000', style: 'solid' }
|
||||||
@ -97,16 +92,7 @@ export default defineComponent({
|
|||||||
slidesStore.removeElementProps({ id: handleElement.value.id, propName: 'outline' })
|
slidesStore.removeElementProps({ id: handleElement.value.id, propName: 'outline' })
|
||||||
}
|
}
|
||||||
addHistorySnapshot()
|
addHistorySnapshot()
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
|
||||||
outline,
|
|
||||||
hasOutline,
|
|
||||||
toggleOutline,
|
|
||||||
updateOutline,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
})
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
@ -56,8 +56,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts" setup>
|
||||||
import { defineComponent, ref, watch } from 'vue'
|
import { ref, watch } from 'vue'
|
||||||
import { storeToRefs } from 'pinia'
|
import { storeToRefs } from 'pinia'
|
||||||
import { useMainStore, useSlidesStore } from '@/store'
|
import { useMainStore, useSlidesStore } from '@/store'
|
||||||
import { PPTElementShadow } from '@/types/slides'
|
import { PPTElementShadow } from '@/types/slides'
|
||||||
@ -65,34 +65,28 @@ import useHistorySnapshot from '@/hooks/useHistorySnapshot'
|
|||||||
|
|
||||||
import ColorButton from './ColorButton.vue'
|
import ColorButton from './ColorButton.vue'
|
||||||
|
|
||||||
export default defineComponent({
|
const slidesStore = useSlidesStore()
|
||||||
name: 'element-shadow',
|
const { handleElement } = storeToRefs(useMainStore())
|
||||||
components: {
|
|
||||||
ColorButton,
|
|
||||||
},
|
|
||||||
setup() {
|
|
||||||
const slidesStore = useSlidesStore()
|
|
||||||
const { handleElement } = storeToRefs(useMainStore())
|
|
||||||
|
|
||||||
const shadow = ref<PPTElementShadow>()
|
const shadow = ref<PPTElementShadow>()
|
||||||
const hasShadow = ref(false)
|
const hasShadow = ref(false)
|
||||||
|
|
||||||
watch(handleElement, () => {
|
watch(handleElement, () => {
|
||||||
if (!handleElement.value) return
|
if (!handleElement.value) return
|
||||||
shadow.value = 'shadow' in handleElement.value ? handleElement.value.shadow : undefined
|
shadow.value = 'shadow' in handleElement.value ? handleElement.value.shadow : undefined
|
||||||
hasShadow.value = !!shadow.value
|
hasShadow.value = !!shadow.value
|
||||||
}, { deep: true, immediate: true })
|
}, { deep: true, immediate: true })
|
||||||
|
|
||||||
const { addHistorySnapshot } = useHistorySnapshot()
|
const { addHistorySnapshot } = useHistorySnapshot()
|
||||||
|
|
||||||
const updateShadow = (shadowProps: Partial<PPTElementShadow>) => {
|
const updateShadow = (shadowProps: Partial<PPTElementShadow>) => {
|
||||||
if (!handleElement.value || !shadow.value) return
|
if (!handleElement.value || !shadow.value) return
|
||||||
const _shadow = { ...shadow.value, ...shadowProps }
|
const _shadow = { ...shadow.value, ...shadowProps }
|
||||||
slidesStore.updateElement({ id: handleElement.value.id, props: { shadow: _shadow } })
|
slidesStore.updateElement({ id: handleElement.value.id, props: { shadow: _shadow } })
|
||||||
addHistorySnapshot()
|
addHistorySnapshot()
|
||||||
}
|
}
|
||||||
|
|
||||||
const toggleShadow = (checked: boolean) => {
|
const toggleShadow = (checked: boolean) => {
|
||||||
if (!handleElement.value) return
|
if (!handleElement.value) return
|
||||||
if (checked) {
|
if (checked) {
|
||||||
const _shadow: PPTElementShadow = { h: 1, v: 1, blur: 2, color: '#000' }
|
const _shadow: PPTElementShadow = { h: 1, v: 1, blur: 2, color: '#000' }
|
||||||
@ -102,16 +96,7 @@ export default defineComponent({
|
|||||||
slidesStore.removeElementProps({ id: handleElement.value.id, propName: 'shadow' })
|
slidesStore.removeElementProps({ id: handleElement.value.id, propName: 'shadow' })
|
||||||
}
|
}
|
||||||
addHistorySnapshot()
|
addHistorySnapshot()
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
|
||||||
shadow,
|
|
||||||
hasShadow,
|
|
||||||
toggleShadow,
|
|
||||||
updateShadow,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
})
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
@ -15,8 +15,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts" setup>
|
||||||
import { computed, defineComponent, watch } from 'vue'
|
import { computed, watch } from 'vue'
|
||||||
import { storeToRefs } from 'pinia'
|
import { storeToRefs } from 'pinia'
|
||||||
import { useMainStore } from '@/store'
|
import { useMainStore } from '@/store'
|
||||||
import { ToolbarStates } from '@/types/toolbar'
|
import { ToolbarStates } from '@/types/toolbar'
|
||||||
@ -34,13 +34,10 @@ interface ElementTabs {
|
|||||||
value: ToolbarStates
|
value: ToolbarStates
|
||||||
}
|
}
|
||||||
|
|
||||||
export default defineComponent({
|
const mainStore = useMainStore()
|
||||||
name: 'toolbar',
|
const { activeElementIdList, handleElement, toolbarState } = storeToRefs(mainStore)
|
||||||
setup() {
|
|
||||||
const mainStore = useMainStore()
|
|
||||||
const { activeElementIdList, handleElement, toolbarState } = storeToRefs(mainStore)
|
|
||||||
|
|
||||||
const elementTabs = computed<ElementTabs[]>(() => {
|
const elementTabs = computed<ElementTabs[]>(() => {
|
||||||
if (handleElement.value?.type === 'text') {
|
if (handleElement.value?.type === 'text') {
|
||||||
return [
|
return [
|
||||||
{ label: '样式', value: ToolbarStates.EL_STYLE },
|
{ label: '样式', value: ToolbarStates.EL_STYLE },
|
||||||
@ -54,35 +51,35 @@ export default defineComponent({
|
|||||||
{ label: '位置', value: ToolbarStates.EL_POSITION },
|
{ label: '位置', value: ToolbarStates.EL_POSITION },
|
||||||
{ label: '动画', value: ToolbarStates.EL_ANIMATION },
|
{ label: '动画', value: ToolbarStates.EL_ANIMATION },
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
const slideTabs = [
|
const slideTabs = [
|
||||||
{ label: '设计', value: ToolbarStates.SLIDE_DESIGN },
|
{ label: '设计', value: ToolbarStates.SLIDE_DESIGN },
|
||||||
{ label: '切换', value: ToolbarStates.SLIDE_ANIMATION },
|
{ label: '切换', value: ToolbarStates.SLIDE_ANIMATION },
|
||||||
{ label: '动画', value: ToolbarStates.EL_ANIMATION },
|
{ label: '动画', value: ToolbarStates.EL_ANIMATION },
|
||||||
]
|
]
|
||||||
const multiSelectTabs = [
|
const multiSelectTabs = [
|
||||||
{ label: '样式', value: ToolbarStates.EL_STYLE },
|
{ label: '样式', value: ToolbarStates.EL_STYLE },
|
||||||
{ label: '位置', value: ToolbarStates.MULTI_POSITION },
|
{ label: '位置', value: ToolbarStates.MULTI_POSITION },
|
||||||
]
|
]
|
||||||
|
|
||||||
const setToolbarState = (value: ToolbarStates) => {
|
const setToolbarState = (value: ToolbarStates) => {
|
||||||
mainStore.setToolbarState(value)
|
mainStore.setToolbarState(value)
|
||||||
}
|
}
|
||||||
|
|
||||||
const currentTabs = computed(() => {
|
const currentTabs = computed(() => {
|
||||||
if (!activeElementIdList.value.length) return slideTabs
|
if (!activeElementIdList.value.length) return slideTabs
|
||||||
else if (activeElementIdList.value.length > 1) return multiSelectTabs
|
else if (activeElementIdList.value.length > 1) return multiSelectTabs
|
||||||
return elementTabs.value
|
return elementTabs.value
|
||||||
})
|
})
|
||||||
|
|
||||||
watch(currentTabs, () => {
|
watch(currentTabs, () => {
|
||||||
const currentTabsValue: ToolbarStates[] = currentTabs.value.map(tab => tab.value)
|
const currentTabsValue: ToolbarStates[] = currentTabs.value.map(tab => tab.value)
|
||||||
if (!currentTabsValue.includes(toolbarState.value)) {
|
if (!currentTabsValue.includes(toolbarState.value)) {
|
||||||
mainStore.setToolbarState(currentTabsValue[0])
|
mainStore.setToolbarState(currentTabsValue[0])
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const currentPanelComponent = computed(() => {
|
const currentPanelComponent = computed(() => {
|
||||||
const panelMap = {
|
const panelMap = {
|
||||||
[ToolbarStates.EL_STYLE]: ElementStylePanel,
|
[ToolbarStates.EL_STYLE]: ElementStylePanel,
|
||||||
[ToolbarStates.EL_POSITION]: ElementPositionPanel,
|
[ToolbarStates.EL_POSITION]: ElementPositionPanel,
|
||||||
@ -93,15 +90,6 @@ export default defineComponent({
|
|||||||
[ToolbarStates.SYMBOL]: SymbolPanel,
|
[ToolbarStates.SYMBOL]: SymbolPanel,
|
||||||
}
|
}
|
||||||
return panelMap[toolbarState.value] || null
|
return panelMap[toolbarState.value] || null
|
||||||
})
|
|
||||||
|
|
||||||
return {
|
|
||||||
toolbarState,
|
|
||||||
currentTabs,
|
|
||||||
setToolbarState,
|
|
||||||
currentPanelComponent,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@ -29,8 +29,8 @@
|
|||||||
</Modal>
|
</Modal>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts" setup>
|
||||||
import { defineComponent, ref } from 'vue'
|
import { ref } from 'vue'
|
||||||
import { storeToRefs } from 'pinia'
|
import { storeToRefs } from 'pinia'
|
||||||
import { useMainStore } from '@/store'
|
import { useMainStore } from '@/store'
|
||||||
import useGlobalHotkey from '@/hooks/useGlobalHotkey'
|
import useGlobalHotkey from '@/hooks/useGlobalHotkey'
|
||||||
@ -44,34 +44,14 @@ import Toolbar from './Toolbar/index.vue'
|
|||||||
import Remark from './Remark/index.vue'
|
import Remark from './Remark/index.vue'
|
||||||
import ExportDialog from './ExportDialog/index.vue'
|
import ExportDialog from './ExportDialog/index.vue'
|
||||||
|
|
||||||
export default defineComponent({
|
const mainStore = useMainStore()
|
||||||
name: 'editor',
|
const { dialogForExport } = storeToRefs(mainStore)
|
||||||
components: {
|
const closeExportDialog = () => mainStore.setDialogForExport('')
|
||||||
EditorHeader,
|
|
||||||
Canvas,
|
|
||||||
CanvasTool,
|
|
||||||
Thumbnails,
|
|
||||||
Toolbar,
|
|
||||||
Remark,
|
|
||||||
ExportDialog,
|
|
||||||
},
|
|
||||||
setup() {
|
|
||||||
const mainStore = useMainStore()
|
|
||||||
const { dialogForExport } = storeToRefs(mainStore)
|
|
||||||
const closeExportDialog = () => mainStore.setDialogForExport('')
|
|
||||||
|
|
||||||
const remarkHeight = ref(40)
|
const remarkHeight = ref(40)
|
||||||
|
|
||||||
useGlobalHotkey()
|
useGlobalHotkey()
|
||||||
usePasteEvent()
|
usePasteEvent()
|
||||||
|
|
||||||
return {
|
|
||||||
remarkHeight,
|
|
||||||
dialogForExport,
|
|
||||||
closeExportDialog,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
})
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
@ -96,10 +96,10 @@
|
|||||||
<Divider style="margin: 20px 0;" />
|
<Divider style="margin: 20px 0;" />
|
||||||
|
|
||||||
<ButtonGroup class="row">
|
<ButtonGroup class="row">
|
||||||
<Button style="flex: 1;" @click="orderElement(handleElement, ElementOrderCommands.TOP)"><IconSendToBack class="icon" /> 置顶</Button>
|
<Button style="flex: 1;" @click="orderElement(handleElement!, ElementOrderCommands.TOP)"><IconSendToBack class="icon" /> 置顶</Button>
|
||||||
<Button style="flex: 1;" @click="orderElement(handleElement, ElementOrderCommands.BOTTOM)"><IconBringToFrontOne class="icon" /> 置底</Button>
|
<Button style="flex: 1;" @click="orderElement(handleElement!, ElementOrderCommands.BOTTOM)"><IconBringToFrontOne class="icon" /> 置底</Button>
|
||||||
<Button style="flex: 1;" @click="orderElement(handleElement, ElementOrderCommands.UP)"><IconBringToFront class="icon" /> 上移</Button>
|
<Button style="flex: 1;" @click="orderElement(handleElement!, ElementOrderCommands.UP)"><IconBringToFront class="icon" /> 上移</Button>
|
||||||
<Button style="flex: 1;" @click="orderElement(handleElement, ElementOrderCommands.DOWN)"><IconSentToBack class="icon" /> 下移</Button>
|
<Button style="flex: 1;" @click="orderElement(handleElement!, ElementOrderCommands.DOWN)"><IconSentToBack class="icon" /> 下移</Button>
|
||||||
</ButtonGroup>
|
</ButtonGroup>
|
||||||
|
|
||||||
<Divider style="margin: 20px 0;" />
|
<Divider style="margin: 20px 0;" />
|
||||||
@ -119,8 +119,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts" setup>
|
||||||
import { defineComponent, Ref, ref } from 'vue'
|
import { ref } from 'vue'
|
||||||
import { storeToRefs } from 'pinia'
|
import { storeToRefs } from 'pinia'
|
||||||
import { useMainStore, useSlidesStore } from '@/store'
|
import { useMainStore, useSlidesStore } from '@/store'
|
||||||
import { PPTElement, TableCell } from '@/types/slides'
|
import { PPTElement, TableCell } from '@/types/slides'
|
||||||
@ -139,41 +139,38 @@ interface TabItem {
|
|||||||
|
|
||||||
const colors = ['#000000', '#ffffff', '#eeece1', '#1e497b', '#4e81bb', '#e2534d', '#9aba60', '#8165a0', '#47acc5', '#f9974c', '#c21401', '#ff1e02', '#ffc12a', '#ffff3a', '#90cf5b', '#00af57']
|
const colors = ['#000000', '#ffffff', '#eeece1', '#1e497b', '#4e81bb', '#e2534d', '#9aba60', '#8165a0', '#47acc5', '#f9974c', '#c21401', '#ff1e02', '#ffc12a', '#ffff3a', '#90cf5b', '#00af57']
|
||||||
|
|
||||||
export default defineComponent({
|
const mainStore = useMainStore()
|
||||||
name: 'element-toolbar',
|
const slidesStore = useSlidesStore()
|
||||||
setup() {
|
const { handleElement, handleElementId, richTextAttrs } = storeToRefs(mainStore)
|
||||||
const mainStore = useMainStore()
|
|
||||||
const slidesStore = useSlidesStore()
|
|
||||||
const { handleElement, handleElementId, richTextAttrs } = storeToRefs(mainStore)
|
|
||||||
|
|
||||||
const { addHistorySnapshot } = useHistorySnapshot()
|
const { addHistorySnapshot } = useHistorySnapshot()
|
||||||
|
|
||||||
const updateElement = (id: string, props: Partial<PPTElement>) => {
|
const updateElement = (id: string, props: Partial<PPTElement>) => {
|
||||||
slidesStore.updateElement({ id, props })
|
slidesStore.updateElement({ id, props })
|
||||||
addHistorySnapshot()
|
addHistorySnapshot()
|
||||||
}
|
}
|
||||||
|
|
||||||
const tabs: TabItem[] = [
|
const tabs: TabItem[] = [
|
||||||
{ key: 'style', label: '样式' },
|
{ key: 'style', label: '样式' },
|
||||||
{ key: 'common', label: '布局' },
|
{ key: 'common', label: '布局' },
|
||||||
]
|
]
|
||||||
const activeTab = ref('common')
|
const activeTab = ref('common')
|
||||||
|
|
||||||
const { orderElement } = useOrderElement()
|
const { orderElement } = useOrderElement()
|
||||||
const { alignElementToCanvas } = useAlignElementToCanvas()
|
const { alignElementToCanvas } = useAlignElementToCanvas()
|
||||||
const { addElementsFromData } = useAddSlidesOrElements()
|
const { addElementsFromData } = useAddSlidesOrElements()
|
||||||
const { deleteElement } = useDeleteElement()
|
const { deleteElement } = useDeleteElement()
|
||||||
|
|
||||||
const copyElement = () => {
|
const copyElement = () => {
|
||||||
const element: PPTElement = JSON.parse(JSON.stringify(handleElement.value))
|
const element: PPTElement = JSON.parse(JSON.stringify(handleElement.value))
|
||||||
addElementsFromData([element])
|
addElementsFromData([element])
|
||||||
}
|
}
|
||||||
|
|
||||||
const emitRichTextCommand = (command: string, value?: string) => {
|
const emitRichTextCommand = (command: string, value?: string) => {
|
||||||
emitter.emit(EmitterEvents.RICH_TEXT_COMMAND, { action: { command, value } })
|
emitter.emit(EmitterEvents.RICH_TEXT_COMMAND, { action: { command, value } })
|
||||||
}
|
}
|
||||||
|
|
||||||
const updateFontColor = (color: string) => {
|
const updateFontColor = (color: string) => {
|
||||||
if (!handleElement.value) return
|
if (!handleElement.value) return
|
||||||
if (handleElement.value.type === 'text' || (handleElement.value.type === 'shape' && handleElement.value.text?.content)) {
|
if (handleElement.value.type === 'text' || (handleElement.value.type === 'shape' && handleElement.value.text?.content)) {
|
||||||
emitter.emit(EmitterEvents.RICH_TEXT_COMMAND, { action: { command: 'color', value: color } })
|
emitter.emit(EmitterEvents.RICH_TEXT_COMMAND, { action: { command: 'color', value: color } })
|
||||||
@ -191,9 +188,9 @@ export default defineComponent({
|
|||||||
if (handleElement.value.type === 'latex') {
|
if (handleElement.value.type === 'latex') {
|
||||||
updateElement(handleElementId.value, { color })
|
updateElement(handleElementId.value, { color })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const updateFill = (color: string) => {
|
const updateFill = (color: string) => {
|
||||||
if (!handleElement.value) return
|
if (!handleElement.value) return
|
||||||
if (
|
if (
|
||||||
handleElement.value.type === 'text' ||
|
handleElement.value.type === 'text' ||
|
||||||
@ -213,26 +210,7 @@ export default defineComponent({
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (handleElement.value.type === 'audio') updateElement(handleElementId.value, { color })
|
if (handleElement.value.type === 'audio') updateElement(handleElementId.value, { color })
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
|
||||||
handleElement: handleElement as Ref<PPTElement>,
|
|
||||||
tabs,
|
|
||||||
activeTab,
|
|
||||||
richTextAttrs,
|
|
||||||
colors,
|
|
||||||
orderElement,
|
|
||||||
alignElementToCanvas,
|
|
||||||
copyElement,
|
|
||||||
deleteElement,
|
|
||||||
emitRichTextCommand,
|
|
||||||
updateFontColor,
|
|
||||||
updateFill,
|
|
||||||
ElementOrderCommands,
|
|
||||||
ElementAlignCommands,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
})
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
@ -8,33 +8,22 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts" setup>
|
||||||
import { defineComponent, PropType } from 'vue'
|
import { PropType } from 'vue'
|
||||||
import { storeToRefs } from 'pinia'
|
import { storeToRefs } from 'pinia'
|
||||||
import { useSnapshotStore } from '@/store'
|
import { useSnapshotStore } from '@/store'
|
||||||
import { Mode } from '@/types/mobile'
|
import { Mode } from '@/types/mobile'
|
||||||
import useHistorySnapshot from '@/hooks/useHistorySnapshot'
|
import useHistorySnapshot from '@/hooks/useHistorySnapshot'
|
||||||
|
|
||||||
export default defineComponent({
|
defineProps({
|
||||||
name: 'mobile-editor-header',
|
|
||||||
props: {
|
|
||||||
changeMode: {
|
changeMode: {
|
||||||
type: Function as PropType<(mode: Mode) => void>,
|
type: Function as PropType<(mode: Mode) => void>,
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
},
|
|
||||||
setup() {
|
|
||||||
const { canUndo, canRedo } = storeToRefs(useSnapshotStore())
|
|
||||||
const { redo, undo } = useHistorySnapshot()
|
|
||||||
|
|
||||||
return {
|
|
||||||
redo,
|
|
||||||
undo,
|
|
||||||
canUndo,
|
|
||||||
canRedo,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const { canUndo, canRedo } = storeToRefs(useSnapshotStore())
|
||||||
|
const { redo, undo } = useHistorySnapshot()
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
@ -14,8 +14,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts" setup>
|
||||||
import { computed, defineComponent, PropType } from 'vue'
|
import { computed, PropType } from 'vue'
|
||||||
import { ElementTypes, PPTElement } from '@/types/slides'
|
import { ElementTypes, PPTElement } from '@/types/slides'
|
||||||
|
|
||||||
import ImageElement from '@/views/components/element/ImageElement/index.vue'
|
import ImageElement from '@/views/components/element/ImageElement/index.vue'
|
||||||
@ -28,9 +28,7 @@ import LatexElement from '@/views/components/element/LatexElement/index.vue'
|
|||||||
import VideoElement from '@/views/components/element/VideoElement/index.vue'
|
import VideoElement from '@/views/components/element/VideoElement/index.vue'
|
||||||
import AudioElement from '@/views/components/element/AudioElement/index.vue'
|
import AudioElement from '@/views/components/element/AudioElement/index.vue'
|
||||||
|
|
||||||
export default defineComponent({
|
const props = defineProps({
|
||||||
name: 'mobile-editable-element',
|
|
||||||
props: {
|
|
||||||
elementInfo: {
|
elementInfo: {
|
||||||
type: Object as PropType<PPTElement>,
|
type: Object as PropType<PPTElement>,
|
||||||
required: true,
|
required: true,
|
||||||
@ -43,9 +41,9 @@ export default defineComponent({
|
|||||||
type: Function as PropType<(e: TouchEvent, element: PPTElement, canMove?: boolean) => void>,
|
type: Function as PropType<(e: TouchEvent, element: PPTElement, canMove?: boolean) => void>,
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
},
|
})
|
||||||
setup(props) {
|
|
||||||
const currentElementComponent = computed(() => {
|
const currentElementComponent = computed(() => {
|
||||||
const elementTypeMap = {
|
const elementTypeMap = {
|
||||||
[ElementTypes.IMAGE]: ImageElement,
|
[ElementTypes.IMAGE]: ImageElement,
|
||||||
[ElementTypes.TEXT]: TextElement,
|
[ElementTypes.TEXT]: TextElement,
|
||||||
@ -58,11 +56,5 @@ export default defineComponent({
|
|||||||
[ElementTypes.AUDIO]: AudioElement,
|
[ElementTypes.AUDIO]: AudioElement,
|
||||||
}
|
}
|
||||||
return elementTypeMap[props.elementInfo.type] || null
|
return elementTypeMap[props.elementInfo.type] || null
|
||||||
})
|
|
||||||
|
|
||||||
return {
|
|
||||||
currentElementComponent,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
@ -29,8 +29,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts" setup>
|
||||||
import { defineComponent, PropType, computed } from 'vue'
|
import { PropType, computed } from 'vue'
|
||||||
import { PPTElement, PPTLineElement } from '@/types/slides'
|
import { PPTElement, PPTLineElement } from '@/types/slides'
|
||||||
import useCommonOperate from '@/views/Editor/Canvas/hooks/useCommonOperate'
|
import useCommonOperate from '@/views/Editor/Canvas/hooks/useCommonOperate'
|
||||||
import { OperateResizeHandlers } from '@/types/edit'
|
import { OperateResizeHandlers } from '@/types/edit'
|
||||||
@ -38,13 +38,7 @@ import { OperateResizeHandlers } from '@/types/edit'
|
|||||||
import BorderLine from '@/views/Editor/Canvas/Operate/BorderLine.vue'
|
import BorderLine from '@/views/Editor/Canvas/Operate/BorderLine.vue'
|
||||||
import ResizeHandler from '@/views/Editor/Canvas/Operate/ResizeHandler.vue'
|
import ResizeHandler from '@/views/Editor/Canvas/Operate/ResizeHandler.vue'
|
||||||
|
|
||||||
export default defineComponent({
|
const props = defineProps({
|
||||||
name: 'mobile-operate',
|
|
||||||
components: {
|
|
||||||
BorderLine,
|
|
||||||
ResizeHandler,
|
|
||||||
},
|
|
||||||
props: {
|
|
||||||
elementInfo: {
|
elementInfo: {
|
||||||
type: Object as PropType<Exclude<PPTElement, PPTLineElement>>,
|
type: Object as PropType<Exclude<PPTElement, PPTLineElement>>,
|
||||||
required: true,
|
required: true,
|
||||||
@ -61,25 +55,19 @@ export default defineComponent({
|
|||||||
type: Function as PropType<(e: MouseEvent, element: Exclude<PPTElement, PPTLineElement>, command: OperateResizeHandlers) => void>,
|
type: Function as PropType<(e: MouseEvent, element: Exclude<PPTElement, PPTLineElement>, command: OperateResizeHandlers) => void>,
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
},
|
|
||||||
setup(props) {
|
|
||||||
const rotate = computed(() => 'rotate' in props.elementInfo ? props.elementInfo.rotate : 0)
|
|
||||||
|
|
||||||
const scaleWidth = computed(() => props.elementInfo.width * props.canvasScale)
|
|
||||||
const scaleHeight = computed(() => props.elementInfo.height * props.canvasScale)
|
|
||||||
const {
|
|
||||||
borderLines,
|
|
||||||
resizeHandlers,
|
|
||||||
textElementResizeHandlers,
|
|
||||||
} = useCommonOperate(scaleWidth, scaleHeight)
|
|
||||||
|
|
||||||
return {
|
|
||||||
rotate,
|
|
||||||
borderLines,
|
|
||||||
resizeHandlers: props.elementInfo.type === 'text' || props.elementInfo.type === 'table' ? textElementResizeHandlers : resizeHandlers,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const rotate = computed(() => 'rotate' in props.elementInfo ? props.elementInfo.rotate : 0)
|
||||||
|
|
||||||
|
const scaleWidth = computed(() => props.elementInfo.width * props.canvasScale)
|
||||||
|
const scaleHeight = computed(() => props.elementInfo.height * props.canvasScale)
|
||||||
|
const {
|
||||||
|
borderLines,
|
||||||
|
resizeHandlers: _resizeHandlers,
|
||||||
|
textElementResizeHandlers,
|
||||||
|
} = useCommonOperate(scaleWidth, scaleHeight)
|
||||||
|
|
||||||
|
const resizeHandlers = props.elementInfo.type === 'text' || props.elementInfo.type === 'table' ? textElementResizeHandlers : _resizeHandlers
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
@ -29,8 +29,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts" setup>
|
||||||
import { computed, defineComponent } from 'vue'
|
import { computed } from 'vue'
|
||||||
import { storeToRefs } from 'pinia'
|
import { storeToRefs } from 'pinia'
|
||||||
import { useSlidesStore } from '@/store'
|
import { useSlidesStore } from '@/store'
|
||||||
import useSlideHandler from '@/hooks/useSlideHandler'
|
import useSlideHandler from '@/hooks/useSlideHandler'
|
||||||
@ -41,19 +41,13 @@ import { VIEWPORT_SIZE } from '@/configs/canvas'
|
|||||||
|
|
||||||
import MobileThumbnails from '../MobileThumbnails.vue'
|
import MobileThumbnails from '../MobileThumbnails.vue'
|
||||||
|
|
||||||
export default defineComponent({
|
const slidesStore = useSlidesStore()
|
||||||
name: 'slide-toolbar',
|
const { viewportRatio, currentSlide } = storeToRefs(slidesStore)
|
||||||
components: {
|
|
||||||
MobileThumbnails,
|
|
||||||
},
|
|
||||||
setup() {
|
|
||||||
const slidesStore = useSlidesStore()
|
|
||||||
const { viewportRatio, currentSlide } = storeToRefs(slidesStore)
|
|
||||||
|
|
||||||
const { createSlide, copyAndPasteSlide, deleteSlide, } = useSlideHandler()
|
const { createSlide, copyAndPasteSlide, deleteSlide, } = useSlideHandler()
|
||||||
const { createTextElement, createImageElement, createShapeElement } = useCreateElement()
|
const { createTextElement, createImageElement, createShapeElement } = useCreateElement()
|
||||||
|
|
||||||
const insertTextElement = () => {
|
const insertTextElement = () => {
|
||||||
const width = 400
|
const width = 400
|
||||||
const height = 56
|
const height = 56
|
||||||
|
|
||||||
@ -63,14 +57,14 @@ export default defineComponent({
|
|||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
}, '<p><span style=\"font-size: 24px\">新添加文本</span></p>')
|
}, '<p><span style=\"font-size: 24px\">新添加文本</span></p>')
|
||||||
}
|
}
|
||||||
|
|
||||||
const insertImageElement = (files: File[]) => {
|
const insertImageElement = (files: FileList) => {
|
||||||
if (!files || !files[0]) return
|
if (!files || !files[0]) return
|
||||||
getImageDataURL(files[0]).then(dataURL => createImageElement(dataURL))
|
getImageDataURL(files[0]).then(dataURL => createImageElement(dataURL))
|
||||||
}
|
}
|
||||||
|
|
||||||
const insertShapeElement = (type: 'square' | 'round') => {
|
const insertShapeElement = (type: 'square' | 'round') => {
|
||||||
const square: ShapePoolItem = {
|
const square: ShapePoolItem = {
|
||||||
viewBox: [200, 200],
|
viewBox: [200, 200],
|
||||||
path: 'M 0 0 L 200 0 L 200 200 L 0 200 Z',
|
path: 'M 0 0 L 200 0 L 200 200 L 0 200 Z',
|
||||||
@ -89,27 +83,14 @@ export default defineComponent({
|
|||||||
width: size,
|
width: size,
|
||||||
height: size,
|
height: size,
|
||||||
}, shape[type])
|
}, shape[type])
|
||||||
}
|
}
|
||||||
|
|
||||||
const remark = computed(() => currentSlide.value?.remark || '')
|
const remark = computed(() => currentSlide.value?.remark || '')
|
||||||
|
|
||||||
const handleInputMark = (e: Event) => {
|
const handleInputMark = (e: Event) => {
|
||||||
const value = (e.target as HTMLTextAreaElement).value
|
const value = (e.target as HTMLTextAreaElement).value
|
||||||
slidesStore.updateSlide({ remark: value })
|
slidesStore.updateSlide({ remark: value })
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
|
||||||
remark,
|
|
||||||
createSlide,
|
|
||||||
copyAndPasteSlide,
|
|
||||||
deleteSlide,
|
|
||||||
insertTextElement,
|
|
||||||
insertImageElement,
|
|
||||||
insertShapeElement,
|
|
||||||
handleInputMark,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
})
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
@ -39,8 +39,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts" setup>
|
||||||
import { computed, defineComponent, onMounted, PropType, ref, watchEffect } from 'vue'
|
import { computed, onMounted, PropType, ref, watchEffect } from 'vue'
|
||||||
import { storeToRefs } from 'pinia'
|
import { storeToRefs } from 'pinia'
|
||||||
import { useMainStore, useSlidesStore } from '@/store'
|
import { useMainStore, useSlidesStore } from '@/store'
|
||||||
import { PPTElement } from '@/types/slides'
|
import { PPTElement } from '@/types/slides'
|
||||||
@ -58,36 +58,26 @@ import SlideToolbar from './SlideToolbar.vue'
|
|||||||
import ElementToolbar from './ElementToolbar.vue'
|
import ElementToolbar from './ElementToolbar.vue'
|
||||||
import Header from './Header.vue'
|
import Header from './Header.vue'
|
||||||
|
|
||||||
export default defineComponent({
|
defineProps({
|
||||||
name: 'mobile-editor',
|
|
||||||
components: {
|
|
||||||
AlignmentLine,
|
|
||||||
MobileEditableElement,
|
|
||||||
MobileOperate,
|
|
||||||
SlideToolbar,
|
|
||||||
ElementToolbar,
|
|
||||||
Header,
|
|
||||||
},
|
|
||||||
props: {
|
|
||||||
changeMode: {
|
changeMode: {
|
||||||
type: Function as PropType<(mode: Mode) => void>,
|
type: Function as PropType<(mode: Mode) => void>,
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
},
|
})
|
||||||
setup() {
|
|
||||||
const slidesStore = useSlidesStore()
|
|
||||||
const mainStore = useMainStore()
|
|
||||||
const { slideIndex, currentSlide, viewportRatio } = storeToRefs(slidesStore)
|
|
||||||
const { activeElementIdList, handleElement } = storeToRefs(mainStore)
|
|
||||||
|
|
||||||
const contentRef = ref<HTMLElement>()
|
const slidesStore = useSlidesStore()
|
||||||
|
const mainStore = useMainStore()
|
||||||
|
const { slideIndex, currentSlide, viewportRatio } = storeToRefs(slidesStore)
|
||||||
|
const { activeElementIdList, handleElement } = storeToRefs(mainStore)
|
||||||
|
|
||||||
const alignmentLines = ref<AlignmentLineProps[]>([])
|
const contentRef = ref<HTMLElement>()
|
||||||
|
|
||||||
const background = computed(() => currentSlide.value.background)
|
const alignmentLines = ref<AlignmentLineProps[]>([])
|
||||||
const { backgroundStyle } = useSlideBackgroundStyle(background)
|
|
||||||
|
|
||||||
const canvasScale = computed(() => {
|
const background = computed(() => currentSlide.value.background)
|
||||||
|
const { backgroundStyle } = useSlideBackgroundStyle(background)
|
||||||
|
|
||||||
|
const canvasScale = computed(() => {
|
||||||
if (!contentRef.value) return 1
|
if (!contentRef.value) return 1
|
||||||
const contentWidth = contentRef.value.clientWidth
|
const contentWidth = contentRef.value.clientWidth
|
||||||
const contentheight = contentRef.value.clientHeight
|
const contentheight = contentRef.value.clientHeight
|
||||||
@ -95,55 +85,38 @@ export default defineComponent({
|
|||||||
const contentRatio = contentheight / contentWidth
|
const contentRatio = contentheight / contentWidth
|
||||||
if (contentRatio >= viewportRatio.value) return (contentWidth - 20) / VIEWPORT_SIZE
|
if (contentRatio >= viewportRatio.value) return (contentWidth - 20) / VIEWPORT_SIZE
|
||||||
return (contentheight - 20) / viewportRatio.value / VIEWPORT_SIZE
|
return (contentheight - 20) / viewportRatio.value / VIEWPORT_SIZE
|
||||||
})
|
})
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
if (activeElementIdList.value.length) mainStore.setActiveElementIdList([])
|
if (activeElementIdList.value.length) mainStore.setActiveElementIdList([])
|
||||||
if (slideIndex.value !== 0) slidesStore.updateSlideIndex(0)
|
if (slideIndex.value !== 0) slidesStore.updateSlideIndex(0)
|
||||||
})
|
})
|
||||||
|
|
||||||
const viewportStyles = computed(() => ({
|
const viewportStyles = computed(() => ({
|
||||||
width: VIEWPORT_SIZE * canvasScale.value + 'px',
|
width: VIEWPORT_SIZE * canvasScale.value + 'px',
|
||||||
height: VIEWPORT_SIZE * viewportRatio.value * canvasScale.value + 'px',
|
height: VIEWPORT_SIZE * viewportRatio.value * canvasScale.value + 'px',
|
||||||
}))
|
}))
|
||||||
|
|
||||||
const elementList = ref<PPTElement[]>([])
|
const elementList = ref<PPTElement[]>([])
|
||||||
const setLocalElementList = () => {
|
const setLocalElementList = () => {
|
||||||
elementList.value = currentSlide.value ? JSON.parse(JSON.stringify(currentSlide.value.elements)) : []
|
elementList.value = currentSlide.value ? JSON.parse(JSON.stringify(currentSlide.value.elements)) : []
|
||||||
}
|
}
|
||||||
watchEffect(setLocalElementList)
|
watchEffect(setLocalElementList)
|
||||||
|
|
||||||
const { dragElement } = useDragElement(elementList, alignmentLines, canvasScale)
|
const { dragElement } = useDragElement(elementList, alignmentLines, canvasScale)
|
||||||
const { scaleElement } = useScaleElement(elementList, alignmentLines, canvasScale)
|
const { scaleElement } = useScaleElement(elementList, alignmentLines, canvasScale)
|
||||||
|
|
||||||
const selectElement = (e: TouchEvent, element: PPTElement, startMove = true) => {
|
const selectElement = (e: TouchEvent, element: PPTElement, startMove = true) => {
|
||||||
if (!activeElementIdList.value.includes(element.id)) {
|
if (!activeElementIdList.value.includes(element.id)) {
|
||||||
mainStore.setActiveElementIdList([element.id])
|
mainStore.setActiveElementIdList([element.id])
|
||||||
mainStore.setHandleElementId(element.id)
|
mainStore.setHandleElementId(element.id)
|
||||||
}
|
}
|
||||||
if (startMove) dragElement(e, element)
|
if (startMove) dragElement(e, element)
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleClickBlankArea = () => {
|
const handleClickBlankArea = () => {
|
||||||
mainStore.setActiveElementIdList([])
|
mainStore.setActiveElementIdList([])
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
|
||||||
contentRef,
|
|
||||||
slideIndex,
|
|
||||||
elementList,
|
|
||||||
canvasScale,
|
|
||||||
viewportStyles,
|
|
||||||
backgroundStyle,
|
|
||||||
activeElementIdList,
|
|
||||||
alignmentLines,
|
|
||||||
selectElement,
|
|
||||||
handleClickBlankArea,
|
|
||||||
scaleElement,
|
|
||||||
handleElement,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
})
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
@ -51,8 +51,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts" setup>
|
||||||
import { computed, defineComponent, onMounted, PropType, ref } from 'vue'
|
import { computed, onMounted, PropType, ref } from 'vue'
|
||||||
import { storeToRefs } from 'pinia'
|
import { storeToRefs } from 'pinia'
|
||||||
import { useSlidesStore } from '@/store'
|
import { useSlidesStore } from '@/store'
|
||||||
import { Mode } from '@/types/mobile'
|
import { Mode } from '@/types/mobile'
|
||||||
@ -60,36 +60,30 @@ import { Mode } from '@/types/mobile'
|
|||||||
import ThumbnailSlide from '@/views/components/ThumbnailSlide/index.vue'
|
import ThumbnailSlide from '@/views/components/ThumbnailSlide/index.vue'
|
||||||
import MobileThumbnails from './MobileThumbnails.vue'
|
import MobileThumbnails from './MobileThumbnails.vue'
|
||||||
|
|
||||||
export default defineComponent({
|
defineProps({
|
||||||
name: 'mobile-player',
|
|
||||||
components: {
|
|
||||||
ThumbnailSlide,
|
|
||||||
MobileThumbnails,
|
|
||||||
},
|
|
||||||
props: {
|
|
||||||
changeMode: {
|
changeMode: {
|
||||||
type: Function as PropType<(mode: Mode) => void>,
|
type: Function as PropType<(mode: Mode) => void>,
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
},
|
})
|
||||||
setup() {
|
|
||||||
const slidesStore = useSlidesStore()
|
|
||||||
const { slides, slideIndex, currentSlide, viewportRatio } = storeToRefs(slidesStore)
|
|
||||||
|
|
||||||
const toolVisible = ref(false)
|
const slidesStore = useSlidesStore()
|
||||||
|
const { slides, slideIndex, currentSlide, viewportRatio } = storeToRefs(slidesStore)
|
||||||
|
|
||||||
const playerSize = ref({ width: 0, height: 0 })
|
const toolVisible = ref(false)
|
||||||
|
|
||||||
onMounted(() => {
|
const playerSize = ref({ width: 0, height: 0 })
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
if (slideIndex.value !== 0) slidesStore.updateSlideIndex(0)
|
if (slideIndex.value !== 0) slidesStore.updateSlideIndex(0)
|
||||||
|
|
||||||
playerSize.value = {
|
playerSize.value = {
|
||||||
width: document.body.clientHeight,
|
width: document.body.clientHeight,
|
||||||
height: document.body.clientWidth,
|
height: document.body.clientWidth,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const slideSize = computed(() => {
|
const slideSize = computed(() => {
|
||||||
const playerRatio = playerSize.value.height / playerSize.value.width
|
const playerRatio = playerSize.value.height / playerSize.value.width
|
||||||
|
|
||||||
let slideWidth = 0
|
let slideWidth = 0
|
||||||
@ -108,16 +102,16 @@ export default defineComponent({
|
|||||||
width: slideWidth,
|
width: slideWidth,
|
||||||
height: slideHeight,
|
height: slideHeight,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const touchInfo = ref<{ x: number; y: number; } | null>(null)
|
const touchInfo = ref<{ x: number; y: number; } | null>(null)
|
||||||
const touchStartListener = (e: TouchEvent) => {
|
const touchStartListener = (e: TouchEvent) => {
|
||||||
touchInfo.value = {
|
touchInfo.value = {
|
||||||
x: e.changedTouches[0].pageX,
|
x: e.changedTouches[0].pageX,
|
||||||
y: e.changedTouches[0].pageY,
|
y: e.changedTouches[0].pageY,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const touchEndListener = (e: TouchEvent) => {
|
const touchEndListener = (e: TouchEvent) => {
|
||||||
if (!touchInfo.value) return
|
if (!touchInfo.value) return
|
||||||
|
|
||||||
const offsetY = Math.abs(touchInfo.value.y - e.changedTouches[0].pageY)
|
const offsetY = Math.abs(touchInfo.value.y - e.changedTouches[0].pageY)
|
||||||
@ -129,20 +123,7 @@ export default defineComponent({
|
|||||||
if (offsetX < 0 && slideIndex.value > 0) slidesStore.updateSlideIndex(slideIndex.value - 1)
|
if (offsetX < 0 && slideIndex.value > 0) slidesStore.updateSlideIndex(slideIndex.value - 1)
|
||||||
if (offsetX > 0 && slideIndex.value < slides.value.length - 1) slidesStore.updateSlideIndex(slideIndex.value + 1)
|
if (offsetX > 0 && slideIndex.value < slides.value.length - 1) slidesStore.updateSlideIndex(slideIndex.value + 1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
|
||||||
slides,
|
|
||||||
slideIndex,
|
|
||||||
currentSlide,
|
|
||||||
playerSize,
|
|
||||||
slideSize,
|
|
||||||
toolVisible,
|
|
||||||
touchStartListener,
|
|
||||||
touchEndListener,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
})
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
@ -17,8 +17,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts" setup>
|
||||||
import { defineComponent, PropType, onMounted, ref } from 'vue'
|
import { PropType, onMounted, ref } from 'vue'
|
||||||
import { storeToRefs } from 'pinia'
|
import { storeToRefs } from 'pinia'
|
||||||
import { useSlidesStore } from '@/store'
|
import { useSlidesStore } from '@/store'
|
||||||
import useLoadSlides from '@/hooks/useLoadSlides'
|
import useLoadSlides from '@/hooks/useLoadSlides'
|
||||||
@ -26,36 +26,22 @@ import { Mode } from '@/types/mobile'
|
|||||||
|
|
||||||
import ThumbnailSlide from '@/views/components/ThumbnailSlide/index.vue'
|
import ThumbnailSlide from '@/views/components/ThumbnailSlide/index.vue'
|
||||||
|
|
||||||
export default defineComponent({
|
const props = defineProps({
|
||||||
name: 'mobile-preview',
|
|
||||||
components: {
|
|
||||||
ThumbnailSlide,
|
|
||||||
},
|
|
||||||
props: {
|
|
||||||
changeMode: {
|
changeMode: {
|
||||||
type: Function as PropType<(mode: Mode) => void>,
|
type: Function as PropType<(mode: Mode) => void>,
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
},
|
})
|
||||||
setup() {
|
|
||||||
const { slides } = storeToRefs(useSlidesStore())
|
|
||||||
const { slidesLoadLimit } = useLoadSlides()
|
|
||||||
|
|
||||||
const mobileRef = ref<HTMLElement>()
|
const { slides } = storeToRefs(useSlidesStore())
|
||||||
const screenWidth = ref(0)
|
const { slidesLoadLimit } = useLoadSlides()
|
||||||
|
|
||||||
onMounted(() => {
|
const mobileRef = ref<HTMLElement>()
|
||||||
|
const screenWidth = ref(0)
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
if (!mobileRef.value) return
|
if (!mobileRef.value) return
|
||||||
screenWidth.value = mobileRef.value.clientWidth
|
screenWidth.value = mobileRef.value.clientWidth
|
||||||
})
|
|
||||||
|
|
||||||
return {
|
|
||||||
slides,
|
|
||||||
slidesLoadLimit,
|
|
||||||
mobileRef,
|
|
||||||
screenWidth,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@ -13,36 +13,20 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts" setup>
|
||||||
import { defineComponent } from 'vue'
|
|
||||||
import { storeToRefs } from 'pinia'
|
import { storeToRefs } from 'pinia'
|
||||||
import { useSlidesStore } from '@/store'
|
import { useSlidesStore } from '@/store'
|
||||||
import useLoadSlides from '@/hooks/useLoadSlides'
|
import useLoadSlides from '@/hooks/useLoadSlides'
|
||||||
|
|
||||||
import ThumbnailSlide from '@/views/components/ThumbnailSlide/index.vue'
|
import ThumbnailSlide from '@/views/components/ThumbnailSlide/index.vue'
|
||||||
|
|
||||||
export default defineComponent({
|
const slidesStore = useSlidesStore()
|
||||||
name: 'mobile-thumbnails',
|
const { slides, slideIndex } = storeToRefs(slidesStore)
|
||||||
components: {
|
|
||||||
ThumbnailSlide,
|
|
||||||
},
|
|
||||||
setup() {
|
|
||||||
const slidesStore = useSlidesStore()
|
|
||||||
const { slides, slideIndex } = storeToRefs(slidesStore)
|
|
||||||
|
|
||||||
const { slidesLoadLimit } = useLoadSlides()
|
const { slidesLoadLimit } = useLoadSlides()
|
||||||
const changeSlideIndex = (index: number) => {
|
const changeSlideIndex = (index: number) => {
|
||||||
slidesStore.updateSlideIndex(index)
|
slidesStore.updateSlideIndex(index)
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
|
||||||
slides,
|
|
||||||
slideIndex,
|
|
||||||
slidesLoadLimit,
|
|
||||||
changeSlideIndex,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
})
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
@ -7,35 +7,25 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts" setup>
|
||||||
import { computed, defineComponent, ref } from 'vue'
|
import { computed, ref } from 'vue'
|
||||||
import { Mode } from '@/types/mobile'
|
import { Mode } from '@/types/mobile'
|
||||||
|
|
||||||
import MobileEditor from './MobileEditor/index.vue'
|
import MobileEditor from './MobileEditor/index.vue'
|
||||||
import MobilePlayer from './MobilePlayer.vue'
|
import MobilePlayer from './MobilePlayer.vue'
|
||||||
import MobilePreview from './MobilePreview.vue'
|
import MobilePreview from './MobilePreview.vue'
|
||||||
|
|
||||||
export default defineComponent({
|
const mode = ref<Mode>('preview')
|
||||||
name: 'mobile',
|
|
||||||
setup() {
|
|
||||||
const mode = ref<Mode>('preview')
|
|
||||||
|
|
||||||
const changeMode = (_mode: Mode) => mode.value = _mode
|
const changeMode = (_mode: Mode) => mode.value = _mode
|
||||||
|
|
||||||
const currentComponent = computed(() => {
|
const currentComponent = computed(() => {
|
||||||
const componentMap = {
|
const componentMap = {
|
||||||
'editor': MobileEditor,
|
'editor': MobileEditor,
|
||||||
'player': MobilePlayer,
|
'player': MobilePlayer,
|
||||||
'preview': MobilePreview,
|
'preview': MobilePreview,
|
||||||
}
|
}
|
||||||
return componentMap[mode.value] || null
|
return componentMap[mode.value] || null
|
||||||
})
|
|
||||||
|
|
||||||
return {
|
|
||||||
currentComponent,
|
|
||||||
changeMode,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@ -58,8 +58,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts" setup>
|
||||||
import { defineComponent, PropType, ref } from 'vue'
|
import { PropType, ref } from 'vue'
|
||||||
import { storeToRefs } from 'pinia'
|
import { storeToRefs } from 'pinia'
|
||||||
import { useSlidesStore } from '@/store'
|
import { useSlidesStore } from '@/store'
|
||||||
import { ContextmenuItem } from '@/components/Contextmenu/types'
|
import { ContextmenuItem } from '@/components/Contextmenu/types'
|
||||||
@ -73,23 +73,16 @@ import ScreenSlideList from './ScreenSlideList.vue'
|
|||||||
import SlideThumbnails from './SlideThumbnails.vue'
|
import SlideThumbnails from './SlideThumbnails.vue'
|
||||||
import WritingBoardTool from './WritingBoardTool.vue'
|
import WritingBoardTool from './WritingBoardTool.vue'
|
||||||
|
|
||||||
export default defineComponent({
|
const props = defineProps({
|
||||||
name: 'screen',
|
|
||||||
components: {
|
|
||||||
ScreenSlideList,
|
|
||||||
SlideThumbnails,
|
|
||||||
WritingBoardTool,
|
|
||||||
},
|
|
||||||
props: {
|
|
||||||
changeViewMode: {
|
changeViewMode: {
|
||||||
type: Function as PropType<(mode: 'base' | 'presenter') => void>,
|
type: Function as PropType<(mode: 'base' | 'presenter') => void>,
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
},
|
})
|
||||||
setup(props) {
|
|
||||||
const { slides, slideIndex } = storeToRefs(useSlidesStore())
|
|
||||||
|
|
||||||
const {
|
const { slides, slideIndex } = storeToRefs(useSlidesStore())
|
||||||
|
|
||||||
|
const {
|
||||||
autoPlayTimer,
|
autoPlayTimer,
|
||||||
autoPlay,
|
autoPlay,
|
||||||
closeAutoPlay,
|
closeAutoPlay,
|
||||||
@ -103,18 +96,18 @@ export default defineComponent({
|
|||||||
execPrev,
|
execPrev,
|
||||||
execNext,
|
execNext,
|
||||||
animationIndex,
|
animationIndex,
|
||||||
} = useExecPlay()
|
} = useExecPlay()
|
||||||
|
|
||||||
const { slideWidth, slideHeight } = useSlideSize()
|
const { slideWidth, slideHeight } = useSlideSize()
|
||||||
const { exitScreening } = useScreening()
|
const { exitScreening } = useScreening()
|
||||||
const { fullscreenState, manualExitFullscreen } = useFullscreen()
|
const { fullscreenState, manualExitFullscreen } = useFullscreen()
|
||||||
|
|
||||||
const rightToolsVisible = ref(false)
|
const rightToolsVisible = ref(false)
|
||||||
const writingBoardToolVisible = ref(false)
|
const writingBoardToolVisible = ref(false)
|
||||||
const slideThumbnailModelVisible = ref(false)
|
const slideThumbnailModelVisible = ref(false)
|
||||||
const laserPen = ref(false)
|
const laserPen = ref(false)
|
||||||
|
|
||||||
const contextmenus = (): ContextmenuItem[] => {
|
const contextmenus = (): ContextmenuItem[] => {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
text: '上一页',
|
text: '上一页',
|
||||||
@ -166,33 +159,7 @@ export default defineComponent({
|
|||||||
handler: exitScreening,
|
handler: exitScreening,
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
|
||||||
slides,
|
|
||||||
slideIndex,
|
|
||||||
slideWidth,
|
|
||||||
slideHeight,
|
|
||||||
mousewheelListener,
|
|
||||||
touchStartListener,
|
|
||||||
touchEndListener,
|
|
||||||
animationIndex,
|
|
||||||
contextmenus,
|
|
||||||
execPrev,
|
|
||||||
execNext,
|
|
||||||
turnSlideToIndex,
|
|
||||||
turnSlideToId,
|
|
||||||
slideThumbnailModelVisible,
|
|
||||||
writingBoardToolVisible,
|
|
||||||
rightToolsVisible,
|
|
||||||
fullscreenState,
|
|
||||||
exitScreening,
|
|
||||||
enterFullscreen,
|
|
||||||
manualExitFullscreen,
|
|
||||||
laserPen,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
})
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
@ -71,8 +71,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts" setup>
|
||||||
import { computed, defineComponent, nextTick, ref, watch, PropType } from 'vue'
|
import { computed, nextTick, ref, watch, PropType } from 'vue'
|
||||||
import { storeToRefs } from 'pinia'
|
import { storeToRefs } from 'pinia'
|
||||||
import { useSlidesStore } from '@/store'
|
import { useSlidesStore } from '@/store'
|
||||||
import { ContextmenuItem } from '@/components/Contextmenu/types'
|
import { ContextmenuItem } from '@/components/Contextmenu/types'
|
||||||
@ -88,28 +88,21 @@ import ThumbnailSlide from '@/views/components/ThumbnailSlide/index.vue'
|
|||||||
import ScreenSlideList from './ScreenSlideList.vue'
|
import ScreenSlideList from './ScreenSlideList.vue'
|
||||||
import WritingBoardTool from './WritingBoardTool.vue'
|
import WritingBoardTool from './WritingBoardTool.vue'
|
||||||
|
|
||||||
export default defineComponent({
|
const props = defineProps({
|
||||||
name: 'presenter-view',
|
|
||||||
components: {
|
|
||||||
ScreenSlideList,
|
|
||||||
ThumbnailSlide,
|
|
||||||
WritingBoardTool,
|
|
||||||
},
|
|
||||||
props: {
|
|
||||||
changeViewMode: {
|
changeViewMode: {
|
||||||
type: Function as PropType<(mode: 'base' | 'presenter') => void>,
|
type: Function as PropType<(mode: 'base' | 'presenter') => void>,
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
},
|
})
|
||||||
setup(props) {
|
|
||||||
const { slides, slideIndex, viewportRatio, currentSlide } = storeToRefs(useSlidesStore())
|
|
||||||
|
|
||||||
const slideListWrapRef = ref<HTMLElement>()
|
const { slides, slideIndex, viewportRatio, currentSlide } = storeToRefs(useSlidesStore())
|
||||||
const thumbnailsRef = ref<HTMLElement>()
|
|
||||||
const writingBoardToolVisible = ref(false)
|
|
||||||
const laserPen = ref(false)
|
|
||||||
|
|
||||||
const {
|
const slideListWrapRef = ref<HTMLElement>()
|
||||||
|
const thumbnailsRef = ref<HTMLElement>()
|
||||||
|
const writingBoardToolVisible = ref(false)
|
||||||
|
const laserPen = ref(false)
|
||||||
|
|
||||||
|
const {
|
||||||
mousewheelListener,
|
mousewheelListener,
|
||||||
touchStartListener,
|
touchStartListener,
|
||||||
touchEndListener,
|
touchEndListener,
|
||||||
@ -118,29 +111,29 @@ export default defineComponent({
|
|||||||
turnSlideToIndex,
|
turnSlideToIndex,
|
||||||
turnSlideToId,
|
turnSlideToId,
|
||||||
animationIndex,
|
animationIndex,
|
||||||
} = useExecPlay()
|
} = useExecPlay()
|
||||||
|
|
||||||
const { slideWidth, slideHeight } = useSlideSize(slideListWrapRef)
|
const { slideWidth, slideHeight } = useSlideSize(slideListWrapRef)
|
||||||
const { exitScreening } = useScreening()
|
const { exitScreening } = useScreening()
|
||||||
const { slidesLoadLimit } = useLoadSlides()
|
const { slidesLoadLimit } = useLoadSlides()
|
||||||
const { fullscreenState, manualExitFullscreen } = useFullscreen()
|
const { fullscreenState, manualExitFullscreen } = useFullscreen()
|
||||||
|
|
||||||
const remarkFontSize = ref(16)
|
const remarkFontSize = ref(16)
|
||||||
const currentSlideRemark = computed(() => {
|
const currentSlideRemark = computed(() => {
|
||||||
return parseText2Paragraphs(currentSlide.value.remark || '无备注')
|
return parseText2Paragraphs(currentSlide.value.remark || '无备注')
|
||||||
})
|
})
|
||||||
|
|
||||||
const handleMousewheelThumbnails = (e: WheelEvent) => {
|
const handleMousewheelThumbnails = (e: WheelEvent) => {
|
||||||
if (!thumbnailsRef.value) return
|
if (!thumbnailsRef.value) return
|
||||||
thumbnailsRef.value.scrollBy(e.deltaY, 0)
|
thumbnailsRef.value.scrollBy(e.deltaY, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
const setRemarkFontSize = (fontSize: number) => {
|
const setRemarkFontSize = (fontSize: number) => {
|
||||||
if (fontSize < 12 || fontSize > 40) return
|
if (fontSize < 12 || fontSize > 40) return
|
||||||
remarkFontSize.value = fontSize
|
remarkFontSize.value = fontSize
|
||||||
}
|
}
|
||||||
|
|
||||||
watch(slideIndex, () => {
|
watch(slideIndex, () => {
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
if (!thumbnailsRef.value) return
|
if (!thumbnailsRef.value) return
|
||||||
|
|
||||||
@ -151,9 +144,9 @@ export default defineComponent({
|
|||||||
const offsetLeft = activeThumbnailRef.offsetLeft
|
const offsetLeft = activeThumbnailRef.offsetLeft
|
||||||
thumbnailsRef.value.scrollTo({ left: offsetLeft - width / 2, behavior: 'smooth' })
|
thumbnailsRef.value.scrollTo({ left: offsetLeft - width / 2, behavior: 'smooth' })
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
const contextmenus = (): ContextmenuItem[] => {
|
const contextmenus = (): ContextmenuItem[] => {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
text: '上一页',
|
text: '上一页',
|
||||||
@ -193,37 +186,7 @@ export default defineComponent({
|
|||||||
handler: exitScreening,
|
handler: exitScreening,
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
|
||||||
slides,
|
|
||||||
slideIndex,
|
|
||||||
viewportRatio,
|
|
||||||
remarkFontSize,
|
|
||||||
currentSlideRemark,
|
|
||||||
setRemarkFontSize,
|
|
||||||
slideListWrapRef,
|
|
||||||
thumbnailsRef,
|
|
||||||
slideWidth,
|
|
||||||
slideHeight,
|
|
||||||
animationIndex,
|
|
||||||
turnSlideToId,
|
|
||||||
mousewheelListener,
|
|
||||||
touchStartListener,
|
|
||||||
touchEndListener,
|
|
||||||
turnSlideToIndex,
|
|
||||||
contextmenus,
|
|
||||||
slidesLoadLimit,
|
|
||||||
handleMousewheelThumbnails,
|
|
||||||
exitScreening,
|
|
||||||
fullscreenState,
|
|
||||||
enterFullscreen,
|
|
||||||
manualExitFullscreen,
|
|
||||||
writingBoardToolVisible,
|
|
||||||
laserPen,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
})
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
@ -19,8 +19,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts" setup>
|
||||||
import { computed, defineComponent, PropType } from 'vue'
|
import { computed, PropType } from 'vue'
|
||||||
import { storeToRefs } from 'pinia'
|
import { storeToRefs } from 'pinia'
|
||||||
import { useSlidesStore } from '@/store'
|
import { useSlidesStore } from '@/store'
|
||||||
import { ElementTypes, PPTElement } from '@/types/slides'
|
import { ElementTypes, PPTElement } from '@/types/slides'
|
||||||
@ -35,9 +35,7 @@ import BaseLatexElement from '@/views/components/element/LatexElement/BaseLatexE
|
|||||||
import ScreenVideoElement from '@/views/components/element/VideoElement/ScreenVideoElement.vue'
|
import ScreenVideoElement from '@/views/components/element/VideoElement/ScreenVideoElement.vue'
|
||||||
import ScreenAudioElement from '@/views/components/element/AudioElement/ScreenAudioElement.vue'
|
import ScreenAudioElement from '@/views/components/element/AudioElement/ScreenAudioElement.vue'
|
||||||
|
|
||||||
export default defineComponent({
|
const props = defineProps({
|
||||||
name: 'screen-element',
|
|
||||||
props: {
|
|
||||||
elementInfo: {
|
elementInfo: {
|
||||||
type: Object as PropType<PPTElement>,
|
type: Object as PropType<PPTElement>,
|
||||||
required: true,
|
required: true,
|
||||||
@ -58,9 +56,9 @@ export default defineComponent({
|
|||||||
type: Function as PropType<() => void>,
|
type: Function as PropType<() => void>,
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
},
|
})
|
||||||
setup(props) {
|
|
||||||
const currentElementComponent = computed(() => {
|
const currentElementComponent = computed(() => {
|
||||||
const elementTypeMap = {
|
const elementTypeMap = {
|
||||||
[ElementTypes.IMAGE]: BaseImageElement,
|
[ElementTypes.IMAGE]: BaseImageElement,
|
||||||
[ElementTypes.TEXT]: BaseTextElement,
|
[ElementTypes.TEXT]: BaseTextElement,
|
||||||
@ -73,12 +71,12 @@ export default defineComponent({
|
|||||||
[ElementTypes.AUDIO]: ScreenAudioElement,
|
[ElementTypes.AUDIO]: ScreenAudioElement,
|
||||||
}
|
}
|
||||||
return elementTypeMap[props.elementInfo.type] || null
|
return elementTypeMap[props.elementInfo.type] || null
|
||||||
})
|
})
|
||||||
|
|
||||||
const { formatedAnimations, theme } = storeToRefs(useSlidesStore())
|
const { formatedAnimations, theme } = storeToRefs(useSlidesStore())
|
||||||
|
|
||||||
// 判断元素是否需要等待执行入场动画:等待执行入场的元素需要先隐藏
|
// 判断元素是否需要等待执行入场动画:等待执行入场的元素需要先隐藏
|
||||||
const needWaitAnimation = computed(() => {
|
const needWaitAnimation = computed(() => {
|
||||||
// 该元素在本页动画序列中的位置
|
// 该元素在本页动画序列中的位置
|
||||||
const elementIndexInAnimation = formatedAnimations.value.findIndex(item => {
|
const elementIndexInAnimation = formatedAnimations.value.findIndex(item => {
|
||||||
const elIds = item.animations.map(item => item.elId)
|
const elIds = item.animations.map(item => item.elId)
|
||||||
@ -97,10 +95,10 @@ export default defineComponent({
|
|||||||
const firstAnimation = formatedAnimations.value[elementIndexInAnimation].animations.find(item => item.elId === props.elementInfo.id)
|
const firstAnimation = formatedAnimations.value[elementIndexInAnimation].animations.find(item => item.elId === props.elementInfo.id)
|
||||||
if (firstAnimation?.type === 'in') return true
|
if (firstAnimation?.type === 'in') return true
|
||||||
return false
|
return false
|
||||||
})
|
})
|
||||||
|
|
||||||
// 打开元素绑定的超链接
|
// 打开元素绑定的超链接
|
||||||
const openLink = () => {
|
const openLink = () => {
|
||||||
const link = props.elementInfo.link
|
const link = props.elementInfo.link
|
||||||
if (!link) return
|
if (!link) return
|
||||||
|
|
||||||
@ -111,16 +109,7 @@ export default defineComponent({
|
|||||||
else if (link.type === 'slide') {
|
else if (link.type === 'slide') {
|
||||||
props.turnSlideToId(link.target)
|
props.turnSlideToId(link.target)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
|
||||||
currentElementComponent,
|
|
||||||
needWaitAnimation,
|
|
||||||
theme,
|
|
||||||
openLink,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
})
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
@ -20,8 +20,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts" setup>
|
||||||
import { computed, PropType, defineComponent, provide } from 'vue'
|
import { computed, PropType, provide } from 'vue'
|
||||||
import { storeToRefs } from 'pinia'
|
import { storeToRefs } from 'pinia'
|
||||||
import { useSlidesStore } from '@/store'
|
import { useSlidesStore } from '@/store'
|
||||||
import { Slide } from '@/types/slides'
|
import { Slide } from '@/types/slides'
|
||||||
@ -31,12 +31,7 @@ import useSlideBackgroundStyle from '@/hooks/useSlideBackgroundStyle'
|
|||||||
|
|
||||||
import ScreenElement from './ScreenElement.vue'
|
import ScreenElement from './ScreenElement.vue'
|
||||||
|
|
||||||
export default defineComponent({
|
const props = defineProps({
|
||||||
name: 'screen-slide',
|
|
||||||
components: {
|
|
||||||
ScreenElement,
|
|
||||||
},
|
|
||||||
props: {
|
|
||||||
slide: {
|
slide: {
|
||||||
type: Object as PropType<Slide>,
|
type: Object as PropType<Slide>,
|
||||||
required: true,
|
required: true,
|
||||||
@ -57,23 +52,15 @@ export default defineComponent({
|
|||||||
type: Function as PropType<() => void>,
|
type: Function as PropType<() => void>,
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
},
|
|
||||||
setup(props) {
|
|
||||||
const { viewportRatio } = storeToRefs(useSlidesStore())
|
|
||||||
|
|
||||||
const background = computed(() => props.slide.background)
|
|
||||||
const { backgroundStyle } = useSlideBackgroundStyle(background)
|
|
||||||
|
|
||||||
const slideId = computed(() => props.slide.id)
|
|
||||||
provide(injectKeySlideId, slideId)
|
|
||||||
|
|
||||||
return {
|
|
||||||
backgroundStyle,
|
|
||||||
VIEWPORT_SIZE,
|
|
||||||
viewportRatio,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const { viewportRatio } = storeToRefs(useSlidesStore())
|
||||||
|
|
||||||
|
const background = computed(() => props.slide.background)
|
||||||
|
const { backgroundStyle } = useSlideBackgroundStyle(background)
|
||||||
|
|
||||||
|
const slideId = computed(() => props.slide.id)
|
||||||
|
provide(injectKeySlideId, slideId)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user