1 Commits
1.0.0 ... 1.0.2

17 changed files with 588 additions and 50 deletions

View File

@@ -29,7 +29,7 @@ db.init_app(app)
login_manager = LoginManager() login_manager = LoginManager()
login_manager.init_app(app) login_manager.init_app(app)
login_manager.login_view = 'index' login_manager.login_view = 'login'
@login_manager.user_loader @login_manager.user_loader
def load_user(user_id): def load_user(user_id):

View File

@@ -1,6 +1,22 @@
import os import os
from datetime import timedelta
class Config: class Config:
SECRET_KEY = os.environ.get('SECRET_KEY') or 'hard_to_guess_string_for_flask_app' SECRET_KEY = os.environ.get('SECRET_KEY') or 'hard_to_guess_string_for_flask_app'
SQLALCHEMY_DATABASE_URI = 'sqlite:///question_collector.db' SQLALCHEMY_DATABASE_URI = 'sqlite:///question_collector.db'
SQLALCHEMY_TRACK_MODIFICATIONS = False SQLALCHEMY_TRACK_MODIFICATIONS = False
SQLALCHEMY_ENGINE_OPTIONS = {
'connect_args': {'check_same_thread': False}
}
# 设置 UTC+8 时区
import pytz
tz = pytz.timezone('Asia/Shanghai')
def naive_now():
from datetime import datetime
return datetime.now(tz)
def utc_now():
from datetime import datetime
return datetime.utcnow()

View File

@@ -1,7 +1,18 @@
from flask_wtf import FlaskForm from flask_wtf import FlaskForm
from wtforms import StringField, TextAreaField, SelectField, BooleanField, SubmitField from wtforms import StringField, TextAreaField, SelectField, BooleanField, SubmitField, PasswordField
from wtforms.validators import DataRequired, Length from wtforms.validators import DataRequired, Length
class LoginForm(FlaskForm):
username = StringField('用户名', validators=[DataRequired(), Length(max=80)])
password = PasswordField('密码', validators=[DataRequired()])
submit = SubmitField('登录')
class RegisterForm(FlaskForm):
username = StringField('用户名', validators=[DataRequired(), Length(max=80)])
password = PasswordField('密码', validators=[DataRequired(), Length(min=6)])
confirm_password = PasswordField('确认密码', validators=[DataRequired()])
submit = SubmitField('注册')
class DemandForm(FlaskForm): class DemandForm(FlaskForm):
title = StringField('需求标题', validators=[DataRequired(), Length(max=200)]) title = StringField('需求标题', validators=[DataRequired(), Length(max=200)])
content = TextAreaField('需求内容', validators=[DataRequired(), Length(max=1000)]) content = TextAreaField('需求内容', validators=[DataRequired(), Length(max=1000)])
@@ -14,7 +25,7 @@ class DemandForm(FlaskForm):
('finance_review', '经费审查委员会'), ('finance_review', '经费审查委员会'),
('women', '女职工委员会') ('women', '女职工委员会')
], validators=[DataRequired()]) ], validators=[DataRequired()])
contact = StringField('联系方式', validators=[DataRequired(), Length(max=100)]) contact = StringField('联系方式')
is_public = BooleanField('是否公开') is_public = BooleanField('是否公开')
submit = SubmitField('提交') submit = SubmitField('提交')

View File

@@ -1,19 +1,42 @@
from __init__ import app, db from __init__ import app, db
from models import User from models import User
from sqlalchemy import text
with app.app_context(): with app.app_context():
db.create_all() db.create_all()
if not User.query.filter_by(username='管理员').first(): # 检查并添加 password_hash 字段(如果不存在)
try:
db.session.execute(text("ALTER TABLE user ADD COLUMN password_hash VARCHAR(200)"))
db.session.commit()
print('已添加 password_hash 字段')
except Exception as e:
if 'duplicate column' in str(e).lower() or 'already exists' in str(e).lower():
print('password_hash 字段已存在')
else:
pass # 字段可能已存在,忽略错误
# 创建默认管理员账号
admin = User.query.filter_by(username='管理员').first()
if not admin:
admin = User( admin = User(
username='管理员', username='admin',
dingtalk_userid='admin', dingtalk_userid='admin',
dingtalk_name='管理员', dingtalk_name='管理员',
dingtalk_dept='', dingtalk_dept='',
role='admin' role='admin'
) )
admin.set_password('admin123') # 设置默认密码
db.session.add(admin) db.session.add(admin)
db.session.commit() db.session.commit()
print('数据库初始化完成,已创建管理员账户') print('数据库初始化完成,已创建管理员账户')
print('用户名: admin')
print('密码: admin123')
print('请及时修改默认密码!')
else: else:
# 确保管理员有密码
if not admin.password_hash:
admin.set_password('admin123')
db.session.commit()
print('已为管理员设置默认密码: admin123')
print('数据库已存在') print('数据库已存在')

View File

@@ -1,18 +1,33 @@
from datetime import datetime from datetime import datetime
from flask_sqlalchemy import SQLAlchemy from flask_sqlalchemy import SQLAlchemy
from flask_login import UserMixin from flask_login import UserMixin
from werkzeug.security import generate_password_hash, check_password_hash
import pytz
db = SQLAlchemy() db = SQLAlchemy()
def now_shanghai():
"""返回上海时区UTC+8的当前时间"""
return datetime.now(pytz.timezone('Asia/Shanghai'))
class User(UserMixin, db.Model): class User(UserMixin, db.Model):
id = db.Column(db.Integer, primary_key=True) id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(80), unique=True, nullable=False) username = db.Column(db.String(80), unique=True, nullable=False)
password_hash = db.Column(db.String(200))
role = db.Column(db.String(20), nullable=False, default='user') role = db.Column(db.String(20), nullable=False, default='user')
contact = db.Column(db.String(100)) contact = db.Column(db.String(100))
dingtalk_userid = db.Column(db.String(100), unique=True, nullable=False) dingtalk_userid = db.Column(db.String(100), unique=True)
dingtalk_name = db.Column(db.String(100)) dingtalk_name = db.Column(db.String(100))
dingtalk_dept = db.Column(db.String(200)) dingtalk_dept = db.Column(db.String(200))
created_at = db.Column(db.DateTime, default=datetime.utcnow) created_at = db.Column(db.DateTime, default=now_shanghai)
def set_password(self, password):
self.password_hash = generate_password_hash(password)
def check_password(self, password):
if not self.password_hash:
return False
return check_password_hash(self.password_hash, password)
def is_admin(self): def is_admin(self):
return self.role == 'admin' return self.role == 'admin'
@@ -27,8 +42,8 @@ class Demand(db.Model):
answer = db.Column(db.Text) answer = db.Column(db.Text)
answered_at = db.Column(db.DateTime) answered_at = db.Column(db.DateTime)
user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False) user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
created_at = db.Column(db.DateTime, default=datetime.utcnow) created_at = db.Column(db.DateTime, default=now_shanghai)
updated_at = db.Column(db.DateTime, default=datetime.utcnow) updated_at = db.Column(db.DateTime, default=now_shanghai)
user = db.relationship('User', backref=db.backref('demands', lazy=True)) user = db.relationship('User', backref=db.backref('demands', lazy=True))

30
package.sh Executable file
View File

@@ -0,0 +1,30 @@
#!/bin/bash
# 项目名称
PROJECT_NAME="mashangban"
# 打包输出目录
OUTPUT_DIR="dist"
# 创建输出目录
mkdir -p $OUTPUT_DIR
# 创建打包文件
echo "开始打包..."
# 使用 tar 打包项目文件
tar -czvf "$OUTPUT_DIR/${PROJECT_NAME}_$(date +%Y%m%d_%H%M%S).tar.gz" \
--exclude='__pycache__' \
--exclude='*.pyc' \
--exclude='.git' \
--exclude='.codebuddy' \
--exclude='node_modules' \
--exclude='*.db' \
--exclude='venv' \
--exclude='env' \
--exclude='.env' \
-C /Users/justin/df_project \
$PROJECT_NAME
echo "打包完成!文件保存在 $OUTPUT_DIR 目录"
ls -lh $OUTPUT_DIR/

View File

@@ -4,3 +4,4 @@ Flask-WTF==1.2.1
Flask-Login==0.6.3 Flask-Login==0.6.3
requests==2.31.0 requests==2.31.0
python-dotenv==1.0.0 python-dotenv==1.0.0
pytz==2024.1

101
routes.py
View File

@@ -1,9 +1,9 @@
from datetime import datetime
from flask import render_template, redirect, url_for, flash, request from flask import render_template, redirect, url_for, flash, request
from math import ceil
from flask_login import login_user, logout_user, login_required, current_user from flask_login import login_user, logout_user, login_required, current_user
from __init__ import app, db from __init__ import app, db
from models import User, Demand from models import User, Demand, now_shanghai
from forms import DemandForm, AnswerForm from forms import DemandForm, AnswerForm, LoginForm, RegisterForm
BRANCH_NAMES = { BRANCH_NAMES = {
'comprehensive': '综合分会', 'comprehensive': '综合分会',
@@ -21,6 +21,53 @@ def utility_processor():
return BRANCH_NAMES.get(branch_key, branch_key) return BRANCH_NAMES.get(branch_key, branch_key)
return dict(get_branch_name=get_branch_name) return dict(get_branch_name=get_branch_name)
@app.route('/login', methods=['GET', 'POST'])
def login():
if current_user.is_authenticated:
return redirect(url_for('index'))
form = LoginForm()
if form.validate_on_submit():
user = User.query.filter_by(username=form.username.data).first()
if user and user.check_password(form.password.data):
login_user(user)
flash('登录成功', 'success')
next_page = request.args.get('next')
return redirect(next_page) if next_page else redirect(url_for('index'))
else:
flash('用户名或密码错误', 'error')
return render_template('login.html', form=form)
@app.route('/logout')
@login_required
def logout():
logout_user()
flash('已退出登录', 'info')
return redirect(url_for('index'))
@app.route('/register', methods=['GET', 'POST'])
def register():
if current_user.is_authenticated:
return redirect(url_for('index'))
form = RegisterForm()
if form.validate_on_submit():
if form.password.data != form.confirm_password.data:
flash('两次输入的密码不一致', 'warning')
return render_template('register.html', form=form)
existing_user = User.query.filter_by(username=form.username.data).first()
if existing_user:
flash('用户名已存在', 'warning')
else:
user = User(
username=form.username.data,
role='user'
)
user.set_password(form.password.data)
db.session.add(user)
db.session.commit()
flash('注册成功,请登录', 'success')
return redirect(url_for('login'))
return render_template('register.html', form=form)
@app.before_request @app.before_request
def before_request(): def before_request():
user_id = request.args.get('userId') user_id = request.args.get('userId')
@@ -55,8 +102,12 @@ def before_request():
@app.route('/') @app.route('/')
def index(): def index():
demands = Demand.query.filter_by(is_public=True).order_by(Demand.created_at.desc()).all() page = request.args.get('page', 1, type=int)
return render_template('index.html', demands=demands) per_page = 10
pagination = Demand.query.filter_by(is_public=True).order_by(Demand.created_at.desc()).paginate(
page=page, per_page=per_page, error_out=False
)
return render_template('index.html', demands=pagination.items, pagination=pagination)
@app.route('/demand/new', methods=['GET', 'POST']) @app.route('/demand/new', methods=['GET', 'POST'])
def new_demand(): def new_demand():
@@ -74,7 +125,7 @@ def new_demand():
) )
db.session.add(demand) db.session.add(demand)
db.session.commit() db.session.commit()
flash('需求提交成功') flash('需求提交成功', 'success')
return redirect(url_for('index')) return redirect(url_for('index'))
return render_template('demand_form.html', form=form, title='提交新需求') return render_template('demand_form.html', form=form, title='提交新需求')
@@ -84,7 +135,7 @@ def edit_demand(id):
return render_template('not_logged_in.html') return render_template('not_logged_in.html')
demand = Demand.query.get_or_404(id) demand = Demand.query.get_or_404(id)
if not demand.can_edit(current_user): if not demand.can_edit(current_user):
flash('无权限编辑此需求') flash('无权限编辑此需求', 'error')
return redirect(url_for('index')) return redirect(url_for('index'))
form = DemandForm(obj=demand) form = DemandForm(obj=demand)
if form.validate_on_submit(): if form.validate_on_submit():
@@ -94,9 +145,9 @@ def edit_demand(id):
demand.contact = form.contact.data demand.contact = form.contact.data
if current_user.is_admin() or not demand.answer: if current_user.is_admin() or not demand.answer:
demand.is_public = form.is_public.data demand.is_public = form.is_public.data
demand.updated_at = datetime.utcnow() demand.updated_at = now_shanghai()
db.session.commit() db.session.commit()
flash('需求更新成功') flash('需求更新成功', 'success')
return redirect(url_for('index')) return redirect(url_for('index'))
return render_template('demand_form.html', form=form, title='编辑需求', demand=demand) return render_template('demand_form.html', form=form, title='编辑需求', demand=demand)
@@ -106,14 +157,14 @@ def answer_demand(id):
return render_template('not_logged_in.html') return render_template('not_logged_in.html')
demand = Demand.query.get_or_404(id) demand = Demand.query.get_or_404(id)
if not current_user.is_admin(): if not current_user.is_admin():
flash('只有管理员可以回答需求') flash('只有管理员可以回答需求', 'error')
return redirect(url_for('index')) return redirect(url_for('index'))
form = AnswerForm(data={'answer': demand.answer or ''}) form = AnswerForm(data={'answer': demand.answer or ''})
if form.validate_on_submit(): if form.validate_on_submit():
demand.answer = form.answer.data demand.answer = form.answer.data
demand.answered_at = datetime.utcnow() demand.answered_at = now_shanghai()
db.session.commit() db.session.commit()
flash('回答已保存') flash('回答已保存', 'success')
return redirect(url_for('index')) return redirect(url_for('index'))
return render_template('answer_form.html', form=form, demand=demand) return render_template('answer_form.html', form=form, demand=demand)
@@ -123,26 +174,34 @@ def toggle_public(id):
return render_template('not_logged_in.html') return render_template('not_logged_in.html')
demand = Demand.query.get_or_404(id) demand = Demand.query.get_or_404(id)
if not current_user.is_admin(): if not current_user.is_admin():
flash('只有管理员可以修改公开状态') flash('只有管理员可以修改公开状态', 'error')
return redirect(url_for('index')) return redirect(url_for(endpoint='admin_demands'))
demand.is_public = not demand.is_public demand.is_public = not demand.is_public
db.session.commit() db.session.commit()
flash('公开状态已更新') flash('公开状态已更新', 'success')
return redirect(url_for('index')) return redirect(url_for('admin_demands'))
@app.route('/my_demands') @app.route('/my_demands')
def my_demands(): def my_demands():
if not current_user.is_authenticated: if not current_user.is_authenticated:
return render_template('not_logged_in.html') return render_template('not_logged_in.html')
demands = Demand.query.filter_by(user_id=current_user.id).order_by(Demand.created_at.desc()).all() page = request.args.get('page', 1, type=int)
return render_template('my_demands.html', demands=demands) per_page = 10
pagination = Demand.query.filter_by(user_id=current_user.id).order_by(Demand.created_at.desc()).paginate(
page=page, per_page=per_page, error_out=False
)
return render_template('my_demands.html', demands=pagination.items, pagination=pagination)
@app.route('/admin/demands') @app.route('/admin/demands')
def admin_demands(): def admin_demands():
if not current_user.is_authenticated: if not current_user.is_authenticated:
return render_template('not_logged_in.html') return render_template('not_logged_in.html')
if not current_user.is_admin(): if not current_user.is_admin():
flash('无权限访问此页面') flash('无权限访问此页面', 'error')
return redirect(url_for('index')) return redirect(url_for('index'))
demands = Demand.query.order_by(Demand.created_at.desc()).all() page = request.args.get('page', 1, type=int)
return render_template('admin_demands.html', demands=demands) per_page = 10
pagination = Demand.query.order_by(Demand.created_at.desc()).paginate(
page=page, per_page=per_page, error_out=False
)
return render_template('admin_demands.html', demands=pagination.items, pagination=pagination)

View File

@@ -433,6 +433,217 @@ h2 {
color: #856404; color: #856404;
} }
.login-container {
max-width: 400px;
margin: 40px auto;
padding: 30px;
background: white;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
}
.login-container h2 {
text-align: center;
margin-bottom: 30px;
color: #4a90d9;
}
.login-container .form-group {
margin-bottom: 20px;
}
.login-container .form-control {
width: 100%;
padding: 12px;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 14px;
}
.login-container .form-control:focus {
outline: none;
border-color: #4a90d9;
}
.login-container .btn-primary {
width: 100%;
padding: 12px;
background-color: #4a90d9;
color: white;
border: none;
border-radius: 4px;
font-size: 16px;
cursor: pointer;
}
.login-container .btn-primary:hover {
background-color: #3a80c9;
}
.login-link {
text-align: center;
margin-top: 20px;
color: #666;
}
.login-link a {
color: #4a90d9;
text-decoration: none;
}
.login-link a:hover {
text-decoration: underline;
}
.dingtalk-login {
margin-top: 30px;
padding-top: 20px;
border-top: 1px solid #eee;
text-align: center;
}
.dingtalk-login p {
color: #999;
margin-bottom: 15px;
font-size: 14px;
}
.btn-dingtalk {
background-color: #1677ff;
color: white;
padding: 10px 20px;
border-radius: 4px;
text-decoration: none;
display: inline-block;
}
.btn-dingtalk:hover {
background-color: #0958d9;
}
.login-options {
display: flex;
flex-direction: column;
gap: 15px;
align-items: center;
margin-top: 30px;
}
.login-options .btn {
min-width: 200px;
}
.btn-secondary {
background-color: #6c757d;
color: white;
}
.btn-secondary:hover {
background-color: #5c636a;
}
.error {
color: #dc3545;
font-size: 14px;
margin-top: 5px;
}
.form-group .error {
color: #dc3545;
font-size: 14px;
margin-top: 5px;
}
/* Toast 弹出提示 - Element UI 风格 */
.toast-container {
position: fixed;
top: 20px;
left: 50%;
transform: translateX(-50%);
z-index: 9999;
display: flex;
flex-direction: column;
align-items: center;
gap: 10px;
pointer-events: none;
}
.toast {
background: white;
padding: 12px 20px;
border-radius: 4px;
box-shadow: 0 2px 12px rgba(0,0,0,0.15);
font-size: 14px;
min-width: 300px;
max-width: 500px;
text-align: center;
opacity: 0;
transform: translateY(-20px);
transition: all 0.3s ease;
pointer-events: auto;
}
.toast.show {
opacity: 1;
transform: translateY(0);
}
.toast-success {
background: #f0f9eb;
border: 1px solid #e1f3d8;
color: #67c23a;
}
.toast-warning {
background: #fdf6ec;
border: 1px solid #faecd8;
color: #e6a23c;
}
.toast-error {
background: #fef0f0;
border: 1px solid #fde2e2;
color: #f56c6c;
}
.toast-info {
background: #edf2fc;
border: 1px solid #d3d4d6;
color: #909399;
}
.toast .toast-icon {
margin-right: 8px;
}
/* 分页样式 */
.pagination {
display: flex;
justify-content: center;
align-items: center;
gap: 15px;
margin-top: 30px;
padding: 20px 0;
}
.btn-page {
background-color: #4a90d9;
color: white;
padding: 8px 16px;
border-radius: 4px;
text-decoration: none;
font-size: 14px;
}
.btn-page:hover {
background-color: #3a80c9;
}
.page-info {
color: #666;
font-size: 14px;
}
.question-preview { .question-preview {
background: #f8f9fa; background: #f8f9fa;
padding: 20px; padding: 20px;

View File

@@ -9,23 +9,27 @@
{% for demand in demands %} {% for demand in demands %}
<div class="question-item"> <div class="question-item">
<div class="question-header"> <div class="question-header">
<span class="branch">{{ get_branch_name(demand.branch) }}</span> <h3 class="demand-title">{{ demand.title }} <span class="branch">{{ get_branch_name(demand.branch) }}</span></h3>
<span class="time">{{ demand.created_at.strftime('%Y-%m-%d %H:%M') }}</span>
<span class="status {% if demand.is_public %}public{% else %}private{% endif %}"> <span class="status {% if demand.is_public %}public{% else %}private{% endif %}">
{% if demand.is_public %}公开{% else %}私有{% endif %} {% if demand.is_public %}公开{% else %}私有{% endif %}
</span> </span>
<span class="user">提交者: {{ demand.user.username }}</span>
</div> </div>
<h3 class="demand-title">{{ demand.title }}</h3> <span class="time">提交者: {{ demand.user.username }} &nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 提交时间: {{ demand.created_at.strftime('%Y-%m-%d %H:%M') }}</span>
<p class="content">{{ demand.content }}</p> <p class="content">{{ demand.content }}</p>
<div class="question-footer"> <div class="question-footer">
<span class="contact">联系方式: {{ demand.contact }}</span> <span class="contact">联系方式: {{ demand.contact }}</span>
{% if demand.answer %} {% if demand.answer %}
<div class="answer-toggle" onclick="toggleAnswer(this)">
<span class="toggle-icon"></span>
<span class="toggle-text">查看回答</span>
</div>
<div class="answer-content" style="display: none;">
<div class="answer"> <div class="answer">
<strong>回答:</strong> <strong>回答:</strong>
<p>{{ demand.answer }}</p> <p>{{ demand.answer }}</p>
<span class="answer-time">回答时间: {{ demand.answered_at.strftime('%Y-%m-%d %H:%M') }}</span> <span class="answer-time">回答时间: {{ demand.answered_at.strftime('%Y-%m-%d %H:%M') }}</span>
</div> </div>
</div>
{% else %} {% else %}
<span class="status waiting">待回答</span> <span class="status waiting">待回答</span>
{% endif %} {% endif %}
@@ -42,7 +46,41 @@
</div> </div>
{% endfor %} {% endfor %}
</div> </div>
{% if pagination.pages > 1 %}
<div class="pagination">
{% if pagination.has_prev %}
<a href="{{ url_for('admin_demands', page=pagination.prev_num) }}" class="btn btn-page">上一页</a>
{% endif %}
<span class="page-info">
共 {{ pagination.total }} 条,第 {{ pagination.page }} / {{ pagination.pages }} 页
</span>
{% if pagination.has_next %}
<a href="{{ url_for('admin_demands', page=pagination.next_num) }}" class="btn btn-page">下一页</a>
{% endif %}
</div>
{% endif %}
{% else %} {% else %}
<p class="empty">暂无需求</p> <p class="empty">暂无需求</p>
{% endif %} {% endif %}
<script>
function toggleAnswer(element) {
var content = element.nextElementSibling;
var icon = element.querySelector('.toggle-icon');
var text = element.querySelector('.toggle-text');
if (content.style.display === 'none') {
content.style.display = 'block';
icon.textContent = '▼';
text.textContent = '收起回答';
} else {
content.style.display = 'none';
icon.textContent = '▶';
text.textContent = '查看回答';
}
}
</script>
{% endblock %} {% endblock %}

View File

@@ -23,20 +23,48 @@
<li><a href="{{ url_for('admin_demands') }}">管理后台</a></li> <li><a href="{{ url_for('admin_demands') }}">管理后台</a></li>
{% endif %} {% endif %}
<li class="user-name">{{ current_user.dingtalk_name or current_user.username }}</li> <li class="user-name">{{ current_user.dingtalk_name or current_user.username }}</li>
<li><a href="{{ url_for('logout') }}">退出</a></li>
{% else %}
<li><a href="{{ url_for('login') }}">登录</a></li>
{% endif %} {% endif %}
</ul> </ul>
</nav> </nav>
</div> </div>
</header> </header>
<main class="container"> <main class="container">
{% with messages = get_flashed_messages() %} {% block content %}{% endblock %}
<div id="toast-container" class="toast-container"></div>
<script>
const toastIcons = {
success: '✓',
warning: '⚠',
error: '✕',
info: ''
};
function showToast(message, type = 'info') {
const container = document.getElementById('toast-container');
const toast = document.createElement('div');
toast.className = `toast toast-${type}`;
toast.innerHTML = `<span class="toast-icon">${toastIcons[type]}</span>${message}`;
container.appendChild(toast);
setTimeout(() => toast.classList.add('show'), 10);
setTimeout(() => {
toast.classList.remove('show');
setTimeout(() => toast.remove(), 300);
}, 3000);
}
// 显示 Flask flash messages
{% with messages = get_flashed_messages(with_categories=true) %}
{% if messages %} {% if messages %}
{% for message in messages %} {% for category, message in messages %}
<div class="flash">{{ message }}</div> showToast('{{ message }}', '{{ category }}');
{% endfor %} {% endfor %}
{% endif %} {% endif %}
{% endwith %} {% endwith %}
{% block content %}{% endblock %} </script>
</main> </main>
<footer> <footer>
<div class="container"> <div class="container">

View File

@@ -19,10 +19,9 @@
{{ form.branch(class="form-control") }} {{ form.branch(class="form-control") }}
</div> </div>
<div class="form-group"> <div class="form-group">
<label>{{ form.contact.label.text }} *</label> <label>{{ form.contact.label.text }}</label>
{{ form.contact(class="form-control") }} {{ form.contact(class="form-control") }}
</div> </div>
{{ form.is_public() }} <label for="is_public">{{ form.is_public.label.text }}</label>
<div class="form-group"> <div class="form-group">
{{ form.submit(class="btn btn-primary") }} {{ form.submit(class="btn btn-primary") }}
<a href="{{ url_for('index') }}" class="btn btn-cancel">取消</a> <a href="{{ url_for('index') }}" class="btn btn-cancel">取消</a>

View File

@@ -27,6 +27,22 @@
</div> </div>
{% endfor %} {% endfor %}
</div> </div>
{% if pagination.pages > 1 %}
<div class="pagination">
{% if pagination.has_prev %}
<a href="{{ url_for('index', page=pagination.prev_num) }}" class="btn btn-page">上一页</a>
{% endif %}
<span class="page-info">
共 {{ pagination.total }} 条,第 {{ pagination.page }} / {{ pagination.pages }} 页
</span>
{% if pagination.has_next %}
<a href="{{ url_for('index', page=pagination.next_num) }}" class="btn btn-page">下一页</a>
{% endif %}
</div>
{% endif %}
{% else %} {% else %}
<p class="empty">暂无公开需求</p> <p class="empty">暂无公开需求</p>
{% endif %} {% endif %}

32
templates/login.html Normal file
View File

@@ -0,0 +1,32 @@
{% extends 'base.html' %}
{% block title %}登录 - 人事共享服务中心"码"上办{% endblock %}
{% block content %}
<div class="login-container">
<h2>登录</h2>
<form method="POST" action="{{ url_for('login') }}">
{{ form.hidden_tag() }}
<div class="form-group">
{{ form.username.label }}
{{ form.username(class="form-control") }}
{% if form.username.errors %}
<div class="error">{{ form.username.errors[0] }}</div>
{% endif %}
</div>
<div class="form-group">
{{ form.password.label }}
{{ form.password(class="form-control") }}
{% if form.password.errors %}
<div class="error">{{ form.password.errors[0] }}</div>
{% endif %}
</div>
<div class="form-group">
{{ form.submit(class="btn btn-primary") }}
</div>
</form>
<p class="login-link">
还没有账号?<a href="{{ url_for('register') }}">立即注册</a>
</p>
</div>
{% endblock %}

View File

@@ -36,6 +36,22 @@
<p class="empty">暂无需求</p> <p class="empty">暂无需求</p>
{% endif %} {% endif %}
{% if pagination.pages > 1 %}
<div class="pagination">
{% if pagination.has_prev %}
<a href="{{ url_for('my_demands', page=pagination.prev_num) }}" class="btn btn-page">上一页</a>
{% endif %}
<span class="page-info">
共 {{ pagination.total }} 条,第 {{ pagination.page }} / {{ pagination.pages }} 页
</span>
{% if pagination.has_next %}
<a href="{{ url_for('my_demands', page=pagination.next_num) }}" class="btn btn-page">下一页</a>
{% endif %}
</div>
{% endif %}
<script> <script>
function toggleAnswer(element) { function toggleAnswer(element) {
var content = element.nextElementSibling; var content = element.nextElementSibling;

View File

@@ -5,7 +5,11 @@
{% block content %} {% block content %}
<div class="login-required"> <div class="login-required">
<div class="login-icon">🔒</div> <div class="login-icon">🔒</div>
<h2>请先通过钉钉登录</h2> <h2>请先登录</h2>
<p>本系统需要通过钉钉账号登录才能使用</p> <p>本系统需要登录才能使用</p>
<div class="login-options">
<a href="{{ url_for('login') }}" class="btn btn-primary">账号密码登录</a>
<a href="{{ url_for('register') }}" class="btn btn-secondary">注册账号</a>
</div>
</div> </div>
{% endblock %} {% endblock %}

39
templates/register.html Normal file
View File

@@ -0,0 +1,39 @@
{% extends 'base.html' %}
{% block title %}注册 - 人事共享服务中心"码"上办{% endblock %}
{% block content %}
<div class="login-container">
<h2>注册账号</h2>
<form method="POST" action="{{ url_for('register') }}">
{{ form.hidden_tag() }}
<div class="form-group">
{{ form.username.label }}
{{ form.username(class="form-control") }}
{% if form.username.errors %}
<div class="error">{{ form.username.errors[0] }}</div>
{% endif %}
</div>
<div class="form-group">
{{ form.password.label }}
{{ form.password(class="form-control") }}
{% if form.password.errors %}
<div class="error">{{ form.password.errors[0] }}</div>
{% endif %}
</div>
<div class="form-group">
{{ form.confirm_password.label }}
{{ form.confirm_password(class="form-control") }}
{% if form.confirm_password.errors %}
<div class="error">{{ form.confirm_password.errors[0] }}</div>
{% endif %}
</div>
<div class="form-group">
{{ form.submit(class="btn btn-primary") }}
</div>
</form>
<p class="login-link">
已有账号?<a href="{{ url_for('login') }}">立即登录</a>
</p>
</div>
{% endblock %}