2025-06-29 21:45:58 +08:00
|
|
|
|
<!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">
|
2025-06-30 19:34:58 +08:00
|
|
|
|
|
|
|
|
|
<!-- 引入必要的脚本 -->
|
|
|
|
|
<script src="https://unpkg.com/@supabase/supabase-js@2"></script>
|
|
|
|
|
<script src="web-app/config.js"></script>
|
|
|
|
|
<script src="web-app/api.js"></script>
|
|
|
|
|
|
2025-06-29 21:45:58 +08:00
|
|
|
|
<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;
|
|
|
|
|
}
|
|
|
|
|
|
2025-06-30 19:34:58 +08:00
|
|
|
|
.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);
|
|
|
|
|
}
|
|
|
|
|
|
2025-06-29 21:45:58 +08:00
|
|
|
|
.header h1 {
|
|
|
|
|
font-size: 20px;
|
|
|
|
|
font-weight: 600;
|
2025-06-30 19:34:58 +08:00
|
|
|
|
margin: 0;
|
2025-06-29 21:45:58 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.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">
|
2025-06-30 19:34:58 +08:00
|
|
|
|
<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>
|
2025-06-29 21:45:58 +08:00
|
|
|
|
</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>
|
2025-06-30 19:34:58 +08:00
|
|
|
|
// 等待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);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-06-29 21:45:58 +08:00
|
|
|
|
// 全局变量
|
|
|
|
|
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();
|
|
|
|
|
}
|
|
|
|
|
|
2025-06-30 19:34:58 +08:00
|
|
|
|
async function startCall() {
|
2025-06-29 21:45:58 +08:00
|
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
|
2025-06-30 19:34:58 +08:00
|
|
|
|
async function endCall() {
|
2025-06-29 21:45:58 +08:00
|
|
|
|
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
|
|
|
|
|
};
|
2025-06-30 19:34:58 +08:00
|
|
|
|
|
|
|
|
|
// 如果用户已登录,保存通话记录到数据库
|
|
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-06-29 21:45:58 +08:00
|
|
|
|
|
|
|
|
|
// 显示账单
|
|
|
|
|
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);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 页面加载完成后初始化
|
2025-06-30 19:34:58 +08:00
|
|
|
|
document.addEventListener('DOMContentLoaded', async function() {
|
2025-06-29 21:45:58 +08:00
|
|
|
|
updateBillHistory();
|
|
|
|
|
generateCalendar();
|
2025-06-30 19:34:58 +08:00
|
|
|
|
|
|
|
|
|
// 初始化应用(包括数据库连接和用户状态检查)
|
|
|
|
|
await initApp();
|
2025-06-29 21:45:58 +08:00
|
|
|
|
});
|
|
|
|
|
</script>
|
|
|
|
|
</body>
|
|
|
|
|
</html>
|