Skip to content

Commit a05781d

Browse files
committed
feature:增加主页管理
1 parent e96450e commit a05781d

File tree

10 files changed

+374
-167
lines changed

10 files changed

+374
-167
lines changed

db.sqlite3

20 KB
Binary file not shown.

jyy_slide_web/asgi.py

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,21 @@
1+
# jyy_slide_web/asgi.py
2+
13
import os
24
import django
35
from channels.routing import ProtocolTypeRouter, URLRouter
46
from channels.auth import AuthMiddlewareStack
5-
from django.core.asgi import get_asgi_application
6-
from slideapp import routing
77

88
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'jyy_slide_web.settings')
99
django.setup()
1010

11+
from slideapp.routing import websocket_urlpatterns
12+
from django.core.asgi import get_asgi_application
13+
1114
application = ProtocolTypeRouter({
12-
"http": get_asgi_application(),
13-
"websocket": AuthMiddlewareStack(
15+
'http': get_asgi_application(),
16+
'websocket': AuthMiddlewareStack(
1417
URLRouter(
15-
routing.websocket_urlpatterns
18+
websocket_urlpatterns
1619
)
1720
),
1821
})

slideapp/consumers.py

Lines changed: 48 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,67 @@
1+
# slideapp/consumers.py
2+
13
from channels.generic.websocket import AsyncWebsocketConsumer
24
import json
35
import tempfile
46
import os
57
import shutil
68
from .src.converter import converter
7-
from django.conf import settings # 导入 settings
9+
from django.conf import settings
10+
from .models import Slide
11+
from asgiref.sync import sync_to_async
812

913
class SlideConsumer(AsyncWebsocketConsumer):
1014
async def connect(self):
15+
# 获取 URL 中的 slide_id 参数
16+
self.slide_id = self.scope['url_route']['kwargs'].get('slide_id')
1117
await self.accept()
1218

1319
async def disconnect(self, close_code):
1420
pass
1521

1622
async def receive(self, text_data):
1723
data = json.loads(text_data)
18-
markdown_content = data['markdown']
24+
action = data.get('action')
25+
26+
if action == 'load':
27+
# 加载指定的 PPT 内容
28+
content = await self.get_slide_content()
29+
await self.send(text_data=json.dumps({
30+
'action': 'load',
31+
'content': content
32+
}))
33+
elif action == 'save':
34+
# 保存当前的 PPT 内容
35+
markdown_content = data['markdown']
36+
await self.save_slide_content(markdown_content)
37+
38+
# 继续处理转换和预览
39+
html_content = await self.convert_markdown_to_html(markdown_content)
40+
await self.send(text_data=json.dumps({
41+
'action': 'preview',
42+
'html': html_content
43+
}))
44+
elif action == 'preview':
45+
# 仅生成预览,不保存
46+
markdown_content = data['markdown']
47+
html_content = await self.convert_markdown_to_html(markdown_content)
48+
await self.send(text_data=json.dumps({
49+
'action': 'preview',
50+
'html': html_content
51+
}))
52+
53+
@sync_to_async
54+
def get_slide_content(self):
55+
slide = Slide.objects.get(id=self.slide_id)
56+
return slide.content
1957

58+
@sync_to_async
59+
def save_slide_content(self, content):
60+
slide = Slide.objects.get(id=self.slide_id)
61+
slide.content = content
62+
slide.save()
63+
64+
async def convert_markdown_to_html(self, markdown_content):
2065
# 创建临时目录
2166
with tempfile.TemporaryDirectory() as temp_dir:
2267
# 创建临时 Markdown 文件
@@ -46,7 +91,4 @@ async def receive(self, text_data):
4691
for filename in os.listdir(source_img_dir):
4792
shutil.copy(os.path.join(source_img_dir, filename), dest_img_dir)
4893

49-
# 发送 HTML 内容回前端
50-
await self.send(text_data=json.dumps({
51-
'html': html_content
52-
}))
94+
return html_content

slideapp/migrations/0001_initial.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
# Generated by Django 5.1 on 2024-09-29 06:45
2+
3+
from django.db import migrations, models
4+
5+
6+
class Migration(migrations.Migration):
7+
8+
initial = True
9+
10+
dependencies = [
11+
]
12+
13+
operations = [
14+
migrations.CreateModel(
15+
name='Slide',
16+
fields=[
17+
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
18+
('title', models.CharField(default='未命名幻灯片', max_length=200)),
19+
('content', models.TextField(blank=True)),
20+
('created_at', models.DateTimeField(auto_now_add=True)),
21+
('updated_at', models.DateTimeField(auto_now=True)),
22+
],
23+
),
24+
]

slideapp/models.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
# slideapp/models.py
2+
13
from django.db import models
24

3-
# Create your models here.
5+
class Slide(models.Model):
6+
title = models.CharField(max_length=200, default='未命名幻灯片')
7+
content = models.TextField(blank=True)
8+
created_at = models.DateTimeField(auto_now_add=True)
9+
updated_at = models.DateTimeField(auto_now=True)

slideapp/routing.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
1+
# slideapp/routing.py
2+
13
from django.urls import re_path
24
from . import consumers
35

46
websocket_urlpatterns = [
5-
re_path(r'^ws/slide/$', consumers.SlideConsumer.as_asgi()),
7+
re_path(r'ws/slide/(?P<slide_id>\d+)/$', consumers.SlideConsumer.as_asgi()),
68
]

slideapp/templates/edit_slide.html

Lines changed: 222 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,222 @@
1+
<!-- templates/edit_slide.html -->
2+
3+
<!DOCTYPE html>
4+
<html>
5+
<head>
6+
<title>{{ slide.title }}</title>
7+
<style>
8+
body {
9+
display: flex;
10+
height: 100vh;
11+
margin: 0;
12+
}
13+
14+
#editor {
15+
width: 50%;
16+
height: 100%;
17+
}
18+
19+
#preview {
20+
width: 50%;
21+
height: 100%;
22+
}
23+
24+
iframe {
25+
width: 100%;
26+
height: 100%;
27+
border: none;
28+
}
29+
30+
#toolbar {
31+
position: absolute;
32+
top: 10px;
33+
left: 10px;
34+
}
35+
36+
#toolbar button {
37+
margin-right: 10px;
38+
}
39+
</style>
40+
</head>
41+
<body>
42+
<!-- 工具栏,包含返回主页的按钮 -->
43+
<div id="toolbar">
44+
<button onclick="location.href='{% url 'index' %}'">返回主页</button>
45+
</div>
46+
47+
<textarea id="editor"></textarea>
48+
<div id="preview">
49+
<iframe id="preview-iframe"></iframe>
50+
</div>
51+
52+
<!-- 引入必要的 JavaScript 库,例如 jQuery -->
53+
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
54+
55+
<!-- 与 WebSocket 进行通信的脚本 -->
56+
<script>
57+
var slideId = {{ slide.id }};
58+
var socket = new WebSocket('ws://' + window.location.host + '/ws/slide/' + slideId + '/');
59+
var currentIndices = {h: 0, v: 0, f: undefined};
60+
var editor = document.getElementById('editor');
61+
62+
socket.onopen = function (e) {
63+
console.log('WebSocket 连接已打开');
64+
// 加载幻灯片内容
65+
socket.send(JSON.stringify({
66+
'action': 'load'
67+
}));
68+
};
69+
70+
socket.onmessage = function (e) {
71+
var data = JSON.parse(e.data);
72+
var action = data['action'];
73+
74+
if (action === 'load') {
75+
// 加载内容到编辑器
76+
editor.value = data['content'];
77+
// 初始加载时更新预览
78+
sendPreview();
79+
} else if (action === 'preview') {
80+
var html_content = data['html'];
81+
82+
var iframe = document.getElementById('preview-iframe');
83+
84+
// 获取当前幻灯片的索引
85+
if (iframe.contentWindow && iframe.contentWindow.Reveal && iframe.contentWindow.Reveal.getIndices) {
86+
currentIndices = iframe.contentWindow.Reveal.getIndices();
87+
}
88+
89+
// 更新预览 iframe 的内容
90+
iframe.contentDocument.open();
91+
iframe.contentDocument.write(html_content);
92+
iframe.contentDocument.close();
93+
94+
// 等待 iframe 加载完成后,恢复幻灯片位置
95+
iframe.onload = function () {
96+
function restoreSlide() {
97+
if (iframe.contentWindow.Reveal && iframe.contentWindow.Reveal.isReady()) {
98+
iframe.contentWindow.Reveal.slide(currentIndices.h, currentIndices.v, currentIndices.f);
99+
100+
// 当幻灯片变化时,更新 currentIndices
101+
iframe.contentWindow.Reveal.on('slidechanged', function (event) {
102+
currentIndices = iframe.contentWindow.Reveal.getIndices();
103+
});
104+
} else {
105+
setTimeout(restoreSlide, 50);
106+
}
107+
}
108+
109+
restoreSlide();
110+
};
111+
}
112+
};
113+
114+
socket.onclose = function (e) {
115+
console.log('WebSocket 连接已关闭');
116+
};
117+
118+
// 监听编辑器内容的变化,实时预览
119+
$('#editor').on('input', function () {
120+
sendPreview();
121+
});
122+
123+
function sendPreview() {
124+
var markdown = $('#editor').val();
125+
socket.send(JSON.stringify({
126+
'action': 'preview',
127+
'markdown': markdown
128+
}));
129+
}
130+
131+
// 自动保存功能,每分钟保存一次
132+
setInterval(function () {
133+
saveSlide();
134+
}, 60000); // 60000 毫秒,即 1 分钟
135+
136+
// 手动保存函数
137+
function saveSlide() {
138+
var markdown = $('#editor').val();
139+
socket.send(JSON.stringify({
140+
'action': 'save',
141+
'markdown': markdown
142+
}));
143+
console.log('幻灯片已保存');
144+
}
145+
146+
// 在页面关闭或刷新时,保存内容
147+
window.addEventListener('beforeunload', function (e) {
148+
saveSlide();
149+
});
150+
151+
// 图片上传的代码,与之前相同
152+
// 获取 CSRF token 的函数
153+
function getCookie(name) {
154+
// ...(保持不变)
155+
}
156+
var csrftoken = getCookie('csrftoken');
157+
158+
// 监听粘贴事件
159+
editor.addEventListener('paste', function (e) {
160+
var items = (e.clipboardData || e.originalEvent.clipboardData).items;
161+
for (var i = 0; i < items.length; i++) {
162+
if (items[i].type.indexOf('image') !== -1) {
163+
var file = items[i].getAsFile();
164+
uploadImage(file);
165+
e.preventDefault();
166+
}
167+
}
168+
});
169+
170+
// 监听拖放事件
171+
editor.addEventListener('drop', function (e) {
172+
e.preventDefault();
173+
var files = e.dataTransfer.files;
174+
for (var i = 0; i < files.length; i++) {
175+
if (files[i].type.indexOf('image') !== -1) {
176+
uploadImage(files[i]);
177+
}
178+
}
179+
});
180+
181+
function uploadImage(file) {
182+
var formData = new FormData();
183+
formData.append('image', file);
184+
185+
// 使用 AJAX 发送图片数据
186+
var xhr = new XMLHttpRequest();
187+
xhr.open('POST', '/upload_image/', true);
188+
189+
xhr.onload = function () {
190+
if (xhr.status === 200) {
191+
var response = JSON.parse(xhr.responseText);
192+
if (response.url) {
193+
insertImageMarkdown(response.url);
194+
} else {
195+
alert('上传失败');
196+
}
197+
} else {
198+
alert('上传失败');
199+
}
200+
};
201+
202+
xhr.setRequestHeader('X-CSRFToken', csrftoken);
203+
xhr.send(formData);
204+
}
205+
206+
function insertImageMarkdown(url) {
207+
var markdownImage = '![](' + url + ')';
208+
209+
// 获取光标位置并插入文本
210+
var startPos = editor.selectionStart;
211+
var endPos = editor.selectionEnd;
212+
var value = editor.value;
213+
214+
editor.value = value.substring(0, startPos) + markdownImage + value.substring(endPos);
215+
editor.selectionStart = editor.selectionEnd = startPos + markdownImage.length;
216+
217+
// 触发输入事件,更新预览
218+
editor.dispatchEvent(new Event('input'));
219+
}
220+
</script>
221+
</body>
222+
</html>

0 commit comments

Comments
 (0)