This commit is contained in:
2025-09-23 08:17:10 +08:00
parent 0cae9749a8
commit 89c79bdaa1
11 changed files with 768 additions and 306 deletions
+115
View File
@@ -0,0 +1,115 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
为query_history表添加user_id字段,并设置为users表id字段的外键
"""
import sqlite3
import logging
# 配置日志
handlers = [logging.FileHandler('db_update.log'), logging.StreamHandler()]
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s',
handlers=handlers
)
logger = logging.getLogger(__name__)
# 数据库文件路径
db_path = 'car_info.db'
def main():
"""
主函数:连接数据库并为query_history表添加user_id字段
"""
logger.info("开始为query_history表添加user_id字段")
try:
# 连接数据库
conn = sqlite3.connect(db_path)
cursor = conn.cursor()
# 检查query_history表是否已经存在user_id字段
cursor.execute("PRAGMA table_info(query_history)")
columns = cursor.fetchall()
has_user_id = any(column[1] == 'user_id' for column in columns)
if has_user_id:
logger.info("query_history表已存在user_id字段,无需添加")
else:
# 添加user_id字段(先不添加外键约束,确保字段可以添加成功)
logger.info("开始添加user_id字段")
cursor.execute("ALTER TABLE query_history ADD COLUMN user_id INTEGER")
conn.commit()
logger.info("user_id字段添加成功")
# 尝试添加外键约束
try:
logger.info("开始添加外键约束")
cursor.execute("""
ALTER TABLE query_history
ADD CONSTRAINT fk_query_history_user_id
FOREIGN KEY (user_id) REFERENCES users(id)
""")
conn.commit()
logger.info("外键约束添加成功")
except sqlite3.Error as e:
# 在SQLite中,某些版本不支持通过ALTER TABLE直接添加外键约束
# 如果添加外键约束失败,记录警告信息并继续
logger.warning(f"添加外键约束失败: {str(e)}")
logger.warning("将创建临时表并重新创建query_history表来添加外键约束")
# 创建临时表
cursor.execute("""
CREATE TABLE query_history_temp (
id INTEGER PRIMARY KEY AUTOINCREMENT,
department TEXT NOT NULL,
query_date TEXT NOT NULL,
plate_number TEXT,
phone TEXT,
results_count INTEGER NOT NULL,
query_ip TEXT NOT NULL,
user_id INTEGER,
FOREIGN KEY (user_id) REFERENCES users(id)
)
""")
# 复制数据到临时表
cursor.execute("""
INSERT INTO query_history_temp (
id, department, query_date, plate_number,
phone, results_count, query_ip
)
SELECT
id, department, query_date, plate_number,
phone, results_count, query_ip
FROM query_history
""")
# 删除原表
cursor.execute("DROP TABLE query_history")
# 重命名临时表
cursor.execute("ALTER TABLE query_history_temp RENAME TO query_history")
# 提交更改
conn.commit()
logger.info("通过重新创建表的方式成功添加外键约束")
# 关闭连接
conn.close()
logger.info("操作完成")
except Exception as e:
logger.error(f"操作过程中发生错误: {str(e)}")
if 'conn' in locals() and conn:
conn.rollback()
conn.close()
if __name__ == '__main__':
main()
+3 -3
View File
@@ -73,7 +73,7 @@ def get_pending_queries():
# 查询query_history表中results_count=-1的所有数据 # 查询query_history表中results_count=-1的所有数据
cursor.execute(""" cursor.execute("""
SELECT id, department, query_date, plate_number, phone, results_count, query_ip SELECT id, query_date, plate_number, phone, results_count, query_ip, user_id
FROM query_history FROM query_history
WHERE results_count = -1 WHERE results_count = -1
ORDER BY query_date DESC ORDER BY query_date DESC
@@ -90,12 +90,12 @@ def get_pending_queries():
for row in rows: for row in rows:
pending_query = { pending_query = {
'id': row['id'], 'id': row['id'],
'department': row['department'],
'query_date': row['query_date'], 'query_date': row['query_date'],
'plate_number': row['plate_number'], 'plate_number': row['plate_number'],
'phone': row['phone'], 'phone': row['phone'],
'results_count': row['results_count'], 'results_count': row['results_count'],
'query_ip': row['query_ip'] 'query_ip': row['query_ip'],
'user_id': row['user_id'] if 'user_id' in row.keys() else None
} }
pending_queries.append(pending_query) pending_queries.append(pending_query)
+26 -98
View File
@@ -1,7 +1,6 @@
from flask import Flask, render_template, request, redirect, url_for, session, jsonify from flask import Flask, render_template, request, redirect, url_for, session, jsonify
from datetime import datetime, timedelta from datetime import datetime, timedelta
import os import os
import random
from flask import Flask, render_template, request, redirect, url_for, session from flask import Flask, render_template, request, redirect, url_for, session
# 导入用户模块 # 导入用户模块
@@ -58,87 +57,6 @@ def init_db():
) )
''') ''')
# 插入一些测试车辆数据
try:
test_cars = [
('京A12345', '13800138001', '110101199001011234', '张三', 'zhangsan@example.com', '北京市朝阳区'),
('沪B54321', '13900139001', '310101199001012345', '李四', 'lisi@example.com', '上海市静安区'),
('粤C67890', '13700137001', '440401199001013456', '王五', 'wangwu@example.com', '广东省珠海市'),
('苏D12345', '13600136001', '320401199001014567', '赵六', 'zhaoliu@example.com', '江苏省常州市'),
('浙E54321', '13500135001', '330501199001015678', '钱七', 'qianqi@example.com', '浙江省湖州市')
]
cursor.executemany("INSERT INTO car_owners (plate_number, phone, id_card, name, email, address) VALUES (?, ?, ?, ?, ?, ?)", test_cars)
except Exception as e:
# 数据已存在或其他错误,忽略
print(f"插入测试数据时出错: {e}")
# 单独处理查询历史演示数据,确保每次都能插入20条记录
try:
# 先删除现有的演示数据
cursor.execute("DELETE FROM query_history WHERE department IN ('IT部', '财务部', '人事部', '市场部', '行政部')")
# 生成最近20天的随机查询历史
provinces = ['', '', '', '', '', '', '', '', '', '']
cities = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'J', 'K']
departments = ['IT部', '财务部', '人事部', '市场部', '行政部']
# 获取现有的车辆信息,用于生成能匹配的查询历史
cursor.execute("SELECT plate_number, phone FROM car_owners")
existing_cars = cursor.fetchall()
for i in range(20):
# 生成随机日期(最近20天内)
days_ago = random.randint(0, 19)
date = (datetime.now() - timedelta(days=days_ago)).strftime('%Y-%m-%d %H:%M:%S')
# 70%的概率使用已存在的车辆信息,30%的概率生成随机信息
use_existing = random.random() < 0.7
if use_existing and existing_cars:
# 使用已存在的车辆信息
car_info = random.choice(existing_cars)
if random.choice([True, False]):
plate_number = car_info[0]
phone = None
else:
plate_number = None
phone = car_info[1]
# 确保结果数量正确
if plate_number:
cursor.execute("SELECT COUNT(*) FROM car_owners WHERE plate_number = ?", (plate_number,))
results_count = cursor.fetchone()[0]
else:
cursor.execute("SELECT COUNT(*) FROM car_owners WHERE phone = ?", (phone,))
results_count = cursor.fetchone()[0]
else:
# 随机生成车牌号或手机号
use_plate = random.choice([True, False])
if use_plate:
plate_number = f"{random.choice(provinces)}{random.choice(cities)}{random.randint(100000, 999999)}"
phone = None
else:
plate_number = None
phone = f"1{random.randint(3000000000, 9999999999)}"
# 检查是否存在匹配结果
if plate_number:
cursor.execute("SELECT COUNT(*) FROM car_owners WHERE plate_number = ?", (plate_number,))
results_count = cursor.fetchone()[0]
else:
cursor.execute("SELECT COUNT(*) FROM car_owners WHERE phone = ?", (phone,))
results_count = cursor.fetchone()[0]
# 随机部门
department = random.choice(departments)
# 生成随机IP地址
query_ip = f"192.168.{random.randint(0, 255)}.{random.randint(1, 254)}"
cursor.execute(
"INSERT INTO query_history (department, query_date, plate_number, phone, results_count, query_ip) VALUES (?, ?, ?, ?, ?, ?)",
(department, date, plate_number, phone, results_count, query_ip)
)
except Exception as e:
print(f"插入查询历史数据时出错: {e}")
try: try:
conn.commit() conn.commit()
except Exception as e: except Exception as e:
@@ -184,15 +102,16 @@ def query():
# 获取客户端IP地址 # 获取客户端IP地址
query_ip = request.remote_addr query_ip = request.remote_addr
# 从用户信息表获取部门信息 # 从用户信息表获取用户ID
user_id = None
try: try:
# 可以复用同一个连接,不需要创建新连接 # 可以复用同一个连接,不需要创建新连接
cursor.execute("SELECT department FROM users WHERE username = ?", (session['username'],)) cursor.execute("SELECT id FROM users WHERE username = ?", (session['username'],))
user_dept = cursor.fetchone() user_info = cursor.fetchone()
if user_dept: if user_info:
department = user_dept[0] user_id = user_info[0]
except Exception as e: except Exception as e:
print(f"获取部门信息时出错: {e}") print(f"获取用户信息时出错: {e}")
# 记录查询历史到数据库 # 记录查询历史到数据库
try: try:
@@ -207,8 +126,8 @@ def query():
results_count = len(results) results_count = len(results)
cursor.execute( cursor.execute(
"INSERT INTO query_history (department, query_date, plate_number, phone, results_count, query_ip) VALUES (?, ?, ?, ?, ?, ?)", "INSERT INTO query_history (query_date, plate_number, phone, results_count, query_ip, user_id) VALUES (?, ?, ?, ?, ?, ?)",
(department, query_date, plate_number if plate_number else None, phone if phone else None, results_count, query_ip) (query_date, plate_number if plate_number else None, phone if phone else None, results_count, query_ip, user_id)
) )
conn.commit() conn.commit()
except Exception as e: except Exception as e:
@@ -300,7 +219,14 @@ def get_recent_queries():
cursor = conn.cursor() cursor = conn.cursor()
try: try:
cursor.execute("SELECT id, department, query_date, plate_number, phone, results_count, query_ip FROM query_history ORDER BY query_date DESC LIMIT 20") # 使用JOIN操作获取用户姓名和部门信息
cursor.execute("""
SELECT qh.id, qh.query_date, qh.plate_number, qh.phone, qh.results_count, qh.query_ip, qh.user_id,
u.name AS user_name, u.department AS user_department
FROM query_history qh
LEFT JOIN users u ON qh.user_id = u.id
ORDER BY qh.query_date DESC LIMIT 20
""")
recent_queries = cursor.fetchall() recent_queries = cursor.fetchall()
# 格式化查询历史数据 # 格式化查询历史数据
@@ -308,12 +234,14 @@ def get_recent_queries():
for q in recent_queries: for q in recent_queries:
formatted_recent_queries.append({ formatted_recent_queries.append({
'id': q[0], 'id': q[0],
'department': q[1], 'query_date': q[1],
'query_date': q[2], 'plate_number': q[2] if q[2] else '未提供',
'plate_number': q[3] if q[3] else '未提供', 'phone': q[3] if q[3] else '未提供',
'phone': q[4] if q[4] else '未提供', 'results_count': q[4],
'results_count': q[5], 'query_ip': q[5],
'query_ip': q[6] 'user_id': q[6] if len(q) > 6 else None,
'user_name': q[7] if len(q) > 7 else '未知',
'user_department': q[8] if len(q) > 8 else '未知'
}) })
return formatted_recent_queries return formatted_recent_queries
@@ -340,7 +268,7 @@ if __name__ == '__main__':
try: try:
init_db() init_db()
app.run(debug=True) app.run(debug=True,host="0.0.0.0",port=99)
finally: finally:
# 确保在程序退出时关闭数据库连接 # 确保在程序退出时关闭数据库连接
close_global_connection() close_global_connection()
BIN
View File
Binary file not shown.
+252
View File
@@ -0,0 +1,252 @@
from DrissionPage._base.chromium import Chromium
from DrissionPage._configs.chromium_options import ChromiumOptions
from DrissionPage._pages.mix_tab import MixTab
from cssselect.parser import parse_simple_selector
from config import ConfigManager # 新增导入
import time
class Sinopec_ewcc(object):
tab:MixTab
# browser:Chromium
def __init__(self):
self.url = 'https://ewcc.sinopec.com'
self.url_info= 'ewcc.sinopec'
self.browser_path= r'C:\Program Files (x86)\Microsoft\Edge\Application\msedge.exe'
self.co = ChromiumOptions().set_browser_path(self.browser_path)
self.co.ignore_certificate_errors(True)
self.browser=Chromium(self.co)
self.config_manager = ConfigManager() # 新增配置管理器实例
self.ewcc_config = self.config_manager.app_config.get('ewcc', {}) # 修改为通过类访问
def quit(self ):
self.browser.quit()
def load_cookies(self):
cookies = self.ewcc_config.get('cookies', {})
print('使用保存的cookies登录')
for cookie in cookies:
self.browser.set.cookies(cookie)
### print(cookie)
def save_cookies(self):
# 保存当前 cookie
self.tab.refresh()
cookies = self.tab.cookies()
### print(cookies)
self.ewcc_config['cookies'] = cookies
self.ewcc_config['savetime'] = time.time()
print("--------------------")
self.config_manager.app_config['ewcc'] = self.ewcc_config # 修改为通过类访问
self.config_manager.save_config() # 修改为调用类方法
def test(self ):
# print(self.browser as Chromium.version )
pass
def open_web(self,url=None):
if url is None:
url=self.url
self.browser.get_tab(url)
def new_tab(self,url=None):
if url is None:
url=self.url
self.tab=self.browser.new_tab(url)
def find_tab(self,url=None,title=None):
if url is None:
url=self.url
if title is None:
title=self.url_info
try:
self.tab= self.browser.get_tab(url=url)
print("根据 url 找到标签")
except:
print("根据 url 没有找到标签")
self.tab=None
if not self.tab:
try:
self.tab=self.browser.get_tab(title=title)
print("根据 标题名 找到 标签")
except :
print("根据 标题名 没有找到标签")
self.tab=None
return self.tab
def is_login(self):
if self.tab is None:
return False
time.sleep(2)
cur_login=False
try:
e=None
e=self.tab.ele("#userName")
print("找到登录成功的标志"+e.text)
cur_login=True
except Exception as e:
print('没有找到登录成功的标志')
print(f'{e}')
cur_login = False
return cur_login
# 判断是否登录 ,如果没有登录 ,则提示十次,间断5秒
i = 1
time.sleep(2)
while i <= 10:
cur_login = False
try:
e = None
e = self.tab.ele('@@tag()=span@@text()^忘记密码')
### print(e.value)
print(f'需要登录 <{i}> :')
time.sleep(5)
except Exception as e:
print('已经登录')
print(f'{e}')
cur_login = True
if cur_login:
break
i += 1
if not cur_login:
print('登录 失败,强制退出。')
return cur_login
def login(self,username=None,userpass=None,otp=None):
self.tab.get(self.url)
self.tab.wait.eles_loaded('xpath://*[@id="authen1Form"]/button')
time.sleep(5)
try:
ele_div=self.tab.ele('#tab3Con')
ele_div.ele('#fs3_username').set.value(self.ewcc_config.get('username','pengf332.hnsy'))
time.sleep(1)
ele_div.ele('#fs3_password').set.value(self.ewcc_config.get('password','Pf7842158c'))
time.sleep(1)
ele_div.ele('#fs3_otpOrSms').set.value(otp)
time.sleep(1)
ele_div.ele('@type=submit').click()
except Exception as e:
print(e)
time.sleep(3)
### 判断是否有仍然发生的界面
try:
ele_process = self.tab.ele('#proceed-button')
ele_process.click()
except Exception as e:
print(e)
time.sleep(3)
return True
def show_login(self):
self.tab.get(self.url)
self.tab.wait.eles_loaded('xpath://*[@id="authen1Form"]/button')
time.sleep(3)
try:
ele_div=self.tab.ele('#tab3Con')
ele_div.ele('#fs3_username').set.value(self.ewcc_config.get('username','pengf332.hnsy'))
ele_div.ele('#fs3_password').set.value(self.ewcc_config.get('password','Pf7842158b'))
ele_div.ele('#fs3_otpOrSms').set.value(self.ewcc_config.get('otpOrSms','129727'))
# ele_div.ele('@type=submit').click()
except Exception as e:
print(e)
time.sleep(60)
return True
def send_message(self):
pass
def query_car_info(self,plate_number=None,phone_number=None,additional_info=None):
self.tab.ele("@@tag()=li@@title=订单中心").click()
time.sleep(2)
self.tab.ele("@@tag()=a@@name=客户信息查询").click()
time.sleep(2)
myframe = self.tab.get_frame("iframeSOC_CSR_DETAIL")
### print(myframe.inner_html)
time.sleep(2)
### print("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")
ele = myframe.ele("#searchType")
### print(ele.inner_html)
myframe.ele("@@tag()=select@@id=searchType").select.by_text("车牌号")
myframe.ele('#searchValue').input(plate_number, clear=True)
myframe.listen.start('acc/getAccCsrInfo')
myframe.ele("@text()=查询").click()
DataPacket = myframe.listen.wait()
### print(DataPacket.response.body)
###print("ccccccccccccccccccccccccccccccccccccccc")
rtn={}
if DataPacket.response.body['success']:
total_num = DataPacket.response.body['data']['total']
# rtn['length']=total_num
# rtn['test']='aaa'
rtn=[]
eles = myframe.eles("@@name=客户信息详情界面@@text()=详情")
for ele in eles:
sele = self.tab.ele("@@text()^客户信息查询@@class:J_menuTab")
if sele:
sele.click()
self.tab.listen.start('getAccCsrInfoByCsrid')
ele.click()
DataPacket = self.tab.listen.wait()
### print(DataPacket.response.body)
rtn.append(DataPacket.response.body['data'])
self.tab.listen.stop()
### print(ele.inner_html)
return rtn
if __name__ == "__main__":
ewcc = Sinopec_ewcc()
# 1 查找现有的标签
print('<1> 查找 现有的标签')
ewcc.find_tab() # 新增:查找标签页
# 2 如果没有登录 ,则使用cookie登录
if not ewcc.is_login():
print('<2> 找不到对应的网站,使用cookies尝试登录')
ewcc.load_cookies()
ewcc.new_tab()
# 3 判断是否登录,如果登录不成功,则使用帐号和密码登录
if not ewcc.is_login():
print('<3> 找不对应的网站,使用帐号密码登录')
# 这里要注意12小时才可以登录一次。
# 这里显示登录界面,并等待输入一分钟
ewcc.show_login()
# 4 判断是否登录 ,如果失败,则发送信息到微信上
if ewcc.is_login():
ewcc.tab.refresh()
ewcc.save_cookies()
print(" 保存cookies")
else:
ewcc.send_message()
# ewcc.query_car_info()
ewcc.quit()
+163
View File
@@ -0,0 +1,163 @@
import requests
import time
import logging
# 配置日志
handlers = [logging.FileHandler('query_processor.log'), logging.StreamHandler()]
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s',
handlers=handlers
)
logger = logging.getLogger(__name__)
# API基础URL
BASE_URL = 'http://localhost:99'
# 演示数据处理函数
def process_query_data(query_data, parameters):
"""
处理查询数据的演示函数
这里使用演示代码代替实际的数据处理逻辑
"""
# 获取用户ID,如果存在
user_id = query_data.get('user_id', '未知')
logger.info(f"处理查询数据: ID={query_data['id']}, 车牌号={query_data['plate_number']}, 用户ID={user_id}")
# 这里是演示处理逻辑
# 在实际应用中,这里应该是真实的数据处理代码
logger.info(f"使用参数进行处理: {parameters}")
# 模拟处理过程
time.sleep(2) # 模拟处理耗时
# 返回模拟的处理结果(简单地将results_count设置为随机数1-5
# 实际应用中,应该根据真实处理结果返回相应的值
results_count = 3 # 演示用的固定结果数
logger.info(f"处理完成,结果数量: {results_count}")
return results_count
# 读取参数配置
def get_parameters():
"""
从后端API读取参数配置
"""
try:
url = f'{BASE_URL}/api/parameters'
response = requests.get(url)
response.raise_for_status()
data = response.json()
if data['success']:
logger.info(f"成功获取参数配置: {data['data']}")
return data['data']
else:
logger.error(f"获取参数配置失败: {data['error']}")
return None
except Exception as e:
logger.error(f"获取参数配置时发生错误: {str(e)}")
return None
# 获取待处理查询
def get_pending_queries():
"""
从后端API获取待处理查询
"""
try:
url = f'{BASE_URL}/api/pending-queries'
response = requests.get(url)
response.raise_for_status()
data = response.json()
if data['success']:
logger.info(f"成功获取待处理查询,共 {data['count']}")
return data['data']
else:
logger.error(f"获取待处理查询失败: {data['error']}")
return []
except Exception as e:
logger.error(f"获取待处理查询时发生错误: {str(e)}")
return []
# 更新查询结果
def update_query_result(query_id, results_count):
"""
更新查询结果到后端API
"""
try:
url = f'{BASE_URL}/api/update-query-result'
data = {
'id': query_id,
'results_count': results_count
}
response = requests.post(url, json=data)
response.raise_for_status()
result = response.json()
if result['success']:
logger.info(f"成功更新查询结果: ID={query_id}, 结果数量={results_count}")
return True
else:
logger.error(f"更新查询结果失败: {result['error']}")
return False
except Exception as e:
logger.error(f"更新查询结果时发生错误: {str(e)}")
return False
# 主处理函数
def main():
logger.info("查询处理代码启动")
# 获取参数配置
parameters = get_parameters()
if not parameters:
logger.warning("无法获取参数配置,使用默认参数继续")
# 使用默认参数继续
parameters = {
'param1': 'default1',
'param2': 'default2',
'param3': 'default3',
'param4': 'default4',
'param5': 'default5'
}
# 主循环
while True:
try:
# 获取待处理查询
pending_queries = get_pending_queries()
# 处理每条查询
for query in pending_queries:
# 处理查询数据
results_count = process_query_data(query, parameters)
# 更新查询结果
update_query_result(query['id'], results_count)
# 等待一段时间后再次检查
wait_time = 60 # 默认等待60秒
# 如果有parameters参数,可以根据参数调整等待时间
if parameters and 'polling_interval' in parameters:
try:
wait_time = int(parameters['polling_interval'])
except (ValueError, TypeError):
logger.warning(f"无效的轮询间隔参数: {parameters['polling_interval']}")
logger.info(f"本次处理完成,等待 {wait_time} 秒后再次检查")
time.sleep(wait_time)
except KeyboardInterrupt:
logger.info("查询处理器被用户中断")
break
except Exception as e:
logger.error(f"处理过程中发生错误: {str(e)}")
# 发生错误时,等待更短的时间后重试
time.sleep(30)
logger.info("查询处理器停止")
if __name__ == '__main__':
main()
+69
View File
@@ -0,0 +1,69 @@
import sqlite3
from db_utils import get_db_connection, close_db_connection
def remove_department_from_query_history():
"""
从query_history表中删除department字段
SQLite不支持直接删除列,采用以下步骤:
1. 创建一个不包含department字段的新表
2. 将原始表中的数据(除department外)复制到新表
3. 删除原始表
4. 重命名新表为原始表名
5. 重新创建索引和外键约束
"""
conn = None
try:
# 获取数据库连接
conn = get_db_connection()
cursor = conn.cursor()
print("开始从query_history表中删除department字段...")
# 1. 创建临时表,不包含department字段
print("1. 创建临时表query_history_new...")
cursor.execute('''
CREATE TABLE IF NOT EXISTS query_history_new (
id INTEGER PRIMARY KEY AUTOINCREMENT,
query_date TEXT NOT NULL,
plate_number TEXT,
phone TEXT,
results_count INTEGER NOT NULL,
query_ip TEXT NOT NULL,
user_id INTEGER,
FOREIGN KEY (user_id) REFERENCES users (id)
)
''')
# 2. 复制数据到新表,排除department字段
print("2. 复制数据到新表,排除department字段...")
cursor.execute('''
INSERT INTO query_history_new (id, query_date, plate_number, phone, results_count, query_ip, user_id)
SELECT id, query_date, plate_number, phone, results_count, query_ip, user_id
FROM query_history
''')
# 3. 删除原表
print("3. 删除原始表query_history...")
cursor.execute('DROP TABLE IF EXISTS query_history')
# 4. 重命名新表为原表名
print("4. 重命名新表为query_history...")
cursor.execute('ALTER TABLE query_history_new RENAME TO query_history')
# 5. 提交事务
conn.commit()
print("操作完成:department字段已成功从query_history表中删除!")
except sqlite3.Error as e:
print(f"数据库操作出错:{e}")
if conn:
conn.rollback()
raise
finally:
if conn:
close_db_connection(conn)
if __name__ == "__main__":
remove_department_from_query_history()
+5
View File
@@ -0,0 +1,5 @@
# Flask应用依赖
flask
# 查询处理器依赖
requests
-15
View File
@@ -1,15 +0,0 @@
# 设置执行策略以允许运行脚本(仅在首次运行时需要取消注释)
# Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope Process
# 激活虚拟环境
& '.\venv\Scripts\Activate.ps1'
# 设置Flask应用程序环境变量
$env:FLASK_APP = "app.py"
$env:FLASK_ENV = "development"
# 启动Flask服务器,允许外部访问(使用python -m flask确保命令可用)
python -m flask run --host=0.0.0.0
# 保持窗口打开
Read-Host "Press Enter to exit"
-14
View File
@@ -1,14 +0,0 @@
import os
import subprocess
# 设置Flask应用程序环境变量
os.environ['FLASK_APP'] = 'app.py'
os.environ['FLASK_ENV'] = 'development'
print("Starting Flask server...")
print("Server will be available at http://localhost:5000/")
print("To allow external access, it's listening on all interfaces (0.0.0.0)")
print("Press Ctrl+C to stop the server")
# 启动Flask服务器,允许外部访问
subprocess.run(['python', '-m', 'flask', 'run', '--host=0.0.0.0'])
+135 -176
View File
@@ -91,6 +91,7 @@
<th>车牌号</th> <th>车牌号</th>
<th>手机号</th> <th>手机号</th>
<th>状态</th> <th>状态</th>
<th>登录用户</th>
<th>登录部门</th> <th>登录部门</th>
<th>登录IP</th> <th>登录IP</th>
</tr> </tr>
@@ -118,7 +119,8 @@
{% endif %} {% endif %}
</span> </span>
</td> </td>
<td>{{ query.department }}</td> <td>{{ query.user_name }}</td>
<td>{{ query.user_department }}</td>
<td>{{ query.query_ip }}</td> <td>{{ query.query_ip }}</td>
</tr> </tr>
{% endfor %} {% endfor %}
@@ -136,19 +138,7 @@
<input type="hidden" id="add_plate_number"> <input type="hidden" id="add_plate_number">
<input type="hidden" id="add_phone"> <input type="hidden" id="add_phone">
<!-- 详情模态框 --> <!-- 详情弹窗已使用layui动态生成,此处不再需要静态模态框 -->
<div id="detailModal" class="fixed inset-0 bg-black/50 z-50 hidden flex items-center justify-center p-4">
<div class="bg-white rounded-lg shadow-xl max-w-4xl w-full max-h-[90vh] overflow-y-auto">
<div class="p-6" id="modalContent">
<!-- 结果列表 -->
<div id="resultList" class="hidden">
<div class="grid grid-cols-1 md:grid-cols-2 gap-6" id="carOwnerCards">
<!-- 车辆信息卡片会动态添加到这里 -->
</div>
</div>
</div>
</div>
</div>
{% include 'footer.html' %} {% include 'footer.html' %}
@@ -158,25 +148,23 @@
document.addEventListener('DOMContentLoaded', function() { document.addEventListener('DOMContentLoaded', function() {
// 获取DOM元素 // 获取DOM元素
var addQueryBtn = document.getElementById('addQueryBtn'); var addQueryBtn = document.getElementById('addQueryBtn');
var addQueryModal = document.getElementById('addQueryModal');
var closeAddModal = document.getElementById('closeAddModal');
var cancelAddBtn = document.getElementById('cancelAddBtn');
var addQueryForm = document.getElementById('addQueryForm');
var addQueryLoading = document.getElementById('addQueryLoading');
var detailModal = document.getElementById('detailModal');
var closeModal = document.getElementById('closeModal');
var loadingState = document.getElementById('loadingState');
var noResultState = document.getElementById('noResultState');
var resultList = document.getElementById('resultList');
var carOwnerCards = document.getElementById('carOwnerCards');
// 为查询历史记录行添加击事件 // 为查询历史记录行添加击事件
var rows = document.querySelectorAll('tr[data-history-id]'); var rows = document.querySelectorAll('tr[data-history-id]');
for (var i = 0; i < rows.length; i++) { for (var i = 0; i < rows.length; i++) {
rows[i].addEventListener('click', function() { rows[i].addEventListener('click', function() {
var historyId = this.getAttribute('data-history-id'); var historyId = this.getAttribute('data-history-id');
loadHistoryDetail(historyId); loadHistoryDetail(historyId);
}); });
// 为了提升用户体验,添加鼠标悬停效果
rows[i].style.cursor = 'pointer';
rows[i].style.transition = 'background-color 0.2s ease';
rows[i].addEventListener('mouseenter', function() {
this.style.backgroundColor = '#f5f7fa';
});
rows[i].addEventListener('mouseleave', function() {
this.style.backgroundColor = '';
});
} }
// 增加查询按钮点击事件 // 增加查询按钮点击事件
@@ -396,48 +384,21 @@
}); });
}); });
// 移除对已删除元素的引用
// 点击详情模态框背景关闭 // 详情弹窗已使用layui实现,点击背景关闭功能由layui自动处理
detailModal.addEventListener('click', function(e) {
if (e.target === detailModal) {
// 兼容classList API
if (detailModal.classList) {
detailModal.classList.add('hidden');
} else {
detailModal.className += ' hidden';
}
}
});
// 表单提交逻辑已整合到layui弹窗中,不需要单独的表单提交事件 // 表单提交逻辑已整合到layui弹窗中,不需要单独的表单提交事件
// 加载查询历史详情 // 加载查询历史详情
function loadHistoryDetail(historyId) { function loadHistoryDetail(historyId) {
// 显示模态框和加载状态 // 创建加载中的弹窗
// 兼容classList API const loadingIndex = layer.load(2, {
if (detailModal.classList) { shade: [0.3, '#fff'],
detailModal.classList.remove('hidden'); content: '<div style="padding: 20px;"><i class="fa fa-spinner fa-spin mr-2"></i>正在加载详情...</div>',
} else { shadeClose: false
detailModal.className = detailModal.className.replace('hidden', ''); });
}
if (loadingState.classList) {
loadingState.classList.remove('hidden');
} else {
loadingState.className = loadingState.className.replace('hidden', '');
}
if (noResultState.classList) {
noResultState.classList.add('hidden');
} else {
noResultState.className += ' hidden';
}
if (resultList.classList) {
resultList.classList.add('hidden');
} else {
resultList.className += ' hidden';
}
carOwnerCards.innerHTML = '';
// 发送AJAX请求获取详情 // 发送AJAX请求获取详情
fetch('/query_history_detail/' + historyId) fetch('/query_history_detail/' + historyId)
@@ -445,99 +406,135 @@
return response.json(); return response.json();
}) })
.then(function(data) { .then(function(data) {
// 隐藏加载状态 // 关闭加载弹窗
// 兼容classList API layer.close(loadingIndex);
if (loadingState.classList) {
loadingState.classList.add('hidden'); // 创建详情弹窗内容
} else { let content = '';
loadingState.className += ' hidden';
// 转义特殊字符以防止XSS攻击
function escapeHtml(text) {
const div = document.createElement('div');
div.textContent = text || '';
return div.innerHTML;
} }
if (data.error) { if (data.error) {
// 显示错误信息 // 显示错误信息
noResultState.querySelector('p').textContent = data.error; content = `
// 兼容classList API <div class="layui-text" style="text-align: center; padding: 60px 40px;">
if (noResultState.classList) { <div style="display: inline-flex; align-items: center; justify-content: center; width: 80px; height: 80px; background-color: #fef0f0; border-radius: 50%; margin-bottom: 20px;">
noResultState.classList.remove('hidden'); <i class="fa fa-exclamation-circle text-red-500 text-4xl"></i>
} else { </div>
noResultState.className = noResultState.className.replace('hidden', ''); <p style="color: #666; font-size: 16px;">${data.error}</p>
} </div>
`;
} else if (data.car_owners && data.car_owners.length > 0) { } else if (data.car_owners && data.car_owners.length > 0) {
// 显示结果列表 // 添加查询信息标题
// 兼容classList API content = `
if (resultList.classList) { <div class="layui-text" style="padding: 15px;">
resultList.classList.remove('hidden'); <div class="mb-4 pb-3 border-b border-gray-200">
} else {
resultList.className = resultList.className.replace('hidden', ''); </div>
} <div class="space-y-4" id="carOwnerCards">
`;
// 添加车辆信息卡片 // 添加车辆信息卡片
for (var i = 0; i < data.car_owners.length; i++) { for (var i = 0; i < data.car_owners.length; i++) {
var carOwner = data.car_owners[i]; var carOwner = data.car_owners[i];
var card = document.createElement('div');
card.className = 'bg-white border border-gray-200 rounded-lg p-5 shadow-sm hover:shadow-md transition-shadow duration-200';
// 使用字符串拼接替代模板字符串 content += `
var cardHtml = ''; <div class="bg-white border border-gray-200 rounded-lg shadow-sm">
cardHtml += '<h4 class="text-lg font-medium text-gray-800 mb-4 flex items-center">'; <div class="p-3 grid grid-cols-1 gap-2">
cardHtml += ' <i class="fa fa-car text-primary mr-2"></i>'; <div class="flex items-center p-2">
cardHtml += ' 车辆信息'; <i class="fa fa-id-card-o text-gray-400 w-8 text-center"></i>
cardHtml += '</h4>'; <span class="text-gray-500 w-20">车牌号:</span>
cardHtml += '<div class="space-y-3">'; <span class="font-medium text-gray-800 flex-1">${escapeHtml(carOwner.plate_number)}</span>
cardHtml += ' <div class="flex justify-between items-center">'; </div>
cardHtml += ' <span class="text-gray-500">车牌号:</span>'; <div class="flex items-center p-2">
cardHtml += ' <span class="font-medium text-gray-800">' + (carOwner.plate_number || '') + '</span>'; <i class="fa fa-mobile text-gray-400 w-8 text-center"></i>
cardHtml += ' </div>'; <span class="text-gray-500 w-20">手机号:</span>
cardHtml += ' <div class="flex justify-between items-center">'; <span class="font-medium text-gray-800 flex-1">${escapeHtml(carOwner.phone)}</span>
cardHtml += ' <span class="text-gray-500">手机号:</span>'; </div>
cardHtml += ' <span class="font-medium text-gray-800">' + (carOwner.phone || '') + '</span>'; <div class="flex items-center p-2">
cardHtml += ' </div>'; <i class="fa fa-user text-gray-400 w-8 text-center"></i>
cardHtml += ' <div class="flex justify-between items-center">'; <span class="text-gray-500 w-20">车主姓名:</span>
cardHtml += ' <span class="text-gray-500">车主姓名:</span>'; <span class="font-medium text-gray-800 flex-1">${escapeHtml(carOwner.name)}</span>
cardHtml += ' <span class="font-medium text-gray-800">' + (carOwner.name || '') + '</span>'; </div>
cardHtml += ' </div>'; <div class="flex items-center p-2">
cardHtml += ' <div class="flex justify-between items-center">'; <i class="fa fa-id-card text-gray-400 w-8 text-center"></i>
cardHtml += ' <span class="text-gray-500">身份证号:</span>'; <span class="text-gray-500 w-20">身份证号:</span>
cardHtml += ' <span class="font-medium text-gray-800">' + (carOwner.id_card || '') + '</span>'; <span class="font-medium text-gray-800 flex-1">${escapeHtml(carOwner.id_card)}</span>
cardHtml += ' </div>'; </div>
cardHtml += ' <div class="flex justify-between items-center">'; </div>
cardHtml += ' <span class="text-gray-500">电子邮箱:</span>'; </div>
cardHtml += ' <span class="font-medium text-gray-800">' + (carOwner.email || '未提供') + '</span>'; `;
cardHtml += ' </div>';
cardHtml += ' <div class="flex justify-between items-center">';
cardHtml += ' <span class="text-gray-500">居住地址:</span>';
cardHtml += ' <span class="font-medium text-gray-800">' + (carOwner.address || '未提供') + '</span>';
cardHtml += ' </div>';
cardHtml += '</div>';
card.innerHTML = cardHtml;
carOwnerCards.appendChild(card);
} }
content += `
</div>
</div>
`;
} else { } else {
// 显示无结果状态 // 显示无结果状态
// 兼容classList API content = `
if (noResultState.classList) { <div class="layui-text" style="text-align: center; padding: 60px 40px;">
noResultState.classList.remove('hidden'); <div style="display: inline-flex; align-items: center; justify-content: center; width: 80px; height: 80px; background-color: #f5f7fa; border-radius: 50%; margin-bottom: 20px;">
} else { <i class="fa fa-search text-gray-300 text-4xl"></i>
noResultState.className = noResultState.className.replace('hidden', ''); </div>
} <p style="color: #666; font-size: 16px; margin-bottom: 8px;">暂无车辆信息</p>
<p style="color: #909399; font-size: 14px;">该查询条件下未找到相关车辆信息</p>
</div>
`;
} }
// 打开详情弹窗 - 优化布局和样式
layer.open({
type: 1,
title: '<i class="fa fa-file-text-o"></i> 查询详情',
area: ['700px', 'auto'], // 使用自适应高度
offset: '10%',
shade: [0.3, '#fff'],
shadeClose: true,
anim: 2,
content: content,
btn: ['关闭'],
btnAlign: 'c',
btnClass: ['layui-btn layui-btn-primary'],
skin: 'layui-layer-molv',
success: function(layero, index) {
}
});
}) })
.catch(function(error) { .catch(function(error) {
// 隐藏加载状态,显示错误信息 // 关闭加载弹窗
// 兼容classList API layer.close(loadingIndex);
if (loadingState.classList) {
loadingState.classList.add('hidden'); // 显示错误弹窗
} else { layer.open({
loadingState.className += ' hidden'; type: 1,
} title: '<i class="fa fa-exclamation-circle"></i> 加载失败',
noResultState.querySelector('p').textContent = '加载详情时发生错误'; area: ['360px', 'auto'],
// 兼容classList API offset: '10%',
if (noResultState.classList) { shade: 0.3,
noResultState.classList.remove('hidden'); shadeClose: false,
} else { anim: 2,
noResultState.className = noResultState.className.replace('hidden', ''); content: `
} <div class="layui-text" style="text-align: center; padding: 30px 20px;">
<div style="display: inline-flex; align-items: center; justify-content: center; width: 60px; height: 60px; background-color: #fef0f0; border-radius: 50%; margin-bottom: 15px;">
<i class="fa fa-exclamation-circle text-red-500 text-3xl"></i>
</div>
<p style="color: #666; font-size: 14px;">加载详情时发生错误</p>
<p style="color: #999; font-size: 12px; margin-top: 8px;">请稍后重试</p>
</div>
`,
btn: ['确定'],
btnAlign: 'c',
btnClass: ['layui-btn layui-btn-primary'],
skin: 'layui-layer-molv'
});
console.error('Error loading history detail:', error); console.error('Error loading history detail:', error);
}); });
} }
@@ -561,44 +558,6 @@
}); });
}); });
</script> </script>
<script>
// 全局搜索变量
var searchQuery = '';
</script>
<script>
// ESC键关闭详情弹窗
document.addEventListener('keydown', function(e) {
// 使用keyCode替代key,提高兼容性
var key = e.key || e.keyCode;
var isEscape = key === 'Escape' || key === 'Esc' || key === 27;
var detailModal = document.getElementById('detailModal');
var isHidden = false;
// 兼容classList API
if (detailModal.classList) {
isHidden = detailModal.classList.contains('hidden');
} else {
isHidden = detailModal.className.indexOf('hidden') !== -1;
}
if (isEscape && !isHidden) {
// 兼容classList API
if (detailModal.classList) {
detailModal.classList.add('hidden');
} else {
detailModal.className += ' hidden';
}
}
});
</script>
</body> </body>
</html> </html>