update at 2026-02-09 16:09:44
This commit is contained in:
@@ -17,10 +17,13 @@ miniprogram/
|
||||
├── pages/
|
||||
│ ├── index/ # 首页:输入、预览、导出
|
||||
│ └── font-picker/ # 字体选择页
|
||||
├── config/
|
||||
│ └── server.js # 远端地址/端口/API 路径统一配置
|
||||
├── utils/
|
||||
│ ├── core/ # 纯算法模块
|
||||
│ └── mp/ # 小程序 API 适配层
|
||||
├── assets/fonts.json # 字体清单(由脚本生成)
|
||||
├── assets/default.json # 首次加载默认配置(内容/颜色/字号/默认字体)
|
||||
├── app.js / app.json / app.wxss
|
||||
└── project.config.json
|
||||
```
|
||||
@@ -34,6 +37,19 @@ miniprogram/
|
||||
5. Nginx 配置 `/api/` 反向代理到渲染服务。
|
||||
6. 编译运行。
|
||||
|
||||
## 服务器配置(换服务器只改一处)
|
||||
|
||||
修改 `miniprogram/config/server.js` 中的 `SERVER_CONFIG`:
|
||||
|
||||
- `protocol`: `https` / `http`
|
||||
- `host`: 服务器域名
|
||||
- `port`: 端口(默认 443/80 可留空)
|
||||
- `apiPrefix`: API 前缀(默认 `/api`)
|
||||
- `fontsManifestPath`: 字体清单路径(默认 `/miniprogram/assets/fonts.json`)
|
||||
- `defaultConfigPath`: 默认配置路径(默认 `/miniprogram/assets/default.json`)
|
||||
|
||||
`app.js` 和 API 调用会自动使用该配置生成完整 URL。
|
||||
|
||||
## 导出说明
|
||||
|
||||
- `SVG`:受微信限制,`shareFileMessage` 需由单次点击直接触发,建议逐个字体导出。
|
||||
@@ -50,6 +66,29 @@ miniprogram/
|
||||
|
||||
如果 `path` 是相对路径(例如 `/fonts/a.ttf`),服务端会根据静态根目录拼接到实际文件路径。
|
||||
|
||||
推荐部署结构:
|
||||
- 字体目录统一放在服务器根目录:`/fonts/`
|
||||
- Web 配置文件独立管理:`/fonts.json`(可选 `/default.json`)
|
||||
- 小程序配置文件独立管理:`/miniprogram/assets/fonts.json`、`/miniprogram/assets/default.json`
|
||||
|
||||
## 首次默认配置(default.json)
|
||||
|
||||
- 默认配置文件与 `fonts.json` 同目录:由 `config/server.js` 自动拼接(默认是 `https://fonts.biboer.cn/miniprogram/assets/default.json`)
|
||||
- 小程序会在首次加载时读取该配置(远端失败则回退本地 `miniprogram/assets/default.js`)
|
||||
- 配置只在首次加载生效,后续始终使用用户本地已保存配置(选择、收藏、颜色、字号、内容)
|
||||
|
||||
示例:
|
||||
|
||||
```json
|
||||
{
|
||||
"inputText": "星程字体转换",
|
||||
"fontSize": 50,
|
||||
"textColor": "#dc2626",
|
||||
"selectedFontIds": ["0001"],
|
||||
"favoriteFontIds": ["0001"]
|
||||
}
|
||||
```
|
||||
|
||||
## 调试命令(仓库根目录)
|
||||
|
||||
```bash
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
const { buildRuntimeConfig } = require('./config/server')
|
||||
|
||||
const runtimeConfig = buildRuntimeConfig()
|
||||
|
||||
App({
|
||||
globalData: {
|
||||
fontsManifestUrl: 'https://fonts.biboer.cn/fonts.json',
|
||||
fontsBaseUrl: 'https://fonts.biboer.cn',
|
||||
svgRenderApiUrl: 'https://fonts.biboer.cn/api/render-svg',
|
||||
...runtimeConfig,
|
||||
apiTimeoutMs: 30000,
|
||||
fonts: null,
|
||||
defaultConfig: null,
|
||||
},
|
||||
})
|
||||
|
||||
19
miniprogram/assets/default.js
Normal file
19
miniprogram/assets/default.js
Normal file
@@ -0,0 +1,19 @@
|
||||
module.exports = {
|
||||
inputText: '星程字体转换',
|
||||
fontSize: 50,
|
||||
textColor: '#dc2626',
|
||||
selectedFontIds: [
|
||||
'0001',
|
||||
'0003',
|
||||
'0006',
|
||||
'0011',
|
||||
'0015',
|
||||
],
|
||||
favoriteFontIds: [
|
||||
'0001',
|
||||
'0003',
|
||||
'0006',
|
||||
'0011',
|
||||
'0015',
|
||||
],
|
||||
}
|
||||
19
miniprogram/assets/default.json
Normal file
19
miniprogram/assets/default.json
Normal file
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"inputText": "星程字体转换",
|
||||
"fontSize": 50,
|
||||
"textColor": "#dc2626",
|
||||
"selectedFontIds": [
|
||||
"0001",
|
||||
"0003",
|
||||
"0006",
|
||||
"0011",
|
||||
"0015"
|
||||
],
|
||||
"favoriteFontIds": [
|
||||
"0001",
|
||||
"0003",
|
||||
"0006",
|
||||
"0011",
|
||||
"0015"
|
||||
]
|
||||
}
|
||||
@@ -1,167 +1,167 @@
|
||||
module.exports = [
|
||||
{
|
||||
"id": "其他字体/AlimamaDaoLiTi",
|
||||
"id": "0001",
|
||||
"name": "AlimamaDaoLiTi",
|
||||
"filename": "AlimamaDaoLiTi.ttf",
|
||||
"category": "其他字体",
|
||||
"path": "/fonts/其他字体/AlimamaDaoLiTi.ttf"
|
||||
},
|
||||
{
|
||||
"id": "其他字体/Hangeuljaemin4-Regular",
|
||||
"id": "0002",
|
||||
"name": "Hangeuljaemin4-Regular",
|
||||
"filename": "Hangeuljaemin4-Regular.ttf",
|
||||
"category": "其他字体",
|
||||
"path": "/fonts/其他字体/Hangeuljaemin4-Regular.ttf"
|
||||
},
|
||||
{
|
||||
"id": "其他字体/I.顏體",
|
||||
"id": "0003",
|
||||
"name": "I.顏體",
|
||||
"filename": "I.顏體.ttf",
|
||||
"category": "其他字体",
|
||||
"path": "/fonts/其他字体/I.顏體.ttf"
|
||||
},
|
||||
{
|
||||
"id": "其他字体/XCDUANZHUANGSONGTI",
|
||||
"id": "0004",
|
||||
"name": "XCDUANZHUANGSONGTI",
|
||||
"filename": "XCDUANZHUANGSONGTI.ttf",
|
||||
"category": "其他字体",
|
||||
"path": "/fonts/其他字体/XCDUANZHUANGSONGTI.ttf"
|
||||
},
|
||||
{
|
||||
"id": "其他字体/qiji-combo",
|
||||
"id": "0005",
|
||||
"name": "qiji-combo",
|
||||
"filename": "qiji-combo.ttf",
|
||||
"category": "其他字体",
|
||||
"path": "/fonts/其他字体/qiji-combo.ttf"
|
||||
},
|
||||
{
|
||||
"id": "其他字体/临海隶书",
|
||||
"id": "0006",
|
||||
"name": "临海隶书",
|
||||
"filename": "临海隶书.ttf",
|
||||
"category": "其他字体",
|
||||
"path": "/fonts/其他字体/临海隶书.ttf"
|
||||
},
|
||||
{
|
||||
"id": "其他字体/京華老宋体_KingHwa_OldSong",
|
||||
"id": "0007",
|
||||
"name": "京華老宋体_KingHwa_OldSong",
|
||||
"filename": "京華老宋体_KingHwa_OldSong.ttf",
|
||||
"category": "其他字体",
|
||||
"path": "/fonts/其他字体/京華老宋体_KingHwa_OldSong.ttf"
|
||||
},
|
||||
{
|
||||
"id": "其他字体/优设标题黑",
|
||||
"id": "0008",
|
||||
"name": "优设标题黑",
|
||||
"filename": "优设标题黑.ttf",
|
||||
"category": "其他字体",
|
||||
"path": "/fonts/其他字体/优设标题黑.ttf"
|
||||
},
|
||||
{
|
||||
"id": "其他字体/包图小白体",
|
||||
"id": "0009",
|
||||
"name": "包图小白体",
|
||||
"filename": "包图小白体.ttf",
|
||||
"category": "其他字体",
|
||||
"path": "/fonts/其他字体/包图小白体.ttf"
|
||||
},
|
||||
{
|
||||
"id": "其他字体/源界明朝",
|
||||
"id": "0010",
|
||||
"name": "源界明朝",
|
||||
"filename": "源界明朝.ttf",
|
||||
"category": "其他字体",
|
||||
"path": "/fonts/其他字体/源界明朝.ttf"
|
||||
},
|
||||
{
|
||||
"id": "其他字体/演示佛系体",
|
||||
"id": "0011",
|
||||
"name": "演示佛系体",
|
||||
"filename": "演示佛系体.ttf",
|
||||
"category": "其他字体",
|
||||
"path": "/fonts/其他字体/演示佛系体.ttf"
|
||||
},
|
||||
{
|
||||
"id": "其他字体/站酷快乐体",
|
||||
"id": "0012",
|
||||
"name": "站酷快乐体",
|
||||
"filename": "站酷快乐体.ttf",
|
||||
"category": "其他字体",
|
||||
"path": "/fonts/其他字体/站酷快乐体.ttf"
|
||||
},
|
||||
{
|
||||
"id": "其他字体/问藏书房",
|
||||
"id": "0013",
|
||||
"name": "问藏书房",
|
||||
"filename": "问藏书房.ttf",
|
||||
"category": "其他字体",
|
||||
"path": "/fonts/其他字体/问藏书房.ttf"
|
||||
},
|
||||
{
|
||||
"id": "其他字体/霞鹜臻楷",
|
||||
"id": "0014",
|
||||
"name": "霞鹜臻楷",
|
||||
"filename": "霞鹜臻楷.ttf",
|
||||
"category": "其他字体",
|
||||
"path": "/fonts/其他字体/霞鹜臻楷.ttf"
|
||||
},
|
||||
{
|
||||
"id": "庞门正道/庞门正道标题体",
|
||||
"id": "0015",
|
||||
"name": "庞门正道标题体",
|
||||
"filename": "庞门正道标题体.ttf",
|
||||
"category": "庞门正道",
|
||||
"path": "/fonts/庞门正道/庞门正道标题体.ttf"
|
||||
},
|
||||
{
|
||||
"id": "庞门正道-测试/庞门正道标题体",
|
||||
"id": "0016",
|
||||
"name": "庞门正道标题体",
|
||||
"filename": "庞门正道标题体.ttf",
|
||||
"category": "庞门正道-测试",
|
||||
"path": "/fonts/庞门正道-测试/庞门正道标题体.ttf"
|
||||
},
|
||||
{
|
||||
"id": "王漢宗/王漢宗勘亭流繁",
|
||||
"id": "0017",
|
||||
"name": "王漢宗勘亭流繁",
|
||||
"filename": "王漢宗勘亭流繁.ttf",
|
||||
"category": "王漢宗",
|
||||
"path": "/fonts/王漢宗/王漢宗勘亭流繁.ttf"
|
||||
},
|
||||
{
|
||||
"id": "王漢宗/王漢宗新潮體",
|
||||
"id": "0018",
|
||||
"name": "王漢宗新潮體",
|
||||
"filename": "王漢宗新潮體.ttf",
|
||||
"category": "王漢宗",
|
||||
"path": "/fonts/王漢宗/王漢宗新潮體.ttf"
|
||||
},
|
||||
{
|
||||
"id": "王漢宗/王漢宗波卡體空陰",
|
||||
"id": "0019",
|
||||
"name": "王漢宗波卡體空陰",
|
||||
"filename": "王漢宗波卡體空陰.ttf",
|
||||
"category": "王漢宗",
|
||||
"path": "/fonts/王漢宗/王漢宗波卡體空陰.ttf"
|
||||
},
|
||||
{
|
||||
"id": "王漢宗/王漢宗細黑體繁",
|
||||
"id": "0020",
|
||||
"name": "王漢宗細黑體繁",
|
||||
"filename": "王漢宗細黑體繁.ttf",
|
||||
"category": "王漢宗",
|
||||
"path": "/fonts/王漢宗/王漢宗細黑體繁.ttf"
|
||||
},
|
||||
{
|
||||
"id": "王漢宗/王漢宗綜藝體雙空陰",
|
||||
"id": "0021",
|
||||
"name": "王漢宗綜藝體雙空陰",
|
||||
"filename": "王漢宗綜藝體雙空陰.ttf",
|
||||
"category": "王漢宗",
|
||||
"path": "/fonts/王漢宗/王漢宗綜藝體雙空陰.ttf"
|
||||
},
|
||||
{
|
||||
"id": "王漢宗/王漢宗超明體繁",
|
||||
"id": "0022",
|
||||
"name": "王漢宗超明體繁",
|
||||
"filename": "王漢宗超明體繁.ttf",
|
||||
"category": "王漢宗",
|
||||
"path": "/fonts/王漢宗/王漢宗超明體繁.ttf"
|
||||
},
|
||||
{
|
||||
"id": "王漢宗/王漢宗酷儷海報",
|
||||
"id": "0023",
|
||||
"name": "王漢宗酷儷海報",
|
||||
"filename": "王漢宗酷儷海報.ttf",
|
||||
"category": "王漢宗",
|
||||
"path": "/fonts/王漢宗/王漢宗酷儷海報.ttf"
|
||||
},
|
||||
{
|
||||
"id": "王漢宗/王漢宗顏楷體繁",
|
||||
"id": "0024",
|
||||
"name": "王漢宗顏楷體繁",
|
||||
"filename": "王漢宗顏楷體繁.ttf",
|
||||
"category": "王漢宗",
|
||||
|
||||
@@ -1,167 +1,167 @@
|
||||
[
|
||||
{
|
||||
"id": "其他字体/AlimamaDaoLiTi",
|
||||
"id": "0001",
|
||||
"name": "AlimamaDaoLiTi",
|
||||
"filename": "AlimamaDaoLiTi.ttf",
|
||||
"category": "其他字体",
|
||||
"path": "/fonts/其他字体/AlimamaDaoLiTi.ttf"
|
||||
},
|
||||
{
|
||||
"id": "其他字体/Hangeuljaemin4-Regular",
|
||||
"id": "0002",
|
||||
"name": "Hangeuljaemin4-Regular",
|
||||
"filename": "Hangeuljaemin4-Regular.ttf",
|
||||
"category": "其他字体",
|
||||
"path": "/fonts/其他字体/Hangeuljaemin4-Regular.ttf"
|
||||
},
|
||||
{
|
||||
"id": "其他字体/I.顏體",
|
||||
"id": "0003",
|
||||
"name": "I.顏體",
|
||||
"filename": "I.顏體.ttf",
|
||||
"category": "其他字体",
|
||||
"path": "/fonts/其他字体/I.顏體.ttf"
|
||||
},
|
||||
{
|
||||
"id": "其他字体/XCDUANZHUANGSONGTI",
|
||||
"id": "0004",
|
||||
"name": "XCDUANZHUANGSONGTI",
|
||||
"filename": "XCDUANZHUANGSONGTI.ttf",
|
||||
"category": "其他字体",
|
||||
"path": "/fonts/其他字体/XCDUANZHUANGSONGTI.ttf"
|
||||
},
|
||||
{
|
||||
"id": "其他字体/qiji-combo",
|
||||
"id": "0005",
|
||||
"name": "qiji-combo",
|
||||
"filename": "qiji-combo.ttf",
|
||||
"category": "其他字体",
|
||||
"path": "/fonts/其他字体/qiji-combo.ttf"
|
||||
},
|
||||
{
|
||||
"id": "其他字体/临海隶书",
|
||||
"id": "0006",
|
||||
"name": "临海隶书",
|
||||
"filename": "临海隶书.ttf",
|
||||
"category": "其他字体",
|
||||
"path": "/fonts/其他字体/临海隶书.ttf"
|
||||
},
|
||||
{
|
||||
"id": "其他字体/京華老宋体_KingHwa_OldSong",
|
||||
"id": "0007",
|
||||
"name": "京華老宋体_KingHwa_OldSong",
|
||||
"filename": "京華老宋体_KingHwa_OldSong.ttf",
|
||||
"category": "其他字体",
|
||||
"path": "/fonts/其他字体/京華老宋体_KingHwa_OldSong.ttf"
|
||||
},
|
||||
{
|
||||
"id": "其他字体/优设标题黑",
|
||||
"id": "0008",
|
||||
"name": "优设标题黑",
|
||||
"filename": "优设标题黑.ttf",
|
||||
"category": "其他字体",
|
||||
"path": "/fonts/其他字体/优设标题黑.ttf"
|
||||
},
|
||||
{
|
||||
"id": "其他字体/包图小白体",
|
||||
"id": "0009",
|
||||
"name": "包图小白体",
|
||||
"filename": "包图小白体.ttf",
|
||||
"category": "其他字体",
|
||||
"path": "/fonts/其他字体/包图小白体.ttf"
|
||||
},
|
||||
{
|
||||
"id": "其他字体/源界明朝",
|
||||
"id": "0010",
|
||||
"name": "源界明朝",
|
||||
"filename": "源界明朝.ttf",
|
||||
"category": "其他字体",
|
||||
"path": "/fonts/其他字体/源界明朝.ttf"
|
||||
},
|
||||
{
|
||||
"id": "其他字体/演示佛系体",
|
||||
"id": "0011",
|
||||
"name": "演示佛系体",
|
||||
"filename": "演示佛系体.ttf",
|
||||
"category": "其他字体",
|
||||
"path": "/fonts/其他字体/演示佛系体.ttf"
|
||||
},
|
||||
{
|
||||
"id": "其他字体/站酷快乐体",
|
||||
"id": "0012",
|
||||
"name": "站酷快乐体",
|
||||
"filename": "站酷快乐体.ttf",
|
||||
"category": "其他字体",
|
||||
"path": "/fonts/其他字体/站酷快乐体.ttf"
|
||||
},
|
||||
{
|
||||
"id": "其他字体/问藏书房",
|
||||
"id": "0013",
|
||||
"name": "问藏书房",
|
||||
"filename": "问藏书房.ttf",
|
||||
"category": "其他字体",
|
||||
"path": "/fonts/其他字体/问藏书房.ttf"
|
||||
},
|
||||
{
|
||||
"id": "其他字体/霞鹜臻楷",
|
||||
"id": "0014",
|
||||
"name": "霞鹜臻楷",
|
||||
"filename": "霞鹜臻楷.ttf",
|
||||
"category": "其他字体",
|
||||
"path": "/fonts/其他字体/霞鹜臻楷.ttf"
|
||||
},
|
||||
{
|
||||
"id": "庞门正道/庞门正道标题体",
|
||||
"id": "0015",
|
||||
"name": "庞门正道标题体",
|
||||
"filename": "庞门正道标题体.ttf",
|
||||
"category": "庞门正道",
|
||||
"path": "/fonts/庞门正道/庞门正道标题体.ttf"
|
||||
},
|
||||
{
|
||||
"id": "庞门正道-测试/庞门正道标题体",
|
||||
"id": "0016",
|
||||
"name": "庞门正道标题体",
|
||||
"filename": "庞门正道标题体.ttf",
|
||||
"category": "庞门正道-测试",
|
||||
"path": "/fonts/庞门正道-测试/庞门正道标题体.ttf"
|
||||
},
|
||||
{
|
||||
"id": "王漢宗/王漢宗勘亭流繁",
|
||||
"id": "0017",
|
||||
"name": "王漢宗勘亭流繁",
|
||||
"filename": "王漢宗勘亭流繁.ttf",
|
||||
"category": "王漢宗",
|
||||
"path": "/fonts/王漢宗/王漢宗勘亭流繁.ttf"
|
||||
},
|
||||
{
|
||||
"id": "王漢宗/王漢宗新潮體",
|
||||
"id": "0018",
|
||||
"name": "王漢宗新潮體",
|
||||
"filename": "王漢宗新潮體.ttf",
|
||||
"category": "王漢宗",
|
||||
"path": "/fonts/王漢宗/王漢宗新潮體.ttf"
|
||||
},
|
||||
{
|
||||
"id": "王漢宗/王漢宗波卡體空陰",
|
||||
"id": "0019",
|
||||
"name": "王漢宗波卡體空陰",
|
||||
"filename": "王漢宗波卡體空陰.ttf",
|
||||
"category": "王漢宗",
|
||||
"path": "/fonts/王漢宗/王漢宗波卡體空陰.ttf"
|
||||
},
|
||||
{
|
||||
"id": "王漢宗/王漢宗細黑體繁",
|
||||
"id": "0020",
|
||||
"name": "王漢宗細黑體繁",
|
||||
"filename": "王漢宗細黑體繁.ttf",
|
||||
"category": "王漢宗",
|
||||
"path": "/fonts/王漢宗/王漢宗細黑體繁.ttf"
|
||||
},
|
||||
{
|
||||
"id": "王漢宗/王漢宗綜藝體雙空陰",
|
||||
"id": "0021",
|
||||
"name": "王漢宗綜藝體雙空陰",
|
||||
"filename": "王漢宗綜藝體雙空陰.ttf",
|
||||
"category": "王漢宗",
|
||||
"path": "/fonts/王漢宗/王漢宗綜藝體雙空陰.ttf"
|
||||
},
|
||||
{
|
||||
"id": "王漢宗/王漢宗超明體繁",
|
||||
"id": "0022",
|
||||
"name": "王漢宗超明體繁",
|
||||
"filename": "王漢宗超明體繁.ttf",
|
||||
"category": "王漢宗",
|
||||
"path": "/fonts/王漢宗/王漢宗超明體繁.ttf"
|
||||
},
|
||||
{
|
||||
"id": "王漢宗/王漢宗酷儷海報",
|
||||
"id": "0023",
|
||||
"name": "王漢宗酷儷海報",
|
||||
"filename": "王漢宗酷儷海報.ttf",
|
||||
"category": "王漢宗",
|
||||
"path": "/fonts/王漢宗/王漢宗酷儷海報.ttf"
|
||||
},
|
||||
{
|
||||
"id": "王漢宗/王漢宗顏楷體繁",
|
||||
"id": "0024",
|
||||
"name": "王漢宗顏楷體繁",
|
||||
"filename": "王漢宗顏楷體繁.ttf",
|
||||
"category": "王漢宗",
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
// CDN 配置文件
|
||||
// 管理所有静态资源的 CDN 地址
|
||||
|
||||
const CDN_BASE_URL = 'https://fonts.biboer.cn';
|
||||
const { buildRuntimeConfig } = require('./server')
|
||||
const runtimeConfig = buildRuntimeConfig()
|
||||
const CDN_BASE_URL = runtimeConfig.fontsBaseUrl;
|
||||
|
||||
// 图标路径配置
|
||||
const ICON_PATHS = {
|
||||
@@ -30,7 +32,7 @@ const ICON_PATHS = {
|
||||
|
||||
// 字体资源路径
|
||||
const FONT_BASE_URL = `${CDN_BASE_URL}/fonts`;
|
||||
const FONTS_JSON_URL = `${CDN_BASE_URL}/fonts.json`;
|
||||
const FONTS_JSON_URL = runtimeConfig.fontsManifestUrl;
|
||||
|
||||
module.exports = {
|
||||
CDN_BASE_URL,
|
||||
|
||||
54
miniprogram/config/server.js
Normal file
54
miniprogram/config/server.js
Normal file
@@ -0,0 +1,54 @@
|
||||
// 远端服务配置(统一修改入口)
|
||||
// 更换服务器时,仅需修改这里。
|
||||
|
||||
const SERVER_CONFIG = {
|
||||
protocol: 'https',
|
||||
host: 'fonts.biboer.cn',
|
||||
// 留空表示使用协议默认端口(https:443 / http:80)
|
||||
port: '',
|
||||
apiPrefix: '/api',
|
||||
fontsManifestPath: '/miniprogram/assets/fonts.json',
|
||||
defaultConfigPath: '/miniprogram/assets/default.json',
|
||||
}
|
||||
|
||||
function buildOrigin() {
|
||||
const protocol = String(SERVER_CONFIG.protocol || 'https').replace(/:$/, '')
|
||||
const host = String(SERVER_CONFIG.host || '').trim()
|
||||
const port = String(SERVER_CONFIG.port || '').trim()
|
||||
|
||||
if (!host) {
|
||||
throw new Error('SERVER_CONFIG.host 未配置')
|
||||
}
|
||||
|
||||
const hasDefaultPort = (protocol === 'https' && port === '443') || (protocol === 'http' && port === '80')
|
||||
const portPart = !port || hasDefaultPort ? '' : `:${port}`
|
||||
return `${protocol}://${host}${portPart}`
|
||||
}
|
||||
|
||||
function normalizePath(path, fallback) {
|
||||
const value = String(path || fallback || '').trim()
|
||||
if (!value) {
|
||||
return '/'
|
||||
}
|
||||
return value.startsWith('/') ? value : `/${value}`
|
||||
}
|
||||
|
||||
function buildRuntimeConfig() {
|
||||
const origin = buildOrigin()
|
||||
const apiPrefix = normalizePath(SERVER_CONFIG.apiPrefix, '/api').replace(/\/$/, '')
|
||||
const fontsManifestPath = normalizePath(SERVER_CONFIG.fontsManifestPath, '/miniprogram/assets/fonts.json')
|
||||
const defaultConfigPath = normalizePath(SERVER_CONFIG.defaultConfigPath, '/miniprogram/assets/default.json')
|
||||
|
||||
return {
|
||||
fontsBaseUrl: origin,
|
||||
fontsManifestUrl: `${origin}${fontsManifestPath}`,
|
||||
defaultConfigUrl: `${origin}${defaultConfigPath}`,
|
||||
svgRenderApiUrl: `${origin}${apiPrefix}/render-svg`,
|
||||
pngRenderApiUrl: `${origin}${apiPrefix}/render-png`,
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
SERVER_CONFIG,
|
||||
buildRuntimeConfig,
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
const { loadFontsManifest } = require('../../utils/mp/font-loader')
|
||||
const { loadFontsManifest, loadDefaultConfig } = require('../../utils/mp/font-loader')
|
||||
const { loadAppState, saveAppState, loadFavorites, saveFavorites } = require('../../utils/mp/storage')
|
||||
const {
|
||||
shareSvgFromUserTap,
|
||||
@@ -11,8 +11,11 @@ const COLOR_PALETTE = ['#000000', '#1d4ed8', '#047857', '#b45309', '#dc2626', '#
|
||||
const FONT_SIZE_MIN = 20
|
||||
const FONT_SIZE_MAX = 120
|
||||
const PREVIEW_RENDER_FONT_SIZE = 120
|
||||
const PREVIEW_MAX_CHARS_PER_LINE = 45
|
||||
const PREVIEW_CACHE_LIMIT = 300
|
||||
const MIN_PREVIEW_IMAGE_WIDTH = 24
|
||||
const MAX_PREVIEW_IMAGE_WIDTH = 2400
|
||||
const FEEDBACK_EMAIL = 'douboer@gmail.com'
|
||||
|
||||
// 临时使用本地图标 - 根据Figma annotation配置
|
||||
const LOCAL_ICON_PATHS = {
|
||||
@@ -58,6 +61,31 @@ function clampFontSize(value, fallback = PREVIEW_RENDER_FONT_SIZE) {
|
||||
return Math.min(FONT_SIZE_MAX, Math.max(FONT_SIZE_MIN, Math.round(base)))
|
||||
}
|
||||
|
||||
function normalizeSelectedFontIds(fontIds) {
|
||||
if (!Array.isArray(fontIds)) {
|
||||
return []
|
||||
}
|
||||
return Array.from(
|
||||
new Set(
|
||||
fontIds
|
||||
.map((id) => String(id || '').trim())
|
||||
.filter(Boolean),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
function buildLegacyFontId(font) {
|
||||
if (!font || typeof font !== 'object') {
|
||||
return ''
|
||||
}
|
||||
const category = String(font.category || '').trim()
|
||||
const name = String(font.name || '').trim()
|
||||
if (!category || !name) {
|
||||
return ''
|
||||
}
|
||||
return `${category}/${name}`
|
||||
}
|
||||
|
||||
function extractErrorMessage(error, fallback) {
|
||||
if (!error) {
|
||||
return fallback
|
||||
@@ -84,6 +112,25 @@ function writePngBufferToTempFile(pngBuffer, fontName) {
|
||||
return filePath
|
||||
}
|
||||
|
||||
function buildPreviewCacheKey(fontId, text, letterSpacing, maxCharsPerLine = PREVIEW_MAX_CHARS_PER_LINE) {
|
||||
const safeFontId = String(fontId || '')
|
||||
const safeText = String(text || '')
|
||||
const spacingNumber = Number(letterSpacing)
|
||||
const safeSpacing = Number.isFinite(spacingNumber) ? spacingNumber.toFixed(4) : '0.0000'
|
||||
return [safeFontId, safeSpacing, String(maxCharsPerLine), safeText].join('::')
|
||||
}
|
||||
|
||||
function setPreviewCache(previewCache, key, value) {
|
||||
previewCache.set(key, value)
|
||||
if (previewCache.size <= PREVIEW_CACHE_LIMIT) {
|
||||
return
|
||||
}
|
||||
const oldestKey = previewCache.keys().next().value
|
||||
if (oldestKey !== undefined) {
|
||||
previewCache.delete(oldestKey)
|
||||
}
|
||||
}
|
||||
|
||||
function formatSvgNumber(value) {
|
||||
const text = String(Number(value).toFixed(2))
|
||||
return text.replace(/\.?0+$/, '')
|
||||
@@ -146,9 +193,9 @@ function applyLocalStyleToFontItem(font, fontSize, color) {
|
||||
Page({
|
||||
data: {
|
||||
inputText: '星程字体转换',
|
||||
fontSize: FONT_SIZE_MAX,
|
||||
fontSize: 50,
|
||||
letterSpacingInput: '0',
|
||||
textColor: '#000000',
|
||||
textColor: '#dc2626',
|
||||
colorPalette: COLOR_PALETTE,
|
||||
selectedFonts: [], // 当前已选中的字体列表
|
||||
fontCategories: [], // 字体分类树
|
||||
@@ -160,6 +207,7 @@ Page({
|
||||
// 搜索功能
|
||||
searchKeyword: '',
|
||||
showSearch: true,
|
||||
feedbackEmail: FEEDBACK_EMAIL,
|
||||
},
|
||||
|
||||
async onLoad() {
|
||||
@@ -170,6 +218,8 @@ Page({
|
||||
console.log('============================')
|
||||
|
||||
this.fontMap = new Map()
|
||||
this.legacyFontIdMap = new Map()
|
||||
this.previewCache = new Map()
|
||||
this.generateTimer = null
|
||||
this.categoryExpandedMap = {}
|
||||
|
||||
@@ -177,7 +227,11 @@ Page({
|
||||
},
|
||||
|
||||
onShow() {
|
||||
const favorites = loadFavorites()
|
||||
const rawFavorites = loadFavorites()
|
||||
const favorites = this.normalizeFontIdList(rawFavorites)
|
||||
if (normalizeSelectedFontIds(rawFavorites).join(',') !== favorites.join(',')) {
|
||||
saveFavorites(favorites)
|
||||
}
|
||||
this.setData({ favorites })
|
||||
this.updateFontTrees()
|
||||
},
|
||||
@@ -196,35 +250,126 @@ Page({
|
||||
this.setData({ selectedFonts })
|
||||
},
|
||||
|
||||
buildFontMaps(fonts) {
|
||||
this.fontMap = new Map()
|
||||
this.legacyFontIdMap = new Map()
|
||||
|
||||
if (!Array.isArray(fonts)) {
|
||||
return
|
||||
}
|
||||
|
||||
fonts.forEach((font) => {
|
||||
const fontId = String(font.id || '').trim()
|
||||
if (!fontId) {
|
||||
return
|
||||
}
|
||||
|
||||
this.fontMap.set(fontId, font)
|
||||
|
||||
const legacyId = buildLegacyFontId(font)
|
||||
if (legacyId && !this.legacyFontIdMap.has(legacyId)) {
|
||||
this.legacyFontIdMap.set(legacyId, fontId)
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
resolveFontId(fontId) {
|
||||
const normalizedId = String(fontId || '').trim()
|
||||
if (!normalizedId) {
|
||||
return ''
|
||||
}
|
||||
|
||||
const hasFontMap = this.fontMap instanceof Map && this.fontMap.size > 0
|
||||
if (!hasFontMap) {
|
||||
return normalizedId
|
||||
}
|
||||
|
||||
if (this.fontMap.has(normalizedId)) {
|
||||
return normalizedId
|
||||
}
|
||||
|
||||
if (this.legacyFontIdMap instanceof Map && this.legacyFontIdMap.has(normalizedId)) {
|
||||
return this.legacyFontIdMap.get(normalizedId) || ''
|
||||
}
|
||||
|
||||
return ''
|
||||
},
|
||||
|
||||
normalizeFontIdList(fontIds) {
|
||||
const normalized = normalizeSelectedFontIds(fontIds)
|
||||
return Array.from(
|
||||
new Set(
|
||||
normalized
|
||||
.map((fontId) => this.resolveFontId(fontId))
|
||||
.filter(Boolean),
|
||||
),
|
||||
)
|
||||
},
|
||||
|
||||
async bootstrap() {
|
||||
wx.showLoading({ title: '加载中', mask: true })
|
||||
try {
|
||||
const state = loadAppState()
|
||||
const fonts = await loadFontsManifest()
|
||||
const favorites = loadFavorites()
|
||||
const isFirstLaunch = !state || !state.updatedAt
|
||||
const [fonts, defaultConfig] = await Promise.all([
|
||||
loadFontsManifest(),
|
||||
loadDefaultConfig(),
|
||||
])
|
||||
this.buildFontMaps(fonts)
|
||||
|
||||
for (const font of fonts) {
|
||||
this.fontMap.set(font.id, font)
|
||||
const rawFavorites = loadFavorites()
|
||||
const normalizedStoredFavorites = this.normalizeFontIdList(rawFavorites)
|
||||
const normalizedDefaultFavorites = this.normalizeFontIdList(defaultConfig.favoriteFontIds)
|
||||
const favorites = isFirstLaunch ? normalizedDefaultFavorites : normalizedStoredFavorites
|
||||
|
||||
if (
|
||||
normalizeSelectedFontIds(rawFavorites).join(',') !== favorites.join(',') ||
|
||||
(isFirstLaunch && favorites.length > 0)
|
||||
) {
|
||||
saveFavorites(favorites)
|
||||
}
|
||||
|
||||
const initialInputText = isFirstLaunch
|
||||
? ((typeof defaultConfig.inputText === 'string' && defaultConfig.inputText) || this.data.inputText)
|
||||
: (state.inputText || this.data.inputText)
|
||||
const initialFontSize = isFirstLaunch
|
||||
? clampFontSize(defaultConfig.fontSize, this.data.fontSize)
|
||||
: clampFontSize(state.fontSize, this.data.fontSize)
|
||||
const initialLetterSpacingInput = isFirstLaunch
|
||||
? (
|
||||
typeof defaultConfig.letterSpacing === 'number'
|
||||
? String(defaultConfig.letterSpacing)
|
||||
: this.data.letterSpacingInput
|
||||
)
|
||||
: (
|
||||
typeof state.letterSpacing === 'number'
|
||||
? String(state.letterSpacing)
|
||||
: this.data.letterSpacingInput
|
||||
)
|
||||
const initialTextColor = isFirstLaunch
|
||||
? normalizeHexColor(defaultConfig.textColor || this.data.textColor)
|
||||
: normalizeHexColor(state.textColor || this.data.textColor)
|
||||
|
||||
this.setData({
|
||||
inputText: state.inputText || this.data.inputText,
|
||||
fontSize: clampFontSize(state.fontSize, this.data.fontSize),
|
||||
letterSpacingInput:
|
||||
typeof state.letterSpacing === 'number' ? String(state.letterSpacing) : this.data.letterSpacingInput,
|
||||
textColor: normalizeHexColor(state.textColor || this.data.textColor),
|
||||
inputText: initialInputText,
|
||||
fontSize: initialFontSize,
|
||||
letterSpacingInput: initialLetterSpacingInput,
|
||||
textColor: initialTextColor,
|
||||
favorites,
|
||||
})
|
||||
this.categoryExpandedMap = state.categoryExpandedMap && typeof state.categoryExpandedMap === 'object'
|
||||
this.categoryExpandedMap = !isFirstLaunch && state.categoryExpandedMap && typeof state.categoryExpandedMap === 'object'
|
||||
? { ...state.categoryExpandedMap }
|
||||
: {}
|
||||
|
||||
// 构建字体树
|
||||
this.updateFontTrees()
|
||||
|
||||
// 如果有保存的选中字体,恢复它们
|
||||
if (state.selectedFontIds && state.selectedFontIds.length > 0) {
|
||||
const selectedFonts = state.selectedFontIds
|
||||
// 恢复选中字体(首次使用走 default.json,后续走本地用户配置)
|
||||
const rawSelectedFontIds = isFirstLaunch ? defaultConfig.selectedFontIds : state.selectedFontIds
|
||||
const normalizedStoredSelectedIds = normalizeSelectedFontIds(rawSelectedFontIds)
|
||||
const initialSelectedFontIds = this.normalizeFontIdList(normalizedStoredSelectedIds)
|
||||
if (initialSelectedFontIds.length > 0) {
|
||||
const selectedFonts = initialSelectedFontIds
|
||||
.map(id => this.fontMap.get(id))
|
||||
.filter(font => font)
|
||||
.map(font => ({
|
||||
@@ -240,6 +385,21 @@ Page({
|
||||
this.updateFontTrees()
|
||||
await this.generateAllPreviews()
|
||||
}
|
||||
|
||||
const migratedSelectedIdsChanged = !isFirstLaunch &&
|
||||
normalizedStoredSelectedIds.join(',') !== initialSelectedFontIds.join(',')
|
||||
|
||||
// 首次加载后立即固化配置,后续全部以用户配置为准
|
||||
if (isFirstLaunch || migratedSelectedIdsChanged) {
|
||||
saveAppState({
|
||||
inputText: this.data.inputText,
|
||||
selectedFontIds: this.data.selectedFonts.map(font => font.id),
|
||||
fontSize: Number(this.data.fontSize),
|
||||
letterSpacing: Number(this.data.letterSpacingInput || 0),
|
||||
textColor: this.data.textColor,
|
||||
categoryExpandedMap: this.categoryExpandedMap,
|
||||
})
|
||||
}
|
||||
} catch (error) {
|
||||
const message = error && error.message ? error.message : '初始化失败'
|
||||
wx.showToast({ title: message, icon: 'none', duration: 2200 })
|
||||
@@ -317,7 +477,7 @@ Page({
|
||||
cat.allSelected = false
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
favoriteFonts.sort((a, b) => {
|
||||
const categoryCompare = String(a.category || '').localeCompare(String(b.category || ''))
|
||||
if (categoryCompare !== 0) return categoryCompare
|
||||
@@ -430,7 +590,7 @@ Page({
|
||||
},
|
||||
|
||||
// 切换分类全选/取消全选
|
||||
onToggleSelectAllInCategory(e) {
|
||||
async onToggleSelectAllInCategory(e) {
|
||||
const category = e.currentTarget.dataset.category
|
||||
if (!category) return
|
||||
|
||||
@@ -438,8 +598,9 @@ Page({
|
||||
if (!categoryFonts || categoryFonts.fonts.length === 0) return
|
||||
|
||||
const allSelected = categoryFonts.allSelected
|
||||
const selectedFonts = [...this.data.selectedFonts]
|
||||
const selectedIdSet = new Set(selectedFonts.map(f => f.id))
|
||||
const previousSelectedMap = new Map(this.data.selectedFonts.map(font => [font.id, font]))
|
||||
const selectedIdSet = new Set(previousSelectedMap.keys())
|
||||
const addedFontIds = []
|
||||
|
||||
if (allSelected) {
|
||||
// 取消全选:移除该分类下的所有字体
|
||||
@@ -449,6 +610,9 @@ Page({
|
||||
} else {
|
||||
// 全选:添加该分类下的所有字体
|
||||
categoryFonts.fonts.forEach(font => {
|
||||
if (!selectedIdSet.has(font.id)) {
|
||||
addedFontIds.push(font.id)
|
||||
}
|
||||
selectedIdSet.add(font.id)
|
||||
})
|
||||
}
|
||||
@@ -456,19 +620,32 @@ Page({
|
||||
const newSelectedFonts = []
|
||||
this.fontMap.forEach(font => {
|
||||
if (selectedIdSet.has(font.id)) {
|
||||
newSelectedFonts.push({
|
||||
id: font.id,
|
||||
name: font.name,
|
||||
category: font.category,
|
||||
showInPreview: true,
|
||||
previewSrc: '',
|
||||
})
|
||||
const existingFont = previousSelectedMap.get(font.id)
|
||||
if (existingFont) {
|
||||
newSelectedFonts.push({
|
||||
...existingFont,
|
||||
showInPreview: typeof existingFont.showInPreview === 'boolean' ? existingFont.showInPreview : true,
|
||||
})
|
||||
} else {
|
||||
newSelectedFonts.push({
|
||||
id: font.id,
|
||||
name: font.name,
|
||||
category: font.category,
|
||||
showInPreview: true,
|
||||
previewSrc: '',
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
this.setData({ selectedFonts: newSelectedFonts })
|
||||
this.updateFontTrees()
|
||||
this.scheduleGenerate()
|
||||
|
||||
if (addedFontIds.length > 0) {
|
||||
for (const fontId of addedFontIds) {
|
||||
await this.generatePreviewForFont(fontId)
|
||||
}
|
||||
}
|
||||
|
||||
saveAppState({
|
||||
inputText: this.data.inputText,
|
||||
@@ -488,6 +665,26 @@ Page({
|
||||
|
||||
try {
|
||||
const letterSpacing = Number(this.data.letterSpacingInput || 0)
|
||||
const cacheKey = buildPreviewCacheKey(fontId, text, letterSpacing, PREVIEW_MAX_CHARS_PER_LINE)
|
||||
const cached = this.previewCache.get(cacheKey)
|
||||
|
||||
if (cached) {
|
||||
const selectedFonts = this.data.selectedFonts
|
||||
const index = selectedFonts.findIndex(f => f.id === fontId)
|
||||
if (index >= 0) {
|
||||
selectedFonts[index].baseSvg = cached.svg
|
||||
selectedFonts[index].baseWidth = cached.width
|
||||
selectedFonts[index].baseHeight = cached.height
|
||||
selectedFonts[index].renderFontSize = PREVIEW_RENDER_FONT_SIZE
|
||||
selectedFonts[index] = applyLocalStyleToFontItem(
|
||||
selectedFonts[index],
|
||||
Number(this.data.fontSize),
|
||||
this.data.textColor,
|
||||
)
|
||||
this.setData({ selectedFonts })
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
const result = await renderSvgByApi({
|
||||
fontId,
|
||||
@@ -496,7 +693,12 @@ Page({
|
||||
fontSize: PREVIEW_RENDER_FONT_SIZE,
|
||||
fillColor: '#000000',
|
||||
letterSpacing,
|
||||
maxCharsPerLine: 45,
|
||||
maxCharsPerLine: PREVIEW_MAX_CHARS_PER_LINE,
|
||||
})
|
||||
setPreviewCache(this.previewCache, cacheKey, {
|
||||
svg: result.svg,
|
||||
width: result.width,
|
||||
height: result.height,
|
||||
})
|
||||
|
||||
// 更新对应字体的预览
|
||||
@@ -850,4 +1052,42 @@ Page({
|
||||
this.setData({ searchKeyword: keyword })
|
||||
this.updateFontTrees()
|
||||
},
|
||||
|
||||
onTapFeedbackEmail() {
|
||||
const email = this.data.feedbackEmail || FEEDBACK_EMAIL
|
||||
const mailtoUrl = `mailto:${email}`
|
||||
|
||||
// 微信环境对 mailto 支持不稳定:优先尝试跳转,失败时自动复制邮箱
|
||||
if (typeof wx.openUrl === 'function') {
|
||||
wx.openUrl({
|
||||
url: mailtoUrl,
|
||||
fail: () => {
|
||||
this.copyFeedbackEmail(email)
|
||||
},
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
this.copyFeedbackEmail(email)
|
||||
},
|
||||
|
||||
copyFeedbackEmail(email) {
|
||||
wx.setClipboardData({
|
||||
data: String(email || FEEDBACK_EMAIL),
|
||||
success: () => {
|
||||
wx.showModal({
|
||||
title: '已复制邮箱',
|
||||
content: '请打开邮件应用并粘贴收件人地址发送反馈。',
|
||||
showCancel: false,
|
||||
confirmText: '知道了',
|
||||
})
|
||||
},
|
||||
fail: () => {
|
||||
wx.showToast({
|
||||
title: '邮箱复制失败',
|
||||
icon: 'none',
|
||||
})
|
||||
},
|
||||
})
|
||||
},
|
||||
})
|
||||
|
||||
@@ -177,7 +177,8 @@
|
||||
|
||||
<!-- 版权说明 -->
|
||||
<view class="copyright-footer">
|
||||
@版权说明:仅SVG和PNG分享,无TTF下载,如侵权,反馈:douboer@gmail.com
|
||||
<text>@版权说明:仅SVG和PNG分享,无TTF下载,如侵权,反馈:</text>
|
||||
<text class="copyright-email-link" bindtap="onTapFeedbackEmail">{{feedbackEmail}}</text>
|
||||
</view>
|
||||
|
||||
<!-- 颜色选择器弹窗 -->
|
||||
|
||||
@@ -233,7 +233,7 @@
|
||||
.preview-content {
|
||||
background: transparent;
|
||||
padding: 4rpx 0;
|
||||
min-height: 80rpx;
|
||||
min-height: 40rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
@@ -284,6 +284,11 @@
|
||||
padding: 8rpx 8rpx;
|
||||
overflow: hidden;
|
||||
min-width: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.favorite-selection {
|
||||
border-left: 2rpx solid #3EE4C3;
|
||||
}
|
||||
|
||||
/* 搜索相关样式 */
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
const { request, downloadFile, readFile } = require('./wx-promisify')
|
||||
|
||||
const localFonts = require('../../assets/fonts')
|
||||
const localDefaultConfig = require('../../assets/default')
|
||||
|
||||
const fontBufferCache = new Map()
|
||||
const MAX_FONT_CACHE = 4
|
||||
@@ -44,6 +45,70 @@ function normalizeManifest(fonts, baseUrl) {
|
||||
.filter((item) => item.url)
|
||||
}
|
||||
|
||||
function normalizeDefaultConfig(config) {
|
||||
const payload = config && typeof config === 'object' ? config : {}
|
||||
const selectedRaw = Array.isArray(payload.selectedFontIds)
|
||||
? payload.selectedFontIds
|
||||
: (Array.isArray(payload.selectedFonts) ? payload.selectedFonts : [])
|
||||
const favoriteRaw = Array.isArray(payload.favoriteFontIds)
|
||||
? payload.favoriteFontIds
|
||||
: (Array.isArray(payload.favoriteFonts) ? payload.favoriteFonts : [])
|
||||
|
||||
const selectedFontIds = Array.from(
|
||||
new Set(
|
||||
selectedRaw
|
||||
.map((item) => String(item || '').trim())
|
||||
.filter(Boolean),
|
||||
),
|
||||
)
|
||||
const favoriteFontIds = Array.from(
|
||||
new Set(
|
||||
favoriteRaw
|
||||
.map((item) => String(item || '').trim())
|
||||
.filter(Boolean),
|
||||
),
|
||||
)
|
||||
|
||||
const result = {
|
||||
selectedFontIds,
|
||||
favoriteFontIds,
|
||||
}
|
||||
|
||||
if (typeof payload.inputText === 'string') {
|
||||
result.inputText = payload.inputText
|
||||
}
|
||||
if (typeof payload.textColor === 'string') {
|
||||
result.textColor = payload.textColor
|
||||
}
|
||||
if (payload.fontSize !== undefined && payload.fontSize !== null && payload.fontSize !== '') {
|
||||
const fontSize = Number(payload.fontSize)
|
||||
if (Number.isFinite(fontSize)) {
|
||||
result.fontSize = fontSize
|
||||
}
|
||||
}
|
||||
if (payload.letterSpacing !== undefined && payload.letterSpacing !== null && payload.letterSpacing !== '') {
|
||||
const letterSpacing = Number(payload.letterSpacing)
|
||||
if (Number.isFinite(letterSpacing)) {
|
||||
result.letterSpacing = letterSpacing
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
function buildDefaultConfigUrl(manifestUrl, baseUrl) {
|
||||
const manifest = String(manifestUrl || '').trim()
|
||||
if (manifest) {
|
||||
const withoutHash = manifest.split('#')[0]
|
||||
const [pathPart] = withoutHash.split('?')
|
||||
const slashIndex = pathPart.lastIndexOf('/')
|
||||
if (slashIndex >= 0) {
|
||||
return `${pathPart.slice(0, slashIndex + 1)}default.json`
|
||||
}
|
||||
}
|
||||
return normalizePath('/miniprogram/assets/default.json', baseUrl)
|
||||
}
|
||||
|
||||
async function loadFontsManifest(options = {}) {
|
||||
const app = getApp()
|
||||
const manifestUrl = options.manifestUrl || app.globalData.fontsManifestUrl
|
||||
@@ -79,6 +144,40 @@ async function loadFontsManifest(options = {}) {
|
||||
}
|
||||
}
|
||||
|
||||
async function loadDefaultConfig(options = {}) {
|
||||
const app = getApp()
|
||||
const manifestUrl = options.manifestUrl || app.globalData.fontsManifestUrl
|
||||
const baseUrl = options.baseUrl || app.globalData.fontsBaseUrl
|
||||
const defaultConfigUrl = options.defaultConfigUrl ||
|
||||
app.globalData.defaultConfigUrl ||
|
||||
buildDefaultConfigUrl(manifestUrl, baseUrl)
|
||||
|
||||
if (app.globalData.defaultConfig && typeof app.globalData.defaultConfig === 'object') {
|
||||
return app.globalData.defaultConfig
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await request({
|
||||
url: defaultConfigUrl,
|
||||
method: 'GET',
|
||||
timeout: 10000,
|
||||
})
|
||||
|
||||
if (response.statusCode < 200 || response.statusCode >= 300) {
|
||||
throw new Error(`获取默认配置失败,状态码: ${response.statusCode}`)
|
||||
}
|
||||
|
||||
const remoteConfig = normalizeDefaultConfig(response.data)
|
||||
app.globalData.defaultConfig = remoteConfig
|
||||
return remoteConfig
|
||||
} catch (error) {
|
||||
console.warn('远程 default.json 加载失败,回退到本地默认配置:', error)
|
||||
const fallbackConfig = normalizeDefaultConfig(localDefaultConfig)
|
||||
app.globalData.defaultConfig = fallbackConfig
|
||||
return fallbackConfig
|
||||
}
|
||||
}
|
||||
|
||||
function setLruCache(key, value) {
|
||||
if (fontBufferCache.has(key)) {
|
||||
fontBufferCache.delete(key)
|
||||
@@ -125,6 +224,7 @@ function listCategories(fonts) {
|
||||
|
||||
module.exports = {
|
||||
loadFontsManifest,
|
||||
loadDefaultConfig,
|
||||
loadFontBuffer,
|
||||
listCategories,
|
||||
}
|
||||
|
||||
@@ -86,9 +86,12 @@ async function renderPngByApi(payload) {
|
||||
? Number(app.globalData.apiTimeoutMs)
|
||||
: 30000
|
||||
const baseApiUrl = buildApiUrl()
|
||||
const apiUrl = /\/api\/render-svg$/.test(baseApiUrl)
|
||||
? baseApiUrl.replace(/\/api\/render-svg$/, '/api/render-png')
|
||||
: `${baseApiUrl.replace(/\/$/, '')}/render-png`
|
||||
const configuredPngApiUrl = app && app.globalData ? app.globalData.pngRenderApiUrl : ''
|
||||
const apiUrl = configuredPngApiUrl || (
|
||||
/\/api\/render-svg$/.test(baseApiUrl)
|
||||
? baseApiUrl.replace(/\/api\/render-svg$/, '/api/render-png')
|
||||
: `${baseApiUrl.replace(/\/$/, '')}/render-png`
|
||||
)
|
||||
|
||||
const response = await request({
|
||||
url: apiUrl,
|
||||
|
||||
Reference in New Issue
Block a user