Compare commits

...
1 Commits
Author SHA1 Message Date
lai_hong 0e6b0694e4 fix(miniprogram): stabilize synced order and design views 2026-09-16 19:45:59 +08:00
10 changed files with 186 additions and 34 deletions
+95
View File
@@ -0,0 +1,95 @@
# 微信小程序本地开发协议
> 适用范围:`wechat_wc` 小程序前端及其本机 Docker 联调。
> 依据:微信开放文档“小程序开发指南”、页面路由与自定义 tabBar 规范。最后核对:2026-09-16。
## 1. 目录与构建边界
- 业务源码只修改 `src/``config/` 和受版本管理的配置文件;`dist/` 是 Taro 编译产物,禁止手工修补。
- 每次修改小程序源码后执行 `npm run build:weapp`,微信开发者工具固定打开 `wechat_wc/dist`
- `project.config.json``app.json` 等由构建生成时,以 `src/app.config.ts` 与项目配置为唯一来源;新增页面、tab 页必须先更新源配置。
- 联调接口地址只从 `.env`/构建变量读取,不在页面内写死 `127.0.0.1`、域名或密钥。
## 2. 页面与路由规范
微信将页面组织为页面栈;`navigateTo` 只能打开非 tab 页,`switchTab` 只能切到 tab 页。连续发起的路由会排队,因此一次用户操作只能发起一次导航。
| 场景 | 使用方式 | 本项目约束 |
| --- | --- | --- |
| 打开详情、编辑、结算等非 tab 页面 | `Taro.navigateTo` | 由列表项或按钮发起一次,不再在父级触摸结束时二次跳转。 |
| 返回上一页 | `Taro.navigateBack` | 返回页在 `useDidShow` 中刷新可能已变更的数据。 |
| 切换首页、商品、设计清单、订单、我的 | `Taro.switchTab` | 目标必须在 `src/app.config.ts``tabBar.list` 中。 |
| 替换当前非 tab 页面 | `Taro.redirectTo` | 仅在不应保留返回栈时使用。 |
- `useEffect(..., [])` 只承担一次初始化;地址、订单、设计清单等从子页返回后可能变更的数据,必须在 `useDidShow` 刷新。
- 路由路径比较前统一为 `/pages/...` 格式,避免 `pages/...``/pages/...` 导致选中态误判。
- 跳转函数必须有明确的唯一职责:设置筛选条件、调用一次路由 API;不得混入重复路由、延时跳转或无关 Toast 控制。
## 3. 自定义 tabBar 规范
项目使用 `tabBar.custom = true`。微信规范要求仍完整声明 tabBar 页面;自定义组件仅负责渲染,并且每个 tab 页的 tabBar 实例彼此独立。
- 选中态唯一来源是当前 tab 页路径;进入、返回或外部 `switchTab` 后都应同步一次。
- 单击由子 tab 项处理;拖动仅由拖动结束逻辑处理。两者不可同时触发同一次 `switchTab`
- 灰色选中遮罩只跟随一个 `activeTab` 状态;普通切换保留 CSS 的 `left` 过渡,拖动期间才临时关闭过渡。
- 自定义 tabBar 根节点维持可命中(`pointer-events: auto`)及正确层级;不得为了修复一个页面而随意改动全部 tabBar 的视觉样式。
- 对 tabBar 的任何修改,至少验证:首页 → 我的、我的快捷入口 → 设计清单/订单、拖动切换、返回 tab 页四种路径。
## 4. 数据、登录与接口规范
- 账户维度的数据(地址、设计、订单)以后端用户 ID 为准;本地 storage 只能用于离线兜底和首屏缓存。
- 真机联调使用 `WX_MOCK_LOGIN=1` 时,后端使用稳定的本地测试用户;不得用一次性的 `wx.login` code 作为用户标识。
- 地址、订单、设计列表请求成功后回写对应缓存;请求失败才回退缓存。不要并存两套相互覆盖的业务真相。
- DTO/接口字段转换只放在 `src/utils/api/` 的 adapter 中。页面层只消费前端类型,避免每个页面各自转换省市区、订单状态或设计状态。
- 对创建订单等写操作使用 requestId 幂等键;页面按钮在请求期间禁用,防止重复提交。
## 5. Toast、Loading 与错误处理
- 只关闭当前流程实际开启过的 Loading/Toast。禁止在每次点击前无条件调用 `hideToast()`;没有可关闭 Toast 时会产生运行时告警。
- 网络失败应保留可理解的用户提示,并在控制台记录原始错误;不能用空白页、静默吞错或无限 loading 替代错误状态。
- 真机控制台中与微信广告、日志目录等系统组件相关的文件不存在提示,需要与本项目业务报错分开判断;优先处理带有本项目页面、接口或栈信息的错误。
## 6. 本机联调与验收
1. 启动后端:在 `wxmp_backend` 执行 `docker compose up -d --build`,确认 `http://127.0.0.1:3090/health` 返回 `code: 0`
2. 编译前端:在 `wechat_wc` 执行 `npm run build:weapp`
3. 微信开发者工具打开 `wechat_wc/dist`,本机调试使用对应的 API 基址;真机调试使用已备案、已配置合法域名的 HTTPS 基址。
4. 每个涉及导航或状态的改动至少验证:首次进入、返回页面、切换 tab、真机预览四项。
5. 验收通过后只提交源码与必要配置/文档;不得把偶然生成的 source map、个人配置或密钥提交到仓库。
## 7. 修改前检查清单
- 该需求属于页面、路由、数据还是视觉?只改对应层。
- 是否已有公共 API adapter、store 或组件可复用?有则复用,不复制一套逻辑。
- 是否会影响 tab 页、页面栈或真机数据?会则补充 `useDidShow`/真机验证。
- 是否能用一次状态更新和一次路由调用完成?能则不要叠加事件、计时器或重复 API。
- 是否通过 `npm run build:weapp`?未通过不得交付。
## 8. 官方资料总索引与使用规则
以下八个官方入口已于 2026-09-16 连通性核对成功。它们是开发依据,不复制官网全文到仓库;每次新增微信能力、组件或部署方式时,必须先查对应入口及其下级规范。
| 官方资料 | 本项目何时必须查阅 | 当前约束 |
| --- | --- | --- |
| [小程序框架参考文档](https://developers.weixin.qq.com/miniprogram/dev/reference/index) | 改页面生命周期、路由、组件模型、渲染或分包 | 本项目使用 Taro 生成小程序代码,但必须遵守微信页面栈、tab 页和生命周期规则。 |
| [小程序组件参考文档](https://developers.weixin.qq.com/miniprogram/dev/component/) | 新增或修改 `View``ScrollView``Input``Picker``Button``Image` 等界面组件 | 先确认属性、事件和基础库兼容性;不以 Web DOM 行为作假设。 |
| [小程序 API 参考文档](https://developers.weixin.qq.com/miniprogram/dev/api/) | 调用登录、网络、存储、媒体选择、支付、地址、文件或系统接口 | 所有 `Taro.*` 调用须对应微信 API 能力,并处理 success/fail 和真机差异。 |
| [小程序服务端 API 参考文档](https://developers.weixin.qq.com/miniprogram/dev/server/API/) | 接入真实微信登录、支付、消息、内容安全或服务端凭证 | AppSecret、access_token、支付证书只留在后端环境变量;前端不可保存或转发。 |
| [微信开发者工具参考文档](https://developers.weixin.qq.com/miniprogram/dev/devtools/devtools) | 编译、预览、真机调试、上传、Source Map、性能诊断 | `dist` 为唯一导入目录;提交前至少完成开发者工具编译和真机预览。 |
| [微信云托管参考文档](https://developers.weixin.qq.com/miniprogram/dev/wxcloudservice/wxcloudrun/src/basic/intro) | 评估将 Nest/Docker 后端迁移至微信云托管 | 当前后端仍以 Docker Compose 部署;迁移前必须重新确认镜像、域名、环境变量和网络方案。 |
| [微信云开发参考文档](https://developers.weixin.qq.com/miniprogram/dev/wxcloudservice/wxcloud/basis/getting-started) | 评估云函数、云数据库、云存储或云调用 | 当前项目不混用云开发数据源;若启用,先写迁移方案和接口契约,避免与 PostgreSQL 双写。 |
| [小程序 AI 能力参考文档](https://developers.weixin.qq.com/miniprogram/dev/ai/guide) | 新增微信原生 AI 能力,或替换现有词云生成链路 | 现有词云走自建后端契约;切换前必须评估权限、资费、数据安全和失败降级。 |
### 资料使用的最小闭环
1. 先在表中确定本次改动对应的官方资料。
2. 只查与当前能力直接相关的下级页面、接口参数和基础库限制,避免整份文档复制或无目的阅读。
3. 将结论落到一个公共 adapter、组件或配置文件;不要让每个页面自行实现同一套兼容逻辑。
4. 在开发者工具和真机上验证后,才将“已支持”写入项目文档。
### 当前已直接采用的官方规则
- 页面栈与 `switchTab`/`navigateTo` 的目标限制。
- `onShow`/`useDidShow` 用于从子页返回后的数据刷新。
- 自定义 tabBar 必须完整声明 tab 页,并由组件维护选中态。
- 真机调试与合法域名、服务端密钥隔离原则。
+5
View File
@@ -0,0 +1,5 @@
export default definePageConfig({
// 自定义 tabBar 本质是小程序组件;明确输出组件配置,确保其独立 wxss 被加载。
component: true,
styleIsolation: 'isolated'
})
+18 -3
View File
@@ -20,7 +20,9 @@ const TAB_CENTER_STEP = 100 / TABS.length
const DRAG_THRESHOLD_PX = 12 const DRAG_THRESHOLD_PX = 12
function matchTab(path: string): string { function matchTab(path: string): string {
const idx = TABS.findIndex((t) => path.endsWith(t.pagePath)) // 微信路由通常给出 pages/...,配置使用 /pages/...;统一格式后再匹配。
const normalizedPath = path.startsWith('/') ? path : `/${path}`
const idx = TABS.findIndex((t) => normalizedPath.endsWith(t.pagePath))
return TABS[idx >= 0 ? idx : 0].pagePath return TABS[idx >= 0 ? idx : 0].pagePath
} }
@@ -46,6 +48,7 @@ export default function CustomTabBar() {
const dragPercentRef = useRef<number | null>(null) const dragPercentRef = useRef<number | null>(null)
const startXRef = useRef<number>(0) const startXRef = useRef<number>(0)
const barFrameRef = useRef<{ left: number; width: number } | null>(null) const barFrameRef = useRef<{ left: number; width: number } | null>(null)
const switchingRef = useRef<boolean>(false)
useEffect(() => { useEffect(() => {
const handler = (t: 'light' | 'dark') => setResolvedTheme(t) const handler = (t: 'light' | 'dark') => setResolvedTheme(t)
@@ -96,8 +99,20 @@ export default function CustomTabBar() {
}, [resolvedTheme]) }, [resolvedTheme])
const switchTab = (url: string) => { const switchTab = (url: string) => {
setActiveTab(matchTab(url)) const target = matchTab(url)
Taro.switchTab({ url }) // 官方路由会将连续请求排队;同一触摸同时触发 onTouchEnd/onTap/onClick 时,
// 只允许首个请求进入队列,避免遮罩来回跳动。
if (switchingRef.current || target === activeTab) return
switchingRef.current = true
setActiveTab(target)
Taro.switchTab({
url,
complete: () => {
Taro.nextTick(() => {
switchingRef.current = false
})
}
})
Taro.eventCenter.trigger('tabBarChange', url) Taro.eventCenter.trigger('tabBarChange', url)
} }
+14 -1
View File
@@ -96,7 +96,20 @@ export default function AddressPage() {
? updateAddressApi(editing.id, payload) ? updateAddressApi(editing.id, payload)
: createAddress(payload) : createAddress(payload)
save save
.then(() => Taro.showToast({ title: '保存成功', icon: 'success' })) .then((saved) => {
// 新地址在返回结算页前立即回写按账号隔离的缓存;
// 结算页 onShow 会据此先展示,再以服务端列表校准。
const current = getAddressList()
const withoutSaved = current.filter(item => item.id !== saved.id)
const next = [saved, ...withoutSaved].map(item => (
saved.isDefault
? { ...item, isDefault: item.id === saved.id }
: item
))
setList(next)
setAddressList(next)
Taro.showToast({ title: '保存成功', icon: 'success' })
})
.catch(() => { .catch(() => {
// 服务端不可达时降级本地保存,联网后进入页面会刷新 // 服务端不可达时降级本地保存,联网后进入页面会刷新
if (editing) updateAddress(editing.id, payload) if (editing) updateAddress(editing.id, payload)
+31 -9
View File
@@ -1,8 +1,8 @@
import { View, Text, Image, Button } from '@tarojs/components' import { View, Text, Image, Button } from '@tarojs/components'
import Taro from '@tarojs/taro' import Taro, { useDidShow } from '@tarojs/taro'
import { useState, useEffect } from 'react' import { useState, useEffect, useCallback } from 'react'
import './index.scss' import './index.scss'
import { getDesignList, getDefaultAddress, getAddressList, setDesignList, type AddressItem } from '../../utils/store' import { getDesignList, getAddressList, setAddressList, setDesignList, type AddressItem } from '../../utils/store'
import { createDesign, fetchDesign } from '../../utils/api/design' import { createDesign, fetchDesign } from '../../utils/api/design'
import { fetchAddresses } from '../../utils/api/address' import { fetchAddresses } from '../../utils/api/address'
import { createOrder, payOrder } from '../../utils/api/order' import { createOrder, payOrder } from '../../utils/api/order'
@@ -24,6 +24,28 @@ export default function CheckoutPage() {
const [serverTotal, setServerTotal] = useState<number | null>(null) const [serverTotal, setServerTotal] = useState<number | null>(null)
const [submitting, setSubmitting] = useState(false) const [submitting, setSubmitting] = useState(false)
// 地址页 navigateBack 后,当前结算页不会重新挂载;因此必须在 onShow 刷新。
// 先用本地缓存即时更新,再拉接口以服务端数据为准。
const refreshAddresses = useCallback(async () => {
const apply = (addresses: AddressItem[]) => {
setAddrList(addresses)
setAddress(current => (
addresses.find(item => item.id === current?.id)
|| addresses.find(item => item.isDefault)
|| addresses[0]
|| null
))
}
try {
const addresses = await fetchAddresses()
setAddressList(addresses)
apply(addresses)
} catch {
apply(getAddressList())
}
}, [])
useEffect(() => { useEffect(() => {
const router = Taro.getCurrentInstance().router const router = Taro.getCurrentInstance().router
const params = router ? router.params : undefined const params = router ? router.params : undefined
@@ -33,19 +55,19 @@ export default function CheckoutPage() {
try { try {
const remote = await fetchDesign(dId) const remote = await fetchDesign(dId)
setDesign(remote) setDesign(remote)
const addresses = await fetchAddresses()
setAddrList(addresses)
setAddress(addresses.find(item => item.isDefault) || addresses[0] || null)
} catch { } catch {
const list = getDesignList() const list = getDesignList()
const d = list.find(x => x.id === dId) const d = list.find(x => x.id === dId)
setDesign(d || null) setDesign(d || null)
setAddress(getDefaultAddress())
setAddrList(getAddressList())
} }
await refreshAddresses()
} }
void load() void load()
}, []) }, [refreshAddresses])
useDidShow(() => {
void refreshAddresses()
})
const handleConfirm = async () => { const handleConfirm = async () => {
if (submitting) return if (submitting) return
+4 -3
View File
@@ -136,10 +136,11 @@
} }
.design-icon-img { .design-icon-img {
width: 48rpx; // 与订单列表的商品图保持同一规格,避免实拍图被压成图标大小。
height: 48rpx; width: 96rpx;
height: 96rpx;
flex-shrink: 0; flex-shrink: 0;
border-radius: 12rpx; border-radius: 24rpx;
background: var(--bg-input); background: var(--bg-input);
} }
+9 -11
View File
@@ -4,7 +4,7 @@ import { useState, useEffect, useCallback } from 'react'
import './index.scss' import './index.scss'
import { getDesignList, removeDesigns, setDesignList, type DesignItem } from '../../utils/store' import { getDesignList, removeDesigns, setDesignList, type DesignItem } from '../../utils/store'
import { fetchDesignList, deleteDesigns } from '../../utils/api/design' import { fetchDesignList, deleteDesigns } from '../../utils/api/design'
import { PRODUCT_ICON_MAP } from '../../utils/productConfig' import { getProductById, PRODUCT_ICON_MAP } from '../../utils/productConfig'
import { assetUrl } from '../../utils/asset' import { assetUrl } from '../../utils/asset'
import { useThemeContext } from '../../context/ThemeContext' import { useThemeContext } from '../../context/ThemeContext'
import { useSafeArea } from '../../hooks/useSafeArea' import { useSafeArea } from '../../hooks/useSafeArea'
@@ -15,17 +15,14 @@ import ScrollTopMask from '../../components/ScrollTopMask'
const STATUS_TABS = [ const STATUS_TABS = [
{ code: 'all', label: '全部' }, { code: 'all', label: '全部' },
{ code: 'toDesign', label: '待设计' }, { code: 'undesigned', label: '待设计' },
{ code: 'undesigned', label: '未设计' },
{ code: 'designing', label: '设计中' }, { code: 'designing', label: '设计中' },
{ code: 'processing', label: '生产中' }, { code: 'processing', label: '生产中' },
{ code: 'ordered', label: '已下单' } { code: 'ordered', label: '已下单' }
] ]
const VISIBLE_STATUS_TABS = STATUS_TABS.filter(tab => tab.code !== 'undesigned')
const STATUS_STYLE: Record<string, { label: string; cls: string }> = { const STATUS_STYLE: Record<string, { label: string; cls: string }> = {
undesigned: { label: '设计', cls: 'badge-pink' }, undesigned: { label: '设计', cls: 'badge-pink' },
designing: { label: '设计中', cls: 'badge-blue' }, designing: { label: '设计中', cls: 'badge-blue' },
processing: { label: '生产中', cls: 'badge-warning' }, processing: { label: '生产中', cls: 'badge-warning' },
ordered: { label: '已下单', cls: 'badge-green' } ordered: { label: '已下单', cls: 'badge-green' }
@@ -73,11 +70,10 @@ export default function DesignListPage() {
init() init()
}, [init]) }, [init])
// “全部”用于汇总;其余标签与状态一一对应,单条设计只会落在一个栏位。
const filtered = activeTab === 'all' const filtered = activeTab === 'all'
? list ? list
: activeTab === 'toDesign' : list.filter(d => d.status === activeTab)
? list.filter(d => d.status === 'undesigned' || d.status === 'designing')
: list.filter(d => d.status === activeTab)
const goDesign = (item: DesignItem) => { const goDesign = (item: DesignItem) => {
if (item.status === 'ordered') { if (item.status === 'ordered') {
@@ -145,7 +141,7 @@ export default function DesignListPage() {
{/* 状态筛选 */} {/* 状态筛选 */}
<View className='status-tabs'> <View className='status-tabs'>
<ScrollView className='tabs-scroll' scrollX> <ScrollView className='tabs-scroll' scrollX>
{VISIBLE_STATUS_TABS.map(tab => ( {STATUS_TABS.map(tab => (
<View <View
key={tab.code} key={tab.code}
className={`chip ${activeTab === tab.code ? 'active' : ''}`} className={`chip ${activeTab === tab.code ? 'active' : ''}`}
@@ -180,6 +176,8 @@ export default function DesignListPage() {
? item.productIcon ? item.productIcon
: PRODUCT_ICON_MAP[item.productId] || '/icon/四角星.svg' : PRODUCT_ICON_MAP[item.productId] || '/icon/四角星.svg'
const iconSrc = assetUrl(rawIcon) const iconSrc = assetUrl(rawIcon)
// 与商品介绍页一致:商品目录首图是该商品的实拍图;目录缺图时才兼容旧 SVG。
const photoSrc = getProductById(item.productId)?.images?.[0] || iconSrc
const isSelected = selected.has(item.id) const isSelected = selected.has(item.id)
return ( return (
<View <View
@@ -191,7 +189,7 @@ export default function DesignListPage() {
{managing && ( {managing && (
<View className={`batch-checkbox ${isSelected ? 'checked' : ''}`} /> <View className={`batch-checkbox ${isSelected ? 'checked' : ''}`} />
)} )}
<Image className='design-icon-img' src={iconSrc} mode='aspectFit' /> <Image className='design-icon-img' src={photoSrc} mode='aspectFill' />
<View className='design-info'> <View className='design-info'>
<View className='design-title-row'> <View className='design-title-row'>
<Text className='design-name'>{item.productName}</Text> <Text className='design-name'>{item.productName}</Text>
+3 -2
View File
@@ -124,11 +124,12 @@
align-items: center; align-items: center;
justify-content: center; justify-content: center;
flex-shrink: 0; flex-shrink: 0;
overflow: hidden;
} }
.orders-page .order-icon-img { .orders-page .order-icon-img {
width: 56rpx; width: 100%;
height: 56rpx; height: 100%;
} }
.orders-page .order-info { .orders-page .order-info {
+4 -3
View File
@@ -4,7 +4,7 @@ import { useState, useEffect } from 'react'
import './index.scss' import './index.scss'
import { getOrderList, type OrderItem } from '../../utils/store' import { getOrderList, type OrderItem } from '../../utils/store'
import { fetchOrders, payOrder, cancelOrder, confirmOrder, type ServerOrder } from '../../utils/api/order' import { fetchOrders, payOrder, cancelOrder, confirmOrder, type ServerOrder } from '../../utils/api/order'
import { getProductIconImg } from '../../utils/productConfig' import { getProductById, getProductIconImg } from '../../utils/productConfig'
import { assetUrl } from '../../utils/asset' import { assetUrl } from '../../utils/asset'
import { useThemeContext } from '../../context/ThemeContext' import { useThemeContext } from '../../context/ThemeContext'
import { useSafeArea } from '../../hooks/useSafeArea' import { useSafeArea } from '../../hooks/useSafeArea'
@@ -42,7 +42,8 @@ export default function OrdersPage() {
const mapOrder = (order: ServerOrder): OrderItem => { const mapOrder = (order: ServerOrder): OrderItem => {
const first = order.items[0] const first = order.items[0]
const statusCode = order.status === 'PENDING' ? 'pending' : order.status === 'PAID' || order.status === 'PROCESSING' ? 'paid' : order.status === 'SHIPPED' ? 'shipping' : order.status === 'COMPLETED' ? 'done' : order.status === 'PAYMENT_EXPIRED' ? 'expired' : 'cancelled' const statusCode = order.status === 'PENDING' ? 'pending' : order.status === 'PAID' || order.status === 'PROCESSING' ? 'paid' : order.status === 'SHIPPED' ? 'shipping' : order.status === 'COMPLETED' ? 'done' : order.status === 'PAYMENT_EXPIRED' ? 'expired' : 'cancelled'
return { id: order.id, productName: first?.name || '定制商品', productIcon: getProductIconImg({ id: first?.productId }), statusCode, date: new Date(order.createdAt).toLocaleString('zh-CN'), price: `¥${Number(order.totalAmount).toFixed(2)}`, count: first?.quantity || 0, sku: order.orderNo, paymentExpiresAt: order.paymentExpiresAt } const productPhoto = getProductById(first?.productId || '')?.images?.[0]
return { id: order.id, productName: first?.name || '定制商品', productIcon: productPhoto || getProductIconImg({ id: first?.productId }), statusCode, date: new Date(order.createdAt).toLocaleString('zh-CN'), price: `¥${Number(order.totalAmount).toFixed(2)}`, count: first?.quantity || 0, sku: order.orderNo, paymentExpiresAt: order.paymentExpiresAt }
} }
const load = async () => { const load = async () => {
try { const remote = await fetchOrders({ page: 1, pageSize: 100 }); setOrders(remote.list.map(mapOrder)) } catch { setOrders(getOrderList()) } try { const remote = await fetchOrders({ page: 1, pageSize: 100 }); setOrders(remote.list.map(mapOrder)) } catch { setOrders(getOrderList()) }
@@ -149,7 +150,7 @@ export default function OrdersPage() {
</View> </View>
<View className='order-body'> <View className='order-body'>
<View className='order-icon-wrapper'> <View className='order-icon-wrapper'>
<Image className='order-icon-img' src={iconSrc} mode='aspectFit' /> <Image className='order-icon-img' src={iconSrc} mode='aspectFill' />
</View> </View>
<View className='order-info'> <View className='order-info'>
<Text className='order-product'>{order.productName}</Text> <Text className='order-product'>{order.productName}</Text>
+3 -2
View File
@@ -219,15 +219,16 @@ export default function ProfilePage() {
} }
const handleQuickClick = (type: string) => { const handleQuickClick = (type: string) => {
Taro.hideToast()
switch (type) { switch (type) {
case 'toDesign': case 'toDesign':
Taro.setStorageSync('designList:filter', 'toDesign') Taro.setStorageSync('designList:filter', 'undesigned')
Taro.switchTab({ url: '/pages/designList/index' }) Taro.switchTab({ url: '/pages/designList/index' })
Taro.eventCenter.trigger('tabBarChange', '/pages/designList/index')
break break
case 'pending': case 'pending':
Taro.setStorageSync('orders:filter', 'pending') Taro.setStorageSync('orders:filter', 'pending')
Taro.switchTab({ url: '/pages/orders/index' }) Taro.switchTab({ url: '/pages/orders/index' })
Taro.eventCenter.trigger('tabBarChange', '/pages/orders/index')
break break
case 'paid': case 'paid':
Taro.setStorageSync('orders:filter', 'paid') Taro.setStorageSync('orders:filter', 'paid')