| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113 |
- <template>
- <div class="text-board-wrapper" :class="{ selected }"
- :style="{ width: '100%', height: '100%', position: 'relative' }">
- <div class="text-board" :style="textStyle">{{ text }}</div>
- <template v-if="selected">
- <div v-for="dir in ['tr', 'tl', 'br', 'bl']" :key="dir" class="resize-handle" :class="dir"
- @mousedown.stop="onResizeMouseDown($event, dir)"></div>
- </template>
- </div>
- </template>
- <script setup lang="ts">
- import { computed } from 'vue';
- const emit = defineEmits(['resize']);
- interface Props {
- text?: string;
- color?: string;
- fontSize?: string | number;
- fontWeight?: string | number;
- align?: 'left' | 'center' | 'right';
- width?: number;
- height?: number;
- selected?: boolean;
- }
- const props = defineProps<Props>();
- const selected = computed(() => !!props.selected);
- const textStyle = computed(() => ({
- color: props.color || '#222',
- fontSize: typeof props.fontSize === 'number' ? props.fontSize + 'px' : props.fontSize || '24px',
- fontWeight: props.fontWeight || 'normal',
- textAlign: props.align || 'center',
- width: '100%',
- height: '100%',
- display: 'flex',
- alignItems: 'center',
- justifyContent: props.align === 'left' ? 'flex-start' : props.align === 'right' ? 'flex-end' : 'center',
- userSelect: 'none',
- }));
- function onResizeMouseDown(e: MouseEvent, dir: string) {
- e.stopPropagation();
- const startX = e.clientX,
- startY = e.clientY;
- const startWidth = Number(props.width) || 200;
- const startHeight = Number(props.height) || 40;
- function onMove(ev: MouseEvent) {
- let newWidth = startWidth,
- newHeight = startHeight;
- if (dir.includes('r')) newWidth += ev.clientX - startX;
- if (dir.includes('l')) newWidth -= ev.clientX - startX;
- if (dir.includes('b')) newHeight += ev.clientY - startY;
- if (dir.includes('t')) newHeight -= ev.clientY - startY;
- emit('resize', { width: Math.max(20, newWidth), height: Math.max(20, newHeight) });
- }
- function onUp() {
- document.removeEventListener('mousemove', onMove);
- document.removeEventListener('mouseup', onUp);
- }
- document.addEventListener('mousemove', onMove);
- document.addEventListener('mouseup', onUp);
- }
- const text = computed(() => props.text || '双击编辑文本');
- </script>
- <style scoped>
- .text-board-wrapper.selected {
- outline: 2px dashed #409eff;
- outline-offset: 0;
- }
- .resize-handle {
- width: 8px;
- height: 8px;
- background: #fff;
- border: 1.5px solid #409eff;
- border-radius: 50%;
- position: absolute;
- z-index: 2;
- }
- .resize-handle.tr {
- top: -4px;
- right: -4px;
- cursor: ne-resize;
- }
- .resize-handle.tl {
- top: -4px;
- left: -4px;
- cursor: nw-resize;
- }
- .resize-handle.br {
- bottom: -4px;
- right: -4px;
- cursor: se-resize;
- }
- .resize-handle.bl {
- bottom: -4px;
- left: -4px;
- cursor: sw-resize;
- }
- .text-board {
- width: 100%;
- height: 100%;
- background: transparent;
- outline: none;
- cursor: text;
- user-select: text;
- }
- </style>
|