From 798550e1f38eed1e95871387dc58efe02e7610fb Mon Sep 17 00:00:00 2001 From: admin Date: Tue, 29 Sep 2026 18:07:16 +0800 Subject: [PATCH] =?UTF-8?q?AI=20=E7=94=9F=E6=88=90:=20=E4=B8=80=E4=B8=AA?= =?UTF-8?q?=E5=9F=BA=E4=BA=8E=20Python=20Flask=20=E7=9A=84=E8=BD=A6?= =?UTF-8?q?=E8=BE=86=E7=AE=A1=E7=90=86=E7=B3=BB=E7=BB=9F=EF=BC=8C=E6=8F=90?= =?UTF-8?q?=E4=BE=9B=E8=BD=A6=E8=BE=86=E7=9A=84=E5=A2=9E=E5=88=A0=E6=94=B9?= =?UTF-8?q?=E6=9F=A5=E3=80=81=E6=90=9C=E7=B4=A2=E5=92=8C=E7=BB=9F=E8=AE=A1?= =?UTF-8?q?=E5=8A=9F=E8=83=BD=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 79 +++++++++++++++++++ app.py | 78 +++++++++++++++++++ models.py | 102 ++++++++++++++++++++++++ requirements.txt | 1 + static/script.js | 82 ++++++++++++++++++++ static/style.css | 179 +++++++++++++++++++++++++++++++++++++++++++ templates/edit.html | 40 ++++++++++ templates/index.html | 117 ++++++++++++++++++++++++++++ 8 files changed, 678 insertions(+) create mode 100644 README.md create mode 100644 app.py create mode 100644 models.py create mode 100644 requirements.txt create mode 100644 static/script.js create mode 100644 static/style.css create mode 100644 templates/edit.html create mode 100644 templates/index.html diff --git a/README.md b/README.md new file mode 100644 index 0000000..7da478d --- /dev/null +++ b/README.md @@ -0,0 +1,79 @@ +# 车辆管理系统 (Vehicle Management System) + +一个基于 Python Flask 的轻量级车辆管理系统,提供车辆的增删改查、搜索和统计功能。 + +## 功能特性 + +- ✅ 车辆列表展示 +- ✅ 添加新车辆 +- ✅ 编辑车辆信息 +- ✅ 删除车辆 +- ✅ 按车牌/品牌/型号搜索 +- ✅ 车辆统计(总数、按状态统计) +- ✅ 数据持久化(SQLite) + +## 技术栈 + +- 后端:Python 3.8+ / Flask +- 数据库:SQLite +- 前端:HTML / CSS / JavaScript + +## 目录结构 + +``` +vehicle-management-system/ +├── app.py # Flask 应用入口 +├── models.py # 数据模型 +├── requirements.txt # 依赖列表 +├── README.md # 项目说明 +├── static/ +│ ├── style.css # 样式文件 +│ └── script.js # 前端脚本 +└── templates/ + ├── index.html # 首页(车辆列表) + └── edit.html # 编辑页面 +``` + +## 快速开始 + +### 1. 安装依赖 + +```bash +pip install -r requirements.txt +``` + +### 2. 启动应用 + +```bash +python app.py +``` + +### 3. 访问 + +打开浏览器访问:http://127.0.0.1:5000 + +## API 接口 + +| 方法 | 路径 | 说明 | +|--------|-----------------------|--------------------| +| GET | / | 首页(车辆列表) | +| GET | /api/vehicles | 获取所有车辆 | +| POST | /api/vehicles | 添加车辆 | +| GET | /api/vehicles/ | 获取单个车辆 | +| PUT | /api/vehicles/ | 更新车辆 | +| DELETE | /api/vehicles/ | 删除车辆 | +| GET | /api/stats | 获取统计数据 | + +## 车辆字段 + +- `plate`:车牌号(唯一) +- `brand`:品牌 +- `model`:型号 +- `year`:年份 +- `color`:颜色 +- `status`:状态(available / in_use / maintenance) +- `owner`:车主 + +## License + +MIT diff --git a/app.py b/app.py new file mode 100644 index 0000000..2a133f8 --- /dev/null +++ b/app.py @@ -0,0 +1,78 @@ +# -*- coding: utf-8 -*- +"""车辆管理系统 - Flask 应用入口""" + +from flask import Flask, render_template, request, jsonify, redirect, url_for +import models + +app = Flask(__name__) + + +@app.route('/') +def index(): + """首页:展示车辆列表,支持搜索""" + keyword = request.args.get('q', '').strip() + vehicles = models.list_vehicles(keyword or None) + stat = models.stats() + return render_template('index.html', vehicles=vehicles, keyword=keyword, stats=stat) + + +@app.route('/edit/') +def edit_page(vid): + """编辑页面""" + vehicle = models.get_vehicle(vid) + if not vehicle: + return redirect(url_for('index')) + return render_template('edit.html', vehicle=vehicle) + + +# ------------------ REST API ------------------ + +@app.route('/api/vehicles', methods=['GET']) +def api_list(): + keyword = request.args.get('q', '').strip() + return jsonify(models.list_vehicles(keyword or None)) + + +@app.route('/api/vehicles', methods=['POST']) +def api_add(): + data = request.get_json(force=True, silent=True) or {} + if not data.get('plate') or not data.get('brand') or not data.get('model'): + return jsonify({'error': 'plate, brand, model 为必填项'}), 400 + try: + vid = models.add_vehicle(data) + except Exception as e: + return jsonify({'error': str(e)}), 400 + return jsonify({'id': vid, 'message': '添加成功'}), 201 + + +@app.route('/api/vehicles/', methods=['GET']) +def api_get(vid): + v = models.get_vehicle(vid) + if not v: + return jsonify({'error': '未找到'}), 404 + return jsonify(v) + + +@app.route('/api/vehicles/', methods=['PUT']) +def api_update(vid): + if not models.get_vehicle(vid): + return jsonify({'error': '未找到'}), 404 + data = request.get_json(force=True, silent=True) or {} + models.update_vehicle(vid, data) + return jsonify({'message': '更新成功'}) + + +@app.route('/api/vehicles/', methods=['DELETE']) +def api_delete(vid): + models.delete_vehicle(vid) + return jsonify({'message': '删除成功'}) + + +@app.route('/api/stats', methods=['GET']) +def api_stats(): + return jsonify(models.stats()) + + +if __name__ == '__main__': + models.init_db() + app.run(host='0.0.0.0', port=5000, debug=True) diff --git a/models.py b/models.py new file mode 100644 index 0000000..84d8a33 --- /dev/null +++ b/models.py @@ -0,0 +1,102 @@ +# -*- coding: utf-8 -*- +"""数据模型:使用 sqlite3 管理车辆数据""" + +import sqlite3 +import os + +DB_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'vehicles.db') + + +def get_conn(): + """获取数据库连接,返回 Row 类型便于转字典""" + conn = sqlite3.connect(DB_PATH) + conn.row_factory = sqlite3.Row + return conn + + +def init_db(): + """初始化数据库表""" + conn = get_conn() + conn.execute(''' + CREATE TABLE IF NOT EXISTS vehicles ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + plate TEXT NOT NULL UNIQUE, + brand TEXT NOT NULL, + model TEXT NOT NULL, + year INTEGER, + color TEXT, + status TEXT DEFAULT 'available', + owner TEXT + ) + ''') + conn.commit() + conn.close() + + +def list_vehicles(keyword=None): + """列出车辆,可按关键字搜索""" + conn = get_conn() + if keyword: + like = f'%{keyword}%' + rows = conn.execute( + 'SELECT * FROM vehicles WHERE plate LIKE ? OR brand LIKE ? OR model LIKE ? ORDER BY id DESC', + (like, like, like) + ).fetchall() + else: + rows = conn.execute('SELECT * FROM vehicles ORDER BY id DESC').fetchall() + conn.close() + return [dict(r) for r in rows] + + +def get_vehicle(vid): + """获取单条车辆""" + conn = get_conn() + row = conn.execute('SELECT * FROM vehicles WHERE id = ?', (vid,)).fetchone() + conn.close() + return dict(row) if row else None + + +def add_vehicle(data): + """新增车辆""" + conn = get_conn() + cur = conn.execute( + 'INSERT INTO vehicles (plate, brand, model, year, color, status, owner) VALUES (?, ?, ?, ?, ?, ?, ?)', + (data.get('plate'), data.get('brand'), data.get('model'), + data.get('year'), data.get('color'), + data.get('status', 'available'), data.get('owner')) + ) + conn.commit() + vid = cur.lastrowid + conn.close() + return vid + + +def update_vehicle(vid, data): + """更新车辆""" + conn = get_conn() + conn.execute( + 'UPDATE vehicles SET plate=?, brand=?, model=?, year=?, color=?, status=?, owner=? WHERE id=?', + (data.get('plate'), data.get('brand'), data.get('model'), + data.get('year'), data.get('color'), data.get('status'), + data.get('owner'), vid) + ) + conn.commit() + conn.close() + + +def delete_vehicle(vid): + """删除车辆""" + conn = get_conn() + conn.execute('DELETE FROM vehicles WHERE id = ?', (vid,)) + conn.commit() + conn.close() + + +def stats(): + """统计信息""" + conn = get_conn() + total = conn.execute('SELECT COUNT(*) AS c FROM vehicles').fetchone()['c'] + rows = conn.execute('SELECT status, COUNT(*) AS c FROM vehicles GROUP BY status').fetchall() + conn.close() + by_status = {r['status']: r['c'] for r in rows} + return {'total': total, 'by_status': by_status} diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..047e950 --- /dev/null +++ b/requirements.txt @@ -0,0 +1 @@ +Flask==3.0.0 diff --git a/static/script.js b/static/script.js new file mode 100644 index 0000000..054fb19 --- /dev/null +++ b/static/script.js @@ -0,0 +1,82 @@ +/* 车辆管理系统前端脚本 */ + +// 打开添加弹窗 +function openAddModal() { + document.getElementById('addModal').classList.add('show'); +} + +// 关闭添加弹窗 +function closeAddModal() { + document.getElementById('addModal').classList.remove('show'); + document.getElementById('addForm').reset(); +} + +// 提交添加表单 +async function submitAdd(e) { + e.preventDefault(); + const form = e.target; + const data = Object.fromEntries(new FormData(form).entries()); + // 年份转数字 + if (data.year) data.year = parseInt(data.year, 10); + + try { + const res = await fetch('/api/vehicles', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(data) + }); + const result = await res.json(); + if (!res.ok) { + alert('添加失败: ' + (result.error || '未知错误')); + return; + } + location.reload(); + } catch (err) { + alert('请求失败: ' + err.message); + } +} + +// 删除车辆 +async function deleteVehicle(id) { + if (!confirm('确定要删除该车辆吗?')) return; + try { + const res = await fetch(`/api/vehicles/${id}`, { method: 'DELETE' }); + if (!res.ok) { + alert('删除失败'); + return; + } + location.reload(); + } catch (err) { + alert('请求失败: ' + err.message); + } +} + +// 提交编辑表单 +async function submitEdit(e, id) { + e.preventDefault(); + const form = e.target; + const data = Object.fromEntries(new FormData(form).entries()); + if (data.year) data.year = parseInt(data.year, 10); + + try { + const res = await fetch(`/api/vehicles/${id}`, { + method: 'PUT', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(data) + }); + const result = await res.json(); + if (!res.ok) { + alert('更新失败: ' + (result.error || '未知错误')); + return; + } + window.location.href = '/'; + } catch (err) { + alert('请求失败: ' + err.message); + } +} + +// 点击弹窗外部关闭 +window.addEventListener('click', function (e) { + const modal = document.getElementById('addModal'); + if (modal && e.target === modal) closeAddModal(); +}); diff --git a/static/style.css b/static/style.css new file mode 100644 index 0000000..8243bb7 --- /dev/null +++ b/static/style.css @@ -0,0 +1,179 @@ +/* 全局样式 */ +* { box-sizing: border-box; margin: 0; padding: 0; } + +body { + font-family: -apple-system, "Segoe UI", "Microsoft YaHei", sans-serif; + background: #f4f6f9; + color: #333; + line-height: 1.6; + min-height: 100vh; +} + +header { + background: linear-gradient(135deg, #2c3e50, #4a6572); + color: #fff; + padding: 30px 40px; + box-shadow: 0 2px 10px rgba(0,0,0,0.1); +} + +header h1 { font-size: 26px; } +.subtitle { font-size: 13px; opacity: 0.75; } + +main { + max-width: 1200px; + margin: 30px auto; + padding: 0 20px; +} + +/* 统计卡片 */ +.stats { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); + gap: 16px; + margin-bottom: 24px; +} + +.stat-card { + background: #fff; + border-radius: 10px; + padding: 20px; + text-align: center; + box-shadow: 0 2px 8px rgba(0,0,0,0.06); + transition: transform 0.2s; +} + +.stat-card:hover { transform: translateY(-3px); } +.stat-num { display: block; font-size: 28px; font-weight: 700; color: #2c3e50; } +.stat-label { font-size: 13px; color: #888; } + +/* 工具栏 */ +.toolbar { + display: flex; + justify-content: space-between; + align-items: center; + gap: 16px; + margin-bottom: 16px; + flex-wrap: wrap; +} + +.search-form { display: flex; gap: 8px; flex: 1; max-width: 500px; } +.search-form input { + flex: 1; + padding: 10px 14px; + border: 1px solid #ddd; + border-radius: 6px; + font-size: 14px; +} +.search-form input:focus { outline: none; border-color: #4a6572; } + +/* 按钮 */ +.btn { + display: inline-block; + padding: 10px 18px; + background: #e0e0e0; + color: #333; + border: none; + border-radius: 6px; + cursor: pointer; + font-size: 14px; + text-decoration: none; + transition: background 0.2s; +} +.btn:hover { background: #d0d0d0; } +.btn-primary { background: #2c3e50; color: #fff; } +.btn-primary:hover { background: #1a252f; } +.btn-danger { background: #e74c3c; color: #fff; } +.btn-danger:hover { background: #c0392b; } +.btn-sm { padding: 6px 12px; font-size: 13px; } +.btn-clear { background: #f39c12; color: #fff; } + +/* 表格 */ +.table-wrap { + background: #fff; + border-radius: 10px; + overflow: hidden; + box-shadow: 0 2px 8px rgba(0,0,0,0.06); +} + +table { width: 100%; border-collapse: collapse; } +th, td { padding: 12px 16px; text-align: left; border-bottom: 1px solid #eee; font-size: 14px; } +th { background: #f8f9fb; font-weight: 600; color: #555; } +tbody tr:hover { background: #fafbfc; } +.empty { text-align: center; color: #999; padding: 40px 0; } + +/* 状态徽章 */ +.badge { + display: inline-block; + padding: 3px 10px; + border-radius: 12px; + font-size: 12px; + font-weight: 500; +} +.badge-available { background: #d4edda; color: #155724; } +.badge-in_use { background: #cce5ff; color: #004085; } +.badge-maintenance { background: #fff3cd; color: #856404; } + +/* 表单卡片 */ +.form-card { + background: #fff; + border-radius: 10px; + padding: 30px; + max-width: 600px; + margin: 0 auto; + box-shadow: 0 2px 8px rgba(0,0,0,0.06); +} + +form label { + display: block; + margin-bottom: 16px; + font-size: 14px; + color: #555; +} + +form input, form select { + display: block; + width: 100%; + margin-top: 6px; + padding: 10px 12px; + border: 1px solid #ddd; + border-radius: 6px; + font-size: 14px; + font-family: inherit; +} +form input:focus, form select:focus { outline: none; border-color: #4a6572; } + +.modal-actions { display: flex; justify-content: flex-end; gap: 10px; margin-top: 20px; } + +/* 弹窗 */ +.modal { + display: none; + position: fixed; + inset: 0; + background: rgba(0,0,0,0.5); + z-index: 100; + align-items: center; + justify-content: center; + padding: 20px; +} +.modal.show { display: flex; } + +.modal-content { + background: #fff; + padding: 30px; + border-radius: 12px; + width: 100%; + max-width: 500px; + max-height: 90vh; + overflow-y: auto; +} +.modal-content h2 { margin-bottom: 20px; color: #2c3e50; } + +.actions { white-space: nowrap; } +.actions .btn { margin-right: 4px; } + +@media (max-width: 768px) { + header { padding: 20px; } + .toolbar { flex-direction: column; align-items: stretch; } + .search-form { max-width: 100%; } + th, td { padding: 8px 10px; font-size: 13px; } +} diff --git a/templates/edit.html b/templates/edit.html new file mode 100644 index 0000000..889522d --- /dev/null +++ b/templates/edit.html @@ -0,0 +1,40 @@ + + + + + 编辑车辆 - 车辆管理系统 + + + +
+

✏️ 编辑车辆

+

ID: {{ vehicle.id }}

+
+ +
+
+
+ + + + + + + + +
+
+
+ + + + diff --git a/templates/index.html b/templates/index.html new file mode 100644 index 0000000..812926b --- /dev/null +++ b/templates/index.html @@ -0,0 +1,117 @@ + + + + + 车辆管理系统 + + + +
+

🚗 车辆管理系统

+

Vehicle Management System

+
+ +
+ +
+
+ {{ stats.total }} + 车辆总数 +
+
+ {{ stats.by_status.get('available', 0) }} + 可用 +
+
+ {{ stats.by_status.get('in_use', 0) }} + 使用中 +
+
+ {{ stats.by_status.get('maintenance', 0) }} + 维修中 +
+
+ + +
+
+ + + {% if keyword %} + 清除 + {% endif %} +
+ +
+ + +
+ + + + + + + + + + + + + + + + {% if vehicles %} + {% for v in vehicles %} + + + + + + + + + + + + {% endfor %} + {% else %} + + {% endif %} + +
ID车牌品牌型号年份颜色状态车主操作
{{ v.id }}{{ v.plate }}{{ v.brand }}{{ v.model }}{{ v.year or '-' }}{{ v.color or '-' }}{{ status_text(v.status) }}{{ v.owner or '-' }} + 编辑 + +
暂无车辆数据
+
+
+ + + + + + +