update at 2026-02-07 12:55:25
1
.gitignore
vendored
@@ -25,3 +25,4 @@ dist-ssr
|
||||
*.sw?
|
||||
|
||||
*.ttf
|
||||
vite.config.ts
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
## 界面快照
|
||||
|
||||

|
||||

|
||||
|
||||
## 当前功能
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/assets/webicon.svg" />
|
||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Font2SVG - 字体转SVG工具</title>
|
||||
</head>
|
||||
|
||||
|
Before Width: | Height: | Size: 170 KiB After Width: | Height: | Size: 170 KiB |
@@ -62,9 +62,15 @@ async function handleExport(format: ExportFormat) {
|
||||
if (selectedItems.length === 1) {
|
||||
// 单个字体,直接下载 SVG
|
||||
const item = selectedItems[0]
|
||||
if (!item?.fontInfo.font) {
|
||||
alert('选中字体未加载完成,请稍后重试')
|
||||
return
|
||||
}
|
||||
|
||||
const font = item.fontInfo.font
|
||||
const svgResult = await generateSvg({
|
||||
text: inputText,
|
||||
font: item.fontInfo.font,
|
||||
font,
|
||||
fontSize: uiStore.fontSize,
|
||||
fillColor: uiStore.textColor,
|
||||
letterSpacing: 0
|
||||
@@ -87,9 +93,15 @@ async function handleExport(format: ExportFormat) {
|
||||
|
||||
for (const item of selectedItems) {
|
||||
try {
|
||||
const font = item.fontInfo.font
|
||||
if (!font) {
|
||||
console.warn(`字体 ${item.fontInfo.name} 尚未加载,已跳过导出`)
|
||||
continue
|
||||
}
|
||||
|
||||
const svgResult = await generateSvg({
|
||||
text: inputText,
|
||||
font: item.fontInfo.font,
|
||||
font,
|
||||
fontSize: uiStore.fontSize,
|
||||
fillColor: uiStore.textColor,
|
||||
letterSpacing: 0
|
||||
@@ -161,12 +173,12 @@ console.log('App.vue: script setup completed')
|
||||
<div class="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">
|
||||
<img src="/assets/webicon.svg" alt="logo" class="w-full h-full object-cover" />
|
||||
<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;">
|
||||
<img src="/assets/icons/星程字体转换.svg" alt="星程SVG文字生成 TEXT to SVG" class="w-full h-full object-contain" />
|
||||
<img src="./assets/icons/星程字体转换.svg" alt="星程SVG文字生成 TEXT to SVG" class="w-full h-full object-contain" />
|
||||
</div>
|
||||
|
||||
<!-- slider - 增加宽度 -->
|
||||
@@ -176,7 +188,7 @@ console.log('App.vue: script setup completed')
|
||||
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"
|
||||
title="减小字体"
|
||||
>
|
||||
<img src="/assets/icons/icons_idx%20_38.svg" alt="A-" class="w-4 h-4 object-contain" />
|
||||
<img src="./assets/icons/icons_idx%20_38.svg" alt="A-" class="w-4 h-4 object-contain" />
|
||||
</button>
|
||||
<div class="flex-1 h-6 flex items-center relative">
|
||||
<input
|
||||
@@ -204,14 +216,14 @@ console.log('App.vue: script setup completed')
|
||||
class="w-6 h-6 shrink-0 cursor-pointer hover:opacity-70 transition-opacity flex items-center justify-center p-0 border-0 bg-transparent"
|
||||
title="增大字体"
|
||||
>
|
||||
<img src="/assets/icons/icons_idx%20_33.svg" alt="A+" class="w-6 h-6 object-contain" />
|
||||
<img src="./assets/icons/icons_idx%20_33.svg" alt="A+" class="w-6 h-6 object-contain" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- 文字颜色选择 -->
|
||||
<div class="shrink-0 relative w-9 h-9">
|
||||
<label class="w-full h-full flex items-center justify-center cursor-pointer">
|
||||
<img src="/assets/icons/choose-color.svg" alt="颜色" class="w-9 h-9 object-contain" />
|
||||
<img src="./assets/icons/choose-color.svg" alt="颜色" class="w-9 h-9 object-contain" />
|
||||
<input
|
||||
type="color"
|
||||
:value="uiStore.textColor"
|
||||
@@ -235,7 +247,7 @@ console.log('App.vue: script setup completed')
|
||||
<!-- 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">
|
||||
<img src="/assets/icons/export.svg" alt="导出" class="w-full h-full object-contain" />
|
||||
<img src="./assets/icons/export.svg" alt="导出" class="w-full h-full object-contain" />
|
||||
</div>
|
||||
|
||||
<button
|
||||
@@ -243,7 +255,7 @@ console.log('App.vue: script setup completed')
|
||||
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"
|
||||
title="导出 SVG"
|
||||
>
|
||||
<img src="/assets/icons/export-svg.svg" alt="导出SVG" class="w-12 h-12 object-contain" />
|
||||
<img src="./assets/icons/export-svg.svg" alt="导出SVG" class="w-12 h-12 object-contain" />
|
||||
</button>
|
||||
|
||||
<button
|
||||
@@ -251,7 +263,7 @@ console.log('App.vue: script setup completed')
|
||||
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"
|
||||
title="导出 PNG"
|
||||
>
|
||||
<img src="/assets/icons/export-png.svg" alt="导出PNG" class="w-12 h-12 object-contain" />
|
||||
<img src="./assets/icons/export-png.svg" alt="导出PNG" class="w-12 h-12 object-contain" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
Before Width: | Height: | Size: 989 B After Width: | Height: | Size: 989 B |
|
Before Width: | Height: | Size: 488 B After Width: | Height: | Size: 488 B |
|
Before Width: | Height: | Size: 3.9 KiB After Width: | Height: | Size: 3.9 KiB |
|
Before Width: | Height: | Size: 330 B After Width: | Height: | Size: 330 B |
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 2.1 KiB |
|
Before Width: | Height: | Size: 749 B After Width: | Height: | Size: 749 B |
|
Before Width: | Height: | Size: 403 B After Width: | Height: | Size: 403 B |
|
Before Width: | Height: | Size: 404 B After Width: | Height: | Size: 404 B |
|
Before Width: | Height: | Size: 304 B After Width: | Height: | Size: 304 B |
|
Before Width: | Height: | Size: 755 B After Width: | Height: | Size: 755 B |
|
Before Width: | Height: | Size: 585 B After Width: | Height: | Size: 585 B |
|
Before Width: | Height: | Size: 304 B After Width: | Height: | Size: 304 B |
|
Before Width: | Height: | Size: 749 B After Width: | Height: | Size: 749 B |
|
Before Width: | Height: | Size: 437 B After Width: | Height: | Size: 437 B |
|
Before Width: | Height: | Size: 392 B After Width: | Height: | Size: 392 B |
|
Before Width: | Height: | Size: 198 B After Width: | Height: | Size: 198 B |
|
Before Width: | Height: | Size: 392 B After Width: | Height: | Size: 392 B |
|
Before Width: | Height: | Size: 392 B After Width: | Height: | Size: 392 B |
|
Before Width: | Height: | Size: 383 B After Width: | Height: | Size: 383 B |
|
Before Width: | Height: | Size: 279 B After Width: | Height: | Size: 279 B |
|
Before Width: | Height: | Size: 41 KiB After Width: | Height: | Size: 41 KiB |
|
Before Width: | Height: | Size: 3.2 KiB After Width: | Height: | Size: 3.2 KiB |
|
Before Width: | Height: | Size: 473 KiB After Width: | Height: | Size: 473 KiB |
|
Before Width: | Height: | Size: 496 B After Width: | Height: | Size: 496 B |
|
Before Width: | Height: | Size: 128 KiB After Width: | Height: | Size: 128 KiB |
9
frontend/src/assets/webicon.svg
Normal file
|
After Width: | Height: | Size: 170 KiB |
@@ -38,7 +38,7 @@ function isInPreview(fontId: string): boolean {
|
||||
>
|
||||
<!-- 字体图标 -->
|
||||
<div class="w-4 h-4 shrink-0">
|
||||
<img src="/assets/icons/icons_idx%20_18.svg" alt="font" class="w-full h-full" />
|
||||
<img src="../assets/icons/icons_idx%20_18.svg" alt="font" class="w-full h-full" />
|
||||
</div>
|
||||
|
||||
<!-- 字体名称 -->
|
||||
@@ -52,7 +52,7 @@ function isInPreview(fontId: string): boolean {
|
||||
class="w-[18px] h-[18px] shrink-0 border rounded-full flex items-center justify-center p-0 bg-transparent"
|
||||
:class="isInPreview(font.id) ? 'bg-[#9b6bc2] border-[#9b6bc2]' : 'border-[#c9cdd4]'"
|
||||
>
|
||||
<img v-if="isInPreview(font.id)" src="/assets/icons/checkbox.svg" alt="选中" class="w-[11px] h-[9px]" />
|
||||
<img v-if="isInPreview(font.id)" src="../assets/icons/checkbox.svg" alt="选中" class="w-[11px] h-[9px]" />
|
||||
</button>
|
||||
|
||||
<!-- 收藏按钮 -->
|
||||
@@ -61,7 +61,7 @@ function isInPreview(fontId: string): boolean {
|
||||
class="w-[18px] h-[17px] shrink-0 p-0 border-0 bg-transparent"
|
||||
>
|
||||
<img
|
||||
src="/assets/icons/icons_idx%20_19.svg"
|
||||
src="../assets/icons/icons_idx%20_19.svg"
|
||||
alt="收藏"
|
||||
class="w-full h-full"
|
||||
:class="isFavorite(font.id) ? 'favorite-active' : ''"
|
||||
|
||||
@@ -51,13 +51,13 @@ function isInPreview(node: FontTreeNode): boolean {
|
||||
>
|
||||
<img
|
||||
v-if="node.expanded"
|
||||
src="/assets/icons/zhedie.svg"
|
||||
src="../assets/icons/zhedie.svg"
|
||||
alt="收起"
|
||||
class="w-[15px] h-[15px]"
|
||||
/>
|
||||
<img
|
||||
v-else
|
||||
src="/assets/icons/icons_idx%20_12.svg"
|
||||
src="../assets/icons/icons_idx%20_12.svg"
|
||||
alt="展开"
|
||||
class="w-[15px] h-[15px]"
|
||||
/>
|
||||
@@ -79,7 +79,7 @@ function isInPreview(node: FontTreeNode): boolean {
|
||||
<!-- 字体列表 -->
|
||||
<div v-if="node.expanded && node.children" class="flex flex-col gap-3 mt-3">
|
||||
<div
|
||||
v-for="(child, index) in node.children"
|
||||
v-for="child in node.children"
|
||||
:key="child.name"
|
||||
class="flex items-center gap-2 border-b border-[#c9cdd4] pb-2 relative"
|
||||
>
|
||||
@@ -88,7 +88,7 @@ function isInPreview(node: FontTreeNode): boolean {
|
||||
|
||||
<!-- 字体图标 -->
|
||||
<div class="w-4 h-4 shrink-0 ml-[17px]">
|
||||
<img src="/assets/icons/icons_idx%20_18.svg" alt="font" class="w-full h-full" />
|
||||
<img src="../assets/icons/icons_idx%20_18.svg" alt="font" class="w-full h-full" />
|
||||
</div>
|
||||
|
||||
<!-- 字体名称 -->
|
||||
@@ -102,7 +102,7 @@ function isInPreview(node: FontTreeNode): boolean {
|
||||
class="w-[18px] h-[18px] shrink-0 border rounded-full flex items-center justify-center p-0 bg-transparent"
|
||||
:class="isInPreview(child) ? 'bg-[#9b6bc2] border-[#9b6bc2]' : 'border-[#c9cdd4]'"
|
||||
>
|
||||
<img v-if="isInPreview(child)" src="/assets/icons/checkbox.svg" alt="选中" class="w-[11px] h-[9px]" />
|
||||
<img v-if="isInPreview(child)" src="../assets/icons/checkbox.svg" alt="选中" class="w-[11px] h-[9px]" />
|
||||
</button>
|
||||
|
||||
<!-- 收藏按钮 -->
|
||||
@@ -111,7 +111,7 @@ function isInPreview(node: FontTreeNode): boolean {
|
||||
class="w-[18px] h-[17px] shrink-0 p-0 border-0 bg-transparent"
|
||||
>
|
||||
<img
|
||||
src="/assets/icons/icons_idx%20_19.svg"
|
||||
src="../assets/icons/icons_idx%20_19.svg"
|
||||
alt="收藏"
|
||||
class="w-full h-full"
|
||||
:class="isFavorite(child) ? 'favorite-active' : ''"
|
||||
|
||||
@@ -97,7 +97,7 @@ function toggleSelectItem(item: PreviewItemType) {
|
||||
>
|
||||
<div class="flex items-center gap-[8px] border-b border-[#c9cdd4] pb-[8px] pr-[8px]">
|
||||
<div class="w-[24px] h-[24px] shrink-0">
|
||||
<img src="/assets/icons/icons_idx%20_32.svg" alt="字体" class="w-full h-full" />
|
||||
<img src="../assets/icons/icons_idx%20_32.svg" alt="字体" class="w-full h-full" />
|
||||
</div>
|
||||
|
||||
<div class="flex-1 text-xs text-[#86909c]">
|
||||
@@ -109,7 +109,7 @@ function toggleSelectItem(item: PreviewItemType) {
|
||||
class="w-[18px] h-[18px] shrink-0 border rounded-full flex items-center justify-center p-0 bg-transparent"
|
||||
:class="item.selected ? 'bg-[#9b6bc2] border-[#9b6bc2]' : 'border-[#c9cdd4]'"
|
||||
>
|
||||
<img v-if="item.selected" src="/assets/icons/checkbox.svg" alt="选中" class="w-[11px] h-[9px]" />
|
||||
<img v-if="item.selected" src="../assets/icons/checkbox.svg" alt="选中" class="w-[11px] h-[9px]" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
|
||||
12
frontend/src/types/modules.d.ts
vendored
@@ -48,4 +48,14 @@ declare module 'opentype.js' {
|
||||
|
||||
declare module 'harfbuzzjs' {
|
||||
export default function (): Promise<any>
|
||||
}
|
||||
}
|
||||
|
||||
declare module 'harfbuzzjs/hb.js' {
|
||||
const createHarfBuzz: any
|
||||
export default createHarfBuzz
|
||||
}
|
||||
|
||||
declare module 'harfbuzzjs/hbjs.js' {
|
||||
const bindHarfBuzz: any
|
||||
export default bindHarfBuzz
|
||||
}
|
||||
|
||||
@@ -2,9 +2,6 @@ import { defineConfig } from 'vite'
|
||||
import vue from '@vitejs/plugin-vue'
|
||||
import UnoCSS from 'unocss/vite'
|
||||
import wasm from 'vite-plugin-wasm'
|
||||
import fs from 'fs'
|
||||
import path from 'path'
|
||||
import { homedir } from 'os'
|
||||
|
||||
// https://vite.dev/config/
|
||||
export default defineConfig({
|
||||
@@ -18,9 +15,5 @@ export default defineConfig({
|
||||
server: {
|
||||
host: '0.0.0.0',
|
||||
port: 5174,
|
||||
https: {
|
||||
key: fs.readFileSync(path.join(homedir(), 'mac.biboer.cn_ecc/mac.biboer.cn.key')),
|
||||
cert: fs.readFileSync(path.join(homedir(), 'mac.biboer.cn_ecc/fullchain.cer'))
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||