refactor: 产品大图迁移OSS,小图标本地打包,资源基址改为.env配置

This commit is contained in:
2026-08-06 23:52:48 +08:00
parent 8de00e6614
commit 0f3132b08e
77 changed files with 83 additions and 70 deletions
+2
View File
@@ -0,0 +1,2 @@
# 阿里云 OSS 资源桶基址,迁移时只改这一个变量
OSS_BASE_URL=https://wordcloudwechat.oss-cn-hangzhou.aliyuncs.com
+1
View File
@@ -2,3 +2,4 @@ node_modules
.claude .claude
.DS_Store .DS_Store
.swc .swc
.env
+16 -17
View File
@@ -16,7 +16,7 @@
``` ```
smart-engraving-miniapp/ smart-engraving-miniapp/
├── config/ # Taro 配置文件 ├── config/ # Taro 配置文件
│ └── index.js # 构建配置(含 copy patternsicon、img、theme.json 复制) │ └── index.js # 构建配置(含 copy patternsicon、theme.json 复制)
├── src/ ├── src/
│ ├── app.tsx # 应用入口(全局 ThemeProvider 挂载) │ ├── app.tsx # 应用入口(全局 ThemeProvider 挂载)
│ ├── app.scss # 全局样式(CSS 变量 + 主题工具类) │ ├── app.scss # 全局样式(CSS 变量 + 主题工具类)
@@ -58,8 +58,8 @@ smart-engraving-miniapp/
│ ├── settings/ # 设置(个人信息、账号管理、退出登录) │ ├── settings/ # 设置(个人信息、账号管理、退出登录)
│ ├── agreement/ # 使用协议 │ ├── agreement/ # 使用协议
│ └── userDatabase/ # 账号管理(密码保护:zhihui2024) │ └── userDatabase/ # 账号管理(密码保护:zhihui2024)
├── src/icon/ # 20 个 icon PNG28~56px打包到 dist/icon ├── src/icon/ # 20 个小图标 PNG/SVG打包到 dist/icon
├── src/img/ # 8 张产品实物压缩图(打包到 dist/img ├── OSS 产品图 # 产品实拍大图放在阿里云 OSSbucket: wordcloudwechat/img
├── package.json ├── package.json
├── tsconfig.json ├── tsconfig.json
└── project.config.json # 微信开发者工具配置 └── project.config.json # 微信开发者工具配置
@@ -194,7 +194,7 @@ npm run dev:h5
## 图片资源与包体积说明 ## 图片资源与包体积说明
当前项目的产品实物照片(`src/img/`)已内置在小程序中。由于微信小程序**预览/上传代码包体积上限为 2MB**,大量高分辨率照片会导致超限 产品实拍大图已迁移到阿里云 OSS`wordcloudwechat/img`),`src/img/` 本地目录已删除;由于微信小程序**上传代码包体积上限为 2MB**,大图继续放远端可以长期保持包体稳定
### 已采取的措施(两步压缩脚本) ### 已采取的措施(两步压缩脚本)
@@ -202,10 +202,10 @@ npm run dev:h5
1. **resize**:最大边长限制到 800px 1. **resize**:最大边长限制到 800px
2. **format**PNG → JPG 2. **format**PNG → JPG
3. **quality**JPG 质量 60% 3. **quality**JPG 质量 60%
- 执行一次即可:`node compress-images.js` - 产品大图目前已迁到 OSS`src/img` 本地目录已删除,不再需要执行本地压缩脚本
- 当前编译后 `dist/` 总大小约 **1.03 MB**(新增 1 个 icon 约 7KB),满足微信限制。 - 当前编译后 `dist/` 总大小约 **1.03 MB**(新增 1 个 icon 约 7KB),满足微信限制。
### ⚠️ 注意:压缩 ≠ 长期方案 ### 当前状态:已迁移 OSS
压缩后的图片在手机上画质会有可见损失(尤其缩放到全屏轮播时)。**建议上线前迁移到 CDN**: 压缩后的图片在手机上画质会有可见损失(尤其缩放到全屏轮播时)。**建议上线前迁移到 CDN**:
@@ -229,23 +229,22 @@ npm run dev:h5
### 阿里云 OSS 实际操作(当前桶:wordcloudwechat ### 阿里云 OSS 实际操作(当前桶:wordcloudwechat
1. 当前 Bucket `wordcloudwechat` 已创建:地域 `华东1(杭州)`,读写权限**公共读**,可直接使用。 1. 当前 Bucket `wordcloudwechat` 已创建:地域 `华东1(杭州)`,读写权限**公共读**,可直接使用。
2. 控制台方式:左侧点「文件管理」,先创建目录 `img/`、`icon/`,再把本地 `src/img/` 下所有文件传到 `img/``src/icon/` 下所有文件传到 `icon/`。目录层级必须和本地一致 2. 只需把 `src/img/` 下的产品大图传到 Bucket 的 `img/` 目录;`src/icon/` 等几 KB 以内的小图标继续打包在本地,不用传 OSS
3. 打开 `src/utils/asset.ts`,将 `ASSET_BASE_URL` 填成桶地址:
```ts
export const ASSET_BASE_URL = 'https://wordcloudwechat.oss-cn-hangzhou.aliyuncs.com'
```
`src/utils/asset.ts` 已默认填成上面的 OSS 地址;如果用 CNAME 域名,可改成 `https://wordcloudwechat.cn-hangzhou.taihangtop.cn`。 3. 资源基地址统一配在项目根目录 `.env`(已提供 `.env.example`):
4. 项目中商品图、首页成品图、商品 icon、设计清单/订单里已存的 icon 会自动拼接成远程 URL;本地路径作为兜底,后端没就绪时不裂图。 ```dotenv
5. 等 OSS 图片稳定后,可把 `config/index.js` 中 `src/img` 的 copy 规则移除,进一步压缩 dist 体积;`src/icon` 按需保留或也迁到 OSS。 OSS_BASE_URL=https://wordcloudwechat.oss-cn-hangzhou.aliyuncs.com
```
后续迁移换桶/换域名时,只需要改 `.env` 里的 `OSS_BASE_URL`。
4. 商品图、首页成品图会走 OSS;代码只在 `path` 以 `/img/` 开头时才拼接远程地址,`/icon/*` 小图标仍从包内加载。
5. 照片稳定走远程后,`config/index.js` 中 `src/img` 的 copy 规则不再需要(当前已移除)。
6. 微信小程序后台需要在「开发管理 - 服务器域名」配置 `downloadFile` 合法域名:`https://wordcloudwechat.oss-cn-hangzhou.aliyuncs.com`;如果改用了 CNAME,则填对应 HTTPS 域名。 6. 微信小程序后台需要在「开发管理 - 服务器域名」配置 `downloadFile` 合法域名:`https://wordcloudwechat.oss-cn-hangzhou.aliyuncs.com`;如果改用了 CNAME,则填对应 HTTPS 域名。
如果用 `ossutil64` 命令行一次传完,在项目根目录执行: 如果用 `ossutil64` 命令行一次传完,在项目根目录执行:
```bash ```bash
ossutil64 config ossutil64 config
ossutil64 cp -r -f img oss://wordcloudwechat/img ossutil64 cp -r -f src/img oss://wordcloudwechat/img
ossutil64 cp -r -f icon oss://wordcloudwechat/icon
``` ```
--- ---
@@ -329,7 +328,7 @@ ossutil64 cp -r -f icon oss://wordcloudwechat/icon
- [ ] 订单物流接口接入(目前为静态 mock) - [ ] 订单物流接口接入(目前为静态 mock)
### 中优先级 ### 中优先级
- [ ] 上传商品图/icon 到阿里云 OSS,并填写 `ASSET_BASE_URL`(前端资源基址已接好) - [x] 产品图已上传阿里云 OSS,资源基址通过 `.env` 的 `OSS_BASE_URL` 配置
- [ ] 添加更多词云底图模板 - [ ] 添加更多词云底图模板
- [ ] 支持从微信聊天记录导入 Excel 名单 - [ ] 支持从微信聊天记录导入 Excel 名单
- [ ] 实现设计稿保存到草稿箱功能 - [ ] 实现设计稿保存到草稿箱功能
+5 -3
View File
@@ -1,3 +1,5 @@
const path = require('path')
require('dotenv').config({ path: path.resolve(__dirname, '../.env') })
const config = { const config = {
projectName: 'smart-engraving-miniapp', projectName: 'smart-engraving-miniapp',
date: '2026-7-20', date: '2026-7-20',
@@ -11,12 +13,12 @@ const config = {
outputRoot: 'dist', outputRoot: 'dist',
plugins: [], plugins: [],
defineConstants: { defineConstants: {
__OSS_BASE_URL__: JSON.stringify(process.env.OSS_BASE_URL || ''),
}, },
copy: { copy: {
patterns: [ patterns: [
{ from: 'src/img', to: 'dist/img' }, { from: 'src/icon', to: 'dist/icon' },
{ from: 'src/icon', to: 'dist/icon' } { from: 'src/theme.json', to: 'dist/theme.json' }
,{ from: 'src/theme.json', to: 'dist/theme.json' }
], ],
options: { options: {
} }
+1 -1
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -1 +1 @@
"use strict";(wx["webpackJsonp"]=wx["webpackJsonp"]||[]).push([[777],{653:function(n,e,t){var a=t(7842),i=t(5544),c=t(118),o=t(758),r=t.n(o),s=t(6540),u=t(8594),l=t(9703),v=t(6606),g=t(4848),f=[{pagePath:"/pages/index/index",label:"\u9996\u9875",icon:"/icon/\u9996\u9875.svg",iconActive:"/icon/\u9996\u9875-fill.svg"},{pagePath:"/pages/shop/index",label:"\u5546\u54c1",icon:"/icon/\u5546\u54c1.svg",iconActive:"/icon/\u5546\u54c1-fill.svg"},{pagePath:"/pages/designList/index",label:"\u8bbe\u8ba1\u6e05\u5355",icon:"/icon/\u8c03\u8272\u76d8.svg",iconActive:"/icon/\u8c03\u8272\u76d8-fill.svg"},{pagePath:"/pages/orders/index",label:"\u8ba2\u5355",icon:"/icon/\u5305\u88f9.svg",iconActive:"/icon/\u5305\u88f9-fill.svg"},{pagePath:"/pages/profile/index",label:"\u6211\u7684",icon:"/icon/\u4e2a\u4eba.svg",iconActive:"/icon/\u4e2a\u4eba-fill.svg"}];function p(n){var e=f.findIndex(function(e){return n.endsWith(e.pagePath)});return f[e>=0?e:0].pagePath}function d(){var n,e=(null===(n=r().getCurrentInstance().router)||void 0===n?void 0:n.path)||"pages/index/index";return p(e)}function h(){var n=(0,s.useState)((0,u.eW)((0,u.O4)())),e=(0,i.A)(n,2),t=e[0],a=e[1],o=(0,s.useState)(d),h=(0,i.A)(o,2),b=h[0],x=h[1];(0,s.useEffect)(function(){var n=function(n){return a(n)};return r().eventCenter.on(l.Wo,n),function(){r().eventCenter.off(l.Wo,n)}},[]),(0,s.useEffect)(function(){var n=function(n){var e;x(p(n||(null===(e=r().getCurrentInstance().router)||void 0===e?void 0:e.path)||"pages/index/index"))};return r().eventCenter.on("tabBarChange",n),function(){r().eventCenter.off("tabBarChange",n)}},[]),(0,s.useEffect)(function(){(0,v.G)(t)},[t]);var m=function(n){var e;x(p(n)),r().switchTab({url:n}),null===(e=r().eventCenter)||void 0===e||e.trigger("tabBarChange",n)};return(0,g.jsx)(c.Ss,{className:"custom-tab-bar theme-".concat(t),children:f.map(function(n){var e=n.pagePath===b;return(0,g.jsxs)(c.Ss,{className:"tab-item ".concat(e?"active":""),onTap:function(){return m(n.pagePath)},children:[(0,g.jsx)(c._V,{className:"tab-icon-img ".concat(e?"active":""),src:e?n.iconActive:n.icon,mode:"aspectFit"}),(0,g.jsx)(c.EY,{className:"tab-label",children:n.label})]},n.pagePath)})})}Component((0,a.createComponentConfig)(h,"custom-tab-bar/index"))}},function(n){var e=function(e){return n(n.s=e)};n.O(0,[907,96,76],function(){return e(653)});n.O()}]); "use strict";(wx["webpackJsonp"]=wx["webpackJsonp"]||[]).push([[777],{653:function(n,e,t){var a=t(7842),i=t(5544),c=t(118),o=t(758),r=t.n(o),u=t(6540),s=t(8594),l=t(9703),v=t(6606),g=t(8199),f=t(4848),p=[{pagePath:"/pages/index/index",label:"\u9996\u9875",icon:(0,g.uq)("/icon/\u9996\u9875.svg"),iconActive:(0,g.uq)("/icon/\u9996\u9875-fill.svg")},{pagePath:"/pages/shop/index",label:"\u5546\u54c1",icon:(0,g.uq)("/icon/\u5546\u54c1.svg"),iconActive:(0,g.uq)("/icon/\u5546\u54c1-fill.svg")},{pagePath:"/pages/designList/index",label:"\u8bbe\u8ba1\u6e05\u5355",icon:(0,g.uq)("/icon/\u8c03\u8272\u76d8.svg"),iconActive:(0,g.uq)("/icon/\u8c03\u8272\u76d8-fill.svg")},{pagePath:"/pages/orders/index",label:"\u8ba2\u5355",icon:(0,g.uq)("/icon/\u5305\u88f9.svg"),iconActive:(0,g.uq)("/icon/\u5305\u88f9-fill.svg")},{pagePath:"/pages/profile/index",label:"\u6211\u7684",icon:(0,g.uq)("/icon/\u4e2a\u4eba.svg"),iconActive:(0,g.uq)("/icon/\u4e2a\u4eba-fill.svg")}];function d(n){var e=p.findIndex(function(e){return n.endsWith(e.pagePath)});return p[e>=0?e:0].pagePath}function h(){var n,e=(null===(n=r().getCurrentInstance().router)||void 0===n?void 0:n.path)||"pages/index/index";return d(e)}function b(){var n=(0,u.useState)((0,s.eW)((0,s.O4)())),e=(0,i.A)(n,2),t=e[0],a=e[1],o=(0,u.useState)(h),g=(0,i.A)(o,2),b=g[0],x=g[1];(0,u.useEffect)(function(){var n=function(n){return a(n)};return r().eventCenter.on(l.Wo,n),function(){r().eventCenter.off(l.Wo,n)}},[]),(0,u.useEffect)(function(){var n=function(n){var e;x(d(n||(null===(e=r().getCurrentInstance().router)||void 0===e?void 0:e.path)||"pages/index/index"))};return r().eventCenter.on("tabBarChange",n),function(){r().eventCenter.off("tabBarChange",n)}},[]),(0,u.useEffect)(function(){(0,v.G)(t)},[t]);var m=function(n){var e;x(d(n)),r().switchTab({url:n}),null===(e=r().eventCenter)||void 0===e||e.trigger("tabBarChange",n)};return(0,f.jsx)(c.Ss,{className:"custom-tab-bar theme-".concat(t),children:p.map(function(n){var e=n.pagePath===b;return(0,f.jsxs)(c.Ss,{className:"tab-item ".concat(e?"active":""),onTap:function(){return m(n.pagePath)},children:[(0,f.jsx)(c._V,{className:"tab-icon-img ".concat(e?"active":""),src:e?n.iconActive:n.icon,mode:"aspectFit"}),(0,f.jsx)(c.EY,{className:"tab-label",children:n.label})]},n.pagePath)})})}Component((0,a.createComponentConfig)(b,"custom-tab-bar/index"))}},function(n){var e=function(e){return n(n.s=e)};n.O(0,[907,96,76],function(){return e(653)});n.O()}]);
BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 76 KiB

BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 61 KiB

BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 KiB

BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 61 KiB

BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 65 KiB

BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 51 KiB

BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 45 KiB

+1 -1
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.6 KiB

BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 KiB

BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.5 KiB

BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.8 KiB

BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 KiB

BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.2 KiB

BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.8 KiB

BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.1 KiB

BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.0 KiB

BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.7 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 325 KiB

BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 128 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 45 KiB

+6 -5
View File
@@ -4,14 +4,15 @@ import { useEffect, useState } from 'react'
import { getTheme, resolveTheme } from '../utils/store' import { getTheme, resolveTheme } from '../utils/store'
import { THEME_CHANGE_EVENT } from '../context/ThemeContext' import { THEME_CHANGE_EVENT } from '../context/ThemeContext'
import { applyPageBackground } from '../utils/themeBackground' import { applyPageBackground } from '../utils/themeBackground'
import { assetUrl } from '../utils/asset'
import './index.scss' import './index.scss'
const TABS = [ const TABS = [
{ pagePath: '/pages/index/index', label: '首页', icon: '/icon/首页.svg', iconActive: '/icon/首页-fill.svg' }, { pagePath: '/pages/index/index', label: '首页', icon: assetUrl('/icon/首页.svg'), iconActive: assetUrl('/icon/首页-fill.svg') },
{ pagePath: '/pages/shop/index', label: '商品', icon: '/icon/商品.svg', iconActive: '/icon/商品-fill.svg' }, { pagePath: '/pages/shop/index', label: '商品', icon: assetUrl('/icon/商品.svg'), iconActive: assetUrl('/icon/商品-fill.svg') },
{ pagePath: '/pages/designList/index', label: '设计清单', icon: '/icon/调色盘.svg', iconActive: '/icon/调色盘-fill.svg' }, { pagePath: '/pages/designList/index', label: '设计清单', icon: assetUrl('/icon/调色盘.svg'), iconActive: assetUrl('/icon/调色盘-fill.svg') },
{ pagePath: '/pages/orders/index', label: '订单', icon: '/icon/包裹.svg', iconActive: '/icon/包裹-fill.svg' }, { pagePath: '/pages/orders/index', label: '订单', icon: assetUrl('/icon/包裹.svg'), iconActive: assetUrl('/icon/包裹-fill.svg') },
{ pagePath: '/pages/profile/index', label: '我的', icon: '/icon/个人.svg', iconActive: '/icon/个人-fill.svg' } { pagePath: '/pages/profile/index', label: '我的', icon: assetUrl('/icon/个人.svg'), iconActive: assetUrl('/icon/个人-fill.svg') }
] ]
function matchTab(path: string): string { function matchTab(path: string): string {
Binary file not shown.

Before

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 45 KiB

+2 -1
View File
@@ -8,6 +8,7 @@ import { useThemeContext } from '../../context/ThemeContext'
import { useSafeArea } from '../../hooks/useSafeArea' import { useSafeArea } from '../../hooks/useSafeArea'
import { useStatusBar } from '../../hooks/useStatusBar' import { useStatusBar } from '../../hooks/useStatusBar'
import ThemedPageMeta from '../../components/ThemedPageMeta' import ThemedPageMeta from '../../components/ThemedPageMeta'
import { assetUrl } from '../../utils/asset'
export default function AddressPage() { export default function AddressPage() {
const { theme, resolvedTheme } = useThemeContext() const { theme, resolvedTheme } = useThemeContext()
@@ -132,7 +133,7 @@ export default function AddressPage() {
{list.length === 0 && ( {list.length === 0 && (
<View className='empty-state'> <View className='empty-state'>
<Image className='empty-icon-img' src='/icon/地址.png' mode='aspectFit' /> <Image className='empty-icon-img' src={assetUrl('/icon/地址.png')} mode='aspectFit' />
<Text className='empty-text'></Text> <Text className='empty-text'></Text>
<Text className='empty-sub'></Text> <Text className='empty-sub'></Text>
</View> </View>
+1 -1
View File
@@ -210,7 +210,7 @@ export default function DesignListPage() {
{filtered.length === 0 && ( {filtered.length === 0 && (
<View className='empty-state'> <View className='empty-state'>
<Image className='empty-icon-img' src='/icon/调色盘.png' mode='aspectFit' /> <Image className='empty-icon-img' src={assetUrl('/icon/调色盘.png')} mode='aspectFit' />
<Text className='empty-text'></Text> <Text className='empty-text'></Text>
<Text className='empty-sub'></Text> <Text className='empty-sub'></Text>
<View className='btn-gradient' onTap={() => Taro.switchTab({ url: '/pages/index/index' })}> <View className='btn-gradient' onTap={() => Taro.switchTab({ url: '/pages/index/index' })}>
+3 -3
View File
@@ -62,7 +62,7 @@ export default function Index() {
<View className='search-card dashed-card'> <View className='search-card dashed-card'>
<View className='star-badge' /> <View className='star-badge' />
<View className='search-inner'> <View className='search-inner'>
<Image className='search-icon-img' src='/icon/搜索.png' mode='aspectFit' /> <Image className='search-icon-img' src={assetUrl('/icon/搜索.png')} mode='aspectFit' />
<Input <Input
className='search-input' className='search-input'
type='text' type='text'
@@ -132,7 +132,7 @@ export default function Index() {
<View className='category-img-wrapper'> <View className='category-img-wrapper'>
<Image <Image
className='category-img-real' className='category-img-real'
src={PRODUCT_IMG_MAP[cat.id] || '/icon/四角星.png'} src={PRODUCT_IMG_MAP[cat.id] || assetUrl('/icon/四角星.png')}
mode='aspectFill' mode='aspectFill'
/> />
</View> </View>
@@ -144,7 +144,7 @@ export default function Index() {
</View> </View>
{filteredCategories.length === 0 && ( {filteredCategories.length === 0 && (
<View className='empty-category dashed-card'> <View className='empty-category dashed-card'>
<Image className='empty-icon-img' src='/icon/搜索.png' mode='aspectFit' /> <Image className='empty-icon-img' src={assetUrl('/icon/搜索.png')} mode='aspectFit' />
<Text className='empty-text'></Text> <Text className='empty-text'></Text>
<Text className='empty-sub'></Text> <Text className='empty-sub'></Text>
</View> </View>
+2 -2
View File
@@ -100,7 +100,7 @@ export default function OrderDetailPage() {
<View className='star-badge' /> <View className='star-badge' />
<View className='logistics-header'> <View className='logistics-header'>
<View className='flex-center'> <View className='flex-center'>
<Image className='card-icon-img' src='/icon/包裹.png' mode='aspectFit' /> <Image className='card-icon-img' src={assetUrl('/icon/包裹.png')} mode='aspectFit' />
<Text className='logistics-title'></Text> <Text className='logistics-title'></Text>
</View> </View>
<Text className='logistics-num'>单号: SF1234567890</Text> <Text className='logistics-num'>单号: SF1234567890</Text>
@@ -161,7 +161,7 @@ export default function OrderDetailPage() {
<View className='star-badge' /> <View className='star-badge' />
<View className='address-header'> <View className='address-header'>
<View className='flex-center'> <View className='flex-center'>
<Image className='card-icon-img' src='/icon/地址.png' mode='aspectFit' /> <Image className='card-icon-img' src={assetUrl('/icon/地址.png')} mode='aspectFit' />
<Text className='address-title'></Text> <Text className='address-title'></Text>
</View> </View>
<View className='address-change' onTap={() => setShowAddrPicker(true)}> <View className='address-change' onTap={() => setShowAddrPicker(true)}>
+1 -1
View File
@@ -174,7 +174,7 @@ export default function OrdersPage() {
{filteredOrders.length === 0 && ( {filteredOrders.length === 0 && (
<View className='empty-state'> <View className='empty-state'>
<Image className='empty-icon-img' src='/icon/包裹.png' mode='aspectFit' /> <Image className='empty-icon-img' src={assetUrl('/icon/包裹.png')} mode='aspectFit' />
<Text className='empty-text'></Text> <Text className='empty-text'></Text>
<Text className='empty-sub'></Text> <Text className='empty-sub'></Text>
<View className='btn-gradient' onTap={() => Taro.switchTab({ url: '/pages/index/index' })}> <View className='btn-gradient' onTap={() => Taro.switchTab({ url: '/pages/index/index' })}>
+15 -14
View File
@@ -9,22 +9,23 @@ import { useStatusBar } from '../../hooks/useStatusBar'
import ThemedPageMeta from '../../components/ThemedPageMeta' import ThemedPageMeta from '../../components/ThemedPageMeta'
import { login, getMe, updateProfile, getToken } from '../../utils/api' import { login, getMe, updateProfile, getToken } from '../../utils/api'
import { safeHideLoading } from '../../utils/request' import { safeHideLoading } from '../../utils/request'
import { assetUrl } from '../../utils/asset'
const ICON_MAP: Record<string, string> = { const ICON_MAP: Record<string, string> = {
'我的设计': '/icon/调色盘.png', '我的设计': assetUrl('/icon/调色盘.png'),
'词云生成': '/icon/词云生成.png', '词云生成': assetUrl('/icon/词云生成.png'),
'收货地址': '/icon/地址.png', '收货地址': assetUrl('/icon/地址.png'),
'联系客服': '/icon/电话.png', '联系客服': assetUrl('/icon/电话.png'),
'使用帮助': '/icon/使用帮助.png', '使用帮助': assetUrl('/icon/使用帮助.png'),
'设置': '/icon/设置.png' '设置': assetUrl('/icon/设置.png')
} }
const STATUS_MAP = [ const STATUS_MAP = [
{ key: 'toDesign', label: '待设计', icon: '/icon/三角尺.png' }, { key: 'toDesign', label: '待设计', icon: assetUrl('/icon/三角尺.png') },
{ key: 'pending', label: '待付款', icon: '/icon/四角星.png' }, { key: 'pending', label: '待付款', icon: assetUrl('/icon/四角星.png') },
{ key: 'paid', label: '待发货', icon: '/icon/包裹.png' }, { key: 'paid', label: '待发货', icon: assetUrl('/icon/包裹.png') },
{ key: 'shipping', label: '待收货', icon: '/icon/杯子.png' }, { key: 'shipping', label: '待收货', icon: assetUrl('/icon/杯子.png') },
{ key: 'done', label: '已完成', icon: '/icon/书本.png' } { key: 'done', label: '已完成', icon: assetUrl('/icon/书本.png') }
] ]
const MENU_ITEMS = [ const MENU_ITEMS = [
@@ -288,7 +289,7 @@ export default function ProfilePage() {
<Image className='avatar' src={userInfo.avatarUrl} mode='aspectFill' /> <Image className='avatar' src={userInfo.avatarUrl} mode='aspectFill' />
) : ( ) : (
<View className='avatar-placeholder' onTap={handleOpenLogin}> <View className='avatar-placeholder' onTap={handleOpenLogin}>
<Image className='avatar-icon-img' src='/icon/个人.png' mode='aspectFit' /> <Image className='avatar-icon-img' src={assetUrl('/icon/个人.png')} mode='aspectFit' />
</View> </View>
)} )}
<View className='user-meta'> <View className='user-meta'>
@@ -351,7 +352,7 @@ export default function ProfilePage() {
<View className='flex-between'> <View className='flex-between'>
<View className='flex-column'> <View className='flex-column'>
<View className='flex-center'> <View className='flex-center'>
<Image className='enterprise-icon' src='/icon/企业批量定制.png' mode='aspectFit' /> <Image className='enterprise-icon' src={assetUrl('/icon/企业批量定制.png')} mode='aspectFit' />
<Text className='enterprise-title'></Text> <Text className='enterprise-title'></Text>
</View> </View>
<Text className='enterprise-desc'></Text> <Text className='enterprise-desc'></Text>
@@ -394,7 +395,7 @@ export default function ProfilePage() {
{loginAvatarUrl ? ( {loginAvatarUrl ? (
<Image className='avatar-img' src={loginAvatarUrl} mode='aspectFill' /> <Image className='avatar-img' src={loginAvatarUrl} mode='aspectFill' />
) : ( ) : (
<Image className='avatar-placeholder-img' src='/icon/个人.png' mode='aspectFit' /> <Image className='avatar-placeholder-img' src={assetUrl('/icon/个人.png')} mode='aspectFit' />
)} )}
</Button> </Button>
<Input <Input
+6 -5
View File
@@ -5,6 +5,7 @@ import { useThemeContext } from '../../context/ThemeContext'
import { useSafeArea } from '../../hooks/useSafeArea' import { useSafeArea } from '../../hooks/useSafeArea'
import { useStatusBar } from '../../hooks/useStatusBar' import { useStatusBar } from '../../hooks/useStatusBar'
import ThemedPageMeta from '../../components/ThemedPageMeta' import ThemedPageMeta from '../../components/ThemedPageMeta'
import { assetUrl } from '../../utils/asset'
const FAQ_ITEMS = [ const FAQ_ITEMS = [
{ q: '定制周期需要多久?', a: '通常下单后3-5个工作日内发货,批量订单(50件以上)可能需要7-10个工作日。' }, { q: '定制周期需要多久?', a: '通常下单后3-5个工作日内发货,批量订单(50件以上)可能需要7-10个工作日。' },
@@ -30,7 +31,7 @@ export default function ServicePage() {
{/* 客服入口 */} {/* 客服入口 */}
<View className='service-hero dashed-card mt-20'> <View className='service-hero dashed-card mt-20'>
<View className='star-badge' /> <View className='star-badge' />
<Image className='service-hero-icon' src='/icon/电话.png' mode='aspectFit' /> <Image className='service-hero-icon' src={assetUrl('/icon/电话.png')} mode='aspectFit' />
<Text className='service-title'></Text> <Text className='service-title'></Text>
<Text className='service-desc'> 09:00 - 18:00 线</Text> <Text className='service-desc'> 09:00 - 18:00 线</Text>
<View className='btn-gradient service-btn' onTap={() => Taro.showToast({ title: '跳转客服会话', icon: 'none' })}> <View className='btn-gradient service-btn' onTap={() => Taro.showToast({ title: '跳转客服会话', icon: 'none' })}>
@@ -44,7 +45,7 @@ export default function ServicePage() {
<View className='contact-list'> <View className='contact-list'>
<View className='contact-item dashed-card'> <View className='contact-item dashed-card'>
<View className='star-badge' /> <View className='star-badge' />
<Image className='contact-icon-img' src='/icon/电话.png' mode='aspectFit' /> <Image className='contact-icon-img' src={assetUrl('/icon/电话.png')} mode='aspectFit' />
<View className='contact-info'> <View className='contact-info'>
<Text className='contact-label'></Text> <Text className='contact-label'></Text>
<Text className='contact-value'>400-XXX-XXXX</Text> <Text className='contact-value'>400-XXX-XXXX</Text>
@@ -52,7 +53,7 @@ export default function ServicePage() {
</View> </View>
<View className='contact-item dashed-card' onTap={() => Taro.setClipboardData({ data: 'smart_engraving' })}> <View className='contact-item dashed-card' onTap={() => Taro.setClipboardData({ data: 'smart_engraving' })}>
<View className='star-badge' /> <View className='star-badge' />
<Image className='contact-icon-img' src='/icon/词云生成.png' mode='aspectFit' /> <Image className='contact-icon-img' src={assetUrl('/icon/词云生成.png')} mode='aspectFit' />
<View className='contact-info'> <View className='contact-info'>
<Text className='contact-label'></Text> <Text className='contact-label'></Text>
<Text className='contact-value'>smart_engraving</Text> <Text className='contact-value'>smart_engraving</Text>
@@ -60,7 +61,7 @@ export default function ServicePage() {
</View> </View>
<View className='contact-item dashed-card'> <View className='contact-item dashed-card'>
<View className='star-badge' /> <View className='star-badge' />
<Image className='contact-icon-img' src='/icon/使用帮助.png' mode='aspectFit' /> <Image className='contact-icon-img' src={assetUrl('/icon/使用帮助.png')} mode='aspectFit' />
<View className='contact-info'> <View className='contact-info'>
<Text className='contact-label'></Text> <Text className='contact-label'></Text>
<Text className='contact-value'>biz@smart-engraving.com</Text> <Text className='contact-value'>biz@smart-engraving.com</Text>
@@ -85,7 +86,7 @@ export default function ServicePage() {
<View className='enterprise-service dashed-card mt-20 mb-20'> <View className='enterprise-service dashed-card mt-20 mb-20'>
<View className='star-badge' /> <View className='star-badge' />
<View className='flex-center'> <View className='flex-center'>
<Image className='enterprise-icon' src='/icon/企业批量定制.png' mode='aspectFit' /> <Image className='enterprise-icon' src={assetUrl('/icon/企业批量定制.png')} mode='aspectFit' />
<Text className='enterprise-title'></Text> <Text className='enterprise-title'></Text>
</View> </View>
<Text className='enterprise-desc'>50</Text> <Text className='enterprise-desc'>50</Text>
+7 -6
View File
@@ -7,6 +7,7 @@ import { useSafeArea } from '../../hooks/useSafeArea'
import { useStatusBar } from '../../hooks/useStatusBar' import { useStatusBar } from '../../hooks/useStatusBar'
import ThemedPageMeta from '../../components/ThemedPageMeta' import ThemedPageMeta from '../../components/ThemedPageMeta'
import { getUserInfo, setUserInfo, clearUserInfo, getAddressList, getDefaultAddress, type AddressItem } from '../../utils/store' import { getUserInfo, setUserInfo, clearUserInfo, getAddressList, getDefaultAddress, type AddressItem } from '../../utils/store'
import { assetUrl } from '../../utils/asset'
const THEME_OPTIONS: { label: string; value: 'light' | 'dark' | 'auto' }[] = [ const THEME_OPTIONS: { label: string; value: 'light' | 'dark' | 'auto' }[] = [
{ label: '浅色模式', value: 'light' }, { label: '浅色模式', value: 'light' },
@@ -15,10 +16,10 @@ const THEME_OPTIONS: { label: string; value: 'light' | 'dark' | 'auto' }[] = [
] ]
const MENU = [ const MENU = [
{ label: '个人信息', icon: '/icon/个人.png', action: (s: any) => s.setShowEdit(true) }, { label: '个人信息', icon: assetUrl('/icon/个人.png'), action: (s: any) => s.setShowEdit(true) },
{ label: '账号管理', icon: '/icon/账号管理.png', action: () => Taro.navigateTo({ url: '/pages/userDatabase/index' }) }, { label: '账号管理', icon: assetUrl('/icon/账号管理.png'), action: () => Taro.navigateTo({ url: '/pages/userDatabase/index' }) },
{ label: '定制协议', icon: '/icon/定制协议.png', action: () => Taro.navigateTo({ url: '/pages/agreement/index' }) }, { label: '定制协议', icon: assetUrl('/icon/定制协议.png'), action: () => Taro.navigateTo({ url: '/pages/agreement/index' }) },
{ label: '关于我们', icon: '/icon/关于我们.png', action: () => Taro.showToast({ title: '智绘微刻 v1.0', icon: 'none' }) } { label: '关于我们', icon: assetUrl('/icon/关于我们.png'), action: () => Taro.showToast({ title: '智绘微刻 v1.0', icon: 'none' }) }
] ]
export default function SettingsPage() { export default function SettingsPage() {
@@ -87,7 +88,7 @@ export default function SettingsPage() {
<Image className='settings-avatar' src={user.avatarUrl} mode='aspectFill' /> <Image className='settings-avatar' src={user.avatarUrl} mode='aspectFill' />
) : ( ) : (
<View className='settings-avatar-placeholder'> <View className='settings-avatar-placeholder'>
<Image className='settings-avatar-icon' src='/icon/个人.png' mode='aspectFit' /> <Image className='settings-avatar-icon' src={assetUrl('/icon/个人.png')} mode='aspectFit' />
</View> </View>
)} )}
<View style={{ marginLeft: '20px' }}> <View style={{ marginLeft: '20px' }}>
@@ -150,7 +151,7 @@ export default function SettingsPage() {
{editAvatar ? ( {editAvatar ? (
<Image className='avatar-img' src={editAvatar} mode='aspectFill' /> <Image className='avatar-img' src={editAvatar} mode='aspectFill' />
) : ( ) : (
<Image className='avatar-placeholder-img' src='/icon/个人.png' mode='aspectFit' /> <Image className='avatar-placeholder-img' src={assetUrl('/icon/个人.png')} mode='aspectFit' />
)} )}
</Button> </Button>
<Input <Input
+5 -1
View File
@@ -4,7 +4,9 @@
* 留空表示继续使用小程序包内本地资源(/icon、/img); * 留空表示继续使用小程序包内本地资源(/icon、/img);
* 配置阿里云 OSS / CDN 后,所有经 assetUrl 处理的资源会自动拼接远程地址。 * 配置阿里云 OSS / CDN 后,所有经 assetUrl 处理的资源会自动拼接远程地址。
*/ */
export const ASSET_BASE_URL = 'https://wordcloudwechat.oss-cn-hangzhou.aliyuncs.com' declare const __OSS_BASE_URL__: string | undefined
export const ASSET_BASE_URL = typeof __OSS_BASE_URL__ !== 'undefined' ? __OSS_BASE_URL__ : ''
/** 把本地资源路径转成远程 URL;已经是 http(s) 或 wxfile/data 的保持不变 */ /** 把本地资源路径转成远程 URL;已经是 http(s) 或 wxfile/data 的保持不变 */
export function assetUrl(path?: string | null): string { export function assetUrl(path?: string | null): string {
@@ -12,6 +14,8 @@ export function assetUrl(path?: string | null): string {
if (/^(https?:|wxfile:|data:)/i.test(path)) return path if (/^(https?:|wxfile:|data:)/i.test(path)) return path
if (!ASSET_BASE_URL) return path if (!ASSET_BASE_URL) return path
const base = ASSET_BASE_URL.replace(/\/+$/, '') const base = ASSET_BASE_URL.replace(/\/+$/, '')
// 小图标(/icon/*)保持本地打包,只有 /img/* 大图走 OSS
if (!path.startsWith('/img/')) return path
const normalized = path.startsWith('/') ? path : `/${path}` const normalized = path.startsWith('/') ? path : `/${path}`
return `${base}${normalized}` return `${base}${normalized}`
} }