From a4fa00c93501c27c16b745b7d262d0bd5a838dcd Mon Sep 17 00:00:00 2001 From: cyh666666 <2398882793@qq.com> Date: Wed, 28 Jan 2026 17:28:50 +0800 Subject: [PATCH] =?UTF-8?q?consumer=E6=A8=A1=E5=9D=97=E5=AE=8C=E6=88=90?= =?UTF-8?q?=E5=BA=A685%=EF=BC=8C=E6=B5=8B=E8=AF=95=E8=BF=9E=E6=8E=A5supaba?= =?UTF-8?q?se?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ak/config.uts | 6 +- doc_mall/SUPABASE_DATA_MIGRATION_GUIDE.md | 247 ++++++ pages.json | 53 +- pages/mall/consumer/cart.uvue | 12 +- pages/mall/consumer/index.uvue | 17 +- pages/mall/consumer/orders.uvue | 8 +- pages/mall/consumer/product-detail.uvue | 32 +- pages/mall/consumer/settings.uvue | 23 +- pages/mall/consumer/wallet.uvue | 415 +++++---- pages/mall/consumer/wallett.uvue | 984 ++++++++++++++++++++++ pages/user/bind-email.uvue | 619 +++----------- pages/user/bind-phone.uvue | 623 +++----------- pages/user/change-password.uvue | 404 ++------- pages/user/test/DEBUG_SIGNUP.md | 4 +- pages/user/test/IMMEDIATE_FIX.md | 2 +- pages/user/test/QUICK_FIX.md | 2 +- pages/user/test/QUICK_FIX_SIGNUP_LOGIN.md | 2 +- utils/supabaseService.uts | 228 +++++ 18 files changed, 2108 insertions(+), 1573 deletions(-) create mode 100644 doc_mall/SUPABASE_DATA_MIGRATION_GUIDE.md create mode 100644 pages/mall/consumer/wallett.uvue create mode 100644 utils/supabaseService.uts diff --git a/ak/config.uts b/ak/config.uts index 7269882b..290e5606 100644 --- a/ak/config.uts +++ b/ak/config.uts @@ -1,12 +1,12 @@ // Supabase 配置 // 内网环境 - 本地部署的 Supabase -// IP: 192.168.1.63 +// IP: 192.168.1.61 (Ubuntu服务器) // Kong HTTP Port: 8000 -export const SUPA_URL: string = 'http://192.168.1.63:8000' +export const SUPA_URL: string = 'http://192.168.1.61:8000' export const SUPA_KEY: string = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoiYW5vbiIsImlzcyI6InN1cGFiYXNlIiwiaWF0IjoxNzY4ODMwNjI0LCJleHAiOjE5MjY1MTA2MjR9.mDVl-kIOdRK9v6VTxo0TDF8r7X7xk3PZXazaavHyVvg' // WebSocket 实时连接(内网使用 ws:// 而非 wss://) -export const WS_URL: string = 'ws://192.168.1.63:8000/realtime/v1/websocket' +export const WS_URL: string = 'ws://192.168.1.61:8000/realtime/v1/websocket' // 备用配置(已注释,如需切换可取消注释) // 开发环境 - 其他内网地址 diff --git a/doc_mall/SUPABASE_DATA_MIGRATION_GUIDE.md b/doc_mall/SUPABASE_DATA_MIGRATION_GUIDE.md new file mode 100644 index 00000000..b0fa749e --- /dev/null +++ b/doc_mall/SUPABASE_DATA_MIGRATION_GUIDE.md @@ -0,0 +1,247 @@ +# Supabase 数据迁移指南 + +## 概述 + +本指南将帮助您将当前使用模拟数据的 uni-app 项目迁移到使用 Supabase 数据库。您的项目已经配置了连接到 Ubuntu 服务器上的 Supabase(IP: 192.168.1.61),现在需要创建数据库表并插入测试数据,然后修改前端代码以使用真实数据。 + +## 第一步:在 Supabase 中创建数据库表 + +### 方法一:通过 Supabase Dashboard 执行 SQL + +1. **打开 Supabase Dashboard** + - 访问:http://192.168.1.61:3000 + - 使用 Dashboard 用户名和密码登录(位于 `supabase_pro/.env` 中的 `DASHBOARD_USERNAME` 和 `DASHBOARD_PASSWORD`) + +2. **进入 SQL Editor** + - 在左侧菜单中点击 "SQL Editor" + - 点击 "New query" 创建新查询 + +3. **执行建表脚本** + - 复制 `sql/001_create_tables.sql` 文件中的全部内容 + - 粘贴到 SQL Editor 中 + - 点击 "Run" 执行 + +4. **执行插入数据脚本** + - 复制 `sql/002_insert_test_data.sql` 文件中的全部内容 + - 粘贴到 SQL Editor 中 + - 点击 "Run" 执行 + +### 方法二:通过命令行执行(如果 Supabase 运行在 Ubuntu 服务器上) + +```bash +# 登录到 Ubuntu 服务器 +ssh hfkj@192.168.1.61 + +# 进入 Supabase 项目目录(假设 Supabase 安装在默认位置) +cd ~/supabase + +# 使用 psql 连接到数据库执行 SQL 脚本 +# 注意:需要知道数据库密码(位于 supabase_pro/.env 中的 POSTGRES_PASSWORD) +PGPASSWORD=yxyHINygZMLSq9jLddrZQBB-CoyGHSF5DwlwWmbrYXc psql -h localhost -U postgres -d postgres -f /path/to/001_create_tables.sql +PGPASSWORD=yxyHINygZMLSq9jLddrZQBB-CoyGHSF5DwlwWmbrYXc psql -h localhost -U postgres -d postgres -f /path/to/002_insert_test_data.sql +``` + +## 第二步:验证数据表创建成功 + +### 在 Supabase Dashboard 中验证 + +1. **查看 Tables** + - 在左侧菜单中点击 "Table Editor" + - 应该能看到 `categories` 和 `products` 表 + +2. **查看数据** + - 点击 `categories` 表,应该能看到 10 条分类数据 + - 点击 `products` 表,应该能看到 18 条商品数据 + +### 通过 API 验证 + +1. **测试分类 API** + ``` + GET http://192.168.1.61:8000/rest/v1/categories + Headers: + apikey: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoiYW5vbiIsImlzcyI6InN1cGFiYXNlIiwiaWF0IjoxNzY4ODMwNjI0LCJleHAiOjE5MjY1MTA2MjR9.mDVl-kIOdRK9v6VTxo0TDF8r7X7xk3PZXazaavHyVvg + ``` + +2. **测试商品 API** + ``` + GET http://192.168.1.61:8000/rest/v1/products?category_id=eq.cold + Headers: + apikey: [同上] + ``` + +## 第三步:修改前端代码使用真实数据 + +### 1. 已创建的服务文件 + +我已经创建了 `utils/supabaseService.uts` 文件,提供了以下功能: +- `getCategories()` - 获取所有分类 +- `getProductsByCategory()` - 获取指定分类的商品 +- `searchProducts()` - 搜索商品 +- `getProductById()` - 获取单个商品详情 +- `getHotProducts()` - 获取热销商品 +- `getRecommendedProducts()` - 获取推荐商品 + +### 2. 修改分类页面 (`pages/mall/consumer/category.uvue`) + +需要将硬编码的模拟数据替换为从 Supabase 获取的数据: + +```typescript +// 在 script 部分添加导入 +import supabaseService from '@/utils/supabaseService.uts' +import type { Category, Product } from '@/utils/supabaseService.uts' + +// 替换 medicineCategories 的初始化 +// 删除原有的 medicineCategories 数组定义 + +// 修改 onMounted 或创建新的生命周期函数 +onMounted(async () => { + await loadCategories() + await loadProducts() +}) + +// 添加加载分类的方法 +const loadCategories = async () => { + const categories = await supabaseService.getCategories() + if (categories.length > 0) { + primaryCategories.value = categories + // 设置默认选中第一个分类 + if (!activePrimary.value && categories[0]) { + activePrimary.value = categories[0].id + } + } +} + +// 修改 selectPrimaryCategory 方法 +const selectPrimaryCategory = async (categoryId: string) => { + activePrimary.value = categoryId + + // 更新当前分类信息 + const category = primaryCategories.value.find(cat => cat.id === categoryId) + if (category) { + currentCategoryName.value = category.name + currentCategoryDesc.value = category.description + } + + // 加载对应商品 + const response = await supabaseService.getProductsByCategory(categoryId) + productList.value = response.data + hasMore.value = response.hasmore +} +``` + +### 3. 修改主页 (`pages/mall/consumer/index.uvue`) + +如果主页显示商品,也需要修改为从 Supabase 获取: + +```typescript +import supabaseService from '@/utils/supabaseService.uts' + +// 获取热销商品 +const loadHotProducts = async () => { + hotProducts.value = await supabaseService.getHotProducts(6) +} + +// 获取推荐商品 +const loadRecommendedProducts = async () => { + recommendedProducts.value = await supabaseService.getRecommendedProducts(6) +} +``` + +## 第四步:测试连接和数据 + +### 1. 测试 Supabase 连接 + +创建一个测试页面或使用现有的页面测试连接: + +```typescript +// 测试代码示例 +const testConnection = async () => { + try { + const categories = await supabaseService.getCategories() + console.log('连接成功,获取到分类数:', categories.length) + uni.showToast({ + title: `连接成功,获取到 ${categories.length} 个分类`, + icon: 'success' + }) + } catch (error) { + console.error('连接失败:', error) + uni.showToast({ + title: '连接失败,请检查配置', + icon: 'error' + }) + } +} +``` + +### 2. 测试数据加载 + +在分类页面测试: +1. 打开分类页面 +2. 检查分类列表是否显示 +3. 点击不同分类,检查商品列表是否更新 +4. 检查商品图片、价格等信息是否正确显示 + +### 3. 测试搜索功能 + +如果项目有搜索页面,测试搜索功能: +1. 输入关键字搜索 +2. 检查返回的商品是否相关 + +## 第五步:处理图片 URL + +### 当前情况 +- 数据库中的 `image` 字段目前为空或使用本地路径 +- 实际项目中,图片应该存储在 Supabase Storage 或 CDN + +### 临时解决方案 +在显示图片时,如果数据库中没有图片 URL,使用默认图片: + +```typescript +const getProductImage = (product: Product) => { + if (product.image && product.image.startsWith('http')) { + return product.image + } + return '/static/images/default-product.png' +} +``` + +### 长期解决方案 +1. 将图片上传到 Supabase Storage +2. 更新数据库中的 `image` 字段为完整 URL + +## 常见问题解决 + +### 1. 连接超时或失败 +- 检查 Ubuntu 服务器上的 Supabase 是否正常运行 +- 检查防火墙设置,确保 8000 和 3000 端口可访问 +- 检查 `ak/config.uts` 中的 IP 地址是否正确 + +### 2. 401 未授权错误 +- 检查 `SUPA_KEY` 是否正确(与 `supabase_pro/.env` 中的 `ANON_KEY` 一致) +- 检查 Supabase 是否已启用匿名访问 + +### 3. 表不存在错误 +- 确认已执行 SQL 脚本创建表 +- 检查表名是否拼写正确(区分大小写) + +### 4. 数据不显示 +- 检查浏览器控制台是否有错误 +- 检查网络请求是否成功 +- 确认数据库中有数据 + +## 下一步优化建议 + +1. **添加加载状态**:在数据加载时显示加载动画 +2. **错误处理**:添加更完善的错误处理和重试机制 +3. **数据缓存**:使用本地存储缓存常用数据,减少网络请求 +4. **分页加载**:实现滚动加载更多商品 +5. **图片优化**:使用图片懒加载和压缩 + +## 总结 + +通过以上步骤,您的项目将从使用模拟数据过渡到使用 Supabase 数据库数据。主要工作包括: +1. 在 Supabase 中创建表和插入测试数据 +2. 修改前端代码使用新的服务层 +3. 测试连接和数据加载 + +完成后,您的应用将具备完整的后端数据支持,为后续添加用户管理、购物车、订单等功能打下基础。 diff --git a/pages.json b/pages.json index 41dc0f0d..30c70f87 100644 --- a/pages.json +++ b/pages.json @@ -1,11 +1,12 @@ { "pages": [ { - "path": "pages/user/login", + "path": "pages/mall/consumer/index", "style": { - "navigationBarTitleText": "用户登录", - "navigationStyle": "custom" - } + "navigationBarTitleText": "首页", + "navigationStyle": "custom", + "enablePullDownRefresh": true + } }, { "path": "pages/user/boot", @@ -44,11 +45,28 @@ } }, { - "path": "pages/mall/consumer/index", + "path": "pages/user/login", "style": { - "navigationBarTitleText": "首页", - "navigationStyle": "custom", - "enablePullDownRefresh": true + "navigationBarTitleText": "用户登录", + "navigationStyle": "custom" + } + }, + { + "path": "pages/user/change-password", + "style": { + "navigationBarTitleText": "修改密码" + } + }, + { + "path": "pages/user/bind-phone", + "style": { + "navigationBarTitleText": "绑定手机" + } + }, + { + "path": "pages/user/bind-email", + "style": { + "navigationBarTitleText": "绑定邮箱" } }, { @@ -75,9 +93,7 @@ "style": { "navigationBarTitleText": "我的" } - } - ], - "subPackages": [ + }, { "path": "pages/mall/consumer/search", "style": { @@ -195,8 +211,21 @@ "navigationBarTitleText": "客服聊天", "navigationStyle": "custom" } + }, + { + "path": "pages/mall/consumer/settings", + "style": { + "navigationBarTitleText": "设置" + } + }, + { + "path": "pages/mall/consumer/wallet", + "style": { + "navigationBarTitleText": "我的钱包" + } } ], + "subPackages": [], "tabBar": { "color": "#999999", "selectedColor": "#ff5000", @@ -241,4 +270,4 @@ "navigationBarBackgroundColor": "#FFFFFF", "backgroundColor": "#F8F8F8" } -} \ No newline at end of file +} diff --git a/pages/mall/consumer/cart.uvue b/pages/mall/consumer/cart.uvue index 5809cc95..25d48391 100644 --- a/pages/mall/consumer/cart.uvue +++ b/pages/mall/consumer/cart.uvue @@ -482,8 +482,18 @@ const goShopping = () => { const navigateToProduct = (product: any) => { // 使用productId(如果存在)作为跳转的商品ID,否则使用id const productId = product.productId || product.id + // 传递完整的参数,确保商品详情页能正确加载 + const params = new URLSearchParams() + params.append('id', productId) + params.append('productId', productId) + params.append('price', product.price?.toString() || '0') + // 商品详情页期望的参数名是originalPrice + params.append('originalPrice', (product.original_price || product.originalPrice || (product.price * 1.2).toFixed(2))?.toString()) + params.append('name', encodeURIComponent(product.name || '')) + params.append('image', encodeURIComponent(product.image || '/static/product1.jpg')) + uni.navigateTo({ - url: `/pages/mall/consumer/product-detail?id=${productId}&name=${encodeURIComponent(product.name)}&price=${product.price}&image=${encodeURIComponent(product.image)}` + url: `/pages/mall/consumer/product-detail?${params.toString()}` }) } diff --git a/pages/mall/consumer/index.uvue b/pages/mall/consumer/index.uvue index 8c6c10a6..854af7d0 100644 --- a/pages/mall/consumer/index.uvue +++ b/pages/mall/consumer/index.uvue @@ -346,6 +346,7 @@ \ No newline at end of file diff --git a/pages/user/bind-email.uvue b/pages/user/bind-email.uvue index 3fdd3ad5..dc7cadc1 100644 --- a/pages/user/bind-email.uvue +++ b/pages/user/bind-email.uvue @@ -1,544 +1,135 @@ + \ No newline at end of file diff --git a/pages/user/bind-phone.uvue b/pages/user/bind-phone.uvue index c98e6406..f0e0e08d 100644 --- a/pages/user/bind-phone.uvue +++ b/pages/user/bind-phone.uvue @@ -1,548 +1,135 @@ + \ No newline at end of file diff --git a/pages/user/change-password.uvue b/pages/user/change-password.uvue index 3b8584d3..2cc8b4fc 100644 --- a/pages/user/change-password.uvue +++ b/pages/user/change-password.uvue @@ -1,353 +1,103 @@ + \ No newline at end of file diff --git a/pages/user/test/DEBUG_SIGNUP.md b/pages/user/test/DEBUG_SIGNUP.md index 39fc1359..30ac5940 100644 --- a/pages/user/test/DEBUG_SIGNUP.md +++ b/pages/user/test/DEBUG_SIGNUP.md @@ -41,7 +41,7 @@ docker-compose logs auth | grep -i error ### 4. 验证配置是否生效 -在 Supabase Dashboard (http://192.168.1.63:3000) 的 SQL Editor 中执行: +在 Supabase Dashboard (http://192.168.1.61:3000) 的 SQL Editor 中执行: ```sql -- 检查当前配置(需要访问 GoTrue 配置) @@ -59,7 +59,7 @@ docker-compose logs auth | grep -i error 确认 `ak/config.uts` 中的配置正确: ```typescript -export const SUPA_URL: string = 'http://192.168.1.63:8000' +export const SUPA_URL: string = 'http://192.168.1.61:8000' export const SUPA_KEY: string = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...' ``` diff --git a/pages/user/test/IMMEDIATE_FIX.md b/pages/user/test/IMMEDIATE_FIX.md index 384778d3..4e072b39 100644 --- a/pages/user/test/IMMEDIATE_FIX.md +++ b/pages/user/test/IMMEDIATE_FIX.md @@ -58,7 +58,7 @@ grep ENABLE_EMAIL_AUTOCONFIRM supabase_pro/.env ## 🔍 验证用户是否创建 -在 Supabase Dashboard (http://192.168.1.63:3000) 的 SQL Editor 中执行: +在 Supabase Dashboard (http://192.168.1.61:3000) 的 SQL Editor 中执行: ```sql -- 检查最新注册的用户 diff --git a/pages/user/test/QUICK_FIX.md b/pages/user/test/QUICK_FIX.md index bf2fea13..184941cd 100644 --- a/pages/user/test/QUICK_FIX.md +++ b/pages/user/test/QUICK_FIX.md @@ -16,7 +16,7 @@ **执行步骤**: -1. **在 Supabase Dashboard (http://192.168.1.63:3000) 中打开 SQL Editor** +1. **在 Supabase Dashboard (http://192.168.1.61:3000) 中打开 SQL Editor** 2. **执行 `USER_AUTH_SCHEMA.sql`** - 创建 `ak_users` 表和 RLS 策略 diff --git a/pages/user/test/QUICK_FIX_SIGNUP_LOGIN.md b/pages/user/test/QUICK_FIX_SIGNUP_LOGIN.md index 51d239d9..12172425 100644 --- a/pages/user/test/QUICK_FIX_SIGNUP_LOGIN.md +++ b/pages/user/test/QUICK_FIX_SIGNUP_LOGIN.md @@ -44,7 +44,7 @@ docker-compose restart auth ### 方法一:在 Supabase Dashboard 中手动确认 -1. 打开 Supabase Dashboard: http://192.168.1.63:3000 +1. 打开 Supabase Dashboard: http://192.168.1.61:3000 2. 进入 **Authentication** → **Users** 3. 找到对应的用户 4. 点击用户,在详情页中点击 **Confirm Email** 按钮 diff --git a/utils/supabaseService.uts b/utils/supabaseService.uts new file mode 100644 index 00000000..0fd1cd5c --- /dev/null +++ b/utils/supabaseService.uts @@ -0,0 +1,228 @@ +import { createClient } from '@/components/supadb/aksupa.uts' +import { SUPA_URL, SUPA_KEY } from '@/ak/config.uts' +import type { AkReqResponse } from '@/uni_modules/ak-req/index.uts' + +// 创建 Supabase 客户端 +const supa = createClient(SUPA_URL, SUPA_KEY) + +// 类型定义 +export interface Category { + id: string + name: string + icon: string + description: string + color: string + created_at?: string +} + +export interface Product { + id: string + category_id: string + name: string + specification: string + price: number + original_price?: number + image?: string + manufacturer: string + sales: number + badge?: string + shop_id?: string + shop_name?: string + created_at?: string +} + +export interface PaginatedResponse { + data: T[] + total: number + page: number + limit: number + hasmore: boolean +} + +class SupabaseService { + // 获取所有分类 + async getCategories(): Promise { + try { + const response = await supa + .from('categories') + .select('*') + .order('name', { ascending: true }) + .execute() + + if (response.error) { + console.error('获取分类失败:', response.error) + return [] + } + + return response.data as Category[] + } catch (error) { + console.error('获取分类异常:', error) + return [] + } + } + + // 获取指定分类的商品 + async getProductsByCategory( + categoryId: string, + page: number = 1, + limit: number = 20 + ): Promise> { + try { + const response = await supa + .from('products') + .select('*', { count: 'exact' }) + .eq('category_id', categoryId) + .order('sales', { ascending: false }) + .page(page) + .limit(limit) + .execute() + + if (response.error) { + console.error('获取商品失败:', response.error) + return { + data: [], + total: 0, + page, + limit, + hasmore: false + } + } + + return { + data: response.data as Product[], + total: response.total || 0, + page, + limit, + hasmore: response.hasmore || false + } + } catch (error) { + console.error('获取商品异常:', error) + return { + data: [], + total: 0, + page, + limit, + hasmore: false + } + } + } + + // 搜索商品 + async searchProducts( + keyword: string, + page: number = 1, + limit: number = 20 + ): Promise> { + try { + const response = await supa + .from('products') + .select('*', { count: 'exact' }) + .or(`name.ilike.%${keyword}%,manufacturer.ilike.%${keyword}%,specification.ilike.%${keyword}%`) + .order('sales', { ascending: false }) + .page(page) + .limit(limit) + .execute() + + if (response.error) { + console.error('搜索商品失败:', response.error) + return { + data: [], + total: 0, + page, + limit, + hasmore: false + } + } + + return { + data: response.data as Product[], + total: response.total || 0, + page, + limit, + hasmore: response.hasmore || false + } + } catch (error) { + console.error('搜索商品异常:', error) + return { + data: [], + total: 0, + page, + limit, + hasmore: false + } + } + } + + // 获取单个商品详情 + async getProductById(productId: string): Promise { + try { + const response = await supa + .from('products') + .select('*') + .eq('id', productId) + .single() + .execute() + + if (response.error) { + console.error('获取商品详情失败:', response.error) + return null + } + + return response.data as Product + } catch (error) { + console.error('获取商品详情异常:', error) + return null + } + } + + // 获取热销商品 + async getHotProducts(limit: number = 10): Promise { + try { + const response = await supa + .from('products') + .select('*') + .order('sales', { ascending: false }) + .limit(limit) + .execute() + + if (response.error) { + console.error('获取热销商品失败:', response.error) + return [] + } + + return response.data as Product[] + } catch (error) { + console.error('获取热销商品异常:', error) + return [] + } + } + + // 获取推荐商品(带badge的商品) + async getRecommendedProducts(limit: number = 10): Promise { + try { + const response = await supa + .from('products') + .select('*') + .not('badge', 'is', null) + .order('sales', { ascending: false }) + .limit(limit) + .execute() + + if (response.error) { + console.error('获取推荐商品失败:', response.error) + return [] + } + + return response.data as Product[] + } catch (error) { + console.error('获取推荐商品异常:', error) + return [] + } + } +} + +// 导出单例实例 +export const supabaseService = new SupabaseService() + +// 默认导出 +export default supabaseService