浏览代码

feat: 新闻系统

SongZihuan 3 年之前
父节点
当前提交
53c8fd4321

+ 16 - 1
app/__init__.py

@@ -1,4 +1,4 @@
-from flask import Flask, Blueprint, get_flashed_messages
+from flask import Flask, Blueprint, get_flashed_messages, render_template
 from waitress import serve
 
 from conf import Config
@@ -30,6 +30,21 @@ class App:
         return {"flash_msg": msg,
                 "flash_height": len(msg)}
 
+    @staticmethod
+    @base.app_errorhandler(404)
+    def error_404(e):
+        return render_template("error.html", error_code="404", error_info=e), 404
+
+    @staticmethod
+    @base.app_errorhandler(403)
+    def error_403(e):
+        return render_template("error.html", error_code="403", error_info=e), 403
+
+    @staticmethod
+    @base.app_errorhandler(500)
+    def error_500(e):
+        return render_template("error.html", error_code="500", error_info=e), 500
+
     def __new__(cls, *args, **kwargs):
         if App.app is not None:
             return App.app

+ 2 - 2
app/auth/views.py

@@ -20,8 +20,8 @@ login_manager.login_view = 'auth.login'
 
 
 class LoginForm(FlaskForm):
-    name = StringField("你的用户名是?", validators=[DataRequired()])
-    passwd = PasswordField("用户密码是?", validators=[DataRequired()])
+    name = StringField("你的用户名是?", validators=[DataRequired(message="请输入用户名")])
+    passwd = PasswordField("用户密码是?", validators=[DataRequired(message="请输入密码")])
     submit = SubmitField("登录")
 
 

+ 0 - 10
app/index/views.py

@@ -15,16 +15,6 @@ def index():
     return render_template("hello/index.html")
 
 
-@hello.app_errorhandler(404)
-def error_404(e):
-    return render_template("hello/error.html", error_code="404", error_info=e), 404
-
-
-@hello.app_errorhandler(403)
-def error_403(e):
-    return render_template("hello/error.html", error_code="403", error_info=e), 403
-
-
 def creat_hello_website(app_: Flask):
     global app
     if app is None:

+ 0 - 0
app/news/__init__.py


+ 44 - 0
app/news/views.py

@@ -0,0 +1,44 @@
+from flask import render_template, Blueprint, Flask, redirect, url_for, flash, abort, request
+from flask_wtf import FlaskForm
+from wtforms import TextAreaField, SubmitField
+from wtforms.validators import DataRequired
+from flask_login import login_required, current_user
+
+from tool.type_ import Optional
+
+from app import views
+from app.web_user import WebUser
+
+news = Blueprint("news", __name__)
+app: Optional[Flask] = None
+
+
+class WriteForm(FlaskForm):
+    context = TextAreaField(validators=[DataRequired(message="请输入内容")])
+    submit = SubmitField()
+
+
+@news.route('/', methods=['GET', 'POST'])
+@login_required
+def index():
+    write_form = WriteForm()
+    if write_form.validate_on_submit():
+        if len(write_form.context.data) < 20:
+            flash("请输入20个字符以上的内容")
+            return redirect(url_for("news.index"))
+        user: WebUser = current_user
+        if not user.write_news(write_form.context.data):
+            abort(500)
+        return redirect(url_for("news.index"))
+    page = request.args.get("page", 1)
+    res, context_list = views.website.get_news(page)
+    if not res:
+        abort(404)
+    return render_template("news/news.html", form=write_form, context_list=context_list)
+
+
+def creat_news_website(app_: Flask):
+    global app
+    if app is None:
+        app = app_
+        app.register_blueprint(news, url_prefix="/news")

+ 1 - 1
app/static/styles/base.css

@@ -9,7 +9,7 @@ body {
 }
 
 /* 顶菜单栏 */
-section.nav-top {
+nav.nav-top {
     position: fixed; /* 固定布局 */
     top: 0;
     left: 0;

+ 59 - 0
app/static/styles/news/news.css

@@ -0,0 +1,59 @@
+.h2-title {
+    font-size: 22px;
+    margin-top: 5px;
+    margin-bottom: 10px;
+    text-align: left;
+}
+
+#writer {
+    display: block;
+    position: relative;
+    height: 200px;
+}
+
+#writer-context {
+    display: block;
+    font-size: 19px;
+    width: 100%;
+    height: 100px;
+    clear: both;
+}
+
+#writer-submit {
+    display: block;
+    font-size: 17px;
+    line-height: 30px;
+    width: 70px;
+    height: 30px;
+    margin-top: 10px;
+    float: right;
+}
+
+.news {
+    width: 100%;
+    margin: 20px auto 5px;
+}
+
+.news-content, .news-author, .news-time {
+    margin-left: 10px;
+    margin-right: 10px;
+}
+
+.news-author, .news-time {
+    font-size: 20px;
+}
+
+.news-author {
+    float: left;
+}
+
+.news-time {
+    float: right;
+}
+
+.news-content {
+    display: block;
+    clear: both;
+    font-size: 22px;
+    padding-top: 5px;
+}

+ 4 - 2
app/store/views.py

@@ -1,6 +1,7 @@
 from flask import render_template, Blueprint, Flask, redirect, url_for, abort, flash
 from wtforms import TextField, SubmitField
 from flask_login import current_user
+from wtforms.validators import DataRequired, NumberRange
 from flask_wtf import FlaskForm
 from flask_login import login_required
 import functools
@@ -14,7 +15,8 @@ app: Optional[Flask] = None
 
 
 class BuyForm(FlaskForm):
-    quantity = TextField()
+    quantity = TextField(validators=[DataRequired(message="请输入兑换数量"),
+                                     NumberRange(1, 11, message="一次性只能兑换1-10个")])
     submit = SubmitField()
 
 
@@ -46,7 +48,7 @@ def buy(goods_id: int):
     abort(404)
 
 
-@store.route('/index', methods=['GET', 'POST'])
+@store.route('/', methods=['GET', 'POST'])
 @login_required
 def index():
     form = BuyForm()

+ 19 - 9
app/templates/base.html

@@ -34,23 +34,33 @@
 <body>
 
 {% block nav %}
-    <section class="nav-top">
+    <nav class="nav-top">
         <p id="nav-title"> HGSSystem-{{ loc }} 在线 </p>
         <ul class="nav-top">
-            <li class="nav-top-item"><a class="nav-top-item" href="{{ url_for('hello.index') }}"> 首页 </a></li>
-            <li class="nav-top-item"><a class="nav-top-item" href="{{ url_for('rank.rank_up', page=1) }}"> 高分榜 </a></li>
-            <li class="nav-top-item"><a class="nav-top-item" href="{{ url_for('rank.rank_down', page=1) }}"> 警告榜 </a>
-            </li>
-            <li class="nav-top-item"><a class="nav-top-item" href="{{ url_for('store.index') }}"> 积分商城 </a></li>
-            <li class="nav-top-item"><a class="nav-top-item"> 新闻 </a></li>
+            <li class="nav-top-item"><a class="nav-top-item" href="{{ url_for('hello.index') }}">
+                首页 </a></li>
+
+            <li class="nav-top-item"><a class="nav-top-item" href="{{ url_for('rank.rank_up', page=1) }}">
+                高分榜 </a></li>
+
+            <li class="nav-top-item"><a class="nav-top-item" href="{{ url_for('rank.rank_down', page=1) }}">
+                警告榜 </a></li>
+
+            <li class="nav-top-item"><a class="nav-top-item" href="{{ url_for('store.index') }}">
+                积分商城 </a></li>
+
+            <li class="nav-top-item"><a class="nav-top-item" href="{{ url_for('news.index') }}">
+                新闻 </a></li>
+
             {% if current_user.is_authenticated %}
                 <li class="nav-top-item"><a class="nav-top-item" href="{{ url_for('auth.about') }}">
                     关于: {{ current_user.name }} </a></li>
             {% else %}
                 <li class="nav-top-item"><a class="nav-top-item" href="{{ url_for('auth.login') }}"> 登录 </a></li>
             {% endif %}
+
         </ul>
-    </section>
+    </nav>
 {% endblock %}
 
 <p id="start-p"></p>  <!-- 占位作用 -->
@@ -60,7 +70,7 @@
         {% if flash_height != 0 %}
             <ul class="top-msg-section">
                 {% for message in flash_msg %}
-                    <li class="top-msg"> {{ flash_height }} {{ message }} </li>
+                    <li class="top-msg"> {{ message }} </li>
                 {% endfor %}
             </ul>
 

+ 0 - 0
app/templates/hello/error.html → app/templates/error.html


+ 22 - 0
app/templates/news/news.html

@@ -0,0 +1,22 @@
+{% extends "base.html" %}
+{% import "news/news_macro.html" as news %}
+
+{% block style %}
+    {{ super() }}
+    <link href="{{ url_for('static', filename='styles/news/news.css') }}" rel="stylesheet">
+{% endblock %}
+
+{% block title %} 新闻 {% endblock %}
+{% block h1_title %} 新闻 {% endblock %}
+
+{% block content %}
+    <h2 class="h2-title"> 发表意见 </h2>
+    <form class="writer clearfix" action="{{ url_for('news.index') }}" method="post">
+        {{ form.hidden_tag() }}
+        {{ form.context(id="writer-context") }}
+        {{ form.submit(id="writer-submit", value="提交") }}
+    </form>
+
+    <h2 class="h2-title"> 最新消息 </h2>
+    {{ news.get_context_list(context_list) }}
+{% endblock %}

+ 10 - 0
app/templates/news/news_macro.html

@@ -0,0 +1,10 @@
+{% macro get_context_list(info_lines) %}
+    {% for line in info_lines %}
+        <section class="news">
+            <p class="news-author"> 作者:{{ line[2] }} </p>
+            <p class="news-time"> 发表时间: {{ line[3] }} </p>
+            <p class="news-content"> {{ line[1] }} </p>
+            <hr>
+        </section>
+    {% endfor %}
+{% endmacro %}

+ 2 - 0
app/views.py

@@ -3,6 +3,7 @@ from app.index.views import creat_hello_website
 from app.rank.views import creat_ranking_website
 from app.auth.views import creat_auth_website
 from app.store.views import creat_store_website
+from app.news.views import creat_news_website
 
 from tool.type_ import *
 from sql.db import DB
@@ -20,3 +21,4 @@ def register(app: Flask, db: DB):
     creat_ranking_website(app)
     creat_auth_website(app)
     creat_store_website(app)
+    creat_news_website(app)

+ 26 - 11
app/web.py

@@ -1,6 +1,7 @@
 from sql.store import get_store_item_list, get_store_item, check_order
 
 from flask import Flask
+from flask_login import current_user
 import datetime
 
 from conf import Config
@@ -12,6 +13,7 @@ from core.garbage import GarbageType
 from sql import DBBit
 from sql.db import DB
 from sql.user import find_user_by_name, find_user_by_id
+from sql.news import write_news, get_news
 
 from . import web_user
 from . import web_goods
@@ -21,13 +23,26 @@ class WebsiteBase:
     def __init__(self, app: Flask, db: DB):
         self._db = db
         self._app = app
+        self._user = current_user  # 把参函传递的user移迁为该变量
 
-
-class AuthWebsite(WebsiteBase):
     @property
     def db(self):
         return self._db
 
+    @property
+    def app(self):
+        return self._app
+
+    @property
+    def user(self):
+        return self._user
+
+    @property
+    def rel_user(self):
+        return self._user._get_current_object()
+
+
+class AuthWebsite(WebsiteBase):
     def load_user_by_name(self, name: uname_t, passwd: passwd_t) -> Optional["web_user.WebUser"]:
         user = find_user_by_name(name, passwd, self._db)
         if user is None:
@@ -72,10 +87,6 @@ class AuthWebsite(WebsiteBase):
 
 
 class StoreWebsite(WebsiteBase):
-    @property
-    def db(self):
-        return self._db
-
     def get_store_list(self) -> Optional[List]:
         return get_store_item_list(self._db)
 
@@ -90,10 +101,6 @@ class StoreWebsite(WebsiteBase):
 
 
 class RankWebsite(WebsiteBase):
-    @property
-    def db(self):
-        return self._db
-
     def get_rank(self, page: int, order_by: str = "DESC") -> Optional[List[Tuple]]:
         offset = 20 * (page - 1)
         cur = self._db.search(columns=['UserID', 'Name', 'Score', 'Reputation'],
@@ -111,6 +118,14 @@ class RankWebsite(WebsiteBase):
         return res
 
 
-class Website(AuthWebsite, StoreWebsite, RankWebsite, WebsiteBase):
+class NewsWebsite(WebsiteBase):
+    def write_news(self, context: str, uid: uid_t):
+        return write_news(context, uid, self.db)
+
+    def get_news(self, page: int = 1):
+        return get_news(limit=20, offset=((page - 1) * 20), db=self.db)
+
+
+class Website(AuthWebsite, StoreWebsite, RankWebsite, NewsWebsite, WebsiteBase):
     def __init__(self, app: Flask, db: DB):
         super(Website, self).__init__(app, db)

+ 18 - 15
app/web_user.py

@@ -76,11 +76,16 @@ class WebUser(UserMixin):
             self.reputation = res.get('reputation', '0')
             self.rubbish = res.get('rubbish', '0')
 
-    def is_manager(self):
-        return self.group == "管理员"
+    @property
+    def is_active(self):
+        return views.website.load_user_by_id(self._uid) is not None
 
-    def get_qr_code(self):
-        return self.order, self._uid
+    @property
+    def is_authenticated(self):
+        return views.website.load_user_by_id(self._uid) is not None
+
+    def get_id(self):
+        return self._uid
 
     @property
     def name(self):
@@ -90,14 +95,6 @@ class WebUser(UserMixin):
     def uid(self):
         return self._uid[:Config.tk_show_uid_len]
 
-    @property
-    def is_active(self):
-        return views.website.load_user_by_id(self._uid) is not None
-
-    @property
-    def is_authenticated(self):
-        return views.website.load_user_by_id(self._uid) is not None
-
     @property
     def order(self) -> str:
         cur = views.website.db.search(columns=["OrderID"],
@@ -108,6 +105,12 @@ class WebUser(UserMixin):
         assert cur.rowcount == 1
         return str(cur.fetchone()[0])
 
+    def is_manager(self):
+        return self.group == "管理员"
+
+    def get_qr_code(self):
+        return self.order, self._uid
+
     def get_order_goods_list(self):
         order = self.order
         if order is None:
@@ -124,12 +127,12 @@ class WebUser(UserMixin):
             res.append(f"#{i} {re[0]} x {re[1]}")
         return res
 
-    def get_id(self):
-        return self._uid
-
     def get_garbage_list(self):
         return views.website.get_user_garbage_list(self._uid, limit=20)
 
     def get_user(self) -> User:
         res = views.website.get_user_by_id(self._uid)
         return res
+
+    def write_news(self, text: str):
+        return views.website.write_news(text, self._uid)

+ 32 - 0
sql/news.py

@@ -0,0 +1,32 @@
+import datetime
+
+from sql.db import DB
+from tool.type_ import *
+from tool.string import mysql_str
+
+
+def write_news(text, uid: uid_t, db: DB):
+    text = mysql_str(text)
+    cur = db.insert(table="context",
+                    columns=["Context", "Author"],
+                    values=f"'{text}', '{uid}'")
+    if cur is None:
+        return False
+    assert cur.rowcount == 1
+    return True
+
+
+def get_news(db: DB, limit: Optional[int] = None, offset: Optional[int] = None):
+    cur = db.search(columns=["ContextID", "Context", "Name", "Time"],
+                    table="context_user",
+                    limit=limit,
+                    offset=offset,
+                    order_by=[("Time", "DESC")])
+    if cur is None:
+        return False, None
+    res = []
+    for i in range(cur.rowcount):
+        re = cur.fetchone()
+        time: datetime.datetime = re[3]
+        res.append((re[0], re[1], re[2], time.strftime("%Y-%m-%d %H:%M")))
+    return True, res

+ 8 - 0
tool/string.py

@@ -0,0 +1,8 @@
+def mysql_str(text: str):
+    return (text
+            .replace("\n", r"\n")
+            .replace(r"'", r"\'")
+            .replace(r'"', r'\"')
+            .replace("\b", r"\b")
+            .replace("\r", "")
+            .replace("\t", r"\t"))