移除 mvcar 目录,已独立为单独仓库
This commit is contained in:
parent
e4e52db4f3
commit
e50111aaad
|
|
@ -1,51 +0,0 @@
|
||||||
FROM python:3.9-slim
|
|
||||||
|
|
||||||
# 设置时区
|
|
||||||
ENV TZ=Asia/Shanghai
|
|
||||||
|
|
||||||
# 完全清空并重写所有源文件
|
|
||||||
RUN rm -f /etc/apt/sources.list /etc/apt/sources.list.d/* && \
|
|
||||||
echo "deb https://mirrors.tuna.tsinghua.edu.cn/debian/ trixie main" > /etc/apt/sources.list && \
|
|
||||||
echo "deb https://mirrors.tuna.tsinghua.edu.cn/debian/ trixie-updates main" >> /etc/apt/sources.list && \
|
|
||||||
echo "deb https://mirrors.tuna.tsinghua.edu.cn/debian-security trixie-security main" >> /etc/apt/sources.list
|
|
||||||
|
|
||||||
|
|
||||||
# 设置 ll 别名
|
|
||||||
RUN echo "alias ll='ls -alF'" >> /root/.bashrc
|
|
||||||
|
|
||||||
# 安装 nginx
|
|
||||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
|
||||||
nginx \
|
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
|
||||||
|
|
||||||
# 设置 pip 清华源
|
|
||||||
RUN pip config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple && \
|
|
||||||
pip config set global.trusted-host pypi.tuna.tsinghua.edu.cn
|
|
||||||
|
|
||||||
# 创建应用目录
|
|
||||||
WORKDIR /app
|
|
||||||
|
|
||||||
# 复制后端文件
|
|
||||||
COPY backend/ ./backend/
|
|
||||||
COPY frontend/ /var/www/html/
|
|
||||||
|
|
||||||
# 安装Python依赖
|
|
||||||
RUN pip install --no-cache-dir -r backend/requirements.txt
|
|
||||||
|
|
||||||
# 创建数据和日志目录
|
|
||||||
RUN mkdir -p data logs
|
|
||||||
|
|
||||||
# 配置nginx
|
|
||||||
COPY nginx/nginx.conf /etc/nginx/sites-available/default
|
|
||||||
|
|
||||||
# 暴露端口
|
|
||||||
EXPOSE 80
|
|
||||||
|
|
||||||
# 启动脚本
|
|
||||||
COPY sbin/start.sh /start.sh
|
|
||||||
RUN chmod +x /start.sh
|
|
||||||
|
|
||||||
# 初始化数据库
|
|
||||||
RUN python backend/init_db.py
|
|
||||||
|
|
||||||
ENTRYPOINT ["/start.sh"]
|
|
||||||
|
|
@ -1,11 +0,0 @@
|
||||||
[[source]]
|
|
||||||
url = "https://pypi.tuna.tsinghua.edu.cn/simple\n"
|
|
||||||
verify_ssl = true
|
|
||||||
name = "pip_conf_index_global"
|
|
||||||
|
|
||||||
[packages]
|
|
||||||
|
|
||||||
[dev-packages]
|
|
||||||
|
|
||||||
[requires]
|
|
||||||
python_version = "3.8"
|
|
||||||
|
|
@ -1,113 +0,0 @@
|
||||||
# backend/app.py
|
|
||||||
import logging
|
|
||||||
from logging.handlers import RotatingFileHandler
|
|
||||||
from flask import Flask, jsonify, request
|
|
||||||
from flask_cors import CORS
|
|
||||||
import sqlite3
|
|
||||||
import os
|
|
||||||
|
|
||||||
# 获取当前文件所在目录
|
|
||||||
basedir = os.path.abspath(os.path.dirname(__file__))
|
|
||||||
db_path = os.path.join(basedir, '..', 'data', 'cars.db')
|
|
||||||
|
|
||||||
# 创建 logs 目录
|
|
||||||
logs_dir = os.path.join(basedir, '..', 'logs')
|
|
||||||
os.makedirs(logs_dir, exist_ok=True)
|
|
||||||
|
|
||||||
# 配置日志
|
|
||||||
log_formatter = logging.Formatter(
|
|
||||||
'%(asctime)s %(levelname)s %(name)s %(message)s'
|
|
||||||
)
|
|
||||||
log_file = os.path.join(logs_dir, 'app.log')
|
|
||||||
|
|
||||||
# 创建 rotating file handler (最大5MB,保留5个备份)
|
|
||||||
file_handler = RotatingFileHandler(
|
|
||||||
log_file,
|
|
||||||
maxBytes=5*1024*1024, # 5MB
|
|
||||||
backupCount=5
|
|
||||||
)
|
|
||||||
file_handler.setFormatter(log_formatter)
|
|
||||||
file_handler.setLevel(logging.INFO)
|
|
||||||
|
|
||||||
# 创建 logger
|
|
||||||
app = Flask(__name__)
|
|
||||||
app.logger.addHandler(file_handler)
|
|
||||||
app.logger.setLevel(logging.INFO)
|
|
||||||
|
|
||||||
# 添加 CORS 支持
|
|
||||||
CORS(app)
|
|
||||||
|
|
||||||
# 数据库连接函数
|
|
||||||
def get_db_connection():
|
|
||||||
conn = sqlite3.connect(db_path)
|
|
||||||
conn.row_factory = sqlite3.Row
|
|
||||||
# 确保使用UTF-8编码
|
|
||||||
conn.execute('PRAGMA encoding = "UTF-8"')
|
|
||||||
return conn
|
|
||||||
|
|
||||||
@app.route('/api/cars', methods=['GET'])
|
|
||||||
def get_all_cars():
|
|
||||||
app.logger.info('获取所有车辆信息')
|
|
||||||
try:
|
|
||||||
conn = get_db_connection()
|
|
||||||
cars = conn.execute('SELECT * FROM cars').fetchall()
|
|
||||||
conn.close()
|
|
||||||
|
|
||||||
cars_list = [dict(car) for car in cars]
|
|
||||||
app.logger.info(f'成功获取 {len(cars_list)} 条车辆记录')
|
|
||||||
return jsonify(cars_list)
|
|
||||||
except Exception as e:
|
|
||||||
app.logger.error(f'获取所有车辆信息时出错: {str(e)}')
|
|
||||||
return jsonify({'error': '服务器内部错误'}), 500
|
|
||||||
|
|
||||||
@app.route('/api/cars/search', methods=['GET'])
|
|
||||||
def search_car():
|
|
||||||
plate = request.args.get('plate', '')
|
|
||||||
app.logger.info(f'搜索车牌号码: {plate}')
|
|
||||||
|
|
||||||
if not plate:
|
|
||||||
app.logger.warning('用户未提供车牌号码')
|
|
||||||
return jsonify({'error': '请输入车牌号码'}), 400
|
|
||||||
|
|
||||||
try:
|
|
||||||
conn = get_db_connection()
|
|
||||||
# 修改查询语句以正确处理中文字符
|
|
||||||
cars = conn.execute('SELECT * FROM cars WHERE plate LIKE ?', (f'%{plate}%',)).fetchall()
|
|
||||||
conn.close()
|
|
||||||
|
|
||||||
cars_list = [dict(car) for car in cars]
|
|
||||||
app.logger.info(f'搜索 "{plate}" 返回 {len(cars_list)} 条结果')
|
|
||||||
return jsonify(cars_list)
|
|
||||||
except Exception as e:
|
|
||||||
app.logger.error(f'搜索车牌号码时出错: {str(e)}')
|
|
||||||
return jsonify({'error': '服务器内部错误'}), 500
|
|
||||||
|
|
||||||
@app.route('/api/cars/<plate>', methods=['GET'])
|
|
||||||
def get_car_by_plate(plate):
|
|
||||||
app.logger.info(f'查询特定车牌号码: {plate}')
|
|
||||||
|
|
||||||
try:
|
|
||||||
conn = get_db_connection()
|
|
||||||
# 修改查询语句以正确处理中文字符
|
|
||||||
car = conn.execute('SELECT * FROM cars WHERE plate = ?', (plate,)).fetchone()
|
|
||||||
conn.close()
|
|
||||||
|
|
||||||
if car is None:
|
|
||||||
app.logger.warning(f'未找到车牌号码: {plate}')
|
|
||||||
return jsonify({'error': '未找到该车牌号码对应的车主信息'}), 404
|
|
||||||
|
|
||||||
app.logger.info(f'成功找到车牌号码: {plate}')
|
|
||||||
return jsonify(dict(car))
|
|
||||||
except Exception as e:
|
|
||||||
app.logger.error(f'查询特定车牌时出错: {str(e)}')
|
|
||||||
return jsonify({'error': '服务器内部错误'}), 500
|
|
||||||
|
|
||||||
# 添加一个根路径用于健康检查
|
|
||||||
@app.route('/', methods=['GET'])
|
|
||||||
def health_check():
|
|
||||||
app.logger.info('健康检查请求')
|
|
||||||
return jsonify({'status': 'ok', 'message': '服务正常运行'})
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
app.logger.info('启动 Flask 应用')
|
|
||||||
app.run(debug=True, host='0.0.0.0', port=5000)
|
|
||||||
|
|
@ -1,41 +0,0 @@
|
||||||
# backend/init_db.py
|
|
||||||
import sqlite3
|
|
||||||
import os
|
|
||||||
|
|
||||||
# 获取当前文件所在目录
|
|
||||||
basedir = os.path.abspath(os.path.dirname(__file__))
|
|
||||||
db_path = os.path.join(basedir, '..', 'data', 'cars.db')
|
|
||||||
|
|
||||||
# 创建数据目录(如果不存在)
|
|
||||||
os.makedirs(os.path.dirname(db_path), exist_ok=True)
|
|
||||||
|
|
||||||
# 创建数据库连接
|
|
||||||
conn = sqlite3.connect(db_path)
|
|
||||||
# 确保使用UTF-8编码
|
|
||||||
conn.execute('PRAGMA encoding = "UTF-8"')
|
|
||||||
cursor = conn.cursor()
|
|
||||||
|
|
||||||
# 创建车辆表
|
|
||||||
cursor.execute('''
|
|
||||||
CREATE TABLE IF NOT EXISTS cars (
|
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
||||||
plate TEXT UNIQUE NOT NULL,
|
|
||||||
phone TEXT NOT NULL,
|
|
||||||
brand TEXT NOT NULL
|
|
||||||
)
|
|
||||||
''')
|
|
||||||
|
|
||||||
# 插入示例数据
|
|
||||||
sample_cars = [
|
|
||||||
("京B67890", "131-2222-2222", "本田雅阁"),
|
|
||||||
("沪C11111", "132-3333-3333", "大众帕萨特"),
|
|
||||||
("浙D22222", "133-4444-4444", "奔驰E级")
|
|
||||||
]
|
|
||||||
|
|
||||||
cursor.executemany('INSERT OR IGNORE INTO cars (plate, phone, brand) VALUES (?, ?, ?)', sample_cars)
|
|
||||||
|
|
||||||
# 提交更改并关闭连接
|
|
||||||
conn.commit()
|
|
||||||
conn.close()
|
|
||||||
|
|
||||||
print("数据库初始化完成")
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
# backend/requirements.txt
|
|
||||||
Flask==2.3.2
|
|
||||||
flask-cors==4.0.0
|
|
||||||
|
|
@ -1,24 +0,0 @@
|
||||||
# backend/view_logs.py
|
|
||||||
import os
|
|
||||||
|
|
||||||
# 获取当前文件所在目录
|
|
||||||
basedir = os.path.abspath(os.path.dirname(__file__))
|
|
||||||
log_file = os.path.join(basedir, '..', 'logs', 'app.log')
|
|
||||||
|
|
||||||
def view_logs(lines=20):
|
|
||||||
"""查看最新的日志条目"""
|
|
||||||
try:
|
|
||||||
with open(log_file, 'r', encoding='utf-8') as f:
|
|
||||||
all_lines = f.readlines()
|
|
||||||
# 获取最后几行
|
|
||||||
latest_lines = all_lines[-lines:] if len(all_lines) > lines else all_lines
|
|
||||||
print(f"最新 {len(latest_lines)} 条日志:")
|
|
||||||
for line in latest_lines:
|
|
||||||
print(line.strip())
|
|
||||||
except FileNotFoundError:
|
|
||||||
print("日志文件不存在,请先运行应用程序")
|
|
||||||
except Exception as e:
|
|
||||||
print(f"读取日志文件时出错: {e}")
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
view_logs()
|
|
||||||
Binary file not shown.
|
|
@ -1,10 +0,0 @@
|
||||||
version: '3.8'
|
|
||||||
|
|
||||||
services:
|
|
||||||
mvcar:
|
|
||||||
build: .
|
|
||||||
ports:
|
|
||||||
- "80:80"
|
|
||||||
volumes:
|
|
||||||
- ./data:/app/data
|
|
||||||
- ./logs:/app/logs
|
|
||||||
|
|
@ -1,180 +0,0 @@
|
||||||
* {
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
box-sizing: border-box;
|
|
||||||
font-family: 'PingFang SC', 'Microsoft YaHei', sans-serif;
|
|
||||||
}
|
|
||||||
|
|
||||||
body {
|
|
||||||
background-color: #f5f7fa;
|
|
||||||
color: #333;
|
|
||||||
line-height: 1.6;
|
|
||||||
padding: 20px;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
min-height: 100vh;
|
|
||||||
}
|
|
||||||
|
|
||||||
.container {
|
|
||||||
max-width: 500px;
|
|
||||||
width: 100%;
|
|
||||||
background: white;
|
|
||||||
border-radius: 16px;
|
|
||||||
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08);
|
|
||||||
overflow: hidden;
|
|
||||||
margin-top: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.header {
|
|
||||||
background: linear-gradient(135deg, #4a6cf7, #6a82fb);
|
|
||||||
color: white;
|
|
||||||
padding: 25px 20px;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.header h1 {
|
|
||||||
font-size: 24px;
|
|
||||||
margin-bottom: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.header p {
|
|
||||||
opacity: 0.9;
|
|
||||||
font-size: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.content {
|
|
||||||
padding: 25px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.info-card {
|
|
||||||
background: #f8f9ff;
|
|
||||||
border-radius: 12px;
|
|
||||||
padding: 20px;
|
|
||||||
margin-bottom: 25px;
|
|
||||||
border-left: 4px solid #4a6cf7;
|
|
||||||
}
|
|
||||||
|
|
||||||
.info-card h2 {
|
|
||||||
color: #4a6cf7;
|
|
||||||
font-size: 18px;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.info-card h2 i {
|
|
||||||
margin-right: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.phone-number {
|
|
||||||
font-size: 28px;
|
|
||||||
font-weight: bold;
|
|
||||||
color: #333;
|
|
||||||
margin: 15px 0;
|
|
||||||
text-align: center;
|
|
||||||
letter-spacing: 1px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.call-button {
|
|
||||||
display: block;
|
|
||||||
width: 100%;
|
|
||||||
background: #4a6cf7;
|
|
||||||
color: white;
|
|
||||||
border: none;
|
|
||||||
border-radius: 50px;
|
|
||||||
padding: 16px;
|
|
||||||
font-size: 18px;
|
|
||||||
font-weight: bold;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: all 0.3s ease;
|
|
||||||
margin-top: 20px;
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
box-shadow: 0 4px 12px rgba(74, 108, 247, 0.3);
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.call-button i {
|
|
||||||
margin-right: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.call-button:hover {
|
|
||||||
background: #3a5ce5;
|
|
||||||
transform: translateY(-2px);
|
|
||||||
box-shadow: 0 6px 15px rgba(74, 108, 247, 0.4);
|
|
||||||
}
|
|
||||||
|
|
||||||
.car-info {
|
|
||||||
background: #f8f9ff;
|
|
||||||
border-radius: 12px;
|
|
||||||
padding: 20px;
|
|
||||||
margin-bottom: 25px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.car-info h3 {
|
|
||||||
color: #4a6cf7;
|
|
||||||
margin-bottom: 15px;
|
|
||||||
font-size: 17px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.car-info p {
|
|
||||||
margin-bottom: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.instructions {
|
|
||||||
margin-top: 30px;
|
|
||||||
background: #f8f9ff;
|
|
||||||
border-radius: 12px;
|
|
||||||
padding: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.instructions h3 {
|
|
||||||
color: #4a6cf7;
|
|
||||||
margin-bottom: 15px;
|
|
||||||
font-size: 17px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.instructions ul {
|
|
||||||
list-style-type: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.instructions li {
|
|
||||||
margin-bottom: 12px;
|
|
||||||
padding-left: 24px;
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
.instructions li:before {
|
|
||||||
content: "•";
|
|
||||||
color: #4a6cf7;
|
|
||||||
font-weight: bold;
|
|
||||||
position: absolute;
|
|
||||||
left: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.footer {
|
|
||||||
text-align: center;
|
|
||||||
margin-top: 30px;
|
|
||||||
color: #666;
|
|
||||||
font-size: 14px;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 480px) {
|
|
||||||
.container {
|
|
||||||
margin-top: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.header {
|
|
||||||
padding: 20px 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.content {
|
|
||||||
padding: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.phone-number {
|
|
||||||
font-size: 24px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,128 +0,0 @@
|
||||||
* {
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
box-sizing: border-box;
|
|
||||||
font-family: 'PingFang SC', 'Microsoft YaHei', sans-serif;
|
|
||||||
}
|
|
||||||
|
|
||||||
body {
|
|
||||||
background-color: #f5f7fa;
|
|
||||||
color: #333;
|
|
||||||
line-height: 1.6;
|
|
||||||
padding: 20px;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
min-height: 100vh;
|
|
||||||
}
|
|
||||||
|
|
||||||
.container {
|
|
||||||
max-width: 500px;
|
|
||||||
width: 100%;
|
|
||||||
background: white;
|
|
||||||
border-radius: 16px;
|
|
||||||
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08);
|
|
||||||
overflow: hidden;
|
|
||||||
margin-top: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.header {
|
|
||||||
background: linear-gradient(135deg, #4a6cf7, #6a82fb);
|
|
||||||
color: white;
|
|
||||||
padding: 25px 20px;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.header h1 {
|
|
||||||
font-size: 24px;
|
|
||||||
margin-bottom: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.header p {
|
|
||||||
opacity: 0.9;
|
|
||||||
font-size: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.content {
|
|
||||||
padding: 25px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.search-section {
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.search-section h2 {
|
|
||||||
color: #4a6cf7;
|
|
||||||
margin-bottom: 20px;
|
|
||||||
font-size: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.input-group {
|
|
||||||
margin-bottom: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.license-input {
|
|
||||||
width: 100%;
|
|
||||||
padding: 15px;
|
|
||||||
border: 2px solid #e1e5f0;
|
|
||||||
border-radius: 12px;
|
|
||||||
font-size: 16px;
|
|
||||||
text-align: center;
|
|
||||||
transition: border-color 0.3s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.license-input:focus {
|
|
||||||
border-color: #4a6cf7;
|
|
||||||
outline: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.search-button {
|
|
||||||
width: 100%;
|
|
||||||
background: #4a6cf7;
|
|
||||||
color: white;
|
|
||||||
border: none;
|
|
||||||
border-radius: 50px;
|
|
||||||
padding: 16px;
|
|
||||||
font-size: 18px;
|
|
||||||
font-weight: bold;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: all 0.3s ease;
|
|
||||||
box-shadow: 0 4px 12px rgba(74, 108, 247, 0.3);
|
|
||||||
}
|
|
||||||
|
|
||||||
.search-button:hover {
|
|
||||||
background: #3a5ce5;
|
|
||||||
transform: translateY(-2px);
|
|
||||||
box-shadow: 0 6px 15px rgba(74, 108, 247, 0.4);
|
|
||||||
}
|
|
||||||
|
|
||||||
.error-message {
|
|
||||||
color: #e74c3c;
|
|
||||||
text-align: center;
|
|
||||||
padding: 15px;
|
|
||||||
background-color: #fdf2f2;
|
|
||||||
border-radius: 8px;
|
|
||||||
margin-top: 20px;
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.footer {
|
|
||||||
text-align: center;
|
|
||||||
margin-top: 30px;
|
|
||||||
color: #666;
|
|
||||||
font-size: 14px;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 480px) {
|
|
||||||
.container {
|
|
||||||
margin-top: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.header {
|
|
||||||
padding: 20px 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.content {
|
|
||||||
padding: 20px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,34 +0,0 @@
|
||||||
<!DOCTYPE html>
|
|
||||||
<html lang="zh-CN">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<title>挪车电话查询</title>
|
|
||||||
<link rel="stylesheet" href="css/style.css">
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div class="container">
|
|
||||||
<div class="header">
|
|
||||||
<h1>挪车电话查询</h1>
|
|
||||||
<p>请输入车牌号码查询车主联系方式</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="content">
|
|
||||||
<div class="search-section">
|
|
||||||
<h2>车牌号码查询</h2>
|
|
||||||
<div class="input-group">
|
|
||||||
<input type="text" class="license-input" id="licensePlate" placeholder="请输入车牌号码,例如:粤A12345">
|
|
||||||
</div>
|
|
||||||
<button class="search-button" id="searchButton">查询</button>
|
|
||||||
<div class="error-message" id="errorMessage"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="footer">
|
|
||||||
<p>挪车电话服务 © kiki.kim</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<script src="js/script.js"></script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
|
|
@ -1,31 +0,0 @@
|
||||||
// frontend/js/result.js
|
|
||||||
document.addEventListener('DOMContentLoaded', function() {
|
|
||||||
console.log('页面加载完成 - 挪车电话结果页面');
|
|
||||||
|
|
||||||
const phoneNumberElement = document.getElementById('phoneNumber');
|
|
||||||
const plateNumberElement = document.getElementById('plateNumber');
|
|
||||||
const carBrandElement = document.getElementById('carBrand');
|
|
||||||
const callButton = document.getElementById('callButton');
|
|
||||||
|
|
||||||
// 从 sessionStorage 获取车辆信息
|
|
||||||
const carInfoJson = sessionStorage.getItem('carInfo');
|
|
||||||
console.log('从 sessionStorage 获取车辆信息');
|
|
||||||
|
|
||||||
if (carInfoJson) {
|
|
||||||
const carInfo = JSON.parse(carInfoJson);
|
|
||||||
console.log(`车辆信息: ${JSON.stringify(carInfo)}`);
|
|
||||||
|
|
||||||
// 显示车辆信息
|
|
||||||
phoneNumberElement.textContent = carInfo.phone;
|
|
||||||
plateNumberElement.textContent = carInfo.plate;
|
|
||||||
carBrandElement.textContent = carInfo.brand;
|
|
||||||
|
|
||||||
// 设置拨号链接
|
|
||||||
callButton.href = 'tel:' + carInfo.phone.replace(/-/g, '');
|
|
||||||
console.log(`设置拨号链接: ${callButton.href}`);
|
|
||||||
} else {
|
|
||||||
console.warn('未找到车辆信息,重定向到搜索页面');
|
|
||||||
// 如果没有车辆信息,返回搜索页面
|
|
||||||
window.location.href = 'index.html';
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
@ -1,90 +0,0 @@
|
||||||
// frontend/js/script.js
|
|
||||||
document.addEventListener('DOMContentLoaded', function() {
|
|
||||||
const searchButton = document.getElementById('searchButton');
|
|
||||||
const licensePlateInput = document.getElementById('licensePlate');
|
|
||||||
const errorMessage = document.getElementById('errorMessage');
|
|
||||||
|
|
||||||
console.log('页面加载完成 - 挪车电话查询页面');
|
|
||||||
|
|
||||||
// 添加搜索频率限制
|
|
||||||
let lastSearchTime = 0;
|
|
||||||
const SEARCH_DELAY = 1000; // 1秒内只能搜索一次
|
|
||||||
|
|
||||||
searchButton.addEventListener('click', function() {
|
|
||||||
const plate = licensePlateInput.value.trim();
|
|
||||||
console.log(`用户尝试搜索车牌号码: ${plate}`);
|
|
||||||
|
|
||||||
// 检查输入长度
|
|
||||||
if (plate.length < 3) {
|
|
||||||
console.warn('输入字符少于3个');
|
|
||||||
showError('请输入至少3个字符进行搜索');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 检查搜索频率
|
|
||||||
const currentTime = new Date().getTime();
|
|
||||||
if (currentTime - lastSearchTime < SEARCH_DELAY) {
|
|
||||||
console.warn('搜索过于频繁');
|
|
||||||
showError('搜索过于频繁,请稍后再试');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
lastSearchTime = currentTime;
|
|
||||||
|
|
||||||
if (!plate) {
|
|
||||||
console.warn('未输入车牌号码');
|
|
||||||
showError('请输入车牌号码');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 从后端API获取数据
|
|
||||||
console.log(`发送请求到: /api/cars/search?plate=${encodeURIComponent(plate)}`);
|
|
||||||
fetch(`/api/cars/search?plate=${encodeURIComponent(plate)}`)
|
|
||||||
.then(response => {
|
|
||||||
console.log(`收到响应状态: ${response.status}`);
|
|
||||||
if (!response.ok) {
|
|
||||||
throw new Error('网络响应错误');
|
|
||||||
}
|
|
||||||
return response.json();
|
|
||||||
})
|
|
||||||
.then(data => {
|
|
||||||
console.log(`收到数据,共 ${data.length} 条记录`);
|
|
||||||
if (data.length > 0) {
|
|
||||||
// 取第一个匹配的结果
|
|
||||||
const carInfo = data[0];
|
|
||||||
console.log(`找到车辆信息: ${JSON.stringify(carInfo)}`);
|
|
||||||
// 将车辆信息存储到 sessionStorage
|
|
||||||
sessionStorage.setItem('carInfo', JSON.stringify(carInfo));
|
|
||||||
console.log('跳转到结果页面');
|
|
||||||
// 跳转到结果页面
|
|
||||||
window.location.href = 'result.html';
|
|
||||||
} else {
|
|
||||||
console.warn('未找到匹配的车辆信息');
|
|
||||||
showError('没有找到该车牌号码对应的车主信息');
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch(error => {
|
|
||||||
console.error('请求失败:', error);
|
|
||||||
showError('查询失败,请稍后再试');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// 回车键触发搜索
|
|
||||||
licensePlateInput.addEventListener('keypress', function(e) {
|
|
||||||
if (e.key === 'Enter') {
|
|
||||||
console.log('用户按下回车键触发搜索');
|
|
||||||
searchButton.click();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
function showError(message) {
|
|
||||||
console.log(`显示错误信息: ${message}`);
|
|
||||||
errorMessage.textContent = message;
|
|
||||||
errorMessage.style.display = 'block';
|
|
||||||
|
|
||||||
// 3秒后隐藏错误信息
|
|
||||||
setTimeout(() => {
|
|
||||||
console.log('隐藏错误信息');
|
|
||||||
errorMessage.style.display = 'none';
|
|
||||||
}, 3000);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
@ -1,48 +0,0 @@
|
||||||
<!DOCTYPE html>
|
|
||||||
<html lang="zh-CN">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<title>挪车电话</title>
|
|
||||||
<link rel="stylesheet" href="css/result.css">
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div class="container">
|
|
||||||
<div class="header">
|
|
||||||
<h1>挪车电话</h1>
|
|
||||||
<p>如需挪车,请直接拨打下方电话</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="content">
|
|
||||||
<div class="info-card">
|
|
||||||
<h2><i>📞</i> 车主联系电话</h2>
|
|
||||||
<p>如需挪车,请拨打下方电话联系车主</p>
|
|
||||||
<div class="phone-number" id="phoneNumber">加载中...</div>
|
|
||||||
<a href="#" class="call-button" id="callButton">
|
|
||||||
<i>☎</i> 一键拨号
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="car-info">
|
|
||||||
<h3>车辆信息</h3>
|
|
||||||
<p>车牌号码: <span id="plateNumber">加载中...</span></p>
|
|
||||||
<p>车辆品牌: <span id="carBrand">加载中...</span></p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="instructions">
|
|
||||||
<h3>使用说明</h3>
|
|
||||||
<ul>
|
|
||||||
<li>点击"一键拨号"按钮可直接拨打车主电话</li>
|
|
||||||
<li>如遇电话无法接通,请稍后再试</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="footer">
|
|
||||||
<p>挪车电话服务 © kiki.kim</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<script src="js/result.js"></script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
|
|
@ -1,24 +0,0 @@
|
||||||
server {
|
|
||||||
listen 80;
|
|
||||||
server_name localhost;
|
|
||||||
|
|
||||||
# 前端静态文件
|
|
||||||
location / {
|
|
||||||
root /var/www/html;
|
|
||||||
index index.html;
|
|
||||||
try_files $uri $uri/ =404;
|
|
||||||
}
|
|
||||||
|
|
||||||
# API请求代理到后端Flask应用
|
|
||||||
location /api/ {
|
|
||||||
proxy_pass http://127.0.0.1:5000;
|
|
||||||
proxy_set_header Host $host;
|
|
||||||
proxy_set_header X-Real-IP $remote_addr;
|
|
||||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
||||||
}
|
|
||||||
|
|
||||||
# 健康检查端点
|
|
||||||
location /health {
|
|
||||||
proxy_pass http://127.0.0.1:5000;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,44 +0,0 @@
|
||||||
# 挪车电话查询系统
|
|
||||||
|
|
||||||
这是一个前后端分离的挪车电话查询系统,使用Python Flask作为后端,SQLite作为数据库,HTML/CSS/JavaScript作为前端。
|
|
||||||
|
|
||||||
## 项目结构
|
|
||||||
|
|
||||||
- `backend/`: 后端代码(Python Flask)
|
|
||||||
- `frontend/`: 前端代码(HTML/CSS/JavaScript)
|
|
||||||
- `data/`: 数据库存放目录
|
|
||||||
|
|
||||||
## 安装与运行
|
|
||||||
|
|
||||||
1. 安装Python依赖:
|
|
||||||
|
|
||||||
|
|
||||||
mvcar/
|
|
||||||
├── backend/
|
|
||||||
│ ├── app.py
|
|
||||||
│ ├── init_db.py
|
|
||||||
│ ├── view_logs.py
|
|
||||||
│ └── requirements.txt
|
|
||||||
├── frontend/
|
|
||||||
│ ├── index.html
|
|
||||||
│ ├── result.html
|
|
||||||
│ ├── css/
|
|
||||||
│ └── js/
|
|
||||||
├── data/
|
|
||||||
├── logs/
|
|
||||||
├── Dockerfile
|
|
||||||
├── nginx.conf
|
|
||||||
├── start.sh
|
|
||||||
└── docker-compose.yml (可选)
|
|
||||||
|
|
||||||
# 构建Docker镜像
|
|
||||||
docker build -t mvcar-app .
|
|
||||||
|
|
||||||
# 构建Docker镜像
|
|
||||||
docker build -t mvcar-app:v1.0.0 -t mvcar-app:latest .
|
|
||||||
|
|
||||||
# 运行容器
|
|
||||||
docker run -d -p 80:80 --name mvcar-container mvcar-app
|
|
||||||
|
|
||||||
# 或者使用docker-compose(如果创建了docker-compose.yml)
|
|
||||||
docker-compose up --build -d
|
|
||||||
|
|
@ -1,8 +0,0 @@
|
||||||
#!/bin/bash
|
|
||||||
|
|
||||||
# 启动后端Flask应用
|
|
||||||
cd /app/backend
|
|
||||||
nohup python app.py > /app/logs/flask.log 2>&1 &
|
|
||||||
|
|
||||||
# 启动nginx
|
|
||||||
nginx -g "daemon off;"
|
|
||||||
|
|
@ -1,2 +0,0 @@
|
||||||
@echo off
|
|
||||||
start "" code .
|
|
||||||
Loading…
Reference in New Issue