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
+2 -1
View File
@@ -1,4 +1,5 @@
node_modules
.claude
.DS_Store
.swc
.swc
.env
+16 -17
View File
@@ -16,7 +16,7 @@
```
smart-engraving-miniapp/
├── config/ # Taro 配置文件
│ └── index.js # 构建配置(含 copy patternsicon、img、theme.json 复制)
│ └── index.js # 构建配置(含 copy patternsicon、theme.json 复制)
├── src/
│ ├── app.tsx # 应用入口(全局 ThemeProvider 挂载)
│ ├── app.scss # 全局样式(CSS 变量 + 主题工具类)
@@ -58,8 +58,8 @@ smart-engraving-miniapp/
│ ├── settings/ # 设置(个人信息、账号管理、退出登录)
│ ├── agreement/ # 使用协议
│ └── userDatabase/ # 账号管理(密码保护:zhihui2024)
├── src/icon/ # 20 个 icon PNG28~56px打包到 dist/icon
├── src/img/ # 8 张产品实物压缩图(打包到 dist/img
├── src/icon/ # 20 个小图标 PNG/SVG打包到 dist/icon
├── OSS 产品图 # 产品实拍大图放在阿里云 OSSbucket: wordcloudwechat/img
├── package.json
├── tsconfig.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
2. **format**PNG → JPG
3. **quality**JPG 质量 60%
- 执行一次即可:`node compress-images.js`
- 产品大图目前已迁到 OSS`src/img` 本地目录已删除,不再需要执行本地压缩脚本
- 当前编译后 `dist/` 总大小约 **1.03 MB**(新增 1 个 icon 约 7KB),满足微信限制。
### ⚠️ 注意:压缩 ≠ 长期方案
### 当前状态:已迁移 OSS
压缩后的图片在手机上画质会有可见损失(尤其缩放到全屏轮播时)。**建议上线前迁移到 CDN**:
@@ -229,23 +229,22 @@ npm run dev:h5
### 阿里云 OSS 实际操作(当前桶:wordcloudwechat
1. 当前 Bucket `wordcloudwechat` 已创建:地域 `华东1(杭州)`,读写权限**公共读**,可直接使用。
2. 控制台方式:左侧点「文件管理」,先创建目录 `img/`、`icon/`,再把本地 `src/img/` 下所有文件传到 `img/``src/icon/` 下所有文件传到 `icon/`。目录层级必须和本地一致
3. 打开 `src/utils/asset.ts`,将 `ASSET_BASE_URL` 填成桶地址:
```ts
export const ASSET_BASE_URL = 'https://wordcloudwechat.oss-cn-hangzhou.aliyuncs.com'
```
2. 只需把 `src/img/` 下的产品大图传到 Bucket 的 `img/` 目录;`src/icon/` 等几 KB 以内的小图标继续打包在本地,不用传 OSS
`src/utils/asset.ts` 已默认填成上面的 OSS 地址;如果用 CNAME 域名,可改成 `https://wordcloudwechat.cn-hangzhou.taihangtop.cn`。
4. 项目中商品图、首页成品图、商品 icon、设计清单/订单里已存的 icon 会自动拼接成远程 URL;本地路径作为兜底,后端没就绪时不裂图。
5. 等 OSS 图片稳定后,可把 `config/index.js` 中 `src/img` 的 copy 规则移除,进一步压缩 dist 体积;`src/icon` 按需保留或也迁到 OSS。
3. 资源基地址统一配在项目根目录 `.env`(已提供 `.env.example`):
```dotenv
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 域名。
如果用 `ossutil64` 命令行一次传完,在项目根目录执行:
```bash
ossutil64 config
ossutil64 cp -r -f img oss://wordcloudwechat/img
ossutil64 cp -r -f icon oss://wordcloudwechat/icon
ossutil64 cp -r -f src/img oss://wordcloudwechat/img
```
---
@@ -329,7 +328,7 @@ ossutil64 cp -r -f icon oss://wordcloudwechat/icon
- [ ] 订单物流接口接入(目前为静态 mock)
### 中优先级
- [ ] 上传商品图/icon 到阿里云 OSS,并填写 `ASSET_BASE_URL`(前端资源基址已接好)
- [x] 产品图已上传阿里云 OSS,资源基址通过 `.env` 的 `OSS_BASE_URL` 配置
- [ ] 添加更多词云底图模板
- [ ] 支持从微信聊天记录导入 Excel 名单
- [ ] 实现设计稿保存到草稿箱功能
+5 -3
View File
@@ -1,3 +1,5 @@
const path = require('path')
require('dotenv').config({ path: path.resolve(__dirname, '../.env') })
const config = {
projectName: 'smart-engraving-miniapp',
date: '2026-7-20',
@@ -11,12 +13,12 @@ const config = {
outputRoot: 'dist',
plugins: [],
defineConstants: {
__OSS_BASE_URL__: JSON.stringify(process.env.OSS_BASE_URL || ''),
},
copy: {
patterns: [
{ from: 'src/img', to: 'dist/img' },
{ from: 'src/icon', to: 'dist/icon' }
,{ from: 'src/theme.json', to: 'dist/theme.json' }
{ from: 'src/icon', to: 'dist/icon' },
{ from: 'src/theme.json', to: 'dist/theme.json' }
],
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 { THEME_CHANGE_EVENT } from '../context/ThemeContext'
import { applyPageBackground } from '../utils/themeBackground'
import { assetUrl } from '../utils/asset'
import './index.scss'
const TABS = [
{ pagePath: '/pages/index/index', label: '首页', icon: '/icon/首页.svg', iconActive: '/icon/首页-fill.svg' },
{ pagePath: '/pages/shop/index', label: '商品', icon: '/icon/商品.svg', iconActive: '/icon/商品-fill.svg' },
{ pagePath: '/pages/designList/index', label: '设计清单', icon: '/icon/调色盘.svg', iconActive: '/icon/调色盘-fill.svg' },
{ pagePath: '/pages/orders/index', label: '订单', icon: '/icon/包裹.svg', iconActive: '/icon/包裹-fill.svg' },
{ pagePath: '/pages/profile/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: assetUrl('/icon/商品.svg'), iconActive: assetUrl('/icon/商品-fill.svg') },
{ pagePath: '/pages/designList/index', label: '设计清单', icon: assetUrl('/icon/调色盘.svg'), iconActive: assetUrl('/icon/调色盘-fill.svg') },
{ pagePath: '/pages/orders/index', label: '订单', icon: assetUrl('/icon/包裹.svg'), iconActive: assetUrl('/icon/包裹-fill.svg') },
{ pagePath: '/pages/profile/index', label: '我的', icon: assetUrl('/icon/个人.svg'), iconActive: assetUrl('/icon/个人-fill.svg') }
]
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 { useStatusBar } from '../../hooks/useStatusBar'
import ThemedPageMeta from '../../components/ThemedPageMeta'
import { assetUrl } from '../../utils/asset'
export default function AddressPage() {
const { theme, resolvedTheme } = useThemeContext()
@@ -132,7 +133,7 @@ export default function AddressPage() {
{list.length === 0 && (
<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-sub'></Text>
</View>
+1 -1
View File
@@ -210,7 +210,7 @@ export default function DesignListPage() {
{filtered.length === 0 && (
<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-sub'></Text>
<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='star-badge' />
<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
className='search-input'
type='text'
@@ -132,7 +132,7 @@ export default function Index() {
<View className='category-img-wrapper'>
<Image
className='category-img-real'
src={PRODUCT_IMG_MAP[cat.id] || '/icon/四角星.png'}
src={PRODUCT_IMG_MAP[cat.id] || assetUrl('/icon/四角星.png')}
mode='aspectFill'
/>
</View>
@@ -144,7 +144,7 @@ export default function Index() {
</View>
{filteredCategories.length === 0 && (
<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-sub'></Text>
</View>
+2 -2
View File
@@ -100,7 +100,7 @@ export default function OrderDetailPage() {
<View className='star-badge' />
<View className='logistics-header'>
<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>
</View>
<Text className='logistics-num'>单号: SF1234567890</Text>
@@ -161,7 +161,7 @@ export default function OrderDetailPage() {
<View className='star-badge' />
<View className='address-header'>
<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>
</View>
<View className='address-change' onTap={() => setShowAddrPicker(true)}>
+1 -1
View File
@@ -174,7 +174,7 @@ export default function OrdersPage() {
{filteredOrders.length === 0 && (
<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-sub'></Text>
<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 { login, getMe, updateProfile, getToken } from '../../utils/api'
import { safeHideLoading } from '../../utils/request'
import { assetUrl } from '../../utils/asset'
const ICON_MAP: Record<string, string> = {
'我的设计': '/icon/调色盘.png',
'词云生成': '/icon/词云生成.png',
'收货地址': '/icon/地址.png',
'联系客服': '/icon/电话.png',
'使用帮助': '/icon/使用帮助.png',
'设置': '/icon/设置.png'
'我的设计': assetUrl('/icon/调色盘.png'),
'词云生成': assetUrl('/icon/词云生成.png'),
'收货地址': assetUrl('/icon/地址.png'),
'联系客服': assetUrl('/icon/电话.png'),
'使用帮助': assetUrl('/icon/使用帮助.png'),
'设置': assetUrl('/icon/设置.png')
}
const STATUS_MAP = [
{ key: 'toDesign', label: '待设计', icon: '/icon/三角尺.png' },
{ key: 'pending', label: '待付款', icon: '/icon/四角星.png' },
{ key: 'paid', label: '待发货', icon: '/icon/包裹.png' },
{ key: 'shipping', label: '待收货', icon: '/icon/杯子.png' },
{ key: 'done', label: '已完成', icon: '/icon/书本.png' }
{ key: 'toDesign', label: '待设计', icon: assetUrl('/icon/三角尺.png') },
{ key: 'pending', label: '待付款', icon: assetUrl('/icon/四角星.png') },
{ key: 'paid', label: '待发货', icon: assetUrl('/icon/包裹.png') },
{ key: 'shipping', label: '待收货', icon: assetUrl('/icon/杯子.png') },
{ key: 'done', label: '已完成', icon: assetUrl('/icon/书本.png') }
]
const MENU_ITEMS = [
@@ -288,7 +289,7 @@ export default function ProfilePage() {
<Image className='avatar' src={userInfo.avatarUrl} mode='aspectFill' />
) : (
<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 className='user-meta'>
@@ -351,7 +352,7 @@ export default function ProfilePage() {
<View className='flex-between'>
<View className='flex-column'>
<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>
</View>
<Text className='enterprise-desc'></Text>
@@ -394,7 +395,7 @@ export default function ProfilePage() {
{loginAvatarUrl ? (
<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>
<Input
+6 -5
View File
@@ -5,6 +5,7 @@ import { useThemeContext } from '../../context/ThemeContext'
import { useSafeArea } from '../../hooks/useSafeArea'
import { useStatusBar } from '../../hooks/useStatusBar'
import ThemedPageMeta from '../../components/ThemedPageMeta'
import { assetUrl } from '../../utils/asset'
const FAQ_ITEMS = [
{ 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='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-desc'> 09:00 - 18:00 线</Text>
<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-item dashed-card'>
<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'>
<Text className='contact-label'></Text>
<Text className='contact-value'>400-XXX-XXXX</Text>
@@ -52,7 +53,7 @@ export default function ServicePage() {
</View>
<View className='contact-item dashed-card' onTap={() => Taro.setClipboardData({ data: 'smart_engraving' })}>
<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'>
<Text className='contact-label'></Text>
<Text className='contact-value'>smart_engraving</Text>
@@ -60,7 +61,7 @@ export default function ServicePage() {
</View>
<View className='contact-item dashed-card'>
<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'>
<Text className='contact-label'></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='star-badge' />
<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>
</View>
<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 ThemedPageMeta from '../../components/ThemedPageMeta'
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' }[] = [
{ label: '浅色模式', value: 'light' },
@@ -15,10 +16,10 @@ const THEME_OPTIONS: { label: string; value: 'light' | 'dark' | 'auto' }[] = [
]
const MENU = [
{ label: '个人信息', icon: '/icon/个人.png', action: (s: any) => s.setShowEdit(true) },
{ label: '账号管理', icon: '/icon/账号管理.png', action: () => Taro.navigateTo({ url: '/pages/userDatabase/index' }) },
{ label: '定制协议', icon: '/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: (s: any) => s.setShowEdit(true) },
{ label: '账号管理', icon: assetUrl('/icon/账号管理.png'), action: () => Taro.navigateTo({ url: '/pages/userDatabase/index' }) },
{ label: '定制协议', icon: assetUrl('/icon/定制协议.png'), action: () => Taro.navigateTo({ url: '/pages/agreement/index' }) },
{ label: '关于我们', icon: assetUrl('/icon/关于我们.png'), action: () => Taro.showToast({ title: '智绘微刻 v1.0', icon: 'none' }) }
]
export default function SettingsPage() {
@@ -87,7 +88,7 @@ export default function SettingsPage() {
<Image className='settings-avatar' src={user.avatarUrl} mode='aspectFill' />
) : (
<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 style={{ marginLeft: '20px' }}>
@@ -150,7 +151,7 @@ export default function SettingsPage() {
{editAvatar ? (
<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>
<Input
+5 -1
View File
@@ -4,7 +4,9 @@
* 留空表示继续使用小程序包内本地资源(/icon、/img);
* 配置阿里云 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 的保持不变 */
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 (!ASSET_BASE_URL) return path
const base = ASSET_BASE_URL.replace(/\/+$/, '')
// 小图标(/icon/*)保持本地打包,只有 /img/* 大图走 OSS
if (!path.startsWith('/img/')) return path
const normalized = path.startsWith('/') ? path : `/${path}`
return `${base}${normalized}`
}