530 lines
12 KiB
Plaintext
530 lines
12 KiB
Plaintext
<!-- 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>
|