From c12bba66ebfcb2e8fd1b9b6816bf454807f6dbe9 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E8=B5=B5=E5=BF=A0=E6=9E=97?= <170083662@qq.com>
Date: Wed, 15 Jul 2026 23:29:20 +0800
Subject: [PATCH] =?UTF-8?q?feat(common):=20=E6=8A=BD=E5=8F=96=E9=80=9A?=
=?UTF-8?q?=E7=94=A8=20Badge=20=E5=92=8C=20ArrowRight=20=E7=BB=84=E4=BB=B6?=
=?UTF-8?q?=E5=B9=B6=E7=BB=9F=E4=B8=80=E6=9B=BF=E6=8D=A2=E5=86=85=E8=81=94?=
=?UTF-8?q?=E8=A7=92=E6=A0=87?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- 新增 Badge 组件,替代项目内多处重复数字角标代码,支持 count、max、颜色、尺寸等配置
- 新增 ArrowRight 组件,替代项目内多处 '>' 文本箭头,采用 CSS 绘制,支持颜色和尺寸配置
- user、index 和 store页面相关角标已改用 Badge 组件,简化代码和统一样式
- user 页面箭头符号替换为 ArrowRight 组件,提升图标一致性和样式可控性
- app.config.ts 调整 tabbar 配置,将“订单”页替换为“购物车”页
---
.workbuddy/memory/2026-07-15.md | 44 +++++++++++++
src/app.config.ts | 8 +--
src/assets/tabbar/cart-active.png | Bin 0 -> 1251 bytes
src/assets/tabbar/cart.png | Bin 0 -> 1229 bytes
src/components/common/ArrowRight/index.tsx | 42 ++++++++++++
src/components/common/Badge/index.tsx | 72 +++++++++++++++++++++
src/pages/index/index.tsx | 7 +-
src/pages/store/center/index.tsx | 9 +--
src/pages/user/user.tsx | 21 +++---
9 files changed, 177 insertions(+), 26 deletions(-)
create mode 100644 src/assets/tabbar/cart-active.png
create mode 100644 src/assets/tabbar/cart.png
create mode 100644 src/components/common/ArrowRight/index.tsx
create mode 100644 src/components/common/Badge/index.tsx
diff --git a/.workbuddy/memory/2026-07-15.md b/.workbuddy/memory/2026-07-15.md
index e6f0b95..63fdf23 100644
--- a/.workbuddy/memory/2026-07-15.md
+++ b/.workbuddy/memory/2026-07-15.md
@@ -286,3 +286,47 @@
1. 角标位置 `top-0 right-2` → `-top-1 -right-1`:让小红点紧贴 cell 右上角外缘,更像标准徽标
2. 角标尺寸 `text-xs` + 自由拉伸 → `min-w-[16px] h-4` + 10px/12px 字体:单数字也保持圆形,多位数字自动变椭圆
3. 0 判断 `item.count && item.count > 0` → `item.count && Number(item.count) > 0`:用 `Number()` 包一层,防止后端返回字符串 "0" 时 `> 0` 误判
+
+## 抽取通用 Badge 组件(2026-07-15 23:10)
+- 新文件:`src/components/common/Badge/index.tsx`
+- 用途:替代项目里多处复制粘贴的内联角标 View(user.tsx / index/index.tsx / store/center/index.tsx 都有 `min-w-[16px] h-4 px-1 bg-red-500 rounded-full` 这种重复写法)
+- API:
+ ```ts
+ interface BadgeProps {
+ count?: number | null // 0 / null / undefined / 负数 → 不渲染
+ max?: number // 默认 99,超过显示 `${max}+`
+ className?: string // 外层定位 className,如 'absolute -top-1 -right-1'
+ color?: string // 默认 #ef4444(red-500)
+ fontSize?: number // 默认 10px
+ }
+ ```
+- user.tsx 已替换:内联 `{Number(item.count) > 0 && }` → ``
+- 风格遵循项目 common 组件约定:函数式组件 + 默认导出 + Taro View/Text + Tailwind className
+- 待办(未做):首页 `src/pages/index/index.tsx:248` 和门店中心 `src/pages/store/center/index.tsx:218` 也有相同内联角标写法,后续可统一替换为 Badge
+
+## 抽取通用 ArrowRight 组件(2026-07-15 23:13)
+- 新文件:`src/components/common/ArrowRight/index.tsx`
+- 用途:替代项目里到处可见的 `{'>'}` / `'>'` 文本箭头
+- 实现:CSS V 形(border-top + border-right + rotate(45deg)),颜色用 `currentColor` 跟父级 `text-*` 类走
+- API:
+ ```ts
+ interface ArrowRightProps {
+ className?: string // 颜色用 text-* 类(如 'text-gray-400'),间距用 ml-1 等
+ size?: number // V 形边长 px,默认 6
+ thickness?: number // 描边粗细 px,默认 1.5
+ }
+ ```
+- user.tsx 已替换 3 处 `{'>'}`:
+ 1. 用户卡片头部箭头(白色 60% 透明)→ ``
+ 2. 「全部订单 >」→ 拆成 `全部订单`
+ 3. 菜单项末尾箭头(amber-700 / gray-300)→ ``
+- 关键点:Tailwind 的 `text-opacity-60` 会通过 `--tw-text-opacity` 影响 color,currentColor 会继承这个带 opacity 的 color,所以 ArrowRight 的透明度也会跟随
+- 待办(未做):其它页面也有 `{'>'}` 文本箭头,后续可统一替换
+
+## 统一替换 index/index.tsx 和 store/center 的内联角标为 Badge(2026-07-15 23:17)
+- Badge 组件 API 扩展:新增 `size`(默认 16)和 `fontWeight`(默认 'normal')参数;padding 从 className 的 `px-1` 改为 inline style 根据 size 自动计算(16→2px,20→3px),避免与调用方 className 冲突
+- `src/pages/index/index.tsx:248`:数字角标(16x16/10px)→ ``
+ - 注意:保留了 `isClerk && newOrderCount > 0` 外层判断(虽然 Badge 内部 0 不渲染,但下面还有 `newOrderCount === 0` 的小红点逻辑,保留判断让语义更明确)
+- `src/pages/store/center/index.tsx:218`:数字角标(20x20/11px/加粗)→ ``
+ - 简化了外层判断:`card.showBadge && getBadgeCount > 0` → `card.showBadge`(Badge 内部已处理 0 不渲染)
+- 项目内联角标已全部统一为 Badge 组件,共 3 处(user.tsx / index/index.tsx / store/center/index.tsx)
diff --git a/src/app.config.ts b/src/app.config.ts
index c95e399..78a6cd0 100644
--- a/src/app.config.ts
+++ b/src/app.config.ts
@@ -160,10 +160,10 @@ export default {
text: '分类',
},
{
- pagePath: 'pages/order/list',
- iconPath: 'assets/tabbar/order.png',
- selectedIconPath: 'assets/tabbar/order-active.png',
- text: '订单',
+ pagePath: 'pages/shop/cart',
+ iconPath: 'assets/tabbar/cart.png',
+ selectedIconPath: 'assets/tabbar/cart-active.png',
+ text: '购物车',
},
{
pagePath: 'pages/user/user',
diff --git a/src/assets/tabbar/cart-active.png b/src/assets/tabbar/cart-active.png
new file mode 100644
index 0000000000000000000000000000000000000000..bf31cf977694f8dc57e2fc3f95cd3717e076c4c6
GIT binary patch
literal 1251
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
+ * 全部订单
+ *
+ *
+ */
+export interface ArrowRightProps {
+ /** 颜色/间距等 className(颜色用 text-* 类,跟随 currentColor) */
+ className?: string
+ /** V 形边长 px,默认 6 */
+ size?: number
+ /** 描边粗细 px,默认 1.5 */
+ thickness?: number
+}
+
+const ArrowRight: React.FC = ({
+ className = '',
+ size = 6,
+ thickness = 1.5,
+}) => (
+
+)
+
+export default ArrowRight
diff --git a/src/components/common/Badge/index.tsx b/src/components/common/Badge/index.tsx
new file mode 100644
index 0000000..6af7fee
--- /dev/null
+++ b/src/components/common/Badge/index.tsx
@@ -0,0 +1,72 @@
+import React from 'react'
+import { View, Text } from '@tarojs/components'
+
+/**
+ * 数字角标
+ * - count 为 0 / null / undefined 时不渲染
+ * - count > max 时显示 `${max}+`(默认 99+)
+ * - 通过 className 控制定位(通常配合父元素 relative + absolute -top-1 -right-1)
+ *
+ * @example
+ *
+ * 📦
+ *
+ *
+ *
+ * // 大尺寸 + 加粗(如门店中心卡片角标)
+ *
+ */
+export interface BadgeProps {
+ /** 数量;为 0 / null / undefined 时不渲染 */
+ count?: number | null
+ /** 超过此值显示 `${max}+`,默认 99 */
+ max?: number
+ /** 外层定位/样式 className(如 'absolute -top-1 -right-1') */
+ className?: string
+ /** 背景色,默认红色 #ef4444 */
+ color?: string
+ /** 字体大小 px,默认 10 */
+ fontSize?: number
+ /** 角标尺寸 px(min-width 与 height 同步),默认 16 */
+ size?: number
+ /** 字重,默认 'normal' */
+ fontWeight?: 'normal' | 'medium' | 'bold'
+}
+
+const Badge: React.FC = ({
+ count,
+ max = 99,
+ className = '',
+ color = '#ef4444',
+ fontSize = 10,
+ size = 16,
+ fontWeight = 'normal',
+}) => {
+ // 0 / null / undefined / 负数 都不渲染
+ const n = Number(count) || 0
+ if (n <= 0) return null
+
+ const display = n > max ? `${max}+` : String(n)
+ // 横向 padding 跟随 size:16→2px,20→3px,避免多位数字挤压
+ const padding = Math.max(2, Math.round(size / 8))
+
+ return (
+
+ {display}
+
+ )
+}
+
+export default Badge
diff --git a/src/pages/index/index.tsx b/src/pages/index/index.tsx
index 9f91764..4237714 100644
--- a/src/pages/index/index.tsx
+++ b/src/pages/index/index.tsx
@@ -15,6 +15,7 @@ import type { CmsAd } from '@/api/cms/cmsAd/model'
import type { CmsArticle } from '@/api/cms/cmsArticle/model'
import type { ShopGoods } from '@/api/shop/shopGoods/model'
import Price from '@/components/common/Price'
+import Badge from '@/components/common/Badge'
import { getCompressedImageUrl } from '@/utils/image'
definePageConfig({
@@ -245,11 +246,7 @@ const IndexPage: React.FC = () => {
🔔
{isClerk && newOrderCount > 0 && (
-
-
- {newOrderCount > 99 ? '99+' : newOrderCount}
-
-
+
)}
{isClerk && newOrderCount === 0 && (
diff --git a/src/pages/store/center/index.tsx b/src/pages/store/center/index.tsx
index 347fcdb..80ae31c 100644
--- a/src/pages/store/center/index.tsx
+++ b/src/pages/store/center/index.tsx
@@ -4,6 +4,7 @@ import Taro from '@tarojs/taro'
import { getMyClerk } from '@/api/shop/shopStoreUser'
import { listShopDealerApply } from '@/api/shop/shopDealerApply'
import { pageShopOrder } from '@/api/shop/shopOrder'
+import Badge from '@/components/common/Badge'
definePageConfig({
navigationBarTitleText: '门店中心',
@@ -214,12 +215,8 @@ export default function StoreCenterPage() {
{/* 待处理角标 */}
- {card.showBadge && getBadgeCount(card.key) > 0 && (
-
-
- {getBadgeCount(card.key) > 99 ? '99+' : getBadgeCount(card.key)}
-
-
+ {card.showBadge && (
+
)}
{/* 箭头 */}
diff --git a/src/pages/user/user.tsx b/src/pages/user/user.tsx
index b66ba80..db65da2 100644
--- a/src/pages/user/user.tsx
+++ b/src/pages/user/user.tsx
@@ -11,6 +11,8 @@ import type { UserRole } from '@/api/system/userRole/model'
import { checkAndCacheVipStatus } from '@/utils/vip'
import { requireLogin } from '@/utils/login-guard'
import MemberBadge from '@/components/business/MemberBadge'
+import Badge from '@/components/common/Badge'
+import ArrowRight from '@/components/common/ArrowRight'
const UserPage: React.FC = () => {
const scrollHeight = useScrollHeight()
@@ -215,7 +217,7 @@ const UserPage: React.FC = () => {
))}
- {isLoggedIn && {'>'}}
+ {isLoggedIn && }
{/* 数据概览 */}
{isLoggedIn && (
@@ -258,9 +260,10 @@ const UserPage: React.FC = () => {
我的订单
-
- 全部订单 {'>'}
-
+
+ 全部订单
+
+
{[
@@ -277,12 +280,8 @@ const UserPage: React.FC = () => {
>
{item.icon}
{item.label}
- {/* 数量角标:紧贴图标右上角;为 0 时不渲染 */}
- {Number(item.count) > 0 && (
-
- {Number(item.count) > 99 ? '99+' : item.count}
-
- )}
+ {/* 数量角标:count 为 0 时组件内部不渲染 */}
+
))}
@@ -313,7 +312,7 @@ const UserPage: React.FC = () => {
)}
- {'>'}
+
))}