AI-News/frontend/app/components/nodes/ResizableImage.vue
2025-12-04 10:04:21 +08:00

128 lines
2.8 KiB
Vue
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<!-- 必须使用 NodeViewWrapper 作为根 -->
<NodeViewWrapper
class="ri-wrap"
:class="{ selected }"
contenteditable="false"
ref="wrap"
>
<img
ref="img"
:src="node.attrs.src"
:alt="node.attrs.alt || ''"
:title="node.attrs.title || ''"
:style="imgStyle"
draggable="false"
@mousedown.stop
@click.stop
/>
<!-- 右下角拖拽手柄等比缩放写回 width -->
<span
v-if="editor.isEditable"
class="ri-handle"
title="拖动调整大小"
@mousedown.stop.prevent="onStartDrag"
/>
</NodeViewWrapper>
</template>
<script setup>
import { NodeViewWrapper } from '@tiptap/vue-3'
import { onBeforeUnmount, ref, computed } from 'vue'
const props = defineProps({
editor: Object,
node: Object,
selected: Boolean,
updateAttributes: Function,
deleteNode: Function,
getPos: Function,
})
const img = ref(null)
const wrap = ref(null)
const imgStyle = computed(() => {
const w = props.node.attrs.width || null
return {
width: w || 'auto',
height: 'auto',
maxWidth: '100%',
display: 'block',
}
})
let dragging = false
let startX = 0
let startWidth = 0
let cleanups = []
function onStartDrag(e) {
if (!img.value) return
const r = img.value.getBoundingClientRect()
startX = e.clientX
startWidth = r.width
dragging = true
document.body.style.userSelect = 'none'
document.body.style.cursor = 'nwse-resize'
const onMove = (ev) => {
if (!dragging) return
const dx = ev.clientX - startX
const containerW = wrap.value?.parentElement?.getBoundingClientRect?.().width || 1200
const newW = Math.max(40, Math.min(containerW, Math.round(startWidth + dx)))
props.updateAttributes({ width: `${newW}px` })
}
const onUp = () => {
dragging = false
document.body.style.userSelect = ''
document.body.style.cursor = ''
window.removeEventListener('mousemove', onMove)
window.removeEventListener('mouseup', onUp)
}
window.addEventListener('mousemove', onMove)
window.addEventListener('mouseup', onUp)
cleanups.push(() => {
window.removeEventListener('mousemove', onMove)
window.removeEventListener('mouseup', onUp)
})
}
onBeforeUnmount(() => cleanups.forEach((fn) => fn()))
</script>
<style scoped>
.ri-wrap {
position: relative;
display: inline-block;
line-height: 0;
max-width: 100%;
border-radius: 10px;
}
.ri-wrap.selected {
outline: 2px solid rgba(99,102,241,.35);
outline-offset: 2px;
}
.ri-wrap img {
max-width: 100%;
height: auto;
border-radius: 10px;
box-shadow: 0 6px 20px rgba(2,6,23,.08);
}
.ri-handle {
position: absolute;
right: -6px;
bottom: -6px;
width: 12px;
height: 12px;
background: #6366f1;
border: 2px solid #fff;
border-radius: 4px;
box-shadow: 0 1px 4px rgba(2,6,23,.25);
cursor: nwse-resize;
}
</style>