Compare commits
No commits in common. "8ba67edc18e70eb9e4b60af1139f1e92dcfce6da" and "e65da3ac8f37890fdf724c3daa276156ca2ade4d" have entirely different histories.
8ba67edc18
...
e65da3ac8f
@ -1,10 +1,6 @@
|
||||
# tool.xpcool.com 变更记录
|
||||
> 倒序:最新在上。格式:YYYY-MM-DD | 类型 | 摘要
|
||||
|
||||
2026-09-01 | CHG | 全站接入图片预览(TDesign ImageViewer):新增共享 AppImageViewer 组件 + useImageViewer 组合式函数;接入 9 处——UploadDrop 缩略图、Base64 还原图、图片裁剪结果、图片转 ICO 预览(多尺寸成组)、OCR 待识别图、二维码结果、图片压缩(原直连 t-image-viewer 重构为共享组件)、代码转图片画布、占位图画布;全局样式新增 .img-previewable
|
||||
|
||||
2026-09-01 | CHG | 图片转 ICO 结果区增强:逐尺寸单独 ICO 下载(buildIco 新增 singles 字段,复用条目数据零额外编码)+ 多尺寸一键打包 ZIP 下载(jszip);另修复 pnpm wrapper 中 node 版本路径(22.22.2 → 22.22.2-2)
|
||||
|
||||
2026-08-31 | CHG | 新增「图片转 ICO」工具:src/utils/ico.ts(纯前端自实现 PNG/BMP DIB/ICO 编码 + 中位切分量化 + 自适应滤波 + 体积上限自动降级),src/views/image-to-ico/ImageToIcoView.vue(尺寸组合/填充/编码方式/量化/体积限制/预览下载/历史),路由注册于 src/router/tools.ts;Pillow 独立解码验证通过
|
||||
|
||||
2026-08-26 | CFG | CHANGELOG 纳入 git 管理(.gitignore 放行 .workbuddy/memory/CHANGELOG.md),中央工作空间迁移至 ../workbuddy.xpcool.com(相对路径索引)
|
||||
|
||||
@ -93,11 +93,3 @@ html[theme-mode='dark'] ::-webkit-scrollbar-thumb {
|
||||
font-size: 14px;
|
||||
color: var(--td-text-color-secondary);
|
||||
}
|
||||
|
||||
/* 可点击放大的图片/画布(配合 AppImageViewer 全站共用):手型光标 + hover 高亮 */
|
||||
.img-previewable {
|
||||
cursor: zoom-in;
|
||||
}
|
||||
.img-previewable:hover {
|
||||
box-shadow: 0 0 0 2px var(--td-brand-color);
|
||||
}
|
||||
|
||||
@ -1,36 +0,0 @@
|
||||
<template>
|
||||
<!-- 全站统一的图片预览器:模态展示、点遮罩可关、ESC 关闭,缩放/下载由 TDesign 内置 -->
|
||||
<t-image-viewer
|
||||
:visible="visible"
|
||||
:images="images"
|
||||
:index="index"
|
||||
:z-index="3000"
|
||||
close-on-overlay
|
||||
@update:visible="emit('update:visible', $event)"
|
||||
@update:index="emit('update:index', $event)"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { PreviewImage } from '@/composables/useImageViewer'
|
||||
|
||||
/**
|
||||
* AppImageViewer:t-image-viewer 的统一薄封装。
|
||||
* 用法(配合 useImageViewer):
|
||||
* <AppImageViewer v-model:visible="visible.value" v-model:index="index.value" :images="images.value" />
|
||||
* 模板中 ref 自动解包,直接写 visible / index 即可。
|
||||
*/
|
||||
defineProps<{
|
||||
/** 是否显示(v-model:visible) */
|
||||
visible: boolean
|
||||
/** 预览图片组 */
|
||||
images: PreviewImage[]
|
||||
/** 当前下标(v-model:index) */
|
||||
index?: number
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
'update:visible': [v: boolean]
|
||||
'update:index': [i: number]
|
||||
}>()
|
||||
</script>
|
||||
@ -33,14 +33,7 @@
|
||||
<!-- 已选文件列表(图片自动生成缩略图回显) -->
|
||||
<div v-else class="file-list" @click.stop>
|
||||
<div v-for="(f, i) in props.files" :key="`${f.name}-${f.size}-${i}`" class="file-item">
|
||||
<img
|
||||
v-if="previews[i]"
|
||||
:src="previews[i]"
|
||||
alt=""
|
||||
class="thumb thumb-clickable"
|
||||
title="点击预览大图"
|
||||
@click.stop="openPreview(i)"
|
||||
/>
|
||||
<img v-if="previews[i]" :src="previews[i]" alt="" class="thumb" />
|
||||
<span v-else class="thumb thumb-icon"><FileIcon /></span>
|
||||
<span class="meta">
|
||||
<span class="name" :title="f.name">{{ f.name }}</span>
|
||||
@ -52,9 +45,6 @@
|
||||
</div>
|
||||
<p v-if="props.multiple && props.files.length < props.max" class="add-more">点击可继续添加文件</p>
|
||||
</div>
|
||||
|
||||
<!-- 点击缩略图放大预览(TDesign ImageViewer) -->
|
||||
<AppImageViewer v-model:visible="visible" v-model:index="index" :images="images" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@ -62,8 +52,6 @@
|
||||
import { onBeforeUnmount, ref, watch } from 'vue'
|
||||
import { DeleteIcon, FileIcon, UploadIcon } from 'tdesign-icons-vue-next'
|
||||
import { formatBytes } from '@/utils'
|
||||
import AppImageViewer from '@/components/common/AppImageViewer.vue'
|
||||
import { useImageViewer } from '@/composables/useImageViewer'
|
||||
|
||||
/**
|
||||
* 通用拖拽上传组件(替代 t-upload 文件模式):
|
||||
@ -97,14 +85,6 @@ const dragging = ref(false)
|
||||
/** 图片缩略图的对象 URL(与 files 一一对应,非图片为空串) */
|
||||
const previews = ref<string[]>([])
|
||||
|
||||
/** 点击缩略图放大预览:图片组取所有非空缩略图,定位到当前点击项 */
|
||||
const { visible, index, images, open } = useImageViewer()
|
||||
function openPreview(i: number) {
|
||||
// 计算「当前缩略图在所有图片缩略图中的序号」,保证预览定位正确
|
||||
const imgIdx = previews.value.slice(0, i + 1).filter(Boolean).length - 1
|
||||
open(previews.value.filter(Boolean), imgIdx)
|
||||
}
|
||||
|
||||
// 深度计数:避免拖过子元素时 dragleave 导致高亮闪烁
|
||||
let dragDepth = 0
|
||||
|
||||
@ -272,14 +252,6 @@ function removeAt(i: number) {
|
||||
color: var(--td-text-color-placeholder);
|
||||
background: var(--td-bg-color-secondarycontainer);
|
||||
}
|
||||
/* 可点击预览的缩略图 */
|
||||
.thumb-clickable {
|
||||
cursor: zoom-in;
|
||||
transition: transform 0.15s;
|
||||
}
|
||||
.thumb-clickable:hover {
|
||||
transform: scale(1.06);
|
||||
}
|
||||
.meta {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
|
||||
@ -1,36 +0,0 @@
|
||||
import { ref } from 'vue'
|
||||
|
||||
/** 可预览的图片:URL 字符串或 File 对象(TDesign ImageViewer 原生支持两种) */
|
||||
export type PreviewImage = string | File
|
||||
|
||||
/**
|
||||
* 图片预览组合式函数:全站各工具页共用。
|
||||
* 典型用法:
|
||||
* const { visible, index, images, open } = useImageViewer()
|
||||
* <AppImageViewer v-model:visible="visible.value" ... />(模板中 ref 自动解包)
|
||||
* 点击图片时 open(urls, i) 打开预览。
|
||||
*/
|
||||
export function useImageViewer() {
|
||||
/** 是否显示预览 */
|
||||
const visible = ref(false)
|
||||
/** 当前预览的下标 */
|
||||
const index = ref(0)
|
||||
/** 预览的图片组(支持左右切换) */
|
||||
const images = ref<PreviewImage[]>([])
|
||||
|
||||
/** 打开预览:传入图片组与起始下标 */
|
||||
function open(imgs: PreviewImage[], startIndex = 0) {
|
||||
const list = imgs.filter(Boolean)
|
||||
if (!list.length) return
|
||||
images.value = list
|
||||
index.value = Math.min(Math.max(startIndex, 0), list.length - 1)
|
||||
visible.value = true
|
||||
}
|
||||
|
||||
/** 关闭预览(一般由组件内部关闭,无需手动调用) */
|
||||
function close() {
|
||||
visible.value = false
|
||||
}
|
||||
|
||||
return { visible, index, images, open, close }
|
||||
}
|
||||
@ -55,8 +55,6 @@ export interface IcoBuildResult {
|
||||
quantized: boolean
|
||||
/** 各尺寸的预览图(DataURL),供页面回显 */
|
||||
previews: Array<{ size: number; url: string }>
|
||||
/** 每个尺寸单独封装的 ICO 文件(与合成包内条目数据一致,用于逐尺寸下载 / 打包 ZIP) */
|
||||
singles: Array<{ size: number; blob: Blob }>
|
||||
}
|
||||
|
||||
/** ICO 内部单条目:尺寸 + 编码后的图像数据 */
|
||||
@ -729,15 +727,12 @@ export async function buildIco(source: HTMLImageElement, params: IcoBuildParams)
|
||||
|
||||
const blob = encodeIco(encoded.images)
|
||||
const previews = active.map((size) => ({ size, url: imageDataToDataUrl(rendered.get(size) as ImageData) }))
|
||||
// 单尺寸 ICO 直接复用合成包里已编码的条目数据,只多包一层 6+16 字节的头部,几乎零开销
|
||||
const singles = encoded.images.map((img) => ({ size: img.size, blob: encodeIco([img]) }))
|
||||
return {
|
||||
blob,
|
||||
entries: encoded.meta,
|
||||
droppedSizes: sizes.filter((s) => !active.includes(s)),
|
||||
quantized: quantize,
|
||||
previews,
|
||||
singles,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -61,20 +61,11 @@
|
||||
<ErrorAlert :message="b64Error" @close="b64Error = ''" />
|
||||
|
||||
<t-card v-if="imgUrl" class="tool-section" title="转换结果">
|
||||
<img
|
||||
:src="imgUrl"
|
||||
alt="Base64 还原的图片"
|
||||
class="result-img img-previewable"
|
||||
title="点击预览大图"
|
||||
@click="openViewer([imgUrl])"
|
||||
/>
|
||||
<img :src="imgUrl" alt="Base64 还原的图片" class="result-img" />
|
||||
<div class="form-actions">
|
||||
<DownloadButton :filename="imgFilename" :get-blob="getImgBlob" label="下载图片" />
|
||||
</div>
|
||||
</t-card>
|
||||
|
||||
<!-- 结果图放大预览 -->
|
||||
<AppImageViewer v-model:visible="viewerVisible" v-model:index="viewerIndex" :images="viewerImages" />
|
||||
</t-tab-panel>
|
||||
</t-tabs>
|
||||
|
||||
@ -95,8 +86,6 @@ import CopyButton from '@/components/common/CopyButton.vue'
|
||||
import DownloadButton from '@/components/common/DownloadButton.vue'
|
||||
import HistoryPanel from '@/components/common/HistoryPanel.vue'
|
||||
import { useHistory } from '@/composables/useHistory'
|
||||
import { useImageViewer } from '@/composables/useImageViewer'
|
||||
import AppImageViewer from '@/components/common/AppImageViewer.vue'
|
||||
import { dataUrlToBlob, fileToDataUrl } from '@/utils'
|
||||
|
||||
const tab = ref('text')
|
||||
@ -108,9 +97,6 @@ const error = ref('')
|
||||
|
||||
const history = useHistory('base64')
|
||||
|
||||
/** 结果图放大预览(TDesign ImageViewer) */
|
||||
const { visible: viewerVisible, index: viewerIndex, images: viewerImages, open: openViewer } = useImageViewer()
|
||||
|
||||
/** 编码:UTF-8 安全的 base64(先转字节再 btoa,避免中文报错) */
|
||||
function encodeText() {
|
||||
error.value = ''
|
||||
|
||||
@ -35,7 +35,7 @@
|
||||
|
||||
<t-card v-if="rendered" class="tool-section" title="预览">
|
||||
<div class="canvas-wrap">
|
||||
<canvas ref="canvasRef" class="code-canvas img-previewable" title="点击预览大图" @click="previewCanvas" />
|
||||
<canvas ref="canvasRef" class="code-canvas" />
|
||||
</div>
|
||||
<div class="form-actions">
|
||||
<t-button theme="primary" @click="downloadPng">
|
||||
@ -44,9 +44,6 @@
|
||||
</t-button>
|
||||
</div>
|
||||
</t-card>
|
||||
|
||||
<!-- 生成图放大预览 -->
|
||||
<AppImageViewer v-model:visible="viewerVisible" v-model:index="viewerIndex" :images="viewerImages" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@ -54,9 +51,7 @@
|
||||
import { ref } from 'vue'
|
||||
import { DownloadIcon } from 'tdesign-icons-vue-next'
|
||||
import ErrorAlert from '@/components/common/ErrorAlert.vue'
|
||||
import AppImageViewer from '@/components/common/AppImageViewer.vue'
|
||||
import { downloadDataUrl } from '@/composables/useDownload'
|
||||
import { useImageViewer } from '@/composables/useImageViewer'
|
||||
|
||||
const LANGS = ['javascript', 'typescript', 'python', 'go', 'html', 'css', 'json', 'sql'].map((l) => ({
|
||||
label: l,
|
||||
@ -70,13 +65,6 @@ const width = ref(800)
|
||||
const filename = ref('demo.js')
|
||||
const error = ref('')
|
||||
const rendered = ref(false)
|
||||
|
||||
/** 点击画布放大预览(把当前画布导出为 PNG 后交给 ImageViewer) */
|
||||
const { visible: viewerVisible, index: viewerIndex, images: viewerImages, open: openViewer } = useImageViewer()
|
||||
function previewCanvas() {
|
||||
const canvas = canvasRef.value
|
||||
if (canvas) openViewer([canvas.toDataURL('image/png')])
|
||||
}
|
||||
const canvasRef = ref<HTMLCanvasElement | null>(null)
|
||||
|
||||
const SAMPLE = `// 示例代码
|
||||
|
||||
@ -108,8 +108,14 @@
|
||||
</div>
|
||||
</t-card>
|
||||
|
||||
<!-- 原图 / 压缩图放大预览(统一使用共享 AppImageViewer) -->
|
||||
<AppImageViewer v-model:visible="viewerVisible" v-model:index="viewerIndex" :images="viewerImages" />
|
||||
<!-- 原图 / 压缩图放大预览(v-if 保证每次打开都从 defaultIndex 开始) -->
|
||||
<t-image-viewer
|
||||
v-if="viewerVisible"
|
||||
v-model="viewerVisible"
|
||||
:images="viewerImages"
|
||||
:default-index="viewerIndex"
|
||||
:close-on-overlay="true"
|
||||
/>
|
||||
|
||||
<HistoryPanel
|
||||
:items="history.items"
|
||||
@ -130,9 +136,7 @@ import JSZip from 'jszip'
|
||||
import UploadDrop from '@/components/common/UploadDrop.vue'
|
||||
import ErrorAlert from '@/components/common/ErrorAlert.vue'
|
||||
import HistoryPanel from '@/components/common/HistoryPanel.vue'
|
||||
import AppImageViewer from '@/components/common/AppImageViewer.vue'
|
||||
import { useHistory } from '@/composables/useHistory'
|
||||
import { useImageViewer } from '@/composables/useImageViewer'
|
||||
import { downloadBlob } from '@/composables/useDownload'
|
||||
import { formatBytes, genFilename } from '@/utils'
|
||||
|
||||
@ -184,8 +188,10 @@ const error = ref('')
|
||||
const doneCount = ref(0)
|
||||
const results = ref<CompressResult[]>([])
|
||||
|
||||
/** 放大预览:统一走共享 useImageViewer,展示「原图 + 压缩图」,从点击的那张开始 */
|
||||
const { visible: viewerVisible, index: viewerIndex, images: viewerImages, open: openViewer } = useImageViewer()
|
||||
/** 放大预览状态:展示「原图 + 压缩图」,从点击的那张开始 */
|
||||
const viewerVisible = ref(false)
|
||||
const viewerImages = ref<string[]>([])
|
||||
const viewerIndex = ref(0)
|
||||
|
||||
/** 历史记录:保存「张数 | 压缩方式 | 格式 | 尺寸」摘要,点击可恢复压缩参数 */
|
||||
const history = useHistory('image-compress')
|
||||
@ -335,7 +341,9 @@ function downloadOne(r: CompressResult) {
|
||||
|
||||
/** 放大预览:优先展示「原图 + 压缩图」两张并可左右切换,失败时只有原图 */
|
||||
function openPreview(r: CompressResult, index: number) {
|
||||
openViewer(r.outUrl ? [r.sourceUrl, r.outUrl] : [r.sourceUrl], r.outUrl ? index : 0)
|
||||
viewerImages.value = r.outUrl ? [r.sourceUrl, r.outUrl] : [r.sourceUrl]
|
||||
viewerIndex.value = r.outUrl ? index : 0
|
||||
viewerVisible.value = true
|
||||
}
|
||||
|
||||
/** 全部结果打包为 ZIP 下载(jszip,纯本地生成) */
|
||||
|
||||
@ -26,21 +26,12 @@
|
||||
<ErrorAlert :message="error" @close="error = ''" />
|
||||
|
||||
<t-card v-if="outUrl" class="tool-section" title="裁剪结果">
|
||||
<img
|
||||
:src="outUrl"
|
||||
alt="裁剪结果预览"
|
||||
class="result-img img-previewable"
|
||||
title="点击预览大图"
|
||||
@click="openViewer([outUrl])"
|
||||
/>
|
||||
<img :src="outUrl" alt="裁剪结果预览" class="result-img" />
|
||||
<div class="form-actions">
|
||||
<DownloadButton :filename="outName" :get-blob="getOutBlob" label="下载裁剪图" />
|
||||
</div>
|
||||
</t-card>
|
||||
|
||||
<!-- 结果图放大预览 -->
|
||||
<AppImageViewer v-model:visible="viewerVisible" v-model:index="viewerIndex" :images="viewerImages" />
|
||||
|
||||
<HistoryPanel
|
||||
:items="history.items"
|
||||
@select="onHistorySelect"
|
||||
@ -59,8 +50,6 @@ import ErrorAlert from '@/components/common/ErrorAlert.vue'
|
||||
import DownloadButton from '@/components/common/DownloadButton.vue'
|
||||
import HistoryPanel from '@/components/common/HistoryPanel.vue'
|
||||
import { useHistory } from '@/composables/useHistory'
|
||||
import { useImageViewer } from '@/composables/useImageViewer'
|
||||
import AppImageViewer from '@/components/common/AppImageViewer.vue'
|
||||
import { dataUrlToBlob, genFilename } from '@/utils'
|
||||
|
||||
/** 比例选项:'free' 表示自由裁剪 */
|
||||
@ -89,9 +78,6 @@ let cropper: Cropper | null = null
|
||||
|
||||
const history = useHistory('image-cropper')
|
||||
|
||||
/** 结果图放大预览(TDesign ImageViewer) */
|
||||
const { visible: viewerVisible, index: viewerIndex, images: viewerImages, open: openViewer } = useImageViewer()
|
||||
|
||||
const outName = computed(() => genFilename('cropped', outType.value.split('/')[1]))
|
||||
|
||||
function ratioValue(v: string): number {
|
||||
|
||||
@ -92,14 +92,7 @@
|
||||
<t-card v-if="result" class="tool-section" title="转换结果">
|
||||
<div class="result-head">
|
||||
<div class="ico-preview checker">
|
||||
<img
|
||||
v-if="icoUrl"
|
||||
:src="icoUrl"
|
||||
alt="ICO 预览"
|
||||
class="img-previewable"
|
||||
title="点击预览大图"
|
||||
@click="openViewer(previewUrls, previewUrls.length - 1)"
|
||||
/>
|
||||
<img v-if="icoUrl" :src="icoUrl" alt="ICO 预览" />
|
||||
</div>
|
||||
<div class="result-summary">
|
||||
<p class="summary-line">
|
||||
@ -117,56 +110,36 @@
|
||||
</p>
|
||||
</div>
|
||||
<div class="result-actions">
|
||||
<!-- 多个尺寸时支持把每个尺寸的单独 ICO 打包成 ZIP 一键下载 -->
|
||||
<t-button v-if="result.singles.length > 1" theme="primary" :loading="zipping" @click="downloadAllZip">
|
||||
打包下载 ZIP
|
||||
</t-button>
|
||||
<t-button v-else theme="primary" @click="downloadIco">下载 ICO</t-button>
|
||||
<t-button theme="primary" @click="downloadIco">下载 ICO</t-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<t-divider />
|
||||
|
||||
<p class="grid-title">各尺寸预览(点击图片放大,右下角可单独下载 PNG / ICO)</p>
|
||||
<p class="grid-title">各尺寸预览(点击右下角可单独下载 PNG)</p>
|
||||
<div class="preview-grid">
|
||||
<div v-for="(p, pi) in result.previews" :key="p.size" class="preview-cell">
|
||||
<div v-for="p in result.previews" :key="p.size" class="preview-cell">
|
||||
<div class="preview-box checker">
|
||||
<img
|
||||
:src="p.url"
|
||||
:alt="`${p.size}px`"
|
||||
:style="{ width: Math.min(p.size, 96) + 'px' }"
|
||||
class="img-previewable"
|
||||
title="点击预览大图"
|
||||
@click="openViewer(previewUrls, pi)"
|
||||
/>
|
||||
<img :src="p.url" :alt="`${p.size}px`" :style="{ width: Math.min(p.size, 96) + 'px' }" />
|
||||
</div>
|
||||
<div class="preview-meta">
|
||||
<div class="preview-size">{{ p.size }}×{{ p.size }}</div>
|
||||
<div class="preview-btns">
|
||||
<t-button size="small" variant="text" theme="primary" @click="downloadPng(p)">PNG</t-button>
|
||||
<t-button size="small" variant="text" theme="primary" @click="downloadSingleIco(p.size)">ICO</t-button>
|
||||
</div>
|
||||
<span class="preview-size">{{ p.size }}×{{ p.size }}</span>
|
||||
<t-button size="small" variant="text" theme="primary" @click="downloadPng(p)">PNG</t-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</t-card>
|
||||
|
||||
<HistoryPanel :items="history.items" @select="onHistorySelect" @remove="history.remove" @clear="history.clear" />
|
||||
|
||||
<!-- 预览图放大查看(多尺寸成组,可左右切换) -->
|
||||
<AppImageViewer v-model:visible="viewerVisible" v-model:index="viewerIndex" :images="viewerImages" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, onBeforeUnmount, ref } from 'vue'
|
||||
import JSZip from 'jszip'
|
||||
import UploadDrop from '@/components/common/UploadDrop.vue'
|
||||
import ErrorAlert from '@/components/common/ErrorAlert.vue'
|
||||
import HistoryPanel from '@/components/common/HistoryPanel.vue'
|
||||
import AppImageViewer from '@/components/common/AppImageViewer.vue'
|
||||
import { useHistory } from '@/composables/useHistory'
|
||||
import { useImageViewer } from '@/composables/useImageViewer'
|
||||
import { downloadBlob } from '@/composables/useDownload'
|
||||
import { dataUrlToBlob, formatBytes } from '@/utils'
|
||||
import {
|
||||
@ -217,10 +190,6 @@ const sourceInfo = ref<{ name: string; width: number; height: number; size: numb
|
||||
/** 历史记录:存「文件名 | 参数摘要」,点击可恢复转换参数 */
|
||||
const history = useHistory('image-to-ico')
|
||||
|
||||
/** 预览图放大查看(各尺寸 PNG DataURL 成组) */
|
||||
const { visible: viewerVisible, index: viewerIndex, images: viewerImages, open: openViewer } = useImageViewer()
|
||||
const previewUrls = computed(() => result.value?.previews.map((p) => p.url) ?? [])
|
||||
|
||||
/** 编码方式对应的说明文案 */
|
||||
const modeHint = computed(() => {
|
||||
if (mode.value === 'bmp') return 'BMP 兼容性最好,但 128/256 尺寸体积会明显偏大'
|
||||
@ -297,31 +266,6 @@ function downloadPng(p: { size: number; url: string }) {
|
||||
downloadBlob(dataUrlToBlob(p.url), `${base}_${p.size}x${p.size}.png`)
|
||||
}
|
||||
|
||||
/** 单独下载某个尺寸的 ICO 文件 */
|
||||
function downloadSingleIco(size: number) {
|
||||
const single = result.value?.singles.find((s) => s.size === size)
|
||||
if (!single) return
|
||||
const base = (sourceInfo.value?.name ?? 'icon').replace(/\.[^.]+$/, '')
|
||||
downloadBlob(single.blob, `${base}_${size}x${size}.ico`)
|
||||
}
|
||||
|
||||
/** 一键把所有单尺寸 ICO 打包成 ZIP 下载 */
|
||||
const zipping = ref(false)
|
||||
async function downloadAllZip() {
|
||||
const singles = result.value?.singles
|
||||
if (!singles?.length) return
|
||||
zipping.value = true
|
||||
try {
|
||||
const base = (sourceInfo.value?.name ?? 'icon').replace(/\.[^.]+$/, '')
|
||||
const zip = new JSZip()
|
||||
for (const s of singles) zip.file(`${base}_${s.size}x${s.size}.ico`, s.blob)
|
||||
const blob = await zip.generateAsync({ type: 'blob' })
|
||||
downloadBlob(blob, `${base}_ico.zip`)
|
||||
} finally {
|
||||
zipping.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 点击历史:解析摘要串恢复转换参数 */
|
||||
function onHistorySelect(text: string) {
|
||||
const sMatch = /sizes=([\d,]+)/.exec(text)
|
||||
@ -509,17 +453,12 @@ onBeforeUnmount(() => {
|
||||
}
|
||||
.preview-meta {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
justify-content: space-between;
|
||||
margin-top: 6px;
|
||||
}
|
||||
.preview-btns {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
.preview-size {
|
||||
font-size: 13px;
|
||||
font-size: 12px;
|
||||
color: var(--td-text-color-secondary);
|
||||
}
|
||||
@media (max-width: 767px) {
|
||||
|
||||
@ -7,14 +7,7 @@
|
||||
|
||||
<t-card class="tool-section" title="上传图片">
|
||||
<UploadDrop v-model:files="files" accept="image/*" tip="支持中文 / 英文识别" />
|
||||
<img
|
||||
v-if="previewUrl"
|
||||
:src="previewUrl"
|
||||
alt="待识别图片"
|
||||
class="preview-img img-previewable"
|
||||
title="点击预览大图"
|
||||
@click="openViewer([previewUrl])"
|
||||
/>
|
||||
<img v-if="previewUrl" :src="previewUrl" alt="待识别图片" class="preview-img" />
|
||||
<div class="form-actions">
|
||||
<t-button theme="primary" :loading="busy" :disabled="!file" @click="recognize">
|
||||
开始识别
|
||||
@ -48,9 +41,6 @@
|
||||
@remove="history.remove"
|
||||
@clear="history.clear"
|
||||
/>
|
||||
|
||||
<!-- 待识别图放大预览 -->
|
||||
<AppImageViewer v-model:visible="viewerVisible" v-model:index="viewerIndex" :images="viewerImages" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@ -66,8 +56,6 @@ import ErrorAlert from '@/components/common/ErrorAlert.vue'
|
||||
import CopyButton from '@/components/common/CopyButton.vue'
|
||||
import HistoryPanel from '@/components/common/HistoryPanel.vue'
|
||||
import { useHistory } from '@/composables/useHistory'
|
||||
import { useImageViewer } from '@/composables/useImageViewer'
|
||||
import AppImageViewer from '@/components/common/AppImageViewer.vue'
|
||||
import { downloadText } from '@/composables/useDownload'
|
||||
import { genFilename } from '@/utils'
|
||||
|
||||
@ -84,9 +72,6 @@ type WorkerInstance = Awaited<ReturnType<typeof createWorker>>
|
||||
const files = ref<File[]>([])
|
||||
const file = ref<File | null>(null)
|
||||
const previewUrl = ref('')
|
||||
|
||||
/** 待识别图放大预览(TDesign ImageViewer) */
|
||||
const { visible: viewerVisible, index: viewerIndex, images: viewerImages, open: openViewer } = useImageViewer()
|
||||
const busy = ref(false)
|
||||
const progress = ref(0)
|
||||
const progressLabel = ref('')
|
||||
|
||||
@ -5,7 +5,7 @@
|
||||
|
||||
<t-card class="tool-section">
|
||||
<div class="preview-wrap">
|
||||
<canvas ref="canvasRef" class="preview-canvas img-previewable" title="点击预览大图" @click="previewCanvas" />
|
||||
<canvas ref="canvasRef" class="preview-canvas" />
|
||||
</div>
|
||||
|
||||
<div class="grid-form">
|
||||
@ -54,9 +54,6 @@
|
||||
@remove="history.remove"
|
||||
@clear="history.clear"
|
||||
/>
|
||||
|
||||
<!-- 占位图放大预览 -->
|
||||
<AppImageViewer v-model:visible="viewerVisible" v-model:index="viewerIndex" :images="viewerImages" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@ -65,8 +62,6 @@ import { computed, ref, watch } from 'vue'
|
||||
import CopyButton from '@/components/common/CopyButton.vue'
|
||||
import HistoryPanel from '@/components/common/HistoryPanel.vue'
|
||||
import { useHistory } from '@/composables/useHistory'
|
||||
import { useImageViewer } from '@/composables/useImageViewer'
|
||||
import AppImageViewer from '@/components/common/AppImageViewer.vue'
|
||||
import { downloadDataUrl } from '@/composables/useDownload'
|
||||
|
||||
interface PlaceholderParams {
|
||||
@ -82,13 +77,6 @@ const history = useHistory('placeholder-image')
|
||||
const canvasRef = ref<HTMLCanvasElement | null>(null)
|
||||
const width = ref(400)
|
||||
const height = ref(300)
|
||||
|
||||
/** 点击画布放大预览(把当前画布导出为 PNG 后交给 ImageViewer) */
|
||||
const { visible: viewerVisible, index: viewerIndex, images: viewerImages, open: openViewer } = useImageViewer()
|
||||
function previewCanvas() {
|
||||
const canvas = canvasRef.value
|
||||
if (canvas) openViewer([canvas.toDataURL('image/png')])
|
||||
}
|
||||
const bgColor = ref('#667eea')
|
||||
const textColor = ref('#ffffff')
|
||||
const text = ref('400 x 300')
|
||||
|
||||
@ -35,13 +35,7 @@
|
||||
|
||||
<t-card v-if="qrUrl" class="tool-section" title="生成结果">
|
||||
<div class="qr-result">
|
||||
<img
|
||||
:src="qrUrl"
|
||||
alt="生成的二维码"
|
||||
class="qr-img img-previewable"
|
||||
title="点击预览大图"
|
||||
@click="openViewer([qrUrl])"
|
||||
/>
|
||||
<img :src="qrUrl" alt="生成的二维码" class="qr-img" />
|
||||
<div class="form-actions">
|
||||
<DownloadButton :filename="`qrcode_${size}.png`" :get-blob="getQrBlob" label="下载 PNG" />
|
||||
<CopyButton :text="text" label="复制内容" />
|
||||
@ -83,9 +77,6 @@
|
||||
/>
|
||||
</t-tab-panel>
|
||||
</t-tabs>
|
||||
|
||||
<!-- 二维码放大预览 -->
|
||||
<AppImageViewer v-model:visible="viewerVisible" v-model:index="viewerIndex" :images="viewerImages" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@ -99,8 +90,6 @@ import CopyButton from '@/components/common/CopyButton.vue'
|
||||
import DownloadButton from '@/components/common/DownloadButton.vue'
|
||||
import HistoryPanel from '@/components/common/HistoryPanel.vue'
|
||||
import { useHistory } from '@/composables/useHistory'
|
||||
import { useImageViewer } from '@/composables/useImageViewer'
|
||||
import AppImageViewer from '@/components/common/AppImageViewer.vue'
|
||||
import { dataUrlToBlob } from '@/utils'
|
||||
|
||||
const tab = ref('generate')
|
||||
@ -118,9 +107,6 @@ const text = ref('')
|
||||
const size = ref(256)
|
||||
const level = ref('M')
|
||||
const dark = ref('#000000')
|
||||
|
||||
/** 二维码放大预览(TDesign ImageViewer) */
|
||||
const { visible: viewerVisible, index: viewerIndex, images: viewerImages, open: openViewer } = useImageViewer()
|
||||
const light = ref('#ffffff')
|
||||
const genBusy = ref(false)
|
||||
const genError = ref('')
|
||||
|
||||
Loading…
Reference in New Issue
Block a user