添加逻辑并测试连接数据库

This commit is contained in:
not-like-juvenile
2026-01-28 19:13:13 +08:00
parent 1cca27096a
commit 76eb96ec0c
8 changed files with 1795 additions and 23 deletions

View File

@@ -1,12 +1,12 @@
// Supabase 配置
// 内网环境 - 本地部署的 Supabase
// IP: 192.168.1.63
// IP: 192.168.1.62
// Kong HTTP Port: 8000
export const SUPA_URL: string = 'http://192.168.1.63:8000'
export const SUPA_URL: string = 'http://192.168.1.62: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.62:8000/realtime/v1/websocket'
// 备用配置(已注释,如需切换可取消注释)
// 开发环境 - 其他内网地址

View File

@@ -324,6 +324,34 @@
"navigationStyle": "custom"
}
},
{
"path": "help-center",
"style": {
"navigationBarTitleText": "帮助中心",
"navigationStyle": "custom"
}
},
{
"path": "about",
"style": {
"navigationBarTitleText": "关于我们",
"navigationStyle": "custom"
}
},
{
"path": "feedback",
"style": {
"navigationBarTitleText": "意见反馈",
"navigationStyle": "custom"
}
},
{
"path": "test",
"style": {
"navigationBarTitleText": "test",
"navigationStyle": "custom"
}
},
{
"path": "settings",
"style": {

View File

@@ -0,0 +1,293 @@
<template>
<view class="about-container">
<!-- 顶部导航栏 -->
<view class="header-bar">
<!-- 左侧:返回按钮 -->
<view class="nav-left" @click="goBack">
<text class="nav-icon">←</text>
</view>
<!-- 中部:页面标题 -->
<text class="page-title">关于我们</text>
<!-- 右侧:留空 -->
<view class="nav-right"></view>
</view>
<!-- 主要内容 -->
<view class="content-wrapper">
<!-- Logo 区域 -->
<view class="logo-section">
<image src="/static/logo.png" class="app-logo" mode="aspectFit" />
</view>
<!-- 版本信息 -->
<view class="version-info">
<text class="version-text">版本: v2.1.5</text>
</view>
<!-- 应用介绍 -->
<view class="app-intro">
<text class="intro-title">快递配送助手</text>
<text class="intro-desc">专为配送员打造的一站式智能配送平台,提升效率,优化服务体验</text>
</view>
<!-- 功能亮点 -->
<view class="feature-section">
<text class="section-header">核心功能</text>
<view class="features">
<view class="feature-item">
<text class="feature-icon">📍</text>
<text class="feature-text">智能路线规划</text>
</view>
<view class="feature-item">
<text class="feature-icon">📱</text>
<text class="feature-text">实时订单管理</text>
</view>
<view class="feature-item">
<text class="feature-icon">💰</text>
<text class="feature-text">在线结算系统</text>
</view>
<view class="feature-item">
<text class="feature-icon">⭐</text>
<text class="feature-text">客户评价反馈</text>
</view>
</view>
</view>
<!-- 公司信息 -->
<view class="company-info">
<text class="section-header">公司信息</text>
<text class="info-item">公司名称: 快递科技有限公司</text>
<text class="info-item">成立时间: 2020年</text>
<text class="info-item">总部地点: 深圳市南山区科技园</text>
<text class="info-item">联系电话: 400-123-4567</text>
<text class="info-item">邮箱: support@delivery.com</text>
</view>
<!-- 更新日志 -->
<view class="update-log">
<text class="section-header">近期更新</text>
<view class="log-item">
<text class="log-version">v2.1.5 (2026-01-27)</text>
<text class="log-desc">• 修复了地图定位精度问题\n• 优化了任务分配算法\n• 提升了语音播报稳定性</text>
</view>
<view class="log-item">
<text class="log-version">v2.1.4 (2026-01-15)</text>
<text class="log-desc">• 新增智能语音助手功能\n• 改进了路线导航算法\n• 修复了部分UI显示异常</text>
</view>
<view class="log-item">
<text class="log-version">v2.1.3 (2025-12-28)</text>
<text class="log-desc">• 新增客户满意度评价\n• 优化了离线地图功能\n• 增强了数据同步机制</text>
</view>
</view>
<!-- 版权信息 -->
<view class="copyright">
<text class="copyright-text">© 2020-2026 快递科技有限公司 版权所有</text>
</view>
</view>
</view>
</template>
<script lang="uts">
export default {
methods: {
// 返回上一页
goBack() {
uni.navigateBack()
}
}
}
</script>
<style scoped>
.about-container {
background-color: #f8f9fa;
min-height: 100vh;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
}
.header-bar {
background-color: #fff;
padding: 20rpx 30rpx;
display: flex;
justify-content: space-between;
align-items: center;
border-bottom: 1rpx solid #e9ecef;
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.05);
}
.nav-left {
display: flex;
align-items: center;
cursor: pointer;
padding: 10rpx;
border-radius: 8rpx;
transition: background-color 0.2s ease;
}
.nav-left:hover {
background-color: #f0f0f0;
}
.nav-left:active {
background-color: #e0e0e0;
}
.nav-icon {
font-size: 36rpx;
color: #333;
}
.page-title {
font-size: 32rpx;
font-weight: bold;
color: #333;
text-align: center;
flex: 1; /* 让标题占据中间剩余空间 */
}
.nav-right {
width: 60rpx; /* 与左侧箭头区域宽度一致 */
height: 1rpx;
}
.content-wrapper {
flex: 1;
display: flex;
flex-direction: column;
padding: 40rpx 30rpx;
gap: 40rpx;
}
.logo-section {
display: flex;
justify-content: center;
margin-top: 20rpx;
}
.app-logo {
width: 150rpx;
height: 150rpx;
border-radius: 20rpx;
background-color: #e3f2fd;
padding: 20rpx;
}
.version-info {
text-align: center;
}
.version-text {
font-size: 28rpx;
color: #666;
font-weight: 500;
background-color: #e8f4fd;
padding: 10rpx 20rpx;
border-radius: 15rpx;
}
.app-intro {
text-align: center;
padding: 30rpx 0;
}
.intro-title {
font-size: 36rpx;
font-weight: bold;
color: #333;
margin-bottom: 15rpx;
display: block;
}
.intro-desc {
font-size: 26rpx;
color: #666;
line-height: 1.6;
}
.feature-section, .company-info, .update-log {
background-color: #fff;
border-radius: 16rpx;
padding: 30rpx;
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.05);
}
.section-header {
font-size: 30rpx;
font-weight: bold;
color: #333;
margin-bottom: 20rpx;
display: block;
}
.features {
display: flex;
flex-direction: column;
gap: 20rpx;
}
.feature-item {
display: flex;
align-items: center;
}
.feature-icon {
font-size: 32rpx;
margin-right: 15rpx;
width: 40rpx;
}
.feature-text {
font-size: 26rpx;
color: #555;
}
.company-info .info-item {
font-size: 26rpx;
color: #666;
margin-bottom: 15rpx;
line-height: 1.5;
}
.update-log {
display: flex;
flex-direction: column;
gap: 25rpx;
}
.log-item {
border-bottom: 1rpx solid #f0f0f0;
padding-bottom: 20rpx;
}
.log-item:last-child {
border-bottom: none;
}
.log-version {
font-size: 26rpx;
font-weight: bold;
color: #333;
display: block;
margin-bottom: 10rpx;
}
.log-desc {
font-size: 24rpx;
color: #666;
line-height: 1.5;
white-space: pre-line;
}
.copyright {
text-align: center;
padding: 30rpx 0;
}
.copyright-text {
font-size: 22rpx;
color: #999;
}
</style>

View File

@@ -1,7 +1,3 @@
✅ 明白了!你不需要完整代码,而是希望我**用简洁的 Markdown 文档形式,解释这个 `delivery` 文件夹下的项目结构和每个页面的功能定位**。
---
# 🚚 配送端模块项目结构说明(`/pages/mall/delivery/`
> 本模块为配送员使用的移动端应用核心功能区,涵盖个人中心、任务管理、收入统计、车辆管理等核心功能。
@@ -25,7 +21,6 @@ delivery/
├── vehicle.uvue # 车辆管理主页
├── vehicle-add.uvue # 添加车辆
├── vehicle-edit.uvue # 编辑车辆信息
└── index.uvue # 首页(与上面重复?应统一命名)
```
> 💡 注:`index.uvue` 出现两次,建议保留一个作为首页入口,另一个可重命名为 `dashboard.uvue` 或 `home.uvue`
@@ -171,7 +166,6 @@ index.uvue
| 问题 | 建议 |
|------|------|
| `index.uvue` 重复 | 重命名为 `dashboard.uvue``home.uvue` |
| 页面间参数传递 | 使用 `uni.navigateTo({ url: '?param=value' })` 并在 `onLoad` 中接收 |
| UTS 语法兼容性 | 如遇问题,可临时改用标准 TS 语法调试 |

View File

@@ -0,0 +1,451 @@
<template>
<view class="feedback-container">
<!-- 顶部导航栏 -->
<view class="header-bar">
<view class="nav-left" @click="goBack">
<text class="nav-icon">←</text>
</view>
<text class="page-title">意见反馈</text>
<view class="nav-right"></view>
</view>
<!-- 表单区域 -->
<view class="form-wrapper">
<!-- 问题类型 -->
<view class="form-item">
<text class="label">问题类型</text>
<picker mode="selector" :range="feedbackTypes" @change="onTypeChange" :value="selectedTypeIndex">
<view class="picker-input">{{ feedbackTypes[selectedTypeIndex] }}</view>
</picker>
</view>
<!-- 标题 -->
<view class="form-item">
<text class="label">标题</text>
<input
type="text"
placeholder="请简要描述问题"
v-model="title"
class="input-field"
maxlength="50"
/>
</view>
<!-- 内容 -->
<view class="form-item">
<text class="label">详细内容</text>
<textarea
placeholder="请描述具体问题、发生时间、订单号等信息至少10字"
v-model="content"
class="textarea-field"
maxlength="500"
auto-height
/>
</view>
<!-- 上传截图(可选) -->
<view class="form-item">
<text class="label">上传截图(可选)</text>
<view class="upload-area" @click="chooseImage">
<text class="upload-icon">🖼️</text>
<text class="upload-text">点击上传截图</text>
<text class="upload-tip">支持 JPG/PNG最多3张</text>
</view>
<!-- 已上传图片预览 -->
<view class="preview-list" v-if="previewImages.length > 0">
<view class="preview-item" v-for="(img, index) in previewImages" :key="index">
<image :src="img" class="preview-img" mode="aspectFill" />
<text class="remove-btn" @click="removeImage(index)">×</text>
</view>
</view>
</view>
<!-- 关联订单号(下拉选择) -->
<view class="form-item">
<text class="label">关联订单号(可选)</text>
<picker mode="selector" :range="orderOptions" @change="onOrderChange" :value="selectedOrderIndex">
<view class="picker-input">
{{ selectedOrderIndex === -1 ? '请选择订单' : orderOptions[selectedOrderIndex] }}
</view>
</picker>
</view>
<!-- 提交按钮 -->
<view class="submit-section">
<button
class="submit-btn"
:disabled="!isValid || isSubmitting"
@click="submitFeedback"
>
{{ isSubmitting ? '提交中...' : '提交反馈' }}
</button>
</view>
</view>
<!-- 底部提示 -->
<view class="footer-tip">
<text>您的反馈将被优先处理我们将在24小时内回复</text>
</view>
</view>
</template>
<script lang="uts">
export default {
data() {
return {
// 表单数据
title: '',
content: '',
selectedTypeIndex: 0,
selectedOrderIndex: -1, // -1 表示未选择
previewImages: [] as string[],
// 配置
feedbackTypes: [
'订单问题',
'配送异常',
'系统故障',
'账号/认证问题',
'车辆/设备问题',
'收入结算问题',
'其他'
],
// 模拟历史订单(实际项目中应从接口获取)
orders: [
{ id: 'ORD20250122001', status: '已完成', created_at: '2025-01-22 14:30' },
{ id: 'ORD20250122002', status: '配送中', created_at: '2025-01-22 13:15' },
{ id: 'ORD20250122003', status: '已取消', created_at: '2025-01-22 10:45' },
{ id: 'ORD20250122004', status: '待接单', created_at: '2025-01-22 09:20' }
],
isSubmitting: false
}
},
computed: {
// 订单选项:格式为 "ORDxxx - 状态 - 时间"
orderOptions() {
if (this.orders.length === 0) return ['无可用订单']
return this.orders.map(order =>
`${order.id} - ${order.status} - ${order.created_at.split(' ')[0]}`
)
},
isValid() {
return this.title.trim().length >= 2 &&
this.content.trim().length >= 10 &&
this.selectedTypeIndex >= 0
}
},
methods: {
goBack() {
uni.navigateBack()
},
onTypeChange(e: UniEvent<HTMLInputElement>) {
this.selectedTypeIndex = e.detail.value as number
},
onOrderChange(e: UniEvent<HTMLInputElement>) {
const index = e.detail.value as number
this.selectedOrderIndex = index
// 如果选择了有效订单,自动填充 orderId 字段(可选)
if (index >= 0 && index < this.orders.length) {
// 实际业务中可绑定 order.id 到某个字段,此处仅作示意
} else {
// 未选择时清空
}
},
chooseImage() {
if (this.previewImages.length >= 3) {
uni.showToast({
title: '最多上传3张图片',
icon: 'none'
})
return
}
uni.chooseImage({
count: 3 - this.previewImages.length,
sizeType: ['compressed'],
sourceType: ['album', 'camera'],
success: (res) => {
const tempFilePaths = res.tempFilePaths
const newImages = [...this.previewImages, ...tempFilePaths]
if (newImages.length > 3) {
this.previewImages = newImages.slice(0, 3)
} else {
this.previewImages = newImages
}
}
})
},
removeImage(index: number) {
this.previewImages.splice(index, 1)
},
submitFeedback() {
if (!this.isValid) {
let errorMsg = ''
if (this.title.trim().length < 2) {
errorMsg = '请填写标题至少2字'
} else if (this.content.trim().length < 10) {
errorMsg = '请填写详细内容至少10字'
} else if (this.selectedTypeIndex < 0) {
errorMsg = '请选择问题类型'
}
uni.showToast({
title: errorMsg,
icon: 'none'
})
return
}
if (this.isSubmitting) return
this.isSubmitting = true
// 模拟提交
uni.showLoading({ title: '提交中...' })
setTimeout(() => {
uni.hideLoading()
uni.showToast({
title: '反馈已提交成功!',
icon: 'success'
})
// 1.5秒后返回上一页
setTimeout(() => {
uni.navigateBack()
}, 1500)
}, 1000)
}
}
}
</script>
<style scoped>
.feedback-container {
background-color: #f8f9fa;
min-height: 100vh;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
}
/* 顶部导航 */
.header-bar {
background-color: #fff;
padding: 20rpx 30rpx;
display: flex;
justify-content: space-between;
align-items: center;
border-bottom: 1rpx solid #e9ecef;
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.05);
}
.nav-left {
display: flex;
align-items: center;
cursor: pointer;
padding: 10rpx;
border-radius: 8rpx;
transition: background-color 0.2s ease;
}
.nav-left:hover {
background-color: #f0f0f0;
}
.nav-left:active {
background-color: #e0e0e0;
}
.nav-icon {
font-size: 36rpx;
color: #333;
}
.page-title {
font-size: 32rpx;
font-weight: bold;
color: #333;
text-align: center;
flex: 1;
}
.nav-right {
width: 60rpx;
height: 1rpx;
}
/* 表单区域 */
.form-wrapper {
padding: 30rpx;
background-color: #fff;
}
.form-item {
margin-bottom: 30rpx;
}
.label {
display: block;
font-size: 28rpx;
color: #333;
margin-bottom: 12rpx;
font-weight: 500;
}
/* 输入框统一样式 */
.input-field,
.textarea-field,
.picker-input {
width: 100%;
padding: 20rpx 24rpx;
border: 1rpx solid #e0e0e0;
border-radius: 12rpx;
font-size: 28rpx;
color: #333;
background-color: #fafafa;
box-sizing: border-box;
}
.textarea-field {
min-height: 180rpx;
height: auto;
padding: 24rpx;
line-height: 1.6;
}
.picker-input {
background-color: #fff;
text-align: right;
color: #666;
padding-right: 40rpx;
position: relative;
}
.picker-input::after {
content: '▼';
position: absolute;
right: 24rpx;
top: 50%;
transform: translateY(-50%);
font-size: 24rpx;
color: #999;
}
/* 上传区域 */
.upload-area {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 40rpx 20rpx;
border: 2rpx dashed #e0e0e0;
border-radius: 16rpx;
background-color: #f8f9fa;
cursor: pointer;
transition: all 0.3s;
}
.upload-area:hover {
border-color: #74b9ff;
background-color: #e8f4fd;
}
.upload-icon {
font-size: 60rpx;
color: #999;
margin-bottom: 16rpx;
}
.upload-text {
font-size: 28rpx;
color: #666;
margin-bottom: 10rpx;
}
.upload-tip {
font-size: 24rpx;
color: #999;
}
/* 图片预览 */
.preview-list {
display: flex;
gap: 20rpx;
margin-top: 20rpx;
flex-wrap: wrap;
}
.preview-item {
position: relative;
width: 120rpx;
height: 120rpx;
border-radius: 12rpx;
overflow: hidden;
}
.preview-img {
width: 100%;
height: 100%;
}
.remove-btn {
position: absolute;
top: 8rpx;
right: 8rpx;
width: 32rpx;
height: 32rpx;
background-color: rgba(0, 0, 0, 0.7);
color: white;
font-size: 24rpx;
display: flex;
align-items: center;
justify-content: center;
border-radius: 50%;
z-index: 1;
}
/* 提交按钮 */
.submit-section {
margin-top: 40rpx;
}
.submit-btn {
width: 100%;
height: 88rpx;
background: linear-gradient(135deg, #74b9ff 0%, #0984e3 100%);
color: white;
font-size: 32rpx;
font-weight: bold;
border-radius: 44rpx;
border: none;
box-shadow: 0 8rpx 20rpx rgba(116, 185, 255, 0.3);
transition: all 0.3s;
}
.submit-btn:disabled {
background: #cccccc; /* 禁用时灰色背景 */
color: #999;
cursor: not-allowed; /* 禁用时显示禁止光标 */
}
.submit-btn:active {
transform: scale(0.98);
box-shadow: 0 4rpx 12rpx rgba(116, 185, 255, 0.2);
}
/* 底部提示 */
.footer-tip {
text-align: center;
padding: 30rpx;
color: #999;
font-size: 24rpx;
background-color: #fff;
border-top: 1rpx solid #e9ecef;
}
</style>

View File

@@ -0,0 +1,524 @@
<template>
<view class="help-center-container">
<!-- 顶部导航栏 -->
<view class="header-bar">
<view class="nav-left" @click="goBack">
<text class="nav-icon">←</text>
</view>
<text class="page-title">帮助中心</text>
<view class="nav-right"></view>
</view>
<!-- 搜索框 -->
<view class="search-bar">
<view class="search-input-wrapper">
<text class="search-icon">🔍</text>
<input
type="text"
placeholder="搜索常见问题..."
v-model="searchQuery"
@input="onSearch"
class="search-input"
/>
</view>
</view>
<!-- 分类导航 -->
<view class="category-tabs">
<scroll-view scroll-x="true" class="tabs-scroll">
<view
v-for="cat in categories"
:key="cat.id"
class="tab-item"
:class="{ active: activeCategory === cat.id }"
@click="switchCategory(cat.id)"
>
{{ cat.name }}
</view>
</scroll-view>
</view>
<!-- 内容区域 -->
<view class="content-wrapper">
<!-- 筛选标签(仅在搜索时显示) -->
<view v-if="searchQuery" class="filter-tags">
<text class="tag">搜索结果:{{ searchResults.length }} 条</text>
<text class="clear-btn" @click="clearSearch">清除</text>
</view>
<!-- 常见问题列表 -->
<view v-if="currentQuestions.length > 0">
<view v-for="item in currentQuestions" :key="item.id" class="question-item">
<view class="question-header" @click="toggleExpand(item)">
<text class="question-text">{{ item.title }}</text>
<text class="expand-icon">{{ item.expanded ? '▲' : '▼' }}</text>
</view>
<view v-if="item.expanded" class="answer-content">
<text class="answer-text">{{ item.content }}</text>
</view>
</view>
</view>
<!-- 无结果提示 -->
<view v-else class="no-result">
<text class="no-result-icon">❓</text>
<text class="no-result-title">暂无匹配结果</text>
<text class="no-result-desc">请尝试更换关键词,或查看以下热门问题</text>
<view class="hot-questions">
<text class="hot-tag">热门问题:</text>
<text class="hot-item" @click="jumpToQuestion('q1')">如何接单?</text>
<text class="hot-item" @click="jumpToQuestion('q2')">配送超时怎么办?</text>
<text class="hot-item" @click="jumpToQuestion('q3')">如何联系客户?</text>
</view>
</view>
</view>
<!-- 底部联系客服 -->
<view class="contact-footer">
<view class="contact-card">
<text class="contact-title">需要人工帮助?</text>
<text class="contact-desc">7×24小时在线客服为您解决配送中遇到的任何问题</text>
<button class="contact-btn" @click="contactCustomerService">联系客服</button>
</view>
</view>
</view>
</template>
<script lang="uts">
export default {
data() {
return {
searchQuery: '',
activeCategory: 'all',
categories: [
{ id: 'all', name: '全部' },
{ id: 'order', name: '订单相关' },
{ id: 'delivery', name: '配送操作' },
{ id: 'account', name: '账号与认证' },
{ id: 'payment', name: '收入与结算' },
{ id: 'vehicle', name: '车辆管理' }
],
questions: [
{
id: 'q1',
title: '如何接单?',
content: '1. 进入【任务列表】页面\n2. 查看“待接单”任务\n3. 点击任务卡片 → 点击【接受订单】按钮\n4. 系统将自动分配该订单给您,状态变为“已接单”\n⚠ 注意同一时间只能接1个订单接单后需在15分钟内取货。',
category: ['order', 'delivery'],
expanded: false
},
{
id: 'q2',
title: '配送超时怎么办?',
content: '若因交通堵塞、客户联系不上等不可抗力导致超时,请在送达后点击【异常上报】→ 选择原因并上传凭证如截图、通话记录系统将审核后减免处罚。建议提前10分钟联系客户确认收货时间。',
category: ['delivery'],
expanded: false
},
{
id: 'q3',
title: '如何联系客户?',
content: '在【任务详情】页点击【联系客户】→ 选择【拨打电话】或【发送短信】。系统会隐藏真实号码,保护双方隐私。首次联系建议使用语音电话,确保沟通效率。',
category: ['delivery', 'order'],
expanded: false
},
{
id: 'q4',
title: '如何修改个人信息?',
content: '进入【个人中心】→ 点击头像 → 【编辑资料】→ 修改姓名/电话/身份证号等信息 → 提交后等待审核通常2小时内完成。注意身份证信息需上传清晰照片。',
category: ['account'],
expanded: false
},
{
id: 'q5',
title: '配送费如何计算?',
content: '基础配送费 = 距离费¥1.5/km + 时间费¥0.5/分钟) + 服务费¥2~5根据时段浮动\n例如3km + 15分钟 = 3×1.5 + 15×0.5 + 3 = ¥15\n实际金额以订单详情页为准。',
category: ['payment'],
expanded: false
},
{
id: 'q6',
title: '车辆信息如何绑定?',
content: '【个人中心】→ 【车辆管理】→ 【添加车辆】→ 输入车牌号、车型、行驶证照片 → 提交审核。审核通过后,系统将自动关联您的配送任务。未绑定车辆无法接单。',
category: ['vehicle'],
expanded: false
},
{
id: 'q7',
title: '如何查看收入明细?',
content: '【个人中心】→ 【收入明细】→ 可按日/周/月筛选 → 查看每笔订单的配送费、奖励、扣款及到账时间。支持导出Excel报表需企业版权限。',
category: ['payment'],
expanded: false
},
{
id: 'q8',
title: '任务被取消了怎么办?',
content: '若客户取消订单,您将收到系统通知。已完成取货的订单,可申请补偿(路径:任务详情 → 【申请补偿】未取货的订单无补偿。每月最多3次无责取消不计入考核。',
category: ['order'],
expanded: false
},
{
id: 'q9',
title: '导航功能不准确?',
content: '请检查:① 手机GPS权限是否开启② 是否使用最新版APP③ 在【设置】→ 【地图偏好】中切换高德/百度地图。仍无效请反馈至客服,并提供截图+坐标。',
category: ['delivery'],
expanded: false
},
{
id: 'q10',
title: '如何提升评分?',
content: '评分由客户评价70%+ 系统考核30%)组成。建议:① 准时送达(≤预计时间);② 礼貌沟通;③ 主动拍照上传(取货/送达);④ 避免频繁拒单。当前平均分4.8/5.0',
category: ['delivery', 'account'],
expanded: false
}
]
}
},
computed: {
currentQuestions() {
if (this.searchQuery) {
return this.searchResults
}
return this.questions.filter(q =>
this.activeCategory === 'all' || q.category.includes(this.activeCategory)
)
},
searchResults() {
const query = this.searchQuery.toLowerCase()
return this.questions.filter(q =>
q.title.toLowerCase().includes(query) ||
q.content.toLowerCase().includes(query)
)
}
},
methods: {
goBack() {
uni.navigateBack()
},
onSearch() {
// 实时搜索(可加防抖)
if (!this.searchQuery) {
this.activeCategory = 'all'
}
},
clearSearch() {
this.searchQuery = ''
this.activeCategory = 'all'
},
switchCategory(categoryId) {
this.activeCategory = categoryId
this.searchQuery = ''
},
toggleExpand(item) {
item.expanded = !item.expanded
// 关闭其他展开项(可选)
// this.questions.forEach(q => { if (q !== item) q.expanded = false })
},
jumpToQuestion(id) {
const item = this.questions.find(q => q.id === id)
if (item) {
item.expanded = true
this.activeCategory = 'all'
this.searchQuery = ''
// 滚动到该问题(简化版)
uni.pageScrollTo({ scrollTop: 300, duration: 300 })
}
},
contactCustomerService() {
uni.showActionSheet({
itemList: ['在线客服', '电话咨询', '提交工单'],
success: (res) => {
if (res.tapIndex === 0) {
uni.navigateTo({ url: '/pages/mall/common/customer-service' })
} else if (res.tapIndex === 1) {
uni.makePhoneCall({ phoneNumber: '400-123-4567' })
} else {
uni.navigateTo({ url: '/pages/mall/common/feedback?from=help' })
}
}
})
}
}
}
</script>
<style scoped>
.help-center-container {
background-color: #f8f9fa;
min-height: 100vh;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
}
/* 顶部导航 */
.header-bar {
background-color: #fff;
padding: 20rpx 30rpx;
display: flex;
justify-content: space-between;
align-items: center;
border-bottom: 1rpx solid #e9ecef;
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.05);
}
.nav-left {
display: flex;
align-items: center;
cursor: pointer;
padding: 10rpx;
border-radius: 8rpx;
transition: background-color 0.2s ease;
}
.nav-left:hover {
background-color: #f0f0f0;
}
.nav-left:active {
background-color: #e0e0e0;
}
.nav-icon {
font-size: 36rpx;
color: #333;
}
.page-title {
font-size: 32rpx;
font-weight: bold;
color: #333;
text-align: center;
flex: 1;
}
.nav-right {
width: 60rpx;
height: 1rpx;
}
/* 搜索框 —— 修复放大镜与输入框垂直对齐 */
.search-bar {
padding: 20rpx 30rpx;
background-color: #fff;
}
.search-input-wrapper {
display: flex;
align-items: center; /* ✅ 垂直居中核心 */
background-color: #f5f5f5;
border-radius: 40rpx;
padding: 12rpx 20rpx;
}
.search-icon {
font-size: 28rpx;
color: #999;
margin-right: 12rpx;
line-height: 1;
display: inline-flex;
align-items: center;
height: 48rpx; /* 与 input 高度一致 */
}
.search-input {
flex: 1;
font-size: 26rpx;
background: transparent;
outline: none;
color: #333;
height: 48rpx;
padding: 0;
}
/* 分类导航 */
.category-tabs {
background-color: #fff;
padding: 0 30rpx 20rpx;
}
.tabs-scroll {
white-space: nowrap;
overflow-x: auto;
-webkit-overflow-scrolling: touch;
}
.tab-item {
display: inline-block;
padding: 12rpx 24rpx;
margin-right: 16rpx;
background-color: #f8f9fa;
border-radius: 30rpx;
font-size: 26rpx;
color: #666;
transition: all 0.3s;
}
.tab-item.active {
background-color: #74b9ff;
color: white;
font-weight: 500;
}
/* 内容区域 */
.content-wrapper {
padding: 0 30rpx;
flex: 1;
}
.filter-tags {
display: flex;
justify-content: space-between;
align-items: center;
padding: 15rpx 0;
background-color: #e8f4fd;
border-radius: 12rpx;
margin-bottom: 20rpx;
}
.tag {
font-size: 24rpx;
color: #333;
}
.clear-btn {
font-size: 24rpx;
color: #74b9ff;
padding: 4rpx 12rpx;
border-radius: 8rpx;
}
.question-item {
background-color: #fff;
border-radius: 16rpx;
overflow: hidden;
margin-bottom: 20rpx;
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.03);
}
.question-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 24rpx 20rpx;
cursor: pointer;
border-bottom: 1rpx solid #f0f0f0;
}
.question-text {
font-size: 28rpx;
color: #333;
font-weight: 500;
flex: 1;
}
.expand-icon {
font-size: 32rpx;
color: #999;
width: 36rpx;
text-align: center;
}
.answer-content {
padding: 0 20rpx 20rpx;
background-color: #f8f9fa;
}
.answer-text {
font-size: 26rpx;
color: #666;
line-height: 1.6;
white-space: pre-line;
}
/* 无结果提示 */
.no-result {
text-align: center;
padding: 80rpx 30rpx;
background-color: #fff;
border-radius: 16rpx;
margin-top: 20rpx;
}
.no-result-icon {
font-size: 80rpx;
color: #e0e0e0;
margin-bottom: 20rpx;
}
.no-result-title {
font-size: 32rpx;
color: #333;
font-weight: bold;
margin-bottom: 15rpx;
}
.no-result-desc {
font-size: 26rpx;
color: #666;
margin-bottom: 25rpx;
}
.hot-questions {
display: flex;
flex-wrap: wrap;
justify-content: center;
gap: 12rpx;
}
.hot-tag {
font-size: 24rpx;
color: #999;
margin-right: 12rpx;
}
.hot-item {
font-size: 24rpx;
color: #74b9ff;
padding: 4rpx 12rpx;
border-radius: 8rpx;
background-color: #e8f4fd;
}
/* 底部联系客服 */
.contact-footer {
padding: 30rpx;
background-color: #fff;
border-top: 1rpx solid #e9ecef;
}
.contact-card {
background: linear-gradient(135deg, #74b9ff 0%, #0984e3 100%);
border-radius: 20rpx;
padding: 30rpx;
color: white;
text-align: center;
}
.contact-title {
font-size: 32rpx;
font-weight: bold;
margin-bottom: 12rpx;
}
.contact-desc {
font-size: 26rpx;
opacity: 0.9;
line-height: 1.5;
margin-bottom: 20rpx;
}
.contact-btn {
width: 100%;
height: 80rpx;
background-color: white;
color: #74b9ff;
font-size: 30rpx;
font-weight: bold;
border-radius: 40rpx;
border: none;
box-shadow: 0 6rpx 15rpx rgba(0, 0, 0, 0.1);
}
</style>

View File

@@ -4,8 +4,8 @@
<!-- 1. 蓝色头像条profile-header -->
<view class="profile-header">
<!-- 返回按钮:最左边垂直居中 -->
<view class="back-box" @click="backToIndex">
<text class="back-icon"></text>
<view class="nav-left" @click="goBack">
<text class="nav-icon"></text>
</view>
<image :src="driverInfo.avatar_url || '/static/default-avatar.png'" class="driver-avatar" @click="editProfile" />
@@ -30,8 +30,12 @@
<view class="toggle-handle"></view>
</view>
</view>
<view v-if="workStatus === 1" class="current-location">
<!-- 修改点:将定位信息和重新定位按钮合并到一个区域 -->
<view v-if="workStatus === 1" class="current-location-section">
<text class="location-text">📍 {{ currentLocation }}</text>
<view class="relocate-icon" @click.stop="showRelocateConfirm">
<text class="icon-text">↻</text>
</view>
</view>
</view>
</view>
@@ -214,7 +218,7 @@ const driverInfo = ref({
})
const workStatus = ref(1) // 1 工作中 0 休息中
const currentLocation = ref('朝阳区建国门附近')
const currentLocation = ref('朝阳区建国门附近') // 默认位置
const taskCounts = ref({ total: 0, pending: 0, ongoing: 0, completed: 0 })
@@ -364,6 +368,41 @@ function toggleWorkStatus() {
})
}
// --- 新增:重新定位逻辑 ---
function showRelocateConfirm() {
uni.showModal({
title: '重新定位',
content: '确定要更新当前位置吗?',
confirmText: '立即定位',
success: (res) => {
if (res.confirm) {
relocate()
}
}
})
}
function relocate() {
uni.showLoading({
title: '获取位置中...',
mask: true
})
// 模拟定位耗时
setTimeout(() => {
uni.hideLoading()
// 模拟定位成功更新位置实际项目中应调用API获取真实位置
currentLocation.value = '朝阳区建国门外大街附近'
uni.showToast({
title: '定位成功',
icon: 'success',
duration: 1500
})
}, 1200)
}
function contactCustomer() {
uni.showActionSheet({
itemList: ['拨打电话', '发送短信'],
@@ -398,10 +437,10 @@ function goToRatings() {
uni.navigateTo({ url: '/pages/mall/delivery/ratings' })
}
function goToHelp() {
uni.navigateTo({ url: '/pages/mall/common/help' })
uni.navigateTo({ url: '/pages/mall/delivery/help-center' })
}
function goToFeedback() {
uni.navigateTo({ url: '/pages/mall/common/feedback' })
uni.navigateTo({ url: '/pages/mall/delivery/feedback' })
}
</script>
@@ -410,7 +449,7 @@ function goToFeedback() {
.profile-header {
position: relative;
}
.back-box {
.nav-left {
position: absolute;
left: 30rpx;
top: 50%;
@@ -423,15 +462,15 @@ function goToFeedback() {
align-items: center;
justify-content: center;
}
.back-box:active {
.nav-left:active {
background: rgba(0, 0, 0, .3);
}
.back-icon {
.nav-icon {
font-size: 40rpx;
color: #fff;
}
/* ---------- 以下与原样式一致 ---------- */
/* ---------- 以下与原样式一致,仅修改工作状态区域 ---------- */
.delivery-profile {
padding: 0 0 120rpx 0;
background-color: #f5f5f5;
@@ -541,14 +580,39 @@ function goToFeedback() {
.toggle-switch.active .toggle-handle {
left: 55rpx;
}
.current-location {
/* --- 修改后的定位信息区域:地址 + 图标 --- */
.current-location-section {
display: flex;
align-items: center;
padding: 15rpx 20rpx;
background: #e8f4fd;
background: #e8f4fd; /* 与原样式保持一致 */
border-radius: 15rpx;
}
.location-text {
font-size: 24rpx; /* 与截图字号一致 */
color: #74b9ff; /* 与截图颜色一致 */
flex: 1; /* 让文字占据剩余空间 */
margin-right: 16rpx; /* 与图标留空隙 */
}
.relocate-icon {
width: 40rpx;
height: 40rpx;
display: flex;
align-items: center;
justify-content: center;
border-radius: 50%;
background: rgba(116, 185, 255, 0.15); /* 浅蓝底 */
color: #74b9ff; /* 主色文字 */
font-size: 24rpx;
color: #74b9ff;
cursor: pointer;
transition: all 0.2s ease;
}
.relocate-icon:hover {
background: rgba(116, 185, 255, 0.3); /* 悬停效果 */
}
.relocate-icon:active {
background: rgba(116, 185, 255, 0.4); /* 点击效果 */
}
.task-tabs {
@@ -556,10 +620,10 @@ function goToFeedback() {
justify-content: space-between;
}
.task-tab {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
flex: 1;
position: relative;
}
.tab-icon {

View File

@@ -0,0 +1,418 @@
<template>
<view class="container">
<view class="header">
<text class="title">Supabase 连接测试</text>
</view>
<view class="config-info">
<text class="label">当前配置:</text>
<text class="value">URL: {{ config.url }}</text>
<text class="value">Key: {{ config.keyMasked }}</text>
</view>
<view class="test-section">
<button class="btn-primary" @click="runAllTests" :disabled="isLoading">
{{ isLoading ? '测试中...' : '运行全部测试' }}
</button>
</view>
<view class="results" v-if="testResults.length > 0">
<text class="section-title">测试结果</text>
<view
class="result-item"
v-for="(result, index) in testResults"
:key="index"
:class="{ success: result.success, error: !result.success }"
>
<text class="result-icon">{{ result.success ? '✅' : '❌' }}</text>
<text class="result-text">{{ result.name }}</text>
<text class="result-message">{{ result.message }}</text>
<text v-if="result.errorDetails" class="error-details">{{ result.errorDetails }}</text>
</view>
</view>
<view class="quick-tests">
<text class="section-title">单项测试</text>
<button class="btn-secondary" @click="testConnection">测试连接</button>
<button class="btn-secondary" @click="testQuery">测试查询</button>
<button class="btn-secondary" @click="testAuth">测试认证</button>
</view>
</view>
</template>
<script lang="uts">
// 导入你的封装库和配置
import supa from '@/components/supadb/aksupainstance.uts'
import { SUPA_URL, SUPA_KEY } from '@/ak/config.uts'
// 定义测试结果类型
type TestResult = {
name: string
success: boolean
message: string
errorDetails?: string
}
// 定义用户类型(用于类型安全查询)
type User = {
id?: number
email?: string
nickname?: string
phone?: string
created_at?: string
}
export default {
data() {
return {
config: {
url: SUPA_URL,
key: SUPA_KEY,
keyMasked: SUPA_KEY.substring(0, 20) + '...' // 隐藏部分密钥
},
isLoading: false,
testResults: [] as TestResult[]
}
},
onLoad() {
console.log('🎯 Supabase 测试页面加载完成')
console.log('📋 配置信息:', {
url: SUPA_URL,
hasKey: !!SUPA_KEY
})
},
methods: {
// 获取错误消息的安全函数
getErrorMessage(err: any): string {
if (err == null) return '未知错误'
if (typeof err === 'string') return err
if (typeof err === 'object') {
if (err.message) return err.message
if (err.error) return err.error
if (err.msg) return err.msg
return JSON.stringify(err)
}
return String(err)
},
// 安全执行测试
async safeExecute<T>(promise: Promise<T>, operationName: string): Promise<{ success: boolean; data?: T; error?: string }> {
try {
const result = await promise
return { success: true, data: result }
} catch (err) {
const errorMessage = this.getErrorMessage(err)
console.error(`❌ ${operationName} 失败:`, err)
return { success: false, error: errorMessage }
}
},
// 测试连接
async testConnection(): Promise<TestResult> {
console.log('🔍 开始测试连接...')
const result = await this.safeExecute(
uni.request({
url: `${SUPA_URL}/rest/v1/`,
method: 'GET',
header: {
'apikey': SUPA_KEY,
'Authorization': `Bearer ${SUPA_KEY}`,
'Content-Type': 'application/json'
},
timeout: 10000
}),
'基础连接测试'
)
if (result.success) {
const response = result.data as any
if (response.statusCode === 200 || response.statusCode === 404) {
return {
name: '基础连接',
success: true,
message: `API 可达 (HTTP ${response.statusCode})`,
errorDetails: response.statusCode === 404 ? 'API 响应正常,但请求的资源不存在' : undefined
}
} else {
return {
name: '基础连接',
success: false,
message: `HTTP 错误`,
errorDetails: `状态码: ${response.statusCode}, 数据: ${JSON.stringify(response.data)}`
}
}
} else {
return {
name: '基础连接',
success: false,
message: '连接失败',
errorDetails: result.error
}
}
},
// 测试查询
async testQuery(): Promise<TestResult> {
console.log('🔍 开始测试查询...')
// 尝试查询 users 表
const queryResult = await this.safeExecute(
supa.from('users').select('id, email, nickname').limit(1).execute(),
'查询测试'
)
if (queryResult.success) {
const { data, error } = queryResult.data as any
if (error) {
const errorMsg = this.getErrorMessage(error)
// 检查是否是表不存在的错误
if (errorMsg.includes('relation') && errorMsg.includes('does not exist')) {
return {
name: '查询测试',
success: false,
message: '表不存在',
errorDetails: 'users 表不存在,建议先在 Studio 中创建表'
}
}
return {
name: '查询测试',
success: false,
message: '查询出错',
errorDetails: errorMsg
}
}
return {
name: '查询测试',
success: true,
message: `查询成功,返回 ${data?.length || 0} 条记录`,
errorDetails: data && data.length > 0 ? `第一条: ${JSON.stringify(data[0])}` : '表为空'
}
} else {
return {
name: '查询测试',
success: false,
message: '查询失败',
errorDetails: queryResult.error
}
}
},
// 测试认证
async testAuth(): Promise<TestResult> {
console.log('🔍 开始测试认证...')
const authResult = await this.safeExecute(
supa.auth.getSession(),
'认证测试'
)
if (authResult.success) {
const { data, error } = authResult.data as any
if (error) {
return {
name: '认证测试',
success: false,
message: '获取会话失败',
errorDetails: this.getErrorMessage(error)
}
}
if (data?.session) {
return {
name: '认证测试',
success: true,
message: '用户已登录',
errorDetails: `用户: ${data.session.user.email || data.session.user.phone || 'ID: ' + data.session.user.id}`
}
} else {
return {
name: '认证测试',
success: true,
message: '未登录(正常状态)',
errorDetails: '当前没有活动会话,需要先登录'
}
}
} else {
return {
name: '认证测试',
success: false,
message: '认证失败',
errorDetails: authResult.error
}
}
},
// 运行所有测试
async runAllTests() {
this.isLoading = true
this.testResults = []
try {
// 依次运行测试
const tests = [
{ name: 'connection', func: this.testConnection },
{ name: 'query', func: this.testQuery },
{ name: 'auth', func: this.testAuth }
]
for (const test of tests) {
console.log(`🧪 运行测试: ${test.name}`)
const result = await test.func.call(this)
this.testResults.push(result)
// 短暂延迟,让用户看到测试进度
await new Promise(resolve => setTimeout(resolve, 500))
}
// 总结结果
const passed = this.testResults.filter(r => r.success).length
const total = this.testResults.length
console.log(`📈 测试总结: ${passed}/${total} 通过`)
} catch (error) {
console.error('❌ 运行测试时发生错误:', error)
this.testResults.push({
name: '整体测试',
success: false,
message: '测试框架错误',
errorDetails: this.getErrorMessage(error)
})
} finally {
this.isLoading = false
}
}
}
}
</script>
<style>
.container {
padding: 30rpx;
background-color: #f8f9fa;
min-height: 100vh;
}
.header {
text-align: center;
margin-bottom: 40rpx;
}
.title {
font-size: 36rpx;
font-weight: bold;
color: #333;
}
.config-info {
background-color: #fff;
padding: 20rpx;
border-radius: 12rpx;
margin-bottom: 30rpx;
}
.label {
font-size: 28rpx;
color: #666;
display: block;
margin-bottom: 10rpx;
}
.value {
font-size: 24rpx;
color: #333;
display: block;
margin-bottom: 8rpx;
word-break: break-all;
}
.test-section {
margin-bottom: 40rpx;
}
.btn-primary {
width: 100%;
height: 80rpx;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: #fff;
border-radius: 12rpx;
font-size: 28rpx;
border: none;
margin-bottom: 20rpx;
}
.btn-secondary {
width: 100%;
height: 70rpx;
background-color: #6c757d;
color: #fff;
border-radius: 8rpx;
font-size: 26rpx;
border: none;
margin-bottom: 15rpx;
}
.btn-primary:disabled, .btn-secondary:disabled {
background-color: #ccc;
}
.results {
margin-bottom: 40rpx;
}
.section-title {
font-size: 30rpx;
font-weight: bold;
color: #333;
display: block;
margin-bottom: 20rpx;
}
.result-item {
background-color: #fff;
padding: 20rpx;
border-radius: 12rpx;
margin-bottom: 15rpx;
}
.result-item.success {
border-left: 4px solid #28a745;
background-color: #d4edda;
}
.result-item.error {
border-left: 4px solid #dc3545;
background-color: #f8d7da;
}
.result-icon {
font-size: 32rpx;
margin-right: 10rpx;
vertical-align: middle;
}
.result-text {
font-size: 26rpx;
color: #333;
font-weight: bold;
vertical-align: middle;
}
.result-message {
display: block;
font-size: 24rpx;
color: #666;
margin-top: 8rpx;
}
.error-details {
display: block;
font-size: 20rpx;
color: #856404;
background-color: #fff3cd;
padding: 8rpx;
border-radius: 4rpx;
margin-top: 8rpx;
word-break: break-all;
white-space: pre-wrap;
}
</style>