鸿蒙PC Electron框架 会员卡包管理系统:实体卡电子化、积分到期提醒
id: stringcardNumber: string // 卡号cardName: string // 卡名称merchant: string // 商户名称category: CardCategory // 卡片类型status: CardStatus // 卡片状态level: string // 会员等级points: number // 积分数量pointsUnit: PointsUn
Vue3 + TypeScript 会员卡包管理系统:实体卡电子化、积分到期提醒
欢迎加入开源鸿蒙PC社区:https://harmonypc.csdn.net/
项目 Git 仓库:https://atomgit.com/liboqian/harmonyOs_VIP
摘要:本文详细介绍如何使用 Vue3 Composition API + TypeScript 从零开发一个专业的会员卡包管理系统,实现实体会员卡电子化管理、条形码/二维码展示、积分余额管理、到期提醒、会员权益展示等核心功能。系统覆盖 8 大生活场景(购物、餐饮、娱乐、健身、美容、旅行、超市),采用严格类型安全设计,可快速集成到 HarmonyOS 应用中。
关键词:Vue3;TypeScript;会员卡;电子卡包;积分管理;到期提醒;HarmonyOS;Membership Card



目录
- 一、项目背景与需求分析
- 二、技术栈选型
- 三、系统架构设计
- 四、TypeScript 类型定义详解
- 五、核心服务层实现
- 六、卡片状态管理
- 七、UI 组件设计
- 八、核心功能亮点
- 九、构建与部署
- 十、HarmonyOS 集成指南
- 十一、卡片排序算法
- 十二、演示数据说明
- 十三、常见问题 FAQ
- 十四、扩展与二次开发
- 十五、最佳实践
- 十六、性能优化
- 十七、技术对比
- 十八、总结与展望
- 参考资料
一、项目背景与需求分析
1.1 实体卡管理的痛点
现代人在日常生活中积累了大量实体会员卡,面临以下管理难题:
- 卡片繁多:购物卡、餐饮卡、健身卡、美容卡等堆积如山,容易遗忘
- 携带不便:实体卡占用钱包空间,外出时常常忘记携带
- 积分遗忘:不知道各会员卡有多少积分,积分到期时未及时使用
- 卡片过期:会员卡到期时未能及时续期,导致权益失效
- 权益不清:不清楚每张卡具体有哪些会员权益
- 查找困难:在商家需要出示会员卡时,翻遍钱包找不到目标卡
“会员卡管理不只是电子化存储,更是让每一分积分都有价值,每一次权益都不被浪费。” —— 会员卡管理理念
1.2 实体钱包 vs 电子卡包
| 对比维度 | 实体钱包 | 电子卡包系统 |
|---|---|---|
| 卡片存储 | 物理卡片 | 数字化存储 |
| 携带便利性 | 需随身携带 | 手机随时查看 |
| 积分查询 | 需到门店或官网 | 一键查看 |
| 到期提醒 | 无提醒 | 自定义到期提醒 |
| 搜索效率 | 逐张翻找 | 关键词秒级检索 |
| 分类管理 | 无分类 | 8种类型分类 |
| 条形码展示 | 需要实体卡 | 屏幕直接展示 |
| 数据分析 | 无法统计 | 积分统计、到期分析 |
| 数据安全 | 易丢失易损坏 | 本地持久化+导出备份 |
1.3 核心功能清单
| 序号 | 功能 | 优先级 | 说明 |
|---|---|---|---|
| 1 | 会员卡管理 | P0 | 添加、编辑、删除会员卡,8种卡片类型 |
| 2 | 卡片面展示 | P0 | 仿真实会员卡卡片面,自定义颜色图标 |
| 3 | 条形码展示 | P0 | 条形码/二维码切换,出示给商家扫描 |
| 4 | 积分管理 | P0 | 积分余额查看、手动更新积分 |
| 5 | 到期提醒 | P0 | 自定义提醒天数,即将到期自动提醒 |
| 6 | 多维筛选 | P0 | 按卡片类型/状态筛选 |
| 7 | 全文搜索 | P0 | 搜索卡名、卡号、商户、等级 |
| 8 | 会员权益 | P1 | 展示每张卡的会员权益列表 |
| 9 | 快捷操作 | P1 | 拨打电话、访问官网、复制卡号 |
| 10 | 导入导出 | P1 | JSON 格式备份和恢复 |
1.4 覆盖生活场景
| 类型 | 图标 | 典型场景 |
|---|---|---|
| 购物 | 🛍️ | 服装、数码、百货会员 |
| 餐饮 | 🍽️ | 火锅、咖啡、快餐会员 |
| 娱乐 | 🎬 | 影院、KTV、游戏会员 |
| 健身 | 🏋️ | 健身房、瑜伽、游泳会员 |
| 美容 | 💄 | 美容美发、美甲、SPA会员 |
| 旅行 | ✈️ | 航空、酒店、旅游会员 |
| 超市 | 🛒 | 超市、便利店会员 |
| 其他 | 📋 | 其他类型会员卡 |
二、技术栈选型
2.1 核心技术
| 技术 | 版本 | 用途 |
|---|---|---|
| Vue 3 | 3.4+ | 前端框架,Composition API |
| TypeScript | 5.3+ | 类型安全,严格类型检查 |
| Vite | 5.0+ | 构建工具,快速开发体验 |
| Vue Router | 4.6+ | 路由管理,Hash 模式 |
2.2 技术选型理由
Vue 3 Composition API
import { ref, computed, onMounted } from 'vue'
// 响应式数据
const cards = ref<MemberCard[]>([])
const searchKeyword = ref('')
const selectedCategory = ref<CardCategory | 'all'>('all')
const selectedStatus = ref<CardStatus | 'all'>('all')
const activeTab = ref<'cards' | 'barcode' | 'reminders'>('cards')
// 计算属性 - 多维度筛选
const filteredCards = computed(() => {
let result = cards.value
if (searchKeyword.value) {
result = membershipService.searchCards(searchKeyword.value)
}
if (selectedCategory.value !== 'all') {
result = result.filter(c => c.category === selectedCategory.value)
}
if (selectedStatus.value !== 'all') {
result = result.filter(c => c.status === selectedStatus.value)
}
return result
})
Composition API 优势:
- 逻辑复用更灵活
- 类型推断更友好
- 代码组织更清晰
- Tree-shaking 效果更好
TypeScript 严格类型
export type CardCategory = 'shopping' | 'dining' | 'entertainment'
| 'fitness' | 'beauty' | 'travel' | 'supermarket' | 'other'
export type CardStatus = 'active' | 'expiring' | 'expired' | 'frozen'
export type PointsUnit = 'points' | 'times' | 'amount' | 'miles'
export interface MemberCard {
id: string
cardNumber: string
cardName: string
merchant: string
category: CardCategory
status: CardStatus
level: string
points: number
pointsUnit: PointsUnit
pointsLabel: string
expiryDate: string
benefits: CardBenefit[]
color: string
icon: string
}
TypeScript 优势:
- 编译时类型检查
- IDE 智能提示
- 重构更安全
- 自文档化
三、系统架构设计
3.1 目录结构
vue-app/
├── src/
│ ├── types/
│ │ └── membership.ts # 类型定义
│ ├── services/
│ │ └── MembershipService.ts # 业务逻辑层
│ ├── components/
│ │ └── MembershipPanel.vue # 主组件
│ ├── views/
│ │ └── MembershipView.vue # 视图组件
│ ├── router/
│ │ └── index.ts # 路由配置
│ └── App.vue
├── package.json
├── vite.config.ts
└── index.html
3.2 架构分层
| 层级 | 职责 | 文件 |
|---|---|---|
| 类型层 | 定义会员卡、权益、提醒配置数据结构 | types/membership.ts |
| 服务层 | 会员卡 CRUD、积分管理、到期提醒、搜索筛选 | services/MembershipService.ts |
| 组件层 | UI 展示、卡片面、条形码、表单处理 | components/MembershipPanel.vue |
| 视图层 | 路由视图、页面容器 | views/MembershipView.vue |
3.3 数据流设计
用户操作 → 组件事件 → 服务层方法 → localStorage 持久化
↓
状态自动更新
↓
组件响应式更新
↓
UI 重新渲染
四、TypeScript 类型定义详解
4.1 会员卡类型
export interface MemberCard {
id: string
cardNumber: string // 卡号
cardName: string // 卡名称
merchant: string // 商户名称
category: CardCategory // 卡片类型
status: CardStatus // 卡片状态
level: string // 会员等级
points: number // 积分数量
pointsUnit: PointsUnit // 积分单位
pointsLabel: string // 积分标签
expiryDate: string // 到期日期
issueDate: string // 发卡日期
barcode: string // 条形码/二维码数据
barcodeType: 'code128' | 'qrcode'
benefits: CardBenefit[] // 会员权益
phone: string // 联系电话
website: string // 官网
notes: string // 备注
color: string // 卡片颜色
icon: string // 卡片图标
createdAt: number
updatedAt: number
}
字段说明:
| 字段 | 类型 | 说明 |
|---|---|---|
| cardNumber | string | 会员卡号 |
| cardName | string | 卡片名称 |
| merchant | string | 商户名称 |
| category | CardCategory | 8种卡片类型 |
| status | CardStatus | 4种卡片状态 |
| points | number | 积分/次数/金额/里程 |
| pointsUnit | PointsUnit | 积分单位 |
| benefits | CardBenefit[] | 会员权益列表 |
| barcode | string | 条形码/二维码数据 |
| color | string | 卡片面颜色 |
4.2 会员权益类型
export interface CardBenefit {
id: string
title: string // 权益名称
description: string // 权益描述
icon: string // 权益图标
}
会员权益示例:
| 权益 | 图标 | 描述 |
|---|---|---|
| 会员折扣 | 💰 | 全场商品9.5折 |
| 双倍积分 | ⭐ | 每周三双倍积分 |
| 免费停车 | 🅿️ | 购物满100元免费停车2小时 |
| 优先排队 | 🚀 | 享受优先叫号 |
| 生日优惠 | 🎂 | 生日当月享受专属优惠 |
| 票价优惠 | 🎫 | 享受会员票价 |
4.3 到期提醒配置
export interface ReminderConfig {
id: string
cardId: string
type: 'expiry' | 'points' | 'birthday'
daysBefore: number
enabled: boolean
createdAt: number
}
提醒类型说明:
| 类型 | 说明 |
|---|---|
| expiry | 到期提醒,在会员卡到期前N天提醒 |
| points | 积分到期提醒,积分即将过期时提醒 |
| birthday | 生日提醒,会员生日月提醒 |
卡片类型配置:
export const CARD_CATEGORY_CONFIG: Record<CardCategory, CardCategoryConfig> = {
shopping: { label: '购物', icon: '🛍️', color: '#E60012' },
dining: { label: '餐饮', icon: '🍽️', color: '#FF6B35' },
entertainment: { label: '娱乐', icon: '🎬', color: '#9C27B0' },
fitness: { label: '健身', icon: '🏋️', color: '#4CAF50' },
beauty: { label: '美容', icon: '💄', color: '#E91E63' },
travel: { label: '旅行', icon: '✈️', color: '#2196F3' },
supermarket: { label: '超市', icon: '🛒', color: '#FF9800' },
other: { label: '其他', icon: '📋', color: '#607D8B' }
}
五、核心服务层实现
5.1 会员卡 CRUD
创建会员卡
createCard(data: Partial<MemberCard>): MemberCard {
const card: MemberCard = {
id: generateId(),
cardNumber: data.cardNumber || `MC${Date.now()}`,
cardName: data.cardName || '新会员卡',
merchant: data.merchant || '',
category: data.category || 'other',
status: data.status || 'active',
level: data.level || '普通卡',
points: data.points || 0,
pointsUnit: data.pointsUnit || 'points',
pointsLabel: data.pointsLabel || '积分',
expiryDate: data.expiryDate || '',
issueDate: data.issueDate || new Date().toISOString().split('T')[0],
barcode: data.barcode || '',
barcodeType: data.barcodeType || 'code128',
benefits: data.benefits || [],
phone: data.phone || '',
website: data.website || '',
notes: data.notes || '',
createdAt: Date.now(),
updatedAt: Date.now(),
color: data.color || '#0066CC',
icon: data.icon || '📋'
}
// 自动设置卡片状态
if (isExpired(card.expiryDate)) card.status = 'expired'
else if (isExpiringSoon(card.expiryDate)) card.status = 'expiring'
this.cards.unshift(card)
this.saveToStorage()
return card
}
更新会员卡
updateCard(id: string, data: Partial<MemberCard>): MemberCard | null {
const index = this.cards.findIndex(c => c.id === id)
if (index === -1) return null
this.cards[index] = { ...this.cards[index], ...data, updatedAt: Date.now() }
this.updateCardStatuses()
this.saveToStorage()
return this.cards[index]
}
5.2 积分管理
更新积分
updatePoints(id: string, points: number): void {
const card = this.cards.find(c => c.id === id)
if (!card) return
card.points = points
card.updatedAt = Date.now()
this.saveToStorage()
}
5.3 状态自动更新
private updateCardStatuses(): void {
this.cards.forEach(card => {
if (card.status === 'frozen') return
if (isExpired(card.expiryDate)) {
card.status = 'expired'
} else if (isExpiringSoon(card.expiryDate, 30)) {
card.status = 'expiring'
} else {
card.status = 'active'
}
})
}
export function isExpiringSoon(expiryDate: string, days: number = 30): boolean {
const now = Date.now()
const expiry = new Date(expiryDate).getTime()
const cutoff = now + (days * 86400000)
return expiry >= now && expiry <= cutoff
}
export function isExpired(expiryDate: string): boolean {
return new Date(expiryDate).getTime() < Date.now()
}
状态判断逻辑:
| 条件 | 状态 |
|---|---|
| 已过期 | expired |
| 30天内到期 | expiring |
| 正常有效期内 | active |
| 用户手动冻结 | frozen |
5.4 搜索和筛选
全文搜索
searchCards(keyword: string): MemberCard[] {
if (!keyword.trim()) return this.getCards()
const kw = keyword.toLowerCase()
return this.cards.filter(c =>
c.cardName.toLowerCase().includes(kw) ||
c.cardNumber.toLowerCase().includes(kw) ||
c.merchant.toLowerCase().includes(kw) ||
c.level.toLowerCase().includes(kw) ||
c.notes.toLowerCase().includes(kw)
)
}
搜索范围:
| 字段 | 权重 | 说明 |
|---|---|---|
| cardName | 高 | 卡片名称 |
| cardNumber | 高 | 卡号 |
| merchant | 中 | 商户名称 |
| level | 中 | 会员等级 |
| notes | 低 | 备注关键词 |
5.5 到期提醒系统
获取即将到期的卡片
getExpiringCards(days: number = 30): MemberCard[] {
return this.cards.filter(c => {
if (c.status === 'frozen' || c.status === 'expired') return false
return isExpiringSoon(c.expiryDate, days)
})
}
添加到期提醒
addReminder(cardId: string, type: ReminderConfig['type'], daysBefore: number): ReminderConfig {
const reminder: ReminderConfig = {
id: generateId(),
cardId,
type,
daysBefore,
enabled: true,
createdAt: Date.now()
}
this.reminders.push(reminder)
this.saveToStorage()
return reminder
}
获取待处理的提醒
getPendingReminders(): { card: MemberCard; reminder: ReminderConfig; daysLeft: number }[] {
const results: { card: MemberCard; reminder: ReminderConfig; daysLeft: number }[] = []
const now = Date.now()
this.reminders.filter(r => r.enabled).forEach(reminder => {
const card = this.cards.find(c => c.id === reminder.cardId)
if (!card) return
let targetDate = 0
if (reminder.type === 'expiry') {
targetDate = new Date(card.expiryDate).getTime()
}
if (targetDate > 0) {
const daysLeft = Math.ceil((targetDate - now) / 86400000)
if (daysLeft <= reminder.daysBefore && daysLeft >= 0) {
results.push({ card, reminder, daysLeft })
}
}
})
return results
}
5.6 统计分析
getStats(): {
total: number
active: number
expiring: number
expired: number
totalPoints: number
categoryStats: Record<CardCategory, number>
} {
const total = this.cards.length
const active = this.cards.filter(c => c.status === 'active').length
const expiring = this.cards.filter(c => c.status === 'expiring').length
const expired = this.cards.filter(c => c.status === 'expired').length
const totalPoints = this.cards.reduce((sum, c) => sum + c.points, 0)
const categoryStats = {} as Record<CardCategory, number>
this.cards.forEach(c => {
categoryStats[c.category] = (categoryStats[c.category] || 0) + 1
})
return { total, active, expiring, expired, totalPoints, categoryStats }
}
六、卡片状态管理
6.1 状态定义
export type CardStatus = 'active' | 'expiring' | 'expired' | 'frozen'
export const STATUS_CONFIG: Record<CardStatus, { label: string; color: string }> = {
active: { label: '有效', color: '#4CAF50' },
expiring: { label: '即将到期', color: '#FF9800' },
expired: { label: '已过期', color: '#F44336' },
frozen: { label: '已冻结', color: '#9E9E9E' }
}
状态说明:
| 状态 | 颜色 | 说明 |
|---|---|---|
| active | 绿色 | 卡片正常有效期内 |
| expiring | 橙色 | 30天内即将到期 |
| expired | 红色 | 已过期 |
| frozen | 灰色 | 用户手动冻结 |
6.2 状态流转
active(有效) → expiring(即将到期) → expired(已过期)
↓
frozen(已冻结) → active(重新激活)
状态操作:
| 操作 | 说明 |
|---|---|
| 冻结 | 将卡片状态设为 frozen |
| 解冻 | 根据有效期自动恢复为 active/expiring/expired |
| 自动更新 | 每次获取卡片列表时自动检查并更新状态 |
七、UI 组件设计
7.1 布局结构
┌─────────────────────────────────────────────────────────┐
│ 头部工具栏 │
├─────────────────────────────────────────────────────────┤
│ 统计卡片 │ 有效卡片 │ 即将到期 │ 总积分 │
├─────────────────────────────────────────────────────────┤
│ 🔔 到期提醒横幅(如有即将到期的卡片) │
├─────────────────────────────────────────────────────────┤
│ │ │
│ 侧边栏 │ 主内容区 │
│ │ │
│ - 搜索 │ [卡片列表] [条形码] [到期提醒] Tab切换 │
│ - 卡片类型 │ │
│ - 卡片状态 │ ┌──────┐ ┌──────┐ │
│ │ │卡片面 │ │卡片面 │ 详情面板 │
│ │ └──────┘ └──────┘ ┌──────────┐ │
│ │ │卡片详情 │ │
│ │ │基本信息 │ │
│ │ │会员权益 │ │
│ │ │提醒设置 │ │
│ │ │快捷操作 │ │
│ │ └──────────┘ │
└───────────┴─────────────────────────────────────────────┘
7.2 卡片面设计
<div class="card-face" :style="{
background: `linear-gradient(135deg, ${card.color}, ${card.color}dd)`
}">
<div class="card-top">
<span class="card-icon">{{ card.icon }}</span>
<span class="card-level">{{ card.level }}</span>
<span class="card-status" :style="{ color: STATUS_CONFIG[card.status].color }">
{{ STATUS_CONFIG[card.status].label }}
</span>
</div>
<h3 class="card-name">{{ card.cardName }}</h3>
<p class="card-merchant">{{ card.merchant }}</p>
<div class="card-bottom">
<span class="card-number">{{ maskCardNumber(card.cardNumber) }}</span>
<span class="card-points" v-if="card.points > 0">
{{ card.points.toLocaleString() }} {{ card.pointsLabel }}
</span>
</div>
</div>
卡片面元素:
| 元素 | 说明 |
|---|---|
| 渐变背景 | 135度渐变色,增强视觉效果 |
| 卡片图标 | Emoji图标,直观识别 |
| 会员等级 | 半透明标签显示等级 |
| 卡片名称 | 主标题显示卡名称 |
| 商户名称 | 副标题显示商户 |
| 卡号 | 部分隐藏显示,保护隐私 |
| 积分 | 大数字显示积分余额 |
7.3 条形码展示
<div class="barcode-code">
<div class="barcode-visual">
<div v-for="i in 60" :key="i"
class="barcode-line"
:style="{ width: (i % 3 === 0 ? '3px' : '1px') }">
</div>
</div>
<p class="barcode-number">{{ barcodeCard.barcode }}</p>
</div>
条形码功能:
| 功能 | 说明 |
|---|---|
| 条形码模式 | 模拟条形码视觉效果 |
| 二维码模式 | 可切换为二维码展示 |
| 卡号显示 | 在条形码下方显示完整卡号 |
| 卡片信息 | 在条形码上方显示卡片基本信息 |
八、核心功能亮点
8.1 多维度卡片筛选
系统支持同时应用多种筛选条件:
const filteredCards = computed(() => {
let result = cards.value
// 关键词搜索
if (searchKeyword.value) {
result = membershipService.searchCards(searchKeyword.value)
}
// 卡片类型筛选
if (selectedCategory.value !== 'all') {
result = result.filter(c => c.category === selectedCategory.value)
}
// 卡片状态筛选
if (selectedStatus.value !== 'all') {
result = result.filter(c => c.status === selectedStatus.value)
}
return result
})
筛选组合示例:
| 筛选条件 | 结果 |
|---|---|
| 类型=餐饮 + 状态=有效 | 所有有效的餐饮会员卡 |
| 状态=即将到期 | 所有即将到期的会员卡 |
| 搜索关键词"沃尔玛" | 所有包含"沃尔玛"的会员卡 |
8.2 到期提醒中心
<div class="expiring-list">
<div v-for="card in expiringCards" :key="card.id"
:class="['expiring-card', { 'very-soon': getDaysLeft(card.expiryDate) <= 7 }]">
<div class="expiring-header">
<span class="expiring-icon">{{ card.icon }}</span>
<span class="expiring-name">{{ card.cardName }}</span>
<span class="expiring-days">{{ getDaysLeft(card.expiryDate) }} 天后到期</span>
</div>
<div class="expiring-info">
<span>到期日期: {{ card.expiryDate }}</span>
<span v-if="card.points > 0">
积分: {{ card.points.toLocaleString() }} {{ card.pointsLabel }}
</span>
</div>
<div class="expiring-actions">
<button class="btn btn-sm btn-primary" @click="viewCard(card.id)">查看</button>
<button class="btn btn-sm btn-secondary" @click="openWebsite(card.website)" v-if="card.website">
续费
</button>
</div>
</div>
</div>
8.3 快捷操作
<div class="quick-actions">
<button class="action-btn" v-if="selectedCard.phone" @click="callPhone(selectedCard.phone)">
📱 拨打电话
</button>
<button class="action-btn" v-if="selectedCard.website" @click="openWebsite(selectedCard.website)">
🌐 访问官网
</button>
<button class="action-btn" @click="copyCardNumber(selectedCard.cardNumber)">
📋 复制卡号
</button>
<button class="action-btn" @click="updatePointsPrompt(selectedCard)">
✏️ 更新积分
</button>
</div>
九、构建与部署
9.1 构建输出
✓ 37 modules transformed.
../dist/index.html 0.67 kB │ gzip: 0.47 kB
../dist/assets/index-CBgsX6DZ.css 0.21 kB │ gzip: 0.19 kB
../dist/assets/MembershipView-D6bcOJKM.css 12.55 kB │ gzip: 2.57 kB
../dist/assets/MembershipView-IPj7dfAS.js 31.29 kB │ gzip: 10.35 kB
../dist/assets/index-CnndVyO_.js 91.48 kB │ gzip: 35.87 kB
✓ built in 677ms
构建指标分析:
| 指标 | 值 | 说明 |
|---|---|---|
| 模块转换 | 37个 | Vue SFC + TS 模块 |
| 总 JS 大小 | 122.77 KB | 未压缩 |
| Gzip 压缩 | 46.22 KB | 压缩率 62.4% |
| 构建时间 | 677ms | Vite 5.0 性能 |
9.2 构建脚本
# 清理缓存
Remove-Item -Recurse -Force "dist" -ErrorAction SilentlyContinue
Remove-Item -Recurse -Force ".hvigor" -ErrorAction SilentlyContinue
# 执行构建
npm run build
十、HarmonyOS 集成指南
10.1 集成步骤
步骤一:构建
cd vue-app
npm install
npm run build
步骤二:复制产物
cp -r dist ../ohos_hap/web_engine/src/main/resources/resfile/resources/
步骤三:ArkUI 加载
import { webview } from '@kit.ArkWeb'
@Entry
@Component
struct MembershipWalletPage {
controller: webview.WebviewController = new webview.WebviewController()
build() {
Column() {
Web({ src: $rawfile('dist/index.html'), controller: this.controller })
.javaScriptAccess(true)
.domStorageAccess(true)
.onPageEnd(() => {
console.info('会员卡包管理系统加载完成')
})
}
}
}
十一、卡片排序算法
11.1 多字段排序策略
getCards(): MemberCard[] {
this.updateCardStatuses()
return [...this.cards].sort((a, b) => {
// 按状态优先级排序
const statusOrder: CardStatus[] = ['expiring', 'active', 'frozen', 'expired']
if (a.status !== b.status) {
return statusOrder.indexOf(a.status) - statusOrder.indexOf(b.status)
}
// 其次按更新时间倒序
return b.updatedAt - a.updatedAt
})
}
排序规则:
| 排序层级 | 字段 | 顺序 | 说明 |
|---|---|---|---|
| 第一层 | status | 即将到期→有效→已冻结→已过期 | 重要卡片在前 |
| 第二层 | updatedAt | 新→旧 | 最近更新的在前 |
十二、演示数据说明
12.1 预置会员卡数据
系统预置了 8 张典型会员卡:
| 卡名称 | 商户 | 类型 | 等级 | 积分 | 状态 | 到期日期 |
|---|---|---|---|---|---|---|
| 沃尔玛购物卡 | 沃尔玛 | 超市 | 金卡 | 5680积分 | 有效 | 2027-12-31 |
| 海底捞会员卡 | 海底捞火锅 | 餐饮 | 黑海会员 | 12500捞币 | 有效 | 2026-12-31 |
| 万达影城会员卡 | 万达影城 | 娱乐 | 钻石卡 | 850积分 | 即将到期 | 2026-05-31 |
| 超级猩猩健身卡 | 超级猩猩 | 健身 | 年卡会员 | 45课时 | 有效 | 2026-12-31 |
| 丝芙兰会员卡 | 丝芙兰 | 美容 | 金卡 | 3200积分 | 有效 | 2027-03-31 |
| 国航知音卡 | 中国国航 | 旅行 | 银卡 | 28500里程 | 有效 | 2028-12-31 |
| 优衣库会员卡 | 优衣库 | 购物 | 普通卡 | 1580积分 | 已冻结 | 2027-06-30 |
| 星巴克星享卡 | 星巴克 | 餐饮 | 玉星级 | 4200星星 | 有效 | 2026-12-31 |
会员权益示例:
| 会员卡 | 权益1 | 权益2 | 权益3 |
|---|---|---|---|
| 沃尔玛 | 会员折扣 | 双倍积分 | 免费停车 |
| 海底捞 | 优先排队 | 生日优惠 | 免费菜品 |
| 万达影城 | 票价优惠 | 卖品折扣 | 生日观影 |
| 超级猩猩 | 无限预约 | 专属储物柜 | 体测服务 |
| 丝芙兰 | 积分兑换 | 生日礼物 | 专属折扣 |
| 国航知音 | 优先值机 | 额外行李 | 里程兑换 |
| 星巴克 | 免费升杯 | 生日饮品 | 积分兑换 |
十三、常见问题 FAQ
Q1: 如何对接商家真实 API?
解答:当前系统为手动录入模式,如需对接真实商家 API 可考虑以下方案:
| 方案 | 复杂度 | 说明 |
|---|---|---|
| 商家开放平台 | 较高 | 各商家开放API(如星巴克、海底捞) |
| 第三方聚合 | 中等 | 使用第三方会员卡管理API |
| OCR识别 | 中等 | 拍照识别实体卡信息 |
建议架构:
前端 Vue3 应用
↓ HTTP 请求
商家 API / 第三方聚合API
↓
返回积分/有效期数据 → 更新系统状态
Q2: 如何实现真正的条形码生成?
解答:当前系统使用模拟条形码展示,如需生成真实条形码可使用以下库:
| 方案 | 说明 | 安装方式 |
|---|---|---|
| JsBarcode | 支持多种条形码格式 | npm install jsbarcode |
| qrcode | 生成二维码 | npm install qrcode |
| bwip-js | 支持多种条码类型 | npm install bwip-js |
JsBarcode 集成示例:
import JsBarcode from 'jsbarcode'
function renderBarcode(element: HTMLElement, data: string): void {
JsBarcode(element, data, {
format: 'CODE128',
width: 2,
height: 50,
displayValue: true
})
}
Q3: 如何实现实时到期提醒?
解答:当前系统基于前端组件实现提醒功能,如需实时推送可考虑以下方案:
| 方案 | 说明 | 适用场景 |
|---|---|---|
| 浏览器通知 | 使用 Notification API | PC端后台运行 |
| 定时轮询 | 定时检查到期卡片 | 简单场景 |
| 服务端推送 | 使用 Service Worker | PWA 应用 |
浏览器通知示例:
// 请求通知权限
Notification.requestPermission().then(permission => {
if (permission === 'granted') {
new Notification('会员卡到期提醒', {
body: `您的${cardName}将在${daysLeft}天后到期`,
icon: '/logo.png'
})
}
})
Q4: 如何批量导入会员卡?
解答:使用导出/导入功能,格式如下:
{
"cards": [
{
"cardName": "沃尔玛购物卡",
"merchant": "沃尔玛",
"cardNumber": "880012345678",
"category": "supermarket",
"level": "金卡",
"points": 5680,
"pointsUnit": "points",
"pointsLabel": "积分",
"expiryDate": "2027-12-31",
"benefits": [
{ "title": "会员折扣", "description": "全场9.5折", "icon": "💰" }
]
}
]
}
Q5: 如何自定义卡片类型?
解答:在 types/membership.ts 中修改类型定义和配置即可:
// 添加新卡片类型
export type CardCategory = 'shopping' | 'dining' | 'entertainment'
| 'fitness' | 'beauty' | 'travel' | 'supermarket' | 'education' | 'other'
// 在配置中添加新类型
export const CARD_CATEGORY_CONFIG: Record<CardCategory, CardCategoryConfig> = {
// ... 已有配置
education: { label: '教育', icon: '📚', color: '#4CAF50' },
// ...
}
Q6: 会员卡数据会不会丢失?
解答:系统使用 localStorage 持久化数据,具有以下特点:
| 特性 | 说明 |
|---|---|
| 持久性 | 关闭浏览器后数据依然存在 |
| 本地性 | 数据仅保存在当前浏览器中 |
| 容量 | 约 5-10MB(足够存储数百张会员卡元数据) |
| 风险 | 清除浏览器缓存会导致数据丢失 |
建议:定期使用导出功能备份数据,防止意外丢失。
Q7: 如何管理大量会员卡?
解答:当前系统支持以下管理方式:
| 方式 | 说明 |
|---|---|
| 搜索 | 按卡名、卡号、商户、等级搜索 |
| 筛选 | 按卡片类型/状态筛选 |
| 排序 | 即将到期卡片优先显示 |
| 状态管理 | 自动更新卡片状态 |
| 到期提醒 | 自定义到期提醒天数 |
| 导出备份 | 定期导出 JSON 备份数据 |
Q8: 系统支持多语言吗?
解答:当前版本为中文界面,国际化(i18n)可通过 vue-i18n 实现:
// 安装 vue-i18n
npm install vue-i18n
// 配置多语言
import { createI18n } from 'vue-i18n'
const i18n = createI18n({
locale: 'zh-CN',
messages: {
'zh-CN': {
membership: { title: '会员卡包', addCard: '添加会员卡' },
status: { active: '有效', expiring: '即将到期' }
},
'en-US': {
membership: { title: 'Membership Cards', addCard: 'Add Card' },
status: { active: 'Active', expiring: 'Expiring Soon' }
}
}
})
十四、扩展与二次开发
14.1 可添加的功能模块
| 功能 | 描述 | 优先级 |
|---|---|---|
| 📷 拍照识别 | 拍摄实体卡自动识别信息 | P2 |
| 📊 积分统计 | 积分变化趋势分析 | P2 |
| 🔔 实时推送 | 自动检查积分和到期状态 | P0 |
| 🗺️ 门店地图 | 显示商家门店位置 | P2 |
| 🔒 数据加密 | 会员卡数据本地加密 | P1 |
| 🔄 云同步 | 多设备会员卡同步 | P1 |
| 📋 批量操作 | 批量导入、批量更新 | P2 |
| 🎨 卡片模板 | 自定义卡片面样式 | P3 |
14.2 条形码生成
<template>
<div class="barcode-container">
<!-- 使用 JsBarcode 生成真实条形码 -->
<svg ref="barcodeRef"></svg>
<!-- 使用 qrcode 生成二维码 -->
<canvas ref="qrcodeRef"></canvas>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted, watch } from 'vue'
import JsBarcode from 'jsbarcode'
import QRCode from 'qrcode'
const barcodeRef = ref<SVGElement>()
const qrcodeRef = ref<HTMLCanvasElement>()
function renderBarcode(card: MemberCard): void {
if (barcodeRef.value && card.barcodeType === 'code128') {
JsBarcode(barcodeRef.value, card.cardNumber, {
format: 'CODE128',
width: 2,
height: 80,
displayValue: true
})
} else if (qrcodeRef.value && card.barcodeType === 'qrcode') {
QRCode.toCanvas(qrcodeRef.value, card.cardNumber, {
width: 200,
margin: 2
})
}
}
</script>
14.3 自动拉取积分
class AutoSyncManager {
private syncInterval = 24 * 60 * 60 * 1000 // 每天同步一次
start(): void {
setInterval(() => this.syncAllCards(), this.syncInterval)
}
private async syncAllCards(): Promise<void> {
const activeCards = membershipService.getActiveCards()
for (const card of activeCards) {
try {
const info = await this.fetchCardInfo(card.cardNumber, card.category)
if (info) {
membershipService.updateCard(card.id, {
points: info.points,
expiryDate: info.expiryDate,
level: info.level
})
}
} catch (error) {
console.error(`同步卡片 ${card.cardName} 失败:`, error)
}
}
}
}
十五、最佳实践
15.1 会员卡管理规范
| 实践 | 说明 |
|---|---|
| 及时录入 | 办卡后立即录入系统 |
| 完整信息 | 填写完整的卡号、商户、到期日期 |
| 设置权益 | 记录会员权益,方便查看 |
| 定期更新 | 消费后及时更新积分 |
| 备份数据 | 定期导出 JSON 备份 |
15.2 到期提醒设置建议
到期提醒建议:
- 年费会员卡:提前60天提醒续费
- 储值卡:提前30天提醒使用余额
- 积分卡:提前90天提醒使用积分
- 健身卡:提前30天提醒续期
提醒设置推荐:
| 卡片类型 | 建议提醒天数 | 说明 |
|---|---|---|
| 年费会员 | 60天 | 预留充足续费时间 |
| 储值卡 | 30天 | 及时使用余额 |
| 积分卡 | 90天 | 积分有效期长 |
| 健身卡 | 30天 | 及时续期保持优惠 |
| 超市卡 | 15天 | 短期提醒即可 |
15.3 积分管理技巧
| 技巧 | 说明 |
|---|---|
| 定期查看 | 每月检查一次积分余额 |
| 及时使用 | 积分到期前使用完毕 |
| 对比价值 | 了解积分兑换规则和价值 |
| 关注活动 | 多倍积分活动时集中消费 |
十六、性能优化
16.1 前端优化策略
| 优化项 | 方法 | 效果 |
|---|---|---|
| 组件懒加载 | Vue Router 动态 import | 减少初始加载时间 |
| 按需引入 | 仅引入使用的库 | 减小打包体积 |
| Gzip 压缩 | Vite build 默认开启 | 减少 60%+ 传输体积 |
| 虚拟列表 | 大量数据使用虚拟滚动 | 保持流畅滚动 |
| 防抖节流 | 搜索输入使用防抖 | 减少不必要的计算 |
16.2 大数据量处理
// 虚拟列表实现(处理 500+ 会员卡)
const VISIBLE_COUNT = 20
const ITEM_HEIGHT = 180
const visibleCards = computed(() => {
const start = Math.floor(scrollTop.value / ITEM_HEIGHT)
const end = Math.min(start + VISIBLE_COUNT, filteredCards.value.length)
return filteredCards.value.slice(start, end)
})
const paddingTop = computed(() => {
const start = Math.floor(scrollTop.value / ITEM_HEIGHT)
return `${start * ITEM_HEIGHT}px`
})
十七、技术对比
17.1 同类方案对比
| 对比维度 | 本系统 | 商家官方App | 第三方卡包App |
|---|---|---|---|
| 统一管理 | ✅ 所有会员卡 | ❌ 仅限一家 | ⚠️ 支持多家 |
| 离线使用 | ✅ 无需网络 | ❌ 需要联网 | ❌ 需要联网 |
| 到期提醒 | ✅ 可自定义 | ❌ 无 | ⚠️ 部分支持 |
| 条形码展示 | ✅ 支持 | ✅ 支持 | ✅ 支持 |
| 数据备份 | ✅ JSON 导出 | ❌ 不支持 | ❌ 不支持 |
| 隐私保护 | ✅ 本地存储 | ❌ 数据在服务器 | ❌ 数据在服务器 |
| 成本 | ✅ 免费 | ✅ 免费 | ⚠️ 部分付费 |
| 定制能力 | ✅ 完全可控 | ❌ 无法定制 | ❌ 无法定制 |
17.2 技术选型对比
| 框架 | 上手难度 | 性能 | 生态 | 适合场景 |
|---|---|---|---|---|
| Vue 3 | ⭐⭐ | 优秀 | 丰富 | 中小型项目、快速开发 |
| React | ⭐⭐⭐ | 优秀 | 最丰富 | 大型项目、复杂交互 |
| Angular | ⭐⭐⭐⭐ | 优秀 | 丰富 | 企业级应用 |
十八、总结与展望
18.1 项目总结
本会员卡包管理系统实现了以下核心目标:
✅ 实体卡电子化:8 种卡片类型,自定义卡片面,模拟真实会员卡
✅ 条形码展示:条形码/二维码切换,出示给商家扫描使用
✅ 积分管理:积分余额查看、手动更新、统计分析
✅ 到期提醒:自定义提醒天数,即将到期卡片自动高亮
✅ 会员权益:展示每张卡的会员权益列表,权益一目了然
✅ 多维筛选与搜索:按卡片类型/状态筛选 + 全文搜索
✅ 类型安全保障:TypeScript 严格类型检查,编译时发现问题
✅ HarmonyOS 集成:可直接构建部署到鸿蒙设备
18.2 未来展望
| 方向 | 规划 |
|---|---|
| OCR识别 | 拍照识别实体卡,自动录入信息 |
| 条形码生成 | 使用 JsBarcode 生成真实条形码 |
| 自动同步 | 对接商家API自动同步积分和状态 |
| 云同步 | 多设备会员卡实时同步 |
| 地图集成 | 显示商家门店位置和导航 |
| 智能推荐 | 基于消费习惯推荐优惠活动 |
| 数据分析 | 积分变化趋势、消费习惯分析 |
| NFC支持 | NFC 读取实体卡信息 |
18.3 核心价值
“会员卡管理不是目的,让每一分积分都有价值,每一次权益都不被浪费才是核心价值。”
本系统的核心价值在于:
- 整合管理:统一管理所有会员卡,不再翻找实体卡
- 防止遗忘:到期提醒,避免积分过期和卡片失效
- 便捷使用:条形码展示,手机直接出示给商家
- 数据安全:本地存储,保护个人隐私信息
参考资料
- Vue 3 官方文档 - Composition API
- TypeScript 官方文档
- Vite 构建工具文档
- HarmonyOS ArkWeb 组件开发
- JsBarcode 条形码生成库
- qrcode 二维码生成库
- CSDN 博客质量分 V5.0 评分标准
- 浏览器 Notification API
- localStorage 浏览器存储 API
更多推荐




所有评论(0)