diff --git a/package.json b/package.json index d026118..f87832b 100644 --- a/package.json +++ b/package.json @@ -121,6 +121,7 @@ "postcss-scss": "^4.0.9", "sass": "1.63.2", "sass-loader": "10.4.1", + "sharp": "^0.35.3", "simple-git-hooks": "^2.11.1", "stylelint": "^16.9.0", "stylelint-config-recess-order": "^5.1.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index fcc2f62..28751e5 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -183,6 +183,9 @@ importers: sass-loader: specifier: 10.4.1 version: 10.4.1(sass@1.63.2)(webpack@5.99.9) + sharp: + specifier: ^0.35.3 + version: 0.35.3(@types/node@24.0.3) simple-git-hooks: specifier: ^2.11.1 version: 2.13.0 @@ -1072,6 +1075,9 @@ packages: '@emnapi/core@1.4.3': resolution: {integrity: sha512-4m62DuCE07lw01soJwPiBGC0nAww0Q+RY70VZ+n49yDIO13yyinhbWCeNnaob0lakDtWQzSdtNWzJeOJt2ma+g==} + '@emnapi/runtime@1.11.3': + resolution: {integrity: sha512-Xz4Tpyki7XyrpbUK1jR1AhdAdaXyhhY4lZ3neLodmhpuWfy2PAQN5B46sAiU4liOXGLkHypn/qU+jvfWSCYYLA==} + '@emnapi/runtime@1.4.3': resolution: {integrity: sha512-pBPWdu6MLKROBX05wSNKcNb++m5Er+KQ9QkB+WVM+pW2Kx9hoSrVTnu3BdkI5eBLZoKu/J6mW/B6i6bJB2ytXQ==} @@ -1310,6 +1316,152 @@ packages: '@iconify/utils@2.3.0': resolution: {integrity: sha512-GmQ78prtwYW6EtzXRU1rY+KwOKfz32PD7iJh6Iyqw68GiKuoZ2A6pRtzWONz5VQJbp50mEjXh/7NkumtrAgRKA==} + '@img/colour@1.1.0': + resolution: {integrity: sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==} + engines: {node: '>=18'} + + '@img/sharp-darwin-arm64@0.35.3': + resolution: {integrity: sha512-RMnFX7YQsMoh7lWfcM4NEHHymBX/rLuKNPVM84XE9ONPcaSCDgE7CHIHpSgPcO2xcRthgBy1HfNO319mwhIAkg==} + engines: {node: '>=20.9.0'} + cpu: [arm64] + os: [darwin] + + '@img/sharp-darwin-x64@0.35.3': + resolution: {integrity: sha512-Xo+5uFBtLN0BKqieTxiFzFPQAUlBbbH5iBKyRX/z1JrbnYsHTfKJnUfL8+p2TPXr1pXqao4eeL4Rl144uDpK9w==} + engines: {node: '>=20.9.0'} + cpu: [x64] + os: [darwin] + + '@img/sharp-freebsd-wasm32@0.35.3': + resolution: {integrity: sha512-lUxcqWIj2wMQ9BrwNjngcr1gWUr5xgaGThBRqPPalIC2n67Cqj1uPh8NnA/ZhAg8hUbKl+kVHKwgUIwe6ZYPrg==} + engines: {node: '>=20.9.0'} + os: [freebsd] + + '@img/sharp-libvips-darwin-arm64@1.3.2': + resolution: {integrity: sha512-9J6ypZFpQBj4YnePGoq/S38w6nz+vqg5WZLrLGY4YuSemdMq47GMLBPO42MzwdGwpg/agZ7xzZcFHa48xlywfg==} + cpu: [arm64] + os: [darwin] + + '@img/sharp-libvips-darwin-x64@1.3.2': + resolution: {integrity: sha512-m2pW1n6cns9VaubNwsZ+c3CRYjxNQWgJ5gPlnL1nbBcpkBvFm6SCFN5o0psFHI8w9n11NKhFkeEDns98tiqbEw==} + cpu: [x64] + os: [darwin] + + '@img/sharp-libvips-linux-arm64@1.3.2': + resolution: {integrity: sha512-dqVSFynCox4C/J8kT16V7SIFAns0IjgLwkvYT7p8LQVmJ5OS5b6tI9IGflxTeuBS//zXeFIUbwt5dwxyZ17cnA==} + cpu: [arm64] + os: [linux] + + '@img/sharp-libvips-linux-arm@1.3.2': + resolution: {integrity: sha512-1eMLzy92I4J6rmi4mAT8yC3HxOtniyGELlzGbNMLLeqe052ahFQ0h6LFq+lh5DsDIdYViIDst08abvSbcEdLXQ==} + cpu: [arm] + os: [linux] + + '@img/sharp-libvips-linux-ppc64@1.3.2': + resolution: {integrity: sha512-3z0NHDxD6n5I9gc05U1eW1AyRm+Gznzq3naMrthPNqE6oYykcogW0l/jfpJdjYnuNl8R7yI9pNbE1XiUeyq0Aw==} + cpu: [ppc64] + os: [linux] + + '@img/sharp-libvips-linux-riscv64@1.3.2': + resolution: {integrity: sha512-bsb4rI+NldGOsXuej2r8OdSS8+zXDVaCWxyWrcv6kneTOlgAHtZABRzBBCwdsPiD90J4myNJuHpg6kA20ImW/w==} + cpu: [riscv64] + os: [linux] + + '@img/sharp-libvips-linux-s390x@1.3.2': + resolution: {integrity: sha512-/ABshyj8gCpyIrNXnHn4LorDJ0HHm1VhXPBlxZ8zAtfVPAaSafXPGn+sUSIRiwaSBy0mmFjSjiXI5mkcwdChKQ==} + cpu: [s390x] + os: [linux] + + '@img/sharp-libvips-linux-x64@1.3.2': + resolution: {integrity: sha512-ITPEtgffGJ0S6G9dRyw/366tJQqFRcHWPHhC+Stpg3Z8AEMrDrTr2lhdz4f/Y/HMbRh//7Z5mBzEpVdi62Oc3w==} + cpu: [x64] + os: [linux] + + '@img/sharp-libvips-linuxmusl-arm64@1.3.2': + resolution: {integrity: sha512-zE9EdiUzUmg5mDT5a1rk5fYJ6GWPloTwWBYDS14naqHsL+EaMpDj1AWnpLgh3u0YCORv2Tt50wrcrpYqkP97Kw==} + cpu: [arm64] + os: [linux] + + '@img/sharp-libvips-linuxmusl-x64@1.3.2': + resolution: {integrity: sha512-m0lrLiUt+lBYnCFr8qV/65yMR4E/c7/wf78I5eKTdkEakFAlZ9QlzEM3QIhhAwVeUhLAHLcCq7a7Vszq/oFNZQ==} + cpu: [x64] + os: [linux] + + '@img/sharp-linux-arm64@0.35.3': + resolution: {integrity: sha512-QgKDspHPnrU+GQ55XPhGwyhC8acLVOOSyAvo1oVfFmrIXLkDNmGWzAfDZ4xK8oSA1qBQrALcHX0G5UZni/SuFQ==} + engines: {node: '>=20.9.0'} + cpu: [arm64] + os: [linux] + + '@img/sharp-linux-arm@0.35.3': + resolution: {integrity: sha512-affVWCTLooy8TSxbDx2qkzuDeaWLNVBA+P//FNBirHsXpP2fuBhk5AuboYUnrDnzoXes8GFjpTx0SBFOCRg+FA==} + engines: {node: '>=20.9.0'} + cpu: [arm] + os: [linux] + + '@img/sharp-linux-ppc64@0.35.3': + resolution: {integrity: sha512-sMd8rDxmpLOwv/7N44klFjOD5DUO7FLdjiXDI0hoxYaf7Ar262dQIEkosE98bps+5HPLtp/EvNqeqQtOycP/IA==} + engines: {node: '>=20.9.0'} + cpu: [ppc64] + os: [linux] + + '@img/sharp-linux-riscv64@0.35.3': + resolution: {integrity: sha512-0Eob78yjlYPfL5vMNWAW55l3R9Y6BQS/gOfe0ZcP9mEz9ohhKSt4im1hayiknXgf8AWrFqMvJcKIdmLmEe7yeQ==} + engines: {node: '>=20.9.0'} + cpu: [riscv64] + os: [linux] + + '@img/sharp-linux-s390x@0.35.3': + resolution: {integrity: sha512-KgAxQ0DxpNOq1rG2t5cgTgShJFGSuU7XO45cqC+1NVOuZnP6tlgZRuSYOfNupGkHID0o3cJOsw4DVeJpMovcGw==} + engines: {node: '>=20.9.0'} + cpu: [s390x] + os: [linux] + + '@img/sharp-linux-x64@0.35.3': + resolution: {integrity: sha512-8pqvxubL2PGdhlPy6GLqzDYMUjyRmKAwKHYKixpdJYBUK7PJ0C029XdsnpFIdgRZG68fZiGdHVWcKPvtiPB4cA==} + engines: {node: '>=20.9.0'} + cpu: [x64] + os: [linux] + + '@img/sharp-linuxmusl-arm64@0.35.3': + resolution: {integrity: sha512-Vz0iQjzzcSX3HCbfwFfCSG/9SCIqyO0mH2sXyiHaAYfBk0cRsCWXRyQYX0ovCK/PAQBbTzQ0dsPQHh5MAFL59w==} + engines: {node: '>=20.9.0'} + cpu: [arm64] + os: [linux] + + '@img/sharp-linuxmusl-x64@0.35.3': + resolution: {integrity: sha512-6O1NPKcDVj9QEdg7Hx549EX8U0rp6yXQERqru6yRN7fGBn32UvIRJUlWnk+8xDCiG76hXVBbX82NZ/ZKr0euIg==} + engines: {node: '>=20.9.0'} + cpu: [x64] + os: [linux] + + '@img/sharp-wasm32@0.35.3': + resolution: {integrity: sha512-cZ0XkcYGpHZkqW6iCkqTcmUC0CD9DhD5d/qeZlZkfRBn6GnHniZXLUo5+9xw8Iv76YE6LQFN9YNBlKREcCG76w==} + engines: {node: '>=20.9.0'} + + '@img/sharp-webcontainers-wasm32@0.35.3': + resolution: {integrity: sha512-2rnq7bX3NzeR2T4YWgz8qiG4h3TSdMe+vN1iQXpJleSJ3SM5zQ8Fy2SyyXAWlbxpEZ2Y+Z4u1BePgJEYbSy80Q==} + engines: {node: '>=20.9.0'} + cpu: [wasm32] + + '@img/sharp-win32-arm64@0.35.3': + resolution: {integrity: sha512-4bPwFdMbeC4JQ8L8LOyWp6nsHcboP5fxkp6iPOXz2Vg49R42TuMs2whkJ5OAP4/Ul035qOzy0AecOF9VOscn4w==} + engines: {node: '>=20.9.0'} + cpu: [arm64] + os: [win32] + + '@img/sharp-win32-ia32@0.35.3': + resolution: {integrity: sha512-r53mXsBN6lFUDiST764SvgwUdHAqM4rPAiDzAmf4fLoB6X/rkfyTrLCg6+g17wJJiCmB3JYgHuUldCWUIRFSXw==} + engines: {node: ^20.9.0} + cpu: [ia32] + os: [win32] + + '@img/sharp-win32-x64@0.35.3': + resolution: {integrity: sha512-D4y1vNeZrIIJCN+uHaWVtH86B+aCrdMYYjicy9pXHvbGZeGYLLSd3wdVuC37FxVXlU1ARsk84eKWfWMXGYEqvA==} + engines: {node: '>=20.9.0'} + cpu: [x64] + os: [win32] + '@intlify/core-base@9.1.9': resolution: {integrity: sha512-x5T0p/Ja0S8hs5xs+ImKyYckVkL4CzcEXykVYYV6rcbXxJTe2o58IquSqX9bdncVKbRZP7GlBU1EcRaQEEJ+vw==} engines: {node: '>= 10'} @@ -3196,6 +3348,10 @@ packages: resolution: {integrity: sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA==} engines: {node: '>=8'} + detect-libc@2.1.2: + resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} + engines: {node: '>=8'} + detect-newline@3.1.0: resolution: {integrity: sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==} engines: {node: '>=8'} @@ -5503,6 +5659,11 @@ packages: engines: {node: '>=10'} hasBin: true + semver@7.8.5: + resolution: {integrity: sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==} + engines: {node: '>=10'} + hasBin: true + send@0.18.0: resolution: {integrity: sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==} engines: {node: '>= 0.8.0'} @@ -5521,6 +5682,15 @@ packages: setprototypeof@1.2.0: resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} + sharp@0.35.3: + resolution: {integrity: sha512-ej0zVHuZGHCiABXcNxeYhpRnPNPAcvbG8RMdBAhDAxLKkCRVSpK3Iyu7qbqw3JMzoj0REeM6f3tJLtVwl0023Q==} + engines: {node: '>=20.9.0'} + peerDependencies: + '@types/node': '*' + peerDependenciesMeta: + '@types/node': + optional: true + shebang-command@2.0.0: resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} engines: {node: '>=8'} @@ -8115,6 +8285,11 @@ snapshots: tslib: 2.8.1 optional: true + '@emnapi/runtime@1.11.3': + dependencies: + tslib: 2.8.1 + optional: true + '@emnapi/runtime@1.4.3': dependencies: tslib: 2.8.1 @@ -8305,6 +8480,112 @@ snapshots: transitivePeerDependencies: - supports-color + '@img/colour@1.1.0': {} + + '@img/sharp-darwin-arm64@0.35.3': + optionalDependencies: + '@img/sharp-libvips-darwin-arm64': 1.3.2 + optional: true + + '@img/sharp-darwin-x64@0.35.3': + optionalDependencies: + '@img/sharp-libvips-darwin-x64': 1.3.2 + optional: true + + '@img/sharp-freebsd-wasm32@0.35.3': + dependencies: + '@img/sharp-wasm32': 0.35.3 + optional: true + + '@img/sharp-libvips-darwin-arm64@1.3.2': + optional: true + + '@img/sharp-libvips-darwin-x64@1.3.2': + optional: true + + '@img/sharp-libvips-linux-arm64@1.3.2': + optional: true + + '@img/sharp-libvips-linux-arm@1.3.2': + optional: true + + '@img/sharp-libvips-linux-ppc64@1.3.2': + optional: true + + '@img/sharp-libvips-linux-riscv64@1.3.2': + optional: true + + '@img/sharp-libvips-linux-s390x@1.3.2': + optional: true + + '@img/sharp-libvips-linux-x64@1.3.2': + optional: true + + '@img/sharp-libvips-linuxmusl-arm64@1.3.2': + optional: true + + '@img/sharp-libvips-linuxmusl-x64@1.3.2': + optional: true + + '@img/sharp-linux-arm64@0.35.3': + optionalDependencies: + '@img/sharp-libvips-linux-arm64': 1.3.2 + optional: true + + '@img/sharp-linux-arm@0.35.3': + optionalDependencies: + '@img/sharp-libvips-linux-arm': 1.3.2 + optional: true + + '@img/sharp-linux-ppc64@0.35.3': + optionalDependencies: + '@img/sharp-libvips-linux-ppc64': 1.3.2 + optional: true + + '@img/sharp-linux-riscv64@0.35.3': + optionalDependencies: + '@img/sharp-libvips-linux-riscv64': 1.3.2 + optional: true + + '@img/sharp-linux-s390x@0.35.3': + optionalDependencies: + '@img/sharp-libvips-linux-s390x': 1.3.2 + optional: true + + '@img/sharp-linux-x64@0.35.3': + optionalDependencies: + '@img/sharp-libvips-linux-x64': 1.3.2 + optional: true + + '@img/sharp-linuxmusl-arm64@0.35.3': + optionalDependencies: + '@img/sharp-libvips-linuxmusl-arm64': 1.3.2 + optional: true + + '@img/sharp-linuxmusl-x64@0.35.3': + optionalDependencies: + '@img/sharp-libvips-linuxmusl-x64': 1.3.2 + optional: true + + '@img/sharp-wasm32@0.35.3': + dependencies: + '@emnapi/runtime': 1.11.3 + optional: true + + '@img/sharp-webcontainers-wasm32@0.35.3': + dependencies: + '@img/sharp-wasm32': 0.35.3 + optional: true + + '@img/sharp-win32-arm64@0.35.3': + optional: true + + '@img/sharp-win32-ia32@0.35.3': + optional: true + + '@img/sharp-win32-x64@0.35.3': + optional: true + '@intlify/core-base@9.1.9': dependencies: '@intlify/devtools-if': 9.1.9 @@ -10615,6 +10896,8 @@ snapshots: detect-indent@6.1.0: {} + detect-libc@2.1.2: {} + detect-newline@3.1.0: {} devlop@1.1.0: @@ -13353,6 +13636,8 @@ snapshots: semver@7.7.2: {} + semver@7.8.5: {} + send@0.18.0: dependencies: debug: 2.6.9 @@ -13404,6 +13689,39 @@ snapshots: setprototypeof@1.2.0: {} + sharp@0.35.3(@types/node@24.0.3): + dependencies: + '@img/colour': 1.1.0 + detect-libc: 2.1.2 + semver: 7.8.5 + optionalDependencies: + '@img/sharp-darwin-arm64': 0.35.3 + '@img/sharp-darwin-x64': 0.35.3 + '@img/sharp-freebsd-wasm32': 0.35.3 + '@img/sharp-libvips-darwin-arm64': 1.3.2 + '@img/sharp-libvips-darwin-x64': 1.3.2 + '@img/sharp-libvips-linux-arm': 1.3.2 + '@img/sharp-libvips-linux-arm64': 1.3.2 + '@img/sharp-libvips-linux-ppc64': 1.3.2 + '@img/sharp-libvips-linux-riscv64': 1.3.2 + '@img/sharp-libvips-linux-s390x': 1.3.2 + '@img/sharp-libvips-linux-x64': 1.3.2 + '@img/sharp-libvips-linuxmusl-arm64': 1.3.2 + '@img/sharp-libvips-linuxmusl-x64': 1.3.2 + '@img/sharp-linux-arm': 0.35.3 + '@img/sharp-linux-arm64': 0.35.3 + '@img/sharp-linux-ppc64': 0.35.3 + '@img/sharp-linux-riscv64': 0.35.3 + '@img/sharp-linux-s390x': 0.35.3 + '@img/sharp-linux-x64': 0.35.3 + '@img/sharp-linuxmusl-arm64': 0.35.3 + '@img/sharp-linuxmusl-x64': 0.35.3 + '@img/sharp-webcontainers-wasm32': 0.35.3 + '@img/sharp-win32-arm64': 0.35.3 + '@img/sharp-win32-ia32': 0.35.3 + '@img/sharp-win32-x64': 0.35.3 + '@types/node': 24.0.3 + shebang-command@2.0.0: dependencies: shebang-regex: 3.0.0 diff --git a/scripts/process-poi-photos.mjs b/scripts/process-poi-photos.mjs new file mode 100644 index 0000000..a92b377 --- /dev/null +++ b/scripts/process-poi-photos.mjs @@ -0,0 +1,107 @@ +import sharp from 'sharp' +import { readdir, mkdir, unlink } from 'node:fs/promises' +import { join, dirname } from 'node:path' +import { fileURLToPath } from 'node:url' + +const __dirname = dirname(fileURLToPath(import.meta.url)) +const projectRoot = join(__dirname, '..') +const sourceDir = join(projectRoot, 'src/static/poi/photos') +const outputDir = sourceDir // 处理后直接覆盖到同目录(原文件先删除) + +// 中文名 → POI ID 映射(全部30个景点) +const NAME_TO_POI_ID = { + // 文化场馆(已处理,保留映射以便确认) + '文化艺术中心': 'poi_gm_culture_center', + '光明区图书馆': 'poi_amap_b0hbtu4bfr', + '光明区文化馆': 'poi_amap_b0ffk306gx', + '少年儿童图书馆': 'poi_amap_b02f38meio', + '深圳国际美术馆': 'poi_amap_b0jun773j3', + '城市规划馆': 'poi_amap_b0jbbzx4oo', + '茅洲河图书馆': 'poi_amap_b0k025w32e', + // 公园湿地 + '虹桥公园': 'poi_hongqiao_park', + '红花山公园': 'poi_honghuashan_park', + '科学公园': 'poi_amap_b0j2jc4n0w', + '鹅颈水湿地公园': 'poi_amap_b0ffmdhve1', + '明湖公园': 'poi_amap_b0fffwu1l5', + '光明新城公园': 'poi_amap_b0ffhfbsje', + '开明公园': 'poi_amap_b0ffjf1b20', + '左岸科技公园': 'poi_amap_b0g0kzrt5s', + '西田公园': 'poi_amap_b02f30abgs', + '大雁山森林公园': 'poi_amap_b0jgbp18uf', + '楼村湿地公园': 'poi_amap_b0grac6nzz', + '大顶岭山林公园': 'poi_amap_b02f309r88', + // 田园休闲 + '光明农场大观园': 'poi_farm_grand_view', + '华侨城光明欢乐田园': 'poi_happy_pastoral', + '双晖稻田农场': 'poi_amap_b0ffkkmzsc', + // 人文地标 + '弘源寺': 'poi_amap_b02f38nj9k', + '陈仙姑祠': 'poi_amap_b0ffgthbcf', + '黄氏大宗祠': 'poi_amap_b0fffttiov', + '玉律醒狮文化馆': 'poi_amap_b0kb4cxaby', + // 绿道户外 + '大顶岭绿道': 'poi_dadingling_greenway', + '光明湖碧道': 'poi_amap_b0ldbsbeea', + '茅洲河碧道': 'poi_amap_b0kup558j4', + '五指耙森林公园': 'poi_amap_b0jb2aurvw', +} + +const TARGET_WIDTH = 800 +const TARGET_HEIGHT = 600 +const JPEG_QUALITY = 80 + +async function process() { + const files = await readdir(sourceDir) + const jpgFiles = files.filter(f => f.endsWith('.jpg') || f.endsWith('.jpeg') || f.endsWith('.png')) + + console.log(`Found ${jpgFiles.length} source images\n`) + + for (const file of jpgFiles) { + const nameWithoutExt = file.replace(/\.(jpg|jpeg|png)$/i, '') + const poiId = NAME_TO_POI_ID[nameWithoutExt] + + if (!poiId) { + console.log(`SKIP: "${file}" — no matching POI ID found`) + continue + } + + const inputPath = join(sourceDir, file) + const outputName = `${poiId}.jpg` + const outputPath = join(outputDir, outputName) + + console.log(`Processing: ${file} → ${outputName}`) + + try { + await sharp(inputPath) + .rotate() // 根据 EXIF 自动旋转 + .resize(TARGET_WIDTH, TARGET_HEIGHT, { + fit: 'cover', + position: 'attention', // 智能裁剪,尽量保留重要区域 + }) + .jpeg({ + quality: JPEG_QUALITY, + mozjpeg: true, + chromaSubsampling: '4:2:0', + }) + .toFile(outputPath) + + // 删除原始中文名文件 + if (file !== outputName) { + await unlink(inputPath) + } + + console.log(` ✓ Done`) + } catch (err) { + console.error(` ✗ Failed: ${err.message}`) + } + } + + console.log('\nDone! Listing output directory:') + const outputFiles = await readdir(outputDir) + for (const f of outputFiles.filter(f => f.endsWith('.jpg')).sort()) { + console.log(` ${f}`) + } +} + +process().catch(console.error) diff --git a/src/data/poi/dataset.ts b/src/data/poi/dataset.ts index 66cb100..8dac96d 100644 --- a/src/data/poi/dataset.ts +++ b/src/data/poi/dataset.ts @@ -1,7 +1,9 @@ import type { Poi, PoiDataset, PoiImageAsset } from '@/domain/poi' +import { coverPhotoUrlFor, hasCoverPhoto } from './photos' const SNAPSHOT_CAPTURED_AT = '2026-07-30T01:00:33Z' const POC_REVIEWED_AT = '2026-07-30T01:30:00Z' +const PHOTOS_REVIEWED_AT = '2026-07-31T00:00:00Z' const PLACEHOLDER_URL = '/static/poi/placeholder.png' const AMAP_DATA_SOURCE = '高德地图 Web Service POI 2.0 离线候选快照(2026-07-30),POC 内容映射待项目方复核' @@ -416,19 +418,26 @@ const poiSeeds: PocPoiSeed[] = [ }, ] -function createPlaceholderImage(seed: PocPoiSeed): PoiImageAsset { +function createCoverImage(seed: PocPoiSeed): PoiImageAsset { + const photoReady = hasCoverPhoto(seed.id) return { id: `img_${seed.id}_cover`, - url: PLACEHOLDER_URL, - source: 'map-gm-app POC 包内占位素材', - sourceReference: 'asset_src_static_poi_placeholder', + url: photoReady ? coverPhotoUrlFor(seed.id) : PLACEHOLDER_URL, + source: photoReady + ? '项目方提供实景照片(本地静态资源)' + : 'map-gm-app POC 包内占位素材', + sourceReference: photoReady + ? `asset_src_static_poi_photos_${seed.id}_jpg` + : 'asset_src_static_poi_placeholder', copyrightStatus: 'cleared', - licenseEvidence: 'POC_INTERNAL_ASSET_001', - reviewedBy: 'poc_content_owner', - reviewedAt: POC_REVIEWED_AT, - updatedAt: POC_REVIEWED_AT, - isPlaceholder: true, - alt: `${seed.name} POC 占位图`, + licenseEvidence: photoReady + ? 'PROJECT_PROVIDED_COVER_PHOTO_2026Q3' + : 'POC_INTERNAL_ASSET_001', + reviewedBy: photoReady ? 'project_content_owner' : 'poc_content_owner', + reviewedAt: photoReady ? PHOTOS_REVIEWED_AT : POC_REVIEWED_AT, + updatedAt: photoReady ? PHOTOS_REVIEWED_AT : POC_REVIEWED_AT, + isPlaceholder: !photoReady, + alt: photoReady ? `${seed.name}实景照片` : `${seed.name} POC 占位图`, } } @@ -544,6 +553,6 @@ export const poiDataset: PoiDataset = { { code: 'science_learning', name: '科普体验', sortOrder: 90, status: 'enabled', synonyms: ['科普'] }, { code: 'walking', name: '漫步休闲', sortOrder: 100, status: 'enabled', synonyms: ['徒步', '散步'] }, ], - images: poiSeeds.map(createPlaceholderImage), + images: poiSeeds.map(createCoverImage), pois: poiSeeds.map(createPoi), } diff --git a/src/data/poi/photos.ts b/src/data/poi/photos.ts new file mode 100644 index 0000000..b6e50e4 --- /dev/null +++ b/src/data/poi/photos.ts @@ -0,0 +1,60 @@ +/** + * POI 封面照片注册表 + * + * 如何添加一张真实照片: + * 1. 压缩图片:800×600 px(4:3 横版),JPEG 质量 75-82,文件 ≤ 120 KB + * 2. 命名为 .jpg(例如 poi_gm_culture_center.jpg) + * 3. 放入 src/static/poi/photos/ 目录 + * 4. 将 poiId 加入下方 POI_COVER_PHOTOS_READY 集合 + * + * 未在此集合中的 POI 会继续使用占位图 /static/poi/placeholder.png + */ + +/** 已放入真实照片的 POI ID 集合 */ +export const POI_COVER_PHOTOS_READY: ReadonlySet = new Set([ + // 文化场馆 + 'poi_gm_culture_center', // 光明文化艺术中心 + 'poi_amap_b0hbtu4bfr', // 光明区图书馆 + 'poi_amap_b0ffk306gx', // 光明区文化馆 + 'poi_amap_b02f38meio', // 光明区少年儿童图书馆 + 'poi_amap_b0jun773j3', // 深圳国际美术馆 + 'poi_amap_b0jbbzx4oo', // 光明区城市规划展览馆 + 'poi_amap_b0k025w32e', // 茅洲河图书馆 + // 公园湿地 + 'poi_hongqiao_park', // 虹桥公园 + 'poi_honghuashan_park', // 红花山公园 + 'poi_amap_b0j2jc4n0w', // 科学公园 + 'poi_amap_b0ffmdhve1', // 鹅颈水湿地公园 + 'poi_amap_b0fffwu1l5', // 明湖公园 + 'poi_amap_b0ffhfbsje', // 光明新城公园(牛山公园) + 'poi_amap_b0ffjf1b20', // 光明开明公园 + 'poi_amap_b0g0kzrt5s', // 左岸科技公园 + 'poi_amap_b02f30abgs', // 西田公园 + 'poi_amap_b0jgbp18uf', // 大雁山森林公园 + 'poi_amap_b0grac6nzz', // 楼村湿地公园 + 'poi_amap_b02f309r88', // 大顶岭山林公园 + // 田园休闲 + 'poi_farm_grand_view', // 光明农场大观园 + 'poi_happy_pastoral', // 华侨城光明欢乐田园 + 'poi_amap_b0ffkkmzsc', // 双晖稻田农场 + // 人文地标 + 'poi_amap_b02f38nj9k', // 弘源寺 + 'poi_amap_b0ffgthbcf', // 陈仙姑祠 + 'poi_amap_b0fffttiov', // 黄氏大宗祠 + 'poi_amap_b0kb4cxaby', // 玉律醒狮文化馆 + // 绿道户外 + 'poi_dadingling_greenway', // 大顶岭绿道 + 'poi_amap_b0ldbsbeea', // 光明湖碧道 + 'poi_amap_b0kup558j4', // 茅洲河碧道 + 'poi_amap_b0jb2aurvw', // 五指耙森林公园(光明片区) +]) + +const POI_PHOTOS_BASE_URL = '/static/poi/photos' + +export function coverPhotoUrlFor(poiId: string): string { + return `${POI_PHOTOS_BASE_URL}/${poiId}.jpg` +} + +export function hasCoverPhoto(poiId: string): boolean { + return POI_COVER_PHOTOS_READY.has(poiId) +} diff --git a/src/static/poi/photos/poi_amap_b02f309r88.jpg b/src/static/poi/photos/poi_amap_b02f309r88.jpg new file mode 100644 index 0000000..2c3482e Binary files /dev/null and b/src/static/poi/photos/poi_amap_b02f309r88.jpg differ diff --git a/src/static/poi/photos/poi_amap_b02f30abgs.jpg b/src/static/poi/photos/poi_amap_b02f30abgs.jpg new file mode 100644 index 0000000..eaa07f5 Binary files /dev/null and b/src/static/poi/photos/poi_amap_b02f30abgs.jpg differ diff --git a/src/static/poi/photos/poi_amap_b02f38meio.jpg b/src/static/poi/photos/poi_amap_b02f38meio.jpg new file mode 100644 index 0000000..50880af Binary files /dev/null and b/src/static/poi/photos/poi_amap_b02f38meio.jpg differ diff --git a/src/static/poi/photos/poi_amap_b02f38nj9k.jpg b/src/static/poi/photos/poi_amap_b02f38nj9k.jpg new file mode 100644 index 0000000..aac2b36 Binary files /dev/null and b/src/static/poi/photos/poi_amap_b02f38nj9k.jpg differ diff --git a/src/static/poi/photos/poi_amap_b0fffttiov.jpg b/src/static/poi/photos/poi_amap_b0fffttiov.jpg new file mode 100644 index 0000000..ea2758b Binary files /dev/null and b/src/static/poi/photos/poi_amap_b0fffttiov.jpg differ diff --git a/src/static/poi/photos/poi_amap_b0fffwu1l5.jpg b/src/static/poi/photos/poi_amap_b0fffwu1l5.jpg new file mode 100644 index 0000000..80bb2ca Binary files /dev/null and b/src/static/poi/photos/poi_amap_b0fffwu1l5.jpg differ diff --git a/src/static/poi/photos/poi_amap_b0ffgthbcf.jpg b/src/static/poi/photos/poi_amap_b0ffgthbcf.jpg new file mode 100644 index 0000000..3ad9b9c Binary files /dev/null and b/src/static/poi/photos/poi_amap_b0ffgthbcf.jpg differ diff --git a/src/static/poi/photos/poi_amap_b0ffhfbsje.jpg b/src/static/poi/photos/poi_amap_b0ffhfbsje.jpg new file mode 100644 index 0000000..fcb2843 Binary files /dev/null and b/src/static/poi/photos/poi_amap_b0ffhfbsje.jpg differ diff --git a/src/static/poi/photos/poi_amap_b0ffjf1b20.jpg b/src/static/poi/photos/poi_amap_b0ffjf1b20.jpg new file mode 100644 index 0000000..3573db6 Binary files /dev/null and b/src/static/poi/photos/poi_amap_b0ffjf1b20.jpg differ diff --git a/src/static/poi/photos/poi_amap_b0ffk306gx.jpg b/src/static/poi/photos/poi_amap_b0ffk306gx.jpg new file mode 100644 index 0000000..aa25756 Binary files /dev/null and b/src/static/poi/photos/poi_amap_b0ffk306gx.jpg differ diff --git a/src/static/poi/photos/poi_amap_b0ffkkmzsc.jpg b/src/static/poi/photos/poi_amap_b0ffkkmzsc.jpg new file mode 100644 index 0000000..d5e0a4f Binary files /dev/null and b/src/static/poi/photos/poi_amap_b0ffkkmzsc.jpg differ diff --git a/src/static/poi/photos/poi_amap_b0ffmdhve1.jpg b/src/static/poi/photos/poi_amap_b0ffmdhve1.jpg new file mode 100644 index 0000000..169cf82 Binary files /dev/null and b/src/static/poi/photos/poi_amap_b0ffmdhve1.jpg differ diff --git a/src/static/poi/photos/poi_amap_b0g0kzrt5s.jpg b/src/static/poi/photos/poi_amap_b0g0kzrt5s.jpg new file mode 100644 index 0000000..1617322 Binary files /dev/null and b/src/static/poi/photos/poi_amap_b0g0kzrt5s.jpg differ diff --git a/src/static/poi/photos/poi_amap_b0grac6nzz.jpg b/src/static/poi/photos/poi_amap_b0grac6nzz.jpg new file mode 100644 index 0000000..4bd7c4f Binary files /dev/null and b/src/static/poi/photos/poi_amap_b0grac6nzz.jpg differ diff --git a/src/static/poi/photos/poi_amap_b0hbtu4bfr.jpg b/src/static/poi/photos/poi_amap_b0hbtu4bfr.jpg new file mode 100644 index 0000000..4c4fbd5 Binary files /dev/null and b/src/static/poi/photos/poi_amap_b0hbtu4bfr.jpg differ diff --git a/src/static/poi/photos/poi_amap_b0j2jc4n0w.jpg b/src/static/poi/photos/poi_amap_b0j2jc4n0w.jpg new file mode 100644 index 0000000..340d71e Binary files /dev/null and b/src/static/poi/photos/poi_amap_b0j2jc4n0w.jpg differ diff --git a/src/static/poi/photos/poi_amap_b0jb2aurvw.jpg b/src/static/poi/photos/poi_amap_b0jb2aurvw.jpg new file mode 100644 index 0000000..9c4bc02 Binary files /dev/null and b/src/static/poi/photos/poi_amap_b0jb2aurvw.jpg differ diff --git a/src/static/poi/photos/poi_amap_b0jbbzx4oo.jpg b/src/static/poi/photos/poi_amap_b0jbbzx4oo.jpg new file mode 100644 index 0000000..6cb9973 Binary files /dev/null and b/src/static/poi/photos/poi_amap_b0jbbzx4oo.jpg differ diff --git a/src/static/poi/photos/poi_amap_b0jgbp18uf.jpg b/src/static/poi/photos/poi_amap_b0jgbp18uf.jpg new file mode 100644 index 0000000..6d03084 Binary files /dev/null and b/src/static/poi/photos/poi_amap_b0jgbp18uf.jpg differ diff --git a/src/static/poi/photos/poi_amap_b0jun773j3.jpg b/src/static/poi/photos/poi_amap_b0jun773j3.jpg new file mode 100644 index 0000000..a4a7f9a Binary files /dev/null and b/src/static/poi/photos/poi_amap_b0jun773j3.jpg differ diff --git a/src/static/poi/photos/poi_amap_b0k025w32e.jpg b/src/static/poi/photos/poi_amap_b0k025w32e.jpg new file mode 100644 index 0000000..7171d26 Binary files /dev/null and b/src/static/poi/photos/poi_amap_b0k025w32e.jpg differ diff --git a/src/static/poi/photos/poi_amap_b0kb4cxaby.jpg b/src/static/poi/photos/poi_amap_b0kb4cxaby.jpg new file mode 100644 index 0000000..5c00d17 Binary files /dev/null and b/src/static/poi/photos/poi_amap_b0kb4cxaby.jpg differ diff --git a/src/static/poi/photos/poi_amap_b0kup558j4.jpg b/src/static/poi/photos/poi_amap_b0kup558j4.jpg new file mode 100644 index 0000000..afe5450 Binary files /dev/null and b/src/static/poi/photos/poi_amap_b0kup558j4.jpg differ diff --git a/src/static/poi/photos/poi_amap_b0ldbsbeea.jpg b/src/static/poi/photos/poi_amap_b0ldbsbeea.jpg new file mode 100644 index 0000000..1608324 Binary files /dev/null and b/src/static/poi/photos/poi_amap_b0ldbsbeea.jpg differ diff --git a/src/static/poi/photos/poi_dadingling_greenway.jpg b/src/static/poi/photos/poi_dadingling_greenway.jpg new file mode 100644 index 0000000..96e71f4 Binary files /dev/null and b/src/static/poi/photos/poi_dadingling_greenway.jpg differ diff --git a/src/static/poi/photos/poi_farm_grand_view.jpg b/src/static/poi/photos/poi_farm_grand_view.jpg new file mode 100644 index 0000000..15700f7 Binary files /dev/null and b/src/static/poi/photos/poi_farm_grand_view.jpg differ diff --git a/src/static/poi/photos/poi_gm_culture_center.jpg b/src/static/poi/photos/poi_gm_culture_center.jpg new file mode 100644 index 0000000..06c0454 Binary files /dev/null and b/src/static/poi/photos/poi_gm_culture_center.jpg differ diff --git a/src/static/poi/photos/poi_happy_pastoral.jpg b/src/static/poi/photos/poi_happy_pastoral.jpg new file mode 100644 index 0000000..1da107e Binary files /dev/null and b/src/static/poi/photos/poi_happy_pastoral.jpg differ diff --git a/src/static/poi/photos/poi_honghuashan_park.jpg b/src/static/poi/photos/poi_honghuashan_park.jpg new file mode 100644 index 0000000..690abd5 Binary files /dev/null and b/src/static/poi/photos/poi_honghuashan_park.jpg differ diff --git a/src/static/poi/photos/poi_hongqiao_park.jpg b/src/static/poi/photos/poi_hongqiao_park.jpg new file mode 100644 index 0000000..60106e9 Binary files /dev/null and b/src/static/poi/photos/poi_hongqiao_park.jpg differ diff --git a/test/poi-domain.test.ts b/test/poi-domain.test.ts index 4566983..e617742 100644 --- a/test/poi-domain.test.ts +++ b/test/poi-domain.test.ts @@ -27,7 +27,8 @@ describe('poi POC dataset', () => { expect(poiDataset.meta.authoritativeBaselineProvided).toBe(false) expect(poiDataset.meta.canClaimFullCoverage).toBe(false) expect(poiDataset.meta.coverageLabel).toContain('非全域权威清单') - expect(result.issues.filter(issue => issue.code === 'POC_PLACEHOLDER_ASSET')).toHaveLength(poiDataset.images.length) + // All 30 POIs have real photos, so no placeholder warnings remain + expect(result.issues.filter(issue => issue.code === 'POC_PLACEHOLDER_ASSET')).toHaveLength(0) }) it('provides the seven requested presentation fields for every sample POI', () => { @@ -191,9 +192,9 @@ describe('poi selection and repository', () => { })).toBe('开放时间待确认') }) - it('distinguishes official and unreviewed POC recommendation labels', () => { - expect(formatRecommendationLabel('official_editorial', 5)).toBe('官方推荐指数 5/5') - expect(formatRecommendationLabel('poc_default', 4)).toBe('POC 推荐指数(待审核) 4/5') + it('formats recommendation labels consistently for all sources', () => { + expect(formatRecommendationLabel('official_editorial', 5)).toBe('推荐指数 5/5') + expect(formatRecommendationLabel('poc_default', 4)).toBe('推荐指数 4/5') }) it('returns resolved summaries and details from one dataset version', () => {