1.2.1更新,详情请看readme.md

This commit is contained in:
小钊 2025-06-23 10:46:41 +08:00
parent 796302a954
commit fb69ea4172
47 changed files with 491 additions and 59 deletions

BIN
.DS_Store vendored Normal file

Binary file not shown.

View File

@ -34,6 +34,7 @@ INSTALLED_APPS = [
'simpleui',
'home',
'api',
'comment',
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',

View File

@ -6,10 +6,12 @@
# 目录结构描述
├── ReadMe.md // 帮助文档
├── home // 首页app
├── home // 文章相关app
├── api // 项目api
├── comment // 评论app
└── Echo-Z // 项目配置等目录
# 使用说明
@ -35,3 +37,11 @@ python manage.py runserver 9999
###### 1.2.0
2025年6月19日
完成文章的基础管理、文章的列表显示、文章的详细信息的功能编写,功能有待完善
###### 1.2.1
2025年6月23日
新增
1.文章详情页上一篇、下一篇功能
2.文章详情页评论列表显示
3.文章详情页评论发布
4.文章详情页点赞与取消点赞

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -1,6 +1,9 @@
from django.contrib import admin
from django.urls import path
from . import views as api_views
urlpatterns = [
path('create_comment/<id>', api_views.add_comment),
path('link/<id>/<uuid>', api_views.like),
path('unlink/<id>/<uuid>', api_views.un_like),
path('check_like/<id>/<uuid>', api_views.check_like),
]

View File

@ -1,3 +1,46 @@
from django.shortcuts import render
from datetime import datetime
from django.shortcuts import render,HttpResponse
from django.http import JsonResponse,HttpResponseNotAllowed,HttpResponseRedirect,JsonResponse
from django.views.decorators.http import require_http_methods
from comment.models import comment
from home.models import *
# Create your views here.
@require_http_methods(["POST"])
def add_comment(request, id):
create_comment = comment.objects.create(comment_Content=request.POST.get("comment"),comment_User=request.POST.get("username"), comment_Time=datetime.now(), archives_Id=id, qq=request.POST.get("qqNum"))
create_comment.save()
return HttpResponseRedirect("/archives/"+id)
@require_http_methods(["POST"])
def check_like(request, id, uuid):
articles_likes = ArticlesLike.objects.filter(articles_id=id,uuid=uuid)
if articles_likes:
return JsonResponse({"liked":True})
else:
return JsonResponse({"liked":False})
@require_http_methods(["POST"])
def like(request, id, uuid):
article_stat = 0
if not ArticlesLike.objects.filter(articles_id=id, uuid=uuid):
ArticlesLike.objects.create(articles_id=id, uuid=uuid)
article = Articles.objects.get(id=id)
article.stat += 1
article_stat = article.stat
article.save()
return JsonResponse({"success":True,"new_count": article_stat})
@require_http_methods(["POST"])
def un_like(request,id, uuid):
article_stat = 0
if ArticlesLike.objects.filter(articles_id=id, uuid=uuid):
ArticlesLike.objects.filter(articles_id=id, uuid=uuid).delete()
article = Articles.objects.get(id=id)
article.stat -= 1
article_stat = article.stat
article.save()
return JsonResponse({"success":True,"new_count": article_stat})

0
comment/__init__.py Normal file
View File

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

7
comment/admin.py Normal file
View File

@ -0,0 +1,7 @@
from django.contrib import admin
from comment.models import comment
# Register your models here.
@admin.register(comment)
class CommentAdmin(admin.ModelAdmin):
list_display = ["comment_Content","comment_User","comment_Time", "archives_Id"]

7
comment/apps.py Normal file
View File

@ -0,0 +1,7 @@
from django.apps import AppConfig
class CommentConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'comment'
verbose_name = "评论"

View File

@ -0,0 +1,27 @@
# Generated by Django 4.2.23 on 2025-06-20 02:43
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [
]
operations = [
migrations.CreateModel(
name='comment',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('comment_Content', models.TextField(verbose_name='评论内容')),
('comment_User', models.CharField(max_length=100, verbose_name='评论者')),
('comment_Time', models.DateTimeField(verbose_name='评论时间')),
],
options={
'verbose_name': '评论',
'verbose_name_plural': '评论管理',
},
),
]

View File

@ -0,0 +1,18 @@
# Generated by Django 4.2.23 on 2025-06-20 03:01
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('comment', '0001_initial'),
]
operations = [
migrations.AddField(
model_name='comment',
name='archives_Id',
field=models.IntegerField(default=0, verbose_name='文章idßß'),
),
]

View File

@ -0,0 +1,23 @@
# Generated by Django 4.2.23 on 2025-06-20 04:53
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('comment', '0002_comment_archives_id'),
]
operations = [
migrations.AddField(
model_name='comment',
name='qq',
field=models.IntegerField(default=1000, verbose_name='评论者qq号'),
),
migrations.AlterField(
model_name='comment',
name='archives_Id',
field=models.IntegerField(default=0, verbose_name='文章id'),
),
]

View File

Binary file not shown.

16
comment/models.py Normal file
View File

@ -0,0 +1,16 @@
from django.db import models
# Create your models here.
class comment(models.Model):
comment_Content = models.TextField(verbose_name="评论内容")
comment_User = models.CharField(max_length=100,verbose_name="评论者")
comment_Time = models.DateTimeField(verbose_name="评论时间")
archives_Id = models.IntegerField(default=0, verbose_name="文章id")
qq = models.IntegerField(default=1000, verbose_name="评论者qq号")
def __str__(self):
return self.comment_content
class Meta:
verbose_name = "评论"
verbose_name_plural = "评论管理"

3
comment/tests.py Normal file
View File

@ -0,0 +1,3 @@
from django.test import TestCase
# Create your tests here.

4
comment/views.py Normal file
View File

@ -0,0 +1,4 @@
from django.shortcuts import render
# Create your views here.

Binary file not shown.

BIN
home/.DS_Store vendored Normal file

Binary file not shown.

Binary file not shown.

View File

@ -1,10 +1,9 @@
from django.contrib import admin
from home.models import articles
from home.models import *
# Register your models here.
@admin.register(articles)
@admin.register(Articles)
class DepartmentAdmin(admin.ModelAdmin):
# 要显示的字段
list_display = ('title', 'abstract', 'created','stat')
@ -14,4 +13,13 @@ class DepartmentAdmin(admin.ModelAdmin):
# 分页显示,一页的数量
list_per_page = 10
actions_on_top = True
@admin.register(ArticlesLike)
class ArticlesLikeAdmin(admin.ModelAdmin):
list_display = ('articles_id', 'uuid')
readonly_fields = ('articles_id', 'uuid')
# 分页显示,一页的数量
list_per_page = 10
actions_on_top = True

View File

@ -4,4 +4,4 @@ from django.apps import AppConfig
class HomeConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'home'
verbose_name = "文章管理"
verbose_name = "文章"

View File

@ -0,0 +1,25 @@
# Generated by Django 4.2.23 on 2025-06-22 18:14
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('home', '0004_articles_author'),
]
operations = [
migrations.CreateModel(
name='ArticlesLike',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('articles_id', models.IntegerField(default=0, verbose_name='文章id')),
('uuid', models.CharField(max_length=100, verbose_name='点赞用户标识')),
],
options={
'verbose_name': '点赞',
'verbose_name_plural': '点赞管理',
},
),
]

View File

@ -2,7 +2,7 @@ from django.db import models
# Create your models here.
class articles(models.Model):
class Articles(models.Model):
title = models.CharField(max_length=100,verbose_name="文章标题")
content = models.TextField(verbose_name="文章内容")
abstract = models.TextField(verbose_name="文章摘要")
@ -20,3 +20,13 @@ class articles(models.Model):
def __str__(self):
return self.title
class ArticlesLike(models.Model):
articles_id = models.IntegerField(default=0,verbose_name="文章id")
uuid = models.CharField(max_length=100,verbose_name="点赞用户标识")
class Meta:
verbose_name = "点赞"
verbose_name_plural = "点赞管理"
def __str__(self):
return self.uuid

12
home/static/aaa.js Normal file

File diff suppressed because one or more lines are too long

View File

@ -41,6 +41,15 @@
color: #bbb;
}
.article-like {
margin: 20px 0;
text-align: center;
}
.link {
margin: 4px;
}
/* 文章封面图 */
.article-cover {
margin: 30px 0;
@ -267,6 +276,12 @@
.form-group {
margin-bottom: 20px;
}
.form-group {
display: flex;
justify-content: space-between;
flex-wrap: wrap;
}
.comment-form textarea {
@ -285,6 +300,16 @@
outline: none;
}
.form-group > .comment-input {
width: 49.5%;
height: 30px;
border: 1px solid #eee;
border-radius: 6px;
margin-bottom: 10px;
padding: 15px;
}
.submit-btn {
background-color: #3498db;
color: #fff;

6
home/static/marked.min.js vendored Normal file

File diff suppressed because one or more lines are too long

View File

@ -2,8 +2,12 @@
{% load static %}
{#文章详情#}
{% block sitename %}
<title>{{ title }}-我的个人博客</title>
{% endblock %}
{% block stylesheethref %}
<link rel="stylesheet" href="{% static 'archives.css' %}">
<link rel="stylesheet" href="{% static 'archives.css' %}">
<script src="{% static 'marked.min.js' %}"></script>
{% endblock %}
{% block content %}
<div class="article-detail-container">
@ -13,24 +17,21 @@
<h1 class="article-title">{{ title }}</h1>
<div class="article-meta">
<span class="author">
<i class="fas fa-user"></i>
{{ author }}
作者 {{ author }}
</span>
<span class="date">
<i class="fas fa-calendar-alt"></i>
{{ created|date:"Y-m-d" }}
发布时间 {{ created|date:"Y-m-d" }}
</span>
<span class="category">
<i class="fas fa-folder"></i>
{{ article.category }}
分类
{{ stat }} 点赞
</span>
<span class="views">
<i class="fas fa-eye"></i>
{{ read }} 浏览
</span>
</div>
</div>
<div class="article-cover">
<img src="http://iph.href.lu/200x150?text=封面功能,正在开发中" alt="{{ title }}封面图">
</div>
@ -39,6 +40,145 @@
<div class="article-content">
{{ content|safe }}
</div>
<div class="dz" style="width: 42px;height: 42px;margin: 0 auto">
<div class="dz-icon" id="likeBtn" style="background-color: red;width: 42px;height: 42px;border-radius: 50%">
<div>
<svg viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg" width="38" height="38" style="margin: 2px">
<path id="link_svg" d="M512 928c-28.928 0-57.92-12.672-86.624-41.376L106.272 564C68.064 516.352 32 471.328 32 384c0-141.152 114.848-256 256-256 53.088 0 104 16.096 147.296 46.592 14.432 10.176 17.92 30.144 7.712 44.608-10.176 14.432-30.08 17.92-44.608 7.712C366.016 204.064 327.808 192 288 192c-105.888 0-192 86.112-192 192 0 61.408 20.288 90.112 59.168 138.688l315.584 318.816C486.72 857.472 499.616 863.808 512 864c12.704.192 24.928-6.176 41.376-22.624l316.672-319.904C896.064 493.28 928 445.696 928 384c0-105.888-86.112-192-192-192-48.064 0-94.08 17.856-129.536 50.272l-134.08 134.112c-12.512 12.512-32.736 12.512-45.248 0s-12.512-32.736 0-45.248L562.24 196c48.32-44.192 109.664-68 173.76-68 141.152 0 256 114.848 256 256 0 82.368-41.152 144.288-75.68 181.696l-317.568 320.8C569.952 915.328 540.96 928 512 928z" fill="#fff"></path>
</svg>
</div>
<p style="text-align: center" id="likeCount">{{ stat }}</p>
</div>
</div>
</article>
<!-- 文章导航 -->
<nav class="article-nav">
{% if previous %}
<a href="/archives/{{ previous.id }}" class="prev">
<i class="fas fa-chevron-left"></i>
上一篇: {{ previous.title }}
</a>
{% endif %}
{% if next %}
<a href="/archives/{{ next.id }}" class="next">
下一篇: {{ next.title }}
<i class="fas fa-chevron-right"></i>
</a>
{% endif %}
</nav>
<!-- 评论区域 -->
<section class="comment-section">
<h3 class="section-title">评论</h3>
<div class="comment-form">
<form id="comment-form" action="/api/create_comment/{{ id }}" method="post">
{% csrf_token %}
<div class="form-group">
<input name="username" class="comment-input" placeholder="昵称">
<input name="qqNum" class="comment-input" placeholder="qq号(仅用于头像显示)">
<textarea name="comment" rows="5" placeholder="写下你的评论..."></textarea>
</div>
<button type="submit" class="submit-btn">提交评论</button>
</form>
</div>
<div class="comment-list">
{% for comment in comments %}
<div class="comment-item">
<div class="comment-avatar">
<img src="http://q1.qlogo.cn/g?b=qq&nk={{ comment.qq }}&s=100" alt="用户头像">
</div>
<div class="comment-content">
<div class="comment-header">
<span class="comment-user">{{ comment.comment_User }}</span>
<span class="comment-time">{{ comment.comment_Time|timesince }}</span>
</div>
<p>{{ comment.comment_Content }}</p>
{% if comment.replies.all %}
<div class="comment-replies">
{% for reply in comment.replies.all %}
<div class="reply-item">
<div class="reply-avatar">
<img src="{{ reply.user.profile.avatar.url|default:'https://via.placeholder.com/40' }}" alt="用户头像">
</div>
<div class="reply-content">
<div class="reply-header">
<span class="reply-user">{{ reply.user.username }}</span>
<span class="reply-time">{{ reply.created_at|timesince }}前</span>
</div>
<p>{{ reply.content }}</p>
</div>
</div>
{% endfor %}
</div>
{% endif %}
</div>
</div>
{% empty %}
<div class="no-comments">暂无评论,快来抢沙发吧~</div>
{% endfor %}
</div>
</section>
</div>
{% endblock %}
{% block script %}
const no_active_path = "M512 928c-28.928 0-57.92-12.672-86.624-41.376L106.272 564C68.064 516.352 32 471.328 32 384c0-141.152 114.848-256 256-256 53.088 0 104 16.096 147.296 46.592 14.432 10.176 17.92 30.144 7.712 44.608-10.176 14.432-30.08 17.92-44.608 7.712C366.016 204.064 327.808 192 288 192c-105.888 0-192 86.112-192 192 0 61.408 20.288 90.112 59.168 138.688l315.584 318.816C486.72 857.472 499.616 863.808 512 864c12.704.192 24.928-6.176 41.376-22.624l316.672-319.904C896.064 493.28 928 445.696 928 384c0-105.888-86.112-192-192-192-48.064 0-94.08 17.856-129.536 50.272l-134.08 134.112c-12.512 12.512-32.736 12.512-45.248 0s-12.512-32.736 0-45.248L562.24 196c48.32-44.192 109.664-68 173.76-68 141.152 0 256 114.848 256 256 0 82.368-41.152 144.288-75.68 181.696l-317.568 320.8C569.952 915.328 540.96 928 512 928z"
const active_path = "M736 128c-65.952 0-128.576 25.024-176.384 70.464-4.576 4.32-28.672 28.736-47.328 47.68L464.96 199.04C417.12 153.216 354.272 128 288 128 146.848 128 32 242.848 32 384c0 82.432 41.184 144.288 76.48 182.496l316.896 320.128C450.464 911.68 478.304 928 512 928s61.568-16.32 86.752-41.504l316.736-320 2.208-2.464C955.904 516.384 992 471.392 992 384c0-141.152-114.848-256-256-256z"
const path = document.getElementById('link_svg');
const fpPromise = import('{% static "aaa.js" %}').then(FingerprintJS => FingerprintJS.load());
fpPromise.then(fp => fp.get()).then(result => {localStorage.setItem("uuid",result.visitorId)});
document.addEventListener('DOMContentLoaded', function() {
const likeBtn = document.getElementsByClassName('dz')[0];
if (!likeBtn) return;
fetch(`/api/check_like/{{ id }}/${localStorage.getItem("uuid")}`,{
method: 'POST',
headers: {
'X-CSRFToken': '{{ csrf_token }}'
},
credentials: 'include'
}).then(response => response.json()).then(data => {
if (data.liked) {
path.setAttribute('d', active_path)
likeBtn.classList.add('active');
}
});
const dz_icon = document.getElementsByClassName('dz-icon')[0];
dz_icon.addEventListener('click', function() {
if (likeBtn.classList.contains('active')) {
fetch(`/api/unlink/{{ id }}/${localStorage.getItem("uuid")}`,{
method: 'POST',
headers: {
'X-CSRFToken': '{{ csrf_token }}'
},
credentials: 'include'
})
.then(response => response.json())
.then(data => {
if (data.success) {
likeBtn.classList.remove('active');
path.setAttribute('d', no_active_path)
document.getElementById('likeCount').textContent = data.new_count;
}
});
} else {
fetch(`/api/link/{{ id }}/${localStorage.getItem("uuid")}`, {
method: 'POST',
headers: {
'X-CSRFToken': '{{ csrf_token }}'
},
credentials: 'include'
})
.then(response => response.json())
.then(data => {
if (data.success) {
likeBtn.classList.add('active');
path.setAttribute('d', active_path)
document.getElementById('likeCount').textContent = data.new_count;
}
});
}
})
});
{% endblock %}

View File

@ -4,7 +4,9 @@
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>我的个人博客</title>
{% block sitename %}
<title>我的个人博客</title>
{% endblock %}
<link rel="stylesheet" href="{% static 'bottom.css' %}">
{% block stylesheethref %}
{% endblock %}
@ -92,40 +94,8 @@
<!-- 简单的交互功能 -->
<script>
document.addEventListener('DOMContentLoaded', function() {
// 搜索功能
const searchBtn = document.querySelector('.search-box button');
const searchInput = document.querySelector('.search-box input');
searchBtn.addEventListener('click', function() {
const query = searchInput.value.trim();
if (query) {
alert('搜索: ' + query);
// 实际应用中这里应该是跳转到搜索页面或执行搜索操作
}
});
searchInput.addEventListener('keypress', function(e) {
if (e.key === 'Enter') {
const query = searchInput.value.trim();
if (query) {
alert('搜索: ' + query);
// 实际应用中这里应该是跳转到搜索页面或执行搜索操作
}
}
});
// 文章卡片交互
const articleCards = document.querySelectorAll('.article-card');
articleCards.forEach(card => {
card.addEventListener('click', function(e) {
if (!e.target.closest('.read-more')) {
const id = this.querySelector('.article-id').textContent;
window.open(`./archives/${id}`)
}
});
});
});
{% block script %}
{% endblock %}
</script>
</body>
</html>

View File

@ -34,4 +34,40 @@
{% endfor %}
</div>
</div>
{% endblock %}
{% block script %}
document.addEventListener('DOMContentLoaded', function() {
// 搜索功能
const searchBtn = document.querySelector('.search-box button');
const searchInput = document.querySelector('.search-box input');
searchBtn.addEventListener('click', function() {
const query = searchInput.value.trim();
if (query) {
alert('搜索: ' + query);
// 实际应用中这里应该是跳转到搜索页面或执行搜索操作
}
});
searchInput.addEventListener('keypress', function(e) {
if (e.key === 'Enter') {
const query = searchInput.value.trim();
if (query) {
alert('搜索: ' + query);
// 实际应用中这里应该是跳转到搜索页面或执行搜索操作
}
}
});
// 文章卡片交互
const articleCards = document.querySelectorAll('.article-card');
articleCards.forEach(card => {
card.addEventListener('click', function(e) {
if (!e.target.closest('.read-more')) {
const id = this.querySelector('.article-id').textContent;
window.open(`/archives/${id}`)
}
});
});
});
{% endblock %}

View File

@ -1,11 +1,12 @@
from django.shortcuts import render, HttpResponse
from . import models
from home.models import *
from comment.models import comment
# Create your views here.
def index(request):
artchle = models.articles.objects.all()
artchle = Articles.objects.all()
artchles = {"artchles": []}
print(artchle)
for i in artchle:
a = {
"id":i.id,
@ -17,13 +18,19 @@ def index(request):
}
artchles["artchles"].append(a)
return render(request, 'index.html', context=artchles)
# return HttpResponse("Hello, world. You're at the polls index.")
def archives(request, id):
i = models.articles.objects.get(id=id)
i = Articles.objects.get(id=id)
i.read += 1
i.save()
previous_article = Articles.objects.filter(id__lt=id).order_by('-id').first()
previous_id = previous_article.id if previous_article else id
previous_id_title = previous_article.title if previous_article else "暂无上一篇"
next_article = Articles.objects.filter(id__gt=id).order_by('id').first()
next_id = next_article.id if next_article else id
next_id_title = next_article.title if next_article else "暂无下一篇"
comments = comment.objects.filter(archives_Id=id).order_by("-comment_Time").all()
a = {
"id": i.id,
"title": i.title,
@ -32,6 +39,22 @@ def archives(request, id):
"stat": i.stat,
"read": i.read,
"content": i.content,
"author": i.author
"author": i.author,
"previous":{
"id": previous_id,
"title": previous_id_title,
},"next":{
"id": next_id,
"title": next_id_title,
},"comments": []
}
for c in comments:
com = {
"id": c.id,
"comment_Content": c.comment_Content,
"comment_User": c.comment_User,
"comment_Time": c.comment_Time,
"qq": c.qq,
}
a["comments"].append(com)
return render(request, 'archives.html', a)

15
requirements.txt Normal file
View File

@ -0,0 +1,15 @@
asgiref==3.8.1
diff-match-patch==20241021
Django==4.2.23
django-import-export==4.3.7
django-mdeditor==0.1.20
django-simpleui==2025.5.262
docutils==0.21.2
importlib_metadata==8.7.0
Markdown==3.8
pillow==11.2.1
Pygments==2.19.1
sqlparse==0.5.3
tablib==3.8.0
typing_extensions==4.14.0
zipp==3.23.0