feat: 元素支持设置超链接

This commit is contained in:
pipipi-pikachu 2021-07-30 22:48:32 +08:00
parent 9c4a6ed936
commit 5fa479bf30
9 changed files with 224 additions and 5 deletions

View File

@ -240,6 +240,7 @@ export default () => {
angle: 45,
}
}
if (el.link) options.hyperlink = { url: el.link }
pptxSlide.addText(textProps, options)
}
@ -255,6 +256,7 @@ export default () => {
if (el.flipV) options.flipV = el.flipV
if (el.rotate) options.rotate = el.rotate
if (el.clip && el.clip.shape === 'ellipse') options.rounding = true
if (el.link) options.hyperlink = { url: el.link }
pptxSlide.addImage(options)
}
@ -271,6 +273,7 @@ export default () => {
h: el.height / 100,
}
if (el.rotate) options.rotate = el.rotate
if (el.link) options.hyperlink = { url: el.link }
pptxSlide.addImage(options)
}
@ -312,6 +315,8 @@ export default () => {
angle: 45,
}
}
if (el.link) options.hyperlink = { url: el.link }
pptxSlide.addShape('custGeom' as pptxgen.ShapeType, options)
}
}

33
src/hooks/useLink.ts Normal file
View 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,
}
}

View File

@ -30,6 +30,7 @@ interface PPTBaseElement {
groupId?: string;
width: number;
height: number;
link?: string;
}
export interface PPTTextElement extends PPTBaseElement {

View File

@ -57,6 +57,10 @@ export default defineComponent({
type: Function as PropType<(e: MouseEvent, element: PPTElement, canMove?: boolean) => void>,
required: true,
},
openLinkDialog: {
type: Function as PropType<() => void>,
required: true,
},
},
setup(props) {
const currentElementComponent = computed(() => {
@ -144,6 +148,10 @@ export default defineComponent({
],
},
{ divider: true },
{
text: '设置链接',
handler: props.openLinkDialog,
},
{
text: props.elementInfo.groupId ? '取消组合' : '组合',
subText: 'Ctrl + G',

View 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>

View 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>

View File

@ -26,6 +26,13 @@
>
{{elementIndexInAnimation + 1}}
</div>
<LinkHandler
:elementInfo="elementInfo"
:openLinkDialog="openLinkDialog"
v-if="isActive && elementInfo.link"
@mousedown.stop
/>
</div>
</template>
@ -41,9 +48,13 @@ import ShapeElementOperate from './ShapeElementOperate.vue'
import LineElementOperate from './LineElementOperate.vue'
import ChartElementOperate from './ChartElementOperate.vue'
import TableElementOperate from './TableElementOperate.vue'
import LinkHandler from './LinkHandler.vue'
export default defineComponent({
name: 'operate',
components: {
LinkHandler,
},
props: {
elementInfo: {
type: Object as PropType<PPTElement>,
@ -77,6 +88,10 @@ export default defineComponent({
type: Function as PropType<(e: MouseEvent, element: PPTElement, command: OperateLineHandler) => void>,
required: true,
},
openLinkDialog: {
type: Function as PropType<() => void>,
required: true,
},
},
setup(props) {
const store = useStore()
@ -134,6 +149,5 @@ export default defineComponent({
display: flex;
justify-content: center;
align-items: center;
margin-bottom: 3px;
}
</style>

View File

@ -43,6 +43,7 @@
:isMultiSelect="activeElementIdList.length > 1"
:rotateElement="rotateElement"
:scaleElement="scaleElement"
:openLinkDialog="openLinkDialog"
:dragLineElement="dragLineElement"
/>
<ViewportBackground />
@ -68,9 +69,20 @@
:elementIndex="index + 1"
:isMultiSelect="activeElementIdList.length > 1"
:selectElement="selectElement"
:openLinkDialog="openLinkDialog"
/>
</div>
</div>
<Modal
v-model:visible="linkDialogVisible"
:footer="null"
centered
:width="540"
destroyOnClose
>
<LinkDialog @close="linkDialogVisible = false" />
</Modal>
</div>
</template>
@ -108,6 +120,7 @@ import AlignmentLine from './AlignmentLine.vue'
import ElementCreateSelection from './ElementCreateSelection.vue'
import MultiSelectOperate from './Operate/MultiSelectOperate.vue'
import Operate from './Operate/index.vue'
import LinkDialog from './LinkDialog.vue'
export default defineComponent({
name: 'editor-canvas',
@ -119,6 +132,7 @@ export default defineComponent({
ElementCreateSelection,
MultiSelectOperate,
Operate,
LinkDialog,
},
setup() {
const store = useStore()
@ -133,6 +147,9 @@ export default defineComponent({
const viewportRef = ref<HTMLElement>()
const alignmentLines = ref<AlignmentLineProps[]>([])
const linkDialogVisible = ref(false)
const openLinkDialog = () => linkDialogVisible.value = true
watch(handleElementId, () => {
store.commit(MutationTypes.SET_ACTIVE_GROUP_ELEMENT_ID, '')
})
@ -249,12 +266,14 @@ export default defineComponent({
viewportStyles,
canvasScale,
mouseSelectionState,
handleClickBlankArea,
removeEditorAreaFocus,
currentSlide,
creatingElement,
insertElementFromCreateSelection,
alignmentLines,
linkDialogVisible,
openLinkDialog,
handleClickBlankArea,
removeEditorAreaFocus,
insertElementFromCreateSelection,
selectElement,
rotateElement,
scaleElement,

View File

@ -1,6 +1,7 @@
<template>
<div
class="screen-element"
:class="{ 'link': elementInfo.link }"
:id="`screen-element-${elementInfo.id}`"
:style="{
zIndex: elementIndex,
@ -8,6 +9,7 @@
fontFamily: theme.fontName,
visibility: needWaitAnimation ? 'hidden' : 'visible',
}"
@click="openLink()"
>
<component
:is="currentElementComponent"
@ -69,11 +71,22 @@ export default defineComponent({
return false
})
const openLink = () => {
if (props.elementInfo.link) window.open(props.elementInfo.link)
}
return {
currentElementComponent,
needWaitAnimation,
theme,
openLink,
}
},
})
</script>
</script>
<style lang="scss" scoped>
.link {
cursor: pointer;
}
</style>