mirror of
https://github.com/pipipi-pikachu/PPTist.git
synced 2025-04-15 02:20:00 +08:00
feat: 元素支持设置超链接
This commit is contained in:
parent
9c4a6ed936
commit
5fa479bf30
@ -240,6 +240,7 @@ export default () => {
|
|||||||
angle: 45,
|
angle: 45,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (el.link) options.hyperlink = { url: el.link }
|
||||||
|
|
||||||
pptxSlide.addText(textProps, options)
|
pptxSlide.addText(textProps, options)
|
||||||
}
|
}
|
||||||
@ -255,6 +256,7 @@ export default () => {
|
|||||||
if (el.flipV) options.flipV = el.flipV
|
if (el.flipV) options.flipV = el.flipV
|
||||||
if (el.rotate) options.rotate = el.rotate
|
if (el.rotate) options.rotate = el.rotate
|
||||||
if (el.clip && el.clip.shape === 'ellipse') options.rounding = true
|
if (el.clip && el.clip.shape === 'ellipse') options.rounding = true
|
||||||
|
if (el.link) options.hyperlink = { url: el.link }
|
||||||
|
|
||||||
pptxSlide.addImage(options)
|
pptxSlide.addImage(options)
|
||||||
}
|
}
|
||||||
@ -271,6 +273,7 @@ export default () => {
|
|||||||
h: el.height / 100,
|
h: el.height / 100,
|
||||||
}
|
}
|
||||||
if (el.rotate) options.rotate = el.rotate
|
if (el.rotate) options.rotate = el.rotate
|
||||||
|
if (el.link) options.hyperlink = { url: el.link }
|
||||||
|
|
||||||
pptxSlide.addImage(options)
|
pptxSlide.addImage(options)
|
||||||
}
|
}
|
||||||
@ -312,6 +315,8 @@ export default () => {
|
|||||||
angle: 45,
|
angle: 45,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (el.link) options.hyperlink = { url: el.link }
|
||||||
|
|
||||||
pptxSlide.addShape('custGeom' as pptxgen.ShapeType, options)
|
pptxSlide.addShape('custGeom' as pptxgen.ShapeType, options)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
33
src/hooks/useLink.ts
Normal file
33
src/hooks/useLink.ts
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
import { MutationTypes, useStore } from '@/store'
|
||||||
|
import { PPTElement } from '@/types/slides'
|
||||||
|
import useHistorySnapshot from '@/hooks/useHistorySnapshot'
|
||||||
|
import { message } from 'ant-design-vue'
|
||||||
|
|
||||||
|
export default () => {
|
||||||
|
const store = useStore()
|
||||||
|
|
||||||
|
const { addHistorySnapshot } = useHistorySnapshot()
|
||||||
|
|
||||||
|
const setLink = (handleElement: PPTElement, link: string) => {
|
||||||
|
const linkRegExp = /^(https?):\/\/[\w\-]+(\.[\w\-]+)+([\w\-.,@?^=%&:\/~+#]*[\w\-@?^=%&\/~+#])?$/
|
||||||
|
if (!link || !linkRegExp.test(link)) {
|
||||||
|
message.error('不是正确的网页链接地址')
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
const props = { link }
|
||||||
|
store.commit(MutationTypes.UPDATE_ELEMENT, { id: handleElement.id, props })
|
||||||
|
addHistorySnapshot()
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
const removeLink = (handleElement: PPTElement) => {
|
||||||
|
store.commit(MutationTypes.REMOVE_ELEMENT_PROPS, { id: handleElement.id, propName: 'link' })
|
||||||
|
addHistorySnapshot()
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
setLink,
|
||||||
|
removeLink,
|
||||||
|
}
|
||||||
|
}
|
@ -30,6 +30,7 @@ interface PPTBaseElement {
|
|||||||
groupId?: string;
|
groupId?: string;
|
||||||
width: number;
|
width: number;
|
||||||
height: number;
|
height: number;
|
||||||
|
link?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface PPTTextElement extends PPTBaseElement {
|
export interface PPTTextElement extends PPTBaseElement {
|
||||||
|
@ -57,6 +57,10 @@ export default defineComponent({
|
|||||||
type: Function as PropType<(e: MouseEvent, element: PPTElement, canMove?: boolean) => void>,
|
type: Function as PropType<(e: MouseEvent, element: PPTElement, canMove?: boolean) => void>,
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
|
openLinkDialog: {
|
||||||
|
type: Function as PropType<() => void>,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
setup(props) {
|
setup(props) {
|
||||||
const currentElementComponent = computed(() => {
|
const currentElementComponent = computed(() => {
|
||||||
@ -144,6 +148,10 @@ export default defineComponent({
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
{ divider: true },
|
{ divider: true },
|
||||||
|
{
|
||||||
|
text: '设置链接',
|
||||||
|
handler: props.openLinkDialog,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
text: props.elementInfo.groupId ? '取消组合' : '组合',
|
text: props.elementInfo.groupId ? '取消组合' : '组合',
|
||||||
subText: 'Ctrl + G',
|
subText: 'Ctrl + G',
|
||||||
|
59
src/views/Editor/Canvas/LinkDialog.vue
Normal file
59
src/views/Editor/Canvas/LinkDialog.vue
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
<template>
|
||||||
|
<div class="link-dialog">
|
||||||
|
<Input v-model:value="link" placeholder="请输入网页链接地址" />
|
||||||
|
|
||||||
|
<div class="btns">
|
||||||
|
<Button @click="close()" style="margin-right: 10px;">取消</Button>
|
||||||
|
<Button type="primary" @click="save()">确认</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { computed, defineComponent, onMounted, ref } from 'vue'
|
||||||
|
import { useStore } from '@/store'
|
||||||
|
import { PPTElement } from '@/types/slides'
|
||||||
|
import useLink from '@/hooks/useLink'
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'link-dialog',
|
||||||
|
setup(props, { emit }) {
|
||||||
|
const store = useStore()
|
||||||
|
const handleElement = computed<PPTElement | null>(() => store.getters.handleElement)
|
||||||
|
|
||||||
|
const link = ref('')
|
||||||
|
|
||||||
|
const { setLink } = useLink()
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
if (handleElement.value?.link) link.value = handleElement.value.link
|
||||||
|
})
|
||||||
|
|
||||||
|
const close = () => emit('close')
|
||||||
|
|
||||||
|
const save = () => {
|
||||||
|
if (handleElement.value) {
|
||||||
|
const success = setLink(handleElement.value, link.value)
|
||||||
|
if (success) close()
|
||||||
|
else link.value = ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
link,
|
||||||
|
close,
|
||||||
|
save,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.link-dialog {
|
||||||
|
padding: 25px 10px 10px 10px;
|
||||||
|
}
|
||||||
|
.btns {
|
||||||
|
margin-top: 10px;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
</style>
|
67
src/views/Editor/Canvas/Operate/LinkHandler.vue
Normal file
67
src/views/Editor/Canvas/Operate/LinkHandler.vue
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
<template>
|
||||||
|
<div class="link-handler">
|
||||||
|
<a class="link" :href="elementInfo.link" target="_blank">{{elementInfo.link}}</a>
|
||||||
|
<div class="btns">
|
||||||
|
<div class="btn" @click="openLinkDialog()">更换</div>
|
||||||
|
<Divider type="vertical" />
|
||||||
|
<div class="btn" @click="removeLink(elementInfo)">移除</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { defineComponent, PropType } from 'vue'
|
||||||
|
import { PPTElement } from '@/types/slides'
|
||||||
|
import useLink from '@/hooks/useLink'
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'link-handler',
|
||||||
|
props: {
|
||||||
|
elementInfo: {
|
||||||
|
type: Object as PropType<PPTElement>,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
openLinkDialog: {
|
||||||
|
type: Function as PropType<() => void>,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
setup() {
|
||||||
|
const { removeLink } = useLink()
|
||||||
|
|
||||||
|
return {
|
||||||
|
removeLink,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.link-handler {
|
||||||
|
height: 30px;
|
||||||
|
position: absolute;
|
||||||
|
top: -36px;
|
||||||
|
left: 0;
|
||||||
|
font-size: 12px;
|
||||||
|
padding: 0 10px;
|
||||||
|
background-color: #fff;
|
||||||
|
box-shadow: $boxShadow;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
color: $themeColor;
|
||||||
|
}
|
||||||
|
.link {
|
||||||
|
margin-right: 20px;
|
||||||
|
word-break: keep-all;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
.btns {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
.btn {
|
||||||
|
word-break: keep-all;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
@ -26,6 +26,13 @@
|
|||||||
>
|
>
|
||||||
{{elementIndexInAnimation + 1}}
|
{{elementIndexInAnimation + 1}}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<LinkHandler
|
||||||
|
:elementInfo="elementInfo"
|
||||||
|
:openLinkDialog="openLinkDialog"
|
||||||
|
v-if="isActive && elementInfo.link"
|
||||||
|
@mousedown.stop
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -41,9 +48,13 @@ import ShapeElementOperate from './ShapeElementOperate.vue'
|
|||||||
import LineElementOperate from './LineElementOperate.vue'
|
import LineElementOperate from './LineElementOperate.vue'
|
||||||
import ChartElementOperate from './ChartElementOperate.vue'
|
import ChartElementOperate from './ChartElementOperate.vue'
|
||||||
import TableElementOperate from './TableElementOperate.vue'
|
import TableElementOperate from './TableElementOperate.vue'
|
||||||
|
import LinkHandler from './LinkHandler.vue'
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: 'operate',
|
name: 'operate',
|
||||||
|
components: {
|
||||||
|
LinkHandler,
|
||||||
|
},
|
||||||
props: {
|
props: {
|
||||||
elementInfo: {
|
elementInfo: {
|
||||||
type: Object as PropType<PPTElement>,
|
type: Object as PropType<PPTElement>,
|
||||||
@ -77,6 +88,10 @@ export default defineComponent({
|
|||||||
type: Function as PropType<(e: MouseEvent, element: PPTElement, command: OperateLineHandler) => void>,
|
type: Function as PropType<(e: MouseEvent, element: PPTElement, command: OperateLineHandler) => void>,
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
|
openLinkDialog: {
|
||||||
|
type: Function as PropType<() => void>,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
setup(props) {
|
setup(props) {
|
||||||
const store = useStore()
|
const store = useStore()
|
||||||
@ -134,6 +149,5 @@ export default defineComponent({
|
|||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
margin-bottom: 3px;
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
@ -43,6 +43,7 @@
|
|||||||
:isMultiSelect="activeElementIdList.length > 1"
|
:isMultiSelect="activeElementIdList.length > 1"
|
||||||
:rotateElement="rotateElement"
|
:rotateElement="rotateElement"
|
||||||
:scaleElement="scaleElement"
|
:scaleElement="scaleElement"
|
||||||
|
:openLinkDialog="openLinkDialog"
|
||||||
:dragLineElement="dragLineElement"
|
:dragLineElement="dragLineElement"
|
||||||
/>
|
/>
|
||||||
<ViewportBackground />
|
<ViewportBackground />
|
||||||
@ -68,9 +69,20 @@
|
|||||||
:elementIndex="index + 1"
|
:elementIndex="index + 1"
|
||||||
:isMultiSelect="activeElementIdList.length > 1"
|
:isMultiSelect="activeElementIdList.length > 1"
|
||||||
:selectElement="selectElement"
|
:selectElement="selectElement"
|
||||||
|
:openLinkDialog="openLinkDialog"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<Modal
|
||||||
|
v-model:visible="linkDialogVisible"
|
||||||
|
:footer="null"
|
||||||
|
centered
|
||||||
|
:width="540"
|
||||||
|
destroyOnClose
|
||||||
|
>
|
||||||
|
<LinkDialog @close="linkDialogVisible = false" />
|
||||||
|
</Modal>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -108,6 +120,7 @@ import AlignmentLine from './AlignmentLine.vue'
|
|||||||
import ElementCreateSelection from './ElementCreateSelection.vue'
|
import ElementCreateSelection from './ElementCreateSelection.vue'
|
||||||
import MultiSelectOperate from './Operate/MultiSelectOperate.vue'
|
import MultiSelectOperate from './Operate/MultiSelectOperate.vue'
|
||||||
import Operate from './Operate/index.vue'
|
import Operate from './Operate/index.vue'
|
||||||
|
import LinkDialog from './LinkDialog.vue'
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: 'editor-canvas',
|
name: 'editor-canvas',
|
||||||
@ -119,6 +132,7 @@ export default defineComponent({
|
|||||||
ElementCreateSelection,
|
ElementCreateSelection,
|
||||||
MultiSelectOperate,
|
MultiSelectOperate,
|
||||||
Operate,
|
Operate,
|
||||||
|
LinkDialog,
|
||||||
},
|
},
|
||||||
setup() {
|
setup() {
|
||||||
const store = useStore()
|
const store = useStore()
|
||||||
@ -133,6 +147,9 @@ export default defineComponent({
|
|||||||
const viewportRef = ref<HTMLElement>()
|
const viewportRef = ref<HTMLElement>()
|
||||||
const alignmentLines = ref<AlignmentLineProps[]>([])
|
const alignmentLines = ref<AlignmentLineProps[]>([])
|
||||||
|
|
||||||
|
const linkDialogVisible = ref(false)
|
||||||
|
const openLinkDialog = () => linkDialogVisible.value = true
|
||||||
|
|
||||||
watch(handleElementId, () => {
|
watch(handleElementId, () => {
|
||||||
store.commit(MutationTypes.SET_ACTIVE_GROUP_ELEMENT_ID, '')
|
store.commit(MutationTypes.SET_ACTIVE_GROUP_ELEMENT_ID, '')
|
||||||
})
|
})
|
||||||
@ -249,12 +266,14 @@ export default defineComponent({
|
|||||||
viewportStyles,
|
viewportStyles,
|
||||||
canvasScale,
|
canvasScale,
|
||||||
mouseSelectionState,
|
mouseSelectionState,
|
||||||
handleClickBlankArea,
|
|
||||||
removeEditorAreaFocus,
|
|
||||||
currentSlide,
|
currentSlide,
|
||||||
creatingElement,
|
creatingElement,
|
||||||
insertElementFromCreateSelection,
|
|
||||||
alignmentLines,
|
alignmentLines,
|
||||||
|
linkDialogVisible,
|
||||||
|
openLinkDialog,
|
||||||
|
handleClickBlankArea,
|
||||||
|
removeEditorAreaFocus,
|
||||||
|
insertElementFromCreateSelection,
|
||||||
selectElement,
|
selectElement,
|
||||||
rotateElement,
|
rotateElement,
|
||||||
scaleElement,
|
scaleElement,
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<div
|
<div
|
||||||
class="screen-element"
|
class="screen-element"
|
||||||
|
:class="{ 'link': elementInfo.link }"
|
||||||
:id="`screen-element-${elementInfo.id}`"
|
:id="`screen-element-${elementInfo.id}`"
|
||||||
:style="{
|
:style="{
|
||||||
zIndex: elementIndex,
|
zIndex: elementIndex,
|
||||||
@ -8,6 +9,7 @@
|
|||||||
fontFamily: theme.fontName,
|
fontFamily: theme.fontName,
|
||||||
visibility: needWaitAnimation ? 'hidden' : 'visible',
|
visibility: needWaitAnimation ? 'hidden' : 'visible',
|
||||||
}"
|
}"
|
||||||
|
@click="openLink()"
|
||||||
>
|
>
|
||||||
<component
|
<component
|
||||||
:is="currentElementComponent"
|
:is="currentElementComponent"
|
||||||
@ -69,11 +71,22 @@ export default defineComponent({
|
|||||||
return false
|
return false
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const openLink = () => {
|
||||||
|
if (props.elementInfo.link) window.open(props.elementInfo.link)
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
currentElementComponent,
|
currentElementComponent,
|
||||||
needWaitAnimation,
|
needWaitAnimation,
|
||||||
theme,
|
theme,
|
||||||
|
openLink,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.link {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
</style>
|
Loading…
x
Reference in New Issue
Block a user