186 lines
5.9 KiB
Python
186 lines
5.9 KiB
Python
# -*- coding: utf-8 -*-
|
||
"""ziwei-power Flask 应用 — 磁场管理打卡系统"""
|
||
|
||
import os
|
||
from datetime import timedelta
|
||
from functools import wraps
|
||
from flask import Flask, render_template, request, jsonify, session, redirect, url_for
|
||
from werkzeug.security import generate_password_hash, check_password_hash
|
||
from database import init_db, get_checkin, save_checkin, delete_checkin, get_all_checkins
|
||
|
||
app = Flask(__name__)
|
||
app.secret_key = os.urandom(24)
|
||
|
||
# 会话持久化:30 天
|
||
app.config['PERMANENT_SESSION_LIFETIME'] = timedelta(days=30)
|
||
|
||
# ── 用户库 ────────────────────────────────────────────
|
||
|
||
USERS = {
|
||
'qiukai': {
|
||
'password_hash': generate_password_hash('AiAlex2018$'),
|
||
'name': '凯哥'
|
||
}
|
||
}
|
||
|
||
# ── 登录检查装饰器 ────────────────────────────────────
|
||
|
||
def login_required(f):
|
||
@wraps(f)
|
||
def decorated(*args, **kwargs):
|
||
if not session.get('logged_in'):
|
||
return redirect(url_for('login_page', next=request.path))
|
||
return f(*args, **kwargs)
|
||
return decorated
|
||
|
||
|
||
# ── 登录路由 ──────────────────────────────────────────
|
||
|
||
@app.route('/login', methods=['GET', 'POST'])
|
||
def login_page():
|
||
if request.method == 'POST':
|
||
username = request.form.get('username', '').strip()
|
||
password = request.form.get('password', '')
|
||
remember = request.form.get('remember') == 'on'
|
||
|
||
user = USERS.get(username)
|
||
if user and check_password_hash(user['password_hash'], password):
|
||
session.permanent = remember
|
||
session['logged_in'] = True
|
||
session['username'] = username
|
||
session['display_name'] = user['name']
|
||
next_url = request.args.get('next', '/')
|
||
return jsonify({'ok': True, 'next': next_url})
|
||
else:
|
||
return jsonify({'ok': False, 'error': '账号或密码错误'}), 401
|
||
|
||
# GET:如果已登录直接跳转
|
||
if session.get('logged_in'):
|
||
return redirect(url_for('index'))
|
||
return render_template('login.html')
|
||
|
||
|
||
@app.route('/logout')
|
||
def logout():
|
||
session.clear()
|
||
return redirect(url_for('login_page'))
|
||
|
||
|
||
# ── 主页 ──────────────────────────────────────────────
|
||
|
||
def compute_stats():
|
||
"""计算统计数据,供 API 和模板共用"""
|
||
rows = get_all_checkins()
|
||
total_days = len(rows)
|
||
total_morning = 0
|
||
total_evening = 0
|
||
total_study = 0
|
||
calendar = {}
|
||
|
||
for row in rows:
|
||
d = row['date']
|
||
data = row['data']
|
||
morning = data.get('morning', [])
|
||
evening = data.get('evening', [])
|
||
study = data.get('study', [])
|
||
|
||
total_morning += sum(1 for x in morning if isinstance(x, str) and x.strip())
|
||
total_evening += sum(1 for x in evening if (
|
||
isinstance(x, str) and x.strip() or
|
||
isinstance(x, dict) and (x.get('mistake', '') or '').strip()
|
||
))
|
||
total_study += sum(1 for x in study if x.get('done'))
|
||
|
||
day_score = 0
|
||
has_morning = any(isinstance(x, str) and x.strip() for x in morning)
|
||
has_evening = any(
|
||
(isinstance(x, str) and x.strip()) or
|
||
(isinstance(x, dict) and (x.get('mistake', '') or '').strip())
|
||
for x in evening
|
||
)
|
||
has_study = any(x.get('done') for x in study)
|
||
if has_morning: day_score += 1
|
||
if has_evening: day_score += 1
|
||
if has_study: day_score += 1
|
||
|
||
calendar[d] = 'pass' if day_score >= 2 else 'fail'
|
||
|
||
return dict(
|
||
total_days=total_days, total_morning=total_morning,
|
||
total_evening=total_evening, total_study=total_study,
|
||
calendar=calendar
|
||
)
|
||
|
||
|
||
@app.route('/')
|
||
@login_required
|
||
def index():
|
||
import json
|
||
stats = compute_stats()
|
||
return render_template('index.html',
|
||
username=session.get('display_name', session.get('username', '')),
|
||
initial_stats=json.dumps(stats, ensure_ascii=False))
|
||
|
||
|
||
# ── API ──────────────────────────────────────────────
|
||
|
||
@app.route('/api/checkin', methods=['GET'])
|
||
@login_required
|
||
def api_get_checkin():
|
||
date = request.args.get('date', '')
|
||
if not date:
|
||
return jsonify({'ok': False, 'error': '缺少 date 参数'}), 400
|
||
row = get_checkin(date)
|
||
return jsonify({'ok': True, 'data': row})
|
||
|
||
|
||
@app.route('/api/checkin', methods=['POST'])
|
||
@login_required
|
||
def api_save_checkin():
|
||
body = request.get_json(force=True)
|
||
date = body.get('date', '')
|
||
if not date:
|
||
return jsonify({'ok': False, 'error': '缺少 date 字段'}), 400
|
||
data = body.get('data', {})
|
||
save_checkin(date, data)
|
||
return jsonify({'ok': True})
|
||
|
||
|
||
@app.route('/api/checkin/<date>', methods=['DELETE'])
|
||
@login_required
|
||
def api_delete_checkin(date):
|
||
delete_checkin(date)
|
||
return jsonify({'ok': True})
|
||
|
||
|
||
@app.route('/api/history', methods=['GET'])
|
||
@login_required
|
||
def api_history():
|
||
rows = get_all_checkins()
|
||
return jsonify({'ok': True, 'data': rows})
|
||
|
||
|
||
@app.route('/api/stats', methods=['GET'])
|
||
@login_required
|
||
def api_stats():
|
||
stats = compute_stats()
|
||
return jsonify({'ok': True, **stats})
|
||
|
||
|
||
@app.route('/api/user', methods=['GET'])
|
||
@login_required
|
||
def api_user():
|
||
return jsonify({
|
||
'ok': True,
|
||
'username': session.get('username'),
|
||
'display_name': session.get('display_name')
|
||
})
|
||
|
||
|
||
# ── 启动 ──────────────────────────────────────────────
|
||
|
||
if __name__ == '__main__':
|
||
init_db()
|
||
app.config['TEMPLATES_AUTO_RELOAD'] = True
|
||
app.run(host='0.0.0.0', port=5058, debug=False)
|