登录注册/数据分析

This commit is contained in:
comlibmb
2026-01-22 21:15:02 +08:00
parent 75fad97d5d
commit fdbee0fa32
41 changed files with 5812 additions and 2102 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -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}`
})
}

View File

@@ -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: {

View 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. 使用循环生成大量测试数据(注意性能)

View 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. 完成!
现在可以开始测试实时大屏功能了!🎉

View 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>

View File

@@ -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
}

View File

@@ -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