Twilioapp-project/index.html
mars 0d57273021 添加数据库集成和用户认证功能
- 新增用户注册和登录系统 (login.html, register.html)
- 集成Supabase数据库连接 (config.js, api.js)
- 完善数据库架构设计 (database-schema.sql)
- 添加部署指南和配置文档 (DEPLOYMENT_GUIDE.md)
- 修复主页面结构和功能完善 (index.html)
- 支持通话记录保存到数据库
- 完整的账单管理和用户认证流程
- 集成OpenAI、Twilio、Stripe等API服务
2025-06-30 19:34:58 +08:00

1507 lines
49 KiB
HTML
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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