Files
medical-mall/docs/CRMEB_TO_UVUE_MIGRATION_GUIDE.md
2026-01-28 17:54:30 +08:00

807 lines
18 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# CRMEB商城系统到uvue项目的重构迁移指南
## 项目概述
本文档基于CRMEB开源商城系统PHP版本指导如何将其核心功能迁移到基于uvue技术栈的项目中。后端使用`@components/supadb`组件库实现不使用PHP技术栈。
## 参考项目分析
### CRMEB核心功能模块
#### 1. 用户系统 (User Module)
- **用户注册登录**:手机号验证码、微信授权登录
- **用户资料管理**:个人信息、收货地址、会员等级
- **用户积分系统**:积分获取、积分消费记录
- **分销系统**:用户推广、佣金结算
#### 2. 商品系统 (Product Module)
- **商品管理**商品信息、SKU规格、商品分类
- **商品展示**:商品详情、商品列表、商品搜索
- **库存管理**:商品库存、规格库存管理
#### 3. 订单系统 (Order Module)
- **购物车**:添加商品、修改数量、删除商品
- **订单创建**:订单确认、地址选择、支付方式
- **订单管理**:订单状态跟踪、订单取消、退款处理
- **物流跟踪**:快递信息查询、物流状态更新
#### 4. 营销活动 (Activity Module)
- **秒杀活动**:限时抢购、库存锁定
- **拼团活动**:团购发起、参团流程
- **砍价活动**:好友助力、砍价进度
- **优惠券系统**:券领取、使用规则
- **积分商城**:积分兑换商品
#### 5. 支付系统 (Payment Module)
- **多渠道支付**:微信支付、支付宝、余额支付
- **支付回调**:订单状态更新、支付记录
#### 6. 客服系统 (Service Module)
- **在线客服**:实时聊天、消息记录
- **售后服务**:退换货处理、服务评价
#### 7. 内容管理系统 (CMS Module)
- **文章系统**:资讯发布、分类管理
- **广告位管理**首页banner、推荐位
#### 8. 系统配置 (System Module)
- **基础配置**:站点信息、支付配置、物流配置
- **权限管理**:管理员权限、操作日志
## 技术栈对比
### 原CRMEB技术栈
```
后端: ThinkPHP 6 + MySQL + Redis
前端: Vue2 + ElementUI + UniApp
其他: Workerman(长连接)、队列、定时任务
```
### 目标技术栈
```
后端: Supabase (PostgreSQL + Auth + Storage + Edge Functions)
前端: uvue + @components/supadb
其他: 实时订阅、文件存储、服务器less函数
```
## 数据架构设计
### Supabase数据库表结构设计
#### 核心数据表
##### 1. 用户表 (users)
```sql
-- 继承Supabase auth.users表扩展字段
CREATE TABLE public.users (
id UUID REFERENCES auth.users(id) PRIMARY KEY,
phone TEXT,
nickname TEXT,
avatar_url TEXT,
gender INTEGER DEFAULT 0,
birthday DATE,
level_id INTEGER DEFAULT 0,
integral INTEGER DEFAULT 0,
balance DECIMAL(10,2) DEFAULT 0,
spread_uid INTEGER,
spread_time TIMESTAMP WITH TIME ZONE,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
```
##### 2. 商品表 (products)
```sql
CREATE TABLE public.products (
id SERIAL PRIMARY KEY,
title TEXT NOT NULL,
description TEXT,
images TEXT[],
category_id INTEGER,
brand_id INTEGER,
price DECIMAL(10,2),
ot_price DECIMAL(10,2),
cost DECIMAL(10,2),
stock INTEGER DEFAULT 0,
sales INTEGER DEFAULT 0,
is_show BOOLEAN DEFAULT true,
is_del BOOLEAN DEFAULT false,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
```
##### 3. 商品规格表 (product_skus)
```sql
CREATE TABLE public.product_skus (
id SERIAL PRIMARY KEY,
product_id INTEGER REFERENCES products(id),
sku TEXT,
price DECIMAL(10,2),
stock INTEGER DEFAULT 0,
image TEXT,
attributes JSONB,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
```
##### 4. 订单表 (orders)
```sql
CREATE TABLE public.orders (
id SERIAL PRIMARY KEY,
order_sn TEXT UNIQUE,
user_id UUID REFERENCES users(id),
total_price DECIMAL(10,2),
pay_price DECIMAL(10,2),
coupon_price DECIMAL(10,2),
pay_type INTEGER DEFAULT 1, -- 1微信 2余额 3线下
status INTEGER DEFAULT 0, -- 订单状态
address_info JSONB,
mark TEXT,
paid BOOLEAN DEFAULT false,
pay_time TIMESTAMP WITH TIME ZONE,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
```
##### 5. 订单商品表 (order_items)
```sql
CREATE TABLE public.order_items (
id SERIAL PRIMARY KEY,
order_id INTEGER REFERENCES orders(id),
product_id INTEGER REFERENCES products(id),
sku_id INTEGER REFERENCES product_skus(id),
product_title TEXT,
product_image TEXT,
sku_info JSONB,
price DECIMAL(10,2),
quantity INTEGER,
total_price DECIMAL(10,2),
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
```
##### 6. 购物车表 (cart)
```sql
CREATE TABLE public.cart (
id SERIAL PRIMARY KEY,
user_id UUID REFERENCES users(id),
product_id INTEGER REFERENCES products(id),
sku_id INTEGER REFERENCES product_skus(id),
quantity INTEGER,
selected BOOLEAN DEFAULT true,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
```
##### 7. 优惠券表 (coupons)
```sql
CREATE TABLE public.coupons (
id SERIAL PRIMARY KEY,
title TEXT,
type INTEGER, -- 1满减 2折扣
value DECIMAL(10,2),
min_price DECIMAL(10,2),
use_start_time TIMESTAMP WITH TIME ZONE,
use_end_time TIMESTAMP WITH TIME ZONE,
stock INTEGER,
receive_count INTEGER DEFAULT 0,
is_show BOOLEAN DEFAULT true,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
```
##### 8. 用户优惠券表 (user_coupons)
```sql
CREATE TABLE public.user_coupons (
id SERIAL PRIMARY KEY,
user_id UUID REFERENCES users(id),
coupon_id INTEGER REFERENCES coupons(id),
status INTEGER DEFAULT 0, -- 0未使用 1已使用 2已过期
use_time TIMESTAMP WITH TIME ZONE,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
```
## API接口设计
### 使用@components/supadb实现的数据操作
#### 用户相关接口
##### 用户注册
```typescript
// 使用Supabase Auth实现
const { data, error } = await supa.auth.signUp({
email: 'user@example.com',
password: 'password'
})
```
##### 用户登录
```typescript
const { data, error } = await supa.auth.signInWithPassword({
email: 'user@example.com',
password: 'password'
})
```
##### 获取用户信息
```typescript
// 使用supadb组件
<supadb
collection="users"
:filter="{ id: userId }"
:getone="true"
v-slot="{ data: userInfo }"
>
<!-- 用户信息显示 -->
</supadb>
```
##### 更新用户信息
```typescript
const result = await supa.from('users').update(userData).eq('id', userId)
```
#### 商品相关接口
##### 获取商品列表
```vue
<supadb
collection="products"
:filter="{ is_show: true, is_del: false }"
:orderby="'sales.desc'"
:pageSize="20"
v-slot="{ data: products, loading, hasmore, loadMore }"
>
<view v-for="product in products" :key="product.id">
<!-- 商品卡片 -->
</view>
<button v-if="hasmore" @click="loadMore">加载更多</button>
</supadb>
```
##### 获取商品详情
```vue
<supadb
collection="products"
:filter="{ id: productId }"
:getone="true"
v-slot="{ data: product }"
>
<!-- 商品详情页 -->
</supadb>
```
##### 商品搜索
```vue
<supadb
collection="products"
:filter="{ title: { ilike: `%${keyword}%` }, is_show: true }"
v-slot="{ data: searchResults }"
>
<!-- 搜索结果 -->
</supadb>
```
#### 订单相关接口
##### 创建订单
```typescript
// 先创建订单记录
const orderData = {
order_sn: generateOrderSn(),
user_id: userId,
total_price: totalPrice,
// ...其他字段
}
const { data: order } = await supa.from('orders').insert(orderData).select().single()
// 然后创建订单商品记录
const orderItems = cartItems.map(item => ({
order_id: order.id,
product_id: item.product_id,
// ...其他字段
}))
await supa.from('order_items').insert(orderItems)
```
##### 获取订单列表
```vue
<supadb
collection="orders"
:filter="{ user_id: userId }"
:orderby="'created_at.desc'"
v-slot="{ data: orders }"
>
<view v-for="order in orders" :key="order.id">
<!-- 订单卡片 -->
</view>
</supadb>
```
#### 营销活动接口
##### 秒杀活动
```vue
<supadb
collection="seckill_products"
:filter="{
start_time: { lte: currentTime },
end_time: { gte: currentTime },
stock: { gt: 0 }
}"
v-slot="{ data: seckillProducts }"
>
<!-- 秒杀商品列表 -->
</supadb>
```
##### 优惠券领取
```typescript
// 检查用户是否已领取
const { data: existing } = await supa
.from('user_coupons')
.select('*')
.eq('user_id', userId)
.eq('coupon_id', couponId)
.single()
if (!existing) {
await supa.from('user_coupons').insert({
user_id: userId,
coupon_id: couponId
})
}
```
## uvue前端页面重构方案
### 页面结构重组
#### 1. 首页 (pages/index/index.uvue)
```vue
<template>
<view class="index-page">
<!-- 轮播图 -->
<swiper-banner />
<!-- 分类导航 -->
<category-nav />
<!-- 商品推荐 -->
<product-recommend />
<!-- 营销活动 -->
<activity-section />
</view>
</template>
<script setup lang="uts">
import { ref } from 'vue'
import supa from '@/components/supadb/aksupainstance.uts'
// 首页数据
const bannerList = ref([])
const categoryList = ref([])
const recommendProducts = ref([])
// 获取首页数据
const loadHomeData = async () => {
// 获取轮播图
const bannerResult = await supa.from('banners').select('*').eq('position', 'home')
bannerList.value = bannerResult.data || []
// 获取分类
const categoryResult = await supa.from('categories').select('*').eq('level', 1)
categoryList.value = categoryResult.data || []
// 获取推荐商品
const productResult = await supa.from('products')
.select('*')
.eq('is_recommend', true)
.eq('is_show', true)
.limit(10)
recommendProducts.value = productResult.data || []
}
</script>
```
#### 2. 商品详情页 (pages/goods/detail.uvue)
```vue
<template>
<view class="product-detail">
<supadb
collection="products"
:filter="{ id: productId }"
:getone="true"
v-slot="{ data: product, loading }"
>
<!-- 商品图片轮播 -->
<image-swiper :images="product?.images || []" />
<!-- 商品信息 -->
<product-info :product="product" />
<!-- 商品规格选择 -->
<sku-selector
:product="product"
@sku-change="handleSkuChange"
/>
<!-- 商品详情 -->
<product-description :content="product?.description" />
</supadb>
</view>
</template>
<script setup lang="uts">
import { ref } from 'vue'
import supa from '@/components/supadb/aksupainstance.uts'
const props = defineProps<{
productId: number
}>()
const selectedSku = ref(null)
const handleSkuChange = (sku) => {
selectedSku.value = sku
}
</script>
```
#### 3. 购物车页面 (pages/cart/index.uvue)
```vue
<template>
<view class="cart-page">
<supadb
collection="cart"
:filter="{ user_id: userId }"
v-slot="{ data: cartItems, loading }"
>
<!-- 购物车商品列表 -->
<cart-item
v-for="item in cartItems"
:key="item.id"
:item="item"
@quantity-change="updateQuantity"
@delete="removeItem"
/>
<!-- 结算栏 -->
<cart-footer
:items="cartItems"
@checkout="goCheckout"
/>
</supadb>
</view>
</template>
<script setup lang="uts">
import { ref } from 'vue'
import supa from '@/components/supadb/aksupainstance.uts'
const userId = ref('') // 从用户信息获取
const updateQuantity = async (itemId, quantity) => {
await supa.from('cart').update({ quantity }).eq('id', itemId)
}
const removeItem = async (itemId) => {
await supa.from('cart').delete().eq('id', itemId)
}
const goCheckout = () => {
// 跳转到结算页面
uni.navigateTo({
url: '/pages/order/checkout'
})
}
</script>
```
## 实时功能实现
### 使用Supabase实时订阅
#### 订单状态更新监听
```typescript
// 监听订单状态变化
const orderSubscription = supa
.channel('order-updates')
.on('postgres_changes',
{
event: 'UPDATE',
schema: 'public',
table: 'orders',
filter: `user_id=eq.${userId}`
},
(payload) => {
console.log('Order updated:', payload)
// 更新订单状态
}
)
.subscribe()
```
#### 库存变化监听
```typescript
// 监听商品库存变化
const stockSubscription = supa
.channel('stock-updates')
.on('postgres_changes',
{
event: 'UPDATE',
schema: 'public',
table: 'products'
},
(payload) => {
// 更新本地商品库存
updateLocalStock(payload.new)
}
)
.subscribe()
```
## 文件存储实现
### 使用Supabase Storage
#### 商品图片上传
```typescript
const uploadProductImage = async (filePath: string, productId: number) => {
const fileName = `product_${productId}_${Date.now()}.jpg`
const { data, error } = await supa.storage
.from('products')
.upload(fileName, filePath)
if (data) {
const { data: urlData } = supa.storage
.from('products')
.getPublicUrl(fileName)
return urlData.publicUrl
}
}
```
#### 用户头像上传
```typescript
const uploadAvatar = async (filePath: string) => {
const fileName = `avatar_${userId}_${Date.now()}.jpg`
const { data, error } = await supa.storage
.from('avatars')
.upload(fileName, filePath)
if (data) {
const { data: urlData } = supa.storage
.from('avatars')
.getPublicUrl(fileName)
// 更新用户头像
await supa.from('users').update({
avatar_url: urlData.publicUrl
}).eq('id', userId)
}
}
```
## 服务器端逻辑实现
### 使用Supabase Edge Functions
#### 订单创建函数
```typescript
// supabase/functions/create-order/index.ts
import { serve } from "https://deno.land/std@0.168.0/http/server.ts"
import { createClient } from 'https://esm.sh/@supabase/supabase-js@2'
serve(async (req) => {
const { userId, items, address } = await req.json()
const supabase = createClient(
Deno.env.get('SUPABASE_URL') ?? '',
Deno.env.get('SUPABASE_ANON_KEY') ?? ''
)
// 生成订单号
const orderSn = `ORDER${Date.now()}${Math.random().toString(36).substr(2, 6).toUpperCase()}`
// 计算总价
let totalPrice = 0
for (const item of items) {
const { data: product } = await supabase
.from('products')
.select('price')
.eq('id', item.productId)
.single()
totalPrice += product.price * item.quantity
}
// 创建订单
const { data: order, error } = await supabase
.from('orders')
.insert({
order_sn: orderSn,
user_id: userId,
total_price: totalPrice,
address_info: address
})
.select()
.single()
if (error) throw error
// 创建订单商品记录
const orderItems = items.map(item => ({
order_id: order.id,
product_id: item.productId,
quantity: item.quantity,
price: item.price,
total_price: item.price * item.quantity
}))
const { error: itemsError } = await supabase
.from('order_items')
.insert(orderItems)
if (itemsError) throw itemsError
return new Response(
JSON.stringify({ order }),
{ headers: { "Content-Type": "application/json" } }
)
})
```
#### 支付回调函数
```typescript
// supabase/functions/payment-callback/index.ts
import { serve } from "https://deno.land/std@0.168.0/http/server.ts"
import { createClient } from 'https://esm.sh/@supabase/supabase-js@2'
serve(async (req) => {
const { orderSn, paymentResult } = await req.json()
const supabase = createClient(
Deno.env.get('SUPABASE_URL') ?? '',
Deno.env.get('SUPABASE_ANON_KEY') ?? ''
)
// 更新订单支付状态
const { error } = await supabase
.from('orders')
.update({
paid: true,
pay_time: new Date().toISOString(),
status: 1 // 已支付
})
.eq('order_sn', orderSn)
if (error) throw error
return new Response(
JSON.stringify({ success: true }),
{ headers: { "Content-Type": "application/json" } }
)
})
```
## 组件重构对照表
### CRMEB组件 → uvue组件映射
| CRMEB组件 | uvue组件 | 功能说明 |
|----------|---------|---------|
| HomeComb | home-comb.uvue | 首页搜索组合 |
| GoodList | product-list.uvue | 商品列表 |
| CouponWindow | coupon-popup.uvue | 优惠券弹窗 |
| CartList | cart-list.uvue | 购物车列表 |
| Payment | payment-selector.uvue | 支付方式选择 |
| AddressWindow | address-selector.uvue | 地址选择弹窗 |
| UserEvaluation | product-review.uvue | 商品评价 |
| ShareRedPackets | share-popup.uvue | 分享红包 |
## 性能优化建议
### 1. 数据缓存策略
```typescript
// 使用Supabase内置缓存
const { data, error } = await supa
.from('products')
.select('*')
.eq('category_id', categoryId)
.order('sales', { ascending: false })
.limit(20)
// 启用缓存
.single()
```
### 2. 图片懒加载
```vue
<template>
<image
:src="imageUrl"
:lazy-load="true"
@load="onImageLoad"
@error="onImageError"
/>
</template>
```
### 3. 列表虚拟化
```vue
<template>
<scroll-view
scroll-y="true"
:scroll-top="scrollTop"
@scroll="handleScroll"
>
<view
v-for="(item, index) in visibleItems"
:key="item.id"
:style="{ transform: `translateY(${item.top}px)` }"
>
<!-- 商品项 -->
</view>
</scroll-view>
</template>
```
## 部署和维护
### 环境配置
```javascript
// config/app.js
export default {
supabase: {
url: 'https://your-project.supabase.co',
anonKey: 'your-anon-key',
serviceRoleKey: 'your-service-role-key' // 服务端使用
}
}
```
### 数据库迁移
```sql
-- 数据库初始化脚本
-- 创建表结构
-- 设置RLS策略
-- 创建索引
-- 设置触发器
```
### 监控和日志
```typescript
// 错误监控
const handleError = (error) => {
console.error('App Error:', error)
// 上报到监控系统
}
// 性能监控
const reportPerformance = (metrics) => {
// 上报性能数据
}
```
## 总结
通过本重构指南我们将CRMEB的核心功能成功迁移到基于uvue + Supabase的技术栈
1. **数据层**使用Supabase替代MySQL + Redis
2. **API层**:使用@components/supadb替代ThinkPHP
3. **前端**使用uvue替代uni-app
4. **实时功能**使用Supabase实时订阅
5. **文件存储**使用Supabase Storage
6. **服务器逻辑**使用Edge Functions
这种架构具有以下优势:
- **开发效率高**:减少后端开发工作
- **维护成本低**Serverless架构
- **扩展性好**:支持实时功能和全球化部署
- **安全性高**Supabase提供完善的安全机制
实际迁移时需要根据具体业务需求进行调整,并充分测试各项功能。