mirror of
https://github.com/pipipi-pikachu/PPTist.git
synced 2025-04-15 02:20:00 +08:00
feat: 添加网页元素demo
This commit is contained in:
parent
e62fbd839d
commit
80c49d88c7
@ -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,
|
||||
}
|
@ -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,
|
||||
}
|
||||
}
|
@ -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
|
||||
|
||||
|
||||
/**
|
||||
|
@ -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
|
||||
})
|
||||
|
@ -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
|
||||
})
|
||||
|
@ -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) => {
|
||||
|
@ -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>
|
@ -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())
|
||||
|
@ -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
|
||||
})
|
||||
|
@ -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
|
||||
})
|
||||
|
@ -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
|
||||
})
|
||||
|
@ -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>
|
116
src/views/components/element/FrameElement/index.vue
Normal file
116
src/views/components/element/FrameElement/index.vue
Normal 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>
|
Loading…
x
Reference in New Issue
Block a user