web-上传组件、富文本组件
This commit is contained in:
@@ -8,14 +8,24 @@ import Quill from 'quill';
|
||||
|
||||
interface ResizeOptions {
|
||||
modules?: string[];
|
||||
onResizeEnd?: (element: HTMLElement) => void; // 拉伸结束回调
|
||||
}
|
||||
|
||||
interface ResizeState {
|
||||
startX: number;
|
||||
startY: number;
|
||||
startWidth: number;
|
||||
startHeight: number;
|
||||
aspectRatio: number;
|
||||
position: string;
|
||||
}
|
||||
|
||||
export class ImageResize {
|
||||
quill: any;
|
||||
options: ResizeOptions;
|
||||
overlay: HTMLElement | null = null;
|
||||
img: HTMLImageElement | HTMLVideoElement | null = null;
|
||||
handle: HTMLElement | null = null;
|
||||
element: HTMLImageElement | HTMLVideoElement | null = null;
|
||||
resizeState: ResizeState | null = null;
|
||||
|
||||
constructor(quill: any, options: ResizeOptions = {}) {
|
||||
this.quill = quill;
|
||||
@@ -25,23 +35,26 @@ export class ImageResize {
|
||||
|
||||
// 等待编辑器完全初始化
|
||||
setTimeout(() => {
|
||||
// 监听编辑器点击事件
|
||||
this.quill.root.addEventListener('click', this.handleClick.bind(this));
|
||||
|
||||
// 点击编辑器外部时隐藏 resize 控件
|
||||
document.addEventListener('click', this.handleDocumentClick.bind(this));
|
||||
|
||||
this.initEventListeners();
|
||||
console.log('✅ ImageResize 事件监听器已添加');
|
||||
}, 100);
|
||||
}
|
||||
|
||||
private initEventListeners() {
|
||||
// 监听编辑器点击事件
|
||||
this.quill.root.addEventListener('click', this.handleClick.bind(this));
|
||||
|
||||
// 点击编辑器外部时隐藏 resize 控件
|
||||
document.addEventListener('click', this.handleDocumentClick.bind(this));
|
||||
}
|
||||
|
||||
handleClick(e: MouseEvent) {
|
||||
const target = e.target as HTMLElement;
|
||||
|
||||
console.log('🖱️ 点击事件:', target.tagName, target);
|
||||
|
||||
// 检查是否点击了图片或视频
|
||||
if (target.tagName === 'IMG' || target.tagName === 'VIDEO') {
|
||||
if (this.isResizableElement(target)) {
|
||||
console.log('📷 检测到图片/视频点击,显示缩放控件');
|
||||
this.showResizer(target as HTMLImageElement | HTMLVideoElement);
|
||||
} else if (!this.overlay || !this.overlay.contains(target)) {
|
||||
@@ -50,6 +63,10 @@ export class ImageResize {
|
||||
}
|
||||
}
|
||||
|
||||
private isResizableElement(element: HTMLElement): boolean {
|
||||
return element.tagName === 'IMG' || element.tagName === 'VIDEO';
|
||||
}
|
||||
|
||||
handleDocumentClick(e: MouseEvent) {
|
||||
const target = e.target as HTMLElement;
|
||||
|
||||
@@ -60,7 +77,7 @@ export class ImageResize {
|
||||
}
|
||||
|
||||
showResizer(element: HTMLImageElement | HTMLVideoElement) {
|
||||
this.img = element;
|
||||
this.element = element;
|
||||
|
||||
console.log('🎯 显示缩放控件,元素:', element);
|
||||
|
||||
@@ -72,6 +89,14 @@ export class ImageResize {
|
||||
if (!this.overlay) return;
|
||||
|
||||
// 更新遮罩层位置和大小
|
||||
this.updateOverlayPosition(element);
|
||||
|
||||
console.log('✅ 缩放控件已显示');
|
||||
}
|
||||
|
||||
private updateOverlayPosition(element: HTMLImageElement | HTMLVideoElement) {
|
||||
if (!this.overlay) return;
|
||||
|
||||
const rect = element.getBoundingClientRect();
|
||||
const containerRect = this.quill.root.getBoundingClientRect();
|
||||
|
||||
@@ -87,15 +112,14 @@ export class ImageResize {
|
||||
this.overlay.style.top = `${rect.top - containerRect.top + this.quill.root.scrollTop}px`;
|
||||
this.overlay.style.width = `${rect.width}px`;
|
||||
this.overlay.style.height = `${rect.height}px`;
|
||||
|
||||
console.log('✅ 缩放控件已显示');
|
||||
}
|
||||
|
||||
hideResizer() {
|
||||
if (this.overlay) {
|
||||
this.overlay.style.display = 'none';
|
||||
}
|
||||
this.img = null;
|
||||
this.element = null;
|
||||
this.resizeState = null;
|
||||
}
|
||||
|
||||
createOverlay() {
|
||||
@@ -199,87 +223,124 @@ export class ImageResize {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
if (!this.img || !this.overlay) return;
|
||||
if (!this.element || !this.overlay) return;
|
||||
|
||||
const startX = e.clientX;
|
||||
const startY = e.clientY;
|
||||
const startWidth = this.img.offsetWidth;
|
||||
const startHeight = this.img.offsetHeight;
|
||||
const aspectRatio = startWidth / startHeight;
|
||||
// 初始化拉伸状态
|
||||
this.resizeState = {
|
||||
startX: e.clientX,
|
||||
startY: e.clientY,
|
||||
startWidth: this.element.offsetWidth,
|
||||
startHeight: this.element.offsetHeight,
|
||||
aspectRatio: this.element.offsetWidth / this.element.offsetHeight,
|
||||
position
|
||||
};
|
||||
|
||||
const handleMouseMove = (moveEvent: MouseEvent) => {
|
||||
if (!this.img || !this.overlay) return;
|
||||
if (!this.element || !this.overlay || !this.resizeState) return;
|
||||
|
||||
const deltaX = moveEvent.clientX - startX;
|
||||
const deltaY = moveEvent.clientY - startY;
|
||||
|
||||
let newWidth = startWidth;
|
||||
let newHeight = startHeight;
|
||||
|
||||
// 根据拉伸方向计算新尺寸
|
||||
if (position.includes('e')) {
|
||||
newWidth = startWidth + deltaX;
|
||||
} else if (position.includes('w')) {
|
||||
newWidth = startWidth - deltaX;
|
||||
}
|
||||
|
||||
if (position.includes('s')) {
|
||||
newHeight = startHeight + deltaY;
|
||||
} else if (position.includes('n')) {
|
||||
newHeight = startHeight - deltaY;
|
||||
}
|
||||
|
||||
// 保持纵横比(对于对角拉伸)
|
||||
if (position.length === 2) {
|
||||
newHeight = newWidth / aspectRatio;
|
||||
}
|
||||
|
||||
// 限制最小尺寸
|
||||
if (newWidth < 50) newWidth = 50;
|
||||
if (newHeight < 50) newHeight = 50;
|
||||
|
||||
// 应用新尺寸(同时设置 style 和属性)
|
||||
this.img.style.width = `${newWidth}px`;
|
||||
this.img.style.height = `${newHeight}px`;
|
||||
this.img.setAttribute('width', `${newWidth}`);
|
||||
this.img.setAttribute('height', `${newHeight}`);
|
||||
|
||||
|
||||
// 更新遮罩层
|
||||
this.overlay.style.width = `${newWidth}px`;
|
||||
this.overlay.style.height = `${newHeight}px`;
|
||||
this.updateElementSize(moveEvent);
|
||||
};
|
||||
|
||||
const handleMouseUp = () => {
|
||||
document.removeEventListener('mousemove', handleMouseMove);
|
||||
document.removeEventListener('mouseup', handleMouseUp);
|
||||
|
||||
// 强制触发 Quill 的 text-change 事件
|
||||
// 使用 Quill 的内部方法
|
||||
console.log('🔄 尝试触发 text-change 事件');
|
||||
if (this.quill.emitter) {
|
||||
this.quill.emitter.emit('text-change', {
|
||||
oldRange: null,
|
||||
range: null,
|
||||
source: 'user'
|
||||
});
|
||||
console.log('✅ text-change 事件已触发');
|
||||
} else {
|
||||
console.log('❌ quill.emitter 不存在');
|
||||
}
|
||||
|
||||
// 备用方案:直接触发 DOM 事件
|
||||
console.log('🔄 尝试触发 input 事件');
|
||||
const event = new Event('input', { bubbles: true });
|
||||
this.quill.root.dispatchEvent(event);
|
||||
console.log('✅ input 事件已触发');
|
||||
|
||||
// 拉伸结束,触发更新
|
||||
this.onResizeEnd();
|
||||
};
|
||||
|
||||
document.addEventListener('mousemove', handleMouseMove);
|
||||
document.addEventListener('mouseup', handleMouseUp);
|
||||
}
|
||||
|
||||
private updateElementSize(moveEvent: MouseEvent) {
|
||||
if (!this.element || !this.overlay || !this.resizeState) return;
|
||||
|
||||
const deltaX = moveEvent.clientX - this.resizeState.startX;
|
||||
const deltaY = moveEvent.clientY - this.resizeState.startY;
|
||||
|
||||
let newWidth = this.resizeState.startWidth;
|
||||
let newHeight = this.resizeState.startHeight;
|
||||
|
||||
// 根据拉伸方向计算新尺寸
|
||||
if (this.resizeState.position.includes('e')) {
|
||||
newWidth = this.resizeState.startWidth + deltaX;
|
||||
} else if (this.resizeState.position.includes('w')) {
|
||||
newWidth = this.resizeState.startWidth - deltaX;
|
||||
}
|
||||
|
||||
if (this.resizeState.position.includes('s')) {
|
||||
newHeight = this.resizeState.startHeight + deltaY;
|
||||
} else if (this.resizeState.position.includes('n')) {
|
||||
newHeight = this.resizeState.startHeight - deltaY;
|
||||
}
|
||||
|
||||
// 保持纵横比(对于对角拉伸)
|
||||
if (this.resizeState.position.length === 2) {
|
||||
newHeight = newWidth / this.resizeState.aspectRatio;
|
||||
}
|
||||
|
||||
// 限制最小尺寸
|
||||
if (newWidth < 50) newWidth = 50;
|
||||
if (newHeight < 50) newHeight = 50;
|
||||
|
||||
// 应用新尺寸
|
||||
this.applyElementSize(newWidth, newHeight);
|
||||
|
||||
// 更新遮罩层
|
||||
this.overlay.style.width = `${newWidth}px`;
|
||||
this.overlay.style.height = `${newHeight}px`;
|
||||
}
|
||||
|
||||
private applyElementSize(width: number, height: number) {
|
||||
if (!this.element) return;
|
||||
|
||||
// 同时设置 style 和属性
|
||||
this.element.style.width = `${width}px`;
|
||||
this.element.style.height = `${height}px`;
|
||||
this.element.setAttribute('width', `${width}`);
|
||||
this.element.setAttribute('height', `${height}`);
|
||||
}
|
||||
|
||||
private onResizeEnd() {
|
||||
if (!this.element) return;
|
||||
|
||||
console.log('🔄 拉伸结束,触发HTML更新');
|
||||
|
||||
// 调用自定义回调
|
||||
if (this.options.onResizeEnd) {
|
||||
this.options.onResizeEnd(this.element);
|
||||
}
|
||||
|
||||
// 强制触发 Quill 的内容更新
|
||||
this.triggerQuillUpdate();
|
||||
}
|
||||
|
||||
private triggerQuillUpdate() {
|
||||
// 方案1: 使用 Quill 的内部方法
|
||||
if (this.quill.emitter) {
|
||||
this.quill.emitter.emit('text-change', {
|
||||
oldRange: null,
|
||||
range: null,
|
||||
source: 'user'
|
||||
});
|
||||
console.log('✅ Quill text-change 事件已触发');
|
||||
}
|
||||
|
||||
// 方案2: 触发 DOM 事件
|
||||
const inputEvent = new Event('input', { bubbles: true });
|
||||
this.quill.root.dispatchEvent(inputEvent);
|
||||
console.log('✅ DOM input 事件已触发');
|
||||
|
||||
// 方案3: 强制更新 Quill 内容
|
||||
setTimeout(() => {
|
||||
if (this.quill.update) {
|
||||
this.quill.update();
|
||||
console.log('✅ Quill 内容已更新');
|
||||
}
|
||||
}, 0);
|
||||
}
|
||||
|
||||
destroy() {
|
||||
if (this.overlay) {
|
||||
this.overlay.remove();
|
||||
|
||||
Reference in New Issue
Block a user