feat: 添加网页元素demo

This commit is contained in:
pipipi-pikachu 2022-06-19 16:57:16 +08:00
parent e62fbd839d
commit 80c49d88c7
13 changed files with 243 additions and 1 deletions

View File

@ -7,6 +7,7 @@ export const ELEMENT_TYPE_ZH = {
table: '表格',
video: '视频',
audio: '音频',
frame: '网页',
}
export const MIN_SIZE = {
@ -17,4 +18,5 @@ export const MIN_SIZE = {
table: 20,
video: 250,
audio: 20,
frame: 200,
}

View File

@ -309,6 +309,19 @@ export default () => {
})
}
const createFrameElement = (url: string) => {
createElement({
type: 'frame',
id: nanoid(10),
width: 800,
height: 480,
rotate: 0,
left: (VIEWPORT_SIZE - 800) / 2,
top: (VIEWPORT_SIZE * viewportRatio.value - 480) / 2,
url,
})
}
return {
createImageElement,
createChartElement,
@ -319,5 +332,6 @@ export default () => {
createLatexElement,
createVideoElement,
createAudioElement,
createFrameElement,
}
}

View File

@ -25,6 +25,7 @@ export const enum ElementTypes {
LATEX = 'latex',
VIDEO = 'video',
AUDIO = 'audio',
FRAME = 'frame',
}
/**
@ -570,7 +571,18 @@ export interface PPTAudioElement extends PPTBaseElement {
}
export type PPTElement = PPTTextElement | PPTImageElement | PPTShapeElement | PPTLineElement | PPTChartElement | PPTTableElement | PPTLatexElement | PPTVideoElement | PPTAudioElement
export interface PPTFrameElement extends PPTBaseElement {
type: 'frame'
id: string;
left: number;
top: number;
width: number;
height: number;
url: string; // 网页链接地址
}
export type PPTElement = PPTTextElement | PPTImageElement | PPTShapeElement | PPTLineElement | PPTChartElement | PPTTableElement | PPTLatexElement | PPTVideoElement | PPTAudioElement | PPTFrameElement
/**

View File

@ -40,6 +40,7 @@ import TableElement from '@/views/components/element/TableElement/index.vue'
import LatexElement from '@/views/components/element/LatexElement/index.vue'
import VideoElement from '@/views/components/element/VideoElement/index.vue'
import AudioElement from '@/views/components/element/AudioElement/index.vue'
import FrameElement from '@/views/components/element/FrameElement/index.vue'
const props = defineProps({
elementInfo: {
@ -75,6 +76,7 @@ const currentElementComponent = computed(() => {
[ElementTypes.LATEX]: LatexElement,
[ElementTypes.VIDEO]: VideoElement,
[ElementTypes.AUDIO]: AudioElement,
[ElementTypes.FRAME]: FrameElement,
}
return elementTypeMap[props.elementInfo.type] || null
})

View File

@ -104,6 +104,7 @@ const currentOperateComponent = computed(() => {
[ElementTypes.LATEX]: CommonElementOperate,
[ElementTypes.VIDEO]: CommonElementOperate,
[ElementTypes.AUDIO]: CommonElementOperate,
[ElementTypes.FRAME]: CommonElementOperate,
}
return elementTypeMap[props.elementInfo.type] || null
})

View File

@ -68,6 +68,7 @@
<IconVideoTwo class="handler-item" />
</Tooltip>
</Popover>
<span class="handler-item" @click="createFrameElement('https://v3.cn.vuejs.org/')">插入网页</span>
</div>
<div class="right-handler">
@ -152,6 +153,7 @@ const {
createLatexElement,
createVideoElement,
createAudioElement,
createFrameElement,
} = useCreateElement()
const insertImageElement = (files: FileList) => {

View File

@ -0,0 +1,29 @@
<template>
<div class="frame-style-panel">
<div class="row">
<div>网页链接</div>
<Input v-model:value="url" placeholder="请输入网页链接" />
<Button @click="updateURL()">确定</Button>
</div>
</div>
</template>
<script lang="ts" setup>
import { ref } from 'vue'
import { storeToRefs } from 'pinia'
import { useMainStore, useSlidesStore } from '@/store'
import useHistorySnapshot from '@/hooks/useHistorySnapshot'
const slidesStore = useSlidesStore()
const { handleElementId } = storeToRefs(useMainStore())
const { addHistorySnapshot } = useHistorySnapshot()
const url = ref('')
const updateURL = () => {
if (!handleElementId.value) return
slidesStore.updateElement({ id: handleElementId.value, props: { url: url.value } })
addHistorySnapshot()
}
</script>

View File

@ -20,6 +20,7 @@ import LatexStylePanel from './LatexStylePanel.vue'
import VideoStylePanel from './VideoStylePanel.vue'
import AudioStylePanel from './AudioStylePanel.vue'
import MultiStylePanel from './MultiStylePanel.vue'
import FrameStylePanel from './FrameStylePanel.vue'
const panelMap = {
[ElementTypes.TEXT]: TextStylePanel,
@ -31,6 +32,7 @@ const panelMap = {
[ElementTypes.LATEX]: LatexStylePanel,
[ElementTypes.VIDEO]: VideoStylePanel,
[ElementTypes.AUDIO]: AudioStylePanel,
[ElementTypes.FRAME]: FrameStylePanel,
}
const { activeElementIdList, activeElementList, handleElement, activeGroupElementId } = storeToRefs(useMainStore())

View File

@ -27,6 +27,7 @@ import TableElement from '@/views/components/element/TableElement/index.vue'
import LatexElement from '@/views/components/element/LatexElement/index.vue'
import VideoElement from '@/views/components/element/VideoElement/index.vue'
import AudioElement from '@/views/components/element/AudioElement/index.vue'
import FrameElement from '@/views/components/element/FrameElement/index.vue'
const props = defineProps({
elementInfo: {
@ -54,6 +55,7 @@ const currentElementComponent = computed(() => {
[ElementTypes.LATEX]: LatexElement,
[ElementTypes.VIDEO]: VideoElement,
[ElementTypes.AUDIO]: AudioElement,
[ElementTypes.FRAME]: FrameElement,
}
return elementTypeMap[props.elementInfo.type] || null
})

View File

@ -34,6 +34,7 @@ import BaseTableElement from '@/views/components/element/TableElement/BaseTableE
import BaseLatexElement from '@/views/components/element/LatexElement/BaseLatexElement.vue'
import ScreenVideoElement from '@/views/components/element/VideoElement/ScreenVideoElement.vue'
import ScreenAudioElement from '@/views/components/element/AudioElement/ScreenAudioElement.vue'
import BaseFrameElement from '@/views/components/element/FrameElement/BaseFrameElement.vue'
const props = defineProps({
elementInfo: {
@ -69,6 +70,7 @@ const currentElementComponent = computed(() => {
[ElementTypes.LATEX]: BaseLatexElement,
[ElementTypes.VIDEO]: ScreenVideoElement,
[ElementTypes.AUDIO]: ScreenAudioElement,
[ElementTypes.FRAME]: BaseFrameElement,
}
return elementTypeMap[props.elementInfo.type] || null
})

View File

@ -27,6 +27,7 @@ import BaseTableElement from '@/views/components/element/TableElement/BaseTableE
import BaseLatexElement from '@/views/components/element/LatexElement/BaseLatexElement.vue'
import BaseVideoElement from '@/views/components/element/VideoElement/BaseVideoElement.vue'
import BaseAudioElement from '@/views/components/element/AudioElement/BaseAudioElement.vue'
import BaseFrameElement from '@/views/components/element/FrameElement/BaseFrameElement.vue'
const props = defineProps({
elementInfo: {
@ -50,6 +51,7 @@ const currentElementComponent = computed(() => {
[ElementTypes.LATEX]: BaseLatexElement,
[ElementTypes.VIDEO]: BaseVideoElement,
[ElementTypes.AUDIO]: BaseAudioElement,
[ElementTypes.FRAME]: BaseFrameElement,
}
return elementTypeMap[props.elementInfo.type] || null
})

View File

@ -0,0 +1,56 @@
<template>
<div class="base-element-frame"
:style="{
top: elementInfo.top + 'px',
left: elementInfo.left + 'px',
width: elementInfo.width + 'px',
height: elementInfo.height + 'px',
}"
>
<div
class="rotate-wrapper"
:style="{ transform: `rotate(${elementInfo.rotate}deg)` }"
>
<div class="element-content">
<iframe
:src="elementInfo.url"
:width="elementInfo.width"
:height="elementInfo.height"
:frameborder="0"
:allowfullscreen="true"
></iframe>
<div class="mask"></div>
</div>
</div>
</div>
</template>
<script lang="ts" setup>
import { PropType } from 'vue'
import { PPTFrameElement } from '@/types/slides'
const props = defineProps({
elementInfo: {
type: Object as PropType<PPTFrameElement>,
required: true,
},
})
</script>
<style lang="scss" scoped>
.base-element-frame {
position: absolute;
}
.element-content {
width: 100%;
height: 100%;
}
.mask {
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
}
</style>

View File

@ -0,0 +1,116 @@
<template>
<div class="editable-element-frame"
:style="{
top: elementInfo.top + 'px',
left: elementInfo.left + 'px',
width: elementInfo.width + 'px',
height: elementInfo.height + 'px',
}"
>
<div
class="rotate-wrapper"
:style="{ transform: `rotate(${elementInfo.rotate}deg)` }"
>
<div
class="element-content"
v-contextmenu="contextmenus"
@mousedown="$event => handleSelectElement($event)"
@touchstart="$event => handleSelectElement($event)"
>
<iframe
:src="elementInfo.url"
:width="elementInfo.width"
:height="elementInfo.height"
:frameborder="0"
:allowfullscreen="true"
></iframe>
<div class="drag-handler top"></div>
<div class="drag-handler bottom"></div>
<div class="drag-handler left"></div>
<div class="drag-handler right"></div>
<div class="mask"
v-if="handleElementId !== elementInfo.id"
@mousedown="$event => handleSelectElement($event, false)"
@touchstart="$event => handleSelectElement($event, false)"
></div>
</div>
</div>
</div>
</template>
<script lang="ts" setup>
import { PropType } from 'vue'
import { storeToRefs } from 'pinia'
import { useMainStore } from '@/store'
import { PPTFrameElement } from '@/types/slides'
import { ContextmenuItem } from '@/components/Contextmenu/types'
const props = defineProps({
elementInfo: {
type: Object as PropType<PPTFrameElement>,
required: true,
},
selectElement: {
type: Function as PropType<(e: MouseEvent | TouchEvent, element: PPTFrameElement, canMove?: boolean) => void>,
required: true,
},
contextmenus: {
type: Function as PropType<() => ContextmenuItem[] | null>,
},
})
const { handleElementId } = storeToRefs(useMainStore())
const handleSelectElement = (e: MouseEvent | TouchEvent, canMove = true) => {
e.stopPropagation()
props.selectElement(e, props.elementInfo, canMove)
}
</script>
<style lang="scss" scoped>
.editable-element-frame {
position: absolute;
}
.element-content {
width: 100%;
height: 100%;
cursor: move;
}
.drag-handler {
position: absolute;
&.top {
height: 20px;
left: 0;
right: 0;
top: 0;
}
&.bottom {
height: 20px;
left: 0;
right: 0;
bottom: 0;
}
&.left {
width: 20px;
top: 0;
bottom: 0;
left: 0;
}
&.right {
width: 20px;
top: 0;
bottom: 0;
right: 0;
}
}
.mask {
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
}
</style>