# 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提供完善的安全机制

实际迁移时需要根据具体业务需求进行调整，并充分测试各项功能。