优化图片分析弹窗,新增可拖动和调整大小功能,改进预览区布局和缩放控制,确保用户体验流畅。同时,修复标题和日期对齐问题,提升模板生成的准确性。
This commit is contained in:
@@ -150,13 +150,21 @@
|
||||
|
||||
<a-modal
|
||||
v-model:open="imageAnalyzeVisible"
|
||||
title="上传图片分析模板"
|
||||
width="920px"
|
||||
destroy-on-close
|
||||
:width="imageAnalyzeModalWidth"
|
||||
:centered="false"
|
||||
wrap-class-name="native-print-image-analyze-modal"
|
||||
:body-style="imageAnalyzeModalBodyStyle"
|
||||
:closable="!imageAnalyzeLoading"
|
||||
:mask-closable="!imageAnalyzeLoading"
|
||||
@cancel="resetImageAnalyzeModal"
|
||||
>
|
||||
<template #title>
|
||||
<div class="image-analyze-modal-title">
|
||||
<span class="image-analyze-modal-title-text">上传图片分析模板</span>
|
||||
<span class="image-analyze-modal-title-tip">按住标题栏可拖动 · 右下角可拉大窗口</span>
|
||||
</div>
|
||||
</template>
|
||||
<div class="image-analyze-body">
|
||||
<a-alert type="info" show-icon style="margin-bottom: 12px">
|
||||
<template #message>
|
||||
@@ -199,10 +207,68 @@
|
||||
<img :src="imageAnalyzeThumbUrl" alt="上传原图" />
|
||||
</div>
|
||||
<div class="image-analyze-preview-frame-wrap">
|
||||
<div class="thumb-label">按生成模板渲染</div>
|
||||
<iframe class="image-analyze-preview-frame" :srcdoc="imageAnalyzePreviewHtml"></iframe>
|
||||
<div class="image-analyze-preview-toolbar">
|
||||
<div class="thumb-label">按生成模板渲染</div>
|
||||
<a-space v-if="imageAnalyzeLayoutPaperPx" size="small" wrap @click.stop>
|
||||
<a-tooltip title="缩小">
|
||||
<a-button type="default" size="small" class="image-analyze-zoom-btn" @click="imageAnalyzeZoomOut">
|
||||
<Icon icon="ant-design:zoom-out-outlined" />
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
<span class="image-analyze-zoom-pct">{{ imageAnalyzeZoomPercentLabel }}</span>
|
||||
<a-tooltip title="放大">
|
||||
<a-button type="default" size="small" class="image-analyze-zoom-btn" @click="imageAnalyzeZoomIn">
|
||||
<Icon icon="ant-design:zoom-in-outlined" />
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
<a-tooltip title="按预览区大小自动适应">
|
||||
<a-button type="default" size="small" @click="imageAnalyzeZoomFit">适应</a-button>
|
||||
</a-tooltip>
|
||||
</a-space>
|
||||
</div>
|
||||
<div
|
||||
ref="imageAnalyzePreviewHostRef"
|
||||
class="image-analyze-preview-host"
|
||||
:style="{ maxHeight: `${imageAnalyzePreviewHostMaxHeight}px` }"
|
||||
>
|
||||
<template v-if="imageAnalyzeLayoutPaperPx">
|
||||
<div class="image-analyze-preview-scroll">
|
||||
<div class="image-analyze-zoom-slot">
|
||||
<div
|
||||
class="image-analyze-scale-shim"
|
||||
:style="{
|
||||
width: `${imageAnalyzeLayoutPaperPx.wPx * imageAnalyzeDisplayScale}px`,
|
||||
height: `${imageAnalyzeLayoutPaperPx.hPx * imageAnalyzeDisplayScale}px`,
|
||||
}"
|
||||
>
|
||||
<div
|
||||
class="image-analyze-scale-inner"
|
||||
:style="{
|
||||
width: `${imageAnalyzeLayoutPaperPx.wPx}px`,
|
||||
height: `${imageAnalyzeLayoutPaperPx.hPx}px`,
|
||||
transform: `scale(${imageAnalyzeDisplayScale})`,
|
||||
}"
|
||||
>
|
||||
<iframe
|
||||
ref="imageAnalyzeIframeRef"
|
||||
class="image-analyze-preview-frame"
|
||||
:srcdoc="imageAnalyzePreviewHtml"
|
||||
@load="onImageAnalyzeIframeLoad"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
v-if="imageAnalyzeVisible"
|
||||
class="image-analyze-resize-handle"
|
||||
aria-hidden="true"
|
||||
@mousedown.prevent="onImageAnalyzeModalResizeStart"
|
||||
/>
|
||||
</div>
|
||||
<template #footer>
|
||||
<a-button @click="resetImageAnalyzeModal">取消</a-button>
|
||||
@@ -215,8 +281,10 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, onMounted, onUnmounted, reactive, ref, watch } from 'vue';
|
||||
import { computed, nextTick, onMounted, onUnmounted, reactive, ref, watch } from 'vue';
|
||||
import { useDebounceFn, useResizeObserver } from '@vueuse/core';
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
import { Icon } from '/@/components/Icon';
|
||||
import { useMessage } from '/@/hooks/web/useMessage';
|
||||
import { add, analyzeImageForNativeFile, edit, queryByCode, queryById } from '../printTemplate.api';
|
||||
import DesignerCanvas from './components/DesignerCanvas.vue';
|
||||
@@ -225,10 +293,14 @@
|
||||
import PropertiesPanel from './components/PropertiesPanel.vue';
|
||||
import ToolbarPalette from './components/ToolbarPalette.vue';
|
||||
import { printHtml } from './core/printService';
|
||||
import { renderNativePrintHtml } from './core/printRenderer';
|
||||
import { renderNativePrintHtml, resolvePrintPageCount } from './core/printRenderer';
|
||||
import { generateNativeMockDataObject } from './core/nativeMockData';
|
||||
import { buildNativeTemplateStylePayload } from './core/nativeTemplateStyleSerialize';
|
||||
import { normalizeImportedNativeSchema } from './core/nativeSchemaNormalize';
|
||||
import {
|
||||
applyDetailTableMultiHeaderFalsePositiveFix,
|
||||
applyStackedHeaderBandLayoutFix,
|
||||
normalizeImportedNativeSchema,
|
||||
} from './core/nativeSchemaNormalize';
|
||||
import { normalizeFreeTableAnchors } from './core/freeTableGrid';
|
||||
import { scaleFreeTableTracks } from './core/freeTableTracks';
|
||||
import { useDesignerStore } from './core/useDesignerStore';
|
||||
@@ -286,6 +358,333 @@
|
||||
const imageAnalyzeDragover = ref(false);
|
||||
const imageAnalyzeFileInputRef = ref<HTMLInputElement | null>(null);
|
||||
|
||||
/** 图片分析弹窗:宽度与内容区最大高度(可拖动右下角调整) */
|
||||
const IMAGE_ANALYZE_MODAL_W_MIN = 680;
|
||||
const IMAGE_ANALYZE_MODAL_W_MAX = 1680;
|
||||
const IMAGE_ANALYZE_BODY_H_MIN = 400;
|
||||
const IMAGE_ANALYZE_BODY_H_MAX = 960;
|
||||
const IMAGE_ANALYZE_MM_TO_CSS_PX = 96 / 25.4;
|
||||
const IMAGE_ANALYZE_ZOOM_STEP = 1.12;
|
||||
const IMAGE_ANALYZE_ZOOM_MULT_MIN = 0.25;
|
||||
const IMAGE_ANALYZE_ZOOM_MULT_MAX = 4;
|
||||
|
||||
const imageAnalyzeModalWidth = ref(1000);
|
||||
const imageAnalyzeModalBodyMaxHeight = ref(760);
|
||||
const imageAnalyzeIframeRef = ref<HTMLIFrameElement | null>(null);
|
||||
const imageAnalyzePreviewHostRef = ref<HTMLElement | null>(null);
|
||||
/** iframe 内实测内容宽高,用于 autoPage 等超出理论纸张时撑开预览 */
|
||||
const imageAnalyzeContentMeasurePx = ref({ w: 0, h: 0 });
|
||||
/** 相对「适应预览区」的倍数,1 表示与自动适应一致 */
|
||||
const imageAnalyzeZoomMult = ref(1);
|
||||
const imageAnalyzeAutoFitScale = ref(1);
|
||||
|
||||
const imageAnalyzeModalBodyStyle = computed(() => ({
|
||||
padding: '12px 16px 22px',
|
||||
maxHeight: `${imageAnalyzeModalBodyMaxHeight.value}px`,
|
||||
overflow: 'auto',
|
||||
position: 'relative',
|
||||
}));
|
||||
|
||||
const imageAnalyzePreviewHostMaxHeight = computed(() =>
|
||||
Math.max(200, imageAnalyzeModalBodyMaxHeight.value - 280),
|
||||
);
|
||||
|
||||
function getImageAnalyzeMockObject(): Record<string, any> {
|
||||
try {
|
||||
return JSON.parse(imageAnalyzeMockJson.value || '{}');
|
||||
} catch (_e) {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
/** 遍历 iframe 文档估算真实内容包围盒(与列表预览一致思路) */
|
||||
function measureImageAnalyzeIframeContentBox(doc: Document): { w: number; h: number } {
|
||||
const body = doc.body;
|
||||
let minTop = Infinity;
|
||||
let maxBottom = 0;
|
||||
let minLeft = Infinity;
|
||||
let maxRight = 0;
|
||||
const visit = (el: Element) => {
|
||||
if (el instanceof HTMLElement) {
|
||||
const r = el.getBoundingClientRect();
|
||||
if (r.height > 0.5 && r.width > 0.5) {
|
||||
minTop = Math.min(minTop, r.top);
|
||||
maxBottom = Math.max(maxBottom, r.bottom);
|
||||
minLeft = Math.min(minLeft, r.left);
|
||||
maxRight = Math.max(maxRight, r.right);
|
||||
}
|
||||
}
|
||||
for (let i = 0; i < el.children.length; i++) {
|
||||
visit(el.children[i]);
|
||||
}
|
||||
};
|
||||
visit(body);
|
||||
const byRect =
|
||||
Number.isFinite(minTop) && maxBottom > 0
|
||||
? { w: Math.ceil(maxRight - minLeft + 6), h: Math.ceil(maxBottom - minTop + 12) }
|
||||
: { w: 0, h: 0 };
|
||||
const sh = Math.max(doc.documentElement.scrollHeight, body.scrollHeight, byRect.h);
|
||||
const sw = Math.max(doc.documentElement.scrollWidth, body.scrollWidth, byRect.w);
|
||||
return { w: Math.ceil(sw), h: Math.ceil(sh) };
|
||||
}
|
||||
|
||||
const imageAnalyzePaperPixelSize = computed(() => {
|
||||
const schema = imageAnalyzePendingSchema.value;
|
||||
if (!schema) {
|
||||
return null;
|
||||
}
|
||||
const mock = getImageAnalyzeMockObject();
|
||||
const pageCount = Math.max(1, resolvePrintPageCount(schema, mock));
|
||||
const wMm = Number(schema.page?.width || 210);
|
||||
const hMm = Number(schema.page?.height || 297);
|
||||
const wPx = wMm * IMAGE_ANALYZE_MM_TO_CSS_PX;
|
||||
const hPx = hMm * pageCount * IMAGE_ANALYZE_MM_TO_CSS_PX;
|
||||
return { wPx, hPx, pageCount };
|
||||
});
|
||||
|
||||
const imageAnalyzeLayoutPaperPx = computed(() => {
|
||||
const ps = imageAnalyzePaperPixelSize.value;
|
||||
if (!ps) {
|
||||
return null;
|
||||
}
|
||||
const m = imageAnalyzeContentMeasurePx.value;
|
||||
return {
|
||||
wPx: Math.max(ps.wPx, m.w || 0),
|
||||
hPx: Math.max(ps.hPx, m.h || 0),
|
||||
};
|
||||
});
|
||||
|
||||
const imageAnalyzeDisplayScale = computed(() => {
|
||||
const raw = imageAnalyzeAutoFitScale.value * imageAnalyzeZoomMult.value;
|
||||
if (!Number.isFinite(raw) || raw <= 0) {
|
||||
return 1;
|
||||
}
|
||||
return Math.min(4, Math.max(0.08, raw));
|
||||
});
|
||||
|
||||
const imageAnalyzeZoomPercentLabel = computed(() => `${Math.round(imageAnalyzeZoomMult.value * 100)}%`);
|
||||
|
||||
function computeImageAnalyzeAutoFitScale() {
|
||||
const host = imageAnalyzePreviewHostRef.value;
|
||||
const ps = imageAnalyzeLayoutPaperPx.value;
|
||||
if (!host || !ps) {
|
||||
imageAnalyzeAutoFitScale.value = 1;
|
||||
return;
|
||||
}
|
||||
const pad = 16;
|
||||
const availW = Math.max(0, host.clientWidth - pad);
|
||||
const availH = Math.max(0, host.clientHeight - pad);
|
||||
if (availW <= 0 || availH <= 0 || ps.wPx <= 0 || ps.hPx <= 0) {
|
||||
imageAnalyzeAutoFitScale.value = 1;
|
||||
return;
|
||||
}
|
||||
const raw = Math.min(availW / ps.wPx, availH / ps.hPx, 1) * 0.96;
|
||||
imageAnalyzeAutoFitScale.value = Number.isFinite(raw) && raw > 0 ? raw : 1;
|
||||
}
|
||||
|
||||
const debouncedComputeImageAnalyzeScale = useDebounceFn(() => {
|
||||
computeImageAnalyzeAutoFitScale();
|
||||
}, 120);
|
||||
|
||||
function onImageAnalyzeIframeLoad() {
|
||||
void nextTick(() => {
|
||||
requestAnimationFrame(() => {
|
||||
requestAnimationFrame(() => {
|
||||
const doc = imageAnalyzeIframeRef.value?.contentDocument;
|
||||
if (!doc?.body) {
|
||||
imageAnalyzeContentMeasurePx.value = { w: 0, h: 0 };
|
||||
debouncedComputeImageAnalyzeScale();
|
||||
return;
|
||||
}
|
||||
imageAnalyzeContentMeasurePx.value = measureImageAnalyzeIframeContentBox(doc);
|
||||
debouncedComputeImageAnalyzeScale();
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function imageAnalyzeZoomIn() {
|
||||
imageAnalyzeZoomMult.value = Math.min(IMAGE_ANALYZE_ZOOM_MULT_MAX, imageAnalyzeZoomMult.value * IMAGE_ANALYZE_ZOOM_STEP);
|
||||
}
|
||||
|
||||
function imageAnalyzeZoomOut() {
|
||||
imageAnalyzeZoomMult.value = Math.max(IMAGE_ANALYZE_ZOOM_MULT_MIN, imageAnalyzeZoomMult.value / IMAGE_ANALYZE_ZOOM_STEP);
|
||||
}
|
||||
|
||||
function imageAnalyzeZoomFit() {
|
||||
imageAnalyzeZoomMult.value = 1;
|
||||
debouncedComputeImageAnalyzeScale();
|
||||
}
|
||||
|
||||
function resetImageAnalyzeModalLayout() {
|
||||
const vh = typeof window !== 'undefined' ? window.innerHeight : 900;
|
||||
imageAnalyzeModalWidth.value = 1000;
|
||||
imageAnalyzeModalBodyMaxHeight.value = Math.min(
|
||||
IMAGE_ANALYZE_BODY_H_MAX,
|
||||
Math.max(IMAGE_ANALYZE_BODY_H_MIN, vh - 140),
|
||||
);
|
||||
imageAnalyzeZoomMult.value = 1;
|
||||
imageAnalyzeAutoFitScale.value = 1;
|
||||
imageAnalyzeContentMeasurePx.value = { w: 0, h: 0 };
|
||||
}
|
||||
|
||||
function clamp(n: number, lo: number, hi: number) {
|
||||
return Math.min(hi, Math.max(lo, n));
|
||||
}
|
||||
|
||||
function onImageAnalyzeModalResizeStart(e: MouseEvent) {
|
||||
if (e.button !== 0) {
|
||||
return;
|
||||
}
|
||||
const startX = e.clientX;
|
||||
const startY = e.clientY;
|
||||
const startW = imageAnalyzeModalWidth.value;
|
||||
const startBh = imageAnalyzeModalBodyMaxHeight.value;
|
||||
const vh = typeof window !== 'undefined' ? window.innerHeight : 900;
|
||||
const maxBody = Math.min(IMAGE_ANALYZE_BODY_H_MAX, Math.max(IMAGE_ANALYZE_BODY_H_MIN, vh - 100));
|
||||
function move(ev: MouseEvent) {
|
||||
const dx = ev.clientX - startX;
|
||||
const dy = ev.clientY - startY;
|
||||
imageAnalyzeModalWidth.value = Math.round(clamp(startW + dx, IMAGE_ANALYZE_MODAL_W_MIN, IMAGE_ANALYZE_MODAL_W_MAX));
|
||||
imageAnalyzeModalBodyMaxHeight.value = Math.round(clamp(startBh + dy, IMAGE_ANALYZE_BODY_H_MIN, maxBody));
|
||||
debouncedComputeImageAnalyzeScale();
|
||||
}
|
||||
function up() {
|
||||
document.removeEventListener('mousemove', move);
|
||||
document.removeEventListener('mouseup', up);
|
||||
}
|
||||
document.addEventListener('mousemove', move);
|
||||
document.addEventListener('mouseup', up);
|
||||
}
|
||||
|
||||
let imageAnalyzeDragTeardown: (() => void) | null = null;
|
||||
|
||||
function teardownImageAnalyzeModalDrag() {
|
||||
imageAnalyzeDragTeardown?.();
|
||||
imageAnalyzeDragTeardown = null;
|
||||
}
|
||||
|
||||
function findImageAnalyzeModalWrap(): HTMLElement | null {
|
||||
const list = document.querySelectorAll('.ant-modal-wrap.native-print-image-analyze-modal');
|
||||
for (let i = list.length - 1; i >= 0; i--) {
|
||||
const el = list[i] as HTMLElement;
|
||||
if (getComputedStyle(el).display !== 'none') {
|
||||
return el;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/** 标题栏拖动整个弹窗(仅作用于本弹窗 wrap) */
|
||||
function setupImageAnalyzeModalDrag() {
|
||||
teardownImageAnalyzeModalDrag();
|
||||
const wrap = findImageAnalyzeModalWrap();
|
||||
if (!wrap) {
|
||||
return;
|
||||
}
|
||||
const dialogHeaderEl = wrap.querySelector('.ant-modal-header') as HTMLElement | null;
|
||||
const dragDom = wrap.querySelector('.ant-modal') as HTMLElement | null;
|
||||
if (!dialogHeaderEl || !dragDom) {
|
||||
return;
|
||||
}
|
||||
dialogHeaderEl.style.cursor = 'move';
|
||||
const getStyle = (dom: HTMLElement, attr: string) => getComputedStyle(dom)[attr as any] as string;
|
||||
|
||||
const onMouseDown = (e: MouseEvent) => {
|
||||
if (e.button !== 0) {
|
||||
return;
|
||||
}
|
||||
const disX = e.clientX;
|
||||
const disY = e.clientY;
|
||||
const screenWidth = document.body.clientWidth;
|
||||
const screenHeight = document.documentElement.clientHeight;
|
||||
const dragDomWidth = dragDom.offsetWidth;
|
||||
const dragDomheight = dragDom.offsetHeight;
|
||||
const minDragDomLeft = dragDom.offsetLeft;
|
||||
const maxDragDomLeft = screenWidth - dragDom.offsetLeft - dragDomWidth;
|
||||
const minDragDomTop = dragDom.offsetTop;
|
||||
let maxDragDomTop = screenHeight - dragDom.offsetTop - dragDomheight;
|
||||
if (maxDragDomTop < 0) {
|
||||
maxDragDomTop = screenHeight - dragDom.offsetTop;
|
||||
}
|
||||
const domLeft = getStyle(dragDom, 'left');
|
||||
const domTop = getStyle(dragDom, 'top');
|
||||
let styL = +domLeft;
|
||||
let styT = +domTop;
|
||||
if (domLeft.includes('%')) {
|
||||
styL = +document.body.clientWidth * (+domLeft.replace(/%/g, '') / 100);
|
||||
styT = +document.body.clientHeight * (+domTop.replace(/%/g, '') / 100);
|
||||
} else {
|
||||
styL = +String(domLeft).replace(/px/g, '');
|
||||
styT = +String(domTop).replace(/px/g, '');
|
||||
}
|
||||
if (!Number.isFinite(styL)) {
|
||||
styL = dragDom.offsetLeft || 0;
|
||||
}
|
||||
if (!Number.isFinite(styT)) {
|
||||
styT = dragDom.offsetTop || 0;
|
||||
}
|
||||
|
||||
function onMove(ev: MouseEvent) {
|
||||
let left = ev.clientX - disX;
|
||||
let top = ev.clientY - disY;
|
||||
if (-left > minDragDomLeft) {
|
||||
left = -minDragDomLeft;
|
||||
} else if (left > maxDragDomLeft) {
|
||||
left = maxDragDomLeft;
|
||||
}
|
||||
if (-top > minDragDomTop) {
|
||||
top = -minDragDomTop;
|
||||
} else if (top > maxDragDomTop) {
|
||||
top = maxDragDomTop;
|
||||
}
|
||||
dragDom.style.left = `${left + styL}px`;
|
||||
dragDom.style.top = `${top + styT}px`;
|
||||
}
|
||||
function onUp() {
|
||||
document.removeEventListener('mousemove', onMove);
|
||||
document.removeEventListener('mouseup', onUp);
|
||||
}
|
||||
document.addEventListener('mousemove', onMove);
|
||||
document.addEventListener('mouseup', onUp);
|
||||
};
|
||||
|
||||
dialogHeaderEl.addEventListener('mousedown', onMouseDown);
|
||||
imageAnalyzeDragTeardown = () => {
|
||||
dialogHeaderEl.removeEventListener('mousedown', onMouseDown);
|
||||
dialogHeaderEl.style.cursor = '';
|
||||
};
|
||||
}
|
||||
|
||||
useResizeObserver(imageAnalyzePreviewHostRef, () => {
|
||||
if (imageAnalyzePreviewHtml.value) {
|
||||
debouncedComputeImageAnalyzeScale();
|
||||
}
|
||||
});
|
||||
|
||||
watch(
|
||||
() => imageAnalyzeVisible.value,
|
||||
(v) => {
|
||||
if (v) {
|
||||
void nextTick(() => {
|
||||
setTimeout(() => setupImageAnalyzeModalDrag(), 40);
|
||||
setTimeout(() => debouncedComputeImageAnalyzeScale(), 100);
|
||||
});
|
||||
} else {
|
||||
teardownImageAnalyzeModalDrag();
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
watch(
|
||||
() => [imageAnalyzePreviewHtml.value, imageAnalyzeLayoutPaperPx.value?.wPx] as const,
|
||||
() => {
|
||||
if (imageAnalyzePreviewHtml.value && imageAnalyzeLayoutPaperPx.value) {
|
||||
void nextTick(() => debouncedComputeImageAnalyzeScale());
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
/** 左侧工具栏宽度(px),0 表示隐藏 */
|
||||
const LS_LEFT_PANEL_KEY = 'qhmes-native-print-left-panel-w';
|
||||
const LEFT_PANEL_MIN = 260;
|
||||
@@ -1083,6 +1482,8 @@
|
||||
imageAnalyzeMockJson.value = '';
|
||||
imageAnalyzeDragover.value = false;
|
||||
imageAnalyzeLoading.value = false;
|
||||
imageAnalyzeContentMeasurePx.value = { w: 0, h: 0 };
|
||||
imageAnalyzeZoomMult.value = 1;
|
||||
resetImageAnalyzeProgressUi();
|
||||
}
|
||||
|
||||
@@ -1094,6 +1495,7 @@
|
||||
|
||||
function openImageAnalyzeModal() {
|
||||
clearImageAnalyzeState();
|
||||
resetImageAnalyzeModalLayout();
|
||||
imageAnalyzeVisible.value = true;
|
||||
}
|
||||
|
||||
@@ -1170,7 +1572,9 @@
|
||||
imageAnalyzeProgress.value = Math.max(imageAnalyzeProgress.value, 44);
|
||||
imageAnalyzeProgressTip.value = '正在解析返回结果…';
|
||||
|
||||
const parsed = normalizeImportedNativeSchema(JSON.parse(res.templateJson));
|
||||
const parsed = applyDetailTableMultiHeaderFalsePositiveFix(
|
||||
applyStackedHeaderBandLayoutFix(normalizeImportedNativeSchema(JSON.parse(res.templateJson))),
|
||||
);
|
||||
imageAnalyzePendingSchema.value = parsed;
|
||||
imageAnalyzeMockJson.value = res.mockDataJson || '{}';
|
||||
const extra = res.aiUsed ? '(已调用视觉大模型)' : '(未调用大模型或已回退占位)';
|
||||
@@ -1260,6 +1664,7 @@
|
||||
|
||||
onUnmounted(() => {
|
||||
stopLeftPanelResize();
|
||||
teardownImageAnalyzeModalDrag();
|
||||
invalidatePendingImageAnalyze();
|
||||
clearImageAnalyzeProgressTimer();
|
||||
imageAnalyzeLoading.value = false;
|
||||
@@ -1577,7 +1982,27 @@
|
||||
border: 1px solid #f0f0f0;
|
||||
}
|
||||
|
||||
.image-analyze-modal-title {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: baseline;
|
||||
gap: 8px 12px;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.image-analyze-modal-title-text {
|
||||
font-weight: 600;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.image-analyze-modal-title-tip {
|
||||
font-size: 12px;
|
||||
font-weight: 400;
|
||||
color: rgba(0, 0, 0, 0.45);
|
||||
}
|
||||
|
||||
.image-analyze-body {
|
||||
position: relative;
|
||||
min-height: 120px;
|
||||
}
|
||||
|
||||
@@ -1654,6 +2079,10 @@
|
||||
flex: 0 0 200px;
|
||||
max-width: 100%;
|
||||
|
||||
.thumb-label {
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
img {
|
||||
max-width: 100%;
|
||||
max-height: 280px;
|
||||
@@ -1667,19 +2096,107 @@
|
||||
.image-analyze-preview-frame-wrap {
|
||||
flex: 1;
|
||||
min-width: 260px;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.image-analyze-preview-toolbar {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 8px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.image-analyze-zoom-btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.image-analyze-zoom-pct {
|
||||
font-size: 12px;
|
||||
color: rgba(0, 0, 0, 0.55);
|
||||
min-width: 40px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.image-analyze-preview-host {
|
||||
overflow: auto;
|
||||
border: 1px solid #f0f0f0;
|
||||
border-radius: 6px;
|
||||
background: #fafafa;
|
||||
min-height: 160px;
|
||||
}
|
||||
|
||||
.image-analyze-preview-scroll {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: flex-start;
|
||||
padding: 10px;
|
||||
min-width: 100%;
|
||||
min-height: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.image-analyze-zoom-slot {
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
.image-analyze-scale-shim {
|
||||
position: relative;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.image-analyze-scale-inner {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
transform-origin: 0 0;
|
||||
will-change: transform;
|
||||
}
|
||||
|
||||
.thumb-label {
|
||||
font-size: 12px;
|
||||
color: rgba(0, 0, 0, 0.45);
|
||||
margin-bottom: 6px;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.image-analyze-preview-toolbar .thumb-label {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.image-analyze-preview-frame {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
height: 320px;
|
||||
border: 1px solid #f0f0f0;
|
||||
border-radius: 4px;
|
||||
height: 100%;
|
||||
border: 0;
|
||||
background: #fff;
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
.image-analyze-resize-handle {
|
||||
position: absolute;
|
||||
right: 2px;
|
||||
bottom: 2px;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
cursor: se-resize;
|
||||
z-index: 5;
|
||||
border-radius: 0 0 6px 0;
|
||||
background: linear-gradient(135deg, transparent 50%, rgba(0, 0, 0, 0.12) 50%);
|
||||
}
|
||||
|
||||
.image-analyze-resize-handle:hover {
|
||||
background: linear-gradient(135deg, transparent 45%, rgba(22, 119, 255, 0.35) 45%);
|
||||
}
|
||||
</style>
|
||||
|
||||
<style lang="less">
|
||||
/* 弹窗挂到 body,需非 scoped 才能命中 */
|
||||
.native-print-image-analyze-modal.ant-modal-wrap .ant-modal-header {
|
||||
user-select: none;
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user