update at 2026-02-14 16:24:00

This commit is contained in:
douboer
2026-02-14 16:24:00 +08:00
parent 797da6eb76
commit 35e17b4ba2
7 changed files with 216 additions and 28 deletions

View File

@@ -1 +1 @@
import{_ as v}from"./index-BuyS14I8.js";function p(t,e,n="text/plain"){const o=new Blob([t],{type:n});s(o,e)}function s(t,e){const n=URL.createObjectURL(t),o=document.createElement("a");o.href=n,o.download=e,document.body.appendChild(o),o.click(),document.body.removeChild(o),URL.revokeObjectURL(n)}function P(t,e){p(t,e,"image/svg+xml")}async function F(t,e="font2svg-export.zip"){const n=(await v(async()=>{const{default:r}=await import("./jszip.min-D7KnG0-e.js").then(i=>i.j);return{default:r}},[])).default,o=new n;for(const r of t)o.file(r.name,r.content);const a=await o.generateAsync({type:"blob"});s(a,e)}function d(t){if(!t)return null;const e=t.match(/-?\d+(\.\d+)?/);if(!e)return null;const n=Number(e[0]);return Number.isFinite(n)?n:null}function x(t){const n=new DOMParser().parseFromString(t,"image/svg+xml").documentElement,o=d(n.getAttribute("width")),a=d(n.getAttribute("height"));if(o&&a)return{width:o,height:a};const r=n.getAttribute("viewBox");if(r){const i=r.trim().split(/[\s,]+/).map(Number);if(i.length===4&&Number.isFinite(i[2])&&Number.isFinite(i[3]))return{width:Math.max(1,i[2]),height:Math.max(1,i[3])}}return{width:1024,height:1024}}async function _(t,e){const n=x(t),o=e?.scale??1,a=Math.max(1,Math.round((e?.width??n.width)*o)),r=Math.max(1,Math.round((e?.height??n.height)*o)),i=document.createElement("canvas");i.width=a,i.height=r;const l=i.getContext("2d");if(!l)throw new Error("无法创建 PNG 画布");e?.backgroundColor?(l.fillStyle=e.backgroundColor,l.fillRect(0,0,a,r)):l.clearRect(0,0,a,r);const f=new Blob([t],{type:"image/svg+xml;charset=utf-8"}),u=URL.createObjectURL(f);try{const c=new Image;await new Promise((w,b)=>{c.onload=()=>w(),c.onerror=()=>b(new Error("SVG 转 PNG 失败")),c.src=u}),l.drawImage(c,0,0,a,r)}finally{URL.revokeObjectURL(u)}const g=await new Promise(c=>{i.toBlob(c,"image/png")});if(!g)throw new Error("PNG 编码失败");return g}async function R(t,e,n){const o=await _(t,n);s(o,e)}function m(t){return t.replace(/[<>:"/\\|?*\x00-\x1F]/g,"_").replace(/\s+/g,"_").substring(0,200)}function h(t,e){const n=m(Array.from(t).slice(0,8).join(""));return`${m(e.substring(0,20))}_${n}`}function B(t,e){return`${h(t,e)}.svg`}function L(t,e){return`${h(t,e)}.png`}export{_ as convertSvgToPngBlob,s as downloadBlob,F as downloadMultipleFiles,R as downloadPngFromSvg,P as downloadSvg,p as downloadText,L as generatePngFilename,B as generateSvgFilename,m as sanitizeFilename};
import{_ as v}from"./index-BJ50x7j1.js";function p(t,e,n="text/plain"){const o=new Blob([t],{type:n});s(o,e)}function s(t,e){const n=URL.createObjectURL(t),o=document.createElement("a");o.href=n,o.download=e,document.body.appendChild(o),o.click(),document.body.removeChild(o),URL.revokeObjectURL(n)}function P(t,e){p(t,e,"image/svg+xml")}async function F(t,e="font2svg-export.zip"){const n=(await v(async()=>{const{default:r}=await import("./jszip.min-D7KnG0-e.js").then(i=>i.j);return{default:r}},[])).default,o=new n;for(const r of t)o.file(r.name,r.content);const a=await o.generateAsync({type:"blob"});s(a,e)}function d(t){if(!t)return null;const e=t.match(/-?\d+(\.\d+)?/);if(!e)return null;const n=Number(e[0]);return Number.isFinite(n)?n:null}function x(t){const n=new DOMParser().parseFromString(t,"image/svg+xml").documentElement,o=d(n.getAttribute("width")),a=d(n.getAttribute("height"));if(o&&a)return{width:o,height:a};const r=n.getAttribute("viewBox");if(r){const i=r.trim().split(/[\s,]+/).map(Number);if(i.length===4&&Number.isFinite(i[2])&&Number.isFinite(i[3]))return{width:Math.max(1,i[2]),height:Math.max(1,i[3])}}return{width:1024,height:1024}}async function _(t,e){const n=x(t),o=e?.scale??1,a=Math.max(1,Math.round((e?.width??n.width)*o)),r=Math.max(1,Math.round((e?.height??n.height)*o)),i=document.createElement("canvas");i.width=a,i.height=r;const l=i.getContext("2d");if(!l)throw new Error("无法创建 PNG 画布");e?.backgroundColor?(l.fillStyle=e.backgroundColor,l.fillRect(0,0,a,r)):l.clearRect(0,0,a,r);const f=new Blob([t],{type:"image/svg+xml;charset=utf-8"}),u=URL.createObjectURL(f);try{const c=new Image;await new Promise((w,b)=>{c.onload=()=>w(),c.onerror=()=>b(new Error("SVG 转 PNG 失败")),c.src=u}),l.drawImage(c,0,0,a,r)}finally{URL.revokeObjectURL(u)}const g=await new Promise(c=>{i.toBlob(c,"image/png")});if(!g)throw new Error("PNG 编码失败");return g}async function R(t,e,n){const o=await _(t,n);s(o,e)}function m(t){return t.replace(/[<>:"/\\|?*\x00-\x1F]/g,"_").replace(/\s+/g,"_").substring(0,200)}function h(t,e){const n=m(Array.from(t).slice(0,8).join(""));return`${m(e.substring(0,20))}_${n}`}function B(t,e){return`${h(t,e)}.svg`}function L(t,e){return`${h(t,e)}.png`}export{_ as convertSvgToPngBlob,s as downloadBlob,F as downloadMultipleFiles,R as downloadPngFromSvg,P as downloadSvg,p as downloadText,L as generatePngFilename,B as generateSvgFilename,m as sanitizeFilename};

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -8,8 +8,8 @@
<link rel="apple-touch-icon" href="/favicon_new.png" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Font2SVG - 字体转SVG工具</title>
<script type="module" crossorigin src="/assets/index-BuyS14I8.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-BgnhMzsV.css">
<script type="module" crossorigin src="/assets/index-BJ50x7j1.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-Blwo0Xg7.css">
</head>
<body>
<div id="app"></div>

View File

@@ -215,21 +215,21 @@ console.log('App.vue: script setup completed')
</script>
<template>
<div class="w-screen h-screen box-border p-8px bg-white flex flex-col overflow-hidden">
<div class="app-container w-screen h-screen box-border p-8px bg-white flex flex-col overflow-hidden">
<!-- Frame 7: 顶部工具栏 -->
<div class="flex gap-2 items-center shrink-0 h-24 px-2 py-1">
<div class="header-row flex gap-2 items-center shrink-0 h-24 px-2 py-1">
<!-- webicon - 48x48 -->
<div class="w-12 h-12 rounded-xl overflow-hidden shrink-0">
<div class="logo-box w-12 h-12 rounded-xl overflow-hidden shrink-0">
<img src="./assets/webicon.svg" alt="logo" class="w-full h-full object-cover" />
</div>
<!-- 星程字体转换 - 弹性宽度 -->
<div class="shrink-0 max-w-[225px] min-w-[120px]" style="height: 72px;">
<!-- 星程字体转换 - 弹性宽度 (移动端隐藏) -->
<div class="app-title shrink-0 max-w-[225px] min-w-[120px]" style="height: 72px;">
<img src="./assets/icons/星程字体转换.svg" alt="星程SVG文字生成 TEXT to SVG" class="w-full h-full object-contain" />
</div>
<!-- slider - 增加宽度 -->
<div class="flex items-center gap-3 px-2 shrink-0 relative" style="width: 280px; height: 32px;">
<div class="slider-box flex items-center gap-3 px-2 shrink-0 relative" style="width: 280px; height: 32px;">
<button
@click="handleFontSizeChange(uiStore.fontSize - 10)"
class="w-4 h-4 shrink-0 cursor-pointer hover:opacity-70 transition-opacity flex items-center justify-center p-0 border-0 bg-transparent"
@@ -281,8 +281,8 @@ console.log('App.vue: script setup completed')
</label>
</div>
<!-- Frame 14: 输入框 - 弹性宽度 -->
<div class="flex-1 min-w-[80px] bg-[#f7f8fa] rounded-lg px-2 py-1 h-12">
<!-- Frame 14: 输入框 - 弹性宽度 (桌面端显示) -->
<div class="input-box-desktop flex-1 min-w-[80px] bg-[#f7f8fa] rounded-lg px-2 py-1 h-12">
<textarea
:value="uiStore.inputText"
@input="handleTextInput"
@@ -292,14 +292,14 @@ console.log('App.vue: script setup completed')
</div>
<!-- Export Group -->
<div class="flex items-center gap-1 shrink-0 border border-[#8552A1] rounded-lg px-1 py-1 bg-[#f7f8fa] shadow-sm">
<div class="w-[18px] h-[42px] shrink-0 pointer-events-none">
<div class="export-group flex items-center gap-1 shrink-0 border border-[#8552A1] rounded-lg px-1 py-1 bg-[#f7f8fa] shadow-sm">
<div class="export-label w-[18px] h-[42px] shrink-0 pointer-events-none">
<img src="./assets/icons/export.svg" alt="导出" class="w-full h-full object-contain" />
</div>
<button
@click="handleExport('svg')"
class="w-12 h-12 shrink-0 cursor-pointer hover:opacity-85 transition-opacity flex items-center justify-center p-0 border-0 bg-transparent"
class="export-btn w-12 h-12 shrink-0 cursor-pointer hover:opacity-85 transition-opacity flex items-center justify-center p-0 border-0 bg-transparent"
title="导出 SVG"
>
<img src="./assets/icons/export-svg.svg" alt="导出SVG" class="w-12 h-12 object-contain" />
@@ -307,7 +307,7 @@ console.log('App.vue: script setup completed')
<button
@click="handleExport('png')"
class="w-12 h-12 shrink-0 cursor-pointer hover:opacity-85 transition-opacity flex items-center justify-center p-0 border-0 bg-transparent"
class="export-btn w-12 h-12 shrink-0 cursor-pointer hover:opacity-85 transition-opacity flex items-center justify-center p-0 border-0 bg-transparent"
title="导出 PNG"
>
<img src="./assets/icons/export-png.svg" alt="导出PNG" class="w-12 h-12 object-contain" />
@@ -315,7 +315,7 @@ console.log('App.vue: script setup completed')
<button
@click="showWeixinQrModal = true"
class="w-[54px] h-[42px] shrink-0 cursor-pointer hover:opacity-85 transition-opacity p-0 border-0 bg-transparent"
class="weixin-btn w-[54px] h-[42px] shrink-0 cursor-pointer hover:opacity-85 transition-opacity p-0 border-0 bg-transparent"
title="微信小程序"
>
<img src="./assets/icons/weixin.svg" alt="微信小程序二维码" class="w-full h-full object-contain" />
@@ -323,6 +323,18 @@ console.log('App.vue: script setup completed')
</div>
</div>
<!-- 移动端输入栏 (仅移动端显示) -->
<div class="input-row-mobile hidden px-2 py-1 shrink-0">
<div class="flex-1 bg-[#f7f8fa] rounded-lg px-2 py-1 h-10">
<textarea
:value="uiStore.inputText"
@input="handleTextInput"
placeholder="此处输入内容"
class="w-full h-full bg-transparent border-none outline-none text-sm text-[#4e5969] placeholder-[#4e5969] resize-none leading-5 overflow-y-auto"
/>
</div>
</div>
<!-- 微信小程序二维码弹窗 -->
<div
v-if="showWeixinQrModal"
@@ -343,18 +355,18 @@ console.log('App.vue: script setup completed')
</div>
<!-- Frame 9: 主内容区 -->
<div class="flex-1 flex gap-2 min-h-0 overflow-hidden px-2">
<!-- Frame 15: 左侧栏 - 弹性宽度 -->
<div class="flex flex-col gap-2 shrink-0 overflow-hidden" style="flex-basis: 400px; max-width: 480px; min-width: 320px;">
<div class="main-content flex-1 flex gap-2 min-h-0 overflow-hidden px-2">
<!-- Frame 15: 左侧栏 - 弹性宽度 (桌面端) -->
<div class="sidebar-left flex flex-col gap-2 shrink-0 overflow-hidden" style="flex-basis: 400px; max-width: 480px; min-width: 320px;">
<!-- Frame 5: 字体选择 - 弹性高度 -->
<div class="flex-[2] border border-solid border-[#f7e0e0] rounded-lg p-1.5 flex flex-col gap-2 overflow-hidden min-h-0">
<div class="font-selector-box flex-[2] border border-solid border-[#f7e0e0] rounded-lg p-1.5 flex flex-col gap-2 overflow-hidden min-h-0">
<div v-overflow-aware class="scrollbar-hover flex-1 overflow-y-auto overflow-x-hidden pr-2">
<FontSelector />
</div>
</div>
<!-- Frame 5: 已收藏字体 - 弹性高度 -->
<div class="border border-solid border-[#f7e0e0] rounded-lg p-1.5 flex flex-col gap-2 flex-1 overflow-hidden min-h-[120px]">
<div class="favorites-box border border-solid border-[#f7e0e0] rounded-lg p-1.5 flex flex-col gap-2 flex-1 overflow-hidden min-h-[120px]">
<div class="flex items-center pr-[9px]">
<h2 class="text-base text-black shrink-0 leading-none flex-1">
已收藏字体{{ favoriteFontCount }}字体
@@ -378,7 +390,7 @@ console.log('App.vue: script setup completed')
</div>
<!-- Frame 8: 右侧预览区 - 弹性宽度 -->
<div class="flex-1 border border-solid border-[#f7e0e0] rounded-lg p-1.5 flex flex-col gap-2 overflow-hidden min-w-0">
<div class="preview-box flex-1 border border-solid border-[#f7e0e0] rounded-lg p-1.5 flex flex-col gap-2 overflow-hidden min-w-0">
<div class="flex items-center pr-[9px]">
<h2 class="text-base text-black shrink-0 leading-none flex-1">效果预览</h2>
<button
@@ -397,10 +409,28 @@ console.log('App.vue: script setup completed')
<SvgPreview ref="svgPreviewRef" />
</div>
</div>
<!-- 移动端底部区域: 选择 + 已收藏 并排 -->
<div class="bottom-section-mobile hidden">
<!-- 字体选择 -->
<div class="font-selector-mobile border border-solid border-[#3EE4C3] rounded-lg p-1.5 flex flex-col gap-1 overflow-hidden">
<h2 class="text-sm text-black shrink-0 leading-none">选择</h2>
<div v-overflow-aware class="scrollbar-hover flex-1 overflow-y-auto overflow-x-hidden">
<FontSelector />
</div>
</div>
<!-- 已收藏字体 -->
<div class="favorites-mobile border border-solid border-[#3EE4C3] rounded-lg p-1.5 flex flex-col gap-1 overflow-hidden">
<h2 class="text-sm text-black shrink-0 leading-none">已收藏</h2>
<div v-overflow-aware class="scrollbar-hover flex-1 overflow-y-auto overflow-x-hidden">
<FavoritesList />
</div>
</div>
</div>
</div>
<!-- 底部版权 -->
<div class="text-[#86909c] text-xs text-center shrink-0 h-6 pt-4 flex items-center justify-center px-2">
<div class="copyright-footer text-[#86909c] text-xs text-center shrink-0 h-6 pt-4 flex items-center justify-center px-2">
@版权说明所有字体来源互联网分享仅供效果预览不做下载传播如有侵权请告知douboer@gmail.com
</div>
</div>
@@ -442,4 +472,162 @@ console.log('App.vue: script setup completed')
border-radius: 10px;
background: transparent;
}
/* 移动端响应式布局 (iOS/Android web) */
@media (max-width: 768px) {
.app-container {
padding: 4px;
}
/* 顶部工具栏 - 压缩 */
.header-row {
height: 48px;
gap: 4px;
padding: 0 4px;
flex-wrap: nowrap;
}
.logo-box {
width: 48px;
height: 48px;
border-radius: 12px;
}
/* 隐藏标题 */
.app-title {
display: none;
}
/* slider 压缩 */
.slider-box {
width: 132px !important;
flex: 1;
min-width: 100px;
gap: 4px;
padding: 0 4px;
height: 24px !important;
}
/* 隐藏桌面端输入框 */
.input-box-desktop {
display: none;
}
/* 显示移动端输入框 */
.input-row-mobile {
display: flex !important;
gap: 6px;
margin-bottom: 8px;
}
/* 导出组缩小 */
.export-group {
gap: 2px;
padding: 2px;
}
.export-label {
width: 12px;
height: 28px;
}
.export-btn {
width: 32px;
height: 32px;
}
.export-btn img {
width: 32px;
height: 32px;
}
.weixin-btn {
width: 36px !important;
height: 28px !important;
}
/* 主内容区 - 改为垂直布局 */
.main-content {
flex-direction: column;
gap: 8px;
padding: 0 4px;
}
/* 隐藏桌面端左侧栏 */
.sidebar-left {
display: none;
}
/* 预览区 - 占上半部分 */
.preview-box {
flex: 1;
min-height: 0;
border-color: #3EE4C3;
}
.preview-box h2 {
font-size: 14px;
}
/* 显示移动端底部区域 */
.bottom-section-mobile {
display: flex !important;
flex-direction: row;
gap: 8px;
flex: 1;
min-height: 0;
}
.font-selector-mobile,
.favorites-mobile {
flex: 1;
min-width: 0;
}
.font-selector-mobile h2,
.favorites-mobile h2 {
font-size: 14px;
margin-bottom: 4px;
}
/* 版权信息缩小 */
.copyright-footer {
font-size: 10px;
padding: 8px 0;
height: auto;
}
}
/* 更小屏幕适配 (iPhone SE 等) */
@media (max-width: 480px) {
.header-row {
height: 44px;
gap: 2px;
}
.logo-box {
width: 44px;
height: 44px;
}
.slider-box {
width: 100px !important;
min-width: 80px;
}
.export-btn {
width: 28px;
height: 28px;
}
.export-btn img {
width: 28px;
height: 28px;
}
.weixin-btn {
width: 32px !important;
height: 24px !important;
}
}
</style>