Flask第八课

from flask import Flask, request, url_for,render_template,jsonify
import sqlite3
from flask_cors import CORS
app = Flask(__name__)
# 全局开启跨域,适配前端页面请求
CORS(app, supports_credentials=True)
# SQLite数据库文件
DB_FILE = "score_data.db"

# 统一返回格式
def success_response(data=None, msg="操作成功"):
    return jsonify({"code": 200, "data": data, "msg": msg})

def fail_response(msg="操作失败", code=500):
    return jsonify({"code": code, "data": None, "msg": msg})

# 初始化数据库、创建数据表
def init_db():
    conn = sqlite3.connect(DB_FILE)
    cursor = conn.cursor()

    # 巡查成绩数据表,字段完全匹配前端提交参数
    create_table_sql = """
    CREATE TABLE IF NOT EXISTS score_record (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        student_id TEXT NOT NULL,
        student_name TEXT NOT NULL,
        layer1_green INTEGER DEFAULT 0,
        layer2_net INTEGER DEFAULT 0,
        layer3 INTEGER DEFAULT 0,
        cone INTEGER DEFAULT 0,
        big_square INTEGER DEFAULT 0,
        yellow_square INTEGER DEFAULT 0,
        red_blue_square INTEGER DEFAULT 0,
        flag INTEGER DEFAULT 0,
        violation INTEGER DEFAULT 0,
        total INTEGER DEFAULT 0,
        remark TEXT,
        create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP
    )
    """
    cursor.execute(create_table_sql)
    conn.commit()
    conn.close()

# 数据库通用查询工具
def db_query(sql, params=()):
    conn = sqlite3.connect(DB_FILE)
    conn.row_factory = sqlite3.Row
    cursor = conn.cursor()
    res = cursor.execute(sql, params).fetchall()
    conn.close()
    return [dict(row) for row in res]

# 数据库通用执行(增/改/删)
def db_execute(sql, params=()):
    conn = sqlite3.connect(DB_FILE)
    cursor = conn.cursor()
    cursor.execute(sql, params)
    last_id = cursor.lastrowid
    conn.commit()
    conn.close()
    return last_id

# 内置测试学生数据(前端下拉使用,不存入数据库)
STUDENT_LIST = [
    {"id": "2026001", "name": "张三1"},
    {"id": "2026002", "name": "李四2"},
    {"id": "2026003", "name": "王五3"},
    {"id": "2026004", "name": "赵六"},
    {"id": "2026005", "name": "钱七"},
    {"id": "2026006", "name": "孙八"}
]

# 接口1:获取学生列表(静态测试数据)
@app.route("/api/student/list", methods=["GET"])
def get_student_list():
    return success_response(data=STUDENT_LIST, msg="学生列表查询成功")

# 接口2:提交巡查成绩,写入SQLite数据库
@app.route("/api/score/add", methods=["POST"])
def add_score():
    try:
        data = request.get_json()
        if not data:
            return fail_response(msg="请求参数不能为空")

        # 提取参数
        student_id = data.get("studentId", "").strip()
        student_name = data.get("studentName", "").strip()
        layer1_green = data.get("layer1Green", 0)
        layer2_net = data.get("layer2Net", 0)
        layer3 = data.get("layer3", 0)
        cone = data.get("cone", 0)
        big_square = data.get("bigSquare", 0)
        yellow_square = data.get("yellowSquare", 0)
        red_blue_square = data.get("redBlueSquare", 0)
        flag = data.get("flag", 0)
        violation = data.get("violation", 0)
        total = data.get("total", 0)
        remark = data.get("remark", "").strip()

        # 参数校验
        if not student_id or not student_name:
            return fail_response(msg="学号和姓名不能为空")

        normal_scores = [layer1_green, layer2_net, layer3, cone, big_square, yellow_square, red_blue_square, flag]
        for score in normal_scores:
            if not (0 <= score <= 100):
                return fail_response(msg="巡查项目分数必须在0-100之间")

        # 插入数据库SQL
        insert_sql = """
        INSERT INTO score_record (
            student_id, student_name, layer1_green, layer2_net, layer3,
            cone, big_square, yellow_square, red_blue_square, flag,
            violation, total, remark
        ) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?)
        """
        params = (
            student_id, student_name, layer1_green, layer2_net, layer3,
            cone, big_square, yellow_square, red_blue_square, flag,
            violation, total, remark
        )
        record_id = db_execute(insert_sql, params)

        print(f"数据入库成功,记录ID:{record_id}")
        return success_response(msg="成绩录入成功,已持久化保存到数据库")

    except Exception as e:
        return fail_response(msg=f"录入失败:{str(e)}")

# 扩展接口3:根据学号查询该学生所有评分记录(可选,前端可调用)
@app.route("/api/score/search/<student_id>", methods=["GET"])
def search_score(student_id):
    try:
        sql = "SELECT * FROM score_record WHERE student_id = ? ORDER BY create_time DESC"
        records = db_query(sql, (student_id,))
        return success_response(data=records, msg="查询成功")
    except Exception as e:
        return fail_response(msg=f"查询失败:{str(e)}")

# 扩展接口4:查询全部成绩记录(分页可自行扩展)
@app.route("/api/score/all", methods=["GET"])
def get_all_score():
    try:
        sql = "SELECT * FROM score_record ORDER BY create_time DESC"
        records = db_query(sql)
        return success_response(data=records, msg="查询全部记录成功")
    except Exception as e:
        return fail_response(msg=f"查询失败:{str(e)}")

@app.route('/explorer')
def explorer_page():
    return render_template("luru.html")

@app.route('/')
def index():
    conn = sqlite3.connect('db.db')
    c = conn.cursor()
    conn.row_factory = sqlite3.Row
    cursor = c.execute(f"SELECT * from users where id=1")
    rows = cursor.fetchall()
    
    cursor = c.execute("SELECT id,name,age,sex,area,img  from singer")
    singers = cursor.fetchall() #获取结果集合
    return render_template("users.html",Row=rows[0],Singer=singers)
    

@app.route('/inittable')
def inittable():
    init_db()
    list=['CREATE TABLE if not exists users(   ID INT,   name  CHAR(50) ,   age   INT,   userid char(50),   pwd char(50),   del int   )']
    list.append('CREATE TABLE IF NOT exists singer(   ID INT,   name  CHAR(50) ,age INT,sex char(10),   area char(50),   img text )')
    list.append('CREATE TABLE IF NOT exists songs(ID INT,name  CHAR(50) ,url text ,singer int)')
    list.append('CREATE TABLE IF NOT exists songsmenu(   ID INT,   userid int,   songsid int,   index_s int);')
    conn = sqlite3.connect('db.db')
    c = conn.cursor()
    for sql in list:
        cursor = c.execute(sql)
        rows = cursor.fetchall()
        print(rows)
    conn.commit()
    return "ok"


@app.route('/initdata')
def initdata():
    list=[]
    list.append("""insert into users values(1,'宋阳',14,'songyang','123456',0);""")
    list.append("""INSERT INTO singer (ID, name, age, sex, area, img) VALUES (1, '周杰伦', 14, '男', '台湾', 'https://c1.kgimg.com/custom/240/20251125/20251125182917808816.jpg'); """)
    list.append("""INSERT INTO singer (ID, name, age, sex, area, img) VALUES (3, '李荣浩', 14, '男', '内地', 'https://img0.baidu.com/it/u=1856246291,3042340795&fm=253&fmt=auto&app=120&f=JPEG?w=920&h=800'); """)
    list.append("""INSERT INTO singer (ID, name, age, sex, area, img) VALUES (2, '毛不易', 14, '男', '内地', 'https://q9.itc.cn/images01/20250807/74936defa6164fe09caa8619cfb65f02.jpeg'); """)
    list.append(""" INSERT INTO singer (ID, name, age, sex, area, img) VALUES (4, '邓紫棋', 14, '女', '台湾', 'https://q1.itc.cn/images01/20260614/ef89e43312104373857654979bff9933.jpeg');""")
    list.append("""insert into songs values(1,'稻香','http://listen.92kk.com//2019/%E4%B8%B2%E7%83%A7%E8%88%9E%E6%9B%B2/201912/20191202/%E6%9C%80%E6%96%B0%E5%A5%BD%E5%90%AC%E7%BD%91%E7%BB%9C%E4%BC%A4%E6%84%9F%E6%AD%8C%E6%9B%B2%E6%8E%A8%E8%8D%90%E9%80%9F%E9%80%92%E8%BD%A6%E8%BD%BDCD%E5%A4%A7%E7%A2%9F-%E9%85%B7%E9%9F%B3%E9%A2%86%E5%9F%9F-DJ%E5%B1%B1%E4%BB%94.mp3',1); """)
    list.append("""insert into songs values(2,'双截棍','%E4%B94.mp3',1); """)
    list.append("""insert into songs values(3,'青花瓷','%E4%B94.mp3',1); """)
    list.append("""insert into songs values(4,'烟花易冷','%E4%B94.mp3',1); """)
    list.append("""insert into songsmenu values(1,1,1,1); """)

    conn = sqlite3.connect('db.db')
    c = conn.cursor()
    for sql in list:
        cursor = c.execute(sql)
        rows = cursor.fetchall()
        print(rows)
    conn.commit()
    return "ok"
@app.route('/greet/<username>')
def greeting(username):
    return f'hello, {username} from ip: {request.remote_addr}'

@app.route('/login')
def login():
    form = '''
        <form action="/password" method="get">
            <input type="text" name="username" placeholder="用户名"></input>
            <input type="password" name="password" placeholder="密码"></input>
            <input type="submit" value="登录"></input>
        </form>
    '''
    return form

def get_user_list():
   
    return 'users', 'passwords'                   # 返回 用户名列表 密码列表

@app.route('/password')
def password():
    username = request.args.get('username')   
    password = request.args.get('password')  
    conn = sqlite3.connect('db.db')
    c = conn.cursor()
    cursor = c.execute(f"SELECT *  from users where userid='{username}' and pwd='{password}'")
    rows = cursor.fetchall()
    if len(rows)==1:
        return "OK"
    return "失败"



app.run(debug=True, host='0.0.0.0', port=80)
<!DOCTYPE html>
<html lang="zh-CN">
  <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>个人中心</title>
    <!-- 引入Bootstrap5 CSS -->
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
    <!-- 图标库 -->
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.0/font/bootstrap-icons.css">
    <style>
      * {
        margin: 0;
        padding: 0;
        box-sizing: border-box;
      }
      body {
        background-color: #f5f7fa;
        font-family: "Microsoft Yahei", sans-serif;
      }
      /* 顶部导航栏 */
      .top-nav {
        background: #fff;
        box-shadow: 0 2px 8px rgba(0,0,0,0.08);
        height: 60px;
        line-height: 60px;
      }
      /* 侧边菜单 */
      .sidebar-card {
        background: #fff;
        border-radius: 8px;
        box-shadow: 0 2px 12px rgba(0,0,0,0.05);
        overflow: hidden;
      }
      .user-info-head {
        background: linear-gradient(135deg, #409eff, #66b1ff);
        color: #fff;
        padding: 30px 20px;
        text-align: center;
      }
      .avatar {
        width: 80px;
        height: 80px;
        border-radius: 50%;
        border: 3px solid #fff;
        object-fit: cover;
        background: #eee;
      }
      .menu-item {
        padding: 14px 24px;
        cursor: pointer;
        transition: all 0.25s;
        color: #606266;
        display: flex;
        align-items: center;
        gap: 10px;
      }
      .menu-item:hover, .menu-item.active {
        background-color: #ecf5ff;
        color: #409eff;
      }
      /* 主内容卡片 */
      .content-card {
        background: #fff;
        border-radius: 8px;
        box-shadow: 0 2px 12px rgba(0,0,0,0.05);
        min-height: 600px;
      }
      .tab-panel {
        display: none;
      }
      .tab-panel.active {
        display: block;
      }
      /* 订单状态标签 */
      .status-tag {
        padding: 4px 10px;
        border-radius: 4px;
        font-size: 13px;
      }
      .wait-pay {
        background: #fff7e6;
        color: #ff7d00;
      }
      .wait-deliver {
        background: #e6f7ff;
        color: #1890ff;
      }
      .finish {
        background: #f0fff4;
        color: #00b42a;
      }
    </style>
  </head>
  <body>
    <!-- 顶部导航 -->
    <nav class="top-nav px-4">
      <div class="container d-flex justify-content-between align-items-center">
        <div class="fs-4 fw-bold text-primary">我的商城</div>
        <div>
          <a href="#" class="text-secondary text-decoration-none me-4">首页</a>
          <a href="#" class="text-primary text-decoration-none fw-medium">个人中心</a>
        </div>
  </div>
    </nav>

    <div class="container py-5">
        <div class="row g-4">
            <!-- 左侧侧边栏 -->
            <div class="col-lg-3 col-md-4">
                <div class="sidebar-card">
                    <div class="user-info-head">
                        <img src="https://picsum.photos/id/1005/80/80" class="avatar mb-3" alt="头像">
                        <h5 class="mb-1">{{  Row[1]  }}</h5>
                        <span class="opacity-75">年龄: {{ Row[2] }}</span>
                    </div>
                    <div class="menu-list">
                        <div class="menu-item active" data-target="info">
                            <i class="bi bi-person"></i>个人资料
                        </div>
                        <div class="menu-item" data-target="order">
                            <i class="bi bi-cart-check"></i>我的订单
                        </div>
                        <div class="menu-item" data-target="address">
                            <i class="bi bi-geo-alt"></i>收货地址
                        </div>
                        <div class="menu-item" data-target="collect">
                            <i class="bi bi-heart"></i>我的收藏
                        </div>
                        <div class="menu-item" data-target="safe">
                            <i class="bi bi-shield-lock"></i>账号安全
                        </div>
                        <div class="menu-item text-danger" data-target="logout">
                            <i class="bi bi-box-arrow-right"></i>退出登录
                        </div>
                    </div>
                </div>
            </div>

            <!-- 右侧主内容区 -->
            <div class="col-lg-9 col-md-8">
                <div class="content-card p-4">
                    <!-- 个人资料面板 -->
                    <div class="tab-panel active" id="info">
                        <h4 class="border-bottom pb-3 mb-4">个人资料</h4>
                        <form class="row g-3 w-75">
                            <div class="col-md-6">
                                <label class="form-label">用户名</label>
                                <input type="text" class="form-control" value="{{ Row[3]}}">
                            </div>
                            <div class="col-md-6">
                                <label class="form-label">昵称</label>
                                <input type="text" class="form-control" value="{{ Row[1]}}">
                            </div>
                            <div class="col-md-6">
                                <label class="form-label">手机号</label>
                                <input type="tel" class="form-control" value="138****1234" disabled>
                            </div>
                            <div class="col-md-6">
                                <label class="form-label">邮箱</label>
                                <input type="email" class="form-control" value="zhangsan@163.com">
                            </div>
                            <div class="col-12">
                                <label class="form-label">个性签名</label>
                                <textarea class="form-control" rows="3">生活明朗,万物可爱 {{Row[4]}}</textarea>
                            </div>
                            <div class="col-12">
                                <button type="button" class="btn btn-primary px-4">保存修改</button>
                            </div>
                        </form>
                    </div>

                    <!-- 我的订单面板 -->
                    <div class="tab-panel" id="order">
                        <h4 class="border-bottom pb-3 mb-4">我的订单</h4>
                        <div class="d-flex gap-3 mb-4">
                            <button class="btn btn-outline-primary active">全部订单</button>
                            <button class="btn btn-outline-secondary">待付款</button>
                            <button class="btn btn-outline-secondary">待发货</button>
                            <button class="btn btn-outline-secondary">已完成</button>
                        </div>
                        <!-- 订单列表项 -->
                        {% for s in Singer %}
                        <div class="border rounded mb-3">
                            <div class="bg-light p-3 d-flex justify-content-between border-bottom">
                               
                                <span class="status-tag wait-pay">{{s[4]}}</span>
                            </div>
                            <div class="p-3 d-flex align-items-center gap-3">
                                <img src="{{s[5]}}" width="80" height="80" class="rounded border">
                                <div class="flex-grow-1">
                                    <p class="mb-1">{{s[1]}}</p>
                                    <span class="text-secondary">{{s[2]}}</span>
                                </div>
                                <button class="btn btn-sm btn-danger">{{s[3]}}</button>
                            </div>
                        </div>
                        {% endfor %}                                             

                    <!-- 收货地址 -->
                    <div class="tab-panel" id="address">
                        <div class="d-flex justify-content-between align-items-center border-bottom pb-3 mb-4">
                            <h4>收货地址管理</h4>
                            <button class="btn btn-primary btn-sm"><i class="bi bi-plus"></i> 新增地址</button>
                        </div>
                        <div class="border rounded p-4 mb-3">
                            <div class="d-flex justify-content-between">
                                <div>
                                    <span class="fw-medium me-3">{{ Row[1] }}</span>
                                    <span>13812345678</span>
                                    <span class="badge bg-primary ms-2">默认地址</span>
                                </div>
                                <div>
                                    <a href="#" class="text-primary me-3 text-decoration-none">编辑</a>
                                    <a href="#" class="text-danger text-decoration-none">删除</a>
                                </div>
                            </div>
                            <p class="text-secondary mt-2 mb-0">河南省周口市太康县XX街道XX小区3号楼2单元501</p>
                        </div>
                        <div class="border rounded p-4">
                            <div class="d-flex justify-content-between">
                                <div>
                                    <span class="fw-medium me-3">张三</span>
                                    <span>13812345678</span>
                                </div>
                                <div>
                                    <a href="#" class="text-primary me-3 text-decoration-none">编辑</a>
                                    <a href="#" class="text-danger text-decoration-none">删除</a>
                                </div>
                            </div>
                            <p class="text-secondary mt-2 mb-0">河南省郑州市金水区花园路XX写字楼1205室</p>
                        </div>
                    </div>

                    <!-- 我的收藏 -->
                    <div class="tab-panel" id="collect">
                        <h4 class="border-bottom pb-3 mb-4">我的收藏</h4>
                        <div class="row g-4">
                            <div class="col-md-4">
                                <div class="border rounded overflow-hidden">
                                    <img src="https://picsum.photos/id/22/300/220" class="w-100">
                                    <div class="p-3">
                                        <p class="mb-1 text-truncate">高颜值无线充电宝20000毫安</p>
                                        <div class="d-flex justify-content-between">
                                            <span class="text-danger fw-bold">¥129</span>
                                            <i class="bi bi-heart-fill text-danger"></i>
                                        </div>
                                    </div>
                                </div>
                            </div>
                            <div class="col-md-4">
                                <div class="border rounded overflow-hidden">
                                    <img src="https://picsum.photos/id/36/300/220" class="w-100">
                                    <div class="p-3">
                                        <p class="mb-1 text-truncate">电竞游戏鼠标轻量化有线</p>
                                        <div class="d-flex justify-content-between">
                                            <span class="text-danger fw-bold">¥79</span>
                                            <i class="bi bi-heart-fill text-danger"></i>
                                        </div>
                                    </div>
                                </div>
                            </div>
                            <div class="col-md-4">
                                <div class="border rounded overflow-hidden">
                                    <img src="https://picsum.photos/id/42/300/220" class="w-100">
                                    <div class="p-3">
                                        <p class="mb-1 text-truncate">机械游戏耳机头戴式降噪</p>
                                        <div class="d-flex justify-content-between">
                                            <span class="text-danger fw-bold">¥159</span>
                                            <i class="bi bi-heart-fill text-danger"></i>
                                        </div>
                                    </div>
                                </div>
                            </div>
                        </div>
                    </div>

                    <!-- 账号安全 -->
                    <div class="tab-panel" id="safe">
                        <h4 class="border-bottom pb-3 mb-4">账号安全设置</h4>
                        <ul class="list-group w-75">
                            <li class="list-group-item d-flex justify-content-between align-items-center py-3">
                                <div>
                                    <h6 class="mb-1">登录密码</h6>
                                    <span class="text-secondary small">建议定期修改密码,保护账号安全</span>
                                </div>
                                <button class="btn btn-outline-primary btn-sm">修改密码</button>
                            </li>
                            <li class="list-group-item d-flex justify-content-between align-items-center py-3">
                                <div>
                                    <h6 class="mb-1">绑定手机</h6>
                                    <span class="text-secondary small">当前绑定:138****1234</span>
                                </div>
                                <button class="btn btn-outline-primary btn-sm">更换手机号</button>
                            </li>
                            <li class="list-group-item d-flex justify-content-between align-items-center py-3">
                                <div>
                                    <h6 class="mb-1">邮箱绑定</h6>
                                    <span class="text-secondary small">当前绑定:zhangsan@163.com</span>
                                </div>
                                <button class="btn btn-outline-primary btn-sm">更换邮箱</button>
                            </li>
                            <li class="list-group-item d-flex justify-content-between align-items-center py-3">
                                <div>
                                    <h6 class="mb-1">登录保护</h6>
                                    <span class="text-secondary small">异地登录短信验证已开启</span>
                                </div>
                                <div class="form-check form-switch">
                                    <input class="form-check-input" type="checkbox" checked>
                                </div>
                            </li>
                        </ul>
                    </div>

                    <!-- 退出登录弹窗提示 -->
                    <div class="tab-panel text-center py-5" id="logout">
                        <i class="bi bi-box-arrow-right text-danger" style="font-size:60px;"></i>
                        <h5 class="mt-3">确定要退出当前账号吗?</h5>
                        <p class="text-secondary">退出后需要重新登录才可使用个人中心功能</p>
                        <div class="mt-4">
                            <button class="btn btn-secondary px-4 me-3 cancel-logout">取消</button>
                            <button class="btn btn-danger px-4">确认退出</button>
                        </div>
                    </div>
                </div>
            </div>
        </div>
    </div>

    <!-- JS逻辑 -->
    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
    <script>
        const menuItems = document.querySelectorAll('.menu-item');
        const tabPanels = document.querySelectorAll('.tab-panel');
        const cancelBtn = document.querySelector('.cancel-logout');

        // 侧边菜单切换面板
        menuItems.forEach(item => {
            item.addEventListener('click', () => {
                // 清除所有激活
                menuItems.forEach(mi => mi.classList.remove('active'));
                tabPanels.forEach(tp => tp.classList.remove('active'));
                // 当前菜单激活
                item.classList.add('active');
                const targetId = item.getAttribute('data-target');
                document.getElementById(targetId).classList.add('active');
            })
        })

        // 退出登录取消返回个人资料页
        cancelBtn.addEventListener('click', () => {
            menuItems.forEach(mi => mi.classList.remove('active'));
            tabPanels.forEach(tp => tp.classList.remove('active'));
            document.querySelector('[data-target="info"]').classList.add('active');
            document.getElementById('info').classList.add('active');
        })
    </script>
</body>
</html>
<!DOCTYPE html>
<html lang="zh-CN">
  <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>博弈前线比赛训练成绩录入系统</title>
    <script src="https://cdn.tailwindcss.com"></script>
    <script src="https://cdn.jsdelivr.net/npm/axios@1.6.0/dist/axios.min.js"></script>
    <script>
      tailwind.config = {
        theme: {
          extend: {
            colors: {
              primary: '#165DFF',
              success: '#00B42A',
              danger: '#F53F3F',
              grayBg: '#F2F3F5'
            }
          }
        }
      }
    </script>
    <style type="text/tailwindcss">
      @layer utilities {
      .form-shadow {
      box-shadow: 0 2px 16px rgba(22, 93, 255, 0.1);
      }
      .input-focus {
      @apply border-primary ring-2 ring-primary/20 outline-none;
      }
      }
    </style>
  </head>
  <body class="bg-grayBg min-h-screen py-6 md:py-10 px-4">
    <div class="max-w-2xl mx-auto">
      <div class="text-center mb-8">
        <h1 class="text-[24px] md:text-[30px] font-bold text-primary">博弈前线比赛训练</h1>
        <p class="text-gray-500 mt-2">支持移动端/电脑端,搜索学号姓名快速选择学生,违例支持扣分</p>
      </div>

      <div class="bg-white rounded-xl p-5 md:p-8 form-shadow">
        <form id="scoreForm">
          <div class="mb-5">
            <label class="block text-gray-700 font-medium mb-1.5">🔍 搜索学生(输入学号/姓名过滤)</label>
            <input type="text" id="studentSearch" placeholder="输入学号或学生姓名快速筛选"
              class="w-full border border-gray-300 rounded-lg px-4 py-2.5 mb-3 transition-all focus:input-focus">
            <select id="studentSelect" size="6" class="w-full border border-gray-300 rounded-lg px-4 py-2 mb-4 focus:input-focus overflow-y-auto">
              <option value="">-- 请先搜索或选择学生 --</option>
            </select>
            <div class="grid grid-cols-1 md:grid-cols-2 gap-4">
              <div>
                <label class="block text-gray-700 font-medium mb-1.5">学号 <span class="text-danger">*</span></label>
                <input type="text" id="studentId" placeholder="下拉选择或手动输入学号"
                  class="w-full border border-gray-300 rounded-lg px-4 py-2.5 transition-all focus:input-focus">
              </div>
              <div>
                <label class="block text-gray-700 font-medium mb-1.5">学生姓名 <span class="text-danger">*</span></label>
                <input type="text" id="studentName" placeholder="下拉选择或手动输入姓名"
                  class="w-full border border-gray-300 rounded-lg px-4 py-2.5 transition-all focus:input-focus">
              </div>
            </div>
          </div>

          <h3 class="text-gray-700 font-semibold mb-3 border-l-4 border-primary pl-3">巡查项目评分(违例支持扣分)</h3>
          <div class="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-4 mb-6">
            <div>
              <label class="block text-gray-600 mb-1">一层绿地</label>
              <input type="number" min="0" max="100" id="layer1Green" placeholder="0-100"
                class="w-full border border-gray-300 rounded-lg px-4 py-2.5 transition-all focus:input-focus">
            </div>
            <div>
            <label class="block text-gray-600 mb-1">二层铁网</label>
            <input type="number" min="0" max="100" id="layer2Net" placeholder="0-100"
              class="w-full border border-gray-300 rounded-lg px-4 py-2.5 transition-all focus:input-focus">
          </div>
          <div>
            <label class="block text-gray-600 mb-1">三层</label>
            <input type="number" min="0" max="100" id="layer3" placeholder="0-100"
              class="w-full border border-gray-300 rounded-lg px-4 py-2.5 transition-all focus:input-focus">
          </div>
          <div>
            <label class="block text-gray-600 mb-1">锥桶</label>
            <input type="number" min="0" max="100" id="cone" placeholder="0-100"
              class="w-full border border-gray-300 rounded-lg px-4 py-2.5 transition-all focus:input-focus">
          </div>
          <div>
            <label class="block text-gray-600 mb-1">大方块</label>
            <input type="number" min="0" max="100" id="bigSquare" placeholder="0-100"
              class="w-full border border-gray-300 rounded-lg px-4 py-2.5 transition-all focus:input-focus">
          </div>
          <div>
            <label class="block text-gray-600 mb-1">黄色方块</label>
            <input type="number" min="0" max="100" id="yellowSquare" placeholder="0-100"
              class="w-full border border-gray-300 rounded-lg px-4 py-2.5 transition-all focus:input-focus">
          </div>
          <div>
            <label class="block text-gray-600 mb-1">红蓝方块</label>
            <input type="number" min="0" max="100" id="redBlueSquare" placeholder="0-100"
              class="w-full border border-gray-300 rounded-lg px-4 py-2.5 transition-all focus:input-focus">
          </div>
          <div>
            <label class="block text-gray-600 mb-1">旗帜</label>
            <input type="number" min="0" max="100" id="flag" placeholder="0-100"
              class="w-full border border-gray-300 rounded-lg px-4 py-2.5 transition-all focus:input-focus">
          </div>
          <div>
            <label class="block text-gray-600 mb-1"><span class="text-danger">违例(可扣分)</span></label>
            <input type="number" id="violation" placeholder="正数加分/负数扣分"
              class="w-full border border-gray-300 rounded-lg px-4 py-2.5 transition-all focus:input-focus">
          </div>
          <div class="md:col-span-3 sm:col-span-2 col-span-1">
            <label class="block text-gray-600 mb-1 font-medium">总分(自动计算)</label>
            <input type="text" id="totalScore" readonly value="0"
              class="w-full border border-gray-200 bg-gray-50 rounded-lg px-4 py-2.5 text-gray-500 font-bold">
          </div>
        </div>

        <div class="mb-7">
          <label class="block text-gray-700 font-medium mb-1.5">备注信息</label>
          <textarea id="remark" rows="3" placeholder="可填写违规说明、特殊情况等"
            class="w-full border border-gray-300 rounded-lg px-4 py-2.5 resize-none transition-all focus:input-focus"></textarea>
        </div>

        <div class="flex flex-col sm:flex-row gap-3">
          <button type="submit" id="submitBtn"
            class="flex-1 bg-primary hover:bg-primary/90 text-white font-medium py-3 rounded-lg transition-all flex items-center justify-center gap-2">
            <span>提交保存成绩</span>
          </button>
          <button type="reset" id="resetBtn"
            class="flex-1 bg-gray-100 hover:bg-gray-200 text-gray-700 font-medium py-3 rounded-lg transition-all">
            重置表单
          </button>
          <button type="button" id="continueBtn"
            class="flex-1 bg-success/10 hover:bg-success/20 text-success font-medium py-3 rounded-lg transition-all">
            连续录入下一位
          </button>
        </div>
      </form>
    </div>

    <div id="msgModal" class="fixed inset-0 bg-black/40 hidden items-center justify-center z-50">
      <div class="bg-white rounded-lg p-6 w-[90%] max-w-sm text-center">
        <div id="modalIcon" class="text-[40px] mb-3"></div>
        <p id="modalText" class="text-gray-700 text-lg"></p>
        <button id="closeModal" class="mt-5 px-6 py-2 bg-primary text-white rounded-lg">确定</button>
      </div>
    </div>
  </div>

  <script>
    const form = document.getElementById('scoreForm');
    const modal = document.getElementById('msgModal');
    const modalIcon = document.getElementById('modalIcon');
    const modalText = document.getElementById('modalText');
    const closeModal = document.getElementById('closeModal');
    const continueBtn = document.getElementById('continueBtn');
    const studentSelect = document.getElementById('studentSelect');
    const studentSearch = document.getElementById('studentSearch');
    const studentId = document.getElementById('studentId');
    const studentName = document.getElementById('studentName');

    // 所有评分项目(除违例外0-100,违例可正负)
    const scoreInputs = [
      document.getElementById('layer1Green'),
      document.getElementById('layer2Net'),
      document.getElementById('layer3'),
      document.getElementById('cone'),
      document.getElementById('bigSquare'),
      document.getElementById('yellowSquare'),
      document.getElementById('redBlueSquare'),
      document.getElementById('flag')
    ];
    const violationInput = document.getElementById('violation');
    const totalInput = document.getElementById('totalScore');

    const defaultStudentList = [    ];

    let allStudentList = [];
    let searchDebounceTimer = null;

    // 总分计算(支持违例扣分)
    function calcTotal() {
      let total = 0;
      // 常规项目加分
      scoreInputs.forEach(input => {
        const val = Number(input.value) || 0;
        total += val;
      });
      // 违例加减分
      const violationVal = Number(violationInput.value) || 0;
      total += violationVal;
      totalInput.value = total;
    }
    // 初始化计算 + 实时监听
    calcTotal();
    scoreInputs.forEach(input => input.addEventListener('input', calcTotal));
    violationInput.addEventListener('input', calcTotal);

    // 【更新接口请求】加载学生列表
    async function loadStudentList() {
      let studentList = [];
      try {
        // 接口地址和Flask完全匹配
        const res = await axios.get('/api/student/list');
        // 后端统一返回code=200
        if (res.data.code === 200 && Array.isArray(res.data.data) && res.data.data.length > 0) {
          studentList = res.data.data;
        } else {
          studentList = defaultStudentList;
        }
      } catch (err) {
        console.warn('获取学生列表接口异常,加载测试样例', err);
        studentList = defaultStudentList;
      }
      allStudentList = studentList;
      renderStudentSelect(allStudentList);
    }

    // 渲染学生下拉
    function renderStudentSelect(list) {
      let html = '<option value="">-- 请先搜索或选择学生 --</option>';
      if (list.length === 0) {
        html += '<option disabled style="color:#999">未找到匹配学生</option>';
      } else {
        list.forEach(item => {
          html += `<option value='${JSON.stringify(item)}'>学号:${item.id} | 姓名:${item.name}</option>`;
        });
      }
      studentSelect.innerHTML = html;
    }

    // 学生搜索防抖
    function handleStudentSearch() {
      clearTimeout(searchDebounceTimer);
      searchDebounceTimer = setTimeout(() => {
        const keyword = studentSearch.value.trim().toLowerCase();
        if (!keyword) {
          renderStudentSelect(allStudentList);
          return;
        }
        const filterList = allStudentList.filter(item => {
          const idStr = item.id.toLowerCase();
          const nameStr = item.name.toLowerCase();
          return idStr.includes(keyword) || nameStr.includes(keyword);
        });
        renderStudentSelect(filterList);
      }, 300);
    }

    // 下拉回填信息
    studentSelect.addEventListener('change', function () {
      const val = this.value;
      if (!val) {
        studentId.value = '';
        studentName.value = '';
        return;
      }
      const info = JSON.parse(val);
      studentId.value = info.id;
      studentName.value = info.name;
    })

    studentSearch.addEventListener('input', handleStudentSearch);

    // 弹窗提示
    function showMsg(type, text) {
      modal.classList.remove('hidden');
      modal.classList.add('flex');
      if (type === 'success') {
        modalIcon.textContent = '✅';
        modalIcon.className = 'text-[40px] mb-3 text-success';
      } else {
        modalIcon.textContent = '❌';
        modalIcon.className = 'text-[40px] mb-3 text-danger';
      }
      modalText.textContent = text;
    }
    closeModal.addEventListener('click', () => {
      modal.classList.add('hidden');
      modal.classList.remove('flex');
    })

    // 【更新接口请求】表单提交成绩
    form.addEventListener('submit', async function (e) {
      e.preventDefault();

      const sId = studentId.value.trim();
      const sName = studentName.value.trim();
      const layer1Green = Number(document.getElementById('layer1Green').value) || 0;
      const layer2Net = Number(document.getElementById('layer2Net').value) || 0;
      const layer3 = Number(document.getElementById('layer3').value) || 0;
      const cone = Number(document.getElementById('cone').value) || 0;
      const bigSquare = Number(document.getElementById('bigSquare').value) || 0;
      const yellowSquare = Number(document.getElementById('yellowSquare').value) || 0;
      const redBlueSquare = Number(document.getElementById('redBlueSquare').value) || 0;
      const flag = Number(document.getElementById('flag').value) || 0;
      const violation = Number(document.getElementById('violation').value) || 0;
      const remark = document.getElementById('remark').value.trim();
      const total = Number(totalInput.value);

      // 表单校验:常规项目0-100,违例不限
      if (!sId) return showMsg('error', '请填写学生学号');
      if (!sName) return showMsg('error', '请填写学生姓名');
      const normalScores = [layer1Green, layer2Net, layer3, cone, bigSquare, yellowSquare, redBlueSquare, flag];
      for (let s of normalScores) {
        if (s < 0 || s > 100) return showMsg('error', '巡查项目分数必须在0~100之间');
      }

      // 请求参数和Flask后端完全对应
      const submitData = {
        studentId: sId,
        studentName: sName,
        layer1Green,
        layer2Net,
        layer3,
        cone,
        bigSquare,
        yellowSquare,
        redBlueSquare,
        flag,
        violation,
        total,
        remark
      };

      try {
        // 提交接口地址匹配Flask
        const apiUrl = '/api/score/add';
        const res = await axios.post(apiUrl, submitData);
        // 后端code=200代表成功
        if (res.data.code === 200) {
          showMsg('success', '成绩录入成功!');
        } else {
          showMsg('error', res.data.msg || '录入失败,请重试');
        }
      } catch (err) {
        console.error('提交异常:', err);
        showMsg('error', '网络异常或服务器错误,请检查后端服务是否启动');
      }
    })

    // 连续录入重置
    continueBtn.addEventListener('click', () => {
      form.reset();
      calcTotal();
      studentSearch.value = "";
      renderStudentSelect(allStudentList);
      studentSearch.focus();
    })

    window.onload = loadStudentList;
  </script>
</body>
</html>

 

posted @ 2026-07-29 18:00  爱吃泡面的皮卡  阅读(7)  评论(0)    收藏  举报