前后端第一版提交
This commit is contained in:
499
hertz_demo/templates/captcha_demo.html
Normal file
499
hertz_demo/templates/captcha_demo.html
Normal file
@@ -0,0 +1,499 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Hertz验证码演示 - Hertz Server Django</title>
|
||||
<style>
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||
line-height: 1.6;
|
||||
color: #333;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
min-height: 100vh;
|
||||
padding: 2rem;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.header {
|
||||
text-align: center;
|
||||
color: white;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.header h1 {
|
||||
font-size: 2.5rem;
|
||||
margin-bottom: 0.5rem;
|
||||
text-shadow: 2px 2px 4px rgba(0,0,0,0.3);
|
||||
}
|
||||
|
||||
.header p {
|
||||
font-size: 1.1rem;
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
.demo-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 2rem;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.demo-card {
|
||||
background: white;
|
||||
border-radius: 10px;
|
||||
box-shadow: 0 10px 30px rgba(0,0,0,0.1);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.demo-header {
|
||||
background: #667eea;
|
||||
color: white;
|
||||
padding: 1rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.demo-header h3 {
|
||||
font-size: 1.3rem;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.demo-content {
|
||||
padding: 1.5rem;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.demo-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.header h1 {
|
||||
font-size: 2rem;
|
||||
}
|
||||
|
||||
body {
|
||||
padding: 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
.form-group {
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.form-group label {
|
||||
display: block;
|
||||
margin-bottom: 0.5rem;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.form-group input {
|
||||
width: 100%;
|
||||
padding: 0.8rem;
|
||||
border: 2px solid #e1e5e9;
|
||||
border-radius: 8px;
|
||||
font-size: 1rem;
|
||||
transition: border-color 0.3s ease;
|
||||
}
|
||||
|
||||
.form-group input:focus {
|
||||
outline: none;
|
||||
border-color: #667eea;
|
||||
}
|
||||
|
||||
.captcha-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.captcha-image {
|
||||
border: 2px solid #e1e5e9;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
transition: border-color 0.3s ease;
|
||||
}
|
||||
|
||||
.captcha-image:hover {
|
||||
border-color: #667eea;
|
||||
}
|
||||
|
||||
.btn {
|
||||
padding: 0.8rem 1.5rem;
|
||||
border: none;
|
||||
border-radius: 5px;
|
||||
cursor: pointer;
|
||||
font-size: 1rem;
|
||||
font-weight: 500;
|
||||
transition: background 0.3s ease;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background: #667eea;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-primary:hover {
|
||||
background: #5a6fd8;
|
||||
}
|
||||
|
||||
.btn-success {
|
||||
background: #4CAF50;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-success:hover {
|
||||
background: #45a049;
|
||||
}
|
||||
|
||||
.btn:disabled {
|
||||
background: #ccc;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.refresh-btn {
|
||||
background: #667eea;
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 0.5rem 1rem;
|
||||
border-radius: 5px;
|
||||
cursor: pointer;
|
||||
font-size: 0.9rem;
|
||||
transition: background 0.3s ease;
|
||||
}
|
||||
|
||||
.refresh-btn:hover {
|
||||
background: #5a6fd8;
|
||||
}
|
||||
|
||||
.submit-btn {
|
||||
width: 100%;
|
||||
background: #667eea;
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 1rem;
|
||||
border-radius: 5px;
|
||||
font-size: 1.1rem;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: background 0.3s ease;
|
||||
}
|
||||
|
||||
.submit-btn:hover {
|
||||
background: #5a6fd8;
|
||||
}
|
||||
|
||||
.back-link {
|
||||
text-align: center;
|
||||
margin-top: 2rem;
|
||||
}
|
||||
|
||||
.back-link a {
|
||||
color: white;
|
||||
text-decoration: none;
|
||||
font-weight: 500;
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
.back-link a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.info-box {
|
||||
background: #f8f9fa;
|
||||
border-left: 4px solid #667eea;
|
||||
padding: 1rem;
|
||||
margin-bottom: 2rem;
|
||||
border-radius: 0 8px 8px 0;
|
||||
}
|
||||
|
||||
.info-box h3 {
|
||||
color: #667eea;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.info-box ul {
|
||||
margin-left: 1.5rem;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.info-box li {
|
||||
margin-bottom: 0.3rem;
|
||||
}
|
||||
|
||||
.info-box code {
|
||||
background: #e9ecef;
|
||||
padding: 0.2rem 0.4rem;
|
||||
border-radius: 4px;
|
||||
font-family: 'Courier New', monospace;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.captcha-types {
|
||||
background: #f8f9fa;
|
||||
border-left: 4px solid #28a745;
|
||||
padding: 1rem;
|
||||
margin-bottom: 2rem;
|
||||
border-radius: 0 8px 8px 0;
|
||||
}
|
||||
|
||||
.type-buttons {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
flex-wrap: wrap;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.type-btn {
|
||||
background: #e9ecef;
|
||||
color: #495057;
|
||||
border: 2px solid #dee2e6;
|
||||
padding: 0.5rem 1rem;
|
||||
border-radius: 5px;
|
||||
cursor: pointer;
|
||||
font-size: 0.9rem;
|
||||
transition: all 0.3s ease;
|
||||
flex: 1;
|
||||
min-width: 120px;
|
||||
}
|
||||
|
||||
.type-btn:hover {
|
||||
background: #dee2e6;
|
||||
border-color: #adb5bd;
|
||||
}
|
||||
|
||||
.type-btn.active {
|
||||
background: #28a745;
|
||||
color: white;
|
||||
border-color: #28a745;
|
||||
}
|
||||
|
||||
.demo-section {
|
||||
background: #fff3cd;
|
||||
border-left: 4px solid #ffc107;
|
||||
padding: 1rem;
|
||||
margin-bottom: 2rem;
|
||||
border-radius: 0 8px 8px 0;
|
||||
}
|
||||
|
||||
.demo-section h4 {
|
||||
color: #856404;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.demo-section p {
|
||||
color: #856404;
|
||||
margin-bottom: 0.3rem;
|
||||
}
|
||||
|
||||
|
||||
|
||||
.success-message {
|
||||
background: #d4edda;
|
||||
color: #155724;
|
||||
border: 1px solid #c3e6cb;
|
||||
padding: 1rem;
|
||||
border-radius: 8px;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="header">
|
||||
<h1>🔐 Hertz验证码演示</h1>
|
||||
<p>{{ demo_description }}</p>
|
||||
</div>
|
||||
|
||||
<div class="demo-grid">
|
||||
<!-- 验证码功能介绍 -->
|
||||
<div class="demo-card">
|
||||
<div class="demo-header">
|
||||
<h3>🎯 Hertz验证码功能特性</h3>
|
||||
<p>自定义验证码系统,支持Redis缓存</p>
|
||||
</div>
|
||||
<div class="demo-content">
|
||||
<div class="info-box">
|
||||
<ul>
|
||||
<li>🔤 随机字符验证码 - 生成随机字母数字组合</li>
|
||||
<li>🎨 自定义样式配置 - 支持颜色、字体、噪声等设置</li>
|
||||
<li>⚡ Ajax刷新功能 - 无需刷新页面</li>
|
||||
<li>💾 Redis缓存 - 高性能数据存储</li>
|
||||
<li>⏰ 超时自动失效 - 可配置过期时间</li>
|
||||
<li>🔧 灵活配置 - 通过settings.py进行配置</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="captcha-types">
|
||||
<h3 style="margin-bottom: 1rem; color: #667eea;">配置信息</h3>
|
||||
<p style="color: #666; font-size: 0.9rem; margin-bottom: 0.5rem;">• 验证码长度: 可通过HERTZ_CAPTCHA_LENGTH配置</p>
|
||||
<p style="color: #666; font-size: 0.9rem; margin-bottom: 0.5rem;">• 图片尺寸: 可通过HERTZ_CAPTCHA_WIDTH/HEIGHT配置</p>
|
||||
<p style="color: #666; font-size: 0.9rem; margin-bottom: 0.5rem;">• 过期时间: 可通过HERTZ_CAPTCHA_TIMEOUT配置</p>
|
||||
<p style="color: #666; font-size: 0.9rem;">• Redis前缀: 可通过HERTZ_CAPTCHA_REDIS_KEY_PREFIX配置</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 验证码测试 -->
|
||||
<div class="demo-card">
|
||||
<div class="demo-header">
|
||||
<h3>🔒 Hertz验证码测试</h3>
|
||||
<p>输入验证码进行功能测试</p>
|
||||
</div>
|
||||
<div class="demo-content">
|
||||
{% if messages %}
|
||||
{% for message in messages %}
|
||||
<div class="success-message" style="{% if message.tags == 'error' %}background: #f8d7da; color: #721c24; border-color: #f5c6cb;{% endif %}">
|
||||
{% if message.tags == 'success' %}✅{% elif message.tags == 'error' %}❌{% endif %} {{ message }}
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
|
||||
<div class="demo-section">
|
||||
<h4>📋 Hertz验证码说明</h4>
|
||||
<p>• 随机字符验证码:生成随机字母和数字组合</p>
|
||||
<p>• 特点:自定义样式,支持噪声干扰,Redis缓存存储</p>
|
||||
<p>• 功能:支持Ajax刷新,自动过期失效</p>
|
||||
</div>
|
||||
|
||||
<form method="post" id="captcha-form">
|
||||
{% csrf_token %}
|
||||
|
||||
<div class="form-group">
|
||||
<label for="captcha_input">验证码:</label>
|
||||
<div class="captcha-container">
|
||||
<img id="captcha-image" src="{{ initial_captcha.image_data }}"
|
||||
alt="验证码" class="captcha-image" onclick="refreshCaptcha()"
|
||||
style="cursor: pointer; border: 2px solid #e1e5e9; border-radius: 8px;">
|
||||
<button type="button" class="refresh-btn" onclick="refreshCaptcha()">🔄 刷新</button>
|
||||
</div>
|
||||
<input type="text" id="captcha_input" name="captcha_input"
|
||||
placeholder="请输入验证码" required
|
||||
style="width: 100%; padding: 0.8rem; border: 2px solid #e1e5e9; border-radius: 8px; font-size: 1rem; margin-top: 0.5rem;">
|
||||
<input type="hidden" id="captcha_id" name="captcha_id" value="{{ initial_captcha.captcha_id }}">
|
||||
</div>
|
||||
|
||||
<button type="submit" class="submit-btn">验证提交</button>
|
||||
</form>
|
||||
|
||||
<div style="margin-top: 1rem; padding: 1rem; background: #e7f3ff; border-radius: 8px; border-left: 4px solid #007bff;">
|
||||
<h4 style="color: #007bff; margin-bottom: 0.5rem;">💡 使用提示</h4>
|
||||
<p style="color: #004085; margin-bottom: 0.3rem;">• 点击验证码图片可以刷新</p>
|
||||
<p style="color: #004085; margin-bottom: 0.3rem;">• 验证码不区分大小写</p>
|
||||
<p style="color: #004085;">• 验证码有效期为5分钟</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="back-link">
|
||||
<a href="/" style="color: white; text-decoration: none; font-weight: 500; font-size: 1.1rem;">← 返回首页</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
let currentCaptchaId = '{{ initial_captcha.captcha_id }}';
|
||||
|
||||
function refreshCaptcha() {
|
||||
fetch(window.location.href, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-Requested-With': 'XMLHttpRequest',
|
||||
'X-CSRFToken': document.querySelector('[name=csrfmiddlewaretoken]').value
|
||||
},
|
||||
body: JSON.stringify({
|
||||
action: 'refresh',
|
||||
captcha_id: currentCaptchaId
|
||||
})
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
document.getElementById('captcha-image').src = data.data.image_data;
|
||||
document.getElementById('captcha_id').value = data.data.captcha_id;
|
||||
document.getElementById('captcha_input').value = '';
|
||||
currentCaptchaId = data.data.captcha_id;
|
||||
} else {
|
||||
console.error('刷新验证码失败:', data.error);
|
||||
alert('刷新验证码失败,请重试');
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('刷新验证码失败:', error);
|
||||
alert('刷新验证码失败,请重试');
|
||||
});
|
||||
}
|
||||
|
||||
// 表单提交处理
|
||||
const captchaForm = document.getElementById('captcha-form');
|
||||
if (captchaForm) {
|
||||
captchaForm.addEventListener('submit', function(e) {
|
||||
e.preventDefault();
|
||||
|
||||
const captchaId = document.getElementById('captcha_id').value;
|
||||
const userInput = document.getElementById('captcha_input').value;
|
||||
|
||||
if (!userInput.trim()) {
|
||||
alert('请输入验证码');
|
||||
return;
|
||||
}
|
||||
|
||||
fetch(window.location.href, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-Requested-With': 'XMLHttpRequest',
|
||||
'X-CSRFToken': document.querySelector('[name=csrfmiddlewaretoken]').value
|
||||
},
|
||||
body: JSON.stringify({
|
||||
action: 'verify',
|
||||
captcha_id: captchaId,
|
||||
user_input: userInput
|
||||
})
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
if (data.valid) {
|
||||
alert('✅ ' + data.message);
|
||||
document.getElementById('captcha_input').value = '';
|
||||
refreshCaptcha();
|
||||
} else {
|
||||
alert('❌ ' + data.message);
|
||||
document.getElementById('captcha_input').value = '';
|
||||
refreshCaptcha();
|
||||
}
|
||||
} else {
|
||||
alert('❌ 验证失败: ' + (data.error || '未知错误'));
|
||||
refreshCaptcha();
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('提交失败:', error);
|
||||
alert('❌ 提交失败,请重试');
|
||||
refreshCaptcha();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// 回车键提交
|
||||
document.getElementById('captcha_input').addEventListener('keypress', function(e) {
|
||||
if (e.key === 'Enter') {
|
||||
captchaForm.dispatchEvent(new Event('submit'));
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
520
hertz_demo/templates/email_demo.html
Normal file
520
hertz_demo/templates/email_demo.html
Normal file
@@ -0,0 +1,520 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>邮件系统演示 - Hertz Server Django</title>
|
||||
<style>
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||
line-height: 1.6;
|
||||
color: #333;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
min-height: 100vh;
|
||||
padding: 2rem;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 1000px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.header {
|
||||
text-align: center;
|
||||
color: white;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.header h1 {
|
||||
font-size: 2.5rem;
|
||||
margin-bottom: 0.5rem;
|
||||
text-shadow: 2px 2px 4px rgba(0,0,0,0.3);
|
||||
}
|
||||
|
||||
.header p {
|
||||
font-size: 1.1rem;
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
.back-link {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.back-link a {
|
||||
color: white;
|
||||
text-decoration: none;
|
||||
font-size: 1.1rem;
|
||||
opacity: 0.9;
|
||||
transition: opacity 0.3s ease;
|
||||
}
|
||||
|
||||
.back-link a:hover {
|
||||
opacity: 1;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.demo-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 2rem;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.demo-card {
|
||||
background: white;
|
||||
padding: 2rem;
|
||||
border-radius: 10px;
|
||||
box-shadow: 0 10px 30px rgba(0,0,0,0.1);
|
||||
transition: transform 0.3s ease, box-shadow 0.3s ease;
|
||||
}
|
||||
|
||||
.demo-card:hover {
|
||||
transform: translateY(-5px);
|
||||
box-shadow: 0 15px 40px rgba(0,0,0,0.15);
|
||||
}
|
||||
|
||||
.demo-card h3 {
|
||||
color: #667eea;
|
||||
margin-bottom: 1rem;
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
.demo-card p {
|
||||
color: #666;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.form-group {
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.form-group label {
|
||||
display: block;
|
||||
margin-bottom: 0.5rem;
|
||||
font-weight: 500;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.form-group input,
|
||||
.form-group textarea,
|
||||
.form-group select {
|
||||
width: 100%;
|
||||
padding: 0.8rem;
|
||||
border: 2px solid #e1e5e9;
|
||||
border-radius: 5px;
|
||||
font-size: 1rem;
|
||||
transition: border-color 0.3s ease;
|
||||
}
|
||||
|
||||
.form-group input:focus,
|
||||
.form-group textarea:focus,
|
||||
.form-group select:focus {
|
||||
outline: none;
|
||||
border-color: #667eea;
|
||||
}
|
||||
|
||||
.form-group textarea {
|
||||
resize: vertical;
|
||||
min-height: 120px;
|
||||
}
|
||||
|
||||
.btn {
|
||||
display: inline-block;
|
||||
padding: 0.8rem 1.5rem;
|
||||
border: none;
|
||||
border-radius: 5px;
|
||||
font-size: 1rem;
|
||||
font-weight: 500;
|
||||
text-decoration: none;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
margin-right: 0.5rem;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background: #667eea;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-primary:hover {
|
||||
background: #5a6fd8;
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.btn-success {
|
||||
background: #28a745;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-success:hover {
|
||||
background: #218838;
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.btn:disabled {
|
||||
background: #6c757d;
|
||||
cursor: not-allowed;
|
||||
transform: none;
|
||||
}
|
||||
|
||||
.alert {
|
||||
padding: 1rem;
|
||||
border-radius: 5px;
|
||||
margin-bottom: 1rem;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.alert-success {
|
||||
background: #d4edda;
|
||||
color: #155724;
|
||||
border: 1px solid #c3e6cb;
|
||||
}
|
||||
|
||||
.alert-error {
|
||||
background: #f8d7da;
|
||||
color: #721c24;
|
||||
border: 1px solid #f5c6cb;
|
||||
}
|
||||
|
||||
.alert-info {
|
||||
background: #d1ecf1;
|
||||
color: #0c5460;
|
||||
border: 1px solid #bee5eb;
|
||||
}
|
||||
|
||||
.email-types {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
margin-bottom: 1rem;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.type-btn {
|
||||
padding: 0.5rem 1rem;
|
||||
border: 2px solid #667eea;
|
||||
background: white;
|
||||
color: #667eea;
|
||||
border-radius: 5px;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.type-btn.active,
|
||||
.type-btn:hover {
|
||||
background: #667eea;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.email-preview {
|
||||
background: #f8f9fa;
|
||||
border: 1px solid #e9ecef;
|
||||
border-radius: 5px;
|
||||
padding: 1rem;
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.email-preview h4 {
|
||||
color: #495057;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.email-preview .preview-content {
|
||||
background: white;
|
||||
padding: 1rem;
|
||||
border-radius: 3px;
|
||||
border-left: 4px solid #667eea;
|
||||
}
|
||||
|
||||
.loading {
|
||||
display: none;
|
||||
text-align: center;
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.loading.show {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.spinner {
|
||||
border: 3px solid #f3f3f3;
|
||||
border-top: 3px solid #667eea;
|
||||
border-radius: 50%;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
animation: spin 1s linear infinite;
|
||||
margin: 0 auto 0.5rem;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.demo-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.header h1 {
|
||||
font-size: 2rem;
|
||||
}
|
||||
|
||||
body {
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.email-types {
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="back-link">
|
||||
<a href="/">← 返回首页</a>
|
||||
</div>
|
||||
|
||||
<div class="header">
|
||||
<h1>📧 邮件系统演示</h1>
|
||||
<p>体验Django邮件发送功能,支持多种邮件类型和模板</p>
|
||||
</div>
|
||||
|
||||
<div class="demo-grid">
|
||||
<div class="demo-card">
|
||||
<h3>📮 邮件发送功能</h3>
|
||||
<p>本演示展示了Django邮件系统的核心功能:</p>
|
||||
<ul style="color: #666; margin-left: 1.5rem; margin-bottom: 1rem;">
|
||||
<li>支持HTML和纯文本邮件</li>
|
||||
<li>多种邮件模板类型</li>
|
||||
<li>SMTP配置和发送状态</li>
|
||||
<li>邮件预览和验证</li>
|
||||
</ul>
|
||||
|
||||
<div class="email-types">
|
||||
<button class="type-btn active" data-type="welcome">欢迎邮件</button>
|
||||
<button class="type-btn" data-type="notification">通知邮件</button>
|
||||
<button class="type-btn" data-type="verification">验证邮件</button>
|
||||
<button class="type-btn" data-type="custom">自定义邮件</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="demo-card">
|
||||
<h3>✉️ 发送邮件测试</h3>
|
||||
|
||||
<div id="message-area"></div>
|
||||
|
||||
<form id="email-form" method="post">
|
||||
{% csrf_token %}
|
||||
<input type="hidden" id="email-type" name="email_type" value="welcome">
|
||||
|
||||
<div class="form-group">
|
||||
<label for="recipient_email">收件人邮箱 *</label>
|
||||
<input type="email" id="recipient_email" name="recipient_email" required
|
||||
placeholder="请输入收件人邮箱地址">
|
||||
</div>
|
||||
|
||||
<div class="form-group" id="subject-group">
|
||||
<label for="subject">邮件主题</label>
|
||||
<input type="text" id="subject" name="subject"
|
||||
placeholder="邮件主题将根据类型自动生成">
|
||||
</div>
|
||||
|
||||
<div class="form-group" id="message-group" style="display: none;">
|
||||
<label for="message">邮件内容</label>
|
||||
<textarea id="message" name="message"
|
||||
placeholder="请输入自定义邮件内容"></textarea>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="recipient_name">收件人姓名</label>
|
||||
<input type="text" id="recipient_name" name="recipient_name"
|
||||
placeholder="收件人姓名(可选)">
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn btn-primary" id="send-btn">
|
||||
📤 发送邮件
|
||||
</button>
|
||||
<button type="button" class="btn btn-success" id="preview-btn">
|
||||
👁️ 预览邮件
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<div class="loading" id="loading">
|
||||
<div class="spinner"></div>
|
||||
<p>正在发送邮件,请稍候...</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="demo-card" id="preview-card" style="display: none;">
|
||||
<h3>📋 邮件预览</h3>
|
||||
<div class="email-preview">
|
||||
<h4>邮件内容预览:</h4>
|
||||
<div class="preview-content" id="preview-content">
|
||||
<!-- 预览内容将在这里显示 -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// 邮件类型切换
|
||||
document.querySelectorAll('.type-btn').forEach(btn => {
|
||||
btn.addEventListener('click', function() {
|
||||
// 移除所有active类
|
||||
document.querySelectorAll('.type-btn').forEach(b => b.classList.remove('active'));
|
||||
// 添加active类到当前按钮
|
||||
this.classList.add('active');
|
||||
|
||||
const type = this.dataset.type;
|
||||
document.getElementById('email-type').value = type;
|
||||
|
||||
// 根据类型显示/隐藏字段
|
||||
const subjectGroup = document.getElementById('subject-group');
|
||||
const messageGroup = document.getElementById('message-group');
|
||||
const subjectInput = document.getElementById('subject');
|
||||
|
||||
if (type === 'custom') {
|
||||
messageGroup.style.display = 'block';
|
||||
subjectInput.placeholder = '请输入邮件主题';
|
||||
subjectInput.required = true;
|
||||
} else {
|
||||
messageGroup.style.display = 'none';
|
||||
subjectInput.placeholder = '邮件主题将根据类型自动生成';
|
||||
subjectInput.required = false;
|
||||
}
|
||||
|
||||
// 更新预览
|
||||
updatePreview();
|
||||
});
|
||||
});
|
||||
|
||||
// 表单提交
|
||||
document.getElementById('email-form').addEventListener('submit', function(e) {
|
||||
e.preventDefault();
|
||||
|
||||
const formData = new FormData(this);
|
||||
const sendBtn = document.getElementById('send-btn');
|
||||
const loading = document.getElementById('loading');
|
||||
const messageArea = document.getElementById('message-area');
|
||||
|
||||
// 显示加载状态
|
||||
sendBtn.disabled = true;
|
||||
loading.classList.add('show');
|
||||
messageArea.innerHTML = '';
|
||||
|
||||
fetch('/demo/email/', {
|
||||
method: 'POST',
|
||||
body: formData,
|
||||
headers: {
|
||||
'X-Requested-With': 'XMLHttpRequest'
|
||||
}
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
loading.classList.remove('show');
|
||||
sendBtn.disabled = false;
|
||||
|
||||
if (data.success) {
|
||||
messageArea.innerHTML = `
|
||||
<div class="alert alert-success">
|
||||
✅ ${data.message}
|
||||
</div>
|
||||
`;
|
||||
} else {
|
||||
messageArea.innerHTML = `
|
||||
<div class="alert alert-error">
|
||||
❌ ${data.message}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
loading.classList.remove('show');
|
||||
sendBtn.disabled = false;
|
||||
messageArea.innerHTML = `
|
||||
<div class="alert alert-error">
|
||||
❌ 发送失败:网络错误
|
||||
</div>
|
||||
`;
|
||||
});
|
||||
});
|
||||
|
||||
// 预览功能
|
||||
document.getElementById('preview-btn').addEventListener('click', function() {
|
||||
updatePreview();
|
||||
const previewCard = document.getElementById('preview-card');
|
||||
previewCard.style.display = previewCard.style.display === 'none' ? 'block' : 'none';
|
||||
});
|
||||
|
||||
function updatePreview() {
|
||||
const type = document.getElementById('email-type').value;
|
||||
const recipientName = document.getElementById('recipient_name').value || '用户';
|
||||
const subject = document.getElementById('subject').value;
|
||||
const message = document.getElementById('message').value;
|
||||
|
||||
let previewContent = '';
|
||||
|
||||
switch(type) {
|
||||
case 'welcome':
|
||||
previewContent = `
|
||||
<h4>🎉 欢迎加入我们!</h4>
|
||||
<p>亲爱的 ${recipientName},</p>
|
||||
<p>欢迎您注册成为我们的用户!我们很高兴您能加入我们的大家庭。</p>
|
||||
<p>在这里,您可以享受到优质的服务和丰富的功能。如果您有任何问题,请随时联系我们。</p>
|
||||
<p>祝您使用愉快!</p>
|
||||
<p>此致<br>Hertz Server Django 团队</p>
|
||||
`;
|
||||
break;
|
||||
case 'notification':
|
||||
previewContent = `
|
||||
<h4>🔔 系统通知</h4>
|
||||
<p>亲爱的 ${recipientName},</p>
|
||||
<p>您有一条新的系统通知:</p>
|
||||
<div style="background: #f8f9fa; padding: 1rem; border-left: 4px solid #007bff; margin: 1rem 0;">
|
||||
<p>您的账户设置已更新,如果这不是您的操作,请立即联系我们。</p>
|
||||
</div>
|
||||
<p>如有疑问,请联系客服。</p>
|
||||
<p>此致<br>Hertz Server Django 团队</p>
|
||||
`;
|
||||
break;
|
||||
case 'verification':
|
||||
previewContent = `
|
||||
<h4>🔐 邮箱验证</h4>
|
||||
<p>亲爱的 ${recipientName},</p>
|
||||
<p>请点击下面的链接验证您的邮箱地址:</p>
|
||||
<div style="text-align: center; margin: 2rem 0;">
|
||||
<a href="#" style="background: #667eea; color: white; padding: 1rem 2rem; text-decoration: none; border-radius: 5px;">验证邮箱</a>
|
||||
</div>
|
||||
<p>如果您没有注册账户,请忽略此邮件。</p>
|
||||
<p>此致<br>Hertz Server Django 团队</p>
|
||||
`;
|
||||
break;
|
||||
case 'custom':
|
||||
previewContent = `
|
||||
<h4>${subject || '自定义邮件'}</h4>
|
||||
<p>亲爱的 ${recipientName},</p>
|
||||
<div style="margin: 1rem 0;">
|
||||
${message ? message.replace(/\n/g, '<br>') : '请输入邮件内容'}
|
||||
</div>
|
||||
<p>此致<br>Hertz Server Django 团队</p>
|
||||
`;
|
||||
break;
|
||||
}
|
||||
|
||||
document.getElementById('preview-content').innerHTML = previewContent;
|
||||
}
|
||||
|
||||
// 初始化预览
|
||||
updatePreview();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
556
hertz_demo/templates/websocket_demo.html
Normal file
556
hertz_demo/templates/websocket_demo.html
Normal file
@@ -0,0 +1,556 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>WebSocket演示 - Hertz Server Django</title>
|
||||
<style>
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||
line-height: 1.6;
|
||||
color: #333;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
min-height: 100vh;
|
||||
padding: 2rem;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.header {
|
||||
text-align: center;
|
||||
color: white;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.header h1 {
|
||||
font-size: 2.5rem;
|
||||
margin-bottom: 0.5rem;
|
||||
text-shadow: 2px 2px 4px rgba(0,0,0,0.3);
|
||||
}
|
||||
|
||||
.header p {
|
||||
font-size: 1.1rem;
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
.demo-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 2rem;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.demo-card {
|
||||
background: white;
|
||||
border-radius: 10px;
|
||||
box-shadow: 0 10px 30px rgba(0,0,0,0.1);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.demo-header {
|
||||
background: #667eea;
|
||||
color: white;
|
||||
padding: 1rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.demo-header h3 {
|
||||
font-size: 1.3rem;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.demo-content {
|
||||
padding: 1.5rem;
|
||||
}
|
||||
|
||||
.connection-status {
|
||||
display: inline-block;
|
||||
padding: 0.3rem 0.8rem;
|
||||
border-radius: 20px;
|
||||
font-size: 0.8rem;
|
||||
font-weight: bold;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.status-disconnected {
|
||||
background: #f44336;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.status-connecting {
|
||||
background: #ff9800;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.status-connected {
|
||||
background: #4CAF50;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.message-area {
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 5px;
|
||||
height: 300px;
|
||||
overflow-y: auto;
|
||||
padding: 1rem;
|
||||
margin-bottom: 1rem;
|
||||
background: #f9f9f9;
|
||||
font-family: monospace;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.message {
|
||||
margin-bottom: 0.5rem;
|
||||
padding: 0.3rem;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.message-system {
|
||||
background: #e3f2fd;
|
||||
color: #1976d2;
|
||||
}
|
||||
|
||||
.message-user {
|
||||
background: #e8f5e8;
|
||||
color: #2e7d32;
|
||||
}
|
||||
|
||||
.message-echo {
|
||||
background: #fff3e0;
|
||||
color: #f57c00;
|
||||
}
|
||||
|
||||
.message-error {
|
||||
background: #ffebee;
|
||||
color: #c62828;
|
||||
}
|
||||
|
||||
.message-notification {
|
||||
background: #f3e5f5;
|
||||
color: #7b1fa2;
|
||||
}
|
||||
|
||||
.input-group {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.input-field {
|
||||
flex: 1;
|
||||
padding: 0.8rem;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 5px;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.btn {
|
||||
padding: 0.8rem 1.5rem;
|
||||
border: none;
|
||||
border-radius: 5px;
|
||||
cursor: pointer;
|
||||
font-size: 1rem;
|
||||
font-weight: 500;
|
||||
transition: background 0.3s ease;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background: #667eea;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-primary:hover {
|
||||
background: #5a6fd8;
|
||||
}
|
||||
|
||||
.btn-success {
|
||||
background: #4CAF50;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-success:hover {
|
||||
background: #45a049;
|
||||
}
|
||||
|
||||
.btn-danger {
|
||||
background: #f44336;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-danger:hover {
|
||||
background: #da190b;
|
||||
}
|
||||
|
||||
.btn:disabled {
|
||||
background: #ccc;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.chat-controls {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.chat-controls input {
|
||||
margin-right: 0.5rem;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.back-link {
|
||||
text-align: center;
|
||||
margin-top: 2rem;
|
||||
}
|
||||
|
||||
.back-link a {
|
||||
color: white;
|
||||
text-decoration: none;
|
||||
font-weight: 500;
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
.back-link a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.demo-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.header h1 {
|
||||
font-size: 2rem;
|
||||
}
|
||||
|
||||
body {
|
||||
padding: 1rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="header">
|
||||
<h1>🌐 WebSocket演示</h1>
|
||||
<p>实时通信功能展示 - 支持聊天室和回声测试</p>
|
||||
</div>
|
||||
|
||||
<div class="demo-grid">
|
||||
<!-- 回声测试 -->
|
||||
<div class="demo-card">
|
||||
<div class="demo-header">
|
||||
<h3>🔄 回声测试</h3>
|
||||
<p>发送消息,服务器会回声返回</p>
|
||||
</div>
|
||||
<div class="demo-content">
|
||||
<div class="connection-status status-disconnected" id="echo-status">未连接</div>
|
||||
<div class="message-area" id="echo-messages"></div>
|
||||
<div class="input-group">
|
||||
<input type="text" class="input-field" id="echo-input" placeholder="输入消息..." disabled>
|
||||
<button class="btn btn-primary" id="echo-send" disabled>发送</button>
|
||||
</div>
|
||||
<div class="input-group">
|
||||
<button class="btn btn-success" id="echo-connect">连接</button>
|
||||
<button class="btn btn-danger" id="echo-disconnect" disabled>断开</button>
|
||||
<button class="btn btn-primary" id="echo-clear">清空</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 聊天室 -->
|
||||
<div class="demo-card">
|
||||
<div class="demo-header">
|
||||
<h3>💬 聊天室</h3>
|
||||
<p>多用户实时聊天功能</p>
|
||||
</div>
|
||||
<div class="demo-content">
|
||||
<div class="connection-status status-disconnected" id="chat-status">未连接</div>
|
||||
<div class="chat-controls">
|
||||
<input type="text" class="input-field" id="username" placeholder="用户名" value="用户" style="width: 120px;">
|
||||
<input type="text" class="input-field" id="room-name" placeholder="房间名" value="general" style="width: 120px;">
|
||||
<button class="btn btn-success" id="chat-connect">加入聊天室</button>
|
||||
<button class="btn btn-danger" id="chat-disconnect" disabled>离开聊天室</button>
|
||||
</div>
|
||||
<div class="message-area" id="chat-messages"></div>
|
||||
<div class="input-group">
|
||||
<input type="text" class="input-field" id="chat-input" placeholder="输入消息..." disabled>
|
||||
<button class="btn btn-primary" id="chat-send" disabled>发送</button>
|
||||
<button class="btn btn-primary" id="chat-clear">清空</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="back-link">
|
||||
<a href="/">← 返回首页</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// WebSocket连接管理
|
||||
let echoSocket = null;
|
||||
let chatSocket = null;
|
||||
|
||||
// DOM元素
|
||||
const echoStatus = document.getElementById('echo-status');
|
||||
const echoMessages = document.getElementById('echo-messages');
|
||||
const echoInput = document.getElementById('echo-input');
|
||||
const echoSendBtn = document.getElementById('echo-send');
|
||||
const echoConnectBtn = document.getElementById('echo-connect');
|
||||
const echoDisconnectBtn = document.getElementById('echo-disconnect');
|
||||
const echoClearBtn = document.getElementById('echo-clear');
|
||||
|
||||
const chatStatus = document.getElementById('chat-status');
|
||||
const chatMessages = document.getElementById('chat-messages');
|
||||
const chatInput = document.getElementById('chat-input');
|
||||
const chatSendBtn = document.getElementById('chat-send');
|
||||
const chatConnectBtn = document.getElementById('chat-connect');
|
||||
const chatDisconnectBtn = document.getElementById('chat-disconnect');
|
||||
const chatClearBtn = document.getElementById('chat-clear');
|
||||
const usernameInput = document.getElementById('username');
|
||||
const roomNameInput = document.getElementById('room-name');
|
||||
|
||||
// 工具函数
|
||||
function addMessage(container, message, type = 'system') {
|
||||
const messageDiv = document.createElement('div');
|
||||
messageDiv.className = `message message-${type}`;
|
||||
messageDiv.innerHTML = `<strong>[${message.timestamp}]</strong> ${message.message}`;
|
||||
container.appendChild(messageDiv);
|
||||
container.scrollTop = container.scrollHeight;
|
||||
}
|
||||
|
||||
function updateStatus(statusElement, status, text) {
|
||||
statusElement.className = `connection-status status-${status}`;
|
||||
statusElement.textContent = text;
|
||||
}
|
||||
|
||||
// 回声测试WebSocket
|
||||
function connectEcho() {
|
||||
updateStatus(echoStatus, 'connecting', '连接中...');
|
||||
|
||||
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
|
||||
const wsUrl = `${protocol}//${window.location.host}/ws/echo/`;
|
||||
|
||||
echoSocket = new WebSocket(wsUrl);
|
||||
|
||||
echoSocket.onopen = function(e) {
|
||||
updateStatus(echoStatus, 'connected', '已连接');
|
||||
echoInput.disabled = false;
|
||||
echoSendBtn.disabled = false;
|
||||
echoConnectBtn.disabled = true;
|
||||
echoDisconnectBtn.disabled = false;
|
||||
};
|
||||
|
||||
echoSocket.onmessage = function(e) {
|
||||
const data = JSON.parse(e.data);
|
||||
console.log('收到WebSocket消息:', data); // 调试信息
|
||||
let messageType = 'system';
|
||||
let displayMessage = data;
|
||||
|
||||
if (data.type === 'echo_message') {
|
||||
messageType = 'echo';
|
||||
// 解码Unicode字符并创建显示用的消息对象
|
||||
let echoText = data.echo_message;
|
||||
if (typeof echoText === 'string') {
|
||||
// 处理可能的Unicode编码
|
||||
try {
|
||||
echoText = decodeURIComponent(escape(echoText));
|
||||
} catch (e) {
|
||||
// 如果解码失败,使用原始文本
|
||||
console.log('Unicode解码失败,使用原始文本');
|
||||
}
|
||||
}
|
||||
displayMessage = {
|
||||
message: echoText || data.echo_message || '回声消息',
|
||||
timestamp: data.timestamp || new Date().toLocaleTimeString()
|
||||
};
|
||||
console.log('处理回声消息:', displayMessage); // 调试信息
|
||||
} else if (data.type === 'error') {
|
||||
messageType = 'error';
|
||||
displayMessage = {
|
||||
message: data.message || '发生错误',
|
||||
timestamp: data.timestamp || new Date().toLocaleTimeString()
|
||||
};
|
||||
} else {
|
||||
// 处理其他类型的消息
|
||||
displayMessage = {
|
||||
message: data.message || JSON.stringify(data),
|
||||
timestamp: data.timestamp || new Date().toLocaleTimeString()
|
||||
};
|
||||
}
|
||||
|
||||
addMessage(echoMessages, displayMessage, messageType);
|
||||
};
|
||||
|
||||
echoSocket.onclose = function(e) {
|
||||
updateStatus(echoStatus, 'disconnected', '已断开');
|
||||
echoInput.disabled = true;
|
||||
echoSendBtn.disabled = true;
|
||||
echoConnectBtn.disabled = false;
|
||||
echoDisconnectBtn.disabled = true;
|
||||
};
|
||||
|
||||
echoSocket.onerror = function(e) {
|
||||
updateStatus(echoStatus, 'disconnected', '连接错误');
|
||||
addMessage(echoMessages, {
|
||||
message: 'WebSocket连接发生错误',
|
||||
timestamp: new Date().toLocaleTimeString()
|
||||
}, 'error');
|
||||
};
|
||||
}
|
||||
|
||||
function disconnectEcho() {
|
||||
if (echoSocket) {
|
||||
echoSocket.close();
|
||||
}
|
||||
}
|
||||
|
||||
function sendEchoMessage() {
|
||||
const message = echoInput.value.trim();
|
||||
if (message && echoSocket && echoSocket.readyState === WebSocket.OPEN) {
|
||||
echoSocket.send(JSON.stringify({
|
||||
'message': message
|
||||
}));
|
||||
echoInput.value = '';
|
||||
}
|
||||
}
|
||||
|
||||
// 聊天室WebSocket
|
||||
function connectChat() {
|
||||
const username = usernameInput.value.trim() || '匿名用户';
|
||||
const roomName = roomNameInput.value.trim() || 'general';
|
||||
|
||||
updateStatus(chatStatus, 'connecting', '连接中...');
|
||||
|
||||
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
|
||||
const wsUrl = `${protocol}//${window.location.host}/ws/chat/${roomName}/`;
|
||||
|
||||
chatSocket = new WebSocket(wsUrl);
|
||||
|
||||
chatSocket.onopen = function(e) {
|
||||
updateStatus(chatStatus, 'connected', `已连接到房间: ${roomName}`);
|
||||
chatInput.disabled = false;
|
||||
chatSendBtn.disabled = false;
|
||||
chatConnectBtn.disabled = true;
|
||||
chatDisconnectBtn.disabled = false;
|
||||
usernameInput.disabled = true;
|
||||
roomNameInput.disabled = true;
|
||||
|
||||
// 发送加入通知
|
||||
chatSocket.send(JSON.stringify({
|
||||
'type': 'user_join',
|
||||
'username': username
|
||||
}));
|
||||
};
|
||||
|
||||
chatSocket.onmessage = function(e) {
|
||||
const data = JSON.parse(e.data);
|
||||
let messageType = 'system';
|
||||
|
||||
if (data.type === 'chat_message') {
|
||||
messageType = 'user';
|
||||
data.message = `${data.username}: ${data.message}`;
|
||||
} else if (data.type === 'user_notification') {
|
||||
messageType = 'notification';
|
||||
} else if (data.type === 'error') {
|
||||
messageType = 'error';
|
||||
}
|
||||
|
||||
addMessage(chatMessages, data, messageType);
|
||||
};
|
||||
|
||||
chatSocket.onclose = function(e) {
|
||||
updateStatus(chatStatus, 'disconnected', '已断开');
|
||||
chatInput.disabled = true;
|
||||
chatSendBtn.disabled = true;
|
||||
chatConnectBtn.disabled = false;
|
||||
chatDisconnectBtn.disabled = true;
|
||||
usernameInput.disabled = false;
|
||||
roomNameInput.disabled = false;
|
||||
};
|
||||
|
||||
chatSocket.onerror = function(e) {
|
||||
updateStatus(chatStatus, 'disconnected', '连接错误');
|
||||
addMessage(chatMessages, {
|
||||
message: 'WebSocket连接发生错误',
|
||||
timestamp: new Date().toLocaleTimeString()
|
||||
}, 'error');
|
||||
};
|
||||
}
|
||||
|
||||
function disconnectChat() {
|
||||
if (chatSocket) {
|
||||
const username = usernameInput.value.trim() || '匿名用户';
|
||||
|
||||
// 发送离开通知
|
||||
chatSocket.send(JSON.stringify({
|
||||
'type': 'user_leave',
|
||||
'username': username
|
||||
}));
|
||||
|
||||
setTimeout(() => {
|
||||
chatSocket.close();
|
||||
}, 100);
|
||||
}
|
||||
}
|
||||
|
||||
function sendChatMessage() {
|
||||
const message = chatInput.value.trim();
|
||||
const username = usernameInput.value.trim() || '匿名用户';
|
||||
|
||||
if (message && chatSocket && chatSocket.readyState === WebSocket.OPEN) {
|
||||
chatSocket.send(JSON.stringify({
|
||||
'type': 'chat_message',
|
||||
'message': message,
|
||||
'username': username
|
||||
}));
|
||||
chatInput.value = '';
|
||||
}
|
||||
}
|
||||
|
||||
// 事件监听器
|
||||
echoConnectBtn.addEventListener('click', connectEcho);
|
||||
echoDisconnectBtn.addEventListener('click', disconnectEcho);
|
||||
echoSendBtn.addEventListener('click', sendEchoMessage);
|
||||
echoClearBtn.addEventListener('click', () => {
|
||||
echoMessages.innerHTML = '';
|
||||
});
|
||||
|
||||
echoInput.addEventListener('keypress', function(e) {
|
||||
if (e.key === 'Enter') {
|
||||
sendEchoMessage();
|
||||
}
|
||||
});
|
||||
|
||||
chatConnectBtn.addEventListener('click', connectChat);
|
||||
chatDisconnectBtn.addEventListener('click', disconnectChat);
|
||||
chatSendBtn.addEventListener('click', sendChatMessage);
|
||||
chatClearBtn.addEventListener('click', () => {
|
||||
chatMessages.innerHTML = '';
|
||||
});
|
||||
|
||||
chatInput.addEventListener('keypress', function(e) {
|
||||
if (e.key === 'Enter') {
|
||||
sendChatMessage();
|
||||
}
|
||||
});
|
||||
|
||||
// 页面卸载时断开连接
|
||||
window.addEventListener('beforeunload', function() {
|
||||
if (echoSocket) {
|
||||
echoSocket.close();
|
||||
}
|
||||
if (chatSocket) {
|
||||
disconnectChat();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user