登录注册/数据分析
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -428,7 +428,7 @@ function changeTrendPeriod(period: string) {
|
||||
|
||||
function viewReportDetail(reportId: string) {
|
||||
uni.navigateTo({
|
||||
url: `/pages/mall/analytics/report-detail?id=${reportId}`
|
||||
url: `/pages/mall/analytics/report-detail?reportId=${reportId}`
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -248,9 +248,18 @@ export default {
|
||||
}
|
||||
},
|
||||
onLoad(options: any) {
|
||||
const reportId = options.reportId as string
|
||||
// 兼容两种参数名:reportId 和 id
|
||||
const reportId = (options.reportId || options.id) as string
|
||||
if (reportId) {
|
||||
this.loadReportDetail(reportId)
|
||||
} else {
|
||||
uni.showToast({
|
||||
title: '缺少报表ID',
|
||||
icon: 'none'
|
||||
})
|
||||
setTimeout(() => {
|
||||
uni.navigateBack()
|
||||
}, 1500)
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
|
||||
181
pages/mall/analytics/test/README.md
Normal file
181
pages/mall/analytics/test/README.md
Normal file
@@ -0,0 +1,181 @@
|
||||
# 数据分析实时大屏 - 测试数据说明
|
||||
|
||||
本目录包含用于测试数据分析实时大屏功能的 SQL 脚本和测试数据。
|
||||
|
||||
## 文件说明
|
||||
|
||||
### 1. `01_create_tables.sql`
|
||||
创建所需的数据表结构,包括:
|
||||
- `orders` - 订单表
|
||||
- `user_sessions` - 用户会话表
|
||||
- `users` - 用户表
|
||||
- `products` - 商品表(可选)
|
||||
- `order_items` - 订单商品关联表(可选)
|
||||
- `page_views` - 访问日志表(可选)
|
||||
|
||||
**执行顺序:** 首先执行此文件创建表结构
|
||||
|
||||
### 2. `02_insert_test_data.sql`
|
||||
插入测试数据,包括:
|
||||
- 8个测试用户
|
||||
- 5个在线用户会话(最近5分钟内有活动)
|
||||
- 15个今日订单(用于计算实时GMV和订单数)
|
||||
- 10个昨日同时段订单(用于计算增长率)
|
||||
- 15条访问日志(用于转化率计算)
|
||||
|
||||
**执行顺序:** 在创建表后执行此文件插入测试数据
|
||||
|
||||
### 3. `03_test_queries.sql`
|
||||
包含各种测试查询,用于验证数据计算逻辑:
|
||||
- 实时GMV查询
|
||||
- 在线用户查询
|
||||
- 转化率查询
|
||||
- 综合实时大屏数据查询
|
||||
- 数据验证查询
|
||||
|
||||
**执行顺序:** 在插入测试数据后执行此文件验证数据
|
||||
|
||||
## 使用方法
|
||||
|
||||
### 方式 1: 通过 Supabase Dashboard(推荐)
|
||||
|
||||
1. **访问 Dashboard**
|
||||
```
|
||||
http://192.168.1.63:8000
|
||||
或
|
||||
http://192.168.1.63:3000 (Studio 默认端口)
|
||||
```
|
||||
|
||||
2. **登录**
|
||||
- 用户名:`supabase`
|
||||
- 密码:`D4ce5p8YBpfYzEoDGZ_7MzehZcWrdCNyDEj_VSUBmOw`
|
||||
|
||||
3. **打开 SQL Editor**
|
||||
- 在左侧菜单找到 "SQL Editor"
|
||||
- 点击 "New Query"
|
||||
|
||||
4. **执行脚本**
|
||||
- 复制 `01_create_tables.sql` 的内容,粘贴并执行
|
||||
- 复制 `02_insert_test_data.sql` 的内容,粘贴并执行
|
||||
- (可选)复制 `03_test_queries.sql` 的内容,验证数据
|
||||
|
||||
### 方式 2: 使用命令行(PostgreSQL)
|
||||
|
||||
```bash
|
||||
# 连接到内网 Supabase 数据库
|
||||
psql -h 192.168.1.63 -p 5432 -U postgres -d postgres
|
||||
|
||||
# 输入密码:yxyHINygZMLSq9jLddrZQBB-CoyGHSF5DwlwWmbrYXc
|
||||
|
||||
# 执行 SQL 文件(需要完整路径)
|
||||
\i D:/datas/hfkj/mall/pages/mall/analytics/test/01_create_tables.sql
|
||||
\i D:/datas/hfkj/mall/pages/mall/analytics/test/02_insert_test_data.sql
|
||||
\i D:/datas/hfkj/mall/pages/mall/analytics/test/03_test_queries.sql
|
||||
```
|
||||
|
||||
### 方式 3: 使用图形工具(DBeaver / pgAdmin)
|
||||
|
||||
1. **创建连接**
|
||||
- 主机:`192.168.1.63`
|
||||
- 端口:`5432`
|
||||
- 数据库:`postgres`
|
||||
- 用户名:`postgres`
|
||||
- 密码:`yxyHINygZMLSq9jLddrZQBB-CoyGHSF5DwlwWmbrYXc`
|
||||
|
||||
2. **执行 SQL**
|
||||
- 打开 SQL 编辑器
|
||||
- 复制 SQL 文件内容并执行
|
||||
|
||||
**详细说明请查看:`SQL_USAGE_GUIDE.md`**
|
||||
|
||||
## 测试数据说明
|
||||
|
||||
### 实时GMV测试数据
|
||||
- **今日订单总数:** 15笔
|
||||
- **今日GMV:** 约 3,500 元(根据订单金额累加)
|
||||
- **昨日同时段订单:** 10笔
|
||||
- **昨日同时段GMV:** 约 2,200 元
|
||||
- **预期增长率:** 约 59%((3500-2200)/2200 * 100)
|
||||
|
||||
### 实时订单测试数据
|
||||
- **今日订单数:** 15笔
|
||||
- **昨日同时段订单数:** 10笔
|
||||
- **预期增长率:** 50%((15-10)/10 * 100)
|
||||
|
||||
### 在线用户测试数据
|
||||
- **最近5分钟内有活动的用户:** 5个
|
||||
- 这些用户会在实时大屏中显示为"在线用户"
|
||||
|
||||
### 转化率测试数据
|
||||
- **今日访问用户数:** 约 10-15个(从 user_sessions 表统计)
|
||||
- **今日下单用户数:** 约 8个(从 orders 表去重统计)
|
||||
- **预期转化率:** 约 53-80%(根据实际数据计算)
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. **时间依赖**
|
||||
- 测试数据使用了 `NOW()` 和相对时间(如 `INTERVAL '1 hour'`)
|
||||
- 每次执行时,数据的时间戳会基于当前时间生成
|
||||
- 建议在测试前先清空相关表的数据(谨慎操作)
|
||||
|
||||
2. **数据冲突**
|
||||
- 脚本使用了 `ON CONFLICT DO NOTHING` 或 `ON CONFLICT DO UPDATE`
|
||||
- 可以多次执行而不会产生重复数据
|
||||
- 如需重新生成数据,请先清空表
|
||||
|
||||
3. **状态值**
|
||||
- 订单状态:`2` 表示已支付/已完成
|
||||
- 用户会话:`is_active = true` 表示活跃会话
|
||||
|
||||
4. **UUID 格式**
|
||||
- 所有 ID 使用 UUID 格式
|
||||
- 测试数据使用了固定的 UUID 便于识别
|
||||
|
||||
## 清理测试数据
|
||||
|
||||
如果需要清理测试数据,可以执行:
|
||||
|
||||
```sql
|
||||
-- 谨慎操作:清空测试数据
|
||||
TRUNCATE TABLE orders, user_sessions, users, order_items, page_views CASCADE;
|
||||
```
|
||||
|
||||
或者删除特定时间范围的数据:
|
||||
|
||||
```sql
|
||||
-- 删除今日的测试订单
|
||||
DELETE FROM orders WHERE created_at >= DATE_TRUNC('day', NOW());
|
||||
|
||||
-- 删除测试用户会话
|
||||
DELETE FROM user_sessions WHERE created_at >= DATE_TRUNC('day', NOW());
|
||||
```
|
||||
|
||||
## 验证实时大屏功能
|
||||
|
||||
执行完测试数据后,在数据分析页面应该能看到:
|
||||
|
||||
1. **实时GMV:** 约 ¥3,500(根据实际订单金额)
|
||||
2. **实时订单:** 15笔
|
||||
3. **在线用户:** 5人
|
||||
4. **转化率:** 约 50-80%(根据实际计算)
|
||||
|
||||
增长率会根据昨日同时段的数据自动计算。
|
||||
|
||||
## 问题排查
|
||||
|
||||
如果实时大屏显示异常,可以:
|
||||
|
||||
1. 执行 `03_test_queries.sql` 中的查询验证数据
|
||||
2. 检查订单状态是否为 `2`(已支付)
|
||||
3. 检查时间范围是否正确(今日 vs 昨日同时段)
|
||||
4. 检查用户会话的 `last_active_at` 是否在最近5分钟内
|
||||
5. 查看浏览器控制台的错误信息
|
||||
|
||||
## 扩展测试数据
|
||||
|
||||
如果需要更多测试数据,可以:
|
||||
|
||||
1. 修改 `02_insert_test_data.sql` 中的 INSERT 语句
|
||||
2. 调整订单金额、数量和时间分布
|
||||
3. 添加更多用户和会话数据
|
||||
4. 使用循环生成大量测试数据(注意性能)
|
||||
304
pages/mall/analytics/test/SQL_USAGE_GUIDE.md
Normal file
304
pages/mall/analytics/test/SQL_USAGE_GUIDE.md
Normal file
@@ -0,0 +1,304 @@
|
||||
# SQL 测试脚本使用指南
|
||||
|
||||
本指南说明如何在内网 Supabase 环境中执行测试 SQL 脚本。
|
||||
|
||||
## 📋 目录结构
|
||||
|
||||
```
|
||||
pages/mall/analytics/test/
|
||||
├── 01_create_tables.sql # 创建表结构
|
||||
├── 02_insert_test_data.sql # 插入测试数据
|
||||
├── 03_test_queries.sql # 测试查询
|
||||
├── 04_cleanup.sql # 清理数据
|
||||
└── SQL_USAGE_GUIDE.md # 本指南
|
||||
```
|
||||
|
||||
## 🚀 执行方式
|
||||
|
||||
### 方式 1: 通过 Supabase Dashboard(推荐)
|
||||
|
||||
如果您的内网 Supabase 有 Dashboard 界面:
|
||||
|
||||
1. **访问 Dashboard**
|
||||
```
|
||||
http://192.168.1.63:8000
|
||||
或
|
||||
http://192.168.1.63:3000 (Studio 默认端口)
|
||||
```
|
||||
|
||||
2. **登录**
|
||||
- 用户名:`supabase`(根据您的配置)
|
||||
- 密码:`D4ce5p8YBpfYzEoDGZ_7MzehZcWrdCNyDEj_VSUBmOw`
|
||||
|
||||
3. **打开 SQL Editor**
|
||||
- 在左侧菜单找到 "SQL Editor" 或 "SQL"
|
||||
- 点击 "New Query"
|
||||
|
||||
4. **执行脚本**
|
||||
- 复制 `01_create_tables.sql` 的内容
|
||||
- 粘贴到 SQL Editor
|
||||
- 点击 "Run" 或按 `Ctrl+Enter`
|
||||
- 等待执行完成
|
||||
|
||||
5. **依次执行其他脚本**
|
||||
- 执行 `02_insert_test_data.sql`(插入测试数据)
|
||||
- 执行 `03_test_queries.sql`(验证数据,可选)
|
||||
|
||||
### 方式 2: 通过 PostgreSQL 客户端(psql)
|
||||
|
||||
如果 Dashboard 不可用,可以直接连接 PostgreSQL:
|
||||
|
||||
1. **连接数据库**
|
||||
```bash
|
||||
# 使用 psql 连接
|
||||
psql -h 192.168.1.63 -p 5432 -U postgres -d postgres
|
||||
|
||||
# 输入密码(根据您的配置)
|
||||
# POSTGRES_PASSWORD=yxyHINygZMLSq9jLddrZQBB-CoyGHSF5DwlwWmbrYXc
|
||||
```
|
||||
|
||||
2. **执行 SQL 文件**
|
||||
```sql
|
||||
-- 在 psql 中执行
|
||||
\i /path/to/01_create_tables.sql
|
||||
\i /path/to/02_insert_test_data.sql
|
||||
\i /path/to/03_test_queries.sql
|
||||
```
|
||||
|
||||
或者直接复制粘贴 SQL 内容到 psql 中执行。
|
||||
|
||||
### 方式 3: 通过 DBeaver / pgAdmin 等图形工具
|
||||
|
||||
1. **创建新连接**
|
||||
- 主机:`192.168.1.63`
|
||||
- 端口:`5432`
|
||||
- 数据库:`postgres`
|
||||
- 用户名:`postgres`
|
||||
- 密码:`yxyHINygZMLSq9jLddrZQBB-CoyGHSF5DwlwWmbrYXc`
|
||||
|
||||
2. **执行 SQL**
|
||||
- 打开 SQL 编辑器
|
||||
- 复制 SQL 文件内容
|
||||
- 执行脚本
|
||||
|
||||
### 方式 4: 通过 HTTP API(程序化执行)
|
||||
|
||||
使用 Supabase REST API 执行 SQL(需要 service_role key):
|
||||
|
||||
```javascript
|
||||
// 注意:这种方式需要 Supabase 的 SQL 执行功能
|
||||
// 通常不推荐,因为安全风险较高
|
||||
const response = await fetch('http://192.168.1.63:8000/rest/v1/rpc/exec_sql', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'apikey': 'YOUR_SERVICE_ROLE_KEY',
|
||||
'Authorization': 'Bearer YOUR_SERVICE_ROLE_KEY',
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
sql: 'SELECT * FROM users LIMIT 1;'
|
||||
})
|
||||
})
|
||||
```
|
||||
|
||||
## 📝 执行顺序
|
||||
|
||||
**重要:必须按顺序执行!**
|
||||
|
||||
1. ✅ **第一步:创建表结构**
|
||||
```sql
|
||||
-- 执行 01_create_tables.sql
|
||||
-- 这会创建所有需要的表和索引
|
||||
```
|
||||
|
||||
2. ✅ **第二步:插入测试数据**
|
||||
```sql
|
||||
-- 执行 02_insert_test_data.sql
|
||||
-- 这会插入测试用户、订单、会话等数据
|
||||
```
|
||||
|
||||
3. ✅ **第三步:验证数据(可选)**
|
||||
```sql
|
||||
-- 执行 03_test_queries.sql
|
||||
-- 验证数据是否正确插入,查看统计信息
|
||||
```
|
||||
|
||||
4. ⚠️ **清理数据(需要时)**
|
||||
```sql
|
||||
-- 执行 04_cleanup.sql
|
||||
-- 谨慎使用:会删除测试数据
|
||||
```
|
||||
|
||||
## 🔍 验证执行结果
|
||||
|
||||
### 检查表是否创建成功
|
||||
|
||||
```sql
|
||||
-- 查看所有表
|
||||
SELECT table_name
|
||||
FROM information_schema.tables
|
||||
WHERE table_schema = 'public'
|
||||
ORDER BY table_name;
|
||||
|
||||
-- 应该看到:
|
||||
-- orders
|
||||
-- user_sessions
|
||||
-- users
|
||||
-- products (可选)
|
||||
-- order_items (可选)
|
||||
-- page_views (可选)
|
||||
```
|
||||
|
||||
### 检查数据是否插入成功
|
||||
|
||||
```sql
|
||||
-- 检查用户数量
|
||||
SELECT COUNT(*) FROM users;
|
||||
-- 应该返回 8
|
||||
|
||||
-- 检查订单数量
|
||||
SELECT COUNT(*) FROM orders WHERE created_at >= DATE_TRUNC('day', NOW());
|
||||
-- 应该返回 15(今日订单)
|
||||
|
||||
-- 检查在线用户
|
||||
SELECT COUNT(*) FROM user_sessions
|
||||
WHERE last_active_at >= NOW() - INTERVAL '5 minutes' AND is_active = true;
|
||||
-- 应该返回 5
|
||||
```
|
||||
|
||||
### 检查实时大屏数据
|
||||
|
||||
```sql
|
||||
-- 执行 03_test_queries.sql 中的综合查询
|
||||
-- 应该能看到:
|
||||
-- - 实时GMV: 约 3,500 元
|
||||
-- - 实时订单: 15 笔
|
||||
-- - 在线用户: 5 人
|
||||
-- - 转化率: 约 50-80%
|
||||
```
|
||||
|
||||
## ⚠️ 注意事项
|
||||
|
||||
### 1. 权限问题
|
||||
|
||||
如果遇到权限错误:
|
||||
```sql
|
||||
-- 确保 postgres 用户有足够权限
|
||||
GRANT ALL PRIVILEGES ON DATABASE postgres TO postgres;
|
||||
GRANT ALL PRIVILEGES ON SCHEMA public TO postgres;
|
||||
```
|
||||
|
||||
### 2. 表已存在
|
||||
|
||||
如果表已存在,脚本会使用 `CREATE TABLE IF NOT EXISTS`,不会报错。
|
||||
但如果需要重新创建:
|
||||
```sql
|
||||
-- 先删除表(谨慎操作)
|
||||
DROP TABLE IF EXISTS order_items CASCADE;
|
||||
DROP TABLE IF EXISTS page_views CASCADE;
|
||||
DROP TABLE IF EXISTS user_sessions CASCADE;
|
||||
DROP TABLE IF EXISTS orders CASCADE;
|
||||
DROP TABLE IF EXISTS users CASCADE;
|
||||
DROP TABLE IF EXISTS products CASCADE;
|
||||
```
|
||||
|
||||
### 3. 时间依赖
|
||||
|
||||
测试数据使用 `NOW()` 函数,每次执行都会基于当前时间生成。
|
||||
- 今日订单:基于当前日期
|
||||
- 昨日订单:当前时间往前推 24 小时
|
||||
- 在线用户:最近 5 分钟内有活动
|
||||
|
||||
### 4. UUID 冲突
|
||||
|
||||
如果重复执行插入脚本,由于使用了 `ON CONFLICT DO NOTHING`,不会产生重复数据。
|
||||
但如果需要重新插入,先执行清理脚本。
|
||||
|
||||
## 🐛 常见问题
|
||||
|
||||
### Q1: 连接被拒绝
|
||||
```
|
||||
Error: connection refused
|
||||
```
|
||||
**解决:**
|
||||
- 检查 Supabase 服务是否运行
|
||||
- 检查防火墙设置
|
||||
- 确认端口 5432 是否开放
|
||||
|
||||
### Q2: 认证失败
|
||||
```
|
||||
Error: password authentication failed
|
||||
```
|
||||
**解决:**
|
||||
- 确认密码是否正确:`yxyHINygZMLSq9jLddrZQBB-CoyGHSF5DwlwWmbrYXc`
|
||||
- 检查用户名是否为 `postgres`
|
||||
|
||||
### Q3: 表已存在错误
|
||||
```
|
||||
Error: relation "orders" already exists
|
||||
```
|
||||
**解决:**
|
||||
- 脚本已使用 `IF NOT EXISTS`,通常不会报错
|
||||
- 如需重新创建,先删除表
|
||||
|
||||
### Q4: 权限不足
|
||||
```
|
||||
Error: permission denied
|
||||
```
|
||||
**解决:**
|
||||
- 使用 postgres 超级用户执行
|
||||
- 或授予相应权限
|
||||
|
||||
## 📊 执行后的预期结果
|
||||
|
||||
执行完所有脚本后,您应该能看到:
|
||||
|
||||
1. **数据库表**
|
||||
- 6 个表已创建(orders, user_sessions, users, products, order_items, page_views)
|
||||
- 所有索引已创建
|
||||
|
||||
2. **测试数据**
|
||||
- 8 个测试用户
|
||||
- 15 个今日订单
|
||||
- 10 个昨日订单
|
||||
- 5 个在线用户会话
|
||||
- 15 条访问日志
|
||||
|
||||
3. **实时大屏显示**
|
||||
- 在数据分析页面应该能看到实时数据
|
||||
- GMV、订单数、在线用户、转化率都有值
|
||||
|
||||
## 🔄 重新执行
|
||||
|
||||
如果需要重新生成测试数据:
|
||||
|
||||
1. **清理数据**
|
||||
```sql
|
||||
-- 执行 04_cleanup.sql
|
||||
```
|
||||
|
||||
2. **重新插入**
|
||||
```sql
|
||||
-- 执行 02_insert_test_data.sql
|
||||
```
|
||||
|
||||
## 📞 获取帮助
|
||||
|
||||
如果遇到问题:
|
||||
|
||||
1. 检查 Supabase 日志
|
||||
2. 查看数据库连接状态
|
||||
3. 验证配置文件 `ak/config.uts` 是否正确
|
||||
4. 使用测试页面验证连接:`/pages/mall/analytics/test/test-connection`
|
||||
|
||||
## 🎯 快速开始
|
||||
|
||||
**最简单的执行方式:**
|
||||
|
||||
1. 打开 Supabase Dashboard(如果有)
|
||||
2. 进入 SQL Editor
|
||||
3. 复制 `01_create_tables.sql` 内容,执行
|
||||
4. 复制 `02_insert_test_data.sql` 内容,执行
|
||||
5. 完成!
|
||||
|
||||
现在可以开始测试实时大屏功能了!🎉
|
||||
529
pages/mall/analytics/test/test-connection.uvue
Normal file
529
pages/mall/analytics/test/test-connection.uvue
Normal file
@@ -0,0 +1,529 @@
|
||||
<!-- Supabase 连接测试页面 -->
|
||||
<template>
|
||||
<view class="test-container">
|
||||
<view class="header">
|
||||
<text class="title">Supabase 连接测试</text>
|
||||
</view>
|
||||
|
||||
<view class="config-section">
|
||||
<text class="section-title">当前配置</text>
|
||||
<view class="config-item">
|
||||
<text class="config-label">Supabase URL:</text>
|
||||
<text class="config-value">{{ configUrl }}</text>
|
||||
</view>
|
||||
<view class="config-item">
|
||||
<text class="config-label">API Key:</text>
|
||||
<text class="config-value">{{ configKey.substring(0, 20) }}...</text>
|
||||
</view>
|
||||
<view class="config-item">
|
||||
<text class="config-label">WebSocket URL:</text>
|
||||
<text class="config-value">{{ configWs }}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="test-section">
|
||||
<button class="test-btn" @click="testConnection" :disabled="isTesting">
|
||||
{{ isTesting ? '测试中...' : '测试连接' }}
|
||||
</button>
|
||||
</view>
|
||||
|
||||
<view class="result-section" v-if="testResult">
|
||||
<text class="section-title">测试结果</text>
|
||||
<view class="result-item" :class="{ success: testResult.success, error: !testResult.success }">
|
||||
<text class="result-icon">{{ testResult.success ? '✅' : '❌' }}</text>
|
||||
<text class="result-text">{{ testResult.message }}</text>
|
||||
</view>
|
||||
|
||||
<view v-if="testResult.details" class="result-details">
|
||||
<text class="details-title">详细信息:</text>
|
||||
<text class="details-text">{{ testResult.details }}</text>
|
||||
</view>
|
||||
|
||||
<view v-if="testResult.data" class="result-data">
|
||||
<text class="data-title">返回数据:</text>
|
||||
<text class="data-text">{{ JSON.stringify(testResult.data, null, 2) }}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="test-list">
|
||||
<text class="section-title">测试项目</text>
|
||||
|
||||
<view class="test-item" v-for="(test, index) in testList" :key="index">
|
||||
<view class="test-info">
|
||||
<text class="test-name">{{ test.name }}</text>
|
||||
<text class="test-status" :class="test.status">{{ getStatusText(test.status) }}</text>
|
||||
</view>
|
||||
<button class="test-item-btn" @click="runTest(test)" :disabled="isTesting">执行</button>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script lang="uts">
|
||||
import supa from '@/components/supadb/aksupainstance.uts'
|
||||
import { SUPA_URL, SUPA_KEY, WS_URL } from '@/ak/config.uts'
|
||||
|
||||
type TestResultType = {
|
||||
success: boolean
|
||||
message: string
|
||||
details?: string
|
||||
data?: any
|
||||
}
|
||||
|
||||
type TestItemType = {
|
||||
name: string
|
||||
status: string
|
||||
func: () => Promise<TestResultType>
|
||||
}
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
configUrl: SUPA_URL,
|
||||
configKey: SUPA_KEY,
|
||||
configWs: WS_URL,
|
||||
isTesting: false,
|
||||
testResult: null as TestResultType | null,
|
||||
testList: [
|
||||
{
|
||||
name: '1. 基础连接测试',
|
||||
status: 'pending',
|
||||
func: this.testBasicConnection
|
||||
} as TestItemType,
|
||||
{
|
||||
name: '2. 查询测试(查询用户表)',
|
||||
status: 'pending',
|
||||
func: this.testQuery
|
||||
} as TestItemType,
|
||||
{
|
||||
name: '3. 认证测试',
|
||||
status: 'pending',
|
||||
func: this.testAuth
|
||||
} as TestItemType,
|
||||
{
|
||||
name: '4. 实时连接测试',
|
||||
status: 'pending',
|
||||
func: this.testRealtime
|
||||
} as TestItemType
|
||||
] as Array<TestItemType>
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// 综合连接测试
|
||||
async testConnection() {
|
||||
this.isTesting = true
|
||||
this.testResult = null
|
||||
|
||||
try {
|
||||
// 测试1: 基础连接
|
||||
const basicResult = await this.testBasicConnection()
|
||||
this.updateTestStatus(0, basicResult.success ? 'success' : 'error')
|
||||
|
||||
if (!basicResult.success) {
|
||||
this.testResult = basicResult
|
||||
this.isTesting = false
|
||||
return
|
||||
}
|
||||
|
||||
// 测试2: 查询测试
|
||||
const queryResult = await this.testQuery()
|
||||
this.updateTestStatus(1, queryResult.success ? 'success' : 'error')
|
||||
|
||||
// 测试3: 认证测试
|
||||
const authResult = await this.testAuth()
|
||||
this.updateTestStatus(2, authResult.success ? 'success' : 'error')
|
||||
|
||||
// 汇总结果
|
||||
const allSuccess = basicResult.success && queryResult.success && authResult.success
|
||||
this.testResult = {
|
||||
success: allSuccess,
|
||||
message: allSuccess
|
||||
? '所有测试通过!Supabase 连接正常。'
|
||||
: '部分测试失败,请查看详细信息。',
|
||||
details: `基础连接: ${basicResult.success ? '✓' : '✗'}, 查询: ${queryResult.success ? '✓' : '✗'}, 认证: ${authResult.success ? '✓' : '✗'}`,
|
||||
data: {
|
||||
basic: basicResult,
|
||||
query: queryResult,
|
||||
auth: authResult
|
||||
}
|
||||
}
|
||||
|
||||
} catch (err) {
|
||||
this.testResult = {
|
||||
success: false,
|
||||
message: '测试过程中发生错误',
|
||||
details: err?.toString() || '未知错误'
|
||||
}
|
||||
} finally {
|
||||
this.isTesting = false
|
||||
}
|
||||
},
|
||||
|
||||
// 测试1: 基础连接
|
||||
async testBasicConnection(): Promise<TestResultType> {
|
||||
try {
|
||||
// 尝试访问 Supabase REST API
|
||||
const response = await uni.request({
|
||||
url: `${SUPA_URL}/rest/v1/`,
|
||||
method: 'GET',
|
||||
header: {
|
||||
'apikey': SUPA_KEY,
|
||||
'Authorization': `Bearer ${SUPA_KEY}`
|
||||
},
|
||||
timeout: 5000
|
||||
})
|
||||
|
||||
if (response.statusCode === 200 || response.statusCode === 404) {
|
||||
// 404 也是正常的,说明服务器响应了
|
||||
return {
|
||||
success: true,
|
||||
message: '基础连接成功',
|
||||
details: `HTTP 状态码: ${response.statusCode}`,
|
||||
data: response.data
|
||||
}
|
||||
} else {
|
||||
return {
|
||||
success: false,
|
||||
message: '连接失败',
|
||||
details: `HTTP 状态码: ${response.statusCode}`
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
return {
|
||||
success: false,
|
||||
message: '无法连接到 Supabase',
|
||||
details: err?.toString() || '网络错误或服务器不可达'
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// 测试2: 查询测试
|
||||
async testQuery(): Promise<TestResultType> {
|
||||
try {
|
||||
// 尝试查询 users 表(如果存在)
|
||||
const { data, error } = await supa
|
||||
.from('users')
|
||||
.select('id, phone, nickname')
|
||||
.limit(5)
|
||||
|
||||
if (error !== null) {
|
||||
// 如果表不存在,尝试查询其他表
|
||||
if (error.message?.includes('relation') || error.message?.includes('does not exist')) {
|
||||
// 尝试查询 orders 表
|
||||
const { data: orderData, error: orderError } = await supa
|
||||
.from('orders')
|
||||
.select('id')
|
||||
.limit(1)
|
||||
|
||||
if (orderError !== null) {
|
||||
return {
|
||||
success: false,
|
||||
message: '查询失败',
|
||||
details: `错误: ${orderError.message || orderError.toString()}`
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: '查询成功(使用 orders 表)',
|
||||
details: 'users 表不存在,但 orders 表可访问',
|
||||
data: orderData
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
success: false,
|
||||
message: '查询失败',
|
||||
details: `错误: ${error.message || error.toString()}`
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: '查询成功',
|
||||
details: `返回 ${data?.length || 0} 条记录`,
|
||||
data: data
|
||||
}
|
||||
} catch (err) {
|
||||
return {
|
||||
success: false,
|
||||
message: '查询测试失败',
|
||||
details: err?.toString() || '未知错误'
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// 测试3: 认证测试
|
||||
async testAuth(): Promise<TestResultType> {
|
||||
try {
|
||||
// 检查是否已登录
|
||||
const { data: sessionData, error: sessionError } = await supa.auth.getSession()
|
||||
|
||||
if (sessionError !== null) {
|
||||
return {
|
||||
success: false,
|
||||
message: '获取会话失败',
|
||||
details: sessionError.message || sessionError.toString()
|
||||
}
|
||||
}
|
||||
|
||||
if (sessionData?.session !== null) {
|
||||
return {
|
||||
success: true,
|
||||
message: '认证成功',
|
||||
details: `用户已登录: ${sessionData.session.user.email || sessionData.session.user.phone || '未知'}`,
|
||||
data: {
|
||||
user: sessionData.session.user,
|
||||
expires_at: sessionData.session.expires_at
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return {
|
||||
success: false,
|
||||
message: '未登录',
|
||||
details: '需要先登录才能测试认证功能'
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
return {
|
||||
success: false,
|
||||
message: '认证测试失败',
|
||||
details: err?.toString() || '未知错误'
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// 测试4: 实时连接测试
|
||||
async testRealtime(): Promise<TestResultType> {
|
||||
try {
|
||||
// WebSocket 连接测试比较复杂,这里只做 URL 验证
|
||||
if (WS_URL.startsWith('ws://') || WS_URL.startsWith('wss://')) {
|
||||
return {
|
||||
success: true,
|
||||
message: 'WebSocket URL 格式正确',
|
||||
details: `URL: ${WS_URL}`
|
||||
}
|
||||
} else {
|
||||
return {
|
||||
success: false,
|
||||
message: 'WebSocket URL 格式错误',
|
||||
details: `URL 应以 ws:// 或 wss:// 开头`
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
return {
|
||||
success: false,
|
||||
message: '实时连接测试失败',
|
||||
details: err?.toString() || '未知错误'
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// 运行单个测试
|
||||
async runTest(test: TestItemType) {
|
||||
this.isTesting = true
|
||||
test.status = 'testing'
|
||||
|
||||
try {
|
||||
const result = await test.func()
|
||||
test.status = result.success ? 'success' : 'error'
|
||||
this.testResult = result
|
||||
} catch (err) {
|
||||
test.status = 'error'
|
||||
this.testResult = {
|
||||
success: false,
|
||||
message: '测试执行失败',
|
||||
details: err?.toString() || '未知错误'
|
||||
}
|
||||
} finally {
|
||||
this.isTesting = false
|
||||
}
|
||||
},
|
||||
|
||||
// 更新测试状态
|
||||
updateTestStatus(index: number, status: string) {
|
||||
if (this.testList[index]) {
|
||||
this.testList[index].status = status
|
||||
}
|
||||
},
|
||||
|
||||
// 获取状态文本
|
||||
getStatusText(status: string): string {
|
||||
const statusMap: Record<string, string> = {
|
||||
'pending': '待测试',
|
||||
'testing': '测试中...',
|
||||
'success': '✓ 通过',
|
||||
'error': '✗ 失败'
|
||||
}
|
||||
return statusMap[status] || '未知'
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.test-container {
|
||||
padding: 40rpx;
|
||||
background-color: #f5f5f5;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.header {
|
||||
margin-bottom: 40rpx;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 36rpx;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.config-section, .test-section, .result-section, .test-list {
|
||||
background-color: #fff;
|
||||
border-radius: 16rpx;
|
||||
padding: 30rpx;
|
||||
margin-bottom: 30rpx;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 28rpx;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.config-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.config-label {
|
||||
font-size: 24rpx;
|
||||
color: #666;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
|
||||
.config-value {
|
||||
font-size: 22rpx;
|
||||
color: #333;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.test-btn {
|
||||
width: 100%;
|
||||
height: 80rpx;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
color: #fff;
|
||||
border-radius: 12rpx;
|
||||
font-size: 28rpx;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.test-btn:disabled {
|
||||
background: #ccc;
|
||||
}
|
||||
|
||||
.result-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 20rpx;
|
||||
border-radius: 12rpx;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.result-item.success {
|
||||
background-color: #e8f5e8;
|
||||
}
|
||||
|
||||
.result-item.error {
|
||||
background-color: #ffebee;
|
||||
}
|
||||
|
||||
.result-icon {
|
||||
font-size: 32rpx;
|
||||
margin-right: 15rpx;
|
||||
}
|
||||
|
||||
.result-text {
|
||||
font-size: 26rpx;
|
||||
color: #333;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.result-details, .result-data {
|
||||
margin-top: 20rpx;
|
||||
padding: 20rpx;
|
||||
background-color: #f8f9fa;
|
||||
border-radius: 8rpx;
|
||||
}
|
||||
|
||||
.details-title, .data-title {
|
||||
font-size: 24rpx;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
margin-bottom: 10rpx;
|
||||
}
|
||||
|
||||
.details-text, .data-text {
|
||||
font-size: 22rpx;
|
||||
color: #666;
|
||||
word-break: break-all;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
.test-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 25rpx 0;
|
||||
border-bottom: 1rpx solid #f0f0f0;
|
||||
}
|
||||
|
||||
.test-item:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.test-info {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.test-name {
|
||||
font-size: 26rpx;
|
||||
color: #333;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
|
||||
.test-status {
|
||||
font-size: 22rpx;
|
||||
}
|
||||
|
||||
.test-status.pending {
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.test-status.testing {
|
||||
color: #2196f3;
|
||||
}
|
||||
|
||||
.test-status.success {
|
||||
color: #4caf50;
|
||||
}
|
||||
|
||||
.test-status.error {
|
||||
color: #f44336;
|
||||
}
|
||||
|
||||
.test-item-btn {
|
||||
padding: 12rpx 24rpx;
|
||||
background-color: #667eea;
|
||||
color: #fff;
|
||||
border-radius: 8rpx;
|
||||
font-size: 24rpx;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.test-item-btn:disabled {
|
||||
background-color: #ccc;
|
||||
}
|
||||
</style>
|
||||
@@ -169,34 +169,44 @@ const loadBanners = async () => {
|
||||
|
||||
if (error !== null) {
|
||||
console.error('加载轮播图失败:', error)
|
||||
bannerList.value = []
|
||||
return
|
||||
}
|
||||
|
||||
bannerList.value = data ?? []
|
||||
} catch (err) {
|
||||
console.error('加载轮播图异常:', err)
|
||||
bannerList.value = []
|
||||
}
|
||||
}
|
||||
|
||||
// 加载分类
|
||||
const loadCategories = async () => {
|
||||
try {
|
||||
// 查询所有活跃分类,然后在前端过滤出顶级分类(parent_id 为 null)
|
||||
// 这样可以避免 UTS 中 .is(null) 的类型问题
|
||||
const { data, error } = await supa
|
||||
.from('categories')
|
||||
.select('*')
|
||||
.eq('is_active', true)
|
||||
.is('parent_id', null)
|
||||
.order('sort_order', { ascending: true })
|
||||
.limit(8)
|
||||
|
||||
if (error !== null) {
|
||||
console.error('加载分类失败:', error)
|
||||
categoryList.value = []
|
||||
return
|
||||
}
|
||||
|
||||
categoryList.value = data ?? []
|
||||
// 过滤出顶级分类(parent_id 为 null 或 undefined)
|
||||
const topCategories = (data ?? []).filter((item: any) => {
|
||||
return item.parent_id === null || item.parent_id === undefined || item.parent_id === ''
|
||||
}).slice(0, 8) // 限制为前8个
|
||||
|
||||
categoryList.value = topCategories
|
||||
} catch (err) {
|
||||
console.error('加载分类异常:', err)
|
||||
// 如果查询失败,使用空数组避免页面崩溃
|
||||
categoryList.value = []
|
||||
}
|
||||
}
|
||||
|
||||
@@ -204,22 +214,25 @@ const loadCategories = async () => {
|
||||
const loadCoupons = async () => {
|
||||
try {
|
||||
const now = new Date().toISOString()
|
||||
// 查询当前时间在有效期内的优惠券:start_time <= now <= end_time
|
||||
const { data, error } = await supa
|
||||
.from('coupon_templates')
|
||||
.select('*')
|
||||
.eq('status', 1)
|
||||
.gte('end_time', now)
|
||||
.lte('start_time', now)
|
||||
.lte('start_time', now) // 开始时间 <= 当前时间
|
||||
.gte('end_time', now) // 结束时间 >= 当前时间
|
||||
.limit(5)
|
||||
|
||||
if (error !== null) {
|
||||
console.error('加载优惠券失败:', error)
|
||||
couponList.value = []
|
||||
return
|
||||
}
|
||||
|
||||
couponList.value = data ?? []
|
||||
} catch (err) {
|
||||
console.error('加载优惠券异常:', err)
|
||||
couponList.value = []
|
||||
}
|
||||
}
|
||||
|
||||
@@ -242,6 +255,9 @@ const loadProducts = async (loadMore: boolean = false) => {
|
||||
|
||||
if (error !== null) {
|
||||
console.error('加载商品失败:', error)
|
||||
if (!loadMore) {
|
||||
productList.value = []
|
||||
}
|
||||
isLoading.value = false
|
||||
return
|
||||
}
|
||||
@@ -261,6 +277,9 @@ const loadProducts = async (loadMore: boolean = false) => {
|
||||
hasMore.value = newProducts.length === pageSize.value
|
||||
} catch (err) {
|
||||
console.error('加载商品异常:', err)
|
||||
if (!loadMore) {
|
||||
productList.value = []
|
||||
}
|
||||
} finally {
|
||||
isLoading.value = false
|
||||
}
|
||||
|
||||
@@ -83,13 +83,6 @@
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/mall/analytics/index",
|
||||
"style": {
|
||||
"navigationBarTitleText": "数据分析",
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/mall/consumer/product-detail",
|
||||
"style": {
|
||||
@@ -461,6 +454,13 @@
|
||||
{
|
||||
"root": "pages/mall/analytics",
|
||||
"pages": [
|
||||
{
|
||||
"path": "index",
|
||||
"style": {
|
||||
"navigationBarTitleText": "数据分析中心",
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "sales-report",
|
||||
"style": {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user