- 新增用户注册和登录系统 (login.html, register.html) - 集成Supabase数据库连接 (config.js, api.js) - 完善数据库架构设计 (database-schema.sql) - 添加部署指南和配置文档 (DEPLOYMENT_GUIDE.md) - 修复主页面结构和功能完善 (index.html) - 支持通话记录保存到数据库 - 完整的账单管理和用户认证流程 - 集成OpenAI、Twilio、Stripe等API服务
1507 lines
49 KiB
HTML
1507 lines
49 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="zh-CN">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>翻译服务应用</title>
|
||
<link rel="manifest" href="manifest.json">
|
||
<meta name="theme-color" content="#4285f4">
|
||
|
||
<!-- 引入必要的脚本 -->
|
||
<script src="https://unpkg.com/@supabase/supabase-js@2"></script>
|
||
<script src="web-app/config.js"></script>
|
||
<script src="web-app/api.js"></script>
|
||
|
||
<style>
|
||
* {
|
||
margin: 0;
|
||
padding: 0;
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
body {
|
||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||
min-height: 100vh;
|
||
color: #333;
|
||
}
|
||
|
||
.app-container {
|
||
max-width: 414px;
|
||
margin: 0 auto;
|
||
background: white;
|
||
min-height: 100vh;
|
||
position: relative;
|
||
box-shadow: 0 0 20px rgba(0,0,0,0.1);
|
||
}
|
||
|
||
.header {
|
||
background: linear-gradient(135deg, #4285f4, #34a853);
|
||
color: white;
|
||
text-align: center;
|
||
padding: 20px;
|
||
position: relative;
|
||
}
|
||
|
||
.header-content {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
}
|
||
|
||
.user-info {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 10px;
|
||
}
|
||
|
||
.login-btn-header {
|
||
color: white;
|
||
text-decoration: none;
|
||
padding: 8px 16px;
|
||
border: 1px solid white;
|
||
border-radius: 20px;
|
||
font-size: 14px;
|
||
transition: all 0.3s ease;
|
||
}
|
||
|
||
.login-btn-header:hover {
|
||
background: white;
|
||
color: #4285f4;
|
||
}
|
||
|
||
.user-name {
|
||
font-size: 14px;
|
||
font-weight: 500;
|
||
}
|
||
|
||
.logout-btn {
|
||
background: rgba(255, 255, 255, 0.2);
|
||
color: white;
|
||
border: 1px solid rgba(255, 255, 255, 0.5);
|
||
padding: 6px 12px;
|
||
border-radius: 15px;
|
||
font-size: 12px;
|
||
cursor: pointer;
|
||
transition: all 0.3s ease;
|
||
}
|
||
|
||
.logout-btn:hover {
|
||
background: rgba(255, 255, 255, 0.3);
|
||
}
|
||
|
||
.header h1 {
|
||
font-size: 20px;
|
||
font-weight: 600;
|
||
margin: 0;
|
||
}
|
||
|
||
.content {
|
||
padding: 20px;
|
||
padding-bottom: 100px;
|
||
min-height: calc(100vh - 140px);
|
||
}
|
||
|
||
.tab-content {
|
||
display: none;
|
||
}
|
||
|
||
.tab-content.active {
|
||
display: block;
|
||
}
|
||
|
||
/* 通话页面样式 */
|
||
.call-page {
|
||
text-align: center;
|
||
}
|
||
|
||
.call-options {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 20px;
|
||
margin-top: 40px;
|
||
}
|
||
|
||
.call-btn {
|
||
padding: 20px;
|
||
border: none;
|
||
border-radius: 15px;
|
||
font-size: 18px;
|
||
font-weight: 600;
|
||
cursor: pointer;
|
||
transition: all 0.3s ease;
|
||
box-shadow: 0 4px 15px rgba(0,0,0,0.1);
|
||
}
|
||
|
||
.voice-call {
|
||
background: linear-gradient(135deg, #4CAF50, #45a049);
|
||
color: white;
|
||
}
|
||
|
||
.video-call {
|
||
background: linear-gradient(135deg, #2196F3, #1976D2);
|
||
color: white;
|
||
}
|
||
|
||
.call-btn:hover {
|
||
transform: translateY(-2px);
|
||
box-shadow: 0 6px 20px rgba(0,0,0,0.15);
|
||
}
|
||
|
||
.call-interface {
|
||
display: none;
|
||
text-align: center;
|
||
margin-top: 40px;
|
||
}
|
||
|
||
.call-timer {
|
||
font-size: 48px;
|
||
font-weight: bold;
|
||
color: #4285f4;
|
||
margin: 40px 0;
|
||
}
|
||
|
||
.call-type-display {
|
||
font-size: 18px;
|
||
color: #666;
|
||
margin-bottom: 20px;
|
||
}
|
||
|
||
.end-call-btn {
|
||
display: none;
|
||
background: #f44336;
|
||
color: white;
|
||
padding: 15px 30px;
|
||
border: none;
|
||
border-radius: 50px;
|
||
font-size: 18px;
|
||
cursor: pointer;
|
||
margin-top: 20px;
|
||
transition: all 0.3s ease;
|
||
}
|
||
|
||
.end-call-btn:hover {
|
||
background: #d32f2f;
|
||
}
|
||
|
||
/* 费率弹窗样式 */
|
||
.modal {
|
||
position: fixed;
|
||
top: 0;
|
||
left: 0;
|
||
width: 100%;
|
||
height: 100%;
|
||
background: rgba(0,0,0,0.5);
|
||
display: flex;
|
||
justify-content: center;
|
||
align-items: center;
|
||
z-index: 1000;
|
||
}
|
||
|
||
.modal.hidden {
|
||
display: none;
|
||
}
|
||
|
||
.modal-content {
|
||
background: white;
|
||
padding: 30px;
|
||
border-radius: 20px;
|
||
width: 90%;
|
||
max-width: 400px;
|
||
text-align: center;
|
||
}
|
||
|
||
.modal h3 {
|
||
font-size: 20px;
|
||
margin-bottom: 20px;
|
||
color: #333;
|
||
}
|
||
|
||
.rate-info {
|
||
background: #f5f5f5;
|
||
padding: 20px;
|
||
border-radius: 15px;
|
||
margin: 20px 0;
|
||
}
|
||
|
||
.rate-item {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
margin: 10px 0;
|
||
font-size: 16px;
|
||
}
|
||
|
||
.translator-option {
|
||
margin: 20px 0;
|
||
text-align: left;
|
||
}
|
||
|
||
.translator-option label {
|
||
display: flex;
|
||
align-items: center;
|
||
cursor: pointer;
|
||
}
|
||
|
||
.translator-option input[type="checkbox"] {
|
||
margin-right: 10px;
|
||
}
|
||
|
||
.modal-buttons {
|
||
display: flex;
|
||
gap: 15px;
|
||
margin-top: 25px;
|
||
}
|
||
|
||
.modal-btn {
|
||
flex: 1;
|
||
padding: 12px;
|
||
border: none;
|
||
border-radius: 10px;
|
||
font-size: 16px;
|
||
cursor: pointer;
|
||
transition: all 0.3s ease;
|
||
}
|
||
|
||
.cancel-btn {
|
||
background: #f0f0f0;
|
||
color: #666;
|
||
}
|
||
|
||
.confirm-btn {
|
||
background: #4285f4;
|
||
color: white;
|
||
}
|
||
|
||
.modal-btn:hover {
|
||
transform: translateY(-1px);
|
||
}
|
||
|
||
/* 账单弹窗样式 */
|
||
.bill-modal .modal-content {
|
||
max-width: 350px;
|
||
}
|
||
|
||
.bill-details {
|
||
text-align: left;
|
||
margin: 20px 0;
|
||
}
|
||
|
||
.bill-row {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
margin: 10px 0;
|
||
padding: 5px 0;
|
||
}
|
||
|
||
.bill-row.total {
|
||
border-top: 2px solid #eee;
|
||
padding-top: 10px;
|
||
margin-top: 15px;
|
||
font-weight: bold;
|
||
font-size: 18px;
|
||
}
|
||
|
||
/* 日历组件样式 */
|
||
.calendar-container {
|
||
background: white;
|
||
border-radius: 15px;
|
||
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
||
overflow: hidden;
|
||
margin-bottom: 20px;
|
||
}
|
||
|
||
.calendar-header {
|
||
background: linear-gradient(135deg, #4285f4, #34a853);
|
||
color: white;
|
||
padding: 20px;
|
||
text-align: center;
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
}
|
||
|
||
.calendar-nav-btn {
|
||
background: none;
|
||
border: none;
|
||
color: white;
|
||
font-size: 20px;
|
||
cursor: pointer;
|
||
padding: 5px 10px;
|
||
border-radius: 5px;
|
||
transition: background 0.3s ease;
|
||
}
|
||
|
||
.calendar-nav-btn:hover {
|
||
background: rgba(255,255,255,0.2);
|
||
}
|
||
|
||
.calendar-month {
|
||
font-size: 18px;
|
||
font-weight: 600;
|
||
}
|
||
|
||
.calendar-grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(7, 1fr);
|
||
}
|
||
|
||
.calendar-day-header {
|
||
padding: 15px 5px;
|
||
text-align: center;
|
||
font-weight: 600;
|
||
color: #666;
|
||
font-size: 14px;
|
||
background: #f8f9fa;
|
||
}
|
||
|
||
.calendar-day {
|
||
padding: 15px 5px;
|
||
text-align: center;
|
||
cursor: pointer;
|
||
transition: all 0.3s ease;
|
||
position: relative;
|
||
min-height: 50px;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
border: 1px solid #f0f0f0;
|
||
}
|
||
|
||
.calendar-day:hover {
|
||
background: #f0f8ff;
|
||
}
|
||
|
||
.calendar-day.other-month {
|
||
color: #ccc;
|
||
}
|
||
|
||
.calendar-day.today {
|
||
background: #4285f4;
|
||
color: white;
|
||
font-weight: bold;
|
||
}
|
||
|
||
.calendar-day.has-appointment {
|
||
background: #e8f5e8;
|
||
color: #2e7d32;
|
||
font-weight: 600;
|
||
}
|
||
|
||
.calendar-day.has-appointment::after {
|
||
content: '•';
|
||
position: absolute;
|
||
bottom: 5px;
|
||
left: 50%;
|
||
transform: translateX(-50%);
|
||
color: #4CAF50;
|
||
font-size: 20px;
|
||
}
|
||
|
||
.appointment-details {
|
||
margin-top: 20px;
|
||
}
|
||
|
||
.appointment-item {
|
||
background: white;
|
||
padding: 15px;
|
||
border-radius: 10px;
|
||
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
|
||
margin-bottom: 10px;
|
||
border-left: 4px solid #4285f4;
|
||
}
|
||
|
||
.appointment-time {
|
||
font-weight: 600;
|
||
color: #4285f4;
|
||
margin-bottom: 5px;
|
||
}
|
||
|
||
.appointment-desc {
|
||
color: #666;
|
||
font-size: 14px;
|
||
}
|
||
|
||
/* 文档页面样式 */
|
||
.document-header {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
margin-bottom: 20px;
|
||
}
|
||
|
||
.document-title {
|
||
font-size: 20px;
|
||
font-weight: 600;
|
||
color: #333;
|
||
}
|
||
|
||
.import-btn {
|
||
background: linear-gradient(135deg, #4285f4, #34a853);
|
||
color: white;
|
||
border: none;
|
||
padding: 10px 20px;
|
||
border-radius: 25px;
|
||
font-size: 14px;
|
||
cursor: pointer;
|
||
transition: all 0.3s ease;
|
||
box-shadow: 0 2px 10px rgba(66, 133, 244, 0.3);
|
||
}
|
||
|
||
.import-btn:hover {
|
||
transform: translateY(-2px);
|
||
box-shadow: 0 4px 15px rgba(66, 133, 244, 0.4);
|
||
}
|
||
|
||
.file-input {
|
||
display: none;
|
||
}
|
||
|
||
.document-stats {
|
||
display: grid;
|
||
grid-template-columns: repeat(2, 1fr);
|
||
gap: 15px;
|
||
margin-bottom: 30px;
|
||
}
|
||
|
||
.stat-card {
|
||
background: white;
|
||
padding: 20px;
|
||
border-radius: 15px;
|
||
text-align: center;
|
||
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
||
}
|
||
|
||
.stat-number {
|
||
font-size: 24px;
|
||
font-weight: bold;
|
||
color: #4285f4;
|
||
margin-bottom: 5px;
|
||
}
|
||
|
||
.stat-label {
|
||
color: #666;
|
||
font-size: 14px;
|
||
}
|
||
|
||
.document-list {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 15px;
|
||
}
|
||
|
||
.document-item {
|
||
background: white;
|
||
padding: 20px;
|
||
border-radius: 15px;
|
||
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
||
}
|
||
|
||
.document-item-header {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
margin-bottom: 10px;
|
||
}
|
||
|
||
.document-name {
|
||
font-weight: 600;
|
||
color: #333;
|
||
}
|
||
|
||
.document-progress {
|
||
font-size: 12px;
|
||
padding: 4px 8px;
|
||
border-radius: 12px;
|
||
}
|
||
|
||
.progress-completed {
|
||
background: #e8f5e8;
|
||
color: #2e7d32;
|
||
}
|
||
|
||
.progress-processing {
|
||
background: #fff3e0;
|
||
color: #f57c00;
|
||
}
|
||
|
||
.document-info {
|
||
color: #666;
|
||
font-size: 14px;
|
||
}
|
||
|
||
/* 个人中心样式 */
|
||
.profile-header {
|
||
text-align: center;
|
||
margin-bottom: 30px;
|
||
}
|
||
|
||
.profile-avatar {
|
||
width: 80px;
|
||
height: 80px;
|
||
border-radius: 50%;
|
||
background: linear-gradient(135deg, #4285f4, #34a853);
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
margin: 0 auto 15px;
|
||
font-size: 32px;
|
||
color: white;
|
||
font-weight: bold;
|
||
}
|
||
|
||
.profile-name {
|
||
font-size: 20px;
|
||
font-weight: 600;
|
||
margin-bottom: 5px;
|
||
}
|
||
|
||
.profile-id {
|
||
color: #666;
|
||
font-size: 14px;
|
||
}
|
||
|
||
.profile-menu {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 1px;
|
||
background: #f0f0f0;
|
||
border-radius: 15px;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.menu-item {
|
||
background: white;
|
||
padding: 20px;
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
cursor: pointer;
|
||
transition: background 0.3s ease;
|
||
}
|
||
|
||
.menu-item:hover {
|
||
background: #f8f9fa;
|
||
}
|
||
|
||
.menu-item-left {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 15px;
|
||
}
|
||
|
||
.menu-icon {
|
||
width: 24px;
|
||
height: 24px;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
font-size: 18px;
|
||
}
|
||
|
||
.menu-text {
|
||
font-size: 16px;
|
||
color: #333;
|
||
}
|
||
|
||
.menu-arrow {
|
||
color: #ccc;
|
||
font-size: 14px;
|
||
}
|
||
|
||
/* 底部导航样式 */
|
||
.bottom-nav {
|
||
position: fixed;
|
||
bottom: 0;
|
||
left: 50%;
|
||
transform: translateX(-50%);
|
||
width: 100%;
|
||
max-width: 414px;
|
||
background: white;
|
||
border-top: 1px solid #eee;
|
||
display: flex;
|
||
justify-content: space-around;
|
||
padding: 10px 0;
|
||
z-index: 100;
|
||
}
|
||
|
||
.nav-item {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
padding: 5px;
|
||
cursor: pointer;
|
||
transition: color 0.3s ease;
|
||
color: #999;
|
||
text-decoration: none;
|
||
}
|
||
|
||
.nav-item.active {
|
||
color: #4285f4;
|
||
}
|
||
|
||
.nav-icon {
|
||
font-size: 20px;
|
||
margin-bottom: 4px;
|
||
}
|
||
|
||
.nav-text {
|
||
font-size: 12px;
|
||
}
|
||
|
||
/* 响应式设计 */
|
||
@media (max-width: 480px) {
|
||
.app-container {
|
||
max-width: 100%;
|
||
}
|
||
|
||
.content {
|
||
padding: 15px;
|
||
}
|
||
}
|
||
|
||
/* 账单历史样式 */
|
||
.bill-list {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 10px;
|
||
margin-top: 20px;
|
||
}
|
||
|
||
.bill-item {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
padding: 15px;
|
||
background: white;
|
||
border-radius: 10px;
|
||
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
|
||
}
|
||
|
||
.bill-info {
|
||
flex: 1;
|
||
}
|
||
|
||
.bill-date {
|
||
font-size: 14px;
|
||
color: #666;
|
||
}
|
||
|
||
.bill-type {
|
||
font-weight: 600;
|
||
margin-top: 2px;
|
||
}
|
||
|
||
.bill-amount {
|
||
font-size: 16px;
|
||
font-weight: bold;
|
||
color: #4285f4;
|
||
margin: 0 15px;
|
||
}
|
||
|
||
.bill-status {
|
||
padding: 4px 8px;
|
||
border-radius: 12px;
|
||
font-size: 12px;
|
||
font-weight: 500;
|
||
}
|
||
|
||
.bill-status.paid {
|
||
background: #e8f5e8;
|
||
color: #2e7d32;
|
||
}
|
||
|
||
.bill-status.unpaid {
|
||
background: #ffebee;
|
||
color: #c62828;
|
||
}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<div class="app-container">
|
||
<div class="header">
|
||
<div class="header-content">
|
||
<h1>翻译服务应用</h1>
|
||
<div class="user-info login-required" style="display: none;">
|
||
<a href="web-app/login.html" class="login-btn-header">登录</a>
|
||
</div>
|
||
<div class="user-info user-only" style="display: none;">
|
||
<span class="user-name">用户名</span>
|
||
<button class="logout-btn" onclick="handleLogout()">登出</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="content">
|
||
<!-- 通话页面 -->
|
||
<div id="call" class="tab-content active">
|
||
<div class="call-page">
|
||
<div class="call-options">
|
||
<button class="call-btn voice-call" onclick="selectCallType('voice')">
|
||
📞 语音通话
|
||
</button>
|
||
<button class="call-btn video-call" onclick="selectCallType('video')">
|
||
📹 视频通话
|
||
</button>
|
||
</div>
|
||
|
||
<div class="call-interface">
|
||
<div class="call-type-display">通话中...</div>
|
||
<div class="call-timer">00:00</div>
|
||
</div>
|
||
|
||
<button class="end-call-btn" onclick="endCall()">
|
||
结束通话
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 预约页面 - 日历组件 -->
|
||
<div id="appointment" class="tab-content">
|
||
<div class="calendar-container">
|
||
<div class="calendar-header">
|
||
<button class="calendar-nav-btn" onclick="previousMonth()">‹</button>
|
||
<div class="calendar-month" id="currentMonth">2024年1月</div>
|
||
<button class="calendar-nav-btn" onclick="nextMonth()">›</button>
|
||
</div>
|
||
<div class="calendar-grid" id="calendarGrid">
|
||
<!-- 日历将通过JavaScript生成 -->
|
||
</div>
|
||
</div>
|
||
|
||
<div class="appointment-details" id="appointmentDetails">
|
||
<div class="appointment-item">
|
||
<div class="appointment-time">今天 14:30</div>
|
||
<div class="appointment-desc">商务会议翻译 - 英语 | 翻译员:张译文</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 文档页面 -->
|
||
<div id="document" class="tab-content">
|
||
<div class="document-header">
|
||
<div class="document-title">文档管理</div>
|
||
<button class="import-btn" onclick="document.getElementById('fileInput').click()">
|
||
📁 导入文档
|
||
</button>
|
||
<input type="file" id="fileInput" class="file-input" multiple accept=".pdf,.doc,.docx,.txt" onchange="handleFileImport(event)">
|
||
</div>
|
||
|
||
<div class="document-stats">
|
||
<div class="stat-card">
|
||
<div class="stat-number" id="totalDocs">24</div>
|
||
<div class="stat-label">总文档数</div>
|
||
</div>
|
||
<div class="stat-card">
|
||
<div class="stat-number">18</div>
|
||
<div class="stat-label">已完成</div>
|
||
</div>
|
||
<div class="stat-card">
|
||
<div class="stat-number">4</div>
|
||
<div class="stat-label">进行中</div>
|
||
</div>
|
||
<div class="stat-card">
|
||
<div class="stat-number">2</div>
|
||
<div class="stat-label">待开始</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="document-list" id="documentList">
|
||
<div class="document-item">
|
||
<div class="document-item-header">
|
||
<div class="document-name">产品说明书.pdf</div>
|
||
<div class="document-progress progress-completed">已完成</div>
|
||
</div>
|
||
<div class="document-info">
|
||
中文 → 英文 | 5页 | 完成时间:2024-01-15
|
||
</div>
|
||
</div>
|
||
|
||
<div class="document-item">
|
||
<div class="document-item-header">
|
||
<div class="document-name">合同文件.docx</div>
|
||
<div class="document-progress progress-processing">翻译中</div>
|
||
</div>
|
||
<div class="document-info">
|
||
英文 → 中文 | 12页 | 进度:60%
|
||
</div>
|
||
</div>
|
||
|
||
<div class="document-item">
|
||
<div class="document-item-header">
|
||
<div class="document-name">技术手册.pdf</div>
|
||
<div class="document-progress progress-processing">审核中</div>
|
||
</div>
|
||
<div class="document-info">
|
||
日文 → 中文 | 8页 | 预计完成:明天
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 个人中心页面 -->
|
||
<div id="profile" class="tab-content">
|
||
<div class="profile-header">
|
||
<div class="profile-avatar">用</div>
|
||
<div class="profile-name">用户名</div>
|
||
<div class="profile-id">ID: 123456789</div>
|
||
</div>
|
||
|
||
<div class="profile-menu">
|
||
<div class="menu-item">
|
||
<div class="menu-item-left">
|
||
<div class="menu-icon">📋</div>
|
||
<div class="menu-text">账单历史</div>
|
||
</div>
|
||
<div class="menu-arrow">›</div>
|
||
</div>
|
||
|
||
<div class="menu-item">
|
||
<div class="menu-item-left">
|
||
<div class="menu-icon">⚙️</div>
|
||
<div class="menu-text">设置</div>
|
||
</div>
|
||
<div class="menu-arrow">›</div>
|
||
</div>
|
||
|
||
<div class="menu-item">
|
||
<div class="menu-item-left">
|
||
<div class="menu-icon">❓</div>
|
||
<div class="menu-text">帮助与支持</div>
|
||
</div>
|
||
<div class="menu-arrow">›</div>
|
||
</div>
|
||
|
||
<div class="menu-item">
|
||
<div class="menu-item-left">
|
||
<div class="menu-icon">ℹ️</div>
|
||
<div class="menu-text">关于我们</div>
|
||
</div>
|
||
<div class="menu-arrow">›</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="bill-list">
|
||
<!-- 账单历史将通过JavaScript动态加载 -->
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 底部导航 -->
|
||
<div class="bottom-nav">
|
||
<div class="nav-item active" onclick="switchTab('call')">
|
||
<div class="nav-icon">📞</div>
|
||
<div class="nav-text">通话</div>
|
||
</div>
|
||
<div class="nav-item" onclick="switchTab('appointment')">
|
||
<div class="nav-icon">📅</div>
|
||
<div class="nav-text">预约</div>
|
||
</div>
|
||
<div class="nav-item" onclick="switchTab('document')">
|
||
<div class="nav-icon">📄</div>
|
||
<div class="nav-text">文档</div>
|
||
</div>
|
||
<div class="nav-item" onclick="switchTab('profile')">
|
||
<div class="nav-icon">👤</div>
|
||
<div class="nav-text">我的</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 费率确认弹窗 -->
|
||
<div id="rate-modal" class="modal hidden">
|
||
<div class="modal-content">
|
||
<h3>通话费率确认</h3>
|
||
<div class="rate-info">
|
||
<div class="rate-item">
|
||
<span>通话类型:</span>
|
||
<span id="modal-call-type">语音通话</span>
|
||
</div>
|
||
<div class="rate-item">
|
||
<span>基础费率:</span>
|
||
<span id="modal-base-rate">¥80/小时</span>
|
||
</div>
|
||
<div class="translator-option">
|
||
<label>
|
||
<input type="checkbox" id="translator-checkbox" onchange="updateModalRate()">
|
||
添加翻译员服务 (+¥50/小时)
|
||
</label>
|
||
</div>
|
||
<div class="rate-item" style="font-weight: bold; border-top: 1px solid #eee; padding-top: 10px;">
|
||
<span>总费率:</span>
|
||
<span id="modal-total-rate">¥80/小时</span>
|
||
</div>
|
||
</div>
|
||
<div style="font-size: 14px; color: #666; margin: 15px 0;">
|
||
* 按分钟计费,最低1分钟
|
||
</div>
|
||
<div class="modal-buttons">
|
||
<button class="modal-btn cancel-btn" onclick="closeRateModal()">取消</button>
|
||
<button class="modal-btn confirm-btn" onclick="confirmAndStartCall()">开始通话</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 账单弹窗 -->
|
||
<div class="bill-modal modal hidden">
|
||
<div class="modal-content">
|
||
<h3>通话账单</h3>
|
||
<div class="bill-details">
|
||
<div class="bill-row">
|
||
<span>通话类型:</span>
|
||
<span id="bill-call-type">语音通话</span>
|
||
</div>
|
||
<div class="bill-row">
|
||
<span>通话时长:</span>
|
||
<span id="bill-duration">2分30秒</span>
|
||
</div>
|
||
<div class="bill-row">
|
||
<span>基础费用:</span>
|
||
<span id="bill-base-rate">¥6.67</span>
|
||
</div>
|
||
<div class="bill-row" id="translator-fee-item" style="display: none;">
|
||
<span>翻译员费用:</span>
|
||
<span id="bill-translator-fee">¥4.17</span>
|
||
</div>
|
||
<div class="bill-row total">
|
||
<span>总计:</span>
|
||
<span id="bill-total-amount">¥6.67</span>
|
||
</div>
|
||
</div>
|
||
<div class="modal-buttons">
|
||
<button class="modal-btn cancel-btn" onclick="closeBill()">稍后支付</button>
|
||
<button class="modal-btn confirm-btn" onclick="payBill()">立即支付</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<script>
|
||
// 等待API管理器初始化
|
||
let apiManagerReady = false;
|
||
|
||
// 初始化应用
|
||
async function initApp() {
|
||
// 等待API管理器初始化
|
||
await new Promise(resolve => {
|
||
const checkInit = () => {
|
||
if (window.apiManager && window.apiManager.supabase) {
|
||
apiManagerReady = true;
|
||
resolve();
|
||
} else {
|
||
setTimeout(checkInit, 100);
|
||
}
|
||
};
|
||
checkInit();
|
||
});
|
||
|
||
// 检查登录状态
|
||
await checkLoginStatus();
|
||
|
||
// 如果用户已登录,加载用户数据
|
||
if (apiManager.currentUser) {
|
||
await loadUserData();
|
||
}
|
||
}
|
||
|
||
// 检查登录状态
|
||
async function checkLoginStatus() {
|
||
if (!apiManagerReady) return;
|
||
|
||
try {
|
||
await apiManager.checkAuthStatus();
|
||
} catch (error) {
|
||
console.error('检查登录状态失败:', error);
|
||
}
|
||
}
|
||
|
||
// 加载用户数据
|
||
async function loadUserData() {
|
||
try {
|
||
// 加载通话记录
|
||
const callRecords = await apiManager.getCallRecords();
|
||
if (callRecords) {
|
||
billHistory = callRecords.map(record => ({
|
||
date: new Date(record.created_at).toLocaleString('zh-CN'),
|
||
type: record.call_type === 'voice' ? '语音通话' : '视频通话',
|
||
duration: Math.ceil(record.duration / 60), // 转换为分钟
|
||
amount: record.total_amount,
|
||
paid: record.status === 'completed',
|
||
hasTranslator: record.has_translator
|
||
}));
|
||
updateBillHistory();
|
||
}
|
||
|
||
// 加载预约记录
|
||
const appointments = await apiManager.getAppointments();
|
||
if (appointments) {
|
||
// 更新预约数据
|
||
console.log('预约记录:', appointments);
|
||
}
|
||
} catch (error) {
|
||
console.error('加载用户数据失败:', error);
|
||
}
|
||
}
|
||
|
||
// 登出处理
|
||
async function handleLogout() {
|
||
try {
|
||
const result = await apiManager.logout();
|
||
if (result.success) {
|
||
// 清空本地数据
|
||
billHistory = [];
|
||
updateBillHistory();
|
||
|
||
// 显示登录提示
|
||
alert('已成功登出');
|
||
} else {
|
||
alert('登出失败,请重试');
|
||
}
|
||
} catch (error) {
|
||
console.error('登出失败:', error);
|
||
alert('登出失败:' + error.message);
|
||
}
|
||
}
|
||
|
||
// 全局变量
|
||
let currentTab = 'call';
|
||
let isCallActive = false;
|
||
let callStartTime = null;
|
||
let callTimer = null;
|
||
let currentCallType = null;
|
||
let hasTranslator = false;
|
||
let currentBill = null;
|
||
|
||
// 日历相关变量
|
||
let currentDate = new Date();
|
||
let selectedDate = null;
|
||
|
||
// 通话费率配置
|
||
const callRates = {
|
||
voice: 80, // 语音通话 ¥80/小时
|
||
video: 120, // 视频通话 ¥120/小时
|
||
translator: 50 // 翻译员服务 ¥50/小时
|
||
};
|
||
|
||
// 账单历史数据
|
||
let billHistory = [
|
||
{
|
||
date: '2024-01-15 14:30',
|
||
type: '视频通话',
|
||
duration: 15,
|
||
amount: 32.50,
|
||
paid: true,
|
||
hasTranslator: true
|
||
},
|
||
{
|
||
date: '2024-01-14 10:15',
|
||
type: '语音通话',
|
||
duration: 8,
|
||
amount: 10.67,
|
||
paid: true,
|
||
hasTranslator: false
|
||
},
|
||
{
|
||
date: '2024-01-13 16:45',
|
||
type: '视频通话',
|
||
duration: 25,
|
||
amount: 50.00,
|
||
paid: false,
|
||
hasTranslator: false
|
||
}
|
||
];
|
||
|
||
// 预约数据
|
||
const appointments = {
|
||
'2024-01-15': [
|
||
{ time: '14:30', desc: '商务会议翻译 - 英语 | 翻译员:张译文' }
|
||
],
|
||
'2024-01-16': [
|
||
{ time: '10:00', desc: '技术文档翻译 - 日语 | 翻译员:待分配' }
|
||
],
|
||
'2024-01-14': [
|
||
{ time: '16:00', desc: '法律咨询翻译 - 法语 | 翻译员:李法兰' }
|
||
]
|
||
};
|
||
|
||
// 文档数据
|
||
let documentCount = 24;
|
||
|
||
// 标签页切换功能
|
||
function switchTab(tabName) {
|
||
// 隐藏所有标签页内容
|
||
document.querySelectorAll('.tab-content').forEach(tab => {
|
||
tab.classList.remove('active');
|
||
});
|
||
|
||
// 显示选中的标签页
|
||
document.getElementById(tabName).classList.add('active');
|
||
|
||
// 更新导航状态
|
||
document.querySelectorAll('.nav-item').forEach(nav => {
|
||
nav.classList.remove('active');
|
||
});
|
||
event.target.closest('.nav-item').classList.add('active');
|
||
|
||
currentTab = tabName;
|
||
|
||
// 如果切换到个人中心,更新账单历史
|
||
if (tabName === 'profile') {
|
||
updateBillHistory();
|
||
} else if (tabName === 'appointment') {
|
||
generateCalendar();
|
||
}
|
||
}
|
||
|
||
// 日历功能
|
||
function generateCalendar() {
|
||
const grid = document.getElementById('calendarGrid');
|
||
const monthDisplay = document.getElementById('currentMonth');
|
||
|
||
const year = currentDate.getFullYear();
|
||
const month = currentDate.getMonth();
|
||
|
||
monthDisplay.textContent = `${year}年${month + 1}月`;
|
||
|
||
const firstDay = new Date(year, month, 1);
|
||
const lastDay = new Date(year, month + 1, 0);
|
||
const startDate = new Date(firstDay);
|
||
startDate.setDate(startDate.getDate() - firstDay.getDay());
|
||
|
||
grid.innerHTML = '';
|
||
|
||
// 添加星期标题
|
||
const weekdays = ['日', '一', '二', '三', '四', '五', '六'];
|
||
weekdays.forEach(day => {
|
||
const dayHeader = document.createElement('div');
|
||
dayHeader.className = 'calendar-day-header';
|
||
dayHeader.textContent = day;
|
||
grid.appendChild(dayHeader);
|
||
});
|
||
|
||
// 生成日历天数
|
||
for (let i = 0; i < 42; i++) {
|
||
const date = new Date(startDate);
|
||
date.setDate(startDate.getDate() + i);
|
||
|
||
const dayElement = document.createElement('div');
|
||
dayElement.className = 'calendar-day';
|
||
dayElement.textContent = date.getDate();
|
||
|
||
const dateStr = date.toISOString().split('T')[0];
|
||
|
||
if (date.getMonth() !== month) {
|
||
dayElement.classList.add('other-month');
|
||
}
|
||
|
||
if (date.toDateString() === new Date().toDateString()) {
|
||
dayElement.classList.add('today');
|
||
}
|
||
|
||
if (appointments[dateStr]) {
|
||
dayElement.classList.add('has-appointment');
|
||
}
|
||
|
||
dayElement.onclick = () => selectDate(dateStr);
|
||
grid.appendChild(dayElement);
|
||
}
|
||
}
|
||
|
||
function previousMonth() {
|
||
currentDate.setMonth(currentDate.getMonth() - 1);
|
||
generateCalendar();
|
||
}
|
||
|
||
function nextMonth() {
|
||
currentDate.setMonth(currentDate.getMonth() + 1);
|
||
generateCalendar();
|
||
}
|
||
|
||
function selectDate(dateStr) {
|
||
selectedDate = dateStr;
|
||
const appointmentDetails = document.getElementById('appointmentDetails');
|
||
|
||
if (appointments[dateStr]) {
|
||
appointmentDetails.innerHTML = '';
|
||
appointments[dateStr].forEach(appointment => {
|
||
const item = document.createElement('div');
|
||
item.className = 'appointment-item';
|
||
item.innerHTML = `
|
||
<div class="appointment-time">${appointment.time}</div>
|
||
<div class="appointment-desc">${appointment.desc}</div>
|
||
`;
|
||
appointmentDetails.appendChild(item);
|
||
});
|
||
} else {
|
||
appointmentDetails.innerHTML = '<div style="text-align: center; color: #666; padding: 20px;">该日期暂无预约</div>';
|
||
}
|
||
}
|
||
|
||
// 文档导入功能
|
||
function handleFileImport(event) {
|
||
const files = event.target.files;
|
||
if (files.length === 0) return;
|
||
|
||
for (let file of files) {
|
||
addDocumentToList(file);
|
||
documentCount++;
|
||
}
|
||
|
||
// 更新文档统计
|
||
document.getElementById('totalDocs').textContent = documentCount;
|
||
|
||
// 显示成功提示
|
||
alert(`成功导入 ${files.length} 个文档!`);
|
||
|
||
// 清空文件输入
|
||
event.target.value = '';
|
||
}
|
||
|
||
function addDocumentToList(file) {
|
||
const documentList = document.getElementById('documentList');
|
||
|
||
const documentItem = document.createElement('div');
|
||
documentItem.className = 'document-item';
|
||
documentItem.innerHTML = `
|
||
<div class="document-item-header">
|
||
<div class="document-name">${file.name}</div>
|
||
<div class="document-progress progress-processing">待处理</div>
|
||
</div>
|
||
<div class="document-info">
|
||
文件大小:${(file.size / 1024).toFixed(1)} KB | 上传时间:${new Date().toLocaleString('zh-CN')}
|
||
</div>
|
||
`;
|
||
|
||
// 插入到列表开头
|
||
documentList.insertBefore(documentItem, documentList.firstChild);
|
||
}
|
||
|
||
// 通话相关功能
|
||
function selectCallType(type) {
|
||
currentCallType = type;
|
||
|
||
// 更新弹窗内容
|
||
const modalCallType = document.getElementById('modal-call-type');
|
||
const modalBaseRate = document.getElementById('modal-base-rate');
|
||
const modalTotalRate = document.getElementById('modal-total-rate');
|
||
const translatorCheckbox = document.getElementById('translator-checkbox');
|
||
|
||
modalCallType.textContent = type === 'voice' ? '语音通话' : '视频通话';
|
||
modalBaseRate.textContent = `¥${callRates[type]}/小时`;
|
||
|
||
// 重置翻译员选项
|
||
translatorCheckbox.checked = false;
|
||
hasTranslator = false;
|
||
modalTotalRate.textContent = `¥${callRates[type]}/小时`;
|
||
|
||
// 显示费率弹窗
|
||
document.getElementById('rate-modal').classList.remove('hidden');
|
||
}
|
||
|
||
function updateModalRate() {
|
||
const translatorCheckbox = document.getElementById('translator-checkbox');
|
||
const modalTotalRate = document.getElementById('modal-total-rate');
|
||
|
||
hasTranslator = translatorCheckbox.checked;
|
||
const baseRate = callRates[currentCallType];
|
||
const totalRate = baseRate + (hasTranslator ? callRates.translator : 0);
|
||
|
||
modalTotalRate.textContent = `¥${totalRate}/小时`;
|
||
}
|
||
|
||
function closeRateModal() {
|
||
document.getElementById('rate-modal').classList.add('hidden');
|
||
currentCallType = null;
|
||
hasTranslator = false;
|
||
}
|
||
|
||
function confirmAndStartCall() {
|
||
if (!currentCallType) return;
|
||
|
||
// 关闭弹窗
|
||
closeRateModal();
|
||
|
||
// 开始通话
|
||
startCall();
|
||
}
|
||
|
||
async function startCall() {
|
||
if (!currentCallType) return;
|
||
|
||
isCallActive = true;
|
||
callStartTime = new Date();
|
||
|
||
// 隐藏选择面板,显示通话界面
|
||
document.querySelector('.call-options').style.display = 'none';
|
||
document.querySelector('.call-interface').style.display = 'block';
|
||
document.querySelector('.end-call-btn').style.display = 'block';
|
||
|
||
// 更新通话界面信息
|
||
const callTypeDisplay = document.querySelector('.call-type-display');
|
||
callTypeDisplay.textContent = currentCallType === 'voice' ? '语音通话中' : '视频通话中';
|
||
|
||
// 开始计时器
|
||
startCallTimer();
|
||
}
|
||
|
||
function startCallTimer() {
|
||
callTimer = setInterval(() => {
|
||
if (callStartTime) {
|
||
const elapsed = Math.floor((new Date() - callStartTime) / 1000);
|
||
const minutes = Math.floor(elapsed / 60);
|
||
const seconds = elapsed % 60;
|
||
document.querySelector('.call-timer').textContent =
|
||
`${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`;
|
||
}
|
||
}, 1000);
|
||
}
|
||
|
||
async function endCall() {
|
||
if (!isCallActive) return;
|
||
|
||
isCallActive = false;
|
||
const callEndTime = new Date();
|
||
const callDuration = Math.ceil((callEndTime - callStartTime) / 1000 / 60); // 分钟,向上取整
|
||
|
||
// 停止计时器
|
||
if (callTimer) {
|
||
clearInterval(callTimer);
|
||
callTimer = null;
|
||
}
|
||
|
||
// 计算费用
|
||
const baseRate = callRates[currentCallType];
|
||
const translatorRate = hasTranslator ? callRates.translator : 0;
|
||
const totalRate = baseRate + translatorRate;
|
||
const amount = Math.max(callDuration * totalRate / 60, totalRate / 60); // 最低1分钟计费
|
||
|
||
// 创建账单
|
||
currentBill = {
|
||
date: new Date().toLocaleString('zh-CN'),
|
||
type: currentCallType === 'voice' ? '语音通话' : '视频通话',
|
||
duration: callDuration,
|
||
amount: parseFloat(amount.toFixed(2)),
|
||
paid: false,
|
||
hasTranslator: hasTranslator
|
||
};
|
||
|
||
// 如果用户已登录,保存通话记录到数据库
|
||
if (apiManagerReady && apiManager.currentUser) {
|
||
try {
|
||
const callData = {
|
||
type: currentCallType,
|
||
duration: callDuration * 60, // 转换为秒
|
||
hasTranslator: hasTranslator,
|
||
baseRate: baseRate,
|
||
translatorRate: translatorRate,
|
||
totalAmount: currentBill.amount,
|
||
status: 'completed'
|
||
};
|
||
|
||
await apiManager.createCallRecord(callData);
|
||
console.log('通话记录已保存到数据库');
|
||
} catch (error) {
|
||
console.error('保存通话记录失败:', error);
|
||
}
|
||
}
|
||
|
||
// 显示账单
|
||
showBillModal();
|
||
|
||
// 重置通话界面
|
||
document.querySelector('.call-interface').style.display = 'none';
|
||
document.querySelector('.end-call-btn').style.display = 'none';
|
||
document.querySelector('.call-options').style.display = 'flex';
|
||
document.querySelector('.call-timer').textContent = '00:00';
|
||
|
||
// 重置状态
|
||
callStartTime = null;
|
||
currentCallType = null;
|
||
hasTranslator = false;
|
||
}
|
||
|
||
function showBillModal() {
|
||
if (!currentBill) return;
|
||
|
||
const modal = document.querySelector('.bill-modal');
|
||
const callTypeSpan = document.getElementById('bill-call-type');
|
||
const durationSpan = document.getElementById('bill-duration');
|
||
const baseRateSpan = document.getElementById('bill-base-rate');
|
||
const translatorFeeItem = document.getElementById('translator-fee-item');
|
||
const translatorFeeSpan = document.getElementById('bill-translator-fee');
|
||
const totalAmountSpan = document.getElementById('bill-total-amount');
|
||
|
||
// 更新账单信息
|
||
callTypeSpan.textContent = currentBill.type;
|
||
const minutes = Math.floor(currentBill.duration);
|
||
const seconds = (currentBill.duration % 1) * 60;
|
||
durationSpan.textContent = `${minutes}分${Math.round(seconds)}秒`;
|
||
|
||
const baseRate = callRates[currentCallType === 'voice' ? 'voice' : 'video'];
|
||
const baseAmount = (baseRate * currentBill.duration / 60).toFixed(2);
|
||
baseRateSpan.textContent = `¥${baseAmount}`;
|
||
|
||
if (currentBill.hasTranslator) {
|
||
translatorFeeItem.style.display = 'block';
|
||
const translatorAmount = (callRates.translator * currentBill.duration / 60).toFixed(2);
|
||
translatorFeeSpan.textContent = `¥${translatorAmount}`;
|
||
} else {
|
||
translatorFeeItem.style.display = 'none';
|
||
}
|
||
|
||
totalAmountSpan.textContent = `¥${currentBill.amount}`;
|
||
|
||
// 显示模态框
|
||
modal.classList.remove('hidden');
|
||
}
|
||
|
||
function closeBill() {
|
||
document.querySelector('.bill-modal').classList.add('hidden');
|
||
if (currentBill) {
|
||
billHistory.unshift(currentBill);
|
||
updateBillHistory();
|
||
}
|
||
currentBill = null;
|
||
}
|
||
|
||
function payBill() {
|
||
if (currentBill) {
|
||
currentBill.paid = true;
|
||
billHistory.unshift(currentBill);
|
||
updateBillHistory();
|
||
alert('支付成功!');
|
||
}
|
||
document.querySelector('.bill-modal').classList.add('hidden');
|
||
currentBill = null;
|
||
}
|
||
|
||
function updateBillHistory() {
|
||
const billList = document.querySelector('.bill-list');
|
||
billList.innerHTML = '';
|
||
|
||
billHistory.forEach(bill => {
|
||
const billItem = document.createElement('div');
|
||
billItem.className = 'bill-item';
|
||
billItem.innerHTML = `
|
||
<div class="bill-info">
|
||
<div class="bill-date">${bill.date}</div>
|
||
<div class="bill-type">${bill.type}${bill.hasTranslator ? ' + 翻译员' : ''}</div>
|
||
</div>
|
||
<div class="bill-amount">¥${bill.amount}</div>
|
||
<div class="bill-status ${bill.paid ? 'paid' : 'unpaid'}">
|
||
${bill.paid ? '已支付' : '待支付'}
|
||
</div>
|
||
`;
|
||
billList.appendChild(billItem);
|
||
});
|
||
}
|
||
|
||
// 页面加载完成后初始化
|
||
document.addEventListener('DOMContentLoaded', async function() {
|
||
updateBillHistory();
|
||
generateCalendar();
|
||
|
||
// 初始化应用(包括数据库连接和用户状态检查)
|
||
await initApp();
|
||
});
|
||
</script>
|
||
</body>
|
||
</html> |